Skip to content

Commit 069e971

Browse files
committed
fix: insert custom folder db
Signed-off-by: alperozturk <alper_ozturk@proton.me>
1 parent ac7d82a commit 069e971

File tree

5 files changed

+415
-198
lines changed

5 files changed

+415
-198
lines changed
Lines changed: 146 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,146 @@
1+
/*
2+
* Nextcloud - Android Client
3+
*
4+
* SPDX-FileCopyrightText: 2025 Alper Ozturk <alper.ozturk@nextcloud.com>
5+
* SPDX-License-Identifier: AGPL-3.0-or-later
6+
*/
7+
8+
package com.nextcloud.client.jobs.autoUpload
9+
10+
import com.owncloud.android.datamodel.FilesystemDataProvider
11+
import com.owncloud.android.datamodel.SyncedFolder
12+
import com.owncloud.android.lib.common.utils.Log_OC
13+
import java.io.IOException
14+
import java.nio.file.AccessDeniedException
15+
import java.nio.file.FileVisitOption
16+
import java.nio.file.FileVisitResult
17+
import java.nio.file.Files
18+
import java.nio.file.Path
19+
import java.nio.file.Paths
20+
import java.nio.file.SimpleFileVisitor
21+
import java.nio.file.attribute.BasicFileAttributes
22+
23+
@Suppress("TooGenericExceptionCaught", "MagicNumber")
24+
class AutoUploadHelper {
25+
companion object {
26+
private const val TAG = "AutoUploadHelper"
27+
private const val MAX_DEPTH = 100
28+
}
29+
30+
fun insertCustomFolderIntoDB(folder: SyncedFolder, filesystemDataProvider: FilesystemDataProvider?): Int {
31+
val path = Paths.get(folder.localPath)
32+
33+
if (!Files.exists(path)) {
34+
Log_OC.w(TAG, "Folder does not exist: ${folder.localPath}")
35+
return 0
36+
}
37+
38+
if (!Files.isReadable(path)) {
39+
Log_OC.w(TAG, "Folder is not readable: ${folder.localPath}")
40+
return 0
41+
}
42+
43+
val enabledTimestampMs = folder.enabledTimestampMs
44+
val lastCheck = folder.lastScanTimestampMs
45+
val excludeHidden = folder.isExcludeHidden
46+
47+
var fileCount = 0
48+
var skipCount = 0
49+
var errorCount = 0
50+
51+
try {
52+
Files.walkFileTree(
53+
path,
54+
setOf(FileVisitOption.FOLLOW_LINKS),
55+
MAX_DEPTH,
56+
object : SimpleFileVisitor<Path>() {
57+
58+
override fun preVisitDirectory(dir: Path, attrs: BasicFileAttributes?): FileVisitResult {
59+
if (excludeHidden && dir != path && dir.toFile().isHidden) {
60+
Log_OC.d(TAG, "Skipping hidden directory: ${dir.fileName}")
61+
skipCount++
62+
return FileVisitResult.SKIP_SUBTREE
63+
}
64+
65+
return FileVisitResult.CONTINUE
66+
}
67+
68+
override fun visitFile(file: Path, attrs: BasicFileAttributes?): FileVisitResult {
69+
try {
70+
// Skip hidden files
71+
val javaFile = file.toFile()
72+
if (excludeHidden && javaFile.isHidden) {
73+
skipCount++
74+
return FileVisitResult.CONTINUE
75+
}
76+
77+
// Skip files already checked
78+
val lastModified = attrs?.lastModifiedTime()?.toMillis()
79+
?: javaFile.lastModified()
80+
if (lastModified < lastCheck) {
81+
skipCount++
82+
return FileVisitResult.CONTINUE
83+
}
84+
85+
// Skip files if the folder doesn't exist yet, the file is older than enabledTimestampMs,
86+
// and this is not the first scan
87+
if (!folder.isExisting && lastModified < enabledTimestampMs && lastCheck != -1L) {
88+
skipCount++
89+
return FileVisitResult.CONTINUE
90+
}
91+
92+
filesystemDataProvider?.storeOrUpdateFileValue(
93+
file.toAbsolutePath().toString(),
94+
lastModified,
95+
javaFile.isDirectory,
96+
folder
97+
)
98+
99+
fileCount++
100+
101+
if (fileCount % 100 == 0) {
102+
Log_OC.d(TAG, "Processed $fileCount files so far...")
103+
}
104+
} catch (e: Exception) {
105+
Log_OC.e(TAG, "Error processing file: $file", e)
106+
errorCount++
107+
}
108+
109+
return FileVisitResult.CONTINUE
110+
}
111+
112+
override fun visitFileFailed(file: Path, exc: IOException?): FileVisitResult {
113+
when (exc) {
114+
is AccessDeniedException -> {
115+
Log_OC.w(TAG, "Access denied: $file")
116+
}
117+
else -> {
118+
Log_OC.e(TAG, "Failed to visit file: $file", exc)
119+
}
120+
}
121+
errorCount++
122+
return FileVisitResult.CONTINUE
123+
}
124+
125+
override fun postVisitDirectory(dir: Path, exc: IOException?): FileVisitResult {
126+
if (exc != null) {
127+
Log_OC.e(TAG, "Error after visiting directory: $dir", exc)
128+
errorCount++
129+
}
130+
return FileVisitResult.CONTINUE
131+
}
132+
}
133+
)
134+
135+
Log_OC.d(
136+
TAG,
137+
"Scan complete for ${folder.localPath}: " +
138+
"$fileCount files processed, $skipCount skipped, $errorCount errors"
139+
)
140+
} catch (e: Exception) {
141+
Log_OC.e(TAG, "Error walking file tree: ${folder.localPath}", e)
142+
}
143+
144+
return fileCount
145+
}
146+
}

app/src/main/java/com/nextcloud/client/jobs/autoUpload/AutoUploadWorker.kt

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,7 @@ class AutoUploadWorker(
7676
private const val NOTIFICATION_ID = 266
7777
}
7878

79+
private val helper = AutoUploadHelper()
7980
private lateinit var syncedFolder: SyncedFolder
8081
private val notificationManager by lazy {
8182
context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
@@ -209,7 +210,7 @@ class AutoUploadWorker(
209210
private suspend fun collectFileChangesFromContentObserverWork(contentUris: Array<String>?) = try {
210211
withContext(Dispatchers.IO) {
211212
if (contentUris.isNullOrEmpty()) {
212-
FilesSyncHelper.insertAllDBEntriesForSyncedFolder(syncedFolder)
213+
FilesSyncHelper.insertAllDBEntriesForSyncedFolder(syncedFolder, helper)
213214
} else {
214215
val isContentUrisStored = FilesSyncHelper.insertChangedEntries(syncedFolder, contentUris)
215216
if (!isContentUrisStored) {
@@ -218,7 +219,7 @@ class AutoUploadWorker(
218219
"changed content uris not stored, fallback to insert all db entries to not lose files"
219220
)
220221

221-
FilesSyncHelper.insertAllDBEntriesForSyncedFolder(syncedFolder)
222+
FilesSyncHelper.insertAllDBEntriesForSyncedFolder(syncedFolder, helper)
222223
}
223224
}
224225
syncedFolder.lastScanTimestampMs = System.currentTimeMillis()

app/src/main/java/com/owncloud/android/utils/FilesSyncHelper.java

Lines changed: 3 additions & 120 deletions
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,8 @@
1919
import com.nextcloud.client.device.PowerManagementService;
2020
import com.nextcloud.client.jobs.BackgroundJobManager;
2121
import com.nextcloud.client.jobs.ContentObserverWork;
22+
import com.nextcloud.client.jobs.autoUpload.AutoUploadHelper;
2223
import com.nextcloud.client.jobs.upload.FileUploadHelper;
23-
import com.nextcloud.client.jobs.upload.FileUploadWorker;
2424
import com.nextcloud.client.network.ConnectivityService;
2525
import com.nextcloud.utils.extensions.UriExtensionsKt;
2626
import com.owncloud.android.MainApp;
@@ -31,20 +31,7 @@
3131
import com.owncloud.android.datamodel.UploadsStorageManager;
3232
import com.owncloud.android.lib.common.utils.Log_OC;
3333

34-
import org.lukhnos.nnio.file.AccessDeniedException;
35-
import org.lukhnos.nnio.file.FileVisitResult;
36-
import org.lukhnos.nnio.file.FileVisitor;
37-
import org.lukhnos.nnio.file.Files;
38-
import org.lukhnos.nnio.file.Path;
39-
import org.lukhnos.nnio.file.Paths;
40-
import org.lukhnos.nnio.file.SimpleFileVisitor;
41-
import org.lukhnos.nnio.file.attribute.BasicFileAttributes;
42-
import org.lukhnos.nnio.file.impl.FileBasedPathImpl;
43-
4434
import java.io.File;
45-
import java.io.IOException;
46-
import java.util.Arrays;
47-
import java.util.Collections;
4835

4936
import static com.owncloud.android.datamodel.OCFile.PATH_SEPARATOR;
5037

@@ -60,110 +47,7 @@ private FilesSyncHelper() {
6047
// utility class -> private constructor
6148
}
6249

63-
/**
64-
* Copy of {@link Files#walkFileTree(Path, FileVisitor)} that walks the file tree in random order.
65-
*
66-
* @see org.lukhnos.nnio.file.Files#walkFileTree(Path, FileVisitor)
67-
*/
68-
public static void walkFileTreeRandomly(Path start, FileVisitor<? super Path> visitor) throws IOException {
69-
File file = start.toFile();
70-
if (!file.canRead()) {
71-
Log_OC.d(TAG, "walkFileTreeRandomly, cant read the file: " + file.getAbsolutePath());
72-
visitor.visitFileFailed(start, new AccessDeniedException(file.toString()));
73-
} else {
74-
Log_OC.d(TAG, "walkFileTreeRandomly, reading file: " + file.getAbsolutePath());
75-
76-
if (Files.isDirectory(start)) {
77-
Log_OC.d(TAG, "walkFileTreeRandomly, file is directory: " + file.getAbsolutePath());
78-
79-
FileVisitResult preVisitDirectoryResult = visitor.preVisitDirectory(start, null);
80-
if (preVisitDirectoryResult == FileVisitResult.CONTINUE) {
81-
Log_OC.d(TAG, "walkFileTreeRandomly, preVisitDirectoryResult == FileVisitResult.CONTINUE");
82-
File[] children = start.toFile().listFiles();
83-
if (children != null) {
84-
Log_OC.d(TAG, "walkFileTreeRandomly, children exists");
85-
86-
Collections.shuffle(Arrays.asList(children));
87-
File[] var5 = children;
88-
int var6 = children.length;
89-
90-
for(int var7 = 0; var7 < var6; ++var7) {
91-
Log_OC.d(TAG, "walkFileTreeRandomly -- recursive call");
92-
File child = var5[var7];
93-
walkFileTreeRandomly(FileBasedPathImpl.get(child), visitor);
94-
}
95-
96-
visitor.postVisitDirectory(start, null);
97-
} else {
98-
Log_OC.w(TAG, "walkFileTreeRandomly, children is null");
99-
}
100-
} else {
101-
Log_OC.w(TAG, "walkFileTreeRandomly, preVisitDirectoryResult != FileVisitResult.CONTINUE");
102-
}
103-
} else {
104-
Log_OC.d(TAG, "walkFileTreeRandomly, file is not directory");
105-
visitor.visitFile(start, new BasicFileAttributes(file));
106-
}
107-
}
108-
}
109-
110-
private static void insertCustomFolderIntoDB(Path path,
111-
SyncedFolder syncedFolder,
112-
FilesystemDataProvider filesystemDataProvider,
113-
long lastCheck) {
114-
Log_OC.d(TAG, "insertCustomFolderIntoDB called");
115-
final long enabledTimestampMs = syncedFolder.getEnabledTimestampMs();
116-
117-
try {
118-
walkFileTreeRandomly(path, new SimpleFileVisitor<>() {
119-
@Override
120-
public FileVisitResult visitFile(Path path, BasicFileAttributes attrs) {
121-
File file = path.toFile();
122-
if (syncedFolder.isExcludeHidden() && file.isHidden()) {
123-
Log_OC.w(TAG, "skipping files, exclude hidden file/folder: " + path);
124-
// exclude hidden file or folder
125-
return FileVisitResult.CONTINUE;
126-
}
127-
128-
if (attrs.lastModifiedTime().toMillis() < lastCheck) {
129-
Log_OC.w(TAG, "skipping files that already checked: " + path);
130-
// skip files that were already checked
131-
return FileVisitResult.CONTINUE;
132-
}
133-
134-
if (syncedFolder.isExisting() || attrs.lastModifiedTime().toMillis() >= enabledTimestampMs) {
135-
// storeOrUpdateFileValue takes a few ms
136-
// -> Rest of this file check takes not even 1 ms.
137-
filesystemDataProvider.storeOrUpdateFileValue(path.toAbsolutePath().toString(),
138-
attrs.lastModifiedTime().toMillis(),
139-
file.isDirectory(), syncedFolder);
140-
} else {
141-
Log_OC.w(TAG, "skipping files. SynchedFolder not exists or enabledTimestampMs not meeting condition" + path);
142-
}
143-
144-
return FileVisitResult.CONTINUE;
145-
}
146-
147-
@Override
148-
public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) {
149-
if (syncedFolder.isExcludeHidden() && dir.compareTo(Paths.get(syncedFolder.getLocalPath())) != 0 && dir.toFile().isHidden()) {
150-
Log_OC.d(TAG, "skipping hidden path: " + dir.getFileName());
151-
return FileVisitResult.SKIP_SUBTREE;
152-
}
153-
return FileVisitResult.CONTINUE;
154-
}
155-
156-
@Override
157-
public FileVisitResult visitFileFailed(Path file, IOException exc) {
158-
return FileVisitResult.CONTINUE;
159-
}
160-
});
161-
} catch (IOException e) {
162-
Log_OC.e(TAG, "Something went wrong while indexing files for auto upload: " + e.getLocalizedMessage());
163-
}
164-
}
165-
166-
public static void insertAllDBEntriesForSyncedFolder(SyncedFolder syncedFolder) {
50+
public static void insertAllDBEntriesForSyncedFolder(SyncedFolder syncedFolder, AutoUploadHelper helper) {
16751
Log_OC.d(TAG, "insertAllDBEntriesForSyncedFolder, called. ID: " + syncedFolder.getId());
16852

16953
final Context context = MainApp.getAppContext();
@@ -197,8 +81,7 @@ public static void insertAllDBEntriesForSyncedFolder(SyncedFolder syncedFolder)
19781
} else {
19882
Log_OC.d(TAG, "inserting other media types: " + mediaType.toString());
19983
FilesystemDataProvider filesystemDataProvider = new FilesystemDataProvider(contentResolver);
200-
Path path = Paths.get(syncedFolder.getLocalPath());
201-
FilesSyncHelper.insertCustomFolderIntoDB(path, syncedFolder, filesystemDataProvider, lastCheckTimestampMs);
84+
helper.insertCustomFolderIntoDB(syncedFolder, filesystemDataProvider);
20285
}
20386

20487
Log_OC.d(TAG,"File-sync finished full check for custom folder "+syncedFolder.getLocalPath()+" within "+(System.nanoTime() - startTime)+ "ns");

0 commit comments

Comments
 (0)