scala/ScalaBook/chapter-09/src/main/scala/scalabook/file/ZipFS.scala

package scalabook.file

import java.util.zip.{
  ZipEntry => JavaZipEntry,
  ZipInputStream => JavaInStream,
  ZipFile => JavaZipFile}

import scala.collection.mutable
import path.Path
import Path._
import VFS.AnyFile

object ZipFS {
  val RootPath = Path.UnixPath("/")
  
  def apply(path: Path): ZipFS = new ZipFS(NativeFile(path))

  def apply(path: String): ZipFS = new ZipFS(NativeFile(path))

  def apply(vfile: AnyFile): ZipFS = new ZipFS(vfile)
}

class ZipFS(source: AnyFile) extends VFS[ZipFile] {
  protected[file] val (nativeZip, path2file) = loadEntries

  private[this] def loadEntries() = {
    val tmpJavaNative = FileUtil.materializeToNative(source).nativeJavaFile
    val nativeZip = new JavaZipFile(tmpJavaNative)
    val path2vfile = new mutable.HashMap[Path, ZipFile]

    var entriesEnum = nativeZip.entries
    while(entriesEnum.hasMoreElements) {
      val nextEntry = entriesEnum.nextElement
      val entryPath = mkpath(nextEntry.getName)
      val zipFile = new ZipFile(this, entryPath, nextEntry)
      path2vfile(entryPath) = zipFile
    }

    // add non-existent folder entries
    val missingPaths = new mutable.HashSet[Path]
    path2vfile.foreach { case (path, _) =>
      def mkAllPaths(path: Path, all: List[Path]): List[Path] = {
        val parent = path.parent
        if(parent.isEmpty)
          path :: all
        else
          mkAllPaths(parent, path :: all)
      }

      mkAllPaths(path, List()).foreach(path => if(!path2vfile.contains(path)) missingPaths(path) = true)
    }

    missingPaths.foreach(path => path2vfile(path) = new SyntheticZipFolder(this, path))
    // add root
    val rootFile = new SyntheticZipFolder(this, ZipFS.RootPath)
    path2vfile(ZipFS.RootPath) = rootFile

    (nativeZip, path2vfile)
  }


  def newTempFile(prefix: String, suffix: String) =
    error("Unsupported operation")

  def name = toString // take advantage of case-class toString
  override def toString = "ZipFS(" + source + ")"

  def mkpath(name: String) = "/".p / name

  def newFile(path: Path) =
    resolve(path).getOrElse(NoFile.as[ZipFile])
  
  override def resolve(path: Path) = path2file.get(path)

  lazy val container = Some(source)
  override def isContained = true

  def roots = List(path2file(ZipFS.RootPath))
}