Deb setup
This commit is contained in:
parent
b3ae68232d
commit
fe83c39604
|
@ -484,3 +484,4 @@ $RECYCLE.BIN/
|
|||
*.swp
|
||||
help.txt
|
||||
data/
|
||||
out/
|
|
@ -0,0 +1,8 @@
|
|||
steps:
|
||||
- name: devel
|
||||
image: mcr.microsoft.com/dotnet/sdk:8.0
|
||||
when:
|
||||
event: push
|
||||
branch: devel
|
||||
commands:
|
||||
- bash test.sh
|
|
@ -0,0 +1,11 @@
|
|||
steps:
|
||||
- name: package
|
||||
image: dotnet-android-docker:latest
|
||||
when:
|
||||
event: tag
|
||||
branch: master
|
||||
commands:
|
||||
- bash package.sh
|
||||
volumes:
|
||||
- /mnt/20TB/Artifacts:/deploy_dir
|
||||
- /mnt/20TB/DebServer/pool:/pool
|
|
@ -0,0 +1,4 @@
|
|||
TessesCMS
|
||||
=========
|
||||
|
||||
> :warning: **THIS IS UNDER CONSTRUCTION** don't use this software yet
|
|
@ -2,13 +2,61 @@ using System;
|
|||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Android.Media;
|
||||
using Avalonia.Android;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Platform;
|
||||
using Avalonia.Platform.Storage;
|
||||
using LibVLCSharp.Platforms.Android;
|
||||
using Newtonsoft.Json;
|
||||
using Tesses.VirtualFilesystem;
|
||||
using Tesses.VirtualFilesystem.Extensions;
|
||||
using Tesses.VirtualFilesystem.Filesystems;
|
||||
namespace Tesses.CMS.Avalonia.Android;
|
||||
public class NCH : NativeControlHost
|
||||
{
|
||||
LibVLCSharp.Shared.MediaPlayer? mediaPlayer;
|
||||
VideoView? view;
|
||||
protected override IPlatformHandle CreateNativeControlCore(IPlatformHandle parent)
|
||||
{
|
||||
/*return Implementation?.CreateControl(IsSecond, parent, () => base.CreateNativeControlCore(parent))
|
||||
?? base.CreateNativeControlCore(parent);*/
|
||||
|
||||
var parentContext = (parent as AndroidViewControlHandle)?.View.Context
|
||||
?? global::Android.App.Application.Context;
|
||||
|
||||
view = new VideoView(parentContext);
|
||||
view.LayoutParameters = new global::Android.Views.ViewGroup.LayoutParams(global::Android.Views.ViewGroup.LayoutParams.MatchParent,global::Android.Views.ViewGroup.LayoutParams.MatchParent);
|
||||
if(mediaPlayer != null)
|
||||
{
|
||||
view.MediaPlayer = mediaPlayer;
|
||||
}
|
||||
return new AndroidViewControlHandle(view);
|
||||
}
|
||||
|
||||
protected override void DestroyNativeControlCore(IPlatformHandle control)
|
||||
{
|
||||
base.DestroyNativeControlCore(control);
|
||||
}
|
||||
|
||||
public LibVLCSharp.Shared.MediaPlayer? MediaPlayer {
|
||||
get {
|
||||
if(view != null)
|
||||
{
|
||||
return view.MediaPlayer;
|
||||
}
|
||||
return mediaPlayer;
|
||||
}
|
||||
set {
|
||||
if(view != null)
|
||||
{
|
||||
view.MediaPlayer = value;
|
||||
|
||||
}
|
||||
mediaPlayer=value;
|
||||
}
|
||||
}
|
||||
}
|
||||
internal class MobilePlatform : IPlatform
|
||||
{
|
||||
string configpath;
|
||||
|
@ -40,6 +88,11 @@ internal class MobilePlatform : IPlatform
|
|||
internal IVirtualFilesystem? virtualFilesystem;
|
||||
public IVirtualFilesystem? DownloadFilesystem => virtualFilesystem;
|
||||
|
||||
public bool CanTakeScreenShots => DownloadFilesystem != null && !string.IsNullOrWhiteSpace(ScreenshotPath);
|
||||
|
||||
public string ScreenshotPath => activity?.GetExternalCacheDirs()?[0].AbsolutePath ?? "";
|
||||
|
||||
public bool MustMoveScreenshot => true;
|
||||
|
||||
public async Task BrowseForDownloadDirectoryAsync()
|
||||
{
|
||||
|
@ -89,4 +142,31 @@ internal class MobilePlatform : IPlatform
|
|||
|
||||
|
||||
}
|
||||
|
||||
public Control CreatePlayer()
|
||||
{
|
||||
return new NCH();
|
||||
// return new global::Android.Widget.Button(this.activity);
|
||||
}
|
||||
|
||||
public void SetMediaPlayer(Control control, global::LibVLCSharp.Shared.MediaPlayer? mediaPlayer)
|
||||
{
|
||||
var ctrl=control as NCH;
|
||||
if(ctrl != null)
|
||||
ctrl.MediaPlayer = mediaPlayer;
|
||||
}
|
||||
|
||||
public global::LibVLCSharp.Shared.MediaPlayer? GetMediaPlayer(Control control)
|
||||
{
|
||||
var ctrl=control as NCH;
|
||||
if(ctrl != null)
|
||||
return ctrl.MediaPlayer;
|
||||
return null;
|
||||
}
|
||||
public void LaunchUrl(string url)
|
||||
{
|
||||
//https://stackoverflow.com/a/3004542
|
||||
//Intent intent = new Intent(Intent.ActionView);
|
||||
|
||||
}
|
||||
}
|
|
@ -19,7 +19,9 @@
|
|||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Avalonia.Android" Version="$(AvaloniaVersion)" />
|
||||
<PackageReference Include="LibVlcSharp" Version="3.8.5" />
|
||||
<PackageReference Include="MimeTypesMap" Version="1.0.8" />
|
||||
<PackageReference Include="VideoLAN.LibVLC.Android" Version="3.5.3" />
|
||||
<PackageReference Include="Xamarin.AndroidX.Core.SplashScreen" Version="1.0.1.1" />
|
||||
</ItemGroup>
|
||||
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
|
@ -35,6 +37,11 @@ internal class DesktopPlatform : IPlatform
|
|||
IVirtualFilesystem? virtualFilesystem;
|
||||
public IVirtualFilesystem? DownloadFilesystem => virtualFilesystem;
|
||||
|
||||
public bool CanTakeScreenShots => true;
|
||||
|
||||
public string ScreenshotPath => DownloadFilesystem == null ? Environment.GetFolderPath(Environment.SpecialFolder.MyPictures) : Path.Combine(Configuration.DownloadPath,"Screenshots");
|
||||
|
||||
public bool MustMoveScreenshot => false;
|
||||
|
||||
public async Task BrowseForDownloadDirectoryAsync()
|
||||
{
|
||||
|
@ -75,21 +82,28 @@ internal class DesktopPlatform : IPlatform
|
|||
|
||||
public Control CreatePlayer()
|
||||
{
|
||||
return new VideoPlayer();
|
||||
return new VideoView();
|
||||
}
|
||||
|
||||
public void SetMediaPlayer(Control control, MediaPlayer? mediaPlayer)
|
||||
{
|
||||
var ctrl = control as VideoPlayer;
|
||||
var ctrl = control as VideoView;
|
||||
if(ctrl != null)
|
||||
ctrl.MediaPlayer = mediaPlayer;
|
||||
}
|
||||
|
||||
public MediaPlayer? GetMediaPlayer(Control control)
|
||||
{
|
||||
var ctrl = control as VideoPlayer;
|
||||
var ctrl = control as VideoView;
|
||||
if(ctrl != null)
|
||||
return ctrl.MediaPlayer;
|
||||
return null;
|
||||
}
|
||||
public void LaunchUrl(string url)
|
||||
{
|
||||
Process p = new Process();
|
||||
p.StartInfo.UseShellExecute=true;
|
||||
p.StartInfo.FileName = url;
|
||||
p.Start();
|
||||
}
|
||||
}
|
|
@ -6,12 +6,24 @@
|
|||
<TargetFramework>net8.0</TargetFramework>
|
||||
<Nullable>enable</Nullable>
|
||||
<BuiltInComInteropSupport>true</BuiltInComInteropSupport>
|
||||
<AssemblyName>tcms-desktop</AssemblyName>
|
||||
<HomePage>https://tesses.net/apps/TessesCMS</HomePage>
|
||||
<Maintainer>Mike Nolan <tesses@tesses.net></Maintainer>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup>
|
||||
<ApplicationManifest>app.manifest</ApplicationManifest>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<Content Include="tcms-desktop.desktop" CopyToPublishDirectory="PreserveNewest" LinuxFileMode="1755">
|
||||
<LinuxPath>/usr/share/applications/tcms-desktop.desktop</LinuxPath>
|
||||
</Content>
|
||||
|
||||
<Content Include="icon.png" CopyToPublishDirectory="PreserveNewest" LinuxFileMode="1755">
|
||||
<LinuxPath>/usr/share/icons/tcms.png</LinuxPath>
|
||||
</Content>
|
||||
<DebDependency Include="libvlc-dev (>= 2.2.2)" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Avalonia.Desktop" Version="$(AvaloniaVersion)" />
|
||||
<!--Condition below is needed to remove Avalonia.Diagnostics package from build output in Release configuration.-->
|
||||
|
@ -20,6 +32,10 @@
|
|||
<PackageReference Include="Tesses.VirtualFilesystem" Version="1.0.2" />
|
||||
<PackageReference Include="Tesses.VirtualFilesystem.Base" Version="1.0.2" />
|
||||
<PackageReference Include="Tesses.Virtualfilesystem.Local" Version="1.0.1" />
|
||||
<PackageReference Include="Packaging.Targets">
|
||||
<Version>0.1.226-*</Version>
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
</PackageReference>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
|
Before Width: | Height: | Size: 51 KiB After Width: | Height: | Size: 51 KiB |
|
@ -0,0 +1,9 @@
|
|||
[Desktop Entry]
|
||||
Type=Application
|
||||
Version=1.0
|
||||
Name=TessesCMS
|
||||
Comment=The app for Tesses Studios
|
||||
Exec=/usr/local/bin/tcms-desktop %U
|
||||
Categories=AudioVideo;Player;
|
||||
Icon=tcms
|
||||
MimeType=application/ogg;application/x-ogg;audio/ogg;audio/vorbis;audio/x-vorbis;audio/x-vorbis+ogg;video/ogg;video/x-ogm;video/x-ogm+ogg;video/x-theora+ogg;video/x-theora;audio/x-speex;audio/opus;application/x-flac;audio/flac;audio/x-flac;audio/x-ms-asf;audio/x-ms-asx;audio/x-ms-wax;audio/x-ms-wma;video/x-ms-asf;video/x-ms-asf-plugin;video/x-ms-asx;video/x-ms-wm;video/x-ms-wmv;video/x-ms-wmx;video/x-ms-wvx;video/x-msvideo;audio/x-pn-windows-acm;video/divx;video/msvideo;video/vnd.divx;video/avi;video/x-avi;application/vnd.rn-realmedia;application/vnd.rn-realmedia-vbr;audio/vnd.rn-realaudio;audio/x-pn-realaudio;audio/x-pn-realaudio-plugin;audio/x-real-audio;audio/x-realaudio;video/vnd.rn-realvideo;audio/mpeg;audio/mpg;audio/mp1;audio/mp2;audio/mp3;audio/x-mp1;audio/x-mp2;audio/x-mp3;audio/x-mpeg;audio/x-mpg;video/mp2t;video/mpeg;video/mpeg-system;video/x-mpeg;video/x-mpeg2;video/x-mpeg-system;application/mpeg4-iod;application/mpeg4-muxcodetable;application/x-extension-m4a;application/x-extension-mp4;audio/aac;audio/m4a;audio/mp4;audio/x-m4a;audio/x-aac;video/mp4;video/mp4v-es;video/x-m4v;application/x-quicktime-media-link;application/x-quicktimeplayer;video/quicktime;application/x-matroska;audio/x-matroska;video/x-matroska;video/webm;audio/webm;audio/3gpp;audio/3gpp2;audio/AMR;audio/AMR-WB;video/3gp;video/3gpp;video/3gpp2;x-scheme-handler/mms;x-scheme-handler/mmsh;x-scheme-handler/rtsp;x-scheme-handler/rtp;x-scheme-handler/rtmp;x-scheme-handler/icy;x-scheme-handler/icyx;application/x-cd-image;x-content/video-vcd;x-content/video-svcd;x-content/video-dvd;x-content/audio-cdda;x-content/audio-player;application/ram;application/xspf+xml;audio/mpegurl;audio/x-mpegurl;audio/scpls;audio/x-scpls;text/google-video-pointer;text/x-google-video-pointer;video/vnd.mpegurl;application/vnd.apple.mpegurl;application/vnd.ms-asf;application/vnd.ms-wpl;application/sdp;audio/dv;video/dv;audio/x-aiff;audio/x-pn-aiff;video/x-anim;video/x-nsv;video/fli;video/flv;video/x-flc;video/x-fli;video/x-flv;audio/wav;audio/x-pn-au;audio/x-pn-wav;audio/x-wav;audio/x-adpcm;audio/ac3;audio/eac3;audio/vnd.dts;audio/vnd.dts.hd;audio/vnd.dolby.heaac.1;audio/vnd.dolby.heaac.2;audio/vnd.dolby.mlp;audio/basic;audio/midi;audio/x-ape;audio/x-gsm;audio/x-musepack;audio/x-tta;audio/x-wavpack;audio/x-shorten;application/x-shockwave-flash;application/x-flash-video;misc/ultravox;image/vnd.rn-realpix;audio/x-it;audio/x-mod;audio/x-s3m;audio/x-xm;application/mxf;
|
|
@ -15,22 +15,44 @@ using Avalonia.Platform;
|
|||
using Tesses.VirtualFilesystem;
|
||||
using Tesses.VirtualFilesystem.Extensions;
|
||||
using System.IO;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using Tesses.CMS.Avalonia.Models;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
namespace Tesses.CMS.Avalonia;
|
||||
|
||||
public partial class App : Application
|
||||
{
|
||||
public static OpenedCtrls OpenedControls {get;}=new OpenedCtrls();
|
||||
public static IPlatform Platform {get;set;} = new NullPlatform();
|
||||
|
||||
public static MainWindow? Window {get;set;}
|
||||
|
||||
public static TessesCMSClient Client {get;} = new TessesCMSClient();
|
||||
|
||||
public static MainView? MainView {get;set;}
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
|
||||
AvaloniaXamlLoader.Load(this);
|
||||
}
|
||||
|
||||
public static List<ActiveDownload> Downloads {get;}=new List<ActiveDownload>();
|
||||
|
||||
public static void AddActiveDownload(ActiveDownload dl)
|
||||
{
|
||||
lock(Downloads)
|
||||
{
|
||||
if(!Downloads.Any(e=>e.Path == dl.Path))
|
||||
{
|
||||
Downloads.Add(dl);
|
||||
dl.Start().Wait(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public override void OnFrameworkInitializationCompleted()
|
||||
{
|
||||
Client.RootUrl = Platform.Configuration.ServerUrl;
|
||||
|
@ -46,11 +68,11 @@ public partial class App : Application
|
|||
}
|
||||
else if (ApplicationLifetime is ISingleViewApplicationLifetime singleViewPlatform)
|
||||
{
|
||||
|
||||
singleViewPlatform.MainView = new MainView
|
||||
MainView = new MainView
|
||||
{
|
||||
DataContext = new MainViewModel(title)
|
||||
};
|
||||
singleViewPlatform.MainView = MainView;
|
||||
}
|
||||
|
||||
base.OnFrameworkInitializationCompleted();
|
||||
|
@ -139,6 +161,75 @@ public partial class App : Application
|
|||
return new Bitmap(AssetLoader.Open(new Uri("avares://Tesses.CMS.Avalonia/Assets/no-media-icon.png")));
|
||||
}
|
||||
|
||||
internal static async Task<IImage> GetShowThumbnailAsync(string username, string name)
|
||||
{
|
||||
//we need to cache the resource
|
||||
if(Platform.Configuration.CacheResources && Platform.DownloadFilesystem != null)
|
||||
{
|
||||
UnixPath cacheDir = Special.Root / "Metadata" / "Cache" / username / "Shows" / name;
|
||||
Log($"About to create directory: {cacheDir}");
|
||||
|
||||
await Platform.DownloadFilesystem.CreateDirectoryAsync(cacheDir);
|
||||
Log($"Created directory: {cacheDir}");
|
||||
UnixPath thumbnailPath = cacheDir / "thumbnail.jpg";
|
||||
|
||||
if(await Platform.DownloadFilesystem.FileExistsAsync(thumbnailPath))
|
||||
{
|
||||
Log($"Image exists: {thumbnailPath}");
|
||||
using(var sr = await Platform.DownloadFilesystem.OpenReadAsync(thumbnailPath))
|
||||
{
|
||||
MemoryStream ms = new MemoryStream();
|
||||
sr.CopyTo(ms);
|
||||
ms.Position=0;
|
||||
Log($"Image read from file: {thumbnailPath}");
|
||||
return new Bitmap(ms);
|
||||
}
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
var metadata = await Client.Shows.GetShowContentMetadataAsync(username,name);
|
||||
if(metadata.HasThumbnail)
|
||||
{
|
||||
using(var strm = await Platform.DownloadFilesystem.OpenAsync(thumbnailPath,System.IO.FileMode.Create,System.IO.FileAccess.Write,System.IO.FileShare.None))
|
||||
{
|
||||
MemoryStream ms=new MemoryStream();
|
||||
Log($"About to read from network and save: {thumbnailPath}");
|
||||
|
||||
await Client.Shows.DownloadThumbnailAsync(username,name,ms);
|
||||
ms.Position=0;
|
||||
ms.CopyTo(strm);
|
||||
ms.Position=0;
|
||||
Log($"Image read from network and saved: {thumbnailPath}");
|
||||
return new Bitmap(ms);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
var metadata = await Client.Shows.GetShowContentMetadataAsync(username,name);
|
||||
if(metadata.HasThumbnail)
|
||||
{
|
||||
MemoryStream ms=new MemoryStream();
|
||||
Log($"About to read from network: {username} {name}");
|
||||
|
||||
await Client.Shows.DownloadThumbnailAsync(username,name,ms);
|
||||
ms.Position = 0;
|
||||
Log($"Image read from network: {username} {name}");
|
||||
|
||||
return new Bitmap(ms);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
Log($"Image does not exist: {username} {name}");
|
||||
|
||||
return new Bitmap(AssetLoader.Open(new Uri("avares://Tesses.CMS.Avalonia/Assets/no-media-icon.png")));
|
||||
}
|
||||
|
||||
|
||||
public static void Log(string text)
|
||||
{
|
||||
lock(App.Platform)
|
||||
|
@ -157,4 +248,217 @@ public partial class App : Application
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal static void StartDownload(UserAccount account, Movie movie)
|
||||
{
|
||||
string username = account.Username;
|
||||
|
||||
FavoritesItem item =new FavoritesItem()
|
||||
{
|
||||
Username = account.Username,
|
||||
UserProperName = account.ProperName,
|
||||
Account = account,
|
||||
Media = JObject.FromObject(movie),
|
||||
MediaName = movie.Name,
|
||||
MediaProperName = movie.ProperName,
|
||||
Type = FavoriteType.Movie
|
||||
};
|
||||
|
||||
App.AddDownload(item);
|
||||
|
||||
|
||||
UnixPath dir = Special.Root / "Downloads" / username / "Movies" / movie.Name;
|
||||
UnixPath incomplete = dir / movie.ProperName + ".mp4.part";
|
||||
UnixPath filename = dir / movie.ProperName + ".mp4";
|
||||
Platform.DownloadFilesystem?.CreateDirectory(dir);
|
||||
|
||||
if(Platform.DownloadFilesystem != null)
|
||||
{
|
||||
ActiveDownload download = new ActiveDownload();
|
||||
download.Name = movie.ProperName;
|
||||
download.Path = filename.Path;
|
||||
download.Start = async()=>{
|
||||
|
||||
if(!await Platform.DownloadFilesystem.FileExistsAsync(filename))
|
||||
{
|
||||
|
||||
DateTime last = DateTime.Now;
|
||||
|
||||
using(var strm = await Platform.DownloadFilesystem.OpenAsync(incomplete,FileMode.OpenOrCreate,FileAccess.Write,FileShare.None,download.TokenSource.Token))
|
||||
{
|
||||
await Client.Movies.DownloadMovieAsync(username,movie.Name,strm,download.TokenSource.Token,new Progress<double>(e=>{
|
||||
|
||||
var now = DateTime.Now;
|
||||
|
||||
if(last.AddSeconds(0.5) < now)
|
||||
{
|
||||
|
||||
download.UpdateProgress(e);
|
||||
last = now;
|
||||
}
|
||||
|
||||
|
||||
}));
|
||||
|
||||
}
|
||||
if(!download.TokenSource.IsCancellationRequested)
|
||||
await Platform.DownloadFilesystem.MoveFileAsync(incomplete,filename);
|
||||
RemoveActiveDownload(download);
|
||||
}
|
||||
};
|
||||
AddActiveDownload(download);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
internal static void AddDownload(FavoritesItem item)
|
||||
{
|
||||
if(App.Platform.DownloadFilesystem == null) return;
|
||||
List<FavoritesItem> items = new List<FavoritesItem>();
|
||||
|
||||
foreach(var _item in EnumerateDownloads())
|
||||
if(_item.Equals(item)) return;
|
||||
else items.Add(_item);
|
||||
|
||||
items.Add(item);
|
||||
|
||||
App.Platform.DownloadFilesystem.CreateDirectory(Special.Root / "Metadata");
|
||||
App.Platform.DownloadFilesystem.WriteAllText(Special.Root / "Metadata" / "Downloads.json",JsonConvert.SerializeObject(items));
|
||||
|
||||
}
|
||||
|
||||
private static void RemoveActiveDownload(ActiveDownload download)
|
||||
{
|
||||
lock(Downloads)
|
||||
{
|
||||
Downloads.Remove(download);
|
||||
download.SendDone();
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
internal static List<ActiveDownload> GetActiveDownloads()
|
||||
{
|
||||
lock(Downloads) {
|
||||
return Downloads.ToList();
|
||||
}
|
||||
}
|
||||
|
||||
internal static IEnumerable<FavoritesItem> EnumerateFavorites()
|
||||
{
|
||||
if(App.Platform.DownloadFilesystem == null) yield break;
|
||||
|
||||
if(!App.Platform.DownloadFilesystem.FileExists(Special.Root / "Metadata" / "Favorites.json")) yield break;
|
||||
|
||||
App.Platform.DownloadFilesystem.CreateDirectory(Special.Root / "Metadata");
|
||||
|
||||
string text = App.Platform.DownloadFilesystem.ReadAllText(Special.Root / "Metadata" / "Favorites.json");
|
||||
|
||||
var res=JsonConvert.DeserializeObject<List<FavoritesItem>>(text);
|
||||
if(res != null)
|
||||
foreach(var item in res)
|
||||
yield return item;
|
||||
}
|
||||
internal static IEnumerable<FavoritesItem> EnumerateDownloads()
|
||||
{
|
||||
if(App.Platform.DownloadFilesystem == null) yield break;
|
||||
|
||||
if(!App.Platform.DownloadFilesystem.FileExists(Special.Root / "Metadata" / "Downloads.json")) yield break;
|
||||
|
||||
App.Platform.DownloadFilesystem.CreateDirectory(Special.Root / "Metadata");
|
||||
|
||||
string text = App.Platform.DownloadFilesystem.ReadAllText(Special.Root / "Metadata" / "Downloads.json");
|
||||
|
||||
var res=JsonConvert.DeserializeObject<List<FavoritesItem>>(text);
|
||||
if(res != null)
|
||||
foreach(var item in res)
|
||||
yield return item;
|
||||
}
|
||||
internal static bool FavoritesExists(FavoritesItem item)
|
||||
{
|
||||
foreach(var _item in EnumerateFavorites())
|
||||
if(_item.Equals(item)) return true;
|
||||
return false;
|
||||
}
|
||||
internal static void AddFavorite(FavoritesItem item)
|
||||
{
|
||||
if(App.Platform.DownloadFilesystem == null) return;
|
||||
List<FavoritesItem> items = new List<FavoritesItem>();
|
||||
|
||||
foreach(var _item in EnumerateFavorites())
|
||||
if(_item.Equals(item)) return;
|
||||
else items.Add(_item);
|
||||
|
||||
items.Add(item);
|
||||
|
||||
App.Platform.DownloadFilesystem.CreateDirectory(Special.Root / "Metadata");
|
||||
App.Platform.DownloadFilesystem.WriteAllText(Special.Root / "Metadata" / "Favorites.json",JsonConvert.SerializeObject(items));
|
||||
}
|
||||
|
||||
internal static void RemoveFavorite(FavoritesItem item)
|
||||
{
|
||||
if(App.Platform.DownloadFilesystem == null) return;
|
||||
List<FavoritesItem> items = new List<FavoritesItem>();
|
||||
|
||||
foreach(var _item in EnumerateFavorites())
|
||||
if(_item.Equals(item)) continue;
|
||||
else items.Add(_item);
|
||||
|
||||
App.Platform.DownloadFilesystem.CreateDirectory(Special.Root / "Metadata");
|
||||
App.Platform.DownloadFilesystem.WriteAllText(Special.Root / "Metadata" / "Favorites.json",JsonConvert.SerializeObject(items));
|
||||
|
||||
}
|
||||
|
||||
internal static void LaunchUrl(string v)
|
||||
{
|
||||
Platform.LaunchUrl(v);
|
||||
}
|
||||
}
|
||||
|
||||
public class ActiveDownload
|
||||
{
|
||||
public CancellationTokenSource TokenSource {get;set;}=new CancellationTokenSource();
|
||||
|
||||
public string Path {get;set;}="";
|
||||
|
||||
|
||||
public string Name {get;set;}="";
|
||||
|
||||
public double Progress {get;set;}=0;
|
||||
|
||||
public event Action<double>? ProgressChanged=null;
|
||||
|
||||
public event Action? Done = null;
|
||||
|
||||
public void UpdateProgress(double d)
|
||||
{
|
||||
|
||||
Progress = d;
|
||||
ProgressChanged?.Invoke(d);
|
||||
}
|
||||
|
||||
public void SendDone()
|
||||
{
|
||||
Done?.Invoke();
|
||||
}
|
||||
|
||||
|
||||
public Func<Task> Start {get;set;}=async()=>{await Task.CompletedTask;};
|
||||
}
|
||||
|
||||
public class OpenedCtrls
|
||||
{
|
||||
List<IDisposable> disposables=new List<IDisposable>();
|
||||
|
||||
public void Register(IDisposable disposable)
|
||||
{
|
||||
disposables.Add(disposable);
|
||||
}
|
||||
|
||||
public void Destroy()
|
||||
{
|
||||
foreach(var item in disposables) item.Dispose();
|
||||
|
||||
disposables.Clear();
|
||||
}
|
||||
}
|
Binary file not shown.
Before Width: | Height: | Size: 172 KiB After Width: | Height: | Size: 318 B |
|
@ -11,6 +11,9 @@ public interface IPlatform
|
|||
bool PlatformUsesNormalPathsForDownload {get;}
|
||||
|
||||
IVirtualFilesystem? DownloadFilesystem {get;}
|
||||
bool CanTakeScreenShots { get; }
|
||||
string ScreenshotPath { get; }
|
||||
bool MustMoveScreenshot { get; }
|
||||
|
||||
Task WriteConfigurationAsync();
|
||||
|
||||
|
@ -22,6 +25,8 @@ public interface IPlatform
|
|||
|
||||
MediaPlayer? GetMediaPlayer(Control control);
|
||||
|
||||
void LaunchUrl(string url);
|
||||
|
||||
}
|
||||
|
||||
public class NullPlatform : IPlatform
|
||||
|
@ -38,6 +43,12 @@ public class NullPlatform : IPlatform
|
|||
|
||||
public Configuration Configuration => _conf;
|
||||
|
||||
public bool CanTakeScreenShots => false;
|
||||
|
||||
public string ScreenshotPath => throw new System.NotImplementedException();
|
||||
|
||||
public bool MustMoveScreenshot => throw new System.NotImplementedException();
|
||||
|
||||
public async Task BrowseForDownloadDirectoryAsync()
|
||||
{
|
||||
await Task.CompletedTask;
|
||||
|
@ -67,4 +78,9 @@ public class NullPlatform : IPlatform
|
|||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
public void LaunchUrl(string url)
|
||||
{
|
||||
|
||||
}
|
||||
}
|
|
@ -0,0 +1,28 @@
|
|||
using Avalonia.Media;
|
||||
using Tesses.CMS.Client;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Converters;
|
||||
using Newtonsoft.Json.Serialization;
|
||||
using System;
|
||||
using ReactiveUI;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Tesses.CMS.Avalonia.Models;
|
||||
|
||||
public class FavoritesPageItem
|
||||
{
|
||||
FavoritesItem item;
|
||||
|
||||
public FavoritesPageItem(FavoritesItem item, Func<FavoritesItem,Task> fav)
|
||||
{
|
||||
this.item=item;
|
||||
ViewCommand = ReactiveCommand.CreateFromTask(async()=>{
|
||||
await fav(item);
|
||||
});
|
||||
}
|
||||
public string Name => item.MediaProperName;
|
||||
|
||||
public string AuthorName => item.UserProperName;
|
||||
|
||||
public IReactiveCommand ViewCommand {get;}
|
||||
}
|
|
@ -0,0 +1,54 @@
|
|||
using Avalonia.Media;
|
||||
using Tesses.CMS.Client;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Converters;
|
||||
using Newtonsoft.Json.Serialization;
|
||||
using Newtonsoft.Json.Linq;
|
||||
|
||||
namespace Tesses.CMS.Avalonia.Models;
|
||||
|
||||
public enum FavoriteType {
|
||||
Movie,
|
||||
Show,
|
||||
Season,
|
||||
Episode,
|
||||
Album,
|
||||
AlbumTrack,
|
||||
MusicVideo,
|
||||
Program,
|
||||
ProgramRelease,
|
||||
Other,
|
||||
OtherFile
|
||||
|
||||
}
|
||||
|
||||
public class FavoritesItem
|
||||
{
|
||||
[JsonProperty("username")]
|
||||
public string Username {get;set;}="";
|
||||
|
||||
[JsonProperty("type")]
|
||||
[JsonConverter(typeof(StringEnumConverter),typeof(CamelCaseNamingStrategy))]
|
||||
public FavoriteType Type {get;set;}=FavoriteType.Movie;
|
||||
|
||||
[JsonProperty("userProperName")]
|
||||
public string UserProperName {get;set;}="";
|
||||
|
||||
[JsonProperty("mediaName")]
|
||||
public string MediaName {get;set;}="";
|
||||
|
||||
[JsonProperty("mediaProperName")]
|
||||
public string MediaProperName {get;set;}="";
|
||||
|
||||
[JsonProperty("account")]
|
||||
public UserAccount Account {get;set;}=new UserAccount();
|
||||
|
||||
[JsonProperty("media")]
|
||||
public JToken Media {get;set;}=JValue.CreateNull();
|
||||
|
||||
public bool Equals(FavoritesItem item)
|
||||
{
|
||||
return Username == item.Username && Type == item.Type && MediaName == item.MediaName;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,16 @@
|
|||
using Avalonia.Media;
|
||||
using Tesses.CMS.Client;
|
||||
|
||||
namespace Tesses.CMS.Avalonia.Models;
|
||||
|
||||
public class ShowItem
|
||||
{
|
||||
public ShowItem(Show show,IImage image)
|
||||
{
|
||||
Show = show;
|
||||
Image = image;
|
||||
}
|
||||
public Show Show {get;}
|
||||
public IImage Image {get;}
|
||||
public string Name => Show.ProperName;
|
||||
}
|
|
@ -0,0 +1,57 @@
|
|||
namespace Tesses.CMS.Avalonia.ViewModels;
|
||||
|
||||
using System;
|
||||
using System.Collections.ObjectModel;
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using CommunityToolkit.Mvvm.Input;
|
||||
using CommunityToolkit.Mvvm.Messaging;
|
||||
using global::Avalonia.Threading;
|
||||
using LibVLCSharp.Shared;
|
||||
|
||||
public partial class ActiveDownloadViewModel : ViewModelBase, IDisposable
|
||||
{
|
||||
ActiveDownload download;
|
||||
Action remove;
|
||||
public ActiveDownloadViewModel(Action remove,ActiveDownload download)
|
||||
{
|
||||
this.remove = remove;
|
||||
this.download=download;
|
||||
this.download.ProgressChanged += ProgressChanged;
|
||||
this.download.Done += Done;
|
||||
Progress = (int)(this.download.Progress* 100); //for progress reasons
|
||||
Name = download.Name;
|
||||
}
|
||||
|
||||
private void ProgressChanged(double obj)
|
||||
{
|
||||
|
||||
Dispatcher.UIThread.Post(()=>{
|
||||
Progress = (int)(obj * 100);
|
||||
});
|
||||
}
|
||||
|
||||
[ObservableProperty]
|
||||
private int _progress=0;
|
||||
|
||||
[ObservableProperty]
|
||||
private string _name;
|
||||
|
||||
[RelayCommand]
|
||||
public void Cancel()
|
||||
{
|
||||
download.TokenSource.Cancel();
|
||||
download.TokenSource.Dispose();
|
||||
}
|
||||
public void Dispose()
|
||||
{
|
||||
this.download.ProgressChanged -= ProgressChanged;
|
||||
this.download.Done -= Done;
|
||||
}
|
||||
|
||||
private void Done()
|
||||
{
|
||||
Dispatcher.UIThread.Invoke(()=>{
|
||||
remove();
|
||||
});
|
||||
}
|
||||
}
|
|
@ -1,19 +1,55 @@
|
|||
namespace Tesses.CMS.Avalonia.ViewModels;
|
||||
|
||||
using System;
|
||||
using System.Collections.ObjectModel;
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using CommunityToolkit.Mvvm.Input;
|
||||
using CommunityToolkit.Mvvm.Messaging;
|
||||
using LibVLCSharp.Shared;
|
||||
|
||||
public partial class DownloadsPageViewModel : ViewModelBase
|
||||
public partial class DownloadsPageViewModel : ViewModelBase, IDisposable
|
||||
{
|
||||
public DownloadsPageViewModel()
|
||||
public DownloadsPageViewModel(MainViewModel mvm)
|
||||
{
|
||||
LibVLC vlc=new LibVLC("--input-repeat=65535");
|
||||
|
||||
Player=new MediaPlayer(new Media(vlc,"https://tytdarchive.site.tesses.net/content/PreMuxed/PzUKeGZiEl0.mp4",FromType.FromLocation));
|
||||
|
||||
|
||||
foreach(var item in App.EnumerateDownloads())
|
||||
{
|
||||
ViewModelBaseItem vmbi=new ViewModelBaseItem();
|
||||
vmbi.Item = new SavedDownloadViewModel(item,mvm);
|
||||
Downloads.Add(vmbi);
|
||||
}
|
||||
foreach(var item in App.GetActiveDownloads())
|
||||
{
|
||||
var vmb=new ViewModelBaseItem();
|
||||
vmb.Item = new ActiveDownloadViewModel(()=>{
|
||||
if(Downloads.Contains(vmb)) Downloads.Remove(vmb);
|
||||
},item);
|
||||
Downloads.Add(vmb);
|
||||
}
|
||||
}
|
||||
[ObservableProperty]
|
||||
private MediaPlayer? _player;
|
||||
|
||||
private ObservableCollection<ViewModelBaseItem> _downloads=new ObservableCollection<ViewModelBaseItem>();
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
foreach(var item in Downloads)
|
||||
{
|
||||
item.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public class ViewModelBaseItem : IDisposable
|
||||
{
|
||||
public ViewModelBaseItem()
|
||||
{
|
||||
|
||||
}
|
||||
public ViewModelBase? Item {get;set;}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
var i = Item as IDisposable;
|
||||
i?.Dispose();
|
||||
}
|
||||
}
|
|
@ -1,8 +1,33 @@
|
|||
namespace Tesses.CMS.Avalonia.ViewModels;
|
||||
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Threading.Tasks;
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using CommunityToolkit.Mvvm.Input;
|
||||
using CommunityToolkit.Mvvm.Messaging;
|
||||
using Tesses.CMS.Avalonia.Models;
|
||||
using Tesses.CMS.Avalonia.Views;
|
||||
|
||||
public partial class FavoritesPageViewModel : ViewModelBase
|
||||
{
|
||||
MainViewModel mvm;
|
||||
public FavoritesPageViewModel(MainViewModel mvm)
|
||||
{
|
||||
this.mvm=mvm;
|
||||
Favorites.Clear();
|
||||
|
||||
foreach(var item in App.EnumerateFavorites())
|
||||
{
|
||||
Favorites.Add(new FavoritesPageItem(item,async (fpg)=>{
|
||||
HomePageViewModel hpvm=new HomePageViewModel();
|
||||
await hpvm.NavigateToAsync(item);
|
||||
mvm.SetHome();
|
||||
mvm.CurrentPage = hpvm;
|
||||
}));
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
public ObservableCollection<FavoritesPageItem> Favorites {get;}=new ObservableCollection<FavoritesPageItem>();
|
||||
}
|
|
@ -1,17 +1,38 @@
|
|||
namespace Tesses.CMS.Avalonia.ViewModels;
|
||||
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using CommunityToolkit.Mvvm.Input;
|
||||
using CommunityToolkit.Mvvm.Messaging;
|
||||
using Tesses.CMS.Avalonia.Models;
|
||||
using Tesses.CMS.Avalonia.ViewModels.HomePages;
|
||||
using Tesses.CMS.Client;
|
||||
using Tesses.VirtualFilesystem;
|
||||
using Tesses.VirtualFilesystem.Extensions;
|
||||
|
||||
public partial class HomePageViewModel : ViewModelBase
|
||||
{
|
||||
public HomePageViewModel()
|
||||
{
|
||||
_currentPage = new HomeUserListPageViewModel(this);
|
||||
_curPage = new HomeUserListPageViewModel(this);
|
||||
}
|
||||
private ViewModelBase _curPage;
|
||||
public ViewModelBase CurrentPage
|
||||
{
|
||||
get
|
||||
{
|
||||
return _curPage;
|
||||
}
|
||||
set{
|
||||
App.OpenedControls.Destroy();
|
||||
_curPage = value;
|
||||
var disp = value as IDisposable;
|
||||
if(disp != null) App.OpenedControls.Register(disp);
|
||||
OnPropertyChanged(nameof(CurrentPage));
|
||||
|
||||
}
|
||||
}
|
||||
[ObservableProperty]
|
||||
private ViewModelBase _currentPage;
|
||||
|
||||
[RelayCommand]
|
||||
private void Back()
|
||||
|
@ -26,4 +47,51 @@ public partial class HomePageViewModel : ViewModelBase
|
|||
//can't go back
|
||||
}
|
||||
}
|
||||
|
||||
public async Task NavigateToAsync(FavoritesItem item)
|
||||
{
|
||||
switch(item.Type)
|
||||
{
|
||||
case FavoriteType.Movie:
|
||||
var homeUserList = new HomeUserListPageViewModel(this);
|
||||
|
||||
|
||||
|
||||
var homeUser = new HomeUserPageViewModel(this,homeUserList,item.Account);
|
||||
|
||||
var homeMovieList = new HomeMovieListPageViewModel(this,homeUser);
|
||||
Movie movie = item.Media.ToObject<Movie>() ?? new Movie(){Name=item.MediaName,ProperName = item.MediaProperName};
|
||||
MovieItem movieItem=new MovieItem(movie,await App.GetMovieThumbnailAsync(item.Username,item.MediaName));
|
||||
|
||||
var homeMovie = new HomeMoviePageViewModel(this,homeMovieList,item.Account,movieItem);
|
||||
CurrentPage = homeMovie;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public async Task PlayDownloadedAsync(FavoritesItem item)
|
||||
{
|
||||
if(App.Platform.DownloadFilesystem == null) return;
|
||||
switch(item.Type)
|
||||
{
|
||||
case FavoriteType.Movie:
|
||||
UnixPath dir = Special.Root / "Downloads" / item.Username / "Movies" / item.MediaName;
|
||||
UnixPath filename = dir / item.MediaProperName + ".mp4";
|
||||
if(!await App.Platform.DownloadFilesystem.FileExistsAsync(filename)) return;
|
||||
|
||||
var homeUserList = new HomeUserListPageViewModel(this);
|
||||
|
||||
|
||||
|
||||
var homeUser = new HomeUserPageViewModel(this,homeUserList,item.Account);
|
||||
|
||||
var homeMovieList = new HomeMovieListPageViewModel(this,homeUser);
|
||||
Movie movie = item.Media.ToObject<Movie>() ?? new Movie(){Name=item.MediaName,ProperName = item.MediaProperName};
|
||||
MovieItem movieItem=new MovieItem(movie,await App.GetMovieThumbnailAsync(item.Username,item.MediaName));
|
||||
|
||||
var homeMovie = new HomeMoviePageViewModel(this,homeMovieList,item.Account,movieItem);
|
||||
CurrentPage = new HomeMovieVideoPlayerViewModel(this,homeMovie,await App.Platform.DownloadFilesystem.OpenReadAsync(filename));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -6,24 +6,27 @@ using System.Threading.Tasks;
|
|||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using CommunityToolkit.Mvvm.Input;
|
||||
using CommunityToolkit.Mvvm.Messaging;
|
||||
using global::Avalonia.Controls;
|
||||
using Tesses.CMS.Avalonia.Models;
|
||||
using Tesses.CMS.Avalonia.Views.HomePages;
|
||||
using Tesses.CMS.Client;
|
||||
|
||||
public partial class HomeMovieListPageViewModel : ViewModelBase, IBackable
|
||||
{
|
||||
HomeUserPageViewModel homePage;
|
||||
public HomeMovieListPageViewModel(HomeUserPageViewModel homePage)
|
||||
HomePageViewModel homePage;
|
||||
HomeUserPageViewModel userPage;
|
||||
public HomeMovieListPageViewModel(HomePageViewModel homePage,HomeUserPageViewModel userPage)
|
||||
{
|
||||
App.Log("In HomeMovieListPageViewModel::ctor block begin");
|
||||
this.homePage = homePage;
|
||||
this.userPage = userPage;
|
||||
Task.Run(async()=>{
|
||||
App.Log("In HomeMovieListPageViewModel::ctor::async block begin");
|
||||
|
||||
await foreach(var item in App.Client.Movies.GetMoviesAsync(homePage.Username))
|
||||
await foreach(var item in App.Client.Movies.GetMoviesAsync(userPage.Username))
|
||||
{
|
||||
try{
|
||||
_movies.Add(new MovieItem(item,await App.GetMovieThumbnailAsync(homePage.Username,item.Name)));
|
||||
_movies.Add(new MovieItem(item,await App.GetMovieThumbnailAsync(userPage.Username,item.Name)));
|
||||
}catch(Exception ex)
|
||||
{
|
||||
App.Log(ex.ToString());
|
||||
|
@ -42,12 +45,15 @@ public partial class HomeMovieListPageViewModel : ViewModelBase, IBackable
|
|||
{
|
||||
if (value is null) return;
|
||||
SelectedListItem=null;
|
||||
homePage.CurrentPage = new HomeMoviePageViewModel(homePage,this,userPage.Account,value);
|
||||
//homePage.CurrentPage = new HomeUserPageViewModel(homePage,this,value);
|
||||
|
||||
|
||||
}
|
||||
|
||||
public ViewModelBase Back()
|
||||
{
|
||||
return homePage;
|
||||
return userPage;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,139 @@
|
|||
|
||||
|
||||
using System;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Threading.Tasks;
|
||||
using Avalonia.Media;
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using CommunityToolkit.Mvvm.Input;
|
||||
using CommunityToolkit.Mvvm.Messaging;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using ReactiveUI;
|
||||
using Tesses.CMS.Avalonia.Models;
|
||||
using Tesses.CMS.Avalonia.Views.HomePages;
|
||||
using Tesses.CMS.Client;
|
||||
namespace Tesses.CMS.Avalonia.ViewModels.HomePages;
|
||||
|
||||
public partial class HomeMoviePageViewModel : ViewModelBase, IBackable
|
||||
{
|
||||
HomePageViewModel home;
|
||||
|
||||
HomeMovieListPageViewModel movieList;
|
||||
|
||||
Movie movie;
|
||||
|
||||
[ObservableProperty]
|
||||
private IImage? _thumbnail=null;
|
||||
|
||||
[ObservableProperty]
|
||||
private string _movieTitle;
|
||||
|
||||
[ObservableProperty]
|
||||
private string _movieCreator;
|
||||
[ObservableProperty]
|
||||
private string _description;
|
||||
[ObservableProperty]
|
||||
private string _published;
|
||||
|
||||
[ObservableProperty]
|
||||
private string _modified;
|
||||
|
||||
string? watchUrl=null;
|
||||
|
||||
|
||||
[ObservableProperty]
|
||||
private bool _canWatch=false;
|
||||
|
||||
[ObservableProperty]
|
||||
private bool _canDownload=false;
|
||||
|
||||
[ObservableProperty]
|
||||
|
||||
private string _addOrRemoveFromFavorites="";
|
||||
private bool _favoriteExists=false;
|
||||
public bool FavoriteExists {
|
||||
get=>_favoriteExists;
|
||||
set{
|
||||
_favoriteExists = value;
|
||||
AddOrRemoveFromFavorites = value ? "Remove from Favorites" : "Add to Favorites";
|
||||
}
|
||||
}
|
||||
|
||||
string username;
|
||||
UserAccount account;
|
||||
|
||||
FavoritesItem fav;
|
||||
|
||||
public HomeMoviePageViewModel(HomePageViewModel home,HomeMovieListPageViewModel movieList,UserAccount account,MovieItem movie)
|
||||
{
|
||||
this.account = account;
|
||||
username = account.Username;
|
||||
this.home = home;
|
||||
this.movie = movie.Movie;
|
||||
this.movieList = movieList;
|
||||
|
||||
this.Thumbnail = movie.Image;
|
||||
this.MovieTitle = movie.Movie.ProperName;
|
||||
this.MovieCreator = account.ProperName;
|
||||
this.Description = movie.Movie.Description;
|
||||
this.Modified = $"Last Modified: {movie.Movie.LastUpdated.ToShortDateString()}";
|
||||
this.Published = $"Published: {movie.Movie.CreationTime.ToShortDateString()}";
|
||||
fav=new FavoritesItem()
|
||||
{
|
||||
Type = FavoriteType.Movie,
|
||||
Username = account.Username,
|
||||
UserProperName = account.ProperName,
|
||||
MediaName = movie.Movie.Name,
|
||||
MediaProperName = movie.Movie.ProperName,
|
||||
Account = account,
|
||||
Media = JObject.FromObject(movie.Movie)
|
||||
};
|
||||
FavoriteExists = App.FavoritesExists(fav);
|
||||
|
||||
Task.Run(async()=>{
|
||||
var res=await App.Client.Movies.GetMovieContentMetadataAsync(username,movie.Movie.Name);
|
||||
if(res.HasBrowserStream)
|
||||
watchUrl = res.BrowserStream;
|
||||
CanWatch = res.HasBrowserStream;
|
||||
|
||||
CanDownload = res.HasDownloadStream;
|
||||
|
||||
|
||||
}).Wait(0);
|
||||
}
|
||||
|
||||
|
||||
public ViewModelBase Back()
|
||||
{
|
||||
return movieList;
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
public void Watch()
|
||||
{
|
||||
if(!string.IsNullOrWhiteSpace(watchUrl))
|
||||
{
|
||||
home.CurrentPage = new HomeMovieVideoPlayerViewModel(home,this,watchUrl);
|
||||
}
|
||||
}
|
||||
[RelayCommand]
|
||||
public void Download()
|
||||
{
|
||||
if(CanDownload)
|
||||
App.StartDownload(account,movie);
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
public void AddToFavorites()
|
||||
{
|
||||
if(FavoriteExists)
|
||||
{
|
||||
App.RemoveFavorite(fav);
|
||||
}
|
||||
else
|
||||
{
|
||||
App.AddFavorite(fav);
|
||||
}
|
||||
FavoriteExists = !FavoriteExists;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,75 @@
|
|||
|
||||
|
||||
using System;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.IO;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Avalonia.Media;
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using CommunityToolkit.Mvvm.Input;
|
||||
using CommunityToolkit.Mvvm.Messaging;
|
||||
using LibVLCSharp.Shared;
|
||||
using Tesses.CMS.Avalonia.Models;
|
||||
using Tesses.CMS.Avalonia.Views.HomePages;
|
||||
using Tesses.CMS.Client;
|
||||
namespace Tesses.CMS.Avalonia.ViewModels.HomePages;
|
||||
|
||||
public partial class HomeMovieVideoPlayerViewModel : ViewModelBase, IBackable, IDisposable
|
||||
{
|
||||
HomePageViewModel home;
|
||||
|
||||
HomeMoviePageViewModel moviePage;
|
||||
|
||||
LibVLC vlc=new LibVLC("--input-repeat=65535");
|
||||
|
||||
public HomeMovieVideoPlayerViewModel(HomePageViewModel home,HomeMoviePageViewModel moviePage, string url)
|
||||
{
|
||||
|
||||
this.home = home;
|
||||
this.moviePage = moviePage;
|
||||
|
||||
|
||||
|
||||
|
||||
Player=new MediaPlayer(new Media(vlc,url,FromType.FromLocation));
|
||||
|
||||
Player.Play();
|
||||
}
|
||||
public HomeMovieVideoPlayerViewModel(HomePageViewModel home,HomeMoviePageViewModel moviePage, Stream strm)
|
||||
{
|
||||
this.Stream = strm;
|
||||
this.home = home;
|
||||
this.moviePage = moviePage;
|
||||
|
||||
|
||||
LibVLC vlc=new LibVLC("--input-repeat=65535");
|
||||
|
||||
Player=new MediaPlayer(new Media(vlc,new StreamMediaInput(strm)));
|
||||
|
||||
Player.Play();
|
||||
}
|
||||
[ObservableProperty]
|
||||
private MediaPlayer? _player;
|
||||
|
||||
|
||||
public ViewModelBase Back()
|
||||
{
|
||||
|
||||
return moviePage;
|
||||
}
|
||||
Stream? Stream=null;
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
|
||||
Thread thrd=new Thread(()=>{
|
||||
Player?.Stop();
|
||||
Thread.Sleep(10000);
|
||||
Player?.Dispose();
|
||||
Stream?.Dispose();
|
||||
vlc.Dispose();
|
||||
});
|
||||
thrd.Start();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,59 @@
|
|||
namespace Tesses.CMS.Avalonia.ViewModels.HomePages;
|
||||
|
||||
using System;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Threading.Tasks;
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using CommunityToolkit.Mvvm.Input;
|
||||
using CommunityToolkit.Mvvm.Messaging;
|
||||
using global::Avalonia.Controls;
|
||||
using Tesses.CMS.Avalonia.Models;
|
||||
using Tesses.CMS.Avalonia.Views.HomePages;
|
||||
using Tesses.CMS.Client;
|
||||
|
||||
public partial class HomeShowListPageViewModel : ViewModelBase, IBackable
|
||||
{
|
||||
HomePageViewModel homePage;
|
||||
HomeUserPageViewModel userPage;
|
||||
public HomeShowListPageViewModel(HomePageViewModel homePage,HomeUserPageViewModel userPage)
|
||||
{
|
||||
App.Log("In HomeShowListPageViewModel::ctor block begin");
|
||||
this.homePage = homePage;
|
||||
this.userPage = userPage;
|
||||
Task.Run(async()=>{
|
||||
App.Log("In HomeShowListPageViewModel::ctor::async block begin");
|
||||
|
||||
await foreach(var item in App.Client.Shows.GetShowsAsync(userPage.Username))
|
||||
{
|
||||
try{
|
||||
_shows.Add(new ShowItem(item,await App.GetShowThumbnailAsync(userPage.Username,item.Name)));
|
||||
}catch(Exception ex)
|
||||
{
|
||||
App.Log(ex.ToString());
|
||||
}
|
||||
}
|
||||
App.Log("In HomeShowListPageViewModel::ctor::async block end");
|
||||
}).Wait(0);
|
||||
App.Log("In HomeShowListPageViewModel::ctor block end");
|
||||
}
|
||||
|
||||
[ObservableProperty]
|
||||
private ObservableCollection<ShowItem> _shows=new ObservableCollection<ShowItem>();
|
||||
[ObservableProperty]
|
||||
private MovieItem? _selectedListItem;
|
||||
partial void OnSelectedListItemChanged(MovieItem? value)
|
||||
{
|
||||
if (value is null) return;
|
||||
SelectedListItem=null;
|
||||
//homePage.CurrentPage = new HomeMoviePageViewModel(homePage,this,userPage.Account,value);
|
||||
//homePage.CurrentPage = new HomeUserPageViewModel(homePage,this,value);
|
||||
|
||||
|
||||
}
|
||||
|
||||
public ViewModelBase Back()
|
||||
{
|
||||
return userPage;
|
||||
}
|
||||
|
||||
}
|
|
@ -34,4 +34,6 @@ public partial class HomeUserListPageViewModel : ViewModelBase
|
|||
SelectedListItem=null;
|
||||
homePage.CurrentPage = new HomeUserPageViewModel(homePage,this,value);
|
||||
}
|
||||
|
||||
|
||||
}
|
|
@ -28,6 +28,9 @@ public partial class HomeUserPageViewModel : ViewModelBase, IBackable
|
|||
|
||||
public string Username => account.Username;
|
||||
|
||||
public string ProperName => account.ProperName;
|
||||
|
||||
public UserAccount Account => account;
|
||||
[ObservableProperty]
|
||||
private ObservableCollection<UserPageItem> _userItems=new ObservableCollection<UserPageItem>();
|
||||
|
||||
|
@ -36,7 +39,8 @@ public partial class HomeUserPageViewModel : ViewModelBase, IBackable
|
|||
this.homePage = homePage;
|
||||
this.userList = userList;
|
||||
this.account = account;
|
||||
UserItems.Add(new UserPageItem("Movies",new HomeMovieListPageViewModel(this)));
|
||||
UserItems.Add(new UserPageItem("Movies",new HomeMovieListPageViewModel(homePage,this)));
|
||||
UserItems.Add(new UserPageItem("Shows",new HomeShowListPageViewModel(homePage,this)));
|
||||
}
|
||||
[ObservableProperty]
|
||||
private UserPageItem? _selectedListItem;
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
namespace Tesses.CMS.Avalonia.ViewModels;
|
||||
|
||||
using System;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Linq;
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
|
@ -11,24 +12,46 @@ public partial class MainViewModel : ViewModelBase
|
|||
{
|
||||
public MainViewModel(string title)
|
||||
{
|
||||
_pages.Add(new Page("Home",()=>new HomePageViewModel()));
|
||||
_pages.Add(new Page("Favorites",()=>new FavoritesPageViewModel(this)));
|
||||
_pages.Add(new Page("Notifications",()=>new NotificationsPageViewModel()));
|
||||
_pages.Add(new Page("Downloads",()=>new DownloadsPageViewModel(this)));
|
||||
_pages.Add(new Page("Video Player",()=>new VideoPlayerViewModel()));
|
||||
_pages.Add(new Page("Settings",()=>new SettingsPageViewModel(this)));
|
||||
SelectedListItem = Pages.First();
|
||||
this.Title = title;
|
||||
|
||||
if(Environment.GetCommandLineArgs().Length > 1)
|
||||
{
|
||||
string path = Environment.GetCommandLineArgs()[1];
|
||||
Setting = true;
|
||||
SelectedListItem = _pages.First(e=>e.Name == "Video Player");
|
||||
CurrentPage = new VideoPlayerViewModel(path);
|
||||
PaneOpen=false;
|
||||
Setting = false;
|
||||
}
|
||||
}
|
||||
[ObservableProperty]
|
||||
private bool _paneOpen=true;
|
||||
[ObservableProperty]
|
||||
private ViewModelBase _currentPage = new HomePageViewModel();
|
||||
private ViewModelBase _curPage=new HomePageViewModel();
|
||||
public ViewModelBase CurrentPage
|
||||
{
|
||||
get
|
||||
{
|
||||
return _curPage;
|
||||
}
|
||||
set{
|
||||
App.OpenedControls.Destroy();
|
||||
_curPage = value;
|
||||
var disp = value as IDisposable;
|
||||
if(disp != null) App.OpenedControls.Register(disp);
|
||||
OnPropertyChanged(nameof(CurrentPage));
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
[ObservableProperty]
|
||||
private ObservableCollection<Page> _pages=new ObservableCollection<Page>(){
|
||||
new Page("Home",()=>new HomePageViewModel()),
|
||||
new Page("Favorites",()=>new FavoritesPageViewModel()),
|
||||
new Page("Notifications",()=>new NotificationsPageViewModel()),
|
||||
new Page("Downloads",()=>new DownloadsPageViewModel()),
|
||||
|
||||
};
|
||||
|
||||
private ObservableCollection<Page> _pages=new ObservableCollection<Page>();
|
||||
[ObservableProperty]
|
||||
private Page? _selectedListItem;
|
||||
[ObservableProperty]
|
||||
|
@ -37,10 +60,12 @@ public partial class MainViewModel : ViewModelBase
|
|||
[ObservableProperty]
|
||||
private string _title;
|
||||
|
||||
public bool Setting {get;set;}
|
||||
|
||||
partial void OnSelectedListItemChanged(Page? value)
|
||||
{
|
||||
if (value is null) return;
|
||||
|
||||
if(Setting) return;
|
||||
|
||||
|
||||
|
||||
|
@ -57,4 +82,9 @@ public partial class MainViewModel : ViewModelBase
|
|||
{
|
||||
App.Log("Login button");
|
||||
}
|
||||
|
||||
internal void SetHome()
|
||||
{
|
||||
SelectedListItem = Pages[0];
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,71 @@
|
|||
namespace Tesses.CMS.Avalonia.ViewModels;
|
||||
|
||||
using System;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Threading.Tasks;
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using CommunityToolkit.Mvvm.Input;
|
||||
using CommunityToolkit.Mvvm.Messaging;
|
||||
using global::Avalonia.Threading;
|
||||
using LibVLCSharp.Shared;
|
||||
using ReactiveUI;
|
||||
using Tesses.CMS.Avalonia.Models;
|
||||
using Tesses.CMS.Client;
|
||||
using Tesses.VirtualFilesystem;
|
||||
|
||||
public partial class SavedDownloadViewModel : ViewModelBase
|
||||
{
|
||||
FavoritesItem item;
|
||||
MainViewModel model;
|
||||
|
||||
public SavedDownloadViewModel(FavoritesItem item,MainViewModel model)
|
||||
{
|
||||
this.item=item;
|
||||
this.model = model;
|
||||
DownloadOrViewCommand = ReactiveCommand.CreateFromTask(DownloadOrViewAsync);
|
||||
UpdateDownloadButton();
|
||||
}
|
||||
public string Name => item.MediaProperName;
|
||||
|
||||
public string AuthorName => item.UserProperName;
|
||||
|
||||
[ObservableProperty]
|
||||
private string _downloadBtnText = "Download";
|
||||
|
||||
bool downloaded=false;
|
||||
|
||||
private void UpdateDownloadButton()
|
||||
{
|
||||
switch(item.Type)
|
||||
{
|
||||
case FavoriteType.Movie:
|
||||
UnixPath dir = Special.Root / "Downloads" / item.Username / "Movies" / item.MediaName;
|
||||
UnixPath filename = dir / item.MediaProperName + ".mp4";
|
||||
downloaded=(App.Platform.DownloadFilesystem?.FileExists(filename) ?? false);
|
||||
DownloadBtnText=downloaded ? "View" : "Download";
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public IReactiveCommand DownloadOrViewCommand {get;}
|
||||
|
||||
public async Task DownloadOrViewAsync()
|
||||
{
|
||||
UpdateDownloadButton();
|
||||
if(downloaded)
|
||||
{
|
||||
model.SetHome();
|
||||
HomePageViewModel hpvm=new ();
|
||||
model.CurrentPage = hpvm;
|
||||
await hpvm.PlayDownloadedAsync(item);
|
||||
return;
|
||||
}
|
||||
switch(item.Type)
|
||||
{
|
||||
case FavoriteType.Movie:
|
||||
App.StartDownload(item.Account,item.Media.ToObject<Movie>() ?? new Movie(){Name=item.MediaName,ProperName=item.MediaProperName});
|
||||
break;
|
||||
}
|
||||
UpdateDownloadButton();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,120 @@
|
|||
|
||||
|
||||
using System;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Media;
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using CommunityToolkit.Mvvm.Input;
|
||||
using CommunityToolkit.Mvvm.Messaging;
|
||||
using LibVLCSharp.Shared;
|
||||
using ReactiveUI;
|
||||
using Tesses.CMS.Avalonia.Models;
|
||||
using Tesses.CMS.Avalonia.Views;
|
||||
using Tesses.CMS.Client;
|
||||
namespace Tesses.CMS.Avalonia.ViewModels;
|
||||
|
||||
public partial class VideoPlayerViewModel : ViewModelBase, IDisposable
|
||||
{
|
||||
bool played=false;
|
||||
|
||||
public VideoPlayerViewModel()
|
||||
{
|
||||
BrowseCommand = ReactiveCommand.CreateFromTask(BrowseAsync);
|
||||
/*
|
||||
|
||||
|
||||
LibVLC vlc=new LibVLC("--input-repeat=65535");
|
||||
|
||||
Player=new MediaPlayer(new Media(vlc,new StreamMediaInput(strm)));
|
||||
|
||||
Player.Play();*/
|
||||
}
|
||||
|
||||
public VideoPlayerViewModel(string path)
|
||||
{
|
||||
BrowseCommand = ReactiveCommand.CreateFromTask(BrowseAsync);
|
||||
played=true;
|
||||
Stream = File.OpenRead(path);
|
||||
Thread thrd=new Thread(()=>{
|
||||
Thread.Sleep(1000);
|
||||
Player=new MediaPlayer(new Media(vlc,new StreamMediaInput(Stream)));
|
||||
|
||||
Player.Play();
|
||||
});
|
||||
thrd.Start();
|
||||
}
|
||||
LibVLC vlc=new LibVLC("--input-repeat=65535");
|
||||
[ObservableProperty]
|
||||
private MediaPlayer? _player;
|
||||
|
||||
|
||||
|
||||
Stream? Stream=null;
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
|
||||
Thread thrd=new Thread(()=>{
|
||||
Player?.Stop();
|
||||
Thread.Sleep(10000);
|
||||
Player?.Dispose();
|
||||
Stream?.Dispose();
|
||||
vlc.Dispose();
|
||||
});
|
||||
thrd.Start();
|
||||
}
|
||||
|
||||
public IReactiveCommand BrowseCommand {get;}
|
||||
public async Task BrowseAsync()
|
||||
{
|
||||
if(App.Window != null)
|
||||
{
|
||||
await OpenAsync(App.Window);
|
||||
}
|
||||
if(App.MainView != null)
|
||||
{
|
||||
await OpenAsync(TopLevel.GetTopLevel(App.MainView));
|
||||
}
|
||||
}
|
||||
|
||||
private async Task OpenAsync(TopLevel? topLevel)
|
||||
{
|
||||
if(topLevel != null)
|
||||
{
|
||||
var res=new global::Avalonia.Platform.Storage.FilePickerOpenOptions();
|
||||
res.AllowMultiple=false;
|
||||
res.Title = "Browse for video";
|
||||
|
||||
var first = (await topLevel.StorageProvider.OpenFilePickerAsync(res)).FirstOrDefault();
|
||||
if(first != null)
|
||||
{
|
||||
if(played)
|
||||
{
|
||||
var player = Player;
|
||||
var strm = Stream;
|
||||
Thread thrd=new Thread(()=>{
|
||||
player?.Stop();
|
||||
Thread.Sleep(10000);
|
||||
player?.Dispose();
|
||||
strm?.Dispose();
|
||||
|
||||
});
|
||||
thrd.Start();
|
||||
}
|
||||
played=true;
|
||||
|
||||
|
||||
Stream = await first.OpenReadAsync();
|
||||
|
||||
Player=new MediaPlayer(new Media(vlc,new StreamMediaInput(Stream)));
|
||||
|
||||
Player.Play();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -5,4 +5,5 @@ namespace Tesses.CMS.Avalonia.ViewModels;
|
|||
|
||||
public class ViewModelBase : ObservableObject
|
||||
{
|
||||
|
||||
}
|
||||
|
|
|
@ -0,0 +1,25 @@
|
|||
<UserControl xmlns="https://github.com/avaloniaui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:vm="clr-namespace:Tesses.CMS.Avalonia.ViewModels"
|
||||
xmlns:v="clr-namespace:Tesses.CMS.Avalonia.Views"
|
||||
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
||||
x:Class="Tesses.CMS.Avalonia.Views.ActiveDownloadView"
|
||||
x:DataType="vm:ActiveDownloadViewModel">
|
||||
<Design.DataContext>
|
||||
<!-- This only sets the DataContext for the previewer in an IDE,
|
||||
to set the actual DataContext for runtime, set the DataContext property in code (look at App.axaml.cs) -->
|
||||
<vm:DownloadsPageViewModel />
|
||||
</Design.DataContext>
|
||||
<Grid RowDefinitions="Auto,Auto">
|
||||
<Grid Grid.Row="0" ColumnDefinitions="*,Auto">
|
||||
<TextBlock Grid.Column="0" Text="{Binding Name}" />
|
||||
<Button Grid.Column="1" Content="Cancel" Command="{Binding CancelCommand}"/>
|
||||
</Grid>
|
||||
<ProgressBar Grid.Row="1" Margin="0 10" Height="20"
|
||||
Minimum="0" Maximum="100" Value="{Binding Progress}"
|
||||
Foreground="Red"
|
||||
ShowProgressText="True"/>
|
||||
</Grid>
|
||||
</UserControl>
|
|
@ -0,0 +1,16 @@
|
|||
using System.Threading.Tasks;
|
||||
using Avalonia.Controls;
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using CommunityToolkit.Mvvm.Input;
|
||||
|
||||
namespace Tesses.CMS.Avalonia.Views;
|
||||
|
||||
public partial class ActiveDownloadView : UserControl
|
||||
{
|
||||
public ActiveDownloadView()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
|
||||
}
|
|
@ -12,6 +12,13 @@
|
|||
to set the actual DataContext for runtime, set the DataContext property in code (look at App.axaml.cs) -->
|
||||
<vm:DownloadsPageViewModel />
|
||||
</Design.DataContext>
|
||||
|
||||
<v:VideoPlayerWrapper MediaPlayer="{Binding Player}" />
|
||||
<ScrollViewer>
|
||||
<ItemsControl ItemsSource="{Binding Downloads}">
|
||||
<ItemsControl.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<TransitioningContentControl Content="{Binding Item}" />
|
||||
</DataTemplate>
|
||||
</ItemsControl.ItemTemplate>
|
||||
</ItemsControl>
|
||||
</ScrollViewer>
|
||||
</UserControl>
|
||||
|
|
|
@ -11,5 +11,20 @@
|
|||
to set the actual DataContext for runtime, set the DataContext property in code (look at App.axaml.cs) -->
|
||||
<vm:FavoritesPageViewModel />
|
||||
</Design.DataContext>
|
||||
<ScrollViewer>
|
||||
<ItemsControl ItemsSource="{Binding Favorites}">
|
||||
<ItemsControl.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<Grid RowDefinitions="Auto,Auto">
|
||||
|
||||
<Grid Grid.Row="0" ColumnDefinitions="*,Auto">
|
||||
<TextBlock FontSize="24" FontWeight="Bold" Grid.Column="0" Text="{Binding Name}" />
|
||||
<Button Grid.Column="1" Content="View" Command="{Binding ViewCommand}"/>
|
||||
</Grid>
|
||||
<TextBlock Grid.Row="1" Text="{Binding AuthorName}" />
|
||||
</Grid>
|
||||
</DataTemplate>
|
||||
</ItemsControl.ItemTemplate>
|
||||
</ItemsControl>
|
||||
</ScrollViewer>
|
||||
</UserControl>
|
||||
|
|
|
@ -0,0 +1,37 @@
|
|||
<UserControl xmlns="https://github.com/avaloniaui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:vm="clr-namespace:Tesses.CMS.Avalonia.ViewModels.HomePages"
|
||||
xmlns:v="clr-namespace:Tesses.CMS.Avalonia.Views"
|
||||
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
||||
x:Class="Tesses.CMS.Avalonia.Views.HomePages.HomeMoviePageView"
|
||||
x:DataType="vm:HomeMoviePageViewModel">
|
||||
<Design.DataContext>
|
||||
<!-- This only sets the DataContext for the previewer in an IDE,
|
||||
to set the actual DataContext for runtime, set the DataContext property in code (look at App.axaml.cs) -->
|
||||
<vm:HomeMoviePageViewModel />
|
||||
</Design.DataContext>
|
||||
<ScrollViewer>
|
||||
<Grid RowDefinitions="Auto,Auto,*" Margin="20">
|
||||
<Grid ColumnDefinitions="Auto,*" Grid.Row="0" Margin="0 0 0 20">
|
||||
<Image Grid.Column="0" Height="107" Width="60" Source="{Binding Thumbnail}" />
|
||||
<StackPanel Margin="20 0 0 0" Grid.Column="1" Orientation="Vertical">
|
||||
<TextBlock FontSize="24" FontWeight="Bold" Text="{Binding MovieTitle}"/>
|
||||
<TextBlock FontSize="18" Text="{Binding MovieCreator}"/>
|
||||
<TextBlock FontSize="18" Text="{Binding Published}"/>
|
||||
<TextBlock FontSize="18" Text="{Binding Modified}"/>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
<Grid Grid.Row="1" ColumnDefinitions="Auto,Auto,Auto">
|
||||
<Button Grid.Column="0" Command="{Binding WatchCommand}" Content="Watch" IsVisible="{Binding CanWatch}"/>
|
||||
<Button Grid.Column="1" Command="{Binding DownloadCommand}" Content="Download" IsVisible="{Binding CanDownload}"/>
|
||||
<Button Grid.Column="2" Command="{Binding AddToFavoritesCommand}" Content="{Binding AddOrRemoveFromFavorites}"/>
|
||||
|
||||
</Grid>
|
||||
|
||||
<v:InlineText Margin="0 20 0 0" Grid.Row="2" Text="{Binding Description}" />
|
||||
|
||||
</Grid>
|
||||
</ScrollViewer>
|
||||
</UserControl>
|
|
@ -0,0 +1,11 @@
|
|||
using Avalonia.Controls;
|
||||
|
||||
namespace Tesses.CMS.Avalonia.Views.HomePages;
|
||||
|
||||
public partial class HomeMoviePageView : UserControl
|
||||
{
|
||||
public HomeMoviePageView()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,17 @@
|
|||
<UserControl xmlns="https://github.com/avaloniaui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:vm="clr-namespace:Tesses.CMS.Avalonia.ViewModels.HomePages"
|
||||
xmlns:v="clr-namespace:Tesses.CMS.Avalonia.Views"
|
||||
|
||||
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
||||
x:Class="Tesses.CMS.Avalonia.Views.HomePages.HomeMovieVideoPlayerView"
|
||||
x:DataType="vm:HomeMovieVideoPlayerViewModel">
|
||||
<Design.DataContext>
|
||||
<!-- This only sets the DataContext for the previewer in an IDE,
|
||||
to set the actual DataContext for runtime, set the DataContext property in code (look at App.axaml.cs) -->
|
||||
<vm:HomeMovieVideoPlayerViewModel />
|
||||
</Design.DataContext>
|
||||
<v:VideoPlayer MediaPlayer="{Binding Player}" />
|
||||
</UserControl>
|
|
@ -0,0 +1,11 @@
|
|||
using Avalonia.Controls;
|
||||
|
||||
namespace Tesses.CMS.Avalonia.Views.HomePages;
|
||||
|
||||
public partial class HomeMovieVideoPlayerView : UserControl
|
||||
{
|
||||
public HomeMovieVideoPlayerView()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,24 @@
|
|||
<UserControl xmlns="https://github.com/avaloniaui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:vm="clr-namespace:Tesses.CMS.Avalonia.ViewModels.HomePages"
|
||||
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
||||
x:Class="Tesses.CMS.Avalonia.Views.HomePages.HomeShowListPageView"
|
||||
x:DataType="vm:HomeShowListPageViewModel">
|
||||
<Design.DataContext>
|
||||
<!-- This only sets the DataContext for the previewer in an IDE,
|
||||
to set the actual DataContext for runtime, set the DataContext property in code (look at App.axaml.cs) -->
|
||||
<vm:HomeMovieListPageViewModel />
|
||||
</Design.DataContext>
|
||||
<ListBox ItemsSource="{Binding Shows}" SelectedItem="{Binding SelectedListItem}">
|
||||
<ListBox.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<Grid ColumnDefinitions="Auto,*">
|
||||
<Image Grid.Column="0" Margin="20" Height="107" Width="60" Source="{Binding Image}" />
|
||||
<TextBlock Grid.Column="1" Text="{Binding Name}"/>
|
||||
</Grid>
|
||||
</DataTemplate>
|
||||
</ListBox.ItemTemplate>
|
||||
</ListBox>
|
||||
</UserControl>
|
|
@ -0,0 +1,11 @@
|
|||
using Avalonia.Controls;
|
||||
|
||||
namespace Tesses.CMS.Avalonia.Views.HomePages;
|
||||
|
||||
public partial class HomeShowListPageView : UserControl
|
||||
{
|
||||
public HomeShowListPageView()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,103 @@
|
|||
using System;
|
||||
using System.Text;
|
||||
using Avalonia;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Controls.Documents;
|
||||
using Avalonia.Data;
|
||||
using Avalonia.Media;
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
|
||||
namespace Tesses.CMS.Avalonia.Views;
|
||||
|
||||
public class InlineText : WrapPanel
|
||||
{
|
||||
/// <summary>
|
||||
/// MediaPlayer Data Bound property
|
||||
/// </summary>
|
||||
/// <summary>
|
||||
/// Defines the <see cref="Text"/> property.
|
||||
/// </summary>
|
||||
public static readonly DirectProperty<InlineText, string> TextProperty =
|
||||
AvaloniaProperty.RegisterDirect<InlineText, string>(
|
||||
nameof(Text),
|
||||
o => o.Text,
|
||||
(o, v) => o.Text = v,
|
||||
defaultBindingMode: BindingMode.TwoWay);
|
||||
string text="";
|
||||
public string Text {
|
||||
get => text;
|
||||
set{
|
||||
try{
|
||||
text = value;
|
||||
SetText();
|
||||
}catch(Exception ex)
|
||||
{
|
||||
_=ex;
|
||||
Console.WriteLine(ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void SetText()
|
||||
{
|
||||
this.Children.Clear();
|
||||
StringBuilder b = new StringBuilder();
|
||||
|
||||
for (int i = 0; i < text.Length; i++)
|
||||
{
|
||||
if (StartsWithAt(text, i, "http:") || StartsWithAt(text, i, "https:") || StartsWithAt(text, i, "ftp:") || StartsWithAt(text, i, "ftps:") || StartsWithAt(text, i, "sftp:") || StartsWithAt(text, i, "magnet:"))
|
||||
{
|
||||
if(b.Length > 0)
|
||||
{
|
||||
this.Children.Add(new TextBlock(){Text = b.ToString()});
|
||||
b.Clear();
|
||||
}
|
||||
StringBuilder b2 = new StringBuilder();
|
||||
for (; i < text.Length; i++)
|
||||
{
|
||||
if (text[i] == '\n' || text[i] == ' ')
|
||||
{
|
||||
b.Append(text[i]);
|
||||
break;
|
||||
}
|
||||
b2.Append(text[i]);
|
||||
}
|
||||
var tb = new TextBlock(){Text = b2.ToString()};
|
||||
tb.Foreground = new SolidColorBrush(Color.FromRgb(0,0,255));
|
||||
tb.TextDecorations = TextDecorations.Underline;
|
||||
|
||||
|
||||
|
||||
tb.Tapped += (sender,e)=>{
|
||||
App.LaunchUrl(b2.ToString());
|
||||
};
|
||||
this.Children.Add(tb);
|
||||
}
|
||||
else
|
||||
{
|
||||
b.Append(text[i]);
|
||||
}
|
||||
}
|
||||
if(b.Length > 0)
|
||||
{
|
||||
this.Children.Add(new TextBlock(){Text = b.ToString()});
|
||||
b.Clear();
|
||||
}
|
||||
}
|
||||
|
||||
private static bool StartsWithAt(string str, int indexof, string startsWith)
|
||||
{
|
||||
if (str.Length - indexof < startsWith.Length) return false;
|
||||
for (int i = 0; i < startsWith.Length; i++)
|
||||
{
|
||||
if (str[i + indexof] != startsWith[i]) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public InlineText()
|
||||
{
|
||||
|
||||
|
||||
}
|
||||
}
|
|
@ -0,0 +1,25 @@
|
|||
<UserControl xmlns="https://github.com/avaloniaui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:vm="clr-namespace:Tesses.CMS.Avalonia.ViewModels"
|
||||
xmlns:v="clr-namespace:Tesses.CMS.Avalonia.Views"
|
||||
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
||||
x:Class="Tesses.CMS.Avalonia.Views.SavedDownloadView"
|
||||
x:DataType="vm:SavedDownloadViewModel">
|
||||
<Design.DataContext>
|
||||
<!-- This only sets the DataContext for the previewer in an IDE,
|
||||
to set the actual DataContext for runtime, set the DataContext property in code (look at App.axaml.cs) -->
|
||||
<vm:SavedDownloadViewModel />
|
||||
</Design.DataContext>
|
||||
<Grid RowDefinitions="Auto,Auto">
|
||||
<Grid RowDefinitions="Auto,Auto">
|
||||
|
||||
<Grid Grid.Row="0" ColumnDefinitions="*,Auto">
|
||||
<TextBlock FontSize="24" FontWeight="Bold" Grid.Column="0" Text="{Binding Name}" />
|
||||
<Button Grid.Column="1" Content="{Binding DownloadBtnText}" Command="{Binding DownloadOrViewCommand}"/>
|
||||
</Grid>
|
||||
<TextBlock Grid.Row="1" Text="{Binding AuthorName}" />
|
||||
</Grid>
|
||||
</Grid>
|
||||
</UserControl>
|
|
@ -0,0 +1,11 @@
|
|||
using Avalonia.Controls;
|
||||
|
||||
namespace Tesses.CMS.Avalonia.Views;
|
||||
|
||||
public partial class SavedDownloadView : UserControl
|
||||
{
|
||||
public SavedDownloadView()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
}
|
|
@ -1,16 +1,51 @@
|
|||
using System;
|
||||
using System.IO;
|
||||
using Avalonia;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Data;
|
||||
using Avalonia.Threading;
|
||||
using LibVLCSharp.Avalonia;
|
||||
using LibVLCSharp.Shared;
|
||||
using Tesses.CMS.Avalonia;
|
||||
using Tesses.CMS.Avalonia.Views;
|
||||
using Tesses.VirtualFilesystem;
|
||||
using Tesses.VirtualFilesystem.Extensions;
|
||||
|
||||
namespace Tesses.CMS.Avalonia.Views;
|
||||
public static class MMS
|
||||
{
|
||||
public static void MoveScreenshot(this IPlatform p,string path)
|
||||
{
|
||||
if(p.DownloadFilesystem == null) return;
|
||||
p.DownloadFilesystem.CreateDirectory(Special.Root/"Screenshots");
|
||||
using(var ms = File.OpenRead(path))
|
||||
{
|
||||
using(var f = p.DownloadFilesystem.OpenWrite(Special.Root/"Screenshots"/Path.GetFileName(path)))
|
||||
{
|
||||
ms.CopyTo(f);
|
||||
}
|
||||
}
|
||||
File.Delete(path);
|
||||
}
|
||||
}
|
||||
|
||||
public class VideoPlayer : Grid
|
||||
{
|
||||
/// <summary>
|
||||
/// MediaPlayer Data Bound property
|
||||
/// </summary>
|
||||
/// <summary>
|
||||
/// Defines the <see cref="MediaPlayer"/> property.
|
||||
/// </summary>
|
||||
public static readonly DirectProperty<VideoPlayer, MediaPlayer?> MediaPlayerProperty =
|
||||
AvaloniaProperty.RegisterDirect<VideoPlayer, MediaPlayer?>(
|
||||
nameof(MediaPlayer),
|
||||
o => o.MediaPlayer,
|
||||
(o, v) => o.MediaPlayer = v,
|
||||
defaultBindingMode: BindingMode.TwoWay);
|
||||
|
||||
public VideoPlayer()
|
||||
{
|
||||
view=new VideoView();
|
||||
view=App.Platform.CreatePlayer();
|
||||
|
||||
|
||||
|
||||
|
@ -23,12 +58,19 @@ public class VideoPlayer : Grid
|
|||
ss.Content = "SS";
|
||||
ss.Click += (sender,e)=>{
|
||||
//take screenshot
|
||||
string realDir=App.Platform.DownloadFilesystem == null ? Environment.GetFolderPath(Environment.SpecialFolder.MyPictures) : Path.Combine(App.Platform.Configuration.DownloadPath,"Screenshots");
|
||||
App.Log($"CanTakeScreenShots: {App.Platform.CanTakeScreenShots}");
|
||||
if(!App.Platform.CanTakeScreenShots) return;
|
||||
|
||||
string realDir =App.Platform.ScreenshotPath; //App.Platform.DownloadFilesystem == null ? Environment.GetFolderPath(Environment.SpecialFolder.MyPictures) : Path.Combine(App.Platform.Configuration.DownloadPath,"Screenshots");
|
||||
App.Log($"ScreenshotPath: {App.Platform.ScreenshotPath}");
|
||||
Directory.CreateDirectory(realDir);
|
||||
if(MediaPlayer == null) return;
|
||||
string name = Path.Combine(realDir,$"TessesCMS-Screenshot-{DateTime.Now.ToString("yyyyMMdd_HHmmss")}-{TimeSpan.FromMilliseconds(MediaPlayer.Position*MediaPlayer.Length).ToString().Replace(":","_")}.png");
|
||||
App.Log($"name: {name}");
|
||||
MediaPlayer.TakeSnapshot(0,name,0,0);
|
||||
|
||||
if(App.Platform.MustMoveScreenshot)
|
||||
App.Platform.MoveScreenshot(name);
|
||||
App.Log("Screenshot taken");
|
||||
};
|
||||
slider = new Slider();
|
||||
slider.Minimum = 0;
|
||||
|
@ -57,6 +99,8 @@ public class VideoPlayer : Grid
|
|||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
seekPanel.ColumnDefinitions.Add(new ColumnDefinition(GridLength.Auto));
|
||||
seekPanel.ColumnDefinitions.Add(new ColumnDefinition(GridLength.Star));
|
||||
seekPanel.ColumnDefinitions.Add(new ColumnDefinition(GridLength.Auto));
|
||||
|
@ -71,23 +115,24 @@ public class VideoPlayer : Grid
|
|||
}
|
||||
Slider slider;
|
||||
Button playBtn;
|
||||
VideoView view;
|
||||
Control view;
|
||||
public MediaPlayer? MediaPlayer
|
||||
{
|
||||
get=>view.MediaPlayer;
|
||||
get=>App.Platform.GetMediaPlayer(view);
|
||||
set{
|
||||
if(view.MediaPlayer != null)
|
||||
var mp = App.Platform.GetMediaPlayer(view);
|
||||
if(mp != null)
|
||||
{
|
||||
view.MediaPlayer.Paused -= Paused;
|
||||
view.MediaPlayer.Playing -= Playing;
|
||||
view.MediaPlayer.PositionChanged -= PositionChanged;
|
||||
mp.Paused -= Paused;
|
||||
mp.Playing -= Playing;
|
||||
mp.PositionChanged -= PositionChanged;
|
||||
}
|
||||
view.MediaPlayer=value;
|
||||
if(view.MediaPlayer != null)
|
||||
App.Platform.SetMediaPlayer(view,value);
|
||||
if(value != null)
|
||||
{
|
||||
view.MediaPlayer.Paused += Paused;
|
||||
view.MediaPlayer.Playing += Playing;
|
||||
view.MediaPlayer.PositionChanged += PositionChanged;
|
||||
value.Paused += Paused;
|
||||
value.Playing += Playing;
|
||||
value.PositionChanged += PositionChanged;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,20 @@
|
|||
<UserControl xmlns="https://github.com/avaloniaui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:vm="clr-namespace:Tesses.CMS.Avalonia.ViewModels"
|
||||
xmlns:v="clr-namespace:Tesses.CMS.Avalonia.Views"
|
||||
|
||||
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
||||
x:Class="Tesses.CMS.Avalonia.Views.VideoPlayerView"
|
||||
x:DataType="vm:VideoPlayerViewModel">
|
||||
<Design.DataContext>
|
||||
<!-- This only sets the DataContext for the previewer in an IDE,
|
||||
to set the actual DataContext for runtime, set the DataContext property in code (look at App.axaml.cs) -->
|
||||
<vm:VideoPlayerViewModel />
|
||||
</Design.DataContext>
|
||||
<Grid RowDefinitions="Auto,*">
|
||||
<Button Command="{Binding BrowseCommand}" Content="Open"/>
|
||||
<v:VideoPlayer Grid.Row="1" MediaPlayer="{Binding Player}" />
|
||||
</Grid>
|
||||
</UserControl>
|
|
@ -0,0 +1,11 @@
|
|||
using Avalonia.Controls;
|
||||
|
||||
namespace Tesses.CMS.Avalonia.Views;
|
||||
|
||||
public partial class VideoPlayerView : UserControl
|
||||
{
|
||||
public VideoPlayerView()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
}
|
|
@ -1,41 +0,0 @@
|
|||
using Avalonia;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Data;
|
||||
using LibVLCSharp.Shared;
|
||||
|
||||
namespace Tesses.CMS.Avalonia.Views;
|
||||
|
||||
public class VideoPlayerWrapper : Grid
|
||||
{
|
||||
public VideoPlayerWrapper()
|
||||
{
|
||||
RowDefinitions.Add(new RowDefinition(GridLength.Star));
|
||||
ColumnDefinitions.Add(new ColumnDefinition(GridLength.Star));
|
||||
Player= App.Platform.CreatePlayer();
|
||||
this.Children.Add(Player);
|
||||
}
|
||||
|
||||
public Control Player {get;}
|
||||
|
||||
public MediaPlayer? MediaPlayer
|
||||
{
|
||||
get=>App.Platform.GetMediaPlayer(Player);
|
||||
set=>App.Platform.SetMediaPlayer(Player,value);
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// MediaPlayer Data Bound property
|
||||
/// </summary>
|
||||
/// <summary>
|
||||
/// Defines the <see cref="MediaPlayer"/> property.
|
||||
/// </summary>
|
||||
public static readonly DirectProperty<VideoPlayerWrapper, MediaPlayer?> MediaPlayerProperty =
|
||||
AvaloniaProperty.RegisterDirect<VideoPlayerWrapper, MediaPlayer?>(
|
||||
nameof(MediaPlayer),
|
||||
o => o.MediaPlayer,
|
||||
(o, v) => o.MediaPlayer = v,
|
||||
defaultBindingMode: BindingMode.TwoWay);
|
||||
|
||||
|
||||
}
|
|
@ -7,13 +7,20 @@
|
|||
<ItemGroup>
|
||||
<PackageReference Include="CommandLineParser" Version="2.9.1" />
|
||||
<PackageReference Include="ReadLine" Version="2.0.1" />
|
||||
<PackageReference Include="Packaging.Targets">
|
||||
<Version>0.1.226-*</Version>
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
</PackageReference>
|
||||
</ItemGroup>
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>net7.0</TargetFramework>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
<AssemblyName>tcms-cli</AssemblyName>
|
||||
<HomePage>https://tesses.net/apps/TessesCMS</HomePage>
|
||||
<Maintainer>Mike Nolan <tesses@tesses.net></Maintainer>
|
||||
</PropertyGroup>
|
||||
|
||||
</Project>
|
||||
|
|
|
@ -151,6 +151,7 @@ namespace Tesses.CMS.Client
|
|||
byte[] buffer=new byte[1024];
|
||||
|
||||
using(var srcStrm = await resp.Content.ReadAsStreamAsync())
|
||||
{
|
||||
do {
|
||||
if(token.IsCancellationRequested) return;
|
||||
read = await srcStrm.ReadAsync(buffer,0,buffer.Length,token);
|
||||
|
@ -158,8 +159,9 @@ namespace Tesses.CMS.Client
|
|||
await dest.WriteAsync(buffer,0,read,token);
|
||||
offset += read;
|
||||
if(total > 0)
|
||||
progress?.Report((double)offset / (double)read);
|
||||
progress?.Report((double)offset / (double)total);
|
||||
} while(read>0);
|
||||
}
|
||||
resp.Dispose();
|
||||
}
|
||||
public ShowClient Shows => new ShowClient(this);
|
||||
|
@ -356,7 +358,18 @@ namespace Tesses.CMS.Client
|
|||
yield return new ShowWithSeasonsAndEpisodes(show,seasons);
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<ShowContentMetaData> GetShowContentMetadataAsync(string user,string show)
|
||||
{
|
||||
return JsonConvert.DeserializeObject<ShowContentMetaData>(await client.client.GetStringAsync($"{client.rooturl.TrimEnd('/')}/api/v1/ShowFile?show={show}&user={user}&type=json"));
|
||||
}
|
||||
public async Task DownloadThumbnailAsync(string user,string show,string dest,CancellationToken token=default,IProgress<double> progress=null)
|
||||
{
|
||||
await client.DownloadFileAsync($"{client.rooturl}/content/{user}/show/{show}/thumbnail.jpg",dest,token,progress);
|
||||
}
|
||||
public async Task DownloadThumbnailAsync(string user,string show,Stream dest,CancellationToken token=default,IProgress<double> progress=null)
|
||||
{
|
||||
await client.DownloadFileAsync($"{client.rooturl}/content/{user}/show/{show}/thumbnail.jpg",dest,token,progress);
|
||||
}
|
||||
}
|
||||
|
||||
public class MovieClient
|
||||
|
|
|
@ -1,8 +1,38 @@
|
|||
using System;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
using System.Collections.Generic;
|
||||
namespace Tesses.CMS.Client
|
||||
{
|
||||
public class ShowContentMetaData
|
||||
{
|
||||
[JsonProperty("show_info")]
|
||||
public Show Info {get;set;}
|
||||
[JsonProperty("has_show_torrent")]
|
||||
public bool HasShowTorrent{get;set;}
|
||||
[JsonProperty("show_torrent_url")]
|
||||
public string ShowTorrentUrl {get;set;}
|
||||
[JsonProperty("has_show_with_extras_torrent")]
|
||||
public bool HasShowWithExtrasTorrent{get;set;}
|
||||
|
||||
[JsonProperty("show_with_extras_torrent_url")]
|
||||
public string ShowWithExtrasTorrentUrl {get;set;}
|
||||
|
||||
|
||||
|
||||
[JsonProperty("has_poster")]
|
||||
public bool HasPoster {get;set;}
|
||||
[JsonProperty("poster_url")]
|
||||
public string PosterUrl {get;set;}
|
||||
|
||||
[JsonProperty("has_thumbnail")]
|
||||
public bool HasThumbnail {get;set;}
|
||||
|
||||
[JsonProperty("thumbnail_url")]
|
||||
|
||||
public string ThumbnailUrl {get;set;}
|
||||
[JsonProperty("extra_streams")]
|
||||
public List<ExtraDataStream> ExtraStreams {get;set;}=new List<ExtraDataStream>();
|
||||
}
|
||||
public class Show
|
||||
{
|
||||
[JsonProperty("proper_name")]
|
||||
|
|
|
@ -5,14 +5,35 @@
|
|||
<TargetFramework>net8.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
<AssemblyName>tcms-server</AssemblyName>
|
||||
<HomePage>https://tesses.net/apps/TessesCMS</HomePage>
|
||||
<Maintainer>Mike Nolan <tesses@tesses.net></Maintainer>
|
||||
<InstallService>true</InstallService>
|
||||
<CreateUser>true</CreateUser>
|
||||
<UserName>tcms-server</UserName>
|
||||
|
||||
<PostInstallScript>chown -R tcms-server:tcms-server /var/lib/tcms-server</PostInstallScript>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.SyndicationFeed.ReaderWriter" Version="1.0.2" />
|
||||
<PackageReference Include="PlaylistsNET" Version="1.4.0" />
|
||||
<PackageReference Include="Tesses.WebServer.EasyServer" Version="1.0.1" />
|
||||
<PackageReference Include="Packaging.Targets">
|
||||
<Version>0.1.226-*</Version>
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
</PackageReference>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Content Include="tcms-server.service" CopyToPublishDirectory="PreserveNewest" LinuxFileMode="1755">
|
||||
<LinuxPath>/etc/systemd/system/tcms-server.service</LinuxPath>
|
||||
</Content>
|
||||
<Content Include="config.json" CopyToPublishDirectory="PreserveNewest" LinuxFileMode="1755">
|
||||
<LinuxPath>/var/lib/tcms-server/config.json</LinuxPath>
|
||||
</Content>
|
||||
|
||||
<DebDependency Include="ffmpeg (>= 7:2.8.6)" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Tesses.CMS\Tesses.CMS.csproj" />
|
||||
<ProjectReference Include="..\Tesses.CMS.Providers.LiteDb\Tesses.CMS.Providers.LiteDb.csproj" />
|
||||
|
|
|
@ -0,0 +1,14 @@
|
|||
{
|
||||
"Title": "YourBranding",
|
||||
"Urls": [{"Url":"https://example.com", "Text":"Example Url"}],
|
||||
"Root": "/",
|
||||
"Email":{
|
||||
"Host": "YourSMTPDomain",
|
||||
"User": "YourUser",
|
||||
"Pass": "YourEmailPassword",
|
||||
"Port": 587,
|
||||
"Encryption": "StartTls",
|
||||
"Email": "YourUser@YourDomain"
|
||||
},
|
||||
"Publish": "NoRestriction"
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
[Unit]
|
||||
Description=Tesses.CMS
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
User=tcms-server
|
||||
Group=tcms-server
|
||||
ExecStart=/usr/local/bin/tcms-server "/var/lib/tcms-server"
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
|
@ -23,9 +23,9 @@ namespace Tesses.CMS
|
|||
[JsonIgnore]
|
||||
public long UserId {get;set;}
|
||||
[JsonProperty("creation_time")]
|
||||
public DateTime CreationTime {get;set;}
|
||||
public DateTime CreationTime {get;set;}=DateTime.Now;
|
||||
[JsonProperty("last_updated_time")]
|
||||
public DateTime LastUpdated {get;set;}
|
||||
public DateTime LastUpdated {get;set;}=DateTime.Now;
|
||||
[JsonProperty("description")]
|
||||
public string Description {get;set;}
|
||||
|
||||
|
|
|
@ -22,9 +22,9 @@ namespace Tesses.CMS
|
|||
[JsonIgnore]
|
||||
public long UserId {get;set;}
|
||||
[JsonProperty("creation_time")]
|
||||
public DateTime CreationTime {get;set;}
|
||||
public DateTime CreationTime {get;set;}=DateTime.Now;
|
||||
[JsonProperty("last_updated_time")]
|
||||
public DateTime LastUpdated {get;set;}
|
||||
public DateTime LastUpdated {get;set;}=DateTime.Now;
|
||||
[JsonProperty("description")]
|
||||
public string Description {get;set;}="";
|
||||
|
||||
|
|
|
@ -14,10 +14,10 @@ namespace Tesses.CMS
|
|||
public string Name {get;set;}="";
|
||||
[JsonIgnore]
|
||||
public long UserId {get;set;}
|
||||
[JsonProperty("created_time")]
|
||||
public DateTime CreationTime {get;set;}
|
||||
[JsonProperty("creation_time")]
|
||||
public DateTime CreationTime {get;set;}=DateTime.Now;
|
||||
[JsonProperty("last_updated_time")]
|
||||
public DateTime LastUpdated {get;set;}
|
||||
public DateTime LastUpdated {get;set;}=DateTime.Now;
|
||||
[JsonProperty("description")]
|
||||
public string Description {get;set;}="";
|
||||
|
||||
|
|
|
@ -16,9 +16,9 @@ namespace Tesses.CMS
|
|||
[JsonIgnore]
|
||||
public long UserId {get;set;}
|
||||
[JsonProperty("creation_time")]
|
||||
public DateTime CreationTime {get;set;}
|
||||
public DateTime CreationTime {get;set;}=DateTime.Now;
|
||||
[JsonProperty("last_updated_time")]
|
||||
public DateTime LastUpdated {get;set;}
|
||||
public DateTime LastUpdated {get;set;}=DateTime.Now;
|
||||
[JsonProperty("description")]
|
||||
public string Description {get;set;}="";
|
||||
[JsonProperty("source")]
|
||||
|
|
|
@ -23,7 +23,7 @@ namespace Tesses.CMS
|
|||
[JsonProperty("platforms")]
|
||||
public List<ProjectReleasePlatform> Platforms {get;set;}=new List<ProjectReleasePlatform>();
|
||||
[JsonProperty("creation_time")]
|
||||
public DateTime CreationTime {get;set;}
|
||||
public DateTime CreationTime {get;set;}=DateTime.Now;
|
||||
}
|
||||
public enum CPUArchitecture
|
||||
{
|
||||
|
|
|
@ -18,9 +18,9 @@ namespace Tesses.CMS
|
|||
[JsonIgnore]
|
||||
public long UserId {get;set;}
|
||||
[JsonProperty("creation_time")]
|
||||
public DateTime CreationTime {get;set;}
|
||||
public DateTime CreationTime {get;set;}=DateTime.Now;
|
||||
[JsonProperty("last_updated_time")]
|
||||
public DateTime LastUpdated {get;set;}
|
||||
public DateTime LastUpdated {get;set;}=DateTime.Now;
|
||||
[JsonProperty("description")]
|
||||
public string Description {get;set;}="";
|
||||
|
||||
|
|
|
@ -15,9 +15,9 @@ namespace Tesses.CMS
|
|||
[JsonIgnore]
|
||||
public long UserId {get;set;}
|
||||
[JsonProperty("creation_time")]
|
||||
public DateTime CreationTime {get;set;}
|
||||
public DateTime CreationTime {get;set;} = DateTime.Now;
|
||||
[JsonProperty("last_updated_time")]
|
||||
public DateTime LastUpdated {get;set;}
|
||||
public DateTime LastUpdated {get;set;} = DateTime.Now;
|
||||
[JsonProperty("description")]
|
||||
public string Description {get;set;}="";
|
||||
|
||||
|
|
|
@ -0,0 +1,58 @@
|
|||
#!/bin/bash
|
||||
dotnet tool install --global dotnet-deb
|
||||
. /deploy_dir/setpath.sh
|
||||
ln -s "$DEPLOY_DIR" publish
|
||||
cd Tesses.CMS.Server
|
||||
dotnet deb -c Release -o out
|
||||
cd out
|
||||
for f in *.deb; do mv -- "$f" "${f%.deb}_all.deb";done
|
||||
cp *.deb /pool/
|
||||
cd ../
|
||||
mkdir ../publish/server/linux-{x64,arm,arm64}
|
||||
dotnet publish -c Release -o ../publish/server/linux-x64 -r linux-x64 --self-contained -p:PublishSingleFile=true -p:PublishReadyToRun=true
|
||||
dotnet publish -c Release -o ../publish/server/linux-arm -r linux-arm --self-contained -p:PublishSingleFile=true -p:PublishReadyToRun=true
|
||||
dotnet publish -c Release -o ../publish/server/linux-arm64 -r linux-arm64 --self-contained -p:PublishSingleFile=true -p:PublishReadyToRun=true
|
||||
mkdir ../publish/server/win-{x86,x64,arm64}
|
||||
dotnet publish -c Release -o ../publish/server/win-x86 -r win-x86 --self-contained -p:PublishSingleFile=true -p:PublishReadyToRun=true
|
||||
dotnet publish -c Release -o ../publish/server/win-x64 -r win-x64 --self-contained -p:PublishSingleFile=true -p:PublishReadyToRun=true
|
||||
dotnet publish -c Release -o ../publish/server/win-arm64 -r win-arm64 --self-contained -p:PublishSingleFile=true -p:PublishReadyToRun=true
|
||||
mkdir ../publish/server/osx-{x64,arm64}
|
||||
dotnet publish -c Release -o ../publish/server/osx-x64 -r osx-x64 --self-contained -p:PublishSingleFile=true -p:PublishReadyToRun=true
|
||||
dotnet publish -c Release -o ../publish/server/osx-arm64 -r osx-arm64 --self-contained -p:PublishSingleFile=true -p:PublishReadyToRun=true
|
||||
cd ../Tesses.CMS.Cli
|
||||
dotnet deb -c Release -o out
|
||||
cd out
|
||||
for f in *.deb; do mv -- "$f" "${f%.deb}_all.deb";done
|
||||
cp *.deb /pool/
|
||||
cd ../
|
||||
mkdir ../publish/cli/linux-{x64,arm,arm64}
|
||||
dotnet publish -c Release -o ../publish/cli/linux-x64 -r linux-x64 --self-contained -p:PublishSingleFile=true -p:PublishReadyToRun=true
|
||||
dotnet publish -c Release -o ../publish/cli/linux-arm -r linux-arm --self-contained -p:PublishSingleFile=true -p:PublishReadyToRun=true
|
||||
dotnet publish -c Release -o ../publish/cli/linux-arm64 -r linux-arm64 --self-contained -p:PublishSingleFile=true -p:PublishReadyToRun=true
|
||||
mkdir ../publish/cli/win-{x86,x64,arm64}
|
||||
dotnet publish -c Release -o ../publish/cli/win-x86 -r win-x86 --self-contained -p:PublishSingleFile=true -p:PublishReadyToRun=true
|
||||
dotnet publish -c Release -o ../publish/cli/win-x64 -r win-x64 --self-contained -p:PublishSingleFile=true -p:PublishReadyToRun=true
|
||||
dotnet publish -c Release -o ../publish/cli/win-arm64 -r win-arm64 --self-contained -p:PublishSingleFile=true -p:PublishReadyToRun=true
|
||||
mkdir ../publish/cli/osx-{x64,arm64}
|
||||
dotnet publish -c Release -o ../publish/cli/osx-x64 -r osx-x64 --self-contained -p:PublishSingleFile=true -p:PublishReadyToRun=true
|
||||
dotnet publish -c Release -o ../publish/cli/osx-arm64 -r osx-arm64 --self-contained -p:PublishSingleFile=true -p:PublishReadyToRun=true
|
||||
cd ../Tesses.CMS.Avalonia/Tesses.CMS.Avalonia.Desktop
|
||||
dotnet deb -c Release -o out
|
||||
cd out
|
||||
for f in *.deb; do mv -- "$f" "${f%.deb}_all.deb";done
|
||||
cp *.deb /pool/
|
||||
cd ../
|
||||
mkdir ../../publish/desktop/linux-{x64,arm,arm64}
|
||||
dotnet publish -c Release -o ../../publish/desktop/linux-x64 -r linux-x64 --self-contained -p:PublishSingleFile=true -p:PublishReadyToRun=true
|
||||
dotnet publish -c Release -o ../../publish/desktop/linux-arm -r linux-arm --self-contained -p:PublishSingleFile=true -p:PublishReadyToRun=true
|
||||
dotnet publish -c Release -o ../../publish/desktop/linux-arm64 -r linux-arm64 --self-contained -p:PublishSingleFile=true -p:PublishReadyToRun=true
|
||||
#mkdir ../../publish/desktop/win-{x86,x64,arm64}
|
||||
#dotnet publish -c Release -o ../../publish/desktop/win-x86 -r win-x86 --self-contained -p:PublishSingleFile=true -p:PublishReadyToRun=true
|
||||
#dotnet publish -c Release -o ../../publish/desktop/win-x64 -r win-x64 --self-contained -p:PublishSingleFile=true -p:PublishReadyToRun=true
|
||||
#dotnet publish -c Release -o ../../publish/desktop/win-arm64 -r win-arm64 --self-contained -p:PublishSingleFile=true -p:PublishReadyToRun=true
|
||||
mkdir ../../publish/desktop/osx-{x64,arm64}
|
||||
dotnet publish -c Release -o ../../publish/desktop/osx-x64 -r osx-x64 --self-contained -p:PublishSingleFile=true -p:PublishReadyToRun=true
|
||||
dotnet publish -c Release -o ../../publish/desktop/osx-arm64 -r osx-arm64 --self-contained -p:PublishSingleFile=true -p:PublishReadyToRun=true
|
||||
cd ../Tesses.CMS.Avalonia.Android
|
||||
mkdir ../../publish/android
|
||||
dotnet publish -c Release -o ../../publish/android
|
Loading…
Reference in New Issue