package com.seafile.seadroid2.monitor; import java.io.File; import java.util.List; import java.util.Map; import org.apache.commons.io.monitor.FileAlterationListener; import org.apache.commons.io.monitor.FileAlterationObserver; import android.util.Log; import com.google.common.collect.Maps; import com.seafile.seadroid2.account.Account; import com.seafile.seadroid2.data.DataManager; import com.seafile.seadroid2.data.SeafCachedFile; import com.seafile.seadroid2.data.SeafRepo; import com.seafile.seadroid2.util.Utils; public class SeafileObserver implements FileAlterationListener { private static final String DEBUG_TAG = "SeafileObserver"; private Account account; private DataManager dataManager; private FileAlterationObserver alterationObserver; private final Map<String, SeafCachedFile> watchedFiles = Maps.newHashMap(); private final CachedFileChangedListener listener; private final RecentDownloadedFilesWorkAround recentDownloadedFiles = new RecentDownloadedFilesWorkAround(); public SeafileObserver(Account account, CachedFileChangedListener listener) { this.account = account; this.dataManager = new DataManager(account); this.listener = listener; alterationObserver = new FileAlterationObserver(getAccountDir()); alterationObserver.addListener(this); watchAllCachedFiles(); } private String getAccountDir() { return dataManager.getAccountDir(); } public FileAlterationObserver getAlterationObserver() { return alterationObserver; } private void watchAllCachedFiles() { List<SeafCachedFile> cachedfiles = dataManager.getCachedFiles(); for (SeafCachedFile cached : cachedfiles) { File file = dataManager.getLocalRepoFile(cached.repoName, cached.repoID, cached.path); if (file.exists()) { watchedFiles.put(file.getPath(), cached); } } Log.d(DEBUG_TAG, "watching files, # total watched " + watchedFiles.size()); } public void watchDownloadedFile(String repoID, String repoName, String pathInRepo, String localpath) { recentDownloadedFiles.addRecentDownloadedFile(localpath); SeafCachedFile cacheInfo = new SeafCachedFile(); cacheInfo.repoID = repoID; cacheInfo.repoName = repoName; cacheInfo.path = pathInRepo; watchedFiles.put(localpath, cacheInfo); Log.d(DEBUG_TAG, "start watch downloaded file " + pathInRepo + ", # total watched " + watchedFiles.size()); } public void setAccount(Account account) { this.account = account; } public Account getAccount() { return account; } public void startWatching() { try { alterationObserver.initialize(); } catch (Exception e) { } alterationObserver.checkAndNotify(); } public void stopWatching() { try { alterationObserver.destroy(); } catch (Exception e) { } } @Override public void onDirectoryChange(File directory) { Log.v(DEBUG_TAG, directory.getPath() + " was modified!"); } @Override public void onDirectoryCreate(File directory) { Log.v(DEBUG_TAG, directory.getPath() + " was created!"); } @Override public void onDirectoryDelete(File directory) { Log.v(DEBUG_TAG, directory.getPath() + " was deleted!"); } @Override public void onFileChange(File file) { String path = file.getPath(); if (recentDownloadedFiles.isRecentDownloadedFiles(path)) { Log.d(DEBUG_TAG, "ignore change signal for recent downloaded file " + path); return; } else { recentDownloadedFiles.removeRecentDownloadedFile(path); } Log.d(DEBUG_TAG, path + " was modified!"); SeafCachedFile cachedFile = watchedFiles.get(path); if (cachedFile != null) { final SeafRepo repo = dataManager.getCachedRepoByID(cachedFile.repoID); if (repo != null && repo.canLocalDecrypt()) { listener.onCachedBlocksChanged(account, cachedFile, file, repo.encVersion); } else { listener.onCachedFileChanged(account, cachedFile, file); } } } @Override public void onFileCreate(File file) { Log.v(DEBUG_TAG, file.getPath() + " was created!"); } @Override public void onFileDelete(File file) { Log.v(DEBUG_TAG, file.getPath() + " was deleted!"); String path = file.getPath(); watchedFiles.remove(path); recentDownloadedFiles.removeRecentDownloadedFile(path); Log.d(DEBUG_TAG, "now watching files, # total watched " + watchedFiles.size()); } @Override public void onStart(FileAlterationObserver fao) { Log.v(DEBUG_TAG, fao.toString() + " start checking event!"); } @Override public void onStop(FileAlterationObserver fao) { Log.v(DEBUG_TAG, fao.toString() + " finished checking event!"); } /** * When user downloads a file, the outdated file is replaced, so the onFileChange signal would * be triggered, which we should not treat it as a modification. This class provides a workaroud * for this. */ private static class RecentDownloadedFilesWorkAround { private final Map<String, Long> recentDownloadedFiles = Maps.newConcurrentMap(); public boolean isRecentDownloadedFiles(String filePath) { Long timestamp = recentDownloadedFiles.get(filePath); if (timestamp != null) { long timeWhenDownloaded = timestamp; long now = Utils.now(); if (now - timeWhenDownloaded < 10000) { return true; } } return false; } public void addRecentDownloadedFile(String filePath) { recentDownloadedFiles.put(filePath, Utils.now()); } public void removeRecentDownloadedFile(String filePath) { recentDownloadedFiles.remove(filePath); Log.d(DEBUG_TAG, "remove recent file, # total watched " + recentDownloadedFiles.size()); } } }