/** * FreeDesktopSearch - A Search Engine for your Desktop * Copyright (C) 2013 Mirko Sertic * * This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public * License as published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along with this program; if not, see <http://www.gnu.org/licenses/>. */ package de.mirkosertic.desktopsearch; import org.apache.log4j.Logger; import java.io.File; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.attribute.BasicFileAttributes; import java.util.HashMap; import java.util.Map; class Backend implements ConfigurationChangeListener { private static final Logger LOGGER = Logger.getLogger(Backend.class); private LuceneIndexHandler luceneIndexHandler; private final ContentExtractor contentExtractor; private ProgressListener progressListener; private final Map<Configuration.CrawlLocation, DirectoryWatcher> locations; private final DirectoryListener directoryListener; private final ExecutorPool executorPool; private final Notifier notifier; private final WatchServiceCache watchServiceCache; private final PreviewProcessor previewProcessor; private Configuration configuration; public Backend(Notifier aNotifier, Configuration aConfiguration, PreviewProcessor aPreviewProcessor) throws IOException { notifier = aNotifier; previewProcessor = aPreviewProcessor; locations = new HashMap<>(); executorPool = new ExecutorPool(); watchServiceCache = new WatchServiceCache(); contentExtractor = new ContentExtractor(aConfiguration); directoryListener = new DirectoryListener() { @Override public void fileDeleted(Configuration.CrawlLocation aFileSystemLocation, Path aFile) { try { String theFilename = aFile.toString(); if (luceneIndexHandler.checkIfExists(theFilename)) { luceneIndexHandler.removeFromIndex(theFilename); aNotifier.showInformation("Deleted " + aFile.getFileName()); } } catch (Exception e) { aNotifier.showError("Error removing " + aFile.getFileName(), e); } } @Override public void fileFoundByCrawler(Configuration.CrawlLocation aLocation, Path aFile) { fileCreatedOrModified(aLocation, aFile, false); } @Override public void fileCreatedOrModified(Configuration.CrawlLocation aLocation, Path aFile) { fileCreatedOrModified(aLocation, aFile, true); } private void fileCreatedOrModified(Configuration.CrawlLocation aLocation, Path aFile, boolean aShowInformation) { String theFileName = aFile.toString(); if (contentExtractor.supportsFile(theFileName)) { try { progressListener.newFileFound(theFileName); BasicFileAttributes theAttributes = Files.readAttributes(aFile, BasicFileAttributes.class); UpdateCheckResult theUpdateCheckResult = luceneIndexHandler.checkIfModified(theFileName, theAttributes.lastModifiedTime().toMillis()); if (theUpdateCheckResult == UpdateCheckResult.UPDATED) { if (aShowInformation) { notifier.showInformation("Reindexed " + aFile.getFileName()); } Content theContent = contentExtractor.extractContentFrom(aFile, theAttributes); if (theContent != null) { luceneIndexHandler.addToIndex(aLocation.getId(), theContent); } } } catch (Exception e) { aNotifier.showError("Error re-inxeding " + aFile.getFileName(), e); } } } }; configurationUpdated(aConfiguration); } @Override public void configurationUpdated(Configuration aConfiguration) throws IOException { setIndexLocation(aConfiguration); configuration = aConfiguration; locations.values().stream().forEach(DirectoryWatcher::stopWatching); locations.clear(); aConfiguration.getCrawlLocations().stream().forEach(e -> { File theDirectory = e.getDirectory(); if (theDirectory.exists() && theDirectory.isDirectory()) { try { add(e); } catch (IOException e1) { LOGGER.error("Error setting filesystem location for " + theDirectory, e1); } } }); } public void setProgressListener(ProgressListener progressListener) { this.progressListener = progressListener; } private void add(Configuration.CrawlLocation aLocation) throws IOException { locations.put(aLocation, new DirectoryWatcher(watchServiceCache, aLocation, DirectoryWatcher.DEFAULT_WAIT_FOR_ACTION, directoryListener, executorPool).startWatching()); } private void setIndexLocation(Configuration aConfiguration) throws IOException { if (luceneIndexHandler != null) { shutdown(); } AnalyzerCache theCache = new AnalyzerCache(aConfiguration); luceneIndexHandler = new LuceneIndexHandler(aConfiguration, theCache, executorPool, previewProcessor); } public void crawlLocations() throws IOException { luceneIndexHandler.crawlingStarts(); Thread theRunner = new Thread() { @Override public void run() { try { luceneIndexHandler.cleanupDeadContent(); } catch (IOException e) { LOGGER.error("Error removing dead content", e); } locations.values().stream().forEach(theWatcher -> { try { theWatcher.crawl(); } catch (Exception e) { LOGGER.error("Error while crawling", e); } }); progressListener.crawlingFinished(); } }; theRunner.start(); } public void shutdown() { luceneIndexHandler.shutdown(); } public QueryResult performQuery(String aQueryString, String aBacklink, String aBasePath, Map<String, String> aDrilldownDimensions) throws IOException { return luceneIndexHandler.performQuery(aQueryString, aBacklink, aBasePath, configuration, aDrilldownDimensions); } public Suggestion[] findSuggestionTermsFor(String aTerm) throws IOException { return luceneIndexHandler.findSuggestionTermsFor(aTerm); } public File getFileOnDiskForDocument(String aDocumentID) throws IOException { return luceneIndexHandler.getFileOnDiskForDocument(aDocumentID); } }