- VocaluxeSongServer: ASP.NET Core server that scans an UltraStar library and serves the song list (ETag), single files (Range) and zip bundles; optional API key; Dockerfile and Gitea workflow for git.disi.dev/projects/vocaluxe-song-server - Game: config section SongServer (Url, ApiKey). At song loading, the song text files are mirrored into a local cache that is loaded like a song folder; without connection the last library is used - VocaluxeLib: CSongFiles hook for all song file access; media are downloaded on demand (covers in the background, preview audio, everything before singing with progress in the sing screen) - Tests for scanner, API and downloads (incl. resume); AGENTS.md for the fork Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
126 lines
4.0 KiB
C#
126 lines
4.0 KiB
C#
#region license
|
|
// This file is part of Vocaluxe.
|
|
//
|
|
// Vocaluxe is free software: you can redistribute it and/or modify
|
|
// it under the terms of the GNU General Public License as published by
|
|
// the Free Software Foundation, either version 3 of the License, or
|
|
// (at your option) any later version.
|
|
//
|
|
// Vocaluxe is distributed in the hope that it will be useful,
|
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
// GNU General Public License for more details.
|
|
//
|
|
// You should have received a copy of the GNU General Public License
|
|
// along with Vocaluxe. If not, see <http://www.gnu.org/licenses/>.
|
|
#endregion
|
|
|
|
using System.Collections.Generic;
|
|
using System.IO;
|
|
using System.Linq;
|
|
using System.Threading.Tasks;
|
|
|
|
namespace VocaluxeLib.Songs
|
|
{
|
|
/// <summary>
|
|
/// Access to the files of a song (audio, video, cover, backgrounds).
|
|
/// Songs from a song server exist locally only as their text file at first; their other files are
|
|
/// downloaded on demand. Everything that opens a song file should check <see cref="IsLocal" /> and
|
|
/// call <see cref="RequestAsync" /> (or <see cref="EnsureLocal" /> on background threads) first.
|
|
/// </summary>
|
|
public interface ISongFileProvider
|
|
{
|
|
/// <summary>True if the file exists locally or can be downloaded.</summary>
|
|
bool Exists(string path);
|
|
|
|
/// <summary>True if the file can be opened now (exists locally and is complete).</summary>
|
|
bool IsLocal(string path);
|
|
|
|
/// <summary>Downloads the files that are not local yet. The task completes when all are local (or failed).</summary>
|
|
Task RequestAsync(IEnumerable<string> paths);
|
|
|
|
/// <summary>Progress of the downloads of these files in [0..1], 1 if all are local.</summary>
|
|
float GetProgress(IEnumerable<string> paths);
|
|
}
|
|
|
|
/// <summary>The default provider: plain local files.</summary>
|
|
public class CLocalSongFileProvider : ISongFileProvider
|
|
{
|
|
public bool Exists(string path)
|
|
{
|
|
return File.Exists(path);
|
|
}
|
|
|
|
public bool IsLocal(string path)
|
|
{
|
|
return File.Exists(path);
|
|
}
|
|
|
|
public Task RequestAsync(IEnumerable<string> paths)
|
|
{
|
|
return Task.CompletedTask;
|
|
}
|
|
|
|
public float GetProgress(IEnumerable<string> paths)
|
|
{
|
|
return 1f;
|
|
}
|
|
}
|
|
|
|
public static class CSongFiles
|
|
{
|
|
public static ISongFileProvider Provider { get; set; } = new CLocalSongFileProvider();
|
|
|
|
public static bool Exists(string path)
|
|
{
|
|
return !string.IsNullOrEmpty(path) && Provider.Exists(path);
|
|
}
|
|
|
|
public static bool IsLocal(string path)
|
|
{
|
|
return !string.IsNullOrEmpty(path) && Provider.IsLocal(path);
|
|
}
|
|
|
|
public static bool AreLocal(IEnumerable<string> paths)
|
|
{
|
|
return paths.All(IsLocal);
|
|
}
|
|
|
|
public static Task RequestAsync(IEnumerable<string> paths)
|
|
{
|
|
var missing = paths.Where(p => !string.IsNullOrEmpty(p) && !Provider.IsLocal(p)).Distinct().ToList();
|
|
return missing.Count == 0 ? Task.CompletedTask : Provider.RequestAsync(missing);
|
|
}
|
|
|
|
/// <summary>Blocks until the file is local. Only for background threads (e.g. cover loading).</summary>
|
|
public static bool EnsureLocal(string path)
|
|
{
|
|
if (IsLocal(path))
|
|
{
|
|
return true;
|
|
}
|
|
|
|
if (!Exists(path))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
try
|
|
{
|
|
RequestAsync(new[] { path }).Wait();
|
|
}
|
|
catch
|
|
{
|
|
// A failed download is reported by the provider; the caller handles the missing file.
|
|
}
|
|
|
|
return IsLocal(path);
|
|
}
|
|
|
|
public static float GetProgress(IEnumerable<string> paths)
|
|
{
|
|
return Provider.GetProgress(paths.Where(p => !string.IsNullOrEmpty(p)));
|
|
}
|
|
}
|
|
}
|