Assignment for RMIT Mixed Reality in 2020
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

242 lines
8.0 KiB

  1. /************************************************************************************
  2. Copyright : Copyright (c) Facebook Technologies, LLC and its affiliates. All rights reserved.
  3. Licensed under the Oculus SDK License Version 3.4.1 (the "License");
  4. you may not use the Oculus SDK except in compliance with the License,
  5. which is provided at the time of installation or download, or which
  6. otherwise accompanies this software in either electronic or hard copy form.
  7. You may obtain a copy of the License at
  8. https://developer.oculus.com/licenses/sdk-3.4.1
  9. Unless required by applicable law or agreed to in writing, the Oculus SDK
  10. distributed under the License is distributed on an "AS IS" BASIS,
  11. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. See the License for the specific language governing permissions and
  13. limitations under the License.
  14. ************************************************************************************/
  15. using System.Collections.Generic;
  16. using System.Diagnostics.CodeAnalysis;
  17. using System.IO;
  18. using System.Linq;
  19. using System.Text.RegularExpressions;
  20. using System;
  21. public class DirectorySyncer
  22. {
  23. public delegate void SyncResultDelegate(SyncResult syncResult);
  24. public readonly string Source;
  25. public readonly string Target;
  26. public SyncResultDelegate WillPerformOperations;
  27. private readonly Regex _ignoreExpression;
  28. // helper classes to simplify transition beyond .NET runtime 3.5
  29. public abstract class CancellationToken
  30. {
  31. protected abstract bool _IsCancellationRequested();
  32. public virtual bool IsCancellationRequested
  33. {
  34. get { return _IsCancellationRequested(); }
  35. }
  36. public void ThrowIfCancellationRequested()
  37. {
  38. if (IsCancellationRequested)
  39. {
  40. throw new Exception("Operation Cancelled");
  41. }
  42. }
  43. public static readonly CancellationToken None = new CancellationTokenNone();
  44. private class CancellationTokenNone : CancellationToken
  45. {
  46. protected override bool _IsCancellationRequested()
  47. {
  48. return false;
  49. }
  50. }
  51. }
  52. public class CancellationTokenSource : CancellationToken
  53. {
  54. private bool _isCancelled;
  55. protected override bool _IsCancellationRequested()
  56. {
  57. return _isCancelled;
  58. }
  59. public void Cancel()
  60. {
  61. _isCancelled = true;
  62. }
  63. public CancellationToken Token
  64. {
  65. get { return this; }
  66. }
  67. }
  68. private static string EnsureTrailingDirectorySeparator(string path)
  69. {
  70. return path.EndsWith("" + Path.DirectorySeparatorChar)
  71. ? path
  72. : path + Path.DirectorySeparatorChar;
  73. }
  74. private static string CheckedDirectory(string nameInExceptionText, string directory)
  75. {
  76. directory = Path.GetFullPath(directory);
  77. if (!Directory.Exists(directory))
  78. {
  79. throw new ArgumentException(string.Format("{0} is not a valid directory for argument ${1}", directory,
  80. nameInExceptionText));
  81. }
  82. return EnsureTrailingDirectorySeparator(directory);
  83. }
  84. public DirectorySyncer(string source, string target, string ignoreRegExPattern = null)
  85. {
  86. Source = CheckedDirectory("source", source);
  87. Target = CheckedDirectory("target", target);
  88. if (Source.StartsWith(Target, StringComparison.OrdinalIgnoreCase) ||
  89. Target.StartsWith(Source, StringComparison.OrdinalIgnoreCase))
  90. {
  91. throw new ArgumentException(string.Format("Paths must not contain each other (source: {0}, target: {1}",
  92. Source, Target));
  93. }
  94. ignoreRegExPattern = ignoreRegExPattern ?? "^$";
  95. _ignoreExpression = new Regex(ignoreRegExPattern, RegexOptions.IgnoreCase);
  96. }
  97. public class SyncResult
  98. {
  99. public readonly IEnumerable<string> Created;
  100. public readonly IEnumerable<string> Updated;
  101. public readonly IEnumerable<string> Deleted;
  102. public SyncResult(IEnumerable<string> created, IEnumerable<string> updated, IEnumerable<string> deleted)
  103. {
  104. Created = created;
  105. Updated = updated;
  106. Deleted = deleted;
  107. }
  108. }
  109. public bool RelativeFilePathIsRelevant(string relativeFilename)
  110. {
  111. return !_ignoreExpression.IsMatch(relativeFilename);
  112. }
  113. public bool RelativeDirectoryPathIsRelevant(string relativeDirName)
  114. {
  115. // Since our ignore patterns look at file names, they may contain trailing path separators
  116. // In order for paths to match those rules, we add a path separator here
  117. return !_ignoreExpression.IsMatch(EnsureTrailingDirectorySeparator(relativeDirName));
  118. }
  119. private HashSet<string> RelevantRelativeFilesBeneathDirectory(string path, CancellationToken cancellationToken)
  120. {
  121. return new HashSet<string>(Directory.GetFiles(path, "*", SearchOption.AllDirectories)
  122. .TakeWhile((s) => !cancellationToken.IsCancellationRequested)
  123. .Select(p => PathHelper.MakeRelativePath(path, p)).Where(RelativeFilePathIsRelevant));
  124. }
  125. private HashSet<string> RelevantRelativeDirectoriesBeneathDirectory(string path,
  126. CancellationToken cancellationToken)
  127. {
  128. return new HashSet<string>(Directory.GetDirectories(path, "*", SearchOption.AllDirectories)
  129. .TakeWhile((s) => !cancellationToken.IsCancellationRequested)
  130. .Select(p => PathHelper.MakeRelativePath(path, p)).Where(RelativeDirectoryPathIsRelevant));
  131. }
  132. public SyncResult Synchronize()
  133. {
  134. return Synchronize(CancellationToken.None);
  135. }
  136. private void DeleteOutdatedFilesFromTarget(SyncResult syncResult, CancellationToken cancellationToken)
  137. {
  138. var outdatedFiles = syncResult.Updated.Union(syncResult.Deleted);
  139. foreach (var fileName in outdatedFiles)
  140. {
  141. File.Delete(Path.Combine(Target, fileName));
  142. cancellationToken.ThrowIfCancellationRequested();
  143. }
  144. }
  145. [SuppressMessage("ReSharper", "ParameterTypeCanBeEnumerable.Local")]
  146. private void DeleteOutdatedEmptyDirectoriesFromTarget(HashSet<string> sourceDirs, HashSet<string> targetDirs,
  147. CancellationToken cancellationToken)
  148. {
  149. var deleted = targetDirs.Except(sourceDirs).OrderByDescending(s => s);
  150. // By sorting in descending order above, we delete leaf-first,
  151. // this is simpler than collapsing the list above (which would also allow us to run these ops in parallel).
  152. // Assumption is that there are few empty folders to delete
  153. foreach (var dir in deleted)
  154. {
  155. Directory.Delete(Path.Combine(Target, dir));
  156. cancellationToken.ThrowIfCancellationRequested();
  157. }
  158. }
  159. [SuppressMessage("ReSharper", "ParameterTypeCanBeEnumerable.Local")]
  160. private void CreateRelevantDirectoriesAtTarget(HashSet<string> sourceDirs, HashSet<string> targetDirs,
  161. CancellationToken cancellationToken)
  162. {
  163. var created = sourceDirs.Except(targetDirs);
  164. foreach (var dir in created)
  165. {
  166. Directory.CreateDirectory(Path.Combine(Target, dir));
  167. cancellationToken.ThrowIfCancellationRequested();
  168. }
  169. }
  170. private void MoveRelevantFilesToTarget(SyncResult syncResult, CancellationToken cancellationToken)
  171. {
  172. // step 3: we move all new files to target
  173. var newFiles = syncResult.Created.Union(syncResult.Updated);
  174. foreach (var fileName in newFiles)
  175. {
  176. var sourceFileName = Path.Combine(Source, fileName);
  177. var destFileName = Path.Combine(Target, fileName);
  178. // target directory exists due to step CreateRelevantDirectoriesAtTarget()
  179. File.Move(sourceFileName, destFileName);
  180. cancellationToken.ThrowIfCancellationRequested();
  181. }
  182. }
  183. public SyncResult Synchronize(CancellationToken cancellationToken)
  184. {
  185. var sourceDirs = RelevantRelativeDirectoriesBeneathDirectory(Source, cancellationToken);
  186. var targetDirs = RelevantRelativeDirectoriesBeneathDirectory(Target, cancellationToken);
  187. var sourceFiles = RelevantRelativeFilesBeneathDirectory(Source, cancellationToken);
  188. var targetFiles = RelevantRelativeFilesBeneathDirectory(Target, cancellationToken);
  189. var created = sourceFiles.Except(targetFiles).OrderBy(s => s).ToList();
  190. var updated = sourceFiles.Intersect(targetFiles).OrderBy(s => s).ToList();
  191. var deleted = targetFiles.Except(sourceFiles).OrderBy(s => s).ToList();
  192. var syncResult = new SyncResult(created, updated, deleted);
  193. if (WillPerformOperations != null)
  194. {
  195. WillPerformOperations.Invoke(syncResult);
  196. }
  197. DeleteOutdatedFilesFromTarget(syncResult, cancellationToken);
  198. DeleteOutdatedEmptyDirectoriesFromTarget(sourceDirs, targetDirs, cancellationToken);
  199. CreateRelevantDirectoriesAtTarget(sourceDirs, targetDirs, cancellationToken);
  200. MoveRelevantFilesToTarget(syncResult, cancellationToken);
  201. return syncResult;
  202. }
  203. }