Skip to content

Commit

Permalink
Merge branch 'develop'
Browse files Browse the repository at this point in the history
  • Loading branch information
fxsth committed Sep 24, 2023
2 parents ae88656 + 9a51bc6 commit aac0329
Show file tree
Hide file tree
Showing 40 changed files with 529 additions and 270 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ Focused on proper functioning rather than good looking design. Features:
- movies
- tv shows, seasons or single episodes
- playlists
- Supports multiple file versions (e.g. in different resolutions / codecs)
- Docker image for amd64 and arm64. Or run the windows executable .

Use `docker pull ghcr.io/fxsth/pledo:latest` to get the docker image of latest release.
Expand Down
8 changes: 7 additions & 1 deletion Web/ClientApp/src/components/DownloadButton.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@ export default class DownloadButton extends React.Component {
this.state = {
mediaKey: this.props.mediaKey,
isLoading: false,
mediatype: this.props.mediaType
mediatype: this.props.mediaType,
mediaFileKey: this.props.mediaFileKey
};
}

Expand Down Expand Up @@ -39,6 +40,11 @@ export default class DownloadButton extends React.Component {
season: this.props.season
})
}
if (typeof this.props.mediaFileKey !== 'undefined') {
input = input + '?' + new URLSearchParams({
mediaFileKey: this.props.mediaFileKey
})
}
return fetch(input, settings)
.then(response => {
if (response.status >= 200 && response.status < 300) {
Expand Down
1 change: 1 addition & 0 deletions Web/ClientApp/src/components/DropdownSetting.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ function DropdownSetting(props) {
{setting.options.map((option) => <option value={option.value} label={option.uiName}/>)}
</Input>
</InputGroup>
<small>{setting.description}</small>
</FormGroup>
)
}
Expand Down
1 change: 1 addition & 0 deletions Web/ClientApp/src/components/FilePathSetting.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ function FilePathSetting(props) {
directory
</button>
</InputGroup>
<small>{setting.description}</small>
<Modal isOpen={showFolderPicker}>
<ModalHeader close={
<Button className="close"
Expand Down
20 changes: 11 additions & 9 deletions Web/ClientApp/src/components/Movies.js
Original file line number Diff line number Diff line change
Expand Up @@ -119,15 +119,17 @@ export class Movies extends Component {
</tr>
</thead>
<tbody>
{movies.map(movie =>
<tr key={movie.title}>
<td>{movie.title}</td>
<td>{movie.year}</td>
<td>{movie.videoCodec}</td>
<td>{movie.videoResolution}</td>
<td>{this.humanizeByteSize(movie.totalBytes)}</td>
<td><DownloadButton mediaType='movie' mediaKey={movie.ratingKey}>Download</DownloadButton></td>
</tr>
{movies.map(movie =>
movie.mediaFiles.map(mediaFile =>
<tr key={mediaFile.downloadUri}>
<td>{movie.title}</td>
<td>{movie.year}</td>
<td>{mediaFile.videoCodec}</td>
<td>{mediaFile.videoResolution}</td>
<td>{this.humanizeByteSize(mediaFile.totalBytes)}</td>
<td><DownloadButton mediaType='movie' mediaKey={movie.ratingKey} mediaFileKey={mediaFile.downloadUri}>Download</DownloadButton></td>
</tr>)

)}
</tbody>
</Table>
Expand Down
4 changes: 3 additions & 1 deletion Web/ClientApp/src/components/Overview.js
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,8 @@ export class Overview extends Component {

render() {
if (this.state.account || this.state.loginPending) {
const serversTotal = this.state.servers?.length ?? 0
const serversOnline = this.state.servers?.filter(x=>x.isOnline)?.length ?? 0
return (
<div>
<h2>Hello, {this.state.account ? this.state.account.username : "User"}!</h2>
Expand All @@ -74,7 +76,7 @@ export class Overview extends Component {
console.log("Sync finished")
this.populateServerData()
}}/>}
<p>You have access to following servers:</p>
<p>You have access to following servers: ({serversOnline}/{serversTotal} online)</p>
<Container>
<Row>
{this.state.servers ? this.state.servers.map(server =>
Expand Down
8 changes: 8 additions & 0 deletions Web/ClientApp/src/components/Settings.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import {FolderPicker} from "./FolderPicker";
import {Modal, ModalBody, ModalHeader, Form, FormGroup, Label, Input, InputGroup, Button} from 'reactstrap';
import FilePathSetting from "./FilePathSetting";
import DropdownSetting from "./DropdownSetting";
import StringSetting from "./StringSetting";

export class Settings extends React.Component {
constructor(props) {
Expand Down Expand Up @@ -56,6 +57,13 @@ export class Settings extends React.Component {
callback={(directory) => this.updateValueOfSetting(setting.key, directory)}
/>
}

{setting.type === "string" &&
<StringSetting
setting={setting}
callback={(value) => this.updateValueOfSetting(setting.key, value)}
/>
}
</>
)}
<Input type="reset" value="Cancel"/>
Expand Down
19 changes: 19 additions & 0 deletions Web/ClientApp/src/components/StringSetting.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import React from "react";
import {FormGroup, Label, Input, InputGroup} from 'reactstrap';

function StringSetting(props) {
let setting = props.setting;
let callback = props.callback;
return (
<FormGroup>
<Label for={setting.key}>{setting.name}</Label>
<InputGroup>
<Input id={setting.key} name={setting.key} type="text" value={setting.value}
onChange={(e) => callback(e.target.value)} />
</InputGroup>
<small>{setting.description}</small>
</FormGroup>
)
}

export default StringSetting;
2 changes: 1 addition & 1 deletion Web/ClientApp/src/components/SyncButton.js
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ export class SyncButton extends Component {
<span>{' ' + this.state.tasks[0].name} </span>
</Button>
} else
return <Button color="primary" onClick={this.handleClick.bind(this)}>Sync all data now</Button>
return <Button color="primary" onClick={this.handleClick.bind(this)}>Sync all metadata now.</Button>
}

async populateTaskData() {
Expand Down
10 changes: 5 additions & 5 deletions Web/ClientApp/src/components/TvShows.js
Original file line number Diff line number Diff line change
Expand Up @@ -155,13 +155,13 @@ export class TvShows extends Component {
<tr>
<td>S{episode.seasonNumber}E{episode.episodeNumber}</td>
<td>{episode.title}</td>
<td>{episode.year}</td>
<td>{episode.videoCodec}</td>
<td>{episode.videoResolution}</td>
<td>{this.humanizeByteSize(episode.totalBytes)}</td>
<td>{episode?.year}</td>
<td>{episode.mediaFiles[0].videoCodec}</td>
<td>{episode.mediaFiles[0].videoResolution}</td>
<td>{this.humanizeByteSize(episode.mediaFiles[0].totalBytes)}</td>
<td><DownloadButton mediaType='episode' mediaKey={episode.ratingKey}>Download</DownloadButton></td>
</tr>
)}
)}
</tbody>
</table>
</CollapsibleTableRow>
Expand Down
2 changes: 2 additions & 0 deletions Web/Constants/SettingsConstants.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,6 @@ public static class SettingsConstants
public const string EpisodeDirectoryKey = "EpisodeDirectoryPath";
public const string MovieFileTemplateKey = "MovieFileTemplate";
public const string EpisodeFileTemplateKey = "EpisodeFileTemplate";
public const string PreferredResolutionKey = "PreferredResolution";
public const string PreferredVideoCodec = "PreferredVideoCodec";
}
8 changes: 4 additions & 4 deletions Web/Controllers/DownloadController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -31,15 +31,15 @@ public async Task<IEnumerable<DownloadElementResource>> GetPendingDownloads()
}

[HttpPost("movie/{key}")]
public async Task DownloadMovie( string key)
public async Task DownloadMovie( string key, [FromQuery] string? mediaFileKey)
{
await _downloadService.DownloadMovie(key);
await _downloadService.DownloadMovie(key, mediaFileKey);
}

[HttpPost("episode/{key}")]
public async Task DownloadEpisode( string key)
public async Task DownloadEpisode( string key, [FromQuery] string? mediaFileKey)
{
await _downloadService.DownloadEpisode(key);
await _downloadService.DownloadEpisode(key, mediaFileKey);
}

[HttpPost("tvshow/{key}")]
Expand Down
2 changes: 1 addition & 1 deletion Web/Controllers/MovieController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,6 @@ public MovieController(UnitOfWork unitOfWork, ILogger<TvShowController> logger)
[HttpGet]
public async Task<IEnumerable<Movie>> Get([FromQuery] string libraryId)
{
return _unitOfWork.MovieRepository.Get(x => x.LibraryId == libraryId);
return _unitOfWork.MovieRepository.Get(x => x.LibraryId == libraryId, includeProperties:nameof(Movie.MediaFiles));
}
}
1 change: 0 additions & 1 deletion Web/Controllers/PlaylistController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
using Web.Data;
using Web.Models;
using Web.Models.DTO;
using Web.Models.Interfaces;

namespace Web.Controllers;

Expand Down
4 changes: 1 addition & 3 deletions Web/Controllers/SyncController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -28,10 +28,8 @@ public async Task Sync(SyncType syncType)
switch (syncType)
{
case SyncType.Full:
await _syncService.SyncAll();
break;
case SyncType.Connection:
await _syncService.SyncConnections();
await _syncService.Sync(syncType);
break;
default:
throw new ArgumentOutOfRangeException(nameof(syncType), syncType, null);
Expand Down
15 changes: 13 additions & 2 deletions Web/Data/DbContext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ public class DbContext : Microsoft.EntityFrameworkCore.DbContext
public DbSet<Movie> Movies { get; set; }
public DbSet<TvShow> TvShows { get; set; }
public DbSet<Episode> Episodes { get; set; }
public DbSet<MediaFile> MediaFiles { get; set; }
public DbSet<BusyTask> Tasks { get; set; }
public DbSet<Setting> Settings { get; set; }
public DbSet<DownloadElement> Downloads { get; set; }
Expand All @@ -22,8 +23,18 @@ protected override void OnModelCreating(ModelBuilder modelBuilder)
modelBuilder.Entity<Playlist>()
.Property(b => b.Items)
.HasConversion(
v => System.Text.Json.JsonSerializer.Serialize(v, (JsonSerializerOptions)null),
v => System.Text.Json.JsonSerializer.Deserialize<List<string>>(v, (JsonSerializerOptions)null));
v => JsonSerializer.Serialize(v, (JsonSerializerOptions)null),
v => JsonSerializer.Deserialize<List<string>>(v, (JsonSerializerOptions)null));

modelBuilder.Entity<Movie>()
.HasMany(e => e.MediaFiles)
.WithOne()
.HasForeignKey(e => e.MovieRatingKey);

modelBuilder.Entity<Episode>()
.HasMany(e => e.MediaFiles)
.WithOne()
.HasForeignKey(e => e.EpisodeRatingKey);
}

public DbContext(DbContextOptions<DbContext> options)
Expand Down
16 changes: 16 additions & 0 deletions Web/Data/DbInitializer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,22 @@ public static void Initialize(DbContext context)
Name = "Episode file template",
Type = "enum"
});
context.AddSettingIfNotExist(new Setting()
{
Key = SettingsConstants.PreferredResolutionKey,
Value = "",
Name = "Preferred resolution",
Description = "If there are multiple file versions, the one with preferred resolution is selected. If there is no match or there is no preference, first version found will be selected.",
Type = "enum"
});
context.AddSettingIfNotExist(new Setting()
{
Key = SettingsConstants.PreferredVideoCodec,
Value = "",
Name = "Preferred video codec",
Description = "If there are multiple file versions, the one with preferred video codec is selected. If there is no match or there is no preference, first version found will be selected.",
Type = "enum"
});
context.SaveChanges();
}

Expand Down
22 changes: 20 additions & 2 deletions Web/Data/EpisodeRepository.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,16 +8,34 @@ public EpisodeRepository(DbContext dbContext) : base(dbContext)
{
}

public Task Upsert(IEnumerable<Episode> t)
public override Task Upsert(IEnumerable<Episode> t)
{
var episodesInDb = DbContext.Episodes.ToHashSet();
var episodesToUpsert = t.ToHashSet();
IEnumerable<Episode> episodes = t.ToList();
var episodesToUpsert = episodes.ToHashSet();
var episodesToDelete = episodesInDb.ExceptBy(episodesToUpsert.Select(x=>x.RatingKey), x=>x.RatingKey);
var episodesToInsert = episodesToUpsert.ExceptBy(episodesInDb.Select(x=>x.RatingKey), x=>x.RatingKey);
var episodesToUpdate = episodesInDb.IntersectBy(episodesToUpsert.Select(x=>x.RatingKey), x=>x.RatingKey);
DbContext.Episodes.RemoveRange(episodesToDelete);
DbContext.Episodes.AddRange(episodesToInsert);
DbContext.Episodes.UpdateRange(episodesToUpdate);

Upsert(episodes.SelectMany(x => x.MediaFiles));

return Task.CompletedTask;
}

private Task Upsert(IEnumerable<MediaFile> t)
{
var inDb = DbContext.MediaFiles.ToHashSet();
var toUpsert = t.ToHashSet();
var toDelete = inDb.Except(toUpsert);
var toInsert = toUpsert.Except(inDb);
var toUpdate = inDb.Intersect(toUpsert);
DbContext.MediaFiles.RemoveRange(toDelete);
DbContext.MediaFiles.AddRange(toInsert);
DbContext.MediaFiles.UpdateRange(toUpdate);

return Task.CompletedTask;
}
}
11 changes: 11 additions & 0 deletions Web/Data/MediaFileRepository.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
using Web.Models;
using Web.Models.Helper;

namespace Web.Data;

public class MediaFileRepository : RepositoryBase<MediaFile>
{
public MediaFileRepository(DbContext dbContext) : base(dbContext)
{
}
}
4 changes: 3 additions & 1 deletion Web/Data/MovieRepository.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@ public MovieRepository(DbContext dbContext) : base(dbContext)
public override Task Upsert(IEnumerable<Movie> t)
{
var moviesInDb = DbContext.Movies.ToHashSet();
var moviesToUpsert = t.ToHashSet();
List<Movie> movies = t.ToList();
var moviesToUpsert = movies.ToHashSet();
var moviesToDelete = moviesInDb.Except(moviesToUpsert, new MovieEqualityComparer());
var moviesToInsert = moviesToUpsert.Except(moviesInDb, new MovieEqualityComparer());
var moviesToUpdate = moviesInDb.Intersect(moviesToUpsert, new MovieEqualityComparer());
Expand All @@ -21,4 +22,5 @@ public override Task Upsert(IEnumerable<Movie> t)
DbContext.Movies.UpdateRange(moviesToUpdate);
return Task.CompletedTask;
}

}
2 changes: 1 addition & 1 deletion Web/Data/ServerRepository.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ public ServerRepository(DbContext dbContext) : base(dbContext)
{
}

public override async Task<Server> GetById(string id)
public override async Task<Server> GetById(string id)
{
return DbContext.Servers.Include(x => x.Connections).AsNoTracking().FirstOrDefault(x => x.Id == id);
}
Expand Down
31 changes: 29 additions & 2 deletions Web/Data/TvShowRepository.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
using Web.Models;
using System.Linq.Expressions;
using Microsoft.EntityFrameworkCore;
using Web.Models;

namespace Web.Data;

Expand All @@ -7,5 +9,30 @@ public class TvShowRepository : RepositoryBase<TvShow>
public TvShowRepository(DbContext dbContext) : base(dbContext)
{
}


public override IEnumerable<TvShow> Get(
Expression<Func<TvShow, bool>>? filter = null,
Func<IQueryable<TvShow>, IOrderedQueryable<TvShow>>? orderBy = null,
string includeProperties = "")
{
IQueryable<TvShow> query = DbContext.Set<TvShow>();

if (filter != null)
{
query = query.Where(filter);
}

query = query.Include(x => x.Episodes)
.ThenInclude(e => e.MediaFiles);


if (orderBy != null)
{
return orderBy(query).ToList();
}
else
{
return query.ToList();
}
}
}
Loading

0 comments on commit aac0329

Please sign in to comment.