|
|
@@ -0,0 +1,259 @@
|
|
|
+using System;
|
|
|
+using System.Collections.Generic;
|
|
|
+using System.ComponentModel;
|
|
|
+using System.ComponentModel.Design;
|
|
|
+using System.IO.Abstractions;
|
|
|
+using System.Linq;
|
|
|
+using BO.AppServer.Metadata.Dto;
|
|
|
+using BO.AppServer.Metadata.Enums;
|
|
|
+using BO.Console.Commands.Base;
|
|
|
+using BO.Console.Commands.Workspace.Sync;
|
|
|
+using BO.Console.Commands.Workspace.Sync.Diff;
|
|
|
+using Quadarax.Foundation.Core.Data.Diff;
|
|
|
+using Quadarax.Foundation.Core.IO;
|
|
|
+using Quadarax.Foundation.Core.QConsole;
|
|
|
+using Quadarax.Foundation.Core.QConsole.Argument;
|
|
|
+using Quadarax.Foundation.Core.QConsole.Attributes;
|
|
|
+using Quadarax.Foundation.Core.QConsole.Value;
|
|
|
+using Quadarax.Foundation.Core.Value;
|
|
|
+
|
|
|
+namespace BO.Console.Commands.Workspace
|
|
|
+{
|
|
|
+ [CommandDefinition]
|
|
|
+ internal class WorkspaceSyncCommand : AbstractWorkspaceCommand
|
|
|
+ {
|
|
|
+ #region *** Constants ***
|
|
|
+
|
|
|
+ private const string CS_NEW_IN = ".in";
|
|
|
+ private const string CS_NEW_OUT = ".out";
|
|
|
+
|
|
|
+ private const string CS_NOTES = "Directory structure:\n" +
|
|
|
+ "["+CS_NEW_IN + "]\t- new artifacts directory for Input type\n" +
|
|
|
+ "\t\t<doc_name>.<artifact_name>.<artifact_ext> - new artifacts file\n" +
|
|
|
+ "\t\t...\n" +
|
|
|
+ "[" +CS_NEW_OUT + "]\t- new artifacts directory for Output type\n" +
|
|
|
+ "[<documentId:8>.<document_name_normalized>]\t- document directory\n" +
|
|
|
+ "\t\t<arifact_id>.<artifact_type>.<artifact_name>.<artifaxt_ext> - artifact file\n" +
|
|
|
+ "\t\t...\n" +
|
|
|
+ "...\n" +
|
|
|
+ "\n" +
|
|
|
+ "Process sequence:\n" +
|
|
|
+ "0. If init flag set, sync clean directory\n" +
|
|
|
+ "1. Get workspace documents with artifacts -> srv_arts\n" +
|
|
|
+ "2. Get sync directory documents with artifacts (skip not well formatted, process incomming also) -> loac_arts\n" +
|
|
|
+ "3. Resolve differences srv_arts and loc_ars -> changed_arts\n" +
|
|
|
+ "4. Push changes changed_arts to database (if delete flag set removes documents in database)\n" +
|
|
|
+ "5. Pull changes changed_arts from database (if delete flag set removes documents in sync directory)\n" +
|
|
|
+ "6. Cleanup incommings\n" +
|
|
|
+ "7. If repeat set go to step 1.\n";
|
|
|
+
|
|
|
+
|
|
|
+ private const string CS_CMD_WRKSPSYNC_NAME = "workspace_sync";
|
|
|
+ private const string CS_CMD_WRKSPSYNC_DESCR = "Download or upload documents from local directory from/to workspace. Can be run as contignous process.";
|
|
|
+
|
|
|
+ private const string CS_ARG_NAME_DIR = "directory";
|
|
|
+ private const string CS_ARG_HINT_DIR = "<sync_directory>";
|
|
|
+ private const string CS_ARG_DESC_DIR = "Defines sync directory, if not defined workspace directory will be created as workspace name.";
|
|
|
+
|
|
|
+ private const string CS_ARG_NAME_WD = "repeat";
|
|
|
+ private const string CS_ARG_HINT_WD = "<is_repeat>";
|
|
|
+ private const string CS_ARG_DESC_WD = "Flag if defined, enables contignous monitoring on sync directory for changes. If not defined process will be run once.";
|
|
|
+
|
|
|
+ private const string CS_ARG_NAME_DELETE = "delete";
|
|
|
+ private const string CS_ARG_HINT_DELETE = "<is_delete_allowed>";
|
|
|
+ private const string CS_ARG_DESC_DELETE = "Flag if defined, deletes documents in application server when document is deleted in sync directory.";
|
|
|
+
|
|
|
+ private const string CS_ARG_NAME_INIT = "init";
|
|
|
+ private const string CS_ARG_HINT_INIT = "<is_initialization>";
|
|
|
+ private const string CS_ARG_DESC_INIT = "Flag if defined, reset sync directory during initialization (all content in sync directory will be deleted on start).";
|
|
|
+
|
|
|
+ private const string CS_ARG_NAME_INT = "interval";
|
|
|
+ private const string CS_ARG_HINT_INT = "<repeat_interval>";
|
|
|
+ private const string CS_ARG_DESC_INT = "Timespan interval between two iterations. Default is 00:30:00.";
|
|
|
+
|
|
|
+
|
|
|
+ #endregion
|
|
|
+
|
|
|
+ #region *** Properties ***
|
|
|
+ public override string Name => CS_CMD_WRKSPSYNC_NAME;
|
|
|
+ public override string Description => CS_CMD_WRKSPSYNC_DESCR;
|
|
|
+
|
|
|
+ public override string Notes => CS_NOTES;
|
|
|
+
|
|
|
+
|
|
|
+ private string SyncDir { get; set; }
|
|
|
+ private bool IsRepeat { get; set; }
|
|
|
+ private bool IsInit { get; set; }
|
|
|
+ private bool IsDeleteAllowed { get; set; }
|
|
|
+
|
|
|
+ private TimeSpan Interval { get; set; }
|
|
|
+ #endregion
|
|
|
+
|
|
|
+
|
|
|
+ #region *** Constructor ***
|
|
|
+
|
|
|
+ public WorkspaceSyncCommand(Engine engine) : base(engine)
|
|
|
+ {
|
|
|
+ }
|
|
|
+
|
|
|
+ #endregion
|
|
|
+
|
|
|
+ #region *** EXECUTE ***
|
|
|
+
|
|
|
+ protected override Result OnExecute()
|
|
|
+ {
|
|
|
+
|
|
|
+
|
|
|
+ bool isExitSignal = false;
|
|
|
+ int nCnt = 0;
|
|
|
+ while (!isExitSignal)
|
|
|
+ {
|
|
|
+ if (!IsRepeat)
|
|
|
+ isExitSignal = true;
|
|
|
+
|
|
|
+ var workspace = Client.GetWorkspaceAsync(WorkspaceIdentificationTypeEnum.ApiKey, ApiKey).Result;
|
|
|
+ if (workspace == null)
|
|
|
+ return new Result(new Exception($"Workspace with API-KEY: {ApiKey} not found."));
|
|
|
+ var documents = Client.GetWorkspaceDocumentsAsync(DocumentsScopeEnums.All, ApiKey, ApiKeyPassword, UserName).Result;
|
|
|
+ var dirInfo = OpenDirectory(workspace);
|
|
|
+
|
|
|
+ var factory = new DocumentEntryComparer();
|
|
|
+ foreach (var dir in dirInfo.EnumerateDirectories())
|
|
|
+ {
|
|
|
+ if (string.Equals(dir.Name, CS_NEW_IN, StringComparison.CurrentCultureIgnoreCase))
|
|
|
+ {
|
|
|
+ foreach (var file in dir.GetFiles())
|
|
|
+ factory.AddLocalDocumentNew(file.Name,true);
|
|
|
+
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (string.Equals(dir.Name, CS_NEW_OUT, StringComparison.CurrentCultureIgnoreCase))
|
|
|
+ {
|
|
|
+ foreach (var file in dir.GetFiles())
|
|
|
+ factory.AddLocalDocumentNew(file.Name,false);
|
|
|
+
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (factory.ValidateDocumentDirectory(dir.Name))
|
|
|
+ {
|
|
|
+
|
|
|
+ foreach (var file in dir.GetFiles())
|
|
|
+ {
|
|
|
+
|
|
|
+ if (factory.ValidateArtifactFileName(file.Name))
|
|
|
+ {
|
|
|
+ factory.AddLocalDocument(dir.Name, file.Name, file.Length);
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ WriteWarning($"Invalid artifact file '{file.Name}' name format! Expects '<arifact_id>.<artifact_type>.<artifact_name>.<artifaxt_ext>'. Skip");
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ WriteWarning($"Invalid document directory '{dir.Name}' name format! Expects '<documentId:8>.<document_name_normalized>'. Skip");
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ foreach (var document in documents)
|
|
|
+ {
|
|
|
+ factory.AddRemoteDocument(document);
|
|
|
+ }
|
|
|
+
|
|
|
+ var diff = factory.GetComparator();
|
|
|
+ var locals = diff.CompareLeftSide().Where(x => x.CompareState != DiffEntry.CompareStateEnum.Equals);
|
|
|
+ foreach (var local in locals)
|
|
|
+ {
|
|
|
+ var item = local.GetItem<DocumentItem>();
|
|
|
+ if (local.CompareState == DiffEntry.CompareStateEnum.CurrentNull ||
|
|
|
+ local.CompareState == DiffEntry.CompareStateEnum.CurrentSmaller)
|
|
|
+ {
|
|
|
+ WriteInfo($"Artifact '{item.ArtName}' will be downloaded");
|
|
|
+ }
|
|
|
+ else if (local.CompareState == DiffEntry.CompareStateEnum.CurrentGrater ||
|
|
|
+ local.CompareState == DiffEntry.CompareStateEnum.OtherNull)
|
|
|
+ {
|
|
|
+ WriteInfo($"Artifact '{item.ArtName}' will be uploaded");
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ return new Result();
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ private IDirectoryInfo OpenDirectory(WorkspaceRDto workspace)
|
|
|
+ {
|
|
|
+ if (workspace == null)
|
|
|
+ throw new ArgumentNullException(nameof(workspace));
|
|
|
+
|
|
|
+ if (string.IsNullOrEmpty(SyncDir))
|
|
|
+ {
|
|
|
+ // Current dir
|
|
|
+ SyncDir = FileSystem.Path.Combine(FileSystem.Directory.GetCurrentDirectory(), FileUtils.SanitizedFileName(FileSystem, workspace.Name));
|
|
|
+ }
|
|
|
+
|
|
|
+ if (IsInit)
|
|
|
+ {
|
|
|
+ if (FileSystem.Directory.Exists(SyncDir))
|
|
|
+ {
|
|
|
+ FileSystem.Directory.Delete(SyncDir,true);
|
|
|
+ WriteDebugInfo($"Directory '{SyncDir}' deleted due 'init' mode.");
|
|
|
+ }
|
|
|
+
|
|
|
+ IsInit = false;
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ if (!FileSystem.Directory.Exists(SyncDir))
|
|
|
+ {
|
|
|
+ FileSystem.Directory.CreateDirectory(SyncDir);
|
|
|
+ WriteInfo($"Sync directory '{SyncDir}' created.");
|
|
|
+
|
|
|
+ var inDir = FileSystem.Path.Combine(SyncDir, CS_NEW_IN);
|
|
|
+ FileSystem.Directory.CreateDirectory(inDir);
|
|
|
+ WriteDebugInfo($"Directory '{inDir}' created.");
|
|
|
+ inDir = FileSystem.Path.Combine(SyncDir, CS_NEW_OUT);
|
|
|
+ FileSystem.Directory.CreateDirectory(inDir);
|
|
|
+ WriteDebugInfo($"Directory '{inDir}' created.");
|
|
|
+ }
|
|
|
+
|
|
|
+ var di = FileSystem.DirectoryInfo.FromDirectoryName(SyncDir);
|
|
|
+ WriteCaption($"Sync directory '{SyncDir}' opened.");
|
|
|
+ return di;
|
|
|
+ }
|
|
|
+
|
|
|
+ #endregion
|
|
|
+
|
|
|
+ #region *** Private operations ***
|
|
|
+
|
|
|
+ protected override void OnValidateArguments()
|
|
|
+ {
|
|
|
+ base.OnValidateArguments();
|
|
|
+
|
|
|
+ SyncDir = GetArgumentValueOrDefault<string>(CS_ARG_NAME_DIR);
|
|
|
+ IsRepeat = GetArgumentValueOrDefault<bool>(CS_ARG_NAME_WD);
|
|
|
+ IsInit = GetArgumentValueOrDefault<bool>(CS_ARG_NAME_INIT);
|
|
|
+ IsDeleteAllowed = GetArgumentValueOrDefault<bool>(CS_ARG_NAME_DELETE);
|
|
|
+ Interval = GetArgumentValueOrDefault<TimeSpan>(CS_ARG_NAME_INT);
|
|
|
+ }
|
|
|
+
|
|
|
+ protected override IEnumerable<AbstractArgument> OnSetupArguments()
|
|
|
+ {
|
|
|
+ var args = base.OnSetupArguments().ToList();
|
|
|
+ args.Add(new NamedArgument(CS_ARG_NAME_DIR, CS_ARG_DESC_DIR, CS_ARG_HINT_DIR, TypeValuesEnum.String, string.Empty, false));
|
|
|
+ args.Add(new FlagArgument(CS_ARG_NAME_WD, CS_ARG_DESC_WD, CS_ARG_HINT_WD, false));
|
|
|
+ args.Add(new NamedArgument(CS_ARG_NAME_INT, CS_ARG_DESC_INT, CS_ARG_HINT_INT, TypeValuesEnum.TimeSpan, TimeSpan.FromSeconds(30).ToString(), false));
|
|
|
+ args.Add(new FlagArgument(CS_ARG_NAME_DELETE, CS_ARG_DESC_DELETE, CS_ARG_HINT_DELETE, false));
|
|
|
+ args.Add(new FlagArgument(CS_ARG_NAME_INIT, CS_ARG_DESC_INIT, CS_ARG_HINT_INIT, false));
|
|
|
+ return args;
|
|
|
+ }
|
|
|
+
|
|
|
+ #endregion
|
|
|
+ }
|
|
|
+}
|