diff --git a/Documentation/TaskDocs.md b/Documentation/TaskDocs.md index 92218b74..d0bc5379 100644 --- a/Documentation/TaskDocs.md +++ b/Documentation/TaskDocs.md @@ -785,7 +785,40 @@ Create installer -## CssCompress (JavaScript.CssCompress) +## ContentFilter +### Description +Filters an Item list with a regular expression applied to the file's content. + Output list contains items from Input list that matched given expression +### Example +Matches from WebServices those files using Web Service Extension, i.e. containing the text Microsoft.Web.Services. + + + + + + + + + + + + + + + +* * * + + + + + + + + + + + + ## CssCompress (JavaScript.CssCompress) ### Description MSBuild task to minimize the size of a css file. ### No example given @@ -2707,7 +2740,65 @@ Search for a version number and update the revision. -## FxCop +## FileSync +### Description +Snycs files or folders in the filesystem or via FTP. +FTP urls are of the form protocol://username:password@server:port/path?ftp-options +Protocol can be either ftp or ftps +Ftp-options are delimited by a & and are of the form parameter or parameter=value +Available ftp-options are: +- passive: for passive ftp mode +- active: for active ftp mode +- connections: for the number of concurrent connections +- zip: for compression if the server supports it +- raw: for no compression +- old: to use the most compatible ftp command set for old ftp servers") +- time: for the server's time offset to utc time. If you omit time, sync + will autodetect the time offset (The MDTM command must be supported by the server for this). +- crc: for auto CRC32 data transfer checksum check. +- md5: for auto MD5 data transfer checksum check. +- sha: for auto SHA1 data transfer checksum check. +- The connections option reqires an int value that limits the maximum concurent connections. +- The default options are passive&connections=10 + +The Mode parameter can be set to either Update, Clone or Add. +- In Update mode sync will keep newer files in the destination. +- In Clone mode, sync will cerate an exact copy of the source. +- In Add mode sync will add all files that are not present or outdated in the destination, but not delete any + outdated files or overwrite newer files. + +The Exclude parameter specifies an exclude pattern for files to exclude. +The LogFile parameter specifies a logfile FileSync writes to. +The Verbose and Silen parameters enable Verbose / Silent mode. + +### Example +Sync the folder C:\Folder with the folder location on FTP server ftp.test.org using user and password as credentials, +and using 10 parallel connections. + + + + +* * * + + + + + + + + + + + + + + + + + + + ## FxCop ### Description Uses FxCop to analyse managed code assemblies and reports on their design best-practice compliance. diff --git a/Source/MSBuild.Community.Tasks/AssemblyInfo.cs b/Source/MSBuild.Community.Tasks/AssemblyInfo.cs index 18731864..c9c15fec 100644 --- a/Source/MSBuild.Community.Tasks/AssemblyInfo.cs +++ b/Source/MSBuild.Community.Tasks/AssemblyInfo.cs @@ -1,11 +1,9 @@ -#region Copyright © 2005 Paul Welter. All rights reserved. +#region Copyright © 2005 Paul Welter. All rights reserved. /* -Copyright © 2005 Paul Welter. All rights reserved. - +Copyright © 2005 Paul Welter. All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: - 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 2. Redistributions in binary form must reproduce the above copyright @@ -13,7 +11,6 @@ 2. Redistributions in binary form must reproduce the above copyright documentation and/or other materials provided with the distribution. 3. The name of the author may not be used to endorse or promote products derived from this software without specific prior written permission. - THIS SOFTWARE IS PROVIDED BY THE AUTHOR "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. @@ -40,757 +37,718 @@ OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. -namespace MSBuild.Community.Tasks -{ - /// - /// Generates an AssemblyInfo files - /// - /// - /// Generates a common version file. - /// - /// ]]> - /// Generates a complete version file. - /// - /// ]]> - /// Generates a complete version file for C++/CLI. - /// - /// ]]> - /// - public class AssemblyInfo : Task - { - #region Constants - - /// - /// The default value of . - /// The value is "AssemblyInfo.cs". - /// - public const string DEFAULT_OUTPUT_FILE = @"AssemblyInfo.cs"; - - private const string CSharp = "CS"; - private const string VisualBasic = "VB"; - private const string CPP = "CPP"; - - private const string CppCodeProviderAssembly = "CppCodeProvider, "+ - "Version=8.0.0.0, " + - "Culture=neutral, " + - "PublicKeyToken=b03f5f7f11d50a3a, " + - "processorArchitecture=MSIL"; - - private const string CppCodeProviderType = "Microsoft.VisualC.CppCodeProvider"; - - private const string CLSCompliantName = "CLSCompliant"; - private const string ComVisibleName = "ComVisible"; - private const string GuidName = "Guid"; - private const string AssemblyTitleName = "AssemblyTitle"; - private const string AssemblyDescriptionName = "AssemblyDescription"; - private const string AssemblyConfigurationName = "AssemblyConfiguration"; - private const string AssemblyCompanyName = "AssemblyCompany"; - private const string AssemblyProductName = "AssemblyProduct"; - private const string AssemblyCopyrightName = "AssemblyCopyright"; - private const string AssemblyTrademarkName = "AssemblyTrademark"; - private const string AssemblyCultureName = "AssemblyCulture"; - private const string AssemblyVersionName = "AssemblyVersion"; - private const string AssemblyFileVersionName = "AssemblyFileVersion"; - private const string AssemblyInformationalVersionName = "AssemblyInformationalVersion"; - private const string AssemblyKeyFileName = "AssemblyKeyFile"; - private const string AssemblyKeyNameName = "AssemblyKeyName"; - private const string AssemblyDelaySignName = "AssemblyDelaySign"; - private const string SkipVerificationName = "SkipVerification"; - private const string UnmanagedCodeName = "UnmanagedCode"; - private const string InternalsVisibleToName = "InternalsVisibleTo"; - private const string AllowPartiallyTrustedCallersName = "AllowPartiallyTrustedCallers"; - - #endregion Constants - - #region Type Constructor - - static AssemblyInfo() - { - // C# - _codeLangMapping["csharp"] = CSharp; - _codeLangMapping["cs"] = CSharp; - _codeLangMapping["c#"] = CSharp; - _codeLangMapping["c sharp"] = CSharp; - - // Visual Basic - _codeLangMapping["vb"] = VisualBasic; - _codeLangMapping["visualbasic"] = VisualBasic; - _codeLangMapping["visual basic"] = VisualBasic; - - // C++/CLI - _codeLangMapping["cpp"] = CPP; - _codeLangMapping["c++"] = CPP; - _codeLangMapping["c++/cli"] = CPP; - _codeLangMapping["c plus plus"] = CPP; - - _attributeNamespaces[CLSCompliantName] = "System"; - _attributeNamespaces[ComVisibleName] = "System.Runtime.InteropServices"; - _attributeNamespaces[GuidName] = "System.Runtime.InteropServices"; - _attributeNamespaces[AssemblyTitleName] = "System.Reflection"; - _attributeNamespaces[AssemblyDescriptionName] = "System.Reflection"; - _attributeNamespaces[AssemblyConfigurationName] = "System.Reflection"; - _attributeNamespaces[AssemblyCompanyName] = "System.Reflection"; - _attributeNamespaces[AssemblyProductName] = "System.Reflection"; - _attributeNamespaces[AssemblyCopyrightName] = "System.Reflection"; - _attributeNamespaces[AssemblyTrademarkName] = "System.Reflection"; - _attributeNamespaces[AssemblyCultureName] = "System.Reflection"; - _attributeNamespaces[AssemblyVersionName] = "System.Reflection"; - _attributeNamespaces[AssemblyFileVersionName] = "System.Reflection"; - _attributeNamespaces[AssemblyInformationalVersionName] = "System.Reflection"; - _attributeNamespaces[AssemblyKeyFileName] = "System.Reflection"; - _attributeNamespaces[AssemblyKeyNameName] = "System.Reflection"; - _attributeNamespaces[AssemblyDelaySignName] = "System.Reflection"; - _attributeNamespaces[InternalsVisibleToName] = "System.Runtime.CompilerServices"; - _attributeNamespaces[AllowPartiallyTrustedCallersName] = "System.Security"; - } - #endregion - - #region Type Fields - - private readonly static Dictionary _codeLangMapping - = new Dictionary(); - - private readonly static Dictionary _attributeNamespaces - = new Dictionary(); - - private static readonly string[] booleanAttributes = { - CLSCompliantName, - AssemblyDelaySignName, - ComVisibleName - }; - private static readonly string[] securityAttributes = { - SkipVerificationName, - UnmanagedCodeName - }; - private static readonly string[] nonGeneratedClassAttributes = { - CLSCompliantName, - AssemblyDelaySignName, - ComVisibleName, - AssemblyKeyFileName, - InternalsVisibleToName - }; - private static readonly string[] markerAttributes = { - AllowPartiallyTrustedCallersName - }; - #endregion - - #region Fields - private readonly Dictionary _attributes; - private string _outputFile; - - #endregion Fields - - #region Constructor - /// - /// Initializes a new instance of the class. - /// - public AssemblyInfo() - { - _attributes = new Dictionary(); - _outputFile = DEFAULT_OUTPUT_FILE; - } - - #endregion Constructor - - #region Input Parameters - - /// - /// Gets or sets the code language. - /// - /// The code language. - [Required] - public string CodeLanguage { get; set; } - - /// - /// Gets or sets a value indicating whether [COMVisible]. - /// - /// true if [COMVisible]; otherwise, false. - public bool ComVisible - { - get { return ReadBooleanAttribute(ComVisibleName); } - set { _attributes[ComVisibleName] = value.ToString(); } - } - - /// - /// Gets or sets a value indicating whether [CLSCompliant]. - /// - /// true if [CLSCompliant]; otherwise, false. - public bool CLSCompliant - { - get { return ReadBooleanAttribute(CLSCompliantName); } - set { _attributes[CLSCompliantName] = value.ToString(); } - } - - /// - /// Gets or sets the GUID. - /// - /// The GUID. - public string Guid - { - get { return ReadAttribute(GuidName); } - set { _attributes[GuidName] = value; } - } - - /// - /// Gets or sets the assembly title. - /// - /// The assembly title. - public string AssemblyTitle - { - get { return ReadAttribute(AssemblyTitleName); } - set { _attributes[AssemblyTitleName] = value; } - } - - /// - /// Gets or sets the assembly description. - /// - /// The assembly description. - public string AssemblyDescription - { - get { return ReadAttribute(AssemblyDescriptionName); } - set { _attributes[AssemblyDescriptionName] = value; } - } - - /// - /// Gets or sets the assembly configuration. - /// - /// The assembly configuration. - public string AssemblyConfiguration - { - get { return ReadAttribute(AssemblyConfigurationName); } - set { _attributes[AssemblyConfigurationName] = value; } - } - - /// - /// Gets or sets the assembly company. - /// - /// The assembly company. - public string AssemblyCompany - { - get { return ReadAttribute(AssemblyCompanyName); } - set { _attributes[AssemblyCompanyName] = value; } - } - - /// - /// Gets or sets the assembly product. - /// - /// The assembly product. - public string AssemblyProduct - { - get { return ReadAttribute(AssemblyProductName); } - set { _attributes[AssemblyProductName] = value; } - } - - /// - /// Gets or sets the assembly copyright. - /// - /// The assembly copyright. - public string AssemblyCopyright - { - get { return ReadAttribute(AssemblyCopyrightName); } - set { _attributes[AssemblyCopyrightName] = value; } - } - - /// - /// Gets or sets the assembly trademark. - /// - /// The assembly trademark. - public string AssemblyTrademark - { - get { return ReadAttribute(AssemblyTrademarkName); } - set { _attributes[AssemblyTrademarkName] = value; } - } - - /// - /// Gets or sets the assembly culture. - /// - /// The assembly culture. - public string AssemblyCulture - { - get { return ReadAttribute(AssemblyCultureName); } - set { _attributes[AssemblyCultureName] = value; } - } - - /// - /// Gets or sets the assembly version. - /// - /// The assembly version. - public string AssemblyVersion - { - get { return ReadAttribute(AssemblyVersionName); } - set { _attributes[AssemblyVersionName] = value; } - } - - /// - /// Gets or sets the assembly file version. - /// - /// The assembly file version. - public string AssemblyFileVersion - { - get { return ReadAttribute(AssemblyFileVersionName); } - set { _attributes[AssemblyFileVersionName] = value; } - } - - /// - /// Gets or sets the assembly informational version. - /// - /// The assembly informational version. - public string AssemblyInformationalVersion - { - get { return ReadAttribute(AssemblyInformationalVersionName); } - set { _attributes[AssemblyInformationalVersionName] = value; } - } - - /// - /// Gets or sets the assembly key file. - /// - public string AssemblyKeyFile - { - get { return ReadAttribute(AssemblyKeyFileName); } - set { _attributes[AssemblyKeyFileName] = value; } - } - - /// - /// Gets or sets the assembly key name. - /// - public string AssemblyKeyName - { - get { return ReadAttribute(AssemblyKeyNameName); } - set { _attributes[AssemblyKeyNameName] = value; } - } - - /// - /// Gets or sets the assembly delay sign value. - /// - public bool AssemblyDelaySign - { - get { return ReadBooleanAttribute(AssemblyDelaySignName); } - set { _attributes[AssemblyDelaySignName] = value.ToString(); } - } - - /// - /// Gets or sets the assembly delay sign value. - /// - public bool SkipVerification - { - get { return ReadBooleanAttribute(SkipVerificationName); } - set { _attributes[SkipVerificationName] = value.ToString(); } - } - - /// - /// Gets or sets the assembly delay sign value. - /// - public bool UnmanagedCode - { - get { return ReadBooleanAttribute(UnmanagedCodeName); } - set { _attributes[UnmanagedCodeName] = value.ToString(); } - } - - /// - /// Gets or sets a value indicating whether to generate the ThisAssmebly class. - /// - public bool GenerateClass { get; set; } - - /// - /// Gets or sets the neutral language which is used as a fallback language configuration - /// if the locale on the computer isn't supported. Example is setting this to "en-US". - /// - public string NeutralResourcesLanguage { get; set; } - - /// - /// Gets or sets the ultimate resource fallback location. - /// - /// The ultimate resource fallback location. - public string UltimateResourceFallbackLocation { get; set; } - - /// - /// Makes it possible to make certain assemblies able to use constructs marked as internal. - /// Example might be setting this value to "UnitTests" assembly. The typical use case might - /// be constructors in classes which shouldn't be available to other assemblies, but the unit - /// tests should be able to use them. - /// - public string InternalsVisibleTo - { - get { return ReadAttribute(InternalsVisibleToName); } - set { _attributes[InternalsVisibleToName] = value; } - } - - /// - /// Gets or sets whether to allow strong-named assemblies to be called by partially trusted code. - /// - public bool AllowPartiallyTrustedCallers - { - get { return ReadBooleanAttribute(AllowPartiallyTrustedCallersName); } - set { _attributes[AllowPartiallyTrustedCallersName] = value.ToString(); } - } - - #endregion Input Parameters - - #region Input/Output Parameters - - /// - /// Gets or sets the output file. - /// - /// The output file. - [Output] - public string OutputFile - { - get { return _outputFile; } - set { _outputFile = value; } - } - - #endregion Input/Output Parameters - - #region Task Overrides - /// - /// When overridden in a derived class, executes the task. - /// - /// - /// true if the task successfully executed; otherwise, false. - /// - public override bool Execute() - { - if (_attributes.Count == 0) - { - Log.LogError("No assembly parameter were set for file \"{0}\".", _outputFile); - return false; - } - - Encoding utf8WithSignature = new UTF8Encoding(true); - - using (StreamWriter writer = new StreamWriter(_outputFile, false, utf8WithSignature)) - { - GenerateFile(writer); - writer.Flush(); - writer.Close(); - Log.LogMessage("Created AssemblyInfo file \"{0}\".", _outputFile); - } - - return true; - } - - #endregion Task Overrides - - #region Private Methods - private void GenerateFile(TextWriter writer) - { - CodeLanguage = (CodeLanguage ?? String.Empty).ToLower(); - - // Get the chosen provider and rename the output file's extension - CodeDomProvider provider = GetProviderAndSetExtension(CodeLanguage, ref _outputFile); - - SetDefaultsForLanguage(CodeLanguage); - - CodeCompileUnit codeCompileUnit = new CodeCompileUnit(); - CodeNamespace codeNamespace = new CodeNamespace(); - codeCompileUnit.Namespaces.Add(codeNamespace); - - // Add each configured attribute - foreach (var attribute in _attributes) - { - String name = attribute.Key; - String value = attribute.Value; - - if (Array.Exists(booleanAttributes, name.Equals)) - { - // Add the boolean attribute with a strongly typed value - AddBooleanAssemblyAttribute(codeCompileUnit, name, value); - } - else if (Array.Exists(securityAttributes, name.Equals)) - { - // Add the security permission request attribute for the given action - AddSecurityPermissionAssemblyAttribute(codeCompileUnit, name, value); - } - else if (Array.Exists(markerAttributes, name.Equals)) - { - // Add the security permission request attribute for the given action - AddMarkerAssemblyAttribute(codeCompileUnit, name, value); - } - else - { - // Add the attribute with the text value - AddAttributeToCodeDom(codeCompileUnit, name, value); - } - } - - // Add an assembly language code attribute to determine the neutral culture - if (NeutralResourcesLanguage != null) - { - AddAssemblyLanguageCodeAttribute(codeCompileUnit); - } - - // Generate an internally accessible class which has the version information - // available as properties - if (GenerateClass) - { - GenerateThisAssemblyClass(codeNamespace); - } - - // Generate code with the chosen provider - provider.GenerateCodeFromCompileUnit(codeCompileUnit, writer, new CodeGeneratorOptions()); - } - - private CodeDomProvider GetProviderAndSetExtension(string codeLanguage, ref string outputFile) - { - String codeLang; - - if (!_codeLangMapping.TryGetValue(codeLanguage, out codeLang)) - { - throw new NotSupportedException("The specified code language is not supported: '" + - CodeLanguage + - "'"); - } - - CodeDomProvider provider; - - switch (codeLang) - { - case CSharp: - provider = new Microsoft.CSharp.CSharpCodeProvider(); - outputFile = Path.ChangeExtension(outputFile, ".cs"); - break; - case VisualBasic: - provider = new Microsoft.VisualBasic.VBCodeProvider(); - outputFile = Path.ChangeExtension(outputFile, ".vb"); - break; - case CPP: - // We load the CppCodeProvider via reflection since a hard reference would - // require client machines to have the provider installed just to run the task. - // This way relieves us of the dependency. - try - { - Assembly cppCodeProvider = Assembly.Load(CppCodeProviderAssembly); - provider = cppCodeProvider.CreateInstance(CppCodeProviderType) - as CodeDomProvider; - } - catch(FileLoadException fileLoadEx) - { - String fusionMessage = String.IsNullOrEmpty(fileLoadEx.FusionLog) - ? "Turn on fusion logging to diagnose the problem further. " + - "(Check http://blogs.msdn.com/suzcook/archive"+ - "/2003/05/29/57120.aspx for more info)" - : "Check fusion log: " + fileLoadEx.FusionLog; - Log.LogError("The C++/CLI code provider could not be loaded. " + - fileLoadEx.Message ?? "" + - (fileLoadEx.InnerException == null - ? "" - : fileLoadEx.InnerException.Message ?? "") + - fusionMessage); - provider = null; - } - catch (FileNotFoundException) - { - Log.LogError("The C++/CLI code provider wasn't found. "+ - "Make sure you have Visual C++ installed."); - provider = null; - } - - outputFile = Path.ChangeExtension(outputFile, ".cpp"); - break; - default: - throw new InvalidOperationException("Shouldn't reach here."); - } - - return provider; - } - - private void SetDefaultsForLanguage(string codeLanguage) - { - switch (codeLanguage) - { - case CSharp: - break; - case VisualBasic: - break; - case CPP: - if (!_attributes.ContainsKey(UnmanagedCodeName)) - { - UnmanagedCode = true; - } - break; - } - } - - private static void AddAttributeToCodeDom(CodeCompileUnit codeCompileUnit, string name, object value) - { - var valueExpression = new CodePrimitiveExpression(value); - var codeAttributeDeclaration = new CodeAttributeDeclaration(_attributeNamespaces[name] + "." + name); - codeAttributeDeclaration.Arguments.Add(new CodeAttributeArgument(valueExpression)); - // add assembly-level argument to code compile unit - codeCompileUnit.AssemblyCustomAttributes.Add(codeAttributeDeclaration); - } - - private static void AddMarkerAttributeToCodeDom(CodeCompileUnit codeCompileUnit, string name) - { - var codeAttributeDeclaration = new CodeAttributeDeclaration(_attributeNamespaces[name] + "." + name); - // add assembly-level argument to code compile unit - codeCompileUnit.AssemblyCustomAttributes.Add(codeAttributeDeclaration); - } - - private static void AddBooleanAssemblyAttribute(CodeCompileUnit codeCompileUnit, String name, String value) - { - bool typedValue; - - if (!bool.TryParse(value, out typedValue)) - { - throw new InvalidOperationException("Boolean attribute " + name + "is not boolean: " + - value); - } - - AddAttributeToCodeDom(codeCompileUnit, name, typedValue); - } - - private static void AddSecurityPermissionAssemblyAttribute(CodeCompileUnit codeCompileUnit, String name, String value) - { - - var codeAttributeDeclaration = new CodeAttributeDeclaration("System.Security.Permissions.SecurityPermissionAttribute"); - - var requestMinimum = new CodeAttributeArgument( - new CodeFieldReferenceExpression( - new CodeTypeReferenceExpression(typeof(SecurityAction)), "RequestMinimum")); - - codeAttributeDeclaration.Arguments.Add(requestMinimum); - - bool typedValue; - if (!bool.TryParse(value, out typedValue)) - { - throw new InvalidOperationException("Boolean security attribute " + name + - "is not boolean: " + value); - } - - CodePrimitiveExpression valueExpression = new CodePrimitiveExpression(typedValue); - - codeAttributeDeclaration.Arguments.Add(new CodeAttributeArgument(name, valueExpression)); - - // add assembly-level argument to code compile unit - codeCompileUnit.AssemblyCustomAttributes.Add(codeAttributeDeclaration); - } - - private static void AddMarkerAssemblyAttribute(CodeCompileUnit codeCompileUnit, String name, String value) - { - bool typedValue; - - if (!bool.TryParse(value, out typedValue)) - { - throw new InvalidOperationException("Boolean attribute " + name + "is not boolean: " + - value); - } - if (typedValue) - { - AddMarkerAttributeToCodeDom(codeCompileUnit, name); - } - } - - private void AddAssemblyLanguageCodeAttribute(CodeCompileUnit codeCompileUnit) - { - var codeAttributeDeclaration = new CodeAttributeDeclaration("System.Resources.NeutralResourcesLanguage"); - codeAttributeDeclaration.Arguments.Add( - new CodeAttributeArgument( - new CodePrimitiveExpression(NeutralResourcesLanguage))); - - if (! string.IsNullOrEmpty(UltimateResourceFallbackLocation)) - codeAttributeDeclaration.Arguments.Add( - new CodeAttributeArgument( - new CodeTypeReferenceExpression(UltimateResourceFallbackLocation))); - - codeCompileUnit.AssemblyCustomAttributes.Add(codeAttributeDeclaration); - } - - private void GenerateThisAssemblyClass(CodeNamespace codeNamespace) - { - //Create Class Declaration - CodeTypeDeclaration thisAssemblyType = new CodeTypeDeclaration("ThisAssembly") - { - IsClass = true, - IsPartial = true, - TypeAttributes = TypeAttributes.NotPublic | - TypeAttributes.Sealed - }; - - CodeConstructor privateConstructor = new CodeConstructor - { - Attributes = MemberAttributes.Private - }; - - thisAssemblyType.Members.Add(privateConstructor); - - foreach (var assemblyAttribute in _attributes) - { - String name = assemblyAttribute.Key; - String value = assemblyAttribute.Value; - - if (Array.Exists(nonGeneratedClassAttributes, name.Equals)) - { - continue; - } - - CodeMemberField field = new CodeMemberField(typeof(string), name) - { - Attributes = MemberAttributes.Assembly | - MemberAttributes.Const, - InitExpression = new CodePrimitiveExpression(value) - }; - - thisAssemblyType.Members.Add(field); - } - - codeNamespace.Types.Add(thisAssemblyType); - } - - private string ReadAttribute(string key) - { - string value; - _attributes.TryGetValue(key, out value); - return value; - } - - private bool ReadBooleanAttribute(string key) - { - string value; - bool result; - - if (!_attributes.TryGetValue(key, out value)) - return false; - if (!bool.TryParse(value, out result)) - return false; - - return result; - } - - #endregion Private Methods - - } -} +namespace MSBuild.Community.Tasks { + /// + /// Generates an AssemblyInfo files + /// + /// + /// Generates a common version file. + /// + /// ]]> + /// Generates a complete version file. + /// + /// ]]> + /// Generates a complete version file for C++/CLI. + /// + /// ]]> + /// + public class AssemblyInfo : Task { + #region Constants + + /// + /// The default value of . + /// The value is "AssemblyInfo.cs". + /// + public const string DEFAULT_OUTPUT_FILE = @"AssemblyInfo.cs"; + + private const string CSharp = "CS"; + private const string VisualBasic = "VB"; + private const string CPP = "CPP"; + + private const string CppCodeProviderAssembly = "CppCodeProvider, "+ + "Version=8.0.0.0, " + + "Culture=neutral, " + + "PublicKeyToken=b03f5f7f11d50a3a, " + + "processorArchitecture=MSIL"; + + private const string CppCodeProviderType = "Microsoft.VisualC.CppCodeProvider"; + + private const string CLSCompliantName = "CLSCompliant"; + private const string ComVisibleName = "ComVisible"; + private const string GuidName = "Guid"; + private const string AssemblyTitleName = "AssemblyTitle"; + private const string AssemblyDescriptionName = "AssemblyDescription"; + private const string AssemblyConfigurationName = "AssemblyConfiguration"; + private const string AssemblyCompanyName = "AssemblyCompany"; + private const string AssemblyProductName = "AssemblyProduct"; + private const string AssemblyCopyrightName = "AssemblyCopyright"; + private const string AssemblyTrademarkName = "AssemblyTrademark"; + private const string AssemblyCultureName = "AssemblyCulture"; + private const string AssemblyVersionName = "AssemblyVersion"; + private const string AssemblyFileVersionName = "AssemblyFileVersion"; + private const string AssemblyInformationalVersionName = "AssemblyInformationalVersion"; + private const string AssemblyKeyFileName = "AssemblyKeyFile"; + private const string AssemblyKeyNameName = "AssemblyKeyName"; + private const string AssemblyDelaySignName = "AssemblyDelaySign"; + private const string SkipVerificationName = "SkipVerification"; + private const string UnmanagedCodeName = "UnmanagedCode"; + private const string InternalsVisibleToName = "InternalsVisibleTo"; + private const string AllowPartiallyTrustedCallersName = "AllowPartiallyTrustedCallers"; + + #endregion Constants + + #region Type Constructor + + static AssemblyInfo() { + // C# + _codeLangMapping["csharp"] = CSharp; + _codeLangMapping["cs"] = CSharp; + _codeLangMapping["c#"] = CSharp; + _codeLangMapping["c sharp"] = CSharp; + + // Visual Basic + _codeLangMapping["vb"] = VisualBasic; + _codeLangMapping["visualbasic"] = VisualBasic; + _codeLangMapping["visual basic"] = VisualBasic; + + // C++/CLI + _codeLangMapping["cpp"] = CPP; + _codeLangMapping["c++"] = CPP; + _codeLangMapping["c++/cli"] = CPP; + _codeLangMapping["c plus plus"] = CPP; + + _attributeNamespaces[CLSCompliantName] = "System"; + _attributeNamespaces[ComVisibleName] = "System.Runtime.InteropServices"; + _attributeNamespaces[GuidName] = "System.Runtime.InteropServices"; + _attributeNamespaces[AssemblyTitleName] = "System.Reflection"; + _attributeNamespaces[AssemblyDescriptionName] = "System.Reflection"; + _attributeNamespaces[AssemblyConfigurationName] = "System.Reflection"; + _attributeNamespaces[AssemblyCompanyName] = "System.Reflection"; + _attributeNamespaces[AssemblyProductName] = "System.Reflection"; + _attributeNamespaces[AssemblyCopyrightName] = "System.Reflection"; + _attributeNamespaces[AssemblyTrademarkName] = "System.Reflection"; + _attributeNamespaces[AssemblyCultureName] = "System.Reflection"; + _attributeNamespaces[AssemblyVersionName] = "System.Reflection"; + _attributeNamespaces[AssemblyFileVersionName] = "System.Reflection"; + _attributeNamespaces[AssemblyInformationalVersionName] = "System.Reflection"; + _attributeNamespaces[AssemblyKeyFileName] = "System.Reflection"; + _attributeNamespaces[AssemblyKeyNameName] = "System.Reflection"; + _attributeNamespaces[AssemblyDelaySignName] = "System.Reflection"; + _attributeNamespaces[InternalsVisibleToName] = "System.Runtime.CompilerServices"; + _attributeNamespaces[AllowPartiallyTrustedCallersName] = "System.Security"; + } + #endregion + + #region Type Fields + + private readonly static Dictionary _codeLangMapping + = new Dictionary(); + + private readonly static Dictionary _attributeNamespaces + = new Dictionary(); + + private static readonly string[] booleanAttributes = { + CLSCompliantName, + AssemblyDelaySignName, + ComVisibleName + }; + private static readonly string[] securityAttributes = { + SkipVerificationName, + UnmanagedCodeName + }; + private static readonly string[] nonGeneratedClassAttributes = { + CLSCompliantName, + AssemblyDelaySignName, + ComVisibleName, + AssemblyKeyFileName, + InternalsVisibleToName + }; + private static readonly string[] markerAttributes = { + AllowPartiallyTrustedCallersName + }; + #endregion + + #region Fields + private readonly Dictionary _attributes; + private string _outputFile; + + #endregion Fields + + #region Constructor + /// + /// Initializes a new instance of the class. + /// + public AssemblyInfo() { + _attributes = new Dictionary(); + _outputFile = DEFAULT_OUTPUT_FILE; + } + + #endregion Constructor + + #region Input Parameters + + /// + /// Gets or sets the code language. + /// + /// The code language. + [Required] + public string CodeLanguage { get; set; } + + /// + /// Gets or sets a value indicating whether [COMVisible]. + /// + /// true if [COMVisible]; otherwise, false. + public bool ComVisible + { + get { return ReadBooleanAttribute(ComVisibleName); } + set { _attributes[ComVisibleName] = value.ToString(); } + } + + /// + /// Gets or sets a value indicating whether [CLSCompliant]. + /// + /// true if [CLSCompliant]; otherwise, false. + public bool CLSCompliant + { + get { return ReadBooleanAttribute(CLSCompliantName); } + set { _attributes[CLSCompliantName] = value.ToString(); } + } + + /// + /// Gets or sets the GUID. + /// + /// The GUID. + public string Guid + { + get { return ReadAttribute(GuidName); } + set { _attributes[GuidName] = value; } + } + + /// + /// Gets or sets the assembly title. + /// + /// The assembly title. + public string AssemblyTitle + { + get { return ReadAttribute(AssemblyTitleName); } + set { _attributes[AssemblyTitleName] = value; } + } + + /// + /// Gets or sets the assembly description. + /// + /// The assembly description. + public string AssemblyDescription + { + get { return ReadAttribute(AssemblyDescriptionName); } + set { _attributes[AssemblyDescriptionName] = value; } + } + + /// + /// Gets or sets the assembly configuration. + /// + /// The assembly configuration. + public string AssemblyConfiguration + { + get { return ReadAttribute(AssemblyConfigurationName); } + set { _attributes[AssemblyConfigurationName] = value; } + } + + /// + /// Gets or sets the assembly company. + /// + /// The assembly company. + public string AssemblyCompany + { + get { return ReadAttribute(AssemblyCompanyName); } + set { _attributes[AssemblyCompanyName] = value; } + } + + /// + /// Gets or sets the assembly product. + /// + /// The assembly product. + public string AssemblyProduct + { + get { return ReadAttribute(AssemblyProductName); } + set { _attributes[AssemblyProductName] = value; } + } + + /// + /// Gets or sets the assembly copyright. + /// + /// The assembly copyright. + public string AssemblyCopyright + { + get { return ReadAttribute(AssemblyCopyrightName); } + set { _attributes[AssemblyCopyrightName] = value; } + } + + /// + /// Gets or sets the assembly trademark. + /// + /// The assembly trademark. + public string AssemblyTrademark + { + get { return ReadAttribute(AssemblyTrademarkName); } + set { _attributes[AssemblyTrademarkName] = value; } + } + + /// + /// Gets or sets the assembly culture. + /// + /// The assembly culture. + public string AssemblyCulture + { + get { return ReadAttribute(AssemblyCultureName); } + set { _attributes[AssemblyCultureName] = value; } + } + + /// + /// Gets or sets the assembly version. + /// + /// The assembly version. + public string AssemblyVersion + { + get { return ReadAttribute(AssemblyVersionName); } + set { _attributes[AssemblyVersionName] = value; } + } + + /// + /// Gets or sets the assembly file version. + /// + /// The assembly file version. + public string AssemblyFileVersion + { + get { return ReadAttribute(AssemblyFileVersionName); } + set { _attributes[AssemblyFileVersionName] = value; } + } + + /// + /// Gets or sets the assembly informational version. + /// + /// The assembly informational version. + public string AssemblyInformationalVersion + { + get { return ReadAttribute(AssemblyInformationalVersionName); } + set { _attributes[AssemblyInformationalVersionName] = value; } + } + + /// + /// Gets or sets the assembly key file. + /// + public string AssemblyKeyFile + { + get { return ReadAttribute(AssemblyKeyFileName); } + set { _attributes[AssemblyKeyFileName] = value; } + } + + /// + /// Gets or sets the assembly key name. + /// + public string AssemblyKeyName + { + get { return ReadAttribute(AssemblyKeyNameName); } + set { _attributes[AssemblyKeyNameName] = value; } + } + + /// + /// Gets or sets the assembly delay sign value. + /// + public bool AssemblyDelaySign + { + get { return ReadBooleanAttribute(AssemblyDelaySignName); } + set { _attributes[AssemblyDelaySignName] = value.ToString(); } + } + + /// + /// Gets or sets the assembly delay sign value. + /// + public bool SkipVerification + { + get { return ReadBooleanAttribute(SkipVerificationName); } + set { _attributes[SkipVerificationName] = value.ToString(); } + } + + /// + /// Gets or sets the assembly delay sign value. + /// + public bool UnmanagedCode + { + get { return ReadBooleanAttribute(UnmanagedCodeName); } + set { _attributes[UnmanagedCodeName] = value.ToString(); } + } + + /// + /// Gets or sets a value indicating whether to generate the ThisAssmebly class. + /// + public bool GenerateClass { get; set; } + + /// + /// Gets or sets the neutral language which is used as a fallback language configuration + /// if the locale on the computer isn't supported. Example is setting this to "en-US". + /// + public string NeutralResourcesLanguage { get; set; } + + /// + /// Gets or sets the ultimate resource fallback location. + /// + /// The ultimate resource fallback location. + public string UltimateResourceFallbackLocation { get; set; } + + /// + /// Makes it possible to make certain assemblies able to use constructs marked as internal. + /// Example might be setting this value to "UnitTests" assembly. The typical use case might + /// be constructors in classes which shouldn't be available to other assemblies, but the unit + /// tests should be able to use them. + /// + public string InternalsVisibleTo + { + get { return ReadAttribute(InternalsVisibleToName); } + set { _attributes[InternalsVisibleToName] = value; } + } + + /// + /// Gets or sets whether to allow strong-named assemblies to be called by partially trusted code. + /// + public bool AllowPartiallyTrustedCallers + { + get { return ReadBooleanAttribute(AllowPartiallyTrustedCallersName); } + set { _attributes[AllowPartiallyTrustedCallersName] = value.ToString(); } + } + + #endregion Input Parameters + + #region Input/Output Parameters + + /// + /// Gets or sets the output file. + /// + /// The output file. + [Output] + public string OutputFile + { + get { return _outputFile; } + set { _outputFile = value; } + } + + #endregion Input/Output Parameters + + #region Task Overrides + /// + /// When overridden in a derived class, executes the task. + /// + /// + /// true if the task successfully executed; otherwise, false. + /// + public override bool Execute() { + if (_attributes.Count == 0) { + Log.LogError("No assembly parameter were set for file \"{0}\".", _outputFile); + return false; + } + + Encoding utf8WithSignature = new UTF8Encoding(true); + + var textbuilder = new StringBuilder(); + + using (StringWriter writer = new StringWriter(textbuilder)) { + GenerateFile(writer); + writer.Flush(); + writer.Close(); + + var text = textbuilder.ToString(); + + if (!File.Exists(_outputFile) || text != File.ReadAllText(_outputFile)) File.WriteAllText(_outputFile, text, utf8WithSignature); + else Log.LogMessage("AssemblyInfo file \"{0}\" unchanged.", _outputFile); + + Log.LogMessage("Created AssemblyInfo file \"{0}\".", _outputFile); + } + + return true; + } + + #endregion Task Overrides + + #region Private Methods + private void GenerateFile(TextWriter writer) { + CodeLanguage = (CodeLanguage ?? String.Empty).ToLower(); + + // Get the chosen provider and rename the output file's extension + CodeDomProvider provider = GetProviderAndSetExtension(CodeLanguage, ref _outputFile); + + SetDefaultsForLanguage(CodeLanguage); + + CodeCompileUnit codeCompileUnit = new CodeCompileUnit(); + CodeNamespace codeNamespace = new CodeNamespace(); + codeCompileUnit.Namespaces.Add(codeNamespace); + + // Add each configured attribute + foreach (var attribute in _attributes) { + String name = attribute.Key; + String value = attribute.Value; + + if (Array.Exists(booleanAttributes, name.Equals)) { + // Add the boolean attribute with a strongly typed value + AddBooleanAssemblyAttribute(codeCompileUnit, name, value); + } else if (Array.Exists(securityAttributes, name.Equals)) { + // Add the security permission request attribute for the given action + AddSecurityPermissionAssemblyAttribute(codeCompileUnit, name, value); + } else if (Array.Exists(markerAttributes, name.Equals)) { + // Add the security permission request attribute for the given action + AddMarkerAssemblyAttribute(codeCompileUnit, name, value); + } else { + // Add the attribute with the text value + AddAttributeToCodeDom(codeCompileUnit, name, value); + } + } + + // Add an assembly language code attribute to determine the neutral culture + if (NeutralResourcesLanguage != null) { + AddAssemblyLanguageCodeAttribute(codeCompileUnit); + } + + // Generate an internally accessible class which has the version information + // available as properties + if (GenerateClass) { + GenerateThisAssemblyClass(codeNamespace); + } + + // Generate code with the chosen provider + provider.GenerateCodeFromCompileUnit(codeCompileUnit, writer, new CodeGeneratorOptions()); + } + + private CodeDomProvider GetProviderAndSetExtension(string codeLanguage, ref string outputFile) { + String codeLang; + + if (!_codeLangMapping.TryGetValue(codeLanguage, out codeLang)) { + throw new NotSupportedException("The specified code language is not supported: '" + + CodeLanguage + + "'"); + } + + CodeDomProvider provider; + + switch (codeLang) { + case CSharp: + provider = new Microsoft.CSharp.CSharpCodeProvider(); + outputFile = Path.ChangeExtension(outputFile, ".cs"); + break; + case VisualBasic: + provider = new Microsoft.VisualBasic.VBCodeProvider(); + outputFile = Path.ChangeExtension(outputFile, ".vb"); + break; + case CPP: + // We load the CppCodeProvider via reflection since a hard reference would + // require client machines to have the provider installed just to run the task. + // This way relieves us of the dependency. + try { + Assembly cppCodeProvider = Assembly.Load(CppCodeProviderAssembly); + provider = cppCodeProvider.CreateInstance(CppCodeProviderType) + as CodeDomProvider; + } catch (FileLoadException fileLoadEx) { + String fusionMessage = String.IsNullOrEmpty(fileLoadEx.FusionLog) + ? "Turn on fusion logging to diagnose the problem further. " + + "(Check http://blogs.msdn.com/suzcook/archive"+ + "/2003/05/29/57120.aspx for more info)" + : "Check fusion log: " + fileLoadEx.FusionLog; + Log.LogError("The C++/CLI code provider could not be loaded. " + + fileLoadEx.Message ?? "" + + (fileLoadEx.InnerException == null + ? "" + : fileLoadEx.InnerException.Message ?? "") + + fusionMessage); + provider = null; + } catch (FileNotFoundException) { + Log.LogError("The C++/CLI code provider wasn't found. "+ + "Make sure you have Visual C++ installed."); + provider = null; + } + + outputFile = Path.ChangeExtension(outputFile, ".cpp"); + break; + default: + throw new InvalidOperationException("Shouldn't reach here."); + } + + return provider; + } + + private void SetDefaultsForLanguage(string codeLanguage) { + switch (codeLanguage) { + case CSharp: + break; + case VisualBasic: + break; + case CPP: + if (!_attributes.ContainsKey(UnmanagedCodeName)) { + UnmanagedCode = true; + } + break; + } + } + + private static void AddAttributeToCodeDom(CodeCompileUnit codeCompileUnit, string name, object value) { + var valueExpression = new CodePrimitiveExpression(value); + var codeAttributeDeclaration = new CodeAttributeDeclaration(_attributeNamespaces[name] + "." + name); + codeAttributeDeclaration.Arguments.Add(new CodeAttributeArgument(valueExpression)); + // add assembly-level argument to code compile unit + codeCompileUnit.AssemblyCustomAttributes.Add(codeAttributeDeclaration); + } + + private static void AddMarkerAttributeToCodeDom(CodeCompileUnit codeCompileUnit, string name) { + var codeAttributeDeclaration = new CodeAttributeDeclaration(_attributeNamespaces[name] + "." + name); + // add assembly-level argument to code compile unit + codeCompileUnit.AssemblyCustomAttributes.Add(codeAttributeDeclaration); + } + + private static void AddBooleanAssemblyAttribute(CodeCompileUnit codeCompileUnit, String name, String value) { + bool typedValue; + + if (!bool.TryParse(value, out typedValue)) { + throw new InvalidOperationException("Boolean attribute " + name + "is not boolean: " + + value); + } + + AddAttributeToCodeDom(codeCompileUnit, name, typedValue); + } + + private static void AddSecurityPermissionAssemblyAttribute(CodeCompileUnit codeCompileUnit, String name, String value) { + + var codeAttributeDeclaration = new CodeAttributeDeclaration("System.Security.Permissions.SecurityPermissionAttribute"); + + var requestMinimum = new CodeAttributeArgument( + new CodeFieldReferenceExpression( + new CodeTypeReferenceExpression(typeof(SecurityAction)), "RequestMinimum")); + + codeAttributeDeclaration.Arguments.Add(requestMinimum); + + bool typedValue; + if (!bool.TryParse(value, out typedValue)) { + throw new InvalidOperationException("Boolean security attribute " + name + + "is not boolean: " + value); + } + + CodePrimitiveExpression valueExpression = new CodePrimitiveExpression(typedValue); + + codeAttributeDeclaration.Arguments.Add(new CodeAttributeArgument(name, valueExpression)); + + // add assembly-level argument to code compile unit + codeCompileUnit.AssemblyCustomAttributes.Add(codeAttributeDeclaration); + } + + private static void AddMarkerAssemblyAttribute(CodeCompileUnit codeCompileUnit, String name, String value) { + bool typedValue; + + if (!bool.TryParse(value, out typedValue)) { + throw new InvalidOperationException("Boolean attribute " + name + "is not boolean: " + + value); + } + if (typedValue) { + AddMarkerAttributeToCodeDom(codeCompileUnit, name); + } + } + + private void AddAssemblyLanguageCodeAttribute(CodeCompileUnit codeCompileUnit) { + var codeAttributeDeclaration = new CodeAttributeDeclaration("System.Resources.NeutralResourcesLanguage"); + codeAttributeDeclaration.Arguments.Add( + new CodeAttributeArgument( + new CodePrimitiveExpression(NeutralResourcesLanguage))); + + if (!string.IsNullOrEmpty(UltimateResourceFallbackLocation)) + codeAttributeDeclaration.Arguments.Add( + new CodeAttributeArgument( + new CodeTypeReferenceExpression(UltimateResourceFallbackLocation))); + + codeCompileUnit.AssemblyCustomAttributes.Add(codeAttributeDeclaration); + } + + private void GenerateThisAssemblyClass(CodeNamespace codeNamespace) { + //Create Class Declaration + CodeTypeDeclaration thisAssemblyType = new CodeTypeDeclaration("ThisAssembly") { + IsClass = true, + IsPartial = true, + TypeAttributes = TypeAttributes.NotPublic | + TypeAttributes.Sealed + }; + + CodeConstructor privateConstructor = new CodeConstructor { + Attributes = MemberAttributes.Private + }; + + thisAssemblyType.Members.Add(privateConstructor); + + foreach (var assemblyAttribute in _attributes) { + String name = assemblyAttribute.Key; + String value = assemblyAttribute.Value; + + if (Array.Exists(nonGeneratedClassAttributes, name.Equals)) { + continue; + } + + CodeMemberField field = new CodeMemberField(typeof(string), name) { + Attributes = MemberAttributes.Assembly | + MemberAttributes.Const, + InitExpression = new CodePrimitiveExpression(value) + }; + + thisAssemblyType.Members.Add(field); + } + + codeNamespace.Types.Add(thisAssemblyType); + } + + private string ReadAttribute(string key) { + string value; + _attributes.TryGetValue(key, out value); + return value; + } + + private bool ReadBooleanAttribute(string key) { + string value; + bool result; + + if (!_attributes.TryGetValue(key, out value)) + return false; + if (!bool.TryParse(value, out result)) + return false; + + return result; + } + + #endregion Private Methods + + } +} \ No newline at end of file diff --git a/Source/MSBuild.Community.Tasks/ContentFilter.cs b/Source/MSBuild.Community.Tasks/ContentFilter.cs new file mode 100644 index 00000000..0dc55507 --- /dev/null +++ b/Source/MSBuild.Community.Tasks/ContentFilter.cs @@ -0,0 +1,93 @@ +#region Copyright © 2006 Andy Johns. All rights reserved. +/* +Copyright © 2006 Andy Johns. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + +1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. +2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. +3. The name of the author may not be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE AUTHOR "AS IS" AND ANY EXPRESS OR +IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, +INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT +NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF +THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ +#endregion + + + +using System; +using System.Collections.Generic; +using System.Text; +using System.Text.RegularExpressions; +using System.IO; +using Microsoft.Build.Utilities; +using Microsoft.Build.Framework; + +namespace MSBuild.Community.Tasks +{ + /// + /// Task to filter an Input list with a Regex expression. + /// Output list contains items from Input list that matched given expression + /// + /// Matches from TestGroup those names ending in a, b or c + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// ]]> + /// + public class ContentFilter : RegexBase + { + /// + /// Performs the Match task + /// + /// if the task ran successfully; + /// otherwise . + public override bool Execute() + { + var regex = new System.Text.RegularExpressions.Regex(Expression.ItemSpec, ExpressionOptions); + + List returnItems = new List(); + + foreach(ITaskItem item in Input) + { + + if (File.Exists(item.ItemSpec) && regex.IsMatch(File.ReadAllText(item.ItemSpec))) + { + returnItems.Add(new TaskItem(item)); + } + } + + Output = returnItems.ToArray(); + + return !Log.HasLoggedErrors; + } + } +} diff --git a/Source/MSBuild.Community.Tasks/MSBuild.Community.Tasks.Targets b/Source/MSBuild.Community.Tasks/MSBuild.Community.Tasks.Targets index ace9d3f5..b1da1383 100644 --- a/Source/MSBuild.Community.Tasks/MSBuild.Community.Tasks.Targets +++ b/Source/MSBuild.Community.Tasks/MSBuild.Community.Tasks.Targets @@ -17,7 +17,8 @@ - + + @@ -29,8 +30,9 @@ + - + diff --git a/Source/MSBuild.Community.Tasks/MSBuild.Community.Tasks.csproj b/Source/MSBuild.Community.Tasks/MSBuild.Community.Tasks.csproj index 3126e8a4..3dd4b053 100644 --- a/Source/MSBuild.Community.Tasks/MSBuild.Community.Tasks.csproj +++ b/Source/MSBuild.Community.Tasks/MSBuild.Community.Tasks.csproj @@ -60,6 +60,7 @@ + @@ -73,6 +74,7 @@ + @@ -186,6 +188,65 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -263,6 +324,8 @@ + + diff --git a/Source/MSBuild.Community.Tasks/Sync/Arguments.cs b/Source/MSBuild.Community.Tasks/Sync/Arguments.cs new file mode 100644 index 00000000..e70628c4 --- /dev/null +++ b/Source/MSBuild.Community.Tasks/Sync/Arguments.cs @@ -0,0 +1,35 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace johnshope.Sync { + + public class Arguments: List { + + public Arguments(string[] args) : base(args.ToList()) { } + + public bool Has(string option) { + int i = IndexOf(option); + if (i >= 0) { + RemoveAt(i); + return true; + } + return false; + } + + public bool Has(string option, out string parameter) { + int i = IndexOf(option); + if (i >= 0 && i < Count-1) { + parameter = this[i+1]; + RemoveRange(i, 2); + return true; + } + parameter = null; + return false; + } + + public string Pop() { if (Count > 0) { var p = this[0]; RemoveAt(0); return p; } return null; } + + } +} diff --git a/Source/MSBuild.Community.Tasks/Sync/Directory.cs b/Source/MSBuild.Community.Tasks/Sync/Directory.cs new file mode 100644 index 00000000..044140b5 --- /dev/null +++ b/Source/MSBuild.Community.Tasks/Sync/Directory.cs @@ -0,0 +1,16 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace johnshope.Sync { + + public class Directory { + + public static IDirectory Parse(Uri url, SyncJob job) { + if (url.IsFile || !url.ToString().Contains(':')) return new LocalDirectory(null, url, job); + else return new FtpDirectory(null, url, job); + } + + } +} diff --git a/Source/MSBuild.Community.Tasks/Sync/FTP/EventArgs/ConnectionClosedEventArgs.cs b/Source/MSBuild.Community.Tasks/Sync/FTP/EventArgs/ConnectionClosedEventArgs.cs new file mode 100644 index 00000000..54568a33 --- /dev/null +++ b/Source/MSBuild.Community.Tasks/Sync/FTP/EventArgs/ConnectionClosedEventArgs.cs @@ -0,0 +1,42 @@ +/* + * Authors: Benton Stark + * + * Copyright (c) 2007-2009 Starksoft, LLC (http://www.starksoft.com) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + */ + +using System; + +namespace Starksoft.Net.Ftp +{ + /// + /// Event arguments to facilitate the response event from the FTP server. + /// + public class ConnectionClosedEventArgs : EventArgs + { + /// + /// Default constructor. + /// + public ConnectionClosedEventArgs() + { + } + } +} \ No newline at end of file diff --git a/Source/MSBuild.Community.Tasks/Sync/FTP/EventArgs/FtpRequestEventArgs.cs b/Source/MSBuild.Community.Tasks/Sync/FTP/EventArgs/FtpRequestEventArgs.cs new file mode 100644 index 00000000..c26e782a --- /dev/null +++ b/Source/MSBuild.Community.Tasks/Sync/FTP/EventArgs/FtpRequestEventArgs.cs @@ -0,0 +1,55 @@ +/* + * Authors: Benton Stark + * + * Copyright (c) 2007-2009 Starksoft, LLC (http://www.starksoft.com) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + */ + +using System; + +namespace Starksoft.Net.Ftp +{ + /// + /// Event arguments to facilitate the FtpClient request event. + /// + public class FtpRequestEventArgs : EventArgs + { + private FtpRequest _request; + + /// + /// Constructor for FtpRequestEventArgs. + /// + /// An FtpRequest object. + public FtpRequestEventArgs(FtpRequest request) + { + _request = request; + } + + /// + /// Client request command text sent from the client to the server. + /// + public FtpRequest Request + { + get { return _request; } + } + } + +} \ No newline at end of file diff --git a/Source/MSBuild.Community.Tasks/Sync/FTP/EventArgs/FtpResponseEventArgs.cs b/Source/MSBuild.Community.Tasks/Sync/FTP/EventArgs/FtpResponseEventArgs.cs new file mode 100644 index 00000000..b90a07e6 --- /dev/null +++ b/Source/MSBuild.Community.Tasks/Sync/FTP/EventArgs/FtpResponseEventArgs.cs @@ -0,0 +1,54 @@ +/* + * Authors: Benton Stark + * + * Copyright (c) 2007-2009 Starksoft, LLC (http://www.starksoft.com) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + */ + +using System; + +namespace Starksoft.Net.Ftp +{ + /// + /// Event arguments to facilitate the response event from the FTP server. + /// + public class FtpResponseEventArgs : EventArgs + { + private FtpResponse _response; + + /// + /// Constructor. + /// + /// FtpResponse object. + public FtpResponseEventArgs(FtpResponse response) + { + _response = response; + } + + /// + /// Response object containing response received from the server. + /// + public FtpResponse Response + { + get { return _response; } + } + } +} \ No newline at end of file diff --git a/Source/MSBuild.Community.Tasks/Sync/FTP/EventArgs/FtpTransferEventArgs.cs b/Source/MSBuild.Community.Tasks/Sync/FTP/EventArgs/FtpTransferEventArgs.cs new file mode 100644 index 00000000..992d53d3 --- /dev/null +++ b/Source/MSBuild.Community.Tasks/Sync/FTP/EventArgs/FtpTransferEventArgs.cs @@ -0,0 +1,37 @@ +///////////////////////////////////////////////////////////// +//// +//// Copyright (c) 2007 Starksoft, LLC +//// All Rights Reserved. +//// +///////////////////////////////////////////////////////////// + +using System; + +namespace Starksoft.Net.Ftp +{ + /// + /// Event arguments to facilitate the FtpClient transfer progress and complete events. + /// + public class FtpTransferEventArgs : EventArgs + { + private long _bytesTransferred; + + /// + /// Constructor. + /// + /// The number of bytes transferred. + public FtpTransferEventArgs(long bytesTransferred) + { + _bytesTransferred = bytesTransferred; + } + + /// + /// The number of bytes transferred. + /// + public long BytesTransferred + { + get { return _bytesTransferred; } + } + + } +} diff --git a/Source/MSBuild.Community.Tasks/Sync/FTP/EventArgs/FxpCopyAsyncCompletedEventArgs.cs b/Source/MSBuild.Community.Tasks/Sync/FTP/EventArgs/FxpCopyAsyncCompletedEventArgs.cs new file mode 100644 index 00000000..ee829af9 --- /dev/null +++ b/Source/MSBuild.Community.Tasks/Sync/FTP/EventArgs/FxpCopyAsyncCompletedEventArgs.cs @@ -0,0 +1,47 @@ +/* + * Authors: Benton Stark + * + * Copyright (c) 2007-2009 Starksoft, LLC (http://www.starksoft.com) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + */ + +using System; +using System.ComponentModel; + +namespace Starksoft.Net.Ftp +{ + /// + /// Provides data for the OpenAsyncCompleted event. + /// + public class FxpCopyAsyncCompletedEventArgs : AsyncCompletedEventArgs + { + /// + /// Initializes a new instance of the FxpCopyAsyncCompletedEventArgs class. + /// + /// Any error that occurred during the asynchronous operation. + /// A value indicating whether the asynchronous operation was canceled. + public FxpCopyAsyncCompletedEventArgs(Exception error, bool canceled) + : base(error, canceled, null) + { + } + } + +} diff --git a/Source/MSBuild.Community.Tasks/Sync/FTP/EventArgs/GetDirListAsyncCompletedEventArgs.cs b/Source/MSBuild.Community.Tasks/Sync/FTP/EventArgs/GetDirListAsyncCompletedEventArgs.cs new file mode 100644 index 00000000..80ff39f4 --- /dev/null +++ b/Source/MSBuild.Community.Tasks/Sync/FTP/EventArgs/GetDirListAsyncCompletedEventArgs.cs @@ -0,0 +1,63 @@ +/* + * Authors: Benton Stark + * + * Copyright (c) 2007-2009 Starksoft, LLC (http://www.starksoft.com) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + */ + +using System; +using System.ComponentModel; + +namespace Starksoft.Net.Ftp +{ + /// + /// Provides data for the GetDirListAsyncCompleted event. + /// + public class GetDirListAsyncCompletedEventArgs : AsyncCompletedEventArgs + { + private FtpItemCollection _directoryListing; + + /// + /// Initializes a new instance of the PutFileAsyncCompletedEventArgs class. + /// + /// Any error that occurred during the asynchronous operation. + /// A value indicating whether the asynchronous operation was canceled. + /// A FtpItemCollection containing the directory listing. + public GetDirListAsyncCompletedEventArgs(Exception error, bool canceled, FtpItemCollection directoryListing) + : base(error, canceled, null) + { + _directoryListing = directoryListing; + } + + /// + /// Directory listing collection. + /// + public FtpItemCollection DirectoryListingResult + { + get + { + //base.RaiseExceptionIfNecessary(); + return _directoryListing; + } + } + } + +} diff --git a/Source/MSBuild.Community.Tasks/Sync/FTP/EventArgs/GetDirListDeepAsyncCompletedEventArgs.cs b/Source/MSBuild.Community.Tasks/Sync/FTP/EventArgs/GetDirListDeepAsyncCompletedEventArgs.cs new file mode 100644 index 00000000..aa5916f5 --- /dev/null +++ b/Source/MSBuild.Community.Tasks/Sync/FTP/EventArgs/GetDirListDeepAsyncCompletedEventArgs.cs @@ -0,0 +1,63 @@ +/* + * Authors: Benton Stark + * + * Copyright (c) 2007-2009 Starksoft, LLC (http://www.starksoft.com) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + */ + +using System; +using System.ComponentModel; + +namespace Starksoft.Net.Ftp +{ + /// + /// Provides data for the GetDirAsyncCompleted event. + /// + public class GetDirListDeepAsyncCompletedEventArgs : AsyncCompletedEventArgs + { + private FtpItemCollection _directoryListing; + + /// + /// Initializes a new instance of the GetDirAsyncCompletedEventArgs class. + /// + /// Any error that occurred during the asynchronous operation. + /// A value indicating whether the asynchronous operation was canceled. + /// A FtpItemCollection containing the directory listing. + public GetDirListDeepAsyncCompletedEventArgs(Exception error, bool canceled, FtpItemCollection directoryListing) + : base(error, canceled, null) + { + _directoryListing = directoryListing; + } + + /// + /// Directory listing collection. + /// + public FtpItemCollection DirectoryListingResult + { + get + { + //base.RaiseExceptionIfNecessary(); + return _directoryListing; + } + } + } + +} \ No newline at end of file diff --git a/Source/MSBuild.Community.Tasks/Sync/FTP/EventArgs/GetFileAsyncCompletedEventArgs.cs b/Source/MSBuild.Community.Tasks/Sync/FTP/EventArgs/GetFileAsyncCompletedEventArgs.cs new file mode 100644 index 00000000..b79ab6b5 --- /dev/null +++ b/Source/MSBuild.Community.Tasks/Sync/FTP/EventArgs/GetFileAsyncCompletedEventArgs.cs @@ -0,0 +1,47 @@ +/* + * Authors: Benton Stark + * + * Copyright (c) 2007-2009 Starksoft, LLC (http://www.starksoft.com) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + */ + +using System; +using System.ComponentModel; + +namespace Starksoft.Net.Ftp +{ + /// + /// Provides data for the GetFileAsyncCompleted event. + /// + public class GetFileAsyncCompletedEventArgs : AsyncCompletedEventArgs + { + /// + /// Initializes a new instance of the GetFileAsyncCompletedEventArgs class. + /// + /// Any error that occurred during the asynchronous operation. + /// A value indicating whether the asynchronous operation was canceled. + public GetFileAsyncCompletedEventArgs(Exception error, bool canceled) + : base(error, canceled, null) + { + } + } + +} \ No newline at end of file diff --git a/Source/MSBuild.Community.Tasks/Sync/FTP/EventArgs/OpenAsyncCompletedEventArgs.cs b/Source/MSBuild.Community.Tasks/Sync/FTP/EventArgs/OpenAsyncCompletedEventArgs.cs new file mode 100644 index 00000000..be3b4d12 --- /dev/null +++ b/Source/MSBuild.Community.Tasks/Sync/FTP/EventArgs/OpenAsyncCompletedEventArgs.cs @@ -0,0 +1,47 @@ +/* + * Authors: Benton Stark + * + * Copyright (c) 2007-2009 Starksoft, LLC (http://www.starksoft.com) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + */ + +using System; +using System.ComponentModel; + +namespace Starksoft.Net.Ftp +{ + /// + /// Provides data for the OpenAsyncCompleted event. + /// + public class OpenAsyncCompletedEventArgs : AsyncCompletedEventArgs + { + /// + /// Initializes a new instance of the OpenAsyncCompletedEventArgs class. + /// + /// Any error that occurred during the asynchronous operation. + /// A value indicating whether the asynchronous operation was canceled. + public OpenAsyncCompletedEventArgs(Exception error, bool canceled) + : base(error, canceled, null) + { + } + } + +} diff --git a/Source/MSBuild.Community.Tasks/Sync/FTP/EventArgs/PutFileAsyncCompletedEventArgs.cs b/Source/MSBuild.Community.Tasks/Sync/FTP/EventArgs/PutFileAsyncCompletedEventArgs.cs new file mode 100644 index 00000000..aea5442d --- /dev/null +++ b/Source/MSBuild.Community.Tasks/Sync/FTP/EventArgs/PutFileAsyncCompletedEventArgs.cs @@ -0,0 +1,47 @@ +/* + * Authors: Benton Stark + * + * Copyright (c) 2007-2009 Starksoft, LLC (http://www.starksoft.com) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + */ + +using System; +using System.ComponentModel; + +namespace Starksoft.Net.Ftp +{ + /// + /// Provides data for the PutFileAsyncCompleted event. + /// + public class PutFileAsyncCompletedEventArgs : AsyncCompletedEventArgs + { + /// + /// Initializes a new instance of the PutFileAsyncCompletedEventArgs class. + /// + /// Any error that occurred during the asynchronous operation. + /// A value indicating whether the asynchronous operation was canceled. + public PutFileAsyncCompletedEventArgs(Exception error, bool canceled) + : base(error, canceled, null) + { + } + } + +} diff --git a/Source/MSBuild.Community.Tasks/Sync/FTP/EventArgs/PutFileUniqueAsyncCompletedEventArgs.cs b/Source/MSBuild.Community.Tasks/Sync/FTP/EventArgs/PutFileUniqueAsyncCompletedEventArgs.cs new file mode 100644 index 00000000..8bf14bbb --- /dev/null +++ b/Source/MSBuild.Community.Tasks/Sync/FTP/EventArgs/PutFileUniqueAsyncCompletedEventArgs.cs @@ -0,0 +1,36 @@ +///////////////////////////////////////////////////////////// +//// +//// Copyright (c) 2007 Starksoft, LLC +//// All Rights Reserved. +//// +///////////////////////////////////////////////////////////// + +using System; + +namespace Starksoft.Net.Ftp +{ + public class PutFileUniqueAsyncCompletedEventArgs : EventArgs + { + private Exception _error; + private bool _cancelled; + + public PutFileUniqueAsyncCompletedEventArgs(Exception error, bool cancelled) + { + _error = error; + _cancelled = cancelled; + } + + public Exception Error + { + get { return _error; } + } + + public bool Cancelled + { + get { return _cancelled; } + } + + } + +} + diff --git a/Source/MSBuild.Community.Tasks/Sync/FTP/EventArgs/TransferCompleteEventArgs.cs b/Source/MSBuild.Community.Tasks/Sync/FTP/EventArgs/TransferCompleteEventArgs.cs new file mode 100644 index 00000000..7eee8f1e --- /dev/null +++ b/Source/MSBuild.Community.Tasks/Sync/FTP/EventArgs/TransferCompleteEventArgs.cs @@ -0,0 +1,87 @@ +/* + * Authors: Benton Stark + * + * Copyright (c) 2007-2009 Starksoft, LLC (http://www.starksoft.com) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + */ + +using System; + +namespace Starksoft.Net.Ftp +{ + /// + /// Event arguments to facilitate the transfer complete event. + /// + public class TransferCompleteEventArgs : EventArgs + { + + private long _bytesTransferred; + private int _bytesPerSecond; + private TimeSpan _elapsedTime; + + /// + /// Constructor. + /// + /// The total number of bytes transferred. + /// The data transfer speed in bytes per second. + /// The time that has elapsed since the data transfer started. + public TransferCompleteEventArgs(long bytesTransferred, int bytesPerSecond, TimeSpan elapsedTime) + { + _bytesTransferred = bytesTransferred; + _bytesPerSecond = bytesPerSecond; + _elapsedTime = elapsedTime; + } + + /// + /// The total number of bytes transferred. + /// + public long BytesTransferred + { + get { return _bytesTransferred; } + } + + /// + /// Gets the data transfer speed in bytes per second. + /// + public int BytesPerSecond + { + get { return _bytesPerSecond; } + } + + /// + /// Gets the data transfer speed in kilobytes per second. + /// + public int KilobytesPerSecond + { + get { return _bytesPerSecond / 1024; } + } + + /// + /// Gets the time that has elapsed since the data transfer started. + /// + public TimeSpan ElapsedTime + { + get { return _elapsedTime; } + } + + + } +} diff --git a/Source/MSBuild.Community.Tasks/Sync/FTP/EventArgs/TransferProgressEventArgs.cs b/Source/MSBuild.Community.Tasks/Sync/FTP/EventArgs/TransferProgressEventArgs.cs new file mode 100644 index 00000000..2fefd5e2 --- /dev/null +++ b/Source/MSBuild.Community.Tasks/Sync/FTP/EventArgs/TransferProgressEventArgs.cs @@ -0,0 +1,84 @@ +/* +Copyright (c) 2007-2009 Benton Stark, Starksoft LLC (http://www.starksoft.com) + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +*/ + +using System; + +namespace Starksoft.Net.Ftp +{ + /// + /// Event arguments to facilitate the transfer progress event. + /// + public class TransferProgressEventArgs : EventArgs + { + + private int _bytesTransferred; + private int _bytesPerSecond; + private TimeSpan _elapsedTime; + + /// + /// Constructor. + /// + /// The number of bytes transferred. + /// The data transfer speed in bytes per second. + /// The time that has elapsed since the data transfer started. + public TransferProgressEventArgs(int bytesTransferred, int bytesPerSecond, TimeSpan elapsedTime) + { + _bytesTransferred = bytesTransferred; + _bytesPerSecond = bytesPerSecond; + _elapsedTime = elapsedTime; + } + + /// + /// The number of bytes transferred. + /// + public int BytesTransferred + { + get { return _bytesTransferred; } + } + + /// + /// Gets the data transfer speed in bytes per second. + /// + public int BytesPerSecond + { + get { return _bytesPerSecond; } + } + + /// + /// Gets the data transfer speed in kilobytes per second. + /// + public int KilobytesPerSecond + { + get { return _bytesPerSecond / 1024; } + } + + /// + /// Gets the time that has elapsed since the data transfer started. + /// + public TimeSpan ElapsedTime + { + get { return _elapsedTime; } + } + + + } +} diff --git a/Source/MSBuild.Community.Tasks/Sync/FTP/EventArgs/ValidateServerCertificateEventArgs.cs b/Source/MSBuild.Community.Tasks/Sync/FTP/EventArgs/ValidateServerCertificateEventArgs.cs new file mode 100644 index 00000000..ec4139d5 --- /dev/null +++ b/Source/MSBuild.Community.Tasks/Sync/FTP/EventArgs/ValidateServerCertificateEventArgs.cs @@ -0,0 +1,95 @@ +/* + * Authors: Benton Stark + * + * Copyright (c) 2007-2009 Starksoft, LLC (http://www.starksoft.com) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + */ + +using System; +using System.Security.Cryptography.X509Certificates; +using System.Net.Security; + +namespace Starksoft.Net.Ftp +{ + /// + /// Event arguments to facilitate the FtpClient transfer progress and complete events. + /// + public class ValidateServerCertificateEventArgs : EventArgs + { + + private X509Certificate2 _certificate; + private X509Chain _chain; + private SslPolicyErrors _policyErrors; + private bool _isCertificateValid; + + /// + /// ValidateServerCertificateEventArgs constructor. + /// + /// X.509 certificate object. + /// X.509 certificate chain. + /// SSL policy errors. + public ValidateServerCertificateEventArgs(X509Certificate2 certificate, X509Chain chain, SslPolicyErrors policyErrors) + { + _certificate = certificate; + _chain = chain; + _policyErrors = policyErrors; + } + + /// + /// The X.509 version 3 server certificate. + /// + public X509Certificate2 Certificate + { + get { return _certificate; } + } + + /// + /// Server chain building engine for server certificate. + /// + public X509Chain Chain + { + get { return _chain; } + } + + /// + /// Enumeration representing SSL (Secure Socket Layer) errors. + /// + public SslPolicyErrors PolicyErrors + { + get { return _policyErrors; } + } + + /// + /// Boolean value indicating if the server certificate is valid and can + /// be accepted by the FtpClient object. + /// + /// + /// Default value is false which results in certificate being rejected and the SSL + /// connection abandoned. Set this value to true to accept the server certificate + /// otherwise the SSL connection will be closed. + /// + public bool IsCertificateValid + { + get { return _isCertificateValid; } + set { _isCertificateValid = value; } + } + } +} diff --git a/Source/MSBuild.Community.Tasks/Sync/FTP/Exceptions/FtpAsynchronousOperationException.cs b/Source/MSBuild.Community.Tasks/Sync/FTP/Exceptions/FtpAsynchronousOperationException.cs new file mode 100644 index 00000000..5cdf57e4 --- /dev/null +++ b/Source/MSBuild.Community.Tasks/Sync/FTP/Exceptions/FtpAsynchronousOperationException.cs @@ -0,0 +1,99 @@ +/* + * Authors: Benton Stark + * + * Copyright (c) 2007-2009 Starksoft, LLC (http://www.starksoft.com) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + */ + +using System; +using System.Runtime.Serialization; + +namespace Starksoft.Net.Ftp +{ + + /// + /// This exception is thrown when an asynchronous operation fails or is cancelled. + /// + [Serializable()] + public class FtpAsynchronousOperationException : FtpException + { + /// + /// Constructor. + /// + public FtpAsynchronousOperationException() + { + } + + /// + /// Constructor. + /// + /// Exception message text. + public FtpAsynchronousOperationException(string message) + : base(message) + { + } + + /// + /// Constructor. + /// + /// Exception message text. + /// The inner exception object. + public FtpAsynchronousOperationException(string message, Exception innerException) + : + base(message, innerException) + { + } + + /// + /// Constructor. + /// + /// Serialization information. + /// Stream context information. + protected FtpAsynchronousOperationException(SerializationInfo info, + StreamingContext context) + : base(info, context) + { + } + + /// + /// Constructor. + /// + /// Exception message text. + /// Ftp response object. + /// The inner exception object. + public FtpAsynchronousOperationException(string message, FtpResponse response, Exception innerException) + : + base(message, response, innerException) + { } + + + /// + /// Constructor. + /// + /// Exception message text. + /// Ftp response object. + public FtpAsynchronousOperationException(string message, FtpResponse response) + : base(message, response) + { } + + } + +} diff --git a/Source/MSBuild.Community.Tasks/Sync/FTP/Exceptions/FtpAuthenticationException.cs b/Source/MSBuild.Community.Tasks/Sync/FTP/Exceptions/FtpAuthenticationException.cs new file mode 100644 index 00000000..e2ea33c2 --- /dev/null +++ b/Source/MSBuild.Community.Tasks/Sync/FTP/Exceptions/FtpAuthenticationException.cs @@ -0,0 +1,99 @@ +/* + * Authors: Benton Stark + * + * Copyright (c) 2007-2009 Starksoft, LLC (http://www.starksoft.com) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + */ + +using System; +using System.Runtime.Serialization; + +namespace Starksoft.Net.Ftp +{ + + /// + /// This exception is thrown when an error occurs during the FTP authentication process. + /// + [Serializable()] + public class FtpAuthenticationException : FtpException + { + /// + /// Constructor. + /// + public FtpAuthenticationException() + { + } + + /// + /// Constructor. + /// + /// Exception message text. + public FtpAuthenticationException(string message) + : base(message) + { + } + + /// + /// Constructor. + /// + /// Exception message text. + /// The inner exception object. + public FtpAuthenticationException(string message, Exception innerException) + : + base(message, innerException) + { + } + + /// + /// Constructor. + /// + /// Serialization information. + /// Stream context information. + protected FtpAuthenticationException(SerializationInfo info, + StreamingContext context) + : base(info, context) + { + } + + /// + /// Constructor. + /// + /// Exception message text. + /// Ftp response object. + /// The inner exception object. + public FtpAuthenticationException(string message, FtpResponse response, Exception innerException) + : + base(message, response, innerException) + { } + + + /// + /// Constructor. + /// + /// Exception message text. + /// Ftp response object. + public FtpAuthenticationException(string message, FtpResponse response) + : base(message, response) + { } + + } + +} \ No newline at end of file diff --git a/Source/MSBuild.Community.Tasks/Sync/FTP/Exceptions/FtpCertificateValidationException.cs b/Source/MSBuild.Community.Tasks/Sync/FTP/Exceptions/FtpCertificateValidationException.cs new file mode 100644 index 00000000..c3ec42ae --- /dev/null +++ b/Source/MSBuild.Community.Tasks/Sync/FTP/Exceptions/FtpCertificateValidationException.cs @@ -0,0 +1,100 @@ +/* + * Authors: Benton Stark + * + * Copyright (c) 2007-2009 Starksoft, LLC (http://www.starksoft.com) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + */ + +using System; +using System.Runtime.Serialization; + +namespace Starksoft.Net.Ftp +{ + + /// + /// This exception is thrown when an X.509 certificate fails validation when establishing a secure command or data connection + /// to the FTP server. + /// + [Serializable()] + public class FtpCertificateValidationException : FtpSecureConnectionException + { + /// + /// Constructor. + /// + public FtpCertificateValidationException() + { + } + + /// + /// Constructor. + /// + /// Exception message text. + public FtpCertificateValidationException(string message) + : base(message) + { + } + + /// + /// Constructor. + /// + /// Exception message text. + /// The inner exception object. + public FtpCertificateValidationException(string message, Exception innerException) + : + base(message, innerException) + { + } + + /// + /// Constructor. + /// + /// Serialization information. + /// Stream context information. + protected FtpCertificateValidationException(SerializationInfo info, + StreamingContext context) + : base(info, context) + { + } + + /// + /// Constructor. + /// + /// Exception message text. + /// Ftp response object. + /// The inner exception object. + public FtpCertificateValidationException(string message, FtpResponse response, Exception innerException) + : + base(message, response, innerException) + { } + + + /// + /// Constructor. + /// + /// Exception message text. + /// Ftp response object. + public FtpCertificateValidationException(string message, FtpResponse response) + : base(message, response) + { } + + } + +} diff --git a/Source/MSBuild.Community.Tasks/Sync/FTP/Exceptions/FtpCommandResponseTimeoutException.cs b/Source/MSBuild.Community.Tasks/Sync/FTP/Exceptions/FtpCommandResponseTimeoutException.cs new file mode 100644 index 00000000..16f1cb10 --- /dev/null +++ b/Source/MSBuild.Community.Tasks/Sync/FTP/Exceptions/FtpCommandResponseTimeoutException.cs @@ -0,0 +1,100 @@ +/* + * Authors: Benton Stark + * + * Copyright (c) 2007-2009 Starksoft, LLC (http://www.starksoft.com) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + */ + +using System; +using System.Runtime.Serialization; + +namespace Starksoft.Net.Ftp +{ + + /// + /// This exception is thrown when the server fails to respond to an FTP command in a timely manner. + /// The waiting time can be adjusted by specifing a different value for the CommandTimeout property. + /// + [Serializable()] + public class FtpCommandResponseTimeoutException : FtpException + { + /// + /// Constructor. + /// + public FtpCommandResponseTimeoutException() + { + } + + /// + /// Constructor. + /// + /// Exception message text. + public FtpCommandResponseTimeoutException(string message) + : base(message) + { + } + + /// + /// Constructor. + /// + /// Exception message text. + /// The inner exception object. + public FtpCommandResponseTimeoutException(string message, Exception innerException) + : + base(message, innerException) + { + } + + /// + /// Constructor. + /// + /// Serialization information. + /// Stream context information. + protected FtpCommandResponseTimeoutException(SerializationInfo info, + StreamingContext context) + : base(info, context) + { + } + + /// + /// Constructor. + /// + /// Exception message text. + /// Ftp response object. + /// The inner exception object. + public FtpCommandResponseTimeoutException(string message, FtpResponse response, Exception innerException) + : + base(message, response, innerException) + { } + + + /// + /// Constructor. + /// + /// Exception message text. + /// Ftp response object. + public FtpCommandResponseTimeoutException(string message, FtpResponse response) + : base(message, response) + { } + + } + +} diff --git a/Source/MSBuild.Community.Tasks/Sync/FTP/Exceptions/FtpConnectionBrokenException.cs b/Source/MSBuild.Community.Tasks/Sync/FTP/Exceptions/FtpConnectionBrokenException.cs new file mode 100644 index 00000000..70eb21e6 --- /dev/null +++ b/Source/MSBuild.Community.Tasks/Sync/FTP/Exceptions/FtpConnectionBrokenException.cs @@ -0,0 +1,98 @@ +/* + * Authors: Benton Stark + * + * Copyright (c) 2007-2009 Starksoft, LLC (http://www.starksoft.com) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + */ + +using System; +using System.Runtime.Serialization; + +namespace Starksoft.Net.Ftp +{ + + /// + /// This exception is thrown when a ftp connection is broken and fails. + /// + [Serializable()] + public class FtpConnectionBrokenException : FtpException + { + /// + /// Constructor. + /// + public FtpConnectionBrokenException() + { + } + + /// + /// Constructor. + /// + /// Exception message text. + public FtpConnectionBrokenException(string message) + : base(message) + { + } + + /// + /// Constructor. + /// + /// Exception message text. + /// The inner exception object. + public FtpConnectionBrokenException(string message, Exception innerException) + : + base(message, innerException) + { + } + + /// + /// Constructor. + /// + /// Serialization information. + /// Stream context information. + protected FtpConnectionBrokenException(SerializationInfo info, + StreamingContext context) + : base(info, context) + { + } + + /// + /// Constructor. + /// + /// Exception message text. + /// Ftp response object. + /// The inner exception object. + public FtpConnectionBrokenException(string message, FtpResponse response, Exception innerException) + : + base(message, response, innerException) + { } + + + /// + /// Constructor. + /// + /// Exception message text. + /// Ftp response object. + public FtpConnectionBrokenException(string message, FtpResponse response) + : base(message, response) + { } + } + +} diff --git a/Source/MSBuild.Community.Tasks/Sync/FTP/Exceptions/FtpConnectionClosedException.cs b/Source/MSBuild.Community.Tasks/Sync/FTP/Exceptions/FtpConnectionClosedException.cs new file mode 100644 index 00000000..d46ad8f9 --- /dev/null +++ b/Source/MSBuild.Community.Tasks/Sync/FTP/Exceptions/FtpConnectionClosedException.cs @@ -0,0 +1,99 @@ +/* + * Authors: Benton Stark + * + * Copyright (c) 2007-2009 Starksoft, LLC (http://www.starksoft.com) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + */ + +using System; +using System.Runtime.Serialization; + +namespace Starksoft.Net.Ftp +{ + + /// + /// This exception is thrown when an error occurs during the FTP connection is closed while attempting to transmit data. + /// + [Serializable()] + public class FtpConnectionClosedException : FtpException + { + /// + /// Constructor. + /// + public FtpConnectionClosedException() + { + } + + /// + /// Constructor. + /// + /// Exception message text. + public FtpConnectionClosedException(string message) + : base(message) + { + } + + /// + /// Constructor. + /// + /// Exception message text. + /// The inner exception object. + public FtpConnectionClosedException(string message, Exception innerException) + : + base(message, innerException) + { + } + + /// + /// Constructor. + /// + /// Serialization information. + /// Stream context information. + protected FtpConnectionClosedException(SerializationInfo info, + StreamingContext context) + : base(info, context) + { + } + + /// + /// Constructor. + /// + /// Exception message text. + /// Ftp response object. + /// The inner exception object. + public FtpConnectionClosedException(string message, FtpResponse response, Exception innerException) + : + base(message, response, innerException) + { } + + + /// + /// Constructor. + /// + /// Exception message text. + /// Ftp response object. + public FtpConnectionClosedException(string message, FtpResponse response) + : base(message, response) + { } + + } + +} \ No newline at end of file diff --git a/Source/MSBuild.Community.Tasks/Sync/FTP/Exceptions/FtpConnectionOpenException.cs b/Source/MSBuild.Community.Tasks/Sync/FTP/Exceptions/FtpConnectionOpenException.cs new file mode 100644 index 00000000..85cef4e8 --- /dev/null +++ b/Source/MSBuild.Community.Tasks/Sync/FTP/Exceptions/FtpConnectionOpenException.cs @@ -0,0 +1,99 @@ +/* + * Authors: Benton Stark + * + * Copyright (c) 2007-2009 Starksoft, LLC (http://www.starksoft.com) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + */ + +using System; +using System.Runtime.Serialization; + +namespace Starksoft.Net.Ftp +{ + + /// + /// This exception is thrown when an error occurs while attempt to open a command connection + /// to the FTP server. + /// + [Serializable()] + public class FtpConnectionOpenException : FtpException + { + /// + /// Constructor. + /// + public FtpConnectionOpenException() + { + } + + /// + /// Constructor. + /// + /// Exception message text. + public FtpConnectionOpenException(string message) + : base(message) + { + } + + /// + /// Constructor. + /// + /// Exception message text. + /// The inner exception object. + public FtpConnectionOpenException(string message, Exception innerException) + : + base(message, innerException) + { + } + + /// + /// Constructor. + /// + /// Serialization information. + /// Stream context information. + protected FtpConnectionOpenException(SerializationInfo info, + StreamingContext context) + : base(info, context) + { + } + + /// + /// Constructor. + /// + /// Exception message text. + /// Ftp response object. + /// The inner exception object. + public FtpConnectionOpenException(string message, FtpResponse response, Exception innerException) + : base(message, response, innerException) + { } + + + /// + /// Constructor. + /// + /// Exception message text. + /// Ftp response object. + public FtpConnectionOpenException(string message, FtpResponse response) + : base(message, response) + { } + + } + +} diff --git a/Source/MSBuild.Community.Tasks/Sync/FTP/Exceptions/FtpDataCompressionException.cs b/Source/MSBuild.Community.Tasks/Sync/FTP/Exceptions/FtpDataCompressionException.cs new file mode 100644 index 00000000..515e6eff --- /dev/null +++ b/Source/MSBuild.Community.Tasks/Sync/FTP/Exceptions/FtpDataCompressionException.cs @@ -0,0 +1,98 @@ +/* + * Authors: Benton Stark + * + * Copyright (c) 2007-2009 Starksoft, LLC (http://www.starksoft.com) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + */ + +using System; +using System.Runtime.Serialization; + +namespace Starksoft.Net.Ftp +{ + + /// + /// This exception is thrown when the FTP server is unable to enable or disable data compression. + /// + [Serializable()] + public class FtpDataCompressionException : FtpException + { + /// + /// Constructor. + /// + public FtpDataCompressionException() + { + } + + /// + /// Constructor. + /// + /// Exception message text. + public FtpDataCompressionException(string message) + : base(message) + { + } + + /// + /// Constructor. + /// + /// Exception message text. + /// The inner exception object. + public FtpDataCompressionException(string message, Exception innerException) + : + base(message, innerException) + { + } + + /// + /// Constructor. + /// + /// Serialization information. + /// Stream context information. + protected FtpDataCompressionException(SerializationInfo info, + StreamingContext context) + : base(info, context) + { + } + + /// + /// Constructor. + /// + /// Exception message text. + /// Ftp response object. + /// The inner exception object. + public FtpDataCompressionException(string message, FtpResponse response, Exception innerException) + : base(message, response, innerException) + { } + + + /// + /// Constructor. + /// + /// Exception message text. + /// Ftp response object. + public FtpDataCompressionException(string message, FtpResponse response) + : base(message, response) + { } + + } + +} diff --git a/Source/MSBuild.Community.Tasks/Sync/FTP/Exceptions/FtpDataConnectionException.cs b/Source/MSBuild.Community.Tasks/Sync/FTP/Exceptions/FtpDataConnectionException.cs new file mode 100644 index 00000000..984ee2d9 --- /dev/null +++ b/Source/MSBuild.Community.Tasks/Sync/FTP/Exceptions/FtpDataConnectionException.cs @@ -0,0 +1,101 @@ +/* + * Authors: Benton Stark + * + * Copyright (c) 2007-2009 Starksoft, LLC (http://www.starksoft.com) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + */ + +using System; +using System.Runtime.Serialization; + +namespace Starksoft.Net.Ftp +{ + + /// + /// This exception is thrown when the FTP client is unable to establish a data connection with the FTP server. + /// Data connection are temporary, secondary connnections used to transfer files and other types of data between the + /// FTP client and the FTP server. The method in which data connections are established is determined by the type + /// of data transfer mode specified when connection to an FTP server (e.g. Passive, Active) + /// + [Serializable()] + public class FtpDataConnectionException : FtpConnectionClosedException + { + /// + /// Constructor. + /// + public FtpDataConnectionException() + { + } + + /// + /// Constructor. + /// + /// Exception message text. + public FtpDataConnectionException(string message) + : base(message) + { + } + + /// + /// Constructor. + /// + /// Exception message text. + /// The inner exception object. + public FtpDataConnectionException(string message, Exception innerException) + : + base(message, innerException) + { + } + + /// + /// Constructor. + /// + /// Serialization information. + /// Stream context information. + protected FtpDataConnectionException(SerializationInfo info, + StreamingContext context) + : base(info, context) + { + } + + /// + /// Constructor. + /// + /// Exception message text. + /// Ftp response object. + /// The inner exception object. + public FtpDataConnectionException(string message, FtpResponse response, Exception innerException) + : base(message, response, innerException) + { } + + + /// + /// Constructor. + /// + /// Exception message text. + /// Ftp response object. + public FtpDataConnectionException(string message, FtpResponse response) + : base(message, response) + { } + + } + +} \ No newline at end of file diff --git a/Source/MSBuild.Community.Tasks/Sync/FTP/Exceptions/FtpDataConnectionTimeoutException.cs b/Source/MSBuild.Community.Tasks/Sync/FTP/Exceptions/FtpDataConnectionTimeoutException.cs new file mode 100644 index 00000000..0e60242c --- /dev/null +++ b/Source/MSBuild.Community.Tasks/Sync/FTP/Exceptions/FtpDataConnectionTimeoutException.cs @@ -0,0 +1,99 @@ +/* + * Authors: Benton Stark + * + * Copyright (c) 2007-2009 Starksoft, LLC (http://www.starksoft.com) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + */ + +using System; +using System.Runtime.Serialization; + +namespace Starksoft.Net.Ftp +{ + + /// + /// This exception is thrown when the server fails to respond to an FTP data connection in a timely manner. + /// The waiting time can be adjusted by specifing a different value for the TransferTimeout property. + /// + [Serializable()] + public class FtpDataConnectionTimeoutException : FtpDataConnectionException + { + /// + /// Constructor. + /// + public FtpDataConnectionTimeoutException() + { + } + + /// + /// Constructor. + /// + /// Exception message text. + public FtpDataConnectionTimeoutException(string message) + : base(message) + { + } + + /// + /// Constructor. + /// + /// Exception message text. + /// The inner exception object. + public FtpDataConnectionTimeoutException(string message, Exception innerException) + : + base(message, innerException) + { + } + + /// + /// Constructor. + /// + /// Serialization information. + /// Stream context information. + protected FtpDataConnectionTimeoutException(SerializationInfo info, + StreamingContext context) + : base(info, context) + { + } + + /// + /// Constructor. + /// + /// Exception message text. + /// Ftp response object. + /// The inner exception object. + public FtpDataConnectionTimeoutException(string message, FtpResponse response, Exception innerException) + : base(message, response, innerException) + { } + + + /// + /// Constructor. + /// + /// Exception message text. + /// Ftp response object. + public FtpDataConnectionTimeoutException(string message, FtpResponse response) + : base(message, response) + { } + + } + +} diff --git a/Source/MSBuild.Community.Tasks/Sync/FTP/Exceptions/FtpDataTransferException.cs b/Source/MSBuild.Community.Tasks/Sync/FTP/Exceptions/FtpDataTransferException.cs new file mode 100644 index 00000000..081af0be --- /dev/null +++ b/Source/MSBuild.Community.Tasks/Sync/FTP/Exceptions/FtpDataTransferException.cs @@ -0,0 +1,98 @@ +/* + * Authors: Benton Stark + * + * Copyright (c) 2007-2009 Starksoft, LLC (http://www.starksoft.com) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + */ + +using System; +using System.Runtime.Serialization; + +namespace Starksoft.Net.Ftp +{ + + /// + /// This exception is thrown when a data transfer fails. + /// + [Serializable()] + public class FtpDataTransferException : FtpException + { + /// + /// Constructor. + /// + public FtpDataTransferException() + { + } + + /// + /// Constructor. + /// + /// Exception message text. + public FtpDataTransferException(string message) + : base(message) + { + } + + /// + /// Constructor. + /// + /// Exception message text. + /// The inner exception object. + public FtpDataTransferException(string message, Exception innerException) + : + base(message, innerException) + { + } + + /// + /// Constructor. + /// + /// Serialization information. + /// Stream context information. + protected FtpDataTransferException(SerializationInfo info, + StreamingContext context) + : base(info, context) + { + } + + /// + /// Constructor. + /// + /// Exception message text. + /// Ftp response object. + /// The inner exception object. + public FtpDataTransferException(string message, FtpResponse response, Exception innerException) + : base(message, response, innerException) + { } + + + /// + /// Constructor. + /// + /// Exception message text. + /// Ftp response object. + public FtpDataTransferException(string message, FtpResponse response) + : base(message, response) + { } + + } + +} diff --git a/Source/MSBuild.Community.Tasks/Sync/FTP/Exceptions/FtpException.cs b/Source/MSBuild.Community.Tasks/Sync/FTP/Exceptions/FtpException.cs new file mode 100644 index 00000000..f2f16294 --- /dev/null +++ b/Source/MSBuild.Community.Tasks/Sync/FTP/Exceptions/FtpException.cs @@ -0,0 +1,129 @@ +/* + * Authors: Benton Stark + * + * Copyright (c) 2007-2009 Starksoft, LLC (http://www.starksoft.com) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + */ + +using System; +using System.Runtime.Serialization; + +namespace Starksoft.Net.Ftp +{ + + /// + /// This exception is thrown when a general FTP exception occurs. + /// + [Serializable()] + public class FtpException : Exception + { + private FtpResponse _response = new FtpResponse(); + + /// + /// Constructor. + /// + public FtpException() + { + } + + /// + /// Constructor. + /// + /// Exception message text. + public FtpException(string message) + : base(message) + { + } + + + /// + /// Constructor. + /// + /// Exception message text. + /// The inner exception object. + public FtpException(string message, Exception innerException) + : + base(message, innerException) + { + } + + /// + /// Constructor. + /// + /// Exception message text. + /// Response object. + /// The inner exception object. + public FtpException(string message, FtpResponse response, Exception innerException) + : + base(message, innerException) + { + _response = response; + } + + + /// + /// Constructor. + /// + /// Exception message text. + /// Ftp response object. + public FtpException(string message, FtpResponse response) + : base(message) + { + _response = response; + } + + + /// + /// Constructor. + /// + /// Serialization information. + /// Stream context information. + protected FtpException(SerializationInfo info, + StreamingContext context) + : base(info, context) + { + } + + /// + /// Gets the last FTP response if one is available. + /// + public FtpResponse LastResponse + { + get { return _response; } + } + + + /// + /// Gets a message that describes the current exception. + /// + public override string Message + { + get + { + if (_response.Code == FtpResponseCode.None) + return base.Message; + else + return String.Format("{0} (Last Server Response: {1} {2})", base.Message, _response.Text, _response.Code); ; + } + } + } + +} diff --git a/Source/MSBuild.Community.Tasks/Sync/FTP/Exceptions/FtpFileIntegrityException.cs b/Source/MSBuild.Community.Tasks/Sync/FTP/Exceptions/FtpFileIntegrityException.cs new file mode 100644 index 00000000..e0bb0b81 --- /dev/null +++ b/Source/MSBuild.Community.Tasks/Sync/FTP/Exceptions/FtpFileIntegrityException.cs @@ -0,0 +1,98 @@ +/* + * Authors: Benton Stark + * + * Copyright (c) 2007-2009 Starksoft, LLC (http://www.starksoft.com) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + */ + +using System; +using System.Runtime.Serialization; + +namespace Starksoft.Net.Ftp +{ + + /// + /// This exception is thrown when a file integrity check fails. + /// + [Serializable()] + public class FtpFileIntegrityException : FtpException + { + /// + /// Constructor. + /// + public FtpFileIntegrityException() + { + } + + /// + /// Constructor. + /// + /// Exception message text. + public FtpFileIntegrityException(string message) + : base(message) + { + } + + /// + /// Constructor. + /// + /// Exception message text. + /// The inner exception object. + public FtpFileIntegrityException(string message, Exception innerException) + : + base(message, innerException) + { + } + + /// + /// Constructor. + /// + /// Serialization information. + /// Stream context information. + protected FtpFileIntegrityException(SerializationInfo info, + StreamingContext context) + : base(info, context) + { + } + + /// + /// Constructor. + /// + /// Exception message text. + /// Ftp response object. + /// The inner exception object. + public FtpFileIntegrityException(string message, FtpResponse response, Exception innerException) + : base(message, response, innerException) + { } + + + /// + /// Constructor. + /// + /// Exception message text. + /// Ftp response object. + public FtpFileIntegrityException(string message, FtpResponse response) + : base(message, response) + { } + + } + +} diff --git a/Source/MSBuild.Community.Tasks/Sync/FTP/Exceptions/FtpLoginException.cs b/Source/MSBuild.Community.Tasks/Sync/FTP/Exceptions/FtpLoginException.cs new file mode 100644 index 00000000..c0621b15 --- /dev/null +++ b/Source/MSBuild.Community.Tasks/Sync/FTP/Exceptions/FtpLoginException.cs @@ -0,0 +1,99 @@ + +/* + * Authors: Benton Stark + * + * Copyright (c) 2007-2009 Starksoft, LLC (http://www.starksoft.com) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + */ + +using System; +using System.Runtime.Serialization; + +namespace Starksoft.Net.Ftp +{ + + /// + /// This exception is thrown when an error occurs during the FTP login process. + /// + [Serializable()] + public class FtpLoginException : FtpException + { + /// + /// Constructor. + /// + public FtpLoginException() + { + } + + /// + /// Constructor. + /// + /// Exception message text. + public FtpLoginException(string message) + : base(message) + { + } + + /// + /// Constructor. + /// + /// Exception message text. + /// The inner exception object. + public FtpLoginException(string message, Exception innerException) + : + base(message, innerException) + { + } + + /// + /// Constructor. + /// + /// Serialization information. + /// Stream context information. + protected FtpLoginException(SerializationInfo info, + StreamingContext context) + : base(info, context) + { + } + + /// + /// Constructor. + /// + /// Exception message text. + /// Ftp response object. + /// The inner exception object. + public FtpLoginException(string message, FtpResponse response, Exception innerException) + : base(message, response, innerException) + { } + + + /// + /// Constructor. + /// + /// Exception message text. + /// Ftp response object. + public FtpLoginException(string message, FtpResponse response) + : base(message, response) + { } + + } + +} \ No newline at end of file diff --git a/Source/MSBuild.Community.Tasks/Sync/FTP/Exceptions/FtpProxyException.cs b/Source/MSBuild.Community.Tasks/Sync/FTP/Exceptions/FtpProxyException.cs new file mode 100644 index 00000000..b1cbbd99 --- /dev/null +++ b/Source/MSBuild.Community.Tasks/Sync/FTP/Exceptions/FtpProxyException.cs @@ -0,0 +1,99 @@ +/* + * Authors: Benton Stark + * + * Copyright (c) 2007-2009 Starksoft, LLC (http://www.starksoft.com) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + */ + +using System; +using System.Runtime.Serialization; + +namespace Starksoft.Net.Ftp +{ + + /// + /// This exception is thrown when an exception occurs while opening a connection to the FTP + /// server using a proxy. See the inner exception for more information when this exception is thrown. + /// + [Serializable()] + public class FtpProxyException : FtpException + { + /// + /// Constructor. + /// + public FtpProxyException() + { + } + + /// + /// Constructor. + /// + /// Exception message text. + public FtpProxyException(string message) + : base(message) + { + } + + /// + /// Constructor. + /// + /// Exception message text. + /// The inner exception object. + public FtpProxyException(string message, Exception innerException) + : + base(message, innerException) + { + } + + /// + /// Constructor. + /// + /// Serialization information. + /// Stream context information. + protected FtpProxyException(SerializationInfo info, + StreamingContext context) + : base(info, context) + { + } + + /// + /// Constructor. + /// + /// Exception message text. + /// Ftp response object. + /// The inner exception object. + public FtpProxyException(string message, FtpResponse response, Exception innerException) + : base(message, response, innerException) + { } + + + /// + /// Constructor. + /// + /// Exception message text. + /// Ftp response object. + public FtpProxyException(string message, FtpResponse response) + : base(message, response) + { } + + } + +} diff --git a/Source/MSBuild.Community.Tasks/Sync/FTP/Exceptions/FtpResponseException.cs b/Source/MSBuild.Community.Tasks/Sync/FTP/Exceptions/FtpResponseException.cs new file mode 100644 index 00000000..5ca82294 --- /dev/null +++ b/Source/MSBuild.Community.Tasks/Sync/FTP/Exceptions/FtpResponseException.cs @@ -0,0 +1,81 @@ +/* + * Authors: Benton Stark + * + * Copyright (c) 2007-2009 Starksoft, LLC (http://www.starksoft.com) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + */ + +using System; +using System.Runtime.Serialization; + +namespace Starksoft.Net.Ftp +{ + + /// + /// This exception is thrown when a file integrity check fails. + /// For detailed information about the error, the FTP server response + /// can be inspected via the Reponse property on this exception. + /// + [Serializable()] + public class FtpResponseException : FtpException + { + /// + /// Constructor. + /// + public FtpResponseException() + { + } + + /// + /// Constructor. + /// + /// Exception message text. + /// Ftp response object. + public FtpResponseException(string message, FtpResponse response) + : base(message, response) + { } + + /// + /// Constructor. + /// + /// Ftp response object. + /// Exception message text. + /// The inner exception object. + public FtpResponseException(string message, FtpResponse response, Exception innerException) + : + base(message, response, innerException) + { + } + + /// + /// Constructor. + /// + /// Serialization information. + /// Stream context information. + protected FtpResponseException(SerializationInfo info, + StreamingContext context) + : base(info, context) + { + } + + } + +} diff --git a/Source/MSBuild.Community.Tasks/Sync/FTP/Exceptions/FtpSecureConnectionException.cs b/Source/MSBuild.Community.Tasks/Sync/FTP/Exceptions/FtpSecureConnectionException.cs new file mode 100644 index 00000000..6ad97f8c --- /dev/null +++ b/Source/MSBuild.Community.Tasks/Sync/FTP/Exceptions/FtpSecureConnectionException.cs @@ -0,0 +1,97 @@ +/* + * Authors: Benton Stark + * + * Copyright (c) 2007-2009 Starksoft, LLC (http://www.starksoft.com) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + */ + +using System; +using System.Runtime.Serialization; + +namespace Starksoft.Net.Ftp +{ + + /// + /// This exception is thrown when an error occurs with a secure command or data connection to the FTP server. + /// + [Serializable()] + public class FtpSecureConnectionException : FtpException + { + /// + /// Constructor. + /// + public FtpSecureConnectionException() + { + } + + /// + /// Constructor. + /// + /// Exception message text. + public FtpSecureConnectionException(string message) + : base(message) + { + } + + /// + /// Constructor. + /// + /// Exception message text. + /// The inner exception object. + public FtpSecureConnectionException(string message, Exception innerException) + : + base(message, innerException) + { + } + + /// + /// Constructor. + /// + /// Serialization information. + /// Stream context information. + protected FtpSecureConnectionException(SerializationInfo info, + StreamingContext context) + : base(info, context) + { + } + + /// + /// Constructor. + /// + /// Exception message text. + /// Ftp response object. + /// The inner exception object. + public FtpSecureConnectionException(string message, FtpResponse response, Exception innerException) + : base(message, response, innerException) + { } + + + /// + /// Constructor. + /// + /// Exception message text. + /// Ftp response object. + public FtpSecureConnectionException(string message, FtpResponse response) + : base(message, response) + { } + } + +} diff --git a/Source/MSBuild.Community.Tasks/Sync/FTP/FtpBase.cs b/Source/MSBuild.Community.Tasks/Sync/FTP/FtpBase.cs new file mode 100644 index 00000000..33f53162 --- /dev/null +++ b/Source/MSBuild.Community.Tasks/Sync/FTP/FtpBase.cs @@ -0,0 +1,2211 @@ +/* + * Authors: Benton Stark + * + * Copyright (c) 2007-2009 Starksoft, LLC (http://www.starksoft.com) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + */ + + +using System; +using System.Net; +using System.Net.Sockets; +using System.Net.Security; +using System.Security.Cryptography; +using System.Security.Cryptography.X509Certificates; +using System.Security.Authentication; +using System.IO; +using System.Text; +using System.Threading; +using System.Globalization; +using System.ComponentModel; +using System.IO.Compression; +using System.Diagnostics; +//using Starksoft.Net.Proxy; + + +namespace Starksoft.Net.Ftp +{ + #region Public Enums + + /// + /// The type of data transfer mode (e.g. Active or Passive). + /// + /// + /// The default setting is Passive data transfer mode. This mode is widely used as a + /// firewall friendly setting for the FTP clients operating behind a firewall. + /// + public enum TransferMode : int + { + /// + /// Active transfer mode. In this mode the FTP server initiates a connection to the client when transfering data. + /// + /// This transfer mode may not work when the FTP client is behind a firewall and is accessing a remote FTP server. + Active, + /// + /// Passive transfer mode. In this mode the FTP client initiates a connection to the server when transfering data. + /// + /// + /// This transfer mode is "firewall friendly" and generally allows an FTP client behind a firewall to access a remote FTP server. + /// This mode is recommended for most data transfers. + /// + Passive + } + + /// + /// The data transfer directory. + /// + internal enum TransferDirection : int + { + /// + /// Transfer data from server to client. + /// + ToClient, + /// + /// Transfer data from client to server. + /// + ToServer + } + + /// + /// Enumeration representing the type of integrity algorithm used to verify the integrity of the file after transfer and storage. + /// + public enum HashingFunction : int + { + /// + /// No algorithm slected. + /// + None, + /// + /// Cyclic redundancy check (CRC). A CRC can be used in the same way as a checksum to detect accidental + /// alteration of data during transmission or storage. + /// + /// + /// It is often falsely assumed that when a message and its CRC are transmitted over an open channel, then when it arrives + /// if the CRC matches the message's calculated CRC then the message can not have been altered in transit. + /// For this reason it is recommended to use SHA1 whenever possible. + /// + /// + Crc32, + /// + /// Message-Digest algorithm 5 (MD5). Hashing function used to produce a 'unique' signature to detect + /// alternation of data during transmission or storage. + /// + /// + /// MD5 is a weak algorithm and has been show to produce collisions. For this reason it is recommended to use SHA1 whenere possible. + /// + /// + Md5, + /// + /// Secure Hash Algorithm (SHA). cryptographic hash functions designed by the National Security Agency (NSA) and published by the NIST as a U.S. Federal Information Processing Standard. + /// + /// + /// SHA1 is the recommended integrity check algorithm. Even a small change in the message will, with overwhelming probability, result in a completely different hash due to the avalanche effect. + /// + Sha1 + } + + #endregion + + #region Public FTP Response Code Enum + + /// + /// Enumeration representing all the various response codes from a FTP server. + /// + public enum FtpResponseCode : int + { + /// + /// No response was received from the server. + /// + None = 0, + /// + /// The command was executed sucessfully (200). + /// + CommandOkay = 200, + /// + /// A syntax error occurred because the command was not recognized (500). + /// + SyntaxErrorCommandUnrecognized = 500, + /// + /// A syntax error occurred because the input parameters or arguments for the command are invalid (501). + /// + SyntaxErrorInParametersOrArguments = 501, + /// + /// The command is considered superfluous and not implemented by the FTP server (202). + /// + CommandNotImplementedSuperfluousAtThisSite = 202, + /// + /// The command is not implement by the FTP server (502). + /// + CommandNotImplemented = 502, + /// + /// A bad sequence of commands was issued (503). + /// + BadSequenceOfCommands = 503, + /// + /// The command does not support the supplied parameter (504). + /// + CommandNotImplementedForThatParameter = 504, + /// + /// Restart marker reply (110). MARK yyyy = mmmm Where yyyy is User-process data + /// stream marker, and mmmm server's equivalent marker (note the spaces between + /// markers and "="). + /// + RestartMarkerReply = 110, + /// + /// System status or system help reply (211). + /// + SystemStatusOrHelpReply = 211, + /// + /// Directory status (212). + /// + DirectoryStatus = 212, + /// + /// File status (213). + /// + FileStatus = 213, + /// + /// Help message (214). On how to use the server or the meaning of a particular + /// non-standard command. This reply is useful only to the human user. + /// + HelpMessage = 214, + /// + /// Name system type where Name is an official system name from the list in the + /// Assigned Numbers document (215). + /// + NameSystemType = 215, + /// + /// Service ready in xxx minutes (120). + /// + ServiceReadyInxxxMinutes = 120, + /// + /// Service is now ready for new user (220). + /// + ServiceReadyForNewUser = 220, + /// + /// Service is closing control connection (221). + /// + ServiceClosingControlConnection = 221, + /// + /// Service not available, closing control connection (421). This may be a reply to any + /// command if the service knows it must shut down. + /// + ServiceNotAvailableClosingControlConnection = 421, + /// + /// Data connection already open; transfer starting (125). + /// + DataConnectionAlreadyOpenSoTransferStarting = 125, + /// + /// Data connection open so no transfer in progress (225). + /// + DataConnectionOpenSoNoTransferInProgress = 225, + /// + /// Can not open data connection (425). + /// + CannotOpenDataConnection = 425, + /// + /// Requested file action successful (for example, file transfer or file abort) (226). + /// + ClosingDataConnection = 226, + /// + /// Connection closed therefore the transfer was aborted (426). + /// + ConnectionClosedSoTransferAborted = 426, + /// + /// Entering Passive Mode (h1,h2,h3,h4,p1,p2) (227). + /// + EnteringPassiveMode = 227, + /// + /// User logged in, proceed (230). + /// + UserLoggedIn = 230, + /// + /// User is not logged in. Command not accepted (530). + /// + NotLoggedIn = 530, + /// + /// The user name was accepted but the password must now be supplied (331). + /// + UserNameOkayButNeedPassword = 331, + /// + /// An account is needed for login (332). + /// + NeedAccountForLogin = 332, + /// + /// An account is needed for storing file on the server (532). + /// + NeedAccountForStoringFiles = 532, + /// + /// File status okay; about to open data connection (150). + /// + FileStatusOkaySoAboutToOpenDataConnection = 150, + /// + /// Requested file action okay, completed (250). + /// + RequestedFileActionOkayAndCompleted = 250, + /// + /// The pathname was created (257). + /// + PathNameCreated = 257, + /// + /// Requested file action pending further information (350). + /// + RequestedFileActionPendingFurtherInformation = 350, + /// + /// Requested file action not taken (450). + /// + RequestedFileActionNotTaken = 450, + /// + /// Requested file action not taken (550). File unavailable (e.g., file busy). + /// + RequestedActionNotTakenFileUnavailable = 550, + /// + /// Requested action aborted (451). Local error in processing. + /// + RequestedActionAbortedDueToLocalErrorInProcessing = 451, + /// + /// Requested action aborted (551). Page type unknown. + /// + RequestedActionAbortedPageTypeUnknown = 551, + /// + /// Requested action not taken (452). Insufficient storage space in system. + /// + RequestedActionNotTakenInsufficientStorage = 452, + /// + /// Requested file action aborted (552). Exceeded storage allocation (for current directory or dataset). + /// + RequestedFileActionAbortedExceededStorageAllocation = 552, + /// + /// Requested action not taken (553). File name not allowed. + /// + RequestedActionNotTakenFileNameNotAllowed = 553, + /// + /// Secure authentication Okay (234). + /// + AuthenticationCommandOkay = 234, + /// + /// SSL service is Unavailable (431). + /// + ServiceIsUnavailable = 431 + } + #endregion + + + /// + /// Defines the possible versions of FtpSecurityProtocol. + /// + public enum FtpSecurityProtocol : int + { + /// + /// No security protocol specified. + /// + None, + /// + /// Specifies Transport Layer Security (TLS) version 1.0 is required to secure communciations. The TLS protocol is defined in IETF RFC 2246 and supercedes the SSL 3.0 protocol. + /// + /// + /// The AUTH TLS command is sent to the FTP server to secure the connection. TLS protocol is the latest version of the SSL protcol and is the security protocol that should be used whenever possible. + /// There are slight differences between SSL version 3.0 and TLS version 1.0, but the protocol remains substantially the same. + /// + Tls1Explicit, + /// + /// Specifies Transport Layer Security (TLS) version 1.0. or Secure Socket Layer (SSL) version 3.0 is acceptable to secure communications in explicit mode. + /// + /// + /// The AUTH SSL command is sent to the FTP server to secure the connection but the security protocol is negotiated between the server and client. + /// TLS protocol is the latest version of the SSL 3.0 protcol and is the security protocol that should be used whenever possible. + /// There are slight differences between SSL version 3.0 and TLS version 1.0, but the protocol remains substantially the same. + /// + Tls1OrSsl3Explicit, + /// + /// Specifies Secure Socket Layer (SSL) version 3.0 is required to secure communications in explicit mode. SSL 3.0 has been superseded by the TLS protocol + /// and is provided for backward compatibility only + /// + /// + /// The AUTH SSL command is sent to the FTP server to secure the connection. TLS protocol is the latest version of the SSL 3.0 protcol and is the security protocol that should be used whenever possible. + /// There are slight differences between SSL version 3.0 and TLS version 1.0, but the protocol remains substantially the same. + /// Some FTP server do not implement TLS or understand the command AUTH TLS. In those situations you should specify the security + /// protocol Ssl3, otherwise specify Tls1. + /// + Ssl3Explicit, + /// + /// Specifies Secure Socket Layer (SSL) version 2.0 is required to secure communications in explicit mode. SSL 2.0 has been superseded by the TLS protocol + /// and is provided for backward compatibility only. SSL 2.0 has several weaknesses and should only be used with legacy FTP server that require it. + /// + /// + /// The AUTH SSL command is sent to the FTP server to secure the connection. TLS protocol is the latest version of the SSL 3.0 protcol and is the security protocol that should be used whenever possible. + /// There are slight differences between SSL version 3.0 and TLS version 1.0, but the protocol remains substantially the same. + /// Some FTP server do not implement TLS or understand the command AUTH TLS. In those situations you should specify the security + /// protocol Ssl3, otherwise specify Tls1. + /// + Ssl2Explicit, + /// + /// Specifies Transport Layer Security (TLS) version 1.0 is required to secure communciations in explicit mode. The TLS protocol is defined in IETF RFC 2246 and supercedes the SSL 3.0 protocol. + /// + /// + /// The AUTH TLS command is sent to the FTP server to secure the connection. TLS protocol is the latest version of the SSL protcol and is the security protocol that should be used whenever possible. + /// There are slight differences between SSL version 3.0 and TLS version 1.0, but the protocol remains substantially the same. + /// + Tls1Implicit, + /// + /// Specifies Transport Layer Security (TLS) version 1.0. or Secure Socket Layer (SSL) version 3.0 is acceptable to secure communications in implicit mode. + /// + /// + /// The AUTH SSL command is sent to the FTP server to secure the connection but the security protocol is negotiated between the server and client. + /// TLS protocol is the latest version of the SSL 3.0 protcol and is the security protocol that should be used whenever possible. + /// There are slight differences between SSL version 3.0 and TLS version 1.0, but the protocol remains substantially the same. + /// + Tls1OrSsl3Implicit, + /// + /// Specifies Secure Socket Layer (SSL) version 3.0 is required to secure communications in implicit mode. SSL 3.0 has been superseded by the TLS protocol + /// and is provided for backward compatibility only + /// + /// + /// The AUTH SSL command is sent to the FTP server to secure the connection. TLS protocol is the latest version of the SSL 3.0 protcol and is the security protocol that should be used whenever possible. + /// There are slight differences between SSL version 3.0 and TLS version 1.0, but the protocol remains substantially the same. + /// Some FTP server do not implement TLS or understand the command AUTH TLS. In those situations you should specify the security + /// protocol Ssl3, otherwise specify Tls1. + /// + Ssl3Implicit, + /// + /// Specifies Secure Socket Layer (SSL) version 2.0 is required to secure communications in implicit mode. SSL 2.0 has been superseded by the TLS protocol + /// and is provided for backward compatibility only. SSL 2.0 has several weaknesses and should only be used with legacy FTP server that require it. + /// + /// + /// The AUTH SSL command is sent to the FTP server to secure the connection. TLS protocol is the latest version of the SSL 3.0 protcol and is the security protocol that should be used whenever possible. + /// There are slight differences between SSL version 3.0 and TLS version 1.0, but the protocol remains substantially the same. + /// Some FTP server do not implement TLS or understand the command AUTH TLS. In those situations you should specify the security + /// protocol Ssl3, otherwise specify Tls1. + /// + Ssl2Implicit + } + + /// + /// Base abstract class for FtpClient. Implements FTP network protocols. + /// + public abstract class FtpBase : IDisposable + { + + #region Constructors + + /// + /// Initializes a new instance of the FtpNetworkAdapter class. + /// + /// Port number the adapter is to communicate on. + /// Value indicating what secure security communications protocol should be used (if any). + internal FtpBase(int port, FtpSecurityProtocol securityProtocol) + { + _port = port; + _securityProtocol = securityProtocol; + } + + /// + /// Initializes a new instance of the FtpNetworkAdapter class. + /// + /// Host the adapter is to communicate on. + /// Port number the adapter is to communicate on. + /// Value indicating what secure security communications protocol should be used (if any). + internal FtpBase(string host, int port, FtpSecurityProtocol securityProtocol) + { + _host = host; + _port = port; + _securityProtocol = securityProtocol; + } + + #endregion + + public johnshope.Sync.SyncJob Job { get; set; } + #region Private Variables and Constants + + private TcpClient _commandConn; + private Stream _commandStream; + private TcpClient _dataConn; + + private int _port; + private string _host; + private TransferMode _dataTransferMode = TransferMode.Passive; + + private FtpResponseQueue _responseQueue = new FtpResponseQueue(); + private FtpResponse _response = new FtpResponse(); + private FtpResponseCollection _responseList = new FtpResponseCollection(); + + private Thread _responseMonitor; + //static object _reponseMonitorLock = new object(); + object _reponseMonitorLock = new object(); + + //private IProxyClient _proxy; + private int _maxUploadSpeed; + private int _maxDownloadSpeed; + + private int _tcpBufferSize = TCP_BUFFER_SIZE; + private int _tcpTimeout = TCP_TIMEOUT; + + private int _transferTimeout = TRANSFER_TIMEOUT; + private int _commandTimeout = COMMAND_TIMEOUT; + + private TcpListener _activeListener; + private static int _activePort; + private int _activePortRangeMin = 50000; + private int _activePortRangeMax = 50080; + + // secure communications specific + private FtpSecurityProtocol _securityProtocol = FtpSecurityProtocol.None; + private X509Certificate2 _serverCertificate; + private X509CertificateCollection _clientCertificates = new X509CertificateCollection(); + + // data compresion specific + private bool _isCompressionEnabled; + + // data integrity specific + private HashingFunction _hashAlgorithm; + + // character encoding + private Encoding _encoding = Encoding.UTF8; + + // thread signal for active mode data transfer + private ManualResetEvent _activeSignal = new ManualResetEvent(false); + + // async background worker event-based object + private BackgroundWorker _asyncWorker; + private bool _asyncCanceled; + + private const int TCP_BUFFER_SIZE = 8192; + private const int TCP_TIMEOUT = 30000; // 30 seconds + + private const int WAIT_FOR_DATA_INTERVAL = 10; // 10 ms + private const int WAIT_FOR_COMMAND_RESPONSE_INTERVAL = 10; // 10 ms + private const int TRANSFER_TIMEOUT = 15000; // 15 seconds + private const int COMMAND_TIMEOUT = 15000; // 15 seconds + + #endregion + + #region Public Events + + /// + /// Server response event. + /// + public event EventHandler ServerResponse; + + /// + /// Server request event. + /// + public event EventHandler ClientRequest; + + /// + /// Data transfer progress event. + /// + public event EventHandler TransferProgress; + + /// + /// Data transfer complete event. + /// + public event EventHandler TransferComplete; + + /// + /// Security certificate authentication event. + /// + public event EventHandler ValidateServerCertificate; + + /// + /// Connection closed event. + /// + public event EventHandler ConnectionClosed; + + #endregion + + #region Public Methods + + /// + /// Cancels any asychronous operation that is currently active. + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + public void CancelAsync() + { + if (_asyncWorker != null && !_asyncWorker.CancellationPending && _asyncWorker.IsBusy) + { + _asyncCanceled = true; + _asyncWorker.CancelAsync(); + } + } + + /// + /// Gets the checksum value from the FTP server for the file specified. Use this value to compare a local checksum to determine file integrity. + /// + /// Hashing function to use. + /// Path to the file ont the remote FTP server. + /// Hash value in a string format. + /// + public string GetChecksum(HashingFunction hash, string path) + { + return GetChecksum(hash, path, 0, 0); + } + + /// + /// Gets the checksum hash value from the FTP server for the file specified. Use this value to compare a local checksum to determine file integrity. + /// + /// Hashing function to use. + /// Path to the file on the remote FTP server. + /// Byte position of where the server should begin computing the hash. + /// Byte position of where the server should end computing the hash. + /// Checksum hash value in a string format. + /// + public string GetChecksum(HashingFunction hash, string path, long startPosition, long endPosition) + { + if (hash == HashingFunction.None) + throw new ArgumentOutOfRangeException("hash", "must contain a value other than 'Unknown'"); + + if (startPosition < 0) + throw new ArgumentOutOfRangeException("startPosition", "must contain a value greater than or equal to 0"); + + if (endPosition < 0) + throw new ArgumentOutOfRangeException("startPosition", "must contain a value greater than or equal to 0"); + + if (startPosition > endPosition) + throw new ArgumentOutOfRangeException("startPosition", "must contain a value less than or equal to endPosition"); + + FtpCmd command = FtpCmd.Unknown; + + switch (hash) + { + case HashingFunction.Crc32: + command = FtpCmd.Xcrc; + break; + + case HashingFunction.Md5: + command = FtpCmd.Xmd5; + break; + case HashingFunction.Sha1: + command = FtpCmd.Xsha1; + break; + } + + // send request to server to get the hash value for the file + // if the restartposition is > 0 then computer the hash on the segment that we resent + if (startPosition > 0) + SendRequest(new FtpRequest(_encoding, command, path, startPosition.ToString(), endPosition.ToString())); + else + SendRequest(new FtpRequest(_encoding, command, path)); + + return _response.Text; + } + + /// + /// Computes a checksum for a local file. + /// + /// Hashing function to use. + /// Path to file to perform checksum operation on. + /// Hash value in a string format. + /// + public string ComputeChecksum(HashingFunction hash, string localPath) + { + if (!File.Exists(localPath)) + throw new ArgumentException("file does not exist.", "localPath"); + + using (FileStream fileStream = File.OpenRead(localPath)) + { + return ComputeChecksum(hash, fileStream); + } + } + + /// + /// Computes a checksum for a Stream object. + /// + /// Hashing function to use. + /// Any System.IO.Stream object. + /// Hash value in a string format. + /// + /// The Stream object must allow reads and must allow seeking. + /// + /// + public string ComputeChecksum(HashingFunction hash, Stream inputStream) + { + return ComputeChecksum(hash, inputStream, 0); + } + + /// + /// Computes a checksum value for a Stream object. + /// + /// Hashing function to use. + /// Any System.IO.Stream object. + /// Byte position of where the hash computation should begin. + /// Hash value in a string format. + /// + /// The Stream object must allow reads and must allow seeking. + /// + /// + public static string ComputeChecksum(HashingFunction hash, Stream inputStream, long startPosition) + { + if (hash == HashingFunction.None) + throw new ArgumentOutOfRangeException("hash", "must contain a value other than 'Unknown'"); + + if (inputStream == null) + throw new ArgumentNullException("inputStream"); + + if (!inputStream.CanRead) + throw new ArgumentException("must be readable. The CanRead property must return a value of 'true'.", "inputStream"); + + if (!inputStream.CanSeek) + throw new ArgumentException("must be seekable. The CanSeek property must return a value of 'true'.", "inputStream"); + + if (startPosition < 0) + throw new ArgumentOutOfRangeException("startPosition", "must contain a value greater than or equal to 0"); + + HashAlgorithm hashAlgo = null; + + switch (hash) + { + case HashingFunction.Crc32: + hashAlgo = new Starksoft.Hashing.Crc32(); + break; + case HashingFunction.Md5: + hashAlgo = new MD5CryptoServiceProvider(); + break; + case HashingFunction.Sha1: + hashAlgo = new SHA1CryptoServiceProvider(); + break; + } + + if (startPosition > 0) + inputStream.Position = startPosition; + else + inputStream.Position = 0; + + byte[] hashArray = hashAlgo.ComputeHash(inputStream); + + // convert byte array to a string + StringBuilder buffer = new StringBuilder(hashArray.Length); + foreach (byte hashByte in hashArray) + { + buffer.Append(hashByte.ToString("x2")); + } + + return buffer.ToString(); + } + + #endregion + + #region Internal Properties + + internal BackgroundWorker AsyncWorker + { + get { return _asyncWorker; } + } + + #endregion + + #region Public Properties + + /// + /// Gets a value indicating whether an asynchronous operation is canceled. + /// + /// Returns true if an asynchronous operation is canceled; otherwise, false. + /// + public bool IsAsyncCanceled + { + get { return _asyncCanceled; } + } + + /// + /// Gets a value indicating whether an asynchronous operation is running. + /// + /// Returns true if an asynchronous operation is running; otherwise, false. + /// + public bool IsBusy + { + get { return _asyncWorker == null ? false : _asyncWorker.IsBusy; } + } + + /// + /// Gets or sets the current port number used by the FtpClient to make a connection to the FTP server. + /// + /// + /// The default value is '80'. This setting can only be changed when the + /// connection to the FTP server is closed. And FtpException is thrown if this + /// setting is changed when the FTP server connection is open. + /// + /// Returns an integer representing the port number used to connect to a remote server. + /// + public int Port + { + get { return _port; } + set + { + if (this.IsConnected) + throw new FtpException("Port property value can not be changed when connection is open."); + + _port = value; + } + } + + /// + /// Gets or sets a text value containing the current host used by the FtpClient to make a connection to the FTP server. + /// + /// + /// This value may be in the form of either a host name or IP address. + /// This setting can only be changed when the + /// connection to the FTP server is closed. And FtpException is thrown if this + /// setting is changed when the FTP server connection is open. + /// + /// Returns a string with either the host name or host ip address. + /// + public string Host + { + get { return _host; } + set + { + if (this.IsConnected) + throw new FtpException("Host property value can not be changed when connection is open."); + + _host = value; + } + } + + /// + /// Gets or sets a value indicating what security protocol such as Secure Sock Layer (SSL) should be used. + /// + /// + /// The default value is 'None'. This setting can only be changed when the + /// connection to the FTP server is closed. An FtpException is thrown if this + /// setting is changed when the FTP server connection is open. + /// + /// Returns an enumerator specifying the choosen security protocol of either TLS v1.0, SSL v3.0 or SSL v2.0. + /// + /// + /// + public FtpSecurityProtocol SecurityProtocol + { + get { return _securityProtocol; } + set + { + if (this.IsConnected) + throw new FtpException("SecurityProtocol property value can not be changed when connection is open."); + + _securityProtocol = value; + } + } + + /// + /// Get Client certificate collection used when connection with a secured SSL/TSL protocol. Add your client certificates + /// if required to connect to the remote FTP server. + /// + /// Returns a X509CertificateCollection list contains X.509 security certificates. + /// + /// + public X509CertificateCollection SecurityCertificates + { + get { return _clientCertificates; } + } + + /// + /// Gets or sets a value indicating that the client will use compression when uploading and downloading + /// data. + /// + /// + /// This value turns on or off the compression algorithm DEFLATE to facility FTP data compression which is compatible with + /// FTP servers that implement compression via the zLib compression software library. The default value is 'False'. + /// This setting can only be changed when the system is not busy conducting other operations. + /// + /// Returns True if compression is enabled; otherwise False; + /// + public bool IsCompressionEnabled + { + get { return _isCompressionEnabled; } + set + { + if (this.IsBusy) + throw new FtpException("IsCompressionEnabled property value can not be changed when the system is busy."); + + try + { + // enable compression + if (this.IsConnected && value && value != _isCompressionEnabled) + CompressionOn(); + + // disable compression + if (this.IsConnected && !value && value != _isCompressionEnabled) + CompressionOff(); + } + catch (FtpException ex) + { + throw new FtpDataCompressionException("An error occurred while trying to enable or disable FTP data compression.", ex); + } + + _isCompressionEnabled = value; + } + } + + /// + /// Gets or sets an Integer value representing the maximum upload speed allowed + /// for data transfers in kilobytes per second. + /// + /// + /// Set this value when you would like to throttle back any upload data transfers. + /// A value of zero means there is no restriction on how fast data uploads are + /// conducted. The default value is zero. This setting is used to throttle data traffic so the FtpClient does + /// not consume all available network bandwidth. + /// + /// + public int MaxUploadSpeed + { + get { return _maxUploadSpeed; } + set + { + if (value * 1024 > Int32.MaxValue || value < 0) + throw new ArgumentOutOfRangeException("value", "The MaxUploadSpeed property must have a range of 0 to 2,097,152."); + + _maxUploadSpeed = value; + } + } + + /// + /// Gets or sets an Integer value representing the maximum download speed allowed + /// for data transfers in kilobytes per second. + /// + /// + /// Set this value when you would like to throttle back any download data transfers. + /// A value of zero means there is no restriction on how fast data uploads are + /// conducted. The default value is zero. This setting is used to throttle data traffic so the FtpClient does + /// not consume all available network bandwidth. + /// + /// + public int MaxDownloadSpeed + { + get { return _maxDownloadSpeed; } + set + { + if (value * 1024 > Int32.MaxValue || value < 0) + throw new ArgumentOutOfRangeException("value", "must have a range of 0 to 2,097,152."); + + _maxDownloadSpeed = value; + } + } + + /// + /// Gets only the last response from the FTP server. + /// + /// Returns a FtpResponse object containing the last FTP server response; other the value null (or Nothing in VB) is returned. + public FtpResponse LastResponse + { + get { return _response; } + } + + /// + /// Gets the list of all responses since the last command was issues to the server. + /// + /// Returns a FtpResponseCollection list containing all the responses. + public FtpResponseCollection LastResponseList + { + get { return _responseList; } + } + + /// + /// Gets or sets the TCP buffer size used when communicating with the FTP server in bytes. + /// + /// Returns an integer value representing the buffer size. The default value is 8192. + public int TcpBufferSize + { + get { return _tcpBufferSize; } + set + { + if (value < 1) + throw new ArgumentOutOfRangeException("value", "must be greater than 0."); + + _tcpBufferSize = value; + } + } + + /// + /// Gets or sets the TCP timeout used when communciating with the FTP server in milliseconds. + /// + /// + /// Default value is 30000 (30 seconds). + /// + /// + /// + public int TcpTimeout + { + get { return _tcpTimeout; } + set + { + if (value < 0) + throw new ArgumentOutOfRangeException("value", "must be greater than or equal to 0."); + + _tcpTimeout = value; + } + } + + /// + /// Gets or sets the data transfer timeout used when communicating with the FTP server in milliseconds. + /// + /// + /// Default value is 15000 (15 seconds). + /// + /// + /// + public int TransferTimeout + { + get { return _transferTimeout; } + set + { + if (value < 1) + throw new ArgumentOutOfRangeException("value", "must be greater than 0."); + + _transferTimeout = value; + } + } + + /// + /// Gets or sets the FTP command timeout used when communciating with the FTP server in milliseconds. + /// + /// + /// Default value is 15000 (15 seconds). + /// + /// + /// + public int CommandTimeout + { + get { return _commandTimeout; } + set + { + if (value < 1) + throw new ArgumentOutOfRangeException("value", "must be greater than 0."); + + _commandTimeout = value; + } + } + + /// + /// The beginning port number range used by the FtpClient when opening a local 'Active' port. The default value is 4051. + /// + /// Value must be less than or equal to the ActivePortRangeMax value. + /// + /// When the FtpClient is in 'Active' mode a local port is opened for communications from the FTP server. + /// The FtpClient will attempt to open an unused TCP listener port between the ActivePortRangeMin and ActivePortRangeMax values. + /// Default value is 50000. + /// + /// + /// + public int ActivePortRangeMin + { + get { return _activePortRangeMin; } + set + { + if (value > _activePortRangeMin) + throw new ArgumentOutOfRangeException("value","must be less than the ActivePortRangeMax value."); + + if (value < 1 || value > 65534) + throw new ArgumentOutOfRangeException("value", "must be between 1 and 65534."); + + if (this.IsBusy) + throw new FtpException("ActivePortRangeMin property value can not be changed when the component is busy."); + + _activePortRangeMin = value; + } + } + + /// + /// The ending port number range used by the FtpClient when opening a local 'Active' port. The default value is 4080. + /// + /// Value must be greater than or equal to the ActivePortRangeMin value. + /// + /// When the FtpClient is in 'Active' mode a local port is opened for communications from the FTP server. + /// The FtpClient will attempt to open an unused TCP listener port between the ActivePortRangeMin and ActivePortRangeMax values. + /// Default value is 50080. + /// + /// + /// + public int ActivePortRangeMax + { + get { return _activePortRangeMax; } + set + { + if (value < _activePortRangeMin) + throw new ArgumentOutOfRangeException("value", "must be greater than the ActivePortRangeMin value."); + + if (value < 1 || value > 65534) + throw new ArgumentOutOfRangeException("value", "must be between 1 and 65534."); + + if (this.IsBusy) + throw new FtpException("ActivePortRangeMax property value can not be changed when the component is busy."); + + _activePortRangeMax = value; + } + } + + + /// + /// Gets or sets the data transfer mode to either Active or Passive. + /// + /// + /// + public TransferMode DataTransferMode + { + get { return _dataTransferMode; } + set + { + if (this.IsBusy) + throw new FtpException("DataTransferMode property value can not be changed when the component is busy."); + + _dataTransferMode = value; + } + } + + /// + /// Gets or sets the the proxy object to use when establishing a connection to the remote FTP server. + /// + /// Create a proxy object when traversing a firewall. + /// + /// FtpClient ftp = new FtpClient(); + /// + /// // create an instance of the client proxy factory for the an ftp client + /// ftp.Proxy = (new ProxyClientFactory()).CreateProxyClient(ProxyType.Http, "localhost", 6588); + /// + /// + /// + /* de + public IProxyClient Proxy + { + get { return _proxy; } + set { _proxy = value; } + } + */ + + /// + /// Gets the connection status to the FTP server. + /// + /// Returns True if the connection is open; otherwise False. + /// + public bool IsConnected + { + get + { + if (_commandConn == null || _commandConn.Client == null) + return false; + + Socket client = _commandConn.Client; + + if (!client.Connected) + return false; + + // this is how you can determine whether a socket is still connected. + bool blockingState = client.Blocking; + bool connected = true; + try + { + byte[] tmp = new byte[1]; + + client.Blocking = false; + client.Send(tmp, 0, 0); + } + catch (SocketException e) + { + // 10035 == WSAEWOULDBLOCK + if (!e.NativeErrorCode.Equals(10035)) + { + connected = false; + } + } + catch (ObjectDisposedException) + { + connected = false; + } + finally + { + try + { + client.Blocking = blockingState; + } + catch + { + connected = false; + } + } + + return connected; + + } + } + + /// + /// Sets the automatic file integrity setting (checksum) option on all data transfers (upload and download). + /// + /// + /// The FtpClient library will throw an FtpFileIntegrityException if the file integrity value do not match. + /// + /// Not all FTP servers support file integrity values such as SHA1, CRC32, or MD5. If you server does support + /// one of these file integrity options, you can set this property and the FtpClient will automatically check + /// each file that is transferred to make sure the hash values match. If the values do not match, an exception + /// is thrown. + /// + /// + /// + /// + public HashingFunction AutoChecksumValidation + { + get { return _hashAlgorithm; } + set { _hashAlgorithm = value; } + } + + /// + /// Gets or sets the character encoding used when sending commands to the FTP server or receiving directory listing information. + /// This encoding value does not affect the encoding of files being transferred. The default value is UTF-8. Some older FTP servers + /// require different character encoding such as UTF-7. + /// + /// + /// To set this value use the .NET System.Text.Encoding class. The following example sets the character encoding to UTF-7. In addition, + /// other encodings can be specified by string name using the static method System.Text.Encoding.GetEncoding(); + /// + /// FtpClient.CharacterEncoding = System.Text.Encoding.UTF7; + /// + /// + public Encoding CharacterEncoding + { + get { return _encoding; } + set + { + if (value == null) + throw new ArgumentNullException("CharacterEncoding"); + _encoding = value; + } + } + + public string Home { get; set; } + + #endregion + + #region Internal Protected Methods + + /// + /// Send a FTP command request to the server. + /// + /// + internal void SendRequest(FtpRequest request) + { + if (_commandConn == null || _commandConn.Connected == false) + throw new FtpConnectionClosedException("Connection is closed."); + + // clear out any responses that might have been pending from a previous + // failed operation + DontWaitForHappyCodes(); + + if (ClientRequest != null) + ClientRequest(this, new FtpRequestEventArgs(request)); + + byte[] buffer = request.GetBytes(); + + try + { + _commandStream.Write(buffer, 0, buffer.Length); + } + catch (IOException ex) + { + throw new FtpConnectionBrokenException("Connection is broken. Failed to send command.", ex); + } + + // most commands will have happy codes but the quote() command does not + if (request.HasHappyCodes) + { + WaitForHappyCodes(request.GetHappyCodes()); + } + else + { + // when there are no happy codes given the we have to give the server some time to response + // since we really don't know what response is the correct one + if (request.Command != FtpCmd.Quit) + Thread.Sleep(2000); + DontWaitForHappyCodes(); + } + } + + private void DontWaitForHappyCodes() + { + if (_responseQueue.Count == 0) + return; + + _responseList.Clear(); + while (_responseQueue.Count > 0) + { + FtpResponse response = _responseQueue.Dequeue(); + _responseList.Add(response); + RaiseServerResponseEvent(new FtpResponse(response)); + } + _response = _responseList.GetLast(); + } + + + + + /// + /// creates a new async worker object for the async events to use. + /// + internal void CreateAsyncWorker() + { + if (_asyncWorker != null) + _asyncWorker.Dispose(); + _asyncWorker = null; + _asyncCanceled = false; + _asyncWorker = new BackgroundWorker(); + } + + /// + /// Closes all connections to the FTP server. + /// + internal void CloseAllConnections() + { + CloseDataConn(); + CloseCommandConn(); + AbortMonitorThread(); + } + + /// + /// The monitor thread should close automatically once the command connection is terminated. If it does not close properly, force it to close. + /// + private void AbortMonitorThread() + { + _responseMonitor.Abort(); + } + + // open a connection to the server + internal void OpenCommandConn() + { + // create a new tcp client object + CreateCommandConnection(); + StartCommandMonitorThread(); + + if (_securityProtocol == FtpSecurityProtocol.Ssl2Explicit || _securityProtocol == FtpSecurityProtocol.Ssl3Explicit || _securityProtocol == FtpSecurityProtocol.Tls1Explicit || _securityProtocol == FtpSecurityProtocol.Tls1OrSsl3Explicit) + CreateSslExplicitCommandStream(); + + if (_securityProtocol == FtpSecurityProtocol.Ssl2Implicit || _securityProtocol == FtpSecurityProtocol.Ssl3Implicit || _securityProtocol == FtpSecurityProtocol.Tls1Implicit || _securityProtocol == FtpSecurityProtocol.Tls1OrSsl3Implicit) + CreateSslImplicitCommandStream(); + + // test to see if this is an asychronous operation and if so make sure + // the user has not requested the operation to be canceled + if (IsAsyncCancellationPending()) + return; + + // this check screws up secure connections so we have to ignore it when secure connections are enabled + if (_securityProtocol == FtpSecurityProtocol.None) + WaitForHappyCodes(FtpResponseCode.ServiceReadyForNewUser); + + } + + internal void TransferData(TransferDirection direction, FtpRequest request, Stream data) + { + TransferData(direction, request, data, 0); + } + + internal void TransferData(TransferDirection direction, FtpRequest request, Stream data, long restartPosition) + { + if (_commandConn == null || _commandConn.Connected == false) + throw new FtpConnectionClosedException("Connection is closed."); + + if (request == null) + throw new ArgumentNullException("request", "value is required"); + + if (data == null) + throw new ArgumentNullException("data", "value is required"); + + switch (direction) + { + case TransferDirection.ToClient: + if (!data.CanWrite) + throw new FtpDataTransferException("Data transfer error. Data conn does not allow write operation."); + break; + case TransferDirection.ToServer: + if (!data.CanRead) + throw new FtpDataTransferException("Data transfer error. Data conn does not allow read operation."); + break; + } + + // if this is a restart then the data stream must support seeking + if (restartPosition > 0 && !data.CanSeek) + throw new FtpDataTransferException("Data transfer restart error. Data conn does not allow seek operation."); + + try + { + // create a thread to begin the process of opening a data connection to the remote server + OpenDataConn(); + + // check for a restart position + if (restartPosition > 0) + { + // instruct the server to restart file transfer at the same position where the output stream left off + SendRequest(new FtpRequest(_encoding, FtpCmd.Rest, restartPosition.ToString(CultureInfo.InvariantCulture))); + + // set the data stream to the same position as the server + data.Position = restartPosition; + } + + // send the data transfer command that requires a separate data connection to be established to transmit data + SendRequest(request); + + // wait for the data connection thread to signal back that a connection has been established + WaitForDataConn(); + + // test to see if the data connection failed to be established sometime the active connection fails due to security settings on the ftp server + if (_dataConn == null) + throw new FtpDataConnectionException("Unable to establish a data connection to the destination. The destination may have refused the connection."); + + // create the data stream object - handles creation of SslStream and DeflateStream objects as well + Stream conn = _dataConn.GetStream(); + + // test to see if we need to enable ssl/tls explicit mode + if (_securityProtocol != FtpSecurityProtocol.None) + { + conn = CreateSslStream(conn); + } + + // test to see if we need to enable compression by using the DeflateStream + if (_isCompressionEnabled) + { + conn = CreateZlibStream(direction, conn); + } + + // based on the direction of the data transfer we need to handle the input and output streams + switch (direction) + { + case TransferDirection.ToClient: + TransferBytes(conn, data, _maxDownloadSpeed * 1024); + break; + case TransferDirection.ToServer: + TransferBytes(data, conn, _maxUploadSpeed * 1024); + break; + } + } + finally + { + // attempt to close the data connection + CloseDataConn(); + } + + // if no errors occurred and this is not a quoted command then we will wait for the server to send a closing connection message + WaitForHappyCodes(FtpResponseCode.ClosingDataConnection); + + // integrity check + if (_hashAlgorithm != HashingFunction.None && request.IsFileTransfer) + DoIntegrityCheck(request, data, restartPosition); + } + + private Stream CreateZlibStream(TransferDirection direction, Stream stream) + { + DeflateStream deflateStream = null; + + switch (direction) + { + case TransferDirection.ToClient: + deflateStream = new DeflateStream(stream, CompressionMode.Decompress, true); + // zlib fix to ignore first two bytes of header data + deflateStream.BaseStream.ReadByte(); + deflateStream.BaseStream.ReadByte(); + break; + + case TransferDirection.ToServer: + deflateStream = new DeflateStream(stream, CompressionMode.Compress, true); + // this is a fix for the DeflateStream class only when sending compressed data to the server. + // Zlib has two bytes of data attached to the header that we have to write before processing the data stream. + deflateStream.BaseStream.WriteByte(120); + deflateStream.BaseStream.WriteByte(218); + break; + } + stream = deflateStream; + return stream; + } + + internal string TransferText(FtpRequest request) + { + Stream output = new MemoryStream(); + TransferData(TransferDirection.ToClient, request, output); + output.Position = 0; + StreamReader reader = new StreamReader(output, _encoding); + return reader.ReadToEnd(); + } + + internal void CompressionOn() + { + try + { + SendRequest(new FtpRequest(_encoding, FtpCmd.Mode, "Z")); + } + catch (Exception ex) + { + throw new FtpDataCompressionException("Unable to enable compression (MODE Z) on the destination.", ex); + } + } + + internal void CompressionOff() + { + try + { + SendRequest(new FtpRequest(_encoding, FtpCmd.Mode, "S")); + } + catch (Exception ex) + { + throw new FtpDataCompressionException("Unable to disable compression (MODE S) on the destination.", ex); + } + } + + #endregion + + #region Private Methods + + private void StartCommandMonitorThread() + { + // start the monitor thread which pumps FtpResponse objects on the FtpResponseQueue + _responseMonitor = new Thread(new ThreadStart(MonitorCommandConnection)); + _responseMonitor.Name = "FtpBase Response Monitor"; + _responseMonitor.Start(); + } + + private bool IsAsyncCancellationPending() + { + if (_asyncWorker != null && _asyncWorker.CancellationPending) + { + _asyncCanceled = true; + return true; + } + return false; + } + + public TimeSpan dw, dr; + + private void TransferBytes(Stream input, Stream output, int maxBytesPerSecond) + { + int bufferSize = _tcpBufferSize > maxBytesPerSecond && maxBytesPerSecond != 0 ? maxBytesPerSecond : _tcpBufferSize; + byte[] buffer = new byte[bufferSize]; + long bytesTotal = 0; + int bytesRead = 0; + DateTime start = DateTime.Now; + TimeSpan elapsed = new TimeSpan(0); + int bytesPerSec = 0; + + while(true) + { + var t0 = DateTime.Now; + bytesRead = input.Read(buffer, 0, bufferSize); + var t1 = DateTime.Now; + + if (bytesRead == 0) + break; + + bytesTotal += bytesRead; + + var t2 = DateTime.Now; + output.Write(buffer, 0, bytesRead); + var t3 = DateTime.Now; + + // calculate some statistics + elapsed = DateTime.Now.Subtract(start); + bytesPerSec = (int)(elapsed.TotalSeconds < 1 ? bytesTotal : bytesTotal / elapsed.TotalSeconds); + + dr += (t1-t0); + dw += (t3-t2); + + // if the consumer subscribes to transfer progress event then fire it + if (TransferProgress != null) + TransferProgress(this, new TransferProgressEventArgs(bytesRead, bytesPerSec, elapsed)); + + // test to see if this is an asychronous operation and if so make sure + // the user has not requested the operation to be canceled + if (IsAsyncCancellationPending()) + throw new FtpAsynchronousOperationException("Asynchronous operation canceled by user."); + + // throttle the transfer if necessary + ThrottleByteTransfer(maxBytesPerSecond, bytesTotal, elapsed, bytesPerSec); + + } ; + + // if (bytesTotal == 0) output.Write(buffer, 0, 0); + + // if the consumer subscribes to transfer complete event then fire it + if (TransferComplete != null) + TransferComplete(this, new TransferCompleteEventArgs(bytesTotal, bytesPerSec, elapsed)); + } + + private void ThrottleByteTransfer(int maxBytesPerSecond, long bytesTotal, TimeSpan elapsed, int bytesPerSec) + { + // we only throttle if the maxBytesPerSecond is not zero (zero turns off the throttle) + if (maxBytesPerSecond > 0) + { + // we only throttle if our through-put is higher than what we want + if (bytesPerSec > maxBytesPerSecond) + { + double elapsedMilliSec = elapsed.TotalSeconds == 0 ? elapsed.TotalMilliseconds : elapsed.TotalSeconds * 1000; + + // need to calc a delay in milliseconds for the throttle wait based on how fast the + // transfer is relative to the speed it needs to be + double millisecDelay = (bytesTotal / (maxBytesPerSecond / 1000) - elapsedMilliSec); + + // can only sleep to a max of an Int32 so we need to check this since bytesTotal is a long value + // this should never be an issue but never say never + if (millisecDelay > Int32.MaxValue) + millisecDelay = Int32.MaxValue; + + // go to sleep + Thread.Sleep((int)millisecDelay); + } + } + } + + private void CreateCommandConnection() + { + if (_host == null || _host.Length == 0) + throw new FtpException("An FTP Host must be specified before opening connection to FTP destination. Set the appropriate value using the Host property on the FtpClient object."); + + try { + // test to see if we should use the user supplied proxy object + // to create the connection + /* if (_proxy != null) + _commandConn = _proxy.CreateConnection(_host, _port); + else */ + _commandConn = new TcpClient(_host, _port); + } /* + catch (ProxyException pex) + { + if (_commandConn != null) + _commandConn.Close(); + + throw new FtpProxyException(String.Format(CultureInfo.InvariantCulture, "A proxy error occurred while creating connection to FTP destination {0} on port {1}.", _host, _port.ToString(CultureInfo.InvariantCulture)), pex); + }*/ + catch (Exception ex) + { + if (_commandConn != null) + _commandConn.Close(); + + throw new FtpConnectionOpenException(String.Format(CultureInfo.InvariantCulture, "An error occurred while opening a connection to FTP destination {0} on port {1}.", _host, _port.ToString(CultureInfo.InvariantCulture)), ex); + } + + // set command connection buffer sizes and timeouts + _commandConn.ReceiveBufferSize = _tcpBufferSize; + _commandConn.ReceiveTimeout = _tcpTimeout; + _commandConn.SendBufferSize = _tcpBufferSize; + _commandConn.SendTimeout = _tcpTimeout; + + // set the command stream object + _commandStream = _commandConn.GetStream(); + } + + + + + private void CloseCommandConn() + { + if (_commandConn == null) + return; + try + { + if (_commandConn.Connected) + { + // send the quit command to the server + SendRequest(new FtpRequest(_encoding, FtpCmd.Quit)); + } + _commandConn.Close(); + } + catch { } + + _commandConn = null; + } + + + private void WaitForHappyCodes(params FtpResponseCode[] happyResponseCodes) + { + WaitForHappyCodes(_commandTimeout, happyResponseCodes); + } + + /// + /// Waits until a happy code has been returned by the FTP server or times out. + /// + /// Maximum time to wait before timing out. + /// Server response codes to wait for. + internal protected void WaitForHappyCodes(int timeout, params FtpResponseCode[] happyResponseCodes) + { + _responseList.Clear(); + do + { + FtpResponse response = GetNextCommandResponse(timeout); + _responseList.Add(response); + RaiseServerResponseEvent(new FtpResponse(response)); + + if (!response.IsInformational) + { + if (IsHappyResponse(response, happyResponseCodes)) + break; + + if (IsUnhappyResponse(response)) + { + _response = response; + throw new FtpResponseException("FTP command failed.", response); + } + } + } while (true); + + _response = _responseList.GetLast(); + } + + private void RaiseServerResponseEvent(FtpResponse response) + { + if (ServerResponse != null) + ServerResponse(this, new FtpResponseEventArgs(response)); + } + + private void RaiseConnectionClosedEvent() + { + if (ConnectionClosed != null) + ConnectionClosed(this, new ConnectionClosedEventArgs()); + } + + private bool IsUnhappyResponse(FtpResponse response) + { + if ( + response.Code == FtpResponseCode.ServiceNotAvailableClosingControlConnection + || response.Code == FtpResponseCode.CannotOpenDataConnection + || response.Code == FtpResponseCode.ConnectionClosedSoTransferAborted + || response.Code == FtpResponseCode.RequestedFileActionNotTaken + || response.Code == FtpResponseCode.RequestedActionAbortedDueToLocalErrorInProcessing + || response.Code == FtpResponseCode.RequestedActionNotTakenInsufficientStorage + || response.Code == FtpResponseCode.SyntaxErrorCommandUnrecognized + || response.Code == FtpResponseCode.SyntaxErrorInParametersOrArguments + || response.Code == FtpResponseCode.CommandNotImplemented + || response.Code == FtpResponseCode.BadSequenceOfCommands + || response.Code == FtpResponseCode.CommandNotImplementedForThatParameter + || response.Code == FtpResponseCode.NotLoggedIn + || response.Code == FtpResponseCode.NeedAccountForStoringFiles + || response.Code == FtpResponseCode.RequestedActionNotTakenFileUnavailable + || response.Code == FtpResponseCode.RequestedActionAbortedPageTypeUnknown + || response.Code == FtpResponseCode.RequestedFileActionAbortedExceededStorageAllocation + || response.Code == FtpResponseCode.RequestedActionNotTakenFileNameNotAllowed) + return true; + else + return false; + } + + private bool IsHappyResponse(FtpResponse response, FtpResponseCode[] happyResponseCodes) + { + // always return true if there are no responses to validate + if (happyResponseCodes.Length == 0) + return true; + + for (int j = 0; j < happyResponseCodes.Length; j++) + { + if (happyResponseCodes[j] == response.Code) + return true; + } + return false; + } + + private void MonitorCommandConnection() + { + byte[] buffer = new byte[_tcpBufferSize]; + StringBuilder response = new StringBuilder(); + while (IsConnected) + { + lock (_reponseMonitorLock) + { + Thread.Sleep(WAIT_FOR_COMMAND_RESPONSE_INTERVAL); + try { + if (_commandConn != null && _commandConn.GetStream().DataAvailable) { + int bytes = _commandStream.Read(buffer, 0, _tcpBufferSize); + string partial = _encoding.GetString(buffer, 0, bytes); + response.Append(partial); + if (!partial.EndsWith("\r\n")) { + continue; + } + + // parse out the response code sent back from the server + // in some cases more than one response can be sent with + // each line separated with a crlf pair. + string[] responseArray = SplitResponse(response.ToString()); + for (int i = 0; i < responseArray.Length; i++) { + _responseQueue.Enqueue(new FtpResponse(responseArray[i])); + } + + response.Remove(0, response.Length); + } + } catch (ThreadAbortException ex) { + throw ex; + } catch { } + } + } + + RaiseConnectionClosedEvent(); + + } + + private FtpResponse GetNextCommandResponse(int timeout) + { + int sleepTime = 0; + while (_responseQueue.Count == 0) + { + if (!IsConnected) + throw new FtpConnectionClosedException("Connection is closed."); + + if (IsAsyncCancellationPending()) + throw new FtpAsynchronousOperationException("Asynchronous operation canceled."); + + Thread.Sleep(WAIT_FOR_DATA_INTERVAL); + sleepTime += WAIT_FOR_DATA_INTERVAL; + if (sleepTime > timeout) + throw new FtpCommandResponseTimeoutException("A timeout occurred while waiting for the destination to send a response. The last reponse from the destination is '" + _response.Text + "'"); + } + + // return next response object from the queue + return _responseQueue.Dequeue(); + } + + private string[] SplitResponse(string response) + { + return response.Split(new char[] { '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries); + } + + private int GetNextActiveModeListenerPort() + { + if (_activePort < _activePortRangeMin || _activePort > _activePortRangeMax) + _activePort = _activePortRangeMin; + else + _activePort++; + + return _activePort; + } + + private void CreateActiveConn() + { + if (_dataConn != null) return; + + string localHost = Dns.GetHostName(); + IPAddress[] localAddresses = Dns.GetHostAddresses(localHost); + IPAddress localAddr = null; + foreach (IPAddress addr in localAddresses) + { + if (addr.AddressFamily == AddressFamily.InterNetwork) + { + localAddr = addr; + } + } + + if (localAddr == null) + { + throw new Exception("Local host does not have an IPv4 address"); + } + + + // Set the event to nonsignaled state. + _activeSignal.Reset(); + + bool success = false; + int listenerPort = 0; + + do + { + int failureCnt = 0; + + try + { + listenerPort = GetNextActiveModeListenerPort(); + _activeListener = new TcpListener(localAddr, listenerPort); + _activeListener.Start(); + success = true; + } + catch (SocketException socketError) + { + if (socketError.ErrorCode == 10048 && ++failureCnt < _activePortRangeMax - _activePortRangeMin) + _activeListener.Stop(); + else + throw new FtpDataConnectionException(String.Format(CultureInfo.InvariantCulture, "An error occurred while trying to create an active connection on host {0} port {1}", localHost, listenerPort.ToString(CultureInfo.InvariantCulture)), socketError); + } + } while (!success); + + byte[] addrBytes = localAddr.GetAddressBytes(); + string dataPortInfo = String.Format(CultureInfo.InvariantCulture, "{0},{1},{2},{3},{4},{5}", addrBytes[0].ToString(CultureInfo.InvariantCulture), addrBytes[1].ToString(CultureInfo.InvariantCulture), addrBytes[2].ToString(CultureInfo.InvariantCulture), addrBytes[3].ToString(CultureInfo.InvariantCulture), listenerPort / 256, listenerPort % 256); + + // Accept the connection. BeginAcceptSocket() creates the accepted socket. + _activeListener.BeginAcceptTcpClient(new AsyncCallback(this.AcceptTcpClientCallback), _activeListener); + + Job.Log.Debug("Opening Active Data Connection on Port " + listenerPort.ToString()); + + // send a command to the server instructing it to connect to + // the local ip address and port that the tcplistener is bound to + try + { + SendRequest(new FtpRequest(_encoding, FtpCmd.Port, dataPortInfo)); + } + catch (FtpException fex) + { + throw new FtpDataConnectionException(String.Format("An error occurred while issuing data port command '{0}' on an active FTP connection.", dataPortInfo), fex); + } + } + + // async callback that occurs once the server has connected to the client listener data connection + private void AcceptTcpClientCallback(IAsyncResult ar) + { + // Get the listener that handles the client request. + TcpListener listener = (TcpListener)ar.AsyncState; + + // make sure that the server didn't close the connection on us or just refuse to allow an active connection + // for security considerations and other purposes some servers will refuse active connections + try + { + _dataConn = listener.EndAcceptTcpClient(ar); + } + catch { } + + // signal the calling thread to continue now that the data connection is open + _activeSignal.Set(); + } + + private void OpenDataConn() + { + // create the approiate ftp data connection depending on how the ftp client should send + // or receive data from the ftp server + if (_dataTransferMode == TransferMode.Active) + CreateActiveConn(); + else + CreatePassiveConn(); + } + + private void CloseDataConn() + { + // close the tcpclient data connection object + if (_dataConn != null) + { + try + { + + //johnshope.Sync.Log.Debug("Close Connection."); + + _dataConn.Close(); + } + catch { } + _dataConn = null; + } + + // stop the tcplistner object if we used it for an active data transfer where we + // are listing and the server makes a connection to the client and pushed data + if (_dataTransferMode == TransferMode.Active && _activeListener != null) + { + try + { + _activeListener.Stop(); + } + catch { } + _activeListener = null; + } + } + + private void WaitForDataConn() + { + // if the transfer mode is active then we have to open a listener and wait for the server to connect before sending + // or receiving data but if the transfer mode is passive then we make the connection (non blocking) + // and therefore there is no need to wait for the server and our signal that the server has connected + // - we already have the connection object since the tcpclient object blocks until the server accepts the connection + if (_dataTransferMode == TransferMode.Active) + { + // wait until a data connection is made before continuing based on a thread blocking signal object + if (!_activeSignal.WaitOne(_transferTimeout, false)) + { + if (_response.Code == FtpResponseCode.CannotOpenDataConnection) + throw new FtpDataConnectionException(String.Format(CultureInfo.InvariantCulture, "The ftp destination was unable to open a data connection to the ftp client on port {0}.", _activePort)); + else + throw new FtpDataConnectionTimeoutException("The data connection timed out waiting for data to transfer from the destination."); + } + } + else + return; + } + + private void CreatePassiveConn() + { + if (_dataConn != null) return; + + // send command to get passive port to be used from the server + try + { + SendRequest(new FtpRequest(_encoding, FtpCmd.Pasv)); + } + catch (FtpException fex) + { + throw new FtpDataConnectionException("An error occurred while issuing up a passive FTP connection command.", fex); + } + + // get the port on the end + // to calculate the port number we must multiple the 5th value by 256 and then add the 6th value + // example: + // Client> PASV + // Server> 227 Entering Passive Mode (123,45,67,89,158,26) + // In the example of PASV mode the server has said it will be listening on IP address 123.45.67.89 + // on TCP port 40474 for the data channel. (Note: the destinationPort is the 158,26 pair and is: 158x256 + 26 = 40474). + + // get the begin and end positions to extract data from the response string + int startIdx = _response.Text.IndexOf("(") + 1; + int endIdx = _response.Text.IndexOf(")"); + + // parse the transfer connection data from the ftp server response + string[] data = _response.Text.Substring(startIdx, endIdx - startIdx).Split(','); + + // build the data host name from the server response + string passiveHost = data[0] + "." + data[1] + "." + data[2] + "." + data[3]; + // extract and convert the port number from the server response + int passivePort = Int32.Parse(data[4], CultureInfo.InvariantCulture) * 256 + Int32.Parse(data[5], CultureInfo.InvariantCulture); + + try + { + // create a new tcp client object and use proxy if supplied + /* if (_proxy != null) + _dataConn = _proxy.CreateConnection(passiveHost, passivePort); + else */ + _dataConn = new TcpClient(passiveHost, passivePort); + + //johnshope.Sync.Log.Debug("Create Passive Connection on Port " + passivePort.ToString()); + + _dataConn.ReceiveBufferSize = _tcpBufferSize; + _dataConn.ReceiveTimeout = _tcpTimeout; + _dataConn.SendBufferSize = _tcpBufferSize; + _dataConn.SendTimeout = _tcpTimeout; + } + catch (Exception ex) + { + throw new FtpDataConnectionException(String.Format(CultureInfo.InvariantCulture, "An error occurred while opening passive data connection to destination '{0}' on port '{1}'.", passiveHost, passivePort), ex); + } + } + + /// + /// Creates an SSL or TLS secured stream. + /// + /// Unsecured stream. + /// Secured stream + private Stream CreateSslStream(Stream stream) + { + // create an SSL or TLS stream that will close the client's stream + SslStream ssl = new SslStream(stream, true, new RemoteCertificateValidationCallback(secureStream_ValidateServerCertificate), null); + + // choose the protocol + SslProtocols protocol = SslProtocols.None; + switch (_securityProtocol) + { + case FtpSecurityProtocol.Tls1OrSsl3Explicit: + case FtpSecurityProtocol.Tls1OrSsl3Implicit: + protocol = SslProtocols.Default; + break; + case FtpSecurityProtocol.Ssl2Explicit: + case FtpSecurityProtocol.Ssl2Implicit: + protocol = SslProtocols.Ssl2; + break; + case FtpSecurityProtocol.Ssl3Explicit: + case FtpSecurityProtocol.Ssl3Implicit: + protocol = SslProtocols.Ssl3; + break; + case FtpSecurityProtocol.Tls1Explicit: + case FtpSecurityProtocol.Tls1Implicit: + protocol = SslProtocols.Tls; + break; + default: + throw new FtpSecureConnectionException(String.Format("Unhandled FtpSecurityProtocol type '{0}'.", _securityProtocol.ToString())); + } + + // The server name must match the name on the server certificate. + try + { + // authenicate the client + ssl.AuthenticateAsClient(_host, _clientCertificates, protocol, true); + } + catch (AuthenticationException authEx) + { + throw new FtpAuthenticationException("Secure FTP session certificate authentication failed.", authEx); + } + + return ssl; + } + + // the following method is invoked by the RemoteCertificateValidationDelegate. + private bool secureStream_ValidateServerCertificate(object sender, X509Certificate certificate, X509Chain chain, SslPolicyErrors sslPolicyErrors) + { + //// if it is the same certificate we have already approved then don't validate it again + if (_serverCertificate != null && certificate.GetCertHashString() == _serverCertificate.GetCertHashString()) + return true; + + // invoke the ValidateServerCertificate event if the user is subscribing to it + // ignore our own logic and let the user decide if the certificate is valid or not + if (ValidateServerCertificate != null) + { + ValidateServerCertificateEventArgs args = new ValidateServerCertificateEventArgs(new X509Certificate2(certificate.GetRawCertData()), chain, sslPolicyErrors); + ValidateServerCertificate(this, args); + // make a copy of the certificate due to sharing violations + if (args.IsCertificateValid) + _serverCertificate = new X509Certificate2(certificate.GetRawCertData()); + return args.IsCertificateValid; + } + else + { + // analyze the policy errors and decide if the certificate should be accepted or not. + if ((sslPolicyErrors & SslPolicyErrors.RemoteCertificateNameMismatch) == SslPolicyErrors.RemoteCertificateNameMismatch) + throw new FtpCertificateValidationException(String.Format("Certificate validation failed. The host name '{0}' does not match the name on the security certificate '{1}'. To override this behavior, subscribe to the ValidateServerCertificate event to validate certificates.", _host, certificate.Issuer)); + + if (sslPolicyErrors == SslPolicyErrors.None || (sslPolicyErrors & SslPolicyErrors.RemoteCertificateChainErrors) == SslPolicyErrors.RemoteCertificateChainErrors) + { + // make a copy of the server certificate due to sharing violations + _serverCertificate = new X509Certificate2(certificate.GetRawCertData()); + return true; + } + else + { + return false; + } + } + + } + + private void CreateSslExplicitCommandStream() + { + try + { + // send authentication type request + string authCommand = ""; + switch (_securityProtocol) + { + case FtpSecurityProtocol.Tls1OrSsl3Explicit: + case FtpSecurityProtocol.Ssl3Explicit: + case FtpSecurityProtocol.Ssl2Explicit: + authCommand = "SSL"; + break; + case FtpSecurityProtocol.Tls1Explicit: + authCommand = "TLS"; + break; + } + + Debug.Assert(authCommand.Length > 0, "auth command should have a value - make sure every enum option in auth command has a corresponding value"); + + SendRequest(new FtpRequest(_encoding, FtpCmd.Auth, authCommand)); + + // set the active command stream to the ssl command stream object + lock (_reponseMonitorLock) + { + _commandStream = CreateSslStream(_commandConn.GetStream()); + } + + SendRequest(new FtpRequest(_encoding, FtpCmd.Pbsz, "0")); + SendRequest(new FtpRequest(_encoding, FtpCmd.Prot, "P")); + } + catch (FtpAuthenticationException fauth) + { + throw new FtpSecureConnectionException(String.Format("An ftp authentication exception occurred while setting up a explicit ssl/tls command stream. {0}", fauth.Message), _response, fauth); + } + catch (FtpException fex) + { + throw new FtpSecureConnectionException(String.Format("An error occurred while setting up a explicit ssl/tls command stream. {0}", fex.Message), _response, fex); + } + + } + + private void CreateSslImplicitCommandStream() + { + try + { + // set the active command stream to the ssl command stream object + lock (_reponseMonitorLock) + { + _commandStream = CreateSslStream(_commandConn.GetStream()); + } + } + catch (FtpAuthenticationException fauth) + { + throw new FtpSecureConnectionException(String.Format("An ftp authentication exception occurred while setting up a implicit ssl/tls command stream. {0}", fauth.Message), _response, fauth); + } + catch (FtpException fex) + { + throw new FtpSecureConnectionException(String.Format("An error occurred while setting up a implicit ssl/tls command stream. {0}", fex.Message), _response, fex); + } + + } + + private void DoIntegrityCheck(FtpRequest request, Stream stream, long restartPosition) + { + // get the file path from the request argument + string path = request.Arguments[0]; + long startPos = restartPosition; + long endPos = stream.Length; + + string streamHash = ComputeChecksum(_hashAlgorithm, stream, startPos); + string serverHash = GetChecksum(_hashAlgorithm, path, startPos, endPos); + + // string compare the dataHash to the server hash value and see if they are the same + if (String.Compare(streamHash, serverHash, StringComparison.InvariantCultureIgnoreCase) != 0) + throw new FtpFileIntegrityException(String.Format("File integrity check failed. The destination integrity value '{0}' for the file '{1}' did not match the data transfer integrity value '{2}'.", serverHash, path, streamHash)); + } + + #endregion + + #region Destructors + + /// + /// Disposes all objects and connections. + /// + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + + /// + /// Dispose Method. + /// + /// + protected virtual void Dispose(bool disposing) + { + if (disposing) + { + if (_asyncWorker != null && _asyncWorker.IsBusy) + _asyncWorker.CancelAsync(); + + if (_activeListener != null) + _activeListener.Stop(); + + if (_dataConn != null && _dataConn.Connected) + _dataConn.Close(); + + if (_commandConn != null && _commandConn.Connected) + _commandConn.Close(); + + if (_activeSignal != null) + _activeSignal.Close(); + + if (_responseMonitor != null && _responseMonitor.IsAlive) + _responseMonitor.Abort(); + } + } + + /// + /// Dispose deconstructor. + /// + ~FtpBase() + { + Dispose(false); + } + + #endregion + } +} diff --git a/Source/MSBuild.Community.Tasks/Sync/FTP/FtpClient.cs b/Source/MSBuild.Community.Tasks/Sync/FTP/FtpClient.cs new file mode 100644 index 00000000..d743250d --- /dev/null +++ b/Source/MSBuild.Community.Tasks/Sync/FTP/FtpClient.cs @@ -0,0 +1,2441 @@ +/* + * Authors: Benton Stark + * + * Copyright (c) 2007-2009 Starksoft, LLC (http://www.starksoft.com) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + */ + +using System; +using System.Net; +using System.Net.Sockets; +using System.IO; +using System.Text; +using System.Threading; +using System.Globalization; +using System.ComponentModel; +using System.Linq; +using System.Collections.Generic; +//using Starksoft.Net.Proxy; + +namespace Starksoft.Net.Ftp { + + #region Public Enums + /// + /// Enumeration representing type of file transfer mode. + /// + public enum TransferType : int { + /// + /// No transfer type. + /// + None, + /// + /// Transfer mode of type 'A' (ascii). + /// + Ascii, + /// + /// Transfer mode of type 'I' (image or binary) + /// + Binary // TYPE I + } + + /// + /// Enumeration representing the three types of actions that FTP supports when + /// uploading or 'putting' a file on an FTP server from the FTP client. + /// + public enum FileAction : int { + /// + /// No action. + /// + None, + /// + /// Create a new file or overwrite an existing file. + /// + Create, + /// + /// Create a new file. Do not overwrite an existing file. + /// + CreateNew, + /// + /// Create a new file or append an existing file. + /// + CreateOrAppend, + /// + /// Resume a file transfer. + /// + Resume, + /// + /// Resume a file transfer if the file already exists. Otherwise, create a new file. + /// + ResumeOrCreate + } + + + #endregion + + /// + /// The FtpClient Component for .NET is a fully .NET coded RFC 959 compatible FTP object component that supports the RFC 959, SOCKS and HTTP proxies, SSLv2, SSLv3, and TLSv1 + /// as well as automatic file integrity checks on all data transfers. + /// The component library also supports a pluggable directory listing parser. The Starksoft FtpClient Component for .NET support most FTP servers. + /// + /// + /// + /// The object implements and uses the following FTP commands and provides a simple to use component. + /// FTP RFC 959 commands (and extended) directly supported: + /// USER RMD CDUP CWD STOU RETR AUTH SITE CHMOD + /// PASS RETR DELE PORT APPE MDTM PROT + /// QUIT PWD TYPE PASV REST SIZE MODE + /// MKD SYST MODE STOR RNFR FEAT XSHA1 + /// NLST HELP RNTO SITE ALLO QUIT XMD5 + /// ABORT STAT LIST NOOP PBSZ XCRC + /// + /// + /// Custom FTP server commands can be executed using the Quote() method. This allows the FtpClient object to handle + /// certain custom commands that are not supported by the RFC 959 standard but are required by specific FTP server + /// implementations for various tasks. + /// + /// + /// The Starksoft FtpClient Component for .NET supports SOCKS v4, SOCKS v4a, SOCKS v5, and HTTP proxy servers. The proxy settings are not read + /// from the local web browser proxy settings so deployment issues are not a problem with using proxy connections. In addition the library also + /// supports active and passive (firewall friendly) mode transfers. The Starksoft FtpClient Component for .NET supports data compression, bandwidth throttling, + /// and secure connections through SSL (Secure Socket Layer) and TLS. The Starksoft FtpClient Component for .NET also supports automatic transfer integrity checks via + /// CRC, MD5, and SHA1. The FtpClient object can parse many different directory listings from various FTP server implementations. But for those servers that are difficult to + /// parse of produce strange directory listings you can write your own ftp item parser. See the IFtpItemParser interface + /// for more information and an example parser. Finally, the Starksoft FtpClient Component for .NET also provides support for encrypting and decrypting PGP data files though a .NET wrapper + /// class that interfaces directly with the open source GNU Open PGP executable. + /// + /// + /// The FtpClient libary has been tested with the following FTP servers and file formats. + /// + /// IIS 6.0 under Microsoft Windows 2000 and Windows 2003 server, + /// Microsoft FTP server running IIS 5.0 + /// Gene6FTP Server + /// ProFTPd + /// Wu-FTPd + /// WS_FTP Server (by Ipswitch) + /// Serv-U FTP Server + /// GNU FTP server + /// Many public FTP servers + /// + /// + /// + /// + /// + /// FtpClient ftp = new FtpClient("ftp.microsoft.com"); + /// // note: DataTransferMode is actually passive mode (PASV) by default + /// ftp.DataTransferMode = DataTransferMode.Passive; + /// ftp.Open("anonymous", "myemail@host.com"); + /// ftp.ChangeDirectory("Softlib"); + /// ftp.GetFile("README.TXT", "c:\\README.TXT"); + /// ftp.Close(); + /// + /// + public class FtpClient : FtpBase { + #region Contructors + + /// + /// FtpClient default constructor. + /// + public FtpClient() + : base(DEFAULT_FTP_PORT, FtpSecurityProtocol.None) { } + + /// + /// Constructor method for FtpClient. + /// + /// String containing the host name or ip address of the remote FTP server. + /// + /// This method takes one parameter to specify + /// the host name (or ip address). + /// + public FtpClient(string host) + : this(host, DEFAULT_FTP_PORT, FtpSecurityProtocol.None) { } + + /// + /// Constructor method for FtpClient. + /// + /// String containing the host name or ip address of the remote FTP server. + /// Port number used to connect to the remote FTP server. + /// + /// This method takes two parameters that specify + /// the host name (or ip address) and the port to connect to the host. + /// + public FtpClient(string host, int port) + : base(host, port, FtpSecurityProtocol.None) { } + + /// + /// Constructor method for FtpClient. + /// + /// String containing the host name or ip address of the remote FTP server. + /// Port number used to connect to the remote FTP server. + /// Enumeration value indicating what security protocol (such as SSL) should be enabled for this connection. + /// + /// This method takes three parameters that specify + /// the host name (or ip address), port to connect to and what security protocol should be used when establishing the connection. + /// + public FtpClient(string host, int port, FtpSecurityProtocol securityProtocol) + : base(host, port, securityProtocol) { } + + #endregion + + #region Private Variables and Constants + + private const int DEFAULT_FTP_PORT = 21; // default port is 21 + private const int FXP_TRANSFER_TIMEOUT = 600000; // 10 minutes + + private TransferType _fileTransferType = TransferType.Binary; + private IFtpItemParser _itemParser; + private string _user; + private string _password; + private bool _opened; + private string _currentDirectory; + private int _fxpTransferTimeout = FXP_TRANSFER_TIMEOUT; + + // transfer log + private Stream _log = new MemoryStream(); + private bool _isLoggingOn; + + #endregion + + #region Public Properties + + /// + /// Gets or sets the file transfer item. + /// + public TransferType FileTransferType { + get { return _fileTransferType; } + set { + _fileTransferType = value; + + if (this.IsConnected == true) { + // update the server with the new file transfer type + SetFileTransferType(); + } + } + } + + /// + /// Gets or sets the the directory item parser to use when parsing directory listing data from the FTP server. + /// This parser is used by the GetDirList() and GetDirList(string) methods. + /// + /// + /// You can create your own custom directory listing parser by creating an object that implements the + /// IFtpItemParser interface. This is particular useful when parsing exotic file directory listing + /// formats from specific FTP servers. + /// + public IFtpItemParser ItemParser { + get { return _itemParser; } + set { _itemParser = value; } + } + + /// + /// Gets or sets logging of file transfers. + /// + /// + /// All data transfer activity can be retrieved from the Log property. + /// + public bool IsLoggingOn { + get { return _isLoggingOn; } + set { _isLoggingOn = value; } + } + + /// + /// Gets or sets the Stream object used for logging data transfer activity. + /// + /// + /// By default a MemoryStream object is created to log all data transfer activity. Any + /// Stream object that can be written to may be used in place of the MemoryStream object. + /// + /// + public Stream Log { + get { return _log; } + set { + if (((Stream)value).CanWrite == false) + throw new ArgumentException("must be writable. The property CanWrite must have a value equals to 'true'.", "value"); + _log = value; + } + } + + /// + /// Gets or sets the timeout value in miliseconds when waiting for an FXP server to server transfer to complete. + /// + /// By default this timeout value is set to 600000 (10 minutes). For large FXP file transfers you may need to adjust this number higher. + public int FxpTransferTimeout { + get { return _fxpTransferTimeout; } + set { _fxpTransferTimeout = value; } + } + + /// + /// Gets the current directory path without sending having to send a request to the server. + /// + /// + public string CurrentDirectory { + get { return _currentDirectory; } + } + + + private int? FindFileTimeOffset(string path) { + if (!string.IsNullOrEmpty(path)) ChangeDirectory(path); + var items = GetDirList(); + var item = items.FirstOrDefault(x => x.ItemType == FtpItemType.File); + if (item != null) { + var t = GetFileDateTime(item.Name, false); + return (int?)((item.Modified - t).TotalHours + 0.5); + } + foreach (var dir in items.Where(x => x.ItemType == FtpItemType.Directory)) { + var offset = FindFileTimeOffset(dir.FullPath); + if (offset.HasValue) return offset; + } + return null; + } + + public int? TimeOffset = null; + public int ServerTimeOffset { + get { + if (!TimeOffset.HasValue) { + TimeOffset = (int)((DateTime.Now - DateTime.UtcNow).TotalHours + 0.5); + TimeOffset = FindFileTimeOffset(null) ?? 0; + } + return TimeOffset.Value; + } + set { TimeOffset = value; } + } + + public string ServerTimeString { + get { + var offset = ServerTimeOffset; + string zone; + if (offset == 0) zone = "Z"; + else { + zone = offset.ToString(); + if (offset > 0) zone = "+" + zone; + else zone = "-" + zone; + } + return DateTime.UtcNow.AddHours(offset).ToShortTimeString() + zone; + } + } + #endregion + + #region Public Methods + + public virtual string RootDirectory { get; set; } + /// + /// Opens a connection to the remote FTP server and log in with user name and password credentials. + /// + /// User name. Many public ftp allow for this value to 'anonymous'. + /// Password. Anonymous public ftp servers generally require a valid email address for a password. + /// Use the Close() method to log off and close the connection to the FTP server. + /// + /// + /// + public virtual void Open(string user, string password) { + if (user == null) + throw new ArgumentNullException("user", "must have a value"); + + if (user.Length == 0) + throw new ArgumentException("must have a value", "user"); + + if (password == null) + throw new ArgumentNullException("password", "must have a value or an empty string"); + + // if the command connection is no already open then open a new command connect + if (!this.IsConnected) + base.OpenCommandConn(); + + // test to see if this is an asychronous operation and if so make sure + // the user has not requested the operation to be canceled + if (base.AsyncWorker != null && base.AsyncWorker.CancellationPending) { + base.CloseAllConnections(); + return; + } + + _user = user; + _password = password; + _currentDirectory = "/"; + + try { + base.SendRequest(new FtpRequest(base.CharacterEncoding, FtpCmd.User, user)); + } catch (FtpException fex) { + throw new FtpConnectionOpenException(String.Format("An error occurred when sending user information. Reason: {0}", base.LastResponse.Text), fex); + } + + // wait for user to log into system and all response messages to be transmitted + Thread.Sleep(500); + + // test to see if this is an asychronous operation and if so make sure + // the user has not requested the operation to be canceled + if (base.AsyncWorker != null && base.AsyncWorker.CancellationPending) { + base.CloseAllConnections(); + return; + } + + // some ftp servers do not require passwords for users and will log you in immediately - no password command is required + if (base.LastResponse.Code != FtpResponseCode.UserLoggedIn) { + try { + base.SendRequest(new FtpRequest(base.CharacterEncoding, FtpCmd.Pass, password)); + } catch (FtpException fex) { + throw new FtpConnectionOpenException(String.Format("An error occurred when sending password information. Reason: {0}", base.LastResponse.Text), fex); + } + + if (base.LastResponse.Code == FtpResponseCode.NotLoggedIn) + throw new FtpLoginException("Unable to log into FTP destination with supplied username and password."); + } + + // test to see if this is an asychronous operation and if so make sure + // the user has not requested the operation to be canceled + if (base.AsyncWorker != null && base.AsyncWorker.CancellationPending) { + base.CloseAllConnections(); + return; + } + + // if the custom item parser is not set then set to use the built-in generic parser + if (_itemParser == null) + _itemParser = new FtpGenericParser(); + + // set the file type used for transfers + SetFileTransferType(); + + // if compression is indicated then send the compression command + if (base.IsCompressionEnabled) + base.CompressionOn(); + + // test to see if this is an asychronous operation and if so make sure + // the user has not requested the operation to be canceled + if (base.AsyncWorker != null && base.AsyncWorker.CancellationPending) { + base.CloseAllConnections(); + return; + } + + _opened = true; + _currentDirectory = GetWorkingDirectory(); + RootDirectory = _currentDirectory; + } + + /// + /// Reopens a lost ftp connection. + /// + /// + /// If the connection is currently open or the connection has never been open and FtpException is thrown. + /// + public virtual void Reopen() { + if (!_opened) + throw new FtpException("You must use the Open() method before using the Reopen() method."); + + // reopen the connection with the same username and password + Open(_user, _password); + } + + /// + /// Change the currently logged in user to another user on the FTP server. + /// + /// The name of user. + /// The password for the user. + public virtual void ChangeUser(string user, string password) { + if (user == null) + throw new ArgumentNullException("user", "must have a value"); + + if (user.Length == 0) + throw new ArgumentException("must have a value", "user"); + + if (password == null) + throw new ArgumentNullException("password", "must have a value"); + + try { + base.SendRequest(new FtpRequest(base.CharacterEncoding, FtpCmd.User, user)); + } catch (FtpException fex) { + throw new FtpException("An error occurred when sending user information.", base.LastResponse, fex); + } + + // wait for user to log into system and all response messages to be transmitted + Thread.Sleep(500); + + // test to see if this is an asychronous operation and if so make sure + // the user has not requested the operation to be canceled + if (base.AsyncWorker != null && base.AsyncWorker.CancellationPending) { + base.CloseAllConnections(); + return; + } + + // some ftp servers do not require passwords for users and will log you in immediately - no password command is required + if (base.LastResponse.Code != FtpResponseCode.UserLoggedIn) { + try { + base.SendRequest(new FtpRequest(base.CharacterEncoding, FtpCmd.Pass, password)); + } catch (FtpException fex) { + throw new FtpException("An error occurred when sending password information.", base.LastResponse, fex); + } + + if (base.LastResponse.Code == FtpResponseCode.NotLoggedIn) + throw new FtpLoginException("Unable to log into FTP destination with supplied username and password."); + } + } + + public virtual string CorrectPath(string path) { + if (path.StartsWith("/")) { + if (RootDirectory.EndsWith("/")) path = RootDirectory + path.Substring(1); + else path = RootDirectory + path; + } + return path; + } + /// + /// Closes connection to the FTP server. + /// + /// + /// + /// + public virtual void Close() { + base.CloseAllConnections(); + } + + /// + /// Changes the current working directory on older FTP servers that cannot handle a full path containing + /// multiple subdirectories. This method will separate the full path into separate change directory commands + /// to support such systems. + /// + /// Path of the new directory to change to. + /// Accepts both foward slash '/' and back slash '\' path names. + /// + /// + public virtual void ChangeDirectoryMultiPath(string path) { + // the change working dir command can generally handle all the weird directory name spaces + // which is nice but frustrating that the ftp server implementors did not fix it for other commands + + if (path == null) + throw new ArgumentNullException("path"); + + if (path.Length == 0) + throw new ArgumentException("must have a value", "path"); + + // replace the windows style directory delimiter with a unix style delimiter + path = path.Replace("\\", "/"); + + char[] splitter = new char[] { '/' }; + + var dirs = path.Split(splitter, StringSplitOptions.RemoveEmptyEntries).ToList(); + List cdirs = new List(); + + if (path.StartsWith("/")) { + _currentDirectory = GetWorkingDirectory(); + cdirs = (_currentDirectory ?? "/").Split(splitter, StringSplitOptions.RemoveEmptyEntries).ToList(); + while (dirs.Count > 0 && cdirs.Count > 0 && dirs[0] == cdirs[0]) { + dirs.RemoveAt(0); cdirs.RemoveAt(0); + } + } + + try { + // issue a single CWD command for each directory + // this is a very reliable method to change directories on all FTP servers + // because some systems do not all a full path to be specified when changing directories + var n = cdirs.Count; + while (n-- > 0) ChangeDirectoryUp(); + foreach (string dir in dirs) { + base.SendRequest(new FtpRequest(base.CharacterEncoding, FtpCmd.Cwd, dir)); + } + } catch (FtpException fex) { + throw new FtpException(String.Format("Could not change working directory to '{0}'.", path), fex); + } finally { + _currentDirectory = GetWorkingDirectory(); + } + } + + /// + /// Changes the current working directory on the server. Some FTP server will not accept this command + /// if the path contains mutiple directories. For those FTP server implementations see the method + /// ChangeDirectoryMultiPath(string). + /// + /// Path of the new directory to change to. + /// Accepts both foward slash '/' and back slash '\' path names. + /// + /// + public virtual void ChangeDirectory(string path) { + // the change working dir command can generally handle all the weird directory name spaces + // which is nice but frustrating that the ftp server implementors did not fix it for other commands + + if (path == null) + throw new ArgumentNullException("path"); + + if (path.Length == 0) + throw new ArgumentException("must have a value", "path"); + + // replace the windows style directory delimiter with a unix style delimiter + path = path.Replace("\\", "/"); + + try { + base.SendRequest(new FtpRequest(base.CharacterEncoding, FtpCmd.Cwd, path)); + } catch (FtpException fex) { + throw new FtpException(String.Format("Could not change working directory to '{0}'.", path), fex); + } finally { + _currentDirectory = GetWorkingDirectory(); + } + } + + + /// + /// Gets the current working directory. + /// + /// A string value containing the current full working directory path on the FTP server. + /// + /// + public virtual string GetWorkingDirectory() { + try { + base.SendRequest(new FtpRequest(base.CharacterEncoding, FtpCmd.Pwd)); + } catch (FtpException fex) { + throw new FtpException("Could not retrieve working directory.", base.LastResponse, fex); + } + + // now we have to fix the directory due to formatting + // most ftp servers send something like this: 257 "/awg/inbound" is current directory. + string dir = base.LastResponse.Text; + + // if the pwd is in quotes, then extract it + if (dir.Substring(0, 1) == "\"") + dir = dir.Substring(1, dir.IndexOf("\"", 1) - 1); + + return dir; + } + + /// + /// Deletes a file on the remote FTP server. + /// + /// The path name of the file to delete. + /// + /// The file is deleted in the current working directory if no path information + /// is specified. Otherwise a full absolute path name can be specified. + /// Note that some FTP servers will not accept a full path. On those systems you must navigate to + /// the directory you wish to delete the file using with the ChangeDirectory() or ChangeDirectoryMultiPath() + /// method. + /// + /// + public virtual void DeleteFile(string path) { + if (path == null) + throw new ArgumentNullException("path"); + + if (path.Length == 0) + throw new ArgumentException("must have a value", "path"); + + + try { + base.SendRequest(new FtpRequest(base.CharacterEncoding, FtpCmd.Dele, path)); + } catch (FtpException fex) { + throw new FtpException(String.Format("Unable to the delete file {0}.", path), base.LastResponse, fex); + } + } + + /// + /// Aborts an action such as transferring a file to or from the server. + /// + /// + /// The abort command is sent up to the server signaling the server to abort the current activity. + /// + public virtual void Abort() { + try { + base.SendRequest(new FtpRequest(base.CharacterEncoding, FtpCmd.Abor)); + } catch (FtpException fex) { + throw new FtpException("Abort command failed or was unable to be issued.", base.LastResponse, fex); + } + } + + /// + /// Creates a new directory on the remote FTP server. + /// + /// The name of a new directory or an absolute path name for a new directory. + /// + /// If a directory name is given for path then the server will create a new subdirectory + /// in the current working directory. Optionally, a full absolute path may be given. + /// Note that some FTP servers will not accept a full path. On those systems you must navigate to + /// the directory you wish to make the subdirectory using with the ChangeDirectory() or ChangeDirectoryMultiPath() + /// method. + /// + public virtual void MakeDirectory(string path) { + if (path == null) + throw new ArgumentNullException("path"); + + if (path.Length == 0) + throw new ArgumentException("must contain a value", "path"); + + + try { + base.SendRequest(new FtpRequest(base.CharacterEncoding, FtpCmd.Mkd, path)); + } catch (FtpException fex) { + throw new FtpException(String.Format("The directory {0} could not be created.", path), base.LastResponse, fex); + } + } + + /// + /// Moves a file on the remote FTP server from one directory to another. + /// + /// Path and/or file name to be moved. + /// Destination path specifying the directory where the file will be moved to. + /// + /// This method actually results in several FTP commands being issued to the server to perform the physical file move. + /// This method is available for your convenience when performing common tasks such as moving processed files out of a pickup directory + /// and into a archive directory. + /// Note that some older FTP server implementations will not accept a full path to a filename. On those systems this method may not work + /// properly. + /// + public virtual void MoveFile(string fromPath, string toPath) { + if (fromPath == null) + throw new ArgumentNullException("fromPath"); + + if (fromPath.Length == 0) + throw new ArgumentException("must contain a value", "fromPath"); + + if (toPath == null) + throw new ArgumentNullException("toPath"); + + if (fromPath.Length == 0) + throw new ArgumentException("must contain a value", "toPath"); + + // retrieve the server file from the current working directory + MemoryStream fileStream = new MemoryStream(); + GetFile(fromPath, fileStream, false); + + // create the remote file in the new location + this.PutFile(fileStream, toPath, FileAction.Create); + + // delete the original file from the original location + this.DeleteFile(fromPath); + } + + /// + /// Deletes a directory from the FTP server. + /// + /// Directory to delete. + /// + /// The path can be either a specific subdirectory relative to the + /// current working directory on the server or an absolute path to + /// the directory to remove. + /// Note that some FTP servers will not accept a full path. On those systems you must navigate to + /// the parent directory of the directory you wish to delete using with the ChangeDirectory() or ChangeDirectoryMultiPath() + /// method. + /// + /// + public virtual void DeleteDirectory(string path) { + if (path == null) + throw new ArgumentNullException("path"); + + if (path.Length == 0) + throw new ArgumentException("must have a value", "path"); + + try { + base.SendRequest(new FtpRequest(base.CharacterEncoding, FtpCmd.Rmd, path)); + } catch (FtpException fex) { + throw new FtpException(String.Format("The FTP destination was unable to delete the directory '{0}'.", path), base.LastResponse, fex); + } + + } + /// + /// Executes the specific help dialog on the FTP server. + /// + /// + /// A string contains the help dialog from the FTP server. + /// + /// + /// Every FTP server supports a different set of commands and this commands + /// can be obtained by the FTP HELP command sent to the FTP server. The information sent + /// back is not parsed or processed in any way by the FtpClient object. + /// + public virtual string GetHelp() { + try { + base.SendRequest(new FtpRequest(base.CharacterEncoding, FtpCmd.Help)); + } catch (FtpException fex) { + throw new FtpException("An error occurred while getting the system help.", base.LastResponse, fex); + } + + return base.LastResponse.Text; + } + + /// + /// Retrieves the data and time for a specific file on the ftp server as a Coordinated Universal Time (UTC) value (formerly known as GMT). + /// + /// The name of the file. + /// Specifies if modified date and time as reported on the FTP server should be adjusted to the local time zone with daylight savings on the client. + /// + /// A date time value. + /// + /// + /// This function uses the MDTM command which is an additional feature command and therefore not supported + /// by all FTP servers. + /// + /// + public virtual DateTime GetFileDateTime(string fileName, bool adjustToLocalTime) { + if (fileName == null) + throw new ArgumentNullException("fileName"); + + if (fileName.Length == 0) + throw new ArgumentException("must contain a value", "fileName"); + + try { + base.SendRequest(new FtpRequest(base.CharacterEncoding, FtpCmd.Mdtm, fileName)); + } catch (FtpException fex) { + throw new FtpException(String.Format("An error occurred when retrieving file date and time for '{0}'.", fileName), base.LastResponse, fex); + } + + string response = base.LastResponse.Text; + + int year = int.Parse(response.Substring(0, 4), CultureInfo.InvariantCulture); + int month = int.Parse(response.Substring(4, 2), CultureInfo.InvariantCulture); + int day = int.Parse(response.Substring(6, 2), CultureInfo.InvariantCulture); + int hour = int.Parse(response.Substring(8, 2), CultureInfo.InvariantCulture); + int minute = int.Parse(response.Substring(10, 2), CultureInfo.InvariantCulture); + int second = int.Parse(response.Substring(12, 2), CultureInfo.InvariantCulture); + + DateTime dateUtc = new DateTime(year, month, day, hour, minute, second, DateTimeKind.Utc); + + if (adjustToLocalTime) + return new DateTime(dateUtc.ToLocalTime().Ticks); + else + return new DateTime(dateUtc.Ticks); + } + + /// + /// Set the date and time for a specific file or directory on the server. + /// + /// The path or name of the file or directory. + /// New date to set on the file or directory. + /// + /// This function uses the MDTM command which is an additional feature command and therefore not supported + /// by all FTP servers. + /// Note that some FTP servers will not accept a full path. On those systems you must navigate to + /// the directory which has the file you wish to set the date and time using with the ChangeDirectory() or ChangeDirectoryMultiPath() + /// method. + /// + /// + public virtual void SetDateTime(string path, DateTime dateTime) { + if (path == null) + throw new ArgumentNullException("path"); + + if (path.Length == 0) + throw new ArgumentException("must have a value", "path"); + + // MDTM [YYMMDDHHMMSS] [filename] + + string dateTimeArg = dateTime.ToUniversalTime().ToString("yyyyMMddHHmmss"); + + try { + base.SendRequest(new FtpRequest(base.CharacterEncoding, FtpCmd.Mdtm, dateTimeArg, path)); + } catch (FtpException fex) { + throw new FtpException(String.Format("An error occurred when setting a file date and time for '{0}'.", path), fex); + } + } + + /// + /// Retrieves the specific status for the FTP server. + /// + /// + /// Each FTP server may return different status dialog information. The status information sent + /// back is not parsed or processed in any way by the FtpClient object. + /// + /// + /// A string containing the status of the FTP server. + /// + public virtual string GetStatus() { + try { + base.SendRequest(new FtpRequest(base.CharacterEncoding, FtpCmd.Stat)); + } catch (FtpException fex) { + throw new FtpException("An error occurred while getting the system status.", base.LastResponse, fex); + } + + return base.LastResponse.Text; + } + + /// + /// Changes the current working directory on the FTP server to the parent directory. + /// + /// + /// If there is no parent directory then ChangeDirectoryUp() will not have + /// any affect on the current working directory. + /// + /// + /// + public virtual void ChangeDirectoryUp() { + try { + base.SendRequest(new FtpRequest(base.CharacterEncoding, FtpCmd.Cdup)); + int i = _currentDirectory.LastIndexOf('/', _currentDirectory.Length - 2); + _currentDirectory = _currentDirectory.Substring(0, i); + + } catch (FtpException fex) { + throw new FtpException("An error occurred when changing directory to the parent (ChangeDirectoryUp).", base.LastResponse, fex); + } + } + + /// + /// Get the file size for a file on the remote FTP server. + /// + /// The name and/or path to the file. + /// An integer specifying the file size. + /// + /// The path can be file name relative to the current working directory or an absolute path. This command is an additional feature + /// that is not supported by all FTP servers. + /// Note that some FTP servers will not accept a full path. On those systems you must navigate to + /// the directory you wish to get the file size using with the ChangeDirectory() or ChangeDirectoryMultiPath() + /// method. + /// + /// + public virtual long GetFileSize(string path) { + if (path == null) + throw new ArgumentNullException("path"); + + if (path.Length == 0) + throw new ArgumentException("must contain a value", "path"); + + try { + base.SendRequest(new FtpRequest(base.CharacterEncoding, FtpCmd.Size, path)); + } catch (FtpException fex) { + throw new FtpException(String.Format("An error occurred when retrieving file size for {0}.", path), base.LastResponse, fex); + } + + return Int64.Parse(base.LastResponse.Text, CultureInfo.InvariantCulture); + } + + /// + /// Get the additional features supported by the remote FTP server. + /// + /// A string containing the additional features beyond the RFC 959 standard supported by the FTP server. + /// + /// This command is an additional feature beyond the RFC 959 standard and therefore is not supported by all FTP servers. + /// + public virtual string GetFeatures() { + try { + base.SendRequest(new FtpRequest(base.CharacterEncoding, FtpCmd.Feat)); + //return base.TransferText(new FtpRequest(base.CharacterEncoding, FtpCmd.Feat)); + } catch (FtpException fex) { + throw new FtpException("An error occurred when retrieving features.", base.LastResponse, fex); + } + var sb = new StringBuilder(); + for (int i = 1; i < base.LastResponseList.Count - 1; i++) sb.AppendLine(base.LastResponseList[i].RawText.Trim()); + + return sb.ToString(); + } + + /// + /// Retrieves the specific status for a file on the FTP server. + /// + /// + /// The path to the file. + /// + /// + /// A string containing the status for the file. + /// + /// + /// Each FTP server may return different status dialog information. The status information sent + /// back is not parsed or processed in any way by the FtpClient object. + /// Note that some FTP servers will not accept a full path. On those systems you must navigate to + /// the directory you wish to get the status of the file using with the ChangeDirectory() or ChangeDirectoryMultiPath() + /// method. + /// + public virtual string GetStatus(string path) { + if (path == null) + throw new ArgumentNullException("path"); + + if (path.Length == 0) + throw new ArgumentException("must contain a value", "path"); + + try { + base.SendRequest(new FtpRequest(base.CharacterEncoding, FtpCmd.Stat, path)); + } catch (FtpException fex) { + throw new FtpException(String.Format("An error occurred when retrieving file status for file '{0}'.", path), base.LastResponse, fex); + } + + return base.LastResponse.Text; + } + + /// + /// Allocates storage for a file on the FTP server prior to data transfer from the FTP client. + /// + /// + /// The storage size to allocate on the FTP server. + /// + /// + /// Some FTP servers may return the client to specify the storage size prior to data transfer from the FTP client to the FTP server. + /// + public virtual void AllocateStorage(long size) { + try { + base.SendRequest(new FtpRequest(base.CharacterEncoding, FtpCmd.Stor, size.ToString())); + } catch (FtpException fex) { + throw new FtpException("An error occurred when trying to allocate storage on the destination.", base.LastResponse, fex); + } + } + + /// + /// Retrieves a string identifying the remote FTP system. + /// + /// + /// A string contains the server type. + /// + /// + /// The string contains the word "Type:", and the default transfer type + /// For example a UNIX FTP server will return 'UNIX Type: L8'. A Windows + /// FTP server will return 'WINDOWS_NT'. + /// + public virtual string GetSystemType() { + try { + base.SendRequest(new FtpRequest(base.CharacterEncoding, FtpCmd.Syst)); + } catch (FtpException fex) { + throw new FtpException("An error occurred while getting the system type.", base.LastResponse, fex); + } + + return base.LastResponse.Text; + } + + /// + /// Uploads a local file specified in the path parameter to the remote FTP server. + /// + /// Path to a file on the local machine. + /// + /// The file is uploaded to the current working directory on the remote server. + /// A unique file name is created by the server. + /// + public virtual void PutFileUnique(string localPath) { + if (localPath == null) + throw new ArgumentNullException("localPath"); + + try { + using (FileStream fileStream = File.OpenRead(localPath)) { + PutFileUnique(fileStream); + } + } catch (FtpException fex) { + WriteToLog(String.Format("Action='PutFileUnique';Action='TransferError';LocalPath='{0}';CurrentDirectory='{1}';ErrorMessage='{2}'", localPath, _currentDirectory, fex.Message)); + throw new FtpException("An error occurred while executing PutFileUnique() on the remote FTP destination.", base.LastResponse, fex); + } + } + + /// + /// Uploads any stream object to the remote FTP server and stores the data under a unique file name assigned by the FTP server. + /// + /// Any stream object on the local client machine. + /// + /// The stream is uploaded to the current working directory on the remote server. + /// A unique file name is created by the server to store the data uploaded from the stream. + /// + /// + /// + /// + /// + /// + /// + /// + public virtual void PutFileUnique(Stream inputStream) { + if (inputStream == null) + throw new ArgumentNullException("inputStream"); + + if (!inputStream.CanRead) + throw new ArgumentException("must be readable. The CanRead property must return a value of 'true'.", "inputStream"); + + WriteToLog(String.Format("Action='PutFileUnique';Action='TransferBegin';CurrentDirectory='{0}'", _currentDirectory)); + + try { + base.TransferData(TransferDirection.ToServer, new FtpRequest(base.CharacterEncoding, FtpCmd.Stor), inputStream); + } catch (Exception ex) { + WriteToLog(String.Format("Action='PutFileUnique';Action='TransferError';CurrentDirectory='{0}';ErrorMessage='{1}'", _currentDirectory, ex.Message)); + throw new FtpException("An error occurred while executing PutFileUnique() on the remote FTP destination.", base.LastResponse, ex); + } + + WriteToLog(String.Format("Action='PutFileUnique';Action='TransferSuccess';CurrentDirectory='{0}'", _currentDirectory)); + + // TODO: return the name of the file created on the server + } + + /// + /// Retrieves a remote file from the FTP server and writes the data to a local file + /// specfied in the localPath parameter. If the local file already exists a System.IO.IOException is thrown. + /// + /// + /// To retrieve a remote file that you need to overwrite an existing file with or append to an existing file + /// see the method GetFile(string, string, FileAction). + /// + /// A path of the remote file. + /// A fully qualified local path to a file on the local machine. + /// + /// + /// + /// + /// + /// + public virtual void GetFile(string remotePath, string localPath) { + GetFile(remotePath, localPath, FileAction.CreateNew); + } + + /// + /// Retrieves a remote file from the FTP server and writes the data to a local file + /// specfied in the localPath parameter. + /// + /// + /// Note that some FTP servers will not accept a full path. On those systems you must navigate to + /// the directory you wish to get the file using with the ChangeDirectory() or ChangeDirectoryMultiPath() + /// method. + /// + /// A path and/or file name to the remote file. + /// A fully qualified local path to a file on the local machine. + /// The type of action to take. + /// + /// + /// + /// + /// + /// + public virtual void GetFile(string remotePath, string localPath, FileAction action) { + if (remotePath == null) + throw new ArgumentNullException("remotePath"); + + if (remotePath.Length == 0) + throw new ArgumentException("must contain a value", "remotePath"); + + if (localPath == null) + throw new ArgumentNullException("localPath"); + + if (localPath.Length == 0) + throw new ArgumentException("must contain a value", "localPath"); + + if (action == FileAction.None) + throw new ArgumentOutOfRangeException("action", "must contain a value other than 'Unknown'"); + + localPath = CorrectLocalPath(localPath); + + WriteToLog(String.Format("Action='GetFile';Status='TransferBegin';LocalPath='{0}';RemotePath='{1}';FileAction='{1}'", localPath, remotePath, action.ToString())); + + FtpRequest request = new FtpRequest(base.CharacterEncoding, FtpCmd.Retr, remotePath); + + try { + switch (action) { + case FileAction.CreateNew: + // create a file stream to stream the file locally to disk that only creates the file if it does not already exist + using (Stream localFile = File.Open(localPath, FileMode.CreateNew)) { + base.TransferData(TransferDirection.ToClient, request, localFile); + } + break; + + case FileAction.Create: + // create a file stream to stream the file locally to disk + using (Stream localFile = File.Open(localPath, FileMode.Create)) { + base.TransferData(TransferDirection.ToClient, request, localFile); + } + break; + case FileAction.CreateOrAppend: + // open the local file + using (Stream localFile = File.Open(localPath, FileMode.OpenOrCreate)) { + // set the file position to the end so that any new data will be appended + localFile.Position = localFile.Length; + base.TransferData(TransferDirection.ToClient, request, localFile); + } + break; + case FileAction.Resume: + using (Stream localFile = File.Open(localPath, FileMode.Open)) { + // get the size of the file already on the server (in bytes) + long remoteSize = GetFileSize(remotePath); + + // if the files are the same size then there is nothing to transfer + if (localFile.Length == remoteSize) + return; + + base.TransferData(TransferDirection.ToClient, request, localFile, localFile.Length - 1); + } + break; + case FileAction.ResumeOrCreate: + if (File.Exists(localPath) && (new FileInfo(localPath)).Length > 0) + GetFile(remotePath, localPath, FileAction.Resume); + else + GetFile(remotePath, localPath, FileAction.Create); + break; + } + } catch (Exception ex) { + WriteToLog(String.Format("Action='GetFile';Status='TransferError';LocalPath='{0}';RemotePath='{1}';FileAction='{1}';ErrorMessage='{2}", localPath, remotePath, action.ToString(), ex.Message)); + throw new FtpException(String.Format("An unexpected exception occurred while retrieving file '{0}'.", remotePath), base.LastResponse, ex); + } + + WriteToLog(String.Format("Action='GetFile';Status='TransferSuccess';LocalPath='{0}';RemotePath='{1}';FileAction='{1}'", localPath, remotePath, action.ToString())); + } + + /// + /// Retrieves a remote file from the FTP server and writes the data to a local stream object + /// specfied in the outStream parameter. + /// + /// A path and/or file name to the remote file. + /// An output stream object used to stream the remote file to the local machine. + /// A true/false value to indicate if the file download needs to be restarted due to a previous partial download. + /// + /// If the remote path is a file name then the file is downloaded from the FTP server current working directory. Otherwise a fully qualified + /// path for the remote file may be specified. The output stream must be writeable and can be any stream object. Finally, the restart parameter + /// is used to send a restart command to the FTP server. The FTP server is instructed to restart the download process at the last position of + /// of the output stream. Not all FTP servers support the restart command. If the FTP server does not support the restart (REST) command, + /// an FtpException error is thrown. + /// Note that some FTP servers will not accept a full path. On those systems you must navigate to + /// the directory you wish to get the file using with the ChangeDirectory() or ChangeDirectoryMultiPath() + /// method. + /// + /// + /// + /// + /// + /// + /// + public virtual void GetFile(string remotePath, Stream outStream, bool restart) { + if (remotePath == null) + throw new ArgumentNullException("remotePath"); + + if (remotePath.Length == 0) + throw new ArgumentException("must contain a value", "remotePath"); + + if (outStream == null) + throw new ArgumentNullException("outStream"); + + if (outStream.CanWrite == false) + throw new ArgumentException("must be writable. The CanWrite property must return the value 'true'.", "outStream"); + + WriteToLog(String.Format("Action='GetFile';Status='TransferBegin';RemotePath='{0}';", remotePath)); + + FtpRequest request = new FtpRequest(base.CharacterEncoding, FtpCmd.Retr, remotePath); + + if (restart) { + // get the size of the file already on the server (in bytes) + long remoteSize = GetFileSize(remotePath); + + // if the files are the same size then there is nothing to transfer + if (outStream.Length == remoteSize) + return; + + base.TransferData(TransferDirection.ToClient, request, outStream, outStream.Length - 1); + } else { + base.TransferData(TransferDirection.ToClient, request, outStream); + } + WriteToLog(String.Format("Action='GetFile';Status='TransferSuccess';RemotePath='{0}';", remotePath)); + + } + + /// + /// Tests to see if a file or directory exists on the remote server. The current working directory must be the + /// parent or root directory of the file or directory whose existence is being tested. For best results, + /// call this method from the root working directory ("/"). + /// + /// The full path to the remote file or directory relative to the current working directory, or the filename + /// or directory in the current working directory. + /// Boolean value indicating if file exists or not. + /// This method will execute a change working directory (CWD) command prior to testing to see if the + /// file or direcotry exists. The original working directory will be changed back to the original value + /// after this method has completed. This method may not work on systems where the directory listing is not being + /// parsed correctly. If the method call GetDirList() does not work properly with your FTP server, this method may not + /// produce reliable results. This method will also not produce reliable results if the directory or file is hidden on the + /// remote FTP server. + /// + public virtual bool Exists(string path) { + if (path == null) + throw new ArgumentNullException("path"); + + if (path.Length == 0) + throw new ArgumentException("must have a value", "path"); + + // replace the windows style directory delimiter with a unix style delimiter + int chgDirCnt = 0; + bool found = false; + bool errorChgDir = false; + path = path.Replace("\\", "/"); + + string[] dirs = path.Split(new char[] { '/' }, StringSplitOptions.RemoveEmptyEntries); + string filename = ""; + if (dirs.Length < 2) + filename = path; + else + filename = dirs[dirs.Length - 1]; + + try { + // if the path contains more than just the filename then + // we must change the directory to where the file is located + if (dirs.Length > 1) { + for (int i = 0; i < dirs.Length - 1; i++) { + ChangeDirectory(dirs[i]); + chgDirCnt++; + } + } + } catch (FtpException) { + errorChgDir = true; + } + + try { + if (!errorChgDir) + found = GetDirList().ContainsName(filename); + } catch (FtpException) { } + + try { + // change directory back up to the original directory + // this is a very reliable method to change directories on all FTP servers + for (int j = 0; j < chgDirCnt; j++) + this.ChangeDirectoryUp(); + } catch (FtpException) { } + + return found; + } + + + /// + /// Retrieves a file name listing of the current working directory from the + /// remote FTP server using the NLST command. + /// + /// A string containing the file listing from the current working directory. + /// + /// + /// + /// + /// + public virtual string GetNameList() { + return base.TransferText(new FtpRequest(base.CharacterEncoding, FtpCmd.Nlst)); + } + + /// + /// Retrieves a file name listing of the current working directory from the + /// remote FTP server using the NLST command. + /// + /// The path to a directory on the remote FTP server. + /// A string containing the file listing from the current working directory. + /// + /// Note that some FTP servers will not accept a full path. On those systems you must navigate to + /// the parent directory you wish to get the name list using with the ChangeDirectory() or ChangeDirectoryMultiPath() + /// method. + /// + /// + /// + /// + /// + /// + /// + public virtual string GetNameList(string path) { + if (path == null) + throw new ArgumentNullException("path"); + + return base.TransferText(new FtpRequest(base.CharacterEncoding, FtpCmd.Nlst, path)); + } + + + /// + /// Retrieves a directory listing of the current working directory from the + /// remote FTP server using the LIST command. + /// + /// A string containing the directory listing of files from the current working directory. + /// + /// + /// + /// + /// + public virtual string GetDirListAsText() { + return base.TransferText(new FtpRequest(base.CharacterEncoding, FtpCmd.List, "-al")); + } + + /// + /// Retrieves a directory listing of the current working directory from the + /// remote FTP server using the LIST command. + /// + /// The path to a directory on the remote FTP server. + /// A string containing the directory listing of files from the current working directory. + /// + /// Note that some FTP servers will not accept a full path. On those systems you must navigate to + /// the parent directory you wish to get the name list using with the ChangeDirectory() or ChangeDirectoryMultiPath() + /// method. + /// + /// + /// + /// + /// + /// + /// + public virtual string GetDirListAsText(string path) { + if (path == null) + throw new ArgumentNullException("path"); + + return base.TransferText(new FtpRequest(base.CharacterEncoding, FtpCmd.List, "-al", path)); + } + + /// + /// Retrieves a list of the files from current working directory on the remote FTP + /// server using the LIST command. + /// + /// FtpItemList collection object. + /// + /// This method returns a FtpItemList collection of FtpItem objects. + /// + /// + /// + /// + /// + /// + public virtual FtpItemCollection GetDirList() { + var items = new FtpItemCollection(_currentDirectory, base.TransferText(new FtpRequest(base.CharacterEncoding, FtpCmd.List, "-al")), _itemParser); + items.SetTimeOffset(ServerTimeOffset); + return items; + } + + /// + /// Retrieves a list of the files from a specified path on the remote FTP + /// server using the LIST command. + /// + /// The path to a directory on the remote FTP server. + /// FtpFileCollection collection object. + /// + /// This method returns a FtpFileCollection object containing a collection of + /// FtpItem objects. Some FTP server implementations will not accept a full path to a resource. On those + /// systems it is best to change the working directory using the ChangeDirectoryMultiPath(string) method and then call + /// the method GetDirList(). + /// + /// + /// + /// + /// + /// + public virtual FtpItemCollection GetDirList(string path) { + if (path == null) + throw new ArgumentNullException("path"); + + var items = new FtpItemCollection(path, base.TransferText(new FtpRequest(base.CharacterEncoding, FtpCmd.List, "-al", path)), _itemParser); + items.SetTimeOffset(ServerTimeOffset); + return items; + } + + /// + /// Deeply retrieves a list of all files and all sub directories from a specified path on the remote FTP + /// server using the LIST command. + /// + /// The path to a directory on the remote FTP server. + /// FtpFileCollection collection object. + /// + /// This method returns a FtpFileCollection object containing a collection of + /// FtpItem objects. + /// Note that some FTP servers will not accept a full path. On those systems you must navigate to + /// the parent directory you wish to get the directory list using with the ChangeDirectory() or ChangeDirectoryMultiPath() + /// method. + /// + /// + /// + /// + /// + /// + public virtual FtpItemCollection GetDirListDeep(string path) { + if (path == null) + throw new ArgumentNullException("path"); + + FtpItemCollection deepCol = new FtpItemCollection(); + ParseDirListDeep(path, deepCol); + deepCol.SetTimeOffset(ServerTimeOffset); + return deepCol; + } + + /// + /// Renames a file or directory on the remote FTP server. + /// + /// The name or absolute path of the file or directory you want to rename. + /// The new name or absolute path of the file or directory. + /// + public virtual void Rename(string name, string newName) { + if (name == null) + throw new ArgumentNullException("name", "must have a value"); + + if (name.Length == 0) + throw new ArgumentException("must have a value", "name"); + + if (newName == null) + throw new ArgumentNullException("newName", "must have a value"); + + if (newName.Length == 0) + throw new ArgumentException("must have a value", "newName"); + + try { + base.SendRequest(new FtpRequest(base.CharacterEncoding, FtpCmd.Rnfr, name)); + base.SendRequest(new FtpRequest(base.CharacterEncoding, FtpCmd.Rnto, newName)); + } catch (FtpException fex) { + throw new FtpException(String.Format("The FTP destination was unable to rename the file or directory '{0}' to the new name '{1}'.", name, newName), base.LastResponse, fex); + } + + } + + /// + /// Send a raw FTP command to the server. + /// + /// A string containing a valid FTP command value such as SYST. + /// The raw textual response from the server. + /// + /// This is an advanced feature of the FtpClient class that allows for any custom or specialized + /// FTP command to be sent to the FTP server. Some FTP server support custom commands outside of + /// the standard FTP command list. The following commands are not supported: PASV, RETR, STOR, and STRU. + /// + /// + /// + /// FtpClient ftp = new FtpClient("ftp.microsoft.com"); + /// ftp.Open("anonymous", "myemail@server.com"); + /// string response = ftp.Quote("SYST"); + /// System.Diagnostics.Debug.WriteLine(response); + /// ftp.Close(); + /// + /// + public virtual string Quote(string command) { + if (command == null) + throw new ArgumentNullException("command"); + + if (command.Length < 3) { + throw new ArgumentException(String.Format("Invalid command '{0}'.", command), "command"); + } + + char[] separator = { ' ' }; + string[] values = command.Split(separator); + + // extract just the code value + string code; + if (values.Length == 0) { + code = command; + } else { + code = values[0]; + } + + // extract the arguments + string args = string.Empty; + if (command.Length > code.Length) { + args = command.Replace(code, "").TrimStart(); + } + + FtpCmd ftpCmd = FtpCmd.Unknown; + try { + // try to parse out the command if we can + ftpCmd = (FtpCmd)Enum.Parse(typeof(FtpCmd), code, true); + } catch { } + + if (ftpCmd == FtpCmd.Pasv || ftpCmd == FtpCmd.Retr || ftpCmd == FtpCmd.Stor || ftpCmd == FtpCmd.Stou || ftpCmd == FtpCmd.Erpt || ftpCmd == FtpCmd.Epsv) + throw new ArgumentException(String.Format("Command '{0}' not supported by Quote() method.", code), "command"); + + if (ftpCmd == FtpCmd.List || ftpCmd == FtpCmd.Nlst) + return base.TransferText(new FtpRequest(base.CharacterEncoding, ftpCmd, args)); + + + if (ftpCmd == FtpCmd.Unknown) + base.SendRequest(new FtpRequest(base.CharacterEncoding, ftpCmd, command)); + else + base.SendRequest(new FtpRequest(base.CharacterEncoding, ftpCmd, args)); + + return base.LastResponseList.GetRawText(); + } + + /// + /// Sends a NOOP or no operation command to the FTP server. This can be used to prevent some servers from logging out the + /// interactive session during file transfer process. + /// + public virtual void NoOperation() { + try { + base.SendRequest(new FtpRequest(base.CharacterEncoding, FtpCmd.Noop)); + } catch (FtpException fex) { + throw new FtpException("An error occurred while issuing the No Operation command (NOOP).", base.LastResponse, fex); + } + } + + /// + /// Issues a site specific change file mode (CHMOD) command to the server. Not all servers implement this command. + /// + /// The path to the file or directory you want to change the mode on. + /// The CHMOD octal value. + /// + /// Common CHMOD values used on web servers. + /// + /// Value User Group Other + /// 755 rwx r-x r-x + /// 744 rwx r-- r-- + /// 766 rwx rw- rw- + /// 777 rwx rwx rwx + /// Note that some FTP servers will not accept a full path. On those systems you must navigate to + /// the directory containing the file or directory you wish to change the mode on by using with the + /// ChangeDirectory() or ChangeDirectoryMultiPath() method. + /// + /// + /// + public virtual void ChangeMode(string path, int octalValue) { + if (path == null) + throw new ArgumentNullException("path"); + + if (path.Length == 0) + throw new ArgumentException("must have a value", "path"); + + try { + base.SendRequest(new FtpRequest(base.CharacterEncoding, FtpCmd.Site, "CHMOD", octalValue.ToString(), path)); + } catch (FtpException fex) { + throw new FtpException(String.Format("Unable to the change file mode for file {0}. Reason: {1}", path, base.LastResponse.Text), base.LastResponse, fex); + } + + if (base.LastResponse.Code == FtpResponseCode.CommandNotImplementedSuperfluousAtThisSite) + throw new FtpException(String.Format("Unable to the change file mode for file {0}. Reason: {1}", path, base.LastResponse.Text), base.LastResponse); + } + + /// + /// Issue a SITE command to the FTP server for site specific implementation commands. + /// + /// One or more command arguments + /// + /// For example, the CHMOD command is issued as a SITE command. + /// + public virtual void Site(string argument) { + if (argument == null) + throw new ArgumentNullException("argument", "must have a value"); + + if (argument.Length == 0) + throw new ArgumentException("must have a value", "argument"); + + base.SendRequest(new FtpRequest(base.CharacterEncoding, FtpCmd.Site, argument)); + } + + /// + /// Uploads a local file specified in the path parameter to the remote FTP server. + /// + /// Path to a file on the local machine. + /// Filename or full path to file on the remote FTP server. + /// The type of put action taken. + /// + /// The file is uploaded to the current working directory on the remote server. The remotePath + /// parameter is used to specify the path and file name used to store the file on the remote server. + /// Note that some FTP servers will not accept a full path. On those systems you must navigate to + /// the directory you wish to put the file using with the ChangeDirectory() or ChangeDirectoryMultiPath() + /// method. + /// + /// + /// + /// + /// + /// + /// + /// + public virtual void PutFile(string localPath, string remotePath, FileAction action) { + using (FileStream fileStream = File.OpenRead(localPath)) { + PutFile(fileStream, remotePath, action); + } + } + + /// + /// Uploads a local file specified in the path parameter to the remote FTP server. + /// An FtpException is thrown if the file already exists. + /// + /// Path to a file on the local machine. + /// Filename or full path to file on the remote FTP server. + /// + /// The file is uploaded to the current working directory on the remote server. The remotePath + /// parameter is used to specify the path and file name used to store the file on the remote server. + /// To overwrite an existing file see the method PutFile(string, string, FileAction) and specify the + /// FileAction Create. + /// Note that some FTP servers will not accept a full path. On those systems you must navigate to + /// the directory you wish to put the file using with the ChangeDirectory() or ChangeDirectoryMultiPath() + /// method. + /// + /// + /// + /// + /// + /// + /// + /// + public virtual void PutFile(string localPath, string remotePath) { + using (FileStream fileStream = File.OpenRead(localPath)) { + PutFile(fileStream, remotePath, FileAction.CreateNew); + } + } + + /// + /// Uploads a local file specified in the path parameter to the remote FTP server. + /// + /// Path to a file on the local machine. + /// The type of put action taken. + /// + /// The file is uploaded to the current working directory on the remote server. + /// + /// + /// + /// + /// + /// + /// + /// + public virtual void PutFile(string localPath, FileAction action) { + using (FileStream fileStream = File.OpenRead(localPath)) { + PutFile(fileStream, ExtractPathItemName(localPath), action); + } + } + + /// + /// Uploads a local file specified in the path parameter to the remote FTP server. + /// An FtpException is thrown if the file already exists. + /// + /// Path to a file on the local machine. + /// + /// The file is uploaded to the current working directory on the remote server. + /// + /// + /// + /// + /// + /// + /// + /// + public virtual void PutFile(string localPath) { + using (FileStream fileStream = File.OpenRead(localPath)) { + PutFile(fileStream, ExtractPathItemName(localPath), FileAction.CreateNew); + } + } + + /// + /// Uploads stream data specified in the inputStream parameter to the remote FTP server. + /// + /// Any open stream object on the local client machine. + /// Filename or path and filename of the file stored on the remote FTP server. + /// The type of put action taken. + /// + /// The stream is uploaded to the current working directory on the remote server. The remotePath + /// parameter is used to specify the path and file name used to store the file on the remote server. + /// Note that some FTP servers will not accept a full path. On those systems you must navigate to + /// the directory you wish to put the file using with the ChangeDirectory() or ChangeDirectoryMultiPath() + /// method. + /// + /// + /// + /// + /// + /// + /// + /// + public virtual void PutFile(Stream inputStream, string remotePath, FileAction action) { + if (inputStream == null) + throw new ArgumentNullException("inputStream"); + + if (!inputStream.CanRead) + throw new ArgumentException("must be readable", "inputStream"); + + if (remotePath == null) + throw new ArgumentNullException("remotePath"); + + if (remotePath.Length == 0) + throw new ArgumentException("must contain a value", "remotePath"); + + if (action == FileAction.None) + throw new ArgumentOutOfRangeException("action", "must contain a value other than 'Unknown'"); + + WriteToLog(String.Format("Action='PutFile';Status='TransferBegin';RemotePath='{0}';FileAction='{1}'", remotePath, action.ToString())); + + try { + switch (action) { + case FileAction.CreateOrAppend: + base.TransferData(TransferDirection.ToServer, new FtpRequest(base.CharacterEncoding, FtpCmd.Appe, remotePath), inputStream); + break; + case FileAction.CreateNew: + if (Exists(remotePath)) { + throw new FtpException("Cannot overwrite existing file when action FileAction.CreateNew is specified."); + } + base.TransferData(TransferDirection.ToServer, new FtpRequest(base.CharacterEncoding, FtpCmd.Stor, remotePath), inputStream); + break; + case FileAction.Create: + base.TransferData(TransferDirection.ToServer, new FtpRequest(base.CharacterEncoding, FtpCmd.Stor, remotePath), inputStream); + break; + case FileAction.Resume: + // get the size of the file already on the server (in bytes) + long remoteSize = GetFileSize(remotePath); + + // if the files are the same size then there is nothing to transfer + if (remoteSize == inputStream.Length) + return; + + // transfer file to the server + base.TransferData(TransferDirection.ToServer, new FtpRequest(base.CharacterEncoding, FtpCmd.Stor, remotePath), inputStream, remoteSize); + break; + case FileAction.ResumeOrCreate: + if (Exists(remotePath)) + PutFile(inputStream, remotePath, FileAction.Resume); + else + PutFile(inputStream, remotePath, FileAction.Create); + break; + } + } catch (FtpException fex) { + WriteToLog(String.Format("Action='PutFile';Status='TransferError';RemotePath='{0}';FileAction='{1}';ErrorMessage='{2}'", remotePath, action.ToString(), fex.Message)); + throw new FtpDataTransferException(String.Format("An error occurred while putting fileName '{0}'.", remotePath), base.LastResponse, fex); + } + + WriteToLog(String.Format("Action='PutFile';Status='TransferSuccess';RemotePath='{0}';FileAction='{1}'", remotePath, action.ToString())); + } + + /// + /// File Exchange Protocol (FXP) allows server-to-server transfer which can greatly speed up file transfers. + /// + /// The name of the file to transfer. + /// The destination FTP server which must be supplied as an open and connected FtpClient object. + /// + /// Both servers must support and have FXP enabled before you can transfer files between two remote servers using FXP. One FTP server must support PASV mode and the other server must allow PORT commands from a foreign address. Finally, firewall settings may interfer with the ability of one server to access the other server. + /// Starksoft FtpClient will coordinate the FTP negoitaion and necessary PORT and PASV transfer commands. + /// + /// + /// + public virtual void FxpCopy(string fileName, FtpClient destination) { + + if (this.IsConnected == false) + throw new FtpException("The connection must be open before a transfer between servers can be intitiated."); + + if (destination == null) + throw new ArgumentNullException("destination"); + + if (destination.IsConnected == false) + throw new FtpException("The destination object must be open and connected before a transfer between servers can be intitiated."); + + if (fileName == null) + throw new ArgumentNullException("fileName"); + + if (fileName.Length == 0) + throw new ArgumentException("must have a value", "fileName"); + + + // send command to destination FTP server to get passive port to be used from the source FTP server + try { + destination.SendRequest(new FtpRequest(base.CharacterEncoding, FtpCmd.Pasv)); + } catch (FtpException fex) { + throw new FtpException(String.Format("An error occurred when trying to set up the passive connection on '{1}' for a destination to destination copy between '{0}' and '{1}'.", this.Host, destination.Host), base.LastResponse, fex); + } + + // get the begin and end positions to extract data from the response string + int startIdx = destination.LastResponse.Text.IndexOf("(") + 1; + int endIdx = destination.LastResponse.Text.IndexOf(")"); + string dataPortInfo = destination.LastResponse.Text.Substring(startIdx, endIdx - startIdx); + + // send a command to the source server instructing it to connect to + // the local ip address and port that the destination server will be bound to + try { + this.SendRequest(new FtpRequest(base.CharacterEncoding, FtpCmd.Port, dataPortInfo)); + } catch (FtpException fex) { + throw new FtpException(String.Format("Command instructing '{0}' to open connection failed.", this.Host), base.LastResponse, fex); + } + + // send command to tell the source server to retrieve the file from the destination server + try { + this.SendRequest(new FtpRequest(base.CharacterEncoding, FtpCmd.Retr, fileName)); + } catch (FtpException fex) { + throw new FtpException(String.Format("An error occurred transfering on a server to server copy between '{0}' and '{1}'.", this.Host, destination.Host), base.LastResponse, fex); + } + + // send command to tell the destination to store the file + try { + destination.SendRequest(new FtpRequest(base.CharacterEncoding, FtpCmd.Stor, fileName)); + } catch (FtpException fex) { + throw new FtpException(String.Format("An error occurred transfering on a server to server copy between '{0}' and '{1}'.", this.Host, destination.Host), base.LastResponse, fex); + } + + // wait until we get a file completed response back from the destination server and the source server + destination.WaitForHappyCodes(this.FxpTransferTimeout, FtpResponseCode.RequestedFileActionOkayAndCompleted, FtpResponseCode.ClosingDataConnection); + this.WaitForHappyCodes(this.FxpTransferTimeout, FtpResponseCode.RequestedFileActionOkayAndCompleted, FtpResponseCode.ClosingDataConnection); + } + + #endregion + + #region Private Methods + + private void ParseDirListDeep(string path, FtpItemCollection deepCol) { + FtpItemCollection itemCol = GetDirList(path); + deepCol.Merge(itemCol); + + foreach (FtpItem item in itemCol) { + // if the this call is being completed asynchronously and the user requests a cancellation + // then stop processing the items and return + if (base.AsyncWorker != null && base.AsyncWorker.CancellationPending) + return; + + // if the item is of type Directory then parse the directory list recursively + if (item.ItemType == FtpItemType.Directory) + ParseDirListDeep(item.FullPath, deepCol); + } + } + + private string CorrectRemotePath(string path) { + if (path.StartsWith("/")) return RootDirectory + path; + else return path; + } + + private string CorrectLocalPath(string path) { + if (path == null) + throw new ArgumentNullException("path"); + + if (path.Length == 0) + throw new ArgumentException("must have a value", "path"); + + string fileName = ExtractPathItemName(path); + string pathOnly = path.Substring(0, path.Length - fileName.Length - 1); + + // if the pathOnly portion contains the root node then we need to add the + // a directory slash back otherwise the final combined path will be something + // like c:myfile.txt and this will result + if (pathOnly.EndsWith(":") && pathOnly.IndexOf("\\") == -1) { + pathOnly += "\\"; + } + + char[] invalidPath = Path.GetInvalidPathChars(); + if (path.IndexOfAny(invalidPath) != -1) { + for (int i = 0; i < invalidPath.Length; i++) { + if (pathOnly.IndexOf(invalidPath[i]) != -1) + pathOnly = pathOnly.Replace(invalidPath[i], '_'); + } + } + + char[] invalidFile = Path.GetInvalidFileNameChars(); + if (fileName.IndexOfAny(invalidFile) != -1) { + for (int i = 0; i < invalidFile.Length; i++) { + if (fileName.IndexOf(invalidFile[i]) != -1) + fileName = fileName.Replace(invalidFile[i], '_'); + } + } + + return Path.Combine(pathOnly, fileName); + + } + + private void SetFileTransferType() { + switch (_fileTransferType) { + case TransferType.Binary: + base.SendRequest(new FtpRequest(base.CharacterEncoding, FtpCmd.Type, "I")); + break; + case TransferType.Ascii: + base.SendRequest(new FtpRequest(base.CharacterEncoding, FtpCmd.Type, "A")); + break; + } + } + + + + private string ExtractPathItemName(string path) { + if (path.IndexOf("\\") != -1) + return path.Substring(path.LastIndexOf("\\") + 1); + else if (path.IndexOf("/") != -1) + return path.Substring(path.LastIndexOf("/") + 1); + else if (path.Length > 0) + return path; + else + throw new FtpException(String.Format(CultureInfo.InvariantCulture, "Item name not found in path {0}.", path)); + } + + + private void WriteToLog(string message) { + if (!_isLoggingOn) + return; + + string line0 = String.Format("[{0}] [{1}] [{2}] {3}", DateTime.Now.ToString("G"), base.Host, base.Port.ToString(), message); + string line = line0 + "\r\n"; + if (_log != null) { + byte[] buffer = base.CharacterEncoding.GetBytes(line); + _log.Write(buffer, 0, buffer.Length); + } + Job.Log.Text(line0); + } + + #endregion + + #region Asynchronous Methods and Events + + private Exception _asyncException; + + //////////////////////////////////////////////////////////////////////////////////// + + /// + /// Event handler for GetDirListAsync method. + /// + /// + /// + public event EventHandler GetDirListAsyncCompleted; + + /// + /// Asynchronously retrieves a list of the files from current working directory on the remote FTP + /// server using the LIST command. + /// + /// + /// This method returns a FtpItemList collection of FtpItem objects through the GetDirListAsyncCompleted event. + /// + /// + /// + /// + /// + /// + /// + /// + public void GetDirListAsync() { + GetDirListAsync(string.Empty); + } + + /// + /// Asynchronously retrieves a list of the files from a specified path on the remote FTP + /// server using the LIST command. + /// + /// The path to a directory on the remote FTP server. + /// This method returns a FtpFileCollection object containing a collection of + /// FtpItem objects. The FtpFileCollection is returned though the GetDirListAsyncCompleted event. + /// + /// + /// + /// + /// + /// + public void GetDirListAsync(string path) { + if (base.AsyncWorker != null && base.AsyncWorker.IsBusy) + throw new InvalidOperationException("The FtpClient object is already busy executing another asynchronous operation. You can only execute one asychronous method at a time."); + + base.CreateAsyncWorker(); + base.AsyncWorker.WorkerSupportsCancellation = true; + base.AsyncWorker.DoWork += new DoWorkEventHandler(GetDirListAsync_DoWork); + base.AsyncWorker.RunWorkerCompleted += new RunWorkerCompletedEventHandler(GetDirListAsync_RunWorkerCompleted); + base.AsyncWorker.RunWorkerAsync(path); + } + + private void GetDirListAsync_DoWork(object sender, DoWorkEventArgs e) { + try { + string path = (string)e.Argument; + e.Result = GetDirList(path); + } catch (Exception ex) { + _asyncException = ex; + } + } + + private void GetDirListAsync_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e) { + if (GetDirListAsyncCompleted != null) + GetDirListAsyncCompleted(this, new GetDirListAsyncCompletedEventArgs(_asyncException, base.IsAsyncCanceled, (FtpItemCollection)e.Result)); + _asyncException = null; + } + + //////////////////////////////////////////////////////////////////////////////////// + + /// + /// Event handler for GetDirListDeepAsync method. + /// + public event EventHandler GetDirListDeepAsyncCompleted; + + /// + /// Asynchronous deep retrieval of a list of all files and all sub directories from the current path on the remote FTP + /// server using the LIST command. + /// + /// This method returns a FtpFileCollection object containing a collection of FtpItem objects through the GetDirListDeepAsyncCompleted event. + /// + /// + /// + /// + /// + /// + /// + public void GetDirListDeepAsync() { + GetDirListDeepAsync(GetWorkingDirectory()); + } + + /// + /// Asynchronous deep retrieval of a list of all files and all sub directories from a specified path on the remote FTP + /// server using the LIST command. + /// + /// The path to a directory on the remote FTP server. + /// This method returns a FtpFileCollection object containing a collection of + /// FtpItem objects the GetDirListDeepAsyncCompleted event. + /// + /// + /// + /// + /// + /// + public void GetDirListDeepAsync(string path) { + if (base.AsyncWorker != null && base.AsyncWorker.IsBusy) + throw new InvalidOperationException("The FtpClient object is already busy executing another asynchronous operation. You can only execute one asychronous method at a time."); + + base.CreateAsyncWorker(); + base.AsyncWorker.WorkerSupportsCancellation = true; + base.AsyncWorker.DoWork += new DoWorkEventHandler(GetDirListDeepAsync_DoWork); + base.AsyncWorker.RunWorkerCompleted += new RunWorkerCompletedEventHandler(GetDirListDeepAsync_RunWorkerCompleted); + base.AsyncWorker.RunWorkerAsync(path); + } + + private void GetDirListDeepAsync_DoWork(object sender, DoWorkEventArgs e) { + try { + string path = (string)e.Argument; + e.Result = GetDirList(path); + } catch (Exception ex) { + _asyncException = ex; + } + } + + private void GetDirListDeepAsync_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e) { + if (GetDirListDeepAsyncCompleted != null) + GetDirListDeepAsyncCompleted(this, new GetDirListDeepAsyncCompletedEventArgs(_asyncException, base.IsAsyncCanceled, (FtpItemCollection)e.Result)); + _asyncException = null; + } + + + //////////////////////////////////////////////////////////////////////////////////// + + /// + /// Event that fires when the GetFileAsync method is invoked. + /// + public event EventHandler GetFileAsyncCompleted; + + /// + /// Asynchronously retrieves a remote file from the FTP server and writes the data to a local file + /// specfied in the localPath parameter. + /// + /// A path and/or file name to the remote file. + /// A fully qualified local path to a file on the local machine. + /// The type of action to take. + /// + /// + /// + /// + /// + /// + /// + /// + public void GetFileAsync(string remotePath, string localPath, FileAction action) { + if (base.AsyncWorker != null && base.AsyncWorker.IsBusy) + throw new InvalidOperationException("The FtpClient object is already busy executing another asynchronous operation. You can only execute one asychronous method at a time."); + + base.CreateAsyncWorker(); + base.AsyncWorker.WorkerSupportsCancellation = true; + base.AsyncWorker.DoWork += new DoWorkEventHandler(GetFileAsync_DoWork); + base.AsyncWorker.RunWorkerCompleted += new RunWorkerCompletedEventHandler(GetFileAsync_RunWorkerCompleted); + Object[] args = new Object[3]; + args[0] = remotePath; + args[1] = localPath; + args[2] = action; + base.AsyncWorker.RunWorkerAsync(args); + } + + private void GetFileAsync_DoWork(object sender, DoWorkEventArgs e) { + try { + Object[] args = (Object[])e.Argument; + GetFile((string)args[0], (string)args[1], (FileAction)args[2]); + } catch (Exception ex) { + _asyncException = ex; + } + + } + + private void GetFileAsync_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e) { + if (GetFileAsyncCompleted != null) + GetFileAsyncCompleted(this, new GetFileAsyncCompletedEventArgs(_asyncException, base.IsAsyncCanceled)); + _asyncException = null; + } + + /// + /// Asynchronously retrieves a remote file from the FTP server and writes the data to a local stream object + /// specfied in the outStream parameter. + /// + /// A path and/or file name to the remote file. + /// An output stream object used to stream the remote file to the local machine. + /// A true/false value to indicate if the file download needs to be restarted due to a previous partial download. + /// + /// If the remote path is a file name then the file is downloaded from the FTP server current working directory. Otherwise a fully qualified + /// path for the remote file may be specified. The output stream must be writeable and can be any stream object. Finally, the restart parameter + /// is used to send a restart command to the FTP server. The FTP server is instructed to restart the download process at the last position of + /// of the output stream. Not all FTP servers support the restart command. If the FTP server does not support the restart (REST) command, + /// an FtpException error is thrown. + /// + /// + /// + /// + /// + /// + /// + /// + /// + public void GetFileAsync(string remotePath, Stream outStream, bool restart) { + if (base.AsyncWorker != null && base.AsyncWorker.IsBusy) + throw new InvalidOperationException("The FtpClient object is already busy executing another asynchronous operation. You can only execute one asychronous method at a time."); + + base.CreateAsyncWorker(); + base.AsyncWorker.WorkerSupportsCancellation = true; + base.AsyncWorker.DoWork += new DoWorkEventHandler(GetFileStreamAsync_DoWork); + base.AsyncWorker.RunWorkerCompleted += new RunWorkerCompletedEventHandler(GetFileAsync_RunWorkerCompleted); + Object[] args = new Object[3]; + args[0] = remotePath; + args[1] = outStream; + args[2] = restart; + base.AsyncWorker.RunWorkerAsync(args); + } + + private void GetFileStreamAsync_DoWork(object sender, DoWorkEventArgs e) { + try { + Object[] args = (Object[])e.Argument; + GetFile((string)args[0], (Stream)args[1], (bool)args[2]); + } catch (Exception ex) { + _asyncException = ex; + } + } + + //////////////////////////////////////////////////////////////////////////////////// + + /// + /// Asynchronous event for PutFileAsync method. + /// + /// + public event EventHandler PutFileAsyncCompleted; + + /// + /// Asynchronously uploads a local file specified in the path parameter to the remote FTP server. + /// + /// Path to a file on the local machine. + /// Filename or full path to file on the remote FTP server. + /// The type of put action taken. + /// + /// The file is uploaded to the current working directory on the remote server. The remotePath + /// parameter is used to specify the path and file name used to store the file on the remote server. + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + public void PutFileAsync(string localPath, string remotePath, FileAction action) { + if (base.AsyncWorker != null && base.AsyncWorker.IsBusy) + throw new InvalidOperationException("The FtpClient object is already busy executing another asynchronous operation. You can only execute one asychronous method at a time."); + + base.CreateAsyncWorker(); + base.AsyncWorker.WorkerSupportsCancellation = true; + base.AsyncWorker.DoWork += new DoWorkEventHandler(PutFileAsync_DoWork); + base.AsyncWorker.RunWorkerCompleted += new RunWorkerCompletedEventHandler(PutFileAsync_RunWorkerCompleted); + Object[] args = new Object[3]; + args[0] = localPath; + args[1] = remotePath; + args[2] = action; + base.AsyncWorker.RunWorkerAsync(args); + } + + private void PutFileAsync_DoWork(object sender, DoWorkEventArgs e) { + try { + Object[] args = (Object[])e.Argument; + PutFile((string)args[0], (string)args[1], (FileAction)args[2]); + } catch (Exception ex) { + _asyncException = ex; + } + } + + private void PutFileAsync_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e) { + if (PutFileAsyncCompleted != null) + PutFileAsyncCompleted(this, new PutFileAsyncCompletedEventArgs(_asyncException, base.IsAsyncCanceled)); + _asyncException = null; + } + + /// + /// Asynchronously uploads stream data specified in the inputStream parameter to the remote FTP server. + /// + /// Any open stream object on the local client machine. + /// Filename or path and filename of the file stored on the remote FTP server. + /// The type of put action taken. + /// + /// The stream is uploaded to the current working directory on the remote server. The remotePath + /// parameter is used to specify the path and file name used to store the file on the remote server. + /// + /// + /// + /// + /// + /// + /// + /// + /// + public void PutFileAsync(Stream inputStream, string remotePath, FileAction action) { + if (base.AsyncWorker != null && base.AsyncWorker.IsBusy) + throw new InvalidOperationException("The FtpClient object is already busy executing another asynchronous operation. You can only execute one asychronous method at a time."); + + base.CreateAsyncWorker(); + base.AsyncWorker.WorkerSupportsCancellation = true; + base.AsyncWorker.DoWork += new DoWorkEventHandler(PutFileStreamAsync_DoWork); + base.AsyncWorker.RunWorkerCompleted += new RunWorkerCompletedEventHandler(PutFileAsync_RunWorkerCompleted); + Object[] args = new Object[3]; + args[0] = inputStream; + args[1] = remotePath; + args[2] = action; + base.AsyncWorker.RunWorkerAsync(args); + } + + private void PutFileStreamAsync_DoWork(object sender, DoWorkEventArgs e) { + try { + Object[] args = (Object[])e.Argument; + PutFile((Stream)args[0], (string)args[1], (FileAction)args[2]); + } catch (Exception ex) { + _asyncException = ex; + } + } + + /// + /// Asynchronously uploads a local file specified in the path parameter to the remote FTP server. + /// + /// Path to a file on the local machine. + /// The type of put action taken. + /// + /// The file is uploaded to the current working directory on the remote server. + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + public void PutFileAsync(string localPath, FileAction action) { + if (base.AsyncWorker != null && base.AsyncWorker.IsBusy) + throw new InvalidOperationException("The FtpClient object is already busy executing another asynchronous operation. You can only execute one asychronous method at a time."); + + base.CreateAsyncWorker(); + base.AsyncWorker.WorkerSupportsCancellation = true; + base.AsyncWorker.DoWork += new DoWorkEventHandler(PutFileLocalAsync_DoWork); + base.AsyncWorker.RunWorkerCompleted += new RunWorkerCompletedEventHandler(PutFileAsync_RunWorkerCompleted); + Object[] args = new Object[2]; + args[0] = localPath; + args[1] = action; + base.AsyncWorker.RunWorkerAsync(args); + } + + private void PutFileLocalAsync_DoWork(object sender, DoWorkEventArgs e) { + try { + Object[] args = (Object[])e.Argument; + PutFile((string)args[0], (FileAction)args[1]); + } catch (Exception ex) { + _asyncException = ex; + } + } + + //////////////////////////////////////////////////////////////////////////////////// + + /// + /// Event handler for OpenAsync method. + /// + public event EventHandler OpenAsyncCompleted; + + /// + /// Asynchronously opens a connection to the remote FTP server and log in with user name and password credentials. + /// + /// User name. Many public ftp allow for this value to 'anonymous'. + /// Password. Anonymous public ftp servers generally require a valid email address for a password. + /// Use the Close() method to log off and close the connection to the FTP server. + /// + /// + /// + /// + /// + public void OpenAsync(string user, string password) { + if (base.AsyncWorker != null && base.AsyncWorker.IsBusy) + throw new InvalidOperationException("The FtpClient object is already busy executing another asynchronous operation. You can only execute one asychronous method at a time."); + + base.CreateAsyncWorker(); + base.AsyncWorker.WorkerSupportsCancellation = true; + base.AsyncWorker.DoWork += new DoWorkEventHandler(OpenAsync_DoWork); + base.AsyncWorker.RunWorkerCompleted += new RunWorkerCompletedEventHandler(OpenAsync_RunWorkerCompleted); + Object[] args = new Object[2]; + args[0] = user; + args[1] = password; + base.AsyncWorker.RunWorkerAsync(args); + } + + private void OpenAsync_DoWork(object sender, DoWorkEventArgs e) { + try { + Object[] args = (Object[])e.Argument; + Open((string)args[0], (string)args[1]); + } catch (Exception ex) { + _asyncException = ex; + } + } + + private void OpenAsync_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e) { + if (OpenAsyncCompleted != null) + OpenAsyncCompleted(this, new OpenAsyncCompletedEventArgs(_asyncException, base.IsAsyncCanceled)); + _asyncException = null; + } + + + //////////////////////////////////////////////////////////////////////////////////// + + /// + /// Asynchronous event for FxpCopyAsync method. + /// + public event EventHandler FxpCopyAsyncCompleted; + + /// + /// Asynchronous File Exchange Protocol (FXP) allows server-to-server transfer which can greatly speed up file transfers. + /// + /// The name of the file to transfer. + /// The destination FTP server which must be supplied as an open and connected FtpClient object. + /// + /// Both servers must support and have FXP enabled before you can transfer files between two remote servers using FXP. One FTP server must support PASV mode and the other server must allow PORT commands from a foreign address. Finally, firewall settings may interfer with the ability of one server to access the other server. + /// Starksoft FtpClient will coordinate the FTP negoitaion and necessary PORT and PASV transfer commands. + /// + /// + /// + /// + /// + public void FxpCopyAsync(string fileName, FtpClient destination) { + if (base.AsyncWorker != null && base.AsyncWorker.IsBusy) + throw new InvalidOperationException("The FtpClient object is already busy executing another asynchronous operation. You can only execute one asychronous method at a time."); + + base.CreateAsyncWorker(); + base.AsyncWorker.WorkerSupportsCancellation = true; + base.AsyncWorker.DoWork += new DoWorkEventHandler(FxpCopyAsync_DoWork); + base.AsyncWorker.RunWorkerCompleted += new RunWorkerCompletedEventHandler(FxpCopyAsync_RunWorkerCompleted); + Object[] args = new Object[2]; + args[0] = fileName; + args[1] = destination; + base.AsyncWorker.RunWorkerAsync(args); + } + + private void FxpCopyAsync_DoWork(object sender, DoWorkEventArgs e) { + try { + Object[] args = (Object[])e.Argument; + FxpCopy((string)args[0], (FtpClient)args[1]); + } catch (Exception ex) { + _asyncException = ex; + } + } + + private void FxpCopyAsync_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e) { + if (FxpCopyAsyncCompleted != null) + FxpCopyAsyncCompleted(this, new FxpCopyAsyncCompletedEventArgs(_asyncException, base.IsAsyncCanceled)); + _asyncException = null; + } + + #endregion + + #region Destructors + + /// + /// Disposes all FtpClient objects and connections. + /// + new protected virtual void Dispose() { + Dispose(true); + GC.SuppressFinalize(this); + } + + /// + /// Dispose Method. + /// + /// + override protected void Dispose(bool disposing) { + if (disposing) { + // close any managed objects + } + + base.Dispose(disposing); + } + + /// + /// Dispose deconstructor. + /// + ~FtpClient() { + Dispose(false); + } + + #endregion + } +} + + + + diff --git a/Source/MSBuild.Community.Tasks/Sync/FTP/FtpGenericParser.cs b/Source/MSBuild.Community.Tasks/Sync/FTP/FtpGenericParser.cs new file mode 100644 index 00000000..b15ee619 --- /dev/null +++ b/Source/MSBuild.Community.Tasks/Sync/FTP/FtpGenericParser.cs @@ -0,0 +1,222 @@ +/* + * Authors: Benton Stark + * + * Copyright (c) 2007-2009 Starksoft, LLC (http://www.starksoft.com) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + */ + +using System; +using System.Text.RegularExpressions; +using System.Globalization; +using System.Text; + +namespace Starksoft.Net.Ftp +{ + /// + /// Generic ftp file and directory listing parser that supports most Unix, Dos, and Windows style FTP + /// directory listings. A custom parser can be created using the IFtpItemParser interface in the event + /// this parser does not suit the needs of a specific FTP server directory format listing. + /// + public class FtpGenericParser : IFtpItemParser + { + // unix regex expressions + Regex _isUnix = new Regex(@"(d|l|-|b|c|p|s)(r|w|x|-|t|s){9}", RegexOptions.Compiled); + Regex _unixAttribs = new Regex(@"(d|l|-|b|c|p|s)(r|w|x|-|t|s){9}", RegexOptions.Compiled); + Regex _unixMonth = new Regex(@"(Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec|mrt|mei|okt)", RegexOptions.Compiled | RegexOptions.IgnoreCase); + Regex _unixDay = new Regex(@"(?<=(Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec|mrt|mei|okt)\s+)\d+", RegexOptions.Compiled | RegexOptions.IgnoreCase); + Regex _unixYear = new Regex(@"(?<=(Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec|mrt|mei|okt)\s+\d+\s+)(19|20)\d\d", RegexOptions.Compiled | RegexOptions.IgnoreCase); + Regex _unixTime = new Regex(@"(?<=(Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec|mrt|mei|okt)\s+\d+\s+)\d+:\d\d", RegexOptions.Compiled | RegexOptions.IgnoreCase); + Regex _unixSize = new Regex(@"\d+(?=(\s+(Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec|mrt|mei|okt)))", RegexOptions.Compiled | RegexOptions.IgnoreCase); + Regex _unixName = new Regex(@"((?<=((Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec|mrt|mei|okt)\s+\d+\s+(19|20)\d\d\s+)|((Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec|mrt|mei|okt)\s+\d+\s+\d+:\d\d\s+)).+)", RegexOptions.Compiled | RegexOptions.IgnoreCase); + Regex _unixSymbLink = new Regex(@"(?<=\s+->\s+).+", RegexOptions.Compiled); + Regex _unixType = new Regex(@"(d|l|-|b|c|p|s)(?=(r|w|x|-|t|s){9})", RegexOptions.Compiled); + + // dos and other expressions + Regex _dosName = new System.Text.RegularExpressions.Regex(@"((?<=\s+).+)|((?<=\d\d:\d\d\s+).+)|((?<=(\d\d:\d\d(AM|PM|am|pm)\s+\d+\s+)).+)", RegexOptions.Compiled); + Regex _dosDate = new System.Text.RegularExpressions.Regex(@"(\d\d-\d\d-\d\d)", RegexOptions.Compiled); + Regex _dosTime = new System.Text.RegularExpressions.Regex(@"(\d\d:\d\d\s*(AM|PM|am|pm))|(\d\d:\d\d)", RegexOptions.Compiled); + Regex _dosSize = new System.Text.RegularExpressions.Regex(@"((?<=(\d\d:\d\d\s*(AM|PM|am|pm)\s*))\d+)|(\d+(?=\s+\d\d-\d\d-\d\d\s+))", RegexOptions.Compiled); + Regex _dosDir = new System.Text.RegularExpressions.Regex(@"|\sDIR\s", RegexOptions.Compiled); + + + /// + /// Method to parse a line of file listing data from the FTP server. + /// + /// Line to parse. + /// Object representing data in parsed file listing line. + public FtpItem ParseLine(string line) + { + if (_isUnix.IsMatch(line)) + return ParseUnixFormat(line); + else + return ParseDosFormat(line); + } + + private FtpItem ParseUnixFormat(string line) + { + //System.Diagnostics.Debug.Assert(! (line == "-r-xr-xr-x 1 root root 21969 Apr 8 1986 rfc982.txt")); + + + string attribs = _unixAttribs.Match(line).ToString(); + string month = _unixMonth.Match(line).ToString(); + string day = _unixDay.Match(line).ToString(); + string year = _unixYear.Match(line).ToString(); + string time = _unixTime.Match(line).ToString(); + string size = _unixSize.Match(line).ToString(); + string name = _unixName.Match(line).ToString().Trim(); + string symbLink = ""; + + // ignore the microsoft 'etc' file that IIS uses for WWW users + if (name == "~ftpsvc~.ckm") + return null; + + // if we find a symbolic link then extract the symbolic link first and then + // extract the file name portion + if (_unixSymbLink.IsMatch(name)) + { + symbLink = _unixSymbLink.Match(name).ToString(); + name = name.Substring(0, name.IndexOf("->")).Trim(); + } + + string itemType = _unixType.Match(line).ToString(); + + + // if the current year is not given in unix then we need to figure it out. + // basically, if a date is within the past 6 months unix will show the + // time instead of the year + if (year.Length == 0) + { + int curMonth = DateTime.Today.Month; + int curYear = DateTime.Today.Year; + + DateTime result; + if (DateTime.TryParse(String.Format(CultureInfo.InvariantCulture, "1-{0}-2007", month), out result)) + { + if ((curMonth - result.Month) < 0) + year = Convert.ToString(curYear - 1, CultureInfo.InvariantCulture); + else + year = curYear.ToString(CultureInfo.InvariantCulture); + } + } + + DateTime dateObj; + DateTime.TryParse(String.Format(CultureInfo.InvariantCulture, "{0}-{1}-{2} {3}", day, month, year, time), out dateObj); + + long sizeLng = 0; + Int64.TryParse(size, out sizeLng); + + FtpItemType itemTypeObj = FtpItemType.Unknown; + switch (itemType.ToLower(CultureInfo.InvariantCulture)) + { + case "l": + itemTypeObj = FtpItemType.SymbolicLink; + break; + case "d": + itemTypeObj = FtpItemType.Directory; + break; + case "-": + itemTypeObj = FtpItemType.File; + break; + case "b": + itemTypeObj = FtpItemType.BlockSpecialFile; + break; + case "c": + itemTypeObj = FtpItemType.CharacterSpecialFile; + break; + case "p": + itemTypeObj = FtpItemType.NamedSocket; + break; + case "s": + itemTypeObj = FtpItemType.DomainSocket; + break; + } + + if (itemTypeObj == FtpItemType.Unknown || name.Trim().Length == 0) + return null; + else + return new FtpItem(name, dateObj, sizeLng, symbLink, attribs, itemTypeObj, line); + } + + private FtpItem ParseDosFormat(string line) + { + string name = _dosName.Match(line).ToString().Trim(); + + // if the name has no length the simply stop processing and return null. + if (name.Trim().Length == 0) + return null; + + string date = _dosDate.Match(line).ToString(); + string time = _dosTime.Match(line).ToString(); + string size = _dosSize.Match(line).ToString(); + string dir = _dosDir.Match(line).ToString().Trim(); + + // put togther the date/time + DateTime dateTime = DateTime.MinValue; + DateTime.TryParse(String.Format("{0} {1}", date.Replace('-', '/'), time), CultureInfo.InvariantCulture, DateTimeStyles.AssumeLocal, out dateTime); + + // parse the file size + long sizeLng = 0; + Int64.TryParse(size, out sizeLng); + + // determine the file item itemType + FtpItemType itemTypeObj; + if (dir.Length > 0) + itemTypeObj = FtpItemType.Directory; + else + itemTypeObj = FtpItemType.File; + + return new FtpItem(name, dateTime, sizeLng, String.Empty, String.Empty, itemTypeObj, line); + } + + private string GetLocalMonthAbrevList(string culture) + { + StringBuilder sb = new StringBuilder(); + string[] engMonths = { "jan", "feb", "mar", "apr", "may", "jun", "jul", "aug", "sep", "oct", "nov", "dec" }; + + //CultureInfo current = CultureInfo.CurrentCulture; + CultureInfo current = CultureInfo.GetCultureInfo(culture); + + string[] months = current.DateTimeFormat.AbbreviatedMonthNames; + + for (int i = 0; i < 12; i++) + { + if (String.Compare(months[i], engMonths[i], StringComparison.InvariantCultureIgnoreCase) != 0) + { + sb.Append(months[i]); + sb.Append("|"); + } + } + + string list = sb.ToString(); + if (list.Length < 0) + list = list.Remove(list.Length - 1, 1); + + return list; + } + + + + + } +} + + + diff --git a/Source/MSBuild.Community.Tasks/Sync/FTP/FtpItem.cs b/Source/MSBuild.Community.Tasks/Sync/FTP/FtpItem.cs new file mode 100644 index 00000000..0a07f60d --- /dev/null +++ b/Source/MSBuild.Community.Tasks/Sync/FTP/FtpItem.cs @@ -0,0 +1,206 @@ +/* + * Authors: Benton Stark + * + * Copyright (c) 2007-2009 Starksoft, LLC (http://www.starksoft.com) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + */ + +using System; +using System.IO; +using System.Text; + +namespace Starksoft.Net.Ftp +{ + /// + /// The itemType of item as reported by the FTP server. + /// + /// + /// Data transmitted from the FTP server after a directory list operation is usually a item itemType of Directory or File. Unix + /// systems also support additional directory item types such as symbolic links and named sockets. Not all FTP servers will report + /// enough information to determine the file itemType. In such cases a file itemType of Unknown is specified. + /// + public enum FtpItemType + { + /// + /// Directory item. + /// + Directory, + /// + /// File item. + /// + File, + /// + /// Symbolic link item. + /// + SymbolicLink, + /// + /// Block special file item. + /// + BlockSpecialFile, + /// + /// Character special file item. + /// + CharacterSpecialFile, + /// + /// Name socket item. + /// + NamedSocket, + /// + /// Domain socket item. + /// + DomainSocket, + /// + /// Unknown item. The system was unable to determine the itemType of item. + /// + Unknown + } + + + /// + /// The FtpItem class represents the file and directory listing items as reported by the FTP server. + /// + /// + /// Usually items are of types Files and Directories although + /// the Unix based FTP servers may report additional information such as permissions and symbolic link information. The FtpItem class supports + /// the most commom versions of Unix, Windows, DOS, and Machintosh Ftp Servers. There is no FTP standard concerning how an FTP server should + /// list file item data. Therefore, the FtpClient object supports a pluggable ftp item parser that you can write to support more exotic + /// ftp item listing formats. + /// + public class FtpItem + { + private string _name; + private DateTime _modified; + private long _size; + private string _symbolicLink; + private FtpItemType _itemType; + private string _attributes; + private string _rawText; + private string _parentPath; + + /// + /// Constructor to create a new ftp item. + /// + /// Name of the item. + /// Modified date and/or time of the item. + /// Number of bytes or size of the item. + /// Symbolic link name. + /// Permission text for item. + /// Type of the item. + /// The raw text of the item. + public FtpItem(string name, DateTime modified, long size, string symbolicLink, string attributes, FtpItemType itemType, string rawText) + { + _name = name; + _modified = modified; + _size = size; + _symbolicLink = symbolicLink; + _attributes = attributes; + _itemType = itemType; + _rawText = rawText; + } + + /// + /// Item name. All FTP servers should report a name value for the FTP item. + /// + public string Name + { + get { return _name; } + } + + /// + /// Permissions text for the item. Many FTP servers will report file permission information. + /// + public string Attributes + { + get { return _attributes; } + } + + /// + /// Modified date and possibly time in the client's local time for the ftp item. + /// + public DateTime Modified + { + get { return _modified ; } + } + + /// + /// Modified date and possibly time in UTC time for the ftp item. + /// + public DateTime ModifiedUtc { + get { return _modified.ToUniversalTime(); } + } + + /// + /// The size of the ftp item as reported by the FTP server. + /// + public long Size + { + get { return _size; } + } + + /// + /// The symbolic link name if the item is of itemType symbolic link. + /// + public string SymbolicLink + { + get { return _symbolicLink; } + } + + /// + /// The itemType of the ftp item. + /// + public FtpItemType ItemType + { + get { return _itemType; } + } + + /// + /// The raw textual line information as reported by the FTP server. This can be useful for examining exotic FTP formats and for debugging + /// a custom ftp item parser. + /// + public string RawText + { + get { return _rawText; } + } + + /// + /// Path to the parent directory. + /// + public string ParentPath + { + get { return _parentPath; } + set { _parentPath = value; } + } + + /// + /// Item full path. + /// + public string FullPath + { + get { return _parentPath == "/" || _parentPath == "//" ? String.Format("{0}{1}", _parentPath, _name) : String.Format("{0}/{1}", _parentPath, _name); } + } + + public void SetTimeOffset(int offset) { + if (_modified.Kind == DateTimeKind.Local || _modified.Kind == DateTimeKind.Unspecified) { + _modified = DateTime.SpecifyKind(_modified.AddHours(-offset), DateTimeKind.Utc).ToLocalTime(); + } + } + } +} \ No newline at end of file diff --git a/Source/MSBuild.Community.Tasks/Sync/FTP/FtpItemCollection.cs b/Source/MSBuild.Community.Tasks/Sync/FTP/FtpItemCollection.cs new file mode 100644 index 00000000..36ffe729 --- /dev/null +++ b/Source/MSBuild.Community.Tasks/Sync/FTP/FtpItemCollection.cs @@ -0,0 +1,232 @@ +/* + * Authors: Benton Stark + * + * Copyright (c) 2007-2009 Starksoft, LLC (http://www.starksoft.com) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + */ + +using System; +using System.Collections.ObjectModel; +using System.Data; +using System.Collections; +using System.Collections.Generic; +using System.ComponentModel; +using System.Globalization; + +namespace Starksoft.Net.Ftp +{ + /// + /// Ftp item list. + /// + public class FtpItemCollection : IEnumerable + { + private List _list = new List(); + + private long _totalSize; + + private static string COL_NAME = "Name"; + private static string COL_MODIFIED = "Modified"; + private static string COL_SIZE = "Size"; + private static string COL_SYMBOLIC_LINK = "SymbolicLink"; + private static string COL_TYPE = "Type"; + private static string COL_ATTRIBUTES = "Attributes"; + private static string COL_RAW_TEXT = "RawText"; + + /// + /// Default constructor for FtpItemCollection. + /// + public FtpItemCollection() + { } + + /// + /// Split a multi-line file list text response and add the parsed items to the collection. + /// + /// Path to the item on the FTP server. + /// The multi-line file list text from the FTP server. + /// Line item parser object used to parse each line of fileList data. + public FtpItemCollection(string path, string fileList, IFtpItemParser itemParser) + { + Parse(path, fileList, itemParser); + } + + /// + /// Merges two FtpItemCollection together into a single collection. + /// + /// Collection to merge with. + public void Merge(FtpItemCollection items) + { + if (items == null) + throw new ArgumentNullException("items", "must have a value"); + + foreach (FtpItem item in items) + { + FtpItem newItem = new FtpItem(item.Name, item.Modified, item.Size, item.SymbolicLink, item.Attributes, item.ItemType, item.RawText); + newItem.ParentPath = item.ParentPath; + this.Add(newItem); + } + } + + private void Parse(string path, string fileList, IFtpItemParser itemParser) + { + string[] lines = SplitFileList(fileList); + + int length = lines.Length - 1; + for (int i = 0; i <= length; i++) + { + FtpItem item = itemParser.ParseLine(lines[i]); + if (item != null && item.Name != "." && item.Name != "..") + { + // set the parent path to the value passed in + item.ParentPath = path; + _list.Add(item); + _totalSize += item.Size; + } + } + } + + private string[] SplitFileList(string response) + { + char[] crlfSplit = new char[2]; + crlfSplit[0] = '\r'; + crlfSplit[1] = '\n'; + return response.Split(crlfSplit, StringSplitOptions.RemoveEmptyEntries); + } + + /// + /// Convert current FtpCollection to a DataTable object. + /// + /// Data table object. + public DataTable ToDataTable() + { + DataTable dataTbl = new DataTable(); + dataTbl.Locale = CultureInfo.InvariantCulture; + + CreateColumns(dataTbl); + + foreach (FtpItem item in _list) + { + DataRow row = dataTbl.NewRow(); + row[COL_NAME] = item.Name; + row[COL_MODIFIED] = item.Modified; + row[COL_SIZE] = item.Size; + row[COL_SYMBOLIC_LINK] = item.SymbolicLink; + row[COL_TYPE] = item.ItemType.ToString(); + row[COL_ATTRIBUTES] = item.Attributes; + row[COL_RAW_TEXT] = item.RawText; + dataTbl.Rows.Add(row); + } + + return dataTbl; + } + + private void CreateColumns(DataTable dataTbl) + { + dataTbl.Columns.Add(new DataColumn(COL_NAME, typeof(string))); + dataTbl.Columns.Add(new DataColumn(COL_MODIFIED, typeof(DateTime))); + dataTbl.Columns.Add(new DataColumn(COL_SIZE, typeof(long))); + dataTbl.Columns.Add(new DataColumn(COL_TYPE, typeof(string))); + dataTbl.Columns.Add(new DataColumn(COL_ATTRIBUTES, typeof(string))); + dataTbl.Columns.Add(new DataColumn(COL_SYMBOLIC_LINK, typeof(string))); + dataTbl.Columns.Add(new DataColumn(COL_RAW_TEXT, typeof(string))); + } + + /// + /// Gets the size, in bytes, of all files in the collection as reported by the FTP server. + /// + public long TotalSize + { + get { return _totalSize; } + } + + /// + /// Searches for the specified object and returns the zero-based index of the + /// first occurrence within the entire FtpItemCollection list. + /// + /// The FtpItem to locate in the collection. + /// The zero-based index of the first occurrence of item within the entire if found; otherwise, -1. + public int IndexOf(FtpItem item) + { + return _list.IndexOf(item); + } + + /// + /// Adds an FtpItem to the end of the FtpItemCollection list. + /// + /// FtpItem object to add. + public void Add(FtpItem item) + { + _list.Add(item); + } + + /// + /// Gets the number of elements actually contained in the FtpItemCollection list. + /// + public int Count + { + get { return _list.Count; } + } + + IEnumerator IEnumerable.GetEnumerator() + { + return _list.GetEnumerator(); + } + + IEnumerator IEnumerable.GetEnumerator() + { + return _list.GetEnumerator(); + } + + /// + /// Gets an FtpItem from the FtpItemCollection based on index value. + /// + /// Numeric index of item to retrieve. + /// FtpItem + public FtpItem this[int index] + { + get { return _list[index]; } + } + + /// + /// Searches for the specified object based on the 'name' parameter value + /// and returns true if an object with the name is found; otherwise false. + /// + /// The name of the FtpItem to locate in the collection. + /// True if the name if found; otherwise false. + public bool ContainsName(string name) + { + if (name == null) + throw new ArgumentNullException("name", "must have a value"); + + foreach (FtpItem item in _list) + { + if (name == item.Name) + return true; + } + + return false; + } + + public void SetTimeOffset(int offset) { + foreach (var item in this) item.SetTimeOffset(offset); + } + + } +} \ No newline at end of file diff --git a/Source/MSBuild.Community.Tasks/Sync/FTP/FtpRequest.cs b/Source/MSBuild.Community.Tasks/Sync/FTP/FtpRequest.cs new file mode 100644 index 00000000..e708e37a --- /dev/null +++ b/Source/MSBuild.Community.Tasks/Sync/FTP/FtpRequest.cs @@ -0,0 +1,421 @@ +/* + * Authors: Benton Stark + * + * Copyright (c) 2007-2009 Starksoft, LLC (http://www.starksoft.com) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + */ + +using System; +using System.Collections.Generic; +using System.Text; +using System.Globalization; + +namespace Starksoft.Net.Ftp +{ + /// + /// FTP server commands. + /// + public enum FtpCmd + { + /// + /// Unknown command issued. + /// + Unknown, + /// + /// The USER command. + /// + User, + /// + /// The PASS command. + /// + Pass, + /// + /// The MKD command. Make new directory. + /// + Mkd, + /// + /// The RMD command. Remove directory. + /// + Rmd, + /// + /// The RETR command. Retrieve file. + /// + Retr, + /// + /// The PWD command. Print working directory. + /// + Pwd, + /// + /// The SYST command. System status. + /// + Syst, + /// + /// The CDUP command. Change directory up. + /// + Cdup, + /// + /// The DELE command. Delete file or directory. + /// + Dele, + /// + /// The TYPE command. Transfer type. + /// + Type, + /// + /// The CWD command. Change working directory. + /// + Cwd, + /// + /// The PORT command. Data port. + /// + Port, + /// + /// The PASV command. Passive port. + /// + Pasv, + /// + /// The STOR command. Store file. + /// + Stor, + /// + /// The STOU command. Store file unique. + /// + Stou, + /// + /// The APPE command. Append file. + /// + Appe, + /// + /// The RNFR command. Rename file from. + /// + Rnfr, + /// + /// The RFTO command. Rename file to. + /// + Rnto, + /// + /// The ABOR command. Abort current operation. + /// + Abor, + /// + /// The LIST command. List files. + /// + List, + /// + /// The NLST command. Namelist files. + /// + Nlst, + /// + /// The SITE command. Site. + /// + Site, + /// + /// The STAT command. Status. + /// + Stat, + /// + /// The NOOP command. No operation. + /// + Noop, + /// + /// The HELP command. Help. + /// + Help, + /// + /// The ALLO command. Allocate space. + /// + Allo, + /// + /// The QUIT command. Quite session. + /// + Quit, + /// + /// The REST command. Restart transfer. + /// + Rest, + /// + /// The AUTH command. Initialize authentication. + /// + Auth, + /// + /// The PBSZ command. + /// + Pbsz, + /// + /// The PROT command. Security protocol. + /// + Prot, + /// + /// The MODE command. Data transfer mode. + /// + Mode, + /// + /// The MDTM command. Month, date, and time. + /// + Mdtm, + /// + /// The SIZE command. File size. + /// + Size, + /// + /// The FEAT command. Supported features. + /// + Feat, + /// + /// The XCRC command. CRC file integrity. + /// + Xcrc, + /// + /// The XMD5 command. MD5 file integrity. + /// + Xmd5, + /// + /// The XSHA1 command. SHA1 file integerity. + /// + Xsha1, + /// + /// The EPSV command. + /// + Epsv, + /// + /// The ERPT command. + /// + Erpt + } + + /// + /// FTP request object which contains the command, arguments and text or an FTP request. + /// + public class FtpRequest + { + private FtpCmd _command; + private string[] _arguments; + private string _text; + private Encoding _encoding; + + /// + /// Default constructor. + /// + public FtpRequest() + { + _encoding = Encoding.UTF8; + _command = new FtpCmd(); + _text = string.Empty; + } + + /// + /// FTP request constructor. + /// + /// Text encoding object to use. + /// FTP request command. + /// Parameters for the request + internal FtpRequest(Encoding encoding, FtpCmd command, params string[] arguments) + { + _encoding = encoding; + _command = command; + _arguments = arguments; + _text = BuildCommandText(); + } + + /// + /// FTP request constructor. + /// + /// Text encoding object to use. + /// FTP request command. + internal FtpRequest(Encoding encoding, FtpCmd command) : this(encoding, command, null) + { } + + /// + /// Get the FTP command enumeration value. + /// + public FtpCmd Command + { + get { return _command; } + } + + /// + /// Get the FTP command arguments (if any). + /// + public List Arguments + { + get + { + return new List(_arguments); + } + } + + /// + /// Get the FTP command text with any arguments. + /// + public string Text + { + get { return _text; } + } + + /// + /// Gets a boolean value indicating if the command is a file transfer or not. + /// + public bool IsFileTransfer + { + get + { + if (_command == FtpCmd.Stou || _command == FtpCmd.Stor || _command == FtpCmd.Retr) + return true; + else + return false; + } + } + + internal string BuildCommandText() + { + string commandText = _command.ToString().ToUpper(CultureInfo.InvariantCulture); + + if (_arguments == null) + { + return commandText; + } + else + { + StringBuilder builder = new StringBuilder(); + foreach (string arg in _arguments) + { + builder.Append(arg); + builder.Append(" "); + } + string argText = builder.ToString().TrimEnd(); + + if (_command == FtpCmd.Unknown) + return argText; + else + return String.Format("{0} {1}", commandText, argText).TrimEnd(); + } + } + + internal byte[] GetBytes() + { + return _encoding.GetBytes(String.Format("{0}\r\n", _text)); + } + + internal bool HasHappyCodes + { + get { return GetHappyCodes().Length == 0 ? false : true; } + } + + + internal FtpResponseCode[] GetHappyCodes() + { + switch(_command) + { + case FtpCmd.Unknown: + return BuildResponseArray(); + case FtpCmd.Allo: + return BuildResponseArray(FtpResponseCode.CommandOkay, FtpResponseCode.CommandNotImplementedSuperfluousAtThisSite); + case FtpCmd.User: + return BuildResponseArray(FtpResponseCode.UserNameOkayButNeedPassword, FtpResponseCode.ServiceReadyForNewUser, FtpResponseCode.UserLoggedIn); + case FtpCmd.Pass: + return BuildResponseArray(FtpResponseCode.UserLoggedIn, FtpResponseCode.ServiceReadyForNewUser, FtpResponseCode.NotLoggedIn); + case FtpCmd.Cwd: + return BuildResponseArray(FtpResponseCode.RequestedFileActionOkayAndCompleted); + case FtpCmd.Pwd: + return BuildResponseArray(FtpResponseCode.PathNameCreated); + case FtpCmd.Dele: + return BuildResponseArray(FtpResponseCode.RequestedFileActionOkayAndCompleted); + case FtpCmd.Abor: + return BuildResponseArray(); + case FtpCmd.Mkd: + return BuildResponseArray(FtpResponseCode.PathNameCreated); + case FtpCmd.Rmd: + return BuildResponseArray(FtpResponseCode.RequestedFileActionOkayAndCompleted); + case FtpCmd.Help: + return BuildResponseArray(FtpResponseCode.SystemStatusOrHelpReply, FtpResponseCode.HelpMessage, FtpResponseCode.FileStatus); + case FtpCmd.Mdtm: + return BuildResponseArray(FtpResponseCode.FileStatus); + case FtpCmd.Stat: + return BuildResponseArray(FtpResponseCode.SystemStatusOrHelpReply, FtpResponseCode.DirectoryStatus, FtpResponseCode.FileStatus); + case FtpCmd.Cdup: + return BuildResponseArray(FtpResponseCode.CommandOkay, FtpResponseCode.RequestedFileActionOkayAndCompleted); + case FtpCmd.Size: + return BuildResponseArray(FtpResponseCode.FileStatus); + case FtpCmd.Feat: + return BuildResponseArray(FtpResponseCode.SystemStatusOrHelpReply); + case FtpCmd.Syst: + return BuildResponseArray(FtpResponseCode.NameSystemType); + case FtpCmd.Rnfr: + return BuildResponseArray(FtpResponseCode.RequestedFileActionPendingFurtherInformation); + case FtpCmd.Rnto: + return BuildResponseArray(FtpResponseCode.RequestedFileActionOkayAndCompleted); + case FtpCmd.Noop: + return BuildResponseArray(FtpResponseCode.CommandOkay); + case FtpCmd.Site: + return BuildResponseArray(FtpResponseCode.CommandOkay, FtpResponseCode.CommandNotImplementedSuperfluousAtThisSite, FtpResponseCode.RequestedFileActionOkayAndCompleted); + case FtpCmd.Pasv: + return BuildResponseArray(FtpResponseCode.EnteringPassiveMode); + case FtpCmd.Port: + return BuildResponseArray(FtpResponseCode.CommandOkay); + case FtpCmd.Type: + return BuildResponseArray(FtpResponseCode.CommandOkay); + case FtpCmd.Rest: + return BuildResponseArray(FtpResponseCode.RequestedFileActionPendingFurtherInformation); + case FtpCmd.Mode: + return BuildResponseArray(FtpResponseCode.CommandOkay); + case FtpCmd.Quit: + return BuildResponseArray(); + case FtpCmd.Auth: + return BuildResponseArray(FtpResponseCode.AuthenticationCommandOkay); + case FtpCmd.Pbsz: + return BuildResponseArray(FtpResponseCode.CommandOkay); + case FtpCmd.Prot: + return BuildResponseArray(FtpResponseCode.CommandOkay); + case FtpCmd.List: + case FtpCmd.Nlst: + return BuildResponseArray(FtpResponseCode.DataConnectionAlreadyOpenSoTransferStarting, + FtpResponseCode.FileStatusOkaySoAboutToOpenDataConnection, + FtpResponseCode.ClosingDataConnection, + FtpResponseCode.RequestedFileActionOkayAndCompleted); + case FtpCmd.Appe: + case FtpCmd.Stor: + case FtpCmd.Stou: + case FtpCmd.Retr: + return BuildResponseArray(FtpResponseCode.DataConnectionAlreadyOpenSoTransferStarting, + FtpResponseCode.FileStatusOkaySoAboutToOpenDataConnection, + FtpResponseCode.ClosingDataConnection, + FtpResponseCode.RequestedFileActionOkayAndCompleted); + case FtpCmd.Xcrc: + case FtpCmd.Xmd5: + case FtpCmd.Xsha1: + return BuildResponseArray(FtpResponseCode.RequestedFileActionOkayAndCompleted); + case FtpCmd.Epsv: + return BuildResponseArray(); + case FtpCmd.Erpt: + return BuildResponseArray(); + + default: + throw new FtpException(String.Format("No response code(s) defined for FtpCmd {0}.", _command.ToString())); + } + } + + private FtpResponseCode[] BuildResponseArray(params FtpResponseCode[] codes) + { + return codes; + } + + } +} diff --git a/Source/MSBuild.Community.Tasks/Sync/FTP/FtpResponse.cs b/Source/MSBuild.Community.Tasks/Sync/FTP/FtpResponse.cs new file mode 100644 index 00000000..ba2db612 --- /dev/null +++ b/Source/MSBuild.Community.Tasks/Sync/FTP/FtpResponse.cs @@ -0,0 +1,135 @@ +/* + * Authors: Benton Stark + * + * Copyright (c) 2007-2009 Starksoft, LLC (http://www.starksoft.com) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + */ + +using System; + +namespace Starksoft.Net.Ftp +{ + /// + /// FTP response class containing the FTP raw text, response code, and other information. + /// + public class FtpResponse + { + private string _rawText; + private string _text; + private FtpResponseCode _code = FtpResponseCode.None; + private bool _isInformational; + + /// + /// Default constructor for FtpResponse. + /// + public FtpResponse() + { } + + /// + /// Constructor for FtpResponse. + /// + /// Raw text information sent from the FTP server. + public FtpResponse(string rawText) + { + _rawText = rawText; + _text = ParseText(rawText); + _code = ParseCode(rawText); + _isInformational = ParseInformational(rawText); + } + + /// + /// Constructor for FtpResponse. + /// + /// FtpResponse object. + public FtpResponse(FtpResponse response) + { + _rawText = response.RawText; + _text = response.Text; + _code = response.Code; + _isInformational = response.IsInformational; + } + + /// + /// Get raw server response text information. + /// + public string RawText + { + get { return _rawText; } + } + + /// + /// Get the server response text. + /// + public string Text + { + get { return _text; } + } + + /// + /// Get a value indicating the FTP server response code. + /// + public FtpResponseCode Code + { + get { return _code; } + } + + internal bool IsInformational + { + get { return _isInformational; } + } + + private FtpResponseCode ParseCode(string rawText) + { + FtpResponseCode code = FtpResponseCode.None; + + if (rawText.Length >= 3) + { + string codeString = rawText.Substring(0, 3); + int codeInt = 0; + + if (Int32.TryParse(codeString, out codeInt)) + { + code = (FtpResponseCode)codeInt; + } + } + + return code; + } + + private string ParseText(string rawText) + { + if (rawText.Length > 4) + return rawText.Substring(4).Trim(); + else + return string.Empty; + } + + private bool ParseInformational(string rawText) + { + if (rawText.Length >= 4 && rawText[3] == '-') + return true; + else + return false; + } + + + } +} diff --git a/Source/MSBuild.Community.Tasks/Sync/FTP/FtpResponseCollection.cs b/Source/MSBuild.Community.Tasks/Sync/FTP/FtpResponseCollection.cs new file mode 100644 index 00000000..e7d6ebe2 --- /dev/null +++ b/Source/MSBuild.Community.Tasks/Sync/FTP/FtpResponseCollection.cs @@ -0,0 +1,132 @@ +/* + * Authors: Benton Stark + * + * Copyright (c) 2007-2009 Starksoft, LLC (http://www.starksoft.com) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + */ + +using System; +using System.Collections; +using System.Collections.Generic; +using System.Text; + + +namespace Starksoft.Net.Ftp +{ + /// + /// Ftp response collection. + /// + public class FtpResponseCollection : IEnumerable + { + private List _list = new List(); + + /// + /// Default constructor. + /// + public FtpResponseCollection() + { } + + /// + /// Searches for the specified object and returns the zero-based index of the + /// first occurrence within the entire FtpResponseCollection list. + /// + /// The FtpResponse object to locate in the collection. + /// The zero-based index of the first occurrence of item within the entire if found; otherwise, -1. + public int IndexOf(FtpResponse item) + { + return _list.IndexOf(item); + } + + + + /// + /// Adds an FtpResponse to the end of the FtpResponseCollection list. + /// + /// FtpResponse object to add. + public void Add(FtpResponse item) + { + _list.Add(item); + } + + /// + /// Gets the number of elements actually contained in the FtpResponseCollection list. + /// + public int Count + { + get { return _list.Count; } + } + + IEnumerator IEnumerable.GetEnumerator() + { + return _list.GetEnumerator(); + } + + IEnumerator IEnumerable.GetEnumerator() + { + return _list.GetEnumerator(); + } + + /// + /// Gets an FtpResponse from the FtpResponseCollection list based on index value. + /// + /// Numeric index of item to retrieve. + /// FtpResponse object. + public FtpResponse this[int index] + { + get { return _list[index]; } + } + + /// + /// Remove all elements from the FtpResponseCollection list. + /// + public void Clear() + { + _list.Clear(); + } + + /// + /// Get the raw FTP server supplied reponse text. + /// + /// A string containing the FTP server response. + public string GetRawText() + { + StringBuilder builder = new StringBuilder(); + foreach(FtpResponse item in _list) + { + builder.Append(item.RawText); + builder.Append("\r\n"); + } + return builder.ToString(); + } + + /// + /// Get the last server response from the FtpResponseCollection list. + /// + /// FtpResponse object. + public FtpResponse GetLast() + { + if (_list.Count == 0) + return new FtpResponse(); + else + return _list[_list.Count - 1]; + } + } +} \ No newline at end of file diff --git a/Source/MSBuild.Community.Tasks/Sync/FTP/FtpResponseQueue.cs b/Source/MSBuild.Community.Tasks/Sync/FTP/FtpResponseQueue.cs new file mode 100644 index 00000000..d0f66fa2 --- /dev/null +++ b/Source/MSBuild.Community.Tasks/Sync/FTP/FtpResponseQueue.cs @@ -0,0 +1,87 @@ +/* + * Authors: Benton Stark + * + * Copyright (c) 2007-2009 Starksoft, LLC (http://www.starksoft.com) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + */ + +using System; +using System.Collections.Generic; +using System.Text; + + +namespace Starksoft.Net.Ftp +{ + /// + /// Thread safe FtpResponse queue object. + /// + internal class FtpResponseQueue + { + private Queue _queue = new Queue(10); + + /// + /// Gets the number of elements contained in the FtpResponseQueue. + /// + public int Count + { + get + { + lock (this) + { + return _queue.Count; + } + } + } + + + + + + /// + /// Adds an Response object to the end of the FtpResponseQueue. + /// + /// An FtpResponse item. + public void Enqueue(FtpResponse item) + { + lock (this) + { + _queue.Enqueue(item); + } + } + + /// + /// Removes and returns the FtpResponse object at the beginning of the FtpResponseQueue. + /// + /// FtpResponse object at the beginning of the FtpResponseQueue + public FtpResponse Dequeue() + { + lock (this) + { + return _queue.Dequeue(); + } + } + + + + + + } +} diff --git a/Source/MSBuild.Community.Tasks/Sync/FTP/Hashing/Crc32.cs b/Source/MSBuild.Community.Tasks/Sync/FTP/Hashing/Crc32.cs new file mode 100644 index 00000000..ce8f5724 --- /dev/null +++ b/Source/MSBuild.Community.Tasks/Sync/FTP/Hashing/Crc32.cs @@ -0,0 +1,157 @@ + +using System; +using System.Security.Cryptography; + +namespace Starksoft.Hashing +{ + internal class Crc32 : HashAlgorithm + { + /// + /// + /// + public const UInt32 DefaultPolynomial = 0xedb88320; + /// + /// + /// + public const UInt32 DefaultSeed = 0xffffffff; + + private UInt32 hash; + private UInt32 seed; + private UInt32[] table; + private static UInt32[] defaultTable; + + /// + /// + /// + public Crc32() + { + table = InitializeTable(DefaultPolynomial); + seed = DefaultSeed; + Initialize(); + } + + /// + /// + /// + /// + /// + public Crc32(UInt32 polynomial, UInt32 seed) + { + table = InitializeTable(polynomial); + this.seed = seed; + Initialize(); + } + + public override void Initialize() + { + hash = seed; + } + + /// + /// + /// + /// + /// + /// + protected override void HashCore(byte[] buffer, int start, int length) + { + hash = CalculateHash(table, hash, buffer, start, length); + } + + /// + /// + /// + /// + protected override byte[] HashFinal() + { + byte[] hashBuffer = UInt32ToBigEndianBytes(~hash); + this.HashValue = hashBuffer; + return hashBuffer; + } + + /// + /// + /// + public override int HashSize + { + get { return 32; } + } + + /// + /// + /// + /// + /// + public static UInt32 Compute(byte[] buffer) + { + return ~CalculateHash(InitializeTable(DefaultPolynomial), DefaultSeed, buffer, 0, buffer.Length); + } + + /// + /// + /// + /// + /// + /// + public static UInt32 Compute(UInt32 seed, byte[] buffer) + { + return ~CalculateHash(InitializeTable(DefaultPolynomial), seed, buffer, 0, buffer.Length); + } + + /// + /// + /// + /// + /// + /// + /// + public static UInt32 Compute(UInt32 polynomial, UInt32 seed, byte[] buffer) + { + return ~CalculateHash(InitializeTable(polynomial), seed, buffer, 0, buffer.Length); + } + + private static UInt32[] InitializeTable(UInt32 polynomial) + { + if (polynomial == DefaultPolynomial && defaultTable != null) + return defaultTable; + + UInt32[] createTable = new UInt32[256]; + for (int i = 0; i < 256; i++) + { + UInt32 entry = (UInt32)i; + for (int j = 0; j < 8; j++) + if ((entry & 1) == 1) + entry = (entry >> 1) ^ polynomial; + else + entry = entry >> 1; + createTable[i] = entry; + } + + if (polynomial == DefaultPolynomial) + defaultTable = createTable; + + return createTable; + } + + private static UInt32 CalculateHash(UInt32[] table, UInt32 seed, byte[] buffer, int start, int size) + { + UInt32 crc = seed; + for (int i = start; i < size; i++) + unchecked + { + crc = (crc >> 8) ^ table[buffer[i] ^ crc & 0xff]; + } + return crc; + } + + private byte[] UInt32ToBigEndianBytes(UInt32 x) + { + return new byte[] { + (byte)((x >> 24) & 0xff), + (byte)((x >> 16) & 0xff), + (byte)((x >> 8) & 0xff), + (byte)(x & 0xff) + }; + } + } +} \ No newline at end of file diff --git a/Source/MSBuild.Community.Tasks/Sync/FTP/IFtpItemParser.cs b/Source/MSBuild.Community.Tasks/Sync/FTP/IFtpItemParser.cs new file mode 100644 index 00000000..d1c74d3e --- /dev/null +++ b/Source/MSBuild.Community.Tasks/Sync/FTP/IFtpItemParser.cs @@ -0,0 +1,140 @@ +/* + * Authors: Benton Stark + * + * Copyright (c) 2007-2009 Starksoft, LLC (http://www.starksoft.com) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + */ + +using System; + +namespace Starksoft.Net.Ftp +{ + /// + /// + /// This interface is used to create a pluggable, custom ftp item parser. The FtpClient object has a property named + /// ItemParser which is used to override the default item parser behavior. You might need to create a custom parser for + /// exotic FTP servers which the FtpClient object does not support. There is no standard supported in the RFC 959 standard + /// as to what format an FTP server must give for directory and file listings. Although newer FTP protocol standards so + /// support a structured directory listing with detailed information, this new format is not widely supported amoung FTP + /// server vendors and there is no hope for support for legacy FTP servers. + /// + /// + /// The FtpClient object can handle the most common formats without issue but for some older or more exotic FTP servers you may + /// such as an MVS legacy system may use a very different format than the common Unix and DOS style format. + /// In this situation, it makes the most sense to create your own ftp item parser to parse the unique directory and file listing. + /// + /// + /// + /// + /// When creating a custom ftp item parser for a specific FTP server you must create a new object and implment the ParseLine method. + /// Each line of directory listing data that is transmitted from the FTP server will result in the PareLine method being called. You must + /// parse the line of data and return a FtpItem object so that the item is added to the FtpItemList collection within the FtpClient object. + /// + /// + /// Below is an example of a custom FtpItem parser that handles both DOS and OS/2 style FTP server listings. Note that not all the information + /// such as file permissions can be obtained from the FTP server. + /// + /// + /// + /// + ///using System; + ///using System.Text.RegularExpressions; + /// + ///public class CustomFtpItemParser : IFtpItemParser + ///{ + /// public FtpItem ParseLine(string line) + /// { + /// // date portion + /// Regex rdate = new System.Text.RegularExpressions.Regex(@"(\d\d-\d\d-\d\d)"); + /// string date = rdate.Match(line).ToString(); + /// + /// // time portion + /// Regex rtime = new System.Text.RegularExpressions.Regex(@"(\d\d:\d\d\s*(AM|PM))|(\d\d:\d\d)", RegexOptions.IgnoreCase); + /// string time = rtime.Match(line).ToString(); + /// + /// // file size portion + /// Regex rsize = new System.Text.RegularExpressions.Regex(@"((?<=(\d\d:\d\d\s*(AM|PM)\s*))\d+)|(\d+(?=\s+\d\d-\d\d-\d\d\s+))", RegexOptions.IgnoreCase); + /// string size = rsize.Match(line).ToString(); + /// + /// // directory + /// Regex rdir = new System.Text.RegularExpressions.Regex(@"<DIR>|\sDIR\s", RegexOptions.IgnoreCase); + /// string dir = rdir.Match(line).ToString(); + /// + /// // name + /// Regex rname = new System.Text.RegularExpressions.Regex(@"((?<=<DIR>\s+).+)|((?<=\d\d:\d\d\s+).+)|((?<=(\d\d:\d\d(AM|PM)\s+\d+\s+)).+)", RegexOptions.IgnoreCase); + /// string name = rname.Match(line).ToString(); + /// + /// // put togther the date/time + /// DateTime dateTime = DateTime.MinValue; + /// DateTime.TryParse(String.Format("{0} {1}", date, time), out dateTime); + /// + /// // parse the file size + /// long sizeLng = 0; + /// Int64.TryParse(size, out sizeLng); + /// + /// // determine the file item itemType + /// FtpItemType itemTypeObj; + /// if (dir.Length > 0) + /// itemTypeObj = FtpItemType.Directory; + /// else + /// itemTypeObj = FtpItemType.File; + /// + /// return new FtpItem(name, dateTime, sizeLng, "", "", itemTypeObj, line); + /// } + ///} + /// + /// + /// + /// // create a FtpClient object to some local windows ftp server in your organization + /// FtpClient ftp = new FtpClient("192.168.1.1"); + /// + /// // use the custom ftp item parser + /// ftp.ItemParser = new CustomFtpItemParser(); + /// + /// // open a connect to the rserver + /// ftp.Open("ftp", "user@mail.com"); + /// FtpItemList list = ftp.GetDirList("/"); + /// + /// // list all the items to the debug output window + /// foreach (FtpItem item in list) + /// { + /// System.Diagnostics.Debug.WriteLine(item.Name + " " + item.Modified.ToString() + " " + item.Size.ToString() + " " + /// + item.SymbolicLink + " " + item.Type.ToString() + " " + item.Permissions + " ---- " + item.RawText); + /// } + /// + /// ftp.Close(); + /// + /// + + public interface IFtpItemParser + { + /// + /// The ParseLine method is called by the FtpClient for each line of directory listing data transmitted by the FTP server to the FTP client. + /// + /// A single line of data for a specific directory for file listing. + /// A new FtpItem object. + + FtpItem ParseLine(string line); + } +} + + + diff --git a/Source/MSBuild.Community.Tasks/Sync/FTP/references/Starksoft.Net.Proxy.XML b/Source/MSBuild.Community.Tasks/Sync/FTP/references/Starksoft.Net.Proxy.XML new file mode 100644 index 00000000..1b70c69d --- /dev/null +++ b/Source/MSBuild.Community.Tasks/Sync/FTP/references/Starksoft.Net.Proxy.XML @@ -0,0 +1,754 @@ + + + + Starksoft.Net.Proxy + + + + + Event arguments class for the EncryptAsyncCompleted event. + + + + + Constructor. + + Exception information generated by the event. + Cancelled event flag. This flag is set to true if the event was cancelled. + Proxy Connection. The initialized and open TcpClient proxy connection. + + + + The proxy connection. + + + + + Proxy client interface. This is the interface that all proxy clients must implement. + + + + + Creates a remote TCP connection through a proxy server to the destination host on the destination port. + + Destination host name or IP address. + Port number to connect to on the destination host. + + Returns an open TcpClient object that can be used normally to communicate + with the destination server + + + This method creates a connection to the proxy server and instructs the proxy server + to make a pass through connection to the specified destination host on the specified + port. + + + + + Asynchronously creates a remote TCP connection through a proxy server to the destination host on the destination port. + + Destination host name or IP address. + Port number to connect to on the destination host. + + Returns an open TcpClient object that can be used normally to communicate + with the destination server + + + This method creates a connection to the proxy server and instructs the proxy server + to make a pass through connection to the specified destination host on the specified + port. + + + + + Event handler for CreateConnectionAsync method completed. + + + + + Gets or sets proxy host name or IP address. + + + + + Gets or sets proxy port number. + + + + + Gets String representing the name of the proxy. + + + + + Gets or set the TcpClient object if one was specified in the constructor. + + + + + The type of proxy. + + + + + No Proxy specified. Note this option will cause an exception to be thrown if used to create a proxy object by the factory. + + + + + HTTP Proxy + + + + + SOCKS v4 Proxy + + + + + SOCKS v4a Proxy + + + + + SOCKS v5 Proxy + + + + + Factory class for creating new proxy client objects. + + + + // create an instance of the client proxy factory + ProxyClientFactory factory = new ProxyClientFactory(); + + // use the proxy client factory to generically specify the type of proxy to create + // the proxy factory method CreateProxyClient returns an IProxyClient object + IProxyClient proxy = factory.CreateProxyClient(ProxyType.Http, "localhost", 6588); + + // create a connection through the proxy to www.starksoft.com over port 80 + System.Net.Sockets.TcpClient tcpClient = proxy.CreateConnection("www.starksoft.com", 80); + + + + + + Factory method for creating new proxy client objects. + + The type of proxy client to create. + Proxy client object. + + + + Factory method for creating new proxy client objects using an existing TcpClient connection object. + + The type of proxy client to create. + Open TcpClient object. + Proxy client object. + + + + Factory method for creating new proxy client objects. + + The type of proxy client to create. + The proxy host or IP address. + The proxy port number. + Proxy client object. + + + + Factory method for creating new proxy client objects. + + The type of proxy client to create. + The proxy host or IP address. + The proxy port number. + The proxy username. This parameter is only used by Socks4 and Socks5 proxy objects. + The proxy user password. This parameter is only used Socks5 proxy objects. + Proxy client object. + + + + Factory method for creating new proxy client objects. + + The type of proxy client to create. + Open TcpClient object. + The proxy host or IP address. + The proxy port number. + The proxy username. This parameter is only used by Socks4 and Socks5 proxy objects. + The proxy user password. This parameter is only used Socks5 proxy objects. + Proxy client object. + + + + Socks5 connection proxy class. This class implements the Socks5 standard proxy protocol. + + + This implementation supports TCP proxy connections with a Socks v5 server. + + + + + Create a Socks5 proxy client object. + + + + + Creates a Socks5 proxy client object using the supplied TcpClient object connection. + + A TcpClient connection object. + + + + Create a Socks5 proxy client object. The default proxy port 1080 is used. + + Host name or IP address of the proxy server. + + + + Create a Socks5 proxy client object. + + Host name or IP address of the proxy server. + Port used to connect to proxy server. + + + + Create a Socks5 proxy client object. The default proxy port 1080 is used. + + Host name or IP address of the proxy server. + Proxy authentication user name. + Proxy authentication password. + + + + Create a Socks5 proxy client object. + + Host name or IP address of the proxy server. + Port used to connect to proxy server. + Proxy authentication user name. + Proxy authentication password. + + + + Creates a remote TCP connection through a proxy server to the destination host on the destination port. + + Destination host name or IP address of the destination server. + Port number to connect to on the destination host. + + Returns an open TcpClient object that can be used normally to communicate + with the destination server + + + This method creates a connection to the proxy server and instructs the proxy server + to make a pass through connection to the specified destination host on the specified + port. + + + + + Cancels any asychronous operation that is currently active. + + + + + Asynchronously creates a remote TCP connection through a proxy server to the destination host on the destination port. + + Destination host name or IP address. + Port number to connect to on the destination host. + + Returns TcpClient object that can be used normally to communicate + with the destination server. + + + This method instructs the proxy server + to make a pass through connection to the specified destination host on the specified + port. + + + + + Gets or sets host name or IP address of the proxy server. + + + + + Gets or sets port used to connect to proxy server. + + + + + Gets String representing the name of the proxy. + + This property will always return the value 'SOCKS5' + + + + Gets or sets proxy authentication user name. + + + + + Gets or sets proxy authentication password. + + + + + Gets or sets the TcpClient object. + This property can be set prior to executing CreateConnection to use an existing TcpClient connection. + + + + + Gets a value indicating whether an asynchronous operation is running. + + Returns true if an asynchronous operation is running; otherwise, false. + + + + + Gets a value indicating whether an asynchronous operation is cancelled. + + Returns true if an asynchronous operation is cancelled; otherwise, false. + + + + + Event handler for CreateConnectionAsync method completed. + + + + + Authentication itemType. + + + + + No authentication used. + + + + + Username and password authentication. + + + + + Socks4a connection proxy class. This class implements the Socks4a standard proxy protocol + which is an extension of the Socks4 protocol + + + In Socks version 4A if the client cannot resolve the destination host's domain name + to find its IP address the server will attempt to resolve it. + + + + + Socks4 connection proxy class. This class implements the Socks4 standard proxy protocol. + + + This class implements the Socks4 proxy protocol standard for TCP communciations. + + + + + Default Socks4 proxy port. + + + + + Socks4 version number. + + + + + Socks4 connection command value. + + + + + Socks4 bind command value. + + + + + Socks4 reply request grant response value. + + + + + Socks4 reply request rejected or failed response value. + + + + + Socks4 reply request rejected becauase the proxy server can not connect to the IDENTD server value. + + + + + Socks4 reply request rejected because of a different IDENTD server. + + + + + Create a Socks4 proxy client object. The default proxy port 1080 is used. + + + + + Creates a Socks4 proxy client object using the supplied TcpClient object connection. + + A TcpClient connection object. + + + + Create a Socks4 proxy client object. The default proxy port 1080 is used. + + Host name or IP address of the proxy server. + Proxy user identification information. + + + + Create a Socks4 proxy client object. + + Host name or IP address of the proxy server. + Port used to connect to proxy server. + Proxy user identification information. + + + + Create a Socks4 proxy client object. The default proxy port 1080 is used. + + Host name or IP address of the proxy server. + + + + Create a Socks4 proxy client object. + + Host name or IP address of the proxy server. + Port used to connect to proxy server. + + + + Creates a TCP connection to the destination host through the proxy server + host. + + Destination host name or IP address of the destination server. + Port number to connect to on the destination server. + + Returns an open TcpClient object that can be used normally to communicate + with the destination server + + + This method creates a connection to the proxy server and instructs the proxy server + to make a pass through connection to the specified destination host on the specified + port. + + + + + Sends a command to the proxy server. + + Proxy server data stream. + Proxy byte command to execute. + Destination host name or IP address. + Destination port number + IDENTD user ID value. + + + + Translate the host name or IP address to a byte array. + + Host name or IP address. + Byte array representing IP address in bytes. + + + + Translate the destination port value to a byte array. + + Destination port. + Byte array representing an 16 bit port number as two bytes. + + + + Receive a byte array from the proxy server and determine and handle and errors that may have occurred. + + Proxy server command response as a byte array. + Destination host. + Destination port number. + + + + Cancels any asychronous operation that is currently active. + + + + + Asynchronously creates a remote TCP connection through a proxy server to the destination host on the destination port + using the supplied open TcpClient object with an open connection to proxy server. + + Destination host name or IP address. + Port number to connect to on the destination host. + + Returns TcpClient object that can be used normally to communicate + with the destination server. + + + This instructs the proxy server to make a pass through connection to the specified destination host on the specified + port. + + + + + Gets or sets host name or IP address of the proxy server. + + + + + Gets or sets port used to connect to proxy server. + + + + + Gets String representing the name of the proxy. + + This property will always return the value 'SOCKS4' + + + + Gets or sets proxy user identification information. + + + + + Gets or sets the TcpClient object. + This property can be set prior to executing CreateConnection to use an existing TcpClient connection. + + + + + Gets a value indicating whether an asynchronous operation is running. + + Returns true if an asynchronous operation is running; otherwise, false. + + + + + Gets a value indicating whether an asynchronous operation is cancelled. + + Returns true if an asynchronous operation is cancelled; otherwise, false. + + + + + Event handler for CreateConnectionAsync method completed. + + + + + Default constructor. + + + + + Creates a Socks4 proxy client object using the supplied TcpClient object connection. + + An open TcpClient object with an established connection. + + + + Create a Socks4a proxy client object. The default proxy port 1080 is used. + + Host name or IP address of the proxy server. + Proxy user identification information for an IDENTD server. + + + + Create a Socks4a proxy client object. + + Host name or IP address of the proxy server. + Port used to connect to proxy server. + Proxy user identification information. + + + + Create a Socks4 proxy client object. The default proxy port 1080 is used. + + Host name or IP address of the proxy server. + + + + Create a Socks4a proxy client object. + + Host name or IP address of the proxy server. + Port used to connect to proxy server. + + + + Sends a command to the proxy server. + + Proxy server data stream. + Proxy byte command to execute. + Destination host name or IP address. + Destination port number + IDENTD user ID value. + + This method override the SendCommand message in the Sock4ProxyClient object. The override adds support for the + Socks4a extensions which allow the proxy client to optionally command the proxy server to resolve the + destination host IP address. + + + + + Gets String representing the name of the proxy. + + This property will always return the value 'SOCKS4a' + + + + HTTP connection proxy class. This class implements the HTTP standard proxy protocol. + + You can use this class to set up a connection to an HTTP proxy server. Calling the + CreateConnection() method initiates the proxy connection and returns a standard + System.Net.Socks.TcpClient object that can be used as normal. The proxy plumbing + is all handled for you. + + + + + + + + + Constructor. + + + + + Creates a HTTP proxy client object using the supplied TcpClient object connection. + + A TcpClient connection object. + + + + Constructor. The default HTTP proxy port 8080 is used. + + Host name or IP address of the proxy. + + + + Constructor. + + Host name or IP address of the proxy server. + Port number for the proxy server. + + + + Creates a remote TCP connection through a proxy server to the destination host on the destination port. + + Destination host name or IP address. + Port number to connect to on the destination host. + + Returns an open TcpClient object that can be used normally to communicate + with the destination server + + + This method creates a connection to the proxy server and instructs the proxy server + to make a pass through connection to the specified destination host on the specified + port. + + + + + Cancels any asychronous operation that is currently active. + + + + + Asynchronously creates a remote TCP connection through a proxy server to the destination host on the destination port. + + Destination host name or IP address. + Port number to connect to on the destination host. + + Returns an open TcpClient object that can be used normally to communicate + with the destination server + + + This method creates a connection to the proxy server and instructs the proxy server + to make a pass through connection to the specified destination host on the specified + port. + + + + + Gets or sets host name or IP address of the proxy server. + + + + + Gets or sets port number for the proxy server. + + + + + Gets String representing the name of the proxy. + + This property will always return the value 'HTTP' + + + + Gets or sets the TcpClient object. + This property can be set prior to executing CreateConnection to use an existing TcpClient connection. + + + + + Gets a value indicating whether an asynchronous operation is running. + + Returns true if an asynchronous operation is running; otherwise, false. + + + + + Gets a value indicating whether an asynchronous operation is cancelled. + + Returns true if an asynchronous operation is cancelled; otherwise, false. + + + + + Event handler for CreateConnectionAsync method completed. + + + + + This exception is thrown when a general, unexpected proxy error. + + + + + Constructor. + + + + + Constructor. + + Exception message text. + + + + Constructor. + + Exception message text. + The inner exception object. + + + + Constructor. + + Serialization information. + Stream context information. + + + diff --git a/Source/MSBuild.Community.Tasks/Sync/FTP/references/Starksoft.Net.Proxy.dll b/Source/MSBuild.Community.Tasks/Sync/FTP/references/Starksoft.Net.Proxy.dll new file mode 100644 index 00000000..46c44003 Binary files /dev/null and b/Source/MSBuild.Community.Tasks/Sync/FTP/references/Starksoft.Net.Proxy.dll differ diff --git a/Source/MSBuild.Community.Tasks/Sync/FtpClient.cs b/Source/MSBuild.Community.Tasks/Sync/FtpClient.cs new file mode 100644 index 00000000..0ff95faf --- /dev/null +++ b/Source/MSBuild.Community.Tasks/Sync/FtpClient.cs @@ -0,0 +1,15 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace johnshope.Sync { + public class FtpClient: Starksoft.Net.Ftp.FtpClient { + + public FtpClient(string host, int port, Starksoft.Net.Ftp.FtpSecurityProtocol protocol, int Index) : base(host, port, protocol) { this.Index = Index; Clients = 0; } + + public int Index { get; set; } + + public int Clients { get; set; } + } +} diff --git a/Source/MSBuild.Community.Tasks/Sync/FtpConnections.cs b/Source/MSBuild.Community.Tasks/Sync/FtpConnections.cs new file mode 100644 index 00000000..df31b882 --- /dev/null +++ b/Source/MSBuild.Community.Tasks/Sync/FtpConnections.cs @@ -0,0 +1,176 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using Starksoft.Net.Ftp; + +namespace johnshope.Sync { + + public class FtpConnections { + + public SyncJob Job { get; set; } + public Log Log { get { return Job.Log; } } + + Dictionary> Queue = new Dictionary>(); + Dictionary TimeOffsets = new Dictionary(); + Dictionary Features = new Dictionary(); + + string Key(FtpClient ftp) { return ftp.Host + ":" + ftp.Port.ToString(); } + string Key(Uri uri) { return uri.Host + ":" + uri.Port.ToString(); } + + int? Connections(Uri url) { + var query = url.Query(); + int con; + if (int.TryParse((query["connections"] ?? "").ToString(), out con)) return con; + else return null; + } + + string Proxy(Uri url) { + var query = url.Query(); + string proxy = (query["proxy"] ?? "").ToString(); + return proxy; + } + + int? TimeOffset(Uri url) { + var query = url.Query(); + int zone = 0; + string zonestr = (string)query["time"]; + if (string.IsNullOrEmpty(zonestr)) return null; + zonestr = zonestr.ToLower(); + if (zonestr == "z" || zonestr == "utc") return 0; + if (!int.TryParse(zonestr, out zone)) return null; + return zone; + } + + int clientIndex = 0; + + public string FTPTag(int n) { return "FTP" + n.ToString(); } + + public FtpClient Open(ref Uri url) { + var queue = Queue[Key(url)]; + var path = url.Path(); + var ftp = queue.DequeueOrBlock(client => client.CurrentDirectory == client.CorrectPath(path)); + try { + if (ftp == null) { + ftp = new johnshope.Sync.FtpClient(url.Host, url.Port, url.Scheme == "ftps" ? FtpSecurityProtocol.Tls1Explicit : FtpSecurityProtocol.None, ++clientIndex); + ftp.Job = Job; + //ftp.IsLoggingOn = Sync.Verbose; + if (Job.Verbose) { + ftp.ClientRequest += new EventHandler((sender, args) => { + lock (Log) { Log.YellowLabel(FTPTag(ftp.Index) + "> "); Log.Text(args.Request.Text); } + }); + ftp.ServerResponse += new EventHandler((sender, args) => { + lock (Log) { Log.Label(FTPTag(ftp.Index) + ": "); Log.Text(args.Response.RawText); } + }); + } + if (url.Query()["passive"] != null || url.Query()["active"] == null) ftp.DataTransferMode = TransferMode.Passive; + else ftp.DataTransferMode = TransferMode.Passive; + ftp.AutoChecksumValidation = HashingFunction.None; + if (url.Query()["md5"] != null) ftp.AutoChecksumValidation = HashingFunction.Md5; + else if (url.Query()["sha"] != null) ftp.AutoChecksumValidation = HashingFunction.Sha1; + else if (url.Query()["crc"] != null) ftp.AutoChecksumValidation = HashingFunction.Crc32; + } else { + if (!ftp.IsConnected) ftp.Reopen(); + } + ftp.Clients++; + if (ftp.Clients != 1) throw new Exception("FTP connection is opened by multiple clients."); + if (!ftp.IsConnected) { + if (!string.IsNullOrEmpty(url.UserInfo)) { + if (url.UserInfo.Contains(':')) { + var user = url.UserInfo.Split(':'); + ftp.Open(user[0], user[1]); + } else { + ftp.Open(url.UserInfo, string.Empty); + } + } else { + ftp.Open("Anonymous", "anonymous"); + } + // set encoding + string features; + if (!Features.TryGetValue(Key(url), out features)) { + features = ftp.GetFeatures(); + Features.Add(Key(url), features); + } + // set encoding + if (url.Query()["old"] == null) { + if (features.Contains("UTF8")) { + ftp.CharacterEncoding = System.Text.Encoding.UTF8; + ftp.Quote("OPTS UTF8 ON"); + } else if (features.Contains("UTF7")) { + ftp.CharacterEncoding = System.Text.Encoding.UTF7; + ftp.Quote("OPTS UTF7 ON"); + } else { + ftp.CharacterEncoding = System.Text.Encoding.ASCII; + } + } else { + ftp.CharacterEncoding = System.Text.Encoding.ASCII; + } + } + // get server local time offset + var offset = TimeOffset(url); + if (offset.HasValue) ftp.TimeOffset = offset; + else if (!ftp.TimeOffset.HasValue) { + lock (queue) { + var offsetclient = queue.FirstOrDefault(client => client != null && client.TimeOffset.HasValue); + if (offsetclient != null) ftp.TimeOffset = offsetclient.TimeOffset; + ftp.TimeOffset = ftp.ServerTimeOffset; + } + } + // change path + path = ftp.CorrectPath(url.Path()); + if (url.Query()["raw"] != null && ftp.IsCompressionEnabled) ftp.CompressionOff(); + if (url.Query()["zip"] != null && ftp.IsCompressionEnabled) ftp.CompressionOn(); + if (ftp.CurrentDirectory != path) { + try { + if (url.Query()["old"] != null) ftp.ChangeDirectoryMultiPath(path); + else ftp.ChangeDirectory(path); + } catch (Exception ex) { + ftp.MakeDirectory(path); + if (url.Query()["old"] != null) ftp.ChangeDirectoryMultiPath(path); + else ftp.ChangeDirectory(path); + } + if (ftp.CurrentDirectory != ftp.CorrectPath(url.Path())) + throw new Exception(string.Format("Cannot change to correct path {0}.", url.Path())); + } + } catch (FtpDataConnectionException ex) { + if (url.Query()["passive"] == null) { + url = new Uri(url.ToString() + (url.Query().Count > 0 ? "&" : "%3F") + "passive", true); + ftp.Close(); + ftp.DataTransferMode = TransferMode.Passive; + ftp.Clients++; + Pass(ftp); + return Open(ref url); + } else { + Log.Exception(ex); + } + } catch (Exception e) { + Log.Exception(e); + } + if (ftp.Clients != 1 || ftp.CurrentDirectory != ftp.CorrectPath(url.Path())) + throw new Exception("FTP connection open postcondition failed."); + return ftp; + } + + public void Pass(FtpClient client) { + if (client == null || client.Clients != 1) throw new Exception("FTP connection pass precondition failed."); + client.Clients--; + Queue[Key(client)].Enqueue(client); + } + + public int Count(Uri url) { if (url.IsFile) return 1; else return Queue[Key(url)].Count; } + + public int Allocate(Uri url) { if (!url.IsFile) { Queue[Key(url)] = new ResourceQueue(); var n = Connections(url) ?? 10; var i = n; while (i-- > 0) Queue[Key(url)].Enqueue(null); return n; } return 1; } + + public void Close() { + foreach (var queue in Queue.Values) { + while (queue.Count > 0) { + var ftp = queue.Dequeue(); + if (ftp != null) { + if (ftp.IsConnected) ftp.Close(); + ftp.Dispose(); + } + } + } + } + } +} diff --git a/Source/MSBuild.Community.Tasks/Sync/FtpDirectory.cs b/Source/MSBuild.Community.Tasks/Sync/FtpDirectory.cs new file mode 100644 index 00000000..36a1bdc3 --- /dev/null +++ b/Source/MSBuild.Community.Tasks/Sync/FtpDirectory.cs @@ -0,0 +1,223 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using Starksoft.Net.Ftp; + +namespace johnshope.Sync { + + public class FtpDirectory : FileOrDirectory, IDirectory { + +// public SyncJob Job { get; set; } + public Log Log { get { return Job.Log; } } + + Uri url; + public Uri Url { get { return url; } set { url = value; } } + + public bool TransferProgress { get; set; } + + public FtpDirectory(FileOrDirectory parent, Uri url, SyncJob job) { + Job = job; + Parent = parent; + if (url.Scheme != "ftp" && url.Scheme != "ftps") throw new NotSupportedException(); + Url = url; + Name = url.File(); + Class = ObjectClass.Directory; + Changed = DateTime.Now.AddDays(2); + if (parent is FtpDirectory) TransferProgress = ((FtpDirectory)parent).TransferProgress; + } + + public IDirectory Source { get; set; } + public IDirectory Destination { get; set; } + + bool UseCompression { get { return Url.Query.Contains("compress"); } } + + public DirectoryListing List() { + var ftp = Job.Connections.Open(ref url); + try { + ftp.FileTransferType = TransferType.Binary; + var list = ftp.GetDirList() + .Select(fi => + fi.ItemType == FtpItemType.Directory ? + new FtpDirectory(this, Url.Relative(fi.Name), Job) : + new FileOrDirectory { Name = fi.Name, Class = ObjectClass.File, Changed = fi.Modified, Size = fi.Size, Parent = this }) + .ToList(); + return new DirectoryListing(list); + } catch (Exception ex) { + Job.Failure(this, ex, ftp); + } finally { + Job.Connections.Pass(ftp); + } + return new DirectoryListing(); + } + + static readonly TimeSpan Interval = TimeSpan.FromSeconds(10); + + class ProgressData { + public string Path; + public long Size; + public long Transferred; + public TimeSpan ElapsedTime; + } + + Dictionary progress = new Dictionary(); + public void ShowProgress(object sender, TransferProgressEventArgs a) { + if (TransferProgress) { + try { + var ftp = (FtpClient)sender; + var p = progress[ftp]; + p.Transferred += a.BytesTransferred; + if (a.ElapsedTime - p.ElapsedTime > Interval) { + Log.Progress(p.Path, p.Size, p.Transferred, a.ElapsedTime); + //Log.Text(ftp.dw.TotalMilliseconds.ToString()); + //Log.Text(ftp.dr.TotalMilliseconds.ToString()); + p.ElapsedTime = a.ElapsedTime; + } + } catch { } + } + } + + public void WriteFile(System.IO.Stream file, FileOrDirectory src) { + if (file == null) return; + + var ftp = Job.Connections.Open(ref url); + try { + if (ftp.FileTransferType != TransferType.Binary) ftp.FileTransferType = TransferType.Binary; + var path = Url.Path() + "/" + src.Name; + if (TransferProgress) { + progress.Add(ftp, new ProgressData { ElapsedTime = new TimeSpan(0), Path = path, Size = src.Size }); + ftp.TransferProgress += ShowProgress; + } + var start = DateTime.Now; + ftp.PutFile(file, src.Name, FileAction.Create); + ftp.SetDateTime(src.Name, src.ChangedUtc); + + Log.Upload(path, src.Size, DateTime.Now - start); + } catch (Exception e) { + Job.Failure(src, e, ftp); + } finally { + if (TransferProgress) { + ftp.TransferProgress -= ShowProgress; + progress.Remove(ftp); + } + Job.Connections.Pass(ftp); + } + } + + public System.IO.Stream ReadFile(FileOrDirectory src) { + var file = new FtpStream(Job); + Job.Threads.DoAsync(() => { + var ftp = Job.Connections.Open(ref url); + try { + if (ftp.FileTransferType != TransferType.Binary) ftp.FileTransferType = TransferType.Binary; + file.Client = ftp; + file.Path = Url.Path() + "/" + src.Name; + file.Size = src.Size; + if (TransferProgress) { + progress.Add(ftp, new ProgressData { ElapsedTime = new TimeSpan(0), Path = file.Path, Size = src.Size }); + ftp.TransferProgress += ShowProgress; + } + file.Client.GetFile(src.Name, file, false); + } catch (Exception ex) { + Job.Failure(src, ex, ftp); + file.Exception(ex); + } finally { + file.Close(); + if (TransferProgress) { + file.Client.TransferProgress -= ShowProgress; + progress.Remove(file.Client); + } + Job.Connections.Pass(file.Client); + } + }); + return file; + } + + public void DeleteFile(FileOrDirectory dest) { + var ftp = Job.Connections.Open(ref url); + try { + ftp.DeleteFile(dest.Name); + } catch (Exception ex) { + Job.Failure(dest, ex, ftp); + } finally { + Job.Connections.Pass(ftp); + } + } + + public void DeleteDirectory(FileOrDirectory dest) { DeleteDirectory(dest, null); } + + public void DeleteDirectory(FileOrDirectory dest, EventHandler onFinished = null) { + var dir = (FtpDirectory)dest; + int con = Job.Connections.Count(dir.url); + if (con == 0) con = 1; + var list = dir.List(); + var subdirs = list.OfType(); + + var n = new SyncJob.Counter(); + n.N = subdirs.Count(); + + var finished = (EventHandler)((sender, args) => { + lock (n) { + n.N--; + if (n.N <= 0) { + var ftp = Job.Connections.Open(ref dir.url); + try { + foreach (var file in list.Where(f => f.Class == ObjectClass.File)) ftp.DeleteFile(file.Name); + ftp.ChangeDirectoryUp(); + ftp.DeleteDirectory(dest.Name); + } catch (Exception ex) { + Job.Failure(dest, ex); + } finally { + Job.Connections.Pass(ftp); + } + if (onFinished != null) onFinished(this, EventArgs.Empty); + } + } + }); + foreach (var item in subdirs) { + var d = item; + var t = new Task(() => { d.DeleteDirectory(d, finished); }); + //t.Finished += finished; + Job.Threads.Do(t); + } + if (subdirs.Count() == 0) finished(this, EventArgs.Empty); + } + + public void Delete(FileOrDirectory dest) { + if (dest.Class == ObjectClass.File) DeleteFile(dest); + else DeleteDirectory(dest); + } + + public IDirectory CreateDirectory(FileOrDirectory dest) { + var ftp = Job.Connections.Open(ref url); + try { + var path = ftp.CorrectPath(Url.Path()); + if (dest != null) path = path + "/" + dest.Name; + //var curpath = ftp.CurrentDirectory; + //var ps = path.Split('/'); + //var cs = curpath.Split('/'); + //var j = cs.Length-1; + //var i = Math.Min(ps.Length, j+1); + //while (j > i-1) { ftp.ChangeDirectoryUp(); j--; } + //while (j > 0 && ps[j] != cs[j]) { ftp.ChangeDirectoryUp(); j--; i = j+1; } + + //while (i < ps.Length) { str.Append("/"); str.Append(ps[i++]); } + + //var dir = str.ToString(); + ftp.MakeDirectory(path); + + //if (url.Query()["old"] != null) ftp.ChangeDirectoryMultiPath(path); + //else ftp.ChangeDirectory(path); + + if (dest != null) return new FtpDirectory(this, Url.Relative(dest.Name), Job); + else return this; + } catch (Exception ex) { + Job.Failure(dest, ex, ftp); + } finally { + Job.Connections.Pass(ftp); + } + return null; + } + + } +} diff --git a/Source/MSBuild.Community.Tasks/Sync/FtpStream.cs b/Source/MSBuild.Community.Tasks/Sync/FtpStream.cs new file mode 100644 index 00000000..9f3abf09 --- /dev/null +++ b/Source/MSBuild.Community.Tasks/Sync/FtpStream.cs @@ -0,0 +1,27 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using Starksoft.Net.Ftp; + +namespace johnshope.Sync { + + public class FtpStream: PipeStream { + + public SyncJob Job { get; set; } + public Log Log { get { return Job.Log; } } + + public FtpClient Client { get; set; } + public string Path { get; set; } + public long Size { get; set; } + DateTime start; + public FtpStream(SyncJob job) : base() { start = DateTime.Now; Job = job; } + + protected override void Dispose(bool disposing) { + base.Dispose(disposing); + //FtpConnections.Pass(Client); + Log.Download(Path, Size, DateTime.Now - start); + } + } + +} diff --git a/Source/MSBuild.Community.Tasks/Sync/IDirectory.cs b/Source/MSBuild.Community.Tasks/Sync/IDirectory.cs new file mode 100644 index 00000000..d2172c56 --- /dev/null +++ b/Source/MSBuild.Community.Tasks/Sync/IDirectory.cs @@ -0,0 +1,50 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Linq; +using System.Text; +using System.IO; + +namespace johnshope.Sync { + + public enum ObjectClass { File, Directory } + + public class FileOrDirectory { + public SyncJob Job { get; set; } + public FileOrDirectory Parent { get; set; } + public string RelativePath { get { if (Parent == null) return Name; else return Parent.RelativePath + "/" + Name; } } + public string Name; + public ObjectClass Class; + public DateTime Changed; + public DateTime ChangedUtc { get { return Changed.ToUniversalTime(); } set { Changed = value.ToLocalTime(); } } + public long Size; + } + + public class DirectoryListing : KeyedCollection { + public DirectoryListing() : base() { } + public DirectoryListing(IEnumerable list): this() { + foreach (var e in list) { + try { Add(e); } catch { } + } + } + + protected override string GetKeyForItem(FileOrDirectory item) { + return item.Name; + } + } + + public interface IDirectory { + SyncJob Job { get; set; } + Uri Url { get; set; } + DirectoryListing List(); + void WriteFile(Stream file, FileOrDirectory src); + Stream ReadFile(FileOrDirectory src); + void Delete(FileOrDirectory dest); + void DeleteFile(FileOrDirectory dest); + void DeleteDirectory(FileOrDirectory dest); + IDirectory CreateDirectory(FileOrDirectory src); + IDirectory Source { get; set; } + IDirectory Destination { get; set; } + } +} diff --git a/Source/MSBuild.Community.Tasks/Sync/LocalDirectory.cs b/Source/MSBuild.Community.Tasks/Sync/LocalDirectory.cs new file mode 100644 index 00000000..6bbab709 --- /dev/null +++ b/Source/MSBuild.Community.Tasks/Sync/LocalDirectory.cs @@ -0,0 +1,122 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.IO; +using System.Web; + +namespace johnshope.Sync { + + class LocalDirectory: FileOrDirectory, IDirectory { + +// public SyncJob Job { get; set; } + public Log Log { get { return Job.Log; } } + + public Uri Url { get; set; } + + public string Path { get { return HttpUtility.UrlDecode(Url.LocalPath); } } + + public LocalDirectory(FileOrDirectory parent, Uri url, SyncJob job) { + Job = job; + Parent = parent; + if (!url.ToString().Contains(':')) { + if (url.ToString() == ".") url = new Uri(Environment.CurrentDirectory); + else url = new Uri(System.IO.Path.Combine(Environment.CurrentDirectory, url.ToString())); + } + if (!url.IsFile) throw new NotSupportedException("url is no local file."); + Url = url; + var info = new DirectoryInfo(Path); + Name = info.Name; + Class = ObjectClass.Directory; + ChangedUtc = info.LastWriteTimeUtc; + } + + public IDirectory Source { get; set; } + public IDirectory Destination { get; set; } + + public DirectoryListing List() { + try { + var info = new DirectoryInfo(Path); + if (info.Exists) { + var finfos = info.GetFileSystemInfos(); + var infos = info.GetFileSystemInfos() + .Select(fi => fi is FileInfo ? new FileOrDirectory { Name = fi.Name, Class = ObjectClass.File, ChangedUtc = fi.LastWriteTimeUtc, Size = ((FileInfo)fi).Length, Parent = this } : new LocalDirectory(this, new Uri(fi.FullName), Job)); + return new DirectoryListing(infos); + } else { + return new DirectoryListing(); + } + } catch (Exception ex) { + Job.Failure(this, ex); + } + return new DirectoryListing(); + } + + public void WriteFile(Stream sstream, FileOrDirectory src) { + if (sstream == null) return; + try { + var path = System.IO.Path.Combine(Path, src.Name); + using (var dstream = File.Create(path)) { + if (sstream is PipeStream) { + ((PipeStream)sstream).Read(dstream); + } else { + Streams.Copy(sstream, dstream); + } + } + File.SetLastAccessTimeUtc(path, src.ChangedUtc); + } catch (Exception ex) { + Job.Failure(src, ex); + } + } + + public Stream ReadFile(FileOrDirectory src) { + try { + var path = System.IO.Path.Combine(Path, src.Name); + return File.OpenRead(path); + } catch (Exception ex) { + Job.Failure(src, ex); + } + return null; + } + + public void DeleteFile(FileOrDirectory dest) { + try { + var path = System.IO.Path.Combine(Path, dest.Name); + if (new FileInfo(path).FullName != new FileInfo(Job.LogFile).FullName) System.IO.File.Delete(path); + } catch (Exception ex) { + Job.Failure(dest, ex); + } + } + + public void DeleteDirectory(FileOrDirectory dest) { + try { + System.IO.Directory.Delete(((LocalDirectory)dest).Path, true); + } catch (Exception ex) { + Job.Failure(dest, ex); + } + } + + public void Delete(FileOrDirectory dest) { + try { + var path = System.IO.Path.Combine(Path, dest.Name); + if (dest.Class == ObjectClass.File) { + if (new FileInfo(path).FullName != new FileInfo(Job.LogFile).FullName) System.IO.File.Delete(path); + } else System.IO.Directory.Delete(path, true); + } catch (Exception ex) { + Job.Failure(dest, ex); + } + } + + public IDirectory CreateDirectory(FileOrDirectory src) { + try { + string path; + if (src == null) path = Path; + else path = System.IO.Path.Combine(Path, src.Name); + System.IO.Directory.CreateDirectory(path); + return new LocalDirectory(this, new Uri(path), Job); + } catch (Exception ex) { + Job.Failure(src, ex); + } + return null; + } + } +} diff --git a/Source/MSBuild.Community.Tasks/Sync/Log.cs b/Source/MSBuild.Community.Tasks/Sync/Log.cs new file mode 100644 index 00000000..26f32f3a --- /dev/null +++ b/Source/MSBuild.Community.Tasks/Sync/Log.cs @@ -0,0 +1,139 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.IO; + +namespace johnshope.Sync { + + public class Log { + + public SyncJob Job = null; + + public int Uploads = 0; + public int Downloads = 0; + public long UploadSize = 0; + public long DownloadSize = 0; + public int Errors = 0; + + const int KB = 1024; + const int MB = 1024 * KB; + const int GB = 1024 * MB; + const int MaxLogSize = 10*MB; + + bool checkDir = true; + + StringBuilder buffer = new StringBuilder(); + + public virtual string Size(long size) { + if (size > GB) return string.Format("{0:F2} GB", size / (1.0 * GB)); + if (size > 100 * KB) return string.Format("{0:F2} MB", size / (1.0 * MB)); + return string.Format("{0:F2} KB", size / (1.0 * KB)); + } + + public virtual void Debug(string text) { + if (Job.Verbose) Text(text); + } + + public virtual void Flush() { + if (Job.LogFile != null) { + try { + if (checkDir) { + checkDir = false; + var dir = Path.GetDirectoryName(Job.LogFile); + if (!string.IsNullOrEmpty(dir) && !System.IO.Directory.Exists(dir)) System.IO.Directory.CreateDirectory(dir); + } + System.IO.File.AppendAllText(Job.LogFile, buffer.ToString(), UTF8Encoding.UTF8); +#if NET4 + buffer.Clear(); +#else + buffer = new StringBuilder(); +#endif + } catch (Exception ex) { + Console.WriteLine("Error writing to the logfile " + Job.LogFile); + Console.WriteLine(ex.Message); + } + } + } + + public virtual void LogText(string text, bool newline) { + lock (this) { + if (Job.LogFile != null) { + if (newline) buffer.AppendLine(text); + else buffer.Append(text); + } + } + } + + public virtual void Text(string text) { lock (this) { Console.WriteLine(text); LogText(text, true); } } + public virtual void RedText(string text) { lock (this) { var oldc = Console.ForegroundColor; Console.ForegroundColor = ConsoleColor.Red; Text(text); Console.ForegroundColor = oldc; } } + public virtual void CyanText(string text) { lock (this) { var oldc = Console.ForegroundColor; Console.ForegroundColor = ConsoleColor.Cyan; Text(text); Console.ForegroundColor = oldc; } } + public virtual void GreenText(string text) { lock (this) { var oldc = Console.ForegroundColor; Console.ForegroundColor = ConsoleColor.Green; Text(text); Console.ForegroundColor = oldc; } } + public virtual void YellowText(string text) { lock (this) { var oldc = Console.ForegroundColor; Console.ForegroundColor = ConsoleColor.Yellow; Text(text); Console.ForegroundColor = oldc; } } + public virtual void YellowLabel(string text) { lock (this) { var oldc = Console.ForegroundColor; Console.ForegroundColor = ConsoleColor.Yellow; Console.Write(text); LogText(text, false); Console.ForegroundColor = oldc; } } + public virtual void RedLabel(string text) { lock (this) { var oldc = Console.ForegroundColor; Console.ForegroundColor = ConsoleColor.Red; Console.Write(text); LogText(text, false); Console.ForegroundColor = oldc; } } + public virtual void Label(string text) { lock (this) { Console.Write(text); LogText(text, false); } } + public virtual void Dot() { lock (this) { var oldc = Console.ForegroundColor; Console.ForegroundColor = ConsoleColor.Green; Console.Write("."); LogText(".", false); Console.ForegroundColor = oldc; } } + + public virtual void Exception(Exception e) { + if (e is System.Threading.ThreadAbortException) throw e; + lock (this) { + Errors++; + RedText("Error"); RedText(e.Message); + if (Job.Verbose) { RedText(e.StackTrace); } + System.Diagnostics.Debugger.Break(); + } + } + public virtual void Exception(FtpClient ftp, Exception e) { + if (e is System.Threading.ThreadAbortException) throw e; + if (ftp == null) Exception(e); + else { + lock (this) { + var prefix = Job.Connections.FTPTag(ftp.Index) + "! "; + Errors++; RedLabel(prefix); RedText("Error"); + var lines = e.Message.Split('\n'); + foreach (var line in lines) { RedLabel(prefix); RedText(line); } + if (Job.Verbose) { + lines = e.StackTrace.Split('\n'); + foreach (var line in lines) { RedLabel(prefix); RedText(line); } + } + System.Diagnostics.Debugger.Break(); + } + } + } + public virtual void Upload(string path, long size, TimeSpan time) { + if (Job.Quiet) Dot(); + else GreenText(string.Format("Uploaded {0} => {1} at {2:F3}/s.", path, Size(size), Size((long)(size / time.TotalSeconds + 0.5)))); + Uploads++; UploadSize += size; + } + public virtual void Download(string path, long size, TimeSpan time) { + if (Job.Quiet) Dot(); + else GreenText(string.Format("Downloaded {0} => {1} at {2:F3}/s.", path, Size(size), Size((long)(size / time.TotalSeconds + 0.5)))); + Downloads++; DownloadSize += size; + } + public virtual void Progress(string path, long size, long part, TimeSpan time) { + if (Job.Quiet) Dot(); + else GreenText(string.Format("Transfer of {0} => {1:F1}% at {2:F3}/s.", path, (part * 100.0 / size), Size((long)(part / time.TotalSeconds + 0.5)))); + } + + + public virtual void Summary(TimeSpan t) { + Text(""); + GreenText(string.Format("#### => {0} Files and {1} transfered in {2:F3} seconds at {3}/s. {4} Errors.", + Math.Max(Uploads, Downloads), Size(UploadSize + DownloadSize), t.TotalSeconds, Size((long)(Math.Max(UploadSize, DownloadSize) / t.TotalSeconds + 0.5)), Errors)); + Text(""); + Text(""); + Text(""); + + if (Job.LogFile != null) { + var log = new FileInfo(Job.LogFile); + if (log.Length > MaxLogSize) { + var loglines = File.ReadAllLines(Job.LogFile).ToList(); + loglines.RemoveRange(0, loglines.Count / 2); + File.WriteAllLines(Job.LogFile, loglines.ToArray()); + } + } + } + + } +} diff --git a/Source/MSBuild.Community.Tasks/Sync/Paths.cs b/Source/MSBuild.Community.Tasks/Sync/Paths.cs new file mode 100644 index 00000000..ed299de3 --- /dev/null +++ b/Source/MSBuild.Community.Tasks/Sync/Paths.cs @@ -0,0 +1,36 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace johnshope.Sync { + + public class Paths { + + private static bool MatchSingle(string pattern, string path) { + var star = pattern.IndexOf('*'); + var slash = path.LastIndexOf('/'); + if (!pattern.Contains('/') && slash != -1) path = path.Substring(slash+1); + if (star != -1) { + return (star == 0 || path.StartsWith(pattern.Substring(0, star))) && (star == pattern.Length-1 || path.EndsWith(pattern.Substring(star+1))); + } else { + return pattern == path; + } + } + /// + /// Checks wether the path matches one of a comma or semicolon separated list of file patterns or a single file pattern. + /// + /// A comma or semicolon separared list of patterns or a single pattern + /// The path to check. + /// True if one of the patterns matches the path. + public static bool Match(string patterns, string path) { + if (patterns == null || path == null) return false; + path = path.Replace('\\', '/'); + foreach (var p in patterns.Split(';', ',').Select(s => s.Trim()).Where(s => !string.IsNullOrEmpty(s))) { + if (MatchSingle(p, path)) return true; + } + return false; + } + + } +} diff --git a/Source/MSBuild.Community.Tasks/Sync/PipeStream.cs b/Source/MSBuild.Community.Tasks/Sync/PipeStream.cs new file mode 100644 index 00000000..ad96f873 --- /dev/null +++ b/Source/MSBuild.Community.Tasks/Sync/PipeStream.cs @@ -0,0 +1,241 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Threading; +using System.IO; + +namespace johnshope.Sync { + + enum StreamConsumer { Undefined, Reader, Writer } + + public class PipeStream: Stream { + + private object _lockForRead; + private object _lockForAll; + private Queue _chunks; + private object _currentChunk; + private long _currentChunkPosition; + private ManualResetEvent _doneWriting; + private ManualResetEvent _dataAvailable; + private WaitHandle[] _events; + private int _doneWritingHandleIndex; + private volatile bool _illegalToWrite; + private Exception _exception; + + [ThreadStatic] + private static StreamConsumer _consumer = StreamConsumer.Undefined; + + public PipeStream() { + _chunks = new Queue(); + _doneWriting = new ManualResetEvent(false); + _dataAvailable = new ManualResetEvent(false); + _events = new WaitHandle[] { _dataAvailable, _doneWriting }; + _doneWritingHandleIndex = 1; + _lockForRead = new object(); + _lockForAll = new object(); + } + + public override bool CanRead { get { return true; } } + public override bool CanSeek { get { return false; } } + public override bool CanWrite { get { return !_illegalToWrite; } } + + public override void Flush() { } + public override long Length { + get { throw new NotSupportedException(); } + } + public override long Position { + get { throw new NotSupportedException(); } + set { throw new NotSupportedException(); } + } + public override long Seek(long offset, SeekOrigin origin) { + throw new NotSupportedException(); + } + public override void SetLength(long value) { + throw new NotSupportedException(); + } + + public override int Read(byte[] buffer, int offset, int count) { + if (_consumer == StreamConsumer.Writer) throw new NotSupportedException("You cannot read & write to a BlockingStream from the same thread."); + _consumer = StreamConsumer.Reader; + + lock (_lockForAll) if (_exception != null) { var ex = _exception; _exception = null; throw ex; } + + if (buffer == null) throw new ArgumentNullException("buffer"); + if (offset < 0 || offset >= buffer.Length) + throw new ArgumentOutOfRangeException("offset"); + if (count < 0 || offset + count > buffer.Length) + throw new ArgumentOutOfRangeException("count"); + if (_dataAvailable == null) + throw new ObjectDisposedException(GetType().Name); + + if (count == 0) return 0; + + while (true) { + int handleIndex = WaitHandle.WaitAny(_events); + lock (_lockForRead) { + lock (_lockForAll) { + if (_currentChunk == null) { + if (_chunks.Count == 0) { + if (handleIndex == _doneWritingHandleIndex) + return 0; + else continue; + } + _currentChunk = _chunks.Dequeue(); + _currentChunkPosition = 0; + } + } + + if (_currentChunk is Stream) { + var read = ((Stream)_currentChunk).Read(buffer, offset, count); + if (read != count) { + _currentChunk = null; + _currentChunkPosition = 0; + lock (_lockForAll) { + if (_chunks.Count == 0) _dataAvailable.Reset(); + } + } else { + _currentChunkPosition += count; + } + return read; + + } else { + + long bytesAvailable = + ((_currentChunk is Stream) ? ((Stream)_currentChunk).Length : ((byte[])_currentChunk).Length) - _currentChunkPosition; + int bytesToCopy; + if (bytesAvailable > count) { + bytesToCopy = count; + Buffer.BlockCopy((byte[])_currentChunk, (int)_currentChunkPosition, buffer, offset, count); + _currentChunkPosition += count; + } else { + bytesToCopy = (int)bytesAvailable; + Buffer.BlockCopy((byte[])_currentChunk, (int)_currentChunkPosition, buffer, offset, bytesToCopy); + _currentChunk = null; + _currentChunkPosition = 0; + lock (_lockForAll) { + if (_chunks.Count == 0) _dataAvailable.Reset(); + } + } + return bytesToCopy; + } + } + } + } + + public virtual void Read(Stream stream) { + if (_consumer == StreamConsumer.Writer) throw new NotSupportedException("You cannot read & write to a BlockingStream from the same thread."); + _consumer = StreamConsumer.Reader; + + if (_dataAvailable == null) + throw new ObjectDisposedException(GetType().Name); + + lock (_lockForAll) if (_exception != null) { var ex = _exception; _exception = null; throw ex; } + + while (true) { + int handleIndex = WaitHandle.WaitAny(_events); + lock (_lockForRead) { + lock (_lockForAll) { + if (_currentChunk == null) { + if (_chunks.Count == 0) { + if (handleIndex == _doneWritingHandleIndex) + return; + else continue; + } + _currentChunk = _chunks.Dequeue(); + _currentChunkPosition = 0; + } + } + + if (_currentChunk is Stream) { + Streams.Copy((Stream)_currentChunk, stream); + } else { + var buffer = (byte[])_currentChunk; + stream.Write(buffer, (int)_currentChunkPosition, buffer.Length); + } + _currentChunk = null; + _currentChunkPosition = 0; + lock (_lockForAll) { + if (_chunks.Count == 0) _dataAvailable.Reset(); + } + } + } + } + + public override void Write(byte[] buffer, int offset, int count) { + if (_consumer == StreamConsumer.Reader) throw new NotSupportedException("You cannot read & write to a BlockingStream from the same thread."); + _consumer = StreamConsumer.Writer; + + lock(_lockForAll) if (_exception != null) { var ex = _exception; _exception = null; throw ex; } + + if (buffer == null) throw new ArgumentNullException("buffer"); + if (offset < 0 || offset >= buffer.Length) + throw new ArgumentOutOfRangeException("offset"); + if (count < 0 || offset + count > buffer.Length) + throw new ArgumentOutOfRangeException("count"); + if (_dataAvailable == null) + throw new ObjectDisposedException(GetType().Name); + + if (count == 0) return; + + byte[] chunk = new byte[count]; + Buffer.BlockCopy(buffer, offset, chunk, 0, count); + lock (_lockForAll) { + if (_illegalToWrite) + throw new InvalidOperationException( + "Writing has already been completed."); + + _chunks.Enqueue(chunk); + _dataAvailable.Set(); + } + } + + public virtual void Write(Stream stream) { + if (_consumer == StreamConsumer.Reader) throw new NotSupportedException("You cannot read & write to a BlockingStream from the same thread."); + _consumer = StreamConsumer.Writer; + + if (_dataAvailable == null) + throw new ObjectDisposedException(GetType().Name); + + lock (_lockForAll) { + if (_exception != null) { var ex = _exception; _exception = null; throw ex; } + if (_illegalToWrite) + throw new InvalidOperationException( + "Writing has already been completed."); + + _chunks.Enqueue(stream); + _dataAvailable.Set(); + } + } + + public void Exception(Exception ex) { lock(_lockForAll) _exception = ex; } + + public void SetEndOfStream() { + if (_consumer == StreamConsumer.Reader) throw new NotSupportedException("You cannot read & write to a BlockingStream from the same thread."); + if (_dataAvailable == null) + throw new ObjectDisposedException(GetType().Name); + lock (_lockForAll) { + _consumer = StreamConsumer.Undefined; + _illegalToWrite = true; + _doneWriting.Set(); + } + } + + public override void Close() { + if (_consumer == StreamConsumer.Reader) { + _consumer = StreamConsumer.Undefined; + base.Close(); + if (_dataAvailable != null) { + _dataAvailable.Close(); + _dataAvailable = null; + } + if (_doneWriting != null) { + _doneWriting.Close(); + _doneWriting = null; + } + } else if (_consumer == StreamConsumer.Writer) { + SetEndOfStream(); + //_consumer = StreamConsumer.Undefined; + } + } + } +} \ No newline at end of file diff --git a/Source/MSBuild.Community.Tasks/Sync/ResourceQueue.cs b/Source/MSBuild.Community.Tasks/Sync/ResourceQueue.cs new file mode 100644 index 00000000..665c8e30 --- /dev/null +++ b/Source/MSBuild.Community.Tasks/Sync/ResourceQueue.cs @@ -0,0 +1,74 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Web; +using System.Threading; + +namespace johnshope.Sync { + + public class ResourceQueue: System.Collections.Generic.LinkedList { + + AutoResetEvent signal = new AutoResetEvent(false); + + public void Enqueue(T entry) { + lock (this) { + var node = First; + while (node != null && node.Value != null) node = node.Next; + if (node == null) AddLast(entry); + else AddBefore(node, entry); + } + signal.Set(); + } + + public T Dequeue() { + lock (this) { + if (base.Count > 0) { + var entry = First; + RemoveFirst(); + return entry.Value; + } else return default(T); + } + } + + public T Dequeue(Func where) { + lock (this) { + if (base.Count > 0) { + var entry = First; + while (entry != null && entry.Value != null && !where(entry.Value)) entry = entry.Next; + if (entry == null) entry = First; + Remove(entry); + return entry.Value; + } else return default(T); + } + } + + + + public event EventHandler Blocking; + public event EventHandler Blocked; + + public T DequeueOrBlock() { + do { + lock (this) { + if (base.Count > 0) return Dequeue(); + } + if (Blocking != null) Blocking(this, EventArgs.Empty); + signal.WaitOne(); + if (Blocked != null) Blocked(this, EventArgs.Empty); + } while (true); + } + public T DequeueOrBlock(Func where) { + do { + lock (this) { + if (base.Count > 0) return Dequeue(where); + } + if (Blocking != null) Blocking(this, EventArgs.Empty); + signal.WaitOne(); + if (Blocked != null) Blocked(this, EventArgs.Empty); + } while (true); + } + + public bool IsEmpty { get { lock (this) return base.Count == 0; } } + public new int Count { get { lock (this) return base.Count; } } + } +} diff --git a/Source/MSBuild.Community.Tasks/Sync/SyncJob.cs b/Source/MSBuild.Community.Tasks/Sync/SyncJob.cs new file mode 100644 index 00000000..373d46e3 --- /dev/null +++ b/Source/MSBuild.Community.Tasks/Sync/SyncJob.cs @@ -0,0 +1,196 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace johnshope.Sync { + + public enum CopyMode { Update, Clone, Add } + + public class SyncJob { + + static readonly TimeSpan dt = TimeSpan.FromMinutes(1); // minimal file time resolution + + + public CopyMode Mode { get; set; } + public string LogFile { get; set; } + public bool Verbose { get; set; } + public bool Quiet { get; set; } + public Log Log = null; + public string ExcludePatterns { get; set; } + public Threads Threads = new Threads(); + public FtpConnections Connections; + + public class FailureInfo { public FileOrDirectory File; public Exception Exception; } + + public Queue Failures = new Queue(); + + public void Failure(FileOrDirectory file, Exception ex) { lock (Failures) Failures.Enqueue(new FailureInfo { File = file, Exception = ex }); Log.Exception(ex); } + public void Failure(FileOrDirectory file, Exception ex, FtpClient ftp) { lock (Failures) Failures.Enqueue(new FailureInfo { File = file, Exception = ex }); Log.Exception(ftp, ex); } + + public IDirectory Root(FileOrDirectory fd) { if (fd.Parent == null) return (IDirectory)fd; else return Root(fd.Parent); } + + public SyncJob() { + Log = new Log { Job = this }; + Connections = new FtpConnections { Job = this }; + ExcludePatterns = ""; + } + + List MyFailures(IDirectory sroot, IDirectory droot) { + List list = new List(); + + lock (Failures) { + int n = Failures.Count; + while (n-- > 0) { + var failure = Failures.Dequeue(); + var root = Root(failure.File); + if (root == sroot || root == droot) list.Add(failure); + else Failures.Enqueue(failure); + } + } + return list; + } + + public void RetryFailures(IDirectory sroot, IDirectory droot) { + + var list = MyFailures(sroot, droot); + + if (list.Count > 0) { + Log.YellowText("#### Retry failed transfers..."); + var set = new HashSet(); + foreach (var failure in list) { + IDirectory dir; + if (failure is IDirectory) dir = (IDirectory)failure.File; + else dir = (IDirectory)failure.File.Parent; + + if (!set.Contains(dir.Source)) { + set.Add(dir.Source); + Directory(dir.Source, dir.Destination); + } + } + + Log.YellowText("#### Summary of errors:"); + foreach (var failure in list) Log.Exception(failure.Exception); + + Log.YellowText("#### Failed transfers:"); + foreach (var failure in list) Log.RedText("Failed transfer: " + failure.File.RelativePath); + } + + list = MyFailures(sroot, droot); // dequeue recurrant failures. + } + + public class Counter { + public int N = 0; + } + + public void Directory(IDirectory sdir, IDirectory ddir) { + + if (ddir == null || sdir == null) return; + + sdir.Source = ddir.Source = sdir; + sdir.Destination = ddir.Destination = ddir; + + if (sdir is FtpDirectory) { + ((FtpDirectory)sdir).TransferProgress = true; + } + if (ddir is FtpDirectory) { + ((FtpDirectory)ddir).TransferProgress = true; + } + var slist = sdir.List().Where(file => !johnshope.Sync.Paths.Match(ExcludePatterns, file.RelativePath)).ToList(); + var dlist = ddir.List(); + //ddir.CreateDirectory(null); + + //Parallel.ForEach(list, new ParallelOptions { MaxDegreeOfParallelism = con }, + var tasks = new List(); + var n = new Counter(); + n.N = slist.Count; + var finished = (EventHandler)((sender, args) => { + lock(n) { + n.N--; + if (n.N <= 0) { + if (Mode != CopyMode.Add) { + foreach (var dest in dlist) + ddir.Delete(dest); + } + } + } + }); + foreach (var source in slist) { + var src = source; + var t = new Task(() => { + FileOrDirectory dest = null; + lock (dlist) { if (dlist.Contains(src.Name)) dest = dlist[src.Name]; } + if (dest != null && dest.Class != src.Class && (src.Changed > dest.Changed || Mode == CopyMode.Clone)) + ddir.Delete(dest); + if (src.Class == ObjectClass.File) { + /*if (Verbose && dest != null) { + johnshope.Sync.Log.CyanText(src.Name + ": " + src.Changed.ToShortDateString() + "-" + src.Changed.ToShortTimeString() + " => " + + dest.Changed.ToShortDateString() + "-" + dest.Changed.ToShortTimeString()); + }*/ + if (dest == null || ((Mode == CopyMode.Update || Mode == CopyMode.Add) && src.Changed > dest.Changed) || (Mode == CopyMode.Clone && (src.Changed > dest.Changed + dt))) { + using (var s = sdir.ReadFile(src)) { + ddir.WriteFile(s, src); + } + } + } else { + if (dest == null) Directory((IDirectory)src, ddir.CreateDirectory(src)); + else Directory((IDirectory)src, (IDirectory)dest); + } + lock (dlist) { dlist.Remove(src.Name); } + }); + tasks.Add(t); + t.Finished += finished; + Threads.Do(t); + } + if (slist.Count == 0) finished(this, EventArgs.Empty); + } + + public void Directory(Uri src, Uri dest) { + try { + var start = DateTime.Now; + Connections.Allocate(src); + Connections.Allocate(dest); + + int nsrc = 1, ndest = 1; + // messages + if (src.Scheme == "ftp" || src.Scheme == "ftps") { + var ftp = Connections.Open(ref src); + Log.Text("Source host: " + src.Authority + " Server Time:" + ftp.ServerTimeString); + Connections.Pass(ftp); + nsrc = Connections.Count(src); + } + if (dest.Scheme == "ftp" || dest.Scheme == "ftps") { + var ftp = Connections.Open(ref dest); + Log.Text("Destination host: " + dest.Authority + " Server Time:" + ftp.ServerTimeString); + Connections.Pass(ftp); + ndest = Connections.Count(dest); + } + Threads.N = Math.Max(nsrc, ndest); + + Log.Text(string.Format("Mode: {0}; Log: {1}; Verbose: {2}, Exclude: {3}", Mode, LogFile, Verbose, ExcludePatterns)); + Log.Text(""); + + if (!string.IsNullOrEmpty(LogFile)) ExcludePatterns += ";" + LogFile; + + var sdir = johnshope.Sync.Directory.Parse(src, this); + var ddir = johnshope.Sync.Directory.Parse(dest, this); + + Directory(sdir, ddir); + Threads.Await(); + + RetryFailures(sdir, ddir); + Threads.Await(); + + Connections.Close(); + + Log.Summary(DateTime.Now - start); + + Log.Flush(); + } catch (Exception ex) { + Log.Exception(ex); + } + Threads.Abort(); + + } + } +} diff --git a/Source/MSBuild.Community.Tasks/Sync/SyncTask.cs b/Source/MSBuild.Community.Tasks/Sync/SyncTask.cs new file mode 100644 index 00000000..fe03a368 --- /dev/null +++ b/Source/MSBuild.Community.Tasks/Sync/SyncTask.cs @@ -0,0 +1,56 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Resources; +using System.Text; +using Microsoft.Build.Utilities; +using Microsoft.Build.Framework; +using johnshope.Sync; + + +namespace MSBuild.Community.Tasks { + + public class FileSync: Microsoft.Build.Utilities.Task { + + protected FileSync() { } + + protected FileSync(ResourceManager taskResources) : base(taskResources) { } + + protected FileSync(ResourceManager taskResources, string helpKeywordPrefix) : base(taskResources, helpKeywordPrefix) { } + + [Required] + public ITaskItem[] Sources { get; set; } + + [Required] + public ITaskItem[] Destinations { get; set; } + + public string Mode { get; set; } + public bool Verbose { get; set; } + public bool Quiet { get; set; } + public string LogFile { get; set; } + public string Exclude { get; set; } + + public override bool Execute() { + if (Sources.Length != Destinations.Length) { + Log.LogMessage("Sync: Number of sources must match destinations."); + return false; + } + var job = new SyncJob(); + job.ExcludePatterns = Exclude; + job.LogFile = LogFile; + job.Mode = (CopyMode)Enum.Parse(typeof(CopyMode), Mode); + job.Quiet = Quiet; + job.Verbose = Verbose; + job.Log = new TaskLog(this); + + + for (int i = 0; i < Sources.Length; i++) { + var src = Sources[i].ItemSpec; + var dest = Destinations[i].ItemSpec; + Log.LogMessage("Syncing {0} to {1}...", src, dest); + job.Directory(new Uri(src), new Uri(dest)); + } + return true; + } + } +} diff --git a/Source/MSBuild.Community.Tasks/Sync/TaskLog.cs b/Source/MSBuild.Community.Tasks/Sync/TaskLog.cs new file mode 100644 index 00000000..fc83782c --- /dev/null +++ b/Source/MSBuild.Community.Tasks/Sync/TaskLog.cs @@ -0,0 +1,20 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using Microsoft.Build.Utilities; + +namespace johnshope.Sync { + + public class TaskLog: johnshope.Sync.Log { + + public TaskLog(Microsoft.Build.Utilities.Task task) { Task = task; } + + public Microsoft.Build.Utilities.Task Task { get; set; } + + public override void Text(string text) { + base.Text(text); + Task.Log.LogMessage(text); + } + } +} diff --git a/Source/MSBuild.Community.Tasks/Sync/Threads.cs b/Source/MSBuild.Community.Tasks/Sync/Threads.cs new file mode 100644 index 00000000..5700eaba --- /dev/null +++ b/Source/MSBuild.Community.Tasks/Sync/Threads.cs @@ -0,0 +1,116 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading; + +namespace johnshope.Sync { + + public class Task { + public bool IsFinished { get; private set; } + public ManualResetEvent Event = new ManualResetEvent(false); + public Task(Action body) { Body = body; IsFinished = false; } + public Action Body { get; set; } + public void Await() { + Event.WaitOne(); + } + public void Run() { + try { Body(); } catch { } + if (Finished != null) Finished(this, EventArgs.Empty); + IsFinished = true; + Event.Set(); + } + public event EventHandler Finished; + } + + public class Threads { + + public int N; + List> threads = new List>(); + List> items = new List>(); + ResourceQueue alltasks = new ResourceQueue(); + + void Schedule(object state) { + int level = (int)state; + do { + Task t = items[level].DequeueOrBlock(); + if (t != null) t.Run(); + } while (true); + } + + void Create(int level) { + if (level >= threads.Count) { + threads.Add(new List()); + items.Add(new ResourceQueue()); + } + var t = new Thread(Schedule); + threads[level].Add(t); + t.Name = "Private Thread Pool [" + level + ", " + (threads[level].Count - 1) + "]"; + t.Start(level); + } + + static Dictionary level = new Dictionary(); + static int Level { + get { + var t = Thread.CurrentThread; + if (level.ContainsKey(t)) return level[t]; + else level.Add(t, -1); + return -1; + } + set { + var t = Thread.CurrentThread; + if (level.ContainsKey(t)) level[t] = value; + else level.Add(t, value); + } + } + + public Task Do(Task t) { + alltasks.Enqueue(t); + int level = 0; + var direct = false; + lock (items) { + if (items.Count <= level) Create(level); + direct = items[level].Count >= N; + } + if (direct) t.Run(); + else { + lock (items) { + items[level].Enqueue(t); + if (items[level].Count > threads[level].Count) Create(level); + } + } + return t; + } + + public Task DoAsync(Action a) { + var t = new Task(a); + alltasks.Enqueue(t); + int level = 1; + lock (items) { + if (items.Count <= level) Create(level); + items[level].Enqueue(t); + while (items[level].Count > threads[level].Count) Create(level); + } + return t; + } + + public void Await() { + var t = alltasks.Dequeue(); + while (t != null) { + if (!t.IsFinished) t.Await(); + t = alltasks.Dequeue(); + } + } + + public void Abort() { + foreach (var level in threads) { + foreach (var t in level) t.Abort(); + } + } + + public void Do(Action a) { + var t = new Task(a); + Do(t); + } + } +} diff --git a/Source/MSBuild.Community.Tasks/Sync/Util.cs b/Source/MSBuild.Community.Tasks/Sync/Util.cs new file mode 100644 index 00000000..70f7fa8d --- /dev/null +++ b/Source/MSBuild.Community.Tasks/Sync/Util.cs @@ -0,0 +1,80 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.IO; +using System.Web; + +namespace johnshope.Sync { + + public static class Streams { + + const int Size = 8*1024; + + public static void Copy(Stream src, Stream dest) { + +#if NET4 + src.CopyTo(dest, Size); +#else + byte[] buffer = new byte[Size]; + + var len = src.Read(buffer, 0, Size); + while (len > 0) { + dest.Write(buffer, 0, len); + len = src.Read(buffer, 0, Size); + } +#endif + } + + public static string Path(this Uri url) { + string name = url.PathAndQuery; + int i = name.IndexOf("%3F"); + if (i >= 0) name = name.Substring(0, i); + return HttpUtility.UrlDecode(name); + } + + public static string PathWithSlash(this Uri url) { + string name = url.Path(); + if (!name.StartsWith("/")) name = "/" + name; + if (!name.EndsWith("/")) name = name + "/"; + return name; + } + + public static string File(this Uri url) { + string file = Path(url); + int i = file.LastIndexOf('/'); + if (i >= 0) file = file.Substring(i + 1); + return file; + } + + public static Hashtable Query(this Uri url) { + string query = url.PathAndQuery; + int i = query.IndexOf("%3F"); + if (i < 0) return new Hashtable(); + query = HttpUtility.UrlDecode(query.Substring(i+3)); + var parameters = query.Split('&'); + var table = new Hashtable(); + foreach (var p in parameters) { + var tokens = p.Split('='); + if (tokens.Length == 1) table[p] = true; + else table[tokens[0]] = tokens[1]; + } + return table; + } + + public static Uri Relative(this Uri url, string subpath) { + var path = url.PathWithSlash(); + if (subpath.StartsWith("/")) subpath = subpath.Substring(1); + path = path + subpath; + var query = url.PathAndQuery; + int i = query.IndexOf("%3F"); + if (i >= 0) query = query.Substring(i+3); + else query = url.Query; + var urlroot = url.Scheme + "://"; + if (!string.IsNullOrEmpty(url.UserInfo)) urlroot += url.UserInfo + "@"; + urlroot += url.Authority; + return new Uri(urlroot + path + "?" + query); + } + } +}