Merge tag 'merge4' into dev-adaptive

This commit is contained in:
Teemu Ikonen 2022-02-04 12:07:33 +02:00
commit 75c3d49b5e
6 changed files with 263 additions and 224 deletions

30
CONTRIBUTING.md Normal file
View File

@ -0,0 +1,30 @@
# Contributing to this repository <!-- omit in toc -->
## Getting started <!-- omit in toc -->
Before you begin:
- Ensure you are using Python 3.5+
- Check out the [existing issues](https://github.com/gpodder/gpodder/issues)
Contributions are made to this repo via Issues and Pull Requests (PRs). Make sure to search for existing Issues and PRs before creating your own.
## Getting the code and setting up the project
1. Fork this project
2. Clone the repository to your machine
3. Create a separate branch to get started, e.g. for feature `feat/branch-name-here` or fix `fix/fix-name-goes-here`
4. Make sure to create a new virtual environment and activate it:
```shell
python3 -m venv venv
source activate venv/bin/activate
```
5. Install dependencies: `python3 tools/localdepends.py`
6. Start the program with debug mode: `./bin/gpodder -v`
7. Make the changes, commit in a branch and push the branch to your fork and then submit a Pull Request.
## Linting
To ensure code quality, we recommend you to run the linter before pushing the changes to your repo. In order to do so ensure the necessary packages are installed by executing:
```shell
pip3 install pytest-cov minimock pycodestyle isort requests pytest pytest-httpserver
```
Execute the linter in the root directory (Linux only): `make lint unittest`. On Windows execute: `pycodestyle share src/gpodder tools bin/* *.py`

View File

@ -460,6 +460,7 @@ class DownloadQueueManager(object):
def queue_task(self, task):
"""Marks a task as queued
"""
self.tasks.queue_task(task)
self.__spawn_threads()
@ -469,7 +470,7 @@ class DownloadTask(object):
You can create a new download task like this:
task = DownloadTask(episode, gpodder.config.Config(CONFIGFILE))
task.status = DownloadTask.DOWNLOADING
task.status = DownloadTask.QUEUED
task.run()
While the download is in progress, you can access its properties:
@ -486,7 +487,8 @@ class DownloadTask(object):
You can cancel a running download task by setting its status:
task.status = DownloadTask.CANCELLED
with task:
task.status = DownloadTask.CANCELLING
The task will then abort as soon as possible (due to the nature
of downloading data, this can take a while when the Internet is
@ -537,9 +539,9 @@ class DownloadTask(object):
The same thing works for failed downloads ("notify_as_failed()").
"""
# Possible states this download task can be in
STATUS_MESSAGE = (_('Queued'), _('Downloading'),
_('Finished'), _('Failed'), _('Cancelled'), _('Paused'))
(QUEUED, DOWNLOADING, DONE, FAILED, CANCELLED, PAUSED) = list(range(6))
STATUS_MESSAGE = (_('Queued'), _('Queued'), _('Downloading'),
_('Finished'), _('Failed'), _('Cancelling'), _('Cancelled'), _('Pausing'), _('Paused'))
(NEW, QUEUED, DOWNLOADING, DONE, FAILED, CANCELLING, CANCELLED, PAUSING, PAUSED) = list(range(9))
# Wheter this task represents a file download or a device sync operation
ACTIVITY_DOWNLOAD, ACTIVITY_SYNCHRONIZE = list(range(2))
@ -550,6 +552,12 @@ class DownloadTask(object):
def __str__(self):
return self.__episode.title
def __enter__(self):
return self.__lock.acquire()
def __exit__(self, type, value, traceback):
self.__lock.release()
def __get_status(self):
return self.__status
@ -602,8 +610,9 @@ class DownloadTask(object):
downloader = property(fget=__get_downloader, fset=__set_downloader)
def cancel(self):
if self.status in (self.DOWNLOADING, self.QUEUED):
self.status = self.CANCELLED
with self:
if self.status in (self.DOWNLOADING, self.QUEUED):
self.status = self.CANCELLING
def removed_from_list(self):
if self.status != self.DONE:
@ -611,7 +620,8 @@ class DownloadTask(object):
def __init__(self, episode, config, downloader=None):
assert episode.download_task is None
self.__status = DownloadTask.QUEUED
self.__lock = threading.RLock()
self.__status = DownloadTask.NEW
self.__activity = DownloadTask.ACTIVITY_DOWNLOAD
self.__status_changed = True
self.__episode = episode
@ -701,10 +711,10 @@ class DownloadTask(object):
self.calculate_speed(count, blockSize)
if self.status == DownloadTask.CANCELLED:
if self.status == DownloadTask.CANCELLING:
raise DownloadCancelledException()
if self.status == DownloadTask.PAUSED:
if self.status == DownloadTask.PAUSING:
raise DownloadCancelledException()
def calculate_speed(self, count, blockSize):
@ -755,29 +765,36 @@ class DownloadTask(object):
self.__start_time = 0
self.__start_blocks = 0
# If the download has already been cancelled, skip it
if self.status == DownloadTask.CANCELLED:
util.delete_file(self.tempname)
self.progress = 0.0
self.speed = 0.0
self.recycle()
return False
# If the download has already been cancelled/paused, skip it
with self:
if self.status == DownloadTask.CANCELLING:
self.status = DownloadTask.CANCELLED
util.delete_file(self.tempname)
self.progress = 0.0
self.speed = 0.0
self.recycle()
return False
# We only start this download if its status is "downloading"
if self.status != DownloadTask.DOWNLOADING:
return False
if self.status == DownloadTask.PAUSING:
self.status = DownloadTask.PAUSED
return False
# We are downloading this file right now
self.status = DownloadTask.DOWNLOADING
self._notification_shown = False
# We only start this download if its status is queued
if self.status != DownloadTask.QUEUED:
return False
# Restore a reference to this task in the episode
# when running a recycled task following a pause or failed
# see #649
if not self.episode.download_task:
self.episode.download_task = self
# We are downloading this file right now
self.status = DownloadTask.DOWNLOADING
self._notification_shown = False
# Restore a reference to this task in the episode
# when running a recycled task following a pause or failed
# see #649
if not self.episode.download_task:
self.episode.download_task = self
url = self.__episode.url
result = DownloadTask.DOWNLOADING
try:
if url == '':
raise DownloadNoURLException()
@ -854,19 +871,20 @@ class DownloadTask(object):
self.__episode.on_downloaded(self.filename)
except DownloadCancelledException:
logger.info('Download has been cancelled/paused: %s', self)
if self.status == DownloadTask.CANCELLED:
if self.status == DownloadTask.CANCELLING:
util.delete_file(self.tempname)
self.progress = 0.0
self.speed = 0.0
result = DownloadTask.CANCELLED
except DownloadNoURLException:
self.status = DownloadTask.FAILED
result = DownloadTask.FAILED
self.error_message = _('Episode has no URL to download')
except urllib.error.ContentTooShortError as ctse:
self.status = DownloadTask.FAILED
result = DownloadTask.FAILED
self.error_message = _('Missing content from server')
except ConnectionError as ce:
# special case request exception
self.status = DownloadTask.FAILED
result = DownloadTask.FAILED
logger.error('Download failed: %s', str(ce), exc_info=True)
d = {'host': ce.args[0].pool.host, 'port': ce.args[0].pool.port}
self.error_message = _("Couldn't connect to server %(host)s:%(port)s" % d)
@ -876,45 +894,54 @@ class DownloadTask(object):
re = re.args[0]
logger.error('%s while downloading "%s"', str(re),
self.__episode.title, exc_info=True)
self.status = DownloadTask.FAILED
result = DownloadTask.FAILED
d = {'error': str(re)}
self.error_message = _('Request Error: %(error)s') % d
except IOError as ioe:
logger.error('%s while downloading "%s": %s', ioe.strerror,
self.__episode.title, ioe.filename, exc_info=True)
self.status = DownloadTask.FAILED
result = DownloadTask.FAILED
d = {'error': ioe.strerror, 'filename': ioe.filename}
self.error_message = _('I/O Error: %(error)s: %(filename)s') % d
except gPodderDownloadHTTPError as gdhe:
logger.error('HTTP %s while downloading "%s": %s',
gdhe.error_code, self.__episode.title, gdhe.error_message,
exc_info=True)
self.status = DownloadTask.FAILED
result = DownloadTask.FAILED
d = {'code': gdhe.error_code, 'message': gdhe.error_message}
self.error_message = _('HTTP Error %(code)s: %(message)s') % d
except Exception as e:
self.status = DownloadTask.FAILED
result = DownloadTask.FAILED
logger.error('Download failed: %s', str(e), exc_info=True)
self.error_message = _('Error: %s') % (str(e),)
if self.status == DownloadTask.FAILED:
self.__episode._download_error = self.error_message
with self:
if result == DownloadTask.FAILED:
self.status = DownloadTask.FAILED
self.__episode._download_error = self.error_message
# Delete empty partial files, they prevent streaming after a download failure (live stream)
if util.calculate_size(self.filename) == 0:
util.delete_file(self.tempname)
# Delete empty partial files, they prevent streaming after a download failure (live stream)
if util.calculate_size(self.filename) == 0:
util.delete_file(self.tempname)
if self.status == DownloadTask.DOWNLOADING:
# Everything went well - we're done
self.status = DownloadTask.DONE
if self.total_size <= 0:
self.total_size = util.calculate_size(self.filename)
logger.info('Total size updated to %d', self.total_size)
self.progress = 1.0
gpodder.user_extensions.on_episode_downloaded(self.__episode)
return True
if result == DownloadTask.DOWNLOADING:
# Everything went well - we're done (even if the task was cancelled/paused,
# since it's finished we might as well mark it done)
self.status = DownloadTask.DONE
if self.total_size <= 0:
self.total_size = util.calculate_size(self.filename)
logger.info('Total size updated to %d', self.total_size)
self.progress = 1.0
gpodder.user_extensions.on_episode_downloaded(self.__episode)
return True
self.speed = 0.0
self.speed = 0.0
# cancelled -- update state to mark it as safe to manipulate this task again
if self.status == DownloadTask.PAUSING:
self.status = DownloadTask.PAUSED
elif self.status == DownloadTask.CANCELLING:
self.status = DownloadTask.CANCELLED
# We finished, but not successfully (at least not really)
return False

View File

@ -46,7 +46,8 @@ class TaskQueue:
def add_task(self, task):
with self.lock:
self.tasks.append(task)
if task not in self.tasks:
self.tasks.append(task)
def remove_task(self, task):
with self.lock:
@ -162,20 +163,27 @@ class DownloadStatusModel:
self.request_update(iter, task)
def register_task(self, task):
self.work_queue.add_task(task)
# self.work_queue.add_task(task)
util.idle_add(self.__add_new_task, task)
def queue_task(self, task):
with task:
if task.status in (task.NEW, task.FAILED, task.CANCELLED, task.PAUSED):
task.status = task.QUEUED
self.work_queue.add_task(task)
def tell_all_tasks_to_quit(self):
for row in self.list:
task = row[DownloadStatusModel.C_TASK]
if task is not None:
# Pause currently-running (and queued) downloads
if task.status in (task.QUEUED, task.DOWNLOADING):
task.status = task.PAUSED
with task:
# Pause currently-running (and queued) downloads
if task.status in (task.QUEUED, task.DOWNLOADING):
task.status = task.PAUSING
# Delete cancelled and failed downloads
if task.status in (task.CANCELLED, task.FAILED):
task.removed_from_list()
# Delete cancelled and failed downloads
if task.status in (task.CANCELLED, task.FAILED):
task.removed_from_list()
def are_downloads_in_progress(self):
"""
@ -210,17 +218,11 @@ class DownloadStatusModel:
return len(self.work_queue)
def get_next(self):
task = self.work_queue.pop()
if task:
task.status = task.DOWNLOADING
return task
return self.work_queue.pop()
def set_downloading(self, task):
if not self.work_queue.remove_task(task):
# Task was already dequeued get_next
return False
task.status = task.DOWNLOADING
return True
# return False if Task was already dequeued by get_next
return self.work_queue.remove_task(task)
class DownloadTaskMonitor(object):

View File

@ -1462,7 +1462,9 @@ class gPodder(BuilderWidget, dbus.service.Object):
download_tasks_seen.add(task)
if (status == download.DownloadTask.DOWNLOADING and
if (status in [download.DownloadTask.DOWNLOADING,
download.DownloadTask.CANCELLING,
download.DownloadTask.PAUSING] and
activity == download.DownloadTask.ACTIVITY_DOWNLOAD):
downloading += 1
total_speed += speed
@ -1840,46 +1842,58 @@ class gPodder(BuilderWidget, dbus.service.Object):
episode_urls = set()
model = self.treeDownloads.get_model()
for row_reference, task in tasks:
if status == download.DownloadTask.QUEUED:
# Only queue task when its paused/failed/cancelled (or forced)
if task.status in (task.PAUSED, task.FAILED, task.CANCELLED) or force_start:
if force_start:
self.download_queue_manager.force_start_task(task)
else:
self.download_queue_manager.queue_task(task)
self.set_download_list_state(gPodderSyncUI.DL_ADDED_ONE)
elif status == download.DownloadTask.CANCELLED:
# Cancelling a download allowed when downloading/queued
if task.status in (task.QUEUED, task.DOWNLOADING):
with task:
if status == download.DownloadTask.QUEUED:
# Only queue task when its paused/failed/cancelled (or forced)
if task.status in (download.DownloadTask.PAUSED,
download.DownloadTask.FAILED,
download.DownloadTask.CANCELLED) or force_start:
if force_start:
self.download_queue_manager.force_start_task(task)
else:
self.download_queue_manager.queue_task(task)
self.set_download_list_state(gPodderSyncUI.DL_ONEOFF)
elif status == download.DownloadTask.CANCELLING:
logger.info(("cancelling task %s" % task.status))
# Cancelling a download allowed when downloading/queued
if task.status in (download.DownloadTask.QUEUED, download.DownloadTask.DOWNLOADING):
task.status = status
# Cancelling paused/failed downloads requires a call to .run()
elif task.status in (download.DownloadTask.PAUSED, download.DownloadTask.FAILED):
task.status = status
# Call run, so the partial file gets deleted
task.run()
task.recycle()
elif status == download.DownloadTask.PAUSING:
# Pausing a download only when queued/downloading
if task.status in (download.DownloadTask.QUEUED, download.DownloadTask.DOWNLOADING):
task.status = status
elif status is None:
# Remove the selected task - cancel downloading/queued tasks
if task.status in (download.DownloadTask.QUEUED, download.DownloadTask.DOWNLOADING):
task.status = download.DownloadTask.CANCELLED
path = row_reference.get_path()
# path isn't set if the item has already been removed from the list
# (to trigger this cancel one downloads in the active list, cancel all
# other downloads, quickly right click on the cancelled on one to get
# the context menu, wait until the active list is cleared, and then
# then choose remove from list)
if path:
model.remove(model.get_iter(path))
# Remember the URL, so we can tell the UI to update
try:
# We don't "see" this task anymore - remove it;
# this is needed, so update_episode_list_icons()
# below gets the correct list of "seen" tasks
self.download_tasks_seen.remove(task)
except KeyError as key_error:
pass
episode_urls.add(task.url)
# Tell the task that it has been removed (so it can clean up)
task.removed_from_list()
else:
# We can (hopefully) simply set the task status here
task.status = status
# Cancelling paused/failed downloads requires a call to .run()
elif task.status in (task.PAUSED, task.FAILED):
task.status = status
# Call run, so the partial file gets deleted
task.run()
elif status == download.DownloadTask.PAUSED:
# Pausing a download only when queued/downloading
if task.status in (task.DOWNLOADING, task.QUEUED):
task.status = status
elif status is None:
# Remove the selected task - cancel downloading/queued tasks
if task.status in (task.QUEUED, task.DOWNLOADING):
task.status = task.CANCELLED
model.remove(model.get_iter(row_reference.get_path()))
# Remember the URL, so we can tell the UI to update
try:
# We don't "see" this task anymore - remove it;
# this is needed, so update_episode_list_icons()
# below gets the correct list of "seen" tasks
self.download_tasks_seen.remove(task)
except KeyError as key_error:
pass
episode_urls.add(task.url)
# Tell the task that it has been removed (so it can clean up)
task.removed_from_list()
else:
# We can (hopefully) simply set the task status here
task.status = status
# Tell the podcasts tab to update icons for our removed podcasts
self.update_episode_list_icons(episode_urls)
# Update the tab title and downloads list
@ -1894,71 +1908,6 @@ class gPodder(BuilderWidget, dbus.service.Object):
selected_tasks, can_queue, can_cancel, can_pause, can_remove, can_force = \
self.downloads_list_get_selection(model, paths)
# def make_menu_item(label, icon_name, tasks=None, status=None, sensitive=True, force_start=False, action=None):
# # This creates a menu item for selection-wide actions
# item = Gtk.ImageMenuItem.new_with_mnemonic(label)
# if icon_name is not None:
# item.set_image(Gtk.Image.new_from_icon_name(icon_name, Gtk.IconSize.MENU))
# if action is not None:
# item.connect('activate', action)
# else:
# item.connect('activate', lambda item: self._for_each_task_set_status(tasks, status, force_start))
# item.set_sensitive(sensitive)
# return item
#
# def move_selected_items_up(menu_item):
# selection = self.treeDownloads.get_selection()
# model, selected_paths = selection.get_selected_rows()
# for path in selected_paths:
# index_above = path[0] - 1
# if index_above < 0:
# return
# self.download_status_model.move_before(
# model.get_iter(path),
# model.get_iter((index_above,)))
#
# def move_selected_items_down(menu_item):
# selection = self.treeDownloads.get_selection()
# model, selected_paths = selection.get_selected_rows()
# for path in reversed(selected_paths):
# index_below = path[0] + 1
# if index_below >= len(model):
# return
# self.download_status_model.move_after(
# model.get_iter(path),
# model.get_iter((index_below,)))
#
# menu = Gtk.Menu()
#
# if can_force:
# menu.append(make_menu_item(_('Start download now'), 'document-save',
# selected_tasks,
# download.DownloadTask.QUEUED,
# force_start=True))
# else:
# menu.append(make_menu_item(_('Download'), 'document-save',
# selected_tasks,
# download.DownloadTask.QUEUED,
# can_queue))
#
# menu.append(make_menu_item(_('Cancel'), 'media-playback-stop',
# selected_tasks,
# download.DownloadTask.CANCELLED,
# can_cancel))
# menu.append(make_menu_item(_('Pause'), 'media-playback-pause',
# selected_tasks,
# download.DownloadTask.PAUSED, can_pause))
# menu.append(Gtk.SeparatorMenuItem())
# menu.append(make_menu_item(_('Move up'), 'go-up',
# action=move_selected_items_up))
# menu.append(make_menu_item(_('Move down'), 'go-down',
# action=move_selected_items_down))
# menu.append(Gtk.SeparatorMenuItem())
# menu.append(make_menu_item(_('Remove from list'), 'list-remove',
# selected_tasks, sensitive=can_remove))
#
# menu.attach_to_widget(treeview)
# menu.show_all()
def make_menu_item(label, action_name, tasks=None, status=None, sensitive=True, force_start=False):
self.application.remove_action(action_name)
@ -1980,9 +1929,9 @@ class gPodder(BuilderWidget, dbus.service.Object):
selected_tasks, download.DownloadTask.QUEUED)
menu.append_item(item)
menu.append_item(make_menu_item(_('Cancel'), 'setDownloadCancelled',
selected_tasks, download.DownloadTask.CANCELLED))
selected_tasks, download.DownloadTask.CANCELLING))
menu.append_item(make_menu_item(_('Pause'), 'setDownloadPaused',
selected_tasks, download.DownloadTask.PAUSED))
selected_tasks, download.DownloadTask.PAUSING))
rmenu = Gio.Menu()
rmenu.append_item(make_menu_item(_('Remove from list'), 'setDownloadRemove',
selected_tasks, sensitive=can_remove))
@ -1996,7 +1945,7 @@ class gPodder(BuilderWidget, dbus.service.Object):
self.context_popover_show(self.downloads_popover, x, y)
return True
def on_mark_episodes_as_old(self, *args):
def on_mark_episodes_as_old(self, item):
assert self.active_channel is not None
for episode in self.active_channel.get_all_episodes():
@ -3340,14 +3289,15 @@ class gPodder(BuilderWidget, dbus.service.Object):
def download_episode_list(self, episodes, add_paused=False, force_start=False, downloader=None):
def queue_tasks(tasks, queued_existing_task):
for task in tasks:
if add_paused:
task.status = task.PAUSED
else:
self.mygpo_client.on_download([task.episode])
if force_start:
self.download_queue_manager.force_start_task(task)
with task:
if add_paused:
task.status = task.PAUSED
else:
self.download_queue_manager.queue_task(task)
self.mygpo_client.on_download([task.episode])
if force_start:
self.download_queue_manager.force_start_task(task)
else:
self.download_queue_manager.queue_task(task)
if tasks or queued_existing_task:
self.set_download_list_state(gPodderSyncUI.DL_ONEOFF)
# Flush updated episode status
@ -3405,14 +3355,16 @@ class gPodder(BuilderWidget, dbus.service.Object):
return
for task in tasks:
if task.status in (task.QUEUED, task.DOWNLOADING):
task.status = task.CANCELLED
elif task.status == task.PAUSED:
task.status = task.CANCELLED
# Call run, so the partial file gets deleted
task.run()
elif force:
task.status = task.CANCELLED
with task:
if task.status in (task.NEW, task.QUEUED, task.DOWNLOADING):
task.status = task.CANCELLING
elif task.status == task.PAUSED:
task.status = task.CANCELLED
# Call run, so the partial file gets deleted
task.run()
task.recycle()
elif force:
task.status = task.CANCELLED
self.update_episode_list_icons([task.url for task in tasks])
self.play_or_download()
@ -4003,13 +3955,14 @@ class gPodder(BuilderWidget, dbus.service.Object):
selected_tasks = [(Gtk.TreeRowReference.new(model, path), model.get_value(model.get_iter(path), 0)) for path in paths]
for tree_row_reference, task in selected_tasks:
if task.status in (task.DOWNLOADING, task.QUEUED):
task.status = task.PAUSED
elif task.status in (task.CANCELLED, task.PAUSED, task.FAILED):
self.download_queue_manager.queue_task(task)
self.set_download_list_state(gPodderSyncUI.DL_ONEOFF)
elif task.status == task.DONE:
model.remove(model.get_iter(tree_row_reference.get_path()))
with task:
if task.status in (task.DOWNLOADING, task.QUEUED):
task.status = task.PAUSED
elif task.status in (task.CANCELLED, task.PAUSED, task.FAILED):
self.download_queue_manager.queue_task(task)
self.set_download_list_state(gPodderSyncUI.DL_ONEOFF)
elif task.status == task.DONE:
model.remove(model.get_iter(tree_row_reference.get_path()))
self.play_or_download()

View File

@ -450,7 +450,7 @@ class PodcastEpisode(PodcastModelObject):
if task is None:
return False
return task.status in (task.DOWNLOADING, task.QUEUED, task.PAUSED)
return task.status in (task.DOWNLOADING, task.QUEUED, task.PAUSING, task.PAUSED, task.CANCELLING)
def check_is_new(self):
return (self.state == gpodder.STATE_NORMAL and self.is_new and

View File

@ -27,7 +27,10 @@ import calendar
import glob
import logging
import os.path
import threading
import time
from enum import Enum
from re import S
from urllib.parse import urlparse
import gpodder
@ -227,7 +230,7 @@ class Device(services.ObservableService):
# XXX: need to check if track is added properly?
sync_task = SyncTask(track)
sync_task.status = sync_task.QUEUED
sync_task.status = sync_task.NEW
sync_task.device = self
# New Task, we must wait on the GTK Loop
self.download_status_model.register_task(sync_task)
@ -694,9 +697,9 @@ class SyncTask(download.DownloadTask):
# An object representing the synchronization task of an episode
# Possible states this sync task can be in
STATUS_MESSAGE = (_('Queued'), _('Synchronizing'),
_('Finished'), _('Failed'), _('Cancelled'), _('Paused'))
(QUEUED, DOWNLOADING, DONE, FAILED, CANCELLED, PAUSED) = list(range(6))
STATUS_MESSAGE = (_('Queued'), _('Queued'), _('Downloading'),
_('Finished'), _('Failed'), _('Cancelling'), _('Cancelled'), _('Pausing'), _('Paused'))
(NEW, QUEUED, DOWNLOADING, DONE, FAILED, CANCELLING, CANCELLED, PAUSING, PAUSED) = list(range(9))
def __str__(self):
return self.__episode.title
@ -748,15 +751,17 @@ class SyncTask(download.DownloadTask):
episode = property(fget=__get_episode)
def cancel(self):
if self.status in (self.DOWNLOADING, self.QUEUED):
self.status = self.CANCELLED
with self:
if self.status in (self.DOWNLOADING, self.QUEUED):
self.status = self.CANCELLED
def removed_from_list(self):
# XXX: Should we delete temporary/incomplete files here?
pass
def __init__(self, episode):
self.__status = SyncTask.QUEUED
self.__lock = threading.RLock()
self.__status = SyncTask.NEW
self.__activity = SyncTask.ACTIVITY_SYNCHRONIZE
self.__status_changed = True
self.__episode = episode
@ -782,6 +787,12 @@ class SyncTask(download.DownloadTask):
# Callbacks
self._progress_updated = lambda x: None
def __enter__(self):
return self.__lock.acquire()
def __exit__(self, type, value, traceback):
self.__lock.release()
def notify_as_finished(self):
if self.status == SyncTask.DONE:
if self._notification_shown:
@ -815,10 +826,10 @@ class SyncTask(download.DownloadTask):
self.progress = max(0.0, min(1.0, (count * blockSize) / self.total_size))
self._progress_updated(self.progress)
if self.status == SyncTask.CANCELLED:
if self.status == SyncTask.CANCELLING:
raise SyncCancelledException()
if self.status == SyncTask.PAUSED:
if self.status == SyncTask.PAUSING:
raise SyncCancelledException()
def recycle(self):
@ -829,38 +840,54 @@ class SyncTask(download.DownloadTask):
self.__start_time = 0
self.__start_blocks = 0
# If the download has already been cancelled, skip it
if self.status == SyncTask.CANCELLED:
# If the download has already been cancelled/paused, skip it
if self.status == SyncTask.CANCELLING:
util.delete_file(self.tempname)
self.progress = 0.0
self.speed = 0.0
self.status = SyncTask.CANCELLED
return False
# We only start this download if its status is "downloading"
if self.status != SyncTask.DOWNLOADING:
if self.status == SyncTask.PAUSING:
self.status = SyncTask.PAUSED
return False
# We are synching this file right now
self.status = SyncTask.DOWNLOADING
with self:
# We only start this download if its status is "queued"
if self.status != SyncTask.QUEUED:
return False
# We are synching this file right now
self.status = SyncTask.DOWNLOADING
self._notification_shown = False
sync_result = SyncTask.DONE
try:
logger.info('Starting SyncTask')
self.device.add_track(self.episode, reporthook=self.status_updated)
except SyncCancelledException as e:
sync_result = SyncTask.CANCELLED
except Exception as e:
self.status = SyncTask.FAILED
sync_result = SyncTask.FAILED
logger.error('Sync failed: %s', str(e), exc_info=True)
self.error_message = _('Error: %s') % (str(e),)
if self.status == SyncTask.DOWNLOADING:
# Everything went well - we're done
self.status = SyncTask.DONE
if self.total_size <= 0:
self.total_size = util.calculate_size(self.filename)
logger.info('Total size updated to %d', self.total_size)
self.progress = 1.0
gpodder.user_extensions.on_episode_synced(self.device, self.__episode)
return True
with self:
if sync_result == SyncTask.CANCELLED:
if self.status == SyncTask.CANCELLING:
self.status = SyncTask.CANCELLED
else:
self.status = SyncTask.PAUSED
elif sync_result == SyncTask.DONE:
# Everything went well - we're done
self.status = SyncTask.DONE
if self.total_size <= 0:
self.total_size = util.calculate_size(self.filename)
logger.info('Total size updated to %d', self.total_size)
self.progress = 1.0
gpodder.user_extensions.on_episode_synced(self.device, self.__episode)
return True
self.speed = 0.0