diff --git a/.gitignore b/.gitignore
index fc25db9..7c43ecd 100644
--- a/.gitignore
+++ b/.gitignore
@@ -484,3 +484,4 @@ $RECYCLE.BIN/
*.swp
help.txt
data/
+out/
\ No newline at end of file
diff --git a/.woodpecker/devel.yml b/.woodpecker/devel.yml
new file mode 100644
index 0000000..51769e7
--- /dev/null
+++ b/.woodpecker/devel.yml
@@ -0,0 +1,8 @@
+steps:
+ - name: devel
+ image: mcr.microsoft.com/dotnet/sdk:8.0
+ when:
+ event: push
+ branch: devel
+ commands:
+ - bash test.sh
\ No newline at end of file
diff --git a/.woodpecker/master.yml b/.woodpecker/master.yml
new file mode 100644
index 0000000..8b736b3
--- /dev/null
+++ b/.woodpecker/master.yml
@@ -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
\ No newline at end of file
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..5640c2a
--- /dev/null
+++ b/README.md
@@ -0,0 +1,4 @@
+TessesCMS
+=========
+
+> :warning: **THIS IS UNDER CONSTRUCTION** don't use this software yet
\ No newline at end of file
diff --git a/Tesses.CMS.Avalonia/Tesses.CMS.Avalonia.Android/MobilePlatform.cs b/Tesses.CMS.Avalonia/Tesses.CMS.Avalonia.Android/MobilePlatform.cs
index fd98eb4..2916c47 100644
--- a/Tesses.CMS.Avalonia/Tesses.CMS.Avalonia.Android/MobilePlatform.cs
+++ b/Tesses.CMS.Avalonia/Tesses.CMS.Avalonia.Android/MobilePlatform.cs
@@ -2,13 +2,61 @@ using System;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
+using Android.Media;
+using Avalonia.Android;
+using Avalonia.Controls;
+using Avalonia.Platform;
using Avalonia.Platform.Storage;
+using LibVLCSharp.Platforms.Android;
using Newtonsoft.Json;
using Tesses.VirtualFilesystem;
using Tesses.VirtualFilesystem.Extensions;
using Tesses.VirtualFilesystem.Filesystems;
namespace Tesses.CMS.Avalonia.Android;
+public class NCH : NativeControlHost
+{
+ LibVLCSharp.Shared.MediaPlayer? mediaPlayer;
+ VideoView? view;
+ protected override IPlatformHandle CreateNativeControlCore(IPlatformHandle parent)
+ {
+ /*return Implementation?.CreateControl(IsSecond, parent, () => base.CreateNativeControlCore(parent))
+ ?? base.CreateNativeControlCore(parent);*/
+ var parentContext = (parent as AndroidViewControlHandle)?.View.Context
+ ?? global::Android.App.Application.Context;
+
+ view = new VideoView(parentContext);
+ view.LayoutParameters = new global::Android.Views.ViewGroup.LayoutParams(global::Android.Views.ViewGroup.LayoutParams.MatchParent,global::Android.Views.ViewGroup.LayoutParams.MatchParent);
+ if(mediaPlayer != null)
+ {
+ view.MediaPlayer = mediaPlayer;
+ }
+ return new AndroidViewControlHandle(view);
+ }
+
+ protected override void DestroyNativeControlCore(IPlatformHandle control)
+ {
+ base.DestroyNativeControlCore(control);
+ }
+
+ public LibVLCSharp.Shared.MediaPlayer? MediaPlayer {
+ get {
+ if(view != null)
+ {
+ return view.MediaPlayer;
+ }
+ return mediaPlayer;
+ }
+ set {
+ if(view != null)
+ {
+ view.MediaPlayer = value;
+
+ }
+ mediaPlayer=value;
+ }
+ }
+}
internal class MobilePlatform : IPlatform
{
string configpath;
@@ -39,7 +87,12 @@ internal class MobilePlatform : IPlatform
internal IVirtualFilesystem? virtualFilesystem;
public IVirtualFilesystem? DownloadFilesystem => virtualFilesystem;
-
+
+ public bool CanTakeScreenShots => DownloadFilesystem != null && !string.IsNullOrWhiteSpace(ScreenshotPath);
+
+ public string ScreenshotPath => activity?.GetExternalCacheDirs()?[0].AbsolutePath ?? "";
+
+ public bool MustMoveScreenshot => true;
public async Task BrowseForDownloadDirectoryAsync()
{
@@ -89,4 +142,31 @@ internal class MobilePlatform : IPlatform
}
+
+ public Control CreatePlayer()
+ {
+ return new NCH();
+ // return new global::Android.Widget.Button(this.activity);
+ }
+
+ public void SetMediaPlayer(Control control, global::LibVLCSharp.Shared.MediaPlayer? mediaPlayer)
+ {
+ var ctrl=control as NCH;
+ if(ctrl != null)
+ ctrl.MediaPlayer = mediaPlayer;
+ }
+
+ public global::LibVLCSharp.Shared.MediaPlayer? GetMediaPlayer(Control control)
+ {
+ var ctrl=control as NCH;
+ if(ctrl != null)
+ return ctrl.MediaPlayer;
+ return null;
+ }
+ public void LaunchUrl(string url)
+ {
+ //https://stackoverflow.com/a/3004542
+ //Intent intent = new Intent(Intent.ActionView);
+
+ }
}
\ No newline at end of file
diff --git a/Tesses.CMS.Avalonia/Tesses.CMS.Avalonia.Android/Tesses.CMS.Avalonia.Android.csproj b/Tesses.CMS.Avalonia/Tesses.CMS.Avalonia.Android/Tesses.CMS.Avalonia.Android.csproj
index d3a34d9..413a0a1 100644
--- a/Tesses.CMS.Avalonia/Tesses.CMS.Avalonia.Android/Tesses.CMS.Avalonia.Android.csproj
+++ b/Tesses.CMS.Avalonia/Tesses.CMS.Avalonia.Android/Tesses.CMS.Avalonia.Android.csproj
@@ -19,7 +19,9 @@
+
+
diff --git a/Tesses.CMS.Avalonia/Tesses.CMS.Avalonia.Desktop/DesktopPlatform.cs b/Tesses.CMS.Avalonia/Tesses.CMS.Avalonia.Desktop/DesktopPlatform.cs
index 7088291..b3d6f29 100644
--- a/Tesses.CMS.Avalonia/Tesses.CMS.Avalonia.Desktop/DesktopPlatform.cs
+++ b/Tesses.CMS.Avalonia/Tesses.CMS.Avalonia.Desktop/DesktopPlatform.cs
@@ -1,3 +1,5 @@
+using System;
+using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
@@ -34,7 +36,12 @@ internal class DesktopPlatform : IPlatform
IVirtualFilesystem? virtualFilesystem;
public IVirtualFilesystem? DownloadFilesystem => virtualFilesystem;
-
+
+ public bool CanTakeScreenShots => true;
+
+ public string ScreenshotPath => DownloadFilesystem == null ? Environment.GetFolderPath(Environment.SpecialFolder.MyPictures) : Path.Combine(Configuration.DownloadPath,"Screenshots");
+
+ public bool MustMoveScreenshot => false;
public async Task BrowseForDownloadDirectoryAsync()
{
@@ -75,21 +82,28 @@ internal class DesktopPlatform : IPlatform
public Control CreatePlayer()
{
- return new VideoPlayer();
+ return new VideoView();
}
public void SetMediaPlayer(Control control, MediaPlayer? mediaPlayer)
{
- var ctrl = control as VideoPlayer;
+ var ctrl = control as VideoView;
if(ctrl != null)
ctrl.MediaPlayer = mediaPlayer;
}
public MediaPlayer? GetMediaPlayer(Control control)
{
- var ctrl = control as VideoPlayer;
+ var ctrl = control as VideoView;
if(ctrl != null)
return ctrl.MediaPlayer;
return null;
}
+ public void LaunchUrl(string url)
+ {
+ Process p = new Process();
+ p.StartInfo.UseShellExecute=true;
+ p.StartInfo.FileName = url;
+ p.Start();
+ }
}
\ No newline at end of file
diff --git a/Tesses.CMS.Avalonia/Tesses.CMS.Avalonia.Desktop/Tesses.CMS.Avalonia.Desktop.csproj b/Tesses.CMS.Avalonia/Tesses.CMS.Avalonia.Desktop/Tesses.CMS.Avalonia.Desktop.csproj
index 44b97dc..8138888 100644
--- a/Tesses.CMS.Avalonia/Tesses.CMS.Avalonia.Desktop/Tesses.CMS.Avalonia.Desktop.csproj
+++ b/Tesses.CMS.Avalonia/Tesses.CMS.Avalonia.Desktop/Tesses.CMS.Avalonia.Desktop.csproj
@@ -6,12 +6,24 @@
net8.0
enable
true
+ tcms-desktop
+ https://tesses.net/apps/TessesCMS
+ Mike Nolan <tesses@tesses.net>
app.manifest
-
+
+
+ /usr/share/applications/tcms-desktop.desktop
+
+
+
+ /usr/share/icons/tcms.png
+
+
+
@@ -20,6 +32,10 @@
+
+ 0.1.226-*
+ all
+
diff --git a/Tesses.CMS/Assets/android-chrome-512x512.png b/Tesses.CMS.Avalonia/Tesses.CMS.Avalonia.Desktop/icon.png
similarity index 100%
rename from Tesses.CMS/Assets/android-chrome-512x512.png
rename to Tesses.CMS.Avalonia/Tesses.CMS.Avalonia.Desktop/icon.png
diff --git a/Tesses.CMS.Avalonia/Tesses.CMS.Avalonia.Desktop/tcms-desktop.desktop b/Tesses.CMS.Avalonia/Tesses.CMS.Avalonia.Desktop/tcms-desktop.desktop
new file mode 100644
index 0000000..48e518d
--- /dev/null
+++ b/Tesses.CMS.Avalonia/Tesses.CMS.Avalonia.Desktop/tcms-desktop.desktop
@@ -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;
\ No newline at end of file
diff --git a/Tesses.CMS.Avalonia/Tesses.CMS.Avalonia/App.axaml.cs b/Tesses.CMS.Avalonia/Tesses.CMS.Avalonia/App.axaml.cs
index c551c12..8d63468 100644
--- a/Tesses.CMS.Avalonia/Tesses.CMS.Avalonia/App.axaml.cs
+++ b/Tesses.CMS.Avalonia/Tesses.CMS.Avalonia/App.axaml.cs
@@ -15,15 +15,23 @@ using Avalonia.Platform;
using Tesses.VirtualFilesystem;
using Tesses.VirtualFilesystem.Extensions;
using System.IO;
+using System.Collections.Generic;
+using System.Threading;
+using Tesses.CMS.Avalonia.Models;
+using Newtonsoft.Json;
+using Newtonsoft.Json.Linq;
namespace Tesses.CMS.Avalonia;
public partial class App : Application
{
+ public static OpenedCtrls OpenedControls {get;}=new OpenedCtrls();
public static IPlatform Platform {get;set;} = new NullPlatform();
public static MainWindow? Window {get;set;}
public static TessesCMSClient Client {get;} = new TessesCMSClient();
+
+ public static MainView? MainView {get;set;}
public override void Initialize()
{
@@ -31,6 +39,20 @@ public partial class App : Application
AvaloniaXamlLoader.Load(this);
}
+ public static List Downloads {get;}=new List();
+
+ public static void AddActiveDownload(ActiveDownload dl)
+ {
+ lock(Downloads)
+ {
+ if(!Downloads.Any(e=>e.Path == dl.Path))
+ {
+ Downloads.Add(dl);
+ dl.Start().Wait(0);
+ }
+ }
+ }
+
public override void OnFrameworkInitializationCompleted()
{
Client.RootUrl = Platform.Configuration.ServerUrl;
@@ -46,11 +68,11 @@ public partial class App : Application
}
else if (ApplicationLifetime is ISingleViewApplicationLifetime singleViewPlatform)
{
-
- singleViewPlatform.MainView = new MainView
+ MainView = new MainView
{
DataContext = new MainViewModel(title)
};
+ singleViewPlatform.MainView = MainView;
}
base.OnFrameworkInitializationCompleted();
@@ -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")));
}
+
+ internal static async Task 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)
{
@@ -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(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 items = new List();
+
+ 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 GetActiveDownloads()
+ {
+ lock(Downloads) {
+ return Downloads.ToList();
+ }
+ }
+
+ internal static IEnumerable 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>(text);
+ if(res != null)
+ foreach(var item in res)
+ yield return item;
+ }
+ internal static IEnumerable 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>(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 items = new List();
+
+ 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 items = new List();
+
+ 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? ProgressChanged=null;
+
+ public event Action? Done = null;
+
+ public void UpdateProgress(double d)
+ {
+
+ Progress = d;
+ ProgressChanged?.Invoke(d);
+ }
+
+ public void SendDone()
+ {
+ Done?.Invoke();
+ }
+
+
+ public Func Start {get;set;}=async()=>{await Task.CompletedTask;};
+}
+
+public class OpenedCtrls
+{
+ List disposables=new List();
+
+ public void Register(IDisposable disposable)
+ {
+ disposables.Add(disposable);
+ }
+
+ public void Destroy()
+ {
+ foreach(var item in disposables) item.Dispose();
+
+ disposables.Clear();
+ }
}
\ No newline at end of file
diff --git a/Tesses.CMS.Avalonia/Tesses.CMS.Avalonia/Assets/avalonia-logo.ico b/Tesses.CMS.Avalonia/Tesses.CMS.Avalonia/Assets/avalonia-logo.ico
index da8d49f..9aad621 100644
Binary files a/Tesses.CMS.Avalonia/Tesses.CMS.Avalonia/Assets/avalonia-logo.ico and b/Tesses.CMS.Avalonia/Tesses.CMS.Avalonia/Assets/avalonia-logo.ico differ
diff --git a/Tesses.CMS.Avalonia/Tesses.CMS.Avalonia/IPlatform.cs b/Tesses.CMS.Avalonia/Tesses.CMS.Avalonia/IPlatform.cs
index fd8c8c5..b2762d5 100644
--- a/Tesses.CMS.Avalonia/Tesses.CMS.Avalonia/IPlatform.cs
+++ b/Tesses.CMS.Avalonia/Tesses.CMS.Avalonia/IPlatform.cs
@@ -11,6 +11,9 @@ public interface IPlatform
bool PlatformUsesNormalPathsForDownload {get;}
IVirtualFilesystem? DownloadFilesystem {get;}
+ bool CanTakeScreenShots { get; }
+ string ScreenshotPath { get; }
+ bool MustMoveScreenshot { get; }
Task WriteConfigurationAsync();
@@ -21,6 +24,8 @@ public interface IPlatform
void SetMediaPlayer(Control control, MediaPlayer? mediaPlayer);
MediaPlayer? GetMediaPlayer(Control control);
+
+ void LaunchUrl(string url);
}
@@ -38,6 +43,12 @@ public class NullPlatform : IPlatform
public Configuration Configuration => _conf;
+ public bool CanTakeScreenShots => false;
+
+ public string ScreenshotPath => throw new System.NotImplementedException();
+
+ public bool MustMoveScreenshot => throw new System.NotImplementedException();
+
public async Task BrowseForDownloadDirectoryAsync()
{
await Task.CompletedTask;
@@ -67,4 +78,9 @@ public class NullPlatform : IPlatform
{
return null;
}
+
+ public void LaunchUrl(string url)
+ {
+
+ }
}
\ No newline at end of file
diff --git a/Tesses.CMS.Avalonia/Tesses.CMS.Avalonia/Models/FavoritePageItem.cs b/Tesses.CMS.Avalonia/Tesses.CMS.Avalonia/Models/FavoritePageItem.cs
new file mode 100644
index 0000000..435f2b9
--- /dev/null
+++ b/Tesses.CMS.Avalonia/Tesses.CMS.Avalonia/Models/FavoritePageItem.cs
@@ -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 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;}
+}
\ No newline at end of file
diff --git a/Tesses.CMS.Avalonia/Tesses.CMS.Avalonia/Models/FavoritesItem.cs b/Tesses.CMS.Avalonia/Tesses.CMS.Avalonia/Models/FavoritesItem.cs
new file mode 100644
index 0000000..37d01e5
--- /dev/null
+++ b/Tesses.CMS.Avalonia/Tesses.CMS.Avalonia/Models/FavoritesItem.cs
@@ -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;
+ }
+
+}
\ No newline at end of file
diff --git a/Tesses.CMS.Avalonia/Tesses.CMS.Avalonia/Models/ShowItem.cs b/Tesses.CMS.Avalonia/Tesses.CMS.Avalonia/Models/ShowItem.cs
new file mode 100644
index 0000000..e611f56
--- /dev/null
+++ b/Tesses.CMS.Avalonia/Tesses.CMS.Avalonia/Models/ShowItem.cs
@@ -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;
+}
\ No newline at end of file
diff --git a/Tesses.CMS.Avalonia/Tesses.CMS.Avalonia/ViewModels/ActiveDownloadViewModel.cs b/Tesses.CMS.Avalonia/Tesses.CMS.Avalonia/ViewModels/ActiveDownloadViewModel.cs
new file mode 100644
index 0000000..59b1e0a
--- /dev/null
+++ b/Tesses.CMS.Avalonia/Tesses.CMS.Avalonia/ViewModels/ActiveDownloadViewModel.cs
@@ -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();
+ });
+ }
+}
\ No newline at end of file
diff --git a/Tesses.CMS.Avalonia/Tesses.CMS.Avalonia/ViewModels/DownloadsPageViewModel.cs b/Tesses.CMS.Avalonia/Tesses.CMS.Avalonia/ViewModels/DownloadsPageViewModel.cs
index 6fce003..424f357 100644
--- a/Tesses.CMS.Avalonia/Tesses.CMS.Avalonia/ViewModels/DownloadsPageViewModel.cs
+++ b/Tesses.CMS.Avalonia/Tesses.CMS.Avalonia/ViewModels/DownloadsPageViewModel.cs
@@ -1,19 +1,55 @@
namespace Tesses.CMS.Avalonia.ViewModels;
+
+using System;
+using System.Collections.ObjectModel;
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using CommunityToolkit.Mvvm.Messaging;
using LibVLCSharp.Shared;
-public partial class DownloadsPageViewModel : ViewModelBase
+public partial class DownloadsPageViewModel : ViewModelBase, IDisposable
{
- public DownloadsPageViewModel()
+ public DownloadsPageViewModel(MainViewModel mvm)
{
- LibVLC vlc=new LibVLC("--input-repeat=65535");
-
- Player=new MediaPlayer(new Media(vlc,"https://tytdarchive.site.tesses.net/content/PreMuxed/PzUKeGZiEl0.mp4",FromType.FromLocation));
-
-
+ foreach(var item in App.EnumerateDownloads())
+ {
+ ViewModelBaseItem vmbi=new ViewModelBaseItem();
+ vmbi.Item = new SavedDownloadViewModel(item,mvm);
+ Downloads.Add(vmbi);
+ }
+ foreach(var item in App.GetActiveDownloads())
+ {
+ var vmb=new ViewModelBaseItem();
+ vmb.Item = new ActiveDownloadViewModel(()=>{
+ if(Downloads.Contains(vmb)) Downloads.Remove(vmb);
+ },item);
+ Downloads.Add(vmb);
+ }
}
[ObservableProperty]
- private MediaPlayer? _player;
+
+ private ObservableCollection _downloads=new ObservableCollection();
+
+ 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();
+ }
}
\ No newline at end of file
diff --git a/Tesses.CMS.Avalonia/Tesses.CMS.Avalonia/ViewModels/FavoritesPageViewModel.cs b/Tesses.CMS.Avalonia/Tesses.CMS.Avalonia/ViewModels/FavoritesPageViewModel.cs
index 3a6f72e..64dc894 100644
--- a/Tesses.CMS.Avalonia/Tesses.CMS.Avalonia/ViewModels/FavoritesPageViewModel.cs
+++ b/Tesses.CMS.Avalonia/Tesses.CMS.Avalonia/ViewModels/FavoritesPageViewModel.cs
@@ -1,8 +1,33 @@
namespace Tesses.CMS.Avalonia.ViewModels;
+
+using System.Collections.ObjectModel;
+using System.Threading.Tasks;
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using CommunityToolkit.Mvvm.Messaging;
+using Tesses.CMS.Avalonia.Models;
+using Tesses.CMS.Avalonia.Views;
+
public partial class FavoritesPageViewModel : ViewModelBase
{
+ MainViewModel mvm;
+ public FavoritesPageViewModel(MainViewModel mvm)
+ {
+ this.mvm=mvm;
+ Favorites.Clear();
+
+ foreach(var item in App.EnumerateFavorites())
+ {
+ Favorites.Add(new FavoritesPageItem(item,async (fpg)=>{
+ HomePageViewModel hpvm=new HomePageViewModel();
+ await hpvm.NavigateToAsync(item);
+ mvm.SetHome();
+ mvm.CurrentPage = hpvm;
+ }));
+ }
+
+
+ }
+ public ObservableCollection Favorites {get;}=new ObservableCollection();
}
\ No newline at end of file
diff --git a/Tesses.CMS.Avalonia/Tesses.CMS.Avalonia/ViewModels/HomePageViewModel.cs b/Tesses.CMS.Avalonia/Tesses.CMS.Avalonia/ViewModels/HomePageViewModel.cs
index 21829da..1b9b626 100644
--- a/Tesses.CMS.Avalonia/Tesses.CMS.Avalonia/ViewModels/HomePageViewModel.cs
+++ b/Tesses.CMS.Avalonia/Tesses.CMS.Avalonia/ViewModels/HomePageViewModel.cs
@@ -1,17 +1,38 @@
namespace Tesses.CMS.Avalonia.ViewModels;
+
+using System;
+using System.Threading.Tasks;
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using CommunityToolkit.Mvvm.Messaging;
+using Tesses.CMS.Avalonia.Models;
using Tesses.CMS.Avalonia.ViewModels.HomePages;
+using Tesses.CMS.Client;
+using Tesses.VirtualFilesystem;
+using Tesses.VirtualFilesystem.Extensions;
public partial class HomePageViewModel : ViewModelBase
{
public HomePageViewModel()
{
- _currentPage = new HomeUserListPageViewModel(this);
+ _curPage = new HomeUserListPageViewModel(this);
+ }
+ private ViewModelBase _curPage;
+ public ViewModelBase CurrentPage
+ {
+ get
+ {
+ return _curPage;
+ }
+ set{
+ App.OpenedControls.Destroy();
+ _curPage = value;
+ var disp = value as IDisposable;
+ if(disp != null) App.OpenedControls.Register(disp);
+ OnPropertyChanged(nameof(CurrentPage));
+
+ }
}
- [ObservableProperty]
- private ViewModelBase _currentPage;
[RelayCommand]
private void Back()
@@ -26,4 +47,51 @@ public partial class HomePageViewModel : ViewModelBase
//can't go back
}
}
+
+ public async Task NavigateToAsync(FavoritesItem item)
+ {
+ switch(item.Type)
+ {
+ case FavoriteType.Movie:
+ var homeUserList = new HomeUserListPageViewModel(this);
+
+
+
+ var homeUser = new HomeUserPageViewModel(this,homeUserList,item.Account);
+
+ var homeMovieList = new HomeMovieListPageViewModel(this,homeUser);
+ Movie movie = item.Media.ToObject() ?? 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() ?? 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;
+ }
+ }
}
\ No newline at end of file
diff --git a/Tesses.CMS.Avalonia/Tesses.CMS.Avalonia/ViewModels/HomePages/HomeMovieListPageViewModel.cs b/Tesses.CMS.Avalonia/Tesses.CMS.Avalonia/ViewModels/HomePages/HomeMovieListPageViewModel.cs
index b610270..6c77401 100644
--- a/Tesses.CMS.Avalonia/Tesses.CMS.Avalonia/ViewModels/HomePages/HomeMovieListPageViewModel.cs
+++ b/Tesses.CMS.Avalonia/Tesses.CMS.Avalonia/ViewModels/HomePages/HomeMovieListPageViewModel.cs
@@ -6,24 +6,27 @@ using System.Threading.Tasks;
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using CommunityToolkit.Mvvm.Messaging;
+using global::Avalonia.Controls;
using Tesses.CMS.Avalonia.Models;
using Tesses.CMS.Avalonia.Views.HomePages;
using Tesses.CMS.Client;
public partial class HomeMovieListPageViewModel : ViewModelBase, IBackable
{
- HomeUserPageViewModel homePage;
- public HomeMovieListPageViewModel(HomeUserPageViewModel homePage)
+ HomePageViewModel homePage;
+ HomeUserPageViewModel userPage;
+ public HomeMovieListPageViewModel(HomePageViewModel homePage,HomeUserPageViewModel userPage)
{
App.Log("In HomeMovieListPageViewModel::ctor block begin");
this.homePage = homePage;
+ this.userPage = userPage;
Task.Run(async()=>{
App.Log("In HomeMovieListPageViewModel::ctor::async block begin");
- await foreach(var item in App.Client.Movies.GetMoviesAsync(homePage.Username))
+ await foreach(var item in App.Client.Movies.GetMoviesAsync(userPage.Username))
{
try{
- _movies.Add(new MovieItem(item,await App.GetMovieThumbnailAsync(homePage.Username,item.Name)));
+ _movies.Add(new MovieItem(item,await App.GetMovieThumbnailAsync(userPage.Username,item.Name)));
}catch(Exception ex)
{
App.Log(ex.ToString());
@@ -42,12 +45,15 @@ public partial class HomeMovieListPageViewModel : ViewModelBase, IBackable
{
if (value is null) return;
SelectedListItem=null;
+ homePage.CurrentPage = new HomeMoviePageViewModel(homePage,this,userPage.Account,value);
//homePage.CurrentPage = new HomeUserPageViewModel(homePage,this,value);
+
+
}
public ViewModelBase Back()
{
- return homePage;
+ return userPage;
}
}
\ No newline at end of file
diff --git a/Tesses.CMS.Avalonia/Tesses.CMS.Avalonia/ViewModels/HomePages/HomeMoviePageViewModel.cs b/Tesses.CMS.Avalonia/Tesses.CMS.Avalonia/ViewModels/HomePages/HomeMoviePageViewModel.cs
new file mode 100644
index 0000000..17e529d
--- /dev/null
+++ b/Tesses.CMS.Avalonia/Tesses.CMS.Avalonia/ViewModels/HomePages/HomeMoviePageViewModel.cs
@@ -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;
+ }
+}
\ No newline at end of file
diff --git a/Tesses.CMS.Avalonia/Tesses.CMS.Avalonia/ViewModels/HomePages/HomeMovieVideoPlayerViewModel.cs b/Tesses.CMS.Avalonia/Tesses.CMS.Avalonia/ViewModels/HomePages/HomeMovieVideoPlayerViewModel.cs
new file mode 100644
index 0000000..c942b33
--- /dev/null
+++ b/Tesses.CMS.Avalonia/Tesses.CMS.Avalonia/ViewModels/HomePages/HomeMovieVideoPlayerViewModel.cs
@@ -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();
+ }
+}
\ No newline at end of file
diff --git a/Tesses.CMS.Avalonia/Tesses.CMS.Avalonia/ViewModels/HomePages/HomeShowListPageViewModel.cs b/Tesses.CMS.Avalonia/Tesses.CMS.Avalonia/ViewModels/HomePages/HomeShowListPageViewModel.cs
new file mode 100644
index 0000000..156d4a2
--- /dev/null
+++ b/Tesses.CMS.Avalonia/Tesses.CMS.Avalonia/ViewModels/HomePages/HomeShowListPageViewModel.cs
@@ -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 _shows=new ObservableCollection();
+ [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;
+ }
+
+}
\ No newline at end of file
diff --git a/Tesses.CMS.Avalonia/Tesses.CMS.Avalonia/ViewModels/HomePages/HomeUserListPageViewModel.cs b/Tesses.CMS.Avalonia/Tesses.CMS.Avalonia/ViewModels/HomePages/HomeUserListPageViewModel.cs
index 6a0eb4f..a1faf11 100644
--- a/Tesses.CMS.Avalonia/Tesses.CMS.Avalonia/ViewModels/HomePages/HomeUserListPageViewModel.cs
+++ b/Tesses.CMS.Avalonia/Tesses.CMS.Avalonia/ViewModels/HomePages/HomeUserListPageViewModel.cs
@@ -34,4 +34,6 @@ public partial class HomeUserListPageViewModel : ViewModelBase
SelectedListItem=null;
homePage.CurrentPage = new HomeUserPageViewModel(homePage,this,value);
}
+
+
}
\ No newline at end of file
diff --git a/Tesses.CMS.Avalonia/Tesses.CMS.Avalonia/ViewModels/HomePages/HomeUserPageViewModel.cs b/Tesses.CMS.Avalonia/Tesses.CMS.Avalonia/ViewModels/HomePages/HomeUserPageViewModel.cs
index dd77db7..a7693e0 100644
--- a/Tesses.CMS.Avalonia/Tesses.CMS.Avalonia/ViewModels/HomePages/HomeUserPageViewModel.cs
+++ b/Tesses.CMS.Avalonia/Tesses.CMS.Avalonia/ViewModels/HomePages/HomeUserPageViewModel.cs
@@ -28,6 +28,9 @@ public partial class HomeUserPageViewModel : ViewModelBase, IBackable
public string Username => account.Username;
+ public string ProperName => account.ProperName;
+
+ public UserAccount Account => account;
[ObservableProperty]
private ObservableCollection _userItems=new ObservableCollection();
@@ -36,7 +39,8 @@ public partial class HomeUserPageViewModel : ViewModelBase, IBackable
this.homePage = homePage;
this.userList = userList;
this.account = account;
- UserItems.Add(new UserPageItem("Movies",new HomeMovieListPageViewModel(this)));
+ UserItems.Add(new UserPageItem("Movies",new HomeMovieListPageViewModel(homePage,this)));
+ UserItems.Add(new UserPageItem("Shows",new HomeShowListPageViewModel(homePage,this)));
}
[ObservableProperty]
private UserPageItem? _selectedListItem;
diff --git a/Tesses.CMS.Avalonia/Tesses.CMS.Avalonia/ViewModels/MainViewModel.cs b/Tesses.CMS.Avalonia/Tesses.CMS.Avalonia/ViewModels/MainViewModel.cs
index 0f8c82a..7a6dd4b 100644
--- a/Tesses.CMS.Avalonia/Tesses.CMS.Avalonia/ViewModels/MainViewModel.cs
+++ b/Tesses.CMS.Avalonia/Tesses.CMS.Avalonia/ViewModels/MainViewModel.cs
@@ -1,5 +1,6 @@
namespace Tesses.CMS.Avalonia.ViewModels;
+using System;
using System.Collections.ObjectModel;
using System.Linq;
using CommunityToolkit.Mvvm.ComponentModel;
@@ -11,24 +12,46 @@ public partial class MainViewModel : ViewModelBase
{
public MainViewModel(string title)
{
+ _pages.Add(new Page("Home",()=>new HomePageViewModel()));
+ _pages.Add(new Page("Favorites",()=>new FavoritesPageViewModel(this)));
+ _pages.Add(new Page("Notifications",()=>new NotificationsPageViewModel()));
+ _pages.Add(new Page("Downloads",()=>new DownloadsPageViewModel(this)));
+ _pages.Add(new Page("Video Player",()=>new VideoPlayerViewModel()));
_pages.Add(new Page("Settings",()=>new SettingsPageViewModel(this)));
SelectedListItem = Pages.First();
this.Title = title;
+
+ if(Environment.GetCommandLineArgs().Length > 1)
+ {
+ string path = Environment.GetCommandLineArgs()[1];
+ Setting = true;
+ SelectedListItem = _pages.First(e=>e.Name == "Video Player");
+ CurrentPage = new VideoPlayerViewModel(path);
+ PaneOpen=false;
+ Setting = false;
+ }
}
[ObservableProperty]
private bool _paneOpen=true;
- [ObservableProperty]
- private ViewModelBase _currentPage = new HomePageViewModel();
+ private ViewModelBase _curPage=new HomePageViewModel();
+ public ViewModelBase CurrentPage
+ {
+ get
+ {
+ return _curPage;
+ }
+ set{
+ App.OpenedControls.Destroy();
+ _curPage = value;
+ var disp = value as IDisposable;
+ if(disp != null) App.OpenedControls.Register(disp);
+ OnPropertyChanged(nameof(CurrentPage));
+
+ }
+ }
[ObservableProperty]
- private ObservableCollection _pages=new ObservableCollection(){
- new Page("Home",()=>new HomePageViewModel()),
- new Page("Favorites",()=>new FavoritesPageViewModel()),
- new Page("Notifications",()=>new NotificationsPageViewModel()),
- new Page("Downloads",()=>new DownloadsPageViewModel()),
-
- };
-
+ private ObservableCollection _pages=new ObservableCollection();
[ObservableProperty]
private Page? _selectedListItem;
[ObservableProperty]
@@ -37,10 +60,12 @@ public partial class MainViewModel : ViewModelBase
[ObservableProperty]
private string _title;
+ public bool Setting {get;set;}
+
partial void OnSelectedListItemChanged(Page? value)
{
if (value is null) return;
-
+ if(Setting) return;
@@ -57,4 +82,9 @@ public partial class MainViewModel : ViewModelBase
{
App.Log("Login button");
}
+
+ internal void SetHome()
+ {
+ SelectedListItem = Pages[0];
+ }
}
diff --git a/Tesses.CMS.Avalonia/Tesses.CMS.Avalonia/ViewModels/SavedDownloadViewModel.cs b/Tesses.CMS.Avalonia/Tesses.CMS.Avalonia/ViewModels/SavedDownloadViewModel.cs
new file mode 100644
index 0000000..65ef640
--- /dev/null
+++ b/Tesses.CMS.Avalonia/Tesses.CMS.Avalonia/ViewModels/SavedDownloadViewModel.cs
@@ -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() ?? new Movie(){Name=item.MediaName,ProperName=item.MediaProperName});
+ break;
+ }
+ UpdateDownloadButton();
+ }
+}
\ No newline at end of file
diff --git a/Tesses.CMS.Avalonia/Tesses.CMS.Avalonia/ViewModels/VideoPlayerViewModel.cs b/Tesses.CMS.Avalonia/Tesses.CMS.Avalonia/ViewModels/VideoPlayerViewModel.cs
new file mode 100644
index 0000000..ad41908
--- /dev/null
+++ b/Tesses.CMS.Avalonia/Tesses.CMS.Avalonia/ViewModels/VideoPlayerViewModel.cs
@@ -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();
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/Tesses.CMS.Avalonia/Tesses.CMS.Avalonia/ViewModels/ViewModelBase.cs b/Tesses.CMS.Avalonia/Tesses.CMS.Avalonia/ViewModels/ViewModelBase.cs
index 3b35673..1001f22 100644
--- a/Tesses.CMS.Avalonia/Tesses.CMS.Avalonia/ViewModels/ViewModelBase.cs
+++ b/Tesses.CMS.Avalonia/Tesses.CMS.Avalonia/ViewModels/ViewModelBase.cs
@@ -5,4 +5,5 @@ namespace Tesses.CMS.Avalonia.ViewModels;
public class ViewModelBase : ObservableObject
{
+
}
diff --git a/Tesses.CMS.Avalonia/Tesses.CMS.Avalonia/Views/ActiveDownloadView.axaml b/Tesses.CMS.Avalonia/Tesses.CMS.Avalonia/Views/ActiveDownloadView.axaml
new file mode 100644
index 0000000..e80c79b
--- /dev/null
+++ b/Tesses.CMS.Avalonia/Tesses.CMS.Avalonia/Views/ActiveDownloadView.axaml
@@ -0,0 +1,25 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/Tesses.CMS.Avalonia/Tesses.CMS.Avalonia/Views/ActiveDownloadView.axaml.cs b/Tesses.CMS.Avalonia/Tesses.CMS.Avalonia/Views/ActiveDownloadView.axaml.cs
new file mode 100644
index 0000000..387333e
--- /dev/null
+++ b/Tesses.CMS.Avalonia/Tesses.CMS.Avalonia/Views/ActiveDownloadView.axaml.cs
@@ -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();
+ }
+
+
+}
\ No newline at end of file
diff --git a/Tesses.CMS.Avalonia/Tesses.CMS.Avalonia/Views/DownloadsPageView.axaml b/Tesses.CMS.Avalonia/Tesses.CMS.Avalonia/Views/DownloadsPageView.axaml
index 9390bdf..eacd8e4 100644
--- a/Tesses.CMS.Avalonia/Tesses.CMS.Avalonia/Views/DownloadsPageView.axaml
+++ b/Tesses.CMS.Avalonia/Tesses.CMS.Avalonia/Views/DownloadsPageView.axaml
@@ -12,6 +12,13 @@
to set the actual DataContext for runtime, set the DataContext property in code (look at App.axaml.cs) -->
-
-
+
+
+
+
+
+
+
+
+
diff --git a/Tesses.CMS.Avalonia/Tesses.CMS.Avalonia/Views/FavoritesPageView.axaml b/Tesses.CMS.Avalonia/Tesses.CMS.Avalonia/Views/FavoritesPageView.axaml
index a732ec1..c7aaef8 100644
--- a/Tesses.CMS.Avalonia/Tesses.CMS.Avalonia/Views/FavoritesPageView.axaml
+++ b/Tesses.CMS.Avalonia/Tesses.CMS.Avalonia/Views/FavoritesPageView.axaml
@@ -11,5 +11,20 @@
to set the actual DataContext for runtime, set the DataContext property in code (look at App.axaml.cs) -->
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Tesses.CMS.Avalonia/Tesses.CMS.Avalonia/Views/HomeMoviePageView.axaml b/Tesses.CMS.Avalonia/Tesses.CMS.Avalonia/Views/HomeMoviePageView.axaml
new file mode 100644
index 0000000..157e4c0
--- /dev/null
+++ b/Tesses.CMS.Avalonia/Tesses.CMS.Avalonia/Views/HomeMoviePageView.axaml
@@ -0,0 +1,37 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/Tesses.CMS.Avalonia/Tesses.CMS.Avalonia/Views/HomeMoviePageView.axaml.cs b/Tesses.CMS.Avalonia/Tesses.CMS.Avalonia/Views/HomeMoviePageView.axaml.cs
new file mode 100644
index 0000000..43ea0bf
--- /dev/null
+++ b/Tesses.CMS.Avalonia/Tesses.CMS.Avalonia/Views/HomeMoviePageView.axaml.cs
@@ -0,0 +1,11 @@
+using Avalonia.Controls;
+
+namespace Tesses.CMS.Avalonia.Views.HomePages;
+
+public partial class HomeMoviePageView : UserControl
+{
+ public HomeMoviePageView()
+ {
+ InitializeComponent();
+ }
+}
\ No newline at end of file
diff --git a/Tesses.CMS.Avalonia/Tesses.CMS.Avalonia/Views/HomeMovieVideoPlayerView.axaml b/Tesses.CMS.Avalonia/Tesses.CMS.Avalonia/Views/HomeMovieVideoPlayerView.axaml
new file mode 100644
index 0000000..5957ee4
--- /dev/null
+++ b/Tesses.CMS.Avalonia/Tesses.CMS.Avalonia/Views/HomeMovieVideoPlayerView.axaml
@@ -0,0 +1,17 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/Tesses.CMS.Avalonia/Tesses.CMS.Avalonia/Views/HomeMovieVideoPlayerView.axaml.cs b/Tesses.CMS.Avalonia/Tesses.CMS.Avalonia/Views/HomeMovieVideoPlayerView.axaml.cs
new file mode 100644
index 0000000..448dcf5
--- /dev/null
+++ b/Tesses.CMS.Avalonia/Tesses.CMS.Avalonia/Views/HomeMovieVideoPlayerView.axaml.cs
@@ -0,0 +1,11 @@
+using Avalonia.Controls;
+
+namespace Tesses.CMS.Avalonia.Views.HomePages;
+
+public partial class HomeMovieVideoPlayerView : UserControl
+{
+ public HomeMovieVideoPlayerView()
+ {
+ InitializeComponent();
+ }
+}
\ No newline at end of file
diff --git a/Tesses.CMS.Avalonia/Tesses.CMS.Avalonia/Views/HomeShowListPageView.axaml b/Tesses.CMS.Avalonia/Tesses.CMS.Avalonia/Views/HomeShowListPageView.axaml
new file mode 100644
index 0000000..2df3129
--- /dev/null
+++ b/Tesses.CMS.Avalonia/Tesses.CMS.Avalonia/Views/HomeShowListPageView.axaml
@@ -0,0 +1,24 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/Tesses.CMS.Avalonia/Tesses.CMS.Avalonia/Views/HomeShowListPageView.axaml.cs b/Tesses.CMS.Avalonia/Tesses.CMS.Avalonia/Views/HomeShowListPageView.axaml.cs
new file mode 100644
index 0000000..8396897
--- /dev/null
+++ b/Tesses.CMS.Avalonia/Tesses.CMS.Avalonia/Views/HomeShowListPageView.axaml.cs
@@ -0,0 +1,11 @@
+using Avalonia.Controls;
+
+namespace Tesses.CMS.Avalonia.Views.HomePages;
+
+public partial class HomeShowListPageView : UserControl
+{
+ public HomeShowListPageView()
+ {
+ InitializeComponent();
+ }
+}
\ No newline at end of file
diff --git a/Tesses.CMS.Avalonia/Tesses.CMS.Avalonia/Views/InlineText.cs b/Tesses.CMS.Avalonia/Tesses.CMS.Avalonia/Views/InlineText.cs
new file mode 100644
index 0000000..374afaf
--- /dev/null
+++ b/Tesses.CMS.Avalonia/Tesses.CMS.Avalonia/Views/InlineText.cs
@@ -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
+{
+ ///
+ /// MediaPlayer Data Bound property
+ ///
+ ///
+ /// Defines the property.
+ ///
+ public static readonly DirectProperty TextProperty =
+ AvaloniaProperty.RegisterDirect(
+ 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()
+ {
+
+
+ }
+}
\ No newline at end of file
diff --git a/Tesses.CMS.Avalonia/Tesses.CMS.Avalonia/Views/SavedDownloadView.axaml b/Tesses.CMS.Avalonia/Tesses.CMS.Avalonia/Views/SavedDownloadView.axaml
new file mode 100644
index 0000000..5156f00
--- /dev/null
+++ b/Tesses.CMS.Avalonia/Tesses.CMS.Avalonia/Views/SavedDownloadView.axaml
@@ -0,0 +1,25 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/Tesses.CMS.Avalonia/Tesses.CMS.Avalonia/Views/SavedDownloadView.axaml.cs b/Tesses.CMS.Avalonia/Tesses.CMS.Avalonia/Views/SavedDownloadView.axaml.cs
new file mode 100644
index 0000000..f9d4303
--- /dev/null
+++ b/Tesses.CMS.Avalonia/Tesses.CMS.Avalonia/Views/SavedDownloadView.axaml.cs
@@ -0,0 +1,11 @@
+using Avalonia.Controls;
+
+namespace Tesses.CMS.Avalonia.Views;
+
+public partial class SavedDownloadView : UserControl
+{
+ public SavedDownloadView()
+ {
+ InitializeComponent();
+ }
+}
\ No newline at end of file
diff --git a/Tesses.CMS.Avalonia/Tesses.CMS.Avalonia.Desktop/VideoPlayer.cs b/Tesses.CMS.Avalonia/Tesses.CMS.Avalonia/Views/VideoPlayer.cs
similarity index 54%
rename from Tesses.CMS.Avalonia/Tesses.CMS.Avalonia.Desktop/VideoPlayer.cs
rename to Tesses.CMS.Avalonia/Tesses.CMS.Avalonia/Views/VideoPlayer.cs
index 5de7a5b..dc353bd 100644
--- a/Tesses.CMS.Avalonia/Tesses.CMS.Avalonia.Desktop/VideoPlayer.cs
+++ b/Tesses.CMS.Avalonia/Tesses.CMS.Avalonia/Views/VideoPlayer.cs
@@ -1,16 +1,51 @@
using System;
using System.IO;
+using Avalonia;
using Avalonia.Controls;
+using Avalonia.Data;
using Avalonia.Threading;
-using LibVLCSharp.Avalonia;
using LibVLCSharp.Shared;
using Tesses.CMS.Avalonia;
+using Tesses.CMS.Avalonia.Views;
+using Tesses.VirtualFilesystem;
+using Tesses.VirtualFilesystem.Extensions;
+
+namespace Tesses.CMS.Avalonia.Views;
+ public static class MMS
+ {
+ public static void MoveScreenshot(this IPlatform p,string path)
+ {
+ if(p.DownloadFilesystem == null) return;
+ p.DownloadFilesystem.CreateDirectory(Special.Root/"Screenshots");
+ using(var ms = File.OpenRead(path))
+ {
+ using(var f = p.DownloadFilesystem.OpenWrite(Special.Root/"Screenshots"/Path.GetFileName(path)))
+ {
+ ms.CopyTo(f);
+ }
+ }
+ File.Delete(path);
+ }
+ }
public class VideoPlayer : Grid
{
+ ///
+ /// MediaPlayer Data Bound property
+ ///
+ ///
+ /// Defines the property.
+ ///
+ public static readonly DirectProperty MediaPlayerProperty =
+ AvaloniaProperty.RegisterDirect(
+ nameof(MediaPlayer),
+ o => o.MediaPlayer,
+ (o, v) => o.MediaPlayer = v,
+ defaultBindingMode: BindingMode.TwoWay);
+
public VideoPlayer()
{
- view=new VideoView();
+ view=App.Platform.CreatePlayer();
@@ -23,12 +58,19 @@ public class VideoPlayer : Grid
ss.Content = "SS";
ss.Click += (sender,e)=>{
//take screenshot
- string realDir=App.Platform.DownloadFilesystem == null ? Environment.GetFolderPath(Environment.SpecialFolder.MyPictures) : Path.Combine(App.Platform.Configuration.DownloadPath,"Screenshots");
+ App.Log($"CanTakeScreenShots: {App.Platform.CanTakeScreenShots}");
+ if(!App.Platform.CanTakeScreenShots) return;
+
+ string realDir =App.Platform.ScreenshotPath; //App.Platform.DownloadFilesystem == null ? Environment.GetFolderPath(Environment.SpecialFolder.MyPictures) : Path.Combine(App.Platform.Configuration.DownloadPath,"Screenshots");
+ App.Log($"ScreenshotPath: {App.Platform.ScreenshotPath}");
Directory.CreateDirectory(realDir);
if(MediaPlayer == null) return;
string name = Path.Combine(realDir,$"TessesCMS-Screenshot-{DateTime.Now.ToString("yyyyMMdd_HHmmss")}-{TimeSpan.FromMilliseconds(MediaPlayer.Position*MediaPlayer.Length).ToString().Replace(":","_")}.png");
+ App.Log($"name: {name}");
MediaPlayer.TakeSnapshot(0,name,0,0);
-
+ if(App.Platform.MustMoveScreenshot)
+ App.Platform.MoveScreenshot(name);
+ App.Log("Screenshot taken");
};
slider = new Slider();
slider.Minimum = 0;
@@ -57,6 +99,8 @@ public class VideoPlayer : Grid
}
}
};
+
+
seekPanel.ColumnDefinitions.Add(new ColumnDefinition(GridLength.Auto));
seekPanel.ColumnDefinitions.Add(new ColumnDefinition(GridLength.Star));
seekPanel.ColumnDefinitions.Add(new ColumnDefinition(GridLength.Auto));
@@ -71,23 +115,24 @@ public class VideoPlayer : Grid
}
Slider slider;
Button playBtn;
- VideoView view;
+ Control view;
public MediaPlayer? MediaPlayer
{
- get=>view.MediaPlayer;
+ get=>App.Platform.GetMediaPlayer(view);
set{
- if(view.MediaPlayer != null)
+ var mp = App.Platform.GetMediaPlayer(view);
+ if(mp != null)
{
- view.MediaPlayer.Paused -= Paused;
- view.MediaPlayer.Playing -= Playing;
- view.MediaPlayer.PositionChanged -= PositionChanged;
+ mp.Paused -= Paused;
+ mp.Playing -= Playing;
+ mp.PositionChanged -= PositionChanged;
}
- view.MediaPlayer=value;
- if(view.MediaPlayer != null)
+ App.Platform.SetMediaPlayer(view,value);
+ if(value != null)
{
- view.MediaPlayer.Paused += Paused;
- view.MediaPlayer.Playing += Playing;
- view.MediaPlayer.PositionChanged += PositionChanged;
+ value.Paused += Paused;
+ value.Playing += Playing;
+ value.PositionChanged += PositionChanged;
}
}
}
diff --git a/Tesses.CMS.Avalonia/Tesses.CMS.Avalonia/Views/VideoPlayerView.axaml b/Tesses.CMS.Avalonia/Tesses.CMS.Avalonia/Views/VideoPlayerView.axaml
new file mode 100644
index 0000000..4049db6
--- /dev/null
+++ b/Tesses.CMS.Avalonia/Tesses.CMS.Avalonia/Views/VideoPlayerView.axaml
@@ -0,0 +1,20 @@
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/Tesses.CMS.Avalonia/Tesses.CMS.Avalonia/Views/VideoPlayerView.axaml.cs b/Tesses.CMS.Avalonia/Tesses.CMS.Avalonia/Views/VideoPlayerView.axaml.cs
new file mode 100644
index 0000000..a5866ae
--- /dev/null
+++ b/Tesses.CMS.Avalonia/Tesses.CMS.Avalonia/Views/VideoPlayerView.axaml.cs
@@ -0,0 +1,11 @@
+using Avalonia.Controls;
+
+namespace Tesses.CMS.Avalonia.Views;
+
+public partial class VideoPlayerView : UserControl
+{
+ public VideoPlayerView()
+ {
+ InitializeComponent();
+ }
+}
\ No newline at end of file
diff --git a/Tesses.CMS.Avalonia/Tesses.CMS.Avalonia/Views/VideoPlayerWrapper.cs b/Tesses.CMS.Avalonia/Tesses.CMS.Avalonia/Views/VideoPlayerWrapper.cs
deleted file mode 100644
index 2c61897..0000000
--- a/Tesses.CMS.Avalonia/Tesses.CMS.Avalonia/Views/VideoPlayerWrapper.cs
+++ /dev/null
@@ -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);
- }
-
-
- ///
- /// MediaPlayer Data Bound property
- ///
- ///
- /// Defines the property.
- ///
- public static readonly DirectProperty MediaPlayerProperty =
- AvaloniaProperty.RegisterDirect(
- nameof(MediaPlayer),
- o => o.MediaPlayer,
- (o, v) => o.MediaPlayer = v,
- defaultBindingMode: BindingMode.TwoWay);
-
-
-}
\ No newline at end of file
diff --git a/Tesses.CMS.Cli/Tesses.CMS.Cli.csproj b/Tesses.CMS.Cli/Tesses.CMS.Cli.csproj
index 90ea3ba..18a418c 100644
--- a/Tesses.CMS.Cli/Tesses.CMS.Cli.csproj
+++ b/Tesses.CMS.Cli/Tesses.CMS.Cli.csproj
@@ -7,13 +7,20 @@
+
+ 0.1.226-*
+ all
+
Exe
- net7.0
+ net8.0
enable
enable
+ tcms-cli
+ https://tesses.net/apps/TessesCMS
+ Mike Nolan <tesses@tesses.net>
diff --git a/Tesses.CMS.Client/Class1.cs b/Tesses.CMS.Client/Class1.cs
index 8952a9d..5e1c2e1 100644
--- a/Tesses.CMS.Client/Class1.cs
+++ b/Tesses.CMS.Client/Class1.cs
@@ -151,6 +151,7 @@ namespace Tesses.CMS.Client
byte[] buffer=new byte[1024];
using(var srcStrm = await resp.Content.ReadAsStreamAsync())
+ {
do {
if(token.IsCancellationRequested) return;
read = await srcStrm.ReadAsync(buffer,0,buffer.Length,token);
@@ -158,8 +159,9 @@ namespace Tesses.CMS.Client
await dest.WriteAsync(buffer,0,read,token);
offset += read;
if(total > 0)
- progress?.Report((double)offset / (double)read);
+ progress?.Report((double)offset / (double)total);
} while(read>0);
+ }
resp.Dispose();
}
public ShowClient Shows => new ShowClient(this);
@@ -356,7 +358,18 @@ namespace Tesses.CMS.Client
yield return new ShowWithSeasonsAndEpisodes(show,seasons);
}
}
-
+ public async Task GetShowContentMetadataAsync(string user,string show)
+ {
+ return JsonConvert.DeserializeObject(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 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 progress=null)
+ {
+ await client.DownloadFileAsync($"{client.rooturl}/content/{user}/show/{show}/thumbnail.jpg",dest,token,progress);
+ }
}
public class MovieClient
diff --git a/Tesses.CMS.Client/Show.cs b/Tesses.CMS.Client/Show.cs
index d4a7a50..39c4f90 100644
--- a/Tesses.CMS.Client/Show.cs
+++ b/Tesses.CMS.Client/Show.cs
@@ -1,8 +1,38 @@
using System;
using Newtonsoft.Json;
-
+using System.Collections.Generic;
namespace Tesses.CMS.Client
{
+ public class ShowContentMetaData
+ {
+ [JsonProperty("show_info")]
+ public Show Info {get;set;}
+ [JsonProperty("has_show_torrent")]
+ public bool HasShowTorrent{get;set;}
+ [JsonProperty("show_torrent_url")]
+ public string ShowTorrentUrl {get;set;}
+ [JsonProperty("has_show_with_extras_torrent")]
+ public bool HasShowWithExtrasTorrent{get;set;}
+
+ [JsonProperty("show_with_extras_torrent_url")]
+ public string ShowWithExtrasTorrentUrl {get;set;}
+
+
+
+ [JsonProperty("has_poster")]
+ public bool HasPoster {get;set;}
+ [JsonProperty("poster_url")]
+ public string PosterUrl {get;set;}
+
+ [JsonProperty("has_thumbnail")]
+ public bool HasThumbnail {get;set;}
+
+ [JsonProperty("thumbnail_url")]
+
+ public string ThumbnailUrl {get;set;}
+ [JsonProperty("extra_streams")]
+ public List ExtraStreams {get;set;}=new List();
+ }
public class Show
{
[JsonProperty("proper_name")]
@@ -17,4 +47,4 @@ namespace Tesses.CMS.Client
[JsonProperty("description")]
public string Description {get;set;}="";
}
-}
\ No newline at end of file
+}
diff --git a/Tesses.CMS.Server/Tesses.CMS.Server.csproj b/Tesses.CMS.Server/Tesses.CMS.Server.csproj
index 2d6cffc..60792ec 100644
--- a/Tesses.CMS.Server/Tesses.CMS.Server.csproj
+++ b/Tesses.CMS.Server/Tesses.CMS.Server.csproj
@@ -5,14 +5,35 @@
net8.0
enable
enable
+ tcms-server
+ https://tesses.net/apps/TessesCMS
+ Mike Nolan <tesses@tesses.net>
+ true
+ true
+ tcms-server
+
+ chown -R tcms-server:tcms-server /var/lib/tcms-server
+
+ 0.1.226-*
+ all
+
+
+
+
+ /etc/systemd/system/tcms-server.service
+
+
+ /var/lib/tcms-server/config.json
+
+
+
-
diff --git a/Tesses.CMS.Server/config.json b/Tesses.CMS.Server/config.json
new file mode 100644
index 0000000..0acc294
--- /dev/null
+++ b/Tesses.CMS.Server/config.json
@@ -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"
+}
diff --git a/Tesses.CMS.Server/tcms-server.service b/Tesses.CMS.Server/tcms-server.service
new file mode 100644
index 0000000..654f6ab
--- /dev/null
+++ b/Tesses.CMS.Server/tcms-server.service
@@ -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
\ No newline at end of file
diff --git a/Tesses.CMS/Album.cs b/Tesses.CMS/Album.cs
index 8f493fb..076c27e 100644
--- a/Tesses.CMS/Album.cs
+++ b/Tesses.CMS/Album.cs
@@ -23,9 +23,9 @@ namespace Tesses.CMS
[JsonIgnore]
public long UserId {get;set;}
[JsonProperty("creation_time")]
- public DateTime CreationTime {get;set;}
+ public DateTime CreationTime {get;set;}=DateTime.Now;
[JsonProperty("last_updated_time")]
- public DateTime LastUpdated {get;set;}
+ public DateTime LastUpdated {get;set;}=DateTime.Now;
[JsonProperty("description")]
public string Description {get;set;}
diff --git a/Tesses.CMS/Episode.cs b/Tesses.CMS/Episode.cs
index 34fe661..95c2e4a 100644
--- a/Tesses.CMS/Episode.cs
+++ b/Tesses.CMS/Episode.cs
@@ -22,9 +22,9 @@ namespace Tesses.CMS
[JsonIgnore]
public long UserId {get;set;}
[JsonProperty("creation_time")]
- public DateTime CreationTime {get;set;}
+ public DateTime CreationTime {get;set;}=DateTime.Now;
[JsonProperty("last_updated_time")]
- public DateTime LastUpdated {get;set;}
+ public DateTime LastUpdated {get;set;}=DateTime.Now;
[JsonProperty("description")]
public string Description {get;set;}="";
diff --git a/Tesses.CMS/Movie.cs b/Tesses.CMS/Movie.cs
index d6dc1af..8148afd 100644
--- a/Tesses.CMS/Movie.cs
+++ b/Tesses.CMS/Movie.cs
@@ -14,10 +14,10 @@ namespace Tesses.CMS
public string Name {get;set;}="";
[JsonIgnore]
public long UserId {get;set;}
- [JsonProperty("created_time")]
- public DateTime CreationTime {get;set;}
+ [JsonProperty("creation_time")]
+ public DateTime CreationTime {get;set;}=DateTime.Now;
[JsonProperty("last_updated_time")]
- public DateTime LastUpdated {get;set;}
+ public DateTime LastUpdated {get;set;}=DateTime.Now;
[JsonProperty("description")]
public string Description {get;set;}="";
diff --git a/Tesses.CMS/Project.cs b/Tesses.CMS/Project.cs
index 6ced4f8..3adbd81 100644
--- a/Tesses.CMS/Project.cs
+++ b/Tesses.CMS/Project.cs
@@ -16,9 +16,9 @@ namespace Tesses.CMS
[JsonIgnore]
public long UserId {get;set;}
[JsonProperty("creation_time")]
- public DateTime CreationTime {get;set;}
+ public DateTime CreationTime {get;set;}=DateTime.Now;
[JsonProperty("last_updated_time")]
- public DateTime LastUpdated {get;set;}
+ public DateTime LastUpdated {get;set;}=DateTime.Now;
[JsonProperty("description")]
public string Description {get;set;}="";
[JsonProperty("source")]
diff --git a/Tesses.CMS/ProjectRelease.cs b/Tesses.CMS/ProjectRelease.cs
index 87faa03..6042158 100644
--- a/Tesses.CMS/ProjectRelease.cs
+++ b/Tesses.CMS/ProjectRelease.cs
@@ -23,7 +23,7 @@ namespace Tesses.CMS
[JsonProperty("platforms")]
public List Platforms {get;set;}=new List();
[JsonProperty("creation_time")]
- public DateTime CreationTime {get;set;}
+ public DateTime CreationTime {get;set;}=DateTime.Now;
}
public enum CPUArchitecture
{
diff --git a/Tesses.CMS/Season.cs b/Tesses.CMS/Season.cs
index a8c3d45..9048424 100644
--- a/Tesses.CMS/Season.cs
+++ b/Tesses.CMS/Season.cs
@@ -18,9 +18,9 @@ namespace Tesses.CMS
[JsonIgnore]
public long UserId {get;set;}
[JsonProperty("creation_time")]
- public DateTime CreationTime {get;set;}
+ public DateTime CreationTime {get;set;}=DateTime.Now;
[JsonProperty("last_updated_time")]
- public DateTime LastUpdated {get;set;}
+ public DateTime LastUpdated {get;set;}=DateTime.Now;
[JsonProperty("description")]
public string Description {get;set;}="";
diff --git a/Tesses.CMS/Show.cs b/Tesses.CMS/Show.cs
index b5a8b24..127444c 100644
--- a/Tesses.CMS/Show.cs
+++ b/Tesses.CMS/Show.cs
@@ -15,9 +15,9 @@ namespace Tesses.CMS
[JsonIgnore]
public long UserId {get;set;}
[JsonProperty("creation_time")]
- public DateTime CreationTime {get;set;}
+ public DateTime CreationTime {get;set;} = DateTime.Now;
[JsonProperty("last_updated_time")]
- public DateTime LastUpdated {get;set;}
+ public DateTime LastUpdated {get;set;} = DateTime.Now;
[JsonProperty("description")]
public string Description {get;set;}="";
diff --git a/package.sh b/package.sh
new file mode 100644
index 0000000..aa06d1d
--- /dev/null
+++ b/package.sh
@@ -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
diff --git a/test.sh b/test.sh
new file mode 100644
index 0000000..cdd51d0
--- /dev/null
+++ b/test.sh
@@ -0,0 +1,9 @@
+#!/bin/bash
+cd Tesses.CMS.Server
+dotnet build
+cd ../Tesses.CMS.Cli
+dotnet build
+cd ../Tesses.CMS.Avalonia/Tesses.CMS.Avalonia.Desktop
+dotnet build
+cd ../Tesses.CMS.Avalonia.Android
+dotnet build
\ No newline at end of file