Merge pull request #958 from croneter/beta-version

Bump master
This commit is contained in:
croneter 2019-08-03 10:44:35 +02:00 committed by GitHub
commit b5e13d0ab6
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
12 changed files with 119 additions and 63 deletions

View file

@ -1,5 +1,5 @@
[![stable version](https://img.shields.io/badge/stable_version-2.9.1-blue.svg?maxAge=60&style=flat) ](https://github.com/croneter/binary_repo/raw/master/stable/repository.plexkodiconnect/repository.plexkodiconnect-1.0.2.zip) [![stable version](https://img.shields.io/badge/stable_version-2.9.3-blue.svg?maxAge=60&style=flat) ](https://github.com/croneter/binary_repo/raw/master/stable/repository.plexkodiconnect/repository.plexkodiconnect-1.0.2.zip)
[![beta version](https://img.shields.io/badge/beta_version-2.9.1-red.svg?maxAge=60&style=flat) ](https://github.com/croneter/binary_repo/raw/master/beta/repository.plexkodiconnectbeta/repository.plexkodiconnectbeta-1.0.2.zip) [![beta version](https://img.shields.io/badge/beta_version-2.9.3-red.svg?maxAge=60&style=flat) ](https://github.com/croneter/binary_repo/raw/master/beta/repository.plexkodiconnectbeta/repository.plexkodiconnectbeta-1.0.2.zip)
[![Installation](https://img.shields.io/badge/wiki-installation-brightgreen.svg?maxAge=60&style=flat)](https://github.com/croneter/PlexKodiConnect/wiki/Installation) [![Installation](https://img.shields.io/badge/wiki-installation-brightgreen.svg?maxAge=60&style=flat)](https://github.com/croneter/PlexKodiConnect/wiki/Installation)
[![FAQ](https://img.shields.io/badge/wiki-FAQ-brightgreen.svg?maxAge=60&style=flat)](https://github.com/croneter/PlexKodiConnect/wiki/faq) [![FAQ](https://img.shields.io/badge/wiki-FAQ-brightgreen.svg?maxAge=60&style=flat)](https://github.com/croneter/PlexKodiConnect/wiki/faq)

View file

@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?> <?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<addon id="plugin.video.plexkodiconnect" name="PlexKodiConnect" version="2.9.1" provider-name="croneter"> <addon id="plugin.video.plexkodiconnect" name="PlexKodiConnect" version="2.9.3" provider-name="croneter">
<requires> <requires>
<import addon="xbmc.python" version="2.1.0"/> <import addon="xbmc.python" version="2.1.0"/>
<import addon="script.module.requests" version="2.9.1" /> <import addon="script.module.requests" version="2.9.1" />
@ -83,7 +83,21 @@
<summary lang="lt_LT">Natūralioji „Plex“ integracija į „Kodi“</summary> <summary lang="lt_LT">Natūralioji „Plex“ integracija į „Kodi“</summary>
<description lang="lt_LT">Prijunkite „Kodi“ prie „Plex Medija Serverio“. Šiame papildinyje daroma prielaida, kad valdote visus savo vaizdo įrašus naudodami „Plex“ (ir nė vieno su „Kodi“). Galite prarasti jau saugomus „Kodi“ vaizdo įrašų ir muzikos duomenų bazių duomenis (kadangi šis papildinys juos tiesiogiai pakeičia). Naudokite savo pačių rizika!</description> <description lang="lt_LT">Prijunkite „Kodi“ prie „Plex Medija Serverio“. Šiame papildinyje daroma prielaida, kad valdote visus savo vaizdo įrašus naudodami „Plex“ (ir nė vieno su „Kodi“). Galite prarasti jau saugomus „Kodi“ vaizdo įrašų ir muzikos duomenų bazių duomenis (kadangi šis papildinys juos tiesiogiai pakeičia). Naudokite savo pačių rizika!</description>
<disclaimer lang="lt_LT">Naudokite savo pačių rizika</disclaimer> <disclaimer lang="lt_LT">Naudokite savo pačių rizika</disclaimer>
<news>version 2.9.1: <news>version 2.9.3:
- version 2.9.2 for everyone
version 2.9.2 (beta only):
- Fix Plex Companion casting from iOS and Android
- Faster sync of playlists
- Sync playlists immediately after synching new/changed items and show an info dialog
- Fix potential playlist sync issues if there is a dot in the playlist name
- Correctly detect whether we already synched a Kodi playlist
- Remove obsolete check if path is indeed in unicode
- Add unicode representation to Playlist() class
- Separate function to wipe all synched Plex playlists
- Less logging when comparing PKC versions
version 2.9.1:
- Fix On Deck and Recently Added Episodes for shows not appending showname and season and episode number - Fix On Deck and Recently Added Episodes for shows not appending showname and season and episode number
version 2.9.0: version 2.9.0:

View file

@ -1,3 +1,17 @@
version 2.9.3:
- version 2.9.2 for everyone
version 2.9.2 (beta only):
- Fix Plex Companion casting from iOS and Android
- Faster sync of playlists
- Sync playlists immediately after synching new/changed items and show an info dialog
- Fix potential playlist sync issues if there is a dot in the playlist name
- Correctly detect whether we already synched a Kodi playlist
- Remove obsolete check if path is indeed in unicode
- Add unicode representation to Playlist() class
- Separate function to wipe all synched Plex playlists
- Less logging when comparing PKC versions
version 2.9.1: version 2.9.1:
- Fix On Deck and Recently Added Episodes for shows not appending showname and season and episode number - Fix On Deck and Recently Added Episodes for shows not appending showname and season and episode number

View file

@ -334,15 +334,27 @@ class FullSync(common.fullsync_mixin):
plexdb.update_section_last_sync(section.section_id, plexdb.update_section_last_sync(section.section_id,
self.current_sync) self.current_sync)
common.update_kodi_library(video=True, music=True) common.update_kodi_library(video=True, music=True)
# Sync Plex playlists to Kodi and vice-versa
if common.PLAYLIST_SYNC_ENABLED:
if self.show_dialog:
if self.dialog:
self.dialog.close()
self.dialog = xbmcgui.DialogProgressBG()
# "Synching playlists"
self.dialog.create(utils.lang(39715))
if not playlists.full_sync():
return False
# SYNC PLAYSTATE of ALL items (otherwise we won't pick up on items that
# were set to unwatched). Also mark all items on the PMS to be able
# to delete the ones still in Kodi
LOG.info('Start synching playstate and userdata for every item')
# In order to not delete all your songs again # In order to not delete all your songs again
if app.SYNC.enable_music: if app.SYNC.enable_music:
kinds.extend([ kinds.extend([
(v.PLEX_TYPE_SONG, v.PLEX_TYPE_ARTIST, itemtypes.Song, True), (v.PLEX_TYPE_SONG, v.PLEX_TYPE_ARTIST, itemtypes.Song, True),
]) ])
# SYNC PLAYSTATE of ALL items (otherwise we won't pick up on items that
# were set to unwatched). Also mark all items on the PMS to be able
# to delete the ones still in Kodi
LOG.info('Start synching playstate and userdata for every item')
# Make sure we're not showing an item's title in the sync dialog # Make sure we're not showing an item's title in the sync dialog
self.title = '' self.title = ''
self.threader.shutdown() self.threader.shutdown()
@ -428,13 +440,6 @@ class FullSync(common.fullsync_mixin):
if self.isCanceled() or not self.full_library_sync(): if self.isCanceled() or not self.full_library_sync():
self.successful = False self.successful = False
return return
if common.PLAYLIST_SYNC_ENABLED:
if self.dialog:
self.dialog.close()
self.dialog = xbmcgui.DialogProgressBG()
self.dialog.create(utils.lang(39715))
if not playlists.full_sync():
self.successful = False
finally: finally:
common.update_kodi_library(video=True, music=True) common.update_kodi_library(video=True, music=True)
if self.dialog: if self.dialog:

View file

@ -57,4 +57,9 @@ def check_migration():
sections.clear_window_vars() sections.clear_window_vars()
sections.delete_videonode_files() sections.delete_videonode_files()
if not utils.compare_version(last_migration, '2.9.3'):
LOG.info('Migrating to version 2.9.2')
# Re-sync all playlists to Kodi
utils.wipe_synched_playlists()
utils.settings('last_migrated_PKC_version', value=v.ADDON_VERSION) utils.settings('last_migrated_PKC_version', value=v.ADDON_VERSION)

View file

@ -14,7 +14,8 @@
from __future__ import absolute_import, division, unicode_literals from __future__ import absolute_import, division, unicode_literals
from logging import getLogger from logging import getLogger
from .common import Playlist, PlaylistError, PlaylistObserver from .common import Playlist, PlaylistError, PlaylistObserver, \
kodi_playlist_hash
from . import pms, db, kodi_pl, plex_pl from . import pms, db, kodi_pl, plex_pl
from ..watchdog import events from ..watchdog import events
@ -221,7 +222,7 @@ def _full_sync():
pass pass
if not sync_kodi_playlist(path): if not sync_kodi_playlist(path):
continue continue
kodi_hash = utils.generate_file_md5(path) kodi_hash = kodi_playlist_hash(path)
playlist = db.get_playlist(path=path) playlist = db.get_playlist(path=path)
if playlist and playlist.kodi_hash == kodi_hash: if playlist and playlist.kodi_hash == kodi_hash:
continue continue
@ -387,7 +388,7 @@ class PlaylistEventhandler(events.FileSystemEventHandler):
def on_created(self, event): def on_created(self, event):
LOG.debug('on_created: %s', event.src_path) LOG.debug('on_created: %s', event.src_path)
old_playlist = db.get_playlist(path=event.src_path) old_playlist = db.get_playlist(path=event.src_path)
kodi_hash = utils.generate_file_md5(event.src_path) kodi_hash = kodi_playlist_hash(event.src_path)
if old_playlist and old_playlist.kodi_hash == kodi_hash: if old_playlist and old_playlist.kodi_hash == kodi_hash:
LOG.debug('Playlist already in DB - skipping') LOG.debug('Playlist already in DB - skipping')
return return
@ -406,7 +407,7 @@ class PlaylistEventhandler(events.FileSystemEventHandler):
def on_modified(self, event): def on_modified(self, event):
LOG.debug('on_modified: %s', event.src_path) LOG.debug('on_modified: %s', event.src_path)
old_playlist = db.get_playlist(path=event.src_path) old_playlist = db.get_playlist(path=event.src_path)
kodi_hash = utils.generate_file_md5(event.src_path) kodi_hash = kodi_playlist_hash(event.src_path)
if old_playlist and old_playlist.kodi_hash == kodi_hash: if old_playlist and old_playlist.kodi_hash == kodi_hash:
LOG.debug('Nothing modified, playlist already in DB - skipping') LOG.debug('Nothing modified, playlist already in DB - skipping')
return return
@ -425,7 +426,7 @@ class PlaylistEventhandler(events.FileSystemEventHandler):
def on_moved(self, event): def on_moved(self, event):
LOG.debug('on_moved: %s to %s', event.src_path, event.dest_path) LOG.debug('on_moved: %s to %s', event.src_path, event.dest_path)
kodi_hash = utils.generate_file_md5(event.dest_path) kodi_hash = kodi_playlist_hash(event.dest_path)
# First check whether we don't already have destination playlist in # First check whether we don't already have destination playlist in
# our DB. Just in case.... # our DB. Just in case....
old_playlist = db.get_playlist(path=event.dest_path) old_playlist = db.get_playlist(path=event.dest_path)

View file

@ -4,6 +4,8 @@ from __future__ import absolute_import, division, unicode_literals
from logging import getLogger from logging import getLogger
import Queue import Queue
import time import time
import os
import hashlib
from ..watchdog import events from ..watchdog import events
from ..watchdog.observers import Observer from ..watchdog.observers import Observer
@ -63,7 +65,7 @@ class Playlist(object):
self.kodi_type = None self.kodi_type = None
self.kodi_hash = None self.kodi_hash = None
def __repr__(self): def __unicode__(self):
return ("{{" return ("{{"
"'plex_id': {self.plex_id}, " "'plex_id': {self.plex_id}, "
"'plex_name': '{self.plex_name}', " "'plex_name': '{self.plex_name}', "
@ -74,6 +76,9 @@ class Playlist(object):
"'kodi_hash': '{self.kodi_hash}'" "'kodi_hash': '{self.kodi_hash}'"
"}}").format(self=self) "}}").format(self=self)
def __repr__(self):
return self.__unicode__().encode('utf-8')
def __str__(self): def __str__(self):
return self.__repr__() return self.__repr__()
@ -101,11 +106,9 @@ class Playlist(object):
@kodi_path.setter @kodi_path.setter
def kodi_path(self, path): def kodi_path(self, path):
if not isinstance(path, unicode):
raise RuntimeError('Path not in unicode: %s' % path)
f = path_ops.path.basename(path) f = path_ops.path.basename(path)
try: try:
self.kodi_filename, self.kodi_extension = f.split('.', 1) self.kodi_filename, self.kodi_extension = f.rsplit('.', 1)
except ValueError: except ValueError:
LOG.error('Trying to set invalid path: %s', path) LOG.error('Trying to set invalid path: %s', path)
raise PlaylistError('Invalid path: %s' % path) raise PlaylistError('Invalid path: %s' % path)
@ -121,6 +124,22 @@ class Playlist(object):
self._kodi_path = path self._kodi_path = path
def kodi_playlist_hash(path):
"""
Returns a md5 hash [unicode] using os.stat() st_size and st_mtime for the
playlist located at path [unicode]
(size of file in bytes and time of most recent content modification)
There are probably way more efficient ways out there to do this
"""
stat = os.stat(path_ops.encode_path(path))
# stat.st_size is of type long; stat.st_mtime is of type float - hash both
m = hashlib.md5()
m.update(repr(stat.st_size))
m.update(repr(stat.st_mtime))
return m.hexdigest().decode('utf-8')
class PlaylistQueue(OrderedSetQueue): class PlaylistQueue(OrderedSetQueue):
""" """
OrderedSetQueue that drops all directory events immediately OrderedSetQueue that drops all directory events immediately

View file

@ -7,7 +7,7 @@ from __future__ import absolute_import, division, unicode_literals
from logging import getLogger from logging import getLogger
import re import re
from .common import Playlist, PlaylistError from .common import Playlist, PlaylistError, kodi_playlist_hash
from . import db, pms from . import db, pms
from ..plex_api import API from ..plex_api import API
@ -71,7 +71,7 @@ def create(plex_id):
except Exception: except Exception:
IGNORE_KODI_PLAYLIST_CHANGE.remove(playlist.kodi_path) IGNORE_KODI_PLAYLIST_CHANGE.remove(playlist.kodi_path)
raise raise
playlist.kodi_hash = utils.generate_file_md5(path) playlist.kodi_hash = kodi_playlist_hash(path)
db.update_playlist(playlist) db.update_playlist(playlist)
LOG.debug('Created Kodi playlist based on Plex playlist: %s', playlist) LOG.debug('Created Kodi playlist based on Plex playlist: %s', playlist)

View file

@ -30,8 +30,8 @@ def create(playlist):
if not plex_ids: if not plex_ids:
LOG.warning('No Plex ids found for playlist %s', playlist) LOG.warning('No Plex ids found for playlist %s', playlist)
raise PlaylistError raise PlaylistError
IGNORE_PLEX_PLAYLIST_CHANGE.append(playlist.plex_id)
pms.add_items(playlist, plex_ids) pms.add_items(playlist, plex_ids)
IGNORE_PLEX_PLAYLIST_CHANGE.append(playlist.plex_id)
db.update_playlist(playlist) db.update_playlist(playlist)
LOG.debug('Done creating Plex playlist %s', playlist) LOG.debug('Done creating Plex playlist %s', playlist)

View file

@ -167,7 +167,7 @@ class PlexCompanion(backgroundthread.KillableThread):
repeat=query.get('repeat'), repeat=query.get('repeat'),
offset=data.get('offset'), offset=data.get('offset'),
transient_token=data.get('token'), transient_token=data.get('token'),
key=key) start_plex_id=key)
@staticmethod @staticmethod
def _process_streams(data): def _process_streams(data):

View file

@ -311,11 +311,15 @@ def initialize():
plexdb.cursor.execute(cmd) plexdb.cursor.execute(cmd)
def wipe(): def wipe(table=None):
""" """
Completely resets the Plex database Completely resets the Plex database.
If a table [unicode] name is provided, only that table will be dropped
""" """
with PlexDBBase() as plexdb: with PlexDBBase() as plexdb:
if table:
tables = [table]
else:
plexdb.cursor.execute("SELECT name FROM sqlite_master WHERE type = 'table'") plexdb.cursor.execute("SELECT name FROM sqlite_master WHERE type = 'table'")
tables = [i[0] for i in plexdb.cursor.fetchall()] tables = [i[0] for i in plexdb.cursor.fetchall()]
for table in tables: for table in tables:

View file

@ -17,7 +17,6 @@ import xml.etree.ElementTree as etree
from . import defused_etree from . import defused_etree
from xml.etree.ElementTree import ParseError from xml.etree.ElementTree import ParseError
from functools import wraps from functools import wraps
import hashlib
import re import re
import gc import gc
try: try:
@ -539,6 +538,29 @@ def create_kodi_db_indicees():
conn.close() conn.close()
def wipe_synched_playlists():
"""
Deletes all synched playlist files on the Kodi side; resets the Plex table
listing all synched Plex playlists
"""
from . import plex_db
try:
with plex_db.PlexDB() as plexdb:
plexdb.cursor.execute('SELECT kodi_path FROM playlists')
playlist_paths = [x[0] for x in plexdb.cursor]
except OperationalError:
# Plex DB completely empty yet
playlist_paths = []
for path in playlist_paths:
try:
path_ops.remove(path)
LOG.info('Removed playlist %s', path)
except (OSError, IOError):
LOG.warn('Could not remove playlist %s', path)
# Now wipe our database
plex_db.wipe(table='playlists')
def wipe_database(reboot=True): def wipe_database(reboot=True):
""" """
Deletes all Plex playlists as well as video nodes, then clears Kodi as well Deletes all Plex playlists as well as video nodes, then clears Kodi as well
@ -550,8 +572,8 @@ def wipe_database(reboot=True):
from . import kodi_db, plex_db from . import kodi_db, plex_db
# Clean up the playlists and video nodes # Clean up the playlists and video nodes
delete_files() delete_files()
# First get the paths to all synced playlists # Wipe all synched playlists
playlist_paths = [] wipe_synched_playlists()
try: try:
with plex_db.PlexDB() as plexdb: with plex_db.PlexDB() as plexdb:
if plexdb.songs_have_been_synced(): if plexdb.songs_have_been_synced():
@ -560,22 +582,12 @@ def wipe_database(reboot=True):
else: else:
LOG.info('No music has been synced in the past - not wiping') LOG.info('No music has been synced in the past - not wiping')
music = False music = False
plexdb.cursor.execute('SELECT kodi_path FROM playlists')
for entry in plexdb.cursor:
playlist_paths.append(entry[0])
except OperationalError: except OperationalError:
# Plex DB completely empty yet. Wipe existing Kodi music only if we # Plex DB completely empty yet. Wipe existing Kodi music only if we
# expect to sync Plex music # expect to sync Plex music
music = settings('enableMusic') == 'true' music = settings('enableMusic') == 'true'
kodi_db.wipe_dbs(music) kodi_db.wipe_dbs(music)
plex_db.wipe() plex_db.wipe()
# Delete all synced playlists
for path in playlist_paths:
try:
path_ops.remove(path)
LOG.debug('Removed playlist %s', path)
except (OSError, IOError):
LOG.warn('Could not remove playlist %s', path)
LOG.info("Resetting all cached artwork.") LOG.info("Resetting all cached artwork.")
# Remove all cached artwork # Remove all cached artwork
@ -639,7 +651,6 @@ def compare_version(current, minimum):
Input strings: e.g. "1.2.3"; always with Major, Minor and Patch! Input strings: e.g. "1.2.3"; always with Major, Minor and Patch!
""" """
LOG.info("current DB: %s minimum DB: %s", current, minimum)
try: try:
curr_major, curr_minor, curr_patch = current.split(".") curr_major, curr_minor, curr_patch = current.split(".")
except ValueError: except ValueError:
@ -931,23 +942,6 @@ class XmlKodiSetting(object):
return element return element
def generate_file_md5(path):
"""
Generates the md5 hash value for the file located at path [unicode].
The hash does not include the path and filename and is thus identical for
a file that was moved/changed name.
Returns a unique unicode containing only hexadecimal digits
"""
m = hashlib.md5()
with open(path_ops.encode_path(path), 'rb') as f:
while True:
piece = f.read(32768)
if not piece:
break
m.update(piece)
return m.hexdigest().decode('utf-8')
def process_method_on_list(method_to_run, items): def process_method_on_list(method_to_run, items):
""" """
helper method that processes a method on each item with pooling if the helper method that processes a method on each item with pooling if the