From 573947582b4617979c8e1e06829c472e2aa648fd Mon Sep 17 00:00:00 2001 From: Et0h Date: Fri, 5 Jun 2015 15:50:11 +0100 Subject: [PATCH 001/134] Initial Shared Playlist UI code --- syncplay/ui/gui.py | 86 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 86 insertions(+) diff --git a/syncplay/ui/gui.py b/syncplay/ui/gui.py index cdea875..c947c14 100644 --- a/syncplay/ui/gui.py +++ b/syncplay/ui/gui.py @@ -59,6 +59,42 @@ class UserlistItemDelegate(QtGui.QStyledItemDelegate): QtGui.QStyledItemDelegate.paint(self, itemQPainter, optionQStyleOptionViewItem, indexQModelIndex) class MainWindow(QtGui.QMainWindow): + class PlaylistWidget(QtGui.QListWidget): + def keyPressEvent(self, event): + if event.key() == Qt.Key_Delete: + self._remove_selected_items() + else: + super(MainWindow.PlaylistWidget, self).keyPressEvent(event) + + def _remove_selected_items(self): + for item in self.selectedItems(): + self.takeItem(self.row(item)) + + def dragEnterEvent(self, event): + data = event.mimeData() + urls = data.urls() + if urls and urls[0].scheme() == 'file': + event.acceptProposedAction() + else: + super(MainWindow.PlaylistWidget, self).dragEnterEvent(event) + + def dropEvent(self, event): + window = self.parent().parent().parent().parent().parent() + if QtGui.QDropEvent.proposedAction(event) == Qt.MoveAction: + QtGui.QDropEvent.setDropAction(event, Qt.CopyAction) # Avoids file being deleted + data = event.mimeData() + urls = data.urls() + + if urls and urls[0].scheme() == 'file': + for url in urls: + dropfilepath = os.path.abspath(unicode(url.toLocalFile())) + if os.path.isfile(dropfilepath): + window.addFileToPlaylist(dropfilepath) + elif os.path.isdir(dropfilepath): + window.addFolderToPlaylist(dropfilepath) + else: + super(MainWindow.PlaylistWidget, self).dropEvent(event) + class topSplitter(QtGui.QSplitter): def createHandle(self): return self.topSplitterHandle(self.orientation(), self) @@ -491,6 +527,38 @@ class MainWindow(QtGui.QMainWindow): self.addPlaybackLayout(window) + window.playlistGroup = QtGui.QGroupBox(u"Enable shared playlists") + window.playlistGroup.setCheckable(True) + window.playlistLayout = QtGui.QHBoxLayout() + window.playlistGroup.setSizePolicy(QtGui.QSizePolicy.Preferred, QtGui.QSizePolicy.Fixed) + window.playlist = self.PlaylistWidget() + window.playlist.setDragDropMode(QtGui.QAbstractItemView.InternalMove) + window.playlist.setSelectionMode(QtGui.QAbstractItemView.ExtendedSelection) + noteFont = QtGui.QFont() + noteFont.setItalic(True) + playlistItem = QtGui.QListWidgetItem(u"Drag file here to add it to shared playlist.") + playlistItem.setFont(noteFont) + window.playlist.addItem(playlistItem) + window.playlist.addItem("") + playlistItem = QtGui.QListWidgetItem(u"You can drag items around too.",) + playlistItem.setFont(noteFont) + window.playlist.addItem(playlistItem) + window.playlist.addItem("") + playlistItem = QtGui.QListWidgetItem(u"...and right click here for more options (soon).") + playlistItem.setFont(noteFont) + window.playlist.addItem(playlistItem) + window.playlist.addItem("") + playlistItem = QtGui.QListWidgetItem(u"<-- Test icon for currently playing file") + window.playlist.addItem(playlistItem) + window.playlist.addItem("") + playlistItem.setFont(noteFont) + playlistItem.setIcon(QtGui.QIcon(self.resourcespath + 'bullet_right_grey.png')) + window.playlist.addItem(playlistItem) + window.playlistLayout.addWidget(window.playlist) + window.playlistLayout.setAlignment(Qt.AlignTop) + window.playlistGroup.setLayout(window.playlistLayout) + window.listLayout.addWidget(window.playlistGroup, Qt.AlignRight) + window.readyPushButton = QtGui.QPushButton() readyFont = QtGui.QFont() readyFont.setWeight(QtGui.QFont.Bold) @@ -781,6 +849,23 @@ class MainWindow(QtGui.QMainWindow): self._syncplayClient._player.openFile(dropfilepath, resetPosition=True) self._syncplayClient.setPosition(0) + def addFileToPlaylist(self, filePath): + if os.path.isfile(filePath): + self.removePlaylistNote() + self.playlist.addItem(os.path.basename(filePath)) + + def addStreamToPlaylist(self, filePath): + self.removePlayListNote() + self.playlist.addItem(os.path.basename(filePath)) + + def removePlaylistNote(self): + if not self.clearedPlaylistNote: + self.playlist.clear() + self.clearedPlaylistNote = True + + def addFolderToPlaylist(self, folderPath): + self.showErrorMessage("Add Folder {}".format(folderPath)) + def saveSettings(self): settings = QSettings("Syncplay", "MainWindow") settings.beginGroup("MainWindow") @@ -838,3 +923,4 @@ class MainWindow(QtGui.QMainWindow): self.setWindowFlags(self.windowFlags() & Qt.WindowCloseButtonHint & Qt.AA_DontUseNativeMenuBar & Qt.WindowMinimizeButtonHint & ~Qt.WindowContextHelpButtonHint) self.show() self.setAcceptDrops(True) + self.clearedPlaylistNote = False From dae3fbbcfea8f515e01eab4f8dcd37a0acadb09d Mon Sep 17 00:00:00 2001 From: Et0h Date: Sat, 6 Jun 2015 18:22:50 +0100 Subject: [PATCH 002/134] Allow for files to be added to top/middle of playlist --- syncplay/ui/gui.py | 127 +++++++++++++++++++++++++++++++++++++-------- 1 file changed, 106 insertions(+), 21 deletions(-) diff --git a/syncplay/ui/gui.py b/syncplay/ui/gui.py index c947c14..4c12b02 100644 --- a/syncplay/ui/gui.py +++ b/syncplay/ui/gui.py @@ -1,5 +1,5 @@ from PySide import QtGui -from PySide.QtCore import Qt, QSettings, QSize, QPoint, QUrl +from PySide.QtCore import Qt, QSettings, QSize, QPoint, QUrl, QLine from syncplay import utils, constants, version from syncplay.messages import getMessage import sys @@ -59,7 +59,75 @@ class UserlistItemDelegate(QtGui.QStyledItemDelegate): QtGui.QStyledItemDelegate.paint(self, itemQPainter, optionQStyleOptionViewItem, indexQModelIndex) class MainWindow(QtGui.QMainWindow): + insertPosition = None + + def setPlaylistInsertPosition(self, newPosition): + if MainWindow.insertPosition <> newPosition: + MainWindow.insertPosition = newPosition + self.playlist.forceUpdate() + + class PlaylistItemDelegate(QtGui.QStyledItemDelegate): + def paint(self, itemQPainter, optionQStyleOptionViewItem, indexQModelIndex): + QtGui.QStyledItemDelegate.paint(self, itemQPainter, optionQStyleOptionViewItem, indexQModelIndex) + itemQPainter.save() + lineAbove = False + lineBelow = False + if MainWindow.insertPosition == 0 and indexQModelIndex.row() == 0: + lineAbove = True + elif MainWindow.insertPosition and indexQModelIndex.row() == MainWindow.insertPosition-1: + lineBelow = True + if lineAbove: + line = QLine(optionQStyleOptionViewItem.rect.topLeft(), optionQStyleOptionViewItem.rect.topRight()) + itemQPainter.drawLine(line) + elif lineBelow: + line = QLine(optionQStyleOptionViewItem.rect.bottomLeft(), optionQStyleOptionViewItem.rect.bottomRight()) + itemQPainter.drawLine(line) + itemQPainter.restore() + + class PlaylistGroupBox(QtGui.QGroupBox): + def dragEnterEvent(self, event): + data = event.mimeData() + urls = data.urls() + window = self.parent().parent().parent().parent() + if urls and urls[0].scheme() == 'file': + event.acceptProposedAction() + window.setPlaylistInsertPosition(window.playlist.count() if window.clearedPlaylistNote else 0) + else: + super(MainWindow.PlaylistGroupBox, self).dragEnterEvent(event) + + def dragLeaveEvent(self, event): + window = self.parent().parent().parent().parent() + window.setPlaylistInsertPosition(None) + + def dropEvent(self, event): + window = self.parent().parent().parent().parent() + window.setPlaylistInsertPosition(None) + if QtGui.QDropEvent.proposedAction(event) == Qt.MoveAction: + QtGui.QDropEvent.setDropAction(event, Qt.CopyAction) # Avoids file being deleted + data = event.mimeData() + urls = data.urls() + + if urls and urls[0].scheme() == 'file': + indexRow = window.playlist.count() if window.clearedPlaylistNote else 0 + + for url in urls[::-1]: + dropfilepath = os.path.abspath(unicode(url.toLocalFile())) + if os.path.isfile(dropfilepath): + window.addFileToPlaylist(dropfilepath, indexRow) + elif os.path.isdir(dropfilepath): + window.addFolderToPlaylist(dropfilepath) + else: + super(MainWindow.PlaylistWidget, self).dropEvent(event) + class PlaylistWidget(QtGui.QListWidget): + def dragLeaveEvent(self, event): + window = self.parent().parent().parent().parent().parent() + window.setPlaylistInsertPosition(None) + + def forceUpdate(self): + root = self.rootIndex() + self.dataChanged(root, root) + def keyPressEvent(self, event): if event.key() == Qt.Key_Delete: self._remove_selected_items() @@ -78,18 +146,39 @@ class MainWindow(QtGui.QMainWindow): else: super(MainWindow.PlaylistWidget, self).dragEnterEvent(event) + def dragMoveEvent(self, event): + data = event.mimeData() + urls = data.urls() + if urls and urls[0].scheme() == 'file': + event.acceptProposedAction() + indexRow = self.indexAt(event.pos()).row() + window = self.parent().parent().parent().parent().parent() + if not window.clearedPlaylistNote: + indexRow = 0 + if indexRow == -1: + indexRow = window.playlist.count() + window.setPlaylistInsertPosition(indexRow) + else: + super(MainWindow.PlaylistWidget, self).dragMoveEvent(event) + def dropEvent(self, event): window = self.parent().parent().parent().parent().parent() + window.setPlaylistInsertPosition(None) if QtGui.QDropEvent.proposedAction(event) == Qt.MoveAction: QtGui.QDropEvent.setDropAction(event, Qt.CopyAction) # Avoids file being deleted data = event.mimeData() urls = data.urls() if urls and urls[0].scheme() == 'file': - for url in urls: + indexRow = self.indexAt(event.pos()).row() + if not window.clearedPlaylistNote: + indexRow = 0 + if indexRow == -1: + indexRow = window.playlist.count() + for url in urls[::-1]: dropfilepath = os.path.abspath(unicode(url.toLocalFile())) if os.path.isfile(dropfilepath): - window.addFileToPlaylist(dropfilepath) + window.addFileToPlaylist(dropfilepath, indexRow) elif os.path.isdir(dropfilepath): window.addFolderToPlaylist(dropfilepath) else: @@ -527,33 +616,26 @@ class MainWindow(QtGui.QMainWindow): self.addPlaybackLayout(window) - window.playlistGroup = QtGui.QGroupBox(u"Enable shared playlists") + window.playlistGroup = self.PlaylistGroupBox(u"Enable shared playlists") window.playlistGroup.setCheckable(True) window.playlistLayout = QtGui.QHBoxLayout() window.playlistGroup.setSizePolicy(QtGui.QSizePolicy.Preferred, QtGui.QSizePolicy.Fixed) + window.playlistGroup.setAcceptDrops(True) window.playlist = self.PlaylistWidget() - window.playlist.setDragDropMode(QtGui.QAbstractItemView.InternalMove) + window.playlist.setItemDelegate(self.PlaylistItemDelegate()) + window.playlist.setDragEnabled(True) + window.playlist.setAcceptDrops(True) + window.playlist.setDropIndicatorShown(True) window.playlist.setSelectionMode(QtGui.QAbstractItemView.ExtendedSelection) + window.playlist.setDefaultDropAction(Qt.MoveAction) + window.playlist.setDragDropMode(QtGui.QAbstractItemView.InternalMove) noteFont = QtGui.QFont() noteFont.setItalic(True) - playlistItem = QtGui.QListWidgetItem(u"Drag file here to add it to shared playlist.") + playlistItem = QtGui.QListWidgetItem(u"Drag file here to add it to the shared playlist.") playlistItem.setFont(noteFont) window.playlist.addItem(playlistItem) - window.playlist.addItem("") - playlistItem = QtGui.QListWidgetItem(u"You can drag items around too.",) playlistItem.setFont(noteFont) window.playlist.addItem(playlistItem) - window.playlist.addItem("") - playlistItem = QtGui.QListWidgetItem(u"...and right click here for more options (soon).") - playlistItem.setFont(noteFont) - window.playlist.addItem(playlistItem) - window.playlist.addItem("") - playlistItem = QtGui.QListWidgetItem(u"<-- Test icon for currently playing file") - window.playlist.addItem(playlistItem) - window.playlist.addItem("") - playlistItem.setFont(noteFont) - playlistItem.setIcon(QtGui.QIcon(self.resourcespath + 'bullet_right_grey.png')) - window.playlist.addItem(playlistItem) window.playlistLayout.addWidget(window.playlist) window.playlistLayout.setAlignment(Qt.AlignTop) window.playlistGroup.setLayout(window.playlistLayout) @@ -849,10 +931,13 @@ class MainWindow(QtGui.QMainWindow): self._syncplayClient._player.openFile(dropfilepath, resetPosition=True) self._syncplayClient.setPosition(0) - def addFileToPlaylist(self, filePath): + def addFileToPlaylist(self, filePath, index = -1): if os.path.isfile(filePath): self.removePlaylistNote() - self.playlist.addItem(os.path.basename(filePath)) + if index == -1: + self.playlist.addItem(os.path.basename(filePath)) + else: + self.playlist.insertItem(index, os.path.basename(filePath)) def addStreamToPlaylist(self, filePath): self.removePlayListNote() From 4327068fc5c0c319fbe469f4ed1d481272857e06 Mon Sep 17 00:00:00 2001 From: Et0h Date: Sat, 6 Jun 2015 19:45:39 +0100 Subject: [PATCH 003/134] Place playlist drop hint below placeholder item(s) --- syncplay/ui/gui.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/syncplay/ui/gui.py b/syncplay/ui/gui.py index 4c12b02..37dff42 100644 --- a/syncplay/ui/gui.py +++ b/syncplay/ui/gui.py @@ -91,7 +91,7 @@ class MainWindow(QtGui.QMainWindow): window = self.parent().parent().parent().parent() if urls and urls[0].scheme() == 'file': event.acceptProposedAction() - window.setPlaylistInsertPosition(window.playlist.count() if window.clearedPlaylistNote else 0) + window.setPlaylistInsertPosition(window.playlist.count()) else: super(MainWindow.PlaylistGroupBox, self).dragEnterEvent(event) @@ -153,9 +153,7 @@ class MainWindow(QtGui.QMainWindow): event.acceptProposedAction() indexRow = self.indexAt(event.pos()).row() window = self.parent().parent().parent().parent().parent() - if not window.clearedPlaylistNote: - indexRow = 0 - if indexRow == -1: + if indexRow == -1 or not window.clearedPlaylistNote: indexRow = window.playlist.count() window.setPlaylistInsertPosition(indexRow) else: From e01521022aeb6bcef08731d5bff07fadac629971 Mon Sep 17 00:00:00 2001 From: Et0h Date: Thu, 18 Jun 2015 13:40:57 +0100 Subject: [PATCH 004/134] Add userlist/playlist splitter to GUI --- syncplay/ui/gui.py | 33 +++++++++++++++++++++++++++++---- 1 file changed, 29 insertions(+), 4 deletions(-) diff --git a/syncplay/ui/gui.py b/syncplay/ui/gui.py index 37dff42..1612570 100644 --- a/syncplay/ui/gui.py +++ b/syncplay/ui/gui.py @@ -195,6 +195,19 @@ class MainWindow(QtGui.QMainWindow): QtGui.QSplitterHandle.mouseMoveEvent(self, event) self.parent().parent().parent().updateListGeometry() + class listSplitter(QtGui.QSplitter): + def createHandle(self): + return self.listSplitter(self.orientation(), self) + + class listSplitter(QtGui.QSplitterHandle): + def mouseReleaseEvent(self, event): + QtGui.QSplitterHandle.mouseReleaseEvent(self, event) + self.parent().parent().parent().parent().parent().updateListGeometry() + + def mouseMoveEvent(self, event): + QtGui.QSplitterHandle.mouseMoveEvent(self, event) + self.parent().parent().parent().parent().parent().updateListGeometry() + def needsClient(f): # @NoSelf @wraps(f) def wrapper(self, *args, **kwds): @@ -577,8 +590,20 @@ class MainWindow(QtGui.QMainWindow): window.listFrame.setMidLineWidth(0) window.listFrame.setSizePolicy(QtGui.QSizePolicy.Preferred, QtGui.QSizePolicy.Preferred) window.listLayout.setContentsMargins(0, 0, 0, 0) - window.listLayout.addWidget(window.listlabel) - window.listLayout.addWidget(window.listTreeView) + + window.userlistLayout = QtGui.QVBoxLayout() + window.userlistFrame = QtGui.QFrame() + window.userlistFrame.setLineWidth(0) + window.userlistFrame.setMidLineWidth(0) + window.userlistFrame.setSizePolicy(QtGui.QSizePolicy.Preferred, QtGui.QSizePolicy.Preferred) + window.userlistLayout.setContentsMargins(0, 0, 0, 0) + window.userlistFrame.setLayout(window.userlistLayout) + window.userlistLayout.addWidget(window.listlabel) + window.userlistLayout.addWidget(window.listTreeView) + + window.listSplit = self.listSplitter(Qt.Vertical, self) + window.listSplit.addWidget(window.userlistFrame) + window.listLayout.addWidget(window.listSplit) window.roomInput = QtGui.QLineEdit() window.roomInput.returnPressed.connect(self.joinRoom) @@ -617,7 +642,7 @@ class MainWindow(QtGui.QMainWindow): window.playlistGroup = self.PlaylistGroupBox(u"Enable shared playlists") window.playlistGroup.setCheckable(True) window.playlistLayout = QtGui.QHBoxLayout() - window.playlistGroup.setSizePolicy(QtGui.QSizePolicy.Preferred, QtGui.QSizePolicy.Fixed) + window.playlistGroup.setSizePolicy(QtGui.QSizePolicy.Preferred, QtGui.QSizePolicy.Preferred) window.playlistGroup.setAcceptDrops(True) window.playlist = self.PlaylistWidget() window.playlist.setItemDelegate(self.PlaylistItemDelegate()) @@ -637,7 +662,7 @@ class MainWindow(QtGui.QMainWindow): window.playlistLayout.addWidget(window.playlist) window.playlistLayout.setAlignment(Qt.AlignTop) window.playlistGroup.setLayout(window.playlistLayout) - window.listLayout.addWidget(window.playlistGroup, Qt.AlignRight) + window.listSplit.addWidget(window.playlistGroup) window.readyPushButton = QtGui.QPushButton() readyFont = QtGui.QFont() From 28c97527b1cbd3bd6c930ddd414b7810fcafbb7b Mon Sep 17 00:00:00 2001 From: Et0h Date: Thu, 18 Jun 2015 13:46:39 +0100 Subject: [PATCH 005/134] Add userlist/playlist splitter to GUI --- syncplay/ui/gui.py | 27 +++++++-------------------- 1 file changed, 7 insertions(+), 20 deletions(-) diff --git a/syncplay/ui/gui.py b/syncplay/ui/gui.py index 1612570..672b319 100644 --- a/syncplay/ui/gui.py +++ b/syncplay/ui/gui.py @@ -88,7 +88,7 @@ class MainWindow(QtGui.QMainWindow): def dragEnterEvent(self, event): data = event.mimeData() urls = data.urls() - window = self.parent().parent().parent().parent() + window = self.parent().parent().parent().parent().parent() if urls and urls[0].scheme() == 'file': event.acceptProposedAction() window.setPlaylistInsertPosition(window.playlist.count()) @@ -96,11 +96,11 @@ class MainWindow(QtGui.QMainWindow): super(MainWindow.PlaylistGroupBox, self).dragEnterEvent(event) def dragLeaveEvent(self, event): - window = self.parent().parent().parent().parent() + window = self.parent().parent().parent().parent().parent() window.setPlaylistInsertPosition(None) def dropEvent(self, event): - window = self.parent().parent().parent().parent() + window = self.parent().parent().parent().parent().parent() window.setPlaylistInsertPosition(None) if QtGui.QDropEvent.proposedAction(event) == Qt.MoveAction: QtGui.QDropEvent.setDropAction(event, Qt.CopyAction) # Avoids file being deleted @@ -121,7 +121,7 @@ class MainWindow(QtGui.QMainWindow): class PlaylistWidget(QtGui.QListWidget): def dragLeaveEvent(self, event): - window = self.parent().parent().parent().parent().parent() + window = self.parent().parent().parent().parent().parent().parent() window.setPlaylistInsertPosition(None) def forceUpdate(self): @@ -152,7 +152,7 @@ class MainWindow(QtGui.QMainWindow): if urls and urls[0].scheme() == 'file': event.acceptProposedAction() indexRow = self.indexAt(event.pos()).row() - window = self.parent().parent().parent().parent().parent() + window = self.parent().parent().parent().parent().parent().parent() if indexRow == -1 or not window.clearedPlaylistNote: indexRow = window.playlist.count() window.setPlaylistInsertPosition(indexRow) @@ -160,7 +160,7 @@ class MainWindow(QtGui.QMainWindow): super(MainWindow.PlaylistWidget, self).dragMoveEvent(event) def dropEvent(self, event): - window = self.parent().parent().parent().parent().parent() + window = self.parent().parent().parent().parent().parent().parent() window.setPlaylistInsertPosition(None) if QtGui.QDropEvent.proposedAction(event) == Qt.MoveAction: QtGui.QDropEvent.setDropAction(event, Qt.CopyAction) # Avoids file being deleted @@ -195,19 +195,6 @@ class MainWindow(QtGui.QMainWindow): QtGui.QSplitterHandle.mouseMoveEvent(self, event) self.parent().parent().parent().updateListGeometry() - class listSplitter(QtGui.QSplitter): - def createHandle(self): - return self.listSplitter(self.orientation(), self) - - class listSplitter(QtGui.QSplitterHandle): - def mouseReleaseEvent(self, event): - QtGui.QSplitterHandle.mouseReleaseEvent(self, event) - self.parent().parent().parent().parent().parent().updateListGeometry() - - def mouseMoveEvent(self, event): - QtGui.QSplitterHandle.mouseMoveEvent(self, event) - self.parent().parent().parent().parent().parent().updateListGeometry() - def needsClient(f): # @NoSelf @wraps(f) def wrapper(self, *args, **kwds): @@ -601,7 +588,7 @@ class MainWindow(QtGui.QMainWindow): window.userlistLayout.addWidget(window.listlabel) window.userlistLayout.addWidget(window.listTreeView) - window.listSplit = self.listSplitter(Qt.Vertical, self) + window.listSplit = QtGui.QSplitter(Qt.Vertical, self) window.listSplit.addWidget(window.userlistFrame) window.listLayout.addWidget(window.listSplit) From 9a12bac37917536d277b4cc5319eb45294b5044b Mon Sep 17 00:00:00 2001 From: Et0h Date: Thu, 1 Oct 2015 22:21:47 +0100 Subject: [PATCH 006/134] Call playlistUpdate when playlist changes --- syncplay/client.py | 3 +++ syncplay/ui/gui.py | 39 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 42 insertions(+) diff --git a/syncplay/client.py b/syncplay/client.py index 3d135e9..aa5a7f3 100644 --- a/syncplay/client.py +++ b/syncplay/client.py @@ -156,6 +156,9 @@ class SyncplayClient(object): return False return True + def playlistUpdate(self, newPlaylist): + print newPlaylist + def _determinePlayerStateChange(self, paused, position): pauseChange = self.getPlayerPaused() != paused and self.getGlobalPaused() != paused _playerDiff = abs(self.getPlayerPosition() - position) diff --git a/syncplay/ui/gui.py b/syncplay/ui/gui.py index bdf6206..529862c 100644 --- a/syncplay/ui/gui.py +++ b/syncplay/ui/gui.py @@ -86,6 +86,7 @@ class UserlistItemDelegate(QtGui.QStyledItemDelegate): class MainWindow(QtGui.QMainWindow): insertPosition = None + playlistState = [] def setPlaylistInsertPosition(self, newPosition): if MainWindow.insertPosition <> newPosition: @@ -111,6 +112,7 @@ class MainWindow(QtGui.QMainWindow): itemQPainter.restore() class PlaylistGroupBox(QtGui.QGroupBox): + def dragEnterEvent(self, event): data = event.mimeData() urls = data.urls() @@ -146,10 +148,30 @@ class MainWindow(QtGui.QMainWindow): super(MainWindow.PlaylistWidget, self).dropEvent(event) class PlaylistWidget(QtGui.QListWidget): + selfWindow = None + + def setWindow(self, window): + self.selfWindow = window + def dragLeaveEvent(self, event): window = self.parent().parent().parent().parent().parent().parent() window.setPlaylistInsertPosition(None) + def rowsMoved(self, sourceParent, sourceStart, sourceEnd, destinationParent, destinationRow): + if self.selfWindow: + super(MainWindow.PlaylistWidget, self).rowsMoved(sourceParent, sourceStart, sourceEnd, destinationParent, destinationRow) + self.selfWindow.playlistUpdated() + + def rowsInserted(self, parent, start, end): + if self.selfWindow: + super(MainWindow.PlaylistWidget, self).rowsInserted(parent, start, end) + self.selfWindow.playlistUpdated() + + def rowsRemoved(self, parent, start, end): + if self.selfWindow: + super(MainWindow.PlaylistWidget, self).rowsRemoved(parent, start, end) + self.selfWindow.playlistUpdated() + def forceUpdate(self): root = self.rootIndex() self.dataChanged(root, root) @@ -163,6 +185,7 @@ class MainWindow(QtGui.QMainWindow): def _remove_selected_items(self): for item in self.selectedItems(): self.takeItem(self.row(item)) + self.selfWindow.playlistUpdated() def dragEnterEvent(self, event): data = event.mimeData() @@ -207,6 +230,7 @@ class MainWindow(QtGui.QMainWindow): window.addFolderToPlaylist(dropfilepath) else: super(MainWindow.PlaylistWidget, self).dropEvent(event) + self.selfWindow.playlistUpdated() class FileSwitchManager(object): def __init__(self): @@ -771,6 +795,20 @@ class MainWindow(QtGui.QMainWindow): def drop(self): self.close() + def getPlaylistState(self): + playlistItems = [] + for playlistItem in xrange(self.playlist.count()): + platlistItemText = self.playlist.item(playlistItem).text() + if platlistItemText <> u"Drag file here to add it to the shared playlist.": + playlistItems.append(platlistItemText) + return playlistItems + + def playlistUpdated(self): + newPlaylist = self.getPlaylistState() + if newPlaylist <> self.playlistState and self._syncplayClient: + self.playlistState = newPlaylist + self._syncplayClient.playlistUpdate(newPlaylist) + def addTopLayout(self, window): window.topSplit = self.topSplitter(Qt.Horizontal, self) @@ -861,6 +899,7 @@ class MainWindow(QtGui.QMainWindow): window.playlistGroup.setSizePolicy(QtGui.QSizePolicy.Preferred, QtGui.QSizePolicy.Preferred) window.playlistGroup.setAcceptDrops(True) window.playlist = self.PlaylistWidget() + window.playlist.setWindow(window) window.playlist.setItemDelegate(self.PlaylistItemDelegate()) window.playlist.setDragEnabled(True) window.playlist.setAcceptDrops(True) From efe32b406e0b1f9cfe2bebd91b7fd49fcaff2f6c Mon Sep 17 00:00:00 2001 From: Uriziel Date: Thu, 1 Oct 2015 23:30:15 +0200 Subject: [PATCH 007/134] Added method stubs for sharedplaylists --- syncplay/client.py | 38 ++++++++++++++++++++++++++++++++++--- syncplay/protocols.py | 39 ++++++++++++++++++++++++++++++++++++++ syncplay/server.py | 44 +++++++++++++++++++++++++++++++++++++++++-- syncplay/ui/gui.py | 2 +- 4 files changed, 117 insertions(+), 6 deletions(-) diff --git a/syncplay/client.py b/syncplay/client.py index aa5a7f3..83f1dfb 100644 --- a/syncplay/client.py +++ b/syncplay/client.py @@ -113,6 +113,9 @@ class SyncplayClient(object): self.autoplayTimer = task.LoopingCall(self.autoplayCountdown) self.autoplayTimeLeft = constants.AUTOPLAY_DELAY + self._playlist = [] + self._playlistIndex = None + self._warnings = self._WarningManager(self._player, self.userlist, self.ui, self) if constants.LIST_RELATIVE_CONFIGS and self._config.has_key('loadedRelativePaths') and self._config['loadedRelativePaths']: paths = "; ".join(self._config['loadedRelativePaths']) @@ -156,9 +159,6 @@ class SyncplayClient(object): return False return True - def playlistUpdate(self, newPlaylist): - print newPlaylist - def _determinePlayerStateChange(self, paused, position): pauseChange = self.getPlayerPaused() != paused and self.getGlobalPaused() != paused _playerDiff = abs(self.getPlayerPosition() - position) @@ -171,6 +171,7 @@ class SyncplayClient(object): pauseChange, seeked = self._determinePlayerStateChange(paused, position) self._playerPosition = position self._playerPaused = paused + # TODO: if position +- 1s of the end of a file AND has next file in playlist -> rewind to 0 and switch file if pauseChange and utils.meetsMinVersion(self.serverVersion, constants.USER_READY_MIN_VERSION): if not self.userlist.currentUser.canControl(): self._player.setPaused(self._globalPaused) @@ -410,6 +411,37 @@ class SyncplayClient(object): self.userlist.currentUser.setFile(filename, duration, size, path) self.sendFile() + # TODO: execute changeToPlaylistIndex if file found in playlist + + def changeToPlaylistIndex(self, index, username = None): + if self._playlistIndex == index: + return + self._playlistIndex = index + if username is None and self._protocol and self._protocol.logged: + self._protocol.setPlaylistIndex(index) + elif username != self.getUsername(): + # TODO: Display info about playlist file change + + if index in self._playlist: + filename = self._playlist[index] + # TODO: Find Path + path = 'https://www.youtube.com/watch?v=0iXX5h6Hxxs' + if path: + self._player.openFile(path) + else: + # TODO: Notify user about file not found + pass + + def changePlaylist(self, files, username = None): + self._playlist = files + + if username is None and self._protocol and self._protocol.logged: + self._protocol.setPlaylist(files) + elif username != self.getUsername(): + pass + # TODO: Display info about playlist change + # TODO: Update UI with new playlist + def __executePrivacySettings(self, filename, size): if self._config['filenamePrivacyMode'] == PRIVACY_SENDHASHED_MODE: filename = utils.hashFilename(filename) diff --git a/syncplay/protocols.py b/syncplay/protocols.py index a91d429..04fe7cc 100644 --- a/syncplay/protocols.py +++ b/syncplay/protocols.py @@ -140,6 +140,10 @@ class SyncClientProtocol(JSONCommandProtocol): user, isReady = values["username"], values["isReady"] manuallyInitiated = values["manuallyInitiated"] if values.has_key("manuallyInitiated") else True self._client.setReady(user, isReady, manuallyInitiated) + elif command == "playlistIndex": + self._client.changeToPlaylistIndex(values['index'], values['user']) + elif command == "playlistChange": + self._client.changePlaylist(values['files'], values['user']) def sendSet(self, setting): self.sendMessage({"Set": setting}) @@ -247,6 +251,21 @@ class SyncClientProtocol(JSONCommandProtocol): } }) + def setPlaylist(self, files): + self.sendSet({ + "playlistChange": { + "files": files + } + }) + + def setPlaylistIndex(self, index): + self.sendSet({ + "playlistIndex": { + "index": index + } + }) + + def handleError(self, error): self.dropWithError(error["message"]) @@ -358,6 +377,10 @@ class SyncServerProtocol(JSONCommandProtocol): elif command == "ready": manuallyInitiated = set_[1]['manuallyInitiated'] if set_[1].has_key("manuallyInitiated") else False self._factory.setReady(self._watcher, set_[1]['isReady'], manuallyInitiated=manuallyInitiated) + elif command == "playlistChange": + self._factory.setPlaylist(self._watcher, set_[1]['files']) + elif command == "playlistIndex": + self._factory.setPlaylistIndex(self._watcher, set_[1]['index']) def sendSet(self, setting): self.sendMessage({"Set": setting}) @@ -389,6 +412,22 @@ class SyncServerProtocol(JSONCommandProtocol): } }) + def setPlaylist(self, username, files): + self.sendSet({ + "playlistChange": { + "user": username, + "files": files + } + }) + + def setPlaylistIndex(self, username, index): + self.sendSet({ + "playlistIndex": { + "user": username, + "index": index + } + }) + def sendUserSetting(self, username, room, file_, event): room = {"name": room.getName()} user = {username: {}} diff --git a/syncplay/server.py b/syncplay/server.py index 3996311..93aceeb 100644 --- a/syncplay/server.py +++ b/syncplay/server.py @@ -72,8 +72,12 @@ class SyncFactory(Factory): self.sendJoinMessage(watcher) else: self.sendRoomSwitchMessage(watcher) + + room = watcher.getRoom() + watcher.setPlaylist(room.getSetBy().getName(), room.getPlaylist()) + watcher.setPlaylistIndex(room.getSetBy().getName(), room.getPlaylistIndex()) if RoomPasswordProvider.isControlledRoom(roomName): - for controller in watcher.getRoom().getControllers(): + for controller in room.getControllers(): watcher.sendControlledRoomAuthStatus(True, controller, roomName) def sendRoomSwitchMessage(self, watcher): @@ -132,6 +136,14 @@ class SyncFactory(Factory): watcher.setReady(isReady) self._roomManager.broadcastRoom(watcher, lambda w: w.sendSetReady(watcher.getName(), watcher.isReady(), manuallyInitiated)) + def setPlaylist(self, watcher, files): + watcher.getRoom().setPlaylist(files, watcher) + self._roomManager.broadcastRoom(watcher, lambda w: w.setPlaylist(watcher.getName(), files)) + + def setPlaylistIndex(self, watcher, index): + watcher.getRoom().setPlaylistIndex(index, watcher) + self._roomManager.broadcastRoom(watcher, lambda w: w.setPlaylistIndex(watcher.getName(), index)) + class RoomManager(object): def __init__(self): self._rooms = {} @@ -214,6 +226,8 @@ class Room(object): self._watchers = {} self._playState = self.STATE_PAUSED self._setBy = None + self._playlist = [] + self._playlistIndex = None def __str__(self, *args, **kwargs): return self.getName() @@ -268,6 +282,18 @@ class Room(object): def canControl(self, watcher): return True + def setPlaylist(self, files, setBy=None): + self._playlist = files + + def setPlaylistIndex(self, index, setBy=None): + self._playlistIndex = index + + def getPlaylist(self): + return self._playlist + + def getPlaylistIndex(self): + return self._playlistIndex + class ControlledRoom(Room): def __init__(self, name): Room.__init__(self, name) @@ -297,6 +323,14 @@ class ControlledRoom(Room): if self.canControl(setBy): Room.setPosition(self, position, setBy) + def setPlaylist(self, files, setBy=None): + if self.canControl(setBy): + self._playlist = files + + def setPlaylistIndex(self, index, setBy=None): + if self.canControl(setBy): + self._playlistIndex = index + def canControl(self, watcher): return watcher.getName() in self._controllers @@ -370,6 +404,12 @@ class Watcher(object): def sendSetReady(self, username, isReady, manuallyInitiated=True): self._connector.sendSetReady(username, isReady, manuallyInitiated) + def setPlaylistIndex(self, username, index): + self._connector.setPlaylistIndex(username, index) + + def setPlaylist(self, username, files): + self._connector.setPlaylist(username, files) + def __lt__(self, b): if self.getPosition() is None or self._file is None: return False @@ -442,4 +482,4 @@ class ConfigurationGetter(object): self._argparser.add_argument('--isolate-rooms', action='store_true', help=getMessage("server-isolate-room-argument")) self._argparser.add_argument('--disable-ready', action='store_true', help=getMessage("server-disable-ready-argument")) self._argparser.add_argument('--salt', metavar='salt', type=str, nargs='?', help=getMessage("server-salt-argument")) - self._argparser.add_argument('--motd-file', metavar='file', type=str, nargs='?', help=getMessage("server-motd-argument")) \ No newline at end of file + self._argparser.add_argument('--motd-file', metavar='file', type=str, nargs='?', help=getMessage("server-motd-argument")) diff --git a/syncplay/ui/gui.py b/syncplay/ui/gui.py index 529862c..377f19b 100644 --- a/syncplay/ui/gui.py +++ b/syncplay/ui/gui.py @@ -807,7 +807,7 @@ class MainWindow(QtGui.QMainWindow): newPlaylist = self.getPlaylistState() if newPlaylist <> self.playlistState and self._syncplayClient: self.playlistState = newPlaylist - self._syncplayClient.playlistUpdate(newPlaylist) + self._syncplayClient.changePlaylist(newPlaylist) def addTopLayout(self, window): window.topSplit = self.topSplitter(Qt.Horizontal, self) From 3322e94e034d00691ec69611fb8eeaa8556e2438 Mon Sep 17 00:00:00 2001 From: Uriziel Date: Fri, 2 Oct 2015 00:08:02 +0200 Subject: [PATCH 008/134] Actually executing filechanges for numeric keys --- syncplay/client.py | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/syncplay/client.py b/syncplay/client.py index 83f1dfb..bcad241 100644 --- a/syncplay/client.py +++ b/syncplay/client.py @@ -411,7 +411,13 @@ class SyncplayClient(object): self.userlist.currentUser.setFile(filename, duration, size, path) self.sendFile() - # TODO: execute changeToPlaylistIndex if file found in playlist + # TODO: Fix for GUIDS + try: + index = self._playlist.index(filename) + self.changeToPlaylistIndex(index) + except ValueError: + pass + def changeToPlaylistIndex(self, index, username = None): if self._playlistIndex == index: @@ -421,8 +427,7 @@ class SyncplayClient(object): self._protocol.setPlaylistIndex(index) elif username != self.getUsername(): # TODO: Display info about playlist file change - - if index in self._playlist: + try: filename = self._playlist[index] # TODO: Find Path path = 'https://www.youtube.com/watch?v=0iXX5h6Hxxs' @@ -431,6 +436,8 @@ class SyncplayClient(object): else: # TODO: Notify user about file not found pass + except IndexError: + pass def changePlaylist(self, files, username = None): self._playlist = files From 8fb06b3faf66d82f1c82bdc67adce61b45c92e85 Mon Sep 17 00:00:00 2001 From: Et0h Date: Thu, 1 Oct 2015 23:34:51 +0100 Subject: [PATCH 009/134] Don't trigger playlist update multiple times when inserting files --- syncplay/ui/gui.py | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/syncplay/ui/gui.py b/syncplay/ui/gui.py index 377f19b..1a7db76 100644 --- a/syncplay/ui/gui.py +++ b/syncplay/ui/gui.py @@ -146,6 +146,7 @@ class MainWindow(QtGui.QMainWindow): window.addFolderToPlaylist(dropfilepath) else: super(MainWindow.PlaylistWidget, self).dropEvent(event) + window.playlistUpdated() class PlaylistWidget(QtGui.QListWidget): selfWindow = None @@ -162,11 +163,6 @@ class MainWindow(QtGui.QMainWindow): super(MainWindow.PlaylistWidget, self).rowsMoved(sourceParent, sourceStart, sourceEnd, destinationParent, destinationRow) self.selfWindow.playlistUpdated() - def rowsInserted(self, parent, start, end): - if self.selfWindow: - super(MainWindow.PlaylistWidget, self).rowsInserted(parent, start, end) - self.selfWindow.playlistUpdated() - def rowsRemoved(self, parent, start, end): if self.selfWindow: super(MainWindow.PlaylistWidget, self).rowsRemoved(parent, start, end) @@ -207,6 +203,7 @@ class MainWindow(QtGui.QMainWindow): window.setPlaylistInsertPosition(indexRow) else: super(MainWindow.PlaylistWidget, self).dragMoveEvent(event) + self.selfWindow.playlistUpdated() def dropEvent(self, event): window = self.parent().parent().parent().parent().parent().parent() From d742b04fed9b76136ff7f2210025ed856c0b02f2 Mon Sep 17 00:00:00 2001 From: Et0h Date: Fri, 2 Oct 2015 00:23:56 +0100 Subject: [PATCH 010/134] Update playlist UI on external change and show message --- syncplay/client.py | 8 +++++--- syncplay/ui/consoleUI.py | 3 +++ syncplay/ui/gui.py | 11 ++++++++++- 3 files changed, 18 insertions(+), 4 deletions(-) diff --git a/syncplay/client.py b/syncplay/client.py index bcad241..4eca152 100644 --- a/syncplay/client.py +++ b/syncplay/client.py @@ -445,9 +445,8 @@ class SyncplayClient(object): if username is None and self._protocol and self._protocol.logged: self._protocol.setPlaylist(files) elif username != self.getUsername(): - pass - # TODO: Display info about playlist change - # TODO: Update UI with new playlist + self.ui.setPlaylist(self._playlist) + self.ui.showMessage(u"{} updated the playlist".format(username)) def __executePrivacySettings(self, filename, size): if self._config['filenamePrivacyMode'] == PRIVACY_SENDHASHED_MODE: @@ -1197,6 +1196,9 @@ class UiManager(object): self.lastSecondaryOSDEndTime = None self.lastError = "" + def setPlaylist(self, newPlaylist): + self.__ui.setPlaylist(newPlaylist) + def showDebugMessage(self, message): if constants.DEBUG_MODE and message.rstrip(): sys.stderr.write("{}{}\n".format(time.strftime(constants.UI_TIME_FORMAT, time.localtime()),message.rstrip())) diff --git a/syncplay/ui/consoleUI.py b/syncplay/ui/consoleUI.py index fcaf7cf..338086f 100644 --- a/syncplay/ui/consoleUI.py +++ b/syncplay/ui/consoleUI.py @@ -22,6 +22,9 @@ class ConsoleUI(threading.Thread): def drop(self): pass + + def setPlaylist(self, newPlaylist): + pass def run(self): try: diff --git a/syncplay/ui/gui.py b/syncplay/ui/gui.py index 1a7db76..c50feb6 100644 --- a/syncplay/ui/gui.py +++ b/syncplay/ui/gui.py @@ -87,6 +87,7 @@ class UserlistItemDelegate(QtGui.QStyledItemDelegate): class MainWindow(QtGui.QMainWindow): insertPosition = None playlistState = [] + blockPlaylistUpdateNotifications = False def setPlaylistInsertPosition(self, newPosition): if MainWindow.insertPosition <> newPosition: @@ -802,7 +803,7 @@ class MainWindow(QtGui.QMainWindow): def playlistUpdated(self): newPlaylist = self.getPlaylistState() - if newPlaylist <> self.playlistState and self._syncplayClient: + if newPlaylist <> self.playlistState and self._syncplayClient and not self.blockPlaylistUpdateNotifications: self.playlistState = newPlaylist self._syncplayClient.changePlaylist(newPlaylist) @@ -1207,6 +1208,14 @@ class MainWindow(QtGui.QMainWindow): self._syncplayClient._player.openFile(dropfilepath, resetPosition=True) self._syncplayClient.setPosition(0) + def setPlaylist(self, newPlaylist): + if newPlaylist == [] and not self.clearedPlaylistNote: + return + self.blockPlaylistUpdateNotifications = True + self.playlist.clear() + self.playlist.insertItems(0, newPlaylist) + self.blockPlaylistUpdateNotifications = False + def addFileToPlaylist(self, filePath, index = -1): if os.path.isfile(filePath): self.removePlaylistNote() From f9c1ad07642e16e9328a0b4cbb634e867f3c3a41 Mon Sep 17 00:00:00 2001 From: Et0h Date: Fri, 2 Oct 2015 00:48:01 +0100 Subject: [PATCH 011/134] Initial code to find files for playlists --- syncplay/client.py | 30 ++++++++++++++++++++++++++---- syncplay/constants.py | 3 ++- 2 files changed, 28 insertions(+), 5 deletions(-) diff --git a/syncplay/client.py b/syncplay/client.py index 4eca152..9b14ed7 100644 --- a/syncplay/client.py +++ b/syncplay/client.py @@ -419,23 +419,45 @@ class SyncplayClient(object): pass + def findFilenameInDirectories(self, filename): + # TODO: Replace this with the code in gui.py + directoryList = self._config["mediaSearchDirectories"] + if filename and directoryList: + for directory in directoryList: + for root, dirs, files in os.walk(directory): + if filename in files: + return os.path.join(root,filename) + return None + def changeToPlaylistIndex(self, index, username = None): + path = None if self._playlistIndex == index: return + if self._player is None: + return self._playlistIndex = index if username is None and self._protocol and self._protocol.logged: self._protocol.setPlaylistIndex(index) elif username != self.getUsername(): + self.ui.showMessage(u"{} changed the playlist selection".format(username)) # TODO: Display info about playlist file change try: filename = self._playlist[index] - # TODO: Find Path - path = 'https://www.youtube.com/watch?v=0iXX5h6Hxxs' + if utils.isURL(filename): + for URI in constants.SAFE_URIS: + if filename.startswith(URI): + self._player.openFile(filename) + return + self.ui.showErrorMessage(u"Could not load {} because it is not known as a safe path.".format(filename)) + return + else: + path = self.findFilenameInDirectories(filename) + # TODO: Find Path properly if path: self._player.openFile(path) else: - # TODO: Notify user about file not found - pass + self.ui.showErrorMessage(u"Could not find file {} for playlist switch!".format(filename)) + return except IndexError: pass diff --git a/syncplay/constants.py b/syncplay/constants.py index a39ec6a..dc53f6b 100644 --- a/syncplay/constants.py +++ b/syncplay/constants.py @@ -181,4 +181,5 @@ SYNCPLAY_UPDATE_URL = u"http://syncplay.pl/checkforupdate?{}" # Params SYNCPLAY_DOWNLOAD_URL = "http://syncplay.pl/download/" SYNCPLAY_PUBLIC_SERVER_LIST_URL = u"http://syncplay.pl/listpublicservers?{}" # Params -PRIVATE_FILE_FIELDS = ["path"] \ No newline at end of file +PRIVATE_FILE_FIELDS = ["path"] +SAFE_URIS = [u"http://www.youtube.com/", u"https://www.youtube.com/"] # TODO: Move into config \ No newline at end of file From dc6a34b1da99f569d5b200b7103b008ed7a3e72f Mon Sep 17 00:00:00 2001 From: Et0h Date: Fri, 2 Oct 2015 01:05:13 +0100 Subject: [PATCH 012/134] Initial code for double click to change playlist selection --- syncplay/ui/gui.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/syncplay/ui/gui.py b/syncplay/ui/gui.py index c50feb6..50af7cf 100644 --- a/syncplay/ui/gui.py +++ b/syncplay/ui/gui.py @@ -580,6 +580,18 @@ class MainWindow(QtGui.QMainWindow): self.readyPushButton.blockSignals(False) self.updateReadyIcon() + def playlistItemClicked(self, item): + # TODO: Integrate into client.py code + filename = item.data() + if isURL(filename): + self._syncplayClient._player.openFile(filename) + else: + pathFound = MainWindow.FileSwitchManager.findFilepath(filename) + if pathFound: + self._syncplayClient._player.openFile(pathFound) + else: + self.ui.showErrorMessage(u"Could not find file {} for playlist switch!".format(filename)) + def roomClicked(self, item): username = item.sibling(item.row(), 0).data() filename = item.sibling(item.row(), 3).data() @@ -905,6 +917,7 @@ class MainWindow(QtGui.QMainWindow): window.playlist.setSelectionMode(QtGui.QAbstractItemView.ExtendedSelection) window.playlist.setDefaultDropAction(Qt.MoveAction) window.playlist.setDragDropMode(QtGui.QAbstractItemView.InternalMove) + window.playlist.doubleClicked.connect(self.playlistItemClicked) noteFont = QtGui.QFont() noteFont.setItalic(True) playlistItem = QtGui.QListWidgetItem(u"Drag file here to add it to the shared playlist.") From f66b925f5dc3a554460ecf9efc5f07019cdc4e3d Mon Sep 17 00:00:00 2001 From: Et0h Date: Fri, 2 Oct 2015 01:47:08 +0100 Subject: [PATCH 013/134] Initial code to change to next file at end --- syncplay/client.py | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/syncplay/client.py b/syncplay/client.py index 9b14ed7..edc467e 100644 --- a/syncplay/client.py +++ b/syncplay/client.py @@ -166,11 +166,36 @@ class SyncplayClient(object): seeked = _playerDiff > constants.SEEK_THRESHOLD and _globalDiff > constants.SEEK_THRESHOLD return pauseChange, seeked + def loadNextFileInPlaylist(self): + # TODO: Fix for GUIDs & add path checks (and make more of code re-use?) + if len(self._playlist) == self._playlistIndex+1: + return + filename = self._playlist[self._playlistIndex+1] + if utils.isURL(filename): + for URI in constants.SAFE_URIS: + if filename.startswith(URI): + self._player.openFile(filename, resetPosition=True) + return + self.ui.showErrorMessage(u"Could not load {} because it is not known as a safe path.".format(filename)) + return + else: + path = self.findFilenameInDirectories(filename) + if path: + self._player.openFile(path, resetPosition=True) + else: + self.ui.showErrorMessage(u"Could not find file {} for playlist switch!".format(filename)) + return + # TODO: Find Path properly + def updatePlayerStatus(self, paused, position): position -= self.getUserOffset() pauseChange, seeked = self._determinePlayerStateChange(paused, position) self._playerPosition = position self._playerPaused = paused + currentLength = self.userlist.currentUser.file["duration"] if self.userlist.currentUser.file else None + if pauseChange and paused and currentLength and currentLength > 10 and abs(position - currentLength ) < 1: + self.loadNextFileInPlaylist() + # TODO: if position +- 1s of the end of a file AND has next file in playlist -> rewind to 0 and switch file if pauseChange and utils.meetsMinVersion(self.serverVersion, constants.USER_READY_MIN_VERSION): if not self.userlist.currentUser.canControl(): From 41cdb4c565e9bc469b8fe421504c727abc1d0808 Mon Sep 17 00:00:00 2001 From: Et0h Date: Fri, 2 Oct 2015 09:05:40 +0100 Subject: [PATCH 014/134] Upver to 1.4.0 ("Yoitsu") --- syncplay/__init__.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/syncplay/__init__.py b/syncplay/__init__.py index b0ad1d2..1f2bc4f 100644 --- a/syncplay/__init__.py +++ b/syncplay/__init__.py @@ -1,4 +1,4 @@ -version = '1.3.4' -milestone = 'Chami' +version = '1.4.0' +milestone = 'Yoitsu' release_number = '21' projectURL = 'http://syncplay.pl/' From c16c220d1c87ce822a4187b06fb939d641ded5c0 Mon Sep 17 00:00:00 2001 From: Et0h Date: Fri, 2 Oct 2015 09:08:09 +0100 Subject: [PATCH 015/134] Reintroduce mpv check for actual video length to improve detection of end of file (previously added by daniel-123) --- syncplay/constants.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/syncplay/constants.py b/syncplay/constants.py index dc53f6b..dfe81ff 100644 --- a/syncplay/constants.py +++ b/syncplay/constants.py @@ -142,7 +142,7 @@ USERLIST_GUI_USERNAME_COLUMN = 0 USERLIST_GUI_FILENAME_COLUMN = 3 MPLAYER_SLAVE_ARGS = ['-slave', '--hr-seek=always', '-nomsgcolor', '-msglevel', 'all=1:global=4:cplayer=4', '-af-add', 'scaletempo'] -MPV_ARGS = ['--force-window', '--idle', '--hr-seek=always', '--keep-open', '--af-add=scaletempo'] +MPV_ARGS = ['--force-window', '--idle', '--hr-seek=always', '--keep-open', '--af-add=scaletempo', '--demuxer-mkv-probe-video-duration=yes'] MPV_SLAVE_ARGS = ['--quiet', '--input-terminal=no', '--input-file=/dev/stdin'] MPV_SLAVE_ARGS_NEW = ['--term-playing-msg=\nANS_filename=${filename}\nANS_length=${=length}\nANS_path=${path}\n', '--terminal=yes'] MPV_NEW_VERSION = False From a81a4ceb16336291f1642a63e4919f7184f96150 Mon Sep 17 00:00:00 2001 From: Et0h Date: Fri, 2 Oct 2015 09:25:30 +0100 Subject: [PATCH 016/134] Don't error when trying to change to next playlist item on empty playlist --- syncplay/client.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/syncplay/client.py b/syncplay/client.py index edc467e..56e40af 100644 --- a/syncplay/client.py +++ b/syncplay/client.py @@ -168,7 +168,7 @@ class SyncplayClient(object): def loadNextFileInPlaylist(self): # TODO: Fix for GUIDs & add path checks (and make more of code re-use?) - if len(self._playlist) == self._playlistIndex+1: + if self._playlistIndex is None or len(self._playlist) == self._playlistIndex+1: return filename = self._playlist[self._playlistIndex+1] if utils.isURL(filename): From 48a2ad65d6d2dcff2bf6cb8fa151be3eb3aed2dd Mon Sep 17 00:00:00 2001 From: Et0h Date: Fri, 2 Oct 2015 12:01:37 +0100 Subject: [PATCH 017/134] Fix resetPosition for streams on mpv (hopefully) --- syncplay/constants.py | 1 + syncplay/players/mpv.py | 13 ++++++++++++- 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/syncplay/constants.py b/syncplay/constants.py index dfe81ff..b9a982e 100644 --- a/syncplay/constants.py +++ b/syncplay/constants.py @@ -116,6 +116,7 @@ MPC_RETRY_WAIT_TIME = 0.01 MPC_MAX_RETRIES = 30 MPC_PAUSE_TOGGLE_DELAY = 0.05 MPV_NEWFILE_IGNORE_TIME = 1 +STREAM_ADDITIONAL_IGNORE_TIME = 10 MPV_LOCK_WAIT_TIME = 0.2 VLC_OPEN_MAX_WAIT_TIME = 15 VLC_MIN_PORT = 10000 diff --git a/syncplay/players/mpv.py b/syncplay/players/mpv.py index ff52af2..de3f9c8 100644 --- a/syncplay/players/mpv.py +++ b/syncplay/players/mpv.py @@ -3,6 +3,7 @@ import subprocess from syncplay.players.mplayer import MplayerPlayer from syncplay.messages import getMessage from syncplay import constants +from syncplay.utils import isURL import os, sys, time class MpvPlayer(MplayerPlayer): @@ -129,7 +130,12 @@ class NewMpvPlayer(OldMpvPlayer): if self.lastMPVPositionUpdate is None: return self._client.getGlobalPosition() + + if self._recentlyReset: + return self._position + diff = time.time() - self.lastMPVPositionUpdate + if diff > constants.MPV_UNRESPONSIVE_THRESHOLD: self.reactor.callFromThread(self._client.ui.showErrorMessage, getMessage("mpv-unresponsive-error").format(int(diff)), True) self.drop() @@ -184,10 +190,15 @@ class NewMpvPlayer(OldMpvPlayer): def openFile(self, filePath, resetPosition=False): if resetPosition: self.lastResetTime = time.time() + if isURL(filePath): + self.lastResetTime += constants.STREAM_ADDITIONAL_IGNORE_TIME self._loadFile(filePath) if self._paused != self._client.getGlobalPaused(): self.setPaused(self._client.getGlobalPaused()) - self.setPosition(self._client.getGlobalPosition()) + if resetPosition == False: + self.setPosition(self._client.getGlobalPosition()) + else: + self._storePosition(0) def _handleUnknownLine(self, line): self.mpvVersionErrorCheck(line) From c5753773411866cd8ab1313d69c4049c88e45c58 Mon Sep 17 00:00:00 2001 From: Et0h Date: Fri, 2 Oct 2015 18:34:12 +0100 Subject: [PATCH 018/134] Add context menu to list of who is playing what --- buildPy2exe.py | 3 +- syncplay/client.py | 4 +-- syncplay/ui/gui.py | 69 ++++++++++++++++++++++++++++++++++++++++------ 3 files changed, 65 insertions(+), 11 deletions(-) diff --git a/buildPy2exe.py b/buildPy2exe.py index a4e7da3..6cbc095 100644 --- a/buildPy2exe.py +++ b/buildPy2exe.py @@ -654,7 +654,8 @@ guiIcons = ['resources/accept.png', 'resources/arrow_undo.png', 'resources/clock 'resources/user_key.png', 'resources/lock.png', 'resources/key_go.png', 'resources/page_white_key.png', 'resources/tick.png', 'resources/lock_open.png', 'resources/empty_checkbox.png', 'resources/tick_checkbox.png', 'resources/world_explore.png', 'resources/application_get.png', 'resources/cog.png', - 'resources/film_go.png', 'resources/world_go.png', 'resources/arrow_refresh.png' + 'resources/film_go.png', 'resources/world_go.png', 'resources/arrow_refresh.png', + 'resources/world_add.png', 'resources/film_add.png' ] resources = ["resources/icon.ico", "resources/syncplay.png"] resources.extend(guiIcons) diff --git a/syncplay/client.py b/syncplay/client.py index 56e40af..c6c31cc 100644 --- a/syncplay/client.py +++ b/syncplay/client.py @@ -176,8 +176,8 @@ class SyncplayClient(object): if filename.startswith(URI): self._player.openFile(filename, resetPosition=True) return - self.ui.showErrorMessage(u"Could not load {} because it is not known as a safe path.".format(filename)) - return + self.ui.showErrorMessage(u"Could not load {} because it is not known as a safe path.".format(filename)) + return else: path = self.findFilenameInDirectories(filename) if path: diff --git a/syncplay/ui/gui.py b/syncplay/ui/gui.py index 50af7cf..0e8ed2b 100644 --- a/syncplay/ui/gui.py +++ b/syncplay/ui/gui.py @@ -532,10 +532,10 @@ class MainWindow(QtGui.QMainWindow): if isControlledRoom and not isController: useritem.setForeground(QtGui.QBrush(QtGui.QColor(constants.STYLE_NOTCONTROLLER_COLOR))) useritem.setFont(font) - useritem.setFlags(useritem.flags() & ~Qt.ItemIsEditable & ~Qt.ItemIsSelectable) - filenameitem.setFlags(filenameitem.flags() & ~Qt.ItemIsEditable & ~Qt.ItemIsSelectable) - filesizeitem.setFlags(filesizeitem.flags() & ~Qt.ItemIsEditable & ~Qt.ItemIsSelectable) - filedurationitem.setFlags(filedurationitem.flags() & ~Qt.ItemIsEditable & ~Qt.ItemIsSelectable) + useritem.setFlags(useritem.flags() & ~Qt.ItemIsEditable) + filenameitem.setFlags(filenameitem.flags() & ~Qt.ItemIsEditable) + filesizeitem.setFlags(filesizeitem.flags() & ~Qt.ItemIsEditable) + filedurationitem.setFlags(filedurationitem.flags() & ~Qt.ItemIsEditable) roomitem.appendRow((useritem, filesizeitem, filedurationitem, filenameitem)) self.listTreeModel = self._usertreebuffer self.listTreeView.setModel(self.listTreeModel) @@ -547,6 +547,51 @@ class MainWindow(QtGui.QMainWindow): MainWindow.FileSwitchManager.setFilenameWatchlist(self.newWatchlist) self.checkForDisabledDir() + def openRoomMenu(self, position): + # TODO: Deselect items after right click + indexes = self.listTreeView.selectedIndexes() + if sys.platform.startswith('win'): + resourcespath = utils.findWorkingDir() + "\\resources\\" + else: + resourcespath = utils.findWorkingDir() + "/resources/" + if len(indexes) > 0: + item = self.listTreeView.selectedIndexes()[0] + else: + return + + menu = QtGui.QMenu() + username = item.sibling(item.row(), 0).data() + if username == self._syncplayClient.userlist.currentUser.username: + shortUsername = "your" # TODO: Use messages.py + elif len(username) < 15: + shortUsername = u"{}'s".format(username) + else: + shortUsername = "{}'s...".format(username[0:12]) + + filename = item.sibling(item.row(), 3).data() + while item.parent().row() != -1: + item = item.parent() + roomToJoin = item.sibling(item.row(), 0).data() + if roomToJoin <> self._syncplayClient.getRoom(): + menu.addAction("Join room {}".format(roomToJoin), lambda: self.joinRoom(roomToJoin)) + elif username and filename and filename <> getMessage("nofile-note"): + if isURL(filename): + menu.addAction(QtGui.QPixmap(resourcespath + "world_add.png"), "Add {} stream to playlist".format(shortUsername), lambda: self.addStreamToPlaylist(filename)) + else: + menu.addAction(QtGui.QPixmap(resourcespath + "film_add.png"), "Add {} file to playlist".format(shortUsername), lambda: self.addStreamToPlaylist(filename)) + + if self._syncplayClient.userlist.currentUser.file is None or filename <> self._syncplayClient.userlist.currentUser.file["name"]: + if isURL(filename): + menu.addAction(QtGui.QPixmap(resourcespath + "world_go.png"), "Open stream {} stream".format(shortUsername), lambda: self.openFile(filename)) + else: + pathFound = MainWindow.FileSwitchManager.findFilepath(filename) + if pathFound: + menu.addAction(QtGui.QPixmap(resourcespath + "film_go.png"), "Open {} file".format(shortUsername), lambda: self.openFile(pathFound)) + else: + return + menu.exec_(self.listTreeView.viewport().mapToGlobal(position)) + + def checkForDisabledDir(self): if MainWindow.FileSwitchManager.disabledDir is not None and MainWindow.FileSwitchManager.currentWindow is not None: self.showErrorMessage(getMessage("folder-search-timeout-error").format(MainWindow.FileSwitchManager.disabledDir)) @@ -590,7 +635,7 @@ class MainWindow(QtGui.QMainWindow): if pathFound: self._syncplayClient._player.openFile(pathFound) else: - self.ui.showErrorMessage(u"Could not find file {} for playlist switch!".format(filename)) + self._syncplayClient.ui.showErrorMessage(u"Could not find file {} for playlist switch!".format(filename)) def roomClicked(self, item): username = item.sibling(item.row(), 0).data() @@ -848,6 +893,9 @@ class MainWindow(QtGui.QMainWindow): window.listTreeView.setModel(window.listTreeModel) window.listTreeView.setIndentation(21) window.listTreeView.doubleClicked.connect(self.roomClicked) + self.listTreeView.setContextMenuPolicy(Qt.CustomContextMenu) + self.listTreeView.setSelectionBehavior(QtGui.QAbstractItemView.SelectRows) + self.listTreeView.customContextMenuRequested.connect(self.openRoomMenu) window.listlabel = QtGui.QLabel(getMessage("userlist-heading-label")) window.listFrame = QtGui.QFrame() window.listFrame.setLineWidth(0) @@ -1236,10 +1284,15 @@ class MainWindow(QtGui.QMainWindow): self.playlist.addItem(os.path.basename(filePath)) else: self.playlist.insertItem(index, os.path.basename(filePath)) + self.playlistUpdated() - def addStreamToPlaylist(self, filePath): - self.removePlayListNote() - self.playlist.addItem(os.path.basename(filePath)) + def openFile(self, filePath, resetPosition=False): + self._syncplayClient._player.openFile(filePath, resetPosition) + + def addStreamToPlaylist(self, streamURI): + self.removePlaylistNote() + self.playlist.addItem(streamURI) + self.playlistUpdated() def removePlaylistNote(self): if not self.clearedPlaylistNote: From b35cf3b3140c691ba1c3d36932f12b358ed1fba9 Mon Sep 17 00:00:00 2001 From: Et0h Date: Fri, 2 Oct 2015 22:58:14 +0100 Subject: [PATCH 019/134] Improve reliability of GUI playlist change handling --- syncplay/ui/gui.py | 47 ++++++++++++++++++++-------------------------- 1 file changed, 20 insertions(+), 27 deletions(-) diff --git a/syncplay/ui/gui.py b/syncplay/ui/gui.py index 0e8ed2b..9819ebc 100644 --- a/syncplay/ui/gui.py +++ b/syncplay/ui/gui.py @@ -87,7 +87,7 @@ class UserlistItemDelegate(QtGui.QStyledItemDelegate): class MainWindow(QtGui.QMainWindow): insertPosition = None playlistState = [] - blockPlaylistUpdateNotifications = False + updatingPlaylist = False def setPlaylistInsertPosition(self, newPosition): if MainWindow.insertPosition <> newPosition: @@ -147,7 +147,6 @@ class MainWindow(QtGui.QMainWindow): window.addFolderToPlaylist(dropfilepath) else: super(MainWindow.PlaylistWidget, self).dropEvent(event) - window.playlistUpdated() class PlaylistWidget(QtGui.QListWidget): selfWindow = None @@ -159,16 +158,6 @@ class MainWindow(QtGui.QMainWindow): window = self.parent().parent().parent().parent().parent().parent() window.setPlaylistInsertPosition(None) - def rowsMoved(self, sourceParent, sourceStart, sourceEnd, destinationParent, destinationRow): - if self.selfWindow: - super(MainWindow.PlaylistWidget, self).rowsMoved(sourceParent, sourceStart, sourceEnd, destinationParent, destinationRow) - self.selfWindow.playlistUpdated() - - def rowsRemoved(self, parent, start, end): - if self.selfWindow: - super(MainWindow.PlaylistWidget, self).rowsRemoved(parent, start, end) - self.selfWindow.playlistUpdated() - def forceUpdate(self): root = self.rootIndex() self.dataChanged(root, root) @@ -179,10 +168,14 @@ class MainWindow(QtGui.QMainWindow): else: super(MainWindow.PlaylistWidget, self).keyPressEvent(event) + def updatePlaylist(self, newPlaylist): + for index in xrange(self.count()): + self.takeItem(0) + self.insertItems(0, newPlaylist) + def _remove_selected_items(self): for item in self.selectedItems(): self.takeItem(self.row(item)) - self.selfWindow.playlistUpdated() def dragEnterEvent(self, event): data = event.mimeData() @@ -204,7 +197,6 @@ class MainWindow(QtGui.QMainWindow): window.setPlaylistInsertPosition(indexRow) else: super(MainWindow.PlaylistWidget, self).dragMoveEvent(event) - self.selfWindow.playlistUpdated() def dropEvent(self, event): window = self.parent().parent().parent().parent().parent().parent() @@ -228,7 +220,6 @@ class MainWindow(QtGui.QMainWindow): window.addFolderToPlaylist(dropfilepath) else: super(MainWindow.PlaylistWidget, self).dropEvent(event) - self.selfWindow.playlistUpdated() class FileSwitchManager(object): def __init__(self): @@ -853,14 +844,16 @@ class MainWindow(QtGui.QMainWindow): def getPlaylistState(self): playlistItems = [] for playlistItem in xrange(self.playlist.count()): - platlistItemText = self.playlist.item(playlistItem).text() - if platlistItemText <> u"Drag file here to add it to the shared playlist.": - playlistItems.append(platlistItemText) + playlistItemText = self.playlist.item(playlistItem).text() + if playlistItemText <> u"Drag file here to add it to the shared playlist.": + playlistItems.append(playlistItemText) return playlistItems - def playlistUpdated(self): + def playlistChangeCheck(self): + if self.updatingPlaylist: + return newPlaylist = self.getPlaylistState() - if newPlaylist <> self.playlistState and self._syncplayClient and not self.blockPlaylistUpdateNotifications: + if newPlaylist <> self.playlistState and self._syncplayClient and not self.updatingPlaylist: self.playlistState = newPlaylist self._syncplayClient.changePlaylist(newPlaylist) @@ -966,6 +959,8 @@ class MainWindow(QtGui.QMainWindow): window.playlist.setDefaultDropAction(Qt.MoveAction) window.playlist.setDragDropMode(QtGui.QAbstractItemView.InternalMove) window.playlist.doubleClicked.connect(self.playlistItemClicked) + self.playlistUpdateTimer = task.LoopingCall(self.playlistChangeCheck) + self.playlistUpdateTimer.start(0.1, True) noteFont = QtGui.QFont() noteFont.setItalic(True) playlistItem = QtGui.QListWidgetItem(u"Drag file here to add it to the shared playlist.") @@ -1270,12 +1265,12 @@ class MainWindow(QtGui.QMainWindow): self._syncplayClient.setPosition(0) def setPlaylist(self, newPlaylist): - if newPlaylist == [] and not self.clearedPlaylistNote: + if newPlaylist == self.playlistState: return - self.blockPlaylistUpdateNotifications = True - self.playlist.clear() - self.playlist.insertItems(0, newPlaylist) - self.blockPlaylistUpdateNotifications = False + self.updatingPlaylist = True + self.playlistState = newPlaylist + self.playlist.updatePlaylist(newPlaylist) + self.updatingPlaylist = False def addFileToPlaylist(self, filePath, index = -1): if os.path.isfile(filePath): @@ -1284,7 +1279,6 @@ class MainWindow(QtGui.QMainWindow): self.playlist.addItem(os.path.basename(filePath)) else: self.playlist.insertItem(index, os.path.basename(filePath)) - self.playlistUpdated() def openFile(self, filePath, resetPosition=False): self._syncplayClient._player.openFile(filePath, resetPosition) @@ -1292,7 +1286,6 @@ class MainWindow(QtGui.QMainWindow): def addStreamToPlaylist(self, streamURI): self.removePlaylistNote() self.playlist.addItem(streamURI) - self.playlistUpdated() def removePlaylistNote(self): if not self.clearedPlaylistNote: From c41aa5819a4bf770dfae86f91a2fc4c9ec0df993 Mon Sep 17 00:00:00 2001 From: Et0h Date: Sat, 3 Oct 2015 00:33:42 +0100 Subject: [PATCH 020/134] Re-send own old playlist if reconnecting to server --- syncplay/client.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/syncplay/client.py b/syncplay/client.py index c6c31cc..46f923a 100644 --- a/syncplay/client.py +++ b/syncplay/client.py @@ -487,6 +487,7 @@ class SyncplayClient(object): pass def changePlaylist(self, files, username = None): + oldPlaylistIsEmpty = self._playlist == [] self._playlist = files if username is None and self._protocol and self._protocol.logged: @@ -494,6 +495,9 @@ class SyncplayClient(object): elif username != self.getUsername(): self.ui.setPlaylist(self._playlist) self.ui.showMessage(u"{} updated the playlist".format(username)) + elif oldPlaylistIsEmpty and self._playlist != []: # To catch being re-sent own playlist after disconnect + self.ui.setPlaylist(self._playlist) + def __executePrivacySettings(self, filename, size): if self._config['filenamePrivacyMode'] == PRIVACY_SENDHASHED_MODE: From 48923ec862b2e14f68532c254f9953cd275b3d33 Mon Sep 17 00:00:00 2001 From: Et0h Date: Sat, 3 Oct 2015 00:38:43 +0100 Subject: [PATCH 021/134] Use consistent clearing code for removePlaylistNote --- syncplay/ui/gui.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/syncplay/ui/gui.py b/syncplay/ui/gui.py index 9819ebc..2beac03 100644 --- a/syncplay/ui/gui.py +++ b/syncplay/ui/gui.py @@ -1289,7 +1289,8 @@ class MainWindow(QtGui.QMainWindow): def removePlaylistNote(self): if not self.clearedPlaylistNote: - self.playlist.clear() + for index in xrange(self.playlist.count()): + self.playlist.takeItem(0) self.clearedPlaylistNote = True def addFolderToPlaylist(self, folderPath): From eb859ccf5e67b4cc55eba00a5917d5a5f8c6b937 Mon Sep 17 00:00:00 2001 From: Et0h Date: Sun, 4 Oct 2015 20:29:02 +0100 Subject: [PATCH 022/134] Add initial code for playlist context menu --- buildPy2exe.py | 2 +- resources/delete.png | Bin 0 -> 715 bytes resources/film_add.png | Bin 0 -> 739 bytes resources/world_add.png | Bin 0 -> 940 bytes syncplay/ui/gui.py | 42 +++++++++++++++++++++++++++++++++++++--- 5 files changed, 40 insertions(+), 4 deletions(-) create mode 100644 resources/delete.png create mode 100644 resources/film_add.png create mode 100644 resources/world_add.png diff --git a/buildPy2exe.py b/buildPy2exe.py index 6cbc095..b7c145b 100644 --- a/buildPy2exe.py +++ b/buildPy2exe.py @@ -655,7 +655,7 @@ guiIcons = ['resources/accept.png', 'resources/arrow_undo.png', 'resources/clock 'resources/tick.png', 'resources/lock_open.png', 'resources/empty_checkbox.png', 'resources/tick_checkbox.png', 'resources/world_explore.png', 'resources/application_get.png', 'resources/cog.png', 'resources/film_go.png', 'resources/world_go.png', 'resources/arrow_refresh.png', - 'resources/world_add.png', 'resources/film_add.png' + 'resources/world_add.png', 'resources/film_add.png', 'resources/delete.png' ] resources = ["resources/icon.ico", "resources/syncplay.png"] resources.extend(guiIcons) diff --git a/resources/delete.png b/resources/delete.png new file mode 100644 index 0000000000000000000000000000000000000000..08f249365afd29594b51210c6e21ba253897505d GIT binary patch literal 715 zcmV;+0yO=JP)C4}Mrzlg<+1Y8PEBfUp0jJpx4B>@E+cy3`^(Gw`Mf+2&yxZm<$to~Vpgvg&QKNR z_f#1(r6svZt%iF?s+n<8X?B&!h3g9Dbb8_=MX}!;HiQSAh`bp^WMl~Z-44teO7W_Y zV4thSL{h;rJY7!l3%5J4H1!tIzB`Dv+YxO(haWeausGZYkI8^hWj6mzo=L0{%;yxzh{5!Htr?51 zvG|W62MzC8BZ76hRpCyO2zOn<%e)K>NHge!-~)Ap33OdWw6hsLYbCxGNt0%wk_2z7 zfyYvXheSG)5HRK1VB~%mq7Dmurw#bi@hEcOr3&G1ZiF*$M=&9nB#VNf&Q^r$4G5kp zTURh&s)E0%5&hyVD}sp<72~zmAY`Y(9aqO6CXF%=zFHGzO-A&I(pE}v70YQxCPJ{Y z4L+?5-crdLn3ZRPEs!A4ehEY3ZRpL~w9>@aMN+{F4dI@v&>(QDHQum!mG~E^$OS8l z!7?%Uwib*ROP67Hw`ika)gX-(8Ia`-u_IEhxG7U<13kSsMW+$lbb2dUMm5p6pa}cjgA+U$^mJ^AjD?&bdi)8~y+Q002ovPDHLkV1g8IMc@Dc literal 0 HcmV?d00001 diff --git a/resources/film_add.png b/resources/film_add.png new file mode 100644 index 0000000000000000000000000000000000000000..40d681feba594596c64e0947b8cae5ce9086f919 GIT binary patch literal 739 zcmV<90v!E`P)GL6il?1Z7P2*|7g?{OO9$@TbH1?j^i>BB&)xBSKKI=BdzS+s=YPijNo#8> z*V5AR)9?3h9JknJv&E~ct9MK$)0#*?;BA_wANhPfI2?{62Z2#lRW+c~=~hJo0)xOm zJRVPmX1?&(C%^N7u`S*k-QpKxJ^Z(|9^Pm)X0%%ELPbRdL;|nZyG-C)6h(2$>2CPu zHV}LVG)@CAV;R;g61NZ@w6lP;HQ#_4pj`q>&t7&BZ`>!@b{&p2>% z;uFft%U6_2<&0b|Pl^NtP7rvQyz7!PR)fwp2bz|(sCys8g|R5kjl@7;mcYXji6kKs z)YjH=1P+m;7l+bqc)r<%r<)y!q^=_J`7#F6mmrhL*y7^iP(eWfClc80_O#V%Wl0vt zwop`5#OLSdv$?ssX_0`yoW)`Zk)#&_^V?|u0JO~k^enKn{t&OHI?)`q?>5}4fxY|e zuBk)XbwFS?n-d0uVVESnbhw2(ueuR>LE$YI0sECfc%li8@ENF@PxkK%^m=`=va)i9 zBrR&;%I)(Q{dgav@drZx=%4f;JlQA+e(wti%o3O;Nv|Gm!E!^6DEAou9&c76g5d9W zURG8Hg+eh;lK%B18OT0MwP%M?*Rq2fby;=r6z($h=w)1caPPQFyo1ux(o9K72}LBL z^bM2>7yD0RfN}3VS(t_E0~tuA()Gf^!c1OX-ojCTB3az0XgskiY+KQ=H}?tm{{Txk VbfZtx1EK%`002ovPDHLkV1hC*Sxo={ literal 0 HcmV?d00001 diff --git a/resources/world_add.png b/resources/world_add.png new file mode 100644 index 0000000000000000000000000000000000000000..6d0d7f74c0d89a5d1975eb65c3e048ace0290daf GIT binary patch literal 940 zcmV;d15^BoP)%?%c;Y_xOF9QVIY7fRV}JLEt>l17rXb zC;_)rpe}tj`1Q{K005d&3V@NxVG|ey-h3^0CX$Y2uaOh_TG3L)SA z^5q{Y0Gd*Yk;&l@aHG5N$^I8}&tW+cTs)R7jj)QaYX>Od5yNKFonUrjc24->neX1Z zQqlo{(Nhh_`Ujf3DM$}r*APOdD#F-=DYy}~ofvDS5RWFFraSw1hv)j=0~p1#4+emb z-#PZGF)#Poq)IgqRGb6_H-zOte!Wa-f1gNt7p*Q&Ut=5B7iU_(xixy%=p8=!>AB|K z6PgiYQx~9`I4Bd`-ZH2}_By5ADbi^dQ@DgQMMO6+E7$01>qP33 z&Vy%>nonQ*X>`xY>R-KbdE9gTpT-w&@@z{Nbw&;8L_l3wQX6(ihFpr3Ekas?*N>kE z;DZ;w;BZq1Ku8DhUH{!*^LOU{T=|Q0Z9TN6Ob)~qjYgRU-KJPBvam75g{wmVTo@nX z-u4`Tqba2Tcl#FWG)4l2eAPSU$OVbxOKKviB! 0: + item = self.playlist.selectedIndexes()[0] + else: + item = None + menu = QtGui.QMenu() + + if item: + firstFile = item.sibling(item.row(), 0).data() + if self._syncplayClient.userlist.currentUser.file is None or firstFile <> self._syncplayClient.userlist.currentUser.file["name"]: + if isURL(firstFile): + menu.addAction(QtGui.QPixmap(resourcespath + "world_go.png"), "Open stream", lambda: self.openFile(firstFile)) + else: + pathFound = MainWindow.FileSwitchManager.findFilepath(firstFile) + if pathFound: + menu.addAction(QtGui.QPixmap(resourcespath + "film_go.png"), "Open file", lambda: self.openFile(pathFound)) + menu.addAction(QtGui.QPixmap(resourcespath + "delete.png"), "Remove from playlist", lambda: self.deleteSelectedPlaylistItems()) + menu.addSeparator() + menu.addAction(QtGui.QPixmap(resourcespath + "arrow_undo.png"), "Undo last change to playlist", lambda: self.undoPlaylistChange()) + menu.exec_(self.playlist.viewport().mapToGlobal(position)) + + def openRoomMenu(self, position): # TODO: Deselect items after right click indexes = self.listTreeView.selectedIndexes() @@ -573,7 +603,7 @@ class MainWindow(QtGui.QMainWindow): if self._syncplayClient.userlist.currentUser.file is None or filename <> self._syncplayClient.userlist.currentUser.file["name"]: if isURL(filename): - menu.addAction(QtGui.QPixmap(resourcespath + "world_go.png"), "Open stream {} stream".format(shortUsername), lambda: self.openFile(filename)) + menu.addAction(QtGui.QPixmap(resourcespath + "world_go.png"), "Open {} stream".format(shortUsername), lambda: self.openFile(filename)) else: pathFound = MainWindow.FileSwitchManager.findFilepath(filename) if pathFound: @@ -959,6 +989,9 @@ class MainWindow(QtGui.QMainWindow): window.playlist.setDefaultDropAction(Qt.MoveAction) window.playlist.setDragDropMode(QtGui.QAbstractItemView.InternalMove) window.playlist.doubleClicked.connect(self.playlistItemClicked) + window.playlist.setContextMenuPolicy(Qt.CustomContextMenu) + window.playlist.setSelectionBehavior(QtGui.QAbstractItemView.SelectRows) + window.playlist.customContextMenuRequested.connect(self.openPlaylistMenu) self.playlistUpdateTimer = task.LoopingCall(self.playlistChangeCheck) self.playlistUpdateTimer.start(0.1, True) noteFont = QtGui.QFont() @@ -1295,6 +1328,9 @@ class MainWindow(QtGui.QMainWindow): def addFolderToPlaylist(self, folderPath): self.showErrorMessage("Add Folder {}".format(folderPath)) + + def deleteSelectedPlaylistItems(self): + self.playlist.remove_selected_items() def saveSettings(self): settings = QSettings("Syncplay", "MainWindow") From 4c4c3eb59dddfaaf2bf3350eac86297fc326b9ef Mon Sep 17 00:00:00 2001 From: Uriziel Date: Sun, 11 Oct 2015 22:19:39 +0200 Subject: [PATCH 023/134] Fixed bugs with file switching in playlist --- syncplay/client.py | 88 +++++++++++++++++++++++++++------------------- 1 file changed, 51 insertions(+), 37 deletions(-) diff --git a/syncplay/client.py b/syncplay/client.py index 46f923a..fc7696b 100644 --- a/syncplay/client.py +++ b/syncplay/client.py @@ -5,7 +5,7 @@ import re import sys import ast from twisted.internet.protocol import ClientFactory -from twisted.internet import reactor, task +from twisted.internet import reactor, task, defer from functools import wraps from copy import deepcopy from syncplay.protocols import SyncClientProtocol @@ -115,6 +115,7 @@ class SyncplayClient(object): self._playlist = [] self._playlistIndex = None + self.__playerReady = defer.Deferred() self._warnings = self._WarningManager(self._player, self.userlist, self.ui, self) if constants.LIST_RELATIVE_CONFIGS and self._config.has_key('loadedRelativePaths') and self._config['loadedRelativePaths']: @@ -139,6 +140,7 @@ class SyncplayClient(object): if not self._player.secondaryOSDSupported: constants.OSD_WARNING_MESSAGE_DURATION = constants.NO_SECONDARY_OSD_WARNING_DURATION self.scheduleAskPlayer() + self.__playerReady.callback(player) def scheduleAskPlayer(self, when=constants.PLAYER_ASK_DELAY): self._askPlayerTimer = task.LoopingCall(self.askPlayer) @@ -192,34 +194,12 @@ class SyncplayClient(object): pauseChange, seeked = self._determinePlayerStateChange(paused, position) self._playerPosition = position self._playerPaused = paused - currentLength = self.userlist.currentUser.file["duration"] if self.userlist.currentUser.file else None - if pauseChange and paused and currentLength and currentLength > 10 and abs(position - currentLength ) < 1: + currentLength = self.userlist.currentUser.file["duration"] if self.userlist.currentUser.file else 0 + if pauseChange and paused and currentLength > 10 and abs(position - currentLength ) < 5: self.loadNextFileInPlaylist() + elif pauseChange and utils.meetsMinVersion(self.serverVersion, constants.USER_READY_MIN_VERSION): + pauseChange = self._toggleReady(pauseChange, paused) - # TODO: if position +- 1s of the end of a file AND has next file in playlist -> rewind to 0 and switch file - if pauseChange and utils.meetsMinVersion(self.serverVersion, constants.USER_READY_MIN_VERSION): - if not self.userlist.currentUser.canControl(): - self._player.setPaused(self._globalPaused) - self.toggleReady(manuallyInitiated=True) - self._playerPaused = self._globalPaused - pauseChange = False - if self.userlist.currentUser.isReady(): - self.ui.showMessage(getMessage("set-as-ready-notification")) - else: - self.ui.showMessage(getMessage("set-as-not-ready-notification")) - elif not paused and not self.instaplayConditionsMet(): - paused = True - self._player.setPaused(paused) - self._playerPaused = paused - self.changeReadyState(True, manuallyInitiated=True) - pauseChange = False - self.ui.showMessage(getMessage("ready-to-unpause-notification")) - else: - lastPausedDiff = time.time() - self.lastPausedOnLeaveTime if self.lastPausedOnLeaveTime else None - if lastPausedDiff is not None and lastPausedDiff < constants.LAST_PAUSED_DIFF_THRESHOLD: - self.lastPausedOnLeaveTime = None - else: - self.changeReadyState(not self.getPlayerPaused(), manuallyInitiated=False) if self._lastGlobalUpdate: self._lastPlayerUpdate = time.time() if (pauseChange or seeked) and self._protocol: @@ -227,6 +207,31 @@ class SyncplayClient(object): self.playerPositionBeforeLastSeek = self.getGlobalPosition() self._protocol.sendState(self.getPlayerPosition(), self.getPlayerPaused(), seeked, None, True) + def _toggleReady(self, pauseChange, paused): + if not self.userlist.currentUser.canControl(): + self._player.setPaused(self._globalPaused) + self.toggleReady(manuallyInitiated=True) + self._playerPaused = self._globalPaused + pauseChange = False + if self.userlist.currentUser.isReady(): + self.ui.showMessage(getMessage("set-as-ready-notification")) + else: + self.ui.showMessage(getMessage("set-as-not-ready-notification")) + elif not paused and not self.instaplayConditionsMet(): + paused = True + self._player.setPaused(paused) + self._playerPaused = paused + self.changeReadyState(True, manuallyInitiated=True) + pauseChange = False + self.ui.showMessage(getMessage("ready-to-unpause-notification")) + else: + lastPausedDiff = time.time() - self.lastPausedOnLeaveTime if self.lastPausedOnLeaveTime else None + if lastPausedDiff is not None and lastPausedDiff < constants.LAST_PAUSED_DIFF_THRESHOLD: + self.lastPausedOnLeaveTime = None + else: + self.changeReadyState(not self.getPlayerPaused(), manuallyInitiated=False) + return pauseChange + def getLocalState(self): paused = self.getPlayerPaused() if self._config['dontSlowDownWithMe']: @@ -436,7 +441,6 @@ class SyncplayClient(object): self.userlist.currentUser.setFile(filename, duration, size, path) self.sendFile() - # TODO: Fix for GUIDS try: index = self._playlist.index(filename) self.changeToPlaylistIndex(index) @@ -456,14 +460,22 @@ class SyncplayClient(object): def changeToPlaylistIndex(self, index, username = None): path = None - if self._playlistIndex == index: + if index is None: return + try: + filename = self._playlist[index] + if username is not None and self.userlist.currentUser.file and filename == self.userlist.currentUser.file['name']: + return + except IndexError: + pass + if self._player is None: + self.__playerReady.addCallback(lambda x: self.changeToPlaylistIndex(index, username)) return self._playlistIndex = index if username is None and self._protocol and self._protocol.logged: self._protocol.setPlaylistIndex(index) - elif username != self.getUsername(): + else: self.ui.showMessage(u"{} changed the playlist selection".format(username)) # TODO: Display info about playlist file change try: @@ -487,17 +499,19 @@ class SyncplayClient(object): pass def changePlaylist(self, files, username = None): - oldPlaylistIsEmpty = self._playlist == [] + try: + filename = self._playlist[self._playlistIndex] + newIndex = files.index(filename) + except: + newIndex = 0 self._playlist = files if username is None and self._protocol and self._protocol.logged: self._protocol.setPlaylist(files) - elif username != self.getUsername(): + self.changeToPlaylistIndex(newIndex) + else: self.ui.setPlaylist(self._playlist) self.ui.showMessage(u"{} updated the playlist".format(username)) - elif oldPlaylistIsEmpty and self._playlist != []: # To catch being re-sent own playlist after disconnect - self.ui.setPlaylist(self._playlist) - def __executePrivacySettings(self, filename, size): if self._config['filenamePrivacyMode'] == PRIVACY_SENDHASHED_MODE: @@ -645,7 +659,7 @@ class SyncplayClient(object): def changeAutoplayState(self, newState): self.autoPlay = newState self.autoplayCheck() - + def changeAutoPlayThrehsold(self, newThreshold): oldAutoplayConditionsMet = self.autoplayConditionsMet() self.autoPlayThreshold = newThreshold @@ -833,7 +847,7 @@ class SyncplayClient(object): def _checkIfYouAreOnlyUserInRoomWhoSupportsReadiness(self): self._userlist._onlyUserInRoomWhoSupportsReadiness() - + def _checkIfYouReAloneInTheRoom(self, OSDOnly): if self._userlist.areYouAloneInRoom(): self._ui.showOSDMessage(getMessage("alone-in-the-room"), constants.WARNING_OSD_MESSAGES_LOOP_INTERVAL, secondaryOSD=True) From d65f7eeb127813d5a9bec9a0b3c26cd4bc7e8cb5 Mon Sep 17 00:00:00 2001 From: Et0h Date: Mon, 12 Oct 2015 13:51:43 +0100 Subject: [PATCH 024/134] Fix bug resetting playlist if you dragged file into playlist created by others --- syncplay/ui/gui.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/syncplay/ui/gui.py b/syncplay/ui/gui.py index 8da9b83..cedce37 100644 --- a/syncplay/ui/gui.py +++ b/syncplay/ui/gui.py @@ -1301,6 +1301,8 @@ class MainWindow(QtGui.QMainWindow): if newPlaylist == self.playlistState: return self.updatingPlaylist = True + if len(newPlaylist) > 0: + self.clearedPlaylistNote = True self.playlistState = newPlaylist self.playlist.updatePlaylist(newPlaylist) self.updatingPlaylist = False From 63731811aebb05633e043484122dc52e1c0ad1d9 Mon Sep 17 00:00:00 2001 From: Et0h Date: Mon, 12 Oct 2015 13:58:44 +0100 Subject: [PATCH 025/134] Do not treat mplayerc.exe (Media Player Classic) as a synonym for mplayer2 --- syncplay/players/mplayer.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/syncplay/players/mplayer.py b/syncplay/players/mplayer.py index dd35762..4c505a3 100644 --- a/syncplay/players/mplayer.py +++ b/syncplay/players/mplayer.py @@ -216,7 +216,7 @@ class MplayerPlayer(BasePlayer): @staticmethod def isValidPlayerPath(path): - if "mplayer" in path and MplayerPlayer.getExpandedPath(path): + if "mplayer" in path and MplayerPlayer.getExpandedPath(path) and not "mplayerc.exe" in path: # "mplayerc.exe" is Media Player Classic (not Home Cinema): return True return False From aceba941fa08a21547589c087fc278f10a4c4d1f Mon Sep 17 00:00:00 2001 From: Et0h Date: Mon, 12 Oct 2015 20:47:51 +0100 Subject: [PATCH 026/134] By default only autoplay if all filenames are the same --- syncplay/client.py | 11 +++++++---- syncplay/ui/ConfigurationGetter.py | 4 +++- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/syncplay/client.py b/syncplay/client.py index fc7696b..ebbc9f9 100644 --- a/syncplay/client.py +++ b/syncplay/client.py @@ -688,7 +688,7 @@ class SyncplayClient(object): return False def autoplayConditionsMet(self): - return self._playerPaused and self.autoPlay and self.userlist.currentUser.canControl() and self.userlist.isReadinessSupported() and self.userlist.areAllUsersInRoomReady() and self.autoPlayThreshold and self.userlist.usersInRoomCount() >= self.autoPlayThreshold + return self._playerPaused and self.autoPlay and self.userlist.currentUser.canControl() and self.userlist.isReadinessSupported() and self.userlist.areAllUsersInRoomReady(requireSameFilenames=self._config["autoplayRequireSameFilenames"]) and self.autoPlayThreshold and self.userlist.usersInRoomCount() >= self.autoPlayThreshold def autoplayTimerIsRunning(self): return self.autoplayTimer.running @@ -1117,14 +1117,17 @@ class SyncplayUserlist(object): user = self._users[username] user.setControllerStatus(True) - def areAllUsersInRoomReady(self): + def areAllUsersInRoomReady(self, requireSameFilenames=False): if not self.currentUser.canControl(): return True if not self.currentUser.isReady(): return False for user in self._users.itervalues(): - if user.room == self.currentUser.room and user.isReadyWithFile() == False: - return False + if user.room == self.currentUser.room: + if user.isReadyWithFile() == False: + return False + elif requireSameFilenames and (self.currentUser.file is None or user.file is None or not utils.sameFilename(self.currentUser.file['name'], user.file['name'])): + return False return True def areAllOtherUsersInRoomReady(self): diff --git a/syncplay/ui/ConfigurationGetter.py b/syncplay/ui/ConfigurationGetter.py index 0fa6284..d185086 100755 --- a/syncplay/ui/ConfigurationGetter.py +++ b/syncplay/ui/ConfigurationGetter.py @@ -51,6 +51,7 @@ class ConfigurationGetter(object): "unpauseAction": constants.UNPAUSE_IFOTHERSREADY_MODE, "autoplayInitialState" : None, "autoplayMinUsers" : -1, + "autoplayRequireSameFilenames": True, "clearGUIData": False, "language" : "", "checkForUpdatesAutomatically" : None, @@ -87,6 +88,7 @@ class ConfigurationGetter(object): "dontSlowDownWithMe", "pauseOnLeave", "readyAtStart", + "autoplayRequireSameFilenames", "clearGUIData", "rewindOnDesync", "slowOnDesync", @@ -121,7 +123,7 @@ class ConfigurationGetter(object): self._iniStructure = { "server_data": ["host", "port", "password"], - "client_settings": ["name", "room", "playerPath", "perPlayerArguments", "slowdownThreshold", "rewindThreshold", "fastforwardThreshold", "slowOnDesync", "rewindOnDesync", "fastforwardOnDesync", "dontSlowDownWithMe", "forceGuiPrompt", "filenamePrivacyMode", "filesizePrivacyMode", "unpauseAction", "pauseOnLeave", "readyAtStart", "autoplayMinUsers", "autoplayInitialState", "mediaSearchDirectories"], + "client_settings": ["name", "room", "playerPath", "perPlayerArguments", "slowdownThreshold", "rewindThreshold", "fastforwardThreshold", "slowOnDesync", "rewindOnDesync", "fastforwardOnDesync", "dontSlowDownWithMe", "forceGuiPrompt", "filenamePrivacyMode", "filesizePrivacyMode", "unpauseAction", "pauseOnLeave", "readyAtStart", "autoplayMinUsers", "autoplayInitialState", "autoplayRequireSameFilenames", "mediaSearchDirectories"], "gui": ["showOSD", "showOSDWarnings", "showSlowdownOSD", "showDifferentRoomOSD", "showSameRoomOSD", "showNonControllerOSD", "showDurationNotification"], "general": ["language", "checkForUpdatesAutomatically", "lastCheckedForUpdates"] } From 2d076de3f61c45cd754c50d6d16f3ccc401aa1a4 Mon Sep 17 00:00:00 2001 From: Et0h Date: Mon, 12 Oct 2015 21:46:16 +0100 Subject: [PATCH 027/134] Remove '--demuxer-mkv-probe-video-duration=yes' for mpv as some had problems with it --- syncplay/constants.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/syncplay/constants.py b/syncplay/constants.py index b9a982e..f6c6230 100644 --- a/syncplay/constants.py +++ b/syncplay/constants.py @@ -143,7 +143,7 @@ USERLIST_GUI_USERNAME_COLUMN = 0 USERLIST_GUI_FILENAME_COLUMN = 3 MPLAYER_SLAVE_ARGS = ['-slave', '--hr-seek=always', '-nomsgcolor', '-msglevel', 'all=1:global=4:cplayer=4', '-af-add', 'scaletempo'] -MPV_ARGS = ['--force-window', '--idle', '--hr-seek=always', '--keep-open', '--af-add=scaletempo', '--demuxer-mkv-probe-video-duration=yes'] +MPV_ARGS = ['--force-window', '--idle', '--hr-seek=always', '--keep-open', '--af-add=scaletempo'] MPV_SLAVE_ARGS = ['--quiet', '--input-terminal=no', '--input-file=/dev/stdin'] MPV_SLAVE_ARGS_NEW = ['--term-playing-msg=\nANS_filename=${filename}\nANS_length=${=length}\nANS_path=${path}\n', '--terminal=yes'] MPV_NEW_VERSION = False From f4a0bbe967baaf47e350796e29e87e8044de5d9b Mon Sep 17 00:00:00 2001 From: Et0h Date: Wed, 14 Oct 2015 11:54:31 +0100 Subject: [PATCH 028/134] Reduce mpv lock wait time due to getCalculatedPosition --- syncplay/constants.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/syncplay/constants.py b/syncplay/constants.py index f6c6230..8a403c5 100644 --- a/syncplay/constants.py +++ b/syncplay/constants.py @@ -117,7 +117,7 @@ MPC_MAX_RETRIES = 30 MPC_PAUSE_TOGGLE_DELAY = 0.05 MPV_NEWFILE_IGNORE_TIME = 1 STREAM_ADDITIONAL_IGNORE_TIME = 10 -MPV_LOCK_WAIT_TIME = 0.2 +MPV_LOCK_WAIT_TIME = 0.05 VLC_OPEN_MAX_WAIT_TIME = 15 VLC_MIN_PORT = 10000 VLC_MAX_PORT = 55000 From bf1825196c96df262748f3e68e2b1b5276724a74 Mon Sep 17 00:00:00 2001 From: Et0h Date: Wed, 14 Oct 2015 12:40:26 +0100 Subject: [PATCH 029/134] Implement "undo playlist change" feature --- syncplay/client.py | 14 ++++++++++++++ syncplay/ui/gui.py | 3 ++- 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/syncplay/client.py b/syncplay/client.py index ebbc9f9..ba1456f 100644 --- a/syncplay/client.py +++ b/syncplay/client.py @@ -114,6 +114,7 @@ class SyncplayClient(object): self.autoplayTimeLeft = constants.AUTOPLAY_DELAY self._playlist = [] + self._previousPlaylist = None self._playlistIndex = None self.__playerReady = defer.Deferred() @@ -504,6 +505,11 @@ class SyncplayClient(object): newIndex = files.index(filename) except: newIndex = 0 + if self._previousPlaylist is None: + if self._playlist <> None and self._playlist <> []: + self._previousPlaylist = self._playlist + elif self._previousPlaylist <> self._playlist and self._playlist <> files: + self._previousPlaylist = self._playlist self._playlist = files if username is None and self._protocol and self._protocol.logged: @@ -513,6 +519,13 @@ class SyncplayClient(object): self.ui.setPlaylist(self._playlist) self.ui.showMessage(u"{} updated the playlist".format(username)) + def undoPlaylistChange(self): + if self._previousPlaylist is not None and self._playlist <> self._previousPlaylist: + undidPlaylist = self._playlist + self.ui.setPlaylist(self._previousPlaylist) + self.changePlaylist(self._previousPlaylist) + self._previousPlaylist = undidPlaylist + def __executePrivacySettings(self, filename, size): if self._config['filenamePrivacyMode'] == PRIVACY_SENDHASHED_MODE: filename = utils.hashFilename(filename) @@ -557,6 +570,7 @@ class SyncplayClient(object): return self.userlist.currentUser.username def setRoom(self, roomName, resetAutoplay=False): + self._previousPlaylist = None self.userlist.currentUser.room = roomName if resetAutoplay: self.resetAutoPlayState() diff --git a/syncplay/ui/gui.py b/syncplay/ui/gui.py index cedce37..61a6d45 100644 --- a/syncplay/ui/gui.py +++ b/syncplay/ui/gui.py @@ -538,8 +538,9 @@ class MainWindow(QtGui.QMainWindow): MainWindow.FileSwitchManager.setFilenameWatchlist(self.newWatchlist) self.checkForDisabledDir() + @needsClient def undoPlaylistChange(self): - self.showErrorMessage("TODO: Undo playlist change!") + self._syncplayClient.undoPlaylistChange() def openPlaylistMenu(self, position): indexes = self.playlist.selectedIndexes() From 34f450938092effb2c7796cfdf7a27540488ffaf Mon Sep 17 00:00:00 2001 From: Et0h Date: Thu, 15 Oct 2015 16:40:32 +0100 Subject: [PATCH 030/134] Improve undo playlist handling when room is changed --- syncplay/client.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/syncplay/client.py b/syncplay/client.py index ba1456f..a6f12b6 100644 --- a/syncplay/client.py +++ b/syncplay/client.py @@ -115,6 +115,7 @@ class SyncplayClient(object): self._playlist = [] self._previousPlaylist = None + self._previousPlaylistRoom = None self._playlistIndex = None self.__playerReady = defer.Deferred() @@ -505,9 +506,10 @@ class SyncplayClient(object): newIndex = files.index(filename) except: newIndex = 0 - if self._previousPlaylist is None: - if self._playlist <> None and self._playlist <> []: - self._previousPlaylist = self._playlist + + if self._previousPlaylistRoom <> self.userlist.currentUser.room: + self._previousPlaylist = None + self._previousPlaylistRoom = self.userlist.currentUser.room elif self._previousPlaylist <> self._playlist and self._playlist <> files: self._previousPlaylist = self._playlist self._playlist = files @@ -570,7 +572,6 @@ class SyncplayClient(object): return self.userlist.currentUser.username def setRoom(self, roomName, resetAutoplay=False): - self._previousPlaylist = None self.userlist.currentUser.room = roomName if resetAutoplay: self.resetAutoPlayState() From e9d25a69a06d46c779d50e13b29ae993137ad25b Mon Sep 17 00:00:00 2001 From: Peter Marheine Date: Thu, 15 Oct 2015 17:20:22 -0600 Subject: [PATCH 031/134] Add status query cookie. This helps prevent fighting when the player takes a long time to report changes in status. It currently fails to play/pause in response to remote commands when latency is sufficiently high, however. --- syncplay/client.py | 35 ++++++++++++++++++++++++++--------- 1 file changed, 26 insertions(+), 9 deletions(-) diff --git a/syncplay/client.py b/syncplay/client.py index 3d135e9..7de9738 100644 --- a/syncplay/client.py +++ b/syncplay/client.py @@ -110,6 +110,8 @@ class SyncplayClient(object): self.autoPlay = False self.autoPlayThreshold = None + self._lastPlayerCommand = time.time() + self.autoplayTimer = task.LoopingCall(self.autoplayCountdown) self.autoplayTimeLeft = constants.AUTOPLAY_DELAY @@ -137,6 +139,16 @@ class SyncplayClient(object): constants.OSD_WARNING_MESSAGE_DURATION = constants.NO_SECONDARY_OSD_WARNING_DURATION self.scheduleAskPlayer() + def _playerRequest(self, f, *args, **kwargs): + """Send a request with cookie to the player.""" + kwargs['cookie'] = time.time() + f(*args, **kwargs) + + def _playerCommand(self, f, *args, **kwargs): + """Send a command to the player, affecting cookie freshness.""" + self._lastPlayerCommand = time.time() + f(*args, **kwargs) + def scheduleAskPlayer(self, when=constants.PLAYER_ASK_DELAY): self._askPlayerTimer = task.LoopingCall(self.askPlayer) self._askPlayerTimer.start(when) @@ -145,7 +157,7 @@ class SyncplayClient(object): if not self._running: return if self._player: - self._player.askForStatus() + self._playerRequest(self._player.askForStatus) self.checkIfConnected() def checkIfConnected(self): @@ -163,14 +175,19 @@ class SyncplayClient(object): seeked = _playerDiff > constants.SEEK_THRESHOLD and _globalDiff > constants.SEEK_THRESHOLD return pauseChange, seeked - def updatePlayerStatus(self, paused, position): + def updatePlayerStatus(self, paused, position, cookie=None): + # Ignore status report if the cookie is stale + if cookie is not None and \ + cookie < constants.PLAYER_COMMAND_DELAY + self._lastPlayerCommand: + return + position -= self.getUserOffset() pauseChange, seeked = self._determinePlayerStateChange(paused, position) self._playerPosition = position self._playerPaused = paused if pauseChange and utils.meetsMinVersion(self.serverVersion, constants.USER_READY_MIN_VERSION): if not self.userlist.currentUser.canControl(): - self._player.setPaused(self._globalPaused) + self._playerCommand(self._player.setPaused, self._globalPaused) self.toggleReady(manuallyInitiated=True) self._playerPaused = self._globalPaused pauseChange = False @@ -180,7 +197,7 @@ class SyncplayClient(object): self.ui.showMessage(getMessage("set-as-not-ready-notification")) elif not paused and not self.instaplayConditionsMet(): paused = True - self._player.setPaused(paused) + self._playerCommand(self._player.setPaused, paused) self._playerPaused = paused self.changeReadyState(True, manuallyInitiated=True) pauseChange = False @@ -213,7 +230,7 @@ class SyncplayClient(object): def _initPlayerState(self, position, paused): if self.userlist.currentUser.file: self.setPosition(position) - self._player.setPaused(paused) + self._playerCommand(self._player.setPaused, paused) madeChangeOnPlayer = True return madeChangeOnPlayer @@ -241,7 +258,7 @@ class SyncplayClient(object): def _serverUnpaused(self, setBy): hideFromOSD = not constants.SHOW_SAME_ROOM_OSD - self._player.setPaused(False) + self._playerCommand(self._player.setPaused, False) madeChangeOnPlayer = True self.ui.showMessage(getMessage("unpause-notification").format(setBy), hideFromOSD) return madeChangeOnPlayer @@ -250,7 +267,7 @@ class SyncplayClient(object): hideFromOSD = not constants.SHOW_SAME_ROOM_OSD if constants.SYNC_ON_PAUSE and self.getUsername() <> setBy: self.setPosition(self.getGlobalPosition()) - self._player.setPaused(True) + self._playerCommand(self._player.setPaused, True) madeChangeOnPlayer = True if (self.lastLeftTime < time.time() - constants.OSD_DURATION) or (hideFromOSD == True): self.ui.showMessage(getMessage("pause-notification").format(setBy), hideFromOSD) @@ -498,13 +515,13 @@ class SyncplayClient(object): if position < 0: position = 0 self._protocol.sendState(self.getPlayerPosition(), self.getPlayerPaused(), True, None, True) - self._player.setPosition(position) + self._playerCommand(self._player.setPosition, position) def setPaused(self, paused): if self._player and self.userlist.currentUser.file: if self._lastPlayerUpdate and not paused: self._lastPlayerUpdate = time.time() - self._player.setPaused(paused) + self._playerCommand(self._player.setPaused, paused) def start(self, host, port): if self._running: From d2ce799bfec5c19743692f0d87d84e53505e41f1 Mon Sep 17 00:00:00 2001 From: Peter Marheine Date: Thu, 15 Oct 2015 17:35:05 -0600 Subject: [PATCH 032/134] Immediately update local player state on server change If status replies are ignored, the result of a server play/pause command could be ignored as well (since the old playing status would end up being seen as a local restart), so set it immediately on dispatching the player command. --- syncplay/client.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/syncplay/client.py b/syncplay/client.py index 7de9738..22da494 100644 --- a/syncplay/client.py +++ b/syncplay/client.py @@ -258,6 +258,10 @@ class SyncplayClient(object): def _serverUnpaused(self, setBy): hideFromOSD = not constants.SHOW_SAME_ROOM_OSD + # In high-player-latency situations we might report our state back to + # the server before any player status is accepted as fresh. Override + # the locally-stored playback state. + self._playerPaused = False self._playerCommand(self._player.setPaused, False) madeChangeOnPlayer = True self.ui.showMessage(getMessage("unpause-notification").format(setBy), hideFromOSD) @@ -265,6 +269,7 @@ class SyncplayClient(object): def _serverPaused(self, setBy): hideFromOSD = not constants.SHOW_SAME_ROOM_OSD + self._playerPaused = True if constants.SYNC_ON_PAUSE and self.getUsername() <> setBy: self.setPosition(self.getGlobalPosition()) self._playerCommand(self._player.setPaused, True) From 5dc2ca0da834804a35f00ec2f8c7fe74dc5d7151 Mon Sep 17 00:00:00 2001 From: Peter Marheine Date: Thu, 15 Oct 2015 19:34:55 -0600 Subject: [PATCH 033/134] Make player command delay configurable. --- syncplay/client.py | 2 +- syncplay/constants.py | 1 + syncplay/messages.py | 3 +++ syncplay/ui/ConfigurationGetter.py | 17 ++++++++++++++--- syncplay/ui/GuiConfiguration.py | 13 +++++++++++++ 5 files changed, 32 insertions(+), 4 deletions(-) diff --git a/syncplay/client.py b/syncplay/client.py index 22da494..df0d668 100644 --- a/syncplay/client.py +++ b/syncplay/client.py @@ -178,7 +178,7 @@ class SyncplayClient(object): def updatePlayerStatus(self, paused, position, cookie=None): # Ignore status report if the cookie is stale if cookie is not None and \ - cookie < constants.PLAYER_COMMAND_DELAY + self._lastPlayerCommand: + cookie < self._lastPlayerCommand + self._config['playerCommandDelay']: return position -= self.getUserOffset() diff --git a/syncplay/constants.py b/syncplay/constants.py index a39ec6a..eadacac 100644 --- a/syncplay/constants.py +++ b/syncplay/constants.py @@ -49,6 +49,7 @@ SERVER_STATE_INTERVAL = 1 WARNING_OSD_MESSAGES_LOOP_INTERVAL = 1 AUTOPLAY_DELAY = 3.0 SYNC_ON_PAUSE = True # Client seek to global position - subtitles may disappear on some media players +DEFAULT_PLAYER_COMMAND_DELAY = 0.05 # Options for the File Switch feature: FOLDER_SEARCH_TIMEOUT = 60.0 # Secs - How long to wait until searches in folder to update cache are aborted (may be longer than this if hard drive needs to spin up) diff --git a/syncplay/messages.py b/syncplay/messages.py index 7abe1ad..ff71183 100755 --- a/syncplay/messages.py +++ b/syncplay/messages.py @@ -194,6 +194,9 @@ en = { "forceguiprompt-label" : "Don't always show the Syncplay configuration window", # (Inverted) "nostore-label" : "Don't store this configuration", # (Inverted) "showosd-label" : "Enable OSD Messages", + "playercommanddelay-title" : "Player latency compensation", + "playercommanddelay-label" : "Seconds to ignore player status after commands", + "playercommanddelay-tooltip" : "Larger values are less likely to spuriously (un)pause but tend to sync less accurately.", "showosdwarnings-label" : "Include warnings (e.g. when files are different, users not ready)", "showsameroomosd-label" : "Include events in your room", diff --git a/syncplay/ui/ConfigurationGetter.py b/syncplay/ui/ConfigurationGetter.py index 0fa6284..b27af5c 100755 --- a/syncplay/ui/ConfigurationGetter.py +++ b/syncplay/ui/ConfigurationGetter.py @@ -63,7 +63,8 @@ class ConfigurationGetter(object): "showSameRoomOSD" : True, "showNonControllerOSD" : False, "showContactInfo" : True, - "showDurationNotification" : True + "showDurationNotification" : True, + "playerCommandDelay": constants.DEFAULT_PLAYER_COMMAND_DELAY } self._defaultConfig = self._config.copy() @@ -117,11 +118,21 @@ class ConfigurationGetter(object): "rewindThreshold", "fastforwardThreshold", "autoplayMinUsers", + "playerCommandDelay", ] self._iniStructure = { "server_data": ["host", "port", "password"], - "client_settings": ["name", "room", "playerPath", "perPlayerArguments", "slowdownThreshold", "rewindThreshold", "fastforwardThreshold", "slowOnDesync", "rewindOnDesync", "fastforwardOnDesync", "dontSlowDownWithMe", "forceGuiPrompt", "filenamePrivacyMode", "filesizePrivacyMode", "unpauseAction", "pauseOnLeave", "readyAtStart", "autoplayMinUsers", "autoplayInitialState", "mediaSearchDirectories"], + "client_settings": ["name", "room", "playerPath", + "perPlayerArguments", "slowdownThreshold", + "rewindThreshold", "fastforwardThreshold", + "slowOnDesync", "rewindOnDesync", + "fastforwardOnDesync", "dontSlowDownWithMe", + "forceGuiPrompt", "filenamePrivacyMode", + "filesizePrivacyMode", "unpauseAction", + "pauseOnLeave", "readyAtStart", "autoplayMinUsers", + "autoplayInitialState", "mediaSearchDirectories", + "playerCommandDelay"], "gui": ["showOSD", "showOSDWarnings", "showSlowdownOSD", "showDifferentRoomOSD", "showSameRoomOSD", "showNonControllerOSD", "showDurationNotification"], "general": ["language", "checkForUpdatesAutomatically", "lastCheckedForUpdates"] } @@ -425,4 +436,4 @@ class SafeConfigParserUnicode(SafeConfigParser): if (value is not None) or (self._optcre == self.OPTCRE): key = " = ".join((key, unicode(value).replace('\n', '\n\t'))) fp.write("%s\n" % key) - fp.write("\n") \ No newline at end of file + fp.write("\n") diff --git a/syncplay/ui/GuiConfiguration.py b/syncplay/ui/GuiConfiguration.py index bfa92ec..ad8f80c 100644 --- a/syncplay/ui/GuiConfiguration.py +++ b/syncplay/ui/GuiConfiguration.py @@ -365,6 +365,8 @@ class ConfigDialog(QtGui.QDialog): widget.setChecked(True) elif isinstance(widget, QLineEdit): widget.setText(self.config[valueName]) + elif isinstance(widget, QDoubleSpinBox): + widget.setValue(self.config[valueName]) def saveValues(self, widget): valueName = str(widget.objectName()) @@ -682,6 +684,10 @@ class ConfigDialog(QtGui.QDialog): self.rewindCheckbox.setObjectName("rewindOnDesync") self.fastforwardCheckbox = QCheckBox(getMessage("fastforwardondesync-label")) self.fastforwardCheckbox.setObjectName("fastforwardOnDesync") + self.commandDelaySpinbox = QDoubleSpinBox() + self.commandDelaySpinbox.setObjectName("playerCommandDelay") + self.commandDelaySpinbox.setMaximum(10) + self.commandDelaySpinbox.setSingleStep(.01) self.desyncSettingsLayout = QtGui.QGridLayout() self.desyncSettingsLayout.setSpacing(2) @@ -710,10 +716,17 @@ class ConfigDialog(QtGui.QDialog): self.othersyncSettingsLayout.setAlignment(Qt.AlignLeft) self.othersyncSettingsLayout.addWidget(self.fastforwardCheckbox, 3, 0,1,2, Qt.AlignLeft) + self.playerLatencyGroup = QtGui.QGroupBox(getMessage("playercommanddelay-title")) + self.playerLatencyLayout = QtGui.QHBoxLayout() + self.playerLatencyGroup.setLayout(self.playerLatencyLayout) + self.playerLatencyLayout.addWidget(self.commandDelaySpinbox) + self.playerLatencyLayout.addWidget(QLabel(getMessage("playercommanddelay-label"))) + self.othersyncSettingsGroup.setLayout(self.othersyncSettingsLayout) self.othersyncSettingsGroup.setMaximumHeight(self.othersyncSettingsGroup.minimumSizeHint().height()) self.syncSettingsLayout.addWidget(self.othersyncSettingsGroup) self.syncSettingsLayout.addWidget(self.desyncSettingsGroup) + self.syncSettingsLayout.addWidget(self.playerLatencyGroup) self.syncSettingsFrame.setLayout(self.syncSettingsLayout) self.desyncSettingsGroup.setMaximumHeight(self.desyncSettingsGroup.minimumSizeHint().height()) self.syncSettingsLayout.setAlignment(Qt.AlignTop) From 16d53345a96828f0bb73414e80785fe9f72ceb8c Mon Sep 17 00:00:00 2001 From: Peter Marheine Date: Thu, 15 Oct 2015 20:11:54 -0600 Subject: [PATCH 034/134] Add cookie support to all player modules. --- syncplay/players/basePlayer.py | 4 ++-- syncplay/players/mpc.py | 12 +++++++----- syncplay/players/mplayer.py | 4 ++-- syncplay/players/mpv.py | 8 +++++--- syncplay/players/vlc.py | 10 +++++++--- 5 files changed, 23 insertions(+), 15 deletions(-) diff --git a/syncplay/players/basePlayer.py b/syncplay/players/basePlayer.py index c8fdcc2..37b81d7 100644 --- a/syncplay/players/basePlayer.py +++ b/syncplay/players/basePlayer.py @@ -6,7 +6,7 @@ class BasePlayer(object): execute updatePlayerStatus(paused, position) on client Given the arguments: boolean paused and float position in seconds ''' - def askForStatus(self): + def askForStatus(self, cookie=None): raise NotImplementedError() ''' @@ -121,4 +121,4 @@ class DummyPlayer(BasePlayer): @staticmethod def getPlayerPathErrors(playerPath, filePath): - return None \ No newline at end of file + return None diff --git a/syncplay/players/mpc.py b/syncplay/players/mpc.py index 8b2c938..440ee43 100644 --- a/syncplay/players/mpc.py +++ b/syncplay/players/mpc.py @@ -420,20 +420,22 @@ class MPCHCAPIPlayer(BasePlayer): return self._mpcApi.lastFilePosition @retry(MpcHcApi.PlayerNotReadyException, constants.MPC_MAX_RETRIES, constants.MPC_RETRY_WAIT_TIME, 1) - def askForStatus(self): + def askForStatus(self, cookie=None): if self._mpcApi.filePlaying and self.__preventAsking.wait(0) and self.__fileUpdate.acquire(0): self.__fileUpdate.release() position = self.__getPosition() paused = self._mpcApi.isPaused() position = float(position) if self.__preventAsking.wait(0) and self.__fileUpdate.acquire(0): - self.__client.updatePlayerStatus(paused, position) + self.__client.updatePlayerStatus(paused, position, cookie=cookie) self.__fileUpdate.release() return - self.__echoGlobalStatus() + self.__echoGlobalStatus(cookie) - def __echoGlobalStatus(self): - self.__client.updatePlayerStatus(self.__client.getGlobalPaused(), self.__client.getGlobalPosition()) + def __echoGlobalStatus(self, cookie): + self.__client.updatePlayerStatus(self.__client.getGlobalPaused(), + self.__client.getGlobalPosition(), + cookie=cookie) def __forcePause(self): for _ in xrange(constants.MPC_MAX_RETRIES): diff --git a/syncplay/players/mplayer.py b/syncplay/players/mplayer.py index dd35762..65124a5 100644 --- a/syncplay/players/mplayer.py +++ b/syncplay/players/mplayer.py @@ -72,14 +72,14 @@ class MplayerPlayer(BasePlayer): self.reactor.callLater(0, self._client.initPlayer, self) self._onFileUpdate() - def askForStatus(self): + def askForStatus(self, cookie=None): self._positionAsk.clear() self._pausedAsk.clear() self._getPaused() self._getPosition() self._positionAsk.wait() self._pausedAsk.wait() - self._client.updatePlayerStatus(self._paused, self._position) + self._client.updatePlayerStatus(self._paused, self._position, cookie=cookie) def _setProperty(self, property_, value): self._listener.sendLine("set_property {} {}".format(property_, value)) diff --git a/syncplay/players/mpv.py b/syncplay/players/mpv.py index ff52af2..1130ca7 100644 --- a/syncplay/players/mpv.py +++ b/syncplay/players/mpv.py @@ -154,14 +154,16 @@ class NewMpvPlayer(OldMpvPlayer): else: self._paused = self._client.getGlobalPaused() - def askForStatus(self): + def askForStatus(self, cookie=None): self._positionAsk.clear() self._pausedAsk.clear() self._getPaused() self._getPosition() self._positionAsk.wait(constants.MPV_LOCK_WAIT_TIME) self._pausedAsk.wait(constants.MPV_LOCK_WAIT_TIME) - self._client.updatePlayerStatus(self._paused if self.fileLoaded else self._client.getGlobalPaused(), self.getCalculatedPosition()) + self._client.updatePlayerStatus(self._paused if self.fileLoaded else self._client.getGlobalPaused(), + self.getCalculatedPosition(), + cookie=cookie) def _preparePlayer(self): if self.delayedFilePath: @@ -219,4 +221,4 @@ class NewMpvPlayer(OldMpvPlayer): if self.fileLoaded == True and self.lastLoadedTime != None and time.time() > (self.lastLoadedTime + constants.MPV_NEWFILE_IGNORE_TIME): return True else: - return False \ No newline at end of file + return False diff --git a/syncplay/players/vlc.py b/syncplay/players/vlc.py index 331fcbf..70a9dd3 100644 --- a/syncplay/players/vlc.py +++ b/syncplay/players/vlc.py @@ -84,16 +84,20 @@ class VlcPlayer(BasePlayer): self.setPaused(self._client.getGlobalPaused()) self.setPosition(self._client.getGlobalPosition()) - def askForStatus(self): + def askForStatus(self, cookie=None): self._filechanged = False self._positionAsk.clear() self._pausedAsk.clear() self._listener.sendLine(".") if self._filename and not self._filechanged: self._positionAsk.wait(constants.PLAYER_ASK_DELAY) - self._client.updatePlayerStatus(self._paused, self.getCalculatedPosition()) + self._client.updatePlayerStatus(self._paused, + self.getCalculatedPosition(), + cookie=cookie) else: - self._client.updatePlayerStatus(self._client.getGlobalPaused(), self._client.getGlobalPosition()) + self._client.updatePlayerStatus(self._client.getGlobalPaused(), + self._client.getGlobalPosition(), + cookie=cookie) def getCalculatedPosition(self): if self._lastVLCPositionUpdate is None: From 4c7c526b8dcbb88d79ddf99108a8514419fbd096 Mon Sep 17 00:00:00 2001 From: Peter Marheine Date: Fri, 16 Oct 2015 20:36:37 -0600 Subject: [PATCH 035/134] Add missing GUI saving for command delay Also adjust the default command delay step to 100ms. --- syncplay/ui/GuiConfiguration.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/syncplay/ui/GuiConfiguration.py b/syncplay/ui/GuiConfiguration.py index ad8f80c..26e7282 100644 --- a/syncplay/ui/GuiConfiguration.py +++ b/syncplay/ui/GuiConfiguration.py @@ -389,6 +389,8 @@ class ConfigDialog(QtGui.QDialog): self.config[radioName] = radioValue elif isinstance(widget, QLineEdit): self.config[valueName] = widget.text() + elif isinstance(widget, QDoubleSpinBox): + self.config[valueName] = widget.value() def connectChildren(self, widget): widgetName = str(widget.objectName()) @@ -687,7 +689,7 @@ class ConfigDialog(QtGui.QDialog): self.commandDelaySpinbox = QDoubleSpinBox() self.commandDelaySpinbox.setObjectName("playerCommandDelay") self.commandDelaySpinbox.setMaximum(10) - self.commandDelaySpinbox.setSingleStep(.01) + self.commandDelaySpinbox.setSingleStep(.1) self.desyncSettingsLayout = QtGui.QGridLayout() self.desyncSettingsLayout.setSpacing(2) @@ -1009,4 +1011,4 @@ class ConfigDialog(QtGui.QDialog): self.processWidget(self, lambda w: self.loadTooltips(w)) self.processWidget(self, lambda w: self.loadValues(w)) self.processWidget(self, lambda w: self.connectChildren(w)) - self.populateEmptyServerList() \ No newline at end of file + self.populateEmptyServerList() From 094a204d9e2d240dd601447862b8d2fcab64b469 Mon Sep 17 00:00:00 2001 From: Peter Marheine Date: Thu, 22 Oct 2015 19:54:14 -0600 Subject: [PATCH 036/134] Update player location on server seek. --- syncplay/client.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/syncplay/client.py b/syncplay/client.py index df0d668..0adf967 100644 --- a/syncplay/client.py +++ b/syncplay/client.py @@ -175,10 +175,15 @@ class SyncplayClient(object): seeked = _playerDiff > constants.SEEK_THRESHOLD and _globalDiff > constants.SEEK_THRESHOLD return pauseChange, seeked + def _ignoringPlayerStatus(self, cookie=None): + if cookie is None: + cookie = time.time() + return cookie < self._lastPlayerCommand + self._config['playerCommandDelay'] + def updatePlayerStatus(self, paused, position, cookie=None): # Ignore status report if the cookie is stale - if cookie is not None and \ - cookie < self._lastPlayerCommand + self._config['playerCommandDelay']: + if self._ignoringPlayerStatus(cookie): + self.ui.showDebugMessage('Ignoring stale player status with cookie {}'.format(cookie)) return position -= self.getUserOffset() @@ -515,6 +520,7 @@ class SyncplayClient(object): def setPosition(self, position): if self._lastPlayerUpdate: self._lastPlayerUpdate = time.time() + self._playerPosition = position position += self.getUserOffset() if self._player and self.userlist.currentUser.file: if position < 0: From 9d28822731f019c95c24791cdb28e673e12a022b Mon Sep 17 00:00:00 2001 From: Et0h Date: Sat, 24 Oct 2015 00:04:04 +0100 Subject: [PATCH 037/134] Hopefully fix VLC playlist file loading issues --- resources/lua/intf/syncplay.lua | 8 +++++--- syncplay/constants.py | 2 +- syncplay/players/vlc.py | 4 ++++ 3 files changed, 10 insertions(+), 4 deletions(-) diff --git a/resources/lua/intf/syncplay.lua b/resources/lua/intf/syncplay.lua index 2cbeee0..6725cb1 100644 --- a/resources/lua/intf/syncplay.lua +++ b/resources/lua/intf/syncplay.lua @@ -5,7 +5,7 @@ Principal author: Etoh Other contributors: DerGenaue, jb Project: http://syncplay.pl/ - Version: 0.2.8 + Version: 0.2.9 Note: * This interface module is intended to be used in conjunction with Syncplay. @@ -84,7 +84,7 @@ You may also need to re-copy the syncplay.lua file when you update VLC. --]==========================================================================] -local connectorversion = "0.2.8" +local connectorversion = "0.2.9" local vlcversion = vlc.misc.version() local durationdelay = 500000 -- Pause for get_duration command etc for increased reliability (uses microseconds) local loopsleepduration = 2500 -- Pause for every event loop (uses microseconds) @@ -463,7 +463,9 @@ function load_file (filepath) -- [Used by load-file command] local uri = vlc.strings.make_uri(filepath) - vlc.playlist.add({{path=uri}}) + vlc.playlist.clear() + vlc.playlist.enqueue({{path=uri}}) + vlc.playlist.next() return "load-file-attempted\n" end diff --git a/syncplay/constants.py b/syncplay/constants.py index 8a403c5..c6c2414 100644 --- a/syncplay/constants.py +++ b/syncplay/constants.py @@ -69,7 +69,7 @@ COMMANDS_AUTH = ['a','auth'] COMMANDS_TOGGLE = ['t','toggle'] MPC_MIN_VER = "1.6.4" VLC_MIN_VERSION = "2.2.1" -VLC_INTERFACE_MIN_VERSION = "0.2.8" +VLC_INTERFACE_MIN_VERSION = "0.2.9" VLC_LATENCY_ERROR_THRESHOLD = 2.0 MPV_UNRESPONSIVE_THRESHOLD = 60.0 CONTROLLED_ROOMS_MIN_VERSION = "1.3.0" diff --git a/syncplay/players/vlc.py b/syncplay/players/vlc.py index 331fcbf..fb62b33 100644 --- a/syncplay/players/vlc.py +++ b/syncplay/players/vlc.py @@ -141,6 +141,10 @@ class VlcPlayer(BasePlayer): return fileURL def openFile(self, filePath, resetPosition=False): + if not utils.isURL(filePath): + normedPath = os.path.normpath(filePath) + if os.path.isfile(normedPath): + filePath = normedPath if utils.isASCII(filePath): self._listener.sendLine('load-file: {}'.format(filePath.encode('ascii', 'ignore'))) else: From 3afa12b910205cea3c6703c09e318a6c9acd2ea2 Mon Sep 17 00:00:00 2001 From: Et0h Date: Sat, 24 Oct 2015 16:25:15 +0100 Subject: [PATCH 038/134] Initial playlist current file indicator --- syncplay/client.py | 4 ++++ syncplay/constants.py | 1 + syncplay/ui/consoleUI.py | 3 +++ syncplay/ui/gui.py | 34 +++++++++++++++++++++++++++++++++- 4 files changed, 41 insertions(+), 1 deletion(-) diff --git a/syncplay/client.py b/syncplay/client.py index a6f12b6..3eb91d0 100644 --- a/syncplay/client.py +++ b/syncplay/client.py @@ -466,6 +466,7 @@ class SyncplayClient(object): return try: filename = self._playlist[index] + self.ui.setPlaylistIndexFilename(filename) if username is not None and self.userlist.currentUser.file and filename == self.userlist.currentUser.file['name']: return except IndexError: @@ -1282,6 +1283,9 @@ class UiManager(object): def setPlaylist(self, newPlaylist): self.__ui.setPlaylist(newPlaylist) + def setPlaylistIndexFilename(self, filename): + self.__ui.setPlaylistIndexFilename(filename) + def showDebugMessage(self, message): if constants.DEBUG_MODE and message.rstrip(): sys.stderr.write("{}{}\n".format(time.strftime(constants.UI_TIME_FORMAT, time.localtime()),message.rstrip())) diff --git a/syncplay/constants.py b/syncplay/constants.py index c6c2414..b3eba57 100644 --- a/syncplay/constants.py +++ b/syncplay/constants.py @@ -177,6 +177,7 @@ FILEITEM_SWITCH_ROLE = 1 FILEITEM_SWITCH_NO_SWITCH = 0 FILEITEM_SWITCH_FILE_SWITCH = 1 FILEITEM_SWITCH_STREAM_SWITCH = 2 +PLAYLISTITEM_CURRENTLYPLAYING_ROLE = 3 SYNCPLAY_UPDATE_URL = u"http://syncplay.pl/checkforupdate?{}" # Params SYNCPLAY_DOWNLOAD_URL = "http://syncplay.pl/download/" diff --git a/syncplay/ui/consoleUI.py b/syncplay/ui/consoleUI.py index 338086f..6eb2057 100644 --- a/syncplay/ui/consoleUI.py +++ b/syncplay/ui/consoleUI.py @@ -25,6 +25,9 @@ class ConsoleUI(threading.Thread): def setPlaylist(self, newPlaylist): pass + + def setPlaylistIndexFilename(self, filename): + pass def run(self): try: diff --git a/syncplay/ui/gui.py b/syncplay/ui/gui.py index 61a6d45..9421aad 100644 --- a/syncplay/ui/gui.py +++ b/syncplay/ui/gui.py @@ -88,6 +88,7 @@ class MainWindow(QtGui.QMainWindow): insertPosition = None playlistState = [] updatingPlaylist = False + playlistIndex = None def setPlaylistInsertPosition(self, newPosition): if MainWindow.insertPosition <> newPosition: @@ -96,8 +97,23 @@ class MainWindow(QtGui.QMainWindow): class PlaylistItemDelegate(QtGui.QStyledItemDelegate): def paint(self, itemQPainter, optionQStyleOptionViewItem, indexQModelIndex): - QtGui.QStyledItemDelegate.paint(self, itemQPainter, optionQStyleOptionViewItem, indexQModelIndex) itemQPainter.save() + currentQAbstractItemModel = indexQModelIndex.model() + currentlyPlayingFile = currentQAbstractItemModel.data(indexQModelIndex, Qt.UserRole + constants.PLAYLISTITEM_CURRENTLYPLAYING_ROLE) + if sys.platform.startswith('win'): + resourcespath = utils.findWorkingDir() + "\\resources\\" + else: + resourcespath = utils.findWorkingDir() + "/resources/" + if currentlyPlayingFile: + fileSwitchIconQPixmap = QtGui.QPixmap(resourcespath + "chevrons_right.png") + itemQPainter.drawPixmap ( + (optionQStyleOptionViewItem.rect.x()), + optionQStyleOptionViewItem.rect.y(), + fileSwitchIconQPixmap.scaled(16, 16, Qt.KeepAspectRatio)) + optionQStyleOptionViewItem.rect.setX(optionQStyleOptionViewItem.rect.x()+16) + + QtGui.QStyledItemDelegate.paint(self, itemQPainter, optionQStyleOptionViewItem, indexQModelIndex) + lineAbove = False lineBelow = False if MainWindow.insertPosition == 0 and indexQModelIndex.row() == 0: @@ -150,6 +166,18 @@ class MainWindow(QtGui.QMainWindow): class PlaylistWidget(QtGui.QListWidget): selfWindow = None + playlistIndexFilename = None + + def setPlaylistIndexFilename(self, filename): + if filename <> self.playlistIndexFilename: + self.playlistIndexFilename = filename + self.updatePlaylistIndexIcon() + + def updatePlaylistIndexIcon(self): + for item in xrange(self.count()): + isPlayingFilename = self.item(item).text() == self.playlistIndexFilename + self.item(item).setData(Qt.UserRole + constants.PLAYLISTITEM_CURRENTLYPLAYING_ROLE, isPlayingFilename) + self.forceUpdate() def setWindow(self, window): self.selfWindow = window @@ -172,6 +200,7 @@ class MainWindow(QtGui.QMainWindow): for index in xrange(self.count()): self.takeItem(0) self.insertItems(0, newPlaylist) + self.updatePlaylistIndexIcon() def remove_selected_items(self): for item in self.selectedItems(): @@ -1308,6 +1337,9 @@ class MainWindow(QtGui.QMainWindow): self.playlist.updatePlaylist(newPlaylist) self.updatingPlaylist = False + def setPlaylistIndexFilename(self, filename): + self.playlist.setPlaylistIndexFilename(filename) + def addFileToPlaylist(self, filePath, index = -1): if os.path.isfile(filePath): self.removePlaylistNote() From d009310af0b94055704fc64659a0840c7133564b Mon Sep 17 00:00:00 2001 From: Et0h Date: Sat, 24 Oct 2015 21:47:16 +0100 Subject: [PATCH 039/134] Change playing file indicator --- resources/bullet_right_grey.png | Bin 0 -> 349 bytes syncplay/ui/gui.py | 8 ++++---- 2 files changed, 4 insertions(+), 4 deletions(-) create mode 100644 resources/bullet_right_grey.png diff --git a/resources/bullet_right_grey.png b/resources/bullet_right_grey.png new file mode 100644 index 0000000000000000000000000000000000000000..b89ee5b5eee59e15d1dd9bc070c0925eb8d61f28 GIT binary patch literal 349 zcmeAS@N?(olHy`uVBq!ia0vp^Y(Ol)0U}ME`OX0;mUKs7M+SzC{oH>NS%G}c0*}aI z1_rtPAk66eH)j@5kiEpy*OmP`r>rQ4;1!MqsX!sA64!{5;QX|b^2DN4hJeJ(yb?V> z*ARs=V?9$nLj!{^MGJwdj(NH`hG+!u?O({%93aw`FU#|d$Gdsy-u4wMB(}`(F%x8Q z38mzB-5_Fr?x*)v>L*?u+E=!3{(l+1pX`y-E?V>nwmnP?mS3c?C^Yoo z_u3uncDK6*Cd^BplFE6!Q0(@$ew&2V7fNN9Wjf7!zCkD3(D(C}bxyapt#~c<*dl3e zX>9#O1&#|QylsgOczrL=+oW^9<9X$a-*TC=oI Date: Sun, 25 Oct 2015 19:30:42 +0000 Subject: [PATCH 040/134] Add file playing icon to installer --- buildPy2exe.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/buildPy2exe.py b/buildPy2exe.py index b7c145b..5b108d0 100644 --- a/buildPy2exe.py +++ b/buildPy2exe.py @@ -654,7 +654,7 @@ guiIcons = ['resources/accept.png', 'resources/arrow_undo.png', 'resources/clock 'resources/user_key.png', 'resources/lock.png', 'resources/key_go.png', 'resources/page_white_key.png', 'resources/tick.png', 'resources/lock_open.png', 'resources/empty_checkbox.png', 'resources/tick_checkbox.png', 'resources/world_explore.png', 'resources/application_get.png', 'resources/cog.png', - 'resources/film_go.png', 'resources/world_go.png', 'resources/arrow_refresh.png', + 'resources/film_go.png', 'resources/world_go.png', 'resources/arrow_refresh.png', 'resources/bullet_right_grey.png', 'resources/world_add.png', 'resources/film_add.png', 'resources/delete.png' ] resources = ["resources/icon.ico", "resources/syncplay.png"] From cf1b2edf4a8aee80132b0e81783990dcef142e5e Mon Sep 17 00:00:00 2001 From: Et0h Date: Sun, 25 Oct 2015 20:08:54 +0000 Subject: [PATCH 041/134] Prevent playlist duplicates --- syncplay/ui/gui.py | 21 ++++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/syncplay/ui/gui.py b/syncplay/ui/gui.py index 2adbf9d..e664a83 100644 --- a/syncplay/ui/gui.py +++ b/syncplay/ui/gui.py @@ -199,7 +199,11 @@ class MainWindow(QtGui.QMainWindow): def updatePlaylist(self, newPlaylist): for index in xrange(self.count()): self.takeItem(0) - self.insertItems(0, newPlaylist) + uniquePlaylist = [] + for item in newPlaylist: + if item not in uniquePlaylist: + uniquePlaylist.append(item) + self.insertItems(0, uniquePlaylist) self.updatePlaylistIndexIcon() def remove_selected_items(self): @@ -1346,14 +1350,25 @@ class MainWindow(QtGui.QMainWindow): if index == -1: self.playlist.addItem(os.path.basename(filePath)) else: - self.playlist.insertItem(index, os.path.basename(filePath)) + filename = os.path.basename(filePath) + + if self.noPlaylistDuplicates(filename): + self.playlist.insertItem(index, filename) def openFile(self, filePath, resetPosition=False): self._syncplayClient._player.openFile(filePath, resetPosition) + def noPlaylistDuplicates(self, filename): + for playlistindex in xrange(self.playlist.count()): + if self.playlist.item(playlistindex).text() == filename: + self.showErrorMessage(u"Could not add second entry for '{}' to the playlist as no duplicates are allowed.".format(filename)) + return False + return True + def addStreamToPlaylist(self, streamURI): self.removePlaylistNote() - self.playlist.addItem(streamURI) + if self.noPlaylistDuplicates(streamURI): + self.playlist.addItem(streamURI) def removePlaylistNote(self): if not self.clearedPlaylistNote: From bb875fddf9d01e202514d20c4b5ee5e6d11b49bf Mon Sep 17 00:00:00 2001 From: Et0h Date: Sun, 15 Nov 2015 21:32:46 +0000 Subject: [PATCH 042/134] Shuffle playlist feature --- buildPy2exe.py | 2 +- resources/arrow_switch.png | Bin 0 -> 683 bytes syncplay/client.py | 12 +++++++++++- syncplay/ui/gui.py | 7 ++++++- 4 files changed, 18 insertions(+), 3 deletions(-) create mode 100644 resources/arrow_switch.png diff --git a/buildPy2exe.py b/buildPy2exe.py index 5b108d0..6e4269d 100644 --- a/buildPy2exe.py +++ b/buildPy2exe.py @@ -653,7 +653,7 @@ guiIcons = ['resources/accept.png', 'resources/arrow_undo.png', 'resources/clock 'resources/eye.png', 'resources/comments.png', 'resources/cog_delete.png', 'resources/chevrons_right.png', 'resources/user_key.png', 'resources/lock.png', 'resources/key_go.png', 'resources/page_white_key.png', 'resources/tick.png', 'resources/lock_open.png', 'resources/empty_checkbox.png', 'resources/tick_checkbox.png', - 'resources/world_explore.png', 'resources/application_get.png', 'resources/cog.png', + 'resources/world_explore.png', 'resources/application_get.png', 'resources/cog.png', 'resources/arrow_switch.png', 'resources/film_go.png', 'resources/world_go.png', 'resources/arrow_refresh.png', 'resources/bullet_right_grey.png', 'resources/world_add.png', 'resources/film_add.png', 'resources/delete.png' ] diff --git a/resources/arrow_switch.png b/resources/arrow_switch.png new file mode 100644 index 0000000000000000000000000000000000000000..258c16c63a20f7474764507475af7961ecf4263a GIT binary patch literal 683 zcmV;c0#yBpP)A3WJ2dfsV78ToP)wU4~UJxT^#Yl8pW;T)B2y+W_BqqmW z&#t|w0SE!KR$a#7aL!?!V{G>0(f7&GF0n$i_yAiovkXpE$M|c8KDe}2*w(W8jK3Y2*x)V03j=u2XF48q6F<0_UD&z zFmi~T?K=p$491faq~>RsPR$VB89_xzOpK2V+=|x$1lD3~x!;g2Mz5ELE831k>xd528II@{FM*4t5cwMI2$KMmfFm;RN43xW+e;$tzEyd|PV<@i4gUfKh|U-I$hymfQ} zt?kWDj37pE;wZ>1WHG%+SvbnRqEaTN#u*-slSm{u7_C6WGBh|el4$>24G=QE;Y9f< z)G2BOvC3T5Jn;{4ig%R|E{ITA5W$|ds8uW$Z^f(HeLnnp>dACn(D>^wEGjH6Et2~F zjpx1H%%|rOCx{iqDPk27MT{DNK*OCgPK;oDLHt!jVx&(zZS&Lq>Ld9Y&Ck!LvbvJw zmn1{|Z(}o^vo(KE_?L 0: + oldPlaylist = self._playlist + random.seed() + shuffledPlaylist = deepcopy(self._playlist) + random.shuffle(shuffledPlaylist) + self.ui.setPlaylist(shuffledPlaylist) + self.changePlaylist(shuffledPlaylist) + self._previousPlaylist = oldPlaylist + def __executePrivacySettings(self, filename, size): if self._config['filenamePrivacyMode'] == PRIVACY_SENDHASHED_MODE: filename = utils.hashFilename(filename) @@ -564,7 +575,6 @@ class SyncplayClient(object): if username and username <> "": self.userlist.currentUser.username = username else: - import random random.seed() random_number = random.randrange(1000, 9999) self.userlist.currentUser.username = "Anonymous" + str(random_number) # Not localised as this would give away locale diff --git a/syncplay/ui/gui.py b/syncplay/ui/gui.py index e664a83..d2e7c48 100644 --- a/syncplay/ui/gui.py +++ b/syncplay/ui/gui.py @@ -575,6 +575,10 @@ class MainWindow(QtGui.QMainWindow): def undoPlaylistChange(self): self._syncplayClient.undoPlaylistChange() + @needsClient + def shufflePlaylist(self): + self._syncplayClient.shufflePlaylist() + def openPlaylistMenu(self, position): indexes = self.playlist.selectedIndexes() if sys.platform.startswith('win'): @@ -598,6 +602,7 @@ class MainWindow(QtGui.QMainWindow): menu.addAction(QtGui.QPixmap(resourcespath + "film_go.png"), "Open file", lambda: self.openFile(pathFound)) menu.addAction(QtGui.QPixmap(resourcespath + "delete.png"), "Remove from playlist", lambda: self.deleteSelectedPlaylistItems()) menu.addSeparator() + menu.addAction(QtGui.QPixmap(resourcespath + "arrow_switch.png"), "Shuffle playlist", lambda: self.shufflePlaylist()) menu.addAction(QtGui.QPixmap(resourcespath + "arrow_undo.png"), "Undo last change to playlist", lambda: self.undoPlaylistChange()) menu.exec_(self.playlist.viewport().mapToGlobal(position)) @@ -1335,7 +1340,7 @@ class MainWindow(QtGui.QMainWindow): if newPlaylist == self.playlistState: return self.updatingPlaylist = True - if len(newPlaylist) > 0: + if newPlaylist and len(newPlaylist) > 0: self.clearedPlaylistNote = True self.playlistState = newPlaylist self.playlist.updatePlaylist(newPlaylist) From 18a17b0aa96c6eae11862740b94421a433a24a93 Mon Sep 17 00:00:00 2001 From: Et0h Date: Tue, 17 Nov 2015 18:17:04 +0000 Subject: [PATCH 043/134] Ignore spaces etc from host input (re-work of contribution by DerGenaue) --- syncplay/ui/GuiConfiguration.py | 1 + 1 file changed, 1 insertion(+) diff --git a/syncplay/ui/GuiConfiguration.py b/syncplay/ui/GuiConfiguration.py index bfa92ec..4d87aa1 100644 --- a/syncplay/ui/GuiConfiguration.py +++ b/syncplay/ui/GuiConfiguration.py @@ -291,6 +291,7 @@ class ConfigDialog(QtGui.QDialog): self.processWidget(self, lambda w: self.saveValues(w)) if self.hostCombobox.currentText(): self.config['host'] = self.hostCombobox.currentText() if ":" in self.hostCombobox.currentText() else self.hostCombobox.currentText() + ":" + unicode(constants.DEFAULT_PORT) + self.config['host'] = self.config['host'].replace(" ","").replace("\t", "").replace("\n","").replace("\r","") else: self.config['host'] = None self.config['playerPath'] = unicode(self.safenormcaseandpath(self.executablepathCombobox.currentText())) From 2c885d07b2360e708cf65c5647d33a1204d1e276 Mon Sep 17 00:00:00 2001 From: Et0h Date: Sun, 6 Dec 2015 16:25:49 +0000 Subject: [PATCH 044/134] Reduce likelihood of moving to next file when at end of playlist --- syncplay/client.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/syncplay/client.py b/syncplay/client.py index 5898e70..63680d1 100644 --- a/syncplay/client.py +++ b/syncplay/client.py @@ -173,7 +173,7 @@ class SyncplayClient(object): def loadNextFileInPlaylist(self): # TODO: Fix for GUIDs & add path checks (and make more of code re-use?) - if self._playlistIndex is None or len(self._playlist) == self._playlistIndex+1: + if self._playlistIndex is None or len(self._playlist) <= self._playlistIndex+1: return filename = self._playlist[self._playlistIndex+1] if utils.isURL(filename): From 1964d25e9666a9afa5286640394dc302a43eef06 Mon Sep 17 00:00:00 2001 From: Et0h Date: Mon, 7 Dec 2015 21:55:23 +0000 Subject: [PATCH 045/134] Reduce MPC problems when file change takes too long --- syncplay/players/mpc.py | 26 ++++++++++++++------------ 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/syncplay/players/mpc.py b/syncplay/players/mpc.py index 8b2c938..6ab1452 100644 --- a/syncplay/players/mpc.py +++ b/syncplay/players/mpc.py @@ -418,20 +418,22 @@ class MPCHCAPIPlayer(BasePlayer): self._mpcApi.askForCurrentPosition() self.__positionUpdate.wait(constants.MPC_LOCK_WAIT_TIME) return self._mpcApi.lastFilePosition - - @retry(MpcHcApi.PlayerNotReadyException, constants.MPC_MAX_RETRIES, constants.MPC_RETRY_WAIT_TIME, 1) + def askForStatus(self): - if self._mpcApi.filePlaying and self.__preventAsking.wait(0) and self.__fileUpdate.acquire(0): - self.__fileUpdate.release() - position = self.__getPosition() - paused = self._mpcApi.isPaused() - position = float(position) - if self.__preventAsking.wait(0) and self.__fileUpdate.acquire(0): - self.__client.updatePlayerStatus(paused, position) + try: + if self._mpcApi.filePlaying and self.__preventAsking.wait(0) and self.__fileUpdate.acquire(0): self.__fileUpdate.release() - return - self.__echoGlobalStatus() - + position = self.__getPosition() + paused = self._mpcApi.isPaused() + position = float(position) + if self.__preventAsking.wait(0) and self.__fileUpdate.acquire(0): + self.__client.updatePlayerStatus(paused, position) + self.__fileUpdate.release() + else: + self.__echoGlobalStatus() + except MpcHcApi.PlayerNotReadyException: + self.__echoGlobalStatus() + def __echoGlobalStatus(self): self.__client.updatePlayerStatus(self.__client.getGlobalPaused(), self.__client.getGlobalPosition()) From a59c7c253c6bea4e8d633e160c548ce899019b77 Mon Sep 17 00:00:00 2001 From: Uriziel Date: Sat, 26 Dec 2015 18:30:07 +0100 Subject: [PATCH 046/134] Fixed relative config loading with utf-8 names --- syncplay/ui/ConfigurationGetter.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/syncplay/ui/ConfigurationGetter.py b/syncplay/ui/ConfigurationGetter.py index d185086..db6f7a1 100755 --- a/syncplay/ui/ConfigurationGetter.py +++ b/syncplay/ui/ConfigurationGetter.py @@ -354,7 +354,7 @@ class ConfigurationGetter(object): for name in constants.CONFIG_NAMES: path = location + os.path.sep + name if os.path.isfile(path) and (os.name == 'nt' or path != os.path.join(os.getenv('HOME', '.'), constants.DEFAULT_CONFIG_NAME_LINUX)): - loadedPaths.append("'" + os.path.normpath(path) + "'") + loadedPaths.append("'" + os.path.normpath(path).decode('utf-8') + "'") self._parseConfigFile(path, createConfig=False) self._checkConfig() return loadedPaths @@ -427,4 +427,4 @@ class SafeConfigParserUnicode(SafeConfigParser): if (value is not None) or (self._optcre == self.OPTCRE): key = " = ".join((key, unicode(value).replace('\n', '\n\t'))) fp.write("%s\n" % key) - fp.write("\n") \ No newline at end of file + fp.write("\n") From 35627599195c07472c4b9a5bd3093c2218ce1932 Mon Sep 17 00:00:00 2001 From: Etoh Date: Sun, 3 Jan 2016 23:43:38 +0000 Subject: [PATCH 047/134] Fix playlist going to wrong next file bug --- syncplay/client.py | 1 + 1 file changed, 1 insertion(+) diff --git a/syncplay/client.py b/syncplay/client.py index 63680d1..0fc289f 100644 --- a/syncplay/client.py +++ b/syncplay/client.py @@ -521,6 +521,7 @@ class SyncplayClient(object): self.changeToPlaylistIndex(newIndex) else: self.ui.setPlaylist(self._playlist) + self.changeToPlaylistIndex(newIndex, username) self.ui.showMessage(u"{} updated the playlist".format(username)) def undoPlaylistChange(self): From a85f9bae6ca46dcfda6bba9944aa60a53f7cf8cf Mon Sep 17 00:00:00 2001 From: Et0h Date: Mon, 4 Jan 2016 00:09:15 +0000 Subject: [PATCH 048/134] Prevent server new managed room exception caused by lack of setByName --- syncplay/server.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/syncplay/server.py b/syncplay/server.py index 93aceeb..14eb6d2 100644 --- a/syncplay/server.py +++ b/syncplay/server.py @@ -74,8 +74,9 @@ class SyncFactory(Factory): self.sendRoomSwitchMessage(watcher) room = watcher.getRoom() - watcher.setPlaylist(room.getSetBy().getName(), room.getPlaylist()) - watcher.setPlaylistIndex(room.getSetBy().getName(), room.getPlaylistIndex()) + roomSetByName = room.getSetBy().getName() if room.getSetBy() else None + watcher.setPlaylist(roomSetByName, room.getPlaylist()) + watcher.setPlaylistIndex(roomSetByName, room.getPlaylistIndex()) if RoomPasswordProvider.isControlledRoom(roomName): for controller in room.getControllers(): watcher.sendControlledRoomAuthStatus(True, controller, roomName) From 5d5c0a741b53e8d452d866f8cd6f17d6f4ad185c Mon Sep 17 00:00:00 2001 From: Et0h Date: Mon, 4 Jan 2016 12:10:28 +0000 Subject: [PATCH 049/134] Server only accepts playlist/index changes from room operators in managed rooms --- syncplay/server.py | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/syncplay/server.py b/syncplay/server.py index 14eb6d2..187dc06 100644 --- a/syncplay/server.py +++ b/syncplay/server.py @@ -138,12 +138,22 @@ class SyncFactory(Factory): self._roomManager.broadcastRoom(watcher, lambda w: w.sendSetReady(watcher.getName(), watcher.isReady(), manuallyInitiated)) def setPlaylist(self, watcher, files): - watcher.getRoom().setPlaylist(files, watcher) - self._roomManager.broadcastRoom(watcher, lambda w: w.setPlaylist(watcher.getName(), files)) + room = watcher.getRoom() + if room.canControl(watcher): + watcher.getRoom().setPlaylist(files, watcher) + self._roomManager.broadcastRoom(watcher, lambda w: w.setPlaylist(watcher.getName(), files)) + else: + watcher.setPlaylist(room.getName(), room.getPlaylist()) + watcher.setPlaylistIndex(room.getName(), room.getPlaylistIndex()) + def setPlaylistIndex(self, watcher, index): - watcher.getRoom().setPlaylistIndex(index, watcher) - self._roomManager.broadcastRoom(watcher, lambda w: w.setPlaylistIndex(watcher.getName(), index)) + room = watcher.getRoom() + if room.canControl(watcher): + watcher.getRoom().setPlaylistIndex(index, watcher) + self._roomManager.broadcastRoom(watcher, lambda w: w.setPlaylistIndex(watcher.getName(), index)) + else: + watcher.setPlaylistIndex(room.getName(), room.getPlaylistIndex()) class RoomManager(object): def __init__(self): From ed5eb55aab228e29cf651692f2d38d6eb804e81c Mon Sep 17 00:00:00 2001 From: Et0h Date: Sat, 9 Jan 2016 08:37:21 +0000 Subject: [PATCH 050/134] Dialogs for adding multiple files/URLs to playlist --- syncplay/ui/gui.py | 99 ++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 83 insertions(+), 16 deletions(-) diff --git a/syncplay/ui/gui.py b/syncplay/ui/gui.py index d2e7c48..abe1f91 100644 --- a/syncplay/ui/gui.py +++ b/syncplay/ui/gui.py @@ -604,6 +604,8 @@ class MainWindow(QtGui.QMainWindow): menu.addSeparator() menu.addAction(QtGui.QPixmap(resourcespath + "arrow_switch.png"), "Shuffle playlist", lambda: self.shufflePlaylist()) menu.addAction(QtGui.QPixmap(resourcespath + "arrow_undo.png"), "Undo last change to playlist", lambda: self.undoPlaylistChange()) + menu.addAction(QtGui.QPixmap(resourcespath + "film_add.png"), "Add file(s) to bottom of playlist", lambda: self.OpenAddFilesToPlaylistDialog()) + menu.addAction(QtGui.QPixmap(resourcespath + "world_add.png"), "Add URL(s) to bottom of playlist", lambda: self.OpenAddURIsToPlaylistDialog()) menu.exec_(self.playlist.viewport().mapToGlobal(position)) @@ -758,6 +760,8 @@ class MainWindow(QtGui.QMainWindow): if ok and seekTime != '': self.seekPosition(seekTime) + + def seekFromButton(self): self.seekPosition(self.seekInput.text()) @@ -813,6 +817,19 @@ class MainWindow(QtGui.QMainWindow): settings.setValue("mediadir", self.mediadirectory) settings.endGroup() + def getInitialMediaDirectory(self): + if self.config["mediaSearchDirectories"] and os.path.isdir(self.config["mediaSearchDirectories"][0]): + defaultdirectory = self.config["mediaSearchDirectories"][0] + elif os.path.isdir(self.mediadirectory): + defaultdirectory = self.mediadirectory + elif os.path.isdir(QtGui.QDesktopServices.storageLocation(QtGui.QDesktopServices.MoviesLocation)): + defaultdirectory = QtGui.QDesktopServices.storageLocation(QtGui.QDesktopServices.MoviesLocation) + elif os.path.isdir(QtGui.QDesktopServices.storageLocation(QtGui.QDesktopServices.HomeLocation)): + defaultdirectory = QtGui.QDesktopServices.storageLocation(QtGui.QDesktopServices.HomeLocation) + else: + defaultdirectory = "" + return defaultdirectory + @needsClient def browseMediapath(self): if self._syncplayClient._player.customOpenDialog == True: @@ -825,16 +842,8 @@ class MainWindow(QtGui.QMainWindow): currentdirectory = os.path.dirname(self._syncplayClient.userlist.currentUser.file["path"]) if self._syncplayClient.userlist.currentUser.file else None if currentdirectory and os.path.isdir(currentdirectory): defaultdirectory = currentdirectory - elif self.config["mediaSearchDirectories"] and os.path.isdir(self.config["mediaSearchDirectories"][0]): - defaultdirectory = self.config["mediaSearchDirectories"][0] - elif os.path.isdir(self.mediadirectory): - defaultdirectory = self.mediadirectory - elif os.path.isdir(QtGui.QDesktopServices.storageLocation(QtGui.QDesktopServices.MoviesLocation)): - defaultdirectory = QtGui.QDesktopServices.storageLocation(QtGui.QDesktopServices.MoviesLocation) - elif os.path.isdir(QtGui.QDesktopServices.storageLocation(QtGui.QDesktopServices.HomeLocation)): - defaultdirectory = QtGui.QDesktopServices.storageLocation(QtGui.QDesktopServices.HomeLocation) else: - defaultdirectory = "" + defaultdirectory = self.getInitialMediaDirectory() browserfilter = "All files (*)" fileName, filtr = QtGui.QFileDialog.getOpenFileName(self, getMessage("browseformedia-label"), defaultdirectory, browserfilter, "", options) @@ -846,6 +855,59 @@ class MainWindow(QtGui.QMainWindow): self.saveMediaBrowseSettings() self._syncplayClient._player.openFile(fileName) + @needsClient + def OpenAddFilesToPlaylistDialog(self): + if self._syncplayClient._player.customOpenDialog == True: + self._syncplayClient._player.openCustomOpenDialog() + return + + self.loadMediaBrowseSettings() + options = QtGui.QFileDialog.Options() + self.mediadirectory = "" + currentdirectory = os.path.dirname(self._syncplayClient.userlist.currentUser.file["path"]) if self._syncplayClient.userlist.currentUser.file else None + if currentdirectory and os.path.isdir(currentdirectory): + defaultdirectory = currentdirectory + else: + defaultdirectory = self.getInitialMediaDirectory() + browserfilter = "All files (*)" + fileNames, filtr = QtGui.QFileDialog.getOpenFileNames(self, getMessage("browseformedia-label"), defaultdirectory, + browserfilter, "", options) + self.updatingPlaylist = True + if fileNames: + for fileName in fileNames: + if sys.platform.startswith('win'): + fileName = fileName.replace("/", "\\") + self.mediadirectory = os.path.dirname(fileName) + self.FileSwitchManager.setCurrentDirectory(self.mediadirectory) + self.saveMediaBrowseSettings() + self.addFileToPlaylist(fileName) + self.updatingPlaylist = False + self.playlist.updatePlaylist(self.getPlaylistState()) + + @needsClient + def OpenAddURIsToPlaylistDialog(self): + URIsDialog = QtGui.QDialog() + URIsDialog.setWindowTitle("Add URLs to playlist (one per line)") + URIsLayout = QtGui.QGridLayout() + URIsTextbox = QtGui.QPlainTextEdit() + URIsTextbox.setLineWrapMode(QtGui.QPlainTextEdit.NoWrap) + URIsLayout.addWidget(URIsTextbox, 0, 0, 1, 1) + URIsButtonBox = QtGui.QDialogButtonBox() + URIsButtonBox.setOrientation(Qt.Horizontal) + URIsButtonBox.setStandardButtons(QtGui.QDialogButtonBox.Ok|QtGui.QDialogButtonBox.Cancel) + URIsButtonBox.accepted.connect(URIsDialog.accept) + URIsButtonBox.rejected.connect(URIsDialog.reject) + URIsLayout.addWidget(URIsButtonBox, 1, 0, 1, 1) + URIsDialog.setLayout(URIsLayout) + URIsDialog.show() + result = URIsDialog.exec_() + if result == QtGui.QDialog.Accepted: + URIsToAdd = utils.convertMultilineStringToList(URIsTextbox.toPlainText()) + self.updatingPlaylist = True + for URI in URIsToAdd: + self.addStreamToPlaylist(URI) + self.updatingPlaylist = False + @needsClient def promptForStreamURL(self): streamURL, ok = QtGui.QInputDialog.getText(self, getMessage("promptforstreamurl-msgbox-label"), @@ -1352,13 +1414,18 @@ class MainWindow(QtGui.QMainWindow): def addFileToPlaylist(self, filePath, index = -1): if os.path.isfile(filePath): self.removePlaylistNote() - if index == -1: - self.playlist.addItem(os.path.basename(filePath)) + filename = os.path.basename(filePath) + if self.playlist == -1: + self.playlist.addItem(filename) else: - filename = os.path.basename(filePath) - - if self.noPlaylistDuplicates(filename): - self.playlist.insertItem(index, filename) + self.playlist.insertItem(index, filename) + elif isURL(filePath): + self.removePlaylistNote() + if self.noPlaylistDuplicates(filePath): + if index == -1: + self.playlist.addItem(filePath) + else: + self.playlist.insertItem(index, filePath) def openFile(self, filePath, resetPosition=False): self._syncplayClient._player.openFile(filePath, resetPosition) @@ -1382,7 +1449,7 @@ class MainWindow(QtGui.QMainWindow): self.clearedPlaylistNote = True def addFolderToPlaylist(self, folderPath): - self.showErrorMessage("Add Folder {}".format(folderPath)) + self.showErrorMessage("Add Folder {}".format(folderPath)) # TODO: Implement "add folder to playlist" def deleteSelectedPlaylistItems(self): self.playlist.remove_selected_items() From 21ea14823ee2a8c38c242c6e7a0585c23582018f Mon Sep 17 00:00:00 2001 From: Et0h Date: Mon, 11 Jan 2016 19:31:03 +0000 Subject: [PATCH 051/134] Mitigate against mpv hanging on Windows --- syncplay/players/mpv.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/syncplay/players/mpv.py b/syncplay/players/mpv.py index de3f9c8..cc9a66d 100644 --- a/syncplay/players/mpv.py +++ b/syncplay/players/mpv.py @@ -163,12 +163,14 @@ class NewMpvPlayer(OldMpvPlayer): def askForStatus(self): self._positionAsk.clear() self._pausedAsk.clear() - self._getPaused() - self._getPosition() + self._getPausedAndPosition() self._positionAsk.wait(constants.MPV_LOCK_WAIT_TIME) self._pausedAsk.wait(constants.MPV_LOCK_WAIT_TIME) self._client.updatePlayerStatus(self._paused if self.fileLoaded else self._client.getGlobalPaused(), self.getCalculatedPosition()) + def _getPausedAndPosition(self): + self._listener.sendLine(u"print_text ANS_pause=${pause}\r\nprint_text ANS_time-pos=${=time-pos}") + def _preparePlayer(self): if self.delayedFilePath: self.openFile(self.delayedFilePath) From cfe123a3616fb1c5f6a09c421ea4bab747f57b89 Mon Sep 17 00:00:00 2001 From: Et0h Date: Tue, 12 Jan 2016 22:57:32 +0000 Subject: [PATCH 052/134] Add initial/experimental playlist looping code --- syncplay/client.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/syncplay/client.py b/syncplay/client.py index 0fc289f..98445ff 100644 --- a/syncplay/client.py +++ b/syncplay/client.py @@ -176,6 +176,8 @@ class SyncplayClient(object): if self._playlistIndex is None or len(self._playlist) <= self._playlistIndex+1: return filename = self._playlist[self._playlistIndex+1] + if filename == "": + filename = self._playlist[0] if utils.isURL(filename): for URI in constants.SAFE_URIS: if filename.startswith(URI): From d87511f60ef0a5dfe66635ef17d8ea19b71a2778 Mon Sep 17 00:00:00 2001 From: Et0h Date: Sun, 17 Jan 2016 12:43:10 +0000 Subject: [PATCH 053/134] Allow shared playlists to be disabled from main UI, and make this persistent --- syncplay/client.py | 78 +++++++++++++++++++++--------- syncplay/ui/ConfigurationGetter.py | 15 +++++- syncplay/ui/gui.py | 22 +++++++-- 3 files changed, 85 insertions(+), 30 deletions(-) diff --git a/syncplay/client.py b/syncplay/client.py index 98445ff..5cd7e22 100644 --- a/syncplay/client.py +++ b/syncplay/client.py @@ -130,6 +130,15 @@ class SyncplayClient(object): if missingStrings is not None and missingStrings is not "": self.ui.showDebugMessage("MISSING/UNUSED STRINGS DETECTED:\n{}".format(missingStrings)) + def needsSharedPlaylistsEnabled(f): # @NoSelf + @wraps(f) + def wrapper(self, *args, **kwds): + if not self._config['sharedPlaylistEnabled']: + self.ui.showDebugMessage("Tried to use shared playlists when it was disabled!") + return + return f(self, *args, **kwds) + return wrapper + def initProtocol(self, protocol): self._protocol = protocol @@ -171,6 +180,7 @@ class SyncplayClient(object): seeked = _playerDiff > constants.SEEK_THRESHOLD and _globalDiff > constants.SEEK_THRESHOLD return pauseChange, seeked + @needsSharedPlaylistsEnabled def loadNextFileInPlaylist(self): # TODO: Fix for GUIDs & add path checks (and make more of code re-use?) if self._playlistIndex is None or len(self._playlist) <= self._playlistIndex+1: @@ -467,9 +477,12 @@ class SyncplayClient(object): path = None if index is None: return + if username is None and not self._config['sharedPlaylistEnabled']: + return try: filename = self._playlist[index] self.ui.setPlaylistIndexFilename(filename) + self._playlistIndex = index if username is not None and self.userlist.currentUser.file and filename == self.userlist.currentUser.file['name']: return except IndexError: @@ -478,31 +491,37 @@ class SyncplayClient(object): if self._player is None: self.__playerReady.addCallback(lambda x: self.changeToPlaylistIndex(index, username)) return - self._playlistIndex = index - if username is None and self._protocol and self._protocol.logged: + + if username is None and self._protocol and self._protocol.logged and self._config["sharedPlaylistEnabled"]: + self._playlistIndex = index self._protocol.setPlaylistIndex(index) else: self.ui.showMessage(u"{} changed the playlist selection".format(username)) - # TODO: Display info about playlist file change - try: - filename = self._playlist[index] - if utils.isURL(filename): - for URI in constants.SAFE_URIS: - if filename.startswith(URI): - self._player.openFile(filename) - return - self.ui.showErrorMessage(u"Could not load {} because it is not known as a safe path.".format(filename)) - return - else: - path = self.findFilenameInDirectories(filename) - # TODO: Find Path properly - if path: - self._player.openFile(path) - else: - self.ui.showErrorMessage(u"Could not find file {} for playlist switch!".format(filename)) - return - except IndexError: - pass + self._playlistIndex = index + self.switchToNewPlaylistIndex(index) + + + @needsSharedPlaylistsEnabled + def switchToNewPlaylistIndex(self, index): + try: + filename = self._playlist[index] + if utils.isURL(filename): + for URI in constants.SAFE_URIS: + if filename.startswith(URI): + self._player.openFile(filename) + return + self.ui.showErrorMessage(u"Could not load {} because it is not known as a safe path.".format(filename)) + return + else: + path = self.findFilenameInDirectories(filename) + # TODO: Find Path properly + if path: + self._player.openFile(path) + else: + self.ui.showErrorMessage(u"Could not find file {} for playlist switch!".format(filename)) + return + except IndexError: + self.ui.showDebugMessage("Could not change playlist index due to IndexError") def changePlaylist(self, files, username = None): try: @@ -519,13 +538,15 @@ class SyncplayClient(object): self._playlist = files if username is None and self._protocol and self._protocol.logged: - self._protocol.setPlaylist(files) - self.changeToPlaylistIndex(newIndex) + if self._config['sharedPlaylistEnabled']: + self._protocol.setPlaylist(files) + self.changeToPlaylistIndex(newIndex) else: self.ui.setPlaylist(self._playlist) self.changeToPlaylistIndex(newIndex, username) self.ui.showMessage(u"{} updated the playlist".format(username)) + @needsSharedPlaylistsEnabled def undoPlaylistChange(self): if self._previousPlaylist is not None and self._playlist <> self._previousPlaylist: undidPlaylist = self._playlist @@ -533,6 +554,7 @@ class SyncplayClient(object): self.changePlaylist(self._previousPlaylist) self._previousPlaylist = undidPlaylist + @needsSharedPlaylistsEnabled def shufflePlaylist(self): if self._playlist and len(self._playlist) > 0: oldPlaylist = self._playlist @@ -685,6 +707,14 @@ class SyncplayClient(object): return wrapper return requireMinVersionDecorator + def changePlaylistEnabledState(self, newState): + oldState = self._config["sharedPlaylistEnabled"] + from syncplay.ui.ConfigurationGetter import ConfigurationGetter + ConfigurationGetter().setConfigOption("sharedPlaylistEnabled", newState) + self._config["sharedPlaylistEnabled"] = newState + if oldState == False and newState == True: + self.changeToPlaylistIndex(self._playlistIndex) + def changeAutoplayState(self, newState): self.autoPlay = newState self.autoplayCheck() diff --git a/syncplay/ui/ConfigurationGetter.py b/syncplay/ui/ConfigurationGetter.py index f2f2929..35611ef 100755 --- a/syncplay/ui/ConfigurationGetter.py +++ b/syncplay/ui/ConfigurationGetter.py @@ -34,6 +34,7 @@ class ConfigurationGetter(object): "playerPath": None, "perPlayerArguments": None, "mediaSearchDirectories": None, + "sharedPlaylistEnabled": False, "file": None, "playerArgs": [], "playerClass": None, @@ -102,7 +103,8 @@ class ConfigurationGetter(object): "showDifferentRoomOSD", "showSameRoomOSD", "showNonControllerOSD", - "showDurationNotification" + "showDurationNotification", + "sharedPlaylistEnabled" ] self._tristate = [ "checkForUpdatesAutomatically", @@ -123,7 +125,7 @@ class ConfigurationGetter(object): self._iniStructure = { "server_data": ["host", "port", "password"], - "client_settings": ["name", "room", "playerPath", "perPlayerArguments", "slowdownThreshold", "rewindThreshold", "fastforwardThreshold", "slowOnDesync", "rewindOnDesync", "fastforwardOnDesync", "dontSlowDownWithMe", "forceGuiPrompt", "filenamePrivacyMode", "filesizePrivacyMode", "unpauseAction", "pauseOnLeave", "readyAtStart", "autoplayMinUsers", "autoplayInitialState", "autoplayRequireSameFilenames", "mediaSearchDirectories"], + "client_settings": ["name", "room", "playerPath", "perPlayerArguments", "slowdownThreshold", "rewindThreshold", "fastforwardThreshold", "slowOnDesync", "rewindOnDesync", "fastforwardOnDesync", "dontSlowDownWithMe", "forceGuiPrompt", "filenamePrivacyMode", "filesizePrivacyMode", "unpauseAction", "pauseOnLeave", "readyAtStart", "autoplayMinUsers", "autoplayInitialState", "autoplayRequireSameFilenames", "mediaSearchDirectories", "sharedPlaylistEnabled"], "gui": ["showOSD", "showOSDWarnings", "showSlowdownOSD", "showDifferentRoomOSD", "showSameRoomOSD", "showNonControllerOSD", "showDurationNotification"], "general": ["language", "checkForUpdatesAutomatically", "lastCheckedForUpdates"] } @@ -411,6 +413,15 @@ class ConfigurationGetter(object): qt4reactor.install() return self._config + def setConfigOption(self, option, value): + path = self._getConfigurationFilePath() + backup = self._config.copy() + self._parseConfigFile(path) + self._config[option] = value + backup[option] = value + self._saveConfig(path) + self._config = backup + class SafeConfigParserUnicode(SafeConfigParser): def write(self, fp): """Write an .ini-format representation of the configuration state.""" diff --git a/syncplay/ui/gui.py b/syncplay/ui/gui.py index 828e82b..0565240 100644 --- a/syncplay/ui/gui.py +++ b/syncplay/ui/gui.py @@ -91,6 +91,8 @@ class MainWindow(QtGui.QMainWindow): playlistIndex = None def setPlaylistInsertPosition(self, newPosition): + if not self.playlist.isEnabled(): + return if MainWindow.insertPosition <> newPosition: MainWindow.insertPosition = newPosition self.playlist.forceUpdate() @@ -146,6 +148,8 @@ class MainWindow(QtGui.QMainWindow): def dropEvent(self, event): window = self.parent().parent().parent().parent().parent() + if not window.playlist.isEnabled(): + return window.setPlaylistInsertPosition(None) if QtGui.QDropEvent.proposedAction(event) == Qt.MoveAction: QtGui.QDropEvent.setDropAction(event, Qt.CopyAction) # Avoids file being deleted @@ -233,6 +237,8 @@ class MainWindow(QtGui.QMainWindow): def dropEvent(self, event): window = self.parent().parent().parent().parent().parent().parent() + if not window.playlist.isEnabled(): + return window.setPlaylistInsertPosition(None) if QtGui.QDropEvent.proposedAction(event) == Qt.MoveAction: QtGui.QDropEvent.setDropAction(event, Qt.CopyAction) # Avoids file being deleted @@ -425,6 +431,9 @@ class MainWindow(QtGui.QMainWindow): self.roomInput.setText(self._syncplayClient.getRoom()) self.config = self._syncplayClient.getConfig() try: + self.playlistGroup.blockSignals(True) + self.playlistGroup.setChecked(self.config['sharedPlaylistEnabled']) + self.playlistGroup.blockSignals(False) self.FileSwitchManager.setMediaDirectories(self.config["mediaSearchDirectories"]) self.updateReadyState(self.config['readyAtStart']) autoplayInitialState = self.config['autoplayInitialState'] @@ -637,10 +646,11 @@ class MainWindow(QtGui.QMainWindow): if roomToJoin <> self._syncplayClient.getRoom(): menu.addAction("Join room {}".format(roomToJoin), lambda: self.joinRoom(roomToJoin)) elif username and filename and filename <> getMessage("nofile-note"): - if isURL(filename): - menu.addAction(QtGui.QPixmap(resourcespath + "world_add.png"), "Add {} stream to playlist".format(shortUsername), lambda: self.addStreamToPlaylist(filename)) - else: - menu.addAction(QtGui.QPixmap(resourcespath + "film_add.png"), "Add {} file to playlist".format(shortUsername), lambda: self.addStreamToPlaylist(filename)) + if self.config['sharedPlaylistEnabled']: + if isURL(filename): + menu.addAction(QtGui.QPixmap(resourcespath + "world_add.png"), "Add {} stream to playlist".format(shortUsername), lambda: self.addStreamToPlaylist(filename)) + else: + menu.addAction(QtGui.QPixmap(resourcespath + "film_add.png"), "Add {} file to playlist".format(shortUsername), lambda: self.addStreamToPlaylist(filename)) if self._syncplayClient.userlist.currentUser.file is None or filename <> self._syncplayClient.userlist.currentUser.file["name"]: if isURL(filename): @@ -1077,6 +1087,7 @@ class MainWindow(QtGui.QMainWindow): window.playlistGroup = self.PlaylistGroupBox(u"Enable shared playlists") window.playlistGroup.setCheckable(True) + window.playlistGroup.toggled.connect(self.changePlaylistEnabledState) window.playlistLayout = QtGui.QHBoxLayout() window.playlistGroup.setSizePolicy(QtGui.QSizePolicy.Preferred, QtGui.QSizePolicy.Preferred) window.playlistGroup.setAcceptDrops(True) @@ -1300,6 +1311,9 @@ class MainWindow(QtGui.QMainWindow): else: self.showDebugMessage("Tried to change ready state too soon.") + def changePlaylistEnabledState(self): + self._syncplayClient.changePlaylistEnabledState(self.playlistGroup.isChecked()) + @needsClient def changeAutoplayThreshold(self, source=None): self._syncplayClient.changeAutoPlayThrehsold(self.autoplayThresholdSpinbox.value()) From cfcf9252cc75868e98a54f721d7f53fcfe4d98c2 Mon Sep 17 00:00:00 2001 From: Et0h Date: Sun, 17 Jan 2016 12:44:09 +0000 Subject: [PATCH 054/134] Add playlist duplicate check to file drop --- syncplay/ui/gui.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/syncplay/ui/gui.py b/syncplay/ui/gui.py index 0565240..fab4a65 100644 --- a/syncplay/ui/gui.py +++ b/syncplay/ui/gui.py @@ -1430,10 +1430,11 @@ class MainWindow(QtGui.QMainWindow): if os.path.isfile(filePath): self.removePlaylistNote() filename = os.path.basename(filePath) - if self.playlist == -1: - self.playlist.addItem(filename) - else: - self.playlist.insertItem(index, filename) + if self.noPlaylistDuplicates(filename): + if self.playlist == -1: + self.playlist.addItem(filename) + else: + self.playlist.insertItem(index, filename) elif isURL(filePath): self.removePlaylistNote() if self.noPlaylistDuplicates(filePath): From 7898916e9043c5608a85bcf85c8b82ddc48a0c10 Mon Sep 17 00:00:00 2001 From: Et0h Date: Sun, 17 Jan 2016 12:46:22 +0000 Subject: [PATCH 055/134] Make menu options unicode to support non-ASCII usernames/filenames --- syncplay/ui/gui.py | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/syncplay/ui/gui.py b/syncplay/ui/gui.py index fab4a65..4b882f1 100644 --- a/syncplay/ui/gui.py +++ b/syncplay/ui/gui.py @@ -604,17 +604,17 @@ class MainWindow(QtGui.QMainWindow): firstFile = item.sibling(item.row(), 0).data() if self._syncplayClient.userlist.currentUser.file is None or firstFile <> self._syncplayClient.userlist.currentUser.file["name"]: if isURL(firstFile): - menu.addAction(QtGui.QPixmap(resourcespath + "world_go.png"), "Open stream", lambda: self.openFile(firstFile)) + menu.addAction(QtGui.QPixmap(resourcespath + "world_go.png"), u"Open stream", lambda: self.openFile(firstFile)) else: pathFound = MainWindow.FileSwitchManager.findFilepath(firstFile) if pathFound: - menu.addAction(QtGui.QPixmap(resourcespath + "film_go.png"), "Open file", lambda: self.openFile(pathFound)) - menu.addAction(QtGui.QPixmap(resourcespath + "delete.png"), "Remove from playlist", lambda: self.deleteSelectedPlaylistItems()) + menu.addAction(QtGui.QPixmap(resourcespath + "film_go.png"), u"Open file", lambda: self.openFile(pathFound)) + menu.addAction(QtGui.QPixmap(resourcespath + "delete.png"), u"Remove from playlist", lambda: self.deleteSelectedPlaylistItems()) menu.addSeparator() - menu.addAction(QtGui.QPixmap(resourcespath + "arrow_switch.png"), "Shuffle playlist", lambda: self.shufflePlaylist()) - menu.addAction(QtGui.QPixmap(resourcespath + "arrow_undo.png"), "Undo last change to playlist", lambda: self.undoPlaylistChange()) - menu.addAction(QtGui.QPixmap(resourcespath + "film_add.png"), "Add file(s) to bottom of playlist", lambda: self.OpenAddFilesToPlaylistDialog()) - menu.addAction(QtGui.QPixmap(resourcespath + "world_add.png"), "Add URL(s) to bottom of playlist", lambda: self.OpenAddURIsToPlaylistDialog()) + menu.addAction(QtGui.QPixmap(resourcespath + "arrow_switch.png"), u"Shuffle playlist", lambda: self.shufflePlaylist()) + menu.addAction(QtGui.QPixmap(resourcespath + "arrow_undo.png"), u"Undo last change to playlist", lambda: self.undoPlaylistChange()) + menu.addAction(QtGui.QPixmap(resourcespath + "film_add.png"), u"Add file(s) to bottom of playlist", lambda: self.OpenAddFilesToPlaylistDialog()) + menu.addAction(QtGui.QPixmap(resourcespath + "world_add.png"), u"Add URL(s) to bottom of playlist", lambda: self.OpenAddURIsToPlaylistDialog()) menu.exec_(self.playlist.viewport().mapToGlobal(position)) @@ -644,21 +644,21 @@ class MainWindow(QtGui.QMainWindow): item = item.parent() roomToJoin = item.sibling(item.row(), 0).data() if roomToJoin <> self._syncplayClient.getRoom(): - menu.addAction("Join room {}".format(roomToJoin), lambda: self.joinRoom(roomToJoin)) + menu.addAction(u"Join room {}".format(roomToJoin), lambda: self.joinRoom(roomToJoin)) elif username and filename and filename <> getMessage("nofile-note"): if self.config['sharedPlaylistEnabled']: if isURL(filename): - menu.addAction(QtGui.QPixmap(resourcespath + "world_add.png"), "Add {} stream to playlist".format(shortUsername), lambda: self.addStreamToPlaylist(filename)) + menu.addAction(QtGui.QPixmap(resourcespath + "world_add.png"), u"Add {} stream to playlist".format(shortUsername), lambda: self.addStreamToPlaylist(filename)) else: - menu.addAction(QtGui.QPixmap(resourcespath + "film_add.png"), "Add {} file to playlist".format(shortUsername), lambda: self.addStreamToPlaylist(filename)) + menu.addAction(QtGui.QPixmap(resourcespath + "film_add.png"), u"Add {} file to playlist".format(shortUsername), lambda: self.addStreamToPlaylist(filename)) if self._syncplayClient.userlist.currentUser.file is None or filename <> self._syncplayClient.userlist.currentUser.file["name"]: if isURL(filename): - menu.addAction(QtGui.QPixmap(resourcespath + "world_go.png"), "Open {} stream".format(shortUsername), lambda: self.openFile(filename)) + menu.addAction(QtGui.QPixmap(resourcespath + "world_go.png"), u"Open {} stream".format(shortUsername), lambda: self.openFile(filename)) else: pathFound = MainWindow.FileSwitchManager.findFilepath(filename) if pathFound: - menu.addAction(QtGui.QPixmap(resourcespath + "film_go.png"), "Open {} file".format(shortUsername), lambda: self.openFile(pathFound)) + menu.addAction(QtGui.QPixmap(resourcespath + "film_go.png"), u"Open {} file".format(shortUsername), lambda: self.openFile(pathFound)) else: return menu.exec_(self.listTreeView.viewport().mapToGlobal(position)) From 8ec9eea45dd3f26e6b18313d4741a07d0ba55c3b Mon Sep 17 00:00:00 2001 From: Et0h Date: Sun, 17 Jan 2016 12:51:24 +0000 Subject: [PATCH 056/134] Don't prompt users to add duplicate files to playlist --- syncplay/ui/gui.py | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/syncplay/ui/gui.py b/syncplay/ui/gui.py index 4b882f1..ea5eb41 100644 --- a/syncplay/ui/gui.py +++ b/syncplay/ui/gui.py @@ -646,7 +646,7 @@ class MainWindow(QtGui.QMainWindow): if roomToJoin <> self._syncplayClient.getRoom(): menu.addAction(u"Join room {}".format(roomToJoin), lambda: self.joinRoom(roomToJoin)) elif username and filename and filename <> getMessage("nofile-note"): - if self.config['sharedPlaylistEnabled']: + if self.config['sharedPlaylistEnabled'] and not self.isItemInPlaylist(filename): if isURL(filename): menu.addAction(QtGui.QPixmap(resourcespath + "world_add.png"), u"Add {} stream to playlist".format(shortUsername), lambda: self.addStreamToPlaylist(filename)) else: @@ -1447,11 +1447,17 @@ class MainWindow(QtGui.QMainWindow): self._syncplayClient._player.openFile(filePath, resetPosition) def noPlaylistDuplicates(self, filename): + if self.isItemInPlaylist(filename): + self.showErrorMessage(u"Could not add second entry for '{}' to the playlist as no duplicates are allowed.".format(filename)) + return False + else: + return True + + def isItemInPlaylist(self, filename): for playlistindex in xrange(self.playlist.count()): if self.playlist.item(playlistindex).text() == filename: - self.showErrorMessage(u"Could not add second entry for '{}' to the playlist as no duplicates are allowed.".format(filename)) - return False - return True + return True + return False def addStreamToPlaylist(self, streamURI): self.removePlaylistNote() From 4e4e3e44c4531a21c4f0d8e0f4c6d43297ea5b37 Mon Sep 17 00:00:00 2001 From: Et0h Date: Sun, 17 Jan 2016 23:47:12 +0000 Subject: [PATCH 057/134] Add "enable shared playlists" as a guiConfig option (and move 'run without storing' into its own button) --- syncplay/messages.py | 11 ++++++--- syncplay/ui/GuiConfiguration.py | 40 ++++++++++++++++----------------- 2 files changed, 28 insertions(+), 23 deletions(-) diff --git a/syncplay/messages.py b/syncplay/messages.py index 7a3349b..18e11d9 100755 --- a/syncplay/messages.py +++ b/syncplay/messages.py @@ -175,6 +175,7 @@ en = { "player-arguments-label" : "Player arguments (if any):", "browse-label" : "Browse", "update-server-list-label" : u"Update list", + "sharedplaylistenabled-label" : u"Enable shared playlists", "more-title" : "Show more settings", "never-rewind-value" : "Never", @@ -192,7 +193,6 @@ en = { "pauseonleave-label" : "Pause when user leaves (e.g. if they are disconnected)", "readyatstart-label" : "Set me as 'ready to watch' by default", "forceguiprompt-label" : "Don't always show the Syncplay configuration window", # (Inverted) - "nostore-label" : "Don't store this configuration", # (Inverted) "showosd-label" : "Enable OSD Messages", "showosdwarnings-label" : "Include warnings (e.g. when files are different, users not ready)", @@ -293,6 +293,7 @@ en = { "executable-path-tooltip" : "Location of your chosen supported media player (MPC-HC, VLC, mplayer2 or mpv).", "media-path-tooltip" : "Location of video or stream to be opened. Necessary for mpv and mplayer2.", "player-arguments-tooltip" : "Additional command line arguments / switches to pass on to this media player.", + "sharedplaylistenabled-tooltip" : u"Room operators can add files to a synced playlist to make it easy for everyone to watching the same thing. Configure media directories under 'Misc'.", "mediasearcdirectories-arguments-tooltip" : u"Directories where Syncplay will search for media files, e.g. when you are using the click to switch feature. Syncplay will look recursively through sub-folders.", "more-tooltip" : "Display less frequently used settings.", @@ -543,8 +544,10 @@ ru = { "executable-path-label" : u"Путь к проигрывателю:", "media-path-label" : u"Путь к видеофайлу:", "player-arguments-label" : u"Аргументы для запуска проигрывателя:", + "sharedplaylistenabled-tooltip" : u"Room operators can add files to a synced playlist to make it easy for everyone to watching the same thing. Configure media directories under 'Misc'.", # TODO: Translate into Russian "browse-label" : u"Выбрать", "update-server-list-label" : u"Update list", # TODO: Translate into Russian + "sharedplaylistenabled-label" : u"Enable shared playlists", # TODO: Translate into Russian "more-title" : u"Больше настроек", "never-rewind-value" : u"Никогда", @@ -562,7 +565,6 @@ ru = { "readyatstart-label" : u"Выставить статус 'готово к просмотру' по умолчанию", "fastforwardondesync-label" : u"Ускорять видео при отставании (рекомендуется)", "forceguiprompt-label" : u"Не показывать больше этот диалог", # (Inverted) - "nostore-label" : u"Не сохранять текущую конфигурацию", # (Inverted) "showosd-label" : u"Включить экранные сообщения (поверх видео)", "showosdwarnings-label" : u"Показывать предупреждения (напр., когда файлы не совпадают)", @@ -663,6 +665,7 @@ ru = { "executable-path-tooltip" : u"Расположение Вашего видеопроигрывателя (MPC-HC, VLC, mplayer2 или mpv).", "media-path-tooltip" : u"Расположение видеофайла или потока для просмотра. Обязательно для mpv и mplayer2.", "player-arguments-tooltip" : u"Передавать дополнительные аргументы командной строки этому проигрывателю.", + "sharedplaylistenabled-tooltip" : u"Room operators can add files to a synced playlist to make it easy for everyone to watching the same thing. Configure media directories under 'Misc'.", # TODO: Translate into Russian "mediasearcdirectories-arguments-tooltip" : u"Directories where Syncplay will search for media files, e.g. when you are using the click to switch feature. Syncplay will look recursively through sub-folders.", # TODO: Translate into Russian "more-tooltip" : u"Показать дополнительные настройки.", @@ -915,6 +918,7 @@ de = { "player-arguments-label" : u"Playerparameter:", "browse-label" : u"Durchsuchen", "update-server-list-label" : u"Liste aktualisieren", + "sharedplaylistenabled-label" : u"Enable shared playlists", # TODO: Translate into German "more-title" : u"Mehr Einstellungen zeigen", "never-rewind-value" : u"Niemals", @@ -930,7 +934,7 @@ de = { "pauseonleave-label" : u"Pausieren wenn ein Benutzer austritt", "readyatstart-label" : u"Standardmäßig auf \'Bereit\' stellen", "forceguiprompt-label" : u"Diesen Dialog nicht mehr anzeigen", - "nostore-label" : u"Diese Konfiguration nicht speichern", + "showosd-label" : u"OSD-Nachrichten anzeigen", "showosdwarnings-label" : u"Zeige Warnungen (z.B. wenn Dateien verschieden)", @@ -1031,6 +1035,7 @@ de = { "executable-path-tooltip" : u"Pfad zum ausgewählten, unterstützten Mediaplayer (MPC-HC, VLC, mplayer2 or mpv).", "media-path-tooltip" : u"Pfad zum wiederzugebenden Video oder Stream. Notwendig für mpv und mplayer2.", "player-arguments-tooltip" : u"Zusätzliche Kommandozeilenparameter / -schalter für diesen Mediaplayer.", + "sharedplaylistenabled-tooltip" : u"Room operators can add files to a synced playlist to make it easy for everyone to watching the same thing. Configure media directories under 'Misc'.", # TODO: Translate into German "mediasearcdirectories-arguments-tooltip" : u"Verzeichnisse, in denen Syncplay nach Mediendateien suchen soll, z.B. wenn du das Click-to-switch-Feature verwendest. Syncplay wird rekursiv Unterordner durchsuchen.", # TODO: Translate Click-to-switch? (or use as name for feature) "more-tooltip" : u"Weitere Einstellungen anzeigen.", diff --git a/syncplay/ui/GuiConfiguration.py b/syncplay/ui/GuiConfiguration.py index c158034..b0ac598 100644 --- a/syncplay/ui/GuiConfiguration.py +++ b/syncplay/ui/GuiConfiguration.py @@ -71,7 +71,7 @@ class ConfigDialog(QtGui.QDialog): moreToggling = False def automaticUpdatePromptCheck(self): - if self.automaticupdatesCheckbox.checkState() == Qt.PartiallyChecked and not self.nostoreCheckbox.isChecked(): + if self.automaticupdatesCheckbox.checkState() == Qt.PartiallyChecked: reply = QtGui.QMessageBox.question(self, "Syncplay", getMessage("promptforupdate-label"), QtGui.QMessageBox.StandardButton.Yes | QtGui.QMessageBox.StandardButton.No) if reply == QtGui.QMessageBox.Yes: @@ -87,9 +87,9 @@ class ConfigDialog(QtGui.QDialog): if self.showmoreCheckbox.isChecked(): self.tabListFrame.show() self.resetButton.show() - self.nostoreCheckbox.show() self.playerargsTextbox.show() self.playerargsLabel.show() + self.runButton.show() self.saveMoreState(True) self.tabListWidget.setCurrentRow(0) self.ensureTabListIsVisible() @@ -97,9 +97,9 @@ class ConfigDialog(QtGui.QDialog): else: self.tabListFrame.hide() self.resetButton.hide() - self.nostoreCheckbox.hide() self.playerargsTextbox.hide() self.playerargsLabel.hide() + self.runButton.hide() self.saveMoreState(False) self.stackedLayout.setCurrentIndex(0) newHeight = self.connectionSettingsGroup.minimumSizeHint().height()+self.mediaplayerSettingsGroup.minimumSizeHint().height()+self.bottomButtonFrame.minimumSizeHint().height()+3 @@ -112,12 +112,6 @@ class ConfigDialog(QtGui.QDialog): self.setFixedWidth(self.minimumSizeHint().width()) self.executablepathCombobox.setFixedWidth(self.mediapathTextbox.width()) - def runButtonTextUpdate(self): - if self.nostoreCheckbox.isChecked(): - self.runButton.setText(getMessage("run-label")) - else: - self.runButton.setText(getMessage("storeandrun-label")) - def openHelp(self): self.QtGui.QDesktopServices.openUrl(QUrl("http://syncplay.pl/guide/client/")) @@ -331,9 +325,14 @@ class ConfigDialog(QtGui.QDialog): self.mediapathTextbox.setText(os.path.normpath(fileName)) self.mediadirectory = os.path.dirname(fileName) self.saveMediaBrowseSettings() - - def _saveDataAndLeave(self): - self.automaticUpdatePromptCheck() + + def _runWithoutStoringConfig(self): + self._saveDataAndLeave(storeConfiguration=False) + + def _saveDataAndLeave(self, storeConfiguration=True): + self.config['noStore'] = storeConfiguration + if storeConfiguration: + self.automaticUpdatePromptCheck() self.loadLastUpdateCheckDate() self.config["perPlayerArguments"] = self.perPlayerArgs @@ -877,14 +876,16 @@ class ConfigDialog(QtGui.QDialog): self.resetButton.setObjectName("reset") self.resetButton.pressed.connect(self.resetSettings) - self.runButton = QtGui.QPushButton(QtGui.QIcon(resourcespath + 'accept.png'), getMessage("storeandrun-label")) + self.runButton = QtGui.QPushButton(QtGui.QIcon(resourcespath + 'accept.png'), getMessage("run-label")) self.runButton.pressed.connect(self._saveDataAndLeave) + self.runButton.setToolTip(getMessage("nostore-tooltip")) + self.storeAndRunButton = QtGui.QPushButton(QtGui.QIcon(resourcespath + 'accept.png'), getMessage("storeandrun-label")) + self.storeAndRunButton.pressed.connect(self._runWithoutStoringConfig) self.bottomButtonLayout.addWidget(self.helpButton) self.bottomButtonLayout.addWidget(self.resetButton) self.bottomButtonLayout.addWidget(self.runButton) + self.bottomButtonLayout.addWidget(self.storeAndRunButton) self.bottomButtonFrame.setLayout(self.bottomButtonLayout) - if config['noStore'] == True: - self.runButton.setText(getMessage("run-label")) self.bottomButtonLayout.setContentsMargins(5,0,5,0) self.mainLayout.addWidget(self.bottomButtonFrame, 1, 0, 1, 2) @@ -893,12 +894,11 @@ class ConfigDialog(QtGui.QDialog): self.bottomCheckboxLayout = QtGui.QGridLayout() self.alwaysshowCheckbox = QCheckBox(getMessage("forceguiprompt-label")) - self.nostoreCheckbox = QCheckBox(getMessage("nostore-label")) + self.enableplaylistsCheckbox = QCheckBox(getMessage("sharedplaylistenabled-label")) self.bottomCheckboxLayout.addWidget(self.showmoreCheckbox) - self.bottomCheckboxLayout.addWidget(self.nostoreCheckbox, 0, 2, Qt.AlignRight) + self.bottomCheckboxLayout.addWidget(self.enableplaylistsCheckbox, 0, 2, Qt.AlignRight) self.alwaysshowCheckbox.setObjectName(constants.INVERTED_STATE_MARKER + "forceGuiPrompt") - self.nostoreCheckbox.setObjectName("noStore") - self.nostoreCheckbox.toggled.connect(self.runButtonTextUpdate) + self.enableplaylistsCheckbox.setObjectName("sharedPlaylistEnabled") self.bottomCheckboxFrame.setLayout(self.bottomCheckboxLayout) self.mainLayout.addWidget(self.bottomCheckboxFrame, 2, 0, 1, 2) @@ -1027,10 +1027,10 @@ class ConfigDialog(QtGui.QDialog): if self.getMoreState() == False: self.tabListFrame.hide() - self.nostoreCheckbox.hide() self.resetButton.hide() self.playerargsTextbox.hide() self.playerargsLabel.hide() + self.runButton.hide() newHeight = self.connectionSettingsGroup.minimumSizeHint().height()+self.mediaplayerSettingsGroup.minimumSizeHint().height()+self.bottomButtonFrame.minimumSizeHint().height()+3 if self.error: newHeight +=self.errorLabel.height()+3 From 05d38184408a7c54dd3f04ff71ba6a277f2cac89 Mon Sep 17 00:00:00 2001 From: Et0h Date: Tue, 19 Jan 2016 13:57:02 +0000 Subject: [PATCH 058/134] Move playlist text to messages.py --- syncplay/messages.py | 64 ++++++++++++++++++++++++++++++++++++++++---- syncplay/ui/gui.py | 42 ++++++++++++++--------------- 2 files changed, 80 insertions(+), 26 deletions(-) diff --git a/syncplay/messages.py b/syncplay/messages.py index 18e11d9..07591ca 100755 --- a/syncplay/messages.py +++ b/syncplay/messages.py @@ -134,6 +134,8 @@ en = { "invalid-seek-value" : u"Invalid seek value", "invalid-offset-value" : u"Invalid offset value", + "cannot-find-file-for-playlist-switch-error " : u"Could not find file {} for playlist switch!", # Filename + "cannot-add-duplicate-error" : u"Could not add second entry for '{}' to the playlist as no duplicates are allowed.", #Filename "switch-file-not-found-error" : u"Could not switch to file '{0}'. Syncplay looks in the folder of the currently playing file and specified media directories.", # File not found "folder-search-timeout-error" : u"The search for media in '{}' was aborted as it took too long. This will occur if you select a folder with too many sub-folders in your list of media folders to search through. Until Syncplay is restarted only the directory of the currently open file will be checked.", #Folder @@ -230,7 +232,8 @@ en = { "contact-label" : "Feel free to e-mail dev@syncplay.pl, chat via the #Syncplay IRC channel on irc.freenode.net, raise an issue via GitHub, like us on Facebook, follow us on Twitter, or visit http://syncplay.pl/", - "joinroom-menu-label" : "Join room", + "joinroom-label" : "Join room", + "joinroom-menu-label" : u"Join room {}", "seektime-menu-label" : "Seek to time", "undoseek-menu-label" : "Undo seek", "play-menu-label" : "Play", @@ -267,6 +270,21 @@ en = { "userguide-menu-label" : "Open user &guide", "update-menu-label" : "Check for &update", + "removefromplaylist-menu-label" : u"Remove from playlist", + "shuffleplaylist-menuu-label" : u"Shuffle playlist", + "undoplaylist-menu-label" : u"Undo last change to playlist", + "addfilestoplaylist-menu-label" : u"Add file(s) to bottom of playlist", + "addurlstoplaylist-menu-label" : u"Add URL(s) to bottom of playlist", + + "addusersfiletoplaylist-menu-label" : u"Add {} file to playlist", # item owner indicator + "addusersstreamstoplaylist-menu-label" : u"Add {} stream to playlist", # item owner indicator + "openusersstream-menu-label" : u"Open {} stream", # [username]'s + "openusersfile-menu-label" : u"Open {} file", # [username]'s + "item-is-yours-indicator" : u"your", # Goes with addusersfiletoplaylist/addusersstreamstoplaylist + "item-is-others-indicator" : u"{}'s", # username - goes with addusersfiletoplaylist/addusersstreamstoplaylist + + "playlist-instruction-item-message" : u"Drag file here to add it to the shared playlist.", + "setoffset-msgbox-label" : "Set offset", "offsetinfo-msgbox-label" : "Offset (see http://syncplay.pl/guide/ for usage instructions):", @@ -465,7 +483,7 @@ ru = { "vlc-interface-oldversion-warning" : u"Внимание: Syncplay обнаружил, что старая версия модуля интерфейса Syncplay для VLC уже установлена в директорию VLC. Пожалуйста, обратитесь к Руководству Пользователя Syncplay (http://syncplay.pl/guide/) за инструкциями о том, как установить syncplay.lua.", "vlc-interface-not-installed" : u"Внимание: Модуль интерфейса Syncplay для VLC не обнаружен в директории VLC. По существу, если Вы используете VLC 2.0, то VLC будет использовать модуль syncplay.lua из директории Syncplay, но в таком случае другие пользовательские скрипты и расширения интерфейса не будут работать. Пожалуйста, обратитесь к Руководству Пользователя Syncplay (http://syncplay.pl/guide/) за инструкциями о том, как установить syncplay.lua.", "media-player-latency-warning": u"Внимание: У Вашего проигрывателя слишком большой отклик ({} секунд). Если Вы замечаете проблемы с синхронизацией, то закройте ресурсоемкие приложения, а если это не помогло - попробуйте другой проигрыватель.", # Seconds to respond - "mpv-unresponsive-error": u"mpv has not responded for {} seconds so appears to have malfunctioned. Please restart Syncplay.", # Seconds to respond # TODO: Translate to Russian + "mpv-unresponsive-error": u"mpv has not responded for {} seconds so appears to have malfunctioned. Please restart Syncplay.", # Seconds to respond # TODO: Translate into Russian # Client prompts "enter-to-exit-prompt" : u"Для выхода нажмите Enter\n", @@ -505,6 +523,8 @@ ru = { "invalid-seek-value" : u"Некорректное значение для перемотки", "invalid-offset-value" : u"Некорректное смещение", + "cannot-find-file-for-playlist-switch-error " : u"Could not find file {} for playlist switch!", # Filename # TODO: Translate into Russian + "cannot-add-duplicate-error" : u"Could not add second entry for '{}' to the playlist as no duplicates are allowed.", #Filename # TODO: Translate into Russian "switch-file-not-found-error" : u"Невозможно переключиться на файл '{0}'. Syncplay looks in the folder of the currently playing file and specified media directories.", # File not found # TODO: Translate last part into Russian "folder-search-timeout-error" : u"The search for media in '{}' was aborted as it took too long. This will occur if you select a folder with too many sub-folders in your list of media folders to search through. Until Syncplay is restarted only the directory of the currently open file will be checked.", #Folder # TODO: Translate into Russian @@ -602,7 +622,8 @@ ru = { "contact-label" : u"Есть идея, нашли ошибку или хотите оставить отзыв? Пишите на dev@syncplay.pl, в IRC канал #Syncplay на irc.freenode.net или задавайте вопросы через GitHub. Кроме того, заходите на http://syncplay.pl/ за инорфмацией, помощью и обновлениями!", - "joinroom-menu-label" : u"Зайти в комнату", + "joinroom-label" : u"Зайти в комнату", + "joinroom-menu-label" : u"Зайти в комнату {}", "seektime-menu-label" : u"Перемотать", "undoseek-menu-label" : u"Отменить перемотку", "play-menu-label" : u"Play", @@ -639,6 +660,21 @@ ru = { "userguide-menu-label" : u"&Руководство Пользователя", "update-menu-label" : u"Проверить &обновления", + "removefromplaylist-menu-label" : u"Remove from playlist", # TODO: Translate into Russian + "shuffleplaylist-menuu-label" : u"Shuffle playlist", # TODO: Translate into Russian + "undoplaylist-menu-label" : u"Undo last change to playlist", # TODO: Translate into Russian + "addfilestoplaylist-menu-label" : u"Add file(s) to bottom of playlist", # TODO: Translate into Russian + "addurlstoplaylist-menu-label" : u"Add URL(s) to bottom of playlist", # TODO: Translate into Russian + + "addusersfiletoplaylist-menu-label" : u"Add {} file to playlist", # item owner indicator # TODO: Translate into Russian + "addusersstreamstoplaylist-menu-label" : u"Add {} stream to playlist", # item owner indicator # TODO: Translate into Russian + "openusersstream-menu-label" : u"Open {} stream", # [username]'s # TODO: Translate into Russian + "openusersfile-menu-label" : u"Open {} file", # [username]'s # TODO: Translate into Russian + "item-is-yours-indicator" : u"your", # Goes with addusersfiletoplaylist/addusersstreamstoplaylist # TODO: Translate into Russian + "item-is-others-indicator" : u"{}'s", # username - goes with addusersfiletoplaylist/addusersstreamstoplaylist # TODO: Translate into Russian + + "playlist-instruction-item-message" : u"Drag file here to add it to the shared playlist.", # TODO: Translate into Russian + "setoffset-msgbox-label" : u"Установить смещение", "offsetinfo-msgbox-label" : u"Смещение (см. инструкцию на странице http://syncplay.pl/guide/):", @@ -704,7 +740,7 @@ ru = { "seektime-msgbox-label" : u"Перемотать к определенному моменту времени (указывать в секундах или мин:сек). Используйте +/-, чтобы перемотать вперед/назад относительно настоящего момента.", "ready-tooltip" : u"Показывает, готовы ли Вы к просмотру или нет.", "autoplay-tooltip" : u"Автоматическое воспроизведение, когда все пользователи с индикаторами готовности будут готовы и присутствует достаточное число пользователей.", - "switch-to-file-tooltip" : u"Double click to switch to {}", # Filename # TODO: Translate to Russian + "switch-to-file-tooltip" : u"Double click to switch to {}", # Filename # TODO: Translate into Russian # In-userlist notes (GUI) "differentsize-note" : u"Размер файла не совпадает!", @@ -877,6 +913,8 @@ de = { "invalid-seek-value" : u"Ungültige Zeitangabe", "invalid-offset-value" : u"Ungültiger Offset-Wert", + "cannot-find-file-for-playlist-switch-error " : u"Could not find file {} for playlist switch!", # Filename # TODO: Translate into German + "cannot-add-duplicate-error" : u"Could not add second entry for '{}' to the playlist as no duplicates are allowed.", #Filename # TODO: Translate into German "switch-file-not-found-error" : u"Konnte nicht zur Datei '{0}' wechseln. Syncplay sucht im Ordner der aktuellen Datei und angegebenen Medien-Verzeichnissen.", # File not found, folder it was not found in "folder-search-timeout-error" : u"Die Suche nach Mediendateien in '{}' wurde abgebrochen weil sie zu lange gedauert hat. Dies tritt auf, wenn ein zu durchsuchender Medienordner zu viele Unterordner hat. Syncplay wird bis zum Neustart nur noch das Verzeichnis der aktuellen Datei durchsuchen.", #Folder @@ -972,7 +1010,8 @@ de = { "contact-label" : u"Du hast eine Idee, einen Bug gefunden oder möchtest Feedback geben? Sende eine E-Mail an dev@syncplay.pl, chatte auf dem #Syncplay IRC-Kanal auf irc.freenode.net oder öffne eine Fehlermeldung auf GitHub. Außerdem findest du auf http://syncplay.pl/ weitere Informationen, Hilfestellungen und Updates.", - "joinroom-menu-label" : u"Raum beitreten", + "joinroom-label" : u"Raum beitreten", + "joinroom-menu-label" : u"Raum beitreten {}", "seektime-menu-label" : u"Spule zu Zeit", "undoseek-menu-label" : u"Rückgängig", "play-menu-label" : u"Wiedergabe", @@ -1009,6 +1048,21 @@ de = { "userguide-menu-label" : u"&Benutzerhandbuch öffnen", "update-menu-label" : u"auf &Aktualisierung prüfen", + "removefromplaylist-menu-label" : u"Remove from playlist", # TODO: Translate into German + "shuffleplaylist-menuu-label" : u"Shuffle playlist", # TODO: Translate into German + "undoplaylist-menu-label" : u"Undo last change to playlist", # TODO: Translate into German + "addfilestoplaylist-menu-label" : u"Add file(s) to bottom of playlist", # TODO: Translate into German + "addurlstoplaylist-menu-label" : u"Add URL(s) to bottom of playlist", # TODO: Translate into German + + "addusersfiletoplaylist-menu-label" : u"Add {} file to playlist", # item owner indicator # TODO: Translate into German + "addusersstreamstoplaylist-menu-label" : u"Add {} stream to playlist", # item owner indicator # TODO: Translate into German + "openusersstream-menu-label" : u"Open {} stream", # [username]'s # TODO: Translate into German + "openusersfile-menu-label" : u"Open {} file", # [username]'s # TODO: Translate into German + "item-is-yours-indicator" : u"your", # Goes with addusersfiletoplaylist/addusersstreamstoplaylist # TODO: Translate into German + "item-is-others-indicator" : u"{}'s", # username - goes with addusersfiletoplaylist/addusersstreamstoplaylist # TODO: Translate into German + + "playlist-instruction-item-message" : u"Drag file here to add it to the shared playlist.", # TODO: Translate into German + "setoffset-msgbox-label" : u"Offset einstellen", "offsetinfo-msgbox-label" : u"Offset (siehe http://syncplay.pl/guide/ für eine Anleitung [Englisch]):", diff --git a/syncplay/ui/gui.py b/syncplay/ui/gui.py index ea5eb41..8251d50 100644 --- a/syncplay/ui/gui.py +++ b/syncplay/ui/gui.py @@ -604,17 +604,17 @@ class MainWindow(QtGui.QMainWindow): firstFile = item.sibling(item.row(), 0).data() if self._syncplayClient.userlist.currentUser.file is None or firstFile <> self._syncplayClient.userlist.currentUser.file["name"]: if isURL(firstFile): - menu.addAction(QtGui.QPixmap(resourcespath + "world_go.png"), u"Open stream", lambda: self.openFile(firstFile)) + menu.addAction(QtGui.QPixmap(resourcespath + "world_go.png"), getMessage("openstreamurl-menu-label"), lambda: self.openFile(firstFile)) else: pathFound = MainWindow.FileSwitchManager.findFilepath(firstFile) if pathFound: - menu.addAction(QtGui.QPixmap(resourcespath + "film_go.png"), u"Open file", lambda: self.openFile(pathFound)) - menu.addAction(QtGui.QPixmap(resourcespath + "delete.png"), u"Remove from playlist", lambda: self.deleteSelectedPlaylistItems()) + menu.addAction(QtGui.QPixmap(resourcespath + "film_go.png"), getMessage("openmedia-menu-label"), lambda: self.openFile(pathFound)) + menu.addAction(QtGui.QPixmap(resourcespath + "delete.png"), getMessage("removefromplaylist-menu-label"), lambda: self.deleteSelectedPlaylistItems()) menu.addSeparator() - menu.addAction(QtGui.QPixmap(resourcespath + "arrow_switch.png"), u"Shuffle playlist", lambda: self.shufflePlaylist()) - menu.addAction(QtGui.QPixmap(resourcespath + "arrow_undo.png"), u"Undo last change to playlist", lambda: self.undoPlaylistChange()) - menu.addAction(QtGui.QPixmap(resourcespath + "film_add.png"), u"Add file(s) to bottom of playlist", lambda: self.OpenAddFilesToPlaylistDialog()) - menu.addAction(QtGui.QPixmap(resourcespath + "world_add.png"), u"Add URL(s) to bottom of playlist", lambda: self.OpenAddURIsToPlaylistDialog()) + menu.addAction(QtGui.QPixmap(resourcespath + "arrow_switch.png"), getMessage("shuffleplaylist-menuu-label"), lambda: self.shufflePlaylist()) + menu.addAction(QtGui.QPixmap(resourcespath + "arrow_undo.png"), getMessage("undoplaylist-menu-label"), lambda: self.undoPlaylistChange()) + menu.addAction(QtGui.QPixmap(resourcespath + "film_add.png"),getMessage("addfilestoplaylist-menu-label"), lambda: self.OpenAddFilesToPlaylistDialog()) + menu.addAction(QtGui.QPixmap(resourcespath + "world_add.png"), getMessage("addurlstoplaylist-menu-label"), lambda: self.OpenAddURIsToPlaylistDialog()) menu.exec_(self.playlist.viewport().mapToGlobal(position)) @@ -633,32 +633,32 @@ class MainWindow(QtGui.QMainWindow): menu = QtGui.QMenu() username = item.sibling(item.row(), 0).data() if username == self._syncplayClient.userlist.currentUser.username: - shortUsername = "your" # TODO: Use messages.py + shortUsername = getMessage("item-is-yours-indicator") elif len(username) < 15: - shortUsername = u"{}'s".format(username) + shortUsername = getMessage("item-is-others-indicator").format(username) else: - shortUsername = "{}'s...".format(username[0:12]) + shortUsername = u"{}...".format(getMessage("item-is-others-indicator").format(username[0:12])) # TODO: Enforce username limits in client and server filename = item.sibling(item.row(), 3).data() while item.parent().row() != -1: item = item.parent() roomToJoin = item.sibling(item.row(), 0).data() if roomToJoin <> self._syncplayClient.getRoom(): - menu.addAction(u"Join room {}".format(roomToJoin), lambda: self.joinRoom(roomToJoin)) + menu.addAction(getMessage("joinroom-menu-label").format(roomToJoin), lambda: self.joinRoom(roomToJoin)) elif username and filename and filename <> getMessage("nofile-note"): if self.config['sharedPlaylistEnabled'] and not self.isItemInPlaylist(filename): if isURL(filename): - menu.addAction(QtGui.QPixmap(resourcespath + "world_add.png"), u"Add {} stream to playlist".format(shortUsername), lambda: self.addStreamToPlaylist(filename)) + menu.addAction(QtGui.QPixmap(resourcespath + "world_add.png"),getMessage("addusersstreamstoplaylist-menu-label").format(shortUsername), lambda: self.addStreamToPlaylist(filename)) else: - menu.addAction(QtGui.QPixmap(resourcespath + "film_add.png"), u"Add {} file to playlist".format(shortUsername), lambda: self.addStreamToPlaylist(filename)) + menu.addAction(QtGui.QPixmap(resourcespath + "film_add.png"), getMessage("addusersfiletoplaylist-menu-label").format(shortUsername), lambda: self.addStreamToPlaylist(filename)) if self._syncplayClient.userlist.currentUser.file is None or filename <> self._syncplayClient.userlist.currentUser.file["name"]: if isURL(filename): - menu.addAction(QtGui.QPixmap(resourcespath + "world_go.png"), u"Open {} stream".format(shortUsername), lambda: self.openFile(filename)) + menu.addAction(QtGui.QPixmap(resourcespath + "world_go.png"), getMessage("openusersstream-menu-label").format(shortUsername), lambda: self.openFile(filename)) else: pathFound = MainWindow.FileSwitchManager.findFilepath(filename) if pathFound: - menu.addAction(QtGui.QPixmap(resourcespath + "film_go.png"), u"Open {} file".format(shortUsername), lambda: self.openFile(pathFound)) + menu.addAction(QtGui.QPixmap(resourcespath + "film_go.png"), getMessage("openusersfile-menu-label").format(shortUsername), lambda: self.openFile(pathFound)) else: return menu.exec_(self.listTreeView.viewport().mapToGlobal(position)) @@ -707,7 +707,7 @@ class MainWindow(QtGui.QMainWindow): if pathFound: self._syncplayClient._player.openFile(pathFound) else: - self._syncplayClient.ui.showErrorMessage(u"Could not find file {} for playlist switch!".format(filename)) + self._syncplayClient.ui.showErrorMessage(getMessage("cannot-find-file-for-playlist-switch-error").format(filename)) def roomClicked(self, item): username = item.sibling(item.row(), 0).data() @@ -986,7 +986,7 @@ class MainWindow(QtGui.QMainWindow): playlistItems = [] for playlistItem in xrange(self.playlist.count()): playlistItemText = self.playlist.item(playlistItem).text() - if playlistItemText <> u"Drag file here to add it to the shared playlist.": + if playlistItemText <> getMessage("playlist-instruction-item-message"): playlistItems.append(playlistItemText) return playlistItems @@ -1054,7 +1054,7 @@ class MainWindow(QtGui.QMainWindow): window.roomInput = QtGui.QLineEdit() window.roomInput.returnPressed.connect(self.joinRoom) window.roomButton = QtGui.QPushButton(QtGui.QIcon(self.resourcespath + 'door_in.png'), - getMessage("joinroom-menu-label")) + getMessage("joinroom-label")) window.roomButton.pressed.connect(self.joinRoom) window.roomLayout = QtGui.QHBoxLayout() window.roomFrame = QtGui.QFrame() @@ -1085,7 +1085,7 @@ class MainWindow(QtGui.QMainWindow): self.addPlaybackLayout(window) - window.playlistGroup = self.PlaylistGroupBox(u"Enable shared playlists") + window.playlistGroup = self.PlaylistGroupBox(getMessage("sharedplaylistenabled-label")) window.playlistGroup.setCheckable(True) window.playlistGroup.toggled.connect(self.changePlaylistEnabledState) window.playlistLayout = QtGui.QHBoxLayout() @@ -1108,7 +1108,7 @@ class MainWindow(QtGui.QMainWindow): self.playlistUpdateTimer.start(0.1, True) noteFont = QtGui.QFont() noteFont.setItalic(True) - playlistItem = QtGui.QListWidgetItem(u"Drag file here to add it to the shared playlist.") + playlistItem = QtGui.QListWidgetItem(getMessage("playlist-instruction-item-message")) playlistItem.setFont(noteFont) window.playlist.addItem(playlistItem) playlistItem.setFont(noteFont) @@ -1448,7 +1448,7 @@ class MainWindow(QtGui.QMainWindow): def noPlaylistDuplicates(self, filename): if self.isItemInPlaylist(filename): - self.showErrorMessage(u"Could not add second entry for '{}' to the playlist as no duplicates are allowed.".format(filename)) + self.showErrorMessage(getMessage("cannot-add-duplicate-error").format(filename)) return False else: return True From 2bc3e66d288c6e4507aacbecadb26ce7f4fe06fc Mon Sep 17 00:00:00 2001 From: Et0h Date: Tue, 19 Jan 2016 15:28:55 +0000 Subject: [PATCH 059/134] Don't change playlist index if there is no playlist --- syncplay/client.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/syncplay/client.py b/syncplay/client.py index 5cd7e22..534a22a 100644 --- a/syncplay/client.py +++ b/syncplay/client.py @@ -474,6 +474,8 @@ class SyncplayClient(object): return None def changeToPlaylistIndex(self, index, username = None): + if not self._playlist or len(self._playlist) == 0: + return path = None if index is None: return From 0e504f77aca2b34f236ed930adfdf30759e07f9c Mon Sep 17 00:00:00 2001 From: Et0h Date: Tue, 19 Jan 2016 15:50:26 +0000 Subject: [PATCH 060/134] Upver to release 22 --- syncplay/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/syncplay/__init__.py b/syncplay/__init__.py index 1f2bc4f..02f66d1 100644 --- a/syncplay/__init__.py +++ b/syncplay/__init__.py @@ -1,4 +1,4 @@ version = '1.4.0' milestone = 'Yoitsu' -release_number = '21' +release_number = '22' projectURL = 'http://syncplay.pl/' From 44f1b77977863a53267169aa273d7d2f1457689f Mon Sep 17 00:00:00 2001 From: Et0h Date: Wed, 20 Jan 2016 20:44:25 +0000 Subject: [PATCH 061/134] Enable shared playlists by default --- syncplay/ui/ConfigurationGetter.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/syncplay/ui/ConfigurationGetter.py b/syncplay/ui/ConfigurationGetter.py index 35611ef..dd42523 100755 --- a/syncplay/ui/ConfigurationGetter.py +++ b/syncplay/ui/ConfigurationGetter.py @@ -34,7 +34,7 @@ class ConfigurationGetter(object): "playerPath": None, "perPlayerArguments": None, "mediaSearchDirectories": None, - "sharedPlaylistEnabled": False, + "sharedPlaylistEnabled": True, "file": None, "playerArgs": [], "playerClass": None, From 18be4a34a1cef2801e4cd0752ff73c391cf7788f Mon Sep 17 00:00:00 2001 From: Et0h Date: Thu, 21 Jan 2016 22:21:40 +0000 Subject: [PATCH 062/134] Hopefully fix playlist index random switch loop bug --- syncplay/client.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/syncplay/client.py b/syncplay/client.py index 534a22a..4a1d427 100644 --- a/syncplay/client.py +++ b/syncplay/client.py @@ -484,7 +484,8 @@ class SyncplayClient(object): try: filename = self._playlist[index] self.ui.setPlaylistIndexFilename(filename) - self._playlistIndex = index + if not self._config['sharedPlaylistEnabled']: + self._playlistIndex = index if username is not None and self.userlist.currentUser.file and filename == self.userlist.currentUser.file['name']: return except IndexError: @@ -493,13 +494,11 @@ class SyncplayClient(object): if self._player is None: self.__playerReady.addCallback(lambda x: self.changeToPlaylistIndex(index, username)) return - + self._playlistIndex = index if username is None and self._protocol and self._protocol.logged and self._config["sharedPlaylistEnabled"]: - self._playlistIndex = index self._protocol.setPlaylistIndex(index) else: self.ui.showMessage(u"{} changed the playlist selection".format(username)) - self._playlistIndex = index self.switchToNewPlaylistIndex(index) From 85812e29f3a15e52da423a1ad5c46b1403d0987f Mon Sep 17 00:00:00 2001 From: Et0h Date: Sat, 23 Jan 2016 13:11:49 +0000 Subject: [PATCH 063/134] Move playlist next file thresholds to constants.py --- syncplay/client.py | 2 +- syncplay/constants.py | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/syncplay/client.py b/syncplay/client.py index 4a1d427..d439b7b 100644 --- a/syncplay/client.py +++ b/syncplay/client.py @@ -210,7 +210,7 @@ class SyncplayClient(object): self._playerPosition = position self._playerPaused = paused currentLength = self.userlist.currentUser.file["duration"] if self.userlist.currentUser.file else 0 - if pauseChange and paused and currentLength > 10 and abs(position - currentLength ) < 5: + if pauseChange and paused and currentLength > constants.PLAYLIST_LOAD_NEXT_FILE_MINIMUM_LENGTH and abs(position - currentLength ) < constants.PLAYLIST_LOAD_NEXT_FILE_TIME_FROM_END_THRESHOLD: self.loadNextFileInPlaylist() elif pauseChange and utils.meetsMinVersion(self.serverVersion, constants.USER_READY_MIN_VERSION): pauseChange = self._toggleReady(pauseChange, paused) diff --git a/syncplay/constants.py b/syncplay/constants.py index b3eba57..869d9c9 100644 --- a/syncplay/constants.py +++ b/syncplay/constants.py @@ -17,6 +17,8 @@ SHOW_TOOLTIPS = True WARN_ABOUT_MISSING_STRINGS = False # (If debug mode is enabled) FALLBACK_INITIAL_LANGUAGE = "en" FALLBACK_PUBLIC_SYNCPLAY_SERVERS = [[u'syncplay.pl:8995 (France)', u'syncplay.pl:8995'],[u'syncplay.pl:8996 (France)', u'syncplay.pl:8996'],[u'syncplay.pl:8997 (France)', u'syncplay.pl:8997'],[u'syncplay.pl:8998 (France)', u'syncplay.pl:8998'],[u'syncplay.pl:8999 (France)', u'syncplay.pl:8999']] +PLAYLIST_LOAD_NEXT_FILE_MINIMUM_LENGTH = 10 # Seconds +PLAYLIST_LOAD_NEXT_FILE_TIME_FROM_END_THRESHOLD = 5 # Seconds (only triggered if file is paused, e.g. due to EOF) #Overriden by config SHOW_OSD = True # Sends Syncplay messages to media player OSD From 037e5aa21efef48103ca1fea379691a7a692208b Mon Sep 17 00:00:00 2001 From: Et0h Date: Sat, 23 Jan 2016 13:42:44 +0000 Subject: [PATCH 064/134] Refactor updateUndoPlaylistBuffer --- syncplay/client.py | 22 +++++++++++++++++----- 1 file changed, 17 insertions(+), 5 deletions(-) diff --git a/syncplay/client.py b/syncplay/client.py index d439b7b..391bb6d 100644 --- a/syncplay/client.py +++ b/syncplay/client.py @@ -531,11 +531,7 @@ class SyncplayClient(object): except: newIndex = 0 - if self._previousPlaylistRoom <> self.userlist.currentUser.room: - self._previousPlaylist = None - self._previousPlaylistRoom = self.userlist.currentUser.room - elif self._previousPlaylist <> self._playlist and self._playlist <> files: - self._previousPlaylist = self._playlist + self.updateUndoPlaylistBuffer(currentPlaylist=self._playlist, newPlaylist=files) self._playlist = files if username is None and self._protocol and self._protocol.logged: @@ -547,6 +543,22 @@ class SyncplayClient(object): self.changeToPlaylistIndex(newIndex, username) self.ui.showMessage(u"{} updated the playlist".format(username)) + def updateUndoPlaylistBuffer(self, currentPlaylist, newPlaylist): + if self.playlistBufferIsFromOldRoom(): + self.movePlaylistBufferToNewRoom() + elif self.playlistBufferNeedsUpdating(currentPlaylist, newPlaylist): + self._previousPlaylist = currentPlaylist + + def playlistBufferIsFromOldRoom(self): + return self._previousPlaylistRoom <> self.userlist.currentUser.room + + def movePlaylistBufferToNewRoom(self): + self._previousPlaylist = None + self._previousPlaylistRoom = self.userlist.currentUser.room + + def playlistBufferNeedsUpdating(self, currentPlaylist, newPlaylist): + return self._previousPlaylist <> currentPlaylist and currentPlaylist <> newPlaylist + @needsSharedPlaylistsEnabled def undoPlaylistChange(self): if self._previousPlaylist is not None and self._playlist <> self._previousPlaylist: From fc70c917bc9a311f8795171d3b837b5dcce4d509 Mon Sep 17 00:00:00 2001 From: Et0h Date: Sat, 23 Jan 2016 14:36:01 +0000 Subject: [PATCH 065/134] Refactor to make _UndoPlaylistManager --- syncplay/client.py | 59 ++++++++++++++++++++++++++-------------------- 1 file changed, 33 insertions(+), 26 deletions(-) diff --git a/syncplay/client.py b/syncplay/client.py index 391bb6d..ae08924 100644 --- a/syncplay/client.py +++ b/syncplay/client.py @@ -115,12 +115,11 @@ class SyncplayClient(object): self.autoplayTimeLeft = constants.AUTOPLAY_DELAY self._playlist = [] - self._previousPlaylist = None - self._previousPlaylistRoom = None self._playlistIndex = None self.__playerReady = defer.Deferred() self._warnings = self._WarningManager(self._player, self.userlist, self.ui, self) + self._undoPlaylist = self._UndoPlaylistManager() if constants.LIST_RELATIVE_CONFIGS and self._config.has_key('loadedRelativePaths') and self._config['loadedRelativePaths']: paths = "; ".join(self._config['loadedRelativePaths']) self.ui.showMessage(getMessage("relative-config-notification").format(paths), noPlayer=True, noTimestamp=True) @@ -531,7 +530,7 @@ class SyncplayClient(object): except: newIndex = 0 - self.updateUndoPlaylistBuffer(currentPlaylist=self._playlist, newPlaylist=files) + self._undoPlaylist.updateUndoPlaylistBuffer(currentPlaylist=self._playlist, newPlaylist=files, newRoom=self.userlist.currentUser.room) self._playlist = files if username is None and self._protocol and self._protocol.logged: @@ -543,40 +542,48 @@ class SyncplayClient(object): self.changeToPlaylistIndex(newIndex, username) self.ui.showMessage(u"{} updated the playlist".format(username)) - def updateUndoPlaylistBuffer(self, currentPlaylist, newPlaylist): - if self.playlistBufferIsFromOldRoom(): - self.movePlaylistBufferToNewRoom() - elif self.playlistBufferNeedsUpdating(currentPlaylist, newPlaylist): - self._previousPlaylist = currentPlaylist - - def playlistBufferIsFromOldRoom(self): - return self._previousPlaylistRoom <> self.userlist.currentUser.room - - def movePlaylistBufferToNewRoom(self): - self._previousPlaylist = None - self._previousPlaylistRoom = self.userlist.currentUser.room - - def playlistBufferNeedsUpdating(self, currentPlaylist, newPlaylist): - return self._previousPlaylist <> currentPlaylist and currentPlaylist <> newPlaylist - @needsSharedPlaylistsEnabled def undoPlaylistChange(self): - if self._previousPlaylist is not None and self._playlist <> self._previousPlaylist: - undidPlaylist = self._playlist - self.ui.setPlaylist(self._previousPlaylist) - self.changePlaylist(self._previousPlaylist) - self._previousPlaylist = undidPlaylist + if self._undoPlaylist.canUndoPlaylist(self._playlist): + newPlaylist = self._undoPlaylist.getPreviousPlaylist() + self.ui.setPlaylist(newPlaylist) + self.changePlaylist(newPlaylist) @needsSharedPlaylistsEnabled def shufflePlaylist(self): if self._playlist and len(self._playlist) > 0: - oldPlaylist = self._playlist random.seed() shuffledPlaylist = deepcopy(self._playlist) random.shuffle(shuffledPlaylist) self.ui.setPlaylist(shuffledPlaylist) self.changePlaylist(shuffledPlaylist) - self._previousPlaylist = oldPlaylist + + class _UndoPlaylistManager(): + def __init__(self): + self._previousPlaylist = None + self._previousPlaylistRoom = None + + def updateUndoPlaylistBuffer(self, currentPlaylist, newPlaylist, newRoom): + if self.playlistBufferIsFromOldRoom(newRoom): + self.movePlaylistBufferToNewRoom(newRoom) + elif self.playlistBufferNeedsUpdating(currentPlaylist, newPlaylist): + self._previousPlaylist = currentPlaylist + + def playlistBufferIsFromOldRoom(self, newRoom): + return self._previousPlaylistRoom <> newRoom + + def movePlaylistBufferToNewRoom(self, currentRoom): + self._previousPlaylist = None + self._previousPlaylistRoom = currentRoom + + def playlistBufferNeedsUpdating(self, currentPlaylist, newPlaylist): + return self._previousPlaylist <> currentPlaylist and currentPlaylist <> newPlaylist + + def canUndoPlaylist(self, currentPlaylist): + return self._previousPlaylist is not None and currentPlaylist <> self._previousPlaylist + + def getPreviousPlaylist(self): + return self._previousPlaylist def __executePrivacySettings(self, filename, size): if self._config['filenamePrivacyMode'] == PRIVACY_SENDHASHED_MODE: From b42f37c999f952ef0eeab0e02cdb4bce0694f523 Mon Sep 17 00:00:00 2001 From: Et0h Date: Sat, 23 Jan 2016 14:39:49 +0000 Subject: [PATCH 066/134] Remove redundant random seeding requests (AKA all of them, as random self-seeds) --- syncplay/client.py | 1 - syncplay/players/vlc.py | 1 - 2 files changed, 2 deletions(-) diff --git a/syncplay/client.py b/syncplay/client.py index ae08924..bad18f3 100644 --- a/syncplay/client.py +++ b/syncplay/client.py @@ -620,7 +620,6 @@ class SyncplayClient(object): if username and username <> "": self.userlist.currentUser.username = username else: - random.seed() random_number = random.randrange(1000, 9999) self.userlist.currentUser.username = "Anonymous" + str(random_number) # Not localised as this would give away locale diff --git a/syncplay/players/vlc.py b/syncplay/players/vlc.py index fb62b33..bdeba7d 100644 --- a/syncplay/players/vlc.py +++ b/syncplay/players/vlc.py @@ -22,7 +22,6 @@ class VlcPlayer(BasePlayer): SLAVE_ARGS = constants.VLC_SLAVE_ARGS if not sys.platform.startswith('darwin'): SLAVE_ARGS.extend(constants.VLC_SLAVE_NONOSX_ARGS) - random.seed() vlcport = random.randrange(constants.VLC_MIN_PORT, constants.VLC_MAX_PORT) if (constants.VLC_MIN_PORT < constants.VLC_MAX_PORT) else constants.VLC_MIN_PORT def __init__(self, client, playerPath, filePath, args): From 383e395658507447906cf3bee90ecfcfac2d104f Mon Sep 17 00:00:00 2001 From: Et0h Date: Sat, 23 Jan 2016 14:52:00 +0000 Subject: [PATCH 067/134] Refactor to avoid super wide lines --- syncplay/client.py | 34 ++++++++++++++++++++++++---------- 1 file changed, 24 insertions(+), 10 deletions(-) diff --git a/syncplay/client.py b/syncplay/client.py index bad18f3..5f81290 100644 --- a/syncplay/client.py +++ b/syncplay/client.py @@ -209,7 +209,8 @@ class SyncplayClient(object): self._playerPosition = position self._playerPaused = paused currentLength = self.userlist.currentUser.file["duration"] if self.userlist.currentUser.file else 0 - if pauseChange and paused and currentLength > constants.PLAYLIST_LOAD_NEXT_FILE_MINIMUM_LENGTH and abs(position - currentLength ) < constants.PLAYLIST_LOAD_NEXT_FILE_TIME_FROM_END_THRESHOLD: + if pauseChange and paused and currentLength > constants.PLAYLIST_LOAD_NEXT_FILE_MINIMUM_LENGTH\ + and abs(position - currentLength ) < constants.PLAYLIST_LOAD_NEXT_FILE_TIME_FROM_END_THRESHOLD: self.loadNextFileInPlaylist() elif pauseChange and utils.meetsMinVersion(self.serverVersion, constants.USER_READY_MIN_VERSION): pauseChange = self._toggleReady(pauseChange, paused) @@ -355,7 +356,8 @@ class SyncplayClient(object): self.behindFirstDetected = time.time() else: durationBehind = time.time() - self.behindFirstDetected - if (durationBehind > (self._config['fastforwardThreshold']-constants.FASTFORWARD_BEHIND_THRESHOLD)) and (diff < (self._config['fastforwardThreshold'] * -1)): + if (durationBehind > (self._config['fastforwardThreshold']-constants.FASTFORWARD_BEHIND_THRESHOLD))\ + and (diff < (self._config['fastforwardThreshold'] * -1)): madeChangeOnPlayer = self._fastforwardPlayerDueToTimeDifference(position, setBy) self.behindFirstDetected = time.time() + constants.FASTFORWARD_RESET_THRESHOLD else: @@ -760,13 +762,16 @@ class SyncplayClient(object): return True elif unpauseAction == constants.UNPAUSE_IFOTHERSREADY_MODE and self.userlist.areAllOtherUsersInRoomReady(): return True - elif unpauseAction == constants.UNPAUSE_IFMINUSERSREADY_MODE and self.userlist.areAllOtherUsersInRoomReady() and self.autoPlayThreshold and self.userlist.usersInRoomCount() >= self.autoPlayThreshold: + elif unpauseAction == constants.UNPAUSE_IFMINUSERSREADY_MODE and self.userlist.areAllOtherUsersInRoomReady()\ + and self.autoPlayThreshold and self.userlist.usersInRoomCount() >= self.autoPlayThreshold: return True else: return False def autoplayConditionsMet(self): - return self._playerPaused and self.autoPlay and self.userlist.currentUser.canControl() and self.userlist.isReadinessSupported() and self.userlist.areAllUsersInRoomReady(requireSameFilenames=self._config["autoplayRequireSameFilenames"]) and self.autoPlayThreshold and self.userlist.usersInRoomCount() >= self.autoPlayThreshold + return self._playerPaused and self.autoPlay and self.userlist.currentUser.canControl() and self.userlist.isReadinessSupported()\ + and self.userlist.areAllUsersInRoomReady(requireSameFilenames=self._config["autoplayRequireSameFilenames"])\ + and self.autoPlayThreshold and self.userlist.usersInRoomCount() >= self.autoPlayThreshold def autoplayTimerIsRunning(self): return self.autoplayTimer.running @@ -785,7 +790,9 @@ class SyncplayClient(object): if not self.autoplayConditionsMet(): self.stopAutoplayCountdown() return - countdownMessage = u"{}{}{}".format(getMessage("all-users-ready").format(self.userlist.readyUserCount()),self._player.osdMessageSeparator, getMessage("autoplaying-notification").format(int(self.autoplayTimeLeft))) + allReadyMessage = getMessage("all-users-ready").format(self.userlist.readyUserCount()) + autoplayingMessage = getMessage("autoplaying-notification").format(int(self.autoplayTimeLeft)) + countdownMessage = u"{}{}{}".format(allReadyMessage,self._player.osdMessageSeparator, autoplayingMessage) self.ui.showOSDMessage(countdownMessage, 1, secondaryOSD=True) if self.autoplayTimeLeft <= 0: self.setPaused(False) @@ -876,9 +883,11 @@ class SyncplayClient(object): response = json.loads(response) publicServers = None if response["public-servers"]: - publicServers = response["public-servers"].replace("”","'").replace(":’","'").replace("’","'").replace("′","'").replace("\n","").replace("\r","") + publicServers = response["public-servers"].\ + replace("”","'").replace(":’","'").replace("’","'").replace("′","'").replace("\n","").replace("\r","") publicServers = ast.literal_eval(publicServers) - return response["version-status"], response["version-message"] if response.has_key("version-message") else None, response["version-url"] if response.has_key("version-url") else None, publicServers + return response["version-status"], response["version-message"] if response.has_key("version-message")\ + else None, response["version-url"] if response.has_key("version-url") else None, publicServers except: return "failed", getMessage("update-check-failed-notification").format(syncplay.version), constants.SYNCPLAY_DOWNLOAD_URL, None @@ -963,9 +972,11 @@ class SyncplayClient(object): fileDifferencesMessage = getMessage("room-file-differences").format(fileDifferencesForRoom) if self._userlist.currentUser.canControl() and self._userlist.isReadinessSupported(): if self._userlist.areAllUsersInRoomReady(): - osdMessage = u"{}{}{}".format(fileDifferencesMessage, self._client._player.osdMessageSeparator, getMessage("all-users-ready").format(self._userlist.readyUserCount())) + allReadyMessage = getMessage("all-users-ready").format(self._userlist.readyUserCount()) + osdMessage = u"{}{}{}".format(fileDifferencesMessage, self._client._player.osdMessageSeparator, allReadyMessage) else: - osdMessage = u"{}{}{}".format(fileDifferencesMessage, self._client._player.osdMessageSeparator, getMessage("not-all-ready").format(self._userlist.usersInRoomNotReady())) + notAllReadyMessage = getMessage("not-all-ready").format(self._userlist.usersInRoomNotReady()) + osdMessage = u"{}{}{}".format(fileDifferencesMessage, self._client._player.osdMessageSeparator, notAllReadyMessage) else: osdMessage = fileDifferencesMessage elif self._userlist.isReadinessSupported(): @@ -1204,7 +1215,10 @@ class SyncplayUserlist(object): if user.room == self.currentUser.room: if user.isReadyWithFile() == False: return False - elif requireSameFilenames and (self.currentUser.file is None or user.file is None or not utils.sameFilename(self.currentUser.file['name'], user.file['name'])): + elif requireSameFilenames and\ + (self.currentUser.file is None + or user.file is None + or not utils.sameFilename(self.currentUser.file['name'], user.file['name'])): return False return True From 37c6e91641f71415a0dee9f17cd917dcfa643a74 Mon Sep 17 00:00:00 2001 From: Et0h Date: Sat, 23 Jan 2016 15:14:23 +0000 Subject: [PATCH 068/134] Remove old TOOD message - we disallow duplicates so don't need GUIDs --- syncplay/client.py | 1 - 1 file changed, 1 deletion(-) diff --git a/syncplay/client.py b/syncplay/client.py index 5f81290..9125095 100644 --- a/syncplay/client.py +++ b/syncplay/client.py @@ -181,7 +181,6 @@ class SyncplayClient(object): @needsSharedPlaylistsEnabled def loadNextFileInPlaylist(self): - # TODO: Fix for GUIDs & add path checks (and make more of code re-use?) if self._playlistIndex is None or len(self._playlist) <= self._playlistIndex+1: return filename = self._playlist[self._playlistIndex+1] From 8f0a22ee3b24b743e5608141f848146aabf7493f Mon Sep 17 00:00:00 2001 From: Et0h Date: Sat, 23 Jan 2016 15:25:37 +0000 Subject: [PATCH 069/134] Move more playlist messages to messages.py --- syncplay/client.py | 12 ++++++------ syncplay/messages.py | 18 +++++++++++++++--- 2 files changed, 21 insertions(+), 9 deletions(-) diff --git a/syncplay/client.py b/syncplay/client.py index 9125095..d5beae4 100644 --- a/syncplay/client.py +++ b/syncplay/client.py @@ -191,14 +191,14 @@ class SyncplayClient(object): if filename.startswith(URI): self._player.openFile(filename, resetPosition=True) return - self.ui.showErrorMessage(u"Could not load {} because it is not known as a safe path.".format(filename)) + self.ui.showErrorMessage(getMessage("cannot-add-unsafe-path-error").format(filename)) return else: path = self.findFilenameInDirectories(filename) if path: self._player.openFile(path, resetPosition=True) else: - self.ui.showErrorMessage(u"Could not find file {} for playlist switch!".format(filename)) + self.ui.showErrorMessage(getMessage("cannot-find-file-for-playlist-switch-error").format(filename)) return # TODO: Find Path properly @@ -498,7 +498,7 @@ class SyncplayClient(object): if username is None and self._protocol and self._protocol.logged and self._config["sharedPlaylistEnabled"]: self._protocol.setPlaylistIndex(index) else: - self.ui.showMessage(u"{} changed the playlist selection".format(username)) + self.ui.showMessage(getMessage("playlist-selection-changed-notification").format(username)) self.switchToNewPlaylistIndex(index) @@ -511,7 +511,7 @@ class SyncplayClient(object): if filename.startswith(URI): self._player.openFile(filename) return - self.ui.showErrorMessage(u"Could not load {} because it is not known as a safe path.".format(filename)) + self.ui.showErrorMessage(getMessage("cannot-add-unsafe-path-error").format(filename)) return else: path = self.findFilenameInDirectories(filename) @@ -519,7 +519,7 @@ class SyncplayClient(object): if path: self._player.openFile(path) else: - self.ui.showErrorMessage(u"Could not find file {} for playlist switch!".format(filename)) + self.ui.showErrorMessage(getMessage("cannot-find-file-for-playlist-switch-error").format(filename)) return except IndexError: self.ui.showDebugMessage("Could not change playlist index due to IndexError") @@ -541,7 +541,7 @@ class SyncplayClient(object): else: self.ui.setPlaylist(self._playlist) self.changeToPlaylistIndex(newIndex, username) - self.ui.showMessage(u"{} updated the playlist".format(username)) + self.ui.showMessage(getMessage("playlist-contents-changed-notification").format(username)) @needsSharedPlaylistsEnabled def undoPlaylistChange(self): diff --git a/syncplay/messages.py b/syncplay/messages.py index 07591ca..51211cf 100755 --- a/syncplay/messages.py +++ b/syncplay/messages.py @@ -62,6 +62,9 @@ en = { "userlist-file-notification" : "File", "controller-userlist-userflag" : "Operator", "ready-userlist-userflag" : "Ready", + + "playlist-selection-changed-notification" : u"{} changed the playlist selection", # Username + "playlist-contents-changed-notification" : u"{} updated the playlist", # Username "update-check-failed-notification" : u"Could not automatically check whether Syncplay {} is up to date. Want to visit http://syncplay.pl/ to manually check for updates?", #Syncplay version "syncplay-uptodate-notification" : u"Syncplay is up to date", @@ -134,8 +137,9 @@ en = { "invalid-seek-value" : u"Invalid seek value", "invalid-offset-value" : u"Invalid offset value", - "cannot-find-file-for-playlist-switch-error " : u"Could not find file {} for playlist switch!", # Filename + "cannot-find-file-for-playlist-switch-error" : u"Could not find file {} for playlist switch!", # Filename "cannot-add-duplicate-error" : u"Could not add second entry for '{}' to the playlist as no duplicates are allowed.", #Filename + "cannot-add-unsafe-path-error" : u"Could not load {} because it is not known as a safe path.", # Filename "switch-file-not-found-error" : u"Could not switch to file '{0}'. Syncplay looks in the folder of the currently playing file and specified media directories.", # File not found "folder-search-timeout-error" : u"The search for media in '{}' was aborted as it took too long. This will occur if you select a folder with too many sub-folders in your list of media folders to search through. Until Syncplay is restarted only the directory of the currently open file will be checked.", #Folder @@ -451,6 +455,9 @@ ru = { "userlist-file-notification" : u"Файл", "controller-userlist-userflag" : u"Оператор", "ready-userlist-userflag" : u"Готов", + + "playlist-selection-changed-notification" : u"{} changed the playlist selection", # Username # TODO: Translate into Russian + "playlist-contents-changed-notification" : u"{} updated the playlist", # Username # TODO: Translate into Russian "update-check-failed-notification" : u"Невозможно автоматически проверить, что версия Syncplay {} все еще актуальна. Хотите зайти на http://syncplay.pl/ и вручную проверить наличие обновлений?", "syncplay-uptodate-notification" : u"Syncplay обновлен", @@ -523,8 +530,9 @@ ru = { "invalid-seek-value" : u"Некорректное значение для перемотки", "invalid-offset-value" : u"Некорректное смещение", - "cannot-find-file-for-playlist-switch-error " : u"Could not find file {} for playlist switch!", # Filename # TODO: Translate into Russian + "cannot-find-file-for-playlist-switch-error" : u"Could not find file {} for playlist switch!", # Filename # TODO: Translate into Russian "cannot-add-duplicate-error" : u"Could not add second entry for '{}' to the playlist as no duplicates are allowed.", #Filename # TODO: Translate into Russian + "cannot-add-unsafe-path-error" : u"Could not load {} because it is not known as a safe path.", # Filename # TODO: Translate to Russian "switch-file-not-found-error" : u"Невозможно переключиться на файл '{0}'. Syncplay looks in the folder of the currently playing file and specified media directories.", # File not found # TODO: Translate last part into Russian "folder-search-timeout-error" : u"The search for media in '{}' was aborted as it took too long. This will occur if you select a folder with too many sub-folders in your list of media folders to search through. Until Syncplay is restarted only the directory of the currently open file will be checked.", #Folder # TODO: Translate into Russian @@ -841,6 +849,9 @@ de = { "userlist-file-notification" : u"Datei", "controller-userlist-userflag" : u"Raumleiter", "ready-userlist-userflag" : u"Bereit", + + "playlist-selection-changed-notification" : u"{} changed the playlist selection", # Username # TODO: Translate into German + "playlist-contents-changed-notification" : u"{} updated the playlist", # Username # TODO: Translate into German "update-check-failed-notification" : u"Konnte nicht automatisch prüfen, ob Syncplay {} aktuell ist. Soll http://syncplay.pl/ geöffnet werden, um manuell nach Updates zu suchen?", #Syncplay version "syncplay-uptodate-notification" : u"Syncplay ist aktuell", @@ -913,8 +924,9 @@ de = { "invalid-seek-value" : u"Ungültige Zeitangabe", "invalid-offset-value" : u"Ungültiger Offset-Wert", - "cannot-find-file-for-playlist-switch-error " : u"Could not find file {} for playlist switch!", # Filename # TODO: Translate into German + "cannot-find-file-for-playlist-switch-error" : u"Could not find file {} for playlist switch!", # Filename # TODO: Translate into German "cannot-add-duplicate-error" : u"Could not add second entry for '{}' to the playlist as no duplicates are allowed.", #Filename # TODO: Translate into German + "cannot-add-unsafe-path-error" : u"Could not load {} because it is not known as a safe path.", # Filename # TODO: Translate to German "switch-file-not-found-error" : u"Konnte nicht zur Datei '{0}' wechseln. Syncplay sucht im Ordner der aktuellen Datei und angegebenen Medien-Verzeichnissen.", # File not found, folder it was not found in "folder-search-timeout-error" : u"Die Suche nach Mediendateien in '{}' wurde abgebrochen weil sie zu lange gedauert hat. Dies tritt auf, wenn ein zu durchsuchender Medienordner zu viele Unterordner hat. Syncplay wird bis zum Neustart nur noch das Verzeichnis der aktuellen Datei durchsuchen.", #Folder From b7fff7e32ca36447f279a7f7ed9daba458926fdf Mon Sep 17 00:00:00 2001 From: Et0h Date: Sat, 23 Jan 2016 16:28:54 +0000 Subject: [PATCH 070/134] Always loop at tne of playlist + At EOF go to next playlist index, not next filename --- syncplay/client.py | 59 ++++++++++++++++++++++++++++--------------- syncplay/constants.py | 1 + 2 files changed, 39 insertions(+), 21 deletions(-) diff --git a/syncplay/client.py b/syncplay/client.py index d5beae4..2154c57 100644 --- a/syncplay/client.py +++ b/syncplay/client.py @@ -181,26 +181,43 @@ class SyncplayClient(object): @needsSharedPlaylistsEnabled def loadNextFileInPlaylist(self): - if self._playlistIndex is None or len(self._playlist) <= self._playlistIndex+1: - return - filename = self._playlist[self._playlistIndex+1] - if filename == "": - filename = self._playlist[0] - if utils.isURL(filename): - for URI in constants.SAFE_URIS: - if filename.startswith(URI): - self._player.openFile(filename, resetPosition=True) - return - self.ui.showErrorMessage(getMessage("cannot-add-unsafe-path-error").format(filename)) + if self._notPlayingCurrentIndex(): return + if self._thereIsNextPlaylistIndex(): + self.switchToNewPlaylistIndex(self._nextPlaylistIndex(), resetPosition=True) else: - path = self.findFilenameInDirectories(filename) - if path: - self._player.openFile(path, resetPosition=True) - else: - self.ui.showErrorMessage(getMessage("cannot-find-file-for-playlist-switch-error").format(filename)) - return - # TODO: Find Path properly + self.rewindFile() + + def _notPlayingCurrentIndex(self): + if self._playlistIndex is None or self._playlist is None or len(self._playlist) <= self._playlistIndex: + self.ui.showDebugMessage(u"Not playing current index - Index none or length issue") + return True + currentPlaylistFilename = self._playlist[self._playlistIndex] + if self.userlist.currentUser.file and currentPlaylistFilename == self.userlist.currentUser.file['name']: + return False + else: + self.ui.showDebugMessage(u"Not playing current index - Filename mismatch or no file") + return True + + def _thereIsNextPlaylistIndex(self): + if self._playlistIndex is None: + return False + elif len(self._playlist) == 1: + return False + else: + return True + + def _nextPlaylistIndex(self): + if self.playlistIsAtEnd(): + return 0 + else: + return self._playlistIndex+1 + + def playlistIsAtEnd(self): + return len(self._playlist) <= self._playlistIndex+1 + + def rewindFile(self): + self.setPosition(0) def updatePlayerStatus(self, paused, position): position -= self.getUserOffset() @@ -503,13 +520,13 @@ class SyncplayClient(object): @needsSharedPlaylistsEnabled - def switchToNewPlaylistIndex(self, index): + def switchToNewPlaylistIndex(self, index, resetPosition=False): try: filename = self._playlist[index] if utils.isURL(filename): for URI in constants.SAFE_URIS: if filename.startswith(URI): - self._player.openFile(filename) + self._player.openFile(filename, resetPosition=resetPosition) return self.ui.showErrorMessage(getMessage("cannot-add-unsafe-path-error").format(filename)) return @@ -517,7 +534,7 @@ class SyncplayClient(object): path = self.findFilenameInDirectories(filename) # TODO: Find Path properly if path: - self._player.openFile(path) + self._player.openFile(path, resetPosition) else: self.ui.showErrorMessage(getMessage("cannot-find-file-for-playlist-switch-error").format(filename)) return diff --git a/syncplay/constants.py b/syncplay/constants.py index 869d9c9..790ab6c 100644 --- a/syncplay/constants.py +++ b/syncplay/constants.py @@ -19,6 +19,7 @@ FALLBACK_INITIAL_LANGUAGE = "en" FALLBACK_PUBLIC_SYNCPLAY_SERVERS = [[u'syncplay.pl:8995 (France)', u'syncplay.pl:8995'],[u'syncplay.pl:8996 (France)', u'syncplay.pl:8996'],[u'syncplay.pl:8997 (France)', u'syncplay.pl:8997'],[u'syncplay.pl:8998 (France)', u'syncplay.pl:8998'],[u'syncplay.pl:8999 (France)', u'syncplay.pl:8999']] PLAYLIST_LOAD_NEXT_FILE_MINIMUM_LENGTH = 10 # Seconds PLAYLIST_LOAD_NEXT_FILE_TIME_FROM_END_THRESHOLD = 5 # Seconds (only triggered if file is paused, e.g. due to EOF) +PLAYLIST_LOOPS = True #Overriden by config SHOW_OSD = True # Sends Syncplay messages to media player OSD From c30878d49998e18d7f224263bcdd1b48e076a931 Mon Sep 17 00:00:00 2001 From: Et0h Date: Sat, 23 Jan 2016 20:09:05 +0000 Subject: [PATCH 071/134] Remove another redundant random.seed() --- syncplay/client.py | 1 - 1 file changed, 1 deletion(-) diff --git a/syncplay/client.py b/syncplay/client.py index 2154c57..4d75484 100644 --- a/syncplay/client.py +++ b/syncplay/client.py @@ -570,7 +570,6 @@ class SyncplayClient(object): @needsSharedPlaylistsEnabled def shufflePlaylist(self): if self._playlist and len(self._playlist) > 0: - random.seed() shuffledPlaylist = deepcopy(self._playlist) random.shuffle(shuffledPlaylist) self.ui.setPlaylist(shuffledPlaylist) From a77d3146de5a31aa5478fb2fccf76417bb1e4542 Mon Sep 17 00:00:00 2001 From: Et0h Date: Sat, 23 Jan 2016 22:05:23 +0000 Subject: [PATCH 072/134] Privatise non-public undo functions --- syncplay/client.py | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/syncplay/client.py b/syncplay/client.py index 4d75484..954f0ba 100644 --- a/syncplay/client.py +++ b/syncplay/client.py @@ -581,26 +581,26 @@ class SyncplayClient(object): self._previousPlaylistRoom = None def updateUndoPlaylistBuffer(self, currentPlaylist, newPlaylist, newRoom): - if self.playlistBufferIsFromOldRoom(newRoom): - self.movePlaylistBufferToNewRoom(newRoom) - elif self.playlistBufferNeedsUpdating(currentPlaylist, newPlaylist): + if self._playlistBufferIsFromOldRoom(newRoom): + self._movePlaylistBufferToNewRoom(newRoom) + elif self._playlistBufferNeedsUpdating(currentPlaylist, newPlaylist): self._previousPlaylist = currentPlaylist - def playlistBufferIsFromOldRoom(self, newRoom): - return self._previousPlaylistRoom <> newRoom - - def movePlaylistBufferToNewRoom(self, currentRoom): - self._previousPlaylist = None - self._previousPlaylistRoom = currentRoom - - def playlistBufferNeedsUpdating(self, currentPlaylist, newPlaylist): - return self._previousPlaylist <> currentPlaylist and currentPlaylist <> newPlaylist - def canUndoPlaylist(self, currentPlaylist): return self._previousPlaylist is not None and currentPlaylist <> self._previousPlaylist def getPreviousPlaylist(self): return self._previousPlaylist + + def _playlistBufferIsFromOldRoom(self, newRoom): + return self._previousPlaylistRoom <> newRoom + + def _movePlaylistBufferToNewRoom(self, currentRoom): + self._previousPlaylist = None + self._previousPlaylistRoom = currentRoom + + def _playlistBufferNeedsUpdating(self, currentPlaylist, newPlaylist): + return self._previousPlaylist <> currentPlaylist and currentPlaylist <> newPlaylist def __executePrivacySettings(self, filename, size): if self._config['filenamePrivacyMode'] == PRIVACY_SENDHASHED_MODE: From 5ce752cbef589ade76ae5dc1bcfafc7c9e34c0ea Mon Sep 17 00:00:00 2001 From: Et0h Date: Sun, 24 Jan 2016 21:54:49 +0000 Subject: [PATCH 073/134] Refactor: Move playlist into its own class --- syncplay/client.py | 366 +++++++++++++++++++++++------------------- syncplay/protocols.py | 4 +- syncplay/ui/gui.py | 6 +- 3 files changed, 203 insertions(+), 173 deletions(-) diff --git a/syncplay/client.py b/syncplay/client.py index 954f0ba..a42a045 100644 --- a/syncplay/client.py +++ b/syncplay/client.py @@ -114,12 +114,10 @@ class SyncplayClient(object): self.autoplayTimer = task.LoopingCall(self.autoplayCountdown) self.autoplayTimeLeft = constants.AUTOPLAY_DELAY - self._playlist = [] - self._playlistIndex = None self.__playerReady = defer.Deferred() self._warnings = self._WarningManager(self._player, self.userlist, self.ui, self) - self._undoPlaylist = self._UndoPlaylistManager() + self.playlist = SyncplayPlaylist(self) if constants.LIST_RELATIVE_CONFIGS and self._config.has_key('loadedRelativePaths') and self._config['loadedRelativePaths']: paths = "; ".join(self._config['loadedRelativePaths']) self.ui.showMessage(getMessage("relative-config-notification").format(paths), noPlayer=True, noTimestamp=True) @@ -129,15 +127,6 @@ class SyncplayClient(object): if missingStrings is not None and missingStrings is not "": self.ui.showDebugMessage("MISSING/UNUSED STRINGS DETECTED:\n{}".format(missingStrings)) - def needsSharedPlaylistsEnabled(f): # @NoSelf - @wraps(f) - def wrapper(self, *args, **kwds): - if not self._config['sharedPlaylistEnabled']: - self.ui.showDebugMessage("Tried to use shared playlists when it was disabled!") - return - return f(self, *args, **kwds) - return wrapper - def initProtocol(self, protocol): self._protocol = protocol @@ -153,6 +142,12 @@ class SyncplayClient(object): self.scheduleAskPlayer() self.__playerReady.callback(player) + def addPlayerReadyCallback(self, lambdaToCall): + self.__playerReady.addCallback(lambdaToCall) + + def playerIsNotReady(self): + return self._player is None + def scheduleAskPlayer(self, when=constants.PLAYER_ASK_DELAY): self._askPlayerTimer = task.LoopingCall(self.askPlayer) self._askPlayerTimer.start(when) @@ -179,43 +174,6 @@ class SyncplayClient(object): seeked = _playerDiff > constants.SEEK_THRESHOLD and _globalDiff > constants.SEEK_THRESHOLD return pauseChange, seeked - @needsSharedPlaylistsEnabled - def loadNextFileInPlaylist(self): - if self._notPlayingCurrentIndex(): - return - if self._thereIsNextPlaylistIndex(): - self.switchToNewPlaylistIndex(self._nextPlaylistIndex(), resetPosition=True) - else: - self.rewindFile() - - def _notPlayingCurrentIndex(self): - if self._playlistIndex is None or self._playlist is None or len(self._playlist) <= self._playlistIndex: - self.ui.showDebugMessage(u"Not playing current index - Index none or length issue") - return True - currentPlaylistFilename = self._playlist[self._playlistIndex] - if self.userlist.currentUser.file and currentPlaylistFilename == self.userlist.currentUser.file['name']: - return False - else: - self.ui.showDebugMessage(u"Not playing current index - Filename mismatch or no file") - return True - - def _thereIsNextPlaylistIndex(self): - if self._playlistIndex is None: - return False - elif len(self._playlist) == 1: - return False - else: - return True - - def _nextPlaylistIndex(self): - if self.playlistIsAtEnd(): - return 0 - else: - return self._playlistIndex+1 - - def playlistIsAtEnd(self): - return len(self._playlist) <= self._playlistIndex+1 - def rewindFile(self): self.setPosition(0) @@ -227,7 +185,7 @@ class SyncplayClient(object): currentLength = self.userlist.currentUser.file["duration"] if self.userlist.currentUser.file else 0 if pauseChange and paused and currentLength > constants.PLAYLIST_LOAD_NEXT_FILE_MINIMUM_LENGTH\ and abs(position - currentLength ) < constants.PLAYLIST_LOAD_NEXT_FILE_TIME_FROM_END_THRESHOLD: - self.loadNextFileInPlaylist() + self.playlist.loadNextFileInPlaylist() elif pauseChange and utils.meetsMinVersion(self.serverVersion, constants.USER_READY_MIN_VERSION): pauseChange = self._toggleReady(pauseChange, paused) @@ -472,13 +430,7 @@ class SyncplayClient(object): filename, size = self.__executePrivacySettings(filename, size) self.userlist.currentUser.setFile(filename, duration, size, path) self.sendFile() - - try: - index = self._playlist.index(filename) - self.changeToPlaylistIndex(index) - except ValueError: - pass - + self.playlist.changeToPlaylistIndexFromFilename(filename) def findFilenameInDirectories(self, filename): # TODO: Replace this with the code in gui.py @@ -490,117 +442,14 @@ class SyncplayClient(object): return os.path.join(root,filename) return None - def changeToPlaylistIndex(self, index, username = None): - if not self._playlist or len(self._playlist) == 0: - return - path = None - if index is None: - return - if username is None and not self._config['sharedPlaylistEnabled']: - return - try: - filename = self._playlist[index] - self.ui.setPlaylistIndexFilename(filename) - if not self._config['sharedPlaylistEnabled']: - self._playlistIndex = index - if username is not None and self.userlist.currentUser.file and filename == self.userlist.currentUser.file['name']: - return - except IndexError: - pass + def openFile(self, filePath, resetPosition=False): + self._player.openFile(filePath, resetPosition) - if self._player is None: - self.__playerReady.addCallback(lambda x: self.changeToPlaylistIndex(index, username)) - return - self._playlistIndex = index - if username is None and self._protocol and self._protocol.logged and self._config["sharedPlaylistEnabled"]: - self._protocol.setPlaylistIndex(index) - else: - self.ui.showMessage(getMessage("playlist-selection-changed-notification").format(username)) - self.switchToNewPlaylistIndex(index) + def setPlaylistIndex(self, index): + self._protocol.setPlaylistIndex(index) - - @needsSharedPlaylistsEnabled - def switchToNewPlaylistIndex(self, index, resetPosition=False): - try: - filename = self._playlist[index] - if utils.isURL(filename): - for URI in constants.SAFE_URIS: - if filename.startswith(URI): - self._player.openFile(filename, resetPosition=resetPosition) - return - self.ui.showErrorMessage(getMessage("cannot-add-unsafe-path-error").format(filename)) - return - else: - path = self.findFilenameInDirectories(filename) - # TODO: Find Path properly - if path: - self._player.openFile(path, resetPosition) - else: - self.ui.showErrorMessage(getMessage("cannot-find-file-for-playlist-switch-error").format(filename)) - return - except IndexError: - self.ui.showDebugMessage("Could not change playlist index due to IndexError") - - def changePlaylist(self, files, username = None): - try: - filename = self._playlist[self._playlistIndex] - newIndex = files.index(filename) - except: - newIndex = 0 - - self._undoPlaylist.updateUndoPlaylistBuffer(currentPlaylist=self._playlist, newPlaylist=files, newRoom=self.userlist.currentUser.room) - self._playlist = files - - if username is None and self._protocol and self._protocol.logged: - if self._config['sharedPlaylistEnabled']: - self._protocol.setPlaylist(files) - self.changeToPlaylistIndex(newIndex) - else: - self.ui.setPlaylist(self._playlist) - self.changeToPlaylistIndex(newIndex, username) - self.ui.showMessage(getMessage("playlist-contents-changed-notification").format(username)) - - @needsSharedPlaylistsEnabled - def undoPlaylistChange(self): - if self._undoPlaylist.canUndoPlaylist(self._playlist): - newPlaylist = self._undoPlaylist.getPreviousPlaylist() - self.ui.setPlaylist(newPlaylist) - self.changePlaylist(newPlaylist) - - @needsSharedPlaylistsEnabled - def shufflePlaylist(self): - if self._playlist and len(self._playlist) > 0: - shuffledPlaylist = deepcopy(self._playlist) - random.shuffle(shuffledPlaylist) - self.ui.setPlaylist(shuffledPlaylist) - self.changePlaylist(shuffledPlaylist) - - class _UndoPlaylistManager(): - def __init__(self): - self._previousPlaylist = None - self._previousPlaylistRoom = None - - def updateUndoPlaylistBuffer(self, currentPlaylist, newPlaylist, newRoom): - if self._playlistBufferIsFromOldRoom(newRoom): - self._movePlaylistBufferToNewRoom(newRoom) - elif self._playlistBufferNeedsUpdating(currentPlaylist, newPlaylist): - self._previousPlaylist = currentPlaylist - - def canUndoPlaylist(self, currentPlaylist): - return self._previousPlaylist is not None and currentPlaylist <> self._previousPlaylist - - def getPreviousPlaylist(self): - return self._previousPlaylist - - def _playlistBufferIsFromOldRoom(self, newRoom): - return self._previousPlaylistRoom <> newRoom - - def _movePlaylistBufferToNewRoom(self, currentRoom): - self._previousPlaylist = None - self._previousPlaylistRoom = currentRoom - - def _playlistBufferNeedsUpdating(self, currentPlaylist, newPlaylist): - return self._previousPlaylist <> currentPlaylist and currentPlaylist <> newPlaylist + def changeToPlaylistIndex(self, *args, **kwargs): + self.playlist.changeToPlaylistIndex(*args, **kwargs) def __executePrivacySettings(self, filename, size): if self._config['filenamePrivacyMode'] == PRIVACY_SENDHASHED_MODE: @@ -662,6 +511,12 @@ class SyncplayClient(object): if storedRoomPassword: self.identifyAsController(storedRoomPassword) + def isConnectedAndInARoom(self): + return self._protocol and self._protocol.logged and self.userlist.currentUser.room + + def sharedPlaylistIsEnabled(self): + return self._config['sharedPlaylistEnabled'] + def connected(self): readyState = self._config['readyAtStart'] if self.userlist.currentUser.isReady() is None else self.userlist.currentUser.isReady() self._protocol.setReady(readyState, manuallyInitiated=False) @@ -744,12 +599,12 @@ class SyncplayClient(object): return requireMinVersionDecorator def changePlaylistEnabledState(self, newState): - oldState = self._config["sharedPlaylistEnabled"] + oldState = self.sharedPlaylistIsEnabled() from syncplay.ui.ConfigurationGetter import ConfigurationGetter ConfigurationGetter().setConfigOption("sharedPlaylistEnabled", newState) self._config["sharedPlaylistEnabled"] = newState if oldState == False and newState == True: - self.changeToPlaylistIndex(self._playlistIndex) + self.playlist.loadCurrentPlaylistIndex() def changeAutoplayState(self, newState): self.autoPlay = newState @@ -1437,4 +1292,179 @@ class UiManager(object): def drop(self): self.__ui.drop() +class SyncplayPlaylist(): + def __init__(self, client): + self._client = client + self._ui = self._client.ui + self._previousPlaylist = None + self._previousPlaylistRoom = None + self._playlist = [] + self._playlistIndex = None + def needsSharedPlaylistsEnabled(f): # @NoSelf + @wraps(f) + def wrapper(self, *args, **kwds): + if not self._client.sharedPlaylistIsEnabled(): + self._ui.showDebugMessage("Tried to use shared playlists when it was disabled!") + return + return f(self, *args, **kwds) + return wrapper + + def changeToPlaylistIndexFromFilename(self, filename): + try: + index = self._playlist.index(filename) + self.changeToPlaylistIndex(index) + except ValueError: + pass + + def changeToPlaylistIndex(self, index, username = None): + if not self._playlist or len(self._playlist) == 0: + return + path = None + if index is None: + return + if username is None and not self._client.sharedPlaylistIsEnabled(): + return + try: + filename = self._playlist[index] + self._ui.setPlaylistIndexFilename(filename) + if not self._client.sharedPlaylistIsEnabled(): + self._playlistIndex = index + if username is not None and self._client.userlist.currentUser.file and filename == self._client.userlist.currentUser.file['name']: + return + except IndexError: + pass + + if self._client.playerIsNotReady(): + self._client.addPlayerReadyCallback(lambda x: self.changeToPlaylistIndex(index, username)) + return + + self._playlistIndex = index + if username is None and self._client.isConnectedAndInARoom() and self._client.sharedPlaylistIsEnabled(): + self._client.setPlaylistIndex(index) + else: + self._ui.showMessage(getMessage("playlist-selection-changed-notification").format(username)) + self.switchToNewPlaylistIndex(index) + + @needsSharedPlaylistsEnabled + def switchToNewPlaylistIndex(self, index, resetPosition=False): + if self._client.playerIsNotReady(): + self._client.addPlayerReadyCallback(lambda x: self.switchToNewPlaylistIndex(index, resetPosition)) + return + + try: + filename = self._playlist[index] + if utils.isURL(filename): + for URI in constants.SAFE_URIS: + if filename.startswith(URI): + self._client.openFile(filename, resetPosition=resetPosition) + return + self._ui.showErrorMessage(getMessage("cannot-add-unsafe-path-error").format(filename)) + return + else: + path = self._client.findFilenameInDirectories(filename) + # TODO: Find Path properly + if path: + self._client.openFile(path, resetPosition) + else: + self._ui.showErrorMessage(getMessage("cannot-find-file-for-playlist-switch-error").format(filename)) + return + except IndexError: + self._ui.showDebugMessage("Could not change playlist index due to IndexError") + + def changePlaylist(self, files, username = None): + try: + filename = self._playlist[self._playlistIndex] + newIndex = files.index(filename) + except: + newIndex = 0 + + self._updateUndoPlaylistBuffer(newPlaylist=files, newRoom=self._client.userlist.currentUser.room) + self._playlist = files + + if username is None and self._client.isConnectedAndInARoom(): + if self._client.sharedPlaylistIsEnabled(): + self._client._protocol.setPlaylist(files) + self.changeToPlaylistIndex(newIndex) + else: + self._ui.setPlaylist(self._playlist) + self.changeToPlaylistIndex(newIndex, username) + self._ui.showMessage(getMessage("playlist-contents-changed-notification").format(username)) + + @needsSharedPlaylistsEnabled + def undoPlaylistChange(self): + if self.canUndoPlaylist(self._playlist): + newPlaylist = self._getPreviousPlaylist() + self._ui.setPlaylist(newPlaylist) + self.changePlaylist(newPlaylist) + + @needsSharedPlaylistsEnabled + def shufflePlaylist(self): + if self._playlist and len(self._playlist) > 0: + shuffledPlaylist = deepcopy(self._playlist) + random.shuffle(shuffledPlaylist) + self._ui.setPlaylist(shuffledPlaylist) + self.changePlaylist(shuffledPlaylist) + + def canUndoPlaylist(self, currentPlaylist): + return self._previousPlaylist is not None and currentPlaylist <> self._previousPlaylist + + def loadCurrentPlaylistIndex(self): + if self._notPlayingCurrentIndex(): + self.switchToNewPlaylistIndex(self._playlistIndex) + + @needsSharedPlaylistsEnabled + def loadNextFileInPlaylist(self): + if self._notPlayingCurrentIndex(): + return + if self._thereIsNextPlaylistIndex(): + self.switchToNewPlaylistIndex(self._nextPlaylistIndex(), resetPosition=True) + else: + self._client.rewindFile() + + def _updateUndoPlaylistBuffer(self, newPlaylist, newRoom): + if self._playlistBufferIsFromOldRoom(newRoom): + self._movePlaylistBufferToNewRoom(newRoom) + elif self._playlistBufferNeedsUpdating(newPlaylist): + self._previousPlaylist = self._playlist + + def _getPreviousPlaylist(self): + return self._previousPlaylist + + def _notPlayingCurrentIndex(self): + if self._playlistIndex is None or self._playlist is None or len(self._playlist) <= self._playlistIndex: + self._ui.showDebugMessage(u"Not playing current index - Index none or length issue") + return True + currentPlaylistFilename = self._playlist[self._playlistIndex] + if self._client.userlist.currentUser.file and currentPlaylistFilename == self._client.userlist.currentUser.file['name']: + return False + else: + self._ui.showDebugMessage(u"Not playing current index - Filename mismatch or no file") + return True + + def _thereIsNextPlaylistIndex(self): + if self._playlistIndex is None: + return False + elif len(self._playlist) == 1: + return False + else: + return True + + def _nextPlaylistIndex(self): + if self._playlistIsAtEnd(): + return 0 + else: + return self._playlistIndex+1 + + def _playlistIsAtEnd(self): + return len(self._playlist) <= self._playlistIndex+1 + + def _playlistBufferIsFromOldRoom(self, newRoom): + return self._previousPlaylistRoom <> newRoom + + def _movePlaylistBufferToNewRoom(self, currentRoom): + self._previousPlaylist = None + self._previousPlaylistRoom = currentRoom + + def _playlistBufferNeedsUpdating(self, newPlaylist): + return self._previousPlaylist <> self._playlist and self._playlist <> newPlaylist diff --git a/syncplay/protocols.py b/syncplay/protocols.py index 04fe7cc..2eeafe8 100644 --- a/syncplay/protocols.py +++ b/syncplay/protocols.py @@ -141,9 +141,9 @@ class SyncClientProtocol(JSONCommandProtocol): manuallyInitiated = values["manuallyInitiated"] if values.has_key("manuallyInitiated") else True self._client.setReady(user, isReady, manuallyInitiated) elif command == "playlistIndex": - self._client.changeToPlaylistIndex(values['index'], values['user']) + self._client.playlist.changeToPlaylistIndex(values['index'], values['user']) elif command == "playlistChange": - self._client.changePlaylist(values['files'], values['user']) + self._client.playlist.changePlaylist(values['files'], values['user']) def sendSet(self, setting): self.sendMessage({"Set": setting}) diff --git a/syncplay/ui/gui.py b/syncplay/ui/gui.py index 8251d50..c147aa3 100644 --- a/syncplay/ui/gui.py +++ b/syncplay/ui/gui.py @@ -582,11 +582,11 @@ class MainWindow(QtGui.QMainWindow): @needsClient def undoPlaylistChange(self): - self._syncplayClient.undoPlaylistChange() + self._syncplayClient.playlist.undoPlaylistChange() @needsClient def shufflePlaylist(self): - self._syncplayClient.shufflePlaylist() + self._syncplayClient.playlist.shufflePlaylist() def openPlaylistMenu(self, position): indexes = self.playlist.selectedIndexes() @@ -996,7 +996,7 @@ class MainWindow(QtGui.QMainWindow): newPlaylist = self.getPlaylistState() if newPlaylist <> self.playlistState and self._syncplayClient and not self.updatingPlaylist: self.playlistState = newPlaylist - self._syncplayClient.changePlaylist(newPlaylist) + self._syncplayClient.playlist.changePlaylist(newPlaylist) def addTopLayout(self, window): window.topSplit = self.topSplitter(Qt.Horizontal, self) From 8334aa4682622c02c79deebd5c96b9d346c05385 Mon Sep 17 00:00:00 2001 From: Et0h Date: Tue, 26 Jan 2016 23:33:26 +0000 Subject: [PATCH 074/134] Repeat mpv youtube-dl failure messages in Syncplay --- syncplay/players/mpv.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/syncplay/players/mpv.py b/syncplay/players/mpv.py index c3cf44b..2439a15 100644 --- a/syncplay/players/mpv.py +++ b/syncplay/players/mpv.py @@ -94,6 +94,9 @@ class OldMpvPlayer(MpvPlayer): elif "[ytdl_hook] Your version of youtube-dl is too old" in line: self._client.ui.showErrorMessage(line) + elif "[ytdl_hook] youtube-dl failed" in line: + self._client.ui.showErrorMessage(line) + def _handleUnknownLine(self, line): self.mpvVersionErrorCheck(line) if "Playing: " in line: From 85ce40bdbd8642e8dd67ef3f42f8ab67946c3926 Mon Sep 17 00:00:00 2001 From: Et0h Date: Tue, 26 Jan 2016 23:48:33 +0000 Subject: [PATCH 075/134] Relay error if file format is not recognized by mpv --- syncplay/constants.py | 2 ++ syncplay/players/mpv.py | 11 ++++------- 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/syncplay/constants.py b/syncplay/constants.py index 488093f..568520b 100644 --- a/syncplay/constants.py +++ b/syncplay/constants.py @@ -111,6 +111,8 @@ MPV_ICONPATH = "mpv.png" MPC_ICONPATH = "mpc-hc.png" MPC64_ICONPATH = "mpc-hc64.png" +MPV_ERROR_MESSAGES_TO_REPEAT = ['[ytdl_hook] Your version of youtube-dl is too old', '[ytdl_hook] youtube-dl failed', 'Failed to recognize file format.'] + #Changing these is usually not something you're looking for PLAYER_ASK_DELAY = 0.1 PING_MOVING_AVERAGE_WEIGHT = 0.85 diff --git a/syncplay/players/mpv.py b/syncplay/players/mpv.py index 2439a15..3167f8b 100644 --- a/syncplay/players/mpv.py +++ b/syncplay/players/mpv.py @@ -83,7 +83,7 @@ class OldMpvPlayer(MpvPlayer): self._paused = not self._paused self._listener.sendLine('cycle pause') - def mpvVersionErrorCheck(self, line): + def mpvErrorCheck(self, line): if "Error parsing option" in line or "Error parsing commandline option" in line: self.quitReason = getMessage("mpv-version-error") @@ -91,14 +91,11 @@ class OldMpvPlayer(MpvPlayer): self.reactor.callFromThread(self._client.ui.showErrorMessage, getMessage("mpv-version-error"), True) self.drop() - elif "[ytdl_hook] Your version of youtube-dl is too old" in line: - self._client.ui.showErrorMessage(line) - - elif "[ytdl_hook] youtube-dl failed" in line: + if any(errormsg in line for errormsg in constants.MPV_ERROR_MESSAGES_TO_REPEAT): self._client.ui.showErrorMessage(line) def _handleUnknownLine(self, line): - self.mpvVersionErrorCheck(line) + self.mpvErrorCheck(line) if "Playing: " in line: newpath = line[9:] oldpath = self._filepath @@ -208,7 +205,7 @@ class NewMpvPlayer(OldMpvPlayer): self._storePosition(0) def _handleUnknownLine(self, line): - self.mpvVersionErrorCheck(line) + self.mpvErrorCheck(line) if line == "" or "Playing:" in line: self._clearFileLoaded() From 3b6f8fd41496b46f28b1704b8615b097b333d114 Mon Sep 17 00:00:00 2001 From: Et0h Date: Fri, 29 Jan 2016 15:30:21 +0000 Subject: [PATCH 076/134] Move to top when shuffling + avoid random index switch bug --- syncplay/client.py | 59 ++++++++++++++++++++++++++++++---------- syncplay/ui/consoleUI.py | 2 +- syncplay/ui/gui.py | 9 ++++-- 3 files changed, 52 insertions(+), 18 deletions(-) diff --git a/syncplay/client.py b/syncplay/client.py index 54faabc..ae8e5e2 100644 --- a/syncplay/client.py +++ b/syncplay/client.py @@ -1255,8 +1255,8 @@ class UiManager(object): self.lastSecondaryOSDEndTime = None self.lastError = "" - def setPlaylist(self, newPlaylist): - self.__ui.setPlaylist(newPlaylist) + def setPlaylist(self, newPlaylist, newIndexFilename=None): + self.__ui.setPlaylist(newPlaylist, newIndexFilename) def setPlaylistIndexFilename(self, filename): self.__ui.setPlaylistIndexFilename(filename) @@ -1349,11 +1349,11 @@ class SyncplayPlaylist(): def changeToPlaylistIndex(self, index, username = None): if not self._playlist or len(self._playlist) == 0: return - path = None if index is None: return if username is None and not self._client.sharedPlaylistIsEnabled(): return + self._playlistIndex = index try: filename = self._playlist[index] self._ui.setPlaylistIndexFilename(filename) @@ -1401,22 +1401,53 @@ class SyncplayPlaylist(): except IndexError: self._ui.showDebugMessage("Could not change playlist index due to IndexError") - def changePlaylist(self, files, username = None): - try: - filename = self._playlist[self._playlistIndex] - newIndex = files.index(filename) - except: + def _getValidIndexFromNewPlaylist(self, newPlaylist=None): + if self._playlistIndex is None or not newPlaylist or len(newPlaylist) <= 1: + return 0 + + i = self._playlistIndex + while i <= len(self._playlist): + try: + filename = self._playlist[i] + validIndex = newPlaylist.index(filename) + return validIndex + except: + i += 1 + + i = self._playlistIndex + while i > 0: + try: + filename = self._playlist[i] + validIndex = newPlaylist.index(filename) + return validIndex+1 if validIndex < len(newPlaylist)-1 else validIndex + except: + i -= 1 + return 0 + + def _getFilenameFromIndexInGivenPlaylist(self, _playlist, _index): + if not _index or not _playlist: + return None + filename = _playlist[_index] if len(_playlist) > _index else None + return filename + + def changePlaylist(self, files, username = None, resetIndex=False): + if resetIndex: newIndex = 0 + filename = files[0] if files and len(files) > 0 else None + else: + newIndex = self._getValidIndexFromNewPlaylist(files) + filename = self._getFilenameFromIndexInGivenPlaylist(files, newIndex) self._updateUndoPlaylistBuffer(newPlaylist=files, newRoom=self._client.userlist.currentUser.room) self._playlist = files - if username is None and self._client.isConnectedAndInARoom(): - if self._client.sharedPlaylistIsEnabled(): + if username is None: + if self._client.isConnectedAndInARoom() and self._client.sharedPlaylistIsEnabled(): self._client._protocol.setPlaylist(files) self.changeToPlaylistIndex(newIndex) + self._ui.setPlaylist(self._playlist, filename) else: - self._ui.setPlaylist(self._playlist) + self._ui.setPlaylist(self._playlist, filename) self.changeToPlaylistIndex(newIndex, username) self._ui.showMessage(getMessage("playlist-contents-changed-notification").format(username)) @@ -1424,16 +1455,14 @@ class SyncplayPlaylist(): def undoPlaylistChange(self): if self.canUndoPlaylist(self._playlist): newPlaylist = self._getPreviousPlaylist() - self._ui.setPlaylist(newPlaylist) - self.changePlaylist(newPlaylist) + self.changePlaylist(newPlaylist, username=None) @needsSharedPlaylistsEnabled def shufflePlaylist(self): if self._playlist and len(self._playlist) > 0: shuffledPlaylist = deepcopy(self._playlist) random.shuffle(shuffledPlaylist) - self._ui.setPlaylist(shuffledPlaylist) - self.changePlaylist(shuffledPlaylist) + self.changePlaylist(shuffledPlaylist, username=None, resetIndex=True) def canUndoPlaylist(self, currentPlaylist): return self._previousPlaylist is not None and currentPlaylist <> self._previousPlaylist diff --git a/syncplay/ui/consoleUI.py b/syncplay/ui/consoleUI.py index 6eb2057..51e9f7d 100644 --- a/syncplay/ui/consoleUI.py +++ b/syncplay/ui/consoleUI.py @@ -23,7 +23,7 @@ class ConsoleUI(threading.Thread): def drop(self): pass - def setPlaylist(self, newPlaylist): + def setPlaylist(self, newPlaylist, newIndexFilename=None): pass def setPlaylistIndexFilename(self, filename): diff --git a/syncplay/ui/gui.py b/syncplay/ui/gui.py index c147aa3..c95f94a 100644 --- a/syncplay/ui/gui.py +++ b/syncplay/ui/gui.py @@ -175,7 +175,7 @@ class MainWindow(QtGui.QMainWindow): def setPlaylistIndexFilename(self, filename): if filename <> self.playlistIndexFilename: self.playlistIndexFilename = filename - self.updatePlaylistIndexIcon() + self.updatePlaylistIndexIcon() def updatePlaylistIndexIcon(self): for item in xrange(self.count()): @@ -1413,14 +1413,19 @@ class MainWindow(QtGui.QMainWindow): self._syncplayClient._player.openFile(dropfilepath, resetPosition=True) self._syncplayClient.setPosition(0) - def setPlaylist(self, newPlaylist): + def setPlaylist(self, newPlaylist, newIndexFilename=None): + if self.updatingPlaylist: + self.ui.showDebugMessage("Trying to set playlist while it is already being updated") if newPlaylist == self.playlistState: + self.playlist.setPlaylistIndexFilename(newIndexFilename) return self.updatingPlaylist = True if newPlaylist and len(newPlaylist) > 0: self.clearedPlaylistNote = True self.playlistState = newPlaylist self.playlist.updatePlaylist(newPlaylist) + if newIndexFilename: + self.playlist.setPlaylistIndexFilename(newIndexFilename) self.updatingPlaylist = False def setPlaylistIndexFilename(self, filename): From 9d4732ad4090ed9838db3931a6e837377fcb38d6 Mon Sep 17 00:00:00 2001 From: Et0h Date: Sat, 30 Jan 2016 13:07:31 +0000 Subject: [PATCH 077/134] Fix double playlist change notification bug --- syncplay/client.py | 34 +++++++++++++++++++++------------- syncplay/players/mpv.py | 2 +- syncplay/ui/gui.py | 1 + 3 files changed, 23 insertions(+), 14 deletions(-) diff --git a/syncplay/client.py b/syncplay/client.py index ae8e5e2..48f2824 100644 --- a/syncplay/client.py +++ b/syncplay/client.py @@ -1329,6 +1329,7 @@ class SyncplayPlaylist(): self._previousPlaylistRoom = None self._playlist = [] self._playlistIndex = None + self.addedChangeListCallback = False def needsSharedPlaylistsEnabled(f): # @NoSelf @wraps(f) @@ -1342,18 +1343,23 @@ class SyncplayPlaylist(): def changeToPlaylistIndexFromFilename(self, filename): try: index = self._playlist.index(filename) - self.changeToPlaylistIndex(index) + if index <> self._playlistIndex: + self.changeToPlaylistIndex(index) except ValueError: pass def changeToPlaylistIndex(self, index, username = None): - if not self._playlist or len(self._playlist) == 0: + if self._playlist is None or len(self._playlist) == 0: return if index is None: return if username is None and not self._client.sharedPlaylistIsEnabled(): return - self._playlistIndex = index + if self._client.playerIsNotReady(): + if not self.addedChangeListCallback: + self.addedChangeListCallback = True + self._client.addPlayerReadyCallback(lambda x: self.changeToPlaylistIndex(index, username)) + return try: filename = self._playlist[index] self._ui.setPlaylistIndexFilename(filename) @@ -1364,13 +1370,10 @@ class SyncplayPlaylist(): except IndexError: pass - if self._client.playerIsNotReady(): - self._client.addPlayerReadyCallback(lambda x: self.changeToPlaylistIndex(index, username)) - return - self._playlistIndex = index - if username is None and self._client.isConnectedAndInARoom() and self._client.sharedPlaylistIsEnabled(): - self._client.setPlaylistIndex(index) + if username is None: + if self._client.isConnectedAndInARoom() and self._client.sharedPlaylistIsEnabled(): + self._client.setPlaylistIndex(index) else: self._ui.showMessage(getMessage("playlist-selection-changed-notification").format(username)) self.switchToNewPlaylistIndex(index) @@ -1431,6 +1434,11 @@ class SyncplayPlaylist(): return filename def changePlaylist(self, files, username = None, resetIndex=False): + if self._playlist == files: + if self._playlistIndex <> 0 and resetIndex: + self.changeToPlaylistIndex(0) + return + if resetIndex: newIndex = 0 filename = files[0] if files and len(files) > 0 else None @@ -1442,13 +1450,13 @@ class SyncplayPlaylist(): self._playlist = files if username is None: - if self._client.isConnectedAndInARoom() and self._client.sharedPlaylistIsEnabled(): + if self._client.isConnectedAndInARoom() and self._client.sharedPlaylistIsEnabled(): self._client._protocol.setPlaylist(files) self.changeToPlaylistIndex(newIndex) self._ui.setPlaylist(self._playlist, filename) + self._ui.showMessage(getMessage("playlist-contents-changed-notification").format(self._client.getUsername())) else: - self._ui.setPlaylist(self._playlist, filename) - self.changeToPlaylistIndex(newIndex, username) + self._ui.setPlaylist(self._playlist) self._ui.showMessage(getMessage("playlist-contents-changed-notification").format(username)) @needsSharedPlaylistsEnabled @@ -1525,4 +1533,4 @@ class SyncplayPlaylist(): self._previousPlaylistRoom = currentRoom def _playlistBufferNeedsUpdating(self, newPlaylist): - return self._previousPlaylist <> self._playlist and self._playlist <> newPlaylist + return self._previousPlaylist <> self._playlist and self._playlist <> newPlaylist \ No newline at end of file diff --git a/syncplay/players/mpv.py b/syncplay/players/mpv.py index 3167f8b..667946b 100644 --- a/syncplay/players/mpv.py +++ b/syncplay/players/mpv.py @@ -91,7 +91,7 @@ class OldMpvPlayer(MpvPlayer): self.reactor.callFromThread(self._client.ui.showErrorMessage, getMessage("mpv-version-error"), True) self.drop() - if any(errormsg in line for errormsg in constants.MPV_ERROR_MESSAGES_TO_REPEAT): + if constants and any(errormsg in line for errormsg in constants.MPV_ERROR_MESSAGES_TO_REPEAT): self._client.ui.showErrorMessage(line) def _handleUnknownLine(self, line): diff --git a/syncplay/ui/gui.py b/syncplay/ui/gui.py index c95f94a..c6d66be 100644 --- a/syncplay/ui/gui.py +++ b/syncplay/ui/gui.py @@ -1418,6 +1418,7 @@ class MainWindow(QtGui.QMainWindow): self.ui.showDebugMessage("Trying to set playlist while it is already being updated") if newPlaylist == self.playlistState: self.playlist.setPlaylistIndexFilename(newIndexFilename) + self.updatingPlaylist = False return self.updatingPlaylist = True if newPlaylist and len(newPlaylist) > 0: From f5f07fd058e645b6c6c53ee7cda05f82e07e3211 Mon Sep 17 00:00:00 2001 From: Et0h Date: Sat, 30 Jan 2016 13:14:43 +0000 Subject: [PATCH 078/134] Add new playlist items to end --- syncplay/ui/gui.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/syncplay/ui/gui.py b/syncplay/ui/gui.py index c6d66be..5633634 100644 --- a/syncplay/ui/gui.py +++ b/syncplay/ui/gui.py @@ -1437,14 +1437,14 @@ class MainWindow(QtGui.QMainWindow): self.removePlaylistNote() filename = os.path.basename(filePath) if self.noPlaylistDuplicates(filename): - if self.playlist == -1: + if self.playlist == -1 or index == -1: self.playlist.addItem(filename) else: self.playlist.insertItem(index, filename) elif isURL(filePath): self.removePlaylistNote() if self.noPlaylistDuplicates(filePath): - if index == -1: + if self.playlist == -1 or index == -1: self.playlist.addItem(filePath) else: self.playlist.insertItem(index, filePath) From b276bc3688b51c53dcfc6c3804c38fe95903da6c Mon Sep 17 00:00:00 2001 From: Et0h Date: Sat, 30 Jan 2016 14:07:12 +0000 Subject: [PATCH 079/134] Don't allow negative mplayer/mpv positions --- syncplay/players/mplayer.py | 4 ++-- syncplay/players/mpv.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/syncplay/players/mplayer.py b/syncplay/players/mplayer.py index dc567b9..03a4d58 100644 --- a/syncplay/players/mplayer.py +++ b/syncplay/players/mplayer.py @@ -105,7 +105,7 @@ class MplayerPlayer(BasePlayer): self.setPosition(self._client.getGlobalPosition()) def setPosition(self, value): - self._position = value + self._position = max(value,0) self._setProperty(self.POSITION_QUERY, "{}".format(value)) time.sleep(0.03) @@ -142,7 +142,7 @@ class MplayerPlayer(BasePlayer): pass def _storePosition(self, value): - self._position = value + self._position = max(value,0) def _storePauseState(self, value): self._paused = value diff --git a/syncplay/players/mpv.py b/syncplay/players/mpv.py index 667946b..a079b32 100644 --- a/syncplay/players/mpv.py +++ b/syncplay/players/mpv.py @@ -150,7 +150,7 @@ class NewMpvPlayer(OldMpvPlayer): if self._recentlyReset(): self._position = 0 elif self._fileIsLoaded(): - self._position = value + self._position = max(value,0) else: self._position = self._client.getGlobalPosition() From 0f9e17928237a1092cb4f1af544f8fddcbe35841 Mon Sep 17 00:00:00 2001 From: Et0h Date: Sat, 30 Jan 2016 14:51:42 +0000 Subject: [PATCH 080/134] Retain playlist UI filename on playlist change unless new filename specified --- syncplay/ui/gui.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/syncplay/ui/gui.py b/syncplay/ui/gui.py index 5633634..33150d4 100644 --- a/syncplay/ui/gui.py +++ b/syncplay/ui/gui.py @@ -1417,7 +1417,8 @@ class MainWindow(QtGui.QMainWindow): if self.updatingPlaylist: self.ui.showDebugMessage("Trying to set playlist while it is already being updated") if newPlaylist == self.playlistState: - self.playlist.setPlaylistIndexFilename(newIndexFilename) + if newIndexFilename: + self.playlist.setPlaylistIndexFilename(newIndexFilename) self.updatingPlaylist = False return self.updatingPlaylist = True From 9a0ea78be3786d6a4bdadd17a18c966587ba38ed Mon Sep 17 00:00:00 2001 From: Et0h Date: Sat, 30 Jan 2016 14:51:59 +0000 Subject: [PATCH 081/134] Upver to release #23 --- syncplay/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/syncplay/__init__.py b/syncplay/__init__.py index 02f66d1..9e38415 100644 --- a/syncplay/__init__.py +++ b/syncplay/__init__.py @@ -1,4 +1,4 @@ version = '1.4.0' milestone = 'Yoitsu' -release_number = '22' +release_number = '23' projectURL = 'http://syncplay.pl/' From 5016e9d102fb53aa5759223ee7ac07fcd3f72763 Mon Sep 17 00:00:00 2001 From: Et0h Date: Sun, 31 Jan 2016 11:10:47 +0000 Subject: [PATCH 082/134] Stop playlist at end (with optional loop setting for multi-file playlists) --- syncplay/client.py | 7 +++++-- syncplay/ui/ConfigurationGetter.py | 7 +++++-- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/syncplay/client.py b/syncplay/client.py index 48f2824..6c2c618 100644 --- a/syncplay/client.py +++ b/syncplay/client.py @@ -479,6 +479,9 @@ class SyncplayClient(object): def changeToPlaylistIndex(self, *args, **kwargs): self.playlist.changeToPlaylistIndex(*args, **kwargs) + def isPlaylistLoopingEnabled(self): + return self._config["loopAtEndOfPlaylist"] + def __executePrivacySettings(self, filename, size): if self._config['filenamePrivacyMode'] == PRIVACY_SENDHASHED_MODE: filename = utils.hashFilename(filename) @@ -1485,8 +1488,6 @@ class SyncplayPlaylist(): return if self._thereIsNextPlaylistIndex(): self.switchToNewPlaylistIndex(self._nextPlaylistIndex(), resetPosition=True) - else: - self._client.rewindFile() def _updateUndoPlaylistBuffer(self, newPlaylist, newRoom): if self._playlistBufferIsFromOldRoom(newRoom): @@ -1513,6 +1514,8 @@ class SyncplayPlaylist(): return False elif len(self._playlist) == 1: return False + elif self._playlistIsAtEnd(): + return self._client.isPlaylistLoopingEnabled() else: return True diff --git a/syncplay/ui/ConfigurationGetter.py b/syncplay/ui/ConfigurationGetter.py index 7f22674..abe21c8 100755 --- a/syncplay/ui/ConfigurationGetter.py +++ b/syncplay/ui/ConfigurationGetter.py @@ -35,6 +35,7 @@ class ConfigurationGetter(object): "perPlayerArguments": None, "mediaSearchDirectories": None, "sharedPlaylistEnabled": True, + "loopAtEndOfPlaylist": False, "file": None, "playerArgs": [], "playerClass": None, @@ -105,7 +106,8 @@ class ConfigurationGetter(object): "showSameRoomOSD", "showNonControllerOSD", "showDurationNotification", - "sharedPlaylistEnabled" + "sharedPlaylistEnabled", + "loopAtEndOfPlaylist" ] self._tristate = [ "checkForUpdatesAutomatically", @@ -136,7 +138,8 @@ class ConfigurationGetter(object): "filesizePrivacyMode", "unpauseAction", "pauseOnLeave", "readyAtStart", "autoplayMinUsers", "autoplayInitialState", "mediaSearchDirectories", - "sharedPlaylistEnabled", "playerCommandDelay"], + "sharedPlaylistEnabled", "playerCommandDelay", + "loopAtEndOfPlaylist"], "gui": ["showOSD", "showOSDWarnings", "showSlowdownOSD", "showDifferentRoomOSD", "showSameRoomOSD", "showNonControllerOSD", "showDurationNotification"], From 42817b90879ba5e825861b7dde30a387d68169ca Mon Sep 17 00:00:00 2001 From: Et0h Date: Sun, 31 Jan 2016 15:11:01 +0000 Subject: [PATCH 083/134] Move FileSwitchManager to Client and use in PlaylistManager --- syncplay/client.py | 145 ++++++++++++++++++++++++++++++++---- syncplay/ui/gui.py | 180 +++++---------------------------------------- 2 files changed, 151 insertions(+), 174 deletions(-) diff --git a/syncplay/client.py b/syncplay/client.py index 6c2c618..5b62d21 100644 --- a/syncplay/client.py +++ b/syncplay/client.py @@ -5,8 +5,9 @@ import re import sys import ast import random +import threading from twisted.internet.protocol import ClientFactory -from twisted.internet import reactor, task, defer +from twisted.internet import reactor, task, defer, threads from functools import wraps from copy import deepcopy from syncplay.protocols import SyncClientProtocol @@ -119,7 +120,9 @@ class SyncplayClient(object): self.__playerReady = defer.Deferred() self._warnings = self._WarningManager(self._player, self.userlist, self.ui, self) + self.fileSwitch = FileSwitchManager(self) self.playlist = SyncplayPlaylist(self) + if constants.LIST_RELATIVE_CONFIGS and self._config.has_key('loadedRelativePaths') and self._config['loadedRelativePaths']: paths = "; ".join(self._config['loadedRelativePaths']) self.ui.showMessage(getMessage("relative-config-notification").format(paths), noPlayer=True, noTimestamp=True) @@ -460,16 +463,6 @@ class SyncplayClient(object): self.sendFile() self.playlist.changeToPlaylistIndexFromFilename(filename) - def findFilenameInDirectories(self, filename): - # TODO: Replace this with the code in gui.py - directoryList = self._config["mediaSearchDirectories"] - if filename and directoryList: - for directory in directoryList: - for root, dirs, files in os.walk(directory): - if filename in files: - return os.path.join(root,filename) - return None - def openFile(self, filePath, resetPosition=False): self._player.openFile(filePath, resetPosition) @@ -1397,8 +1390,7 @@ class SyncplayPlaylist(): self._ui.showErrorMessage(getMessage("cannot-add-unsafe-path-error").format(filename)) return else: - path = self._client.findFilenameInDirectories(filename) - # TODO: Find Path properly + path = self._client.fileSwitch.findFilepath(filename) if path: self._client.openFile(path, resetPosition) else: @@ -1536,4 +1528,129 @@ class SyncplayPlaylist(): self._previousPlaylistRoom = currentRoom def _playlistBufferNeedsUpdating(self, newPlaylist): - return self._previousPlaylist <> self._playlist and self._playlist <> newPlaylist \ No newline at end of file + return self._previousPlaylist <> self._playlist and self._playlist <> newPlaylist + +class FileSwitchManager(object): + def __init__(self, client): + self._client = client + self.fileSwitchTimer = task.LoopingCall(self.updateInfo) + self.mediaFilesCache = {} + self.filenameWatchlist = [] + self.currentDirectory = None + self.mediaDirectories = None + self.lock = threading.Lock() + self.folderSearchEnabled = True + self.disabledDir = None + self.newInfo = False + self.currentlyUpdating = False + self.newInfo = False + self.newWatchlist = [] + self.fileSwitchTimer.start(constants.FOLDER_SEARCH_DOUBLE_CHECK_INTERVAL, True) + + def setClient(self, newClient): + self.client = newClient + + def setCurrentDirectory(self, curDir): + self.currentDirectory = curDir + self.updateInfo() + + def setMediaDirectories(self, mediaDirs): + self.mediaDirectories = mediaDirs + self.updateInfo() + + def checkForFileSwitchUpdate(self, bob=None): + if bob is not None: + print bob + if self.newInfo: + self.newInfo = False + self.infoUpdated() + + def updateInfo(self): + if len(self.filenameWatchlist) > 0 or len(self.mediaFilesCache) == 0 and self.currentlyUpdating == False: + threads.deferToThread(self._updateInfoThread).addCallback(self.checkForFileSwitchUpdate) + + def setFilenameWatchlist(self, unfoundFilenames): + self.filenameWatchlist = unfoundFilenames + + def _updateInfoThread(self): + if not self.folderSearchEnabled: + if self.areWatchedFilenamesInCurrentDir(): + self.newInfo = True + return + + with self.lock: + try: + self.currentlyUpdating = True + dirsToSearch = self.mediaDirectories + + if dirsToSearch: + newMediaFilesCache = {} + startTime = time.time() + for directory in dirsToSearch: + for root, dirs, files in os.walk(directory): + newMediaFilesCache[root] = files + if time.time() - startTime > constants.FOLDER_SEARCH_TIMEOUT: + self.disabledDir = directory + self.folderSearchEnabled = False + if self.areWatchedFilenamesInCurrentDir(): + self.newInfo = True + return + + if self.mediaFilesCache <> newMediaFilesCache: + self.mediaFilesCache = newMediaFilesCache + self.newInfo = True + elif self.areWatchedFilenamesInCurrentDir(): + self.newInfo = True + finally: + self.currentlyUpdating = False + + def infoUpdated(self): + if self.areWatchedFilenamesInCache() or self.areWatchedFilenamesInCurrentDir(): + self.updateListOfWhoIsPlayingWhat() + + def updateListOfWhoIsPlayingWhat(self): + self._client.showUserList() + + def findFilepath(self, filename): + if filename is None: + return + + if self.currentDirectory is not None: + candidatePath = os.path.join(self.currentDirectory,filename) + if os.path.isfile(candidatePath): + return candidatePath + + if self.mediaFilesCache is not None: + for directory in self.mediaFilesCache: + files = self.mediaFilesCache[directory] + + if len(files) > 0 and filename in files: + filepath = os.path.join(directory, filename) + if os.path.isfile(filepath): + return filepath + + def areWatchedFilenamesInCurrentDir(self): + if self.filenameWatchlist is not None and self.currentDirectory is not None: + for filename in self.filenameWatchlist: + potentialPath = os.path.join(self.currentDirectory,filename) + if os.path.isfile(potentialPath): + return True + + def areWatchedFilenamesInCache(self): + if self.filenameWatchlist is not None: + for filename in self.filenameWatchlist: + if self.isFilenameInCache(filename): + return True + + def isFilenameInCurrentDir(self, filename): + if filename is not None and self.currentDirectory is not None: + potentialPath = os.path.join(self.currentDirectory,filename) + if os.path.isfile(potentialPath): + return True + + def isFilenameInCache(self, filename): + if filename is not None and self.mediaFilesCache is not None: + for directory in self.mediaFilesCache: + files = self.mediaFilesCache[directory] + if filename in files: + return True \ No newline at end of file diff --git a/syncplay/ui/gui.py b/syncplay/ui/gui.py index 33150d4..d5c5d9e 100644 --- a/syncplay/ui/gui.py +++ b/syncplay/ui/gui.py @@ -10,8 +10,7 @@ import re import os from syncplay.utils import formatTime, sameFilename, sameFilesize, sameFileduration, RoomPasswordProvider, formatSize, isURL from functools import wraps -from twisted.internet import task, threads -import threading +from twisted.internet import task lastCheckedForUpdates = None class UserlistItemDelegate(QtGui.QStyledItemDelegate): @@ -260,148 +259,7 @@ class MainWindow(QtGui.QMainWindow): else: super(MainWindow.PlaylistWidget, self).dropEvent(event) - class FileSwitchManager(object): - def __init__(self): - self.fileSwitchTimer = task.LoopingCall(self.updateInfo) - self.fileSwitchTimer.start(constants.FOLDER_SEARCH_DOUBLE_CHECK_INTERVAL, True) - mediaFilesCache = {} - filenameWatchlist = [] - currentDirectory = None - mediaDirectories = None - lock = threading.Lock() - client = None - currentWindow = None - folderSearchEnabled = True - disabledDir = None - newInfo = False - currentlyUpdating = False - - @staticmethod - def setWindow(window): - MainWindow.FileSwitchManager.currentWindow = window - - @staticmethod - def setClient(newClient): - MainWindow.FileSwitchManager.client = newClient - - @staticmethod - def setCurrentDirectory(curDir): - MainWindow.FileSwitchManager.currentDirectory = curDir - MainWindow.FileSwitchManager.updateInfo() - - @staticmethod - def setMediaDirectories(mediaDirs): - MainWindow.FileSwitchManager.mediaDirectories = mediaDirs - MainWindow.FileSwitchManager.updateInfo() - - @staticmethod - def checkForUpdate(self=None): - if MainWindow.FileSwitchManager.newInfo: - MainWindow.FileSwitchManager.newInfo = False - MainWindow.FileSwitchManager.infoUpdated() - - @staticmethod - def updateInfo(): - if len(MainWindow.FileSwitchManager.filenameWatchlist) > 0 or len(MainWindow.FileSwitchManager.mediaFilesCache) == 0 and MainWindow.FileSwitchManager.currentlyUpdating == False: - threads.deferToThread(MainWindow.FileSwitchManager._updateInfoThread).addCallback(MainWindow.FileSwitchManager.checkForUpdate) - - @staticmethod - def setFilenameWatchlist(unfoundFilenames): - MainWindow.FileSwitchManager.filenameWatchlist = unfoundFilenames - - @staticmethod - def _updateInfoThread(): - if not MainWindow.FileSwitchManager.folderSearchEnabled: - if MainWindow.FileSwitchManager.areWatchedFilenamesInCurrentDir(): - MainWindow.FileSwitchManager.newInfo = True - return - - with MainWindow.FileSwitchManager.lock: - try: - MainWindow.FileSwitchManager.currentlyUpdating = True - dirsToSearch = MainWindow.FileSwitchManager.mediaDirectories - - if dirsToSearch: - newMediaFilesCache = {} - startTime = time.time() - for directory in dirsToSearch: - for root, dirs, files in os.walk(directory): - newMediaFilesCache[root] = files - if time.time() - startTime > constants.FOLDER_SEARCH_TIMEOUT: - if MainWindow.FileSwitchManager.client is not None and MainWindow.FileSwitchManager.currentWindow is not None: - MainWindow.FileSwitchManager.disabledDir = directory - MainWindow.FileSwitchManager.folderSearchEnabled = False - if MainWindow.FileSwitchManager.areWatchedFilenamesInCurrentDir(): - MainWindow.FileSwitchManager.newInfo = True - return - - if MainWindow.FileSwitchManager.mediaFilesCache <> newMediaFilesCache: - MainWindow.FileSwitchManager.mediaFilesCache = newMediaFilesCache - MainWindow.FileSwitchManager.newInfo = True - elif MainWindow.FileSwitchManager.areWatchedFilenamesInCurrentDir(): - MainWindow.FileSwitchManager.newInfo = True - finally: - MainWindow.FileSwitchManager.currentlyUpdating = False - - @staticmethod - def infoUpdated(): - if MainWindow.FileSwitchManager.areWatchedFilenamesInCache() or MainWindow.FileSwitchManager.areWatchedFilenamesInCurrentDir(): - MainWindow.FileSwitchManager.updateListOfWhoIsPlayingWhat() - - @staticmethod - def updateListOfWhoIsPlayingWhat(): - if MainWindow.FileSwitchManager.client is not None: - MainWindow.FileSwitchManager.client.showUserList() - - @staticmethod - def findFilepath(filename): - if filename is None: - return - - if MainWindow.FileSwitchManager.currentDirectory is not None: - candidatePath = os.path.join(MainWindow.FileSwitchManager.currentDirectory,filename) - if os.path.isfile(candidatePath): - return candidatePath - - if MainWindow.FileSwitchManager.mediaFilesCache is not None: - for directory in MainWindow.FileSwitchManager.mediaFilesCache: - files = MainWindow.FileSwitchManager.mediaFilesCache[directory] - - if len(files) > 0 and filename in files: - filepath = os.path.join(directory, filename) - if os.path.isfile(filepath): - return filepath - - @staticmethod - def areWatchedFilenamesInCurrentDir(): - if MainWindow.FileSwitchManager.filenameWatchlist is not None and MainWindow.FileSwitchManager.currentDirectory is not None: - for filename in MainWindow.FileSwitchManager.filenameWatchlist: - potentialPath = os.path.join(MainWindow.FileSwitchManager.currentDirectory,filename) - if os.path.isfile(potentialPath): - return True - - @staticmethod - def areWatchedFilenamesInCache(): - if MainWindow.FileSwitchManager.filenameWatchlist is not None: - for filename in MainWindow.FileSwitchManager.filenameWatchlist: - if MainWindow.FileSwitchManager.isFilenameInCache(filename): - return True - - @staticmethod - def isFilenameInCurrentDir(filename): - if filename is not None and MainWindow.FileSwitchManager.currentDirectory is not None: - potentialPath = os.path.join(MainWindow.FileSwitchManager.currentDirectory,filename) - if os.path.isfile(potentialPath): - return True - - @staticmethod - def isFilenameInCache(filename): - if filename is not None and MainWindow.FileSwitchManager.mediaFilesCache is not None: - for directory in MainWindow.FileSwitchManager.mediaFilesCache: - files = MainWindow.FileSwitchManager.mediaFilesCache[directory] - if filename in files: - return True class topSplitter(QtGui.QSplitter): def createHandle(self): @@ -427,14 +285,13 @@ class MainWindow(QtGui.QMainWindow): def addClient(self, client): self._syncplayClient = client - MainWindow.FileSwitchManager.setClient(client) self.roomInput.setText(self._syncplayClient.getRoom()) self.config = self._syncplayClient.getConfig() try: self.playlistGroup.blockSignals(True) self.playlistGroup.setChecked(self.config['sharedPlaylistEnabled']) self.playlistGroup.blockSignals(False) - self.FileSwitchManager.setMediaDirectories(self.config["mediaSearchDirectories"]) + self._syncplayClient.fileSwitch.setMediaDirectories(self.config["mediaSearchDirectories"]) self.updateReadyState(self.config['readyAtStart']) autoplayInitialState = self.config['autoplayInitialState'] if autoplayInitialState is not None: @@ -467,6 +324,7 @@ class MainWindow(QtGui.QMainWindow): else: self.newMessage(time.strftime(constants.UI_TIME_FORMAT, time.localtime()) + message + "
") + @needsClient def getFileSwitchState(self, filename): if filename: if filename == getMessage("nofile-note"): @@ -476,19 +334,20 @@ class MainWindow(QtGui.QMainWindow): if isURL(filename): return constants.FILEITEM_SWITCH_STREAM_SWITCH elif filename not in self.newWatchlist: - if MainWindow.FileSwitchManager.findFilepath(filename): + if self._syncplayClient.fileSwitch.findFilepath(filename): return constants.FILEITEM_SWITCH_FILE_SWITCH else: self.newWatchlist.extend([filename]) return constants.FILEITEM_SWITCH_NO_SWITCH + @needsClient def showUserList(self, currentUser, rooms): self._usertreebuffer = QtGui.QStandardItemModel() self._usertreebuffer.setHorizontalHeaderLabels( (getMessage("roomuser-heading-label"), getMessage("size-heading-label"), getMessage("duration-heading-label"), getMessage("filename-heading-label") )) usertreeRoot = self._usertreebuffer.invisibleRootItem() if self._syncplayClient.userlist.currentUser.file and self._syncplayClient.userlist.currentUser.file and os.path.isfile(self._syncplayClient.userlist.currentUser.file["path"]): - MainWindow.FileSwitchManager.setCurrentDirectory(os.path.dirname(self._syncplayClient.userlist.currentUser.file["path"])) + self._syncplayClient.fileSwitch.setCurrentDirectory(os.path.dirname(self._syncplayClient.userlist.currentUser.file["path"])) for room in rooms: self.newWatchlist = [] @@ -577,7 +436,7 @@ class MainWindow(QtGui.QMainWindow): self.listTreeView.setRootIsDecorated(False) self.listTreeView.expandAll() self.updateListGeometry() - MainWindow.FileSwitchManager.setFilenameWatchlist(self.newWatchlist) + self._syncplayClient.fileSwitch.setFilenameWatchlist(self.newWatchlist) self.checkForDisabledDir() @needsClient @@ -588,6 +447,7 @@ class MainWindow(QtGui.QMainWindow): def shufflePlaylist(self): self._syncplayClient.playlist.shufflePlaylist() + @needsClient def openPlaylistMenu(self, position): indexes = self.playlist.selectedIndexes() if sys.platform.startswith('win'): @@ -606,7 +466,7 @@ class MainWindow(QtGui.QMainWindow): if isURL(firstFile): menu.addAction(QtGui.QPixmap(resourcespath + "world_go.png"), getMessage("openstreamurl-menu-label"), lambda: self.openFile(firstFile)) else: - pathFound = MainWindow.FileSwitchManager.findFilepath(firstFile) + pathFound = self._syncplayClient.fileSwitch.findFilepath(firstFile) if pathFound: menu.addAction(QtGui.QPixmap(resourcespath + "film_go.png"), getMessage("openmedia-menu-label"), lambda: self.openFile(pathFound)) menu.addAction(QtGui.QPixmap(resourcespath + "delete.png"), getMessage("removefromplaylist-menu-label"), lambda: self.deleteSelectedPlaylistItems()) @@ -656,7 +516,7 @@ class MainWindow(QtGui.QMainWindow): if isURL(filename): menu.addAction(QtGui.QPixmap(resourcespath + "world_go.png"), getMessage("openusersstream-menu-label").format(shortUsername), lambda: self.openFile(filename)) else: - pathFound = MainWindow.FileSwitchManager.findFilepath(filename) + pathFound = self._syncplayClient.fileSwitch.findFilepath(filename) if pathFound: menu.addAction(QtGui.QPixmap(resourcespath + "film_go.png"), getMessage("openusersfile-menu-label").format(shortUsername), lambda: self.openFile(pathFound)) else: @@ -664,10 +524,11 @@ class MainWindow(QtGui.QMainWindow): menu.exec_(self.listTreeView.viewport().mapToGlobal(position)) + @needsClient def checkForDisabledDir(self): - if MainWindow.FileSwitchManager.disabledDir is not None and MainWindow.FileSwitchManager.currentWindow is not None: - self.showErrorMessage(getMessage("folder-search-timeout-error").format(MainWindow.FileSwitchManager.disabledDir)) - MainWindow.FileSwitchManager.disabledDir = None + if self._syncplayClient.fileSwitch.disabledDir is not None and self._syncplayClient.fileSwitch.currentWindow is not None: + self.showErrorMessage(getMessage("folder-search-timeout-error").format(self._syncplayClient.fileSwitch.disabledDir)) + self._syncplayClient.fileSwitch.disabledDir = None def updateListGeometry(self): try: @@ -697,13 +558,14 @@ class MainWindow(QtGui.QMainWindow): self.readyPushButton.blockSignals(False) self.updateReadyIcon() + @needsClient def playlistItemClicked(self, item): # TODO: Integrate into client.py code filename = item.data() if isURL(filename): self._syncplayClient._player.openFile(filename) else: - pathFound = MainWindow.FileSwitchManager.findFilepath(filename) + pathFound = self._syncplayClient.fileSwitch.findFilepath(filename) if pathFound: self._syncplayClient._player.openFile(pathFound) else: @@ -723,11 +585,11 @@ class MainWindow(QtGui.QMainWindow): if isURL(filename): self._syncplayClient._player.openFile(filename) else: - pathFound = MainWindow.FileSwitchManager.findFilepath(filename) + pathFound = self._syncplayClient.fileSwitch.findFilepath(filename) if pathFound: self._syncplayClient._player.openFile(pathFound) else: - MainWindow.FileSwitchManager.updateInfo() + self._syncplayClient.fileSwitch.updateInfo() self.showErrorMessage(getMessage("switch-file-not-found-error").format(filename)) @needsClient @@ -861,7 +723,7 @@ class MainWindow(QtGui.QMainWindow): if sys.platform.startswith('win'): fileName = fileName.replace("/", "\\") self.mediadirectory = os.path.dirname(fileName) - self.FileSwitchManager.setCurrentDirectory(self.mediadirectory) + self._syncplayClient.fileSwitch.setCurrentDirectory(self.mediadirectory) self.saveMediaBrowseSettings() self._syncplayClient._player.openFile(fileName) @@ -888,7 +750,7 @@ class MainWindow(QtGui.QMainWindow): if sys.platform.startswith('win'): fileName = fileName.replace("/", "\\") self.mediadirectory = os.path.dirname(fileName) - self.FileSwitchManager.setCurrentDirectory(self.mediadirectory) + self._syncplayClient.fileSwitch.setCurrentDirectory(self.mediadirectory) self.saveMediaBrowseSettings() self.addFileToPlaylist(fileName) self.updatingPlaylist = False @@ -1529,8 +1391,6 @@ class MainWindow(QtGui.QMainWindow): def __init__(self): super(MainWindow, self).__init__() - FileSwitchManager = self.FileSwitchManager() - FileSwitchManager.setWindow(self) self.newWatchlist = [] self.publicServerList = [] self.lastCheckedForUpdates = None From c77fc66f1561c95c63191f5588bbd166008aaa56 Mon Sep 17 00:00:00 2001 From: Et0h Date: Sun, 31 Jan 2016 15:32:10 +0000 Subject: [PATCH 084/134] Don't duplicate file check if currentDirectory is also in mediaFilesCache & allow checkForFileSwitchUpdate to be called without argument --- syncplay/client.py | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/syncplay/client.py b/syncplay/client.py index 5b62d21..918885c 100644 --- a/syncplay/client.py +++ b/syncplay/client.py @@ -1558,16 +1558,14 @@ class FileSwitchManager(object): self.mediaDirectories = mediaDirs self.updateInfo() - def checkForFileSwitchUpdate(self, bob=None): - if bob is not None: - print bob + def checkForFileSwitchUpdate(self): if self.newInfo: self.newInfo = False self.infoUpdated() def updateInfo(self): if len(self.filenameWatchlist) > 0 or len(self.mediaFilesCache) == 0 and self.currentlyUpdating == False: - threads.deferToThread(self._updateInfoThread).addCallback(self.checkForFileSwitchUpdate) + threads.deferToThread(self._updateInfoThread).addCallback(lambda x: self.checkForFileSwitchUpdate()) def setFilenameWatchlist(self, unfoundFilenames): self.filenameWatchlist = unfoundFilenames @@ -1622,8 +1620,9 @@ class FileSwitchManager(object): if self.mediaFilesCache is not None: for directory in self.mediaFilesCache: + if self.currentDirectory and self.currentDirectory == directory: + continue files = self.mediaFilesCache[directory] - if len(files) > 0 and filename in files: filepath = os.path.join(directory, filename) if os.path.isfile(filepath): From c1300464a58513465580c30f315395d3bfcb90a7 Mon Sep 17 00:00:00 2001 From: Et0h Date: Sun, 31 Jan 2016 16:11:39 +0000 Subject: [PATCH 085/134] Highlight missing files in playlist --- syncplay/ui/gui.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/syncplay/ui/gui.py b/syncplay/ui/gui.py index d5c5d9e..8fe227b 100644 --- a/syncplay/ui/gui.py +++ b/syncplay/ui/gui.py @@ -178,8 +178,14 @@ class MainWindow(QtGui.QMainWindow): def updatePlaylistIndexIcon(self): for item in xrange(self.count()): - isPlayingFilename = self.item(item).text() == self.playlistIndexFilename + itemFilename = self.item(item).text() + isPlayingFilename = itemFilename == self.playlistIndexFilename self.item(item).setData(Qt.UserRole + constants.PLAYLISTITEM_CURRENTLYPLAYING_ROLE, isPlayingFilename) + fileSwitchState = self.selfWindow.getFileSwitchState(itemFilename) + if fileSwitchState == constants.FILEITEM_SWITCH_NO_SWITCH and not isPlayingFilename: + self.item(item).setForeground(QtGui.QBrush(QtGui.QColor(constants.STYLE_DIFFERENTITEM_COLOR))) + else: + self.item(item).setForeground(QtGui.QBrush(QtGui.QColor(QtGui.QPalette.ColorRole(QtGui.QPalette.Text)))) self.forceUpdate() def setWindow(self, window): From b98ba598863552b2aa0fb2958019bdfb9ae520be Mon Sep 17 00:00:00 2001 From: Uriziel Date: Sun, 7 Feb 2016 01:04:55 +0100 Subject: [PATCH 086/134] Optimize server code a bit --- syncplay/server.py | 23 +++++++++++++++++------ 1 file changed, 17 insertions(+), 6 deletions(-) diff --git a/syncplay/server.py b/syncplay/server.py index 187dc06..be86be0 100644 --- a/syncplay/server.py +++ b/syncplay/server.py @@ -239,6 +239,8 @@ class Room(object): self._setBy = None self._playlist = [] self._playlistIndex = None + self.__lastUpdate = time.time() + self.__position = 0 def __str__(self, *args, **kwargs): return self.getName() @@ -247,18 +249,22 @@ class Room(object): return self._name def getPosition(self): - if self._watchers: + age = time.time() - self.__lastUpdate + if self._watchers and age > 1: watcher = min(self._watchers.values()) self._setBy = watcher - return watcher.getPosition() + self.__position = watcher.getPosition() + self.__lastUpdate = time.time() + return self.__position else: - return 0 + return self.__position + age if self._playState == self.STATE_PLAYING else 0 def setPaused(self, paused=STATE_PAUSED, setBy=None): self._playState = paused self._setBy = setBy def setPosition(self, position, setBy=None): + self.__position = position for watcher in self._watchers.itervalues(): watcher.setPosition(position) self._setBy = setBy @@ -283,6 +289,8 @@ class Room(object): return del self._watchers[watcher.getName()] watcher.setRoom(None) + if not self._watchers: + self.__position = 0 def isEmpty(self): return not bool(self._watchers) @@ -311,12 +319,15 @@ class ControlledRoom(Room): self._controllers = {} def getPosition(self): - if self._controllers: + age = time.time() - self.__lastUpdate + if self._controllers and age > 1: watcher = min(self._controllers.values()) self._setBy = watcher - return watcher.getPosition() + self.__position = watcher.getPosition() + self.__lastUpdate = time.time() + return self.__position else: - return 0 + return self.__position + age if self._playState == self.STATE_PLAYING else 0 def addController(self, watcher): self._controllers[watcher.getName()] = watcher From 0888ead8e68b2eea3711a91748054832e933936d Mon Sep 17 00:00:00 2001 From: Et0h Date: Sun, 7 Feb 2016 09:52:19 +0000 Subject: [PATCH 087/134] When watched file is found, update playlist and load file --- syncplay/client.py | 33 +++++++++++++++++++++++++-------- syncplay/ui/consoleUI.py | 3 +++ syncplay/ui/gui.py | 31 +++++++++++++++++++++++++------ 3 files changed, 53 insertions(+), 14 deletions(-) diff --git a/syncplay/client.py b/syncplay/client.py index 918885c..6b94ad2 100644 --- a/syncplay/client.py +++ b/syncplay/client.py @@ -466,6 +466,10 @@ class SyncplayClient(object): def openFile(self, filePath, resetPosition=False): self._player.openFile(filePath, resetPosition) + def fileSwitchFoundFiles(self): + self.ui.fileSwitchFoundFiles() + self.playlist.loadCurrentPlaylistIndex() + def setPlaylistIndex(self, index): self._protocol.setPlaylistIndex(index) @@ -1257,6 +1261,9 @@ class UiManager(object): def setPlaylistIndexFilename(self, filename): self.__ui.setPlaylistIndexFilename(filename) + def fileSwitchFoundFiles(self): + self.__ui.fileSwitchFoundFiles() + def showDebugMessage(self, message): if constants.DEBUG_MODE and message.rstrip(): sys.stderr.write("{}{}\n".format(time.strftime(constants.UI_TIME_FORMAT, time.localtime()),message.rstrip())) @@ -1390,7 +1397,7 @@ class SyncplayPlaylist(): self._ui.showErrorMessage(getMessage("cannot-add-unsafe-path-error").format(filename)) return else: - path = self._client.fileSwitch.findFilepath(filename) + path = self._client.fileSwitch.findFilepath(filename, highPriority=True) if path: self._client.openFile(path, resetPosition) else: @@ -1533,7 +1540,6 @@ class SyncplayPlaylist(): class FileSwitchManager(object): def __init__(self, client): self._client = client - self.fileSwitchTimer = task.LoopingCall(self.updateInfo) self.mediaFilesCache = {} self.filenameWatchlist = [] self.currentDirectory = None @@ -1543,8 +1549,8 @@ class FileSwitchManager(object): self.disabledDir = None self.newInfo = False self.currentlyUpdating = False - self.newInfo = False self.newWatchlist = [] + self.fileSwitchTimer = task.LoopingCall(self.updateInfo) self.fileSwitchTimer.start(constants.FOLDER_SEARCH_DOUBLE_CHECK_INTERVAL, True) def setClient(self, newClient): @@ -1604,12 +1610,9 @@ class FileSwitchManager(object): def infoUpdated(self): if self.areWatchedFilenamesInCache() or self.areWatchedFilenamesInCurrentDir(): - self.updateListOfWhoIsPlayingWhat() + self._client.fileSwitchFoundFiles() - def updateListOfWhoIsPlayingWhat(self): - self._client.showUserList() - - def findFilepath(self, filename): + def findFilepath(self, filename, highPriority=False): if filename is None: return @@ -1628,6 +1631,20 @@ class FileSwitchManager(object): if os.path.isfile(filepath): return filepath + if highPriority and self.folderSearchEnabled: + directoryList = self.mediaDirectories + startTime = time.time() + if filename and directoryList: + for directory in directoryList: + for root, dirs, files in os.walk(directory): + if filename in files: + return os.path.join(root,filename) + if time.time() - startTime > constants.FOLDER_SEARCH_TIMEOUT: + self.disabledDir = directory + self.folderSearchEnabled = False + return None + return None + def areWatchedFilenamesInCurrentDir(self): if self.filenameWatchlist is not None and self.currentDirectory is not None: for filename in self.filenameWatchlist: diff --git a/syncplay/ui/consoleUI.py b/syncplay/ui/consoleUI.py index 51e9f7d..242482e 100644 --- a/syncplay/ui/consoleUI.py +++ b/syncplay/ui/consoleUI.py @@ -83,6 +83,9 @@ class ConsoleUI(threading.Thread): def userListChange(self): pass + def fileSwitchFoundFiles(self): + pass + def showMessage(self, message, noTimestamp=False): message = message.encode(sys.stdout.encoding, 'replace') if noTimestamp: diff --git a/syncplay/ui/gui.py b/syncplay/ui/gui.py index 8fe227b..5e0862d 100644 --- a/syncplay/ui/gui.py +++ b/syncplay/ui/gui.py @@ -181,11 +181,12 @@ class MainWindow(QtGui.QMainWindow): itemFilename = self.item(item).text() isPlayingFilename = itemFilename == self.playlistIndexFilename self.item(item).setData(Qt.UserRole + constants.PLAYLISTITEM_CURRENTLYPLAYING_ROLE, isPlayingFilename) - fileSwitchState = self.selfWindow.getFileSwitchState(itemFilename) - if fileSwitchState == constants.FILEITEM_SWITCH_NO_SWITCH and not isPlayingFilename: - self.item(item).setForeground(QtGui.QBrush(QtGui.QColor(constants.STYLE_DIFFERENTITEM_COLOR))) - else: + fileIsAvailable = self.selfWindow.isFileAvailable(itemFilename) + if fileIsAvailable: self.item(item).setForeground(QtGui.QBrush(QtGui.QColor(QtGui.QPalette.ColorRole(QtGui.QPalette.Text)))) + else: + self.item(item).setForeground(QtGui.QBrush(QtGui.QColor(constants.STYLE_DIFFERENTITEM_COLOR))) + self.selfWindow._syncplayClient.fileSwitch.setFilenameWatchlist(self.selfWindow.newWatchlist) self.forceUpdate() def setWindow(self, window): @@ -346,6 +347,20 @@ class MainWindow(QtGui.QMainWindow): self.newWatchlist.extend([filename]) return constants.FILEITEM_SWITCH_NO_SWITCH + @needsClient + def isFileAvailable(self, filename): + if filename: + if filename == getMessage("nofile-note"): + return None + if isURL(filename): + return True + elif filename not in self.newWatchlist: + if self._syncplayClient.fileSwitch.findFilepath(filename): + return True + else: + self.newWatchlist.extend([filename]) + return False + @needsClient def showUserList(self, currentUser, rooms): self._usertreebuffer = QtGui.QStandardItemModel() @@ -571,7 +586,7 @@ class MainWindow(QtGui.QMainWindow): if isURL(filename): self._syncplayClient._player.openFile(filename) else: - pathFound = self._syncplayClient.fileSwitch.findFilepath(filename) + pathFound = self._syncplayClient.fileSwitch.findFilepath(filename, highPriority=True) if pathFound: self._syncplayClient._player.openFile(pathFound) else: @@ -591,7 +606,7 @@ class MainWindow(QtGui.QMainWindow): if isURL(filename): self._syncplayClient._player.openFile(filename) else: - pathFound = self._syncplayClient.fileSwitch.findFilepath(filename) + pathFound = self._syncplayClient.fileSwitch.findFilepath(filename, highPriority=True) if pathFound: self._syncplayClient._player.openFile(pathFound) else: @@ -602,6 +617,10 @@ class MainWindow(QtGui.QMainWindow): def userListChange(self): self._syncplayClient.showUserList() + def fileSwitchFoundFiles(self): + self._syncplayClient.showUserList() + self.playlist.updatePlaylistIndexIcon() + def updateRoomName(self, room=""): self.roomInput.setText(room) From 1194cce05a4c54841fb42c3d9976698c1840d545 Mon Sep 17 00:00:00 2001 From: Et0h Date: Sun, 7 Feb 2016 13:10:56 +0000 Subject: [PATCH 088/134] Fix server rewind position to 0 on pause bug --- syncplay/server.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/syncplay/server.py b/syncplay/server.py index be86be0..cb26dc8 100644 --- a/syncplay/server.py +++ b/syncplay/server.py @@ -257,7 +257,7 @@ class Room(object): self.__lastUpdate = time.time() return self.__position else: - return self.__position + age if self._playState == self.STATE_PLAYING else 0 + return self.__position + (age if self._playState == self.STATE_PLAYING else 0) def setPaused(self, paused=STATE_PAUSED, setBy=None): self._playState = paused @@ -327,7 +327,7 @@ class ControlledRoom(Room): self.__lastUpdate = time.time() return self.__position else: - return self.__position + age if self._playState == self.STATE_PLAYING else 0 + return self.__position + (age if self._playState == self.STATE_PLAYING else 0) def addController(self, watcher): self._controllers[watcher.getName()] = watcher From e2617088b8599a760e7a17f079c777b2a816a9e4 Mon Sep 17 00:00:00 2001 From: Et0h Date: Sun, 7 Feb 2016 15:38:50 +0000 Subject: [PATCH 089/134] Fix server error when trying to getPosition when position is none --- syncplay/server.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/syncplay/server.py b/syncplay/server.py index cb26dc8..73ccd38 100644 --- a/syncplay/server.py +++ b/syncplay/server.py @@ -256,8 +256,10 @@ class Room(object): self.__position = watcher.getPosition() self.__lastUpdate = time.time() return self.__position - else: + elif self.__position is not None: return self.__position + (age if self._playState == self.STATE_PLAYING else 0) + else: + return 0 def setPaused(self, paused=STATE_PAUSED, setBy=None): self._playState = paused @@ -326,8 +328,10 @@ class ControlledRoom(Room): self.__position = watcher.getPosition() self.__lastUpdate = time.time() return self.__position - else: + elif self.__position is not None: return self.__position + (age if self._playState == self.STATE_PLAYING else 0) + else: + return 0 def addController(self, watcher): self._controllers[watcher.getName()] = watcher From c40b9e5aff075c353414fcaac256b10630e09936 Mon Sep 17 00:00:00 2001 From: Et0h Date: Thu, 18 Feb 2016 14:06:50 +0000 Subject: [PATCH 090/134] PLAYLIST_LOOPS is redundant as it is now a config option --- syncplay/constants.py | 1 - 1 file changed, 1 deletion(-) diff --git a/syncplay/constants.py b/syncplay/constants.py index 568520b..267ab1c 100644 --- a/syncplay/constants.py +++ b/syncplay/constants.py @@ -19,7 +19,6 @@ FALLBACK_INITIAL_LANGUAGE = "en" FALLBACK_PUBLIC_SYNCPLAY_SERVERS = [[u'syncplay.pl:8995 (France)', u'syncplay.pl:8995'],[u'syncplay.pl:8996 (France)', u'syncplay.pl:8996'],[u'syncplay.pl:8997 (France)', u'syncplay.pl:8997'],[u'syncplay.pl:8998 (France)', u'syncplay.pl:8998'],[u'syncplay.pl:8999 (France)', u'syncplay.pl:8999']] PLAYLIST_LOAD_NEXT_FILE_MINIMUM_LENGTH = 10 # Seconds PLAYLIST_LOAD_NEXT_FILE_TIME_FROM_END_THRESHOLD = 5 # Seconds (only triggered if file is paused, e.g. due to EOF) -PLAYLIST_LOOPS = True #Overriden by config SHOW_OSD = True # Sends Syncplay messages to media player OSD From 5a36a2416a13aed7fb55621492ef312a804d0618 Mon Sep 17 00:00:00 2001 From: Uriziel Date: Thu, 3 Mar 2016 20:13:24 +0100 Subject: [PATCH 091/134] Revert "Merge branch 'player-cookie' of https://github.com/tari/syncplay into sharedplaylists" This reverts commit 509dfea336e48f7ccd848e44ebe8bb60e6332822, reversing changes made to 5ce752cbef589ade76ae5dc1bcfafc7c9e34c0ea. --- syncplay/client.py | 47 ++++++------------------------ syncplay/constants.py | 1 - syncplay/messages.py | 3 -- syncplay/players/basePlayer.py | 4 +-- syncplay/players/mpc.py | 17 +++++------ syncplay/players/mplayer.py | 4 +-- syncplay/players/mpv.py | 8 ++--- syncplay/players/vlc.py | 10 ++----- syncplay/ui/ConfigurationGetter.py | 7 ++--- syncplay/ui/GuiConfiguration.py | 15 ---------- 10 files changed, 28 insertions(+), 88 deletions(-) diff --git a/syncplay/client.py b/syncplay/client.py index 6b94ad2..23f58a0 100644 --- a/syncplay/client.py +++ b/syncplay/client.py @@ -112,8 +112,6 @@ class SyncplayClient(object): self.autoPlay = False self.autoPlayThreshold = None - self._lastPlayerCommand = time.time() - self.autoplayTimer = task.LoopingCall(self.autoplayCountdown) self.autoplayTimeLeft = constants.AUTOPLAY_DELAY @@ -153,16 +151,6 @@ class SyncplayClient(object): def playerIsNotReady(self): return self._player is None - def _playerRequest(self, f, *args, **kwargs): - """Send a request with cookie to the player.""" - kwargs['cookie'] = time.time() - f(*args, **kwargs) - - def _playerCommand(self, f, *args, **kwargs): - """Send a command to the player, affecting cookie freshness.""" - self._lastPlayerCommand = time.time() - f(*args, **kwargs) - def scheduleAskPlayer(self, when=constants.PLAYER_ASK_DELAY): self._askPlayerTimer = task.LoopingCall(self.askPlayer) self._askPlayerTimer.start(when) @@ -171,7 +159,7 @@ class SyncplayClient(object): if not self._running: return if self._player: - self._playerRequest(self._player.askForStatus) + self._player.askForStatus() self.checkIfConnected() def checkIfConnected(self): @@ -192,22 +180,11 @@ class SyncplayClient(object): def rewindFile(self): self.setPosition(0) - def _ignoringPlayerStatus(self, cookie=None): - if cookie is None: - cookie = time.time() - return cookie < self._lastPlayerCommand + self._config['playerCommandDelay'] - - def updatePlayerStatus(self, paused, position, cookie=None): - # Ignore status report if the cookie is stale - if self._ignoringPlayerStatus(cookie): - self.ui.showDebugMessage('Ignoring stale player status with cookie {}'.format(cookie)) - return - + def updatePlayerStatus(self, paused, position): position -= self.getUserOffset() pauseChange, seeked = self._determinePlayerStateChange(paused, position) self._playerPosition = position self._playerPaused = paused - currentLength = self.userlist.currentUser.file["duration"] if self.userlist.currentUser.file else 0 if pauseChange and paused and currentLength > constants.PLAYLIST_LOAD_NEXT_FILE_MINIMUM_LENGTH\ and abs(position - currentLength ) < constants.PLAYLIST_LOAD_NEXT_FILE_TIME_FROM_END_THRESHOLD: @@ -224,7 +201,7 @@ class SyncplayClient(object): def _toggleReady(self, pauseChange, paused): if not self.userlist.currentUser.canControl(): - self._playerCommand(self._player.setPaused, self._globalPaused) + self._player.setPaused(self._globalPaused) self.toggleReady(manuallyInitiated=True) self._playerPaused = self._globalPaused pauseChange = False @@ -234,7 +211,7 @@ class SyncplayClient(object): self.ui.showMessage(getMessage("set-as-not-ready-notification")) elif not paused and not self.instaplayConditionsMet(): paused = True - self._playerCommand(self._player.setPaused, paused) + self._player.setPaused(paused) self._playerPaused = paused self.changeReadyState(True, manuallyInitiated=True) pauseChange = False @@ -262,7 +239,7 @@ class SyncplayClient(object): def _initPlayerState(self, position, paused): if self.userlist.currentUser.file: self.setPosition(position) - self._playerCommand(self._player.setPaused, paused) + self._player.setPaused(paused) madeChangeOnPlayer = True return madeChangeOnPlayer @@ -290,21 +267,16 @@ class SyncplayClient(object): def _serverUnpaused(self, setBy): hideFromOSD = not constants.SHOW_SAME_ROOM_OSD - # In high-player-latency situations we might report our state back to - # the server before any player status is accepted as fresh. Override - # the locally-stored playback state. - self._playerPaused = False - self._playerCommand(self._player.setPaused, False) + self._player.setPaused(False) madeChangeOnPlayer = True self.ui.showMessage(getMessage("unpause-notification").format(setBy), hideFromOSD) return madeChangeOnPlayer def _serverPaused(self, setBy): hideFromOSD = not constants.SHOW_SAME_ROOM_OSD - self._playerPaused = True if constants.SYNC_ON_PAUSE and self.getUsername() <> setBy: self.setPosition(self.getGlobalPosition()) - self._playerCommand(self._player.setPaused, True) + self._player.setPaused(True) madeChangeOnPlayer = True if (self.lastLeftTime < time.time() - constants.OSD_DURATION) or (hideFromOSD == True): self.ui.showMessage(getMessage("pause-notification").format(setBy), hideFromOSD) @@ -569,19 +541,18 @@ class SyncplayClient(object): def setPosition(self, position): if self._lastPlayerUpdate: self._lastPlayerUpdate = time.time() - self._playerPosition = position position += self.getUserOffset() if self._player and self.userlist.currentUser.file: if position < 0: position = 0 self._protocol.sendState(self.getPlayerPosition(), self.getPlayerPaused(), True, None, True) - self._playerCommand(self._player.setPosition, position) + self._player.setPosition(position) def setPaused(self, paused): if self._player and self.userlist.currentUser.file: if self._lastPlayerUpdate and not paused: self._lastPlayerUpdate = time.time() - self._playerCommand(self._player.setPaused, paused) + self._player.setPaused(paused) def start(self, host, port): if self._running: diff --git a/syncplay/constants.py b/syncplay/constants.py index 267ab1c..ec0959c 100644 --- a/syncplay/constants.py +++ b/syncplay/constants.py @@ -51,7 +51,6 @@ SERVER_STATE_INTERVAL = 1 WARNING_OSD_MESSAGES_LOOP_INTERVAL = 1 AUTOPLAY_DELAY = 3.0 SYNC_ON_PAUSE = True # Client seek to global position - subtitles may disappear on some media players -DEFAULT_PLAYER_COMMAND_DELAY = 0.05 # Options for the File Switch feature: FOLDER_SEARCH_TIMEOUT = 60.0 # Secs - How long to wait until searches in folder to update cache are aborted (may be longer than this if hard drive needs to spin up) diff --git a/syncplay/messages.py b/syncplay/messages.py index 3624500..51211cf 100755 --- a/syncplay/messages.py +++ b/syncplay/messages.py @@ -200,9 +200,6 @@ en = { "readyatstart-label" : "Set me as 'ready to watch' by default", "forceguiprompt-label" : "Don't always show the Syncplay configuration window", # (Inverted) "showosd-label" : "Enable OSD Messages", - "playercommanddelay-title" : "Player latency compensation", - "playercommanddelay-label" : "Seconds to ignore player status after commands", - "playercommanddelay-tooltip" : "Larger values are less likely to spuriously (un)pause but tend to sync less accurately.", "showosdwarnings-label" : "Include warnings (e.g. when files are different, users not ready)", "showsameroomosd-label" : "Include events in your room", diff --git a/syncplay/players/basePlayer.py b/syncplay/players/basePlayer.py index 37b81d7..c8fdcc2 100644 --- a/syncplay/players/basePlayer.py +++ b/syncplay/players/basePlayer.py @@ -6,7 +6,7 @@ class BasePlayer(object): execute updatePlayerStatus(paused, position) on client Given the arguments: boolean paused and float position in seconds ''' - def askForStatus(self, cookie=None): + def askForStatus(self): raise NotImplementedError() ''' @@ -121,4 +121,4 @@ class DummyPlayer(BasePlayer): @staticmethod def getPlayerPathErrors(playerPath, filePath): - return None + return None \ No newline at end of file diff --git a/syncplay/players/mpc.py b/syncplay/players/mpc.py index 75e0ac7..6ab1452 100644 --- a/syncplay/players/mpc.py +++ b/syncplay/players/mpc.py @@ -2,8 +2,7 @@ import time import threading import thread -# noinspection PyUnresolvedReferences -import win32con, win32api, win32gui, ctypes, ctypes.wintypes +import win32con, win32api, win32gui, ctypes, ctypes.wintypes #@UnresolvedImport @UnusedImport from functools import wraps from syncplay.players.basePlayer import BasePlayer import re @@ -420,7 +419,7 @@ class MPCHCAPIPlayer(BasePlayer): self.__positionUpdate.wait(constants.MPC_LOCK_WAIT_TIME) return self._mpcApi.lastFilePosition - def askForStatus(self, cookie=None): + def askForStatus(self): try: if self._mpcApi.filePlaying and self.__preventAsking.wait(0) and self.__fileUpdate.acquire(0): self.__fileUpdate.release() @@ -428,17 +427,15 @@ class MPCHCAPIPlayer(BasePlayer): paused = self._mpcApi.isPaused() position = float(position) if self.__preventAsking.wait(0) and self.__fileUpdate.acquire(0): - self.__client.updatePlayerStatus(paused, position, cookie=cookie) + self.__client.updatePlayerStatus(paused, position) self.__fileUpdate.release() else: - self.__echoGlobalStatus(cookie) + self.__echoGlobalStatus() except MpcHcApi.PlayerNotReadyException: - self.__echoGlobalStatus(cookie) + self.__echoGlobalStatus() - def __echoGlobalStatus(self, cookie): - self.__client.updatePlayerStatus(self.__client.getGlobalPaused(), - self.__client.getGlobalPosition(), - cookie=cookie) + def __echoGlobalStatus(self): + self.__client.updatePlayerStatus(self.__client.getGlobalPaused(), self.__client.getGlobalPosition()) def __forcePause(self): for _ in xrange(constants.MPC_MAX_RETRIES): diff --git a/syncplay/players/mplayer.py b/syncplay/players/mplayer.py index 03a4d58..b175a96 100644 --- a/syncplay/players/mplayer.py +++ b/syncplay/players/mplayer.py @@ -72,14 +72,14 @@ class MplayerPlayer(BasePlayer): self.reactor.callLater(0, self._client.initPlayer, self) self._onFileUpdate() - def askForStatus(self, cookie=None): + def askForStatus(self): self._positionAsk.clear() self._pausedAsk.clear() self._getPaused() self._getPosition() self._positionAsk.wait() self._pausedAsk.wait() - self._client.updatePlayerStatus(self._paused, self._position, cookie=cookie) + self._client.updatePlayerStatus(self._paused, self._position) def _setProperty(self, property_, value): self._listener.sendLine("set_property {} {}".format(property_, value)) diff --git a/syncplay/players/mpv.py b/syncplay/players/mpv.py index a079b32..a0b30e0 100644 --- a/syncplay/players/mpv.py +++ b/syncplay/players/mpv.py @@ -160,15 +160,13 @@ class NewMpvPlayer(OldMpvPlayer): else: self._paused = self._client.getGlobalPaused() - def askForStatus(self, cookie=None): + def askForStatus(self): self._positionAsk.clear() self._pausedAsk.clear() self._getPausedAndPosition() self._positionAsk.wait(constants.MPV_LOCK_WAIT_TIME) self._pausedAsk.wait(constants.MPV_LOCK_WAIT_TIME) - self._client.updatePlayerStatus(self._paused if self.fileLoaded else self._client.getGlobalPaused(), - self.getCalculatedPosition(), - cookie=cookie) + self._client.updatePlayerStatus(self._paused if self.fileLoaded else self._client.getGlobalPaused(), self.getCalculatedPosition()) def _getPausedAndPosition(self): self._listener.sendLine(u"print_text ANS_pause=${pause}\r\nprint_text ANS_time-pos=${=time-pos}") @@ -234,4 +232,4 @@ class NewMpvPlayer(OldMpvPlayer): if self.fileLoaded == True and self.lastLoadedTime != None and time.time() > (self.lastLoadedTime + constants.MPV_NEWFILE_IGNORE_TIME): return True else: - return False + return False \ No newline at end of file diff --git a/syncplay/players/vlc.py b/syncplay/players/vlc.py index a6cdcff..bdeba7d 100644 --- a/syncplay/players/vlc.py +++ b/syncplay/players/vlc.py @@ -83,20 +83,16 @@ class VlcPlayer(BasePlayer): self.setPaused(self._client.getGlobalPaused()) self.setPosition(self._client.getGlobalPosition()) - def askForStatus(self, cookie=None): + def askForStatus(self): self._filechanged = False self._positionAsk.clear() self._pausedAsk.clear() self._listener.sendLine(".") if self._filename and not self._filechanged: self._positionAsk.wait(constants.PLAYER_ASK_DELAY) - self._client.updatePlayerStatus(self._paused, - self.getCalculatedPosition(), - cookie=cookie) + self._client.updatePlayerStatus(self._paused, self.getCalculatedPosition()) else: - self._client.updatePlayerStatus(self._client.getGlobalPaused(), - self._client.getGlobalPosition(), - cookie=cookie) + self._client.updatePlayerStatus(self._client.getGlobalPaused(), self._client.getGlobalPosition()) def getCalculatedPosition(self): if self._lastVLCPositionUpdate is None: diff --git a/syncplay/ui/ConfigurationGetter.py b/syncplay/ui/ConfigurationGetter.py index abe21c8..0165a34 100755 --- a/syncplay/ui/ConfigurationGetter.py +++ b/syncplay/ui/ConfigurationGetter.py @@ -66,8 +66,7 @@ class ConfigurationGetter(object): "showSameRoomOSD" : True, "showNonControllerOSD" : False, "showContactInfo" : True, - "showDurationNotification" : True, - "playerCommandDelay": constants.DEFAULT_PLAYER_COMMAND_DELAY + "showDurationNotification" : True } self._defaultConfig = self._config.copy() @@ -124,7 +123,6 @@ class ConfigurationGetter(object): "rewindThreshold", "fastforwardThreshold", "autoplayMinUsers", - "playerCommandDelay", ] self._iniStructure = { @@ -138,8 +136,7 @@ class ConfigurationGetter(object): "filesizePrivacyMode", "unpauseAction", "pauseOnLeave", "readyAtStart", "autoplayMinUsers", "autoplayInitialState", "mediaSearchDirectories", - "sharedPlaylistEnabled", "playerCommandDelay", - "loopAtEndOfPlaylist"], + "sharedPlaylistEnabled", "loopAtEndOfPlaylist"], "gui": ["showOSD", "showOSDWarnings", "showSlowdownOSD", "showDifferentRoomOSD", "showSameRoomOSD", "showNonControllerOSD", "showDurationNotification"], diff --git a/syncplay/ui/GuiConfiguration.py b/syncplay/ui/GuiConfiguration.py index 82ef13a..b0ac598 100644 --- a/syncplay/ui/GuiConfiguration.py +++ b/syncplay/ui/GuiConfiguration.py @@ -416,8 +416,6 @@ class ConfigDialog(QtGui.QDialog): widget.setChecked(True) elif isinstance(widget, QLineEdit): widget.setText(self.config[valueName]) - elif isinstance(widget, QDoubleSpinBox): - widget.setValue(self.config[valueName]) def saveValues(self, widget): valueName = str(widget.objectName()) @@ -440,8 +438,6 @@ class ConfigDialog(QtGui.QDialog): self.config[radioName] = radioValue elif isinstance(widget, QLineEdit): self.config[valueName] = widget.text() - elif isinstance(widget, QDoubleSpinBox): - self.config[valueName] = widget.value() def connectChildren(self, widget): widgetName = str(widget.objectName()) @@ -737,10 +733,6 @@ class ConfigDialog(QtGui.QDialog): self.rewindCheckbox.setObjectName("rewindOnDesync") self.fastforwardCheckbox = QCheckBox(getMessage("fastforwardondesync-label")) self.fastforwardCheckbox.setObjectName("fastforwardOnDesync") - self.commandDelaySpinbox = QDoubleSpinBox() - self.commandDelaySpinbox.setObjectName("playerCommandDelay") - self.commandDelaySpinbox.setMaximum(10) - self.commandDelaySpinbox.setSingleStep(.1) self.desyncSettingsLayout = QtGui.QGridLayout() self.desyncSettingsLayout.setSpacing(2) @@ -769,17 +761,10 @@ class ConfigDialog(QtGui.QDialog): self.othersyncSettingsLayout.setAlignment(Qt.AlignLeft) self.othersyncSettingsLayout.addWidget(self.fastforwardCheckbox, 3, 0,1,2, Qt.AlignLeft) - self.playerLatencyGroup = QtGui.QGroupBox(getMessage("playercommanddelay-title")) - self.playerLatencyLayout = QtGui.QHBoxLayout() - self.playerLatencyGroup.setLayout(self.playerLatencyLayout) - self.playerLatencyLayout.addWidget(self.commandDelaySpinbox) - self.playerLatencyLayout.addWidget(QLabel(getMessage("playercommanddelay-label"))) - self.othersyncSettingsGroup.setLayout(self.othersyncSettingsLayout) self.othersyncSettingsGroup.setMaximumHeight(self.othersyncSettingsGroup.minimumSizeHint().height()) self.syncSettingsLayout.addWidget(self.othersyncSettingsGroup) self.syncSettingsLayout.addWidget(self.desyncSettingsGroup) - self.syncSettingsLayout.addWidget(self.playerLatencyGroup) self.syncSettingsFrame.setLayout(self.syncSettingsLayout) self.desyncSettingsGroup.setMaximumHeight(self.desyncSettingsGroup.minimumSizeHint().height()) self.syncSettingsLayout.setAlignment(Qt.AlignTop) From 0e5e8eafa27bf2c21e19d385c4e1a168ea0401b1 Mon Sep 17 00:00:00 2001 From: Et0h Date: Thu, 3 Mar 2016 19:38:25 +0000 Subject: [PATCH 092/134] Upver to release 24 --- syncplay/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/syncplay/__init__.py b/syncplay/__init__.py index 9e38415..38cf47b 100644 --- a/syncplay/__init__.py +++ b/syncplay/__init__.py @@ -1,4 +1,4 @@ version = '1.4.0' milestone = 'Yoitsu' -release_number = '23' +release_number = '24' projectURL = 'http://syncplay.pl/' From fbfb5c039ca9b6d6ca30e9ab5d4d15c9b9d99452 Mon Sep 17 00:00:00 2001 From: Et0h Date: Thu, 3 Mar 2016 19:43:49 +0000 Subject: [PATCH 093/134] Undelete Russian nostore-tooltip message --- syncplay/messages_ru.py | 1 + 1 file changed, 1 insertion(+) diff --git a/syncplay/messages_ru.py b/syncplay/messages_ru.py index b24fa70..ba7cba6 100644 --- a/syncplay/messages_ru.py +++ b/syncplay/messages_ru.py @@ -308,6 +308,7 @@ ru = { "pauseonleave-tooltip" : u"Приостановить воспроизведение, если Вы покинули комнату или кто-то из зрителей отключился от сервера.", "readyatstart-tooltip" : u"Отметить Вас готовым к просмотру сразу же (по умолчанию Вы отмечены не готовым)", "forceguiprompt-tooltip" : u"Окно настройки не будет отображаться при открытии файла в Syncplay.", # (Inverted) + "nostore-tooltip" : u"Запустить Syncplay с данной конфигурацией, но не сохранять изменения навсегда.", "rewindondesync-tooltip" : u"Перематывать назад, когда это необходимо для синхронизации. Отключение этой опции может привести к большим рассинхронизациям!", "fastforwardondesync-tooltip" : u"Перематывать вперед при рассинхронизации с оператором комнаты (или если включена опция 'Никогда не замедлять и не перематывать видео другим').", "showosd-tooltip" : u"Отправлять сообщения Syncplay в видеопроигрыватель и отображать их поверх видео (OSD - On Screen Display).", From 7b75a04b463dc8cde033ff6aa24cedc6e24fba5f Mon Sep 17 00:00:00 2001 From: Et0h Date: Sat, 7 May 2016 08:34:33 +0100 Subject: [PATCH 094/134] Add youtu.be and Vimeo as safe URIs --- syncplay/constants.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/syncplay/constants.py b/syncplay/constants.py index ec0959c..a348874 100644 --- a/syncplay/constants.py +++ b/syncplay/constants.py @@ -188,4 +188,4 @@ SYNCPLAY_DOWNLOAD_URL = "http://syncplay.pl/download/" SYNCPLAY_PUBLIC_SERVER_LIST_URL = u"http://syncplay.pl/listpublicservers?{}" # Params PRIVATE_FILE_FIELDS = ["path"] -SAFE_URIS = [u"http://www.youtube.com/", u"https://www.youtube.com/"] # TODO: Move into config \ No newline at end of file +SAFE_URIS = [u"http://www.youtube.com/", u"https://www.youtube.com/", u"http://www.vimeo.com/", u"https://www.vimeo.com/",u"http://player.vimeo.com",u"https://player.vimeo.com",u"http://www.youtu.be/", u"https://www.youtu.be/",u"http://youtu.be/", u"https://youtu.be/"] # TODO: Move into config \ No newline at end of file From 5d28692996aeafe4c50947d9d0f5a43c3e2a11a4 Mon Sep 17 00:00:00 2001 From: Et0h Date: Sat, 7 May 2016 09:25:32 +0100 Subject: [PATCH 095/134] Shared playlist auto-switch to URIs by default, but only to user-specified 'trustedURIs' if 'onlySwitchToTrustedURIs' is True --- syncplay/client.py | 19 ++++++++++++++----- syncplay/constants.py | 3 +-- syncplay/messages_de.py | 2 +- syncplay/messages_en.py | 2 +- syncplay/messages_ru.py | 2 +- syncplay/ui/ConfigurationGetter.py | 9 +++++++-- 6 files changed, 25 insertions(+), 12 deletions(-) diff --git a/syncplay/client.py b/syncplay/client.py index 23f58a0..9e72aa8 100644 --- a/syncplay/client.py +++ b/syncplay/client.py @@ -435,6 +435,16 @@ class SyncplayClient(object): self.sendFile() self.playlist.changeToPlaylistIndexFromFilename(filename) + def isURITrusted(self, URIToTest): + if self._config['onlySwitchToTrustedURIs']: + if self._config['trustedURIs']: + for URI in self._config['trustedURIs']: + if URIToTest.startswith(URI): + return True + return False + else: + return True + def openFile(self, filePath, resetPosition=False): self._player.openFile(filePath, resetPosition) @@ -1361,11 +1371,10 @@ class SyncplayPlaylist(): try: filename = self._playlist[index] if utils.isURL(filename): - for URI in constants.SAFE_URIS: - if filename.startswith(URI): - self._client.openFile(filename, resetPosition=resetPosition) - return - self._ui.showErrorMessage(getMessage("cannot-add-unsafe-path-error").format(filename)) + if self._client.isURITrusted(filename): + self._client.openFile(filename, resetPosition=resetPosition) + else: + self._ui.showErrorMessage(getMessage("cannot-add-unsafe-path-error").format(filename)) return else: path = self._client.fileSwitch.findFilepath(filename, highPriority=True) diff --git a/syncplay/constants.py b/syncplay/constants.py index a348874..107b538 100644 --- a/syncplay/constants.py +++ b/syncplay/constants.py @@ -187,5 +187,4 @@ SYNCPLAY_UPDATE_URL = u"http://syncplay.pl/checkforupdate?{}" # Params SYNCPLAY_DOWNLOAD_URL = "http://syncplay.pl/download/" SYNCPLAY_PUBLIC_SERVER_LIST_URL = u"http://syncplay.pl/listpublicservers?{}" # Params -PRIVATE_FILE_FIELDS = ["path"] -SAFE_URIS = [u"http://www.youtube.com/", u"https://www.youtube.com/", u"http://www.vimeo.com/", u"https://www.vimeo.com/",u"http://player.vimeo.com",u"https://player.vimeo.com",u"http://www.youtu.be/", u"https://www.youtu.be/",u"http://youtu.be/", u"https://youtu.be/"] # TODO: Move into config \ No newline at end of file +PRIVATE_FILE_FIELDS = ["path"] \ No newline at end of file diff --git a/syncplay/messages_de.py b/syncplay/messages_de.py index 2dc25ce..56ba962 100644 --- a/syncplay/messages_de.py +++ b/syncplay/messages_de.py @@ -375,7 +375,7 @@ de = { "playlist-contents-changed-notification" : u"{} updated the playlist", # Username "cannot-find-file-for-playlist-switch-error" : u"Could not find file {} for playlist switch!", # Filename "cannot-add-duplicate-error" : u"Could not add second entry for '{}' to the playlist as no duplicates are allowed.", #Filename - "cannot-add-unsafe-path-error" : u"Could not load {} because it is not known as a safe path.", # Filename + "cannot-add-unsafe-path-error" : u"Could not load {} because it is not known as a trusted path.", # Filename "sharedplaylistenabled-label" : u"Enable shared playlists", "removefromplaylist-menu-label" : u"Remove from playlist", "shuffleplaylist-menuu-label" : u"Shuffle playlist", diff --git a/syncplay/messages_en.py b/syncplay/messages_en.py index ab92c1f..3ae0448 100644 --- a/syncplay/messages_en.py +++ b/syncplay/messages_en.py @@ -376,7 +376,7 @@ en = { "playlist-contents-changed-notification" : u"{} updated the playlist", # Username "cannot-find-file-for-playlist-switch-error" : u"Could not find file {} for playlist switch!", # Filename "cannot-add-duplicate-error" : u"Could not add second entry for '{}' to the playlist as no duplicates are allowed.", #Filename - "cannot-add-unsafe-path-error" : u"Could not load {} because it is not known as a safe path.", # Filename + "cannot-add-unsafe-path-error" : u"Could not load {} because it is not known as a trusted path.", # Filename "sharedplaylistenabled-label" : u"Enable shared playlists", "removefromplaylist-menu-label" : u"Remove from playlist", "shuffleplaylist-menuu-label" : u"Shuffle playlist", diff --git a/syncplay/messages_ru.py b/syncplay/messages_ru.py index ba7cba6..5cdca6d 100644 --- a/syncplay/messages_ru.py +++ b/syncplay/messages_ru.py @@ -375,7 +375,7 @@ ru = { "playlist-contents-changed-notification" : u"{} updated the playlist", # Username "cannot-find-file-for-playlist-switch-error" : u"Could not find file {} for playlist switch!", # Filename "cannot-add-duplicate-error" : u"Could not add second entry for '{}' to the playlist as no duplicates are allowed.", #Filename - "cannot-add-unsafe-path-error" : u"Could not load {} because it is not known as a safe path.", # Filename + "cannot-add-unsafe-path-error" : u"Could not load {} because it is not known as a trusted path.", # Filename "sharedplaylistenabled-label" : u"Enable shared playlists", "removefromplaylist-menu-label" : u"Remove from playlist", "shuffleplaylist-menuu-label" : u"Shuffle playlist", diff --git a/syncplay/ui/ConfigurationGetter.py b/syncplay/ui/ConfigurationGetter.py index 0165a34..a8d2446 100755 --- a/syncplay/ui/ConfigurationGetter.py +++ b/syncplay/ui/ConfigurationGetter.py @@ -36,6 +36,8 @@ class ConfigurationGetter(object): "mediaSearchDirectories": None, "sharedPlaylistEnabled": True, "loopAtEndOfPlaylist": False, + "onlySwitchToTrustedURIs": False, + "trustedURIs": None, "file": None, "playerArgs": [], "playerClass": None, @@ -106,7 +108,8 @@ class ConfigurationGetter(object): "showNonControllerOSD", "showDurationNotification", "sharedPlaylistEnabled", - "loopAtEndOfPlaylist" + "loopAtEndOfPlaylist", + "onlySwitchToTrustedURIs" ] self._tristate = [ "checkForUpdatesAutomatically", @@ -116,6 +119,7 @@ class ConfigurationGetter(object): self._serialised = [ "perPlayerArguments", "mediaSearchDirectories", + "trustedURIs", ] self._numeric = [ @@ -136,7 +140,8 @@ class ConfigurationGetter(object): "filesizePrivacyMode", "unpauseAction", "pauseOnLeave", "readyAtStart", "autoplayMinUsers", "autoplayInitialState", "mediaSearchDirectories", - "sharedPlaylistEnabled", "loopAtEndOfPlaylist"], + "sharedPlaylistEnabled", "loopAtEndOfPlaylist", + "onlySwitchToTrustedURIs", "trustedURIs"], "gui": ["showOSD", "showOSDWarnings", "showSlowdownOSD", "showDifferentRoomOSD", "showSameRoomOSD", "showNonControllerOSD", "showDurationNotification"], From ea5813e2b5352ffb20802b10114d16214208aeb8 Mon Sep 17 00:00:00 2001 From: Et0h Date: Sat, 7 May 2016 10:18:39 +0100 Subject: [PATCH 096/134] Update fileSwitch when playlist is changed --- syncplay/ui/gui.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/syncplay/ui/gui.py b/syncplay/ui/gui.py index 5e0862d..1646884 100644 --- a/syncplay/ui/gui.py +++ b/syncplay/ui/gui.py @@ -884,6 +884,7 @@ class MainWindow(QtGui.QMainWindow): if newPlaylist <> self.playlistState and self._syncplayClient and not self.updatingPlaylist: self.playlistState = newPlaylist self._syncplayClient.playlist.changePlaylist(newPlaylist) + self._syncplayClient.fileSwitch.updateInfo() def addTopLayout(self, window): window.topSplit = self.topSplitter(Qt.Horizontal, self) @@ -1316,6 +1317,7 @@ class MainWindow(QtGui.QMainWindow): if newIndexFilename: self.playlist.setPlaylistIndexFilename(newIndexFilename) self.updatingPlaylist = False + self._syncplayClient.fileSwitch.updateInfo() def setPlaylistIndexFilename(self, filename): self.playlist.setPlaylistIndexFilename(filename) From 571f671ce2ffea519c6d1b7408873545297d5ac6 Mon Sep 17 00:00:00 2001 From: Et0h Date: Sat, 7 May 2016 10:20:02 +0100 Subject: [PATCH 097/134] Reduce folder search double-check threshold to 65 seconds --- syncplay/constants.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/syncplay/constants.py b/syncplay/constants.py index 107b538..6c06c09 100644 --- a/syncplay/constants.py +++ b/syncplay/constants.py @@ -54,7 +54,7 @@ SYNC_ON_PAUSE = True # Client seek to global position - subtitles may disappear # Options for the File Switch feature: FOLDER_SEARCH_TIMEOUT = 60.0 # Secs - How long to wait until searches in folder to update cache are aborted (may be longer than this if hard drive needs to spin up) -FOLDER_SEARCH_DOUBLE_CHECK_INTERVAL = 120.0 # Secs - Frequency of updating cache when someone is playing a file not in current cache +FOLDER_SEARCH_DOUBLE_CHECK_INTERVAL = 65.0 # Secs - Frequency of updating cache when someone is playing a file not in current cache #Usually there's no need to adjust these LAST_PAUSED_DIFF_THRESHOLD = 2 From 09907a004144d056c76f46908edf365bdd1b099c Mon Sep 17 00:00:00 2001 From: Et0h Date: Sat, 7 May 2016 10:46:48 +0100 Subject: [PATCH 098/134] Hopefully fix double playlist advance bug --- syncplay/client.py | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/syncplay/client.py b/syncplay/client.py index 9e72aa8..0a14354 100644 --- a/syncplay/client.py +++ b/syncplay/client.py @@ -188,7 +188,7 @@ class SyncplayClient(object): currentLength = self.userlist.currentUser.file["duration"] if self.userlist.currentUser.file else 0 if pauseChange and paused and currentLength > constants.PLAYLIST_LOAD_NEXT_FILE_MINIMUM_LENGTH\ and abs(position - currentLength ) < constants.PLAYLIST_LOAD_NEXT_FILE_TIME_FROM_END_THRESHOLD: - self.playlist.loadNextFileInPlaylist() + self.playlist.advancePlaylistCheck() elif pauseChange and utils.meetsMinVersion(self.serverVersion, constants.USER_READY_MIN_VERSION): pauseChange = self._toggleReady(pauseChange, paused) @@ -395,6 +395,9 @@ class SyncplayClient(object): position += diff return position + def getStoredPlayerPosition(self): + return self._playerPosition if self._playerPosition is not None else None + def getPlayerPaused(self): if not self._lastPlayerUpdate: if self._lastGlobalUpdate: @@ -1314,6 +1317,7 @@ class SyncplayPlaylist(): self._playlist = [] self._playlistIndex = None self.addedChangeListCallback = False + self._lastPlaylistIndexChange = time.time() def needsSharedPlaylistsEnabled(f): # @NoSelf @wraps(f) @@ -1339,6 +1343,7 @@ class SyncplayPlaylist(): return if username is None and not self._client.sharedPlaylistIsEnabled(): return + self._lastPlaylistIndexChange = time.time() if self._client.playerIsNotReady(): if not self.addedChangeListCallback: self.addedChangeListCallback = True @@ -1364,6 +1369,7 @@ class SyncplayPlaylist(): @needsSharedPlaylistsEnabled def switchToNewPlaylistIndex(self, index, resetPosition=False): + self._lastPlaylistIndexChange = time.time() if self._client.playerIsNotReady(): self._client.addPlayerReadyCallback(lambda x: self.switchToNewPlaylistIndex(index, resetPosition)) return @@ -1461,6 +1467,19 @@ class SyncplayPlaylist(): if self._notPlayingCurrentIndex(): self.switchToNewPlaylistIndex(self._playlistIndex) + @needsSharedPlaylistsEnabled + def advancePlaylistCheck(self): + position = self._client.getStoredPlayerPosition() + currentLength = self._client.userlist.currentUser.file["duration"] if self._client.userlist.currentUser.file else 0 + if currentLength > constants.PLAYLIST_LOAD_NEXT_FILE_MINIMUM_LENGTH\ + and abs(position - currentLength ) < constants.PLAYLIST_LOAD_NEXT_FILE_TIME_FROM_END_THRESHOLD\ + and self._notJustChangedPlaylist(): + self.loadNextFileInPlaylist() + + def _notJustChangedPlaylist(self): + secondsSinceLastChange = time.time() - self._lastPlaylistIndexChange + return secondsSinceLastChange > constants.PLAYLIST_LOAD_NEXT_FILE_TIME_FROM_END_THRESHOLD + @needsSharedPlaylistsEnabled def loadNextFileInPlaylist(self): if self._notPlayingCurrentIndex(): From f7b78557b1f6e569b0f9f7e5fc9be8192d5ac38e Mon Sep 17 00:00:00 2001 From: Et0h Date: Sat, 7 May 2016 11:06:04 +0100 Subject: [PATCH 099/134] Hopefully fix set to 'not ready' at EOF bug --- syncplay/client.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/syncplay/client.py b/syncplay/client.py index 0a14354..7a7f807 100644 --- a/syncplay/client.py +++ b/syncplay/client.py @@ -190,7 +190,9 @@ class SyncplayClient(object): and abs(position - currentLength ) < constants.PLAYLIST_LOAD_NEXT_FILE_TIME_FROM_END_THRESHOLD: self.playlist.advancePlaylistCheck() elif pauseChange and utils.meetsMinVersion(self.serverVersion, constants.USER_READY_MIN_VERSION): - pauseChange = self._toggleReady(pauseChange, paused) + if currentLength == 0 or currentLength == -1 or\ + not (not self.playlist.notJustChangedPlaylist() and abs(position - currentLength ) < constants.PLAYLIST_LOAD_NEXT_FILE_TIME_FROM_END_THRESHOLD): + pauseChange = self._toggleReady(pauseChange, paused) if self._lastGlobalUpdate: self._lastPlayerUpdate = time.time() @@ -1473,10 +1475,10 @@ class SyncplayPlaylist(): currentLength = self._client.userlist.currentUser.file["duration"] if self._client.userlist.currentUser.file else 0 if currentLength > constants.PLAYLIST_LOAD_NEXT_FILE_MINIMUM_LENGTH\ and abs(position - currentLength ) < constants.PLAYLIST_LOAD_NEXT_FILE_TIME_FROM_END_THRESHOLD\ - and self._notJustChangedPlaylist(): + and self.notJustChangedPlaylist(): self.loadNextFileInPlaylist() - def _notJustChangedPlaylist(self): + def notJustChangedPlaylist(self): secondsSinceLastChange = time.time() - self._lastPlaylistIndexChange return secondsSinceLastChange > constants.PLAYLIST_LOAD_NEXT_FILE_TIME_FROM_END_THRESHOLD From 23f223e7ecb6bd01e82db92e535744dee4394d2c Mon Sep 17 00:00:00 2001 From: Et0h Date: Sat, 7 May 2016 12:17:43 +0100 Subject: [PATCH 100/134] Add 'loopSingleFiles' option to 'client_settings' (default: False) --- syncplay/client.py | 13 +++++++++++-- syncplay/ui/ConfigurationGetter.py | 3 +++ 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/syncplay/client.py b/syncplay/client.py index 7a7f807..951b93a 100644 --- a/syncplay/client.py +++ b/syncplay/client.py @@ -463,6 +463,9 @@ class SyncplayClient(object): def changeToPlaylistIndex(self, *args, **kwargs): self.playlist.changeToPlaylistIndex(*args, **kwargs) + def loopSingleFiles(self): + return self._config["loopSingleFiles"] + def isPlaylistLoopingEnabled(self): return self._config["loopAtEndOfPlaylist"] @@ -1486,7 +1489,13 @@ class SyncplayPlaylist(): def loadNextFileInPlaylist(self): if self._notPlayingCurrentIndex(): return - if self._thereIsNextPlaylistIndex(): + + if len(self._playlist) == 1 and self._client.loopSingleFiles(): + self._client.rewindFile() + self._client.setPaused(False) + reactor.callLater(0.5, self._client.setPaused, False,) + + elif self._thereIsNextPlaylistIndex(): self.switchToNewPlaylistIndex(self._nextPlaylistIndex(), resetPosition=True) def _updateUndoPlaylistBuffer(self, newPlaylist, newRoom): @@ -1512,7 +1521,7 @@ class SyncplayPlaylist(): def _thereIsNextPlaylistIndex(self): if self._playlistIndex is None: return False - elif len(self._playlist) == 1: + elif len(self._playlist) == 1 and not self._client.loopSingleFiles(): return False elif self._playlistIsAtEnd(): return self._client.isPlaylistLoopingEnabled() diff --git a/syncplay/ui/ConfigurationGetter.py b/syncplay/ui/ConfigurationGetter.py index a8d2446..ebe3da9 100755 --- a/syncplay/ui/ConfigurationGetter.py +++ b/syncplay/ui/ConfigurationGetter.py @@ -36,6 +36,7 @@ class ConfigurationGetter(object): "mediaSearchDirectories": None, "sharedPlaylistEnabled": True, "loopAtEndOfPlaylist": False, + "loopSingleFiles" : False, "onlySwitchToTrustedURIs": False, "trustedURIs": None, "file": None, @@ -109,6 +110,7 @@ class ConfigurationGetter(object): "showDurationNotification", "sharedPlaylistEnabled", "loopAtEndOfPlaylist", + "loopSingleFiles", "onlySwitchToTrustedURIs" ] self._tristate = [ @@ -141,6 +143,7 @@ class ConfigurationGetter(object): "pauseOnLeave", "readyAtStart", "autoplayMinUsers", "autoplayInitialState", "mediaSearchDirectories", "sharedPlaylistEnabled", "loopAtEndOfPlaylist", + "loopSingleFiles", "onlySwitchToTrustedURIs", "trustedURIs"], "gui": ["showOSD", "showOSDWarnings", "showSlowdownOSD", "showDifferentRoomOSD", "showSameRoomOSD", From 176b2f65dc6de480530a3d2380f81a27446304a3 Mon Sep 17 00:00:00 2001 From: Et0h Date: Sun, 8 May 2016 10:40:36 +0100 Subject: [PATCH 101/134] Double-check rewinding after file change --- syncplay/client.py | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/syncplay/client.py b/syncplay/client.py index 951b93a..bc3a2ef 100644 --- a/syncplay/client.py +++ b/syncplay/client.py @@ -71,6 +71,7 @@ class SyncplayClient(object): self.lastControlPasswordAttempt = None self.serverVersion = "0.0.0" + self.lastRewindTime = None self.lastLeftTime = 0 self.lastPausedOnLeaveTime = None self.lastLeftUser = u"" @@ -178,7 +179,18 @@ class SyncplayClient(object): return pauseChange, seeked def rewindFile(self): - self.setPosition(0) + self.setPosition(-1) + self.establishRewindDoubleCheck() + + def establishRewindDoubleCheck(self): + reactor.callLater(0.5, self.doubleCheckRewindFile,) + reactor.callLater(1, self.doubleCheckRewindFile,) + reactor.callLater(1.5, self.doubleCheckRewindFile,) + + def doubleCheckRewindFile(self): + if self.getStoredPlayerPosition() > 5: + self.setPosition(-1) + self.ui.showDebugMessage("Rewinded after double-check") def updatePlayerStatus(self, paused, position): position -= self.getUserOffset() @@ -452,6 +464,9 @@ class SyncplayClient(object): def openFile(self, filePath, resetPosition=False): self._player.openFile(filePath, resetPosition) + if resetPosition: + self.establishRewindDoubleCheck() + self.lastRewindTime = time.time() def fileSwitchFoundFiles(self): self.ui.fileSwitchFoundFiles() @@ -559,6 +574,9 @@ class SyncplayClient(object): def setPosition(self, position): if self._lastPlayerUpdate: self._lastPlayerUpdate = time.time() + if self.lastRewindTime is not None and abs(time.time() - self.lastRewindTime) < 1.0 and position > 5: + self.ui.showDebugMessage("Ignored seek to {} after rewind".format(position)) + return position += self.getUserOffset() if self._player and self.userlist.currentUser.file: if position < 0: From 1de40303bdabf20410791800b466aaf236e6bbd6 Mon Sep 17 00:00:00 2001 From: Et0h Date: Sun, 8 May 2016 13:48:42 +0100 Subject: [PATCH 102/134] Only auto-switch to trusted domains by default --- syncplay/client.py | 20 ++++++++++++-------- syncplay/constants.py | 3 +++ syncplay/messages_de.py | 2 ++ syncplay/messages_en.py | 2 ++ syncplay/messages_ru.py | 2 ++ syncplay/ui/ConfigurationGetter.py | 10 +++++----- syncplay/ui/GuiConfiguration.py | 16 ++++++++++++++++ 7 files changed, 42 insertions(+), 13 deletions(-) diff --git a/syncplay/client.py b/syncplay/client.py index bc3a2ef..a619c39 100644 --- a/syncplay/client.py +++ b/syncplay/client.py @@ -453,14 +453,18 @@ class SyncplayClient(object): self.playlist.changeToPlaylistIndexFromFilename(filename) def isURITrusted(self, URIToTest): - if self._config['onlySwitchToTrustedURIs']: - if self._config['trustedURIs']: - for URI in self._config['trustedURIs']: - if URIToTest.startswith(URI): - return True - return False - else: - return True + for trustedProtocol in constants.TRUSTABLE_WEB_PROTOCOLS: + if URIToTest.startswith(trustedProtocol): + if self._config['onlySwitchToTrustedDomains']: + if self._config['trustedDomains']: + for trustedDomain in self._config['trustedDomains']: + trustableURI = ''.join([trustedProtocol,trustedDomain,u"/"]) + if URIToTest.startswith(trustableURI): + return True + return False + else: + return True + return False def openFile(self, filePath, resetPosition=False): self._player.openFile(filePath, resetPosition) diff --git a/syncplay/constants.py b/syncplay/constants.py index 6c06c09..7c6326d 100644 --- a/syncplay/constants.py +++ b/syncplay/constants.py @@ -187,4 +187,7 @@ SYNCPLAY_UPDATE_URL = u"http://syncplay.pl/checkforupdate?{}" # Params SYNCPLAY_DOWNLOAD_URL = "http://syncplay.pl/download/" SYNCPLAY_PUBLIC_SERVER_LIST_URL = u"http://syncplay.pl/listpublicservers?{}" # Params +DEFAULT_TRUSTED_DOMAINS = [u"youtube.com",u"youtu.be",u"vimeo.com"] +TRUSTABLE_WEB_PROTOCOLS = [u"http://www.",u"https://www.",u"http://",u"https://"] + PRIVATE_FILE_FIELDS = ["path"] \ No newline at end of file diff --git a/syncplay/messages_de.py b/syncplay/messages_de.py index 56ba962..d87d9c8 100644 --- a/syncplay/messages_de.py +++ b/syncplay/messages_de.py @@ -220,6 +220,7 @@ de = { "unpause-ifothersready-option" : u"Wiedergeben wenn bereits als Bereit gesetzt oder alle anderen bereit sind (Standard)", "unpause-ifminusersready-option" : u"Wiedergeben wenn bereits als Bereit gesetzt oder die minimale Anzahl anderer Nutzer bereit ist", "unpause-always" : u"Immer wiedergeben", + "syncplay-trusteddomains-title": u"Trusted domains (for streaming services and hosted content)", # TODO: Translate into German "help-label" : u"Hilfe", "reset-label" : u"Standardwerte zurücksetzen", @@ -323,6 +324,7 @@ de = { "unpause-ifalreadyready-tooltip" : u"Wenn du nicht bereit bist und Play drückst wirst du als bereit gesetzt - zum Starten der Wiedergabe nochmal drücken.", "unpause-ifothersready-tooltip" : u"Wenn du Play drückst und nicht bereit bist, wird nur gestartet, wenn alle anderen bereit sind.", "unpause-ifminusersready-tooltip" : u"Wenn du Play drückst und nicht bereit bist, wird nur gestartet, wenn die minimale Anzahl anderer Benutzer bereit ist.", + "trusteddomains-arguments-tooltip" : u"Domains that it is okay for Syncplay to automatically switch to when shared playlists is enabled.", # TODO: Translate into German "help-tooltip" : u"Öffnet Hilfe auf syncplay.pl [Englisch]", "reset-tooltip" : u"Alle Einstellungen auf Standardwerte zurücksetzen.", diff --git a/syncplay/messages_en.py b/syncplay/messages_en.py index 3ae0448..b568e25 100644 --- a/syncplay/messages_en.py +++ b/syncplay/messages_en.py @@ -222,6 +222,7 @@ en = { "unpause-ifothersready-option" : u"Unpause if already ready or others in room are ready (default)", "unpause-ifminusersready-option" : u"Unpause if already ready or if all others ready and min users ready", "unpause-always" : u"Always unpause", + "syncplay-trusteddomains-title": u"Trusted domains (for streaming services and hosted content)", "help-label" : "Help", "reset-label" : "Restore defaults", @@ -323,6 +324,7 @@ en = { "unpause-ifalreadyready-tooltip" : u"If you press unpause when not ready it will set you as ready - press unpause again to unpause.", "unpause-ifothersready-tooltip" : u"If you press unpause when not ready, it will only upause if others are ready.", "unpause-ifminusersready-tooltip" : u"If you press unpause when not ready, it will only upause if others are ready and minimum users threshold is met.", + "trusteddomains-arguments-tooltip" : u"Domains that it is okay for Syncplay to automatically switch to when shared playlists is enabled.", "help-tooltip" : "Opens the Syncplay.pl user guide.", "reset-tooltip" : "Reset all settings to the default configuration.", diff --git a/syncplay/messages_ru.py b/syncplay/messages_ru.py index 5cdca6d..bf969e9 100644 --- a/syncplay/messages_ru.py +++ b/syncplay/messages_ru.py @@ -222,6 +222,7 @@ ru = { "unpause-ifothersready-option" : u"Unpause if already ready or others in room are ready (default)", # TODO: Translate into Russian "unpause-ifminusersready-option" : u"Unpause if already ready or if all others ready and min users ready", # TODO: Translate into Russian "unpause-always" : u"Always unpause", # TODO: Translate into Russian + "syncplay-trusteddomains-title": u"Trusted domains (for streaming services and hosted content)", # TODO: Translate into Russian "help-label" : u"Помощь", "reset-label" : u"Сброс настроек", @@ -323,6 +324,7 @@ ru = { "unpause-ifalreadyready-tooltip" : u"If you press unpause when not ready it will set you as ready - press unpause again to unpause.", # TODO: Translate into Russian "unpause-ifothersready-tooltip" : u"If you press unpause when not ready, it will only upause if others are ready.", # TODO: Translate into Russian "unpause-ifminusersready-tooltip" : u"If you press unpause when not ready, it will only upause if others are ready and minimum users threshold is met.", # TODO: Translate into Russian + "trusteddomains-arguments-tooltip" : u"Domains that it is okay for Syncplay to automatically switch to when shared playlists is enabled.", # TODO: Translate into Russian "help-tooltip" : u"Открыть Руководство Пользователя на Syncplay.pl.", "reset-tooltip" : u"Сбрасывает все настройки Syncplay в начальное состояние.", diff --git a/syncplay/ui/ConfigurationGetter.py b/syncplay/ui/ConfigurationGetter.py index ebe3da9..5447679 100755 --- a/syncplay/ui/ConfigurationGetter.py +++ b/syncplay/ui/ConfigurationGetter.py @@ -37,8 +37,8 @@ class ConfigurationGetter(object): "sharedPlaylistEnabled": True, "loopAtEndOfPlaylist": False, "loopSingleFiles" : False, - "onlySwitchToTrustedURIs": False, - "trustedURIs": None, + "onlySwitchToTrustedDomains": True, + "trustedDomains": constants.DEFAULT_TRUSTED_DOMAINS, "file": None, "playerArgs": [], "playerClass": None, @@ -111,7 +111,7 @@ class ConfigurationGetter(object): "sharedPlaylistEnabled", "loopAtEndOfPlaylist", "loopSingleFiles", - "onlySwitchToTrustedURIs" + "onlySwitchToTrustedDomains" ] self._tristate = [ "checkForUpdatesAutomatically", @@ -121,7 +121,7 @@ class ConfigurationGetter(object): self._serialised = [ "perPlayerArguments", "mediaSearchDirectories", - "trustedURIs", + "trustedDomains", ] self._numeric = [ @@ -144,7 +144,7 @@ class ConfigurationGetter(object): "autoplayInitialState", "mediaSearchDirectories", "sharedPlaylistEnabled", "loopAtEndOfPlaylist", "loopSingleFiles", - "onlySwitchToTrustedURIs", "trustedURIs"], + "onlySwitchToTrustedDomains", "trustedDomains"], "gui": ["showOSD", "showOSDWarnings", "showSlowdownOSD", "showDifferentRoomOSD", "showSameRoomOSD", "showNonControllerOSD", "showDurationNotification"], diff --git a/syncplay/ui/GuiConfiguration.py b/syncplay/ui/GuiConfiguration.py index efc57f4..ecb0234 100644 --- a/syncplay/ui/GuiConfiguration.py +++ b/syncplay/ui/GuiConfiguration.py @@ -370,6 +370,7 @@ class ConfigDialog(QtGui.QDialog): self.config["perPlayerArguments"] = self.perPlayerArgs self.config["mediaSearchDirectories"] = utils.convertMultilineStringToList(self.mediasearchTextEdit.toPlainText()) + self.config["trustedDomains"] = utils.convertMultilineStringToList(self.trusteddomainsTextEdit.toPlainText()) self.processWidget(self, lambda w: self.saveValues(w)) if self.hostCombobox.currentText(): @@ -504,6 +505,7 @@ class ConfigDialog(QtGui.QDialog): self.perPlayerArgs = self.config["perPlayerArguments"] self.mediaSearchDirectories = self.config["mediaSearchDirectories"] + self.trustedDomains = self.config["trustedDomains"] self.connectionSettingsGroup = QtGui.QGroupBox(getMessage("connection-group-title")) self.loadSavedPublicServerList() @@ -794,10 +796,24 @@ class ConfigDialog(QtGui.QDialog): self.othersyncSettingsLayout.setAlignment(Qt.AlignLeft) self.othersyncSettingsLayout.addWidget(self.fastforwardCheckbox, 3, 0,1,2, Qt.AlignLeft) + + ## Trusted domains + + self.trusteddomainsSettingsGroup = QtGui.QGroupBox(getMessage("syncplay-trusteddomains-title")) + self.trusteddomainsSettingsLayout = QtGui.QVBoxLayout() + self.trusteddomainsSettingsGroup.setLayout(self.trusteddomainsSettingsLayout) + + self.trusteddomainsTextEdit = QPlainTextEdit(utils.getListAsMultilineString(self.trustedDomains)) + self.trusteddomainsTextEdit.setObjectName(constants.LOAD_SAVE_MANUALLY_MARKER + "trusteddomains-arguments") + self.trusteddomainsTextEdit.setLineWrapMode(QtGui.QPlainTextEdit.NoWrap) + self.trusteddomainsSettingsLayout.addWidget(self.trusteddomainsTextEdit) + self.trusteddomainsSettingsGroup.setMaximumHeight(self.trusteddomainsSettingsGroup.minimumSizeHint().height()) + self.othersyncSettingsGroup.setLayout(self.othersyncSettingsLayout) self.othersyncSettingsGroup.setMaximumHeight(self.othersyncSettingsGroup.minimumSizeHint().height()) self.syncSettingsLayout.addWidget(self.othersyncSettingsGroup) self.syncSettingsLayout.addWidget(self.desyncSettingsGroup) + self.syncSettingsLayout.addWidget(self.trusteddomainsSettingsGroup) self.syncSettingsFrame.setLayout(self.syncSettingsLayout) self.desyncSettingsGroup.setMaximumHeight(self.desyncSettingsGroup.minimumSizeHint().height()) self.syncSettingsLayout.setAlignment(Qt.AlignTop) From baceaad489f142aad20be7233215f7a7cfaf20c4 Mon Sep 17 00:00:00 2001 From: Et0h Date: Sun, 8 May 2016 14:17:42 +0100 Subject: [PATCH 103/134] Prevent escaping mpv via newlines --- syncplay/players/mplayer.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/syncplay/players/mplayer.py b/syncplay/players/mplayer.py index b175a96..cc39b00 100644 --- a/syncplay/players/mplayer.py +++ b/syncplay/players/mplayer.py @@ -88,7 +88,7 @@ class MplayerPlayer(BasePlayer): self._listener.sendLine("get_property {}".format(property_)) def displayMessage(self, message, duration=(constants.OSD_DURATION * 1000), secondaryOSD=False): - self._listener.sendLine(u'{} "{!s}" {} {}'.format(self.OSD_QUERY, message, duration, constants.MPLAYER_OSD_LEVEL).encode('utf-8')) + self._listener.sendLine(u'{} "{!s}" {} {}'.format(self.OSD_QUERY, self._stripNewlines(message), duration, constants.MPLAYER_OSD_LEVEL).encode('utf-8')) def setSpeed(self, value): self._setProperty('speed', "{:.2f}".format(value)) @@ -129,10 +129,17 @@ class MplayerPlayer(BasePlayer): def _getPosition(self): self._getProperty(self.POSITION_QUERY) + def _stripNewlines(self, text): + text = text.replace("\r", "") + text = text.replace("\n", "") + return text + def _quoteArg(self, arg): arg = arg.replace('\\', '\\\\') arg = arg.replace("'", "\\'") arg = arg.replace('"', '\\"') + arg = arg.replace("\r", "") + arg = arg.replace("\n", "") return u'"{}"'.format(arg) def _fileIsLoaded(self): From 0dc2a23df01416675c874dac28807a8eafdc2bd7 Mon Sep 17 00:00:00 2001 From: Et0h Date: Fri, 27 May 2016 20:22:36 +0100 Subject: [PATCH 104/134] Be more aggressive in updating file cache --- syncplay/client.py | 22 ++++++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/syncplay/client.py b/syncplay/client.py index a619c39..f2738d7 100644 --- a/syncplay/client.py +++ b/syncplay/client.py @@ -1575,6 +1575,7 @@ class FileSwitchManager(object): self.mediaFilesCache = {} self.filenameWatchlist = [] self.currentDirectory = None + self.lastCheckedCurrentDirectory = None self.mediaDirectories = None self.lock = threading.Lock() self.folderSearchEnabled = True @@ -1602,15 +1603,19 @@ class FileSwitchManager(object): self.infoUpdated() def updateInfo(self): - if len(self.filenameWatchlist) > 0 or len(self.mediaFilesCache) == 0 and self.currentlyUpdating == False: + if not self.currentlyUpdating: threads.deferToThread(self._updateInfoThread).addCallback(lambda x: self.checkForFileSwitchUpdate()) def setFilenameWatchlist(self, unfoundFilenames): self.filenameWatchlist = unfoundFilenames def _updateInfoThread(self): + if not self.folderSearchEnabled: - if self.areWatchedFilenamesInCurrentDir(): + if self.lastCheckedCurrentDirecotry <> self.currentDirectory: + self.lastCheckedCurrentDirectory = self.currentDirectory + self.newInfo = True + elif self.areWatchedFilenamesInCurrentDir(): self.newInfo = True return @@ -1632,8 +1637,9 @@ class FileSwitchManager(object): self.newInfo = True return - if self.mediaFilesCache <> newMediaFilesCache: + if self.mediaFilesCache <> newMediaFilesCache or self.lastCheckedCurrentDirectory <> self.currentDirectory: self.mediaFilesCache = newMediaFilesCache + self.lastCheckedCurrentDirectory = self.currentDirectory self.newInfo = True elif self.areWatchedFilenamesInCurrentDir(): self.newInfo = True @@ -1701,4 +1707,12 @@ class FileSwitchManager(object): for directory in self.mediaFilesCache: files = self.mediaFilesCache[directory] if filename in files: - return True \ No newline at end of file + return True + + def getDirectoryOfFilenameInCache(self, filename): + if filename is not None and self.mediaFilesCache is not None: + for directory in self.mediaFilesCache: + files = self.mediaFilesCache[directory] + if filename in files: + return directory + return None \ No newline at end of file From cbc820300795a27aa64eb4beff9d085bd3343d99 Mon Sep 17 00:00:00 2001 From: Et0h Date: Sat, 28 May 2016 16:27:02 +0100 Subject: [PATCH 105/134] Remove special status of current directory and only look for files in media directories (for consistency) --- syncplay/client.py | 40 ++-------------------------------------- syncplay/messages_de.py | 4 ++-- syncplay/messages_en.py | 4 ++-- syncplay/messages_ru.py | 4 ++-- 4 files changed, 8 insertions(+), 44 deletions(-) diff --git a/syncplay/client.py b/syncplay/client.py index f2738d7..3b47150 100644 --- a/syncplay/client.py +++ b/syncplay/client.py @@ -1575,7 +1575,6 @@ class FileSwitchManager(object): self.mediaFilesCache = {} self.filenameWatchlist = [] self.currentDirectory = None - self.lastCheckedCurrentDirectory = None self.mediaDirectories = None self.lock = threading.Lock() self.folderSearchEnabled = True @@ -1591,7 +1590,6 @@ class FileSwitchManager(object): def setCurrentDirectory(self, curDir): self.currentDirectory = curDir - self.updateInfo() def setMediaDirectories(self, mediaDirs): self.mediaDirectories = mediaDirs @@ -1610,15 +1608,6 @@ class FileSwitchManager(object): self.filenameWatchlist = unfoundFilenames def _updateInfoThread(self): - - if not self.folderSearchEnabled: - if self.lastCheckedCurrentDirecotry <> self.currentDirectory: - self.lastCheckedCurrentDirectory = self.currentDirectory - self.newInfo = True - elif self.areWatchedFilenamesInCurrentDir(): - self.newInfo = True - return - with self.lock: try: self.currentlyUpdating = True @@ -1633,36 +1622,24 @@ class FileSwitchManager(object): if time.time() - startTime > constants.FOLDER_SEARCH_TIMEOUT: self.disabledDir = directory self.folderSearchEnabled = False - if self.areWatchedFilenamesInCurrentDir(): - self.newInfo = True return - if self.mediaFilesCache <> newMediaFilesCache or self.lastCheckedCurrentDirectory <> self.currentDirectory: + if self.mediaFilesCache <> newMediaFilesCache: self.mediaFilesCache = newMediaFilesCache - self.lastCheckedCurrentDirectory = self.currentDirectory - self.newInfo = True - elif self.areWatchedFilenamesInCurrentDir(): self.newInfo = True finally: self.currentlyUpdating = False def infoUpdated(self): - if self.areWatchedFilenamesInCache() or self.areWatchedFilenamesInCurrentDir(): + if self.areWatchedFilenamesInCache(): self._client.fileSwitchFoundFiles() def findFilepath(self, filename, highPriority=False): if filename is None: return - if self.currentDirectory is not None: - candidatePath = os.path.join(self.currentDirectory,filename) - if os.path.isfile(candidatePath): - return candidatePath - if self.mediaFilesCache is not None: for directory in self.mediaFilesCache: - if self.currentDirectory and self.currentDirectory == directory: - continue files = self.mediaFilesCache[directory] if len(files) > 0 and filename in files: filepath = os.path.join(directory, filename) @@ -1683,25 +1660,12 @@ class FileSwitchManager(object): return None return None - def areWatchedFilenamesInCurrentDir(self): - if self.filenameWatchlist is not None and self.currentDirectory is not None: - for filename in self.filenameWatchlist: - potentialPath = os.path.join(self.currentDirectory,filename) - if os.path.isfile(potentialPath): - return True - def areWatchedFilenamesInCache(self): if self.filenameWatchlist is not None: for filename in self.filenameWatchlist: if self.isFilenameInCache(filename): return True - def isFilenameInCurrentDir(self, filename): - if filename is not None and self.currentDirectory is not None: - potentialPath = os.path.join(self.currentDirectory,filename) - if os.path.isfile(potentialPath): - return True - def isFilenameInCache(self, filename): if filename is not None and self.mediaFilesCache is not None: for directory in self.mediaFilesCache: diff --git a/syncplay/messages_de.py b/syncplay/messages_de.py index d87d9c8..6e7b36b 100644 --- a/syncplay/messages_de.py +++ b/syncplay/messages_de.py @@ -136,7 +136,7 @@ de = { "invalid-seek-value" : u"Ungültige Zeitangabe", "invalid-offset-value" : u"Ungültiger Offset-Wert", - "switch-file-not-found-error" : u"Konnte nicht zur Datei '{0}' wechseln. Syncplay sucht im Ordner der aktuellen Datei und angegebenen Medien-Verzeichnissen.", # File not found, folder it was not found in + "switch-file-not-found-error" : u"Konnte nicht zur Datei '{0}' wechseln. Syncplay looks in the specified media directories.", # File not found, folder it was not found in # TODO: Re-translate "Syncplay sucht im Ordner der aktuellen Datei und angegebenen Medien-Verzeichnissen." to reference to checking in "current media directory" "folder-search-timeout-error" : u"Die Suche nach Mediendateien in '{}' wurde abgebrochen weil sie zu lange gedauert hat. Dies tritt auf, wenn ein zu durchsuchender Medienordner zu viele Unterordner hat. Syncplay wird bis zum Neustart nur noch das Verzeichnis der aktuellen Datei durchsuchen.", #Folder "failed-to-load-server-list-error" : u"Konnte die Liste der öffentlichen Server nicht laden. Bitte besuche http://www.syncplay.pl/ [Englisch] mit deinem Browser.", @@ -375,7 +375,7 @@ de = { # Playlists TODO: Translate all this to German "playlist-selection-changed-notification" : u"{} changed the playlist selection", # Username "playlist-contents-changed-notification" : u"{} updated the playlist", # Username - "cannot-find-file-for-playlist-switch-error" : u"Could not find file {} for playlist switch!", # Filename + "cannot-find-file-for-playlist-switch-error" : u"Could not find file {} in media directories for playlist switch!", # Filename "cannot-add-duplicate-error" : u"Could not add second entry for '{}' to the playlist as no duplicates are allowed.", #Filename "cannot-add-unsafe-path-error" : u"Could not load {} because it is not known as a trusted path.", # Filename "sharedplaylistenabled-label" : u"Enable shared playlists", diff --git a/syncplay/messages_en.py b/syncplay/messages_en.py index b568e25..0eabf95 100644 --- a/syncplay/messages_en.py +++ b/syncplay/messages_en.py @@ -136,7 +136,7 @@ en = { "invalid-seek-value" : u"Invalid seek value", "invalid-offset-value" : u"Invalid offset value", - "switch-file-not-found-error" : u"Could not switch to file '{0}'. Syncplay looks in the folder of the currently playing file and specified media directories.", # File not found + "switch-file-not-found-error" : u"Could not switch to file '{0}'. Syncplay looks specified media directories.", # File not found "folder-search-timeout-error" : u"The search for media in '{}' was aborted as it took too long. This will occur if you select a folder with too many sub-folders in your list of media folders to search through. Until Syncplay is restarted only the directory of the currently open file will be checked.", #Folder "failed-to-load-server-list-error" : u"Failed to load public server list. Please visit http://www.syncplay.pl/ in your browser.", @@ -376,7 +376,7 @@ en = { # Playlists "playlist-selection-changed-notification" : u"{} changed the playlist selection", # Username "playlist-contents-changed-notification" : u"{} updated the playlist", # Username - "cannot-find-file-for-playlist-switch-error" : u"Could not find file {} for playlist switch!", # Filename + "cannot-find-file-for-playlist-switch-error" : u"Could not find file {} in media directories for playlist switch!", # Filename "cannot-add-duplicate-error" : u"Could not add second entry for '{}' to the playlist as no duplicates are allowed.", #Filename "cannot-add-unsafe-path-error" : u"Could not load {} because it is not known as a trusted path.", # Filename "sharedplaylistenabled-label" : u"Enable shared playlists", diff --git a/syncplay/messages_ru.py b/syncplay/messages_ru.py index bf969e9..bca2e5b 100644 --- a/syncplay/messages_ru.py +++ b/syncplay/messages_ru.py @@ -136,7 +136,7 @@ ru = { "invalid-seek-value" : u"Некорректное значение для перемотки", "invalid-offset-value" : u"Некорректное смещение", - "switch-file-not-found-error" : u"Невозможно переключиться на файл '{0}'. Syncplay looks in the folder of the currently playing file and specified media directories.", # File not found # TODO: Translate last part into Russian + "switch-file-not-found-error" : u"Невозможно переключиться на файл '{0}'. Syncplay looks in the folder specified media directories.", # File not found # TODO: Translate last part into Russian "folder-search-timeout-error" : u"The search for media in '{}' was aborted as it took too long. This will occur if you select a folder with too many sub-folders in your list of media folders to search through. Until Syncplay is restarted only the directory of the currently open file will be checked.", #Folder # TODO: Translate into Russian "failed-to-load-server-list-error" : u"Failed to load public server list. Please visit http://www.syncplay.pl/ in your browser.", # TODO: Translate into Russian @@ -375,7 +375,7 @@ ru = { # Playlists TODO: Translate all this to Russian "playlist-selection-changed-notification" : u"{} changed the playlist selection", # Username "playlist-contents-changed-notification" : u"{} updated the playlist", # Username - "cannot-find-file-for-playlist-switch-error" : u"Could not find file {} for playlist switch!", # Filename + "cannot-find-file-for-playlist-switch-error" : u"Could not find file {} in media directories for playlist switch!", # Filename "cannot-add-duplicate-error" : u"Could not add second entry for '{}' to the playlist as no duplicates are allowed.", #Filename "cannot-add-unsafe-path-error" : u"Could not load {} because it is not known as a trusted path.", # Filename "sharedplaylistenabled-label" : u"Enable shared playlists", From fe69df56089074837c0ef099a032e8429875176f Mon Sep 17 00:00:00 2001 From: Et0h Date: Mon, 30 May 2016 20:47:35 +0100 Subject: [PATCH 106/134] Update playlist file availability even if no new files, as file may have been deleted --- syncplay/client.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/syncplay/client.py b/syncplay/client.py index 3b47150..039b557 100644 --- a/syncplay/client.py +++ b/syncplay/client.py @@ -1631,8 +1631,7 @@ class FileSwitchManager(object): self.currentlyUpdating = False def infoUpdated(self): - if self.areWatchedFilenamesInCache(): - self._client.fileSwitchFoundFiles() + self._client.fileSwitchFoundFiles() def findFilepath(self, filename, highPriority=False): if filename is None: From d3eaa22f5a2af0d01a94e7ab2e0f6b02d8a7dc55 Mon Sep 17 00:00:00 2001 From: Et0h Date: Mon, 30 May 2016 20:48:34 +0100 Subject: [PATCH 107/134] Reduce folder search interval to 30 seconds --- syncplay/constants.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/syncplay/constants.py b/syncplay/constants.py index 7c6326d..f919e53 100644 --- a/syncplay/constants.py +++ b/syncplay/constants.py @@ -53,8 +53,9 @@ AUTOPLAY_DELAY = 3.0 SYNC_ON_PAUSE = True # Client seek to global position - subtitles may disappear on some media players # Options for the File Switch feature: -FOLDER_SEARCH_TIMEOUT = 60.0 # Secs - How long to wait until searches in folder to update cache are aborted (may be longer than this if hard drive needs to spin up) -FOLDER_SEARCH_DOUBLE_CHECK_INTERVAL = 65.0 # Secs - Frequency of updating cache when someone is playing a file not in current cache +FOLDER_SEARCH_FIRST_FILE_TIMEOUT = 10.0 # Secs - How long to wait to find the first file in folder search (to take account of HDD spin up) +FOLDER_SEARCH_TIMEOUT = 3.0 # Secs - How long to wait until searches in folder to update cache are aborted (after first file is found) +FOLDER_SEARCH_DOUBLE_CHECK_INTERVAL = 30.0 # Secs - Frequency of updating cache when someone is playing a file not in current cache #Usually there's no need to adjust these LAST_PAUSED_DIFF_THRESHOLD = 2 From 409ad460aafe97a85dc02426f02d75e05b958036 Mon Sep 17 00:00:00 2001 From: Et0h Date: Tue, 31 May 2016 18:24:37 +0100 Subject: [PATCH 108/134] Spin up hard drives to prevent premature timeout when caching directories --- syncplay/client.py | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/syncplay/client.py b/syncplay/client.py index 039b557..69cba9d 100644 --- a/syncplay/client.py +++ b/syncplay/client.py @@ -1614,6 +1614,18 @@ class FileSwitchManager(object): dirsToSearch = self.mediaDirectories if dirsToSearch: + # Spin up hard drives to prevent premature timeout + randomFilename = u"RandomFile"+unicode(random.randrange(10000, 99999))+".txt" + for directory in dirsToSearch: + startTime = time.time() + if os.path.isfile(os.path.join(directory, randomFilename)): + randomFilename = u"RandomFile"+unicode(random.randrange(10000, 99999))+".txt" + if time.time() - startTime > constants.FOLDER_SEARCH_FIRST_FILE_TIMEOUT: + self.disabledDir = directory + self.folderSearchEnabled = False + return + + # Actual directory search newMediaFilesCache = {} startTime = time.time() for directory in dirsToSearch: @@ -1647,6 +1659,17 @@ class FileSwitchManager(object): if highPriority and self.folderSearchEnabled: directoryList = self.mediaDirectories + # Spin up hard drives to prevent premature timeout + randomFilename = u"RandomFile"+unicode(random.randrange(10000, 99999))+".txt" + for directory in directoryList: + startTime = time.time() + if os.path.isfile(os.path.join(directory, randomFilename)): + randomFilename = u"RandomFile"+unicode(random.randrange(10000, 99999))+".txt" + if time.time() - startTime > constants.FOLDER_SEARCH_FIRST_FILE_TIMEOUT: + self.disabledDir = directory + self.folderSearchEnabled = False + return + startTime = time.time() if filename and directoryList: for directory in directoryList: From b1c8c9f5b266f9797f456a6ee631a6813ad9dd05 Mon Sep 17 00:00:00 2001 From: Et0h Date: Thu, 2 Jun 2016 14:17:47 +0100 Subject: [PATCH 109/134] Update folder search timeout messages and ensure they are actually displayed --- syncplay/client.py | 10 +++++++--- syncplay/messages_de.py | 3 ++- syncplay/messages_en.py | 5 +++-- syncplay/messages_ru.py | 3 ++- syncplay/ui/gui.py | 8 -------- 5 files changed, 14 insertions(+), 15 deletions(-) diff --git a/syncplay/client.py b/syncplay/client.py index 69cba9d..02a84b0 100644 --- a/syncplay/client.py +++ b/syncplay/client.py @@ -1621,8 +1621,8 @@ class FileSwitchManager(object): if os.path.isfile(os.path.join(directory, randomFilename)): randomFilename = u"RandomFile"+unicode(random.randrange(10000, 99999))+".txt" if time.time() - startTime > constants.FOLDER_SEARCH_FIRST_FILE_TIMEOUT: - self.disabledDir = directory self.folderSearchEnabled = False + self._client.ui.showErrorMessage(getMessage("folder-search-first-file-timeout-error").format(directory)) return # Actual directory search @@ -1634,6 +1634,10 @@ class FileSwitchManager(object): if time.time() - startTime > constants.FOLDER_SEARCH_TIMEOUT: self.disabledDir = directory self.folderSearchEnabled = False + if not self.folderSearchEnabled: + if self.disabledDir is not None: + self._client.ui.showErrorMessage(getMessage("folder-search-timeout-error").format(self.disabledDir)) + self.disabledDir = None return if self.mediaFilesCache <> newMediaFilesCache: @@ -1665,9 +1669,9 @@ class FileSwitchManager(object): startTime = time.time() if os.path.isfile(os.path.join(directory, randomFilename)): randomFilename = u"RandomFile"+unicode(random.randrange(10000, 99999))+".txt" + if not self.folderSearchEnabled: + return if time.time() - startTime > constants.FOLDER_SEARCH_FIRST_FILE_TIMEOUT: - self.disabledDir = directory - self.folderSearchEnabled = False return startTime = time.time() diff --git a/syncplay/messages_de.py b/syncplay/messages_de.py index 6e7b36b..d3a38a6 100644 --- a/syncplay/messages_de.py +++ b/syncplay/messages_de.py @@ -137,7 +137,8 @@ de = { "invalid-offset-value" : u"Ungültiger Offset-Wert", "switch-file-not-found-error" : u"Konnte nicht zur Datei '{0}' wechseln. Syncplay looks in the specified media directories.", # File not found, folder it was not found in # TODO: Re-translate "Syncplay sucht im Ordner der aktuellen Datei und angegebenen Medien-Verzeichnissen." to reference to checking in "current media directory" - "folder-search-timeout-error" : u"Die Suche nach Mediendateien in '{}' wurde abgebrochen weil sie zu lange gedauert hat. Dies tritt auf, wenn ein zu durchsuchender Medienordner zu viele Unterordner hat. Syncplay wird bis zum Neustart nur noch das Verzeichnis der aktuellen Datei durchsuchen.", #Folder + "folder-search-timeout-error" : u"The search for media in media directories was aborted as it took too long to search through '{}'. This will occur if you select a folder with too many sub-folders in your list of media folders to search through. For automatic file switching to work again please remove this directory as a media directory (optionally using a sub-directory instead) and re-open Syncplay.", #Folder # TODO: Re-translate using new language + "folder-search-first-file-timeout-error" : u"The search for media in '{}' was aborted as it took too long to access the directory. This could happen if it is a network drive or if you configure your drive to spin down after a period of inactivity. For automatic file switching to work again please remove this directory as a media directory or resolve the issue (e.g. by changing power saving settings) and re-open Syncplay.", #Folder # TODO: Translate "failed-to-load-server-list-error" : u"Konnte die Liste der öffentlichen Server nicht laden. Bitte besuche http://www.syncplay.pl/ [Englisch] mit deinem Browser.", diff --git a/syncplay/messages_en.py b/syncplay/messages_en.py index 0eabf95..0f40410 100644 --- a/syncplay/messages_en.py +++ b/syncplay/messages_en.py @@ -136,8 +136,9 @@ en = { "invalid-seek-value" : u"Invalid seek value", "invalid-offset-value" : u"Invalid offset value", - "switch-file-not-found-error" : u"Could not switch to file '{0}'. Syncplay looks specified media directories.", # File not found - "folder-search-timeout-error" : u"The search for media in '{}' was aborted as it took too long. This will occur if you select a folder with too many sub-folders in your list of media folders to search through. Until Syncplay is restarted only the directory of the currently open file will be checked.", #Folder + "switch-file-not-found-error" : u"Could not switch to file '{0}'. Syncplay looks in specified media directories.", # File not found + "folder-search-timeout-error" : u"The search for media in media directories was aborted as it took too long to search through '{}'. This will occur if you select a folder with too many sub-folders in your list of media folders to search through. For automatic file switching to work again please remove this directory as a media directory (optionally using a sub-directory instead) and re-open Syncplay.", #Folder + "folder-search-first-file-timeout-error" : u"The search for media in '{}' was aborted as it took too long to access the directory. This could happen if it is a network drive or if you configure your drive to spin down after a period of inactivity. For automatic file switching to work again please remove this directory as a media directory or resolve the issue (e.g. by changing power saving settings) and re-open Syncplay.", #Folder "failed-to-load-server-list-error" : u"Failed to load public server list. Please visit http://www.syncplay.pl/ in your browser.", diff --git a/syncplay/messages_ru.py b/syncplay/messages_ru.py index bca2e5b..5648e72 100644 --- a/syncplay/messages_ru.py +++ b/syncplay/messages_ru.py @@ -137,7 +137,8 @@ ru = { "invalid-offset-value" : u"Некорректное смещение", "switch-file-not-found-error" : u"Невозможно переключиться на файл '{0}'. Syncplay looks in the folder specified media directories.", # File not found # TODO: Translate last part into Russian - "folder-search-timeout-error" : u"The search for media in '{}' was aborted as it took too long. This will occur if you select a folder with too many sub-folders in your list of media folders to search through. Until Syncplay is restarted only the directory of the currently open file will be checked.", #Folder # TODO: Translate into Russian + "folder-search-timeout-error" : u"The search for media in media directories was aborted as it took too long to search through '{}'. This will occur if you select a folder with too many sub-folders in your list of media folders to search through. For automatic file switching to work again please remove this directory as a media directory (optionally using a sub-directory instead) and re-open Syncplay.", #Folder # TODO: Translate + "folder-search-first-file-timeout-error" : u"The search for media in '{}' was aborted as it took too long to access the directory. This could happen if it is a network drive or if you configure your drive to spin down after a period of inactivity. For automatic file switching to work again please remove this directory as a media directory or resolve the issue (e.g. by changing power saving settings) and re-open Syncplay.", #Folder # TODO: Translate "failed-to-load-server-list-error" : u"Failed to load public server list. Please visit http://www.syncplay.pl/ in your browser.", # TODO: Translate into Russian diff --git a/syncplay/ui/gui.py b/syncplay/ui/gui.py index 1646884..57baf88 100644 --- a/syncplay/ui/gui.py +++ b/syncplay/ui/gui.py @@ -458,7 +458,6 @@ class MainWindow(QtGui.QMainWindow): self.listTreeView.expandAll() self.updateListGeometry() self._syncplayClient.fileSwitch.setFilenameWatchlist(self.newWatchlist) - self.checkForDisabledDir() @needsClient def undoPlaylistChange(self): @@ -544,13 +543,6 @@ class MainWindow(QtGui.QMainWindow): return menu.exec_(self.listTreeView.viewport().mapToGlobal(position)) - - @needsClient - def checkForDisabledDir(self): - if self._syncplayClient.fileSwitch.disabledDir is not None and self._syncplayClient.fileSwitch.currentWindow is not None: - self.showErrorMessage(getMessage("folder-search-timeout-error").format(self._syncplayClient.fileSwitch.disabledDir)) - self._syncplayClient.fileSwitch.disabledDir = None - def updateListGeometry(self): try: roomtocheck = 0 From a8d3c3ccc88f9f7462fdd0c0d1a8a1f659c95226 Mon Sep 17 00:00:00 2001 From: Et0h Date: Thu, 2 Jun 2016 14:37:59 +0100 Subject: [PATCH 110/134] Only update cache if media directories are specified --- syncplay/client.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/syncplay/client.py b/syncplay/client.py index 02a84b0..48eaf7a 100644 --- a/syncplay/client.py +++ b/syncplay/client.py @@ -1601,7 +1601,7 @@ class FileSwitchManager(object): self.infoUpdated() def updateInfo(self): - if not self.currentlyUpdating: + if not self.currentlyUpdating and self.mediaDirectories: threads.deferToThread(self._updateInfoThread).addCallback(lambda x: self.checkForFileSwitchUpdate()) def setFilenameWatchlist(self, unfoundFilenames): From 53e32e228305b4dd55c19ced9b09ada19821d764 Mon Sep 17 00:00:00 2001 From: Et0h Date: Thu, 2 Jun 2016 16:30:54 +0100 Subject: [PATCH 111/134] Allow user to set media directories via main GUI --- buildPy2exe.py | 1 + resources/film_folder_edit.png | Bin 0 -> 1021 bytes syncplay/client.py | 9 +++++++++ syncplay/messages_de.py | 7 +++++-- syncplay/messages_en.py | 7 +++++-- syncplay/messages_ru.py | 7 +++++-- syncplay/ui/gui.py | 27 ++++++++++++++++++++++++++- 7 files changed, 51 insertions(+), 7 deletions(-) create mode 100644 resources/film_folder_edit.png diff --git a/buildPy2exe.py b/buildPy2exe.py index af07026..3a0d09b 100644 --- a/buildPy2exe.py +++ b/buildPy2exe.py @@ -655,6 +655,7 @@ guiIcons = ['resources/accept.png', 'resources/arrow_undo.png', 'resources/clock 'resources/tick.png', 'resources/lock_open.png', 'resources/empty_checkbox.png', 'resources/tick_checkbox.png', 'resources/world_explore.png', 'resources/application_get.png', 'resources/cog.png', 'resources/arrow_switch.png', 'resources/film_go.png', 'resources/world_go.png', 'resources/arrow_refresh.png', 'resources/bullet_right_grey.png', + 'film_folder_edit.png', 'resources/world_add.png', 'resources/film_add.png', 'resources/delete.png', 'resources/spinner.mng' ] resources = ["resources/icon.ico", "resources/syncplay.png"] diff --git a/resources/film_folder_edit.png b/resources/film_folder_edit.png new file mode 100644 index 0000000000000000000000000000000000000000..ba80c6463bdc518845bb2918d752adf20a5d8ee7 GIT binary patch literal 1021 zcmV;Devck}XRHaYS2|K;xisP!~odGt5!3MGEb5O92;f$TCbsVQxku+Y}v}8FNWx zzD(u@iq1mEu+EiQpXh;^MKw!a_l#(TvAh8zGpdY5J_mWI{Gn=2EFXrXt-BYPlh_Q_rjmA-ekLdM!xAOK! zFxh>mu>n=LfgZ;t2vbJn=H`y&Kwr1!rG-GB$> z{eYnl`0&Ogva_>YN~LlzGc(gqP4sSaKDL#K(}TM8s;kz?pB&yRnX8Hgt)mEy<0W`+ zG!JFNHE8Z_gXi0KFnP5F&M)7`g&NcRvf6SLDzI0x@Vg3 z^~^zBbnn9atp+6MPGI#>JB*cOu+Zfov-oG~hb%h~ z7;SfHx!x)hPIMm*}=E^A(Uu{SL(jiHzeS>pQpC7u)uL{|x3##7H(%sU!lxD4Fm zcM*0Ug71t56I}`+H8piGB_+jAG3S+n9Ay!W{D!q)AH-)pm>I6cSlj>C0u@<=p=Cp!KN=oWZOiUz0>`-vB7$o>S_}O+?+tfg03ZBllajS6??58!b zw{64Xoh=ACULptF#dDL8kWdsKA0MP9PHdeML*CGe?>lK_vI|El(;*CZKp1JqNJ}oh zH*Ln;9RtEn8sR@o;1_H%F(HBx7Z>NHJV(=OJgpfQJjXH?$jm self._client._config["mediaSearchDirectories"]: + from syncplay.ui.ConfigurationGetter import ConfigurationGetter + ConfigurationGetter().setConfigOption("mediaSearchDirectories", mediaDirs) + self._client._config["mediaSearchDirectories"] = mediaDirs + self._client.ui.showMessage(getMessage("media-directory-list-updated-notification")) + self.folderSearchEnabled = True + self.setMediaDirectories(mediaDirs) + def setMediaDirectories(self, mediaDirs): self.mediaDirectories = mediaDirs self.updateInfo() diff --git a/syncplay/messages_de.py b/syncplay/messages_de.py index d3a38a6..f92f17f 100644 --- a/syncplay/messages_de.py +++ b/syncplay/messages_de.py @@ -28,6 +28,8 @@ de = { "current-offset-notification" : u"Aktueller Offset: {} Sekunden", # Offset + "media-directory-list-updated-notification" : u"Syncplay media directories have been updated.", # TODO: Translate + "room-join-notification" : u"<{}> hat den Raum '{}' betreten", # User "left-notification" : u"<{}> ist gegangen", # User "left-paused-notification" : u"<{}> ist gegangen, <{}> pausierte", # User who left, User who paused @@ -137,8 +139,8 @@ de = { "invalid-offset-value" : u"Ungültiger Offset-Wert", "switch-file-not-found-error" : u"Konnte nicht zur Datei '{0}' wechseln. Syncplay looks in the specified media directories.", # File not found, folder it was not found in # TODO: Re-translate "Syncplay sucht im Ordner der aktuellen Datei und angegebenen Medien-Verzeichnissen." to reference to checking in "current media directory" - "folder-search-timeout-error" : u"The search for media in media directories was aborted as it took too long to search through '{}'. This will occur if you select a folder with too many sub-folders in your list of media folders to search through. For automatic file switching to work again please remove this directory as a media directory (optionally using a sub-directory instead) and re-open Syncplay.", #Folder # TODO: Re-translate using new language - "folder-search-first-file-timeout-error" : u"The search for media in '{}' was aborted as it took too long to access the directory. This could happen if it is a network drive or if you configure your drive to spin down after a period of inactivity. For automatic file switching to work again please remove this directory as a media directory or resolve the issue (e.g. by changing power saving settings) and re-open Syncplay.", #Folder # TODO: Translate + "folder-search-timeout-error" : u"The search for media in media directories was aborted as it took too long to search through '{}'. This will occur if you select a folder with too many sub-folders in your list of media folders to search through. For automatic file switching to work again please select File->Set Media Directories in the menu bar and remove this directory or replace it with an appropriate sub-folder.", #Folder # TODO: Translate + "folder-search-first-file-timeout-error" : u"The search for media in '{}' was aborted as it took too long to access the directory. This could happen if it is a network drive or if you configure your drive to spin down after a period of inactivity. For automatic file switching to work again please go to File->Set Media Directories and either remove the directory or resolve the issue (e.g. by changing power saving settings).", #Folder # TODO: Translate "failed-to-load-server-list-error" : u"Konnte die Liste der öffentlichen Server nicht laden. Bitte besuche http://www.syncplay.pl/ [Englisch] mit deinem Browser.", @@ -255,6 +257,7 @@ de = { "file-menu-label" : u"&Datei", # & precedes shortcut key "openmedia-menu-label" : u"&Mediendatei öffnen...", "openstreamurl-menu-label" : u"&Stream URL öffnen", + "setmediadirectories-menu-label" : u"Set media &directories", # TODO: Translate "exit-menu-label" : u"&Beenden", "advanced-menu-label" : u"&Erweitert", "window-menu-label" : u"&Fenster", diff --git a/syncplay/messages_en.py b/syncplay/messages_en.py index 0f40410..2519be0 100644 --- a/syncplay/messages_en.py +++ b/syncplay/messages_en.py @@ -28,6 +28,8 @@ en = { "current-offset-notification" : "Current offset: {} seconds", # Offset + "media-directory-list-updated-notification" : u"Syncplay media directories have been updated.", + "room-join-notification" : u"<{}> has joined the room: '{}'", # User "left-notification" : u"<{}> has left", # User "left-paused-notification" : u"<{}> left, <{}> paused", # User who left, User who paused @@ -137,8 +139,8 @@ en = { "invalid-offset-value" : u"Invalid offset value", "switch-file-not-found-error" : u"Could not switch to file '{0}'. Syncplay looks in specified media directories.", # File not found - "folder-search-timeout-error" : u"The search for media in media directories was aborted as it took too long to search through '{}'. This will occur if you select a folder with too many sub-folders in your list of media folders to search through. For automatic file switching to work again please remove this directory as a media directory (optionally using a sub-directory instead) and re-open Syncplay.", #Folder - "folder-search-first-file-timeout-error" : u"The search for media in '{}' was aborted as it took too long to access the directory. This could happen if it is a network drive or if you configure your drive to spin down after a period of inactivity. For automatic file switching to work again please remove this directory as a media directory or resolve the issue (e.g. by changing power saving settings) and re-open Syncplay.", #Folder + "folder-search-timeout-error" : u"The search for media in media directories was aborted as it took too long to search through '{}'. This will occur if you select a folder with too many sub-folders in your list of media folders to search through. For automatic file switching to work again please select File->Set Media Directories in the menu bar and remove this directory or replace it with an appropriate sub-folder.", #Folder + "folder-search-first-file-timeout-error" : u"The search for media in '{}' was aborted as it took too long to access the directory. This could happen if it is a network drive or if you configure your drive to spin down after a period of inactivity. For automatic file switching to work again please go to File->Set Media Directories and either remove the directory or resolve the issue (e.g. by changing power saving settings).", #Folder "failed-to-load-server-list-error" : u"Failed to load public server list. Please visit http://www.syncplay.pl/ in your browser.", @@ -257,6 +259,7 @@ en = { "file-menu-label" : "&File", # & precedes shortcut key "openmedia-menu-label" : "&Open media file", "openstreamurl-menu-label" : "Open &media stream URL", + "setmediadirectories-menu-label" : u"Set media &directories", "exit-menu-label" : "E&xit", "advanced-menu-label" : "&Advanced", "window-menu-label" : "&Window", diff --git a/syncplay/messages_ru.py b/syncplay/messages_ru.py index 5648e72..6179e8a 100644 --- a/syncplay/messages_ru.py +++ b/syncplay/messages_ru.py @@ -28,6 +28,8 @@ ru = { "current-offset-notification" : u"Текущее смещение: {} секунд(ы)", # Offset + "media-directory-list-updated-notification" : u"Syncplay media directories have been updated.", # TODO: Translate + "room-join-notification" : u"<{}> зашел(зашла) в комнату: '{}'", # User "left-notification" : u"<{}> покинул(а) комнату", # User "left-paused-notification" : u"<{}> покинул(а) комнату, <{}> приостановил(а) воспроизведение", # User who left, User who paused @@ -137,8 +139,8 @@ ru = { "invalid-offset-value" : u"Некорректное смещение", "switch-file-not-found-error" : u"Невозможно переключиться на файл '{0}'. Syncplay looks in the folder specified media directories.", # File not found # TODO: Translate last part into Russian - "folder-search-timeout-error" : u"The search for media in media directories was aborted as it took too long to search through '{}'. This will occur if you select a folder with too many sub-folders in your list of media folders to search through. For automatic file switching to work again please remove this directory as a media directory (optionally using a sub-directory instead) and re-open Syncplay.", #Folder # TODO: Translate - "folder-search-first-file-timeout-error" : u"The search for media in '{}' was aborted as it took too long to access the directory. This could happen if it is a network drive or if you configure your drive to spin down after a period of inactivity. For automatic file switching to work again please remove this directory as a media directory or resolve the issue (e.g. by changing power saving settings) and re-open Syncplay.", #Folder # TODO: Translate + "folder-search-timeout-error" : u"The search for media in media directories was aborted as it took too long to search through '{}'. This will occur if you select a folder with too many sub-folders in your list of media folders to search through. For automatic file switching to work again please select File->Set Media Directories in the menu bar and remove this directory or replace it with an appropriate sub-folder.", #Folder # TODO: Translate + "folder-search-first-file-timeout-error" : u"The search for media in '{}' was aborted as it took too long to access the directory. This could happen if it is a network drive or if you configure your drive to spin down after a period of inactivity. For automatic file switching to work again please go to File->Set Media Directories and either remove the directory or resolve the issue (e.g. by changing power saving settings).", #Folder # TODO: Translate "failed-to-load-server-list-error" : u"Failed to load public server list. Please visit http://www.syncplay.pl/ in your browser.", # TODO: Translate into Russian @@ -257,6 +259,7 @@ ru = { "file-menu-label" : u"&Файл", # & precedes shortcut key "openmedia-menu-label" : u"&Открыть видеофайл", "openstreamurl-menu-label" : u"Открыть URL &потокового вещания", + "setmediadirectories-menu-label" : u"Set media &directories", # TODO: Translate "exit-menu-label" : u"&Выход", "advanced-menu-label" : u"&Дополнительно", "window-menu-label" : u"&Окна", diff --git a/syncplay/ui/gui.py b/syncplay/ui/gui.py index 57baf88..c35943b 100644 --- a/syncplay/ui/gui.py +++ b/syncplay/ui/gui.py @@ -776,7 +776,7 @@ class MainWindow(QtGui.QMainWindow): @needsClient def OpenAddURIsToPlaylistDialog(self): URIsDialog = QtGui.QDialog() - URIsDialog.setWindowTitle("Add URLs to playlist (one per line)") + URIsDialog.setWindowTitle(u"Add URLs to playlist (one per line)") # TODO: Move to messages_*.py URIsLayout = QtGui.QGridLayout() URIsTextbox = QtGui.QPlainTextEdit() URIsTextbox.setLineWrapMode(QtGui.QPlainTextEdit.NoWrap) @@ -797,6 +797,27 @@ class MainWindow(QtGui.QMainWindow): self.addStreamToPlaylist(URI) self.updatingPlaylist = False + def openSetMediaDirectoriesDialog(self): + MediaDirectoriesDialog = QtGui.QDialog() + MediaDirectoriesDialog.setWindowTitle(getMessage("syncplay-mediasearchdirectories-title")) # TODO: Move to messages_*.py + MediaDirectoriesLayout = QtGui.QGridLayout() + MediaDirectoriesTextbox = QtGui.QPlainTextEdit() + MediaDirectoriesTextbox.setLineWrapMode(QtGui.QPlainTextEdit.NoWrap) + MediaDirectoriesTextbox.setPlainText(utils.getListAsMultilineString(self.config["mediaSearchDirectories"])) + MediaDirectoriesLayout.addWidget(MediaDirectoriesTextbox, 0, 0, 1, 1) + MediaDirectoriesButtonBox = QtGui.QDialogButtonBox() + MediaDirectoriesButtonBox.setOrientation(Qt.Horizontal) + MediaDirectoriesButtonBox.setStandardButtons(QtGui.QDialogButtonBox.Ok|QtGui.QDialogButtonBox.Cancel) + MediaDirectoriesButtonBox.accepted.connect(MediaDirectoriesDialog.accept) + MediaDirectoriesButtonBox.rejected.connect(MediaDirectoriesDialog.reject) + MediaDirectoriesLayout.addWidget(MediaDirectoriesButtonBox, 1, 0, 1, 1) + MediaDirectoriesDialog.setLayout(MediaDirectoriesLayout) + MediaDirectoriesDialog.show() + result = MediaDirectoriesDialog.exec_() + if result == QtGui.QDialog.Accepted: + newMediaDirectories = utils.convertMultilineStringToList(MediaDirectoriesTextbox.toPlainText()) + self._syncplayClient.fileSwitch.changeMediaDirectories(newMediaDirectories) + @needsClient def promptForStreamURL(self): streamURL, ok = QtGui.QInputDialog.getText(self, getMessage("promptforstreamurl-msgbox-label"), @@ -1092,6 +1113,10 @@ class MainWindow(QtGui.QMainWindow): window.openAction = window.fileMenu.addAction(QtGui.QIcon(self.resourcespath + 'world_explore.png'), getMessage("openstreamurl-menu-label")) window.openAction.triggered.connect(self.promptForStreamURL) + window.openAction = window.fileMenu.addAction(QtGui.QIcon(self.resourcespath + 'film_folder_edit.png'), + getMessage("setmediadirectories-menu-label")) + window.openAction.triggered.connect(self.openSetMediaDirectoriesDialog) + window.exitAction = window.fileMenu.addAction(QtGui.QIcon(self.resourcespath + 'cross.png'), getMessage("exit-menu-label")) From 1c9a56e9d57eba12d538b75de3111968dacad768 Mon Sep 17 00:00:00 2001 From: Et0h Date: Thu, 2 Jun 2016 17:35:19 +0100 Subject: [PATCH 112/134] Fix thread crash with directory search error messages + Add error/notification when adding file(s) not in a known media directory --- syncplay/client.py | 40 +++++++++++++++++++++++++++++++--------- syncplay/messages_de.py | 1 + syncplay/messages_en.py | 1 + syncplay/messages_ru.py | 1 + syncplay/ui/gui.py | 1 + 5 files changed, 35 insertions(+), 9 deletions(-) diff --git a/syncplay/client.py b/syncplay/client.py index a187e42..23c21e8 100644 --- a/syncplay/client.py +++ b/syncplay/client.py @@ -434,6 +434,7 @@ class SyncplayClient(object): return self._globalPaused def updateFile(self, filename, duration, path): + newPath = u"" if utils.isURL(path): filename = path @@ -447,6 +448,8 @@ class SyncplayClient(object): size = os.path.getsize(path) except: size = 0 + if not utils.isURL(path) and os.path.exists(path): + self.fileSwitch.notifyUserIfFileNotInMediaDirectory(filename, path) filename, size = self.__executePrivacySettings(filename, size) self.userlist.currentUser.setFile(filename, duration, size, path) self.sendFile() @@ -1403,6 +1406,7 @@ class SyncplayPlaylist(): try: filename = self._playlist[index] + # TODO: Handle isse with index being None if utils.isURL(filename): if self._client.isURITrusted(filename): self._client.openFile(filename, resetPosition=resetPosition) @@ -1578,12 +1582,13 @@ class FileSwitchManager(object): self.mediaDirectories = None self.lock = threading.Lock() self.folderSearchEnabled = True - self.disabledDir = None + self.directorySearchError = None self.newInfo = False self.currentlyUpdating = False self.newWatchlist = [] self.fileSwitchTimer = task.LoopingCall(self.updateInfo) self.fileSwitchTimer.start(constants.FOLDER_SEARCH_DOUBLE_CHECK_INTERVAL, True) + self.mediaDirectoriesNotFound = [] def setClient(self, newClient): self.client = newClient @@ -1597,6 +1602,7 @@ class FileSwitchManager(object): ConfigurationGetter().setConfigOption("mediaSearchDirectories", mediaDirs) self._client._config["mediaSearchDirectories"] = mediaDirs self._client.ui.showMessage(getMessage("media-directory-list-updated-notification")) + self.mediaDirectoriesNotFound = [] self.folderSearchEnabled = True self.setMediaDirectories(mediaDirs) @@ -1608,6 +1614,9 @@ class FileSwitchManager(object): if self.newInfo: self.newInfo = False self.infoUpdated() + if self.directorySearchError: + self._client.ui.showErrorMessage(self.directorySearchError) + self.directorySearchError = None def updateInfo(self): if not self.currentlyUpdating and self.mediaDirectories: @@ -1631,7 +1640,7 @@ class FileSwitchManager(object): randomFilename = u"RandomFile"+unicode(random.randrange(10000, 99999))+".txt" if time.time() - startTime > constants.FOLDER_SEARCH_FIRST_FILE_TIMEOUT: self.folderSearchEnabled = False - self._client.ui.showErrorMessage(getMessage("folder-search-first-file-timeout-error").format(directory)) + self.directorySearchError = getMessage("folder-search-first-file-timeout-error").format(directory) return # Actual directory search @@ -1641,12 +1650,8 @@ class FileSwitchManager(object): for root, dirs, files in os.walk(directory): newMediaFilesCache[root] = files if time.time() - startTime > constants.FOLDER_SEARCH_TIMEOUT: - self.disabledDir = directory + self.directorySearchError = getMessage("folder-search-timeout-error").format(directory) self.folderSearchEnabled = False - if not self.folderSearchEnabled: - if self.disabledDir is not None: - self._client.ui.showErrorMessage(getMessage("folder-search-timeout-error").format(self.disabledDir)) - self.disabledDir = None return if self.mediaFilesCache <> newMediaFilesCache: @@ -1690,8 +1695,8 @@ class FileSwitchManager(object): if filename in files: return os.path.join(root,filename) if time.time() - startTime > constants.FOLDER_SEARCH_TIMEOUT: - self.disabledDir = directory self.folderSearchEnabled = False + self.directorySearchError = getMessage("folder-search-timeout-error").format(directory) return None return None @@ -1714,4 +1719,21 @@ class FileSwitchManager(object): files = self.mediaFilesCache[directory] if filename in files: return directory - return None \ No newline at end of file + return None + + def notifyUserIfFileNotInMediaDirectory(self, filenameToFind, path): + directoryToFind = os.path.dirname(path) + if directoryToFind in self.mediaDirectoriesNotFound: + return + if self.mediaDirectories and self.mediaFilesCache is not None: + if self.mediaFilesCache: + if directoryToFind in self.mediaFilesCache: + return + for directory in self.mediaFilesCache: + files = self.mediaFilesCache[directory] + if filenameToFind in files: + return + if directoryToFind in self.mediaFilesCache: + return + self._client.ui.showErrorMessage(getMessage("added-file-not-in-media-directory-error").format(directoryToFind)) + self.mediaDirectoriesNotFound.append(directoryToFind) \ No newline at end of file diff --git a/syncplay/messages_de.py b/syncplay/messages_de.py index f92f17f..89a824f 100644 --- a/syncplay/messages_de.py +++ b/syncplay/messages_de.py @@ -141,6 +141,7 @@ de = { "switch-file-not-found-error" : u"Konnte nicht zur Datei '{0}' wechseln. Syncplay looks in the specified media directories.", # File not found, folder it was not found in # TODO: Re-translate "Syncplay sucht im Ordner der aktuellen Datei und angegebenen Medien-Verzeichnissen." to reference to checking in "current media directory" "folder-search-timeout-error" : u"The search for media in media directories was aborted as it took too long to search through '{}'. This will occur if you select a folder with too many sub-folders in your list of media folders to search through. For automatic file switching to work again please select File->Set Media Directories in the menu bar and remove this directory or replace it with an appropriate sub-folder.", #Folder # TODO: Translate "folder-search-first-file-timeout-error" : u"The search for media in '{}' was aborted as it took too long to access the directory. This could happen if it is a network drive or if you configure your drive to spin down after a period of inactivity. For automatic file switching to work again please go to File->Set Media Directories and either remove the directory or resolve the issue (e.g. by changing power saving settings).", #Folder # TODO: Translate + "added-file-not-in-media-directory-error" : u"You loaded a file in '{}' which is not a known media directory. You can add this as a media directory by selecting File->Set Media Directories in the menu bar.", #Folder # TODO: Translate "failed-to-load-server-list-error" : u"Konnte die Liste der öffentlichen Server nicht laden. Bitte besuche http://www.syncplay.pl/ [Englisch] mit deinem Browser.", diff --git a/syncplay/messages_en.py b/syncplay/messages_en.py index 2519be0..ba99bec 100644 --- a/syncplay/messages_en.py +++ b/syncplay/messages_en.py @@ -141,6 +141,7 @@ en = { "switch-file-not-found-error" : u"Could not switch to file '{0}'. Syncplay looks in specified media directories.", # File not found "folder-search-timeout-error" : u"The search for media in media directories was aborted as it took too long to search through '{}'. This will occur if you select a folder with too many sub-folders in your list of media folders to search through. For automatic file switching to work again please select File->Set Media Directories in the menu bar and remove this directory or replace it with an appropriate sub-folder.", #Folder "folder-search-first-file-timeout-error" : u"The search for media in '{}' was aborted as it took too long to access the directory. This could happen if it is a network drive or if you configure your drive to spin down after a period of inactivity. For automatic file switching to work again please go to File->Set Media Directories and either remove the directory or resolve the issue (e.g. by changing power saving settings).", #Folder + "added-file-not-in-media-directory-error" : u"You loaded a file in '{}' which is not a known media directory. You can add this as a media directory by selecting File->Set Media Directories in the menu bar.", #Folder "failed-to-load-server-list-error" : u"Failed to load public server list. Please visit http://www.syncplay.pl/ in your browser.", diff --git a/syncplay/messages_ru.py b/syncplay/messages_ru.py index 6179e8a..5efbc89 100644 --- a/syncplay/messages_ru.py +++ b/syncplay/messages_ru.py @@ -141,6 +141,7 @@ ru = { "switch-file-not-found-error" : u"Невозможно переключиться на файл '{0}'. Syncplay looks in the folder specified media directories.", # File not found # TODO: Translate last part into Russian "folder-search-timeout-error" : u"The search for media in media directories was aborted as it took too long to search through '{}'. This will occur if you select a folder with too many sub-folders in your list of media folders to search through. For automatic file switching to work again please select File->Set Media Directories in the menu bar and remove this directory or replace it with an appropriate sub-folder.", #Folder # TODO: Translate "folder-search-first-file-timeout-error" : u"The search for media in '{}' was aborted as it took too long to access the directory. This could happen if it is a network drive or if you configure your drive to spin down after a period of inactivity. For automatic file switching to work again please go to File->Set Media Directories and either remove the directory or resolve the issue (e.g. by changing power saving settings).", #Folder # TODO: Translate + "added-file-not-in-media-directory-error" : u"You loaded a file in '{}' which is not a known media directory. You can add this as a media directory by selecting File->Set Media Directories in the menu bar.", #Folder #TODO: Translate "failed-to-load-server-list-error" : u"Failed to load public server list. Please visit http://www.syncplay.pl/ in your browser.", # TODO: Translate into Russian diff --git a/syncplay/ui/gui.py b/syncplay/ui/gui.py index c35943b..40b5ddc 100644 --- a/syncplay/ui/gui.py +++ b/syncplay/ui/gui.py @@ -1348,6 +1348,7 @@ class MainWindow(QtGui.QMainWindow): self.playlist.addItem(filename) else: self.playlist.insertItem(index, filename) + self._syncplayClient.fileSwitch.notifyUserIfFileNotInMediaDirectory(filename, filePath) elif isURL(filePath): self.removePlaylistNote() if self.noPlaylistDuplicates(filePath): From f4ca4c5c0073fec6d15dfea2556fb180df804ff3 Mon Sep 17 00:00:00 2001 From: Et0h Date: Thu, 2 Jun 2016 18:06:39 +0100 Subject: [PATCH 113/134] Display error if no media directories are set, and reset cache when media directories set to blank, and don't switch to None index in playlist --- syncplay/client.py | 17 ++++++++++++----- syncplay/messages_de.py | 1 + syncplay/messages_en.py | 1 + syncplay/messages_ru.py | 1 + syncplay/ui/gui.py | 2 ++ 5 files changed, 17 insertions(+), 5 deletions(-) diff --git a/syncplay/client.py b/syncplay/client.py index 23c21e8..1063882 100644 --- a/syncplay/client.py +++ b/syncplay/client.py @@ -1405,6 +1405,9 @@ class SyncplayPlaylist(): return try: + if index is None: + self._ui.showDebugMessage(u"Cannot switch to None index in playlist") + return filename = self._playlist[index] # TODO: Handle isse with index being None if utils.isURL(filename): @@ -1597,14 +1600,18 @@ class FileSwitchManager(object): self.currentDirectory = curDir def changeMediaDirectories(self, mediaDirs): - if mediaDirs <> self._client._config["mediaSearchDirectories"]: - from syncplay.ui.ConfigurationGetter import ConfigurationGetter - ConfigurationGetter().setConfigOption("mediaSearchDirectories", mediaDirs) - self._client._config["mediaSearchDirectories"] = mediaDirs - self._client.ui.showMessage(getMessage("media-directory-list-updated-notification")) + from syncplay.ui.ConfigurationGetter import ConfigurationGetter + ConfigurationGetter().setConfigOption("mediaSearchDirectories", mediaDirs) + self._client._config["mediaSearchDirectories"] = mediaDirs + self._client.ui.showMessage(getMessage("media-directory-list-updated-notification")) self.mediaDirectoriesNotFound = [] self.folderSearchEnabled = True self.setMediaDirectories(mediaDirs) + if mediaDirs == "": + self._client.ui.showErrorMessage(getMessage("no-media-directories-error")) + self.mediaFilesCache = {} + self.newInfo = True + self.checkForFileSwitchUpdate() def setMediaDirectories(self, mediaDirs): self.mediaDirectories = mediaDirs diff --git a/syncplay/messages_de.py b/syncplay/messages_de.py index 89a824f..658ef63 100644 --- a/syncplay/messages_de.py +++ b/syncplay/messages_de.py @@ -142,6 +142,7 @@ de = { "folder-search-timeout-error" : u"The search for media in media directories was aborted as it took too long to search through '{}'. This will occur if you select a folder with too many sub-folders in your list of media folders to search through. For automatic file switching to work again please select File->Set Media Directories in the menu bar and remove this directory or replace it with an appropriate sub-folder.", #Folder # TODO: Translate "folder-search-first-file-timeout-error" : u"The search for media in '{}' was aborted as it took too long to access the directory. This could happen if it is a network drive or if you configure your drive to spin down after a period of inactivity. For automatic file switching to work again please go to File->Set Media Directories and either remove the directory or resolve the issue (e.g. by changing power saving settings).", #Folder # TODO: Translate "added-file-not-in-media-directory-error" : u"You loaded a file in '{}' which is not a known media directory. You can add this as a media directory by selecting File->Set Media Directories in the menu bar.", #Folder # TODO: Translate + "no-media-directories-error" : u"No media directories have been set. For shared playlist and file switching features to work properly please select File->Set Media Directories and specify where Syncplay should look to find media files.", # TODO: Translate "failed-to-load-server-list-error" : u"Konnte die Liste der öffentlichen Server nicht laden. Bitte besuche http://www.syncplay.pl/ [Englisch] mit deinem Browser.", diff --git a/syncplay/messages_en.py b/syncplay/messages_en.py index ba99bec..08347ac 100644 --- a/syncplay/messages_en.py +++ b/syncplay/messages_en.py @@ -142,6 +142,7 @@ en = { "folder-search-timeout-error" : u"The search for media in media directories was aborted as it took too long to search through '{}'. This will occur if you select a folder with too many sub-folders in your list of media folders to search through. For automatic file switching to work again please select File->Set Media Directories in the menu bar and remove this directory or replace it with an appropriate sub-folder.", #Folder "folder-search-first-file-timeout-error" : u"The search for media in '{}' was aborted as it took too long to access the directory. This could happen if it is a network drive or if you configure your drive to spin down after a period of inactivity. For automatic file switching to work again please go to File->Set Media Directories and either remove the directory or resolve the issue (e.g. by changing power saving settings).", #Folder "added-file-not-in-media-directory-error" : u"You loaded a file in '{}' which is not a known media directory. You can add this as a media directory by selecting File->Set Media Directories in the menu bar.", #Folder + "no-media-directories-error" : u"No media directories have been set. For shared playlist and file switching features to work properly please select File->Set Media Directories and specify where Syncplay should look to find media files.", "failed-to-load-server-list-error" : u"Failed to load public server list. Please visit http://www.syncplay.pl/ in your browser.", diff --git a/syncplay/messages_ru.py b/syncplay/messages_ru.py index 5efbc89..45db12c 100644 --- a/syncplay/messages_ru.py +++ b/syncplay/messages_ru.py @@ -142,6 +142,7 @@ ru = { "folder-search-timeout-error" : u"The search for media in media directories was aborted as it took too long to search through '{}'. This will occur if you select a folder with too many sub-folders in your list of media folders to search through. For automatic file switching to work again please select File->Set Media Directories in the menu bar and remove this directory or replace it with an appropriate sub-folder.", #Folder # TODO: Translate "folder-search-first-file-timeout-error" : u"The search for media in '{}' was aborted as it took too long to access the directory. This could happen if it is a network drive or if you configure your drive to spin down after a period of inactivity. For automatic file switching to work again please go to File->Set Media Directories and either remove the directory or resolve the issue (e.g. by changing power saving settings).", #Folder # TODO: Translate "added-file-not-in-media-directory-error" : u"You loaded a file in '{}' which is not a known media directory. You can add this as a media directory by selecting File->Set Media Directories in the menu bar.", #Folder #TODO: Translate + "no-media-directories-error" : u"No media directories have been set. For shared playlist and file switching features to work properly please select File->Set Media Directories and specify where Syncplay should look to find media files.", # TODO: Translate "failed-to-load-server-list-error" : u"Failed to load public server list. Please visit http://www.syncplay.pl/ in your browser.", # TODO: Translate into Russian diff --git a/syncplay/ui/gui.py b/syncplay/ui/gui.py index 40b5ddc..17283f4 100644 --- a/syncplay/ui/gui.py +++ b/syncplay/ui/gui.py @@ -299,6 +299,8 @@ class MainWindow(QtGui.QMainWindow): self.playlistGroup.setChecked(self.config['sharedPlaylistEnabled']) self.playlistGroup.blockSignals(False) self._syncplayClient.fileSwitch.setMediaDirectories(self.config["mediaSearchDirectories"]) + if not self.config["mediaSearchDirectories"]: + self._syncplayClient.ui.showErrorMessage(getMessage("no-media-directories-error")) self.updateReadyState(self.config['readyAtStart']) autoplayInitialState = self.config['autoplayInitialState'] if autoplayInitialState is not None: From deaed4c97f5578ab618fd4997c8c1fa3f9d9c364 Mon Sep 17 00:00:00 2001 From: Et0h Date: Thu, 2 Jun 2016 18:25:38 +0100 Subject: [PATCH 114/134] Add 'set media directories' to playlist context menu --- syncplay/ui/gui.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/syncplay/ui/gui.py b/syncplay/ui/gui.py index 17283f4..3b1e331 100644 --- a/syncplay/ui/gui.py +++ b/syncplay/ui/gui.py @@ -497,6 +497,8 @@ class MainWindow(QtGui.QMainWindow): menu.addAction(QtGui.QPixmap(resourcespath + "arrow_undo.png"), getMessage("undoplaylist-menu-label"), lambda: self.undoPlaylistChange()) menu.addAction(QtGui.QPixmap(resourcespath + "film_add.png"),getMessage("addfilestoplaylist-menu-label"), lambda: self.OpenAddFilesToPlaylistDialog()) menu.addAction(QtGui.QPixmap(resourcespath + "world_add.png"), getMessage("addurlstoplaylist-menu-label"), lambda: self.OpenAddURIsToPlaylistDialog()) + menu.addSeparator() + menu.addAction(QtGui.QPixmap(resourcespath + "film_folder_edit.png"), getMessage("setmediadirectories-menu-label"), lambda: self.openSetMediaDirectoriesDialog()) menu.exec_(self.playlist.viewport().mapToGlobal(position)) From 0205d4b4212f8f6f81f5e833d55f384f039e500d Mon Sep 17 00:00:00 2001 From: Et0h Date: Thu, 2 Jun 2016 19:12:37 +0100 Subject: [PATCH 115/134] Add label below AddURI and SetMediaDir dialogs & move AddURI message to messages_*.py --- syncplay/messages_de.py | 2 ++ syncplay/messages_en.py | 2 ++ syncplay/messages_ru.py | 2 ++ syncplay/ui/gui.py | 14 +++++++++----- 4 files changed, 15 insertions(+), 5 deletions(-) diff --git a/syncplay/messages_de.py b/syncplay/messages_de.py index 658ef63..9f1b6c7 100644 --- a/syncplay/messages_de.py +++ b/syncplay/messages_de.py @@ -279,6 +279,8 @@ de = { "promptforstreamurl-msgbox-label" : u"Stream URL öffnen", "promptforstreamurlinfo-msgbox-label" : u"Stream URL", + "adduris-msgbox-label" : u"Add URLs to playlist (one per line)", # TODO: Translate + "createcontrolledroom-msgbox-label" : u"Zentral gesteuerten Raum erstellen", "controlledroominfo-msgbox-label" : u"Namen des zentral gesteuerten Raums eingeben\r\n(siehe http://syncplay.pl/guide/ für eine Anleitung [Englisch]):", diff --git a/syncplay/messages_en.py b/syncplay/messages_en.py index 08347ac..280fd92 100644 --- a/syncplay/messages_en.py +++ b/syncplay/messages_en.py @@ -281,6 +281,8 @@ en = { "promptforstreamurl-msgbox-label" : "Open media stream URL", "promptforstreamurlinfo-msgbox-label" : "Stream URL", + "adduris-msgbox-label" : u"Add URLs to playlist (one per line)", + "createcontrolledroom-msgbox-label" : "Create managed room", "controlledroominfo-msgbox-label" : "Enter name of managed room\r\n(see http://syncplay.pl/guide/ for usage instructions):", diff --git a/syncplay/messages_ru.py b/syncplay/messages_ru.py index 45db12c..2c48970 100644 --- a/syncplay/messages_ru.py +++ b/syncplay/messages_ru.py @@ -281,6 +281,8 @@ ru = { "promptforstreamurl-msgbox-label" : u"Открыть URL потокового вещания", "promptforstreamurlinfo-msgbox-label" : u"URL потока", + "adduris-msgbox-label" : u"Add URLs to playlist (one per line)", # TODO: Translate + "createcontrolledroom-msgbox-label" : u"Создать управляемую комнату", "controlledroominfo-msgbox-label" : u"Введите имя управляемой комнаты\r\n(см. инструкцию на странице http://syncplay.pl/guide/):", diff --git a/syncplay/ui/gui.py b/syncplay/ui/gui.py index 3b1e331..f9570f6 100644 --- a/syncplay/ui/gui.py +++ b/syncplay/ui/gui.py @@ -780,17 +780,19 @@ class MainWindow(QtGui.QMainWindow): @needsClient def OpenAddURIsToPlaylistDialog(self): URIsDialog = QtGui.QDialog() - URIsDialog.setWindowTitle(u"Add URLs to playlist (one per line)") # TODO: Move to messages_*.py + URIsDialog.setWindowTitle(getMessage("adduris-msgbox-label")) URIsLayout = QtGui.QGridLayout() + URIsLabel = QtGui.QLabel(getMessage("adduris-msgbox-label")) + URIsLayout.addWidget(URIsLabel, 0, 0, 1, 1) URIsTextbox = QtGui.QPlainTextEdit() URIsTextbox.setLineWrapMode(QtGui.QPlainTextEdit.NoWrap) - URIsLayout.addWidget(URIsTextbox, 0, 0, 1, 1) + URIsLayout.addWidget(URIsTextbox, 1, 0, 1, 1) URIsButtonBox = QtGui.QDialogButtonBox() URIsButtonBox.setOrientation(Qt.Horizontal) URIsButtonBox.setStandardButtons(QtGui.QDialogButtonBox.Ok|QtGui.QDialogButtonBox.Cancel) URIsButtonBox.accepted.connect(URIsDialog.accept) URIsButtonBox.rejected.connect(URIsDialog.reject) - URIsLayout.addWidget(URIsButtonBox, 1, 0, 1, 1) + URIsLayout.addWidget(URIsButtonBox, 2, 0, 1, 1) URIsDialog.setLayout(URIsLayout) URIsDialog.show() result = URIsDialog.exec_() @@ -805,16 +807,18 @@ class MainWindow(QtGui.QMainWindow): MediaDirectoriesDialog = QtGui.QDialog() MediaDirectoriesDialog.setWindowTitle(getMessage("syncplay-mediasearchdirectories-title")) # TODO: Move to messages_*.py MediaDirectoriesLayout = QtGui.QGridLayout() + MediaDirectoriesLabel = QtGui.QLabel(getMessage("syncplay-mediasearchdirectories-title")) + MediaDirectoriesLayout.addWidget(MediaDirectoriesLabel, 0, 0, 1, 1) MediaDirectoriesTextbox = QtGui.QPlainTextEdit() MediaDirectoriesTextbox.setLineWrapMode(QtGui.QPlainTextEdit.NoWrap) MediaDirectoriesTextbox.setPlainText(utils.getListAsMultilineString(self.config["mediaSearchDirectories"])) - MediaDirectoriesLayout.addWidget(MediaDirectoriesTextbox, 0, 0, 1, 1) + MediaDirectoriesLayout.addWidget(MediaDirectoriesTextbox, 1, 0, 1, 1) MediaDirectoriesButtonBox = QtGui.QDialogButtonBox() MediaDirectoriesButtonBox.setOrientation(Qt.Horizontal) MediaDirectoriesButtonBox.setStandardButtons(QtGui.QDialogButtonBox.Ok|QtGui.QDialogButtonBox.Cancel) MediaDirectoriesButtonBox.accepted.connect(MediaDirectoriesDialog.accept) MediaDirectoriesButtonBox.rejected.connect(MediaDirectoriesDialog.reject) - MediaDirectoriesLayout.addWidget(MediaDirectoriesButtonBox, 1, 0, 1, 1) + MediaDirectoriesLayout.addWidget(MediaDirectoriesButtonBox, 2, 0, 1, 1) MediaDirectoriesDialog.setLayout(MediaDirectoriesLayout) MediaDirectoriesDialog.show() result = MediaDirectoriesDialog.exec_() From d93fcb56501ee1df60f61b1c58930190cbe7373d Mon Sep 17 00:00:00 2001 From: Et0h Date: Thu, 2 Jun 2016 19:58:20 +0100 Subject: [PATCH 116/134] Add "Add folder" button to "Set media directories" dialog --- syncplay/messages_de.py | 2 ++ syncplay/messages_en.py | 2 ++ syncplay/messages_ru.py | 2 ++ syncplay/ui/gui.py | 25 +++++++++++++++++++++---- 4 files changed, 27 insertions(+), 4 deletions(-) diff --git a/syncplay/messages_de.py b/syncplay/messages_de.py index 9f1b6c7..a02fa66 100644 --- a/syncplay/messages_de.py +++ b/syncplay/messages_de.py @@ -279,6 +279,8 @@ de = { "promptforstreamurl-msgbox-label" : u"Stream URL öffnen", "promptforstreamurlinfo-msgbox-label" : u"Stream URL", + "addfolder-label" : u"Add folder", # TODO: Translate + "adduris-msgbox-label" : u"Add URLs to playlist (one per line)", # TODO: Translate "createcontrolledroom-msgbox-label" : u"Zentral gesteuerten Raum erstellen", diff --git a/syncplay/messages_en.py b/syncplay/messages_en.py index 280fd92..055ce4d 100644 --- a/syncplay/messages_en.py +++ b/syncplay/messages_en.py @@ -281,6 +281,8 @@ en = { "promptforstreamurl-msgbox-label" : "Open media stream URL", "promptforstreamurlinfo-msgbox-label" : "Stream URL", + "addfolder-label" : u"Add folder", + "adduris-msgbox-label" : u"Add URLs to playlist (one per line)", "createcontrolledroom-msgbox-label" : "Create managed room", diff --git a/syncplay/messages_ru.py b/syncplay/messages_ru.py index 2c48970..660ced1 100644 --- a/syncplay/messages_ru.py +++ b/syncplay/messages_ru.py @@ -281,6 +281,8 @@ ru = { "promptforstreamurl-msgbox-label" : u"Открыть URL потокового вещания", "promptforstreamurlinfo-msgbox-label" : u"URL потока", + "addfolder-label" : u"Add folder", # TODO: Translate + "adduris-msgbox-label" : u"Add URLs to playlist (one per line)", # TODO: Translate "createcontrolledroom-msgbox-label" : u"Создать управляемую комнату", diff --git a/syncplay/ui/gui.py b/syncplay/ui/gui.py index f9570f6..909119e 100644 --- a/syncplay/ui/gui.py +++ b/syncplay/ui/gui.py @@ -710,10 +710,10 @@ class MainWindow(QtGui.QMainWindow): settings.setValue("mediadir", self.mediadirectory) settings.endGroup() - def getInitialMediaDirectory(self): - if self.config["mediaSearchDirectories"] and os.path.isdir(self.config["mediaSearchDirectories"][0]): + def getInitialMediaDirectory(self, includeUserSpecifiedDirectories=True): + if self.config["mediaSearchDirectories"] and os.path.isdir(self.config["mediaSearchDirectories"][0]) and includeUserSpecifiedDirectories: defaultdirectory = self.config["mediaSearchDirectories"][0] - elif os.path.isdir(self.mediadirectory): + elif includeUserSpecifiedDirectories and os.path.isdir(self.mediadirectory): defaultdirectory = self.mediadirectory elif os.path.isdir(QtGui.QDesktopServices.storageLocation(QtGui.QDesktopServices.MoviesLocation)): defaultdirectory = QtGui.QDesktopServices.storageLocation(QtGui.QDesktopServices.MoviesLocation) @@ -803,12 +803,13 @@ class MainWindow(QtGui.QMainWindow): self.addStreamToPlaylist(URI) self.updatingPlaylist = False + @needsClient def openSetMediaDirectoriesDialog(self): MediaDirectoriesDialog = QtGui.QDialog() MediaDirectoriesDialog.setWindowTitle(getMessage("syncplay-mediasearchdirectories-title")) # TODO: Move to messages_*.py MediaDirectoriesLayout = QtGui.QGridLayout() MediaDirectoriesLabel = QtGui.QLabel(getMessage("syncplay-mediasearchdirectories-title")) - MediaDirectoriesLayout.addWidget(MediaDirectoriesLabel, 0, 0, 1, 1) + MediaDirectoriesLayout.addWidget(MediaDirectoriesLabel, 0, 0, 1, 2) MediaDirectoriesTextbox = QtGui.QPlainTextEdit() MediaDirectoriesTextbox.setLineWrapMode(QtGui.QPlainTextEdit.NoWrap) MediaDirectoriesTextbox.setPlainText(utils.getListAsMultilineString(self.config["mediaSearchDirectories"])) @@ -819,6 +820,9 @@ class MainWindow(QtGui.QMainWindow): MediaDirectoriesButtonBox.accepted.connect(MediaDirectoriesDialog.accept) MediaDirectoriesButtonBox.rejected.connect(MediaDirectoriesDialog.reject) MediaDirectoriesLayout.addWidget(MediaDirectoriesButtonBox, 2, 0, 1, 1) + MediaDirectoriesAddFolderButton = QtGui.QPushButton(getMessage("addfolder-label")) + MediaDirectoriesAddFolderButton.pressed.connect(lambda: self.openAddMediaDirectoryDialog(MediaDirectoriesTextbox, MediaDirectoriesDialog)) + MediaDirectoriesLayout.addWidget(MediaDirectoriesAddFolderButton, 1, 1, 1, 1, Qt.AlignTop) MediaDirectoriesDialog.setLayout(MediaDirectoriesLayout) MediaDirectoriesDialog.show() result = MediaDirectoriesDialog.exec_() @@ -826,6 +830,19 @@ class MainWindow(QtGui.QMainWindow): newMediaDirectories = utils.convertMultilineStringToList(MediaDirectoriesTextbox.toPlainText()) self._syncplayClient.fileSwitch.changeMediaDirectories(newMediaDirectories) + @needsClient + def openAddMediaDirectoryDialog(self, MediaDirectoriesTextbox, MediaDirectoriesDialog): + folderName = unicode(QtGui.QFileDialog.getExistingDirectory(self,None,self.getInitialMediaDirectory(includeUserSpecifiedDirectories=False),QtGui.QFileDialog.ShowDirsOnly)) + if folderName: + existingMediaDirs = MediaDirectoriesTextbox.toPlainText() + if existingMediaDirs == "": + newMediaDirList = folderName + else: + newMediaDirList = existingMediaDirs + u"\n" + folderName + MediaDirectoriesTextbox.setPlainText(newMediaDirList) + MediaDirectoriesDialog.raise_() + MediaDirectoriesDialog.activateWindow() + @needsClient def promptForStreamURL(self): streamURL, ok = QtGui.QInputDialog.getText(self, getMessage("promptforstreamurl-msgbox-label"), From 1970c6029cc293fd86ca6e05867a0f61683dd1e8 Mon Sep 17 00:00:00 2001 From: Et0h Date: Thu, 2 Jun 2016 20:10:07 +0100 Subject: [PATCH 117/134] Make AddURI and setMediaDir dialogs modal so they can't be lost --- syncplay/ui/gui.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/syncplay/ui/gui.py b/syncplay/ui/gui.py index 909119e..4d776bd 100644 --- a/syncplay/ui/gui.py +++ b/syncplay/ui/gui.py @@ -794,6 +794,7 @@ class MainWindow(QtGui.QMainWindow): URIsButtonBox.rejected.connect(URIsDialog.reject) URIsLayout.addWidget(URIsButtonBox, 2, 0, 1, 1) URIsDialog.setLayout(URIsLayout) + URIsDialog.setModal(True) URIsDialog.show() result = URIsDialog.exec_() if result == QtGui.QDialog.Accepted: @@ -824,6 +825,7 @@ class MainWindow(QtGui.QMainWindow): MediaDirectoriesAddFolderButton.pressed.connect(lambda: self.openAddMediaDirectoryDialog(MediaDirectoriesTextbox, MediaDirectoriesDialog)) MediaDirectoriesLayout.addWidget(MediaDirectoriesAddFolderButton, 1, 1, 1, 1, Qt.AlignTop) MediaDirectoriesDialog.setLayout(MediaDirectoriesLayout) + MediaDirectoriesDialog.setModal(True) MediaDirectoriesDialog.show() result = MediaDirectoriesDialog.exec_() if result == QtGui.QDialog.Accepted: From 8a4975b0416f5f1421fdf7bffc704e24faf6fdc9 Mon Sep 17 00:00:00 2001 From: Et0h Date: Thu, 2 Jun 2016 20:21:39 +0100 Subject: [PATCH 118/134] Use better error message when trying to add folder to playlist --- syncplay/ui/gui.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/syncplay/ui/gui.py b/syncplay/ui/gui.py index 4d776bd..6f017f8 100644 --- a/syncplay/ui/gui.py +++ b/syncplay/ui/gui.py @@ -1412,7 +1412,7 @@ class MainWindow(QtGui.QMainWindow): self.clearedPlaylistNote = True def addFolderToPlaylist(self, folderPath): - self.showErrorMessage("Add Folder {}".format(folderPath)) # TODO: Implement "add folder to playlist" + self.showErrorMessage(u"You tried to add the folder '{}' to the playlist. Syncplay only currently supports adding files to the playlist.".format(folderPath)) # TODO: Implement "add folder to playlist" def deleteSelectedPlaylistItems(self): self.playlist.remove_selected_items() From 5bf136c58f4423e627a964e687aaaabb9a33af45 Mon Sep 17 00:00:00 2001 From: Et0h Date: Thu, 2 Jun 2016 20:59:27 +0100 Subject: [PATCH 119/134] Give error when media directory does not exist --- syncplay/client.py | 3 +++ syncplay/messages_de.py | 1 + syncplay/messages_en.py | 1 + syncplay/messages_ru.py | 1 + 4 files changed, 6 insertions(+) diff --git a/syncplay/client.py b/syncplay/client.py index 1063882..0913a80 100644 --- a/syncplay/client.py +++ b/syncplay/client.py @@ -1642,6 +1642,9 @@ class FileSwitchManager(object): # Spin up hard drives to prevent premature timeout randomFilename = u"RandomFile"+unicode(random.randrange(10000, 99999))+".txt" for directory in dirsToSearch: + if not os.path.isdir(directory): + self.directorySearchError = getMessage("cannot-find-directory-error").format(directory) + startTime = time.time() if os.path.isfile(os.path.join(directory, randomFilename)): randomFilename = u"RandomFile"+unicode(random.randrange(10000, 99999))+".txt" diff --git a/syncplay/messages_de.py b/syncplay/messages_de.py index a02fa66..7e8844a 100644 --- a/syncplay/messages_de.py +++ b/syncplay/messages_de.py @@ -143,6 +143,7 @@ de = { "folder-search-first-file-timeout-error" : u"The search for media in '{}' was aborted as it took too long to access the directory. This could happen if it is a network drive or if you configure your drive to spin down after a period of inactivity. For automatic file switching to work again please go to File->Set Media Directories and either remove the directory or resolve the issue (e.g. by changing power saving settings).", #Folder # TODO: Translate "added-file-not-in-media-directory-error" : u"You loaded a file in '{}' which is not a known media directory. You can add this as a media directory by selecting File->Set Media Directories in the menu bar.", #Folder # TODO: Translate "no-media-directories-error" : u"No media directories have been set. For shared playlist and file switching features to work properly please select File->Set Media Directories and specify where Syncplay should look to find media files.", # TODO: Translate + "cannot-find-directory-error" : u"Could not find media directory '{}'. To update your list of media directories please select File->Set Media Directories from the menu bar and specify where Syncplay should look to find media files.", # TODO: Translate "failed-to-load-server-list-error" : u"Konnte die Liste der öffentlichen Server nicht laden. Bitte besuche http://www.syncplay.pl/ [Englisch] mit deinem Browser.", diff --git a/syncplay/messages_en.py b/syncplay/messages_en.py index 055ce4d..2aafa48 100644 --- a/syncplay/messages_en.py +++ b/syncplay/messages_en.py @@ -143,6 +143,7 @@ en = { "folder-search-first-file-timeout-error" : u"The search for media in '{}' was aborted as it took too long to access the directory. This could happen if it is a network drive or if you configure your drive to spin down after a period of inactivity. For automatic file switching to work again please go to File->Set Media Directories and either remove the directory or resolve the issue (e.g. by changing power saving settings).", #Folder "added-file-not-in-media-directory-error" : u"You loaded a file in '{}' which is not a known media directory. You can add this as a media directory by selecting File->Set Media Directories in the menu bar.", #Folder "no-media-directories-error" : u"No media directories have been set. For shared playlist and file switching features to work properly please select File->Set Media Directories and specify where Syncplay should look to find media files.", + "cannot-find-directory-error" : u"Could not find media directory '{}'. To update your list of media directories please select File->Set Media Directories from the menu bar and specify where Syncplay should look to find media files.", "failed-to-load-server-list-error" : u"Failed to load public server list. Please visit http://www.syncplay.pl/ in your browser.", diff --git a/syncplay/messages_ru.py b/syncplay/messages_ru.py index 660ced1..1b98007 100644 --- a/syncplay/messages_ru.py +++ b/syncplay/messages_ru.py @@ -143,6 +143,7 @@ ru = { "folder-search-first-file-timeout-error" : u"The search for media in '{}' was aborted as it took too long to access the directory. This could happen if it is a network drive or if you configure your drive to spin down after a period of inactivity. For automatic file switching to work again please go to File->Set Media Directories and either remove the directory or resolve the issue (e.g. by changing power saving settings).", #Folder # TODO: Translate "added-file-not-in-media-directory-error" : u"You loaded a file in '{}' which is not a known media directory. You can add this as a media directory by selecting File->Set Media Directories in the menu bar.", #Folder #TODO: Translate "no-media-directories-error" : u"No media directories have been set. For shared playlist and file switching features to work properly please select File->Set Media Directories and specify where Syncplay should look to find media files.", # TODO: Translate + "cannot-find-directory-error" : u"Could not find media directory '{}'. To update your list of media directories please select File->Set Media Directories from the menu bar and specify where Syncplay should look to find media files.", # TODO: Translate "failed-to-load-server-list-error" : u"Failed to load public server list. Please visit http://www.syncplay.pl/ in your browser.", # TODO: Translate into Russian From 8ad4ca586e35174be2244b169aa9f82e103bfdb0 Mon Sep 17 00:00:00 2001 From: Et0h Date: Fri, 3 Jun 2016 13:05:01 +0100 Subject: [PATCH 120/134] MPC should be considered 'not file ready' if it has just closed a file --- syncplay/players/mpc.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/syncplay/players/mpc.py b/syncplay/players/mpc.py index 6ab1452..c38b074 100644 --- a/syncplay/players/mpc.py +++ b/syncplay/players/mpc.py @@ -103,7 +103,7 @@ class MpcHcApi: elif cmd == self.CMD_STATE: self.loadState = int(value) - fileNotReady = self.loadState == self.__MPC_LOADSTATE.MLS_CLOSING or self.loadState == self.__MPC_LOADSTATE.MLS_LOADING + fileNotReady = self.loadState == self.__MPC_LOADSTATE.MLS_CLOSING or self.loadState == self.__MPC_LOADSTATE.MLS_LOADING or self.loadState == self.__MPC_LOADSTATE.MLS_CLOSED if fileNotReady: self.playState = None self.__locks.fileReady.clear() From 66087de5f0d2775f8ca5b04abfc8032fcdc97488 Mon Sep 17 00:00:00 2001 From: Et0h Date: Sat, 11 Jun 2016 13:24:05 +0100 Subject: [PATCH 121/134] Check if directory is in media directory, not just media cache, to avoid false negative if cache is unpopulated --- syncplay/client.py | 26 ++++++++++++++++++-------- 1 file changed, 18 insertions(+), 8 deletions(-) diff --git a/syncplay/client.py b/syncplay/client.py index 0913a80..edc347a 100644 --- a/syncplay/client.py +++ b/syncplay/client.py @@ -1731,19 +1731,29 @@ class FileSwitchManager(object): return directory return None + def isDirectoryInList(self, directoryToFind, folderList): + if directoryToFind and folderList: + normedDirectoryToFind = os.path.normcase(os.path.normpath(directoryToFind)) + for listedFolder in folderList: + normedListedFolder = os.path.normcase(os.path.normpath(listedFolder)) + if normedDirectoryToFind.startswith(normedListedFolder): + return True + return False + def notifyUserIfFileNotInMediaDirectory(self, filenameToFind, path): directoryToFind = os.path.dirname(path) if directoryToFind in self.mediaDirectoriesNotFound: return - if self.mediaDirectories and self.mediaFilesCache is not None: - if self.mediaFilesCache: + if self.mediaDirectories is not None and self.mediaFilesCache is not None: + if directoryToFind in self.mediaFilesCache: + return + for directory in self.mediaFilesCache: + files = self.mediaFilesCache[directory] + if filenameToFind in files: + return if directoryToFind in self.mediaFilesCache: return - for directory in self.mediaFilesCache: - files = self.mediaFilesCache[directory] - if filenameToFind in files: - return - if directoryToFind in self.mediaFilesCache: - return + if self.isDirectoryInList(directoryToFind, self.mediaDirectories): + return self._client.ui.showErrorMessage(getMessage("added-file-not-in-media-directory-error").format(directoryToFind)) self.mediaDirectoriesNotFound.append(directoryToFind) \ No newline at end of file From 9ef4cb3b3b572c1ebe59039a30fa03f6220c7749 Mon Sep 17 00:00:00 2001 From: Et0h Date: Sat, 11 Jun 2016 13:25:40 +0100 Subject: [PATCH 122/134] Fix path of film_folder_edit.png (icon for setting media directories) --- buildPy2exe.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/buildPy2exe.py b/buildPy2exe.py index 3a0d09b..673325d 100644 --- a/buildPy2exe.py +++ b/buildPy2exe.py @@ -655,7 +655,7 @@ guiIcons = ['resources/accept.png', 'resources/arrow_undo.png', 'resources/clock 'resources/tick.png', 'resources/lock_open.png', 'resources/empty_checkbox.png', 'resources/tick_checkbox.png', 'resources/world_explore.png', 'resources/application_get.png', 'resources/cog.png', 'resources/arrow_switch.png', 'resources/film_go.png', 'resources/world_go.png', 'resources/arrow_refresh.png', 'resources/bullet_right_grey.png', - 'film_folder_edit.png', + 'resources/film_folder_edit.png', 'resources/world_add.png', 'resources/film_add.png', 'resources/delete.png', 'resources/spinner.mng' ] resources = ["resources/icon.ico", "resources/syncplay.png"] From 1e9e9609f22d8b5baf372aab91c40e6fc293f6d7 Mon Sep 17 00:00:00 2001 From: Et0h Date: Sat, 11 Jun 2016 13:41:29 +0100 Subject: [PATCH 123/134] Increase use of Unicode in strings --- syncplay/client.py | 18 +++---- syncplay/messages_en.py | 14 ++--- syncplay/players/mpv.py | 2 +- syncplay/ui/GuiConfiguration.py | 34 ++++++------ syncplay/ui/gui.py | 92 ++++++++++++++++----------------- 5 files changed, 80 insertions(+), 80 deletions(-) diff --git a/syncplay/client.py b/syncplay/client.py index edc347a..de75975 100644 --- a/syncplay/client.py +++ b/syncplay/client.py @@ -129,7 +129,7 @@ class SyncplayClient(object): if constants.DEBUG_MODE and constants.WARN_ABOUT_MISSING_STRINGS: missingStrings = getMissingStrings() if missingStrings is not None and missingStrings is not "": - self.ui.showDebugMessage("MISSING/UNUSED STRINGS DETECTED:\n{}".format(missingStrings)) + self.ui.showDebugMessage(u"MISSING/UNUSED STRINGS DETECTED:\n{}".format(missingStrings)) def initProtocol(self, protocol): self._protocol = protocol @@ -953,9 +953,9 @@ class SyncplayUser(object): def __repr__(self, *args, **kwargs): if self.file: - return "{}: {} ({}, {})".format(self.username, self.file['name'], self.file['duration'], self.file['size']) + return u"{}: {} ({}, {})".format(self.username, self.file['name'], self.file['duration'], self.file['size']) else: - return "{}".format(self.username) + return u"{}".format(self.username) def setControllerStatus(self, isController): self._controller = isController @@ -1280,7 +1280,7 @@ class UiManager(object): def showDebugMessage(self, message): if constants.DEBUG_MODE and message.rstrip(): - sys.stderr.write("{}{}\n".format(time.strftime(constants.UI_TIME_FORMAT, time.localtime()),message.rstrip())) + sys.stderr.write(u"{}{}\n".format(time.strftime(constants.UI_TIME_FORMAT, time.localtime()),message.rstrip())) def showMessage(self, message, noPlayer=False, noTimestamp=False, secondaryOSD=False): if not noPlayer: self.showOSDMessage(message, duration=constants.OSD_DURATION, secondaryOSD=secondaryOSD) @@ -1353,7 +1353,7 @@ class SyncplayPlaylist(): @wraps(f) def wrapper(self, *args, **kwds): if not self._client.sharedPlaylistIsEnabled(): - self._ui.showDebugMessage("Tried to use shared playlists when it was disabled!") + self._ui.showDebugMessage(u"Tried to use shared playlists when it was disabled!") return return f(self, *args, **kwds) return wrapper @@ -1640,14 +1640,14 @@ class FileSwitchManager(object): if dirsToSearch: # Spin up hard drives to prevent premature timeout - randomFilename = u"RandomFile"+unicode(random.randrange(10000, 99999))+".txt" + randomFilename = u"RandomFile"+unicode(random.randrange(10000, 99999))+u".txt" for directory in dirsToSearch: if not os.path.isdir(directory): self.directorySearchError = getMessage("cannot-find-directory-error").format(directory) startTime = time.time() if os.path.isfile(os.path.join(directory, randomFilename)): - randomFilename = u"RandomFile"+unicode(random.randrange(10000, 99999))+".txt" + randomFilename = u"RandomFile"+unicode(random.randrange(10000, 99999))+u".txt" if time.time() - startTime > constants.FOLDER_SEARCH_FIRST_FILE_TIMEOUT: self.folderSearchEnabled = False self.directorySearchError = getMessage("folder-search-first-file-timeout-error").format(directory) @@ -1688,11 +1688,11 @@ class FileSwitchManager(object): if highPriority and self.folderSearchEnabled: directoryList = self.mediaDirectories # Spin up hard drives to prevent premature timeout - randomFilename = u"RandomFile"+unicode(random.randrange(10000, 99999))+".txt" + randomFilename = u"RandomFile"+unicode(random.randrange(10000, 99999))+u".txt" for directory in directoryList: startTime = time.time() if os.path.isfile(os.path.join(directory, randomFilename)): - randomFilename = u"RandomFile"+unicode(random.randrange(10000, 99999))+".txt" + randomFilename = u"RandomFile"+unicode(random.randrange(10000, 99999))+u".txt" if not self.folderSearchEnabled: return if time.time() - startTime > constants.FOLDER_SEARCH_FIRST_FILE_TIMEOUT: diff --git a/syncplay/messages_en.py b/syncplay/messages_en.py index 2aafa48..e695292 100644 --- a/syncplay/messages_en.py +++ b/syncplay/messages_en.py @@ -207,7 +207,7 @@ en = { "showdifferentroomosd-label" : "Include events in other rooms", "showslowdownosd-label" :"Include slowing down / reverting notifications", "language-label" : "Language:", - "automatic-language" : "Default ({})", # Default language + "automatic-language" : u"Default ({})", # Default language "showdurationnotification-label" : "Warn about media duration mismatches", "basics-label" : "Basics", "readiness-label" : u"Play/Pause", @@ -345,7 +345,7 @@ en = { "seektime-msgbox-label" : "Jump to specified time (in seconds / min:sec). Use +/- for relative seek.", "ready-tooltip" : "Indicates whether you are ready to watch.", "autoplay-tooltip" : "Auto-play when all users who have readiness indicator are ready and minimum user threshold met.", - "switch-to-file-tooltip" : "Double click to switch to {}", # Filename + "switch-to-file-tooltip" : u"Double click to switch to {}", # Filename # In-userlist notes (GUI) "differentsize-note" : "Different size!", @@ -358,8 +358,8 @@ en = { # Server notifications "welcome-server-notification" : "Welcome to Syncplay server, ver. {0}", # version - "client-connected-room-server-notification" : "{0}({2}) connected to room '{1}'", # username, host, room - "client-left-server-notification" : "{0} left server", # name + "client-connected-room-server-notification" : u"{0}({2}) connected to room '{1}'", # username, host, room + "client-left-server-notification" : u"{0} left server", # name "no-salt-notification" : "PLEASE NOTE: To allow room operator passwords generated by this server instance to still work when the server is restarted, please add the following command line argument when running the Syncplay server in the future: --salt {}", #Salt @@ -373,13 +373,13 @@ en = { "server-disable-ready-argument" : u"disable readiness feature", "server-motd-argument": "path to file from which motd will be fetched", "server-messed-up-motd-unescaped-placeholders": "Message of the Day has unescaped placeholders. All $ signs should be doubled ($$).", - "server-messed-up-motd-too-long": "Message of the Day is too long - maximum of {} chars, {} given.", + "server-messed-up-motd-too-long": u"Message of the Day is too long - maximum of {} chars, {} given.", # Server errors - "unknown-command-server-error" : "Unknown command {}", # message + "unknown-command-server-error" : u"Unknown command {}", # message "not-json-server-error" : "Not a json encoded string {}", # message "not-known-server-error" : "You must be known to server before sending this command", - "client-drop-server-error" : "Client drop: {} -- {}", # host, error + "client-drop-server-error" : u"Client drop: {} -- {}", # host, error "password-required-server-error" : "Password required", "wrong-password-server-error" : "Wrong password supplied", "hello-server-error" : "Not enough Hello arguments", diff --git a/syncplay/players/mpv.py b/syncplay/players/mpv.py index a0b30e0..01bf160 100644 --- a/syncplay/players/mpv.py +++ b/syncplay/players/mpv.py @@ -76,7 +76,7 @@ class OldMpvPlayer(MpvPlayer): OSD_QUERY = 'show_text' def _setProperty(self, property_, value): - self._listener.sendLine("no-osd set {} {}".format(property_, value)) + self._listener.sendLine(u"no-osd set {} {}".format(property_, value)) def setPaused(self, value): if self._paused <> value: diff --git a/syncplay/ui/GuiConfiguration.py b/syncplay/ui/GuiConfiguration.py index ecb0234..fbe32d7 100644 --- a/syncplay/ui/GuiConfiguration.py +++ b/syncplay/ui/GuiConfiguration.py @@ -835,27 +835,27 @@ class ConfigDialog(QtGui.QDialog): self.showSameRoomOSDCheckbox = QCheckBox(getMessage("showsameroomosd-label")) self.showSameRoomOSDCheckbox.setObjectName("showSameRoomOSD") - self.showSameRoomOSDCheckbox.setStyleSheet(constants.STYLE_SUBCHECKBOX.format(self.posixresourcespath + "chevrons_right.png")) + self.showSameRoomOSDCheckbox.setStyleSheet(constants.STYLE_SUBCHECKBOX.format(self.posixresourcespath + u"chevrons_right.png")) self.osdSettingsLayout.addWidget(self.showSameRoomOSDCheckbox) self.showNonControllerOSDCheckbox = QCheckBox(getMessage("shownoncontrollerosd-label")) self.showNonControllerOSDCheckbox.setObjectName("showNonControllerOSD") - self.showNonControllerOSDCheckbox.setStyleSheet(constants.STYLE_SUBCHECKBOX.format(self.posixresourcespath + "chevrons_right.png")) + self.showNonControllerOSDCheckbox.setStyleSheet(constants.STYLE_SUBCHECKBOX.format(self.posixresourcespath + u"chevrons_right.png")) self.osdSettingsLayout.addWidget(self.showNonControllerOSDCheckbox) self.showDifferentRoomOSDCheckbox = QCheckBox(getMessage("showdifferentroomosd-label")) self.showDifferentRoomOSDCheckbox.setObjectName("showDifferentRoomOSD") - self.showDifferentRoomOSDCheckbox.setStyleSheet(constants.STYLE_SUBCHECKBOX.format(self.posixresourcespath + "chevrons_right.png")) + self.showDifferentRoomOSDCheckbox.setStyleSheet(constants.STYLE_SUBCHECKBOX.format(self.posixresourcespath + u"chevrons_right.png")) self.osdSettingsLayout.addWidget(self.showDifferentRoomOSDCheckbox) self.slowdownOSDCheckbox = QCheckBox(getMessage("showslowdownosd-label")) self.slowdownOSDCheckbox.setObjectName("showSlowdownOSD") - self.slowdownOSDCheckbox.setStyleSheet(constants.STYLE_SUBCHECKBOX.format(self.posixresourcespath + "chevrons_right.png")) + self.slowdownOSDCheckbox.setStyleSheet(constants.STYLE_SUBCHECKBOX.format(self.posixresourcespath + u"chevrons_right.png")) self.osdSettingsLayout.addWidget(self.slowdownOSDCheckbox) self.showOSDWarningsCheckbox = QCheckBox(getMessage("showosdwarnings-label")) self.showOSDWarningsCheckbox.setObjectName("showOSDWarnings") - self.showOSDWarningsCheckbox.setStyleSheet(constants.STYLE_SUBCHECKBOX.format(self.posixresourcespath + "chevrons_right.png")) + self.showOSDWarningsCheckbox.setStyleSheet(constants.STYLE_SUBCHECKBOX.format(self.posixresourcespath + u"chevrons_right.png")) self.osdSettingsLayout.addWidget(self.showOSDWarningsCheckbox) self.subitems['showOSD'] = ["showSameRoomOSD", "showDifferentRoomOSD", "showSlowdownOSD", "showOSDWarnings", "showNonControllerOSD"] @@ -915,20 +915,20 @@ class ConfigDialog(QtGui.QDialog): self.bottomButtonFrame = QtGui.QFrame() self.bottomButtonLayout = QtGui.QHBoxLayout() - self.helpButton = QtGui.QPushButton(QtGui.QIcon(self.resourcespath + 'help.png'), getMessage("help-label")) + self.helpButton = QtGui.QPushButton(QtGui.QIcon(self.resourcespath + u'help.png'), getMessage("help-label")) self.helpButton.setObjectName("help") self.helpButton.setMaximumSize(self.helpButton.sizeHint()) self.helpButton.pressed.connect(self.openHelp) - self.resetButton = QtGui.QPushButton(QtGui.QIcon(resourcespath + 'cog_delete.png'),getMessage("reset-label")) + self.resetButton = QtGui.QPushButton(QtGui.QIcon(resourcespath + u'cog_delete.png'),getMessage("reset-label")) self.resetButton.setMaximumSize(self.resetButton.sizeHint()) self.resetButton.setObjectName("reset") self.resetButton.pressed.connect(self.resetSettings) - self.runButton = QtGui.QPushButton(QtGui.QIcon(resourcespath + 'accept.png'), getMessage("run-label")) + self.runButton = QtGui.QPushButton(QtGui.QIcon(resourcespath + u'accept.png'), getMessage("run-label")) self.runButton.pressed.connect(self._saveDataAndLeave) self.runButton.setToolTip(getMessage("nostore-tooltip")) - self.storeAndRunButton = QtGui.QPushButton(QtGui.QIcon(resourcespath + 'accept.png'), getMessage("storeandrun-label")) + self.storeAndRunButton = QtGui.QPushButton(QtGui.QIcon(resourcespath + u'accept.png'), getMessage("storeandrun-label")) self.storeAndRunButton.pressed.connect(self._runWithoutStoringConfig) self.bottomButtonLayout.addWidget(self.helpButton) self.bottomButtonLayout.addWidget(self.resetButton) @@ -956,11 +956,11 @@ class ConfigDialog(QtGui.QDialog): self.tabListLayout = QtGui.QHBoxLayout() self.tabListFrame = QtGui.QFrame() self.tabListWidget = QtGui.QListWidget() - self.tabListWidget.addItem(QtGui.QListWidgetItem(QtGui.QIcon(self.resourcespath + "house.png"),getMessage("basics-label"))) - self.tabListWidget.addItem(QtGui.QListWidgetItem(QtGui.QIcon(self.resourcespath + "control_pause_blue.png"),getMessage("readiness-label"))) - self.tabListWidget.addItem(QtGui.QListWidgetItem(QtGui.QIcon(self.resourcespath + "film_link.png"),getMessage("sync-label"))) - self.tabListWidget.addItem(QtGui.QListWidgetItem(QtGui.QIcon(self.resourcespath + "comments.png"),getMessage("messages-label"))) - self.tabListWidget.addItem(QtGui.QListWidgetItem(QtGui.QIcon(self.resourcespath + "cog.png"),getMessage("misc-label"))) + self.tabListWidget.addItem(QtGui.QListWidgetItem(QtGui.QIcon(self.resourcespath + u"house.png"),getMessage("basics-label"))) + self.tabListWidget.addItem(QtGui.QListWidgetItem(QtGui.QIcon(self.resourcespath + u"control_pause_blue.png"),getMessage("readiness-label"))) + self.tabListWidget.addItem(QtGui.QListWidgetItem(QtGui.QIcon(self.resourcespath + u"film_link.png"),getMessage("sync-label"))) + self.tabListWidget.addItem(QtGui.QListWidgetItem(QtGui.QIcon(self.resourcespath + u"comments.png"),getMessage("messages-label"))) + self.tabListWidget.addItem(QtGui.QListWidgetItem(QtGui.QIcon(self.resourcespath + u"cog.png"),getMessage("misc-label"))) self.tabListLayout.addWidget(self.tabListWidget) self.tabListFrame.setLayout(self.tabListLayout) self.tabListFrame.setFixedWidth(self.tabListFrame.minimumSizeHint().width()) @@ -1046,15 +1046,15 @@ class ConfigDialog(QtGui.QDialog): if sys.platform.startswith('win'): resourcespath = utils.findWorkingDir() + "\\resources\\" else: - resourcespath = utils.findWorkingDir() + "/resources/" - self.posixresourcespath = utils.findWorkingDir().replace("\\","/") + "/resources/" + resourcespath = utils.findWorkingDir() + u"/resources/" + self.posixresourcespath = utils.findWorkingDir().replace(u"\\","/") + u"/resources/" self.resourcespath = resourcespath super(ConfigDialog, self).__init__() self.setWindowTitle(getMessage("config-window-title")) self.setWindowFlags(self.windowFlags() & Qt.WindowCloseButtonHint & ~Qt.WindowContextHelpButtonHint) - self.setWindowIcon(QtGui.QIcon(resourcespath + "syncplay.png")) + self.setWindowIcon(QtGui.QIcon(resourcespath + u"syncplay.png")) self.stackedLayout = QtGui.QStackedLayout() self.stackedFrame = QtGui.QFrame() diff --git a/syncplay/ui/gui.py b/syncplay/ui/gui.py index 6f017f8..6d9b127 100644 --- a/syncplay/ui/gui.py +++ b/syncplay/ui/gui.py @@ -29,12 +29,12 @@ class UserlistItemDelegate(QtGui.QStyledItemDelegate): currentQAbstractItemModel = indexQModelIndex.model() itemQModelIndex = currentQAbstractItemModel.index(indexQModelIndex.row(), constants.USERLIST_GUI_USERNAME_COLUMN, indexQModelIndex.parent()) if sys.platform.startswith('win'): - resourcespath = utils.findWorkingDir() + "\\resources\\" + resourcespath = utils.findWorkingDir() + u"\\resources\\" else: - resourcespath = utils.findWorkingDir() + "/resources/" - controlIconQPixmap = QtGui.QPixmap(resourcespath + "user_key.png") - tickIconQPixmap = QtGui.QPixmap(resourcespath + "tick.png") - crossIconQPixmap = QtGui.QPixmap(resourcespath + "cross.png") + resourcespath = utils.findWorkingDir() + u"/resources/" + controlIconQPixmap = QtGui.QPixmap(resourcespath + u"user_key.png") + tickIconQPixmap = QtGui.QPixmap(resourcespath + u"tick.png") + crossIconQPixmap = QtGui.QPixmap(resourcespath + u"cross.png") roomController = currentQAbstractItemModel.data(itemQModelIndex, Qt.UserRole + constants.USERITEM_CONTROLLER_ROLE) userReady = currentQAbstractItemModel.data(itemQModelIndex, Qt.UserRole + constants.USERITEM_READY_ROLE) @@ -60,14 +60,14 @@ class UserlistItemDelegate(QtGui.QStyledItemDelegate): optionQStyleOptionViewItem.rect.setX(optionQStyleOptionViewItem.rect.x()+constants.USERLIST_GUI_USERNAME_OFFSET) if column == constants.USERLIST_GUI_FILENAME_COLUMN: if sys.platform.startswith('win'): - resourcespath = utils.findWorkingDir() + "\\resources\\" + resourcespath = utils.findWorkingDir() + u"\\resources\\" else: - resourcespath = utils.findWorkingDir() + "/resources/" + resourcespath = utils.findWorkingDir() + u"/resources/" currentQAbstractItemModel = indexQModelIndex.model() itemQModelIndex = currentQAbstractItemModel.index(indexQModelIndex.row(), constants.USERLIST_GUI_FILENAME_COLUMN, indexQModelIndex.parent()) fileSwitchRole = currentQAbstractItemModel.data(itemQModelIndex, Qt.UserRole + constants.FILEITEM_SWITCH_ROLE) if fileSwitchRole == constants.FILEITEM_SWITCH_FILE_SWITCH: - fileSwitchIconQPixmap = QtGui.QPixmap(resourcespath + "film_go.png") + fileSwitchIconQPixmap = QtGui.QPixmap(resourcespath + u"film_go.png") itemQPainter.drawPixmap ( (optionQStyleOptionViewItem.rect.x()), optionQStyleOptionViewItem.rect.y(), @@ -75,7 +75,7 @@ class UserlistItemDelegate(QtGui.QStyledItemDelegate): optionQStyleOptionViewItem.rect.setX(optionQStyleOptionViewItem.rect.x()+16) elif fileSwitchRole == constants.FILEITEM_SWITCH_STREAM_SWITCH: - streamSwitchIconQPixmap = QtGui.QPixmap(resourcespath + "world_go.png") + streamSwitchIconQPixmap = QtGui.QPixmap(resourcespath + u"world_go.png") itemQPainter.drawPixmap ( (optionQStyleOptionViewItem.rect.x()), optionQStyleOptionViewItem.rect.y(), @@ -102,11 +102,11 @@ class MainWindow(QtGui.QMainWindow): currentQAbstractItemModel = indexQModelIndex.model() currentlyPlayingFile = currentQAbstractItemModel.data(indexQModelIndex, Qt.UserRole + constants.PLAYLISTITEM_CURRENTLYPLAYING_ROLE) if sys.platform.startswith('win'): - resourcespath = utils.findWorkingDir() + "\\resources\\" + resourcespath = utils.findWorkingDir() + u"\\resources\\" else: - resourcespath = utils.findWorkingDir() + "/resources/" + resourcespath = utils.findWorkingDir() + u"/resources/" if currentlyPlayingFile: - currentlyplayingIconQPixmap = QtGui.QPixmap(resourcespath + "bullet_right_grey.png") + currentlyplayingIconQPixmap = QtGui.QPixmap(resourcespath + u"bullet_right_grey.png") itemQPainter.drawPixmap ( (optionQStyleOptionViewItem.rect.x()+4), optionQStyleOptionViewItem.rect.y(), @@ -324,14 +324,14 @@ class MainWindow(QtGui.QMainWindow): def showMessage(self, message, noTimestamp=False): message = unicode(message) - message = message.replace("&", "&").replace('"', """).replace("<", "<").replace(">", ">") - message = message.replace("<", "<".format(constants.STYLE_USERNAME)) - message = message.replace(">", ">") - message = message.replace("\n", "
") + message = message.replace(u"&", u"&").replace(u'"', u""").replace(u"<", u"<").replace(">", u">") + message = message.replace(u"<", u"<".format(constants.STYLE_USERNAME)) + message = message.replace(u">", u">") + message = message.replace(u"\n", u"
") if noTimestamp: self.newMessage(u"{}
".format(message)) else: - self.newMessage(time.strftime(constants.UI_TIME_FORMAT, time.localtime()) + message + "
") + self.newMessage(time.strftime(constants.UI_TIME_FORMAT, time.localtime()) + message + u"
") @needsClient def getFileSwitchState(self, filename): @@ -404,7 +404,7 @@ class MainWindow(QtGui.QMainWindow): useritem.setData(isReadyWithFile, Qt.UserRole + constants.USERITEM_READY_ROLE) if user.file: filesizeitem = QtGui.QStandardItem(formatSize(user.file['size'])) - filedurationitem = QtGui.QStandardItem("({})".format(formatTime(user.file['duration']))) + filedurationitem = QtGui.QStandardItem(u"({})".format(formatTime(user.file['duration']))) filename = user.file['name'] if isURL(filename): filename = urllib.unquote(filename) @@ -473,9 +473,9 @@ class MainWindow(QtGui.QMainWindow): def openPlaylistMenu(self, position): indexes = self.playlist.selectedIndexes() if sys.platform.startswith('win'): - resourcespath = utils.findWorkingDir() + "\\resources\\" + resourcespath = utils.findWorkingDir() + u"\\resources\\" else: - resourcespath = utils.findWorkingDir() + "/resources/" + resourcespath = utils.findWorkingDir() + u"/resources/" if len(indexes) > 0: item = self.playlist.selectedIndexes()[0] else: @@ -486,19 +486,19 @@ class MainWindow(QtGui.QMainWindow): firstFile = item.sibling(item.row(), 0).data() if self._syncplayClient.userlist.currentUser.file is None or firstFile <> self._syncplayClient.userlist.currentUser.file["name"]: if isURL(firstFile): - menu.addAction(QtGui.QPixmap(resourcespath + "world_go.png"), getMessage("openstreamurl-menu-label"), lambda: self.openFile(firstFile)) + menu.addAction(QtGui.QPixmap(resourcespath + u"world_go.png"), getMessage("openstreamurl-menu-label"), lambda: self.openFile(firstFile)) else: pathFound = self._syncplayClient.fileSwitch.findFilepath(firstFile) if pathFound: - menu.addAction(QtGui.QPixmap(resourcespath + "film_go.png"), getMessage("openmedia-menu-label"), lambda: self.openFile(pathFound)) - menu.addAction(QtGui.QPixmap(resourcespath + "delete.png"), getMessage("removefromplaylist-menu-label"), lambda: self.deleteSelectedPlaylistItems()) + menu.addAction(QtGui.QPixmap(resourcespath + u"film_go.png"), getMessage("openmedia-menu-label"), lambda: self.openFile(pathFound)) + menu.addAction(QtGui.QPixmap(resourcespath + u"delete.png"), getMessage("removefromplaylist-menu-label"), lambda: self.deleteSelectedPlaylistItems()) menu.addSeparator() - menu.addAction(QtGui.QPixmap(resourcespath + "arrow_switch.png"), getMessage("shuffleplaylist-menuu-label"), lambda: self.shufflePlaylist()) - menu.addAction(QtGui.QPixmap(resourcespath + "arrow_undo.png"), getMessage("undoplaylist-menu-label"), lambda: self.undoPlaylistChange()) - menu.addAction(QtGui.QPixmap(resourcespath + "film_add.png"),getMessage("addfilestoplaylist-menu-label"), lambda: self.OpenAddFilesToPlaylistDialog()) - menu.addAction(QtGui.QPixmap(resourcespath + "world_add.png"), getMessage("addurlstoplaylist-menu-label"), lambda: self.OpenAddURIsToPlaylistDialog()) + menu.addAction(QtGui.QPixmap(resourcespath + u"arrow_switch.png"), getMessage("shuffleplaylist-menuu-label"), lambda: self.shufflePlaylist()) + menu.addAction(QtGui.QPixmap(resourcespath + u"arrow_undo.png"), getMessage("undoplaylist-menu-label"), lambda: self.undoPlaylistChange()) + menu.addAction(QtGui.QPixmap(resourcespath + u"film_add.png"),getMessage("addfilestoplaylist-menu-label"), lambda: self.OpenAddFilesToPlaylistDialog()) + menu.addAction(QtGui.QPixmap(resourcespath + u"world_add.png"), getMessage("addurlstoplaylist-menu-label"), lambda: self.OpenAddURIsToPlaylistDialog()) menu.addSeparator() - menu.addAction(QtGui.QPixmap(resourcespath + "film_folder_edit.png"), getMessage("setmediadirectories-menu-label"), lambda: self.openSetMediaDirectoriesDialog()) + menu.addAction(QtGui.QPixmap(resourcespath + u"film_folder_edit.png"), getMessage("setmediadirectories-menu-label"), lambda: self.openSetMediaDirectoriesDialog()) menu.exec_(self.playlist.viewport().mapToGlobal(position)) @@ -506,9 +506,9 @@ class MainWindow(QtGui.QMainWindow): # TODO: Deselect items after right click indexes = self.listTreeView.selectedIndexes() if sys.platform.startswith('win'): - resourcespath = utils.findWorkingDir() + "\\resources\\" + resourcespath = utils.findWorkingDir() + u"\\resources\\" else: - resourcespath = utils.findWorkingDir() + "/resources/" + resourcespath = utils.findWorkingDir() + u"/resources/" if len(indexes) > 0: item = self.listTreeView.selectedIndexes()[0] else: @@ -532,17 +532,17 @@ class MainWindow(QtGui.QMainWindow): elif username and filename and filename <> getMessage("nofile-note"): if self.config['sharedPlaylistEnabled'] and not self.isItemInPlaylist(filename): if isURL(filename): - menu.addAction(QtGui.QPixmap(resourcespath + "world_add.png"),getMessage("addusersstreamstoplaylist-menu-label").format(shortUsername), lambda: self.addStreamToPlaylist(filename)) + menu.addAction(QtGui.QPixmap(resourcespath + u"world_add.png"),getMessage("addusersstreamstoplaylist-menu-label").format(shortUsername), lambda: self.addStreamToPlaylist(filename)) else: - menu.addAction(QtGui.QPixmap(resourcespath + "film_add.png"), getMessage("addusersfiletoplaylist-menu-label").format(shortUsername), lambda: self.addStreamToPlaylist(filename)) + menu.addAction(QtGui.QPixmap(resourcespath + u"film_add.png"), getMessage("addusersfiletoplaylist-menu-label").format(shortUsername), lambda: self.addStreamToPlaylist(filename)) if self._syncplayClient.userlist.currentUser.file is None or filename <> self._syncplayClient.userlist.currentUser.file["name"]: if isURL(filename): - menu.addAction(QtGui.QPixmap(resourcespath + "world_go.png"), getMessage("openusersstream-menu-label").format(shortUsername), lambda: self.openFile(filename)) + menu.addAction(QtGui.QPixmap(resourcespath + u"world_go.png"), getMessage("openusersstream-menu-label").format(shortUsername), lambda: self.openFile(filename)) else: pathFound = self._syncplayClient.fileSwitch.findFilepath(filename) if pathFound: - menu.addAction(QtGui.QPixmap(resourcespath + "film_go.png"), getMessage("openusersfile-menu-label").format(shortUsername), lambda: self.openFile(pathFound)) + menu.addAction(QtGui.QPixmap(resourcespath + u"film_go.png"), getMessage("openusersfile-menu-label").format(shortUsername), lambda: self.openFile(pathFound)) else: return menu.exec_(self.listTreeView.viewport().mapToGlobal(position)) @@ -627,10 +627,10 @@ class MainWindow(QtGui.QMainWindow): message = unicode(message) if criticalerror: QtGui.QMessageBox.critical(self, "Syncplay", message) - message = message.replace("&", "&").replace('"', """).replace("<", "<").replace(">", ">") - message = message.replace("\n", "
") - message = "".format(constants.STYLE_ERRORNOTIFICATION) + message + "" - self.newMessage(time.strftime(constants.UI_TIME_FORMAT, time.localtime()) + message + "
") + message = message.replace(u"&", u"&").replace(u'"', u""").replace(u"<", u"<").replace(u">", u">") + message = message.replace(u"\n", u"
") + message = u"".format(constants.STYLE_ERRORNOTIFICATION) + message + u"" + self.newMessage(time.strftime(constants.UI_TIME_FORMAT, time.localtime()) + message + u"
") @needsClient def joinRoom(self, room=None): @@ -649,7 +649,7 @@ class MainWindow(QtGui.QMainWindow): def seekPositionDialog(self): seekTime, ok = QtGui.QInputDialog.getText(self, getMessage("seektime-menu-label"), getMessage("seektime-msgbox-label"), QtGui.QLineEdit.Normal, - "0:00") + u"0:00") if ok and seekTime != '': self.seekPosition(seekTime) @@ -1103,20 +1103,20 @@ class MainWindow(QtGui.QMainWindow): window.playbackFrame.setLayout(window.playbackLayout) window.seekInput = QtGui.QLineEdit() window.seekInput.returnPressed.connect(self.seekFromButton) - window.seekButton = QtGui.QPushButton(QtGui.QIcon(self.resourcespath + 'clock_go.png'), "") + window.seekButton = QtGui.QPushButton(QtGui.QIcon(self.resourcespath + u'clock_go.png'), "") window.seekButton.setToolTip(getMessage("seektime-menu-label")) window.seekButton.pressed.connect(self.seekFromButton) window.seekInput.setText("0:00") window.seekInput.setFixedWidth(60) window.playbackLayout.addWidget(window.seekInput) window.playbackLayout.addWidget(window.seekButton) - window.unseekButton = QtGui.QPushButton(QtGui.QIcon(self.resourcespath + 'arrow_undo.png'), "") + window.unseekButton = QtGui.QPushButton(QtGui.QIcon(self.resourcespath + u'arrow_undo.png'), "") window.unseekButton.setToolTip(getMessage("undoseek-menu-label")) window.unseekButton.pressed.connect(self.undoSeek) window.miscLayout = QtGui.QHBoxLayout() window.playbackLayout.addWidget(window.unseekButton) - window.playButton = QtGui.QPushButton(QtGui.QIcon(self.resourcespath + 'control_play_blue.png'), "") + window.playButton = QtGui.QPushButton(QtGui.QIcon(self.resourcespath + u'control_play_blue.png'), "") window.playButton.setToolTip(getMessage("play-menu-label")) window.playButton.pressed.connect(self.play) window.playbackLayout.addWidget(window.playButton) @@ -1347,7 +1347,7 @@ class MainWindow(QtGui.QMainWindow): def setPlaylist(self, newPlaylist, newIndexFilename=None): if self.updatingPlaylist: - self.ui.showDebugMessage("Trying to set playlist while it is already being updated") + self.ui.showDebugMessage(u"Trying to set playlist while it is already being updated") if newPlaylist == self.playlistState: if newIndexFilename: self.playlist.setPlaylistIndexFilename(newIndexFilename) @@ -1470,9 +1470,9 @@ class MainWindow(QtGui.QMainWindow): self.folderSearchEnabled = True self.QtGui = QtGui if sys.platform.startswith('win'): - self.resourcespath = utils.findWorkingDir() + "\\resources\\" + self.resourcespath = utils.findWorkingDir() + u"\\resources\\" else: - self.resourcespath = utils.findWorkingDir() + "/resources/" + self.resourcespath = utils.findWorkingDir() + u"/resources/" self.setWindowFlags(self.windowFlags() & Qt.AA_DontUseNativeMenuBar) self.setWindowTitle("Syncplay v" + version) self.mainLayout = QtGui.QVBoxLayout() @@ -1481,7 +1481,7 @@ class MainWindow(QtGui.QMainWindow): self.addMenubar(self) self.addMainFrame(self) self.loadSettings() - self.setWindowIcon(QtGui.QIcon(self.resourcespath + "syncplay.png")) + self.setWindowIcon(QtGui.QIcon(self.resourcespath + u"syncplay.png")) self.setWindowFlags(self.windowFlags() & Qt.WindowCloseButtonHint & Qt.AA_DontUseNativeMenuBar & Qt.WindowMinimizeButtonHint & ~Qt.WindowContextHelpButtonHint) self.show() self.setAcceptDrops(True) From 7bf8132335d70bcee17a8a99f326bd0abda02751 Mon Sep 17 00:00:00 2001 From: Et0h Date: Sat, 11 Jun 2016 15:10:45 +0100 Subject: [PATCH 124/134] Strip space from end of added URL and ignore empty URLs --- syncplay/ui/gui.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/syncplay/ui/gui.py b/syncplay/ui/gui.py index 6d9b127..6cbe5e3 100644 --- a/syncplay/ui/gui.py +++ b/syncplay/ui/gui.py @@ -801,7 +801,10 @@ class MainWindow(QtGui.QMainWindow): URIsToAdd = utils.convertMultilineStringToList(URIsTextbox.toPlainText()) self.updatingPlaylist = True for URI in URIsToAdd: - self.addStreamToPlaylist(URI) + if URI.endswith(u" "): + URI = URI[:-1] + if URI <> "": + self.addStreamToPlaylist(URI) self.updatingPlaylist = False @needsClient From 18f6f9c50f7e0fd67056d3b9884128a7b766245f Mon Sep 17 00:00:00 2001 From: Et0h Date: Sat, 11 Jun 2016 15:15:28 +0100 Subject: [PATCH 125/134] Add Unicode to some showDebugMessage entries --- syncplay/players/mplayer.py | 2 +- syncplay/players/vlc.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/syncplay/players/mplayer.py b/syncplay/players/mplayer.py index cc39b00..0532203 100644 --- a/syncplay/players/mplayer.py +++ b/syncplay/players/mplayer.py @@ -327,7 +327,7 @@ class MplayerPlayer(BasePlayer): if not isinstance(line, unicode): line = line.decode('utf8') line = (line + u"\n").encode('utf8') - self.__playerController._client.ui.showDebugMessage("player >> {}".format(line)) + self.__playerController._client.ui.showDebugMessage(u"player >> {}".format(line)) self.__process.stdin.write(line) except IOError: pass diff --git a/syncplay/players/vlc.py b/syncplay/players/vlc.py index bdeba7d..2a82c75 100644 --- a/syncplay/players/vlc.py +++ b/syncplay/players/vlc.py @@ -368,7 +368,7 @@ class VlcPlayer(BasePlayer): self.sendLine("get-vlc-version") try: self.push(line + "\n") - self.__playerController._client.ui.showDebugMessage("player >> {}".format(line)) + self.__playerController._client.ui.showDebugMessage(u"player >> {}".format(line)) except: pass if line == "close-vlc": From e71de798e98ec22480d4b5441fc59c07f1201841 Mon Sep 17 00:00:00 2001 From: Et0h Date: Sat, 11 Jun 2016 15:41:23 +0100 Subject: [PATCH 126/134] Use rstrip to strip all spaces from right-hand side of URI --- syncplay/ui/gui.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/syncplay/ui/gui.py b/syncplay/ui/gui.py index 6cbe5e3..537c3b7 100644 --- a/syncplay/ui/gui.py +++ b/syncplay/ui/gui.py @@ -801,8 +801,7 @@ class MainWindow(QtGui.QMainWindow): URIsToAdd = utils.convertMultilineStringToList(URIsTextbox.toPlainText()) self.updatingPlaylist = True for URI in URIsToAdd: - if URI.endswith(u" "): - URI = URI[:-1] + URI = URI.rstrip() if URI <> "": self.addStreamToPlaylist(URI) self.updatingPlaylist = False From 28eb7a0561f99c4b57674c4bb1df1e647f785ac4 Mon Sep 17 00:00:00 2001 From: Et0h Date: Sat, 11 Jun 2016 19:51:10 +0100 Subject: [PATCH 127/134] Queue/drop messages sent while mpv is loading a file & throttle mpv message sending (+ revert problematic unicode) --- syncplay/__init__.py | 2 +- syncplay/client.py | 2 +- syncplay/constants.py | 2 ++ syncplay/players/mplayer.py | 50 +++++++++++++++++++++++++++++++++++-- syncplay/players/mpv.py | 11 +++++++- syncplay/players/vlc.py | 2 +- 6 files changed, 63 insertions(+), 6 deletions(-) diff --git a/syncplay/__init__.py b/syncplay/__init__.py index 38cf47b..fef95bb 100644 --- a/syncplay/__init__.py +++ b/syncplay/__init__.py @@ -1,4 +1,4 @@ version = '1.4.0' milestone = 'Yoitsu' -release_number = '24' +release_number = '25' projectURL = 'http://syncplay.pl/' diff --git a/syncplay/client.py b/syncplay/client.py index de75975..d0b4261 100644 --- a/syncplay/client.py +++ b/syncplay/client.py @@ -1280,7 +1280,7 @@ class UiManager(object): def showDebugMessage(self, message): if constants.DEBUG_MODE and message.rstrip(): - sys.stderr.write(u"{}{}\n".format(time.strftime(constants.UI_TIME_FORMAT, time.localtime()),message.rstrip())) + sys.stderr.write("{}{}\n".format(time.strftime(constants.UI_TIME_FORMAT, time.localtime()),message.rstrip())) def showMessage(self, message, noPlayer=False, noTimestamp=False, secondaryOSD=False): if not noPlayer: self.showOSDMessage(message, duration=constants.OSD_DURATION, secondaryOSD=secondaryOSD) diff --git a/syncplay/constants.py b/syncplay/constants.py index f919e53..62c1eca 100644 --- a/syncplay/constants.py +++ b/syncplay/constants.py @@ -121,6 +121,8 @@ MPC_RETRY_WAIT_TIME = 0.01 MPC_MAX_RETRIES = 30 MPC_PAUSE_TOGGLE_DELAY = 0.05 MPV_NEWFILE_IGNORE_TIME = 1 +MPV_SENDMESSAGE_COOLDOWN_TIME = 0.05 +MPV_MAX_NEWFILE_COOLDOWN_TIME = 3 STREAM_ADDITIONAL_IGNORE_TIME = 10 MPV_LOCK_WAIT_TIME = 0.05 VLC_OPEN_MAX_WAIT_TIME = 15 diff --git a/syncplay/players/mplayer.py b/syncplay/players/mplayer.py index 0532203..5cf9bd5 100644 --- a/syncplay/players/mplayer.py +++ b/syncplay/players/mplayer.py @@ -266,6 +266,10 @@ class MplayerPlayer(BasePlayer): class __Listener(threading.Thread): def __init__(self, playerController, playerPath, filePath, args): + self.sendQueue = [] + self.readyToSend = True + self.lastSendTime = None + self.lastNotReadyTime = None self.__playerController = playerController if self.__playerController.getPlayerPathErrors(playerPath,filePath): raise ValueError() @@ -322,12 +326,54 @@ class MplayerPlayer(BasePlayer): self.__playerController.lineReceived(line) self.__playerController.drop() - def sendLine(self, line): + def isReadyForSend(self): + self.checkForReadinessOverride() + return self.readyToSend + + def setReadyToSend(self, newReadyState): + oldState = self.readyToSend + self.readyToSend = newReadyState + self.lastNotReadyTime = time.time() if newReadyState == False else None + if self.readyToSend == True: + self.__playerController._client.ui.showDebugMessage(" Ready to send: True") + else: + self.__playerController._client.ui.showDebugMessage(" Ready to send: False") + if self.readyToSend == True and oldState == False: + self.processSendQueue() + + def checkForReadinessOverride(self): + if self.lastNotReadyTime and time.time() - self.lastNotReadyTime > constants.MPV_MAX_NEWFILE_COOLDOWN_TIME: + self.setReadyToSend(True) + + def sendLine(self, line, notReadyAfterThis=None): + self.checkForReadinessOverride() + if self.readyToSend == False and "print_text ANS_pause" in line: + self.__playerController._client.ui.showDebugMessage(" Not ready to get status update, so skipping") + return + self.sendQueue.append(line) + self.processSendQueue() + if notReadyAfterThis: + self.setReadyToSend(False) + + def processSendQueue(self): + while self.sendQueue and self.readyToSend: + if self.lastSendTime and time.time() - self.lastSendTime < constants.MPV_SENDMESSAGE_COOLDOWN_TIME: + self.__playerController._client.ui.showDebugMessage(" Throttling message send, so sleeping for {}".format(constants.MPV_SENDMESSAGE_COOLDOWN_TIME)) + time.sleep(constants.MPV_SENDMESSAGE_COOLDOWN_TIME) + try: + lineToSend = self.sendQueue.pop() + if lineToSend: + self.lastSendTime = time.time() + self.actuallySendLine(lineToSend) + except IndexError: + pass + + def actuallySendLine(self, line): try: if not isinstance(line, unicode): line = line.decode('utf8') line = (line + u"\n").encode('utf8') - self.__playerController._client.ui.showDebugMessage(u"player >> {}".format(line)) + self.__playerController._client.ui.showDebugMessage("player >> {}".format(line)) self.__process.stdin.write(line) except IOError: pass diff --git a/syncplay/players/mpv.py b/syncplay/players/mpv.py index 01bf160..ff8cf97 100644 --- a/syncplay/players/mpv.py +++ b/syncplay/players/mpv.py @@ -163,6 +163,10 @@ class NewMpvPlayer(OldMpvPlayer): def askForStatus(self): self._positionAsk.clear() self._pausedAsk.clear() + if not self._listener.isReadyForSend: + self._client.ui.showDebugMessage("mpv not ready for update") + return + self._getPausedAndPosition() self._positionAsk.wait(constants.MPV_LOCK_WAIT_TIME) self._pausedAsk.wait(constants.MPV_LOCK_WAIT_TIME) @@ -183,7 +187,7 @@ class NewMpvPlayer(OldMpvPlayer): def _loadFile(self, filePath): self._clearFileLoaded() - self._listener.sendLine(u'loadfile {}'.format(self._quoteArg(filePath))) + self._listener.sendLine(u'loadfile {}'.format(self._quoteArg(filePath)), notReadyAfterThis=True) def setPosition(self, value): super(self.__class__, self).setPosition(value) @@ -206,10 +210,15 @@ class NewMpvPlayer(OldMpvPlayer): self.mpvErrorCheck(line) if line == "" or "Playing:" in line: + self._listener.setReadyToSend(False) self._clearFileLoaded() elif line == "": self._onFileUpdate() + self._listener.setReadyToSend(True) + + elif "Failed" in line or "failed" in line or "No video or audio streams selected" in line or "error" in line: + self._listener.setReadyToSend(True) def _recentlyReset(self): if not self.lastResetTime: diff --git a/syncplay/players/vlc.py b/syncplay/players/vlc.py index 2a82c75..bdeba7d 100644 --- a/syncplay/players/vlc.py +++ b/syncplay/players/vlc.py @@ -368,7 +368,7 @@ class VlcPlayer(BasePlayer): self.sendLine("get-vlc-version") try: self.push(line + "\n") - self.__playerController._client.ui.showDebugMessage(u"player >> {}".format(line)) + self.__playerController._client.ui.showDebugMessage("player >> {}".format(line)) except: pass if line == "close-vlc": From 6a56a637d311a51ca30c84bc1086332521345cd8 Mon Sep 17 00:00:00 2001 From: Et0h Date: Mon, 13 Jun 2016 00:11:37 +0100 Subject: [PATCH 128/134] Avoid unnecessary items in mpv sendMessage queue --- syncplay/constants.py | 2 ++ syncplay/players/mplayer.py | 21 +++++++++++++++++++++ 2 files changed, 23 insertions(+) diff --git a/syncplay/constants.py b/syncplay/constants.py index 62c1eca..a84f9fc 100644 --- a/syncplay/constants.py +++ b/syncplay/constants.py @@ -157,6 +157,8 @@ MPV_NEW_VERSION = False VLC_SLAVE_ARGS = ['--extraintf=luaintf', '--lua-intf=syncplay', '--no-quiet', '--no-input-fast-seek', '--play-and-pause', '--start-time=0'] VLC_SLAVE_NONOSX_ARGS = ['--no-one-instance', '--no-one-instance-when-started-from-file'] +MPV_SUPERSEDE_IF_DUPLICATE_COMMANDS = ["no-osd set time-pos ", "loadfile "] +MPV_REMOVE_BOTH_IF_DUPLICATE_COMMANDS = ["cycle pause"] MPLAYER_ANSWER_REGEX = "^ANS_([a-zA-Z_-]+)=(.+)$|^(Exiting)\.\.\. \((.+)\)$" VLC_ANSWER_REGEX = r"(?:^(?P[a-zA-Z_]+)(?:\: )?(?P.*))" UI_COMMAND_REGEX = r"^(?P[^\ ]+)(?:\ (?P.+))?" diff --git a/syncplay/players/mplayer.py b/syncplay/players/mplayer.py index 5cf9bd5..929f1dd 100644 --- a/syncplay/players/mplayer.py +++ b/syncplay/players/mplayer.py @@ -350,6 +350,27 @@ class MplayerPlayer(BasePlayer): if self.readyToSend == False and "print_text ANS_pause" in line: self.__playerController._client.ui.showDebugMessage(" Not ready to get status update, so skipping") return + try: + if self.sendQueue: + if constants.MPV_SUPERSEDE_IF_DUPLICATE_COMMANDS: + for command in constants.MPV_SUPERSEDE_IF_DUPLICATE_COMMANDS: + if line.startswith(command): + for itemID, deletionCandidate in enumerate(self.sendQueue): + if deletionCandidate.startswith(command): + self.__playerController._client.ui.showDebugMessage(u" Remove duplicate (supersede): {}".format(self.sendQueue[itemID])) + self.sendQueue.remove(self.sendQueue[itemID]) + break + break + if constants.MPV_REMOVE_BOTH_IF_DUPLICATE_COMMANDS: + for command in constants.MPV_REMOVE_BOTH_IF_DUPLICATE_COMMANDS: + if line == command: + for itemID, deletionCandidate in enumerate(self.sendQueue): + if deletionCandidate == command: + self.__playerController._client.ui.showDebugMessage(u" Remove duplicate (delete both): {}".format(self.sendQueue[itemID])) + self.__playerController._client.ui.showDebugMessage(self.sendQueue[itemID]) + return + except: + self.__playerController._client.ui.showDebugMessage(" Problem removing duplicates, etc") self.sendQueue.append(line) self.processSendQueue() if notReadyAfterThis: From dc798d1173d3c47cf2f91b6082b2e65f1fb2abdb Mon Sep 17 00:00:00 2001 From: Et0h Date: Sat, 2 Jul 2016 16:31:02 +0100 Subject: [PATCH 129/134] Limit trusted domains to just Youtube --- syncplay/constants.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/syncplay/constants.py b/syncplay/constants.py index a84f9fc..d15ad5d 100644 --- a/syncplay/constants.py +++ b/syncplay/constants.py @@ -192,7 +192,7 @@ SYNCPLAY_UPDATE_URL = u"http://syncplay.pl/checkforupdate?{}" # Params SYNCPLAY_DOWNLOAD_URL = "http://syncplay.pl/download/" SYNCPLAY_PUBLIC_SERVER_LIST_URL = u"http://syncplay.pl/listpublicservers?{}" # Params -DEFAULT_TRUSTED_DOMAINS = [u"youtube.com",u"youtu.be",u"vimeo.com"] +DEFAULT_TRUSTED_DOMAINS = [u"youtube.com",u"youtu.be"] TRUSTABLE_WEB_PROTOCOLS = [u"http://www.",u"https://www.",u"http://",u"https://"] PRIVATE_FILE_FIELDS = ["path"] \ No newline at end of file From a1ab184e9a588854cdcab4de4b1c125dacf29417 Mon Sep 17 00:00:00 2001 From: Et0h Date: Sat, 2 Jul 2016 17:33:46 +0100 Subject: [PATCH 130/134] Add 'Set Trusted Domain' feature to main GUI and highlight untrusted domains in playlist --- syncplay/__init__.py | 2 +- syncplay/client.py | 6 ++++++ syncplay/constants.py | 1 + syncplay/messages_de.py | 4 +++- syncplay/messages_en.py | 4 +++- syncplay/messages_ru.py | 4 +++- syncplay/ui/gui.py | 38 +++++++++++++++++++++++++++++++++++++- 7 files changed, 54 insertions(+), 5 deletions(-) diff --git a/syncplay/__init__.py b/syncplay/__init__.py index fef95bb..4696516 100644 --- a/syncplay/__init__.py +++ b/syncplay/__init__.py @@ -1,4 +1,4 @@ version = '1.4.0' milestone = 'Yoitsu' -release_number = '25' +release_number = '26' projectURL = 'http://syncplay.pl/' diff --git a/syncplay/client.py b/syncplay/client.py index d0b4261..0443712 100644 --- a/syncplay/client.py +++ b/syncplay/client.py @@ -455,6 +455,12 @@ class SyncplayClient(object): self.sendFile() self.playlist.changeToPlaylistIndexFromFilename(filename) + def setTrustedDomains(self, newTrustedDomains): + from syncplay.ui.ConfigurationGetter import ConfigurationGetter + ConfigurationGetter().setConfigOption("trustedDomains", newTrustedDomains) + self._config['trustedDomains'] = newTrustedDomains + self.fileSwitchFoundFiles() + def isURITrusted(self, URIToTest): for trustedProtocol in constants.TRUSTABLE_WEB_PROTOCOLS: if URIToTest.startswith(trustedProtocol): diff --git a/syncplay/constants.py b/syncplay/constants.py index d15ad5d..b92b56b 100644 --- a/syncplay/constants.py +++ b/syncplay/constants.py @@ -144,6 +144,7 @@ STYLE_ERRORNOTIFICATION = "color: red;" STYLE_DIFFERENTITEM_COLOR = 'red' STYLE_NOFILEITEM_COLOR = 'blue' STYLE_NOTCONTROLLER_COLOR = 'grey' +STYLE_UNTRUSTEDITEM_COLOR = 'purple' USERLIST_GUI_USERNAME_OFFSET = 21 # Pixels USERLIST_GUI_USERNAME_COLUMN = 0 diff --git a/syncplay/messages_de.py b/syncplay/messages_de.py index 7e8844a..38bf60f 100644 --- a/syncplay/messages_de.py +++ b/syncplay/messages_de.py @@ -267,6 +267,7 @@ de = { "setoffset-menu-label" : u"&Offset einstellen", "createcontrolledroom-menu-label" : u"&Zentral gesteuerten Raum erstellen", "identifyascontroller-menu-label" : u"Als Raumleiter &identifizieren", + "settrusteddomains-menu-label" : u"Set &trusted domains", # TODO: Translate "playback-menu-label" : u"&Wiedergabe", @@ -283,6 +284,7 @@ de = { "addfolder-label" : u"Add folder", # TODO: Translate "adduris-msgbox-label" : u"Add URLs to playlist (one per line)", # TODO: Translate + "trusteddomains-msgbox-label" : u"Domains it is okay to automatically switch to (one per line)", # TODO: Translate "createcontrolledroom-msgbox-label" : u"Zentral gesteuerten Raum erstellen", "controlledroominfo-msgbox-label" : u"Namen des zentral gesteuerten Raums eingeben\r\n(siehe http://syncplay.pl/guide/ für eine Anleitung [Englisch]):", @@ -388,7 +390,7 @@ de = { "playlist-contents-changed-notification" : u"{} updated the playlist", # Username "cannot-find-file-for-playlist-switch-error" : u"Could not find file {} in media directories for playlist switch!", # Filename "cannot-add-duplicate-error" : u"Could not add second entry for '{}' to the playlist as no duplicates are allowed.", #Filename - "cannot-add-unsafe-path-error" : u"Could not load {} because it is not known as a trusted path.", # Filename + "cannot-add-unsafe-path-error" : u"Could not automatically load {} because it is not on a trusted domain. You can switch to the URL manually by double clicking it in the playlist, and add trusted domains via File->Advanced->Set Trusted Domains.", # Filename "sharedplaylistenabled-label" : u"Enable shared playlists", "removefromplaylist-menu-label" : u"Remove from playlist", "shuffleplaylist-menuu-label" : u"Shuffle playlist", diff --git a/syncplay/messages_en.py b/syncplay/messages_en.py index e695292..ccf49ce 100644 --- a/syncplay/messages_en.py +++ b/syncplay/messages_en.py @@ -269,6 +269,7 @@ en = { "setoffset-menu-label" : "Set &offset", "createcontrolledroom-menu-label" : "&Create managed room", "identifyascontroller-menu-label" : "&Identify as room operator", + "settrusteddomains-menu-label" : u"Set &trusted domains", "playback-menu-label" : u"&Playback", @@ -285,6 +286,7 @@ en = { "addfolder-label" : u"Add folder", "adduris-msgbox-label" : u"Add URLs to playlist (one per line)", + "trusteddomains-msgbox-label" : u"Domains it is okay to automatically switch to (one per line)", "createcontrolledroom-msgbox-label" : "Create managed room", "controlledroominfo-msgbox-label" : "Enter name of managed room\r\n(see http://syncplay.pl/guide/ for usage instructions):", @@ -389,7 +391,7 @@ en = { "playlist-contents-changed-notification" : u"{} updated the playlist", # Username "cannot-find-file-for-playlist-switch-error" : u"Could not find file {} in media directories for playlist switch!", # Filename "cannot-add-duplicate-error" : u"Could not add second entry for '{}' to the playlist as no duplicates are allowed.", #Filename - "cannot-add-unsafe-path-error" : u"Could not load {} because it is not known as a trusted path.", # Filename + "cannot-add-unsafe-path-error" : u"Could not automatically load {} because it is not on a trusted domain. You can switch to the URL manually by double clicking it in the playlist, and add trusted domains via File->Advanced->Set Trusted Domains.", # Filename "sharedplaylistenabled-label" : u"Enable shared playlists", "removefromplaylist-menu-label" : u"Remove from playlist", "shuffleplaylist-menuu-label" : u"Shuffle playlist", diff --git a/syncplay/messages_ru.py b/syncplay/messages_ru.py index 1b98007..30e2a66 100644 --- a/syncplay/messages_ru.py +++ b/syncplay/messages_ru.py @@ -269,6 +269,7 @@ ru = { "setoffset-menu-label" : u"Установить &смещение", "createcontrolledroom-menu-label" : u"&Создать управляемую комнату", "identifyascontroller-menu-label" : u"&Войти как оператор комнаты", + "settrusteddomains-menu-label" : u"Set &trusted domains", # TODO: Translate "playback-menu-label" : u"&Воспроизведение", @@ -285,6 +286,7 @@ ru = { "addfolder-label" : u"Add folder", # TODO: Translate "adduris-msgbox-label" : u"Add URLs to playlist (one per line)", # TODO: Translate + "trusteddomains-msgbox-label" : u"Domains it is okay to automatically switch to (one per line)", # TODO: Translate "createcontrolledroom-msgbox-label" : u"Создать управляемую комнату", "controlledroominfo-msgbox-label" : u"Введите имя управляемой комнаты\r\n(см. инструкцию на странице http://syncplay.pl/guide/):", @@ -388,7 +390,7 @@ ru = { "playlist-contents-changed-notification" : u"{} updated the playlist", # Username "cannot-find-file-for-playlist-switch-error" : u"Could not find file {} in media directories for playlist switch!", # Filename "cannot-add-duplicate-error" : u"Could not add second entry for '{}' to the playlist as no duplicates are allowed.", #Filename - "cannot-add-unsafe-path-error" : u"Could not load {} because it is not known as a trusted path.", # Filename + "cannot-add-unsafe-path-error" : u"Could not automatically load {} because it is not on a trusted domain. You can switch to the URL manually by double clicking it in the playlist, and add trusted domains via File->Advanced->Set Trusted Domains.", # Filename # TODO: Translate "sharedplaylistenabled-label" : u"Enable shared playlists", "removefromplaylist-menu-label" : u"Remove from playlist", "shuffleplaylist-menuu-label" : u"Shuffle playlist", diff --git a/syncplay/ui/gui.py b/syncplay/ui/gui.py index 537c3b7..8349f35 100644 --- a/syncplay/ui/gui.py +++ b/syncplay/ui/gui.py @@ -182,7 +182,10 @@ class MainWindow(QtGui.QMainWindow): isPlayingFilename = itemFilename == self.playlistIndexFilename self.item(item).setData(Qt.UserRole + constants.PLAYLISTITEM_CURRENTLYPLAYING_ROLE, isPlayingFilename) fileIsAvailable = self.selfWindow.isFileAvailable(itemFilename) - if fileIsAvailable: + fileIsUntrusted = self.selfWindow.isItemUntrusted(itemFilename) + if fileIsUntrusted: + self.item(item).setForeground(QtGui.QBrush(QtGui.QColor(constants.STYLE_UNTRUSTEDITEM_COLOR))) + elif fileIsAvailable: self.item(item).setForeground(QtGui.QBrush(QtGui.QColor(QtGui.QPalette.ColorRole(QtGui.QPalette.Text)))) else: self.item(item).setForeground(QtGui.QBrush(QtGui.QColor(constants.STYLE_DIFFERENTITEM_COLOR))) @@ -349,6 +352,10 @@ class MainWindow(QtGui.QMainWindow): self.newWatchlist.extend([filename]) return constants.FILEITEM_SWITCH_NO_SWITCH + @needsClient + def isItemUntrusted(self, filename): + return isURL(filename) and not self._syncplayClient.isURITrusted(filename) + @needsClient def isFileAvailable(self, filename): if filename: @@ -834,6 +841,32 @@ class MainWindow(QtGui.QMainWindow): newMediaDirectories = utils.convertMultilineStringToList(MediaDirectoriesTextbox.toPlainText()) self._syncplayClient.fileSwitch.changeMediaDirectories(newMediaDirectories) + @needsClient + def openSetTrustedDomainsDialog(self): + TrustedDomainsDialog = QtGui.QDialog() + TrustedDomainsDialog.setWindowTitle(getMessage("settrusteddomains-menu-label")) + TrustedDomainsLayout = QtGui.QGridLayout() + TrustedDomainsLabel = QtGui.QLabel(getMessage("trusteddomains-msgbox-label")) + TrustedDomainsLayout.addWidget(TrustedDomainsLabel, 0, 0, 1, 1) + TrustedDomainsTextbox = QtGui.QPlainTextEdit() + TrustedDomainsTextbox.setLineWrapMode(QtGui.QPlainTextEdit.NoWrap) + TrustedDomainsTextbox.setPlainText(utils.getListAsMultilineString(self.config["trustedDomains"])) + TrustedDomainsLayout.addWidget(TrustedDomainsTextbox, 1, 0, 1, 1) + TrustedDomainsButtonBox = QtGui.QDialogButtonBox() + TrustedDomainsButtonBox.setOrientation(Qt.Horizontal) + TrustedDomainsButtonBox.setStandardButtons(QtGui.QDialogButtonBox.Ok|QtGui.QDialogButtonBox.Cancel) + TrustedDomainsButtonBox.accepted.connect(TrustedDomainsDialog.accept) + TrustedDomainsButtonBox.rejected.connect(TrustedDomainsDialog.reject) + TrustedDomainsLayout.addWidget(TrustedDomainsButtonBox, 2, 0, 1, 1) + TrustedDomainsDialog.setLayout(TrustedDomainsLayout) + TrustedDomainsDialog.setModal(True) + TrustedDomainsDialog.show() + result = TrustedDomainsDialog.exec_() + if result == QtGui.QDialog.Accepted: + newTrustedDomains = utils.convertMultilineStringToList(TrustedDomainsTextbox.toPlainText()) + self._syncplayClient.setTrustedDomains(newTrustedDomains) + + @needsClient def openAddMediaDirectoryDialog(self, MediaDirectoriesTextbox, MediaDirectoriesDialog): folderName = unicode(QtGui.QFileDialog.getExistingDirectory(self,None,self.getInitialMediaDirectory(includeUserSpecifiedDirectories=False),QtGui.QFileDialog.ShowDirsOnly)) @@ -1179,6 +1212,9 @@ class MainWindow(QtGui.QMainWindow): window.identifyascontroller = window.advancedMenu.addAction(QtGui.QIcon(self.resourcespath + 'key_go.png'), getMessage("identifyascontroller-menu-label")) window.identifyascontroller.triggered.connect(self.identifyAsController) + window.setTrustedDomainsAction = window.advancedMenu.addAction(QtGui.QIcon(self.resourcespath + 'film_folder_edit.png'), + getMessage("settrusteddomains-menu-label")) + window.setTrustedDomainsAction.triggered.connect(self.openSetTrustedDomainsDialog) window.menuBar.addMenu(window.advancedMenu) From c506606654babd9b473e31264bf2e2f5d632384d Mon Sep 17 00:00:00 2001 From: Et0h Date: Sat, 2 Jul 2016 21:00:53 +0100 Subject: [PATCH 131/134] Temporarily disable associate in Windows installer as it is broken --- buildPy2exe.py | 100 ++++++++++++++++++++++++------------------------- 1 file changed, 50 insertions(+), 50 deletions(-) diff --git a/buildPy2exe.py b/buildPy2exe.py index 673325d..5c4468d 100644 --- a/buildPy2exe.py +++ b/buildPy2exe.py @@ -127,13 +127,13 @@ NSIS_SCRIPT_TEMPLATE = r""" Var Dialog Var Icon_Syncplay Var Icon_Syncplay_Handle - Var CheckBox_Associate + ;Var CheckBox_Associate Var CheckBox_VLC Var CheckBox_AutomaticUpdates Var CheckBox_StartMenuShortcut Var CheckBox_DesktopShortcut Var CheckBox_QuickLaunchShortcut - Var CheckBox_Associate_State + ;Var CheckBox_Associate_State Var CheckBox_VLC_State Var CheckBox_AutomaticUpdates_State Var CheckBox_StartMenuShortcut_State @@ -165,13 +165,13 @@ NSIS_SCRIPT_TEMPLATE = r""" Var Drive Var VLC_Directory - !macro APP_ASSOCIATE EXT FileCLASS DESCRIPTION COMMANDTEXT COMMAND - WriteRegStr HKCR ".$${EXT}" "" "$${FileCLASS}" - WriteRegStr HKCR "$${FileCLASS}" "" `$${DESCRIPTION}` - WriteRegStr HKCR "$${FileCLASS}\shell" "" "open" - WriteRegStr HKCR "$${FileCLASS}\shell\open" "" `$${COMMANDTEXT}` - WriteRegStr HKCR "$${FileCLASS}\shell\open\command" "" `$${COMMAND}` - !macroend + ;!macro APP_ASSOCIATE EXT FileCLASS DESCRIPTION COMMANDTEXT COMMAND + ; WriteRegStr HKCR ".$${EXT}" "" "$${FileCLASS}" + ; WriteRegStr HKCR "$${FileCLASS}" "" `$${DESCRIPTION}` + ; WriteRegStr HKCR "$${FileCLASS}\shell" "" "open" + ; WriteRegStr HKCR "$${FileCLASS}\shell\open" "" `$${COMMANDTEXT}` + ; WriteRegStr HKCR "$${FileCLASS}\shell\open\command" "" `$${COMMAND}` + ;!macroend !macro APP_UNASSOCIATE EXT FileCLASS ; Backup the previously associated File class @@ -180,10 +180,10 @@ NSIS_SCRIPT_TEMPLATE = r""" DeleteRegKey HKCR `$${FileCLASS}` !macroend - !macro ASSOCIATE EXT - !insertmacro APP_ASSOCIATE "$${EXT}" "Syncplay.$${EXT}" "$$INSTDIR\Syncplay.exe,%1%" \ - "Open with Syncplay" "$$INSTDIR\Syncplay.exe $$\"%1$$\"" - !macroend + ;!macro ASSOCIATE EXT + ; !insertmacro APP_ASSOCIATE "$${EXT}" "Syncplay.$${EXT}" "$$INSTDIR\Syncplay.exe,%1%" \ + ; "Open with Syncplay" "$$INSTDIR\Syncplay.exe $$\"%1$$\"" + ;!macroend !macro UNASSOCIATE EXT !insertmacro APP_UNASSOCIATE "$${EXT}" "Syncplay.$${EXT}" @@ -197,7 +197,7 @@ NSIS_SCRIPT_TEMPLATE = r""" MessageBox MB_OK|MB_ICONEXCLAMATION "The installer is already running." Abort - StrCpy $$CheckBox_Associate_State $${BST_CHECKED} + ;StrCpy $$CheckBox_Associate_State $${BST_CHECKED} StrCpy $$CheckBox_StartMenuShortcut_State $${BST_CHECKED} Call GetVLCDir Call UpdateVLCCheckbox @@ -257,8 +257,8 @@ NSIS_SCRIPT_TEMPLATE = r""" $${NSD_CreateLabel} 321u 122u 132 8u "$$(^SpaceAvailable)$$AvailibleSpaceGiB.$$AvailibleSpaceGB" Pop $$Label_Space - $${NSD_CreateCheckBox} 8u 59u 187u 10u "$$(^Associate)" - Pop $$CheckBox_Associate + ;$${NSD_CreateCheckBox} 8u 59u 187u 10u "$$(^Associate)" + ;Pop $$CheckBox_Associate $${NSD_CreateBrowseButton} 185u 70u 70u 14u "$$(^BrowseVLCBtn)" Pop $$Button_Browse_VLC @@ -283,9 +283,9 @@ NSIS_SCRIPT_TEMPLATE = r""" $${NSD_CreateCheckbox} 158u 111u 130u 10u "$$(^QuickLaunchBar)" Pop $$CheckBox_QuickLaunchShortcut - $${If} $$CheckBox_Associate_State == $${BST_CHECKED} - $${NSD_Check} $$CheckBox_Associate - $${EndIf} + ;$${If} $$CheckBox_Associate_State == $${BST_CHECKED} + ; $${NSD_Check} $$CheckBox_Associate + ;$${EndIf} $${If} $$CheckBox_VLC_State == $${BST_CHECKED} $${NSD_Check} $$CheckBox_VLC @@ -317,7 +317,7 @@ NSIS_SCRIPT_TEMPLATE = r""" Function DirectoryCustomLeave $${NSD_GetText} $$Text_Directory $$INSTDIR - $${NSD_GetState} $$CheckBox_Associate $$CheckBox_Associate_State + ;$${NSD_GetState} $$CheckBox_Associate $$CheckBox_Associate_State $${NSD_GetState} $$CheckBox_VLC $$CheckBox_VLC_State $${NSD_GetState} $$CheckBox_AutomaticUpdates $$CheckBox_AutomaticUpdates_State $${NSD_GetState} $$CheckBox_StartMenuShortcut $$CheckBox_StartMenuShortcut_State @@ -395,10 +395,10 @@ NSIS_SCRIPT_TEMPLATE = r""" FunctionEnd Function InstallOptions - $${If} $$CheckBox_Associate_State == $${BST_CHECKED} - Call Associate - DetailPrint "Associated Syncplay with multimedia files" - $${EndIf} + ;$${If} $$CheckBox_Associate_State == $${BST_CHECKED} + ; Call Associate + ; DetailPrint "Associated Syncplay with multimedia files" + ;$${EndIf} $${If} $$CheckBox_StartMenuShortcut_State == $${BST_CHECKED} CreateDirectory $$SMPROGRAMS\Syncplay @@ -425,32 +425,32 @@ NSIS_SCRIPT_TEMPLATE = r""" FunctionEnd ;Associates extensions with Syncplay - Function Associate - !insertmacro ASSOCIATE avi - !insertmacro ASSOCIATE mpg - !insertmacro ASSOCIATE mpeg - !insertmacro ASSOCIATE mpe - !insertmacro ASSOCIATE m1v - !insertmacro ASSOCIATE m2v - !insertmacro ASSOCIATE mpv2 - !insertmacro ASSOCIATE mp2v - !insertmacro ASSOCIATE mkv - !insertmacro ASSOCIATE mp4 - !insertmacro ASSOCIATE m4v - !insertmacro ASSOCIATE mp4v - !insertmacro ASSOCIATE 3gp - !insertmacro ASSOCIATE 3gpp - !insertmacro ASSOCIATE 3g2 - !insertmacro ASSOCIATE 3pg2 - !insertmacro ASSOCIATE flv - !insertmacro ASSOCIATE f4v - !insertmacro ASSOCIATE rm - !insertmacro ASSOCIATE wmv - !insertmacro ASSOCIATE swf - !insertmacro ASSOCIATE rmvb - !insertmacro ASSOCIATE divx - !insertmacro ASSOCIATE amv - FunctionEnd + ;Function Associate + ; !insertmacro ASSOCIATE avi + ; !insertmacro ASSOCIATE mpg + ; !insertmacro ASSOCIATE mpeg + ; !insertmacro ASSOCIATE mpe + ; !insertmacro ASSOCIATE m1v + ; !insertmacro ASSOCIATE m2v + ; !insertmacro ASSOCIATE mpv2 + ; !insertmacro ASSOCIATE mp2v + ; !insertmacro ASSOCIATE mkv + ; !insertmacro ASSOCIATE mp4 + ; !insertmacro ASSOCIATE m4v + ; !insertmacro ASSOCIATE mp4v + ; !insertmacro ASSOCIATE 3gp + ; !insertmacro ASSOCIATE 3gpp + ; !insertmacro ASSOCIATE 3g2 + ; !insertmacro ASSOCIATE 3pg2 + ; !insertmacro ASSOCIATE flv + ; !insertmacro ASSOCIATE f4v + ; !insertmacro ASSOCIATE rm + ; !insertmacro ASSOCIATE wmv + ; !insertmacro ASSOCIATE swf + ; !insertmacro ASSOCIATE rmvb + ; !insertmacro ASSOCIATE divx + ; !insertmacro ASSOCIATE amv + ;FunctionEnd Function WriteRegistry Call GetSize From 2653042327023395a8ba467fba27e93636b6f6fd Mon Sep 17 00:00:00 2001 From: Et0h Date: Sat, 2 Jul 2016 21:04:02 +0100 Subject: [PATCH 132/134] Add icon for 'Set Trusted Domains' menu item + tweak wording --- buildPy2exe.py | 1 + resources/shield_edit.png | Bin 0 -> 773 bytes syncplay/ui/gui.py | 9 ++++----- 3 files changed, 5 insertions(+), 5 deletions(-) create mode 100644 resources/shield_edit.png diff --git a/buildPy2exe.py b/buildPy2exe.py index 5c4468d..403bd4a 100644 --- a/buildPy2exe.py +++ b/buildPy2exe.py @@ -656,6 +656,7 @@ guiIcons = ['resources/accept.png', 'resources/arrow_undo.png', 'resources/clock 'resources/world_explore.png', 'resources/application_get.png', 'resources/cog.png', 'resources/arrow_switch.png', 'resources/film_go.png', 'resources/world_go.png', 'resources/arrow_refresh.png', 'resources/bullet_right_grey.png', 'resources/film_folder_edit.png', + 'resources/shield_edit.png', 'resources/world_add.png', 'resources/film_add.png', 'resources/delete.png', 'resources/spinner.mng' ] resources = ["resources/icon.ico", "resources/syncplay.png"] diff --git a/resources/shield_edit.png b/resources/shield_edit.png new file mode 100644 index 0000000000000000000000000000000000000000..c4e00c2798ddcdb3e8c4be9513f99879327ae65c GIT binary patch literal 773 zcmV+g1N!`lP)pF8gxZibW?9;ba!ELWdKlNX>N2bPDNB8 zb~7$DE-^4L^m3s900MzYL_t(IPqmUsXj4%bh2PeN3&E9Z7lkf#rjxcS|}DR zScD3?)M{0vNKq@LLlQb@Dpu@Z5^0*mq|K86@ z19$Or|L@#$?|+&9spGKnrEf1E-WP;IXgu!4qrPu4bv|8;e4u;q3g~#K8QmKSO zp;(d2mruxKva&=X*)t|^C=`OlVu91?L^7E~wOU2JUguY(Qb8h-fYoZn>gptBSz9vchen0GXJ2s6X1mAWcA|HZNd>$@Q^EAa7hD0L(fg_O!qR}XJc6N}>X8Ct; zt-nCp(2s&!36#De@0Q}@@TDTf8Ah#E^9BSa?-34%k;!Cu1Fv}&IoCtfwpNffJx9g! z9zT=o*cA8uqBz5-R4SCqWdwsk1OfrxgxovjlOpGSfJS8thW$&(+l(DwT6A9 z^(4g^h8q)##UfIv6ns7(ZxnYe;=AV_8mt-RXb;#_H;VoNgl5{H8}GS5(Oq#f9<|wQ z`~*mR_l6j0TR(-bg8gzCKfL!5nY@Ta(O{Hf`^9|_I2;Zn98&l-x4GD#z$SW8OD&~ALSYj{_KW*nM;8Ql;QKHDmwF6eEuAPQx{-6Fk=;q!1Slnc`k2n6l4?O;8=-dwMlg{WD^VHr9LN#AkIjty>2OiY}n zE&o>MnIr6zo~F9~X Date: Sat, 2 Jul 2016 23:16:51 +0100 Subject: [PATCH 133/134] Fix issue with loading relative path containing utf-8 character --- syncplay/__init__.py | 2 +- syncplay/ui/ConfigurationGetter.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/syncplay/__init__.py b/syncplay/__init__.py index 4696516..0ab7cb8 100644 --- a/syncplay/__init__.py +++ b/syncplay/__init__.py @@ -1,4 +1,4 @@ version = '1.4.0' milestone = 'Yoitsu' -release_number = '26' +release_number = '27' projectURL = 'http://syncplay.pl/' diff --git a/syncplay/ui/ConfigurationGetter.py b/syncplay/ui/ConfigurationGetter.py index 5447679..92a330b 100755 --- a/syncplay/ui/ConfigurationGetter.py +++ b/syncplay/ui/ConfigurationGetter.py @@ -378,7 +378,7 @@ class ConfigurationGetter(object): for name in constants.CONFIG_NAMES: path = location + os.path.sep + name if os.path.isfile(path) and (os.name == 'nt' or path != os.path.join(os.getenv('HOME', '.'), constants.DEFAULT_CONFIG_NAME_LINUX)): - loadedPaths.append("'" + os.path.normpath(path).decode('utf-8') + "'") + loadedPaths.append(u"'{}'".format(os.path.normpath(path))) self._parseConfigFile(path, createConfig=False) self._checkConfig() return loadedPaths From 50785fd34938fee874d48accc0777ee778f560af Mon Sep 17 00:00:00 2001 From: Et0h Date: Sat, 2 Jul 2016 23:29:46 +0100 Subject: [PATCH 134/134] Add 'set trusted domains' item to playlist context menu --- syncplay/ui/gui.py | 1 + 1 file changed, 1 insertion(+) diff --git a/syncplay/ui/gui.py b/syncplay/ui/gui.py index 0a323c4..dc62c85 100644 --- a/syncplay/ui/gui.py +++ b/syncplay/ui/gui.py @@ -506,6 +506,7 @@ class MainWindow(QtGui.QMainWindow): menu.addAction(QtGui.QPixmap(resourcespath + u"world_add.png"), getMessage("addurlstoplaylist-menu-label"), lambda: self.OpenAddURIsToPlaylistDialog()) menu.addSeparator() menu.addAction(QtGui.QPixmap(resourcespath + u"film_folder_edit.png"), getMessage("setmediadirectories-menu-label"), lambda: self.openSetMediaDirectoriesDialog()) + menu.addAction(QtGui.QPixmap(resourcespath + u"shield_edit.png"), getMessage("settrusteddomains-menu-label"), lambda: self.openSetTrustedDomainsDialog()) menu.exec_(self.playlist.viewport().mapToGlobal(position))