<commit_before>﻿using System;
using System.IO;
using System.Linq;
using System.Security.AccessControl;
using System.Security.Principal;
using System.Threading;
using SIL.Reporting;

namespace SIL.IO
{
	public class DirectoryUtilities
	{

		/// <summary>
		/// Makes a full copy of the specified directory in the system's temporary directory.
		/// If the copy fails at any point in the process, the user is notified of the
		/// problem and an attempt is made to remove the destination directory if the failure
		/// happened part way through the process.
		/// </summary>
		/// <param name="srcDirectory">Directory to copy</param>
		/// <returns>Null if the copy was unsuccessful, otherwise the path to the copied directory</returns>

		public static string CopyDirectoryToTempDirectory(string srcDirectory)
		{
			string dstDirectory;
			return (CopyDirectory(srcDirectory, Path.GetTempPath(), out dstDirectory) ? dstDirectory : null);
		}


		/// <summary>
		/// Makes a copy of the specifed source directory and its contents in the specified
		/// destination directory. The copy has the same directory name as the source, but ends up
		/// as a sub directory of the specified destination directory. The destination directory must
		/// already exist. If the copy fails at any point in the process, the user is notified
		/// of the problem and an attempt is made to remove the destination directory if the
		/// failure happened part way through the process.
		/// </summary>
		/// <param name="srcDirectory">Directory being copied</param>
		/// <param name="dstDirectoryParent">Destination directory where source directory and its contents are copied</param>
		/// <returns>true if successful, otherwise, false.</returns>

		public static bool CopyDirectory(string srcDirectory, string dstDirectoryParent)
		{
			string dstDirectory;
			return CopyDirectory(srcDirectory, dstDirectoryParent, out dstDirectory);
		}


		private static bool CopyDirectory(string srcDirectory, string dstDirectoryParent, out string dstDirectory)
		{
			dstDirectory = Path.Combine(dstDirectoryParent, Path.GetFileName(srcDirectory));

			if (!Directory.Exists(dstDirectoryParent))
			{
				ErrorReport.NotifyUserOfProblem(new DirectoryNotFoundException(dstDirectoryParent + " not found."),
					"{0} was unable to copy the directory {1} to {2}", EntryAssembly.ProductName, srcDirectory, dstDirectoryParent);
				return false;
			}
			if (AreDirectoriesEquivalent(srcDirectory, dstDirectory))
			{
				ErrorReport.NotifyUserOfProblem(new Exception("Cannot copy directory to itself."),
					"{0} was unable to copy the directory {1} to {2}", EntryAssembly.ProductName, srcDirectory, dstDirectoryParent);
				return false;
			}

			return CopyDirectoryContents(srcDirectory, dstDirectory);
		}


		/// <summary>
		/// Copies the specified source directory's contents to the specified destination directory.
		/// If the destination directory does not exist, it will be created first. If the source
		/// directory contains sub directorys, those and their content will also be copied. If the
		/// copy fails at any point in the process, the user is notified of the problem and
		/// an attempt is made to remove the destination directory if the failure happened part
		/// way through the process.
		/// </summary>
		/// <param name="sourcePath">Directory whose contents will be copied</param>
		/// <param name="destinationPath">Destination directory receiving the content of the source directory</param>
		/// <returns>true if successful, otherwise, false.</returns>
		///
		public static bool CopyDirectoryContents(string sourcePath, string destinationPath)
		{
			try
			{
				CopyDirectoryWithException(sourcePath,destinationPath);
			}
			catch (Exception e)
			{
				//Review: generally, it's better if Palaso doesn't undertake to make these kind of UI decisions.
				//I've extracted CopyDirectoryWithException, so as not to mess up whatever client is using this version
				ReportFailedCopyAndCleanUp(e, sourcePath, destinationPath);
				return false;
			}

			return true;
		}

		public static void CopyDirectoryWithException(string sourcePath, string destinationPath)
		{
			if (!Directory.Exists(destinationPath))
				Directory.CreateDirectory(destinationPath);

			// Copy all the files.
			foreach (var filepath in Directory.GetFiles(sourcePath))
			{
				var filename = Path.GetFileName(filepath);
				File.Copy(filepath, Path.Combine(destinationPath, filename));
			}

			// Copy all the sub directorys.
			foreach (var directorypath in Directory.GetDirectories(sourcePath))
			{
				var directoryname = Path.GetFileName(directorypath);
				CopyDirectoryContents(directorypath, Path.Combine(destinationPath, directoryname));
			}
		}

		public static bool AreDirectoriesEquivalent(string dir1, string dir2)
		{
			return AreDirectoriesEquivalent(new DirectoryInfo(dir1), new DirectoryInfo(dir2));
		}

		// Gleaned from http://stackoverflow.com/questions/2281531/how-can-i-compare-directory-paths-in-c
		public static bool AreDirectoriesEquivalent(DirectoryInfo dirInfo1, DirectoryInfo dirInfo2)
		{
			StringComparison comparison;
#if !MONO
			comparison = StringComparison.InvariantCultureIgnoreCase;
#else
			comparison = StringComparison.InvariantCulture;
#endif
			var backslash = new char[] { '\\' }; // added this step because mono does not implicitly convert from char to char[]
			return String.Compare(dirInfo1.FullName.TrimEnd(backslash), dirInfo2.FullName.TrimEnd(backslash), comparison) == 0;
		}

		/// <summary>
		/// Move <paramref name="sourcePath"/> to <paramref name="destinationPath"/>. If src
		/// and dest are on different partitions (e.g., temp and documents are on different
		/// drives) then this method will do a copy followed by a delete. This is in contrast
		/// to Directory.Move which fails if src and dest are on different partitions.<commit_msg>Fix two issues with copying directories<commit_after>﻿// Copyright (c) 2017 SIL International
// This software is licensed under the MIT License (http://opensource.org/licenses/MIT)
using System;
using System.IO;
using System.Linq;
using System.Security.AccessControl;
using System.Security.Principal;
using System.Threading;
using SIL.Reporting;

namespace SIL.IO
{
	public static class DirectoryUtilities
	{

		/// <summary>
		/// Makes a full copy of the specified directory in the system's temporary directory.
		/// If the copy fails at any point in the process, the user is notified of the
		/// problem and an attempt is made to remove the destination directory if the failure
		/// happened part way through the process.
		/// </summary>
		/// <param name="srcDirectory">Directory to copy</param>
		/// <returns>Null if the copy was unsuccessful, otherwise the path to the copied directory</returns>

		public static string CopyDirectoryToTempDirectory(string srcDirectory)
		{
			string dstDirectory;
			return (CopyDirectory(srcDirectory, Path.GetTempPath(), out dstDirectory) ? dstDirectory : null);
		}


		/// <summary>
		/// Makes a copy of the specifed source directory and its contents in the specified
		/// destination directory. The copy has the same directory name as the source, but ends up
		/// as a sub directory of the specified destination directory. The destination directory must
		/// already exist. If the copy fails at any point in the process, the user is notified
		/// of the problem and an attempt is made to remove the destination directory if the
		/// failure happened part way through the process.
		/// </summary>
		/// <param name="srcDirectory">Directory being copied</param>
		/// <param name="dstDirectoryParent">Destination directory where source directory and its contents are copied</param>
		/// <returns>true if successful, otherwise, false.</returns>

		public static bool CopyDirectory(string srcDirectory, string dstDirectoryParent)
		{
			string dstDirectory;
			return CopyDirectory(srcDirectory, dstDirectoryParent, out dstDirectory);
		}


		private static bool CopyDirectory(string srcDirectory, string dstDirectoryParent, out string dstDirectory)
		{
			dstDirectory = Path.Combine(dstDirectoryParent, Path.GetFileName(srcDirectory));

			if (!Directory.Exists(dstDirectoryParent))
			{
				ErrorReport.NotifyUserOfProblem(new DirectoryNotFoundException(dstDirectoryParent + " not found."),
					"{0} was unable to copy the directory {1} to {2}", EntryAssembly.ProductName, srcDirectory, dstDirectoryParent);
				return false;
			}
			if (AreDirectoriesEquivalent(srcDirectory, dstDirectory))
			{
				ErrorReport.NotifyUserOfProblem(new Exception("Cannot copy directory to itself."),
					"{0} was unable to copy the directory {1} to {2}", EntryAssembly.ProductName, srcDirectory, dstDirectoryParent);
				return false;
			}

			return CopyDirectoryContents(srcDirectory, dstDirectory);
		}


		/// <summary>
		/// Copies the specified source directory's contents to the specified destination directory.
		/// If the destination directory does not exist, it will be created first. If the source
		/// directory contains sub directorys, those and their content will also be copied. If the
		/// copy fails at any point in the process, the user is notified of the problem and
		/// an attempt is made to remove the destination directory if the failure happened part
		/// way through the process.
		/// </summary>
		/// <param name="sourcePath">Directory whose contents will be copied</param>
		/// <param name="destinationPath">Destination directory receiving the content of the source directory</param>
		/// <returns>true if successful, otherwise, false.</returns>
		///
		public static bool CopyDirectoryContents(string sourcePath, string destinationPath)
		{
			try
			{
				CopyDirectoryWithException(sourcePath,destinationPath);
			}
			catch (Exception e)
			{
				//Review: generally, it's better if Palaso doesn't undertake to make these kind of UI decisions.
				//I've extracted CopyDirectoryWithException, so as not to mess up whatever client is using this version
				ReportFailedCopyAndCleanUp(e, sourcePath, destinationPath);
				return false;
			}

			return true;
		}

		public static void CopyDirectoryWithException(string sourcePath, string destinationPath, bool overwrite = false)
		{
			if (!Directory.Exists(destinationPath))
				Directory.CreateDirectory(destinationPath);

			// Copy all the files.
			foreach (var filepath in Directory.GetFiles(sourcePath))
			{
				var filename = Path.GetFileName(filepath);
				File.Copy(filepath, Path.Combine(destinationPath, filename), overwrite);
			}

			// Copy all the sub directories.
			foreach (var directorypath in Directory.GetDirectories(sourcePath))
			{
				var directoryname = Path.GetFileName(directorypath);
				CopyDirectoryWithException(directorypath, Path.Combine(destinationPath, directoryname), overwrite);
			}
		}

		public static bool AreDirectoriesEquivalent(string dir1, string dir2)
		{
			return AreDirectoriesEquivalent(new DirectoryInfo(dir1), new DirectoryInfo(dir2));
		}

		// Gleaned from http://stackoverflow.com/questions/2281531/how-can-i-compare-directory-paths-in-c
		public static bool AreDirectoriesEquivalent(DirectoryInfo dirInfo1, DirectoryInfo dirInfo2)
		{
			StringComparison comparison;
#if !MONO
			comparison = StringComparison.InvariantCultureIgnoreCase;
#else
			comparison = StringComparison.InvariantCulture;
#endif
			var backslash = new char[] { '\\' }; // added this step because mono does not implicitly convert from char to char[]
			return String.Compare(dirInfo1.FullName.TrimEnd(backslash), dirInfo2.FullName.TrimEnd(backslash), comparison) == 0;
		}

		/// <summary>
		/// Move <paramref name="sourcePath"/> to <paramref name="destinationPath"/>. If src
		/// and dest are on different partitions (e.g., temp and documents are on different
		/// drives) then this method will do a copy followed by a delete. This is in contrast
		/// to Directory.Move which fails if src and dest are on different partitions.
{'fraction_non_alphanumeric': 0.08954974521760922, 'fraction_numerical': 0.004009690084370562, 'mean_word_length': 4.077184054283291, 'pattern_counts': {'":': 0, '<': 53, '<?xml version=': 0, '>': 53, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '1986913', 'n_tokens_mistral': 3467, 'n_tokens_neox': 3004, 'n_words': 1165}