diff options
| author | Luke <luke.pulverenti@gmail.com> | 2016-05-28 19:48:26 -0400 |
|---|---|---|
| committer | Luke <luke.pulverenti@gmail.com> | 2016-05-28 19:48:26 -0400 |
| commit | e205caee0380dc49ef4bcdb0016ef38a0c3a9ea5 (patch) | |
| tree | 75409d7a5c9317cdf0a1daaca8bd3fcc2fb1f25f | |
| parent | 996da16d9030b9f33d834536503d10991ec09c29 (diff) | |
| parent | 2e2766bd19d6d974abe11284cc065ee7692ae1ed (diff) | |
Merge pull request #1786 from MediaBrowser/dev
Dev
11 files changed, 137 insertions, 252 deletions
diff --git a/MediaBrowser.Api/LiveTv/LiveTvService.cs b/MediaBrowser.Api/LiveTv/LiveTvService.cs index d3a4558c8..a59b5f351 100644 --- a/MediaBrowser.Api/LiveTv/LiveTvService.cs +++ b/MediaBrowser.Api/LiveTv/LiveTvService.cs @@ -550,9 +550,7 @@ namespace MediaBrowser.Api.LiveTv var response = await _httpClient.Get(new HttpRequestOptions { - Url = "https://json.schedulesdirect.org/20141201/available/countries", - CacheLength = TimeSpan.FromDays(1), - CacheMode = CacheMode.Unconditional + Url = "https://json.schedulesdirect.org/20141201/available/countries" }).ConfigureAwait(false); diff --git a/MediaBrowser.Api/Playback/BaseStreamingService.cs b/MediaBrowser.Api/Playback/BaseStreamingService.cs index 421ccdb5d..253e3d8e4 100644 --- a/MediaBrowser.Api/Playback/BaseStreamingService.cs +++ b/MediaBrowser.Api/Playback/BaseStreamingService.cs @@ -1519,6 +1519,13 @@ namespace MediaBrowser.Api.Playback } else if (i == 25) { + if (videoRequest != null) + { + videoRequest.ForceLiveStream = string.Equals("true", val, StringComparison.OrdinalIgnoreCase); + } + } + else if (i == 26) + { if (!string.IsNullOrWhiteSpace(val) && videoRequest != null) { SubtitleDeliveryMethod method; @@ -1528,7 +1535,7 @@ namespace MediaBrowser.Api.Playback } } } - else if (i == 26) + else if (i == 27) { request.TranscodingMaxAudioChannels = int.Parse(val, UsCulture); } diff --git a/MediaBrowser.Api/Subtitles/SubtitleService.cs b/MediaBrowser.Api/Subtitles/SubtitleService.cs index c2183ad7b..1382527e2 100644 --- a/MediaBrowser.Api/Subtitles/SubtitleService.cs +++ b/MediaBrowser.Api/Subtitles/SubtitleService.cs @@ -98,6 +98,9 @@ namespace MediaBrowser.Api.Subtitles [ApiMember(Name = "EndPositionTicks", Description = "EndPositionTicks", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")] public long? EndPositionTicks { get; set; } + + [ApiMember(Name = "CopyTimestamps", Description = "CopyTimestamps", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET")] + public bool CopyTimestamps { get; set; } } [Route("/Videos/{Id}/{MediaSourceId}/Subtitles/{Index}/subtitles.m3u8", "GET", Summary = "Gets an HLS subtitle playlist.")] @@ -175,7 +178,7 @@ namespace MediaBrowser.Api.Subtitles var endPositionTicks = Math.Min(runtime, positionTicks + segmentLengthTicks); - var url = string.Format("stream.vtt?StartPositionTicks={0}&EndPositionTicks={1}&api_key={2}", + var url = string.Format("stream.vtt?CopyTimestamps=true,StartPositionTicks={0}&EndPositionTicks={1}&api_key={2}", positionTicks.ToString(CultureInfo.InvariantCulture), endPositionTicks.ToString(CultureInfo.InvariantCulture), accessToken); @@ -222,6 +225,7 @@ namespace MediaBrowser.Api.Subtitles request.Format, request.StartPositionTicks, request.EndPositionTicks, + request.CopyTimestamps, CancellationToken.None).ConfigureAwait(false); } diff --git a/MediaBrowser.Controller/Entities/UserViewBuilder.cs b/MediaBrowser.Controller/Entities/UserViewBuilder.cs index d4a8b0730..2bde80641 100644 --- a/MediaBrowser.Controller/Entities/UserViewBuilder.cs +++ b/MediaBrowser.Controller/Entities/UserViewBuilder.cs @@ -332,13 +332,14 @@ namespace MediaBrowser.Controller.Entities private QueryResult<BaseItem> GetMusicAlbumArtists(Folder parent, User user, InternalItemsQuery query) { - var items = _libraryManager.GetItemList(new InternalItemsQuery(user) + var items = parent.QueryRecursive(new InternalItemsQuery(user) { Recursive = true, ParentId = parent.Id, - IncludeItemTypes = new[] { typeof(Audio.Audio).Name } + IncludeItemTypes = new[] { typeof(Audio.Audio).Name }, + EnableTotalRecordCount = false - }).Cast<IHasAlbumArtist>(); + }).Items.Cast<IHasAlbumArtist>(); var artists = _libraryManager.GetAlbumArtists(items); @@ -347,13 +348,14 @@ namespace MediaBrowser.Controller.Entities private QueryResult<BaseItem> GetMusicArtists(Folder parent, User user, InternalItemsQuery query) { - var items = _libraryManager.GetItemList(new InternalItemsQuery(user) + var items = parent.QueryRecursive(new InternalItemsQuery(user) { Recursive = true, ParentId = parent.Id, - IncludeItemTypes = new[] { typeof(Audio.Audio).Name, typeof(MusicVideo).Name } + IncludeItemTypes = new[] { typeof(Audio.Audio).Name, typeof(MusicVideo).Name }, + EnableTotalRecordCount = false - }).Cast<IHasArtist>(); + }).Items.Cast<IHasArtist>(); var artists = _libraryManager.GetArtists(items); @@ -362,13 +364,14 @@ namespace MediaBrowser.Controller.Entities private QueryResult<BaseItem> GetFavoriteArtists(Folder parent, User user, InternalItemsQuery query) { - var items = _libraryManager.GetItemList(new InternalItemsQuery(user) + var items = parent.QueryRecursive(new InternalItemsQuery(user) { Recursive = true, ParentId = parent.Id, - IncludeItemTypes = new[] { typeof(Audio.Audio).Name } + IncludeItemTypes = new[] { typeof(Audio.Audio).Name }, + EnableTotalRecordCount = false - }).Cast<IHasAlbumArtist>(); + }).Items.Cast<IHasAlbumArtist>(); var artists = _libraryManager.GetAlbumArtists(items).Where(i => _userDataManager.GetUserData(user, i).IsFavorite); diff --git a/MediaBrowser.Controller/MediaEncoding/ISubtitleEncoder.cs b/MediaBrowser.Controller/MediaEncoding/ISubtitleEncoder.cs index e538b84d8..44489cbf5 100644 --- a/MediaBrowser.Controller/MediaEncoding/ISubtitleEncoder.cs +++ b/MediaBrowser.Controller/MediaEncoding/ISubtitleEncoder.cs @@ -10,13 +10,6 @@ namespace MediaBrowser.Controller.MediaEncoding /// <summary> /// Gets the subtitles. /// </summary> - /// <param name="itemId">The item identifier.</param> - /// <param name="mediaSourceId">The media source identifier.</param> - /// <param name="subtitleStreamIndex">Index of the subtitle stream.</param> - /// <param name="outputFormat">The output format.</param> - /// <param name="startTimeTicks">The start time ticks.</param> - /// <param name="endTimeTicks">The end time ticks.</param> - /// <param name="cancellationToken">The cancellation token.</param> /// <returns>Task{Stream}.</returns> Task<Stream> GetSubtitles(string itemId, string mediaSourceId, @@ -24,6 +17,7 @@ namespace MediaBrowser.Controller.MediaEncoding string outputFormat, long startTimeTicks, long? endTimeTicks, + bool preserveOriginalTimestamps, CancellationToken cancellationToken); /// <summary> diff --git a/MediaBrowser.MediaEncoding/Subtitles/SubtitleEncoder.cs b/MediaBrowser.MediaEncoding/Subtitles/SubtitleEncoder.cs index 8c33cc7c0..25adbcdb0 100644 --- a/MediaBrowser.MediaEncoding/Subtitles/SubtitleEncoder.cs +++ b/MediaBrowser.MediaEncoding/Subtitles/SubtitleEncoder.cs @@ -58,6 +58,7 @@ namespace MediaBrowser.MediaEncoding.Subtitles string outputFormat, long startTimeTicks, long? endTimeTicks, + bool preserveOriginalTimestamps, CancellationToken cancellationToken) { var ms = new MemoryStream(); @@ -68,7 +69,7 @@ namespace MediaBrowser.MediaEncoding.Subtitles var trackInfo = reader.Parse(stream, cancellationToken); - FilterEvents(trackInfo, startTimeTicks, endTimeTicks, false); + FilterEvents(trackInfo, startTimeTicks, endTimeTicks, preserveOriginalTimestamps); var writer = GetWriter(outputFormat); @@ -116,6 +117,7 @@ namespace MediaBrowser.MediaEncoding.Subtitles string outputFormat, long startTimeTicks, long? endTimeTicks, + bool preserveOriginalTimestamps, CancellationToken cancellationToken) { var subtitle = await GetSubtitleStream(itemId, mediaSourceId, subtitleStreamIndex, cancellationToken) @@ -130,7 +132,7 @@ namespace MediaBrowser.MediaEncoding.Subtitles using (var stream = subtitle.Item1) { - return await ConvertSubtitles(stream, inputFormat, outputFormat, startTimeTicks, endTimeTicks, cancellationToken).ConfigureAwait(false); + return await ConvertSubtitles(stream, inputFormat, outputFormat, startTimeTicks, endTimeTicks, preserveOriginalTimestamps, cancellationToken).ConfigureAwait(false); } } diff --git a/MediaBrowser.Server.Implementations/IO/FileRefresher.cs b/MediaBrowser.Server.Implementations/IO/FileRefresher.cs index 74dfbc679..18c52ab29 100644 --- a/MediaBrowser.Server.Implementations/IO/FileRefresher.cs +++ b/MediaBrowser.Server.Implementations/IO/FileRefresher.cs @@ -5,6 +5,7 @@ using System.Linq; using System.Threading; using System.Threading.Tasks; using CommonIO; +using MediaBrowser.Common.Events; using MediaBrowser.Common.ScheduledTasks; using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Entities; @@ -24,9 +25,14 @@ namespace MediaBrowser.Server.Implementations.IO private readonly List<string> _affectedPaths = new List<string>(); private Timer _timer; private readonly object _timerLock = new object(); + public string Path { get; private set; } + + public event EventHandler<EventArgs> Completed; public FileRefresher(string path, IFileSystem fileSystem, IServerConfigurationManager configurationManager, ILibraryManager libraryManager, ITaskManager taskManager, ILogger logger) { + logger.Debug("New file refresher created for {0}", path); + Path = path; _affectedPaths.Add(path); _fileSystem = fileSystem; @@ -36,7 +42,24 @@ namespace MediaBrowser.Server.Implementations.IO Logger = logger; } - private void RestartTimer() + private void AddAffectedPath(string path) + { + if (!_affectedPaths.Contains(path, StringComparer.Ordinal)) + { + _affectedPaths.Add(path); + } + } + + public void AddPath(string path) + { + lock (_timerLock) + { + AddAffectedPath(path); + } + RestartTimer(); + } + + public void RestartTimer() { lock (_timerLock) { @@ -51,6 +74,23 @@ namespace MediaBrowser.Server.Implementations.IO } } + public void ResetPath(string path, string affectedFile) + { + lock (_timerLock) + { + Logger.Debug("Resetting file refresher from {0} to {1}", Path, path); + + Path = path; + AddAffectedPath(path); + + if (!string.IsNullOrWhiteSpace(affectedFile)) + { + AddAffectedPath(affectedFile); + } + } + RestartTimer(); + } + private async void OnTimerCallback(object state) { // Extend the timer as long as any of the paths are still being written to. @@ -64,10 +104,11 @@ namespace MediaBrowser.Server.Implementations.IO Logger.Debug("Timer stopped."); DisposeTimer(); + EventHelper.FireEventIfNotNull(Completed, this, EventArgs.Empty, Logger); try { - await ProcessPathChanges(_affectedPaths).ConfigureAwait(false); + await ProcessPathChanges(_affectedPaths.ToList()).ConfigureAwait(false); } catch (Exception ex) { @@ -130,7 +171,7 @@ namespace MediaBrowser.Server.Implementations.IO { item = LibraryManager.FindByPath(path, null); - path = Path.GetDirectoryName(path); + path = System.IO.Path.GetDirectoryName(path); } if (item != null) @@ -222,7 +263,7 @@ namespace MediaBrowser.Server.Implementations.IO } } - public void DisposeTimer() + private void DisposeTimer() { lock (_timerLock) { diff --git a/MediaBrowser.Server.Implementations/IO/LibraryMonitor.cs b/MediaBrowser.Server.Implementations/IO/LibraryMonitor.cs index 09ca134d1..0690d62dd 100644 --- a/MediaBrowser.Server.Implementations/IO/LibraryMonitor.cs +++ b/MediaBrowser.Server.Implementations/IO/LibraryMonitor.cs @@ -26,13 +26,9 @@ namespace MediaBrowser.Server.Implementations.IO /// </summary> private readonly ConcurrentDictionary<string, FileSystemWatcher> _fileSystemWatchers = new ConcurrentDictionary<string, FileSystemWatcher>(StringComparer.OrdinalIgnoreCase); /// <summary> - /// The update timer - /// </summary> - private Timer _updateTimer; - /// <summary> /// The affected paths /// </summary> - private readonly ConcurrentDictionary<string, string> _affectedPaths = new ConcurrentDictionary<string, string>(); + private readonly List<FileRefresher> _activeRefreshers = new List<FileRefresher>(); /// <summary> /// A dynamic list of paths that should be ignored. Added to during our own file sytem modifications. @@ -44,8 +40,8 @@ namespace MediaBrowser.Server.Implementations.IO /// </summary> private readonly IReadOnlyList<string> _alwaysIgnoreFiles = new List<string> { - "thumbs.db", - "small.jpg", + "thumbs.db", + "small.jpg", "albumart.jpg", // WMC temp recording directories that will constantly be written to @@ -54,11 +50,6 @@ namespace MediaBrowser.Server.Implementations.IO }; /// <summary> - /// The timer lock - /// </summary> - private readonly object _timerLock = new object(); - - /// <summary> /// Add the path to our temporary ignore list. Use when writing to a path within our listening scope. /// </summary> /// <param name="path">The path.</param> @@ -463,226 +454,58 @@ namespace MediaBrowser.Server.Implementations.IO if (monitorPath) { // Avoid implicitly captured closure - var affectedPath = path; - _affectedPaths.AddOrUpdate(path, path, (key, oldValue) => affectedPath); - } - - RestartTimer(); - } - - private void RestartTimer() - { - lock (_timerLock) - { - if (_updateTimer == null) - { - _updateTimer = new Timer(TimerStopped, null, TimeSpan.FromSeconds(ConfigurationManager.Configuration.LibraryMonitorDelay), TimeSpan.FromMilliseconds(-1)); - } - else - { - _updateTimer.Change(TimeSpan.FromSeconds(ConfigurationManager.Configuration.LibraryMonitorDelay), TimeSpan.FromMilliseconds(-1)); - } - } - } - - /// <summary> - /// Timers the stopped. - /// </summary> - /// <param name="stateInfo">The state info.</param> - private async void TimerStopped(object stateInfo) - { - // Extend the timer as long as any of the paths are still being written to. - if (_affectedPaths.Any(p => IsFileLocked(p.Key))) - { - Logger.Info("Timer extended."); - RestartTimer(); - return; - } - - Logger.Debug("Timer stopped."); - - DisposeTimer(); - - var paths = _affectedPaths.Keys.ToList(); - _affectedPaths.Clear(); - - try - { - await ProcessPathChanges(paths).ConfigureAwait(false); - } - catch (Exception ex) - { - Logger.ErrorException("Error processing directory changes", ex); + CreateRefresher(path); } } - private bool IsFileLocked(string path) + private void CreateRefresher(string path) { - if (Environment.OSVersion.Platform != PlatformID.Win32NT) - { - // Causing lockups on linux - return false; - } + var parentPath = Path.GetDirectoryName(path); - try + lock (_activeRefreshers) { - var data = _fileSystem.GetFileSystemInfo(path); - - if (!data.Exists - || data.IsDirectory - - // Opening a writable stream will fail with readonly files - || data.Attributes.HasFlag(FileAttributes.ReadOnly)) + var refreshers = _activeRefreshers.ToList(); + foreach (var refresher in refreshers) { - return false; - } - } - catch (IOException) - { - return false; - } - catch (Exception ex) - { - Logger.ErrorException("Error getting file system info for: {0}", ex, path); - return false; - } - - // In order to determine if the file is being written to, we have to request write access - // But if the server only has readonly access, this is going to cause this entire algorithm to fail - // So we'll take a best guess about our access level - var requestedFileAccess = ConfigurationManager.Configuration.SaveLocalMeta - ? FileAccess.ReadWrite - : FileAccess.Read; + // Path is already being refreshed + if (string.Equals(path, refresher.Path, StringComparison.Ordinal)) + { + refresher.RestartTimer(); + return; + } - try - { - using (_fileSystem.GetFileStream(path, FileMode.Open, requestedFileAccess, FileShare.ReadWrite)) - { - if (_updateTimer != null) + // Parent folder is already being refreshed + if (_fileSystem.ContainsSubPath(refresher.Path, path)) { - //file is not locked - return false; + refresher.AddPath(path); + return; } - } - } - catch (DirectoryNotFoundException) - { - // File may have been deleted - return false; - } - catch (FileNotFoundException) - { - // File may have been deleted - return false; - } - catch (IOException) - { - //the file is unavailable because it is: - //still being written to - //or being processed by another thread - //or does not exist (has already been processed) - Logger.Debug("{0} is locked.", path); - return true; - } - catch (Exception ex) - { - Logger.ErrorException("Error determining if file is locked: {0}", ex, path); - return false; - } - return false; - } + // New path is a parent + if (_fileSystem.ContainsSubPath(path, refresher.Path)) + { + refresher.ResetPath(path, null); + return; + } - private void DisposeTimer() - { - lock (_timerLock) - { - if (_updateTimer != null) - { - _updateTimer.Dispose(); - _updateTimer = null; + // They are siblings. Rebase the refresher to the parent folder. + if (string.Equals(parentPath, Path.GetDirectoryName(refresher.Path), StringComparison.Ordinal)) + { + refresher.ResetPath(parentPath, path); + return; + } } - } - } - - /// <summary> - /// Processes the path changes. - /// </summary> - /// <param name="paths">The paths.</param> - /// <returns>Task.</returns> - private async Task ProcessPathChanges(List<string> paths) - { - var itemsToRefresh = paths - .Select(GetAffectedBaseItem) - .Where(item => item != null) - .Distinct() - .ToList(); - - foreach (var p in paths) - { - Logger.Info(p + " reports change."); - } - - // If the root folder changed, run the library task so the user can see it - if (itemsToRefresh.Any(i => i is AggregateFolder)) - { - TaskManager.CancelIfRunningAndQueue<RefreshMediaLibraryTask>(); - return; - } - - foreach (var item in itemsToRefresh) - { - Logger.Info(item.Name + " (" + item.Path + ") will be refreshed."); - try - { - await item.ChangedExternally().ConfigureAwait(false); - } - catch (IOException ex) - { - // For now swallow and log. - // Research item: If an IOException occurs, the item may be in a disconnected state (media unavailable) - // Should we remove it from it's parent? - Logger.ErrorException("Error refreshing {0}", ex, item.Name); - } - catch (Exception ex) - { - Logger.ErrorException("Error refreshing {0}", ex, item.Name); - } + var newRefresher = new FileRefresher(path, _fileSystem, ConfigurationManager, LibraryManager, TaskManager, Logger); + newRefresher.Completed += NewRefresher_Completed; + _activeRefreshers.Add(newRefresher); } } - /// <summary> - /// Gets the affected base item. - /// </summary> - /// <param name="path">The path.</param> - /// <returns>BaseItem.</returns> - private BaseItem GetAffectedBaseItem(string path) + private void NewRefresher_Completed(object sender, EventArgs e) { - BaseItem item = null; - - while (item == null && !string.IsNullOrEmpty(path)) - { - item = LibraryManager.FindByPath(path, null); - - path = Path.GetDirectoryName(path); - } - - if (item != null) - { - // If the item has been deleted find the first valid parent that still exists - while (!_fileSystem.DirectoryExists(item.Path) && !_fileSystem.FileExists(item.Path)) - { - item = item.GetParent(); - - if (item == null) - { - break; - } - } - } - - return item; + var refresher = (FileRefresher)sender; + DisposeRefresher(refresher); } /// <summary> @@ -713,10 +536,29 @@ namespace MediaBrowser.Server.Implementations.IO watcher.Dispose(); } - DisposeTimer(); - _fileSystemWatchers.Clear(); - _affectedPaths.Clear(); + DisposeRefreshers(); + } + + private void DisposeRefresher(FileRefresher refresher) + { + lock (_activeRefreshers) + { + refresher.Dispose(); + _activeRefreshers.Remove(refresher); + } + } + + private void DisposeRefreshers() + { + lock (_activeRefreshers) + { + foreach (var refresher in _activeRefreshers.ToList()) + { + refresher.Dispose(); + } + _activeRefreshers.Clear(); + } } /// <summary> diff --git a/MediaBrowser.Server.Implementations/LiveTv/LiveTvManager.cs b/MediaBrowser.Server.Implementations/LiveTv/LiveTvManager.cs index bbdc4ab0d..e126e5411 100644 --- a/MediaBrowser.Server.Implementations/LiveTv/LiveTvManager.cs +++ b/MediaBrowser.Server.Implementations/LiveTv/LiveTvManager.cs @@ -1959,10 +1959,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv var defaults = await GetNewTimerDefaultsInternal(cancellationToken, program).ConfigureAwait(false); var info = _tvDtoService.GetSeriesTimerInfoDto(defaults.Item1, defaults.Item2, null); - info.Days = new List<DayOfWeek> - { - program.StartDate.ToLocalTime().DayOfWeek - }; + info.Days = defaults.Item1.Days; info.DayPattern = _tvDtoService.GetDayPattern(info.Days); diff --git a/MediaBrowser.Server.Implementations/Sync/SyncJobProcessor.cs b/MediaBrowser.Server.Implementations/Sync/SyncJobProcessor.cs index bbba06870..398dcc86b 100644 --- a/MediaBrowser.Server.Implementations/Sync/SyncJobProcessor.cs +++ b/MediaBrowser.Server.Implementations/Sync/SyncJobProcessor.cs @@ -748,7 +748,7 @@ namespace MediaBrowser.Server.Implementations.Sync _fileSystem.CreateDirectory(Path.GetDirectoryName(path)); - using (var stream = await _subtitleEncoder.GetSubtitles(streamInfo.ItemId, streamInfo.MediaSourceId, subtitleStreamIndex, subtitleStreamInfo.Format, 0, null, cancellationToken).ConfigureAwait(false)) + using (var stream = await _subtitleEncoder.GetSubtitles(streamInfo.ItemId, streamInfo.MediaSourceId, subtitleStreamIndex, subtitleStreamInfo.Format, 0, null, false, cancellationToken).ConfigureAwait(false)) { using (var fs = _fileSystem.GetFileStream(path, FileMode.Create, FileAccess.Write, FileShare.Read, true)) { diff --git a/MediaBrowser.WebDashboard/Api/DashboardService.cs b/MediaBrowser.WebDashboard/Api/DashboardService.cs index 1569c1fc5..904953cfc 100644 --- a/MediaBrowser.WebDashboard/Api/DashboardService.cs +++ b/MediaBrowser.WebDashboard/Api/DashboardService.cs @@ -356,9 +356,6 @@ namespace MediaBrowser.WebDashboard.Api DeleteFoldersByName(Path.Combine(bowerPath, "Sortable"), "meteor"); DeleteFoldersByName(Path.Combine(bowerPath, "Sortable"), "st"); DeleteFoldersByName(Path.Combine(bowerPath, "Swiper"), "src"); - DeleteFoldersByName(Path.Combine(bowerPath, "material-design-lite"), "src"); - DeleteFoldersByName(Path.Combine(bowerPath, "material-design-lite"), "utils"); - _fileSystem.DeleteFile(Path.Combine(bowerPath, "material-design-lite", "gulpfile.babel.js")); _fileSystem.DeleteDirectory(Path.Combine(bowerPath, "marked"), true); _fileSystem.DeleteDirectory(Path.Combine(bowerPath, "marked-element"), true); |
