Use file size and last modification time to compare Kodi playlist files instead of slow MD5 hash of file

This commit is contained in:
croneter 2019-08-01 13:56:50 +02:00
parent 92a7fa7c7a
commit 26fa1ff909
4 changed files with 26 additions and 25 deletions

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
@ -121,6 +123,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

@ -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:
@ -931,23 +930,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