lyrics support, code cleanup

This commit is contained in:
Laura Hausmann 2020-12-21 20:42:29 +01:00
parent 193cc39ce2
commit b9794110cd
Signed by untrusted user: zotan
GPG Key ID: 5EC1D38FFC321311
12 changed files with 245 additions and 340 deletions

View File

@ -1,21 +0,0 @@
# http://docs.gitlab.com/ce/ci/docker/using_docker_build.html#using-the-gitlab-container-registry
# The docker tag is the first 6 letters of the Git commit id
job_build_dotnet:
stage: build
image: archlinux/base:latest
variables:
GIT_SUBMODULE_STRATEGY: recursive
script:
- pacman-key --init
- pacman-key --recv-keys 3FABB87C7C9F7E5FF2B6CB7B11A7E7E4DB9351DE
- pacman-key --lsign-key 3FABB87C7C9F7E5FF2B6CB7B11A7E7E4DB9351DE
- bash -c "echo -e '"'[zotancc]\nServer = https://arch.prod.zotan.network/zotancc/os/$arch'"' >> /etc/pacman.conf"
- pacman -Syu --needed dotnet-sdk-bin --noconfirm
- curl -Lo warp-packer https://github.com/dgiagio/warp/releases/download/v0.3.0/linux-x64.warp-packer && chmod +x warp-packer
- dotnet publish -c Release -r linux-x64
- ./warp-packer --arch linux-x64 --input_dir bin/Release/netcoreapp3.1/linux-x64/publish --exec webmusic --output webmusic.linux.run
- chmod +x webmusic.linux.run
artifacts:
paths:
- webmusic.linux.run

View File

@ -1,26 +1,25 @@
@page
@model ErrorModel
@{
ViewData["Title"] = "Error";
ViewData["Title"] = "Error";
}
<h1 class="text-danger">Error.</h1>
<h2 class="text-danger">An error occurred while processing your request.</h2>
@if (Model.ShowRequestId)
{
<p>
<strong>Request ID:</strong> <code>@Model.RequestId</code>
</p>
@if (Model.ShowRequestId) {
<p>
<strong>Request ID:</strong> <code>@Model.RequestId</code>
</p>
}
<h3>Development Mode</h3>
<p>
Swapping to the <strong>Development</strong> environment displays detailed information about the error that occurred.
Swapping to the <strong>Development</strong> environment displays detailed information about the error that occurred.
</p>
<p>
<strong>The Development environment shouldn't be enabled for deployed applications.</strong>
It can result in displaying sensitive information from exceptions to end users.
For local debugging, enable the <strong>Development</strong> environment by setting the <strong>ASPNETCORE_ENVIRONMENT</strong> environment variable to <strong>Development</strong>
and restarting the app.
</p>
<strong>The Development environment shouldn't be enabled for deployed applications.</strong>
It can result in displaying sensitive information from exceptions to end users.
For local debugging, enable the <strong>Development</strong> environment by setting the <strong>ASPNETCORE_ENVIRONMENT</strong> environment variable to <strong>Development</strong>
and restarting the app.
</p>

View File

@ -1,23 +1,16 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
namespace webmusic.Pages
{
[ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)]
public class ErrorModel : PageModel
{
public string RequestId { get; set; }
namespace webmusic.Pages {
[ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)]
public class ErrorModel : PageModel {
public string RequestId { get; set; }
public bool ShowRequestId => !string.IsNullOrEmpty(RequestId);
public bool ShowRequestId => !string.IsNullOrEmpty(RequestId);
public void OnGet()
{
RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier;
}
}
public void OnGet() {
RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier;
}
}
}

View File

@ -1,47 +1,44 @@
@page
@using System.Web
@model IndexModel
@{
ViewData["Title"] = $"webmusic on .NET {Environment.Version}";
var path = HttpUtility.UrlDecode(Request.QueryString.Value.TrimStart('?'));
if (path.EndsWith(".m3u"))
{
Layout = null;
Response.ContentType = "text/plain";
}
ViewData["Title"] = $"webmusic on .NET {Environment.Version}";
}
@if (Model.path.Contains("/.."))
{
return;
@if (Model.Path.Contains("/..")) {
return;
}
@if (path.EndsWith(".m3u"))
{
@foreach (var file in Model.files)
{
@Html.Raw("https://" + Request.Host + "/" + Model.fullpath + "/" + file.Replace("?", "%3F") +"\n")
}
@if (Model.Path.EndsWith(".lrc")) {
<h3>@Model.Path</h3>
<p>@Html.Raw((await System.IO.File.ReadAllTextAsync("music" + Model.Path)).Replace("\n", "<br/>"))</p>
}
else
{
<h2>@Model.displaypath <span id="state"></span> <span id="flags"></span></h2>
<a class="action-muted">[..]</a>
<a href="?@Model.path_oneup" class="entry-muted cfont"> Go back</a><br/>
<a class="action-muted">[--]</a>
<a href="/playlist/@(Request.QueryString).m3u" class="entry-muted cfont"> Download playlist</a><br/><br/>
@foreach (var dir in Model.dirs)
{
<a class="action" href="#">[--]</a>
<a href="?@Model.path/@dir"> @dir</a>
<br/>
}
@foreach (var file in Model.files)
{
var jspath = Model.Encode(Model.fullpath);
var jsfile = jspath + "/" + Model.Encode(file);
<script>queue.push('@Html.Raw(jsfile)')</script>
<a class="action" href="@Html.Raw(Model.Encode(Model.fullpath+"/"+file))" download>[DL]</a>
<a href="#" onclick="playSong('@Html.Raw(jsfile)')"> @file</a>
<br/>
}
else {
<h2>
@Model.Displaypath <span id="state"></span> <span id="flags"></span>
</h2>
<a class="action-muted">[..]</a>
<a href="?@Model.PathOneup" class="entry-muted cfont"> Go back</a>
<br/>
<a class="action-muted">[--]</a>
<a href="/playlist/@(Request.QueryString).m3u" class="entry-muted cfont"> Download playlist</a>
<br/>
<br/>
@foreach (var dir in Model.Dirs) {
<a class="action" href="#">[--]</a>
<a href="?@Model.Path/@dir"> @dir</a>
<br/>
}
@foreach (var file in Model.Files) {
var jspath = IndexModel.Encode(Model.Fullpath);
var jsfile = jspath + "/" + IndexModel.Encode(file);
var basename = System.IO.Path.GetFileNameWithoutExtension(file);
var lrcfile = basename + ".lrc";
var lrcpath = System.IO.Path.Combine(Model.Fullpath, lrcfile);
<script>queue.push('@Html.Raw(jsfile)')</script>
<a class="action" href="@Html.Raw(IndexModel.Encode(Model.Fullpath + "/" + file))" download>[DL]</a>
<a href="#" onclick="playSong('@Html.Raw(jsfile)')"> @file</a>
@if (System.IO.File.Exists(lrcpath)) {
<a class="action" href="?@Model.Path/@lrcfile" target="_blank">[LRC]</a>
}
<br/>
}
}

View File

@ -1,4 +1,4 @@
using System.Collections;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
@ -6,139 +6,117 @@ using System.Text.RegularExpressions;
using System.Web;
using Microsoft.AspNetCore.Mvc.RazorPages;
namespace webmusic.Pages {
public class IndexModel : PageModel {
private const string Root = "music";
public List<string> Dirs = new();
public string Displaypath = "";
public List<string> Files = new();
public string Fullpath = "";
public string Path = "";
public string PathOneup = "";
namespace webmusic.Pages
{
public class IndexModel : PageModel
{
public static string root = "music";
public string path = "";
public string displaypath = "";
public string path_oneup = "";
public string fullpath = "";
public List<string> dirs = new List<string>();
public List<string> files = new List<string>();
public void OnGet()
{
if (Request.QueryString.HasValue)
path = HttpUtility.UrlDecode(Request.QueryString.Value.Remove(0,1)
.Replace("+", "%2B"));
if (path.EndsWith(".m3u"))
path = path.Substring(0, path.Length - 4);
if (path.Contains("/.."))
{
Response.Redirect("/Error");
return;
}
path_oneup = Regex.Match(path, @".*(?=\/)").Value;
fullpath = root + path;
displaypath = string.IsNullOrWhiteSpace(path) ? "/" : path;
dirs = Directory.GetDirectories(fullpath).Select(Path.GetFileName).ToList();
dirs.RemoveAll(p => p.StartsWith("."));
dirs.Sort();
files = Directory.GetFiles(fullpath).Select(Path.GetFileName).ToList();
files.RemoveAll(p => p.EndsWith(".m3u"));
files.RemoveAll(p => p.StartsWith("."));
files.Sort(new AlphanumComparatorFast());
}
public void OnGet() {
if (Request.QueryString.HasValue)
if (Request.QueryString.Value != null)
Path = HttpUtility.UrlDecode(Request.QueryString.Value.TrimStart('?').Replace("+", "%2B"));
if (Path.Contains("/..")) {
Response.Redirect("/Error");
return;
}
public string Encode(string str)
{
return str.Replace("\"", "%22").Replace("'", "%27")
.Replace("?", "%3F").Replace("&", "%26")
.Replace(" ", "%20");
}
if (Path.EndsWith(".lrc"))
return;
public class AlphanumComparatorFast : IComparer<string>
{
public int Compare(string x, string y)
{
string s1 = x;
if (s1 == null)
{
return 0;
}
PathOneup = Regex.Match(Path, @".*(?=\/)").Value;
Fullpath = Root + Path;
Displaypath = string.IsNullOrWhiteSpace(Path) ? "/" : Path;
Dirs = Directory.GetDirectories(Fullpath).Select(System.IO.Path.GetFileName).ToList();
Dirs.RemoveAll(p => p.StartsWith("."));
Dirs.Sort();
Files = Directory.GetFiles(Fullpath).Select(System.IO.Path.GetFileName).ToList();
Files.RemoveAll(p => p.EndsWith(".m3u"));
Files.RemoveAll(p => p.EndsWith(".lrc"));
Files.RemoveAll(p => p.StartsWith("."));
Files.Sort(new AlphanumComparatorFast());
}
if (!(y is string s2))
{
return 0;
}
public static string Encode(string str) => str.Replace("\"", "%22")
.Replace("'", "%27")
.Replace("?", "%3F")
.Replace("&", "%26")
.Replace(" ", "%20");
int len1 = s1.Length;
int len2 = s2.Length;
int marker1 = 0;
int marker2 = 0;
private class AlphanumComparatorFast : IComparer<string> {
public int Compare(string x, string y) {
var s1 = x;
if (s1 == null)
return 0;
// Walk through two the strings with two markers.
while (marker1 < len1 && marker2 < len2)
{
char ch1 = s1[marker1];
char ch2 = s2[marker2];
if (!(y is { } s2))
return 0;
// Some buffers we can build up characters in for each chunk.
char[] space1 = new char[len1];
int loc1 = 0;
char[] space2 = new char[len2];
int loc2 = 0;
var len1 = s1.Length;
var len2 = s2.Length;
var marker1 = 0;
var marker2 = 0;
// Walk through all following characters that are digits or
// characters in BOTH strings starting at the appropriate marker.
// Collect char arrays.
do
{
space1[loc1++] = ch1;
marker1++;
// Walk through two the strings with two markers.
while (marker1 < len1 && marker2 < len2) {
var ch1 = s1[marker1];
var ch2 = s2[marker2];
if (marker1 < len1)
{
ch1 = s1[marker1];
}
else
{
break;
}
} while (char.IsDigit(ch1) == char.IsDigit(space1[0]));
// Some buffers we can build up characters in for each chunk.
var space1 = new char[len1];
var loc1 = 0;
var space2 = new char[len2];
var loc2 = 0;
do
{
space2[loc2++] = ch2;
marker2++;
// Walk through all following characters that are digits or
// characters in BOTH strings starting at the appropriate marker.
// Collect char arrays.
do {
space1[loc1++] = ch1;
marker1++;
if (marker2 < len2)
{
ch2 = s2[marker2];
}
else
{
break;
}
} while (char.IsDigit(ch2) == char.IsDigit(space2[0]));
if (marker1 < len1)
ch1 = s1[marker1];
else
break;
} while (char.IsDigit(ch1) == char.IsDigit(space1[0]));
// If we have collected numbers, compare them numerically.
// Otherwise, if we have strings, compare them alphabetically.
string str1 = new string(space1);
string str2 = new string(space2);
do {
space2[loc2++] = ch2;
marker2++;
int result;
if (marker2 < len2)
ch2 = s2[marker2];
else
break;
} while (char.IsDigit(ch2) == char.IsDigit(space2[0]));
if (char.IsDigit(space1[0]) && char.IsDigit(space2[0]))
{
int thisNumericChunk = int.Parse(str1);
int thatNumericChunk = int.Parse(str2);
result = thisNumericChunk.CompareTo(thatNumericChunk);
}
else
{
result = str1.CompareTo(str2);
}
// If we have collected numbers, compare them numerically.
// Otherwise, if we have strings, compare them alphabetically.
var str1 = new string(space1);
var str2 = new string(space2);
if (result != 0)
{
return result;
}
}
return len1 - len2;
}
}
}
int result;
if (char.IsDigit(space1[0]) && char.IsDigit(space2[0])) {
var thisNumericChunk = int.Parse(str1);
var thatNumericChunk = int.Parse(str2);
result = thisNumericChunk.CompareTo(thatNumericChunk);
}
else {
result = string.Compare(str1, str2, StringComparison.Ordinal);
}
if (result != 0)
return result;
}
return len1 - len2;
}
}
}
}

View File

@ -1,37 +1,37 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-capable" content="yes">
<title>@ViewData["Title"]</title>
<link href="https://fonts.googleapis.com/css?family=Inconsolata" rel="stylesheet">
<link href="https://fonts.googleapis.com/css?family=Ubuntu+Mono" rel="stylesheet">
<script src="/howler.core.js"></script>
<script src="/webmusic.js"></script>
<link href="/webmusic.css" rel="stylesheet"/>
<link rel="apple-touch-icon" sizes="57x57" href="/favicon/apple-icon-57x57.png">
<link rel="apple-touch-icon" sizes="60x60" href="/favicon/apple-icon-60x60.png">
<link rel="apple-touch-icon" sizes="72x72" href="/favicon/apple-icon-72x72.png">
<link rel="apple-touch-icon" sizes="76x76" href="/favicon/apple-icon-76x76.png">
<link rel="apple-touch-icon" sizes="114x114" href="/favicon/apple-icon-114x114.png">
<link rel="apple-touch-icon" sizes="120x120" href="/favicon/apple-icon-120x120.png">
<link rel="apple-touch-icon" sizes="144x144" href="/favicon/apple-icon-144x144.png">
<link rel="apple-touch-icon" sizes="152x152" href="/favicon/apple-icon-152x152.png">
<link rel="apple-touch-icon" sizes="180x180" href="/favicon/apple-icon-180x180.png">
<link rel="icon" type="image/png" sizes="192x192" href="/favicon/android-icon-192x192.png">
<link rel="icon" type="image/png" sizes="32x32" href="/favicon/favicon-32x32.png">
<link rel="icon" type="image/png" sizes="96x96" href="/favicon/favicon-96x96.png">
<link rel="icon" type="image/png" sizes="16x16" href="/favicon/favicon-16x16.png">
<link rel="manifest" href="/manifest.json">
<meta name="msapplication-TileColor" content="#ffffff">
<meta name="msapplication-TileImage" content="/favicon/ms-icon-144x144.png">
<meta name="theme-color" content="#ffffff">
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-capable" content="yes">
<title>@ViewData["Title"]</title>
<link href="https://fonts.googleapis.com/css?family=Inconsolata" rel="stylesheet">
<link href="https://fonts.googleapis.com/css?family=Ubuntu+Mono" rel="stylesheet">
<script src="/howler.core.js"></script>
<script src="/webmusic.js"></script>
<link href="/webmusic.css" rel="stylesheet"/>
<link rel="apple-touch-icon" sizes="57x57" href="/favicon/apple-icon-57x57.png">
<link rel="apple-touch-icon" sizes="60x60" href="/favicon/apple-icon-60x60.png">
<link rel="apple-touch-icon" sizes="72x72" href="/favicon/apple-icon-72x72.png">
<link rel="apple-touch-icon" sizes="76x76" href="/favicon/apple-icon-76x76.png">
<link rel="apple-touch-icon" sizes="114x114" href="/favicon/apple-icon-114x114.png">
<link rel="apple-touch-icon" sizes="120x120" href="/favicon/apple-icon-120x120.png">
<link rel="apple-touch-icon" sizes="144x144" href="/favicon/apple-icon-144x144.png">
<link rel="apple-touch-icon" sizes="152x152" href="/favicon/apple-icon-152x152.png">
<link rel="apple-touch-icon" sizes="180x180" href="/favicon/apple-icon-180x180.png">
<link rel="icon" type="image/png" sizes="192x192" href="/favicon/android-icon-192x192.png">
<link rel="icon" type="image/png" sizes="32x32" href="/favicon/favicon-32x32.png">
<link rel="icon" type="image/png" sizes="96x96" href="/favicon/favicon-96x96.png">
<link rel="icon" type="image/png" sizes="16x16" href="/favicon/favicon-16x16.png">
<link rel="manifest" href="/manifest.json">
<meta name="msapplication-TileColor" content="#ffffff">
<meta name="msapplication-TileImage" content="/favicon/ms-icon-144x144.png">
<meta name="theme-color" content="#ffffff">
</head>
<body>
<div id="container">
@RenderBody()
@RenderBody()
</div>
</body>
</html>
</html>

View File

@ -1,3 +1,3 @@
@using webmusic
@namespace webmusic.Pages
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers

View File

@ -1,3 +1,3 @@
@{
Layout = "_Layout";
}
Layout = "_Layout";
}

View File

@ -1,19 +1,15 @@
@page
@using System.Web
@model IndexModel
@{
Layout = null;
Response.ContentType = "text/plain";
Layout = null;
Response.ContentType = "text/plain";
}
@if (Model.path.Contains(".."))
{
return;
@if (Model.Path.Contains("..")) {
return;
}
@foreach (var dir in Model.dirs)
{
@Html.Raw(dir+"\n")
@foreach (var dir in Model.Dirs) {
@Html.Raw(dir + "\n")
}
@foreach (var file in Model.files)
{
@Html.Raw(file+"\n")
@foreach (var file in Model.Files) {
@Html.Raw(file + "\n")
}

View File

@ -2,15 +2,13 @@
@using System.Web
@model IndexModel
@{
var path = HttpUtility.UrlDecode(Request.QueryString.Value.TrimStart('?'));
Layout = null;
Response.ContentType = "text/plain";
var path = HttpUtility.UrlDecode(Request.QueryString.Value.TrimStart('?'));
Layout = null;
Response.ContentType = "text/plain";
}
@if (Model.path.Contains(".."))
{
return;
}
@foreach (var file in Model.files)
{
@Html.Raw("https://" + Request.Host + "/" + Model.fullpath + "/" + file.Replace("?", "%3F") +"\n")
@if (Model.Path.Contains("..")) {
return;
}
@foreach (var file in Model.Files) {
@Html.Raw("https://" + Request.Host + "/" + Model.Fullpath + "/" + file.Replace("?", "%3F") + "\n")
}

View File

@ -1,25 +1,13 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore;
using Microsoft.AspNetCore;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging;
namespace webmusic
{
public class Program
{
public static void Main(string[] args)
{
CreateWebHostBuilder(args).Build().Run();
}
namespace webmusic {
public class Program {
public static void Main(string[] args) {
CreateWebHostBuilder(args).Build().Run();
}
public static IWebHostBuilder CreateWebHostBuilder(string[] args) =>
WebHost.CreateDefaultBuilder(args)
.UseWebRoot(".")
.UseStartup<Startup>();
}
public static IWebHostBuilder CreateWebHostBuilder(string[] args) =>
WebHost.CreateDefaultBuilder(args).UseWebRoot(".").UseStartup<Startup>();
}
}

View File

@ -1,68 +1,45 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.HttpsPolicy;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.StaticFiles;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
namespace webmusic
{
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
namespace webmusic {
public class Startup {
// This method gets called by the runtime. Use this method to add services to the container.
public static void ConfigureServices(IServiceCollection services) {
services.Configure<CookiePolicyOptions>(options => {
// This lambda determines whether user consent for non-essential cookies is needed for a given request.
options.CheckConsentNeeded = context => true;
options.MinimumSameSitePolicy = SameSiteMode.None;
});
public IConfiguration Configuration { get; }
services.Configure<MvcOptions>(options => { options.EnableEndpointRouting = false; });
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
services.Configure<CookiePolicyOptions>(options =>
{
// This lambda determines whether user consent for non-essential cookies is needed for a given request.
options.CheckConsentNeeded = context => true;
options.MinimumSameSitePolicy = SameSiteMode.None;
});
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Latest);
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public static void Configure(IApplicationBuilder app, IWebHostEnvironment env) {
var provider = new FileExtensionContentTypeProvider();
provider.Mappings[".flac"] = "audio/flac";
provider.Mappings[".m3u"] = "audio/m3u";
provider.Mappings[".opus"] = "audio/opus";
if (env.IsDevelopment()) {
app.UseDeveloperExceptionPage();
}
else {
app.UseExceptionHandler("/Error");
// The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
app.UseHsts();
}
services.Configure<MvcOptions>(options => { options.EnableEndpointRouting = false; });
app.UseStaticFiles(new StaticFileOptions {ContentTypeProvider = provider});
app.UseCookiePolicy();
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Latest);
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
var provider = new FileExtensionContentTypeProvider();
provider.Mappings[".flac"] = "audio/flac";
provider.Mappings[".m3u"] = "audio/m3u";
provider.Mappings[".opus"] = "audio/opus";
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
app.UseExceptionHandler("/Error");
// The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
app.UseHsts();
}
app.UseStaticFiles(new StaticFileOptions()
{
ContentTypeProvider = provider
});
app.UseCookiePolicy();
app.UseMvc();
}
}
app.UseMvc();
}
}
}