diff --git a/app/src/main/java/foundation/e/drive/EdriveApplication.java b/app/src/main/java/foundation/e/drive/EdriveApplication.java index e8de2b00a4caa19b665ead13d0700f82ca3683bd..e36b57528dcb30de291936473c4e7b321beab0a0 100644 --- a/app/src/main/java/foundation/e/drive/EdriveApplication.java +++ b/app/src/main/java/foundation/e/drive/EdriveApplication.java @@ -31,17 +31,17 @@ 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, new FileEventListener()); + mFileObserver = new RecursiveFileObserver(getApplicationContext(), pathForObserver, fileEventListener); SharedPreferences prefs = getSharedPreferences(AppConstants.SHARED_PREFERENCE_NAME, Context.MODE_PRIVATE); @@ -77,8 +77,9 @@ public class EdriveApplication extends Application { */ public void startRecursiveFileObserver(){ if (!mFileObserver.isWatching()) { + fileEventListener.bindToSynchronizationService(); mFileObserver.startWatching(); - Log.d(TAG, "Starting RecursiveFileObserver on media's root folder"); + Log.d(TAG, "Starting RecursiveFileObserver on root folder"); } else { Log.w(TAG, "RecursiveFileObserver (for media's root folder) was already running"); @@ -87,7 +88,8 @@ public class EdriveApplication extends Application { public void stopRecursiveFileObserver(){ mFileObserver.stopWatching(); - Log.d(TAG, "RecursiveFileObserver on media's root folder stops watching "); + 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 index bcceca7b84ca2af9839339d5341490610fa07728..a4a0b634c9acea0ce2ed8c56312a43394a179591 100644 --- a/app/src/main/java/foundation/e/drive/FileObservers/FileEventListener.java +++ b/app/src/main/java/foundation/e/drive/FileObservers/FileEventListener.java @@ -1,5 +1,6 @@ /* * 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 @@ -8,23 +9,170 @@ 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 final static String TAG = FileEventListener.class.getSimpleName(); + 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.CLOSE_WRITE) { //Event triggered after modification/creation - Log.d(TAG, "CLOSE_WRITE event for :" + file.getName()); - } else if (event == FileObserver.DELETE) { - Log.d(TAG, "DELETE event for :" + file.getName()); + 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/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/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/services/ObserverService.java b/app/src/main/java/foundation/e/drive/services/ObserverService.java index 632d5b8a1c23e47299c0e18da3a46d3fe49c9daa..a84cb696c85e132e5a8c07d53103e84ccca9c626 100644 --- a/app/src/main/java/foundation/e/drive/services/ObserverService.java +++ b/app/src/main/java/foundation/e/drive/services/ObserverService.java @@ -50,6 +50,7 @@ import foundation.e.drive.utils.AppConstants; import foundation.e.drive.utils.CommonUtils; import foundation.e.drive.utils.DavClientProvider; import foundation.e.drive.utils.ServiceExceptionHandler; +import foundation.e.drive.utils.SynchronizationServiceConnection; import static com.owncloud.android.lib.resources.files.FileUtils.PATH_SEPARATOR; import static foundation.e.drive.utils.AppConstants.INITIALIZATION_HAS_BEEN_DONE; @@ -70,36 +71,24 @@ public class ObserverService extends Service implements OnRemoteOperationListene private int initialFolderCounter; private Account mAccount; private HashMap syncRequests; //integer is SyncedFileState id; Parcelable is the operation - - private SynchronizationService synchronizationService; - private boolean boundToSynchronizationService = false; - private ServiceConnection SynchronizationServiceConnection = new ServiceConnection() { - @Override - public void onServiceConnected(ComponentName componentName, IBinder iBinder) { - SynchronizationService.SynchronizationBinder binder = (SynchronizationService.SynchronizationBinder) iBinder; - synchronizationService = binder.getService(); - boundToSynchronizationService = true; - } - - @Override - public void onServiceDisconnected(ComponentName componentName) { - Log.e(TAG, "onServiceDisconnected"); - boundToSynchronizationService = false; - } - }; + private SynchronizationServiceConnection synchronizationServiceConnection = new SynchronizationServiceConnection(); /* Lifecycle Methods */ @Override public void onDestroy(){ Log.i(TAG, "onDestroy()"); + unbindService(synchronizationServiceConnection); super.onDestroy(); this.mSyncedFolders = null; } + @Override public int onStartCommand(Intent intent, int flags, int startId) { Log.i(TAG, "onStartCommand("+startId+")"); + final Intent SynchronizationServiceIntent = new Intent(this, SynchronizationService.class); + bindService(SynchronizationServiceIntent, synchronizationServiceConnection, Context.BIND_AUTO_CREATE); CommonUtils.setServiceUnCaughtExceptionHandler(this); @@ -162,11 +151,8 @@ public class ObserverService extends Service implements OnRemoteOperationListene } this.syncRequests = new HashMap<>(); - Intent SynchronizationServiceIntent = new Intent(this.getApplicationContext(), SynchronizationService.class); - bindService(SynchronizationServiceIntent, SynchronizationServiceConnection, Context.BIND_AUTO_CREATE); - begin(); - return super.onStartCommand( intent, flags, startId ); + return START_NOT_STICKY; } /* Common methods */ @@ -317,11 +303,11 @@ public class ObserverService extends Service implements OnRemoteOperationListene } this.startScan(false); - Log.v(TAG, "operationsForIntent contains "+ syncRequests.size() ); + Log.v(TAG, "operationsForIntent contains " + syncRequests.size()); //After everything has been scanned. Send Intent to OperationmanagerService with data in bundle if (syncRequests != null && !syncRequests.isEmpty()) { - this.synchronizationService.queueOperations(syncRequests.values()); + passSyncRequestsToSynchronizationService(); } else { Log.w(TAG, "There is no file to sync."); getSharedPreferences(AppConstants.SHARED_PREFERENCE_NAME, Context.MODE_PRIVATE) @@ -330,13 +316,22 @@ public class ObserverService extends Service implements OnRemoteOperationListene .putBoolean(AppConstants.KEY_OMS_IS_WORKING, false) .apply(); } - this.isWorking = false; - unbindService(SynchronizationServiceConnection); this.stopSelf(); + } + } + + private void passSyncRequestsToSynchronizationService() { + if (synchronizationServiceConnection.isBoundToSynchronizationService()) { + synchronizationServiceConnection.getSynchronizationService().queueOperations(syncRequests.values()); + synchronizationServiceConnection.getSynchronizationService().startSynchronization(); + } else { + Log.e(TAG, "ERROR: impossible to bind ObserverService to SynchronizationService"); } } + + /** * Method to get Id of SyncedFolder to scan * @return List id of SyncedFolder to scan @@ -431,8 +426,13 @@ public class ObserverService extends Service implements OnRemoteOperationListene String fileName = CommonUtils.getFileNameFromPath(remoteFilePath); //get remote file's name if (fileName != null) { + int scannableValue = 0; + if (parentFolder.isEnabled()) { + if (parentFolder.isScanRemote()) scannableValue++; + if (parentFolder.isScanLocal()) scannableValue += 2; + } //create syncedFileState - SyncedFileState newRemoteFile = new SyncedFileState(-1, fileName, parentFolder.getLocalFolder() + fileName, remoteFilePath, remoteFile.getEtag(), 0, parentFolder.getId(), parentFolder.isMediaType()); + SyncedFileState newRemoteFile = new SyncedFileState(-1, fileName, parentFolder.getLocalFolder() + fileName, remoteFilePath, remoteFile.getEtag(), 0, parentFolder.getId(), parentFolder.isMediaType(), scannableValue); //Store it in DB int storedId = DbHelper.manageSyncedFileStateDB(newRemoteFile, "INSERT", this); @@ -631,8 +631,6 @@ public class ObserverService extends Service implements OnRemoteOperationListene } } } //end of iterator loop - - if (contentToSyncFound) { DbHelper.updateSyncedFolders(mSyncedFolders, this); //@ToDo: maybe do this when all contents will be synced. List syncedFileStates = DbHelper.getSyncedFileStatesByFolders(this, @@ -706,8 +704,14 @@ public class ObserverService extends Service implements OnRemoteOperationListene //look into synced folders if folder path exist for(SyncedFolder syncedFolder : mSyncedFolders){ if (syncedFolder.getLocalFolder().equals(parentPath)){ + int scannableValue = 0; + if (syncedFolder.isEnabled()) { + if (syncedFolder.isScanRemote()) scannableValue++; + if (syncedFolder.isScanLocal()) scannableValue += 2; + } + //create the syncedFile State - SyncedFileState newSyncedFileState = new SyncedFileState(-1, localFile.getName(), filePath, syncedFolder.getRemoteFolder() + localFile.getName(), "", 0, syncedFolder.getId(), syncedFolder.isMediaType()); + SyncedFileState newSyncedFileState = new SyncedFileState(-1, localFile.getName(), filePath, syncedFolder.getRemoteFolder() + localFile.getName(), "", 0, syncedFolder.getId(), syncedFolder.isMediaType(),scannableValue); //Store it in DB int storedId = DbHelper.manageSyncedFileStateDB(newSyncedFileState, "INSERT", this); diff --git a/app/src/main/java/foundation/e/drive/services/SynchronizationService.java b/app/src/main/java/foundation/e/drive/services/SynchronizationService.java index d532786e5599b196184d512fc7326d01116c35f5..e03d4a55e343dfd6403ef4bf0300b1837963a8ae 100644 --- a/app/src/main/java/foundation/e/drive/services/SynchronizationService.java +++ b/app/src/main/java/foundation/e/drive/services/SynchronizationService.java @@ -66,16 +66,17 @@ public class SynchronizationService extends Service implements OnRemoteOperation public int onStartCommand(Intent intent, int flags, int startId) { Log.d(TAG, "onStartCommand()"); - final SharedPreferences prefs = getSharedPreferences(AppConstants.SHARED_PREFERENCE_NAME, Context.MODE_PRIVATE); + SharedPreferences prefs = this.getSharedPreferences(AppConstants.SHARED_PREFERENCE_NAME, Context.MODE_PRIVATE); + final String accountName = prefs.getString(AccountManager.KEY_ACCOUNT_NAME, ""); + final String accountType = prefs.getString(AccountManager.KEY_ACCOUNT_TYPE, ""); + account = CommonUtils.getAccount(accountName, accountType, AccountManager.get(this)); - if (prefs.getString(AccountManager.KEY_ACCOUNT_NAME, null) == null) { + if (account == null) { Log.w(TAG, "No account available. Stop SynchronizationService"); stopSelf(); return START_NOT_STICKY; } - - account = (Account) intent.getParcelableExtra("account"); syncedRequestQueue = new ConcurrentLinkedDeque<>(); startedOperations = new Hashtable<>(); threadPool = new Thread[workerAmount]; @@ -259,7 +260,7 @@ public class SynchronizationService extends Service implements OnRemoteOperation } public class SynchronizationBinder extends Binder{ - SynchronizationService getService(){ + public SynchronizationService getService(){ return SynchronizationService.this; } } diff --git a/app/src/main/java/foundation/e/drive/utils/SynchronizationServiceConnection.java b/app/src/main/java/foundation/e/drive/utils/SynchronizationServiceConnection.java new file mode 100644 index 0000000000000000000000000000000000000000..7f828ff94aba24f7b55d1395959ed206882b0bf1 --- /dev/null +++ b/app/src/main/java/foundation/e/drive/utils/SynchronizationServiceConnection.java @@ -0,0 +1,52 @@ +/** + * 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.utils; + +import android.content.ComponentName; +import android.content.ServiceConnection; +import android.os.IBinder; +import android.util.Log; + +import foundation.e.drive.services.SynchronizationService; + +/** + * @author Vincent Bourgmayer + */ +public class SynchronizationServiceConnection implements ServiceConnection { + private final static String TAG = SynchronizationServiceConnection.class.getSimpleName(); + + private SynchronizationService synchronizationService; + private boolean boundToSynchronizationService = false; + + @Override + public void onServiceConnected(ComponentName componentName, IBinder iBinder) { + Log.i(TAG, "onServiceConnected: binding to SynchronizationService"); + SynchronizationService.SynchronizationBinder binder = (SynchronizationService.SynchronizationBinder) iBinder; + synchronizationService = binder.getService(); + boundToSynchronizationService = true; + } + + @Override + public void onServiceDisconnected(ComponentName componentName) { + Log.i(TAG, "onServiceDisconnected: unbinding from SynchronizationService"); + boundToSynchronizationService = false; + } + + public boolean isBoundToSynchronizationService() { + return boundToSynchronizationService; + } + + /** + * Get SynchronizationService. Might be null! + * @return + */ + public SynchronizationService getSynchronizationService() { + return synchronizationService; + } +}