nextcloud-android/src/main/java/com/owncloud/android/jobs/FilesSyncJob.java

264 lines
12 KiB
Java

/*
* Nextcloud Android client application
*
* @author Mario Danic
* @author Chris Narkiewicz
* Copyright (C) 2017 Mario Danic
* Copyright (C) 2017 Nextcloud
* Copyright (C) 2919 Chris Narkiewicz
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE
* License as published by the Free Software Foundation; either
* version 3 of the License, or any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU AFFERO GENERAL PUBLIC LICENSE for more details.
*
* You should have received a copy of the GNU Affero General Public
* License along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.owncloud.android.jobs;
import android.accounts.Account;
import android.content.ContentResolver;
import android.content.Context;
import android.content.res.Resources;
import android.os.Build;
import android.os.PowerManager;
import android.text.TextUtils;
import com.evernote.android.job.Job;
import com.evernote.android.job.util.support.PersistableBundleCompat;
import com.nextcloud.client.account.UserAccountManager;
import com.nextcloud.client.core.Clock;
import com.nextcloud.client.device.PowerManagementService;
import com.nextcloud.client.network.ConnectivityService;
import com.nextcloud.client.preferences.AppPreferences;
import com.owncloud.android.MainApp;
import com.owncloud.android.R;
import com.owncloud.android.datamodel.ArbitraryDataProvider;
import com.owncloud.android.datamodel.FilesystemDataProvider;
import com.owncloud.android.datamodel.MediaFolderType;
import com.owncloud.android.datamodel.SyncedFolder;
import com.owncloud.android.datamodel.SyncedFolderProvider;
import com.owncloud.android.datamodel.UploadsStorageManager;
import com.owncloud.android.files.services.FileUploader;
import com.owncloud.android.lib.common.utils.Log_OC;
import com.owncloud.android.operations.UploadFileOperation;
import com.owncloud.android.ui.activity.SettingsActivity;
import com.owncloud.android.utils.FileStorageUtils;
import com.owncloud.android.utils.FilesSyncHelper;
import com.owncloud.android.utils.MimeType;
import com.owncloud.android.utils.MimeTypeUtil;
import java.io.File;
import java.text.ParsePosition;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Locale;
import java.util.TimeZone;
import androidx.annotation.NonNull;
import androidx.exifinterface.media.ExifInterface;
/*
Job that:
- restarts existing jobs if required
- finds new and modified files since we last run this
- creates upload tasks
*/
public class FilesSyncJob extends Job {
public static final String TAG = "FilesSyncJob";
public static final String SKIP_CUSTOM = "skipCustom";
public static final String OVERRIDE_POWER_SAVING = "overridePowerSaving";
private static final String WAKELOCK_TAG_SEPARATION = ":";
private final UserAccountManager userAccountManager;
private final AppPreferences preferences;
private final UploadsStorageManager uploadsStorageManager;
private final ConnectivityService connectivityService;
private final PowerManagementService powerManagementService;
private final Clock clock;
FilesSyncJob(final UserAccountManager userAccountManager,
final AppPreferences preferences,
final UploadsStorageManager uploadsStorageManager,
final ConnectivityService connectivityService,
final PowerManagementService powerManagementService,
final Clock clock) {
this.userAccountManager = userAccountManager;
this.preferences = preferences;
this.uploadsStorageManager = uploadsStorageManager;
this.connectivityService = connectivityService;
this.powerManagementService = powerManagementService;
this.clock = clock;
}
@NonNull
@Override
protected Result onRunJob(@NonNull Params params) {
final Context context = MainApp.getAppContext();
PowerManager.WakeLock wakeLock = null;
if (android.os.Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
PowerManager powerManager = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
wakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, MainApp.getAuthority() +
WAKELOCK_TAG_SEPARATION + TAG);
wakeLock.acquire(10 * 60 * 1000);
}
PersistableBundleCompat bundle = params.getExtras();
final boolean overridePowerSaving = bundle.getBoolean(OVERRIDE_POWER_SAVING, false);
// If we are in power save mode, better to postpone upload
if (powerManagementService.isPowerSavingEnabled() && !overridePowerSaving) {
if (wakeLock != null) {
wakeLock.release();
}
return Result.SUCCESS;
}
Resources resources = MainApp.getAppContext().getResources();
boolean lightVersion = resources.getBoolean(R.bool.syncedFolder_light);
final boolean skipCustom = bundle.getBoolean(SKIP_CUSTOM, false);
FilesSyncHelper.restartJobsIfNeeded(uploadsStorageManager,
userAccountManager,
connectivityService,
powerManagementService);
FilesSyncHelper.insertAllDBEntries(preferences, clock, skipCustom);
// Create all the providers we'll need
final ContentResolver contentResolver = context.getContentResolver();
final FilesystemDataProvider filesystemDataProvider = new FilesystemDataProvider(contentResolver);
SyncedFolderProvider syncedFolderProvider = new SyncedFolderProvider(contentResolver, preferences, clock);
Locale currentLocale = context.getResources().getConfiguration().locale;
SimpleDateFormat sFormatter = new SimpleDateFormat("yyyy:MM:dd HH:mm:ss", currentLocale);
sFormatter.setTimeZone(TimeZone.getTimeZone(TimeZone.getDefault().getID()));
FileUploader.UploadRequester requester = new FileUploader.UploadRequester();
for (SyncedFolder syncedFolder : syncedFolderProvider.getSyncedFolders()) {
if ((syncedFolder.isEnabled()) && (!skipCustom || MediaFolderType.CUSTOM != syncedFolder.getType())) {
syncFolder(context, resources, lightVersion, filesystemDataProvider, currentLocale, sFormatter,
requester, syncedFolder);
}
}
if (wakeLock != null) {
wakeLock.release();
}
return Result.SUCCESS;
}
private void syncFolder(Context context, Resources resources, boolean lightVersion,
FilesystemDataProvider filesystemDataProvider, Locale currentLocale,
SimpleDateFormat sFormatter, FileUploader.UploadRequester requester,
SyncedFolder syncedFolder) {
String remotePath;
boolean subfolderByDate;
Integer uploadAction;
boolean needsCharging;
boolean needsWifi;
File file;
ArbitraryDataProvider arbitraryDataProvider;
Account account = userAccountManager.getAccountByName(syncedFolder.getAccount());
if (lightVersion) {
arbitraryDataProvider = new ArbitraryDataProvider(context.getContentResolver());
} else {
arbitraryDataProvider = null;
}
for (String path : filesystemDataProvider.getFilesForUpload(syncedFolder.getLocalPath(),
Long.toString(syncedFolder.getId()))) {
file = new File(path);
Long lastModificationTime = calculateLastModificationTime(file, syncedFolder, sFormatter);
String mimeType = MimeTypeUtil.getBestMimeTypeByFilename(file.getAbsolutePath());
if (lightVersion) {
needsCharging = resources.getBoolean(R.bool.syncedFolder_light_on_charging);
needsWifi = account == null || arbitraryDataProvider.getBooleanValue(account.name,
SettingsActivity.SYNCED_FOLDER_LIGHT_UPLOAD_ON_WIFI);
String uploadActionString = resources.getString(R.string.syncedFolder_light_upload_behaviour);
uploadAction = getUploadAction(uploadActionString);
subfolderByDate = resources.getBoolean(R.bool.syncedFolder_light_use_subfolders);
remotePath = resources.getString(R.string.syncedFolder_remote_folder);
} else {
needsCharging = syncedFolder.getChargingOnly();
needsWifi = syncedFolder.getWifiOnly();
uploadAction = syncedFolder.getUploadAction();
subfolderByDate = syncedFolder.getSubfolderByDate();
remotePath = syncedFolder.getRemotePath();
}
requester.uploadFileWithOverwrite(
context,
account,
file.getAbsolutePath(),
FileStorageUtils.getInstantUploadFilePath(
file,
currentLocale,
remotePath,
syncedFolder.getLocalPath(),
lastModificationTime,
subfolderByDate),
uploadAction,
mimeType,
true, // create parent folder if not existent
UploadFileOperation.CREATED_AS_INSTANT_PICTURE,
needsWifi,
needsCharging,
true
);
filesystemDataProvider.updateFilesystemFileAsSentForUpload(path,
Long.toString(syncedFolder.getId()));
}
}
private Long calculateLastModificationTime(File file, SyncedFolder syncedFolder, SimpleDateFormat formatter) {
Long lastModificationTime = file.lastModified();
if (MediaFolderType.IMAGE == syncedFolder.getType()) {
String mimeTypeString = FileStorageUtils.getMimeTypeFromName(file.getAbsolutePath());
if (MimeType.JPEG.equalsIgnoreCase(mimeTypeString)
|| MimeType.TIFF.equalsIgnoreCase(mimeTypeString)) {
try {
ExifInterface exifInterface = new ExifInterface(file.getAbsolutePath());
String exifDate = exifInterface.getAttribute(ExifInterface.TAG_DATETIME);
if (!TextUtils.isEmpty(exifDate)) {
ParsePosition pos = new ParsePosition(0);
Date dateTime = formatter.parse(exifDate, pos);
lastModificationTime = dateTime.getTime();
}
} catch (Exception e) {
Log_OC.d(TAG, "Failed to get the proper time " + e.getLocalizedMessage());
}
}
}
return lastModificationTime;
}
private Integer getUploadAction(String action) {
switch (action) {
case "LOCAL_BEHAVIOUR_FORGET":
return FileUploader.LOCAL_BEHAVIOUR_FORGET;
case "LOCAL_BEHAVIOUR_MOVE":
return FileUploader.LOCAL_BEHAVIOUR_MOVE;
case "LOCAL_BEHAVIOUR_DELETE":
return FileUploader.LOCAL_BEHAVIOUR_DELETE;
default:
return FileUploader.LOCAL_BEHAVIOUR_FORGET;
}
}
}