diff --git a/.gitignore b/.gitignore
index d1a9a9a44..d537ab816 100644
--- a/.gitignore
+++ b/.gitignore
@@ -84,7 +84,6 @@ Generated_Code #added for RIA/Silverlight projects
# Backup & report files from converting an old project file to a newer
# Visual Studio version. Backup files are not needed, because we have git ;-)
_UpgradeReport_Files/
-Backup*/
UpgradeLog*.XML
# SQL Server files
diff --git a/src/NzbDrone.Api/Frontend/Mappers/BackupFileMapper.cs b/src/NzbDrone.Api/Frontend/Mappers/BackupFileMapper.cs
new file mode 100644
index 000000000..a2e111430
--- /dev/null
+++ b/src/NzbDrone.Api/Frontend/Mappers/BackupFileMapper.cs
@@ -0,0 +1,31 @@
+using System.IO;
+using NLog;
+using NzbDrone.Common;
+using NzbDrone.Common.Disk;
+using NzbDrone.Common.EnvironmentInfo;
+
+namespace NzbDrone.Api.Frontend.Mappers
+{
+ public class BackupFileMapper : StaticResourceMapperBase
+ {
+ private readonly IAppFolderInfo _appFolderInfo;
+
+ public BackupFileMapper(IAppFolderInfo appFolderInfo, IDiskProvider diskProvider, Logger logger)
+ : base(diskProvider, logger)
+ {
+ _appFolderInfo = appFolderInfo;
+ }
+
+ protected override string Map(string resourceUrl)
+ {
+ var path = resourceUrl.Replace("/backup/", "").Replace('/', Path.DirectorySeparatorChar);
+
+ return Path.Combine(_appFolderInfo.GetBackupFolder(), path);
+ }
+
+ public override bool CanHandle(string resourceUrl)
+ {
+ return resourceUrl.StartsWith("/backup/") && resourceUrl.ContainsIgnoreCase("nzbdrone_backup_") && resourceUrl.EndsWith(".zip");
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/NzbDrone.Api/NzbDrone.Api.csproj b/src/NzbDrone.Api/NzbDrone.Api.csproj
index f0ab8561f..d4d52cb96 100644
--- a/src/NzbDrone.Api/NzbDrone.Api.csproj
+++ b/src/NzbDrone.Api/NzbDrone.Api.csproj
@@ -131,6 +131,7 @@
+
@@ -165,6 +166,8 @@
+
+
diff --git a/src/NzbDrone.Api/System/Backup/BackupModule.cs b/src/NzbDrone.Api/System/Backup/BackupModule.cs
new file mode 100644
index 000000000..b5074793e
--- /dev/null
+++ b/src/NzbDrone.Api/System/Backup/BackupModule.cs
@@ -0,0 +1,32 @@
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using NzbDrone.Core.Backup;
+
+namespace NzbDrone.Api.System.Backup
+{
+ public class BackupModule : NzbDroneRestModule
+ {
+ private readonly IBackupService _backupService;
+
+ public BackupModule(IBackupService backupService) : base("system/backup")
+ {
+ _backupService = backupService;
+ GetResourceAll = GetBackupFiles;
+ }
+
+ public List GetBackupFiles()
+ {
+ var backups = _backupService.GetBackups();
+
+ return backups.Select(b => new BackupResource
+ {
+ Id = b.Path.GetHashCode(),
+ Name = Path.GetFileName(b.Path),
+ Path = b.Path,
+ Type = b.Type,
+ Time = b.Time
+ }).ToList();
+ }
+ }
+}
diff --git a/src/NzbDrone.Api/System/Backup/BackupResource.cs b/src/NzbDrone.Api/System/Backup/BackupResource.cs
new file mode 100644
index 000000000..732eee3c0
--- /dev/null
+++ b/src/NzbDrone.Api/System/Backup/BackupResource.cs
@@ -0,0 +1,14 @@
+using System;
+using NzbDrone.Api.REST;
+using NzbDrone.Core.Backup;
+
+namespace NzbDrone.Api.System.Backup
+{
+ public class BackupResource : RestResource
+ {
+ public String Name { get; set; }
+ public String Path { get; set; }
+ public BackupType Type { get; set; }
+ public DateTime Time { get; set; }
+ }
+}
diff --git a/src/NzbDrone.Common.Test/DiskProviderTests/DiskProviderFixtureBase.cs b/src/NzbDrone.Common.Test/DiskProviderTests/DiskProviderFixtureBase.cs
index 5af9890ba..493914c12 100644
--- a/src/NzbDrone.Common.Test/DiskProviderTests/DiskProviderFixtureBase.cs
+++ b/src/NzbDrone.Common.Test/DiskProviderTests/DiskProviderFixtureBase.cs
@@ -172,7 +172,7 @@ namespace NzbDrone.Common.Test.DiskProviderTests
public void empty_folder_should_return_folder_modified_date()
{
var tempfolder = new DirectoryInfo(TempFolder);
- Subject.FolderGetLastWrite(TempFolder).Should().Be(tempfolder.LastWriteTimeUtc);
+ Subject.FolderGetLastWriteUtc(TempFolder).Should().Be(tempfolder.LastWriteTimeUtc);
}
[Test]
@@ -189,8 +189,8 @@ namespace NzbDrone.Common.Test.DiskProviderTests
Subject.WriteAllText(testFile, "Test");
- Subject.FolderGetLastWrite(TempFolder).Should().BeOnOrAfter(DateTime.UtcNow.AddMinutes(-1));
- Subject.FolderGetLastWrite(TempFolder).Should().BeBefore(DateTime.UtcNow.AddMinutes(1));
+ Subject.FolderGetLastWriteUtc(TempFolder).Should().BeOnOrAfter(DateTime.UtcNow.AddMinutes(-1));
+ Subject.FolderGetLastWriteUtc(TempFolder).Should().BeBefore(DateTime.UtcNow.AddMinutes(1));
}
[Test]
@@ -238,7 +238,7 @@ namespace NzbDrone.Common.Test.DiskProviderTests
[Explicit]
public void check_last_write()
{
- Console.WriteLine(Subject.FolderGetLastWrite(GetFilledTempFolder().FullName));
+ Console.WriteLine(Subject.FolderGetLastWriteUtc(GetFilledTempFolder().FullName));
Console.WriteLine(GetFilledTempFolder().LastWriteTimeUtc);
}
diff --git a/src/NzbDrone.Common/ArchiveProvider.cs b/src/NzbDrone.Common/ArchiveService.cs
similarity index 86%
rename from src/NzbDrone.Common/ArchiveProvider.cs
rename to src/NzbDrone.Common/ArchiveService.cs
index 5d7d644d5..1899d8f3d 100644
--- a/src/NzbDrone.Common/ArchiveProvider.cs
+++ b/src/NzbDrone.Common/ArchiveService.cs
@@ -12,6 +12,8 @@ namespace NzbDrone.Common
public interface IArchiveService
{
void Extract(string compressedFile, string destination);
+ void ExtractZip(string compressedFile, string destination);
+ void CreateZip(string path, params string[] files);
}
public class ArchiveService : IArchiveService
@@ -40,7 +42,22 @@ namespace NzbDrone.Common
_logger.Debug("Extraction complete.");
}
- private void ExtractZip(string compressedFile, string destination)
+ public void CreateZip(string path, params string[] files)
+ {
+ using (var zipFile = ZipFile.Create(path))
+ {
+ zipFile.BeginUpdate();
+
+ foreach (var file in files)
+ {
+ zipFile.Add(file, Path.GetFileName(file));
+ }
+
+ zipFile.CommitUpdate();
+ }
+ }
+
+ public void ExtractZip(string compressedFile, string destination)
{
using (var fileStream = File.OpenRead(compressedFile))
{
diff --git a/src/NzbDrone.Common/Disk/DiskProviderBase.cs b/src/NzbDrone.Common/Disk/DiskProviderBase.cs
index c9de8d11c..540c694cd 100644
--- a/src/NzbDrone.Common/Disk/DiskProviderBase.cs
+++ b/src/NzbDrone.Common/Disk/DiskProviderBase.cs
@@ -33,7 +33,7 @@ namespace NzbDrone.Common.Disk
return new DirectoryInfo(path).CreationTimeUtc;
}
- public DateTime FolderGetLastWrite(string path)
+ public DateTime FolderGetLastWriteUtc(string path)
{
CheckFolderExists(path);
diff --git a/src/NzbDrone.Common/Disk/IDiskProvider.cs b/src/NzbDrone.Common/Disk/IDiskProvider.cs
index afbd7ce60..473a0211d 100644
--- a/src/NzbDrone.Common/Disk/IDiskProvider.cs
+++ b/src/NzbDrone.Common/Disk/IDiskProvider.cs
@@ -12,7 +12,7 @@ namespace NzbDrone.Common.Disk
void SetPermissions(string path, string mask, string user, string group);
long? GetTotalSize(string path);
DateTime FolderGetCreationTimeUtc(string path);
- DateTime FolderGetLastWrite(string path);
+ DateTime FolderGetLastWriteUtc(string path);
DateTime FileGetCreationTimeUtc(string path);
DateTime FileGetLastWrite(string path);
DateTime FileGetLastWriteUtc(string path);
diff --git a/src/NzbDrone.Common/NzbDrone.Common.csproj b/src/NzbDrone.Common/NzbDrone.Common.csproj
index bab538ec7..e9bdd86f0 100644
--- a/src/NzbDrone.Common/NzbDrone.Common.csproj
+++ b/src/NzbDrone.Common/NzbDrone.Common.csproj
@@ -59,8 +59,8 @@
-
+
diff --git a/src/NzbDrone.Common/PathExtensions.cs b/src/NzbDrone.Common/PathExtensions.cs
index f57051f61..b99d39a69 100644
--- a/src/NzbDrone.Common/PathExtensions.cs
+++ b/src/NzbDrone.Common/PathExtensions.cs
@@ -11,9 +11,9 @@ namespace NzbDrone.Common
private const string APP_CONFIG_FILE = "config.xml";
private const string NZBDRONE_DB = "nzbdrone.db";
private const string NZBDRONE_LOG_DB = "logs.db";
- private const string BACKUP_ZIP_FILE = "NzbDrone_Backup.zip";
private const string NLOG_CONFIG_FILE = "nlog.config";
private const string UPDATE_CLIENT_EXE = "NzbDrone.Update.exe";
+ private const string BACKUP_FOLDER = "Backups";
private static readonly string UPDATE_SANDBOX_FOLDER_NAME = "nzbdrone_update" + Path.DirectorySeparatorChar;
private static readonly string UPDATE_PACKAGE_FOLDER_NAME = "NzbDrone" + Path.DirectorySeparatorChar;
@@ -226,9 +226,9 @@ namespace NzbDrone.Common
return Path.Combine(GetUpdateSandboxFolder(appFolderInfo), UPDATE_CLIENT_EXE);
}
- public static string GetConfigBackupFile(this IAppFolderInfo appFolderInfo)
+ public static string GetBackupFolder(this IAppFolderInfo appFolderInfo)
{
- return Path.Combine(GetAppDataPath(appFolderInfo), BACKUP_ZIP_FILE);
+ return Path.Combine(GetAppDataPath(appFolderInfo), BACKUP_FOLDER);
}
public static string GetNzbDroneDatabase(this IAppFolderInfo appFolderInfo)
diff --git a/src/NzbDrone.Core.Test/ProviderTests/RecycleBinProviderTests/CleanupFixture.cs b/src/NzbDrone.Core.Test/ProviderTests/RecycleBinProviderTests/CleanupFixture.cs
index 9436770ae..2f92eae2a 100644
--- a/src/NzbDrone.Core.Test/ProviderTests/RecycleBinProviderTests/CleanupFixture.cs
+++ b/src/NzbDrone.Core.Test/ProviderTests/RecycleBinProviderTests/CleanupFixture.cs
@@ -18,7 +18,7 @@ namespace NzbDrone.Core.Test.ProviderTests.RecycleBinProviderTests
private void WithExpired()
{
- Mocker.GetMock().Setup(s => s.FolderGetLastWrite(It.IsAny()))
+ Mocker.GetMock().Setup(s => s.FolderGetLastWriteUtc(It.IsAny()))
.Returns(DateTime.UtcNow.AddDays(-10));
Mocker.GetMock().Setup(s => s.FileGetLastWriteUtc(It.IsAny()))
@@ -27,7 +27,7 @@ namespace NzbDrone.Core.Test.ProviderTests.RecycleBinProviderTests
private void WithNonExpired()
{
- Mocker.GetMock().Setup(s => s.FolderGetLastWrite(It.IsAny()))
+ Mocker.GetMock().Setup(s => s.FolderGetLastWriteUtc(It.IsAny()))
.Returns(DateTime.UtcNow.AddDays(-3));
Mocker.GetMock().Setup(s => s.FileGetLastWriteUtc(It.IsAny()))
diff --git a/src/NzbDrone.Core/Backup/Backup.cs b/src/NzbDrone.Core/Backup/Backup.cs
new file mode 100644
index 000000000..880ef6106
--- /dev/null
+++ b/src/NzbDrone.Core/Backup/Backup.cs
@@ -0,0 +1,11 @@
+using System;
+
+namespace NzbDrone.Core.Backup
+{
+ public class Backup
+ {
+ public String Path { get; set; }
+ public BackupType Type { get; set; }
+ public DateTime Time { get; set; }
+ }
+}
diff --git a/src/NzbDrone.Core/Backup/BackupCommand.cs b/src/NzbDrone.Core/Backup/BackupCommand.cs
new file mode 100644
index 000000000..29199c67a
--- /dev/null
+++ b/src/NzbDrone.Core/Backup/BackupCommand.cs
@@ -0,0 +1,24 @@
+using NzbDrone.Core.Messaging.Commands;
+
+namespace NzbDrone.Core.Backup
+{
+ public class BackupCommand : Command
+ {
+ public BackupType Type { get; set; }
+
+ public override bool SendUpdatesToClient
+ {
+ get
+ {
+ return true;
+ }
+ }
+ }
+
+ public enum BackupType
+ {
+ Scheduled = 0 ,
+ Manual = 1,
+ Update = 2
+ }
+}
diff --git a/src/NzbDrone.Core/Backup/BackupService.cs b/src/NzbDrone.Core/Backup/BackupService.cs
new file mode 100644
index 000000000..eb90fa3d2
--- /dev/null
+++ b/src/NzbDrone.Core/Backup/BackupService.cs
@@ -0,0 +1,168 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Text.RegularExpressions;
+using Marr.Data;
+using NLog;
+using NzbDrone.Common;
+using NzbDrone.Common.Disk;
+using NzbDrone.Common.EnvironmentInfo;
+using NzbDrone.Core.Datastore;
+using NzbDrone.Core.Instrumentation.Extensions;
+using NzbDrone.Core.Messaging.Commands;
+
+namespace NzbDrone.Core.Backup
+{
+ public interface IBackupService
+ {
+ void Backup(BackupType backupType);
+ List GetBackups();
+ }
+
+ public class BackupService : IBackupService, IExecute
+ {
+ private readonly IDatabase _maindDb;
+ private readonly IDiskProvider _diskProvider;
+ private readonly IAppFolderInfo _appFolderInfo;
+ private readonly IArchiveService _archiveService;
+ private readonly Logger _logger;
+
+ private string _backupTempFolder;
+
+ private static readonly Regex BackupFileRegex = new Regex(@"nzbdrone_backup_[._0-9]+\.zip", RegexOptions.Compiled | RegexOptions.IgnoreCase);
+
+ public BackupService(IDatabase maindDb,
+ IDiskProvider diskProvider,
+ IAppFolderInfo appFolderInfo,
+ IArchiveService archiveService,
+ Logger logger)
+ {
+ _maindDb = maindDb;
+ _diskProvider = diskProvider;
+ _appFolderInfo = appFolderInfo;
+ _archiveService = archiveService;
+ _logger = logger;
+
+ _backupTempFolder = Path.Combine(_appFolderInfo.TempFolder, "nzbdrone_backup");
+ }
+
+ public void Backup(BackupType backupType)
+ {
+ _logger.ProgressInfo("Starting Backup");
+
+ var backupFilename = String.Format("nzbdrone_backup_{0:yyyy.MM.dd_HH.mm.ss}.zip", DateTime.Now);
+ var backupPath = Path.Combine(GetBackupFolder(backupType), backupFilename);
+
+ Cleanup();
+
+ if (backupType != BackupType.Manual)
+ {
+ CleanupOldBackups(backupType);
+ }
+
+ _diskProvider.EnsureFolder(_backupTempFolder);
+ _diskProvider.EnsureFolder(_appFolderInfo.GetBackupFolder());
+
+ BackupConfigFile();
+ BackupDatabase();
+
+ _logger.ProgressDebug("Creating backup zip");
+ _archiveService.CreateZip(backupPath, _diskProvider.GetFiles(_backupTempFolder, SearchOption.TopDirectoryOnly));
+ _logger.ProgressDebug("Backup zip created");
+ }
+
+ public List GetBackups()
+ {
+ var backups = new List();
+
+ foreach (var backupType in Enum.GetValues(typeof(BackupType)).Cast())
+ {
+ var folder = GetBackupFolder(backupType);
+
+ if (_diskProvider.FolderExists(folder))
+ {
+ backups.AddRange(GetBackupFiles(folder).Select(b => new Backup
+ {
+ Path = Path.GetFileName(b),
+ Type = backupType,
+ Time = _diskProvider.FileGetLastWriteUtc(b)
+ }));
+ }
+ }
+
+ return backups;
+ }
+
+ private void Cleanup()
+ {
+ if (_diskProvider.FolderExists(_backupTempFolder))
+ {
+ _diskProvider.EmptyFolder(_backupTempFolder);
+ }
+ }
+
+ private void BackupDatabase()
+ {
+ _logger.ProgressDebug("Backing up database");
+
+ using (var unitOfWork = new UnitOfWork(() => _maindDb.GetDataMapper()))
+ {
+ unitOfWork.BeginTransaction();
+
+ var databaseFile = _appFolderInfo.GetNzbDroneDatabase();
+ var tempDatabaseFile = Path.Combine(_backupTempFolder, Path.GetFileName(databaseFile));
+
+ _diskProvider.CopyFile(databaseFile, tempDatabaseFile, true);
+
+ unitOfWork.Commit();
+ }
+ }
+
+ private void BackupConfigFile()
+ {
+ _logger.ProgressDebug("Backing up config.xml");
+
+ var configFile = _appFolderInfo.GetConfigPath();
+ var tempConfigFile = Path.Combine(_backupTempFolder, Path.GetFileName(configFile));
+
+ _diskProvider.CopyFile(configFile, tempConfigFile, true);
+ }
+
+ private void CleanupOldBackups(BackupType backupType)
+ {
+ _logger.Debug("Cleaning up old backup files");
+ var files = GetBackupFiles(GetBackupFolder(backupType));
+
+ foreach (var file in files)
+ {
+ var lastWriteTime = _diskProvider.FileGetLastWriteUtc(file);
+
+ if (lastWriteTime.AddDays(28) < DateTime.UtcNow)
+ {
+ _logger.Debug("Deleting old backup file: {0}", file);
+ _diskProvider.DeleteFile(file);
+ }
+ }
+
+ _logger.Debug("Finished cleaning up old backup files");
+ }
+
+ private String GetBackupFolder(BackupType backupType)
+ {
+ return Path.Combine(_appFolderInfo.GetBackupFolder(), backupType.ToString().ToLower());
+ }
+
+ private IEnumerable GetBackupFiles(String path)
+ {
+ var files = _diskProvider.GetFiles(path, SearchOption.TopDirectoryOnly);
+
+ return files.Where(f => BackupFileRegex.IsMatch(f));
+ }
+
+ public void Execute(BackupCommand message)
+ {
+ Backup(message.Type);
+ }
+ }
+}
diff --git a/src/NzbDrone.Core/Jobs/TaskManager.cs b/src/NzbDrone.Core/Jobs/TaskManager.cs
index 788e6df0c..4656f3a76 100644
--- a/src/NzbDrone.Core/Jobs/TaskManager.cs
+++ b/src/NzbDrone.Core/Jobs/TaskManager.cs
@@ -2,6 +2,7 @@ using System;
using System.Collections.Generic;
using System.Linq;
using NLog;
+using NzbDrone.Core.Backup;
using NzbDrone.Core.Configuration;
using NzbDrone.Core.Configuration.Events;
using NzbDrone.Core.DataAugmentation.Scene;
@@ -56,6 +57,7 @@ namespace NzbDrone.Core.Jobs
new ScheduledTask{ Interval = 6*60, TypeName = typeof(CheckHealthCommand).FullName},
new ScheduledTask{ Interval = 12*60, TypeName = typeof(RefreshSeriesCommand).FullName},
new ScheduledTask{ Interval = 24*60, TypeName = typeof(HousekeepingCommand).FullName},
+ new ScheduledTask{ Interval = 7*24*60, TypeName = typeof(BackupCommand).FullName},
new ScheduledTask
{
diff --git a/src/NzbDrone.Core/MediaFiles/RecycleBinProvider.cs b/src/NzbDrone.Core/MediaFiles/RecycleBinProvider.cs
index ac4fa3c3d..b8fad1a47 100644
--- a/src/NzbDrone.Core/MediaFiles/RecycleBinProvider.cs
+++ b/src/NzbDrone.Core/MediaFiles/RecycleBinProvider.cs
@@ -128,7 +128,7 @@ namespace NzbDrone.Core.MediaFiles
foreach (var folder in _diskProvider.GetDirectories(_configService.RecycleBin))
{
- if (_diskProvider.FolderGetLastWrite(folder).AddDays(7) > DateTime.UtcNow)
+ if (_diskProvider.FolderGetLastWriteUtc(folder).AddDays(7) > DateTime.UtcNow)
{
logger.Debug("Folder hasn't expired yet, skipping: {0}", folder);
continue;
diff --git a/src/NzbDrone.Core/NzbDrone.Core.csproj b/src/NzbDrone.Core/NzbDrone.Core.csproj
index 9b758ab25..9e4e64c3f 100644
--- a/src/NzbDrone.Core/NzbDrone.Core.csproj
+++ b/src/NzbDrone.Core/NzbDrone.Core.csproj
@@ -104,6 +104,9 @@
Properties\SharedAssemblyInfo.cs
+
+
+
diff --git a/src/UI/Cells/cells.less b/src/UI/Cells/cells.less
index 8d591460b..98c5fbf3a 100644
--- a/src/UI/Cells/cells.less
+++ b/src/UI/Cells/cells.less
@@ -168,3 +168,7 @@ td.delete-episode-file-cell {
.episode-number-cell {
cursor : default;
}
+
+.backup-type-cell {
+ width : 20px;
+}
diff --git a/src/UI/Content/theme.less b/src/UI/Content/theme.less
index 47a0b323f..6c13b071b 100644
--- a/src/UI/Content/theme.less
+++ b/src/UI/Content/theme.less
@@ -95,6 +95,8 @@
}
th {
+ cursor : default;
+
&.sortable {
&:hover {
background : @table-bg-hover;
diff --git a/src/UI/Controller.js b/src/UI/Controller.js
index 19d7c3760..87ef4e535 100644
--- a/src/UI/Controller.js
+++ b/src/UI/Controller.js
@@ -12,7 +12,6 @@ define(
'Release/ReleaseLayout',
'System/SystemLayout',
'SeasonPass/SeasonPassLayout',
- 'System/Update/UpdateLayout',
'Series/Editor/SeriesEditorLayout'
], function (NzbDroneController,
AppLayout,
@@ -25,7 +24,6 @@ define(
ReleaseLayout,
SystemLayout,
SeasonPassLayout,
- UpdateLayout,
SeriesEditorLayout) {
return NzbDroneController.extend({
@@ -71,11 +69,6 @@ define(
this.showMainRegion(new SeasonPassLayout());
},
- update: function () {
- this.setTitle('Updates');
- this.showMainRegion(new UpdateLayout());
- },
-
seriesEditor: function () {
this.setTitle('Series Editor');
this.showMainRegion(new SeriesEditorLayout());
diff --git a/src/UI/System/Backup/BackupCollection.js b/src/UI/System/Backup/BackupCollection.js
new file mode 100644
index 000000000..6b52ca3b9
--- /dev/null
+++ b/src/UI/System/Backup/BackupCollection.js
@@ -0,0 +1,19 @@
+'use strict';
+define(
+ [
+ 'backbone.pageable',
+ 'System/Backup/BackupModel'
+ ], function (PageableCollection, BackupModel) {
+ return PageableCollection.extend({
+ url : window.NzbDrone.ApiRoot + '/system/backup',
+ model: BackupModel,
+
+ state: {
+ sortKey : 'time',
+ order : 1,
+ pageSize : 100000
+ },
+
+ mode: 'client'
+ });
+ });
diff --git a/src/UI/System/Backup/BackupEmptyView.js b/src/UI/System/Backup/BackupEmptyView.js
new file mode 100644
index 000000000..51e72eafd
--- /dev/null
+++ b/src/UI/System/Backup/BackupEmptyView.js
@@ -0,0 +1,10 @@
+'use strict';
+
+define(
+ [
+ 'marionette'
+ ], function (Marionette) {
+ return Marionette.ItemView.extend({
+ template: 'System/Backup/BackupEmptyViewTemplate'
+ });
+ });
diff --git a/src/UI/System/Backup/BackupEmptyViewTemplate.html b/src/UI/System/Backup/BackupEmptyViewTemplate.html
new file mode 100644
index 000000000..5a480c62a
--- /dev/null
+++ b/src/UI/System/Backup/BackupEmptyViewTemplate.html
@@ -0,0 +1 @@
+No backups are available
\ No newline at end of file
diff --git a/src/UI/System/Backup/BackupFilenameCell.js b/src/UI/System/Backup/BackupFilenameCell.js
new file mode 100644
index 000000000..f853bdf6e
--- /dev/null
+++ b/src/UI/System/Backup/BackupFilenameCell.js
@@ -0,0 +1,12 @@
+'use strict';
+define(
+ [
+ 'Cells/TemplatedCell'
+ ], function (TemplatedCell) {
+ return TemplatedCell.extend({
+
+ className: 'series-title',
+ template : 'System/Backup/BackupFilenameCellTemplate'
+
+ });
+ });
diff --git a/src/UI/System/Backup/BackupFilenameCellTemplate.html b/src/UI/System/Backup/BackupFilenameCellTemplate.html
new file mode 100644
index 000000000..9dc32e9f5
--- /dev/null
+++ b/src/UI/System/Backup/BackupFilenameCellTemplate.html
@@ -0,0 +1,2 @@
+{{name}}
+
diff --git a/src/UI/System/Backup/BackupLayout.js b/src/UI/System/Backup/BackupLayout.js
new file mode 100644
index 000000000..98840384d
--- /dev/null
+++ b/src/UI/System/Backup/BackupLayout.js
@@ -0,0 +1,106 @@
+'use strict';
+define(
+ [
+ 'vent',
+ 'marionette',
+ 'backgrid',
+ 'System/Backup/BackupCollection',
+ 'Cells/RelativeDateCell',
+ 'System/Backup/BackupFilenameCell',
+ 'System/Backup/BackupTypeCell',
+ 'System/Backup/BackupEmptyView',
+ 'Shared/LoadingView',
+ 'Shared/Toolbar/ToolbarLayout'
+ ], function (vent, Marionette, Backgrid, BackupCollection, RelativeDateCell, BackupFilenameCell, BackupTypeCell, EmptyView, LoadingView, ToolbarLayout) {
+ return Marionette.Layout.extend({
+ template: 'System/Backup/BackupLayoutTemplate',
+
+ regions: {
+ backups : '#x-backups',
+ toolbar : '#x-backup-toolbar'
+ },
+
+ columns: [
+ {
+ name : 'type',
+ label : '',
+ sortable : false,
+ cell : BackupTypeCell
+ },
+ {
+ name : 'this',
+ label : 'Name',
+ sortable : false,
+ cell : BackupFilenameCell
+ },
+ {
+ name : 'time',
+ label : 'Time',
+ sortable : false,
+ cell : RelativeDateCell
+ }
+ ],
+
+ leftSideButtons: {
+ type : 'default',
+ storeState: false,
+ collapse : false,
+ items :
+ [
+ {
+ title : 'Backup',
+ icon : 'icon-file-text',
+ command : 'backup',
+ properties : { type: 'manual' },
+ successMessage: 'Database and settings were backed up successfully',
+ errorMessage : 'Backup Failed!'
+ }
+ ]
+ },
+
+ initialize: function () {
+ this.backupCollection = new BackupCollection();
+
+ this.listenTo(this.backupCollection, 'sync', this._showBackups);
+ this.listenTo(vent, vent.Events.CommandComplete, this._commandComplete);
+ },
+
+ onRender: function () {
+ this._showToolbar();
+ this.backups.show(new LoadingView());
+
+ this.backupCollection.fetch();
+ },
+
+ _showBackups: function () {
+
+ if (this.backupCollection.length === 0) {
+ this.backups.show(new EmptyView());
+ }
+
+ else {
+ this.backups.show(new Backgrid.Grid({
+ columns : this.columns,
+ collection: this.backupCollection,
+ className : 'table table-hover'
+ }));
+ }
+ },
+
+ _showToolbar : function () {
+ this.toolbar.show(new ToolbarLayout({
+ left :
+ [
+ this.leftSideButtons
+ ],
+ context: this
+ }));
+ },
+
+ _commandComplete: function (options) {
+ if (options.command.get('name') === 'backup') {
+ this.backupCollection.fetch();
+ }
+ }
+ });
+ });
diff --git a/src/UI/System/Backup/BackupLayoutTemplate.html b/src/UI/System/Backup/BackupLayoutTemplate.html
new file mode 100644
index 000000000..b2b9f91f9
--- /dev/null
+++ b/src/UI/System/Backup/BackupLayoutTemplate.html
@@ -0,0 +1,10 @@
+
+
diff --git a/src/UI/System/Backup/BackupModel.js b/src/UI/System/Backup/BackupModel.js
new file mode 100644
index 000000000..530a080c6
--- /dev/null
+++ b/src/UI/System/Backup/BackupModel.js
@@ -0,0 +1,9 @@
+'use strict';
+define(
+ [
+ 'backbone'
+ ], function (Backbone) {
+ return Backbone.Model.extend({
+
+ });
+ });
diff --git a/src/UI/System/Backup/BackupTypeCell.js b/src/UI/System/Backup/BackupTypeCell.js
new file mode 100644
index 000000000..d836299bb
--- /dev/null
+++ b/src/UI/System/Backup/BackupTypeCell.js
@@ -0,0 +1,33 @@
+'use strict';
+define(
+ [
+ 'Cells/NzbDroneCell'
+ ], function (NzbDroneCell) {
+ return NzbDroneCell.extend({
+
+ className: 'backup-type-cell',
+
+ render: function () {
+ this.$el.empty();
+
+ var icon = 'icon-time';
+ var title = 'Scheduled';
+
+ var type = this.model.get(this.column.get('name'));
+
+ if (type === 'manual') {
+ icon = 'icon-book';
+ title = 'Manual';
+ }
+
+ else if (type === 'update') {
+ icon = 'icon-retweet';
+ title = 'Before update';
+ }
+
+ this.$el.html(''.format(icon, title));
+
+ return this;
+ }
+ });
+ });
diff --git a/src/UI/System/SystemLayout.js b/src/UI/System/SystemLayout.js
index 3500681c7..29e8562f7 100644
--- a/src/UI/System/SystemLayout.js
+++ b/src/UI/System/SystemLayout.js
@@ -7,6 +7,7 @@ define(
'System/Info/SystemInfoLayout',
'System/Logs/LogsLayout',
'System/Update/UpdateLayout',
+ 'System/Backup/BackupLayout',
'Shared/Messenger'
], function ($,
Backbone,
@@ -14,26 +15,30 @@ define(
SystemInfoLayout,
LogsLayout,
UpdateLayout,
+ BackupLayout,
Messenger) {
return Marionette.Layout.extend({
template: 'System/SystemLayoutTemplate',
regions: {
- info : '#info',
+ info : '#info',
logs : '#logs',
- updates: '#updates'
+ updates : '#updates',
+ backup : '#backup'
},
ui: {
- infoTab : '.x-info-tab',
- logsTab : '.x-logs-tab',
- updatesTab: '.x-updates-tab'
+ infoTab : '.x-info-tab',
+ logsTab : '.x-logs-tab',
+ updatesTab : '.x-updates-tab',
+ backupTab : '.x-backup-tab'
},
events: {
'click .x-info-tab' : '_showInfo',
'click .x-logs-tab' : '_showLogs',
'click .x-updates-tab': '_showUpdates',
+ 'click .x-backup-tab': '_showBackup',
'click .x-shutdown' : '_shutdown',
'click .x-restart' : '_restart'
},
@@ -52,6 +57,9 @@ define(
case 'updates':
this._showUpdates();
break;
+ case 'backup':
+ this._showBackup();
+ break;
default:
this._showInfo();
}
@@ -91,6 +99,16 @@ define(
this._navigate('system/updates');
},
+ _showBackup: function (e) {
+ if (e) {
+ e.preventDefault();
+ }
+
+ this.backup.show(new BackupLayout());
+ this.ui.backupTab.tab('show');
+ this._navigate('system/backup');
+ },
+
_shutdown: function () {
$.ajax({
url: window.NzbDrone.ApiRoot + '/system/shutdown',
diff --git a/src/UI/System/SystemLayoutTemplate.html b/src/UI/System/SystemLayoutTemplate.html
index e3b245715..aa6996850 100644
--- a/src/UI/System/SystemLayoutTemplate.html
+++ b/src/UI/System/SystemLayoutTemplate.html
@@ -2,6 +2,7 @@
Info
Logs
Updates
+ Backup
\ No newline at end of file