diff --git a/ShareFileSnapIn/SyncSfItem.cs b/ShareFileSnapIn/SyncSfItem.cs index 25bc51a..70d81f7 100644 --- a/ShareFileSnapIn/SyncSfItem.cs +++ b/ShareFileSnapIn/SyncSfItem.cs @@ -1,838 +1,864 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using System.Management.Automation; -using ShareFile.Api; -using ShareFile.Api.Models; -using System.IO; -using ShareFile.Api.Client; -using ShareFile.Api.Client.Transfers; -using ShareFile.Api.Client.FileSystem; -using ShareFile.Api.Client.Transfers.Uploaders; -using System.Threading; -using ShareFile.Api.Powershell.Parallel; -using ShareFile.Api.Powershell.Log; -using ShareFile.Api.Client.Requests; -using ShareFile.Api.Client.Exceptions; -using ShareFile.Api.Powershell.Properties; -using System.Collections.ObjectModel; -using System.Text.RegularExpressions; - -namespace ShareFile.Api.Powershell -{ - /// - /// Sync-SFItem command to sync/copy/move items between local and sharefile locations - /// - [Cmdlet("Sync", Noun, SupportsShouldProcess = true, DefaultParameterSetName = SetNameAttr)] - public class SyncSfItem : PSCmdlet - { - #region Local Variables - - private const string Noun = "SfItem"; - - // different Parameter Set Names for different type of behaviors of command - private const string SetNameAttr = "SetAttr"; - private const string SetNamePos = "SetPos"; - private const string SetNameHelp = "SetHelp"; - - private FileSupport FileSupport; - - // Keep track of abandone/resume functionality if disconnected meanwhile any operation - private Resume.ResumeSupport ResumeSupport { get; set; } - - #endregion - - #region Command Arguments - - /// - /// Sharefile location - /// - [Alias("SF", "ShareFile")] - [Parameter(Mandatory = true, ParameterSetName = SetNameAttr)] - [Parameter(Mandatory = true, Position = 0, ParameterSetName = SetNamePos)] - public string ShareFilePath { get; set; } - - /// - /// Local location - /// - [Alias("L", "Local", "Path")] - [Parameter(Position = 1, ParameterSetName = SetNamePos)] - [Parameter(Mandatory = false, ParameterSetName = SetNameAttr)] - public string LocalPath { get; set; } - - /// - /// Download param flag - /// - [Alias("Down", "D")] - [Parameter()] - public SwitchParameter Download { get; set; } - - /// - /// Upload param flag - /// - [Alias("Up", "U")] - [Parameter()] - public SwitchParameter Upload { get; set; } - - /// - /// Synchronize param flag - /// - [Alias("Sync", "S")] - [Parameter()] - public SwitchParameter Synchronize { get; set; } - - /// - /// Recursive/Deep param flag - /// - [Alias("Deep", "R")] - [Parameter()] - public SwitchParameter Recursive { get; set; } - - /// - /// Create root folder param flag - /// - [Alias("CR")] - [Parameter()] - public SwitchParameter CreateRoot { get; set; } - - /// - /// Overwrite param flag - /// - [Alias("O")] - [Parameter()] - public SwitchParameter OverWrite { get; set; } - - /// - /// Move (Cut+Paste) param flag - /// - [Alias("M")] - [Parameter()] - public SwitchParameter Move { get; set; } - - /// - /// Keep Folders param flag (in case of move just delete files from source location) - /// - [Alias("KF")] - [Parameter()] - public SwitchParameter KeepFolders { get; set; } - - /// - /// Strict param flag - /// - [Parameter()] - public SwitchParameter Strict { get; set; } - - /// - /// Version param flag - /// - [Alias("Version", "V")] - [Parameter(ParameterSetName = SetNameHelp, Mandatory = false)] - public SwitchParameter Help { get; set; } - - /// - /// Details/Description text to upload to Sharefile location - /// - [Parameter()] - public String Details { get; set; } - - #endregion - - /// - /// Sync-SFItem Command is invoked - /// - protected override void ProcessRecord() - { - FileSupport = new FileSupport(TestMethod); - ProviderInfo providerInfo; - PSDriveInfo driveInfo; - - if (Upload && Download) - { - throw new PSArgumentException("Provide only one switch to Upload or Download the files"); - } - - if (!string.IsNullOrEmpty(ShareFilePath)) - { - if (!Upload && !Download) - { - throw new PSArgumentException("Upload or Download switch must be specified"); - } - - if (string.IsNullOrEmpty(LocalPath) || string.IsNullOrEmpty(LocalPath.Trim())) - { - // use current user directory location if Local path is not specified in arguments - LocalPath = this.SessionState.Path.CurrentFileSystemLocation.Path; - } - - if (Synchronize) - { - Recursive = true; - } - - if (Move) - { - Recursive = true; - } - - ShareFilePath = ShareFilePath.Trim(); - LocalPath = LocalPath.Trim(); - - ActionType actionType = OverWrite ? ActionType.Force : (Synchronize ? ActionType.Sync : ActionType.None); - - int transactionId = new Random((int)DateTime.Now.Ticks).Next(); - - // if current user directory is local storage and ShareFile path provided withouth drive letter then append the drive letter with sharefile location - if (this.SessionState.Path.CurrentLocation.Provider.ImplementingType != typeof(ShareFileProvider) && ShareFilePath.IndexOf(":") < 1) - { - Collection providerDrives = this.SessionState.Drive.GetAll();// ForProvider("ShareFile"); - foreach (PSDriveInfo driveObj in providerDrives) - { - if (driveObj.Provider.ImplementingType == typeof(ShareFileProvider)) - { - if (ShareFilePath.StartsWith("/") || ShareFilePath.StartsWith(@"\")) - { - ShareFilePath = ShareFilePath.Substring(1); - } - string sfDrive = String.Format("{0}:/", driveObj.Name); - ShareFilePath = Path.Combine(sfDrive, ShareFilePath); - break; - } - } - } - - var sourcePath = Upload ? LocalPath : ShareFilePath; - - // it will resolve paths if wildcards are used e.g. "D:\\*.txt" then get paths of all files with txt extension from D:\\ location - var resolvedPaths = this.GetResolvedProviderPathFromPSPath(sourcePath, out providerInfo); - - if (Download) - { - this.SessionState.Path.GetUnresolvedProviderPathFromPSPath(sourcePath, out providerInfo, out driveInfo); - - var client = ((ShareFileDriveInfo)driveInfo).Client; - - StartDownload(client, driveInfo, resolvedPaths, actionType); - } - else - { - var unresolvedPath = this.SessionState.Path.GetUnresolvedProviderPathFromPSPath(ShareFilePath, out providerInfo, out driveInfo); - - var client = ((ShareFileDriveInfo)driveInfo).Client; - Item targetItem = ShareFileProvider.GetShareFileItem((ShareFileDriveInfo)driveInfo, unresolvedPath); - - if (targetItem == null && !unresolvedPath.StartsWith(String.Format(@"\{0}\", Utility.DefaultSharefileFolder))) - { - string updatedPath = String.Format(@"\{0}\{1}", Utility.DefaultSharefileFolder, unresolvedPath); - targetItem = ShareFileProvider.GetShareFileItem((ShareFileDriveInfo)driveInfo, updatedPath, null, null); - } - //else if (targetItem == null) - //{ - // targetItem = ShareFileProvider.GetShareFileItem((ShareFileDriveInfo)driveInfo, ShareFilePath, null, null); - //} - - if (targetItem == null) - { - throw new FileNotFoundException("Destination path not found on ShareFile server."); - } - - // if user didn't specify the Sharefile HomeFolder in path then appending in path - // e.g. if user tries sf:/Folder1 as sharefile target then resolve this path to sf:/My Files & Folders/Folder1 - if ((targetItem as Folder).Info.IsAccountRoot == true) - { - string updatedPath = String.Format(@"\{0}\{1}", Utility.DefaultSharefileFolder, unresolvedPath); - targetItem = ShareFileProvider.GetShareFileItem((ShareFileDriveInfo)driveInfo, updatedPath, null, null); - } - - StartUpload(client, transactionId, targetItem, resolvedPaths, actionType); - } - - WriteObject("Sync operation successfully completed."); - } - - if (Help) - { - WriteObject("SFCLI version " + Resources.Version); - } - } - - #region Download & methods - - /// - /// Start download process - /// - private void StartDownload(ShareFileClient client, PSDriveInfo driveInfo, ICollection resolvedPaths, ActionType actionType) - { - int transactionId = new Random((int)DateTime.Now.Ticks).Next(); - - ActionManager actionManager = new ActionManager(this, string.Empty); - bool firstIteration = true; - - var shareFileItems = new List(); - foreach (string path in resolvedPaths) - { - var item = Utility.ResolveShareFilePath(driveInfo, path); - - if (item == null) - { - throw new FileNotFoundException(string.Format("Source path '{0}' not found on ShareFile server.", path)); - } - - var target = new DirectoryInfo(LocalPath); - - if (!target.Exists) - { - throw new Exception(string.Format("Destination '{0}' path not found on local drive.", LocalPath)); - } - - // if create root folder flag is specified then create a container folder first - if (firstIteration && CreateRoot) - { - Models.Folder parentFolder = client.Items.GetParent(item.url).Execute() as Folder; - - target = CreateLocalFolder(target, parentFolder); - firstIteration = false; - } - - if (item is Models.Folder) - { - // if user downloading the root drive then download its root folders - if ((item as Folder).Info.IsAccountRoot.GetValueOrDefault()) - { - var children = client.Items.GetChildren(item.url) - .Select("Id") - .Select("url") - .Select("FileName") - .Select("FileSizeBytes") - .Select("Hash") - .Select("Info") - .Execute(); - - foreach (var child in children.Feed) - { - if (child is Models.Folder) - { - DownloadRecursive(client, transactionId, child, target, actionType); - - shareFileItems.Add(child); - } - } - } - else - { - DownloadRecursive(client, transactionId, item, target, actionType); - - shareFileItems.Add(item); - } - } - else if (item is Models.File) - { - DownloadAction downloadAction = new DownloadAction(FileSupport, client, transactionId, (Models.File)item, target, actionType); - actionManager.AddAction(downloadAction); - - shareFileItems.Add(item); - } - } - - actionManager.Execute(); - - // if strict flag is specified then also clean the target files which are not in source - if (Strict) - { - var target = new DirectoryInfo(LocalPath); - var directories = target.GetDirectories(); - - foreach (string path in resolvedPaths) - { - var item = Utility.ResolveShareFilePath(driveInfo, path); - - if (item is Folder) - { - foreach (DirectoryInfo directory in directories) - { - if (directory.Name.Equals(item.Name)) - { - DeleteLocalStrictRecursive(client, item, directory); - break; - } - } - } - } - } - - // on move remove source files - if (Move) - { - foreach(var item in shareFileItems) - { - DeleteShareFileItemRecursive(client, item, Recursive); - } - } - } - - /// - /// Download all items recursively - /// - private void DownloadRecursive(ShareFileClient client, int downloadId, Models.Item source, DirectoryInfo target, ActionType actionType) - { - if (source is Models.Folder) - { - var subdir = CreateLocalFolder(target, source as Folder); - - var children = client.Items.GetChildren(source.url) - .Select("Id") - .Select("url") - .Select("FileName") - .Select("FileSizeBytes") - .Select("Hash") - .Select("Info") - .Execute(); - - if (children != null) - { - (source as Folder).Children = children.Feed; - - ActionManager actionManager = new ActionManager(this, source.Name); - - foreach (var child in children.Feed) - { - child.Parent = source; - - if (child is Models.Folder && Recursive) - { - DownloadRecursive(client, downloadId, child, subdir, actionType); - } - else if (child is Models.File) - { - DownloadAction downloadAction = new DownloadAction(FileSupport, client, downloadId, (Models.File)child, subdir, actionType); - actionManager.AddAction(downloadAction); - } - } - - actionManager.Execute(); - } - } - } - - /// - /// Delete the Sharefile items if Move flag is used - /// - private void DeleteShareFileItemRecursive(ShareFileClient client, Models.Item source, bool deleteFolder) - { - if (source is Models.Folder) - { - var children = (source as Folder).Children; - var childFiles = children.OfType(); - var childFolders = children.OfType(); - - RemoveShareFileItems(client, source, childFiles); - - if (Recursive) - { - foreach (var childFolder in childFolders) - { - DeleteShareFileItemRecursive(client, childFolder, !KeepFolders); - } - } - - if (deleteFolder) - { - if(!HasChildren(client, source as Models.Folder)) - { - RemoveShareFileItem(client, source); - } - } - } - - if (source is Models.File) - { - RemoveShareFileItem(client, source); - } - } - - /// - /// Clean the target folders in case of Strict flag - /// - private void DeleteLocalStrictRecursive(ShareFileClient client, Models.Item source, DirectoryInfo target) - { - var directories = target.GetDirectories(); - var children = client.Items.GetChildren(source.url).Execute(); - - foreach (DirectoryInfo directory in directories) - { - bool found = false; - - foreach (var child in children.Feed) - { - if (child is Models.Folder && child.Name.Equals(directory.Name)) - { - DeleteLocalStrictRecursive(client, child, directory); - found = true; - break; - } - } - - if (!found) - { - RemoveLocalItem(directory); - } - } - - var files = target.GetFiles(); - - foreach (FileInfo file in files) - { - bool found = false; - foreach (var child in children.Feed) - { - if (child is Models.File && child.Name.Equals(file.Name)) - { - found = true; - break; - } - } - - if (!found) - { - RemoveLocalItem(file); - } - } - } - - /// - /// Create local folder - /// - private DirectoryInfo CreateLocalFolder(DirectoryInfo target, Folder source) - { - string sourceFolderName = string.Empty; - - // if source is user's Home/Root folder then specify default name - if (source.Info.IsAHomeFolder == true && source.Info.IsAStartFolder == true) - { - sourceFolderName = Utility.DefaultSharefileFolder; - } - else - { - sourceFolderName = source.FileName; - } - var subdirCheck = new DirectoryInfo(System.IO.Path.Combine(target.FullName, sourceFolderName)); - - if (subdirCheck.Exists) - { - if (Synchronize) - return subdirCheck; - - if (!OverWrite) - throw new IOException("Path " + subdirCheck.FullName + " already exists. Use -Overwrite to ignore"); - } - - return target.CreateSubdirectory(sourceFolderName); - } - - #endregion - - #region Upload & methods - - /// - /// Start Upload to Sharefile location - /// - private void StartUpload(ShareFileClient client, int uploadId, Models.Item target, ICollection resolvedPaths, ActionType actionType) - { - int transactionId = new Random((int)DateTime.Now.Ticks).Next(); - - ActionManager actionManager = new ActionManager(this, string.Empty); - bool firstIteration = true; - - foreach (string path in resolvedPaths) - { - FileAttributes attr = System.IO.File.GetAttributes(path); - FileSystemInfo source = ((attr & FileAttributes.Directory) == FileAttributes.Directory) ? new DirectoryInfo(path) : source = new FileInfo(path); - - // create an extra parent folder if CreateRoot flag is specified on target location - if (firstIteration && CreateRoot) - { - DirectoryInfo parentFolder = Directory.GetParent(path); - var newFolder = new Models.Folder() { Name = parentFolder.Name }; - target = client.Items.CreateFolder(target.url, newFolder, OverWrite, false).Execute(); - firstIteration = false; - } - - if (source is DirectoryInfo) - { - UploadRecursive(client, uploadId, source, target, actionType); - } - else - { - IAction uploadAction = new UploadAction(FileSupport, client, source, target, Details, actionType); - actionManager.AddAction(uploadAction); - } - } - - actionManager.Execute(); - - if (Strict) - { - foreach (string path in resolvedPaths) - { - FileAttributes attr = System.IO.File.GetAttributes(path); - if ((attr & FileAttributes.Directory) == FileAttributes.Directory) - { - DirectoryInfo source = new DirectoryInfo(path); - - var children = client.Items.GetChildren(target.url).Execute(); - - foreach (var child in children.Feed) - { - if (child is Models.Folder && child.Name.Equals(source.Name)) - { - DeleteSharefileStrictRecursive(client, source as DirectoryInfo, child); - break; - } - } - } - } - } - - if (Move) - { - foreach (string path in resolvedPaths) - { - FileAttributes attr = System.IO.File.GetAttributes(path); - FileSystemInfo source = ((attr & FileAttributes.Directory) == FileAttributes.Directory) ? new DirectoryInfo(path) : source = new FileInfo(path); - - DeleteLocalItemRecursive(source, !KeepFolders); - } - } - } - - /// - /// Upload contents recursively - /// - private void UploadRecursive(ShareFileClient client, int uploadId, FileSystemInfo source, Models.Item target, ActionType actionType) - { - if (source is DirectoryInfo) - { - var newFolder = new Models.Folder() { Name = source.Name }; - bool isExist = false; - - if (Synchronize) - { - try - { - string path = String.Format("/{0}", source.Name); - Item item = null; - try - { - item = client.Items.ByPath(target.url, path).Execute(); - } - catch (ODataException e) - { - if (e.Code != System.Net.HttpStatusCode.NotFound) - { - throw e; - } - } - - if (item != null && item is Folder) - { - isExist = true; - newFolder = (Folder)item; - } - } - catch { } - } - - if (!isExist) - { - newFolder = client.Items.CreateFolder(target.url, newFolder, OverWrite, false).Execute(); - } - - ActionManager actionManager = new ActionManager(this, source.Name); - - foreach (var fsInfo in ((DirectoryInfo)source).EnumerateFileSystemInfos()) - { - if (fsInfo is DirectoryInfo && Recursive) - { - UploadRecursive(client, uploadId, fsInfo, newFolder, actionType); - } - else if (fsInfo is FileInfo) - { - IAction uploadAction = new UploadAction(FileSupport, client, fsInfo, newFolder, Details, actionType); - actionManager.AddAction(uploadAction); - } - } - - actionManager.Execute(); - } - } - - /// - /// Delete sharefile contents on Strict flag to make exact copy of source - /// - private void DeleteSharefileStrictRecursive(ShareFileClient client, DirectoryInfo source, Item target) - { - var children = client.Items.GetChildren(target.url).Execute(); - var directories = source.GetDirectories(); - var files = source.GetFiles(); - - foreach (var child in children.Feed) - { - bool found = false; - if (child is Models.Folder) - { - foreach (DirectoryInfo directory in directories) - { - if (directory.Name.Equals(child.Name)) - { - DeleteSharefileStrictRecursive(client, directory, child); - found = true; - } - } - } - else if (child is Models.File) - { - foreach (FileInfo file in files) - { - if (file.Name.Equals(child.Name)) - { - found = true; - break; - } - } - } - - if (!found) - { - RemoveShareFileItem(client, child); - } - } - - //foreach (DirectoryInfo directory in directories) - //{ - // foreach (var child in children.Feed) - // { - // if (child is Models.Folder && child.Name.Equals(directory.Name)) - // { - // DeleteLocalStrictRecursive(client, child, directory); - // break; - // } - // } - //} - - - - //foreach (FileInfo file in files) - //{ - // bool found = false; - // foreach (var child in children.Feed) - // { - // if (child is Models.File && child.Name.Equals(file.Name)) - // { - // found = true; - // } - // } - - // if (!found) - // { - // RemoveLocalItem(file); - // } - //} - } - - /// - /// Delete local items if Move flag is specified - /// - private void DeleteLocalItemRecursive(FileSystemInfo source, bool deleteFolder) - { - if (source is DirectoryInfo) - { - foreach (var child in (source as DirectoryInfo).EnumerateFileSystemInfos()) - { - if (child is FileInfo || Recursive) - { - DeleteLocalItemRecursive(child, !KeepFolders); - } - } - } - - if (source is FileInfo || deleteFolder) - { - RemoveLocalItem(source); - } - } - - #endregion - - #region Utility Methods - - /// - /// Remove sharefile item - /// - private bool RemoveShareFileItem(ShareFileClient client, Item item) - { - Query query = new Query(client); - - query.HttpMethod = "DELETE"; - query.Id(item.Id); - query.From("Items"); - - try - { - client.Execute(query); - } - catch - { - return false; - } - - return true; - } - - private bool RemoveShareFileItems(ShareFileClient client, Item parentFolder, IEnumerable items) - { - try - { - client.Items.BulkDelete(parentFolder.url, items.Select(x => x.Id)).Execute(); - return true; - } - catch - { - return false; - } - } - - private bool HasChildren(ShareFileClient client, Folder folder) - { - try - { - var children = client.Items.GetChildren(folder.url).Top(1).Execute(); - return children.count > 0; - } - catch - { - return false; - } - } - - /// - /// Remove local item - /// - private bool RemoveLocalItem(FileSystemInfo item) - { - item.Delete(); - - return true; - } - - /// - /// Delegate method (which will be used for Marking file status of completed files) - /// - private void TestMethod(String fileName) - { - - } - - #endregion - } -} +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Management.Automation; +using ShareFile.Api; +using ShareFile.Api.Models; +using System.IO; +using ShareFile.Api.Client; +using ShareFile.Api.Client.Transfers; +using ShareFile.Api.Client.FileSystem; +using ShareFile.Api.Client.Transfers.Uploaders; +using System.Threading; +using ShareFile.Api.Powershell.Parallel; +using ShareFile.Api.Powershell.Log; +using ShareFile.Api.Client.Requests; +using ShareFile.Api.Client.Exceptions; +using ShareFile.Api.Powershell.Properties; +using System.Collections.ObjectModel; +using System.Text.RegularExpressions; + +namespace ShareFile.Api.Powershell +{ + /// + /// Sync-SFItem command to sync/copy/move items between local and sharefile locations + /// + [Cmdlet("Sync", Noun, SupportsShouldProcess = true, DefaultParameterSetName = SetNameAttr)] + public class SyncSfItem : PSCmdlet + { + #region Local Variables + + private const string Noun = "SfItem"; + + // different Parameter Set Names for different type of behaviors of command + private const string SetNameAttr = "SetAttr"; + private const string SetNamePos = "SetPos"; + private const string SetNameHelp = "SetHelp"; + + private FileSupport FileSupport; + + // Keep track of abandone/resume functionality if disconnected meanwhile any operation + private Resume.ResumeSupport ResumeSupport { get; set; } + + #endregion + + #region Command Arguments + + /// + /// Sharefile location + /// + [Alias("SF", "ShareFile")] + [Parameter(Mandatory = true, ParameterSetName = SetNameAttr)] + [Parameter(Mandatory = true, Position = 0, ParameterSetName = SetNamePos)] + public string ShareFilePath { get; set; } + + /// + /// Local location + /// + [Alias("L", "Local", "Path")] + [Parameter(Position = 1, ParameterSetName = SetNamePos)] + [Parameter(Mandatory = false, ParameterSetName = SetNameAttr)] + public string LocalPath { get; set; } + + /// + /// Download param flag + /// + [Alias("Down", "D")] + [Parameter()] + public SwitchParameter Download { get; set; } + + /// + /// Upload param flag + /// + [Alias("Up", "U")] + [Parameter()] + public SwitchParameter Upload { get; set; } + + /// + /// Synchronize param flag + /// + [Alias("Sync", "S")] + [Parameter()] + public SwitchParameter Synchronize { get; set; } + + /// + /// Recursive/Deep param flag + /// + [Alias("Deep", "R")] + [Parameter()] + public SwitchParameter Recursive { get; set; } + + /// + /// Create root folder param flag + /// + [Alias("CR")] + [Parameter()] + public SwitchParameter CreateRoot { get; set; } + + /// + /// Overwrite param flag + /// + [Alias("O")] + [Parameter()] + public SwitchParameter OverWrite { get; set; } + + /// + /// Move (Cut+Paste) param flag + /// + [Alias("M")] + [Parameter()] + public SwitchParameter Move { get; set; } + + /// + /// Keep Folders param flag (in case of move just delete files from source location) + /// + [Alias("KF")] + [Parameter()] + public SwitchParameter KeepFolders { get; set; } + + /// + /// Strict param flag + /// + [Parameter()] + public SwitchParameter Strict { get; set; } + + /// + /// Version param flag + /// + [Alias("Version", "V")] + [Parameter(ParameterSetName = SetNameHelp, Mandatory = false)] + public SwitchParameter Help { get; set; } + + /// + /// Details/Description text to upload to Sharefile location + /// + [Parameter()] + public String Details { get; set; } + + #endregion + + /// + /// Sync-SFItem Command is invoked + /// + protected override void ProcessRecord() + { + FileSupport = new FileSupport(TestMethod); + ProviderInfo providerInfo; + PSDriveInfo driveInfo; + + if (Upload && Download) + { + throw new PSArgumentException("Provide only one switch to Upload or Download the files"); + } + + if (!string.IsNullOrEmpty(ShareFilePath)) + { + if (!Upload && !Download) + { + throw new PSArgumentException("Upload or Download switch must be specified"); + } + + if (string.IsNullOrEmpty(LocalPath) || string.IsNullOrEmpty(LocalPath.Trim())) + { + // use current user directory location if Local path is not specified in arguments + LocalPath = this.SessionState.Path.CurrentFileSystemLocation.Path; + } + // KA - Fix. It makes no sense why Synchronize or Move should force recursive. This is not the behavior of the original sfcli.exe. + /* + if (Synchronize) + { + Recursive = true; + } + + if (Move) + { + Recursive = true; + }*/ + + ShareFilePath = ShareFilePath.Trim(); + LocalPath = LocalPath.Trim(); + + ActionType actionType = OverWrite ? ActionType.Force : (Synchronize ? ActionType.Sync : ActionType.None); + + int transactionId = new Random((int)DateTime.Now.Ticks).Next(); + + // if current user directory is local storage and ShareFile path provided withouth drive letter then append the drive letter with sharefile location + if (this.SessionState.Path.CurrentLocation.Provider.ImplementingType != typeof(ShareFileProvider) && ShareFilePath.IndexOf(":") < 1) + { + Collection providerDrives = this.SessionState.Drive.GetAll();// ForProvider("ShareFile"); + foreach (PSDriveInfo driveObj in providerDrives) + { + if (driveObj.Provider.ImplementingType == typeof(ShareFileProvider)) + { + if (ShareFilePath.StartsWith("/") || ShareFilePath.StartsWith(@"\")) + { + ShareFilePath = ShareFilePath.Substring(1); + } + string sfDrive = String.Format("{0}:/", driveObj.Name); + ShareFilePath = Path.Combine(sfDrive, ShareFilePath); + break; + } + } + } + + var sourcePath = Upload ? LocalPath : ShareFilePath; + + // it will resolve paths if wildcards are used e.g. "D:\\*.txt" then get paths of all files with txt extension from D:\\ location + var resolvedPaths = this.GetResolvedProviderPathFromPSPath(sourcePath, out providerInfo); + + if (Download) + { + this.SessionState.Path.GetUnresolvedProviderPathFromPSPath(sourcePath, out providerInfo, out driveInfo); + + var client = ((ShareFileDriveInfo)driveInfo).Client; + + StartDownload(client, driveInfo, resolvedPaths, actionType); + } + else + { + var unresolvedPath = this.SessionState.Path.GetUnresolvedProviderPathFromPSPath(ShareFilePath, out providerInfo, out driveInfo); + + var client = ((ShareFileDriveInfo)driveInfo).Client; + Item targetItem = ShareFileProvider.GetShareFileItem((ShareFileDriveInfo)driveInfo, unresolvedPath); + + if (targetItem == null && !unresolvedPath.StartsWith(String.Format(@"\{0}\", Utility.DefaultSharefileFolder))) + { + string updatedPath = String.Format(@"\{0}\{1}", Utility.DefaultSharefileFolder, unresolvedPath); + targetItem = ShareFileProvider.GetShareFileItem((ShareFileDriveInfo)driveInfo, updatedPath, null, null); + } + //else if (targetItem == null) + //{ + // targetItem = ShareFileProvider.GetShareFileItem((ShareFileDriveInfo)driveInfo, ShareFilePath, null, null); + //} + + if (targetItem == null) + { + throw new FileNotFoundException("Destination path not found on ShareFile server."); + } + + // if user didn't specify the Sharefile HomeFolder in path then appending in path + // e.g. if user tries sf:/Folder1 as sharefile target then resolve this path to sf:/My Files & Folders/Folder1 + if ((targetItem as Folder).Info.IsAccountRoot == true) + { + string updatedPath = String.Format(@"\{0}\{1}", Utility.DefaultSharefileFolder, unresolvedPath); + targetItem = ShareFileProvider.GetShareFileItem((ShareFileDriveInfo)driveInfo, updatedPath, null, null); + } + + StartUpload(client, transactionId, targetItem, resolvedPaths, actionType); + } + + WriteObject("Sync operation successfully completed."); + } + + if (Help) + { + WriteObject("SFCLI version " + Resources.Version); + } + } + + #region Download & methods + + /// + /// Start download process + /// + private void StartDownload(ShareFileClient client, PSDriveInfo driveInfo, ICollection resolvedPaths, ActionType actionType) + { + int transactionId = new Random((int)DateTime.Now.Ticks).Next(); + + ActionManager actionManager = new ActionManager(this, string.Empty); + bool firstIteration = true; + + var shareFileItems = new List(); + foreach (string path in resolvedPaths) + { + var item = Utility.ResolveShareFilePath(driveInfo, path); + + if (item == null) + { + throw new FileNotFoundException(string.Format("Source path '{0}' not found on ShareFile server.", path)); + } + + var target = new DirectoryInfo(LocalPath); + + if (!target.Exists) + { + throw new Exception(string.Format("Destination '{0}' path not found on local drive.", LocalPath)); + } + + // if create root folder flag is specified then create a container folder first + // KA - Fix. When CreateRoot is used and the source item is a folder we should NOT create the parent of that folder. + // This possibly fixes another unknown scenario when the root folder is specified as the source. + if (firstIteration && CreateRoot && !(item is Models.Folder)) + { + Models.Folder parentFolder = client.Items.GetParent(item.url).Execute() as Folder; + + target = CreateLocalFolder(target, parentFolder); + firstIteration = false; + } + + if (item is Models.Folder) + { + // if user downloading the root drive then download its root folders + // KA - Fix. We should also process only subfolders and files if CreateRoot is not used and source is a folder. + // This prevents DownloadRecursive from creating the parent folder in conditions where CreateRoot is not specified. + // Code adapted from DownloadRecursive function and processes both file sources and folder sources appropriately now. + // if ((item as Folder).Info.IsAccountRoot.GetValueOrDefault()) + if ((item as Folder).Info.IsAccountRoot.GetValueOrDefault() || !CreateRoot) + { + var children = client.Items.GetChildren(item.url) + .Select("Id") + .Select("url") + .Select("FileName") + .Select("FileSizeBytes") + .Select("Hash") + .Select("Info") + .Execute(); + + if (children != null) + { + (item as Folder).Children = children.Feed; + + foreach (var child in children.Feed) + { + child.Parent = item; + + if (child is Models.Folder && ((item as Folder).Info.IsAccountRoot.GetValueOrDefault() || Recursive)) + { + DownloadRecursive(client, transactionId, child, target, actionType); + + shareFileItems.Add(child); + } + else if (child is Models.File) + { + DownloadAction downloadAction = new DownloadAction(FileSupport, client, transactionId, (Models.File)child, target, actionType); + actionManager.AddAction(downloadAction); + } + } + if (!(item as Folder).Info.IsAccountRoot.GetValueOrDefault()) { shareFileItems.Add(item); } + } + } + else + { + DownloadRecursive(client, transactionId, item, target, actionType); + + shareFileItems.Add(item); + } + } + else if (item is Models.File) + { + DownloadAction downloadAction = new DownloadAction(FileSupport, client, transactionId, (Models.File)item, target, actionType); + actionManager.AddAction(downloadAction); + + shareFileItems.Add(item); + } + } + + actionManager.Execute(); + + // if strict flag is specified then also clean the target files which are not in source + if (Strict) + { + var target = new DirectoryInfo(LocalPath); + var directories = target.GetDirectories(); + + foreach (string path in resolvedPaths) + { + var item = Utility.ResolveShareFilePath(driveInfo, path); + + if (item is Folder) + { + foreach (DirectoryInfo directory in directories) + { + if (directory.Name.Equals(item.Name)) + { + DeleteLocalStrictRecursive(client, item, directory); + break; + } + } + } + } + } + + // on move remove source files + if (Move) + { + foreach(var item in shareFileItems) + { + // KA - Fix. Replaced 'Recursive' with '!KeepFolders'. This prevents "Move" from deleting folders even when KeepFolders is specified. + // This fixes the bug that causes the source folder to be deleted in all scenarios where "Move" is specified and it does not contain children. + // DeleteShareFileItemRecursive(client, item, Recursive); + DeleteShareFileItemRecursive(client, item, !KeepFolders); + } + } + } + + /// + /// Download all items recursively + /// + private void DownloadRecursive(ShareFileClient client, int downloadId, Models.Item source, DirectoryInfo target, ActionType actionType) + { + if (source is Models.Folder) + { + var subdir = CreateLocalFolder(target, source as Folder); + + var children = client.Items.GetChildren(source.url) + .Select("Id") + .Select("url") + .Select("FileName") + .Select("FileSizeBytes") + .Select("Hash") + .Select("Info") + .Execute(); + + if (children != null) + { + (source as Folder).Children = children.Feed; + + ActionManager actionManager = new ActionManager(this, source.Name); + + foreach (var child in children.Feed) + { + child.Parent = source; + + if (child is Models.Folder && Recursive) + { + DownloadRecursive(client, downloadId, child, subdir, actionType); + } + else if (child is Models.File) + { + DownloadAction downloadAction = new DownloadAction(FileSupport, client, downloadId, (Models.File)child, subdir, actionType); + actionManager.AddAction(downloadAction); + } + } + + actionManager.Execute(); + } + } + } + + /// + /// Delete the Sharefile items if Move flag is used + /// + private void DeleteShareFileItemRecursive(ShareFileClient client, Models.Item source, bool deleteFolder) + { + if (source is Models.Folder) + { + var children = (source as Folder).Children; + var childFiles = children.OfType(); + var childFolders = children.OfType(); + + RemoveShareFileItems(client, source, childFiles); + + if (Recursive) + { + foreach (var childFolder in childFolders) + { + // KA - Fix. If we pass in 'deleteFolder' I don't know why we don't continue to use that here + // This keeps the behavior of DeleteShareFileItemRecursive consistent with the deleteFolder flag + // DeleteShareFileItemRecursive(client, childFolder, !KeepFolders); + DeleteShareFileItemRecursive(client, childFolder, deleteFolder); + } + } + + if (deleteFolder) + { + if(!HasChildren(client, source as Models.Folder)) + { + RemoveShareFileItem(client, source); + } + } + } + + if (source is Models.File) + { + RemoveShareFileItem(client, source); + } + } + + /// + /// Clean the target folders in case of Strict flag + /// + private void DeleteLocalStrictRecursive(ShareFileClient client, Models.Item source, DirectoryInfo target) + { + var directories = target.GetDirectories(); + var children = client.Items.GetChildren(source.url).Execute(); + + foreach (DirectoryInfo directory in directories) + { + bool found = false; + + foreach (var child in children.Feed) + { + if (child is Models.Folder && child.Name.Equals(directory.Name)) + { + DeleteLocalStrictRecursive(client, child, directory); + found = true; + break; + } + } + + if (!found) + { + RemoveLocalItem(directory); + } + } + + var files = target.GetFiles(); + + foreach (FileInfo file in files) + { + bool found = false; + foreach (var child in children.Feed) + { + if (child is Models.File && child.Name.Equals(file.Name)) + { + found = true; + break; + } + } + + if (!found) + { + RemoveLocalItem(file); + } + } + } + + /// + /// Create local folder + /// + private DirectoryInfo CreateLocalFolder(DirectoryInfo target, Folder source) + { + string sourceFolderName = string.Empty; + + // if source is user's Home/Root folder then specify default name + if (source.Info.IsAHomeFolder == true && source.Info.IsAStartFolder == true) + { + sourceFolderName = Utility.DefaultSharefileFolder; + } + else + { + sourceFolderName = source.FileName; + } + var subdirCheck = new DirectoryInfo(System.IO.Path.Combine(target.FullName, sourceFolderName)); + + if (subdirCheck.Exists) + { + if (Synchronize) + return subdirCheck; + + if (!OverWrite) + throw new IOException("Path " + subdirCheck.FullName + " already exists. Use -Overwrite to ignore"); + } + + return target.CreateSubdirectory(sourceFolderName); + } + + #endregion + + #region Upload & methods + + /// + /// Start Upload to Sharefile location + /// + private void StartUpload(ShareFileClient client, int uploadId, Models.Item target, ICollection resolvedPaths, ActionType actionType) + { + int transactionId = new Random((int)DateTime.Now.Ticks).Next(); + + ActionManager actionManager = new ActionManager(this, string.Empty); + bool firstIteration = true; + + foreach (string path in resolvedPaths) + { + FileAttributes attr = System.IO.File.GetAttributes(path); + FileSystemInfo source = ((attr & FileAttributes.Directory) == FileAttributes.Directory) ? new DirectoryInfo(path) : source = new FileInfo(path); + + // create an extra parent folder if CreateRoot flag is specified on target location + if (firstIteration && CreateRoot) + { + DirectoryInfo parentFolder = Directory.GetParent(path); + var newFolder = new Models.Folder() { Name = parentFolder.Name }; + target = client.Items.CreateFolder(target.url, newFolder, OverWrite, false).Execute(); + firstIteration = false; + } + + if (source is DirectoryInfo) + { + UploadRecursive(client, uploadId, source, target, actionType); + } + else + { + IAction uploadAction = new UploadAction(FileSupport, client, source, target, Details, actionType); + actionManager.AddAction(uploadAction); + } + } + + actionManager.Execute(); + + if (Strict) + { + foreach (string path in resolvedPaths) + { + FileAttributes attr = System.IO.File.GetAttributes(path); + if ((attr & FileAttributes.Directory) == FileAttributes.Directory) + { + DirectoryInfo source = new DirectoryInfo(path); + + var children = client.Items.GetChildren(target.url).Execute(); + + foreach (var child in children.Feed) + { + if (child is Models.Folder && child.Name.Equals(source.Name)) + { + DeleteSharefileStrictRecursive(client, source as DirectoryInfo, child); + break; + } + } + } + } + } + + if (Move) + { + foreach (string path in resolvedPaths) + { + FileAttributes attr = System.IO.File.GetAttributes(path); + FileSystemInfo source = ((attr & FileAttributes.Directory) == FileAttributes.Directory) ? new DirectoryInfo(path) : source = new FileInfo(path); + + DeleteLocalItemRecursive(source, !KeepFolders); + } + } + } + + /// + /// Upload contents recursively + /// + private void UploadRecursive(ShareFileClient client, int uploadId, FileSystemInfo source, Models.Item target, ActionType actionType) + { + if (source is DirectoryInfo) + { + var newFolder = new Models.Folder() { Name = source.Name }; + bool isExist = false; + + if (Synchronize) + { + try + { + string path = String.Format("/{0}", source.Name); + Item item = null; + try + { + item = client.Items.ByPath(target.url, path).Execute(); + } + catch (ODataException e) + { + if (e.Code != System.Net.HttpStatusCode.NotFound) + { + throw e; + } + } + + if (item != null && item is Folder) + { + isExist = true; + newFolder = (Folder)item; + } + } + catch { } + } + + if (!isExist) + { + newFolder = client.Items.CreateFolder(target.url, newFolder, OverWrite, false).Execute(); + } + + ActionManager actionManager = new ActionManager(this, source.Name); + + foreach (var fsInfo in ((DirectoryInfo)source).EnumerateFileSystemInfos()) + { + if (fsInfo is DirectoryInfo && Recursive) + { + UploadRecursive(client, uploadId, fsInfo, newFolder, actionType); + } + else if (fsInfo is FileInfo) + { + IAction uploadAction = new UploadAction(FileSupport, client, fsInfo, newFolder, Details, actionType); + actionManager.AddAction(uploadAction); + } + } + + actionManager.Execute(); + } + } + + /// + /// Delete sharefile contents on Strict flag to make exact copy of source + /// + private void DeleteSharefileStrictRecursive(ShareFileClient client, DirectoryInfo source, Item target) + { + var children = client.Items.GetChildren(target.url).Execute(); + var directories = source.GetDirectories(); + var files = source.GetFiles(); + + foreach (var child in children.Feed) + { + bool found = false; + if (child is Models.Folder) + { + foreach (DirectoryInfo directory in directories) + { + if (directory.Name.Equals(child.Name)) + { + DeleteSharefileStrictRecursive(client, directory, child); + found = true; + } + } + } + else if (child is Models.File) + { + foreach (FileInfo file in files) + { + if (file.Name.Equals(child.Name)) + { + found = true; + break; + } + } + } + + if (!found) + { + RemoveShareFileItem(client, child); + } + } + + //foreach (DirectoryInfo directory in directories) + //{ + // foreach (var child in children.Feed) + // { + // if (child is Models.Folder && child.Name.Equals(directory.Name)) + // { + // DeleteLocalStrictRecursive(client, child, directory); + // break; + // } + // } + //} + + + + //foreach (FileInfo file in files) + //{ + // bool found = false; + // foreach (var child in children.Feed) + // { + // if (child is Models.File && child.Name.Equals(file.Name)) + // { + // found = true; + // } + // } + + // if (!found) + // { + // RemoveLocalItem(file); + // } + //} + } + + /// + /// Delete local items if Move flag is specified + /// + private void DeleteLocalItemRecursive(FileSystemInfo source, bool deleteFolder) + { + if (source is DirectoryInfo) + { + foreach (var child in (source as DirectoryInfo).EnumerateFileSystemInfos()) + { + if (child is FileInfo || Recursive) + { + DeleteLocalItemRecursive(child, !KeepFolders); + } + } + } + + if (source is FileInfo || deleteFolder) + { + RemoveLocalItem(source); + } + } + + #endregion + + #region Utility Methods + + /// + /// Remove sharefile item + /// + private bool RemoveShareFileItem(ShareFileClient client, Item item) + { + Query query = new Query(client); + + query.HttpMethod = "DELETE"; + query.Id(item.Id); + query.From("Items"); + + try + { + client.Execute(query); + } + catch + { + return false; + } + + return true; + } + + private bool RemoveShareFileItems(ShareFileClient client, Item parentFolder, IEnumerable items) + { + try + { + client.Items.BulkDelete(parentFolder.url, items.Select(x => x.Id)).Execute(); + return true; + } + catch + { + return false; + } + } + + private bool HasChildren(ShareFileClient client, Folder folder) + { + try + { + var children = client.Items.GetChildren(folder.url).Top(1).Execute(); + return children.count > 0; + } + catch + { + return false; + } + } + + /// + /// Remove local item + /// + private bool RemoveLocalItem(FileSystemInfo item) + { + item.Delete(); + + return true; + } + + /// + /// Delegate method (which will be used for Marking file status of completed files) + /// + private void TestMethod(String fileName) + { + + } + + #endregion + } +}