From 70b79523e291e62fd963c0d974909bca5a22c650 Mon Sep 17 00:00:00 2001 From: Victor Andreasson Date: Fri, 13 Sep 2024 00:27:04 +0200 Subject: [PATCH] Add setting for enabling subfolders The feature is disabled by default. In the long run, we will probably want to enable it by default, and finally remove it. But I'm not yet sure how to do that without disrupting the user experience. --- .../orgzly/android/repos/DocumentRepoTest.kt | 32 +++++-- .../orgzly/android/prefs/AppPreferences.java | 13 +++ .../orgzly/android/repos/DocumentRepo.java | 21 +++-- .../orgzly/android/repos/DropboxClient.java | 4 +- .../com/orgzly/android/repos/DropboxRepo.java | 9 ++ .../com/orgzly/android/repos/GitRepo.java | 10 ++- .../com/orgzly/android/repos/WebdavRepo.kt | 17 +++- app/src/main/res/values/prefs_keys.xml | 4 + app/src/main/res/values/strings.xml | 4 + app/src/main/res/xml/prefs_screen_sync.xml | 6 ++ .../orgzly/android/repos/DropboxRepoTest.kt | 32 +++++-- .../com/orgzly/android/repos/GitRepoTest.kt | 32 +++++-- .../orgzly/android/repos/WebdavRepoTest.kt | 32 +++++-- .../com/orgzly/android/repos/SyncRepoTest.kt | 90 +++++++++++++++++-- 14 files changed, 268 insertions(+), 38 deletions(-) diff --git a/app/src/androidTest/java/com/orgzly/android/repos/DocumentRepoTest.kt b/app/src/androidTest/java/com/orgzly/android/repos/DocumentRepoTest.kt index 6ee38a981..c451503fb 100644 --- a/app/src/androidTest/java/com/orgzly/android/repos/DocumentRepoTest.kt +++ b/app/src/androidTest/java/com/orgzly/android/repos/DocumentRepoTest.kt @@ -51,8 +51,13 @@ class DocumentRepoTest : SyncRepoTest, OrgzlyTest() { } @Test - override fun testGetBooks_singleFileInSubfolder() { - SyncRepoTest.testGetBooks_singleFileInSubfolder(repoDirectory, syncRepo) + override fun testGetBooks_singleFileInSubfolderWhenEnabled() { + SyncRepoTest.testGetBooks_singleFileInSubfolderWhenEnabled(repoDirectory, syncRepo) + } + + @Test + override fun testGetBooks_singleFileInSubfolderWhenDisabled() { + SyncRepoTest.testGetBooks_singleFileInSubfolderWhenDisabled(repoDirectory, syncRepo) } @Test @@ -81,8 +86,13 @@ class DocumentRepoTest : SyncRepoTest, OrgzlyTest() { } @Test - override fun testStoreBook_producesSameUriAsRetrieveBook() { - SyncRepoTest.testStoreBook_producesSameUriAsRetrieveBook(syncRepo) + override fun testStoreBook_producesSameUriAsRetrieveBookWithSubfolder() { + SyncRepoTest.testStoreBook_producesSameUriAsRetrieveBookWithSubfolder(syncRepo) + } + + @Test + override fun testStoreBook_producesSameUriAsRetrieveBookWithoutSubfolder() { + SyncRepoTest.testStoreBook_producesSameUriAsRetrieveBookWithoutSubfolder(syncRepo) } @Test @@ -95,6 +105,11 @@ class DocumentRepoTest : SyncRepoTest, OrgzlyTest() { SyncRepoTest.testStoreBook_inSubfolder(repoDirectory, syncRepo) } + @Test(expected = IOException::class) + override fun testStoreBook_inSubfolderWhenDisabled() { + SyncRepoTest.testStoreBook_inSubfolderWhenDisabled(syncRepo) + } + @Test override fun testRenameBook_expectedUri() { SyncRepoTest.testRenameBook_expectedUri(syncRepo) @@ -106,8 +121,13 @@ class DocumentRepoTest : SyncRepoTest, OrgzlyTest() { } @Test - override fun testRenameBook_fromRootToSubfolder() { - SyncRepoTest.testRenameBook_fromRootToSubfolder(syncRepo) + override fun testRenameBook_fromRootToSubfolderWhenEnabled() { + SyncRepoTest.testRenameBook_fromRootToSubfolderWhenEnabled(syncRepo) + } + + @Test(expected = IOException::class) + override fun testRenameBook_fromRootToSubfolderWhenDisabled() { + SyncRepoTest.testRenameBook_fromRootToSubfolderWhenDisabled(syncRepo) } @Test diff --git a/app/src/main/java/com/orgzly/android/prefs/AppPreferences.java b/app/src/main/java/com/orgzly/android/prefs/AppPreferences.java index 99015bc6b..54c0edbcc 100644 --- a/app/src/main/java/com/orgzly/android/prefs/AppPreferences.java +++ b/app/src/main/java/com/orgzly/android/prefs/AppPreferences.java @@ -1106,6 +1106,19 @@ public static void refileLastLocation(Context context, String value) { getStateSharedPreferences(context).edit().putString(key, value).apply(); } + /* + * Subfolder support + */ + public static boolean subfolderSupport(Context context) { + String key = context.getResources().getString(R.string.pref_key_enable_repo_subfolders); + return getStateSharedPreferences(context).getBoolean(key, false); + } + + public static void subfolderSupport(Context context, boolean value) { + String key = context.getResources().getString(R.string.pref_key_enable_repo_subfolders); + getStateSharedPreferences(context).edit().putBoolean(key, value).apply(); + } + /* * Repository properties map */ diff --git a/app/src/main/java/com/orgzly/android/repos/DocumentRepo.java b/app/src/main/java/com/orgzly/android/repos/DocumentRepo.java index 03dc61eca..d2b0068bc 100644 --- a/app/src/main/java/com/orgzly/android/repos/DocumentRepo.java +++ b/app/src/main/java/com/orgzly/android/repos/DocumentRepo.java @@ -12,6 +12,7 @@ import com.orgzly.R; import com.orgzly.android.BookName; import com.orgzly.android.db.entity.Repo; +import com.orgzly.android.prefs.AppPreferences; import com.orgzly.android.util.LogUtils; import com.orgzly.android.util.MiscUtils; @@ -117,6 +118,8 @@ private List walkFileTree() { for (DocumentFile node : currentDir.listFiles()) { String repoRelativePath = BookName.getRepoRelativePath(repoUri, node.getUri()); if (node.isDirectory()) { + if (!AppPreferences.subfolderSupport(context)) + continue; if (Build.VERSION.SDK_INT >= 26) { if (ignores.isPathIgnored(repoRelativePath, true)) { continue; @@ -176,10 +179,14 @@ public VersionedRook storeBook(File file, String repoRelativePath) throws IOExce } DocumentFile destinationFile = getDocumentFileFromPath(repoRelativePath); if (repoRelativePath.contains("/")) { - DocumentFile destinationDir = ensureDirectoryHierarchy(repoRelativePath); - String fileName = Uri.parse(repoRelativePath).getLastPathSegment(); - if (destinationDir.findFile(fileName) == null) { - destinationFile = destinationDir.createFile("text/*", fileName); + if (AppPreferences.subfolderSupport(context)) { + DocumentFile destinationDir = ensureDirectoryHierarchy(repoRelativePath); + String fileName = Uri.parse(repoRelativePath).getLastPathSegment(); + if (destinationDir.findFile(fileName) == null) { + destinationFile = destinationDir.createFile("text/*", fileName); + } + } else { + throw new IOException(context.getString(R.string.subfolder_support_disabled)); } } else { if (!destinationFile.exists()) { @@ -246,7 +253,11 @@ public VersionedRook renameBook(Uri oldFullUri, String newName) throws IOExcepti Uri newUri = oldFullUri; if (newName.contains("/")) { - newDir = ensureDirectoryHierarchy(newName); + if (AppPreferences.subfolderSupport(context)) { + newDir = ensureDirectoryHierarchy(newName); + } else { + throw new IOException(context.getString(R.string.subfolder_support_disabled)); + } } else { newDir = repoDocumentFile; } diff --git a/app/src/main/java/com/orgzly/android/repos/DropboxClient.java b/app/src/main/java/com/orgzly/android/repos/DropboxClient.java index fceec62ee..5d76ba55f 100644 --- a/app/src/main/java/com/orgzly/android/repos/DropboxClient.java +++ b/app/src/main/java/com/orgzly/android/repos/DropboxClient.java @@ -180,7 +180,7 @@ public List getBooks(Uri repoUri, RepoIgnoreNode ignores) throws list.add(book); } } - if (metadata instanceof FolderMetadata) { + if (metadata instanceof FolderMetadata && AppPreferences.subfolderSupport(mContext)) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { if (ignores.isPathIgnored(pathRelativeToRepoRoot, true)) { continue; @@ -385,6 +385,8 @@ public void deleteFolder(String path) throws IOException { } else { throw new IOException("Not a directory: " + path); } + } catch (GetMetadataErrorException e) { + // Do nothing; the folder does not exist. } catch (DbxException e) { e.printStackTrace(); if (e.getMessage() != null) { diff --git a/app/src/main/java/com/orgzly/android/repos/DropboxRepo.java b/app/src/main/java/com/orgzly/android/repos/DropboxRepo.java index 21cfdf65c..85b26acc4 100644 --- a/app/src/main/java/com/orgzly/android/repos/DropboxRepo.java +++ b/app/src/main/java/com/orgzly/android/repos/DropboxRepo.java @@ -5,7 +5,10 @@ import androidx.annotation.NonNull; +import com.orgzly.R; +import com.orgzly.android.App; import com.orgzly.android.BookName; +import com.orgzly.android.prefs.AppPreferences; import java.io.File; import java.io.IOException; @@ -56,11 +59,17 @@ public InputStream openRepoFileInputStream(String repoRelativePath) throws IOExc @Override public VersionedRook storeBook(File file, String repoRelativePath) throws IOException { + Context context = App.getAppContext(); + if (repoRelativePath.contains("/") && !AppPreferences.subfolderSupport(context)) + throw new IOException(context.getString(R.string.subfolder_support_disabled)); return client.upload(file, repoUri, repoRelativePath); } @Override public VersionedRook renameBook(Uri oldFullUri, String newName) throws IOException { + Context context = App.getAppContext(); + if (newName.contains("/") && !AppPreferences.subfolderSupport(context)) + throw new IOException(context.getString(R.string.subfolder_support_disabled)); BookName oldBookName = BookName.fromRepoRelativePath(BookName.getRepoRelativePath(repoUri, oldFullUri)); String newRelativePath = BookName.repoRelativePath(newName, oldBookName.getFormat()); String newEncodedRelativePath = Uri.encode(newRelativePath, "/"); diff --git a/app/src/main/java/com/orgzly/android/repos/GitRepo.java b/app/src/main/java/com/orgzly/android/repos/GitRepo.java index 2cf1941e1..056633a12 100644 --- a/app/src/main/java/com/orgzly/android/repos/GitRepo.java +++ b/app/src/main/java/com/orgzly/android/repos/GitRepo.java @@ -7,6 +7,8 @@ import android.util.Log; import com.orgzly.BuildConfig; +import com.orgzly.R; +import com.orgzly.android.App; import com.orgzly.android.BookFormat; import com.orgzly.android.BookName; import com.orgzly.android.db.entity.Repo; @@ -14,6 +16,7 @@ import com.orgzly.android.git.GitPreferences; import com.orgzly.android.git.GitPreferencesFromRepoPrefs; import com.orgzly.android.git.GitTransportSetter; +import com.orgzly.android.prefs.AppPreferences; import com.orgzly.android.prefs.RepoPreferences; import com.orgzly.android.util.LogUtils; @@ -260,6 +263,7 @@ public List getBooks() throws IOException { walk.setRecursive(true); walk.addTree(synchronizer.currentHead().getTree()); final RepoIgnoreNode ignores = new RepoIgnoreNode(this); + boolean supportsSubFolders = AppPreferences.subfolderSupport(App.getAppContext()); walk.setFilter(new TreeFilter() { @Override public boolean include(TreeWalk walker) { @@ -269,7 +273,7 @@ public boolean include(TreeWalk walker) { if (ignores.isIgnored(repoRelativePath, isDirectory) == IgnoreNode.MatchResult.IGNORED) return false; if (isDirectory) - return true; + return supportsSubFolders; return BookName.isSupportedFormatFileName(repoRelativePath); } @@ -298,6 +302,10 @@ public void delete(Uri uri) throws IOException { } public VersionedRook renameBook(Uri oldFullUri, String newName) throws IOException { + Context context = App.getAppContext(); + if (newName.contains("/") && !AppPreferences.subfolderSupport(context)) { + throw new IOException(context.getString(R.string.subfolder_support_disabled)); + } String oldPath = oldFullUri.toString().replaceFirst("^/", ""); String newPath = BookName.repoRelativePath(newName, BookFormat.ORG); if (synchronizer.renameFileInRepo(oldPath, newPath)) { diff --git a/app/src/main/java/com/orgzly/android/repos/WebdavRepo.kt b/app/src/main/java/com/orgzly/android/repos/WebdavRepo.kt index 81599b893..034ebfb93 100644 --- a/app/src/main/java/com/orgzly/android/repos/WebdavRepo.kt +++ b/app/src/main/java/com/orgzly/android/repos/WebdavRepo.kt @@ -9,7 +9,10 @@ import com.burgstaller.okhttp.basic.BasicAuthenticator import com.burgstaller.okhttp.digest.CachingAuthenticator import com.burgstaller.okhttp.digest.Credentials import com.burgstaller.okhttp.digest.DigestAuthenticator +import com.orgzly.R +import com.orgzly.android.App import com.orgzly.android.BookName +import com.orgzly.android.prefs.AppPreferences import com.thegrizzlylabs.sardineandroid.DavResource import com.thegrizzlylabs.sardineandroid.impl.OkHttpSardine import okhttp3.OkHttpClient @@ -166,8 +169,14 @@ class WebdavRepo( val ignores = RepoIgnoreNode(this) + val listDepth = if (AppPreferences.subfolderSupport(App.getAppContext())) { + -1 + } else { + 1 + } + return sardine - .list(url, -1) + .list(url, listDepth) .mapNotNull { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { if (!BookName.isSupportedFormatFileName(it.name) || ignores.isPathIgnored(it.getRelativePath(), it.isDirectory)) { @@ -222,6 +231,9 @@ class WebdavRepo( val encodedRepoPath = Uri.encode(repoRelativePath, "/") if (encodedRepoPath != null) { if (encodedRepoPath.contains("/")) { + val context = App.getAppContext() + if (!AppPreferences.subfolderSupport(context)) + throw IOException(context.getString(R.string.subfolder_support_disabled)) ensureDirectoryHierarchy(encodedRepoPath) } } @@ -244,6 +256,9 @@ class WebdavRepo( } if (newName.contains("/")) { + val context = App.getAppContext() + if (!AppPreferences.subfolderSupport(context)) + throw IOException(context.getString(R.string.subfolder_support_disabled)) ensureDirectoryHierarchy(newEncodedRelativePath) } diff --git a/app/src/main/res/values/prefs_keys.xml b/app/src/main/res/values/prefs_keys.xml index 4d2c14855..686ef9ad0 100644 --- a/app/src/main/res/values/prefs_keys.xml +++ b/app/src/main/res/values/prefs_keys.xml @@ -561,6 +561,10 @@ pref_key_highlight_edited_rich_text false + + pref_key_enable_repo_subfolders + false + pref_key_git_ssh_key_type pref_key_git_author diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index c26eeac1d..32016cc3d 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -87,6 +87,8 @@ Repositories Location to synchronize your notebooks with + Support repository subfolders + Load from and write to subfolders SSH key generation Generate key pair for Git repo sync View generated SSH public key @@ -784,4 +786,6 @@ Saved to %s Renaming notebook to a different subdirectory requires Android 7 or higher + + Support for subfolders is disabled diff --git a/app/src/main/res/xml/prefs_screen_sync.xml b/app/src/main/res/xml/prefs_screen_sync.xml index 78c2d01cf..b5b8ca710 100644 --- a/app/src/main/res/xml/prefs_screen_sync.xml +++ b/app/src/main/res/xml/prefs_screen_sync.xml @@ -15,6 +15,12 @@ android:targetClass="com.orgzly.android.ui.repos.ReposActivity"/> + +