Refactor DownloadStatusManager to DownloadStatusModel

Cleans up some GTK+ requirements of gpodder.services and makes
the purpose of DownloadStatusModel vs. DownloadQueueManager
clearer to the developer.
This commit is contained in:
Thomas Perl 2009-08-24 16:17:32 +02:00
parent a6c54819d1
commit 965cecfa7f
4 changed files with 154 additions and 134 deletions

View File

@ -19,7 +19,7 @@
#
# download.py -- Download client using DownloadStatusManager
# download.py -- Download queue management
# Thomas Perl <thp@perli.net> 2007-09-15
#
# Based on libwget.py (2005-10-29)
@ -328,8 +328,7 @@ class DownloadQueueWorker(threading.Thread):
class DownloadQueueManager(object):
def __init__(self, download_status_manager, config):
self.download_status_manager = download_status_manager
def __init__(self, config):
self._config = config
self.tasks = collections.deque()
@ -362,15 +361,8 @@ class DownloadQueueManager(object):
with self.worker_threads_access:
return len(self.worker_threads) > 0
def add_resumed_task(self, task):
"""Simply add the task without starting the download"""
self.download_status_manager.register_task(task)
def add_task(self, task):
if task.status == DownloadTask.INIT:
# This task is fresh, so add it to our status manager
self.download_status_manager.register_task(task)
else:
if task.status != DownloadTask.INIT:
# This task is old so update episode from db
task.episode.reload_from_db()
task.status = DownloadTask.QUEUED

View File

@ -0,0 +1,128 @@
# -*- coding: utf-8 -*-
#
# gPodder - A media aggregator and podcast client
# Copyright (c) 2005-2009 Thomas Perl and the gPodder Team
#
# gPodder is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 3 of the License, or
# (at your option) any later version.
#
# gPodder is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
#
# gpodder.gtkui.download -- Download management in GUIs (2009-08-24)
# Based on code from gpodder.services (thp, 2007-08-24)
#
import gpodder
from gpodder.liblogger import log
from gpodder import util
from gpodder import download
import gtk
import gobject
import collections
_ = gpodder.gettext
class DownloadStatusModel(gtk.ListStore):
# Symbolic names for our columns, so we know what we're up to
C_TASK, C_NAME, C_URL, C_PROGRESS, C_PROGRESS_TEXT, C_SIZE_TEXT, \
C_ICON_NAME, C_SPEED_TEXT, C_STATUS_TEXT = range(9)
def __init__(self):
gtk.ListStore.__init__(self, object, str, str, int, str, str, str, str, str)
# Set up stock icon IDs for tasks
self._status_ids = collections.defaultdict(lambda: None)
self._status_ids[download.DownloadTask.DOWNLOADING] = gtk.STOCK_GO_DOWN
self._status_ids[download.DownloadTask.DONE] = gtk.STOCK_APPLY
self._status_ids[download.DownloadTask.FAILED] = gtk.STOCK_STOP
self._status_ids[download.DownloadTask.CANCELLED] = gtk.STOCK_CANCEL
self._status_ids[download.DownloadTask.PAUSED] = gtk.STOCK_MEDIA_PAUSE
def request_update(self, iter, task=None):
if task is None:
# Ongoing update request from UI - get task from model
task = self.get_value(iter, self.C_TASK)
else:
# Initial update request - update non-changing fields
self.set(iter,
self.C_TASK, task,
self.C_NAME, str(task),
self.C_URL, task.url)
if task.status == task.FAILED:
status_message = _('Failed: %s') % (task.error_message,)
else:
status_message = task.STATUS_MESSAGE[task.status]
if task.status == task.DOWNLOADING:
speed_message = '%s/s' % util.format_filesize(task.speed)
else:
speed_message = ''
self.set(iter,
self.C_PROGRESS, 100.*task.progress,
self.C_PROGRESS_TEXT, '%.0f%%' % (task.progress*100.,),
self.C_SIZE_TEXT, util.format_filesize(task.total_size),
self.C_ICON_NAME, self._status_ids[task.status],
self.C_SPEED_TEXT, speed_message,
self.C_STATUS_TEXT, status_message)
def __add_new_task(self, task):
iter = self.append()
self.request_update(iter, task)
def register_task(self, task):
util.idle_add(self.__add_new_task, task)
def tell_all_tasks_to_quit(self):
for row in self:
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
# Delete cancelled and failed downloads
if task.status in (task.CANCELLED, task.FAILED):
task.removed_from_list()
def are_downloads_in_progress(self):
"""
Returns True if there are any downloads in the
QUEUED or DOWNLOADING status, False otherwise.
"""
for row in self:
task = row[DownloadStatusModel.C_TASK]
if task is not None and \
task.status in (task.DOWNLOADING, \
task.QUEUED):
return True
return False
def cancel_by_url(self, url):
for row in self:
task = row[DownloadStatusModel.C_TASK]
if task is not None and task.url == url and \
task.status in (task.DOWNLOADING, \
task.QUEUED):
task.status = task.CANCELLED
return True
return False

View File

@ -96,6 +96,7 @@ from gpodder.gtkui.model import PodcastListModel
from gpodder.gtkui.model import EpisodeListModel
from gpodder.gtkui.opml import OpmlListModel
from gpodder.gtkui.config import ConfigModel
from gpodder.gtkui.download import DownloadStatusModel
from gpodder.libgpodder import db
from gpodder.libgpodder import gl
@ -503,8 +504,8 @@ class gPodder(BuilderWidget, dbus.service.Object):
self.tray_icon = None
self.gpodder_episode_window = None
self.download_status_manager = services.DownloadStatusManager()
self.download_queue_manager = download.DownloadQueueManager(self.download_status_manager, gl.config)
self.download_status_model = DownloadStatusModel()
self.download_queue_manager = download.DownloadQueueManager(gl.config)
self.fullscreen = False
self.minimized = False
@ -660,8 +661,6 @@ class gPodder(BuilderWidget, dbus.service.Object):
self.treeDownloads.set_rubber_banding(True)
# columns and renderers for "download progress" tab
DownloadStatusManager = services.DownloadStatusManager
# First column: [ICON] Episodename
column = gtk.TreeViewColumn(_('Episode'))
@ -672,12 +671,12 @@ class gPodder(BuilderWidget, dbus.service.Object):
cell.set_property('stock-size', gtk.ICON_SIZE_MENU)
column.pack_start(cell, expand=False)
column.add_attribute(cell, 'stock-id', \
DownloadStatusManager.C_ICON_NAME)
DownloadStatusModel.C_ICON_NAME)
cell = gtk.CellRendererText()
cell.set_property('ellipsize', pango.ELLIPSIZE_END)
column.pack_start(cell, expand=True)
column.add_attribute(cell, 'text', DownloadStatusManager.C_NAME)
column.add_attribute(cell, 'text', DownloadStatusModel.C_NAME)
column.set_sizing(gtk.TREE_VIEW_COLUMN_AUTOSIZE)
column.set_resizable(True)
@ -686,24 +685,24 @@ class gPodder(BuilderWidget, dbus.service.Object):
# Second column: Progress
column = gtk.TreeViewColumn(_('Progress'), gtk.CellRendererProgress(),
value=DownloadStatusManager.C_PROGRESS, \
text=DownloadStatusManager.C_PROGRESS_TEXT)
value=DownloadStatusModel.C_PROGRESS, \
text=DownloadStatusModel.C_PROGRESS_TEXT)
self.treeDownloads.append_column(column)
# Third column: Size
if gpodder.interface != gpodder.MAEMO:
column = gtk.TreeViewColumn(_('Size'), gtk.CellRendererText(),
text=DownloadStatusManager.C_SIZE_TEXT)
text=DownloadStatusModel.C_SIZE_TEXT)
self.treeDownloads.append_column(column)
# Fourth column: Speed
column = gtk.TreeViewColumn(_('Speed'), gtk.CellRendererText(),
text=DownloadStatusManager.C_SPEED_TEXT)
text=DownloadStatusModel.C_SPEED_TEXT)
self.treeDownloads.append_column(column)
# Fifth column: Status
column = gtk.TreeViewColumn(_('Status'), gtk.CellRendererText(),
text=DownloadStatusManager.C_STATUS_TEXT)
text=DownloadStatusModel.C_STATUS_TEXT)
self.treeDownloads.append_column(column)
# After we've set up most of the window, show it :)
@ -718,7 +717,7 @@ class gPodder(BuilderWidget, dbus.service.Object):
services.cover_downloader.register('cover-available', self.cover_download_finished)
services.cover_downloader.register('cover-removed', self.cover_file_removed)
self.treeDownloads.set_model(self.download_status_manager.get_tree_model())
self.treeDownloads.set_model(self.download_status_model)
self.download_tasks_seen = set()
self.download_list_update_enabled = False
self.last_download_count = 0
@ -810,7 +809,7 @@ class gPodder(BuilderWidget, dbus.service.Object):
self.download_list_update_enabled = True
def on_btnCleanUpDownloads_clicked(self, button):
model = self.treeDownloads.get_model()
model = self.download_status_model
all_tasks = [(gtk.TreeRowReference(model, row.path), row[0]) for row in model]
changed_episode_urls = []
@ -842,7 +841,7 @@ class gPodder(BuilderWidget, dbus.service.Object):
def update_downloads_list(self):
try:
model = self.treeDownloads.get_model()
model = self.download_status_model
downloading, failed, finished, queued, others = 0, 0, 0, 0, 0
total_speed, total_size, done_size = 0, 0, 0
@ -864,9 +863,9 @@ class gPodder(BuilderWidget, dbus.service.Object):
model = ()
for row in model:
self.download_status_manager.request_update(row.iter)
self.download_status_model.request_update(row.iter)
task = row[self.download_status_manager.C_TASK]
task = row[self.download_status_model.C_TASK]
speed, size, status, progress = task.speed, task.total_size, task.status, task.progress
total_size += size
@ -2181,7 +2180,7 @@ class gPodder(BuilderWidget, dbus.service.Object):
Displays a confirmation dialog (and closes/hides gPodder)
"""
downloading = self.download_status_manager.are_downloads_in_progress()
downloading = self.download_status_model.are_downloads_in_progress()
# Only iconify if we are using the window's "X" button,
# but not when we are using "Quit" in the menu or toolbar
@ -2240,7 +2239,7 @@ class gPodder(BuilderWidget, dbus.service.Object):
self.tray_icon.set_visible(False)
# Notify all tasks to to carry out any clean-up actions
self.download_status_manager.tell_all_tasks_to_quit()
self.download_status_model.tell_all_tasks_to_quit()
while gtk.events_pending():
gtk.main_iteration(False)
@ -2412,9 +2411,10 @@ class gPodder(BuilderWidget, dbus.service.Object):
if add_paused:
task.status = task.PAUSED
self.download_queue_manager.add_resumed_task(task)
else:
self.download_queue_manager.add_task(task)
self.download_status_model.register_task(task)
self.enable_download_list_update()
def new_episodes_show(self, episodes):
@ -2834,7 +2834,7 @@ class gPodder(BuilderWidget, dbus.service.Object):
# cancel any active downloads from this channel
for episode in self.active_channel.get_all_episodes():
self.download_status_manager.cancel_by_url(episode.url)
self.download_status_model.cancel_by_url(episode.url)
# get the URL of the podcast we want to select next
position = self.channels.index(self.active_channel)
@ -2980,7 +2980,7 @@ class gPodder(BuilderWidget, dbus.service.Object):
self.toolDownload.set_sensitive( False)
self.toolPlay.set_sensitive( False)
self.toolTransfer.set_sensitive( False)
self.toolCancel.set_sensitive( False)#services.download_status_manager.has_items())
self.toolCancel.set_sensitive( False)#services.download_status_model.has_items())
def on_treeChannels_row_activated(self, widget, path, *args):
# double-click action of the podcast list or enter
@ -3090,7 +3090,7 @@ class gPodder(BuilderWidget, dbus.service.Object):
if self.gpodder_episode_window is None:
log('First-time use of episode window --- creating', sender=self)
self.gpodder_episode_window = gPodderEpisode(\
download_status_manager=self.download_status_manager, \
download_status_model=self.download_status_model, \
episode_is_downloading=self.episode_is_downloading)
self.gpodder_episode_window.show(episode=episode, download_callback=download_callback, play_callback=play_callback)
@ -3908,7 +3908,7 @@ class gPodderEpisode(BuilderWidget):
b.place_cursor(b.get_start_iter())
def on_cancel(self, widget):
self.download_status_manager.cancel_by_url(self.episode.url)
self.download_status_model.cancel_by_url(self.episode.url)
def on_delete_event(self, widget, event):
# Avoid destroying the dialog, simply hide

View File

@ -36,12 +36,10 @@ from gpodder import download
import gtk
import gobject
import collections
import threading
import time
import urllib2
import os
import os.path
_ = gpodder.gettext
@ -287,101 +285,3 @@ class CoverDownloader(ObservableService):
cover_downloader = CoverDownloader()
class DownloadStatusManager(object):
# Types of columns, needed for creation of gtk.ListStore
COLUMNS = (object, str, str, int, str, str, str, str, str)
# Symbolic names for our columns, so we know what we're up to
C_TASK, C_NAME, C_URL, C_PROGRESS, C_PROGRESS_TEXT, C_SIZE_TEXT, \
C_ICON_NAME, C_SPEED_TEXT, C_STATUS_TEXT = range(len(COLUMNS))
def __init__(self):
self.__model = gtk.ListStore(*DownloadStatusManager.COLUMNS)
# Shorten the name (for below)
DownloadTask = download.DownloadTask
# Set up stock icon IDs for tasks
self.status_stock_ids = collections.defaultdict(lambda: None)
self.status_stock_ids[DownloadTask.DOWNLOADING] = gtk.STOCK_GO_DOWN
self.status_stock_ids[DownloadTask.DONE] = gtk.STOCK_APPLY
self.status_stock_ids[DownloadTask.FAILED] = gtk.STOCK_STOP
self.status_stock_ids[DownloadTask.CANCELLED] = gtk.STOCK_CANCEL
self.status_stock_ids[DownloadTask.PAUSED] = gtk.STOCK_MEDIA_PAUSE
def get_tree_model(self):
return self.__model
def request_update(self, iter, task=None):
if task is None:
# Ongoing update request from UI - get task from model
task = self.__model.get_value(iter, self.C_TASK)
else:
# Initial update request - update non-changing fields
self.__model.set(iter,
self.C_TASK, task,
self.C_NAME, str(task),
self.C_URL, task.url)
if task.status == task.FAILED:
status_message = _('Failed: %s') % (task.error_message,)
else:
status_message = task.STATUS_MESSAGE[task.status]
if task.status == task.DOWNLOADING:
speed_message = '%s/s' % util.format_filesize(task.speed)
else:
speed_message = ''
self.__model.set(iter,
self.C_PROGRESS, 100.*task.progress,
self.C_PROGRESS_TEXT, '%.0f%%' % (task.progress*100.,),
self.C_SIZE_TEXT, util.format_filesize(task.total_size),
self.C_ICON_NAME, self.status_stock_ids[task.status],
self.C_SPEED_TEXT, speed_message,
self.C_STATUS_TEXT, status_message)
def __add_new_task(self, task):
iter = self.__model.append()
self.request_update(iter, task)
def register_task(self, task):
util.idle_add(self.__add_new_task, task)
def tell_all_tasks_to_quit(self):
for row in self.__model:
task = row[DownloadStatusManager.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
# Delete cancelled and failed downloads
if task.status in (task.CANCELLED, task.FAILED):
task.removed_from_list()
def are_downloads_in_progress(self):
"""
Returns True if there are any downloads in the
QUEUED or DOWNLOADING status, False otherwise.
"""
for row in self.__model:
task = row[DownloadStatusManager.C_TASK]
if task is not None and \
task.status in (task.DOWNLOADING, \
task.QUEUED):
return True
return False
def cancel_by_url(self, url):
for row in self.__model:
task = row[DownloadStatusManager.C_TASK]
if task is not None and task.url == url and \
task.status in (task.DOWNLOADING, \
task.QUEUED):
task.status = task.CANCELLED
return True
return False