Deb setup
This commit is contained in:
parent
b3ae68232d
commit
fe83c39604
|
@ -484,3 +484,4 @@ $RECYCLE.BIN/
|
||||||
*.swp
|
*.swp
|
||||||
help.txt
|
help.txt
|
||||||
data/
|
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.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using Android.Media;
|
||||||
|
using Avalonia.Android;
|
||||||
|
using Avalonia.Controls;
|
||||||
|
using Avalonia.Platform;
|
||||||
using Avalonia.Platform.Storage;
|
using Avalonia.Platform.Storage;
|
||||||
|
using LibVLCSharp.Platforms.Android;
|
||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
using Tesses.VirtualFilesystem;
|
using Tesses.VirtualFilesystem;
|
||||||
using Tesses.VirtualFilesystem.Extensions;
|
using Tesses.VirtualFilesystem.Extensions;
|
||||||
using Tesses.VirtualFilesystem.Filesystems;
|
using Tesses.VirtualFilesystem.Filesystems;
|
||||||
namespace Tesses.CMS.Avalonia.Android;
|
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
|
internal class MobilePlatform : IPlatform
|
||||||
{
|
{
|
||||||
string configpath;
|
string configpath;
|
||||||
|
@ -39,7 +87,12 @@ internal class MobilePlatform : IPlatform
|
||||||
|
|
||||||
internal IVirtualFilesystem? virtualFilesystem;
|
internal IVirtualFilesystem? virtualFilesystem;
|
||||||
public IVirtualFilesystem? DownloadFilesystem => 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()
|
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>
|
<ItemGroup>
|
||||||
<PackageReference Include="Avalonia.Android" Version="$(AvaloniaVersion)" />
|
<PackageReference Include="Avalonia.Android" Version="$(AvaloniaVersion)" />
|
||||||
|
<PackageReference Include="LibVlcSharp" Version="3.8.5" />
|
||||||
<PackageReference Include="MimeTypesMap" Version="1.0.8" />
|
<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" />
|
<PackageReference Include="Xamarin.AndroidX.Core.SplashScreen" Version="1.0.1.1" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
using System;
|
||||||
|
using System.Diagnostics;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
@ -34,7 +36,12 @@ internal class DesktopPlatform : IPlatform
|
||||||
|
|
||||||
IVirtualFilesystem? virtualFilesystem;
|
IVirtualFilesystem? virtualFilesystem;
|
||||||
public IVirtualFilesystem? DownloadFilesystem => 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()
|
public async Task BrowseForDownloadDirectoryAsync()
|
||||||
{
|
{
|
||||||
|
@ -75,21 +82,28 @@ internal class DesktopPlatform : IPlatform
|
||||||
|
|
||||||
public Control CreatePlayer()
|
public Control CreatePlayer()
|
||||||
{
|
{
|
||||||
return new VideoPlayer();
|
return new VideoView();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void SetMediaPlayer(Control control, MediaPlayer? mediaPlayer)
|
public void SetMediaPlayer(Control control, MediaPlayer? mediaPlayer)
|
||||||
{
|
{
|
||||||
var ctrl = control as VideoPlayer;
|
var ctrl = control as VideoView;
|
||||||
if(ctrl != null)
|
if(ctrl != null)
|
||||||
ctrl.MediaPlayer = mediaPlayer;
|
ctrl.MediaPlayer = mediaPlayer;
|
||||||
}
|
}
|
||||||
|
|
||||||
public MediaPlayer? GetMediaPlayer(Control control)
|
public MediaPlayer? GetMediaPlayer(Control control)
|
||||||
{
|
{
|
||||||
var ctrl = control as VideoPlayer;
|
var ctrl = control as VideoView;
|
||||||
if(ctrl != null)
|
if(ctrl != null)
|
||||||
return ctrl.MediaPlayer;
|
return ctrl.MediaPlayer;
|
||||||
return null;
|
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>
|
<TargetFramework>net8.0</TargetFramework>
|
||||||
<Nullable>enable</Nullable>
|
<Nullable>enable</Nullable>
|
||||||
<BuiltInComInteropSupport>true</BuiltInComInteropSupport>
|
<BuiltInComInteropSupport>true</BuiltInComInteropSupport>
|
||||||
|
<AssemblyName>tcms-desktop</AssemblyName>
|
||||||
|
<HomePage>https://tesses.net/apps/TessesCMS</HomePage>
|
||||||
|
<Maintainer>Mike Nolan <tesses@tesses.net></Maintainer>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<ApplicationManifest>app.manifest</ApplicationManifest>
|
<ApplicationManifest>app.manifest</ApplicationManifest>
|
||||||
</PropertyGroup>
|
</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>
|
<ItemGroup>
|
||||||
<PackageReference Include="Avalonia.Desktop" Version="$(AvaloniaVersion)" />
|
<PackageReference Include="Avalonia.Desktop" Version="$(AvaloniaVersion)" />
|
||||||
<!--Condition below is needed to remove Avalonia.Diagnostics package from build output in Release configuration.-->
|
<!--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" Version="1.0.2" />
|
||||||
<PackageReference Include="Tesses.VirtualFilesystem.Base" Version="1.0.2" />
|
<PackageReference Include="Tesses.VirtualFilesystem.Base" Version="1.0.2" />
|
||||||
<PackageReference Include="Tesses.Virtualfilesystem.Local" Version="1.0.1" />
|
<PackageReference Include="Tesses.Virtualfilesystem.Local" Version="1.0.1" />
|
||||||
|
<PackageReference Include="Packaging.Targets">
|
||||||
|
<Version>0.1.226-*</Version>
|
||||||
|
<PrivateAssets>all</PrivateAssets>
|
||||||
|
</PackageReference>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<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,15 +15,23 @@ using Avalonia.Platform;
|
||||||
using Tesses.VirtualFilesystem;
|
using Tesses.VirtualFilesystem;
|
||||||
using Tesses.VirtualFilesystem.Extensions;
|
using Tesses.VirtualFilesystem.Extensions;
|
||||||
using System.IO;
|
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;
|
namespace Tesses.CMS.Avalonia;
|
||||||
|
|
||||||
public partial class App : Application
|
public partial class App : Application
|
||||||
{
|
{
|
||||||
|
public static OpenedCtrls OpenedControls {get;}=new OpenedCtrls();
|
||||||
public static IPlatform Platform {get;set;} = new NullPlatform();
|
public static IPlatform Platform {get;set;} = new NullPlatform();
|
||||||
|
|
||||||
public static MainWindow? Window {get;set;}
|
public static MainWindow? Window {get;set;}
|
||||||
|
|
||||||
public static TessesCMSClient Client {get;} = new TessesCMSClient();
|
public static TessesCMSClient Client {get;} = new TessesCMSClient();
|
||||||
|
|
||||||
|
public static MainView? MainView {get;set;}
|
||||||
|
|
||||||
public override void Initialize()
|
public override void Initialize()
|
||||||
{
|
{
|
||||||
|
@ -31,6 +39,20 @@ public partial class App : Application
|
||||||
AvaloniaXamlLoader.Load(this);
|
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()
|
public override void OnFrameworkInitializationCompleted()
|
||||||
{
|
{
|
||||||
Client.RootUrl = Platform.Configuration.ServerUrl;
|
Client.RootUrl = Platform.Configuration.ServerUrl;
|
||||||
|
@ -46,11 +68,11 @@ public partial class App : Application
|
||||||
}
|
}
|
||||||
else if (ApplicationLifetime is ISingleViewApplicationLifetime singleViewPlatform)
|
else if (ApplicationLifetime is ISingleViewApplicationLifetime singleViewPlatform)
|
||||||
{
|
{
|
||||||
|
MainView = new MainView
|
||||||
singleViewPlatform.MainView = new MainView
|
|
||||||
{
|
{
|
||||||
DataContext = new MainViewModel(title)
|
DataContext = new MainViewModel(title)
|
||||||
};
|
};
|
||||||
|
singleViewPlatform.MainView = MainView;
|
||||||
}
|
}
|
||||||
|
|
||||||
base.OnFrameworkInitializationCompleted();
|
base.OnFrameworkInitializationCompleted();
|
||||||
|
@ -138,6 +160,75 @@ public partial class App : Application
|
||||||
|
|
||||||
return new Bitmap(AssetLoader.Open(new Uri("avares://Tesses.CMS.Avalonia/Assets/no-media-icon.png")));
|
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)
|
public static void Log(string text)
|
||||||
{
|
{
|
||||||
|
@ -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;}
|
bool PlatformUsesNormalPathsForDownload {get;}
|
||||||
|
|
||||||
IVirtualFilesystem? DownloadFilesystem {get;}
|
IVirtualFilesystem? DownloadFilesystem {get;}
|
||||||
|
bool CanTakeScreenShots { get; }
|
||||||
|
string ScreenshotPath { get; }
|
||||||
|
bool MustMoveScreenshot { get; }
|
||||||
|
|
||||||
Task WriteConfigurationAsync();
|
Task WriteConfigurationAsync();
|
||||||
|
|
||||||
|
@ -21,6 +24,8 @@ public interface IPlatform
|
||||||
void SetMediaPlayer(Control control, MediaPlayer? mediaPlayer);
|
void SetMediaPlayer(Control control, MediaPlayer? mediaPlayer);
|
||||||
|
|
||||||
MediaPlayer? GetMediaPlayer(Control control);
|
MediaPlayer? GetMediaPlayer(Control control);
|
||||||
|
|
||||||
|
void LaunchUrl(string url);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -38,6 +43,12 @@ public class NullPlatform : IPlatform
|
||||||
|
|
||||||
public Configuration Configuration => _conf;
|
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()
|
public async Task BrowseForDownloadDirectoryAsync()
|
||||||
{
|
{
|
||||||
await Task.CompletedTask;
|
await Task.CompletedTask;
|
||||||
|
@ -67,4 +78,9 @@ public class NullPlatform : IPlatform
|
||||||
{
|
{
|
||||||
return null;
|
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;
|
namespace Tesses.CMS.Avalonia.ViewModels;
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Collections.ObjectModel;
|
||||||
using CommunityToolkit.Mvvm.ComponentModel;
|
using CommunityToolkit.Mvvm.ComponentModel;
|
||||||
using CommunityToolkit.Mvvm.Input;
|
using CommunityToolkit.Mvvm.Input;
|
||||||
using CommunityToolkit.Mvvm.Messaging;
|
using CommunityToolkit.Mvvm.Messaging;
|
||||||
using LibVLCSharp.Shared;
|
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");
|
foreach(var item in App.EnumerateDownloads())
|
||||||
|
{
|
||||||
Player=new MediaPlayer(new Media(vlc,"https://tytdarchive.site.tesses.net/content/PreMuxed/PzUKeGZiEl0.mp4",FromType.FromLocation));
|
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]
|
[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;
|
namespace Tesses.CMS.Avalonia.ViewModels;
|
||||||
|
|
||||||
|
using System.Collections.ObjectModel;
|
||||||
|
using System.Threading.Tasks;
|
||||||
using CommunityToolkit.Mvvm.ComponentModel;
|
using CommunityToolkit.Mvvm.ComponentModel;
|
||||||
using CommunityToolkit.Mvvm.Input;
|
using CommunityToolkit.Mvvm.Input;
|
||||||
using CommunityToolkit.Mvvm.Messaging;
|
using CommunityToolkit.Mvvm.Messaging;
|
||||||
|
using Tesses.CMS.Avalonia.Models;
|
||||||
|
using Tesses.CMS.Avalonia.Views;
|
||||||
|
|
||||||
public partial class FavoritesPageViewModel : ViewModelBase
|
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;
|
namespace Tesses.CMS.Avalonia.ViewModels;
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Threading.Tasks;
|
||||||
using CommunityToolkit.Mvvm.ComponentModel;
|
using CommunityToolkit.Mvvm.ComponentModel;
|
||||||
using CommunityToolkit.Mvvm.Input;
|
using CommunityToolkit.Mvvm.Input;
|
||||||
using CommunityToolkit.Mvvm.Messaging;
|
using CommunityToolkit.Mvvm.Messaging;
|
||||||
|
using Tesses.CMS.Avalonia.Models;
|
||||||
using Tesses.CMS.Avalonia.ViewModels.HomePages;
|
using Tesses.CMS.Avalonia.ViewModels.HomePages;
|
||||||
|
using Tesses.CMS.Client;
|
||||||
|
using Tesses.VirtualFilesystem;
|
||||||
|
using Tesses.VirtualFilesystem.Extensions;
|
||||||
|
|
||||||
public partial class HomePageViewModel : ViewModelBase
|
public partial class HomePageViewModel : ViewModelBase
|
||||||
{
|
{
|
||||||
public HomePageViewModel()
|
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]
|
[RelayCommand]
|
||||||
private void Back()
|
private void Back()
|
||||||
|
@ -26,4 +47,51 @@ public partial class HomePageViewModel : ViewModelBase
|
||||||
//can't go back
|
//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.ComponentModel;
|
||||||
using CommunityToolkit.Mvvm.Input;
|
using CommunityToolkit.Mvvm.Input;
|
||||||
using CommunityToolkit.Mvvm.Messaging;
|
using CommunityToolkit.Mvvm.Messaging;
|
||||||
|
using global::Avalonia.Controls;
|
||||||
using Tesses.CMS.Avalonia.Models;
|
using Tesses.CMS.Avalonia.Models;
|
||||||
using Tesses.CMS.Avalonia.Views.HomePages;
|
using Tesses.CMS.Avalonia.Views.HomePages;
|
||||||
using Tesses.CMS.Client;
|
using Tesses.CMS.Client;
|
||||||
|
|
||||||
public partial class HomeMovieListPageViewModel : ViewModelBase, IBackable
|
public partial class HomeMovieListPageViewModel : ViewModelBase, IBackable
|
||||||
{
|
{
|
||||||
HomeUserPageViewModel homePage;
|
HomePageViewModel homePage;
|
||||||
public HomeMovieListPageViewModel(HomeUserPageViewModel homePage)
|
HomeUserPageViewModel userPage;
|
||||||
|
public HomeMovieListPageViewModel(HomePageViewModel homePage,HomeUserPageViewModel userPage)
|
||||||
{
|
{
|
||||||
App.Log("In HomeMovieListPageViewModel::ctor block begin");
|
App.Log("In HomeMovieListPageViewModel::ctor block begin");
|
||||||
this.homePage = homePage;
|
this.homePage = homePage;
|
||||||
|
this.userPage = userPage;
|
||||||
Task.Run(async()=>{
|
Task.Run(async()=>{
|
||||||
App.Log("In HomeMovieListPageViewModel::ctor::async block begin");
|
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{
|
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)
|
}catch(Exception ex)
|
||||||
{
|
{
|
||||||
App.Log(ex.ToString());
|
App.Log(ex.ToString());
|
||||||
|
@ -42,12 +45,15 @@ public partial class HomeMovieListPageViewModel : ViewModelBase, IBackable
|
||||||
{
|
{
|
||||||
if (value is null) return;
|
if (value is null) return;
|
||||||
SelectedListItem=null;
|
SelectedListItem=null;
|
||||||
|
homePage.CurrentPage = new HomeMoviePageViewModel(homePage,this,userPage.Account,value);
|
||||||
//homePage.CurrentPage = new HomeUserPageViewModel(homePage,this,value);
|
//homePage.CurrentPage = new HomeUserPageViewModel(homePage,this,value);
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public ViewModelBase Back()
|
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;
|
SelectedListItem=null;
|
||||||
homePage.CurrentPage = new HomeUserPageViewModel(homePage,this,value);
|
homePage.CurrentPage = new HomeUserPageViewModel(homePage,this,value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
|
@ -28,6 +28,9 @@ public partial class HomeUserPageViewModel : ViewModelBase, IBackable
|
||||||
|
|
||||||
public string Username => account.Username;
|
public string Username => account.Username;
|
||||||
|
|
||||||
|
public string ProperName => account.ProperName;
|
||||||
|
|
||||||
|
public UserAccount Account => account;
|
||||||
[ObservableProperty]
|
[ObservableProperty]
|
||||||
private ObservableCollection<UserPageItem> _userItems=new ObservableCollection<UserPageItem>();
|
private ObservableCollection<UserPageItem> _userItems=new ObservableCollection<UserPageItem>();
|
||||||
|
|
||||||
|
@ -36,7 +39,8 @@ public partial class HomeUserPageViewModel : ViewModelBase, IBackable
|
||||||
this.homePage = homePage;
|
this.homePage = homePage;
|
||||||
this.userList = userList;
|
this.userList = userList;
|
||||||
this.account = account;
|
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]
|
[ObservableProperty]
|
||||||
private UserPageItem? _selectedListItem;
|
private UserPageItem? _selectedListItem;
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
namespace Tesses.CMS.Avalonia.ViewModels;
|
namespace Tesses.CMS.Avalonia.ViewModels;
|
||||||
|
|
||||||
|
using System;
|
||||||
using System.Collections.ObjectModel;
|
using System.Collections.ObjectModel;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using CommunityToolkit.Mvvm.ComponentModel;
|
using CommunityToolkit.Mvvm.ComponentModel;
|
||||||
|
@ -11,24 +12,46 @@ public partial class MainViewModel : ViewModelBase
|
||||||
{
|
{
|
||||||
public MainViewModel(string title)
|
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)));
|
_pages.Add(new Page("Settings",()=>new SettingsPageViewModel(this)));
|
||||||
SelectedListItem = Pages.First();
|
SelectedListItem = Pages.First();
|
||||||
this.Title = title;
|
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]
|
[ObservableProperty]
|
||||||
private bool _paneOpen=true;
|
private bool _paneOpen=true;
|
||||||
[ObservableProperty]
|
private ViewModelBase _curPage=new HomePageViewModel();
|
||||||
private ViewModelBase _currentPage = 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]
|
[ObservableProperty]
|
||||||
private ObservableCollection<Page> _pages=new ObservableCollection<Page>(){
|
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()),
|
|
||||||
|
|
||||||
};
|
|
||||||
|
|
||||||
[ObservableProperty]
|
[ObservableProperty]
|
||||||
private Page? _selectedListItem;
|
private Page? _selectedListItem;
|
||||||
[ObservableProperty]
|
[ObservableProperty]
|
||||||
|
@ -37,10 +60,12 @@ public partial class MainViewModel : ViewModelBase
|
||||||
[ObservableProperty]
|
[ObservableProperty]
|
||||||
private string _title;
|
private string _title;
|
||||||
|
|
||||||
|
public bool Setting {get;set;}
|
||||||
|
|
||||||
partial void OnSelectedListItemChanged(Page? value)
|
partial void OnSelectedListItemChanged(Page? value)
|
||||||
{
|
{
|
||||||
if (value is null) return;
|
if (value is null) return;
|
||||||
|
if(Setting) return;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@ -57,4 +82,9 @@ public partial class MainViewModel : ViewModelBase
|
||||||
{
|
{
|
||||||
App.Log("Login button");
|
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
|
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) -->
|
to set the actual DataContext for runtime, set the DataContext property in code (look at App.axaml.cs) -->
|
||||||
<vm:DownloadsPageViewModel />
|
<vm:DownloadsPageViewModel />
|
||||||
</Design.DataContext>
|
</Design.DataContext>
|
||||||
|
<ScrollViewer>
|
||||||
<v:VideoPlayerWrapper MediaPlayer="{Binding Player}" />
|
<ItemsControl ItemsSource="{Binding Downloads}">
|
||||||
|
<ItemsControl.ItemTemplate>
|
||||||
|
<DataTemplate>
|
||||||
|
<TransitioningContentControl Content="{Binding Item}" />
|
||||||
|
</DataTemplate>
|
||||||
|
</ItemsControl.ItemTemplate>
|
||||||
|
</ItemsControl>
|
||||||
|
</ScrollViewer>
|
||||||
</UserControl>
|
</UserControl>
|
||||||
|
|
|
@ -11,5 +11,20 @@
|
||||||
to set the actual DataContext for runtime, set the DataContext property in code (look at App.axaml.cs) -->
|
to set the actual DataContext for runtime, set the DataContext property in code (look at App.axaml.cs) -->
|
||||||
<vm:FavoritesPageViewModel />
|
<vm:FavoritesPageViewModel />
|
||||||
</Design.DataContext>
|
</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>
|
</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;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
|
using Avalonia;
|
||||||
using Avalonia.Controls;
|
using Avalonia.Controls;
|
||||||
|
using Avalonia.Data;
|
||||||
using Avalonia.Threading;
|
using Avalonia.Threading;
|
||||||
using LibVLCSharp.Avalonia;
|
|
||||||
using LibVLCSharp.Shared;
|
using LibVLCSharp.Shared;
|
||||||
using Tesses.CMS.Avalonia;
|
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
|
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()
|
public VideoPlayer()
|
||||||
{
|
{
|
||||||
view=new VideoView();
|
view=App.Platform.CreatePlayer();
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@ -23,12 +58,19 @@ public class VideoPlayer : Grid
|
||||||
ss.Content = "SS";
|
ss.Content = "SS";
|
||||||
ss.Click += (sender,e)=>{
|
ss.Click += (sender,e)=>{
|
||||||
//take screenshot
|
//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);
|
Directory.CreateDirectory(realDir);
|
||||||
if(MediaPlayer == null) return;
|
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");
|
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);
|
MediaPlayer.TakeSnapshot(0,name,0,0);
|
||||||
|
if(App.Platform.MustMoveScreenshot)
|
||||||
|
App.Platform.MoveScreenshot(name);
|
||||||
|
App.Log("Screenshot taken");
|
||||||
};
|
};
|
||||||
slider = new Slider();
|
slider = new Slider();
|
||||||
slider.Minimum = 0;
|
slider.Minimum = 0;
|
||||||
|
@ -57,6 +99,8 @@ public class VideoPlayer : Grid
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
seekPanel.ColumnDefinitions.Add(new ColumnDefinition(GridLength.Auto));
|
seekPanel.ColumnDefinitions.Add(new ColumnDefinition(GridLength.Auto));
|
||||||
seekPanel.ColumnDefinitions.Add(new ColumnDefinition(GridLength.Star));
|
seekPanel.ColumnDefinitions.Add(new ColumnDefinition(GridLength.Star));
|
||||||
seekPanel.ColumnDefinitions.Add(new ColumnDefinition(GridLength.Auto));
|
seekPanel.ColumnDefinitions.Add(new ColumnDefinition(GridLength.Auto));
|
||||||
|
@ -71,23 +115,24 @@ public class VideoPlayer : Grid
|
||||||
}
|
}
|
||||||
Slider slider;
|
Slider slider;
|
||||||
Button playBtn;
|
Button playBtn;
|
||||||
VideoView view;
|
Control view;
|
||||||
public MediaPlayer? MediaPlayer
|
public MediaPlayer? MediaPlayer
|
||||||
{
|
{
|
||||||
get=>view.MediaPlayer;
|
get=>App.Platform.GetMediaPlayer(view);
|
||||||
set{
|
set{
|
||||||
if(view.MediaPlayer != null)
|
var mp = App.Platform.GetMediaPlayer(view);
|
||||||
|
if(mp != null)
|
||||||
{
|
{
|
||||||
view.MediaPlayer.Paused -= Paused;
|
mp.Paused -= Paused;
|
||||||
view.MediaPlayer.Playing -= Playing;
|
mp.Playing -= Playing;
|
||||||
view.MediaPlayer.PositionChanged -= PositionChanged;
|
mp.PositionChanged -= PositionChanged;
|
||||||
}
|
}
|
||||||
view.MediaPlayer=value;
|
App.Platform.SetMediaPlayer(view,value);
|
||||||
if(view.MediaPlayer != null)
|
if(value != null)
|
||||||
{
|
{
|
||||||
view.MediaPlayer.Paused += Paused;
|
value.Paused += Paused;
|
||||||
view.MediaPlayer.Playing += Playing;
|
value.Playing += Playing;
|
||||||
view.MediaPlayer.PositionChanged += PositionChanged;
|
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>
|
<ItemGroup>
|
||||||
<PackageReference Include="CommandLineParser" Version="2.9.1" />
|
<PackageReference Include="CommandLineParser" Version="2.9.1" />
|
||||||
<PackageReference Include="ReadLine" Version="2.0.1" />
|
<PackageReference Include="ReadLine" Version="2.0.1" />
|
||||||
|
<PackageReference Include="Packaging.Targets">
|
||||||
|
<Version>0.1.226-*</Version>
|
||||||
|
<PrivateAssets>all</PrivateAssets>
|
||||||
|
</PackageReference>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<OutputType>Exe</OutputType>
|
<OutputType>Exe</OutputType>
|
||||||
<TargetFramework>net7.0</TargetFramework>
|
<TargetFramework>net8.0</TargetFramework>
|
||||||
<ImplicitUsings>enable</ImplicitUsings>
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
<Nullable>enable</Nullable>
|
<Nullable>enable</Nullable>
|
||||||
|
<AssemblyName>tcms-cli</AssemblyName>
|
||||||
|
<HomePage>https://tesses.net/apps/TessesCMS</HomePage>
|
||||||
|
<Maintainer>Mike Nolan <tesses@tesses.net></Maintainer>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
|
|
|
@ -151,6 +151,7 @@ namespace Tesses.CMS.Client
|
||||||
byte[] buffer=new byte[1024];
|
byte[] buffer=new byte[1024];
|
||||||
|
|
||||||
using(var srcStrm = await resp.Content.ReadAsStreamAsync())
|
using(var srcStrm = await resp.Content.ReadAsStreamAsync())
|
||||||
|
{
|
||||||
do {
|
do {
|
||||||
if(token.IsCancellationRequested) return;
|
if(token.IsCancellationRequested) return;
|
||||||
read = await srcStrm.ReadAsync(buffer,0,buffer.Length,token);
|
read = await srcStrm.ReadAsync(buffer,0,buffer.Length,token);
|
||||||
|
@ -158,8 +159,9 @@ namespace Tesses.CMS.Client
|
||||||
await dest.WriteAsync(buffer,0,read,token);
|
await dest.WriteAsync(buffer,0,read,token);
|
||||||
offset += read;
|
offset += read;
|
||||||
if(total > 0)
|
if(total > 0)
|
||||||
progress?.Report((double)offset / (double)read);
|
progress?.Report((double)offset / (double)total);
|
||||||
} while(read>0);
|
} while(read>0);
|
||||||
|
}
|
||||||
resp.Dispose();
|
resp.Dispose();
|
||||||
}
|
}
|
||||||
public ShowClient Shows => new ShowClient(this);
|
public ShowClient Shows => new ShowClient(this);
|
||||||
|
@ -356,7 +358,18 @@ namespace Tesses.CMS.Client
|
||||||
yield return new ShowWithSeasonsAndEpisodes(show,seasons);
|
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
|
public class MovieClient
|
||||||
|
|
|
@ -1,8 +1,38 @@
|
||||||
using System;
|
using System;
|
||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
|
using System.Collections.Generic;
|
||||||
namespace Tesses.CMS.Client
|
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
|
public class Show
|
||||||
{
|
{
|
||||||
[JsonProperty("proper_name")]
|
[JsonProperty("proper_name")]
|
||||||
|
@ -17,4 +47,4 @@ namespace Tesses.CMS.Client
|
||||||
[JsonProperty("description")]
|
[JsonProperty("description")]
|
||||||
public string Description {get;set;}="";
|
public string Description {get;set;}="";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,14 +5,35 @@
|
||||||
<TargetFramework>net8.0</TargetFramework>
|
<TargetFramework>net8.0</TargetFramework>
|
||||||
<ImplicitUsings>enable</ImplicitUsings>
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
<Nullable>enable</Nullable>
|
<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>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Microsoft.SyndicationFeed.ReaderWriter" Version="1.0.2" />
|
<PackageReference Include="Microsoft.SyndicationFeed.ReaderWriter" Version="1.0.2" />
|
||||||
<PackageReference Include="PlaylistsNET" Version="1.4.0" />
|
<PackageReference Include="PlaylistsNET" Version="1.4.0" />
|
||||||
<PackageReference Include="Tesses.WebServer.EasyServer" Version="1.0.1" />
|
<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>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ProjectReference Include="..\Tesses.CMS\Tesses.CMS.csproj" />
|
<ProjectReference Include="..\Tesses.CMS\Tesses.CMS.csproj" />
|
||||||
<ProjectReference Include="..\Tesses.CMS.Providers.LiteDb\Tesses.CMS.Providers.LiteDb.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]
|
[JsonIgnore]
|
||||||
public long UserId {get;set;}
|
public long UserId {get;set;}
|
||||||
[JsonProperty("creation_time")]
|
[JsonProperty("creation_time")]
|
||||||
public DateTime CreationTime {get;set;}
|
public DateTime CreationTime {get;set;}=DateTime.Now;
|
||||||
[JsonProperty("last_updated_time")]
|
[JsonProperty("last_updated_time")]
|
||||||
public DateTime LastUpdated {get;set;}
|
public DateTime LastUpdated {get;set;}=DateTime.Now;
|
||||||
[JsonProperty("description")]
|
[JsonProperty("description")]
|
||||||
public string Description {get;set;}
|
public string Description {get;set;}
|
||||||
|
|
||||||
|
|
|
@ -22,9 +22,9 @@ namespace Tesses.CMS
|
||||||
[JsonIgnore]
|
[JsonIgnore]
|
||||||
public long UserId {get;set;}
|
public long UserId {get;set;}
|
||||||
[JsonProperty("creation_time")]
|
[JsonProperty("creation_time")]
|
||||||
public DateTime CreationTime {get;set;}
|
public DateTime CreationTime {get;set;}=DateTime.Now;
|
||||||
[JsonProperty("last_updated_time")]
|
[JsonProperty("last_updated_time")]
|
||||||
public DateTime LastUpdated {get;set;}
|
public DateTime LastUpdated {get;set;}=DateTime.Now;
|
||||||
[JsonProperty("description")]
|
[JsonProperty("description")]
|
||||||
public string Description {get;set;}="";
|
public string Description {get;set;}="";
|
||||||
|
|
||||||
|
|
|
@ -14,10 +14,10 @@ namespace Tesses.CMS
|
||||||
public string Name {get;set;}="";
|
public string Name {get;set;}="";
|
||||||
[JsonIgnore]
|
[JsonIgnore]
|
||||||
public long UserId {get;set;}
|
public long UserId {get;set;}
|
||||||
[JsonProperty("created_time")]
|
[JsonProperty("creation_time")]
|
||||||
public DateTime CreationTime {get;set;}
|
public DateTime CreationTime {get;set;}=DateTime.Now;
|
||||||
[JsonProperty("last_updated_time")]
|
[JsonProperty("last_updated_time")]
|
||||||
public DateTime LastUpdated {get;set;}
|
public DateTime LastUpdated {get;set;}=DateTime.Now;
|
||||||
[JsonProperty("description")]
|
[JsonProperty("description")]
|
||||||
public string Description {get;set;}="";
|
public string Description {get;set;}="";
|
||||||
|
|
||||||
|
|
|
@ -16,9 +16,9 @@ namespace Tesses.CMS
|
||||||
[JsonIgnore]
|
[JsonIgnore]
|
||||||
public long UserId {get;set;}
|
public long UserId {get;set;}
|
||||||
[JsonProperty("creation_time")]
|
[JsonProperty("creation_time")]
|
||||||
public DateTime CreationTime {get;set;}
|
public DateTime CreationTime {get;set;}=DateTime.Now;
|
||||||
[JsonProperty("last_updated_time")]
|
[JsonProperty("last_updated_time")]
|
||||||
public DateTime LastUpdated {get;set;}
|
public DateTime LastUpdated {get;set;}=DateTime.Now;
|
||||||
[JsonProperty("description")]
|
[JsonProperty("description")]
|
||||||
public string Description {get;set;}="";
|
public string Description {get;set;}="";
|
||||||
[JsonProperty("source")]
|
[JsonProperty("source")]
|
||||||
|
|
|
@ -23,7 +23,7 @@ namespace Tesses.CMS
|
||||||
[JsonProperty("platforms")]
|
[JsonProperty("platforms")]
|
||||||
public List<ProjectReleasePlatform> Platforms {get;set;}=new List<ProjectReleasePlatform>();
|
public List<ProjectReleasePlatform> Platforms {get;set;}=new List<ProjectReleasePlatform>();
|
||||||
[JsonProperty("creation_time")]
|
[JsonProperty("creation_time")]
|
||||||
public DateTime CreationTime {get;set;}
|
public DateTime CreationTime {get;set;}=DateTime.Now;
|
||||||
}
|
}
|
||||||
public enum CPUArchitecture
|
public enum CPUArchitecture
|
||||||
{
|
{
|
||||||
|
|
|
@ -18,9 +18,9 @@ namespace Tesses.CMS
|
||||||
[JsonIgnore]
|
[JsonIgnore]
|
||||||
public long UserId {get;set;}
|
public long UserId {get;set;}
|
||||||
[JsonProperty("creation_time")]
|
[JsonProperty("creation_time")]
|
||||||
public DateTime CreationTime {get;set;}
|
public DateTime CreationTime {get;set;}=DateTime.Now;
|
||||||
[JsonProperty("last_updated_time")]
|
[JsonProperty("last_updated_time")]
|
||||||
public DateTime LastUpdated {get;set;}
|
public DateTime LastUpdated {get;set;}=DateTime.Now;
|
||||||
[JsonProperty("description")]
|
[JsonProperty("description")]
|
||||||
public string Description {get;set;}="";
|
public string Description {get;set;}="";
|
||||||
|
|
||||||
|
|
|
@ -15,9 +15,9 @@ namespace Tesses.CMS
|
||||||
[JsonIgnore]
|
[JsonIgnore]
|
||||||
public long UserId {get;set;}
|
public long UserId {get;set;}
|
||||||
[JsonProperty("creation_time")]
|
[JsonProperty("creation_time")]
|
||||||
public DateTime CreationTime {get;set;}
|
public DateTime CreationTime {get;set;} = DateTime.Now;
|
||||||
[JsonProperty("last_updated_time")]
|
[JsonProperty("last_updated_time")]
|
||||||
public DateTime LastUpdated {get;set;}
|
public DateTime LastUpdated {get;set;} = DateTime.Now;
|
||||||
[JsonProperty("description")]
|
[JsonProperty("description")]
|
||||||
public string Description {get;set;}="";
|
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