QML UI: Add support for gpodder.net sync (bug 1400)

This right now only syncs actions when an account is
already configured. The configuration UI will come only
on the Harmattan QML UI (it has Qt Quick Components).
This commit is contained in:
Thomas Perl 2011-11-02 10:41:38 +01:00
parent 463c570b1e
commit fb42e0b51b
4 changed files with 170 additions and 34 deletions

View File

@ -11,6 +11,16 @@ Item {
height: (Config.largeSpacing * 4) + (150 * Config.scale) + 110
property variant episode: undefined
property int startedFrom: 0
onStartedFromChanged: {
console.log('started from: ' + startedFrom)
}
function playedUntil(position) {
console.log('played until: ' + parseInt(position))
controller.storePlaybackAction(episode, startedFrom, position)
}
Connections {
target: mediaButtonsHandler
@ -48,22 +58,34 @@ Item {
if (status == 6 && seekLater) {
position = episode.qposition*1000
seekLater = false
mediaPlayer.startedFrom = position/1000
} else if (status == 7) {
mediaPlayer.playedUntil(audioPlayer.position/1000)
}
}
function setPosition(position) {
if (!playing) {
playing = true
} else {
mediaPlayer.playedUntil(audioPlayer.position/1000)
}
episode.qposition = position*episode.qduration
audioPlayer.position = position*episode.qduration*1000
mediaPlayer.startedFrom = audioPlayer.position/1000
}
}
function togglePlayback(episode) {
if (mediaPlayer.episode == episode) {
if (audioPlayer.paused) {
mediaPlayer.startedFrom = audioPlayer.position/1000
}
audioPlayer.paused = !audioPlayer.paused
if (audioPlayer.paused) {
mediaPlayer.playedUntil(audioPlayer.position/1000)
}
return
}

View File

@ -2269,40 +2269,11 @@ class gPodder(BuilderWidget, dbus.service.Object):
_('Episode actions from gpodder.net are merged.'), \
False, self.get_dialog_parent())
for idx, action in enumerate(self.mygpo_client.get_episode_actions()):
if action.action == 'play':
episode = self.find_episode(action.podcast_url, \
action.episode_url)
if episode is not None:
logger.debug('Play action for %s', episode.url)
episode.mark(is_played=True)
if action.timestamp > episode.current_position_updated and \
action.position is not None:
logger.debug('Updating position for %s', episode.url)
episode.current_position = action.position
episode.current_position_updated = action.timestamp
if action.total:
logger.debug('Updating total time for %s', episode.url)
episode.total_time = action.total
episode.save()
elif action.action == 'delete':
episode = self.find_episode(action.podcast_url, \
action.episode_url)
if episode is not None:
if not episode.was_downloaded(and_exists=True):
# Set the episode to a "deleted" state
logger.debug('Marking as deleted: %s', episode.url)
episode.delete_from_disk()
episode.save()
indicator.on_message(N_('%(count)d action processed', '%(count)d actions processed', idx) % {'count':idx})
while gtk.events_pending():
gtk.main_iteration(False)
self.mygpo_client.process_episode_actions(self.find_episode)
indicator.on_finished()
self.db.commit()

View File

@ -211,13 +211,60 @@ class MygPoClient(object):
self._store.remove(rewritten_urls)
return rewritten_urls
def get_episode_actions(self):
def process_episode_actions(self, find_episode, on_updated=None):
"""Process received episode actions
The parameter "find_episode" should be a function accepting
two parameters (podcast_url and episode_url). It will be used
to get an episode object that needs to be updated. It should
return None if the requested episode does not exist.
The optional callback "on_updated" should accept a single
parameter (the episode object) and will be called whenever
the episode data is changed in some way.
"""
logger.debug('Processing received episode actions...')
for action in self._store.load(ReceivedEpisodeAction):
yield action
if action.action not in ('play', 'delete'):
# Ignore all other action types for now
continue
episode = find_episode(action.podcast_url, action.episode_url)
if episode is None:
# The episode does not exist on this client
continue
if action.action == 'play':
logger.debug('Play action for %s', episode.url)
episode.mark(is_played=True)
if (action.timestamp > episode.current_position_updated and
action.position is not None):
logger.debug('Updating position for %s', episode.url)
episode.current_position = action.position
episode.current_position_updated = action.timestamp
if action.total:
logger.debug('Updating total time for %s', episode.url)
episode.total_time = action.total
episode.save()
if on_updated is not None:
on_updated(episode)
elif action.action == 'delete':
if not episode.was_downloaded(and_exists=True):
# Set the episode to a "deleted" state
logger.debug('Marking as deleted: %s', episode.url)
episode.delete_from_disk()
episode.save()
if on_updated is not None:
on_updated(episode)
# Remove all received episode actions
self._store.delete(ReceivedEpisodeAction)
self._store.commit()
logger.debug('Received episode actions processed.')
def get_received_actions(self):
"""Returns a list of ReceivedSubscribeAction objects

View File

@ -37,6 +37,7 @@ N_ = gpodder.ngettext
from gpodder import core
from gpodder import util
from gpodder import my
from gpodder.model import Model
@ -54,6 +55,19 @@ class Controller(QObject):
self.context_menu_actions = []
self.episode_list_title = u''
self.current_input_dialog = None
self.root.config.add_observer(self.on_config_changed)
def on_config_changed(self, name, old_value, new_value):
logger.info('Config changed: %s (%s -> %s)', name,
old_value, new_value)
if name == 'mygpo_enabled':
self.myGpoEnabledChanged.emit()
elif name == 'mygpo_username':
self.myGpoUsernameChanged.emit()
elif name == 'mygpo_password':
self.myGpoPasswordChanged.emit()
elif name == 'mygpo_device_caption':
self.myGpoDeviceCaptionChanged.emit()
episodeListTitleChanged = Signal()
@ -80,6 +94,15 @@ class Controller(QObject):
def loadLastEpisode(self):
self.root.load_last_episode()
@Slot(QObject, int, int)
def storePlaybackAction(self, episode, start, end):
if end - 5 < start:
logger.info('Ignoring too short playback action.')
return
total = episode.qduration
self.root.mygpo_client.on_playback_full(episode, start, end, total)
self.root.mygpo_client.flush()
@Slot(QObject)
def podcastSelected(self, podcast):
self.setEpisodeListTitle(podcast.qtitle)
@ -96,6 +119,56 @@ class Controller(QObject):
windowTitle = Property(unicode, getWindowTitle,
setWindowTitle, notify=windowTitleChanged)
@Slot()
def saveMyGpoSettings(self):
# Update the device settings and upload changes
self.root.mygpo_client.create_device()
self.root.mygpo_client.flush(now=True)
myGpoEnabledChanged = Signal()
def getMyGpoEnabled(self):
return self.root.config.mygpo_enabled
def setMyGpoEnabled(self, enabled):
self.root.config.mygpo_enabled = enabled
myGpoEnabled = Property(bool, getMyGpoEnabled,
setMyGpoEnabled, notify=myGpoEnabledChanged)
myGpoUsernameChanged = Signal()
def getMyGpoUsername(self):
return model.convert(self.root.config.mygpo_username)
def setMyGpoUsername(self, username):
self.root.config.mygpo_username = username
myGpoUsername = Property(unicode, getMyGpoUsername,
setMyGpoUsername, notify=myGpoUsernameChanged)
myGpoPasswordChanged = Signal()
def getMyGpoPassword(self):
return model.convert(self.root.config.mygpo_password)
def setMyGpoPassword(self, password):
self.root.config.mygpo_password = password
myGpoPassword = Property(unicode, getMyGpoPassword,
setMyGpoPassword, notify=myGpoPasswordChanged)
myGpoDeviceCaptionChanged = Signal()
def getMyGpoDeviceCaption(self):
return model.convert(self.root.config.mygpo_device_caption)
def setMyGpoDeviceCaption(self, caption):
self.root.config.mygpo_device_caption = caption
myGpoDeviceCaption = Property(unicode, getMyGpoDeviceCaption,
setMyGpoDeviceCaption, notify=myGpoDeviceCaptionChanged)
@Slot(QObject)
def podcastContextMenu(self, podcast):
menu = []
@ -128,6 +201,14 @@ class Controller(QObject):
if isinstance(podcast, model.EpisodeSubsetView):
podcast.qupdate()
def find_episode(self, podcast_url, episode_url):
for podcast in self.podcast_model.get_podcasts():
if podcast.url == podcast_url:
for episode in podcast.get_all_episodes():
if episode.url == episode_url:
return episode
return None
@Slot(int)
def contextMenuResponse(self, index):
assert index < len(self.context_menu_actions)
@ -138,6 +219,9 @@ class Controller(QObject):
action.target.qupdate(force=True, \
finished_callback=self.update_subset_stats)
elif action.action == 'update-all':
# Process episode actions received from gpodder.net
self.root.mygpo_client.process_episode_actions(self.find_episode)
for podcast in self.root.podcast_model.get_objects():
podcast.qupdate(finished_callback=self.update_subset_stats)
elif action.action == 'force-update-all':
@ -239,6 +323,8 @@ class Controller(QObject):
@Slot(QObject)
def downloadEpisode(self, episode):
episode.qdownload(self.root.config, self.update_subset_stats)
self.root.mygpo_client.on_download([episode])
self.root.mygpo_client.flush()
@Slot(QObject)
def cancelDownload(self, episode):
@ -250,6 +336,8 @@ class Controller(QObject):
def delete():
episode.delete_episode()
self.update_subset_stats()
self.root.mygpo_client.on_delete([episode])
self.root.mygpo_client.flush()
self.confirm_action(_('Delete this episode?'), _('Delete'), delete)
@ -418,6 +506,9 @@ class qtPodder(QObject):
self.db = self.core.db
self.model = self.core.model
# Initialize the gpodder.net client
self.mygpo_client = my.MygPoClient(self.config)
gpodder.user_hooks.on_ui_initialized(self.model,
self.hooks_podcast_update_cb,
self.hooks_episode_download_cb)
@ -512,6 +603,7 @@ class qtPodder(QObject):
def on_quit(self):
self.save_pending_data()
self.view.hide()
self.core.shutdown()
self.app.quit()
@ -548,9 +640,13 @@ class qtPodder(QObject):
def insert_podcast(self, podcast):
self.podcast_model.insert_object(podcast)
self.mygpo_client.on_subscribe([podcast.url])
self.mygpo_client.flush()
def remove_podcast(self, podcast):
self.podcast_model.remove_object(podcast)
self.mygpo_client.on_unsubscribe([podcast.url])
self.mygpo_client.flush()
def load_podcasts(self):
podcasts = map(model.QPodcast, self.model.get_podcasts())