v1.0.76: multi-folder backup (many folders -> one remote base, each its own subfolder)
Build & Release APK / build (push) Successful in 12m53s
Build & Release APK / build (push) Successful in 12m53s
Adds an opt-in 'Back up multiple folders' mode to Add Sync: pick N folders + one remote base, and the app creates one normal pair per folder at base/<folderName>. Each source keeps its own subfolder so same-named files across folders never collide/overwrite — the safe many-to-one. - AddPairViewModel: multiFolder + localPaths state, batch save via buildEntity; folderLeafName() + uniqueSubName() derive collision-free remote subfolders - AddPairScreen: mode toggle (new pairs only), folder list w/ add/remove, remote-base labelling - SyncEngine: first-sync against a not-yet-created remote folder no longer fails — treat 404 as empty remote ONLY when no prior state exists (no DELETE_* can fire), and MKCOL the base dir so first uploads have a parent. An established pair still fails loudly on 404 (never mass-deletes a vanished remote). - Tests: MultiFolderTest, isRemoteNotFound cases in ExcludePathTest
This commit is contained in:
@@ -91,4 +91,16 @@ class ExcludePathTest {
|
||||
assertTrue(isExcludedPath("a/.hidden", pair(skipHiddenFiles = true)))
|
||||
assertFalse(isExcludedPath("a/.hidden", pair(skipHiddenFiles = false)))
|
||||
}
|
||||
|
||||
@Test fun `remote-not-found detected from http status messages`() {
|
||||
assertTrue(isRemoteNotFound(Exception("HTTP 404")))
|
||||
assertTrue(isRemoteNotFound(Exception("Not Found")))
|
||||
assertTrue(isRemoteNotFound(RuntimeException("PROPFIND failed: notfound")))
|
||||
}
|
||||
|
||||
@Test fun `remote-not-found is false for other errors`() {
|
||||
assertFalse(isRemoteNotFound(Exception("HTTP 500")))
|
||||
assertFalse(isRemoteNotFound(Exception("timeout")))
|
||||
assertFalse(isRemoteNotFound(Exception(null as String?)))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
package com.syncflow.ui.addpair
|
||||
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Assert.assertTrue
|
||||
import org.junit.Test
|
||||
|
||||
/**
|
||||
* Multi-folder backup: each selected folder maps to its OWN remote subfolder. The leaf-name and
|
||||
* uniqueness helpers are what keep that safe — same-named folders from different parents must not
|
||||
* collapse into one remote dir and overwrite each other.
|
||||
*/
|
||||
class MultiFolderTest {
|
||||
|
||||
@Test fun `leaf name from SAF tree uri`() {
|
||||
assertEquals("Camera", folderLeafName("content://com.android.externalstorage.documents/tree/primary%3ADCIM%2FCamera"))
|
||||
assertEquals("DCIM", folderLeafName("content://com.android.externalstorage.documents/tree/primary%3ADCIM"))
|
||||
}
|
||||
|
||||
@Test fun `leaf name from plain filesystem path`() {
|
||||
assertEquals("Camera", folderLeafName("/storage/emulated/0/DCIM/Camera"))
|
||||
assertEquals("Pictures", folderLeafName("/storage/emulated/0/Pictures/"))
|
||||
}
|
||||
|
||||
@Test fun `blank-ish paths fall back to folder`() {
|
||||
assertEquals("folder", folderLeafName(""))
|
||||
assertEquals("folder", folderLeafName("/"))
|
||||
}
|
||||
|
||||
@Test fun `unique sub names disambiguate collisions`() {
|
||||
val used = mutableSetOf<String>()
|
||||
assertEquals("Camera", uniqueSubName("Camera", used))
|
||||
assertEquals("Camera_2", uniqueSubName("Camera", used))
|
||||
assertEquals("Camera_3", uniqueSubName("Camera", used))
|
||||
assertEquals("Pictures", uniqueSubName("Pictures", used))
|
||||
}
|
||||
|
||||
@Test fun `unique sub name flattens slashes`() {
|
||||
val used = mutableSetOf<String>()
|
||||
assertTrue('/' !in uniqueSubName("a/b/c", used))
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user