PageRenderTime 61ms CodeModel.GetById 29ms RepoModel.GetById 0ms app.codeStats 0ms

/src/tools/CollabUpload/Program.cs

https://bitbucket.org/cprieto/sharpsvn
C# | 504 lines | 414 code | 72 blank | 18 comment | 60 complexity | 4c491163d4256eed178c5e738ffa0e3b MD5 | raw file
Possible License(s): Apache-2.0
  1. // $Id$
  2. //
  3. // Copyright 2008-2009 The SharpSvn Project
  4. //
  5. // Licensed under the Apache License, Version 2.0 (the "License");
  6. // you may not use this file except in compliance with the License.
  7. // You may obtain a copy of the License at
  8. //
  9. // http://www.apache.org/licenses/LICENSE-2.0
  10. //
  11. // Unless required by applicable law or agreed to in writing, software
  12. // distributed under the License is distributed on an "AS IS" BASIS,
  13. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14. // See the License for the specific language governing permissions and
  15. // limitations under the License.
  16. using System;
  17. using System.Collections.Generic;
  18. using System.Text;
  19. using System.Net;
  20. using System.IO;
  21. using System.Xml;
  22. using System.Web;
  23. using QQn.TurtleUtils.IO;
  24. using System.Text.RegularExpressions;
  25. using System.Globalization;
  26. namespace CollabUpload
  27. {
  28. public class Program
  29. {
  30. class Args
  31. {
  32. public string Site;
  33. public int Folder;
  34. public string UserName;
  35. public string Password;
  36. public string Name;
  37. public string Status;
  38. public string Description;
  39. public string Result;
  40. public int MaxUploads;
  41. public TimeSpan Keep;
  42. public readonly List<string> Files = new List<string>();
  43. }
  44. class Document
  45. {
  46. public Uri DownloadUri;
  47. public string Name;
  48. public string Who;
  49. public string Status;
  50. public DateTime Date;
  51. public Uri ReserveUri;
  52. public string Description;
  53. public Uri EditUri;
  54. public int FileId;
  55. }
  56. static void Main(string[] args)
  57. {
  58. Args a = Parse(args);
  59. if (a != null)
  60. Run(a);
  61. }
  62. static Args Parse(string[] args)
  63. {
  64. Args aa = new Args();
  65. int i;
  66. for (i = 0; i < args.Length; i++)
  67. {
  68. string key = args[i];
  69. if (!string.IsNullOrEmpty(key) && key[0] == '-')
  70. {
  71. if (args[i] == "--")
  72. {
  73. i++;
  74. break;
  75. }
  76. bool argAvailable = i + 1 < args.Length;
  77. string value = argAvailable ? args[i + 1] : null;
  78. switch (key)
  79. {
  80. case "--site":
  81. if (!argAvailable)
  82. return ArgError("--host requires argument");
  83. aa.Site = value.Contains(".") ? value : string.Format("{0}.open.collab.net", value);
  84. i++;
  85. break;
  86. case "--folder":
  87. if (!argAvailable || !int.TryParse(value, out aa.Folder))
  88. return ArgError("--folder requires integer argument");
  89. i++;
  90. break;
  91. case "--username":
  92. if (!argAvailable)
  93. return ArgError("--username requires argument");
  94. aa.UserName = value;
  95. i++;
  96. break;
  97. case "--password":
  98. if (!argAvailable)
  99. return ArgError("--password requires argument");
  100. aa.Password = value;
  101. i++;
  102. break;
  103. case "--name":
  104. if (!argAvailable)
  105. return ArgError("--name requires argument");
  106. aa.Name = value;
  107. i++;
  108. break;
  109. case "--description":
  110. if (!argAvailable)
  111. return ArgError("--description requires argument");
  112. aa.Description = value;
  113. i++;
  114. break;
  115. case "--status":
  116. if (!argAvailable)
  117. return ArgError("--status requires argument");
  118. aa.Status = value;
  119. i++;
  120. break;
  121. case "--result":
  122. if (!argAvailable)
  123. return ArgError("--result requires argument");
  124. aa.Result = value;
  125. i++;
  126. break;
  127. case "--max-uploads":
  128. if (!argAvailable || !int.TryParse(value, out aa.MaxUploads))
  129. return ArgError("--max-uploads requires integer argument");
  130. i++;
  131. break;
  132. case "--keep":
  133. if (!argAvailable || !TimeSpan.TryParse(value, out aa.Keep))
  134. return ArgError("--keep requires timespan argument DD.hh:mm");
  135. i++;
  136. break;
  137. default:
  138. return ArgError(string.Format("Unknown argument: {0}", value));
  139. }
  140. }
  141. else
  142. aa.Files.Add(key);
  143. }
  144. while (i < args.Length)
  145. aa.Files.Add(args[i++]);
  146. return aa;
  147. }
  148. static private Args ArgError(string error)
  149. {
  150. Console.Error.WriteLine("Error: {0}", error);
  151. return null;
  152. }
  153. const string UserAgentName = "Mozilla/8.0 (compatible; Collab Uploader C#)";
  154. static bool Run(Args args)
  155. {
  156. CookieContainer cookBox = new CookieContainer();
  157. string requestUri = string.Format("http://{0}/servlets/ProjectDocumentAdd?folderID={1}", args.Site, args.Folder);
  158. HttpWebRequest wr = (HttpWebRequest)WebRequest.Create(requestUri);
  159. wr.UserAgent = UserAgentName;
  160. wr.CookieContainer = cookBox;
  161. string responseUri;
  162. string text;
  163. using (WebResponse response = wr.GetResponse())
  164. {
  165. responseUri = response.ResponseUri.ToString();
  166. text = GetText(response);
  167. }
  168. MaybeLogin(args, cookBox, requestUri, responseUri, ref text);
  169. // Ok, we are logged in; let's upload the files
  170. if (!string.IsNullOrEmpty(args.Result) && File.Exists(args.Result))
  171. File.Delete(args.Result);
  172. foreach (string file in args.Files)
  173. {
  174. Console.WriteLine("Uploading {0}", Path.GetFileName(file));
  175. wr = (HttpWebRequest)WebRequest.Create(requestUri + "&action=Add%20document");
  176. wr.UserAgent = UserAgentName;
  177. wr.CookieContainer = cookBox;
  178. wr.Timeout = 1800 * 1000; // Half an hour should be enough
  179. using (WebFormWriter wfw = new WebFormWriter(wr, WebRequestPostDataEncoding.MultipartFormData, "POST"))
  180. {
  181. wfw.AddValue("name", args.Name ?? Path.GetFileName(file));
  182. wfw.AddValue("status", args.Status ?? "Draft");
  183. wfw.AddValue("description", args.Description ?? "Description");
  184. wfw.AddValue("initiallylocked", "");
  185. wfw.AddValue("type", "file");
  186. wfw.AddValue("textFormat", "pre");
  187. wfw.AddFile("file", file);
  188. wfw.AddValue("docUrl", "");
  189. wfw.AddValue("submit", "submit");
  190. wfw.AddValue("maxDepth", "");
  191. }
  192. using (WebResponse response = wr.GetResponse())
  193. {
  194. text = GetText(response);
  195. if (text.Contains("\"error"))
  196. {
  197. Console.Error.WriteLine("Upload failed");
  198. return false;
  199. }
  200. // This page is currently not valid Xml; use regular expressions instead
  201. Regex re = new Regex("\\<a[^>]*href=\"(?<url>[^\"]*" + Regex.Escape(Path.GetFileName(file)) + ")\"[^>]*\\>"
  202. + "\\s*" + Regex.Escape(args.Name ?? Path.GetFileName(file)) + "\\s*</a>");
  203. Match m = re.Match(text);
  204. if (m.Success)
  205. {
  206. if (!string.IsNullOrEmpty(args.Result))
  207. File.AppendAllText(args.Result, new Uri(new Uri(requestUri), m.Groups["url"].Value).AbsoluteUri + Environment.NewLine);
  208. }
  209. else
  210. {
  211. Console.Error.WriteLine("Failed to retrieve url from upload output");
  212. Console.Error.WriteLine();
  213. Console.Error.WriteLine("=====================");
  214. Console.Error.WriteLine(text);
  215. Console.Error.WriteLine("=====================");
  216. }
  217. }
  218. }
  219. if (args.MaxUploads > 0 || args.Keep != TimeSpan.Zero)
  220. {
  221. List<Document> docs = GetDocumentList(args, cookBox);
  222. RemoveOnStatus(args, docs); // Filter all not interesting documents (status, locks)
  223. SortDocs(docs);
  224. if (args.MaxUploads > 0 && docs.Count > args.MaxUploads)
  225. {
  226. int from = docs.Count - args.MaxUploads;
  227. docs.RemoveRange(from, docs.Count - from);
  228. }
  229. if (args.Keep > TimeSpan.Zero)
  230. {
  231. FilterOnDate(docs, DateTime.Now.Date - args.Keep);
  232. }
  233. GC.KeepAlive(docs);
  234. foreach (Document doc in docs)
  235. {
  236. Console.WriteLine("Deleting {0}", doc.Name);
  237. DeleteFile(args, cookBox, doc.FileId);
  238. }
  239. }
  240. return true;
  241. }
  242. private static void RemoveOnStatus(Args args, List<Document> docs)
  243. {
  244. string filterStatus = args.Status ?? "Draft";
  245. docs.RemoveAll(delegate(Document d)
  246. {
  247. if (d.ReserveUri == null)
  248. return true; // Document is reserved
  249. else if (!string.Equals(filterStatus, d.Status, StringComparison.OrdinalIgnoreCase))
  250. return true; // Status doesn't match
  251. return false;
  252. });
  253. }
  254. private static void SortDocs(List<Document> docs)
  255. {
  256. docs.Sort(
  257. delegate(Document d1, Document d2)
  258. {
  259. return StringComparer.OrdinalIgnoreCase.Compare(d1.Name, d2.Name);
  260. });
  261. }
  262. private static void FilterOnDate(List<Document> docs, DateTime keepAfter)
  263. {
  264. docs.RemoveAll(
  265. delegate(Document d)
  266. {
  267. if (d.Date != DateTime.MinValue && d.Date >= keepAfter)
  268. return true;
  269. else
  270. return false;
  271. });
  272. }
  273. private static void MaybeLogin(Args args, CookieContainer cookBox, string requestUri, string responseUri, ref string text)
  274. {
  275. HttpWebRequest wr;
  276. if (responseUri != requestUri && responseUri.Contains("/Login"))
  277. {
  278. // Standard Collabnet login on secondary host..
  279. XmlDocument doc = new XmlDocument();
  280. XmlReaderSettings xs = new XmlReaderSettings();
  281. xs.ValidationFlags = System.Xml.Schema.XmlSchemaValidationFlags.None;
  282. xs.ValidationType = ValidationType.None;
  283. xs.ProhibitDtd = false;
  284. int nS = text.IndexOf("<html");
  285. if (nS > 0)
  286. text = text.Substring(nS).Replace("&nbsp;", "&#160;").Replace("&copy;", "&#169;");
  287. int lastForm = text.LastIndexOf("<form ", StringComparison.OrdinalIgnoreCase);
  288. if (lastForm >= 0)
  289. {
  290. int formClose = text.IndexOf("</form", lastForm, StringComparison.OrdinalIgnoreCase);
  291. if (formClose >= 0)
  292. text = text.Substring(lastForm, text.IndexOf('>', formClose) - lastForm+1);
  293. }
  294. doc.Load(XmlReader.Create(new StringReader(text), xs));
  295. XmlNamespaceManager nsmgr = new XmlNamespaceManager(doc.NameTable);
  296. nsmgr.AddNamespace("html", "http://www.w3.org/1999/xhtml");
  297. XmlElement form = (XmlElement)doc.SelectSingleNode("//html:form[@id='loginform']", nsmgr);
  298. if(form == null)
  299. form = (XmlElement)doc.SelectSingleNode("//form[@id='loginform']", nsmgr);
  300. StringBuilder sb = new StringBuilder();
  301. wr = (HttpWebRequest)WebRequest.Create(new Uri(new Uri(responseUri), new Uri(form.GetAttribute("action"))));
  302. wr.UserAgent = UserAgentName;
  303. wr.CookieContainer = cookBox;
  304. using (WebFormWriter wfw = new WebFormWriter(wr, WebRequestPostDataEncoding.WwwFormUrlEncoded, "POST"))
  305. {
  306. foreach (XmlElement el in form.SelectNodes(".//html:input[@name] | .//input[@name]", nsmgr))
  307. {
  308. string name = el.GetAttribute("name");
  309. switch (name.ToUpperInvariant())
  310. {
  311. case "LOGINID":
  312. wfw.AddValue(name, args.UserName);
  313. break;
  314. case "PASSWORD":
  315. wfw.AddValue(name, args.Password);
  316. break;
  317. default:
  318. string v = el.GetAttribute("value");
  319. wfw.AddValue(name, v ?? "");
  320. break;
  321. }
  322. }
  323. }
  324. using (WebResponse response = wr.GetResponse())
  325. {
  326. responseUri = response.ResponseUri.ToString();
  327. text = GetText(response);
  328. }
  329. if (responseUri != requestUri)
  330. {
  331. throw new InvalidOperationException("Invalid credentials specified");
  332. }
  333. }
  334. }
  335. private static List<Document> GetDocumentList(Args args, CookieContainer cookBox)
  336. {
  337. HttpWebRequest wr;
  338. string responseUri;
  339. string text;
  340. string requestUri = string.Format("http://{0}/servlets/ProjectDocumentList?folderID={1}", args.Site, args.Folder);
  341. wr = (HttpWebRequest)WebRequest.Create(requestUri);
  342. wr.UserAgent = "Mozilla/8.0 (compatible; Collab Uploader C#)";
  343. wr.CookieContainer = cookBox;
  344. using (WebResponse response = wr.GetResponse())
  345. {
  346. responseUri = response.ResponseUri.ToString();
  347. text = GetText(response);
  348. }
  349. string body = "([^<]|(\\<(a|div)[^>]*>[^<]*\\<\\/(a|div)>))*";
  350. Regex re = new Regex("\\<tr[^>]*>\\s*\\<td[^>]*>\\s*\\<div[^>]+class=\"leaf\"[^>]*\\>\\s*" +
  351. "\\<a href=\"(?<url>[^\"]*)\"[^>]*>\\s*(?<name>[^<]*)\\<\\/a\\>\\s*\\<\\/div\\>\\s*\\<\\/td>\\s*" +
  352. "\\<td[^>]*>\\s*(?<status>[^<]*)\\</td>\\s*" +
  353. "\\<td[^>]*>\\s*\\<(a)[^>]*>(?<who>[^<]*)<\\/a>\\s*on\\s(?<date>[^<]*)\\<\\/td>\\s*" +
  354. "\\<td[^>]*>" + body + "\\<\\/td>\\s*" +
  355. "\\<td[^>]*>\\s*[^<]*(\\<a[^>]*href=\"(?<reserveUrl>[^\"]*)\"[^>]*>\\s*Reserve[^<]*\\<\\/a>)?" + body + "\\<\\/td>\\s*" +
  356. "\\<td[^>]*>\\s*(?<desc>[^<]*)\\<\\/td>\\s*" +
  357. "\\<td[^>]*>\\s*(\\<a[^>]*href=\"(?<editUrl>[^\"]*)\"[^>]*>\\s*[a-zA-Z ]+\\s*\\<\\/a>)?" + body + "\\s*<\\/td>\\s*" +
  358. "\\<\\/tr\\>"
  359. , RegexOptions.Singleline | RegexOptions.ExplicitCapture | RegexOptions.IgnoreCase);
  360. List<Document> docs = new List<Document>();
  361. Uri baseUri = new Uri(string.Format("http://{0}/servlets/qq", args.Site));
  362. foreach (Match m in re.Matches(text))
  363. {
  364. string url = m.Groups["url"].Value.Trim();
  365. string name = m.Groups["name"].Value.Trim();
  366. string status = m.Groups["status"].Value.Trim();
  367. string who = m.Groups["who"].Value.Trim();
  368. string date = m.Groups["date"].Value.Trim();
  369. string reserveUrl = m.Groups["reserveUrl"].Value.Trim();
  370. string desc = m.Groups["desc"].Value.Trim();
  371. string editUrl = m.Groups["editUrl"].Value.Trim();
  372. Document d = new Document();
  373. d.DownloadUri = new Uri(baseUri, url);
  374. d.Name = name;
  375. d.Who = who;
  376. d.Status = status;
  377. d.ReserveUri = string.IsNullOrEmpty(reserveUrl) ? null : new Uri(baseUri, reserveUrl);
  378. d.Description = desc;
  379. d.EditUri = new Uri(baseUri, editUrl);
  380. if (!int.TryParse(editUrl.Substring(editUrl.IndexOf("documentID=") + 11), out d.FileId))
  381. continue;
  382. DateTime when;
  383. if (!string.IsNullOrEmpty(date) && DateTime.TryParse(date.Replace(" at "," "), out when))
  384. {
  385. d.Date = when;
  386. }
  387. docs.Add(d);
  388. }
  389. return docs;
  390. }
  391. private static void DeleteFile(Args args, CookieContainer cookBox, int fileId)
  392. {
  393. string requestUri = string.Format("http://{0}/servlets/ProjectDocumentDelete", args.Site);
  394. HttpWebRequest wr = (HttpWebRequest)WebRequest.Create(requestUri);
  395. wr.UserAgent = UserAgentName;
  396. wr.CookieContainer = cookBox;
  397. using (WebFormWriter wfw = new WebFormWriter(wr, WebRequestPostDataEncoding.WwwFormUrlEncoded, "POST"))
  398. {
  399. wfw.AddValue("documentID", fileId.ToString(CultureInfo.InvariantCulture));
  400. wfw.AddValue("Button", "Confirm delete");
  401. wfw.AddValue("maxDepth", "");
  402. }
  403. using (WebResponse response = wr.GetResponse())
  404. {
  405. if (!response.ResponseUri.AbsolutePath.EndsWith("/ProjectDocumentList", StringComparison.OrdinalIgnoreCase))
  406. throw new InvalidOperationException("Delete failed");
  407. }
  408. }
  409. static string GetText(WebResponse response)
  410. {
  411. using (StreamReader sr = new StreamReader(response.GetResponseStream()))
  412. {
  413. string text = sr.ReadToEnd();
  414. int nS;
  415. int nE;
  416. while (0 <= (nS = text.IndexOf("<script", StringComparison.OrdinalIgnoreCase))
  417. && (0 <= (nE = text.IndexOf("</script>", nS+1,StringComparison.OrdinalIgnoreCase))))
  418. {
  419. string del = text.Substring(nS, nE-nS+9);
  420. text = text.Remove(nS, nE-nS+9);
  421. }
  422. while (0 <= (nS = text.IndexOf("<!--", StringComparison.OrdinalIgnoreCase))
  423. && (0 <= (nE = text.IndexOf("-->", nS + 4, StringComparison.OrdinalIgnoreCase))))
  424. {
  425. string del = text.Substring(nS, nE - nS + 3);
  426. text = text.Remove(nS, nE - nS + 3);
  427. }
  428. return text;
  429. }
  430. }
  431. }
  432. }