/// /// 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.Collections.Generic; using System.Diagnostics; using JetBrains.DataStructures; using JetBrains.Omea.Base; using JetBrains.Omea.OpenAPI; namespace JetBrains.Omea.Favorites { /// /// Fake bookmarks profile which restricts everything and is registered for parent folders /// of folders of profiles, e.x., the "Mozilla" folder for the path "Mozilla->Firefox - default" /// internal class FakeRestrictProfile: IBookmarkProfile { #region IBookmarkProfile Members public bool CanRename( IResource res, out string error ) { error = _error; return false; } public char[] InvalidNameChars { get { return new char[] {}; } } public bool CanMove( IResource res, IResource parent, out string error ) { error = _error; return false; } public bool CanCreate( IResource res, out string error ) { error = _error; return false; } public bool CanDelete( IResource res, out string error ) { error = _error; return false; } public void Create( IResource res ) { } public string Name { get { if( _name == null ) { _name = _random.NextDouble().ToString(); } return _name; } } public void Rename( IResource res, string newName ) { } public void Delete( IResource res ) { } public void StartImport() { } public void Move( IResource res, IResource parent, IResource oldParent ) { } public void Dispose() { } #endregion private string _name; private const string _error = "No changes can be made here."; private static readonly Random _random = new Random(); } /// /// Implemetation of IBookmarkService /// internal class BookmarkService: IBookmarkService { public BookmarkService() { _theService = this; _rootFolder = Core.ResourceTreeManager.GetRootForType( "Weblink" ); Core.ResourceTreeManager.SetResourceNodeSort( _rootFolder, "Type- Name" ); _rootFolder.DisplayName = "Bookmarks"; _profileNames2RootResources = new HashMap(); _rootResources2Profiles = new HashMap(); _propName = Core.Props.Name; } #region IBookmarkService implementation public void RegisterProfile( IBookmarkProfile profile ) { Guard.NullArgument( profile, "profile" ); string name = NormalizeProfileName( profile.Name ); if( _profileNames2RootResources.Contains( name ) ) { throw new InvalidOperationException( "Bookmark profile '" + profile.Name + "' is already registered" ); } string[] nameParts = profile.Name.Split( '\\', '/' ); IResource parent = _rootFolder; IResource interimParent = null; foreach( string namePart in nameParts ) { if( namePart.Length == 0 ) { throw new Exception( "Invalid profile name: " + profile.Name ); } parent = FindOrCreateFolder( parent, namePart ); /** * Register fake all-restricting profile for interim folders which contain * profiles' folders, e.x., for Mozilla->profile1, Mozilla->profile2... etc... */ if( interimParent != null && GetOwnerProfile( interimParent ) == null ) { FakeRestrictProfile fakeProfile = new FakeRestrictProfile(); _profileNames2RootResources[ NormalizeProfileName( fakeProfile.Name ) ] = interimParent; _rootResources2Profiles[ interimParent ] = fakeProfile; } interimParent = parent; } if( parent == _rootFolder ) { throw new Exception( "Invalid profile name: " + profile.Name ); } _profileNames2RootResources[ name ] = parent; _rootResources2Profiles[ parent ] = profile; if( _rootResources2Profiles.Count != _profileNames2RootResources.Count ) { throw new Exception( "Root resources and profiles count mismatch. Last added profile name: " + profile.Name ); } Trace.WriteLine( "Registered bookmark profile: " + profile.Name, "Favorites.Plugin" ); } public void DeRegisterProfile( IBookmarkProfile profile ) { Guard.NullArgument( profile, "profile" ); using( profile ) { string name = NormalizeProfileName( profile.Name ); HashMap.Entry e = _profileNames2RootResources.GetEntry( name ); if( e == null ) { throw new InvalidOperationException( "Bookmark profile '" + profile.Name + "' was not registered" ); } IResource profileRoot = (IResource) e.Value; _profileNames2RootResources.Remove( name ); _rootResources2Profiles.Remove( profileRoot ); if( _rootResources2Profiles.Count != _profileNames2RootResources.Count ) { throw new Exception( "Root resources and profiles count mismatch. Last removed profile name: " + profile.Name ); } if( profileRoot.HasProp( FavoritesPlugin._propInvisible ) ) { DeleteFolder( profileRoot ); } } } public IBookmarkProfile[] Profiles { get { IBookmarkProfile[] profiles = new IBookmarkProfile[ _rootResources2Profiles.Count ]; int i = 0; foreach( HashMap.Entry e in _rootResources2Profiles ) { profiles[ i++ ] = (IBookmarkProfile) e.Value; } return profiles; } } public IBookmarkProfile GetOwnerProfile( IResource res ) { Guard.NullArgument( res, "res" ); IBookmarkProfile result = null; while( res != null && ( result = (IBookmarkProfile) _rootResources2Profiles[ res ] ) == null ) { res = GetParent( res ); } return result; } public IResource BookmarksRoot { get { return _rootFolder; } } public IResource GetProfileRoot( string profileName ) { return (IResource) _profileNames2RootResources[ NormalizeProfileName( profileName ) ]; } public IResource GetProfileRoot( IBookmarkProfile profile ) { return GetProfileRoot( profile.Name ); } public IResourceList GetBookmarks() { return GetBookmarks( _rootFolder ); } public IResourceList GetBookmarks( IBookmarkProfile profile ) { return GetBookmarks( GetProfileRoot( profile ) ); } public IResource FindOrCreateBookmark( IResource parent, string name, string url ) { return FindOrCreateBookmark( parent, name, url, false ); } public void SetName( IResource res, string name ) { SetNameImpl( res, name ); } public void SetUrl( IResource res, string url ) { SetUrlImpl( res, url ); } public void SetParent( IResource res, IResource parent ) { if( !Core.ResourceStore.IsOwnerThread() ) { Core.ResourceAP.RunUniqueJob( new ResourceResourceDelegate( SetParent ), res, parent ); } else { res.SetProp( FavoritesPlugin._propParent, parent ); } } public IResource FindOrCreateFolder( IResource parent, string name ) { if( !Core.ResourceStore.IsOwnerThread() ) { return (IResource) Core.ResourceAP.RunUniqueJob( new ResourceStringDelegate2( FindOrCreateFolder ), parent, name ); } IResource result; IResourceList candidates = SubNodesWithName( "Folder", parent, name ); if( candidates.Count > 0 ) { result = candidates[ 0 ]; result.BeginUpdate(); } else { result = Core.ResourceStore.BeginNewResource( "Folder" ); Core.WorkspaceManager.AddToActiveWorkspace( result ); } try { result.SetProp( _propName, name ); result.DeleteProp( "_DisplayName" ); result.SetProp( FavoritesPlugin._propParent, parent ); } finally { result.EndUpdate(); } return result; } public void DeleteBookmark( IResource res ) { if( res.Type != "Weblink" ) { throw new ArgumentException( "DeleteBookmark() is applicable to the \"Weblink\" resources only" ); } if( !Core.ResourceStore.IsOwnerThread() ) { Core.ResourceAP.QueueJob( new ResourceDelegate( DeleteBookmark ), res ); } else { IResource source = res.GetLinkProp( "Source" ); if( source != null ) { source.Delete(); } res.Delete(); } } public void DeleteBookmarks( IResourceList resources ) { if( !Core.ResourceStore.IsOwnerThread() ) { Core.ResourceAP.QueueJob( new ResourceListDelegate( DeleteBookmarks ), resources ); } else { foreach( IResource res in resources ) { DeleteBookmark( res ); } } } public void DeleteFolder( IResource res ) { if( res.Type != "Folder" ) { throw new ArgumentException( "DeleteFolder() is applicable to the \"Folder\" resources only" ); } if( !Core.ResourceStore.IsOwnerThread() ) { Core.ResourceAP.QueueJob( new ResourceDelegate( DeleteFolder ), res ); } else { IResourceList subnodes = SubNodes( null, res ); foreach( IResource subnode in subnodes ) { if( subnode.Type == "Folder" ) { DeleteFolder( subnode ); } else { DeleteBookmark( subnode ); } } res.Delete(); } } public void DeleteFolders( IResourceList resources ) { if( !Core.ResourceStore.IsOwnerThread() ) { Core.ResourceAP.QueueJob( new ResourceListDelegate( DeleteFolders ), resources ); } else { foreach( IResource res in resources ) { DeleteFolder( res ); } } } #endregion /// /// 0 - download when system is idle /// 1 - download when imported /// 2 - downlaod on preview /// public static int DownloadMethod { get { return Core.SettingStore.ReadInt( "Favorites", "DownloadMethod", 0 ); } set { if( value < 0 || value > 2 ) { throw new ArgumentOutOfRangeException( "value", "Can only be in [0, 2]" ); } Core.SettingStore.WriteInt( "Favorites", "DownloadMethod", value ); } } public void SynchronizeBookmarks() { IResourceList weblinks = GetBookmarks(); List postponedIds = new List(); foreach( IResource weblink in weblinks.ValidResources ) { string URL = weblink.GetPropText( FavoritesPlugin._propURL ); if( URL.Length > 0 ) { if( weblink.HasProp( "Source" ) || weblink.HasProp( Core.Props.LastError ) ) { QueueWeblink( weblink, URL, BookmarkSynchronizationTime( weblink ) ); } else { postponedIds.Add( weblink.Id ); } } } if( postponedIds.Count > 0 ) { IResourceList postponedWeblinks = Core.ResourceStore.ListFromIds( postponedIds.ToArray(), false ); int downloadMethod = DownloadMethod; if( downloadMethod == 0 ) { Core.NetworkAP.QueueIdleJob( new FavoritesUpdateQueue( postponedWeblinks ) ); } else if( downloadMethod == 1 ) { Core.NetworkAP.QueueJob( new FavoritesUpdateQueue( postponedWeblinks ) ); } } } public static IResourceList SubNodes( string type, IResource res ) { return res.GetLinksTo( type, FavoritesPlugin._propParent ); } public static IResourceList SubNodesWithName( string type, IResource res, string name ) { return SubNodes( type, res ).Intersect( Core.ResourceStore.FindResources( null, _propName, name ), true ); } public static IResourceList GetBookmarks( IResource parent ) { IResourceList result = SubNodes( "Weblink", parent ); IResourceList folders = SubNodes( "Folder", parent ); foreach( IResource folder in folders ) { result = result.Union( GetBookmarks( folder ), true ); } return result; } public static bool HasSubNodeWithName( IResource node, string name ) { name = name.ToLower(); IResourceList childs = SubNodes( node.Type, node ); foreach( IResource child in childs ) { if( name == child.GetPropText( Core.Props.Name ).ToLower() ) { return true; } } return false; } public static IResource GetBookmarksRoot() { return _theService.BookmarksRoot; } public static IResource GetParent( IResource res ) { if( res != GetBookmarksRoot() ) { IResourceList parents = res.GetLinksFrom( null, FavoritesPlugin._propParent ); if( parents.Count > 0 ) { return parents[ 0 ]; } } return null; } #region implementation details private delegate IResource FindOrCreateBookmarkDelegate( IResource parent, string name, string url, bool transient ); private delegate void ResourceStringDelegate( IResource res, string str ); private delegate IResource ResourceStringDelegate2( IResource res, string str ); private delegate void ResourceResourceDelegate( IResource res1, IResource res2 ); internal static IResource FindOrCreateBookmark( IResource parent, string name, string url, bool transient ) { url = url.Trim(); if( url.IndexOf( "://" ) < 0 ) { url = "http://" + url; } IResourceStore store = Core.ResourceStore; IResourceList candidates = SubNodesWithName( "Weblink", parent, name ).Intersect( store.FindResources( null, FavoritesPlugin._propURL, url ), true ); if( candidates.Count > 0 ) { return candidates[ 0 ]; } if( !store.IsOwnerThread() ) { return (IResource) Core.ResourceAP.RunUniqueJob( new FindOrCreateBookmarkDelegate( FindOrCreateBookmark ), parent, name, url, transient ); } IResource result = ( transient ) ? store.NewResourceTransient( "Weblink" ) : store.BeginNewResource( "Weblink" ); try { result.SetProp( _propName, name ); result.SetProp( FavoritesPlugin._propURL, url ); result.AddLink( FavoritesPlugin._propParent, parent ); } finally { if( !transient ) { result.EndUpdate(); } } return result; } private static void SetNameImpl( IResource res, string name ) { if( !res.IsDeleted ) { if( !Core.ResourceStore.IsOwnerThread() ) { Core.ResourceAP.RunUniqueJob( new ResourceStringDelegate( SetNameImpl ), res, name ); } else { res.DeleteProp( "_DisplayName" ); res.SetProp( Core.Props.Name, name ); } } } private static IResource SetUrlImpl( IResource res, string url ) { if( !Core.ResourceStore.IsOwnerThread() ) { return (IResource) Core.ResourceAP.RunUniqueJob( new ResourceStringDelegate2( SetUrlImpl ), res, url ); } res.SetProp( FavoritesPlugin._propURL, url ); return res; } internal static string NormalizeProfileName( string profileName ) { return profileName.ToLower().Trim( '\\', '/' ); } internal static int BookmarkSynchronizationFrequency( IResource webLink ) { int freq = 0; while( webLink != null && ( freq = webLink.GetIntProp( FavoritesPlugin._propUpdateFreq ) ) <= 0 ) { webLink = GetParent( webLink ); } return freq; } internal static DateTime BookmarkSynchronizationTime( IResource webLink ) { int freq = BookmarkSynchronizationFrequency( webLink ); if( freq <= 0 ) { return DateTime.MaxValue; } DateTime lastUpdated = webLink.GetDateProp( FavoritesPlugin._propLastUpdated ); if( lastUpdated == DateTime.MinValue ) { lastUpdated = DateTime.Now; } return lastUpdated.AddSeconds( freq ); } internal static void QueueWeblink( IResource webLink, string URL, DateTime when ) { if( when < DateTime.MaxValue ) { FavoritesTools.TraceIfAllowed( "Queueing " + URL + " to be processed at " + when ); Core.NetworkAP.QueueJobAt( when, new FavoriteJob( webLink, URL ) ); } } internal static void ImmediateQueueWeblink( IResource webLink, string URL ) { FavoritesTools.TraceIfAllowed( "Queueing " + URL ); Core.NetworkAP.QueueJob( JobPriority.Immediate, new FavoriteJob( webLink, URL ) ); } #endregion private static BookmarkService _theService; private readonly IResource _rootFolder; private readonly HashMap _profileNames2RootResources; private readonly HashMap _rootResources2Profiles; private static int _propName; } }