package de.dal33t.powerfolder.disk.dao; import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.HashSet; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import de.dal33t.powerfolder.disk.DiskItemFilter; import de.dal33t.powerfolder.disk.dao.FileInfoCriteria.Type; import de.dal33t.powerfolder.light.DirectoryInfo; import de.dal33t.powerfolder.light.FileHistory; import de.dal33t.powerfolder.light.FileInfo; import de.dal33t.powerfolder.util.Reject; import de.dal33t.powerfolder.util.StringUtils; import de.dal33t.powerfolder.util.Util; import de.dal33t.powerfolder.util.logging.Loggable; /** * A {@link FileInfoDAO} implementation based on fast, in-memory * {@link ConcurrentHashMap}s. * * @author sprajc */ public class FileInfoDAOHashMapImpl extends Loggable implements FileInfoDAO { private final ConcurrentMap<String, Domain> domains = Util .createConcurrentHashMap(4); private String selfDomain; private DiskItemFilter filter; public FileInfoDAOHashMapImpl(String selfDomain, DiskItemFilter filter) { super(); this.selfDomain = selfDomain; this.filter = filter; if (filter == null) { this.filter = new DiskItemFilter(); } } public int count(String domain, boolean includeDirs, boolean excludeIgnored) { Domain d = getDomain(domain); if (excludeIgnored) { int c = 0; for (FileInfo fInfo : d.files.keySet()) { if (filter.isRetained(fInfo) && !fInfo.isDeleted()) { c++; } } if (includeDirs) { for (FileInfo dInfo : d.directories.keySet()) { if (filter.isRetained(dInfo) && !dInfo.isDeleted()) { c++; } } } return c; } else { return d.files.size() + (includeDirs ? d.directories.size() : 0); } } public int countInSync(String domain, boolean includeDirs, boolean excludeIgnored) { Domain d = getDomain(domain); int c = 0; for (FileInfo fInfo : d.files.values()) { if (filter.isExcluded(fInfo) || fInfo.isDeleted()) { continue; } FileInfo newestFileInfo = findNewestVersion(fInfo, domains.keySet()); if (inSync(fInfo, newestFileInfo)) { c++; } } if (includeDirs) { for (FileInfo fInfo : d.directories.values()) { if (filter.isExcluded(fInfo) || fInfo.isDeleted()) { continue; } FileInfo newestFileInfo = findNewestVersion(fInfo, domains.keySet()); if (inSync(fInfo, newestFileInfo)) { c++; } } } return c; } public long bytesInSync(String domain) { Domain d = getDomain(domain); long bytes = 0; for (FileInfo fInfo : d.files.values()) { if (filter.isExcluded(fInfo) || fInfo.isDeleted()) { continue; } FileInfo newestFileInfo = findNewestVersion(fInfo, domains.keySet()); if (inSync(fInfo, newestFileInfo)) { bytes += fInfo.getSize(); } } return bytes; } private static boolean inSync(FileInfo fileInfo, FileInfo newestFileInfo) { if (newestFileInfo == null) { // It is intended not to use Reject.ifNull for performance reasons. throw new NullPointerException("Newest FileInfo not found of " + fileInfo.toDetailString()); } if (fileInfo == null) { return false; } return !newestFileInfo.isNewerThan(fileInfo); } public void delete(String domain, FileInfo info) { if (info.isFile()) { getDomain(domain).files.remove(info); } else { logWarning("Deleting directory: " + info.toDetailString()); getDomain(domain).directories.remove(info); } } public void deleteDomain(String domain, int newInitialSize) { String theDomain = StringUtils.isBlank(domain) ? selfDomain : domain; domains.remove(theDomain); if (newInitialSize > 0) { domains.put(theDomain, new Domain(newInitialSize)); if (isFiner()) { logFiner("Created new domain (" + theDomain + ") with initial capacity " + newInitialSize); } } } public FileInfo find(FileInfo info, String domain) { FileInfo res = getDomain(domain).files.get(info); if (res != null) { return res; } return getDomain(domain).directories.get(info); } public Collection<FileInfo> findAllFiles(String domain) { return Collections.unmodifiableCollection(getDomain(domain).files .values()); } public Collection<DirectoryInfo> findAllDirectories(String domain) { return Collections.unmodifiableCollection(getDomain(domain).directories .values()); } public FileInfo findNewestVersion(FileInfo info, String... domainStrings) { return findNewestVersion(info, Arrays.asList(domainStrings)); } private FileInfo findNewestVersion(FileInfo info, Collection<String> domainStrings) { FileInfo newestVersion = null; for (String domain : domainStrings) { Domain d = getDomain(domain); // Get remote file FileInfo candidateFile = d.files.get(info); if (candidateFile == null) { candidateFile = d.directories.get(info); } if (candidateFile == null) { continue; } if (!candidateFile.isValid()) { continue; } // Check if remote file in newer if (newestVersion == null || candidateFile.isNewerThan(newestVersion)) { // log.finer("Newer version found at " + member); newestVersion = candidateFile; } } return newestVersion; } public void stop() { domains.clear(); } public void store(String domain, FileInfo... infos) { store(domain, Arrays.asList(infos)); } public void store(String domain, Collection<FileInfo> infos) { Domain d = getDomain(domain); for (FileInfo fileInfo : infos) { if (fileInfo.isFile()) { d.files.put(fileInfo, fileInfo); // Make sure not dir is left with name name. d.directories.remove(fileInfo); } else { if (isFiner()) { logFiner("Storing directory: " + fileInfo.toDetailString()); } d.directories.put((DirectoryInfo) fileInfo, (DirectoryInfo) fileInfo); // Make sure not file is left with name name. d.files.remove(fileInfo); } } } public Collection<FileInfo> findInDirectory(String domainStr, DirectoryInfo directoryInfo, boolean recursive) { FileInfoCriteria crit = new FileInfoCriteria(); crit.addDomain(domainStr); crit.setPath(directoryInfo); crit.setRecursive(recursive); return findFiles(crit); } public Collection<FileInfo> findInDirectory(String domainStr, String path, boolean recursive) { FileInfoCriteria crit = new FileInfoCriteria(); crit.addDomain(domainStr); crit.setPath(path); crit.setRecursive(recursive); return findFiles(crit); } public Collection<FileInfo> findFiles(FileInfoCriteria criteria) { Reject.ifTrue(criteria.getDomains().isEmpty(), "No domains/members selected in criteria"); String path = criteria.getPath(); if (path == null) { path = ""; } if (path.equals("/")) { path = ""; } if (path.length() > 0 && !path.endsWith("/")) { path += "/"; } boolean recursive = criteria.isRecursive(); Collection<FileInfo> items = new HashSet<FileInfo>(); for (String domainStr : criteria.getDomains()) { Domain domain = getDomain(domainStr); if (domain == null) { continue; } if (criteria.getType() == Type.DIRECTORIES_ONLY || criteria.getType() == Type.FILES_AND_DIRECTORIES) { for (DirectoryInfo dInfo : domain.directories.values()) { // if (filter.isExcluded(dInfo)) { // continue; // } if (criteria.getMaxResults() > 0 && items.size() >= criteria.getMaxResults()) { return items; } if (isInSubDir(dInfo, path, recursive) && !Util.equalsRelativeName(dInfo.getRelativeName(), path)) { if (!items.contains(dInfo) && matches(dInfo, criteria.getKeyWords())) { items.add(dInfo); } } } } if (criteria.getType() == Type.FILES_ONLY || criteria.getType() == Type.FILES_AND_DIRECTORIES) { for (FileInfo fInfo : domain.files.values()) { // if (filter.isExcluded(fInfo)) { // continue; // } if (criteria.getMaxResults() > 0 && items.size() >= criteria.getMaxResults()) { return items; } if (isInSubDir(fInfo, path, recursive)) { if (!items.contains(fInfo) && matches(fInfo, criteria.getKeyWords())) { items.add(fInfo); } } } } } return items; } public FileHistory getFileHistory(FileInfo fileInfo) { // TODO Auto-generated method stub return null; } // Internals ************************************************************** private Domain getDomain(String domain) { String theDomain = StringUtils.isBlank(domain) ? selfDomain : domain; synchronized (domains) { Domain d = domains.get(theDomain); if (d != null) { return d; } if (isFiner()) { logFiner("Domain '" + theDomain + "' created"); } d = new Domain(500); domains.put(theDomain, d); return d; } } /* * TODO: Performance optimization */ private boolean matches(FileInfo fInfo, Set<String> keyWords) { if (keyWords.isEmpty()) { return true; } String lower = fInfo.getRelativeName().toLowerCase(); for (String keyWord : keyWords) { if (!lower.contains(keyWord)) { return false; } } return true; } private boolean isInSubDir(FileInfo fInfo, String path, boolean recursive) { if (!fInfo.getRelativeName().startsWith(path)) { return false; } if (recursive) { return true; } int offset = path.length() + 1; int i = fInfo.getRelativeName().indexOf('/', offset); // No other subdirectory at end. return i < 0; } private static class Domain { private final ConcurrentMap<FileInfo, FileInfo> files; private final ConcurrentMap<DirectoryInfo, DirectoryInfo> directories = Util .createConcurrentHashMap(4); public Domain(int suggestedSize) { super(); files = Util.createConcurrentHashMap(suggestedSize); } public String toString() { return "Domain: " + files.size() + " files, " + directories.size() + " dirs"; } } }