2 * configuration file & directory
4 * License : The MIT License
5 * Copyright(c) 2009 olyutorskii
8 package jp.sfjp.jindolf.config;
11 import java.io.FileOutputStream;
12 import java.io.IOException;
13 import java.io.InputStream;
14 import java.io.OutputStream;
15 import java.io.OutputStreamWriter;
16 import java.io.PrintWriter;
17 import java.io.Writer;
18 import java.nio.charset.Charset;
19 import java.nio.charset.StandardCharsets;
20 import java.nio.file.Files;
21 import java.nio.file.Path;
22 import java.nio.file.Paths;
23 import javax.swing.JDialog;
24 import javax.swing.JOptionPane;
25 import jp.sfjp.jindolf.ResourceManager;
26 import jp.sfjp.jindolf.VerInfo;
27 import jp.sfjp.jindolf.view.LockErrorPane;
30 * Jindolf設定格納ディレクトリに関するあれこれ。
32 public final class ConfigDirUtils{
34 private static final String TITLE_BUILDCONF =
35 VerInfo.TITLE + "設定格納ディレクトリの設定";
37 private static final String JINCONF = "Jindolf";
38 private static final String JINCONF_DOT = ".jindolf";
39 private static final String FILE_README = "README.txt";
40 private static final Charset CHARSET_README = StandardCharsets.UTF_8;
42 private static final String MSG_POST =
44 + "<li><code>" + CmdOption.OPT_CONFDIR + "</code>"
45 + " オプション指定により、<br/>"
46 + "任意の設定格納ディレクトリを指定することができます。<br/>"
47 + "<li><code>" + CmdOption.OPT_NOCONF + "</code>"
48 + " オプション指定により、<br/>"
49 + "設定格納ディレクトリを使わずに起動することができます。<br/>"
56 private ConfigDirUtils(){
67 * <li>起動元JARファイルと同じディレクトリに、
68 * アクセス可能なディレクトリ"Jindolf"が
71 * <li>起動元JARファイルおよび"Jindolf"が発見できなければ、
72 * MacOSX環境の場合"~/Library/Application Support/Jindolf/"を返す。
73 * Windows環境の場合"%USERPROFILE%\Jindolf\"を返す。
75 * <li>それ以外の環境(Linux,etc?)の場合"~/.jindolf/"を返す。
79 * <p>返すディレクトリが存在しているか否か、
80 * アクセス可能か否かは呼び出し元で判断せよ。
84 public static File getImplicitConfigDirectory(){
87 File jarParent = FileUtils.getJarDirectory();
88 if(jarParent != null && FileUtils.isAccessibleDirectory(jarParent)){
89 result = new File(jarParent, JINCONF);
90 if(FileUtils.isAccessibleDirectory(result)){
95 File appset = FileUtils.getAppSetDir();
96 if(appset == null) return null;
98 if(FileUtils.isMacOSXFs() || FileUtils.isWindowsOSFs()){
99 result = new File(appset, JINCONF);
101 result = new File(appset, JINCONF_DOT);
108 * まだ存在しない設定格納ディレクトリを新規に作成する。
110 * <p>エラーがあればダイアログ提示とともにVM終了する。
112 * @param confPath 設定格納ディレクトリ
113 * @param isImplicitPath ディレクトリが暗黙的に指定されたものならtrue。
114 * @return 新規に作成した設定格納ディレクトリ
115 * @throws IllegalArgumentException すでにそのディレクトリは存在する。
117 public static File buildConfigDirectory(File confPath,
118 boolean isImplicitPath )
119 throws IllegalArgumentException{
120 if(confPath.exists()) throw new IllegalArgumentException();
122 File absPath = FileUtils.supplyFullPath(confPath);
124 String preErrMessage =
126 + getCenteredFileName(absPath)
128 if( ! isImplicitPath ){
131 + CmdOption.OPT_CONFDIR
132 + "</code> オプション"
137 File existsAncestor = FileUtils.findExistsAncestor(absPath);
138 if(existsAncestor == null){
139 abortNoRoot(absPath, preErrMessage);
140 }else if( ! existsAncestor.canWrite() ){
141 abortCantWriteAncestor(existsAncestor, preErrMessage);
145 "設定ファイル格納ディレクトリ<br/>"
146 + getCenteredFileName(absPath)
148 boolean confirmed = confirmBuildConfigDir(existsAncestor, prompt);
150 abortQuitBuildConfigDir();
155 success = absPath.mkdirs();
156 }catch(SecurityException e){
160 if( ! success || ! absPath.exists() ){
161 abortCantBuildConfigDir(absPath);
164 FileUtils.setOwnerOnlyAccess(absPath);
166 checkAccessibility(absPath);
168 touchReadme(absPath);
174 * ローカル画像キャッシュディレクトリを作る。
177 * ファイルavatarCache.jsonが作られる。
179 * @param imgCacheDir ローカル画像キャッシュディレクトリ
181 public static void buildImageCacheDir(File imgCacheDir){
182 if(imgCacheDir.exists()) return;
184 String jsonRes = "resources/image/avatarCache.json";
185 InputStream is = ResourceManager.getResourceAsStream(jsonRes);
186 if(is == null) return;
188 imgCacheDir.mkdirs();
189 ConfigDirUtils.checkAccessibility(imgCacheDir);
191 Path cachePath = imgCacheDir.toPath();
192 Path jsonLeaf = Paths.get("avatarCache.json");
193 Path path = cachePath.resolve(jsonLeaf);
195 Files.copy(is, path);
196 }catch(IOException e){
197 abortCantAccessConfigDir(path.toFile());
205 * 共通エラーメッセージ確認ダイアログを表示する。
211 private static void showErrorMessage(CharSequence seq){
213 new JOptionPane(seq.toString(),
214 JOptionPane.ERROR_MESSAGE);
221 * 共通エラーメッセージ確認ダイアログを表示する。
227 private static void showWarnMessage(CharSequence seq){
229 new JOptionPane(seq.toString(),
230 JOptionPane.WARNING_MESSAGE);
237 * 情報提示メッセージ確認ダイアログを表示する。
243 private static void showInfoMessage(CharSequence seq){
245 new JOptionPane(seq.toString(),
246 JOptionPane.INFORMATION_MESSAGE);
252 * ダイアログを表示し、閉じられるまで待つ。
254 * @param pane ダイアログの元となるペイン
256 private static void showDialog(JOptionPane pane){
257 JDialog dialog = pane.createDialog(TITLE_BUILDCONF);
258 dialog.setResizable(true);
261 dialog.setVisible(true);
270 private static void abort(){
277 * 設定ディレクトリのルートファイルシステムもしくはドライブレターに
278 * アクセスできないエラーをダイアログに提示し、VM終了する。
280 * @param path 設定ディレクトリ
281 * @param preMessage メッセージ前半
283 private static void abortNoRoot(File path, String preMessage){
284 File root = FileUtils.findRootFile(path);
287 + preMessage + "<br/>"
288 + getCenteredFileName(root)
289 + "を用意する方法が不明です。<br/>"
298 * 設定ディレクトリの祖先に書き込めないエラーをダイアログで提示し、
301 * @param existsAncestor 存在するもっとも近い祖先
302 * @param preMessage メッセージ前半
304 private static void abortCantWriteAncestor(File existsAncestor,
308 + preMessage + "<br/>"
309 + getCenteredFileName(existsAncestor)
311 + "処理の続行は不可能です。<br/>"
320 * 設定ディレクトリを新規に生成してよいかダイアログで問い合わせる。
322 * @param existsAncestor 存在するもっとも近い祖先
323 * @param preMessage メッセージ前半
324 * @return 生成してよいと指示があればtrue
326 private static boolean confirmBuildConfigDir(File existsAncestor,
330 + preMessage + "<br/>"
331 + "このディレクトリを今から<br/>"
332 + getCenteredFileName(existsAncestor)
333 + "に作成して構いませんか?<br/>"
334 + "このディレクトリ名は、後からいつでもヘルプウィンドウで<br/>"
339 new JOptionPane(message,
340 JOptionPane.QUESTION_MESSAGE,
341 JOptionPane.YES_NO_OPTION);
345 Object result = pane.getValue();
346 if(result == null) return false;
347 else if( ! (result instanceof Integer) ) return false;
349 int ival = (Integer) result;
350 if(ival == JOptionPane.YES_OPTION) return true;
356 * 設定ディレクトリ生成をやめた操作への警告をダイアログで提示し、
359 private static void abortQuitBuildConfigDir(){
362 + "設定ディレクトリの作成をせずに起動を中止します。<br/>"
370 * 設定ディレクトリが生成できないエラーをダイアログで提示し、
373 * @param path 生成できなかったディレクトリ
375 private static void abortCantBuildConfigDir(File path){
379 + getCenteredFileName(path)
389 * 設定ディレクトリへアクセスできないエラーをダイアログで提示し、
392 * @param path アクセスできないディレクトリ
394 private static void abortCantAccessConfigDir(File path){
398 + getCenteredFileName(path)
401 + "このディレクトリへのアクセス権を調整し"
402 + "読み書きできるようにしてください。<br/>"
410 * ファイルに書き込めないエラーをダイアログで提示し、VM終了する。
412 * @param file 書き込めなかったファイル
414 private static void abortCantWrite(File file){
418 + getCenteredFileName(file)
427 * 指定されたディレクトリにREADMEファイルを生成する。
429 * <p>生成できなければダイアログ表示とともにVM終了する。
431 * @param path READMEの格納ディレクトリ
433 private static void touchReadme(File path){
434 File file = new File(path, FILE_README);
437 file.createNewFile();
438 }catch(IOException e){
439 abortCantAccessConfigDir(path);
442 PrintWriter writer = null;
444 OutputStream ostream = new FileOutputStream(file);
445 Writer owriter = new OutputStreamWriter(ostream, CHARSET_README);
446 writer = new PrintWriter(owriter);
447 writer.println(CHARSET_README.name() + " Japanese");
450 + "Jindolfの各種設定が格納されるディレクトリです。");
453 + "http://jindolf.osdn.jp/"
457 + "「" + JINCONF + "」"
461 "コピーすれば、そちらの設定が優先して使われます。");
463 "「lock」の名前を持つファイルはロックファイルです。");
464 }catch(IOException | SecurityException e){
465 abortCantWrite(file);
476 * 設定ディレクトリがアクセス可能でなければ
477 * エラーダイアログを出してVM終了する。
479 * @param confDir 設定ディレクトリ
481 public static void checkAccessibility(File confDir){
482 if( ! FileUtils.isAccessibleDirectory(confDir) ){
483 abortCantAccessConfigDir(confDir);
490 * センタリングされたファイル名表示のHTML表記を出力する。
495 public static String getCenteredFileName(File path){
496 return "<center>[ "
497 + FileUtils.getHtmledFileName(path)
505 * <p>呼び出しから戻ってもまだロックオブジェクトが
507 * 今後設定ディレクトリは一切使わずに起動を続行するものとする。
509 * <p>ロックファイルの強制解除に失敗した場合はVM終了する。
511 * @param lock エラーを起こしたロック
513 public static void confirmLockError(InterVMLock lock){
514 File lockFile = lock.getLockFile();
515 LockErrorPane lockPane = new LockErrorPane(lockFile);
516 JDialog lockDialog = lockPane.createDialog(TITLE_BUILDCONF);
517 lockDialog.setResizable(true);
521 lockDialog.setVisible(true);
523 Object result = lockPane.getValue();
524 boolean aborted = LockErrorPane.isAborted(result);
525 boolean windowClosed = result == null;
527 if(aborted || windowClosed){
530 }else if(lockPane.isRadioRetry()){
532 if(lock.isFileOwner()) break;
533 }else if(lockPane.isRadioContinue()){
536 + "設定ディレクトリを使わずに起動を続行します。<br/>"
537 + "今回、各種設定の読み込み・保存はできません。<br/>"
539 + CmdOption.OPT_NOCONF
541 + "を使うとこの警告は出なくなります。"
544 }else if(lockPane.isRadioForce()){
546 if(lock.isExistsFile()){
549 + "ロックファイルの強制解除に失敗しました。<br/>"
551 + "が見つからないのであれば、<br/>"
552 + "なんとかしてロックファイル<br/>"
553 + getCenteredFileName(lock.getLockFile())
561 if(lock.isFileOwner()) break;
565 + getCenteredFileName(lock.getLockFile())
566 + "を確保することができません。<br/>"
574 lockDialog.dispose();