/// <summary>
 /// Authenticates and returns user info.
 /// </summary>
 /// <returns>
 /// The user info, including username and token.
 /// </returns>
 public override async Task<UserInfo> Authenticate()
 {
     var uriBuilder = new UriBuilder("https://www.douban.com/service/auth2/auth");
     uriBuilder.AppendAuthenticationCommonFields(ServerConnection);
     uriBuilder.AppendQuery(StringTable.ResponseType, StringTable.Code);
     var redirectedUri = await GetRedirectedUri(uriBuilder.Uri);
     if (redirectedUri != null)
     {
         var queries = redirectedUri.GetQueries();
         string code;
         if (queries.TryGetValue(StringTable.Code, out code) && !string.IsNullOrEmpty(code))
         {
             var tokenUrl = new UriBuilder("https://www.douban.com/service/auth2/token");
             tokenUrl.AppendAuthenticationCommonFields(ServerConnection);
             tokenUrl.AppendQuery(StringTable.GrantType, StringTable.AuthorizationCode);
             tokenUrl.AppendQuery(StringTable.Code, code);
             var jsonContent = await ServerConnection.Post(tokenUrl.Uri, null);
             return ParseLogOnResult(jsonContent);
         }
         string error;
         if (queries.TryGetValue(StringTable.Error, out error))
         {
             throw new ServerException(-1, error);
         }
     }
     throw new OperationCanceledException("User cancelled OAuth.");
 }
 /// <summary>
 /// Creates the get play list URI.
 /// </summary>
 /// <param name="connection">The server connection.</param>
 /// <param name="channelId">The channel ID.</param>
 /// <param name="type">The report type.</param>
 /// <param name="sid">The SID of current song.</param>
 /// <param name="start">The start song code.</param>
 /// <param name="formats">The format of music file.</param>
 /// <param name="kbps">The bit rate of music file.</param>
 /// <param name="playedTime">The played time of current song.</param>
 /// <param name="mode">The mode.</param>
 /// <param name="excludedSids">The excluded SIDs.</param>
 /// <param name="max">The maximum size of returned play list.</param>
 /// <returns></returns>
 public static Uri CreateGetPlayListUri(this IServerConnection connection, int channelId, ReportType type, string sid, string start, string formats, int? kbps, double? playedTime, string mode, IEnumerable<string> excludedSids, int? max)
 {
     var uriBuilder = new UriBuilder("https://api.douban.com/v2/fm/playlist");
     uriBuilder.AppendUsageCommonFields(connection);
     uriBuilder.AppendQuery(StringTable.Channel, channelId.ToString(CultureInfo.InvariantCulture));
     uriBuilder.AppendQuery(StringTable.Type, ReportTypeString.GetString(type));
     uriBuilder.AppendQuery(StringTable.Sid, sid);
     uriBuilder.AppendQuery(StringTable.Start, start); // If start song code is not empty, then the first song returned will always be the same one.
     uriBuilder.AppendQuery(StringTable.Formats, formats);
     uriBuilder.AppendQuery(StringTable.Kbps, kbps?.ToString(CultureInfo.InvariantCulture));
     uriBuilder.AppendQuery(StringTable.PlayedTime, playedTime?.ToString("F1", CultureInfo.InvariantCulture));
     uriBuilder.AppendQuery(StringTable.Mode, mode);
     uriBuilder.AppendQuery(StringTable.Exclude, excludedSids == null ? null : String.Join(",", excludedSids));
     uriBuilder.AppendQuery(StringTable.Max, max?.ToString(CultureInfo.InvariantCulture));
     return uriBuilder.Uri;
 }
 /// <summary>
 /// Creates the get recommended channels URI.
 /// </summary>
 /// <param name="connection">The server connection.</param>
 /// <returns></returns>
 public static Uri CreateGetRecommendedChannelsUri(this IServerConnection connection)
 {
     var uriBuilder = new UriBuilder("https://api.douban.com/v2/fm/app_channels");
     uriBuilder.AppendUsageCommonFields(connection);
     // ReSharper disable once StringLiteralTypo
     uriBuilder.AppendQuery(StringTable.IconCategory, "xlarge");
     return uriBuilder.Uri;
 }
        /// <summary>
        /// Gets detailed information about the user.
        /// </summary>
        /// <param name="username">The login username for the user.</param>
        /// <returns>Detailed information about the user.</returns>
        /// <exception cref="WebServiceException">The specified username does not exist, or the caller does not have permission to view the users.</exception>
        public User GetUser(string username)
        {
            var qb = new UriBuilder(client.BaseUri.AppendPath(UserUriPrefix));
            qb.AppendQuery("username", username);
            qb.AppendQuery("expand", "groups");

            return GetUser(qb.Uri);
        }
        /// <summary>
        /// Gets a list of all groups.
        /// </summary>
        /// <returns>A list of groups.</returns>
        public IEnumerable<string> GetGroups()
        {
            var qb = new UriBuilder(client.BaseUri.AppendPath(GroupPickerUriPrefix));
            qb.AppendQuery("maxResults", "500");

            var response = client.Get<JsonObject>(qb.Uri.ToString());

            var groups = response.Get<IEnumerable<JsonObject>>("groups");

            return groups.Select(a => a.Get<string>("name"));
        }
        /// <summary>
        /// Authenticates and returns user info.
        /// </summary>
        /// <returns>
        /// The user info, including username and token.
        /// </returns>
        /// <exception cref="System.InvalidOperationException">
        /// Username is empty
        /// or
        /// Password is empty
        /// </exception>
        public override async Task<UserInfo> Authenticate()
        {
            if (string.IsNullOrWhiteSpace(Username))
            {
                throw new InvalidOperationException("Username is empty");
            }
            if (string.IsNullOrEmpty(Password))
            {
                throw new InvalidOperationException("Password is empty");
            }

            var uriBuilder = new UriBuilder("https://www.douban.com/service/auth2/token");
            uriBuilder.AppendAuthenticationCommonFields(ServerConnection);
            uriBuilder.AppendQuery(StringTable.GrantType, StringTable.Password);
            uriBuilder.AppendQuery(StringTable.Username, Username);
            uriBuilder.AppendQuery(StringTable.Password, Password);
            var data = uriBuilder.RemoveQuery();
            var jsonContent = await ServerConnection.Post(uriBuilder.Uri, data);
            return ParseLogOnResult(jsonContent);
        }
        /// <summary>
        /// Retrieves a list of users who may be used as assignee when creating an issue. For a list of users when editing an issue, see <see cref="IIssueRestClient.GetAssignableUsers(string, int?, int?)"/>.
        /// </summary>
        /// <param name="projectKey">The unique key for the project (e.g. "AA").</param>
        /// <param name="startAt">The index of the first user to return (0-based).</param>
        /// <param name="maxResults">The maximum number of users to return (defaults to 50). The maximum allowed value is 1000. If you specify a value that is higher than this number, your search results will be truncated.</param>
        /// <returns>Returns a list of users who may be assigned to an issue during creation.</returns>
        public IEnumerable<User> GetAssignableUsers(string projectKey, int? startAt, int? maxResults)
        {
            var qb = new UriBuilder(client.BaseUri.AppendPath(JiraUserRestClient.UserAssignableSearchUriPrefix));
            qb.AppendQuery("project", projectKey);

            if (maxResults != null)
            {
                qb.AppendQuery("maxResults", maxResults.ToString());
            }

            if (startAt != null)
            {
                qb.AppendQuery("startAt", startAt.ToString());
            }

            return client.Get<IEnumerable<User>>(qb.Uri.ToString());
        }
        /// <summary>
        /// Returns the meta data for creating issues.
        /// This includes the available projects, issue types and fields, including field types and whether or not those fields are required. Projects will not be returned if the user does not have permission to create issues in that project.</summary>
        /// <param name="options">A set of options that allow the project/field/issue types to be constrained. The <see cref="GetCreateIssueMetadataOptionsBuilder"/> class can be used to help generate the appropriate request.</param>
        /// <seealso cref="GetCreateIssueMetadataOptionsBuilder"/>
        /// <returns>A collection of fields for each project that corresponds with fields in the create screen for each project/issue type.</returns>
        /// <exception cref="WebServiceException">The caller is not logged in, or does not have permission to view any of the requested projects.</exception>
        public IEnumerable<CimProject> GetCreateIssueMetadata(GetCreateIssueMetadataOptions options)
        {
            var qb = new UriBuilder(client.BaseUri);
            qb.Path = qb.Path.AppendPath("issue", "createmeta");

            if (options != null)
            {
                if (options.ProjectIds != null && options.ProjectIds.Any())
                {
                    qb.AppendQuery("projectIds", options.ProjectIds.Join(","));
                }

                if (options.ProjectKeys != null && options.ProjectKeys.Any())
                {
                    qb.AppendQuery("projectKeys", options.ProjectKeys.Join(","));
                }

                if (options.IssueTypeIds != null && options.IssueTypeIds.Any())
                {
                    qb.AppendQuery("issuetypeIds", options.IssueTypeIds.Join(","));
                }

                if (options.IssueTypeName != null)
                {
                    foreach (var i in options.IssueTypeName)
                    {
                        qb.AppendQuery("issuetypeNames", i);
                    }
                }

                if (options.Expandos != null && options.Expandos.Any())
                {
                    qb.AppendQuery("expand", options.Expandos.Join(","));
                }
            }

            var json = client.Get<JsonObject>(qb.Uri.ToString());
            return json.Get<IEnumerable<CimProject>>("projects");
        }
 /// <summary>
 /// Delete an issue.
 /// If the issue has subtasks you must set the parameter <see cref="deleteSubtasks"/> to delete the issue. You cannot delete an issue without its subtasks also being deleted.
 /// </summary>
 /// <param name="issueKey">The unique key of the issue to delete.</param>
 /// <param name="deleteSubtasks">Must be set to true if the issue has subtasks.</param>
 /// <exception cref="WebServiceException">The requested issue is not found, or the user does not have permission to delete it.</exception>
 public void DeleteIssue(string issueKey, bool deleteSubtasks)
 {
     var qb = new UriBuilder(client.BaseUri);
     qb.Path = qb.Path.AppendPath("issue", issueKey);
     qb.AppendQuery("deleteSubtasks", deleteSubtasks.ToString().ToLower());
     client.Delete<JsonObject>(qb.Uri.ToString());
 }
        /// <summary>
        /// Adds a new work log entry to issue.
        /// </summary>
        /// <param name="worklogUri">The URI for the work log resource for the selected issue.</param>
        /// <param name="worklogInput">The work log information to add to the issue.</param>
        /// <exception cref="WebServiceException">If the issue does not exist, or the the calling user does not have permission to add work log information to the issue.</exception>
        public void AddWorklog(Uri worklogUri, WorklogInput worklogInput)
        {
            var qb = new UriBuilder(worklogUri);
            qb.AppendQuery("adjustEstimate", worklogInput.AdjustEstimate.ToString().ToLower());

            switch (worklogInput.AdjustEstimate)
            {
                case WorklogInput.AdjustmentEstimate.New:
                    qb.AppendQuery("newEstimate", worklogInput.AdjustEstimateValue.NullToEmpty());
                    break;

                case WorklogInput.AdjustmentEstimate.Manual:
                    qb.AppendQuery("reduceBy", worklogInput.AdjustEstimateValue.NullToEmpty());
                    break;
            }

            client.Post<JsonObject>(qb.Uri.ToString(), WorklogInputJsonGenerator.Generate(worklogInput));
        }
        /// <summary>
        /// Deletes a project component.
        /// </summary>
        /// <param name="componentUri">The URI of the component to delete.</param>
        /// <param name="moveIssueToComponentUri">Any issues assigned to the component being deleted will be moved to the specified component.</param>
        /// <exception cref="WebServiceException">The caller is not logged in and does not have permission to delete the component.</exception>
        public void RemoveComponent(Uri componentUri, Uri moveIssueToComponentUri)
        {
            var qb = new UriBuilder(componentUri);
            if (moveIssueToComponentUri != null)
            {
                qb.AppendQuery("moveIssuesTo", moveIssueToComponentUri.ToString());
            }

            client.Delete<JsonObject>(qb.Uri.ToString());
        }
 /// <summary>
 /// Creates the get lyrics URI.
 /// </summary>
 /// <param name="sid">The SID of the song.</param>
 /// <param name="ssid">The SSID of the song.</param>
 /// <returns></returns>
 public static Uri CreateGetLyricsUri(string sid, string ssid)
 {
     var uriBuilder = new UriBuilder("https://api.douban.com/v2/fm/lyric");
     uriBuilder.AppendQuery(StringTable.Sid, sid);
     uriBuilder.AppendQuery(StringTable.Ssid, ssid);
     return uriBuilder.Uri;
 }
 /// <summary>
 /// Creates the get channel info URI.
 /// </summary>
 /// <param name="connection">The server connection.</param>
 /// <param name="channelId">The channel ID.</param>
 /// <returns></returns>
 public static Uri CreateGetChannelInfoUri(this IServerConnection connection, int channelId)
 {
     var uriBuilder = new UriBuilder("https://api.douban.com/v2/fm/channel_info");
     uriBuilder.AppendUsageCommonFields(connection);
     uriBuilder.AppendQuery(StringTable.Id, channelId.ToString(CultureInfo.InvariantCulture));
     return uriBuilder.Uri;
 }
 /// <summary>
 /// Creates the search channel URI.
 /// </summary>
 /// <param name="connection">The server connection.</param>
 /// <param name="query">The query.</param>
 /// <param name="start">The preferred index of the first channel in the returned channel array.</param>
 /// <param name="size">The max size of returned channel array.</param>
 /// <returns></returns>
 public static Uri CreateSearchChannelUri(this IServerConnection connection, string query, int start, int size)
 {
     var uriBuilder = new UriBuilder("https://api.douban.com/v2/fm/search/channel");
     uriBuilder.AppendUsageCommonFields(connection);
     uriBuilder.AppendQuery(StringTable.Query, query);
     uriBuilder.AppendQuery(StringTable.Start, start.ToString(CultureInfo.InvariantCulture));
     uriBuilder.AppendQuery(StringTable.Limit, size.ToString(CultureInfo.InvariantCulture));
     return uriBuilder.Uri;
 }
        /// <summary>
        /// Performs a JQL search and returns issues matching the query.
        /// </summary>
        /// <param name="jql">A valid JQL query. Restricted JQL characters (like '/') must be properly escaped.</param>
        /// <param name="maxResults">The maximum results for the search. If null, the default number configured in JIRA is used (generally 50).</param>
        /// <param name="startAt">Starting index (0-based) defining how many issues should be skipped in the result set.</param>
        /// <param name="fields">A set of fields which should be retrieved.You can specify *all for all fields\or *navigable (which is the default value, used when null is given) which will cause to include only
        /// navigable fields in the result. To ignore the specific field you can use "-" before the field's name.
        /// Note that the following fields: summary, issuetype, created, updated, project and status are
        /// required. These fields are included in *all and *navigable.</param>
        /// <returns>The issues that match a given JQL query.</returns>
        /// <exception cref="WebServiceException">There was a problem parsing the JQL query..</exception>
        public SearchResult SearchJql(string jql, int? maxResults, int? startAt, params string[] fields)
        {
            var qb = new UriBuilder(searchUri);
            qb.AppendQuery("jql", jql);

            if (maxResults != null)
            {
                qb.AppendQuery("maxResults", maxResults.ToString());
            }

            if (startAt != null)
            {
                qb.AppendQuery("startAt", startAt.ToString());
            }

            if (fields != null && fields.Length > 0)
            {
                qb.AppendQuery("fields", fields.Join(","));
            }

            var query = qb.Uri.ToString();

            return query.Length > MaxJqlLengthForGet ? client.Put<SearchResult>(query, string.Empty) : client.Get<SearchResult>(query);
        }
        /// <summary>
        /// Get a list of the transitions possible for this issue by the current user, along with fields that are required and their types.
        /// </summary>
        /// <param name="transitionsUri">URI of transitions resource of selected issue. Usually obtained by getting the <see cref="Issue.TransitionsUri"/> property.</param>
        /// <returns>Transition information about the transitions available for the selected issue in its current state.</returns>
        /// <exception cref="WebServiceException">The requested transition URI is not found, or the user does not have permission to view it.</exception>
        public IEnumerable<Transition> GetTransitions(Uri transitionsUri)
        {
            var qb = new UriBuilder(transitionsUri);
            qb.AppendQuery("expand", "transitions.fields");

            var json = client.Get<JsonObject>(qb.Uri.ToString());
            return TransitionJsonParser.Parse(json);
        }
        /// <summary>
        /// Removes selected person as a watcher for selected issue.
        /// </summary>
        /// <param name="watchersUri">URI of the watchers resource for the selected issue. Usually obtained by getting the <see cref="BasicWatchers.Self"/> property on the <see cref="Issue"/>.</param>
        /// <param name="username">The user to remove as a watcher.</param>
        /// <exception cref="WebServiceException">If the issue does not exist, or the the calling user does not have permission to modifier watchers of the issue.</exception>
        public void RemoveWatcher(Uri watchersUri, string username)
        {
            var qb = new UriBuilder(watchersUri);
            if (GetServerInfo().BuildNumber >= ServerVersionConstants.BuildNumberJira44)
            {
                qb.AppendQuery("username", username);
            }
            else
            {
                qb.Path.AppendPath(username);
            }

            client.Delete<JsonObject>(qb.Uri.ToString());
        }
 /// <summary>
 /// Creates the get song URL URI.
 /// </summary>
 /// <param name="connection">The server connection.</param>
 /// <param name="sid">The SID of the song.</param>
 /// <param name="ssid">The SSID of the song.</param>
 /// <returns></returns>
 public static Uri CreateGetSongUrlUri(this IServerConnection connection, string sid, string ssid)
 {
     var uriBuilder = new UriBuilder("https://api.douban.com/v2/fm/song_url");
     uriBuilder.AppendUsageCommonFields(connection);
     uriBuilder.AppendQuery(StringTable.Sid, sid);
     uriBuilder.AppendQuery(StringTable.Ssid, ssid);
     return uriBuilder.Uri;
 }