KDP(電子出版)のメモ 急急如律令

Amazon Kindleダイレクト・パブリッシングでの電子出版や電子書籍の作成販売について、文章やイラストの作成や編集方法について書いています。

Windowsインストーラー向けにjarを生成

Gradleビルドでwindowsインストーラー向けにjarを生成している。inttelijで実行時にテンプレートファイルが見つからないから this.jarPath = "app/";

jpackageコマンド

jpackage  --type msi --win-menu --win-dir-chooser --win-shortcut --win-shortcut-prompt --copyright kyukyunyorituryo --icon AozoraEpub3.ico --app-version 1.1.1.31 --description "改造版AozoraEpub3 青空文庫テキストePub3変換"  -n "AozoraEpub3" --main-jar AozoraEpub3.jar --input C:\Users\Owner\Desktop\AozoraEpub3-1.1.1b31Q 

あなた:

jarファイルで配布しているバージョンとインストーラーとも互換性を保ちたい

String propFileName = "AozoraEpub3.ini";
    this.jarPath = "";
            //設定ファイル読み込み
    props = new Properties();
    try {
        if (Files.exists(Path.of(this.jarPath + this.propFileName))) {
            FileInputStream fos = new FileInputStream(this.jarPath + this.propFileName);
            props.load(fos);
            fos.close();
        }
    } catch (Exception e) {
        throw new RuntimeException(e);
    }
            //アプレットの設定をPropertiesに反映
    this.setProperties(this.props);

    //設定ファイル更新
    FileOutputStream fos = new FileOutputStream(this.jarPath+this.propFileName);
    this.props.store(fos, "AozoraEpub3 Parameters");
    fos.close();

ChatGPT:

なるほど、 jar 配布版(ユーザーが java -jar で動かす)と、jpackage で作ったインストーラー版(exe+同梱JRE)で同じ設定ファイル AozoraEpub3.ini を共通にしたい、ということですね。

つまり:

  • 両方のバージョンで 同じ場所の ini を読んで書きたい
  • ただし、jpackage 版では 実行パス構造(app/lib/ 配下など) が変わる

両方のバージョンで 同じ場所の ini を読んで書きたい

ただし、jpackage 版では 実行パス構造(app/lib/ 配下など) が変わる

これを両立させる方法を整理します👇

配布形式 実行パス 現在の this.jarPath 問題点
jar版 MyApp.jar のあるフォルダ "" または jar のパス OK
jpackage版 MyApp/app/lib/MyApp.jar の中で実行 "" だと app/lib 配下を見てしまう ini が見つからない/別フォルダに作られる

「jarが置かれている場所」ではなく、 「実行アプリのルート(=exeやrun-cli.batがある場所)」を this.jarPath に設定すれば両方対応できます。

String propFileName = "AozoraEpub3.ini";

// jarPath を実行環境に応じて決定
String jarPath = getBasePath();
this.jarPath = jarPath.endsWith(File.separator) ? jarPath : jarPath + File.separator;

// 設定ファイル読み込み
Properties props = new Properties();
try {
    Path iniPath = Path.of(this.jarPath, propFileName);
    if (Files.exists(iniPath)) {
        try (FileInputStream fis = new FileInputStream(iniPath.toFile())) {
            props.load(fis);
        }
    }
} catch (Exception e) {
    throw new RuntimeException("設定ファイル読み込みエラー: " + e.getMessage(), e);
}

// 設定反映
this.setProperties(props);

// 設定ファイル更新(例として即保存)
try (FileOutputStream fos = new FileOutputStream(Path.of(this.jarPath, propFileName).toFile())) {
    props.store(fos, "AozoraEpub3 Parameters");
}

String propFileName = "AozoraEpub3.ini";

// jarPath を実行環境に応じて決定
String jarPath = getBasePath();
this.jarPath = jarPath.endsWith(File.separator) ? jarPath : jarPath + File.separator;

// 設定ファイル読み込み
Properties props = new Properties();
try {
    Path iniPath = Path.of(this.jarPath, propFileName);
    if (Files.exists(iniPath)) {
        try (FileInputStream fis = new FileInputStream(iniPath.toFile())) {
            props.load(fis);
        }
    }
} catch (Exception e) {
    throw new RuntimeException("設定ファイル読み込みエラー: " + e.getMessage(), e);
}

// 設定反映
this.setProperties(props);

// 設定ファイル更新(例として即保存)
try (FileOutputStream fos = new FileOutputStream(Path.of(this.jarPath, propFileName).toFile())) {
    props.store(fos, "AozoraEpub3 Parameters");
}
private static String getBasePath() {
    try {
        // 実行 jar の場所を取得
        String path = AozoraEpub3.class.getProtectionDomain().getCodeSource().getLocation().toURI().getPath();
        File jarFile = new File(path);

        // jpackage版(app/lib/配下にある)なら2階層上へ
        File dir = jarFile.getParentFile();
        if (dir.getName().equals("lib") && dir.getParentFile().getName().equals("app")) {
            return dir.getParentFile().getParent(); // exeがあるフォルダ
        } else {
            return dir.getAbsolutePath(); // 通常jar版
        }
    } catch (Exception e) {
        // フォールバック:カレントディレクトリ
        return new File(".").getAbsolutePath();
    }
}

private static String getBasePath() {
    try {
        // 実行 jar の場所を取得
        String path = AozoraEpub3.class.getProtectionDomain().getCodeSource().getLocation().toURI().getPath();
        File jarFile = new File(path);

        // jpackage版(app/lib/配下にある)なら2階層上へ
        File dir = jarFile.getParentFile();
        if (dir.getName().equals("lib") && dir.getParentFile().getName().equals("app")) {
            return dir.getParentFile().getParent(); // exeがあるフォルダ
        } else {
            return dir.getAbsolutePath(); // 通常jar版
        }
    } catch (Exception e) {
        // フォールバック:カレントディレクトリ
        return new File(".").getAbsolutePath();
    }
}
配布形態 実行場所 getBasePath() の戻り値 ini 保存先
jar版 MyApp.jar と同じ場所 MyApp/ MyApp/AozoraEpub3.ini
jpackage版 MyApp.exe MyApp/ MyApp/AozoraEpub3.ini
run-cli.bat 版 MyApp/run-cli.bat MyApp/ MyApp/AozoraEpub3.ini

これで完全に共通化できます。

将来的に「設定をユーザープロファイル内に保存したい」なら:

String userDir = System.getProperty("user.home") + "/.aozoraepub3/";
Files.createDirectories(Path.of(userDir));

String userDir = System.getProperty("user.home") + "/.aozoraepub3/";
Files.createDirectories(Path.of(userDir));

なども可能です(EPUBアプリでよく使う構成)。

目的 解決策
jar配布とjpackage配布でini共通化 getBasePath()でルートパス自動検出
同梱JRE CLI版(run-cli.bat)でも動作 同じフォルダ構成なのでOK
日本語ファイル名・パス対応 Path.of() + UTF-8で問題なし

もし AozoraEpub3.ini を「アプリと同じ場所」に置く方針で決まりなら、 上の getBasePath() を使うのが一番安全です。

必要であれば、このコードを既存クラス構造(AozoraEpub3クラス全体)に組み込んだ完全版を出します。 それを希望しますか?

あなた:

program filesに保存できないときはAppDataに保存して。 this.jarPathは他で利用しているので変更しないで別の名前を作って。 cliは別に作るので考えなくていい。

ChatGPT:

了解です。 要件を整理すると、次のようになります 👇

  • this.jarPath は既存の用途のため 変更しない
  • 設定ファイル AozoraEpub3.ini を

アプリと同じ場所に保存できるならそこに保存

Program Files 配下など書き込み不可なら AppData に保存 - アプリと同じ場所に保存できるならそこに保存 - Program Files 配下など書き込み不可なら AppData に保存 - GUI 版のみ対象(CLIは別対応不要) - 既存の AozoraEpub3.ini の読込/保存ロジックをできるだけ維持したい

this.jarPath は既存の用途のため 変更しない

設定ファイル AozoraEpub3.ini を

  • アプリと同じ場所に保存できるならそこに保存
  • Program Files 配下など書き込み不可なら AppData に保存

アプリと同じ場所に保存できるならそこに保存

Program Files 配下など書き込み不可なら AppData に保存

GUI 版のみ対象(CLIは別対応不要)

既存の AozoraEpub3.ini の読込/保存ロジックをできるだけ維持したい

  1. 新しいプロパティ this.configPath を追加。 保存先を「アプリのルートまたはAppData」に自動切り替える。
  2. 書き込み不可の場所なら例外を検知し、自動でAppDataへ退避。
  3. 読み込み時は優先順位を

① アプリのルート(iniが存在すれば読む)

② AppData の順にする。 4. ① アプリのルート(iniが存在すれば読む) 5. ② AppData の順にする。

新しいプロパティ this.configPath を追加。 保存先を「アプリのルートまたはAppData」に自動切り替える。

書き込み不可の場所なら例外を検知し、自動でAppDataへ退避。

読み込み時は優先順位を

  • ① アプリのルート(iniが存在すれば読む)
  • ② AppData の順にする。

① アプリのルート(iniが存在すれば読む)

② AppData の順にする。

String propFileName = "AozoraEpub3.ini";
this.jarPath = ""; // ← 既存のまま維持
this.configPath = determineConfigPath(propFileName); // 新たに追加

Properties props = new Properties();

// ---- 設定ファイル読み込み ----
try {
    Path iniPath = Path.of(this.configPath, propFileName);
    if (Files.exists(iniPath)) {
        try (FileInputStream fis = new FileInputStream(iniPath.toFile())) {
            props.load(fis);
        }
    }
} catch (Exception e) {
    throw new RuntimeException("設定ファイル読み込みエラー: " + e.getMessage(), e);
}

// ---- 設定を反映 ----
this.setProperties(props);

// ---- 設定ファイル更新 ----
try {
    Path iniPath = Path.of(this.configPath, propFileName);
    try (FileOutputStream fos = new FileOutputStream(iniPath.toFile())) {
        props.store(fos, "AozoraEpub3 Parameters");
    }
} catch (IOException e) {
    throw new RuntimeException("設定ファイル書き込みエラー: " + e.getMessage(), e);
}

String propFileName = "AozoraEpub3.ini";
this.jarPath = ""; // ← 既存のまま維持
this.configPath = determineConfigPath(propFileName); // 新たに追加

Properties props = new Properties();

// ---- 設定ファイル読み込み ----
try {
    Path iniPath = Path.of(this.configPath, propFileName);
    if (Files.exists(iniPath)) {
        try (FileInputStream fis = new FileInputStream(iniPath.toFile())) {
            props.load(fis);
        }
    }
} catch (Exception e) {
    throw new RuntimeException("設定ファイル読み込みエラー: " + e.getMessage(), e);
}

// ---- 設定を反映 ----
this.setProperties(props);

// ---- 設定ファイル更新 ----
try {
    Path iniPath = Path.of(this.configPath, propFileName);
    try (FileOutputStream fos = new FileOutputStream(iniPath.toFile())) {
        props.store(fos, "AozoraEpub3 Parameters");
    }
} catch (IOException e) {
    throw new RuntimeException("設定ファイル書き込みエラー: " + e.getMessage(), e);
}
private String determineConfigPath(String propFileName) {
    try {
        // 実行Jarまたはexeの位置を取得
        String basePath = AozoraEpub3.class
                .getProtectionDomain()
                .getCodeSource()
                .getLocation()
                .toURI()
                .getPath();

        File baseDir = new File(basePath).getParentFile();

        // jpackage構成(app/lib配下)の場合は2階層上に戻す
        if (baseDir.getName().equals("lib") && baseDir.getParentFile().getName().equals("app")) {
            baseDir = baseDir.getParentFile().getParentFile();
        }

        Path appDir = baseDir.toPath();
        Path iniPath = appDir.resolve(propFileName);

        // ① アプリフォルダに書き込み可能ならそのまま使う
        if (Files.exists(iniPath) || canWrite(appDir)) {
            return appDir.toString();
        }

        // ② 書けない場合は AppData を使用
        Path appDataDir = Path.of(System.getenv("APPDATA"), "AozoraEpub3");
        Files.createDirectories(appDataDir);
        return appDataDir.toString();

    } catch (Exception e) {
        // フォールバック:AppData
        Path appDataDir = Path.of(System.getenv("APPDATA"), "AozoraEpub3");
        try {
            Files.createDirectories(appDataDir);
        } catch (IOException ignored) {}
        return appDataDir.toString();
    }
}

private boolean canWrite(Path dir) {
    try {
        Path tmp = dir.resolve(".write_test");
        Files.createFile(tmp);
        Files.delete(tmp);
        return true;
    } catch (IOException e) {
        return false;
    }
}

private String determineConfigPath(String propFileName) {
    try {
        // 実行Jarまたはexeの位置を取得
        String basePath = AozoraEpub3.class
                .getProtectionDomain()
                .getCodeSource()
                .getLocation()
                .toURI()
                .getPath();

        File baseDir = new File(basePath).getParentFile();

        // jpackage構成(app/lib配下)の場合は2階層上に戻す
        if (baseDir.getName().equals("lib") && baseDir.getParentFile().getName().equals("app")) {
            baseDir = baseDir.getParentFile().getParentFile();
        }

        Path appDir = baseDir.toPath();
        Path iniPath = appDir.resolve(propFileName);

        // ① アプリフォルダに書き込み可能ならそのまま使う
        if (Files.exists(iniPath) || canWrite(appDir)) {
            return appDir.toString();
        }

        // ② 書けない場合は AppData を使用
        Path appDataDir = Path.of(System.getenv("APPDATA"), "AozoraEpub3");
        Files.createDirectories(appDataDir);
        return appDataDir.toString();

    } catch (Exception e) {
        // フォールバック:AppData
        Path appDataDir = Path.of(System.getenv("APPDATA"), "AozoraEpub3");
        try {
            Files.createDirectories(appDataDir);
        } catch (IOException ignored) {}
        return appDataDir.toString();
    }
}

private boolean canWrite(Path dir) {
    try {
        Path tmp = dir.resolve(".write_test");
        Files.createFile(tmp);
        Files.delete(tmp);
        return true;
    } catch (IOException e) {
        return false;
    }
}
実行場所 設定ファイル保存先 動作例
通常のフォルダ(書き込み可) 同フォルダ AozoraEpub3/AozoraEpub3.ini
Program Files(書き込み不可) %AppData%\AozoraEpub3\ C:\Users\\AppData\Roaming\AozoraEpub3\AozoraEpub3.ini
jar配布版 jarと同じフォルダ 同上判定で自動処理
  • AppData に移行した際でも、ユーザー設定は AozoraEpub3.ini で共通フォーマットのまま保持。
  • jar 配布版と jpackage 版で 読み書き先は自動的に調整される。
  • this.jarPath を既存処理で引き続き利用可能。

AppData に移行した際でも、ユーザー設定は AozoraEpub3.ini で共通フォーマットのまま保持。

jar 配布版と jpackage 版で 読み書き先は自動的に調整される。

this.jarPath を既存処理で引き続き利用可能。

この構成で、

  • jar版・exe版どちらも動作
  • Program Filesなどの制限環境でも自動保存先切替 が両立します。

jar版・exe版どちらも動作

Program Filesなどの制限環境でも自動保存先切替 が両立します。

ご希望があれば、AozoraEpub3 クラス全体(既存構造を維持した完全統合版)として再出力もできます。 その場合、この設定読み書き部分が含まれるクラス全体を貼ってもらえますか?