blob: 991c419ae750d528a49a907a9d6dfb8a30f5195e [file] [log] [blame] [raw]
Florian Nückef2f6d0f2013-09-30 21:42:09 +02001package li.cil.oc.server.fs
2
Florian Nücke6aa2bd82013-12-08 07:59:49 +01003import java.io
4import java.io.ByteArrayInputStream
5import java.util.concurrent.Callable
Florian Nückee35dfb92014-10-05 16:25:49 +02006import java.util.zip.ZipEntry
7import java.util.zip.ZipFile
Florian Nückecf521f42014-06-19 16:23:26 +02008
9import com.google.common.cache.CacheBuilder
Florian Nücke6aa2bd82013-12-08 07:59:49 +010010import li.cil.oc.OpenComputers
Florian Nückee35dfb92014-10-05 16:25:49 +020011import li.cil.oc.server.fs.ZipFileInputStreamFileSystem.ArchiveDirectory
12import li.cil.oc.server.fs.ZipFileInputStreamFileSystem.ArchiveFile
Florian Nückecf521f42014-06-19 16:23:26 +020013
Florian Nückef2f6d0f2013-09-30 21:42:09 +020014import scala.collection.mutable
Florian Nücke6aa2bd82013-12-08 07:59:49 +010015import scala.language.postfixOps
Florian Nückef2f6d0f2013-09-30 21:42:09 +020016
Florian Nücke6aa2bd82013-12-08 07:59:49 +010017class ZipFileInputStreamFileSystem(private val archive: ArchiveDirectory) extends InputStreamFileSystem {
Florian Nücke7cf9fad2013-10-28 15:16:55 +010018
19 def spaceTotal = spaceUsed
20
21 def spaceUsed = spaceUsed_
22
Florian Nücke1845ba02014-02-17 20:04:39 +010023 private lazy val spaceUsed_ = ZipFileInputStreamFileSystem.synchronized {
Florian Nücke6aa2bd82013-12-08 07:59:49 +010024 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ücke95e99972013-10-22 13:01:01 +020029 }
Florian Nücke95e99972013-10-22 13:01:01 +020030
31 // ----------------------------------------------------------------------- //
32
Florian Nücke1845ba02014-02-17 20:04:39 +010033 override def exists(path: String) = ZipFileInputStreamFileSystem.synchronized {
34 entry(path).isDefined
Florian Nücke98058282013-10-05 14:17:23 +020035 }
Florian Nückef2f6d0f2013-09-30 21:42:09 +020036
Florian Nücke1845ba02014-02-17 20:04:39 +010037 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ücke95e99972013-10-22 13:01:01 +020042 }
Florian Nückef2f6d0f2013-09-30 21:42:09 +020043
Florian Nücke1845ba02014-02-17 20:04:39 +010044 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ückef2f6d0f2013-09-30 21:42:09 +020060 }
61
Florian Nücke6aa2bd82013-12-08 07:59:49 +010062 // ----------------------------------------------------------------------- //
63
Florian Nücked0d4f502014-04-20 22:12:00 +020064 override protected def openInputChannel(path: String) = ZipFileInputStreamFileSystem.synchronized {
Florian Nückefc960372014-04-21 13:39:59 +020065 entry(path).map(entry => new InputStreamChannel(entry.openStream()))
Florian Nücke1845ba02014-02-17 20:04:39 +010066 }
Florian Nücke90bbdc22013-10-02 15:11:49 +020067
Florian Nücke95e99972013-10-22 13:01:01 +020068 // ----------------------------------------------------------------------- //
69
Florian Nücke6aa2bd82013-12-08 07:59:49 +010070 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ückef2f6d0f2013-09-30 21:42:09 +020074 }
Florian Nückef2f6d0f2013-09-30 21:42:09 +020075}
Florian Nücke6aa2bd82013-12-08 07:59:49 +010076
77object 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ücke1845ba02014-02-17 20:04:39 +010083 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ücke62f5a7a2014-03-16 09:01:03 +010088 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ücke1845ba02014-02-17 20:04:39 +010094 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ücke6aa2bd82013-12-08 07:59:49 +0100102 }
103 }
Florian Nücke1845ba02014-02-17 20:04:39 +0100104 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ücke6aa2bd82013-12-08 07:59:49 +0100117 }
Florian Nücke1845ba02014-02-17 20:04:39 +0100118 root
Florian Nücke6aa2bd82013-12-08 07:59:49 +0100119 }
Florian Nücke62f5a7a2014-03-16 09:01:03 +0100120 finally {
121 zip.close()
122 }
Florian Nücke6aa2bd82013-12-08 07:59:49 +0100123 }
Florian Nücke1845ba02014-02-17 20:04:39 +0100124 })) match {
125 case Some(archive) => new ZipFileInputStreamFileSystem(archive)
126 case _ => null
Florian Nücke6aa2bd82013-12-08 07:59:49 +0100127 }
Florian Nücke1845ba02014-02-17 20:04:39 +0100128 }
129 catch {
130 case e: Throwable =>
Florian Nücke8ce4b352014-07-24 16:35:54 +0200131 OpenComputers.log.warn("Failed creating ZIP file system.", e)
Florian Nücke1845ba02014-02-17 20:04:39 +0100132 null
Florian Nücke6aa2bd82013-12-08 07:59:49 +0100133 }
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}