OSDN Git Service

modify buildImageCacheDir()
[jindolf/Jindolf.git] / src / main / java / jp / sfjp / jindolf / config / ConfigDirUtils.java
1 /*
2  * configuration file & directory
3  *
4  * License : The MIT License
5  * Copyright(c) 2009 olyutorskii
6  */
7
8 package jp.sfjp.jindolf.config;
9
10 import java.io.BufferedInputStream;
11 import java.io.File;
12 import java.io.IOException;
13 import java.io.InputStream;
14 import java.nio.file.Files;
15 import java.nio.file.Path;
16 import java.nio.file.Paths;
17 import java.text.MessageFormat;
18 import java.util.logging.Level;
19 import java.util.logging.Logger;
20 import javax.swing.JDialog;
21 import javax.swing.JOptionPane;
22 import jp.sfjp.jindolf.ResourceManager;
23 import jp.sfjp.jindolf.VerInfo;
24 import jp.sfjp.jindolf.view.LockErrorPane;
25
26 /**
27  * Jindolf設定格納ディレクトリに関するあれこれ。
28  */
29 public final class ConfigDirUtils{
30
31     private static final Logger LOGGER = Logger.getAnonymousLogger();
32
33     private static final String TITLE_BUILDCONF =
34             VerInfo.TITLE + "設定格納ディレクトリの設定";
35
36     private static final Path JINCONF     = Paths.get("Jindolf");
37     private static final Path JINCONF_DOT = Paths.get(".jindolf");
38     private static final Path FILE_README = Paths.get("README.txt");
39     private static final Path FILE_AVATARJSON = Paths.get("avatarCache.json");
40
41     private static final String RES_DIR = "resources";
42     private static final String RES_README = RES_DIR + "/README.txt";
43     private static final String RES_IMGDIR = RES_DIR + "/image";
44     private static final String RES_AVATARJSON = RES_IMGDIR + "/avatarCache.json";
45
46     private static final String MSG_POST =
47             "<ul>"
48             + "<li><code>" + CmdOption.OPT_CONFDIR + "</code>"
49             + "&nbsp;オプション指定により、<br/>"
50             + "任意の設定格納ディレクトリを指定することができます。<br/>"
51             + "<li><code>" + CmdOption.OPT_NOCONF + "</code>"
52             + "&nbsp;オプション指定により、<br/>"
53             + "設定格納ディレクトリを使わずに起動することができます。<br/>"
54             + "</ul>";
55
56     private static final int ERR_ABORT = 1;
57
58
59     /**
60      * 隠れコンストラクタ。
61      */
62     private ConfigDirUtils(){
63         assert false;
64     }
65
66
67     /**
68      * VMごとアプリを異常終了させる。
69      *
70      * <p>終了コードは1。
71      */
72     private static void abort(){
73         System.exit(ERR_ABORT);
74         assert false;
75         return;
76     }
77
78     /**
79      * ダイアログを表示し、閉じられるまで待つ。
80      *
81      * @param pane ダイアログの元となるペイン
82      */
83     private static void showDialog(JOptionPane pane){
84         JDialog dialog = pane.createDialog(TITLE_BUILDCONF);
85         dialog.setResizable(true);
86         dialog.pack();
87
88         dialog.setVisible(true);
89         dialog.dispose();
90
91         return;
92     }
93
94     /**
95      * 設定ディレクトリ操作の
96      * 共通エラーメッセージ確認ダイアログを表示する。
97      *
98      * <p>閉じるまで待つ。
99      *
100      * @param txt メッセージ
101      */
102     private static void showErrorMessage(String txt){
103         JOptionPane pane;
104         pane = new JOptionPane(txt, JOptionPane.ERROR_MESSAGE);
105         showDialog(pane);
106         return;
107     }
108
109     /**
110      * 設定ディレクトリ操作の
111      * 共通エラーメッセージ確認ダイアログを表示する。
112      *
113      * <p>閉じるまで待つ。
114      *
115      * @param seq メッセージtxt
116      */
117     private static void showWarnMessage(String txt){
118         JOptionPane pane;
119         pane = new JOptionPane(txt, JOptionPane.WARNING_MESSAGE);
120         showDialog(pane);
121         return;
122     }
123
124     /**
125      * 設定ディレクトリ操作の
126      * 情報提示メッセージ確認ダイアログを表示する。
127      *
128      * <p>閉じるまで待つ。
129      *
130      * @param seq メッセージtxt
131      */
132     private static void showInfoMessage(String txt){
133         JOptionPane pane;
134         pane = new JOptionPane(txt, JOptionPane.INFORMATION_MESSAGE);
135         showDialog(pane);
136         return;
137     }
138
139     /**
140      * センタリングされたファイル名表示のHTML表記を出力する。
141      *
142      * @param path ファイル
143      * @return HTML表記
144      */
145     public static String getCenteredFileName(Path path){
146         String form = "<center>[&nbsp;{0}&nbsp;]</center><br/>";
147         String fileName = FileUtils.getHtmledFileName(path);
148         String result = MessageFormat.format(form, fileName);
149         return result;
150     }
151
152     /**
153      * 設定ディレクトリ生成をやめた操作への警告をダイアログで提示し、
154      * VM終了する。
155      */
156     private static void abortQuitBuildConfigDir(){
157         String msg =
158                 "<html>"
159                 + "設定ディレクトリの作成をせずに起動を中止します。<br/>"
160                 + MSG_POST
161                 + "</html>";
162
163         showWarnMessage(msg);
164         abort();
165         assert false;
166
167         return;
168     }
169
170     /**
171      * ディレクトリが生成できないエラーをダイアログで提示し、
172      * VM終了する。
173      *
174      * @param path 生成できなかったディレクトリ
175      */
176     private static void abortCantBuildDir(Path path){
177         String form =
178                 "<html>"
179                 + "ディレクトリ<br/>"
180                 + "{0}"
181                 + "の作成に失敗しました。"
182                 + "起動を中止します。<br/>"
183                 + MSG_POST
184                 + "</html>";
185         String fileName = getCenteredFileName(path);
186         String msg = MessageFormat.format(form, fileName);
187
188         showErrorMessage(msg);
189         abort();
190         assert false;
191
192         return;
193     }
194
195     /**
196      * ディレクトリへアクセスできないエラーをダイアログで提示し、
197      * VM終了する。
198      *
199      * @param path アクセスできないディレクトリ
200      */
201     private static void abortCantAccessDir(Path path){
202         String form =
203                 "<html>"
204                 + "ディレクトリ<br/>"
205                 + "{0}"
206                 + "へのアクセスができません。"
207                 + "起動を中止します。<br/>"
208                 + "このディレクトリへのアクセス権を調整し"
209                 + "読み書きできるようにしてください。<br/>"
210                 + MSG_POST
211                 + "</html>";
212         String fileName = getCenteredFileName(path);
213         String msg = MessageFormat.format(form, fileName);
214
215         showErrorMessage(msg);
216         abort();
217         assert false;
218
219         return;
220     }
221
222     /**
223      * ファイルに書き込めないエラーをダイアログで提示し、VM終了する。
224      *
225      * @param path 書き込めなかったファイル
226      */
227     private static void abortCantWrite(Path path){
228         String form =
229                 "<html>"
230                 + "ファイル<br/>"
231                 + "{0}"
232                 + "への書き込みができません。"
233                 + "起動を中止します。<br/>"
234                 + "</html>";
235         String fileName = getCenteredFileName(path);
236         String msg = MessageFormat.format(form, fileName);
237
238         showErrorMessage(msg);
239         abort();
240         assert false;
241
242         return;
243     }
244
245     /**
246      * 設定ディレクトリがアクセス可能でなければ
247      * エラーダイアログを出してVM終了する。
248      *
249      * @param confDir 設定ディレクトリ
250      */
251     public static void checkDirPerm(Path confDir){
252         if( ! FileUtils.isAccessibleDirectory(confDir) ){
253             abortCantAccessDir(confDir);
254         }
255
256         return;
257     }
258
259     /**
260      * アプリケーション設定ディレクトリを返す。
261      *
262      * <p>存在の有無、アクセスの可否は関知しない。
263      *
264      * <p>WindowsやLinuxではホームディレクトリ。
265      * Mac OS X ではさらにホームディレクトリの下の
266      * "Library/Application Support/"
267      *
268      * @return アプリケーション設定ディレクトリ
269      */
270     public static Path getAppSetDir(){
271         Path home = FileUtils.getHomeDirectory();
272
273         Path result = home;
274
275         if(FileUtils.isMacOSXFs()){
276             result = result.resolve("Library");
277             result = result.resolve("Application Support");
278         }
279
280         return result;
281     }
282
283     /**
284      * 暗黙的な設定格納ディレクトリを返す。
285      *
286      * <ul>
287      *
288      * <li>起動元JARファイルと同じディレクトリに、
289      * アクセス可能なディレクトリ"Jindolf"が
290      * すでに存在していればそれを返す。
291      *
292      * <li>起動元JARファイルおよび"Jindolf"が発見できなければ、
293      * MacOSX環境の場合"~/Library/Application Support/Jindolf/"を返す。
294      * Windows環境の場合"%USERPROFILE%\Jindolf\"を返す。
295      *
296      * <li>それ以外の環境(Linux,etc?)の場合"~/.jindolf/"を返す。
297      *
298      * </ul>
299      *
300      * <p>返すディレクトリが存在しているか否か、
301      * アクセス可能か否かは呼び出し元で判断せよ。
302      *
303      * @return 設定格納ディレクトリ
304      */
305     public static Path getDefaultConfDirPath(){
306         Path jarParent = FileUtils.getJarDirectory();
307         if(FileUtils.isAccessibleDirectory(jarParent)){
308             Path confPath = jarParent.resolve(JINCONF);
309             if(FileUtils.isAccessibleDirectory(confPath)){
310                 return confPath;
311             }
312         }
313
314         Path appset = getAppSetDir();
315
316         Path result;
317         if(FileUtils.isMacOSXFs() || FileUtils.isWindowsOSFs()){
318             result = appset.resolve(JINCONF);
319         }else{
320             result = appset.resolve(JINCONF_DOT);
321         }
322
323         return result;
324     }
325
326     /**
327      * まだ存在しない設定格納ディレクトリを新規に作成する。
328      *
329      * <p>エラーがあればダイアログ提示とともにVM終了する。
330      *
331      * <p>既に存在すればなにもしない。
332      *
333      * @param confPath 設定格納ディレクトリ
334      * @return 新規に作成した設定格納ディレクトリの絶対パス
335      */
336     public static Path buildConfDirPath(Path confPath){
337         Path absPath = confPath.toAbsolutePath();
338         if(Files.exists(absPath)) return absPath;
339
340         boolean confirmed = confirmBuildConfigDir(absPath);
341         if( ! confirmed ){
342             abortQuitBuildConfigDir();
343             assert false;
344         }
345
346         try{
347             Files.createDirectories(absPath);
348         }catch(IOException | SecurityException e){
349             String msg = MessageFormat.format(
350                     "{0}を生成できません", absPath.toString());
351             LOGGER.log(Level.SEVERE, msg, e);
352             abortCantBuildDir(absPath);
353             assert false;
354         }
355
356         // FileUtils.setOwnerOnlyAccess(absPath);
357
358         checkDirPerm(absPath);
359
360         touchReadme(absPath);
361
362         return absPath;
363     }
364
365     /**
366      * 設定ディレクトリを新規に生成してよいかダイアログで問い合わせる。
367      *
368      * @param confDir 設定ディレクトリ
369      * @return 生成してよいと指示があればtrue
370      */
371     private static boolean confirmBuildConfigDir(Path confDir){
372         String form =
373                 "<html>"
374                 + "設定ファイル格納ディレクトリ<br/>"
375                 + "{0}を作成します。<br/>"
376                 + "このディレクトリを今から作成して構いませんか?<br/>"
377                 + "このディレクトリ名は、後からいつでもヘルプウィンドウで<br/>"
378                 + "確認することができます。"
379                 + "</html>";
380         String confName = getCenteredFileName(confDir);
381         String msg = MessageFormat.format(form, confName);
382
383         JOptionPane pane;
384         pane = new JOptionPane(msg,
385                                JOptionPane.QUESTION_MESSAGE,
386                                JOptionPane.YES_NO_OPTION);
387         showDialog(pane);
388
389         Object val = pane.getValue();
390         if( ! (val instanceof Integer) ) return false;
391         int ival = (int) val;
392         boolean result = ival == JOptionPane.YES_OPTION;
393
394         return result;
395     }
396
397     /**
398      * ローカル画像キャッシュディレクトリを作る。
399      *
400      * <p>作られたディレクトリ内に
401      * ファイルavatarCache.jsonが作られる。
402      *
403      * @param imgCacheDir ローカル画像キャッシュディレクトリ
404      */
405     public static void buildImageCacheDir(Path imgCacheDir){
406         Path absPath = imgCacheDir.toAbsolutePath();
407         if(Files.exists(absPath)) return;
408
409         try{
410             Files.createDirectories(absPath);
411         }catch(IOException e){
412             String msg = MessageFormat.format(
413                     "ディレクトリ{0}を生成できません", absPath.toString());
414             LOGGER.log(Level.SEVERE, msg, e);
415             abortCantBuildDir(absPath);
416             assert false;
417         }
418
419         checkDirPerm(absPath);
420
421         Path jsonPath = imgCacheDir.resolve(FILE_AVATARJSON);
422
423         try(InputStream ris =
424                 ResourceManager.getResourceAsStream(RES_AVATARJSON)){
425             InputStream is = new BufferedInputStream(ris);
426             Files.copy(is, jsonPath);
427         }catch(IOException e){
428             String msg = MessageFormat.format(
429                     "ファイル{0}を生成できませんでした", jsonPath.toString()
430             );
431             LOGGER.log(Level.SEVERE, msg, e);
432             abortCantWrite(jsonPath);
433             assert false;
434         }
435
436         return;
437     }
438
439     /**
440      * ロックエラーダイアログの表示。
441      *
442      * <p>呼び出しから戻ってもまだロックオブジェクトが
443      * ロックファイルのオーナーでない場合、
444      * 今後設定ディレクトリは一切使わずに起動を続行するものとする。
445      *
446      * <p>ロックファイルの強制解除に失敗した場合はVM終了する。
447      *
448      * @param lock エラーを起こしたロック
449      */
450     public static void confirmLockError(InterVMLock lock){
451         File lockFile = lock.getLockFile();
452         LockErrorPane lockPane = new LockErrorPane(lockFile.toPath());
453         JDialog lockDialog = lockPane.createDialog(TITLE_BUILDCONF);
454         lockDialog.setResizable(true);
455         lockDialog.pack();
456
457         for(;;){
458             lockDialog.setVisible(true);
459
460             Object result = lockPane.getValue();
461             boolean aborted = LockErrorPane.isAborted(result);
462             boolean windowClosed = result == null;
463
464             if(aborted || windowClosed){
465                 abort();
466                 assert false;
467                 break;
468             }else if(lockPane.isRadioRetry()){
469                 lock.tryLock();
470                 if(lock.isFileOwner()) break;
471             }else if(lockPane.isRadioContinue()){
472                 String msg =
473                         "<html>"
474                         + "設定ディレクトリを使わずに起動を続行します。<br/>"
475                         + "今回、各種設定の読み込み・保存はできません。<br/>"
476                         + "<code>"
477                         + CmdOption.OPT_NOCONF
478                         + "</code> オプション"
479                         + "を使うとこの警告は出なくなります。"
480                         + "</html>";
481                 showInfoMessage(msg);
482                 break;
483             }else if(lockPane.isRadioForce()){
484                 lock.forceRemove();
485                 if(lock.isExistsFile()){
486                     String form =
487                             "<html>"
488                             + "ロックファイルの強制解除に失敗しました。<br/>"
489                             + "他に動いているJindolf"
490                             + "が見つからないのであれば、<br/>"
491                             + "なんとかしてロックファイル<br/>"
492                             + "{0}"
493                             + "を削除してください。<br/>"
494                             + "起動を中止します。"
495                             + "</html>";
496                     String fileName = getCenteredFileName(lockFile.toPath());
497                     String msg = MessageFormat.format(form, fileName);
498
499                     showErrorMessage(msg);
500                     abort();
501                     assert false;
502
503                     break;
504                 }
505                 lock.tryLock();
506                 if(lock.isFileOwner()) break;
507
508                 String form =
509                         "<html>"
510                         + "ロックファイル<br/>"
511                         + "{0}"
512                         + "を確保することができません。<br/>"
513                         + "起動を中止します。"
514                         + "</html>";
515                 String fileName = getCenteredFileName(lockFile.toPath());
516                 String msg = MessageFormat.format(form, fileName);
517
518                 showErrorMessage(msg);
519                 abort();
520                 assert false;
521
522                 break;
523             }
524         }
525
526         lockDialog.dispose();
527
528         return;
529     }
530
531     /**
532      * 指定されたディレクトリにREADMEファイルを生成する。
533      *
534      * <p>生成できなければダイアログ表示とともにVM終了する。
535      *
536      * @param path READMEの格納ディレクトリ
537      */
538     private static void touchReadme(Path path){
539         Path readme = path.resolve(FILE_README);
540
541         try(InputStream ris =
542                 ResourceManager.getResourceAsStream(RES_README)){
543             InputStream is = new BufferedInputStream(ris);
544             Files.copy(is, readme);
545         }catch(IOException e){
546             String msg = MessageFormat.format(
547                     "{0}が生成できませんでした", readme.toString()
548             );
549             LOGGER.log(Level.SEVERE, msg, e);
550             abortCantWrite(readme);
551             assert false;
552         }
553
554         return;
555     }
556
557 }