@Override public int compare(RelativeFile f1, RelativeFile f2) { String s1 = f1.getOsIndependentRelativePath(); String s2 = f2.getOsIndependentRelativePath(); if (s1.equals(SdkConstants.FN_APK_CLASSES_DEX)) { return -1; } else if (s2.equals(SdkConstants.FN_APK_CLASSES_DEX)) { return 1; } else { return s1.compareTo(s2); } } }
/** * Loads all files in a directory recursively. * * @param directory the directory, must exist and be a readable directory * @return all files in the directory, sub-directories included */ @NonNull public static ImmutableSet<RelativeFile> fromDirectory(@NonNull File directory) { return fromDirectory(directory, directory); }
/** * Updates native libraries in the archive. * * @param files the resources to update * @throws IOException failed to update the archive */ public void updateNativeLibraries(@NonNull ImmutableMap<RelativeFile, FileStatus> files) throws IOException { updateFiles( PackagedFileUpdates.fromIncrementalRelativeFileSet( Maps.filterKeys( files, rf -> mAbiPredicate.test(rf.getOsIndependentRelativePath()) ) ) ); }
/** * Reads files and builds an incremental relative file set. Each individual file in * {@code files} may be a file or directory. If it is a directory, then all files in the * directory are added as if {@link #fromDirectory(File)} had been invoked; if it is a file, * then it is assumed to be a zip file and all files in the zip are added as if * {@link #fromZip(File)} had been invoked. * * <p>The status of each file is set to {@link FileStatus#NEW}. This method is used to construct * an initial set of files and is, therefore, an incremental update from zero. * * @param files the files and directories * @return the file set * @throws IOException failed to read the files */ @NonNull public static ImmutableMap<RelativeFile, FileStatus> fromZipsAndDirectories( @NonNull Iterable<File> files) throws IOException { Set<ImmutableMap<RelativeFile, FileStatus>> sets = Sets.newHashSet(); for (File f : files) { if (f.isFile()) { sets.add(fromZip(f)); } else { sets.add(fromDirectory(f)); } } return union(sets); }
/** * Writes incremental state. * * @throws IOException failed to write state */ private void writeState() throws IOException { File stateFile = new File(mIncrementalDir, STATE_FILE); Properties props = new Properties(); int currIdx = 0; for (BiMap.Entry<RelativeFile, String> entry : mNameMap.entrySet()) { props.put(BASE_KEY_PREFIX + currIdx, entry.getKey().getBase().getPath()); props.put(FILE_KEY_PREFIX + currIdx, entry.getKey().getFile().getPath()); props.put(RENAMED_KEY_PREFIX + currIdx, entry.getValue()); currIdx++; } Closer closer = Closer.create(); try { props.store(closer.register(new FileWriter(stateFile)), null); } catch (Throwable t) { throw closer.rethrow(t); } finally { closer.close(); } }
relativeUpdates.putAll(fromZip(file, cache, cacheUpdates)); } else { while (possibleBaseDirectory != null) { if (baseFiles.contains(possibleBaseDirectory)) { relativeUpdates.put(new RelativeFile(possibleBaseDirectory, file), status); break;
/** * Incrementally updates a resource in the packaging. The resource can be added or removed, * depending on the change made to the file. * * @param file the file to update * @param modificationType the type of file modification * @throws PackagerException failed to update the package */ public void updateResource(@NonNull RelativeFile file, @NonNull FileStatus modificationType) throws PackagerException { if (modificationType == FileStatus.NEW || modificationType == FileStatus.CHANGED) { doAddFile(file.getFile(), file.getOsIndependentRelativePath()); } else { throw new UnsupportedOperationException("Cannot remove a file from archive."); } }
/** * Loads all files in a directory recursively. Creates al files relative to another directory. * * @param base the directory to use for relative files * @param directory the directory to get files from, must exist and be a readable directory * @return all files in the directory, sub-directories included */ @NonNull private static ImmutableSet<RelativeFile> fromDirectory( @NonNull File base, @NonNull File directory) { Preconditions.checkArgument(base.isDirectory(), "!base.isDirectory()"); Preconditions.checkArgument(directory.isDirectory(), "!directory.isDirectory()"); Set<RelativeFile> files = Sets.newHashSet(); File[] directoryFiles = Verify.verifyNotNull(directory.listFiles(), "directory.listFiles() == null"); for (File file : directoryFiles) { if (file.isDirectory()) { files.addAll(fromDirectory(base, file)); } else { files.add(new RelativeFile(base, file)); } } return ImmutableSet.copyOf(files); }
/** * Reads a zip file and adds all files in the file in a new incremental relative set. The * status of each file is set to {@link FileStatus#NEW}. This method is used to construct an * initial set of files and is, therefore, an incremental update from zero. * * @param zip the zip file to read, must be a valid, existing zip file * @return the file set * @throws IOException failed to read the zip file */ @NonNull public static ImmutableMap<RelativeFile, FileStatus> fromZip(@NonNull File zip) throws IOException { return fromZip(zip, FileStatus.NEW); }
/** * Obtains a predicate that checks if a file is in an input set. * * @param inputSet the input set * @return the predicate */ @NonNull private Function<File, RelativeFile> inInputSet(@NonNull InputSet inputSet) { Map<File, RelativeFile> inverseFiltered = mFiles.entrySet().stream() .filter(e -> e.getValue() == inputSet) .map(Map.Entry::getKey) .collect( HashMap::new, (m, rf) -> m.put(rf.getFile(), rf), Map::putAll); return inverseFiltered::get; }
RelativeFile rf = new RelativeFile(new File(base), new File(file)); mNameMap.put(rf, rename);
/** * Removes any cached version of the given path. * * @param f the path * @throws IOException failed to remove the file */ public void remove(@NonNull File f) throws IOException { File toRemove = new File(directory, key(f)); if (toRemove.exists()) { FileUtils.delete(toRemove); } }
/** * Reads a zip file and adds all files in the file in a new incremental relative set. The * status of each file is set to {@code status}. * * @param zip the zip file to read, must be a valid, existing zip file * @param status the status to set the files to * @return the file set * @throws IOException failed to read the zip file */ @NonNull public static ImmutableMap<RelativeFile, FileStatus> fromZip( @NonNull File zip, FileStatus status) throws IOException { Preconditions.checkArgument(zip.isFile(), "!zip.isFile()"); return ImmutableMap.<RelativeFile, FileStatus>builder() .putAll( Maps.asMap( RelativeFiles.fromZip(zip), f -> status)) .build(); }
/** * Constructs a predicate over relative files from a predicate over paths, applying it to the * normalized relative path contained in the relative file. * * @param predicate the file predicate * @return the relative file predicate built upon {@code predicate} */ @NonNull public static Predicate<RelativeFile> fromPathPredicate(@NonNull Predicate<String> predicate) { return rf -> predicate.test(rf.getOsIndependentRelativePath()); }
/** * Obtains the cached file corresponding to the file with the given path. * * @param f the path * @return the cached file, {@code null} if there is no file in the cache that corresponds to * the given file */ @Nullable public File get(@NonNull File f) { File file = new File(directory, key(f)); if (file.isFile()) { return file; } else { return null; } }
/** * Loads all files in a directory recursively, filtering the results with a predicate. * Filtering is only done at the end so, even if a directory is excluded from the filter, * its files will be included if they are accepted by the filter. * * @param directory the directory, must exist and be a readable directory * @param filter a predicate to filter which files should be included in the result; only * files to whom the filter application results in {@code true} are included in the result * @return all files in the directory, sub-directories included */ @NonNull public static ImmutableSet<RelativeFile> fromDirectory( @NonNull File directory, @NonNull final Predicate<RelativeFile> filter) { return ImmutableSet.copyOf(Sets.filter(fromDirectory(directory, directory), filter::test)); }
/** * Obtains the file name in the OS-independent relative path in a relative file. * * @param file the file, <i>e.g.</i>, {@code foo/bar} * @return the file name, <i>e.g.</i>, {@code bar} */ @NonNull private static String getOsIndependentFileName(@NonNull RelativeFile file) { String[] pathSplit = file.getOsIndependentRelativePath().split("/"); return pathSplit[pathSplit.length - 1]; }
/** * Adds a file to the cache, replacing any file that had the exact same absolute path. * * @param f the file to add * @throws IOException failed to copy the file into the cache */ public void add(@NonNull File f) throws IOException { Preconditions.checkArgument(f.isFile(), "!f.isFile()"); if (!directory.isDirectory()) { FileUtils.mkdirs(directory); } String k = key(f); Files.copy(f, new File(directory, k)); }
/** * Creates a set of updates based on a map of update. This set will * contain one entry per entry in the set in a 1-1 match. * * @param set the set with updates * @return the transform set */ @NonNull static Set<PackagedFileUpdate> fromIncrementalRelativeFileSet( @NonNull Map<RelativeFile, FileStatus> set) { Set<PackagedFileUpdate> r = Sets.newHashSet(); for (Map.Entry<RelativeFile, FileStatus> entry : set.entrySet()) { r.add(new PackagedFileUpdate(entry.getKey(), entry.getKey().getOsIndependentRelativePath(), entry.getValue())); } return r; } }
/** * Updates resources in the archive. * * @param files the resources to update * @throws IOException failed to update the archive */ public void updateJavaResources(@NonNull ImmutableMap<RelativeFile, FileStatus> files) throws IOException { /* * There is a bug somewhere in the proguard build tasks that places .class files as * resources. These will be removed here, but this filtering code can -- and should -- be * removed once that bug is fixed. */ updateFiles( PackagedFileUpdates.fromIncrementalRelativeFileSet( Maps.filterKeys( files, rf -> !rf.getOsIndependentRelativePath() .endsWith(SdkConstants.DOT_CLASS)))); }