From 8659e6832526f19f2ef9d42de1eac39a4368ec3a Mon Sep 17 00:00:00 2001 From: Kai Yang Date: Tue, 19 May 2015 17:48:50 +0800 Subject: [PATCH] Add comments --- .../ServerConnectionTests.cs | 8 +- Kfstorm.DoubanFM.Core/AuthenticationBase.cs | 30 ++++ Kfstorm.DoubanFM.Core/Channel.cs | 37 +++++ Kfstorm.DoubanFM.Core/ChannelGroup.cs | 21 +++ Kfstorm.DoubanFM.Core/ChannelList.cs | 9 ++ .../ChannelNotSelectedException.cs | 3 + Kfstorm.DoubanFM.Core/EventArgs{T}.cs | 4 + Kfstorm.DoubanFM.Core/ExceptionHelper.cs | 18 +++ Kfstorm.DoubanFM.Core/IAuthentication.cs | 7 + Kfstorm.DoubanFM.Core/IPlayer.cs | 71 +++++++++ Kfstorm.DoubanFM.Core/IServerConnection.cs | 70 +++++++++ Kfstorm.DoubanFM.Core/ISession.cs | 23 +++ .../Kfstorm.DoubanFM.Core.csproj | 2 + Kfstorm.DoubanFM.Core/NextCommandType.cs | 12 ++ .../NoAvailableSongsException.cs | 3 + Kfstorm.DoubanFM.Core/OAuthAuthentication.cs | 25 +++ .../PasswordAuthentication.cs | 30 ++++ Kfstorm.DoubanFM.Core/Player.Network.cs | 35 ++++- Kfstorm.DoubanFM.Core/Player.Parser.cs | 80 ++++++---- Kfstorm.DoubanFM.Core/Player.cs | 144 ++++++++++++++++-- Kfstorm.DoubanFM.Core/ReportType.cs | 26 +++- Kfstorm.DoubanFM.Core/ReportTypeString.cs | 11 +- Kfstorm.DoubanFM.Core/ServerConnection.cs | 100 +++++++++++- Kfstorm.DoubanFM.Core/ServerException.cs | 51 +++++-- Kfstorm.DoubanFM.Core/Session.cs | 44 ++++++ Kfstorm.DoubanFM.Core/Song.cs | 115 ++++++++++++++ .../SongNotSelectedException.cs | 4 +- Kfstorm.DoubanFM.Core/StringTable.cs | 3 + Kfstorm.DoubanFM.Core/UriHelper.cs | 29 ++++ Kfstorm.DoubanFM.Core/UserInfo.cs | 33 ++++ 30 files changed, 978 insertions(+), 70 deletions(-) diff --git a/Kfstorm.DoubanFM.Core.UnitTest/ServerConnectionTests.cs b/Kfstorm.DoubanFM.Core.UnitTest/ServerConnectionTests.cs index d1e5aad..c0ced03 100644 --- a/Kfstorm.DoubanFM.Core.UnitTest/ServerConnectionTests.cs +++ b/Kfstorm.DoubanFM.Core.UnitTest/ServerConnectionTests.cs @@ -37,7 +37,7 @@ public async void TestServerException_Get() responseMock.Setup(r => r.GetResponseStream()).Returns(new MemoryStream(Encoding.UTF8.GetBytes(Resource.ErrorResponseSample))); serverConnection.Protected().Setup("CreateRequest", ItExpr.IsAny()).Returns(requestMock.Object); - var ex = await AssertEx.ThrowsAsync(async () => await serverConnection.Object.Get(new Uri("http://anyUri.com"))); + var ex = await AssertEx.ThrowsAsync(async () => await serverConnection.Object.Get(new Uri("http://anyUri.com"), null)); Assert.IsNotNull(ex); Assert.AreEqual(123, ex.Code); Assert.IsNotEmpty(ex.ErrorMessage); @@ -53,7 +53,7 @@ public async void TestServerException_Get_OldApi() responseMock.Setup(r => r.GetResponseStream()).Returns(new MemoryStream(Encoding.UTF8.GetBytes(Resource.ErrorResponseSample_OldApi))); serverConnection.Protected().Setup("CreateRequest", ItExpr.IsAny()).Returns(requestMock.Object); - var ex = await AssertEx.ThrowsAsync(async () => await serverConnection.Object.Get(new Uri("http://anyUri.com"))); + var ex = await AssertEx.ThrowsAsync(async () => await serverConnection.Object.Get(new Uri("http://anyUri.com"), null)); Assert.IsNotNull(ex); Assert.AreEqual(123, ex.Code); Assert.IsNotEmpty(ex.ErrorMessage); @@ -69,7 +69,7 @@ public async void TestServerException_Post() responseMock.Setup(r => r.GetResponseStream()).Returns(new MemoryStream(Encoding.UTF8.GetBytes(Resource.ErrorResponseSample))); serverConnection.Protected().Setup("CreateRequest", ItExpr.IsAny()).Returns(requestMock.Object); - var ex = await AssertEx.ThrowsAsync(async () => await serverConnection.Object.Get(new Uri("http://anyUri.com"))); + var ex = await AssertEx.ThrowsAsync(async () => await serverConnection.Object.Get(new Uri("http://anyUri.com"), null)); Assert.IsNotNull(ex); Assert.AreEqual(123, ex.Code); Assert.IsNotEmpty(ex.ErrorMessage); @@ -85,7 +85,7 @@ public async void TestServerException_Post_OldApi() responseMock.Setup(r => r.GetResponseStream()).Returns(new MemoryStream(Encoding.UTF8.GetBytes(Resource.ErrorResponseSample_OldApi))); serverConnection.Protected().Setup("CreateRequest", ItExpr.IsAny()).Returns(requestMock.Object); - var ex = await AssertEx.ThrowsAsync(async () => await serverConnection.Object.Get(new Uri("http://anyUri.com"))); + var ex = await AssertEx.ThrowsAsync(async () => await serverConnection.Object.Get(new Uri("http://anyUri.com"), null)); Assert.IsNotNull(ex); Assert.AreEqual(123, ex.Code); Assert.IsNotEmpty(ex.ErrorMessage); diff --git a/Kfstorm.DoubanFM.Core/AuthenticationBase.cs b/Kfstorm.DoubanFM.Core/AuthenticationBase.cs index a270e3f..1663dac 100644 --- a/Kfstorm.DoubanFM.Core/AuthenticationBase.cs +++ b/Kfstorm.DoubanFM.Core/AuthenticationBase.cs @@ -4,18 +4,42 @@ namespace Kfstorm.DoubanFM.Core { + /// + /// A abstract base class for all implementations of interface + /// public abstract class AuthenticationBase : IAuthentication { + /// + /// Gets the logger. + /// + /// + /// The logger. + /// protected ILog Logger { get; } + /// + /// Initializes a new instance of the class. + /// + /// The server connection. protected AuthenticationBase(IServerConnection serverConnection) { ServerConnection = serverConnection; Logger = LogManager.GetLogger(GetType()); } + /// + /// Gets the server connection. + /// + /// + /// The server connection. + /// protected IServerConnection ServerConnection { get; } + /// + /// Parses the result of logging on. + /// + /// Content of JSON format. + /// The user information in the result. protected UserInfo ParseLogOnResult(string jsonContent) { var obj = JObject.Parse(jsonContent); @@ -29,6 +53,12 @@ protected UserInfo ParseLogOnResult(string jsonContent) }; } + /// + /// Authenticates and returns user info. + /// + /// + /// The user info, including username and token. + /// public abstract Task Authenticate(); } } diff --git a/Kfstorm.DoubanFM.Core/Channel.cs b/Kfstorm.DoubanFM.Core/Channel.cs index 77350aa..ca7d2e2 100644 --- a/Kfstorm.DoubanFM.Core/Channel.cs +++ b/Kfstorm.DoubanFM.Core/Channel.cs @@ -2,8 +2,15 @@ namespace Kfstorm.DoubanFM.Core { + /// + /// An instance of Channel class indicates a channel in douban.fm. A channel usually contains a lot of songs. + /// public class Channel : IEquatable { + /// + /// Initializes a new instance of the class. + /// + /// The channel ID. public Channel(int id) { Id = id; @@ -19,14 +26,44 @@ public override int GetHashCode() return Id; } + /// + /// Gets or sets the name of the channel. + /// + /// + /// The name. + /// public string Name { get; set; } + /// + /// Gets or sets the description of the channel. + /// + /// + /// The description. + /// public string Description { get; set; } + /// + /// Gets the ID of the channel. + /// + /// + /// The ID. + /// public int Id { get; } + /// + /// Gets or sets the song count of the channel. + /// + /// + /// The song count. + /// public int SongCount { get; set; } + /// + /// Gets or sets the cover URL of the channel. + /// + /// + /// The cover URL. + /// public string CoverUrl { get; set; } public override bool Equals(object obj) diff --git a/Kfstorm.DoubanFM.Core/ChannelGroup.cs b/Kfstorm.DoubanFM.Core/ChannelGroup.cs index b9066a4..3e2bf92 100644 --- a/Kfstorm.DoubanFM.Core/ChannelGroup.cs +++ b/Kfstorm.DoubanFM.Core/ChannelGroup.cs @@ -1,9 +1,30 @@ namespace Kfstorm.DoubanFM.Core { + /// + /// Represents a group of channels + /// public class ChannelGroup { + /// + /// Gets the ID of the group. + /// + /// + /// The ID of the group. + /// public int GroupId { get; internal set; } + /// + /// Gets the name of the group. + /// + /// + /// The name of the group. + /// public string GroupName { get; internal set; } + /// + /// Gets the channels. + /// + /// + /// The channels. + /// public Channel[] Channels { get; internal set; } } } \ No newline at end of file diff --git a/Kfstorm.DoubanFM.Core/ChannelList.cs b/Kfstorm.DoubanFM.Core/ChannelList.cs index 7c4fa1d..e9af01e 100644 --- a/Kfstorm.DoubanFM.Core/ChannelList.cs +++ b/Kfstorm.DoubanFM.Core/ChannelList.cs @@ -1,7 +1,16 @@ namespace Kfstorm.DoubanFM.Core { + /// + /// Represents a list of channels, including many channel groups. + /// public class ChannelList { + /// + /// Gets the channel groups. + /// + /// + /// The channel groups. + /// public ChannelGroup[] ChannelGroups { get; internal set; } } } \ No newline at end of file diff --git a/Kfstorm.DoubanFM.Core/ChannelNotSelectedException.cs b/Kfstorm.DoubanFM.Core/ChannelNotSelectedException.cs index 23ad47e..b4014e2 100644 --- a/Kfstorm.DoubanFM.Core/ChannelNotSelectedException.cs +++ b/Kfstorm.DoubanFM.Core/ChannelNotSelectedException.cs @@ -2,6 +2,9 @@ namespace Kfstorm.DoubanFM.Core { + /// + /// This type of exception will be thrown when some operations which need a selected channel are triggerred but the current channel of the player is not selected. + /// public class ChannelNotSelectedException : Exception { } diff --git a/Kfstorm.DoubanFM.Core/EventArgs{T}.cs b/Kfstorm.DoubanFM.Core/EventArgs{T}.cs index b7e3add..6a307b1 100644 --- a/Kfstorm.DoubanFM.Core/EventArgs{T}.cs +++ b/Kfstorm.DoubanFM.Core/EventArgs{T}.cs @@ -2,6 +2,10 @@ namespace Kfstorm.DoubanFM.Core { + /// + /// A simple subclass of EventArgs which contains single data object. + /// + /// The type of data object. public class EventArgs : EventArgs { public T Object { get; set; } diff --git a/Kfstorm.DoubanFM.Core/ExceptionHelper.cs b/Kfstorm.DoubanFM.Core/ExceptionHelper.cs index 7d3c9d9..387b878 100644 --- a/Kfstorm.DoubanFM.Core/ExceptionHelper.cs +++ b/Kfstorm.DoubanFM.Core/ExceptionHelper.cs @@ -4,8 +4,18 @@ namespace Kfstorm.DoubanFM.Core { + /// + /// A helper class of exception + /// public static class ExceptionHelper { + /// + /// Logs the exception if the action throws any. + /// + /// The logger. + /// The action. + /// The log message. + /// public static async Task LogExceptionIfAny(ILog logger, Func action, string message = null) { try @@ -19,6 +29,14 @@ public static async Task LogExceptionIfAny(ILog logger, Func action, strin } } + /// + /// Logs the exception if the action throws any. + /// + /// + /// The logger. + /// The action. + /// The log message. + /// public static async Task LogExceptionIfAny(ILog logger, Func> action, string message = null) { try diff --git a/Kfstorm.DoubanFM.Core/IAuthentication.cs b/Kfstorm.DoubanFM.Core/IAuthentication.cs index da284d2..e56dd9d 100644 --- a/Kfstorm.DoubanFM.Core/IAuthentication.cs +++ b/Kfstorm.DoubanFM.Core/IAuthentication.cs @@ -2,8 +2,15 @@ namespace Kfstorm.DoubanFM.Core { + /// + /// Indicates a method of user authentication + /// public interface IAuthentication { + /// + /// Authenticates and returns user info. + /// + /// The user info, including username and token. Task Authenticate(); } } diff --git a/Kfstorm.DoubanFM.Core/IPlayer.cs b/Kfstorm.DoubanFM.Core/IPlayer.cs index e6f9988..0e1d81d 100644 --- a/Kfstorm.DoubanFM.Core/IPlayer.cs +++ b/Kfstorm.DoubanFM.Core/IPlayer.cs @@ -4,21 +4,92 @@ namespace Kfstorm.DoubanFM.Core { + /// + /// Controls playing logic, including channel/song switch and red heart marking. + /// public interface IPlayer { + /// + /// Gets the session. + /// + /// + /// The session. + /// ISession Session { get; } + /// + /// Gets the server connection. + /// + /// + /// The server connection. + /// IServerConnection ServerConnection { get; } + /// + /// Gets the current song. + /// + /// + /// The current song. + /// Song CurrentSong { get; } + /// + /// Gets the channel list. + /// + /// + /// The channel list. + /// + /// + /// The channel list is a collection of sample channels, organized by groups. + /// ChannelList ChannelList { get; } + /// + /// Gets the current channel. + /// + /// + /// The current channel. + /// Channel CurrentChannel { get; } + /// + /// Gets the configuration. + /// + /// + /// The configuration. + /// + /// + /// The configuration stores information needed by player. Such as music format and bit rate, which will be used when communiating with server. + /// IDictionary Config { get; } + /// + /// Occurs when current song changed. + /// event EventHandler> CurrentSongChanged; + /// + /// Occurs when current channel changed. + /// event EventHandler> CurrentChannelChanged; + /// + /// Refreshes the channel list. + /// + /// Task RefreshChannelList(); + /// + /// Changes the channel. + /// + /// The new channel. + /// Task ChangeChannel(Channel newChannel); + /// + /// Switch to next song. + /// + /// the type of next operation. + /// Task Next(NextCommandType type); + /// + /// Sets the red heart. + /// + /// if set to true then current song will be marked with red heart, indicating that user likes this song. Otherwise remove the red heart mark. + /// + /// Set redHeart to false doesn't means user dislike current song. Task SetRedHeart(bool redHeart); } } \ No newline at end of file diff --git a/Kfstorm.DoubanFM.Core/IServerConnection.cs b/Kfstorm.DoubanFM.Core/IServerConnection.cs index 97b5565..88a3caf 100644 --- a/Kfstorm.DoubanFM.Core/IServerConnection.cs +++ b/Kfstorm.DoubanFM.Core/IServerConnection.cs @@ -5,19 +5,89 @@ namespace Kfstorm.DoubanFM.Core { + /// + /// Controls communication with server + /// public interface IServerConnection { + /// + /// Gets the context. The context contains contextual information about server connection, such as client ID and access token. + /// + /// + /// The context. + /// IDictionary Context { get; } + /// + /// Gets or sets the client ID. + /// + /// + /// The client ID. + /// string ClientId { get; set; } + /// + /// Gets or sets the client secret. + /// + /// + /// The client secret. + /// string ClientSecret { get; set; } + /// + /// Gets or sets the name of the application. + /// + /// + /// The name of the application. + /// string AppName { get; set; } + /// + /// Gets or sets the application version. + /// + /// + /// The application version. + /// string AppVersion { get; set; } + /// + /// Gets or sets the redirect URI. + /// + /// + /// The redirect URI. + /// Uri RedirectUri { get; set; } + /// + /// Gets or sets the access token. + /// + /// + /// The access token. + /// string AccessToken { get; set; } + /// + /// Gets or sets the UDID. + /// + /// + /// The UDID. + /// string Udid { get; set; } + /// + /// Send an HTTP GET request to the specified URI, and get the response content as string. + /// + /// The URI. + /// The modifier to change the request before sending. The modifier can be null. + /// The content of response. Task Get(Uri uri, Action modifier); + /// + /// Send an HTTP POST request to the specified URI, and get the response content as string. + /// + /// The URI. + /// The binary data need to be posted. Null or empty array means no data. + /// The content of response. Task Post(Uri uri, byte[] data); + /// + /// Send an HTTP POST request to the specified URI, and get the response content as string. + /// + /// The URI. + /// The binary data need to be posted. Null or empty array means no data. + /// The modifier to change the request before sending. The modifier can be null. + /// The content of response. Task Post(Uri uri, byte[] data, Action modifier); } } \ No newline at end of file diff --git a/Kfstorm.DoubanFM.Core/ISession.cs b/Kfstorm.DoubanFM.Core/ISession.cs index 084baac..aa6095c 100644 --- a/Kfstorm.DoubanFM.Core/ISession.cs +++ b/Kfstorm.DoubanFM.Core/ISession.cs @@ -2,13 +2,36 @@ namespace Kfstorm.DoubanFM.Core { + /// + /// Controls user session + /// public interface ISession { + /// + /// Gets the user information. + /// + /// + /// The user information. + /// UserInfo UserInfo { get; } + /// + /// Gets the server connection. + /// + /// + /// The server connection. + /// IServerConnection ServerConnection { get; } + /// + /// Logs on with specified authentication method. + /// + /// The authentication method. + /// Task LogOn(IAuthentication authentication); + /// + /// Logs off. + /// void LogOff(); } } \ No newline at end of file diff --git a/Kfstorm.DoubanFM.Core/Kfstorm.DoubanFM.Core.csproj b/Kfstorm.DoubanFM.Core/Kfstorm.DoubanFM.Core.csproj index 5f505cb..143e7db 100644 --- a/Kfstorm.DoubanFM.Core/Kfstorm.DoubanFM.Core.csproj +++ b/Kfstorm.DoubanFM.Core/Kfstorm.DoubanFM.Core.csproj @@ -23,6 +23,7 @@ prompt 4 false + bin\Debug\Kfstorm.DoubanFM.Core.XML pdbonly @@ -32,6 +33,7 @@ prompt 4 false + bin\Release\Kfstorm.DoubanFM.Core.XML diff --git a/Kfstorm.DoubanFM.Core/NextCommandType.cs b/Kfstorm.DoubanFM.Core/NextCommandType.cs index 714f233..0db064e 100644 --- a/Kfstorm.DoubanFM.Core/NextCommandType.cs +++ b/Kfstorm.DoubanFM.Core/NextCommandType.cs @@ -1,9 +1,21 @@ namespace Kfstorm.DoubanFM.Core { + /// + /// The type of next song command + /// public enum NextCommandType { + /// + /// Current song ended + /// CurrentSongEnded, + /// + /// Skip current song (not ended) + /// SkipCurrentSong, + /// + /// Ban current song (hate current song) + /// BanCurrentSong, } } diff --git a/Kfstorm.DoubanFM.Core/NoAvailableSongsException.cs b/Kfstorm.DoubanFM.Core/NoAvailableSongsException.cs index 371189d..1d26ad0 100644 --- a/Kfstorm.DoubanFM.Core/NoAvailableSongsException.cs +++ b/Kfstorm.DoubanFM.Core/NoAvailableSongsException.cs @@ -2,6 +2,9 @@ namespace Kfstorm.DoubanFM.Core { + /// + /// This type of exception will be throwed when trying to get a list of songs for current channel but server returned an empty list. + /// public class NoAvailableSongsException : Exception { } diff --git a/Kfstorm.DoubanFM.Core/OAuthAuthentication.cs b/Kfstorm.DoubanFM.Core/OAuthAuthentication.cs index 1c763c3..49e70df 100644 --- a/Kfstorm.DoubanFM.Core/OAuthAuthentication.cs +++ b/Kfstorm.DoubanFM.Core/OAuthAuthentication.cs @@ -3,16 +3,41 @@ namespace Kfstorm.DoubanFM.Core { + /// + /// OAuth authentication method + /// public class OAuthAuthentication : AuthenticationBase { + /// + /// Gets or sets the redirect URI. + /// + /// + /// The redirect URI. + /// public Uri RedirectUri => ServerConnection.RedirectUri; + /// + /// Initializes a new instance of the class. + /// + /// The server connection. public OAuthAuthentication(IServerConnection serverConnection) : base(serverConnection) { } + /// + /// Gets or sets the delegate of getting the redirected URI. + /// + /// + /// The delegate of getting the redirected URI. + /// public Func> GetRedirectedUri { get; set; } + /// + /// Authenticates and returns user info. + /// + /// + /// The user info, including username and token. + /// public override async Task Authenticate() { Logger.Info("Start OAuth authentication."); diff --git a/Kfstorm.DoubanFM.Core/PasswordAuthentication.cs b/Kfstorm.DoubanFM.Core/PasswordAuthentication.cs index 066466a..10eb676 100644 --- a/Kfstorm.DoubanFM.Core/PasswordAuthentication.cs +++ b/Kfstorm.DoubanFM.Core/PasswordAuthentication.cs @@ -3,11 +3,37 @@ namespace Kfstorm.DoubanFM.Core { + /// + /// Password authentication method + /// public class PasswordAuthentication : AuthenticationBase { + /// + /// Gets or sets the username. + /// + /// + /// The username. + /// public string Username { get; set; } + /// + /// Gets or sets the password. + /// + /// + /// The password. + /// public string Password { get; set; } + /// + /// Authenticates and returns user info. + /// + /// + /// The user info, including username and token. + /// + /// + /// Username is empty + /// or + /// Password is empty + /// public override async Task Authenticate() { if (string.IsNullOrWhiteSpace(Username)) @@ -30,6 +56,10 @@ public override async Task Authenticate() return ParseLogOnResult(jsonContent); } + /// + /// Initializes a new instance of the class. + /// + /// The server connection. public PasswordAuthentication(IServerConnection serverConnection) : base(serverConnection) { } diff --git a/Kfstorm.DoubanFM.Core/Player.Network.cs b/Kfstorm.DoubanFM.Core/Player.Network.cs index f69a1be..145e586 100644 --- a/Kfstorm.DoubanFM.Core/Player.Network.cs +++ b/Kfstorm.DoubanFM.Core/Player.Network.cs @@ -9,6 +9,10 @@ namespace Kfstorm.DoubanFM.Core { partial class Player { + /// + /// Creates the get channel list URI. + /// + /// protected virtual Uri CreateGetChannelListUri() { var uriBuilder = new UriBuilder("https://api.douban.com/v2/fm/app_channels"); @@ -18,6 +22,16 @@ protected virtual Uri CreateGetChannelListUri() return uriBuilder.Uri; } + /// + /// Creates the get play list URI. + /// + /// The channel ID. + /// The report type. + /// The SID of current song. + /// The format of music file. + /// The bit rate of music file. + /// The played time of current song. + /// protected virtual Uri CreateGetPlayListUri(int channelId, ReportType type, string sid, string formats, int? kbps, double? playedTime) { var uriBuilder = new UriBuilder("https://api.douban.com/v2/fm/playlist"); @@ -31,9 +45,13 @@ protected virtual Uri CreateGetPlayListUri(int channelId, ReportType type, strin return uriBuilder.Uri; } + /// + /// Gets the channel list. + /// + /// protected virtual async Task GetChannelList() { - var jsonContent = await ServerConnection.Get(CreateGetChannelListUri(), RequestModifier); + var jsonContent = await ServerConnection.Get(CreateGetChannelListUri(), ModifyRequest); var newChannelList = ParseChannelList(jsonContent); var groupCount = newChannelList.ChannelGroups.Length; var channelCount = newChannelList.ChannelGroups.Sum(group => group.Channels.Length); @@ -41,6 +59,13 @@ protected virtual async Task GetChannelList() return newChannelList; } + /// + /// Gets the play list. + /// + /// The report type. + /// The channel ID. + /// The SID of current song. + /// protected virtual async Task GetPlayList(ReportType type, int channelId, string sid) { object formats; @@ -49,13 +74,17 @@ protected virtual async Task GetPlayList(ReportType type, int channelId, Config.TryGetValue(StringTable.Formats, out kbps); var uri = CreateGetPlayListUri(channelId, type, sid, (string)formats, (int?)kbps, null /* TODO: fill played time here */); - var jsonContent = await ServerConnection.Get(uri, RequestModifier); + var jsonContent = await ServerConnection.Get(uri, ModifyRequest); var newPlayList = ParsePlayList(jsonContent); Logger.Info($"Got play list. Type: {type}. Channel ID: {channelId}. Sid: {sid}. song count: {newPlayList.Length}. Detail: {JsonConvert.SerializeObject(newPlayList)}"); return newPlayList; } - protected virtual void RequestModifier(HttpWebRequest request) + /// + /// Modifies the request. + /// + /// The request. + protected virtual void ModifyRequest(HttpWebRequest request) { var uri = request.RequestUri; if (!string.IsNullOrEmpty(ServerConnection.AccessToken) && uri.Host.Equals("api.douban.com", StringComparison.OrdinalIgnoreCase)) diff --git a/Kfstorm.DoubanFM.Core/Player.Parser.cs b/Kfstorm.DoubanFM.Core/Player.Parser.cs index f9407ea..a910b47 100644 --- a/Kfstorm.DoubanFM.Core/Player.Parser.cs +++ b/Kfstorm.DoubanFM.Core/Player.Parser.cs @@ -6,27 +6,37 @@ namespace Kfstorm.DoubanFM.Core { partial class Player { + /// + /// Parses the channel list. + /// + /// Content of JSON format. + /// The channel list. protected virtual ChannelList ParseChannelList(string jsonContent) { var obj = JObject.Parse(jsonContent); return new ChannelList { ChannelGroups = (from @group in obj["groups"] - select new ChannelGroup - { - GroupId = (int)@group["group_id"], - GroupName = (string)@group["group_name"], - Channels = ((JArray)@group["chls"]).Select(chl => new Channel((int)chl["id"]) - { - Name = (string)chl["name"], - Description = (string)chl["intro"], - SongCount = (int)chl["song_num"], - CoverUrl = (string)chl["cover"], - }).ToArray(), - }).ToArray() + select new ChannelGroup + { + GroupId = (int)@group["group_id"], + GroupName = (string)@group["group_name"], + Channels = ((JArray)@group["chls"]).Select(chl => new Channel((int)chl["id"]) + { + Name = (string)chl["name"], + Description = (string)chl["intro"], + SongCount = (int)chl["song_num"], + CoverUrl = (string)chl["cover"], + }).ToArray(), + }).ToArray() }; } + /// + /// Parses the play list. + /// + /// Content of JSON format. + /// The play list. protected virtual Song[] ParsePlayList(string jsonContent) { var obj = JObject.Parse(jsonContent); @@ -34,30 +44,36 @@ protected virtual Song[] ParsePlayList(string jsonContent) if (obj.TryGetValue("song", out songs) && songs != null) { return (from song in songs - select new Song((string) song["sid"]) - { - AlbumUrl = (string) song["album"], - PictureUrl = (string) song["picture"], - Ssid = (string) song["ssid"], - Artist = (string) song["artist"], - Url = (string) song["url"], - Company = (string) song["company"], - Title = (string) song["title"], - AverageRating = ParseOptional(song["rating_avg"]), - Length = (int) song["length"], - SubType = (string) song["subtype"], - PublishTime = ParseOptional(song["public_time"]), - SongListsCount = ParseOptional(song["songlists_count"]), - Aid = (string) song["aid"], - Sha256 = (string) song["sha256"], - Kbps = ParseOptional(song["kbps"]), - AlbumTitle = (string) song["albumtitle"], - Like = ParseOptional(song["like"]), - }).ToArray(); + select new Song((string)song["sid"]) + { + AlbumUrl = (string)song["album"], + PictureUrl = (string)song["picture"], + Ssid = (string)song["ssid"], + Artist = (string)song["artist"], + Url = (string)song["url"], + Company = (string)song["company"], + Title = (string)song["title"], + AverageRating = ParseOptional(song["rating_avg"]), + Length = (int)song["length"], + SubType = (string)song["subtype"], + PublishTime = ParseOptional(song["public_time"]), + SongListsCount = ParseOptional(song["songlists_count"]), + Aid = (string)song["aid"], + Sha256 = (string)song["sha256"], + Kbps = ParseOptional(song["kbps"]), + AlbumTitle = (string)song["albumtitle"], + Like = ParseOptional(song["like"]), + }).ToArray(); } return new Song[0]; } + /// + /// Parses an optional object/field. + /// + /// The type of the object/field. + /// The JSON token. + /// The value of the object/field. private T ParseOptional(JToken obj) { try diff --git a/Kfstorm.DoubanFM.Core/Player.cs b/Kfstorm.DoubanFM.Core/Player.cs index b8475de..ccaa80f 100644 --- a/Kfstorm.DoubanFM.Core/Player.cs +++ b/Kfstorm.DoubanFM.Core/Player.cs @@ -6,20 +6,47 @@ namespace Kfstorm.DoubanFM.Core { + /// + /// The default implementation of interface + /// public partial class Player : IPlayer { + /// + /// The logger + /// protected ILog Logger = LogManager.GetLogger(typeof(Player)); private volatile Song _currentSong; private volatile Channel _currentChannel; + /// + /// The state lock + /// protected object StateLock = new object(); + /// + /// Gets the session. + /// + /// + /// The session. + /// public ISession Session { get; } + /// + /// Gets the server connection. + /// + /// + /// The server connection. + /// public IServerConnection ServerConnection { get; } + /// + /// Gets the current song. + /// + /// + /// The current song. + /// public Song CurrentSong { get { return _currentSong; } @@ -36,10 +63,28 @@ protected set } } - private Queue _nextSongs; + /// + /// Stores a list of songs will be played next. + /// + private Queue _pendingSongs; + /// + /// Gets the channel list. + /// + /// + /// The channel list. + /// + /// + /// The channel list is a collection of sample channels, organized by groups. + /// public ChannelList ChannelList { get; protected set; } + /// + /// Gets the current channel. + /// + /// + /// The current channel. + /// public Channel CurrentChannel { get { return _currentChannel; } @@ -53,28 +98,69 @@ protected set } } + /// + /// Gets or sets the asynchronous expected channel ID. + /// + /// + /// The asynchronous expected channel ID. + /// + /// + /// This property will be set to the new channel ID when changing current channel. Any async operation related to current channel should check this property before anything take effect, to avoid outdated operation takes effect. + /// For example, when changing channel from A to B asynchronously (call it operation 1), user triggerred next command (call it operation 2, which will update the pending songs based on current channel). + /// Assume below execution timeline: + /// Operation 1 start -> operation 2 start -> operation 1 finish -> operation 2 finish. + /// When operation 1 finishes, current channel will be switched to channel B. And when operation 2 finishes, it will try to update the pending songs based on channel A. Now check the consistence of this property (B) and the channel ID in operation 2 (A) can prevent outdated server response. + /// protected int? AsyncExpectedChannelId { get; set; } + /// + /// Gets the configuration. + /// + /// + /// The configuration. + /// + /// + /// The configuration stores information needed by player. Such as music format and bit rate, which will be used when communiating with server. + /// public IDictionary Config { get; } = new Dictionary(StringComparer.OrdinalIgnoreCase); + /// + /// Occurs when current song changed. + /// public event EventHandler> CurrentSongChanged; + /// + /// Occurs when current channel changed. + /// public event EventHandler> CurrentChannelChanged; + /// + /// Initializes a new instance of the class. + /// + /// The session. public Player(ISession session) { ServerConnection = session.ServerConnection; Session = session; } + /// + /// Refreshes the channel list. + /// + /// public async Task RefreshChannelList() { await LogExceptionIfAny(Logger, async () => ChannelList = await GetChannelList()); Logger.Info("Channel list refreshed."); } + /// + /// Switch to next song. + /// + /// the type of next operation. + /// public async Task Next(NextCommandType type) { - ThrowExceptionIfNoChannel(); + ThrowExceptionIfCurrentChannelIsNull(); ReportType reportType; if (CurrentSong == null) { @@ -101,6 +187,11 @@ public async Task Next(NextCommandType type) await LogExceptionIfAny(Logger, async () => await Report(reportType, CurrentChannel.Id, CurrentSong?.Sid)); } + /// + /// Changes the channel. + /// + /// The new channel. + /// public async Task ChangeChannel(Channel newChannel) { if (CurrentChannel != newChannel) @@ -112,7 +203,7 @@ public async Task ChangeChannel(Channel newChannel) { await LogExceptionIfAny(Logger, async () => { - await Report(ReportType.NewChannel, newChannel.Id, CurrentSong?.Sid); + await Report(ReportType.CurrentChannelChanged, newChannel.Id, CurrentSong?.Sid); /* If user called ChangeChannel twice in a short time, say call 1 and call 2. @@ -128,10 +219,16 @@ So we need to check AsyncExpectedChannelId here because it should be call 2's ID } } + /// + /// Sets the red heart. + /// + /// if set to true then current song will be marked with red heart, indicating that user likes this song. Otherwise remove the red heart mark. + /// + /// Set redHeart to false doesn't means user dislike current song. public async Task SetRedHeart(bool redHeart) { - ThrowExceptionIfNoChannel(); - ThrowExceptionIfNoSong(); + ThrowExceptionIfCurrentChannelIsNull(); + ThrowExceptionIfCurrentSongIsNull(); var sid = CurrentSong.Sid; await LogExceptionIfAny(Logger, async () => await Report(redHeart ? ReportType.Like : ReportType.CancelLike, CurrentChannel.Id, CurrentSong?.Sid)); if (CurrentSong != null && CurrentSong.Sid == sid) @@ -140,18 +237,33 @@ public async Task SetRedHeart(bool redHeart) } } + /// + /// Raises the event. + /// + /// The instance containing the event data. protected virtual void OnCurrentSongChanged(EventArgs e) { Logger.Info($"Current song changed. {e.Object}"); CurrentSongChanged?.Invoke(this, e); } + /// + /// Raises the event. + /// + /// The instance containing the event data. protected virtual void OnCurrentChannelChanged(EventArgs e) { Logger.Info($"Current channel changed. {e.Object}"); CurrentChannelChanged?.Invoke(this, e); } + /// + /// Send a report to server. + /// + /// The type of report. + /// The channel ID. + /// The SID of current song. + /// private async Task Report(ReportType type, int channelId, string sid) { try @@ -168,7 +280,7 @@ private async Task Report(ReportType type, int channelId, string sid) { throw new NoAvailableSongsException(); } - if (_nextSongs.Count == 0) + if (_pendingSongs.Count == 0) { await Report(ReportType.PlayListEmpty, channelId, sid); return; @@ -178,19 +290,19 @@ private async Task Report(ReportType type, int channelId, string sid) { if (newPlayList.Length != 0) { - if (_nextSongs == null) + if (_pendingSongs == null) { - _nextSongs = new Queue(); + _pendingSongs = new Queue(); } - _nextSongs.Clear(); + _pendingSongs.Clear(); foreach (var song in newPlayList) { - _nextSongs.Enqueue(song); + _pendingSongs.Enqueue(song); } } if (changeCurrentSong) { - CurrentSong = _nextSongs.Dequeue(); + CurrentSong = _pendingSongs.Dequeue(); } } else @@ -205,7 +317,10 @@ private async Task Report(ReportType type, int channelId, string sid) } } - private void ThrowExceptionIfNoChannel() + /// + /// Throws the exception if current channel is null. + /// + private void ThrowExceptionIfCurrentChannelIsNull() { if (CurrentChannel == null) { @@ -213,7 +328,10 @@ private void ThrowExceptionIfNoChannel() } } - private void ThrowExceptionIfNoSong() + /// + /// Throws the exception if current song is null. + /// + private void ThrowExceptionIfCurrentSongIsNull() { if (CurrentSong == null) { diff --git a/Kfstorm.DoubanFM.Core/ReportType.cs b/Kfstorm.DoubanFM.Core/ReportType.cs index e53b4e2..56d4b45 100644 --- a/Kfstorm.DoubanFM.Core/ReportType.cs +++ b/Kfstorm.DoubanFM.Core/ReportType.cs @@ -1,13 +1,37 @@ namespace Kfstorm.DoubanFM.Core { + /// + /// The type of report + /// public enum ReportType { - NewChannel, + /// + /// Current channel changed + /// + CurrentChannelChanged, + /// + /// Skip current song + /// SkipCurrentSong, + /// + /// Ban current song + /// BanCurrentSong, + /// + /// The pending songs play list is empty + /// PlayListEmpty, + /// + /// Current song ended + /// CurrentSongEnded, + /// + /// Like current song (mark red heart) + /// Like, + /// + /// Cancel "like current song" (remove red heart mark) + /// CancelLike, } } diff --git a/Kfstorm.DoubanFM.Core/ReportTypeString.cs b/Kfstorm.DoubanFM.Core/ReportTypeString.cs index f509c40..d53af50 100644 --- a/Kfstorm.DoubanFM.Core/ReportTypeString.cs +++ b/Kfstorm.DoubanFM.Core/ReportTypeString.cs @@ -3,11 +3,14 @@ namespace Kfstorm.DoubanFM.Core { + /// + /// Manages the mapping between and single letter string + /// public static class ReportTypeString { private static readonly Dictionary EnumStringMapping = new Dictionary { - {ReportType.NewChannel, "n" }, + {ReportType.CurrentChannelChanged, "n" }, {ReportType.BanCurrentSong, "b" }, {ReportType.PlayListEmpty, "p" }, {ReportType.SkipCurrentSong, "s" }, @@ -16,6 +19,12 @@ public static class ReportTypeString {ReportType.CancelLike, "u" }, }; + /// + /// Gets the string based on report type. + /// + /// The report type. + /// The string. + /// public static string GetString(ReportType type) { string stringValue; diff --git a/Kfstorm.DoubanFM.Core/ServerConnection.cs b/Kfstorm.DoubanFM.Core/ServerConnection.cs index 946cc98..3549022 100644 --- a/Kfstorm.DoubanFM.Core/ServerConnection.cs +++ b/Kfstorm.DoubanFM.Core/ServerConnection.cs @@ -9,10 +9,25 @@ namespace Kfstorm.DoubanFM.Core { + /// + /// The default implementation of + /// public class ServerConnection : IServerConnection { + /// + /// The logger + /// protected ILog Logger = LogManager.GetLogger(typeof(ServerConnection)); + /// + /// Initializes a new instance of the class. + /// + /// The client ID. + /// The client secret. + /// Name of the application. + /// The application version. + /// The redirect URI. + /// The UDID. public ServerConnection(string clientId, string clientSecret, string appName, string appVersion, Uri redirectUri, string udid) { ClientId = clientId; @@ -23,36 +38,75 @@ public ServerConnection(string clientId, string clientSecret, string appName, st Udid = udid; } + /// + /// Initializes a new instance of the class. + /// public ServerConnection() { } + /// + /// Gets the context. The context contains contextual information about server connection, such as client ID and access token. + /// + /// + /// The context. + /// public IDictionary Context { get; } = new Dictionary(); + /// + /// Gets or sets the client ID. + /// + /// + /// The client ID. + /// public string ClientId { get { return GetContextOptional(StringTable.ClientId); } set { Context[StringTable.ClientId] = value; } } + /// + /// Gets or sets the client secret. + /// + /// + /// The client secret. + /// public string ClientSecret { get { return GetContextOptional(StringTable.ClientSecret); } set { Context[StringTable.ClientSecret] = value; } } + /// + /// Gets or sets the name of the application. + /// + /// + /// The name of the application. + /// public string AppName { get { return GetContextOptional(StringTable.AppName); } set { Context[StringTable.AppName] = value; } } + /// + /// Gets or sets the application version. + /// + /// + /// The application version. + /// public string AppVersion { get { return GetContextOptional(StringTable.Version); } set { Context[StringTable.Version] = value; } } + /// + /// Gets or sets the redirect URI. + /// + /// + /// The redirect URI. + /// public Uri RedirectUri { get @@ -63,18 +117,35 @@ public Uri RedirectUri set { Context[StringTable.RedirectUri] = value?.AbsoluteUri; } } + /// + /// Gets or sets the access token. + /// + /// + /// The access token. + /// public string AccessToken { get { return GetContextOptional(StringTable.AccessToken); } set { Context[StringTable.AccessToken] = value; } } + /// + /// Gets or sets the UDID. + /// + /// + /// The UDID. + /// public string Udid { get { return GetContextOptional(StringTable.Udid); } set { Context[StringTable.Udid] = value; } } + /// + /// Gets the optional context. + /// + /// The name of the context. + /// The optional context. protected virtual string GetContextOptional(string name) { string temp; @@ -85,16 +156,22 @@ protected virtual string GetContextOptional(string name) return null; } + /// + /// Creates an HTTP request. + /// + /// The URI. + /// protected virtual HttpWebRequest CreateRequest(Uri uri) { return WebRequest.CreateHttp(uri); } - public virtual async Task Get(Uri uri) - { - return await Get(uri, null); - } - + /// + /// Send an HTTP GET request to the specified URI, and get the response content as string. + /// + /// The URI. + /// The modifier to change the request before sending. The modifier can be null. + /// The content of response. public async Task Get(Uri uri, Action modifier) { Logger.Debug($"GET: {uri}"); @@ -114,11 +191,24 @@ public async Task Get(Uri uri, Action modifier) })); } + /// + /// Send an HTTP POST request to the specified URI, and get the response content as string. + /// + /// The URI. + /// The binary data need to be posted. Null or empty array means no data. + /// The content of response. public virtual async Task Post(Uri uri, byte[] data) { return await Post(uri, data, null); } + /// + /// Send an HTTP POST request to the specified URI, and get the response content as string. + /// + /// The URI. + /// The binary data need to be posted. Null or empty array means no data. + /// The modifier to change the request before sending. The modifier can be null. + /// The content of response. public async Task Post(Uri uri, byte[] data, Action modifier) { var dataLength = data?.Length ?? 0; diff --git a/Kfstorm.DoubanFM.Core/ServerException.cs b/Kfstorm.DoubanFM.Core/ServerException.cs index f12c240..ad1deab 100644 --- a/Kfstorm.DoubanFM.Core/ServerException.cs +++ b/Kfstorm.DoubanFM.Core/ServerException.cs @@ -7,15 +7,41 @@ namespace Kfstorm.DoubanFM.Core { + /// + /// Represents an error returned by server + /// public class ServerException : Exception { + /// + /// Gets the error code. + /// + /// + /// The error code. + /// public int Code { get; } + /// + /// Gets the error message. + /// + /// + /// The error message. + /// public string ErrorMessage { get; } + /// + /// Initializes a new instance of the class. + /// + /// The error code. + /// The error message. public ServerException(int code, string errorMessage) : this(code, errorMessage, null) { } + /// + /// Initializes a new instance of the class. + /// + /// The error code. + /// The error message. + /// The inner exception. public ServerException(int code, string errorMessage, Exception innerException) : base($"code: {code} msg: {errorMessage}", innerException) { @@ -23,6 +49,13 @@ public ServerException(int code, string errorMessage, Exception innerException) ErrorMessage = errorMessage; } + /// + /// If the action throws WebException, then parse the exception and rethrow a new instance of . + /// + /// + /// The action. + /// + /// public static async Task TryThrow(Func> action) { try @@ -35,13 +68,18 @@ public static async Task TryThrow(Func> action) // ReSharper disable once AssignNullToNotNullAttribute var reader = new StreamReader(stream, Encoding.UTF8); var jsonContent = await reader.ReadToEndAsync(); - int code; - string message; - ParseServerException(jsonContent, out code, out message); + var obj = JObject.Parse(jsonContent); + var code = (int)obj["code"]; + var message = (string)obj["msg"]; throw new ServerException(code, message, ex); } } + /// + /// If the content contains error, then parse the content and throw a new instance of . + /// + /// Content of JSON format. + /// public static void TryThrow(string jsonContent) { JObject obj; @@ -65,12 +103,5 @@ public static void TryThrow(string jsonContent) } } } - - private static void ParseServerException(string jsonContent, out int code, out string message) - { - var obj = JObject.Parse(jsonContent); - code = (int)obj["code"]; - message = (string)obj["msg"]; - } } } diff --git a/Kfstorm.DoubanFM.Core/Session.cs b/Kfstorm.DoubanFM.Core/Session.cs index 0992d80..21d5e72 100644 --- a/Kfstorm.DoubanFM.Core/Session.cs +++ b/Kfstorm.DoubanFM.Core/Session.cs @@ -5,21 +5,57 @@ namespace Kfstorm.DoubanFM.Core { + /// + /// The default implementation of + /// public class Session : ISession { + /// + /// The logger + /// protected ILog Logger = LogManager.GetLogger(typeof(Session)); + /// + /// Gets the user information. + /// + /// + /// The user information. + /// public UserInfo UserInfo { get; protected set; } + /// + /// Gets the server connection. + /// + /// + /// The server connection. + /// public IServerConnection ServerConnection { get; } + /// + /// Indicates whether the instance in working state. + /// protected int IsWorking; + /// + /// Initializes a new instance of the class. + /// + /// The server connection. public Session(IServerConnection serverConnection) { ServerConnection = serverConnection; } + /// + /// Logs on with specified authentication method. + /// + /// The authentication method. + /// + /// + /// Already logged on + /// or + /// Another unfinished log on/off request exists. + /// + /// Null user info is not allowed public async Task LogOn(IAuthentication authentication) { @@ -54,6 +90,14 @@ public async Task LogOn(IAuthentication authentication) throw new InvalidOperationException("Another unfinished log on/off request exists."); } + /// + /// Logs off. + /// + /// + /// Already logged off + /// or + /// Another unfinished log on/off request exists. + /// public void LogOff() { if (UserInfo == null) diff --git a/Kfstorm.DoubanFM.Core/Song.cs b/Kfstorm.DoubanFM.Core/Song.cs index 8fade11..b1d4192 100644 --- a/Kfstorm.DoubanFM.Core/Song.cs +++ b/Kfstorm.DoubanFM.Core/Song.cs @@ -2,27 +2,142 @@ namespace Kfstorm.DoubanFM.Core { + /// + /// Represents a song in douban.fm + /// public class Song : IEquatable { + /// + /// Gets or sets the album URL. + /// + /// + /// The album URL. + /// public string AlbumUrl { get; set; } + /// + /// Gets or sets the picture URL of cover. + /// + /// + /// The picture URL of cover. + /// public string PictureUrl { get; set; } + /// + /// Gets or sets the SSID. + /// + /// + /// The SSID. + /// public string Ssid { get; set; } + /// + /// Gets or sets the artist. + /// + /// + /// The artist. + /// public string Artist { get; set; } + /// + /// Gets or sets the URL of music file. + /// + /// + /// The URL of music file. + /// public string Url { get; set; } + /// + /// Gets or sets the company. + /// + /// + /// The company. + /// public string Company { get; set; } + /// + /// Gets or sets the title. + /// + /// + /// The title. + /// public string Title { get; set; } + /// + /// Gets or sets the average rating. + /// + /// + /// The average rating. + /// public double? AverageRating { get; set; } + /// + /// Gets or sets the length. + /// + /// + /// The length. + /// public int Length { get; set; } + /// + /// Gets or sets the sub type. + /// + /// + /// The sub type. + /// public string SubType { get; set; } + /// + /// Gets or sets the publish time. + /// + /// + /// The publish time. + /// public int? PublishTime { get; set; } + /// + /// Gets or sets the song lists count. + /// + /// + /// The song lists count. + /// public int? SongListsCount { get; set; } + /// + /// Gets the SID. + /// + /// + /// The SID. + /// public string Sid { get; } + /// + /// Gets or sets the AID. + /// + /// + /// The AID. + /// public string Aid { get; set; } + /// + /// Gets or sets the SHA256. + /// + /// + /// The SHA256. + /// public string Sha256 { get; set; } + /// + /// Gets or sets the bit rate. + /// + /// + /// The bit rate. + /// public int? Kbps { get; set; } + /// + /// Gets or sets the album title. + /// + /// + /// The album title. + /// public string AlbumTitle { get; set; } + /// + /// Gets or sets a value indicating whether this is marked with red heart. + /// + /// + /// true if is marked with red heart; otherwise, false. + /// public bool Like { get; set; } + /// + /// Initializes a new instance of the class. + /// + /// The SID. public Song(string sid) { Sid = sid; diff --git a/Kfstorm.DoubanFM.Core/SongNotSelectedException.cs b/Kfstorm.DoubanFM.Core/SongNotSelectedException.cs index 306cee8..77d4f0e 100644 --- a/Kfstorm.DoubanFM.Core/SongNotSelectedException.cs +++ b/Kfstorm.DoubanFM.Core/SongNotSelectedException.cs @@ -2,7 +2,9 @@ namespace Kfstorm.DoubanFM.Core { - [Serializable] + /// + /// This type of exception will be thrown when some operations which need current song is not null are triggerred but the current song of the player is null. + /// public class SongNotSelectedException : Exception { } diff --git a/Kfstorm.DoubanFM.Core/StringTable.cs b/Kfstorm.DoubanFM.Core/StringTable.cs index b8c5ab4..73e2714 100644 --- a/Kfstorm.DoubanFM.Core/StringTable.cs +++ b/Kfstorm.DoubanFM.Core/StringTable.cs @@ -1,5 +1,8 @@ namespace Kfstorm.DoubanFM.Core { + /// + /// A helper class to store all the common strings + /// public class StringTable { public const string ApiKey = "apikey"; diff --git a/Kfstorm.DoubanFM.Core/UriHelper.cs b/Kfstorm.DoubanFM.Core/UriHelper.cs index 6cab9b6..af3613e 100644 --- a/Kfstorm.DoubanFM.Core/UriHelper.cs +++ b/Kfstorm.DoubanFM.Core/UriHelper.cs @@ -5,8 +5,17 @@ namespace Kfstorm.DoubanFM.Core { + /// + /// A helper class of + /// public static class UriHelper { + /// + /// Appends the query to a URI. + /// + /// The URI. + /// The query name. + /// The query value. public static void AppendQuery(this UriBuilder uriBuilder, string name, string value) { name = Uri.EscapeDataString(name); @@ -27,6 +36,11 @@ public static void AppendQuery(this UriBuilder uriBuilder, string name, string v } } + /// + /// Removes the query from a URI. + /// + /// The URI. + /// The removed query in binary format (encoded with UTF-8) public static byte[] RemoveQuery(this UriBuilder uriBuilder) { var query = uriBuilder.Query; @@ -38,6 +52,11 @@ public static byte[] RemoveQuery(this UriBuilder uriBuilder) return Encoding.UTF8.GetBytes(query); } + /// + /// Appends the authentication common fields. + /// + /// The URI. + /// The server connection. public static void AppendAuthenticationCommonFields(this UriBuilder uriBuilder, IServerConnection serverConnection) { AppendQuery(uriBuilder, StringTable.ClientId, serverConnection.ClientId); @@ -45,6 +64,11 @@ public static void AppendAuthenticationCommonFields(this UriBuilder uriBuilder, AppendQuery(uriBuilder, StringTable.RedirectUri, serverConnection.RedirectUri.AbsoluteUri); } + /// + /// Appends the usage common fields. + /// + /// The URI. + /// The server connection. public static void AppendUsageCommonFields(this UriBuilder uriBuilder, IServerConnection serverConnection) { AppendQuery(uriBuilder, StringTable.ApiKey, serverConnection.ClientId); @@ -53,6 +77,11 @@ public static void AppendUsageCommonFields(this UriBuilder uriBuilder, IServerCo AppendQuery(uriBuilder, StringTable.Udid, serverConnection.Udid); } + /// + /// Gets the queries. + /// + /// The URI. + /// The queries public static IDictionary GetQueries(this Uri uri) { var queries = uri.Query.TrimStart('?').Split(new[] { '&' }, StringSplitOptions.RemoveEmptyEntries); diff --git a/Kfstorm.DoubanFM.Core/UserInfo.cs b/Kfstorm.DoubanFM.Core/UserInfo.cs index b4aea3e..97f23cd 100644 --- a/Kfstorm.DoubanFM.Core/UserInfo.cs +++ b/Kfstorm.DoubanFM.Core/UserInfo.cs @@ -2,12 +2,45 @@ namespace Kfstorm.DoubanFM.Core { + /// + /// Represents the user information returned in authentication result + /// public class UserInfo { + /// + /// Gets or sets the access token. + /// + /// + /// The access token. + /// public string AccessToken { get; set; } + /// + /// Gets or sets the username. + /// + /// + /// The username. + /// public string Username { get; set; } + /// + /// Gets or sets the user ID. + /// + /// + /// The user ID. + /// public long UserId { get; set; } + /// + /// Gets or sets the seconds which the access token will expire in. + /// + /// + /// The seconds which the access token will expire in. + /// public long ExpiresIn { get; set; } + /// + /// Gets or sets the refresh token. + /// + /// + /// The refresh token. + /// public string RefreshToken { get; set; } public override string ToString()