From ae06548c9d4152ccf2876db90ec7e70fc21f6147 Mon Sep 17 00:00:00 2001
From: marcelveldt <m.vanderveldt@outlook.com>
Date: Wed, 20 Jan 2016 20:21:56 +0100
Subject: [PATCH] fix: redundant userdata update for music rating add settings
 for music ratings import/export

---
 contextmenu.py                 |   8 +-
 resources/lib/downloadutils.py |   1 +
 resources/lib/itemtypes.py     |  94 ++--------------------
 resources/lib/musicutils.py    | 141 +++++++++++++++++++++++++++++----
 resources/settings.xml         |   4 +
 5 files changed, 142 insertions(+), 106 deletions(-)

diff --git a/contextmenu.py b/contextmenu.py
index e7bee464..ab557b9f 100644
--- a/contextmenu.py
+++ b/contextmenu.py
@@ -112,9 +112,11 @@ if __name__ == '__main__':
                 if newvalue:
                     newvalue = int(newvalue)
                     if newvalue > 5: newvalue = "5"
-                    musicutils.updateRatingToFile(newvalue, API.getFilePath())
-                    like, favourite, deletelike = musicutils.getEmbyRatingFromKodiRating(newvalue)
-                    API.updateUserRating(embyid, like, favourite, deletelike)
+                    if utils.settings('enableUpdateSongRating') == "true":
+                        musicutils.updateRatingToFile(newvalue, API.getFilePath())
+                    if utils.settings('enableExportSongRating') == "true":
+                        like, favourite, deletelike = musicutils.getEmbyRatingFromKodiRating(newvalue)
+                        API.updateUserRating(embyid, like, favourite, deletelike)
                     query = ' '.join(( "UPDATE song","SET rating = ?", "WHERE idSong = ?" ))
                     kodicursor.execute(query, (newvalue,itemid,))
                     kodiconn.commit()
diff --git a/resources/lib/downloadutils.py b/resources/lib/downloadutils.py
index 1c244e74..578c932c 100644
--- a/resources/lib/downloadutils.py
+++ b/resources/lib/downloadutils.py
@@ -274,6 +274,7 @@ class DownloadUtils():
                                         verify=verifyssl)
 
                     elif type == "POST":
+                        print url
                         r = requests.post(url,
                                         json=postBody,
                                         headers=header,
diff --git a/resources/lib/itemtypes.py b/resources/lib/itemtypes.py
index 6bcdc2b2..7397b9f6 100644
--- a/resources/lib/itemtypes.py
+++ b/resources/lib/itemtypes.py
@@ -38,7 +38,7 @@ class Items(object):
         self.directpath = utils.settings('useDirectPaths') == "1"
         self.music_enabled = utils.settings('enableMusic') == "true"
         self.contentmsg = utils.settings('newContent') == "true"
-        
+
         self.artwork = artwork.Artwork()
         self.emby = embyserver.Read_EmbyServer()
         self.emby_db = embydb.Embydb_Functions(embycursor)
@@ -1613,6 +1613,9 @@ class Music(Items):
         Items.__init__(self, embycursor, musiccursor)
 
         self.directstream = utils.settings('streamMusic') == "true"
+        self.enableimportsongrating = utils.settings('enableImportSongRating') == "true"
+        self.enableexportsongrating = utils.settings('enableExportSongRating') == "true"
+        self.enableupdatesongrating = utils.settings('enableUpdateSongRating') == "true"
         self.userid = utils.window('emby_currUser')
         self.server = utils.window('emby_server%s' % self.userid)
 
@@ -1952,9 +1955,9 @@ class Music(Items):
         #the server doesn't support comment on songs so this will always be empty
         comment = API.getOverview()
         
-        #if enabled, try to get the rating and comment value from the file itself
+        #if enabled, try to get the rating from file and/or emby
         if not self.directstream:
-            rating, comment, hasEmbeddedCover = self.getSongTagsFromFile(itemid, rating, API)
+            rating, comment, hasEmbeddedCover = musicutils.getAdditionalSongTags(itemid, rating, API, kodicursor, emby_db, self.enableimportsongrating, self.enableexportsongrating, self.enableupdatesongrating)
         
         ##### GET THE FILE AND PATH #####
         if self.directstream:
@@ -2191,7 +2194,7 @@ class Music(Items):
             # Process playstates
             playcount = userdata['PlayCount']
             dateplayed = userdata['LastPlayedDate']
-            rating, comment, hasEmbeddedCover = self.getSongTagsFromFile(itemid, rating, API)
+            rating, comment, hasEmbeddedCover = musicutils.getAdditionalSongTags(itemid, rating, API, kodicursor, emby_db, self.enableimportsongrating, self.enableexportsongrating, self.enableupdatesongrating)
             
             query = "UPDATE song SET iTimesPlayed = ?, lastplayed = ?, rating = ? WHERE idSong = ?"
             kodicursor.execute(query, (playcount, dateplayed, rating, kodiid))
@@ -2203,89 +2206,6 @@ class Music(Items):
 
         emby_db.updateReference(itemid, checksum)
 
-    def getSongTagsFromFile(self, embyid, emby_rating, API):
-        
-        kodicursor = self.kodicursor
-
-        previous_values = None
-        filename = API.getFilePath()
-        rating = 0
-        emby_rating = int(round(emby_rating, 0))
-        
-        #get file rating and comment tag from file itself.
-        #TODO: should we make this an optional setting if it impacts sync speed too much ?
-        file_rating, comment, hasEmbeddedCover = musicutils.getSongTags(filename)
-
-        emby_dbitem = self.emby_db.getItem_byId(embyid)
-        try:
-            kodiid = emby_dbitem[0]
-        except TypeError:
-            # Item is not in database.
-            currentvalue = None
-        else:
-            query = "SELECT rating FROM song WHERE idSong = ?"
-            kodicursor.execute(query, (kodiid,))
-            try:
-                currentvalue = int(round(float(kodicursor.fetchone()[0]),0))
-            except: currentvalue = None
-        
-        # Only proceed if we actually have a rating from the file
-        if file_rating is None and currentvalue:
-            return (currentvalue, comment, False)
-        elif file_rating is None and not currentvalue:
-            return (emby_rating, comment, False)
-        
-        self.logMsg("getSongTagsFromFile --> embyid: %s - emby_rating: %s - file_rating: %s - current rating in kodidb: %s" %(embyid, emby_rating, file_rating, currentvalue))
-        
-        updateFileRating = False
-        updateEmbyRating = False
-
-        if currentvalue != None:
-            # we need to translate the emby values...
-            if emby_rating == 1 and currentvalue == 2:
-                emby_rating = 2
-            if emby_rating == 3 and currentvalue == 4:
-                emby_rating = 4
-                
-            if (emby_rating == file_rating) and (file_rating != currentvalue):
-                #the rating has been updated from kodi itself, update change to both emby ands file
-                rating = currentvalue
-                updateFileRating = True
-                updateEmbyRating = True
-            elif (emby_rating != currentvalue) and (file_rating == currentvalue):
-                #emby rating changed - update the file
-                rating = emby_rating
-                updateFileRating = True  
-            elif (file_rating != currentvalue) and (emby_rating == currentvalue):
-                #file rating was updated, sync change to emby
-                rating = file_rating
-                updateEmbyRating = True
-            elif (emby_rating != currentvalue) and (file_rating != currentvalue):
-                #both ratings have changed (corner case) - the highest rating wins...
-                if emby_rating > file_rating:
-                    rating = emby_rating
-                    updateFileRating = True
-                else:
-                    rating = file_rating
-                    updateEmbyRating = True
-            else:
-                #nothing has changed, just return the current value
-                rating = currentvalue
-        else:      
-            # no rating yet in DB, always prefer file details...
-            rating = file_rating
-            updateEmbyRating = True
-            
-        if updateFileRating:
-            musicutils.updateRatingToFile(rating, filename)
-            
-        if updateEmbyRating:
-            # sync details to emby server. Translation needed between ID3 rating and emby likes/favourites:
-            like, favourite, deletelike = musicutils.getEmbyRatingFromKodiRating(rating)
-            API.updateUserRating(embyid, like, favourite, deletelike)
-        
-        return (rating, comment, hasEmbeddedCover)
-        
     def remove(self, itemid):
         # Remove kodiid, fileid, pathid, emby reference
         emby_db = self.emby_db
diff --git a/resources/lib/musicutils.py b/resources/lib/musicutils.py
index 2625cc7f..915714ec 100644
--- a/resources/lib/musicutils.py
+++ b/resources/lib/musicutils.py
@@ -17,9 +17,9 @@ import base64
 def logMsg(msg, lvl=1):
     utils.logMsg("%s %s" % ("Emby", "musictools"), msg, lvl)
 
-def getRealFileName(filename):
+def getRealFileName(filename,useTemp = False):
     #get the filename path accessible by python if possible...
-    isTemp = False
+    useTemp = False
 
     if not xbmcvfs.exists(filename):
         logMsg( "File does not exist! %s" %(filename), 0)
@@ -38,14 +38,16 @@ def getRealFileName(filename):
         filename = filename.replace("smb://","\\\\").replace("/","\\")
     else:
         #file can not be accessed by python directly, we copy it for processing...
-        isTemp = True
+        useTemp = True
+    
+    if useTemp:
         if "/" in filename: filepart = filename.split("/")[-1]
         else: filepart = filename.split("\\")[-1]
         tempfile = "special://temp/"+filepart
         xbmcvfs.copy(filename, tempfile)
         filename = xbmc.translatePath(tempfile).decode("utf-8")
         
-    return (isTemp,filename)
+    return (useTemp,filename)
 
 def getEmbyRatingFromKodiRating(rating):
     # Translation needed between Kodi/ID3 rating and emby likes/favourites:
@@ -61,7 +63,112 @@ def getEmbyRatingFromKodiRating(rating):
     if (rating == 1 or rating == 2): deletelike = True
     if (rating >= 5): favourite = True
     return(like, favourite, deletelike)
+
+def getAdditionalSongTags(embyid, emby_rating, API, kodicursor, emby_db, enableimportsongrating, enableexportsongrating, enableupdatesongrating):
+    previous_values = None
+    filename = API.getFilePath()
+    rating = 0
+    emby_rating = int(round(emby_rating, 0))
     
+    #get file rating and comment tag from file itself.
+    if enableimportsongrating:
+        file_rating, comment, hasEmbeddedCover = getSongTags(filename)
+    else: 
+        file_rating = 0
+        comment = ""
+        hasEmbeddedCover = False
+        
+
+    emby_dbitem = emby_db.getItem_byId(embyid)
+    try:
+        kodiid = emby_dbitem[0]
+    except TypeError:
+        # Item is not in database.
+        currentvalue = None
+    else:
+        query = "SELECT rating FROM song WHERE idSong = ?"
+        kodicursor.execute(query, (kodiid,))
+        try:
+            currentvalue = int(round(float(kodicursor.fetchone()[0]),0))
+        except: currentvalue = None
+    
+    # Only proceed if we actually have a rating from the file
+    if file_rating is None and currentvalue:
+        return (currentvalue, comment, False)
+    elif file_rating is None and not currentvalue:
+        return (emby_rating, comment, False)
+    
+    logMsg("getAdditionalSongTags --> embyid: %s - emby_rating: %s - file_rating: %s - current rating in kodidb: %s" %(embyid, emby_rating, file_rating, currentvalue))
+    
+    updateFileRating = False
+    updateEmbyRating = False
+
+    if currentvalue != None:
+        # we need to translate the emby values...
+        if emby_rating == 1 and currentvalue == 2:
+            emby_rating = 2
+        if emby_rating == 3 and currentvalue == 4:
+            emby_rating = 4
+            
+        #if updating rating into file is disabled, we ignore the rating in the file...
+        if not enableupdatesongrating:
+            file_rating = currentvalue
+        #if convert emby likes/favourites convert to song rating is disabled, we ignore the emby rating...
+        if not enableexportsongrating:
+            emby_rating = currentvalue
+            
+        if (emby_rating == file_rating) and (file_rating != currentvalue):
+            #the rating has been updated from kodi itself, update change to both emby ands file
+            rating = currentvalue
+            updateFileRating = True
+            updateEmbyRating = True
+        elif (emby_rating != currentvalue) and (file_rating == currentvalue):
+            #emby rating changed - update the file
+            rating = emby_rating
+            updateFileRating = True  
+        elif (file_rating != currentvalue) and (emby_rating == currentvalue):
+            #file rating was updated, sync change to emby
+            rating = file_rating
+            updateEmbyRating = True
+        elif (emby_rating != currentvalue) and (file_rating != currentvalue):
+            #both ratings have changed (corner case) - the highest rating wins...
+            if emby_rating > file_rating:
+                rating = emby_rating
+                updateFileRating = True
+            else:
+                rating = file_rating
+                updateEmbyRating = True
+        else:
+            #nothing has changed, just return the current value
+            rating = currentvalue
+    else:      
+        # no rating yet in DB
+        if enableimportsongrating:
+            #prefer the file rating
+            rating = file_rating
+            #determine if we should also send the rating to emby server
+            if enableexportsongrating:
+                if emby_rating == 1 and file_rating == 2:
+                    emby_rating = 2
+                if emby_rating == 3 and file_rating == 4:
+                    emby_rating = 4
+                if emby_rating != file_rating:
+                    updateEmbyRating = True
+                
+        elif enableexportsongrating:
+            #set the initial rating to emby value
+            rating = emby_rating
+        
+    if updateFileRating and enableupdatesongrating:
+        updateRatingToFile(rating, filename)
+        
+    if updateEmbyRating and enableexportsongrating:
+        # sync details to emby server. Translation needed between ID3 rating and emby likes/favourites:
+        like, favourite, deletelike = getEmbyRatingFromKodiRating(rating)
+        API.updateUserRating(embyid, like, favourite, deletelike)
+    
+    return (rating, comment, hasEmbeddedCover)
+        
 def getSongTags(file):
     # Get the actual ID3 tags for music songs as the server is lacking that info
     rating = 0
@@ -120,31 +227,33 @@ def getSongTags(file):
 def updateRatingToFile(rating, file):
     #update the rating from Emby to the file
     
-    isTemp,filename = getRealFileName(file)
-    logMsg( "setting song rating: %s for filename: %s" %(rating,filename))
+    isTemp,tempfile = getRealFileName(file,True)
+    logMsg( "setting song rating: %s for filename: %s" %(rating,tempfile))
     
-    if not filename:
+    if not tempfile:
         return
     
     try:
-        if filename.lower().endswith(".flac"):
-            audio = FLAC(filename)
+        if tempfile.lower().endswith(".flac"):
+            audio = FLAC(tempfile)
             calcrating = int(round((float(rating) / 5) * 100, 0))
             audio["rating"] = str(calcrating)
             audio.save()
-        elif filename.lower().endswith(".mp3"):
-            audio = ID3(filename)
+        elif tempfile.lower().endswith(".mp3"):
+            audio = ID3(tempfile)
             calcrating = int(round((float(rating) / 5) * 255, 0))
             audio.add(id3.POPM(email="Windows Media Player 9 Series", rating=calcrating, count=1))
             audio.save()
         else:
-            logMsg( "Not supported fileformat: %s" %(filename))
+            logMsg( "Not supported fileformat: %s" %(tempfile))
             
-        #remove tempfile if needed....
-        if isTemp:
+        #once we have succesfully written the flags we move the temp file to destination, otherwise not proceeding and just delete the temp
+        #safety check: we check if we can read the new tags successfully from the tempfile before deleting anything
+        checksum_rating, checksum_comment, checksum_hasEmbeddedCover = getSongTags(tempfile)
+        if checksum_rating == rating:
             xbmcvfs.delete(file)
-            xbmcvfs.copy(filename,file)
-            xbmcvfs.delete(filename)
+            xbmcvfs.copy(tempfile,file)
+        xbmcvfs.delete(tempfile)
             
     except Exception as e:
         #file in use ?
diff --git a/resources/settings.xml b/resources/settings.xml
index e26d4564..6191e1db 100644
--- a/resources/settings.xml
+++ b/resources/settings.xml
@@ -60,6 +60,10 @@
 	    <setting id="ignoreSpecialsNextEpisodes" type="bool" label="Ignore specials in next episodes" default="false" />
 		<setting id="additionalUsers" type="text" label="Permanent users to add to the session" default="" />
 		<setting id="startupDelay" type="number" label="Startup Delay (seconds)" default="0" option="int" />
+        <setting type="sep" label="Music metadata options" />
+        <setting id="enableImportSongRating" type="bool" label="Import Music song rating directly from files" default="true" />
+        <setting id="enableExportSongRating" type="bool" label="Convert Music song rating to Emby (likes/favourites)" default="false" />
+        <setting id="enableUpdateSongRating" type="bool" label="Allow rating in song files to be updated by Kodi/Emby" default="false" />
 	</category>
 	
 	<category label="30022">