Loading app/build.gradle +15 −18 Original line number Diff line number Diff line Loading @@ -23,7 +23,7 @@ def getTestProp(String propName) { android { compileSdkVersion 28 compileSdkVersion 31 defaultConfig { applicationId "foundation.e.drive" minSdkVersion 26 Loading Loading @@ -61,27 +61,24 @@ android { dependencies { api project(':NextcloudLib') implementation fileTree(include: ['*.jar'], dir: 'libs') implementation 'com.android.support:appcompat-v7:26.1.0' androidTestImplementation 'com.android.support.test:runner:1.0.2' androidTestImplementation 'com.android.support.test:rules:1.0.2' androidTestImplementation 'androidx.annotation:annotation:1.3.0' androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.1' api 'androidx.annotation:annotation:1.3.0' api project(':NextcloudLib') implementation 'androidx.core:core:1.6.0' //start to add lib for test - 1/4/21 //@TODO: add junit runner as lib for testImplementation def work_version = "2.7.1" // (Java only) implementation "androidx.work:work-runtime:$work_version" testImplementation 'com.android.support.test:runner:1.0.2' testImplementation 'com.android.support.test:rules:1.0.2' androidTestImplementation 'androidx.test:runner:1.4.0' androidTestImplementation 'androidx.test:rules:1.4.0' androidTestImplementation 'androidx.annotation:annotation:1.3.0' androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0' androidTestImplementation 'junit:junit:4.12' testImplementation 'androidx.test:runner:1.4.0' testImplementation 'androidx.test:rules:1.4.0' testImplementation 'junit:junit:4.12' //testImplementation 'org.robolectric:robolectric:4.4' //need AndroidX testImplementation "org.robolectric:robolectric:3.8" testImplementation 'org.robolectric:robolectric:4.4' testImplementation('org.mockito:mockito-inline:3.4.0') //testImplementation Libs.AndroidX.Test.archCoreTesting //TODO: replace by not android X version //implementation 'androidx.localbroadcastmanager:localbroadcastmanager:1.0.0' androidTestImplementation 'junit:junit:4.12' } app/src/main/AndroidManifest.xml +8 −15 Original line number Diff line number Diff line Loading @@ -8,10 +8,11 @@ <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" /> <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" /> <!-- needed for PersistedJob --> <uses-permission android:name="android.permission.GET_ACCOUNTS" /> <uses-permission android:name="android.permission.READ_CONTACTS" /> <uses-permission android:name="android.permission.READ_SYNC_SETTINGS" /> <uses-permission android:name="android.permission.KILL_BACKGROUND_PROCESSES" /> <uses-permission android:name="android.permission.ACCESS_NOTIFICATION_POLICY" /> <uses-permission android:name="android.permission.QUERY_ALL_PACKAGES" /> <uses-permission android:name="android.permission.READ_CONTACTS" /> <permission android:name="android.permission.FORCE_STOP_PACKAGES" Loading @@ -25,6 +26,7 @@ android:icon="@mipmap/ic_eelo" android:label="@string/app_name" android:roundIcon="@mipmap/ic_eelo_round"> <!-- widget --> <activity android:exported="true" android:name=".activity.AccountsActivity" Loading @@ -45,7 +47,7 @@ android:resource="@xml/e_drive_widget_info" /> </receiver> <!-- Providers --> <!-- eDrive --> <provider android:name=".providers.MediasSyncProvider" android:authorities="foundation.e.drive.providers.MediasSyncProvider" Loading @@ -57,7 +59,9 @@ android:authorities="foundation.e.drive.providers.SettingsSyncProvider" android:enabled="true" android:exported="true" android:label="Application settings" /> <!-- Services --> android:label="Application settings" /> <!-- Services --> <service android:name=".services.InitializerService" android:enabled="true" Loading @@ -74,22 +78,11 @@ <action android:name="drive.services.ResetService" /> </intent-filter> </service> <service android:name=".jobs.ScannerJob" android:permission="android.permission.BIND_JOB_SERVICE" /> <service android:name=".services.ObserverService" android:enabled="true" /> <service android:name=".services.OperationManagerService" /> <service android:name=".services.SynchronizationService" /> <receiver android:name=".receivers.BatteryStateReceiver" android:enabled="true"> <intent-filter> <action android:name="android.intent.action.BATTERY_LOW" /> <action android:name="android.intent.action.BATTERY_OKAY" /> </intent-filter> </receiver> <receiver android:name=".receivers.PackageEventReceiver" android:enabled="true"> Loading app/src/main/java/foundation/e/drive/EdriveApplication.java +43 −19 Original line number Diff line number Diff line Loading @@ -11,13 +11,20 @@ package foundation.e.drive; import android.accounts.Account; import android.accounts.AccountManager; import android.app.Application; import android.app.NotificationChannel; import android.app.NotificationManager; import android.content.Context; import android.content.Intent; import android.content.SharedPreferences; import android.os.Build; import android.os.Environment; import android.util.Log; import foundation.e.drive.FileObservers.FileEventListener; import foundation.e.drive.FileObservers.RecursiveFileObserver; import foundation.e.drive.services.SynchronizationService; import foundation.e.drive.utils.AppConstants; import foundation.e.drive.utils.CommonUtils; import foundation.e.drive.utils.JobUtils; /** * Class representing the eDrive application. Loading @@ -26,42 +33,59 @@ import foundation.e.drive.utils.JobUtils; public class EdriveApplication extends Application { private static final String TAG = "EdriveApplication"; private RecursiveFileObserver mFileObserver = null; private FileEventListener fileEventListener; @Override public void onCreate() { super.onCreate(); fileEventListener = new FileEventListener(getApplicationContext()); Log.i(TAG, "Starting"); resetOperationManagerSetting(); final String pathForObserver = Environment.getExternalStorageDirectory().getAbsolutePath(); mFileObserver = new RecursiveFileObserver(getApplicationContext(), pathForObserver, fileEventListener); SharedPreferences prefs = getSharedPreferences(AppConstants.SHARED_PREFERENCE_NAME, Context.MODE_PRIVATE); CommonUtils.createNotificationChannel(getApplicationContext()); if (prefs.getString(AccountManager.KEY_ACCOUNT_NAME, null) != null) { scheduleScannerJob(); Log.d(TAG, "Account already registered"); startRecursiveFileObserver(); Intent SynchronizationServiceIntent = new Intent(getApplicationContext(), SynchronizationService.class); startService(SynchronizationServiceIntent); } else { Account mAccount = CommonUtils.getAccount(getString(R.string.eelo_account_type), AccountManager.get(this)); if (mAccount != null) { if (mAccount == null) {return ;} String accountName = mAccount.name; String accountType = mAccount.type; prefs.edit().putString(AccountManager.KEY_ACCOUNT_NAME, accountName) .putString(AccountManager.KEY_ACCOUNT_TYPE, accountType) .apply(); scheduleScannerJob(); } } } private void scheduleScannerJob() { if (!JobUtils.isScannerJobRegistered(this)) { JobUtils.scheduleScannerJob(this); /** * Start Recursive FileObserver if not already watching */ public void startRecursiveFileObserver(){ if (!mFileObserver.isWatching()) { fileEventListener.bindToSynchronizationService(); mFileObserver.startWatching(); Log.d(TAG, "Starting RecursiveFileObserver on root folder"); } else { Log.w(TAG, "RecursiveFileObserver (for media's root folder) was already running"); } } private void resetOperationManagerSetting() { getSharedPreferences(AppConstants.SHARED_PREFERENCE_NAME, Context.MODE_PRIVATE).edit() .putBoolean(AppConstants.KEY_OMS_IS_WORKING, false) .apply(); public void stopRecursiveFileObserver(){ mFileObserver.stopWatching(); fileEventListener.unbindFromSynchronizationService(); Log.d(TAG, "RecursiveFileObserver on root folder stops watching "); } @Override Loading app/src/main/java/foundation/e/drive/FileObservers/FileEventListener.java 0 → 100644 +178 −0 Original line number Diff line number Diff line /* * Copyright © Narinder Rana (/e/ foundation). * Copyright © Vincent Bourgmayer (/e/ foundation). * All rights reserved. This program and the accompanying materials * are made available under the terms of the GNU Public License v3.0 * which accompanies this distribution, and is available at * http://www.gnu.org/licenses/gpl.html */ package foundation.e.drive.FileObservers; import static foundation.e.drive.models.SyncRequest.Type.UPLOAD; import android.content.Context; import android.content.Intent; import android.os.FileObserver; import android.util.Log; import com.owncloud.android.lib.resources.files.FileUtils; import java.io.File; import foundation.e.drive.database.DbHelper; import foundation.e.drive.models.SyncRequest; import foundation.e.drive.models.SyncedFileState; import foundation.e.drive.models.SyncedFolder; import foundation.e.drive.services.SynchronizationService; import foundation.e.drive.utils.CommonUtils; import foundation.e.drive.utils.SynchronizationServiceConnection; /** * @author Narinder Rana * @author vincent Bourgmayer */ public class FileEventListener { private static final String TAG = FileEventListener.class.getSimpleName(); private final Context appContext; private final SynchronizationServiceConnection serviceConnection = new SynchronizationServiceConnection(); public FileEventListener(Context applicationContext) { this.appContext = applicationContext; } public void onEvent(int event, File file) { if (event == FileObserver.DELETE) { if (file.isDirectory()) { handleDirectoryDelete(file); } else { handleFileDelete(file); } } else if (event == FileObserver.CLOSE_WRITE) { if (file.isDirectory()) { handleDirectoryCloseWrite(file); } else { handleFileCloseWrite(file); } } else if (event == FileObserver.MOVE_SELF){ Log.d(TAG, file.getAbsolutePath() + " has been moved. Not handled yet"); } } private void sendSyncRequestToSynchronizationService(SyncRequest request) { Log.d(TAG, "Sending a SyncRequest for " + request.getSyncedFileState().getName()); if (serviceConnection.isBoundToSynchronizationService()) { serviceConnection.getSynchronizationService().queueOperation(request); serviceConnection.getSynchronizationService().startSynchronization(); }else{ Log.w(TAG, "Impossible to send SyncRequest. FileEventListener is not bound to SynchronizationService"); } } private void handleDirectoryCloseWrite(File directory) { final String fileLocalPath = CommonUtils.getLocalPath(directory); Log.d(TAG, "handleDirectoryCloseWrite(" + fileLocalPath + ")"); SyncedFolder folder = DbHelper.getSyncedFolderByLocalPath(fileLocalPath, appContext); if (folder == null) { //it's a directory creation final String parentPath = CommonUtils.getLocalPath(directory.getParentFile()); SyncedFolder parentFolder = DbHelper.getSyncedFolderByLocalPath(parentPath, appContext); if (parentFolder != null) { //if parent is in the DB folder = new SyncedFolder(parentFolder, directory.getName() + FileUtils.PATH_SEPARATOR, directory.lastModified(), ""); DbHelper.insertSyncedFolder(folder, appContext); } } else { //It's a directory update folder.setLastModified(directory.lastModified()); DbHelper.updateSyncedFolder(folder, appContext); } } private void handleDirectoryDelete(File directory) { final String fileLocalPath = CommonUtils.getLocalPath(directory); Log.d(TAG, "handleDirectoryDelete("+fileLocalPath+")"); SyncedFolder folder = DbHelper.getSyncedFolderByLocalPath(fileLocalPath, appContext); if (folder == null) { //look for parent final String parentPath = CommonUtils.getLocalPath(directory.getParentFile()); SyncedFolder parentFolder = DbHelper.getSyncedFolderByLocalPath(parentPath, appContext); if (parentFolder != null ) { //if parent is in the DB folder = new SyncedFolder(parentFolder, directory.getName()+ FileUtils.PATH_SEPARATOR, directory.lastModified(), ""); folder.setEnabled(false); DbHelper.insertSyncedFolder(folder, appContext); } } else { //If already in DB if (folder.isEnabled()) { folder.setEnabled(false); DbHelper.updateSyncedFolder(folder, appContext); } } } private void handleFileCloseWrite(File file) { final String fileLocalPath = CommonUtils.getLocalPath(file); Log.d(TAG, "handleFileCloseWrite("+fileLocalPath+")"); SyncRequest request = null; SyncedFileState fileState = DbHelper.loadSyncedFile( appContext, fileLocalPath, true); if (fileState == null) { //New file discovered final String parentPath = CommonUtils.getLocalPath(file.getParentFile()); SyncedFolder parentFolder = DbHelper.getSyncedFolderByLocalPath(parentPath, appContext); if (parentFolder == null || !parentFolder.isEnabled()) { Log.w(TAG, "Won't send sync request: no parent are known for new file: "+file.getName()); return; } int scannableValue = 0; if (parentFolder.isEnabled()) { if (parentFolder.isScanRemote()) scannableValue++; if (parentFolder.isScanLocal()) scannableValue += 2; } final String remotePath = parentFolder.getRemoteFolder()+file.getName(); fileState = new SyncedFileState(-1, file.getName(), CommonUtils.getLocalPath(file), remotePath, "", 0L, parentFolder.getId(), parentFolder.isMediaType(), scannableValue); int storedId = DbHelper.manageSyncedFileStateDB(fileState, "INSERT", appContext); if (storedId > 0) { fileState.setId(storedId); request = new SyncRequest(fileState, UPLOAD); } else { Log.w(TAG, "New File " + file.getName() + " observed but impossible to insert it in DB"); } } else { //File update if (fileState.getScannable() > 1) { request = new SyncRequest(fileState, UPLOAD); } } if (request != null) { sendSyncRequestToSynchronizationService(request); } } private void handleFileDelete(File file) { final String fileLocalPath = CommonUtils.getLocalPath(file); Log.d(TAG, "handleFileDelete("+fileLocalPath+")"); SyncedFileState fileState = DbHelper.loadSyncedFile( appContext, fileLocalPath, true); if (fileState == null) { Log.d(TAG, "Ignore event because file is not already in database"); return; } //If already in DB if (fileState.getScannable() > 0) { fileState.setScannable(0); DbHelper.manageSyncedFileStateDB(fileState, "UPDATE", appContext); } } public void unbindFromSynchronizationService(){ if(serviceConnection.isBoundToSynchronizationService()) appContext.unbindService(serviceConnection); else Log.w(TAG, "Not bound to SynchronizationService: can't unbind."); } public void bindToSynchronizationService(){ Log.d(TAG, "bindToSynchronizationService()"); final Intent SynchronizationServiceIntent = new Intent(appContext, SynchronizationService.class); appContext.bindService(SynchronizationServiceIntent, serviceConnection, Context.BIND_AUTO_CREATE); } } app/src/main/java/foundation/e/drive/FileObservers/RecursiveFileObserver.java 0 → 100644 +174 −0 Original line number Diff line number Diff line /* * Copyright © Narinder Rana (/e/ foundation). * All rights reserved. This program and the accompanying materials * are made available under the terms of the GNU Public License v3.0 * which accompanies this distribution, and is available at * http://www.gnu.org/licenses/gpl.html */ package foundation.e.drive.FileObservers; import android.content.Context; import android.os.FileObserver; import java.io.File; import java.io.FileFilter; import java.util.HashMap; import java.util.List; import java.util.Stack; import foundation.e.drive.database.DbHelper; import foundation.e.drive.models.SyncedFolder; /** * @author Narinder Rana * @author Vincent Bourgmayer */ public class RecursiveFileObserver extends FileObserver { private final HashMap<String, FileObserver> observers = new HashMap<>(); private static final FileFilter WATCHABLE_DIRECTORIES_FILTER = new FileFilter() { @Override public boolean accept(File file) { return file.isDirectory() && !file.getName().startsWith("."); } }; private boolean watching = false; private final Context applicationContext; private String path; private int mask; private FileEventListener listener; public RecursiveFileObserver(Context applicationContext, String path, FileEventListener listener) { this(applicationContext, path, ALL_EVENTS, listener); } public RecursiveFileObserver(Context applicationContext, String path, int mask, FileEventListener listener) { super(path, mask); this.path = path; this.mask = mask | FileObserver.CREATE | FileObserver.DELETE_SELF; this.listener = listener; this.applicationContext = applicationContext; } @Override public void onEvent(int event, String path) { File file; if (path == null) { file = new File(this.path); } else { file = new File(this.path, path); } notify(event, file); } private void notify(int event, File file) { if (listener != null) { listener.onEvent(event & FileObserver.ALL_EVENTS, file); } } @Override public void startWatching() { Stack<String> stack = new Stack<>(); List<SyncedFolder> mSyncedFolders = DbHelper.getAllSyncedFolders(applicationContext); if (!mSyncedFolders.isEmpty()){ for (SyncedFolder syncedFolder:mSyncedFolders){ stack.push(syncedFolder.getLocalFolder()); stack.push(syncedFolder.getRemoteFolder()); } watching = true; } // Recursively watch all child directories while (!stack.empty()) { String parent = stack.pop(); startWatching(parent); File path = new File(parent); File[] files = path.listFiles(WATCHABLE_DIRECTORIES_FILTER); if (files != null) { for (File file : files) { stack.push(file.getAbsolutePath()); } } } } /** * Start watching a single file * @param path */ private void startWatching(String path) { synchronized (observers) { FileObserver observer = observers.remove(path); if (observer != null) { observer.stopWatching(); } observer = new SingleFileObserver(path, mask); observer.startWatching(); observers.put(path, observer); } } @Override public void stopWatching() { for (FileObserver observer : observers.values()) { observer.stopWatching(); } observers.clear(); watching = false; } /** * Stop watching a single file * @param path */ private void stopWatching(String path) { synchronized (observers) { FileObserver observer = observers.remove(path); if (observer != null) { observer.stopWatching(); } } } public boolean isWatching(){ return watching; } private class SingleFileObserver extends FileObserver { private String filePath; public SingleFileObserver(String path, int mask) { super(path, mask); filePath = path; } @Override public void onEvent(int event, String path) { File file; if (path == null) { file = new File(filePath); } else { file = new File(filePath, path); } switch (event & FileObserver.ALL_EVENTS) { case DELETE_SELF: RecursiveFileObserver.this.stopWatching(filePath); break; case CREATE: if (WATCHABLE_DIRECTORIES_FILTER.accept(file)) { RecursiveFileObserver.this.startWatching(file.getAbsolutePath()); } break; } RecursiveFileObserver.this.notify(event, file); } } } Loading
app/build.gradle +15 −18 Original line number Diff line number Diff line Loading @@ -23,7 +23,7 @@ def getTestProp(String propName) { android { compileSdkVersion 28 compileSdkVersion 31 defaultConfig { applicationId "foundation.e.drive" minSdkVersion 26 Loading Loading @@ -61,27 +61,24 @@ android { dependencies { api project(':NextcloudLib') implementation fileTree(include: ['*.jar'], dir: 'libs') implementation 'com.android.support:appcompat-v7:26.1.0' androidTestImplementation 'com.android.support.test:runner:1.0.2' androidTestImplementation 'com.android.support.test:rules:1.0.2' androidTestImplementation 'androidx.annotation:annotation:1.3.0' androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.1' api 'androidx.annotation:annotation:1.3.0' api project(':NextcloudLib') implementation 'androidx.core:core:1.6.0' //start to add lib for test - 1/4/21 //@TODO: add junit runner as lib for testImplementation def work_version = "2.7.1" // (Java only) implementation "androidx.work:work-runtime:$work_version" testImplementation 'com.android.support.test:runner:1.0.2' testImplementation 'com.android.support.test:rules:1.0.2' androidTestImplementation 'androidx.test:runner:1.4.0' androidTestImplementation 'androidx.test:rules:1.4.0' androidTestImplementation 'androidx.annotation:annotation:1.3.0' androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0' androidTestImplementation 'junit:junit:4.12' testImplementation 'androidx.test:runner:1.4.0' testImplementation 'androidx.test:rules:1.4.0' testImplementation 'junit:junit:4.12' //testImplementation 'org.robolectric:robolectric:4.4' //need AndroidX testImplementation "org.robolectric:robolectric:3.8" testImplementation 'org.robolectric:robolectric:4.4' testImplementation('org.mockito:mockito-inline:3.4.0') //testImplementation Libs.AndroidX.Test.archCoreTesting //TODO: replace by not android X version //implementation 'androidx.localbroadcastmanager:localbroadcastmanager:1.0.0' androidTestImplementation 'junit:junit:4.12' }
app/src/main/AndroidManifest.xml +8 −15 Original line number Diff line number Diff line Loading @@ -8,10 +8,11 @@ <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" /> <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" /> <!-- needed for PersistedJob --> <uses-permission android:name="android.permission.GET_ACCOUNTS" /> <uses-permission android:name="android.permission.READ_CONTACTS" /> <uses-permission android:name="android.permission.READ_SYNC_SETTINGS" /> <uses-permission android:name="android.permission.KILL_BACKGROUND_PROCESSES" /> <uses-permission android:name="android.permission.ACCESS_NOTIFICATION_POLICY" /> <uses-permission android:name="android.permission.QUERY_ALL_PACKAGES" /> <uses-permission android:name="android.permission.READ_CONTACTS" /> <permission android:name="android.permission.FORCE_STOP_PACKAGES" Loading @@ -25,6 +26,7 @@ android:icon="@mipmap/ic_eelo" android:label="@string/app_name" android:roundIcon="@mipmap/ic_eelo_round"> <!-- widget --> <activity android:exported="true" android:name=".activity.AccountsActivity" Loading @@ -45,7 +47,7 @@ android:resource="@xml/e_drive_widget_info" /> </receiver> <!-- Providers --> <!-- eDrive --> <provider android:name=".providers.MediasSyncProvider" android:authorities="foundation.e.drive.providers.MediasSyncProvider" Loading @@ -57,7 +59,9 @@ android:authorities="foundation.e.drive.providers.SettingsSyncProvider" android:enabled="true" android:exported="true" android:label="Application settings" /> <!-- Services --> android:label="Application settings" /> <!-- Services --> <service android:name=".services.InitializerService" android:enabled="true" Loading @@ -74,22 +78,11 @@ <action android:name="drive.services.ResetService" /> </intent-filter> </service> <service android:name=".jobs.ScannerJob" android:permission="android.permission.BIND_JOB_SERVICE" /> <service android:name=".services.ObserverService" android:enabled="true" /> <service android:name=".services.OperationManagerService" /> <service android:name=".services.SynchronizationService" /> <receiver android:name=".receivers.BatteryStateReceiver" android:enabled="true"> <intent-filter> <action android:name="android.intent.action.BATTERY_LOW" /> <action android:name="android.intent.action.BATTERY_OKAY" /> </intent-filter> </receiver> <receiver android:name=".receivers.PackageEventReceiver" android:enabled="true"> Loading
app/src/main/java/foundation/e/drive/EdriveApplication.java +43 −19 Original line number Diff line number Diff line Loading @@ -11,13 +11,20 @@ package foundation.e.drive; import android.accounts.Account; import android.accounts.AccountManager; import android.app.Application; import android.app.NotificationChannel; import android.app.NotificationManager; import android.content.Context; import android.content.Intent; import android.content.SharedPreferences; import android.os.Build; import android.os.Environment; import android.util.Log; import foundation.e.drive.FileObservers.FileEventListener; import foundation.e.drive.FileObservers.RecursiveFileObserver; import foundation.e.drive.services.SynchronizationService; import foundation.e.drive.utils.AppConstants; import foundation.e.drive.utils.CommonUtils; import foundation.e.drive.utils.JobUtils; /** * Class representing the eDrive application. Loading @@ -26,42 +33,59 @@ import foundation.e.drive.utils.JobUtils; public class EdriveApplication extends Application { private static final String TAG = "EdriveApplication"; private RecursiveFileObserver mFileObserver = null; private FileEventListener fileEventListener; @Override public void onCreate() { super.onCreate(); fileEventListener = new FileEventListener(getApplicationContext()); Log.i(TAG, "Starting"); resetOperationManagerSetting(); final String pathForObserver = Environment.getExternalStorageDirectory().getAbsolutePath(); mFileObserver = new RecursiveFileObserver(getApplicationContext(), pathForObserver, fileEventListener); SharedPreferences prefs = getSharedPreferences(AppConstants.SHARED_PREFERENCE_NAME, Context.MODE_PRIVATE); CommonUtils.createNotificationChannel(getApplicationContext()); if (prefs.getString(AccountManager.KEY_ACCOUNT_NAME, null) != null) { scheduleScannerJob(); Log.d(TAG, "Account already registered"); startRecursiveFileObserver(); Intent SynchronizationServiceIntent = new Intent(getApplicationContext(), SynchronizationService.class); startService(SynchronizationServiceIntent); } else { Account mAccount = CommonUtils.getAccount(getString(R.string.eelo_account_type), AccountManager.get(this)); if (mAccount != null) { if (mAccount == null) {return ;} String accountName = mAccount.name; String accountType = mAccount.type; prefs.edit().putString(AccountManager.KEY_ACCOUNT_NAME, accountName) .putString(AccountManager.KEY_ACCOUNT_TYPE, accountType) .apply(); scheduleScannerJob(); } } } private void scheduleScannerJob() { if (!JobUtils.isScannerJobRegistered(this)) { JobUtils.scheduleScannerJob(this); /** * Start Recursive FileObserver if not already watching */ public void startRecursiveFileObserver(){ if (!mFileObserver.isWatching()) { fileEventListener.bindToSynchronizationService(); mFileObserver.startWatching(); Log.d(TAG, "Starting RecursiveFileObserver on root folder"); } else { Log.w(TAG, "RecursiveFileObserver (for media's root folder) was already running"); } } private void resetOperationManagerSetting() { getSharedPreferences(AppConstants.SHARED_PREFERENCE_NAME, Context.MODE_PRIVATE).edit() .putBoolean(AppConstants.KEY_OMS_IS_WORKING, false) .apply(); public void stopRecursiveFileObserver(){ mFileObserver.stopWatching(); fileEventListener.unbindFromSynchronizationService(); Log.d(TAG, "RecursiveFileObserver on root folder stops watching "); } @Override Loading
app/src/main/java/foundation/e/drive/FileObservers/FileEventListener.java 0 → 100644 +178 −0 Original line number Diff line number Diff line /* * Copyright © Narinder Rana (/e/ foundation). * Copyright © Vincent Bourgmayer (/e/ foundation). * All rights reserved. This program and the accompanying materials * are made available under the terms of the GNU Public License v3.0 * which accompanies this distribution, and is available at * http://www.gnu.org/licenses/gpl.html */ package foundation.e.drive.FileObservers; import static foundation.e.drive.models.SyncRequest.Type.UPLOAD; import android.content.Context; import android.content.Intent; import android.os.FileObserver; import android.util.Log; import com.owncloud.android.lib.resources.files.FileUtils; import java.io.File; import foundation.e.drive.database.DbHelper; import foundation.e.drive.models.SyncRequest; import foundation.e.drive.models.SyncedFileState; import foundation.e.drive.models.SyncedFolder; import foundation.e.drive.services.SynchronizationService; import foundation.e.drive.utils.CommonUtils; import foundation.e.drive.utils.SynchronizationServiceConnection; /** * @author Narinder Rana * @author vincent Bourgmayer */ public class FileEventListener { private static final String TAG = FileEventListener.class.getSimpleName(); private final Context appContext; private final SynchronizationServiceConnection serviceConnection = new SynchronizationServiceConnection(); public FileEventListener(Context applicationContext) { this.appContext = applicationContext; } public void onEvent(int event, File file) { if (event == FileObserver.DELETE) { if (file.isDirectory()) { handleDirectoryDelete(file); } else { handleFileDelete(file); } } else if (event == FileObserver.CLOSE_WRITE) { if (file.isDirectory()) { handleDirectoryCloseWrite(file); } else { handleFileCloseWrite(file); } } else if (event == FileObserver.MOVE_SELF){ Log.d(TAG, file.getAbsolutePath() + " has been moved. Not handled yet"); } } private void sendSyncRequestToSynchronizationService(SyncRequest request) { Log.d(TAG, "Sending a SyncRequest for " + request.getSyncedFileState().getName()); if (serviceConnection.isBoundToSynchronizationService()) { serviceConnection.getSynchronizationService().queueOperation(request); serviceConnection.getSynchronizationService().startSynchronization(); }else{ Log.w(TAG, "Impossible to send SyncRequest. FileEventListener is not bound to SynchronizationService"); } } private void handleDirectoryCloseWrite(File directory) { final String fileLocalPath = CommonUtils.getLocalPath(directory); Log.d(TAG, "handleDirectoryCloseWrite(" + fileLocalPath + ")"); SyncedFolder folder = DbHelper.getSyncedFolderByLocalPath(fileLocalPath, appContext); if (folder == null) { //it's a directory creation final String parentPath = CommonUtils.getLocalPath(directory.getParentFile()); SyncedFolder parentFolder = DbHelper.getSyncedFolderByLocalPath(parentPath, appContext); if (parentFolder != null) { //if parent is in the DB folder = new SyncedFolder(parentFolder, directory.getName() + FileUtils.PATH_SEPARATOR, directory.lastModified(), ""); DbHelper.insertSyncedFolder(folder, appContext); } } else { //It's a directory update folder.setLastModified(directory.lastModified()); DbHelper.updateSyncedFolder(folder, appContext); } } private void handleDirectoryDelete(File directory) { final String fileLocalPath = CommonUtils.getLocalPath(directory); Log.d(TAG, "handleDirectoryDelete("+fileLocalPath+")"); SyncedFolder folder = DbHelper.getSyncedFolderByLocalPath(fileLocalPath, appContext); if (folder == null) { //look for parent final String parentPath = CommonUtils.getLocalPath(directory.getParentFile()); SyncedFolder parentFolder = DbHelper.getSyncedFolderByLocalPath(parentPath, appContext); if (parentFolder != null ) { //if parent is in the DB folder = new SyncedFolder(parentFolder, directory.getName()+ FileUtils.PATH_SEPARATOR, directory.lastModified(), ""); folder.setEnabled(false); DbHelper.insertSyncedFolder(folder, appContext); } } else { //If already in DB if (folder.isEnabled()) { folder.setEnabled(false); DbHelper.updateSyncedFolder(folder, appContext); } } } private void handleFileCloseWrite(File file) { final String fileLocalPath = CommonUtils.getLocalPath(file); Log.d(TAG, "handleFileCloseWrite("+fileLocalPath+")"); SyncRequest request = null; SyncedFileState fileState = DbHelper.loadSyncedFile( appContext, fileLocalPath, true); if (fileState == null) { //New file discovered final String parentPath = CommonUtils.getLocalPath(file.getParentFile()); SyncedFolder parentFolder = DbHelper.getSyncedFolderByLocalPath(parentPath, appContext); if (parentFolder == null || !parentFolder.isEnabled()) { Log.w(TAG, "Won't send sync request: no parent are known for new file: "+file.getName()); return; } int scannableValue = 0; if (parentFolder.isEnabled()) { if (parentFolder.isScanRemote()) scannableValue++; if (parentFolder.isScanLocal()) scannableValue += 2; } final String remotePath = parentFolder.getRemoteFolder()+file.getName(); fileState = new SyncedFileState(-1, file.getName(), CommonUtils.getLocalPath(file), remotePath, "", 0L, parentFolder.getId(), parentFolder.isMediaType(), scannableValue); int storedId = DbHelper.manageSyncedFileStateDB(fileState, "INSERT", appContext); if (storedId > 0) { fileState.setId(storedId); request = new SyncRequest(fileState, UPLOAD); } else { Log.w(TAG, "New File " + file.getName() + " observed but impossible to insert it in DB"); } } else { //File update if (fileState.getScannable() > 1) { request = new SyncRequest(fileState, UPLOAD); } } if (request != null) { sendSyncRequestToSynchronizationService(request); } } private void handleFileDelete(File file) { final String fileLocalPath = CommonUtils.getLocalPath(file); Log.d(TAG, "handleFileDelete("+fileLocalPath+")"); SyncedFileState fileState = DbHelper.loadSyncedFile( appContext, fileLocalPath, true); if (fileState == null) { Log.d(TAG, "Ignore event because file is not already in database"); return; } //If already in DB if (fileState.getScannable() > 0) { fileState.setScannable(0); DbHelper.manageSyncedFileStateDB(fileState, "UPDATE", appContext); } } public void unbindFromSynchronizationService(){ if(serviceConnection.isBoundToSynchronizationService()) appContext.unbindService(serviceConnection); else Log.w(TAG, "Not bound to SynchronizationService: can't unbind."); } public void bindToSynchronizationService(){ Log.d(TAG, "bindToSynchronizationService()"); final Intent SynchronizationServiceIntent = new Intent(appContext, SynchronizationService.class); appContext.bindService(SynchronizationServiceIntent, serviceConnection, Context.BIND_AUTO_CREATE); } }
app/src/main/java/foundation/e/drive/FileObservers/RecursiveFileObserver.java 0 → 100644 +174 −0 Original line number Diff line number Diff line /* * Copyright © Narinder Rana (/e/ foundation). * All rights reserved. This program and the accompanying materials * are made available under the terms of the GNU Public License v3.0 * which accompanies this distribution, and is available at * http://www.gnu.org/licenses/gpl.html */ package foundation.e.drive.FileObservers; import android.content.Context; import android.os.FileObserver; import java.io.File; import java.io.FileFilter; import java.util.HashMap; import java.util.List; import java.util.Stack; import foundation.e.drive.database.DbHelper; import foundation.e.drive.models.SyncedFolder; /** * @author Narinder Rana * @author Vincent Bourgmayer */ public class RecursiveFileObserver extends FileObserver { private final HashMap<String, FileObserver> observers = new HashMap<>(); private static final FileFilter WATCHABLE_DIRECTORIES_FILTER = new FileFilter() { @Override public boolean accept(File file) { return file.isDirectory() && !file.getName().startsWith("."); } }; private boolean watching = false; private final Context applicationContext; private String path; private int mask; private FileEventListener listener; public RecursiveFileObserver(Context applicationContext, String path, FileEventListener listener) { this(applicationContext, path, ALL_EVENTS, listener); } public RecursiveFileObserver(Context applicationContext, String path, int mask, FileEventListener listener) { super(path, mask); this.path = path; this.mask = mask | FileObserver.CREATE | FileObserver.DELETE_SELF; this.listener = listener; this.applicationContext = applicationContext; } @Override public void onEvent(int event, String path) { File file; if (path == null) { file = new File(this.path); } else { file = new File(this.path, path); } notify(event, file); } private void notify(int event, File file) { if (listener != null) { listener.onEvent(event & FileObserver.ALL_EVENTS, file); } } @Override public void startWatching() { Stack<String> stack = new Stack<>(); List<SyncedFolder> mSyncedFolders = DbHelper.getAllSyncedFolders(applicationContext); if (!mSyncedFolders.isEmpty()){ for (SyncedFolder syncedFolder:mSyncedFolders){ stack.push(syncedFolder.getLocalFolder()); stack.push(syncedFolder.getRemoteFolder()); } watching = true; } // Recursively watch all child directories while (!stack.empty()) { String parent = stack.pop(); startWatching(parent); File path = new File(parent); File[] files = path.listFiles(WATCHABLE_DIRECTORIES_FILTER); if (files != null) { for (File file : files) { stack.push(file.getAbsolutePath()); } } } } /** * Start watching a single file * @param path */ private void startWatching(String path) { synchronized (observers) { FileObserver observer = observers.remove(path); if (observer != null) { observer.stopWatching(); } observer = new SingleFileObserver(path, mask); observer.startWatching(); observers.put(path, observer); } } @Override public void stopWatching() { for (FileObserver observer : observers.values()) { observer.stopWatching(); } observers.clear(); watching = false; } /** * Stop watching a single file * @param path */ private void stopWatching(String path) { synchronized (observers) { FileObserver observer = observers.remove(path); if (observer != null) { observer.stopWatching(); } } } public boolean isWatching(){ return watching; } private class SingleFileObserver extends FileObserver { private String filePath; public SingleFileObserver(String path, int mask) { super(path, mask); filePath = path; } @Override public void onEvent(int event, String path) { File file; if (path == null) { file = new File(filePath); } else { file = new File(filePath, path); } switch (event & FileObserver.ALL_EVENTS) { case DELETE_SELF: RecursiveFileObserver.this.stopWatching(filePath); break; case CREATE: if (WATCHABLE_DIRECTORIES_FILTER.accept(file)) { RecursiveFileObserver.this.startWatching(file.getAbsolutePath()); } break; } RecursiveFileObserver.this.notify(event, file); } } }