ManagedFileSystem.cs 33 KB

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