ManagedFileSystem.cs 29 KB

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