/// <summary> /// Initializes a new instance of the Folder class. /// </summary> /// <param name="name">Folder name</param> /// <param name="parent">Parent folder</param> internal Folder(string name, Folder parent) { Name = name; Parent = parent; Children = new Collection<Folder>(); Links = new Collection<Place>(); Tours = new Collection<Tour>(); // Only for community root folder, parent will be null. if (parent == null) { Group = "Explorer"; Searchable = "True"; FolderType = "Earth"; RootFolder = this; } else { parent.Children.Add(this); RootFolder = Parent.RootFolder; } }
/// <summary> /// Loops through all the items and adds them in to the payload xml. And also recursively loops through all the folders and do the same. /// </summary> /// <param name="currentDirectory">Current folder getting processed</param> /// <param name="folder">Current folder object</param> private void ProcessFolderItems(DirectoryInfo currentDirectory, Folder folder) { bool containOnlySingleWTMLFile = true; // Get all the directories excluding hidden, system and reparse point directories. List<DirectoryInfo> directories = currentDirectory.GetDirectories().Where( e => !e.Attributes.HasFlag(FileAttributes.Hidden) & !e.Attributes.HasFlag(FileAttributes.System) & !e.Attributes.HasFlag(FileAttributes.ReparsePoint)).ToList(); foreach (DirectoryInfo childDirectory in directories) { // 1. If any pyramid/plate/DEM plate folders are present, ignore them since they will be used only by WTML. // 2. In the root community folder, if any folder with name "All Tours" or "Latest", ignore them also. if ((childDirectory.Name.Equals(Constants.PyramidFolder, StringComparison.OrdinalIgnoreCase) || childDirectory.Name.Equals(Constants.PlatesFolder, StringComparison.OrdinalIgnoreCase) || childDirectory.Name.Equals(Constants.DEMPlatesFolder, StringComparison.OrdinalIgnoreCase)) || (this.CommunityLocation.Equals(currentDirectory.Parent.FullName, StringComparison.OrdinalIgnoreCase) && (childDirectory.Name.Equals(Constants.AllToursFolder, StringComparison.OrdinalIgnoreCase) || childDirectory.Name.Equals(Constants.LatestFolder, StringComparison.OrdinalIgnoreCase)))) { continue; } // If any folders are there (other than pyramid), don't consider as a folder which contains only single WTML file. containOnlySingleWTMLFile = false; Folder child = new Folder(childDirectory.Name, folder); // Process the folders/files present inside the child folder. This will be recursive. ProcessFolderItems(childDirectory, child); } ProcessFiles(currentDirectory, folder, containOnlySingleWTMLFile); }
/// <summary> /// Process all the files in the current folder and adds them to the appropriate folder object. /// </summary> /// <param name="currentDirectory">Current folder being processed</param> /// <param name="folder">Folder object representing current folder</param> /// <param name="containOnlySingleWTMLFile">Current directory contains only a single WTML file</param> private void ProcessFiles(DirectoryInfo currentDirectory, Folder folder, bool containOnlySingleWTMLFile) { int wtmlFileCount = 0; // Ignore system and hidden files. List<FileInfo> files = currentDirectory.GetFiles().Where(e => !e.Attributes.HasFlag(FileAttributes.Hidden) & !e.Attributes.HasFlag(FileAttributes.System)).ToList(); foreach (FileInfo file in files) { // Ignore thumbnail files, description files and plate files. if ((Path.GetFileNameWithoutExtension(file.Name).Equals("Thumbnail", StringComparison.OrdinalIgnoreCase) && file.IsImageFile()) || file.Extension.Equals(Constants.PlateFileExtension, StringComparison.OrdinalIgnoreCase) || file.Name.Equals(Constants.DescriptionFileName, StringComparison.OrdinalIgnoreCase)) { continue; } // For local WTML files (WTML not served by SharingService), thumbnail file will have the same name as WTML file. Need // to find such image files (images and WTML files with same name) and ignore them also. if (file.IsImageFile()) { string searchPattern = string.Format(CultureInfo.InvariantCulture, Constants.WTMLFile, Path.GetFileNameWithoutExtension(file.Name)); FileInfo[] wtmlFile = currentDirectory.GetFiles(searchPattern); if (wtmlFile.Length > 0) { continue; } } // Get the file Id, i.e. relative path by removing the community folder location. string fileId = file.FullName.Replace(this.CommunityLocation, string.Empty).TrimStartSlashes(); if (file.Extension.Equals(Constants.WTMLExtension, StringComparison.OrdinalIgnoreCase)) { // Processing WTML files (.wtml) here. // Get number of WTML files in current folder. If there is only one WTML file along with its Pyramid, plate and // thumbnail files in a folder, then the folder containing the WTML file will not be shown in WWT. wtmlFileCount++; // For WTML files, replace the URLs if they are having local URLs, leave them as it is if they are http or https. XmlDocument wtmlFileDoc = new XmlDocument(); wtmlFileDoc.Load(file.FullName); ReplaceLocalUrlWithRelative(wtmlFileDoc, currentDirectory.FullName); string innerText = string.Empty; bool latestFile = file.IsLatestFile(this.CommunityServiceLatestFileDays); // In case if there is only one item inside a WTML file, then the root node/document element which // displays a folder for the WTML will be ignored. In case if there are more then one items in a WTML file, // then the folder will be displayed which will be grouping the items in WWT. if (wtmlFileDoc.DocumentElement.ChildNodes.Count == 1) { if (latestFile) { // Adding the ModifiedDate attribute to the FirstChild element which will be added as children of Latest folder. // This is needed for sorting the WTML collection in Latest folder. XmlAttribute modifiedDateAttrib = wtmlFileDoc.CreateAttribute(Constants.ModifiedDateAttribute); modifiedDateAttrib.Value = XmlConvert.ToString(file.GetModifiedDate(), XmlDateTimeSerializationMode.Utc); wtmlFileDoc.DocumentElement.FirstChild.Attributes.Append(modifiedDateAttrib); } innerText = wtmlFileDoc.DocumentElement.InnerXml; } else { if (latestFile) { // Adding the ModifiedDate attribute to the Document element which will be added as children of Latest folder. // This is needed for sorting the WTML collection in Latest folder. XmlAttribute modifiedDateAttrib = wtmlFileDoc.CreateAttribute(Constants.ModifiedDateAttribute); modifiedDateAttrib.Value = XmlConvert.ToString(file.GetModifiedDate(), XmlDateTimeSerializationMode.Utc); wtmlFileDoc.DocumentElement.Attributes.Append(modifiedDateAttrib); } innerText = wtmlFileDoc.DocumentElement.OuterXml; } folder.InnerText += innerText; if (latestFile) { // Add the WTML inner text to the "Latest" folder which is the second children of RootFolder only if the file is modified with // the specified date in the configuration file. folder.RootFolder.Children[1].InnerText += innerText; } if (wtmlFileCount > 1) { containOnlySingleWTMLFile = false; } } else if (file.Extension.Equals(Constants.TourExtension, StringComparison.OrdinalIgnoreCase)) { // Processing Tour files (.wtt) here. // If any files other than WTML files, don't consider as a folder which contains only WTML file. containOnlySingleWTMLFile = false; // For tour files, get the properties needed for constructing the Tour tag from WTT file itself. XmlDocument tourFile = ParseTourFile(file); if (tourFile != null) { // Creating the tour object by extracting the required properties from Tour XML file. Tour tour = new Tour( tourFile.GetAttributeValue("Tour", "Title"), tourFile.GetAttributeValue("Tour", "ID"), fileId, tourFile.GetAttributeValue("Tour", "ThumbnailUrl")); tour.Description = tourFile.GetAttributeValue("Tour", "Descirption"); tour.Author = tourFile.GetAttributeValue("Tour", "Author"); tour.OrganizationUrl = tourFile.GetAttributeValue("Tour", "OrganizationUrl"); tour.OrganizationName = tourFile.GetAttributeValue("Tour", "OrganizationName"); tour.AuthorImageUrl = tourFile.GetAttributeValue("Tour", "AuthorImageUrl"); tour.ModifiedDate = file.GetModifiedDate(); tour.Parent = folder; // Add the tour to the "Latest" folder which is the second children of RootFolder only if the file is modified with // the specified date in the configuration file. if (file.IsLatestFile(this.CommunityServiceLatestFileDays)) { Tour existingTour = folder.RootFolder.Children[1].Tours.FirstOrDefault(e => e.ID == tour.ID); // Make sure the same Tour is not added already which could be there in some other folder. // If the tour already exists, replace the current tour if it is the latest one. if (existingTour == null) { folder.RootFolder.Children[1].Tours.Add(tour); } else if (tour.ModifiedDate > existingTour.ModifiedDate) { folder.RootFolder.Children[1].Tours.Remove(existingTour); folder.RootFolder.Children[1].Tours.Add(tour); } } } } else if (file.Extension.Equals(Constants.TextExtension, StringComparison.OrdinalIgnoreCase) && file.Name.StartsWith("Link", StringComparison.OrdinalIgnoreCase)) { // Processing external link files (Link*.txt) here. // If any files other than WTML files, don't consider as a folder which contains only WTML file. containOnlySingleWTMLFile = false; // For link files (Link*.txt), read the links from the file content. ParseLinkFile(folder, file); } else { // Processing all other local files here. // If any files other than WTML files, don't consider as a folder which contains only WTML file. containOnlySingleWTMLFile = false; // Add links for all other files. Place place = new Place(file.Name, fileId, string.Empty); place.IsLocal = true; place.Parent = folder; place.ModifiedDate = file.GetModifiedDate(); // Add the Place to the "Latest" folder which is the second children of RootFolder only if the file is modified with // the specified date in the configuration file. if (file.IsLatestFile(this.CommunityServiceLatestFileDays)) { Place existingPlace = folder.RootFolder.Children[1].Links.FirstOrDefault(e => (e.Name == place.Name && e.Thumbnail == place.Thumbnail)); // Make sure the same Link is not added already which could be there in some other folder. Links will be // identified same based on their name and thumbnail. // If the Link already exists, replace the current Link if it is the latest one. if (existingPlace == null) { folder.RootFolder.Children[1].Links.Add(place); } else if (place.ModifiedDate > existingPlace.ModifiedDate) { folder.RootFolder.Children[1].Links.Remove(existingPlace); folder.RootFolder.Children[1].Links.Add(place); } } } } if (containOnlySingleWTMLFile && currentDirectory.GetFiles().Length > 0) { // If only WTML files are there in the current directory, remove folder which contains the WTML file and // add the WTML file to its parent. If current folder is community folder (parent will be null), don't do this. if (folder.Parent != null) { folder.Parent.InnerText += folder.InnerText; folder.Parent.Children.Remove(folder); } } }
/// <summary> /// Parses link file and adds Place objects to folder object for the links present inside the text file. /// </summary> /// <param name="folder">Folder to which the link belongs to</param> /// <param name="file">Link file object</param> private void ParseLinkFile(Folder folder, FileInfo file) { try { FileStream fileStream = null; try { fileStream = file.OpenRead(); using (StreamReader streamReader = new StreamReader(fileStream)) { string lineText = string.Empty; while (null != (lineText = streamReader.ReadLine())) { // Link file is tab delimited. Its format is Name\tUrl\tThumbnail. Thumbnail is optional. string[] urlParts = lineText.Split(new char[] { '\t' }, StringSplitOptions.RemoveEmptyEntries); if (urlParts.Length == 2) { AddPlaceElementToFolder( urlParts[0], urlParts[1], string.Empty, file.IsLatestFile(this.CommunityServiceLatestFileDays), folder, file.GetModifiedDate()); } else if (urlParts.Length == 3) { AddPlaceElementToFolder( urlParts[0], urlParts[1], urlParts[3], file.IsLatestFile(this.CommunityServiceLatestFileDays), folder, file.GetModifiedDate()); } } } } finally { if (fileStream != null) { fileStream.Dispose(); } } } catch (XmlException ex) { // Consume any Xml Exception. ErrorHandler.LogException(ex); } catch (IOException ex) { // Consume any IO Exception. ErrorHandler.LogException(ex); } }
/// <summary> /// Saves the payload Folder in to Cache. /// </summary> /// <param name="payloadFilePath">Payload file path for the community</param> /// <param name="folder">Root folder object which needs to cached</param> /// <returns>True if saved to cache, false otherwise</returns> private static bool SavePayloadToCache(string payloadFilePath, Folder folder) { bool savedToCache = false; try { if (folder != null) { using (FileStream fileStream = new FileStream(payloadFilePath, FileMode.Create, FileAccess.Write)) { // IsLocal property of the Place object needs to be serialized so that while // De-serialized and processed for URL rewriting, IsLocal property can be used. XmlAttributeOverrides overrides = new XmlAttributeOverrides(); XmlAttributes attributes = new XmlAttributes(); attributes.XmlIgnore = false; overrides.Add(typeof(Place), "IsLocal", attributes); XmlTextWriter xmlTextWriter = new XmlTextWriter(fileStream, ASCIIEncoding.Unicode); XmlSerializer xmlSerializer = new XmlSerializer(typeof(Folder), overrides); xmlSerializer.Serialize(xmlTextWriter, folder); savedToCache = true; } } } catch (IOException ex) { ErrorHandler.LogException(ex); } catch (UnauthorizedAccessException ex) { ErrorHandler.LogException(ex); } return savedToCache; }
/// <summary> /// Adds the place object to the folder with the given name, URL, thumbnail. If the link file is latest, then /// Place will be added the "Latest" folder also. /// </summary> /// <param name="name">Name of the link</param> /// <param name="url">Url for the link</param> /// <param name="thumbnail">Thumbnail image of the link</param> /// <param name="latestFile">Is the link file latest?</param> /// <param name="folder">Folder to which the link belongs to</param> /// <param name="modifiedDate">Links file modified date</param> private static void AddPlaceElementToFolder(string name, string url, string thumbnail, bool latestFile, Folder folder, DateTime modifiedDate) { Place place = new Place(name, url, thumbnail); place.Parent = folder; place.ModifiedDate = modifiedDate; // Add the Place to the "Latest" folder which is the second children of RootFolder only if the file is modified with // the specified date in the configuration file. if (latestFile) { Place existingPlace = folder.RootFolder.Children[1].Links.FirstOrDefault(e => (e.Name == place.Name && e.Thumbnail == place.Thumbnail)); // Make sure the same Link is not added already which could be there in some other folder. Links will be // identified same based on their name and thumbnail. // If the Link already exists, replace the current Link if it is the latest one. if (existingPlace == null) { folder.RootFolder.Children[1].Links.Add(place); } else if (place.ModifiedDate > existingPlace.ModifiedDate) { folder.RootFolder.Children[1].Links.Remove(existingPlace); folder.RootFolder.Children[1].Links.Add(place); } } }
public Folder GetPayloadDetails(string communityId) { Folder rootFolder = null; try { string communityFolder = Path.Combine(this.CommunityLocation, communityId); string payloadFilePath = string.Format(CultureInfo.CurrentCulture, "{0}\\{1}_payload.xml", communityFolder, communityId); DirectoryInfo dirInfo = new DirectoryInfo(communityFolder); // Delete if any payload files are pending to be deleted, because cache dependency could not delete last time. for (int i = communityFolderCacheDependency.FilesToBeDeleted.Count - 1; i >= 0; i--) { try { string filePath = communityFolderCacheDependency.FilesToBeDeleted[i]; File.Delete(filePath); communityFolderCacheDependency.FilesToBeDeleted.Remove(filePath); } catch (IOException ex) { ErrorHandler.LogException(ex); } } if (File.Exists(payloadFilePath)) { try { using (Stream stream = File.OpenRead(payloadFilePath)) { // IsLocal property of the Place object needs to be serialized so that while // De-serialized and processed for URL rewriting, IsLocal property can be used. XmlAttributeOverrides overrides = new XmlAttributeOverrides(); XmlAttributes attributes = new XmlAttributes(); attributes.XmlIgnore = false; overrides.Add(typeof(Place), "IsLocal", attributes); XmlSerializer serializer = new XmlSerializer(typeof(Folder), overrides); XmlReader reader = new XmlTextReader(stream); rootFolder = (Folder)serializer.Deserialize(reader); } } catch (IOException ex) { ErrorHandler.LogException(ex); } } else { // Get thumbnail relative file path, if any image file with name Thumbnail exists. string thumbnailFile = dirInfo.GetThumbnailFilePath(this.CommunityLocation); // Creating the root community folder object. rootFolder = new Folder(dirInfo.Name, null); rootFolder.Thumbnail = thumbnailFile; // By default, two folder with name "All Tours" and "Latest" will be added to all communities. // "All Tours" will be having all the tours which are present in the community anywhere in the folder structure. // "Latest" will be having all the tours, WTMLs and Places/Links which are modified in last n days and present // in the community anywhere in the folder structure. Folder allToursFolder = new Folder(Constants.AllToursFolder, rootFolder); Folder latestFolder = new Folder(Constants.LatestFolder, rootFolder); // Loop through all the folder contents recursively. ProcessFolderItems(dirInfo, rootFolder); // This will make sure that there are not events fired while creating/overwriting the payload XML file. communityFolderCacheDependency.CommunityFolderWatcher.EnableRaisingEvents = false; SavePayloadToCache(payloadFilePath, rootFolder); } } catch (DirectoryNotFoundException ex) { ErrorHandler.LogException(ex); throw new FaultException(ex.Message); } catch (SecurityException ex) { ErrorHandler.LogException(ex); throw new FaultException(ex.Message); } catch (UnauthorizedAccessException ex) { ErrorHandler.LogException(ex); throw new FaultException(ex.Message); } catch (ArgumentException ex) { ErrorHandler.LogException(ex); throw new FaultException(ex.Message); } catch (IOException ex) { ErrorHandler.LogException(ex); throw new FaultException(ex.Message); } finally { // If the community folder watcher is not enabled for events, enable them. if (!communityFolderCacheDependency.CommunityFolderWatcher.EnableRaisingEvents) { communityFolderCacheDependency.CommunityFolderWatcher.EnableRaisingEvents = true; } } return rootFolder; }