ManagedFileSystem.cs 39 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Diagnostics;
  4. using System.IO;
  5. using System.Linq;
  6. using System.Text;
  7. using MediaBrowser.Model.IO;
  8. using Microsoft.Extensions.Logging;
  9. using MediaBrowser.Model.System;
  10. namespace Emby.Server.Implementations.IO
  11. {
  12. /// <summary>
  13. /// Class ManagedFileSystem
  14. /// </summary>
  15. public class ManagedFileSystem : IFileSystem
  16. {
  17. protected ILogger Logger;
  18. private readonly bool _supportsAsyncFileStreams;
  19. private char[] _invalidFileNameChars;
  20. private readonly List<IShortcutHandler> _shortcutHandlers = new List<IShortcutHandler>();
  21. private bool EnableSeparateFileAndDirectoryQueries;
  22. private string _tempPath;
  23. private SharpCifsFileSystem _sharpCifsFileSystem;
  24. private IEnvironmentInfo _environmentInfo;
  25. private bool _isEnvironmentCaseInsensitive;
  26. private string _defaultDirectory;
  27. public ManagedFileSystem(ILogger logger, IEnvironmentInfo environmentInfo, string defaultDirectory, string tempPath, bool enableSeparateFileAndDirectoryQueries)
  28. {
  29. Logger = logger;
  30. _supportsAsyncFileStreams = true;
  31. _tempPath = tempPath;
  32. _environmentInfo = environmentInfo;
  33. _defaultDirectory = defaultDirectory;
  34. // On Linux with mono, this needs to be true or symbolic links are ignored
  35. EnableSeparateFileAndDirectoryQueries = enableSeparateFileAndDirectoryQueries;
  36. SetInvalidFileNameChars(environmentInfo.OperatingSystem == MediaBrowser.Model.System.OperatingSystem.Windows);
  37. _sharpCifsFileSystem = new SharpCifsFileSystem(environmentInfo.OperatingSystem);
  38. _isEnvironmentCaseInsensitive = environmentInfo.OperatingSystem == MediaBrowser.Model.System.OperatingSystem.Windows;
  39. }
  40. public string DefaultDirectory
  41. {
  42. get
  43. {
  44. var value = _defaultDirectory;
  45. if (!string.IsNullOrEmpty(value))
  46. {
  47. try
  48. {
  49. if (DirectoryExists(value))
  50. {
  51. return value;
  52. }
  53. }
  54. catch
  55. {
  56. }
  57. }
  58. return null;
  59. }
  60. }
  61. public void AddShortcutHandler(IShortcutHandler handler)
  62. {
  63. _shortcutHandlers.Add(handler);
  64. }
  65. protected void SetInvalidFileNameChars(bool enableManagedInvalidFileNameChars)
  66. {
  67. if (enableManagedInvalidFileNameChars)
  68. {
  69. _invalidFileNameChars = Path.GetInvalidFileNameChars();
  70. }
  71. else
  72. {
  73. // Be consistent across platforms because the windows server will fail to query network shares that don't follow windows conventions
  74. // https://referencesource.microsoft.com/#mscorlib/system/io/path.cs
  75. _invalidFileNameChars = new char[] { '\"', '<', '>', '|', '\0', (Char)1, (Char)2, (Char)3, (Char)4, (Char)5, (Char)6, (Char)7, (Char)8, (Char)9, (Char)10, (Char)11, (Char)12, (Char)13, (Char)14, (Char)15, (Char)16, (Char)17, (Char)18, (Char)19, (Char)20, (Char)21, (Char)22, (Char)23, (Char)24, (Char)25, (Char)26, (Char)27, (Char)28, (Char)29, (Char)30, (Char)31, ':', '*', '?', '\\', '/' };
  76. }
  77. }
  78. public char DirectorySeparatorChar
  79. {
  80. get
  81. {
  82. return Path.DirectorySeparatorChar;
  83. }
  84. }
  85. public string GetFullPath(string path)
  86. {
  87. return Path.GetFullPath(path);
  88. }
  89. /// <summary>
  90. /// Determines whether the specified filename is shortcut.
  91. /// </summary>
  92. /// <param name="filename">The filename.</param>
  93. /// <returns><c>true</c> if the specified filename is shortcut; otherwise, <c>false</c>.</returns>
  94. /// <exception cref="System.ArgumentNullException">filename</exception>
  95. public virtual bool IsShortcut(string filename)
  96. {
  97. if (string.IsNullOrEmpty(filename))
  98. {
  99. throw new ArgumentNullException("filename");
  100. }
  101. var extension = Path.GetExtension(filename);
  102. return _shortcutHandlers.Any(i => string.Equals(extension, i.Extension, StringComparison.OrdinalIgnoreCase));
  103. }
  104. /// <summary>
  105. /// Resolves the shortcut.
  106. /// </summary>
  107. /// <param name="filename">The filename.</param>
  108. /// <returns>System.String.</returns>
  109. /// <exception cref="System.ArgumentNullException">filename</exception>
  110. public virtual string ResolveShortcut(string filename)
  111. {
  112. if (string.IsNullOrEmpty(filename))
  113. {
  114. throw new ArgumentNullException("filename");
  115. }
  116. var extension = Path.GetExtension(filename);
  117. var handler = _shortcutHandlers.FirstOrDefault(i => string.Equals(extension, i.Extension, StringComparison.OrdinalIgnoreCase));
  118. if (handler != null)
  119. {
  120. return handler.Resolve(filename);
  121. }
  122. return null;
  123. }
  124. public string MakeAbsolutePath(string folderPath, string filePath)
  125. {
  126. if (String.IsNullOrWhiteSpace(filePath)) return filePath;
  127. if (filePath.Contains(@"://")) return filePath; //stream
  128. if (filePath.Length > 3 && filePath[1] == ':' && filePath[2] == '/') return filePath; //absolute local path
  129. // unc path
  130. if (filePath.StartsWith("\\\\"))
  131. {
  132. return filePath;
  133. }
  134. var firstChar = filePath[0];
  135. if (firstChar == '/')
  136. {
  137. // For this we don't really know.
  138. return filePath;
  139. }
  140. if (firstChar == '\\') //relative path
  141. {
  142. filePath = filePath.Substring(1);
  143. }
  144. try
  145. {
  146. string path = System.IO.Path.Combine(folderPath, filePath);
  147. path = System.IO.Path.GetFullPath(path);
  148. return path;
  149. }
  150. catch (ArgumentException)
  151. {
  152. return filePath;
  153. }
  154. catch (PathTooLongException)
  155. {
  156. return filePath;
  157. }
  158. catch (NotSupportedException)
  159. {
  160. return filePath;
  161. }
  162. }
  163. /// <summary>
  164. /// Creates the shortcut.
  165. /// </summary>
  166. /// <param name="shortcutPath">The shortcut path.</param>
  167. /// <param name="target">The target.</param>
  168. /// <exception cref="System.ArgumentNullException">
  169. /// shortcutPath
  170. /// or
  171. /// target
  172. /// </exception>
  173. public void CreateShortcut(string shortcutPath, string target)
  174. {
  175. if (string.IsNullOrEmpty(shortcutPath))
  176. {
  177. throw new ArgumentNullException("shortcutPath");
  178. }
  179. if (string.IsNullOrEmpty(target))
  180. {
  181. throw new ArgumentNullException("target");
  182. }
  183. var extension = Path.GetExtension(shortcutPath);
  184. var handler = _shortcutHandlers.FirstOrDefault(i => string.Equals(extension, i.Extension, StringComparison.OrdinalIgnoreCase));
  185. if (handler != null)
  186. {
  187. handler.Create(shortcutPath, target);
  188. }
  189. else
  190. {
  191. throw new NotImplementedException();
  192. }
  193. }
  194. /// <summary>
  195. /// Returns a <see cref="FileSystemMetadata"/> object for the specified file or directory path.
  196. /// </summary>
  197. /// <param name="path">A path to a file or directory.</param>
  198. /// <returns>A <see cref="FileSystemMetadata"/> object.</returns>
  199. /// <remarks>If the specified path points to a directory, the returned <see cref="FileSystemMetadata"/> object's
  200. /// <see cref="FileSystemMetadata.IsDirectory"/> property will be set to true and all other properties will reflect the properties of the directory.</remarks>
  201. public FileSystemMetadata GetFileSystemInfo(string path)
  202. {
  203. if (_sharpCifsFileSystem.IsEnabledForPath(path))
  204. {
  205. return _sharpCifsFileSystem.GetFileSystemInfo(path);
  206. }
  207. // Take a guess to try and avoid two file system hits, but we'll double-check by calling Exists
  208. if (Path.HasExtension(path))
  209. {
  210. var fileInfo = new FileInfo(path);
  211. if (fileInfo.Exists)
  212. {
  213. return GetFileSystemMetadata(fileInfo);
  214. }
  215. return GetFileSystemMetadata(new DirectoryInfo(path));
  216. }
  217. else
  218. {
  219. var fileInfo = new DirectoryInfo(path);
  220. if (fileInfo.Exists)
  221. {
  222. return GetFileSystemMetadata(fileInfo);
  223. }
  224. return GetFileSystemMetadata(new FileInfo(path));
  225. }
  226. }
  227. /// <summary>
  228. /// Returns a <see cref="FileSystemMetadata"/> object for the specified file path.
  229. /// </summary>
  230. /// <param name="path">A path to a file.</param>
  231. /// <returns>A <see cref="FileSystemMetadata"/> object.</returns>
  232. /// <remarks><para>If the specified path points to a directory, the returned <see cref="FileSystemMetadata"/> object's
  233. /// <see cref="FileSystemMetadata.IsDirectory"/> property and the <see cref="FileSystemMetadata.Exists"/> property will both be set to false.</para>
  234. /// <para>For automatic handling of files <b>and</b> directories, use <see cref="GetFileSystemInfo"/>.</para></remarks>
  235. public FileSystemMetadata GetFileInfo(string path)
  236. {
  237. if (_sharpCifsFileSystem.IsEnabledForPath(path))
  238. {
  239. return _sharpCifsFileSystem.GetFileInfo(path);
  240. }
  241. var fileInfo = new FileInfo(path);
  242. return GetFileSystemMetadata(fileInfo);
  243. }
  244. /// <summary>
  245. /// Returns a <see cref="FileSystemMetadata"/> object for the specified directory path.
  246. /// </summary>
  247. /// <param name="path">A path to a directory.</param>
  248. /// <returns>A <see cref="FileSystemMetadata"/> object.</returns>
  249. /// <remarks><para>If the specified path points to a file, the returned <see cref="FileSystemMetadata"/> object's
  250. /// <see cref="FileSystemMetadata.IsDirectory"/> property will be set to true and the <see cref="FileSystemMetadata.Exists"/> property will be set to false.</para>
  251. /// <para>For automatic handling of files <b>and</b> directories, use <see cref="GetFileSystemInfo"/>.</para></remarks>
  252. public FileSystemMetadata GetDirectoryInfo(string path)
  253. {
  254. if (_sharpCifsFileSystem.IsEnabledForPath(path))
  255. {
  256. return _sharpCifsFileSystem.GetDirectoryInfo(path);
  257. }
  258. var fileInfo = new DirectoryInfo(path);
  259. return GetFileSystemMetadata(fileInfo);
  260. }
  261. private FileSystemMetadata GetFileSystemMetadata(FileSystemInfo info)
  262. {
  263. var result = new FileSystemMetadata();
  264. result.Exists = info.Exists;
  265. result.FullName = info.FullName;
  266. result.Extension = info.Extension;
  267. result.Name = info.Name;
  268. if (result.Exists)
  269. {
  270. result.IsDirectory = info is DirectoryInfo || (info.Attributes & FileAttributes.Directory) == FileAttributes.Directory;
  271. //if (!result.IsDirectory)
  272. //{
  273. // result.IsHidden = (info.Attributes & FileAttributes.Hidden) == FileAttributes.Hidden;
  274. //}
  275. var fileInfo = info as FileInfo;
  276. if (fileInfo != null)
  277. {
  278. result.Length = fileInfo.Length;
  279. result.DirectoryName = fileInfo.DirectoryName;
  280. }
  281. result.CreationTimeUtc = GetCreationTimeUtc(info);
  282. result.LastWriteTimeUtc = GetLastWriteTimeUtc(info);
  283. }
  284. else
  285. {
  286. result.IsDirectory = info is DirectoryInfo;
  287. }
  288. return result;
  289. }
  290. private ExtendedFileSystemInfo GetExtendedFileSystemInfo(string path)
  291. {
  292. var result = new ExtendedFileSystemInfo();
  293. var info = new FileInfo(path);
  294. if (info.Exists)
  295. {
  296. result.Exists = true;
  297. var attributes = info.Attributes;
  298. result.IsHidden = (attributes & FileAttributes.Hidden) == FileAttributes.Hidden;
  299. result.IsReadOnly = (attributes & FileAttributes.ReadOnly) == FileAttributes.ReadOnly;
  300. }
  301. return result;
  302. }
  303. /// <summary>
  304. /// The space char
  305. /// </summary>
  306. private const char SpaceChar = ' ';
  307. /// <summary>
  308. /// Takes a filename and removes invalid characters
  309. /// </summary>
  310. /// <param name="filename">The filename.</param>
  311. /// <returns>System.String.</returns>
  312. /// <exception cref="System.ArgumentNullException">filename</exception>
  313. public string GetValidFilename(string filename)
  314. {
  315. var builder = new StringBuilder(filename);
  316. foreach (var c in _invalidFileNameChars)
  317. {
  318. builder = builder.Replace(c, SpaceChar);
  319. }
  320. return builder.ToString();
  321. }
  322. /// <summary>
  323. /// Gets the creation time UTC.
  324. /// </summary>
  325. /// <param name="info">The info.</param>
  326. /// <returns>DateTime.</returns>
  327. public DateTime GetCreationTimeUtc(FileSystemInfo info)
  328. {
  329. // This could throw an error on some file systems that have dates out of range
  330. try
  331. {
  332. return info.CreationTimeUtc;
  333. }
  334. catch (Exception ex)
  335. {
  336. Logger.LogError(ex, "Error determining CreationTimeUtc for {FullName}", info.FullName);
  337. return DateTime.MinValue;
  338. }
  339. }
  340. /// <summary>
  341. /// Gets the creation time UTC.
  342. /// </summary>
  343. /// <param name="path">The path.</param>
  344. /// <returns>DateTime.</returns>
  345. public DateTime GetCreationTimeUtc(string path)
  346. {
  347. return GetCreationTimeUtc(GetFileSystemInfo(path));
  348. }
  349. public DateTime GetCreationTimeUtc(FileSystemMetadata info)
  350. {
  351. return info.CreationTimeUtc;
  352. }
  353. public DateTime GetLastWriteTimeUtc(FileSystemMetadata info)
  354. {
  355. return info.LastWriteTimeUtc;
  356. }
  357. /// <summary>
  358. /// Gets the creation time UTC.
  359. /// </summary>
  360. /// <param name="info">The info.</param>
  361. /// <returns>DateTime.</returns>
  362. public DateTime GetLastWriteTimeUtc(FileSystemInfo info)
  363. {
  364. // This could throw an error on some file systems that have dates out of range
  365. try
  366. {
  367. return info.LastWriteTimeUtc;
  368. }
  369. catch (Exception ex)
  370. {
  371. Logger.LogError(ex, "Error determining LastAccessTimeUtc for {FullName}", info.FullName);
  372. return DateTime.MinValue;
  373. }
  374. }
  375. /// <summary>
  376. /// Gets the last write time UTC.
  377. /// </summary>
  378. /// <param name="path">The path.</param>
  379. /// <returns>DateTime.</returns>
  380. public DateTime GetLastWriteTimeUtc(string path)
  381. {
  382. return GetLastWriteTimeUtc(GetFileSystemInfo(path));
  383. }
  384. /// <summary>
  385. /// Gets the file stream.
  386. /// </summary>
  387. /// <param name="path">The path.</param>
  388. /// <param name="mode">The mode.</param>
  389. /// <param name="access">The access.</param>
  390. /// <param name="share">The share.</param>
  391. /// <param name="isAsync">if set to <c>true</c> [is asynchronous].</param>
  392. /// <returns>FileStream.</returns>
  393. public Stream GetFileStream(string path, FileOpenMode mode, FileAccessMode access, FileShareMode share, bool isAsync = false)
  394. {
  395. if (_sharpCifsFileSystem.IsEnabledForPath(path))
  396. {
  397. return _sharpCifsFileSystem.GetFileStream(path, mode, access, share);
  398. }
  399. if (_supportsAsyncFileStreams && isAsync)
  400. {
  401. return GetFileStream(path, mode, access, share, FileOpenOptions.Asynchronous);
  402. }
  403. return GetFileStream(path, mode, access, share, FileOpenOptions.None);
  404. }
  405. public Stream GetFileStream(string path, FileOpenMode mode, FileAccessMode access, FileShareMode share, FileOpenOptions fileOpenOptions)
  406. {
  407. if (_sharpCifsFileSystem.IsEnabledForPath(path))
  408. {
  409. return _sharpCifsFileSystem.GetFileStream(path, mode, access, share);
  410. }
  411. var defaultBufferSize = 4096;
  412. return new FileStream(path, GetFileMode(mode), GetFileAccess(access), GetFileShare(share), defaultBufferSize, GetFileOptions(fileOpenOptions));
  413. }
  414. private FileOptions GetFileOptions(FileOpenOptions mode)
  415. {
  416. var val = (int)mode;
  417. return (FileOptions)val;
  418. }
  419. private FileMode GetFileMode(FileOpenMode mode)
  420. {
  421. switch (mode)
  422. {
  423. //case FileOpenMode.Append:
  424. // return FileMode.Append;
  425. case FileOpenMode.Create:
  426. return FileMode.Create;
  427. case FileOpenMode.CreateNew:
  428. return FileMode.CreateNew;
  429. case FileOpenMode.Open:
  430. return FileMode.Open;
  431. case FileOpenMode.OpenOrCreate:
  432. return FileMode.OpenOrCreate;
  433. //case FileOpenMode.Truncate:
  434. // return FileMode.Truncate;
  435. default:
  436. throw new Exception("Unrecognized FileOpenMode");
  437. }
  438. }
  439. private FileAccess GetFileAccess(FileAccessMode mode)
  440. {
  441. switch (mode)
  442. {
  443. //case FileAccessMode.ReadWrite:
  444. // return FileAccess.ReadWrite;
  445. case FileAccessMode.Write:
  446. return FileAccess.Write;
  447. case FileAccessMode.Read:
  448. return FileAccess.Read;
  449. default:
  450. throw new Exception("Unrecognized FileAccessMode");
  451. }
  452. }
  453. private FileShare GetFileShare(FileShareMode mode)
  454. {
  455. switch (mode)
  456. {
  457. case FileShareMode.ReadWrite:
  458. return FileShare.ReadWrite;
  459. case FileShareMode.Write:
  460. return FileShare.Write;
  461. case FileShareMode.Read:
  462. return FileShare.Read;
  463. case FileShareMode.None:
  464. return FileShare.None;
  465. default:
  466. throw new Exception("Unrecognized FileShareMode");
  467. }
  468. }
  469. public void SetHidden(string path, bool isHidden)
  470. {
  471. if (_environmentInfo.OperatingSystem != MediaBrowser.Model.System.OperatingSystem.Windows)
  472. {
  473. return;
  474. }
  475. if (_sharpCifsFileSystem.IsEnabledForPath(path))
  476. {
  477. _sharpCifsFileSystem.SetHidden(path, isHidden);
  478. return;
  479. }
  480. var info = GetExtendedFileSystemInfo(path);
  481. if (info.Exists && info.IsHidden != isHidden)
  482. {
  483. if (isHidden)
  484. {
  485. File.SetAttributes(path, File.GetAttributes(path) | FileAttributes.Hidden);
  486. }
  487. else
  488. {
  489. FileAttributes attributes = File.GetAttributes(path);
  490. attributes = RemoveAttribute(attributes, FileAttributes.Hidden);
  491. File.SetAttributes(path, attributes);
  492. }
  493. }
  494. }
  495. public void SetReadOnly(string path, bool isReadOnly)
  496. {
  497. if (_environmentInfo.OperatingSystem != MediaBrowser.Model.System.OperatingSystem.Windows)
  498. {
  499. return;
  500. }
  501. if (_sharpCifsFileSystem.IsEnabledForPath(path))
  502. {
  503. _sharpCifsFileSystem.SetReadOnly(path, isReadOnly);
  504. return;
  505. }
  506. var info = GetExtendedFileSystemInfo(path);
  507. if (info.Exists && info.IsReadOnly != isReadOnly)
  508. {
  509. if (isReadOnly)
  510. {
  511. File.SetAttributes(path, File.GetAttributes(path) | FileAttributes.ReadOnly);
  512. }
  513. else
  514. {
  515. FileAttributes attributes = File.GetAttributes(path);
  516. attributes = RemoveAttribute(attributes, FileAttributes.ReadOnly);
  517. File.SetAttributes(path, attributes);
  518. }
  519. }
  520. }
  521. public void SetAttributes(string path, bool isHidden, bool isReadOnly)
  522. {
  523. if (_environmentInfo.OperatingSystem != MediaBrowser.Model.System.OperatingSystem.Windows)
  524. {
  525. return;
  526. }
  527. if (_sharpCifsFileSystem.IsEnabledForPath(path))
  528. {
  529. _sharpCifsFileSystem.SetAttributes(path, isHidden, isReadOnly);
  530. return;
  531. }
  532. var info = GetExtendedFileSystemInfo(path);
  533. if (!info.Exists)
  534. {
  535. return;
  536. }
  537. if (info.IsReadOnly == isReadOnly && info.IsHidden == isHidden)
  538. {
  539. return;
  540. }
  541. var attributes = File.GetAttributes(path);
  542. if (isReadOnly)
  543. {
  544. attributes = attributes | FileAttributes.ReadOnly;
  545. }
  546. else
  547. {
  548. attributes = RemoveAttribute(attributes, FileAttributes.ReadOnly);
  549. }
  550. if (isHidden)
  551. {
  552. attributes = attributes | FileAttributes.Hidden;
  553. }
  554. else
  555. {
  556. attributes = RemoveAttribute(attributes, FileAttributes.Hidden);
  557. }
  558. File.SetAttributes(path, attributes);
  559. }
  560. private static FileAttributes RemoveAttribute(FileAttributes attributes, FileAttributes attributesToRemove)
  561. {
  562. return attributes & ~attributesToRemove;
  563. }
  564. /// <summary>
  565. /// Swaps the files.
  566. /// </summary>
  567. /// <param name="file1">The file1.</param>
  568. /// <param name="file2">The file2.</param>
  569. public void SwapFiles(string file1, string file2)
  570. {
  571. if (string.IsNullOrEmpty(file1))
  572. {
  573. throw new ArgumentNullException("file1");
  574. }
  575. if (string.IsNullOrEmpty(file2))
  576. {
  577. throw new ArgumentNullException("file2");
  578. }
  579. var temp1 = Path.Combine(_tempPath, Guid.NewGuid().ToString("N"));
  580. // Copying over will fail against hidden files
  581. SetHidden(file1, false);
  582. SetHidden(file2, false);
  583. Directory.CreateDirectory(_tempPath);
  584. CopyFile(file1, temp1, true);
  585. CopyFile(file2, file1, true);
  586. CopyFile(temp1, file2, true);
  587. }
  588. private char GetDirectorySeparatorChar(string path)
  589. {
  590. if (_sharpCifsFileSystem.IsEnabledForPath(path))
  591. {
  592. return _sharpCifsFileSystem.GetDirectorySeparatorChar(path);
  593. }
  594. return Path.DirectorySeparatorChar;
  595. }
  596. public bool ContainsSubPath(string parentPath, string path)
  597. {
  598. if (string.IsNullOrEmpty(parentPath))
  599. {
  600. throw new ArgumentNullException("parentPath");
  601. }
  602. if (string.IsNullOrEmpty(path))
  603. {
  604. throw new ArgumentNullException("path");
  605. }
  606. var separatorChar = GetDirectorySeparatorChar(parentPath);
  607. return path.IndexOf(parentPath.TrimEnd(separatorChar) + separatorChar, StringComparison.OrdinalIgnoreCase) != -1;
  608. }
  609. public bool IsRootPath(string path)
  610. {
  611. if (string.IsNullOrEmpty(path))
  612. {
  613. throw new ArgumentNullException("path");
  614. }
  615. var parent = GetDirectoryName(path);
  616. if (!string.IsNullOrEmpty(parent))
  617. {
  618. return false;
  619. }
  620. return true;
  621. }
  622. public string GetDirectoryName(string path)
  623. {
  624. if (_sharpCifsFileSystem.IsEnabledForPath(path))
  625. {
  626. return _sharpCifsFileSystem.GetDirectoryName(path);
  627. }
  628. return Path.GetDirectoryName(path);
  629. }
  630. public string NormalizePath(string path)
  631. {
  632. if (string.IsNullOrEmpty(path))
  633. {
  634. throw new ArgumentNullException("path");
  635. }
  636. if (_sharpCifsFileSystem.IsEnabledForPath(path))
  637. {
  638. return _sharpCifsFileSystem.NormalizePath(path);
  639. }
  640. if (path.EndsWith(":\\", StringComparison.OrdinalIgnoreCase))
  641. {
  642. return path;
  643. }
  644. return path.TrimEnd(GetDirectorySeparatorChar(path));
  645. }
  646. public bool AreEqual(string path1, string path2)
  647. {
  648. if (path1 == null && path2 == null)
  649. {
  650. return true;
  651. }
  652. if (path1 == null || path2 == null)
  653. {
  654. return false;
  655. }
  656. return string.Equals(NormalizePath(path1), NormalizePath(path2), StringComparison.OrdinalIgnoreCase);
  657. }
  658. public string GetFileNameWithoutExtension(FileSystemMetadata info)
  659. {
  660. if (info.IsDirectory)
  661. {
  662. return info.Name;
  663. }
  664. return Path.GetFileNameWithoutExtension(info.FullName);
  665. }
  666. public string GetFileNameWithoutExtension(string path)
  667. {
  668. return Path.GetFileNameWithoutExtension(path);
  669. }
  670. public bool IsPathFile(string path)
  671. {
  672. // Cannot use Path.IsPathRooted because it returns false under mono when using windows-based paths, e.g. C:\\
  673. if (_sharpCifsFileSystem.IsEnabledForPath(path))
  674. {
  675. return true;
  676. }
  677. if (path.IndexOf("://", StringComparison.OrdinalIgnoreCase) != -1 &&
  678. !path.StartsWith("file://", StringComparison.OrdinalIgnoreCase))
  679. {
  680. return false;
  681. }
  682. return true;
  683. //return Path.IsPathRooted(path);
  684. }
  685. public void DeleteFile(string path)
  686. {
  687. if (_sharpCifsFileSystem.IsEnabledForPath(path))
  688. {
  689. _sharpCifsFileSystem.DeleteFile(path);
  690. return;
  691. }
  692. SetAttributes(path, false, false);
  693. File.Delete(path);
  694. }
  695. public void DeleteDirectory(string path, bool recursive)
  696. {
  697. if (_sharpCifsFileSystem.IsEnabledForPath(path))
  698. {
  699. _sharpCifsFileSystem.DeleteDirectory(path, recursive);
  700. return;
  701. }
  702. Directory.Delete(path, recursive);
  703. }
  704. public void CreateDirectory(string path)
  705. {
  706. if (_sharpCifsFileSystem.IsEnabledForPath(path))
  707. {
  708. _sharpCifsFileSystem.CreateDirectory(path);
  709. return;
  710. }
  711. Directory.CreateDirectory(path);
  712. }
  713. public List<FileSystemMetadata> GetDrives()
  714. {
  715. // Only include drives in the ready state or this method could end up being very slow, waiting for drives to timeout
  716. return DriveInfo.GetDrives().Where(d => d.IsReady).Select(d => new FileSystemMetadata
  717. {
  718. Name = GetName(d),
  719. FullName = d.RootDirectory.FullName,
  720. IsDirectory = true
  721. }).ToList();
  722. }
  723. private string GetName(DriveInfo drive)
  724. {
  725. return drive.Name;
  726. }
  727. public IEnumerable<FileSystemMetadata> GetDirectories(string path, bool recursive = false)
  728. {
  729. if (_sharpCifsFileSystem.IsEnabledForPath(path))
  730. {
  731. return _sharpCifsFileSystem.GetDirectories(path, recursive);
  732. }
  733. var searchOption = recursive ? SearchOption.AllDirectories : SearchOption.TopDirectoryOnly;
  734. return ToMetadata(new DirectoryInfo(path).EnumerateDirectories("*", searchOption));
  735. }
  736. public IEnumerable<FileSystemMetadata> GetFiles(string path, bool recursive = false)
  737. {
  738. return GetFiles(path, null, false, recursive);
  739. }
  740. public IEnumerable<FileSystemMetadata> GetFiles(string path, string[] extensions, bool enableCaseSensitiveExtensions, bool recursive = false)
  741. {
  742. if (_sharpCifsFileSystem.IsEnabledForPath(path))
  743. {
  744. return _sharpCifsFileSystem.GetFiles(path, extensions, enableCaseSensitiveExtensions, recursive);
  745. }
  746. var searchOption = recursive ? SearchOption.AllDirectories : SearchOption.TopDirectoryOnly;
  747. // On linux and osx the search pattern is case sensitive
  748. // If we're OK with case-sensitivity, and we're only filtering for one extension, then use the native method
  749. if ((enableCaseSensitiveExtensions || _isEnvironmentCaseInsensitive) && extensions != null && extensions.Length == 1)
  750. {
  751. return ToMetadata(new DirectoryInfo(path).EnumerateFiles("*" + extensions[0], searchOption));
  752. }
  753. var files = new DirectoryInfo(path).EnumerateFiles("*", searchOption);
  754. if (extensions != null && extensions.Length > 0)
  755. {
  756. files = files.Where(i =>
  757. {
  758. var ext = i.Extension;
  759. if (ext == null)
  760. {
  761. return false;
  762. }
  763. return extensions.Contains(ext, StringComparer.OrdinalIgnoreCase);
  764. });
  765. }
  766. return ToMetadata(files);
  767. }
  768. public IEnumerable<FileSystemMetadata> GetFileSystemEntries(string path, bool recursive = false)
  769. {
  770. if (_sharpCifsFileSystem.IsEnabledForPath(path))
  771. {
  772. return _sharpCifsFileSystem.GetFileSystemEntries(path, recursive);
  773. }
  774. var directoryInfo = new DirectoryInfo(path);
  775. var searchOption = recursive ? SearchOption.AllDirectories : SearchOption.TopDirectoryOnly;
  776. if (EnableSeparateFileAndDirectoryQueries)
  777. {
  778. return ToMetadata(directoryInfo.EnumerateDirectories("*", searchOption))
  779. .Concat(ToMetadata(directoryInfo.EnumerateFiles("*", searchOption)));
  780. }
  781. return ToMetadata(directoryInfo.EnumerateFileSystemInfos("*", searchOption));
  782. }
  783. private IEnumerable<FileSystemMetadata> ToMetadata(IEnumerable<FileSystemInfo> infos)
  784. {
  785. return infos.Select(GetFileSystemMetadata);
  786. }
  787. public string[] ReadAllLines(string path)
  788. {
  789. if (_sharpCifsFileSystem.IsEnabledForPath(path))
  790. {
  791. return _sharpCifsFileSystem.ReadAllLines(path);
  792. }
  793. return File.ReadAllLines(path);
  794. }
  795. public void WriteAllLines(string path, IEnumerable<string> lines)
  796. {
  797. if (_sharpCifsFileSystem.IsEnabledForPath(path))
  798. {
  799. _sharpCifsFileSystem.WriteAllLines(path, lines);
  800. return;
  801. }
  802. File.WriteAllLines(path, lines);
  803. }
  804. public Stream OpenRead(string path)
  805. {
  806. if (_sharpCifsFileSystem.IsEnabledForPath(path))
  807. {
  808. return _sharpCifsFileSystem.OpenRead(path);
  809. }
  810. return File.OpenRead(path);
  811. }
  812. private void CopyFileUsingStreams(string source, string target, bool overwrite)
  813. {
  814. using (var sourceStream = OpenRead(source))
  815. {
  816. using (var targetStream = GetFileStream(target, FileOpenMode.Create, FileAccessMode.Write, FileShareMode.Read))
  817. {
  818. sourceStream.CopyTo(targetStream);
  819. }
  820. }
  821. }
  822. public void CopyFile(string source, string target, bool overwrite)
  823. {
  824. var enableSharpCifsForSource = _sharpCifsFileSystem.IsEnabledForPath(source);
  825. if (enableSharpCifsForSource != _sharpCifsFileSystem.IsEnabledForPath(target))
  826. {
  827. CopyFileUsingStreams(source, target, overwrite);
  828. return;
  829. }
  830. if (enableSharpCifsForSource)
  831. {
  832. _sharpCifsFileSystem.CopyFile(source, target, overwrite);
  833. return;
  834. }
  835. File.Copy(source, target, overwrite);
  836. }
  837. public void MoveFile(string source, string target)
  838. {
  839. if (_sharpCifsFileSystem.IsEnabledForPath(source))
  840. {
  841. _sharpCifsFileSystem.MoveFile(source, target);
  842. return;
  843. }
  844. File.Move(source, target);
  845. }
  846. public void MoveDirectory(string source, string target)
  847. {
  848. if (_sharpCifsFileSystem.IsEnabledForPath(source))
  849. {
  850. _sharpCifsFileSystem.MoveDirectory(source, target);
  851. return;
  852. }
  853. Directory.Move(source, target);
  854. }
  855. public bool DirectoryExists(string path)
  856. {
  857. if (_sharpCifsFileSystem.IsEnabledForPath(path))
  858. {
  859. return _sharpCifsFileSystem.DirectoryExists(path);
  860. }
  861. return Directory.Exists(path);
  862. }
  863. public bool FileExists(string path)
  864. {
  865. if (_sharpCifsFileSystem.IsEnabledForPath(path))
  866. {
  867. return _sharpCifsFileSystem.FileExists(path);
  868. }
  869. return File.Exists(path);
  870. }
  871. public string ReadAllText(string path)
  872. {
  873. if (_sharpCifsFileSystem.IsEnabledForPath(path))
  874. {
  875. return _sharpCifsFileSystem.ReadAllText(path);
  876. }
  877. return File.ReadAllText(path);
  878. }
  879. public byte[] ReadAllBytes(string path)
  880. {
  881. if (_sharpCifsFileSystem.IsEnabledForPath(path))
  882. {
  883. return _sharpCifsFileSystem.ReadAllBytes(path);
  884. }
  885. return File.ReadAllBytes(path);
  886. }
  887. public void WriteAllText(string path, string text, Encoding encoding)
  888. {
  889. if (_sharpCifsFileSystem.IsEnabledForPath(path))
  890. {
  891. _sharpCifsFileSystem.WriteAllText(path, text, encoding);
  892. return;
  893. }
  894. File.WriteAllText(path, text, encoding);
  895. }
  896. public void WriteAllText(string path, string text)
  897. {
  898. if (_sharpCifsFileSystem.IsEnabledForPath(path))
  899. {
  900. _sharpCifsFileSystem.WriteAllText(path, text);
  901. return;
  902. }
  903. File.WriteAllText(path, text);
  904. }
  905. public void WriteAllBytes(string path, byte[] bytes)
  906. {
  907. if (_sharpCifsFileSystem.IsEnabledForPath(path))
  908. {
  909. _sharpCifsFileSystem.WriteAllBytes(path, bytes);
  910. return;
  911. }
  912. File.WriteAllBytes(path, bytes);
  913. }
  914. public string ReadAllText(string path, Encoding encoding)
  915. {
  916. if (_sharpCifsFileSystem.IsEnabledForPath(path))
  917. {
  918. return _sharpCifsFileSystem.ReadAllText(path, encoding);
  919. }
  920. return File.ReadAllText(path, encoding);
  921. }
  922. public IEnumerable<string> GetDirectoryPaths(string path, bool recursive = false)
  923. {
  924. if (_sharpCifsFileSystem.IsEnabledForPath(path))
  925. {
  926. return _sharpCifsFileSystem.GetDirectoryPaths(path, recursive);
  927. }
  928. var searchOption = recursive ? SearchOption.AllDirectories : SearchOption.TopDirectoryOnly;
  929. return Directory.EnumerateDirectories(path, "*", searchOption);
  930. }
  931. public IEnumerable<string> GetFilePaths(string path, bool recursive = false)
  932. {
  933. return GetFilePaths(path, null, false, recursive);
  934. }
  935. public IEnumerable<string> GetFilePaths(string path, string[] extensions, bool enableCaseSensitiveExtensions, bool recursive = false)
  936. {
  937. if (_sharpCifsFileSystem.IsEnabledForPath(path))
  938. {
  939. return _sharpCifsFileSystem.GetFilePaths(path, extensions, enableCaseSensitiveExtensions, recursive);
  940. }
  941. var searchOption = recursive ? SearchOption.AllDirectories : SearchOption.TopDirectoryOnly;
  942. // On linux and osx the search pattern is case sensitive
  943. // If we're OK with case-sensitivity, and we're only filtering for one extension, then use the native method
  944. if ((enableCaseSensitiveExtensions || _isEnvironmentCaseInsensitive) && extensions != null && extensions.Length == 1)
  945. {
  946. return Directory.EnumerateFiles(path, "*" + extensions[0], searchOption);
  947. }
  948. var files = Directory.EnumerateFiles(path, "*", searchOption);
  949. if (extensions != null && extensions.Length > 0)
  950. {
  951. files = files.Where(i =>
  952. {
  953. var ext = Path.GetExtension(i);
  954. if (ext == null)
  955. {
  956. return false;
  957. }
  958. return extensions.Contains(ext, StringComparer.OrdinalIgnoreCase);
  959. });
  960. }
  961. return files;
  962. }
  963. public IEnumerable<string> GetFileSystemEntryPaths(string path, bool recursive = false)
  964. {
  965. if (_sharpCifsFileSystem.IsEnabledForPath(path))
  966. {
  967. return _sharpCifsFileSystem.GetFileSystemEntryPaths(path, recursive);
  968. }
  969. var searchOption = recursive ? SearchOption.AllDirectories : SearchOption.TopDirectoryOnly;
  970. return Directory.EnumerateFileSystemEntries(path, "*", searchOption);
  971. }
  972. public virtual void SetExecutable(string path)
  973. {
  974. if (_environmentInfo.OperatingSystem == MediaBrowser.Model.System.OperatingSystem.OSX)
  975. {
  976. RunProcess("chmod", "+x \"" + path + "\"", GetDirectoryName(path));
  977. }
  978. }
  979. private void RunProcess(string path, string args, string workingDirectory)
  980. {
  981. using (var process = Process.Start(new ProcessStartInfo
  982. {
  983. Arguments = args,
  984. FileName = path,
  985. CreateNoWindow = true,
  986. WorkingDirectory = workingDirectory,
  987. WindowStyle = ProcessWindowStyle.Normal
  988. }))
  989. {
  990. process.WaitForExit();
  991. }
  992. }
  993. }
  994. }