ManagedFileSystem.cs 33 KB

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