Initial code to move FileCheck search into separate thread + add caching

This commit is contained in:
Et0h 2015-09-20 10:43:43 +01:00
parent 7bc9159d33
commit 6607cd5a2d
4 changed files with 177 additions and 51 deletions

View File

@ -1,4 +1,4 @@
version = '1.3.3' version = '1.3.3'
milestone = 'Chami' milestone = 'Chami'
release_number = '19' release_number = '20'
projectURL = 'http://syncplay.pl/' projectURL = 'http://syncplay.pl/'

View File

@ -49,7 +49,11 @@ SERVER_STATE_INTERVAL = 1
WARNING_OSD_MESSAGES_LOOP_INTERVAL = 1 WARNING_OSD_MESSAGES_LOOP_INTERVAL = 1
AUTOPLAY_DELAY = 3.0 AUTOPLAY_DELAY = 3.0
SYNC_ON_PAUSE = True # Client seek to global position - subtitles may disappear on some media players SYNC_ON_PAUSE = True # Client seek to global position - subtitles may disappear on some media players
FOLDER_SEARCH_TIMEOUT = 2.0 # Secs
# 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
MEDIA_CACHE_CHECK_INTERVAL = 0.2 # Secs - Frequency of checking for the cache being updated
#Usually there's no need to adjust these #Usually there's no need to adjust these
LAST_PAUSED_DIFF_THRESHOLD = 2 LAST_PAUSED_DIFF_THRESHOLD = 2

View File

@ -10,6 +10,8 @@ import re
import os import os
from syncplay.utils import formatTime, sameFilename, sameFilesize, sameFileduration, RoomPasswordProvider, formatSize, isURL from syncplay.utils import formatTime, sameFilename, sameFilesize, sameFileduration, RoomPasswordProvider, formatSize, isURL
from functools import wraps from functools import wraps
from twisted.internet import task
import threading
lastCheckedForUpdates = None lastCheckedForUpdates = None
class UserlistItemDelegate(QtGui.QStyledItemDelegate): class UserlistItemDelegate(QtGui.QStyledItemDelegate):
@ -83,6 +85,153 @@ class UserlistItemDelegate(QtGui.QStyledItemDelegate):
QtGui.QStyledItemDelegate.paint(self, itemQPainter, optionQStyleOptionViewItem, indexQModelIndex) QtGui.QStyledItemDelegate.paint(self, itemQPainter, optionQStyleOptionViewItem, indexQModelIndex)
class MainWindow(QtGui.QMainWindow): class MainWindow(QtGui.QMainWindow):
class FileSwitchManager(object):
def __init__(self):
self.fileSwitchTimer = task.LoopingCall(self.updateInfo)
self.fileSwitchTimer.start(constants.FOLDER_SEARCH_DOUBLE_CHECK_INTERVAL, True)
self.fileCheckTimer = task.LoopingCall(self.checkForUpdate)
self.fileCheckTimer.start(constants.MEDIA_CACHE_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():
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:
newThread = threading.Thread(target=MainWindow.FileSwitchManager._updateInfoThread)
newThread.setDaemon(True)
newThread.start()
@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): class topSplitter(QtGui.QSplitter):
def createHandle(self): def createHandle(self):
return self.topSplitterHandle(self.orientation(), self) return self.topSplitterHandle(self.orientation(), self)
@ -107,9 +256,11 @@ class MainWindow(QtGui.QMainWindow):
def addClient(self, client): def addClient(self, client):
self._syncplayClient = client self._syncplayClient = client
MainWindow.FileSwitchManager.setClient(client)
self.roomInput.setText(self._syncplayClient.getRoom()) self.roomInput.setText(self._syncplayClient.getRoom())
self.config = self._syncplayClient.getConfig() self.config = self._syncplayClient.getConfig()
try: try:
self.FileSwitchManager.setMediaDirectories(self.config["mediaSearchDirectories"])
self.updateReadyState(self.config['readyAtStart']) self.updateReadyState(self.config['readyAtStart'])
autoplayInitialState = self.config['autoplayInitialState'] autoplayInitialState = self.config['autoplayInitialState']
if autoplayInitialState is not None: if autoplayInitialState is not None:
@ -150,28 +301,11 @@ class MainWindow(QtGui.QMainWindow):
return constants.FILEITEM_SWITCH_NO_SWITCH return constants.FILEITEM_SWITCH_NO_SWITCH
if isURL(filename): if isURL(filename):
return constants.FILEITEM_SWITCH_STREAM_SWITCH return constants.FILEITEM_SWITCH_STREAM_SWITCH
else: elif filename not in self.newWatchlist:
currentPath = self._syncplayClient.userlist.currentUser.file["path"] if self._syncplayClient.userlist.currentUser.file else None if MainWindow.FileSwitchManager.findFilepath(filename):
if self.folderSearchEnabled:
try:
filenamesInDirectories = utils.findFilenameInDirectories(filename, self.config["mediaSearchDirectories"])
except IOError as errorMessage:
self.showErrorMessage(errorMessage)
filenamesInDirectories = None
self.folderSearchEnabled = False
else:
filenamesInDirectories = None
if filenamesInDirectories:
return constants.FILEITEM_SWITCH_FILE_SWITCH return constants.FILEITEM_SWITCH_FILE_SWITCH
elif currentPath:
currentDirectory = os.path.dirname(currentPath)
newPath = os.path.join(currentDirectory, filename)
if os.path.isfile(newPath):
return constants.FILEITEM_SWITCH_FILE_SWITCH
else:
return constants.FILEITEM_SWITCH_NO_SWITCH
else: else:
return constants.FILEITEM_SWITCH_NO_SWITCH self.newWatchlist.extend([filename])
return constants.FILEITEM_SWITCH_NO_SWITCH return constants.FILEITEM_SWITCH_NO_SWITCH
def showUserList(self, currentUser, rooms): def showUserList(self, currentUser, rooms):
@ -179,8 +313,11 @@ class MainWindow(QtGui.QMainWindow):
self._usertreebuffer.setHorizontalHeaderLabels( self._usertreebuffer.setHorizontalHeaderLabels(
(getMessage("roomuser-heading-label"), getMessage("size-heading-label"), getMessage("duration-heading-label"), getMessage("filename-heading-label") )) (getMessage("roomuser-heading-label"), getMessage("size-heading-label"), getMessage("duration-heading-label"), getMessage("filename-heading-label") ))
usertreeRoot = self._usertreebuffer.invisibleRootItem() 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"]))
for room in rooms: for room in rooms:
self.newWatchlist = []
roomitem = QtGui.QStandardItem(room) roomitem = QtGui.QStandardItem(room)
font = QtGui.QFont() font = QtGui.QFont()
font.setItalic(True) font.setItalic(True)
@ -266,6 +403,13 @@ class MainWindow(QtGui.QMainWindow):
self.listTreeView.setRootIsDecorated(False) self.listTreeView.setRootIsDecorated(False)
self.listTreeView.expandAll() self.listTreeView.expandAll()
self.updateListGeometry() self.updateListGeometry()
MainWindow.FileSwitchManager.setFilenameWatchlist(self.newWatchlist)
self.checkForDisabledDir()
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
def updateListGeometry(self): def updateListGeometry(self):
try: try:
@ -309,26 +453,11 @@ class MainWindow(QtGui.QMainWindow):
if isURL(filename): if isURL(filename):
self._syncplayClient._player.openFile(filename) self._syncplayClient._player.openFile(filename)
else: else:
currentPath = self._syncplayClient.userlist.currentUser.file["path"] if self._syncplayClient.userlist.currentUser.file else None pathFound = MainWindow.FileSwitchManager.findFilepath(filename)
if self.folderSearchEnabled:
try:
pathFound = utils.findFilenameInDirectories(filename, self.config["mediaSearchDirectories"])
except IOError as errorMessage:
self.showErrorMessage(errorMessage)
pathFound = None
self.folderSearchEnabled = False
else:
pathFound = None
if pathFound: if pathFound:
self._syncplayClient._player.openFile(pathFound) self._syncplayClient._player.openFile(pathFound)
elif currentPath:
currentDirectory = os.path.dirname(currentPath)
newPath = os.path.join(currentDirectory, filename)
if os.path.isfile(newPath):
self._syncplayClient._player.openFile(newPath)
else:
self.showErrorMessage(getMessage("switch-file-not-found-error").format(filename, currentDirectory))
else: else:
MainWindow.FileSwitchManager.updateInfo()
self.showErrorMessage(getMessage("switch-file-not-found-error").format(filename)) self.showErrorMessage(getMessage("switch-file-not-found-error").format(filename))
@needsClient @needsClient
@ -455,6 +584,7 @@ class MainWindow(QtGui.QMainWindow):
if sys.platform.startswith('win'): if sys.platform.startswith('win'):
fileName = fileName.replace("/", "\\") fileName = fileName.replace("/", "\\")
self.mediadirectory = os.path.dirname(fileName) self.mediadirectory = os.path.dirname(fileName)
self.FileSwitchManager.setCurrentDirectory(self.mediadirectory)
self.saveMediaBrowseSettings() self.saveMediaBrowseSettings()
self._syncplayClient._player.openFile(fileName) self._syncplayClient._player.openFile(fileName)
@ -785,7 +915,7 @@ class MainWindow(QtGui.QMainWindow):
self._syncplayClient.changeReadyState(self.readyPushButton.isChecked()) self._syncplayClient.changeReadyState(self.readyPushButton.isChecked())
else: else:
self.showDebugMessage("Tried to change ready state too soon.") self.showDebugMessage("Tried to change ready state too soon.")
@needsClient @needsClient
def changeAutoplayThreshold(self, source=None): def changeAutoplayThreshold(self, source=None):
self._syncplayClient.changeAutoPlayThrehsold(self.autoplayThresholdSpinbox.value()) self._syncplayClient.changeAutoPlayThrehsold(self.autoplayThresholdSpinbox.value())
@ -931,6 +1061,9 @@ class MainWindow(QtGui.QMainWindow):
def __init__(self): def __init__(self):
super(MainWindow, self).__init__() super(MainWindow, self).__init__()
FileSwitchManager = self.FileSwitchManager()
FileSwitchManager.setWindow(self)
self.newWatchlist = []
self.publicServerList = [] self.publicServerList = []
self.lastCheckedForUpdates = None self.lastCheckedForUpdates = None
self._syncplayClient = None self._syncplayClient = None

View File

@ -250,17 +250,6 @@ def getListAsMultilineString(pathArray):
def convertMultilineStringToList(multilineString): def convertMultilineStringToList(multilineString):
return unicode.split(multilineString,u"\n") if multilineString else "" return unicode.split(multilineString,u"\n") if multilineString else ""
def findFilenameInDirectories(filename, directoryList):
if filename and directoryList:
startTime = time.time()
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:
raise IOError(getMessage("folder-search-timeout-error").format(directory))
return None
def getListOfPublicServers(): def getListOfPublicServers():
try: try:
import urllib, syncplay, sys, messages, json import urllib, syncplay, sys, messages, json