add new sharing function

This commit is contained in:
Luke Pulverenti 2015-06-30 19:59:45 -04:00
parent 86571a6297
commit 04041105d8
16 changed files with 516 additions and 5 deletions

View File

@ -106,6 +106,7 @@
<Compile Include="Reports\Stat\ReportStatGroup.cs" />
<Compile Include="Reports\Stat\ReportStatItem.cs" />
<Compile Include="Reports\Stat\ReportStatResult.cs" />
<Compile Include="Social\SharingService.cs" />
<Compile Include="StartupWizardService.cs" />
<Compile Include="Subtitles\SubtitleService.cs" />
<Compile Include="Movies\CollectionService.cs" />

View File

@ -0,0 +1,138 @@
using MediaBrowser.Common.Extensions;
using MediaBrowser.Controller.Dlna;
using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.Net;
using MediaBrowser.Controller.Social;
using MediaBrowser.Model.Entities;
using MediaBrowser.Model.Social;
using ServiceStack;
using System;
using System.IO;
using System.Threading.Tasks;
namespace MediaBrowser.Api.Social
{
[Route("/Social/Shares/{Id}", "GET", Summary = "Gets a share")]
[Authenticated]
public class GetSocialShareInfo : IReturn<SocialShareInfo>
{
[ApiMember(Name = "Id", Description = "The id of the item", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")]
public string Id { get; set; }
}
[Route("/Social/Shares/Public/{Id}", "GET", Summary = "Gets a share")]
public class GetPublicSocialShareInfo : IReturn<SocialShareInfo>
{
[ApiMember(Name = "Id", Description = "The id of the item", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")]
public string Id { get; set; }
}
[Route("/Social/Shares/Public/{Id}/Image", "GET", Summary = "Gets a share")]
public class GetShareImage
{
[ApiMember(Name = "Id", Description = "The id of the item", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")]
public string Id { get; set; }
}
[Route("/Social/Shares", "POST", Summary = "Creates a share")]
[Authenticated]
public class CreateShare : IReturn<SocialShareInfo>
{
[ApiMember(Name = "ItemId", Description = "The id of the item", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "POST")]
public string ItemId { get; set; }
[ApiMember(Name = "UserId", Description = "The user id", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "POST")]
public string UserId { get; set; }
}
[Route("/Social/Shares/{Id}", "DELETE", Summary = "Deletes a share")]
[Authenticated]
public class DeleteShare : IReturnVoid
{
[ApiMember(Name = "Id", Description = "The id of the item", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "DELETE")]
public string Id { get; set; }
}
public class SharingService : BaseApiService
{
private readonly ISharingManager _sharingManager;
private readonly ILibraryManager _libraryManager;
private readonly IDlnaManager _dlnaManager;
public SharingService(ISharingManager sharingManager, IDlnaManager dlnaManager, ILibraryManager libraryManager)
{
_sharingManager = sharingManager;
_dlnaManager = dlnaManager;
_libraryManager = libraryManager;
}
public object Get(GetSocialShareInfo request)
{
var info = _sharingManager.GetShareInfo(request.Id);
return ToOptimizedResult(info);
}
public object Get(GetPublicSocialShareInfo request)
{
var info = _sharingManager.GetShareInfo(request.Id);
if (info.ExpirationDate >= DateTime.UtcNow)
{
throw new ResourceNotFoundException();
}
return ToOptimizedResult(info);
}
public async Task<object> Post(CreateShare request)
{
var info = await _sharingManager.CreateShare(request.ItemId, request.UserId).ConfigureAwait(false);
return ToOptimizedResult(info);
}
public void Delete(DeleteShare request)
{
var task = _sharingManager.DeleteShare(request.Id);
Task.WaitAll(task);
}
public object Get(GetShareImage request)
{
var share = _sharingManager.GetShareInfo(request.Id);
if (share == null)
{
throw new ResourceNotFoundException();
}
if (share.ExpirationDate >= DateTime.UtcNow)
{
throw new ResourceNotFoundException();
}
var item = _libraryManager.GetItemById(share.ItemId);
var image = item.GetImageInfo(ImageType.Primary, 0);
if (image != null)
{
return ToStaticFileResult(image.Path);
}
// Grab a dlna icon if nothing else is available
using (var response = _dlnaManager.GetIcon("logo240.jpg"))
{
using (var ms = new MemoryStream())
{
response.Stream.CopyTo(ms);
ms.Position = 0;
var bytes = ms.ToArray();
return ResultFactory.GetResult(bytes, "image/" + response.Format.ToString().ToLower());
}
}
}
}
}

View File

@ -329,6 +329,7 @@
<Compile Include="Security\IAuthenticationRepository.cs" />
<Compile Include="Security\IEncryptionManager.cs" />
<Compile Include="Session\AuthenticationRequest.cs" />
<Compile Include="Social\ISharingManager.cs" />
<Compile Include="Subtitles\ISubtitleManager.cs" />
<Compile Include="Subtitles\ISubtitleProvider.cs" />
<Compile Include="Providers\ItemIdentifier.cs" />

View File

@ -0,0 +1,28 @@
using MediaBrowser.Model.Social;
using System.Threading.Tasks;
namespace MediaBrowser.Controller.Social
{
public interface ISharingManager
{
/// <summary>
/// Creates the share.
/// </summary>
/// <param name="itemId">The item identifier.</param>
/// <param name="userId">The user identifier.</param>
/// <returns>Task&lt;SocialShareInfo&gt;.</returns>
Task<SocialShareInfo> CreateShare(string itemId, string userId);
/// <summary>
/// Gets the share information.
/// </summary>
/// <param name="id">The identifier.</param>
/// <returns>SocialShareInfo.</returns>
SocialShareInfo GetShareInfo(string id);
/// <summary>
/// Deletes the share.
/// </summary>
/// <param name="id">The identifier.</param>
/// <returns>Task.</returns>
Task DeleteShare(string id);
}
}

View File

@ -1076,6 +1076,9 @@
<Compile Include="..\MediaBrowser.Model\Session\UserDataChangeInfo.cs">
<Link>Session\UserDataChangeInfo.cs</Link>
</Compile>
<Compile Include="..\MediaBrowser.Model\Social\SocialShareInfo.cs">
<Link>Social\SocialShareInfo.cs</Link>
</Compile>
<Compile Include="..\MediaBrowser.Model\Sync\CompleteSyncJobInfo.cs">
<Link>Sync\CompleteSyncJobInfo.cs</Link>
</Compile>

View File

@ -1032,6 +1032,9 @@
<Compile Include="..\MediaBrowser.Model\Session\UserDataChangeInfo.cs">
<Link>Session\UserDataChangeInfo.cs</Link>
</Compile>
<Compile Include="..\mediabrowser.model\social\SocialShareInfo.cs">
<Link>Social\SocialShareInfo.cs</Link>
</Compile>
<Compile Include="..\MediaBrowser.Model\Sync\CompleteSyncJobInfo.cs">
<Link>Sync\CompleteSyncJobInfo.cs</Link>
</Compile>

View File

@ -25,7 +25,7 @@ namespace MediaBrowser.Model.Configuration
/// </summary>
/// <value>The public HTTPS port.</value>
public int PublicHttpsPort { get; set; }
/// <summary>
/// Gets or sets the HTTP server port number.
/// </summary>
@ -49,7 +49,7 @@ namespace MediaBrowser.Model.Configuration
/// </summary>
/// <value><c>true</c> if [enable user specific user views]; otherwise, <c>false</c>.</value>
public bool EnableUserSpecificUserViews { get; set; }
/// <summary>
/// Gets or sets the value pointing to the file system where the ssl certiifcate is located..
/// </summary>
@ -103,7 +103,7 @@ namespace MediaBrowser.Model.Configuration
/// </summary>
/// <value><c>true</c> if [enable library metadata sub folder]; otherwise, <c>false</c>.</value>
public bool EnableLibraryMetadataSubFolder { get; set; }
/// <summary>
/// Gets or sets the preferred metadata language.
/// </summary>
@ -211,6 +211,8 @@ namespace MediaBrowser.Model.Configuration
public AutoOnOff EnableLibraryMonitor { get; set; }
public int SharingExpirationDays { get; set; }
/// <summary>
/// Initializes a new instance of the <see cref="ServerConfiguration" /> class.
/// </summary>
@ -231,6 +233,7 @@ namespace MediaBrowser.Model.Configuration
EnableUPnP = true;
SharingExpirationDays = 30;
MinResumePct = 5;
MaxResumePct = 90;

View File

@ -379,6 +379,7 @@
<Compile Include="Session\TranscodingInfo.cs" />
<Compile Include="Session\UserDataChangeInfo.cs" />
<Compile Include="Devices\ContentUploadHistory.cs" />
<Compile Include="Social\SocialShareInfo.cs" />
<Compile Include="Sync\CompleteSyncJobInfo.cs" />
<Compile Include="Sync\DeviceFileInfo.cs" />
<Compile Include="Sync\ItemFIleInfo.cs" />

View File

@ -0,0 +1,16 @@
using System;
namespace MediaBrowser.Model.Social
{
public class SocialShareInfo
{
public string Id { get; set; }
public string Url { get; set; }
public string ItemId { get; set; }
public string UserId { get; set; }
public DateTime ExpirationDate { get; set; }
public string Name { get; set; }
public string ImageUrl { get; set; }
public string Overview { get; set; }
}
}

View File

@ -38,7 +38,7 @@
"HeaderSupportTheTeam": "Support the Emby Team",
"TextEnjoyBonusFeatures": "Enjoy Bonus Features",
"TitleLiveTV": "Live TV",
"ButtonCancelSyncJob": "Cancel sync job",
"ButtonCancelSyncJob": "Cancel sync job",
"TitleSync": "Sync",
"HeaderSelectDate": "Select Date",
"ButtonDonate": "Donate",
@ -811,5 +811,8 @@
"ErrorMessagePasswordNotMatchConfirm": "The password and password confirmation must match.",
"ErrorMessageUsernameInUse": "The username is already in use. Please choose a new name and try again.",
"ErrorMessageEmailInUse": "The email address is already in use. Please enter a new email address and try again, or use the forgot password feature.",
"MessageThankYouForConnectSignUp": "Thank you for signing up for Emby Connect. An email will be sent to your address with instructions on how to confirm your new account. Please confirm the account and then return here to sign in."
"MessageThankYouForConnectSignUp": "Thank you for signing up for Emby Connect. An email will be sent to your address with instructions on how to confirm your new account. Please confirm the account and then return here to sign in.",
"HeaderShare": "Share",
"ButtonShareHelp": "Only a web page containing media information will be shared. Media files are never shared publicly."
}

View File

@ -313,6 +313,9 @@
"OptionAllowRemoteControlOthers": "Allow remote control of other users",
"OptionAllowRemoteSharedDevices": "Allow remote control of shared devices",
"OptionAllowRemoteSharedDevicesHelp": "Dlna devices are considered shared until a user begins controlling it.",
"OptionAllowLinkSharing": "Allow social media sharing",
"OptionAllowLinkSharingHelp": "Only web pages containing media information are shared. Media files are never shared publicly. Shares are time-limited and will expire based on your server sharing settings.",
"HeaderSharing": "Sharing",
"HeaderRemoteControl": "Remote Control",
"OptionMissingTmdbId": "Missing Tmdb Id",
"OptionIsHD": "HD",

View File

@ -227,6 +227,8 @@
<Compile Include="Logging\PatternsLogger.cs" />
<Compile Include="MediaEncoder\EncodingManager.cs" />
<Compile Include="Persistence\BaseSqliteRepository.cs" />
<Compile Include="Social\SharingManager.cs" />
<Compile Include="Social\SharingRepository.cs" />
<Compile Include="Sorting\StartDateComparer.cs" />
<Compile Include="Sync\SyncHelper.cs" />
<Compile Include="Sync\SyncJobOptions.cs" />

View File

@ -0,0 +1,88 @@
using MediaBrowser.Common.Extensions;
using MediaBrowser.Controller;
using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.Social;
using MediaBrowser.Model.Social;
using System;
using System.Threading.Tasks;
namespace MediaBrowser.Server.Implementations.Social
{
public class SharingManager : ISharingManager
{
private readonly SharingRepository _repository;
private readonly IServerConfigurationManager _config;
private readonly ILibraryManager _libraryManager;
private readonly IServerApplicationHost _appHost;
public SharingManager(SharingRepository repository, IServerConfigurationManager config, ILibraryManager libraryManager, IServerApplicationHost appHost)
{
_repository = repository;
_config = config;
_libraryManager = libraryManager;
_appHost = appHost;
}
public async Task<SocialShareInfo> CreateShare(string itemId, string userId)
{
if (string.IsNullOrWhiteSpace(itemId))
{
throw new ArgumentNullException("itemId");
}
if (string.IsNullOrWhiteSpace(userId))
{
throw new ArgumentNullException("userId");
}
var item = _libraryManager.GetItemById(itemId);
if (item == null)
{
throw new ResourceNotFoundException();
}
var externalUrl = _appHost.GetSystemInfo().WanAddress;
if (string.IsNullOrWhiteSpace(externalUrl))
{
throw new InvalidOperationException("No external server address is currently available.");
}
var info = new SocialShareInfo
{
Id = Guid.NewGuid().ToString("N"),
ExpirationDate = DateTime.UtcNow.AddDays(_config.Configuration.SharingExpirationDays),
ItemId = itemId,
UserId = userId,
Overview = item.Overview,
Name = GetTitle(item)
};
info.ImageUrl = externalUrl + "/Social/Shares/Public/" + info.Id + "/Image";
info.ImageUrl = externalUrl + "/web/shared.html?id=" + info.Id;
await _repository.CreateShare(info).ConfigureAwait(false);
return GetShareInfo(info.Id);
}
private string GetTitle(BaseItem item)
{
return item.Name;
}
public SocialShareInfo GetShareInfo(string id)
{
var info = _repository.GetShareInfo(id);
return info;
}
public Task DeleteShare(string id)
{
return _repository.DeleteShare(id);
}
}
}

View File

@ -0,0 +1,184 @@
using MediaBrowser.Common.Configuration;
using MediaBrowser.Model.Logging;
using MediaBrowser.Model.Social;
using MediaBrowser.Server.Implementations.Persistence;
using System;
using System.Data;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
namespace MediaBrowser.Server.Implementations.Social
{
public class SharingRepository : BaseSqliteRepository
{
private IDbConnection _connection;
private IDbCommand _saveShareCommand;
private readonly IApplicationPaths _appPaths;
public SharingRepository(ILogManager logManager, IApplicationPaths appPaths)
: base(logManager)
{
_appPaths = appPaths;
}
/// <summary>
/// Opens the connection to the database
/// </summary>
/// <returns>Task.</returns>
public async Task Initialize()
{
var dbFile = Path.Combine(_appPaths.DataPath, "shares.db");
_connection = await SqliteExtensions.ConnectToDb(dbFile, Logger).ConfigureAwait(false);
string[] queries = {
"create table if not exists Shares (Id GUID, ItemId TEXT, UserId TEXT, ExpirationDate DateTime, PRIMARY KEY (Id))",
"create index if not exists idx_Shares on Shares(Id)",
//pragmas
"pragma temp_store = memory",
"pragma shrink_memory"
};
_connection.RunQueries(queries, Logger);
PrepareStatements();
}
/// <summary>
/// Prepares the statements.
/// </summary>
private void PrepareStatements()
{
_saveShareCommand = _connection.CreateCommand();
_saveShareCommand.CommandText = "replace into Shares (Id, ItemId, UserId, ExpirationDate) values (@Id, @ItemId, @UserId, @ExpirationDate)";
_saveShareCommand.Parameters.Add(_saveShareCommand, "@Id");
_saveShareCommand.Parameters.Add(_saveShareCommand, "@ItemId");
_saveShareCommand.Parameters.Add(_saveShareCommand, "@UserId");
_saveShareCommand.Parameters.Add(_saveShareCommand, "@ExpirationDate");
}
public async Task CreateShare(SocialShareInfo info)
{
if (info == null)
{
throw new ArgumentNullException("info");
}
if (string.IsNullOrWhiteSpace(info.Id))
{
throw new ArgumentNullException("info.Id");
}
var cancellationToken = CancellationToken.None;
cancellationToken.ThrowIfCancellationRequested();
await WriteLock.WaitAsync(cancellationToken).ConfigureAwait(false);
IDbTransaction transaction = null;
try
{
transaction = _connection.BeginTransaction();
_saveShareCommand.GetParameter(0).Value = new Guid(info.Id);
_saveShareCommand.GetParameter(1).Value = info.ItemId;
_saveShareCommand.GetParameter(2).Value = info.UserId;
_saveShareCommand.GetParameter(3).Value = info.ExpirationDate;
_saveShareCommand.Transaction = transaction;
_saveShareCommand.ExecuteNonQuery();
transaction.Commit();
}
catch (OperationCanceledException)
{
if (transaction != null)
{
transaction.Rollback();
}
throw;
}
catch (Exception e)
{
Logger.ErrorException("Failed to save share:", e);
if (transaction != null)
{
transaction.Rollback();
}
throw;
}
finally
{
if (transaction != null)
{
transaction.Dispose();
}
WriteLock.Release();
}
}
public SocialShareInfo GetShareInfo(string id)
{
if (string.IsNullOrWhiteSpace(id))
{
throw new ArgumentNullException("id");
}
var cmd = _connection.CreateCommand();
cmd.CommandText = "select Id, ItemId, UserId, ExpirationDate from Shares where id = @id";
cmd.Parameters.Add(cmd, "@id", DbType.Guid).Value = new Guid(id);
using (var reader = cmd.ExecuteReader(CommandBehavior.SequentialAccess | CommandBehavior.SingleResult | CommandBehavior.SingleRow))
{
if (reader.Read())
{
return GetSocialShareInfo(reader);
}
}
return null;
}
private SocialShareInfo GetSocialShareInfo(IDataReader reader)
{
var info = new SocialShareInfo();
info.Id = reader.GetGuid(0).ToString("N");
info.ItemId = reader.GetString(1);
info.UserId = reader.GetString(2);
info.ExpirationDate = reader.GetDateTime(3).ToUniversalTime();
return info;
}
public async Task DeleteShare(string id)
{
}
protected override void CloseConnection()
{
if (_connection != null)
{
if (_connection.IsOpen())
{
_connection.Close();
}
_connection.Dispose();
_connection = null;
}
}
}
}

View File

@ -38,6 +38,7 @@ using MediaBrowser.Controller.Providers;
using MediaBrowser.Controller.Resolvers;
using MediaBrowser.Controller.Security;
using MediaBrowser.Controller.Session;
using MediaBrowser.Controller.Social;
using MediaBrowser.Controller.Sorting;
using MediaBrowser.Controller.Subtitles;
using MediaBrowser.Controller.Sync;
@ -84,6 +85,7 @@ using MediaBrowser.Server.Implementations.Playlists;
using MediaBrowser.Server.Implementations.Security;
using MediaBrowser.Server.Implementations.ServerManager;
using MediaBrowser.Server.Implementations.Session;
using MediaBrowser.Server.Implementations.Social;
using MediaBrowser.Server.Implementations.Sync;
using MediaBrowser.Server.Implementations.Themes;
using MediaBrowser.Server.Implementations.TV;
@ -522,6 +524,10 @@ namespace MediaBrowser.Server.Startup.Common
MediaEncoder, ChapterManager);
RegisterSingleInstance(EncodingManager);
var sharingRepo = new SharingRepository(LogManager, ApplicationPaths);
await sharingRepo.Initialize().ConfigureAwait(false);
RegisterSingleInstance<ISharingManager>(new SharingManager(sharingRepo, ServerConfigurationManager, LibraryManager, this));
RegisterSingleInstance<ISsdpHandler>(new SsdpHandler(LogManager.GetLogger("SsdpHandler"), ServerConfigurationManager, this));
var activityLogRepo = await GetActivityLogRepository().ConfigureAwait(false);

View File

@ -193,6 +193,15 @@
<Content Include="dashboard-ui\scripts\homeupcoming.js">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="dashboard-ui\scripts\sharingmanager.js">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="dashboard-ui\scripts\sharingwidget.js">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="dashboard-ui\share.html">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="dashboard-ui\themes\android.css">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
@ -274,6 +283,17 @@
<Content Include="dashboard-ui\thirdparty\paper-button-style.css">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="dashboard-ui\thirdparty\social-share-kit-1.0.4\.gitignore" />
<Content Include="dashboard-ui\thirdparty\social-share-kit-1.0.4\dist\css\social-share-kit.css">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="dashboard-ui\thirdparty\social-share-kit-1.0.4\dist\fonts\social-share-kit.svg">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="dashboard-ui\thirdparty\social-share-kit-1.0.4\dist\js\social-share-kit.js" />
<Content Include="dashboard-ui\thirdparty\social-share-kit-1.0.4\dist\js\social-share-kit.min.js">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="dashboard-ui\voice\voice.css">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
@ -2416,6 +2436,17 @@
<None Include="dashboard-ui\thirdparty\materialicons\MaterialIcons-Regular.woff2">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Include="dashboard-ui\thirdparty\social-share-kit-1.0.4\dist\fonts\social-share-kit.eot">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Include="dashboard-ui\thirdparty\social-share-kit-1.0.4\dist\fonts\social-share-kit.ttf">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Include="dashboard-ui\thirdparty\social-share-kit-1.0.4\dist\fonts\social-share-kit.woff">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Include="dashboard-ui\thirdparty\social-share-kit-1.0.4\LICENSE" />
<None Include="dashboard-ui\thirdparty\social-share-kit-1.0.4\README.md" />
<None Include="packages.config" />
</ItemGroup>
<ItemGroup />