// // (C) Copyright 2003-2019 by Autodesk, Inc. // // Permission to use, copy, modify, and distribute this software in // object code form for any purpose and without fee is hereby granted, // provided that the above copyright notice appears in all copies and // that both that copyright notice and the limited warranty and // restricted rights notice below appear in all supporting // documentation. // // AUTODESK PROVIDES THIS PROGRAM "AS IS" AND WITH ALL FAULTS. // AUTODESK SPECIFICALLY DISCLAIMS ANY IMPLIED WARRANTY OF // MERCHANTABILITY OR FITNESS FOR A PARTICULAR USE. AUTODESK, INC. // DOES NOT WARRANT THAT THE OPERATION OF THE PROGRAM WILL BE // UNINTERRUPTED OR ERROR FREE. // // Use, duplication, or disclosure by the U.S. Government is subject to // restrictions set forth in FAR 52.227-19 (Commercial Computer // Software - Restricted Rights) and DFAR 252.227-7013(c)(1)(ii) // (Rights in Technical Data and Computer Software), as applicable. // using System; using System.Collections.Generic; using System.Globalization; using System.IO; using System.Linq; using System.Text; using Autodesk.Revit; using Autodesk.Revit.DB; using Autodesk.Revit.DB.ExternalService; namespace Revit.SDK.Samples.ExternalResourceDBServer.CS { /// /// Derive from the IExternalResourceServer interface to create a server class /// for providing external resources (files or data) to Revit. This example server /// provides both keynote data and Revit links. /// The external resource framework has been designed so that the end user /// loads external resources by browsing with an "open file" dialog. When the user /// chooses an external resource server, the file dialog will display the contents /// of the server's "root folder." From there, the user can navigate through a system /// of folders and files specified by the IExternalResourceServer implementation. The /// files and folders might be the actual contents of a directory tree only accessible /// by the server, or might be some other logical structure relevant to the resource /// that is being loaded. /// To demonstrate the flexibility of the framework, this example server retrieves /// keynote data from a fictitious database for users in France and Germany, and from /// files for all other users. Revit link models are also obtained from files. To keep /// the demonstration simple, the "root" folder for file-based resources is assumed to be /// \SampleResourceServerRoot, located immediately under the directory where the DLL for /// this project is placed. See the separate *.rtf file for additional documentation. /// public class SampleExternalResourceDBServer : IExternalResourceServer { /// /// Default constructor /// public SampleExternalResourceDBServer() { } // Methods that must be implemented by a server for any of Revit's external services #region IExternalServer Implementation /// Indicate which of Revit's external services this server supports. /// Servers derived from IExternalResourceServer *must* return /// ExternalServices.BuiltInExternalServices.ExternalResourceService. public ExternalServiceId GetServiceId() { return ExternalServices.BuiltInExternalServices.ExternalResourceService; } /// Uniquely identifies this server to Revit's ExternalService registry public System.Guid GetServerId() { return new Guid("5F3CAA13-F073-4F93-BDC2-B7F4B806CDAF"); } /// /// Implement this method to return the name of the server. /// public System.String GetName() { return "SDK Sample ExtRes Server"; } /// /// # Implement this method to return the id of the vendor of the server. /// public System.String GetVendorId() { return "ADSK"; } /// /// Provide a short description of the server for display to the end user. /// public System.String GetDescription() { return "A Revit SDK sample external resource server which provides keynote data and Revit links."; } #endregion IExternalServer Implementation /// Methods implemented specifically by servers for the ExternalResource service #region IExternalResourceServer Implementation public string GetShortName() { return GetName(); } /// /// Returns a URL address of the provider of this Revit add-in. /// public virtual String GetInformationLink() { return "http://www.autodesk.com"; } /// /// Specify an image to be displayed in browser dialogs when the end user is selecting a resource to load into Revit. /// /// A string containing the full path to an icon file containing 48x48, 32x32 and 16x16 pixel images. public string GetIconPath() { return string.Empty; } /// /// IExternalResourceServer classes can support more than one type of external resource. /// This one supports keynotes and Revit links. /// public bool SupportsExternalResourceType(ExternalResourceType resourceType) { return (resourceType == ExternalResourceTypes.BuiltInExternalResourceTypes.KeynoteTable || resourceType == ExternalResourceTypes.BuiltInExternalResourceTypes.RevitLink); } /// /// Return a list of resources and sub folders under a folder. /// The Revit user always loads external resources by browsing with a file navigation /// dialog, much like they would when selecting files on a locally-accessible drive. /// The SetupBrowserData method allows the server implementer to simulate an organized /// system of files and folders to support browsing for external resources. /// Purely for demonstration purposes, this sample server obtains its keynote data from /// a "database," and creates a "fake" directory structure for either German or French users /// to browse keynote data. However, for all other users - and for Revit Links, file browsing /// data is generated using actual files on in a folder location under the directory containing /// this DLL. /// public void SetupBrowserData(ExternalResourceBrowserData browserData) { ExternalResourceMatchOptions matchOptions = browserData.GetMatchOptions(); ExternalResourceType resourceType = matchOptions.ResourceType; CultureInfo currentCulture = CultureInfo.CurrentCulture; String currentCultureName = currentCulture.ToString(); // Revit will call SupportsExternalResourceType() before calling this method, so we // can assume that resourceType will be KeynoteTable or RevitLink. if (resourceType == ExternalResourceTypes.BuiltInExternalResourceTypes.KeynoteTable && (currentCultureName == "de-DE" || currentCultureName == "fr-FR")) { // French and German Keynote Data Are Obtained From a "Database" SetupKeynoteDatabaseBrowserData(browserData, currentCultureName); } else { // Keynote Data in Other Languages, and Revit Links, are Obtained From File SetupFileBasedBrowserData(browserData); } } /// /// Checks whether the given ExternalResourceReference is formatted correctly for this server. /// The format should match one of the formats created in the SetupBrowserData method. /// public bool IsResourceWellFormed(ExternalResourceReference extRef) { if ( extRef.ServerId != GetServerId() ) return false; if ( !extRef.HasValidDisplayPath() ) return false; // Either the ExternalResourceReference has a valid database key (German/French keynote case) ... IDictionary refMap = extRef.GetReferenceInformation(); if (refMap.ContainsKey(RefMapDBKeyEntry)) { return KeynotesDatabase.IsValidDBKey(refMap[RefMapDBKeyEntry]); } else if (refMap.ContainsKey(RefMapLinkPathEntry)) // ... OR it is a Revit link file { return File.Exists(GetFullServerLinkFilePath(extRef)); } // ... OR it is a keynote file (non- French/German cases). return File.Exists(GetFullServerKeynoteFilePath(extRef)); } /// /// Implement this method to compare two ExternalResourceReferences. /// /// The string-string IDictionary of reference information stored in one ExternalResourceReference /// The string-string IDictionary of reference information stored in a second ExternalResourceReference /// public virtual bool AreSameResources(IDictionary referenceInformation_1, IDictionary referenceInformation_2) { bool same = true; if (referenceInformation_1.Count != referenceInformation_2.Count) { same = false; } else { foreach (string key in referenceInformation_1.Keys) { if (!referenceInformation_2.ContainsKey(key) || referenceInformation_1[key] != referenceInformation_2[key]) { same = false; break; } } } return same; } /// /// Servers can override the name for UI purposes, but here we just return the names that we /// used when we first created the Resources in SetupBrowserData(). /// public String GetInSessionPath(ExternalResourceReference err, String savedPath) { return savedPath; } /// /// Loads the resources. /// /// A GUID that uniquely identifies this resource load request from Revit. /// The type of external resource that Revit is asking the server to load. /// An ExternalResourceReference identifying which particular resource to load. /// Context information, including the name of Revit document that is calling the server, /// the resource that is currently loaded and the type of load operation (automatic or user-driven). /// An ExternalResourceLoadContent object that will be populated with load data by the /// server. There are different subclasses of ExternalResourceLoadContent for different ExternalResourceTypes. public void LoadResource(Guid loadRequestId, ExternalResourceType resourceType, ExternalResourceReference resourceReference, ExternalResourceLoadContext loadContext, ExternalResourceLoadContent loadContent) { loadContent.LoadStatus = ExternalResourceLoadStatus.Failure; // The following checks can help with testing. However, they should not be necessary, since Revit checks all input paramters // before calling this method. if (loadRequestId == Guid.Empty) throw new ArgumentNullException("loadRequestId"); if (resourceType == null) throw new ArgumentNullException("resourceType"); if (resourceReference == null) throw new ArgumentNullException("resourceReference"); if (loadContext == null) throw new ArgumentNullException("loadContext"); if (loadContent == null) throw new ArgumentNullException("loadContent"); if (!SupportsExternalResourceType(resourceType)) throw new ArgumentOutOfRangeException("resourceType", "The specified resource type is not supported by this server."); // The server indicates what version of the resource is being loaded. loadContent.Version = GetCurrentlyAvailableResourceVersion(resourceReference); // resourceReference is for Keynote Data if (resourceType == ExternalResourceTypes.BuiltInExternalResourceTypes.KeynoteTable) { LoadKeynoteDataResource(resourceReference, loadContent); } else // resourceReference is a Revit Link { LoadRevitLink(resourceReference, loadContent); } } /// /// Indicates whether the given version of a resource is the most current /// version of the data. /// /// The ExternalResourceReference to check. /// An enum indicating whether the resource is current, out of date, or of unknown status public ResourceVersionStatus GetResourceVersionStatus(ExternalResourceReference extRef) { // Determine whether currently loaded version is out of date, and return appropriate status. String currentlyLoadedVersion = extRef.Version; if (currentlyLoadedVersion == String.Empty) return ResourceVersionStatus.Unknown; return currentlyLoadedVersion == GetCurrentlyAvailableResourceVersion(extRef) ? ResourceVersionStatus.Current : ResourceVersionStatus.OutOfDate; } /// /// Implement this to extend the base IExternalResourceServer interface with additional methods /// that are specific to particular types of external resource (for example, Revit Links). /// NOTE: There are no extension methods required for keynote resources. /// /// An ExternalResourceServerExtensions object that can be populated with /// sub-interface objects which can perform operations related to specific types of External Resource. public virtual void GetTypeSpecificServerOperations(ExternalResourceServerExtensions extensions) { RevitLinkOperations revitLinkOps = extensions.GetRevitLinkOperations(); //revitLinkOps.SetGetLocalPathForOpenCallback(new GetLinkPathForOpen()); // jeremy //revitLinkOps.SetOnLocalLinkSharedCoordinatesSavedCallback(new LocalLinkSharedCoordinatesSaved()); // jeremy } #endregion IExternalResourceServer Implementation #region SampleExternalResourceDBServer Implementation /// /// Returns the path of the server's root folder. The contents of this folder will be displayed /// when the user first selects this server while browsing to load keynote data or a Revit link /// (unless the user loading keynote data is in France or Germany). /// For this example server, the root folder is simply a directory immediately under the folder /// where the DLL for this assembly is located. /// private static String RootFolder { get { string assemblyFolder = Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().Location); string rootFolderName = assemblyFolder + "\\SampleResourceServerRoot"; DirectoryInfo rootFolder = new DirectoryInfo(rootFolderName); if (!rootFolder.Exists) rootFolder.Create(); return rootFolderName; } } /// /// Returns the string used to access the French and German keynotes database key that is /// stored in an ExternalResourceReference's reference information string-string Dictionary. /// See the SetupKeynoteDatabaseBrowserData method. /// private static String RefMapDBKeyEntry { get { return "DBKey"; } } /// /// Returns the string used to access the server-based relative path to the Revit link file /// that is stored in an ExternalResourceReference's reference information string-string Dictionary. /// private static String RefMapLinkPathEntry { get { return "Path"; } } /// /// Returns a string specifying the version of an external resource available from the server. /// /// The ExternalResourceReference of the resource whose version is requested. /// A string containing the version of the specified resource. private String GetCurrentlyAvailableResourceVersion(ExternalResourceReference extResRef) { IDictionary refMap = extResRef.GetReferenceInformation(); if (refMap.ContainsKey(RefMapDBKeyEntry)) { return KeynotesDatabase.CurrentVersion; } else if (refMap.ContainsKey(RefMapLinkPathEntry)) // ... OR it is a Revit link file { String serverLinkPath = GetFullServerLinkFilePath(extResRef); return GetFileVersion(serverLinkPath); } // ... OR it is a keynote file (non- French/German cases). String serverKeynoteFilePath = GetFullServerKeynoteFilePath(extResRef); return GetFileVersion(serverKeynoteFilePath); } /// /// Provides Revit's file browser dialog with information for navigating the /// fictitious database containing French and German keynotes data. /// private void SetupKeynoteDatabaseBrowserData(ExternalResourceBrowserData browserData, String currentCultureName) { string folderPath = browserData.FolderPath; // To make this demonstration simple and clear, this code creates two sub-folders with names hard-coded // in French and German, and one keynote data source (must have the extension *.txt) under each of // these sub-folders. // To make subsequent recognition of these resources easier (in the server's LoadResource method and // elsewhere), a database "key" is stored in each resource's string-string Dictionary. if (currentCultureName == "de-DE") { if (folderPath == "/") // Top-level { browserData.AddSubFolder("Unterordner1"); browserData.AddSubFolder("Unterordner2"); } else if (folderPath.EndsWith("/Unterordner1")) { var refMap = new Dictionary(); refMap[RefMapDBKeyEntry] = "1"; browserData.AddResource("Keynotes1_de-DE.txt", KeynotesDatabase.CurrentVersion, refMap); } else if (folderPath.EndsWith("/Unterordner2")) { var refMap = new Dictionary(); refMap[RefMapDBKeyEntry] = "2"; browserData.AddResource("Keynotes2_de-DE.txt", KeynotesDatabase.CurrentVersion, refMap); } } else if (currentCultureName == "fr-FR") { if (folderPath == "/") // Top-level { browserData.AddSubFolder("Sous-dossier1"); browserData.AddSubFolder("Sous-dossier2"); } else if (folderPath.EndsWith("/Sous-dossier1")) { var refMap = new Dictionary(); refMap[RefMapDBKeyEntry] = "3"; browserData.AddResource("Keynotes1_fr-FR.txt", KeynotesDatabase.CurrentVersion, refMap); } else if (folderPath.EndsWith("/Sous-dossier2")) { var refMap = new Dictionary(); refMap[RefMapDBKeyEntry] = "4"; browserData.AddResource("Keynotes2_fr-FR.txt", KeynotesDatabase.CurrentVersion, refMap); } } } /// /// A convenience utility method. For resources obtained from files on the server, we consider /// the resource version to be the last-modified date of the file. /// /// The full path of a file on disk. /// The version (last-modified data) of the specified file. private String GetFileVersion(String filePath) { FileInfo fileInfo = new FileInfo(filePath); DateTime lastModifiedTime = fileInfo.LastWriteTimeUtc; CultureInfo enUs = new CultureInfo("en-us"); return lastModifiedTime.ToString(enUs); } /// /// Computes the full path of a ExternalResourceReference's keynote data file location on the server. /// /// An ExternalResourceReference for a keynote data file stored on this server. /// A string representing the full path to the Revit link resource on the server drive. private String GetFullServerKeynoteFilePath(ExternalResourceReference extRef) { String inSessionPath = extRef.InSessionPath; String serverName = GetName(); // As an alternative to using the inSessionPath to determine the actual file path, the // preferred method is to store the relative server path of the file in the // ExternalResourceReference's referenceInformation (as is done for links). // This would be particularly true if you were overriding the default in-session path in // the GetInSessionPath() method. String serverKeynoteFilePath = inSessionPath.Replace(serverName + "://", RootFolder + "\\"); return serverKeynoteFilePath; } /// /// Provides Revit's file browser dialog with information for navigating a /// directory stucture of files available from the server. /// In this example implementation, the server simply echoes the directories /// and files that it locates under its RootFolder (subject to the appropriate file /// type filter pattern). /// private void SetupFileBasedBrowserData(ExternalResourceBrowserData browserData) { ExternalResourceMatchOptions matchOptions = browserData.GetMatchOptions(); // filter some resources out by specified filter pattern ExternalResourceType resourceType = matchOptions.ResourceType; string filterPattern = resourceType == ExternalResourceTypes.BuiltInExternalResourceTypes.KeynoteTable ? "*.txt" : "*.rvt"; // Expose a "real" directory structure for the user to browse. // The server's root folder is specified in the RootFolder property. string folderPath = browserData.FolderPath; string path = RootFolder + folderPath.Replace('/', '\\'); if (Directory.Exists(path)) { DirectoryInfo dir = new DirectoryInfo(path); DirectoryInfo[] subDirs = dir.GetDirectories(); foreach (DirectoryInfo subDir in subDirs) { browserData.AddSubFolder(subDir.Name); } FileInfo[] subFiles = dir.GetFiles(filterPattern, SearchOption.TopDirectoryOnly); foreach (FileInfo file in subFiles) { if (resourceType == ExternalResourceTypes.BuiltInExternalResourceTypes.KeynoteTable) browserData.AddResource(file.Name, GetFileVersion(file.FullName)); else { var refMap = new Dictionary(); // Relative Path of Link File is Stored in the ExternalResourceReference that // Will Be Addded to the BrowserData. refMap[RefMapLinkPathEntry] = folderPath.TrimEnd('/') + '/' + file.Name; browserData.AddResource(file.Name, GetFileVersion(file.FullName), refMap); } } } else { throw new ArgumentException("Path is invalid"); } } /// /// Loads the keynote data resources, either from the fictitious French/German database, or from a file. /// /// An ExternalResourceReference identifying which particular resource to load. /// An ExternalResourceLoadContent object that will be populated with load data by the /// server. There are different subclasses of ExternalResourceLoadContent for different ExternalResourceTypes. private void LoadKeynoteDataResource(ExternalResourceReference resourceReference, ExternalResourceLoadContent loadContent) { KeyBasedTreeEntriesLoadContent kdrlc = (KeyBasedTreeEntriesLoadContent)loadContent; if (kdrlc == null) throw new ArgumentException("Wrong type of ExternalResourceLoadContent (expecting a KeyBasedTreeEntriesLoadContent) for keynote data.", "loadContent"); kdrlc.Reset(); // Either the ExternalResourceReference has a valid database key (German/French case) ... IDictionary refMap = resourceReference.GetReferenceInformation(); if (refMap.ContainsKey(RefMapDBKeyEntry)) { try { KeynotesDatabase.LoadKeynoteEntries(refMap[RefMapDBKeyEntry], ref kdrlc); kdrlc.BuildEntries(); loadContent.LoadStatus = ExternalResourceLoadStatus.Success; } catch (ArgumentOutOfRangeException) { } catch (ArgumentNullException) { } } else { // ... OR it is a real file (all other cases). String serverKeynoteFilePath = GetFullServerKeynoteFilePath(resourceReference); bool doesReadingSucceed = KeynoteEntries.LoadKeynoteEntriesFromFile(serverKeynoteFilePath, kdrlc); if (doesReadingSucceed) { kdrlc.BuildEntries(); loadContent.LoadStatus = ExternalResourceLoadStatus.Success; } } } /// /// Loads a specified Revit link external resource. /// /// An ExternalResourceReference identifying which particular resource to load. /// An ExternalResourceLoadContent object that will be populated with load data by the /// server. There are different subclasses of ExternalResourceLoadContent for different ExternalResourceTypes. private void LoadRevitLink(ExternalResourceReference resourceReference, ExternalResourceLoadContent loadContent) { LinkLoadContent linkLoadContent = (LinkLoadContent)loadContent; if (linkLoadContent == null) throw new ArgumentException("Wrong type of ExternalResourceLoadContent (expecting a LinkLoadContent) for Revit links.", "loadContent"); try { // Copy the file from the path under the server "root" folder to a secret "cache" folder on the users machine String fullCachedPath = GetFullLinkCachedFilePath(resourceReference); String cacheFolder = System.IO.Path.GetDirectoryName(fullCachedPath); if (!System.IO.Directory.Exists(cacheFolder)) { System.IO.Directory.CreateDirectory(cacheFolder); } String serverLinkPath = GetFullServerLinkFilePath(resourceReference); System.IO.File.Copy(serverLinkPath, fullCachedPath, true); // Overwrite ModelPath linksPath = ModelPathUtils.ConvertUserVisiblePathToModelPath(fullCachedPath); linkLoadContent.SetLinkDataPath(linksPath); loadContent.LoadStatus = ExternalResourceLoadStatus.Success; } catch (System.Exception) { } } /// /// Computes the full path of a Revit link ExternalResourceReference's location on the server. /// /// An ExternalResourceReference for a Revit link stored on this server. /// A string representing the full path to the Revit link resource on the server drive. public static String GetFullServerLinkFilePath(ExternalResourceReference resource) { if (resource == null) return ""; IDictionary refMap = resource.GetReferenceInformation(); if (!refMap.ContainsKey(RefMapLinkPathEntry)) return ""; return RootFolder + resource.GetReferenceInformation()[RefMapLinkPathEntry].Replace("/", "\\"); } /// /// Returns the path of the root folder of the cache where the server will make /// local copies of Revit links. For this example server, the cache root folder is /// simply a directory immediately under the folder where the DLL for this assembly is /// located. /// private static String LocalLinkCacheFolder { get { string assemblyFolder = Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().Location); string cacheRootFolderName = assemblyFolder + "\\SampleResourceServerLinkCache"; DirectoryInfo cacheRootFolder = new DirectoryInfo(cacheRootFolderName); if (!cacheRootFolder.Exists) cacheRootFolder.Create(); return cacheRootFolderName; } } /// /// Computes the full path on the end user's local drive where an /// ExternalResourceReference's link model will be copied when the user loads the /// link from the server. ///The paths of the local copies relative to this cache root folder will be the /// same as the paths of the original server copies relative to the Revit links /// to the root folder on the (compare this method with the GetFullServerLinkFilePath method). /// /// An ExternalResourceReference for a Revit link stored on this server. /// A string representing the full path to the link model on the end user's local drive. public static String GetFullLinkCachedFilePath(ExternalResourceReference resource) { if (resource == null) return ""; IDictionary refMap = resource.GetReferenceInformation(); if (!refMap.ContainsKey(RefMapLinkPathEntry)) return ""; return LocalLinkCacheFolder + resource.GetReferenceInformation()[RefMapLinkPathEntry].Replace("/", "\\"); } #endregion SampleExternalResourceDBServer Implementation #region SampleExternalResourceDBServer Member Variables #endregion SampleExternalResourceDBServer Member Variables } }