diff --git a/redmine-net-api-dev.sln b/redmine-net-api-dev.sln new file mode 100644 index 00000000..5d112ca3 --- /dev/null +++ b/redmine-net-api-dev.sln @@ -0,0 +1,36 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.10.34916.146 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "redmine-net-api", "src\redmine-net-api\redmine-net-api.csproj", "{E955CF52-3F03-46A6-9B72-420726AE1FCD}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "sample", "sample\sample.csproj", "{CEA0AE5A-09F5-412E-848F-58CE72C8F114}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + DebugJson|Any CPU = DebugJson|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {E955CF52-3F03-46A6-9B72-420726AE1FCD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {E955CF52-3F03-46A6-9B72-420726AE1FCD}.Debug|Any CPU.Build.0 = Debug|Any CPU + {E955CF52-3F03-46A6-9B72-420726AE1FCD}.DebugJson|Any CPU.ActiveCfg = DebugJson|Any CPU + {E955CF52-3F03-46A6-9B72-420726AE1FCD}.DebugJson|Any CPU.Build.0 = DebugJson|Any CPU + {E955CF52-3F03-46A6-9B72-420726AE1FCD}.Release|Any CPU.ActiveCfg = Release|Any CPU + {E955CF52-3F03-46A6-9B72-420726AE1FCD}.Release|Any CPU.Build.0 = Release|Any CPU + {CEA0AE5A-09F5-412E-848F-58CE72C8F114}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {CEA0AE5A-09F5-412E-848F-58CE72C8F114}.Debug|Any CPU.Build.0 = Debug|Any CPU + {CEA0AE5A-09F5-412E-848F-58CE72C8F114}.DebugJson|Any CPU.ActiveCfg = Debug|Any CPU + {CEA0AE5A-09F5-412E-848F-58CE72C8F114}.DebugJson|Any CPU.Build.0 = Debug|Any CPU + {CEA0AE5A-09F5-412E-848F-58CE72C8F114}.Release|Any CPU.ActiveCfg = Release|Any CPU + {CEA0AE5A-09F5-412E-848F-58CE72C8F114}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {D7E10C0C-0862-43D7-A3F8-23D13B8DFD3F} + EndGlobalSection +EndGlobal diff --git a/src/redmine-net-api/IRedmineManager.cs b/src/redmine-net-api/IRedmineManager.cs index b8148141..56f9215a 100644 --- a/src/redmine-net-api/IRedmineManager.cs +++ b/src/redmine-net-api/IRedmineManager.cs @@ -103,7 +103,7 @@ void Delete(string id, RequestOptions requestOptions = null) /// Returns the token for uploaded file. /// /// - Upload UploadFile(byte[] data); + Upload UploadFile(byte[] data, RequestOptions requestOptions = null); /// /// Downloads a file from the specified address. diff --git a/src/redmine-net-api/Net/WebClient/InternalRedmineApiWebClient.cs b/src/redmine-net-api/Net/WebClient/InternalRedmineApiWebClient.cs index 5a9b9c45..69a9b5b3 100644 --- a/src/redmine-net-api/Net/WebClient/InternalRedmineApiWebClient.cs +++ b/src/redmine-net-api/Net/WebClient/InternalRedmineApiWebClient.cs @@ -131,7 +131,7 @@ public ApiResponseMessage Download(string address, RequestOptions requestOptions public ApiResponseMessage Upload(string address, byte[] data, RequestOptions requestOptions = null) { var content = new ByteArrayApiRequestMessageContent(data); - return HandleRequest(address, HttpVerbs.UPLOAD, requestOptions, content); + return HandleRequest(address, HttpVerbs.POST, requestOptions, content); } #if !(NET20) diff --git a/src/redmine-net-api/Net/WebClient/MessageContent/ByteArrayApiRequestMessageContent.cs b/src/redmine-net-api/Net/WebClient/MessageContent/ByteArrayApiRequestMessageContent.cs index 4f72fc83..ec06145b 100644 --- a/src/redmine-net-api/Net/WebClient/MessageContent/ByteArrayApiRequestMessageContent.cs +++ b/src/redmine-net-api/Net/WebClient/MessageContent/ByteArrayApiRequestMessageContent.cs @@ -21,5 +21,6 @@ internal class ByteArrayApiRequestMessageContent : ApiRequestMessageContent public ByteArrayApiRequestMessageContent(byte[] content) { Body = content; + ContentType = RedmineConstants.CONTENT_TYPE_APPLICATION_STREAM; } } \ No newline at end of file diff --git a/src/redmine-net-api/RedmineKeys.cs b/src/redmine-net-api/RedmineKeys.cs index 5bd7661e..4022bb3e 100644 --- a/src/redmine-net-api/RedmineKeys.cs +++ b/src/redmine-net-api/RedmineKeys.cs @@ -268,6 +268,10 @@ public static class RedmineKeys /// /// /// + public const string FIELD = "field"; + /// + /// + /// public const string FIELD_FORMAT = "field_format"; /// /// @@ -362,7 +366,7 @@ public static class RedmineKeys /// /// /// - public const string ISSUE_CUSTOM_FIELD_IDS = "issue_custom_field_ids"; + public const string ISSUE_CUSTOM_FIELDS = "issue_custom_fields"; /// /// /// @@ -849,6 +853,10 @@ public static class RedmineKeys /// /// public const string WIKI_PAGES = "wiki_pages"; + /// + /// + /// + public const string ENABLED_STANDARD_FIELDS = "enabled_standard_fields"; } diff --git a/src/redmine-net-api/RedmineManager.cs b/src/redmine-net-api/RedmineManager.cs index f683dc3a..d5fd7c30 100644 --- a/src/redmine-net-api/RedmineManager.cs +++ b/src/redmine-net-api/RedmineManager.cs @@ -128,6 +128,15 @@ public List Get(RequestOptions requestOptions = null) return GetObjects(uri, requestOptions); } + /// + public List GetChildren(string ownerId, RequestOptions requestOptions = null) + where T : class, new() + { + var uri = RedmineApiUrls.GetListFragment(ownerId); + + return GetObjects(uri, requestOptions); + } + /// public PagedResults GetPaginated(RequestOptions requestOptions = null) where T : class, new() @@ -171,11 +180,11 @@ public void Delete(string id, RequestOptions requestOptions = null) } /// - public Upload UploadFile(byte[] data) + public Upload UploadFile(byte[] data, RequestOptions requestOptions = null) { var url = RedmineApiUrls.UploadFragment(); - var response = ApiClient.Upload(url, data); + var response = ApiClient.Upload(url, data, requestOptions); return response.DeserializeTo(Serializer); } diff --git a/src/redmine-net-api/RedmineManagerOptionsBuilder.cs b/src/redmine-net-api/RedmineManagerOptionsBuilder.cs index 8e682f0b..b5b49d23 100644 --- a/src/redmine-net-api/RedmineManagerOptionsBuilder.cs +++ b/src/redmine-net-api/RedmineManagerOptionsBuilder.cs @@ -177,6 +177,22 @@ public RedmineManagerOptionsBuilder WithVersion(Version version) return this; } + /// + /// Redmine�̃��[�g�p�X + /// + public string RootPath { get; private set; } + + /// + /// ���[�g�p�X��ݒ肷�� + /// + /// + /// + public RedmineManagerOptionsBuilder WithRootPath(string rootPath) + { + RootPath = rootPath; + return this; + } + /// /// /// @@ -201,7 +217,7 @@ internal RedmineManagerOptions Build() { ClientOptions ??= new RedmineWebClientOptions(); - var baseAddress = CreateRedmineUri(Host, ClientOptions.Scheme); + var baseAddress = CreateRedmineUri(Host, ClientOptions.Scheme, RootPath); var options = new RedmineManagerOptions() { @@ -268,7 +284,7 @@ internal static void EnsureDomainNameIsValid(string domainName) } } - internal static Uri CreateRedmineUri(string host, string scheme = null) + internal static Uri CreateRedmineUri(string host, string scheme = null, string root = null) { if (host.IsNullOrWhiteSpace() || host.Equals("string.Empty", StringComparison.OrdinalIgnoreCase)) { @@ -362,6 +378,10 @@ internal static Uri CreateRedmineUri(string host, string scheme = null) uriBuilder.Port = int.TryParse(uri.LocalPath, out var port) ? port : uri.Port; uriBuilder.Host = uri.Host; } + if (!string.IsNullOrWhiteSpace(root)) + { + uriBuilder.Path = root; + } } try diff --git a/src/redmine-net-api/Types/CustomField.cs b/src/redmine-net-api/Types/CustomField.cs index 28573116..451ea8ea 100644 --- a/src/redmine-net-api/Types/CustomField.cs +++ b/src/redmine-net-api/Types/CustomField.cs @@ -73,6 +73,10 @@ public sealed class CustomField : IdentifiableName, IEquatable /// /// public bool Searchable { get; internal set; } + /// + /// + /// + public string Description { get; internal set; } /// /// @@ -139,6 +143,7 @@ public override void ReadXml(XmlReader reader) case RedmineKeys.SEARCHABLE: Searchable = reader.ReadElementContentAsBoolean(); break; case RedmineKeys.TRACKERS: Trackers = reader.ReadElementContentAsCollection(); break; case RedmineKeys.VISIBLE: Visible = reader.ReadElementContentAsBoolean(); break; + case RedmineKeys.DESCRIPTION: Description = reader.ReadElementContentAsString().Trim(); break; default: reader.Read(); break; } } @@ -206,13 +211,13 @@ public bool Equals(CustomField other) && Multiple == other.Multiple && Searchable == other.Searchable && Visible == other.Visible - && string.Equals(CustomizedType,other.CustomizedType, StringComparison.OrdinalIgnoreCase) - && string.Equals(DefaultValue,other.DefaultValue, StringComparison.OrdinalIgnoreCase) - && string.Equals(FieldFormat,other.FieldFormat, StringComparison.OrdinalIgnoreCase) + && string.Equals(CustomizedType, other.CustomizedType, StringComparison.OrdinalIgnoreCase) + && string.Equals(DefaultValue, other.DefaultValue, StringComparison.OrdinalIgnoreCase) + && string.Equals(FieldFormat, other.FieldFormat, StringComparison.OrdinalIgnoreCase) && MaxLength == other.MaxLength && MinLength == other.MinLength - && string.Equals(Name,other.Name, StringComparison.OrdinalIgnoreCase) - && string.Equals(Regexp,other.Regexp, StringComparison.OrdinalIgnoreCase) + && string.Equals(Name, other.Name, StringComparison.OrdinalIgnoreCase) + && string.Equals(Regexp, other.Regexp, StringComparison.OrdinalIgnoreCase) && PossibleValues.Equals(other.PossibleValues) && Roles.Equals(other.Roles) && Trackers.Equals(other.Trackers); diff --git a/src/redmine-net-api/Types/CustomFieldValue.cs b/src/redmine-net-api/Types/CustomFieldValue.cs index 0a0f1033..560bda70 100644 --- a/src/redmine-net-api/Types/CustomFieldValue.cs +++ b/src/redmine-net-api/Types/CustomFieldValue.cs @@ -42,12 +42,19 @@ public CustomFieldValue() /// /// /// + /// /// - public CustomFieldValue(string value) + public CustomFieldValue(string value, bool token = false) { Info = value; + IsToken = token; } - + + /// + /// + /// + public bool IsToken { get; private set; } + #region Properties /// diff --git a/src/redmine-net-api/Types/Field.cs b/src/redmine-net-api/Types/Field.cs new file mode 100644 index 00000000..59634884 --- /dev/null +++ b/src/redmine-net-api/Types/Field.cs @@ -0,0 +1,139 @@ +/* + Copyright 2011 - 2023 Adrian Popescu + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +using System; +using System.Diagnostics; +using System.Xml; +using System.Xml.Schema; +using System.Xml.Serialization; +using Newtonsoft.Json; +using Redmine.Net.Api.Internals; +using Redmine.Net.Api.Serialization; + +namespace Redmine.Net.Api.Types +{ + /// + /// + /// + [DebuggerDisplay("{" + nameof(DebuggerDisplay) + ",nq}")] + [XmlRoot(RedmineKeys.FIELD)] + #pragma warning disable CA1711 + public sealed class Field : IXmlSerializable, IJsonSerializable, IEquatable + #pragma warning restore CA1711 + { + #region Properties + /// + /// + /// + public string Info { get; internal set; } + #endregion + + #region Implementation of IXmlSerializable + + /// + /// + /// + /// + public XmlSchema GetSchema() { return null; } + + /// + /// + /// + /// + public void ReadXml(XmlReader reader) + { + reader.Read(); + if (reader.NodeType == XmlNodeType.Text) + { + Info = reader.Value; + } + } + + /// + /// + /// + /// + public void WriteXml(XmlWriter writer) { } + + #endregion + + #region Implementation of IJsonSerialization + /// + /// + /// + /// + public void ReadJson(JsonReader reader) + { + if (reader.TokenType == JsonToken.String) + { + Info = reader.Value.ToString(); + } + } + + /// + /// + /// + /// + public void WriteJson(JsonWriter writer) { } + #endregion + + #region Implementation of IEquatable + /// + /// + /// + /// + /// + public bool Equals(Field other) + { + return other != null && Info == other.Info; + } + + /// + /// + /// + /// + /// + public override bool Equals(object obj) + { + if (ReferenceEquals(null, obj)) return false; + if (ReferenceEquals(this, obj)) return true; + if (obj.GetType() != GetType()) return false; + return Equals(obj as Field); + } + + /// + /// + /// + /// + public override int GetHashCode() + { + unchecked + { + var hashCode = 13; + hashCode = HashCodeHelper.GetHashCode(Info, hashCode); + return hashCode; + } + } + #endregion + + /// + /// + /// + /// + private string DebuggerDisplay => $"[{nameof(Field)}: {Info}]"; + + } +} \ No newline at end of file diff --git a/src/redmine-net-api/Types/IssueCustomField.cs b/src/redmine-net-api/Types/IssueCustomField.cs index d278d5fc..78735300 100644 --- a/src/redmine-net-api/Types/IssueCustomField.cs +++ b/src/redmine-net-api/Types/IssueCustomField.cs @@ -18,6 +18,7 @@ limitations under the License. using System.Collections.Generic; using System.Diagnostics; using System.Globalization; +using System.Linq; using System.Xml; using System.Xml.Serialization; using Newtonsoft.Json; @@ -58,7 +59,7 @@ public override void ReadXml(XmlReader reader) Name = reader.GetAttribute(RedmineKeys.NAME); Multiple = reader.ReadAttributeAsBoolean(RedmineKeys.MULTIPLE); - reader.Read(); + // reader.Read(); if (reader.NodeType == XmlNodeType.Whitespace) { @@ -76,25 +77,46 @@ public override void ReadXml(XmlReader reader) return; } - var attributeExists = !reader.GetAttribute("type").IsNullOrWhiteSpace(); - if (!attributeExists) + if (reader.IsEmptyElement) { - if (reader.IsEmptyElement) - { - reader.Read(); - return; - } + reader.Read(); + return; + } + reader.Read(); + var attributeExists = !reader.GetAttribute("type").IsNullOrWhiteSpace(); + if (!attributeExists) + { Values = new List { new CustomFieldValue(reader.ReadElementContentAsString()) }; } else - { Values = reader.ReadElementContentAsCollection(); - } + + + //var attributeExists = !reader.GetAttribute("type").IsNullOrWhiteSpace(); + + //if (!attributeExists) + //{ + // if (reader.IsEmptyElement) + // { + // reader.Read(); + // return; + // } + + // reader.Read(); + // Values = new List + // { + // new CustomFieldValue(reader.ReadElementContentAsString()) + // }; + //} + //else + //{ + // Values = reader.ReadElementContentAsCollection(); + //} } /// @@ -103,7 +125,7 @@ public override void ReadXml(XmlReader reader) /// public override void WriteXml(XmlWriter writer) { - if (Values == null) + if (Values == null || !Values.Any()) { return; } @@ -116,6 +138,12 @@ public override void WriteXml(XmlWriter writer) { writer.WriteArrayStringElement(RedmineKeys.VALUE, Values, GetValue); } + else if (Values[0].IsToken && !Values[0].Info.IsNullOrWhiteSpace()) + { + writer.WriteStartElement(RedmineKeys.VALUE); + writer.WriteElementString(RedmineKeys.TOKEN, Values[0].Info); + writer.WriteEndElement(); + } else { writer.WriteElementString(RedmineKeys.VALUE, itemsCount > 0 ? Values[0].Info : null); diff --git a/src/redmine-net-api/Types/Permission.cs b/src/redmine-net-api/Types/Permission.cs index 3a658453..bae4b147 100644 --- a/src/redmine-net-api/Types/Permission.cs +++ b/src/redmine-net-api/Types/Permission.cs @@ -77,23 +77,9 @@ public void WriteXml(XmlWriter writer) { } /// public void ReadJson(JsonReader reader) { - while (reader.Read()) + if (reader.TokenType == JsonToken.String) { - if (reader.TokenType == JsonToken.EndObject) - { - return; - } - - if (reader.TokenType != JsonToken.PropertyName) - { - continue; - } - - switch (reader.Value) - { - case RedmineKeys.PERMISSION: Info = reader.ReadAsString(); break; - default: reader.Read(); break; - } + Info = reader.Value.ToString(); } } diff --git a/src/redmine-net-api/Types/Project.cs b/src/redmine-net-api/Types/Project.cs index 44dd0c0d..d5aae737 100644 --- a/src/redmine-net-api/Types/Project.cs +++ b/src/redmine-net-api/Types/Project.cs @@ -170,7 +170,7 @@ public override void ReadXml(XmlReader reader) { case RedmineKeys.ID: Id = reader.ReadElementContentAsInt(); break; case RedmineKeys.CREATED_ON: CreatedOn = reader.ReadElementContentAsNullableDateTime(); break; - case RedmineKeys.CUSTOM_FIELDS: CustomFields = reader.ReadElementContentAsCollection(); break; + case RedmineKeys.ISSUE_CUSTOM_FIELDS: CustomFields = reader.ReadElementContentAsCollection(); break; case RedmineKeys.DESCRIPTION: Description = reader.ReadElementContentAsString(); break; case RedmineKeys.ENABLED_MODULES: EnabledModules = reader.ReadElementContentAsCollection(); break; case RedmineKeys.HOMEPAGE: HomePage = reader.ReadElementContentAsString(); break; @@ -211,7 +211,7 @@ public override void WriteXml(XmlWriter writer) if (Id == 0) { - writer.WriteRepeatableElement(RedmineKeys.ISSUE_CUSTOM_FIELD_IDS, (IEnumerable)CustomFields); + writer.WriteRepeatableElement(RedmineKeys.ISSUE_CUSTOM_FIELDS, (IEnumerable)CustomFields); return; } @@ -285,7 +285,7 @@ public override void WriteJson(JsonWriter writer) if (Id == 0) { - writer.WriteRepeatableElement(RedmineKeys.ISSUE_CUSTOM_FIELD_IDS, (IEnumerable)CustomFields); + writer.WriteRepeatableElement(RedmineKeys.ISSUE_CUSTOM_FIELDS, (IEnumerable)CustomFields); return; } diff --git a/src/redmine-net-api/Types/Tracker.cs b/src/redmine-net-api/Types/Tracker.cs index 157b75aa..7cc0aad5 100644 --- a/src/redmine-net-api/Types/Tracker.cs +++ b/src/redmine-net-api/Types/Tracker.cs @@ -15,10 +15,13 @@ limitations under the License. */ using System; +using System.Collections.Generic; using System.Diagnostics; +using System.Linq; using System.Xml; using System.Xml.Serialization; using Newtonsoft.Json; +using Newtonsoft.Json.Linq; using Redmine.Net.Api.Extensions; using Redmine.Net.Api.Internals; @@ -41,6 +44,11 @@ public class Tracker : IdentifiableName, IEquatable /// public string Description { get; internal set; } + /// + /// Get the enabled fields of this tracker. + /// + public IList EnabledStandardFields { get; internal set; } + #region Implementation of IXmlSerialization /// /// Generates an object from its XML representation. @@ -63,6 +71,7 @@ public override void ReadXml(XmlReader reader) case RedmineKeys.NAME: Name = reader.ReadElementContentAsString(); break; case RedmineKeys.DEFAULT_STATUS: DefaultStatus = new IdentifiableName(reader); break; case RedmineKeys.DESCRIPTION: Description = reader.ReadElementContentAsString(); break; + case RedmineKeys.ENABLED_STANDARD_FIELDS: EnabledStandardFields = reader.ReadElementContentAsCollection(); break; default: reader.Read(); break; } } @@ -95,6 +104,7 @@ public override void ReadJson(JsonReader reader) case RedmineKeys.NAME: Name = reader.ReadAsString(); break; case RedmineKeys.DEFAULT_STATUS: DefaultStatus = new IdentifiableName(reader); break; case RedmineKeys.DESCRIPTION: Description = reader.ReadAsString(); break; + case RedmineKeys.ENABLED_STANDARD_FIELDS: EnabledStandardFields = reader.ReadAsCollection(); break; default: reader.Read(); break; } } @@ -140,6 +150,7 @@ public override int GetHashCode() var hashCode = 13; hashCode = HashCodeHelper.GetHashCode(Id, hashCode); hashCode = HashCodeHelper.GetHashCode(Name, hashCode); + hashCode = HashCodeHelper.GetHashCode(EnabledStandardFields, hashCode); return hashCode; } } diff --git a/src/redmine-net-api/redmine-net-api.csproj b/src/redmine-net-api/redmine-net-api.csproj index cc750f06..69240489 100644 --- a/src/redmine-net-api/redmine-net-api.csproj +++ b/src/redmine-net-api/redmine-net-api.csproj @@ -4,7 +4,7 @@ Redmine.Net.Api redmine-net-api - net20;net40;net45;net451;net452;net46;net461;net462;net47;net471;net472;net48;net481;net60;net70;net80 + net40;net45;net451;net452;net46;net461;net462;net47;net471;net472;net48;net481;net60;net70;net80 false True true