mirror of
https://github.com/nextcloud/android.git
synced 2024-11-21 12:45:32 +03:00
Internal two way sync
Signed-off-by: tobiasKaminsky <tobias@kaminsky.me>
This commit is contained in:
parent
24eb7f02e8
commit
82c6956566
33 changed files with 1789 additions and 29 deletions
|
@ -1206,4 +1206,4 @@
|
|||
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '082a63031678a67879428f688f02d3b5')"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
1245
app/schemas/com.nextcloud.client.database.NextcloudDatabase/83.json
Normal file
1245
app/schemas/com.nextcloud.client.database.NextcloudDatabase/83.json
Normal file
File diff suppressed because it is too large
Load diff
Binary file not shown.
Before Width: | Height: | Size: 42 KiB After Width: | Height: | Size: 35 KiB |
Binary file not shown.
Before Width: | Height: | Size: 43 KiB After Width: | Height: | Size: 35 KiB |
|
@ -106,7 +106,6 @@ public class FileIT extends AbstractOnServerIT {
|
|||
assertTrue(new SynchronizeFolderOperation(targetContext,
|
||||
folderPath,
|
||||
user,
|
||||
System.currentTimeMillis(),
|
||||
fileDataStorageManager)
|
||||
.execute(targetContext)
|
||||
.isSuccess());
|
||||
|
|
|
@ -255,6 +255,9 @@
|
|||
<activity
|
||||
android:name=".ui.activity.SyncedFoldersActivity"
|
||||
android:exported="false" />
|
||||
<activity
|
||||
android:name=".ui.activity.InternalTwoWaySyncActivity"
|
||||
android:exported="false" />
|
||||
<activity
|
||||
android:name="com.nextcloud.client.widget.DashboardWidgetConfigurationActivity"
|
||||
android:exported="false">
|
||||
|
@ -627,4 +630,4 @@
|
|||
</activity>
|
||||
</application>
|
||||
|
||||
</manifest>
|
||||
</manifest>
|
||||
|
|
|
@ -60,7 +60,8 @@ import com.owncloud.android.db.ProviderMeta
|
|||
AutoMigration(from = 78, to = 79, spec = DatabaseMigrationUtil.ResetCapabilitiesPostMigration::class),
|
||||
AutoMigration(from = 79, to = 80),
|
||||
AutoMigration(from = 80, to = 81),
|
||||
AutoMigration(from = 81, to = 82)
|
||||
AutoMigration(from = 81, to = 82),
|
||||
AutoMigration(from = 82, to = 83)
|
||||
],
|
||||
exportSchema = true
|
||||
)
|
||||
|
|
|
@ -49,4 +49,10 @@ interface FileDao {
|
|||
|
||||
@Query("SELECT * FROM filelist where file_owner = :fileOwner AND etag_in_conflict IS NOT NULL")
|
||||
fun getFilesWithSyncConflict(fileOwner: String): List<FileEntity>
|
||||
|
||||
@Query(
|
||||
"SELECT * FROM filelist where file_owner = :fileOwner AND internal_two_way_sync_timestamp >= 0 " +
|
||||
"ORDER BY internal_two_way_sync_timestamp DESC"
|
||||
)
|
||||
fun getInternalTwoWaySyncFolders(fileOwner: String): List<FileEntity>
|
||||
}
|
||||
|
|
|
@ -115,5 +115,9 @@ data class FileEntity(
|
|||
@ColumnInfo(name = ProviderTableMeta.FILE_METADATA_GPS)
|
||||
val metadataGPS: String?,
|
||||
@ColumnInfo(name = ProviderTableMeta.FILE_E2E_COUNTER)
|
||||
val e2eCounter: Long?
|
||||
val e2eCounter: Long?,
|
||||
@ColumnInfo(name = ProviderTableMeta.FILE_INTERNAL_TWO_WAY_SYNC_TIMESTAMP)
|
||||
val internalTwoWaySync: Long?,
|
||||
@ColumnInfo(name = ProviderTableMeta.FILE_INTERNAL_TWO_WAY_SYNC_RESULT)
|
||||
val internalTwoWaySyncResult: String?
|
||||
)
|
||||
|
|
|
@ -54,6 +54,7 @@ import com.owncloud.android.ui.activity.FileActivity;
|
|||
import com.owncloud.android.ui.activity.FileDisplayActivity;
|
||||
import com.owncloud.android.ui.activity.FilePickerActivity;
|
||||
import com.owncloud.android.ui.activity.FolderPickerActivity;
|
||||
import com.owncloud.android.ui.activity.InternalTwoWaySyncActivity;
|
||||
import com.owncloud.android.ui.activity.ManageAccountsActivity;
|
||||
import com.owncloud.android.ui.activity.ManageSpaceActivity;
|
||||
import com.owncloud.android.ui.activity.NotificationsActivity;
|
||||
|
@ -476,4 +477,7 @@ abstract class ComponentsModule {
|
|||
|
||||
@ContributesAndroidInjector
|
||||
abstract TestJob testJob();
|
||||
|
||||
@ContributesAndroidInjector
|
||||
abstract InternalTwoWaySyncActivity internalTwoWaySyncActivity();
|
||||
}
|
||||
|
|
|
@ -95,6 +95,7 @@ class BackgroundJobFactory @Inject constructor(
|
|||
GeneratePdfFromImagesWork::class -> createPDFGenerateWork(context, workerParameters)
|
||||
HealthStatusWork::class -> createHealthStatusWork(context, workerParameters)
|
||||
TestJob::class -> createTestJob(context, workerParameters)
|
||||
InternalTwoWaySyncWork::class -> createInternalTwoWaySyncWork(context, workerParameters)
|
||||
else -> null // caller falls back to default factory
|
||||
}
|
||||
}
|
||||
|
@ -277,4 +278,14 @@ class BackgroundJobFactory @Inject constructor(
|
|||
backgroundJobManager.get()
|
||||
)
|
||||
}
|
||||
|
||||
private fun createInternalTwoWaySyncWork(context: Context, params: WorkerParameters): InternalTwoWaySyncWork {
|
||||
return InternalTwoWaySyncWork(
|
||||
context,
|
||||
params,
|
||||
accountManager,
|
||||
powerManagementService,
|
||||
connectivityService
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -168,4 +168,5 @@ interface BackgroundJobManager {
|
|||
fun schedulePeriodicHealthStatus()
|
||||
fun startHealthStatus()
|
||||
fun bothFilesSyncJobsRunning(syncedFolderID: Long): Boolean
|
||||
fun scheduleInternal2WaySync()
|
||||
}
|
||||
|
|
|
@ -84,6 +84,8 @@ internal class BackgroundJobManagerImpl(
|
|||
const val JOB_PERIODIC_HEALTH_STATUS = "periodic_health_status"
|
||||
const val JOB_IMMEDIATE_HEALTH_STATUS = "immediate_health_status"
|
||||
|
||||
const val JOB_INTERNAL_TWO_WAY_SYNC = "internal_two_way_sync"
|
||||
|
||||
const val JOB_TEST = "test_job"
|
||||
|
||||
const val MAX_CONTENT_TRIGGER_DELAY_MS = 10000L
|
||||
|
@ -647,4 +649,13 @@ internal class BackgroundJobManagerImpl(
|
|||
request
|
||||
)
|
||||
}
|
||||
|
||||
override fun scheduleInternal2WaySync() {
|
||||
val request = periodicRequestBuilder(
|
||||
jobClass = InternalTwoWaySyncWork::class,
|
||||
jobName = JOB_INTERNAL_TWO_WAY_SYNC
|
||||
).build()
|
||||
|
||||
workManager.enqueueUniquePeriodicWork(JOB_INTERNAL_TWO_WAY_SYNC, ExistingPeriodicWorkPolicy.KEEP, request)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,91 @@
|
|||
/*
|
||||
* Nextcloud - Android Client
|
||||
*
|
||||
* SPDX-FileCopyrightText: 2024 Tobias Kaminsky <tobias.kaminsky@nextcloud.com>
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
*/
|
||||
|
||||
package com.nextcloud.client.jobs
|
||||
|
||||
import android.content.Context
|
||||
import androidx.work.Worker
|
||||
import androidx.work.WorkerParameters
|
||||
import com.nextcloud.client.account.UserAccountManager
|
||||
import com.nextcloud.client.device.PowerManagementService
|
||||
import com.nextcloud.client.network.ConnectivityService
|
||||
import com.owncloud.android.MainApp
|
||||
import com.owncloud.android.datamodel.FileDataStorageManager
|
||||
import com.owncloud.android.lib.common.utils.Log_OC
|
||||
import com.owncloud.android.operations.SynchronizeFolderOperation
|
||||
import com.owncloud.android.utils.FileStorageUtils
|
||||
import java.io.File
|
||||
|
||||
@Suppress("Detekt.NestedBlockDepth")
|
||||
class InternalTwoWaySyncWork(
|
||||
private val context: Context,
|
||||
params: WorkerParameters,
|
||||
private val userAccountManager: UserAccountManager,
|
||||
private val powerManagementService: PowerManagementService,
|
||||
private val connectivityService: ConnectivityService
|
||||
) : Worker(context, params) {
|
||||
override fun doWork(): Result {
|
||||
Log_OC.d(TAG, "Worker started!")
|
||||
|
||||
var result = true
|
||||
|
||||
if (powerManagementService.isPowerSavingEnabled ||
|
||||
!connectivityService.isConnected || connectivityService.isInternetWalled
|
||||
) {
|
||||
Log_OC.d(TAG, "Not starting due to constraints!")
|
||||
return Result.success()
|
||||
}
|
||||
|
||||
val users = userAccountManager.allUsers
|
||||
|
||||
for (user in users) {
|
||||
val fileDataStorageManager = FileDataStorageManager(user, context.contentResolver)
|
||||
val folders = fileDataStorageManager.getInternalTwoWaySyncFolders(user)
|
||||
|
||||
for (folder in folders) {
|
||||
val freeSpaceLeft = File(folder.storagePath).getFreeSpace()
|
||||
val localFolderSize = FileStorageUtils.getFolderSize(File(folder.storagePath, MainApp.getDataFolder()))
|
||||
val remoteFolderSize = folder.fileLength
|
||||
|
||||
if (freeSpaceLeft < (remoteFolderSize - localFolderSize)) {
|
||||
Log_OC.d(TAG, "Not enough space left!")
|
||||
result = false
|
||||
}
|
||||
|
||||
Log_OC.d(TAG, "Folder ${folder.remotePath}: started!")
|
||||
val operation = SynchronizeFolderOperation(context, folder.remotePath, user, fileDataStorageManager)
|
||||
.execute(context)
|
||||
|
||||
if (operation.isSuccess) {
|
||||
Log_OC.d(TAG, "Folder ${folder.remotePath}: finished!")
|
||||
} else {
|
||||
Log_OC.d(TAG, "Folder ${folder.remotePath} failed!")
|
||||
result = false
|
||||
}
|
||||
|
||||
folder.apply {
|
||||
internalFolderSyncResult = operation.code.toString()
|
||||
internalFolderSyncTimestamp = System.currentTimeMillis()
|
||||
}
|
||||
|
||||
fileDataStorageManager.saveFile(folder)
|
||||
}
|
||||
}
|
||||
|
||||
return if (result) {
|
||||
Log_OC.d(TAG, "Worker finished with success!")
|
||||
Result.success()
|
||||
} else {
|
||||
Log_OC.d(TAG, "Worker finished with failure!")
|
||||
Result.failure()
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val TAG = "InternalTwoWaySyncWork"
|
||||
}
|
||||
}
|
|
@ -371,6 +371,7 @@ public class MainApp extends Application implements HasAndroidInjector {
|
|||
backgroundJobManager.scheduleMediaFoldersDetectionJob();
|
||||
backgroundJobManager.startMediaFoldersDetectionJob();
|
||||
backgroundJobManager.schedulePeriodicHealthStatus();
|
||||
backgroundJobManager.scheduleInternal2WaySync();
|
||||
}
|
||||
|
||||
registerGlobalPassCodeProtection();
|
||||
|
|
|
@ -77,6 +77,7 @@ import androidx.annotation.VisibleForTesting;
|
|||
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
|
||||
import kotlin.Pair;
|
||||
|
||||
@SuppressFBWarnings("CE")
|
||||
public class FileDataStorageManager {
|
||||
private static final String TAG = FileDataStorageManager.class.getSimpleName();
|
||||
|
||||
|
@ -558,6 +559,8 @@ public class FileDataStorageManager {
|
|||
cv.put(ProviderTableMeta.FILE_SHAREES, gson.toJson(fileOrFolder.getSharees()));
|
||||
cv.put(ProviderTableMeta.FILE_TAGS, gson.toJson(fileOrFolder.getTags()));
|
||||
cv.put(ProviderTableMeta.FILE_RICH_WORKSPACE, fileOrFolder.getRichWorkspace());
|
||||
cv.put(ProviderTableMeta.FILE_INTERNAL_TWO_WAY_SYNC_TIMESTAMP, fileOrFolder.getInternalFolderSyncTimestamp());
|
||||
cv.put(ProviderTableMeta.FILE_INTERNAL_TWO_WAY_SYNC_RESULT, fileOrFolder.getInternalFolderSyncResult());
|
||||
return cv;
|
||||
}
|
||||
|
||||
|
@ -602,6 +605,8 @@ public class FileDataStorageManager {
|
|||
cv.put(ProviderTableMeta.FILE_METADATA_GPS, gson.toJson(file.getGeoLocation()));
|
||||
cv.put(ProviderTableMeta.FILE_METADATA_LIVE_PHOTO, file.getLinkedFileIdForLivePhoto());
|
||||
cv.put(ProviderTableMeta.FILE_E2E_COUNTER, file.getE2eCounter());
|
||||
cv.put(ProviderTableMeta.FILE_INTERNAL_TWO_WAY_SYNC_TIMESTAMP, file.getInternalFolderSyncTimestamp());
|
||||
cv.put(ProviderTableMeta.FILE_INTERNAL_TWO_WAY_SYNC_RESULT, file.getInternalFolderSyncResult());
|
||||
|
||||
return cv;
|
||||
}
|
||||
|
@ -1035,6 +1040,7 @@ public class FileDataStorageManager {
|
|||
ocFile.setLivePhoto(fileEntity.getMetadataLivePhoto());
|
||||
ocFile.setHidden(nullToZero(fileEntity.getHidden()) == 1);
|
||||
ocFile.setE2eCounter(fileEntity.getE2eCounter());
|
||||
ocFile.setInternalFolderSyncTimestamp(fileEntity.getInternalTwoWaySync());
|
||||
|
||||
String sharees = fileEntity.getSharees();
|
||||
// Surprisingly JSON deserialization causes significant overhead.
|
||||
|
@ -2477,4 +2483,29 @@ public class FileDataStorageManager {
|
|||
|
||||
return files;
|
||||
}
|
||||
|
||||
public List<OCFile> getInternalTwoWaySyncFolders(User user) {
|
||||
List<FileEntity> fileEntities = fileDao.getInternalTwoWaySyncFolders(user.getAccountName());
|
||||
List<OCFile> files = new ArrayList<>(fileEntities.size());
|
||||
|
||||
for (FileEntity fileEntity : fileEntities) {
|
||||
files.add(createFileInstance(fileEntity));
|
||||
}
|
||||
|
||||
return files;
|
||||
}
|
||||
|
||||
public boolean isPartOfInternalTwoWaySync(OCFile file) {
|
||||
if (file.isInternalFolderSync()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
while (file != null && !OCFile.ROOT_PATH.equals(file.getDecryptedRemotePath())) {
|
||||
if (file.isInternalFolderSync()) {
|
||||
return true;
|
||||
}
|
||||
file = getFileById(file.getParentId());
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -117,6 +117,8 @@ public class OCFile implements Parcelable, Comparable<OCFile>, ServerFileInterfa
|
|||
@Nullable
|
||||
private GeoLocation geolocation;
|
||||
private List<String> tags = new ArrayList<>();
|
||||
private Long internalFolderSyncTimestamp = -1L;
|
||||
private String internalFolderSyncResult = "";
|
||||
|
||||
/**
|
||||
* URI to the local path of the file contents, if stored in the device; cached after first call to
|
||||
|
@ -1051,6 +1053,26 @@ public class OCFile implements Parcelable, Comparable<OCFile>, ServerFileInterfa
|
|||
this.e2eCounter = e2eCounter;
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isInternalFolderSync() {
|
||||
return internalFolderSyncTimestamp >= 0;
|
||||
}
|
||||
|
||||
public Long getInternalFolderSyncTimestamp() {
|
||||
return internalFolderSyncTimestamp;
|
||||
}
|
||||
|
||||
public void setInternalFolderSyncTimestamp(Long internalFolderSyncTimestamp) {
|
||||
this.internalFolderSyncTimestamp = internalFolderSyncTimestamp;
|
||||
}
|
||||
|
||||
public String getInternalFolderSyncResult() {
|
||||
return internalFolderSyncResult;
|
||||
}
|
||||
|
||||
public void setInternalFolderSyncResult(String internalFolderSyncResult) {
|
||||
this.internalFolderSyncResult = internalFolderSyncResult;
|
||||
}
|
||||
|
||||
public boolean isAPKorAAB() {
|
||||
if ("gplay".equals(BuildConfig.FLAVOR)) {
|
||||
|
|
|
@ -25,7 +25,7 @@ import java.util.List;
|
|||
*/
|
||||
public class ProviderMeta {
|
||||
public static final String DB_NAME = "filelist";
|
||||
public static final int DB_VERSION = 82;
|
||||
public static final int DB_VERSION = 83;
|
||||
|
||||
private ProviderMeta() {
|
||||
// No instance
|
||||
|
@ -120,6 +120,8 @@ public class ProviderMeta {
|
|||
public static final String FILE_LOCK_TOKEN = "lock_token";
|
||||
public static final String FILE_TAGS = "tags";
|
||||
public static final String FILE_E2E_COUNTER = "e2e_counter";
|
||||
public static final String FILE_INTERNAL_TWO_WAY_SYNC_TIMESTAMP = "internal_two_way_sync_timestamp";
|
||||
public static final String FILE_INTERNAL_TWO_WAY_SYNC_RESULT = "internal_two_way_sync_result";
|
||||
|
||||
public static final List<String> FILE_ALL_COLUMNS = Collections.unmodifiableList(Arrays.asList(
|
||||
_ID,
|
||||
|
@ -171,7 +173,9 @@ public class ProviderMeta {
|
|||
FILE_METADATA_LIVE_PHOTO,
|
||||
FILE_E2E_COUNTER,
|
||||
FILE_TAGS,
|
||||
FILE_METADATA_GPS));
|
||||
FILE_METADATA_GPS,
|
||||
FILE_INTERNAL_TWO_WAY_SYNC_TIMESTAMP,
|
||||
FILE_INTERNAL_TWO_WAY_SYNC_RESULT));
|
||||
public static final String FILE_DEFAULT_SORT_ORDER = FILE_NAME + " collate nocase asc";
|
||||
|
||||
// Columns of ocshares table
|
||||
|
|
|
@ -697,6 +697,7 @@ public class RefreshFolderOperation extends RemoteOperation {
|
|||
if (localFile != null) {
|
||||
updatedFile.setFileId(localFile.getFileId());
|
||||
updatedFile.setLastSyncDateForData(localFile.getLastSyncDateForData());
|
||||
updatedFile.setInternalFolderSyncTimestamp(localFile.getInternalFolderSyncTimestamp());
|
||||
updatedFile.setModificationTimestampAtLastSyncForData(
|
||||
localFile.getModificationTimestampAtLastSyncForData()
|
||||
);
|
||||
|
|
|
@ -295,6 +295,8 @@ public class SynchronizeFileOperation extends SyncOperation {
|
|||
}
|
||||
|
||||
private void requestForDownload(OCFile file) {
|
||||
Log_OC.d("InternalTwoWaySyncWork", "download file: " + file.getFileName());
|
||||
|
||||
FileDownloadHelper.Companion.instance().downloadFile(
|
||||
mUser,
|
||||
file);
|
||||
|
|
|
@ -55,9 +55,6 @@ public class SynchronizeFolderOperation extends SyncOperation {
|
|||
|
||||
private static final String TAG = SynchronizeFolderOperation.class.getSimpleName();
|
||||
|
||||
/** Time stamp for the synchronization process in progress */
|
||||
private long mCurrentSyncTime;
|
||||
|
||||
/** Remote path of the folder to synchronize */
|
||||
private String mRemotePath;
|
||||
|
||||
|
@ -95,17 +92,14 @@ public class SynchronizeFolderOperation extends SyncOperation {
|
|||
* @param context Application context.
|
||||
* @param remotePath Path to synchronize.
|
||||
* @param user Nextcloud account where the folder is located.
|
||||
* @param currentSyncTime Time stamp for the synchronization process in progress.
|
||||
*/
|
||||
public SynchronizeFolderOperation(Context context,
|
||||
String remotePath,
|
||||
User user,
|
||||
long currentSyncTime,
|
||||
FileDataStorageManager storageManager) {
|
||||
super(storageManager);
|
||||
|
||||
mRemotePath = remotePath;
|
||||
mCurrentSyncTime = currentSyncTime;
|
||||
this.user = user;
|
||||
mContext = context;
|
||||
mRemoteFolderChanged = false;
|
||||
|
@ -365,7 +359,7 @@ public class SynchronizeFolderOperation extends SyncOperation {
|
|||
}
|
||||
|
||||
private void updateLocalStateData(OCFile remoteFile, OCFile localFile, OCFile updatedFile) {
|
||||
updatedFile.setLastSyncDateForProperties(mCurrentSyncTime);
|
||||
updatedFile.setLastSyncDateForProperties(System.currentTimeMillis());
|
||||
if (localFile != null) {
|
||||
updatedFile.setFileId(localFile.getFileId());
|
||||
updatedFile.setLastSyncDateForData(localFile.getLastSyncDateForData());
|
||||
|
@ -393,8 +387,19 @@ public class SynchronizeFolderOperation extends SyncOperation {
|
|||
}
|
||||
}
|
||||
|
||||
private void classifyFileForLaterSyncOrDownload(OCFile remoteFile, OCFile localFile) {
|
||||
if (!remoteFile.isFolder()) {
|
||||
@SuppressFBWarnings("JLM")
|
||||
private void classifyFileForLaterSyncOrDownload(OCFile remoteFile, OCFile localFile) throws OperationCancelledException {
|
||||
if (remoteFile.isFolder()) {
|
||||
/// to download children files recursively
|
||||
synchronized (mCancellationRequested) {
|
||||
if (mCancellationRequested.get()) {
|
||||
throw new OperationCancelledException();
|
||||
}
|
||||
startSyncFolderOperation(remoteFile.getRemotePath());
|
||||
}
|
||||
|
||||
} else {
|
||||
/// prepare content synchronization for files (any file, not just favorites)
|
||||
SynchronizeFileOperation operation = new SynchronizeFileOperation(
|
||||
localFile,
|
||||
remoteFile,
|
||||
|
|
|
@ -707,7 +707,6 @@ public class OperationsService extends Service {
|
|||
this, // TODO remove this dependency from construction time
|
||||
remotePath,
|
||||
user,
|
||||
System.currentTimeMillis(), // TODO remove this dependency from construction time
|
||||
fileDataStorageManager
|
||||
);
|
||||
break;
|
||||
|
|
|
@ -0,0 +1,30 @@
|
|||
/*
|
||||
* Nextcloud - Android Client
|
||||
*
|
||||
* SPDX-FileCopyrightText: 2024 Tobias Kaminsky <tobias.kaminsky@nextcloud.com>
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
*/
|
||||
|
||||
package com.owncloud.android.ui.activity
|
||||
|
||||
import android.os.Bundle
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import com.nextcloud.client.di.Injectable
|
||||
import com.owncloud.android.databinding.InternalTwoWaySyncLayoutBinding
|
||||
import com.owncloud.android.ui.adapter.InternalTwoWaySyncAdapter
|
||||
|
||||
class InternalTwoWaySyncActivity : BaseActivity(), Injectable {
|
||||
lateinit var binding: InternalTwoWaySyncLayoutBinding
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
|
||||
binding = InternalTwoWaySyncLayoutBinding.inflate(layoutInflater)
|
||||
setContentView(binding.root)
|
||||
|
||||
binding.list.apply {
|
||||
adapter = InternalTwoWaySyncAdapter(fileDataStorageManager, user.get(), context)
|
||||
layoutManager = LinearLayoutManager(context)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -171,6 +171,9 @@ public class SettingsActivity extends PreferenceActivity
|
|||
// Details
|
||||
setupDetailsCategory(preferenceScreen);
|
||||
|
||||
// Sync
|
||||
setupSyncCategory();
|
||||
|
||||
// More
|
||||
setupMoreCategory();
|
||||
|
||||
|
@ -310,13 +313,19 @@ public class SettingsActivity extends PreferenceActivity
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void setupSyncCategory() {
|
||||
final PreferenceCategory preferenceCategorySync = (PreferenceCategory) findPreference("sync");
|
||||
viewThemeUtils.files.themePreferenceCategory(preferenceCategorySync);
|
||||
|
||||
setupAutoUploadPreference(preferenceCategorySync);
|
||||
setupInternalTwoWaySyncPreference(preferenceCategorySync);
|
||||
}
|
||||
|
||||
private void setupMoreCategory() {
|
||||
final PreferenceCategory preferenceCategoryMore = (PreferenceCategory) findPreference("more");
|
||||
viewThemeUtils.files.themePreferenceCategory(preferenceCategoryMore);
|
||||
|
||||
setupAutoUploadPreference(preferenceCategoryMore);
|
||||
|
||||
setupCalendarPreference(preferenceCategoryMore);
|
||||
|
||||
setupBackupPreference();
|
||||
|
@ -548,6 +557,16 @@ public class SettingsActivity extends PreferenceActivity
|
|||
});
|
||||
}
|
||||
}
|
||||
|
||||
private void setupInternalTwoWaySyncPreference(PreferenceCategory preferenceCategorySync) {
|
||||
Preference twoWaySync = findPreference("internal_two_way_sync");
|
||||
|
||||
twoWaySync.setOnPreferenceClickListener(preference -> {
|
||||
Intent intent = new Intent(this, InternalTwoWaySyncActivity.class);
|
||||
startActivity(intent);
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
private void setupBackupPreference() {
|
||||
Preference pContactsBackup = findPreference("backup");
|
||||
|
|
|
@ -424,8 +424,12 @@ public class StorageMigration {
|
|||
throw new MigrationException(R.string.file_migration_failed_dir_already_exists);
|
||||
}
|
||||
|
||||
if (dstFile.getFreeSpace() < FileStorageUtils.getFolderSize(new File(srcFile, MainApp.getDataFolder()))) {
|
||||
throw new MigrationException(R.string.file_migration_failed_not_enough_space);
|
||||
try {
|
||||
if (dstFile.getFreeSpace() < FileStorageUtils.getFolderSize(new File(srcFile, MainApp.getDataFolder()))) {
|
||||
throw new MigrationException(R.string.file_migration_failed_not_enough_space);
|
||||
}
|
||||
} catch (MigrationException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,43 @@
|
|||
/*
|
||||
* Nextcloud - Android Client
|
||||
*
|
||||
* SPDX-FileCopyrightText: 2024 Tobias Kaminsky <tobias.kaminsky@nextcloud.com>
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
*/
|
||||
|
||||
package com.owncloud.android.ui.adapter
|
||||
|
||||
import android.content.Context
|
||||
import android.view.LayoutInflater
|
||||
import android.view.ViewGroup
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import com.nextcloud.client.account.User
|
||||
import com.owncloud.android.databinding.InternalTwoWaySyncViewHolderBinding
|
||||
import com.owncloud.android.datamodel.FileDataStorageManager
|
||||
import com.owncloud.android.datamodel.OCFile
|
||||
|
||||
class InternalTwoWaySyncAdapter(
|
||||
dataStorageManager: FileDataStorageManager,
|
||||
user: User,
|
||||
val context: Context
|
||||
) : RecyclerView.Adapter<InternalTwoWaySyncViewHolder>() {
|
||||
var folders: List<OCFile> = dataStorageManager.getInternalTwoWaySyncFolders(user)
|
||||
|
||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): InternalTwoWaySyncViewHolder {
|
||||
return InternalTwoWaySyncViewHolder(
|
||||
InternalTwoWaySyncViewHolderBinding.inflate(
|
||||
LayoutInflater.from(parent.context),
|
||||
parent,
|
||||
false
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
override fun getItemCount(): Int {
|
||||
return folders.size
|
||||
}
|
||||
|
||||
override fun onBindViewHolder(holder: InternalTwoWaySyncViewHolder, position: Int) {
|
||||
holder.bind(folders[position], context)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,44 @@
|
|||
/*
|
||||
* Nextcloud - Android Client
|
||||
*
|
||||
* SPDX-FileCopyrightText: 2024 Tobias Kaminsky <tobias.kaminsky@nextcloud.com>
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
*/
|
||||
|
||||
package com.owncloud.android.ui.adapter
|
||||
|
||||
import android.content.Context
|
||||
import android.view.View
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import com.owncloud.android.R
|
||||
import com.owncloud.android.databinding.InternalTwoWaySyncViewHolderBinding
|
||||
import com.owncloud.android.datamodel.OCFile
|
||||
import com.owncloud.android.utils.DisplayUtils
|
||||
|
||||
class InternalTwoWaySyncViewHolder(val binding: InternalTwoWaySyncViewHolderBinding) :
|
||||
RecyclerView.ViewHolder(binding.root) {
|
||||
fun bind(folder: OCFile, context: Context) {
|
||||
binding.run {
|
||||
size.text = DisplayUtils.bytesToHumanReadable(folder.fileLength)
|
||||
name.text = folder.decryptedFileName
|
||||
|
||||
if (folder.internalFolderSyncResult.isEmpty()) {
|
||||
syncResult.visibility = View.GONE
|
||||
syncResultDivider.visibility = View.GONE
|
||||
} else {
|
||||
syncResult.visibility = View.VISIBLE
|
||||
syncResultDivider.visibility = View.VISIBLE
|
||||
syncResult.text = folder.internalFolderSyncResult
|
||||
}
|
||||
|
||||
if (folder.internalFolderSyncTimestamp == 0L) {
|
||||
syncTimestamp.text = context.getString(R.string.internal_two_way_sync_not_yet)
|
||||
} else {
|
||||
syncTimestamp.text = DisplayUtils.getRelativeTimestamp(
|
||||
context,
|
||||
folder.internalFolderSyncTimestamp
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -261,6 +261,7 @@ public class FileDetailFragment extends FileFragment implements OnClickListener,
|
|||
binding.favorite.setOnClickListener(this);
|
||||
binding.overflowMenu.setOnClickListener(this);
|
||||
binding.lastModificationTimestamp.setOnClickListener(this);
|
||||
binding.folderSyncButton.setOnClickListener(this);
|
||||
|
||||
updateFileDetails(false, false);
|
||||
}
|
||||
|
@ -471,8 +472,14 @@ public class FileDetailFragment extends FileFragment implements OnClickListener,
|
|||
boolean showDetailedTimestamp = !preferences.isShowDetailedTimestampEnabled();
|
||||
preferences.setShowDetailedTimestampEnabled(showDetailedTimestamp);
|
||||
setFileModificationTimestamp(getFile(), showDetailedTimestamp);
|
||||
|
||||
Log_OC.e(TAG, "Incorrect view clicked!");
|
||||
} else if (id == R.id.folder_sync_button) {
|
||||
if (binding.folderSyncButton.isChecked()) {
|
||||
getFile().setInternalFolderSyncTimestamp(0L);
|
||||
} else {
|
||||
getFile().setInternalFolderSyncTimestamp(-1L);
|
||||
}
|
||||
|
||||
storageManager.saveFile(getFile());
|
||||
} else {
|
||||
Log_OC.e(TAG, "Incorrect view clicked!");
|
||||
}
|
||||
|
@ -556,6 +563,17 @@ public class FileDetailFragment extends FileFragment implements OnClickListener,
|
|||
if (fabMain != null) {
|
||||
fabMain.hide();
|
||||
}
|
||||
|
||||
binding.syncBlock.setVisibility(file.isFolder() ? View.VISIBLE : View.GONE);
|
||||
|
||||
if (file.isInternalFolderSync()) {
|
||||
binding.folderSyncButton.setChecked(file.isInternalFolderSync());
|
||||
} else {
|
||||
if (storageManager.isPartOfInternalTwoWaySync(file)) {
|
||||
binding.folderSyncButton.setChecked(true);
|
||||
binding.folderSyncButton.setEnabled(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
setupViewPager();
|
||||
|
|
|
@ -170,6 +170,39 @@
|
|||
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/syncBlock"
|
||||
android:layout_width="match_parent"
|
||||
android:orientation="vertical"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<View
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="1dp"
|
||||
android:background="@color/list_divider_background" />
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
android:paddingStart="@dimen/standard_padding"
|
||||
android:paddingTop="@dimen/standard_half_padding"
|
||||
android:paddingEnd="@dimen/zero"
|
||||
android:paddingBottom="@dimen/standard_half_padding">
|
||||
|
||||
<CheckBox
|
||||
android:id="@+id/folder_sync_button"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/sync" />
|
||||
</LinearLayout>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<View
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="1dp"
|
||||
|
|
18
app/src/main/res/layout/internal_two_way_sync_layout.xml
Normal file
18
app/src/main/res/layout/internal_two_way_sync_layout.xml
Normal file
|
@ -0,0 +1,18 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
~ Nextcloud - Android Client
|
||||
~
|
||||
~ SPDX-FileCopyrightText: 2024 Tobias Kaminsky <tobias@kaminsky.me>
|
||||
~ SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
-->
|
||||
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:id="@+id/list"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
|
@ -0,0 +1,97 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
~ Nextcloud - Android Client
|
||||
~
|
||||
~ SPDX-FileCopyrightText: 2024 Andy Scherzinger <info@andy-scherzinger.de>
|
||||
~ SPDX-FileCopyrightText: 2024 Tobias Kaminsky <tobias@kaminsky.me>
|
||||
~ SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
-->
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="@dimen/min_list_item_size"
|
||||
android:orientation="horizontal"
|
||||
android:paddingStart="@dimen/standard_half_padding"
|
||||
android:paddingEnd="@dimen/standard_half_padding">
|
||||
|
||||
<ImageView
|
||||
android:layout_width="@dimen/file_icon_size"
|
||||
android:layout_height="@dimen/file_icon_size"
|
||||
android:layout_gravity="center_vertical"
|
||||
android:contentDescription="@null"
|
||||
android:src="@drawable/folder" />
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="@dimen/min_list_item_size"
|
||||
android:layout_marginStart="@dimen/standard_half_margin"
|
||||
android:gravity="center_vertical"
|
||||
android:orientation="vertical">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/name"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:ellipsize="middle"
|
||||
android:gravity="center_vertical"
|
||||
android:singleLine="true"
|
||||
android:textColor="@color/text_color"
|
||||
android:textSize="@dimen/two_line_primary_text_size"
|
||||
tools:text="Folder abc" />
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/size"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="center_vertical"
|
||||
android:textColor="@color/list_item_lastmod_and_filesize_text"
|
||||
android:textSize="@dimen/two_line_secondary_text_size"
|
||||
tools:text="241 Mb" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="end"
|
||||
android:paddingStart="@dimen/zero"
|
||||
android:paddingEnd="@dimen/standard_quarter_padding"
|
||||
android:text="@string/info_separator"
|
||||
android:textColor="@color/list_item_lastmod_and_filesize_text"
|
||||
android:textSize="@dimen/two_line_secondary_text_size" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/sync_timestamp"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="center_vertical"
|
||||
android:textColor="@color/list_item_lastmod_and_filesize_text"
|
||||
android:textSize="@dimen/two_line_secondary_text_size"
|
||||
tools:text="5 min ago" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/sync_result_divider"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="end"
|
||||
android:paddingStart="@dimen/zero"
|
||||
android:paddingEnd="@dimen/standard_quarter_padding"
|
||||
android:text="@string/info_separator"
|
||||
android:textColor="@color/list_item_lastmod_and_filesize_text"
|
||||
android:textSize="@dimen/two_line_secondary_text_size" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/sync_result"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="center_vertical"
|
||||
android:textColor="@color/list_item_lastmod_and_filesize_text"
|
||||
android:textSize="@dimen/two_line_secondary_text_size"
|
||||
tools:text="Success" />
|
||||
|
||||
</LinearLayout>
|
||||
</LinearLayout>
|
||||
|
||||
</LinearLayout>
|
|
@ -1218,13 +1218,15 @@
|
|||
<string name="sub_folder_rule_day">Year/Month/Day</string>
|
||||
<string name="secure_share_not_set_up">Secure sharing is not set up for this user</string>
|
||||
<string name="share_not_allowed_when_file_drop">Resharing is not allowed during secure file drop</string>
|
||||
<string name="prefs_category_sync">Sync</string>
|
||||
<string name="internal_two_way_sync">Internal two way sync</string>
|
||||
<string name="prefs_two_way_sync_summary">Manage internal folders for two way sync</string>
|
||||
<string name="internal_two_way_sync_not_yet">Not yet, soon to be synced</string>
|
||||
<string name="gplay_restriction">Google restricted downloading APK/AAB files!</string>
|
||||
<string name="file_list_empty_local_search">No file or folder matching your search</string>
|
||||
|
||||
<string name="unified_search_fragment_calendar_event_not_found">Event not found, you can always sync to update. Redirecting to web…</string>
|
||||
<string name="unified_search_fragment_contact_not_found">Contact not found, you can always sync to update. Redirecting to web…</string>
|
||||
<string name="unified_search_fragment_permission_needed">Permissions are required to open search result otherwise it will redirected to web…</string>
|
||||
|
||||
<string name="file_name_validator_current_path_is_invalid">Current folder name is invalid, please rename the folder. Redirecting to root</string>
|
||||
<string name="file_name_validator_rename_before_move_or_copy">%s. Please rename the file before moving or copying</string>
|
||||
<string name="file_name_validator_upload_content_error">Some contents cannot able to uploaded due to contains reserved names or invalid character</string>
|
||||
|
@ -1233,4 +1235,5 @@
|
|||
<string name="file_name_validator_error_reserved_names">%s is a forbidden name</string>
|
||||
<string name="file_name_validator_error_forbidden_file_extensions">.%s is a forbidden file extension</string>
|
||||
<string name="file_name_validator_error_ends_with_space_period">Name ends with a space or a period</string>
|
||||
<string name="sync">Sync</string>
|
||||
</resources>
|
||||
|
|
|
@ -53,13 +53,23 @@
|
|||
android:key="show_media_scan_notifications"/>
|
||||
</PreferenceCategory>
|
||||
|
||||
<PreferenceCategory
|
||||
android:title="@string/prefs_category_sync"
|
||||
android:key="sync">
|
||||
<Preference
|
||||
android:title="@string/drawer_synced_folders"
|
||||
android:key="syncedFolders"
|
||||
android:summary="@string/prefs_sycned_folders_summary" />
|
||||
|
||||
<Preference
|
||||
android:title="@string/internal_two_way_sync"
|
||||
android:key="internal_two_way_sync"
|
||||
android:summary="@string/prefs_two_way_sync_summary" />
|
||||
</PreferenceCategory>
|
||||
|
||||
<PreferenceCategory
|
||||
android:title="@string/prefs_category_more"
|
||||
android:key="more">
|
||||
<Preference
|
||||
android:title="@string/drawer_synced_folders"
|
||||
android:key="syncedFolders"
|
||||
android:summary="@string/prefs_sycned_folders_summary" />
|
||||
<Preference
|
||||
android:title="@string/prefs_calendar_contacts"
|
||||
android:key="calendar_contacts"
|
||||
|
|
Loading…
Reference in a new issue