public SynchronizedEventArgs(SynchronizingEventArgs args)
		{
			SourceFilePath = args.SourceFilePath;
			DestinationFilePath = args.DestinationFilePath;
			State = args.State;
			Operation = args.Operation;
		}
		public SynchronizationExceptionEventArgs(SynchronizingEventArgs args, Exception exception)
		{
			SourceFilePath = args.SourceFilePath;
			DestinationFilePath = args.DestinationFilePath;
			State = args.State;
			Exception = exception;
		}
		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;
		}