All Downloads are FREE. Search and download functionalities are using the official Maven repository.

aQute.lib.deployer.FileRepo Maven / Gradle / Ivy

Go to download

This command line utility is the Swiss army knife of OSGi. It provides you with a breadth of tools to understand and manage OSGi based systems. This project basically uses bndlib.

There is a newer version: 7.1.0
Show newest version
package aQute.lib.deployer;

import java.io.Closeable;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.security.DigestInputStream;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.SortedSet;
import java.util.TreeSet;
import java.util.jar.Manifest;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import aQute.bnd.osgi.Constants;
import aQute.bnd.osgi.Instruction;
import aQute.bnd.osgi.Jar;
import aQute.bnd.osgi.Processor;
import aQute.bnd.service.Actionable;
import aQute.bnd.service.Plugin;
import aQute.bnd.service.Refreshable;
import aQute.bnd.service.Registry;
import aQute.bnd.service.RegistryPlugin;
import aQute.bnd.service.RepositoryListenerPlugin;
import aQute.bnd.service.RepositoryPlugin;
import aQute.bnd.service.repository.SearchableRepository.ResourceDescriptor;
import aQute.bnd.version.Version;
import aQute.lib.collections.SortedList;
import aQute.lib.hex.Hex;
import aQute.lib.io.IO;
import aQute.lib.json.JSONCodec;
import aQute.lib.persistentmap.PersistentMap;
import aQute.libg.command.Command;
import aQute.libg.cryptography.SHA1;
import aQute.libg.cryptography.SHA256;
import aQute.libg.reporter.ReporterAdapter;
import aQute.service.reporter.Reporter;

/**
 * A FileRepo is the primary and example implementation of a repository based on
 * a file system. It maintains its files in a bsn/bsn-version.jar style from a
 * given location. It implements all the functions of the
 * {@link RepositoryPlugin}, {@link Refreshable}, {@link Actionable}, and
 * {@link Closeable}. The FileRepo can be extended or used as is. When used as
 * is, it is possible to add shell commands to the life cycle of the FileRepo.
 * This life cycle is as follows:
 * 
    *
  • {@link #CMD_INIT} - Is only executed when the location did not exist
  • *
  • {@link #CMD_OPEN} - Called (after init if necessary) to open it once
  • *
  • {@link #CMD_REFRESH} - Called when refreshed.
  • *
  • {@link #CMD_BEFORE_PUT} - Before the file system is changed
  • *
  • {@link #CMD_AFTER_PUT} - After the file system has changed, and the put *
  • {@link #CMD_BEFORE_GET} - Before the file is gotten
  • *
  • {@link #CMD_AFTER_ACTION} - Before the file is gotten
  • *
  • {@link #CMD_CLOSE} - When the repo is closed and no more actions will * take place
  • was a success *
  • {@link #CMD_ABORT_PUT} - When the put is aborted.
  • *
  • {@link #CMD_CLOSE} - To close the repository.
  • *
* Additionally, it is possible to set the {@link #CMD_SHELL} and the * {@link #CMD_PATH}. Notice that you can use the ${global} macro to read global * (that is, machine local) settings from the ~/.bnd/settings.json file (can be * managed with bnd). */ @aQute.bnd.annotation.plugin.BndPlugin(name = "Filerepo", parameters = FileRepo.Config.class) public class FileRepo implements Plugin, RepositoryPlugin, Refreshable, RegistryPlugin, Actionable, Closeable { private final static Logger logger = LoggerFactory.getLogger(FileRepo.class); interface Config { String name(); String location(); boolean readonly(); boolean trace(); boolean index(); String cmd_path(); String cmd_shell(); String cmd_init(); String cmd_open(); String cmd_after_put(); String cmd_before_put(); String cmd_abort_put(); String cmd_before_get(); String cmd_after_action(); String cmd_refresh(); String cmd_close(); } /** * If set, will trace to stdout. Works only if no reporter is set. */ public final static String TRACE = "trace"; /** * Property name for the location of the repo, must be a valid path name * using forward slashes (see {@link IO#getFile(String)}. */ public final static String LOCATION = "location"; /** * Property name for the readonly state of the repository. If no, will * read/write, otherwise it must be a boolean value read by * {@link Boolean#parseBoolean(String)}. Read only repositories will not * accept writes. Defaults to false. */ public final static String READONLY = "readonly"; /** * Property name for the latest option of the repository. If true, will copy * the put jar to a 'latest' file (option must be a boolean value read by * {@link Boolean#parseBoolean(String)}). Defaults to true. */ public final static String LATEST_OPTION = "latest"; /** * Set the name of this repository (optional) */ public final static String NAME = "name"; /** * Should this file repo have an index? Either true or false (absent) */ public final static String INDEX = "index"; /** * Path property for commands. A comma separated path for directories to be * searched for command. May contain $ @} which will be replaced by the * system path. If this property is not set, the system path is assumed. */ public static final String CMD_PATH = "cmd.path"; /** * The name ( and path) of the shell to execute the commands. By default * this is sh and searched in the path. */ public static final String CMD_SHELL = "cmd.shell"; /** * Property for commands. The command only runs when the location does not * exist. *

*/ public static final String CMD_INIT = "cmd.init"; /** * Property for commands. Command is run before the repo is first used. *

*/ public static final String CMD_OPEN = "cmd.open"; /** * Property for commands. The command runs after a put operation. *

*/ public static final String CMD_AFTER_PUT = "cmd.after.put"; /** * Property for commands. The command runs when the repository is refreshed. *

*/ public static final String CMD_REFRESH = "cmd.refresh"; /** * Property for commands. The command runs after the file is put. *

*/ public static final String CMD_BEFORE_PUT = "cmd.before.put"; /** * Property for commands. The command runs when a put is aborted after file * changes were made. *

*/ public static final String CMD_ABORT_PUT = "cmd.abort.put"; /** * Property for commands. The command runs after the file is put. *

*/ public static final String CMD_CLOSE = "cmd.close"; /** * Property for commands. Will be run after an action has been executed. *

*/ public static final String CMD_AFTER_ACTION = "cmd.after.action"; /** * Called before a before get. */ public static final String CMD_BEFORE_GET = "cmd.before.get"; /** * Options used when the options are null */ static final PutOptions DEFAULTOPTIONS = new PutOptions(); public static final int MAX_MAJOR = 999999999; private static final String LATEST_POSTFIX = "-" + Constants.VERSION_ATTR_LATEST + ".jar"; public static final Version LATEST_VERSION = new Version(MAX_MAJOR, 0, 0); private static final SortedSet LATEST_SET = new TreeSet<>(Collections.singleton(LATEST_VERSION)); final static JSONCodec codec = new JSONCodec(); String shell; String path; String init; String open; String refresh; String beforePut; String afterPut; String abortPut; String beforeGet; String close; String action; File[] EMPTY_FILES = new File[0]; protected File root; Registry registry; boolean createLatest = true; boolean canWrite = true; private final static Pattern REPO_FILE = Pattern .compile("(?:([-.\\w]+)-)(" + Version.VERSION_STRING + "|" + Constants.VERSION_ATTR_LATEST + ")\\.(jar|lib)"); Reporter reporter; boolean dirty = true; String name; boolean inited; boolean trace; PersistentMap index; private boolean hasIndex; public FileRepo() {} public FileRepo(String name, File location, boolean canWrite) { this.name = name; this.root = location; this.canWrite = canWrite; } /** * Initialize the repository Subclasses should first call this method and * then if it returns true, do their own initialization * * @return true if initialized, false if already had been initialized. * @throws Exception */ protected boolean init() throws Exception { if (inited) return false; inited = true; if (reporter == null) { ReporterAdapter reporter = trace ? new ReporterAdapter(System.out) : new ReporterAdapter(); reporter.setTrace(trace); reporter.setExceptions(trace); this.reporter = reporter; } logger.debug("init"); if (!root.isDirectory()) { IO.mkdirs(root); if (!root.isDirectory()) throw new IllegalArgumentException("Location cannot be turned into a directory " + root); exec(init, IO.absolutePath(root)); } if (hasIndex) index = new PersistentMap<>(new File(root, ".index"), ResourceDescriptor.class); open(); return true; } /** * @see aQute.bnd.service.Plugin#setProperties(java.util.Map) */ @Override public void setProperties(Map map) { String location = map.get(LOCATION); if (location == null) throw new IllegalArgumentException("Location must be set on a FileRepo plugin"); root = IO.getFile(IO.home, location); String readonly = map.get(READONLY); if (readonly != null) canWrite = !Boolean.valueOf(readonly) .booleanValue(); String createLatest = map.get(LATEST_OPTION); if (createLatest != null) this.createLatest = Boolean.valueOf(createLatest) .booleanValue(); hasIndex = Processor.isTrue(map.get(INDEX)); name = map.get(NAME); path = map.get(CMD_PATH); shell = map.get(CMD_SHELL); init = map.get(CMD_INIT); open = map.get(CMD_OPEN); refresh = map.get(CMD_REFRESH); beforePut = map.get(CMD_BEFORE_PUT); abortPut = map.get(CMD_ABORT_PUT); afterPut = map.get(CMD_AFTER_PUT); beforeGet = map.get(CMD_BEFORE_GET); close = map.get(CMD_CLOSE); action = map.get(CMD_AFTER_ACTION); trace = Boolean.parseBoolean(map.get(TRACE)); } /** * Answer if this repository can write. */ @Override public boolean canWrite() { return canWrite; } /** * Local helper method that tries to insert a file in the repository. This * method can be overridden but MUST not change the content of the tmpFile. * This method should also create a latest version of the artifact for * reference by tools like ant etc. *

* It is allowed to rename the file, the tmp file must be beneath the root * directory to prevent rename problems. * * @param tmpFile source file * @param digest * @return a File that contains the content of the tmpFile * @throws Exception */ protected File putArtifact(File tmpFile, byte[] digest) throws Exception { return putArtifact(tmpFile, null, digest); } protected File putArtifact(File tmpFile, PutOptions options, byte[] digest) throws Exception { assert (tmpFile != null); try (Jar tmpJar = new Jar(tmpFile)) { String bsn = null; if (options != null && options.bsn != null) { bsn = options.bsn; } else { bsn = tmpJar.getBsn(); } if (bsn == null) throw new IllegalArgumentException("No bsn set in jar: " + tmpFile); Version version = null; if (options != null && options.version != null) { version = options.version; } else { try { version = new Version(tmpJar.getVersion()); } catch (Exception e) { throw new IllegalArgumentException("Incorrect version in : " + tmpFile + " " + tmpJar.getVersion()); } } if (version == null) { /* * should not happen because bsn != null, which mean that the * jar is valid and it has a manifest. just to be safe though */ version = Version.LOWEST; } logger.debug("bsn={} version={}", bsn, version); File dir = new File(root, bsn); IO.mkdirs(dir); if (!dir.isDirectory()) throw new IOException("Could not create directory " + dir); String fName = bsn + "-" + version.toStringWithoutQualifier() + ".jar"; File file = new File(dir, fName); logger.debug("updating {}", file.getAbsolutePath()); if (hasIndex) index.put(bsn + "-" + version.toStringWithoutQualifier(), buildDescriptor(tmpFile, tmpJar, digest, bsn, version)); // An open jar on file will fail rename on windows tmpJar.close(); dirty = true; if (file.isFile() && !file.canWrite()) { // older versions of this class made file readonly file.setWritable(true); } IO.rename(tmpFile, file); fireBundleAdded(file); afterPut(file, bsn, version, Hex.toHexString(digest)); if (createLatest) { File latest = new File(dir, bsn + LATEST_POSTFIX); IO.copy(file, latest); } logger.debug("updated {}", file.getAbsolutePath()); return file; } } /* * (non-Javadoc) * @see aQute.bnd.service.RepositoryPlugin#put(java.io.InputStream, * aQute.bnd.service.RepositoryPlugin.PutOptions) */ @Override public PutResult put(InputStream stream, PutOptions options) throws Exception { /* determine if the put is allowed */ if (!canWrite) { throw new IOException("Repository is read-only"); } assert stream != null; if (options == null) options = DEFAULTOPTIONS; init(); /* * copy the artifact from the (new/digest) stream into a temporary file * in the root directory of the repository */ File tmpFile = IO.createTempFile(root, "put", ".jar"); try (DigestInputStream dis = new DigestInputStream(stream, MessageDigest.getInstance("SHA-1"))) { IO.copy(dis, tmpFile); byte[] digest = dis.getMessageDigest() .digest(); if (options.digest != null && !Arrays.equals(digest, options.digest)) throw new IOException("Retrieved artifact digest doesn't match specified digest"); /* * put the artifact into the repository (from the temporary file) */ beforePut(tmpFile); File file = putArtifact(tmpFile, options, digest); PutResult result = new PutResult(); result.digest = digest; result.artifact = file.toURI(); return result; } catch (Exception e) { abortPut(tmpFile); throw e; } finally { IO.delete(tmpFile); } } public void setLocation(String string) { root = IO.getFile(string); } @Override public void setReporter(Reporter reporter) { this.reporter = reporter; } @Override public List list(String regex) throws Exception { init(); Instruction pattern = null; if (regex != null) pattern = new Instruction(regex); List result = new ArrayList<>(); if (root == null) { if (reporter != null) reporter.error("FileRepo root directory is not set."); } else { if (root.isDirectory()) { for (File f : IO.listFiles(root)) { if (!f.isDirectory()) continue; // ignore non-directories String fileName = f.getName(); if (fileName.charAt(0) == '.') continue; // ignore hidden files if (pattern == null || pattern.matches(fileName)) result.add(fileName); } } else if (reporter != null) reporter.error("FileRepo root directory (%s) does not exist", root); } return result; } @Override public SortedSet versions(String bsn) throws Exception { init(); File dir = new File(root, bsn); boolean latest = false; if (dir.isDirectory()) { List list = new ArrayList<>(); for (String v : IO.list(dir)) { Matcher m = REPO_FILE.matcher(v); if (m.matches()) { String version = m.group(2); if (!version.equals(Constants.VERSION_ATTR_LATEST)) list.add(new Version(version)); else latest = true; } } if (list.isEmpty() && latest) return LATEST_SET; else return new SortedList<>(list); } return SortedList.empty(); } @Override public String toString() { return String.format("%s [%-40s r/w=%s]", getName(), IO.absolutePath(getRoot()), canWrite()); } @Override public File getRoot() { return root; } @Override public boolean refresh() throws Exception { init(); exec(refresh, root); rebuildIndex(); return true; } @Override public String getName() { if (name == null) { return getLocation(); } return name; } /* * (non-Javadoc) * @see aQute.bnd.service.RepositoryPlugin#get(java.lang.String, * aQute.bnd.version.Version, java.util.Map) */ @Override public File get(String bsn, Version version, Map properties, DownloadListener... listeners) throws Exception { init(); beforeGet(bsn, version); File file = getLocal(bsn, version, properties); if (file.exists()) { for (DownloadListener l : listeners) { try { l.success(file); } catch (Exception e) { reporter.exception(e, "Download listener for %s", file); } } return file; } return null; } @Override public void setRegistry(Registry registry) { this.registry = registry; } @Override public String getLocation() { return root.toString(); } @Override public Map actions(Object... target) throws Exception { if (target == null || target.length == 0) { Map actions = new LinkedHashMap<>(); actions.put("Rebuild Resource Index", () -> { try { refresh(); } catch (Exception e) { throw new RuntimeException(e); } }); return actions; // no default actions } try { String bsn = (String) target[0]; Version version = (Version) target[1]; final File f = get(bsn, version, null); if (f == null) return null; Map actions = new HashMap<>(); actions.put("Delete " + bsn + "-" + status(bsn, version), () -> { IO.delete(f); if (f.getParentFile() .list().length == 0) IO.delete(f.getParentFile()); afterAction(f, "delete"); }); return actions; } catch (Exception e) { return null; } } protected void afterAction(File f, String key) { exec(action, root, f, key); } /* * (non-Javadoc) * @see aQute.bnd.service.Actionable#tooltip(java.lang.Object[]) */ @Override @SuppressWarnings("unchecked") public String tooltip(Object... target) throws Exception { if (target == null || target.length == 0) return String.format("%s\n%s", getName(), root); try { String bsn = (String) target[0]; Version version = (Version) target[1]; Map map = null; if (target.length > 2) map = (Map) target[2]; File f = getLocal(bsn, version, map); String s = ""; ResourceDescriptor descriptor = getDescriptor(bsn, version); if (descriptor != null && descriptor.description != null) { s = descriptor.description + "\n"; } s += String.format("Path: %s\nSize: %s\nSHA1: %s", IO.absolutePath(f), readable(f.length(), 0), SHA1.digest(f) .asHex()); if (f.getName() .endsWith(".lib") && f.isFile()) { s += "\n" + IO.collect(f); } return s; } catch (Exception e) { return null; } } /* * (non-Javadoc) * @see aQute.bnd.service.Actionable#title(java.lang.Object[]) */ @Override public String title(Object... target) throws Exception { if (target == null || target.length == 0) return getName(); if (target.length == 1 && target[0] instanceof String string) return string; if (target.length == 2 && target[0] instanceof String string && target[1] instanceof Version version) { return status(string, version); } return null; } protected File getLocal(String bsn, Version version, Map properties) { File dir = new File(root, bsn); if (LATEST_VERSION.equals(version)) { File fjar = new File(dir, bsn + LATEST_POSTFIX); if (fjar.isFile()) return fjar.getAbsoluteFile(); } File fjar = new File(dir, bsn + "-" + version.toStringWithoutQualifier() + ".jar"); if (fjar.isFile()) return fjar.getAbsoluteFile(); File sfjar = new File(dir, version.toStringWithoutQualifier() + ".jar"); if (sfjar.isFile()) return sfjar.getAbsoluteFile(); File flib = new File(dir, bsn + "-" + version.toStringWithoutQualifier() + ".lib"); if (flib.isFile()) return flib.getAbsoluteFile(); File sflib = new File(dir, version.toStringWithoutQualifier() + ".lib"); if (sflib.isFile()) return sflib.getAbsoluteFile(); return fjar.getAbsoluteFile(); } protected String status(String bsn, Version version) { File file = getLocal(bsn, version, null); String vs; if (LATEST_VERSION.equals(version)) { vs = Constants.VERSION_ATTR_LATEST; } else { vs = version.toString(); } StringBuilder sb = new StringBuilder(vs); String del = " ["; if (file.getName() .endsWith(".lib")) { sb.append(del) .append("L"); del = ""; } else if (!file.getName() .endsWith(".jar")) { sb.append(del) .append("?"); del = ""; } if (!file.isFile()) { sb.append(del) .append("X"); del = ""; } if (file.length() == 0) { sb.append(del) .append("0"); del = ""; } if (del.equals("")) sb.append("]"); return sb.toString(); } private static String[] names = { "bytes", "Kb", "Mb", "Gb" }; private Object readable(long length, int n) { if (length < 0) return ""; if (length < 1024 || n >= names.length) return length + names[n]; return readable(length / 1024, n + 1); } @Override public void close() throws IOException { if (inited) { exec(close, IO.absolutePath(getRoot())); if (hasIndex) index.close(); } } protected void open() { exec(open, IO.absolutePath(getRoot())); } protected void beforePut(File tmp) { exec(beforePut, IO.absolutePath(getRoot()), IO.absolutePath(tmp)); } protected void afterPut(File file, String bsn, Version version, String sha) { exec(afterPut, IO.absolutePath(getRoot()), IO.absolutePath(file), sha); } protected void abortPut(File tmpFile) { exec(abortPut, IO.absolutePath(getRoot()), IO.absolutePath(tmpFile)); } protected void beforeGet(String bsn, Version version) { exec(beforeGet, IO.absolutePath(getRoot()), bsn, version); } protected void fireBundleAdded(File file) throws Exception { if (registry == null) return; List listeners = registry.getPlugins(RepositoryListenerPlugin.class); if (listeners.isEmpty()) return; try (Jar jar = new Jar(file)) { for (RepositoryListenerPlugin listener : listeners) { try { listener.bundleAdded(this, jar, file); } catch (Exception e) { if (reporter != null) reporter.warning("Repository listener threw an unexpected exception: %s", e); } } } } /** * Execute a command. Used in different stages so that the repository can be * synced with external tools. */ void exec(String line, Object... args) { if (line == null) { logger.debug("Line is empty, args={}", args == null ? new Object[0] : args); return; } logger.debug("exec {}", line); try { if (args != null) { for (int i = 0; i < args.length; i++) { if (i == 0) { // replaceAll backslash magic ensures windows paths // remain intact line = line.replaceAll("\\$\\{@\\}", args[0].toString() .replaceAll("\\\\", "\\\\\\\\")); } // replaceAll backslash magic ensures windows paths remain // intact line = line.replaceAll("\\$" + i, args[i].toString() .replaceAll("\\\\", "\\\\\\\\")); } } // purge remaining placeholders line = line.replaceAll("\\s*\\$[0-9]", ""); int result = 0; StringBuilder stdout = new StringBuilder(); StringBuilder stderr = new StringBuilder(); if (IO.isWindows()) { Command cmd = new Command().arg("cmd", "/c", line); cmd.setCwd(getRoot()); result = cmd.execute(stdout, stderr); } else { if (shell == null) { shell = "sh"; } Command cmd = new Command(shell); cmd.setCwd(getRoot()); if (path != null) { cmd.inherit(); String oldpath = cmd.var("PATH"); path = path.replaceAll("\\s*,\\s*", File.pathSeparator); path = path.replaceAll("\\$\\{@\\}", oldpath); cmd.var("PATH", path); } result = cmd.execute(line, stdout, stderr); } if (result != 0) { reporter.error("Command %s failed with %s %s %s", line, result, stdout, stderr); } } catch (Exception e) { e.printStackTrace(); reporter.exception(e, "%s", e); } } /* * 8 Set the root directory directly */ public void setDir(File repoDir) { this.root = repoDir; } /** * Delete an entry from the repository and cleanup the directory * * @param bsn * @param version * @throws Exception */ public void delete(String bsn, Version version) throws Exception { init(); assert bsn != null; SortedSet versions; if (version == null) versions = versions(bsn); else versions = new SortedList<>(version); for (Version v : versions) { File f = getLocal(bsn, version, null); if (!f.isFile()) reporter.error("No artifact found for %s:%s", bsn, version); else IO.delete(f); } if (versions(bsn).isEmpty()) IO.delete(new File(root, bsn)); index.remove(bsn + "-" + version); } public ResourceDescriptor getDescriptor(String bsn, Version version) throws Exception { init(); if (hasIndex) { ResourceDescriptor resourceDescriptor = index.get(bsn + "-" + version); if (resourceDescriptor == null) System.out.println("Keys " + index.keySet()); return resourceDescriptor; } return null; } public SortedSet getResources() throws Exception { init(); if (hasIndex) { TreeSet resources = new TreeSet<>((a, b) -> { if (a == b) return 0; int r = a.bsn.compareTo(b.bsn); if (r != 0) return r; if (a.version != b.version) { if (a.version == null) return 1; if (b.version == null) return -1; r = a.version.compareTo(b.version); if (r != 0) return r; } if (a.id.length > b.id.length) return 1; if (a.id.length < b.id.length) return -1; for (int i = 0; i < a.id.length; i++) { if (a.id[i] > b.id[i]) return 1; if (a.id[i] < b.id[i]) return 1; } return 0; }); for (ResourceDescriptor rd : index.values()) { resources.add(rd); } return resources; } return null; } public ResourceDescriptor getResource(byte[] sha) throws Exception { init(); if (hasIndex) { for (ResourceDescriptor rd : index.values()) { if (Arrays.equals(rd.id, sha)) return rd; } } return null; } void rebuildIndex() throws Exception { init(); if (!hasIndex || !dirty) return; index.clear(); for (String bsn : list(null)) { for (Version version : versions(bsn)) { File f = get(bsn, version, null); index.put(bsn + "-" + version, buildDescriptor(f, null, null, bsn, version)); } } dirty = false; } private ResourceDescriptor buildDescriptor(File f, Jar jar, byte[] digest, String bsn, Version version) throws NoSuchAlgorithmException, Exception { init(); Jar tmpjar = jar; if (jar == null) tmpjar = new Jar(f); try { Manifest m = tmpjar.getManifest(); ResourceDescriptor rd = new ResourceDescriptor(); rd.bsn = bsn; rd.version = version; rd.description = m.getMainAttributes() .getValue(Constants.BUNDLE_DESCRIPTION); rd.id = digest; if (rd.id == null) rd.id = SHA1.digest(f) .digest(); rd.sha256 = SHA256.digest(f) .digest(); rd.url = f.toURI(); return rd; } finally { if (tmpjar != null) tmpjar.close(); } } public void setIndex(boolean b) { hasIndex = b; } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy