PageRenderTime 37ms CodeModel.GetById 13ms RepoModel.GetById 0ms app.codeStats 0ms

/src/SystemLayer/VistaFileOpenDialog.cs

https://bitbucket.org/tuldok89/openpdn
C# | 510 lines | 391 code | 92 blank | 27 comment | 33 complexity | 59e6f3117288f27f44b81d39db409446 MD5 | raw file
  1. /////////////////////////////////////////////////////////////////////////////////
  2. // Paint.NET //
  3. // Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. //
  4. // Portions Copyright (C) Microsoft Corporation. All Rights Reserved. //
  5. // See src/Resources/Files/License.txt for full licensing and attribution //
  6. // details. //
  7. // . //
  8. /////////////////////////////////////////////////////////////////////////////////
  9. using System;
  10. using System.Collections.Generic;
  11. using System.ComponentModel;
  12. using System.IO;
  13. using System.Net;
  14. using System.Runtime.InteropServices;
  15. using System.Threading;
  16. using PaintDotNet.Base;
  17. namespace PaintDotNet.SystemLayer
  18. {
  19. internal sealed class VistaFileOpenDialog
  20. : VistaFileDialog,
  21. IFileOpenDialog,
  22. NativeInterfaces.IFileDialogEvents
  23. {
  24. private CancelableTearOff _cancelSink;
  25. private sealed class CancelableTearOff
  26. : ICancelable
  27. {
  28. private volatile bool _canceled;
  29. public bool Canceled
  30. {
  31. get
  32. {
  33. return _canceled;
  34. }
  35. }
  36. public void RequestCancel()
  37. {
  38. _canceled = true;
  39. }
  40. }
  41. private string[] _fileNames;
  42. private NativeInterfaces.IFileOpenDialog FileOpenDialog
  43. {
  44. get
  45. {
  46. return FileDialog as NativeInterfaces.IFileOpenDialog;
  47. }
  48. }
  49. public bool CheckFileExists
  50. {
  51. get
  52. {
  53. return GetOptions(NativeConstants.FOS.FOS_FILEMUSTEXIST);
  54. }
  55. set
  56. {
  57. SetOptions(NativeConstants.FOS.FOS_FILEMUSTEXIST, value);
  58. }
  59. }
  60. public bool Multiselect
  61. {
  62. get
  63. {
  64. return GetOptions(NativeConstants.FOS.FOS_ALLOWMULTISELECT);
  65. }
  66. set
  67. {
  68. SetOptions(NativeConstants.FOS.FOS_ALLOWMULTISELECT, value);
  69. }
  70. }
  71. public string[] FileNames
  72. {
  73. get
  74. {
  75. return (string[])(_fileNames ?? new string[0]).Clone();
  76. }
  77. private set
  78. {
  79. _fileNames = (string[])value.Clone();
  80. }
  81. }
  82. public VistaFileOpenDialog()
  83. : base(new NativeInterfaces.NativeFileOpenDialog())
  84. {
  85. FileDialogEvents = this;
  86. }
  87. int NativeInterfaces.IFileDialogEvents.OnFileOk(NativeInterfaces.IFileDialog pfd)
  88. {
  89. int hr = NativeConstants.S_OK;
  90. NativeInterfaces.IShellItemArray results;
  91. FileOpenDialog.GetResults(out results);
  92. uint count;
  93. results.GetCount(out count);
  94. var items = new List<NativeInterfaces.IShellItem>();
  95. var needLocalCopy = new List<NativeInterfaces.IShellItem>();
  96. var cannotCopy = new List<NativeInterfaces.IShellItem>();
  97. var localPathNames = new List<string>();
  98. for (uint i = 0; i < count; ++i)
  99. {
  100. NativeInterfaces.IShellItem item;
  101. results.GetItemAt(i, out item);
  102. items.Add(item);
  103. }
  104. foreach (NativeInterfaces.IShellItem item in items)
  105. {
  106. // If it's a file system object, nothing special needs to be done.
  107. NativeConstants.SFGAO sfgaoAttribs;
  108. item.GetAttributes((NativeConstants.SFGAO)0xffffffff, out sfgaoAttribs);
  109. if ((sfgaoAttribs & NativeConstants.SFGAO.SFGAO_FILESYSTEM) == NativeConstants.SFGAO.SFGAO_FILESYSTEM)
  110. {
  111. string pathName;
  112. item.GetDisplayName(NativeConstants.SIGDN.SIGDN_FILESYSPATH, out pathName);
  113. localPathNames.Add(pathName);
  114. }
  115. else if ((sfgaoAttribs & NativeConstants.SFGAO.SFGAO_STREAM) == NativeConstants.SFGAO.SFGAO_STREAM)
  116. {
  117. needLocalCopy.Add(item);
  118. }
  119. else
  120. {
  121. cannotCopy.Add(item);
  122. }
  123. }
  124. Marshal.ReleaseComObject(results);
  125. results = null;
  126. if (needLocalCopy.Count > 0)
  127. {
  128. IntPtr hwnd = IntPtr.Zero;
  129. var oleWindow = (NativeInterfaces.IOleWindow)pfd;
  130. oleWindow.GetWindow(out hwnd);
  131. var win32Window = new Win32Window(hwnd, oleWindow);
  132. IFileTransferProgressEvents progressEvents = FileDialogUICallbacks.CreateFileTransferProgressEvents();
  133. ThreadStart copyThreadProc =
  134. delegate
  135. {
  136. try
  137. {
  138. progressEvents.SetItemCount(needLocalCopy.Count);
  139. for (int i = 0; i < needLocalCopy.Count; ++i)
  140. {
  141. NativeInterfaces.IShellItem item = needLocalCopy[i];
  142. string pathName;
  143. progressEvents.SetItemOrdinal(i);
  144. CopyResult result = CreateLocalCopy(item, progressEvents, out pathName);
  145. switch (result)
  146. {
  147. case CopyResult.Success:
  148. localPathNames.Add(pathName);
  149. break;
  150. case CopyResult.Skipped:
  151. break;
  152. case CopyResult.CancelOperation:
  153. hr = NativeConstants.S_FALSE;
  154. break;
  155. default:
  156. throw new InvalidEnumArgumentException();
  157. }
  158. }
  159. }
  160. finally
  161. {
  162. OperationResult result = hr == NativeConstants.S_OK ? OperationResult.Finished : OperationResult.Canceled;
  163. progressEvents.EndOperation(result);
  164. }
  165. };
  166. var copyThread = new Thread(copyThreadProc);
  167. copyThread.SetApartmentState(ApartmentState.STA);
  168. EventHandler onUIShown =
  169. (sender, e) => copyThread.Start();
  170. _cancelSink = new CancelableTearOff();
  171. progressEvents.BeginOperation(win32Window, onUIShown, _cancelSink);
  172. _cancelSink = null;
  173. copyThread.Join();
  174. Marshal.ReleaseComObject(oleWindow);
  175. oleWindow = null;
  176. }
  177. FileNames = localPathNames.ToArray();
  178. // If they selected a bunch of files, and then they all errored or something, then don't proceed.
  179. if (FileNames.Length == 0)
  180. {
  181. hr = NativeConstants.S_FALSE;
  182. }
  183. foreach (NativeInterfaces.IShellItem item in items)
  184. {
  185. Marshal.ReleaseComObject(item);
  186. }
  187. items.Clear();
  188. items = null;
  189. GC.KeepAlive(pfd);
  190. return hr;
  191. }
  192. private enum CopyResult
  193. {
  194. Success,
  195. Skipped,
  196. CancelOperation
  197. }
  198. // Returns true if the item copied successfully, false if it didn't (error or skipped)
  199. private CopyResult CreateLocalCopy(
  200. NativeInterfaces.IShellItem item,
  201. IProgressEvents<string> progressEvents,
  202. out string pathNameResult)
  203. {
  204. CopyResult returnResult;
  205. WorkItemResult itemResult;
  206. string displayName;
  207. item.GetDisplayName(NativeConstants.SIGDN.SIGDN_NORMALDISPLAY, out displayName);
  208. progressEvents.SetItemInfo(displayName);
  209. progressEvents.BeginItem();
  210. while (true)
  211. {
  212. // Determine whether to copy from HTTP or from IStream. The heuristic we use here is simple:
  213. // if the attributes has SFGAO_CANCOPY, we IStream it. Else, we HTTP it.
  214. NativeConstants.SFGAO attribs;
  215. item.GetAttributes((NativeConstants.SFGAO)0xfffffff, out attribs);
  216. try
  217. {
  218. if ((attribs & NativeConstants.SFGAO.SFGAO_CANCOPY) == NativeConstants.SFGAO.SFGAO_CANCOPY)
  219. {
  220. CreateLocalCopyFromIStreamSource(item, progressEvents, out pathNameResult);
  221. }
  222. else
  223. {
  224. CreateLocalCopyFromHttpSource(item, progressEvents, out pathNameResult);
  225. }
  226. returnResult = CopyResult.Success;
  227. itemResult = WorkItemResult.Finished;
  228. break;
  229. }
  230. catch (OperationCanceledException)
  231. {
  232. returnResult = CopyResult.CancelOperation;
  233. itemResult = WorkItemResult.Skipped;
  234. pathNameResult = null;
  235. break;
  236. }
  237. catch (Exception ex)
  238. {
  239. WorkItemFailureAction choice = progressEvents.ReportItemFailure(ex);
  240. if (choice == WorkItemFailureAction.SkipItem)
  241. {
  242. pathNameResult = null;
  243. returnResult = CopyResult.Skipped;
  244. itemResult = WorkItemResult.Skipped;
  245. break;
  246. }
  247. if (choice == WorkItemFailureAction.RetryItem)
  248. {
  249. continue;
  250. }
  251. if (choice == WorkItemFailureAction.CancelOperation)
  252. {
  253. pathNameResult = null;
  254. returnResult = CopyResult.CancelOperation;
  255. itemResult = WorkItemResult.Skipped;
  256. break;
  257. }
  258. }
  259. }
  260. progressEvents.EndItem(itemResult);
  261. return returnResult;
  262. }
  263. private void CreateLocalCopyFromHttpSource(
  264. NativeInterfaces.IShellItem item,
  265. IProgressEvents<string> progressEvents,
  266. out string pathNameResult)
  267. {
  268. string url;
  269. item.GetDisplayName(NativeConstants.SIGDN.SIGDN_URL, out url);
  270. var uri = new Uri(url);
  271. string pathName = FileSystem.GetTempPathName(url);
  272. WebRequest webRequest = WebRequest.Create(uri);
  273. webRequest.Timeout = 5000;
  274. using (WebResponse webResponse = webRequest.GetResponse())
  275. {
  276. VerifyNotCanceled();
  277. if (webResponse != null)
  278. using (Stream uriStream = webResponse.GetResponseStream())
  279. {
  280. VerifyNotCanceled();
  281. using (var outStream = new FileStream(pathName, FileMode.Create, FileAccess.Write, FileShare.Read))
  282. {
  283. VerifyNotCanceled();
  284. const int bufSize = 512;
  285. long length = webResponse.ContentLength;
  286. long bytesLeft = length;
  287. var buffer = new byte[bufSize];
  288. progressEvents.SetItemWorkTotal(length);
  289. while (bytesLeft > 0)
  290. {
  291. if (uriStream != null)
  292. {
  293. int amtRead = uriStream.Read(buffer, 0, buffer.Length);
  294. VerifyNotCanceled();
  295. outStream.Write(buffer, 0, amtRead);
  296. VerifyNotCanceled();
  297. bytesLeft -= amtRead;
  298. }
  299. progressEvents.SetItemWorkProgress(length - bytesLeft);
  300. VerifyNotCanceled();
  301. }
  302. }
  303. }
  304. }
  305. pathNameResult = pathName;
  306. }
  307. private unsafe void CreateLocalCopyFromIStreamSource(
  308. NativeInterfaces.IShellItem item,
  309. IProgressEvents<string> progressEvents,
  310. out string pathNameResult)
  311. {
  312. string fileName;
  313. item.GetDisplayName(NativeConstants.SIGDN.SIGDN_NORMALDISPLAY, out fileName);
  314. string pathName = FileSystem.GetTempPathName(fileName);
  315. Guid bhidStream = NativeConstants.BHID_Stream;
  316. var iidIStream = new Guid(NativeConstants.IID_IStream);
  317. NativeInterfaces.IStream iStream;
  318. item.BindToHandler(IntPtr.Zero, ref bhidStream, ref iidIStream, out iStream);
  319. try
  320. {
  321. VerifyNotCanceled();
  322. NativeStructs.STATSTG statstg;
  323. iStream.Stat(out statstg, NativeConstants.STATFLAG.STATFLAG_NONAME);
  324. progressEvents.SetItemWorkTotal((long)statstg.cbSize);
  325. const int bufSize = 4096;
  326. var buffer = new byte[bufSize];
  327. fixed (void* pbBuffer = buffer)
  328. {
  329. var pbBuffer2 = new IntPtr(pbBuffer);
  330. ulong qwBytesLeft = statstg.cbSize;
  331. using (var localFile = new FileStream(pathName, FileMode.Create, FileAccess.Write, FileShare.Read))
  332. {
  333. VerifyNotCanceled();
  334. // NOTE: We do not call VerifyNotCanceled() during any individual item. This is because, while testing
  335. // this, it was determined that oftentimes the transfer gets very confused if we just stop
  336. // calling Read() and jump straight to Marshal.ReleaseComObject(). By "confused" I mean that
  337. // the "Canceling..." text would remain on the progress dialog for up to a minute, and then
  338. // the blinking light on the camera would continue indefinitely and you wouldn't be able to
  339. // use the camera again until you unplugged it and plugged it back in.
  340. while (qwBytesLeft > 0)
  341. {
  342. var wantToRead = (uint)Math.Min(qwBytesLeft, bufSize);
  343. uint amtRead;
  344. iStream.Read(pbBuffer2, wantToRead, out amtRead);
  345. if (amtRead > qwBytesLeft)
  346. {
  347. throw new InvalidOperationException("IStream::Read() reported that more bytes were read than were in the file");
  348. }
  349. qwBytesLeft -= amtRead;
  350. localFile.Write(buffer, 0, (int)amtRead);
  351. progressEvents.SetItemWorkProgress((long)(statstg.cbSize - qwBytesLeft));
  352. }
  353. }
  354. VerifyNotCanceled();
  355. }
  356. }
  357. finally
  358. {
  359. if (iStream != null)
  360. {
  361. try
  362. {
  363. Marshal.ReleaseComObject(iStream);
  364. }
  365. catch (Exception)
  366. {
  367. }
  368. iStream = null;
  369. }
  370. }
  371. pathNameResult = pathName;
  372. }
  373. int NativeInterfaces.IFileDialogEvents.OnFolderChanging(
  374. NativeInterfaces.IFileDialog pfd,
  375. NativeInterfaces.IShellItem psiFolder)
  376. {
  377. return NativeConstants.E_NOTIMPL;
  378. }
  379. void NativeInterfaces.IFileDialogEvents.OnFolderChange(NativeInterfaces.IFileDialog pfd)
  380. {
  381. }
  382. void NativeInterfaces.IFileDialogEvents.OnSelectionChange(NativeInterfaces.IFileDialog pfd)
  383. {
  384. }
  385. void NativeInterfaces.IFileDialogEvents.OnShareViolation(
  386. NativeInterfaces.IFileDialog pfd,
  387. NativeInterfaces.IShellItem psi,
  388. out NativeConstants.FDE_SHAREVIOLATION_RESPONSE pResponse)
  389. {
  390. pResponse = NativeConstants.FDE_SHAREVIOLATION_RESPONSE.FDESVR_DEFAULT;
  391. }
  392. void NativeInterfaces.IFileDialogEvents.OnTypeChange(NativeInterfaces.IFileDialog pfd)
  393. {
  394. }
  395. void NativeInterfaces.IFileDialogEvents.OnOverwrite(
  396. NativeInterfaces.IFileDialog pfd,
  397. NativeInterfaces.IShellItem psi,
  398. out NativeConstants.FDE_OVERWRITE_RESPONSE pResponse)
  399. {
  400. pResponse = NativeConstants.FDE_OVERWRITE_RESPONSE.FDEOR_DEFAULT;
  401. }
  402. /// <summary>
  403. /// If the operation has been canceled, then this throws OperationCanceledException.
  404. /// </summary>
  405. /// <remarks>
  406. /// The general guideline for when to call this method is: (1) right after calling any
  407. /// method that may take awhile to complete, such as Read() or Write(), and (2) right
  408. /// after calling ReportItemProgress().
  409. /// </remarks>
  410. private void VerifyNotCanceled()
  411. {
  412. if (_cancelSink != null && _cancelSink.Canceled)
  413. {
  414. throw new OperationCanceledException();
  415. }
  416. }
  417. }
  418. }