1515import javax .xml .validation .Validator ;
1616import java .io .IOException ;
1717import java .io .StringReader ;
18- import java .nio .charset .StandardCharsets ;
19- import java .nio .file .AtomicMoveNotSupportedException ;
2018import java .nio .file .Files ;
2119import java .nio .file .Path ;
22- import java .nio .file .StandardCopyOption ;
23- import java .nio .file .StandardOpenOption ;
2420import java .util .List ;
2521import java .util .UUID ;
26- import java .util .concurrent .locks .Lock ;
27- import java .util .concurrent .locks .ReentrantLock ;
2822
2923/**
3024 * Implemenation of the {@link QuickAccessService} for KDE desktop environments using Dolphin file browser.
3327@ CheckAvailability
3428@ OperatingSystem (OperatingSystem .Value .LINUX )
3529@ Priority (90 )
36- public class DolphinPlaces implements QuickAccessService {
30+ public class DolphinPlaces extends FileConfiguredQuickAccess implements QuickAccessService {
3731
38- private static final int MAX_FILE_SIZE = 1 << 20 ; //xml is quite verbose
32+ private static final int MAX_FILE_SIZE = 1 << 20 ; //1MiB, xml is quite verbose
3933 private static final Path PLACES_FILE = Path .of (System .getProperty ("user.home" ), ".local/share/user-places.xbel" );
40- private static final Path TMP_FILE = Path .of (System .getProperty ("java.io.tmpdir" ), "user-places.xbel.cryptomator.tmp" );
41- private static final Lock MODIFY_LOCK = new ReentrantLock ();
4234 private static final String ENTRY_TEMPLATE = """
4335 <bookmark href=\" %s\" >
4436 <title>%s</title>
@@ -52,7 +44,6 @@ public class DolphinPlaces implements QuickAccessService {
5244 </info>
5345 </bookmark>""" ;
5446
55-
5647 private static final Validator XML_VALIDATOR ;
5748
5849 static {
@@ -65,84 +56,58 @@ public class DolphinPlaces implements QuickAccessService {
6556 }
6657 }
6758
59+ //SPI constructor
60+ public DolphinPlaces () {
61+ super (PLACES_FILE , MAX_FILE_SIZE );
62+ }
6863
6964 @ Override
70- public QuickAccessService .QuickAccessEntry add (Path target , String displayName ) throws QuickAccessServiceException {
71- String id = UUID .randomUUID ().toString ();
65+ EntryAndConfig addEntryToConfig (String config , Path target , String displayName ) throws QuickAccessServiceException {
7266 try {
73- MODIFY_LOCK .lock ();
74- if (Files .size (PLACES_FILE ) > MAX_FILE_SIZE ) {
75- throw new IOException ("File %s exceeds size of %d bytes" .formatted (PLACES_FILE , MAX_FILE_SIZE ));
76- }
77- var placesContent = Files .readString (PLACES_FILE );
67+ String id = UUID .randomUUID ().toString ();
7868 //validate
79- XML_VALIDATOR .validate (new StreamSource (new StringReader (placesContent )));
69+ XML_VALIDATOR .validate (new StreamSource (new StringReader (config )));
8070 // modify
81- int insertIndex = placesContent .lastIndexOf ("</xbel" ); //cannot be -1 due to validation; we do not match the end tag, since betweent tag name and closing bracket can be whitespaces
82- try (var writer = Files .newBufferedWriter (TMP_FILE , StandardCharsets .UTF_8 , StandardOpenOption .WRITE , StandardOpenOption .CREATE , StandardOpenOption .TRUNCATE_EXISTING )) {
83- writer .write (placesContent , 0 , insertIndex );
84- writer .newLine ();
85- writer .write (ENTRY_TEMPLATE .formatted (target .toUri (), displayName , id ).indent (1 ));
86- writer .newLine ();
87- writer .write (placesContent , insertIndex , placesContent .length () - insertIndex );
88- }
89- // save
90- persistTmpFile ();
91- return new DolphinPlacesEntry (id );
71+ int insertIndex = config .lastIndexOf ("</xbel" ); //cannot be -1 due to validation; we do not match the whole end tag, since between tag name and closing bracket can be whitespaces
72+ var adjustedConfig = config .substring (0 , insertIndex ) //
73+ + "\n " //
74+ + ENTRY_TEMPLATE .formatted (target .toUri (), displayName , id ).indent (1 ) //
75+ + "\n " //
76+ + config .substring (insertIndex );
77+ return new EntryAndConfig (new DolphinPlacesEntry (id ), adjustedConfig );
9278 } catch (SAXException | IOException e ) {
9379 throw new QuickAccessServiceException ("Adding entry to KDE places file failed." , e );
94- } finally {
95- MODIFY_LOCK .unlock ();
9680 }
9781 }
9882
99- private static class DolphinPlacesEntry implements QuickAccessEntry {
83+ private class DolphinPlacesEntry extends FileConfiguredQuickAccessEntry implements QuickAccessEntry {
10084
10185 private final String id ;
102- private volatile boolean isRemoved = false ;
10386
10487 DolphinPlacesEntry (String id ) {
10588 this .id = id ;
10689 }
10790
10891 @ Override
109- public void remove ( ) throws QuickAccessServiceException {
92+ public String removeEntryFromConfig ( String config ) throws QuickAccessServiceException {
11093 try {
111- MODIFY_LOCK .lock ();
112- if (isRemoved ) {
113- return ;
114- }
115- if (Files .size (PLACES_FILE ) > MAX_FILE_SIZE ) {
116- throw new IOException ("File %s exceeds size of %d bytes" .formatted (PLACES_FILE , MAX_FILE_SIZE ));
117- }
118- var placesContent = Files .readString (PLACES_FILE );
119- int idIndex = placesContent .lastIndexOf (id );
94+ int idIndex = config .lastIndexOf (id );
12095 if (idIndex == -1 ) {
121- isRemoved = true ;
122- return ; //we assume someone has removed our entry
96+ return config ; //assume someone has removed our entry, nothing to do
12397 }
12498 //validate
125- XML_VALIDATOR .validate (new StreamSource (new StringReader (placesContent )));
99+ XML_VALIDATOR .validate (new StreamSource (new StringReader (config )));
126100 //modify
127- int openingTagIndex = indexOfEntryOpeningTag (placesContent , idIndex );
128- var contentToWrite1 = placesContent .substring (0 , openingTagIndex ).stripTrailing ();
101+ int openingTagIndex = indexOfEntryOpeningTag (config , idIndex );
102+ var contentToWrite1 = config .substring (0 , openingTagIndex ).stripTrailing ();
129103
130- int closingTagEndIndex = placesContent .indexOf ('>' , placesContent .indexOf ("</bookmark" , idIndex ));
131- var part2Tmp = placesContent .substring (closingTagEndIndex + 1 ).split ("\\ A\\ v+" , 2 ); //removing leading vertical whitespaces, but no indentation
104+ int closingTagEndIndex = config .indexOf ('>' , config .indexOf ("</bookmark" , idIndex ));
105+ var part2Tmp = config .substring (closingTagEndIndex + 1 ).split ("\\ A\\ v+" , 2 ); //removing leading vertical whitespaces, but no indentation
132106 var contentToWrite2 = part2Tmp [part2Tmp .length - 1 ];
133107
134- try (var writer = Files .newBufferedWriter (TMP_FILE , StandardCharsets .UTF_8 , StandardOpenOption .WRITE , StandardOpenOption .CREATE , StandardOpenOption .TRUNCATE_EXISTING )) {
135- writer .write (contentToWrite1 );
136- writer .newLine ();
137- writer .write (contentToWrite2 );
138- }
139- // save
140- persistTmpFile ();
141- isRemoved = true ;
108+ return contentToWrite1 + "\n " + contentToWrite2 ;
142109 } catch (IOException | SAXException e ) {
143110 throw new QuickAccessServiceException ("Removing entry from KDE places file failed." , e );
144- } finally {
145- MODIFY_LOCK .unlock ();
146111 }
147112 }
148113
@@ -158,14 +123,6 @@ private int indexOfEntryOpeningTag(String placesContent, int idIndex) {
158123 }
159124 }
160125
161- static void persistTmpFile () throws IOException {
162- try {
163- Files .move (TMP_FILE , PLACES_FILE , StandardCopyOption .REPLACE_EXISTING , StandardCopyOption .ATOMIC_MOVE );
164- } catch (AtomicMoveNotSupportedException e ) {
165- Files .move (TMP_FILE , PLACES_FILE , StandardCopyOption .REPLACE_EXISTING );
166- }
167- }
168-
169126 @ CheckAvailability
170127 public static boolean isSupported () {
171128 return Files .exists (PLACES_FILE );
0 commit comments