All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.nawforce.pkgforce.documents.DirectoryTree.scala Maven / Gradle / Ivy

/*
 * Copyright (c) 2023 Certinia Inc. All rights reserved.
 */
package com.nawforce.pkgforce.documents

import com.nawforce.pkgforce.path.PathLike

import scala.collection.mutable

/* Directory tree for maintaining information on state of a file system and refreshing to locate changes.
 * Changes are detected using file modification times. If the underlying filesystem only reports in seconds
 * then the accuracy of what has been changed may be compromised. */
class DirectoryTree private (val path: PathLike) {
  private var fileModTimes   = Array[(String, Long)]()
  private var subDirectories = Array[DirectoryTree]()

  /* Create a refreshed version of this node reporting any changed files.  */
  def refresh(changed: mutable.ArrayBuffer[String]): DirectoryTree = {
    if (path.isDirectory) {
      val (files, directories) = path.splitDirectoryEntries()
      refreshSubdirectories(directories, changed)
      refreshFiles(files, changed)
    } else {
      // If the directory was removed report everything changed
      collectFiles(changed)
      fileModTimes = Array()
      subDirectories = Array()
    }
    this
  }

  def directories: Array[DirectoryTree] = subDirectories

  def fileModificationTimes: Map[String, Long] = fileModTimes.toMap

  private def refreshSubdirectories(
    directoryPaths: Array[PathLike],
    changed: mutable.ArrayBuffer[String]
  ): Unit = {
    // Find deleted & report all files recursively as changed
    val currentDirectories = directoryPaths.toSet
    subDirectories
      .filterNot(sd => currentDirectories.contains(sd.path))
      .foreach(_.collectFiles(changed))

    // Regenerate sub directory list
    val existing = subDirectories.map(d => (d.path, d)).toMap
    subDirectories = directoryPaths
      .map(dir => {
        existing.get(dir) match {
          case None            => DirectoryTree(dir, changed)
          case Some(directory) => directory.refresh(changed)
        }
      })
  }

  private def refreshFiles(
    filePaths: Array[PathLike],
    changed: mutable.ArrayBuffer[String]
  ): Unit = {
    // Find deleted & report as changed
    val currentFiles = filePaths.map(_.toString).toSet
    fileModTimes
      .map(_._1)
      .filterNot(currentFiles.contains)
      .foreach(path => changed.append(path))

    // Regenerate, reporting on any changed mod times
    val existingModTimes = fileModTimes.toMap
    fileModTimes = filePaths.flatMap(path => {
      val modified = path.lastModified()
      modified match {
        case None =>
          changed.append(path.toString)
        case Some(newModTime) if newModTime != existingModTimes.getOrElse(path.toString, 0) =>
          changed.append(path.toString)
        case _ => ()
      }
      modified.map(modTime => (path.toString, modTime))
    })
  }

  /* Collect all file names, recursively */
  private def collectFiles(changed: mutable.ArrayBuffer[String]): Unit = {
    fileModTimes.map(_._1).foreach(path => changed.append(path))
    subDirectories.foreach(_.collectFiles(changed))
  }
}

object DirectoryTree {
  def apply(path: PathLike, changed: mutable.ArrayBuffer[String]): DirectoryTree = {
    val dir = new DirectoryTree(path)
    if (path.isDirectory)
      dir.refresh(changed)
    dir
  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy