diff --git a/NzbDrone.Common.Test/EnviromentProviderTest.cs b/NzbDrone.Common.Test/EnvironmentProviderTest.cs similarity index 100% rename from NzbDrone.Common.Test/EnviromentProviderTest.cs rename to NzbDrone.Common.Test/EnvironmentProviderTest.cs diff --git a/NzbDrone.Common.Test/EnvironmentTests/EnviromentProviderTest.cs b/NzbDrone.Common.Test/EnvironmentTests/EnvironmentProviderTest.cs similarity index 100% rename from NzbDrone.Common.Test/EnvironmentTests/EnviromentProviderTest.cs rename to NzbDrone.Common.Test/EnvironmentTests/EnvironmentProviderTest.cs diff --git a/NzbDrone.Common.Test/NzbDrone.Common.Test.csproj b/NzbDrone.Common.Test/NzbDrone.Common.Test.csproj index dd04fc5bf..5e20e20f9 100644 --- a/NzbDrone.Common.Test/NzbDrone.Common.Test.csproj +++ b/NzbDrone.Common.Test/NzbDrone.Common.Test.csproj @@ -64,13 +64,13 @@ - + - + diff --git a/NzbDrone.Core/Datastore/BasicRepository.cs b/NzbDrone.Core/Datastore/BasicRepository.cs index bcfe31652..eee37e2cf 100644 --- a/NzbDrone.Core/Datastore/BasicRepository.cs +++ b/NzbDrone.Core/Datastore/BasicRepository.cs @@ -32,6 +32,7 @@ namespace NzbDrone.Core.Datastore void SetFields(TModel model, params Expression>[] properties); TModel Single(); PagingSpec GetPaged(PagingSpec pagingSpec); + void DeleteAll(); } @@ -217,6 +218,10 @@ namespace NzbDrone.Core.Datastore return pagingSpec; } + public void DeleteAll() + { + DataMapper.Delete(c => c.Id > 0); + } private void PublishModelEvent(TModel model, RepositoryAction action) { diff --git a/NzbDrone.Core/Instrumentation/Commands/DeleteLogFilesCommand.cs b/NzbDrone.Core/Instrumentation/Commands/DeleteLogFilesCommand.cs new file mode 100644 index 000000000..5d3228afb --- /dev/null +++ b/NzbDrone.Core/Instrumentation/Commands/DeleteLogFilesCommand.cs @@ -0,0 +1,8 @@ +using NzbDrone.Common.Messaging; + +namespace NzbDrone.Core.Instrumentation.Commands +{ + public class DeleteLogFilesCommand : ICommand + { + } +} \ No newline at end of file diff --git a/NzbDrone.Core/Instrumentation/DeleteLogFilesService.cs b/NzbDrone.Core/Instrumentation/DeleteLogFilesService.cs new file mode 100644 index 000000000..5c98646ad --- /dev/null +++ b/NzbDrone.Core/Instrumentation/DeleteLogFilesService.cs @@ -0,0 +1,40 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using NLog; +using NzbDrone.Common; +using NzbDrone.Common.EnvironmentInfo; +using NzbDrone.Common.Messaging; +using NzbDrone.Core.Instrumentation.Commands; + +namespace NzbDrone.Core.Instrumentation +{ + public interface IDeleteLogFilesService + { + } + + public class DeleteLogFilesService : IDeleteLogFilesService, IExecute + { + private readonly IDiskProvider _diskProvider; + private readonly IAppFolderInfo _appFolderInfo; + private readonly Logger _logger; + + public DeleteLogFilesService(IDiskProvider diskProvider, IAppFolderInfo appFolderInfo, Logger logger) + { + _diskProvider = diskProvider; + _appFolderInfo = appFolderInfo; + _logger = logger; + } + + public void Execute(DeleteLogFilesCommand message) + { + var logFiles = _diskProvider.GetFiles(_appFolderInfo.GetLogFolder(), SearchOption.TopDirectoryOnly); + + foreach (var logFile in logFiles) + { + _diskProvider.DeleteFile(logFile); + } + } + } +} diff --git a/NzbDrone.Core/Instrumentation/LogService.cs b/NzbDrone.Core/Instrumentation/LogService.cs index 56f38a345..42ed10a79 100644 --- a/NzbDrone.Core/Instrumentation/LogService.cs +++ b/NzbDrone.Core/Instrumentation/LogService.cs @@ -1,4 +1,5 @@ -using NzbDrone.Common.Messaging; +using System; +using NzbDrone.Common.Messaging; using NzbDrone.Core.Datastore; using NzbDrone.Core.Instrumentation.Commands; @@ -9,7 +10,7 @@ namespace NzbDrone.Core.Instrumentation PagingSpec Paged(PagingSpec pagingSpec); } - public class LogService : ILogService, IExecute + public class LogService : ILogService, IExecute, IExecute { private readonly ILogRepository _logRepository; @@ -27,5 +28,10 @@ namespace NzbDrone.Core.Instrumentation { _logRepository.Trim(); } + + public void Execute(ClearLogCommand message) + { + _logRepository.DeleteAll(); + } } } \ No newline at end of file diff --git a/NzbDrone.Core/NzbDrone.Core.csproj b/NzbDrone.Core/NzbDrone.Core.csproj index b717a5269..c6a7dbede 100644 --- a/NzbDrone.Core/NzbDrone.Core.csproj +++ b/NzbDrone.Core/NzbDrone.Core.csproj @@ -207,7 +207,10 @@ + + + diff --git a/UI/Logs/Files/Layout.js b/UI/Logs/Files/Layout.js index dafb51a6c..f170f3999 100644 --- a/UI/Logs/Files/Layout.js +++ b/UI/Logs/Files/Layout.js @@ -9,12 +9,14 @@ define( 'Logs/Files/Collection', 'Logs/Files/Row', 'Logs/Files/ContentsView', - 'Logs/Files/ContentsModel' - ], function (App, Marionette, Backgrid, FilenameCell, RelativeDateCell, LogFileCollection, LogFileRow, ContentsView, ContentsModel) { + 'Logs/Files/ContentsModel', + 'Shared/Toolbar/ToolbarLayout' + ], function (App, Marionette, Backgrid, FilenameCell, RelativeDateCell, LogFileCollection, LogFileRow, ContentsView, ContentsModel, ToolbarLayout) { return Marionette.Layout.extend({ template: 'Logs/Files/LayoutTemplate', regions: { + toolbar : '#x-toolbar', grid : '#x-grid', contents : '#x-contents' }, @@ -35,22 +37,62 @@ define( initialize: function () { this.collection = new LogFileCollection(); - this.collectionPromise = this.collection.fetch(); + App.vent.on(App.Commands.ShowLogFile, this._showLogFile, this); }, onShow: function () { - var self = this; + this._fetchAndShow(); + this._showToolbar(); this._showTable(); + }, - this.collectionPromise.done(function (){ - self._showLogFile({ model: _.first(self.collection.models) }); + _fetchAndShow: function () { + var self = this; + + var promise = this.collection.fetch(); + promise.done(function () { + self._showLogFile({ model: self.collection.first() }); }); }, - _showTable: function () { + _showToolbar: function () { + var leftSideButtons = { + type : 'default', + storeState: false, + items : + [ + { + title : 'Refresh', + icon : 'icon-refresh', + ownerContext : this, + callback : this._refreshLogs + }, + + { + title : 'Delete Log Files', + icon : 'icon-trash', + command : 'deleteLogFiles', + successMessage : 'Log files have been deleted', + errorMessage : 'Failed to delete log files', + ownerContext : this, + successCallback: this._refreshLogs + } + ] + }; + + this.toolbar.show(new ToolbarLayout({ + left : + [ + leftSideButtons + ], + context: this + })); + }, + + _showTable: function () { this.grid.show(new Backgrid.Grid({ row : LogFileRow, columns : this.columns, @@ -60,6 +102,13 @@ define( }, _showLogFile: function (options) { + + this.contents.close(); + + if (!options.model) { + return; + } + var self = this; var filename = options.model.get('filename'); @@ -74,6 +123,10 @@ define( self.contents.show(new ContentsView({ model: model })); } }); + }, + + _refreshLogs: function () { + this._fetchAndShow(); } }); }); diff --git a/UI/Logs/Files/LayoutTemplate.html b/UI/Logs/Files/LayoutTemplate.html index 5852266e8..df28d4b8a 100644 --- a/UI/Logs/Files/LayoutTemplate.html +++ b/UI/Logs/Files/LayoutTemplate.html @@ -1,4 +1,5 @@ -
+
+
diff --git a/UI/Logs/Layout.js b/UI/Logs/Layout.js index f6965761d..fda86cc62 100644 --- a/UI/Logs/Layout.js +++ b/UI/Logs/Layout.js @@ -53,19 +53,6 @@ define( } ], - leftSideButtons: { - type : 'default', - storeState: false, - items : - [ - { - title: 'Files', - icon : 'icon-file', - route: 'logs/files' - } - ] - }, - initialize: function () { this.collection = new LogCollection(); this.collection.fetch(); @@ -92,13 +79,48 @@ define( }, _showToolbar: function () { + var leftSideButtons = { + type : 'default', + storeState: false, + items : + [ + { + title : 'Refresh', + icon : 'icon-refresh', + ownerContext : this, + callback : this._refreshLogs + }, + + { + title : 'Clear Logs', + icon : 'icon-trash', + command : 'clearLog', + successMessage : 'Logs have been cleared', + errorMessage : 'Failed to clear logs', + ownerContext : this, + successCallback: this._refreshLogs + }, + + { + title: 'Files', + icon : 'icon-file', + route: 'logs/files' + } + ] + }; + this.toolbar.show(new ToolbarLayout({ left : [ - this.leftSideButtons + leftSideButtons ], context: this })); + }, + + _refreshLogs: function () { + this.collection.fetch({ reset: true }); + this._showTable(); } }); }); diff --git a/UI/Shared/Toolbar/Button/ButtonView.js b/UI/Shared/Toolbar/Button/ButtonView.js index be8639a45..8dc30d6c3 100644 --- a/UI/Shared/Toolbar/Button/ButtonView.js +++ b/UI/Shared/Toolbar/Button/ButtonView.js @@ -44,8 +44,9 @@ define( } }, - invokeCommand: function () { + //TODO: Use Actioneer to handle icon swapping + var command = this.model.get('command'); if (command) { this.idle = false; @@ -60,18 +61,35 @@ define( message: self.model.get('successMessage') }); } + + if (self.model.get('successCallback')) { + if (!self.model.ownerContext) { + throw 'ownerContext must be set.'; + } + + self.model.get('successCallback').call(self.model.ownerContext); + } }); commandPromise.fail(function (options) { if (options.readyState === 0 || options.status === 0) { return; } + if (self.model.get('errorMessage')) { Messenger.show({ message: self.model.get('errorMessage'), type : 'error' }); } + + if (self.model.get('failCallback')) { + if (!self.model.ownerContext) { + throw 'ownerContext must be set.'; + } + + self.model.get('failCallback').call(self.model.ownerContext); + } }); commandPromise.always(function () { @@ -81,6 +99,14 @@ define( self.idle = true; } }); + + if (self.model.get('alwaysCallback')) { + if (!self.model.ownerContext) { + throw 'ownerContext must be set.'; + } + + self.model.get('alwaysCallback').call(self.model.ownerContext); + } } }, @@ -103,7 +129,6 @@ define( throw 'ownerContext must be set.'; } - var callback = this.model.get('callback'); if (callback) { callback.call(this.model.ownerContext);