2 * configuration file & directory
4 * License : The MIT License
5 * Copyright(c) 2009 olyutorskii
8 package jp.sfjp.jindolf.config;
10 import java.io.BufferedInputStream;
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 javax.swing.JDialog;
19 import javax.swing.JOptionPane;
20 import jp.sfjp.jindolf.ResourceManager;
21 import jp.sfjp.jindolf.VerInfo;
22 import jp.sfjp.jindolf.view.LockErrorPane;
25 * Jindolf設定格納ディレクトリに関するあれこれ。
27 public final class ConfigDirUtils{
29 private static final String TITLE_BUILDCONF =
30 VerInfo.TITLE + "設定格納ディレクトリの設定";
32 private static final String JINCONF = "Jindolf";
33 private static final String JINCONF_DOT = ".jindolf";
34 private static final String FILE_README = "README.txt";
36 private static final String RES_DIR = "resources";
37 private static final String RES_README = RES_DIR + "/README.txt";
39 private static final String MSG_POST =
41 + "<li><code>" + CmdOption.OPT_CONFDIR + "</code>"
42 + " オプション指定により、<br/>"
43 + "任意の設定格納ディレクトリを指定することができます。<br/>"
44 + "<li><code>" + CmdOption.OPT_NOCONF + "</code>"
45 + " オプション指定により、<br/>"
46 + "設定格納ディレクトリを使わずに起動することができます。<br/>"
49 private static final int ERR_ABORT = 1;
55 private ConfigDirUtils(){
65 private static void abort(){
66 System.exit(ERR_ABORT);
72 * ダイアログを表示し、閉じられるまで待つ。
74 * @param pane ダイアログの元となるペイン
76 private static void showDialog(JOptionPane pane){
77 JDialog dialog = pane.createDialog(TITLE_BUILDCONF);
78 dialog.setResizable(true);
81 dialog.setVisible(true);
89 * 共通エラーメッセージ確認ダイアログを表示する。
95 private static void showErrorMessage(String txt){
97 pane = new JOptionPane(txt, JOptionPane.ERROR_MESSAGE);
104 * 共通エラーメッセージ確認ダイアログを表示する。
108 * @param seq メッセージtxt
110 private static void showWarnMessage(String txt){
112 pane = new JOptionPane(txt, JOptionPane.WARNING_MESSAGE);
119 * 情報提示メッセージ確認ダイアログを表示する。
123 * @param seq メッセージtxt
125 private static void showInfoMessage(String txt){
127 pane = new JOptionPane(txt, JOptionPane.INFORMATION_MESSAGE);
133 * センタリングされたファイル名表示のHTML表記を出力する。
138 public static String getCenteredFileName(Path path){
139 String form = "<center>[ {0} ]</center><br/>";
140 String fileName = FileUtils.getHtmledFileName(path);
141 String result = MessageFormat.format(form, fileName);
146 * 設定ディレクトリ生成をやめた操作への警告をダイアログで提示し、
149 private static void abortQuitBuildConfigDir(){
152 + "設定ディレクトリの作成をせずに起動を中止します。<br/>"
156 showWarnMessage(msg);
163 * 設定ディレクトリが生成できないエラーをダイアログで提示し、
166 * @param path 生成できなかったディレクトリ
168 private static void abortCantBuildConfigDir(Path path){
177 String fileName = getCenteredFileName(path);
178 String msg = MessageFormat.format(form, fileName);
180 showErrorMessage(msg);
187 * 設定ディレクトリへアクセスできないエラーをダイアログで提示し、
190 * @param path アクセスできないディレクトリ
192 private static void abortCantAccessConfigDir(Path path){
199 + "このディレクトリへのアクセス権を調整し"
200 + "読み書きできるようにしてください。<br/>"
203 String fileName = getCenteredFileName(path);
204 String msg = MessageFormat.format(form, fileName);
206 showErrorMessage(msg);
213 * ファイルに書き込めないエラーをダイアログで提示し、VM終了する。
215 * @param path 書き込めなかったファイル
217 private static void abortCantWrite(Path path){
225 String fileName = getCenteredFileName(path);
226 String msg = MessageFormat.format(form, fileName);
228 showErrorMessage(msg);
235 * 設定ディレクトリのルートファイルシステムもしくはドライブレターに
236 * アクセスできないエラーをダイアログに提示し、VM終了する。
238 * @param path 設定ディレクトリ
239 * @param preMessage メッセージ前半
241 private static void abortNoRoot(Path path, String preMessage){
245 + "{1}を用意する方法が不明です。<br/>"
250 Path root = path.getRoot();
251 String fileName = getCenteredFileName(root);
252 String msg = MessageFormat.format(form, preMessage, fileName);
254 showErrorMessage(msg);
261 * 設定ディレクトリの祖先に書き込めないエラーをダイアログで提示し、
264 * @param existsAncestor 存在するもっとも近い祖先
265 * @param preMessage メッセージ前半
267 private static void abortCantWriteAncestor(Path existsAncestor,
272 + "{1}への書き込みができないため、"
273 + "処理の続行は不可能です。<br/>"
278 String fileName = getCenteredFileName(existsAncestor);
279 String msg = MessageFormat.format(form, preMessage, fileName);
281 showErrorMessage(msg);
288 * 設定ディレクトリがアクセス可能でなければ
289 * エラーダイアログを出してVM終了する。
291 * @param confDir 設定ディレクトリ
293 public static void checkDirPerm(Path confDir){
294 if( ! FileUtils.isAccessibleDirectory(confDir) ){
295 abortCantAccessConfigDir(confDir);
302 * アプリケーション設定ディレクトリを返す。
304 * <p>存在の有無、アクセスの可否は関知しない。
306 * <p>WindowsやLinuxではホームディレクトリ。
307 * Mac OS X ではさらにホームディレクトリの下の
308 * "Library/Application Support/"
310 * @return アプリケーション設定ディレクトリ
312 public static Path getAppSetDir(){
313 Path home = FileUtils.getHomeDirectory();
314 if(home == null) return null;
318 if(FileUtils.isMacOSXFs()){
319 result = result.resolve("Library");
320 result = result.resolve("Application Support");
331 * <li>起動元JARファイルと同じディレクトリに、
332 * アクセス可能なディレクトリ"Jindolf"が
335 * <li>起動元JARファイルおよび"Jindolf"が発見できなければ、
336 * MacOSX環境の場合"~/Library/Application Support/Jindolf/"を返す。
337 * Windows環境の場合"%USERPROFILE%\Jindolf\"を返す。
339 * <li>それ以外の環境(Linux,etc?)の場合"~/.jindolf/"を返す。
343 * <p>返すディレクトリが存在しているか否か、
344 * アクセス可能か否かは呼び出し元で判断せよ。
348 public static Path getImplicitConfigDirectory(){
349 Path jarParent = FileUtils.getJarDirectory();
350 if(jarParent != null && FileUtils.isAccessibleDirectory(jarParent)){
351 Path confPath = jarParent.resolve(JINCONF);
352 if(FileUtils.isAccessibleDirectory(confPath)){
357 Path appset = getAppSetDir();
358 if(appset == null) return null;
361 if(FileUtils.isMacOSXFs() || FileUtils.isWindowsOSFs()){
362 result = appset.resolve(JINCONF);
364 result = appset.resolve(JINCONF_DOT);
371 * まだ存在しない設定格納ディレクトリを新規に作成する。
373 * <p>エラーがあればダイアログ提示とともにVM終了する。
375 * @param confPath 設定格納ディレクトリ
376 * @param isImplicitPath ディレクトリが暗黙的に指定されたものならtrue。
377 * @return 新規に作成した設定格納ディレクトリ
378 * @throws IllegalArgumentException すでにそのディレクトリは存在する。
380 public static Path buildConfigDirectory(Path confPath,
381 boolean isImplicitPath )
382 throws IllegalArgumentException{
383 if(Files.exists(confPath)) throw new IllegalArgumentException();
385 Path absPath = confPath.toAbsolutePath();
393 + CmdOption.OPT_CONFDIR
394 + "</code> オプション"
398 String fileName = getCenteredFileName(absPath);
403 String preErrMessage = MessageFormat.format(form, optlead, fileName);
405 Path existsAncestor = FileUtils.findExistsAncestor(absPath);
406 if(existsAncestor == null){
407 abortNoRoot(absPath, preErrMessage);
408 }else if( ! Files.isWritable(existsAncestor) ){
409 abortCantWriteAncestor(existsAncestor, preErrMessage);
413 "設定ファイル格納ディレクトリ<br/>"
415 String dirName = getCenteredFileName(absPath);
416 String prompt = MessageFormat.format(promptForm, dirName);
417 boolean confirmed = confirmBuildConfigDir(existsAncestor, prompt);
419 abortQuitBuildConfigDir();
424 Files.createDirectories(absPath);
426 }catch(IOException | SecurityException e){
430 if( ! success || ! Files.exists(absPath) ){
431 abortCantBuildConfigDir(absPath);
434 // FileUtils.setOwnerOnlyAccess(absPath);
436 checkDirPerm(absPath);
438 touchReadme(absPath);
444 * ローカル画像キャッシュディレクトリを作る。
447 * ファイルavatarCache.jsonが作られる。
449 * @param imgCacheDir ローカル画像キャッシュディレクトリ
451 public static void buildImageCacheDir(Path imgCacheDir){
452 if(Files.exists(imgCacheDir)) return;
455 Files.createDirectories(imgCacheDir);
456 }catch(IOException e){
459 ConfigDirUtils.checkDirPerm(imgCacheDir);
461 String jsonRes = "resources/image/avatarCache.json";
462 InputStream is = ResourceManager.getResourceAsStream(jsonRes);
463 if(is == null) return;
465 Path cachePath = imgCacheDir;
466 Path jsonLeaf = Paths.get("avatarCache.json");
467 Path path = cachePath.resolve(jsonLeaf);
469 try(InputStream bis = new BufferedInputStream(is)){
470 Files.copy(bis, path);
471 }catch(IOException e){
472 abortCantAccessConfigDir(path);
479 * 設定ディレクトリを新規に生成してよいかダイアログで問い合わせる。
481 * @param existsAncestor 存在するもっとも近い祖先
482 * @param preMessage メッセージ前半
483 * @return 生成してよいと指示があればtrue
485 private static boolean confirmBuildConfigDir(Path existsAncestor,
490 + "このディレクトリを今から<br/>"
491 + "{1}に作成して構いませんか?<br/>"
492 + "このディレクトリ名は、後からいつでもヘルプウィンドウで<br/>"
495 String fileName = getCenteredFileName(existsAncestor);
496 String msg = MessageFormat.format(form, preMessage, fileName);
499 pane = new JOptionPane(msg,
500 JOptionPane.QUESTION_MESSAGE,
501 JOptionPane.YES_NO_OPTION);
505 Object val = pane.getValue();
506 if( ! (val instanceof Integer) ) return false;
507 int ival = (Integer) val;
508 boolean result = ival == JOptionPane.YES_OPTION;
516 * <p>呼び出しから戻ってもまだロックオブジェクトが
518 * 今後設定ディレクトリは一切使わずに起動を続行するものとする。
520 * <p>ロックファイルの強制解除に失敗した場合はVM終了する。
522 * @param lock エラーを起こしたロック
524 public static void confirmLockError(InterVMLock lock){
525 File lockFile = lock.getLockFile();
526 LockErrorPane lockPane = new LockErrorPane(lockFile.toPath());
527 JDialog lockDialog = lockPane.createDialog(TITLE_BUILDCONF);
528 lockDialog.setResizable(true);
532 lockDialog.setVisible(true);
534 Object result = lockPane.getValue();
535 boolean aborted = LockErrorPane.isAborted(result);
536 boolean windowClosed = result == null;
538 if(aborted || windowClosed){
541 }else if(lockPane.isRadioRetry()){
543 if(lock.isFileOwner()) break;
544 }else if(lockPane.isRadioContinue()){
547 + "設定ディレクトリを使わずに起動を続行します。<br/>"
548 + "今回、各種設定の読み込み・保存はできません。<br/>"
550 + CmdOption.OPT_NOCONF
552 + "を使うとこの警告は出なくなります。"
554 showInfoMessage(msg);
556 }else if(lockPane.isRadioForce()){
558 if(lock.isExistsFile()){
561 + "ロックファイルの強制解除に失敗しました。<br/>"
563 + "が見つからないのであれば、<br/>"
564 + "なんとかしてロックファイル<br/>"
569 String fileName = getCenteredFileName(lockFile.toPath());
570 String msg = MessageFormat.format(form, fileName);
572 showErrorMessage(msg);
578 if(lock.isFileOwner()) break;
584 + "を確保することができません。<br/>"
587 String fileName = getCenteredFileName(lockFile.toPath());
588 String msg = MessageFormat.format(form, fileName);
590 showErrorMessage(msg);
597 lockDialog.dispose();
603 * 指定されたディレクトリにREADMEファイルを生成する。
605 * <p>生成できなければダイアログ表示とともにVM終了する。
607 * @param path READMEの格納ディレクトリ
609 private static void touchReadme(Path path){
610 Path readme = path.resolve(FILE_README);
612 InputStream resReadme =
613 ResourceManager.getResourceAsStream(RES_README);
615 try(InputStream bis = new BufferedInputStream(resReadme)){
616 Files.copy(bis, readme);
617 }catch(IOException e){
618 abortCantWrite(readme);