jellyfin/Emby.Server.Implementations/Devices/DeviceManager.cs

493 lines
16 KiB
C#
Raw Normal View History

2019-11-01 17:38:54 +00:00
#pragma warning disable CS1591
2014-10-11 20:38:13 +00:00
using System;
using System.Collections.Generic;
using System.Globalization;
2014-10-11 20:38:13 +00:00
using System.IO;
using System.Linq;
using System.Threading.Tasks;
2020-05-13 02:10:35 +00:00
using Jellyfin.Data.Enums;
using MediaBrowser.Common.Configuration;
using MediaBrowser.Common.Extensions;
2015-11-03 04:34:47 +00:00
using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Devices;
2018-09-12 17:26:21 +00:00
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Library;
2018-09-12 17:26:21 +00:00
using MediaBrowser.Controller.Plugins;
using MediaBrowser.Controller.Security;
using MediaBrowser.Model.Configuration;
using MediaBrowser.Model.Devices;
using MediaBrowser.Model.Entities;
using MediaBrowser.Model.Events;
using MediaBrowser.Model.Globalization;
using MediaBrowser.Model.IO;
using MediaBrowser.Model.Net;
using MediaBrowser.Model.Querying;
2018-09-12 17:26:21 +00:00
using MediaBrowser.Model.Serialization;
using MediaBrowser.Model.Session;
using MediaBrowser.Model.Users;
using Microsoft.Extensions.Logging;
2014-10-11 20:38:13 +00:00
2016-11-03 07:14:14 +00:00
namespace Emby.Server.Implementations.Devices
2014-10-11 20:38:13 +00:00
{
public class DeviceManager : IDeviceManager
{
2018-09-12 17:26:21 +00:00
private readonly IJsonSerializer _json;
2014-10-11 20:38:13 +00:00
private readonly IUserManager _userManager;
private readonly IFileSystem _fileSystem;
private readonly ILibraryMonitor _libraryMonitor;
2015-11-03 04:34:47 +00:00
private readonly IServerConfigurationManager _config;
2018-09-12 17:26:21 +00:00
private readonly ILibraryManager _libraryManager;
private readonly ILocalizationManager _localizationManager;
private readonly IAuthenticationRepository _authRepo;
2020-04-04 19:40:06 +00:00
private readonly Dictionary<string, ClientCapabilities> _capabilitiesCache;
2018-09-12 17:26:21 +00:00
public event EventHandler<GenericEventArgs<Tuple<string, DeviceOptions>>> DeviceOptionsUpdated;
2020-04-04 19:40:06 +00:00
public event EventHandler<GenericEventArgs<CameraImageUploadInfo>> CameraImageUploaded;
2018-09-12 17:26:21 +00:00
private readonly object _cameraUploadSyncLock = new object();
private readonly object _capabilitiesSyncLock = new object();
2014-12-13 03:56:30 +00:00
public DeviceManager(
IAuthenticationRepository authRepo,
IJsonSerializer json,
ILibraryManager libraryManager,
ILocalizationManager localizationManager,
IUserManager userManager,
IFileSystem fileSystem,
ILibraryMonitor libraryMonitor,
2019-02-06 19:38:42 +00:00
IServerConfigurationManager config)
2014-10-11 20:38:13 +00:00
{
2018-09-12 17:26:21 +00:00
_json = json;
2014-10-11 20:38:13 +00:00
_userManager = userManager;
_fileSystem = fileSystem;
_libraryMonitor = libraryMonitor;
_config = config;
2018-09-12 17:26:21 +00:00
_libraryManager = libraryManager;
_localizationManager = localizationManager;
_authRepo = authRepo;
2020-04-04 19:40:06 +00:00
_capabilitiesCache = new Dictionary<string, ClientCapabilities>(StringComparer.OrdinalIgnoreCase);
2014-10-11 20:38:13 +00:00
}
2018-09-12 17:26:21 +00:00
public void SaveCapabilities(string deviceId, ClientCapabilities capabilities)
2014-10-11 20:38:13 +00:00
{
2018-09-12 17:26:21 +00:00
var path = Path.Combine(GetDevicePath(deviceId), "capabilities.json");
Directory.CreateDirectory(Path.GetDirectoryName(path));
2018-09-12 17:26:21 +00:00
lock (_capabilitiesSyncLock)
2014-10-11 20:38:13 +00:00
{
2018-09-12 17:26:21 +00:00
_capabilitiesCache[deviceId] = capabilities;
2014-10-11 20:38:13 +00:00
2018-09-12 17:26:21 +00:00
_json.SerializeToFile(capabilities, path);
}
}
2014-10-11 20:38:13 +00:00
2018-09-12 17:26:21 +00:00
public void UpdateDeviceOptions(string deviceId, DeviceOptions options)
{
_authRepo.UpdateDeviceOptions(deviceId, options);
2014-10-11 20:38:13 +00:00
2018-09-12 17:26:21 +00:00
if (DeviceOptionsUpdated != null)
{
DeviceOptionsUpdated(this, new GenericEventArgs<Tuple<string, DeviceOptions>>()
{
Argument = new Tuple<string, DeviceOptions>(deviceId, options)
});
2014-10-11 20:38:13 +00:00
}
2018-09-12 17:26:21 +00:00
}
2014-10-11 20:38:13 +00:00
2018-09-12 17:26:21 +00:00
public DeviceOptions GetDeviceOptions(string deviceId)
{
return _authRepo.GetDeviceOptions(deviceId);
}
2014-10-11 20:38:13 +00:00
2018-09-12 17:26:21 +00:00
public ClientCapabilities GetCapabilities(string id)
{
lock (_capabilitiesSyncLock)
{
if (_capabilitiesCache.TryGetValue(id, out var result))
2018-09-12 17:26:21 +00:00
{
return result;
}
2017-10-13 19:18:05 +00:00
2018-09-12 17:26:21 +00:00
var path = Path.Combine(GetDevicePath(id), "capabilities.json");
try
{
return _json.DeserializeFromFile<ClientCapabilities>(path) ?? new ClientCapabilities();
}
catch
{
}
}
2014-10-13 20:14:53 +00:00
2018-09-12 17:26:21 +00:00
return new ClientCapabilities();
2014-10-11 20:38:13 +00:00
}
2018-09-12 17:26:21 +00:00
public DeviceInfo GetDevice(string id)
2014-10-11 20:38:13 +00:00
{
2018-09-12 17:26:21 +00:00
return GetDevice(id, true);
2014-10-11 20:38:13 +00:00
}
2018-09-12 17:26:21 +00:00
private DeviceInfo GetDevice(string id, bool includeCapabilities)
2014-10-11 20:38:13 +00:00
{
2018-09-12 17:26:21 +00:00
var session = _authRepo.Get(new AuthenticationInfoQuery
{
DeviceId = id
}).Items.FirstOrDefault();
var device = session == null ? null : ToDeviceInfo(session);
return device;
2014-10-11 20:38:13 +00:00
}
2014-12-13 03:56:30 +00:00
public QueryResult<DeviceInfo> GetDevices(DeviceQuery query)
2014-10-11 20:38:13 +00:00
{
2020-02-19 20:56:35 +00:00
IEnumerable<AuthenticationInfo> sessions = _authRepo.Get(new AuthenticationInfoQuery
2018-09-12 17:26:21 +00:00
{
//UserId = query.UserId
HasUser = true
}).Items;
2019-01-07 23:27:46 +00:00
2019-01-02 18:13:35 +00:00
// TODO: DeviceQuery doesn't seem to be used from client. Not even Swagger.
2014-12-15 05:49:04 +00:00
if (query.SupportsSync.HasValue)
{
var val = query.SupportsSync.Value;
2020-02-19 20:56:35 +00:00
sessions = sessions.Where(i => GetCapabilities(i.DeviceId).SupportsSync == val);
2014-12-15 05:49:04 +00:00
}
2018-09-12 17:26:21 +00:00
if (!query.UserId.Equals(Guid.Empty))
2014-12-13 03:56:30 +00:00
{
2018-09-12 17:26:21 +00:00
var user = _userManager.GetUserById(query.UserId);
2014-12-13 03:56:30 +00:00
2020-02-19 20:56:35 +00:00
sessions = sessions.Where(i => CanAccessDevice(user, i.DeviceId));
2014-12-31 06:24:49 +00:00
}
2018-09-12 17:26:21 +00:00
var array = sessions.Select(ToDeviceInfo).ToArray();
2020-02-19 20:56:35 +00:00
return new QueryResult<DeviceInfo>(array);
2014-10-11 20:38:13 +00:00
}
2018-09-12 17:26:21 +00:00
private DeviceInfo ToDeviceInfo(AuthenticationInfo authInfo)
{
var caps = GetCapabilities(authInfo.DeviceId);
return new DeviceInfo
{
AppName = authInfo.AppName,
AppVersion = authInfo.AppVersion,
Id = authInfo.DeviceId,
LastUserId = authInfo.UserId,
LastUserName = authInfo.UserName,
Name = authInfo.DeviceName,
DateLastActivity = authInfo.DateLastActivity,
2020-02-19 20:56:35 +00:00
IconUrl = caps?.IconUrl
2018-09-12 17:26:21 +00:00
};
}
private string GetDevicesPath()
{
return Path.Combine(_config.ApplicationPaths.DataPath, "devices");
}
private string GetDevicePath(string id)
2014-10-11 20:38:13 +00:00
{
return Path.Combine(GetDevicesPath(), id.GetMD5().ToString("N", CultureInfo.InvariantCulture));
2014-10-11 20:38:13 +00:00
}
public ContentUploadHistory GetCameraUploadHistory(string deviceId)
{
2018-09-12 17:26:21 +00:00
var path = Path.Combine(GetDevicePath(deviceId), "camerauploads.json");
lock (_cameraUploadSyncLock)
{
try
{
return _json.DeserializeFromFile<ContentUploadHistory>(path);
}
catch (IOException)
{
return new ContentUploadHistory
{
DeviceId = deviceId
};
}
}
2014-10-11 20:38:13 +00:00
}
public async Task AcceptCameraUpload(string deviceId, Stream stream, LocalFileInfo file)
{
2018-09-12 17:26:21 +00:00
var device = GetDevice(deviceId, false);
var uploadPathInfo = GetUploadPath(device);
var path = uploadPathInfo.Item1;
2014-10-11 20:38:13 +00:00
if (!string.IsNullOrWhiteSpace(file.Album))
{
path = Path.Combine(path, _fileSystem.GetValidFilename(file.Album));
}
path = Path.Combine(path, file.Name);
2015-09-10 18:28:22 +00:00
path = Path.ChangeExtension(path, MimeTypes.ToExtension(file.MimeType) ?? "jpg");
2014-10-11 20:38:13 +00:00
Directory.CreateDirectory(Path.GetDirectoryName(path));
2015-09-10 18:28:22 +00:00
2018-09-12 17:26:21 +00:00
await EnsureLibraryFolder(uploadPathInfo.Item2, uploadPathInfo.Item3).ConfigureAwait(false);
_libraryMonitor.ReportFileSystemChangeBeginning(path);
2014-10-11 20:38:13 +00:00
try
{
2020-01-08 16:52:50 +00:00
using (var fs = new FileStream(path, FileMode.Create, FileAccess.Write, FileShare.Read))
2014-10-11 20:38:13 +00:00
{
await stream.CopyToAsync(fs).ConfigureAwait(false);
}
2018-09-12 17:26:21 +00:00
AddCameraUpload(deviceId, file);
2014-10-11 20:38:13 +00:00
}
finally
{
_libraryMonitor.ReportFileSystemChangeComplete(path, true);
}
if (CameraImageUploaded != null)
{
CameraImageUploaded?.Invoke(this, new GenericEventArgs<CameraImageUploadInfo>
{
Argument = new CameraImageUploadInfo
{
Device = device,
FileInfo = file
}
});
}
2014-10-11 20:38:13 +00:00
}
2018-09-12 17:26:21 +00:00
private void AddCameraUpload(string deviceId, LocalFileInfo file)
{
2018-09-12 17:26:21 +00:00
var path = Path.Combine(GetDevicePath(deviceId), "camerauploads.json");
Directory.CreateDirectory(Path.GetDirectoryName(path));
2018-09-12 17:26:21 +00:00
lock (_cameraUploadSyncLock)
2014-10-13 20:14:53 +00:00
{
2018-09-12 17:26:21 +00:00
ContentUploadHistory history;
try
{
history = _json.DeserializeFromFile<ContentUploadHistory>(path);
}
catch (IOException)
{
history = new ContentUploadHistory
{
DeviceId = deviceId
};
}
history.DeviceId = deviceId;
var list = history.FilesUploaded.ToList();
list.Add(file);
2018-12-28 15:48:26 +00:00
history.FilesUploaded = list.ToArray();
2018-09-12 17:26:21 +00:00
_json.SerializeToFile(history, path);
2014-10-13 20:14:53 +00:00
}
2018-09-12 17:26:21 +00:00
}
2014-10-12 01:46:02 +00:00
2018-09-12 17:26:21 +00:00
internal Task EnsureLibraryFolder(string path, string name)
{
var existingFolders = _libraryManager
.RootFolder
.Children
.OfType<Folder>()
.Where(i => _fileSystem.AreEqual(path, i.Path) || _fileSystem.ContainsSubPath(i.Path, path))
.ToList();
if (existingFolders.Count > 0)
{
return Task.CompletedTask;
}
Directory.CreateDirectory(path);
2018-09-12 17:26:21 +00:00
var libraryOptions = new LibraryOptions
{
PathInfos = new[] { new MediaPathInfo { Path = path } },
EnablePhotos = true,
EnableRealtimeMonitor = false,
SaveLocalMetadata = true
};
if (string.IsNullOrWhiteSpace(name))
{
name = _localizationManager.GetLocalizedString("HeaderCameraUploads");
}
return _libraryManager.AddVirtualFolder(name, CollectionType.HomeVideos, libraryOptions, true);
}
private Tuple<string, string, string> GetUploadPath(DeviceInfo device)
{
2014-10-13 20:14:53 +00:00
var config = _config.GetUploadOptions();
2017-03-07 18:27:56 +00:00
var path = config.CameraUploadPath;
2018-09-12 17:26:21 +00:00
2017-03-07 18:27:56 +00:00
if (string.IsNullOrWhiteSpace(path))
2014-10-11 20:38:13 +00:00
{
2017-03-07 18:27:56 +00:00
path = DefaultCameraUploadsPath;
2014-10-11 20:38:13 +00:00
}
2018-09-12 17:26:21 +00:00
var topLibraryPath = path;
2014-10-12 01:46:02 +00:00
if (config.EnableCameraUploadSubfolders)
{
path = Path.Combine(path, _fileSystem.GetValidFilename(device.Name));
}
2018-09-12 17:26:21 +00:00
return new Tuple<string, string, string>(path, topLibraryPath, null);
2015-11-03 04:34:47 +00:00
}
2018-09-12 17:26:21 +00:00
internal string GetUploadsPath()
2014-10-13 20:14:53 +00:00
{
2018-09-12 17:26:21 +00:00
var config = _config.GetUploadOptions();
var path = config.CameraUploadPath;
2014-10-13 20:14:53 +00:00
2018-09-12 17:26:21 +00:00
if (string.IsNullOrWhiteSpace(path))
{
path = DefaultCameraUploadsPath;
}
2017-10-13 19:18:05 +00:00
2018-09-12 17:26:21 +00:00
return path;
}
2014-10-13 20:14:53 +00:00
private string DefaultCameraUploadsPath => Path.Combine(_config.CommonApplicationPaths.DataPath, "camerauploads");
2014-12-29 20:18:48 +00:00
2020-05-13 02:10:35 +00:00
public bool CanAccessDevice(Jellyfin.Data.Entities.User user, string deviceId)
2014-12-29 20:18:48 +00:00
{
2018-09-12 17:26:21 +00:00
if (user == null)
2014-12-29 20:18:48 +00:00
{
2018-09-12 17:26:21 +00:00
throw new ArgumentException("user not found");
2014-12-29 20:18:48 +00:00
}
2018-09-12 17:26:21 +00:00
if (string.IsNullOrEmpty(deviceId))
2014-12-29 20:18:48 +00:00
{
throw new ArgumentNullException(nameof(deviceId));
2014-12-29 20:18:48 +00:00
}
2020-05-13 02:10:35 +00:00
if (user.HasPermission(PermissionKind.EnableAllDevices)
|| user.HasPermission(PermissionKind.IsAdministrator))
{
return true;
}
if (!user.GetPreference(PreferenceKind.EnabledDevices).Contains(deviceId, StringComparer.OrdinalIgnoreCase))
2014-12-29 20:18:48 +00:00
{
var capabilities = GetCapabilities(deviceId);
2015-01-20 05:19:13 +00:00
if (capabilities != null && capabilities.SupportsPersistentIdentifier)
2014-12-29 20:18:48 +00:00
{
return false;
}
}
return true;
}
2018-09-12 17:26:21 +00:00
}
public class DeviceManagerEntryPoint : IServerEntryPoint
{
private readonly DeviceManager _deviceManager;
private readonly IServerConfigurationManager _config;
private ILogger _logger;
public DeviceManagerEntryPoint(
IDeviceManager deviceManager,
IServerConfigurationManager config,
ILogger<DeviceManagerEntryPoint> logger)
2018-09-12 17:26:21 +00:00
{
_deviceManager = (DeviceManager)deviceManager;
_config = config;
_logger = logger;
}
2019-01-27 14:40:37 +00:00
public async Task RunAsync()
2018-09-12 17:26:21 +00:00
{
if (!_config.Configuration.CameraUploadUpgraded && _config.Configuration.IsStartupWizardCompleted)
{
var path = _deviceManager.GetUploadsPath();
if (Directory.Exists(path))
2018-09-12 17:26:21 +00:00
{
try
{
await _deviceManager.EnsureLibraryFolder(path, null).ConfigureAwait(false);
}
catch (Exception ex)
{
2018-12-20 12:11:26 +00:00
_logger.LogError(ex, "Error creating camera uploads library");
2018-09-12 17:26:21 +00:00
}
_config.Configuration.CameraUploadUpgraded = true;
_config.SaveConfiguration();
}
}
}
#region IDisposable Support
private bool disposedValue = false; // To detect redundant calls
protected virtual void Dispose(bool disposing)
{
if (!disposedValue)
{
if (disposing)
{
// TODO: dispose managed state (managed objects).
}
// TODO: free unmanaged resources (unmanaged objects) and override a finalizer below.
// TODO: set large fields to null.
disposedValue = true;
}
}
// TODO: override a finalizer only if Dispose(bool disposing) above has code to free unmanaged resources.
// ~DeviceManagerEntryPoint() {
// // Do not change this code. Put cleanup code in Dispose(bool disposing) above.
// Dispose(false);
// }
// This code added to correctly implement the disposable pattern.
public void Dispose()
{
// Do not change this code. Put cleanup code in Dispose(bool disposing) above.
Dispose(true);
// TODO: uncomment the following line if the finalizer is overridden above.
// GC.SuppressFinalize(this);
2014-12-29 20:18:48 +00:00
}
2018-09-12 17:26:21 +00:00
#endregion
2014-10-11 20:38:13 +00:00
}
public class DevicesConfigStore : IConfigurationFactory
{
public IEnumerable<ConfigurationStore> GetConfigurations()
{
2018-09-12 17:26:21 +00:00
return new ConfigurationStore[]
2014-10-11 20:38:13 +00:00
{
new ConfigurationStore
{
Key = "devices",
ConfigurationType = typeof(DevicesOptions)
}
};
}
}
public static class UploadConfigExtension
{
public static DevicesOptions GetUploadOptions(this IConfigurationManager config)
{
return config.GetConfiguration<DevicesOptions>("devices");
}
}
2018-12-28 15:48:26 +00:00
}