示例#1
0
    def test_smartSyncDelAndShuffle(self):
        cfg.logger.info("Running test_smartSyncDelAndShuffle")
        shuffle(self.plPath)

        currentDir = getLocalSongs(self.plPath)

        #deletes a third (rounded down) of the songs in the playlist
        toDelete = []
        for _ in range(int(len(currentDir) / 3)):
            randSong = random.choice(currentDir)

            while randSong in toDelete:
                #ensures we dont try to delete the same song twice
                randSong = random.choice(currentDir)

            toDelete.append(randSong)

        for song in toDelete:
            os.remove(f'{self.plPath}/{song}')

        smartSync(self.plPath)

        with shelve.open(f"{self.plPath}/{cfg.metaDataName}",
                         'c',
                         writeback=True) as metaData:
            passed = metaDataMatches(metaData, self.plPath)

        self.assertTrue(passed)
示例#2
0
def metaDataSongsCorrect(metaData, plPath):
    '''
    tests if metadata ids corrispond to the correct remote songs.
    returns boolian test result and logs details

    test fails if local title does not match remote title
    manually added songs are ignored
    '''
    cfg.logger.info("Testing if Metadata IDs Corrispond to Correct Songs")

    currentDir = getLocalSongs(plPath)

    localIds = metaData['ids']
    localTitles = map(lambda title: re.sub(cfg.filePrependRE, '', title),
                      currentDir)

    for i, localTitle in enumerate(localTitles):
        localId = localIds[i]
        if localId != cfg.manualAddId:

            remoteTitle = getTitle(
                f"https://www.youtube.com/watch?v={localId}")
            if localTitle != remoteTitle:
                message = (f"{i}th Local Title:          {localTitle}\n"
                           f"Differes from Remote Title: {remoteTitle}\n"
                           f"With same Id:               {localId}")
                cfg.logger.error(message)
                return False
    cfg.logger.info("Test Passed")
    return True
示例#3
0
def getPlaylistData(name):
    '''used to validate playlist returns list of tups (id, song name)'''
    result = []
    songs = getLocalSongs(f"{cfg.testPlPath}/{name}")
    with shelve.open(f"{cfg.testPlPath}/{name}/{cfg.metaDataName}",
                     'c',
                     writeback=True) as metaData:
        for i, songId in enumerate(metaData['ids']):
            result.append((songId, songs[i]))

    return result
示例#4
0
def move(plPath, currentIndex, newIndex):
    if currentIndex==newIndex:
        cfg.logger.info("Indexes Are the Same")
        return


    correctStateCorruption(plPath)

    currentDir = getLocalSongs(plPath)


    with shelve.open(f"{plPath}/{cfg.metaDataName}", 'c',writeback=True) as metaData:

        idsLen = len(metaData["ids"])
        numDigits = getNumDigets(idsLen)


        if currentIndex>=idsLen:
            cfg.logger.error(f"No song has Index {currentIndex}, Largest Index is {idsLen-1}")
            return
        elif currentIndex<0:
            cfg.logger.error(f"No Song has a Negative Index")
            return

        #clamp newIndex
        if newIndex > idsLen-1:
            newIndex = idsLen-1
        elif newIndex < 0:
            newIndex = 0
        
        cfg.logger.info(f"Moving {currentDir[currentIndex]} to Index {newIndex}")
        
        #moves song to end of list
        tempName = relabel(metaData,cfg.logger.debug,plPath,currentDir[currentIndex],currentIndex,idsLen,numDigits)

        if currentIndex>newIndex:
            #shifts all songs from newIndex to currentIndex-1 by +1
            for i in reversed(range(newIndex,currentIndex)):
                
                oldName = currentDir[i]
                relabel(metaData,cfg.logger.debug,plPath,oldName,i,i+1,numDigits)
    
        
        else:
            #shifts all songs from currentIndex+1 to newIndex by -1
            for i in range(currentIndex+1,newIndex+1):
                oldName = currentDir[i]
                relabel(metaData,cfg.logger.debug,plPath,oldName,i,i-1,numDigits)
        
        #moves song back
        relabel(metaData,cfg.logger.debug,plPath,tempName,idsLen,newIndex,numDigits)
        del metaData['ids'][idsLen]
示例#5
0
def _removeGaps(plPath):
    currentDir = getLocalSongs(plPath)

    numDidgets = len(str(len(currentDir)))

    for i, oldName in enumerate(currentDir):
        newPrepend = f"{createNumLabel(i,numDidgets)}_"
        oldPrepend = re.search(cfg.filePrependRE, oldName).group(0)
        if oldPrepend != newPrepend:
            newName = re.sub(cfg.filePrependRE,
                             f"{createNumLabel(i,numDidgets)}_", oldName)
            cfg.logger.debug(f"Renaming {oldName} to {newName}")
            os.rename(f"{plPath}/{oldName}", f"{plPath}/{newName}")
            cfg.logger.debug("Renaming Complete")
示例#6
0
def editPlaylist(plPath, newOrder, deletions=False):
    '''
    metaData is json as defined in newPlaylist
    newOrder is an ordered list of tuples (Id of song, where to find it )
    the "where to find it" is the number in the old ordering (None if song is to be downloaded)

    note if song is in playlist already the id of song in newOrder will not be used
    '''

    currentDir = getLocalSongs(plPath)

    numDigets = len(
        str(2 * len(newOrder))
    )  #needed for creating starting number for auto ordering ie) 001, 0152
    # len is doubled because we will be first numbering with numbers above the
    # so state remains recoverable in event of crash

    with shelve.open(f"{plPath}/{cfg.metaDataName}", 'c',
                     writeback=True) as metaData:
        idsLen = len(metaData['ids'])
        cfg.logger.info(f"Editing Playlist...")
        cfg.logger.debug(f"Old Order: {metaData['ids']}")

        for i in range(len(newOrder)):
            newId, oldIndex = newOrder[i]

            newIndex = idsLen + i  # we reorder the playlist with exclusivly new numbers in case a crash occurs

            if oldIndex == None:
                # must download new song
                download(metaData, plPath, newId, newIndex, numDigets)

            else:
                #song exists locally, but must be reordered/renamed

                oldName = currentDir[oldIndex]

                relabel(metaData, cfg.logger.debug, plPath, oldName, oldIndex,
                        newIndex, numDigets)

    if deletions:
        oldIndices = [item[1] for item in newOrder]
        with shelve.open(f"{plPath}/{cfg.metaDataName}", 'c',
                         writeback=True) as metaData:
            for i in reversed(range(len(currentDir))):
                if i not in oldIndices:
                    delete(metaData, plPath, currentDir[i], i)

    _checkBlanks(plPath)
    _removeGaps(plPath)
示例#7
0
def metaDataMatches(metaData, plPath):
    '''
    metadata and local playlist must perfectly match remote playlist for this to return true
    '''
    cfg.logger.info("Testing If Metadata Perfectly Matches Remote Playlist")

    currentDir = getLocalSongs(plPath)

    localIds = metaData['ids']

    remoteIds, remoteTitles = getIdsAndTitles(metaData['url'])

    if len(localIds) != len(currentDir):
        cfg.logger.error(
            f"metadata ids and local playlist differ in length {len(localIds)} to {len(currentDir)}"
        )
        return False
    if len(localIds) != len(remoteIds):
        cfg.logger.error(
            f"local and remote playlists differ in length {len(localIds)} to {len(remoteIds)}"
        )
        return False

    for i, localTitle in enumerate(currentDir):
        localTitle, _ = os.path.splitext(localTitle)
        localTitle = re.sub(cfg.filePrependRE, '', localTitle)

        localId = localIds[i]
        remoteId = remoteIds[i]
        remoteTitle = remoteTitles[i]

        if localId != remoteId:
            message = (f"{i}th Local id:          {localId}\n"
                       f"With title:              {localTitle}\n"
                       f"Differes from Remote id: {remoteId}\n"
                       f"With title:              {remoteTitle}")
            cfg.logger.error(message)
            return False

        if localTitle != remoteTitle:
            message = (f"{i}th Local Title:          {localTitle}\n"
                       f"With Id:                    {localId}\n"
                       f"Differes from Remote Title: {remoteTitle}\n"
                       f"With Id:                    {localId}")
            cfg.logger.error(message)
            return False
    return True
示例#8
0
def manualAdd(plPath, songPath, posistion):
    '''put song in posistion in the playlist'''

    if not os.path.exists(songPath):
        cfg.logger.error(f'{songPath} Does Not Exist')
        return

    correctStateCorruption(plPath)

    currentDir = getLocalSongs(plPath)

    with shelve.open(f"{plPath}/{cfg.metaDataName}", 'c',writeback=True) as metaData:

        idsLen = len(metaData["ids"])
        numDigits = getNumDigets(idsLen)

        #clamp posistion
        if posistion > idsLen:
            posistion = idsLen
        elif posistion < 0:
            posistion = 0
        
        cfg.logger.info(f"Adding {ntpath.basename(songPath)} to {ntpath.basename(plPath)} in Posistion {posistion}")

        #shifting elements
        for i in reversed(range(posistion, idsLen)):
            oldName = currentDir[i]

            newName = re.sub(cfg.filePrependRE, f"{createNumLabel(i+1,numDigits)}_" , oldName)

            with noInterrupt:
                rename(metaData,cfg.logger.debug,plPath,oldName,newName,i+1,metaData["ids"][i])

                metaData["ids"][i] = '' #wiped in case of crash, this blank entries can be removed restoring state


        newSongName = f"{createNumLabel(posistion,numDigits)}_" + ntpath.basename(songPath)

        with noInterrupt:
            os.rename(songPath,f'{plPath}/{newSongName}')

            if posistion >= len(metaData["ids"]):
                metaData["ids"].append(cfg.manualAddId)
            else:
                metaData["ids"][posistion] = cfg.manualAddId
示例#9
0
def showPlaylist(plPath, lineBreak='', urlWithoutId = "https://www.youtube.com/watch?v="):
    '''
    printer can be print or some level of cfg.logger
    lineBreak can be set to newline if you wish to format for small screens
    urlWithoutId is added if you wish to print out all full urls
    '''
    with shelve.open(f"{plPath}/{cfg.metaDataName}", 'c',writeback=True) as metaData:
        cfg.logger.critical(f"Playlist URL: {metaData['url']}")

        currentDir = getLocalSongs(plPath)

        if urlWithoutId != None:
            spacer=' '*(len(urlWithoutId)+11)

            cfg.logger.critical(f"i: ID{spacer}{lineBreak}->   Local Title{lineBreak}")
            for i,songId in enumerate(metaData['ids']):
                url = f"{urlWithoutId}{songId}"
                cfg.logger.critical(f"{i}: {url}{lineBreak}  ->  {currentDir[i]}{lineBreak}")
示例#10
0
def swap(plPath, index1, index2):
    '''moves song to provided posistion, shifting all below it down'''
    if index1 == index2:
        cfg.logger.info(f"Given Index are the Same")


    correctStateCorruption(plPath)

    currentDir = getLocalSongs(plPath)



    with shelve.open(f"{plPath}/{cfg.metaDataName}", 'c',writeback=True) as metaData:

        idsLen = len(metaData["ids"])
        numDigits = getNumDigets(idsLen)

        if index1>=idsLen or index2>=idsLen:
            cfg.logger.error(f"Given Index is Larger than Max {idsLen-1}")
            return
        elif index1<0 or index2<0:
            cfg.logger.error(f"Given Index is Negative")
            return

        cfg.logger.info(f"Swapping {currentDir[index1]} and {currentDir[index2]}")
        #shift index1 out of the way (to idsLen)

        oldName = currentDir[index1]
        tempName = relabel(metaData,cfg.logger.debug,plPath,oldName,index1,idsLen,numDigits)

        
        #move index2 to index1's old location

        oldName = currentDir[index2]
        relabel(metaData,cfg.logger.debug,plPath,oldName,index2,index1,numDigits)

        #move index1 (now =idsLen) to index2's old location

        oldName = tempName
        relabel(metaData,cfg.logger.debug,plPath,oldName,idsLen,index2,numDigits)

        del metaData["ids"][idsLen]
示例#11
0
    def updateSongs(self, plPath):
        self.clear_widgets()
        localSongs = getLocalSongs(plPath)

        for i, song in enumerate(localSongs):
            self.add_widget(DragLabel(self, i, text=song))
示例#12
0
def moveRange(plPath, start, end, newStart):
    '''
    moves block of songs from start to end indices, to newStart
    ie) start = 4, end = 6, newStart = 2
    0 1 2 3 4 5 6 7 -> 0 1 4 5 6 2 3
    '''

    correctStateCorruption(plPath)

    if start == newStart:
        return

    currentDir = getLocalSongs(plPath)

    with shelve.open(f"{plPath}/{cfg.metaDataName}", 'c',writeback=True) as metaData:

        idsLen = len(metaData["ids"])
        numDigits = getNumDigets(idsLen)


        if start>=idsLen:
            cfg.logger.error(f"No song has Index {start}, Largest Index is {idsLen-1}")
            return

        elif start<0:
            cfg.logger.error(f"No Song has a Negative Index")
            return
            
        #clamp end index
        if end>=idsLen or end == -1:
            end = idsLen-1

        elif end<=start:
            cfg.logger.error("End Index Must be Greater Than Start Index (or -1)")
            return

        #clamp newStart
        if newStart > idsLen:
            newStart = idsLen
        elif newStart < -1:
            newStart = -1
        
        # Sanatization over

        # number of elements to move
        blockSize = end-start+1

        # make room for block
        for i in reversed(range(newStart+1,idsLen)):
            oldName = currentDir[i]
            newIndex =i+blockSize
            relabel(metaData,cfg.logger.debug,plPath,oldName,i,newIndex,numDigits)
        

        #accounts for block of songs being shifted if start>newStart
        offset = 0
        if start>newStart:
            currentDir = getLocalSongs(plPath)
            offset = blockSize
        
        # shift block into gap made
        for i,oldIndex in enumerate(range(start,end+1)):

            oldName = currentDir[oldIndex]
            newIndex = i + newStart+1
            relabel(metaData,cfg.logger.debug,plPath,oldName,oldIndex+offset,newIndex,numDigits)

    # remove number gap in playlist and remove blanks in metadata
    correctStateCorruption(plPath)
示例#13
0
def _checkDeletions(plPath):
    '''
    checks if metadata has songs that are no longer in directory
    '''
    currentDir = getLocalSongs(plPath)

    with shelve.open(f"{plPath}/{cfg.metaDataName}", 'c',
                     writeback=True) as metaData:

        idsLen = len(metaData['ids'])

        # there have been no deletions, however there may be a gap in numberings
        # which would be falsely detected as a deletion if this check wheren't here
        if idsLen == len(currentDir):
            return

        #song numbers in currentDir
        currentDirNums = [
            int(re.match(cfg.filePrependRE, song).group()[:-1])
            for song in currentDir
        ]

        numRange = range(idsLen)

        #difference between whats in the folder and whats in metadata
        deleted = [i for i in numRange if i not in currentDirNums]

        numDeleted = len(deleted)

        if numDeleted > 0:
            cfg.logger.debug(
                f"songs numbered {deleted} are no longer in playlist")

            numDidgets = len(str(len(metaData["ids"]) - numDeleted))
            newIndex = 0

            for newIndex, oldIndex in enumerate(currentDirNums):
                if newIndex != oldIndex:
                    oldName = currentDir[newIndex]
                    newName = re.sub(
                        cfg.filePrependRE,
                        f"{createNumLabel(newIndex,numDidgets)}_", oldName)

                    with noInterrupt:
                        cfg.logger.debug(f"Renaming {oldName} to {newName}")
                        os.rename(f"{plPath}/{oldName}", f"{plPath}/{newName}")

                        if newIndex in deleted:
                            # we only remove the deleted entry from metadata when its posistion has been filled
                            cfg.logger.debug(
                                f"Removing {metaData['ids'][newIndex]} from metadata"
                            )

                            #need to adjust for number already deleted
                            removedAlready = (numDeleted - len(deleted))
                            del metaData["ids"][newIndex - removedAlready]
                            del deleted[0]

                        cfg.logger.debug("Renaming Complete")

            while len(deleted) != 0:
                index = deleted[0]
                # we remove any remaining deleted entries from metadata
                # note even if the program crashed at this point, running this fuction
                # again would yeild an uncorrupted state
                removedAlready = (numDeleted - len(deleted))
                with noInterrupt:
                    cfg.logger.debug(
                        f"Removing {metaData['ids'][index - removedAlready]} from metadata"
                    )
                    del metaData["ids"][index - removedAlready]
                    del deleted[0]