Florian Nücke | f2f6d0f | 2013-09-30 21:42:09 +0200 | [diff] [blame] | 1 | package li.cil.oc.server.fs |
| 2 | |
Florian Nücke | 6aa2bd8 | 2013-12-08 07:59:49 +0100 | [diff] [blame] | 3 | import java.io |
| 4 | import java.io.ByteArrayInputStream |
| 5 | import java.util.concurrent.Callable |
Florian Nücke | e35dfb9 | 2014-10-05 16:25:49 +0200 | [diff] [blame] | 6 | import java.util.zip.ZipEntry |
| 7 | import java.util.zip.ZipFile |
Florian Nücke | cf521f4 | 2014-06-19 16:23:26 +0200 | [diff] [blame] | 8 | |
| 9 | import com.google.common.cache.CacheBuilder |
Florian Nücke | 6aa2bd8 | 2013-12-08 07:59:49 +0100 | [diff] [blame] | 10 | import li.cil.oc.OpenComputers |
Florian Nücke | e35dfb9 | 2014-10-05 16:25:49 +0200 | [diff] [blame] | 11 | import li.cil.oc.server.fs.ZipFileInputStreamFileSystem.ArchiveDirectory |
| 12 | import li.cil.oc.server.fs.ZipFileInputStreamFileSystem.ArchiveFile |
Florian Nücke | cf521f4 | 2014-06-19 16:23:26 +0200 | [diff] [blame] | 13 | |
Florian Nücke | f2f6d0f | 2013-09-30 21:42:09 +0200 | [diff] [blame] | 14 | import scala.collection.mutable |
Florian Nücke | 6aa2bd8 | 2013-12-08 07:59:49 +0100 | [diff] [blame] | 15 | import scala.language.postfixOps |
Florian Nücke | f2f6d0f | 2013-09-30 21:42:09 +0200 | [diff] [blame] | 16 | |
Florian Nücke | 6aa2bd8 | 2013-12-08 07:59:49 +0100 | [diff] [blame] | 17 | class ZipFileInputStreamFileSystem(private val archive: ArchiveDirectory) extends InputStreamFileSystem { |
Florian Nücke | 7cf9fad | 2013-10-28 15:16:55 +0100 | [diff] [blame] | 18 | |
| 19 | def spaceTotal = spaceUsed |
| 20 | |
| 21 | def spaceUsed = spaceUsed_ |
| 22 | |
Florian Nücke | 1845ba0 | 2014-02-17 20:04:39 +0100 | [diff] [blame] | 23 | private lazy val spaceUsed_ = ZipFileInputStreamFileSystem.synchronized { |
Florian Nücke | 6aa2bd8 | 2013-12-08 07:59:49 +0100 | [diff] [blame] | 24 | def recurse(d: ArchiveDirectory): Long = d.children.foldLeft(0L)((acc, c) => acc + (c match { |
| 25 | case directory: ArchiveDirectory => recurse(directory) |
| 26 | case file: ArchiveFile => file.size |
| 27 | })) |
| 28 | recurse(archive) |
Florian Nücke | 95e9997 | 2013-10-22 13:01:01 +0200 | [diff] [blame] | 29 | } |
Florian Nücke | 95e9997 | 2013-10-22 13:01:01 +0200 | [diff] [blame] | 30 | |
| 31 | // ----------------------------------------------------------------------- // |
| 32 | |
Florian Nücke | 1845ba0 | 2014-02-17 20:04:39 +0100 | [diff] [blame] | 33 | override def exists(path: String) = ZipFileInputStreamFileSystem.synchronized { |
| 34 | entry(path).isDefined |
Florian Nücke | 9805828 | 2013-10-05 14:17:23 +0200 | [diff] [blame] | 35 | } |
Florian Nücke | f2f6d0f | 2013-09-30 21:42:09 +0200 | [diff] [blame] | 36 | |
Florian Nücke | 1845ba0 | 2014-02-17 20:04:39 +0100 | [diff] [blame] | 37 | override def size(path: String) = ZipFileInputStreamFileSystem.synchronized { |
| 38 | entry(path) match { |
| 39 | case Some(file) if !file.isDirectory => file.size |
| 40 | case _ => 0L |
| 41 | } |
Florian Nücke | 95e9997 | 2013-10-22 13:01:01 +0200 | [diff] [blame] | 42 | } |
Florian Nücke | f2f6d0f | 2013-09-30 21:42:09 +0200 | [diff] [blame] | 43 | |
Florian Nücke | 1845ba0 | 2014-02-17 20:04:39 +0100 | [diff] [blame] | 44 | override def isDirectory(path: String) = ZipFileInputStreamFileSystem.synchronized { |
| 45 | entry(path).exists(_.isDirectory) |
| 46 | } |
| 47 | |
| 48 | def lastModified(path: String) = ZipFileInputStreamFileSystem.synchronized { |
| 49 | entry(path) match { |
| 50 | case Some(file) => file.lastModified |
| 51 | case _ => 0L |
| 52 | } |
| 53 | } |
| 54 | |
| 55 | override def list(path: String) = ZipFileInputStreamFileSystem.synchronized { |
| 56 | entry(path) match { |
| 57 | case Some(entry) if entry.isDirectory => entry.list() |
| 58 | case _ => null |
| 59 | } |
Florian Nücke | f2f6d0f | 2013-09-30 21:42:09 +0200 | [diff] [blame] | 60 | } |
| 61 | |
Florian Nücke | 6aa2bd8 | 2013-12-08 07:59:49 +0100 | [diff] [blame] | 62 | // ----------------------------------------------------------------------- // |
| 63 | |
Florian Nücke | d0d4f50 | 2014-04-20 22:12:00 +0200 | [diff] [blame] | 64 | override protected def openInputChannel(path: String) = ZipFileInputStreamFileSystem.synchronized { |
Florian Nücke | fc96037 | 2014-04-21 13:39:59 +0200 | [diff] [blame] | 65 | entry(path).map(entry => new InputStreamChannel(entry.openStream())) |
Florian Nücke | 1845ba0 | 2014-02-17 20:04:39 +0100 | [diff] [blame] | 66 | } |
Florian Nücke | 90bbdc2 | 2013-10-02 15:11:49 +0200 | [diff] [blame] | 67 | |
Florian Nücke | 95e9997 | 2013-10-22 13:01:01 +0200 | [diff] [blame] | 68 | // ----------------------------------------------------------------------- // |
| 69 | |
Florian Nücke | 6aa2bd8 | 2013-12-08 07:59:49 +0100 | [diff] [blame] | 70 | private def entry(path: String) = { |
| 71 | val cleanPath = "/" + path.replace("\\", "/").replace("//", "/").stripPrefix("/").stripSuffix("/") |
| 72 | if (cleanPath == "/") Some(archive) |
| 73 | else archive.find(cleanPath.split("/")) |
Florian Nücke | f2f6d0f | 2013-09-30 21:42:09 +0200 | [diff] [blame] | 74 | } |
Florian Nücke | f2f6d0f | 2013-09-30 21:42:09 +0200 | [diff] [blame] | 75 | } |
Florian Nücke | 6aa2bd8 | 2013-12-08 07:59:49 +0100 | [diff] [blame] | 76 | |
| 77 | object ZipFileInputStreamFileSystem { |
| 78 | private val cache = com.google.common.cache.CacheBuilder.newBuilder(). |
| 79 | weakValues(). |
| 80 | asInstanceOf[CacheBuilder[String, ArchiveDirectory]]. |
| 81 | build[String, ArchiveDirectory]() |
| 82 | |
Florian Nücke | 1845ba0 | 2014-02-17 20:04:39 +0100 | [diff] [blame] | 83 | def fromFile(file: io.File, innerPath: String) = ZipFileInputStreamFileSystem.synchronized { |
| 84 | try { |
| 85 | Option(cache.get(file.getPath + ":" + innerPath, new Callable[ArchiveDirectory] { |
| 86 | def call = { |
| 87 | val zip = new ZipFile(file.getPath) |
Florian Nücke | 62f5a7a | 2014-03-16 09:01:03 +0100 | [diff] [blame] | 88 | try { |
| 89 | val cleanedPath = innerPath.stripPrefix("/").stripSuffix("/") + "/" |
| 90 | val rootEntry = zip.getEntry(cleanedPath) |
| 91 | if (rootEntry == null || !rootEntry.isDirectory) { |
| 92 | throw new IllegalArgumentException(s"Root path $innerPath doesn't exist or is not a directory in ZIP file ${file.getName}.") |
| 93 | } |
Florian Nücke | 1845ba0 | 2014-02-17 20:04:39 +0100 | [diff] [blame] | 94 | val directories = mutable.Set.empty[ArchiveDirectory] |
| 95 | val files = mutable.Set.empty[ArchiveFile] |
| 96 | val iterator = zip.entries() |
| 97 | while (iterator.hasMoreElements) { |
| 98 | val entry = iterator.nextElement() |
| 99 | if (entry.getName.startsWith(cleanedPath)) { |
| 100 | if (entry.isDirectory) directories += new ArchiveDirectory(entry, cleanedPath) |
| 101 | else files += new ArchiveFile(zip, entry, cleanedPath) |
Florian Nücke | 6aa2bd8 | 2013-12-08 07:59:49 +0100 | [diff] [blame] | 102 | } |
| 103 | } |
Florian Nücke | 1845ba0 | 2014-02-17 20:04:39 +0100 | [diff] [blame] | 104 | var root: ArchiveDirectory = null |
| 105 | for (entry <- directories ++ files) { |
| 106 | if (entry.path.length > 0) { |
| 107 | val parent = entry.path.substring(0, math.max(entry.path.lastIndexOf('/'), 0)) |
| 108 | directories.find(d => d.path == parent) match { |
| 109 | case Some(directory) => directory.children += entry |
| 110 | case _ => |
| 111 | } |
| 112 | } |
| 113 | else { |
| 114 | assert(entry.isInstanceOf[ArchiveDirectory]) |
| 115 | root = entry.asInstanceOf[ArchiveDirectory] |
| 116 | } |
Florian Nücke | 6aa2bd8 | 2013-12-08 07:59:49 +0100 | [diff] [blame] | 117 | } |
Florian Nücke | 1845ba0 | 2014-02-17 20:04:39 +0100 | [diff] [blame] | 118 | root |
Florian Nücke | 6aa2bd8 | 2013-12-08 07:59:49 +0100 | [diff] [blame] | 119 | } |
Florian Nücke | 62f5a7a | 2014-03-16 09:01:03 +0100 | [diff] [blame] | 120 | finally { |
| 121 | zip.close() |
| 122 | } |
Florian Nücke | 6aa2bd8 | 2013-12-08 07:59:49 +0100 | [diff] [blame] | 123 | } |
Florian Nücke | 1845ba0 | 2014-02-17 20:04:39 +0100 | [diff] [blame] | 124 | })) match { |
| 125 | case Some(archive) => new ZipFileInputStreamFileSystem(archive) |
| 126 | case _ => null |
Florian Nücke | 6aa2bd8 | 2013-12-08 07:59:49 +0100 | [diff] [blame] | 127 | } |
Florian Nücke | 1845ba0 | 2014-02-17 20:04:39 +0100 | [diff] [blame] | 128 | } |
| 129 | catch { |
| 130 | case e: Throwable => |
Florian Nücke | 8ce4b35 | 2014-07-24 16:35:54 +0200 | [diff] [blame] | 131 | OpenComputers.log.warn("Failed creating ZIP file system.", e) |
Florian Nücke | 1845ba0 | 2014-02-17 20:04:39 +0100 | [diff] [blame] | 132 | null |
Florian Nücke | 6aa2bd8 | 2013-12-08 07:59:49 +0100 | [diff] [blame] | 133 | } |
| 134 | } |
| 135 | |
| 136 | abstract class Archive(entry: ZipEntry, root: String) { |
| 137 | val path = entry.getName.stripPrefix(root).stripSuffix("/") |
| 138 | |
| 139 | val name = path.substring(path.lastIndexOf('/') + 1) |
| 140 | |
| 141 | val lastModified = entry.getTime |
| 142 | |
| 143 | val isDirectory = entry.isDirectory |
| 144 | |
| 145 | def size: Int |
| 146 | |
| 147 | def list(): Array[String] |
| 148 | |
| 149 | def openStream(): io.InputStream |
| 150 | |
| 151 | def find(path: Iterable[String]): Option[Archive] |
| 152 | } |
| 153 | |
| 154 | private class ArchiveFile(zip: ZipFile, entry: ZipEntry, root: String) extends Archive(entry, root) { |
| 155 | val data = { |
| 156 | val in = zip.getInputStream(entry) |
| 157 | Iterator.continually(in.read).takeWhile(-1 !=).map(_.toByte).toArray |
| 158 | } |
| 159 | |
| 160 | val size = data.length |
| 161 | |
| 162 | def list() = null |
| 163 | |
| 164 | def openStream() = new ByteArrayInputStream(data) |
| 165 | |
| 166 | def find(path: Iterable[String]) = |
| 167 | if (path.size == 1 && path.head == name) Some(this) |
| 168 | else None |
| 169 | } |
| 170 | |
| 171 | private class ArchiveDirectory(entry: ZipEntry, root: String) extends Archive(entry, root) { |
| 172 | val children = mutable.Set.empty[Archive] |
| 173 | |
| 174 | val size = 0 |
| 175 | |
| 176 | def list() = children.map(c => c.name + (if (c.isDirectory) "/" else "")).toArray |
| 177 | |
| 178 | def openStream() = null |
| 179 | |
| 180 | def find(path: Iterable[String]) = |
| 181 | if (path.head == name) { |
| 182 | if (path.size == 1) Some(this) |
| 183 | else { |
| 184 | val subPath = path.drop(1) |
| 185 | children.map(_.find(subPath)).collectFirst { |
| 186 | case Some(a) => a |
| 187 | } |
| 188 | } |
| 189 | } |
| 190 | else None |
| 191 | } |
| 192 | |
| 193 | } |