From fba809c0ff752b41affe807bc9bbcd5212642c11 Mon Sep 17 00:00:00 2001 From: jxpxxzj Date: Fri, 4 Oct 2019 00:05:38 +0800 Subject: [PATCH] update --- .gitignore | 3 +- PrivateCloudMusic/Entities/Music.cs | 21 +++ PrivateCloudMusic/Entities/Playlist.cs | 2 - PrivateCloudMusic/Handlers/CategoryHandler.cs | 85 +++++++++++- PrivateCloudMusic/Handlers/MusicHandler.cs | 24 ++-- PrivateCloudMusic/PrivateCloudMusic.csproj | 13 ++ PrivateCloudMusic/Program.cs | 8 +- PrivateCloudMusic/Proto/api.proto | 121 ++++++++++++------ .../Proxy/Middlewares/BaseMiddleware.cs | 2 +- .../Proxy/Middlewares/Logging.cs | 4 +- PrivateCloudMusic/Proxy/Middlewares/Preset.cs | 4 +- PrivateCloudMusic/Proxy/ProxyHandler.cs | 34 ++++- PrivateCloudMusic/Proxy/Router.cs | 12 +- PrivateCloudMusic/Services/MusicService.cs | 78 +++++++---- PrivateCloudMusic/Startup.cs | 8 +- PrivateCloudMusic/Utils/ConfigManager.cs | 12 ++ PrivateCloudMusic/Utils/StringUtils.cs | 60 +++++++++ 17 files changed, 379 insertions(+), 112 deletions(-) create mode 100644 PrivateCloudMusic/Utils/StringUtils.cs diff --git a/.gitignore b/.gitignore index 0756dd2..aad49d4 100644 --- a/.gitignore +++ b/.gitignore @@ -434,4 +434,5 @@ $RECYCLE.BIN/ *.litedb -.vscode/ \ No newline at end of file +.vscode/ +music/ \ No newline at end of file diff --git a/PrivateCloudMusic/Entities/Music.cs b/PrivateCloudMusic/Entities/Music.cs index d0fb883..a02b95b 100644 --- a/PrivateCloudMusic/Entities/Music.cs +++ b/PrivateCloudMusic/Entities/Music.cs @@ -1,5 +1,6 @@ using System.IO; using LiteDB; +using Pcm.Proto; namespace Pcm.Entities { @@ -15,11 +16,31 @@ public class Music public uint Track { get; set; } public uint TrackCount { get; set; } + public int PictureCount { get; set; } public string FileName => Path.GetFileName(FilePath); public string FilePath { get; set; } public int PlayCount { get; set; } public string MimeType { get; set; } public long Length { get; set; } + + public GetMusicResponseBody ToResp() => new GetMusicResponseBody() + { + Id = MusicId.ToString(), + Title = Title, + Album = Album ?? string.Empty, + Genres = { Genres }, + Performers = { Performers }, + Track = Track, + TrackCount = TrackCount != 0 ? TrackCount : Track, + PictureCount = PictureCount, + FileName = FileName, + PlayCount = PlayCount, + Length = Length, + MimeType = MimeType, + CreatedAt = MusicId.Timestamp + }; + + public static implicit operator GetMusicResponseBody(Music music) => music.ToResp(); } } \ No newline at end of file diff --git a/PrivateCloudMusic/Entities/Playlist.cs b/PrivateCloudMusic/Entities/Playlist.cs index 42876d5..819ab3e 100644 --- a/PrivateCloudMusic/Entities/Playlist.cs +++ b/PrivateCloudMusic/Entities/Playlist.cs @@ -12,7 +12,5 @@ public class Playlist [BsonRef("music")] public List Musics { get; set; } - - public DateTime CreatedAt { get; set; } } } \ No newline at end of file diff --git a/PrivateCloudMusic/Handlers/CategoryHandler.cs b/PrivateCloudMusic/Handlers/CategoryHandler.cs index b6f6fca..099a8dc 100644 --- a/PrivateCloudMusic/Handlers/CategoryHandler.cs +++ b/PrivateCloudMusic/Handlers/CategoryHandler.cs @@ -1,30 +1,101 @@ +using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; +using Pcm.Entities; using Pcm.Proto; using Pcm.Proxy; using Pcm.Services; +using Pcm.Utils; +using System; namespace Pcm.Handlers { public class CategoryHandler { - public async Task Get(Context ctx, Request req, Response resp) - { - return await Task.FromResult(StatusCode.Ok); - } - - public async Task List(Context ctx, Request req, Response resp) + public async Task ListByAlbum(Context ctx, Request req, Response resp) { var musics = MusicService.Instance.Collection.FindAll() .Select(t => t.ToResp()) .GroupBy(m => m.Album) .ToDictionary(m => m.Key, m => new ListMusicResponseBody() { Musics = { m.ToList() }}); - resp.Body.ListByAlbum = new ListByAlbumResponseBody() + resp.Body.ListByAlbumBody = new ListByAlbumResponseBody() { Albums = { musics } }; return await Task.FromResult(StatusCode.Ok); } + + + public async Task ListByPerformer(Context ctx, Request req, Response resp) + { + var musics = MusicService.Instance.Collection.FindAll() + // extend to musics with single performer + .SelectMany(m => m.Performers.Select(performer => new Music() + { + MusicId = m.MusicId, + Title = m.Title, + Album = m.Album, + Genres = m.Genres, + Performers = new[] {performer}, + Track = m.Track, + TrackCount = m.TrackCount, + PictureCount = m.PictureCount, + FilePath = m.FilePath, + PlayCount = m.PlayCount, + Length = m.Length, + MimeType = m.MimeType + })) + .GroupBy(t => t.Performers[0]) + .ToDictionary(t => t.Key, t => new List(t.Select(m => m.MusicId) + .Distinct() + .Select(m => MusicService.Instance.Get(m.ToString())) + .Select(m => m.ToResp()))) + .ToDictionary(t => t.Key, t => new ListMusicResponseBody() { Musics = { t.Value }}); + + resp.Body.ListByPerformerBody = new ListByPerformerResponseBody() + { + Performers = { musics } + }; + return await Task.FromResult(StatusCode.Ok); + } + + public async Task Search(Context ctx, Request req, Response resp) + { + var keywords = req.Body.ListSearchBody.Keyword.ToLower().RemoveSpecialCharacters().Split(" "); + var searchThreshold = ConfigManager.GetInt("searchThreshold", 2); + + var musics = MusicService.Instance.Collection.FindAll() + // extend to musics with single performer + .SelectMany(m => m.Performers.Select(performer => new Music() + { + MusicId = m.MusicId, + Title = m.Title, + Album = m.Album ?? string.Empty, + Genres = m.Genres, + Performers = new[] {performer}, + Track = m.Track, + TrackCount = m.TrackCount, + PictureCount = m.PictureCount, + FilePath = m.FilePath, + PlayCount = m.PlayCount, + Length = m.Length, + MimeType = m.MimeType + })) + .Where(t => t.Album.ToLower().Distance(keywords) <= searchThreshold || + t.Title.ToLower().Distance(keywords) <= searchThreshold || + t.Performers[0].ToLower().Distance(keywords) <= searchThreshold || + t.FileName.ToLower().Distance(keywords) <= searchThreshold) + .Select(t => t.MusicId) + .Distinct() + .Select(t => MusicService.Instance.Get(t.ToString())) + .Select(t => t.ToResp()); + + resp.Body.ListSearchBody = new ListSearchResponseBody() + { + Musics = { musics } + }; + return await Task.FromResult(StatusCode.Ok); + } } } \ No newline at end of file diff --git a/PrivateCloudMusic/Handlers/MusicHandler.cs b/PrivateCloudMusic/Handlers/MusicHandler.cs index 82a549d..74e182f 100644 --- a/PrivateCloudMusic/Handlers/MusicHandler.cs +++ b/PrivateCloudMusic/Handlers/MusicHandler.cs @@ -1,3 +1,4 @@ +using System.Linq; using System.Threading.Tasks; using Pcm.Proto; using Pcm.Proxy; @@ -12,27 +13,20 @@ public async Task Get(Context ctx, Request req, Response resp) var music = MusicService.Instance.Get(req.Body.GetMusicBody.Id); if (music != null) { - resp.Body.GetMusicBody = new GetMusicResponseBody() - { - Title = music.Title, - Album = music.Album, - Genres = { music.Genres }, - Performers = { music.Performers }, - Track = music.Track, - TrackCount = music.TrackCount, - FileName = music.FileName, - PlayCount = music.PlayCount, - Length = music.Length, - MimeType = music.MimeType, - CreatedAt = music.MusicId.Timestamp - }; + resp.Body.GetMusicBody = music; } return await Task.FromResult(StatusCode.Ok); } - + public async Task List(Context ctx, Request req, Response resp) { + var musics = MusicService.Instance.Collection.FindAll(); + resp.Body.ListMusicBody = new ListMusicResponseBody() + { + Musics = { musics.Select(m => m.ToResp()) } + }; + return await Task.FromResult(StatusCode.Ok); } diff --git a/PrivateCloudMusic/PrivateCloudMusic.csproj b/PrivateCloudMusic/PrivateCloudMusic.csproj index 3e1a266..c9cf96e 100644 --- a/PrivateCloudMusic/PrivateCloudMusic.csproj +++ b/PrivateCloudMusic/PrivateCloudMusic.csproj @@ -27,6 +27,19 @@ + + + + + + + + + + + + + diff --git a/PrivateCloudMusic/Program.cs b/PrivateCloudMusic/Program.cs index de84b0b..4b5eccb 100644 --- a/PrivateCloudMusic/Program.cs +++ b/PrivateCloudMusic/Program.cs @@ -1,7 +1,6 @@ using Microsoft.AspNetCore.Hosting; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Hosting; -using Pcm.Services; using Pcm.Utils; namespace Pcm @@ -11,15 +10,10 @@ public class Program public static void Main(string[] args) { ConfigManager.Config = new ConfigurationBuilder() - .AddJsonFile("config.json", optional: true) //this is not needed, but could be useful + .AddJsonFile("config.json", true) //this is not needed, but could be useful .AddEnvironmentVariables() .AddCommandLine(args) .Build(); - - var m = MusicService.Instance.Add(@"/Users/xiaozijin/Downloads/03.オードリー.mp3"); - - - CreateHostBuilder(args).Build().Run(); } diff --git a/PrivateCloudMusic/Proto/api.proto b/PrivateCloudMusic/Proto/api.proto index fb0ad34..3edfa7b 100644 --- a/PrivateCloudMusic/Proto/api.proto +++ b/PrivateCloudMusic/Proto/api.proto @@ -9,22 +9,56 @@ message GetMusicRequestBody { string id = 1; } -message GetMusicResponseBody { - string title = 1; - string album = 2; - repeated string genres = 3; - repeated string performers = 4; - uint32 track = 5; - uint32 track_count = 6; - string file_name = 7; - int32 play_count = 8; - int64 length = 9; - string mime_type = 10; - int64 created_at = 11; +message ListMusicRequestBody { + +} + +message ListByAlbumRequestBody { + +} + +message ListByPerformerRequestBody { + +} + +message ListSearchRequestBody { + string keyword = 1; } // response +message GetMusicResponseBody { + string id = 1; + string title = 2; + string album = 3; + repeated string genres = 4; + repeated string performers = 5; + uint32 track = 6; + uint32 track_count = 7; + string file_name = 8; + int32 play_count = 9; + int32 picture_count = 10; + int64 length = 11; + string mime_type = 12; + int64 created_at = 13; +} + +message ListMusicResponseBody { + repeated GetMusicResponseBody musics = 1; +} + +message ListByAlbumResponseBody { + map albums = 1; +} + +message ListByPerformerResponseBody { + map performers = 1; +} + +message ListSearchResponseBody { + repeated GetMusicResponseBody musics = 1; +} + // base message Request { @@ -39,26 +73,53 @@ message Request { message RequestBody { GetMusicRequestBody get_music_body = 100; + ListMusicRequestBody list_music_body = 200; + // 201 + ListByAlbumRequestBody list_by_album_body = 202; + ListByPerformerRequestBody list_by_performer_body = 203; + // 204 + // 205 + ListSearchRequestBody list_search_body = 206; +} + + +message Response { + int32 cmd = 1; + int64 sequence_id = 2; //Same as request + int32 status_code = 3; //refer to enum StatusCode + string error_desc = 4; + ResponseBody body = 6; + string log_id = 7; + map extra = 8; +} + +message ResponseBody { + GetMusicResponseBody get_music_body = 100; + ListMusicResponseBody list_music_body = 200; + // 201 + ListByAlbumResponseBody list_by_album_body = 202; + ListByPerformerResponseBody list_by_performer_body = 203; + // 204 + // 205 + ListSearchResponseBody list_search_body = 206; } + enum CMD { CMD_NOT_USED = 0; - + GET_MUSIC = 100; LIST_MUSIC = 200; LIST_PLAYLIST = 201; - LIST_ALBUM = 202; - LIST_ARTIST = 203; - LIST_TYPE = 204; + LIST_BY_ALBUM = 202; + LIST_BY_PERFORMER = 203; + LIST_BY_GENRE = 204; LIST_FOLDER_TREE = 205; - + LIST_SEARCH = 206; + GET_PLAYLIST = 300; - GET_ALBUM = 301; - GET_ARTIST = 302; - GET_TYPE = 303; - GET_FOLDER = 304; - + ACTION_RESCAN_MUSIC = 400; ACTION_MODIFY_MUSIC_EXT = 401; ACTION_ADD_MUSIC = 402; @@ -68,7 +129,7 @@ enum CMD { ACTION_ADD_MUSIC_TO_PLAYLIST = 406; ACTION_REMOVE_MUSIC_FROM_PLAYLIST = 407; ACTION_DELETE_PLAYLIST = 408; - + REPORT_PLAYER_START = 500; REPORT_MUSIC_STOP = 503; } @@ -81,20 +142,6 @@ enum Refer { SERVER = 4; } -message Response { - int32 cmd = 1; - int64 sequence_id = 2; //Same as request - int32 status_code = 3; //refer to enum StatusCode - string error_desc = 4; - ResponseBody body = 6; - string log_id = 7; - map extra = 8; -} - -message ResponseBody { - GetMusicResponseBody get_music_body = 100; -} - enum StatusCode { OK = 0; INVALID_TOKEN = 1; diff --git a/PrivateCloudMusic/Proxy/Middlewares/BaseMiddleware.cs b/PrivateCloudMusic/Proxy/Middlewares/BaseMiddleware.cs index bad750d..f18aed0 100644 --- a/PrivateCloudMusic/Proxy/Middlewares/BaseMiddleware.cs +++ b/PrivateCloudMusic/Proxy/Middlewares/BaseMiddleware.cs @@ -2,6 +2,6 @@ namespace Pcm.Proxy.Middlewares { public abstract class BaseMiddleware { - public abstract Endpoint Create(Endpoint func); + public abstract Endpoint Create(Endpoint next); } } \ No newline at end of file diff --git a/PrivateCloudMusic/Proxy/Middlewares/Logging.cs b/PrivateCloudMusic/Proxy/Middlewares/Logging.cs index a96e60f..e3a21d8 100644 --- a/PrivateCloudMusic/Proxy/Middlewares/Logging.cs +++ b/PrivateCloudMusic/Proxy/Middlewares/Logging.cs @@ -9,7 +9,7 @@ public class Logging : BaseMiddleware { public static readonly string K_LOGID = "logid"; - public override Endpoint Create(Endpoint func) + public override Endpoint Create(Endpoint next) { return async (ctx, req, resp) => { @@ -22,7 +22,7 @@ public override Endpoint Create(Endpoint func) try { - code = await func(ctx, req, resp); + code = await next(ctx, req, resp); } catch (Exception ex) { diff --git a/PrivateCloudMusic/Proxy/Middlewares/Preset.cs b/PrivateCloudMusic/Proxy/Middlewares/Preset.cs index 2f41f4d..8212a93 100644 --- a/PrivateCloudMusic/Proxy/Middlewares/Preset.cs +++ b/PrivateCloudMusic/Proxy/Middlewares/Preset.cs @@ -5,7 +5,7 @@ namespace Pcm.Proxy.Middlewares { public class Preset : BaseMiddleware { - public override Endpoint Create(Endpoint func) + public override Endpoint Create(Endpoint next) { return async (ctx, req, resp) => { @@ -17,7 +17,7 @@ public override Endpoint Create(Endpoint func) try { - code = await func(ctx, req, resp); + code = await next(ctx, req, resp); } catch (Exception ex) { diff --git a/PrivateCloudMusic/Proxy/ProxyHandler.cs b/PrivateCloudMusic/Proxy/ProxyHandler.cs index 6aa041f..0d43bcd 100644 --- a/PrivateCloudMusic/Proxy/ProxyHandler.cs +++ b/PrivateCloudMusic/Proxy/ProxyHandler.cs @@ -4,11 +4,9 @@ using System.Threading; using System.Threading.Tasks; using Microsoft.AspNetCore.Http; -using System.Buffers; using System.IO; using System.Linq; using System.Net; -using System.Net.Http.Headers; using System.Text; using Google.Protobuf; using Microsoft.AspNetCore.Routing; @@ -20,7 +18,7 @@ namespace Pcm.Proxy { - public class MessageHandler + public class ProxyHandler { private readonly Dictionary _webSockets = new Dictionary(); @@ -30,7 +28,7 @@ public class MessageHandler private readonly Router _router = new Router(); - public MessageHandler() + public ProxyHandler() { addMiddlewares(); addParser(); @@ -41,8 +39,11 @@ public MessageHandler() private void addMiddlewares() { + // outer _middlewares.Add(new Logging()); _middlewares.Add(new Preset()); + // inner + // router } private void addParser() @@ -164,5 +165,30 @@ public async Task HandleMusic(HttpContext ctx) await ctx.Response.SendFileAsync(fi); } + + public async Task HandleCover(HttpContext ctx) + { + var id = ctx.GetRouteValue("id").ToString(); + var indexObj = ctx.GetRouteValue("index"); + var index = 0; + if (indexObj != null) + { + index = int.Parse(indexObj.ToString()); + } + + var music = MusicService.Instance.Get(id); + if (music == null) + { + throw new FileNotFoundException(); + } + + var tfile = TagLib.File.Create(music.FilePath); + index = Math.Max(0, index); + index = Math.Min(index, tfile.Tag.Pictures.Count() - 1); + var img = tfile.Tag.Pictures[index]; + + ctx.Response.ContentType = img.MimeType; + await ctx.Response.BodyWriter.WriteAsync(img.Data.Data); + } } } \ No newline at end of file diff --git a/PrivateCloudMusic/Proxy/Router.cs b/PrivateCloudMusic/Proxy/Router.cs index 0dc4e7b..c2c3aa3 100644 --- a/PrivateCloudMusic/Proxy/Router.cs +++ b/PrivateCloudMusic/Proxy/Router.cs @@ -17,17 +17,19 @@ private void initRouter() { var defaultHandler = new DefaultHandler(); var music = new MusicHandler(); - var album = new AlbumHandler(); + var category = new CategoryHandler(); var playlist = new PlaylistHandler(); register(CMD.NotUsed, defaultHandler.Default); // 0 register(CMD.GetMusic, music.Get); // 100 - register(CMD.GetPlaylist, playlist.Get); // 200 - register(CMD.GetAlbum, album.Get); // 201 - + register(CMD.ListMusic, music.List); // 200 + register(CMD.GetPlaylist, playlist.Get); // 201 + register(CMD.ListSearch, category.Search); // 206 + register(CMD.ListPlaylist, playlist.List); // 301 - register(CMD.ListAlbum, album.List); // 302 + register(CMD.ListByAlbum, category.ListByAlbum); // 302 + register(CMD.ListByPerformer, category.ListByPerformer); // 303 register(CMD.ActionCreatePlaylist, playlist.Create); // 404 register(CMD.ActionRenamePlaylist, playlist.Rename); // 405 diff --git a/PrivateCloudMusic/Services/MusicService.cs b/PrivateCloudMusic/Services/MusicService.cs index 9021efb..07311c4 100644 --- a/PrivateCloudMusic/Services/MusicService.cs +++ b/PrivateCloudMusic/Services/MusicService.cs @@ -1,8 +1,10 @@ using System; using System.IO; +using System.Linq; using LiteDB; using Pcm.Entities; using Pcm.Utils; +using TagLib; namespace Pcm.Services { @@ -12,22 +14,26 @@ public class MusicService public static MusicService Instance => lazy.Value; private FileSystemWatcher fsw; + public DirectoryInfo MusicDir { get; set; } private MusicService() { Collection = DbManager.DB.GetCollection("music"); var musicPath = ConfigManager.Get("musicPath", "music"); - var dir = Directory.Exists(musicPath) ? new DirectoryInfo(musicPath) : Directory.CreateDirectory(musicPath); + MusicDir = Directory.Exists(musicPath) ? new DirectoryInfo(musicPath) : Directory.CreateDirectory(musicPath); fsw = new FileSystemWatcher() { - Path = dir.FullName, + Path = MusicDir.FullName, IncludeSubdirectories = true }; fsw.Created += FswOnCreated; fsw.Changed += FswOnChanged; fsw.Deleted += FswOnDeleted; + + // init when started + Scan(); } private void FswOnDeleted(object sender, FileSystemEventArgs e) @@ -61,34 +67,56 @@ public Music Get(string id) public Music Add(string filePath) { - var exist = Collection.FindOne(t => t.FilePath == filePath); - if (exist != null) + try { - return exist; - } + var exist = Collection.FindOne(t => t.FilePath == filePath); + if (exist != null) + { + return exist; + } - var tfile = TagLib.File.Create(filePath); - - var id = ObjectId.NewObjectId(); + var tfile = TagLib.File.Create(filePath); + + if ((tfile.Properties.MediaTypes & MediaTypes.Audio) != 0) + { + var id = ObjectId.NewObjectId(); - var music = new Music - { - MusicId = id, - Title = tfile.Tag.Title, - Album = tfile.Tag.Album, - Genres = tfile.Tag.Genres, - Performers = tfile.Tag.Performers, - Track = tfile.Tag.Track, - TrackCount = tfile.Tag.TrackCount, - Length = (int)tfile.Properties.Duration.TotalSeconds, - MimeType = tfile.MimeType, - FilePath = filePath, - PlayCount = 0, - }; + var music = new Music + { + MusicId = id, + Title = tfile.Tag.Title, + Album = tfile.Tag.Album, + Genres = tfile.Tag.Genres, + Performers = tfile.Tag.Performers, + Track = tfile.Tag.Track, + TrackCount = tfile.Tag.TrackCount, + PictureCount = tfile.Tag.Pictures.Count(), + Length = (int)tfile.Properties.Duration.TotalSeconds, + MimeType = tfile.MimeType, + FilePath = filePath, + PlayCount = 0, + }; - var bsonId = Collection.Insert(music); + Collection.Insert(music); + return Get(id.ToString()); + } + } + catch + { + // ignored + } + + return null; + } - return Get(id.ToString()); + public void Scan() + { + var files = Directory.GetFiles(MusicDir.FullName, "*.*", SearchOption.AllDirectories) + .Where(f => Path.GetFileName(f) != ".DS_Store"); // wtf macOS? + foreach (var file in files) + { + Add(file); + } } } diff --git a/PrivateCloudMusic/Startup.cs b/PrivateCloudMusic/Startup.cs index 54c474a..3f5f37f 100644 --- a/PrivateCloudMusic/Startup.cs +++ b/PrivateCloudMusic/Startup.cs @@ -1,4 +1,3 @@ -using System; using Anemonis.AspNetCore.RequestDecompression; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; @@ -25,7 +24,7 @@ public void ConfigureServices(IServiceCollection services) // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. public void Configure(IApplicationBuilder app, IWebHostEnvironment env) { - var handler = new MessageHandler(); + var handler = new ProxyHandler(); if (env.IsDevelopment()) { @@ -37,8 +36,9 @@ public void Configure(IApplicationBuilder app, IWebHostEnvironment env) app.UseEndpoints(endpoints => { - endpoints.MapPost("/http", async context => await handler.HandleHttp(context)); - endpoints.MapGet("/music/{id}", async context => await handler.HandleMusic(context)); + endpoints.MapPost("/http", context => handler.HandleHttp(context)); + endpoints.MapGet("/music/{id}", context => handler.HandleMusic(context)); + endpoints.MapGet("/cover/{id}/{index?}", context => handler.HandleCover(context)); }); app.Use(async (context, next) => diff --git a/PrivateCloudMusic/Utils/ConfigManager.cs b/PrivateCloudMusic/Utils/ConfigManager.cs index 2df52c2..cce4aac 100644 --- a/PrivateCloudMusic/Utils/ConfigManager.cs +++ b/PrivateCloudMusic/Utils/ConfigManager.cs @@ -10,5 +10,17 @@ public static string Get(string key, string defaultVal = "") { return Config[key] ?? defaultVal; } + + public static int GetInt(string key, int defaultVal = 0) + { + var strVal = Get(key); + + if (string.IsNullOrEmpty(strVal) || !int.TryParse(strVal, out var val)) + { + return defaultVal; + } + + return val; + } } } \ No newline at end of file diff --git a/PrivateCloudMusic/Utils/StringUtils.cs b/PrivateCloudMusic/Utils/StringUtils.cs new file mode 100644 index 0000000..40736ab --- /dev/null +++ b/PrivateCloudMusic/Utils/StringUtils.cs @@ -0,0 +1,60 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text.RegularExpressions; + +namespace Pcm.Utils +{ + public static class StringUtils + { + public static int Distance(this string s, string t) + { + if (s == null) s = string.Empty; + if (t == null) t = string.Empty; + + var bounds = new { Height = s.Length + 1, Width = t.Length + 1 }; + + var matrix = new int[bounds.Height, bounds.Width]; + + for (var height = 0; height < bounds.Height; height++) { matrix[height, 0] = height; }; + for (var width = 0; width < bounds.Width; width++) { matrix[0, width] = width; }; + + for (var height = 1; height < bounds.Height; height++) + { + for (var width = 1; width < bounds.Width; width++) + { + var cost = s[height - 1] == t[width - 1] ? 0 : 1; + var insertion = matrix[height, width - 1] + 1; + var deletion = matrix[height - 1, width] + 1; + var substitution = matrix[height - 1, width - 1] + cost; + + var distance = Math.Min(insertion, Math.Min(deletion, substitution)); + + if (height > 1 && width > 1 && s[height - 1] == t[width - 2] && s[height - 2] == t[width - 1]) + { + distance = Math.Min(distance, matrix[height - 2, width - 2] + cost); + } + + matrix[height, width] = distance; + } + } + + return matrix[bounds.Height - 1, bounds.Width - 1]; + } + + private static readonly Regex _charsReg = new Regex(@"[^\p{IsBasicLatin}\p{IsHiragana}\p{IsKatakana}\p{IsCJKUnifiedIdeographs}\s]", RegexOptions.Compiled); + private static readonly Regex _specReg = new Regex("[ \\[ \\] \\^ \\-_*×――(^)(^)$%~!@#$…&%¥—+=<>《》!!???::•`·、。,;,.;\"‘’“”'-]", RegexOptions.Compiled); + + public static string RemoveSpecialCharacters(this string s) + { + var strCjk = _charsReg.Replace(s, " "); + var rawStr = _specReg.Replace(strCjk, " "); + return rawStr; + } + + public static int Distance(this string s, IEnumerable t) + { + return s.RemoveSpecialCharacters().Split(" ").SelectMany(str => t.Select(str.Distance)).Min(); + } + } +} \ No newline at end of file