add device upload options

This commit is contained in:
Luke Pulverenti
2014-10-11 16:38:13 -04:00
parent 2486cffa71
commit f3539686bd
50 changed files with 1030 additions and 209 deletions

View File

@@ -0,0 +1,68 @@
using MediaBrowser.Common.Configuration;
using MediaBrowser.Controller.Entities;
using System.IO;
using System.Linq;
namespace MediaBrowser.Server.Implementations.Devices
{
public class CameraUploadsFolder : BasePluginFolder
{
public CameraUploadsFolder()
{
Name = "Camera Uploads";
DisplayMediaType = "CollectionFolder";
}
public override bool IsVisible(User user)
{
return GetChildren(user, true).Any() &&
base.IsVisible(user);
}
public override bool IsHidden
{
get
{
return true;
}
}
public override bool IsHiddenFromUser(User user)
{
return false;
}
public override string CollectionType
{
get { return Model.Entities.CollectionType.Photos; }
}
public override string GetClientTypeName()
{
return typeof(CollectionFolder).Name;
}
}
public class CameraUploadsDynamicFolder : IVirtualFolderCreator
{
private readonly IApplicationPaths _appPaths;
public CameraUploadsDynamicFolder(IApplicationPaths appPaths)
{
_appPaths = appPaths;
}
public BasePluginFolder GetFolder()
{
var path = Path.Combine(_appPaths.DataPath, "camerauploads");
Directory.CreateDirectory(path);
return new CameraUploadsFolder
{
Path = path
};
}
}
}

View File

@@ -0,0 +1,149 @@
using MediaBrowser.Common.Configuration;
using MediaBrowser.Common.IO;
using MediaBrowser.Controller.Devices;
using MediaBrowser.Controller.Library;
using MediaBrowser.Model.Devices;
using MediaBrowser.Model.Session;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
namespace MediaBrowser.Server.Implementations.Devices
{
public class DeviceManager : IDeviceManager
{
private readonly IDeviceRepository _repo;
private readonly IUserManager _userManager;
private readonly IFileSystem _fileSystem;
private readonly ILibraryMonitor _libraryMonitor;
private readonly IConfigurationManager _config;
public DeviceManager(IDeviceRepository repo, IUserManager userManager, IFileSystem fileSystem, ILibraryMonitor libraryMonitor, IConfigurationManager config)
{
_repo = repo;
_userManager = userManager;
_fileSystem = fileSystem;
_libraryMonitor = libraryMonitor;
_config = config;
}
public Task RegisterDevice(string reportedId, string name, string usedByUserId)
{
var device = GetDevice(reportedId) ?? new DeviceInfo
{
Id = reportedId
};
device.Name = name;
if (!string.IsNullOrWhiteSpace(usedByUserId))
{
var user = _userManager.GetUserById(usedByUserId);
device.LastUserId = user.Id.ToString("N");
device.LastUserName = user.Name;
}
device.DateLastModified = DateTime.UtcNow;
return _repo.SaveDevice(device);
}
public Task SaveCapabilities(string reportedId, ClientCapabilities capabilities)
{
return _repo.SaveCapabilities(reportedId, capabilities);
}
public ClientCapabilities GetCapabilities(string reportedId)
{
return _repo.GetCapabilities(reportedId);
}
public DeviceInfo GetDevice(string id)
{
return _repo.GetDevice(id);
}
public IEnumerable<DeviceInfo> GetDevices()
{
return _repo.GetDevices().OrderByDescending(i => i.DateLastModified);
}
public Task DeleteDevice(string id)
{
return _repo.DeleteDevice(id);
}
public ContentUploadHistory GetCameraUploadHistory(string deviceId)
{
return _repo.GetCameraUploadHistory(deviceId);
}
public async Task AcceptCameraUpload(string deviceId, Stream stream, LocalFileInfo file)
{
var path = GetUploadPath(deviceId);
if (!string.IsNullOrWhiteSpace(file.Album))
{
path = Path.Combine(path, _fileSystem.GetValidFilename(file.Album));
}
Directory.CreateDirectory(path);
path = Path.Combine(path, file.Name);
_libraryMonitor.ReportFileSystemChangeBeginning(path);
try
{
using (var fs = _fileSystem.GetFileStream(path, FileMode.Create, FileAccess.Write, FileShare.Read))
{
await stream.CopyToAsync(fs).ConfigureAwait(false);
}
_repo.AddCameraUpload(deviceId, file);
}
finally
{
_libraryMonitor.ReportFileSystemChangeComplete(path, true);
}
}
private string GetUploadPath(string deviceId)
{
var config = _config.GetUploadOptions();
if (!string.IsNullOrWhiteSpace(config.CameraUploadPath))
{
return config.CameraUploadPath;
}
return Path.Combine(_config.CommonApplicationPaths.DataPath, "camerauploads");
}
}
public class DevicesConfigStore : IConfigurationFactory
{
public IEnumerable<ConfigurationStore> GetConfigurations()
{
return new List<ConfigurationStore>
{
new ConfigurationStore
{
Key = "devices",
ConfigurationType = typeof(DevicesOptions)
}
};
}
}
public static class UploadConfigExtension
{
public static DevicesOptions GetUploadOptions(this IConfigurationManager config)
{
return config.GetConfiguration<DevicesOptions>("devices");
}
}
}

View File

@@ -0,0 +1,176 @@
using MediaBrowser.Common.Configuration;
using MediaBrowser.Common.Extensions;
using MediaBrowser.Controller.Devices;
using MediaBrowser.Model.Devices;
using MediaBrowser.Model.Serialization;
using MediaBrowser.Model.Session;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
namespace MediaBrowser.Server.Implementations.Devices
{
public class DeviceRepository : IDeviceRepository
{
private readonly object _syncLock = new object();
private readonly IApplicationPaths _appPaths;
private readonly IJsonSerializer _json;
private ConcurrentBag<DeviceInfo> _devices;
public DeviceRepository(IApplicationPaths appPaths, IJsonSerializer json)
{
_appPaths = appPaths;
_json = json;
}
private string GetDevicesPath()
{
return Path.Combine(_appPaths.DataPath, "devices");
}
private string GetDevicePath(string id)
{
return Path.Combine(GetDevicesPath(), id.GetMD5().ToString("N"));
}
public Task SaveDevice(DeviceInfo device)
{
var path = Path.Combine(GetDevicePath(device.Id), "device.json");
Directory.CreateDirectory(Path.GetDirectoryName(path));
lock (_syncLock)
{
_json.SerializeToFile(device, path);
_devices = null;
}
return Task.FromResult(true);
}
public Task SaveCapabilities(string reportedId, ClientCapabilities capabilities)
{
var device = GetDevice(reportedId);
device.Capabilities = capabilities;
SaveDevice(device);
return Task.FromResult(true);
}
public ClientCapabilities GetCapabilities(string reportedId)
{
var device = GetDevice(reportedId);
return device == null ? null : device.Capabilities;
}
public DeviceInfo GetDevice(string id)
{
return GetDevices().FirstOrDefault(i => string.Equals(i.Id, id, StringComparison.OrdinalIgnoreCase));
}
public IEnumerable<DeviceInfo> GetDevices()
{
if (_devices == null)
{
lock (_syncLock)
{
if (_devices == null)
{
_devices = new ConcurrentBag<DeviceInfo>(LoadDevices());
}
}
}
return _devices.ToList();
}
private IEnumerable<DeviceInfo> LoadDevices()
{
var path = GetDevicesPath();
try
{
return new DirectoryInfo(path)
.EnumerateFiles("*", SearchOption.AllDirectories)
.Where(i => string.Equals(i.Name, "device.json", StringComparison.OrdinalIgnoreCase))
.Select(i => _json.DeserializeFromFile<DeviceInfo>(i.FullName))
.ToList();
}
catch (IOException)
{
return new List<DeviceInfo>();
}
}
public Task DeleteDevice(string id)
{
var path = GetDevicePath(id);
lock (_syncLock)
{
try
{
Directory.Delete(path, true);
}
catch (DirectoryNotFoundException)
{
}
_devices = null;
}
return Task.FromResult(true);
}
public ContentUploadHistory GetCameraUploadHistory(string deviceId)
{
var path = Path.Combine(GetDevicePath(deviceId), "camerauploads.json");
lock (_syncLock)
{
try
{
return _json.DeserializeFromFile<ContentUploadHistory>(path);
}
catch (IOException)
{
return new ContentUploadHistory
{
DeviceId = deviceId
};
}
}
}
public void AddCameraUpload(string deviceId, LocalFileInfo file)
{
var path = Path.Combine(GetDevicePath(deviceId), "camerauploads.json");
Directory.CreateDirectory(Path.GetDirectoryName(path));
lock (_syncLock)
{
ContentUploadHistory history;
try
{
history = _json.DeserializeFromFile<ContentUploadHistory>(path);
}
catch (IOException)
{
history = new ContentUploadHistory
{
DeviceId = deviceId
};
}
history.DeviceId = deviceId;
history.FilesUploaded.Add(file);
_json.SerializeToFile(history, path);
}
}
}
}

View File

@@ -60,41 +60,45 @@ namespace MediaBrowser.Server.Implementations.Library
var standaloneFolders = folders.Where(i => UserView.IsExcludedFromGrouping(i) || excludeFolderIds.Contains(i.Id)).ToList();
list.AddRange(standaloneFolders);
var recursiveChildren = folders
var foldersWithViewTypes = folders
.Except(standaloneFolders)
.SelectMany(i => i.GetRecursiveChildren(user, false))
.OfType<ICollectionFolder>()
.ToList();
if (recursiveChildren.OfType<Series>().Any())
list.AddRange(standaloneFolders);
if (foldersWithViewTypes.Any(i => string.Equals(i.CollectionType, CollectionType.TvShows, StringComparison.OrdinalIgnoreCase)) ||
foldersWithViewTypes.Any(i => string.IsNullOrWhiteSpace(i.CollectionType)))
{
list.Add(await GetUserView(CollectionType.TvShows, user, string.Empty, cancellationToken).ConfigureAwait(false));
}
if (recursiveChildren.OfType<MusicAlbum>().Any() ||
recursiveChildren.OfType<MusicVideo>().Any())
if (foldersWithViewTypes.Any(i => string.Equals(i.CollectionType, CollectionType.Music, StringComparison.OrdinalIgnoreCase)) ||
foldersWithViewTypes.Any(i => string.Equals(i.CollectionType, CollectionType.MusicVideos, StringComparison.OrdinalIgnoreCase)))
{
list.Add(await GetUserView(CollectionType.Music, user, string.Empty, cancellationToken).ConfigureAwait(false));
}
if (recursiveChildren.OfType<Movie>().Any())
if (foldersWithViewTypes.Any(i => string.Equals(i.CollectionType, CollectionType.Movies, StringComparison.OrdinalIgnoreCase)) ||
foldersWithViewTypes.Any(i => string.IsNullOrWhiteSpace(i.CollectionType)))
{
list.Add(await GetUserView(CollectionType.Movies, user, string.Empty, cancellationToken).ConfigureAwait(false));
}
if (recursiveChildren.OfType<Game>().Any())
if (foldersWithViewTypes.Any(i => string.Equals(i.CollectionType, CollectionType.Games, StringComparison.OrdinalIgnoreCase)))
{
list.Add(await GetUserView(CollectionType.Games, user, string.Empty, cancellationToken).ConfigureAwait(false));
}
if (user.Configuration.DisplayCollectionsView &&
recursiveChildren.OfType<BoxSet>().Any())
folders
.Except(standaloneFolders)
.SelectMany(i => i.GetRecursiveChildren(user, false)).OfType<BoxSet>().Any())
{
list.Add(await GetUserView(CollectionType.BoxSets, user, string.Empty, cancellationToken).ConfigureAwait(false));
}
if (recursiveChildren.OfType<Playlist>().Any())
if (foldersWithViewTypes.Any(i => string.Equals(i.CollectionType, CollectionType.Playlists, StringComparison.OrdinalIgnoreCase)))
{
list.Add(_playlists.GetPlaylistsFolder(user.Id.ToString("N")));
}

View File

@@ -602,5 +602,12 @@
"DashboardTourNotifications": "Automatically send notifications of server events to your mobile device, email and more.",
"DashboardTourScheduledTasks": "Easily manage long running operations with scheduled tasks. Decide when they run, and how often.",
"DashboardTourMobile": "The Media Browser dashboard works great on smartphones and tablets. Manage your server from the palm of your hand anytime, anywhere.",
"MessageRefreshQueued": "Refresh queued"
"MessageRefreshQueued": "Refresh queued",
"TabDevices": "Devices",
"DeviceLastUsedByUserName": "Last used by {0}",
"HeaderDeleteDevice": "Delete Device",
"DeleteDeviceConfirmation": "Are you sure you with to delete this device? It will reappear the next time a user signs in with it.",
"LabelEnableCameraUploadFor": "Enable camera upload for:",
"HeaderSelectUploadPath": "Select Upload Path",
"LabelEnableCameraUploadForHelp": "Uploads will occur automatically in the background when signed into Media Browser."
}

View File

@@ -1215,5 +1215,13 @@
"OptionDateAddedImportTime": "Use date scanned into the library",
"OptionDateAddedFileTime": "Use file creation date",
"LabelDateAddedBehaviorHelp": "If a metadata value is present it will always be used before either of these options.",
"LabelNumberTrailerToPlay": "Number of trailers to play:"
"LabelNumberTrailerToPlay": "Number of trailers to play:",
"TitleDevices": "Devices",
"TabCameraUpload": "Camera Upload",
"TabDevices": "Devices",
"TitleDevices": "Devices",
"HeaderCameraUploadHelp": "Automatically upload photos and videos taken from your mobile devices into Media Browser.",
"MessageNoDevicesSupportCameraUpload": "You currently don't have any devices that support camera upload.",
"LabelUploadPath": "Upload path:",
"LabelUploadPathHelp": "Select a custom upload path, if desired. If unspecified an internal data folder will be used."
}

View File

@@ -119,6 +119,9 @@
<Compile Include="Connect\ConnectData.cs" />
<Compile Include="Connect\ConnectManager.cs" />
<Compile Include="Connect\Responses.cs" />
<Compile Include="Devices\DeviceManager.cs" />
<Compile Include="Devices\DeviceRepository.cs" />
<Compile Include="Devices\CameraUploadsFolder.cs" />
<Compile Include="Drawing\ImageHeader.cs" />
<Compile Include="Drawing\PercentPlayedDrawer.cs" />
<Compile Include="Drawing\PlayedIndicatorDrawer.cs" />

View File

@@ -3,6 +3,7 @@ using MediaBrowser.Common.Extensions;
using MediaBrowser.Common.Net;
using MediaBrowser.Controller;
using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Devices;
using MediaBrowser.Controller.Drawing;
using MediaBrowser.Controller.Dto;
using MediaBrowser.Controller.Entities;
@@ -64,6 +65,7 @@ namespace MediaBrowser.Server.Implementations.Session
private readonly IServerApplicationHost _appHost;
private readonly IAuthenticationRepository _authRepo;
private readonly IDeviceManager _deviceManager;
/// <summary>
/// Gets or sets the configuration manager.
@@ -80,7 +82,7 @@ namespace MediaBrowser.Server.Implementations.Session
public event EventHandler<GenericEventArgs<AuthenticationRequest>> AuthenticationFailed;
public event EventHandler<GenericEventArgs<AuthenticationRequest>> AuthenticationSucceeded;
/// <summary>
/// Occurs when [playback start].
/// </summary>
@@ -111,7 +113,7 @@ namespace MediaBrowser.Server.Implementations.Session
/// <param name="logger">The logger.</param>
/// <param name="userRepository">The user repository.</param>
/// <param name="libraryManager">The library manager.</param>
public SessionManager(IUserDataManager userDataRepository, IServerConfigurationManager configurationManager, ILogger logger, IUserRepository userRepository, ILibraryManager libraryManager, IUserManager userManager, IMusicManager musicManager, IDtoService dtoService, IImageProcessor imageProcessor, IItemRepository itemRepo, IJsonSerializer jsonSerializer, IServerApplicationHost appHost, IHttpClient httpClient, IAuthenticationRepository authRepo)
public SessionManager(IUserDataManager userDataRepository, IServerConfigurationManager configurationManager, ILogger logger, IUserRepository userRepository, ILibraryManager libraryManager, IUserManager userManager, IMusicManager musicManager, IDtoService dtoService, IImageProcessor imageProcessor, IItemRepository itemRepo, IJsonSerializer jsonSerializer, IServerApplicationHost appHost, IHttpClient httpClient, IAuthenticationRepository authRepo, IDeviceManager deviceManager)
{
_userDataRepository = userDataRepository;
_configurationManager = configurationManager;
@@ -127,6 +129,7 @@ namespace MediaBrowser.Server.Implementations.Session
_appHost = appHost;
_httpClient = httpClient;
_authRepo = authRepo;
_deviceManager = deviceManager;
}
/// <summary>
@@ -394,7 +397,9 @@ namespace MediaBrowser.Server.Implementations.Session
try
{
var connection = _activeConnections.GetOrAdd(key, keyName =>
SessionInfo connection;
if (!_activeConnections.TryGetValue(key, out connection))
{
var sessionInfo = new SessionInfo
{
@@ -411,8 +416,15 @@ namespace MediaBrowser.Server.Implementations.Session
OnSessionStarted(sessionInfo);
return sessionInfo;
});
_activeConnections.TryAdd(key, sessionInfo);
connection = sessionInfo;
if (!string.IsNullOrEmpty(deviceId))
{
var userIdString = userId.HasValue ? userId.Value.ToString("N") : null;
await _deviceManager.RegisterDevice(deviceId, deviceName, userIdString).ConfigureAwait(false);
}
}
connection.DeviceName = deviceName;
connection.UserId = userId;
@@ -1226,7 +1238,7 @@ namespace MediaBrowser.Server.Implementations.Session
var token = await GetAuthorizationToken(user.Id.ToString("N"), request.DeviceId, request.App, request.DeviceName).ConfigureAwait(false);
EventHelper.FireEventIfNotNull(AuthenticationSucceeded, this, new GenericEventArgs<AuthenticationRequest>(request), _logger);
var session = await LogSessionActivity(request.App,
request.AppVersion,
request.DeviceId,
@@ -1234,7 +1246,7 @@ namespace MediaBrowser.Server.Implementations.Session
request.RemoteEndPoint,
user)
.ConfigureAwait(false);
return new AuthenticationResult
{
User = _userManager.GetUserDto(user, request.RemoteEndPoint),
@@ -1339,7 +1351,7 @@ namespace MediaBrowser.Server.Implementations.Session
/// </summary>
/// <param name="sessionId">The session identifier.</param>
/// <param name="capabilities">The capabilities.</param>
public void ReportCapabilities(string sessionId, SessionCapabilities capabilities)
public void ReportCapabilities(string sessionId, ClientCapabilities capabilities)
{
var session = GetSession(sessionId);
@@ -1347,7 +1359,7 @@ namespace MediaBrowser.Server.Implementations.Session
}
private async void ReportCapabilities(SessionInfo session,
SessionCapabilities capabilities,
ClientCapabilities capabilities,
bool saveCapabilities)
{
session.PlayableMediaTypes = capabilities.PlayableMediaTypes;
@@ -1375,56 +1387,14 @@ namespace MediaBrowser.Server.Implementations.Session
}
}
private string GetCapabilitiesFilePath(string deviceId)
private ClientCapabilities GetSavedCapabilities(string deviceId)
{
var filename = deviceId.GetMD5().ToString("N") + ".json";
return Path.Combine(_configurationManager.ApplicationPaths.CachePath, "devices", filename);
return _deviceManager.GetCapabilities(deviceId);
}
private SessionCapabilities GetSavedCapabilities(string deviceId)
private Task SaveCapabilities(string deviceId, ClientCapabilities capabilities)
{
var path = GetCapabilitiesFilePath(deviceId);
try
{
return _jsonSerializer.DeserializeFromFile<SessionCapabilities>(path);
}
catch (DirectoryNotFoundException)
{
return null;
}
catch (FileNotFoundException)
{
return null;
}
catch (Exception ex)
{
_logger.ErrorException("Error getting saved capabilities", ex);
return null;
}
}
private readonly SemaphoreSlim _capabilitiesLock = new SemaphoreSlim(1, 1);
private async Task SaveCapabilities(string deviceId, SessionCapabilities capabilities)
{
var path = GetCapabilitiesFilePath(deviceId);
Directory.CreateDirectory(Path.GetDirectoryName(path));
await _capabilitiesLock.WaitAsync().ConfigureAwait(false);
try
{
_jsonSerializer.SerializeToFile(capabilities, path);
}
catch (Exception ex)
{
_logger.ErrorException("Error saving to {0}", ex, path);
}
finally
{
_capabilitiesLock.Release();
}
return _deviceManager.SaveCapabilities(deviceId, capabilities);
}
public SessionInfoDto GetSessionInfoDto(SessionInfo session)

View File

@@ -75,7 +75,7 @@ namespace MediaBrowser.Server.Implementations.Session
}
else
{
var capabilities = new SessionCapabilities
var capabilities = new ClientCapabilities
{
PlayableMediaTypes = Session.PlayableMediaTypes,
SupportedCommands = Session.SupportedCommands,

View File

@@ -1,9 +1,11 @@
using MediaBrowser.Common.Extensions;
using System.IO;
using MediaBrowser.Common.Extensions;
using MediaBrowser.Controller.Drawing;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Entities.Audio;
using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.Sync;
using MediaBrowser.Model.Devices;
using MediaBrowser.Model.Entities;
using MediaBrowser.Model.Logging;
using MediaBrowser.Model.Querying;