diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index d475c3151a9d7a4ce904130ba6d0ba5c5f1caa8d..30859edd891a332cd88abebce535f0d27c95421d 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -1,4 +1,4 @@
-image: "registry.gitlab.e.foundation:5000/e/apps/docker-android-apps-cicd:latest"
+image: "registry.gitlab.e.foundation/e/os/docker-android-apps-cicd:latest"
stages:
- test
diff --git a/app/build.gradle b/app/build.gradle
index c65b01d763b0d28230f03af8e2f0b82c8c54df46..e1ec7cf1f328351a27a6b23fbe9a249dc46bdaa9 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -23,7 +23,7 @@ def getTestProp(String propName) {
android {
- compileSdkVersion 28
+ compileSdkVersion 31
defaultConfig {
applicationId "foundation.e.drive"
minSdkVersion 26
@@ -53,32 +53,28 @@ android {
//includeAndroidResources = true
}
}
-
}
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'
}
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index be871995566a3c5de7fc4bbb8df3365b36fc9856..d2d8002ad1efd6a93e64efcdbd7259c904ef9e8f 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -17,6 +17,8 @@ http://www.gnu.org/licenses/gpl.html
+
+
-
-
+
-
-
-
-
-
-
diff --git a/app/src/main/java/foundation/e/drive/EdriveApplication.java b/app/src/main/java/foundation/e/drive/EdriveApplication.java
index de7bdcf7f1a21e588e0d84f882d62c419f3d3e62..a8f39f238e6594391b2f74f5f0d58481f18c222b 100644
--- a/app/src/main/java/foundation/e/drive/EdriveApplication.java
+++ b/app/src/main/java/foundation/e/drive/EdriveApplication.java
@@ -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.
@@ -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) {
- String accountName = mAccount.name;
- String accountType = mAccount.type;
+ if (mAccount == null) {return ;}
- prefs.edit().putString(AccountManager.KEY_ACCOUNT_NAME, accountName)
- .putString(AccountManager.KEY_ACCOUNT_TYPE, accountType)
- .apply();
+ String accountName = mAccount.name;
+ String accountType = mAccount.type;
- scheduleScannerJob();
- }
+ prefs.edit().putString(AccountManager.KEY_ACCOUNT_NAME, accountName)
+ .putString(AccountManager.KEY_ACCOUNT_TYPE, accountType)
+ .apply();
}
}
- 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
diff --git a/app/src/main/java/foundation/e/drive/FileObservers/FileEventListener.java b/app/src/main/java/foundation/e/drive/FileObservers/FileEventListener.java
new file mode 100644
index 0000000000000000000000000000000000000000..a4a0b634c9acea0ce2ed8c56312a43394a179591
--- /dev/null
+++ b/app/src/main/java/foundation/e/drive/FileObservers/FileEventListener.java
@@ -0,0 +1,178 @@
+/*
+ * 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);
+ }
+}
diff --git a/app/src/main/java/foundation/e/drive/FileObservers/RecursiveFileObserver.java b/app/src/main/java/foundation/e/drive/FileObservers/RecursiveFileObserver.java
new file mode 100644
index 0000000000000000000000000000000000000000..1e4a98741522f50a92de2de4b61db631cf237a66
--- /dev/null
+++ b/app/src/main/java/foundation/e/drive/FileObservers/RecursiveFileObserver.java
@@ -0,0 +1,174 @@
+/*
+ * 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 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 stack = new Stack<>();
+
+ List 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);
+ }
+ }
+}
diff --git a/app/src/main/java/foundation/e/drive/database/DbHelper.java b/app/src/main/java/foundation/e/drive/database/DbHelper.java
index d1f94ee781423297c4ba95af632a4a9e9d625876..383c5fdb4c26f75bf149943d5aac80d5df431d40 100644
--- a/app/src/main/java/foundation/e/drive/database/DbHelper.java
+++ b/app/src/main/java/foundation/e/drive/database/DbHelper.java
@@ -24,7 +24,7 @@ import foundation.e.drive.models.SyncedFileState;
*/
public final class DbHelper extends SQLiteOpenHelper {
final private static String TAG = DbHelper.class.getSimpleName(); //Tag for log
- private static final int DATABASE_VERSION = 19; //20/09/2018
+ private static final int DATABASE_VERSION = 20; //16/03/2022
public static final String DATABASE_NAME = "eelo_drive.db";
/**
@@ -53,18 +53,22 @@ public final class DbHelper extends SQLiteOpenHelper {
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
Log.i(TAG, "onUpgrade(db, "+oldVersion+", "+newVersion+")");
- if(oldVersion < 19){
- try {
+ try {
+ if (oldVersion < 19) {
db.execSQL(SyncedFolderContract.UPDATE_TABLE_TO_VERSION_19);
db.execSQL(SyncedFileStateContract.UPDATE_TABLE_TO_VERSION_19);
-
db.execSQL(SyncedFileStateContract.UPDATE_MEDIA_DATA_TO_VERSION_19);
db.execSQL(SyncedFileStateContract.UPDATE_SETTINGS_DATA_TO_VERSION_19);
db.execSQL(SyncedFolderContract.UPDATE_MEDIA_DATA_TO_VERSION_19);
db.execSQL(SyncedFolderContract.UPDATE_SETTINGS_DATA_TO_VERSION_19);
- }catch(Exception e){
- Log.e(TAG, toString());
}
+ if (oldVersion < 20) {
+ db.execSQL(SyncedFileStateContract.UPDATE_TABLE_TO_VERSION_20);
+ db.execSQL(SyncedFileStateContract.UPDATE_MEDIA_DATA_TO_VERSION_20);
+ db.execSQL(SyncedFileStateContract.UPDATE_SETTINGS_DATA_TO_VERSION_20);
+ }
+ } catch(Exception e) {
+ Log.e(TAG, toString());
}
}
@@ -116,7 +120,6 @@ public final class DbHelper extends SQLiteOpenHelper {
}
dao.close();
}
-
return result;
}
@@ -178,6 +181,19 @@ public final class DbHelper extends SQLiteOpenHelper {
return result;
}
+
+ public static int updateSyncedFolder(SyncedFolder syncedFolder, Context context) {
+ int result = -1;
+ //Connect to DB
+ SyncedFolderDAO dao = openSyncedFolderDAO(context, true);
+ if (dao == null){
+ return result;
+ }
+ result = dao.update( syncedFolder );
+ dao.close();
+ return result;
+ }
+
/**
* Load SyncedFolder's from DB
* @param context app or service activity
@@ -243,6 +259,16 @@ public final class DbHelper extends SQLiteOpenHelper {
return result;
}
+ public static SyncedFolder getSyncedFolderByLocalPath(String localPath, Context context){
+ SyncedFolderDAO dao = openSyncedFolderDAO(context, true);
+ if (dao == null) {
+ return null;
+ }
+ SyncedFolder syncedFolder = dao.getSyncedFolderByLocalPath(localPath);
+ dao.close();
+ return syncedFolder;
+ }
+
/**
* Set the lastModified value of SyncedFolder to 1.
* The goal is to force to rescan it next time.
diff --git a/app/src/main/java/foundation/e/drive/database/SyncedFileStateContract.java b/app/src/main/java/foundation/e/drive/database/SyncedFileStateContract.java
index 3d0f69cff904c80d6e663b79febce3189955e6ed..2b91c8c30470cc554f11bab09ce8c847b9e0800d 100644
--- a/app/src/main/java/foundation/e/drive/database/SyncedFileStateContract.java
+++ b/app/src/main/java/foundation/e/drive/database/SyncedFileStateContract.java
@@ -25,6 +25,7 @@ class SyncedFileStateContract implements BaseColumns{
static final String LOCAL_LAST_MODIFIED = "local_last_modified";
static final String SYNCEDFOLDER_ID = "synced_folder_id";
static final String IS_MEDIA_TYPE = "is_media_type";
+ static final String SCANNABLE = "scannable";
static final String SQL_CREATE_TABLE_SYNCEDFILESTATE =
"CREATE TABLE "+TABLE_NAME+" ( "
@@ -36,6 +37,7 @@ class SyncedFileStateContract implements BaseColumns{
+LOCAL_LAST_MODIFIED+" INTEGER, "
+ SYNCEDFOLDER_ID +" INTEGER, "
+IS_MEDIA_TYPE+" BOOLEAN,"
+ +SCANNABLE+" INTEGER, "
+"CONSTRAINT synced_unicity_constraint UNIQUE ("
+FILE_NAME+", "
+LOCAL_PATH+", "
@@ -45,8 +47,8 @@ class SyncedFileStateContract implements BaseColumns{
static final String SQL_DELETE_TABLE_SYNCEDFILESTATE = " DROP TABLE IF EXISTS "
+ TABLE_NAME;
+ //Update for version 18 and lower
static final String UPDATE_TABLE_TO_VERSION_19 = "ALTER TABLE "+TABLE_NAME+" ADD COLUMN "+IS_MEDIA_TYPE+" BOOLEAN;";
-
static final String UPDATE_MEDIA_DATA_TO_VERSION_19 = "UPDATE "+TABLE_NAME+
" SET "+IS_MEDIA_TYPE+" = 1 WHERE "+
REMOTE_PATH+" LIKE \"/Photos/%\" OR "+
@@ -60,4 +62,11 @@ class SyncedFileStateContract implements BaseColumns{
static final String UPDATE_SETTINGS_DATA_TO_VERSION_19 = "UPDATE "+TABLE_NAME+
" SET "+IS_MEDIA_TYPE+" = 0 WHERE "+REMOTE_PATH+" LIKE \"/Devices/%\";";
+ //update for version 19 and lower
+ static final String UPDATE_TABLE_TO_VERSION_20 = "ALTER TABLE "+TABLE_NAME+" ADD COLUMN "+SCANNABLE+" INTEGER;";
+ static final String UPDATE_MEDIA_DATA_TO_VERSION_20 = "UPDATE "+TABLE_NAME+" SET "+SCANNABLE+" = 3 WHERE"
+ + IS_MEDIA_TYPE+" = 1 ;" ;
+ static final String UPDATE_SETTINGS_DATA_TO_VERSION_20 = "UPDATE "+TABLE_NAME+" SET "+SCANNABLE+" = 2 WHERE"
+ + IS_MEDIA_TYPE+" = 0 ;" ;
+
}
diff --git a/app/src/main/java/foundation/e/drive/database/SyncedFileStateDAO.java b/app/src/main/java/foundation/e/drive/database/SyncedFileStateDAO.java
index 9186581314def460b0ee5fae97ac75a0f1476953..9864b9de22b6628c2285305d1b2a24157acb6608 100644
--- a/app/src/main/java/foundation/e/drive/database/SyncedFileStateDAO.java
+++ b/app/src/main/java/foundation/e/drive/database/SyncedFileStateDAO.java
@@ -20,6 +20,8 @@ import java.util.ArrayList;
import java.util.List;
import foundation.e.drive.models.SyncedFileState;
+
+import static foundation.e.drive.database.SyncedFileStateContract.SCANNABLE;
import static foundation.e.drive.database.SyncedFileStateContract.TABLE_NAME;
import static foundation.e.drive.database.SyncedFileStateContract.FILE_NAME;
import static foundation.e.drive.database.SyncedFileStateContract.IS_MEDIA_TYPE;
@@ -39,16 +41,6 @@ class SyncedFileStateDAO {
private SQLiteDatabase mDB;
private final DbHelper mHelper;
- /*private final String[] allColumns = { SyncedFileStateContract._ID,
- SyncedFileStateContract.FILE_NAME,
- SyncedFileStateContract.LOCAL_PATH,
- SyncedFileStateContract.REMOTE_PATH,
- SyncedFileStateContract.LAST_ETAG,
-
- SyncedFileStateContract.LOCAL_LAST_MODIFIED,
- SyncedFileStateContract.SYNCEDFOLDER_ID,
- SyncedFileStateContract.IS_MEDIA_TYPE
- };*/
SyncedFileStateDAO(Context context){
this.mHelper = new DbHelper(context);
@@ -75,7 +67,7 @@ class SyncedFileStateDAO {
* @param syncedFileState syncedFileState to convert
* @return a ContentValues object
*/
- private ContentValues toContentValues(SyncedFileState syncedFileState){
+ private ContentValues toContentValues(SyncedFileState syncedFileState) {
ContentValues values = new ContentValues();
values.put( FILE_NAME, syncedFileState.getName() );
values.put( LOCAL_PATH, syncedFileState.getLocalPath() );
@@ -84,6 +76,7 @@ class SyncedFileStateDAO {
values.put( LOCAL_LAST_MODIFIED, syncedFileState.getLocalLastModified() );
values.put( SYNCEDFOLDER_ID, syncedFileState.getSyncedFolderId() );
values.put( IS_MEDIA_TYPE, syncedFileState.isMediaType() ? 1 : 0 );
+ values.put( SCANNABLE, syncedFileState.getScannable());
return values;
}
@@ -142,7 +135,7 @@ class SyncedFileStateDAO {
* @param syncedFolderids List of path to filter. Need to be directory path
* @return List List of SyncedFileState filtered on syncedFolder ID.
*/
- List getBySyncedFolderID(List syncedFolderids){
+ List getBySyncedFolderID(List syncedFolderids) {
String query = "Select "
+SyncedFileStateContract._ID+", "
+FILE_NAME+", "
@@ -151,10 +144,11 @@ class SyncedFileStateDAO {
+LAST_ETAG+", "
+LOCAL_LAST_MODIFIED+", "
+ SYNCEDFOLDER_ID+", "
- + IS_MEDIA_TYPE
+ + IS_MEDIA_TYPE+", "
+ + SCANNABLE
+" FROM "
+TABLE_NAME;
- if(syncedFolderids.size() > 0) {
+ if (syncedFolderids.size() > 0) {
query+=" WHERE ";
for (int i = -1, idsSize = syncedFolderids.size(); ++i < idsSize; ) {
@@ -181,8 +175,8 @@ class SyncedFileStateDAO {
* @param path local path or remote path
* @return SyncedFileState obtain by the query or null if none has been found
*/
- SyncedFileState getByPath(String path, boolean isLocalPath){
- String query = "Select "
+ SyncedFileState getByPath(String path, boolean isLocalPath) {
+ String query = "Select "
+SyncedFileStateContract._ID+", "
+FILE_NAME+", "
+LOCAL_PATH+", "
@@ -190,10 +184,11 @@ class SyncedFileStateDAO {
+LAST_ETAG+", "
+LOCAL_LAST_MODIFIED+", "
+ SYNCEDFOLDER_ID+", "
- + IS_MEDIA_TYPE+
+ + IS_MEDIA_TYPE+", "
+ + SCANNABLE+
" FROM "
+TABLE_NAME+" WHERE ";
- if(isLocalPath)
+ if (isLocalPath)
query+=LOCAL_PATH ;
else
query+=REMOTE_PATH ;
@@ -203,7 +198,7 @@ class SyncedFileStateDAO {
Cursor cursor = mDB.rawQuery(query, null);
cursor.moveToFirst();
SyncedFileState syncedFileState = null;
- if( !cursor.isAfterLast()) {
+ if ( !cursor.isAfterLast()) {
syncedFileState = cursorToSyncedFileState(cursor);
}
cursor.close();
@@ -224,7 +219,8 @@ class SyncedFileStateDAO {
cursor.getString(4 ),// last Etag
cursor.getLong(5 ),//Local last modified
cursor.getLong(6 ), //SyncedFolderID
- (cursor.getInt(7) == 1 ) //is Media Type
+ (cursor.getInt(7) == 1), //is Media Type
+ cursor.getInt(8) //scannable
);
}
}
diff --git a/app/src/main/java/foundation/e/drive/database/SyncedFolderDAO.java b/app/src/main/java/foundation/e/drive/database/SyncedFolderDAO.java
index 8af54a1f40873e8dfb914799ef3a71968da6242a..c70be5e51f9177c62b64b83e2275caf8e5c27cb1 100644
--- a/app/src/main/java/foundation/e/drive/database/SyncedFolderDAO.java
+++ b/app/src/main/java/foundation/e/drive/database/SyncedFolderDAO.java
@@ -29,6 +29,8 @@ import static foundation.e.drive.database.SyncedFolderContract.SCANREMOTE;
import static foundation.e.drive.database.SyncedFolderContract.IS_MEDIA_TYPE;
import static foundation.e.drive.database.SyncedFolderContract.TABLE_NAME;
+import com.owncloud.android.lib.resources.files.FileUtils;
+
/**
* @author Vincent Bourgmayer
* Source: https://vogella.developpez.com/tutoriels/android/utilisation-base-donnees-sqlite/
@@ -174,6 +176,17 @@ class SyncedFolderDAO {
return result;
}
+ SyncedFolder getSyncedFolderByLocalPath(String localPath){
+ Cursor cursor = mDB.query(TABLE_NAME, allColumns, LOCAL_PATH+" like \""+localPath+ FileUtils.PATH_SEPARATOR+"\"", new String[0], null, null, null);
+ cursor.moveToFirst();
+ SyncedFolder result = null;
+ if ( !cursor.isAfterLast() ) {
+ result = cursorToSyncedFolder(cursor);
+ }
+ cursor.close();
+ return result;
+ }
+
/**
* Create a syncedFolder object from data in cursor.
* @param cursor cursor containing data to build syncedFolder
diff --git a/app/src/main/java/foundation/e/drive/jobs/ScannerJob.java b/app/src/main/java/foundation/e/drive/jobs/ScannerJob.java
deleted file mode 100644
index 878f554b882119cefd4795e8131d2660ddcba33a..0000000000000000000000000000000000000000
--- a/app/src/main/java/foundation/e/drive/jobs/ScannerJob.java
+++ /dev/null
@@ -1,54 +0,0 @@
-/*
- * 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.jobs;
-
-import android.app.job.JobParameters;
-import android.app.job.JobService;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.util.Log;
-import foundation.e.drive.receivers.ScreenOffReceiver;
-import foundation.e.drive.services.ObserverService;
-import foundation.e.drive.utils.CommonUtils;
-
-/**
- * @author Vincent Bourgmayer
- */
-public class ScannerJob extends JobService {
- final private String TAG = ScannerJob.class.getSimpleName(); //Tag for log
-
- @Override
- public boolean onStartJob(JobParameters params) {
- Log.i(TAG, "onStartJob()");
-
- Log.d(TAG, "RegisterReceiver: screenOffReceiver");
- IntentFilter filter = new IntentFilter(Intent.ACTION_SCREEN_ON);
- filter.addAction(Intent.ACTION_SCREEN_OFF);
- getApplicationContext().registerReceiver(ScreenOffReceiver.getInstance(), filter);
-
- Intent observerServiceIntent = new Intent(this, ObserverService.class);
- this.startService(observerServiceIntent);
- jobFinished(params, false);
- return true;
- }
-
- /**
- *
- * @param params
- * @return default return... ?
- */
- @Override
- public boolean onStopJob(JobParameters params) {
- Log.i(TAG, "onStopJob");
- boolean unregisteredReceiver = CommonUtils.unregisterScreenOff(getApplicationContext());
- Intent observerServiceIntent = new Intent(this, ObserverService.class);
- this.stopService(observerServiceIntent);
- return false;
- }
-}
diff --git a/app/src/main/java/foundation/e/drive/models/DownloadRequest.java b/app/src/main/java/foundation/e/drive/models/DownloadRequest.java
new file mode 100644
index 0000000000000000000000000000000000000000..7403eea5b8fa714963682429bdcef7a8a4754de9
--- /dev/null
+++ b/app/src/main/java/foundation/e/drive/models/DownloadRequest.java
@@ -0,0 +1,26 @@
+/*
+ * 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.models;
+
+import com.owncloud.android.lib.resources.files.model.RemoteFile;
+
+/**
+ * @author vincent Bourgmayer
+ */
+public class DownloadRequest extends SyncRequest {
+ private final RemoteFile remoteFile;
+
+ public DownloadRequest (RemoteFile remoteFile, SyncedFileState syncedFileState) {
+ super(syncedFileState, Type.DOWNLOAD);
+ this.remoteFile = remoteFile;
+ }
+
+ public RemoteFile getRemoteFile() {
+ return remoteFile;
+ }
+}
diff --git a/app/src/main/java/foundation/e/drive/models/FileObserver.java b/app/src/main/java/foundation/e/drive/models/FileObserver.java
new file mode 100644
index 0000000000000000000000000000000000000000..6407b8a66b1345c6b00ec03cf833dde3f97c5ee1
--- /dev/null
+++ b/app/src/main/java/foundation/e/drive/models/FileObserver.java
@@ -0,0 +1,33 @@
+/*
+ * 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.models;
+
+import java.io.File;
+import java.io.Serializable;
+import java.util.List;
+
+/**
+ * @author Narinder Rana
+ */
+public class FileObserver implements Serializable {
+
+ private List files;
+ public FileObserver(List files) {
+ this.files = files;
+
+ }
+
+ public List getFiles() {
+ return files;
+ }
+
+ public void setFiles(List files) {
+ this.files = files;
+ }
+}
diff --git a/app/src/main/java/foundation/e/drive/models/SyncRequest.java b/app/src/main/java/foundation/e/drive/models/SyncRequest.java
new file mode 100644
index 0000000000000000000000000000000000000000..e2412cdf2fa5d9fb550cd08364174c85a2ae205f
--- /dev/null
+++ b/app/src/main/java/foundation/e/drive/models/SyncRequest.java
@@ -0,0 +1,39 @@
+/*
+ * 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.models;
+
+import androidx.annotation.Nullable;
+
+public class SyncRequest {
+ public enum Type { UPLOAD, DOWNLOAD, REMOTE_DELETE};
+
+ private final SyncedFileState syncedFileState;
+
+ private final Type operationType;
+
+ public SyncRequest(SyncedFileState syncedFileState, Type operationType) {
+ this.syncedFileState = syncedFileState;
+ this.operationType = operationType;
+ }
+
+ public Type getOperationType() {
+ return operationType;
+ }
+
+ public SyncedFileState getSyncedFileState() {
+ return syncedFileState;
+ }
+
+ @Override
+ public boolean equals(@Nullable Object obj) {
+ if (obj instanceof SyncRequest) {
+ return (syncedFileState.getId() == ((SyncRequest) obj).syncedFileState.getId() );
+ }
+ return super.equals(obj);
+ }
+}
diff --git a/app/src/main/java/foundation/e/drive/models/SyncedFileState.java b/app/src/main/java/foundation/e/drive/models/SyncedFileState.java
index 42c76a102482a4681845082e752f1edf57211f3d..f3b842b33d8cca29b3232913882330906aa4f8e2 100644
--- a/app/src/main/java/foundation/e/drive/models/SyncedFileState.java
+++ b/app/src/main/java/foundation/e/drive/models/SyncedFileState.java
@@ -17,7 +17,10 @@ import android.os.Parcelable;
*/
public class SyncedFileState implements Parcelable {
-
+ public static final int NOT_SCANNABLE=0;
+ public static final int ECLOUD_SCANNABLE=1;
+ public static final int DEVICE_SCANNABLE=2;
+ public static final int ALL_SCANNABLE=3;
protected SyncedFileState(){}; //@ToRemove. Test Only. It's to allow to make a mock SyncedFileState Class in test.
private int id;
@@ -27,7 +30,8 @@ public class SyncedFileState implements Parcelable {
private String lastETAG; //Last got Etag for the file
private long localLastModified;
private long syncedFolderId;
- private boolean isMediaType; //if true this is a media synchronisable else it it is a settings synchronisable.
+ private boolean isMediaType; // true if this is a media synchronisable else it is a settings synchronisable.
+ private int scannable;
/**
* Full constructor
@@ -39,7 +43,7 @@ public class SyncedFileState implements Parcelable {
* @param lastModified Last modified time where local file has changed
* @param isMediaType true if its sync as medias or as device/app' settings.
*/
- public SyncedFileState(int id, String name, String localPath, String remotePath, String etag, long lastModified, long syncedFolderId, boolean isMediaType){
+ public SyncedFileState(int id, String name, String localPath, String remotePath, String etag, long lastModified, long syncedFolderId, boolean isMediaType, int scannable){
this.id = id;
this.name = name;
this.localPath = localPath;
@@ -48,6 +52,7 @@ public class SyncedFileState implements Parcelable {
this.localLastModified = lastModified;
this.syncedFolderId = syncedFolderId;
this.isMediaType = isMediaType;
+ this.scannable = scannable;
}
protected SyncedFileState(Parcel in) {
@@ -59,6 +64,7 @@ public class SyncedFileState implements Parcelable {
localLastModified = in.readLong();
syncedFolderId = in.readLong();
isMediaType = in.readByte() != 0;
+ scannable = in.readInt();
}
@Override
@@ -71,6 +77,7 @@ public class SyncedFileState implements Parcelable {
dest.writeLong(localLastModified);
dest.writeLong(syncedFolderId);
dest.writeByte((byte) (isMediaType ? 1 : 0));
+ dest.writeInt(scannable);
}
public static final Creator CREATOR = new Creator() {
@@ -152,6 +159,24 @@ public class SyncedFileState implements Parcelable {
return isMediaType;
}
+
+ /**
+ * Return in which context the file can scan
+ * @return 0: not scannable. 1: scannable on ecloud. 2: scannable on device. 3: scannable everywhere
+ */
+ public int getScannable() {
+ return scannable;
+ }
+
+ /**
+ * Define in which context the file can be scan
+ * @param scannable 0: never. 1: on cloud only. 2: on device only. 3: in every context.
+ */
+ public void setScannable(int scannable) {
+ this.scannable = scannable;
+ }
+
+
@Override
public String toString(){
return "SyncedFileState :"
@@ -162,10 +187,10 @@ public class SyncedFileState implements Parcelable {
+"\nLast Etag: "+this.lastETAG
+"\nLocal last modified: "+this.localLastModified
+"\nSyncedFolderId: "+this.syncedFolderId
- +"\nisMediaType: "+this.isMediaType;
+ +"\nisMediaType: "+this.isMediaType
+ +"\nscannable: "+this.scannable;
}
-
@Override
public int describeContents() {
return 0;
diff --git a/app/src/main/java/foundation/e/drive/operations/ComparableOperation.java b/app/src/main/java/foundation/e/drive/operations/ComparableOperation.java
deleted file mode 100644
index 02b8308c0419428754e02e8920fbcd7f3111759f..0000000000000000000000000000000000000000
--- a/app/src/main/java/foundation/e/drive/operations/ComparableOperation.java
+++ /dev/null
@@ -1,18 +0,0 @@
-package foundation.e.drive.operations;
-
-import com.owncloud.android.lib.common.operations.RemoteOperation;
-
-public interface ComparableOperation {
-
- /**
- * Say if File affected by operation is a media or a settings
- * @return
- */
- public boolean isMediaType();
-
- /**
- * Cast instance to generic remoteOperation Type
- * @return RemoteOperation this instance
- */
- public RemoteOperation toRemoteOperation();
-}
diff --git a/app/src/main/java/foundation/e/drive/operations/CreateInitialFolderRemoteOperation.java b/app/src/main/java/foundation/e/drive/operations/CreateInitialFolderRemoteOperation.java
deleted file mode 100644
index 72b837a343873c06cbdf1af74186c1979b947eb2..0000000000000000000000000000000000000000
--- a/app/src/main/java/foundation/e/drive/operations/CreateInitialFolderRemoteOperation.java
+++ /dev/null
@@ -1,64 +0,0 @@
-/*
- * 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.operations;
-
-import android.content.Context;
-import android.util.Log;
-import com.owncloud.android.lib.common.OwnCloudClient;
-import com.owncloud.android.lib.common.operations.RemoteOperation;
-import com.owncloud.android.lib.common.operations.RemoteOperationResult;
-import com.owncloud.android.lib.resources.files.CreateFolderRemoteOperation;
-import java.io.File;
-import foundation.e.drive.database.DbHelper;
-import foundation.e.drive.models.SyncedFolder;
-
-/**
- * @author Vincent Bourgmayer
- * Perform initial folder creation
- */
-public class CreateInitialFolderRemoteOperation extends RemoteOperation {
- private static final String TAG = CreateInitialFolderRemoteOperation.class.getSimpleName();
-
- private SyncedFolder mSyncedFolder;
- private String mRemotePath;
- private boolean mCreateFullPath;//should recreate parent path if not exist or not
- private final Context mContext;
-
- public CreateInitialFolderRemoteOperation(SyncedFolder root, boolean createFullPath, Context context) {
- super();
- this.mSyncedFolder = root;
- this.mRemotePath = root.getRemoteFolder();
- this.mCreateFullPath = createFullPath;
- this.mContext = context;
- }
-
- @Override
- protected RemoteOperationResult run(OwnCloudClient client) {
- File folder = new File(mSyncedFolder.getLocalFolder() );
- if( !folder.exists() ){
- Log.e(TAG, "Local folder doesn't exist, so create it");
- folder.mkdirs();
- }
-
- //Perfom request
- CreateFolderRemoteOperation createFolderOperation = new CreateFolderRemoteOperation(mRemotePath, mCreateFullPath);
- RemoteOperationResult createOperationResult = createFolderOperation.execute(client, true);
-
- if(createOperationResult.isSuccess() || createOperationResult.getCode() == RemoteOperationResult.ResultCode.FOLDER_ALREADY_EXISTS ){
- if(DbHelper.insertSyncedFolder(mSyncedFolder, mContext) >= 0 ) {
- Log.d(TAG, "Insertion in DB succeed");
- return new RemoteOperationResult(RemoteOperationResult.ResultCode.OK);
- }else {
- Log.d(TAG, "insertion of folder in DB failed");
- return new RemoteOperationResult(RemoteOperationResult.ResultCode.FOLDER_ALREADY_EXISTS);
- }
- }
- return createOperationResult;
- }
-}
diff --git a/app/src/main/java/foundation/e/drive/operations/DownloadFileOperation.java b/app/src/main/java/foundation/e/drive/operations/DownloadFileOperation.java
index cfcb8a79dc06d4e676900e3a3ccaf85a6e49c724..4445239c721e7a0ead86a1264ed77327d9b88cde 100644
--- a/app/src/main/java/foundation/e/drive/operations/DownloadFileOperation.java
+++ b/app/src/main/java/foundation/e/drive/operations/DownloadFileOperation.java
@@ -9,8 +9,6 @@
package foundation.e.drive.operations;
import android.content.Context;
-import android.os.Parcel;
-import android.os.Parcelable;
import android.util.Log;
import com.owncloud.android.lib.common.OwnCloudClient;
import com.owncloud.android.lib.common.operations.RemoteOperation;
@@ -26,167 +24,115 @@ import foundation.e.drive.utils.CommonUtils;
* @author Vincent Bourgmayer
* Encapsulate a global download process for a file
*/
-public class DownloadFileOperation extends RemoteOperation implements ComparableOperation, Parcelable {
+public class DownloadFileOperation extends RemoteOperation {
private final static String TAG = DownloadFileOperation.class.getSimpleName();
- private final RemoteFile mRFile;
- private Context mContext;
- private String mTargetPath;
+ private final RemoteFile remoteFile;
+ private Context context;
+ private String targetPath;
private int restartCounter =0;
- private SyncedFileState mSyncedState;
+ private SyncedFileState syncedFileState;
private String previousEtag;
-
/**
* COnstructor of download operation where syncedFileState is already known
* @param remoteFile remote file to Download
* @param syncedFileState SyncedFileState corresponding to remote file
*/
- public DownloadFileOperation(RemoteFile remoteFile, SyncedFileState syncedFileState){ this.mRFile = remoteFile;
- this.mSyncedState = syncedFileState;
- this.previousEtag = mSyncedState.getLastETAG();
- this.mTargetPath = this.mSyncedState.getLocalPath();
- }
-
- protected DownloadFileOperation(Parcel in) {
- mRFile = in.readParcelable(RemoteFile.class.getClassLoader());
- mTargetPath = in.readString();
- restartCounter = in.readInt();
- mSyncedState = in.readParcelable(SyncedFileState.class.getClassLoader());
- previousEtag = in.readString();
+ public DownloadFileOperation(RemoteFile remoteFile, SyncedFileState syncedFileState, Context context) {
+ this.remoteFile = remoteFile;
+ this.syncedFileState = syncedFileState;
+ this.previousEtag = syncedFileState.getLastETAG();
+ this.targetPath = syncedFileState.getLocalPath();
+ this.context = context;
}
- @Override
- public void writeToParcel(Parcel dest, int flags) {
- dest.writeParcelable(mRFile, flags);
- dest.writeString(mTargetPath);
- dest.writeInt(restartCounter);
- dest.writeParcelable(mSyncedState, flags);
- dest.writeString(previousEtag);
- }
-
-
- public static final Creator CREATOR = new Creator() {
- @Override
- public DownloadFileOperation createFromParcel(Parcel in) {
- return new DownloadFileOperation(in);
- }
-
- @Override
- public DownloadFileOperation[] newArray(int size) {
- return new DownloadFileOperation[size];
- }
- };
-
- public void setContext(Context context){
- this.mContext = context;
- }
-
-
@Override
protected RemoteOperationResult run(OwnCloudClient ownCloudClient) {
Log.i(TAG, "run(ownCloudClient)");
//get or build synced file equivalent of this.mFile
- if(mSyncedState == null || mTargetPath == null || mTargetPath.isEmpty()) {
- Log.e(TAG, "mSyncedState or mTargetPath is empty or null. Can't Download in those conditions");
+ if (syncedFileState == null || targetPath == null || targetPath.isEmpty()) {
+ Log.e(TAG, "syncedFileState or targetPath is empty or null. Can't Download in those conditions");
return new RemoteOperationResult(RemoteOperationResult.ResultCode.FORBIDDEN);
- }else if(mSyncedState.getId() == -1){
- this.mSyncedState.setId( DbHelper.manageSyncedFileStateDB(this.mSyncedState, "INSERT", mContext) );
+ } else if (syncedFileState.getId() == -1) {
+ this.syncedFileState.setId(DbHelper.manageSyncedFileStateDB(this.syncedFileState, "INSERT", context));
}
- if(mSyncedState.getLastETAG().equals( mRFile.getEtag() ) && mSyncedState.getLocalLastModified() > 0L){
+ if (syncedFileState.getLastETAG().equals(remoteFile.getEtag()) && syncedFileState.getLocalLastModified() > 0L) {
//Same etag and localLastModified not null mean the file is up to date
Log.w(TAG, "File already up-to-date");
- return new RemoteOperationResult(RemoteOperationResult.ResultCode.ETAG_UNCHANGED );
+ return new RemoteOperationResult(RemoteOperationResult.ResultCode.ETAG_UNCHANGED);
}
- String tmpTargetPath = mContext.getExternalCacheDir()+ FileUtils.PATH_SEPARATOR+mSyncedState.getName();
- DownloadFileRemoteOperation downloadOperation = new DownloadFileRemoteOperation(mRFile.getRemotePath(),
+
+ final String tmpTargetPath = context.getExternalCacheDir()+ FileUtils.PATH_SEPARATOR+ syncedFileState.getName();
+ final DownloadFileRemoteOperation downloadOperation = new DownloadFileRemoteOperation(remoteFile.getRemotePath(),
tmpTargetPath);
- RemoteOperationResult downloadResult = downloadOperation.execute( ownCloudClient );
- RemoteOperationResult.ResultCode mResultCode;
+ final RemoteOperationResult downloadResult = downloadOperation.execute(ownCloudClient);
+ RemoteOperationResult.ResultCode resultCode;
boolean mustRestart = true;
- if( downloadResult.isSuccess() ){
- File tmpLocalFile = new File(tmpTargetPath);
- if( !tmpLocalFile.exists() ){
+ if (downloadResult.isSuccess()) {
+ final File tmpLocalFile = new File(tmpTargetPath);
+ if (!tmpLocalFile.exists()) {
Log.e(TAG, "Downloaded file doesn't exist or is null");
- mResultCode = RemoteOperationResult.ResultCode.FILE_NOT_FOUND;
+ resultCode = RemoteOperationResult.ResultCode.FILE_NOT_FOUND;
- }else if(tmpLocalFile.length() != mRFile.getLength() ){
+ } else if (tmpLocalFile.length() != remoteFile.getLength()) {
Log.e(TAG, "Local and remote file doesn't have the same size.");
- mResultCode = RemoteOperationResult.ResultCode.INVALID_OVERWRITE;
+ resultCode = RemoteOperationResult.ResultCode.INVALID_OVERWRITE;
tmpLocalFile.delete();
- }else{
+ } else {
//file has been correctly download.
- File localFile = new File(mTargetPath);
- if( localFile.exists() ){
+ final File localFile = new File(targetPath);
+ if (localFile.exists()) {
localFile.delete();
}
//Check parentFolder existence and create if needed
- String parentFoldersPath = localFile.getParent();
- File localParentFile = new File(parentFoldersPath);
- if( !localParentFile.exists() ){
- if( localParentFile.mkdirs() )
- Log.d(TAG, "Created folders: "+parentFoldersPath );
+ final String parentFoldersPath = localFile.getParent();
+ final File localParentFile = new File(parentFoldersPath);
+ if (!localParentFile.exists()) {
+ if (localParentFile.mkdirs())
+ Log.d(TAG, "Created folders: "+parentFoldersPath);
else
- Log.d(TAG, "Can't create folders: "+parentFoldersPath );
+ Log.d(TAG, "Can't create folders: "+parentFoldersPath);
}
boolean renameResult = tmpLocalFile.renameTo(localFile);
- if(!renameResult)
+ if (!renameResult)
Log.d(TAG, "File hasn't been successfully moved at its place");
- mSyncedState.setLocalLastModified( localFile.lastModified() )
- .setLastETAG( mRFile.getEtag() );
+ syncedFileState.setLocalLastModified(localFile.lastModified())
+ .setLastETAG(remoteFile.getEtag());
mustRestart = false;
- mResultCode = RemoteOperationResult.ResultCode.OK;
+ resultCode = RemoteOperationResult.ResultCode.OK;
//needed to make Gallery show new image
- CommonUtils.doActionMediaScannerConnexionScanFile(mContext, mSyncedState.getLocalPath() );
+ CommonUtils.doActionMediaScannerConnexionScanFile(context, syncedFileState.getLocalPath());
}
- }else{
+ } else {
//If download failed
- Log.e(TAG, "Download failed: "+downloadResult.getLogMessage() );
- mResultCode = RemoteOperationResult.ResultCode.UNKNOWN_ERROR;
+ Log.e(TAG, "Download failed: "+downloadResult.getLogMessage());
+ resultCode = RemoteOperationResult.ResultCode.UNKNOWN_ERROR;
}
- if(mustRestart){
+ if (mustRestart) {
Log.w(TAG, restartCounter+" unsuccessfull trial.s of downloading file "
- +mRFile.getRemotePath() );
- mSyncedState.setLastETAG(this.previousEtag);
- if(this.restartCounter < 3){
+ + remoteFile.getRemotePath());
+ syncedFileState.setLastETAG(this.previousEtag);
+ if (this.restartCounter < 3) {
this.restartCounter += 1;
return this.run(ownCloudClient);
- }else{
- mResultCode = RemoteOperationResult.ResultCode.INVALID_OVERWRITE;
+ } else {
+ resultCode = RemoteOperationResult.ResultCode.INVALID_OVERWRITE;
}
}
//So now, we can update instance of SyncedState and save it to DB
- if( DbHelper.manageSyncedFileStateDB( mSyncedState, "UPDATE", mContext ) <= 0 ){
+ if (DbHelper.manageSyncedFileStateDB(syncedFileState, "UPDATE", context) <= 0) {
Log.e(TAG, "DB update failed: 0 affected row");
//@TODO : do smtg
}
- return new RemoteOperationResult( mResultCode );
- }
-
- /**
- * tell what type of operation is it
- * @return true if it is an operation for media's element, then false mean its a settings operation
- */
- @Override
- public boolean isMediaType(){
- return this.mSyncedState.isMediaType();
- }
-
- @Override
- public RemoteOperation toRemoteOperation() {
- return this;
- }
-
- @Override
- public int describeContents() {
- return 0;
+ return new RemoteOperationResult(resultCode);
}
}
diff --git a/app/src/main/java/foundation/e/drive/operations/RemoveFileOperation.java b/app/src/main/java/foundation/e/drive/operations/RemoveFileOperation.java
index 6b2132b2b6daefbed03e00b05abd5135cc95ec62..d1f4570b13065ac99d5c495109667c6caff9e005 100644
--- a/app/src/main/java/foundation/e/drive/operations/RemoveFileOperation.java
+++ b/app/src/main/java/foundation/e/drive/operations/RemoveFileOperation.java
@@ -8,10 +8,6 @@
package foundation.e.drive.operations;
-import android.os.Parcel;
-import android.os.Parcelable;
-
-import com.owncloud.android.lib.common.operations.RemoteOperation;
import com.owncloud.android.lib.resources.files.RemoveFileRemoteOperation;
import foundation.e.drive.models.SyncedFileState;
@@ -21,58 +17,16 @@ import foundation.e.drive.models.SyncedFileState;
* Created by Vincent on 19/06/2018.
* Class to be able to wrap concerned SyncedFileState in operation, so it can be retrieve at the end
*/
-public class RemoveFileOperation extends RemoveFileRemoteOperation implements ComparableOperation, Parcelable {
+public class RemoveFileOperation extends RemoveFileRemoteOperation {
- private SyncedFileState mSyncedFileState;
+ private SyncedFileState syncedFileState;
public RemoveFileOperation(SyncedFileState syncedFileState) {
- super( syncedFileState.getRemotePath() );
- this.mSyncedFileState = syncedFileState;
- }
-
- protected RemoveFileOperation(Parcel in) {
- super(in.readString());
- mSyncedFileState = in.readParcelable(SyncedFileState.class.getClassLoader());
- }
-
- @Override
- public void writeToParcel(Parcel dest, int flags) {
- dest.writeString(mSyncedFileState.getRemotePath());
- dest.writeParcelable(mSyncedFileState, flags);
- }
-
- @Override
- public int describeContents() {
- return 0;
+ super(syncedFileState.getRemotePath());
+ this.syncedFileState = syncedFileState;
}
- public static final Creator CREATOR = new Creator() {
- @Override
- public RemoveFileOperation createFromParcel(Parcel in) {
- return new RemoveFileOperation(in);
- }
-
- @Override
- public RemoveFileOperation[] newArray(int size) {
- return new RemoveFileOperation[size];
- }
- };
-
public SyncedFileState getSyncedFileState() {
- return mSyncedFileState;
- }
-
- /**
- * tell what type of element to sync it is
- * @return true if it is an operation for media's element, then false mean its a settings operation
- */
- @Override
- public boolean isMediaType(){
- return this.mSyncedFileState.isMediaType();
- }
-
- @Override
- public RemoteOperation toRemoteOperation() {
- return this;
+ return syncedFileState;
}
}
diff --git a/app/src/main/java/foundation/e/drive/operations/UploadFileOperation.java b/app/src/main/java/foundation/e/drive/operations/UploadFileOperation.java
index d4d685d08f401af3f8e3af4d7569dbf64e3b6fb3..5d972a0b2b30dae77900bf53d9a66b2e31fec27a 100644
--- a/app/src/main/java/foundation/e/drive/operations/UploadFileOperation.java
+++ b/app/src/main/java/foundation/e/drive/operations/UploadFileOperation.java
@@ -9,8 +9,6 @@
package foundation.e.drive.operations;
import android.content.Context;
-import android.os.Parcel;
-import android.os.Parcelable;
import android.util.Log;
import androidx.annotation.VisibleForTesting;
@@ -34,61 +32,25 @@ import foundation.e.drive.utils.CommonUtils;
* @author Vincent Bourgmayer
* High level Operation which upload a local file to a remote cloud storage
*/
-public class UploadFileOperation extends RemoteOperation implements ComparableOperation, Parcelable {
+public class UploadFileOperation extends RemoteOperation {
private final static String TAG = UploadFileOperation.class.getSimpleName();
-
private int restartCounter =0;
private long previousLastModified; //get to restore real value if all trials fails
- private boolean checkEtag;
- private Context mContext;
- private SyncedFileState mSyncedState;
-
+ private Context context;
+ private SyncedFileState syncedState;
private long availableQuota = -1;
- protected UploadFileOperation(Parcel in) {
- restartCounter = in.readInt();
- previousLastModified = in.readLong();
- checkEtag = in.readByte() != 0;
- mSyncedState = in.readParcelable(SyncedFileState.class.getClassLoader());
- availableQuota = in.readLong();
- }
-
- public static final Creator CREATOR = new Creator() {
- @Override
- public UploadFileOperation createFromParcel(Parcel in) {
- return new UploadFileOperation(in);
- }
-
- @Override
- public UploadFileOperation[] newArray(int size) {
- return new UploadFileOperation[size];
- }
- };
-
- @Override
- public RemoteOperation toRemoteOperation() {
- return this;
- }
-
-
/**
* Construct an upload operation with an already known syncedFileState
* @param syncedFileState syncedFileState corresponding to file.
- * @param checkEtag if we should use IF MATCH header with etag
*/
- public UploadFileOperation (SyncedFileState syncedFileState, boolean checkEtag){
- this.mSyncedState = syncedFileState;
- this.previousLastModified = mSyncedState.getLocalLastModified();
- this.checkEtag = checkEtag;
+ public UploadFileOperation (SyncedFileState syncedFileState, Context context) {
+ this.syncedState = syncedFileState;
+ this.previousLastModified = syncedState.getLocalLastModified();
+ this.context = context;
}
- public void setContext(Context context){
- this.mContext = context;
- }
-
-
-
/**
* Execute the operation:
*
@@ -101,99 +63,103 @@ public class UploadFileOperation extends RemoteOperation implements ComparableOp
* hasn't change since last update or "forbidden" if no remote path can be fetch.
*/
@Override
- protected RemoteOperationResult run( OwnCloudClient client ) {
+ protected RemoteOperationResult run(OwnCloudClient client ) {
//as operation isn't executed immediatly, file might have been deleted since creation of operation
- if(mSyncedState == null ){
+ if (syncedState == null ) {
Log.e(TAG, "run(client): no syncedFileState or target path, can't perform upload operation");
return new RemoteOperationResult(ResultCode.FORBIDDEN);
}
- File file = new File(mSyncedState.getLocalPath());
- if(file == null || !file.exists()){
+ File file = new File(syncedState.getLocalPath());
+ if (file == null || !file.exists()) {
Log.w(TAG, "Can't get the file. It might have been deleted");
return new RemoteOperationResult(ResultCode.FORBIDDEN);
}
- String targetPath = mSyncedState.getRemotePath();
+ final String targetPath = syncedState.getRemotePath();
//If an Etag is already Stored and LastModified from DB is the same as real file
- if (mSyncedState.isLastEtagStored()
- && mSyncedState.getLocalLastModified() == file.lastModified()) {
- Log.d(TAG, "mySyncedState last modified: "+mSyncedState.getLocalLastModified()+" <=> mFile last modified: "+file.lastModified() +": So return sync_conflict");
+ if (syncedState.isLastEtagStored()
+ && syncedState.getLocalLastModified() == file.lastModified()) {
+ Log.d(TAG, "syncedState last modified: "+ syncedState.getLocalLastModified()+" <=> file last modified: "+file.lastModified() +": So return sync_conflict");
return new RemoteOperationResult(ResultCode.SYNC_CONFLICT);
}
- if(this.availableQuota == -1){
+ Float relativeQuotaBeforeFileUpload = 0.0f;
+ if (this.availableQuota == -1) {
RemoteOperationResult checkQuotaResult = checkAvailableSpace(client, file.length());
- if( checkQuotaResult.getCode() != ResultCode.OK ){
+ if (checkQuotaResult.getCode() != ResultCode.OK) {
return new RemoteOperationResult(checkQuotaResult.getCode());
+ } else {
+ relativeQuotaBeforeFileUpload = ((Double) checkQuotaResult.getSingleData()).floatValue();
}
}
- UploadFileRemoteOperation uploadOperation = buildUploadOperation(file, targetPath);
+ final UploadFileRemoteOperation uploadOperation = buildUploadOperation(file, targetPath);
// Execute UploadFileOperation
- RemoteOperationResult uploadResult = uploadOperation.execute( client );
- ResultCode mResultCode;
+ RemoteOperationResult uploadResult = uploadOperation.execute(client );
+ ResultCode resultCode;
boolean mustRestart = true;
//if upload is a success
- if( uploadResult.isSuccess() ){
+ if (uploadResult.isSuccess()) {
Object data = uploadResult.getSingleData();
- if(data != null){
- mSyncedState.setLastETAG((String) data);
+ if (data != null) {
+ syncedState.setLastETAG((String) data);
}
- mSyncedState.setLocalLastModified(file.lastModified());
- mResultCode = uploadResult.getCode();
+ syncedState.setLocalLastModified(file.lastModified());
+ resultCode = uploadResult.getCode();
mustRestart = false;
- }else{
+ } else {
//Si les répértoires ou mettre le fichier n'existe pas, on les ajoutes.
- if( uploadResult.getCode() == ResultCode.FILE_NOT_FOUND ){
- Log.d(TAG, "Catched a File not found result for : "+file.getName()+", create missing remote path then retry");
- String remoteFoldersPath = targetPath.substring( 0, targetPath.lastIndexOf(FileUtils.PATH_SEPARATOR)+1 );
- mResultCode = ResultCode.FILE_NOT_FOUND;
- CreateFolderRemoteOperation createFolderOperation = new CreateFolderRemoteOperation( remoteFoldersPath, true );
+ if (uploadResult.getCode() == ResultCode.CONFLICT ) {
+ resultCode = ResultCode.CONFLICT;
+ Log.d(TAG, "Catched a conflict result for : "+file.getName()+", create missing remote path then retry");
+ final String remoteFolderPath = targetPath.substring(0, targetPath.lastIndexOf(FileUtils.PATH_SEPARATOR)+1 );
+ final CreateFolderRemoteOperation createFolderOperation = new CreateFolderRemoteOperation(remoteFolderPath, true );
try{
- RemoteOperationResult createFolderResult = createFolderOperation.execute( client );
+ RemoteOperationResult createFolderResult = createFolderOperation.execute(client );
- if(!createFolderResult.isSuccess() && createFolderResult.getCode() != ResultCode.FOLDER_ALREADY_EXISTS){
- mResultCode = createFolderResult.getCode();
+ if (!createFolderResult.isSuccess() && createFolderResult.getCode() != ResultCode.FOLDER_ALREADY_EXISTS) {
+ resultCode = createFolderResult.getCode();
Log.e(TAG, createFolderResult.getLogMessage());
mustRestart = false;
- mSyncedState.setLocalLastModified( this.previousLastModified);
+ syncedState.setLocalLastModified(this.previousLastModified);
}
- }catch(Exception e){
+ }catch(Exception e) {
Log.e(TAG, e.toString() );
- mSyncedState.setLocalLastModified( this.previousLastModified);
+ syncedState.setLocalLastModified(this.previousLastModified);
mustRestart = false;
}
- }else if(uploadResult.getCode() == ResultCode.QUOTA_EXCEEDED){
- mResultCode = ResultCode.QUOTA_EXCEEDED;
+ } else if (uploadResult.getCode() == ResultCode.QUOTA_EXCEEDED) {
+ resultCode = ResultCode.QUOTA_EXCEEDED;
mustRestart = false;
- }else{
+ } else {
//Upload failed
Log.e(TAG, "UploadFileRemoteOperation for : " + file.getName() + " failed => code: " + uploadResult.getCode());
- mResultCode = ResultCode.UNKNOWN_ERROR;
+ resultCode = ResultCode.UNKNOWN_ERROR;
mustRestart = false;
}
}
- if(mustRestart) {
+ if (mustRestart) {
if (this.restartCounter < 1) {
this.restartCounter += 1;
//if we encounter more than three times same error, stop trying to download.
return this.run(client);
} else {
- mSyncedState.setLocalLastModified( this.previousLastModified); //Revert syncFileState to its previous state
+ syncedState.setLocalLastModified(this.previousLastModified); //Revert syncFileState to its previous state
}
}
// updated syncedFile in DB
- DbHelper.manageSyncedFileStateDB(mSyncedState, "UPDATE", mContext);
+ DbHelper.manageSyncedFileStateDB(syncedState, "UPDATE", context);
ArrayList