Commits

mizipzor  committed 9778d97

Initial import.

  • Participants
  • Tags 0.0.0

Comments (0)

Files changed (10)

+syntax: glob
+bin/*
+obj/*
+_ReSharper*
+*.csproj.user
+obj
+packages
+*.ReSharper.user
+*.suo
+using Plossum.CommandLine;
+
+namespace Tcad
+{
+    [CommandLineManager(
+        EnabledOptionStyles = OptionStyles.Group | OptionStyles.LongUnix)]
+    internal sealed class Options
+    {
+        public Options()
+        {
+            BuildId = "lastFinished";
+            Glob = "*dll";
+        }
+
+        [CommandLineOption(
+            Name = "b",
+            Aliases = "build",
+            Description = "The build to download artifacts from (required)",
+            MinOccurs = 1)]
+        public string BuildType { get; set; }
+
+        [CommandLineOption(
+            Name = "i",
+            Aliases = "build-id",
+            Description = "The specific build to download (default: lastFinished)")]
+        public string BuildId { get; set; }
+
+        [CommandLineOption(
+            Name = "o",
+            Aliases = "outdir",
+            Description = "Directory to download files to (default: CWD)")]
+        public string OutDir { get; set; }
+
+        [CommandLineOption(
+            Name = "d",
+            Aliases = "dry-run",
+            Description = "Do not download any artifacts")]
+        public bool Dry { get; set; }
+
+        [CommandLineOption(
+            Name = "g",
+            Aliases = "glob",
+            Description = "Only download files matching glob expression (default: *dll)")]
+        public string Glob { get; set; }
+
+        [CommandLineOption(
+            Name = "v",
+            Aliases = "verbose",
+            Description = "Print every handled artifact")]
+        public bool Verbose { get; set; }
+
+        [CommandLineOption(
+            Name = "r",
+            Aliases = "recursive",
+            Description = "If true, keeps the artifact folder structure as seen on TeamCity")]
+        public bool Recursive { get; set; }
+
+        [CommandLineOption(
+            Name = "h",
+            Aliases = "help",
+            Description = "Displays this help text")]
+        public bool Help { get; set; }
+    }
+}
+using System;
+
+using Plossum.CommandLine;
+
+namespace Tcad
+{
+    internal class Program
+    {
+        private static void Main(string[] args)
+        {
+            Options options = new Options();
+            CommandLineParser parser = new CommandLineParser(options);
+            parser.Parse(args, false);
+
+            if (options.Help)
+            {
+                Console.WriteLine(parser.UsageInfo.GetHeaderAsString(79));
+                Console.WriteLine(parser.UsageInfo.GetOptionsAsString(79));
+                Environment.Exit(0);
+            }
+            else if (parser.HasErrors)
+            {
+                Console.WriteLine(parser.UsageInfo.GetErrorsAsString(79));
+                Environment.Exit(-1);
+            }
+            else
+            {
+                TeamCityArtifactDownloader tcad =
+                    new TeamCityArtifactDownloader(parser, options);
+                tcad.Run();
+            }
+        }
+    }
+}

File Properties/AssemblyInfo.cs

+using System.Reflection;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+
+// General Information about an assembly is controlled through the following 
+// set of attributes. Change these attribute values to modify the information
+// associated with an assembly.
+[assembly: AssemblyTitle("tcad")]
+
+// Setting ComVisible to false makes the types in this assembly not visible 
+// to COM components.  If you need to access a type in this assembly from 
+// COM, set the ComVisible attribute to true on that type.
+[assembly: ComVisible(false)]
+
+// The following GUID is for the ID of the typelib if this project is exposed to COM
+[assembly: Guid("99959fea-a9ab-44ea-9206-e54b8e65da63")]
+
+// Version information for an assembly consists of the following four values:
+//
+//      Major Version
+//      Minor Version 
+//      Build Number
+//      Revision
+//
+// You can specify all the values or you can default the Build and Revision Numbers 
+// by using the '*' as shown below:
+// [assembly: AssemblyVersion("1.0.*")]
+[assembly: AssemblyVersion("0.0.0.0")]

File TeamCityArtifactDownloader.cs

+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Net;
+using System.Text;
+using System.Xml;
+
+using Plossum.CommandLine;
+
+using tccli;
+
+namespace Tcad
+{
+    internal class TeamCityArtifactDownloader
+    {
+        private readonly CommandLineParser parser;
+        private readonly Options options;
+        private UriBuilder uriBuilder;
+        private string userName;
+        private string password;
+        private string outdir;
+
+        public TeamCityArtifactDownloader(CommandLineParser parser, Options options)
+        {
+            this.parser = parser;
+            this.options = options;
+        }
+
+        /// <summary>
+        /// Do magic.
+        /// </summary>
+        /// <returns>Exit code.</returns>
+        public int Run()
+        {
+            // make sure a host was specified
+            if (parser.RemainingArguments.Count < 1)
+            {
+                // TODO tell user how to specify host
+
+                return -1;
+            }
+
+            // enforce trailing separator for simplicity
+            outdir = options.OutDir ?? Directory.GetCurrentDirectory();
+            if (!outdir.EndsWith(Path.DirectorySeparatorChar.ToString()))
+            {
+                outdir += Path.DirectorySeparatorChar;
+            }
+
+            uriBuilder = new UriBuilder(parser.RemainingArguments[0]);
+            userName = uriBuilder.UserName;
+            password = uriBuilder.Password;
+
+            // TODO if password is not supplied, go interactive
+
+            // go through every artifact
+            foreach (string fileName in GetArtifacts())
+            {
+                // filter on glob
+                if (string.IsNullOrEmpty(options.Glob) ||
+                    fileName.Glob(options.Glob))
+                {
+                    DownloadArtifact(fileName);
+                }
+            }
+            return 0;
+        }
+
+        /// <summary>
+        /// Download a single artifact, respecting output options.
+        /// </summary>
+        /// <param name="artifactPath">The full path of the artifact.</param>
+        public void DownloadArtifact(string artifactPath)
+        {
+            string localPath = outdir;
+
+            // teamcity uses forward slashes in paths
+            if (artifactPath.Contains("/"))
+            {
+                //localPath += options.Recursive
+                //    ? artifactPath.Replace('/', Path.DirectorySeparatorChar)
+                //    : artifactPath.Split('/').Last();
+                artifactPath = artifactPath.Replace(
+                    '/', Path.DirectorySeparatorChar);
+            }
+            localPath += options.Recursive
+                ? artifactPath
+                : artifactPath.Split(Path.DirectorySeparatorChar).Last();
+
+            // make sure output directory exists
+            string directory = Path.GetDirectoryName(localPath);
+            if (!string.IsNullOrEmpty(directory))
+            {
+                if (!Directory.Exists(directory))
+                {
+                    Directory.CreateDirectory(directory);
+                }
+            }
+
+            // download the file
+            if (!options.Dry)
+            {
+                string filePath = CreateWebRequestUri(artifactPath);
+                HttpWebRequest request = CreateWebRequest(filePath);
+                Utils.DownloadFile(request, localPath);
+            }
+            if (options.Verbose)
+            {
+                Console.WriteLine(artifactPath);
+            }
+        }
+
+        /// <summary>
+        /// Returns the full path of each artifact, including file extension.
+        /// </summary>
+        /// <returns></returns>
+        public IEnumerable<string> GetArtifacts()
+        {
+            List<string> list = new List<string>();
+            using (MemoryStream stream = new MemoryStream())
+            {
+                string path = CreateWebRequestUri("teamcity-ivy.xml");
+                HttpWebRequest request = CreateWebRequest(path);
+                Utils.DownloadFile(request, stream);
+                stream.Seek(0, SeekOrigin.Begin);
+                XmlTextReader reader = new XmlTextReader(stream);
+                while (reader.Read())
+                {
+                    if (reader.NodeType != XmlNodeType.Element ||
+                        reader.Name != "artifact")
+                    {
+                        continue;
+                    }
+                    string name = reader.GetAttribute("name");
+                    string ext = reader.GetAttribute("ext");
+                    list.Add(string.Format("{0}.{1}", name, ext));
+                }
+            }
+            return list;
+        }
+
+        private string CreateWebRequestUri(string file)
+        {
+            string path = string.Format(
+                "httpAuth/repository/download/{0}/{1}/{2}",
+                options.BuildType,
+                options.BuildId,
+                file);
+            return uriBuilder.Uri + path;
+        }
+
+        private string CreateAuthInfo()
+        {
+            string authInfo = userName + ":" + password;
+            authInfo = Convert.ToBase64String(Encoding.Default.GetBytes(authInfo));
+            return authInfo;
+        }
+
+        private HttpWebRequest CreateWebRequest(string uri)
+        {
+            HttpWebRequest request = (HttpWebRequest)WebRequest.Create(uri);
+            //request.Proxy = null;
+            string authInfo = CreateAuthInfo();
+            request.Headers["Authorization"] = "Basic " + authInfo;
+            return request;
+        }
+    }
+}
+using System;
+using System.IO;
+using System.Net;
+using System.Text.RegularExpressions;
+
+namespace tccli
+{
+    public static class Utils
+    {
+        /// <summary>
+        /// Download file to memory and return content as a string.
+        /// </summary>
+        /// <param name="request"></param>
+        /// <returns></returns>
+        public static string DownloadFile(WebRequest request)
+        {
+            using (var stream = new MemoryStream())
+            {
+                // write to stream 
+                DownloadFile(request, stream);
+                // Jump to the start position of the stream
+                stream.Seek(0, SeekOrigin.Begin);
+
+                StreamReader reader = new StreamReader(stream);
+                return reader.ReadToEnd();
+            }
+        }
+
+        /// <summary>
+        /// Download file from TeamCity to local file.
+        /// </summary>
+        /// <param name="request"></param>
+        /// <param name="localFilename"></param>
+        public static void DownloadFile(
+            WebRequest request,
+            string localFilename)
+        {
+            Stream localStream;
+
+            // Create the local file
+            if (File.Exists(localFilename))
+            {
+                localStream = File.Open(
+                    localFilename, FileMode.Truncate);
+            }
+            else
+            {
+                localStream = File.Create(localFilename);
+            }
+
+            DownloadFile(request, localStream);
+            localStream.Close();
+        }
+
+        /// <summary>
+        /// From http://www.codeguru.com/columns/dotnettips/article.php/c7005/Downloading-Files-with-the-WebRequest-and-WebResponse-Classes.htm
+        /// </summary>
+        /// <param name="request"></param>
+        /// <param name="localStream"></param>
+        /// <returns></returns>
+        public static int DownloadFile(
+            WebRequest request,
+            Stream localStream)
+        {
+            // Function will return the number of bytes processed
+            // to the caller. Initialize to 0 here.
+            int bytesProcessed = 0;
+
+            // Assign values to these objects here so that they can
+            // be referenced in the finally block
+            Stream remoteStream = null;
+            WebResponse response = null;
+
+            // Use a try/catch/finally block as both the WebRequest and Stream
+            // classes throw exceptions upon error
+            try
+            {
+                // Create a request for the specified remote file name
+                //WebRequest request = WebRequest.Create(remoteFilename);
+                if (request != null)
+                {
+                    // Send the request to the server and retrieve the
+                    // WebResponse object 
+                    response = request.GetResponse();
+                    if (response != null)
+                    {
+                        //Console.WriteLine(string.Format("Download: {0}", request.RequestUri));
+
+                        // Once the WebResponse object has been retrieved,
+                        // get the stream object associated with the response's data
+                        remoteStream = response.GetResponseStream();
+
+                        if (remoteStream != null)
+                        {
+                            // Allocate a 1k buffer
+                            byte[] buffer = new byte[1024];
+                            int bytesRead;
+
+                            // Simple do/while loop to read from stream until
+                            // no bytes are returned
+                            do
+                            {
+                                // Read data (up to 1k) from the stream
+                                bytesRead = remoteStream.Read(buffer, 0, buffer.Length);
+
+                                // Write the data to the local file
+                                localStream.Write(buffer, 0, bytesRead);
+
+                                // Increment total bytes processed
+                                bytesProcessed += bytesRead;
+                            }
+                            while (bytesRead > 0);
+                        }
+                    }
+                }
+
+                //var sr = new StreamReader(localStream);
+                //var myStr = sr.ReadToEnd();
+                //return myStr;
+            }
+            catch (Exception e)
+            {
+                Console.WriteLine(e.Message);
+            }
+            finally
+            {
+                // Close the response and streams objects here 
+                // to make sure they're closed even if an exception
+                // is thrown at some point
+                if (response != null)
+                {
+                    response.Close();
+                }
+                if (remoteStream != null)
+                {
+                    remoteStream.Close();
+                }
+            }
+
+            // Return total bytes processed to caller.
+            return bytesProcessed;
+        }
+
+        /// <summary>
+        /// Compares the string against a given pattern.
+        /// </summary>
+        /// <param name="str">The string.</param>
+        /// <param name="wildcard">The wildcard, where "*" means any sequence of characters, and "?" means any single character.</param>
+        /// <returns><c>true</c> if the string matches the given pattern; otherwise <c>false</c>.</returns>
+        public static bool Glob(this string str, string wildcard)
+        {
+            return new Regex(
+                "^" + Regex.Escape(wildcard).Replace(@"\*", ".*").Replace(@"\?", ".") + "$",
+                RegexOptions.IgnoreCase | RegexOptions.Singleline
+            ).IsMatch(str);
+        }
+    }
+}

File install_dependencies.bat

+nuget install packages.config -SolutionDirectory .

File packages.config

+<?xml version="1.0" encoding="utf-8"?>
+<packages>
+  <package id="C5" version="1.0.2.0" targetFramework="net40-Client" />
+  <package id="Plossum.CommandLine" version="0.3.0.14" targetFramework="net40-Client" />
+</packages>
+<?xml version="1.0" encoding="utf-8"?>
+<Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+  <PropertyGroup>
+    <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
+    <Platform Condition=" '$(Platform)' == '' ">x86</Platform>
+    <ProductVersion>8.0.30703</ProductVersion>
+    <SchemaVersion>2.0</SchemaVersion>
+    <ProjectGuid>{A0BAD428-255B-46F1-8C92-E3D3DAF2E5D0}</ProjectGuid>
+    <OutputType>Exe</OutputType>
+    <AppDesignerFolder>Properties</AppDesignerFolder>
+    <RootNamespace>tcad</RootNamespace>
+    <AssemblyName>tcad</AssemblyName>
+    <TargetFrameworkVersion>v4.0</TargetFrameworkVersion>
+    <TargetFrameworkProfile>Client</TargetFrameworkProfile>
+    <FileAlignment>512</FileAlignment>
+  </PropertyGroup>
+  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|x86' ">
+    <PlatformTarget>x86</PlatformTarget>
+    <DebugSymbols>true</DebugSymbols>
+    <DebugType>full</DebugType>
+    <Optimize>false</Optimize>
+    <OutputPath>bin\Debug\</OutputPath>
+    <DefineConstants>DEBUG;TRACE</DefineConstants>
+    <ErrorReport>prompt</ErrorReport>
+    <WarningLevel>4</WarningLevel>
+  </PropertyGroup>
+  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|x86' ">
+    <PlatformTarget>x86</PlatformTarget>
+    <DebugType>pdbonly</DebugType>
+    <Optimize>true</Optimize>
+    <OutputPath>bin\Release\</OutputPath>
+    <DefineConstants>TRACE</DefineConstants>
+    <ErrorReport>prompt</ErrorReport>
+    <WarningLevel>4</WarningLevel>
+  </PropertyGroup>
+  <ItemGroup>
+    <Reference Include="C5">
+      <HintPath>packages\C5.1.0.2.0\lib\net40\C5.dll</HintPath>
+    </Reference>
+    <Reference Include="Plossum CommandLine">
+      <HintPath>packages\Plossum.CommandLine.0.3.0.14\lib\net40\Plossum CommandLine.dll</HintPath>
+    </Reference>
+    <Reference Include="System" />
+    <Reference Include="System.Core" />
+    <Reference Include="System.Xml.Linq" />
+    <Reference Include="System.Data.DataSetExtensions" />
+    <Reference Include="Microsoft.CSharp" />
+    <Reference Include="System.Data" />
+    <Reference Include="System.Xml" />
+  </ItemGroup>
+  <ItemGroup>
+    <Compile Include="Options.cs" />
+    <Compile Include="Program.cs" />
+    <Compile Include="Properties\AssemblyInfo.cs" />
+    <Compile Include="TeamCityArtifactDownloader.cs" />
+    <Compile Include="Utils.cs" />
+  </ItemGroup>
+  <ItemGroup>
+    <None Include="packages.config" />
+  </ItemGroup>
+  <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
+  <!-- To modify your build process, add your task inside one of the targets below and uncomment it. 
+       Other similar extension points exist, see Microsoft.Common.targets.
+  <Target Name="BeforeBuild">
+  </Target>
+  <Target Name="AfterBuild">
+  </Target>
+  -->
+</Project>
+
+Microsoft Visual Studio Solution File, Format Version 11.00
+# Visual Studio 2010
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "tcad", "tcad.csproj", "{A0BAD428-255B-46F1-8C92-E3D3DAF2E5D0}"
+EndProject
+Global
+	GlobalSection(SolutionConfigurationPlatforms) = preSolution
+		Debug|x86 = Debug|x86
+		Release|x86 = Release|x86
+	EndGlobalSection
+	GlobalSection(ProjectConfigurationPlatforms) = postSolution
+		{A0BAD428-255B-46F1-8C92-E3D3DAF2E5D0}.Debug|x86.ActiveCfg = Debug|x86
+		{A0BAD428-255B-46F1-8C92-E3D3DAF2E5D0}.Debug|x86.Build.0 = Debug|x86
+		{A0BAD428-255B-46F1-8C92-E3D3DAF2E5D0}.Release|x86.ActiveCfg = Release|x86
+		{A0BAD428-255B-46F1-8C92-E3D3DAF2E5D0}.Release|x86.Build.0 = Release|x86
+	EndGlobalSection
+	GlobalSection(SolutionProperties) = preSolution
+		HideSolutionNode = FALSE
+	EndGlobalSection
+EndGlobal