Skip to content

Commit

Permalink
Merge branch 'v2.3' into main
Browse files Browse the repository at this point in the history
  • Loading branch information
ellman12 committed May 30, 2023
2 parents 1c46d2e + f6eebd2 commit 1d985ce
Show file tree
Hide file tree
Showing 42 changed files with 1,160 additions and 566 deletions.
69 changes: 57 additions & 12 deletions PSS/PSS/Backend/Connection.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@ public static class Connection
private const string CONNECTION_STRING = "Host=localhost; Port=5432; User Id=postgres; Password=Ph0t0s_Server; Database=PSS";
public static readonly NpgsqlConnection connection = new(CONNECTION_STRING);

///Asynchronously creates and opens a new connection object for use in Async Connection.cs methods, and returns the new connection.
private static async Task<NpgsqlConnection> CreateLocalConnectionAsync()
///Asynchronously creates, opens, and returns a new connection object.
public static async Task<NpgsqlConnection> CreateLocalConnectionAsync()
{
NpgsqlConnection localConn = new(CONNECTION_STRING);
await localConn.OpenAsync();
Expand All @@ -37,7 +37,7 @@ public static void Close()
///<param name="path">The short path that will be stored in media. Convention is to use '/' as the separator.</param>
///<param name="dateTaken">When this item was taken.</param>
///<param name="uuid">The uuid of this item.</param>
///<param name="thumbnail">ONLY FOR VIDEOS. A base64 string for the video thumbnail. Use null or "" for pictures.</param>
///<param name="thumbnail">A base64 string representing the thumbnail.</param>
///<param name="starred">Is this item starred or not?</param>
public static async Task InsertMedia(string path, DateTime? dateTaken, Guid uuid, string thumbnail, bool starred = false)
{
Expand All @@ -49,13 +49,10 @@ public static async Task InsertMedia(string path, DateTime? dateTaken, Guid uuid
cmd.Parameters.AddWithValue("@path", path);
cmd.Parameters.AddWithValue("@uuid", uuid);
cmd.Parameters.AddWithValue("@starred", starred);
if (dateTaken != null)
cmd.Parameters.AddWithValue("@dateTaken", dateTaken);

if (!String.IsNullOrWhiteSpace(thumbnail))
cmd.Parameters.AddWithValue("@thumbnail", thumbnail);
cmd.Parameters.AddWithValue("@thumbnail", thumbnail);
if (dateTaken != null) cmd.Parameters.AddWithValue("@dateTaken", dateTaken);

cmd.CommandText = $"INSERT INTO media (path, {(dateTaken == null ? "" : "date_taken,")} starred, uuid {(String.IsNullOrWhiteSpace(thumbnail) ? "" : ", thumbnail")}) VALUES (@path, {(dateTaken == null ? "" : "@dateTaken, ")} @starred, @uuid {(String.IsNullOrWhiteSpace(thumbnail) ? "" : ", @thumbnail")}) ON CONFLICT(path) DO NOTHING";
cmd.CommandText = $"INSERT INTO media (path, {(dateTaken == null ? "" : "date_taken,")} starred, uuid , thumbnail) VALUES (@path, {(dateTaken == null ? "" : "@dateTaken, ")} @starred, @uuid, @thumbnail) ON CONFLICT(path) DO NOTHING";

await cmd.ExecuteNonQueryAsync();
}
Expand Down Expand Up @@ -215,13 +212,14 @@ public static List<MediaRow> LoadMemories(string monthName, int day)
{
List<MediaRow> memories = new();
int month = DateTime.ParseExact(monthName, "MMMM", System.Globalization.CultureInfo.CurrentCulture).Month;
string dd = day < 10 ? $"0{day}" : day.ToString();

try
{
Open();
using NpgsqlCommand cmd = new($"SELECT path, date_taken, starred, uuid, thumbnail FROM media WHERE CAST(date_taken as TEXT) LIKE '%{month}-{day} %' ORDER BY date_taken DESC", connection);
using NpgsqlCommand cmd = new($"SELECT path, date_taken, date_added, starred, uuid, thumbnail, description FROM media WHERE CAST(date_taken as TEXT) LIKE '%{month}-{dd}%' ORDER BY date_taken DESC", connection);
using NpgsqlDataReader r = cmd.ExecuteReader();
while (r.Read()) memories.Add(new MediaRow(r.GetString(0), r.GetDateTime(1), r.GetBoolean(2), r.GetGuid(3), r.IsDBNull(4) ? null : r.GetString(4)));
while (r.Read()) memories.Add(new MediaRow(r.GetString(0), r.IsDBNull(1) ? null : r.GetDateTime(1), r.GetDateTime(2), r.GetBoolean(3), r.GetGuid(4), r.GetString(5), r.IsDBNull(6) ? null : r.GetString(6)));
}
catch (NpgsqlException e)
{
Expand Down Expand Up @@ -500,6 +498,50 @@ public static void UpdateCollectionCover(int collectionID, string path)
}
}

///<summary>Given the integer id of a collection represented as a string, return a new <see cref="Collection"/> with the extra details about a Collection, like name, last updated, folder, etc.</summary>
/// <param name="collectionID"></param>
public static async Task<Collection> GetCollectionDetailsAsync(string collectionID)
{
NpgsqlConnection localConn = await CreateLocalConnectionAsync();

try
{
await using NpgsqlCommand cmd = new($"SELECT name, last_updated, folder, readonly FROM collections WHERE id = {collectionID}", localConn);
await using NpgsqlDataReader r = await cmd.ExecuteReaderAsync(CommandBehavior.SingleRow);
await r.ReadAsync();
return new Collection(Int32.Parse(collectionID), r.GetString(0), r.GetDateTime(1), r.GetBoolean(2), r.GetBoolean(3));
}
catch (NpgsqlException e)
{
L.LogException(e);
return null;
}
finally
{
await localConn.CloseAsync();
}
}

///Toggles a Collection's readonly field in the collections table.
public static async Task ToggleReadonlyAsync(Collection collection)
{
NpgsqlConnection localConn = await CreateLocalConnectionAsync();

try
{
await using NpgsqlCommand cmd = new($"UPDATE collections SET readonly = {!collection.readOnly} WHERE id = {collection.id}", localConn);
await cmd.ExecuteNonQueryAsync();
}
catch (NpgsqlException e)
{
L.LogException(e);
}
finally
{
await localConn.CloseAsync();
}
}

///<summary>Given an collection id, attempt to return its name.</summary>
///<param name="id">The id of the collection.</param>
///<returns>Collection name.</returns>
Expand Down Expand Up @@ -664,9 +706,10 @@ public static void RemoveFromCollection(Guid uuid, int collectionID)
///<summary>Load all the albums and/or folders in the collections table.</summary>
///<param name="showAlbums">Should albums be selected?</param>
///<param name="showFolders">Should folders be selected?</param>
///<param name="showReadonly">Should readonly collections be selected?</param>
///<param name="mode">How should the data be sorted?</param>
///<returns>A List&lt;Collection&gt; of all the albums and/or folders.</returns>
public static List<Collection> GetCollectionsTable(bool showAlbums, bool showFolders, CMSortMode mode = CMSortMode.Title)
public static List<Collection> GetCollectionsTable(bool showAlbums, bool showFolders, bool showReadonly, CMSortMode mode = CMSortMode.Title)
{
List<Collection> collections = new();

Expand All @@ -689,6 +732,8 @@ public static List<Collection> GetCollectionsTable(bool showAlbums, bool showFol
where = "WHERE folder = true";
else
where = "WHERE folder = true and folder = false";

if (!String.IsNullOrEmpty(where) && !showReadonly) where += " AND readonly = false";

try
{
Expand Down
32 changes: 24 additions & 8 deletions PSS/PSS/Backend/Functions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@ namespace PSS.Backend
///<summary>Static class of misc functions.</summary>
public static class Functions
{
public static readonly HashSet<string> SupportedImageExts = new() {".jpg", ".jpeg", ".png", ".gif"};
public static readonly HashSet<string> SupportedVideoExts = new() {".mp4", ".mkv", ".mov"};
public static readonly HashSet<string> SupportedExts = new() {".jpg", ".jpeg", ".png", ".gif", ".mp4", ".mkv", ".mov"};

///<summary>
///Take a byte long like 10900000000 and turn it into a more readable string like 10.9 GB.
///One thing to note is this uses things like kibibyte instead of the usual things like kilobyte because this is usually what's used for disk storage.
Expand Down Expand Up @@ -48,25 +52,32 @@ public static string FormatBytes(long bytes)
///</summary>
public static void VisToggle(ref string visibility) => visibility = visibility == "visible" ? "hidden" : "visible";

///<summary>Given the absolute path to a video file, use ffmpeg to generate a compressed thumbnail of the first frame.</summary>
///<param name="videoAbsPath">The absolute path to where the video file is.</param>
///<returns>A base64 string representing the first frame of the video, but heavily compressed.</returns>
public static string GenerateThumbnail(string videoAbsPath)
///<summary>Given the absolute path to a image/video file, use ffmpeg to generate a compressed thumbnail of the image or the first frame.</summary>
///<param name="filePath">The absolute path to where the file is.</param>
///<returns>A base64 string representing the thumbnail.</returns>
public static string GenerateThumbnail(string filePath)
{
string thumbnailFullPath = Path.Join(S.tmpFolderPath, Guid.NewGuid() + ".jpg");
ProcessStartInfo ffmpegInfo = new()
{
CreateNoWindow = true,
FileName = "ffmpeg",
Arguments = $"-i \"{videoAbsPath}\" -vf \"select=eq(n\\,0)\" -vf scale=320:-2 -q:v {S.thumbnailQuality} \"{thumbnailFullPath}\""
Arguments = $"-i \"{filePath}\" -loglevel quiet "
};

string ext = Path.GetExtension(filePath).ToLower();
if (ext == ".png")
ffmpegInfo.Arguments += $"-compression_level 100 \"{thumbnailFullPath}\""; //0-100 for quality. 100 is lowest.
else
ffmpegInfo.Arguments += $"{(SupportedVideoExts.Contains(ext) ? "-vf \"select=eq(n\\,0)\"" : "")} -vf scale=320:-2 -q:v {S.thumbnailQuality} \"{thumbnailFullPath}\"";

Process ffmpegProcess = Process.Start(ffmpegInfo);
ffmpegProcess!.WaitForExit();

byte[] bytes = File.ReadAllBytes(thumbnailFullPath);

try { File.Delete(thumbnailFullPath); }
catch (Exception e) { Console.WriteLine(e.Message); }
catch (Exception e) { L.LogException(e); }

return Convert.ToBase64String(bytes);
}
Expand All @@ -77,9 +88,14 @@ public static string GenerateThumbnail(string videoAbsPath)
///</summary>
public static List<string> GetSupportedFiles(string rootPath)
{
string[] validExts = {".jpg", ".jpeg", ".png", ".gif", ".mp4", ".mkv", ".mov"};
string[] allPaths = Directory.GetFiles(rootPath, "*.*", SearchOption.AllDirectories);
return allPaths.Where(path => validExts.Contains(Path.GetExtension(path).ToLower())).ToList();
return allPaths.Where(path => SupportedExts.Contains(Path.GetExtension(path).ToLower())).ToList();
}

///<summary>Returns the size of a folder in bytes.</summary>
public static long GetFolderSize(string path)
{
return new DirectoryInfo(path).EnumerateFiles("*", SearchOption.AllDirectories).Sum(file => file.Length);
}
}
}
8 changes: 4 additions & 4 deletions PSS/PSS/Backend/ImportFile.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ public class ImportFile
public string renamedFilename;

///The file extension of the item.
public string extension;
public readonly string extension;

///The path relative to pss_import of this item, which does NOT contain 'pss_import' at the front.
public string shortPath;
Expand All @@ -24,7 +24,7 @@ public class ImportFile
public string absolutePath;

///null for images; otherwise a base64 string for video files that contains a compressed thumbnail of the first frame of the video, generated by ffmpepg.
public string thumbnail;
public readonly string thumbnail;

///The date and time this file was taken, taken from the file's metadata. null if none found.
public DateTime? metadataDateTaken;
Expand Down Expand Up @@ -64,7 +64,7 @@ public ImportFile(string absPath)
shortPath = absolutePath.Substring(S.importFolderPath.Length + 1); //Ensures no '/' at start.
originalFilename = renamedFilename = Path.GetFileNameWithoutExtension(absolutePath);
extension = Path.GetExtension(absolutePath);
thumbnail = D.IsVideoExt(extension!) ? F.GenerateThumbnail(absolutePath) : null;
thumbnail = F.GenerateThumbnail(absolutePath);
D.GetDateTakenFromBoth(absolutePath!, out metadataDateTaken, out filenameDateTaken);
uuid = Guid.NewGuid();

Expand Down Expand Up @@ -102,7 +102,7 @@ private ImportFile(IReadOnlyList<string> split)
renamedFilename = split[1];
extension = split[2];
shortPath = split[3];
thumbnail = split[5] == "" ? null : split[5];
thumbnail = split[5];
if (!String.IsNullOrWhiteSpace(split[6])) metadataDateTaken = DateTime.Parse(split[6]);
if (!String.IsNullOrWhiteSpace(split[7])) filenameDateTaken = DateTime.Parse(split[7]);
if (!String.IsNullOrWhiteSpace(split[8])) customDateTaken = DateTime.Parse(split[8]);
Expand Down
8 changes: 4 additions & 4 deletions PSS/PSS/Backend/Maintenance.cs
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ public static List<MediaRow> GetMediaMissingFiles()
try
{
C.Open();
using NpgsqlCommand cmd = new("SELECT path, date_taken, date_added, starred, separate, uuid, thumbnail FROM media", C.connection);
using NpgsqlCommand cmd = new("SELECT path, date_taken, date_added, starred, separate, uuid, thumbnail, description FROM media", C.connection);
cmd.ExecuteNonQuery();
using NpgsqlDataReader r = cmd.ExecuteReader();

Expand All @@ -48,13 +48,13 @@ public static List<MediaRow> GetMediaMissingFiles()
string shortPath = r.GetString(0);
string fullPath = Path.Combine(S.libFolderPath, shortPath);
if (!File.Exists(fullPath))
missingFiles.Add(new MediaRow(shortPath, r.IsDBNull(1) ? null : r.GetDateTime(1), r.GetDateTime(2), r.GetBoolean(3), r.GetBoolean(4), r.GetGuid(5), r.IsDBNull(6) ? null : r.GetString(6)));
missingFiles.Add(new MediaRow(shortPath, r.IsDBNull(1) ? null : r.GetDateTime(1), r.GetDateTime(2), r.GetBoolean(3), r.GetBoolean(4), r.GetGuid(5), r.GetString(6), r.IsDBNull(7) ? null : r.GetString(7)));
}
r.Close();
}
catch (NpgsqlException e)
{
Console.WriteLine("An unknown error occurred. Error code: " + e.ErrorCode + " Message: " + e.Message);
L.LogException(e);
}
finally
{
Expand All @@ -80,7 +80,7 @@ public static void RemoveMediaMissingFiles(List<MediaRow> rows)
}
catch (NpgsqlException e)
{
Console.WriteLine("An unknown error occurred. Error code: " + e.ErrorCode + " Message: " + e.Message);
L.LogException(e);
}
finally
{
Expand Down
19 changes: 19 additions & 0 deletions PSS/PSS/Backend/Records/Collection.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ public record Collection
public readonly string name;
public readonly string cover;
public readonly DateTime dateUpdated;
public bool folder, readOnly;

public Collection(int id, string name, string cover)
{
Expand All @@ -22,4 +23,22 @@ public Collection(int id, string name, string cover, DateTime dateUpdated)
this.cover = cover;
this.dateUpdated = dateUpdated;
}

public Collection(int id, string name, DateTime dateUpdated, bool folder, bool readOnly)
{
this.id = id;
this.name = name;
this.dateUpdated = dateUpdated;
this.folder = folder;
this.readOnly = readOnly;
}

public Collection(int id, string name, string cover, DateTime dateUpdated, bool readOnly)
{
this.id = id;
this.name = name;
this.cover = cover;
this.dateUpdated = dateUpdated;
this.readOnly = readOnly;
}
}
15 changes: 15 additions & 0 deletions PSS/PSS/Backend/Records/MediaRow.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ public record MediaRow
public readonly string thumbnail;
public readonly DateTime? dateDeleted;
public string description;
public readonly bool video;

public MediaRow(string p, DateTime? dt, Guid uuid, string thumbnail, DateTime? dateDeleted)
{
Expand All @@ -29,6 +30,7 @@ public MediaRow(string p, DateTime? dt, bool starred, Guid uuid, string thumbnai
this.starred = starred;
this.uuid = uuid;
this.thumbnail = thumbnail;
video = D.IsVideoExt(Path.GetExtension(path));
}

public MediaRow(string p, DateTime? dt, DateTime da, bool starred, Guid uuid)
Expand Down Expand Up @@ -58,6 +60,7 @@ public MediaRow(string p, DateTime? dt, DateTime da, bool starred, Guid uuid, st
this.uuid = uuid;
this.thumbnail = thumbnail;
this.description = description;
video = D.IsVideoExt(Path.GetExtension(path));
}

public MediaRow(string p, DateTime? dt, DateTime da, bool starred, bool separate, Guid uuid, string thumbnail)
Expand All @@ -70,4 +73,16 @@ public MediaRow(string p, DateTime? dt, DateTime da, bool starred, bool separate
this.uuid = uuid;
this.thumbnail = thumbnail;
}

public MediaRow(string p, DateTime? dt, DateTime da, bool starred, bool separate, Guid uuid, string thumbnail, string description)
{
path = p;
dateTaken = dt;
dateAdded = da;
this.starred = starred;
this.separate = separate;
this.uuid = uuid;
this.thumbnail = thumbnail;
this.description = description;
}
}
13 changes: 6 additions & 7 deletions PSS/PSS/Backend/SQL Scripts/Create Tables.sql
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@ CREATE TABLE IF NOT EXISTS public.media
starred boolean NOT NULL DEFAULT false,
separate boolean NOT NULL DEFAULT false, -- Is this item in a folder?
uuid uuid NOT NULL DEFAULT uuid_generate_v1(),
thumbnail text DEFAULT NULL, -- Base64 string representing video thumbnail.
date_deleted timestamp without time zone DEFAULT NULL, -- If this has a value, it's not in the Trash.
thumbnail text NOT NULL, -- Base64 string representing thumbnail.
date_deleted timestamp without time zone DEFAULT NULL, -- If this has a value, it's in the Trash.
description text DEFAULT NULL,
PRIMARY KEY (path, uuid),
UNIQUE (path),
Expand All @@ -21,13 +21,12 @@ ALTER TABLE public.media OWNER to postgres;

CREATE TABLE IF NOT EXISTS public.collections
(
id serial NOT NULL,
name text NOT NULL,
cover text DEFAULT NULL REFERENCES media(path) ON DELETE SET NULL, -- References short path in media. If the cover is deleted from media, remove cover from any collections.
id serial NOT NULL PRIMARY KEY,
name text NOT NULL UNIQUE,
cover text DEFAULT NULL REFERENCES media(path) ON DELETE SET NULL,
last_updated timestamp without time zone NOT NULL, -- The last time this item was renamed, added to/removed from, etc.
folder boolean NOT NULL DEFAULT false, -- If this is a folder and thus its contents should remain separate from rest of library.
PRIMARY KEY (id),
UNIQUE (name)
readonly boolean NOT NULL DEFAULT false -- If this Collection has been marked as readonly, it cannot: be renamed, have items added/removed, change if it's a folder or not, appear in CollectionSelector, or be deleted.
) TABLESPACE pg_default;
ALTER TABLE public.collections OWNER to postgres;

Expand Down
12 changes: 3 additions & 9 deletions PSS/PSS/Pages/AllItems.razor
Original file line number Diff line number Diff line change
Expand Up @@ -25,13 +25,7 @@
}

@code {
private static LibraryContentViewer libContentViewer = new();
private static int selectedItemsCount;
private static string headerVis, collectionsVis;

protected override void OnInitialized()
{
selectedItemsCount = 0;
headerVis = collectionsVis = "hidden";
}
private LibraryContentViewer libContentViewer = new();
private int selectedItemsCount;
private string headerVis = "hidden", collectionsVis = "hidden";
}
Loading

0 comments on commit 1d985ce

Please sign in to comment.