/**
* Nextcloud Android client application
*
* @author Mario Danic
* Copyright (C) 2017 Mario Danic
* Copyright (C) 2017 Nextcloud
*
* 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 .
*/
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.provider.MediaStore;
import android.text.TextUtils;
import android.util.Log;
import com.evernote.android.job.JobManager;
import com.evernote.android.job.JobRequest;
import com.evernote.android.job.util.Device;
import com.owncloud.android.MainApp;
import com.owncloud.android.authentication.AccountUtils;
import com.owncloud.android.datamodel.ArbitraryDataProvider;
import com.owncloud.android.datamodel.FilesystemDataProvider;
import com.owncloud.android.datamodel.MediaFolder;
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.db.UploadResult;
import com.owncloud.android.files.services.FileUploader;
import com.owncloud.android.jobs.AutoUploadJob;
import com.owncloud.android.operations.UploadFileOperation;
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.io.RandomAccessFile;
import java.nio.channels.FileChannel;
import java.nio.channels.FileLock;
import java.nio.channels.OverlappingFileLockException;
public class FilesSyncHelper {
public static final String TAG = "FileSyncHelper";
public static void insertAllDBEntriesForSyncedFolder(SyncedFolder syncedFolder) {
final Context context = MainApp.getAppContext();
final ContentResolver contentResolver = context.getContentResolver();
ArbitraryDataProvider arbitraryDataProvider = new ArbitraryDataProvider(contentResolver);
String syncedFolderInitiatedKey = "syncedFolderIntitiated_" + syncedFolder.getId();
boolean dryRun = TextUtils.isEmpty(arbitraryDataProvider.getValue
("global", syncedFolderInitiatedKey));
if (MediaFolder.IMAGE == syncedFolder.getType()) {
FilesSyncHelper.insertContentIntoDB(android.provider.MediaStore.Images.Media.INTERNAL_CONTENT_URI
, dryRun, syncedFolder);
FilesSyncHelper.insertContentIntoDB(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, dryRun,
syncedFolder);
if (dryRun) {
arbitraryDataProvider.storeOrUpdateKeyValue("global", syncedFolderInitiatedKey,
"1");
}
} else if (MediaFolder.VIDEO == syncedFolder.getType()) {
FilesSyncHelper.insertContentIntoDB(android.provider.MediaStore.Video.Media.INTERNAL_CONTENT_URI,
dryRun, syncedFolder);
FilesSyncHelper.insertContentIntoDB(MediaStore.Video.Media.EXTERNAL_CONTENT_URI, dryRun,
syncedFolder);
if (dryRun) {
arbitraryDataProvider.storeOrUpdateKeyValue("global", syncedFolderInitiatedKey,
"1");
}
} else {
try {
FilesystemDataProvider filesystemDataProvider = new FilesystemDataProvider(contentResolver);
Path path = Paths.get(syncedFolder.getLocalPath());
Files.walkFileTree(path, new SimpleFileVisitor() {
@Override
public FileVisitResult visitFile(Path path, BasicFileAttributes attrs) throws IOException {
File file = path.toFile();
FileChannel channel = new RandomAccessFile(file, "rw").getChannel();
FileLock lock = channel.lock();
try {
lock = channel.tryLock();
filesystemDataProvider.storeOrUpdateFileValue(path.toAbsolutePath().toString(),
attrs.lastModifiedTime().toMillis(), file.isDirectory(), syncedFolder, dryRun);
} catch (OverlappingFileLockException e) {
filesystemDataProvider.storeOrUpdateFileValue(path.toAbsolutePath().toString(),
attrs.lastModifiedTime().toMillis(), file.isDirectory(), syncedFolder, dryRun);
} finally {
lock.release();
}
return FileVisitResult.CONTINUE;
}
@Override
public FileVisitResult visitFileFailed(Path file, IOException exc) {
return FileVisitResult.CONTINUE;
}
});
if (dryRun) {
arbitraryDataProvider.storeOrUpdateKeyValue("global", syncedFolderInitiatedKey,
"1");
}
} catch (IOException e) {
Log.d(TAG, "Something went wrong while indexing files for auto upload");
}
}
}
public static void insertAllDBEntries() {
final Context context = MainApp.getAppContext();
final ContentResolver contentResolver = context.getContentResolver();
SyncedFolderProvider syncedFolderProvider = new SyncedFolderProvider(contentResolver);
for (SyncedFolder syncedFolder : syncedFolderProvider.getSyncedFolders()) {
if (syncedFolder.isEnabled()) {
insertAllDBEntriesForSyncedFolder(syncedFolder);
}
}
}
private static void insertContentIntoDB(Uri uri, boolean dryRun, 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 = path + "/%";
} else {
path = path + "%";
}
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();
filesystemDataProvider.storeOrUpdateFileValue(cursor.getString(column_index_data),
cursor.getLong(column_index_date_modified), isFolder, syncedFolder, dryRun);
}
cursor.close();
}
}
public static void restartJobsIfNeeded() {
final Context context = MainApp.getAppContext();
boolean restartedInCurrentIteration;
int countRestartedJobs = 0;
FileUploader.UploadRequester uploadRequester = new FileUploader.UploadRequester();
boolean accountExists;
boolean fileExists;
for (JobRequest jobRequest : JobManager.instance().getAllJobRequestsForTag(AutoUploadJob.TAG)) {
restartedInCurrentIteration = false;
accountExists = false;
fileExists = new File(jobRequest.getExtras().getString(AutoUploadJob.LOCAL_PATH, "")).exists();
// check if accounts still exists
for (Account account : AccountUtils.getAccounts(context)) {
if (account.name.equals(jobRequest.getExtras().getString(AutoUploadJob.ACCOUNT, ""))) {
accountExists = true;
break;
}
}
if (accountExists && fileExists) {
if (countRestartedJobs < 5) {
// Handle case of charging
if (jobRequest.requiresCharging() && Device.isCharging(context)) {
if (jobRequest.requiredNetworkType().equals(JobRequest.NetworkType.CONNECTED) &&
!Device.getNetworkType(context).equals(JobRequest.NetworkType.ANY)) {
jobRequest.cancelAndEdit().build().schedule();
countRestartedJobs++;
restartedInCurrentIteration = true;
} else if (jobRequest.requiredNetworkType().equals(JobRequest.NetworkType.UNMETERED) &&
Device.getNetworkType(context).equals(JobRequest.NetworkType.UNMETERED)) {
jobRequest.cancelAndEdit().build().schedule();
countRestartedJobs++;
restartedInCurrentIteration = true;
}
}
// Handle case of wifi
if (!restartedInCurrentIteration) {
if (jobRequest.requiredNetworkType().equals(JobRequest.NetworkType.CONNECTED) &&
!Device.getNetworkType(context).equals(JobRequest.NetworkType.ANY)) {
jobRequest.cancelAndEdit().build().schedule();
countRestartedJobs++;
} else if (jobRequest.requiredNetworkType().equals(JobRequest.NetworkType.UNMETERED) &&
Device.getNetworkType(context).equals(JobRequest.NetworkType.UNMETERED)) {
jobRequest.cancelAndEdit().build().schedule();
countRestartedJobs++;
}
}
}
} else {
JobManager.instance().cancel(jobRequest.getJobId());
}
}
UploadsStorageManager uploadsStorageManager = new UploadsStorageManager(context.getContentResolver(), context);
OCUpload[] failedUploads = uploadsStorageManager.getFailedUploads();
for (OCUpload failedUpload: failedUploads) {
accountExists = false;
fileExists = new File(failedUpload.getLocalPath()).exists();
restartedInCurrentIteration = false;
// check if accounts still exists
for (Account account : AccountUtils.getAccounts(context)) {
if (account.name.equals(failedUpload.getAccountName())) {
accountExists = true;
break;
}
}
if (!failedUpload.getLastResult().equals(UploadResult.UPLOADED)) {
if (failedUpload.getCreadtedBy() == UploadFileOperation.CREATED_AS_INSTANT_PICTURE) {
if (accountExists && fileExists) {
if (countRestartedJobs < 5) {
// Handle case of charging
if (failedUpload.isWhileChargingOnly() && Device.isCharging(context)) {
if (failedUpload.isUseWifiOnly() &&
Device.getNetworkType(context).equals(JobRequest.NetworkType.UNMETERED)) {
uploadRequester.retry(context, failedUpload);
restartedInCurrentIteration = true;
countRestartedJobs++;
} else if (!failedUpload.isUseWifiOnly() &&
!Device.getNetworkType(context).equals(JobRequest.NetworkType.ANY)) {
uploadRequester.retry(context, failedUpload);
restartedInCurrentIteration = true;
countRestartedJobs++;
}
}
// Handle case of wifi
if (!restartedInCurrentIteration) {
if (failedUpload.isUseWifiOnly() &&
Device.getNetworkType(context).equals(JobRequest.NetworkType.UNMETERED)) {
uploadRequester.retry(context, failedUpload);
countRestartedJobs++;
} else if (!failedUpload.isUseWifiOnly() &&
!Device.getNetworkType(context).equals(JobRequest.NetworkType.ANY)) {
uploadRequester.retry(context, failedUpload);
countRestartedJobs++;
}
}
}
} else {
uploadsStorageManager.removeUpload(failedUpload);
}
} else {
if (accountExists && fileExists) {
if (countRestartedJobs < 5) {
uploadRequester.retry(context, failedUpload);
countRestartedJobs++;
}
} else {
uploadsStorageManager.removeUpload(failedUpload);
}
}
}
}
}
}