Skip to content

Commit 0bf61c5

Browse files
authored
Merge pull request #134 from xgi/feature/133-keybind-update
Feature/133 keybind update fixes #133
2 parents 66f1c7c + e10c3d1 commit 0bf61c5

14 files changed

+130
-33
lines changed

castero/__init__.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,9 +23,11 @@
2323
{key_help} - show this help screen
2424
{key_exit} - exit the client
2525
{key_add_feed} - add a feed
26-
{key_delete} - delete the selected feed
27-
{key_reload} - reload/refresh feeds
26+
{key_remove} - remove the selected feed
27+
{key_reload} - reload all feeds
28+
{key_reload_selected} - reload the selected feed
2829
{key_save} - save episode for offline playback
30+
{key_delete} - delete downloaded episodes
2931
{key_up/key_down} - navigate up/down in menus
3032
{key_right/key_left} - navigate right/left in menus
3133
{key_scroll_up/key_scroll_down} - scroll up/down in menus

castero/config.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,13 @@ def migrate(self, conf, default_conf) -> None:
130130
for key in default_conf[section]:
131131
default_conf_dict[key] = default_conf[section][key]
132132

133+
# In an update, we renamed key_delete to key_remove and added a
134+
# different key_delete value. If the user has key_delete but not
135+
# key_removed, we swap these values manually.
136+
if 'key_delete' in conf_dict and 'key_remove' not in conf_dict:
137+
conf_dict['key_remove'] = conf_dict['key_delete']
138+
conf_dict['key_delete'] = default_conf_dict['key_delete']
139+
133140
with open(self._default_path, "r") as default_conf_file:
134141
lines = default_conf_file.readlines()
135142
for line in lines:

castero/database.py

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -412,8 +412,8 @@ def queue(self) -> List[Episode]:
412412
# retrieved ep_ids to get complete list of desired episodes
413413
return [episodes_cache[ep_id] for ep_id in ep_ids]
414414

415-
def reload(self, display=None) -> None:
416-
"""Reload all feeds in the database.
415+
def reload(self, display=None, feeds=None) -> None:
416+
"""Reload feeds in the database.
417417
418418
To preserve user metadata for episodes (such as played/marked status),
419419
we use Episode.replace_from() which "manually" copies such fields to
@@ -436,10 +436,13 @@ def reload(self, display=None) -> None:
436436
437437
Args:
438438
display: (optional) the display to write status updates to
439+
feeds: (optional) a list of feeds to reload. If not specified,
440+
all feeds in the database will be reloaded
439441
"""
440-
feeds = self.feeds()
442+
if feeds is None:
443+
feeds = self.feeds()
441444
total_feeds = len(feeds)
442-
completed_feeds = 1
445+
completed_feeds = 0
443446

444447
reqs = []
445448
url_pairs = {}
@@ -456,24 +459,21 @@ def reload(self, display=None) -> None:
456459
else:
457460
file_feeds.append(feed)
458461

459-
if display is not None:
460-
display.change_status("Reloading feeds...")
461-
462462
# handle each response as downloads complete asynchronously
463463
for response in grequests.imap(reqs, size=3):
464+
if display is not None:
465+
display.change_status(
466+
"Reloading feeds (%d/%d)" % (completed_feeds, total_feeds))
464467
old_feed = url_pairs[response.request.url]
465468
new_feed = Feed(url=response.request.url, response=response)
466-
self._reload_feed(old_feed, new_feed)
469+
self._reload_feed_data(old_feed, new_feed)
467470

468471
completed_feeds += 1
469-
if display is not None:
470-
display.change_status(
471-
"Reloading feeds (%d/%d)" % (completed_feeds, total_feeds))
472472

473473
# handle each file-based feed
474474
for old_feed in file_feeds:
475475
new_feed = Feed(file=old_feed.key)
476-
self._reload_feed(old_feed, new_feed)
476+
self._reload_feed_data(old_feed, new_feed)
477477

478478
completed_feeds += 1
479479
if display is not None:
@@ -485,7 +485,7 @@ def reload(self, display=None) -> None:
485485
"Successfully reloaded %d feeds" % total_feeds)
486486
display.menus_valid = False
487487

488-
def _reload_feed(self, old_feed: Feed, new_feed: Feed):
488+
def _reload_feed_data(self, old_feed: Feed, new_feed: Feed):
489489
"""Helper method to update a feed and its episodes in the database.
490490
491491
Args:

castero/display.py

Lines changed: 59 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -551,36 +551,85 @@ def reload_feeds(self) -> None:
551551
t = threading.Thread(target=self.database.reload, args=[self])
552552
t.start()
553553

554+
def reload_selected_feed(self, feed: Feed) -> None:
555+
"""Reloads the selected feed.
556+
557+
This method starts the reloading in a new un-managed thread.
558+
"""
559+
t = threading.Thread(target=self.database.reload, args=[self, [feed]])
560+
t.start()
561+
554562
def save_episodes(self, feed=None, episode=None) -> None:
555563
"""Save a feed or episode.
556564
557-
If the user is saving an episode and the episode is already saved, this
558-
method will instead ask the user if they would like to delete the
559-
downloaded episode. However, if the user is saving a feed, there is no
560-
prompt to delete episodes, even if some are downloaded. In this case,
561-
downloaded episodes are simply skipped.
562-
563565
Exactly one of either feed or episode must be given.
564566
565567
Args:
566568
feed: (optional) a feed to download all episodes of
567-
episode: (optional) an episode to download or delete
569+
episode: (optional) an episode to download
568570
"""
569571
assert (feed is None or episode is None) and (feed is not episode)
570572

571573
if feed is not None:
574+
num_saved = 0
575+
num_to_save = 0
572576
for episode in self.database.episodes(feed):
573577
if not episode.downloaded:
574-
self._download_queue.add(episode)
578+
num_to_save += 1
579+
580+
if num_to_save == 0:
581+
return
582+
583+
should_delete = self._get_y_n(
584+
"Are you sure you want to download %d"
585+
" episodes from this feed? (y/n): " % num_to_save)
586+
if should_delete:
587+
for episode in self.database.episodes(feed):
588+
if not episode.downloaded:
589+
self._download_queue.add(episode)
590+
else:
591+
if not episode.downloaded:
592+
self._download_queue.add(episode)
593+
594+
def delete_episodes(self, feed=None, episode=None) -> None:
595+
"""Delete a downloaded episode, or all of those from a feed.
596+
597+
Exactly one of either feed or episode must be given.
598+
599+
Args:
600+
feed: (optional) a feed to delete all episodes of
601+
episode: (optional) an episode or delete
602+
"""
603+
assert (feed is None or episode is None) and (feed is not episode)
604+
605+
if feed is not None:
606+
num_deleted = 0
607+
num_to_delete = 0
608+
for episode in self.database.episodes(feed):
609+
if episode.downloaded:
610+
num_to_delete += 1
611+
612+
if num_to_delete == 0:
613+
return
614+
615+
should_delete = self._get_y_n(
616+
"Are you sure you want to delete %d downloaded"
617+
" episodes from this feed? (y/n): " % num_to_delete)
618+
if should_delete:
619+
for episode in self.database.episodes(feed):
620+
if episode.downloaded:
621+
episode.delete(self)
622+
num_deleted += 1
623+
self.menus_valid = False
624+
self.change_status(
625+
"Successfully deleted %d episodes" % num_deleted)
575626
else:
576627
if episode.downloaded:
577628
should_delete = self._get_y_n(
578629
"Are you sure you want to delete the downloaded"
579630
" episode? (y/n): ")
580631
if should_delete:
581632
episode.delete(self)
582-
else:
583-
self._download_queue.add(episode)
584633

585634
def filter_menu(self, menu: Menu) -> None:
586635
menu.filter_text = self._get_input_str("Filter: ")

castero/perspective.py

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -172,12 +172,16 @@ def _generic_handle_input(self, c) -> bool:
172172
queue.change_rate(-1, display=self._display)
173173
elif c == key_mapping[Config['key_add_feed']]:
174174
self._display.add_feed()
175-
elif c == key_mapping[Config['key_delete']]:
175+
elif c == key_mapping[Config['key_remove']]:
176176
if self._active_window == 0:
177177
self._display.delete_feed(self._feed_menu.item)
178178
self.update_menus()
179179
elif c == key_mapping[Config['key_reload']]:
180180
self._display.reload_feeds()
181+
elif c == key_mapping[Config['key_reload_selected']]:
182+
feed = self._feed_menu.item
183+
if feed is not None:
184+
self._display.reload_selected_feed(feed)
181185
elif c == key_mapping[Config['key_show_url']]:
182186
if self._active_window == 1 and self._episode_menu.item:
183187
self._display.show_episode_url(self._episode_menu.item)
@@ -186,6 +190,11 @@ def _generic_handle_input(self, c) -> bool:
186190
self._display.save_episodes(feed=self._feed_menu.item)
187191
elif self._active_window == 1 and self._episode_menu.item:
188192
self._display.save_episodes(episode=self._episode_menu.item)
193+
elif c == key_mapping[Config['key_delete']]:
194+
if self._active_window == 0 and self._feed_menu.item:
195+
self._display.delete_episodes(feed=self._feed_menu.item)
196+
elif self._active_window == 1 and self._episode_menu.item:
197+
self._display.delete_episodes(episode=self._episode_menu.item)
189198
elif c == key_mapping[Config['key_execute']]:
190199
if self._active_window == 1 and self._episode_menu.item:
191200
self._display.execute_command(self._episode_menu.item)

castero/perspectives/downloadedperspective.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,10 @@ def handle_input(self, c) -> bool:
138138
if self._downloaded_menu.item:
139139
self._display.save_episodes(episode=self._downloaded_menu.item)
140140
self._display.menus_valid = False
141+
elif c == key_mapping[Config['key_delete']]:
142+
if self._downloaded_menu.item:
143+
self._display.delete_episodes(episode=self._downloaded_menu.item)
144+
self._display.menus_valid = False
141145
elif c == key_mapping[Config['key_mark_played']]:
142146
if self._active_window == 0:
143147
episode = self._downloaded_menu.item
@@ -149,7 +153,9 @@ def handle_input(self, c) -> bool:
149153
episode = self._downloaded_menu.item
150154
if episode is not None:
151155
self._display.execute_command(episode)
152-
elif c == key_mapping[Config['key_delete']]:
156+
elif c == key_mapping[Config['key_reload_selected']]:
157+
pass
158+
elif c == key_mapping[Config['key_remove']]:
153159
pass
154160
else:
155161
keep_running = self._generic_handle_input(c)

castero/perspectives/queueperspective.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -132,7 +132,7 @@ def handle_input(self, c) -> bool:
132132
queue.stop()
133133
queue.clear()
134134
self._display.menus_valid = False
135-
elif c == key_mapping[Config['key_delete']]:
135+
elif c == key_mapping[Config['key_remove']]:
136136
self._remove_selected_from_queue()
137137
elif c == key_mapping[Config['key_show_url']]:
138138
item = self._queue_menu.item
@@ -142,8 +142,12 @@ def handle_input(self, c) -> bool:
142142
item = self._queue_menu.item
143143
if item is not None:
144144
self._display.execute_command(item.episode)
145+
elif c == key_mapping[Config['key_reload_selected']]:
146+
pass
145147
elif c == key_mapping[Config['key_save']]:
146148
pass
149+
elif c == key_mapping[Config['key_delete']]:
150+
pass
147151
elif c == key_mapping[Config['key_mark_played']]:
148152
pass
149153
elif c == key_mapping[Config['key_filter']]:

castero/templates/castero.conf

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -164,18 +164,26 @@ key_exit = q
164164
# default: a
165165
key_add_feed = a
166166

167-
# Delete the selected feed.
167+
# Remove the selected feed.
168168
# default: d
169-
key_delete = d
169+
key_remove = d
170170

171-
# Reload/refresh feeds.
171+
# Reload/refresh all feeds.
172172
# default: r
173173
key_reload = r
174174

175+
# Reload/refresh the selected feed.
176+
# default: R
177+
key_reload_selected = R
178+
175179
# Save episode for offline playback.
176180
# default: s
177181
key_save = s
178182

183+
# Delete downloaded episodes.
184+
# default: x
185+
key_delete = x
186+
179187
# Navigate up.
180188
# default: UP
181189
key_up = UP

tests/datafiles/working_no_comments.conf

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,10 +39,14 @@ key_exit = q
3939

4040
key_add_feed = a
4141

42-
key_delete = d
42+
key_remove = d
43+
44+
key_delete = x
4345

4446
key_reload = r
4547

48+
key_reload_selected = R
49+
4650
key_save = s
4751

4852
key_up = UP

tests/test_database.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -142,7 +142,7 @@ def test_database_reload(prevent_modification, display):
142142

143143
display.change_status = mock.MagicMock(name="change_status")
144144
mydatabase.reload(display)
145-
assert display.change_status.call_count == 3
145+
assert display.change_status.call_count == 2
146146
assert mydatabase.feeds()[0].title == real_title
147147

148148

0 commit comments

Comments
 (0)