Skip to content

Commit 3802582

Browse files
committed
Initial code for watched subfolders
1 parent db95264 commit 3802582

19 files changed

+515
-23
lines changed

syncplay/client.py

Lines changed: 122 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,9 @@ def __init__(self, playerClass, ui, config):
7373
constants.FOLDER_SEARCH_TIMEOUT = config['folderSearchTimeout']
7474
constants.FOLDER_SEARCH_DOUBLE_CHECK_INTERVAL = config['folderSearchDoubleCheckInterval']
7575
constants.FOLDER_SEARCH_WARNING_THRESHOLD = config['folderSearchWarningThreshold']
76+
constants.WATCHED_SUBFOLDER = config['watchedSubfolder']
77+
constants.WATCHED_AUTOMOVE = config['watchedAutoMove'] if len(constants.WATCHED_SUBFOLDER) > 0 else False
78+
constants.WATCHED_AUTOCREATESUBFOLDERS = config['watchedSubfolderAutocreate'] if len(constants.WATCHED_SUBFOLDER) > 0 else False
7679

7780
self.controlpasswords = {}
7881
self.lastControlPasswordAttempt = None
@@ -81,7 +84,9 @@ def __init__(self, playerClass, ui, config):
8184
self.serverFeatures = {}
8285

8386
self.lastRewindTime = None
87+
self._pendingWatchedMoves = []
8488
self.lastUpdatedFileTime = None
89+
self._lastWatchedMoveAttempt = 0.0 #Secs
8590
self.lastAdvanceTime = None
8691
self.fileOpenBeforeChangingPlaylistIndex = None
8792
self.waitingToLoadNewfile = False
@@ -248,6 +253,12 @@ def updatePlayerStatus(self, paused, position):
248253
):
249254
pauseChange = self._toggleReady(pauseChange, paused)
250255

256+
if self._pendingWatchedMoves and (time.time() - (self._lastWatchedMoveAttempt or 0.0)) >= constants.WATCHED_CHECKQUEUE_INTERVAL:
257+
self._lastWatchedMoveAttempt = time.time()
258+
try:
259+
self._tryMovePendingWatchedFiles()
260+
except Exception:
261+
pass
251262
if self._lastGlobalUpdate:
252263
self._lastPlayerUpdate = time.time()
253264
if (pauseChange or seeked) and self._protocol:
@@ -263,6 +274,13 @@ def prepareToChangeToNewPlaylistItemAndRewind(self):
263274
self.fileOpenBeforeChangingPlaylistIndex = self.userlist.currentUser.file["path"] if self.userlist.currentUser.file else None
264275
self.waitingToLoadNewfile = True
265276
self.waitingToLoadNewfileSince = time.time()
277+
position = self.getStoredPlayerPosition()
278+
currentLength = self.userlist.currentUser.file["duration"] if self.userlist.currentUser.file else 0
279+
if (
280+
currentLength > constants.PLAYLIST_LOAD_NEXT_FILE_MINIMUM_LENGTH and
281+
abs(position - currentLength) < constants.PLAYLIST_LOAD_NEXT_FILE_TIME_FROM_END_THRESHOLD
282+
):
283+
self.markWatchedFilePendingMove()
266284

267285
def prepareToAdvancePlaylist(self):
268286
if self.playlist.canSwitchToNextPlaylistIndex():
@@ -516,6 +534,88 @@ def getGlobalPaused(self):
516534
return True
517535
return self._globalPaused
518536

537+
def markWatchedFilePendingMove(self):
538+
try:
539+
currentFile = self.userlist.currentUser.file if self.userlist and self.userlist.currentUser else None
540+
currentFilePath = currentFile.get("path") if currentFile else None
541+
if currentFilePath and not utils.isURL(currentFilePath) and currentFilePath not in self._pendingWatchedMoves:
542+
self._pendingWatchedMoves.append(currentFilePath)
543+
self.ui.showDebugMessage("Marked for watched move: {}".format(currentFilePath))
544+
except Exception as e:
545+
self.ui.showDebugMessage("Could not mark watched file: {}".format(e))
546+
547+
def userInitiatedMarkWatched(self, fileSourcePath):
548+
try:
549+
directory = os.path.dirname(fileSourcePath)
550+
filename = os.path.basename(fileSourcePath)
551+
watchedDirectory = utils.getWatchedSubfolder(directory)
552+
utils.createWatchedSubdirIfNeeded(watchedDirectory)
553+
if not os.path.isdir(watchedDirectory):
554+
self.ui.showErrorMessage("'{}' subfolder not found for this file.".format(constants.WATCHED_SUBFOLDER)) # TODO: Move to Language
555+
return
556+
watchedDirectoryFilepath = os.path.join(watchedDirectory, filename)
557+
watchedDirectoryName = os.path.basename(os.path.dirname(watchedDirectoryFilepath))
558+
if os.path.isfile(watchedDirectoryFilepath):
559+
self.ui.showErrorMessage("A file with the same name already exists in '{}' subfolder.".format(watchedDirectoryName)) # TODO: Move to Language
560+
return
561+
utils.moveFile(fileSourcePath, watchedDirectoryFilepath)
562+
self.fileSwitch.updateInfo()
563+
self.ui.showMessage("Moved file '{}' to '\{}\\'".format(fileSourcePath, watchedDirectoryName))
564+
except Exception as e:
565+
self.ui.showErrorMessage("Could not mark as watched: {}".format(e)) # TODO: Move to language
566+
567+
def userInitiatedMarkUnwatched(self, fileSourcePath):
568+
try:
569+
watchedDirectoryPath = os.path.dirname(fileSourcePath)
570+
filename = os.path.basename(fileSourcePath)
571+
if not utils.isWatchedSubfolder(watchedDirectoryPath):
572+
self.ui.showErrorMessage("This file is not in a '{}' subfolder.".format(constants.WATCHED_SUBFOLDER))
573+
return
574+
unwatchedDirectoryPath = utils.getUnwatchedParentfolder(watchedDirectoryPath)
575+
unwatchedDirectoryPathName = os.path.basename(unwatchedDirectoryPath)
576+
unwatchedFilePath = os.path.join(unwatchedDirectoryPath, filename)
577+
if os.path.isfile(unwatchedFilePath):
578+
self.ui.showErrorMessage("A file with the same name already exists in the parent folder.")
579+
return
580+
utils.moveFile(fileSourcePath, unwatchedFilePath)
581+
self.fileSwitch.updateInfo()
582+
self.ui.showMessage("Moved file '{}' to '\{}\\'".format(fileSourcePath, unwatchedDirectoryPathName)) # TODO: Move to Language
583+
except Exception as e:
584+
self.ui.showErrorMessage("Could not mark as unwatched: {}".format(e)) # TODO: Move to Language
585+
586+
def _tryMovePendingWatchedFiles(self):
587+
if not constants.WATCHED_AUTOMOVE:
588+
self._pendingWatchedMoves = []
589+
return
590+
591+
if not self._pendingWatchedMoves:
592+
return
593+
594+
for pendingWatchedMove in list(self._pendingWatchedMoves):
595+
try:
596+
if not os.path.exists(pendingWatchedMove):
597+
self._pendingWatchedMoves.remove(pendingWatchedMove)
598+
continue
599+
originalDir = os.path.dirname(pendingWatchedMove)
600+
watchedDir = utils.getWatchedSubfolder(originalDir)
601+
utils.createWatchedSubdirIfNeeded(watchedDir)
602+
if not os.path.isdir(watchedDir):
603+
self._pendingWatchedMoves.remove(pendingWatchedMove)
604+
continue
605+
destFilepath = os.path.join(watchedDir, os.path.basename(pendingWatchedMove))
606+
if os.path.exists(destFilepath):
607+
self.ui.showErrorMessage(getMessage("cannot-move-file-due-to-name-conflict-error").format(pendingWatchedMove, constants.WATCHED_SUBFOLDER))
608+
self._pendingWatchedMoves.remove(pendingWatchedMove)
609+
continue
610+
utils.moveFile(pendingWatchedMove, destFilepath)
611+
self.fileSwitch.updateInfo()
612+
try:
613+
self.ui.showMessage("Moved '{}' to '{}' sub-folder".format(pendingWatchedMove, constants.WATCHED_SUBFOLDER))
614+
self._pendingWatchedMoves.remove(pendingWatchedMove)
615+
except Exception:
616+
pass
617+
except Exception as e:
618+
self.ui.showDebugMessage("Deferring watched move for '{}': {}".format(pendingWatchedMove, e))
519619
def eofReportedByPlayer(self):
520620
if self.playlist.notJustChangedPlaylist() and self.userlist.currentUser.file:
521621
self.ui.showDebugMessage("Fixing file duration to allow for playlist advancement")
@@ -914,6 +1014,16 @@ def stop(self, promptForAction=False):
9141014
self.destroyProtocol()
9151015
if self._player:
9161016
self._player.drop()
1017+
1018+
if self._pendingWatchedMoves:
1019+
for _ in range(constants.WATCHED_PLAYERWAIT_MAXRETRIES):
1020+
try:
1021+
self._tryMovePendingWatchedFiles()
1022+
if not self._pendingWatchedMoves:
1023+
break
1024+
except Exception:
1025+
pass
1026+
time.sleep(constants.WATCHED_PLAYERWAIT_INTERVAL)
9171027
if self.ui:
9181028
self.ui.drop()
9191029
reactor.callLater(0.1, reactor.stop)
@@ -2091,7 +2201,8 @@ def advancePlaylistCheck(self):
20912201
abs(position - currentLength) < constants.PLAYLIST_LOAD_NEXT_FILE_TIME_FROM_END_THRESHOLD and
20922202
self.notJustChangedPlaylist()
20932203
):
2094-
self.loadNextFileInPlaylist()
2204+
self._client.markWatchedFilePendingMove()
2205+
self.loadNextFileInPlaylist()
20952206

20962207
def notJustChangedPlaylist(self):
20972208
secondsSinceLastChange = time.time() - self._lastPlaylistIndexChange
@@ -2278,20 +2389,22 @@ def findFilepath(self, filename, highPriority=False):
22782389
return
22792390

22802391
if self._client.userlist.currentUser.file and utils.sameFilename(filename, self._client.userlist.currentUser.file['name']):
2281-
return self._client.userlist.currentUser.file['path']
2392+
return utils.getCorrectedPathForFile(self._client.userlist.currentUser.file['path'])
22822393

22832394
if self.mediaFilesCache is not None:
22842395
for directory in self.mediaFilesCache:
22852396
files = self.mediaFilesCache[directory]
22862397
if len(files) > 0 and filename in files:
22872398
filepath = os.path.join(directory, filename)
2399+
filepath = utils.getCorrectedPathForFile(filepath)
22882400
if os.path.isfile(filepath):
22892401
return filepath
22902402

22912403
if self.folderSearchEnabled and self.mediaDirectories is not None:
22922404
directoryList = self.mediaDirectories
22932405
for directory in directoryList:
22942406
filepath = os.path.join(directory, filename)
2407+
filepath = utils.getCorrectedPathForFile(filepath)
22952408
if os.path.isfile(filepath):
22962409
return filepath
22972410

@@ -2313,7 +2426,13 @@ def getDirectoryOfFilenameInCache(self, filename):
23132426
for directory in self.mediaFilesCache:
23142427
files = self.mediaFilesCache[directory]
23152428
if filename in files:
2316-
return directory
2429+
filepath = os.path.join(directory, filename)
2430+
if os.path.isfile(filepath):
2431+
return directory
2432+
watched_directory = os.path.join(directory, constants.WATCHED_SUBFOLDER)
2433+
watched_filepath = os.path.join(directory, constants.WATCHED_SUBFOLDER, filename)
2434+
if os.path.isfile(watched_filepath):
2435+
return watched_directory
23172436
return None
23182437

23192438
def isDirectoryInList(self, directoryToFind, folderList):

syncplay/constants.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,11 @@ def getValueForOS(constantDict):
110110
FOLDER_SEARCH_WARNING_THRESHOLD = 2.0 # Secs - how long until a warning saying how many files have been scanned
111111
FOLDER_SEARCH_DOUBLE_CHECK_INTERVAL = 30.0 # Secs - Frequency of updating cache
112112

113+
# Changable values for watched features (you usually don't need to change these)
114+
WATCHED_CHECKQUEUE_INTERVAL = 1.0 # Secs
115+
WATCHED_PLAYERWAIT_INTERVAL = 0.1 # Secs
116+
WATCHED_PLAYERWAIT_MAXRETRIES = 80
117+
113118
# Usually there's no need to adjust these
114119
DOUBLE_CHECK_REWIND = True
115120
LAST_PAUSED_DIFF_THRESHOLD = 2
@@ -332,6 +337,10 @@ def getValueForOS(constantDict):
332337

333338
VLC_EOF_DURATION_THRESHOLD = 2.0
334339

340+
WATCHED_SUBFOLDER = "Watched"
341+
WATCHED_AUTOMOVE = False
342+
WATCHED_AUTOCREATESUBFOLDERS = False
343+
335344
PRIVACY_HIDDENFILENAME = "**Hidden filename**"
336345
INVERTED_STATE_MARKER = "*"
337346
ERROR_MESSAGE_MARKER = "*"

syncplay/messages_de.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -554,4 +554,20 @@
554554

555555
"playlist-empty-error": "Wiedergabeliste is aktuell leer.",
556556
"playlist-invalid-index-error": "Ungültiger Wiedergabelisten-Index",
557+
558+
# Watched file functionality
559+
# TODO: Please double-check these translations and remove this message if they are fine
560+
"folders-label": "Ordner",
561+
"syncplay-watchedfiles-title": "Gesehene Dateien",
562+
"syncplay-watchedautomove-label": "Gesehene Dateien automatisch in Unterordner verschieben (falls vorhanden)",
563+
"syncplay-watchedmovesubfolder-label": "Unterordner für gesehene Dateien",
564+
"syncplay-watchedsubfolderautocreate-label": "Unterordner für gesehene Dateien bei Bedarf automatisch erstellen",
565+
"mark-as-watched-menu-label": "Als gesehen markieren",
566+
"mark-as-unwatched-menu-label": "Als ungesehen markieren",
567+
"watchedautomove-tooltip": "Verschiebt die Datei automatisch in den Unterordner für gesehene Dateien, wenn das Ende der Datei erreicht ist. Dies funktioniert nur, wenn der übergeordnete Ordner ein Medienordner ist.",
568+
"watchedsubfolder-tooltip": "Unterordner (relativ zum Ordner der Datei), in den gesehene Dateien verschoben werden (der Unterordner muss existieren, damit die Datei verschoben werden kann). Dies funktioniert nur, wenn der übergeordnete Ordner ein Medienordner ist.",
569+
"watchedsubfolderautocreate-tooltip": "Erstellt den Unterordner für gesehene Dateien beim Verschieben automatisch, falls er noch nicht existiert. Dies funktioniert nur, wenn der übergeordnete Ordner ein Medienordner ist.",
570+
"cannot-move-file-due-to-name-conflict-error": "Konnte '{}' nicht in den Unterordner '{}' verschieben, da bereits eine Datei mit diesem Namen existiert.", # Path, subfolder
571+
"moved-file-to-subfolder-notification": "Verschoben: '{}' in den Unterordner '{}'.", # Path, subfolder
572+
557573
}

0 commit comments

Comments
 (0)