| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334 |
- using System;
- using System.Collections.Generic;
- using System.ComponentModel;
- using System.ComponentModel.Design;
- using System.IO;
- using System.IO.Abstractions;
- using System.Linq;
- using System.Threading;
- 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.IO.Extensions;
- 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;
- using Quadarax.Foundation.Core.Value.Extensions;
- 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:8>.<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.";
- private const string CS_ARG_DESC_USER = "If defined, gets APIKey and password from defined user default workspace, otherwise use APIkey from argument or configuration.";
- #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;
- if (!string.IsNullOrEmpty(UserName))
- {
- WriteDebugInfo($"Getting default workspace info for user '{UserName}'");
- var workspaces = Client.GetWorkspacesAsync(WorkspaceScopeEnums.MyDefault, UserName).Result;
- if (!workspaces.Any())
- throw new Exception($"No default workspace for user '{UserName}' set.");
- var userWorkspace = workspaces.First();
- WriteInfo($"User has default workspace named '{userWorkspace.Name}' [{userWorkspace.Id}] (Created:{userWorkspace.Audit.Created.ToFileShortDateTimeString()}, Changed:{userWorkspace.Audit.Changed?.ToFileShortDateTimeString()})");
- WriteCaption($"Using APIKey: {userWorkspace.ApiKeyNormalized}");
- SetApiKey(userWorkspace.ApiKeyNormalized, userWorkspace.Apipassword);
- }
- var workspace = Client.GetWorkspaceAsync(WorkspaceIdentificationTypeEnum.ApiKey, ApiKey).Result;
- if (workspace == null)
- return new Result(new Exception($"Workspace with API-KEY: {ApiKey} not found."));
- var dirInfo = OpenDirectory(workspace);
- var iteration = 0;
- while (!isExitSignal)
- {
- if (!IsRepeat)
- isExitSignal = true;
- WriteInfo($"Iteration {iteration} started.");
- var documents = Client.GetWorkspaceDocumentsAsync(DocumentsScopeEnums.All, ApiKey, ApiKeyPassword, UserName).Result;
-
- 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.CompareRightSide().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 uploaded");
-
- }
- else if (local.CompareState == DiffEntry.CompareStateEnum.CurrentGrater ||
- local.CompareState == DiffEntry.CompareStateEnum.OtherNull)
- {
- WriteInfo($"Artifact '{item.ArtName}' will be downloaded");
- DownloadArtifact(dirInfo, item);
- }
- }
- if (IsRepeat)
- Thread.CurrentThread.Join(Interval);
- }
-
- 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;
- }
- private void DownloadArtifact(IDirectoryInfo outputDir, DocumentItem artifactItem)
- {
- if (outputDir == null)
- throw new ArgumentNullException(nameof(outputDir));
- if (artifactItem == null)
- throw new ArgumentNullException(nameof(artifactItem));
- var currentDir = outputDir;
- using (var artFs = Client.GetWorkspaceDocumentContentAsync(ApiKey, ApiKeyPassword,
- artifactItem.DocId.GetValueOrDefault(), artifactItem.ArtId.GetValueOrDefault(),string.Empty).Result)
- {
- long fileSize = 0;
- var fileName = GetArtifactPhysicalFileName(artifactItem);
- var dirName = GetDocumentPhysicalDirName(artifactItem);
- currentDir = currentDir.GetDirectory(dirName) ?? outputDir.CreateSubdirectory(dirName);
- using (var outFs = currentDir.CreateFile(fileName))
- {
- //CopyStream(artFs, outFs);
- artFs.CopyTo(outFs);
- outFs.Flush();
- fileSize = outFs.Length;
- }
- WriteInfo($"{fileName} downloaded [{artifactItem.ArtLength}/{fileSize} bytes]");
- }
- }
- public static void CopyStream(Stream input, Stream output)
- {
- byte[] buffer = new byte[16 * 1024];
- int read;
- while ((read = input.Read(buffer, 0, buffer.Length)) > 0)
- {
- output.Write(buffer, 0, read);
- }
- }
- private string GetArtifactPhysicalFileName(DocumentItem artifactItem)
- {
- //<arifact_id:8>.<artifact_type>.<artifact_name>.<artifaxt_ext> - artifact file
- return $"{artifactItem.ArtId.GetValueOrDefault():D8}.{artifactItem.ArtType}.{FileUtils.SanitizedFileName(FileSystem,artifactItem.ArtName)}{artifactItem.ArtExt}";
- }
- private string GetDocumentPhysicalDirName(DocumentItem documentItem)
- {
- //[<documentId:8>.<document_name_normalized>]
- return $"{documentItem.DocId.GetValueOrDefault():D8}.{FileUtils.SanitizedFileName(FileSystem, documentItem.DocName)}";
- }
- #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));
- AppendUserArgument(args, CS_ARG_DESC_USER);
- return args;
- }
- #endregion
- }
- }
|