4141import java .util .Map ;
4242
4343/**
44- * Simple parser and writer for Linux .desktop files.
44+ * Parser and writer for Linux .desktop files.
4545 * <p>
46- * Supports reading and writing key-value pairs within the [Desktop Entry] section.
47- * This implementation is minimal and focused on URI scheme registration needs.
46+ * Instance-based API for reading and writing .desktop files. Supports all
47+ * standard [Desktop Entry] section fields plus custom keys. This class
48+ * provides a convenient way to manage desktop files for both application
49+ * launching and URI scheme registration.
50+ * </p>
51+ * <p>
52+ * Note: This class will eventually move to {@code org.scijava.util.DesktopFile}
53+ * in scijava-common once the design is finalized.
4854 * </p>
4955 *
5056 * @author Curtis Rueden
5157 */
5258public class DesktopFile {
5359
60+ private final Path path ;
5461 private final Map <String , String > entries ;
5562 private final List <String > comments ;
5663
57- public DesktopFile () {
64+ /**
65+ * Creates a DesktopFile instance for the given path.
66+ * <p>
67+ * If the file exists, it will be loaded when {@link #load()} is called.
68+ * If it doesn't exist, the entries map will be empty until populated
69+ * programmatically or {@link #load()} is called.
70+ * </p>
71+ *
72+ * @param path the path to the .desktop file
73+ */
74+ public DesktopFile (final Path path ) {
75+ this .path = path ;
5876 this .entries = new LinkedHashMap <>();
5977 this .comments = new ArrayList <>();
6078 }
6179
6280 /**
63- * Parses a .desktop file from disk.
81+ * Gets the file path for this desktop file.
82+ *
83+ * @return the path
84+ */
85+ public Path path () {
86+ return path ;
87+ }
88+
89+ /**
90+ * Checks if the file exists on disk.
91+ *
92+ * @return true if the file exists
93+ */
94+ public boolean exists () {
95+ return Files .exists (path );
96+ }
97+
98+ /**
99+ * Loads the .desktop file from disk.
100+ * <p>
101+ * Clears any existing entries and comments, then reads from the file.
102+ * If the file doesn't exist, entries will be empty after this call.
103+ * </p>
64104 *
65- * @param path Path to the .desktop file
66- * @return Parsed DesktopFile
67105 * @throws IOException if reading fails
68106 */
69- public static DesktopFile parse (final Path path ) throws IOException {
70- final DesktopFile df = new DesktopFile ();
107+ public void load () throws IOException {
108+ entries .clear ();
109+ comments .clear ();
110+
111+ if (!exists ()) {
112+ return ;
113+ }
114+
71115 boolean inDesktopEntry = false ;
72116
73117 try (final BufferedReader reader = Files .newBufferedReader (path , StandardCharsets .UTF_8 )) {
@@ -90,7 +134,7 @@ public static DesktopFile parse(final Path path) throws IOException {
90134
91135 // Skip empty lines and comments
92136 if (trimmed .isEmpty () || trimmed .startsWith ("#" )) {
93- df . comments .add (line );
137+ comments .add (line );
94138 continue ;
95139 }
96140
@@ -99,21 +143,21 @@ public static DesktopFile parse(final Path path) throws IOException {
99143 if (equals > 0 ) {
100144 final String key = line .substring (0 , equals ).trim ();
101145 final String value = line .substring (equals + 1 );
102- df . entries .put (key , value );
146+ entries .put (key , value );
103147 }
104148 }
105149 }
106-
107- return df ;
108150 }
109151
110152 /**
111- * Writes the .desktop file to disk.
153+ * Saves the .desktop file to disk.
154+ * <p>
155+ * Creates parent directories if needed. Overwrites any existing file.
156+ * </p>
112157 *
113- * @param path Path to write to
114158 * @throws IOException if writing fails
115159 */
116- public void writeTo ( final Path path ) throws IOException {
160+ public void save ( ) throws IOException {
117161 // Ensure parent directory exists
118162 final Path parent = path .getParent ();
119163 if (parent != null && !Files .exists (parent )) {
@@ -140,6 +184,48 @@ public void writeTo(final Path path) throws IOException {
140184 }
141185 }
142186
187+ /**
188+ * Deletes the file from disk.
189+ *
190+ * @throws IOException if deletion fails
191+ */
192+ public void delete () throws IOException {
193+ Files .deleteIfExists (path );
194+ }
195+
196+ /**
197+ * Parses a .desktop file from disk (static convenience method).
198+ *
199+ * @param path Path to the .desktop file
200+ * @return Parsed DesktopFile
201+ * @throws IOException if reading fails
202+ */
203+ public static DesktopFile parse (final Path path ) throws IOException {
204+ final DesktopFile df = new DesktopFile (path );
205+ df .load ();
206+ return df ;
207+ }
208+
209+ /**
210+ * Writes the .desktop file to disk (for backward compatibility).
211+ *
212+ * @param path Path to write to
213+ * @throws IOException if writing fails
214+ */
215+ public void writeTo (final Path path ) throws IOException {
216+ final Path oldPath = this .path ;
217+ // Temporarily change path, save, then restore
218+ try {
219+ // Create a temporary instance with the new path
220+ final DesktopFile temp = new DesktopFile (path );
221+ temp .entries .putAll (this .entries );
222+ temp .comments .addAll (this .comments );
223+ temp .save ();
224+ } catch (final Exception e ) {
225+ throw new IOException ("Failed to write to " + path , e );
226+ }
227+ }
228+
143229 /**
144230 * Gets the value for a key.
145231 *
@@ -157,9 +243,98 @@ public String get(final String key) {
157243 * @param value The value
158244 */
159245 public void set (final String key , final String value ) {
160- entries .put (key , value );
246+ if (value == null ) {
247+ entries .remove (key );
248+ } else {
249+ entries .put (key , value );
250+ }
161251 }
162252
253+ // -- Standard field accessors --
254+
255+ public String getType () {
256+ return get ("Type" );
257+ }
258+
259+ public void setType (final String type ) {
260+ set ("Type" , type );
261+ }
262+
263+ public String getVersion () {
264+ return get ("Version" );
265+ }
266+
267+ public void setVersion (final String version ) {
268+ set ("Version" , version );
269+ }
270+
271+ public String getName () {
272+ return get ("Name" );
273+ }
274+
275+ public void setName (final String name ) {
276+ set ("Name" , name );
277+ }
278+
279+ public String getGenericName () {
280+ return get ("GenericName" );
281+ }
282+
283+ public void setGenericName (final String genericName ) {
284+ set ("GenericName" , genericName );
285+ }
286+
287+ public String getComment () {
288+ return get ("Comment" );
289+ }
290+
291+ public void setComment (final String comment ) {
292+ set ("Comment" , comment );
293+ }
294+
295+ public String getExec () {
296+ return get ("Exec" );
297+ }
298+
299+ public void setExec (final String exec ) {
300+ set ("Exec" , exec );
301+ }
302+
303+ public String getIcon () {
304+ return get ("Icon" );
305+ }
306+
307+ public void setIcon (final String icon ) {
308+ set ("Icon" , icon );
309+ }
310+
311+ public String getPath () {
312+ return get ("Path" );
313+ }
314+
315+ public void setPath (final String path ) {
316+ set ("Path" , path );
317+ }
318+
319+ public boolean getTerminal () {
320+ final String value = get ("Terminal" );
321+ return "true" .equalsIgnoreCase (value );
322+ }
323+
324+ public void setTerminal (final boolean terminal ) {
325+ set ("Terminal" , terminal ? "true" : "false" );
326+ }
327+
328+ public String getCategories () {
329+ return get ("Categories" );
330+ }
331+
332+ public void setCategories (final String categories ) {
333+ set ("Categories" , categories );
334+ }
335+
336+ // -- MimeType handling --
337+
163338 /**
164339 * Checks if a MimeType entry contains a specific MIME type.
165340 *
0 commit comments