Merge pull request #34 from greenshot/bugfix/BUG-2016

BUG-2016: Added SVG support for the jira plugin
This commit is contained in:
Robin Krom 2016-09-02 10:49:47 +02:00 committed by GitHub
commit 4a0ec2448f
10 changed files with 248 additions and 34 deletions

View file

@ -37,7 +37,8 @@ namespace GreenshotJiraPlugin.Forms {
private readonly JiraConnector _jiraConnector;
private Issue _selectedIssue;
private readonly GreenshotColumnSorter _columnSorter;
private readonly JiraConfiguration _config = IniConfig.GetIniSection<JiraConfiguration>();
private static readonly JiraConfiguration JiraConfig = IniConfig.GetIniSection<JiraConfiguration>();
private static readonly CoreConfiguration CoreConfig = IniConfig.GetIniSection<CoreConfiguration>();
public JiraForm(JiraConnector jiraConnector) {
InitializeComponent();
@ -83,12 +84,12 @@ namespace GreenshotJiraPlugin.Forms {
jiraFilterBox.SelectedIndex = 0;
}
ChangeModus(true);
if (_config.LastUsedJira != null)
if (JiraConfig.LastUsedJira != null)
{
_selectedIssue = await _jiraConnector.GetIssueAsync(_config.LastUsedJira);
_selectedIssue = await _jiraConnector.GetIssueAsync(JiraConfig.LastUsedJira);
if (_selectedIssue != null)
{
jiraKey.Text = _config.LastUsedJira;
jiraKey.Text = JiraConfig.LastUsedJira;
uploadButton.Enabled = true;
}
}
@ -117,7 +118,7 @@ namespace GreenshotJiraPlugin.Forms {
}
public async Task UploadAsync(IBinaryContainer attachment) {
_config.LastUsedJira = _selectedIssue.Key;
JiraConfig.LastUsedJira = _selectedIssue.Key;
attachment.Filename = jiraFilenameBox.Text;
await _jiraConnector.AttachAsync(_selectedIssue.Key, attachment);
@ -145,31 +146,50 @@ namespace GreenshotJiraPlugin.Forms {
MessageBox.Show(this, ex.Message, "Error in filter", MessageBoxButtons.OK, MessageBoxIcon.Error);
}
jiraListView.BeginUpdate();
jiraListView.Items.Clear();
if (issues?.Count > 0) {
jiraListView.Columns.Clear();
LangKey[] columns = { LangKey.column_id, LangKey.column_created, LangKey.column_assignee, LangKey.column_reporter, LangKey.column_summary };
foreach (LangKey column in columns) {
jiraListView.Columns.Add(Language.GetString("jira", column));
}
foreach (var issue in issues) {
var item = new ListViewItem(issue.Key)
LangKey[] columns = { LangKey.column_issueType, LangKey.column_id, LangKey.column_created, LangKey.column_assignee, LangKey.column_reporter, LangKey.column_summary };
foreach (LangKey column in columns)
{
string translation;
if (!Language.TryGetString("jira", column, out translation))
{
Tag = issue
translation = "";
}
jiraListView.Columns.Add(translation);
}
var imageList = new ImageList {
ImageSize = CoreConfig.IconSize
};
jiraListView.SmallImageList = imageList;
jiraListView.LargeImageList = imageList;
foreach (var issue in issues) {
var issueIcon = await _jiraConnector.GetIssueTypeBitmapAsync(issue);
imageList.Images.Add(issueIcon);
var item = new ListViewItem
{
Tag = issue,
ImageIndex = imageList.Images.Count - 1
};
item.SubItems.Add(issue.Key);
item.SubItems.Add(issue.Fields.Created.ToString("d", DateTimeFormatInfo.InvariantInfo));
item.SubItems.Add(issue.Fields.Assignee?.DisplayName);
item.SubItems.Add(issue.Fields.Reporter?.DisplayName);
item.SubItems.Add(issue.Fields.Summary);
jiraListView.Items.Add(item);
for (int i = 0; i < columns.Length; i++)
{
jiraListView.AutoResizeColumn(i, ColumnHeaderAutoResizeStyle.ColumnContent);
}
jiraListView.Invalidate();
jiraListView.Update();
}
for (int i = 0; i < columns.Length; i++) {
jiraListView.AutoResizeColumn(i, ColumnHeaderAutoResizeStyle.ColumnContent);
}
jiraListView.Refresh();
}
jiraListView.EndUpdate();
jiraListView.Refresh();
}
}

View file

@ -49,8 +49,13 @@
<Reference Include="log4net">
<HintPath>..\Greenshot\Lib\log4net.dll</HintPath>
</Reference>
<Reference Include="Svg, Version=2.2.1.1459, Culture=neutral, PublicKeyToken=12a0bac221edeae2, processorArchitecture=MSIL">
<HintPath>..\packages\Svg.2.2.1\lib\net35\Svg.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="System" />
<Reference Include="System.Drawing" />
<Reference Include="System.Net.Http" />
<Reference Include="System.Runtime.Caching" />
<Reference Include="System.Web.Services" />
<Reference Include="System.Windows.Forms" />
@ -82,6 +87,7 @@
<Compile Include="LanguageKeys.cs" />
<Compile Include="Log4NetLogger.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="SvgBitmapHttpContentConverter.cs" />
<None Include="Languages\language_jiraplugin-de-DE.xml">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
@ -105,6 +111,10 @@
<Project>{5B924697-4DCD-4F98-85F1-105CB84B7341}</Project>
<Name>GreenshotPlugin</Name>
</ProjectReference>
<ProjectReference Include="..\Greenshot\Greenshot.csproj">
<Project>{CD642BF4-D815-4D67-A0B5-C69F0B8231AF}</Project>
<Name>Greenshot</Name>
</ProjectReference>
</ItemGroup>
<PropertyGroup>
<PostBuildEvent>mkdir "$(SolutionDir)Greenshot\bin\$(Configuration)\Plugins\$(ProjectName)"
@ -112,12 +122,13 @@
del /q /s "$(SolutionDir)Greenshot\bin\$(Configuration)\Plugins\$(ProjectName)"\*
if "$(ConfigurationName)" == "Release" (
..\..\..\packages\LibZ.Tool.1.2.0.0\tools\libz.exe inject-dll --assembly $(ProjectDir)bin\$(Configuration)\$(TargetFileName) --include $(ProjectDir)bin\$(Configuration)\dapplo*.dll --move
..\..\..\packages\LibZ.Tool.1.2.0.0\tools\libz.exe inject-dll --assembly $(ProjectDir)bin\$(Configuration)\$(TargetFileName) --include $(ProjectDir)bin\$(Configuration)\dapplo*.dll --include $(ProjectDir)bin\$(Configuration)\svg.dll --move
)
copy "$(ProjectDir)bin\$(Configuration)\$(TargetFileName)" "$(SolutionDir)Greenshot\bin\$(Configuration)\Plugins\$(ProjectName)\*.gsp"
if "$(ConfigurationName)" == "Debug" (
copy "$(ProjectDir)bin\$(Configuration)\Dapplo.*" "$(SolutionDir)Greenshot\bin\$(Configuration)\Plugins\$(ProjectName)\"
copy "$(ProjectDir)bin\$(Configuration)\Svg.dll" "$(SolutionDir)Greenshot\bin\$(Configuration)\Plugins\$(ProjectName)\"
copy "$(ProjectDir)bin\$(Configuration)\$(ProjectName).pdb" "$(SolutionDir)Greenshot\bin\$(Configuration)\Plugins\$(ProjectName)\"
)

View file

@ -31,7 +31,7 @@ namespace GreenshotJiraPlugin
/// <summary>
/// This is the bach for the IssueType bitmaps
/// </summary>
public class IssueTypeBitmapCache : AsyncMemoryCache<Issue, Bitmap>
public class IssueTypeBitmapCache : AsyncMemoryCache<IssueType, Bitmap>
{
private readonly JiraApi _jiraApi;
@ -42,9 +42,14 @@ namespace GreenshotJiraPlugin
ExpireTimeSpan = TimeSpan.FromHours(1);
}
protected override async Task<Bitmap> CreateAsync(Issue issue, CancellationToken cancellationToken = new CancellationToken())
protected override string CreateKey(IssueType keyObject)
{
return await _jiraApi.GetUriContentAsync<Bitmap>(issue.Fields.IssueType.IconUri, cancellationToken).ConfigureAwait(false);
return keyObject.Name;
}
protected override async Task<Bitmap> CreateAsync(IssueType issueType, CancellationToken cancellationToken = new CancellationToken())
{
return await _jiraApi.GetUriContentAsync<Bitmap>(issueType.IconUri, cancellationToken).ConfigureAwait(false);
}
}
}

View file

@ -24,9 +24,11 @@ using System;
using System.Collections.Generic;
using System.Drawing;
using System.IO;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;
using Dapplo.HttpExtensions;
using Dapplo.Jira;
using Dapplo.Jira.Entities;
using Greenshot.IniFile;
@ -38,7 +40,8 @@ namespace GreenshotJiraPlugin {
/// </summary>
public class JiraConnector : IDisposable {
private static readonly log4net.ILog Log = log4net.LogManager.GetLogger(typeof(JiraConnector));
private static readonly JiraConfiguration Config = IniConfig.GetIniSection<JiraConfiguration>();
private static readonly JiraConfiguration JiraConfig = IniConfig.GetIniSection<JiraConfiguration>();
private static readonly CoreConfiguration CoreConfig = IniConfig.GetIniSection<CoreConfiguration>();
// Used to remove the wsdl information from the old SOAP Uri
public const string DefaultPostfix = "/rpc/soap/jirasoapservice-v2?wsdl";
private DateTimeOffset _loggedInTime = DateTimeOffset.MinValue;
@ -48,6 +51,26 @@ namespace GreenshotJiraPlugin {
private string _url;
private JiraApi _jiraApi;
private IssueTypeBitmapCache _issueTypeBitmapCache;
private static readonly SvgBitmapHttpContentConverter SvgBitmapHttpContentConverterInstance = new SvgBitmapHttpContentConverter();
static JiraConnector()
{
if (HttpExtensionsGlobals.HttpContentConverters.All(x => x.GetType() != typeof(SvgBitmapHttpContentConverter)))
{
HttpExtensionsGlobals.HttpContentConverters.Add(SvgBitmapHttpContentConverterInstance);
}
SvgBitmapHttpContentConverterInstance.Width = CoreConfig.IconSize.Width;
SvgBitmapHttpContentConverterInstance.Height = CoreConfig.IconSize.Height;
CoreConfig.PropertyChanged += (sender, args) =>
{
if (args.PropertyName == nameof(CoreConfig.IconSize))
{
SvgBitmapHttpContentConverterInstance.Width = CoreConfig.IconSize.Width;
SvgBitmapHttpContentConverterInstance.Height = CoreConfig.IconSize.Height;
}
};
}
public void Dispose() {
if (_jiraApi != null)
@ -56,9 +79,10 @@ namespace GreenshotJiraPlugin {
}
}
public JiraConnector() {
_url = Config.Url.Replace(DefaultPostfix, "");
_timeout = Config.Timeout;
public JiraConnector()
{
_url = JiraConfig.Url.Replace(DefaultPostfix, "");
_timeout = JiraConfig.Timeout;
}
/// <summary>
@ -86,7 +110,7 @@ namespace GreenshotJiraPlugin {
{
loginInfo = await _jiraApi.StartSessionAsync(user, password);
// Worked, store the url in the configuration
Config.Url = _url;
JiraConfig.Url = _url;
IniConfig.Save();
}
catch (Exception)
@ -177,7 +201,14 @@ namespace GreenshotJiraPlugin {
public async Task<Issue> GetIssueAsync(string issueKey)
{
await CheckCredentials();
return await _jiraApi.GetIssueAsync(issueKey).ConfigureAwait(false);
try
{
return await _jiraApi.GetIssueAsync(issueKey).ConfigureAwait(false);
}
catch
{
return null;
}
}
/// <summary>
@ -185,7 +216,6 @@ namespace GreenshotJiraPlugin {
/// </summary>
/// <param name="issueKey"></param>
/// <param name="content">IBinaryContainer</param>
/// <param name="filename"></param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
public async Task AttachAsync(string issueKey, IBinaryContainer content, CancellationToken cancellationToken = default(CancellationToken))
@ -221,7 +251,7 @@ namespace GreenshotJiraPlugin {
public async Task<IList<Issue>> SearchAsync(Filter filter, CancellationToken cancellationToken = default(CancellationToken))
{
await CheckCredentials();
var searchResult = await _jiraApi.SearchAsync(filter.Jql, 20, new[] { "summary", "reporter", "assignee", "created" }, cancellationToken).ConfigureAwait(false);
var searchResult = await _jiraApi.SearchAsync(filter.Jql, 20, new[] { "summary", "reporter", "assignee", "created", "issuetype" }, cancellationToken).ConfigureAwait(false);
return searchResult.Issues;
}
@ -233,7 +263,7 @@ namespace GreenshotJiraPlugin {
/// <returns>Bitmap</returns>
public async Task<Bitmap> GetIssueTypeBitmapAsync(Issue issue, CancellationToken cancellationToken = default(CancellationToken))
{
return await _issueTypeBitmapCache.GetOrCreateAsync(issue, cancellationToken).ConfigureAwait(false);
return await _issueTypeBitmapCache.GetOrCreateAsync(issue.Fields.IssueType, cancellationToken).ConfigureAwait(false);
}
public Uri JiraBaseUri => _jiraApi.JiraBaseUri;

View file

@ -24,7 +24,6 @@ using Greenshot.IniFile;
using Greenshot.Plugin;
using System;
using System.Threading.Tasks;
using Dapplo.HttpExtensions.ContentConverter;
using Dapplo.Log.Facade;
using GreenshotJiraPlugin.Forms;

View file

@ -66,6 +66,11 @@ namespace GreenshotJiraPlugin {
catch (Exception ex)
{
Log.Error(ex);
// Remove issue from the last used jira config, as it caused an issue (probably not there)
if (Config.LastUsedJira == jiraKey)
{
Config.LastUsedJira = null;
}
}
}
if (jiraIssues.Count > 0) {

View file

@ -20,10 +20,11 @@
*/
namespace GreenshotJiraPlugin {
public enum LangKey {
public enum LangKey {
upload_menu_item,
column_assignee,
column_created,
column_issueType,
column_id,
column_reporter,
column_summary,
@ -41,5 +42,5 @@ namespace GreenshotJiraPlugin {
upload_success,
upload_failure,
communication_wait,
}
}
}

View file

@ -0,0 +1,129 @@
/*
* Greenshot - a free and open source screenshot tool
* Copyright (C) 2007-2016 Thomas Braun, Jens Klingen, Robin Krom
*
* For more information see: http://getgreenshot.org/
* The Greenshot project is hosted on GitHub https://github.com/greenshot/greenshot
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 1 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Drawing.Imaging;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
using Dapplo.HttpExtensions;
using Dapplo.HttpExtensions.ContentConverter;
using Dapplo.HttpExtensions.Extensions;
using Dapplo.HttpExtensions.Support;
using Dapplo.Log.Facade;
using System.Net.Http;
using System.Net.Http.Headers;
using GreenshotPlugin.Core;
using Svg;
namespace GreenshotJiraPlugin
{
/// <summary>
/// This adds SVG image support for the Jira Plugin
/// </summary>
public class SvgBitmapHttpContentConverter : IHttpContentConverter
{
private static readonly LogSource Log = new LogSource();
private static readonly IList<string> SupportedContentTypes = new List<string>();
static SvgBitmapHttpContentConverter()
{
SupportedContentTypes.Add(MediaTypes.Svg.EnumValueOf());
}
/// <inheritdoc />
public int Order => 0;
public int Width { get; set; }
public int Height { get; set; }
/// <summary>
/// This checks if the HttpContent can be converted to a Bitmap and is assignable to the specified Type
/// </summary>
/// <param name="typeToConvertTo">This should be something we can assign Bitmap to</param>
/// <param name="httpContent">HttpContent to process</param>
/// <returns>true if it can convert</returns>
public bool CanConvertFromHttpContent(Type typeToConvertTo, HttpContent httpContent)
{
if (typeToConvertTo == typeof(object) || !typeToConvertTo.IsAssignableFrom(typeof(Bitmap)))
{
return false;
}
var httpBehaviour = HttpBehaviour.Current;
return !httpBehaviour.ValidateResponseContentType || SupportedContentTypes.Contains(httpContent.GetContentType());
}
/// <inheritdoc />
public async Task<object> ConvertFromHttpContentAsync(Type resultType, HttpContent httpContent, CancellationToken cancellationToken = default(CancellationToken))
{
if (!CanConvertFromHttpContent(resultType, httpContent))
{
var exMessage = "CanConvertFromHttpContent resulted in false, ConvertFromHttpContentAsync is not supposed to be called.";
Log.Error().WriteLine(exMessage);
throw new NotSupportedException(exMessage);
}
using (var memoryStream = (MemoryStream) await StreamHttpContentConverter.Instance.ConvertFromHttpContentAsync(typeof(MemoryStream), httpContent, cancellationToken).ConfigureAwait(false))
{
Log.Debug().WriteLine("Creating a Bitmap from the SVG.");
var bitmap = ImageHelper.CreateEmpty(Width, Height, PixelFormat.Format32bppArgb, Color.Transparent, 96, 96);
var svgDoc = SvgDocument.Open<SvgDocument>(memoryStream);
svgDoc.Width = Width;
svgDoc.Height = Height;
svgDoc.Draw(bitmap);
return bitmap;
}
}
/// <inheritdoc />
public bool CanConvertToHttpContent(Type typeToConvert, object content)
{
return false;
}
/// <inheritdoc />
public HttpContent ConvertToHttpContent(Type typeToConvert, object content)
{
return null;
}
/// <inheritdoc />
public void AddAcceptHeadersForType(Type resultType, HttpRequestMessage httpRequestMessage)
{
if (resultType == null)
{
throw new ArgumentNullException(nameof(resultType));
}
if (httpRequestMessage == null)
{
throw new ArgumentNullException(nameof(httpRequestMessage));
}
if (resultType == typeof(object) || !resultType.IsAssignableFrom(typeof(Bitmap)))
{
return;
}
httpRequestMessage.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue(MediaTypes.Svg.EnumValueOf()));
Log.Debug().WriteLine("Modified the header(s) of the HttpRequestMessage: Accept: {0}", httpRequestMessage.Headers.Accept);
}
}
}

View file

@ -4,4 +4,5 @@
<package id="Dapplo.Jira" version="0.1.59" targetFramework="net45" />
<package id="Dapplo.Log.Facade" version="0.5.4" targetFramework="net45" />
<package id="LibZ.Tool" version="1.2.0.0" targetFramework="net45" />
<package id="Svg" version="2.2.1" targetFramework="net45" />
</packages>

View file

@ -532,6 +532,19 @@ namespace GreenshotPlugin.Core {
return resources.TryGetValue(prefix + "." + key, out languageString);
}
/// <summary>
/// TryGet method which combines hasKey & GetString
/// </summary>
/// <param name="prefix">string with prefix</param>
/// <param name="key">Enum with key</param>
/// <param name="languageString">out string</param>
/// <returns></returns>
public static bool TryGetString(string prefix, Enum key, out string languageString)
{
return resources.TryGetValue(prefix + "." + key, out languageString);
}
public static string Translate(object key) {
string typename = key.GetType().Name;
string enumKey = typename + "." + key;