Skip to content

sync refactor#38

Open
alexdremov wants to merge 1 commit into
mainfrom
sync-refactor
Open

sync refactor#38
alexdremov wants to merge 1 commit into
mainfrom
sync-refactor

Conversation

@alexdremov

Copy link
Copy Markdown
Owner

No description provided.

Copilot AI review requested due to automatic review settings May 7, 2026 20:18
@github-actions

github-actions Bot commented May 7, 2026

Copy link
Copy Markdown

Code Coverage Report

Overall Project 23.31% -2.02% 🍏
Files changed 10.18% 🍏

File Coverage
SyncManager.kt 12.65% -79% 🍏

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Refactors SyncManager to split the sync flow into smaller helpers (lock handling, local/remote scanning, uploads/downloads, pending deletions) while keeping the single-sync global semaphore and adding path normalization for cross-provider consistency.

Changes:

  • Extracted lock acquisition/release + error/cancellation handling into performSyncWithLocks.
  • Reworked core sync flow into performSyncInternal with helper functions for scanning, uploads/downloads, and deletion processing.
  • Introduced normalizePath(...) and standardized logging/progress reporting.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +305 to +314
val remotePath = "${config.remotePath.trimEnd('/')}/$relPath"
try {
provider.deleteFile(remotePath)
if (config.syncPdf && relPath.endsWith(".notate")) {
val pdfPath = relPath.substringBeforeLast(".") + ".pdf"
provider.deleteFile("${config.remotePath.trimEnd('/')}/$pdfPath")
}

// Save updated metadata
SyncPreferencesManager.saveProjectSyncMetadata(
context,
projectId,
syncMetadata.copy(files = fileStates),
)

// Update last sync time
SyncPreferencesManager.updateProjectSyncConfig(context, config.copy(lastSyncTimestamp = System.currentTimeMillis()))
updateProgress(100, "Sync complete")
Logger.d("SyncManager", "Sync finished successfully")
} catch (e: CancellationException) {
Logger.d("SyncManager", "Sync cancelled")
updateProgress(0, "Sync cancelled: ${e.message}")
SyncPreferencesManager.removePendingDeletion(context, projectId, deletion.relativePath)
fileStates.remove(relPath)
} catch (e: java.io.FileNotFoundException) {
Comment on lines +219 to +226
updateProgress(15, "Scanning remote storage...")
val allRemoteFiles =
scanRemoteFilesRecursively(provider, config.remotePath, "")
.filterNot { remote ->
SyncPreferencesManager
.getPendingDeletions(context)
.filter { it.projectId == projectId }
.map { it.relativePath.replace("\\", "/") }
.toSet()

updateProgress(15, "Listing remote files recursively...")
Logger.d("SyncManager", "Scanning remote files at ${config.remotePath}")
val allRemoteFilesRaw = scanRemoteFilesRecursively(provider, config.remotePath, "")

// Filter out files that are pending deletion
val allRemoteFiles =
allRemoteFilesRaw.filter {
val cleanPath = it.relativePath.replace("\\", "/")
!remainingPendingDeletions.contains(cleanPath)
}

Logger.d("SyncManager", "Found ${localFiles.size} local files and ${allRemoteFiles.size} remote files")

val totalSteps = localFiles.size + allRemoteFiles.size
var currentStep = 0

updateProgress(20, "Synchronizing files...")
.any { it.projectId == projectId && normalizePath(it.relativePath) == normalizePath(remote.relativePath) }
}
Comment on lines +230 to 233
// 5. Sync Logic
val totalSteps = localFiles.size + allRemoteFiles.size
var currentStep = 0

Comment on lines +242 to +249
if (state == null || local.lastModified > state.lastLocalModified || remote == null) {
val progress = 20 + (currentStep * 60 / totalSteps)
val stepSize = 60 / totalSteps
updateProgress(progress, "Uploading ${local.name}...")

// Update State
try {
val updatedRemoteItems = provider.listFiles(remotePath.substringBeforeLast('/'))
val updatedRemoteFile = updatedRemoteItems.find { it.name == localFile.name }

if (updatedRemoteFile != null) {
fileStates[cleanRelativePath] =
FileSyncState(
lastLocalModified = localFile.lastModified,
lastRemoteModified = updatedRemoteFile.lastModified,
lastSyncTime = System.currentTimeMillis(),
)
} else {
// Weird, we just uploaded it.
Logger.w("SyncManager", "Uploaded file not found immediately after upload: $cleanRelativePath")
}
} catch (e: Exception) {
Logger.w("SyncManager", "Failed to update metadata after upload for $cleanRelativePath", e)
}
}
uploadFileWithRetries(local, config, provider, fileStates) { p, m ->
val subProgress = progress + (p * stepSize / 100)
updateProgress(subProgress, "${local.name}: $m")
}
// 5a. Uploads
for (local in localFiles) {
if (SaveStatusManager.isSaving(local.path)) continue
Comment on lines +333 to +334

provider.createDirectory(remotePath.substringBeforeLast('/'))
Comment on lines +440 to +446
private fun scanLocalFiles(projectId: String): List<LocalFile> {
val project = PreferencesManager.getProjects(context).find { it.id == projectId } ?: return emptyList()
val result = mutableListOf<LocalFile>()
if (project.uri.startsWith("content://")) {
DocumentFile.fromTreeUri(context, Uri.parse(project.uri))?.let {
scanDocumentFilesRecursively(context, it, "", result)
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants