OpenConcerto

Dépôt officiel du code source de l'ERP OpenConcerto
sonarqube

svn://code.openconcerto.org/openconcerto

Rev

Rev 144 | Details | Compare with Previous | Last modification | View Log | RSS feed

Rev Author Line No. Line
142 ilm 1
/*
2
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
3
 *
182 ilm 4
 * Copyright 2011-2019 OpenConcerto, by ILM Informatique. All rights reserved.
142 ilm 5
 *
6
 * The contents of this file are subject to the terms of the GNU General Public License Version 3
7
 * only ("GPL"). You may not use this file except in compliance with the License. You can obtain a
8
 * copy of the License at http://www.gnu.org/licenses/gpl-3.0.html See the License for the specific
9
 * language governing permissions and limitations under the License.
10
 *
11
 * When distributing the software, include this License Header Notice in each file.
12
 */
13
 
14
 package org.openconcerto.utils;
15
 
16
import java.io.File;
17
import java.io.IOException;
144 ilm 18
import java.nio.file.Files;
19
import java.nio.file.attribute.PosixFilePermissions;
142 ilm 20
 
21
/**
22
 * A set of base directories.
23
 *
24
 * @author Sylvain CUAZ
25
 */
26
public abstract class BaseDirs {
27
 
28
    static private final String DATA = "Data";
29
    static private final String PREFERENCES = "Preferences";
30
    static private final String CACHES = "Caches";
31
 
32
    // https://specifications.freedesktop.org/basedir-spec/basedir-spec-latest.html
33
    static public final class XDG extends BaseDirs {
34
        protected XDG(final ProductInfo info, final String subdir) {
35
            super(info, subdir);
36
        }
37
 
182 ilm 38
        private String getHomePath() {
39
            // ATTN on FreeBSD man 8 service : "service command sets HOME to /" thus one should
40
            // probably not use this class (perhaps Portable).
41
            return StringUtils.coalesce(System.getenv("HOME"), System.getProperty("user.home"));
42
        }
43
 
44
        private String getInHomePath(final String subPath) {
45
            final String homePath = getHomePath();
46
            // If no home, use current working directory
47
            return homePath == null ? subPath : homePath + '/' + subPath;
48
        }
49
 
142 ilm 50
        @Override
51
        protected File _getAppDataFolder() {
52
            /*
53
             * $XDG_DATA_HOME defines the base directory relative to which user specific data files
54
             * should be stored. If $XDG_DATA_HOME is either not set or empty, a default equal to
55
             * $HOME/.local/share should be used.
56
             */
182 ilm 57
            return new File(StringUtils.coalesce(System.getenv("XDG_DATA_HOME"), getInHomePath(".local/share")), this.getAppID());
142 ilm 58
        }
59
 
60
        @Override
61
        protected File _getPreferencesFolder() {
62
            /*
63
             * $XDG_CONFIG_HOME defines the base directory relative to which user specific
64
             * configuration files should be stored. If $XDG_CONFIG_HOME is either not set or empty,
65
             * a default equal to $HOME/.config should be used.
66
             */
182 ilm 67
            return new File(StringUtils.coalesce(System.getenv("XDG_CONFIG_HOME"), getInHomePath(".config")), this.getAppID());
142 ilm 68
        }
69
 
70
        @Override
71
        protected File _getCacheFolder() {
72
            /*
73
             * $XDG_CACHE_HOME defines the base directory relative to which user specific
74
             * non-essential data files should be stored. If $XDG_CACHE_HOME is either not set or
75
             * empty, a default equal to $HOME/.cache should be used.
76
             */
182 ilm 77
            return new File(StringUtils.coalesce(System.getenv("XDG_CACHE_HOME"), getInHomePath(".cache")), this.getAppID());
142 ilm 78
        }
79
    }
80
 
81
    static public final class Unknown extends BaseDirs {
82
 
83
        protected Unknown(final ProductInfo info, final String subdir) {
84
            super(info, subdir);
85
        }
86
 
87
    }
88
 
89
    static public final class Windows extends BaseDirs {
90
        private final String path;
91
 
92
        protected Windows(final ProductInfo info, final String subdir) {
93
            super(info, subdir);
94
            final String orgID = info.getOrganizationName() == null ? null : FileUtils.sanitize(info.getOrganizationName());
95
            final String appID = this.getAppName();
96
            // handle missing org and avoid OpenConcerto/OpenConcerto
97
            this.path = orgID == null || orgID.equals(appID) ? appID : orgID + File.separatorChar + appID;
98
            // ProductInfo test emptiness
99
            assert this.path.charAt(0) != File.separatorChar && this.path.charAt(this.path.length() - 1) != File.separatorChar : "Separator not in between : " + this.path;
100
        }
101
 
102
        protected final String getPath() {
103
            return this.path;
104
        }
105
 
106
        @Override
107
        protected File _getAppDataFolder() {
108
            // do not use LOCALAPPDATA as the user needs its data synchronised
109
            return new File(System.getenv("APPDATA"), this.getPath() + File.separatorChar + DATA);
110
        }
111
 
112
        @Override
113
        protected File _getPreferencesFolder() {
114
            // do not use LOCALAPPDATA as configuration should be small enough to be synchronised on
115
            // the network
116
            return new File(System.getenv("APPDATA"), this.getPath() + File.separatorChar + PREFERENCES);
117
        }
118
 
119
        @Override
120
        protected File _getCacheFolder() {
121
            // use LOCALAPPDATA as caches can be quite big and don't need to be synchronised
122
            return new File(System.getenv("LOCALAPPDATA"), this.getPath() + File.separatorChar + CACHES);
123
        }
124
    }
125
 
126
    // https://developer.apple.com/library/mac/qa/qa1170/_index.html
127
    static public final class Mac extends BaseDirs {
128
 
129
        protected Mac(final ProductInfo info, final String subdir) {
130
            super(info, subdir);
131
        }
132
 
133
        @Override
134
        protected File _getAppDataFolder() {
135
            // NOTE : "Application Support" directory is reserved for non-essential application
136
            // resources
137
            return new File(System.getProperty("user.home") + "/Library/" + this.getAppName());
138
        }
139
 
140
        @Override
141
        protected File _getPreferencesFolder() {
142
            return new File(System.getProperty("user.home") + "/Library/Preferences/" + this.getAppFullID());
143
        }
144
 
145
        @Override
146
        protected File _getCacheFolder() {
147
            return new File(System.getProperty("user.home") + "/Library/Caches/" + this.getAppFullID());
148
        }
149
    }
150
 
151
    static public final class Portable extends BaseDirs {
152
 
153
        private final File rootDir;
154
 
155
        protected Portable(final File rootDir, final ProductInfo info, final String subdir) {
156
            super(info, subdir);
157
            this.rootDir = rootDir;
158
        }
159
 
160
        public final File getRootDir() {
161
            return this.rootDir;
162
        }
163
 
164
        @Override
165
        protected File _getAppDataFolder() {
166
            return new File(this.getRootDir(), DATA);
167
        }
168
 
169
        @Override
170
        protected File _getPreferencesFolder() {
171
            return new File(this.getRootDir(), PREFERENCES);
172
        }
173
 
174
        @Override
175
        protected File _getCacheFolder() {
176
            return new File(this.getRootDir(), CACHES);
177
        }
178
    }
179
 
180
    public static final BaseDirs createPortable(final File rootDir, final ProductInfo info, final String subdir) {
181
        return new Portable(rootDir, info, subdir);
182
    }
183
 
184
    public static final BaseDirs create(final ProductInfo info) {
185
        return create(info, null);
186
    }
187
 
188
    public static final BaseDirs create(final ProductInfo info, final String subdir) {
189
        final OSFamily os = OSFamily.getInstance();
190
        if (os == OSFamily.Windows)
191
            return new Windows(info, subdir);
192
        else if (os == OSFamily.Mac)
193
            return new Mac(info, subdir);
194
        else if (os instanceof OSFamily.Unix)
195
            return new XDG(info, subdir);
196
        else
197
            return new Unknown(info, subdir);
198
    }
199
 
200
    private final ProductInfo info;
201
    private final String subdir;
202
 
203
    protected BaseDirs(final ProductInfo info, final String subdir) {
204
        this.info = info;
205
        this.subdir = subdir == null ? null : FileUtils.sanitize(subdir);
206
    }
207
 
208
    // should use other methods to avoid invalid characters
209
    private final ProductInfo getInfo() {
210
        return this.info;
211
    }
212
 
213
    protected final String getAppName() {
214
        return FileUtils.sanitize(this.getInfo().getName());
215
    }
216
 
217
    protected final String getAppID() {
218
        return this.getInfo().getID();
219
    }
220
 
221
    protected final String getAppFullID() {
222
        final String res = this.getInfo().getFullID();
223
        return res != null ? res : this.getAppID();
224
    }
225
 
182 ilm 226
    static public final File getFolderToWrite(final File dir) throws IOException {
227
        // MAYBE test for symlink
142 ilm 228
        if (dir.isDirectory() && dir.canWrite())
229
            return dir;
230
        if (dir.exists())
231
            throw new IOException((dir.isDirectory() ? "Not writable: " : "Not a directory: ") + dir);
144 ilm 232
        // create with 0700 mode (from § Referencing this specification)
233
        final String perms = "rwx------";
234
        try {
235
            Files.createDirectories(dir.toPath(), PosixFilePermissions.asFileAttribute(PosixFilePermissions.fromString(perms)));
236
        } catch (UnsupportedOperationException e) {
237
            // e.g. this is Windows
238
            Files.createDirectories(dir.toPath());
239
            FileUtils.setFilePermissionsFromPOSIX(dir, perms);
240
        }
142 ilm 241
        return dir;
242
    }
243
 
244
    protected final File getSubDir(final File dir) {
245
        return this.subdir == null ? dir : new File(dir, this.subdir);
246
    }
247
 
248
    protected File _getAppDataFolder() {
249
        return new File(System.getProperty("user.home"), "." + this.getAppFullID());
250
    }
251
 
252
    // where to write user-hidden data files (e.g. mbox files, DB files)
253
    public final File getAppDataFolder() {
254
        return getSubDir(_getAppDataFolder());
255
    }
256
 
257
    public final File getAppDataFolderToWrite() throws IOException {
258
        return getFolderToWrite(this.getAppDataFolder());
259
    }
260
 
261
    protected File _getPreferencesFolder() {
182 ilm 262
        return this._getAppDataFolder();
142 ilm 263
    }
264
 
265
    // where to write configuration
266
    public final File getPreferencesFolder() {
267
        return getSubDir(_getPreferencesFolder());
268
    }
269
 
270
    public final File getPreferencesFolderToWrite() throws IOException {
271
        return getFolderToWrite(this.getPreferencesFolder());
272
    }
273
 
274
    protected File _getCacheFolder() {
275
        return new File(System.getProperty("java.io.tmpdir"), this.getAppFullID());
276
    }
277
 
278
    // where to write data that can be re-created
279
    public final File getCacheFolder() {
280
        return getSubDir(_getCacheFolder());
281
    }
282
 
283
    public final File getCacheFolderToWrite() throws IOException {
284
        return getFolderToWrite(this.getCacheFolder());
285
    }
286
 
182 ilm 287
    protected File _getStateFolder() {
288
        return this._getCacheFolder();
289
    }
290
 
291
    // where to write data that is non-essential but cannot be recreated
292
    // - logfiles
293
    // - state of application windows on exit
294
    // - recently opened files
295
    // See STATE directory in https://wiki.debian.org/XDGBaseDirectorySpecification
296
    public final File getStateFolder() {
297
        return getSubDir(_getStateFolder());
298
    }
299
 
300
    public final File getStateFolderToWrite() throws IOException {
301
        return getFolderToWrite(this.getStateFolder());
302
    }
303
 
142 ilm 304
    @Override
305
    public String toString() {
306
        return BaseDirs.class.getSimpleName() + " " + this.getClass().getSimpleName();
307
    }
308
 
144 ilm 309
    public static void main(String[] args) throws IOException {
142 ilm 310
        final String appName = args.length > 0 ? args[0] : "fooApp";
311
        final String companyName = args.length > 1 ? args[1] : "acme";
312
        final String subdir = System.getProperty("subdir");
313
        final BaseDirs instance = create(new ProductInfo(CollectionUtils.createMap(ProductInfo.ORGANIZATION_NAME, companyName, ProductInfo.NAME, appName)), subdir);
314
        System.out.println(instance);
315
        System.out.println("app data : " + instance.getAppDataFolder());
316
        System.out.println("preferences : " + instance.getPreferencesFolder());
317
        System.out.println("cache : " + instance.getCacheFolder());
144 ilm 318
        // test creation and permission
319
        if (Boolean.getBoolean("createCacheDir")) {
320
            final File createdCache = instance.getCacheFolderToWrite();
321
            System.out.println("cache dir created : " + createdCache);
322
            if (Boolean.getBoolean("deleteCacheDir")) {
323
                Files.delete(createdCache.toPath());
324
                System.out.println("cache dir deleted");
325
            }
326
        }
142 ilm 327
    }
328
}