Click here to Skip to main content
15,896,453 members
Articles / Web Development / HTML

Gallery Server Pro - An ASP.NET Gallery for Sharing Photos, Video, Audio and Other Media

Rate me:
Please Sign up or sign in to vote.
4.86/5 (131 votes)
18 Oct 2013GPL331 min read 829.8K   539  
Gallery Server Pro is a complete, stable ASP.NET gallery for sharing photos, video, audio and other media. This article presents the overall architecture and major features.
using System;
using System.Collections.Generic;
using System.Threading;
using System.Web.Script.Services;
using System.Web.Services;
using GalleryServerPro.Business;
using GalleryServerPro.Business.Interfaces;
using GalleryServerPro.Web;
using GalleryServerPro.Web.Controller;
using GalleryServerPro.Web.Entity;
using GalleryServerPro.Web.Pages;

namespace Gsp
{
	/// <summary>
	/// Contains web services that can be used to interact with the gallery.
	/// </summary>
	[ScriptService]
	[WebService(Namespace = "http://www.galleryserverpro.com/")]
	[WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)]
	[System.ComponentModel.ToolboxItem(false)]
	public class Gallery : WebService
	{
		#region Constructors

		/// <summary>
		/// Initializes the <see cref="Gallery"/> class.
		/// </summary>
		static Gallery()
		{
			if (!GalleryController.IsInitialized)
			{
				GalleryController.InitializeGspApplication();
			}
		}

		/// <summary>
		/// Initializes a new instance of the <see cref="Gallery"/> class.
		/// </summary>
		public Gallery()
		{
			// Ensure the app is initialized. This should have been done in the static constructor, but if anything went wrong
			// there, it may not be initialized, so we check again.
			if (!GalleryController.IsInitialized)
			{
				GalleryController.InitializeGspApplication();
			}
		}

		#endregion

		#region Public Web Methods

		/// <summary>
		/// Get information for the specified media object, including its previous and next media object.
		/// </summary>
		/// <param name="mediaObjectId">The ID that uniquely identifies the media object.</param>
		/// <param name="displayType">The type of display object to receive (thumbnail, optimized, original).</param>
		/// <returns>Returns an instance of MediaObjectWebEntity containing information for the specified media object,
		/// including its previous and next media object.</returns>
		[WebMethod(EnableSession = true)]
		public MediaObjectWebEntity GetMediaObjectHtml(int mediaObjectId, DisplayObjectType displayType)
		{
			try
			{
				return GalleryPage.GetMediaObjectHtml(Factory.LoadMediaObjectInstance(mediaObjectId), displayType, true);
			}
			catch (Exception ex)
			{
				AppErrorController.LogError(ex);
				throw;
			}
		}

		/// <summary>
		/// Permanently deletes the specified media object from the file system and data store. No action is taken if the
		/// user does not have delete permission.
		/// </summary>
		/// <param name="mediaObjectId">The ID that uniquely identifies the media object to be deleted.</param>
		[WebMethod(EnableSession = true)]
		public void DeleteMediaObject(int mediaObjectId)
		{
			IGalleryObject mo = null;

			try
			{
				mo = Factory.LoadMediaObjectInstance(mediaObjectId);
				if (Util.IsUserAuthorized(SecurityActions.DeleteMediaObject, mo.Parent.Id, mo.GalleryId, mo.IsPrivate))
				{
					mo.Delete();
					HelperFunctions.PurgeCache();
				}
			}
			catch (Exception ex)
			{
				if (mo != null)
				{
					AppErrorController.LogError(ex, mo.GalleryId);
				}
				else
				{
					AppErrorController.LogError(ex);
				}
				throw;
			}
		}

		/// <summary>
		/// Permanently deletes the specified albums from the file system and data store. No action is taken if the
		/// user does not have delete permission.
		/// </summary>
		/// <param name="albumIds">The IDs that uniquely identifies the albums to be deleted.</param>
		[WebMethod(EnableSession = true)]
		public void DeleteAlbums(int[] albumIds)
		{
			IGalleryObject album = null;

			try
			{
				foreach (int albumId in albumIds)
				{
					try
					{
						album = Factory.LoadAlbumInstance(albumId, false);

						if (Util.IsUserAuthorized(SecurityActions.DeleteAlbum, album.Id, album.GalleryId, album.IsPrivate))
						{
							album.Delete();
						}
					}
					catch (GalleryServerPro.ErrorHandler.CustomExceptions.InvalidAlbumException) { }
				}
				HelperFunctions.PurgeCache();
			}
			catch (Exception ex)
			{
				if (album != null)
				{
					AppErrorController.LogError(ex, album.GalleryId);
				}
				else
				{
					AppErrorController.LogError(ex);
				}
				throw;
			}
		}

		/// <summary>
		/// Update the media object with the specified title and persist to the data store. The title is validated before
		/// saving, and may be altered to conform to business rules, such as removing HTML tags. The validated title is returned.
		/// If the user is not authorized to edit the title, no action is taken and the original title as stored in the data
		/// store is returned.
		/// </summary>
		/// <param name="mediaObjectId">The ID that uniquely identifies the media object whose title is to be updated.</param>
		/// <param name="title">The title to be saved to the media object.</param>
		/// <returns>Returns the validated title.</returns>
		[WebMethod(EnableSession = true)]
		public string UpdateMediaObjectTitle(int mediaObjectId, string title)
		{
			IGalleryObject mo = null;

			try
			{
				mo = Factory.LoadMediaObjectInstance(mediaObjectId, true);
				if (Util.IsUserAuthorized(SecurityActions.EditMediaObject, mo.Parent.Id, mo.GalleryId, mo.IsPrivate))
				{
					string previousTitle = mo.Title;
					mo.Title = Util.CleanHtmlTags(title, mo.GalleryId);

					if (mo.Title != previousTitle)
						GalleryObjectController.SaveGalleryObject(mo);

					HelperFunctions.PurgeCache();
				}

				return mo.Title;
			}
			catch (Exception ex)
			{
				if (mo != null)
				{
					AppErrorController.LogError(ex, mo.GalleryId);
				}
				else
				{
					AppErrorController.LogError(ex);
				}
				throw;
			}
		}

		/// <summary>
		/// Update the album with the specified properties in the albumEntity parameter. The title is validated before
		/// saving, and may be altered to conform to business rules, such as removing HTML tags. After the object is persisted
		/// to the data store, the albumEntity parameter is updated with the latest properties from the album object and returned.
		/// If the user is not authorized to edit the album, no action is taken.
		/// </summary>
		/// <param name="albumEntity">An AlbumWebEntity instance containing data to be persisted to the data store.</param>
		/// <returns>Returns an AlbumWebEntity instance containing the data as persisted to the data store. Some properties,
		/// such as the Title property, may be slightly altered to conform to validation rules.</returns>
		[WebMethod(EnableSession = true)]
		public AlbumWebEntity UpdateAlbumInfo(AlbumWebEntity albumEntity)
		{
			try
			{
				albumEntity.Owner = Util.HtmlDecode(albumEntity.Owner);

				return AlbumController.UpdateAlbumInfo(albumEntity);
			}
			catch (Exception ex)
			{
				AppErrorController.LogError(ex);
				throw;
			}
		}

		/// <summary>
		/// Retrieve album information for the specified album ID. Returns an object with empty properties if the user
		/// does not have permission to view the specified album.
		/// </summary>
		/// <param name="albumId">The album ID for which to retrieve information.</param>
		/// <returns>Returns AlbumWebEntity object containing information about the requested album.</returns>
		[WebMethod(EnableSession = true)]
		public AlbumWebEntity GetAlbumInfo(int albumId)
		{
			IAlbum album = null;

			try
			{
				AlbumWebEntity albumEntity = new AlbumWebEntity();

				album = Factory.LoadAlbumInstance(albumId, false);

				if (Util.IsUserAuthorized(SecurityActions.ViewAlbumOrMediaObject, albumId, album.GalleryId, album.IsPrivate))
				{
					albumEntity.Title = album.Title;
					albumEntity.Summary = album.Summary;
					albumEntity.DateStart = album.DateStart;
					albumEntity.DateEnd = album.DateEnd;
					albumEntity.IsPrivate = album.IsPrivate;
				}

				return albumEntity;
			}
			catch (Exception ex)
			{
				if (album != null)
				{
					AppErrorController.LogError(ex, album.GalleryId);
				}
				else
				{
					AppErrorController.LogError(ex);
				}
				throw;
			}
		}

		/// <summary>
		/// Get a list of metadata items corresponding to the specified mediaObjectId. Guaranteed to not return null.
		/// </summary>
		/// <param name="mediaObjectId">The ID of the media object for which to return its metadata items.</param>
		/// <returns>Returns a generic list of MetadataItemWebEntity objects that contain the metadata for the 
		/// specified media object.</returns>
		[WebMethod(EnableSession = true)]
		public List<MetadataItemWebEntity> GetMetadataItems(int mediaObjectId)
		{
			IGalleryObject mo = null;

			try
			{
				List<MetadataItemWebEntity> metadataItems = new List<MetadataItemWebEntity>();

				mo = Factory.LoadMediaObjectInstance(mediaObjectId);
				if (Util.IsUserAuthorized(SecurityActions.ViewAlbumOrMediaObject, RoleController.GetGalleryServerRolesForUser(), mo.Parent.Id, mo.GalleryId, mo.Parent.IsPrivate))
				{
					foreach (IGalleryObjectMetadataItem metadata in mo.MetadataItems.GetVisibleItems())
					{
						metadataItems.Add(new MetadataItemWebEntity(metadata.Description, metadata.Value));
					}
				}

				return metadataItems;
			}
			catch (Exception ex)
			{
				if (mo != null)
				{
					AppErrorController.LogError(ex, mo.GalleryId);
				}
				else
				{
					AppErrorController.LogError(ex);
				}
				throw;
			}
		}

		/// <summary>
		/// Permanently deletes the specified application error item from the error log. No action is taken if the
		/// user does not have delete permission.
		/// </summary>
		/// <param name="appErrorId">The ID that uniquely identifies the media object to be deleted.</param>
		[WebMethod(EnableSession = true)]
		public void DeleteAppError(int appErrorId)
		{
			IAppError appError = null;

			try
			{
				appError = Factory.GetAppErrors().FindById(appErrorId);

				bool isAuthorized = true;

				// If the error has a gallery ID (not all do), then check the user's permission. For those errors without a gallery ID,
				// just assume the user has permission, because there is no way to verify the user can delete this error. We could do something
				// that mostly works like verifying the user is a gallery admin for at least one gallery, but the function we are trying to
				// protect is deleting an error message, which is not that important to worry about.
				if (appError.GalleryId > int.MinValue)
				{
					isAuthorized = Util.IsUserAuthorized(SecurityActions.AdministerSite | SecurityActions.AdministerGallery, RoleController.GetGalleryServerRolesForUser(), int.MinValue, appError.GalleryId, false);
				}

				if (isAuthorized)
				{
					GalleryServerPro.ErrorHandler.Error.Delete(appErrorId);
					HelperFunctions.PurgeCache();
				}
			}
			catch (Exception ex)
			{
				if (appError != null)
				{
					AppErrorController.LogError(ex, appError.GalleryId);
				}
				else
				{
					AppErrorController.LogError(ex);
				}
				throw;
			}
		}

		/// <summary>
		/// Stores the user's preference for showing or hiding the metadata popup window. The information is saved
		/// to the user's profile. Applies only to the gallery specified by <paramref name="galleryId" />.
		/// </summary>
		/// <param name="isVisible">if set to <c>true</c> the metadata popup window is visible; otherwise <c>false</c>.</param>
		/// <param name="galleryId">The gallery ID.</param>
		[WebMethod(EnableSession = true)]
		public void SetMetaDataVisibility(bool isVisible, int galleryId)
		{
			try
			{
				IUserProfile profile = ProfileController.GetProfile();
				profile.GetGalleryProfile(galleryId).ShowMediaObjectMetadata = isVisible;
				ProfileController.SaveProfile(profile);
			}
			catch (Exception ex)
			{
				AppErrorController.LogError(ex);

#if DEBUG
				throw;
#endif
			}
		}

		/// <summary>
		/// Excecute a maintenance routine to help ensure data integrity and eliminate unused data. For example, roles are synchronized between
		/// the membership system and the GSP roles. Also, albums with owners that no longer exist are reset to not have an owner. This web
		/// service method is intended to be called periodically; for example, once each time the application starts. Code in the Render
		/// method of the base class <see cref="GalleryPage" /> is responsible for knowing when and how to invoke this method.
		/// </summary>
		/// <remarks>The first iteration of the maintenace routine was invoked on a background thread, but background threads cannot access
		/// HttpContext.Current, which is required for the DotNetNuke implementation (and potential future versions of GSP's implementation),
		/// so that approach was replaced with this one.</remarks>
		[WebMethod(EnableSession = false)]
		public void PerformMaintenance()
		{
			try
			{
				Util.PerformMaintenance();
			}
			catch (Exception ex)
			{
				AppErrorController.LogError(ex);
			}
		}

		///// <summary>
		///// Recycles the current web application.
		///// </summary>
		//[WebMethod(EnableSession = true)]
		//public void RecycleApp()
		//{
		//  Util.ForceAppRecycle();
		//}

		#region Synchronize Web Services

		/// <summary>
		/// Synchronize the specified album with the physical directory on the hard drive.
		/// </summary>
		/// <param name="albumId">The album id for the album to synchronize.</param>
		/// <param name="synchId">A GUID that uniquely indentifies the synchronization. If another synchronization is in 
		/// progress, a <see cref="GalleryServerPro.ErrorHandler.CustomExceptions.SynchronizationInProgressException" /> exception is thrown.</param>
		/// <param name="isRecursive">If set to <c>true</c> the synchronization continues drilling down into directories
		/// below the current one.</param>
		/// <param name="overwriteThumb">if set to <c>true</c> the thumbnail image for each media object is deleted and overwritten with a new one
		/// based on the original file. Applies to all media objects.</param>
		/// <param name="overwriteOpt">if set to <c>true</c> the optimized image for each media object is deleted and overwritten with a new one
		/// based on the original file. Only relevant for images.</param>
		/// <param name="regenerateMetadata">if set to <c>true</c> the existing metadata for each media object is replaced with
		/// the metadata stored within the original media object file.</param>
		/// <exception cref="GalleryServerPro.ErrorHandler.CustomExceptions.SynchronizationInProgressException">
		/// Thrown if another synchronization is in progress.</exception>
		[WebMethod(EnableSession = true)]
		public void Synchronize(int albumId, string synchId, bool isRecursive, bool overwriteThumb, bool overwriteOpt, bool regenerateMetadata)
		{
			IAlbum album = null;

			try
			{
				#region Check user authorization

				bool isUserAuthenticated = Util.IsAuthenticated;
				if (!isUserAuthenticated)
					return;

				album = Factory.LoadAlbumInstance(albumId, true, true, false);

				if (!Util.IsUserAuthorized(SecurityActions.Synchronize, RoleController.GetGalleryServerRolesForUser(), albumId, album.GalleryId, false))
					return;

				#endregion

				SynchronizationManager synchMgr = new SynchronizationManager(album.GalleryId);

				synchMgr.IsRecursive = isRecursive;
				synchMgr.OverwriteThumbnail = overwriteThumb;
				synchMgr.OverwriteOptimized = overwriteOpt;
				synchMgr.RegenerateMetadata = regenerateMetadata;

				synchMgr.Synchronize(synchId, album, Util.UserName);
			}
			catch (Exception ex)
			{
				if (album != null)
				{
					AppErrorController.LogError(ex, album.GalleryId);
				}
				else
				{
					AppErrorController.LogError(ex);
				}
				throw;
			}
		}

		/// <summary>
		/// Synchronize galleries in the data store with its physical directory on the hard drive. The <paramref name="password" />
		/// must match <see cref="IGallerySettings.RemoteAccessPassword" />. This method is designed to be invoked by anonymous users
		/// who have the correct password. The sync is invoked on a separate thread so this method returns immediately.
		/// </summary>
		/// <param name="isRecursive">If set to <c>true</c> the synchronization continues drilling down into directories
		/// below the current one.</param>
		/// <param name="overwriteThumbnails">if set to <c>true</c> the thumbnail image for each media object is deleted and overwritten with a new one
		/// based on the original file. Applies to all media objects.</param>
		/// <param name="overwriteOptimizedImages">if set to <c>true</c> the optimized image for each media object is deleted and overwritten with a new one
		/// based on the original file. Only relevant for images.</param>
		/// <param name="regenerateMetadata">if set to <c>true</c> the existing metadata for each media object is replaced with
		/// the metadata stored within the original media object file.</param>
		/// <param name="password">The password that authorizes the caller to invoke a synchronization.</param>
		/// <remarks>NOTE TO DEVELOPER: If you change the name of this method, update the property 
		/// <see cref="GalleryServerPro.Web.Pages.Admin.albums.SyncAllGalleriesUrl" />.</remarks>
		[WebMethod(EnableSession = false)]
		public void SyncAllGalleries(bool isRecursive, bool overwriteThumbnails, bool overwriteOptimizedImages, bool regenerateMetadata, string password)
		{
			foreach (IGallery gallery in Factory.LoadGalleries())
			{
				SyncAlbum(Factory.LoadRootAlbumInstance(gallery.GalleryId, false).Id, isRecursive, overwriteThumbnails, overwriteOptimizedImages, regenerateMetadata, password);
			}
		}

		/// <summary>
		/// Synchronize the specified album with the physical directory on the hard drive. The <paramref name="password" />
		/// must match <see cref="IGallerySettings.RemoteAccessPassword" />. This method is designed to be invoked by anonymous users
		/// who have the correct password. The sync is invoked on a separate thread so this method returns immediately.
		/// The sync is recursive, with the overwrite thumbnail, overwrite optimized, and regenerate metadata options disabled.
		/// </summary>
		/// <param name="isRecursive">If set to <c>true</c> the synchronization continues drilling down into directories
		/// below the current one.</param>
		/// <param name="overwriteThumbnails">if set to <c>true</c> the thumbnail image for each media object is deleted and overwritten with a new one
		/// based on the original file. Applies to all media objects.</param>
		/// <param name="overwriteOptimizedImages">if set to <c>true</c> the optimized image for each media object is deleted and overwritten with a new one
		/// based on the original file. Only relevant for images.</param>
		/// <param name="regenerateMetadata">if set to <c>true</c> the existing metadata for each media object is replaced with
		/// the metadata stored within the original media object file.</param>
		/// <param name="albumId">The album ID for the album to synchronize.</param>
		/// <param name="password">The password that authorizes the caller to invoke a synchronization.</param>
		/// <remarks>NOTE TO DEVELOPER: If you change the name of this method, update the property 
		/// <see cref="GalleryServerPro.Web.Pages.Admin.albums.SyncAlbumUrl" />.</remarks>
		[WebMethod(EnableSession = false)]
		public void SyncAlbum(int albumId, bool isRecursive, bool overwriteThumbnails, bool overwriteOptimizedImages, bool regenerateMetadata, string password)
		{
			IAlbum album = null;

			try
			{
				album = Factory.LoadAlbumInstance(albumId, true, true, false);

				if (!ValidateRemoteSync(album, password))
				{
					return;
				}

				SynchronizeSettingsEntity syncSettings = new SynchronizeSettingsEntity();
				syncSettings.SyncInitiator = SyncInitiator.RemoteApp;
				syncSettings.AlbumToSynchronize = album;
				syncSettings.IsRecursive = isRecursive;
				syncSettings.OverwriteThumbnails = overwriteThumbnails;
				syncSettings.OverwriteOptimized = overwriteOptimizedImages;
				syncSettings.RegenerateMetadata = regenerateMetadata;

				// Start sync on new thread
				Thread notifyAdminThread = new Thread(GalleryController.Synchronize);
				notifyAdminThread.Start(syncSettings);
			}
			catch (Exception ex)
			{
				if (album != null)
				{
					AppErrorController.LogError(ex, album.GalleryId);
				}
				else
				{
					AppErrorController.LogError(ex);
				}
				throw;
			}
		}

		private static bool ValidateRemoteSync(IAlbum album, string password)
		{
			IGallerySettings gallerySettings = Factory.LoadGallerySetting(album.GalleryId);

			if (!gallerySettings.EnableRemoteSync)
			{
				AppErrorController.LogEvent(string.Format("Cannot start synchronization: A web service request to start synchronizing album '{0}' (ID {1}) was received, but the gallery is currently configured to disallow remote synchronizations. This feature can be enabled on the Albums page in the Site admin area.", album.Title, album.Id), album.GalleryId);
				return false;
			}

			if (!gallerySettings.RemoteAccessPassword.Equals(password))
			{
				AppErrorController.LogEvent(string.Format("Cannot start synchronization: A web service request to start synchronizing album '{0}' (ID {1}) was received, but the specified password is incorrect.", album.Title, album.Id), album.GalleryId);
				return false;
			}

			return true;
		}

		/// <summary>
		/// Gets information about the current synchronization. If no synchronization is in progress, returns information about
		/// the most recent synchronization.
		/// </summary>
		/// <param name="galleryId">The gallery ID.</param>
		/// <returns>
		/// Returns information about the current synchronization.
		/// </returns>
		[WebMethod]
		public SynchStatusWebEntity GetCurrentStatus(int galleryId)
		{
			ISynchronizationStatus synchStatus = SynchronizationStatus.GetInstance(galleryId);

			try
			{
				SynchStatusWebEntity synchStatusWeb = new SynchStatusWebEntity();

				synchStatusWeb.SynchId = synchStatus.SynchId;
				synchStatusWeb.TotalFileCount = synchStatus.TotalFileCount;
				synchStatusWeb.CurrentFileIndex = synchStatus.CurrentFileIndex + 1;

				if ((synchStatus.CurrentFilePath != null) && (synchStatus.CurrentFileName != null))
					synchStatusWeb.CurrentFile = System.IO.Path.Combine(synchStatus.CurrentFilePath, synchStatus.CurrentFileName);

				synchStatusWeb.Status = synchStatus.Status.ToString();
				synchStatusWeb.StatusForUI = GetFriendlyStatusText(synchStatus.Status);
				synchStatusWeb.PercentComplete = CalculatePercentComplete(synchStatus);

				// Update the Skipped Files, but only when the synch is complete.
				lock (synchStatus)
				{
					if (synchStatus.Status == SynchronizationState.Complete)
					{
						if (synchStatus.SkippedMediaObjects.Count > GlobalConstants.MaxNumberOfSkippedObjectsToDisplayAfterSynch)
						{
							// We have a large number of skipped media objects. We don't want to send it all to the browsers, because it might take
							// too long or cause an error if it serializes to a string longer than int.MaxValue, so let's trim it down.
							synchStatus.SkippedMediaObjects.RemoveRange(GlobalConstants.MaxNumberOfSkippedObjectsToDisplayAfterSynch,
																													synchStatus.SkippedMediaObjects.Count -
																													GlobalConstants.MaxNumberOfSkippedObjectsToDisplayAfterSynch);
						}
						synchStatusWeb.SkippedFiles = synchStatus.SkippedMediaObjects;
					}
				}
				return synchStatusWeb;
			}
			catch (Exception ex)
			{
				AppErrorController.LogError(ex, synchStatus.GalleryId);
				throw;
			}
		}

		/// <summary>
		/// Terminates the synchronization with the specified <paramref name="taskId"/>.
		/// </summary>
		/// <param name="galleryId">The gallery ID.</param>
		/// <param name="taskId">The task id (also knows as synch ID) representing the synchronization to cancel.</param>
		[WebMethod]
		public void TerminateTask(int galleryId, string taskId)
		{
			ISynchronizationStatus synchStatus = SynchronizationStatus.GetInstance(galleryId);

			try
			{
				synchStatus.CancelSynchronization(taskId);
			}
			catch (Exception ex)
			{
				AppErrorController.LogError(ex);
				throw;
			}
		}

		#endregion

		#endregion

		#region Private Methods

		private static string GetFriendlyStatusText(SynchronizationState status)
		{
			switch (status)
			{
				case SynchronizationState.AnotherSynchronizationInProgress: return Resources.GalleryServerPro.Task_Synch_Progress_Status_SynchInProgressException_Hdr;
				case SynchronizationState.Complete: return status.ToString();
				case SynchronizationState.Error: return status.ToString();
				case SynchronizationState.PersistingToDataStore: return Resources.GalleryServerPro.Task_Synch_Progress_Status_PersistingToDataStore_Hdr;
				case SynchronizationState.SynchronizingFiles: return Resources.GalleryServerPro.Task_Synch_Progress_Status_SynchInProgress_Hdr;
				default: throw new System.ComponentModel.InvalidEnumArgumentException("The GetFriendlyStatusText() method in synchronize.aspx encountered a SynchronizationState enum value it was not designed for. This method must be updated.");
			}
		}

		private static int CalculatePercentComplete(ISynchronizationStatus synchStatus)
		{
			if (synchStatus.Status == SynchronizationState.SynchronizingFiles)
				return (int)(((double)synchStatus.CurrentFileIndex / (double)synchStatus.TotalFileCount) * 100);
			else
				return 100;
		}

		/// <summary>
		/// Generate a comma-delimited string containing a list of the file extensions that are in the specified filenames.
		/// If more than one filename has the same extension, the extension is listed just once in the output.
		/// </summary>
		/// <param name="filenames">A list of filenames.</param>
		/// <returns>Returns a comma-delimited string containing a list of the file extensions that are in the specified filenames.</returns>
		private static string GetFileExtensions(System.Collections.Specialized.StringCollection filenames)
		{
			System.Configuration.CommaDelimitedStringCollection fileExts = new System.Configuration.CommaDelimitedStringCollection();

			foreach (string filename in filenames)
			{
				string fileExt = System.IO.Path.GetExtension(filename);
				if (!fileExts.Contains(fileExt))
				{
					fileExts.Add(fileExt);
				}
			}

			return fileExts.ToString();
		}

		#endregion

	}
}

By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.

If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for it.

License

This article, along with any associated source code and files, is licensed under The GNU General Public License (GPLv3)


Written By
Software Developer (Senior) Tech Info Systems
United States United States
I have nearly 20 years of industry experience in software development, architecture, and Microsoft Office products. My company Tech Info Systems provides custom software development services for corporations, governments, and other organizations. Tech Info Systems is a registered member of the Microsoft Partner Program and I am a Microsoft Certified Professional Developer (MCPD).

I am the creator and lead developer of Gallery Server Pro, a free, open source ASP.NET gallery for sharing photos, video, audio, documents, and other files over the web. It has been developed over several years and has involved thousands of hours. The end result is a robust, configurable, and professional grade gallery that can be integrated into your web site, whether you are a large corporation, small business, professional photographer, or a local church.

Comments and Discussions