Merged and cleaned-up the device sync code (bug 1579)

This commit is contained in:
Thomas Perl 2012-07-09 21:08:40 +02:00
parent 5b949efba2
commit 972c045ad9
7 changed files with 220 additions and 197 deletions

View File

@ -228,7 +228,7 @@
<object class="GtkAction" id="item_sync">
<property name="stock_id">gtk-refresh</property>
<property name="name">item_sync</property>
<property name="label" translatable="yes">Sync to Device</property>
<property name="label" translatable="yes">Sync to device</property>
<signal handler="on_sync_to_device_activate" name="activate"/>
</object>
<accelerator key="S" modifiers="GDK_CONTROL_MASK"/>

View File

@ -155,22 +155,26 @@ defaults = {
},
},
# Synchronization with portable devices (MP3 players, etc..)
'device_sync': {
# Settings for Device sync
'device_type': 'none', # Possible values: 'none', 'filesystem'
'device_folder': '/media',
'one_folder_per_podcast': True,
'skip_played_episodes': True,
'delete_played_episodes': False,
'max_filename_length': 999,
'custom_sync_name': '{episode.pubdate_prop}_{episode.title}',
'custom_sync_name_enabled': False,
'after_sync': {
'mark_episodes_played': False,
'delete_episodes': False,
'sync_disks': False,
},
'after_sync': {
'mark_episodes_played': False,
'delete_episodes': False,
'sync_disks': False,
},
},
'youtube': {
'preferred_fmt_id': 18,

View File

@ -531,9 +531,10 @@ class DownloadTask(object):
STATUS_MESSAGE = (_('Added'), _('Queued'), _('Downloading'),
_('Finished'), _('Failed'), _('Cancelled'), _('Paused'))
(INIT, QUEUED, DOWNLOADING, DONE, FAILED, CANCELLED, PAUSED) = range(7)
(ACTIVITY_DOWNLOAD, ACTIVITY_SYNCHRONIZE) = range(2)
# Wheter this task represents a file download or a device sync operation
ACTIVITY_DOWNLOAD, ACTIVITY_SYNCHRONIZE = range(2)
def __str__(self):
return self.__episode.title
@ -592,7 +593,7 @@ class DownloadTask(object):
def __init__(self, episode, config):
assert episode.download_task is None
self.__status = DownloadTask.INIT
self.__activity=DownloadTask.ACTIVITY_DOWNLOAD
self.__activity = DownloadTask.ACTIVITY_DOWNLOAD
self.__status_changed = True
self.__episode = episode
self._config = config

View File

@ -93,7 +93,7 @@ class OnSyncActionList(gtk.ListStore):
row[self.C_ON_SYNC_DELETE]):
return index
if (self._config.device_sync.after_sync.mark_episodes_played and
row[self.C_ON_SYNC_MARK_PLAYED] and not
row[self.C_ON_SYNC_MARK_PLAYED] and not
self._config.device_sync.after_sync.delete_episodes):
return index
return 0 # Some sane default
@ -375,9 +375,12 @@ class gPodderPreferences(BuilderWidget):
if device_type == 'none':
self.btn_filesystemMountpoint.set_label('')
self.btn_filesystemMountpoint.set_sensitive(False)
elif device_type == 'filesystem': #JOSEPH: add back in ipod & mtp support
elif device_type == 'filesystem':
self.btn_filesystemMountpoint.set_label(self._config.device_sync.device_folder)
self.btn_filesystemMountpoint.set_sensitive(True)
else:
# TODO: Add support for iPod and MTP devices
pass
children = self.btn_filesystemMountpoint.get_children()
if children:
@ -385,9 +388,10 @@ class gPodderPreferences(BuilderWidget):
label.set_alignment(0., .5)
def on_btn_device_mountpoint_clicked(self, widget):
fs = gtk.FileChooserDialog( title = _('Select folder for mount point'), action = gtk.FILE_CHOOSER_ACTION_SELECT_FOLDER)
fs.add_button( gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL)
fs.add_button( gtk.STOCK_OPEN, gtk.RESPONSE_OK)
fs = gtk.FileChooserDialog(title=_('Select folder for mount point'),
action=gtk.FILE_CHOOSER_ACTION_SELECT_FOLDER)
fs.add_button(gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL)
fs.add_button(gtk.STOCK_OPEN, gtk.RESPONSE_OK)
fs.set_current_folder(self.btn_filesystemMountpoint.get_label())
if fs.run() == gtk.RESPONSE_OK:
filename = fs.get_filename()

View File

@ -1,7 +1,7 @@
# -*- coding: utf-8 -*-
#
# gPodder - A media aggregator and podcast client
# Copyright (c) 2005-2011 Thomas Perl and the gPodder Team
# Copyright (c) 2005-2012 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
@ -19,6 +19,7 @@
# gpodder.gtkui.desktop.sync - Glue code between GTK+ UI and sync module
# Thomas Perl <thp@gpodder.org>; 2009-09-05 (based on code from gui.py)
# Ported to gPodder 3 by Joseph Wickremasinghe in June 2012
import gtk
import threading
@ -28,33 +29,37 @@ _ = gpodder.gettext
from gpodder import util
from gpodder import sync
import logging
logger = logging.getLogger(__name__)
class gPodderSyncUI(object):
def __init__(self, config, notification,
parent_window, show_confirmation,
update_episode_list_icons,
update_podcast_list_model,
preferences_widget,
episode_selector_class, download_status_model,
download_queue_manager,
enable_download_list_update,
def __init__(self, config, notification, parent_window,
show_confirmation,
update_episode_list_icons,
update_podcast_list_model,
preferences_widget,
episode_selector_class,
download_status_model,
download_queue_manager,
enable_download_list_update,
commit_changes_to_database):
self.device = None
self._config = config
self.notification = notification
self.parent_window = parent_window
self.show_confirmation = show_confirmation
self.update_episode_list_icons = update_episode_list_icons
self.update_podcast_list_model = update_podcast_list_model
self.preferences_widget = preferences_widget
self.episode_selector_class = episode_selector_class
self.download_status_model = download_status_model
self.download_queue_manager = download_queue_manager
self.enable_download_list_update = enable_download_list_update
self.commit_changes_to_database = commit_changes_to_database
self.download_status_model=download_status_model
self.download_queue_manager=download_queue_manager
self.enable_download_list_update=enable_download_list_update
self.device=None
def _filter_sync_episodes(self, channels, only_downloaded=False):
"""Return a list of episodes for device synchronization
@ -70,7 +75,7 @@ class gPodderSyncUI(object):
continue
for episode in channel.get_all_episodes():
if (episode.was_downloaded(and_exists=True) or
if (episode.was_downloaded(and_exists=True) or
not only_downloaded):
episodes.append(episode)
return episodes
@ -86,7 +91,6 @@ class gPodderSyncUI(object):
self.notification(message, title, widget=self.preferences_widget)
def on_synchronize_episodes(self, channels, episodes=None, force_played=True):
device = sync.open_device(self)
if device is None:
@ -95,14 +99,13 @@ class gPodderSyncUI(object):
if not device.open():
return self._show_message_cannot_open()
else:
#only set if device is configured
#and opened successfully
self.device=device
# Only set if device is configured and opened successfully
self.device = device
if episodes is None:
force_played = False
episodes = self._filter_sync_episodes(channels)
def check_free_space():
# "Will we add this episode to the device?"
def will_add(episode):
@ -111,7 +114,7 @@ class gPodderSyncUI(object):
return False
# Might not be synced if it's played already
if (not force_played and
if (not force_played and
self._config.device_sync.skip_played_episodes):
return False
@ -139,9 +142,9 @@ class gPodderSyncUI(object):
device.close()
return
# Finally start the synchronization process
# Finally start the synchronization process
def sync_thread_func():
self.enable_download_list_update()
self.enable_download_list_update()
device.add_sync_tasks(episodes, force_played=force_played)
threading.Thread(target=sync_thread_func).start()
@ -150,9 +153,9 @@ class gPodderSyncUI(object):
def cleanup_episodes():
# 'skip_played_episodes' must be used or else all the
# played tracks will be copied then immediately deleted
if (self._config.device_sync.delete_played_episodes and
if (self._config.device_sync.delete_played_episodes and
self._config.device_sync.skip_played_episodes):
all_episodes = self._filter_sync_episodes(channels,
all_episodes = self._filter_sync_episodes(channels,
only_downloaded=False)
episodes_on_device = device.get_all_tracks()
for local_episode in all_episodes:
@ -173,3 +176,4 @@ class gPodderSyncUI(object):
# 2. Check for free space (in UI thread)
# 3. Sync the device (in UI thread)
threading.Thread(target=cleanup_episodes).start()

View File

@ -1096,11 +1096,13 @@ class gPodder(BuilderWidget, dbus.service.Object):
download_tasks_seen.add(task)
if (status == download.DownloadTask.DOWNLOADING and activity==download.DownloadTask.ACTIVITY_DOWNLOAD):
if (status == download.DownloadTask.DOWNLOADING and
activity == download.DownloadTask.ACTIVITY_DOWNLOAD):
downloading += 1
total_speed += speed
elif (status == download.DownloadTask.DOWNLOADING and activity==download.DownloadTask.ACTIVITY_SYNCHRONIZE):
synchronizing+=1
elif (status == download.DownloadTask.DOWNLOADING and
activity == download.DownloadTask.ACTIVITY_SYNCHRONIZE):
synchronizing += 1
elif status == download.DownloadTask.FAILED:
failed += 1
elif status == download.DownloadTask.DONE:
@ -1121,7 +1123,7 @@ class gPodder(BuilderWidget, dbus.service.Object):
if downloading > 0:
s.append(N_('%(count)d active', '%(count)d active', downloading) % {'count':downloading})
if synchronizing > 0:
s.append(N_('%(count)d active', '%(count)d active', synchronizing) % {'count':synchronizing})
s.append(N_('%(count)d active', '%(count)d active', synchronizing) % {'count':synchronizing})
if failed > 0:
s.append(N_('%(count)d failed', '%(count)d failed', failed) % {'count':failed})
if queued > 0:
@ -1356,31 +1358,34 @@ class gPodder(BuilderWidget, dbus.service.Object):
return selected_tasks, can_queue, can_cancel, can_pause, can_remove, can_force
def downloads_finished(self, download_tasks_seen):
# Separate tasks into downloads & syncs
# Since calling notify_as_finished or notify_as_failed clears the flag,
# need to iterate through downloads & syncs separately, else all sync
# tasks will have their flags cleared if we do downloads first
#separate tasks into downloads & syncs
#since calling notify_as_finished or notify_as_failed clears the flag,
#need to iterate through downloads & syncs separately, else all sync
#tasks will have their flags cleared if we do downloads first
download_tasks=filter((lambda task: type(task).__name__=='DownloadTask'),download_tasks_seen)
def filter_by_activity(activity, tasks):
return filter(lambda task: task.activity == activity, tasks)
sync_tasks=filter((lambda task: type(task).__name__=='SyncTask'),download_tasks_seen)
download_tasks = filter_by_activity(download.DownloadTask.ACTIVITY_DOWNLOAD,
download_tasks_seen)
finished_downloads = [str(task) for task in download_tasks if
task.notify_as_finished()]
failed_downloads = [str(task)+' ('+task.error_message+')'
for task in download_tasks if
task.notify_as_failed()]
finished_downloads = [str(task)
for task in download_tasks if task.notify_as_finished()]
failed_downloads = ['%s (%s)' % (str(task), task.error_message)
for task in download_tasks if task.notify_as_failed()]
#Note that 'finished_ / failed_downloads' is a list of strings
#Whereas 'finished_ / failed_syncs' is a list of SyncTask objects
finished_syncs=filter((lambda task: task.notify_as_finished()),sync_tasks)
failed_syncs=filter((lambda task: task.notify_as_failed()),sync_tasks)
sync_tasks = filter_by_activity(download.DownloadTask.ACTIVITY_SYNCHRONIZE,
download_tasks_seen)
finished_syncs = [task for task in sync_tasks if task.notify_as_finished()]
failed_syncs = [task for task in sync_tasks if task.notify_as_failed()]
# Note that 'finished_ / failed_downloads' is a list of strings
# Whereas 'finished_ / failed_syncs' is a list of SyncTask objects
if finished_downloads and failed_downloads:
message = self.format_episode_list(finished_downloads, 5)
message += '\n\n<i>%s</i>\n' % _('These downloads failed:')
message += '\n\n<i>%s</i>\n' % _('Could not download some episodes:')
message += self.format_episode_list(failed_downloads, 5)
self.show_message(message, _('Downloads finished'), True, widget=self.labelDownloads)
elif finished_downloads:
@ -1392,7 +1397,7 @@ class gPodder(BuilderWidget, dbus.service.Object):
if finished_syncs and failed_syncs:
message = self.format_episode_list(map((lambda task: str(task)),finished_syncs), 5)
message += '\n\n<i>%s</i>\n' % _('These syncs failed:')
message += '\n\n<i>%s</i>\n' % _('Could not sync some episodes:')
message += self.format_episode_list(map((lambda task: str(task)),failed_syncs), 5)
self.show_message(message, _('Device synchronization finished'), True, widget=self.labelDownloads)
elif finished_syncs:
@ -1402,21 +1407,19 @@ class gPodder(BuilderWidget, dbus.service.Object):
message = self.format_episode_list(map((lambda task: str(task)),failed_syncs))
self.show_message(message, _('Device synchronization failed'), True, widget=self.labelDownloads)
#do post sync processing if appropriate
for task in finished_syncs+failed_syncs:
# Do post-sync processing if required
for task in finished_syncs + failed_syncs:
if self.config.device_sync.after_sync.mark_episodes_played:
logger.info('Marking as played on transfer: %s', task.episode.url)
task.episode.mark(is_played=True)
task.episode.mark(is_played=True)
if self.config.device_sync.after_sync.delete_episodes:
logger.info('Removing episode after transfer: %s', task.episode.url)
task.episode.delete_from_disk()
self.sync_ui.device.close()
#update icon list to show changes, if any
# Update icon list to show changes, if any
self.update_episode_list_icons(all=True)
@ -3469,15 +3472,17 @@ class gPodder(BuilderWidget, dbus.service.Object):
self.download_episode_list(episodes=[episode])
def on_sync_to_device_activate(self, widget, episodes=None, force_played=True):
self.sync_ui = gPodderSyncUI(self.config, self.notification,
self.main_window, self.show_confirmation,
self.update_episode_list_icons,
self.update_podcast_list_model, self.toolPreferences,
self.sync_ui = gPodderSyncUI(self.config, self.notification,
self.main_window,
self.show_confirmation,
self.update_episode_list_icons,
self.update_podcast_list_model,
self.toolPreferences,
gPodderEpisodeSelector,
self.download_status_model,self.download_queue_manager,
self.enable_download_list_update,
self.commit_changes_to_database
)
self.download_status_model,
self.download_queue_manager,
self.enable_download_list_update,
self.commit_changes_to_database)
self.sync_ui.on_synchronize_episodes(self.channels, episodes, force_played)

View File

@ -1,7 +1,7 @@
# -*- coding: utf-8 -*-
#
# gPodder - A media aggregator and podcast client
# Copyright (c) 2005-2011 Thomas Perl and the gPodder Team
# Copyright (c) 2005-2012 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
@ -21,12 +21,13 @@
# sync.py -- Device synchronization
# Thomas Perl <thp@perli.net> 2007-12-06
# based on libipodsync.py (2006-04-05 Thomas Perl)
# Ported to gPodder 3 by Joseph Wickremasinghe in June 2012
import gpodder
from gpodder import util
from gpodder import services
from download import DownloadTask
from gpodder import download
import logging
logger = logging.getLogger(__name__)
@ -35,24 +36,31 @@ import calendar
_ = gpodder.gettext
gpod_available = True
try:
import gpod
except:
gpod_available = False
logger.warning('Could not find gpod')
#
# TODO: Re-enable iPod and MTP sync support
#
pymtp_available = True
try:
import gpodder.gpopymtp as pymtp
except:
pymtp_available = False
logger.warning('Could not load gpopymtp (libmtp not installed?).')
pymtp_available = False
gpod_available = False
#gpod_available = True
#try:
# import gpod
#except:
# gpod_available = False
# logger.warning('Could not find gpod')
#
#pymtp_available = True
#try:
# import gpodder.gpopymtp as pymtp
#except:
# pymtp_available = False
# logger.warning('Could not load gpopymtp (libmtp not installed?).')
try:
import eyeD3
except:
logger.warning( 'Could not find eyeD3')
logger.warning('Could not find eyeD3')
import os.path
import glob
@ -103,13 +111,16 @@ if pymtp_available:
# logger.info('MTP.mkdir: %s = %u' % (path, folder_id))
return folder_id
def open_device(self):
config=self._config
device_type=self._config.device_sync.device_type
def open_device(gui):
config = gui._config
device_type = gui._config.device_sync.device_type
if device_type == 'filesystem':
return MP3PlayerDevice(config,self.download_status_model, self.download_queue_manager)
else:
return None
return MP3PlayerDevice(config,
gui.download_status_model,
gui.download_queue_manager)
return None
def get_track_length(filename):
if util.find_command('mplayer') is not None:
@ -132,7 +143,7 @@ def get_track_length(filename):
class SyncTrack(object):
"""
This represents a track that is on a device. You need
to specify at least the following keyword arguments,
to specify at least the following keyword arguments,
because these will be used to display the track in the
GUI. All other keyword arguments are optional and can
be used to reference internal objects, etc... See the
@ -192,11 +203,10 @@ class Device(services.ObservableService):
return True
def add_sync_tasks(self,tracklist, force_played=False):
for track in list(tracklist):
# Filter tracks that are not meant to be synchronized
does_not_exist = not track.was_downloaded(and_exists=True)
exclude_played = (not track.is_new and
exclude_played = (not track.is_new and
self._config.device_sync.skip_played_episodes)
wrong_type = track.file_type() not in self.allowed_types
@ -204,11 +214,11 @@ class Device(services.ObservableService):
logger.info('Excluding %s from sync', track.title)
tracklist.remove(track)
for id, track in enumerate(sorted(tracklist, key=lambda e: e.pubdate_prop)):
for track in sorted(tracklist, key=lambda e: e.pubdate_prop):
if self.cancelled:
return False
#JOSEPH: need to check if track is added properly?
# XXX: need to check if track is added properly?
sync_task=SyncTask(track)
sync_task.status=sync_task.QUEUED
@ -218,12 +228,13 @@ class Device(services.ObservableService):
return True
def remove_tracks(self, tracklist=[]):
for id, track in enumerate(tracklist):
def remove_tracks(self, tracklist):
for idx, track in enumerate(tracklist):
if self.cancelled:
return False
self.notify('progress', id, len(tracklist))
self.notify('progress', idx, len(tracklist))
self.remove_track(track)
return True
def get_all_tracks(self):
@ -256,7 +267,6 @@ class iPodDevice(Device):
self.itdb = None
self.podcast_playlist = None
def get_free_space(self):
# Reserve 10 MiB for iTunesDB writing (to be on the safe side)
@ -292,11 +302,12 @@ class iPodDevice(Device):
self.notify('status', _('Saving iPod database'))
gpod.itdb_write(self.itdb, None)
self.itdb = None
if self._config.ipod_write_gtkpod_extended:
self.notify('status', _('Writing extended gtkpod database'))
ext_filename = os.path.join(self.mountpoint, 'iPod_Control', 'iTunes', 'iTunesDB.ext')
idb_filename = os.path.join(self.mountpoint, 'iPod_Control', 'iTunes', 'iTunesDB')
itunes_folder = os.path.join(self.mountpoint, 'iPod_Control', 'iTunes')
ext_filename = os.path.join(itunes_folder, 'iTunesDB.ext')
idb_filename = os.path.join(itunes_folder, 'iTunesDB')
if os.path.exists(ext_filename) and os.path.exists(idb_filename):
try:
db = gpod.ipod.Database(self.mountpoint)
@ -304,13 +315,11 @@ class iPodDevice(Device):
gpod.gtkpod.write(ext_filename, db, idb_filename)
db.close()
except:
logger.error('Error when writing iTunesDB.ext', sender=self, traceback=True)
logger.error('Error writing iTunesDB.ext')
else:
logger.warning('I could not find %s or %s. Will not update extended gtkpod DB.', ext_filename, idb_filename, sender=self)
else:
logger.info('Not writing extended gtkpod DB. Set "ipod_write_gpod_extended" to True if I should write it.', sender=self)
logger.warning('Could not find %s or %s.',
ext_filename, idb_filename)
Device.close(self)
return True
@ -325,18 +334,18 @@ class iPodDevice(Device):
gtrack = track.libgpodtrack
if gtrack.playcount > 0:
if delete_from_db and not gtrack.rating:
logger.info('Deleting episode from db %s', gtrack.title, sender=self)
logger.info('Deleting episode from db %s', gtrack.title)
channel.delete_episode(episode)
else:
logger.info('Marking episode as played %s', gtrack.title, sender=self)
logger.info('Marking episode as played %s', gtrack.title)
def purge(self):
for track in gpod.sw_get_playlist_tracks(self.podcasts_playlist):
if gpod.itdb_filename_on_ipod(track) is None:
logger.info('Episode has no file: %s', track.title, sender=self)
logger.info('Episode has no file: %s', track.title)
# self.remove_track_gpod(track)
elif track.playcount > 0 and not track.rating:
logger.info('Purging episode: %s', track.title, sender=self)
logger.info('Purging episode: %s', track.title)
self.remove_track_gpod(track)
def get_all_tracks(self):
@ -346,7 +355,7 @@ class iPodDevice(Device):
if filename is None:
# This can happen if the episode is deleted on the device
logger.info('Episode has no file: %s', track.title, sender=self)
logger.info('Episode has no file: %s', track.title)
self.remove_track_gpod(track)
continue
@ -358,12 +367,17 @@ class iPodDevice(Device):
released = util.format_date(released)
except ValueError, ve:
# timestamp out of range for platform time_t (bug 418)
logger.info('Cannot convert track time: %s', ve, sender=self)
logger.info('Cannot convert track time: %s', ve)
released = 0
t = SyncTrack(track.title, length, modified, modified_sort=timestamp, libgpodtrack=track, playcount=track.playcount, released=released, podcast=track.artist)
t = SyncTrack(track.title, length, modified,
modified_sort=timestamp,
libgpodtrack=track,
playcount=track.playcount,
released=released,
podcast=track.artist)
tracks.append(t)
return tracks
return tracks
def remove_track(self, track):
self.notify('status', _('Removing %s') % track.title)
@ -375,7 +389,7 @@ class iPodDevice(Device):
try:
gpod.itdb_playlist_remove_track(self.podcasts_playlist, track)
except:
logger.info('Track %s not in playlist', track.title, sender=self)
logger.info('Track %s not in playlist', track.title)
gpod.itdb_track_unlink(track)
util.delete_file(filename)
@ -395,7 +409,7 @@ class iPodDevice(Device):
local_filename = original_filename
if util.calculate_size(original_filename) > self.get_free_space():
logger.error('Not enough space on %s, sync aborted...', self.mountpoint, sender = self)
logger.error('Not enough space on %s, sync aborted...', self.mountpoint)
d = {'episode': episode.title, 'mountpoint': self.mountpoint}
message =_('Error copying %(episode)s: Not enough free space on %(mountpoint)s')
self.errors.append(message % d)
@ -406,11 +420,11 @@ class iPodDevice(Device):
(fn, extension) = os.path.splitext(local_filename)
if extension.lower().endswith('ogg'):
logger.error('Cannot copy .ogg files to iPod.', sender=self)
logger.error('Cannot copy .ogg files to iPod.')
return False
track = gpod.itdb_track_new()
# Add release time to track if pubdate has a valid value
if episode.pubdate > 0:
try:
@ -422,7 +436,7 @@ class iPodDevice(Device):
#
# + 2082844800 for unixtime => mactime (1970 => 1904)
track.time_released = int(episode.pubdate + 2082844800)
track.title = str(episode.title)
track.album = str(episode.channel.title)
track.artist = str(episode.channel.title)
@ -459,7 +473,6 @@ class iPodDevice(Device):
def set_podcast_flags(self, track, episode):
try:
# Set several flags for to podcast values
track.remember_playback_position = 0x01
track.flag1 = 0x02
@ -467,17 +480,18 @@ class iPodDevice(Device):
track.flag3 = 0x01
track.flag4 = 0x01
except:
logger.warning('Seems like your python-gpod is out-of-date.', sender=self)
logger.warning('Seems like your python-gpod is out-of-date.')
class MP3PlayerDevice(Device):
def __init__(self, config,download_status_model,download_queue_manager):
def __init__(self, config,
download_status_model,
download_queue_manager):
Device.__init__(self, config)
self.destination = self._config.device_sync.device_folder
self.buffer_size = 1024*1024 # 1 MiB
self.download_status_model=download_status_model
self.download_queue_manager=download_queue_manager
self.download_status_model = download_status_model
self.download_queue_manager = download_queue_manager
def get_free_space(self):
return util.get_free_disk_space(self.destination)
@ -485,14 +499,13 @@ class MP3PlayerDevice(Device):
def open(self):
Device.open(self)
self.notify('status', _('Opening MP3 player'))
if util.directory_is_writable(self.destination):
self.notify('status', _('MP3 player opened'))
# build the initial tracks_list
self.tracks_list = self.get_all_tracks()
return True
else:
return False
return False
def add_track(self, episode,reporthook=None):
self.notify('status', _('Adding %s') % episode.title.decode('utf-8', 'ignore'))
@ -501,22 +514,24 @@ class MP3PlayerDevice(Device):
# Add channel title as subfolder
folder = episode.channel.title
# Clean up the folder name for use on limited devices
folder = util.sanitize_filename(folder, self._config.device_sync.max_filename_length)
folder = util.sanitize_filename(folder,
self._config.device_sync.max_filename_length)
folder = os.path.join(self.destination, folder)
else:
folder = self.destination
folder = util.sanitize_encoding(folder)
#filename = episode.local_filename(create=False)
filename = episode.local_filename(create=False)
# The file has to exist, if we ought to transfer it, and therefore,
# local_filename(create=False) must never return None as filename
assert episode.local_filename(create=False) is not None
assert filename is not None
from_file = util.sanitize_encoding(episode.local_filename(create=False))
filename_base = util.sanitize_filename(episode.sync_filename(self._config.device_sync.custom_sync_name_enabled,
self._config.device_sync.custom_sync_name),
self._config.device_sync.max_filename_length)
from_file = util.sanitize_encoding(filename)
filename_base = util.sanitize_filename(episode.sync_filename(
self._config.device_sync.custom_sync_name_enabled,
self._config.device_sync.custom_sync_name),
self._config.device_sync.max_filename_length)
to_file = filename_base + os.path.splitext(from_file)[1].lower()
@ -536,13 +551,14 @@ class MP3PlayerDevice(Device):
return False
if not os.path.exists(to_file):
logger.info('Copying %s => %s', os.path.basename(from_file), to_file.decode(util.encoding))
self.copy_file_progress(from_file, to_file,reporthook)
logger.info('Copying %s => %s',
os.path.basename(from_file),
to_file.decode(util.encoding))
self.copy_file_progress(from_file, to_file, reporthook)
return True
def copy_file_progress(self, from_file, to_file, reporthook=None):
try:
out_file = open(to_file, 'wb')
except IOError, ioerror:
@ -559,8 +575,8 @@ class MP3PlayerDevice(Device):
self.cancel()
return False
in_file.seek(0, 2)
bytes = in_file.tell()
in_file.seek(0, os.SEEK_END)
total_bytes = in_file.tell()
in_file.seek(0)
bytes_read = 0
@ -583,14 +599,13 @@ class MP3PlayerDevice(Device):
logger.error('Error while trying to unlink %s. OH MY!' % to_file)
self.cancel()
return False
#self.notify('sub-progress', int(min(100, 100*float(bytes_read)/float(bytes))))
reporthook(bytes_read, 1, bytes)
reporthook(bytes_read, 1, total_bytes)
s = in_file.read(self.buffer_size)
out_file.close()
in_file.close()
return True
def get_all_tracks(self):
tracks = []
@ -610,14 +625,18 @@ class MP3PlayerDevice(Device):
else:
podcast_name = None
t = SyncTrack(title, length, modified, modified_sort=timestamp, filename=filename, podcast=podcast_name)
t = SyncTrack(title, length, modified,
modified_sort=timestamp,
filename=filename,
podcast=podcast_name)
tracks.append(t)
return tracks
def episode_on_device(self, episode):
e = util.sanitize_filename(episode.sync_filename(self._config.device_sync.custom_sync_name_enabled,
self._config.device_sync.custom_sync_name),
self._config.device_sync.max_filename_length)
e = util.sanitize_filename(episode.sync_filename(
self._config.device_sync.custom_sync_name_enabled,
self._config.device_sync.custom_sync_name),
self._config.device_sync.max_filename_length)
return self._track_on_device(e)
def remove_track(self, track):
@ -667,7 +686,7 @@ class MTPDevice(Device):
d = time.gmtime(date)
return time.strftime("%Y%m%d-%H%M%S.0Z", d)
except Exception, exc:
logger.error('ERROR: An error has happend while trying to convert date to an mtp string (%s)')
logger.error('ERROR: An error has happend while trying to convert date to an mtp string')
return None
def __mtp_to_date(self, mtp):
@ -675,7 +694,6 @@ class MTPDevice(Device):
this parse the mtp's string representation for date
according to specifications (YYYYMMDDThhmmss.s) to
a python time object
"""
if not mtp:
return None
@ -772,7 +790,7 @@ class MTPDevice(Device):
needed = util.calculate_size(filename)
free = self.get_free_space()
if needed > free:
logger.error('Not enough space on device %s: %s available, but need at least %s', self.get_name(), util.format_filesize(free), util.format_filesize(needed), sender=self)
logger.error('Not enough space on device %s: %s available, but need at least %s', self.get_name(), util.format_filesize(free), util.format_filesize(needed))
self.cancelled = True
return False
@ -858,13 +876,11 @@ class MTPDevice(Device):
return self.__MTPDevice.get_freespace()
else:
return 0
class SyncCancelledException(Exception): pass
class SyncTask(DownloadTask):
#An object representing the synchronization task of an episode
class SyncTask(download.DownloadTask):
# An object representing the synchronization task of an episode
# Possible states this sync task can be in
STATUS_MESSAGE = (_('Added'), _('Queued'), _('Synchronizing'),
@ -884,14 +900,14 @@ class SyncTask(DownloadTask):
self.__status = status
status = property(fget=__get_status, fset=__set_status)
def __get_device(self):
return self.__device
def __set_device(self, device):
self.__device = device
device = property(fget=__get_device, fset=__set_device)
device = property(fget=__get_device, fset=__set_device)
def __get_status_changed(self):
if self.__status_changed:
@ -901,7 +917,7 @@ class SyncTask(DownloadTask):
return False
status_changed = property(fget=__get_status_changed)
def __get_activity(self):
return self.__activity
@ -910,15 +926,11 @@ class SyncTask(DownloadTask):
activity = property(fget=__get_activity, fset=__set_activity)
def __get_url(self):
return "Test Ep URL"
def __get_empty_string(self):
return ''
url = property(fget=__get_url)
def __get_podcast_url(self):
return "Test Channel URL"
podcast_url = property(fget=__get_podcast_url)
url = property(fget=__get_empty_string)
podcast_url = property(fget=__get_empty_string)
def __get_episode(self):
return self.__episode
@ -930,21 +942,19 @@ class SyncTask(DownloadTask):
self.status = self.CANCELLED
def removed_from_list(self):
#JOSEPH: really need to delete this
if self.status not in (self.DOWNLOADING, self.QUEUED, self.DONE):
util.delete_file(self.tempname)
# XXX: Should we delete temporary/incomplete files here?
pass
def __init__(self,episode):
def __init__(self, episode):
self.__status = SyncTask.INIT
self.__activity=SyncTask.ACTIVITY_SYNCHRONIZE
self.__activity = SyncTask.ACTIVITY_SYNCHRONIZE
self.__status_changed = True
self.__episode = episode
# Create the target filename and save it in the database
self.filename = self.__episode.local_filename(create=False)
self.tempname = self.filename + '.partial'
self.total_size = self.__episode.file_size
self.speed = 0.0
self.progress = 0.0
@ -962,7 +972,6 @@ class SyncTask(DownloadTask):
# Callbacks
self._progress_updated = lambda x: None
def notify_as_finished(self):
if self.status == SyncTask.DONE:
if self._notification_shown:
@ -993,7 +1002,7 @@ class SyncTask(DownloadTask):
self.total_size = float(totalSize)
if self.total_size > 0:
self.progress = max(0.0, min(1.0, float(count*blockSize)/self.total_size))
self.progress = max(0.0, min(1.0, float(count*blockSize)/self.total_size))
self._progress_updated(self.progress)
if self.status == SyncTask.CANCELLED:
@ -1002,7 +1011,6 @@ class SyncTask(DownloadTask):
if self.status == SyncTask.PAUSED:
raise SyncCancelledException()
def recycle(self):
self.episode.download_task = None
@ -1027,11 +1035,8 @@ class SyncTask(DownloadTask):
self._notification_shown = False
try:
logger.info("Starting SyncTask")
self.device.add_track(self.episode,reporthook=self.status_updated)
logger.info('Starting SyncTask')
self.device.add_track(self.episode, reporthook=self.status_updated)
except Exception, e:
self.status = SyncTask.FAILED
logger.error('Download failed: %s', str(e), exc_info=True)
@ -1046,7 +1051,7 @@ class SyncTask(DownloadTask):
self.progress = 1.0
gpodder.user_extensions.on_episode_downloaded(self.__episode)
return True
self.speed = 0.0
# We finished, but not successfully (at least not really)