$ mvn -C clean install
<dependency> <groupId>com.io7m.jvvfs</groupId> <artifactId>io7m-jvvfs-core</artifactId> <version>4.0.1</version> </dependency>
Copyright © 2014 <code@io7m.com> http://io7m.com
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
final PathReal path = new PathReal("/path/to/application/resources");
final FilesystemType fs = Filesystem.makeWithArchiveDirectory(log, path);
final PathVirtual mount = PathVirtual.ofString("/");
fs.mountArchive("archive.zip", mount);
final PathVirtual path = PathVirtual.ofString("/a/b/c/file.txt");
final InputStream stream = fs.openFile(path);
// If "archive1.zip" contains "/a/b/file.txt", then "archive2.zip" could, for
// example, be mounted at "/a" or "/a/b".
fs.mount("archive1.zip", PathVirtual.ROOT);
assert fs.isDirectory(PathVirtual.ofString("/a"));
assert fs.isDirectory(PathVirtual.ofString("/a/b"));
fs.mount("archive2.zip", PathVirtual.ofString("/a"));
// Explicitly creating "/c" and mounting "archive2.zip" there is also a possibility.
fs.createDirectory(PathVirtual.ofString("/c"));
assert fs.isDirectory(PathVirtual.ofString("/c"));
fs.mount("archive2.zip", PathVirtual.ofString("/c"));
// Assuming "/xyz/file.txt" exists in "archive2.zip", both of the following will work:
final InputStream s0 = fs.openFile("/a/xyz/file.txt");
final InputStream s1 = fs.openFile("/c/xyz/file.txt");
com.io7m.jvvfs.logs.shell = true
com.io7m.jvvfs.level = LOG_ERROR
jvvfs>
archives close file-list file-list-long file-read file-size file-time
help list-mounts mkdir mount unmount
jvvfs> archives
complex.zip
encrypted.zip
files1-3.zip
files4-6.zip
io7m-jvvfs.properties
single-file-and-subdir-implicit.zip
single-file-and-subdir.zip
single-file-in-subdir-subdir.zip
single-file.jar
single-file.zip
subdir-shadow.zip
subdir-subdir-shadow.zip
unknown.unknown
jvvfs> mkdir /z
jvvfs> file-list-long /
z/ 2013-06-29 14:19:51 +0000
jvvfs> file-list-long /z
jvvfs> mount files1-3.zip /z
jvvfs> file-list-long /z
file1.txt 2013-05-08 11:35:16 +0000 7
file2.txt 2013-05-08 11:35:20 +0000 7
file3.txt 2013-05-08 11:35:24 +0000 7
jvvfs> file-read /z/file1.txt
File 1
jvvfs> list-mounts
/z /tmp/jvvfs/files1-3.zip
jvvfs> mount complex.zip /
jvvfs> list-mounts
/ /tmp/jvvfs/complex.zip
/z /tmp/jvvfs/files1-3.zip
jvvfs> file-list-long /
a/ 2012-03-15 21:13:12 +0000
b/ 2012-03-15 21:13:12 +0000
z/ 2013-06-28 22:13:47 +0000
jvvfs> file-list-long /a
a/ 2012-03-15 21:13:12 +0000
a1.txt 2012-03-15 21:13:12 +0000 9
a2.txt 2012-03-15 21:13:12 +0000 9
a3.txt 2012-03-15 21:13:12 +0000 9
b/ 2012-03-15 21:13:12 +0000
c/ 2012-03-15 21:13:12 +0000
jvvfs> mount files4-6.zip /a
jvvfs> file-list-long /a
a/ 2012-03-15 21:13:12 +0000
a1.txt 2012-03-15 21:13:12 +0000 9
a2.txt 2012-03-15 21:13:12 +0000 9
a3.txt 2012-03-15 21:13:12 +0000 9
b/ 2012-03-15 21:13:12 +0000
c/ 2012-03-15 21:13:12 +0000
file4.txt 2013-05-08 11:36:02 +0000 7
file5.txt 2013-05-08 11:36:06 +0000 7
file6.txt 2013-05-08 11:36:10 +0000 7
jvvfs> list-mounts
/a /tmp/jvvfs/files4-6.zip
/ /tmp/jvvfs/complex.zip
/z /tmp/jvvfs/files1-3.zip
jvvfs> close
jvvfs> list-mounts
jvvfs> file-list-long /
jvvfs> mount encrypted.zip /
shell : error : filesystem error: archive 'encrypted.zip' appears to be corrupt - invalid CEN header (encrypted entry)
Inductive io (F S : Type) : Type := | Success : S -> io F S | Failure : F -> io F S.
Axiom name : Set. Axiom name_valid : name -> Prop.
Definition path_virtual :=
list {n : name | name_valid n}.
Definition root : path_virtual :=
nil.
Require Coq.Lists.List. Definition is_prefix (p0 p1 : path_virtual) := List.firstn (length p0) p1 = p0. Definition is_ancestor_of (p0 p1 : path_virtual) := p0 <> p1 /\ is_prefix p0 p1.
Require Coq.Lists.List. Definition is_parent_of (p0 p1 : path_virtual) := is_ancestor_of p0 p1 /\ exists n, app p0 (n :: nil) = p1.
Require Coq.Lists.List. Definition subtract (p0 p1 : path_virtual) (_ : is_ancestor_of p1 p0 \/ p0 = p1) := List.skipn (length p1) p0.
Require Coq.Lists.List.
Fixpoint ancestors_including_self (p : path_virtual) : list path_virtual :=
cons root (match p with
| nil => nil
| cons y ys => List.map (cons y) (ancestors_including_self ys)
end).
Definition ancestors (p : path_virtual) : list path_virtual :=
List.removelast (ancestors_including_self p).
Axiom archive : Set.
Axiom archive_mount : archive -> path_virtual.
Inductive file_reference := | FSReferenceFile | FSReferenceDirectory.
Axiom archive_lookup : archive -> path_virtual -> io error_code (option file_reference).
Axiom archive_lookup_file_ancestor : forall
(a : archive)
(p q : path_virtual),
is_ancestor_of p q ->
archive_lookup a p = Success _ _ (Some FSReferenceFile) ->
archive_lookup a q = Failure _ _ FSErrorNotADirectory.
Axiom archive_lookup_ancestor : forall
(a : archive)
(p q : path_virtual),
is_ancestor_of p q ->
(exists r, archive_lookup a q = Success _ _ (Some r)) ->
archive_lookup a p = Success _ _ (Some FSReferenceDirectory).
Axiom archive_directory_list : archive -> path_virtual -> io error_code (list name).
Axiom archive_directory_list_file : forall
(a : archive)
(p : path_virtual),
archive_lookup a p = Success _ _ (Some FSReferenceFile) ->
archive_directory_list a p = Failure _ _ FSErrorNotADirectory.
Require Coq.Arith.Peano_dec.
Axiom ancestor_or_equal : forall (p q : path_virtual),
{is_ancestor_of p q \/ q = p}+{~is_ancestor_of p q \/ q = p}.
Fixpoint filesystem_lookup_union'
(archives : list archive)
(p : path_virtual)
(checks : nat)
: io error_code (option file_reference) :=
match archives with
| nil => Success _ _ None
| cons a rest =>
let m := archive_mount a in
match ancestor_or_equal m p with
| right _ => filesystem_lookup_union' rest p checks
| left H =>
match archive_lookup a (subtract p m H) with
| Success None => filesystem_lookup_union' rest p (S checks)
| Success (Some r) => Success _ _ (Some r)
| Failure FSErrorNotADirectory =>
if Peano_dec.eq_nat_dec checks 0
then Failure _ _ FSErrorNotADirectory
else Success _ _ None
| Failure e => Failure _ _ e
end
end
end.
Definition filesystem_lookup_union
(archives : list archive)
(p : path_virtual)
:= filesystem_lookup_union' archives p 0.
Fixpoint filesystem_lookup_ancestors
(archives : list archive)
(p_a : list path_virtual)
: io error_code (option file_reference) :=
match p_a with
| nil => Success _ _ (Some (FSReferenceDirectory))
| cons q qs =>
match filesystem_lookup_union archives q with
| Success None => Failure _ _ FSErrorNotADirectory
| Success (Some FSReferenceFile) => Failure _ _ FSErrorNotADirectory
| Success (Some FSReferenceDirectory) => filesystem_lookup_ancestors archives qs
| Failure e => Failure _ _ e
end
end.
Definition filesystem_lookup
(archives : list archive)
(p : path_virtual)
: io error_code (option file_reference) :=
match filesystem_lookup_ancestors archives (ancestors p) with
| Success _ => filesystem_lookup_union archives p
| Failure e => Failure _ _ e
end.
Require Coq.Arith.Peano_dec.
Fixpoint filesystem_lookup_typical
(archives : list archive)
(p : path_virtual)
: io error_code (option file_reference) :=
match archives with
| nil => Success _ _ None
| cons a rest =>
let m := archive_mount a in
match ancestor_or_equal m p with
| right _ => filesystem_lookup_typical rest p
| left H => archive_lookup a (subtract p m H)
end
end.
Require Coq.Arith.Peano_dec.
Require Coq.Lists.ListSet.
Axiom name_eq_dec : forall (n m : name),
{n = m}+{n <> m}.
Fixpoint filesystem_directory_list_union'
(archives : list archive)
(p : path_virtual)
(checks : nat)
(current : ListSet.set name)
: io error_code (list name) :=
match archives with
| nil => Success _ _ current
| cons a rest =>
let m := archive_mount a in
match ancestor_or_equal m p with
| right _ => filesystem_directory_list_union' rest p checks current
| left H =>
match archive_directory_list a (subtract p m H) with
| Success names => filesystem_directory_list_union' rest p (S checks) (ListSet.set_union name_eq_dec current names)
| Failure FSErrorNotADirectory =>
if Peano_dec.eq_nat_dec checks 0
then Failure _ _ FSErrorNotADirectory
else Success _ _ current
| Failure e => Failure _ _ e
end
end
end.
Definition filesystem_directory_list_union
(archives : list archive)
(p : path_virtual)
:= filesystem_directory_list_union' archives p 0 nil.
Definition filesystem_directory_list
(archives : list archive)
(p : path_virtual)
: io error_code (list name) :=
match filesystem_lookup archives p with
| Success None => Failure _ _ FSErrorNotADirectory
| Success (Some FSReferenceFile) => Failure _ _ FSErrorNotADirectory
| Success (Some FSReferenceDirectory) => filesystem_directory_list_union archives p
| Failure e => Failure _ _ e
end.