Merge pull request #4787 from ArisuOngaku/auto-upload-start-date-persistence

Make synced folder init/enable date persistent
This commit is contained in:
Tobias Kaminsky 2019-11-19 13:21:43 +01:00 committed by GitHub
commit 94502f66b5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
18 changed files with 211 additions and 155 deletions

View File

@ -32,6 +32,7 @@ import com.owncloud.android.files.services.FileDownloader;
import com.owncloud.android.files.services.FileUploader; import com.owncloud.android.files.services.FileUploader;
import com.owncloud.android.jobs.NotificationJob; import com.owncloud.android.jobs.NotificationJob;
import com.owncloud.android.providers.DiskLruImageCacheFileProvider; import com.owncloud.android.providers.DiskLruImageCacheFileProvider;
import com.owncloud.android.providers.FileContentProvider;
import com.owncloud.android.providers.UsersAndGroupsSearchProvider; import com.owncloud.android.providers.UsersAndGroupsSearchProvider;
import com.owncloud.android.services.AccountManagerService; import com.owncloud.android.services.AccountManagerService;
import com.owncloud.android.services.OperationsService; import com.owncloud.android.services.OperationsService;
@ -148,6 +149,7 @@ abstract class ComponentsModule {
@ContributesAndroidInjector abstract BootupBroadcastReceiver bootupBroadcastReceiver(); @ContributesAndroidInjector abstract BootupBroadcastReceiver bootupBroadcastReceiver();
@ContributesAndroidInjector abstract NotificationJob.NotificationReceiver notificationJobBroadcastReceiver(); @ContributesAndroidInjector abstract NotificationJob.NotificationReceiver notificationJobBroadcastReceiver();
@ContributesAndroidInjector abstract FileContentProvider fileContentProvider();
@ContributesAndroidInjector abstract UsersAndGroupsSearchProvider usersAndGroupsSearchProvider(); @ContributesAndroidInjector abstract UsersAndGroupsSearchProvider usersAndGroupsSearchProvider();
@ContributesAndroidInjector abstract DiskLruImageCacheFileProvider diskLruImageCacheFileProvider(); @ContributesAndroidInjector abstract DiskLruImageCacheFileProvider diskLruImageCacheFileProvider();

View File

@ -26,6 +26,7 @@ import androidx.annotation.RequiresApi
import androidx.work.ListenableWorker import androidx.work.ListenableWorker
import androidx.work.WorkerFactory import androidx.work.WorkerFactory
import androidx.work.WorkerParameters import androidx.work.WorkerParameters
import com.nextcloud.client.core.Clock
import com.nextcloud.client.device.DeviceInfo import com.nextcloud.client.device.DeviceInfo
import com.nextcloud.client.device.PowerManagementService import com.nextcloud.client.device.PowerManagementService
import com.nextcloud.client.preferences.AppPreferences import com.nextcloud.client.preferences.AppPreferences
@ -40,6 +41,7 @@ import javax.inject.Provider
class BackgroundJobFactory @Inject constructor( class BackgroundJobFactory @Inject constructor(
private val preferences: AppPreferences, private val preferences: AppPreferences,
private val contentResolver: ContentResolver, private val contentResolver: ContentResolver,
private val clock: Clock,
private val powerManagerService: PowerManagementService, private val powerManagerService: PowerManagementService,
private val backgroundJobManager: Provider<BackgroundJobManager>, private val backgroundJobManager: Provider<BackgroundJobManager>,
private val deviceInfo: DeviceInfo private val deviceInfo: DeviceInfo
@ -58,13 +60,14 @@ class BackgroundJobFactory @Inject constructor(
} }
return when (workerClass) { return when (workerClass) {
ContentObserverWork::class -> createContentObserverJob(context, workerParameters) ContentObserverWork::class -> createContentObserverJob(context, workerParameters, clock)
else -> null // falls back to default factory else -> null // falls back to default factory
} }
} }
private fun createContentObserverJob(context: Context, workerParameters: WorkerParameters): ListenableWorker? { private fun createContentObserverJob(context: Context, workerParameters: WorkerParameters, clock: Clock):
val folderResolver = SyncedFolderProvider(contentResolver, preferences) ListenableWorker? {
val folderResolver = SyncedFolderProvider(contentResolver, preferences, clock)
@RequiresApi(Build.VERSION_CODES.N) @RequiresApi(Build.VERSION_CODES.N)
if (deviceInfo.apiLevel >= Build.VERSION_CODES.N) { if (deviceInfo.apiLevel >= Build.VERSION_CODES.N) {
return ContentObserverWork( return ContentObserverWork(

View File

@ -45,6 +45,7 @@ import com.evernote.android.job.JobManager;
import com.evernote.android.job.JobRequest; import com.evernote.android.job.JobRequest;
import com.nextcloud.client.account.UserAccountManager; import com.nextcloud.client.account.UserAccountManager;
import com.nextcloud.client.appinfo.AppInfo; import com.nextcloud.client.appinfo.AppInfo;
import com.nextcloud.client.core.Clock;
import com.nextcloud.client.device.PowerManagementService; import com.nextcloud.client.device.PowerManagementService;
import com.nextcloud.client.di.ActivityInjector; import com.nextcloud.client.di.ActivityInjector;
import com.nextcloud.client.di.DaggerAppComponent; import com.nextcloud.client.di.DaggerAppComponent;
@ -161,6 +162,9 @@ public class MainApp extends MultiDexApplication implements HasAndroidInjector {
@Inject @Inject
BackgroundJobManager backgroundJobManager; BackgroundJobManager backgroundJobManager;
@Inject
Clock clock;
private PassCodeManager passCodeManager; private PassCodeManager passCodeManager;
@SuppressWarnings("unused") @SuppressWarnings("unused")
@ -268,7 +272,8 @@ public class MainApp extends MultiDexApplication implements HasAndroidInjector {
preferences, preferences,
uploadsStorageManager, uploadsStorageManager,
connectivityService, connectivityService,
powerManagementService powerManagementService,
clock
) )
); );
@ -304,7 +309,8 @@ public class MainApp extends MultiDexApplication implements HasAndroidInjector {
accountManager, accountManager,
connectivityService, connectivityService,
powerManagementService, powerManagementService,
backgroundJobManager); backgroundJobManager,
clock);
initContactsBackup(accountManager); initContactsBackup(accountManager);
notificationChannels(); notificationChannels();
@ -462,23 +468,24 @@ public class MainApp extends MultiDexApplication implements HasAndroidInjector {
final UserAccountManager accountManager, final UserAccountManager accountManager,
final ConnectivityService connectivityService, final ConnectivityService connectivityService,
final PowerManagementService powerManagementService, final PowerManagementService powerManagementService,
final BackgroundJobManager jobManager final BackgroundJobManager jobManager,
final Clock clock
) { ) {
updateToAutoUpload(); updateToAutoUpload();
cleanOldEntries(); cleanOldEntries(clock);
updateAutoUploadEntries(); updateAutoUploadEntries(clock);
if (getAppContext() != null) { if (getAppContext() != null) {
if (PermissionUtil.checkSelfPermission(getAppContext(), if (PermissionUtil.checkSelfPermission(getAppContext(),
Manifest.permission.WRITE_EXTERNAL_STORAGE)) { Manifest.permission.WRITE_EXTERNAL_STORAGE)) {
splitOutAutoUploadEntries(); splitOutAutoUploadEntries(clock);
} else { } else {
AppPreferences preferences = AppPreferencesImpl.fromContext(getAppContext()); AppPreferences preferences = AppPreferencesImpl.fromContext(getAppContext());
preferences.setAutoUploadSplitEntriesEnabled(true); preferences.setAutoUploadSplitEntriesEnabled(true);
} }
} }
initiateExistingAutoUploadEntries(); initiateExistingAutoUploadEntries(clock);
FilesSyncHelper.scheduleFilesSyncIfNeeded(mContext, jobManager); FilesSyncHelper.scheduleFilesSyncIfNeeded(mContext, jobManager);
FilesSyncHelper.restartJobsIfNeeded( FilesSyncHelper.restartJobsIfNeeded(
@ -685,18 +692,18 @@ public class MainApp extends MultiDexApplication implements HasAndroidInjector {
} }
} }
private static void updateAutoUploadEntries() { private static void updateAutoUploadEntries(Clock clock) {
// updates entries to reflect their true paths // updates entries to reflect their true paths
Context context = getAppContext(); Context context = getAppContext();
AppPreferences preferences = AppPreferencesImpl.fromContext(context); AppPreferences preferences = AppPreferencesImpl.fromContext(context);
if (!preferences.isAutoUploadPathsUpdateEnabled()) { if (!preferences.isAutoUploadPathsUpdateEnabled()) {
SyncedFolderProvider syncedFolderProvider = SyncedFolderProvider syncedFolderProvider =
new SyncedFolderProvider(MainApp.getAppContext().getContentResolver(), preferences); new SyncedFolderProvider(MainApp.getAppContext().getContentResolver(), preferences, clock);
syncedFolderProvider.updateAutoUploadPaths(mContext); syncedFolderProvider.updateAutoUploadPaths(mContext);
} }
} }
private static void splitOutAutoUploadEntries() { private static void splitOutAutoUploadEntries(Clock clock) {
Context context = getAppContext(); Context context = getAppContext();
AppPreferences preferences = AppPreferencesImpl.fromContext(context); AppPreferences preferences = AppPreferencesImpl.fromContext(context);
if (!preferences.isAutoUploadSplitEntriesEnabled()) { if (!preferences.isAutoUploadSplitEntriesEnabled()) {
@ -705,7 +712,7 @@ public class MainApp extends MultiDexApplication implements HasAndroidInjector {
Log_OC.i(TAG, "Migrate synced_folders records for image/video split"); Log_OC.i(TAG, "Migrate synced_folders records for image/video split");
ContentResolver contentResolver = context.getContentResolver(); ContentResolver contentResolver = context.getContentResolver();
SyncedFolderProvider syncedFolderProvider = new SyncedFolderProvider(contentResolver, preferences); SyncedFolderProvider syncedFolderProvider = new SyncedFolderProvider(contentResolver, preferences, clock);
final List<MediaFolder> imageMediaFolders = MediaProvider.getImageFolders(contentResolver, 1, null, true); final List<MediaFolder> imageMediaFolders = MediaProvider.getImageFolders(contentResolver, 1, null, true);
final List<MediaFolder> videoMediaFolders = MediaProvider.getVideoFolders(contentResolver, 1, null, true); final List<MediaFolder> videoMediaFolders = MediaProvider.getVideoFolders(contentResolver, 1, null, true);
@ -751,12 +758,12 @@ public class MainApp extends MultiDexApplication implements HasAndroidInjector {
} }
} }
private static void initiateExistingAutoUploadEntries() { private static void initiateExistingAutoUploadEntries(Clock clock) {
new Thread(() -> { new Thread(() -> {
AppPreferences preferences = AppPreferencesImpl.fromContext(getAppContext()); AppPreferences preferences = AppPreferencesImpl.fromContext(getAppContext());
if (!preferences.isAutoUploadInitialized()) { if (!preferences.isAutoUploadInitialized()) {
SyncedFolderProvider syncedFolderProvider = SyncedFolderProvider syncedFolderProvider =
new SyncedFolderProvider(MainApp.getAppContext().getContentResolver(), preferences); new SyncedFolderProvider(MainApp.getAppContext().getContentResolver(), preferences, clock);
for (SyncedFolder syncedFolder : syncedFolderProvider.getSyncedFolders()) { for (SyncedFolder syncedFolder : syncedFolderProvider.getSyncedFolders()) {
if (syncedFolder.isEnabled()) { if (syncedFolder.isEnabled()) {
@ -770,7 +777,7 @@ public class MainApp extends MultiDexApplication implements HasAndroidInjector {
}).start(); }).start();
} }
private static void cleanOldEntries() { private static void cleanOldEntries(Clock clock) {
// previous versions of application created broken entries in the SyncedFolderProvider // previous versions of application created broken entries in the SyncedFolderProvider
// database, and this cleans all that and leaves 1 (newest) entry per synced folder // database, and this cleans all that and leaves 1 (newest) entry per synced folder
@ -779,7 +786,7 @@ public class MainApp extends MultiDexApplication implements HasAndroidInjector {
if (!preferences.isLegacyClean()) { if (!preferences.isLegacyClean()) {
SyncedFolderProvider syncedFolderProvider = SyncedFolderProvider syncedFolderProvider =
new SyncedFolderProvider(context.getContentResolver(), preferences); new SyncedFolderProvider(context.getContentResolver(), preferences, clock);
List<SyncedFolder> syncedFolderList = syncedFolderProvider.getSyncedFolders(); List<SyncedFolder> syncedFolderList = syncedFolderProvider.getSyncedFolders();
Map<Pair<String, String>, Long> syncedFolders = new HashMap<>(); Map<Pair<String, String>, Long> syncedFolders = new HashMap<>();

View File

@ -30,23 +30,23 @@ import lombok.Setter;
/** /**
* Synced folder entity containing all information per synced folder. * Synced folder entity containing all information per synced folder.
*/ */
@Getter
@Setter
@AllArgsConstructor @AllArgsConstructor
public class SyncedFolder implements Serializable, Cloneable { public class SyncedFolder implements Serializable, Cloneable {
public static final long UNPERSISTED_ID = Long.MIN_VALUE; public static final long UNPERSISTED_ID = Long.MIN_VALUE;
public static final long EMPTY_ENABLED_TIMESTAMP_MS = -1;
private static final long serialVersionUID = -793476118299906429L; private static final long serialVersionUID = -793476118299906429L;
private long id = UNPERSISTED_ID; @Getter @Setter private long id;
private String localPath; @Getter @Setter private String localPath;
private String remotePath; @Getter @Setter private String remotePath;
private Boolean wifiOnly; @Getter @Setter private Boolean wifiOnly;
private Boolean chargingOnly; @Getter @Setter private Boolean chargingOnly;
private Boolean subfolderByDate; @Getter @Setter private Boolean subfolderByDate;
private String account; @Getter @Setter private String account;
private Integer uploadAction; @Getter @Setter private Integer uploadAction;
private boolean enabled; @Getter private boolean enabled;
private MediaFolderType type; @Getter private long enabledTimestampMs;
@Getter @Setter private MediaFolderType type;
/** /**
* constructor for new, to be persisted entity. * constructor for new, to be persisted entity.
@ -59,11 +59,25 @@ public class SyncedFolder implements Serializable, Cloneable {
* @param account the account owning the synced folder * @param account the account owning the synced folder
* @param uploadAction the action to be done after the upload * @param uploadAction the action to be done after the upload
* @param enabled flag if synced folder config is active * @param enabled flag if synced folder config is active
* @param timestampMs the current timestamp in milliseconds
* @param type the type of the folder * @param type the type of the folder
*/ */
public SyncedFolder(String localPath, String remotePath, Boolean wifiOnly, Boolean chargingOnly, public SyncedFolder(String localPath, String remotePath, Boolean wifiOnly, Boolean chargingOnly,
Boolean subfolderByDate, String account, Integer uploadAction, Boolean enabled, Boolean subfolderByDate, String account, Integer uploadAction, Boolean enabled,
MediaFolderType type) { long timestampMs, MediaFolderType type) {
this(UNPERSISTED_ID, localPath, remotePath, wifiOnly, chargingOnly, subfolderByDate, account, uploadAction,
enabled, timestampMs, type);
}
/**
* constructor for wrapping existing folders.
*
* @param id id
*/
protected SyncedFolder(long id, String localPath, String remotePath, Boolean wifiOnly, Boolean chargingOnly,
Boolean subfolderByDate, String account, Integer uploadAction, Boolean enabled,
long timestampMs, MediaFolderType type) {
this.id = id;
this.localPath = localPath; this.localPath = localPath;
this.remotePath = remotePath; this.remotePath = remotePath;
this.wifiOnly = wifiOnly; this.wifiOnly = wifiOnly;
@ -71,10 +85,18 @@ public class SyncedFolder implements Serializable, Cloneable {
this.subfolderByDate = subfolderByDate; this.subfolderByDate = subfolderByDate;
this.account = account; this.account = account;
this.uploadAction = uploadAction; this.uploadAction = uploadAction;
this.enabled = enabled; this.setEnabled(enabled, timestampMs);
this.type = type; this.type = type;
} }
/**
* @param timestampMs the current timestamp in milliseconds
*/
public void setEnabled(boolean enabled, long timestampMs) {
this.enabled = enabled;
this.enabledTimestampMs = enabled ? timestampMs : EMPTY_ENABLED_TIMESTAMP_MS;
}
public Object clone() { public Object clone() {
try { try {
return super.clone(); return super.clone();

View File

@ -56,9 +56,11 @@ public class SyncedFolderDisplayItem extends SyncedFolder {
*/ */
public SyncedFolderDisplayItem(long id, String localPath, String remotePath, Boolean wifiOnly, Boolean chargingOnly, public SyncedFolderDisplayItem(long id, String localPath, String remotePath, Boolean wifiOnly, Boolean chargingOnly,
Boolean subfolderByDate, String account, Integer uploadAction, Boolean enabled, Boolean subfolderByDate, String account, Integer uploadAction, Boolean enabled,
List<String> filePaths, String folderName, long numberOfFiles, MediaFolderType type) long timestampMs, List<String> filePaths, String folderName, long numberOfFiles,
MediaFolderType type)
{ {
super(id, localPath, remotePath, wifiOnly, chargingOnly, subfolderByDate, account, uploadAction, enabled, type); super(id, localPath, remotePath, wifiOnly, chargingOnly, subfolderByDate, account, uploadAction, enabled,
timestampMs, type);
this.filePaths = filePaths; this.filePaths = filePaths;
this.folderName = folderName; this.folderName = folderName;
this.numberOfFiles = numberOfFiles; this.numberOfFiles = numberOfFiles;
@ -66,8 +68,9 @@ public class SyncedFolderDisplayItem extends SyncedFolder {
public SyncedFolderDisplayItem(long id, String localPath, String remotePath, Boolean wifiOnly, Boolean chargingOnly, public SyncedFolderDisplayItem(long id, String localPath, String remotePath, Boolean wifiOnly, Boolean chargingOnly,
Boolean subfolderByDate, String account, Integer uploadAction, Boolean enabled, Boolean subfolderByDate, String account, Integer uploadAction, Boolean enabled,
String folderName, MediaFolderType type) { long timestampMs, String folderName, MediaFolderType type) {
super(id, localPath, remotePath, wifiOnly, chargingOnly, subfolderByDate, account, uploadAction, enabled, type); super(id, localPath, remotePath, wifiOnly, chargingOnly, subfolderByDate, account, uploadAction, enabled,
timestampMs, type);
this.folderName = folderName; this.folderName = folderName;
} }
} }

View File

@ -27,6 +27,7 @@ import android.content.Context;
import android.database.Cursor; import android.database.Cursor;
import android.net.Uri; import android.net.Uri;
import com.nextcloud.client.core.Clock;
import com.nextcloud.client.preferences.AppPreferences; import com.nextcloud.client.preferences.AppPreferences;
import com.nextcloud.client.preferences.AppPreferencesImpl; import com.nextcloud.client.preferences.AppPreferencesImpl;
import com.owncloud.android.db.ProviderMeta; import com.owncloud.android.db.ProviderMeta;
@ -47,20 +48,22 @@ import static com.owncloud.android.datamodel.OCFile.PATH_SEPARATOR;
public class SyncedFolderProvider extends Observable { public class SyncedFolderProvider extends Observable {
static private final String TAG = SyncedFolderProvider.class.getSimpleName(); static private final String TAG = SyncedFolderProvider.class.getSimpleName();
private ContentResolver mContentResolver; private final ContentResolver mContentResolver;
private AppPreferences preferences; private final AppPreferences preferences;
private final Clock clock;
/** /**
* constructor. * constructor.
* *
* @param contentResolver the ContentResolver to work with. * @param contentResolver the ContentResolver to work with.
*/ */
public SyncedFolderProvider(ContentResolver contentResolver, AppPreferences preferences) { public SyncedFolderProvider(ContentResolver contentResolver, AppPreferences preferences, Clock clock) {
if (contentResolver == null) { if (contentResolver == null) {
throw new IllegalArgumentException("Cannot create an instance with a NULL contentResolver"); throw new IllegalArgumentException("Cannot create an instance with a NULL contentResolver");
} }
mContentResolver = contentResolver; mContentResolver = contentResolver;
this.preferences = preferences; this.preferences = preferences;
this.clock = clock;
} }
/** /**
@ -162,7 +165,7 @@ public class SyncedFolderProvider extends Observable {
// read sync folder object and update // read sync folder object and update
SyncedFolder syncedFolder = createSyncedFolderFromCursor(cursor); SyncedFolder syncedFolder = createSyncedFolderFromCursor(cursor);
syncedFolder.setEnabled(enabled); syncedFolder.setEnabled(enabled, clock.getCurrentTime());
// update sync folder object in db // update sync folder object in db
result = updateSyncFolder(syncedFolder); result = updateSyncFolder(syncedFolder);
@ -347,11 +350,13 @@ public class SyncedFolderProvider extends Observable {
ProviderMeta.ProviderTableMeta.SYNCED_FOLDER_UPLOAD_ACTION)); ProviderMeta.ProviderTableMeta.SYNCED_FOLDER_UPLOAD_ACTION));
Boolean enabled = cursor.getInt(cursor.getColumnIndex( Boolean enabled = cursor.getInt(cursor.getColumnIndex(
ProviderMeta.ProviderTableMeta.SYNCED_FOLDER_ENABLED)) == 1; ProviderMeta.ProviderTableMeta.SYNCED_FOLDER_ENABLED)) == 1;
long enabledTimestampMs = cursor.getLong(cursor.getColumnIndex(
ProviderMeta.ProviderTableMeta.SYNCED_FOLDER_ENABLED_TIMESTAMP_MS));
MediaFolderType type = MediaFolderType.getById(cursor.getInt(cursor.getColumnIndex( MediaFolderType type = MediaFolderType.getById(cursor.getInt(cursor.getColumnIndex(
ProviderMeta.ProviderTableMeta.SYNCED_FOLDER_TYPE))); ProviderMeta.ProviderTableMeta.SYNCED_FOLDER_TYPE)));
syncedFolder = new SyncedFolder(id, localPath, remotePath, wifiOnly, chargingOnly, subfolderByDate, syncedFolder = new SyncedFolder(id, localPath, remotePath, wifiOnly, chargingOnly, subfolderByDate,
accountName, uploadAction, enabled, type); accountName, uploadAction, enabled, enabledTimestampMs, type);
} }
return syncedFolder; return syncedFolder;
} }
@ -370,6 +375,7 @@ public class SyncedFolderProvider extends Observable {
cv.put(ProviderMeta.ProviderTableMeta.SYNCED_FOLDER_WIFI_ONLY, syncedFolder.getWifiOnly()); cv.put(ProviderMeta.ProviderTableMeta.SYNCED_FOLDER_WIFI_ONLY, syncedFolder.getWifiOnly());
cv.put(ProviderMeta.ProviderTableMeta.SYNCED_FOLDER_CHARGING_ONLY, syncedFolder.getChargingOnly()); cv.put(ProviderMeta.ProviderTableMeta.SYNCED_FOLDER_CHARGING_ONLY, syncedFolder.getChargingOnly());
cv.put(ProviderMeta.ProviderTableMeta.SYNCED_FOLDER_ENABLED, syncedFolder.isEnabled()); cv.put(ProviderMeta.ProviderTableMeta.SYNCED_FOLDER_ENABLED, syncedFolder.isEnabled());
cv.put(ProviderMeta.ProviderTableMeta.SYNCED_FOLDER_ENABLED_TIMESTAMP_MS, syncedFolder.getEnabledTimestampMs());
cv.put(ProviderMeta.ProviderTableMeta.SYNCED_FOLDER_SUBFOLDER_BY_DATE, syncedFolder.getSubfolderByDate()); cv.put(ProviderMeta.ProviderTableMeta.SYNCED_FOLDER_SUBFOLDER_BY_DATE, syncedFolder.getSubfolderByDate());
cv.put(ProviderMeta.ProviderTableMeta.SYNCED_FOLDER_ACCOUNT, syncedFolder.getAccount()); cv.put(ProviderMeta.ProviderTableMeta.SYNCED_FOLDER_ACCOUNT, syncedFolder.getAccount());
cv.put(ProviderMeta.ProviderTableMeta.SYNCED_FOLDER_UPLOAD_ACTION, syncedFolder.getUploadAction()); cv.put(ProviderMeta.ProviderTableMeta.SYNCED_FOLDER_UPLOAD_ACTION, syncedFolder.getUploadAction());

View File

@ -31,7 +31,7 @@ import com.owncloud.android.MainApp;
*/ */
public class ProviderMeta { public class ProviderMeta {
public static final String DB_NAME = "filelist"; public static final String DB_NAME = "filelist";
public static final int DB_VERSION = 49; public static final int DB_VERSION = 50;
private ProviderMeta() { private ProviderMeta() {
// No instance // No instance
@ -220,6 +220,7 @@ public class ProviderMeta {
public static final String SYNCED_FOLDER_WIFI_ONLY = "wifi_only"; public static final String SYNCED_FOLDER_WIFI_ONLY = "wifi_only";
public static final String SYNCED_FOLDER_CHARGING_ONLY = "charging_only"; public static final String SYNCED_FOLDER_CHARGING_ONLY = "charging_only";
public static final String SYNCED_FOLDER_ENABLED = "enabled"; public static final String SYNCED_FOLDER_ENABLED = "enabled";
public static final String SYNCED_FOLDER_ENABLED_TIMESTAMP_MS = "enabled_timestamp_ms";
public static final String SYNCED_FOLDER_TYPE = "type"; public static final String SYNCED_FOLDER_TYPE = "type";
public static final String SYNCED_FOLDER_SUBFOLDER_BY_DATE = "subfolder_by_date"; public static final String SYNCED_FOLDER_SUBFOLDER_BY_DATE = "subfolder_by_date";
public static final String SYNCED_FOLDER_ACCOUNT = "account"; public static final String SYNCED_FOLDER_ACCOUNT = "account";

View File

@ -28,6 +28,7 @@ import android.content.Context;
import android.content.Intent; import android.content.Intent;
import com.nextcloud.client.account.UserAccountManager; import com.nextcloud.client.account.UserAccountManager;
import com.nextcloud.client.core.Clock;
import com.nextcloud.client.device.PowerManagementService; import com.nextcloud.client.device.PowerManagementService;
import com.nextcloud.client.jobs.BackgroundJobManager; import com.nextcloud.client.jobs.BackgroundJobManager;
import com.nextcloud.client.network.ConnectivityService; import com.nextcloud.client.network.ConnectivityService;
@ -53,6 +54,7 @@ public class BootupBroadcastReceiver extends BroadcastReceiver {
@Inject ConnectivityService connectivityService; @Inject ConnectivityService connectivityService;
@Inject PowerManagementService powerManagementService; @Inject PowerManagementService powerManagementService;
@Inject BackgroundJobManager backgroundJobManager; @Inject BackgroundJobManager backgroundJobManager;
@Inject Clock clock;
/** /**
* Receives broadcast intent reporting that the system was just boot up. * Receives broadcast intent reporting that the system was just boot up.
@ -69,7 +71,8 @@ public class BootupBroadcastReceiver extends BroadcastReceiver {
accountManager, accountManager,
connectivityService, connectivityService,
powerManagementService, powerManagementService,
backgroundJobManager); backgroundJobManager,
clock);
MainApp.initContactsBackup(accountManager); MainApp.initContactsBackup(accountManager);
} else { } else {
Log_OC.d(TAG, "Getting wrong intent: " + intent.getAction()); Log_OC.d(TAG, "Getting wrong intent: " + intent.getAction());

View File

@ -38,6 +38,7 @@ import com.evernote.android.job.Job;
import com.evernote.android.job.util.support.PersistableBundleCompat; import com.evernote.android.job.util.support.PersistableBundleCompat;
import com.google.gson.Gson; import com.google.gson.Gson;
import com.nextcloud.client.account.UserAccountManager; import com.nextcloud.client.account.UserAccountManager;
import com.nextcloud.client.core.Clock;
import com.nextcloud.client.preferences.AppPreferencesImpl; import com.nextcloud.client.preferences.AppPreferencesImpl;
import com.owncloud.android.MainApp; import com.owncloud.android.MainApp;
import com.owncloud.android.R; import com.owncloud.android.R;
@ -57,7 +58,6 @@ import com.owncloud.android.ui.activity.ContactsPreferenceActivity;
import com.owncloud.android.ui.events.AccountRemovedEvent; import com.owncloud.android.ui.events.AccountRemovedEvent;
import com.owncloud.android.utils.EncryptionUtils; import com.owncloud.android.utils.EncryptionUtils;
import com.owncloud.android.utils.FileStorageUtils; import com.owncloud.android.utils.FileStorageUtils;
import com.owncloud.android.utils.FilesSyncHelper;
import com.owncloud.android.utils.PushUtils; import com.owncloud.android.utils.PushUtils;
import org.greenrobot.eventbus.EventBus; import org.greenrobot.eventbus.EventBus;
@ -81,12 +81,14 @@ public class AccountRemovalJob extends Job implements AccountManagerCallback<Boo
public static final String ACCOUNT = "account"; public static final String ACCOUNT = "account";
public static final String REMOTE_WIPE = "remote_wipe"; public static final String REMOTE_WIPE = "remote_wipe";
private UploadsStorageManager uploadsStorageManager; private final UploadsStorageManager uploadsStorageManager;
private UserAccountManager userAccountManager; private final UserAccountManager userAccountManager;
private final Clock clock;
public AccountRemovalJob(UploadsStorageManager uploadStorageManager, UserAccountManager accountManager) { public AccountRemovalJob(UploadsStorageManager uploadStorageManager, UserAccountManager accountManager, Clock clock) {
this.uploadsStorageManager = uploadStorageManager; this.uploadsStorageManager = uploadStorageManager;
this.userAccountManager = accountManager; this.userAccountManager = accountManager;
this.clock = clock;
} }
@NonNull @NonNull
@ -129,7 +131,7 @@ public class AccountRemovalJob extends Job implements AccountManagerCallback<Boo
arbitraryDataProvider.deleteKeyForAccount(account.name, PENDING_FOR_REMOVAL); arbitraryDataProvider.deleteKeyForAccount(account.name, PENDING_FOR_REMOVAL);
// remove synced folders set for account // remove synced folders set for account
remoceSyncedFolders(context, account, arbitraryDataProvider); remoceSyncedFolders(context, account, clock);
// delete all uploads for account // delete all uploads for account
uploadsStorageManager.removeAccountUploads(account); uploadsStorageManager.removeAccountUploads(account);
@ -174,17 +176,16 @@ public class AccountRemovalJob extends Job implements AccountManagerCallback<Boo
} }
} }
private void remoceSyncedFolders(Context context, Account account, ArbitraryDataProvider arbitraryDataProvider) { private void remoceSyncedFolders(Context context, Account account, Clock clock) {
SyncedFolderProvider syncedFolderProvider = new SyncedFolderProvider(context.getContentResolver(), SyncedFolderProvider syncedFolderProvider = new SyncedFolderProvider(context.getContentResolver(),
AppPreferencesImpl.fromContext(context)); AppPreferencesImpl.fromContext(context),
clock);
List<SyncedFolder> syncedFolders = syncedFolderProvider.getSyncedFolders(); List<SyncedFolder> syncedFolders = syncedFolderProvider.getSyncedFolders();
List<Long> syncedFolderIds = new ArrayList<>(); List<Long> syncedFolderIds = new ArrayList<>();
for (SyncedFolder syncedFolder : syncedFolders) { for (SyncedFolder syncedFolder : syncedFolders) {
if (syncedFolder.getAccount().equals(account.name)) { if (syncedFolder.getAccount().equals(account.name)) {
arbitraryDataProvider.deleteKeyForAccount(FilesSyncHelper.GLOBAL,
FilesSyncHelper.SYNCEDFOLDERINITIATED + syncedFolder.getId());
syncedFolderIds.add(syncedFolder.getId()); syncedFolderIds.add(syncedFolder.getId());
} }
} }

View File

@ -34,6 +34,7 @@ import android.text.TextUtils;
import com.evernote.android.job.Job; import com.evernote.android.job.Job;
import com.evernote.android.job.util.support.PersistableBundleCompat; import com.evernote.android.job.util.support.PersistableBundleCompat;
import com.nextcloud.client.account.UserAccountManager; import com.nextcloud.client.account.UserAccountManager;
import com.nextcloud.client.core.Clock;
import com.nextcloud.client.device.PowerManagementService; import com.nextcloud.client.device.PowerManagementService;
import com.nextcloud.client.network.ConnectivityService; import com.nextcloud.client.network.ConnectivityService;
import com.nextcloud.client.preferences.AppPreferences; import com.nextcloud.client.preferences.AppPreferences;
@ -76,22 +77,25 @@ public class FilesSyncJob extends Job {
public static final String OVERRIDE_POWER_SAVING = "overridePowerSaving"; public static final String OVERRIDE_POWER_SAVING = "overridePowerSaving";
private static final String WAKELOCK_TAG_SEPARATION = ":"; private static final String WAKELOCK_TAG_SEPARATION = ":";
private UserAccountManager userAccountManager; private final UserAccountManager userAccountManager;
private AppPreferences preferences; private final AppPreferences preferences;
private UploadsStorageManager uploadsStorageManager; private final UploadsStorageManager uploadsStorageManager;
private ConnectivityService connectivityService; private final ConnectivityService connectivityService;
private PowerManagementService powerManagementService; private final PowerManagementService powerManagementService;
private final Clock clock;
FilesSyncJob(final UserAccountManager userAccountManager, FilesSyncJob(final UserAccountManager userAccountManager,
final AppPreferences preferences, final AppPreferences preferences,
final UploadsStorageManager uploadsStorageManager, final UploadsStorageManager uploadsStorageManager,
final ConnectivityService connectivityService, final ConnectivityService connectivityService,
final PowerManagementService powerManagementService) { final PowerManagementService powerManagementService,
final Clock clock) {
this.userAccountManager = userAccountManager; this.userAccountManager = userAccountManager;
this.preferences = preferences; this.preferences = preferences;
this.uploadsStorageManager = uploadsStorageManager; this.uploadsStorageManager = uploadsStorageManager;
this.connectivityService = connectivityService; this.connectivityService = connectivityService;
this.powerManagementService = powerManagementService; this.powerManagementService = powerManagementService;
this.clock = clock;
} }
@NonNull @NonNull
@ -126,13 +130,12 @@ public class FilesSyncJob extends Job {
userAccountManager, userAccountManager,
connectivityService, connectivityService,
powerManagementService); powerManagementService);
FilesSyncHelper.insertAllDBEntries(preferences, skipCustom); FilesSyncHelper.insertAllDBEntries(preferences, clock, skipCustom);
// Create all the providers we'll need // Create all the providers we'll need
final ContentResolver contentResolver = context.getContentResolver(); final ContentResolver contentResolver = context.getContentResolver();
final FilesystemDataProvider filesystemDataProvider = new FilesystemDataProvider(contentResolver); final FilesystemDataProvider filesystemDataProvider = new FilesystemDataProvider(contentResolver);
SyncedFolderProvider syncedFolderProvider = new SyncedFolderProvider(contentResolver, SyncedFolderProvider syncedFolderProvider = new SyncedFolderProvider(contentResolver, preferences, clock);
preferences);
Locale currentLocale = context.getResources().getConfiguration().locale; Locale currentLocale = context.getResources().getConfiguration().locale;
SimpleDateFormat sFormatter = new SimpleDateFormat("yyyy:MM:dd HH:mm:ss", currentLocale); SimpleDateFormat sFormatter = new SimpleDateFormat("yyyy:MM:dd HH:mm:ss", currentLocale);

View File

@ -39,6 +39,7 @@ import android.text.TextUtils;
import com.evernote.android.job.Job; import com.evernote.android.job.Job;
import com.google.gson.Gson; import com.google.gson.Gson;
import com.nextcloud.client.account.UserAccountManager; import com.nextcloud.client.account.UserAccountManager;
import com.nextcloud.client.core.Clock;
import com.nextcloud.client.preferences.AppPreferences; import com.nextcloud.client.preferences.AppPreferences;
import com.nextcloud.client.preferences.AppPreferencesImpl; import com.nextcloud.client.preferences.AppPreferencesImpl;
import com.owncloud.android.R; import com.owncloud.android.R;
@ -74,11 +75,13 @@ public class MediaFoldersDetectionJob extends Job {
private static final String DISABLE_DETECTION_CLICK = "DISABLE_DETECTION_CLICK"; private static final String DISABLE_DETECTION_CLICK = "DISABLE_DETECTION_CLICK";
private UserAccountManager userAccountManager; private final UserAccountManager userAccountManager;
private Random randomId = new Random(); private final Clock clock;
private final Random randomId = new Random();
MediaFoldersDetectionJob(UserAccountManager accountManager) { MediaFoldersDetectionJob(UserAccountManager accountManager, Clock clock) {
this.userAccountManager = accountManager; this.userAccountManager = accountManager;
this.clock = clock;
} }
@NonNull @NonNull
@ -88,7 +91,8 @@ public class MediaFoldersDetectionJob extends Job {
ContentResolver contentResolver = context.getContentResolver(); ContentResolver contentResolver = context.getContentResolver();
ArbitraryDataProvider arbitraryDataProvider = new ArbitraryDataProvider(contentResolver); ArbitraryDataProvider arbitraryDataProvider = new ArbitraryDataProvider(contentResolver);
SyncedFolderProvider syncedFolderProvider = new SyncedFolderProvider(contentResolver, SyncedFolderProvider syncedFolderProvider = new SyncedFolderProvider(contentResolver,
AppPreferencesImpl.fromContext(context)); AppPreferencesImpl.fromContext(context),
clock);
Gson gson = new Gson(); Gson gson = new Gson();
String arbitraryDataString; String arbitraryDataString;
MediaFoldersModel mediaFoldersModel; MediaFoldersModel mediaFoldersModel;

View File

@ -29,6 +29,7 @@ import android.content.Context;
import com.evernote.android.job.Job; import com.evernote.android.job.Job;
import com.evernote.android.job.JobCreator; import com.evernote.android.job.JobCreator;
import com.nextcloud.client.account.UserAccountManager; import com.nextcloud.client.account.UserAccountManager;
import com.nextcloud.client.core.Clock;
import com.nextcloud.client.device.PowerManagementService; import com.nextcloud.client.device.PowerManagementService;
import com.nextcloud.client.network.ConnectivityService; import com.nextcloud.client.network.ConnectivityService;
import com.nextcloud.client.preferences.AppPreferences; import com.nextcloud.client.preferences.AppPreferences;
@ -48,6 +49,7 @@ public class NCJobCreator implements JobCreator {
private final UploadsStorageManager uploadsStorageManager; private final UploadsStorageManager uploadsStorageManager;
private final ConnectivityService connectivityService; private final ConnectivityService connectivityService;
private final PowerManagementService powerManagementService; private final PowerManagementService powerManagementService;
private final Clock clock;
public NCJobCreator( public NCJobCreator(
Context context, Context context,
@ -55,7 +57,8 @@ public class NCJobCreator implements JobCreator {
AppPreferences preferences, AppPreferences preferences,
UploadsStorageManager uploadsStorageManager, UploadsStorageManager uploadsStorageManager,
ConnectivityService connectivityServices, ConnectivityService connectivityServices,
PowerManagementService powerManagementService PowerManagementService powerManagementService,
Clock clock
) { ) {
this.context = context; this.context = context;
this.accountManager = accountManager; this.accountManager = accountManager;
@ -63,6 +66,7 @@ public class NCJobCreator implements JobCreator {
this.uploadsStorageManager = uploadsStorageManager; this.uploadsStorageManager = uploadsStorageManager;
this.connectivityService = connectivityServices; this.connectivityService = connectivityServices;
this.powerManagementService = powerManagementService; this.powerManagementService = powerManagementService;
this.clock = clock;
} }
@Override @Override
@ -73,19 +77,20 @@ public class NCJobCreator implements JobCreator {
case ContactsImportJob.TAG: case ContactsImportJob.TAG:
return new ContactsImportJob(); return new ContactsImportJob();
case AccountRemovalJob.TAG: case AccountRemovalJob.TAG:
return new AccountRemovalJob(uploadsStorageManager, accountManager); return new AccountRemovalJob(uploadsStorageManager, accountManager, clock);
case FilesSyncJob.TAG: case FilesSyncJob.TAG:
return new FilesSyncJob(accountManager, return new FilesSyncJob(accountManager,
preferences, preferences,
uploadsStorageManager, uploadsStorageManager,
connectivityService, connectivityService,
powerManagementService); powerManagementService,
clock);
case OfflineSyncJob.TAG: case OfflineSyncJob.TAG:
return new OfflineSyncJob(accountManager, connectivityService, powerManagementService); return new OfflineSyncJob(accountManager, connectivityService, powerManagementService);
case NotificationJob.TAG: case NotificationJob.TAG:
return new NotificationJob(context, accountManager); return new NotificationJob(context, accountManager);
case MediaFoldersDetectionJob.TAG: case MediaFoldersDetectionJob.TAG:
return new MediaFoldersDetectionJob(accountManager); return new MediaFoldersDetectionJob(accountManager, clock);
default: default:
return null; return null;
} }

View File

@ -42,9 +42,11 @@ import android.net.Uri;
import android.os.Binder; import android.os.Binder;
import android.text.TextUtils; import android.text.TextUtils;
import com.nextcloud.client.core.Clock;
import com.owncloud.android.MainApp; import com.owncloud.android.MainApp;
import com.owncloud.android.R; import com.owncloud.android.R;
import com.owncloud.android.datamodel.OCFile; import com.owncloud.android.datamodel.OCFile;
import com.owncloud.android.datamodel.SyncedFolder;
import com.owncloud.android.db.ProviderMeta; import com.owncloud.android.db.ProviderMeta;
import com.owncloud.android.db.ProviderMeta.ProviderTableMeta; import com.owncloud.android.db.ProviderMeta.ProviderTableMeta;
import com.owncloud.android.lib.common.accounts.AccountUtils; import com.owncloud.android.lib.common.accounts.AccountUtils;
@ -58,7 +60,10 @@ import java.util.ArrayList;
import java.util.HashMap; import java.util.HashMap;
import java.util.Locale; import java.util.Locale;
import javax.inject.Inject;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import dagger.android.AndroidInjection;
/** /**
* The ContentProvider for the ownCloud App. * The ContentProvider for the ownCloud App.
@ -91,6 +96,7 @@ public class FileContentProvider extends ContentProvider {
public static final int ARBITRARY_DATA_TABLE_INTRODUCTION_VERSION = 20; public static final int ARBITRARY_DATA_TABLE_INTRODUCTION_VERSION = 20;
public static final int MINIMUM_PATH_SEGMENTS_SIZE = 1; public static final int MINIMUM_PATH_SEGMENTS_SIZE = 1;
@Inject protected Clock clock;
private DataBaseHelper mDbHelper; private DataBaseHelper mDbHelper;
private Context mContext; private Context mContext;
private UriMatcher mUriMatcher; private UriMatcher mUriMatcher;
@ -414,6 +420,7 @@ public class FileContentProvider extends ContentProvider {
@Override @Override
public boolean onCreate() { public boolean onCreate() {
AndroidInjection.inject(this);
mDbHelper = new DataBaseHelper(getContext()); mDbHelper = new DataBaseHelper(getContext());
mContext = getContext(); mContext = getContext();
@ -822,6 +829,7 @@ public class FileContentProvider extends ContentProvider {
+ ProviderTableMeta.SYNCED_FOLDER_WIFI_ONLY + " INTEGER, " // wifi_only + ProviderTableMeta.SYNCED_FOLDER_WIFI_ONLY + " INTEGER, " // wifi_only
+ ProviderTableMeta.SYNCED_FOLDER_CHARGING_ONLY + " INTEGER, " // charging only + ProviderTableMeta.SYNCED_FOLDER_CHARGING_ONLY + " INTEGER, " // charging only
+ ProviderTableMeta.SYNCED_FOLDER_ENABLED + " INTEGER, " // enabled + ProviderTableMeta.SYNCED_FOLDER_ENABLED + " INTEGER, " // enabled
+ ProviderTableMeta.SYNCED_FOLDER_ENABLED_TIMESTAMP_MS + " INTEGER, " // enable date
+ ProviderTableMeta.SYNCED_FOLDER_SUBFOLDER_BY_DATE + " INTEGER, " // subfolder by date + ProviderTableMeta.SYNCED_FOLDER_SUBFOLDER_BY_DATE + " INTEGER, " // subfolder by date
+ ProviderTableMeta.SYNCED_FOLDER_ACCOUNT + " TEXT, " // account + ProviderTableMeta.SYNCED_FOLDER_ACCOUNT + " TEXT, " // account
+ ProviderTableMeta.SYNCED_FOLDER_UPLOAD_ACTION + " INTEGER, " // upload action + ProviderTableMeta.SYNCED_FOLDER_UPLOAD_ACTION + " INTEGER, " // upload action
@ -2013,6 +2021,30 @@ public class FileContentProvider extends ContentProvider {
if (!upgraded) { if (!upgraded) {
Log_OC.i(SQL, String.format(Locale.ENGLISH, UPGRADE_VERSION_MSG, oldVersion, newVersion)); Log_OC.i(SQL, String.format(Locale.ENGLISH, UPGRADE_VERSION_MSG, oldVersion, newVersion));
} }
if (oldVersion < 50 && newVersion >= 50) {
Log_OC.i(SQL, "Entering in the #50 add persistent enable date to synced_folders table");
db.beginTransaction();
try {
db.execSQL(ALTER_TABLE + ProviderTableMeta.SYNCED_FOLDERS_TABLE_NAME +
ADD_COLUMN + ProviderTableMeta.SYNCED_FOLDER_ENABLED_TIMESTAMP_MS + " INTEGER ");
db.execSQL("UPDATE " + ProviderTableMeta.SYNCED_FOLDERS_TABLE_NAME + " SET " +
ProviderTableMeta.SYNCED_FOLDER_ENABLED_TIMESTAMP_MS + " = CASE " +
" WHEN enabled = 0 THEN " + SyncedFolder.EMPTY_ENABLED_TIMESTAMP_MS + " " +
" ELSE " + clock.getCurrentTime() +
" END ");
upgraded = true;
db.setTransactionSuccessful();
} finally {
db.endTransaction();
}
}
if (!upgraded) {
Log_OC.i(SQL, String.format(Locale.ENGLISH, UPGRADE_VERSION_MSG, oldVersion, newVersion));
}
} }
@Override @Override

View File

@ -41,13 +41,13 @@ import android.view.View;
import android.widget.LinearLayout; import android.widget.LinearLayout;
import android.widget.TextView; import android.widget.TextView;
import com.nextcloud.client.core.Clock;
import com.nextcloud.client.device.PowerManagementService; import com.nextcloud.client.device.PowerManagementService;
import com.nextcloud.client.di.Injectable; import com.nextcloud.client.di.Injectable;
import com.nextcloud.client.preferences.AppPreferences; import com.nextcloud.client.preferences.AppPreferences;
import com.owncloud.android.BuildConfig; import com.owncloud.android.BuildConfig;
import com.owncloud.android.MainApp; import com.owncloud.android.MainApp;
import com.owncloud.android.R; import com.owncloud.android.R;
import com.owncloud.android.datamodel.ArbitraryDataProvider;
import com.owncloud.android.datamodel.MediaFolder; import com.owncloud.android.datamodel.MediaFolder;
import com.owncloud.android.datamodel.MediaFolderType; import com.owncloud.android.datamodel.MediaFolderType;
import com.owncloud.android.datamodel.MediaProvider; import com.owncloud.android.datamodel.MediaProvider;
@ -113,6 +113,7 @@ public class SyncedFoldersActivity extends FileActivity implements SyncedFolderA
private int type; private int type;
@Inject AppPreferences preferences; @Inject AppPreferences preferences;
@Inject PowerManagementService powerManagementService; @Inject PowerManagementService powerManagementService;
@Inject Clock clock;
@Override @Override
protected void onCreate(Bundle savedInstanceState) { protected void onCreate(Bundle savedInstanceState) {
@ -223,8 +224,8 @@ public class SyncedFoldersActivity extends FileActivity implements SyncedFolderA
final int gridWidth = getResources().getInteger(R.integer.media_grid_width); final int gridWidth = getResources().getInteger(R.integer.media_grid_width);
boolean lightVersion = getResources().getBoolean(R.bool.syncedFolder_light); boolean lightVersion = getResources().getBoolean(R.bool.syncedFolder_light);
mAdapter = new SyncedFolderAdapter(this, gridWidth, this, lightVersion); mAdapter = new SyncedFolderAdapter(this, clock, gridWidth, this, lightVersion);
mSyncedFolderProvider = new SyncedFolderProvider(getContentResolver(), preferences); mSyncedFolderProvider = new SyncedFolderProvider(getContentResolver(), preferences, clock);
final GridLayoutManager lm = new GridLayoutManager(this, gridWidth); final GridLayoutManager lm = new GridLayoutManager(this, gridWidth);
mAdapter.setLayoutManager(lm); mAdapter.setLayoutManager(lm);
@ -386,6 +387,7 @@ public class SyncedFoldersActivity extends FileActivity implements SyncedFolderA
syncedFolder.getAccount(), syncedFolder.getAccount(),
syncedFolder.getUploadAction(), syncedFolder.getUploadAction(),
syncedFolder.isEnabled(), syncedFolder.isEnabled(),
clock.getCurrentTime(),
filePaths, filePaths,
localFolder.getName(), localFolder.getName(),
files.length, files.length,
@ -411,6 +413,7 @@ public class SyncedFoldersActivity extends FileActivity implements SyncedFolderA
syncedFolder.getAccount(), syncedFolder.getAccount(),
syncedFolder.getUploadAction(), syncedFolder.getUploadAction(),
syncedFolder.isEnabled(), syncedFolder.isEnabled(),
clock.getCurrentTime(),
mediaFolder.filePaths, mediaFolder.filePaths,
mediaFolder.folderName, mediaFolder.folderName,
mediaFolder.numberOfFiles, mediaFolder.numberOfFiles,
@ -432,9 +435,10 @@ public class SyncedFoldersActivity extends FileActivity implements SyncedFolderA
true, true,
false, false,
false, false,
getAccount().name, getAccount().name,
FileUploader.LOCAL_BEHAVIOUR_FORGET, FileUploader.LOCAL_BEHAVIOUR_FORGET,
false, false,
clock.getCurrentTime(),
mediaFolder.filePaths, mediaFolder.filePaths,
mediaFolder.folderName, mediaFolder.folderName,
mediaFolder.numberOfFiles, mediaFolder.numberOfFiles,
@ -519,7 +523,7 @@ public class SyncedFoldersActivity extends FileActivity implements SyncedFolderA
SyncedFolderDisplayItem emptyCustomFolder = new SyncedFolderDisplayItem( SyncedFolderDisplayItem emptyCustomFolder = new SyncedFolderDisplayItem(
SyncedFolder.UNPERSISTED_ID, null, null, true, false, SyncedFolder.UNPERSISTED_ID, null, null, true, false,
false, getAccount().name, false, getAccount().name,
FileUploader.LOCAL_BEHAVIOUR_FORGET, false, null, MediaFolderType.CUSTOM); FileUploader.LOCAL_BEHAVIOUR_FORGET, false, clock.getCurrentTime(), null, MediaFolderType.CUSTOM);
onSyncFolderSettingsClick(0, emptyCustomFolder); onSyncFolderSettingsClick(0, emptyCustomFolder);
} }
@ -548,9 +552,6 @@ public class SyncedFoldersActivity extends FileActivity implements SyncedFolderA
@Override @Override
public void onSyncStatusToggleClick(int section, SyncedFolderDisplayItem syncedFolderDisplayItem) { public void onSyncStatusToggleClick(int section, SyncedFolderDisplayItem syncedFolderDisplayItem) {
ArbitraryDataProvider arbitraryDataProvider = new ArbitraryDataProvider(MainApp.getAppContext().
getContentResolver());
if (syncedFolderDisplayItem.getId() > UNPERSISTED_ID) { if (syncedFolderDisplayItem.getId() > UNPERSISTED_ID) {
mSyncedFolderProvider.updateSyncedFolderEnabled(syncedFolderDisplayItem.getId(), mSyncedFolderProvider.updateSyncedFolderEnabled(syncedFolderDisplayItem.getId(),
syncedFolderDisplayItem.isEnabled()); syncedFolderDisplayItem.isEnabled());
@ -565,9 +566,6 @@ public class SyncedFoldersActivity extends FileActivity implements SyncedFolderA
FilesSyncHelper.insertAllDBEntriesForSyncedFolder(syncedFolderDisplayItem); FilesSyncHelper.insertAllDBEntriesForSyncedFolder(syncedFolderDisplayItem);
showBatteryOptimizationInfo(); showBatteryOptimizationInfo();
} else {
String syncedFolderInitiatedKey = "syncedFolderIntitiated_" + syncedFolderDisplayItem.getId();
arbitraryDataProvider.deleteKeyForAccount("global", syncedFolderInitiatedKey);
} }
} }
@ -600,9 +598,6 @@ public class SyncedFoldersActivity extends FileActivity implements SyncedFolderA
@Override @Override
public void onSaveSyncedFolderPreference(SyncedFolderParcelable syncedFolder) { public void onSaveSyncedFolderPreference(SyncedFolderParcelable syncedFolder) {
ArbitraryDataProvider arbitraryDataProvider = new ArbitraryDataProvider(MainApp.getAppContext().
getContentResolver());
// custom folders newly created aren't in the list already, // custom folders newly created aren't in the list already,
// so triggering a refresh // so triggering a refresh
if (MediaFolderType.CUSTOM == syncedFolder.getType() && syncedFolder.getId() == UNPERSISTED_ID) { if (MediaFolderType.CUSTOM == syncedFolder.getType() && syncedFolder.getId() == UNPERSISTED_ID) {
@ -610,15 +605,12 @@ public class SyncedFoldersActivity extends FileActivity implements SyncedFolderA
SyncedFolder.UNPERSISTED_ID, syncedFolder.getLocalPath(), syncedFolder.getRemotePath(), SyncedFolder.UNPERSISTED_ID, syncedFolder.getLocalPath(), syncedFolder.getRemotePath(),
syncedFolder.getWifiOnly(), syncedFolder.getChargingOnly(), syncedFolder.getSubfolderByDate(), syncedFolder.getWifiOnly(), syncedFolder.getChargingOnly(), syncedFolder.getSubfolderByDate(),
syncedFolder.getAccount(), syncedFolder.getUploadAction(), syncedFolder.getEnabled(), syncedFolder.getAccount(), syncedFolder.getUploadAction(), syncedFolder.getEnabled(),
new File(syncedFolder.getLocalPath()).getName(), syncedFolder.getType()); clock.getCurrentTime(), new File(syncedFolder.getLocalPath()).getName(), syncedFolder.getType());
long storedId = mSyncedFolderProvider.storeSyncedFolder(newCustomFolder); long storedId = mSyncedFolderProvider.storeSyncedFolder(newCustomFolder);
if (storedId != -1) { if (storedId != -1) {
newCustomFolder.setId(storedId); newCustomFolder.setId(storedId);
if (newCustomFolder.isEnabled()) { if (newCustomFolder.isEnabled()) {
FilesSyncHelper.insertAllDBEntriesForSyncedFolder(newCustomFolder); FilesSyncHelper.insertAllDBEntriesForSyncedFolder(newCustomFolder);
} else {
String syncedFolderInitiatedKey = "syncedFolderIntitiated_" + newCustomFolder.getId();
arbitraryDataProvider.deleteKeyForAccount("global", syncedFolderInitiatedKey);
} }
} }
mAdapter.addSyncFolderItem(newCustomFolder); mAdapter.addSyncFolderItem(newCustomFolder);
@ -635,9 +627,6 @@ public class SyncedFoldersActivity extends FileActivity implements SyncedFolderA
item.setId(storedId); item.setId(storedId);
if (item.isEnabled()) { if (item.isEnabled()) {
FilesSyncHelper.insertAllDBEntriesForSyncedFolder(item); FilesSyncHelper.insertAllDBEntriesForSyncedFolder(item);
} else {
String syncedFolderInitiatedKey = "syncedFolderIntitiated_" + item.getId();
arbitraryDataProvider.deleteKeyForAccount("global", syncedFolderInitiatedKey);
} }
} }
} else { } else {
@ -645,9 +634,6 @@ public class SyncedFoldersActivity extends FileActivity implements SyncedFolderA
mSyncedFolderProvider.updateSyncFolder(item); mSyncedFolderProvider.updateSyncFolder(item);
if (item.isEnabled()) { if (item.isEnabled()) {
FilesSyncHelper.insertAllDBEntriesForSyncedFolder(item); FilesSyncHelper.insertAllDBEntriesForSyncedFolder(item);
} else {
String syncedFolderInitiatedKey = "syncedFolderIntitiated_" + item.getId();
arbitraryDataProvider.deleteKeyForAccount("global", syncedFolderInitiatedKey);
} }
} }
@ -699,7 +685,7 @@ public class SyncedFoldersActivity extends FileActivity implements SyncedFolderA
item.setChargingOnly(chargingOnly); item.setChargingOnly(chargingOnly);
item.setSubfolderByDate(subfolderByDate); item.setSubfolderByDate(subfolderByDate);
item.setUploadAction(uploadAction); item.setUploadAction(uploadAction);
item.setEnabled(enabled); item.setEnabled(enabled, clock.getCurrentTime());
return item; return item;
} }

View File

@ -33,6 +33,7 @@ import android.widget.TextView;
import com.afollestad.sectionedrecyclerview.SectionedRecyclerViewAdapter; import com.afollestad.sectionedrecyclerview.SectionedRecyclerViewAdapter;
import com.afollestad.sectionedrecyclerview.SectionedViewHolder; import com.afollestad.sectionedrecyclerview.SectionedViewHolder;
import com.nextcloud.client.core.Clock;
import com.owncloud.android.R; import com.owncloud.android.R;
import com.owncloud.android.datamodel.MediaFolderType; import com.owncloud.android.datamodel.MediaFolderType;
import com.owncloud.android.datamodel.SyncedFolderDisplayItem; import com.owncloud.android.datamodel.SyncedFolderDisplayItem;
@ -54,14 +55,16 @@ import butterknife.ButterKnife;
public class SyncedFolderAdapter extends SectionedRecyclerViewAdapter<SectionedViewHolder> { public class SyncedFolderAdapter extends SectionedRecyclerViewAdapter<SectionedViewHolder> {
private final Context mContext; private final Context mContext;
private final Clock clock;
private final int mGridWidth; private final int mGridWidth;
private final int mGridTotal; private final int mGridTotal;
private final ClickListener mListener; private final ClickListener mListener;
private final List<SyncedFolderDisplayItem> mSyncFolderItems; private final List<SyncedFolderDisplayItem> mSyncFolderItems;
private final boolean mLight; private final boolean mLight;
public SyncedFolderAdapter(Context context, int gridWidth, ClickListener listener, boolean light) { public SyncedFolderAdapter(Context context, Clock clock, int gridWidth, ClickListener listener, boolean light) {
mContext = context; mContext = context;
this.clock = clock;
mGridWidth = gridWidth; mGridWidth = gridWidth;
mGridTotal = gridWidth * 2; mGridTotal = gridWidth * 2;
mListener = listener; mListener = listener;
@ -148,7 +151,7 @@ public class SyncedFolderAdapter extends SectionedRecyclerViewAdapter<SectionedV
holder.syncStatusButton.setVisibility(View.VISIBLE); holder.syncStatusButton.setVisibility(View.VISIBLE);
holder.syncStatusButton.setTag(section); holder.syncStatusButton.setTag(section);
holder.syncStatusButton.setOnClickListener(v -> { holder.syncStatusButton.setOnClickListener(v -> {
mSyncFolderItems.get(section).setEnabled(!mSyncFolderItems.get(section).isEnabled()); mSyncFolderItems.get(section).setEnabled(!mSyncFolderItems.get(section).isEnabled(), clock.getCurrentTime());
setSyncButtonActiveIcon(holder.syncStatusButton, mSyncFolderItems.get(section).isEnabled()); setSyncButtonActiveIcon(holder.syncStatusButton, mSyncFolderItems.get(section).isEnabled());
mListener.onSyncStatusToggleClick(section, mSyncFolderItems.get(section)); mListener.onSyncStatusToggleClick(section, mSyncFolderItems.get(section));
}); });
@ -157,7 +160,7 @@ public class SyncedFolderAdapter extends SectionedRecyclerViewAdapter<SectionedV
holder.syncStatusButton.setVisibility(View.VISIBLE); holder.syncStatusButton.setVisibility(View.VISIBLE);
holder.syncStatusButton.setTag(section); holder.syncStatusButton.setTag(section);
holder.syncStatusButton.setOnClickListener(v -> { holder.syncStatusButton.setOnClickListener(v -> {
mSyncFolderItems.get(section).setEnabled(!mSyncFolderItems.get(section).isEnabled()); mSyncFolderItems.get(section).setEnabled(!mSyncFolderItems.get(section).isEnabled(), clock.getCurrentTime());
setSyncButtonActiveIcon(holder.syncStatusButton, mSyncFolderItems.get(section).isEnabled()); setSyncButtonActiveIcon(holder.syncStatusButton, mSyncFolderItems.get(section).isEnabled());
mListener.onSyncStatusToggleClick(section, mSyncFolderItems.get(section)); mListener.onSyncStatusToggleClick(section, mSyncFolderItems.get(section));
}); });

View File

@ -30,18 +30,16 @@ import android.database.Cursor;
import android.net.Uri; import android.net.Uri;
import android.os.Build; import android.os.Build;
import android.provider.MediaStore; import android.provider.MediaStore;
import android.text.TextUtils;
import android.util.Log;
import com.evernote.android.job.JobManager; import com.evernote.android.job.JobManager;
import com.evernote.android.job.JobRequest; import com.evernote.android.job.JobRequest;
import com.nextcloud.client.account.UserAccountManager; import com.nextcloud.client.account.UserAccountManager;
import com.nextcloud.client.core.Clock;
import com.nextcloud.client.device.PowerManagementService; import com.nextcloud.client.device.PowerManagementService;
import com.nextcloud.client.jobs.BackgroundJobManager; import com.nextcloud.client.jobs.BackgroundJobManager;
import com.nextcloud.client.network.ConnectivityService; import com.nextcloud.client.network.ConnectivityService;
import com.nextcloud.client.preferences.AppPreferences; import com.nextcloud.client.preferences.AppPreferences;
import com.owncloud.android.MainApp; import com.owncloud.android.MainApp;
import com.owncloud.android.datamodel.ArbitraryDataProvider;
import com.owncloud.android.datamodel.FilesystemDataProvider; import com.owncloud.android.datamodel.FilesystemDataProvider;
import com.owncloud.android.datamodel.MediaFolderType; import com.owncloud.android.datamodel.MediaFolderType;
import com.owncloud.android.datamodel.SyncedFolder; import com.owncloud.android.datamodel.SyncedFolder;
@ -51,6 +49,7 @@ import com.owncloud.android.db.OCUpload;
import com.owncloud.android.files.services.FileUploader; import com.owncloud.android.files.services.FileUploader;
import com.owncloud.android.jobs.FilesSyncJob; import com.owncloud.android.jobs.FilesSyncJob;
import com.owncloud.android.jobs.OfflineSyncJob; import com.owncloud.android.jobs.OfflineSyncJob;
import com.owncloud.android.lib.common.utils.Log_OC;
import org.lukhnos.nnio.file.FileVisitResult; import org.lukhnos.nnio.file.FileVisitResult;
import org.lukhnos.nnio.file.Files; import org.lukhnos.nnio.file.Files;
@ -73,7 +72,6 @@ public final class FilesSyncHelper {
public static final String TAG = "FileSyncHelper"; public static final String TAG = "FileSyncHelper";
public static final String GLOBAL = "global"; public static final String GLOBAL = "global";
public static final String SYNCEDFOLDERINITIATED = "syncedFolderIntitiated_";
public static final int ContentSyncJobId = 315; public static final int ContentSyncJobId = 315;
@ -84,59 +82,34 @@ public final class FilesSyncHelper {
public static void insertAllDBEntriesForSyncedFolder(SyncedFolder syncedFolder) { public static void insertAllDBEntriesForSyncedFolder(SyncedFolder syncedFolder) {
final Context context = MainApp.getAppContext(); final Context context = MainApp.getAppContext();
final ContentResolver contentResolver = context.getContentResolver(); final ContentResolver contentResolver = context.getContentResolver();
ArbitraryDataProvider arbitraryDataProvider = new ArbitraryDataProvider(contentResolver);
Long currentTime = System.currentTimeMillis(); final long enabledTimestampMs = syncedFolder.getEnabledTimestampMs();
double currentTimeInSeconds = currentTime / 1000.0;
String currentTimeString = Long.toString((long) currentTimeInSeconds);
String syncedFolderInitiatedKey = SYNCEDFOLDERINITIATED + syncedFolder.getId(); if (syncedFolder.isEnabled() && enabledTimestampMs >= 0) {
boolean dryRun = TextUtils.isEmpty(arbitraryDataProvider.getValue MediaFolderType mediaType = syncedFolder.getType();
(GLOBAL, syncedFolderInitiatedKey)); if (mediaType == MediaFolderType.IMAGE) {
FilesSyncHelper.insertContentIntoDB(MediaStore.Images.Media.INTERNAL_CONTENT_URI
if (MediaFolderType.IMAGE == syncedFolder.getType()) { , syncedFolder);
if (dryRun) {
arbitraryDataProvider.storeOrUpdateKeyValue(GLOBAL, syncedFolderInitiatedKey,
currentTimeString);
} else {
FilesSyncHelper.insertContentIntoDB(android.provider.MediaStore.Images.Media.INTERNAL_CONTENT_URI
, syncedFolder);
FilesSyncHelper.insertContentIntoDB(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, FilesSyncHelper.insertContentIntoDB(MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
syncedFolder); syncedFolder);
} } else if (mediaType == MediaFolderType.VIDEO) {
FilesSyncHelper.insertContentIntoDB(MediaStore.Video.Media.INTERNAL_CONTENT_URI,
} else if (MediaFolderType.VIDEO == syncedFolder.getType()) { syncedFolder);
if (dryRun) {
arbitraryDataProvider.storeOrUpdateKeyValue(GLOBAL, syncedFolderInitiatedKey,
currentTimeString);
} else {
FilesSyncHelper.insertContentIntoDB(android.provider.MediaStore.Video.Media.INTERNAL_CONTENT_URI,
syncedFolder);
FilesSyncHelper.insertContentIntoDB(MediaStore.Video.Media.EXTERNAL_CONTENT_URI, FilesSyncHelper.insertContentIntoDB(MediaStore.Video.Media.EXTERNAL_CONTENT_URI,
syncedFolder); syncedFolder);
} } else {
try {
} else {
try {
if (dryRun) {
arbitraryDataProvider.storeOrUpdateKeyValue(GLOBAL, syncedFolderInitiatedKey,
currentTimeString);
} else {
FilesystemDataProvider filesystemDataProvider = new FilesystemDataProvider(contentResolver); FilesystemDataProvider filesystemDataProvider = new FilesystemDataProvider(contentResolver);
Path path = Paths.get(syncedFolder.getLocalPath()); Path path = Paths.get(syncedFolder.getLocalPath());
String dateInitiated = arbitraryDataProvider.getValue(GLOBAL,
syncedFolderInitiatedKey);
Files.walkFileTree(path, new SimpleFileVisitor<Path>() { Files.walkFileTree(path, new SimpleFileVisitor<Path>() {
@Override @Override
public FileVisitResult visitFile(Path path, BasicFileAttributes attrs) { public FileVisitResult visitFile(Path path, BasicFileAttributes attrs) {
File file = path.toFile(); File file = path.toFile();
if (attrs.lastModifiedTime().toMillis() >= Long.parseLong(dateInitiated) * 1000) { if (attrs.lastModifiedTime().toMillis() >= enabledTimestampMs) {
filesystemDataProvider.storeOrUpdateFileValue(path.toAbsolutePath().toString(), filesystemDataProvider.storeOrUpdateFileValue(path.toAbsolutePath().toString(),
attrs.lastModifiedTime().toMillis(), file.isDirectory(), syncedFolder); attrs.lastModifiedTime().toMillis(),
file.isDirectory(), syncedFolder);
} }
return FileVisitResult.CONTINUE; return FileVisitResult.CONTINUE;
@ -147,20 +120,17 @@ public final class FilesSyncHelper {
return FileVisitResult.CONTINUE; return FileVisitResult.CONTINUE;
} }
}); });
} catch (IOException e) {
Log_OC.e(TAG, "Something went wrong while indexing files for auto upload", e);
} }
} catch (IOException e) {
Log.e(TAG, "Something went wrong while indexing files for auto upload " + e.getLocalizedMessage());
} }
} }
} }
public static void insertAllDBEntries(AppPreferences preferences, boolean skipCustom) { public static void insertAllDBEntries(AppPreferences preferences, Clock clock, boolean skipCustom) {
final Context context = MainApp.getAppContext(); final Context context = MainApp.getAppContext();
final ContentResolver contentResolver = context.getContentResolver(); final ContentResolver contentResolver = context.getContentResolver();
SyncedFolderProvider syncedFolderProvider = new SyncedFolderProvider(contentResolver, SyncedFolderProvider syncedFolderProvider = new SyncedFolderProvider(contentResolver, preferences, clock);
preferences);
for (SyncedFolder syncedFolder : syncedFolderProvider.getSyncedFolders()) { for (SyncedFolder syncedFolder : syncedFolderProvider.getSyncedFolders()) {
if (syncedFolder.isEnabled() && (MediaFolderType.CUSTOM != syncedFolder.getType() || !skipCustom)) { if (syncedFolder.isEnabled() && (MediaFolderType.CUSTOM != syncedFolder.getType() || !skipCustom)) {
@ -172,7 +142,6 @@ public final class FilesSyncHelper {
private static void insertContentIntoDB(Uri uri, SyncedFolder syncedFolder) { private static void insertContentIntoDB(Uri uri, SyncedFolder syncedFolder) {
final Context context = MainApp.getAppContext(); final Context context = MainApp.getAppContext();
final ContentResolver contentResolver = context.getContentResolver(); final ContentResolver contentResolver = context.getContentResolver();
ArbitraryDataProvider arbitraryDataProvider = new ArbitraryDataProvider(contentResolver);
Cursor cursor; Cursor cursor;
int column_index_data; int column_index_data;
@ -191,11 +160,10 @@ public final class FilesSyncHelper {
} }
path = path + "%"; path = path + "%";
String syncedFolderInitiatedKey = SYNCEDFOLDERINITIATED + syncedFolder.getId(); long enabledTimestampMs = syncedFolder.getEnabledTimestampMs();
String dateInitiated = arbitraryDataProvider.getValue(GLOBAL, syncedFolderInitiatedKey);
cursor = context.getContentResolver().query(uri, projection, MediaStore.MediaColumns.DATA + " LIKE ?", cursor = context.getContentResolver().query(uri, projection, MediaStore.MediaColumns.DATA + " LIKE ?",
new String[]{path}, null); new String[]{path}, null);
if (cursor != null) { if (cursor != null) {
column_index_data = cursor.getColumnIndexOrThrow(MediaStore.MediaColumns.DATA); column_index_data = cursor.getColumnIndexOrThrow(MediaStore.MediaColumns.DATA);
@ -203,9 +171,10 @@ public final class FilesSyncHelper {
while (cursor.moveToNext()) { while (cursor.moveToNext()) {
contentPath = cursor.getString(column_index_data); contentPath = cursor.getString(column_index_data);
isFolder = new File(contentPath).isDirectory(); isFolder = new File(contentPath).isDirectory();
if (cursor.getLong(column_index_date_modified) >= Long.parseLong(dateInitiated)) { if (cursor.getLong(column_index_date_modified) >= enabledTimestampMs / 1000.0) {
filesystemDataProvider.storeOrUpdateFileValue(contentPath, filesystemDataProvider.storeOrUpdateFileValue(contentPath,
cursor.getLong(column_index_date_modified), isFolder, syncedFolder); cursor.getLong(column_index_date_modified), isFolder,
syncedFolder);
} }
} }
cursor.close(); cursor.close();

View File

@ -24,6 +24,7 @@ import android.content.ContentResolver
import android.content.Context import android.content.Context
import android.os.Build import android.os.Build
import androidx.work.WorkerParameters import androidx.work.WorkerParameters
import com.nextcloud.client.core.Clock
import com.nextcloud.client.device.DeviceInfo import com.nextcloud.client.device.DeviceInfo
import com.nextcloud.client.device.PowerManagementService import com.nextcloud.client.device.PowerManagementService
import com.nextcloud.client.preferences.AppPreferences import com.nextcloud.client.preferences.AppPreferences
@ -59,6 +60,9 @@ class BackgroundJobFactoryTest {
@Mock @Mock
private lateinit var deviceInfo: DeviceInfo private lateinit var deviceInfo: DeviceInfo
@Mock
private lateinit var clock: Clock
private lateinit var factory: BackgroundJobFactory private lateinit var factory: BackgroundJobFactory
@Before @Before
@ -67,6 +71,7 @@ class BackgroundJobFactoryTest {
factory = BackgroundJobFactory( factory = BackgroundJobFactory(
preferences, preferences,
contentResolver, contentResolver,
clock,
powerManagementService, powerManagementService,
Provider { backgroundJobManager }, Provider { backgroundJobManager },
deviceInfo deviceInfo

View File

@ -177,6 +177,7 @@ public class SyncedFoldersActivityTest {
"test@nextcloud.com", "test@nextcloud.com",
1, 1,
enabled, enabled,
System.currentTimeMillis(),
new ArrayList<String>(), new ArrayList<String>(),
folderName, folderName,
2, 2,