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 __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 __init__(self, camera, module): CameraModuleInterface.__init__(self, camera, module) self.module = module self.view = module.getView() self.hasQuit = False self.boundaryItems = {} ## setup UI self.ui = CameraInterfaceTemplate() self.widget = dockarea.DockArea() w = QtGui.QWidget() self.ui.setupUi(w) # takes care of displaying image data, # contrast & background subtraction user interfaces self.imagingCtrl = ImagingCtrl() self.frameDisplay = self.imagingCtrl.frameDisplay ## Move control panels into docks recDock = dockarea.Dock(name="Recording", widget=self.imagingCtrl, size=(100, 10), autoOrientation=False) devDock = dockarea.Dock(name="Device Control", widget=self.ui.devCtrlWidget, size=(100, 10), autoOrientation=False) dispDock = dockarea.Dock(name="Display Control", widget=self.frameDisplay.contrastWidget(), size=(100, 600), autoOrientation=False) bgDock = dockarea.Dock(name="Background Subtraction", widget=self.frameDisplay.backgroundWidget(), size=(100, 10), autoOrientation=False) self.widget.addDock(recDock) self.widget.addDock(devDock, 'bottom', recDock) self.widget.addDock(dispDock, 'bottom', devDock) self.widget.addDock(bgDock, 'bottom', dispDock) ## Camera state variables self.cam = camera self.roi = None self.exposure = 0.001 self.binning = 1 self.region = None ## set up item groups self.cameraItemGroup = pg.ItemGroup( ) ## translated with scope, scaled with camera objective self.imageItemGroup = pg.ItemGroup( ) ## translated and scaled as each frame arrives self.view.addItem(self.imageItemGroup) self.view.addItem(self.cameraItemGroup) self.cameraItemGroup.setZValue(0) self.imageItemGroup.setZValue(-2) ## video image item self.imageItem = self.frameDisplay.imageItem() self.view.addItem(self.imageItem) self.imageItem.setParentItem(self.imageItemGroup) self.imageItem.setZValue(-10) ## open camera, determine bit depth and sensor area self.openCamera() ## Initialize values self.lastCameraPosition = Point(self.camSize[0] * 0.5, self.camSize[1] * 0.5) self.lastCameraScale = Point(1.0, 1.0) self.scopeCenter = [self.camSize[0] * 0.5, self.camSize[1] * 0.5] self.cameraScale = [1, 1] ## Camera region-of-interest control self.roi = CamROI(self.camSize, parent=self.cameraItemGroup) self.roi.sigRegionChangeFinished.connect(self.regionWidgetChanged) self.roi.setZValue(-1) self.setRegion() ## Set up microscope objective borders self.borders = CameraItemGroup(self.cam) self.module.addItem(self.borders) self.borders.setZValue(-1) self.cam.sigGlobalTransformChanged.connect(self.globalTransformChanged) self.globalTransformChanged() # initially set binning and exposure from camera state self.exposure = self.cam.getParam('exposure') self.binning = self.cam.getParam('binning')[0] ## Initialize values/connections in Camera Dock self.setUiBinning(self.binning) self.ui.spinExposure.setValue(self.exposure) self.ui.spinExposure.setOpts(dec=True, step=1, minStep=100e-6, siPrefix=True, suffix='s', bounds=[0, 10]) #Signals from self.ui.btnSnap and self.ui.recordStackBtn are caught by the RecordThread self.ui.btnFullFrame.clicked.connect(lambda: self.setRegion()) self.proxy1 = SignalProxy(self.ui.binningCombo.currentIndexChanged, slot=self.binningComboChanged) self.ui.spinExposure.valueChanged.connect( self.setExposure ) ## note that this signal (from acq4.util.SpinBox) is delayed. ## Signals from Camera device self.cam.sigNewFrame.connect(self.newFrame) self.cam.sigCameraStopped.connect(self.cameraStopped) self.cam.sigCameraStarted.connect(self.cameraStarted) self.cam.sigShowMessage.connect(self.showMessage) self.frameDisplay.imageUpdated.connect(self.imageUpdated) self.imagingCtrl.sigStartVideoClicked.connect(self.startAcquireClicked) self.imagingCtrl.sigStopVideoClicked.connect(self.stopAcquireClicked) self.imagingCtrl.ui.acquireFrameBtn.setEnabled(False)
class CameraInterface(CameraModuleInterface): """ This class provides all the functionality necessary for a camera to display images and controls within the camera module's main window. Each camera that connects to a camera module must implement an instance of this interface. The interface provides a control GUI via the controlWidget() method and directly manages its own GraphicsItems within the camera module's view box. """ sigNewFrame = Qt.Signal(object, object) # self, frame def __init__(self, camera, module): CameraModuleInterface.__init__(self, camera, module) self.module = module self.view = module.getView() self.hasQuit = False self.boundaryItems = {} ## setup UI self.ui = CameraInterfaceTemplate() self.widget = dockarea.DockArea() w = Qt.QWidget() self.ui.setupUi(w) # takes care of displaying image data, # contrast & background subtraction user interfaces self.imagingCtrl = ImagingCtrl() self.frameDisplay = self.imagingCtrl.frameDisplay ## Move control panels into docks recDock = dockarea.Dock(name="Recording", widget=self.imagingCtrl, size=(100, 10), autoOrientation=False) devDock = dockarea.Dock(name="Device Control", widget=self.ui.devCtrlWidget, size=(100, 10), autoOrientation=False) dispDock = dockarea.Dock(name="Display Control", widget=self.frameDisplay.contrastWidget(), size=(100, 600), autoOrientation=False) bgDock = dockarea.Dock(name="Background Subtraction", widget=self.frameDisplay.backgroundWidget(), size=(100, 10), autoOrientation=False) self.widget.addDock(recDock) self.widget.addDock(devDock, 'bottom', recDock) self.widget.addDock(dispDock, 'bottom', devDock) self.widget.addDock(bgDock, 'bottom', dispDock) ## Camera state variables self.cam = camera self.roi = None self.exposure = 0.001 self.binning = 1 self.region = None ## set up item groups self.cameraItemGroup = pg.ItemGroup( ) ## translated with scope, scaled with camera objective self.imageItemGroup = pg.ItemGroup( ) ## translated and scaled as each frame arrives self.view.addItem(self.imageItemGroup) self.view.addItem(self.cameraItemGroup) self.cameraItemGroup.setZValue(0) self.imageItemGroup.setZValue(-2) ## video image item self.imageItem = self.frameDisplay.imageItem() self.view.addItem(self.imageItem) self.imageItem.setParentItem(self.imageItemGroup) self.imageItem.setZValue(-10) ## open camera, determine bit depth and sensor area self.openCamera() ## Initialize values self.lastCameraPosition = Point(self.camSize[0] * 0.5, self.camSize[1] * 0.5) self.lastCameraScale = Point(1.0, 1.0) self.scopeCenter = [self.camSize[0] * 0.5, self.camSize[1] * 0.5] self.cameraScale = [1, 1] ## Camera region-of-interest control self.roi = CamROI(self.camSize, parent=self.cameraItemGroup) self.roi.sigRegionChangeFinished.connect(self.regionWidgetChanged) self.roi.setZValue(-1) self.setRegion() ## Set up microscope objective borders self.borders = CameraItemGroup(self.cam) self.module.addItem(self.borders) self.borders.setZValue(-1) self.cam.sigGlobalTransformChanged.connect(self.globalTransformChanged) self.globalTransformChanged() # initially set binning and exposure from camera state self.exposure = self.cam.getParam('exposure') self.binning = self.cam.getParam('binning')[0] ## Initialize values/connections in Camera Dock self.setUiBinning(self.binning) self.ui.spinExposure.setValue(self.exposure) self.ui.spinExposure.setOpts(dec=True, step=1, minStep=100e-6, siPrefix=True, suffix='s', bounds=[0, 10]) #Signals from self.ui.btnSnap and self.ui.recordStackBtn are caught by the RecordThread self.ui.btnFullFrame.clicked.connect(lambda: self.setRegion()) self.binningComboProxy = SignalProxy( self.ui.binningCombo.currentIndexChanged, slot=self.binningComboChanged) self.ui.spinExposure.valueChanged.connect( self.setExposure ) ## note that this signal (from acq4.util.SpinBox) is delayed. ## Signals from Camera device self.cam.sigNewFrame.connect(self.newFrame) self.cam.sigCameraStopped.connect(self.cameraStopped) self.cam.sigCameraStarted.connect(self.cameraStarted) self.cam.sigShowMessage.connect(self.showMessage) self.cam.sigParamsChanged.connect(self.cameraParamsChanged) self.frameDisplay.imageUpdated.connect(self.imageUpdated) self.imagingCtrl.sigStartVideoClicked.connect(self.startAcquireClicked) self.imagingCtrl.sigStopVideoClicked.connect(self.stopAcquireClicked) self.imagingCtrl.ui.acquireFrameBtn.setEnabled(False) # Hotkey signals for action, key in self.cam.camConfig.get('hotkeys', {}).items(): if action not in ['snap', 'start']: raise ValueError("Unknown hotkey action %r" % action) dev = Manager.getManager().getDevice(key['device']) dev.addKeyCallback(key['key'], self.hotkeyPressed, (action, )) def newFrame(self, frame): self.imagingCtrl.newFrame(frame) self.sigNewFrame.emit(self, frame) def controlWidget(self): return self.widget def openCamera(self, ind=0): try: self.camSize = self.cam.getParam('sensorSize') self.showMessage("Opened camera %s" % self.cam, 5000) self.scope = self.cam.getScopeDevice() try: bins = self.cam.listParams('binning')[0][0] except: bins = self.cam.listParams('binningX')[0] bins.sort() bins.reverse() for b in bins: self.ui.binningCombo.addItem(str(b)) except: self.showMessage("Error opening camera") raise def globalTransformChanged(self, emitter=None, changedDev=None, transform=None): ## scope has moved; update viewport and camera outlines. ## This is only used when the camera is not running-- ## if the camera is running, then this is taken care of in drawFrame to ## ensure that the image remains stationary on screen. if not self.cam.isRunning(): tr = pg.SRTTransform(self.cam.globalTransform()) self.updateTransform(tr) def imageUpdated(self, frame): ## New image is displayed; update image transform self.imageItem.setTransform(frame.frameTransform().as2D()) ## Update viewport to correct for scope movement/scaling tr = pg.SRTTransform(frame.deviceTransform()) self.updateTransform(tr) self.imageItemGroup.setTransform(tr) def updateTransform(self, tr): ## update view for new transform such that sensor bounds remain stationary on screen. pos = tr.getTranslation() scale = tr.getScale() if scale != self.lastCameraScale: anchor = self.view.mapViewToDevice(self.lastCameraPosition) self.view.scaleBy(scale / self.lastCameraScale) Qt.QApplication.processEvents() anchor2 = self.view.mapDeviceToView(anchor) diff = pos - anchor2 self.lastCameraScale = scale else: diff = pos - self.lastCameraPosition self.view.translateBy(diff) self.lastCameraPosition = pos self.cameraItemGroup.setTransform(tr) def regionWidgetChanged(self, *args): self.updateRegion() def updateRegion(self, autoRestart=True): #self.clearFrameBuffer() r = self.roi.parentBounds() newRegion = [ int(r.left()), int(r.top()), int(r.width()), int(r.height()) ] if self.region != newRegion: self.region = newRegion self.cam.setParam('region', self.region, autoRestart=autoRestart) def quit(self): self.imagingCtrl.quit() if self.hasQuit: return try: self.cam.sigNewFrame.disconnect(self.newFrame) self.cam.sigCameraStopped.disconnect(self.cameraStopped) self.cam.sigCameraStarted.disconnect(self.cameraStarted) self.cam.sigShowMessage.disconnect(self.showMessage) except TypeError: pass self.hasQuit = True if self.cam.isRunning(): self.cam.stop() if not self.cam.wait(10000): printExc("Timed out while waiting for acq thread exit!") def hotkeyPressed(self, device, keys, action): callback = { 'snap': self.imagingCtrl.saveFrameClicked, 'start': self.imagingCtrl.acquireVideoClicked, }.get(action) callback() def cameraStopped(self): self.imagingCtrl.acquisitionStopped() def cameraStarted(self): self.imagingCtrl.acquisitionStarted() def binningComboChanged(self, args): self.setBinning(*args) def setBinning(self, ind=None, autoRestart=True): """Set camera's binning value. If ind is specified, it is the index from binningCombo from which to grab the new binning value.""" #self.backgroundFrame = None if ind is not None: self.binning = int(self.ui.binningCombo.itemText(ind)) self.cam.setParam('binning', (self.binning, self.binning), autoRestart=autoRestart) #self.clearFrameBuffer() ###self.updateRgnLabel() def setUiBinning(self, b, updateCamera=True): ind = self.ui.binningCombo.findText(str(b)) if ind == -1: raise Exception("Binning mode %s not in list." % str(b)) if updateCamera: self.ui.binningCombo.setCurrentIndex(ind) else: with self.binningComboProxy.block(): self.ui.binningCombo.setCurrentIndex(ind) def setExposure(self, e=None, autoRestart=True): if e is not None: self.exposure = e self.cam.setParam('exposure', self.exposure, autoRestart=autoRestart) def updateCameraDecorations(self): ps = self.cameraScale pos = self.lastCameraPosition cs = self.camSize if ps is None: return m = self.cam.globalTransform() self.cameraItemGroup.setTransform(pg.SRTTransform(m)) def setRegion(self, rgn=None): if rgn is None: rgn = [0, 0, self.camSize[0] - 1, self.camSize[1] - 1] self.roi.setPos([rgn[0], rgn[1]]) self.roi.setSize([self.camSize[0], self.camSize[1]]) def cameraParamsChanged(self, changes): # camera parameters changed; update ui to match if 'exposure' in changes: with pg.SignalBlock(self.ui.spinExposure.valueChanged, self.setExposure): self.ui.spinExposure.setValue(changes['exposure']) if 'binningX' in changes: self.setUiBinning(changes['binningX'], updateCamera=False) elif 'binning' in changes: self.setUiBinning(changes['binning'][0], updateCamera=False) if 'region' in changes: with pg.SignalBlock(self.roi.sigRegionChangeFinished, self.regionWidgetChanged): rgn = changes['region'] self.roi.setPos([rgn[0], rgn[1]]) self.roi.setSize([rgn[2], rgn[3]]) if 'regionX' in changes or 'regionY' in changes: with pg.SignalBlock(self.roi.sigRegionChangeFinished, self.regionWidgetChanged): x = changes.get('regionX', self.roi.pos().x()) y = changes.get('regionY', self.roi.pos().y()) self.roi.setPos([x, y]) if 'regionW' in changes or 'regionH' in changes: with pg.SignalBlock(self.roi.sigRegionChangeFinished, self.regionWidgetChanged): w = changes.get('regionW', self.roi.state['size'][0]) h = changes.get('regionH', self.roi.state['size'][1]) self.roi.setSize([w, h]) def startAcquireClicked(self, mode): """User clicked the acquire video button. """ try: self.cam.setParam('triggerMode', 'Normal', autoRestart=False) self.setBinning(autoRestart=False) self.setExposure(autoRestart=False) self.updateRegion(autoRestart=False) self.cam.start() Manager.logMsg("Camera started aquisition.", importance=0) except: self.imagingCtrl.acquisitionStopped() printExc("Error starting camera:") def stopAcquireClicked(self): self.cam.stop() Manager.logMsg("Camera stopped acquisition.", importance=0) def showMessage(self, msg, delay=2000): self.module.showMessage(msg, delay) def getImageItem(self): return self.imageItem def boundingRect(self): """ Return bounding rect of this imaging device in global coordinates """ return self.cam.getBoundary().boundingRect() def takeImage(self, closeShutter=None): # closeShutter is used for laser scanning devices; we can ignore it here. return self.getDevice().acquireFrames(1, stack=False)
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()