#----------------------------------------------------------------------------- # Name: FileExplorer.py # Purpose: Classes for filesystem exploring # # Author: Riaan Booysen # # Created: 2001/03/06 # RCS-ID: $Id: FileExplorer.py,v 1.27 2005/05/18 12:59:08 riaan Exp $ # Copyright: (c) 2001 - 2005 Riaan Booysen # Licence: GPL #----------------------------------------------------------------------------- print 'importing Explorers.FileExplorer' import os, time, stat import wx import Preferences, Utils import ExplorerNodes from Models import Controllers, EditorHelper from PropEdit import PropertyEditors, InspectorEditorControls class FileSysCatNode(ExplorerNodes.CategoryNode): # protocol = 'config.file' itemProtocol = 'file' defName, defaultStruct = Preferences.explorerFileSysRootDefault def __init__(self, clipboard, config, parent, bookmarks): ExplorerNodes.CategoryNode.__init__(self, 'Filesystem', ('explorer', 'file'), clipboard, config, parent) self.bookmarks = bookmarks if not self.entries and wx.Platform == '__WXMSW__': drives = {} for x in range(ord('C'), ord('Z')+1): driveName = '%s:\\'%(chr(x)) if os.path.exists(driveName): drives[driveName] = driveName self.entries = drives self.updateConfig() wx.LogMessage('%d drives added to the filesystem definition.'%len(drives)) def createParentNode(self): return self.parent def createCatCompanion(self, catNode): comp = ExplorerNodes.CategoryStringCompanion(catNode.treename, self) return comp def createChildNode(self, entry, value, forceFolder=True): if forceFolder: Node = NonCheckPyFolderNode else: Node = FileSysNode node = Node(entry, value, self.clipboard, -1, self, self.bookmarks) if node.isFolderish(): node.imgIdx = EditorHelper.imgFSDrive else: node.imgIdx = Controllers.identifyFile(value #localfs=node.filter == 'BoaFiles' )[0].imgIdx return node ## def newItem(self): ## name = ExplorerNodes.CategoryNode.newItem() ## self.entries[name] = copy.copy(self.defaultStruct) ## self.updateConfig() ## return name def renameItem(self, name, newName): if self.entries.has_key(newName): raise Exception, 'Name exists' self.entries[newName] = newName del self.entries[name] self.updateConfig() def getNodeFromPath(self, respath, forceFolder=True): cn = self.createChildNode(os.path.basename(respath), respath, forceFolder) return cn (wxID_FSOPEN, wxID_FSTEST, wxID_FSNEW, wxID_FSNEWFOLDER, wxID_FSCVS, wxID_FSBOOKMARK, wxID_FSFINDINFILES, wxID_FSFINDFILES, wxID_FSFILTERIMAGES, wxID_FSSETASCWD, wxID_FSNEWZIP, wxID_FSINSPECT, ) = Utils.wxNewIds(12) (wxID_FSFILTER, wxID_FSFILTERBOAMODULES, wxID_FSFILTERSTDMODULES, wxID_FSFILTERINTMODULES, wxID_FSFILTERALLMODULES, ) = Utils.wxNewIds(5) filterDescrOrd = ['BoaFiles', 'StdFiles', 'BoaIntFiles', 'ImageFiles', 'AllFiles'] filterDescr = {'BoaFiles': ('Boa files', wxID_FSFILTERBOAMODULES), 'StdFiles': ('Standard files', wxID_FSFILTERSTDMODULES), 'BoaIntFiles': ('Internal files', wxID_FSFILTERINTMODULES), 'ImageFiles': ('Image files', wxID_FSFILTERIMAGES), 'AllFiles': ('All files', wxID_FSFILTERALLMODULES)} # XXX CVS and Zip support must also move to registering class FileSysController(ExplorerNodes.Controller, ExplorerNodes.ClipboardControllerMix): bookmarkBmp = 'Images/Shared/Bookmark.png' findBmp = 'Images/Shared/Find.png' inspectBmp = 'Images/Shared/Inspector.png' def __init__(self, editor, list, inspector, controllers): ExplorerNodes.Controller.__init__(self, editor) ExplorerNodes.ClipboardControllerMix.__init__(self) self.editor = editor self.inspector = inspector self.list = list ## if controllers.has_key('cvs'): ## self.cvsController = controllers['cvs'] ## else: ## self.cvsController = None self.menu = wx.Menu() self.fileMenuDef = [ (wxID_FSOPEN, 'Open', self.OnOpenItems, '-'), (-1, '-', None, ''), (wxID_FSINSPECT, 'Inspect', self.OnInspectItem, self.inspectBmp), (-1, '-', None, ''), ] + self.clipMenuDef + [ (wxID_FSSETASCWD, 'Set as os.cwd', self.OnSetAsSysCwd, '-'), (-1, '-', None, ''), (wxID_FSFINDINFILES, 'Find', self.OnFindFSItem, self.findBmp), ] if controllers.has_key('zip'): self.newMenuDef.append( (wxID_FSNEWZIP, 'Empty zip archive', self.OnEmptyZipArchive, '-') ) if controllers.has_key('tar.gz'): self.newMenuDef.append( (wxID_FSNEWZIP, 'Empty tar.gz archive', self.OnEmptyTarGzipArchive, '-') ) self.setupMenu(self.menu, self.list, self.fileMenuDef) filters = [] for filter in filterDescrOrd: descr, wid = filterDescr[filter] filters.append( (wid, '+'+descr, self.OnFilterFSItems , '-') ) self.fileFilterMenuDef = filters self.fileFilterMenu = wx.Menu() self.setupMenu(self.fileFilterMenu, self.list, self.fileFilterMenuDef, False) # Check default option self.fileFilterMenu.Check(wxID_FSFILTERBOAMODULES, True) self.menu.AppendMenu(wxID_FSFILTER, 'Filter', self.fileFilterMenu) if controllers.has_key('cvs'): self.menu.AppendMenu(wxID_FSCVS, 'CVS', controllers['cvs'].fileCVSMenu) self.toolbarMenus = [self.fileMenuDef] def destroy(self): ExplorerNodes.ClipboardControllerMix.destroy(self) self.fileFilterMenuDef = [] self.fileMenuDef = [] self.toolbarMenus = [] self.menu.Destroy() def OnTest(self, event): print self.list.node.clipboard.globClip.currentClipboard def OnFilterFSItems(self, event): evtid = event.GetId() self.groupCheckMenu(self.fileFilterMenu, self.fileFilterMenuDef, evtid, True) for filter in filterDescrOrd: descr, wid = filterDescr[filter] if wid == evtid: self.list.node.setFilter(filter) self.list.refreshCurrent() break def addFindResults(self, pattern, mapResults): """ mapResult is map of tuples in this form {'Module': ('Line no', 'Col', 'Text'), ...} """ nd = self.list.node if not isinstance( self.list.node, ResultsFolderNode ): self.list.node = ResultsFolderNode('Results', nd.resourcepath, nd.clipboard, -1, nd, nd.bookmarks) mapFindInFileCount = {} for oFindRes in mapResults.keys(): if len( mapResults[oFindRes] ): mapFindInFileCount[oFindRes] = len( mapResults[oFindRes] ) self.list.node.results = map(None, mapFindInFileCount.values(), mapFindInFileCount.keys() ) self.list.node.lastSearch = pattern self.list.refreshCurrent() def OnFindFSItem(self, event): # XXX This is nasty, redesign self.list.addFindResults = self.addFindResults import FindReplaceDlg dlg = FindReplaceDlg.FindReplaceDlg(self.list, self.editor.finder, self.list, 0) dlg.SetWorkingFolder(self.list.node.resourcepath) try: dlg.ShowModal() finally: dlg.Destroy() def OnSetAsSysCwd(self, event): node = self.list.getSelection() if node: path = node.resourcepath else: path = self.list.node.resourcepath if os.path.isfile(path): path = os.path.split(path)[0] os.chdir(path) self.editor.setStatus('Updated os.cwd to %s'%path, ringBell=True) def OnEmptyZipArchive(self, event): # must move to zip files = os.listdir(self.list.node.resourcepath) newName = Utils.getValidName(files, 'archive', 'zip') import zipfile zipfile.ZipFile(os.path.join(self.list.node.resourcepath, newName), 'w', zipfile.ZIP_DEFLATED).close() self.list.refreshCurrent() self.list.selectItemNamed(newName) self.list.EnsureVisible(self.list.selected) def OnEmptyTarGzipArchive(self, event): pass def OnInspectItem(self, event): if self.list.node: item = self.list.getSelection() if item: comp = FileSysCompanion(item.name, item) comp.updateProps() self.inspector.selectObject(comp, False, focusPage=1) class FileSysAttrPropEdit(PropertyEditors.PropertyEditor): def inspectorEdit(self): self.editorCtrl = InspectorEditorControls.BeveledLabelIEC(self, self.getValue()) self.editorCtrl.createControl(self.parent, self.idx, self.width) self.editorCtrl.setValue(self.getValue()) def getDisplayValue(self): return self.value class FileSysCompanion(ExplorerNodes.ExplorerCompanion): def __init__(self, name, fsNode): ExplorerNodes.ExplorerCompanion.__init__(self, name) self.fsNode = fsNode def getPropEditor(self, prop): return FileSysAttrPropEdit timeFmt = '%a, %d %b %Y %H:%M:%S' def getPropertyItems(self): res = [] attrs = self.fsNode.stdAttrs for date in ('creation-date', 'modify-date', 'access-date'): value = attrs[date] if value: res.append( (date, time.strftime(self.timeFmt, time.localtime(value))) ) value = attrs['read-only'] res.append( ('read-only', value and 'True' or 'False') ) value = attrs['size'] if value: res.append( ('size', str(value)) ) return res class FileSysExpClipboard(ExplorerNodes.ExplorerClipboard): def clipPaste_FileSysExpClipboard(self, node, nodes, mode): # XXX Delayed cut (delete on paste or move) should clear the clipboard # XXX or refresh clipboard with new paths and clear 'cut' mode for clipnode in nodes: if mode == 'cut': node.moveFileFrom(clipnode) self.clipNodes = [] elif mode == 'copy': node.copyFileFrom(clipnode) clipPaste_DefaultClipboard = clipPaste_FileSysExpClipboard def _genericFSPaste(self, node, nodes, mode): for other in nodes: other.copyToFS(node) def clipPaste_ZopeEClip(self, node, nodes, mode): # XXX Pasting a cut from Zope does not delete the cut items from Zope for zopeNode in nodes: zopeNode.downloadToFS(os.path.join(node.resourcepath, zopeNode.name)) def clipPaste_FTPExpClipboard(self, node, nodes, mode): # XXX Pasting a cut from FTP does not delete the cut items from FTP for file in nodes: file.ftpConn.download(file.resourcepath, os.path.join(node.resourcepath, file.name)) clipPaste_SSHExpClipboard = _genericFSPaste clipPaste_ZipExpClipboard = _genericFSPaste clipPaste_DAVExpClipboard = _genericFSPaste class FileSysNode(ExplorerNodes.ExplorerNode): protocol = 'file' filter = 'BoaFiles' lastSearch = '' subExplorerReg = {'file': [], 'folder': []} connection = False pathSep = os.sep def __init__(self, name, resourcepath, clipboard, imgIdx, parent=None, bookmarks=None, properties={}): ExplorerNodes.ExplorerNode.__init__(self, name, resourcepath, clipboard, imgIdx, parent, properties or {}) self.bookmarks = bookmarks self.exts = EditorHelper.extMap.keys() + EditorHelper.inspectableFilesReg.keys() self.entries = [] # XXX not used ? self.doCVS = True self.doZip = True self.allowedProtocols = ['*'] self.updateStdAttrs() def destroy(self): self.entries = [] def isDir(self): return os.path.isdir(self.resourcepath) def isFolderish(self): return self.isDir() or filter(lambda se, rp=self.resourcepath, ap=self.allowedProtocols : (ap == ['*'] or se[0].protocol in ap) and se[1](rp), self.subExplorerReg['file']) def createParentNode(self): parent = os.path.abspath(os.path.join(self.resourcepath, '..')) if parent[-2:] == '..': parent = parent[:-2] return FileSysNode(os.path.basename(parent), parent, self.clipboard, EditorHelper.imgFolder, self, self.bookmarks) def createChildNode(self, file, filename = ''): if not filename: filename = os.path.join(self.resourcepath, file) ext = os.path.splitext(filename)[1].lower() exts, extSubTypes = self.getFilterExts() # Files if ('.*' in exts or ext in exts) and os.path.isfile(filename): for Other, otherIdFunc, imgIdx in self.subExplorerReg['file']: if '*' in self.allowedProtocols or \ Other.protocol in self.allowedProtocols: if otherIdFunc(filename): return 'fol', Other(file, filename, self.clipboard, imgIdx, self, self.bookmarks) Model = Controllers.identifyFile(filename)[0] if extSubTypes.has_key(ext): for SubTypeModel in extSubTypes[ext]: if issubclass(Model, SubTypeModel): break else: return '', None return 'mod', FileSysNode(file, filename, self.clipboard, Model.imgIdx, self, self.bookmarks, {}) #{'datetime': time.strftime('%a %b %d %H:%M:%S %Y', # time.gmtime(os.stat(filename)[stat.ST_MTIME]))}) # Directories elif os.path.isdir(filename): for other, otherIdFunc, imgIdx in self.subExplorerReg['folder']: if self.filter == 'BoaFiles' and \ '*' in self.allowedProtocols or '*' in self.allowedProtocols or \ other.protocol in self.allowedProtocols: if otherIdFunc(filename): return 'fol', other(file, filename, self.clipboard, imgIdx, self, self.bookmarks) return 'fol', FileSysNode(file, filename, self.clipboard, EditorHelper.imgFolder, self, self.bookmarks) else: return '', None def openList(self): try: files = os.listdir(self.resourcepath) except Exception, err: raise ExplorerNodes.TransportError(err) files.sort() entries = {'mod': [], 'fol': []} for file in files: tp, node = self.createChildNode(file) if node: entries[tp].append(node) self.entries = entries['fol'] + entries['mod'] return self.entries def deleteItems(self, names): for name in names: path = os.path.join(self.resourcepath, name) if os.path.isdir(path): os.rmdir(path) else: os.remove(path) def renameItem(self, name, newName): oldfile = os.path.join(self.resourcepath, name) newfile = os.path.join(self.resourcepath, newName) os.rename(oldfile, newfile) def newFolder(self, name): os.mkdir(os.path.join(self.resourcepath, name)) return name def newBlankDocument(self, name=''): newpath = os.path.join(self.resourcepath, name) if not os.path.exists(newpath): open(newpath, 'w').write(' ') return name def copyFileFrom(self, node): """ Copy node into self (only called for folders)""" import shutil if not node.isDir(): if node.resourcepath == os.path.join(self.resourcepath, node.name): newNameBase = os.path.join(self.resourcepath, 'copy%s_of_'+node.name) num = '' while 1: newName = newNameBase%num if os.path.exists(newName): try: num = str(int(num) + 1) except: num = '2' else: shutil.copy(node.resourcepath, newName) break else: shutil.copy(node.resourcepath, self.resourcepath) ## names = map(lambda n: n.name, self.entries) ## dir, name = os.path.split(self.resourcepath) ## name, ext = os.path.splitext(name) ## name = Util.getValidName(names, name, ext) ## shutil.copy(node.resourcepath, name) ## else: ## shutil.copy(node.resourcepath, self.resourcepath) else: shutil.copytree(node.resourcepath, os.path.join(self.resourcepath, node.name)) def moveFileFrom(self, node): # Moving into directory being moved should not be allowed sp = os.path.normpath(node.resourcepath) dp = os.path.normpath(self.resourcepath) if dp[:len(sp)] == sp: raise Exception('Cannot move into itself') self.copyFileFrom(node) if not node.isFolderish(): os.remove(node.resourcepath) else: import shutil shutil.rmtree(node.resourcepath) def setFilter(self, filter): self.__class__.filter = filter def getFilterExts(self): return {'BoaFiles': (self.exts, {}), 'StdFiles': (self.exts, {}), 'BoaIntFiles': (EditorHelper.internalFilesReg, {}), 'ImageFiles': (EditorHelper.imageExtReg, EditorHelper.imageSubTypeExtReg), 'AllFiles': (['.*'], {})}[self.filter] def load(self, mode='rb'): try: data = open(self.resourcepath, mode).read() self.updateStdAttrs() return data except IOError, error: raise ExplorerNodes.TransportLoadError(error, self.resourcepath) def save(self, filename, data, mode='wb', overwriteNewer=False): if self.resourcepath != filename: self.resourcepath = filename self.name = os.path.basename(self.resourcepath) try: if not overwriteNewer and self.fileIsNewer(): raise ExplorerNodes.TransportModifiedSaveError('This file has ' 'been saved by someone else since it was loaded', self.resourcepath) open(self.resourcepath, mode).write(data) except IOError, error: raise ExplorerNodes.TransportSaveError(error, self.resourcepath) self.updateStdAttrs() def getNodeFromPath(self, respath): name = os.path.basename(respath) return self.createChildNode(name, respath) def updateStdAttrs(self): exists = os.path.exists(self.resourcepath) self.stdAttrs['read-only'] = exists and \ not os.access(self.resourcepath, os.W_OK) self.stdAttrs['modify-date'] = exists and \ os.stat(self.resourcepath)[stat.ST_MTIME] or 0.0 self.stdAttrs['creation-date'] = exists and \ os.stat(self.resourcepath)[stat.ST_CTIME] or 0.0 self.stdAttrs['access-date'] = exists and \ os.stat(self.resourcepath)[stat.ST_ATIME] or 0.0 self.stdAttrs['size'] = exists and \ os.stat(self.resourcepath)[stat.ST_SIZE] or 0 def setStdAttr(self, attr, value=None): if attr == 'read-only': os.chmod(self.resourcepath, value and 0444 or 0666) self.updateStdAttrs() def fileIsNewer(self): return (os.path.exists(self.resourcepath) and \ os.stat(self.resourcepath)[stat.ST_MTIME] or 0.0) > \ self.stdAttrs['modify-date'] class ResultsFolderNode(FileSysNode): results = [] def openList(self): self.parentOpensChildren = True self.results.sort() self.results.reverse() entries = [] for occrs, filename in self.results: tp, node = self.createChildNode('(%d) %s'%(occrs, filename), os.path.join(self.resourcepath, filename)) if node: entries.append(node) self.entries = entries return self.entries def openParent(self, editor): editor.explorer.tree.SelectItem(editor.explorer.tree.GetSelection()) return True def open(self, node, editor): mod, cntrl = node.open(editor) view = mod.getSourceView() if view: view.doFind(self.lastSearch) view.doNextMatch() return mod, cntrl def getTitle(self): return 'Find results for %s in %s' % (self.lastSearch, self.resourcepath) class NonCheckPyFolderNode(FileSysNode): def isFolderish(self): return True class CurWorkDirNode(FileSysNode): protocol = 'os.cwd' def __init__(self, clipboard, parent, bookmarks): self.cwd = os.path.abspath(os.getcwd()) FileSysNode.__init__(self, 'os.cwd', self.cwd, clipboard, EditorHelper.imgPathFolder, parent) self.bookmarks = bookmarks self.bold = True #self.setFilter('AllFiles') def openList(self): self.cwd = self.resourcepath = os.path.abspath(os.getcwd()) return FileSysNode.openList(self) def getTitle(self): return 'os.cwd://%s'%self.cwd def getURI(self): return self.getTitle() def uriSplitFile(filename, filepath): return 'file', '', filepath, filename def findFileExplorerNode(category, respath, transports): for tp in transports.entries: if tp.itemProtocol == 'file': return tp.getNodeFromPath(respath, forceFolder=False) raise ExplorerNodes.TransportError( 'FileSysCatNode not found in transports %s'%transports.entries) #------------------------------------------------------------------------------- ExplorerNodes.register(FileSysNode, clipboard=FileSysExpClipboard, confdef=('explorer', 'file'), controller=FileSysController, category=FileSysCatNode) ExplorerNodes.register(CurWorkDirNode, clipboard='file', controller='file', root=True) ExplorerNodes.fileOpenDlgProtReg.append('file') ExplorerNodes.uriSplitReg[('file', 2)] = uriSplitFile ExplorerNodes.uriSplitReg[('os.cwd', 2)] = uriSplitFile ExplorerNodes.transportFindReg['file'] = findFileExplorerNode