tbr-deal-finder 0.3.1__tar.gz → 0.3.3__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {tbr_deal_finder-0.3.1 → tbr_deal_finder-0.3.3}/.github/workflows/build-release-dmg.yml +52 -20
- {tbr_deal_finder-0.3.1 → tbr_deal_finder-0.3.3}/.github/workflows/build-release-exe.yml +3 -3
- {tbr_deal_finder-0.3.1 → tbr_deal_finder-0.3.3}/CHANGELOG.md +23 -0
- {tbr_deal_finder-0.3.1 → tbr_deal_finder-0.3.3}/Makefile +1 -1
- {tbr_deal_finder-0.3.1 → tbr_deal_finder-0.3.3}/PKG-INFO +1 -1
- tbr_deal_finder-0.3.3/docs/desktop-app.md +133 -0
- {tbr_deal_finder-0.3.1 → tbr_deal_finder-0.3.3}/pyproject.toml +10 -1
- {tbr_deal_finder-0.3.1 → tbr_deal_finder-0.3.3}/scripts/packaging/create_dmg.sh +4 -1
- {tbr_deal_finder-0.3.1 → tbr_deal_finder-0.3.3}/tbr_deal_finder/__init__.py +1 -1
- tbr_deal_finder-0.3.3/tbr_deal_finder/desktop_updater.py +71 -0
- {tbr_deal_finder-0.3.1 → tbr_deal_finder-0.3.3}/tbr_deal_finder/gui/main.py +80 -27
- {tbr_deal_finder-0.3.1 → tbr_deal_finder-0.3.3}/tbr_deal_finder/gui/pages/all_books.py +7 -0
- {tbr_deal_finder-0.3.1 → tbr_deal_finder-0.3.3}/tbr_deal_finder/gui/pages/base_book_page.py +1 -2
- {tbr_deal_finder-0.3.1 → tbr_deal_finder-0.3.3}/tbr_deal_finder/gui/pages/book_details.py +2 -3
- {tbr_deal_finder-0.3.1 → tbr_deal_finder-0.3.3}/tbr_deal_finder/gui/pages/latest_deals.py +24 -20
- {tbr_deal_finder-0.3.1 → tbr_deal_finder-0.3.3}/tbr_deal_finder/gui/pages/settings.py +1 -0
- {tbr_deal_finder-0.3.1 → tbr_deal_finder-0.3.3}/tbr_deal_finder/retailer_deal.py +20 -29
- {tbr_deal_finder-0.3.1 → tbr_deal_finder-0.3.3}/tbr_deal_finder/utils.py +28 -19
- {tbr_deal_finder-0.3.1 → tbr_deal_finder-0.3.3}/uv.lock +1 -1
- tbr_deal_finder-0.3.1/docs/desktop-app.md +0 -251
- tbr_deal_finder-0.3.1/tbr_deal_finder/desktop_updater.py +0 -147
- {tbr_deal_finder-0.3.1 → tbr_deal_finder-0.3.3}/.github/workflows/publish-to-pypi.yaml +0 -0
- {tbr_deal_finder-0.3.1 → tbr_deal_finder-0.3.3}/.gitignore +0 -0
- {tbr_deal_finder-0.3.1 → tbr_deal_finder-0.3.3}/.python-version +0 -0
- {tbr_deal_finder-0.3.1 → tbr_deal_finder-0.3.3}/DESIGN.md +0 -0
- {tbr_deal_finder-0.3.1 → tbr_deal_finder-0.3.3}/LICENSE +0 -0
- {tbr_deal_finder-0.3.1 → tbr_deal_finder-0.3.3}/README.md +0 -0
- {tbr_deal_finder-0.3.1 → tbr_deal_finder-0.3.3}/assets/icon.ico +0 -0
- {tbr_deal_finder-0.3.1 → tbr_deal_finder-0.3.3}/assets/icon.png +0 -0
- {tbr_deal_finder-0.3.1 → tbr_deal_finder-0.3.3}/docs/development.md +0 -0
- {tbr_deal_finder-0.3.1 → tbr_deal_finder-0.3.3}/docs/python-cli.md +0 -0
- {tbr_deal_finder-0.3.1 → tbr_deal_finder-0.3.3}/tbr_deal_finder/__main__.py +0 -0
- {tbr_deal_finder-0.3.1 → tbr_deal_finder-0.3.3}/tbr_deal_finder/book.py +0 -0
- {tbr_deal_finder-0.3.1 → tbr_deal_finder-0.3.3}/tbr_deal_finder/cli.py +0 -0
- {tbr_deal_finder-0.3.1 → tbr_deal_finder-0.3.3}/tbr_deal_finder/config.py +0 -0
- {tbr_deal_finder-0.3.1 → tbr_deal_finder-0.3.3}/tbr_deal_finder/gui/__init__.py +0 -0
- {tbr_deal_finder-0.3.1 → tbr_deal_finder-0.3.3}/tbr_deal_finder/gui/pages/__init__.py +0 -0
- {tbr_deal_finder-0.3.1 → tbr_deal_finder-0.3.3}/tbr_deal_finder/gui/pages/all_deals.py +0 -0
- {tbr_deal_finder-0.3.1 → tbr_deal_finder-0.3.3}/tbr_deal_finder/migrations.py +0 -0
- {tbr_deal_finder-0.3.1 → tbr_deal_finder-0.3.3}/tbr_deal_finder/owned_books.py +0 -0
- {tbr_deal_finder-0.3.1 → tbr_deal_finder-0.3.3}/tbr_deal_finder/queries/get_active_deals.sql +0 -0
- {tbr_deal_finder-0.3.1 → tbr_deal_finder-0.3.3}/tbr_deal_finder/queries/get_deals_found_at.sql +0 -0
- {tbr_deal_finder-0.3.1 → tbr_deal_finder-0.3.3}/tbr_deal_finder/queries/latest_deal_last_ran_most_recent_success.sql +0 -0
- {tbr_deal_finder-0.3.1 → tbr_deal_finder-0.3.3}/tbr_deal_finder/queries/latest_unknown_book_sync.sql +0 -0
- {tbr_deal_finder-0.3.1 → tbr_deal_finder-0.3.3}/tbr_deal_finder/retailer/__init__.py +0 -0
- {tbr_deal_finder-0.3.1 → tbr_deal_finder-0.3.3}/tbr_deal_finder/retailer/amazon.py +0 -0
- {tbr_deal_finder-0.3.1 → tbr_deal_finder-0.3.3}/tbr_deal_finder/retailer/amazon_custom_auth.py +0 -0
- {tbr_deal_finder-0.3.1 → tbr_deal_finder-0.3.3}/tbr_deal_finder/retailer/audible.py +0 -0
- {tbr_deal_finder-0.3.1 → tbr_deal_finder-0.3.3}/tbr_deal_finder/retailer/chirp.py +0 -0
- {tbr_deal_finder-0.3.1 → tbr_deal_finder-0.3.3}/tbr_deal_finder/retailer/kindle.py +0 -0
- {tbr_deal_finder-0.3.1 → tbr_deal_finder-0.3.3}/tbr_deal_finder/retailer/librofm.py +0 -0
- {tbr_deal_finder-0.3.1 → tbr_deal_finder-0.3.3}/tbr_deal_finder/retailer/models.py +0 -0
- {tbr_deal_finder-0.3.1 → tbr_deal_finder-0.3.3}/tbr_deal_finder/tracked_books.py +0 -0
- {tbr_deal_finder-0.3.1 → tbr_deal_finder-0.3.3}/tbr_deal_finder/version_check.py +0 -0
- {tbr_deal_finder-0.3.1 → tbr_deal_finder-0.3.3}/tbr_deal_finder.py +0 -0
@@ -13,11 +13,12 @@ jobs:
|
|
13
13
|
env:
|
14
14
|
HAS_MACOS_CERTIFICATE: ${{ secrets.MACOS_CERTIFICATE != '' }}
|
15
15
|
HAS_MACOS_CERTIFICATE_PASSWORD: ${{ secrets.MACOS_CERTIFICATE_PASSWORD != '' }}
|
16
|
-
|
16
|
+
VERSION: ${{ github.event.release.tag_name }}
|
17
|
+
|
17
18
|
steps:
|
18
19
|
- name: Checkout code
|
19
20
|
uses: actions/checkout@v4
|
20
|
-
|
21
|
+
|
21
22
|
- name: Set up Python
|
22
23
|
uses: actions/setup-python@v5
|
23
24
|
with:
|
@@ -25,7 +26,7 @@ jobs:
|
|
25
26
|
|
26
27
|
- name: Install uv
|
27
28
|
uses: astral-sh/setup-uv@v5
|
28
|
-
|
29
|
+
|
29
30
|
- name: Install project dependencies
|
30
31
|
run: |
|
31
32
|
uv sync
|
@@ -34,7 +35,7 @@ jobs:
|
|
34
35
|
uses: maxim-lobanov/setup-xcode@v1
|
35
36
|
with:
|
36
37
|
xcode-version: latest-stable
|
37
|
-
|
38
|
+
|
38
39
|
- name: Set up CocoaPods
|
39
40
|
uses: maxim-lobanov/setup-cocoapods@v1
|
40
41
|
with:
|
@@ -43,44 +44,76 @@ jobs:
|
|
43
44
|
- name: Import Code Signing Certificate (if available)
|
44
45
|
if: ${{ env.HAS_MACOS_CERTIFICATE == 'true' && env.HAS_MACOS_CERTIFICATE_PASSWORD == 'true' }}
|
45
46
|
run: |
|
47
|
+
set -e # Exit on any error
|
48
|
+
|
46
49
|
# Create temporary keychain
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
+
KEYCHAIN_PASSWORD="temp_keychain_password"
|
51
|
+
KEYCHAIN_NAME="temp.keychain-db"
|
52
|
+
|
53
|
+
security create-keychain -p "$KEYCHAIN_PASSWORD" "$KEYCHAIN_NAME"
|
54
|
+
security default-keychain -s "$KEYCHAIN_NAME"
|
55
|
+
security unlock-keychain -p "$KEYCHAIN_PASSWORD" "$KEYCHAIN_NAME"
|
56
|
+
security set-keychain-settings -t 3600 -u "$KEYCHAIN_NAME"
|
50
57
|
|
51
|
-
#
|
58
|
+
# Decode PKCS12 certificate
|
52
59
|
echo "${{ secrets.MACOS_CERTIFICATE }}" | base64 --decode > certificate.p12
|
53
|
-
|
60
|
+
|
61
|
+
# Verify file exists and has content
|
62
|
+
if [ ! -s certificate.p12 ]; then
|
63
|
+
echo "❌ Certificate file is empty or missing"
|
64
|
+
exit 1
|
65
|
+
fi
|
66
|
+
|
67
|
+
# Import PKCS12 to keychain (specifying format explicitly is required)
|
68
|
+
echo "Importing certificate..."
|
69
|
+
security import certificate.p12 \
|
70
|
+
-k "$KEYCHAIN_NAME" \
|
71
|
+
-P "${{ secrets.MACOS_CERTIFICATE_PASSWORD }}" \
|
72
|
+
-f pkcs12 \
|
73
|
+
-T /usr/bin/codesign
|
54
74
|
|
55
75
|
# Allow codesign to access the certificate
|
56
|
-
security set-key-partition-list
|
76
|
+
security set-key-partition-list \
|
77
|
+
-S apple-tool:,apple:,codesign: \
|
78
|
+
-s \
|
79
|
+
-k "$KEYCHAIN_PASSWORD" \
|
80
|
+
"$KEYCHAIN_NAME" 2>/dev/null || true
|
57
81
|
|
58
82
|
# Set code signing identity
|
59
83
|
if [ -n "${{ secrets.CODESIGN_IDENTITY }}" ]; then
|
60
|
-
# Use provided identity name
|
61
84
|
echo "CODESIGN_IDENTITY=${{ secrets.CODESIGN_IDENTITY }}" >> $GITHUB_ENV
|
62
85
|
echo "✅ Code signing certificate imported: ${{ secrets.CODESIGN_IDENTITY }}"
|
63
86
|
else
|
64
87
|
# Auto-detect identity from certificate
|
65
|
-
CERT_IDENTITY=$(security find-identity -v -p codesigning
|
88
|
+
CERT_IDENTITY=$(security find-identity -v -p codesigning "$KEYCHAIN_NAME" | grep -v "0 valid identities" | head -1 | awk -F'"' '{print $2}')
|
89
|
+
if [ -z "$CERT_IDENTITY" ]; then
|
90
|
+
# Fallback to any identity if no codesigning specific one found
|
91
|
+
CERT_IDENTITY=$(security find-identity -v "$KEYCHAIN_NAME" | grep -v "0 valid identities" | head -1 | awk -F'"' '{print $2}')
|
92
|
+
fi
|
93
|
+
|
94
|
+
if [ -z "$CERT_IDENTITY" ]; then
|
95
|
+
echo "❌ No signing identity found"
|
96
|
+
exit 1
|
97
|
+
fi
|
98
|
+
|
66
99
|
echo "CODESIGN_IDENTITY=$CERT_IDENTITY" >> $GITHUB_ENV
|
67
100
|
echo "✅ Code signing certificate imported (auto-detected): $CERT_IDENTITY"
|
68
101
|
fi
|
69
102
|
|
70
|
-
# Clean up
|
103
|
+
# Clean up
|
71
104
|
rm -f certificate.p12
|
72
|
-
|
105
|
+
|
73
106
|
- name: Build DMG
|
74
107
|
run: |
|
75
108
|
make build-mac
|
76
|
-
|
109
|
+
|
77
110
|
- name: Cleanup Code Signing
|
78
111
|
if: ${{ env.HAS_MACOS_CERTIFICATE == 'true' && env.HAS_MACOS_CERTIFICATE_PASSWORD == 'true' }}
|
79
112
|
run: |
|
80
113
|
# Remove temporary keychain
|
81
114
|
security delete-keychain temp.keychain || true
|
82
115
|
echo "🧹 Cleaned up code signing keychain"
|
83
|
-
|
116
|
+
|
84
117
|
- name: Find and verify DMG was created
|
85
118
|
id: find-dmg
|
86
119
|
run: |
|
@@ -95,7 +128,7 @@ jobs:
|
|
95
128
|
echo "✅ DMG file created successfully: $DMG_FILE"
|
96
129
|
echo "dmg_path=$DMG_FILE" >> $GITHUB_OUTPUT
|
97
130
|
echo "dmg_name=$(basename $DMG_FILE)" >> $GITHUB_OUTPUT
|
98
|
-
|
131
|
+
|
99
132
|
- name: Get DMG info
|
100
133
|
id: dmg-info
|
101
134
|
run: |
|
@@ -103,7 +136,7 @@ jobs:
|
|
103
136
|
echo "size=$DMG_SIZE" >> $GITHUB_OUTPUT
|
104
137
|
echo "📦 DMG Size: $DMG_SIZE"
|
105
138
|
echo "📦 DMG Name: ${{ steps.find-dmg.outputs.dmg_name }}"
|
106
|
-
|
139
|
+
|
107
140
|
- name: Upload DMG to Release
|
108
141
|
env:
|
109
142
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
@@ -111,5 +144,4 @@ jobs:
|
|
111
144
|
gh release upload ${{ github.event.release.tag_name }} \
|
112
145
|
"${{ steps.find-dmg.outputs.dmg_path }}" \
|
113
146
|
--clobber \
|
114
|
-
--repo ${{ github.repository }}
|
115
|
-
|
147
|
+
--repo ${{ github.repository }}
|
@@ -138,7 +138,7 @@ jobs:
|
|
138
138
|
DefaultGroupName=${{ env.APP_DISPLAY_NAME }}
|
139
139
|
AllowNoIcons=yes
|
140
140
|
OutputDir=.
|
141
|
-
OutputBaseFilename=${{ env.APP_FILENAME_BASE }}-${{ steps.app-info.outputs.app_version }}-Setup
|
141
|
+
OutputBaseFilename=${{ env.APP_FILENAME_BASE }}-${{ steps.app-info.outputs.app_version }}-Windows-Setup
|
142
142
|
SetupIconFile=assets\icon.ico
|
143
143
|
Compression=lzma2/normal
|
144
144
|
SolidCompression=no
|
@@ -184,13 +184,13 @@ jobs:
|
|
184
184
|
with:
|
185
185
|
certificate: '${{ secrets.WINDOWS_CERTIFICATE }}'
|
186
186
|
password: '${{ secrets.WINDOWS_CERTIFICATE_PASSWORD }}'
|
187
|
-
files: '${{ env.APP_FILENAME_BASE }}-${{ steps.app-info.outputs.app_version }}-Setup.exe'
|
187
|
+
files: '${{ env.APP_FILENAME_BASE }}-${{ steps.app-info.outputs.app_version }}-Windows-Setup.exe'
|
188
188
|
|
189
189
|
- name: Upload Installer to Release
|
190
190
|
env:
|
191
191
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
192
192
|
run: |
|
193
|
-
$installerFile = "${{ env.APP_FILENAME_BASE }}-${{ steps.app-info.outputs.app_version }}-Setup.exe"
|
193
|
+
$installerFile = "${{ env.APP_FILENAME_BASE }}-${{ steps.app-info.outputs.app_version }}-Windows-Setup.exe"
|
194
194
|
|
195
195
|
gh release upload ${{ github.event.release.tag_name }} `
|
196
196
|
$installerFile `
|
@@ -3,6 +3,29 @@
|
|
3
3
|
|
4
4
|
---
|
5
5
|
|
6
|
+
## 0.3.3 (September 10, 2025)
|
7
|
+
|
8
|
+
Notes:
|
9
|
+
* Improved updater for Mac app
|
10
|
+
|
11
|
+
BUG FIXES:
|
12
|
+
* Fixed Mac app cert
|
13
|
+
* Fixed issue where pricing graph points were running off graph
|
14
|
+
|
15
|
+
---
|
16
|
+
|
17
|
+
## 0.3.2 (September 8, 2025)
|
18
|
+
|
19
|
+
Notes:
|
20
|
+
* Disable nav bar buttons when performing certain operations
|
21
|
+
* Added config check to CLI location when running the desktop app for backwards compatibility
|
22
|
+
* Improved performance when retrieving "latest deals"
|
23
|
+
|
24
|
+
BUG FIXES:
|
25
|
+
* Fixed issue with scroll bar in the "all deals" page
|
26
|
+
|
27
|
+
---
|
28
|
+
|
6
29
|
## 0.3.1 (September 5, 2025)
|
7
30
|
|
8
31
|
Notes:
|
@@ -31,7 +31,7 @@ BUILD_SCRIPT := scripts/packaging/build_cross_platform.py
|
|
31
31
|
# Build self-signed macOS DMG (recommended)
|
32
32
|
build-mac:
|
33
33
|
@echo "🍎 Building app"
|
34
|
-
uv run flet build macos --output ${DIST_DIR}/app/
|
34
|
+
NONINTERACTIVE=1 NO_COLOR=1 TERM=dumb CI=true uv run flet build macos --output ${DIST_DIR}/app/
|
35
35
|
@echo ""
|
36
36
|
@echo "📦 Creating self-signed macOS DMG for app"
|
37
37
|
bash scripts/packaging/create_dmg.sh
|
@@ -0,0 +1,133 @@
|
|
1
|
+
# Desktop App Guide
|
2
|
+
|
3
|
+
Everything you need to know about installing, using, and updating the TBR Deal Finder desktop application.
|
4
|
+
|
5
|
+
## Installation
|
6
|
+
|
7
|
+
The desktop app provides a beautiful graphical interface for managing your book deals without any command line knowledge required.
|
8
|
+
|
9
|
+
### 🍎 macOS Installation
|
10
|
+
|
11
|
+
#### Download & Install
|
12
|
+
1. Go to the [latest release](https://github.com/yourusername/tbr-deal-finder/releases/latest)
|
13
|
+
2. Download `TBRDealFinder-{version}-macOS.dmg`
|
14
|
+
3. **Open the DMG**: Double-click the downloaded `.dmg` file
|
15
|
+
4. **Handle Security Warning**: macOS will show "Cannot verify developer"
|
16
|
+
- Click Done to handle the warning before continuing with the install
|
17
|
+
- [Follow guide from Apple](https://support.apple.com/guide/mac-help/open-a-mac-app-from-an-unknown-developer-mh40616/mac)
|
18
|
+
5. **Install**: Drag the TBR Deal Finder app to your Applications folder
|
19
|
+
6. **Launch**: Double-click the app in Applications
|
20
|
+
7. **Wait for loader**: The app has a special loader and can take a minute to load for the first time
|
21
|
+
|
22
|
+
#### Troubleshooting macOS
|
23
|
+
- **"App can't be opened"**: Use right-click → Open instead of double-clicking
|
24
|
+
- **Still getting warnings**: Go to System Preferences → Security & Privacy → General → Click "Open Anyway"
|
25
|
+
|
26
|
+
### 🪟 Windows Installation
|
27
|
+
|
28
|
+
#### Download & Install
|
29
|
+
1. Go to the [latest release](https://github.com/yourusername/tbr-deal-finder/releases/latest)
|
30
|
+
2. Download `TBRDealFinder-{version}-Windows.exe`
|
31
|
+
3. **Run the Installer**: Double-click the downloaded `.exe` file
|
32
|
+
4. **Handle Security Warning**: Windows will show "Unknown publisher"
|
33
|
+
- **Solution**: Click "More info" → Click "Run anyway"
|
34
|
+
5. **Install**: Follow the installation wizard
|
35
|
+
6. **Launch**: The app will be available in your Start Menu or Desktop
|
36
|
+
7. **Wait for loader**: The app has a special loader and can take a minute to load for the first time
|
37
|
+
|
38
|
+
#### Troubleshooting Windows
|
39
|
+
- **Windows Defender blocks**: Click "More info" → "Run anyway"
|
40
|
+
- **Still blocked**: Temporarily disable real-time protection, install, then re-enable
|
41
|
+
|
42
|
+
## 🎯 First Time Setup
|
43
|
+
|
44
|
+
### Getting Your Reading Lists
|
45
|
+
Before using the app, export your reading lists:
|
46
|
+
|
47
|
+
#### StoryGraph Export
|
48
|
+
1. Open [StoryGraph](https://app.thestorygraph.com/)
|
49
|
+
2. Click your profile icon → "Manage Account"
|
50
|
+
3. Scroll to "Manage Your Data" → "Export StoryGraph Library"
|
51
|
+
4. Click "Generate export" → Wait and refresh → Download CSV
|
52
|
+
|
53
|
+
#### Goodreads Export
|
54
|
+
1. Visit [Goodreads Export](https://www.goodreads.com/review/import)
|
55
|
+
2. Click "Export Library" → Wait for email → Download CSV
|
56
|
+
|
57
|
+
#### Custom CSV
|
58
|
+
Create your own with these columns:
|
59
|
+
- `Title` (required)
|
60
|
+
- `Authors` (required)
|
61
|
+
- `Read Status` (optional: set to "to-read" for tracking)
|
62
|
+
|
63
|
+
### Setup Wizard
|
64
|
+
1. **Launch the App** for the first time
|
65
|
+
2. **Follow the Setup Wizard**:
|
66
|
+
- Upload your CSV file(s)
|
67
|
+
- Select your country/region
|
68
|
+
- Set maximum price for deals
|
69
|
+
- Set minimum discount percentage
|
70
|
+
3. **Start Finding Deals**: The app begins searching automatically
|
71
|
+
|
72
|
+
## 🔄 Updating the Desktop App
|
73
|
+
|
74
|
+
### Checking for Updates
|
75
|
+
Currently, updates require manual download:
|
76
|
+
1. **Check Current Version**: Look in Settings/About section
|
77
|
+
2. **Visit Releases**: Go to [latest releases](https://github.com/yourusername/tbr-deal-finder/releases/latest)
|
78
|
+
3. **Compare Versions**: See if a newer version is available
|
79
|
+
|
80
|
+
### Installing Updates
|
81
|
+
|
82
|
+
#### All Platforms
|
83
|
+
1. **Download Latest Version**:
|
84
|
+
- macOS: `TBRDealFinder-{version}-macOS.dmg`
|
85
|
+
- Windows: `TBRDealFinder-{version}-Windows.exe`
|
86
|
+
2. **Install Over Existing**: Follow same installation steps
|
87
|
+
- If prompted, choose to replace files or overwrite the existing application
|
88
|
+
3. **Preserve Settings**: Your configuration and data are automatically preserved
|
89
|
+
4. **Verify Update**: Check version in Settings after installation
|
90
|
+
|
91
|
+
## ❓ Troubleshooting
|
92
|
+
|
93
|
+
### App Won't Launch
|
94
|
+
- **macOS**: Right-click app → Open, check Security & Privacy settings
|
95
|
+
- **Windows**: Run as administrator, check Windows Defender
|
96
|
+
|
97
|
+
### No Deals Found
|
98
|
+
- **Check CSV Format**: Ensure titles and authors are correct
|
99
|
+
- **Adjust Filters**: Lower discount threshold or raise price limit
|
100
|
+
- **Wait**: Deals fluctuate - check back regularly
|
101
|
+
|
102
|
+
### Performance Issues
|
103
|
+
#### Initial Run
|
104
|
+
When the app initially starts the app may be white up to a minute while the tbr deal finder package loads.
|
105
|
+
|
106
|
+
#### Getting latest deals
|
107
|
+
Getting pricing info for Kindle can take up to a second per book.
|
108
|
+
It doesn't sound like a lot, but if you have 400 books in your tbr it may take around 7 minutes.
|
109
|
+
If you're not checking for deals on Kindle, getting deals should only take a couple minutes.
|
110
|
+
Don't close the app while getting deals or progress may be lost.
|
111
|
+
|
112
|
+
#### Still having issues
|
113
|
+
- **Restart App**: Close and reopen to clear memory
|
114
|
+
- **Update**: Ensure you're running the latest version
|
115
|
+
- **System**: Close other applications to free resources
|
116
|
+
|
117
|
+
## 🆘 Getting Help
|
118
|
+
|
119
|
+
### Community Support
|
120
|
+
1. **GitHub Issues**: [Report bugs or ask questions](https://github.com/yourusername/tbr-deal-finder/issues)
|
121
|
+
2. **Search First**: Someone might have had the same issue
|
122
|
+
3. **Provide Details**: Include OS version, error messages, screenshots
|
123
|
+
|
124
|
+
### What to Include in Bug Reports
|
125
|
+
- **Operating System**: macOS 12.1, Windows 11, Ubuntu 22.04, etc.
|
126
|
+
- **App Version**: Found in Settings/About
|
127
|
+
- **Error Messages**: Exact text of any errors
|
128
|
+
- **Screenshots**: Visual problems are easier to diagnose
|
129
|
+
- **Steps to Reproduce**: What you did when the problem occurred
|
130
|
+
|
131
|
+
---
|
132
|
+
|
133
|
+
**Ready to discover amazing book deals? Download the desktop app and start saving money on your reading list!** 📚💰
|
@@ -1,6 +1,6 @@
|
|
1
1
|
[project]
|
2
2
|
name = "tbr-deal-finder"
|
3
|
-
version = "0.3.
|
3
|
+
version = "0.3.3"
|
4
4
|
description = "Track price drops and find deals on books in your TBR list across audiobook and ebook formats."
|
5
5
|
readme = "README.md"
|
6
6
|
requires-python = ">=3.13"
|
@@ -30,6 +30,15 @@ product = "TBR Deal Finder"
|
|
30
30
|
app.module = "tbr_deal_finder"
|
31
31
|
app.path = "."
|
32
32
|
|
33
|
+
# Dependency locks
|
34
|
+
# Fix for https://github.com/flet-dev/flet/issues/5630
|
35
|
+
android.min_sdk_version = 24
|
36
|
+
|
37
|
+
[tool.flet.flutter.pubspec.dependency_overrides]
|
38
|
+
# Dependency locks
|
39
|
+
# Fix for https://github.com/flet-dev/flet/issues/5630
|
40
|
+
webview_flutter_android = "4.10.1"
|
41
|
+
|
33
42
|
[tool.setuptools.package-data]
|
34
43
|
"tbr-deal-finder" = [
|
35
44
|
"queries/**/*.sql",
|
@@ -13,11 +13,14 @@
|
|
13
13
|
|
14
14
|
DIST_DIR="gui_dist"
|
15
15
|
APP_NAME="TBR Deal Finder"
|
16
|
-
DMG_NAME="TBR-Deal-Finder-
|
16
|
+
DMG_NAME="TBR-Deal-Finder-${VERSION#v}-mac"
|
17
17
|
VOLUME_NAME="TBR Deal Finder"
|
18
18
|
SOURCE_APP="${DIST_DIR}/app/${APP_NAME}.app"
|
19
19
|
OUTPUT_DMG="${DIST_DIR}/${DMG_NAME}.dmg"
|
20
20
|
|
21
|
+
echo "📋 Using version: ${VERSION}"
|
22
|
+
echo "📦 DMG name will be: ${DMG_NAME}.dmg"
|
23
|
+
|
21
24
|
# Determine signing approach
|
22
25
|
if [ -z "$CODESIGN_IDENTITY" ]; then
|
23
26
|
# Default to ad-hoc signing for local development
|
@@ -0,0 +1,71 @@
|
|
1
|
+
"""
|
2
|
+
Desktop application update checker and handler.
|
3
|
+
For packaged desktop applications (.dmg/.exe).
|
4
|
+
"""
|
5
|
+
import json
|
6
|
+
import logging
|
7
|
+
import os
|
8
|
+
import platform
|
9
|
+
import subprocess
|
10
|
+
import tempfile
|
11
|
+
import webbrowser
|
12
|
+
from pathlib import Path
|
13
|
+
from typing import Optional, Dict, Any
|
14
|
+
|
15
|
+
import requests
|
16
|
+
from packaging import version
|
17
|
+
|
18
|
+
from tbr_deal_finder import __VERSION__
|
19
|
+
|
20
|
+
logger = logging.getLogger(__name__)
|
21
|
+
|
22
|
+
class DesktopUpdater:
|
23
|
+
"""Handle updates for packaged desktop applications."""
|
24
|
+
|
25
|
+
def __init__(self, github_repo: str = "WillNye/tbr-deal-finder"):
|
26
|
+
self.github_repo = github_repo
|
27
|
+
self.current_version = __VERSION__
|
28
|
+
self.platform = platform.system().lower()
|
29
|
+
|
30
|
+
def check_for_updates(self) -> Optional[Dict[str, Any]]:
|
31
|
+
"""
|
32
|
+
Check GitHub releases for newer versions.
|
33
|
+
Returns dict with update info or None if no update available.
|
34
|
+
"""
|
35
|
+
try:
|
36
|
+
# Check GitHub releases API
|
37
|
+
response = requests.get(
|
38
|
+
f"https://api.github.com/repos/{self.github_repo}/releases/latest",
|
39
|
+
timeout=5
|
40
|
+
)
|
41
|
+
response.raise_for_status()
|
42
|
+
|
43
|
+
release_data = response.json()
|
44
|
+
latest_version = release_data["tag_name"].lstrip("v")
|
45
|
+
if version.parse(latest_version) > version.parse(self.current_version):
|
46
|
+
download_url = release_data["html_url"]
|
47
|
+
if self.platform == "darwin":
|
48
|
+
for asset in release_data["assets"]:
|
49
|
+
if asset["browser_download_url"].endswith(".dmg"):
|
50
|
+
download_url = asset["browser_download_url"]
|
51
|
+
|
52
|
+
return {
|
53
|
+
"version": latest_version,
|
54
|
+
"download_url": download_url,
|
55
|
+
"release_notes": release_data.get("body", ""),
|
56
|
+
}
|
57
|
+
|
58
|
+
return None
|
59
|
+
|
60
|
+
except Exception as e:
|
61
|
+
logger.error(f"Failed to check updates for {self.github_repo}: {e}")
|
62
|
+
return None
|
63
|
+
|
64
|
+
|
65
|
+
# Global instance
|
66
|
+
desktop_updater = DesktopUpdater()
|
67
|
+
|
68
|
+
|
69
|
+
def check_for_desktop_updates() -> Optional[Dict[str, Any]]:
|
70
|
+
"""Convenience function to check for updates."""
|
71
|
+
return desktop_updater.check_for_updates()
|
@@ -1,6 +1,10 @@
|
|
1
1
|
import asyncio
|
2
2
|
import os
|
3
3
|
import base64
|
4
|
+
import subprocess
|
5
|
+
import sys
|
6
|
+
from datetime import datetime
|
7
|
+
from pathlib import Path
|
4
8
|
|
5
9
|
import flet as ft
|
6
10
|
|
@@ -17,6 +21,7 @@ from tbr_deal_finder.gui.pages.all_deals import AllDealsPage
|
|
17
21
|
from tbr_deal_finder.gui.pages.latest_deals import LatestDealsPage
|
18
22
|
from tbr_deal_finder.gui.pages.all_books import AllBooksPage
|
19
23
|
from tbr_deal_finder.gui.pages.book_details import BookDetailsPage
|
24
|
+
from tbr_deal_finder.utils import get_duckdb_conn, get_latest_deal_last_ran
|
20
25
|
|
21
26
|
|
22
27
|
class TBRDealFinderApp:
|
@@ -26,7 +31,9 @@ class TBRDealFinderApp:
|
|
26
31
|
self.current_page = "all_deals"
|
27
32
|
self.selected_book = None
|
28
33
|
self.update_info = None # Store update information
|
29
|
-
|
34
|
+
self.nav_disabled = False # Track navigation disabled state
|
35
|
+
self._last_run_time = None
|
36
|
+
|
30
37
|
# Initialize pages
|
31
38
|
self.settings_page = SettingsPage(self)
|
32
39
|
self.all_deals_page = AllDealsPage(self)
|
@@ -258,6 +265,16 @@ class TBRDealFinderApp:
|
|
258
265
|
|
259
266
|
def nav_changed(self, e):
|
260
267
|
"""Handle navigation rail selection changes"""
|
268
|
+
# Prevent navigation if disabled
|
269
|
+
if self.nav_disabled:
|
270
|
+
# Reset to current page selection to prevent visual change
|
271
|
+
current_indices = {"all_deals": 0, "latest_deals": 1, "all_books": 2}
|
272
|
+
self.nav_rail.selected_index = current_indices.get(self.current_page, 0)
|
273
|
+
# Reapply disabled state after page update
|
274
|
+
self.nav_rail.disabled = True
|
275
|
+
self.page.update()
|
276
|
+
return
|
277
|
+
|
261
278
|
destinations = ["all_deals", "latest_deals", "all_books"]
|
262
279
|
if e.control.selected_index < len(destinations):
|
263
280
|
self.current_page = destinations[e.control.selected_index]
|
@@ -284,10 +301,26 @@ class TBRDealFinderApp:
|
|
284
301
|
self.all_books_page.refresh_page_state()
|
285
302
|
|
286
303
|
def refresh_all_pages(self):
|
287
|
-
"""Refresh all pages by clearing their state and reloading data"""
|
304
|
+
"""Refresh all pages except all_books_page by clearing their state and reloading data"""
|
288
305
|
self.all_deals_page.refresh_page_state()
|
289
306
|
self.latest_deals_page.refresh_page_state()
|
290
|
-
|
307
|
+
|
308
|
+
def disable_navigation(self):
|
309
|
+
"""Disable navigation rail during background operations"""
|
310
|
+
self.nav_disabled = True
|
311
|
+
if hasattr(self, 'nav_rail'):
|
312
|
+
self.nav_rail.disabled = True
|
313
|
+
self.page.update()
|
314
|
+
|
315
|
+
def enable_navigation(self):
|
316
|
+
"""Enable navigation rail after background operations complete"""
|
317
|
+
if not self.nav_disabled:
|
318
|
+
return
|
319
|
+
|
320
|
+
self.nav_disabled = False
|
321
|
+
if hasattr(self, 'nav_rail'):
|
322
|
+
self.nav_rail.disabled = False
|
323
|
+
self.page.update()
|
291
324
|
|
292
325
|
def get_current_page_content(self):
|
293
326
|
"""Get content for the current page"""
|
@@ -309,6 +342,7 @@ class TBRDealFinderApp:
|
|
309
342
|
|
310
343
|
def get_config_prompt(self):
|
311
344
|
"""Show config setup prompt when no config exists"""
|
345
|
+
self.disable_navigation()
|
312
346
|
return ft.Container(
|
313
347
|
content=ft.Column([
|
314
348
|
ft.Icon(ft.Icons.SETTINGS, size=64, color=ft.Colors.GREY_400),
|
@@ -592,10 +626,6 @@ class TBRDealFinderApp:
|
|
592
626
|
def close_dialog(e):
|
593
627
|
dialog.open = False
|
594
628
|
self.page.update()
|
595
|
-
|
596
|
-
def view_release_and_close(e):
|
597
|
-
self.view_release_notes(e)
|
598
|
-
close_dialog(e)
|
599
629
|
|
600
630
|
def download_and_close(e):
|
601
631
|
self.download_update(e)
|
@@ -609,10 +639,13 @@ class TBRDealFinderApp:
|
|
609
639
|
], spacing=10),
|
610
640
|
content=ft.Column([
|
611
641
|
ft.Text(f"Version {self.update_info['version']} is now available!"),
|
612
|
-
ft.
|
613
|
-
|
642
|
+
ft.Divider(),
|
643
|
+
ft.Text(
|
644
|
+
self.update_info.get('release_notes', 'No release notes available.'),
|
645
|
+
selectable=True
|
646
|
+
),
|
647
|
+
], scroll=ft.ScrollMode.AUTO, spacing=10, tight=True),
|
614
648
|
actions=[
|
615
|
-
ft.TextButton("View Release Notes", on_click=view_release_and_close),
|
616
649
|
ft.ElevatedButton("Download Update", on_click=download_and_close),
|
617
650
|
ft.TextButton("Later", on_click=close_dialog),
|
618
651
|
],
|
@@ -646,24 +679,36 @@ class TBRDealFinderApp:
|
|
646
679
|
dialog.open = True
|
647
680
|
self.page.update()
|
648
681
|
|
649
|
-
def view_release_notes(self, e):
|
650
|
-
"""Open release notes in browser."""
|
651
|
-
if self.update_info:
|
652
|
-
import webbrowser
|
653
|
-
webbrowser.open(self.update_info['release_url'])
|
654
|
-
|
655
682
|
def download_update(self, e):
|
656
683
|
"""Handle update download."""
|
657
684
|
if not self.update_info or not self.update_info.get('download_url'):
|
658
685
|
return
|
659
|
-
|
660
|
-
|
661
|
-
|
662
|
-
|
663
|
-
|
664
|
-
|
665
|
-
|
666
|
-
|
686
|
+
|
687
|
+
if sys.platform == "darwin":
|
688
|
+
dmg_path = Path(
|
689
|
+
f"~/Downloads/TBR-Deal-Finder-{self.update_info['version']}-mac.dmg"
|
690
|
+
).expanduser()
|
691
|
+
|
692
|
+
# Show download instructions
|
693
|
+
self.show_download_instructions()
|
694
|
+
|
695
|
+
if not dmg_path.exists():
|
696
|
+
# Using curl or urllib to download to prevent Mac warning
|
697
|
+
subprocess.run([
|
698
|
+
"curl", "-L",
|
699
|
+
self.update_info['download_url'],
|
700
|
+
"-o", dmg_path
|
701
|
+
])
|
702
|
+
|
703
|
+
subprocess.run(["open", dmg_path])
|
704
|
+
else:
|
705
|
+
# For now, open download URL in browser
|
706
|
+
# In a more advanced implementation, you could download in-app
|
707
|
+
import webbrowser
|
708
|
+
webbrowser.open(self.update_info['download_url'])
|
709
|
+
|
710
|
+
# Show download instructions
|
711
|
+
self.show_download_instructions()
|
667
712
|
|
668
713
|
def show_download_instructions(self):
|
669
714
|
"""Show instructions for installing the downloaded update."""
|
@@ -672,7 +717,7 @@ class TBRDealFinderApp:
|
|
672
717
|
self.page.update()
|
673
718
|
|
674
719
|
instructions = {
|
675
|
-
"darwin": "1.
|
720
|
+
"darwin": "1. Wait for the update to download (can take a minute or 2)\n2. Close TBR Deal Finder\n3. Once the installer opens, drag the app to Applications folder\n3. When prompted, select Replace\n4. Restart the application",
|
676
721
|
"windows": "1. Download will start in your browser\n2. Run the downloaded .exe installer\n3. Follow the installation wizard\n4. Restart the application",
|
677
722
|
}
|
678
723
|
|
@@ -703,12 +748,20 @@ class TBRDealFinderApp:
|
|
703
748
|
dialog.open = True
|
704
749
|
self.page.update()
|
705
750
|
|
706
|
-
|
707
|
-
|
708
751
|
def check_for_updates_button(self):
|
709
752
|
"""Check for updates when button is clicked."""
|
710
753
|
self.check_for_updates_manual()
|
711
754
|
|
755
|
+
def update_last_run_time(self):
|
756
|
+
db_conn = get_duckdb_conn()
|
757
|
+
self._last_run_time = get_latest_deal_last_ran(db_conn)
|
758
|
+
|
759
|
+
def get_last_run_time(self) -> datetime:
|
760
|
+
if not self._last_run_time:
|
761
|
+
self.update_last_run_time()
|
762
|
+
|
763
|
+
return self._last_run_time
|
764
|
+
|
712
765
|
|
713
766
|
def main():
|
714
767
|
"""Main entry point for the GUI application"""
|