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:
parent
463c570b1e
commit
fb42e0b51b
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
@ -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()
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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())
|
||||
|
|
Loading…
Reference in New Issue