nextcloud-android/src/main/java/com/owncloud/android/utils/FilesSyncHelper.java

251 lines
11 KiB
Java

/*
* Nextcloud Android client application
*
* @author Mario Danic
* @author Chris Narkiewicz
*
* Copyright (C) 2017 Mario Danic
* Copyright (C) 2017 Nextcloud
* Copyright (C) 2019 Chris Narkiewicz <hello@ezaquarii.com>
*
* 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.utils;
import android.accounts.Account;
import android.content.ContentResolver;
import android.content.Context;
import android.database.Cursor;
import android.net.Uri;
import android.os.Build;
import android.provider.MediaStore;
import com.evernote.android.job.JobManager;
import com.evernote.android.job.JobRequest;
import com.nextcloud.client.account.UserAccountManager;
import com.nextcloud.client.core.Clock;
import com.nextcloud.client.device.PowerManagementService;
import com.nextcloud.client.jobs.BackgroundJobManager;
import com.nextcloud.client.network.ConnectivityService;
import com.nextcloud.client.preferences.AppPreferences;
import com.owncloud.android.MainApp;
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.db.OCUpload;
import com.owncloud.android.files.services.FileUploader;
import com.owncloud.android.jobs.FilesSyncJob;
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.Files;
import org.lukhnos.nnio.file.Path;
import org.lukhnos.nnio.file.Paths;
import org.lukhnos.nnio.file.SimpleFileVisitor;
import org.lukhnos.nnio.file.attribute.BasicFileAttributes;
import java.io.File;
import java.io.IOException;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import static com.owncloud.android.datamodel.OCFile.PATH_SEPARATOR;
/**
* Various utilities that make auto upload tick
*/
public final class FilesSyncHelper {
public static final String TAG = "FileSyncHelper";
public static final String GLOBAL = "global";
public static final int ContentSyncJobId = 315;
private FilesSyncHelper() {
// utility class -> private constructor
}
public static void insertAllDBEntriesForSyncedFolder(SyncedFolder syncedFolder) {
final Context context = MainApp.getAppContext();
final ContentResolver contentResolver = context.getContentResolver();
final long enabledTimestampMs = syncedFolder.getEnabledTimestampMs();
if (syncedFolder.isEnabled() && enabledTimestampMs >= 0) {
MediaFolderType mediaType = syncedFolder.getType();
if (mediaType == MediaFolderType.IMAGE) {
FilesSyncHelper.insertContentIntoDB(MediaStore.Images.Media.INTERNAL_CONTENT_URI
, syncedFolder);
FilesSyncHelper.insertContentIntoDB(MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
syncedFolder);
} else if (mediaType == MediaFolderType.VIDEO) {
FilesSyncHelper.insertContentIntoDB(MediaStore.Video.Media.INTERNAL_CONTENT_URI,
syncedFolder);
FilesSyncHelper.insertContentIntoDB(MediaStore.Video.Media.EXTERNAL_CONTENT_URI,
syncedFolder);
} else {
try {
FilesystemDataProvider filesystemDataProvider = new FilesystemDataProvider(contentResolver);
Path path = Paths.get(syncedFolder.getLocalPath());
Files.walkFileTree(path, new SimpleFileVisitor<Path>() {
@Override
public FileVisitResult visitFile(Path path, BasicFileAttributes attrs) {
File file = path.toFile();
if (attrs.lastModifiedTime().toMillis() >= enabledTimestampMs) {
filesystemDataProvider.storeOrUpdateFileValue(path.toAbsolutePath().toString(),
attrs.lastModifiedTime().toMillis(),
file.isDirectory(), syncedFolder);
}
return FileVisitResult.CONTINUE;
}
@Override
public FileVisitResult visitFileFailed(Path file, IOException exc) {
return FileVisitResult.CONTINUE;
}
});
} catch (IOException e) {
Log_OC.e(TAG, "Something went wrong while indexing files for auto upload", e);
}
}
}
}
public static void insertAllDBEntries(AppPreferences preferences, Clock clock, boolean skipCustom) {
final Context context = MainApp.getAppContext();
final ContentResolver contentResolver = context.getContentResolver();
SyncedFolderProvider syncedFolderProvider = new SyncedFolderProvider(contentResolver, preferences, clock);
for (SyncedFolder syncedFolder : syncedFolderProvider.getSyncedFolders()) {
if (syncedFolder.isEnabled() && (MediaFolderType.CUSTOM != syncedFolder.getType() || !skipCustom)) {
insertAllDBEntriesForSyncedFolder(syncedFolder);
}
}
}
private static void insertContentIntoDB(Uri uri, SyncedFolder syncedFolder) {
final Context context = MainApp.getAppContext();
final ContentResolver contentResolver = context.getContentResolver();
Cursor cursor;
int column_index_data;
int column_index_date_modified;
final FilesystemDataProvider filesystemDataProvider = new FilesystemDataProvider(contentResolver);
String contentPath;
boolean isFolder;
String[] projection = {MediaStore.MediaColumns.DATA, MediaStore.MediaColumns.DATE_MODIFIED};
String path = syncedFolder.getLocalPath();
if (!path.endsWith(PATH_SEPARATOR)) {
path = path + PATH_SEPARATOR;
}
path = path + "%";
long enabledTimestampMs = syncedFolder.getEnabledTimestampMs();
cursor = context.getContentResolver().query(uri, projection, MediaStore.MediaColumns.DATA + " LIKE ?",
new String[]{path}, null);
if (cursor != null) {
column_index_data = cursor.getColumnIndexOrThrow(MediaStore.MediaColumns.DATA);
column_index_date_modified = cursor.getColumnIndexOrThrow(MediaStore.MediaColumns.DATE_MODIFIED);
while (cursor.moveToNext()) {
contentPath = cursor.getString(column_index_data);
isFolder = new File(contentPath).isDirectory();
if (cursor.getLong(column_index_date_modified) >= enabledTimestampMs / 1000.0) {
filesystemDataProvider.storeOrUpdateFileValue(contentPath,
cursor.getLong(column_index_date_modified), isFolder,
syncedFolder);
}
}
cursor.close();
}
}
public static void restartJobsIfNeeded(final UploadsStorageManager uploadsStorageManager,
final UserAccountManager accountManager,
final ConnectivityService connectivityService,
final PowerManagementService powerManagementService) {
final Context context = MainApp.getAppContext();
FileUploader.UploadRequester uploadRequester = new FileUploader.UploadRequester();
boolean accountExists;
OCUpload[] failedUploads = uploadsStorageManager.getFailedUploads();
for (OCUpload failedUpload : failedUploads) {
accountExists = false;
// check if accounts still exists
for (Account account : accountManager.getAccounts()) {
if (account.name.equals(failedUpload.getAccountName())) {
accountExists = true;
break;
}
}
if (!accountExists) {
uploadsStorageManager.removeUpload(failedUpload);
}
}
new Thread(() -> {
if (connectivityService.getActiveNetworkType() != JobRequest.NetworkType.ANY &&
!connectivityService.isInternetWalled()) {
uploadRequester.retryFailedUploads(context,
null,
uploadsStorageManager,
connectivityService,
accountManager,
powerManagementService,
null);
}
}).start();
}
public static void scheduleFilesSyncIfNeeded(Context context, BackgroundJobManager jobManager) {
// always run this because it also allows us to perform retries of manual uploads
new JobRequest.Builder(FilesSyncJob.TAG)
.setPeriodic(900000L, 300000L)
.setUpdateCurrent(true)
.build()
.schedule();
if (context != null && Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
jobManager.scheduleContentObserverJob();
}
}
public static void scheduleOfflineSyncIfNeeded() {
Set<JobRequest> jobRequests = JobManager.instance().getAllJobRequestsForTag(OfflineSyncJob.TAG);
if (jobRequests.isEmpty()) {
new JobRequest.Builder(OfflineSyncJob.TAG)
.setPeriodic(TimeUnit.MINUTES.toMillis(15), TimeUnit.MINUTES.toMillis(5))
.setUpdateCurrent(false)
.build()
.schedule();
}
}
}