public static void Sync(IEnumerable<string> sourcePaths, string destinationPath)
		{
			foreach (var sourcePath in sourcePaths)
			{
				if (!Directory.Exists(sourcePath)) continue;
				if (!Directory.Exists(destinationPath)) continue;

				var sourcePathCollection = new List<string>();
				sourcePathCollection.AddRange(Directory.GetDirectories(sourcePath));

				foreach (var sourceLibraryPath in sourcePathCollection)
				{
					var legacyLibraryPath = Path.Combine(sourceLibraryPath, Constants.OldPrimaryFileStorageName);
					var sourceLibraryCachePath = Directory.Exists(legacyLibraryPath) ? legacyLibraryPath : sourceLibraryPath;

					var libraryFolderName = Path.GetFileName(sourceLibraryPath);

					var sourceLibraryCacheFile = File.Exists(Path.Combine(sourceLibraryCachePath, Constants.LocalStorageFileName)) ?
						new FileInfo(Path.Combine(sourceLibraryCachePath, Constants.LocalStorageFileName)) :
						new FileInfo(Path.Combine(sourceLibraryCachePath, Constants.LegacyStorageFileName));

					if (!sourceLibraryCacheFile.Exists) continue;

					var destinationLibraryPath = Path.Combine(destinationPath, libraryFolderName);
					if (!Directory.Exists(destinationLibraryPath))
						Directory.CreateDirectory(destinationLibraryPath);

					var destinationLibraryCacheFile = new FileInfo(Path.Combine(destinationLibraryPath, sourceLibraryCacheFile.Name));
					if (!destinationLibraryCacheFile.Exists ||
						destinationLibraryCacheFile.LastWriteTime < sourceLibraryCacheFile.LastWriteTime ||
						destinationLibraryCacheFile.Length != sourceLibraryCacheFile.Length
						)
					{
						var syncHelper = new SynchronizationHelper();
						var syncOptions = new SynchronizationOptions(
							new DirectoryInfo(sourceLibraryCachePath),
							new DirectoryInfo(destinationLibraryPath),
							true
							);
						syncHelper.SynchronizeFolder(syncOptions);
					}
				}

				var destinationPathCollection = new List<string>();
				destinationPathCollection.AddRange(Directory.GetDirectories(destinationPath));
				foreach (var destinationLibraryPath in destinationPathCollection)
				{
					var libraryFolderName = Path.GetFileName(destinationLibraryPath);
					if (!Directory.Exists(Path.Combine(sourcePath, libraryFolderName)))
						Utils.DeleteFolder(destinationLibraryPath);
				}
				break;
			}
		}
		public static SynchronizationOptions CopyForChildFolder(
			SynchronizationOptions sourceOptions,
			DirectoryInfo newSourceDir,
			DirectoryInfo newDestinationDir
			)
		{
			var newOptions = new SynchronizationOptions(
				newSourceDir,
				newDestinationDir,
				sourceOptions.DeleteExtraFilesInDestination);
			newOptions.FilterList = sourceOptions.FilterList;
			return newOptions;
		}
		/// <summary>Synchronizes the files / sub-directories from a source folder to a destination folder. </summary>
		public SynchronizationResult SynchronizeFolder(
			SynchronizationOptions options)
		{
			try
			{
				Reset();

				if (options == null)
				{
					throw new ArgumentNullException("options");
				}

				var result = SynchronizeFolderInternal(options);

				if (SynchronizationCompleting == null)
					return result;

				SynchronizationCompleting(
					this,
					new SynchronizationCompletingEventArgs(result));

				return result;
			}
			catch (Exception ex)
			{
				if (SynchronizationCompleting == null)
					return SynchronizationResult.AbortedDueToError;

				SynchronizationCompleting(
					this,
					new SynchronizationCompletingEventArgs(SynchronizationResult.AbortedDueToError, ex));

				return SynchronizationResult.AbortedDueToError;
			}
			finally
			{
				Completed = true;
			}
		}
		private static SynchronizationResult SyncPrimaryRoot(
			Library library,
			bool isWebSync,
			SyncLog syncLog,
			CancellationToken cancellationToken)
		{
			var synchronizer = new SynchronizationHelper();
			synchronizer.FileSynchronized += syncLog.OnFileSynchronized;
			synchronizer.FolderSynchronized += syncLog.OnFolderSynchronized;

			var whiteListFolderNames = GetSyncedSpecialFolders(library, isWebSync);
			synchronizer.FolderSynchronizing += (o, e) =>
			{
				e.Cancel = whiteListFolderNames.Contains(Path.GetFileName(e.DestinationFilePath));
			};
			synchronizer.FileSynchronizing += (o, e) =>
			{
				if (cancellationToken.IsCancellationRequested)
					synchronizer.Abort(SynchronizationResult.AbortedDueToShutDown);
			};
			synchronizer.SynchronizationCompleting += (o, e) =>
			{
				if (e.Result != SynchronizationResult.Completed)
					syncLog.AbortLoging();
			};

			var filesWhiteListItems = library.Pages
				.SelectMany(p => p.AllLinks)
				.OfType<LibraryFileLink>()
				.Where(link => link.DataSourceId == library.DataSourceId)
				.Select(link => link.FullPath)
				.ToList();

			filesWhiteListItems.Add(Path.Combine(library.Path, Constants.LocalStorageFileName));
			if (isWebSync)
			{
				filesWhiteListItems.Add(Path.Combine(library.Path, Constants.LibrariesJsonFileName));
				filesWhiteListItems.Add(Path.Combine(library.Path, Constants.ShortLibraryInfoFileName));
			}

			var destinationPath = GetLibrarySyncDestinationPath(library, isWebSync);

			var syncOptions = new SynchronizationOptions(
				new DirectoryInfo(library.Path),
				new DirectoryInfo(destinationPath),
				true);
			syncOptions.FilterList = SyncFilterList.Create(filesWhiteListItems, SyncFilterType.ByWhiteList);

			if (!Directory.Exists(destinationPath))
				synchronizer.CreateFolder(destinationPath);

			return synchronizer.SynchronizeFolder(syncOptions);
		}
		private static SynchronizationResult SyncSpecialFolder(
			string specialFolderPath,
			string destinationFolderPath,
			SyncLog syncLog,
			CancellationToken cancellationToken)
		{
			var synchronizer = new SynchronizationHelper();
			synchronizer.FileSynchronized += syncLog.OnFileSynchronized;
			synchronizer.FolderSynchronized += syncLog.OnFolderSynchronized;
			synchronizer.FileSynchronizing += (o, e) =>
			{
				if (cancellationToken.IsCancellationRequested)
					synchronizer.Abort(SynchronizationResult.AbortedDueToShutDown);
			};
			synchronizer.SynchronizationCompleting += (o, e) =>
			{
				if (e.Result != SynchronizationResult.Completed)
					syncLog.AbortLoging();
			};

			if (!Directory.Exists(destinationFolderPath))
				synchronizer.CreateFolder(destinationFolderPath);

			var syncOptions = new SynchronizationOptions(
				new DirectoryInfo(specialFolderPath),
				new DirectoryInfo(destinationFolderPath),
				true);

			return synchronizer.SynchronizeFolder(syncOptions);
		}
		private SynchronizationResult SynchronizeFolderInternal(
			SynchronizationOptions options)
		{
			if (ShouldAbort())
			{
				return _abortResult;
			}

			var srcFiles = options.SourceDirectory.GetFiles();
			var dstFiles = options.DestinationDirectory.GetFiles();

			var srcDirs = options.SourceDirectory.GetDirectories();
			var dstDirs = options.DestinationDirectory.GetDirectories();

			var srcTaggedFiles = srcFiles
				.Where(fileInfo => options.FilterList == null || options.FilterList.IsMatchFile(fileInfo.FullName))
				.ToDictionary(fi => fi.Name);
			var srcTaggedDirs = srcDirs
				.Where(directoryInfo => options.FilterList == null || options.FilterList.IsMatchDirectory(directoryInfo.FullName))
				.ToDictionary(di => di.Name);

			var dstTaggedFiles = dstFiles.ToDictionary(fi => fi.Name);
			var dstTaggedDirs = dstDirs.ToDictionary(di => di.Name);

			foreach (var srcFileInfo in srcTaggedFiles.Values)
			{
				var toCopy = false;
				var operation = SynchronizationOperation.None;

				if (dstTaggedFiles.ContainsKey(srcFileInfo.Name))
				{
					var dstFileInfo = dstTaggedFiles[srcFileInfo.Name];
					if (srcFileInfo.LastWriteTimeUtc > dstFileInfo.LastWriteTimeUtc)
					{
						toCopy = true;
						operation = SynchronizationOperation.Update;
					}
				}
				else
				{
					toCopy = true;
					operation = SynchronizationOperation.Add;
				}

				if (ShouldAbort())
				{
					return _abortResult;
				}

				if (!toCopy)
					continue;

				var destinationFileName = Path.Combine(options.DestinationDirectory.FullName, srcFileInfo.Name);
				var args = new SynchronizingEventArgs(
					srcFileInfo.FullName,
					destinationFileName,
					operation);

				FileSynchronizing?.Invoke(this, args);

				if (args.Cancel)
					continue;

				try
				{
					srcFileInfo.CopyTo(destinationFileName, true);

					FileSynchronized?.Invoke(this, new SynchronizedEventArgs(args));
				}
				catch (Exception ex)
				{
					Error?.Invoke(this, new SynchronizationExceptionEventArgs(args, ex));
				}
			}

			if (options.DeleteExtraFilesInDestination)
			{
				foreach (var dstFileInfo in dstFiles)
				{
					if (ShouldAbort())
						return _abortResult;

					if (srcTaggedFiles.ContainsKey(dstFileInfo.Name))
						continue;

					var args = new SynchronizingEventArgs(
						dstFileInfo.FullName,
						dstFileInfo.FullName,
						SynchronizationOperation.Delete);

					FileSynchronizing?.Invoke(this, args);

					if (args.Cancel)
						continue;

					try
					{
						dstFileInfo.Delete();
						FileSynchronized?.Invoke(this, new SynchronizedEventArgs(args));
					}
					catch (Exception ex)
					{
						Error?.Invoke(this, new SynchronizationExceptionEventArgs(args, ex));
					}
				}
			}

			foreach (var srcSubDir in srcTaggedDirs.Values)
			{
				DirectoryInfo dstSubDir;
				SynchronizationOperation operation;
				if (dstTaggedDirs.ContainsKey(srcSubDir.Name))
				{
					dstSubDir = dstTaggedDirs[srcSubDir.Name];
					operation = SynchronizationOperation.Update;
				}
				else
				{
					dstSubDir = CreateFolder(Path.Combine(options.DestinationDirectory.FullName, srcSubDir.Name));
					operation = SynchronizationOperation.Add;
				}

				if (ShouldAbort())
				{
					return _abortResult;
				}

				var args = new SynchronizingEventArgs(
					srcSubDir.FullName,
					dstSubDir.FullName,
					operation);

				FolderSynchronizing?.Invoke(this, args);

				if (args.Cancel) continue;

				var childResult = SynchronizeFolderInternal(
					SynchronizationOptions.CopyForChildFolder(options, srcSubDir, dstSubDir));

				if (childResult != SynchronizationResult.Completed)
					return childResult;
			}

			// NOTE: this logic is not correct. We will end up deleting even files
			// whose extension is not present in the "extensions" parameter (if not null).
			if (options.DeleteExtraFilesInDestination)
			{
				foreach (var dstSubDir in dstDirs)
				{
					if (ShouldAbort())
						return _abortResult;

					if (!srcTaggedDirs.ContainsKey(dstSubDir.Name))
					{
						var args = new SynchronizingEventArgs(
							dstSubDir.FullName,
							dstSubDir.FullName,
							SynchronizationOperation.Delete);
						FolderSynchronizing?.Invoke(this, args);

						if (args.Cancel) continue;

						DeleteFolder(dstSubDir);
					}
				}
			}

			return SynchronizationResult.Completed;
		}