Remove now-obsolete testplaza WinForms app project (may revisit in future as I do intend on a .Net Core-compatible GUI app). Add inline documentation for the libplaza library project. Fix some bugs with the WinPlaza GUI app.

This commit is contained in:
Maff 2019-11-05 16:45:22 +00:00
parent 104901142e
commit 73914ad8a0
15 changed files with 204 additions and 721 deletions

View File

@ -5,12 +5,7 @@ VisualStudioVersion = 16.0.29306.81
MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WinPlaza", "WinPlaza\WinPlaza.csproj", "{72EEB593-D74D-4775-875A-CD0078923EC3}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "testplaza", "testplaza\testplaza.csproj", "{DAB02D69-CD02-4C74-A110-7907E5B4A568}"
ProjectSection(ProjectDependencies) = postProject
{B8CD29C0-4FAF-4A02-B00C-3180E60841D3} = {B8CD29C0-4FAF-4A02-B00C-3180E60841D3}
EndProjectSection
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "libplaza", "libplaza\libplaza.csproj", "{B8CD29C0-4FAF-4A02-B00C-3180E60841D3}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "libplaza", "libplaza\libplaza.csproj", "{B8CD29C0-4FAF-4A02-B00C-3180E60841D3}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
@ -22,10 +17,6 @@ Global
{72EEB593-D74D-4775-875A-CD0078923EC3}.Debug|Any CPU.Build.0 = Debug|Any CPU
{72EEB593-D74D-4775-875A-CD0078923EC3}.Release|Any CPU.ActiveCfg = Release|Any CPU
{72EEB593-D74D-4775-875A-CD0078923EC3}.Release|Any CPU.Build.0 = Release|Any CPU
{DAB02D69-CD02-4C74-A110-7907E5B4A568}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{DAB02D69-CD02-4C74-A110-7907E5B4A568}.Debug|Any CPU.Build.0 = Debug|Any CPU
{DAB02D69-CD02-4C74-A110-7907E5B4A568}.Release|Any CPU.ActiveCfg = Release|Any CPU
{DAB02D69-CD02-4C74-A110-7907E5B4A568}.Release|Any CPU.Build.0 = Release|Any CPU
{B8CD29C0-4FAF-4A02-B00C-3180E60841D3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{B8CD29C0-4FAF-4A02-B00C-3180E60841D3}.Debug|Any CPU.Build.0 = Debug|Any CPU
{B8CD29C0-4FAF-4A02-B00C-3180E60841D3}.Release|Any CPU.ActiveCfg = Release|Any CPU

View File

@ -58,7 +58,13 @@ namespace WinPlaza {
SetStatus($"Logged in as {await plaza.GetUser()}");
}
private async void DgtRefresh(bool Force = false) {
Status s=await plaza.Broadcast(Force);
Status s;
try {
s=await plaza.Status(Force);
} catch (NullReferenceException) {
SetStatus("Ran into a problem while fetching now-playing.");
return;
}
if(s.FaultOccurred||s.InMaintenance) {
if(s.InMaintenance)
SetStatus("Nightwave Plaza is currently in maintenance mode.");
@ -66,10 +72,6 @@ namespace WinPlaza {
SetStatus("Ran into a problem while fetching now-playing.");
return;
}
if(s.ArtworkUri == null || s.Duration == 0) {
Console.WriteLine("why is async so hard");
throw new Exception("Something went real weird, woah. Time to Crash!");
}
slDuration.Value = s.CalculatedElapsed;
sbListeners.Text = $"{s.Listeners} listeners";
lbLikeCt.Content = s.Likes;
@ -77,9 +79,9 @@ namespace WinPlaza {
lbElapsed.Content = $"{(s.CalculatedElapsed / 60).ToString("D")}:{(s.CalculatedElapsed % 60).ToString("D2")}";
if(LastArtwork != s.ArtworkUri || Force) {
//TODO should probably break this part out into a separate function cause this feels real messy
Nightwave.Vote v = await plaza.GetVote() ?? Nightwave.Vote.Neutral;
Vote v = await plaza.Vote() ?? Vote.Neutral;
switch(v) {
case Nightwave.Vote.Dislike:
case Vote.Dislike:
sbVote.Text = "👎";
sbVote.ToolTip = "You dislike this.";
btLike.Content = "👍";
@ -87,7 +89,7 @@ namespace WinPlaza {
btDislike.Content = "🤷‍♀️";
btDislike.ToolTip = "Remove your vote for this track";
break;
case Nightwave.Vote.Like:
case Vote.Like:
sbVote.Text = "👍";
sbVote.ToolTip = "You like this.";
btLike.Content = "💖";
@ -95,7 +97,7 @@ namespace WinPlaza {
btDislike.Content = "👎";
btDislike.ToolTip = "Dislike this track";
break;
case Nightwave.Vote.Favourite:
case Vote.Favourite:
sbVote.Text = "💖";
sbVote.ToolTip = "You have favourited this.";
btLike.Content = "🤷‍♀️";
@ -270,14 +272,14 @@ namespace WinPlaza {
}
private async void BtLike_Click(object sender, RoutedEventArgs e) {
_ = Dispatcher.BeginInvoke(new dgtCtrl(DgtToggleEnabled), btLike);
Nightwave.Vote v = await plaza.GetVote() ?? Nightwave.Vote.Neutral;
if(v == Nightwave.Vote.Like)
v = Nightwave.Vote.Favourite;
else if(v == Nightwave.Vote.Favourite)
v = Nightwave.Vote.Neutral;
Vote v = await plaza.Vote() ?? Vote.Neutral;
if(v == Vote.Like)
v = Vote.Favourite;
else if(v == Vote.Favourite)
v = Vote.Neutral;
else
v = Nightwave.Vote.Like;
if(await plaza.CastVote(v)) {
v = Vote.Like;
if(await plaza.Vote(v)) {
SetStatus($"Submitted {v}!");
} else
SetStatus($"Something went weird while casting {v}");
@ -286,10 +288,10 @@ namespace WinPlaza {
}
private async void BtDislike_Click(object sender, RoutedEventArgs e) {
_ = Dispatcher.BeginInvoke(new dgtCtrl(DgtToggleEnabled), btDislike);
Nightwave.Vote v = Nightwave.Vote.Dislike;
if(await plaza.GetVote() == Nightwave.Vote.Dislike)
v = Nightwave.Vote.Neutral;
if(await plaza.CastVote(v))
Vote v = Vote.Dislike;
if(await plaza.Vote() == Vote.Dislike)
v = Vote.Neutral;
if(await plaza.Vote(v))
SetStatus($"Submitted {v}!");
else
SetStatus($"Something went weird while casting {v}");

View File

@ -4,12 +4,11 @@ using System.Net.Http;
using System.Threading.Tasks;
using Newtonsoft.Json;
/// <summary>
/// Classes and other definitions relating to the <c>Nightwave</c> Plaza API
/// </summary>
namespace libplaza {
internal class PlazaApi {
internal const string ApiBase="https://api.plaza.one/";
internal const string ArtworkBase="https://plaza.one/";
internal const string RadioBase="https://radio.plaza.one/";
}
#region API models
public class PlazaPlayback {
public string artist;
public string title;
@ -42,56 +41,159 @@ namespace libplaza {
public class PlazaGetVote {
public int rate;
}
public class PlazaLogoutResult {
public string status;
}
public class PlazaLogonResult {
public string status;
public string username;
public string email;
public string token;
}
#endregion
#region Exceptions
/// <summary>
/// An <see cref="Exception"/> object indicating that the <c>Nightwave</c> API is under maintenance
/// </summary>
internal class PlazaInMaintenanceException : Exception { }
/// <summary>
/// An <see cref="Exception"/> object indicating that an error or exception occurred when communicating with the <c>Nightwave</c> API.
/// </summary>
internal class PlazaUnavailableException : Exception { }
#endregion
#region Type Definitions
/// <summary>
/// A <c>Status</c> object represents the current broadcast information returned by a <c>Nightwave</c> API object.
/// </summary>
public class Status {
/// <summary><c>InMaintenance</c> indicates whether the <c>Nightwave</c> API is in maintenance mode</summary>
public bool InMaintenance;
/// <summary><c>FaultOccurred</c> indicates whether an exception occurred while querying the <c>Nightwave</c> API</summary>
public bool FaultOccurred;
/// <summary><c>Title</c> contains the currently-playing track's title</summary>
public string Title;
/// <summary><c>Artist</c> contains the currently-playing track's creating artist</summary>
public string Artist;
/// <summary><c>Album</c> contains the currently-playing track's album, if any</summary>
public string Album;
/// <summary><c>ArtworkUri</c> contains a relative URI to the artwork for the currently-playing track</summary>
public string ArtworkUri;
/// <summary><c>Elapsed</c> contains an integer value of the elapsed seconds for the current track</summary>
public int Elapsed;
/// <summary><c>Duration</c> contains an integer value of the duration in seconds of the current track</summary>
public int Duration;
/// <summary><c>Since</c> contains a 64-bit integer value of the epoch timestamp when the broadcast information was updated</summary>
public long Since;
/// <summary><c>ServerTimeOffset</c> contains an integer value of the time difference between the local computer and the <c>Nightwave</c> API server</summary>
public int ServerTimeOffset;
public int CalculatedElapsed =>
/// <summary><c>CalculatedElapsed</c> returns a dynamically-generated integer value of the elapsed seconds, taking time offsets and time since <see cref="Since"/></summary>
public int CalculatedElapsed =>
(int)(DateTimeOffset.Now.ToUnixTimeSeconds() - ServerTimeOffset - Since) + Elapsed;
/// <summary><c>Likes</c> contains an integer value of the number of likes the currently-playing track has received</summary>
public int Likes;
/// <summary><c>Dislikes</c> contains an integer value of the number of dislikes the currently-playing track has received</summary>
public int Dislikes;
/// <summary><c>Listeners</c> contains an integer value of the number of people currently listening to the broadcast</summary>
public int Listeners;
}
/// <summary>
/// <c>Vote</c> represents the user's disposition towards the currently-playing track.
/// </summary>
public enum Vote {
/// <summary>To <c>Dislike</c> is to indicate the user's disposition towards this track is one of displeasure; that the user does not appreciate hearing this track, perhaps even that the user wishes not to hear this track.</summary>
Dislike = -1,
/// <summary>To be of <c>Neutral</c> disposition is to indicate the user has no feelings towards this track, or that the user does have feelings, but not strong enough to be worth expressing</summary>
Neutral,
/// <summary>To <c>Like</c> is to indicate the user's disposition towards this track is one of enjoyment or pleasure; that the user appreciated this track and/or wishes to indicate such.</summary>
Like,
/// <summary>To <c>Favourite</c> is functionally equivalent to <see cref="Like"/>, with the additional effect of adding the track to the user's favourites list.</summary>
Favourite
}
#endregion
/// <summary>
/// <c>Nightwave</c> is the primary class by which the Nightwave Plaza API can be used.
/// </summary>
public class Nightwave {
/// <summary><c>MinRefreshTime</c> represents the minimum number of seconds that must pass before the cached <c>Status</c> may be invalidated</summary>
public int MinRefreshTime=10;
private long tsLastRefresh=0;
private static HttpClient _hcl=new HttpClient();
public enum Vote {
Dislike=-1,
Neutral,
Like,
Favourite
}
private Status _bc;
public Nightwave() {
_hcl.BaseAddress = new Uri(PlazaApi.ApiBase);
/// <summary>
/// Creates a new <c>Nightwave</c> instance.
/// </summary>
/// <param name="baseuri">Specify an alternate URI for the API - useful if testing a dev server.</param>
/// <example>
/// To create a <c>Nightwave</c> API object targeting the production Nightwave Plaza API:
/// <code>
/// Nightwave nw = new Nightwave();
/// </code>
/// To create a <c>Nightwave</c> API object targeting a local server (eg., during development):
/// <code>Nightwave nwd = new Nightwave("http://localhost:8000");</code>
/// </example>
public Nightwave(string baseuri = "https://api.plaza.one/") {
_hcl.BaseAddress = new Uri(baseuri);
_hcl.Timeout = TimeSpan.FromSeconds(2);
_hcl.DefaultRequestHeaders.Accept.Clear();
_hcl.DefaultRequestHeaders.Accept.Add(
new System.Net.Http.Headers.MediaTypeWithQualityHeaderValue("application/json"));
}
public async Task<Status> Broadcast(bool Force = false) {
if(!Force && tsLastRefresh + MinRefreshTime >= DateTimeOffset.Now.ToUnixTimeSeconds() && _bc.CalculatedElapsed < _bc.Duration)
return _bc;
await Update();
tsLastRefresh = DateTimeOffset.Now.ToUnixTimeSeconds();
/// <summary>
/// De-initialises the <c>Nightwave</c> API object.
/// <para>Currently, this is equivalent to synchronously logging out the current session.</para>
/// </summary>
~Nightwave() {
_ = Task.Run(async () => await Logout());
}
/// <summary>
/// Retrieves the current Nightwave Plaza radio broadcast information, if possible
/// </summary>
/// <example>
/// If Nightwave Plaza is broadcasting normally:
/// <code>
/// Nightwave n = new Nightwave();
/// Status s = await n.Status();
/// Console.WriteLine(s.Title);
/// </code>
/// The Console output would show "リサフランク420 / 現代のコンピュー", if that track were playing.
/// In the event that an HTTP request fails or Nightwave Plaza is in maintenance mode, the appropriate flag will be set
/// <code>
/// Nightwave n = new Nightwave();
/// Status s = await n.Status();
/// if (s.InMaintenance) {
/// Console.WriteLine("In maintenance!");
/// return;
/// } else if (s.FaultOccurred) {
/// Console.WriteLine("A fault occurred!");
/// return;
/// }
/// Console.WriteLine(s.Title);
/// </code>
/// The Console output would show "In maintenance!" or "A fault occurred!", depending on the conditions.
/// </example>
/// <remarks>
/// This method has inherent caching, and thus will not necessarily result in the <c>Status</c> object returned being fully fresh.
/// The <c>Status</c> object being returned may be a cached object UNLESS the <paramref name="Force"/> parameter is true
/// OR the <c>Status</c> object is older than the number of seconds stored by <see cref="MinRefreshTime"/>
/// OR <c>Status.CalculatedElapsed</c> is equal to or greater than <c>Status.Duration</c>.
/// </remarks>
/// <param name="Force">If true, the API will always be queried, and no caching will be performed.</param>
/// <returns>A populated <c>Status</c> object describing the current broadcast details.</returns>
public async Task<Status> Status(bool Force = false) {
//An API call will ONLY be made if Force is set, if the cached data is "stale",
// or if the currently-playing track's elapsed time is more than the track duration.
if(Force
|| (tsLastRefresh + MinRefreshTime < DateTimeOffset.Now.ToUnixTimeSeconds())
|| _bc.CalculatedElapsed < _bc.Duration) {
await Update();
tsLastRefresh = DateTimeOffset.Now.ToUnixTimeSeconds();
}
return _bc;
}
/// <summary>
/// A basic wrapper around <see cref="GetNightwaveAsync"/> which provides basic error handling.
/// </summary>
/// <returns>If called with <c>await</c>, returns nothing. If called without, returns a <c>Task</c> object for the method call</returns>
public async Task Update() {
try {
Status _=await GetNightwaveAsync();
@ -105,12 +207,17 @@ namespace libplaza {
_bc.FaultOccurred = true;
}
}
/// <summary>
/// Communicates with the <c>Nightwave</c> API to retrieve the current broadcast information.
/// </summary>
/// <returns>Returns a <c>Status</c> object representing the current broadcast information.</returns>
/// <exception cref="PlazaUnavailableException">May be thrown in event of a non-success (HTTP/1.1 2XX) return code</exception>
/// <exception cref="PlazaInMaintenanceException">May be thrown in event that the API indicates it is in maintenance</exception>
public async Task<Status> GetNightwaveAsync() {
Status status;
HttpResponseMessage _r=await _hcl.GetAsync("/status");
if(!_r.IsSuccessStatusCode)
throw new PlazaUnavailableException();
//string _=await _r.Content.ReadAsStringAsync();
PlazaJson j=JsonConvert.DeserializeObject<PlazaJson>(await _r.Content.ReadAsStringAsync());
if(j.maintenance)
throw new PlazaInMaintenanceException();
@ -125,11 +232,15 @@ namespace libplaza {
Dislikes = j.playback.hates,
Listeners = j.listeners,
ServerTimeOffset = (int)(DateTimeOffset.Now.ToUnixTimeSeconds() - _r.Headers.Date.Value.ToUnixTimeSeconds()),
ArtworkUri = $"{PlazaApi.ArtworkBase}{j.playback.artwork}"
ArtworkUri = $"https://plaza.one/{j.playback.artwork}"
};
return status;
}
public async Task<Vote?> GetVote() {
/// <summary>
/// Fetches the user's <see cref="Vote"/> for the currently-playing track.
/// </summary>
/// <returns>A <see cref="Vote"/> object indicating the user's disposition towards the track. If the vote could not be returned, this will be <c>null</c></returns>
public async Task<Vote?> Vote() {
if(!await CheckSession())
return null;
HttpResponseMessage _r=await _hcl.GetAsync("/vote");
@ -138,7 +249,12 @@ namespace libplaza {
PlazaGetVote r=JsonConvert.DeserializeObject<PlazaGetVote>(await _r.Content.ReadAsStringAsync());
return (Vote)r.rate;
}
public async Task<bool> CastVote(Vote v) {
/// <summary>
/// Casts the given <see cref="Vote"/> specified in <paramref name="v"/> as the user's vote on the current track.
/// </summary>
/// <param name="v">A <see cref="Vote"/> object indicating the user's disposition towards the current track</param>
/// <returns>A <c>bool</c>ean value indicating the success of the vote operation</returns>
public async Task<bool> Vote(Vote v) {
if(!await CheckSession())
return false;
HttpResponseMessage _r=await _hcl.PostAsync("/vote", new FormUrlEncodedContent(new[] {
@ -150,16 +266,29 @@ namespace libplaza {
return false;
return true;
}
/// <summary>
/// An overload method to <see cref="CheckSession"/> to first set the <c>X-Access-Token</c> header to <paramref name="str"/>
/// </summary>
/// <param name="str">A valid session token for the <c>Nightwave</c> API</param>
/// <returns>A <c>bool</c>ean value indicating whether the token given in <paramref name="str"/> was able to perform an authenticated API request</returns>
public async Task<bool> CheckSession(string str) {
_=_hcl.DefaultRequestHeaders.Remove("X-Access-Token");
_ = _hcl.DefaultRequestHeaders.Remove("X-Access-Token");
_hcl.DefaultRequestHeaders.Add("X-Access-Token", str);
return await CheckSession();
}
/// <summary>
/// Performs a basic read-only API request to determine whether the session token, if present, is valid.
/// </summary>
/// <returns>A <c>bool</c>ean value indicating whether the request succeeded</returns>
public async Task<bool> CheckSession() {
if(await GetUser() != null)
return true;
return false;
}
/// <summary>
/// Performs a basic read-only API request to fetch the currently-authenticated user's profile details.
/// </summary>
/// <returns>If successful, returns a <c>string</c> containing the authenticated user's username. If unsuccessful, will return null.</returns>
public async Task<string> GetUser() {
HttpResponseMessage _r=await _hcl.GetAsync("/user");
if(!_r.IsSuccessStatusCode)
@ -169,7 +298,22 @@ namespace libplaza {
return null;
return r.username;
}
public async Task<bool> Login(string u, string p) => await Login(Convert.ToBase64String(System.Text.Encoding.ASCII.GetBytes($"{u}:{p}")));
/// <summary>
/// Performs a login request to the <c>Nightwave</c> API with the given <paramref name="u">username</paramref> and <paramref name="p">password</paramref>.
/// </summary>
/// <param name="u">A <c>string</c> containing the user's username</param>
/// <param name="p">A <c>string</c> containing the user's password</param>
/// <returns>A <c>bool</c>ean value indicating whether the login attempt succeeded</returns>
/// <remarks>
/// Functionally, this method is a wrapper for <see cref="Login(string)"/>, performing the function of generating a Basic authentication string
/// </remarks>
public async Task<bool> Login(string u, string p) =>
await Login(Convert.ToBase64String(System.Text.Encoding.ASCII.GetBytes($"{u}:{p}")));
/// <summary>
/// Performs a login request to the <c>Nightwave</c> API with the given HTTP Basic authentication <paramref name="str">string</paramref>.
/// </summary>
/// <param name="str">A valid HTTP Basic authentication string (<c>md5(username:password)</c>)</param>
/// <returns>A <c>bool</c>ean value indicating whether the login attempt succeeded</returns>
public async Task<bool> Login(string str) {
_hcl.DefaultRequestHeaders.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Basic", str);
HttpResponseMessage _r=await _hcl.PostAsync("/user/login", null);
@ -179,15 +323,20 @@ namespace libplaza {
if(r.status != "success")
return false;
_hcl.DefaultRequestHeaders.Add("X-Access-Token", r.token);
_=_hcl.DefaultRequestHeaders.Remove("Authorization");
_ = _hcl.DefaultRequestHeaders.Remove("Authorization");
return true;
}
#if DEBUG
public string GetAuthorizationToken() {
IEnumerator<string> h=_hcl.DefaultRequestHeaders.GetValues("X-Access-Token").GetEnumerator();
_ = h.MoveNext();
return h.Current;
/// <summary>
/// Performs a logout request to the <c>Nightwave</c> API.
/// </summary>
/// <returns>If called with <c>await</c>, returns nothing. Otherwise, returns a <c>Task</c> object representing the method call</returns>
public async Task Logout() {
//Any API call errors are assumed to indicate an invalid session
// Since the goal of a logout function is to not have a valid session, this is a good thing.
if(await CheckSession())
_ = await _hcl.PostAsync("/user/logout", null);
_ = _hcl.DefaultRequestHeaders.Remove("Authorization");
_ = _hcl.DefaultRequestHeaders.Remove("X-Access-Token");
}
#endif
}
}

View File

@ -1,6 +0,0 @@
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<startup>
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.7.2" />
</startup>
</configuration>

View File

@ -1,133 +0,0 @@
namespace testplaza {
partial class Form1 {
/// <summary>
/// Required designer variable.
/// </summary>
private System.ComponentModel.IContainer components = null;
/// <summary>
/// Clean up any resources being used.
/// </summary>
/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
protected override void Dispose(bool disposing) {
if(disposing && (components != null)) {
components.Dispose();
}
base.Dispose(disposing);
}
#region Windows Form Designer generated code
/// <summary>
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
/// </summary>
private void InitializeComponent() {
this.pbAlbumArt = new System.Windows.Forms.PictureBox();
this.txInfo = new System.Windows.Forms.TextBox();
this.btRefresh = new System.Windows.Forms.Button();
this.btLike = new System.Windows.Forms.Button();
this.btDislike = new System.Windows.Forms.Button();
this.lbLikes = new System.Windows.Forms.Label();
this.lbDislikes = new System.Windows.Forms.Label();
((System.ComponentModel.ISupportInitialize)(this.pbAlbumArt)).BeginInit();
this.SuspendLayout();
//
// pbAlbumArt
//
this.pbAlbumArt.Location = new System.Drawing.Point(12, 12);
this.pbAlbumArt.Name = "pbAlbumArt";
this.pbAlbumArt.Size = new System.Drawing.Size(200, 200);
this.pbAlbumArt.SizeMode = System.Windows.Forms.PictureBoxSizeMode.StretchImage;
this.pbAlbumArt.TabIndex = 0;
this.pbAlbumArt.TabStop = false;
//
// txInfo
//
this.txInfo.Enabled = false;
this.txInfo.Location = new System.Drawing.Point(218, 12);
this.txInfo.Multiline = true;
this.txInfo.Name = "txInfo";
this.txInfo.Size = new System.Drawing.Size(570, 200);
this.txInfo.TabIndex = 1;
//
// btRefresh
//
this.btRefresh.Location = new System.Drawing.Point(218, 218);
this.btRefresh.Name = "btRefresh";
this.btRefresh.Size = new System.Drawing.Size(570, 50);
this.btRefresh.TabIndex = 2;
this.btRefresh.Text = "Refresh";
this.btRefresh.UseVisualStyleBackColor = true;
this.btRefresh.Click += new System.EventHandler(this.BtRefresh_Click);
//
// btLike
//
this.btLike.Location = new System.Drawing.Point(12, 218);
this.btLike.Name = "btLike";
this.btLike.Size = new System.Drawing.Size(81, 50);
this.btLike.TabIndex = 3;
this.btLike.Text = "Like";
this.btLike.UseVisualStyleBackColor = true;
this.btLike.Click += new System.EventHandler(this.BtLike_Click);
//
// btDislike
//
this.btDislike.Location = new System.Drawing.Point(121, 218);
this.btDislike.Name = "btDislike";
this.btDislike.Size = new System.Drawing.Size(91, 50);
this.btDislike.TabIndex = 4;
this.btDislike.Text = "Dislike";
this.btDislike.UseVisualStyleBackColor = true;
this.btDislike.Click += new System.EventHandler(this.BtDislike_Click);
//
// lbLikes
//
this.lbLikes.AutoSize = true;
this.lbLikes.Location = new System.Drawing.Point(99, 222);
this.lbLikes.Name = "lbLikes";
this.lbLikes.Size = new System.Drawing.Size(16, 17);
this.lbLikes.TabIndex = 5;
this.lbLikes.Text = "0";
//
// lbDislikes
//
this.lbDislikes.AutoSize = true;
this.lbDislikes.Location = new System.Drawing.Point(99, 247);
this.lbDislikes.Name = "lbDislikes";
this.lbDislikes.Size = new System.Drawing.Size(16, 17);
this.lbDislikes.TabIndex = 6;
this.lbDislikes.Text = "0";
//
// Form1
//
this.AutoScaleDimensions = new System.Drawing.SizeF(8F, 16F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.ClientSize = new System.Drawing.Size(800, 280);
this.Controls.Add(this.lbDislikes);
this.Controls.Add(this.lbLikes);
this.Controls.Add(this.btDislike);
this.Controls.Add(this.btLike);
this.Controls.Add(this.btRefresh);
this.Controls.Add(this.txInfo);
this.Controls.Add(this.pbAlbumArt);
this.Name = "Form1";
this.Text = "libplaza validation app";
((System.ComponentModel.ISupportInitialize)(this.pbAlbumArt)).EndInit();
this.ResumeLayout(false);
this.PerformLayout();
}
#endregion
private System.Windows.Forms.PictureBox pbAlbumArt;
private System.Windows.Forms.TextBox txInfo;
private System.Windows.Forms.Button btRefresh;
private System.Windows.Forms.Button btLike;
private System.Windows.Forms.Button btDislike;
private System.Windows.Forms.Label lbLikes;
private System.Windows.Forms.Label lbDislikes;
}
}

View File

@ -1,36 +0,0 @@
using System;
using System.Windows.Forms;
using libplaza;
namespace testplaza {
public partial class Form1 : Form {
public Form1() => InitializeComponent();
private Nightwave plaza=new Nightwave();
private void BtLike_Click(object sender, EventArgs e) {
}
private void BtDislike_Click(object sender, EventArgs e) {
}
private async void BtRefresh_Click(object sender, EventArgs e) {
Nightwave.Status now=await plaza.Broadcast();
txInfo.ResetText();
txInfo.AppendText($"Plaza is {plaza.Broadcasting}\n");
if(plaza.Broadcasting == Nightwave.BroadcastStatus.OffAir)
return;
lbLikes.Text = now.Likes.ToString();
lbDislikes.Text = now.Dislikes.ToString();
txInfo.AppendText($"{now.Listeners} listeners\n");
txInfo.AppendText($"Playing {now.Title}\n");
txInfo.AppendText($"From album {now.Album}\n");
txInfo.AppendText($"By {now.Artist}\n");
txInfo.AppendText($"Song is {now.Duration}s long\n");
txInfo.AppendText($"{now.Elapsed}s elapsed (calculated elapsed is {now.CalculatedElapsed}s)\n");
pbAlbumArt.LoadAsync(now.ArtworkUri);
txInfo.AppendText($"Album art is at:\n{now.ArtworkUri}\n");
}
}
}

View File

@ -1,120 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
</root>

View File

@ -1,19 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace testplaza {
static class Program {
/// <summary>
/// The main entry point for the application.
/// </summary>
[STAThread]
static void Main() {
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
Application.Run(new Form1());
}
}
}

View File

@ -1,36 +0,0 @@
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
// General Information about an assembly is controlled through the following
// set of attributes. Change these attribute values to modify the information
// associated with an assembly.
[assembly: AssemblyTitle("testplaza")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("testplaza")]
[assembly: AssemblyCopyright("Copyright © 2019")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
// Setting ComVisible to false makes the types in this assembly not visible
// to COM components. If you need to access a type in this assembly from
// COM, set the ComVisible attribute to true on that type.
[assembly: ComVisible(false)]
// The following GUID is for the ID of the typelib if this project is exposed to COM
[assembly: Guid("dab02d69-cd02-4c74-a110-7907e5b4a568")]
// Version information for an assembly consists of the following four values:
//
// Major Version
// Minor Version
// Build Number
// Revision
//
// You can specify all the values or you can default the Build and Revision Numbers
// by using the '*' as shown below:
// [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion("1.0.0.0")]
[assembly: AssemblyFileVersion("1.0.0.0")]

View File

@ -1,62 +0,0 @@
//------------------------------------------------------------------------------
// <auto-generated>
// This code was generated by a tool.
// Runtime Version:4.0.30319.42000
//
// Changes to this file may cause incorrect behavior and will be lost if
// the code is regenerated.
// </auto-generated>
//------------------------------------------------------------------------------
namespace testplaza.Properties {
/// <summary>
/// A strongly-typed resource class, for looking up localized strings, etc.
/// </summary>
// This class was auto-generated by the StronglyTypedResourceBuilder
// class via a tool like ResGen or Visual Studio.
// To add or remove a member, edit your .ResX file then rerun ResGen
// with the /str option, or rebuild your VS project.
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")]
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
internal class Resources {
private static global::System.Resources.ResourceManager resourceMan;
private static global::System.Globalization.CultureInfo resourceCulture;
[global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
internal Resources() {
}
/// <summary>
/// Returns the cached ResourceManager instance used by this class.
/// </summary>
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
internal static global::System.Resources.ResourceManager ResourceManager {
get {
if((resourceMan == null)) {
global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("testplaza.Properties.Resources", typeof(Resources).Assembly);
resourceMan = temp;
}
return resourceMan;
}
}
/// <summary>
/// Overrides the current thread's CurrentUICulture property for all
/// resource lookups using this strongly typed resource class.
/// </summary>
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
internal static global::System.Globalization.CultureInfo Culture {
get {
return resourceCulture;
}
set {
resourceCulture = value;
}
}
}
}

View File

@ -1,117 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
</root>

View File

@ -1,26 +0,0 @@
//------------------------------------------------------------------------------
// <auto-generated>
// This code was generated by a tool.
// Runtime Version:4.0.30319.42000
//
// Changes to this file may cause incorrect behavior and will be lost if
// the code is regenerated.
// </auto-generated>
//------------------------------------------------------------------------------
namespace testplaza.Properties {
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "11.0.0.0")]
internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase {
private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings())));
public static Settings Default {
get {
return defaultInstance;
}
}
}
}

View File

@ -1,7 +0,0 @@
<?xml version='1.0' encoding='utf-8'?>
<SettingsFile xmlns="http://schemas.microsoft.com/VisualStudio/2004/01/settings" CurrentProfile="(Default)">
<Profiles>
<Profile Name="(Default)" />
</Profiles>
<Settings />
</SettingsFile>

View File

@ -1,4 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="Newtonsoft.Json" version="12.0.2" targetFramework="net472" />
</packages>

View File

@ -1,93 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProjectGuid>{DAB02D69-CD02-4C74-A110-7907E5B4A568}</ProjectGuid>
<OutputType>WinExe</OutputType>
<RootNamespace>testplaza</RootNamespace>
<AssemblyName>testplaza</AssemblyName>
<TargetFrameworkVersion>v4.7.2</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment>
<AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
<Deterministic>true</Deterministic>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<PlatformTarget>AnyCPU</PlatformTarget>
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<OutputPath>bin\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<PlatformTarget>AnyCPU</PlatformTarget>
<DebugType>pdbonly</DebugType>
<Optimize>true</Optimize>
<OutputPath>bin\Release\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<ItemGroup>
<Reference Include="Newtonsoft.Json, Version=12.0.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed, processorArchitecture=MSIL">
<HintPath>..\packages\Newtonsoft.Json.12.0.2\lib\net45\Newtonsoft.Json.dll</HintPath>
</Reference>
<Reference Include="System" />
<Reference Include="System.Core" />
<Reference Include="System.Xml.Linq" />
<Reference Include="System.Data.DataSetExtensions" />
<Reference Include="Microsoft.CSharp" />
<Reference Include="System.Data" />
<Reference Include="System.Deployment" />
<Reference Include="System.Drawing" />
<Reference Include="System.Net.Http" />
<Reference Include="System.Windows.Forms" />
<Reference Include="System.Xml" />
</ItemGroup>
<ItemGroup>
<Compile Include="Form1.cs">
<SubType>Form</SubType>
</Compile>
<Compile Include="Form1.Designer.cs">
<DependentUpon>Form1.cs</DependentUpon>
</Compile>
<Compile Include="Program.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<EmbeddedResource Include="Form1.resx">
<DependentUpon>Form1.cs</DependentUpon>
</EmbeddedResource>
<EmbeddedResource Include="Properties\Resources.resx">
<Generator>ResXFileCodeGenerator</Generator>
<LastGenOutput>Resources.Designer.cs</LastGenOutput>
<SubType>Designer</SubType>
</EmbeddedResource>
<Compile Include="Properties\Resources.Designer.cs">
<AutoGen>True</AutoGen>
<DependentUpon>Resources.resx</DependentUpon>
</Compile>
<None Include="packages.config" />
<None Include="Properties\Settings.settings">
<Generator>SettingsSingleFileGenerator</Generator>
<LastGenOutput>Settings.Designer.cs</LastGenOutput>
</None>
<Compile Include="Properties\Settings.Designer.cs">
<AutoGen>True</AutoGen>
<DependentUpon>Settings.settings</DependentUpon>
<DesignTimeSharedInput>True</DesignTimeSharedInput>
</Compile>
</ItemGroup>
<ItemGroup>
<None Include="App.config" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\libplaza\libplaza.csproj">
<Project>{B8CD29C0-4FAF-4A02-B00C-3180E60841D3}</Project>
<Name>libplaza</Name>
</ProjectReference>
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
</Project>