ManagedFileSystem.cs 37 KB

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