/// /// Copyright © 2003-2008 JetBrains s.r.o. /// You may distribute under the terms of the GNU General Public License, as published by the Free Software Foundation, version 2 (see License.txt in the repository root folder). /// using System; using System.Drawing; using System.IO; using System.Reflection; using System.Threading; using System.Windows.Forms; using JetBrains.Omea.OpenAPI; using JetBrains.Omea.Base; using JetBrains.Omea.FileTypes; using JetBrains.Omea.GUIControls; using JetBrains.DataStructures; using JetBrains.Omea.ResourceTools; namespace JetBrains.Omea.FilePlugin { [PluginDescription("File Explorer", "JetBrains Inc.", "Simple file explorer in Omea with the facilities to represent the Folders tree, include/exclude folders from text searching.", PluginDescriptionFormat.PlainText, "Icons/FilePluginIcon.png")] public class FileProxy : IPlugin, IStreamProvider, IResourceTextIndexingPermitter, IResourceIconProvider, IResourceUIHandler, IResourceDragDropHandler { #region IPlugin Members public void Register() { _folderChanged = SelectedFolderChanged; _fileRenamed = SelectedFolderRenamed; RegisterTypes(); IUIManager uiMgr = Core.UIManager; OptionsPaneCreator filePaneCreator = SetFoldersForm.SetFoldersFormCreator; uiMgr.RegisterOptionsGroup( "Folders & Files", "The Folders & Files options enable you to control the indexing of folders and files." ); uiMgr.RegisterOptionsPane( "Folders & Files", "Indexed Folders", filePaneCreator, "The Indexed Folders options enable you to specify what folders on your computer should be indexed by [product name], or specifically excluded from indexing." ); uiMgr.RegisterWizardPane( "Indexed Folders", filePaneCreator, 0 ); uiMgr.RegisterOptionsPane( "Folders & Files", "File Options", FileOptionsPane.FileOptionsPaneCreator, "The File Options enable you to control the indexing and display of hidden files, and to specify the extensions of files that [product name] should treat as plain text files." ); uiMgr.RegisterResourceSelectPane( _folderResourceType, typeof( FileFoldersSelectorPane ) ); Core.TabManager.RegisterResourceTypeTab( "Files", "Files", new[] { _folderResourceType }, _propParentFolder, 5 ); IResource filesRoot = FoldersCollection.Instance.FilesRoot; Image img = Utils.TryGetEmbeddedResourceImageFromAssembly( Assembly.GetExecutingAssembly(), "FilePlugin.Icons.Folders24.png" ); _pane = Core.LeftSidebar.RegisterResourceStructureTreePane( "FileFolders", "Files", "Indexed Folders", img, filesRoot ); _pane.ParentProperty = _propParentFolder; _pane.AddNodeFilter( new FoldersFilter() ); _pane.WorkspaceFilterTypes = new[] { _folderResourceType }; Core.ResourceTreeManager.SetResourceNodeSort( filesRoot, "DisplayName" ); Core.LeftSidebar.RegisterViewPaneShortcut( "FileFolders", Keys.Control | Keys.Alt | Keys.I ); IPluginLoader loader = Core.PluginLoader; loader.RegisterStreamProvider( _folderResourceType, this ); loader.RegisterResourceUIHandler( _folderResourceType, this ); loader.RegisterResourceDragDropHandler( _folderResourceType, new DragDropLinkAdapter( this ) ); Core.UIManager.RegisterResourceLocationLink( _folderResourceType, 0, _folderResourceType ); Core.ResourceIconManager.RegisterPropTypeIcon( _propParentFolder, LoadIconFromAssembly( "file.ico" ) ); Core.ResourceIconManager.RegisterResourceIconProvider( _folderResourceType, this ); Core.ResourceBrowser.RegisterLinksPaneFilter( "FileFolder", new FileFolderLinksFilter() ); ((FileResourceManager)Core.FileResourceManager).RegisterFileTypeColumns( _folderResourceType ); LoadResourceIcons(); _filesProcessor = new FileAsyncProcessor(); _filesProcessor.ThreadName = "Files AsyncProcessor"; Core.UIManager.RegisterIndicatorLight( "Files Processor", _filesProcessor, 60, LoadIconFromAssembly( "files_idle.ico" ), LoadIconFromAssembly( "files_busy.ico" ), LoadIconFromAssembly( "files_stuck.ico" ) ); Core.ResourceStore.LinkAdded += ResourceStore_LinkAdded; Core.ResourceBrowser.ContentChanged += ResourceBrowser_ContentChanged; } private static Icon LoadIconFromAssembly( string iconName ) { return new Icon( Assembly.GetExecutingAssembly().GetManifestResourceStream( "FilePlugin.Icons." + iconName ) ); } public void Startup() { Core.StateChanged += Core_StateChanged; } public void Shutdown() { if( _filesProcessor != null ) { Core.UIManager.DeRegisterIndicatorLight( "Files Processor" ); _filesProcessor.Dispose(); } FoldersCollection.Instance.Dispose(); } private static void Core_StateChanged( object sender, EventArgs e ) { if( Core.State == CoreState.Running ) { _filesProcessor.StartThread(); _filesProcessor.ThreadPriority = ThreadPriority.BelowNormal; FoldersCollection.LoadFoldersForest(); } else if( Core.State == CoreState.ShuttingDown ) { FoldersCollection.Instance.Interrupted = true; FoldersCollection.ProcessPendingFileDeletions(); } } #endregion #region IStreamProvider Members public Stream GetResourceStream( IResource resource ) { string path = FoldersCollection.Instance.GetFullName( resource ); return IOTools.OpenRead( path ); } #endregion #region IResourceTextIndexingPermitter Members bool IResourceTextIndexingPermitter.CanIndexResource( IResource res ) { return !res.HasProp( _propDeletedFile ); } #endregion public void LoadResourceIcons() { _folderIcon = LoadIconFromAssembly( "FolderClosed.ico" ); _openFolderIcon = LoadIconFromAssembly( "FolderOpen.ico" ); _excludedFolderIcon = LoadIconFromAssembly( "FolderExcluded.ico" ); } public Icon GetResourceIcon( IResource resource ) { if( resource.Type == _folderResourceType ) { string directory = resource.GetPropText( _propDirectory ); FoldersCollection instance = FoldersCollection.Instance; if( instance.IsPathDeferred( directory ) || instance.IsPathMonitored( directory ) ) { return ( resource.GetIntProp( "Open" ) != 0 ) ? _openFolderIcon : _folderIcon; } return _excludedFolderIcon; } return null; } public Icon GetDefaultIcon( string resType ) { return ( resType == _folderResourceType ) ? _folderIcon : null; } #region IResourceUIHandler Members public bool CanDropResources( IResource targetResource, IResourceList dragResources ) { return false; } public bool CanRenameResource( IResource res ) { res = res.GetLinkProp( _propParentFolder ); return res != null && res.Type == _folderResourceType; } public void ResourcesDropped( IResource targetResource, IResourceList droppedResources ) { } public bool ResourceRenamed( IResource res, string newName ) { string directory = res.GetPropText( _propDirectory ); if( res.Type == _folderResourceType ) { string destination; try { destination = Path.Combine( Directory.GetParent( directory ).FullName, newName ); Directory.Move( directory, destination ); } catch( Exception e ) { MessageBox.Show( Core.MainWindow, "Can't rename '" + directory + "': " + e.Message, "Error", MessageBoxButtons.OK, MessageBoxIcon.Error ); return false; } FoldersCollection.Instance.RenameDirectory( res, newName, destination ); } else { string source = FoldersCollection.Instance.GetFullName( res ); string destination = Path.Combine( directory , newName ); if( source == destination ) return false; try { File.Move( source, destination ); } catch( Exception e ) { MessageBox.Show( Core.MainWindow, "Can't rename '" + source + "': " + e.Message, "Error", MessageBoxButtons.OK, MessageBoxIcon.Error ); return false; } new ResourceProxy( res ).SetPropAsync( Core.Props.Name, newName ); } return true; } public void ResourceNodeSelected( IResource res ) { try { ResourceNodeSelected( res, true ); } catch( Exception e ) { Utils.DisplayException( Core.MainWindow, e, "Error" ); Core.UserInterfaceAP.QueueJob("GoBack", new MethodInvoker( Core.ResourceBrowser.GoBack ) ); } } #endregion #region IResourceDragDropHandler Members public void Drop( IResource targetResource, IDataObject data, DragDropEffects allowedEffect, int keyState ) { IResourceList droppedResources = data.GetData( typeof( IResourceList ) ) as IResourceList; if( droppedResources != null && targetResource.Type == _folderResourceType ) { string directory = targetResource.GetPropText( _propDirectory ); try { foreach( IResource file in droppedResources ) { string droppedName = file.GetPropText( Core.Props.Name ); string dest = Path.Combine( directory, droppedName ); if( file.Type == _folderResourceType ) { if( ( keyState & 8 ) != 0 ) { // copy files FileInfo[] fileInfos = IOTools.GetFiles( file.GetPropText( _propDirectory ) ); if( fileInfos != null ) { if( Directory.Exists( dest ) || IOTools.CreateDirectory( dest ) != null ) { foreach( FileInfo fileInfo in fileInfos ) { string path = IOTools.GetFullName( fileInfo ); if( path.Length > 0 ) { File.Copy( path, IOTools.Combine( dest, IOTools.GetFileName( path ) ) ); } } } } } else { Core.ResourceAP.RunUniqueJob( "Moving directory", new MoveDirectoryDelegate( MoveDirectory ), dest, file, targetResource ); } } else { string source = Path.Combine( file.GetPropText( _propDirectory ), droppedName ); if( ( keyState & 8 ) != 0 ) { File.Copy( source, dest, false ); } else { Core.ResourceAP.RunUniqueJob( "Moving file", new MoveFileDelegate( MoveFile ), source, dest, directory, file, targetResource ); } } } } catch( Exception e ) { MessageBox.Show( Core.MainWindow, GetExceptionMessage( e ), "Error", MessageBoxButtons.OK, MessageBoxIcon.Error ); } } } public DragDropEffects DragOver( IResource targetResource, IDataObject data, DragDropEffects allowedEffect, int keyState ) { if( targetResource.Type != _folderResourceType ) { return DragDropEffects.None; } IResourceList dragResources = data.GetData( typeof( IResourceList ) ) as IResourceList; if( dragResources != null ) { string targetPath = targetResource.GetPropText( _propDirectory ); foreach( IResource file in dragResources ) { IResource folder = file.GetLinkProp( _propParentFolder ); if( file.Type != _folderResourceType ) { if( folder == null || folder.Type != _folderResourceType ) { return DragDropEffects.None; } } else { // do not drop folder to itself or a subfolder if( folder == targetResource || targetPath.StartsWith( file.GetPropText( _propDirectory ) ) ) { return DragDropEffects.None; } } } return ( ( keyState & 8 ) != 0 ) ? DragDropEffects.Copy : DragDropEffects.Link; } return DragDropEffects.None; } public void AddResourceDragData( IResourceList dragResources, IDataObject dataObject ) {} #endregion #region implementation details private void RegisterTypes() { IResourceStore store = Core.ResourceStore; IResource folderRes = store.FindUniqueResource( "ResourceType", "Name", _folderResourceType ); if( folderRes == null ) { store.ResourceTypes.Register( _folderResourceType, "File Folder", "Name", ResourceTypeFlags.NoIndex | ResourceTypeFlags.ResourceContainer | ResourceTypeFlags.Internal ); } else { folderRes.SetProp( "NoIndex", 1 ); folderRes.SetProp( "ResourceContainer", 1 ); folderRes.SetProp( "Internal", 1 ); Core.ResourceStore.ResourceTypes ["FileFolder"].DisplayName = "File Folder"; } /** * file folders statuses: * 0 - monitored (immediate) * 1 - deferred * 2 - excluded */ _propStatus = store.PropTypes.Register( "Status", PropDataType.Int ); _propDirectory = store.PropTypes.Register( "Directory", PropDataType.String ); _propSize = store.PropTypes.Register( "Size", PropDataType.Int ); _propFileType = store.PropTypes.Register( "FileType", PropDataType.String ); _propParentFolder = store.PropTypes.Register( "ParentFolder", PropDataType.Link, PropTypeFlags.DirectedLink | PropTypeFlags.SourceLink, this ); store.PropTypes.RegisterDisplayName( _propParentFolder, "Folder", "File" ); _propNew = store.PropTypes.Register( "New", PropDataType.Bool, PropTypeFlags.Internal ); _propDeleted = store.PropTypes.Register( "Deleted", PropDataType.Bool, PropTypeFlags.Internal ); _propDeletedFile = store.PropTypes.Register( "DeletedFile", PropDataType.Bool, PropTypeFlags.Internal ); Core.WorkspaceManager.RegisterWorkspaceContainerType( _folderResourceType, new[] { _propParentFolder }, _propParentFolder ); Core.WorkspaceManager.RegisterWorkspaceSelectorFilter( _folderResourceType, new FoldersFilter() ); IResourceList rootFolders = store.FindResourcesWithProp( _folderResourceType, _propStatus ); if( rootFolders.Count == 0 ) { store.GetAllResources( _folderResourceType ).DeleteAll(); } else { foreach( IResource folder in rootFolders ) { folder.SetProp( _propDirectory, folder.GetPropText( Core.Props.Name ) ); } } /** * for files, deleting permanent UnknownFile resources if they aren't linked * with no other resources except file folders */ IResourceList unknownFiles = store.FindResourcesWithProp( _unknownFileResourceType, _propDirectory ); foreach( IResource unknownFile in unknownFiles ) { int[] links = unknownFile.GetLinkTypeIds(); if( links.Length == 1 && links[ 0 ] == _propDirectory ) { unknownFile.Delete(); } } if( store.ResourceTypes.Exist( "File" ) ) { store.ResourceTypes.Delete( "File" ); } /** * delete tree root garbage */ IResourceList roots = store.FindResources( "ResourceTreeRoot", "RootResourceType", _folderResourceType ); foreach( IResource root in roots ) { if( root.GetLinksTo( _folderResourceType, _propParentFolder ).Count == 0 ) { FoldersCollection.Instance.DeleteResource( root ); } } foreach( IResource res in FoldersCollection.Instance.FilesRoot.GetLinksTo( null, _propParentFolder ) ) { if( res.Type != _folderResourceType || !res.HasProp( _propStatus ) ) { FoldersCollection.Instance.DeleteResource( res ); } } store.RegisterUniqueRestriction( _folderResourceType, _propDirectory ); } /** * Moving directories/files in resource thread */ private delegate void MoveDirectoryDelegate( string dest, IResource folder, IResource targetResource ); private static void MoveDirectory( string dest, IResource folder, IResource targetResource ) { string source = folder.GetPropText( _propDirectory ); Directory.Move( source, dest ); folder.BeginUpdate(); try { folder.SetProp( _propDirectory, dest ); folder.SetProp( _propParentFolder, targetResource ); } finally { folder.EndUpdate(); } foreach( IResource file in folder.GetLinksTo( null, _propParentFolder ).ValidResources ) { if( file.Type != _folderResourceType ) { file.SetProp( _propDirectory, dest ); } else { string name = file.GetPropText( _propDirectory ).Replace( source, null ).Trim( '/', '\\' ); file.SetProp( _propDirectory, IOTools.Combine( dest, name ) ); } } } private delegate void MoveFileDelegate( string source, string dest, string directory, IResource file, IResource targetResource ); private static void MoveFile( string source, string dest, string directory, IResource file, IResource targetResource ) { File.Move( source, dest ); if( !file.IsTransient ) { file.BeginUpdate(); try { file.SetProp( _propDirectory, directory ); file.SetProp( _propParentFolder, targetResource ); } finally { file.EndUpdate(); } } } /** * Actual folder contents displaying code */ private delegate void ResourceNodeSelectedDelegate( IResource res, bool displayAnotherFolder ); private static void ResourceNodeSelected( IResource res, bool displayAnotherFolder ) { if( Core.State != CoreState.ShuttingDown && res.Type == _folderResourceType ) { IResourceBrowser rBrowser = Core.ResourceBrowser; // for deleted folder display empty resource list if( res.IsDeleted ) { rBrowser.DisplayResourceList( null, Core.ResourceStore.EmptyResourceList, res.DisplayName, null ); return; } if( !displayAnotherFolder && res != rBrowser.OwnerResource ) { return; } IResourceList selected = null; if( res == rBrowser.OwnerResource ) { selected = rBrowser.SelectedResources; } FoldersCollection foldersCollection = FoldersCollection.Instance; string path = res.GetPropText( _propDirectory ); IResourceList folderContents = res.GetLinksToLive( null, _propParentFolder ); /** * look through folders, create new ones and delete obsolete */ DirectoryInfo[] dirs = IOTools.GetDirectories( path ); if( dirs != null ) { foreach( DirectoryInfo dir in dirs ) { foldersCollection.FindOrCreateDirectory( dir.FullName ); } HashSet fileNames = new HashSet(); bool viewHidden = Core.SettingStore.ReadBool( "FilePlugin", "ViewHidden", false ); foreach( IResource file in folderContents.ValidResources ) { if( file.Type == _folderResourceType ) { if( !Directory.Exists( file.GetPropText( _propDirectory ) ) ) { foldersCollection.DeleteResource( file ); } } else { string fullname = foldersCollection.GetFullName( file ); if( !File.Exists( fullname ) ) { foldersCollection.DeleteResource( file ); } else { FileInfo fi = IOTools.GetFileInfo( fullname ); if( viewHidden || ( fi.Attributes & FileAttributes.Hidden ) == 0 ) { fileNames.Add( file.GetPropText( Core.Props.Name ) ); } } } } /** * collect not indexed files */ FileInfo[] files = IOTools.GetFiles( path ); if( files != null ) { foreach( FileInfo fi in files ) { if( ( viewHidden || ( fi.Attributes & FileAttributes.Hidden ) == 0 ) && !fileNames.Contains( fi.Name ) ) { IResource file = foldersCollection.FindOrCreateFile( fi, true ); if( file != null ) { folderContents = folderContents.Union( file.ToResourceList(), true ); } } } } } /** * for folders excluded from indexing display deleted resources as well */ if( FoldersCollection.Instance.IsPathDeferred( path ) || FoldersCollection.Instance.IsPathMonitored( path ) ) { folderContents = folderContents.Minus( Core.ResourceStore.FindResourcesWithPropLive( null, _propDeletedFile ) ); } ColumnDescriptor[] fileColumns = new ColumnDescriptor[ 4 ]; fileColumns[ 0 ].PropNames = new[] { "DisplayName" }; fileColumns[ 0 ].Width = 300; fileColumns[ 0 ].CustomComparer = new FoldersUpComparer(); fileColumns[ 1 ].PropNames = new[] { "FileType" }; fileColumns[ 1 ].Width = 120; fileColumns[ 2 ].PropNames = new[] { "Size" }; fileColumns[ 2 ].Width = 120; fileColumns[ 3 ].PropNames = new[] { "Date" }; fileColumns[ 3 ].Width = 120; rBrowser.DisplayUnfilteredResourceList( res, folderContents, path, Core.DisplayColumnManager.AddAnyTypeColumns( fileColumns ) ); if( selected != null ) { foreach( IResource file in selected.ValidResources ) { rBrowser.SelectResource( file ); } } } } /** * folders filter for tree pane */ private class FoldersFilter : IResourceNodeFilter { public bool AcceptNode( IResource res, int level ) { if ( res.Type == _folderResourceType && res.HasProp( _propNew ) ) return false; if ( res.HasProp( _propParentFolder ) && res.Type != _folderResourceType ) return false; return true; } } /** * custom comparer for file browser's DisplayName column */ private class FoldersUpComparer : IResourceComparer { public int CompareResources( IResource r1, IResource r2 ) { if( r1.Type == _folderResourceType ) { if( r2.Type == _folderResourceType ) { return r1.DisplayName.CompareTo( r2.DisplayName ); } return -1; } if( r2.Type == _folderResourceType ) { return 1; } return r1.DisplayName.CompareTo( r2.DisplayName ); } } /** * tracking adding links to file resources * if link added to a transient file resource, it becomes permanent * ParentFolder links are obviously ignored */ private static void ResourceStore_LinkAdded( object sender, LinkEventArgs e ) { IResource res = e.Source; if( e.PropType != _propParentFolder && res.IsTransient) { IResource source = FileResourceManager.GetSource( res ); if( source != null && source.Type == _folderResourceType ) { res.EndUpdate(); Core.TextIndexManager.QueryIndexing( res.Id ); } } } /// /// Handler on resource browser's content changed. /// private void ResourceBrowser_ContentChanged( object sender, EventArgs e ) { IResource owner = Core.ResourceBrowser.OwnerResource; if( owner != null && owner.Type == _folderResourceType ) { Core.ResourceAP.QueueJob(JobPriority.Immediate, "Set Folder Watcher", new ResourceDelegate( SetCurrentFolderWatcher ), owner ); } else { Core.ResourceAP.QueueJob(JobPriority.Immediate, "Remove Folder Watcher", new MethodInvoker( DisposeCurrentFolderWatcher ) ); } } private void DisposeCurrentFolderWatcher() { if( _selectedFolderWatcher != null ) { _selectedFolderWatcher.Changed -= _folderChanged; _selectedFolderWatcher.Created -= _folderChanged; _selectedFolderWatcher.Deleted -= _folderChanged; _selectedFolderWatcher.Renamed -= _fileRenamed; _selectedFolderWatcher.Dispose(); _selectedFolderWatcher = null; } } private void SetCurrentFolderWatcher( IResource owner ) { DisposeCurrentFolderWatcher(); string path = owner.GetPropText( _propDirectory ); if( !FoldersCollection.Instance.IsPathMonitored( path ) ) { try { FileSystemWatcher watcher = new FileSystemWatcher(); watcher.Path = path; watcher.IncludeSubdirectories = false; watcher.NotifyFilter = NotifyFilters.LastWrite | NotifyFilters.FileName | NotifyFilters.DirectoryName; watcher.Changed += _folderChanged; watcher.Created += _folderChanged; watcher.Deleted += _folderChanged; watcher.Renamed += _fileRenamed; watcher.EnableRaisingEvents = true; _selectedFolderWatcher = watcher; } catch { _selectedFolderWatcher = null; } } } /** * handler of changes in selected folder of file browser */ private void SelectedFolderChanged( object source, FileSystemEventArgs e ) { Core.ResourceAP.QueueJob(JobPriority.Immediate, "Selected Folder Changed", new MethodInvoker( SelectedFolderChangedImpl ) ); } private void SelectedFolderRenamed( object sender, RenamedEventArgs e ) { Core.ResourceAP.QueueJob( JobPriority.Immediate, "Selected Folder Changed", new MethodInvoker( SelectedFolderChangedImpl ) ); } private void SelectedFolderChangedImpl() { IResource folder = _pane.SelectedNode; if( folder == null ) { DisposeCurrentFolderWatcher(); } else { UpdateFoldersTreePane( folder ); } } internal static void UpdateFoldersTreePane( IResource updatedFolder ) { if( updatedFolder != null && updatedFolder == Core.ResourceBrowser.OwnerResource ) { Core.UIManager.QueueUIJob( new ResourceNodeSelectedDelegate( ResourceNodeSelected ), updatedFolder, false ); } } private static string GetExceptionMessage( Exception e ) { return Utils.GetMostInnerException( e ).Message; } private class FileFoldersSelectorPane : ResourceTreeSelectPane, IResourceNodeFilter { public FileFoldersSelectorPane() { _resourceTree.ParentProperty = _propParentFolder; _resourceTree.AddNodeFilter( this ); } public override IResource GetSelectorRoot( string resType ) { return FoldersCollection.Instance.FilesRoot; } bool IResourceNodeFilter.AcceptNode( IResource res, int level ) { return res.Type == _folderResourceType; } } private class FileFolderLinksFilter : ILinksPaneFilter { public bool AcceptLinkType( IResource displayedResource, int propId, ref string displayName ) { if ( propId == _propParentFolder ) { displayName = "Parent Folder"; } else if ( propId == -_propParentFolder ) { displayName = "Contains"; } return true; } public bool AcceptLink( IResource displayedResource, int propId, IResource targetResource, ref string linkTooltip ) { return true; } public bool AcceptAction( IResource displayedResource, IAction action ) { return true; } } internal const string _folderResourceType = "FileFolder"; internal const string _unknownFileResourceType = "UnknownFile"; internal static int _propStatus; internal static int _propDirectory; internal static int _propSize; internal static int _propFileType; internal static int _propParentFolder; internal static int _propNew; internal static int _propDeleted; internal static int _propDeletedFile; internal static IResourceTreePane _pane; private FileSystemWatcher _selectedFolderWatcher; private FileSystemEventHandler _folderChanged; private RenamedEventHandler _fileRenamed; private Icon _folderIcon, _openFolderIcon, _excludedFolderIcon; internal static FileAsyncProcessor _filesProcessor; #endregion } }