tbr-deal-finder 0.3.1__py3-none-any.whl → 0.3.2__py3-none-any.whl

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.
@@ -1,5 +1,5 @@
1
1
  from pathlib import Path
2
2
 
3
- __VERSION__ = "0.3.1"
3
+ __VERSION__ = "0.3.2"
4
4
 
5
5
  QUERY_PATH = Path(__file__).parent.joinpath("queries")
@@ -26,6 +26,7 @@ class TBRDealFinderApp:
26
26
  self.current_page = "all_deals"
27
27
  self.selected_book = None
28
28
  self.update_info = None # Store update information
29
+ self.nav_disabled = False # Track navigation disabled state
29
30
 
30
31
  # Initialize pages
31
32
  self.settings_page = SettingsPage(self)
@@ -258,6 +259,16 @@ class TBRDealFinderApp:
258
259
 
259
260
  def nav_changed(self, e):
260
261
  """Handle navigation rail selection changes"""
262
+ # Prevent navigation if disabled
263
+ if self.nav_disabled:
264
+ # Reset to current page selection to prevent visual change
265
+ current_indices = {"all_deals": 0, "latest_deals": 1, "all_books": 2}
266
+ self.nav_rail.selected_index = current_indices.get(self.current_page, 0)
267
+ # Reapply disabled state after page update
268
+ self.nav_rail.disabled = True
269
+ self.page.update()
270
+ return
271
+
261
272
  destinations = ["all_deals", "latest_deals", "all_books"]
262
273
  if e.control.selected_index < len(destinations):
263
274
  self.current_page = destinations[e.control.selected_index]
@@ -289,6 +300,23 @@ class TBRDealFinderApp:
289
300
  self.latest_deals_page.refresh_page_state()
290
301
  self.all_books_page.refresh_page_state()
291
302
 
303
+ def disable_navigation(self):
304
+ """Disable navigation rail during background operations"""
305
+ self.nav_disabled = True
306
+ if hasattr(self, 'nav_rail'):
307
+ self.nav_rail.disabled = True
308
+ self.page.update()
309
+
310
+ def enable_navigation(self):
311
+ """Enable navigation rail after background operations complete"""
312
+ if not self.nav_disabled:
313
+ return
314
+
315
+ self.nav_disabled = False
316
+ if hasattr(self, 'nav_rail'):
317
+ self.nav_rail.disabled = False
318
+ self.page.update()
319
+
292
320
  def get_current_page_content(self):
293
321
  """Get content for the current page"""
294
322
  if self.config is None and self.current_page != "settings":
@@ -309,6 +337,7 @@ class TBRDealFinderApp:
309
337
 
310
338
  def get_config_prompt(self):
311
339
  """Show config setup prompt when no config exists"""
340
+ self.disable_navigation()
312
341
  return ft.Container(
313
342
  content=ft.Column([
314
343
  ft.Icon(ft.Icons.SETTINGS, size=64, color=ft.Colors.GREY_400),
@@ -39,6 +39,9 @@ class AllBooksPage(BaseBookPage):
39
39
 
40
40
  async def _async_load_items(self):
41
41
  """Load TBR books asynchronously using Flet's async support"""
42
+ # Disable navigation during the loading operation
43
+ self.app.disable_navigation()
44
+
42
45
  try:
43
46
  # Run the async operation directly
44
47
  await self.app.auth_all_configured_retailers()
@@ -50,6 +53,10 @@ class AllBooksPage(BaseBookPage):
50
53
  self.filtered_items = []
51
54
  finally:
52
55
  self.set_loading(False)
56
+
57
+ # Re-enable navigation after the operation completes
58
+ self.app.enable_navigation()
59
+
53
60
  # Update the page to reflect the loaded data
54
61
  self.app.page.update()
55
62
 
@@ -142,8 +142,7 @@ class BaseBookPage(ABC):
142
142
  item_tiles.append(tile)
143
143
 
144
144
  return ft.Container(
145
- content=ft.ListView(item_tiles, spacing=5),
146
- height=700,
145
+ content=ft.Column(item_tiles, spacing=5),
147
146
  border=ft.border.all(1, ft.Colors.OUTLINE),
148
147
  border_radius=8,
149
148
  padding=10
@@ -274,6 +274,9 @@ class LatestDealsPage(BaseBookPage):
274
274
  self.progress_container.visible = True
275
275
  self.run_button.disabled = True
276
276
 
277
+ # Disable navigation during the operation
278
+ self.app.disable_navigation()
279
+
277
280
  # Update the page to show loading state
278
281
  self.app.page.update()
279
282
 
@@ -299,6 +302,9 @@ class LatestDealsPage(BaseBookPage):
299
302
  self.run_button.disabled = False
300
303
  self.check_last_run() # Refresh the status
301
304
 
305
+ # Re-enable navigation after the operation
306
+ self.app.enable_navigation()
307
+
302
308
  # Update the page to reset loading state
303
309
  self.app.page.update()
304
310
 
@@ -309,6 +309,7 @@ class SettingsPage:
309
309
  clear_unknown_books()
310
310
 
311
311
  self.show_success("Configuration saved successfully!")
312
+ self.app.enable_navigation()
312
313
  self.app.config_updated(self.config)
313
314
 
314
315
  except Exception as ex:
@@ -2,16 +2,14 @@ import asyncio
2
2
  import copy
3
3
  from collections import defaultdict
4
4
 
5
- import click
6
5
  import pandas as pd
7
- from tqdm.asyncio import tqdm_asyncio
8
6
 
9
7
  from tbr_deal_finder.book import Book, get_active_deals, BookFormat
10
8
  from tbr_deal_finder.config import Config
11
9
  from tbr_deal_finder.tracked_books import get_tbr_books, get_unknown_books, set_unknown_books
12
10
  from tbr_deal_finder.retailer import RETAILER_MAP
13
11
  from tbr_deal_finder.retailer.models import Retailer
14
- from tbr_deal_finder.utils import get_duckdb_conn, echo_info, echo_err, is_gui_env
12
+ from tbr_deal_finder.utils import get_duckdb_conn, echo_info, echo_err
15
13
 
16
14
 
17
15
  def update_retailer_deal_table(config: Config, new_deals: list[Book]):
@@ -67,6 +65,13 @@ async def _get_books(
67
65
  Returns:
68
66
  List of Book objects with updated pricing and availability
69
67
  """
68
+
69
+ echo_info(f"Getting deals from {retailer.name}")
70
+ books = _get_retailer_relevant_tbr_books(
71
+ retailer,
72
+ books,
73
+ )
74
+
70
75
  semaphore = asyncio.Semaphore(retailer.max_concurrency)
71
76
  response = []
72
77
  unknown_books = []
@@ -81,11 +86,7 @@ async def _get_books(
81
86
  if book.deal_id not in ignored_deal_ids
82
87
  ]
83
88
 
84
- if is_gui_env():
85
- results = await asyncio.gather(*tasks)
86
- else:
87
- results = await tqdm_asyncio.gather(*tasks, desc=f"Getting latest prices from {retailer.name}")
88
-
89
+ results = await asyncio.gather(*tasks)
89
90
  for book in results:
90
91
  if not book:
91
92
  """Cases where we know the retailer has the book but it's not coming back.
@@ -99,9 +100,7 @@ async def _get_books(
99
100
  elif not book.exists:
100
101
  unknown_books.append(book)
101
102
 
102
- click.echo()
103
- for book in unknown_books:
104
- echo_info(f"{book.title} by {book.authors} not found")
103
+ echo_info(f"Finished getting deals from {retailer.name}")
105
104
 
106
105
  return response, unknown_books
107
106
 
@@ -184,34 +183,26 @@ async def _get_latest_deals(config: Config):
184
183
  ignore_books: list[Book] = get_unknown_books(config)
185
184
  ignored_deal_ids: set[str] = {book.deal_id for book in ignore_books}
186
185
 
186
+ tasks = []
187
187
  for retailer_str in config.tracked_retailers:
188
188
  retailer = RETAILER_MAP[retailer_str]()
189
189
  await retailer.set_auth()
190
190
 
191
- relevant_tbr_books = _get_retailer_relevant_tbr_books(
192
- retailer,
193
- tbr_books,
191
+ tasks.append(
192
+ _get_books(
193
+ config,
194
+ retailer,
195
+ tbr_books,
196
+ ignored_deal_ids
197
+ )
194
198
  )
195
199
 
196
- echo_info(f"Getting deals from {retailer.name}")
197
- click.echo("\n---------------")
198
- retailer_books, u_books = await _get_books(
199
- config,
200
- retailer,
201
- relevant_tbr_books,
202
- ignored_deal_ids
203
- )
200
+ results = await asyncio.gather(*tasks)
201
+ for retailer_books, u_books in results:
204
202
  books.extend(retailer_books)
205
203
  unknown_books.extend(u_books)
206
- click.echo("---------------\n")
207
204
 
208
205
  _apply_proper_list_prices(books)
209
-
210
- books = [
211
- book
212
- for book in books
213
- ]
214
-
215
206
  update_retailer_deal_table(config, books)
216
207
  set_unknown_books(config, unknown_books)
217
208
 
tbr_deal_finder/utils.py CHANGED
@@ -27,25 +27,34 @@ def get_data_dir() -> Path:
27
27
  app_name = "TBR Deal Finder"
28
28
 
29
29
  if custom_path := os.getenv("TBR_DEAL_FINDER_CUSTOM_PATH"):
30
- path = Path(custom_path)
31
-
32
- elif not is_gui_env():
33
- path = Path.home() / ".tbr_deal_finder"
34
-
35
- elif sys.platform == "win32":
36
- # Windows: C:\Users\Username\AppData\Local\AppAuthor\AppName
37
- base = os.environ.get("LOCALAPPDATA", os.path.expanduser("~\\AppData\\Local"))
38
- path = Path(base) / app_author / app_name
39
-
40
- elif sys.platform == "darwin":
41
- # macOS: ~/Library/Application Support/AppName
42
- path = Path.home() / "Library" / "Application Support" / app_name
43
-
44
- else: # Linux and others
45
- # Linux: ~/.local/share/appname (following XDG spec)
46
- xdg_data_home = os.environ.get("XDG_DATA_HOME",
47
- os.path.expanduser("~/.local/share"))
48
- path = Path(xdg_data_home) / app_name.lower()
30
+ path = Path(custom_path).expanduser()
31
+ else:
32
+ cli_path = Path.home() / ".tbr_deal_finder"
33
+ if sys.platform == "win32":
34
+ # Windows: C:\Users\Username\AppData\Local\AppAuthor\AppName
35
+ base = os.environ.get("LOCALAPPDATA", os.path.expanduser("~\\AppData\\Local"))
36
+ gui_path = Path(base) / app_author / app_name
37
+
38
+ elif sys.platform == "darwin":
39
+ # macOS: ~/Library/Application Support/AppName
40
+ gui_path = Path.home() / "Library" / "Application Support" / app_name
41
+
42
+ else: # Linux and others
43
+ # Linux: ~/.local/share/appname (following XDG spec)
44
+ xdg_data_home = os.environ.get("XDG_DATA_HOME",
45
+ os.path.expanduser("~/.local/share"))
46
+ gui_path = Path(xdg_data_home) / app_name.lower()
47
+
48
+ if is_gui_env():
49
+ path = gui_path
50
+ if cli_path.exists() and not path.exists():
51
+ # Use the cli path if it exists and the gui path does not
52
+ path = cli_path
53
+ else:
54
+ path = cli_path
55
+ if gui_path.exists() and not path.exists():
56
+ # Use the gui path if it exists and the cli path does not
57
+ path = gui_path
49
58
 
50
59
  # Create directory if it doesn't exist
51
60
  path.mkdir(parents=True, exist_ok=True)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: tbr-deal-finder
3
- Version: 0.3.1
3
+ Version: 0.3.2
4
4
  Summary: Track price drops and find deals on books in your TBR list across audiobook and ebook formats.
5
5
  License: MIT
6
6
  License-File: LICENSE
@@ -1,4 +1,4 @@
1
- tbr_deal_finder/__init__.py,sha256=iYLaqaUWltN1HqI41ELPAw8KoYbwf9AbF4iXz4we5xw,104
1
+ tbr_deal_finder/__init__.py,sha256=3Zo2T2Th8AT8qexLKWZhNJIeyXPVf2N76kkDGJw5Wc0,104
2
2
  tbr_deal_finder/__main__.py,sha256=b2-3WiGIno_XVusUDijQXa2MthKahFz6CVH-hIBe7es,165
3
3
  tbr_deal_finder/book.py,sha256=WbvDEeI923iQX_eIsC9H7y-qKVPkfCqwjBCqChssFCM,6740
4
4
  tbr_deal_finder/cli.py,sha256=16vZnWS9TTWPhIjZsjsf7OY7Btb0ULOfws5EkUefbRY,7089
@@ -6,19 +6,19 @@ tbr_deal_finder/config.py,sha256=pocHMEoJVIoiuRzEtKDAmsGmiiLgD-lN6n6TU9gI8Lc,398
6
6
  tbr_deal_finder/desktop_updater.py,sha256=VCD4MW1ckx6yyq0N_CJrOth8wePqPhTjhvdJTKsUVkw,5123
7
7
  tbr_deal_finder/migrations.py,sha256=fO7r2JbWb6YG0CsPqauakwvbKaEFPxqX1PP8c8N03Wc,4951
8
8
  tbr_deal_finder/owned_books.py,sha256=Cf1VeiSg7XBi_TXptJfy5sO1mEgMMQWbJ_P6SzAx0nQ,516
9
- tbr_deal_finder/retailer_deal.py,sha256=S5E7XHYx-L-31jvuIJ_vaaTCeTSxCjosLFkdA-fBc1o,7525
9
+ tbr_deal_finder/retailer_deal.py,sha256=l2n-79kSNZfPh73PZBbSE6tkNYYCaHPEehIPcelDPeY,7214
10
10
  tbr_deal_finder/tracked_books.py,sha256=u3KfBNlwvsEwTfM5TAJVLbiTmm1lTe2k70JJOszQz1k,12714
11
- tbr_deal_finder/utils.py,sha256=mWqn-AUniLG0If3Hm2fwvRf2tycA-47oHtd3HKFfzbU,3159
11
+ tbr_deal_finder/utils.py,sha256=LJYRNPRO3XBFIlodGzxCxlxiQ9viNYGU5QjNnjz4qMA,3635
12
12
  tbr_deal_finder/version_check.py,sha256=tzuKWngnjSdjPAOdyuPk2ym6Lv3LyeObJAfgsBIH9YQ,1217
13
13
  tbr_deal_finder/gui/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
14
- tbr_deal_finder/gui/main.py,sha256=I2YNyZkIln6bEl1E2aHqHd3eOJurgMBW-gZMN1MQAOU,26413
14
+ tbr_deal_finder/gui/main.py,sha256=VvdCdSH3mbVhw_3PaDD78U8RQV-drTyXyCNSWFwmoe8,27539
15
15
  tbr_deal_finder/gui/pages/__init__.py,sha256=1xc9Ib3oX_hqxLdhZvbCv3AhwOmVBSklZZC6CAcvoMU,20
16
- tbr_deal_finder/gui/pages/all_books.py,sha256=FG9mHofSmNUiYY9EeOBZeWUAAAxhvkTTWLoK1tJJrSc,3418
16
+ tbr_deal_finder/gui/pages/all_books.py,sha256=uQguQ9_NjM5kq_D5DbOWFB9om1lOdPk1iTcjH0yl9bg,3655
17
17
  tbr_deal_finder/gui/pages/all_deals.py,sha256=rY08w4XXFc_Jbd39BPJ6fO00vstuuu2YHvZterHx5ZI,2389
18
- tbr_deal_finder/gui/pages/base_book_page.py,sha256=Car1OSX571xBz_WHCMYZ4f5-Nd0FLB5MC_xC41g5aFU,10491
18
+ tbr_deal_finder/gui/pages/base_book_page.py,sha256=kmHjJE4eIGrMhXtBWePn_eXfO6qJ36kIoQ7hE1xB3ic,10465
19
19
  tbr_deal_finder/gui/pages/book_details.py,sha256=POknUa9yNjfqhC6eXuw7RtaRcFtQj_CdvJ8mK2r6DDo,22047
20
- tbr_deal_finder/gui/pages/latest_deals.py,sha256=IuUg2mJEMqoDz4EoMtcfJFCvvSMrem7raJdkjDP5xvE,13813
21
- tbr_deal_finder/gui/pages/settings.py,sha256=5kr6FXdp6s7OalOEfRgzZOhjrzILiw4CgT3nIOhaHYk,14316
20
+ tbr_deal_finder/gui/pages/latest_deals.py,sha256=E-DoINTb7hAdiope7y9kqCdWuViBq3zDmMmFEL-vzso,14019
21
+ tbr_deal_finder/gui/pages/settings.py,sha256=gdQXi508yd_oZ5lj0mxuDL5qSxmH256LwPi5gTo2Cqg,14357
22
22
  tbr_deal_finder/queries/get_active_deals.sql,sha256=nh0F1lRV6YVrUV7gsQpjsgfXmN9R0peBeMHRifjgpUM,212
23
23
  tbr_deal_finder/queries/get_deals_found_at.sql,sha256=KqrtQk7FS4Hf74RyL1r-oD2D-RJz1urrxKxkwlvjAro,139
24
24
  tbr_deal_finder/queries/latest_deal_last_ran_most_recent_success.sql,sha256=W4cNMAHtcW2DzQyPL8SHHFcbVZQKVK2VfTzazxC3LJU,107
@@ -31,8 +31,8 @@ tbr_deal_finder/retailer/chirp.py,sha256=GR8yeXp-1DHMktepy5TecHslrUibpLM7LfueIuT
31
31
  tbr_deal_finder/retailer/kindle.py,sha256=kA4SO2kl2SvJHADkBYyYMgq_bditStbiTiW7piQPAFI,5282
32
32
  tbr_deal_finder/retailer/librofm.py,sha256=dn1kaJEQ9g_AOAFNKhUtFLxYxQZa6RAHgXhT_dx-O3k,7514
33
33
  tbr_deal_finder/retailer/models.py,sha256=56xTwcLcw3bFcvTDOb85TktqtksvvyY95hZBbp9-5mY,3340
34
- tbr_deal_finder-0.3.1.dist-info/METADATA,sha256=4RkxKZB-76ZNwWBIkhZhmPmcU3_otzi1_BQUccI_9gE,3448
35
- tbr_deal_finder-0.3.1.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
36
- tbr_deal_finder-0.3.1.dist-info/entry_points.txt,sha256=xjeRw7aX_jbX1ERC--IgYIa2oLNgeRefsMbKeTAVb70,112
37
- tbr_deal_finder-0.3.1.dist-info/licenses/LICENSE,sha256=rNc0wNPn4d4HHu6ZheJzeUaz_FbJ4rj2Dr2FjAivkNg,1064
38
- tbr_deal_finder-0.3.1.dist-info/RECORD,,
34
+ tbr_deal_finder-0.3.2.dist-info/METADATA,sha256=W3GS1LYeE-KXn0aE9S8d4v7GtRGTAobTw4ZeUmXYtqw,3448
35
+ tbr_deal_finder-0.3.2.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
36
+ tbr_deal_finder-0.3.2.dist-info/entry_points.txt,sha256=xjeRw7aX_jbX1ERC--IgYIa2oLNgeRefsMbKeTAVb70,112
37
+ tbr_deal_finder-0.3.2.dist-info/licenses/LICENSE,sha256=rNc0wNPn4d4HHu6ZheJzeUaz_FbJ4rj2Dr2FjAivkNg,1064
38
+ tbr_deal_finder-0.3.2.dist-info/RECORD,,