ManagedFileSystem.cs 33 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970
  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. => new FileStream(path, GetFileMode(mode), GetFileAccess(access), GetFileShare(share), 4096, GetFileOptions(fileOpenOptions));
  388. private static FileOptions GetFileOptions(FileOpenOptions mode)
  389. {
  390. var val = (int)mode;
  391. return (FileOptions)val;
  392. }
  393. private static FileMode GetFileMode(FileOpenMode mode)
  394. {
  395. switch (mode)
  396. {
  397. //case FileOpenMode.Append:
  398. // return FileMode.Append;
  399. case FileOpenMode.Create:
  400. return FileMode.Create;
  401. case FileOpenMode.CreateNew:
  402. return FileMode.CreateNew;
  403. case FileOpenMode.Open:
  404. return FileMode.Open;
  405. case FileOpenMode.OpenOrCreate:
  406. return FileMode.OpenOrCreate;
  407. //case FileOpenMode.Truncate:
  408. // return FileMode.Truncate;
  409. default:
  410. throw new Exception("Unrecognized FileOpenMode");
  411. }
  412. }
  413. private static FileAccess GetFileAccess(FileAccessMode mode)
  414. {
  415. switch (mode)
  416. {
  417. //case FileAccessMode.ReadWrite:
  418. // return FileAccess.ReadWrite;
  419. case FileAccessMode.Write:
  420. return FileAccess.Write;
  421. case FileAccessMode.Read:
  422. return FileAccess.Read;
  423. default:
  424. throw new Exception("Unrecognized FileAccessMode");
  425. }
  426. }
  427. private static FileShare GetFileShare(FileShareMode mode)
  428. {
  429. switch (mode)
  430. {
  431. case FileShareMode.ReadWrite:
  432. return FileShare.ReadWrite;
  433. case FileShareMode.Write:
  434. return FileShare.Write;
  435. case FileShareMode.Read:
  436. return FileShare.Read;
  437. case FileShareMode.None:
  438. return FileShare.None;
  439. default:
  440. throw new Exception("Unrecognized FileShareMode");
  441. }
  442. }
  443. public void SetHidden(string path, bool isHidden)
  444. {
  445. if (_environmentInfo.OperatingSystem != MediaBrowser.Model.System.OperatingSystem.Windows)
  446. {
  447. return;
  448. }
  449. var info = GetExtendedFileSystemInfo(path);
  450. if (info.Exists && info.IsHidden != isHidden)
  451. {
  452. if (isHidden)
  453. {
  454. File.SetAttributes(path, File.GetAttributes(path) | FileAttributes.Hidden);
  455. }
  456. else
  457. {
  458. var attributes = File.GetAttributes(path);
  459. attributes = RemoveAttribute(attributes, FileAttributes.Hidden);
  460. File.SetAttributes(path, attributes);
  461. }
  462. }
  463. }
  464. public void SetReadOnly(string path, bool isReadOnly)
  465. {
  466. if (_environmentInfo.OperatingSystem != MediaBrowser.Model.System.OperatingSystem.Windows)
  467. {
  468. return;
  469. }
  470. var info = GetExtendedFileSystemInfo(path);
  471. if (info.Exists && info.IsReadOnly != isReadOnly)
  472. {
  473. if (isReadOnly)
  474. {
  475. File.SetAttributes(path, File.GetAttributes(path) | FileAttributes.ReadOnly);
  476. }
  477. else
  478. {
  479. var attributes = File.GetAttributes(path);
  480. attributes = RemoveAttribute(attributes, FileAttributes.ReadOnly);
  481. File.SetAttributes(path, attributes);
  482. }
  483. }
  484. }
  485. public void SetAttributes(string path, bool isHidden, bool isReadOnly)
  486. {
  487. if (_environmentInfo.OperatingSystem != MediaBrowser.Model.System.OperatingSystem.Windows)
  488. {
  489. return;
  490. }
  491. var info = GetExtendedFileSystemInfo(path);
  492. if (!info.Exists)
  493. {
  494. return;
  495. }
  496. if (info.IsReadOnly == isReadOnly && info.IsHidden == isHidden)
  497. {
  498. return;
  499. }
  500. var attributes = File.GetAttributes(path);
  501. if (isReadOnly)
  502. {
  503. attributes = attributes | FileAttributes.ReadOnly;
  504. }
  505. else
  506. {
  507. attributes = RemoveAttribute(attributes, FileAttributes.ReadOnly);
  508. }
  509. if (isHidden)
  510. {
  511. attributes = attributes | FileAttributes.Hidden;
  512. }
  513. else
  514. {
  515. attributes = RemoveAttribute(attributes, FileAttributes.Hidden);
  516. }
  517. File.SetAttributes(path, attributes);
  518. }
  519. private static FileAttributes RemoveAttribute(FileAttributes attributes, FileAttributes attributesToRemove)
  520. {
  521. return attributes & ~attributesToRemove;
  522. }
  523. /// <summary>
  524. /// Swaps the files.
  525. /// </summary>
  526. /// <param name="file1">The file1.</param>
  527. /// <param name="file2">The file2.</param>
  528. public void SwapFiles(string file1, string file2)
  529. {
  530. if (string.IsNullOrEmpty(file1))
  531. {
  532. throw new ArgumentNullException(nameof(file1));
  533. }
  534. if (string.IsNullOrEmpty(file2))
  535. {
  536. throw new ArgumentNullException(nameof(file2));
  537. }
  538. var temp1 = Path.Combine(_tempPath, Guid.NewGuid().ToString("N"));
  539. // Copying over will fail against hidden files
  540. SetHidden(file1, false);
  541. SetHidden(file2, false);
  542. Directory.CreateDirectory(_tempPath);
  543. CopyFile(file1, temp1, true);
  544. CopyFile(file2, file1, true);
  545. CopyFile(temp1, file2, true);
  546. }
  547. private static char GetDirectorySeparatorChar(string path)
  548. {
  549. return Path.DirectorySeparatorChar;
  550. }
  551. public bool ContainsSubPath(string parentPath, string path)
  552. {
  553. if (string.IsNullOrEmpty(parentPath))
  554. {
  555. throw new ArgumentNullException(nameof(parentPath));
  556. }
  557. if (string.IsNullOrEmpty(path))
  558. {
  559. throw new ArgumentNullException(nameof(path));
  560. }
  561. var separatorChar = GetDirectorySeparatorChar(parentPath);
  562. return path.IndexOf(parentPath.TrimEnd(separatorChar) + separatorChar, StringComparison.OrdinalIgnoreCase) != -1;
  563. }
  564. public bool IsRootPath(string path)
  565. {
  566. if (string.IsNullOrEmpty(path))
  567. {
  568. throw new ArgumentNullException(nameof(path));
  569. }
  570. var parent = GetDirectoryName(path);
  571. if (!string.IsNullOrEmpty(parent))
  572. {
  573. return false;
  574. }
  575. return true;
  576. }
  577. public string GetDirectoryName(string path)
  578. {
  579. return Path.GetDirectoryName(path);
  580. }
  581. public string NormalizePath(string path)
  582. {
  583. if (string.IsNullOrEmpty(path))
  584. {
  585. throw new ArgumentNullException(nameof(path));
  586. }
  587. if (path.EndsWith(":\\", StringComparison.OrdinalIgnoreCase))
  588. {
  589. return path;
  590. }
  591. return path.TrimEnd(GetDirectorySeparatorChar(path));
  592. }
  593. public bool AreEqual(string path1, string path2)
  594. {
  595. if (path1 == null && path2 == null)
  596. {
  597. return true;
  598. }
  599. if (path1 == null || path2 == null)
  600. {
  601. return false;
  602. }
  603. return string.Equals(NormalizePath(path1), NormalizePath(path2), StringComparison.OrdinalIgnoreCase);
  604. }
  605. public string GetFileNameWithoutExtension(FileSystemMetadata info)
  606. {
  607. if (info.IsDirectory)
  608. {
  609. return info.Name;
  610. }
  611. return Path.GetFileNameWithoutExtension(info.FullName);
  612. }
  613. public string GetFileNameWithoutExtension(string path)
  614. {
  615. return Path.GetFileNameWithoutExtension(path);
  616. }
  617. public bool IsPathFile(string path)
  618. {
  619. // Cannot use Path.IsPathRooted because it returns false under mono when using windows-based paths, e.g. C:\\
  620. if (path.IndexOf("://", StringComparison.OrdinalIgnoreCase) != -1 &&
  621. !path.StartsWith("file://", StringComparison.OrdinalIgnoreCase))
  622. {
  623. return false;
  624. }
  625. return true;
  626. //return Path.IsPathRooted(path);
  627. }
  628. public void DeleteFile(string path)
  629. {
  630. SetAttributes(path, false, false);
  631. File.Delete(path);
  632. }
  633. public void DeleteDirectory(string path, bool recursive)
  634. {
  635. Directory.Delete(path, recursive);
  636. }
  637. public void CreateDirectory(string path)
  638. {
  639. Directory.CreateDirectory(path);
  640. }
  641. public List<FileSystemMetadata> GetDrives()
  642. {
  643. // Only include drives in the ready state or this method could end up being very slow, waiting for drives to timeout
  644. return DriveInfo.GetDrives().Where(d => d.IsReady).Select(d => new FileSystemMetadata
  645. {
  646. Name = d.Name,
  647. FullName = d.RootDirectory.FullName,
  648. IsDirectory = true
  649. }).ToList();
  650. }
  651. public IEnumerable<FileSystemMetadata> GetDirectories(string path, bool recursive = false)
  652. {
  653. var searchOption = recursive ? SearchOption.AllDirectories : SearchOption.TopDirectoryOnly;
  654. return ToMetadata(new DirectoryInfo(path).EnumerateDirectories("*", searchOption));
  655. }
  656. public IEnumerable<FileSystemMetadata> GetFiles(string path, bool recursive = false)
  657. {
  658. return GetFiles(path, null, false, recursive);
  659. }
  660. public IEnumerable<FileSystemMetadata> GetFiles(string path, string[] extensions, bool enableCaseSensitiveExtensions, bool recursive = false)
  661. {
  662. var searchOption = recursive ? SearchOption.AllDirectories : SearchOption.TopDirectoryOnly;
  663. // On linux and osx the search pattern is case sensitive
  664. // If we're OK with case-sensitivity, and we're only filtering for one extension, then use the native method
  665. if ((enableCaseSensitiveExtensions || _isEnvironmentCaseInsensitive) && extensions != null && extensions.Length == 1)
  666. {
  667. return ToMetadata(new DirectoryInfo(path).EnumerateFiles("*" + extensions[0], searchOption));
  668. }
  669. var files = new DirectoryInfo(path).EnumerateFiles("*", searchOption);
  670. if (extensions != null && extensions.Length > 0)
  671. {
  672. files = files.Where(i =>
  673. {
  674. var ext = i.Extension;
  675. if (ext == null)
  676. {
  677. return false;
  678. }
  679. return extensions.Contains(ext, StringComparer.OrdinalIgnoreCase);
  680. });
  681. }
  682. return ToMetadata(files);
  683. }
  684. public IEnumerable<FileSystemMetadata> GetFileSystemEntries(string path, bool recursive = false)
  685. {
  686. var directoryInfo = new DirectoryInfo(path);
  687. var searchOption = recursive ? SearchOption.AllDirectories : SearchOption.TopDirectoryOnly;
  688. if (EnableSeparateFileAndDirectoryQueries)
  689. {
  690. return ToMetadata(directoryInfo.EnumerateDirectories("*", searchOption))
  691. .Concat(ToMetadata(directoryInfo.EnumerateFiles("*", searchOption)));
  692. }
  693. return ToMetadata(directoryInfo.EnumerateFileSystemInfos("*", searchOption));
  694. }
  695. private IEnumerable<FileSystemMetadata> ToMetadata(IEnumerable<FileSystemInfo> infos)
  696. {
  697. return infos.Select(GetFileSystemMetadata);
  698. }
  699. public string[] ReadAllLines(string path)
  700. {
  701. return File.ReadAllLines(path);
  702. }
  703. public void WriteAllLines(string path, IEnumerable<string> lines)
  704. {
  705. File.WriteAllLines(path, lines);
  706. }
  707. public Stream OpenRead(string path)
  708. {
  709. return File.OpenRead(path);
  710. }
  711. public void CopyFile(string source, string target, bool overwrite)
  712. {
  713. File.Copy(source, target, overwrite);
  714. }
  715. public void MoveFile(string source, string target)
  716. {
  717. File.Move(source, target);
  718. }
  719. public void MoveDirectory(string source, string target)
  720. {
  721. Directory.Move(source, target);
  722. }
  723. public bool DirectoryExists(string path)
  724. {
  725. return Directory.Exists(path);
  726. }
  727. public bool FileExists(string path)
  728. {
  729. return File.Exists(path);
  730. }
  731. public string ReadAllText(string path)
  732. {
  733. return File.ReadAllText(path);
  734. }
  735. public byte[] ReadAllBytes(string path)
  736. {
  737. return File.ReadAllBytes(path);
  738. }
  739. public void WriteAllText(string path, string text, Encoding encoding)
  740. {
  741. File.WriteAllText(path, text, encoding);
  742. }
  743. public void WriteAllText(string path, string text)
  744. {
  745. File.WriteAllText(path, text);
  746. }
  747. public void WriteAllBytes(string path, byte[] bytes)
  748. {
  749. File.WriteAllBytes(path, bytes);
  750. }
  751. public string ReadAllText(string path, Encoding encoding)
  752. {
  753. return File.ReadAllText(path, encoding);
  754. }
  755. public IEnumerable<string> GetDirectoryPaths(string path, bool recursive = false)
  756. {
  757. var searchOption = recursive ? SearchOption.AllDirectories : SearchOption.TopDirectoryOnly;
  758. return Directory.EnumerateDirectories(path, "*", searchOption);
  759. }
  760. public IEnumerable<string> GetFilePaths(string path, bool recursive = false)
  761. {
  762. return GetFilePaths(path, null, false, recursive);
  763. }
  764. public IEnumerable<string> GetFilePaths(string path, string[] extensions, bool enableCaseSensitiveExtensions, bool recursive = false)
  765. {
  766. var searchOption = recursive ? SearchOption.AllDirectories : SearchOption.TopDirectoryOnly;
  767. // On linux and osx the search pattern is case sensitive
  768. // If we're OK with case-sensitivity, and we're only filtering for one extension, then use the native method
  769. if ((enableCaseSensitiveExtensions || _isEnvironmentCaseInsensitive) && extensions != null && extensions.Length == 1)
  770. {
  771. return Directory.EnumerateFiles(path, "*" + extensions[0], searchOption);
  772. }
  773. var files = Directory.EnumerateFiles(path, "*", searchOption);
  774. if (extensions != null && extensions.Length > 0)
  775. {
  776. files = files.Where(i =>
  777. {
  778. var ext = Path.GetExtension(i);
  779. if (ext == null)
  780. {
  781. return false;
  782. }
  783. return extensions.Contains(ext, StringComparer.OrdinalIgnoreCase);
  784. });
  785. }
  786. return files;
  787. }
  788. public IEnumerable<string> GetFileSystemEntryPaths(string path, bool recursive = false)
  789. {
  790. var searchOption = recursive ? SearchOption.AllDirectories : SearchOption.TopDirectoryOnly;
  791. return Directory.EnumerateFileSystemEntries(path, "*", searchOption);
  792. }
  793. public virtual void SetExecutable(string path)
  794. {
  795. if (_environmentInfo.OperatingSystem == MediaBrowser.Model.System.OperatingSystem.OSX)
  796. {
  797. RunProcess("chmod", "+x \"" + path + "\"", GetDirectoryName(path));
  798. }
  799. }
  800. private static void RunProcess(string path, string args, string workingDirectory)
  801. {
  802. using (var process = Process.Start(new ProcessStartInfo
  803. {
  804. Arguments = args,
  805. FileName = path,
  806. CreateNoWindow = true,
  807. WorkingDirectory = workingDirectory,
  808. WindowStyle = ProcessWindowStyle.Normal
  809. }))
  810. {
  811. process.WaitForExit();
  812. }
  813. }
  814. }
  815. }