Manage multiple Tv Root Folders in Settings/General.
Start of AddExisting.
This commit is contained in:
parent
2e9dd7f1ff
commit
2871723bfe
|
@ -66,25 +66,25 @@ namespace NzbDrone.Core.Test
|
||||||
//Assert.AreEqual(title, result, postTitle);
|
//Assert.AreEqual(title, result, postTitle);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
//[Test]
|
||||||
public void get_unmapped()
|
//public void get_unmapped()
|
||||||
{
|
//{
|
||||||
//Setup
|
// //Setup
|
||||||
var kernel = new MockingKernel();
|
// var kernel = new MockingKernel();
|
||||||
|
|
||||||
|
|
||||||
kernel.Bind<ISeriesProvider>().To<SeriesProvider>();
|
// kernel.Bind<ISeriesProvider>().To<SeriesProvider>();
|
||||||
kernel.Bind<IDiskProvider>().ToConstant(MockLib.GetStandardDisk(0, 0));
|
// kernel.Bind<IDiskProvider>().ToConstant(MockLib.GetStandardDisk(0, 0));
|
||||||
kernel.Bind<IConfigProvider>().ToConstant(MockLib.StandardConfig);
|
// kernel.Bind<IConfigProvider>().ToConstant(MockLib.StandardConfig);
|
||||||
|
|
||||||
var seriesController = kernel.Get<ISeriesProvider>();
|
// var seriesController = kernel.Get<ISeriesProvider>();
|
||||||
|
|
||||||
//Act
|
// //Act
|
||||||
var unmappedFolder = seriesController.GetUnmappedFolders();
|
// var unmappedFolder = seriesController.GetUnmappedFolders();
|
||||||
|
|
||||||
//Assert
|
// //Assert
|
||||||
Assert.AreElementsEqualIgnoringOrder(MockLib.StandardSeries, unmappedFolder.Values);
|
// Assert.AreElementsEqualIgnoringOrder(MockLib.StandardSeries, unmappedFolder.Values);
|
||||||
}
|
//}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -62,6 +62,7 @@ namespace NzbDrone.Core
|
||||||
_kernel.Bind<IHttpProvider>().To<HttpProvider>();
|
_kernel.Bind<IHttpProvider>().To<HttpProvider>();
|
||||||
_kernel.Bind<IHistoryProvider>().To<HistoryProvider>();
|
_kernel.Bind<IHistoryProvider>().To<HistoryProvider>();
|
||||||
_kernel.Bind<IQualityProvider>().To<QualityProvider>();
|
_kernel.Bind<IQualityProvider>().To<QualityProvider>();
|
||||||
|
_kernel.Bind<IRootDirProvider>().To<RootDirProvider>();
|
||||||
_kernel.Bind<IExtenalNotificationProvider>().To<ExternalNotificationProvider>();
|
_kernel.Bind<IExtenalNotificationProvider>().To<ExternalNotificationProvider>();
|
||||||
_kernel.Bind<IXbmcProvider>().To<XbmcProvider>();
|
_kernel.Bind<IXbmcProvider>().To<XbmcProvider>();
|
||||||
_kernel.Bind<IConfigProvider>().To<ConfigProvider>().InSingletonScope();
|
_kernel.Bind<IConfigProvider>().To<ConfigProvider>().InSingletonScope();
|
||||||
|
|
|
@ -183,6 +183,7 @@
|
||||||
<Compile Include="Providers\IPostProcessingProvider.cs" />
|
<Compile Include="Providers\IPostProcessingProvider.cs" />
|
||||||
<Compile Include="Providers\IQualityProvider.cs" />
|
<Compile Include="Providers\IQualityProvider.cs" />
|
||||||
<Compile Include="Providers\IRenameProvider.cs" />
|
<Compile Include="Providers\IRenameProvider.cs" />
|
||||||
|
<Compile Include="Providers\IRootDirProvider.cs" />
|
||||||
<Compile Include="Providers\IRssSyncProvider.cs" />
|
<Compile Include="Providers\IRssSyncProvider.cs" />
|
||||||
<Compile Include="Providers\IRssProvider.cs" />
|
<Compile Include="Providers\IRssProvider.cs" />
|
||||||
<Compile Include="Providers\ITimerProvider.cs" />
|
<Compile Include="Providers\ITimerProvider.cs" />
|
||||||
|
@ -190,6 +191,7 @@
|
||||||
<Compile Include="Providers\PostProcessingProvider.cs" />
|
<Compile Include="Providers\PostProcessingProvider.cs" />
|
||||||
<Compile Include="Providers\QualityProvider.cs" />
|
<Compile Include="Providers\QualityProvider.cs" />
|
||||||
<Compile Include="Providers\RenameProvider.cs" />
|
<Compile Include="Providers\RenameProvider.cs" />
|
||||||
|
<Compile Include="Providers\RootDirProvider.cs" />
|
||||||
<Compile Include="Providers\RssSyncProvider.cs" />
|
<Compile Include="Providers\RssSyncProvider.cs" />
|
||||||
<Compile Include="Providers\RssProvider.cs" />
|
<Compile Include="Providers\RssProvider.cs" />
|
||||||
<Compile Include="Providers\TimerProvider.cs" />
|
<Compile Include="Providers\TimerProvider.cs" />
|
||||||
|
@ -229,6 +231,7 @@
|
||||||
<Compile Include="Repository\Indexer.cs" />
|
<Compile Include="Repository\Indexer.cs" />
|
||||||
<Compile Include="Repository\Config.cs" />
|
<Compile Include="Repository\Config.cs" />
|
||||||
<Compile Include="Repository\Quality\QualityProfile.cs" />
|
<Compile Include="Repository\Quality\QualityProfile.cs" />
|
||||||
|
<Compile Include="Repository\RootDir.cs" />
|
||||||
<Compile Include="Repository\Season.cs" />
|
<Compile Include="Repository\Season.cs" />
|
||||||
<Compile Include="Repository\Quality\QualityTypes.cs" />
|
<Compile Include="Repository\Quality\QualityTypes.cs" />
|
||||||
<Compile Include="Repository\Series.cs" />
|
<Compile Include="Repository\Series.cs" />
|
||||||
|
|
|
@ -0,0 +1,16 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using NzbDrone.Core.Repository;
|
||||||
|
|
||||||
|
namespace NzbDrone.Core.Providers
|
||||||
|
{
|
||||||
|
public interface IRootDirProvider
|
||||||
|
{
|
||||||
|
List<RootDir> GetAll();
|
||||||
|
void Add(RootDir rootDir);
|
||||||
|
void Remove(int rootDirId);
|
||||||
|
void Update(RootDir rootDir);
|
||||||
|
}
|
||||||
|
}
|
|
@ -18,13 +18,12 @@ namespace NzbDrone.Core.Providers
|
||||||
/// <param name="id">The TVDB ID of the series</param>
|
/// <param name="id">The TVDB ID of the series</param>
|
||||||
/// <returns>Whether or not the show is monitored</returns>
|
/// <returns>Whether or not the show is monitored</returns>
|
||||||
bool IsMonitored(long id);
|
bool IsMonitored(long id);
|
||||||
|
|
||||||
TvdbSeries MapPathToSeries(string path);
|
TvdbSeries MapPathToSeries(string path);
|
||||||
void AddSeries(string path, TvdbSeries series);
|
void AddSeries(string path, TvdbSeries series);
|
||||||
Dictionary<Guid, String> GetUnmappedFolders();
|
|
||||||
Series FindSeries(string cleanTitle);
|
Series FindSeries(string cleanTitle);
|
||||||
bool QualityWanted(int seriesId, QualityTypes quality);
|
bool QualityWanted(int seriesId, QualityTypes quality);
|
||||||
void UpdateSeries(Series series);
|
void UpdateSeries(Series series);
|
||||||
void DeleteSeries(int seriesId);
|
void DeleteSeries(int seriesId);
|
||||||
|
bool SeriesPathExists(string cleanPath);
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -1,8 +1,11 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
namespace NzbDrone.Core.Providers
|
namespace NzbDrone.Core.Providers
|
||||||
{
|
{
|
||||||
public interface ISyncProvider
|
public interface ISyncProvider
|
||||||
{
|
{
|
||||||
void SyncUnmappedFolders();
|
bool BeginSyncUnmappedFolders(List<string> paths);
|
||||||
void BeginSyncUnmappedFolders();
|
List<String> GetUnmappedFolders(string path);
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -0,0 +1,43 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using NzbDrone.Core.Repository;
|
||||||
|
using SubSonic.Repository;
|
||||||
|
|
||||||
|
namespace NzbDrone.Core.Providers
|
||||||
|
{
|
||||||
|
public class RootDirProvider : IRootDirProvider
|
||||||
|
{
|
||||||
|
private readonly IRepository _sonioRepo;
|
||||||
|
|
||||||
|
public RootDirProvider(IRepository sonicRepo)
|
||||||
|
{
|
||||||
|
_sonioRepo = sonicRepo;
|
||||||
|
}
|
||||||
|
|
||||||
|
#region IRootDirProvider
|
||||||
|
|
||||||
|
public List<RootDir> GetAll()
|
||||||
|
{
|
||||||
|
return _sonioRepo.All<RootDir>().ToList();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Add(RootDir rootDir)
|
||||||
|
{
|
||||||
|
_sonioRepo.Add(rootDir);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Remove(int rootDirId)
|
||||||
|
{
|
||||||
|
_sonioRepo.Delete<RootDir>(rootDirId);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Update(RootDir rootDir)
|
||||||
|
{
|
||||||
|
_sonioRepo.Update(rootDir);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
}
|
||||||
|
}
|
|
@ -65,26 +65,6 @@ namespace NzbDrone.Core.Providers
|
||||||
return profile.Allowed.Contains(quality);
|
return profile.Allowed.Contains(quality);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Dictionary<Guid, String> GetUnmappedFolders()
|
|
||||||
{
|
|
||||||
Logger.Debug("Generating list of unmapped folders");
|
|
||||||
if (String.IsNullOrEmpty(_config.SeriesRoot))
|
|
||||||
throw new InvalidOperationException("TV Series folder is not configured yet.");
|
|
||||||
|
|
||||||
var results = new Dictionary<Guid, String>();
|
|
||||||
foreach (string seriesFolder in _diskProvider.GetDirectories(_config.SeriesRoot))
|
|
||||||
{
|
|
||||||
var cleanPath = Parser.NormalizePath(new DirectoryInfo(seriesFolder).FullName);
|
|
||||||
if (!_sonioRepo.Exists<Series>(s => s.Path == cleanPath))
|
|
||||||
{
|
|
||||||
results.Add(Guid.NewGuid(), cleanPath);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Logger.Debug("{0} unmapped folders detected.", results.Count);
|
|
||||||
return results;
|
|
||||||
}
|
|
||||||
|
|
||||||
public TvdbSeries MapPathToSeries(string path)
|
public TvdbSeries MapPathToSeries(string path)
|
||||||
{
|
{
|
||||||
var seriesPath = new DirectoryInfo(path);
|
var seriesPath = new DirectoryInfo(path);
|
||||||
|
@ -149,6 +129,14 @@ namespace NzbDrone.Core.Providers
|
||||||
_sonioRepo.Delete<Series>(seriesId);
|
_sonioRepo.Delete<Series>(seriesId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public bool SeriesPathExists(string cleanPath)
|
||||||
|
{
|
||||||
|
if (_sonioRepo.Exists<Series>(s => s.Path == cleanPath))
|
||||||
|
return true;
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
#region Static Helpers
|
#region Static Helpers
|
||||||
|
|
|
@ -3,7 +3,6 @@ using System.Collections.Generic;
|
||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Text;
|
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using NLog;
|
using NLog;
|
||||||
using NzbDrone.Core.Model.Notification;
|
using NzbDrone.Core.Model.Notification;
|
||||||
|
@ -16,21 +15,54 @@ namespace NzbDrone.Core.Providers
|
||||||
private readonly IEpisodeProvider _episodeProvider;
|
private readonly IEpisodeProvider _episodeProvider;
|
||||||
private readonly IMediaFileProvider _mediaFileProvider;
|
private readonly IMediaFileProvider _mediaFileProvider;
|
||||||
private readonly INotificationProvider _notificationProvider;
|
private readonly INotificationProvider _notificationProvider;
|
||||||
|
private readonly IDiskProvider _diskProvider;
|
||||||
|
|
||||||
private ProgressNotification _seriesSyncNotification;
|
private ProgressNotification _seriesSyncNotification;
|
||||||
private Thread _seriesSyncThread;
|
private Thread _seriesSyncThread;
|
||||||
|
private List<string> _syncList;
|
||||||
|
|
||||||
private static readonly Logger Logger = LogManager.GetCurrentClassLogger();
|
private static readonly Logger Logger = LogManager.GetCurrentClassLogger();
|
||||||
|
|
||||||
public SyncProvider(ISeriesProvider seriesProvider, IEpisodeProvider episodeProvider, IMediaFileProvider mediaFileProvider, INotificationProvider notificationProvider)
|
public SyncProvider(ISeriesProvider seriesProvider, IEpisodeProvider episodeProvider,
|
||||||
|
IMediaFileProvider mediaFileProvider, INotificationProvider notificationProvider,
|
||||||
|
IDiskProvider diskProvider)
|
||||||
{
|
{
|
||||||
_seriesProvider = seriesProvider;
|
_seriesProvider = seriesProvider;
|
||||||
_episodeProvider = episodeProvider;
|
_episodeProvider = episodeProvider;
|
||||||
_mediaFileProvider = mediaFileProvider;
|
_mediaFileProvider = mediaFileProvider;
|
||||||
_notificationProvider = notificationProvider;
|
_notificationProvider = notificationProvider;
|
||||||
|
_diskProvider = diskProvider;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void BeginSyncUnmappedFolders()
|
#region ISyncProvider Members
|
||||||
|
|
||||||
|
public List<String> GetUnmappedFolders(string path)
|
||||||
|
{
|
||||||
|
Logger.Debug("Generating list of unmapped folders");
|
||||||
|
if (String.IsNullOrEmpty(path))
|
||||||
|
throw new InvalidOperationException("Invalid path provided");
|
||||||
|
|
||||||
|
if (!_diskProvider.FolderExists(path))
|
||||||
|
{
|
||||||
|
Logger.Debug("Path supplied does not exist: {0}", path);
|
||||||
|
}
|
||||||
|
|
||||||
|
var results = new List<String>();
|
||||||
|
foreach (string seriesFolder in _diskProvider.GetDirectories(path))
|
||||||
|
{
|
||||||
|
var cleanPath = Parser.NormalizePath(new DirectoryInfo(seriesFolder).FullName);
|
||||||
|
|
||||||
|
if (!_seriesProvider.SeriesPathExists(cleanPath))
|
||||||
|
results.Add(cleanPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
Logger.Debug("{0} unmapped folders detected.", results.Count);
|
||||||
|
return results;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
public bool BeginSyncUnmappedFolders(List<string> paths)
|
||||||
{
|
{
|
||||||
Logger.Debug("User has request series folder scan");
|
Logger.Debug("User has request series folder scan");
|
||||||
if (_seriesSyncThread == null || !_seriesSyncThread.IsAlive)
|
if (_seriesSyncThread == null || !_seriesSyncThread.IsAlive)
|
||||||
|
@ -42,15 +74,22 @@ namespace NzbDrone.Core.Providers
|
||||||
Priority = ThreadPriority.Lowest
|
Priority = ThreadPriority.Lowest
|
||||||
};
|
};
|
||||||
|
|
||||||
|
_syncList = paths;
|
||||||
_seriesSyncThread.Start();
|
_seriesSyncThread.Start();
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
Logger.Warn("Series folder scan already in progress. Ignoring request.");
|
Logger.Warn("Series folder scan already in progress. Ignoring request.");
|
||||||
}
|
|
||||||
|
//return false if sync was already running, then we can tell the user to try again later
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void SyncUnmappedFolders()
|
//return true if sync has started
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void SyncUnmappedFolders()
|
||||||
{
|
{
|
||||||
Logger.Info("Starting Series folder scan");
|
Logger.Info("Starting Series folder scan");
|
||||||
|
|
||||||
|
@ -60,15 +99,20 @@ namespace NzbDrone.Core.Providers
|
||||||
{
|
{
|
||||||
_notificationProvider.Register(_seriesSyncNotification);
|
_notificationProvider.Register(_seriesSyncNotification);
|
||||||
_seriesSyncNotification.CurrentStatus = "Analysing Folder";
|
_seriesSyncNotification.CurrentStatus = "Analysing Folder";
|
||||||
var unmappedFolders = _seriesProvider.GetUnmappedFolders();
|
_seriesSyncNotification.ProgressMax = _syncList.Count;
|
||||||
_seriesSyncNotification.ProgressMax = unmappedFolders.Count;
|
|
||||||
|
|
||||||
foreach (string seriesFolder in unmappedFolders.Values)
|
foreach (var seriesFolder in _syncList)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
_seriesSyncNotification.CurrentStatus = String.Format("Searching For: {0}", CultureInfo.CurrentCulture.TextInfo.ToTitleCase(new DirectoryInfo(seriesFolder).Name));
|
_seriesSyncNotification.CurrentStatus = String.Format("Searching For: {0}", CultureInfo.CurrentCulture.TextInfo.ToTitleCase(new DirectoryInfo(seriesFolder).Name));
|
||||||
|
|
||||||
|
if (_seriesProvider.SeriesPathExists(Parser.NormalizePath(seriesFolder)))
|
||||||
|
{
|
||||||
|
Logger.Debug("Folder '{0}' is mapped in the database. Skipping.'", seriesFolder);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
Logger.Debug("Folder '{0}' isn't mapped in the database. Trying to map it.'", seriesFolder);
|
Logger.Debug("Folder '{0}' isn't mapped in the database. Trying to map it.'", seriesFolder);
|
||||||
var mappedSeries = _seriesProvider.MapPathToSeries(seriesFolder);
|
var mappedSeries = _seriesProvider.MapPathToSeries(seriesFolder);
|
||||||
|
|
||||||
|
@ -86,7 +130,6 @@ namespace NzbDrone.Core.Providers
|
||||||
_episodeProvider.RefreshEpisodeInfo(mappedSeries.Id);
|
_episodeProvider.RefreshEpisodeInfo(mappedSeries.Id);
|
||||||
_seriesSyncNotification.CurrentStatus = String.Format("{0}: finding episodes on disk...", mappedSeries.SeriesName);
|
_seriesSyncNotification.CurrentStatus = String.Format("{0}: finding episodes on disk...", mappedSeries.SeriesName);
|
||||||
_mediaFileProvider.Scan(_seriesProvider.GetSeries(mappedSeries.Id));
|
_mediaFileProvider.Scan(_seriesProvider.GetSeries(mappedSeries.Id));
|
||||||
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
|
|
@ -0,0 +1,18 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using SubSonic.SqlGeneration.Schema;
|
||||||
|
|
||||||
|
namespace NzbDrone.Core.Repository
|
||||||
|
{
|
||||||
|
public class RootDir
|
||||||
|
{
|
||||||
|
[SubSonicPrimaryKey(true)]
|
||||||
|
public int RootDirId { get; set; }
|
||||||
|
|
||||||
|
public string Path { get; set; }
|
||||||
|
|
||||||
|
public bool Default { get; set; }
|
||||||
|
}
|
||||||
|
}
|
|
@ -22,6 +22,7 @@ namespace NzbDrone.Web.Controllers
|
||||||
private readonly IQualityProvider _qualityProvider;
|
private readonly IQualityProvider _qualityProvider;
|
||||||
private readonly IMediaFileProvider _mediaFileProvider;
|
private readonly IMediaFileProvider _mediaFileProvider;
|
||||||
private readonly IRenameProvider _renameProvider;
|
private readonly IRenameProvider _renameProvider;
|
||||||
|
private readonly IRootDirProvider _rootDirProvider;
|
||||||
|
|
||||||
//
|
//
|
||||||
// GET: /Series/
|
// GET: /Series/
|
||||||
|
@ -29,7 +30,7 @@ namespace NzbDrone.Web.Controllers
|
||||||
public SeriesController(ISyncProvider syncProvider, ISeriesProvider seriesProvider,
|
public SeriesController(ISyncProvider syncProvider, ISeriesProvider seriesProvider,
|
||||||
IEpisodeProvider episodeProvider, IRssSyncProvider rssSyncProvider,
|
IEpisodeProvider episodeProvider, IRssSyncProvider rssSyncProvider,
|
||||||
IQualityProvider qualityProvider, IMediaFileProvider mediaFileProvider,
|
IQualityProvider qualityProvider, IMediaFileProvider mediaFileProvider,
|
||||||
IRenameProvider renameProvider)
|
IRenameProvider renameProvider, IRootDirProvider rootDirProvider)
|
||||||
{
|
{
|
||||||
_seriesProvider = seriesProvider;
|
_seriesProvider = seriesProvider;
|
||||||
_episodeProvider = episodeProvider;
|
_episodeProvider = episodeProvider;
|
||||||
|
@ -38,6 +39,7 @@ namespace NzbDrone.Web.Controllers
|
||||||
_qualityProvider = qualityProvider;
|
_qualityProvider = qualityProvider;
|
||||||
_mediaFileProvider = mediaFileProvider;
|
_mediaFileProvider = mediaFileProvider;
|
||||||
_renameProvider = renameProvider;
|
_renameProvider = renameProvider;
|
||||||
|
_rootDirProvider = rootDirProvider;
|
||||||
}
|
}
|
||||||
|
|
||||||
public ActionResult Index()
|
public ActionResult Index()
|
||||||
|
@ -51,9 +53,15 @@ namespace NzbDrone.Web.Controllers
|
||||||
return View(new AddSeriesModel());
|
return View(new AddSeriesModel());
|
||||||
}
|
}
|
||||||
|
|
||||||
public ActionResult Sync()
|
public ActionResult AddExisting()
|
||||||
{
|
{
|
||||||
_syncProvider.BeginSyncUnmappedFolders();
|
return View();
|
||||||
|
}
|
||||||
|
|
||||||
|
public ActionResult Sync(List<String> paths)
|
||||||
|
{
|
||||||
|
//Todo: Make this do something...
|
||||||
|
_syncProvider.BeginSyncUnmappedFolders(paths);
|
||||||
return RedirectToAction("Index");
|
return RedirectToAction("Index");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -63,9 +71,9 @@ namespace NzbDrone.Web.Controllers
|
||||||
return RedirectToAction("Index");
|
return RedirectToAction("Index");
|
||||||
}
|
}
|
||||||
|
|
||||||
public ActionResult UnMapped()
|
public ActionResult UnMapped(string path)
|
||||||
{
|
{
|
||||||
return View(_seriesProvider.GetUnmappedFolders().Select(c => new MappingModel() { Id = 1, Path = c.Value }).ToList());
|
return View(_syncProvider.GetUnmappedFolders(path).Select(c => new MappingModel() { Id = 1, Path = c }).ToList());
|
||||||
}
|
}
|
||||||
|
|
||||||
public ActionResult LoadEpisodes(int seriesId)
|
public ActionResult LoadEpisodes(int seriesId)
|
||||||
|
@ -103,6 +111,24 @@ namespace NzbDrone.Web.Controllers
|
||||||
Total = data.Count()
|
Total = data.Count()
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[GridAction]
|
||||||
|
public ActionResult _AjaxUnmappedFoldersGrid()
|
||||||
|
{
|
||||||
|
var unmappedList = new List<String>();
|
||||||
|
|
||||||
|
foreach (var folder in _rootDirProvider.GetAll())
|
||||||
|
unmappedList.AddRange(_syncProvider.GetUnmappedFolders(folder.Path));
|
||||||
|
|
||||||
|
var seriesPaths = unmappedList.Select(c => new AddExistingSeriesModel
|
||||||
|
{
|
||||||
|
IsWanted = true,
|
||||||
|
Path = c
|
||||||
|
});
|
||||||
|
|
||||||
|
return View(new GridModel(seriesPaths));
|
||||||
|
}
|
||||||
|
|
||||||
private IEnumerable<Episode> GetData(GridCommand command)
|
private IEnumerable<Episode> GetData(GridCommand command)
|
||||||
{
|
{
|
||||||
|
|
||||||
|
|
|
@ -9,6 +9,7 @@ using NzbDrone.Core;
|
||||||
using NzbDrone.Core.Helpers;
|
using NzbDrone.Core.Helpers;
|
||||||
using NzbDrone.Core.Model;
|
using NzbDrone.Core.Model;
|
||||||
using NzbDrone.Core.Providers;
|
using NzbDrone.Core.Providers;
|
||||||
|
using NzbDrone.Core.Repository;
|
||||||
using NzbDrone.Core.Repository.Quality;
|
using NzbDrone.Core.Repository.Quality;
|
||||||
using NzbDrone.Web.Models;
|
using NzbDrone.Web.Models;
|
||||||
|
|
||||||
|
@ -20,15 +21,19 @@ namespace NzbDrone.Web.Controllers
|
||||||
private IConfigProvider _configProvider;
|
private IConfigProvider _configProvider;
|
||||||
private IIndexerProvider _indexerProvider;
|
private IIndexerProvider _indexerProvider;
|
||||||
private IQualityProvider _qualityProvider;
|
private IQualityProvider _qualityProvider;
|
||||||
|
private IRootDirProvider _rootDirProvider;
|
||||||
|
|
||||||
private static readonly Logger Logger = LogManager.GetCurrentClassLogger();
|
private static readonly Logger Logger = LogManager.GetCurrentClassLogger();
|
||||||
private string _settingsSaved = "Settings Saved.";
|
private string _settingsSaved = "Settings Saved.";
|
||||||
private string _settingsFailed = "Error Saving Settings, please fix any errors";
|
private string _settingsFailed = "Error Saving Settings, please fix any errors";
|
||||||
|
|
||||||
public SettingsController(IConfigProvider configProvider, IIndexerProvider indexerProvider, IQualityProvider qualityProvider)
|
public SettingsController(IConfigProvider configProvider, IIndexerProvider indexerProvider,
|
||||||
|
IQualityProvider qualityProvider, IRootDirProvider rootDirProvider)
|
||||||
{
|
{
|
||||||
_configProvider = configProvider;
|
_configProvider = configProvider;
|
||||||
_indexerProvider = indexerProvider;
|
_indexerProvider = indexerProvider;
|
||||||
_qualityProvider = qualityProvider;
|
_qualityProvider = qualityProvider;
|
||||||
|
_rootDirProvider = rootDirProvider;
|
||||||
}
|
}
|
||||||
|
|
||||||
public ActionResult Index(string viewName)
|
public ActionResult Index(string viewName)
|
||||||
|
@ -37,21 +42,18 @@ namespace NzbDrone.Web.Controllers
|
||||||
ViewData["viewName"] = viewName;
|
ViewData["viewName"] = viewName;
|
||||||
|
|
||||||
else
|
else
|
||||||
ViewData["viewName"] = "General";
|
return RedirectToAction("General");
|
||||||
|
|
||||||
return View("Index", new SettingsModel
|
return View("Index");
|
||||||
{
|
|
||||||
TvFolder = _configProvider.SeriesRoot
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public ActionResult General()
|
public ActionResult General()
|
||||||
{
|
{
|
||||||
ViewData["viewName"] = "General";
|
ViewData["viewName"] = "General";
|
||||||
|
|
||||||
return View("Index", new SettingsModel
|
return View("Index", new SettingsModel
|
||||||
{
|
{
|
||||||
TvFolder = _configProvider.SeriesRoot,
|
Directories = new List<RootDir>()
|
||||||
Quality = Convert.ToInt32(_configProvider.GetValue("Quality", "1", true)),
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -185,6 +187,11 @@ namespace NzbDrone.Web.Controllers
|
||||||
return View("UserProfileSection", new QualityProfile { Name = "New Profile", UserProfile = true });
|
return View("UserProfileSection", new QualityProfile { Name = "New Profile", UserProfile = true });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public ViewResult AddRootDir()
|
||||||
|
{
|
||||||
|
return View("RootDir", new RootDir { Default = false });
|
||||||
|
}
|
||||||
|
|
||||||
public ActionResult SubMenu()
|
public ActionResult SubMenu()
|
||||||
{
|
{
|
||||||
return PartialView();
|
return PartialView();
|
||||||
|
@ -202,9 +209,30 @@ namespace NzbDrone.Web.Controllers
|
||||||
[HttpPost]
|
[HttpPost]
|
||||||
public ActionResult SaveGeneral(SettingsModel data)
|
public ActionResult SaveGeneral(SettingsModel data)
|
||||||
{
|
{
|
||||||
if (ModelState.IsValid)
|
if (data.Directories.Count > 0)
|
||||||
{
|
{
|
||||||
_configProvider.SeriesRoot = data.TvFolder;
|
//If the Javascript was beaten we need to return an error
|
||||||
|
if (!data.Directories.Exists(d => d.Default))
|
||||||
|
return Content(_settingsFailed);
|
||||||
|
|
||||||
|
var currentRootDirs = _rootDirProvider.GetAll();
|
||||||
|
|
||||||
|
foreach (var currentRootDir in currentRootDirs)
|
||||||
|
{
|
||||||
|
var closureRootDir = currentRootDir;
|
||||||
|
if (!data.Directories.Exists(d => d.RootDirId == closureRootDir.RootDirId))
|
||||||
|
_rootDirProvider.Remove(closureRootDir.RootDirId);
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var dir in data.Directories)
|
||||||
|
{
|
||||||
|
if (dir.RootDirId == 0)
|
||||||
|
_rootDirProvider.Add(dir);
|
||||||
|
|
||||||
|
else
|
||||||
|
_rootDirProvider.Update(dir);
|
||||||
|
}
|
||||||
|
|
||||||
return Content(_settingsSaved);
|
return Content(_settingsSaved);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,13 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Web;
|
||||||
|
|
||||||
|
namespace NzbDrone.Web.Models
|
||||||
|
{
|
||||||
|
public class AddExistingSeriesModel
|
||||||
|
{
|
||||||
|
public bool IsWanted { get; set; }
|
||||||
|
public string Path { get; set; }
|
||||||
|
}
|
||||||
|
}
|
|
@ -10,22 +10,7 @@ namespace NzbDrone.Web.Models
|
||||||
|
|
||||||
public class SettingsModel
|
public class SettingsModel
|
||||||
{
|
{
|
||||||
|
[DisplayName("TV Series Root Folder(s)")]
|
||||||
[DataType(DataType.Text)]
|
public List<RootDir> Directories { get; set; }
|
||||||
[Required(ErrorMessage = "Please enter a valid TV path")]
|
|
||||||
[DisplayName("TV Folder")]
|
|
||||||
public String TvFolder
|
|
||||||
{
|
|
||||||
get;
|
|
||||||
set;
|
|
||||||
}
|
|
||||||
|
|
||||||
[DataType(DataType.Text)]
|
|
||||||
[DisplayName("Initial Quality")]
|
|
||||||
public int Quality
|
|
||||||
{
|
|
||||||
get;
|
|
||||||
set;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -86,6 +86,7 @@
|
||||||
<Compile Include="Helpers\HtmlPrefixScopeExtensions.cs" />
|
<Compile Include="Helpers\HtmlPrefixScopeExtensions.cs" />
|
||||||
<Compile Include="Helpers\IsCurrentActionHelper.cs" />
|
<Compile Include="Helpers\IsCurrentActionHelper.cs" />
|
||||||
<Compile Include="Models\AccountModels.cs" />
|
<Compile Include="Models\AccountModels.cs" />
|
||||||
|
<Compile Include="Models\AddExistingSeriesModel.cs" />
|
||||||
<Compile Include="Models\AddSeriesModel.cs" />
|
<Compile Include="Models\AddSeriesModel.cs" />
|
||||||
<Compile Include="Models\DownloadSettingsModel.cs" />
|
<Compile Include="Models\DownloadSettingsModel.cs" />
|
||||||
<Compile Include="Models\EpisodeSortingModel.cs" />
|
<Compile Include="Models\EpisodeSortingModel.cs" />
|
||||||
|
@ -271,6 +272,7 @@
|
||||||
<Content Include="Scripts\Notification.js" />
|
<Content Include="Scripts\Notification.js" />
|
||||||
<Content Include="Views\Home\Test.aspx" />
|
<Content Include="Views\Home\Test.aspx" />
|
||||||
<Content Include="Views\Log\Index.aspx" />
|
<Content Include="Views\Log\Index.aspx" />
|
||||||
|
<Content Include="Views\Series\AddExisting.aspx" />
|
||||||
<Content Include="Views\Series\Details.aspx" />
|
<Content Include="Views\Series\Details.aspx" />
|
||||||
<Content Include="Views\Series\Edit.aspx" />
|
<Content Include="Views\Series\Edit.aspx" />
|
||||||
<Content Include="Views\Series\EpisodeDetail.ascx" />
|
<Content Include="Views\Series\EpisodeDetail.ascx" />
|
||||||
|
@ -278,6 +280,7 @@
|
||||||
<Content Include="Views\Series\SubMenu.ascx" />
|
<Content Include="Views\Series\SubMenu.ascx" />
|
||||||
<Content Include="Views\Series\Unmapped.aspx" />
|
<Content Include="Views\Series\Unmapped.aspx" />
|
||||||
<Content Include="Views\Series\Add.aspx" />
|
<Content Include="Views\Series\Add.aspx" />
|
||||||
|
<Content Include="Views\Settings\RootDir.ascx" />
|
||||||
<Content Include="Views\Settings\Notifications.ascx" />
|
<Content Include="Views\Settings\Notifications.ascx" />
|
||||||
<Content Include="Views\Settings\Downloads.ascx" />
|
<Content Include="Views\Settings\Downloads.ascx" />
|
||||||
<Content Include="Views\Settings\EpisodeSorting.ascx" />
|
<Content Include="Views\Settings\EpisodeSorting.ascx" />
|
||||||
|
|
|
@ -15,4 +15,8 @@
|
||||||
|
|
||||||
//Add Existing
|
//Add Existing
|
||||||
|
|
||||||
|
//Ask user for existing TV Root Folder...
|
||||||
|
//Get list of unmapped folders and allow the user to check off the ones they want to add...
|
||||||
|
//
|
||||||
|
|
||||||
</asp:Content>
|
</asp:Content>
|
|
@ -0,0 +1,35 @@
|
||||||
|
<%@ Page Title="" Language="C#" MasterPageFile="~/Views/Shared/Site.Master" Inherits="System.Web.Mvc.ViewPage<dynamic>" %>
|
||||||
|
|
||||||
|
<%@ Import Namespace="Telerik.Web.Mvc.UI" %>
|
||||||
|
<%@ Import Namespace="NzbDrone.Web.Models" %>
|
||||||
|
|
||||||
|
<asp:Content ID="Content1" ContentPlaceHolderID="TitleContent" runat="server">
|
||||||
|
Add Existing Series
|
||||||
|
</asp:Content>
|
||||||
|
<asp:Content ID="Menu" ContentPlaceHolderID="ActionMenu" runat="server">
|
||||||
|
<%
|
||||||
|
Html.RenderPartial("SubMenu");
|
||||||
|
%>
|
||||||
|
</asp:Content>
|
||||||
|
<asp:Content ID="Content2" ContentPlaceHolderID="MainContent" runat="server">
|
||||||
|
|
||||||
|
//Get AJAX listing of unmapped directories
|
||||||
|
|
||||||
|
<%
|
||||||
|
Html.Telerik().Grid<AddExistingSeriesModel>().Name("Unmapped Series Folders")
|
||||||
|
.Columns(columns =>
|
||||||
|
{
|
||||||
|
columns.Bound(c => c.IsWanted).Width(0).Title("Is Wanted?");
|
||||||
|
columns.Bound(c => c.Path);
|
||||||
|
})
|
||||||
|
//.DetailView(detailView => detailView.Template(e => Html.RenderPartial("EpisodeDetail", e)))
|
||||||
|
//.DetailView(detailView => detailView.ClientTemplate("<div><#= Overview #></div>"))
|
||||||
|
//.Sortable(rows => rows.OrderBy(epSort => epSort.Add(c => c.EpisodeNumber).Descending()).Enabled(true))
|
||||||
|
.Footer(false)
|
||||||
|
.DataBinding(d => d.Ajax().Select("_AjaxUnmappedFoldersGrid", "Series"))
|
||||||
|
//.EnableCustomBinding(true)
|
||||||
|
//.ClientEvents(e => e.OnDetailViewExpand("episodeDetailExpanded")) //Causes issues displaying the episode detail multiple times...
|
||||||
|
.Render();
|
||||||
|
%>`
|
||||||
|
|
||||||
|
</asp:Content>
|
|
@ -5,7 +5,7 @@
|
||||||
<% Html.Telerik().Menu().Name("telerikGrid").Items(items =>
|
<% Html.Telerik().Menu().Name("telerikGrid").Items(items =>
|
||||||
{
|
{
|
||||||
items.Add().Text("View Unmapped Folders").Action("Unmapped", "Series");
|
items.Add().Text("View Unmapped Folders").Action("Unmapped", "Series");
|
||||||
items.Add().Text("Sync With Disk").Action("Sync", "Series");
|
items.Add().Text("Sync With Disk").Action("Sync", "Series", new { paths = "test" });
|
||||||
items.Add().Text("Start RSS Sync").Action("RssSync", "Series");
|
items.Add().Text("Start RSS Sync").Action("RssSync", "Series");
|
||||||
items.Add().Text("Rename All").Action("RenameAll", "Series");
|
items.Add().Text("Rename All").Action("RenameAll", "Series");
|
||||||
items.Add().Text("Add Series").Action("Add", "Series");
|
items.Add().Text("Add Series").Action("Add", "Series");
|
||||||
|
|
|
@ -27,15 +27,21 @@
|
||||||
<% using (Html.BeginForm("SaveGeneral", "Settings", FormMethod.Post, new { id = "form", name = "form" }))
|
<% using (Html.BeginForm("SaveGeneral", "Settings", FormMethod.Post, new { id = "form", name = "form" }))
|
||||||
{%>
|
{%>
|
||||||
<%: Html.ValidationSummary(true, "Unable to save your settings. Please correct the errors and try again.") %>
|
<%: Html.ValidationSummary(true, "Unable to save your settings. Please correct the errors and try again.") %>
|
||||||
<fieldset>
|
<fieldset>
|
||||||
<legend>General</legend>
|
<legend>General</legend>
|
||||||
|
|
||||||
<div class="editor-label">
|
<div style="padding-top: 10px;">
|
||||||
<%= Html.LabelFor(model => model.TvFolder) %>
|
<div style="padding-left: 7px; margin-bottom: 5px;">
|
||||||
|
<a id="addItem" style="text-decoration:none;" href="<%: Url.Action("AddRootDir", "Settings") %>">
|
||||||
|
<img src="../../Content/Images/Plus.png" alt="Add New Profile" />
|
||||||
|
<h4 style="margin-left: 3px; display: inline; color: Black;">Add New Root Directory</h4></a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="root-dirs">
|
||||||
|
<%foreach (var item in Model.Directories) { %>
|
||||||
|
<% Html.RenderPartial("RootDir", item); %>
|
||||||
|
<% } %>
|
||||||
</div>
|
</div>
|
||||||
<div class="editor-field">
|
|
||||||
<%= Html.TextBoxFor(model => model.TvFolder) %>
|
|
||||||
<%= Html.ValidationMessageFor(model => model.TvFolder) %>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<p>
|
<p>
|
||||||
|
@ -44,3 +50,35 @@
|
||||||
</fieldset>
|
</fieldset>
|
||||||
<% } Html.EndForm();%>
|
<% } Html.EndForm();%>
|
||||||
<div id="result"></div>
|
<div id="result"></div>
|
||||||
|
|
||||||
|
<script type="text/javascript">
|
||||||
|
|
||||||
|
$("#addItem").click(function () {
|
||||||
|
$.ajax({
|
||||||
|
url: this.href,
|
||||||
|
cache: false,
|
||||||
|
success: function (html) { $("#root-dirs").append(html); }
|
||||||
|
});
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
|
||||||
|
$("a.deleteRow").live("click", function () {
|
||||||
|
$(this).parents("div.rootDirSection:first").remove();
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
|
||||||
|
$(".defaultCheckbox").live("change", function () {
|
||||||
|
var checked = $(this).attr('checked');
|
||||||
|
|
||||||
|
if (checked) {
|
||||||
|
var thisOne = this;
|
||||||
|
$(".defaultCheckbox").attr('checked', false);
|
||||||
|
$(this).attr('checked', true);
|
||||||
|
}
|
||||||
|
|
||||||
|
//Don't let the user uncheck a checkbox (Like a radio button)
|
||||||
|
else {
|
||||||
|
$(this).attr('checked', true);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
|
@ -0,0 +1,36 @@
|
||||||
|
<%@ Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl<NzbDrone.Core.Repository.RootDir>" %>
|
||||||
|
<%@ Import Namespace="NzbDrone.Web.Helpers" %>
|
||||||
|
|
||||||
|
<% using (Html.BeginCollectionItem("Directories"))
|
||||||
|
{ %>
|
||||||
|
|
||||||
|
<%
|
||||||
|
var idClean = ViewData.TemplateInfo.HtmlFieldPrefix.Replace('[', '_').Replace(']', '_');
|
||||||
|
//string sortable1 = String.Format("{0}_sortable1", idClean);
|
||||||
|
%>
|
||||||
|
|
||||||
|
<style type="text/css">
|
||||||
|
.root_dir_text { width:300px; }
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<div class="rootDirSection">
|
||||||
|
|
||||||
|
<fieldset style="width:350px; height:16px; margin:0px; margin-top: 0px; border-color:#CCCCCD; -khtml-border-radius:8px; border-radius:8px; -moz-border-radius:8px; -webkit-border-radius:8px;">
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<%: Html.CheckBoxFor(m => m.Default, new { @class = "defaultCheckbox" }) %>
|
||||||
|
<%: Html.TextBoxFor(m => m.Path, new { @class="root_dir_text" }) %>
|
||||||
|
<a href="#" class="deleteRow"><img src="../../Content/Images/X.png" alt="Delete"/></a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<%: Html.ValidationMessageFor(m => m.Path) %>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="hiddenProfileDetails">
|
||||||
|
<%= Html.TextBoxFor(x => x.RootDirId, new { @style = "display:none" })%>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</fieldset>
|
||||||
|
</div>
|
||||||
|
<% } %>
|
|
@ -12,6 +12,8 @@
|
||||||
string sortable2 = String.Format("{0}_sortable2", idClean);
|
string sortable2 = String.Format("{0}_sortable2", idClean);
|
||||||
string allowedStringName = String.Format("{0}_AllowedString", idClean);
|
string allowedStringName = String.Format("{0}_AllowedString", idClean);
|
||||||
string connectedSortable = String.Format("connected{0}", idClean);
|
string connectedSortable = String.Format("connected{0}", idClean);
|
||||||
|
string title = String.Format("{0}_Title", idClean);
|
||||||
|
string nameBox = String.Format("{0}_Name", idClean);
|
||||||
%>
|
%>
|
||||||
|
|
||||||
<style type="text/css">
|
<style type="text/css">
|
||||||
|
@ -24,7 +26,6 @@
|
||||||
.sortable1 li.ui-state-highlight, .sortable2 li.ui-state-highlight { background: #fbf5d0; border-color: #065EFE; }
|
.sortable1 li.ui-state-highlight, .sortable2 li.ui-state-highlight { background: #fbf5d0; border-color: #065EFE; }
|
||||||
.removeDiv { float: left; display:block; }
|
.removeDiv { float: left; display:block; }
|
||||||
.ui-state-highlight { height: 1.5em; line-height: 1.2em; }
|
.ui-state-highlight { height: 1.5em; line-height: 1.2em; }
|
||||||
|
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<script type="text/javascript">
|
<script type="text/javascript">
|
||||||
|
@ -53,7 +54,7 @@
|
||||||
<fieldset style="width:275px; margin:5px; margin-top: 0px; border-color:#CCCCCD">
|
<fieldset style="width:275px; margin:5px; margin-top: 0px; border-color:#CCCCCD">
|
||||||
|
|
||||||
<div id="qualityHeader" style="padding-bottom: 5px; margin: 0px;">
|
<div id="qualityHeader" style="padding-bottom: 5px; margin: 0px;">
|
||||||
<h2 style="display:inline; padding-right: 4px; margin-left: 4px;"><%= Html.DisplayTextFor(m => m.Name) %></h2>
|
<h2 style="display:inline; padding-right: 4px; margin-left: 4px;" id="<%= title %>"><%= Html.DisplayTextFor(m => m.Name) %></h2>
|
||||||
<a href="#" class="deleteRow"><img src="../../Content/Images/X.png" alt="Delete" /></a>
|
<a href="#" class="deleteRow"><img src="../../Content/Images/X.png" alt="Delete" /></a>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@ -115,4 +116,12 @@
|
||||||
</div>
|
</div>
|
||||||
</fieldset>
|
</fieldset>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<script type="text/javascript">
|
||||||
|
$("#<%: nameBox %>").keyup(function () {
|
||||||
|
var value = $(this).val();
|
||||||
|
$("#<%= title %>").text(value);
|
||||||
|
}).keyup();
|
||||||
|
</script>
|
||||||
|
|
||||||
<% } %>
|
<% } %>
|
Loading…
Reference in New Issue