class FileHandle(QtCore.QObject): sigChanged = QtCore.Signal(object, object, object) # (self, change, (args)) sigDelayedChange = QtCore.Signal(object, object) # (self, changes) def __init__(self, path, manager): QtCore.QObject.__init__(self) self.manager = manager self.delayedChanges = [] self.path = os.path.abspath(path) self.parentDir = None #self.lock = threading.RLock() self.lock = Mutex(QtCore.QMutex.Recursive) self.sigproxy = SignalProxy(self.sigChanged, slot=self.delayedChange) def getFile(self, fn): return getFileHandle(os.path.join(self.name(), fn)) def __repr__(self): return "<%s '%s' (0x%x)>" % (self.__class__.__name__, self.name(), self.__hash__()) def __reduce__(self): return (getHandle, (self.name(),)) def name(self, relativeTo=None): """Return the full name of this file with its absolute path""" #self.checkExists() with self.lock: path = self.path if relativeTo == self: path = '' elif relativeTo is not None: rpath = relativeTo.name() if not self.isGrandchildOf(relativeTo): raise Exception("Path %s is not child of %s" % (path, rpath)) return path[len(os.path.join(rpath, '')):] return path def shortName(self): """Return the name of this file without its path""" #self.checkExists() return os.path.split(self.name())[1] def ext(self): """Return file's extension""" return os.path.splitext(self.name())[1] def parent(self): self.checkExists() with self.lock: if self.parentDir is None: dirName = os.path.split(self.name())[0] self.parentDir = self.manager.getDirHandle(dirName) return self.parentDir def info(self): self.checkExists() info = self.parent()._fileInfo(self.shortName()) return advancedTypes.ProtectedDict(info) def setInfo(self, info=None, **args): """Set meta-information for this file. Updates all keys specified in info, leaving others unchanged.""" if info is None: info = args self.checkExists() self.emitChanged('meta') return self.parent()._setFileInfo(self.shortName(), info) def isManaged(self): self.checkExists() return self.parent().isManaged(self.shortName()) def move(self, newDir): self.checkExists() with self.lock: oldDir = self.parent() fn1 = self.name() name = self.shortName() fn2 = os.path.join(newDir.name(), name) if os.path.exists(fn2): raise Exception("Destination file %s already exists." % fn2) # print "<> DataManager.move: about to move %s => %s" % (fn1, fn2) if oldDir.isManaged() and not newDir.isManaged(): raise Exception("Not moving managed file to unmanaged location--this would cause loss of meta info.") os.rename(fn1, fn2) # print "<> DataManager.move: moved." self.path = fn2 self.parentDir = None # print "<> DataManager.move: inform DM of change.." self.manager._handleChanged(self, 'moved', fn1, fn2) if oldDir.isManaged() and newDir.isManaged(): # print "<> DataManager.move: new parent index old info.." newDir.indexFile(name, info=oldDir._fileInfo(name)) elif newDir.isManaged(): # print "<> DataManager.move: new parent index (no info)" newDir.indexFile(name) if oldDir.isManaged() and oldDir.isManaged(name): # print "<> DataManager.move: old parent forget child.." oldDir.forget(name) # print "<> DataManager.move: emit 'moved'.." self.emitChanged('moved', fn1, fn2) # print "<> DataManager.move: oldDir emit 'children'" oldDir._childChanged() # print "<> DataManager.move: newDir emit 'children'" newDir._childChanged() def rename(self, newName): #print "Rename %s -> %s" % (self.name(), newName) self.checkExists() with self.lock: parent = self.parent() fn1 = self.name() oldName = self.shortName() fn2 = os.path.join(parent.name(), newName) if os.path.exists(fn2): raise Exception("Destination file %s already exists." % fn2) #print "rename", fn1, fn2 info = {} if parent.isManaged(oldName): info = parent._fileInfo(oldName) parent.forget(oldName) os.rename(fn1, fn2) self.path = fn2 self.manager._handleChanged(self, 'renamed', fn1, fn2) if parent.isManaged(oldName): parent.indexFile(newName, info=info) self.emitChanged('renamed', fn1, fn2) self.parent()._childChanged() def delete(self): self.checkExists() with self.lock: parent = self.parent() fn1 = self.name() oldName = self.shortName() if self.isFile(): os.remove(fn1) else: shutil.rmtree(fn1) self.manager._handleChanged(self, 'deleted', fn1) self.path = None if parent.isManaged(): parent.forget(oldName) self.emitChanged('deleted', fn1) parent._childChanged() def read(self, *args, **kargs): self.checkExists() with self.lock: typ = self.fileType() if typ is None: fd = open(self.name(), 'r') data = fd.read() fd.close() else: cls = filetypes.getFileType(typ) data = cls.read(self, *args, **kargs) #mod = __import__('acq4.filetypes.%s' % typ, fromlist=['*']) #func = getattr(mod, 'fromFile') #data = func(fileName=self.name()) return data def fileType(self): with self.lock: info = self.info() ## Use the recorded object_type to read the file if possible. ## Otherwise, ask the filetypes to choose the type for us. if '__object_type__' not in info: typ = filetypes.suggestReadType(self) else: typ = info['__object_type__'] return typ def emitChanged(self, change, *args): self.delayedChanges.append(change) self.sigChanged.emit(self, change, args) def delayedChange(self, args): changes = list(set(self.delayedChanges)) self.delayedChanges = [] #self.emit(QtCore.SIGNAL('delayedChange'), self, changes) self.sigDelayedChange.emit(self, changes) def hasChildren(self): self.checkExists() return False def _parentMoved(self, oldDir, newDir): """Inform this object that it has been moved as a result of its (grand)parent having moved.""" prefix = os.path.join(oldDir, '') if self.path[:len(prefix)] != prefix: raise Exception("File %s is not in moved tree %s, should not update!" % (self.path, oldDir)) subName = self.path[len(prefix):] #while subName[0] == os.path.sep: #subName = subName[1:] newName = os.path.join(newDir, subName) #print "===", oldDir, newDir, subName, newName if not os.path.exists(newName): raise Exception("File %s does not exist." % newName) self.path = newName self.parentDir = None #print "parent of %s changed" % self.name() self.emitChanged('parent') def exists(self, name=None): if self.path is None: return False if name is not None: raise Exception("Cannot check for subpath existence on FileHandle.") return os.path.exists(self.path) def checkExists(self): if not self.exists(): raise Exception("File '%s' does not exist." % self.path) def checkDeleted(self): if self.path is None: raise Exception("File has been deleted.") def isDir(self, path=None): return False def isFile(self): return True def _deleted(self): self.path = None def isGrandchildOf(self, grandparent): """Return true if this files is anywhere in the tree beneath grandparent.""" gname = os.path.join(grandparent.name(), '') return self.name()[:len(gname)] == gname def write(self, data, **kwargs): self.parent().writeFile(data, self.shortName(), **kwargs) def flushSignals(self): """If any delayed signals are pending, send them now.""" self.sigproxy.flush()
class FileHandle(QtCore.QObject): sigChanged = QtCore.Signal(object, object, object) # (self, change, (args)) sigDelayedChange = QtCore.Signal(object, object) # (self, changes) def __init__(self, path, manager): QtCore.QObject.__init__(self) self.manager = manager self.delayedChanges = [] self.path = os.path.abspath(path) self.parentDir = None self.lock = Mutex(QtCore.QMutex.Recursive) self.sigproxy = SignalProxy(self.sigChanged, slot=self.delayedChange) def getFile(self, fn): return getFileHandle(os.path.join(self.name(), fn)) def __repr__(self): return "<%s '%s' (0x%x)>" % (self.__class__.__name__, self.name(), self.__hash__()) def __reduce__(self): return (getHandle, (self.name(),)) def name(self, relativeTo=None): """Return the full name of this file with its absolute path""" #self.checkExists() with self.lock: path = self.path if relativeTo == self: path = '' elif relativeTo is not None: commonParent = relativeTo pcount = 0 while True: if self is commonParent or self.isGrandchildOf(commonParent): break else: pcount += 1 commonParent = commonParent.parent() if commonParent is None: raise Exception("No relative path found from %s to %s." % (relativeTo.name(), self.name())) rpath = path[len(os.path.join(commonParent.name(), '')):] if pcount == 0: return rpath else: ppath = os.path.join(*(['..'] * pcount)) if rpath != '': return os.path.join(ppath, rpath) else: return ppath return path def shortName(self): """Return the name of this file without its path""" #self.checkExists() return os.path.split(self.name())[1] def ext(self): """Return file's extension""" return os.path.splitext(self.name())[1] def parent(self): self.checkExists() with self.lock: if self.parentDir is None: dirName = os.path.split(self.name())[0] self.parentDir = self.manager.getDirHandle(dirName) return self.parentDir def info(self): self.checkExists() info = self.parent()._fileInfo(self.shortName()) return advancedTypes.ProtectedDict(info) def setInfo(self, info=None, **args): """Set meta-information for this file. Updates all keys specified in info, leaving others unchanged.""" if info is None: info = args self.checkExists() self.emitChanged('meta') return self.parent()._setFileInfo(self.shortName(), info) def isManaged(self): self.checkExists() return self.parent().isManaged(self.shortName()) def move(self, newDir): self.checkExists() with self.lock: oldDir = self.parent() fn1 = self.name() name = self.shortName() fn2 = os.path.join(newDir.name(), name) if os.path.exists(fn2): raise Exception("Destination file %s already exists." % fn2) if oldDir.isManaged() and not newDir.isManaged(): raise Exception("Not moving managed file to unmanaged location--this would cause loss of meta info.") os.rename(fn1, fn2) self.path = fn2 self.parentDir = None self.manager._handleChanged(self, 'moved', fn1, fn2) if oldDir.isManaged() and newDir.isManaged(): newDir.indexFile(name, info=oldDir._fileInfo(name)) elif newDir.isManaged(): newDir.indexFile(name) if oldDir.isManaged() and oldDir.isManaged(name): oldDir.forget(name) self.emitChanged('moved', fn1, fn2) oldDir._childChanged() newDir._childChanged() def rename(self, newName): """Rename this file. *newName* should be the new name of the file *excluding* its path. """ self.checkExists() with self.lock: parent = self.parent() fn1 = self.name() oldName = self.shortName() fn2 = os.path.join(parent.name(), newName) if os.path.exists(fn2): raise Exception("Destination file %s already exists." % fn2) info = {} managed = parent.isManaged(oldName) if managed: info = parent._fileInfo(oldName) parent.forget(oldName) os.rename(fn1, fn2) self.path = fn2 self.manager._handleChanged(self, 'renamed', fn1, fn2) if managed: parent.indexFile(newName, info=info) self.emitChanged('renamed', fn1, fn2) self.parent()._childChanged() def delete(self): self.checkExists() with self.lock: parent = self.parent() fn1 = self.name() oldName = self.shortName() if parent.isManaged(): parent.forget(oldName) if self.isFile(): os.remove(fn1) else: shutil.rmtree(fn1) self.manager._handleChanged(self, 'deleted', fn1) self.path = None self.emitChanged('deleted', fn1) parent._childChanged() def read(self, *args, **kargs): self.checkExists() with self.lock: typ = self.fileType() if typ is None: fd = open(self.name(), 'r') data = fd.read() fd.close() else: cls = filetypes.getFileType(typ) data = cls.read(self, *args, **kargs) return data def fileType(self): with self.lock: info = self.info() ## Use the recorded object_type to read the file if possible. ## Otherwise, ask the filetypes to choose the type for us. if '__object_type__' not in info: typ = filetypes.suggestReadType(self) else: typ = info['__object_type__'] return typ def emitChanged(self, change, *args): self.delayedChanges.append(change) self.sigChanged.emit(self, change, args) def delayedChange(self, args): changes = list(set(self.delayedChanges)) self.delayedChanges = [] self.sigDelayedChange.emit(self, changes) def hasChildren(self): # self.checkExists() return False def _parentMoved(self, oldDir, newDir): """Inform this object that it has been moved as a result of its (grand)parent having moved.""" prefix = os.path.join(oldDir, '') if self.path[:len(prefix)] != prefix: raise Exception("File %s is not in moved tree %s, should not update!" % (self.path, oldDir)) subName = self.path[len(prefix):] newName = os.path.join(newDir, subName) if not os.path.exists(newName): raise Exception("File %s does not exist." % newName) self.path = newName self.parentDir = None self.emitChanged('parent') def exists(self, name=None): if self.path is None: return False if name is not None: raise Exception("Cannot check for subpath existence on FileHandle.") return os.path.exists(self.path) def checkExists(self): if not self.exists(): raise Exception("File '%s' does not exist." % self.path) def checkDeleted(self): if self.path is None: raise Exception("File has been deleted.") def isDir(self, path=None): return False def isFile(self): return True def _deleted(self): self.path = None def isGrandchildOf(self, grandparent): """Return true if this files is anywhere in the tree beneath grandparent.""" gname = os.path.join(abspath(grandparent.name()), '') return abspath(self.name())[:len(gname)] == gname def write(self, data, **kwargs): self.parent().writeFile(data, self.shortName(), **kwargs) def flushSignals(self): """If any delayed signals are pending, send them now.""" self.sigproxy.flush()