Skip to content

Commit d398446

Browse files
authored
Merge pull request #77 from cryptomator/feature/quick-access-nautilus
Feature: Implementation of Quick Access API for GNOME Nautilus
2 parents 759432d + 7040c0e commit d398446

File tree

5 files changed

+124
-1
lines changed

5 files changed

+124
-1
lines changed

pom.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@
4040

4141
<!-- runtime dependencies -->
4242

43-
<api.version>1.3.1</api.version>
43+
<api.version>1.4.0-beta2</api.version>
4444
<secret-service.version>2.0.1-alpha</secret-service.version>
4545
<kdewallet.version>1.4.0</kdewallet.version>
4646
<slf4j.version>2.0.13</slf4j.version>

src/main/java/module-info.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
import org.cryptomator.integrations.keychain.KeychainAccessProvider;
2+
import org.cryptomator.integrations.quickaccess.QuickAccessService;
23
import org.cryptomator.integrations.revealpath.RevealPathService;
34
import org.cryptomator.integrations.tray.TrayMenuController;
45
import org.cryptomator.linux.keychain.KDEWalletKeychainAccess;
56
import org.cryptomator.linux.keychain.SecretServiceKeychainAccess;
7+
import org.cryptomator.linux.quickaccess.NautilusBookmarks;
68
import org.cryptomator.linux.revealpath.DBusSendRevealPathService;
79
import org.cryptomator.linux.tray.AppindicatorTrayMenuController;
810

@@ -17,6 +19,7 @@
1719
provides KeychainAccessProvider with SecretServiceKeychainAccess, KDEWalletKeychainAccess;
1820
provides RevealPathService with DBusSendRevealPathService;
1921
provides TrayMenuController with AppindicatorTrayMenuController;
22+
provides QuickAccessService with NautilusBookmarks;
2023

2124
opens org.cryptomator.linux.tray to org.cryptomator.integrations.api;
2225
}
Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
package org.cryptomator.linux.quickaccess;
2+
3+
import org.cryptomator.integrations.common.CheckAvailability;
4+
import org.cryptomator.integrations.common.DisplayName;
5+
import org.cryptomator.integrations.common.OperatingSystem;
6+
import org.cryptomator.integrations.common.Priority;
7+
import org.cryptomator.integrations.quickaccess.QuickAccessService;
8+
import org.cryptomator.integrations.quickaccess.QuickAccessServiceException;
9+
10+
import java.io.IOException;
11+
import java.nio.charset.StandardCharsets;
12+
import java.nio.file.Files;
13+
import java.nio.file.Path;
14+
import java.nio.file.StandardCopyOption;
15+
import java.nio.file.StandardOpenOption;
16+
import java.util.concurrent.TimeUnit;
17+
import java.util.concurrent.locks.Lock;
18+
import java.util.concurrent.locks.ReentrantReadWriteLock;
19+
20+
@Priority(100)
21+
@CheckAvailability
22+
@OperatingSystem(OperatingSystem.Value.LINUX)
23+
@DisplayName("GNOME Nautilus Bookmarks")
24+
public class NautilusBookmarks implements QuickAccessService {
25+
26+
private static final int MAX_FILE_SIZE = 4096;
27+
private static final Path BOOKMARKS_FILE = Path.of(System.getProperty("user.home"), ".config/gtk-3.0/bookmarks");
28+
private static final Path TMP_FILE = BOOKMARKS_FILE.resolveSibling("bookmarks.cryptomator.tmp");
29+
private static final Lock BOOKMARKS_LOCK = new ReentrantReadWriteLock().writeLock();
30+
31+
@Override
32+
public QuickAccessService.QuickAccessEntry add(Path target, String displayName) throws QuickAccessServiceException {
33+
String entryLine = "file://" + target.toAbsolutePath() + " " + displayName;
34+
try {
35+
BOOKMARKS_LOCK.lock();
36+
if (Files.size(BOOKMARKS_FILE) > MAX_FILE_SIZE) {
37+
throw new IOException("File %s exceeds size of %d bytes".formatted(BOOKMARKS_FILE, MAX_FILE_SIZE));
38+
}
39+
//by reading all lines, we ensure that each line is terminated with EOL
40+
var entries = Files.readAllLines(BOOKMARKS_FILE, StandardCharsets.UTF_8);
41+
entries.add(entryLine);
42+
Files.write(TMP_FILE, entries, StandardCharsets.UTF_8, StandardOpenOption.WRITE, StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING);
43+
Files.move(TMP_FILE, BOOKMARKS_FILE, StandardCopyOption.REPLACE_EXISTING, StandardCopyOption.ATOMIC_MOVE);
44+
return new NautilusQuickAccessEntry(entryLine);
45+
} catch (IOException e) {
46+
throw new QuickAccessServiceException("Adding entry to Nautilus bookmarks file failed.", e);
47+
} finally {
48+
BOOKMARKS_LOCK.unlock();
49+
}
50+
}
51+
52+
static class NautilusQuickAccessEntry implements QuickAccessEntry {
53+
54+
private final String line;
55+
private volatile boolean isRemoved = false;
56+
57+
NautilusQuickAccessEntry(String line) {
58+
this.line = line;
59+
}
60+
61+
@Override
62+
public void remove() throws QuickAccessServiceException {
63+
try {
64+
BOOKMARKS_LOCK.lock();
65+
if (isRemoved) {
66+
return;
67+
}
68+
if (Files.size(BOOKMARKS_FILE) > MAX_FILE_SIZE) {
69+
throw new IOException("File %s exceeds size of %d bytes".formatted(BOOKMARKS_FILE, MAX_FILE_SIZE));
70+
}
71+
var entries = Files.readAllLines(BOOKMARKS_FILE);
72+
if (entries.remove(line)) {
73+
Files.write(TMP_FILE, entries, StandardCharsets.UTF_8, StandardOpenOption.WRITE, StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING);
74+
Files.move(TMP_FILE, BOOKMARKS_FILE, StandardCopyOption.REPLACE_EXISTING, StandardCopyOption.ATOMIC_MOVE);
75+
}
76+
isRemoved = true;
77+
} catch (IOException e) {
78+
throw new QuickAccessServiceException("Removing entry from Nautilus bookmarks file failed", e);
79+
} finally {
80+
BOOKMARKS_LOCK.unlock();
81+
}
82+
}
83+
}
84+
85+
@CheckAvailability
86+
public static boolean isSupported() {
87+
try {
88+
var nautilusExistsProc = new ProcessBuilder().command("test", "`command -v nautilus`").start();
89+
if (nautilusExistsProc.waitFor(5000, TimeUnit.MILLISECONDS)) {
90+
return nautilusExistsProc.exitValue() == 0;
91+
}
92+
} catch (IOException | InterruptedException e) {
93+
//NO-OP
94+
}
95+
return false;
96+
}
97+
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
org.cryptomator.linux.quickaccess.NautilusSidebarService
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
package org.cryptomator.linux.quickaccess;
2+
3+
import org.cryptomator.integrations.quickaccess.QuickAccessServiceException;
4+
import org.junit.jupiter.api.Disabled;
5+
import org.junit.jupiter.api.DisplayName;
6+
import org.junit.jupiter.api.Test;
7+
import org.junit.jupiter.api.io.TempDir;
8+
9+
import java.nio.file.Path;
10+
import java.time.Duration;
11+
12+
public class NautilusBookmarksIT {
13+
14+
@Test
15+
@DisplayName("Adds for 20s an entryto the Nautilus sidebar")
16+
@Disabled
17+
public void testSidebarIntegration(@TempDir Path tmpdir) throws QuickAccessServiceException, InterruptedException {
18+
var entry = new NautilusBookmarks().add(tmpdir, "integrations-linux");
19+
Thread.sleep(Duration.ofSeconds(20));
20+
entry.remove();
21+
}
22+
}

0 commit comments

Comments
 (0)