From cb34c4349bb35b6e65188a95f95b0d61bb2f0c6b Mon Sep 17 00:00:00 2001
From: Helle <54167962+Helle-Daryd@users.noreply.github.com>
Date: Tue, 23 Jul 2024 18:06:33 +0200
Subject: [PATCH 01/10] Fixed the image loading
We are now requesting the correct permission on for Android sdk level
33 and above.
---
app/src/main/AndroidManifest.xml | 3 ++-
app/src/main/java/com/orgzly/android/util/AppPermissions.kt | 5 ++++-
2 files changed, 6 insertions(+), 2 deletions(-)
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 4242e8453..1f46ad192 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -12,7 +12,8 @@
-
+
+
diff --git a/app/src/main/java/com/orgzly/android/util/AppPermissions.kt b/app/src/main/java/com/orgzly/android/util/AppPermissions.kt
index 426fa497e..0e7d05da5 100644
--- a/app/src/main/java/com/orgzly/android/util/AppPermissions.kt
+++ b/app/src/main/java/com/orgzly/android/util/AppPermissions.kt
@@ -70,7 +70,10 @@ object AppPermissions {
Usage.BOOK_EXPORT -> Manifest.permission.WRITE_EXTERNAL_STORAGE
Usage.SYNC_START -> Manifest.permission.WRITE_EXTERNAL_STORAGE
Usage.SAVED_SEARCHES_EXPORT_IMPORT -> Manifest.permission.WRITE_EXTERNAL_STORAGE
- Usage.EXTERNAL_FILES_ACCESS -> Manifest.permission.READ_EXTERNAL_STORAGE
+ Usage.EXTERNAL_FILES_ACCESS -> if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU)
+ Manifest.permission.READ_MEDIA_IMAGES
+ else
+ Manifest.permission.READ_EXTERNAL_STORAGE
Usage.POST_NOTIFICATIONS -> Manifest.permission.POST_NOTIFICATIONS
}
}
From b711871e5852cefbcef831e15cef989ae6f5fc4f Mon Sep 17 00:00:00 2001
From: Victor Andreasson
Date: Wed, 24 Jul 2024 01:41:52 +0200
Subject: [PATCH 02/10] Grant the READ_MEDIA_IMAGES permission during
ExternalLinksTest
---
.../java/com/orgzly/android/espresso/ExternalLinksTest.kt | 7 +++++++
1 file changed, 7 insertions(+)
diff --git a/app/src/androidTest/java/com/orgzly/android/espresso/ExternalLinksTest.kt b/app/src/androidTest/java/com/orgzly/android/espresso/ExternalLinksTest.kt
index a356f22a8..68faff3ca 100644
--- a/app/src/androidTest/java/com/orgzly/android/espresso/ExternalLinksTest.kt
+++ b/app/src/androidTest/java/com/orgzly/android/espresso/ExternalLinksTest.kt
@@ -6,6 +6,7 @@ import androidx.test.core.app.ActivityScenario
import androidx.test.espresso.action.ViewActions.click
import androidx.test.espresso.assertion.ViewAssertions.matches
import androidx.test.espresso.matcher.ViewMatchers.withText
+import androidx.test.rule.GrantPermissionRule
import com.orgzly.R
import com.orgzly.android.App
import com.orgzly.android.OrgzlyTest
@@ -15,6 +16,7 @@ import com.orgzly.android.espresso.util.EspressoUtils.onNoteInBook
import com.orgzly.android.espresso.util.EspressoUtils.onSnackbar
import com.orgzly.android.ui.main.MainActivity
import org.hamcrest.Matchers.startsWith
+import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.Parameterized
@@ -25,6 +27,11 @@ class ExternalLinksTest(private val param: Parameter) : OrgzlyTest() {
data class Parameter(val link: String, val check: () -> Any)
+ @get:Rule
+ val grantPermissionRule: GrantPermissionRule = GrantPermissionRule.grant(
+ android.Manifest.permission.READ_MEDIA_IMAGES
+ )
+
companion object {
@JvmStatic
@Parameterized.Parameters(name = "{index}: {0}")
From 1323925346f0e97bdbbf3735d1fdf143b81a55b3 Mon Sep 17 00:00:00 2001
From: Victor Andreasson
Date: Wed, 24 Jul 2024 14:07:30 +0200
Subject: [PATCH 03/10] Grant permission when running the relevant API version
---
.../com/orgzly/android/espresso/ExternalLinksTest.kt | 9 ++++++---
1 file changed, 6 insertions(+), 3 deletions(-)
diff --git a/app/src/androidTest/java/com/orgzly/android/espresso/ExternalLinksTest.kt b/app/src/androidTest/java/com/orgzly/android/espresso/ExternalLinksTest.kt
index 68faff3ca..1e0218971 100644
--- a/app/src/androidTest/java/com/orgzly/android/espresso/ExternalLinksTest.kt
+++ b/app/src/androidTest/java/com/orgzly/android/espresso/ExternalLinksTest.kt
@@ -1,5 +1,6 @@
package com.orgzly.android.espresso
+import android.os.Build
import android.os.Environment
import android.os.SystemClock
import androidx.test.core.app.ActivityScenario
@@ -28,9 +29,11 @@ class ExternalLinksTest(private val param: Parameter) : OrgzlyTest() {
data class Parameter(val link: String, val check: () -> Any)
@get:Rule
- val grantPermissionRule: GrantPermissionRule = GrantPermissionRule.grant(
- android.Manifest.permission.READ_MEDIA_IMAGES
- )
+ val grantPermissionRule: GrantPermissionRule = if (Build.VERSION.SDK_INT >= 33) {
+ GrantPermissionRule.grant(android.Manifest.permission.READ_MEDIA_IMAGES)
+ } else {
+ GrantPermissionRule.grant()
+ }
companion object {
@JvmStatic
From f9e031c34586ed1c2ea485fbe3f771e0a004f2ea Mon Sep 17 00:00:00 2001
From: Victor Andreasson
Date: Sat, 27 Jul 2024 22:50:11 +0200
Subject: [PATCH 04/10] Release 1.8.25-beta.1
---
app/build.gradle | 4 ++--
app/src/main/res/layout/dialog_whats_new.xml | 13 +++++++++++++
2 files changed, 15 insertions(+), 2 deletions(-)
diff --git a/app/build.gradle b/app/build.gradle
index 543ad2dcd..8d3c04500 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -10,8 +10,8 @@ android {
defaultConfig {
minSdkVersion 21
targetSdkVersion 33
- versionCode 212
- versionName "1.8.24"
+ versionCode 213
+ versionName "1.8.25-beta.1"
applicationId = "com.orgzlyrevived"
resValue "string", "application_id", "com.orgzlyrevived"
diff --git a/app/src/main/res/layout/dialog_whats_new.xml b/app/src/main/res/layout/dialog_whats_new.xml
index 28a7422d9..f36621931 100644
--- a/app/src/main/res/layout/dialog_whats_new.xml
+++ b/app/src/main/res/layout/dialog_whats_new.xml
@@ -20,6 +20,19 @@
android:layout_height="wrap_content" />
+
+
+
+
+
+
Date: Sun, 9 Jun 2024 00:53:00 +0200
Subject: [PATCH 05/10] Add class RepoIgnoreNode which compares file names
against .orgzlyignore rules
The class extends the JGit class IgnoreNode, which can do most of the
work for us.
The SyncRepo interface needed a new method to allow reading an arbitrary
file from the repository, since the .orgzlyignore file is not an ORG
file.
---
.../android/git/GitFileSynchronizer.java | 6 ++
.../com/orgzly/android/repos/ContentRepo.java | 7 +++
.../orgzly/android/repos/DatabaseRepo.java | 6 ++
.../orgzly/android/repos/DirectoryRepo.java | 7 +++
.../orgzly/android/repos/DropboxClient.java | 25 ++++++++
.../com/orgzly/android/repos/DropboxRepo.java | 6 ++
.../com/orgzly/android/repos/GitRepo.java | 7 +++
.../com/orgzly/android/repos/MockRepo.java | 6 ++
.../orgzly/android/repos/RepoIgnoreNode.kt | 61 +++++++++++++++++++
.../com/orgzly/android/repos/SyncRepo.java | 9 +++
.../com/orgzly/android/repos/WebdavRepo.kt | 8 +++
app/src/main/res/values/strings.xml | 2 +-
12 files changed, 149 insertions(+), 1 deletion(-)
create mode 100644 app/src/main/java/com/orgzly/android/repos/RepoIgnoreNode.kt
diff --git a/app/src/main/java/com/orgzly/android/git/GitFileSynchronizer.java b/app/src/main/java/com/orgzly/android/git/GitFileSynchronizer.java
index e976c95f2..cfa0fa4fe 100644
--- a/app/src/main/java/com/orgzly/android/git/GitFileSynchronizer.java
+++ b/app/src/main/java/com/orgzly/android/git/GitFileSynchronizer.java
@@ -29,8 +29,10 @@
import org.eclipse.jgit.treewalk.TreeWalk;
import java.io.File;
+import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
+import java.io.InputStream;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.List;
@@ -61,6 +63,10 @@ public void retrieveLatestVersionOfFile(
MiscUtils.copyFile(repoDirectoryFile(repositoryPath), destination);
}
+ public InputStream openRepoFileInputStream(String repositoryPath) throws FileNotFoundException {
+ return new FileInputStream(repoDirectoryFile(repositoryPath));
+ }
+
private void fetch() throws IOException {
try {
if (BuildConfig.LOG_DEBUG) {
diff --git a/app/src/main/java/com/orgzly/android/repos/ContentRepo.java b/app/src/main/java/com/orgzly/android/repos/ContentRepo.java
index 0b1148ebd..50e94eaa3 100644
--- a/app/src/main/java/com/orgzly/android/repos/ContentRepo.java
+++ b/app/src/main/java/com/orgzly/android/repos/ContentRepo.java
@@ -126,6 +126,13 @@ public VersionedRook retrieveBook(String fileName, File destinationFile) throws
return new VersionedRook(repoId, RepoType.DOCUMENT, repoUri, sourceFile.getUri(), rev, mtime);
}
+ @Override
+ public InputStream openRepoFileInputStream(String fileName) throws IOException {
+ DocumentFile sourceFile = repoDocumentFile.findFile(fileName);
+ if (sourceFile == null) throw new FileNotFoundException();
+ return context.getContentResolver().openInputStream(sourceFile.getUri());
+ }
+
@Override
public VersionedRook storeBook(File file, String fileName) throws IOException {
if (!file.exists()) {
diff --git a/app/src/main/java/com/orgzly/android/repos/DatabaseRepo.java b/app/src/main/java/com/orgzly/android/repos/DatabaseRepo.java
index 9a2c5554f..7d371a12e 100644
--- a/app/src/main/java/com/orgzly/android/repos/DatabaseRepo.java
+++ b/app/src/main/java/com/orgzly/android/repos/DatabaseRepo.java
@@ -8,6 +8,7 @@
import java.io.File;
import java.io.IOException;
+import java.io.InputStream;
import java.util.List;
/**
@@ -52,6 +53,11 @@ public VersionedRook retrieveBook(String fileName, File file) {
return dbRepo.retrieveBook(repoId, repoUri, uri, file);
}
+ @Override
+ public InputStream openRepoFileInputStream(String fileName) throws IOException {
+ throw new UnsupportedOperationException("Not implemented");
+ }
+
@Override
public VersionedRook storeBook(File file, String fileName) throws IOException {
String content = MiscUtils.readStringFromFile(file);
diff --git a/app/src/main/java/com/orgzly/android/repos/DirectoryRepo.java b/app/src/main/java/com/orgzly/android/repos/DirectoryRepo.java
index bac17240c..c623b9fd6 100644
--- a/app/src/main/java/com/orgzly/android/repos/DirectoryRepo.java
+++ b/app/src/main/java/com/orgzly/android/repos/DirectoryRepo.java
@@ -12,8 +12,10 @@
import org.jetbrains.annotations.NotNull;
import java.io.File;
+import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
+import java.io.InputStream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
@@ -130,6 +132,11 @@ public VersionedRook retrieveBook(String fileName, File destinationFile) throws
return new VersionedRook(repoId, RepoType.DIRECTORY, repoUri, uri, rev, mtime);
}
+ @Override
+ public InputStream openRepoFileInputStream(String fileName) throws IOException {
+ return new FileInputStream(repoUri.buildUpon().appendPath(fileName).build().getPath());
+ }
+
@Override
public VersionedRook storeBook(File file, String fileName) throws IOException {
if (!file.exists()) {
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 e0fdbe082..12482fadf 100644
--- a/app/src/main/java/com/orgzly/android/repos/DropboxClient.java
+++ b/app/src/main/java/com/orgzly/android/repos/DropboxClient.java
@@ -4,6 +4,7 @@
import android.content.Context;
import android.net.Uri;
+import com.dropbox.core.DbxDownloader;
import com.dropbox.core.DbxException;
import com.dropbox.core.DbxRequestConfig;
import com.dropbox.core.android.Auth;
@@ -25,6 +26,7 @@
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileInputStream;
+import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
@@ -233,6 +235,29 @@ public VersionedRook download(Uri repoUri, String fileName, File localFile) thro
}
}
+ public InputStream streamFile(Uri repoUri, String fileName) throws IOException {
+ linkedOrThrow();
+
+ Uri uri = repoUri.buildUpon().appendPath(fileName).build();
+ FileMetadata metadata;
+ String rev;
+ DbxDownloader downloader;
+
+ try {
+ Metadata pathMetadata = dbxClient.files().getMetadata(uri.getPath());
+ metadata = (FileMetadata) pathMetadata;
+ rev = metadata.getRev();
+ downloader = dbxClient.files().download(metadata.getPathLower(), rev);
+ } catch (DbxException e) {
+ if (e instanceof GetMetadataErrorException) {
+ if (((GetMetadataErrorException) e).errorValue.getPathValue() == LookupError.NOT_FOUND) {
+ throw new FileNotFoundException();
+ }
+ }
+ throw new RuntimeException(e);
+ }
+ return downloader.getInputStream();
+ }
/** Upload file to Dropbox. */
public VersionedRook upload(File file, Uri repoUri, String fileName) throws IOException {
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 484da45a6..402ca1594 100644
--- a/app/src/main/java/com/orgzly/android/repos/DropboxRepo.java
+++ b/app/src/main/java/com/orgzly/android/repos/DropboxRepo.java
@@ -8,6 +8,7 @@
import java.io.File;
import java.io.IOException;
+import java.io.InputStream;
import java.util.List;
public class DropboxRepo implements SyncRepo {
@@ -46,6 +47,11 @@ public VersionedRook retrieveBook(String fileName, File file) throws IOException
return client.download(repoUri, fileName, file);
}
+ @Override
+ public InputStream openRepoFileInputStream(String fileName) throws IOException {
+ return client.streamFile(repoUri, fileName);
+ }
+
@Override
public VersionedRook storeBook(File file, String fileName) throws IOException {
return client.upload(file, repoUri, fileName);
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 3d60b5cf6..2e1f31225 100644
--- a/app/src/main/java/com/orgzly/android/repos/GitRepo.java
+++ b/app/src/main/java/com/orgzly/android/repos/GitRepo.java
@@ -38,6 +38,7 @@
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
+import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;
@@ -221,6 +222,12 @@ public VersionedRook retrieveBook(String fileName, File destination) throws IOEx
return currentVersionedRook(sourceUri);
}
+ @Override
+ public InputStream openRepoFileInputStream(String fileName) throws IOException {
+ Uri sourceUri = Uri.parse(fileName);
+ return synchronizer.openRepoFileInputStream(sourceUri.getPath());
+ }
+
private VersionedRook currentVersionedRook(Uri uri) {
RevCommit commit = null;
uri = Uri.parse(Uri.decode(uri.toString()));
diff --git a/app/src/main/java/com/orgzly/android/repos/MockRepo.java b/app/src/main/java/com/orgzly/android/repos/MockRepo.java
index a6fca9e8d..63c6f93e5 100644
--- a/app/src/main/java/com/orgzly/android/repos/MockRepo.java
+++ b/app/src/main/java/com/orgzly/android/repos/MockRepo.java
@@ -8,6 +8,7 @@
import java.io.File;
import java.io.IOException;
+import java.io.InputStream;
import java.util.List;
/**
@@ -56,6 +57,11 @@ public VersionedRook retrieveBook(String fileName, File file) throws IOException
return databaseRepo.retrieveBook(fileName, file);
}
+ @Override
+ public InputStream openRepoFileInputStream(String fileName) throws IOException {
+ throw new UnsupportedOperationException("Not implemented");
+ }
+
@Override
public VersionedRook storeBook(File file, String fileName) throws IOException {
SystemClock.sleep(SLEEP_FOR_STORE_BOOK);
diff --git a/app/src/main/java/com/orgzly/android/repos/RepoIgnoreNode.kt b/app/src/main/java/com/orgzly/android/repos/RepoIgnoreNode.kt
new file mode 100644
index 000000000..d8fe16411
--- /dev/null
+++ b/app/src/main/java/com/orgzly/android/repos/RepoIgnoreNode.kt
@@ -0,0 +1,61 @@
+package com.orgzly.android.repos
+
+import android.os.Build
+import androidx.annotation.RequiresApi
+import com.orgzly.R
+import com.orgzly.android.App
+import org.eclipse.jgit.ignore.IgnoreNode
+import java.io.FileNotFoundException
+import java.io.IOException
+import kotlin.io.path.Path
+
+class RepoIgnoreNode(repo: SyncRepo) : IgnoreNode() {
+
+ init {
+ try {
+ val inputStream = repo.openRepoFileInputStream(IGNORE_FILE)
+ inputStream.use {
+ parse(it)
+ }
+ inputStream.close()
+ } catch (ignored: FileNotFoundException) {}
+ }
+
+ @RequiresApi(Build.VERSION_CODES.O)
+ fun isPathIgnored(pathString: String, isDirectory: Boolean): Boolean {
+ if (rules.isEmpty()) {
+ return false
+ }
+ val path = Path(pathString)
+ return when (isIgnored(pathString, isDirectory)) {
+ MatchResult.IGNORED ->
+ true
+ MatchResult.NOT_IGNORED ->
+ false
+ MatchResult.CHECK_PARENT ->
+ if (path.parent != null) {
+ // Recursive call
+ isPathIgnored(path.parent.toString(), true)
+ } else {
+ false
+ }
+ else -> false
+ }
+ }
+
+ @RequiresApi(Build.VERSION_CODES.O)
+ fun ensureFileNameIsNotIgnored(filePath: String) {
+ if (isPathIgnored(filePath, false)) {
+ throw IOException(
+ App.getAppContext().getString(
+ R.string.error_file_matches_repo_ignore_rule,
+ IGNORE_FILE,
+ )
+ )
+ }
+ }
+
+ companion object {
+ const val IGNORE_FILE = ".orgzlyignore"
+ }
+}
diff --git a/app/src/main/java/com/orgzly/android/repos/SyncRepo.java b/app/src/main/java/com/orgzly/android/repos/SyncRepo.java
index 6b2a7301b..ed22ef2d1 100644
--- a/app/src/main/java/com/orgzly/android/repos/SyncRepo.java
+++ b/app/src/main/java/com/orgzly/android/repos/SyncRepo.java
@@ -4,6 +4,7 @@
import java.io.File;
import java.io.IOException;
+import java.io.InputStream;
import java.util.List;
/**
@@ -32,6 +33,14 @@ public interface SyncRepo {
*/
VersionedRook retrieveBook(String fileName, File destination) throws IOException;
+ /**
+ * Open a file in the repository for reading. Originally added for parsing the .orgzlyignore
+ * file.
+ * @param fileName The file to open
+ * @throws IOException
+ */
+ InputStream openRepoFileInputStream(String fileName) throws IOException;
+
/**
* Uploads book storing it under given filename under repo's url.
* @param file The contents of this file should be stored at the remote location/repo
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 e8bcd01e5..bc098a768 100644
--- a/app/src/main/java/com/orgzly/android/repos/WebdavRepo.kt
+++ b/app/src/main/java/com/orgzly/android/repos/WebdavRepo.kt
@@ -15,6 +15,7 @@ import com.thegrizzlylabs.sardineandroid.impl.OkHttpSardine
import okhttp3.OkHttpClient
import okio.Buffer
import java.io.File
+import java.io.FileNotFoundException
import java.io.FileOutputStream
import java.io.InputStream
import java.security.KeyStore
@@ -186,6 +187,13 @@ class WebdavRepo(
return sardine.list(fileUrl).first().toVersionedRook()
}
+ override fun openRepoFileInputStream(fileName: String): InputStream {
+ val fileUrl = Uri.withAppendedPath(uri, fileName).toUrl()
+ if (!sardine.exists(fileUrl))
+ throw FileNotFoundException()
+ return sardine.get(fileUrl)
+ }
+
override fun storeBook(file: File?, fileName: String?): VersionedRook {
val fileUrl = Uri.withAppendedPath(uri, fileName).toUrl()
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 375f73245..e67fec24d 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -770,7 +770,7 @@
Error while trying to generate the SSH key pair
Message: \n
Error: SSH key can only be unlocked from an activity
- Repository filename matches a rule in %s
+ Error: Repository file name matches a rule in %s
No change
From 7bb7a5421b4a6f83f52a148908a817bcc15faebe Mon Sep 17 00:00:00 2001
From: Victor Andreasson
Date: Sun, 9 Jun 2024 01:07:10 +0200
Subject: [PATCH 06/10] Apply the ignore rules in .orgzlyignore during
SyncRepo.getBooks()
I also speeded up the Git file tree walk. (We were performing similar
checks two times -- first in the TreeFilter.include() method override,
and then later in the actual processing of interesting nodes. I have
concluded that we can filter out all uninteresting nodes in one place.)
---
.../com/orgzly/android/repos/ContentRepo.java | 9 +++++++-
.../orgzly/android/repos/DirectoryRepo.java | 16 ++++++++++++--
.../orgzly/android/repos/DropboxClient.java | 9 +++++++-
.../com/orgzly/android/repos/DropboxRepo.java | 3 ++-
.../com/orgzly/android/repos/GitRepo.java | 22 ++++++++-----------
.../com/orgzly/android/repos/WebdavRepo.kt | 18 +++++++++++----
6 files changed, 55 insertions(+), 22 deletions(-)
diff --git a/app/src/main/java/com/orgzly/android/repos/ContentRepo.java b/app/src/main/java/com/orgzly/android/repos/ContentRepo.java
index 50e94eaa3..ef87d2ac2 100644
--- a/app/src/main/java/com/orgzly/android/repos/ContentRepo.java
+++ b/app/src/main/java/com/orgzly/android/repos/ContentRepo.java
@@ -21,6 +21,7 @@
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.List;
+import java.util.Objects;
/**
* Using DocumentFile, for devices running Lollipop or later.
@@ -69,12 +70,18 @@ public List getBooks() throws IOException {
DocumentFile[] files = repoDocumentFile.listFiles();
+ RepoIgnoreNode ignores = new RepoIgnoreNode(this);
+
if (files != null) {
// Can't compare TreeDocumentFile
// Arrays.sort(files);
for (DocumentFile file : files) {
- if (BookName.isSupportedFormatFileName(file.getName())) {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
+ if (ignores.isPathIgnored(Objects.requireNonNull(file.getName()), false)) {
+ continue;
+ }
+ } if (BookName.isSupportedFormatFileName(file.getName())) {
if (BuildConfig.LOG_DEBUG) {
LogUtils.d(TAG,
diff --git a/app/src/main/java/com/orgzly/android/repos/DirectoryRepo.java b/app/src/main/java/com/orgzly/android/repos/DirectoryRepo.java
index c623b9fd6..db2b59064 100644
--- a/app/src/main/java/com/orgzly/android/repos/DirectoryRepo.java
+++ b/app/src/main/java/com/orgzly/android/repos/DirectoryRepo.java
@@ -1,6 +1,7 @@
package com.orgzly.android.repos;
import android.net.Uri;
+import android.os.Build;
import android.util.Log;
import com.orgzly.android.BookName;
@@ -83,10 +84,21 @@ public Uri getUri() {
@Override
public List getBooks() {
+ RepoIgnoreNode ignores = new RepoIgnoreNode(this);
+
List result = new ArrayList<>();
- File[] files = mDirectory.listFiles((dir, filename) ->
- BookName.isSupportedFormatFileName(filename));
+ File[] files;
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
+ files = mDirectory.listFiles(
+ (dir, filename) -> BookName.isSupportedFormatFileName(filename)
+ && !ignores.isPathIgnored(filename, false)
+ );
+ } else {
+ files = mDirectory.listFiles(
+ (dir, filename) -> BookName.isSupportedFormatFileName(filename)
+ );
+ }
if (files != null) {
Arrays.sort(files);
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 12482fadf..940039415 100644
--- a/app/src/main/java/com/orgzly/android/repos/DropboxClient.java
+++ b/app/src/main/java/com/orgzly/android/repos/DropboxClient.java
@@ -3,6 +3,7 @@
import android.app.Activity;
import android.content.Context;
import android.net.Uri;
+import android.os.Build;
import com.dropbox.core.DbxDownloader;
import com.dropbox.core.DbxException;
@@ -129,7 +130,7 @@ private void deleteCredential() {
AppPreferences.dropboxSerializedCredential(mContext, null);
}
- public List getBooks(Uri repoUri) throws IOException {
+ public List getBooks(Uri repoUri, RepoIgnoreNode ignores) throws IOException {
linkedOrThrow();
List list = new ArrayList<>();
@@ -153,6 +154,12 @@ public List getBooks(Uri repoUri) throws IOException {
if (metadata instanceof FileMetadata) {
FileMetadata file = (FileMetadata) metadata;
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
+ if (ignores.isPathIgnored(file.getName(), false)) {
+ continue;
+ }
+ }
+
if (BookName.isSupportedFormatFileName(file.getName())) {
Uri uri = repoUri.buildUpon().appendPath(file.getName()).build();
VersionedRook book = new VersionedRook(
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 402ca1594..74dd25a65 100644
--- a/app/src/main/java/com/orgzly/android/repos/DropboxRepo.java
+++ b/app/src/main/java/com/orgzly/android/repos/DropboxRepo.java
@@ -39,7 +39,8 @@ public Uri getUri() {
@Override
public List getBooks() throws IOException {
- return client.getBooks(repoUri);
+ RepoIgnoreNode ignores = new RepoIgnoreNode(this);
+ return client.getBooks(repoUri, ignores);
}
@Override
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 2e1f31225..0b18bfa85 100644
--- a/app/src/main/java/com/orgzly/android/repos/GitRepo.java
+++ b/app/src/main/java/com/orgzly/android/repos/GitRepo.java
@@ -291,14 +291,18 @@ public List getBooks() throws IOException {
walk.reset();
walk.setRecursive(true);
walk.addTree(synchronizer.currentHead().getTree());
- final IgnoreNode ignores = getIgnores();
+ final RepoIgnoreNode ignores = new RepoIgnoreNode(this);
walk.setFilter(new TreeFilter() {
@Override
public boolean include(TreeWalk walker) {
- final FileMode mode = walker.getFileMode(0);
- final String filePath = walker.getPathString();
+ final FileMode mode = walk.getFileMode();
final boolean isDirectory = mode == FileMode.TREE;
- return !(ignores.isIgnored(filePath, isDirectory) == IgnoreNode.MatchResult.IGNORED);
+ final String filePath = walk.getPathString();
+ if (ignores.isIgnored(filePath, isDirectory) == IgnoreNode.MatchResult.IGNORED)
+ return false;
+ if (isDirectory)
+ return true;
+ return BookName.isSupportedFormatFileName(filePath);
}
@Override
@@ -312,15 +316,7 @@ public TreeFilter clone() {
}
});
while (walk.next()) {
- final FileMode mode = walk.getFileMode(0);
- final boolean isDirectory = mode == FileMode.TREE;
- final String filePath = walk.getPathString();
- if (isDirectory)
- continue;
- if (BookName.isSupportedFormatFileName(filePath))
- result.add(
- currentVersionedRook(
- Uri.withAppendedPath(Uri.EMPTY, walk.getPathString())));
+ result.add(currentVersionedRook(Uri.withAppendedPath(Uri.EMPTY, walk.getPathString())));
}
return result;
}
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 bc098a768..da62353a2 100644
--- a/app/src/main/java/com/orgzly/android/repos/WebdavRepo.kt
+++ b/app/src/main/java/com/orgzly/android/repos/WebdavRepo.kt
@@ -1,6 +1,7 @@
package com.orgzly.android.repos
import android.net.Uri
+import android.os.Build
import com.burgstaller.okhttp.AuthenticationCacheInterceptor
import com.burgstaller.okhttp.CachingAuthenticatorDecorator
import com.burgstaller.okhttp.DispatchingAuthenticator
@@ -26,7 +27,6 @@ import java.util.concurrent.TimeUnit
import javax.net.ssl.SSLContext
import javax.net.ssl.TrustManagerFactory
import javax.net.ssl.X509TrustManager
-import kotlin.time.Duration
class WebdavRepo(
@@ -163,13 +163,23 @@ class WebdavRepo(
sardine.createDirectory(url)
}
+ val ignores = RepoIgnoreNode(this)
+
return sardine
.list(url)
.mapNotNull {
- if (it.isDirectory || !BookName.isSupportedFormatFileName(it.name)) {
- null
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
+ if (it.isDirectory || !BookName.isSupportedFormatFileName(it.name) || ignores.isPathIgnored(it.name, false)) {
+ null
+ } else {
+ it.toVersionedRook()
+ }
} else {
- it.toVersionedRook()
+ if (it.isDirectory || !BookName.isSupportedFormatFileName(it.name)) {
+ null
+ } else {
+ it.toVersionedRook()
+ }
}
}
.toMutableList()
From 36b24514f4b67c102e5e13764a548c3a597751d0 Mon Sep 17 00:00:00 2001
From: Victor Andreasson
Date: Sun, 9 Jun 2024 01:12:57 +0200
Subject: [PATCH 07/10] Always check ignore rules when linking and renaming
books
---
.../com/orgzly/android/data/DataRepository.kt | 14 ++++++-
.../com/orgzly/android/repos/GitRepo.java | 39 ++-----------------
.../com/orgzly/android/repos/RepoUtils.java | 9 +++++
.../java/com/orgzly/android/sync/SyncUtils.kt | 2 +
4 files changed, 27 insertions(+), 37 deletions(-)
diff --git a/app/src/main/java/com/orgzly/android/data/DataRepository.kt b/app/src/main/java/com/orgzly/android/data/DataRepository.kt
index 5b8067594..a4f08fcbf 100644
--- a/app/src/main/java/com/orgzly/android/data/DataRepository.kt
+++ b/app/src/main/java/com/orgzly/android/data/DataRepository.kt
@@ -6,6 +6,7 @@ import android.content.Intent
import android.content.res.Resources
import android.media.MediaScannerConnection
import android.net.Uri
+import android.os.Build
import android.os.Handler
import android.text.TextUtils
import androidx.lifecycle.LiveData
@@ -52,7 +53,6 @@ import com.orgzly.org.parser.OrgParser
import com.orgzly.org.parser.OrgParserWriter
import com.orgzly.org.utils.StateChangeLogic
import java.io.*
-import java.lang.IllegalStateException
import java.util.*
import java.util.concurrent.Callable
import javax.inject.Inject
@@ -384,6 +384,10 @@ class DataRepository @Inject constructor(
bookView.syncedTo?.let { vrook ->
val repo = getRepoInstance(vrook.repoId, vrook.repoType, vrook.repoUri.toString())
+ /* Do not rename if the new filename will be ignored */
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O)
+ RepoUtils.ensureFileNameIsNotIgnored(repo, BookName.fileName(name, BookFormat.ORG))
+
val movedVrook = repo.renameBook(vrook.uri, name)
updateBookLinkAndSync(book.id, movedVrook)
@@ -505,6 +509,14 @@ class DataRepository @Inject constructor(
"Repo ${repo.url} not found"
}.id
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
+ // Ensure that the resulting file name is not ignored in this repo
+ val syncRepo = getRepoInstance(repo.id, repo.type, repo.url)
+ val bookName = getBook(bookId)!!.name
+ val fileName = BookName.fileName(bookName, BookFormat.ORG)
+ RepoUtils.ensureFileNameIsNotIgnored(syncRepo, fileName)
+ }
+
db.bookLink().upsert(bookId, repoId)
}
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 0b18bfa85..4bd759889 100644
--- a/app/src/main/java/com/orgzly/android/repos/GitRepo.java
+++ b/app/src/main/java/com/orgzly/android/repos/GitRepo.java
@@ -7,8 +7,7 @@
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;
import com.orgzly.android.git.GitFileSynchronizer;
@@ -35,7 +34,6 @@
import org.eclipse.jgit.util.FileUtils;
import java.io.File;
-import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
@@ -45,7 +43,6 @@
public class GitRepo implements SyncRepo, TwoWaySyncRepo {
private final static String TAG = GitRepo.class.getName();
private final long repoId;
- private final Context context = App.getAppContext();
/**
* Used as cause when we try to clone into a non-empty directory
@@ -189,7 +186,6 @@ public boolean isAutoSyncSupported() {
}
public VersionedRook storeBook(File file, String fileName) throws IOException {
- ensureFileNameIsNotIgnored(fileName);
File destination = synchronizer.repoDirectoryFile(fileName);
if (destination.exists()) {
@@ -241,34 +237,6 @@ private VersionedRook currentVersionedRook(Uri uri) {
return new VersionedRook(repoId, RepoType.GIT, getUri(), uri, commit.name(), mtime);
}
- private IgnoreNode getIgnores() throws IOException {
- IgnoreNode ignores = new IgnoreNode();
- File ignoreFile = synchronizer.repoDirectoryFile(context.getString(R.string.repo_ignore_rules_file));
- if (ignoreFile.exists()) {
- FileInputStream in = new FileInputStream(ignoreFile);
- try {
- ignores.parse(in);
- } finally {
- in.close();
- }
- }
- return ignores;
- }
-
- /**
- * Since subdirectories are currently not supported, we only check the file name against the
- * ignore rules.
- * @param fileName Name of the file which the user tries to write to
- * @throws IOException
- */
- private void ensureFileNameIsNotIgnored(String fileName) throws IOException {
- IgnoreNode ignores = getIgnores();
- if (ignores.isIgnored(fileName, false) == IgnoreNode.MatchResult.IGNORED) {
- throw new IOException(context.getString(R.string.error_file_matches_repo_ignore_rule,
- context.getString(R.string.repo_ignore_rules_file)));
- }
- }
-
public boolean isUnchanged() throws IOException {
// Check if the current head is unchanged.
// If so, we can read all the VersionedRooks from the database.
@@ -329,10 +297,9 @@ public void delete(Uri uri) throws IOException {
if (synchronizer.deleteFileFromRepo(uri)) synchronizer.tryPush();
}
- public VersionedRook renameBook(Uri oldUri, String newRookName) throws IOException {
+ public VersionedRook renameBook(Uri oldUri, String newBookName) throws IOException {
String oldFileName = oldUri.toString().replaceFirst("^/", "");
- String newFileName = newRookName + ".org";
- ensureFileNameIsNotIgnored(newFileName);
+ String newFileName = BookName.fileName(newBookName, BookFormat.ORG);
if (synchronizer.renameFileInRepo(oldFileName, newFileName)) {
synchronizer.tryPush();
return currentVersionedRook(Uri.EMPTY.buildUpon().appendPath(newFileName).build());
diff --git a/app/src/main/java/com/orgzly/android/repos/RepoUtils.java b/app/src/main/java/com/orgzly/android/repos/RepoUtils.java
index 1b1a73569..639e77cc2 100644
--- a/app/src/main/java/com/orgzly/android/repos/RepoUtils.java
+++ b/app/src/main/java/com/orgzly/android/repos/RepoUtils.java
@@ -1,5 +1,9 @@
package com.orgzly.android.repos;
+import android.os.Build;
+
+import androidx.annotation.RequiresApi;
+
import java.util.Collection;
public class RepoUtils {
@@ -26,5 +30,10 @@ public static boolean isAutoSyncSupported(Collection repos) {
}
return true;
}
+
+ @RequiresApi(api = Build.VERSION_CODES.O)
+ public static void ensureFileNameIsNotIgnored(SyncRepo repo, String fileName) {
+ new RepoIgnoreNode(repo).ensureFileNameIsNotIgnored(fileName);
+ }
}
diff --git a/app/src/main/java/com/orgzly/android/sync/SyncUtils.kt b/app/src/main/java/com/orgzly/android/sync/SyncUtils.kt
index 8d36f1873..714592a11 100644
--- a/app/src/main/java/com/orgzly/android/sync/SyncUtils.kt
+++ b/app/src/main/java/com/orgzly/android/sync/SyncUtils.kt
@@ -157,6 +157,8 @@ object SyncUtils {
repoEntity = dataRepository.getRepos().iterator().next()
repoUrl = repoEntity.url
fileName = BookName.fileName(namesake.book.book.name, BookFormat.ORG)
+ /* Set repo link before saving to ensure repo ignore rules are checked */
+ dataRepository.setLink(namesake.book.book.id, repoEntity)
dataRepository.saveBookToRepo(repoEntity, fileName, namesake.book, BookFormat.ORG)
bookAction = BookAction.forNow(BookAction.Type.INFO, namesake.status.msg(repoUrl))
}
From 453b085b876ec6a068d8ca8b42502cd7d1945dba Mon Sep 17 00:00:00 2001
From: Victor Andreasson
Date: Sun, 9 Jun 2024 01:14:20 +0200
Subject: [PATCH 08/10] Add/update tests for .orgzlyignore support
---
.../java/com/orgzly/android/TestUtils.java | 31 ++++
.../orgzly/android/espresso/SyncingTest.java | 12 +-
.../android/repos/DirectoryRepoTest.java | 23 +++
.../orgzly/android/repos/DropboxRepoTest.java | 80 +++++++--
.../com/orgzly/android/repos/GitRepoTest.kt | 152 ++++++++++++++++++
.../android/repos/RepoIgnoreNodeTest.kt | 47 ++++++
.../git/GitPreferencesFromRepoPrefs.java | 16 +-
.../orgzly/android/prefs/AppPreferences.java | 4 +
.../com/orgzly/android/repos/MockRepo.java | 3 +-
9 files changed, 338 insertions(+), 30 deletions(-)
create mode 100644 app/src/androidTest/java/com/orgzly/android/repos/GitRepoTest.kt
create mode 100644 app/src/androidTest/java/com/orgzly/android/repos/RepoIgnoreNodeTest.kt
diff --git a/app/src/androidTest/java/com/orgzly/android/TestUtils.java b/app/src/androidTest/java/com/orgzly/android/TestUtils.java
index 297f3edd9..07eb863a3 100644
--- a/app/src/androidTest/java/com/orgzly/android/TestUtils.java
+++ b/app/src/androidTest/java/com/orgzly/android/TestUtils.java
@@ -5,11 +5,13 @@
import android.net.Uri;
+import com.orgzly.BuildConfig;
import com.orgzly.android.data.DataRepository;
import com.orgzly.android.data.DbRepoBookRepository;
import com.orgzly.android.db.entity.BookAction;
import com.orgzly.android.db.entity.BookView;
import com.orgzly.android.db.entity.Repo;
+import com.orgzly.android.prefs.AppPreferences;
import com.orgzly.android.repos.RepoType;
import com.orgzly.android.repos.RepoWithProps;
import com.orgzly.android.repos.SyncRepo;
@@ -18,6 +20,10 @@
import com.orgzly.android.sync.SyncUtils;
import com.orgzly.android.util.MiscUtils;
+import org.json.JSONException;
+import org.json.JSONObject;
+import org.junit.Assume;
+
import java.io.File;
import java.io.IOException;
import java.util.Map;
@@ -149,4 +155,29 @@ public Map sync() {
return null;
}
+
+ public Map syncOrThrow() throws Exception {
+ Map nameGroups = SyncUtils.groupAllNotebooksByName(dataRepository);
+
+ for (BookNamesake group : nameGroups.values()) {
+ BookAction action = SyncUtils.syncNamesake(dataRepository, group);
+ dataRepository.setBookLastActionAndSyncStatus(
+ group.getBook().getBook().getId(), action, group.getStatus().toString());
+ }
+
+ return nameGroups;
+ }
+
+ public void dropboxTestPreflight() throws JSONException {
+ Assume.assumeTrue(BuildConfig.IS_DROPBOX_ENABLED);
+ Assume.assumeTrue(BuildConfig.DROPBOX_APP_KEY.length() > 0);
+ Assume.assumeTrue(BuildConfig.DROPBOX_REFRESH_TOKEN.length() > 0);
+
+ JSONObject mockSerializedDbxCredential = new JSONObject();
+ mockSerializedDbxCredential.put("access_token", "dummy");
+ mockSerializedDbxCredential.put("expires_at", System.currentTimeMillis());
+ mockSerializedDbxCredential.put("refresh_token", BuildConfig.DROPBOX_REFRESH_TOKEN);
+ mockSerializedDbxCredential.put("app_key", BuildConfig.DROPBOX_APP_KEY);
+ AppPreferences.dropboxSerializedCredential(App.getAppContext(), mockSerializedDbxCredential.toString());
+ }
}
diff --git a/app/src/androidTest/java/com/orgzly/android/espresso/SyncingTest.java b/app/src/androidTest/java/com/orgzly/android/espresso/SyncingTest.java
index 71290172e..d82bdf1ec 100644
--- a/app/src/androidTest/java/com/orgzly/android/espresso/SyncingTest.java
+++ b/app/src/androidTest/java/com/orgzly/android/espresso/SyncingTest.java
@@ -35,7 +35,6 @@
import androidx.test.core.app.ActivityScenario;
-import com.orgzly.BuildConfig;
import com.orgzly.R;
import com.orgzly.android.OrgzlyTest;
import com.orgzly.android.RetryTestRule;
@@ -45,9 +44,9 @@
import com.orgzly.android.sync.SyncRunner;
import com.orgzly.android.ui.main.MainActivity;
+import org.json.JSONException;
import org.junit.After;
import org.junit.Assert;
-import org.junit.Assume;
import org.junit.Rule;
import org.junit.Test;
@@ -659,9 +658,8 @@ public void testEncodingAfterSyncSaving() {
}
@Test
- public void testSettingLinkToRenamedRepo() {
- Assume.assumeTrue(BuildConfig.IS_DROPBOX_ENABLED);
-
+ public void testSettingLinkToRenamedRepo() throws JSONException {
+ testUtils.dropboxTestPreflight();
Repo repo = testUtils.setupRepo(RepoType.MOCK, "mock://repo-a");
testUtils.setupRook(repo, "mock://repo-a/booky.org", "Täht", "1abcde", 1400067156000L);
scenario = ActivityScenario.launch(MainActivity.class);
@@ -715,8 +713,8 @@ public void testSettingLinkToRenamedRepo() {
}
@Test
- public void testRenamingReposRemovesLinksWhatUsedThem() {
- Assume.assumeTrue(BuildConfig.IS_DROPBOX_ENABLED);
+ public void testRenamingReposRemovesLinksWhatUsedThem() throws JSONException {
+ testUtils.dropboxTestPreflight();
testUtils.setupRepo(RepoType.MOCK, "mock://repo-a");
testUtils.setupRepo(RepoType.MOCK, "mock://repo-b");
diff --git a/app/src/androidTest/java/com/orgzly/android/repos/DirectoryRepoTest.java b/app/src/androidTest/java/com/orgzly/android/repos/DirectoryRepoTest.java
index d2aaf23fd..60133d30c 100644
--- a/app/src/androidTest/java/com/orgzly/android/repos/DirectoryRepoTest.java
+++ b/app/src/androidTest/java/com/orgzly/android/repos/DirectoryRepoTest.java
@@ -1,5 +1,6 @@
package com.orgzly.android.repos;
+import android.os.Build;
import android.os.Environment;
import com.orgzly.android.BookName;
@@ -20,6 +21,7 @@
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
+import static org.junit.Assume.assumeTrue;
public class DirectoryRepoTest extends OrgzlyTest {
private static final String TAG = DirectoryRepoTest.class.getName();
@@ -84,6 +86,27 @@ public void testExtension() throws IOException {
assertEquals(repoUriString + "/03.org", books.get(0).getUri().toString());
}
+ @Test
+ public void testGetBooksRespectsIgnoreRules() throws IOException {
+ assumeTrue(Build.VERSION.SDK_INT >= Build.VERSION_CODES.O);
+ RepoWithProps repoWithProps = new RepoWithProps(new Repo(13, RepoType.DIRECTORY, repoUriString));
+ DirectoryRepo repo = new DirectoryRepo(repoWithProps, true);
+
+ // Add .org files
+ MiscUtils.writeStringToFile("content", new File(dirFile, "file1.org"));
+ MiscUtils.writeStringToFile("content", new File(dirFile, "file2.org"));
+ MiscUtils.writeStringToFile("content", new File(dirFile, "file3.org"));
+
+ // Add .orgzlyignore file
+ MiscUtils.writeStringToFile("*1.org\nfile3*", new File(dirFile, RepoIgnoreNode.IGNORE_FILE));
+
+ List books = repo.getBooks();
+
+ assertEquals(1, books.size());
+ assertEquals("file2", BookName.getInstance(context, books.get(0)).getName());
+ assertEquals(repoUriString + "/file2.org", books.get(0).getUri().toString());
+ }
+
@Test
public void testListDownloadsDirectory() throws IOException {
File dir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS);
diff --git a/app/src/androidTest/java/com/orgzly/android/repos/DropboxRepoTest.java b/app/src/androidTest/java/com/orgzly/android/repos/DropboxRepoTest.java
index 90f9bbe4b..38a9ba6e0 100644
--- a/app/src/androidTest/java/com/orgzly/android/repos/DropboxRepoTest.java
+++ b/app/src/androidTest/java/com/orgzly/android/repos/DropboxRepoTest.java
@@ -1,25 +1,26 @@
package com.orgzly.android.repos;
-import com.orgzly.BuildConfig;
+import com.orgzly.android.App;
import com.orgzly.android.BookName;
import com.orgzly.android.OrgzlyTest;
import com.orgzly.android.db.entity.BookView;
-import com.orgzly.android.prefs.AppPreferences;
import com.orgzly.android.util.MiscUtils;
-import org.json.JSONObject;
-import org.junit.Assume;
import org.junit.Before;
+import org.junit.Rule;
import org.junit.Test;
+import org.junit.rules.ExpectedException;
import java.io.File;
import java.io.IOException;
+import java.util.List;
import java.util.UUID;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
-import android.os.SystemClock;
+import android.net.Uri;
public class DropboxRepoTest extends OrgzlyTest {
private static final String DROPBOX_TEST_DIR = "/orgzly-android-tests";
@@ -27,18 +28,12 @@ public class DropboxRepoTest extends OrgzlyTest {
@Before
public void setUp() throws Exception {
super.setUp();
- Assume.assumeTrue(BuildConfig.IS_DROPBOX_ENABLED);
- Assume.assumeTrue(BuildConfig.DROPBOX_APP_KEY.length() > 0);
- Assume.assumeTrue(BuildConfig.DROPBOX_REFRESH_TOKEN.length() > 0);
-
- JSONObject mockSerializedDbxCredential = new JSONObject();
- mockSerializedDbxCredential.put("access_token", "dummy");
- mockSerializedDbxCredential.put("expires_at", System.currentTimeMillis());
- mockSerializedDbxCredential.put("refresh_token", BuildConfig.DROPBOX_REFRESH_TOKEN);
- mockSerializedDbxCredential.put("app_key", BuildConfig.DROPBOX_APP_KEY);
- AppPreferences.dropboxSerializedCredential(context, mockSerializedDbxCredential.toString());
+ testUtils.dropboxTestPreflight();
}
+ @Rule
+ public ExpectedException exceptionRule = ExpectedException.none();
+
@Test
public void testUrl() {
assertEquals(
@@ -75,6 +70,53 @@ public void testRenameBook() throws IOException {
assertEquals(repoUriString + "/booky-renamed.org", bookView.getSyncedTo().getUri().toString());
}
+ @Test
+ public void testIgnoreRulePreventsLinkingBook() throws Exception {
+ Uri repoUri = testUtils.repoInstance(RepoType.DROPBOX, randomUrl()).getUri();
+ testUtils.setupRepo(RepoType.DROPBOX, repoUri.toString());
+ uploadFileToRepo(repoUri, RepoIgnoreNode.IGNORE_FILE, "*.org");
+ testUtils.setupBook("booky", "");
+ exceptionRule.expect(IOException.class);
+ exceptionRule.expectMessage("matches a rule in .orgzlyignore");
+ testUtils.syncOrThrow();
+ }
+
+ @Test
+ public void testIgnoreRulePreventsLoadingBook() throws Exception {
+ SyncRepo repo = testUtils.repoInstance(RepoType.DROPBOX, randomUrl());
+ testUtils.setupRepo(RepoType.DROPBOX, repo.getUri().toString());
+
+ // Create two .org files
+ uploadFileToRepo(repo.getUri(), "ignored.org", "1 2 3");
+ uploadFileToRepo(repo.getUri(), "notignored.org", "1 2 3");
+ // Create .orgzlyignore
+ uploadFileToRepo(repo.getUri(), RepoIgnoreNode.IGNORE_FILE, "ignored.org");
+ testUtils.sync();
+
+ List bookViews = dataRepository.getBooks();
+ assertEquals(1, bookViews.size());
+ assertEquals("notignored", bookViews.get(0).getBook().getName());
+ }
+
+ @Test
+ public void testIgnoreRulePreventsRenamingBook() throws Exception {
+ BookView bookView;
+ Uri repoUri = testUtils.repoInstance(RepoType.DROPBOX, randomUrl()).getUri();
+ testUtils.setupRepo(RepoType.DROPBOX, repoUri.toString());
+ uploadFileToRepo(repoUri, RepoIgnoreNode.IGNORE_FILE, "badname*");
+ testUtils.setupBook("goodname", "");
+ testUtils.sync();
+ bookView = dataRepository.getBookView("goodname");
+ dataRepository.renameBook(bookView, "badname");
+ bookView = dataRepository.getBooks().get(0);
+ assertTrue(
+ bookView.getBook()
+ .getLastAction()
+ .toString()
+ .contains("matches a rule in .orgzlyignore")
+ );
+ }
+
@Test
public void testDropboxFileRename() throws IOException {
SyncRepo repo = testUtils.repoInstance(RepoType.DROPBOX, randomUrl());
@@ -98,6 +140,14 @@ public void testDropboxFileRename() throws IOException {
assertEquals("notebook-renamed.org", BookName.getInstance(context, repo.getBooks().get(0)).getFileName());
}
+ private void uploadFileToRepo(Uri repoUri, String fileName, String fileContents) throws IOException {
+ DropboxClient client = new DropboxClient(App.getAppContext(), 0);
+ File tmpFile = File.createTempFile("abc", null);
+ MiscUtils.writeStringToFile(fileContents, tmpFile);
+ client.upload(tmpFile, repoUri, fileName);
+ tmpFile.delete();
+ }
+
private String randomUrl() {
return "dropbox:"+ DROPBOX_TEST_DIR + "/" + UUID.randomUUID().toString();
}
diff --git a/app/src/androidTest/java/com/orgzly/android/repos/GitRepoTest.kt b/app/src/androidTest/java/com/orgzly/android/repos/GitRepoTest.kt
new file mode 100644
index 000000000..3f4b1a260
--- /dev/null
+++ b/app/src/androidTest/java/com/orgzly/android/repos/GitRepoTest.kt
@@ -0,0 +1,152 @@
+package com.orgzly.android.repos
+
+import android.net.Uri
+import android.os.Build
+import androidx.core.net.toUri
+import com.orgzly.R
+import com.orgzly.android.OrgzlyTest
+import com.orgzly.android.db.entity.BookView
+import com.orgzly.android.db.entity.Repo
+import com.orgzly.android.git.GitFileSynchronizer
+import com.orgzly.android.git.GitPreferencesFromRepoPrefs
+import com.orgzly.android.prefs.AppPreferences
+import com.orgzly.android.prefs.RepoPreferences
+import com.orgzly.android.util.MiscUtils
+import org.eclipse.jgit.api.Git
+import org.junit.Assert.assertEquals
+import org.junit.Assert.assertTrue
+import org.junit.Assume
+import org.junit.Rule
+import org.junit.Test
+import org.junit.rules.ExpectedException
+import java.io.File
+import java.io.IOException
+import java.nio.file.Path
+import kotlin.io.path.createTempDirectory
+
+class GitRepoTest : OrgzlyTest() {
+
+ private lateinit var bareRepoPath: Path
+ private lateinit var repoUri: Uri
+ private lateinit var gitPreferences: GitPreferencesFromRepoPrefs
+ private lateinit var workingtree: File
+ private lateinit var repoPreferences: RepoPreferences
+ private lateinit var repo: Repo
+ private lateinit var syncRepo: GitRepo
+ private lateinit var git: Git
+ private lateinit var synchronizer: GitFileSynchronizer
+
+ @Rule
+ @JvmField
+ var exceptionRule: ExpectedException = ExpectedException.none()
+
+ override fun setUp() {
+ super.setUp()
+ bareRepoPath = createTempDirectory()
+ Git.init().setBare(true).setDirectory(bareRepoPath.toFile()).call()
+ AppPreferences.gitIsEnabled(context, true)
+ repoUri = bareRepoPath.toFile().toUri()
+ repo = testUtils.setupRepo(RepoType.GIT, repoUri.toString())
+ repoPreferences = RepoPreferences(context, repo.id, repoUri)
+ gitPreferences = GitPreferencesFromRepoPrefs(repoPreferences)
+ workingtree = File(gitPreferences.repositoryFilepath())
+ workingtree.mkdirs()
+ git = GitRepo.ensureRepositoryExists(gitPreferences, true, null)
+ syncRepo = dataRepository.getRepoInstance(repo.id, RepoType.GIT, repo.url) as GitRepo
+ synchronizer = GitFileSynchronizer(git, gitPreferences)
+ }
+
+ override fun tearDown() {
+ super.tearDown()
+ testUtils.deleteRepo(repo.url)
+ workingtree.deleteRecursively()
+ bareRepoPath.toFile()?.deleteRecursively()
+ }
+
+ @Test
+ fun testSyncNewBookWithoutLinkAndOneRepo() {
+ testUtils.setupBook("book1", "book content")
+ testUtils.sync()
+ val bookView = dataRepository.getBooks()[0]
+ assertEquals(repoUri.toString(), bookView.linkRepo?.url)
+ assertEquals(1, syncRepo.books.size)
+ assertEquals(bookView.syncedTo.toString(), syncRepo.books[0].toString())
+ assertEquals(context.getString(R.string.sync_status_saved, repo.url), bookView.book.lastAction!!.message)
+ assertEquals("/book1.org", bookView.syncedTo!!.uri.toString())
+ }
+
+ @Test
+ fun testIgnoredFilesInRepoAreNotLoaded() {
+ Assume.assumeTrue(Build.VERSION.SDK_INT >= Build.VERSION_CODES.O)
+ // Create ignore file in working tree and commit
+ val ignoreFileContents = """
+ ignoredbook.org
+ ignored-*.org
+ """.trimIndent()
+ addAndCommitIgnoreFile(ignoreFileContents)
+ // Add multiple files to repo
+ for (fileName in arrayOf("ignoredbook.org", "ignored-3.org", "notignored.org")) {
+ val tmpFile = File.createTempFile("orgzlytest", null)
+ MiscUtils.writeStringToFile("book content", tmpFile)
+ synchronizer.addAndCommitNewFile(tmpFile, fileName)
+ tmpFile.delete()
+ }
+ testUtils.sync()
+ assertEquals(1, syncRepo.books.size)
+ assertEquals(1, dataRepository.getBooks().size)
+ assertEquals("notignored", dataRepository.getBooks()[0].book.name)
+ }
+
+ @Test
+ fun testUnIgnoredFilesInRepoAreLoaded() {
+ Assume.assumeTrue(Build.VERSION.SDK_INT >= Build.VERSION_CODES.O)
+ // Create ignore file in working tree and commit
+ val ignoreFileContents = """
+ *.org
+ !notignored.org
+ """.trimIndent()
+ addAndCommitIgnoreFile(ignoreFileContents)
+ // Add multiple files to repo
+ for (fileName in arrayOf("ignoredbook.org", "ignored-3.org", "notignored.org")) {
+ val tmpFile = File.createTempFile("orgzlytest", null)
+ MiscUtils.writeStringToFile("book content", tmpFile)
+ synchronizer.addAndCommitNewFile(tmpFile, fileName)
+ tmpFile.delete()
+ }
+ testUtils.sync()
+ assertEquals(1, syncRepo.books.size)
+ assertEquals(1, dataRepository.getBooks().size)
+ assertEquals("notignored", dataRepository.getBooks()[0].book.name)
+ }
+
+ @Test
+ fun testIgnoreRulePreventsLinkingBook() {
+ Assume.assumeTrue(Build.VERSION.SDK_INT >= Build.VERSION_CODES.O)
+ addAndCommitIgnoreFile("*.org")
+ testUtils.setupBook("booky", "")
+ exceptionRule.expect(IOException::class.java)
+ exceptionRule.expectMessage("matches a rule in .orgzlyignore")
+ testUtils.syncOrThrow()
+ }
+
+ @Test
+ fun testIgnoreRulePreventsRenamingBook() {
+ Assume.assumeTrue(Build.VERSION.SDK_INT >= Build.VERSION_CODES.O)
+ addAndCommitIgnoreFile("badname*")
+ testUtils.setupBook("goodname", "")
+ testUtils.sync()
+ var bookView: BookView? = dataRepository.getBookView("goodname")
+ dataRepository.renameBook(bookView!!, "badname")
+ bookView = dataRepository.getBooks()[0]
+ assertTrue(
+ bookView.book.lastAction.toString().contains("matches a rule in .orgzlyignore")
+ )
+ }
+
+ private fun addAndCommitIgnoreFile(contents: String) {
+ val tmpFile = File.createTempFile("orgzlytest", null)
+ MiscUtils.writeStringToFile(contents, tmpFile)
+ synchronizer.addAndCommitNewFile(tmpFile, RepoIgnoreNode.IGNORE_FILE)
+ tmpFile.delete()
+ }
+}
\ No newline at end of file
diff --git a/app/src/androidTest/java/com/orgzly/android/repos/RepoIgnoreNodeTest.kt b/app/src/androidTest/java/com/orgzly/android/repos/RepoIgnoreNodeTest.kt
new file mode 100644
index 000000000..7f983087f
--- /dev/null
+++ b/app/src/androidTest/java/com/orgzly/android/repos/RepoIgnoreNodeTest.kt
@@ -0,0 +1,47 @@
+package com.orgzly.android.repos
+
+import com.orgzly.android.OrgzlyTest
+import com.orgzly.android.db.entity.Repo
+import org.junit.Assert.assertEquals
+import org.junit.Test
+import java.io.ByteArrayInputStream
+import java.io.FileNotFoundException
+import java.io.InputStream
+import java.util.HashMap
+
+class RepoIgnoreNodeTest : OrgzlyTest() {
+
+ class MockRepoWithMockIgnoreFile : MockRepo(repoWithProps, null) {
+ override fun openRepoFileInputStream(filePath: String): InputStream {
+ if (filePath == RepoIgnoreNode.IGNORE_FILE) {
+ val ignoreFileContents = """
+ IgnoredAnywhere.org
+ /OnlyIgnoredInRoot.org
+ CompletelyExcludedFolder/
+ PartiallyExcludedFolder/**
+ !PartiallyExcludedFolder/included-*.org
+ """.trimIndent()
+ return ByteArrayInputStream(ignoreFileContents.toByteArray())
+ } else {
+ throw FileNotFoundException()
+ }
+ }
+ }
+
+ @Test
+ fun testIsPathIgnoredSyntax() {
+ val repo = MockRepoWithMockIgnoreFile()
+ val ignores = RepoIgnoreNode(repo)
+ assertEquals(true, ignores.isPathIgnored("IgnoredAnywhere.org", false))
+ assertEquals(true, ignores.isPathIgnored("SomeFolder/IgnoredAnywhere.org", false))
+ assertEquals(true, ignores.isPathIgnored("OnlyIgnoredInRoot.org", false))
+ assertEquals(false, ignores.isPathIgnored("SomeFolder/OnlyIgnoredInRoot.org", false))
+ assertEquals(true, ignores.isPathIgnored("CompletelyExcludedFolder/file.org", false))
+ assertEquals(true, ignores.isPathIgnored("PartiallyExcludedFolder/whatever.org", false))
+ assertEquals(false, ignores.isPathIgnored("PartiallyExcludedFolder/included-file.org", false))
+ }
+
+ companion object {
+ private val repoWithProps = RepoWithProps(Repo(0, RepoType.MOCK, "mock://repo"), HashMap())
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/orgzly/android/git/GitPreferencesFromRepoPrefs.java b/app/src/main/java/com/orgzly/android/git/GitPreferencesFromRepoPrefs.java
index d1468add8..8d043356e 100644
--- a/app/src/main/java/com/orgzly/android/git/GitPreferencesFromRepoPrefs.java
+++ b/app/src/main/java/com/orgzly/android/git/GitPreferencesFromRepoPrefs.java
@@ -16,13 +16,15 @@ public GitPreferencesFromRepoPrefs(RepoPreferences prefs) {
@Override
public GitTransportSetter createTransportSetter() {
String scheme = remoteUri().getScheme();
- if ("https".equals(scheme)) {
- String username = repoPreferences.getStringValue(R.string.pref_key_git_https_username, "");
- String password = repoPreferences.getStringValue(R.string.pref_key_git_https_password, "");
- return new HTTPSTransportSetter(username, password);
- } else {
- // assume SSH, since ssh:// usually isn't specified as the scheme when cloning via SSH.
- return new GitSshKeyTransportSetter();
+ switch (scheme) {
+ case "https":
+ String username = repoPreferences.getStringValue(R.string.pref_key_git_https_username, "");
+ String password = repoPreferences.getStringValue(R.string.pref_key_git_https_password, "");
+ return new HTTPSTransportSetter(username, password);
+ case "file":
+ return tc -> tc;
+ default:
+ return new GitSshKeyTransportSetter();
}
}
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 625b1f557..99015bc6b 100644
--- a/app/src/main/java/com/orgzly/android/prefs/AppPreferences.java
+++ b/app/src/main/java/com/orgzly/android/prefs/AppPreferences.java
@@ -936,6 +936,10 @@ public static boolean gitIsEnabled(Context context) {
context.getResources().getString(R.string.pref_key_git_is_enabled),
context.getResources().getBoolean(R.bool.pref_default_git_is_enabled));
}
+
+ public static void gitIsEnabled(Context context, Boolean value) {
+ getDefaultSharedPreferences(context).edit().putBoolean(context.getResources().getString(R.string.pref_key_git_is_enabled), value).apply();
+ }
public static String defaultRepositoryStorageDirectory(Context context) {
File path = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS);
diff --git a/app/src/main/java/com/orgzly/android/repos/MockRepo.java b/app/src/main/java/com/orgzly/android/repos/MockRepo.java
index 63c6f93e5..9145c9a3d 100644
--- a/app/src/main/java/com/orgzly/android/repos/MockRepo.java
+++ b/app/src/main/java/com/orgzly/android/repos/MockRepo.java
@@ -7,6 +7,7 @@
import com.orgzly.android.data.DbRepoBookRepository;
import java.io.File;
+import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.util.List;
@@ -59,7 +60,7 @@ public VersionedRook retrieveBook(String fileName, File file) throws IOException
@Override
public InputStream openRepoFileInputStream(String fileName) throws IOException {
- throw new UnsupportedOperationException("Not implemented");
+ throw new FileNotFoundException();
}
@Override
From 4f7429889a4cac124fea542c88b10fb00bb438ac Mon Sep 17 00:00:00 2001
From: Victor Andreasson
Date: Sun, 9 Jun 2024 19:19:08 +0200
Subject: [PATCH 09/10] Add a few more sleeps to flaky Espresso tests
---
.../androidTest/java/com/orgzly/android/espresso/BookTest.java | 1 +
.../java/com/orgzly/android/espresso/QueryFragmentTest.java | 2 ++
.../java/com/orgzly/android/espresso/util/EspressoUtils.java | 1 -
3 files changed, 3 insertions(+), 1 deletion(-)
diff --git a/app/src/androidTest/java/com/orgzly/android/espresso/BookTest.java b/app/src/androidTest/java/com/orgzly/android/espresso/BookTest.java
index e94588c37..532959c55 100644
--- a/app/src/androidTest/java/com/orgzly/android/espresso/BookTest.java
+++ b/app/src/androidTest/java/com/orgzly/android/espresso/BookTest.java
@@ -444,6 +444,7 @@ public void testSetDeadlineTimeForNewNote() {
onView(withId(R.id.date_picker_button)).perform(click());
onView(withClassName(equalTo(DatePicker.class.getName()))).perform(setDate(2014, 4, 1));
onView(withText(android.R.string.ok)).perform(click());
+ SystemClock.sleep(100);
onView(withText(R.string.set)).perform(click());
onView(withId(R.id.deadline_button)).check(matches(withText(userDateTime("<2014-04-01 Tue>"))));
}
diff --git a/app/src/androidTest/java/com/orgzly/android/espresso/QueryFragmentTest.java b/app/src/androidTest/java/com/orgzly/android/espresso/QueryFragmentTest.java
index d9d6c95fc..0afbd5ef3 100644
--- a/app/src/androidTest/java/com/orgzly/android/espresso/QueryFragmentTest.java
+++ b/app/src/androidTest/java/com/orgzly/android/espresso/QueryFragmentTest.java
@@ -246,6 +246,7 @@ public void testClickingNote() {
defaultSetUp();
onView(allOf(withText("book-two"), isDisplayed())).perform(click());
+ SystemClock.sleep(200);
searchForTextCloseKeyboard("b.book-two Note");
onView(withId(R.id.fragment_query_search_view_flipper)).check(matches(isDisplayed()));
onNotesInSearch().check(matches(recyclerViewItemCount(29)));
@@ -615,6 +616,7 @@ public void testSearchForTagOrTag() {
scenario = ActivityScenario.launch(MainActivity.class);
onView(allOf(withText("notebook"), isDisplayed())).perform(click());
+ SystemClock.sleep(200);
searchForTextCloseKeyboard("tn.a or tn.b");
onView(withId(R.id.fragment_query_search_view_flipper)).check(matches(isDisplayed()));
onNotesInSearch().check(matches(recyclerViewItemCount(2)));
diff --git a/app/src/androidTest/java/com/orgzly/android/espresso/util/EspressoUtils.java b/app/src/androidTest/java/com/orgzly/android/espresso/util/EspressoUtils.java
index 88ac1912f..cc401eaba 100644
--- a/app/src/androidTest/java/com/orgzly/android/espresso/util/EspressoUtils.java
+++ b/app/src/androidTest/java/com/orgzly/android/espresso/util/EspressoUtils.java
@@ -341,7 +341,6 @@ public static ViewInteraction contextualToolbarOverflowMenu() {
}
public static void searchForTextCloseKeyboard(String str) {
- SystemClock.sleep(100);
onView(isRoot()).perform(waitId(R.id.search_view, 5000));
onView(allOf(withId(R.id.search_view), isDisplayed())).perform(click());
onView(isRoot()).perform(waitId(R.id.search_src_text, 5000));
From 33d1f7bc88cbd1b8bd102e1fd382d33dd8fd30a1 Mon Sep 17 00:00:00 2001
From: Victor Andreasson
Date: Fri, 2 Aug 2024 14:34:25 +0200
Subject: [PATCH 10/10] Release 1.8.25
---
app/build.gradle | 4 ++--
app/src/main/res/layout/dialog_whats_new.xml | 2 +-
metadata/en-US/changelogs/214.txt | 2 ++
3 files changed, 5 insertions(+), 3 deletions(-)
create mode 100644 metadata/en-US/changelogs/214.txt
diff --git a/app/build.gradle b/app/build.gradle
index 8d3c04500..27beb38f4 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -10,8 +10,8 @@ android {
defaultConfig {
minSdkVersion 21
targetSdkVersion 33
- versionCode 213
- versionName "1.8.25-beta.1"
+ versionCode 214
+ versionName "1.8.25"
applicationId = "com.orgzlyrevived"
resValue "string", "application_id", "com.orgzlyrevived"
diff --git a/app/src/main/res/layout/dialog_whats_new.xml b/app/src/main/res/layout/dialog_whats_new.xml
index f36621931..1cb332d59 100644
--- a/app/src/main/res/layout/dialog_whats_new.xml
+++ b/app/src/main/res/layout/dialog_whats_new.xml
@@ -31,7 +31,7 @@
+ app:text="Support selecting multiple notebooks" />