diff --git a/.github/scripts/windows-publish.ps1 b/.github/scripts/windows-publish.ps1
index 5d2ec84c1..9aedcbf33 100644
--- a/.github/scripts/windows-publish.ps1
+++ b/.github/scripts/windows-publish.ps1
@@ -70,6 +70,14 @@ function Main() {
# Copy-Item -Path $multimedia -Destination $archiveName\plugins -Recurse
# }
+ $multimedia_ffmpeg_av_dll="{0}\bin\av*.dll" -f $env:QTDIR.Trim()
+ $multimedia_ffmpeg_sw_dll="{0}\bin\sw*.dll" -f $env:QTDIR.Trim()
+ if (Test-Path $multimedia_ffmpeg_av_dll && Test-Path $multimedia_ffmpeg_sw_dll) {
+ Write-Host "copy multimedia_ffmpeg_dlls $($multimedia_ffmpeg_av_dll) $($multimedia_ffmpeg_sw_dll) from qt"
+ Copy-Item -Path $multimedia_ffmpeg_av_dll -Destination $archiveName\
+ Copy-Item -Path $multimedia_ffmpeg_sw_dll -Destination $archiveName\
+ }
+
Write-Host "compress zip..."
# 打包zip
Compress-Archive -Path $archiveName -DestinationPath $archiveName'.zip'
diff --git a/.github/workflows/AutoTag.yml b/.github/workflows/AutoTag.yml
index 02f147a59..2d0488680 100644
--- a/.github/workflows/AutoTag.yml
+++ b/.github/workflows/AutoTag.yml
@@ -8,6 +8,7 @@ on:
branches:
- dev
- master
+ - experimental
paths-ignore:
- 'docs/**'
@@ -26,7 +27,7 @@ jobs:
prerelease: true
steps:
- - uses: actions/checkout@v3
+ - uses: actions/checkout@v4
- name: set git short sha
id: vars
diff --git a/.github/workflows/Sonar Cloud.yml b/.github/workflows/Sonar Cloud.yml
index 577e8a6ae..a036a4fdb 100644
--- a/.github/workflows/Sonar Cloud.yml
+++ b/.github/workflows/Sonar Cloud.yml
@@ -46,11 +46,11 @@ jobs:
sudo apt install openjdk-17-jdk openjdk-17-jre
java -version
- - uses: actions/checkout@v3
+ - uses: actions/checkout@v4
with:
fetch-depth: 0 # Shallow clones should be disabled for a better relevancy of analysis
submodules: true
- - uses: actions/setup-node@v3
+ - uses: actions/setup-node@v4
with:
node-version: 18
- name: Install sonar-scanner and build-wrapper
diff --git a/.github/workflows/auto format.yml b/.github/workflows/auto format.yml
index 814538d0f..2cc5467dd 100644
--- a/.github/workflows/auto format.yml
+++ b/.github/workflows/auto format.yml
@@ -6,6 +6,7 @@ on:
# - dev
# - master
- staged
+ - experimental
paths-ignore:
- "docs/**"
# - ".github/**"
@@ -18,7 +19,7 @@ jobs:
clang-format:
runs-on: ubuntu-latest
steps:
- - uses: actions/checkout@v3.5.2
+ - uses: actions/checkout@v4
with:
fetch-depth: 2
@@ -35,10 +36,10 @@ jobs:
css-js-html-format:
runs-on: ubuntu-latest
steps:
- - uses: actions/checkout@v3
+ - uses: actions/checkout@v4
with:
fetch-depth: 2
- - uses: actions/setup-node@v3
+ - uses: actions/setup-node@v4
with:
node-version: 16
diff --git a/.github/workflows/cmake build check.yml b/.github/workflows/cmake build check.yml
index 8d93fe3f1..a4126db5d 100644
--- a/.github/workflows/cmake build check.yml
+++ b/.github/workflows/cmake build check.yml
@@ -8,6 +8,7 @@ on:
branches:
- dev
- master
+ - experimental
- staged
paths-ignore:
- 'docs/**'
@@ -50,7 +51,7 @@ jobs:
sudo make install
cd ..
sudo apt-get install libzim-dev
- - uses: actions/checkout@v3
+ - uses: actions/checkout@v4
with:
fetch-depth: 0 # Shallow clones should be disabled for a better relevancy of analysis
submodules: false
@@ -77,7 +78,7 @@ jobs:
modules: qtwebengine qtwebchannel qtpositioning qt5compat qtmultimedia qtimageformats qtspeech qtserialport
setup-python: 'false'
- uses: ilammy/msvc-dev-cmd@v1
- - uses: actions/checkout@v3
+ - uses: actions/checkout@v4
with:
fetch-depth: 0 # Shallow clones should be disabled for a better relevancy of analysis
submodules: true
@@ -103,7 +104,7 @@ jobs:
modules: qtwebengine qtwebchannel qtpositioning qt5compat qtmultimedia qtimageformats qtspeech qtserialport
setup-python: 'false'
- uses: ilammy/msvc-dev-cmd@v1
- - uses: actions/checkout@v3
+ - uses: actions/checkout@v4
with:
fetch-depth: 0 # Shallow clones should be disabled for a better relevancy of analysis
submodules: false
diff --git a/.github/workflows/deploy_website.yml b/.github/workflows/deploy_website.yml
index 1a6972913..7136ef3a2 100644
--- a/.github/workflows/deploy_website.yml
+++ b/.github/workflows/deploy_website.yml
@@ -14,7 +14,7 @@ jobs:
deploy:
runs-on: ubuntu-latest
steps:
- - uses: actions/checkout@v3
+ - uses: actions/checkout@v4
- uses: actions/setup-python@v4
with:
python-version: 3.x
diff --git a/.github/workflows/macos-arm-homebrew.yml b/.github/workflows/macos-arm-homebrew.yml
index fd76c2bd6..cc32931d7 100644
--- a/.github/workflows/macos-arm-homebrew.yml
+++ b/.github/workflows/macos-arm-homebrew.yml
@@ -8,6 +8,7 @@ on:
branches:
- dev
- master
+ - experimental
# - staged
paths-ignore:
- 'docs/**'
@@ -30,7 +31,7 @@ jobs:
version-suffix: alpha
prerelease: true
steps:
- - uses: actions/checkout@v3
+ - uses: actions/checkout@v4
with:
fetch-depth: 0
submodules: true
diff --git a/.github/workflows/macos-homebrew-PR-check.yml b/.github/workflows/macos-homebrew-PR-check.yml
index ecd735fe0..4c564b9b2 100644
--- a/.github/workflows/macos-homebrew-PR-check.yml
+++ b/.github/workflows/macos-homebrew-PR-check.yml
@@ -10,6 +10,7 @@ on:
branches:
- dev
- master
+ - experimental
- staged
paths-ignore:
- 'docs/**'
@@ -38,7 +39,7 @@ jobs:
arch: ${{ matrix.qt_arch }}
modules: qtwebengine qtwebchannel qtpositioning qt5compat qtmultimedia qtspeech
- - uses: actions/checkout@v3
+ - uses: actions/checkout@v4
with:
fetch-depth: 1
submodules: true
diff --git a/.github/workflows/macos-homebrew-breakpad.yml b/.github/workflows/macos-homebrew-breakpad.yml
index 3465328c8..a71b0339a 100644
--- a/.github/workflows/macos-homebrew-breakpad.yml
+++ b/.github/workflows/macos-homebrew-breakpad.yml
@@ -31,7 +31,7 @@ jobs:
prerelease: true
steps:
- - uses: actions/checkout@v3
+ - uses: actions/checkout@v4
with:
fetch-depth: 0
submodules: true
diff --git a/.github/workflows/macos-homebrew.yml b/.github/workflows/macos-homebrew.yml
index ab55bb707..d33dccba0 100644
--- a/.github/workflows/macos-homebrew.yml
+++ b/.github/workflows/macos-homebrew.yml
@@ -8,6 +8,7 @@ on:
branches:
- dev
- master
+ - experimental
# - staged
paths-ignore:
- 'docs/**'
@@ -30,7 +31,7 @@ jobs:
version-suffix: alpha
prerelease: true
steps:
- - uses: actions/checkout@v3
+ - uses: actions/checkout@v4
with:
fetch-depth: 0
submodules: true
diff --git a/.github/workflows/ubuntu-6.2.yml b/.github/workflows/ubuntu-6.2.yml
index 20f540fe4..6f60cd410 100644
--- a/.github/workflows/ubuntu-6.2.yml
+++ b/.github/workflows/ubuntu-6.2.yml
@@ -70,7 +70,7 @@ jobs:
# sudo make install
# cd ..
- - uses: actions/checkout@v3
+ - uses: actions/checkout@v4
with:
fetch-depth: 0
submodules: true
diff --git a/.github/workflows/ubuntu-PR-check.yml b/.github/workflows/ubuntu-PR-check.yml
index f5613efa3..c6d944388 100644
--- a/.github/workflows/ubuntu-PR-check.yml
+++ b/.github/workflows/ubuntu-PR-check.yml
@@ -1,83 +1,84 @@
-name: Ubuntu-PR-check
-concurrency:
- group: ${{ github.workflow }}-${{ github.ref }}
- cancel-in-progress: true
-on:
-
- workflow_dispatch:
-
- pull_request:
- branches:
- - dev
- - master
- - staged
- paths-ignore:
- - 'docs/**'
- # - ".github/**"
- - "howto/**"
- - "*.md"
- - "locale/**"
- - "website/**"
-
-jobs:
- build:
- name: Build
- runs-on: ${{ matrix.os }}
- strategy:
- matrix:
- os: [ubuntu-latest]
- qt_ver: [5.15.2,6.6.0]
- qt_arch: [gcc_64]
-
- steps:
- - name: Install Qt
- if: ${{ matrix.qt_ver == '5.15.2' }}
- uses: jurplel/install-qt-action@v3
- with:
- version: ${{ matrix.qt_ver }}
-
- modules: qtwebengine
-
- - name: Install Qt
- if: ${{ matrix.qt_ver != '5.15.2' }}
- uses: jurplel/install-qt-action@v3
- with:
- version: ${{ matrix.qt_ver }}
- arch: ${{ matrix.qt_arch }}
-
- modules: qtwebengine qtwebchannel qtpositioning qt5compat qtmultimedia qtimageformats qtspeech
-
- - name: ubuntu install thirdparty dependencies
- run: |
- sudo apt-get install git pkg-config build-essential qt5-qmake
- sudo apt-get install libvorbis-dev zlib1g-dev libhunspell-dev x11proto-record-dev
- sudo apt-get install qtdeclarative5-dev libxtst-dev liblzo2-dev libbz2-dev
- sudo apt-get install libavutil-dev libavformat-dev libeb16-dev
- sudo apt-get install libqt5webkit5-dev libqt5svg5-dev libqt5x11extras5-dev qttools5-dev
- sudo apt-get install qttools5-dev-tools qtmultimedia5-dev libqt5multimedia5-plugins doxygen libzstd-dev #libopencc-dev
- sudo apt-get install libxkbcommon-dev libc6-dev
- sudo ln -sf /usr/bin/x86_64-linux-gnu-ld.gold /usr/bin/ld
- git clone https://github.com/BYVoid/OpenCC
- pwd
- cd OpenCC/
- make PREFIX=/usr -j$(nproc)
- sudo make install
- cd ..
- sudo apt install libxapian-dev
- sudo apt-get install libzim-dev
- - uses: actions/checkout@v3
- with:
- fetch-depth: 1
- submodules: true
-
- - name: build goldendict
- run: |
- qmake CONFIG+=release PREFIX=/usr CONFIG+=zim_support CONFIG+=chinese_conversion_support CONFIG+=use_iconv CONFIG+=use_xapian
- make INSTALL_ROOT=appdir -j`nproc` install; find appdir/
-
- #copy missing shared dll to appdir.
- mkdir -p appdir/usr/lib
- cp $(ldd appdir/usr/bin/goldendict | grep -o '\W/[^ ]*' |grep gobject ) appdir/usr/lib
- cp $(ldd appdir/usr/bin/goldendict | grep -o '\W/[^ ]*' |grep libpango ) appdir/usr/lib
-
-
+name: Ubuntu-PR-check
+concurrency:
+ group: ${{ github.workflow }}-${{ github.ref }}
+ cancel-in-progress: true
+on:
+
+ workflow_dispatch:
+
+ pull_request:
+ branches:
+ - dev
+ - master
+ - experimental
+ - staged
+ paths-ignore:
+ - 'docs/**'
+ # - ".github/**"
+ - "howto/**"
+ - "*.md"
+ - "locale/**"
+ - "website/**"
+
+jobs:
+ build:
+ name: Build
+ runs-on: ${{ matrix.os }}
+ strategy:
+ matrix:
+ os: [ubuntu-latest]
+ qt_ver: [5.15.2,6.6.0]
+ qt_arch: [gcc_64]
+
+ steps:
+ - name: Install Qt
+ if: ${{ matrix.qt_ver == '5.15.2' }}
+ uses: jurplel/install-qt-action@v3
+ with:
+ version: ${{ matrix.qt_ver }}
+
+ modules: qtwebengine
+
+ - name: Install Qt
+ if: ${{ matrix.qt_ver != '5.15.2' }}
+ uses: jurplel/install-qt-action@v3
+ with:
+ version: ${{ matrix.qt_ver }}
+ arch: ${{ matrix.qt_arch }}
+
+ modules: qtwebengine qtwebchannel qtpositioning qt5compat qtmultimedia qtimageformats qtspeech
+
+ - name: ubuntu install thirdparty dependencies
+ run: |
+ sudo apt-get install git pkg-config build-essential qt5-qmake
+ sudo apt-get install libvorbis-dev zlib1g-dev libhunspell-dev x11proto-record-dev
+ sudo apt-get install qtdeclarative5-dev libxtst-dev liblzo2-dev libbz2-dev
+ sudo apt-get install libavutil-dev libavformat-dev libeb16-dev
+ sudo apt-get install libqt5webkit5-dev libqt5svg5-dev libqt5x11extras5-dev qttools5-dev
+ sudo apt-get install qttools5-dev-tools qtmultimedia5-dev libqt5multimedia5-plugins doxygen libzstd-dev #libopencc-dev
+ sudo apt-get install libxkbcommon-dev libc6-dev
+ sudo ln -sf /usr/bin/x86_64-linux-gnu-ld.gold /usr/bin/ld
+ git clone https://github.com/BYVoid/OpenCC
+ pwd
+ cd OpenCC/
+ make PREFIX=/usr -j$(nproc)
+ sudo make install
+ cd ..
+ sudo apt install libxapian-dev
+ sudo apt-get install libzim-dev
+ - uses: actions/checkout@v4
+ with:
+ fetch-depth: 1
+ submodules: true
+
+ - name: build goldendict
+ run: |
+ qmake CONFIG+=release PREFIX=/usr CONFIG+=zim_support CONFIG+=chinese_conversion_support CONFIG+=use_iconv CONFIG+=use_xapian
+ make INSTALL_ROOT=appdir -j`nproc` install; find appdir/
+
+ #copy missing shared dll to appdir.
+ mkdir -p appdir/usr/lib
+ cp $(ldd appdir/usr/bin/goldendict | grep -o '\W/[^ ]*' |grep gobject ) appdir/usr/lib
+ cp $(ldd appdir/usr/bin/goldendict | grep -o '\W/[^ ]*' |grep libpango ) appdir/usr/lib
+
+
diff --git a/.github/workflows/ubuntu.yml b/.github/workflows/ubuntu.yml
index ec8a4169c..c01f7b8c4 100644
--- a/.github/workflows/ubuntu.yml
+++ b/.github/workflows/ubuntu.yml
@@ -1,192 +1,192 @@
-name: Ubuntu
-# Qt官方没有linux平台的x86包
-concurrency:
- group: ${{ github.workflow }}-${{ github.ref }}
- cancel-in-progress: true
-on:
- workflow_dispatch:
- # push:
- # branches:
- # - dev
- # - master
- # paths-ignore:
- # - 'docs/**'
- # - "howto/**"
- # - "*.md"
- # - ".clang-format"
-
-jobs:
- build:
- name: Build
- runs-on: ${{ matrix.os }}
- strategy:
- matrix:
- os: [ubuntu-20.04]
- qt_ver: [5.15.2]
- qt_arch: [gcc_64]
- env:
- version: 24.02.16
- version-suffix: alpha
- prerelease: true
- steps:
- - name: Install Qt
- uses: jurplel/install-qt-action@v3
- with:
- version: ${{ matrix.qt_ver }}
-
- modules: qtwebengine
- - name: ubuntu install thirdparty dependencies
- run: |
- sudo apt-get install git pkg-config build-essential qt5-qmake
- sudo apt-get install libvorbis-dev zlib1g-dev libhunspell-dev x11proto-record-dev
- sudo apt-get install qtdeclarative5-dev libxtst-dev liblzo2-dev libbz2-dev
- sudo apt-get install libavutil-dev libavformat-dev libeb16-dev
- sudo apt-get install libqt5webkit5-dev libqt5svg5-dev libqt5x11extras5-dev qttools5-dev
- sudo apt-get install qttools5-dev-tools qtmultimedia5-dev libqt5multimedia5-plugins doxygen libzstd-dev libspeechd-dev #libopencc-dev
- sudo ln -sf /usr/bin/x86_64-linux-gnu-ld.gold /usr/bin/ld
- sudo apt install libxapian-dev
- sudo apt install libfuse2
- git clone https://github.com/BYVoid/OpenCC
- pwd
- cd OpenCC/
- make PREFIX=/usr -j$(nproc)
- sudo make install
- cd ..
- sudo add-apt-repository --yes --update ppa:kiwixteam/release
- sudo apt install libzim-dev
- - uses: actions/checkout@v3
- with:
- fetch-depth: 0
- submodules: true
-
- - name: version-file
- shell: bash
- env:
- VAR_SUFFIX: ${{env.version-suffix}}
- VAR_VERSION: ${{env.version}}
- run: |
- current_tag=$(git rev-parse --short=8 HEAD)
- release_date=$(date +'%Y%m%d')
- echo "$VAR_VERSION-$VAR_SUFFIX.$release_date.$current_tag">version.txt
- cat version.txt
- echo "$version"
-
- - name: build goldendict
- run: |
- qmake CONFIG+=release PREFIX=/usr CONFIG+=zim_support CONFIG+=chinese_conversion_support CONFIG+=use_iconv CONFIG+=use_xapian
- make INSTALL_ROOT=appdir -j`nproc` install; find appdir/
-
- #copy missing shared dll to appdir.
- mkdir -p appdir/usr/lib
- cp $(ldd appdir/usr/bin/goldendict | grep -o '\W/[^ ]*' |grep gobject ) appdir/usr/lib
- cp $(ldd appdir/usr/bin/goldendict | grep -o '\W/[^ ]*' |grep libpango ) appdir/usr/lib
-
- - name: Build AppImage
- run: |
- sudo apt install fcitx5-frontend-qt5
- wget -c -nv "https://github.com/linuxdeploy/linuxdeploy-plugin-qt/releases/download/continuous/linuxdeploy-plugin-qt-x86_64.AppImage"
- chmod a+x linuxdeploy-plugin-qt-x86_64.AppImage
- wget -c -nv "https://github.com/linuxdeploy/linuxdeploy/releases/download/continuous/linuxdeploy-x86_64.AppImage"
- chmod a+x linuxdeploy-x86_64.AppImage
- ./linuxdeploy-x86_64.AppImage --appdir appdir --output appimage --plugin qt -i redist/icons/goldendict.png -d redist/io.github.xiaoyifang.goldendict_ng.desktop
-
- - name: changelog
- id: changelog
- run: |
- previousTag=$(git tag --sort=-creatordate | grep "^v" | sed -n 2p)
- echo "previousTag : $previousTag"
-
- echo "prev_tag=$previousTag" >> $GITHUB_OUTPUT
- echo "curr_tag=$(git tag --sort=-creatordate | grep "^v" | sed -n 1p)" >> $GITHUB_OUTPUT
- - name: Set outputs
- id: vars
- run: |
- echo "sha_short=$(git rev-parse --short=8 HEAD)" >> $GITHUB_OUTPUT
- echo "release_date=$(date +'%Y%m%d')" >> $GITHUB_OUTPUT
- echo "release_time=$(date +'%H%M%S')" >> $GITHUB_OUTPUT
- echo "release_time_clock=$(date +'%H:%M:%S')" >> $GITHUB_OUTPUT
- echo "release_hm=$(date +'%y%m%d')" >> $GITHUB_OUTPUT
- appname=$(ls *.AppImage* -1|head -n1)
- echo "appname=$appname" >> $GITHUB_OUTPUT
-
- - name: "Build Changelog"
- id: build_changelog
- uses: mikepenz/release-changelog-builder-action@v3
- with:
- commitMode: false
- fromTag: ${{ steps.changelog.outputs.prev_tag }}
- toTag: "${{ steps.changelog.outputs.curr_tag }}"
- configurationJson: |
- {
- "template": "#{{CHANGELOG}}\n\n\n🔴 Uncategorized
\n\n#{{UNCATEGORIZED}}\n ",
- "categories": [
- {
- "title": "## 🚀 Features",
- "labels": ["feature","feat","opt"]
- },
- {
- "title": "## 🐛 Fixes",
- "labels": ["fix","bug"]
- }
- ,
- {
- "title": "## 🤖 Github action",
- "labels": ["action"]
- }
- ,
- {
- "title": "## 🧼 Clean Code",
- "labels": ["clean"]
- }
-
- ],
- "label_extractor": [
- {
- "pattern": "([^:]*):.*",
- "target": "$1",
- "on_property": "title",
- "flags": "gu"
- }
- ]
- }
- - name: uploadRelease
- # if: startsWith(github.event.ref, 'refs/tags/')
- uses: svenstaro/upload-release-action@v2
- with:
- repo_token: ${{ secrets.GITHUB_TOKEN }}
- file: ${{ steps.vars.outputs.appname }}
- asset_name: ${{ matrix.qt_ver }}-${{ steps.vars.outputs.appname }}
- tag: v${{env.version}}-${{env.version-suffix}}.${{ steps.vars.outputs.sha_short }}
- overwrite: true
- release_name: GoldenDict-ng-v${{env.version}}-${{env.version-suffix}}.${{ steps.vars.outputs.release_hm }}.${{ steps.vars.outputs.sha_short }}
- prerelease: ${{env.prerelease}}
- body: |
- #### Install instructions for Windows, macOS and Linux
-
- .
-
- #### Filename pattern (文件名模式): **[Qt version]-GoldenDict-ng-[OS]-[release-date].[ext]**
-
- Qt6.X is recommended for various enhancements.
-
- Windows users can use either `****-installer.exe` (for installer) or `****.zip` (unzip and run).
- The `goldendict.exe` can be dropped into previous installation's folder (if dependencies aren't changed).
-
- Linux users can use Flatpak or build from source.
- https://flathub.org/apps/io.github.xiaoyifang.goldendict_ng
-
- macOS users can use `.dmg` installer.
-
- `6.5.1-GoldenDict.exe_windows-2019_20230701.zip` means built with Qt6.5.1, windows/msvc-2019 at 20230701 as a zip archive.
-
- #### Build Details
-
- Flatpak
- macOS: macOS-12 and macOS-13
- Windows: Visual studio 2019
- based on: ${{github.ref_name}}
-
-
- #### Changes
-
- ${{steps.build_changelog.outputs.changelog}}
+name: Ubuntu
+# Qt官方没有linux平台的x86包
+concurrency:
+ group: ${{ github.workflow }}-${{ github.ref }}
+ cancel-in-progress: true
+on:
+ workflow_dispatch:
+ # push:
+ # branches:
+ # - dev
+ # - master
+ # paths-ignore:
+ # - 'docs/**'
+ # - "howto/**"
+ # - "*.md"
+ # - ".clang-format"
+
+jobs:
+ build:
+ name: Build
+ runs-on: ${{ matrix.os }}
+ strategy:
+ matrix:
+ os: [ubuntu-20.04]
+ qt_ver: [5.15.2]
+ qt_arch: [gcc_64]
+ env:
+ version: 24.02.16
+ version-suffix: alpha
+ prerelease: true
+ steps:
+ - name: Install Qt
+ uses: jurplel/install-qt-action@v3
+ with:
+ version: ${{ matrix.qt_ver }}
+
+ modules: qtwebengine
+ - name: ubuntu install thirdparty dependencies
+ run: |
+ sudo apt-get install git pkg-config build-essential qt5-qmake
+ sudo apt-get install libvorbis-dev zlib1g-dev libhunspell-dev x11proto-record-dev
+ sudo apt-get install qtdeclarative5-dev libxtst-dev liblzo2-dev libbz2-dev
+ sudo apt-get install libavutil-dev libavformat-dev libeb16-dev
+ sudo apt-get install libqt5webkit5-dev libqt5svg5-dev libqt5x11extras5-dev qttools5-dev
+ sudo apt-get install qttools5-dev-tools qtmultimedia5-dev libqt5multimedia5-plugins doxygen libzstd-dev libspeechd-dev #libopencc-dev
+ sudo ln -sf /usr/bin/x86_64-linux-gnu-ld.gold /usr/bin/ld
+ sudo apt install libxapian-dev
+ sudo apt install libfuse2
+ git clone https://github.com/BYVoid/OpenCC
+ pwd
+ cd OpenCC/
+ make PREFIX=/usr -j$(nproc)
+ sudo make install
+ cd ..
+ sudo add-apt-repository --yes --update ppa:kiwixteam/release
+ sudo apt install libzim-dev
+ - uses: actions/checkout@v4
+ with:
+ fetch-depth: 0
+ submodules: true
+
+ - name: version-file
+ shell: bash
+ env:
+ VAR_SUFFIX: ${{env.version-suffix}}
+ VAR_VERSION: ${{env.version}}
+ run: |
+ current_tag=$(git rev-parse --short=8 HEAD)
+ release_date=$(date +'%Y%m%d')
+ echo "$VAR_VERSION-$VAR_SUFFIX.$release_date.$current_tag">version.txt
+ cat version.txt
+ echo "$version"
+
+ - name: build goldendict
+ run: |
+ qmake CONFIG+=release PREFIX=/usr CONFIG+=zim_support CONFIG+=chinese_conversion_support CONFIG+=use_iconv CONFIG+=use_xapian
+ make INSTALL_ROOT=appdir -j`nproc` install; find appdir/
+
+ #copy missing shared dll to appdir.
+ mkdir -p appdir/usr/lib
+ cp $(ldd appdir/usr/bin/goldendict | grep -o '\W/[^ ]*' |grep gobject ) appdir/usr/lib
+ cp $(ldd appdir/usr/bin/goldendict | grep -o '\W/[^ ]*' |grep libpango ) appdir/usr/lib
+
+ - name: Build AppImage
+ run: |
+ sudo apt install fcitx5-frontend-qt5
+ wget -c -nv "https://github.com/linuxdeploy/linuxdeploy-plugin-qt/releases/download/continuous/linuxdeploy-plugin-qt-x86_64.AppImage"
+ chmod a+x linuxdeploy-plugin-qt-x86_64.AppImage
+ wget -c -nv "https://github.com/linuxdeploy/linuxdeploy/releases/download/continuous/linuxdeploy-x86_64.AppImage"
+ chmod a+x linuxdeploy-x86_64.AppImage
+ ./linuxdeploy-x86_64.AppImage --appdir appdir --output appimage --plugin qt -i redist/icons/goldendict.png -d redist/io.github.xiaoyifang.goldendict_ng.desktop
+
+ - name: changelog
+ id: changelog
+ run: |
+ previousTag=$(git tag --sort=-creatordate | grep "^v" | sed -n 2p)
+ echo "previousTag : $previousTag"
+
+ echo "prev_tag=$previousTag" >> $GITHUB_OUTPUT
+ echo "curr_tag=$(git tag --sort=-creatordate | grep "^v" | sed -n 1p)" >> $GITHUB_OUTPUT
+ - name: Set outputs
+ id: vars
+ run: |
+ echo "sha_short=$(git rev-parse --short=8 HEAD)" >> $GITHUB_OUTPUT
+ echo "release_date=$(date +'%Y%m%d')" >> $GITHUB_OUTPUT
+ echo "release_time=$(date +'%H%M%S')" >> $GITHUB_OUTPUT
+ echo "release_time_clock=$(date +'%H:%M:%S')" >> $GITHUB_OUTPUT
+ echo "release_hm=$(date +'%y%m%d')" >> $GITHUB_OUTPUT
+ appname=$(ls *.AppImage* -1|head -n1)
+ echo "appname=$appname" >> $GITHUB_OUTPUT
+
+ - name: "Build Changelog"
+ id: build_changelog
+ uses: mikepenz/release-changelog-builder-action@v3
+ with:
+ commitMode: false
+ fromTag: ${{ steps.changelog.outputs.prev_tag }}
+ toTag: "${{ steps.changelog.outputs.curr_tag }}"
+ configurationJson: |
+ {
+ "template": "#{{CHANGELOG}}\n\n\n🔴 Uncategorized
\n\n#{{UNCATEGORIZED}}\n ",
+ "categories": [
+ {
+ "title": "## 🚀 Features",
+ "labels": ["feature","feat","opt"]
+ },
+ {
+ "title": "## 🐛 Fixes",
+ "labels": ["fix","bug"]
+ }
+ ,
+ {
+ "title": "## 🤖 Github action",
+ "labels": ["action"]
+ }
+ ,
+ {
+ "title": "## 🧼 Clean Code",
+ "labels": ["clean"]
+ }
+
+ ],
+ "label_extractor": [
+ {
+ "pattern": "([^:]*):.*",
+ "target": "$1",
+ "on_property": "title",
+ "flags": "gu"
+ }
+ ]
+ }
+ - name: uploadRelease
+ # if: startsWith(github.event.ref, 'refs/tags/')
+ uses: svenstaro/upload-release-action@v2
+ with:
+ repo_token: ${{ secrets.GITHUB_TOKEN }}
+ file: ${{ steps.vars.outputs.appname }}
+ asset_name: ${{ matrix.qt_ver }}-${{ steps.vars.outputs.appname }}
+ tag: v${{env.version}}-${{env.version-suffix}}.${{ steps.vars.outputs.sha_short }}
+ overwrite: true
+ release_name: GoldenDict-ng-v${{env.version}}-${{env.version-suffix}}.${{ steps.vars.outputs.release_hm }}.${{ steps.vars.outputs.sha_short }}
+ prerelease: ${{env.prerelease}}
+ body: |
+ #### Install instructions for Windows, macOS and Linux
+
+ .
+
+ #### Filename pattern (文件名模式): **[Qt version]-GoldenDict-ng-[OS]-[release-date].[ext]**
+
+ Qt6.X is recommended for various enhancements.
+
+ Windows users can use either `****-installer.exe` (for installer) or `****.zip` (unzip and run).
+ The `goldendict.exe` can be dropped into previous installation's folder (if dependencies aren't changed).
+
+ Linux users can use Flatpak or build from source.
+ https://flathub.org/apps/io.github.xiaoyifang.goldendict_ng
+
+ macOS users can use `.dmg` installer.
+
+ `6.5.1-GoldenDict.exe_windows-2019_20230701.zip` means built with Qt6.5.1, windows/msvc-2019 at 20230701 as a zip archive.
+
+ #### Build Details
+
+ Flatpak
+ macOS: macOS-12 and macOS-13
+ Windows: Visual studio 2019
+ based on: ${{github.ref_name}}
+
+
+ #### Changes
+
+ ${{steps.build_changelog.outputs.changelog}}
diff --git a/.github/workflows/windows-6.x.yml b/.github/workflows/windows-6.x.yml
index 8c06fd3c5..c0c3e9ab6 100644
--- a/.github/workflows/windows-6.x.yml
+++ b/.github/workflows/windows-6.x.yml
@@ -11,6 +11,7 @@ on:
branches:
- dev
- master
+ - experimental
# - staged
paths-ignore:
- 'docs/**'
@@ -27,7 +28,7 @@ jobs:
strategy:
matrix:
os: [windows-2019]
- qt_ver: [ 6.6.2 ]
+ qt_ver: [ 6.6.2,6.7.0 ]
qt_arch: [win64_msvc2019_64]
env:
targetName: GoldenDict.exe
@@ -47,7 +48,7 @@ jobs:
modules: qtwebengine qtwebchannel qtpositioning qt5compat qtmultimedia qtimageformats qtspeech
setup-python: 'false'
- - uses: actions/checkout@v3
+ - uses: actions/checkout@v4
with:
fetch-depth: 0
submodules: true
diff --git a/.github/workflows/windows-PR-check.yml b/.github/workflows/windows-PR-check.yml
index fc23e4837..482a50587 100644
--- a/.github/workflows/windows-PR-check.yml
+++ b/.github/workflows/windows-PR-check.yml
@@ -1,68 +1,69 @@
-name: Windows-PR-check
-concurrency:
- group: ${{ github.workflow }}-${{ github.ref }}
- cancel-in-progress: true
-on:
-
- workflow_dispatch:
-
- pull_request:
- branches:
- - dev
- - master
- - staged
- paths-ignore:
- - 'docs/**'
- # - ".github/**"
- - "howto/**"
- - "*.md"
- - "locale/**"
- - "website/**"
-
-jobs:
- build:
- name: Build
- # see https://github.com/actions/virtual-environments/blob/main/images/win/Windows2019-Readme.md
- runs-on: ${{ matrix.os }}
- strategy:
- matrix:
- os: [windows-2019]
- qt_ver: [5.15.2,6.6.0]
- qt_arch: [win64_msvc2019_64]
- steps:
- - uses: actions/setup-python@v3
- with:
- python-version: '3.9'
- - name: Install Qt
- if: ${{ matrix.qt_ver == '5.15.2' }}
- uses: jurplel/install-qt-action@v3
- with:
- version: ${{ matrix.qt_ver }}
- arch: ${{ matrix.qt_arch }}
-
- modules: qtwebengine
- - name: Install Qt
- if: ${{ matrix.qt_ver != '5.15.2' }}
- uses: jurplel/install-qt-action@v3
- with:
- version: ${{ matrix.qt_ver }}
- # target: ${{ matrix.qt_target }}
- arch: ${{ matrix.qt_arch }}
-
- modules: qtwebengine qtwebchannel qtpositioning qt5compat qtmultimedia qtimageformats qtspeech
- setup-python: 'false'
-
- - uses: actions/checkout@v3
- with:
- fetch-depth: 1
- submodules: true
-
- - uses: ilammy/msvc-dev-cmd@v1
- # with:
- # arch: ${{ matrix.msvc_arch }}
- - name: msvc-build goldendict
- id: build
- shell: cmd
- run: |
- qmake "CONFIG+=zim_support" CONFIG+=release CONFIG+=use_iconv CONFIG+=use_xapian CONFIG+=no_ffmpeg_player
- nmake
+name: Windows-PR-check
+concurrency:
+ group: ${{ github.workflow }}-${{ github.ref }}
+ cancel-in-progress: true
+on:
+
+ workflow_dispatch:
+
+ pull_request:
+ branches:
+ - dev
+ - master
+ - experimental
+ - staged
+ paths-ignore:
+ - 'docs/**'
+ # - ".github/**"
+ - "howto/**"
+ - "*.md"
+ - "locale/**"
+ - "website/**"
+
+jobs:
+ build:
+ name: Build
+ # see https://github.com/actions/virtual-environments/blob/main/images/win/Windows2019-Readme.md
+ runs-on: ${{ matrix.os }}
+ strategy:
+ matrix:
+ os: [windows-2019]
+ qt_ver: [5.15.2,6.6.2]
+ qt_arch: [win64_msvc2019_64]
+ steps:
+ - uses: actions/setup-python@v3
+ with:
+ python-version: '3.9'
+ - name: Install Qt
+ if: ${{ matrix.qt_ver == '5.15.2' }}
+ uses: jurplel/install-qt-action@v3
+ with:
+ version: ${{ matrix.qt_ver }}
+ arch: ${{ matrix.qt_arch }}
+
+ modules: qtwebengine
+ - name: Install Qt
+ if: ${{ matrix.qt_ver != '5.15.2' }}
+ uses: jurplel/install-qt-action@v3
+ with:
+ version: ${{ matrix.qt_ver }}
+ # target: ${{ matrix.qt_target }}
+ arch: ${{ matrix.qt_arch }}
+
+ modules: qtwebengine qtwebchannel qtpositioning qt5compat qtmultimedia qtimageformats qtspeech
+ setup-python: 'false'
+
+ - uses: actions/checkout@v4
+ with:
+ fetch-depth: 1
+ submodules: true
+
+ - uses: ilammy/msvc-dev-cmd@v1
+ # with:
+ # arch: ${{ matrix.msvc_arch }}
+ - name: msvc-build goldendict
+ id: build
+ shell: cmd
+ run: |
+ qmake "CONFIG+=zim_support" CONFIG+=release CONFIG+=use_iconv CONFIG+=use_xapian CONFIG+=no_ffmpeg_player
+ nmake
diff --git a/.github/workflows/windows.yml b/.github/workflows/windows.yml
index a2d3419f4..a066e6c49 100644
--- a/.github/workflows/windows.yml
+++ b/.github/workflows/windows.yml
@@ -1,241 +1,241 @@
-name: Windows
-concurrency:
- group: ${{ github.workflow }}-${{ github.ref }}
- cancel-in-progress: true
-on:
- workflow_dispatch:
- # push:
- # branches:
- # - dev
- # - master
- # paths-ignore:
- # - 'docs/**'
- # - "howto/**"
- # - "*.md"
- # - ".clang-format"
-
-jobs:
- build:
- name: Build
- # 运行平台
- # 参考文档 https://github.com/actions/virtual-environments/blob/main/images/win/Windows2019-Readme.md
- runs-on: ${{ matrix.os }}
- strategy:
- matrix:
- os: [windows-2019]
- qt_ver: [5.15.2]
- qt_arch: [win64_msvc2019_64]
- env:
- targetName: GoldenDict.exe
- version: 24.02.16
- version-suffix: alpha
- prerelease: true
- # 步骤
- steps:
- # 安装Qt
- - name: Install Qt
- # 使用外部action。这个action专门用来安装Qt
- uses: jurplel/install-qt-action@v3
- with:
- # Version of Qt to install
- version: ${{ matrix.qt_ver }}
- # Target platform for build
- # target: ${{ matrix.qt_target }}
- arch: ${{ matrix.qt_arch }}
-
- modules: qtwebengine
- # 拉取代码
- - uses: actions/checkout@v3
- with:
- fetch-depth: 0
- submodules: true
-
- - name: changelog
- id: changelog
- shell: bash
- run: |
- previousTag=$(git tag --sort=-creatordate | grep "^v" | sed -n 2p)
- echo "previousTag : $previousTag"
-
-
- echo "prev_tag=$previousTag" >> $GITHUB_OUTPUT
- echo "curr_tag=$(git tag --sort=-creatordate | grep "^v" | sed -n 1p)" >> $GITHUB_OUTPUT
-
- - name: Set outputs
- id: vars
- shell: bash
- run: |
- echo "sha_short=$(git rev-parse --short=8 HEAD)" >> $GITHUB_OUTPUT
- echo "release_date=$(date +'%Y%m%d')" >> $GITHUB_OUTPUT
- echo "release_time=$(date +'%H%M%S')" >> $GITHUB_OUTPUT
- echo "release_time_clock=$(date +'%H:%M:%S')" >> $GITHUB_OUTPUT
- echo "release_hm=$(date +'%y%m%d')" >> $GITHUB_OUTPUT
-
- - name: version-file
- shell: bash
- env:
- VAR_SUFFIX: ${{env.version-suffix}}
- VAR_VERSION: ${{env.version}}
- run: |
- current_tag=$(git rev-parse --short=8 HEAD)
- release_date=$(date +'%Y%m%d')
- echo "$VAR_VERSION-$VAR_SUFFIX.$release_date.$current_tag">version.txt
- cat version.txt
- echo "$version"
-
- - uses: ilammy/msvc-dev-cmd@v1
- # msvc编译
- - name: msvc-build goldendict
- id: build
- shell: cmd
- run: |
- qmake "CONFIG+=zim_support" CONFIG+=release CONFIG+=use_iconv CONFIG+=use_xapian CONFIG+=no_ffmpeg_player
- nmake
-
- echo winSdkDir=%WindowsSdkDir% >> %GITHUB_ENV%
- echo winSdkVer=%WindowsSdkVersion% >> %GITHUB_ENV%
- echo vcToolsInstallDir=%VCToolsInstallDir% >> %GITHUB_ENV%
- echo vcToolsRedistDir=%VCToolsRedistDir% >> %GITHUB_ENV%
- echo QTDIR=%Qt5_DIR% >> %GITHUB_ENV%
-
- # 打包
- - name: windows ps package
- id: package-windows
- env:
- archiveName: GoldenDict-Windows.${{ steps.vars.outputs.sha_short }}-${{ steps.vars.outputs.release_time }}
- shell: pwsh
- run: |
- & .github\scripts\windows-publish.ps1 ${env:archiveName} ${env:targetName}
-
- - name: package
- id: package
- shell: bash
- run: |
- packageName=GoldenDict-Windows.${{ steps.vars.outputs.sha_short }}-${{ steps.vars.outputs.release_time }}
- echo $packageName
- echo "packageName=$packageName" >> $GITHUB_OUTPUT
-
- - name: "Build Changelog"
- id: build_changelog
- uses: mikepenz/release-changelog-builder-action@v3
- with:
- commitMode: false
- fromTag: ${{ steps.changelog.outputs.prev_tag }}
- toTag: "${{ steps.changelog.outputs.curr_tag }}"
- configurationJson: |
- {
- "template": "#{{CHANGELOG}}\n\n\n🔴 Uncategorized
\n\n#{{UNCATEGORIZED}}\n ",
- "categories": [
- {
- "title": "## 🚀 Features",
- "labels": ["feature","feat","opt"]
- },
- {
- "title": "## 🐛 Fixes",
- "labels": ["fix","bug"]
- }
- ,
- {
- "title": "## 🤖 Github action",
- "labels": ["action"]
- }
- ,
- {
- "title": "## 🧼 Clean Code",
- "labels": ["clean"]
- }
-
- ],
- "label_extractor": [
- {
- "pattern": "([^:]*):.*",
- "target": "$1",
- "on_property": "title",
- "flags": "gu"
- }
- ]
- }
-
- - name: upload goldendict.exe only
- # if: startsWith(github.event.ref, 'refs/tags/')
- uses: svenstaro/upload-release-action@v2
- with:
- repo_token: ${{ secrets.GITHUB_TOKEN }}
- file: release/${{ env.targetName }}
- asset_name: ${{ matrix.qt_ver }}-${{ env.targetName }}
-
- tag: v${{env.version}}-${{env.version-suffix}}.${{ steps.vars.outputs.sha_short }}
- overwrite: true
- release_name: GoldenDict-ng-v${{env.version}}-${{env.version-suffix}}.${{ steps.vars.outputs.release_hm }}.${{ steps.vars.outputs.sha_short }}
- prerelease: ${{env.prerelease}}
-
-
- - name: copy nsis
- shell: bash
- run: |
- ls -al
- cp tools/nsis/GoldenDict.nsi ${{ steps.package.outputs.packageName }}/
- - name: Create installer
- uses: joncloud/makensis-action@v4
- with:
- arguments: "/V3"
- script-file: ${{ steps.package.outputs.packageName }}/GoldenDict.nsi
- - name: make installer
- run: |
- ls *.exe
- cd ${{ steps.package.outputs.packageName }}
- ls *.exe
- - name: upload goldendict installer
- # if: startsWith(github.event.ref, 'refs/tags/')
- uses: svenstaro/upload-release-action@v2
- with:
- repo_token: ${{ secrets.GITHUB_TOKEN }}
- file: ${{ steps.package.outputs.packageName }}/GoldenDict-ng-v23-Installer.exe
- asset_name: ${{ matrix.qt_ver }}-GoldenDict-ng-Installer.exe
-
- tag: v${{env.version}}-${{env.version-suffix}}.${{ steps.vars.outputs.sha_short }}
- overwrite: true
- release_name: GoldenDict-ng-v${{env.version}}-${{env.version-suffix}}.${{ steps.vars.outputs.release_hm }}.${{ steps.vars.outputs.sha_short }}
- prerelease: ${{env.prerelease}}
- - name: uploadRelease
- uses: svenstaro/upload-release-action@v2
- with:
- repo_token: ${{ secrets.GITHUB_TOKEN }}
- file: ${{ steps.package.outputs.packageName }}.zip
- asset_name: ${{ matrix.qt_ver }}-${{ env.targetName }}_${{ matrix.os }}_${{steps.vars.outputs.release_date}}.zip
-
- tag: v${{env.version}}-${{env.version-suffix}}.${{ steps.vars.outputs.sha_short }}
- overwrite: true
- release_name: GoldenDict-ng-v${{env.version}}-${{env.version-suffix}}.${{ steps.vars.outputs.release_hm }}.${{ steps.vars.outputs.sha_short }}
- prerelease: ${{env.prerelease}}
- body: |
- #### Install instructions for Windows, macOS and Linux
-
- .
-
- #### Filename pattern (文件名模式): **[Qt version]-GoldenDict-ng-[OS]-[release-date].[ext]**
-
- Qt6.X is recommended for various enhancements.
-
- Windows users can use either `****-installer.exe` (for installer) or `****.zip` (unzip and run).
- The `goldendict.exe` can be dropped into previous installation's folder (if dependencies aren't changed).
-
- Linux users can use Flatpak or build from source.
- https://flathub.org/apps/io.github.xiaoyifang.goldendict_ng
-
- macOS users can use `.dmg` installer.
-
- `6.5.1-GoldenDict.exe_windows-2019_20230701.zip` means built with Qt6.5.1, windows/msvc-2019 at 20230701 as a zip archive.
-
- #### Build Details
-
- Flatpak
- macOS: macOS-12 and macOS-13
- Windows: Visual studio 2019
- based on: ${{github.ref_name}}
-
-
-
- #### Changes
-
- ${{steps.build_changelog.outputs.changelog}}
+name: Windows
+concurrency:
+ group: ${{ github.workflow }}-${{ github.ref }}
+ cancel-in-progress: true
+on:
+ workflow_dispatch:
+ # push:
+ # branches:
+ # - dev
+ # - master
+ # paths-ignore:
+ # - 'docs/**'
+ # - "howto/**"
+ # - "*.md"
+ # - ".clang-format"
+
+jobs:
+ build:
+ name: Build
+ # 运行平台
+ # 参考文档 https://github.com/actions/virtual-environments/blob/main/images/win/Windows2019-Readme.md
+ runs-on: ${{ matrix.os }}
+ strategy:
+ matrix:
+ os: [windows-2019]
+ qt_ver: [5.15.2]
+ qt_arch: [win64_msvc2019_64]
+ env:
+ targetName: GoldenDict.exe
+ version: 24.02.16
+ version-suffix: alpha
+ prerelease: true
+ # 步骤
+ steps:
+ # 安装Qt
+ - name: Install Qt
+ # 使用外部action。这个action专门用来安装Qt
+ uses: jurplel/install-qt-action@v3
+ with:
+ # Version of Qt to install
+ version: ${{ matrix.qt_ver }}
+ # Target platform for build
+ # target: ${{ matrix.qt_target }}
+ arch: ${{ matrix.qt_arch }}
+
+ modules: qtwebengine
+ # 拉取代码
+ - uses: actions/checkout@v4
+ with:
+ fetch-depth: 0
+ submodules: true
+
+ - name: changelog
+ id: changelog
+ shell: bash
+ run: |
+ previousTag=$(git tag --sort=-creatordate | grep "^v" | sed -n 2p)
+ echo "previousTag : $previousTag"
+
+
+ echo "prev_tag=$previousTag" >> $GITHUB_OUTPUT
+ echo "curr_tag=$(git tag --sort=-creatordate | grep "^v" | sed -n 1p)" >> $GITHUB_OUTPUT
+
+ - name: Set outputs
+ id: vars
+ shell: bash
+ run: |
+ echo "sha_short=$(git rev-parse --short=8 HEAD)" >> $GITHUB_OUTPUT
+ echo "release_date=$(date +'%Y%m%d')" >> $GITHUB_OUTPUT
+ echo "release_time=$(date +'%H%M%S')" >> $GITHUB_OUTPUT
+ echo "release_time_clock=$(date +'%H:%M:%S')" >> $GITHUB_OUTPUT
+ echo "release_hm=$(date +'%y%m%d')" >> $GITHUB_OUTPUT
+
+ - name: version-file
+ shell: bash
+ env:
+ VAR_SUFFIX: ${{env.version-suffix}}
+ VAR_VERSION: ${{env.version}}
+ run: |
+ current_tag=$(git rev-parse --short=8 HEAD)
+ release_date=$(date +'%Y%m%d')
+ echo "$VAR_VERSION-$VAR_SUFFIX.$release_date.$current_tag">version.txt
+ cat version.txt
+ echo "$version"
+
+ - uses: ilammy/msvc-dev-cmd@v1
+ # msvc编译
+ - name: msvc-build goldendict
+ id: build
+ shell: cmd
+ run: |
+ qmake "CONFIG+=zim_support" CONFIG+=release CONFIG+=use_iconv CONFIG+=use_xapian CONFIG+=no_ffmpeg_player
+ nmake
+
+ echo winSdkDir=%WindowsSdkDir% >> %GITHUB_ENV%
+ echo winSdkVer=%WindowsSdkVersion% >> %GITHUB_ENV%
+ echo vcToolsInstallDir=%VCToolsInstallDir% >> %GITHUB_ENV%
+ echo vcToolsRedistDir=%VCToolsRedistDir% >> %GITHUB_ENV%
+ echo QTDIR=%Qt5_DIR% >> %GITHUB_ENV%
+
+ # 打包
+ - name: windows ps package
+ id: package-windows
+ env:
+ archiveName: GoldenDict-Windows.${{ steps.vars.outputs.sha_short }}-${{ steps.vars.outputs.release_time }}
+ shell: pwsh
+ run: |
+ & .github\scripts\windows-publish.ps1 ${env:archiveName} ${env:targetName}
+
+ - name: package
+ id: package
+ shell: bash
+ run: |
+ packageName=GoldenDict-Windows.${{ steps.vars.outputs.sha_short }}-${{ steps.vars.outputs.release_time }}
+ echo $packageName
+ echo "packageName=$packageName" >> $GITHUB_OUTPUT
+
+ - name: "Build Changelog"
+ id: build_changelog
+ uses: mikepenz/release-changelog-builder-action@v3
+ with:
+ commitMode: false
+ fromTag: ${{ steps.changelog.outputs.prev_tag }}
+ toTag: "${{ steps.changelog.outputs.curr_tag }}"
+ configurationJson: |
+ {
+ "template": "#{{CHANGELOG}}\n\n\n🔴 Uncategorized
\n\n#{{UNCATEGORIZED}}\n ",
+ "categories": [
+ {
+ "title": "## 🚀 Features",
+ "labels": ["feature","feat","opt"]
+ },
+ {
+ "title": "## 🐛 Fixes",
+ "labels": ["fix","bug"]
+ }
+ ,
+ {
+ "title": "## 🤖 Github action",
+ "labels": ["action"]
+ }
+ ,
+ {
+ "title": "## 🧼 Clean Code",
+ "labels": ["clean"]
+ }
+
+ ],
+ "label_extractor": [
+ {
+ "pattern": "([^:]*):.*",
+ "target": "$1",
+ "on_property": "title",
+ "flags": "gu"
+ }
+ ]
+ }
+
+ - name: upload goldendict.exe only
+ # if: startsWith(github.event.ref, 'refs/tags/')
+ uses: svenstaro/upload-release-action@v2
+ with:
+ repo_token: ${{ secrets.GITHUB_TOKEN }}
+ file: release/${{ env.targetName }}
+ asset_name: ${{ matrix.qt_ver }}-${{ env.targetName }}
+
+ tag: v${{env.version}}-${{env.version-suffix}}.${{ steps.vars.outputs.sha_short }}
+ overwrite: true
+ release_name: GoldenDict-ng-v${{env.version}}-${{env.version-suffix}}.${{ steps.vars.outputs.release_hm }}.${{ steps.vars.outputs.sha_short }}
+ prerelease: ${{env.prerelease}}
+
+
+ - name: copy nsis
+ shell: bash
+ run: |
+ ls -al
+ cp tools/nsis/GoldenDict.nsi ${{ steps.package.outputs.packageName }}/
+ - name: Create installer
+ uses: joncloud/makensis-action@v4
+ with:
+ arguments: "/V3"
+ script-file: ${{ steps.package.outputs.packageName }}/GoldenDict.nsi
+ - name: make installer
+ run: |
+ ls *.exe
+ cd ${{ steps.package.outputs.packageName }}
+ ls *.exe
+ - name: upload goldendict installer
+ # if: startsWith(github.event.ref, 'refs/tags/')
+ uses: svenstaro/upload-release-action@v2
+ with:
+ repo_token: ${{ secrets.GITHUB_TOKEN }}
+ file: ${{ steps.package.outputs.packageName }}/GoldenDict-ng-v23-Installer.exe
+ asset_name: ${{ matrix.qt_ver }}-GoldenDict-ng-Installer.exe
+
+ tag: v${{env.version}}-${{env.version-suffix}}.${{ steps.vars.outputs.sha_short }}
+ overwrite: true
+ release_name: GoldenDict-ng-v${{env.version}}-${{env.version-suffix}}.${{ steps.vars.outputs.release_hm }}.${{ steps.vars.outputs.sha_short }}
+ prerelease: ${{env.prerelease}}
+ - name: uploadRelease
+ uses: svenstaro/upload-release-action@v2
+ with:
+ repo_token: ${{ secrets.GITHUB_TOKEN }}
+ file: ${{ steps.package.outputs.packageName }}.zip
+ asset_name: ${{ matrix.qt_ver }}-${{ env.targetName }}_${{ matrix.os }}_${{steps.vars.outputs.release_date}}.zip
+
+ tag: v${{env.version}}-${{env.version-suffix}}.${{ steps.vars.outputs.sha_short }}
+ overwrite: true
+ release_name: GoldenDict-ng-v${{env.version}}-${{env.version-suffix}}.${{ steps.vars.outputs.release_hm }}.${{ steps.vars.outputs.sha_short }}
+ prerelease: ${{env.prerelease}}
+ body: |
+ #### Install instructions for Windows, macOS and Linux
+
+ .
+
+ #### Filename pattern (文件名模式): **[Qt version]-GoldenDict-ng-[OS]-[release-date].[ext]**
+
+ Qt6.X is recommended for various enhancements.
+
+ Windows users can use either `****-installer.exe` (for installer) or `****.zip` (unzip and run).
+ The `goldendict.exe` can be dropped into previous installation's folder (if dependencies aren't changed).
+
+ Linux users can use Flatpak or build from source.
+ https://flathub.org/apps/io.github.xiaoyifang.goldendict_ng
+
+ macOS users can use `.dmg` installer.
+
+ `6.5.1-GoldenDict.exe_windows-2019_20230701.zip` means built with Qt6.5.1, windows/msvc-2019 at 20230701 as a zip archive.
+
+ #### Build Details
+
+ Flatpak
+ macOS: macOS-12 and macOS-13
+ Windows: Visual studio 2019
+ based on: ${{github.ref_name}}
+
+
+
+ #### Changes
+
+ ${{steps.build_changelog.outputs.changelog}}
diff --git a/CMakeLists.txt b/CMakeLists.txt
index d6ddc35cf..3683de9f8 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -49,7 +49,7 @@ set(CMAKE_AUTORCC ON) # not included in the qt_standard_project_setup
#### Things required during configuration
block() # generate version.txt
- string(TIMESTAMP build_time)
+ string(TIMESTAMP build_time UTC)
find_package(Git)
if (EXISTS "${CMAKE_SOURCE_DIR}/.git" AND GIT_FOUND)
execute_process(
@@ -232,7 +232,6 @@ if (LINUX OR BSD)
endif ()
if (WIN32)
- find_program(TOOL_WINDEPLOYQT NAMES windeployqt PATHS "${Qt6_ROOT}/bin")
set_target_properties(${GOLDENDICT}
PROPERTIES
WIN32_EXECUTABLE TRUE
@@ -243,14 +242,24 @@ if (WIN32)
add_custom_target(windeploy
COMMENT "Deploy everything to the output dir"
DEPENDS ${GOLDENDICT} # build this target will check if Goldendict.exe is already built
- COMMAND ${TOOL_WINDEPLOYQT} --no-quick-import "${GD_WIN_OUTPUT_DIR}/${GOLDENDICT}.exe" --plugindir "${GD_WIN_OUTPUT_DIR}/plugins"
+ COMMAND ${WINDEPLOYQT_EXECUTABLE} --no-quick-import "${GD_WIN_OUTPUT_DIR}/${GOLDENDICT}.exe" --plugindir "${GD_WIN_OUTPUT_DIR}/plugins"
COMMAND ${CMAKE_COMMAND} -E copy_directory "${CMAKE_SOURCE_DIR}/opencc" "${GD_WIN_OUTPUT_DIR}/opencc"
COMMAND ${CMAKE_COMMAND} -E copy "${CMAKE_SOURCE_DIR}/LICENSE.txt" "${GD_WIN_OUTPUT_DIR}/LICENSE.txt"
COMMAND ${CMAKE_COMMAND} -E rm -f "${GD_WIN_OUTPUT_DIR}/goldendict.exe.manifest" "${GD_WIN_OUTPUT_DIR}/eb.dll.manifest"
WORKING_DIRECTORY ${GD_WIN_OUTPUT_DIR}
)
- #TODO: use CPack to make the output folder as NSIS installer
+ # use CPack to make the output folder as NSIS installer
+ install(
+ DIRECTORY "${GD_WIN_OUTPUT_DIR}/"
+ DESTINATION .
+ )
+
+ set(CPACK_PACKAGE_FILE_NAME "${Qt6Widgets_VERSION}-${PROJECT_NAME}-${PROJECT_VERSION}-${CMAKE_SYSTEM_NAME}-${CMAKE_SYSTEM_PROCESSOR}")
+ set(CPACK_GENERATOR "7Z;NSIS")
+ set(CPACK_NSIS_MANIFEST_DPI_AWARE ON)
+ set(CPACK_RESOURCE_FILE_LICENSE "${CMAKE_CURRENT_SOURCE_DIR}/LICENSE.txt")
+ include(CPack)
endif ()
feature_summary(WHAT ALL DESCRIPTION "Build configuration:")
diff --git a/CMake_Win.cmake b/CMake_Win.cmake
index 5b96c257f..354edd89a 100644
--- a/CMake_Win.cmake
+++ b/CMake_Win.cmake
@@ -28,7 +28,7 @@ target_link_libraries(${GOLDENDICT} PRIVATE ${THIRD_PARTY_LIBARY})
# Copy .dlls to output dir
-file(GLOB DLL_FILES LIST_DIRECTORIES false "${CMAKE_SOURCE_DIR}/winlibs/lib/msvc/*.dll")
+file(GLOB DLL_FILES LIST_DIRECTORIES false "${CMAKE_SOURCE_DIR}/winlibs/lib/msvc/*.dll" "${Qt6_ROOT}/bin/av*.dll" "${Qt6_ROOT}/bin/sw*.dll")
foreach (A_DLL_FILE ${DLL_FILES})
get_filename_component(TEMP_VAR_HOLDING_DLL_FILENAME ${A_DLL_FILE} NAME)
configure_file("${A_DLL_FILE}" "${GD_WIN_OUTPUT_DIR}/${TEMP_VAR_HOLDING_DLL_FILENAME}" COPYONLY)
diff --git a/goldendict.pro b/goldendict.pro
index c45fc1572..c4cc13ac6 100644
--- a/goldendict.pro
+++ b/goldendict.pro
@@ -290,7 +290,6 @@ HEADERS += \
src/audioplayerinterface.hh \
src/btreeidx.hh \
src/chunkedstorage.hh \
- src/common/atomic_rename.hh \
src/common/base_type.hh \
src/common/ex.hh \
src/common/file.hh \
@@ -419,7 +418,6 @@ SOURCES += \
src/audioplayerfactory.cc \
src/btreeidx.cc \
src/chunkedstorage.cc \
- src/common/atomic_rename.cc \
src/common/file.cc \
src/common/filetype.cc \
src/common/folding.cc \
diff --git a/locale/ru_RU.ts b/locale/ru_RU.ts
index 9276e2cd8..1842ccf48 100644
--- a/locale/ru_RU.ts
+++ b/locale/ru_RU.ts
@@ -91,7 +91,7 @@
- <h3 align="center">Добро пожаловать в <b>GoldenDict</b>!</h3><p>Чтобы начать работу с программой, сначала зайдите на сайт <em>Редактировать | Словари</em> , чтобы добавить пути к каталогам для поиска файлов словарей, настроить различные сайты Википедии или другие источники, настроить порядок словарей или создать группы словарей.<p>И тогда вы'готовы искать свои слова! Вы можете сделать это в этом окне, используя панель слева, или вы можете <a href="https://xiaoyifang.github.io/goldendict-ng/ui_popup/">искать слова из других активных приложений</a>. <p>Чтобы настроить программу, проверьте доступные настройки на <em>Редактировать | Предпочтения</em>. Все настройки там имеют подсказки, обязательно прочтите их, если у вас есть в чем-то сомнения.<p>Если вам нужна дополнительная помощь, у вас есть какие-либо вопросы, предложения или просто интересно, что думают другие, добро пожаловать на форум программы's <a href="https://github.com/xiaoyifang/goldendict/discussions"></a>.<p>Проверьте наличие обновлений на веб-сайте программы's <a href="https://github.com/xiaoyifang/goldendict"></a> . <p>(c) 2008-2013 Константин Исаков. Лицензия GPLv3 или более поздняя.
+ <h3 align="center">Добро пожаловать в <b>GoldenDict</b>!</h3><p>Чтобы начать работу с программой, сначала откройте меню <em>Правка | Словари</em>, чтобы добавить пути к каталогам со словарями, к Википедии и другим сайтам, упорядочить и сгруппировать словари.<p>И тогда вы сможете искать слова! Это можно делать непосредственно в этом окне, используя панель поиска слева, или <a href="https://xiaoyifang.github.io/goldendict-ng/ui_popup/">отправляя слова из других программ</a>. <p>Чтобы настроить GoldenDict, перейдите в настройки на <em>Правка | Настройки</em>. Все опции имеют подсказки: обязательно прочтите их, если у вас есть сомнения.<p>Если вам нужна помощь, или у вас есть какие-либо вопросы, предложения или просто интересно, что думают другие, заходите на форум GoldenDict's <a href="https://github.com/xiaoyifang/goldendict/discussions"></a>.<p> Проверить обновления можно на сайте <a href="https://github.com/xiaoyifang/goldendict"></a> . <p>(c) 2008-2013 Константин Исаков. Лицензия GPLv3 или более поздняя.
diff --git a/src/audioplayerfactory.cc b/src/audioplayerfactory.cc
index 05ff67c33..3e37ee369 100644
--- a/src/audioplayerfactory.cc
+++ b/src/audioplayerfactory.cc
@@ -65,9 +65,9 @@ void AudioPlayerFactory::reset()
#endif
}
- QScopedPointer< ExternalAudioPlayer > externalPlayer( new ExternalAudioPlayer );
+ std::unique_ptr< ExternalAudioPlayer > externalPlayer( new ExternalAudioPlayer );
setAudioPlaybackProgram( *externalPlayer );
- playerPtr.reset( externalPlayer.take() );
+ playerPtr.reset( externalPlayer.release() );
}
void AudioPlayerFactory::setAudioPlaybackProgram( ExternalAudioPlayer & externalPlayer )
diff --git a/src/btreeidx.cc b/src/btreeidx.cc
index 22132dc3d..bc1d44470 100644
--- a/src/btreeidx.cc
+++ b/src/btreeidx.cc
@@ -50,7 +50,7 @@ string const & BtreeDictionary::ensureInitDone()
return empty;
}
-void BtreeIndex::openIndex( IndexInfo const & indexInfo, File::Class & file, QMutex & mutex )
+void BtreeIndex::openIndex( IndexInfo const & indexInfo, File::Index & file, QMutex & mutex )
{
indexNodeSize = indexInfo.btreeMaxElements;
rootOffset = indexInfo.rootOffset;
@@ -754,7 +754,7 @@ void BtreeIndex::antialias( wstring const & str, vector< WordArticleLink > & cha
/// leaf nodes.
static uint32_t buildBtreeNode( IndexedWords::const_iterator & nextIndex,
size_t indexSize,
- File::Class & file,
+ File::Index & file,
size_t maxElements,
uint32_t & lastLeafLinkOffset )
{
@@ -984,7 +984,7 @@ void IndexedWords::addSingleWord( wstring const & index_word, uint32_t articleOf
operator[]( Utf8::encode( folded ) ).emplace_back( Utf8::encode( word ), articleOffset );
}
-IndexInfo buildIndex( IndexedWords const & indexedWords, File::Class & file )
+IndexInfo buildIndex( IndexedWords const & indexedWords, File::Index & file )
{
size_t indexSize = indexedWords.size();
auto nextIndex = indexedWords.begin();
diff --git a/src/btreeidx.hh b/src/btreeidx.hh
index 77c905460..07b4ce3f0 100644
--- a/src/btreeidx.hh
+++ b/src/btreeidx.hh
@@ -82,7 +82,7 @@ public:
/// Opens the index. The file reference is saved to be used for
/// subsequent lookups.
/// The mutex is the one to be locked when working with the file.
- void openIndex( IndexInfo const &, File::Class &, QMutex & );
+ void openIndex( IndexInfo const &, File::Index &, QMutex & );
/// Finds articles that match the given string. A case-insensitive search
/// is performed.
@@ -140,7 +140,7 @@ protected:
protected:
QMutex * idxFileMutex;
- File::Class * idxFile;
+ File::Index * idxFile;
private:
@@ -276,7 +276,7 @@ struct IndexedWords: public map< string, vector< WordArticleLink > >
/// Builds the index, as a compressed btree. Returns IndexInfo.
/// All the data is stored to the given file, beginning from its current
/// position.
-IndexInfo buildIndex( IndexedWords const &, File::Class & file );
+IndexInfo buildIndex( IndexedWords const &, File::Index & file );
} // namespace BtreeIndexing
diff --git a/src/chunkedstorage.cc b/src/chunkedstorage.cc
index 9a33e7a4a..c9fa93397 100644
--- a/src/chunkedstorage.cc
+++ b/src/chunkedstorage.cc
@@ -14,7 +14,7 @@ enum {
ChunkMaxSize = 65536 // Can't be more since it would overflow the address
};
-Writer::Writer( File::Class & f ):
+Writer::Writer( File::Index & f ):
file( f ),
chunkStarted( false ),
bufferUsed( 0 )
@@ -115,7 +115,7 @@ uint32_t Writer::finish()
return offset;
}
-Reader::Reader( File::Class & f, uint32_t offset ):
+Reader::Reader( File::Index & f, uint32_t offset ):
file( f )
{
file.seek( offset );
diff --git a/src/chunkedstorage.hh b/src/chunkedstorage.hh
index 76e958ed5..29705b496 100644
--- a/src/chunkedstorage.hh
+++ b/src/chunkedstorage.hh
@@ -31,11 +31,11 @@ DEF_EX( mapFailed, "Failed to map/unmap the file", Ex )
class Writer
{
vector< uint32_t > offsets;
- File::Class & file;
+ File::Index & file;
size_t scratchPadOffset, scratchPadSize;
public:
- explicit Writer( File::Class & );
+ explicit Writer( File::Index & );
/// Starts new block. Returns its address.
uint32_t startNewBlock();
@@ -72,12 +72,12 @@ private:
class Reader
{
vector< uint32_t > offsets;
- File::Class & file;
+ File::Index & file;
public:
/// Creates reader by giving it a file to read from and the offset returned
/// by Writer::finish().
- Reader( File::Class &, uint32_t );
+ Reader( File::Index &, uint32_t );
/// Reads the block previously written by Writer, identified by its address.
/// Uses the user-provided storage to load the entire chunk, and then to
diff --git a/src/common/atomic_rename.cc b/src/common/atomic_rename.cc
deleted file mode 100644
index d4e0fc1ed..000000000
--- a/src/common/atomic_rename.cc
+++ /dev/null
@@ -1,42 +0,0 @@
-/* This file is (c) 2008-2012 Konstantin Isakov
- * Part of GoldenDict. Licensed under GPLv3 or later, see the LICENSE file */
-
-#include "atomic_rename.hh"
-#include
-#include
-#include // for wchar_t
-#include
-#include
-
-#ifdef Q_OS_WIN32
- #include
-#endif
-
-#include
-
-
-bool renameAtomically( QString const & oldName, QString const & newName )
-{
-#ifdef Q_OS_WIN32
-
- QString srcFile( QDir::toNativeSeparators( oldName ) );
- QVector< wchar_t > srcFileW( srcFile.size() + 1 );
- srcFileW[ srcFile.toWCharArray( srcFileW.data() ) ] = 0;
-
- QString destFile( QDir::toNativeSeparators( newName ) );
- QVector< wchar_t > destFileW( destFile.size() + 1 );
- destFileW[ destFile.toWCharArray( destFileW.data() ) ] = 0;
-
- if ( !MoveFileExW( srcFileW.data(), destFileW.data(), MOVEFILE_REPLACE_EXISTING ) )
- return false;
-
-#else
-
- if ( rename( QFile::encodeName( QDir::toNativeSeparators( oldName ) ).data(),
- QFile::encodeName( QDir::toNativeSeparators( newName ) ).data() ) )
- return false;
-
-#endif
-
- return true;
-}
diff --git a/src/common/atomic_rename.hh b/src/common/atomic_rename.hh
deleted file mode 100644
index 4d28c568c..000000000
--- a/src/common/atomic_rename.hh
+++ /dev/null
@@ -1,14 +0,0 @@
-/* This file is (c) 2008-2012 Konstantin Isakov
- * Part of GoldenDict. Licensed under GPLv3 or later, see the LICENSE file */
-
-#ifndef __ATOMIC_RENAME_HH_INCLUDED__
-#define __ATOMIC_RENAME_HH_INCLUDED__
-
-#include
-
-/// Performs an atomic rename of file, from oldBame to newName. If newName
-/// exists, it gets overwritten. Names should feature Qt-style separators
-/// (straight slashes). Returns true on success, false on failure.
-bool renameAtomically( QString const & oldName, QString const & newName );
-
-#endif
diff --git a/src/common/file.cc b/src/common/file.cc
index fefa51f58..6c542bbcc 100644
--- a/src/common/file.cc
+++ b/src/common/file.cc
@@ -35,13 +35,13 @@ bool tryPossibleZipName( std::string const & name, std::string & copyTo )
void loadFromFile( std::string const & filename, std::vector< char > & data )
{
- File::Class f( filename, "rb" );
+ File::Index f( filename, "rb" );
auto size = f.file().size(); // QFile::size() obtains size via statx on Linux
data.resize( size );
f.read( data.data(), size );
}
-void Class::open( char const * mode )
+void Index::open( char const * mode )
{
QFile::OpenMode openMode = QIODevice::Text;
@@ -74,26 +74,26 @@ void Class::open( char const * mode )
throw exCantOpen( f.fileName().toStdString() + ": " + f.errorString().toUtf8().data() );
}
-Class::Class( std::string_view filename, char const * mode )
+Index::Index( std::string_view filename, char const * mode )
{
f.setFileName( QString::fromUtf8( filename.data(), filename.size() ) );
open( mode );
}
-void Class::read( void * buf, qint64 size )
+void Index::read( void * buf, qint64 size )
{
if ( f.read( static_cast< char * >( buf ), size ) != size ) {
throw exReadError();
}
}
-size_t Class::readRecords( void * buf, qint64 size, qint64 count )
+size_t Index::readRecords( void * buf, qint64 size, qint64 count )
{
qint64 result = f.read( static_cast< char * >( buf ), size * count );
return result < 0 ? result : result / size;
}
-void Class::write( void const * buf, qint64 size )
+void Index::write( void const * buf, qint64 size )
{
if ( 0 == size ) {
return;
@@ -106,13 +106,13 @@ void Class::write( void const * buf, qint64 size )
f.write( static_cast< char const * >( buf ), size );
}
-size_t Class::writeRecords( void const * buf, qint64 size, qint64 count )
+size_t Index::writeRecords( void const * buf, qint64 size, qint64 count )
{
qint64 result = f.write( static_cast< const char * >( buf ), size * count );
return result < 0 ? result : result / size;
}
-char * Class::gets( char * s, int size, bool stripNl )
+char * Index::gets( char * s, int size, bool stripNl )
{
qint64 len = f.readLine( s, size );
char * result = len > 0 ? s : nullptr;
@@ -134,7 +134,7 @@ char * Class::gets( char * s, int size, bool stripNl )
return result;
}
-std::string Class::gets( bool stripNl )
+std::string Index::gets( bool stripNl )
{
char buf[ 1024 ];
@@ -144,61 +144,61 @@ std::string Class::gets( bool stripNl )
return { buf };
}
-QByteArray Class::readall()
+QByteArray Index::readall()
{
return f.readAll();
};
-void Class::seek( qint64 offset )
+void Index::seek( qint64 offset )
{
if ( !f.seek( offset ) )
throw exSeekError();
}
-uchar * Class::map( qint64 offset, qint64 size )
+uchar * Index::map( qint64 offset, qint64 size )
{
return f.map( offset, size );
}
-bool Class::unmap( uchar * address )
+bool Index::unmap( uchar * address )
{
return f.unmap( address );
}
-void Class::seekEnd()
+void Index::seekEnd()
{
if ( !f.seek( f.size() ) )
throw exSeekError();
}
-void Class::rewind()
+void Index::rewind()
{
seek( 0 );
}
-qint64 Class::tell()
+qint64 Index::tell()
{
return f.pos();
}
-bool Class::eof() const
+bool Index::eof() const
{
return f.atEnd();
}
-QFile & Class::file()
+QFile & Index::file()
{
return f;
}
-void Class::close()
+void Index::close()
{
f.close();
}
-Class::~Class() noexcept
+Index::~Index() noexcept
{
f.close();
}
diff --git a/src/common/file.hh b/src/common/file.hh
index c6b08fbab..0cfc0017c 100644
--- a/src/common/file.hh
+++ b/src/common/file.hh
@@ -13,12 +13,7 @@
#include
#include
-/// A wrapper over QFile with some GD specific functions
-/// and exception throwing which are required for older coded to work correctly
-/// Consider the wrapped QFile as private implementation in the `Pimpl Idiom`
-///
-/// Note: this is used *only* in code related to `Dictionary::CLass` to manage dict files.
-/// In other places, just use QFile directly.
+/// File utilities
namespace File {
DEF_EX( Ex, "File exception", std::exception )
@@ -40,7 +35,8 @@ inline bool exists( std::string_view filename ) noexcept
return QFileInfo::exists( QString::fromUtf8( filename.data(), filename.size() ) );
};
-class Class
+/// Exclusivly used for processing GD's index files
+class Index
{
QFile f;
@@ -48,7 +44,7 @@ public:
QMutex lock;
// Create QFile Object and open() it.
- Class( std::string_view filename, char const * mode );
+ Index( std::string_view filename, char const * mode );
/// QFile::read & QFile::write , but with exception throwing
void read( void * buf, qint64 size );
@@ -115,7 +111,7 @@ public:
/// Closes the file. No further operations are valid.
void close();
- ~Class() noexcept;
+ ~Index() noexcept;
private:
// QFile::open but with fopen-like mode settings.
diff --git a/src/config.cc b/src/config.cc
index d81627808..92cafb879 100644
--- a/src/config.cc
+++ b/src/config.cc
@@ -3,7 +3,7 @@
#include "config.hh"
#include "folding.hh"
-#include
+#include
#include
#include
#include
@@ -17,8 +17,6 @@
#include
-#include "atomic_rename.hh"
-
#include
#if defined( HAVE_X11 )
@@ -1310,7 +1308,7 @@ void saveGroup( Group const & data, QDomElement & group )
void save( Class const & c )
{
- QFile configFile( getConfigFileName() + ".tmp" );
+ QSaveFile configFile( getConfigFileName() );
if ( !configFile.open( QFile::WriteOnly ) )
throw exCantWriteConfigFile();
@@ -2233,14 +2231,9 @@ void save( Class const & c )
hd.appendChild( opt );
}
- QByteArray result( dd.toByteArray() );
-
- if ( configFile.write( result ) != result.size() )
+ configFile.write( dd.toByteArray() );
+ if ( !configFile.commit() )
throw exCantWriteConfigFile();
-
- configFile.close();
-
- renameAtomically( configFile.fileName(), getConfigFileName() );
}
QString getConfigFileName()
diff --git a/src/dict/aard.cc b/src/dict/aard.cc
index 4aefd3b15..e713b86d3 100644
--- a/src/dict/aard.cc
+++ b/src/dict/aard.cc
@@ -119,7 +119,7 @@ __attribute__( ( packed ) )
bool indexIsOldOrBad( string const & indexFile )
{
- File::Class idx( indexFile, "rb" );
+ File::Index idx( indexFile, "rb" );
IdxHeader header;
@@ -214,10 +214,10 @@ class AardDictionary: public BtreeIndexing::BtreeDictionary
{
QMutex idxMutex;
QMutex aardMutex;
- File::Class idx;
+ File::Index idx;
IdxHeader idxHeader;
ChunkedStorage::Reader chunks;
- File::Class df;
+ File::Index df;
public:
@@ -774,7 +774,7 @@ vector< sptr< Dictionary::Class > > makeDictionaries( vector< string > const & f
}
}
- File::Class df( fileName, "rb" );
+ File::Index df( fileName, "rb" );
AAR_header dictHeader;
@@ -839,7 +839,7 @@ vector< sptr< Dictionary::Class > > makeDictionaries( vector< string > const & f
initializing.indexingDictionary( dictName );
- File::Class idx( indexFile, "wb" );
+ File::Index idx( indexFile, "wb" );
IdxHeader idxHeader;
memset( &idxHeader, 0, sizeof( idxHeader ) );
diff --git a/src/dict/bgl.cc b/src/dict/bgl.cc
index 6f697dca0..253859965 100644
--- a/src/dict/bgl.cc
+++ b/src/dict/bgl.cc
@@ -91,7 +91,7 @@ __attribute__( ( packed ) )
bool indexIsOldOrBad( string const & indexFile )
{
- File::Class idx( indexFile, "rb" );
+ File::Index idx( indexFile, "rb" );
IdxHeader header;
@@ -175,7 +175,7 @@ DEF_EX( exChunkIndexOutOfRange, "Chunk index is out of range", Dictionary::Ex )
class BglDictionary: public BtreeIndexing::BtreeDictionary
{
QMutex idxMutex;
- File::Class idx;
+ File::Index idx;
IdxHeader idxHeader;
ChunkedStorage::Reader chunks;
@@ -809,7 +809,7 @@ class BglResourceRequest: public Dictionary::DataRequest
{
QMutex & idxMutex;
- File::Class & idx;
+ File::Index & idx;
uint32_t resourceListOffset, resourcesCount;
string name;
@@ -819,7 +819,7 @@ class BglResourceRequest: public Dictionary::DataRequest
public:
BglResourceRequest( QMutex & idxMutex_,
- File::Class & idx_,
+ File::Index & idx_,
uint32_t resourceListOffset_,
uint32_t resourcesCount_,
string const & name_ ):
@@ -953,12 +953,12 @@ void BglDictionary::replaceCharsetEntities( string & text )
class ResourceHandler: public Babylon::ResourceHandler
{
- File::Class & idxFile;
+ File::Index & idxFile;
list< pair< string, uint32_t > > resources;
public:
- ResourceHandler( File::Class & idxFile_ ):
+ ResourceHandler( File::Index & idxFile_ ):
idxFile( idxFile_ )
{
}
@@ -1048,7 +1048,7 @@ vector< sptr< Dictionary::Class > > makeDictionaries( vector< string > const & f
initializing.indexingDictionary( b.title() );
- File::Class idx( indexFile, "wb" );
+ File::Index idx( indexFile, "wb" );
IdxHeader idxHeader;
diff --git a/src/dict/dictdfiles.cc b/src/dict/dictdfiles.cc
index c2ef09e10..941e5194b 100644
--- a/src/dict/dictdfiles.cc
+++ b/src/dict/dictdfiles.cc
@@ -73,7 +73,7 @@ __attribute__( ( packed ) )
bool indexIsOldOrBad( string const & indexFile )
{
- File::Class idx( indexFile, "rb" );
+ File::Index idx( indexFile, "rb" );
IdxHeader header;
@@ -84,7 +84,7 @@ bool indexIsOldOrBad( string const & indexFile )
class DictdDictionary: public BtreeIndexing::BtreeDictionary
{
QMutex idxMutex;
- File::Class idx, indexFile; // The later is .index file
+ File::Index idx, indexFile; // The later is .index file
IdxHeader idxHeader;
dictData * dz;
QMutex indexFileMutex, dzMutex;
@@ -579,7 +579,7 @@ vector< sptr< Dictionary::Class > > makeDictionaries( vector< string > const & f
initializing.indexingDictionary( dictionaryName );
- File::Class idx( indexFile, "wb" );
+ File::Index idx( indexFile, "wb" );
IdxHeader idxHeader;
@@ -592,7 +592,7 @@ vector< sptr< Dictionary::Class > > makeDictionaries( vector< string > const & f
IndexedWords indexedWords;
- File::Class indexFile( dictFiles[ 0 ], "rb" );
+ File::Index indexFile( dictFiles[ 0 ], "rb" );
// Read words from index until none's left.
diff --git a/src/dict/dsl.cc b/src/dict/dsl.cc
index 61b63679e..a4a4fd1f0 100644
--- a/src/dict/dsl.cc
+++ b/src/dict/dsl.cc
@@ -131,7 +131,7 @@ struct InsidedCard
bool indexIsOldOrBad( string const & indexFile, bool hasZipFile )
{
- File::Class idx( indexFile, "rb" );
+ File::Index idx( indexFile, "rb" );
IdxHeader header;
@@ -143,7 +143,7 @@ bool indexIsOldOrBad( string const & indexFile, bool hasZipFile )
class DslDictionary: public BtreeIndexing::BtreeDictionary
{
QMutex idxMutex;
- File::Class idx;
+ File::Index idx;
IdxHeader idxHeader;
sptr< ChunkedStorage::Reader > chunks;
string preferredSoundDictionary;
@@ -1718,7 +1718,7 @@ vector< sptr< Dictionary::Class > > makeDictionaries( vector< string > const & f
gdDebug( "Dsl: Building the index for dictionary: %s\n",
QString::fromStdU32String( scanner.getDictionaryName() ).toUtf8().data() );
- File::Class idx( indexFile, "wb" );
+ File::Index idx( indexFile, "wb" );
IdxHeader idxHeader;
diff --git a/src/dict/epwing.cc b/src/dict/epwing.cc
index edb3b7451..470f4b51f 100644
--- a/src/dict/epwing.cc
+++ b/src/dict/epwing.cc
@@ -70,7 +70,7 @@ __attribute__( ( packed ) )
bool indexIsOldOrBad( string const & indexFile )
{
- File::Class idx( indexFile, "rb" );
+ File::Index idx( indexFile, "rb" );
IdxHeader header;
@@ -84,7 +84,7 @@ class EpwingDictionary: public BtreeIndexing::BtreeDictionary
Q_DECLARE_TR_FUNCTIONS( Epwing::EpwingDictionary )
QMutex idxMutex;
- File::Class idx;
+ File::Index idx;
IdxHeader idxHeader;
string bookName;
ChunkedStorage::Reader chunks;
@@ -1201,7 +1201,7 @@ vector< sptr< Dictionary::Class > > makeDictionaries( vector< string > const & f
QByteArray nameData = str.toUtf8();
initializing.indexingDictionary( nameData.data() );
- File::Class idx( indexFile, "wb" );
+ File::Index idx( indexFile, "wb" );
IdxHeader idxHeader{};
diff --git a/src/dict/gls.cc b/src/dict/gls.cc
index 739296cd9..ed02a580c 100644
--- a/src/dict/gls.cc
+++ b/src/dict/gls.cc
@@ -332,7 +332,7 @@ __attribute__( ( packed ) )
bool indexIsOldOrBad( string const & indexFile, bool hasZipFile )
{
- File::Class idx( indexFile, "rb" );
+ File::Index idx( indexFile, "rb" );
IdxHeader header;
@@ -344,7 +344,7 @@ bool indexIsOldOrBad( string const & indexFile, bool hasZipFile )
class GlsDictionary: public BtreeIndexing::BtreeDictionary
{
QMutex idxMutex;
- File::Class idx;
+ File::Index idx;
IdxHeader idxHeader;
dictData * dz;
ChunkedStorage::Reader chunks;
@@ -1233,7 +1233,7 @@ vector< sptr< Dictionary::Class > > makeDictionaries( vector< string > const & f
gdDebug( "Gls: Building the index for dictionary: %s\n",
QString::fromStdU32String( scanner.getDictionaryName() ).toUtf8().data() );
- File::Class idx( indexFile, "wb" );
+ File::Index idx( indexFile, "wb" );
IdxHeader idxHeader;
diff --git a/src/dict/lsa.cc b/src/dict/lsa.cc
index 2d206a935..644da7652 100644
--- a/src/dict/lsa.cc
+++ b/src/dict/lsa.cc
@@ -65,7 +65,7 @@ __attribute__( ( packed ) )
bool indexIsOldOrBad( string const & indexFile )
{
- File::Class idx( indexFile, "rb" );
+ File::Index idx( indexFile, "rb" );
IdxHeader header;
@@ -91,10 +91,10 @@ struct Entry
public:
// Reads an entry from the file's current position
- Entry( File::Class & f );
+ Entry( File::Index & f );
};
-Entry::Entry( File::Class & f )
+Entry::Entry( File::Index & f )
{
bool firstEntry = ( f.tell() == 13 );
// Read the entry's filename
@@ -147,7 +147,7 @@ Entry::Entry( File::Class & f )
class LsaDictionary: public BtreeIndexing::BtreeDictionary
{
QMutex idxMutex;
- File::Class idx;
+ File::Index idx;
IdxHeader idxHeader;
public:
@@ -390,7 +390,7 @@ sptr< Dictionary::DataRequest > LsaDictionary::getResource( string const & name
if ( chain.empty() )
return std::make_shared< Dictionary::DataRequestInstant >( false ); // No such resource
- File::Class f( getDictionaryFilenames()[ 0 ], "rb" );
+ File::Index f( getDictionaryFilenames()[ 0 ], "rb" );
f.seek( chain[ 0 ].articleOffset );
Entry e( f );
@@ -503,7 +503,7 @@ vector< sptr< Dictionary::Class > > makeDictionaries( vector< string > const & f
continue;
try {
- File::Class f( *i, "rb" );
+ File::Index f( *i, "rb" );
/// Check the signature
@@ -528,7 +528,7 @@ vector< sptr< Dictionary::Class > > makeDictionaries( vector< string > const & f
initializing.indexingDictionary( Utils::Fs::basename( *i ) );
- File::Class idx( indexFile, "wb" );
+ File::Index idx( indexFile, "wb" );
IdxHeader idxHeader;
diff --git a/src/dict/mdx.cc b/src/dict/mdx.cc
index ba5a50766..da891ee03 100644
--- a/src/dict/mdx.cc
+++ b/src/dict/mdx.cc
@@ -191,7 +191,7 @@ class IndexedMdd: public BtreeIndexing::BtreeIndex
class MdxDictionary: public BtreeIndexing::BtreeDictionary
{
QMutex idxMutex;
- File::Class idx;
+ File::Index idx;
string idxFileName;
IdxHeader idxHeader;
string encoding;
@@ -1220,7 +1220,7 @@ class ResourceHandler: public MdictParser::RecordHandler
static bool indexIsOldOrBad( vector< string > const & dictFiles, string const & indexFile )
{
- File::Class idx( indexFile, "rb" );
+ File::Index idx( indexFile, "rb" );
IdxHeader header;
return idx.readRecords( &header, sizeof( header ), 1 ) != 1 || header.signature != kSignature
@@ -1296,7 +1296,7 @@ vector< sptr< Dictionary::Class > > makeDictionaries( vector< string > const & f
}
}
- File::Class idx( indexFile, "wb" );
+ File::Index idx( indexFile, "wb" );
IdxHeader idxHeader;
memset( &idxHeader, 0, sizeof( idxHeader ) );
// We write a dummy header first. At the end of the process the header
diff --git a/src/dict/sdict.cc b/src/dict/sdict.cc
index 589ead36a..d5cb74f71 100644
--- a/src/dict/sdict.cc
+++ b/src/dict/sdict.cc
@@ -112,7 +112,7 @@ __attribute__( ( packed ) )
bool indexIsOldOrBad( string const & indexFile )
{
- File::Class idx( indexFile, "rb" );
+ File::Index idx( indexFile, "rb" );
IdxHeader header;
@@ -123,10 +123,10 @@ bool indexIsOldOrBad( string const & indexFile )
class SdictDictionary: public BtreeIndexing::BtreeDictionary
{
QMutex idxMutex, sdictMutex;
- File::Class idx;
+ File::Index idx;
IdxHeader idxHeader;
ChunkedStorage::Reader chunks;
- File::Class df;
+ File::Index df; // Not an index, uses this type for legacy reasons.
public:
@@ -676,7 +676,7 @@ vector< sptr< Dictionary::Class > > makeDictionaries( vector< string > const & f
try {
gdDebug( "SDict: Building the index for dictionary: %s\n", fileName.c_str() );
- File::Class df( fileName, "rb" );
+ File::Index df( fileName, "rb" );
DCT_header dictHeader;
@@ -706,7 +706,7 @@ vector< sptr< Dictionary::Class > > makeDictionaries( vector< string > const & f
initializing.indexingDictionary( dictName );
- File::Class idx( indexFile, "wb" );
+ File::Index idx( indexFile, "wb" );
IdxHeader idxHeader;
memset( &idxHeader, 0, sizeof( idxHeader ) );
diff --git a/src/dict/slob.cc b/src/dict/slob.cc
index 4a27469fb..3b35052b3 100644
--- a/src/dict/slob.cc
+++ b/src/dict/slob.cc
@@ -103,7 +103,7 @@ struct RefEntry
bool indexIsOldOrBad( string const & indexFile )
{
- File::Class idx( indexFile, "rb" );
+ File::Index idx( indexFile, "rb" );
IdxHeader header;
@@ -575,7 +575,7 @@ class SlobDictionary: public BtreeIndexing::BtreeDictionary
{
QMutex idxMutex;
QMutex slobMutex, idxResourceMutex;
- File::Class idx;
+ File::Index idx;
BtreeIndex resourceIndex;
IdxHeader idxHeader;
SlobFile sf;
@@ -1387,7 +1387,7 @@ vector< sptr< Dictionary::Class > > makeDictionaries( vector< string > const & f
initializing.indexingDictionary( sf.getDictionaryName().toUtf8().constData() );
- File::Class idx( indexFile, "wb" );
+ File::Index idx( indexFile, "wb" );
IdxHeader idxHeader;
memset( &idxHeader, 0, sizeof( idxHeader ) );
diff --git a/src/dict/sounddir.cc b/src/dict/sounddir.cc
index a61852cd8..4d167fd4a 100644
--- a/src/dict/sounddir.cc
+++ b/src/dict/sounddir.cc
@@ -53,7 +53,7 @@ __attribute__( ( packed ) )
bool indexIsOldOrBad( string const & indexFile )
{
- File::Class idx( indexFile, "rb" );
+ File::Index idx( indexFile, "rb" );
IdxHeader header;
@@ -65,7 +65,7 @@ class SoundDirDictionary: public BtreeIndexing::BtreeDictionary
{
string name;
QMutex idxMutex;
- File::Class idx;
+ File::Index idx;
IdxHeader idxHeader;
ChunkedStorage::Reader chunks;
QString iconFilename;
@@ -362,7 +362,7 @@ sptr< Dictionary::DataRequest > SoundDirDictionary::getResource( string const &
// Now try loading that file
try {
- File::Class f( fileName.toStdString(), "rb" );
+ File::Index f( fileName.toStdString(), "rb" );
sptr< Dictionary::DataRequestInstant > dr = std::make_shared< Dictionary::DataRequestInstant >( true );
@@ -463,7 +463,7 @@ vector< sptr< Dictionary::Class > > makeDictionaries( Config::SoundDirs const &
initializing.indexingDictionary( soundDir.name.toUtf8().data() );
- File::Class idx( indexFile, "wb" );
+ File::Index idx( indexFile, "wb" );
IdxHeader idxHeader;
diff --git a/src/dict/sources.cc b/src/dict/sources.cc
index 87a1d7bb3..ced7b6e85 100644
--- a/src/dict/sources.cc
+++ b/src/dict/sources.cc
@@ -41,7 +41,7 @@ Sources::Sources( QWidget * parent, Config::Class const & cfg ):
// anyone?
QItemEditorCreatorBase * programTypeEditorCreator = new QStandardItemEditorCreator< ProgramTypeEditor >();
- itemEditorFactory->registerEditor( QVariant::Int, programTypeEditorCreator );
+ itemEditorFactory->registerEditor( QMetaType::Int, programTypeEditorCreator );
itemDelegate->setItemEditorFactory( itemEditorFactory.get() );
diff --git a/src/dict/stardict.cc b/src/dict/stardict.cc
index a34ae5ab2..a7728d53e 100644
--- a/src/dict/stardict.cc
+++ b/src/dict/stardict.cc
@@ -90,7 +90,7 @@ struct Ifo
string sametypesequence, dicttype, description;
string copyright, author, email, website, date;
- explicit Ifo( File::Class & );
+ explicit Ifo( File::Index & );
};
enum {
@@ -123,7 +123,7 @@ __attribute__( ( packed ) )
bool indexIsOldOrBad( string const & indexFile )
{
- File::Class idx( indexFile, "rb" );
+ File::Index idx( indexFile, "rb" );
IdxHeader header;
@@ -134,7 +134,7 @@ bool indexIsOldOrBad( string const & indexFile )
class StardictDictionary: public BtreeIndexing::BtreeDictionary
{
QMutex idxMutex;
- File::Class idx;
+ File::Index idx;
IdxHeader idxHeader;
string bookName;
string sameTypeSequence;
@@ -1039,7 +1039,7 @@ QString const & StardictDictionary::getDescription()
if ( !dictionaryDescription.isEmpty() )
return dictionaryDescription;
- File::Class ifoFile( getDictionaryFilenames()[ 0 ], "r" );
+ File::Index ifoFile( getDictionaryFilenames()[ 0 ], "r" );
Ifo ifo( ifoFile );
if ( !ifo.copyright.empty() ) {
@@ -1402,7 +1402,7 @@ static char const * beginsWith( char const * substr, char const * str )
return strncmp( str, substr, len ) == 0 ? str + len : 0;
}
-Ifo::Ifo( File::Class & f ):
+Ifo::Ifo( File::Index & f ):
wordcount( 0 ),
synwordcount( 0 ),
idxfilesize( 0 ),
@@ -1809,7 +1809,7 @@ vector< sptr< Dictionary::Class > > makeDictionaries( vector< string > const & f
if ( Dictionary::needToRebuildIndex( dictFiles, indexFile ) || indexIsOldOrBad( indexFile ) ) {
// Building the index
- File::Class ifoFile( fileName, "r" );
+ File::Index ifoFile( fileName, "r" );
Ifo ifo( ifoFile );
@@ -1840,7 +1840,7 @@ vector< sptr< Dictionary::Class > > makeDictionaries( vector< string > const & f
initializing.indexingDictionary( ifo.bookname );
- File::Class idx( indexFile, "wb" );
+ File::Index idx( indexFile, "wb" );
IdxHeader idxHeader;
diff --git a/src/dict/xdxf.cc b/src/dict/xdxf.cc
index bb7e40f73..6534f23d7 100644
--- a/src/dict/xdxf.cc
+++ b/src/dict/xdxf.cc
@@ -129,7 +129,7 @@ __attribute__( ( packed ) )
bool indexIsOldOrBad( string const & indexFile )
{
- File::Class idx( indexFile, "rb" );
+ File::Index idx( indexFile, "rb" );
IdxHeader header;
@@ -141,7 +141,7 @@ bool indexIsOldOrBad( string const & indexFile )
class XdxfDictionary: public BtreeIndexing::BtreeDictionary
{
QMutex idxMutex;
- File::Class idx;
+ File::Index idx;
IdxHeader idxHeader;
sptr< ChunkedStorage::Reader > chunks;
QMutex dzMutex;
@@ -1040,7 +1040,7 @@ vector< sptr< Dictionary::Class > > makeDictionaries( vector< string > const & f
//initializing.indexingDictionary( nameFromFileName( dictFiles[ 0 ] ) );
- File::Class idx( indexFile, "wb" );
+ File::Index idx( indexFile, "wb" );
IdxHeader idxHeader;
map< string, string > abrv;
diff --git a/src/dict/zim.cc b/src/dict/zim.cc
index 9147f72c0..5f5ef0c82 100644
--- a/src/dict/zim.cc
+++ b/src/dict/zim.cc
@@ -98,7 +98,7 @@ __attribute__( ( packed ) )
// Some supporting functions
bool indexIsOldOrBad( string const & indexFile )
{
- File::Class idx( indexFile, "rb" );
+ File::Index idx( indexFile, "rb" );
IdxHeader header;
@@ -162,7 +162,7 @@ class ZimDictionary: public BtreeIndexing::BtreeDictionary
{
QMutex idxMutex;
QMutex zimMutex;
- File::Class idx;
+ File::Index idx;
IdxHeader idxHeader;
ZimFile df;
set< quint32 > articlesIndexedForFTS;
@@ -842,7 +842,7 @@ vector< sptr< Dictionary::Class > > makeDictionaries( vector< string > const & f
initializing.indexingDictionary( firstName.mid( n + 1 ).toUtf8().constData() );
}
- File::Class idx( indexFile, "wb" );
+ File::Index idx( indexFile, "wb" );
IdxHeader idxHeader;
memset( &idxHeader, 0, sizeof( idxHeader ) );
idxHeader.namePtr = 0xFFFFFFFF;
diff --git a/src/dict/zipsounds.cc b/src/dict/zipsounds.cc
index 9b16c53d9..61f60ef3c 100644
--- a/src/dict/zipsounds.cc
+++ b/src/dict/zipsounds.cc
@@ -61,7 +61,7 @@ __attribute__( ( packed ) )
bool indexIsOldOrBad( string const & indexFile )
{
- File::Class idx( indexFile, "rb" );
+ File::Index idx( indexFile, "rb" );
IdxHeader header;
@@ -98,7 +98,7 @@ wstring stripExtension( string const & str )
class ZipSoundsDictionary: public BtreeIndexing::BtreeDictionary
{
QMutex idxMutex;
- File::Class idx;
+ File::Index idx;
IdxHeader idxHeader;
sptr< ChunkedStorage::Reader > chunks;
IndexedZip zipsFile;
@@ -394,7 +394,7 @@ vector< sptr< Dictionary::Class > > makeDictionaries( vector< string > const & f
if ( Dictionary::needToRebuildIndex( dictFiles, indexFile ) || indexIsOldOrBad( indexFile ) ) {
gdDebug( "Zips: Building the index for dictionary: %s\n", fileName.c_str() );
- File::Class idx( indexFile, "wb" );
+ File::Index idx( indexFile, "wb" );
IdxHeader idxHeader;
memset( &idxHeader, 0, sizeof( idxHeader ) );
diff --git a/src/externalaudioplayer.cc b/src/externalaudioplayer.cc
index fef94c3b5..89268cc05 100644
--- a/src/externalaudioplayer.cc
+++ b/src/externalaudioplayer.cc
@@ -16,7 +16,7 @@ ExternalAudioPlayer::~ExternalAudioPlayer()
// Set viewer to null first and foremost to make sure that onViewerDestroyed()
// doesn't attempt to start viewer or mess the smart pointer up.
- stopAndDestroySynchronously( viewer.take() );
+ stopAndDestroySynchronously( viewer.release() );
stopAndDestroySynchronously( exitingViewer );
}
@@ -56,7 +56,7 @@ void ExternalAudioPlayer::stop()
// 1) the process gets a chance to clean up and save its state;
// 2) there is no event loop blocking and consequently no (short) UI freeze
// while synchronously waiting for the external process to exit.
- exitingViewer = viewer.take();
+ exitingViewer = viewer.release();
}
else // viewer is either not started or already stopped -> simply destroy it.
viewer.reset();
@@ -72,14 +72,14 @@ void ExternalAudioPlayer::onViewerDestroyed( QObject * destroyedViewer )
emit error( errorMessage );
}
}
- else if ( viewer.data() == destroyedViewer )
- viewer.take(); // viewer finished and died -> release ownership.
+ else if ( viewer.get() == destroyedViewer )
+ viewer.reset( nullptr ); // viewer finished and died -> reset
}
QString ExternalAudioPlayer::startViewer()
{
Q_ASSERT( !exitingViewer && viewer );
- connect( viewer.data(), &QObject::destroyed, this, &ExternalAudioPlayer::onViewerDestroyed );
+ connect( viewer.get(), &QObject::destroyed, this, &ExternalAudioPlayer::onViewerDestroyed );
try {
viewer->start();
}
diff --git a/src/externalaudioplayer.hh b/src/externalaudioplayer.hh
index a85467626..02cb52140 100644
--- a/src/externalaudioplayer.hh
+++ b/src/externalaudioplayer.hh
@@ -4,9 +4,8 @@
#ifndef EXTERNALAUDIOPLAYER_HH_INCLUDED
#define EXTERNALAUDIOPLAYER_HH_INCLUDED
-#include
-#include
#include "audioplayerinterface.hh"
+#include
class ExternalViewer;
@@ -34,16 +33,16 @@ private:
///< the current viewer (if any) is not started yet
///< and waits for exitingViewer to be destroyed first.
- struct ScopedPointerDeleteLater
+ struct QObjectDeleteLater
{
- static void cleanup( QObject * p )
+ void operator()( QObject * p )
{
if ( p )
p->deleteLater();
}
};
// deleteLater() is safer because viewer actively participates in the QEventLoop.
- QScopedPointer< ExternalViewer, ScopedPointerDeleteLater > viewer;
+ std::unique_ptr< ExternalViewer, QObjectDeleteLater > viewer;
};
#endif // EXTERNALAUDIOPLAYER_HH_INCLUDED
diff --git a/src/ftshelpers.cc b/src/ftshelpers.cc
index a84ff3f98..8c42b20f4 100644
--- a/src/ftshelpers.cc
+++ b/src/ftshelpers.cc
@@ -91,7 +91,7 @@ void makeFTSIndex( BtreeIndexing::BtreeDictionary * dict, QAtomicInt & isCancell
if ( Utils::AtomicInt::loadAcquire( isCancelled ) )
throw exUserAbort();
- dict->sortArticlesOffsetsForFTS( offsets, isCancelled );
+ // dict->sortArticlesOffsetsForFTS( offsets, isCancelled );
// incremental build the index.
// get the last address.
diff --git a/src/headwordsmodel.cc b/src/headwordsmodel.cc
index d5ea4499b..ee081e5e2 100644
--- a/src/headwordsmodel.cc
+++ b/src/headwordsmodel.cc
@@ -150,7 +150,7 @@ void HeadwordListModel::fetchMore( const QModelIndex & parent )
}
QSet< QString > filtered;
- for ( const auto & word : qAsConst( headword ) ) {
+ for ( const auto & word : std::as_const( headword ) ) {
if ( !containWord( word ) )
filtered.insert( word );
}
diff --git a/src/history.cc b/src/history.cc
index 7b1257577..8a4662c82 100644
--- a/src/history.cc
+++ b/src/history.cc
@@ -3,8 +3,9 @@
#include "history.hh"
#include "config.hh"
-#include "atomic_rename.hh"
#include
+#include
+#include
History::History( unsigned size, unsigned maxItemLength_ ):
maxSize( size ),
@@ -117,7 +118,7 @@ bool History::save()
if ( !dirty )
return true;
- QFile file( Config::getHistoryFileName() + ".tmp" );
+ QSaveFile file( Config::getHistoryFileName() );
if ( !file.open( QFile::WriteOnly | QIODevice::Text ) )
return false;
@@ -136,11 +137,13 @@ bool History::save()
return false;
}
- file.close();
-
- dirty = false;
+ if ( file.commit() ) {
+ dirty = false;
+ return true;
+ }
- return renameAtomically( file.fileName(), Config::getHistoryFileName() );
+ qDebug() << "Failed to save history file";
+ return false;
}
void History::clear()
diff --git a/src/main.cc b/src/main.cc
index 27eb29526..c909b7afa 100644
--- a/src/main.cc
+++ b/src/main.cc
@@ -21,12 +21,12 @@
#endif
#include "termination.hh"
-#include "atomic_rename.hh"
#include
#include
#include
#include
#include
+#include
#include
#include "gddebug.hh"
@@ -301,6 +301,24 @@ void processCommandLine( QCoreApplication * app, GDOptions * result )
result->word.chop( 1 );
}
}
+
+ // Handle cases where we get encoded URL
+ if ( result->word.startsWith( QStringLiteral( "xn--" ) ) ) {
+ // For `kde-open` or `gio` or others, URL are encoded into ACE or Punycode
+ #if QT_VERSION >= QT_VERSION_CHECK( 6, 3, 0 )
+ result->word = QUrl::fromAce( result->word.toLatin1(), QUrl::IgnoreIDNWhitelist );
+ #else
+ // Old Qt's fromAce only applies to whitelisted domains, so we add .com to bypass this restriction :)
+ // https://bugreports.qt.io/browse/QTBUG-29080
+ result->word.append( QStringLiteral( ".com" ) );
+ result->word = QUrl::fromAce( result->word.toLatin1() );
+ result->word.chop( 4 );
+ #endif
+ }
+ else if ( result->word.startsWith( QStringLiteral( "%" ) ) ) {
+ // For Firefox or other browsers where URL are percent encoded
+ result->word = QUrl::fromPercentEncoding( result->word.toLatin1() );
+ }
#endif
}
}
@@ -480,7 +498,10 @@ int main( int argc, char ** argv )
return -1;
QString configFile = Config::getConfigFileName();
- renameAtomically( configFile, configFile + ".bad" );
+ QFile::rename( configFile,
+ configFile % QStringLiteral( "." )
+ % QDateTime::currentDateTime().toString( QStringLiteral( "yyyyMMdd_HHmmss" ) )
+ % QStringLiteral( ".bad" ) );
continue;
}
break;
diff --git a/src/metadata.cc b/src/metadata.cc
index 582a9d828..880c77d76 100644
--- a/src/metadata.cc
+++ b/src/metadata.cc
@@ -1,5 +1,5 @@
#include "metadata.hh"
-#include "toml++/toml.h"
+#include "toml++/toml.hpp"
#include
#include
diff --git a/src/ui/articleview.cc b/src/ui/articleview.cc
index fce90d767..ed6940b52 100644
--- a/src/ui/articleview.cc
+++ b/src/ui/articleview.cc
@@ -94,7 +94,6 @@ ArticleView::ArticleView( QWidget * parent,
Instances::Groups const & groups_,
bool popupView_,
Config::Class const & cfg_,
- QAction & openSearchAction_,
QLineEdit const * translateLine_,
QAction * dictionaryBarToggled_,
GroupComboBox const * groupComboBox_ ):
@@ -113,7 +112,6 @@ ArticleView::ArticleView( QWidget * parent,
selectCurrentArticleAction( this ),
copyAsTextAction( this ),
inspectAction( this ),
- openSearchAction( openSearchAction_ ),
searchIsOpened( false ),
dictionaryBarToggled( dictionaryBarToggled_ ),
groupComboBox( groupComboBox_ ),
@@ -201,9 +199,6 @@ ArticleView::ArticleView( QWidget * parent,
webview->addAction( &articleDownAction );
connect( &articleDownAction, &QAction::triggered, this, &ArticleView::moveOneArticleDown );
- webview->addAction( &openSearchAction );
- connect( &openSearchAction, &QAction::triggered, this, &ArticleView::openSearch );
-
selectCurrentArticleAction.setShortcut( QKeySequence( "Ctrl+Shift+A" ) );
selectCurrentArticleAction.setText( tr( "Select Current Article" ) );
webview->addAction( &selectCurrentArticleAction );
diff --git a/src/ui/articleview.hh b/src/ui/articleview.hh
index 7544be7e0..a29d05df9 100644
--- a/src/ui/articleview.hh
+++ b/src/ui/articleview.hh
@@ -49,7 +49,6 @@ class ArticleView: public QWidget
QAction pasteAction, articleUpAction, articleDownAction, goBackAction, goForwardAction, selectCurrentArticleAction,
copyAsTextAction, inspectAction;
- QAction & openSearchAction;
bool searchIsOpened;
bool expandOptionalParts;
QString rangeVarName;
@@ -105,7 +104,6 @@ public:
Instances::Groups const &,
bool popupView,
Config::Class const & cfg,
- QAction & openSearchAction_,
QLineEdit const * translateLine,
QAction * dictionaryBarToggled = nullptr,
GroupComboBox const * groupComboBox = nullptr );
@@ -319,6 +317,9 @@ signals:
public slots:
+ /// Opens the search (Ctrl+F)
+ void openSearch();
+
void on_searchPrevious_clicked();
void on_searchNext_clicked();
@@ -361,9 +362,6 @@ private slots:
/// Nagivates to the next article relative to the active one.
void moveOneArticleDown();
- /// Opens the search area
- void openSearch();
-
void on_searchText_textEdited();
void on_searchText_returnPressed();
void on_searchCloseButton_clicked();
diff --git a/src/ui/favoritespanewidget.cc b/src/ui/favoritespanewidget.cc
index 3aac937f8..788df1748 100644
--- a/src/ui/favoritespanewidget.cc
+++ b/src/ui/favoritespanewidget.cc
@@ -9,12 +9,15 @@
#include
#include
#include
+#include
+#include
+#include
+
#include
#include
#include "favoritespanewidget.hh"
#include "gddebug.hh"
-#include "atomic_rename.hh"
#include "globalbroadcaster.hh"
/************************************************** FavoritesPaneWidget *********************************************/
@@ -624,7 +627,10 @@ void FavoritesModel::readData()
dom.clear();
favoritesFile.close();
- renameAtomically( m_favoritesFilename, m_favoritesFilename + ".bak" );
+ QFile::rename( m_favoritesFilename,
+ m_favoritesFilename % QStringLiteral( "." )
+ % QDateTime::currentDateTime().toString( QStringLiteral( "yyyyMMdd_HHmmss" ) )
+ % QStringLiteral( ".bad" ) );
}
else
favoritesFile.close();
@@ -642,7 +648,7 @@ void FavoritesModel::saveData()
if ( !dirty )
return;
- QFile tmpFile( m_favoritesFilename + ".tmp" );
+ QSaveFile tmpFile( m_favoritesFilename );
if ( !tmpFile.open( QFile::WriteOnly ) ) {
gdWarning( "Can't write favorites file, error: %s", tmpFile.errorString().toUtf8().data() );
return;
@@ -661,11 +667,12 @@ void FavoritesModel::saveData()
return;
}
- tmpFile.close();
-
- if ( renameAtomically( tmpFile.fileName(), m_favoritesFilename ) )
+ if ( tmpFile.commit() ) {
dirty = false;
-
+ }
+ else {
+ qDebug() << "Failed to save favorite file";
+ }
dom.clear();
}
diff --git a/src/ui/mainwindow.cc b/src/ui/mainwindow.cc
index 2cd1cdc37..c77092f03 100644
--- a/src/ui/mainwindow.cc
+++ b/src/ui/mainwindow.cc
@@ -1783,7 +1783,6 @@ ArticleView * MainWindow::createNewTab( bool switchToIt, QString const & name )
groupInstances,
false,
cfg,
- *ui.searchInPageAction,
translateLine,
dictionaryBar.toggleViewAction(),
groupList );
@@ -1823,6 +1822,8 @@ ArticleView * MainWindow::createNewTab( bool switchToIt, QString const & name )
connect( view, &ArticleView::zoomOut, this, &MainWindow::zoomout );
connect( view, &ArticleView::saveBookmarkSignal, this, &MainWindow::addBookmarkToFavorite );
+ connect( ui.searchInPageAction, &QAction::triggered, view, &ArticleView::openSearch );
+
view->setSelectionBySingleClick( cfg.preferences.selectWordBySingleClick );
int index = cfg.preferences.newTabsOpenAfterCurrentOne ? ui.tabWidget->currentIndex() + 1 : ui.tabWidget->count();
diff --git a/src/ui/scanpopup.cc b/src/ui/scanpopup.cc
index 5b4aa34d5..c52eae249 100644
--- a/src/ui/scanpopup.cc
+++ b/src/ui/scanpopup.cc
@@ -85,8 +85,6 @@ ScanPopup::ScanPopup( QWidget * parent,
{
ui.setupUi( this );
- openSearchAction.setShortcut( QKeySequence( "Ctrl+F" ) );
-
if ( layoutDirection() == Qt::RightToLeft ) {
// Adjust button icons for Right-To-Left layout
ui.goBackButton->setIcon( QIcon( ":/icons/next.svg" ) );
@@ -104,7 +102,6 @@ ScanPopup::ScanPopup( QWidget * parent,
groups,
true,
cfg,
- openSearchAction,
ui.translateBox->translateLine(),
dictionaryBar.toggleViewAction() );
@@ -114,6 +111,9 @@ ScanPopup::ScanPopup( QWidget * parent,
connect( definition, &ArticleView::sendWordToHistory, this, &ScanPopup::sendWordToHistory );
connect( definition, &ArticleView::typingEvent, this, &ScanPopup::typingEvent );
+ openSearchAction.setShortcut( QKeySequence( "Ctrl+F" ) );
+ connect( &openSearchAction, &QAction::triggered, definition, &ArticleView::openSearch );
+
wordListDefaultFont = ui.translateBox->completerWidget()->font();
translateLineDefaultFont = ui.translateBox->font();
groupListDefaultFont = ui.groupList->font();
diff --git a/thirdparty/fmt/README_about_upgrade.md b/thirdparty/fmt/README_about_upgrade.md
index 75d3e1050..a0051d9a5 100644
--- a/thirdparty/fmt/README_about_upgrade.md
+++ b/thirdparty/fmt/README_about_upgrade.md
@@ -1,4 +1,4 @@
-This dir includes fmt 10.0.0 (as of May 2023)
+This dir includes fmt 10.2.1 (as of January 2024)
---
diff --git a/thirdparty/fmt/include/fmt/args.h b/thirdparty/fmt/include/fmt/args.h
new file mode 100644
index 000000000..ad1654bbb
--- /dev/null
+++ b/thirdparty/fmt/include/fmt/args.h
@@ -0,0 +1,235 @@
+// Formatting library for C++ - dynamic argument lists
+//
+// Copyright (c) 2012 - present, Victor Zverovich
+// All rights reserved.
+//
+// For the license information refer to format.h.
+
+#ifndef FMT_ARGS_H_
+#define FMT_ARGS_H_
+
+#include // std::reference_wrapper
+#include // std::unique_ptr
+#include
+
+#include "core.h"
+
+FMT_BEGIN_NAMESPACE
+
+namespace detail {
+
+template struct is_reference_wrapper : std::false_type {};
+template
+struct is_reference_wrapper> : std::true_type {};
+
+template auto unwrap(const T& v) -> const T& { return v; }
+template
+auto unwrap(const std::reference_wrapper& v) -> const T& {
+ return static_cast(v);
+}
+
+class dynamic_arg_list {
+ // Workaround for clang's -Wweak-vtables. Unlike for regular classes, for
+ // templates it doesn't complain about inability to deduce single translation
+ // unit for placing vtable. So storage_node_base is made a fake template.
+ template struct node {
+ virtual ~node() = default;
+ std::unique_ptr> next;
+ };
+
+ template struct typed_node : node<> {
+ T value;
+
+ template
+ FMT_CONSTEXPR typed_node(const Arg& arg) : value(arg) {}
+
+ template
+ FMT_CONSTEXPR typed_node(const basic_string_view& arg)
+ : value(arg.data(), arg.size()) {}
+ };
+
+ std::unique_ptr> head_;
+
+ public:
+ template auto push(const Arg& arg) -> const T& {
+ auto new_node = std::unique_ptr>(new typed_node(arg));
+ auto& value = new_node->value;
+ new_node->next = std::move(head_);
+ head_ = std::move(new_node);
+ return value;
+ }
+};
+} // namespace detail
+
+/**
+ \rst
+ A dynamic version of `fmt::format_arg_store`.
+ It's equipped with a storage to potentially temporary objects which lifetimes
+ could be shorter than the format arguments object.
+
+ It can be implicitly converted into `~fmt::basic_format_args` for passing
+ into type-erased formatting functions such as `~fmt::vformat`.
+ \endrst
+ */
+template
+class dynamic_format_arg_store
+#if FMT_GCC_VERSION && FMT_GCC_VERSION < 409
+ // Workaround a GCC template argument substitution bug.
+ : public basic_format_args
+#endif
+{
+ private:
+ using char_type = typename Context::char_type;
+
+ template struct need_copy {
+ static constexpr detail::type mapped_type =
+ detail::mapped_type_constant::value;
+
+ enum {
+ value = !(detail::is_reference_wrapper::value ||
+ std::is_same>::value ||
+ std::is_same>::value ||
+ (mapped_type != detail::type::cstring_type &&
+ mapped_type != detail::type::string_type &&
+ mapped_type != detail::type::custom_type))
+ };
+ };
+
+ template
+ using stored_type = conditional_t<
+ std::is_convertible>::value &&
+ !detail::is_reference_wrapper::value,
+ std::basic_string, T>;
+
+ // Storage of basic_format_arg must be contiguous.
+ std::vector> data_;
+ std::vector> named_info_;
+
+ // Storage of arguments not fitting into basic_format_arg must grow
+ // without relocation because items in data_ refer to it.
+ detail::dynamic_arg_list dynamic_args_;
+
+ friend class basic_format_args;
+
+ auto get_types() const -> unsigned long long {
+ return detail::is_unpacked_bit | data_.size() |
+ (named_info_.empty()
+ ? 0ULL
+ : static_cast(detail::has_named_args_bit));
+ }
+
+ auto data() const -> const basic_format_arg* {
+ return named_info_.empty() ? data_.data() : data_.data() + 1;
+ }
+
+ template void emplace_arg(const T& arg) {
+ data_.emplace_back(detail::make_arg(arg));
+ }
+
+ template
+ void emplace_arg(const detail::named_arg& arg) {
+ if (named_info_.empty()) {
+ constexpr const detail::named_arg_info* zero_ptr{nullptr};
+ data_.insert(data_.begin(), {zero_ptr, 0});
+ }
+ data_.emplace_back(detail::make_arg(detail::unwrap(arg.value)));
+ auto pop_one = [](std::vector>* data) {
+ data->pop_back();
+ };
+ std::unique_ptr>, decltype(pop_one)>
+ guard{&data_, pop_one};
+ named_info_.push_back({arg.name, static_cast(data_.size() - 2u)});
+ data_[0].value_.named_args = {named_info_.data(), named_info_.size()};
+ guard.release();
+ }
+
+ public:
+ constexpr dynamic_format_arg_store() = default;
+
+ /**
+ \rst
+ Adds an argument into the dynamic store for later passing to a formatting
+ function.
+
+ Note that custom types and string types (but not string views) are copied
+ into the store dynamically allocating memory if necessary.
+
+ **Example**::
+
+ fmt::dynamic_format_arg_store store;
+ store.push_back(42);
+ store.push_back("abc");
+ store.push_back(1.5f);
+ std::string result = fmt::vformat("{} and {} and {}", store);
+ \endrst
+ */
+ template void push_back(const T& arg) {
+ if (detail::const_check(need_copy::value))
+ emplace_arg(dynamic_args_.push>(arg));
+ else
+ emplace_arg(detail::unwrap(arg));
+ }
+
+ /**
+ \rst
+ Adds a reference to the argument into the dynamic store for later passing to
+ a formatting function.
+
+ **Example**::
+
+ fmt::dynamic_format_arg_store store;
+ char band[] = "Rolling Stones";
+ store.push_back(std::cref(band));
+ band[9] = 'c'; // Changing str affects the output.
+ std::string result = fmt::vformat("{}", store);
+ // result == "Rolling Scones"
+ \endrst
+ */
+ template void push_back(std::reference_wrapper arg) {
+ static_assert(
+ need_copy::value,
+ "objects of built-in types and string views are always copied");
+ emplace_arg(arg.get());
+ }
+
+ /**
+ Adds named argument into the dynamic store for later passing to a formatting
+ function. ``std::reference_wrapper`` is supported to avoid copying of the
+ argument. The name is always copied into the store.
+ */
+ template
+ void push_back(const detail::named_arg& arg) {
+ const char_type* arg_name =
+ dynamic_args_.push>(arg.name).c_str();
+ if (detail::const_check(need_copy::value)) {
+ emplace_arg(
+ fmt::arg(arg_name, dynamic_args_.push>(arg.value)));
+ } else {
+ emplace_arg(fmt::arg(arg_name, arg.value));
+ }
+ }
+
+ /** Erase all elements from the store */
+ void clear() {
+ data_.clear();
+ named_info_.clear();
+ dynamic_args_ = detail::dynamic_arg_list();
+ }
+
+ /**
+ \rst
+ Reserves space to store at least *new_cap* arguments including
+ *new_cap_named* named arguments.
+ \endrst
+ */
+ void reserve(size_t new_cap, size_t new_cap_named) {
+ FMT_ASSERT(new_cap >= new_cap_named,
+ "Set of arguments includes set of named arguments");
+ data_.reserve(new_cap);
+ named_info_.reserve(new_cap_named);
+ }
+};
+
+FMT_END_NAMESPACE
+
+#endif // FMT_ARGS_H_
diff --git a/thirdparty/fmt/include/fmt/chrono.h b/thirdparty/fmt/include/fmt/chrono.h
new file mode 100644
index 000000000..9d54574e1
--- /dev/null
+++ b/thirdparty/fmt/include/fmt/chrono.h
@@ -0,0 +1,2240 @@
+// Formatting library for C++ - chrono support
+//
+// Copyright (c) 2012 - present, Victor Zverovich
+// All rights reserved.
+//
+// For the license information refer to format.h.
+
+#ifndef FMT_CHRONO_H_
+#define FMT_CHRONO_H_
+
+#include
+#include
+#include // std::isfinite
+#include // std::memcpy
+#include
+#include
+#include
+#include
+#include
+
+#include "ostream.h" // formatbuf
+
+FMT_BEGIN_NAMESPACE
+
+// Check if std::chrono::local_t is available.
+#ifndef FMT_USE_LOCAL_TIME
+# ifdef __cpp_lib_chrono
+# define FMT_USE_LOCAL_TIME (__cpp_lib_chrono >= 201907L)
+# else
+# define FMT_USE_LOCAL_TIME 0
+# endif
+#endif
+
+// Check if std::chrono::utc_timestamp is available.
+#ifndef FMT_USE_UTC_TIME
+# ifdef __cpp_lib_chrono
+# define FMT_USE_UTC_TIME (__cpp_lib_chrono >= 201907L)
+# else
+# define FMT_USE_UTC_TIME 0
+# endif
+#endif
+
+// Enable tzset.
+#ifndef FMT_USE_TZSET
+// UWP doesn't provide _tzset.
+# if FMT_HAS_INCLUDE("winapifamily.h")
+# include
+# endif
+# if defined(_WIN32) && (!defined(WINAPI_FAMILY) || \
+ (WINAPI_FAMILY == WINAPI_FAMILY_DESKTOP_APP))
+# define FMT_USE_TZSET 1
+# else
+# define FMT_USE_TZSET 0
+# endif
+#endif
+
+// Enable safe chrono durations, unless explicitly disabled.
+#ifndef FMT_SAFE_DURATION_CAST
+# define FMT_SAFE_DURATION_CAST 1
+#endif
+#if FMT_SAFE_DURATION_CAST
+
+// For conversion between std::chrono::durations without undefined
+// behaviour or erroneous results.
+// This is a stripped down version of duration_cast, for inclusion in fmt.
+// See https://github.com/pauldreik/safe_duration_cast
+//
+// Copyright Paul Dreik 2019
+namespace safe_duration_cast {
+
+template ::value &&
+ std::numeric_limits::is_signed ==
+ std::numeric_limits::is_signed)>
+FMT_CONSTEXPR auto lossless_integral_conversion(const From from, int& ec)
+ -> To {
+ ec = 0;
+ using F = std::numeric_limits;
+ using T = std::numeric_limits;
+ static_assert(F::is_integer, "From must be integral");
+ static_assert(T::is_integer, "To must be integral");
+
+ // A and B are both signed, or both unsigned.
+ if (detail::const_check(F::digits <= T::digits)) {
+ // From fits in To without any problem.
+ } else {
+ // From does not always fit in To, resort to a dynamic check.
+ if (from < (T::min)() || from > (T::max)()) {
+ // outside range.
+ ec = 1;
+ return {};
+ }
+ }
+ return static_cast(from);
+}
+
+/**
+ * converts From to To, without loss. If the dynamic value of from
+ * can't be converted to To without loss, ec is set.
+ */
+template ::value &&
+ std::numeric_limits::is_signed !=
+ std::numeric_limits::is_signed)>
+FMT_CONSTEXPR auto lossless_integral_conversion(const From from, int& ec)
+ -> To {
+ ec = 0;
+ using F = std::numeric_limits;
+ using T = std::numeric_limits;
+ static_assert(F::is_integer, "From must be integral");
+ static_assert(T::is_integer, "To must be integral");
+
+ if (detail::const_check(F::is_signed && !T::is_signed)) {
+ // From may be negative, not allowed!
+ if (fmt::detail::is_negative(from)) {
+ ec = 1;
+ return {};
+ }
+ // From is positive. Can it always fit in To?
+ if (detail::const_check(F::digits > T::digits) &&
+ from > static_cast(detail::max_value())) {
+ ec = 1;
+ return {};
+ }
+ }
+
+ if (detail::const_check(!F::is_signed && T::is_signed &&
+ F::digits >= T::digits) &&
+ from > static_cast(detail::max_value())) {
+ ec = 1;
+ return {};
+ }
+ return static_cast(from); // Lossless conversion.
+}
+
+template ::value)>
+FMT_CONSTEXPR auto lossless_integral_conversion(const From from, int& ec)
+ -> To {
+ ec = 0;
+ return from;
+} // function
+
+// clang-format off
+/**
+ * converts From to To if possible, otherwise ec is set.
+ *
+ * input | output
+ * ---------------------------------|---------------
+ * NaN | NaN
+ * Inf | Inf
+ * normal, fits in output | converted (possibly lossy)
+ * normal, does not fit in output | ec is set
+ * subnormal | best effort
+ * -Inf | -Inf
+ */
+// clang-format on
+template ::value)>
+FMT_CONSTEXPR auto safe_float_conversion(const From from, int& ec) -> To {
+ ec = 0;
+ using T = std::numeric_limits;
+ static_assert(std::is_floating_point::value, "From must be floating");
+ static_assert(std::is_floating_point::value, "To must be floating");
+
+ // catch the only happy case
+ if (std::isfinite(from)) {
+ if (from >= T::lowest() && from <= (T::max)()) {
+ return static_cast(from);
+ }
+ // not within range.
+ ec = 1;
+ return {};
+ }
+
+ // nan and inf will be preserved
+ return static_cast(from);
+} // function
+
+template ::value)>
+FMT_CONSTEXPR auto safe_float_conversion(const From from, int& ec) -> To {
+ ec = 0;
+ static_assert(std::is_floating_point::value, "From must be floating");
+ return from;
+}
+
+/**
+ * safe duration cast between integral durations
+ */
+template ::value),
+ FMT_ENABLE_IF(std::is_integral::value)>
+auto safe_duration_cast(std::chrono::duration from,
+ int& ec) -> To {
+ using From = std::chrono::duration;
+ ec = 0;
+ // the basic idea is that we need to convert from count() in the from type
+ // to count() in the To type, by multiplying it with this:
+ struct Factor
+ : std::ratio_divide {};
+
+ static_assert(Factor::num > 0, "num must be positive");
+ static_assert(Factor::den > 0, "den must be positive");
+
+ // the conversion is like this: multiply from.count() with Factor::num
+ // /Factor::den and convert it to To::rep, all this without
+ // overflow/underflow. let's start by finding a suitable type that can hold
+ // both To, From and Factor::num
+ using IntermediateRep =
+ typename std::common_type::type;
+
+ // safe conversion to IntermediateRep
+ IntermediateRep count =
+ lossless_integral_conversion(from.count(), ec);
+ if (ec) return {};
+ // multiply with Factor::num without overflow or underflow
+ if (detail::const_check(Factor::num != 1)) {
+ const auto max1 = detail::max_value() / Factor::num;
+ if (count > max1) {
+ ec = 1;
+ return {};
+ }
+ const auto min1 =
+ (std::numeric_limits::min)() / Factor::num;
+ if (detail::const_check(!std::is_unsigned::value) &&
+ count < min1) {
+ ec = 1;
+ return {};
+ }
+ count *= Factor::num;
+ }
+
+ if (detail::const_check(Factor::den != 1)) count /= Factor::den;
+ auto tocount = lossless_integral_conversion(count, ec);
+ return ec ? To() : To(tocount);
+}
+
+/**
+ * safe duration_cast between floating point durations
+ */
+template ::value),
+ FMT_ENABLE_IF(std::is_floating_point::value)>
+auto safe_duration_cast(std::chrono::duration from,
+ int& ec) -> To {
+ using From = std::chrono::duration;
+ ec = 0;
+ if (std::isnan(from.count())) {
+ // nan in, gives nan out. easy.
+ return To{std::numeric_limits::quiet_NaN()};
+ }
+ // maybe we should also check if from is denormal, and decide what to do about
+ // it.
+
+ // +-inf should be preserved.
+ if (std::isinf(from.count())) {
+ return To{from.count()};
+ }
+
+ // the basic idea is that we need to convert from count() in the from type
+ // to count() in the To type, by multiplying it with this:
+ struct Factor
+ : std::ratio_divide {};
+
+ static_assert(Factor::num > 0, "num must be positive");
+ static_assert(Factor::den > 0, "den must be positive");
+
+ // the conversion is like this: multiply from.count() with Factor::num
+ // /Factor::den and convert it to To::rep, all this without
+ // overflow/underflow. let's start by finding a suitable type that can hold
+ // both To, From and Factor::num
+ using IntermediateRep =
+ typename std::common_type::type;
+
+ // force conversion of From::rep -> IntermediateRep to be safe,
+ // even if it will never happen be narrowing in this context.
+ IntermediateRep count =
+ safe_float_conversion(from.count(), ec);
+ if (ec) {
+ return {};
+ }
+
+ // multiply with Factor::num without overflow or underflow
+ if (detail::const_check(Factor::num != 1)) {
+ constexpr auto max1 = detail::max_value() /
+ static_cast(Factor::num);
+ if (count > max1) {
+ ec = 1;
+ return {};
+ }
+ constexpr auto min1 = std::numeric_limits::lowest() /
+ static_cast(Factor::num);
+ if (count < min1) {
+ ec = 1;
+ return {};
+ }
+ count *= static_cast(Factor::num);
+ }
+
+ // this can't go wrong, right? den>0 is checked earlier.
+ if (detail::const_check(Factor::den != 1)) {
+ using common_t = typename std::common_type::type;
+ count /= static_cast(Factor::den);
+ }
+
+ // convert to the to type, safely
+ using ToRep = typename To::rep;
+
+ const ToRep tocount = safe_float_conversion(count, ec);
+ if (ec) {
+ return {};
+ }
+ return To{tocount};
+}
+} // namespace safe_duration_cast
+#endif
+
+// Prevents expansion of a preceding token as a function-style macro.
+// Usage: f FMT_NOMACRO()
+#define FMT_NOMACRO
+
+namespace detail {
+template struct null {};
+inline auto localtime_r FMT_NOMACRO(...) -> null<> { return null<>(); }
+inline auto localtime_s(...) -> null<> { return null<>(); }
+inline auto gmtime_r(...) -> null<> { return null<>(); }
+inline auto gmtime_s(...) -> null<> { return null<>(); }
+
+inline auto get_classic_locale() -> const std::locale& {
+ static const auto& locale = std::locale::classic();
+ return locale;
+}
+
+template struct codecvt_result {
+ static constexpr const size_t max_size = 32;
+ CodeUnit buf[max_size];
+ CodeUnit* end;
+};
+
+template
+void write_codecvt(codecvt_result& out, string_view in_buf,
+ const std::locale& loc) {
+#if FMT_CLANG_VERSION
+# pragma clang diagnostic push
+# pragma clang diagnostic ignored "-Wdeprecated"
+ auto& f = std::use_facet>(loc);
+# pragma clang diagnostic pop
+#else
+ auto& f = std::use_facet>(loc);
+#endif
+ auto mb = std::mbstate_t();
+ const char* from_next = nullptr;
+ auto result = f.in(mb, in_buf.begin(), in_buf.end(), from_next,
+ std::begin(out.buf), std::end(out.buf), out.end);
+ if (result != std::codecvt_base::ok)
+ FMT_THROW(format_error("failed to format time"));
+}
+
+template
+auto write_encoded_tm_str(OutputIt out, string_view in, const std::locale& loc)
+ -> OutputIt {
+ if (detail::is_utf8() && loc != get_classic_locale()) {
+ // char16_t and char32_t codecvts are broken in MSVC (linkage errors) and
+ // gcc-4.
+#if FMT_MSC_VERSION != 0 || \
+ (defined(__GLIBCXX__) && !defined(_GLIBCXX_USE_DUAL_ABI))
+ // The _GLIBCXX_USE_DUAL_ABI macro is always defined in libstdc++ from gcc-5
+ // and newer.
+ using code_unit = wchar_t;
+#else
+ using code_unit = char32_t;
+#endif
+
+ using unit_t = codecvt_result;
+ unit_t unit;
+ write_codecvt(unit, in, loc);
+ // In UTF-8 is used one to four one-byte code units.
+ auto u =
+ to_utf8>();
+ if (!u.convert({unit.buf, to_unsigned(unit.end - unit.buf)}))
+ FMT_THROW(format_error("failed to format time"));
+ return copy_str(u.c_str(), u.c_str() + u.size(), out);
+ }
+ return copy_str(in.data(), in.data() + in.size(), out);
+}
+
+template ::value)>
+auto write_tm_str(OutputIt out, string_view sv, const std::locale& loc)
+ -> OutputIt {
+ codecvt_result unit;
+ write_codecvt(unit, sv, loc);
+ return copy_str(unit.buf, unit.end, out);
+}
+
+template ::value)>
+auto write_tm_str(OutputIt out, string_view sv, const std::locale& loc)
+ -> OutputIt {
+ return write_encoded_tm_str(out, sv, loc);
+}
+
+template
+inline void do_write(buffer& buf, const std::tm& time,
+ const std::locale& loc, char format, char modifier) {
+ auto&& format_buf = formatbuf>(buf);
+ auto&& os = std::basic_ostream(&format_buf);
+ os.imbue(loc);
+ const auto& facet = std::use_facet>(loc);
+ auto end = facet.put(os, os, Char(' '), &time, format, modifier);
+ if (end.failed()) FMT_THROW(format_error("failed to format time"));
+}
+
+template ::value)>
+auto write(OutputIt out, const std::tm& time, const std::locale& loc,
+ char format, char modifier = 0) -> OutputIt {
+ auto&& buf = get_buffer(out);
+ do_write(buf, time, loc, format, modifier);
+ return get_iterator(buf, out);
+}
+
+template ::value)>
+auto write(OutputIt out, const std::tm& time, const std::locale& loc,
+ char format, char modifier = 0) -> OutputIt {
+ auto&& buf = basic_memory_buffer();
+ do_write(buf, time, loc, format, modifier);
+ return write_encoded_tm_str(out, string_view(buf.data(), buf.size()), loc);
+}
+
+template
+struct is_same_arithmetic_type
+ : public std::integral_constant::value &&
+ std::is_integral::value) ||
+ (std::is_floating_point::value &&
+ std::is_floating_point::value)> {
+};
+
+template <
+ typename To, typename FromRep, typename FromPeriod,
+ FMT_ENABLE_IF(is_same_arithmetic_type::value)>
+auto fmt_duration_cast(std::chrono::duration from) -> To {
+#if FMT_SAFE_DURATION_CAST
+ // Throwing version of safe_duration_cast is only available for
+ // integer to integer or float to float casts.
+ int ec;
+ To to = safe_duration_cast::safe_duration_cast(from, ec);
+ if (ec) FMT_THROW(format_error("cannot format duration"));
+ return to;
+#else
+ // Standard duration cast, may overflow.
+ return std::chrono::duration_cast(from);
+#endif
+}
+
+template <
+ typename To, typename FromRep, typename FromPeriod,
+ FMT_ENABLE_IF(!is_same_arithmetic_type::value)>
+auto fmt_duration_cast(std::chrono::duration from) -> To {
+ // Mixed integer <-> float cast is not supported by safe_duration_cast.
+ return std::chrono::duration_cast(from);
+}
+
+template
+auto to_time_t(
+ std::chrono::time_point time_point)
+ -> std::time_t {
+ // Cannot use std::chrono::system_clock::to_time_t since this would first
+ // require a cast to std::chrono::system_clock::time_point, which could
+ // overflow.
+ return fmt_duration_cast>(
+ time_point.time_since_epoch())
+ .count();
+}
+} // namespace detail
+
+FMT_BEGIN_EXPORT
+
+/**
+ Converts given time since epoch as ``std::time_t`` value into calendar time,
+ expressed in local time. Unlike ``std::localtime``, this function is
+ thread-safe on most platforms.
+ */
+inline auto localtime(std::time_t time) -> std::tm {
+ struct dispatcher {
+ std::time_t time_;
+ std::tm tm_;
+
+ dispatcher(std::time_t t) : time_(t) {}
+
+ auto run() -> bool {
+ using namespace fmt::detail;
+ return handle(localtime_r(&time_, &tm_));
+ }
+
+ auto handle(std::tm* tm) -> bool { return tm != nullptr; }
+
+ auto handle(detail::null<>) -> bool {
+ using namespace fmt::detail;
+ return fallback(localtime_s(&tm_, &time_));
+ }
+
+ auto fallback(int res) -> bool { return res == 0; }
+
+#if !FMT_MSC_VERSION
+ auto fallback(detail::null<>) -> bool {
+ using namespace fmt::detail;
+ std::tm* tm = std::localtime(&time_);
+ if (tm) tm_ = *tm;
+ return tm != nullptr;
+ }
+#endif
+ };
+ dispatcher lt(time);
+ // Too big time values may be unsupported.
+ if (!lt.run()) FMT_THROW(format_error("time_t value out of range"));
+ return lt.tm_;
+}
+
+#if FMT_USE_LOCAL_TIME
+template
+inline auto localtime(std::chrono::local_time time) -> std::tm {
+ return localtime(
+ detail::to_time_t(std::chrono::current_zone()->to_sys(time)));
+}
+#endif
+
+/**
+ Converts given time since epoch as ``std::time_t`` value into calendar time,
+ expressed in Coordinated Universal Time (UTC). Unlike ``std::gmtime``, this
+ function is thread-safe on most platforms.
+ */
+inline auto gmtime(std::time_t time) -> std::tm {
+ struct dispatcher {
+ std::time_t time_;
+ std::tm tm_;
+
+ dispatcher(std::time_t t) : time_(t) {}
+
+ auto run() -> bool {
+ using namespace fmt::detail;
+ return handle(gmtime_r(&time_, &tm_));
+ }
+
+ auto handle(std::tm* tm) -> bool { return tm != nullptr; }
+
+ auto handle(detail::null<>) -> bool {
+ using namespace fmt::detail;
+ return fallback(gmtime_s(&tm_, &time_));
+ }
+
+ auto fallback(int res) -> bool { return res == 0; }
+
+#if !FMT_MSC_VERSION
+ auto fallback(detail::null<>) -> bool {
+ std::tm* tm = std::gmtime(&time_);
+ if (tm) tm_ = *tm;
+ return tm != nullptr;
+ }
+#endif
+ };
+ auto gt = dispatcher(time);
+ // Too big time values may be unsupported.
+ if (!gt.run()) FMT_THROW(format_error("time_t value out of range"));
+ return gt.tm_;
+}
+
+template
+inline auto gmtime(
+ std::chrono::time_point time_point)
+ -> std::tm {
+ return gmtime(detail::to_time_t(time_point));
+}
+
+namespace detail {
+
+// Writes two-digit numbers a, b and c separated by sep to buf.
+// The method by Pavel Novikov based on
+// https://johnnylee-sde.github.io/Fast-unsigned-integer-to-time-string/.
+inline void write_digit2_separated(char* buf, unsigned a, unsigned b,
+ unsigned c, char sep) {
+ unsigned long long digits =
+ a | (b << 24) | (static_cast(c) << 48);
+ // Convert each value to BCD.
+ // We have x = a * 10 + b and we want to convert it to BCD y = a * 16 + b.
+ // The difference is
+ // y - x = a * 6
+ // a can be found from x:
+ // a = floor(x / 10)
+ // then
+ // y = x + a * 6 = x + floor(x / 10) * 6
+ // floor(x / 10) is (x * 205) >> 11 (needs 16 bits).
+ digits += (((digits * 205) >> 11) & 0x000f00000f00000f) * 6;
+ // Put low nibbles to high bytes and high nibbles to low bytes.
+ digits = ((digits & 0x00f00000f00000f0) >> 4) |
+ ((digits & 0x000f00000f00000f) << 8);
+ auto usep = static_cast(sep);
+ // Add ASCII '0' to each digit byte and insert separators.
+ digits |= 0x3030003030003030 | (usep << 16) | (usep << 40);
+
+ constexpr const size_t len = 8;
+ if (const_check(is_big_endian())) {
+ char tmp[len];
+ std::memcpy(tmp, &digits, len);
+ std::reverse_copy(tmp, tmp + len, buf);
+ } else {
+ std::memcpy(buf, &digits, len);
+ }
+}
+
+template
+FMT_CONSTEXPR inline auto get_units() -> const char* {
+ if (std::is_same::value) return "as";
+ if (std::is_same::value) return "fs";
+ if (std::is_same::value) return "ps";
+ if (std::is_same::value) return "ns";
+ if (std::is_same::value) return "µs";
+ if (std::is_same::value) return "ms";
+ if (std::is_same::value) return "cs";
+ if (std::is_same::value) return "ds";
+ if (std::is_same>::value) return "s";
+ if (std::is_same::value) return "das";
+ if (std::is_same::value) return "hs";
+ if (std::is_same::value) return "ks";
+ if (std::is_same::value) return "Ms";
+ if (std::is_same::value) return "Gs";
+ if (std::is_same::value) return "Ts";
+ if (std::is_same::value) return "Ps";
+ if (std::is_same::value) return "Es";
+ if (std::is_same>::value) return "min";
+ if (std::is_same>::value) return "h";
+ if (std::is_same>::value) return "d";
+ return nullptr;
+}
+
+enum class numeric_system {
+ standard,
+ // Alternative numeric system, e.g. 十二 instead of 12 in ja_JP locale.
+ alternative
+};
+
+// Glibc extensions for formatting numeric values.
+enum class pad_type {
+ unspecified,
+ // Do not pad a numeric result string.
+ none,
+ // Pad a numeric result string with zeros even if the conversion specifier
+ // character uses space-padding by default.
+ zero,
+ // Pad a numeric result string with spaces.
+ space,
+};
+
+template
+auto write_padding(OutputIt out, pad_type pad, int width) -> OutputIt {
+ if (pad == pad_type::none) return out;
+ return std::fill_n(out, width, pad == pad_type::space ? ' ' : '0');
+}
+
+template
+auto write_padding(OutputIt out, pad_type pad) -> OutputIt {
+ if (pad != pad_type::none) *out++ = pad == pad_type::space ? ' ' : '0';
+ return out;
+}
+
+// Parses a put_time-like format string and invokes handler actions.
+template
+FMT_CONSTEXPR auto parse_chrono_format(const Char* begin, const Char* end,
+ Handler&& handler) -> const Char* {
+ if (begin == end || *begin == '}') return begin;
+ if (*begin != '%') FMT_THROW(format_error("invalid format"));
+ auto ptr = begin;
+ pad_type pad = pad_type::unspecified;
+ while (ptr != end) {
+ auto c = *ptr;
+ if (c == '}') break;
+ if (c != '%') {
+ ++ptr;
+ continue;
+ }
+ if (begin != ptr) handler.on_text(begin, ptr);
+ ++ptr; // consume '%'
+ if (ptr == end) FMT_THROW(format_error("invalid format"));
+ c = *ptr;
+ switch (c) {
+ case '_':
+ pad = pad_type::space;
+ ++ptr;
+ break;
+ case '-':
+ pad = pad_type::none;
+ ++ptr;
+ break;
+ case '0':
+ pad = pad_type::zero;
+ ++ptr;
+ break;
+ }
+ if (ptr == end) FMT_THROW(format_error("invalid format"));
+ c = *ptr++;
+ switch (c) {
+ case '%':
+ handler.on_text(ptr - 1, ptr);
+ break;
+ case 'n': {
+ const Char newline[] = {'\n'};
+ handler.on_text(newline, newline + 1);
+ break;
+ }
+ case 't': {
+ const Char tab[] = {'\t'};
+ handler.on_text(tab, tab + 1);
+ break;
+ }
+ // Year:
+ case 'Y':
+ handler.on_year(numeric_system::standard);
+ break;
+ case 'y':
+ handler.on_short_year(numeric_system::standard);
+ break;
+ case 'C':
+ handler.on_century(numeric_system::standard);
+ break;
+ case 'G':
+ handler.on_iso_week_based_year();
+ break;
+ case 'g':
+ handler.on_iso_week_based_short_year();
+ break;
+ // Day of the week:
+ case 'a':
+ handler.on_abbr_weekday();
+ break;
+ case 'A':
+ handler.on_full_weekday();
+ break;
+ case 'w':
+ handler.on_dec0_weekday(numeric_system::standard);
+ break;
+ case 'u':
+ handler.on_dec1_weekday(numeric_system::standard);
+ break;
+ // Month:
+ case 'b':
+ case 'h':
+ handler.on_abbr_month();
+ break;
+ case 'B':
+ handler.on_full_month();
+ break;
+ case 'm':
+ handler.on_dec_month(numeric_system::standard);
+ break;
+ // Day of the year/month:
+ case 'U':
+ handler.on_dec0_week_of_year(numeric_system::standard);
+ break;
+ case 'W':
+ handler.on_dec1_week_of_year(numeric_system::standard);
+ break;
+ case 'V':
+ handler.on_iso_week_of_year(numeric_system::standard);
+ break;
+ case 'j':
+ handler.on_day_of_year();
+ break;
+ case 'd':
+ handler.on_day_of_month(numeric_system::standard);
+ break;
+ case 'e':
+ handler.on_day_of_month_space(numeric_system::standard);
+ break;
+ // Hour, minute, second:
+ case 'H':
+ handler.on_24_hour(numeric_system::standard, pad);
+ break;
+ case 'I':
+ handler.on_12_hour(numeric_system::standard, pad);
+ break;
+ case 'M':
+ handler.on_minute(numeric_system::standard, pad);
+ break;
+ case 'S':
+ handler.on_second(numeric_system::standard, pad);
+ break;
+ // Other:
+ case 'c':
+ handler.on_datetime(numeric_system::standard);
+ break;
+ case 'x':
+ handler.on_loc_date(numeric_system::standard);
+ break;
+ case 'X':
+ handler.on_loc_time(numeric_system::standard);
+ break;
+ case 'D':
+ handler.on_us_date();
+ break;
+ case 'F':
+ handler.on_iso_date();
+ break;
+ case 'r':
+ handler.on_12_hour_time();
+ break;
+ case 'R':
+ handler.on_24_hour_time();
+ break;
+ case 'T':
+ handler.on_iso_time();
+ break;
+ case 'p':
+ handler.on_am_pm();
+ break;
+ case 'Q':
+ handler.on_duration_value();
+ break;
+ case 'q':
+ handler.on_duration_unit();
+ break;
+ case 'z':
+ handler.on_utc_offset(numeric_system::standard);
+ break;
+ case 'Z':
+ handler.on_tz_name();
+ break;
+ // Alternative representation:
+ case 'E': {
+ if (ptr == end) FMT_THROW(format_error("invalid format"));
+ c = *ptr++;
+ switch (c) {
+ case 'Y':
+ handler.on_year(numeric_system::alternative);
+ break;
+ case 'y':
+ handler.on_offset_year();
+ break;
+ case 'C':
+ handler.on_century(numeric_system::alternative);
+ break;
+ case 'c':
+ handler.on_datetime(numeric_system::alternative);
+ break;
+ case 'x':
+ handler.on_loc_date(numeric_system::alternative);
+ break;
+ case 'X':
+ handler.on_loc_time(numeric_system::alternative);
+ break;
+ case 'z':
+ handler.on_utc_offset(numeric_system::alternative);
+ break;
+ default:
+ FMT_THROW(format_error("invalid format"));
+ }
+ break;
+ }
+ case 'O':
+ if (ptr == end) FMT_THROW(format_error("invalid format"));
+ c = *ptr++;
+ switch (c) {
+ case 'y':
+ handler.on_short_year(numeric_system::alternative);
+ break;
+ case 'm':
+ handler.on_dec_month(numeric_system::alternative);
+ break;
+ case 'U':
+ handler.on_dec0_week_of_year(numeric_system::alternative);
+ break;
+ case 'W':
+ handler.on_dec1_week_of_year(numeric_system::alternative);
+ break;
+ case 'V':
+ handler.on_iso_week_of_year(numeric_system::alternative);
+ break;
+ case 'd':
+ handler.on_day_of_month(numeric_system::alternative);
+ break;
+ case 'e':
+ handler.on_day_of_month_space(numeric_system::alternative);
+ break;
+ case 'w':
+ handler.on_dec0_weekday(numeric_system::alternative);
+ break;
+ case 'u':
+ handler.on_dec1_weekday(numeric_system::alternative);
+ break;
+ case 'H':
+ handler.on_24_hour(numeric_system::alternative, pad);
+ break;
+ case 'I':
+ handler.on_12_hour(numeric_system::alternative, pad);
+ break;
+ case 'M':
+ handler.on_minute(numeric_system::alternative, pad);
+ break;
+ case 'S':
+ handler.on_second(numeric_system::alternative, pad);
+ break;
+ case 'z':
+ handler.on_utc_offset(numeric_system::alternative);
+ break;
+ default:
+ FMT_THROW(format_error("invalid format"));
+ }
+ break;
+ default:
+ FMT_THROW(format_error("invalid format"));
+ }
+ begin = ptr;
+ }
+ if (begin != ptr) handler.on_text(begin, ptr);
+ return ptr;
+}
+
+template struct null_chrono_spec_handler {
+ FMT_CONSTEXPR void unsupported() {
+ static_cast(this)->unsupported();
+ }
+ FMT_CONSTEXPR void on_year(numeric_system) { unsupported(); }
+ FMT_CONSTEXPR void on_short_year(numeric_system) { unsupported(); }
+ FMT_CONSTEXPR void on_offset_year() { unsupported(); }
+ FMT_CONSTEXPR void on_century(numeric_system) { unsupported(); }
+ FMT_CONSTEXPR void on_iso_week_based_year() { unsupported(); }
+ FMT_CONSTEXPR void on_iso_week_based_short_year() { unsupported(); }
+ FMT_CONSTEXPR void on_abbr_weekday() { unsupported(); }
+ FMT_CONSTEXPR void on_full_weekday() { unsupported(); }
+ FMT_CONSTEXPR void on_dec0_weekday(numeric_system) { unsupported(); }
+ FMT_CONSTEXPR void on_dec1_weekday(numeric_system) { unsupported(); }
+ FMT_CONSTEXPR void on_abbr_month() { unsupported(); }
+ FMT_CONSTEXPR void on_full_month() { unsupported(); }
+ FMT_CONSTEXPR void on_dec_month(numeric_system) { unsupported(); }
+ FMT_CONSTEXPR void on_dec0_week_of_year(numeric_system) { unsupported(); }
+ FMT_CONSTEXPR void on_dec1_week_of_year(numeric_system) { unsupported(); }
+ FMT_CONSTEXPR void on_iso_week_of_year(numeric_system) { unsupported(); }
+ FMT_CONSTEXPR void on_day_of_year() { unsupported(); }
+ FMT_CONSTEXPR void on_day_of_month(numeric_system) { unsupported(); }
+ FMT_CONSTEXPR void on_day_of_month_space(numeric_system) { unsupported(); }
+ FMT_CONSTEXPR void on_24_hour(numeric_system) { unsupported(); }
+ FMT_CONSTEXPR void on_12_hour(numeric_system) { unsupported(); }
+ FMT_CONSTEXPR void on_minute(numeric_system) { unsupported(); }
+ FMT_CONSTEXPR void on_second(numeric_system) { unsupported(); }
+ FMT_CONSTEXPR void on_datetime(numeric_system) { unsupported(); }
+ FMT_CONSTEXPR void on_loc_date(numeric_system) { unsupported(); }
+ FMT_CONSTEXPR void on_loc_time(numeric_system) { unsupported(); }
+ FMT_CONSTEXPR void on_us_date() { unsupported(); }
+ FMT_CONSTEXPR void on_iso_date() { unsupported(); }
+ FMT_CONSTEXPR void on_12_hour_time() { unsupported(); }
+ FMT_CONSTEXPR void on_24_hour_time() { unsupported(); }
+ FMT_CONSTEXPR void on_iso_time() { unsupported(); }
+ FMT_CONSTEXPR void on_am_pm() { unsupported(); }
+ FMT_CONSTEXPR void on_duration_value() { unsupported(); }
+ FMT_CONSTEXPR void on_duration_unit() { unsupported(); }
+ FMT_CONSTEXPR void on_utc_offset(numeric_system) { unsupported(); }
+ FMT_CONSTEXPR void on_tz_name() { unsupported(); }
+};
+
+struct tm_format_checker : null_chrono_spec_handler {
+ FMT_NORETURN void unsupported() { FMT_THROW(format_error("no format")); }
+
+ template
+ FMT_CONSTEXPR void on_text(const Char*, const Char*) {}
+ FMT_CONSTEXPR void on_year(numeric_system) {}
+ FMT_CONSTEXPR void on_short_year(numeric_system) {}
+ FMT_CONSTEXPR void on_offset_year() {}
+ FMT_CONSTEXPR void on_century(numeric_system) {}
+ FMT_CONSTEXPR void on_iso_week_based_year() {}
+ FMT_CONSTEXPR void on_iso_week_based_short_year() {}
+ FMT_CONSTEXPR void on_abbr_weekday() {}
+ FMT_CONSTEXPR void on_full_weekday() {}
+ FMT_CONSTEXPR void on_dec0_weekday(numeric_system) {}
+ FMT_CONSTEXPR void on_dec1_weekday(numeric_system) {}
+ FMT_CONSTEXPR void on_abbr_month() {}
+ FMT_CONSTEXPR void on_full_month() {}
+ FMT_CONSTEXPR void on_dec_month(numeric_system) {}
+ FMT_CONSTEXPR void on_dec0_week_of_year(numeric_system) {}
+ FMT_CONSTEXPR void on_dec1_week_of_year(numeric_system) {}
+ FMT_CONSTEXPR void on_iso_week_of_year(numeric_system) {}
+ FMT_CONSTEXPR void on_day_of_year() {}
+ FMT_CONSTEXPR void on_day_of_month(numeric_system) {}
+ FMT_CONSTEXPR void on_day_of_month_space(numeric_system) {}
+ FMT_CONSTEXPR void on_24_hour(numeric_system, pad_type) {}
+ FMT_CONSTEXPR void on_12_hour(numeric_system, pad_type) {}
+ FMT_CONSTEXPR void on_minute(numeric_system, pad_type) {}
+ FMT_CONSTEXPR void on_second(numeric_system, pad_type) {}
+ FMT_CONSTEXPR void on_datetime(numeric_system) {}
+ FMT_CONSTEXPR void on_loc_date(numeric_system) {}
+ FMT_CONSTEXPR void on_loc_time(numeric_system) {}
+ FMT_CONSTEXPR void on_us_date() {}
+ FMT_CONSTEXPR void on_iso_date() {}
+ FMT_CONSTEXPR void on_12_hour_time() {}
+ FMT_CONSTEXPR void on_24_hour_time() {}
+ FMT_CONSTEXPR void on_iso_time() {}
+ FMT_CONSTEXPR void on_am_pm() {}
+ FMT_CONSTEXPR void on_utc_offset(numeric_system) {}
+ FMT_CONSTEXPR void on_tz_name() {}
+};
+
+inline auto tm_wday_full_name(int wday) -> const char* {
+ static constexpr const char* full_name_list[] = {
+ "Sunday", "Monday", "Tuesday", "Wednesday",
+ "Thursday", "Friday", "Saturday"};
+ return wday >= 0 && wday <= 6 ? full_name_list[wday] : "?";
+}
+inline auto tm_wday_short_name(int wday) -> const char* {
+ static constexpr const char* short_name_list[] = {"Sun", "Mon", "Tue", "Wed",
+ "Thu", "Fri", "Sat"};
+ return wday >= 0 && wday <= 6 ? short_name_list[wday] : "???";
+}
+
+inline auto tm_mon_full_name(int mon) -> const char* {
+ static constexpr const char* full_name_list[] = {
+ "January", "February", "March", "April", "May", "June",
+ "July", "August", "September", "October", "November", "December"};
+ return mon >= 0 && mon <= 11 ? full_name_list[mon] : "?";
+}
+inline auto tm_mon_short_name(int mon) -> const char* {
+ static constexpr const char* short_name_list[] = {
+ "Jan", "Feb", "Mar", "Apr", "May", "Jun",
+ "Jul", "Aug", "Sep", "Oct", "Nov", "Dec",
+ };
+ return mon >= 0 && mon <= 11 ? short_name_list[mon] : "???";
+}
+
+template
+struct has_member_data_tm_gmtoff : std::false_type {};
+template
+struct has_member_data_tm_gmtoff>
+ : std::true_type {};
+
+template
+struct has_member_data_tm_zone : std::false_type {};
+template
+struct has_member_data_tm_zone>
+ : std::true_type {};
+
+#if FMT_USE_TZSET
+inline void tzset_once() {
+ static bool init = []() -> bool {
+ _tzset();
+ return true;
+ }();
+ ignore_unused(init);
+}
+#endif
+
+// Converts value to Int and checks that it's in the range [0, upper).
+template ::value)>
+inline auto to_nonnegative_int(T value, Int upper) -> Int {
+ if (!std::is_unsigned::value &&
+ (value < 0 || to_unsigned(value) > to_unsigned(upper))) {
+ FMT_THROW(fmt::format_error("chrono value is out of range"));
+ }
+ return static_cast(value);
+}
+template ::value)>
+inline auto to_nonnegative_int(T value, Int upper) -> Int {
+ if (value < 0 || value > static_cast(upper))
+ FMT_THROW(format_error("invalid value"));
+ return static_cast(value);
+}
+
+constexpr auto pow10(std::uint32_t n) -> long long {
+ return n == 0 ? 1 : 10 * pow10(n - 1);
+}
+
+// Counts the number of fractional digits in the range [0, 18] according to the
+// C++20 spec. If more than 18 fractional digits are required then returns 6 for
+// microseconds precision.
+template () / 10)>
+struct count_fractional_digits {
+ static constexpr int value =
+ Num % Den == 0 ? N : count_fractional_digits::value;
+};
+
+// Base case that doesn't instantiate any more templates
+// in order to avoid overflow.
+template
+struct count_fractional_digits {
+ static constexpr int value = (Num % Den == 0) ? N : 6;
+};
+
+// Format subseconds which are given as an integer type with an appropriate
+// number of digits.
+template