Code formatting (indents etc.) to get it consistent, simplify it for contributors.

This commit is contained in:
Robin Krom 2021-03-28 19:24:26 +02:00
parent 726644de99
commit e8c0b307ee
No known key found for this signature in database
GPG key ID: BCC01364F1371490
435 changed files with 46647 additions and 39014 deletions

View file

@ -25,56 +25,55 @@ using Greenshot.Plugin.Box.Forms;
using GreenshotPlugin.Core; using GreenshotPlugin.Core;
using GreenshotPlugin.IniFile; using GreenshotPlugin.IniFile;
namespace Greenshot.Plugin.Box { namespace Greenshot.Plugin.Box
/// <summary> {
/// Description of ImgurConfiguration. /// <summary>
/// </summary> /// Description of ImgurConfiguration.
[IniSection("Box", Description = "Greenshot Box Plugin configuration")] /// </summary>
public class BoxConfiguration : IniSection { [IniSection("Box", Description = "Greenshot Box Plugin configuration")]
[IniProperty("UploadFormat", Description="What file type to use for uploading", DefaultValue="png")] public class BoxConfiguration : IniSection
public OutputFormat UploadFormat { get; set; } {
[IniProperty("UploadFormat", Description = "What file type to use for uploading", DefaultValue = "png")]
public OutputFormat UploadFormat { get; set; }
[IniProperty("UploadJpegQuality", Description="JPEG file save quality in %.", DefaultValue="80")] [IniProperty("UploadJpegQuality", Description = "JPEG file save quality in %.", DefaultValue = "80")]
public int UploadJpegQuality { get; set; } public int UploadJpegQuality { get; set; }
[IniProperty("AfterUploadLinkToClipBoard", Description = "After upload send Box link to clipboard.", DefaultValue = "true")] [IniProperty("AfterUploadLinkToClipBoard", Description = "After upload send Box link to clipboard.", DefaultValue = "true")]
public bool AfterUploadLinkToClipBoard { get; set; } public bool AfterUploadLinkToClipBoard { get; set; }
[IniProperty("UseSharedLink", Description = "Use the shared link, instead of the private, on the clipboard", DefaultValue = "True")] [IniProperty("UseSharedLink", Description = "Use the shared link, instead of the private, on the clipboard", DefaultValue = "True")]
public bool UseSharedLink { get; set; } public bool UseSharedLink { get; set; }
[IniProperty("FolderId", Description = "Folder ID to upload to, only change if you know what you are doing!", DefaultValue = "0")]
public string FolderId { get; set; }
[IniProperty("RefreshToken", Description = "Box authorization refresh Token", Encrypted = true)] [IniProperty("FolderId", Description = "Folder ID to upload to, only change if you know what you are doing!", DefaultValue = "0")]
public string RefreshToken { get; set; } public string FolderId { get; set; }
/// <summary> [IniProperty("RefreshToken", Description = "Box authorization refresh Token", Encrypted = true)]
/// Not stored public string RefreshToken { get; set; }
/// </summary>
public string AccessToken {
get;
set;
}
/// <summary> /// <summary>
/// Not stored /// Not stored
/// </summary> /// </summary>
public DateTimeOffset AccessTokenExpires { public string AccessToken { get; set; }
get;
set;
}
/// <summary> /// <summary>
/// A form for token /// Not stored
/// </summary> /// </summary>
/// <returns>bool true if OK was pressed, false if cancel</returns> public DateTimeOffset AccessTokenExpires { get; set; }
public bool ShowConfigDialog() {
DialogResult result = new SettingsForm().ShowDialog();
if (result == DialogResult.OK) {
return true;
}
return false;
}
} /// <summary>
/// A form for token
/// </summary>
/// <returns>bool true if OK was pressed, false if cancel</returns>
public bool ShowConfigDialog()
{
DialogResult result = new SettingsForm().ShowDialog();
if (result == DialogResult.OK)
{
return true;
}
return false;
}
}
} }

View file

@ -24,33 +24,42 @@ using System.Drawing;
using GreenshotPlugin.Core; using GreenshotPlugin.Core;
using GreenshotPlugin.Interfaces; using GreenshotPlugin.Interfaces;
namespace Greenshot.Plugin.Box { namespace Greenshot.Plugin.Box
public class BoxDestination : AbstractDestination { {
private readonly BoxPlugin _plugin; public class BoxDestination : AbstractDestination
public BoxDestination(BoxPlugin plugin) { {
_plugin = plugin; private readonly BoxPlugin _plugin;
}
public override string Designation => "Box"; public BoxDestination(BoxPlugin plugin)
{
_plugin = plugin;
}
public override string Description => Language.GetString("box", LangKey.upload_menu_item); public override string Designation => "Box";
public override Image DisplayIcon { public override string Description => Language.GetString("box", LangKey.upload_menu_item);
get {
ComponentResourceManager resources = new ComponentResourceManager(typeof(BoxPlugin));
return (Image)resources.GetObject("Box");
}
}
public override ExportInformation ExportCapture(bool manuallyInitiated, ISurface surface, ICaptureDetails captureDetails) { public override Image DisplayIcon
ExportInformation exportInformation = new ExportInformation(Designation, Description); {
string uploadUrl = _plugin.Upload(captureDetails, surface); get
if (uploadUrl != null) { {
exportInformation.ExportMade = true; ComponentResourceManager resources = new ComponentResourceManager(typeof(BoxPlugin));
exportInformation.Uri = uploadUrl; return (Image) resources.GetObject("Box");
} }
ProcessExport(exportInformation, surface); }
return exportInformation;
} public override ExportInformation ExportCapture(bool manuallyInitiated, ISurface surface, ICaptureDetails captureDetails)
} {
ExportInformation exportInformation = new ExportInformation(Designation, Description);
string uploadUrl = _plugin.Upload(captureDetails, surface);
if (uploadUrl != null)
{
exportInformation.ExportMade = true;
exportInformation.Uri = uploadUrl;
}
ProcessExport(exportInformation, surface);
return exportInformation;
}
}
} }

View file

@ -22,39 +22,35 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.Runtime.Serialization; using System.Runtime.Serialization;
namespace Greenshot.Plugin.Box { namespace Greenshot.Plugin.Box
[DataContract] {
public class Authorization { [DataContract]
[DataMember(Name = "access_token")] public class Authorization
public string AccessToken { get; set; } {
[DataMember(Name = "expires_in")] [DataMember(Name = "access_token")] public string AccessToken { get; set; }
public int ExpiresIn { get; set; } [DataMember(Name = "expires_in")] public int ExpiresIn { get; set; }
[DataMember(Name = "refresh_token")] [DataMember(Name = "refresh_token")] public string RefreshToken { get; set; }
public string RefreshToken { get; set; } [DataMember(Name = "token_type")] public string TokenType { get; set; }
[DataMember(Name = "token_type")] }
public string TokenType { get; set; }
}
[DataContract]
public class SharedLink {
[DataMember(Name = "url")]
public string Url { get; set; }
[DataMember(Name = "download_url")]
public string DownloadUrl { get; set; }
}
[DataContract] [DataContract]
public class FileEntry { public class SharedLink
[DataMember(Name = "id")] {
public string Id { get; set; } [DataMember(Name = "url")] public string Url { get; set; }
[DataMember(Name = "name")] [DataMember(Name = "download_url")] public string DownloadUrl { get; set; }
public string Name { get; set; } }
[DataMember(Name = "shared_link")]
public SharedLink SharedLink { get; set; }
}
[DataContract] [DataContract]
public class Upload { public class FileEntry
[DataMember(Name = "entries")] {
public List<FileEntry> Entries { get; set; } [DataMember(Name = "id")] public string Id { get; set; }
} [DataMember(Name = "name")] public string Name { get; set; }
[DataMember(Name = "shared_link")] public SharedLink SharedLink { get; set; }
}
[DataContract]
public class Upload
{
[DataMember(Name = "entries")] public List<FileEntry> Entries { get; set; }
}
} }

View file

@ -30,99 +30,111 @@ using GreenshotPlugin.IniFile;
using GreenshotPlugin.Interfaces; using GreenshotPlugin.Interfaces;
using GreenshotPlugin.Interfaces.Plugin; using GreenshotPlugin.Interfaces.Plugin;
namespace Greenshot.Plugin.Box { namespace Greenshot.Plugin.Box
/// <summary> {
/// This is the Box base code /// <summary>
/// </summary> /// This is the Box base code
/// </summary>
[Plugin("Box", true)] [Plugin("Box", true)]
public class BoxPlugin : IGreenshotPlugin { public class BoxPlugin : IGreenshotPlugin
private static readonly log4net.ILog LOG = log4net.LogManager.GetLogger(typeof(BoxPlugin)); {
private static BoxConfiguration _config; private static readonly log4net.ILog LOG = log4net.LogManager.GetLogger(typeof(BoxPlugin));
private ComponentResourceManager _resources; private static BoxConfiguration _config;
private ToolStripMenuItem _itemPlugInConfig; private ComponentResourceManager _resources;
private ToolStripMenuItem _itemPlugInConfig;
public void Dispose() { public void Dispose()
Dispose(true); {
GC.SuppressFinalize(this); Dispose(true);
} GC.SuppressFinalize(this);
}
protected void Dispose(bool disposing) protected void Dispose(bool disposing)
{ {
if (!disposing) return; if (!disposing) return;
if (_itemPlugInConfig == null) return; if (_itemPlugInConfig == null) return;
_itemPlugInConfig.Dispose(); _itemPlugInConfig.Dispose();
_itemPlugInConfig = null; _itemPlugInConfig = null;
} }
/// <summary> /// <summary>
/// Implementation of the IGreenshotPlugin.Initialize /// Implementation of the IGreenshotPlugin.Initialize
/// </summary> /// </summary>
public bool Initialize() { public bool Initialize()
{
// Register configuration (don't need the configuration itself) // Register configuration (don't need the configuration itself)
_config = IniConfig.GetIniSection<BoxConfiguration>(); _config = IniConfig.GetIniSection<BoxConfiguration>();
_resources = new ComponentResourceManager(typeof(BoxPlugin)); _resources = new ComponentResourceManager(typeof(BoxPlugin));
SimpleServiceProvider.Current.AddService<IDestination>(new BoxDestination(this)); SimpleServiceProvider.Current.AddService<IDestination>(new BoxDestination(this));
_itemPlugInConfig = new ToolStripMenuItem { _itemPlugInConfig = new ToolStripMenuItem
Image = (Image) _resources.GetObject("Box"), {
Text = Language.GetString("box", LangKey.Configure) Image = (Image) _resources.GetObject("Box"),
}; Text = Language.GetString("box", LangKey.Configure)
_itemPlugInConfig.Click += ConfigMenuClick; };
_itemPlugInConfig.Click += ConfigMenuClick;
PluginUtils.AddToContextMenu(_itemPlugInConfig); PluginUtils.AddToContextMenu(_itemPlugInConfig);
Language.LanguageChanged += OnLanguageChanged; Language.LanguageChanged += OnLanguageChanged;
return true; return true;
} }
public void OnLanguageChanged(object sender, EventArgs e) { public void OnLanguageChanged(object sender, EventArgs e)
if (_itemPlugInConfig != null) { {
_itemPlugInConfig.Text = Language.GetString("box", LangKey.Configure); if (_itemPlugInConfig != null)
} {
} _itemPlugInConfig.Text = Language.GetString("box", LangKey.Configure);
}
}
public void Shutdown() { public void Shutdown()
LOG.Debug("Box Plugin shutdown."); {
} LOG.Debug("Box Plugin shutdown.");
}
/// <summary> /// <summary>
/// Implementation of the IPlugin.Configure /// Implementation of the IPlugin.Configure
/// </summary> /// </summary>
public void Configure() { public void Configure()
_config.ShowConfigDialog(); {
} _config.ShowConfigDialog();
}
public void ConfigMenuClick(object sender, EventArgs eventArgs) { public void ConfigMenuClick(object sender, EventArgs eventArgs)
_config.ShowConfigDialog(); {
} _config.ShowConfigDialog();
}
/// <summary> /// <summary>
/// This will be called when the menu item in the Editor is clicked /// This will be called when the menu item in the Editor is clicked
/// </summary> /// </summary>
public string Upload(ICaptureDetails captureDetails, ISurface surfaceToUpload) { public string Upload(ICaptureDetails captureDetails, ISurface surfaceToUpload)
SurfaceOutputSettings outputSettings = new SurfaceOutputSettings(_config.UploadFormat, _config.UploadJpegQuality, false); {
try { SurfaceOutputSettings outputSettings = new SurfaceOutputSettings(_config.UploadFormat, _config.UploadJpegQuality, false);
string url = null; try
string filename = Path.GetFileName(FilenameHelper.GetFilename(_config.UploadFormat, captureDetails)); {
SurfaceContainer imageToUpload = new SurfaceContainer(surfaceToUpload, outputSettings, filename); string url = null;
string filename = Path.GetFileName(FilenameHelper.GetFilename(_config.UploadFormat, captureDetails));
SurfaceContainer imageToUpload = new SurfaceContainer(surfaceToUpload, outputSettings, filename);
new PleaseWaitForm().ShowAndWait("Box", Language.GetString("box", LangKey.communication_wait), new PleaseWaitForm().ShowAndWait("Box", Language.GetString("box", LangKey.communication_wait),
delegate { delegate { url = BoxUtils.UploadToBox(imageToUpload, captureDetails.Title, filename); }
url = BoxUtils.UploadToBox(imageToUpload, captureDetails.Title, filename); );
}
);
if (url != null && _config.AfterUploadLinkToClipBoard) { if (url != null && _config.AfterUploadLinkToClipBoard)
ClipboardHelper.SetClipboardData(url); {
} ClipboardHelper.SetClipboardData(url);
}
return url; return url;
} catch (Exception ex) { }
LOG.Error("Error uploading.", ex); catch (Exception ex)
MessageBox.Show(Language.GetString("box", LangKey.upload_failure) + " " + ex.Message); {
return null; LOG.Error("Error uploading.", ex);
} MessageBox.Show(Language.GetString("box", LangKey.upload_failure) + " " + ex.Message);
} return null;
} }
}
}
} }

View file

@ -27,112 +27,128 @@ using GreenshotPlugin.Core;
using GreenshotPlugin.Core.OAuth; using GreenshotPlugin.Core.OAuth;
using GreenshotPlugin.IniFile; using GreenshotPlugin.IniFile;
namespace Greenshot.Plugin.Box { namespace Greenshot.Plugin.Box
{
/// <summary> /// <summary>
/// Description of BoxUtils. /// Description of BoxUtils.
/// </summary> /// </summary>
public static class BoxUtils { public static class BoxUtils
private static readonly log4net.ILog Log = log4net.LogManager.GetLogger(typeof(BoxUtils)); {
private static readonly BoxConfiguration Config = IniConfig.GetIniSection<BoxConfiguration>(); private static readonly log4net.ILog Log = log4net.LogManager.GetLogger(typeof(BoxUtils));
private const string UploadFileUri = "https://upload.box.com/api/2.0/files/content"; private static readonly BoxConfiguration Config = IniConfig.GetIniSection<BoxConfiguration>();
private const string FilesUri = "https://www.box.com/api/2.0/files/{0}"; private const string UploadFileUri = "https://upload.box.com/api/2.0/files/content";
private const string FilesUri = "https://www.box.com/api/2.0/files/{0}";
/// <summary>
/// Put string
/// </summary>
/// <param name="url"></param>
/// <param name="content"></param>
/// <param name="settings">OAuth2Settings</param>
/// <returns>response</returns>
public static string HttpPut(string url, string content, OAuth2Settings settings) {
var webRequest= OAuth2Helper.CreateOAuth2WebRequest(HTTPMethod.PUT, url, settings);
byte[] data = Encoding.UTF8.GetBytes(content);
using (var requestStream = webRequest.GetRequestStream()) {
requestStream.Write(data, 0, data.Length);
}
return NetworkHelper.GetResponseAsString(webRequest);
}
/// <summary>
/// Do the actual upload to Box
/// For more details on the available parameters, see: http://developers.box.net/w/page/12923951/ApiFunction_Upload%20and%20Download
/// </summary>
/// <param name="image">Image for box upload</param>
/// <param name="title">Title of box upload</param>
/// <param name="filename">Filename of box upload</param>
/// <returns>url to uploaded image</returns>
public static string UploadToBox(SurfaceContainer image, string title, string filename) {
// Fill the OAuth2Settings
var settings = new OAuth2Settings
{
AuthUrlPattern = "https://app.box.com/api/oauth2/authorize?client_id={ClientId}&response_type=code&state={State}&redirect_uri={RedirectUrl}",
TokenUrl = "https://api.box.com/oauth2/token",
CloudServiceName = "Box",
ClientId = BoxCredentials.ClientId,
ClientSecret = BoxCredentials.ClientSecret,
RedirectUrl = "https://getgreenshot.org/authorize/box",
AuthorizeMode = OAuth2AuthorizeMode.JsonReceiver,
RefreshToken = Config.RefreshToken,
AccessToken = Config.AccessToken,
AccessTokenExpires = Config.AccessTokenExpires
};
// Copy the settings from the config, which is kept in memory and on the disk
try {
var webRequest = OAuth2Helper.CreateOAuth2WebRequest(HTTPMethod.POST, UploadFileUri, settings);
IDictionary<string, object> parameters = new Dictionary<string, object>
{
{ "file", image },
{ "parent_id", Config.FolderId }
};
NetworkHelper.WriteMultipartFormData(webRequest, parameters);
var response = NetworkHelper.GetResponseAsString(webRequest);
Log.DebugFormat("Box response: {0}", response);
var upload = JsonSerializer.Deserialize<Upload>(response);
if (upload?.Entries == null || upload.Entries.Count == 0) return null;
if (Config.UseSharedLink) {
string filesResponse = HttpPut(string.Format(FilesUri, upload.Entries[0].Id), "{\"shared_link\": {\"access\": \"open\"}}", settings);
var file = JsonSerializer.Deserialize<FileEntry>(filesResponse);
return file.SharedLink.Url;
}
return $"http://www.box.com/files/0/f/0/1/f_{upload.Entries[0].Id}";
} finally {
// Copy the settings back to the config, so they are stored.
Config.RefreshToken = settings.RefreshToken;
Config.AccessToken = settings.AccessToken;
Config.AccessTokenExpires = settings.AccessTokenExpires;
Config.IsDirty = true;
IniConfig.Save();
}
}
}
/// <summary>
/// A simple helper class for the DataContractJsonSerializer
/// </summary>
internal static class JsonSerializer {
/// <summary> /// <summary>
/// Helper method to parse JSON to object /// Put string
/// </summary> /// </summary>
/// <typeparam name="T"></typeparam> /// <param name="url"></param>
/// <param name="jsonString"></param> /// <param name="content"></param>
/// <returns></returns> /// <param name="settings">OAuth2Settings</param>
public static T Deserialize<T>(string jsonString) { /// <returns>response</returns>
var deserializer = new DataContractJsonSerializer(typeof(T)); public static string HttpPut(string url, string content, OAuth2Settings settings)
{
var webRequest = OAuth2Helper.CreateOAuth2WebRequest(HTTPMethod.PUT, url, settings);
byte[] data = Encoding.UTF8.GetBytes(content);
using (var requestStream = webRequest.GetRequestStream())
{
requestStream.Write(data, 0, data.Length);
}
return NetworkHelper.GetResponseAsString(webRequest);
}
/// <summary>
/// Do the actual upload to Box
/// For more details on the available parameters, see: http://developers.box.net/w/page/12923951/ApiFunction_Upload%20and%20Download
/// </summary>
/// <param name="image">Image for box upload</param>
/// <param name="title">Title of box upload</param>
/// <param name="filename">Filename of box upload</param>
/// <returns>url to uploaded image</returns>
public static string UploadToBox(SurfaceContainer image, string title, string filename)
{
// Fill the OAuth2Settings
var settings = new OAuth2Settings
{
AuthUrlPattern = "https://app.box.com/api/oauth2/authorize?client_id={ClientId}&response_type=code&state={State}&redirect_uri={RedirectUrl}",
TokenUrl = "https://api.box.com/oauth2/token",
CloudServiceName = "Box",
ClientId = BoxCredentials.ClientId,
ClientSecret = BoxCredentials.ClientSecret,
RedirectUrl = "https://getgreenshot.org/authorize/box",
AuthorizeMode = OAuth2AuthorizeMode.JsonReceiver,
RefreshToken = Config.RefreshToken,
AccessToken = Config.AccessToken,
AccessTokenExpires = Config.AccessTokenExpires
};
// Copy the settings from the config, which is kept in memory and on the disk
try
{
var webRequest = OAuth2Helper.CreateOAuth2WebRequest(HTTPMethod.POST, UploadFileUri, settings);
IDictionary<string, object> parameters = new Dictionary<string, object>
{
{
"file", image
},
{
"parent_id", Config.FolderId
}
};
NetworkHelper.WriteMultipartFormData(webRequest, parameters);
var response = NetworkHelper.GetResponseAsString(webRequest);
Log.DebugFormat("Box response: {0}", response);
var upload = JsonSerializer.Deserialize<Upload>(response);
if (upload?.Entries == null || upload.Entries.Count == 0) return null;
if (Config.UseSharedLink)
{
string filesResponse = HttpPut(string.Format(FilesUri, upload.Entries[0].Id), "{\"shared_link\": {\"access\": \"open\"}}", settings);
var file = JsonSerializer.Deserialize<FileEntry>(filesResponse);
return file.SharedLink.Url;
}
return $"http://www.box.com/files/0/f/0/1/f_{upload.Entries[0].Id}";
}
finally
{
// Copy the settings back to the config, so they are stored.
Config.RefreshToken = settings.RefreshToken;
Config.AccessToken = settings.AccessToken;
Config.AccessTokenExpires = settings.AccessTokenExpires;
Config.IsDirty = true;
IniConfig.Save();
}
}
}
/// <summary>
/// A simple helper class for the DataContractJsonSerializer
/// </summary>
internal static class JsonSerializer
{
/// <summary>
/// Helper method to parse JSON to object
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="jsonString"></param>
/// <returns></returns>
public static T Deserialize<T>(string jsonString)
{
var deserializer = new DataContractJsonSerializer(typeof(T));
using var stream = new MemoryStream(); using var stream = new MemoryStream();
byte[] content = Encoding.UTF8.GetBytes(jsonString); byte[] content = Encoding.UTF8.GetBytes(jsonString);
stream.Write(content, 0, content.Length); stream.Write(content, 0, content.Length);
stream.Seek(0, SeekOrigin.Begin); stream.Seek(0, SeekOrigin.Begin);
return (T)deserializer.ReadObject(stream); return (T) deserializer.ReadObject(stream);
} }
} }
} }

View file

@ -21,7 +21,9 @@
using GreenshotPlugin.Controls; using GreenshotPlugin.Controls;
namespace Greenshot.Plugin.Box.Forms { namespace Greenshot.Plugin.Box.Forms
public class BoxForm : GreenshotForm { {
} public class BoxForm : GreenshotForm
{
}
} }

View file

@ -19,18 +19,21 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>. * along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
namespace Greenshot.Plugin.Box.Forms { namespace Greenshot.Plugin.Box.Forms
/// <summary> {
/// Description of PasswordRequestForm. /// <summary>
/// </summary> /// Description of PasswordRequestForm.
public partial class SettingsForm : BoxForm { /// </summary>
public SettingsForm() { public partial class SettingsForm : BoxForm
// {
// The InitializeComponent() call is required for Windows Forms designer support. public SettingsForm()
// {
InitializeComponent(); //
AcceptButton = buttonOK; // The InitializeComponent() call is required for Windows Forms designer support.
CancelButton = buttonCancel; //
} InitializeComponent();
} AcceptButton = buttonOK;
CancelButton = buttonCancel;
}
}
} }

View file

@ -18,11 +18,14 @@
* You should have received a copy of the GNU General Public License * You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>. * along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
namespace Greenshot.Plugin.Box {
public enum LangKey { namespace Greenshot.Plugin.Box
upload_menu_item, {
upload_failure, public enum LangKey
communication_wait, {
Configure upload_menu_item,
} upload_failure,
communication_wait,
Configure
}
} }

View file

@ -26,283 +26,333 @@ using GreenshotConfluencePlugin.confluence;
using GreenshotPlugin.Core; using GreenshotPlugin.Core;
using GreenshotPlugin.IniFile; using GreenshotPlugin.IniFile;
namespace Greenshot.Plugin.Confluence { namespace Greenshot.Plugin.Confluence
public class Page { {
public Page(RemotePage page) { public class Page
Id = page.id; {
Title = page.title; public Page(RemotePage page)
SpaceKey = page.space; {
Url = page.url; Id = page.id;
Content = page.content; Title = page.title;
} SpaceKey = page.space;
public Page(RemoteSearchResult searchResult, string space) { Url = page.url;
Id = searchResult.id; Content = page.content;
Title = searchResult.title; }
SpaceKey = space;
Url = searchResult.url;
Content = searchResult.excerpt;
}
public Page(RemotePageSummary pageSummary) { public Page(RemoteSearchResult searchResult, string space)
Id = pageSummary.id; {
Title = pageSummary.title; Id = searchResult.id;
SpaceKey = pageSummary.space; Title = searchResult.title;
Url =pageSummary.url; SpaceKey = space;
} Url = searchResult.url;
public long Id { Content = searchResult.excerpt;
get; }
set;
} public Page(RemotePageSummary pageSummary)
public string Title { {
get; Id = pageSummary.id;
set; Title = pageSummary.title;
} SpaceKey = pageSummary.space;
public string Url { Url = pageSummary.url;
get; }
set;
} public long Id { get; set; }
public string Content { public string Title { get; set; }
get; public string Url { get; set; }
set; public string Content { get; set; }
} public string SpaceKey { get; set; }
public string SpaceKey { }
get;
set; public class Space
} {
} public Space(RemoteSpaceSummary space)
public class Space { {
public Space(RemoteSpaceSummary space) { Key = space.key;
Key = space.key; Name = space.name;
Name = space.name; }
}
public string Key { public string Key { get; set; }
get; public string Name { get; set; }
set; }
}
public string Name {
get;
set;
}
}
/// <summary> /// <summary>
/// For details see the Confluence API site /// For details see the Confluence API site
/// See: http://confluence.atlassian.com/display/CONFDEV/Remote+API+Specification /// See: http://confluence.atlassian.com/display/CONFDEV/Remote+API+Specification
/// </summary> /// </summary>
public class ConfluenceConnector : IDisposable { public class ConfluenceConnector : IDisposable
private static readonly log4net.ILog Log = log4net.LogManager.GetLogger(typeof(ConfluenceConnector)); {
private const string AuthFailedExceptionName = "com.atlassian.confluence.rpc.AuthenticationFailedException"; private static readonly log4net.ILog Log = log4net.LogManager.GetLogger(typeof(ConfluenceConnector));
private const string V2Failed = "AXIS"; private const string AuthFailedExceptionName = "com.atlassian.confluence.rpc.AuthenticationFailedException";
private static readonly ConfluenceConfiguration Config = IniConfig.GetIniSection<ConfluenceConfiguration>(); private const string V2Failed = "AXIS";
private string _credentials; private static readonly ConfluenceConfiguration Config = IniConfig.GetIniSection<ConfluenceConfiguration>();
private DateTime _loggedInTime = DateTime.Now; private string _credentials;
private bool _loggedIn; private DateTime _loggedInTime = DateTime.Now;
private ConfluenceSoapServiceService _confluence; private bool _loggedIn;
private readonly int _timeout; private ConfluenceSoapServiceService _confluence;
private string _url; private readonly int _timeout;
private readonly Cache<string, RemotePage> _pageCache = new Cache<string, RemotePage>(60 * Config.Timeout); private string _url;
private readonly Cache<string, RemotePage> _pageCache = new Cache<string, RemotePage>(60 * Config.Timeout);
public void Dispose() { public void Dispose()
Dispose(true); {
GC.SuppressFinalize(this); Dispose(true);
} GC.SuppressFinalize(this);
}
protected void Dispose(bool disposing) { protected void Dispose(bool disposing)
if (_confluence != null) { {
Logout(); if (_confluence != null)
} {
if (disposing) { Logout();
if (_confluence != null) { }
_confluence.Dispose();
_confluence = null;
}
}
}
public ConfluenceConnector(string url, int timeout) { if (disposing)
_timeout = timeout; {
Init(url); if (_confluence != null)
} {
_confluence.Dispose();
_confluence = null;
}
}
}
private void Init(string url) { public ConfluenceConnector(string url, int timeout)
_url = url; {
_confluence = new ConfluenceSoapServiceService _timeout = timeout;
{ Init(url);
Url = url, }
Proxy = NetworkHelper.CreateProxy(new Uri(url))
};
}
~ConfluenceConnector() { private void Init(string url)
Dispose(false); {
} _url = url;
_confluence = new ConfluenceSoapServiceService
{
Url = url,
Proxy = NetworkHelper.CreateProxy(new Uri(url))
};
}
/// <summary> ~ConfluenceConnector()
/// Internal login which catches the exceptions {
/// </summary> Dispose(false);
/// <returns>true if login was done sucessfully</returns> }
private bool DoLogin(string user, string password) {
try {
_credentials = _confluence.login(user, password);
_loggedInTime = DateTime.Now;
_loggedIn = true;
} catch (Exception e) {
// Check if confluence-v2 caused an error, use v1 instead
if (e.Message.Contains(V2Failed) && _url.Contains("v2")) {
Init(_url.Replace("v2", "v1"));
return DoLogin(user, password);
}
// check if auth failed
if (e.Message.Contains(AuthFailedExceptionName)) {
return false;
}
// Not an authentication issue
_loggedIn = false;
_credentials = null;
e.Data.Add("user", user);
e.Data.Add("url", _url);
throw;
}
return true;
}
public void Login() { /// <summary>
Logout(); /// Internal login which catches the exceptions
try { /// </summary>
// Get the system name, so the user knows where to login to /// <returns>true if login was done sucessfully</returns>
string systemName = _url.Replace(ConfluenceConfiguration.DEFAULT_POSTFIX1,""); private bool DoLogin(string user, string password)
systemName = systemName.Replace(ConfluenceConfiguration.DEFAULT_POSTFIX2, ""); {
CredentialsDialog dialog = new CredentialsDialog(systemName) try
{ {
Name = null _credentials = _confluence.login(user, password);
}; _loggedInTime = DateTime.Now;
while (dialog.Show(dialog.Name) == DialogResult.OK) { _loggedIn = true;
if (DoLogin(dialog.Name, dialog.Password)) { }
if (dialog.SaveChecked) { catch (Exception e)
dialog.Confirm(true); {
} // Check if confluence-v2 caused an error, use v1 instead
return; if (e.Message.Contains(V2Failed) && _url.Contains("v2"))
} else { {
try { Init(_url.Replace("v2", "v1"));
dialog.Confirm(false); return DoLogin(user, password);
} catch (ApplicationException e) { }
// exception handling ...
Log.Error("Problem using the credentials dialog", e);
}
// For every windows version after XP show an incorrect password baloon
dialog.IncorrectPassword = true;
// Make sure the dialog is display, the password was false!
dialog.AlwaysDisplay = true;
}
}
} catch (ApplicationException e) {
// exception handling ...
Log.Error("Problem using the credentials dialog", e);
}
}
public void Logout() { // check if auth failed
if (_credentials != null) { if (e.Message.Contains(AuthFailedExceptionName))
_confluence.logout(_credentials); {
_credentials = null; return false;
_loggedIn = false; }
}
}
private void CheckCredentials() { // Not an authentication issue
if (_loggedIn) { _loggedIn = false;
if (_loggedInTime.AddMinutes(_timeout-1).CompareTo(DateTime.Now) < 0) { _credentials = null;
Logout(); e.Data.Add("user", user);
Login(); e.Data.Add("url", _url);
} throw;
} else { }
Login();
}
}
public bool IsLoggedIn => _loggedIn; return true;
}
public void AddAttachment(long pageId, string mime, string comment, string filename, IBinaryContainer image) { public void Login()
CheckCredentials(); {
// Comment is ignored, see: http://jira.atlassian.com/browse/CONF-9395 Logout();
var attachment = new RemoteAttachment try
{ {
comment = comment, // Get the system name, so the user knows where to login to
fileName = filename, string systemName = _url.Replace(ConfluenceConfiguration.DEFAULT_POSTFIX1, "");
contentType = mime systemName = systemName.Replace(ConfluenceConfiguration.DEFAULT_POSTFIX2, "");
}; CredentialsDialog dialog = new CredentialsDialog(systemName)
_confluence.addAttachment(_credentials, pageId, attachment, image.ToByteArray()); {
} Name = null
};
while (dialog.Show(dialog.Name) == DialogResult.OK)
{
if (DoLogin(dialog.Name, dialog.Password))
{
if (dialog.SaveChecked)
{
dialog.Confirm(true);
}
public Page GetPage(string spaceKey, string pageTitle) { return;
RemotePage page = null; }
string cacheKey = spaceKey + pageTitle; else
if (_pageCache.Contains(cacheKey)) { {
page = _pageCache[cacheKey]; try
} {
if (page == null) { dialog.Confirm(false);
CheckCredentials(); }
page = _confluence.getPage(_credentials, spaceKey, pageTitle); catch (ApplicationException e)
_pageCache.Add(cacheKey, page); {
} // exception handling ...
return new Page(page); Log.Error("Problem using the credentials dialog", e);
} }
public Page GetPage(long pageId) { // For every windows version after XP show an incorrect password baloon
RemotePage page = null; dialog.IncorrectPassword = true;
string cacheKey = pageId.ToString(); // Make sure the dialog is display, the password was false!
dialog.AlwaysDisplay = true;
}
}
}
catch (ApplicationException e)
{
// exception handling ...
Log.Error("Problem using the credentials dialog", e);
}
}
if (_pageCache.Contains(cacheKey)) { public void Logout()
page = _pageCache[cacheKey]; {
} if (_credentials != null)
if (page == null) { {
CheckCredentials(); _confluence.logout(_credentials);
page = _confluence.getPage(_credentials, pageId); _credentials = null;
_pageCache.Add(cacheKey, page); _loggedIn = false;
} }
return new Page(page); }
}
public Page GetSpaceHomepage(Space spaceSummary) { private void CheckCredentials()
CheckCredentials(); {
RemoteSpace spaceDetail = _confluence.getSpace(_credentials, spaceSummary.Key); if (_loggedIn)
RemotePage page = _confluence.getPage(_credentials, spaceDetail.homePage); {
return new Page(page); if (_loggedInTime.AddMinutes(_timeout - 1).CompareTo(DateTime.Now) < 0)
} {
Logout();
Login();
}
}
else
{
Login();
}
}
public IEnumerable<Space> GetSpaceSummaries() { public bool IsLoggedIn => _loggedIn;
CheckCredentials();
RemoteSpaceSummary [] spaces = _confluence.getSpaces(_credentials);
foreach(RemoteSpaceSummary space in spaces) {
yield return new Space(space);
}
}
public IEnumerable<Page> GetPageChildren(Page parentPage) { public void AddAttachment(long pageId, string mime, string comment, string filename, IBinaryContainer image)
CheckCredentials(); {
RemotePageSummary[] pages = _confluence.getChildren(_credentials, parentPage.Id); CheckCredentials();
foreach(RemotePageSummary page in pages) { // Comment is ignored, see: http://jira.atlassian.com/browse/CONF-9395
yield return new Page(page); var attachment = new RemoteAttachment
} {
} comment = comment,
fileName = filename,
contentType = mime
};
_confluence.addAttachment(_credentials, pageId, attachment, image.ToByteArray());
}
public IEnumerable<Page> GetPageSummaries(Space space) { public Page GetPage(string spaceKey, string pageTitle)
CheckCredentials(); {
RemotePageSummary[] pages = _confluence.getPages(_credentials, space.Key); RemotePage page = null;
foreach(RemotePageSummary page in pages) { string cacheKey = spaceKey + pageTitle;
yield return new Page(page); if (_pageCache.Contains(cacheKey))
} {
} page = _pageCache[cacheKey];
}
public IEnumerable<Page> SearchPages(string query, string space) { if (page == null)
CheckCredentials(); {
foreach(var searchResult in _confluence.search(_credentials, query, 20)) { CheckCredentials();
Log.DebugFormat("Got result of type {0}", searchResult.type); page = _confluence.getPage(_credentials, spaceKey, pageTitle);
if ("page".Equals(searchResult.type)) _pageCache.Add(cacheKey, page);
{ }
yield return new Page(searchResult, space);
} return new Page(page);
} }
}
} public Page GetPage(long pageId)
{
RemotePage page = null;
string cacheKey = pageId.ToString();
if (_pageCache.Contains(cacheKey))
{
page = _pageCache[cacheKey];
}
if (page == null)
{
CheckCredentials();
page = _confluence.getPage(_credentials, pageId);
_pageCache.Add(cacheKey, page);
}
return new Page(page);
}
public Page GetSpaceHomepage(Space spaceSummary)
{
CheckCredentials();
RemoteSpace spaceDetail = _confluence.getSpace(_credentials, spaceSummary.Key);
RemotePage page = _confluence.getPage(_credentials, spaceDetail.homePage);
return new Page(page);
}
public IEnumerable<Space> GetSpaceSummaries()
{
CheckCredentials();
RemoteSpaceSummary[] spaces = _confluence.getSpaces(_credentials);
foreach (RemoteSpaceSummary space in spaces)
{
yield return new Space(space);
}
}
public IEnumerable<Page> GetPageChildren(Page parentPage)
{
CheckCredentials();
RemotePageSummary[] pages = _confluence.getChildren(_credentials, parentPage.Id);
foreach (RemotePageSummary page in pages)
{
yield return new Page(page);
}
}
public IEnumerable<Page> GetPageSummaries(Space space)
{
CheckCredentials();
RemotePageSummary[] pages = _confluence.getPages(_credentials, space.Key);
foreach (RemotePageSummary page in pages)
{
yield return new Page(page);
}
}
public IEnumerable<Page> SearchPages(string query, string space)
{
CheckCredentials();
foreach (var searchResult in _confluence.search(_credentials, query, 20))
{
Log.DebugFormat("Got result of type {0}", searchResult.type);
if ("page".Equals(searchResult.type))
{
yield return new Page(searchResult, space);
}
}
}
}
} }

View file

@ -23,63 +23,45 @@ using System;
using GreenshotPlugin.Core; using GreenshotPlugin.Core;
using GreenshotPlugin.IniFile; using GreenshotPlugin.IniFile;
namespace Greenshot.Plugin.Confluence { namespace Greenshot.Plugin.Confluence
/// <summary> {
/// Description of ConfluenceConfiguration. /// <summary>
/// </summary> /// Description of ConfluenceConfiguration.
[Serializable] /// </summary>
[IniSection("Confluence", Description="Greenshot Confluence Plugin configuration")] [Serializable]
public class ConfluenceConfiguration : IniSection { [IniSection("Confluence", Description = "Greenshot Confluence Plugin configuration")]
public const string DEFAULT_POSTFIX1 = "/rpc/soap-axis/confluenceservice-v1?wsdl"; public class ConfluenceConfiguration : IniSection
public const string DEFAULT_POSTFIX2 = "/rpc/soap-axis/confluenceservice-v2?wsdl"; {
public const string DEFAULT_PREFIX = "http://"; public const string DEFAULT_POSTFIX1 = "/rpc/soap-axis/confluenceservice-v1?wsdl";
private const string DEFAULT_URL = DEFAULT_PREFIX + "confluence"; public const string DEFAULT_POSTFIX2 = "/rpc/soap-axis/confluenceservice-v2?wsdl";
public const string DEFAULT_PREFIX = "http://";
private const string DEFAULT_URL = DEFAULT_PREFIX + "confluence";
[IniProperty("Url", Description="Url to Confluence system, including wsdl.", DefaultValue=DEFAULT_URL)] [IniProperty("Url", Description = "Url to Confluence system, including wsdl.", DefaultValue = DEFAULT_URL)]
public string Url { public string Url { get; set; }
get;
set;
}
[IniProperty("Timeout", Description="Session timeout in minutes", DefaultValue="30")]
public int Timeout {
get;
set;
}
[IniProperty("UploadFormat", Description="What file type to use for uploading", DefaultValue="png")] [IniProperty("Timeout", Description = "Session timeout in minutes", DefaultValue = "30")]
public OutputFormat UploadFormat { public int Timeout { get; set; }
get;
set; [IniProperty("UploadFormat", Description = "What file type to use for uploading", DefaultValue = "png")]
} public OutputFormat UploadFormat { get; set; }
[IniProperty("UploadJpegQuality", Description="JPEG file save quality in %.", DefaultValue="80")]
public int UploadJpegQuality { [IniProperty("UploadJpegQuality", Description = "JPEG file save quality in %.", DefaultValue = "80")]
get; public int UploadJpegQuality { get; set; }
set;
} [IniProperty("UploadReduceColors", Description = "Reduce color amount of the uploaded image to 256", DefaultValue = "False")]
[IniProperty("UploadReduceColors", Description="Reduce color amount of the uploaded image to 256", DefaultValue="False")] public bool UploadReduceColors { get; set; }
public bool UploadReduceColors {
get; [IniProperty("OpenPageAfterUpload", Description = "Open the page where the picture is uploaded after upload", DefaultValue = "True")]
set; public bool OpenPageAfterUpload { get; set; }
}
[IniProperty("OpenPageAfterUpload", Description="Open the page where the picture is uploaded after upload", DefaultValue="True")] [IniProperty("CopyWikiMarkupForImageToClipboard", Description = "Copy the Wikimarkup for the recently uploaded image to the Clipboard", DefaultValue = "True")]
public bool OpenPageAfterUpload { public bool CopyWikiMarkupForImageToClipboard { get; set; }
get;
set; [IniProperty("SearchSpaceKey", Description = "Key of last space that was searched for")]
} public string SearchSpaceKey { get; set; }
[IniProperty("CopyWikiMarkupForImageToClipboard", Description="Copy the Wikimarkup for the recently uploaded image to the Clipboard", DefaultValue="True")]
public bool CopyWikiMarkupForImageToClipboard { [IniProperty("IncludePersonSpaces", Description = "Include personal spaces in the search & browse spaces list", DefaultValue = "False")]
get; public bool IncludePersonSpaces { get; set; }
set; }
}
[IniProperty("SearchSpaceKey", Description="Key of last space that was searched for")]
public string SearchSpaceKey {
get;
set;
}
[IniProperty("IncludePersonSpaces", Description = "Include personal spaces in the search & browse spaces list", DefaultValue = "False")]
public bool IncludePersonSpaces {
get;
set;
}
}
} }

View file

@ -32,174 +32,221 @@ using GreenshotPlugin.IniFile;
using GreenshotPlugin.Interfaces; using GreenshotPlugin.Interfaces;
using GreenshotPlugin.Interfaces.Plugin; using GreenshotPlugin.Interfaces.Plugin;
namespace Greenshot.Plugin.Confluence { namespace Greenshot.Plugin.Confluence
/// <summary> {
/// Description of ConfluenceDestination. /// <summary>
/// </summary> /// Description of ConfluenceDestination.
public class ConfluenceDestination : AbstractDestination { /// </summary>
private static readonly log4net.ILog Log = log4net.LogManager.GetLogger(typeof(ConfluenceDestination)); public class ConfluenceDestination : AbstractDestination
private static readonly ConfluenceConfiguration ConfluenceConfig = IniConfig.GetIniSection<ConfluenceConfiguration>(); {
private static readonly CoreConfiguration CoreConfig = IniConfig.GetIniSection<CoreConfiguration>(); private static readonly log4net.ILog Log = log4net.LogManager.GetLogger(typeof(ConfluenceDestination));
private static readonly Image ConfluenceIcon; private static readonly ConfluenceConfiguration ConfluenceConfig = IniConfig.GetIniSection<ConfluenceConfiguration>();
private readonly Page _page; private static readonly CoreConfiguration CoreConfig = IniConfig.GetIniSection<CoreConfiguration>();
private static readonly Image ConfluenceIcon;
private readonly Page _page;
static ConfluenceDestination() { static ConfluenceDestination()
IsInitialized = false;
try {
Uri confluenceIconUri = new Uri("/GreenshotConfluencePlugin;component/Images/Confluence.ico", UriKind.Relative);
using (Stream iconStream = Application.GetResourceStream(confluenceIconUri)?.Stream)
{
// TODO: Check what to do with the IImage
ConfluenceIcon = ImageHelper.FromStream(iconStream);
}
IsInitialized = true;
} catch (Exception ex) {
Log.ErrorFormat("Problem in the confluence static initializer: {0}", ex.Message);
}
}
public static bool IsInitialized
{ {
get; IsInitialized = false;
private set; try
{
Uri confluenceIconUri = new Uri("/GreenshotConfluencePlugin;component/Images/Confluence.ico", UriKind.Relative);
using (Stream iconStream = Application.GetResourceStream(confluenceIconUri)?.Stream)
{
// TODO: Check what to do with the IImage
ConfluenceIcon = ImageHelper.FromStream(iconStream);
}
IsInitialized = true;
}
catch (Exception ex)
{
Log.ErrorFormat("Problem in the confluence static initializer: {0}", ex.Message);
}
} }
public ConfluenceDestination() { public static bool IsInitialized { get; private set; }
}
public ConfluenceDestination(Page page) { public ConfluenceDestination()
_page = page; {
} }
public override string Designation { public ConfluenceDestination(Page page)
get { {
return "Confluence"; _page = page;
} }
}
public override string Description { public override string Designation
get { {
if (_page == null) { get { return "Confluence"; }
return Language.GetString("confluence", LangKey.upload_menu_item); }
} else {
return Language.GetString("confluence", LangKey.upload_menu_item) + ": \"" + _page.Title + "\"";
}
}
}
public override bool IsDynamic { public override string Description
get { {
return true; get
} {
} if (_page == null)
{
return Language.GetString("confluence", LangKey.upload_menu_item);
}
else
{
return Language.GetString("confluence", LangKey.upload_menu_item) + ": \"" + _page.Title + "\"";
}
}
}
public override bool IsActive { public override bool IsDynamic
get { {
return base.IsActive && !string.IsNullOrEmpty(ConfluenceConfig.Url); get { return true; }
} }
}
public override Image DisplayIcon { public override bool IsActive
get { {
return ConfluenceIcon; get { return base.IsActive && !string.IsNullOrEmpty(ConfluenceConfig.Url); }
} }
}
public override IEnumerable<IDestination> DynamicDestinations() { public override Image DisplayIcon
if (ConfluencePlugin.ConfluenceConnectorNoLogin == null || !ConfluencePlugin.ConfluenceConnectorNoLogin.IsLoggedIn) { {
yield break; get { return ConfluenceIcon; }
} }
List<Page> currentPages = ConfluenceUtils.GetCurrentPages();
if (currentPages == null || currentPages.Count == 0) {
yield break;
}
foreach(Page currentPage in currentPages) {
yield return new ConfluenceDestination(currentPage);
}
}
public override ExportInformation ExportCapture(bool manuallyInitiated, ISurface surface, ICaptureDetails captureDetails) { public override IEnumerable<IDestination> DynamicDestinations()
ExportInformation exportInformation = new ExportInformation(Designation, Description); {
// force password check to take place before the pages load if (ConfluencePlugin.ConfluenceConnectorNoLogin == null || !ConfluencePlugin.ConfluenceConnectorNoLogin.IsLoggedIn)
if (!ConfluencePlugin.ConfluenceConnector.IsLoggedIn) { {
return exportInformation; yield break;
} }
Page selectedPage = _page; List<Page> currentPages = ConfluenceUtils.GetCurrentPages();
bool openPage = (_page == null) && ConfluenceConfig.OpenPageAfterUpload; if (currentPages == null || currentPages.Count == 0)
string filename = FilenameHelper.GetFilenameWithoutExtensionFromPattern(CoreConfig.OutputFileFilenamePattern, captureDetails); {
if (selectedPage == null) { yield break;
Forms.ConfluenceUpload confluenceUpload = new Forms.ConfluenceUpload(filename); }
bool? dialogResult = confluenceUpload.ShowDialog();
if (dialogResult.HasValue && dialogResult.Value) { foreach (Page currentPage in currentPages)
selectedPage = confluenceUpload.SelectedPage; {
if (confluenceUpload.IsOpenPageSelected) { yield return new ConfluenceDestination(currentPage);
openPage = false; }
} }
filename = confluenceUpload.Filename;
} public override ExportInformation ExportCapture(bool manuallyInitiated, ISurface surface, ICaptureDetails captureDetails)
} {
string extension = "." + ConfluenceConfig.UploadFormat; ExportInformation exportInformation = new ExportInformation(Designation, Description);
if (!filename.ToLower().EndsWith(extension)) { // force password check to take place before the pages load
filename += extension; if (!ConfluencePlugin.ConfluenceConnector.IsLoggedIn)
} {
if (selectedPage != null) { return exportInformation;
}
Page selectedPage = _page;
bool openPage = (_page == null) && ConfluenceConfig.OpenPageAfterUpload;
string filename = FilenameHelper.GetFilenameWithoutExtensionFromPattern(CoreConfig.OutputFileFilenamePattern, captureDetails);
if (selectedPage == null)
{
Forms.ConfluenceUpload confluenceUpload = new Forms.ConfluenceUpload(filename);
bool? dialogResult = confluenceUpload.ShowDialog();
if (dialogResult.HasValue && dialogResult.Value)
{
selectedPage = confluenceUpload.SelectedPage;
if (confluenceUpload.IsOpenPageSelected)
{
openPage = false;
}
filename = confluenceUpload.Filename;
}
}
string extension = "." + ConfluenceConfig.UploadFormat;
if (!filename.ToLower().EndsWith(extension))
{
filename += extension;
}
if (selectedPage != null)
{
bool uploaded = Upload(surface, selectedPage, filename, out var errorMessage); bool uploaded = Upload(surface, selectedPage, filename, out var errorMessage);
if (uploaded) { if (uploaded)
if (openPage) { {
try if (openPage)
{ {
Process.Start(selectedPage.Url); try
} {
catch Process.Start(selectedPage.Url);
{ }
// Ignore catch
} {
} // Ignore
exportInformation.ExportMade = true; }
exportInformation.Uri = selectedPage.Url; }
} else {
exportInformation.ErrorMessage = errorMessage;
}
}
ProcessExport(exportInformation, surface);
return exportInformation;
}
private bool Upload(ISurface surfaceToUpload, Page page, string filename, out string errorMessage) { exportInformation.ExportMade = true;
SurfaceOutputSettings outputSettings = new SurfaceOutputSettings(ConfluenceConfig.UploadFormat, ConfluenceConfig.UploadJpegQuality, ConfluenceConfig.UploadReduceColors); exportInformation.Uri = selectedPage.Url;
errorMessage = null; }
try { else
new PleaseWaitForm().ShowAndWait(Description, Language.GetString("confluence", LangKey.communication_wait), {
delegate { exportInformation.ErrorMessage = errorMessage;
ConfluencePlugin.ConfluenceConnector.AddAttachment(page.Id, "image/" + ConfluenceConfig.UploadFormat.ToString().ToLower(), null, filename, new SurfaceContainer(surfaceToUpload, outputSettings, filename)); }
} }
);
Log.Debug("Uploaded to Confluence."); ProcessExport(exportInformation, surface);
if (!ConfluenceConfig.CopyWikiMarkupForImageToClipboard) return exportInformation;
{ }
return true;
} private bool Upload(ISurface surfaceToUpload, Page page, string filename, out string errorMessage)
int retryCount = 2; {
while (retryCount >= 0) { SurfaceOutputSettings outputSettings =
try { new SurfaceOutputSettings(ConfluenceConfig.UploadFormat, ConfluenceConfig.UploadJpegQuality, ConfluenceConfig.UploadReduceColors);
Clipboard.SetText("!" + filename + "!"); errorMessage = null;
break; try
} catch (Exception ee) { {
if (retryCount == 0) { new PleaseWaitForm().ShowAndWait(Description, Language.GetString("confluence", LangKey.communication_wait),
Log.Error(ee); delegate
} else { {
Thread.Sleep(100); ConfluencePlugin.ConfluenceConnector.AddAttachment(page.Id, "image/" + ConfluenceConfig.UploadFormat.ToString().ToLower(), null, filename,
} new SurfaceContainer(surfaceToUpload, outputSettings, filename));
} finally { }
--retryCount; );
} Log.Debug("Uploaded to Confluence.");
} if (!ConfluenceConfig.CopyWikiMarkupForImageToClipboard)
return true; {
} catch(Exception e) { return true;
errorMessage = e.Message; }
}
return false; int retryCount = 2;
} while (retryCount >= 0)
} {
try
{
Clipboard.SetText("!" + filename + "!");
break;
}
catch (Exception ee)
{
if (retryCount == 0)
{
Log.Error(ee);
}
else
{
Thread.Sleep(100);
}
}
finally
{
--retryCount;
}
}
return true;
}
catch (Exception e)
{
errorMessage = e.Message;
}
return false;
}
}
} }

View file

@ -28,109 +28,142 @@ using GreenshotPlugin.IniFile;
using GreenshotPlugin.Interfaces; using GreenshotPlugin.Interfaces;
using GreenshotPlugin.Interfaces.Plugin; using GreenshotPlugin.Interfaces.Plugin;
namespace Greenshot.Plugin.Confluence { namespace Greenshot.Plugin.Confluence
/// <summary> {
/// This is the ConfluencePlugin base code /// <summary>
/// </summary> /// This is the ConfluencePlugin base code
[Plugin("Confluence", true)] /// </summary>
public class ConfluencePlugin : IGreenshotPlugin { [Plugin("Confluence", true)]
private static readonly log4net.ILog LOG = log4net.LogManager.GetLogger(typeof(ConfluencePlugin)); public class ConfluencePlugin : IGreenshotPlugin
private static ConfluenceConnector _confluenceConnector; {
private static ConfluenceConfiguration _config; private static readonly log4net.ILog LOG = log4net.LogManager.GetLogger(typeof(ConfluencePlugin));
private static ConfluenceConnector _confluenceConnector;
private static ConfluenceConfiguration _config;
public void Dispose() { public void Dispose()
Dispose(true); {
GC.SuppressFinalize(this); Dispose(true);
} GC.SuppressFinalize(this);
}
protected void Dispose(bool disposing) { protected void Dispose(bool disposing)
//if (disposing) {} {
} //if (disposing) {}
}
private static void CreateConfluenceConnector() { private static void CreateConfluenceConnector()
if (_confluenceConnector == null) { {
if (_config.Url.Contains("soap-axis")) { if (_confluenceConnector == null)
_confluenceConnector = new ConfluenceConnector(_config.Url, _config.Timeout); {
} else { if (_config.Url.Contains("soap-axis"))
_confluenceConnector = new ConfluenceConnector(_config.Url + ConfluenceConfiguration.DEFAULT_POSTFIX2, _config.Timeout); {
} _confluenceConnector = new ConfluenceConnector(_config.Url, _config.Timeout);
} }
} else
{
_confluenceConnector = new ConfluenceConnector(_config.Url + ConfluenceConfiguration.DEFAULT_POSTFIX2, _config.Timeout);
}
}
}
public static ConfluenceConnector ConfluenceConnectorNoLogin { public static ConfluenceConnector ConfluenceConnectorNoLogin
get { {
return _confluenceConnector; get { return _confluenceConnector; }
} }
}
public static ConfluenceConnector ConfluenceConnector { public static ConfluenceConnector ConfluenceConnector
get { {
if (_confluenceConnector == null) { get
CreateConfluenceConnector(); {
} if (_confluenceConnector == null)
try { {
if (_confluenceConnector != null && !_confluenceConnector.IsLoggedIn) { CreateConfluenceConnector();
_confluenceConnector.Login(); }
}
} catch (Exception e) { try
MessageBox.Show(Language.GetFormattedString("confluence", LangKey.login_error, e.Message)); {
} if (_confluenceConnector != null && !_confluenceConnector.IsLoggedIn)
return _confluenceConnector; {
} _confluenceConnector.Login();
} }
}
catch (Exception e)
{
MessageBox.Show(Language.GetFormattedString("confluence", LangKey.login_error, e.Message));
}
return _confluenceConnector;
}
}
/// <summary>
/// Implementation of the IGreenshotPlugin.Initialize
/// </summary>
public bool Initialize()
{
// Register configuration (don't need the configuration itself)
_config = IniConfig.GetIniSection<ConfluenceConfiguration>();
if (_config.IsDirty)
{
IniConfig.Save();
}
try
{
TranslationManager.Instance.TranslationProvider = new LanguageXMLTranslationProvider();
//resources = new ComponentResourceManager(typeof(ConfluencePlugin));
}
catch (Exception ex)
{
LOG.ErrorFormat("Problem in ConfluencePlugin.Initialize: {0}", ex.Message);
return false;
}
/// <summary>
/// Implementation of the IGreenshotPlugin.Initialize
/// </summary>
public bool Initialize() {
// Register configuration (don't need the configuration itself)
_config = IniConfig.GetIniSection<ConfluenceConfiguration>();
if(_config.IsDirty) {
IniConfig.Save();
}
try {
TranslationManager.Instance.TranslationProvider = new LanguageXMLTranslationProvider();
//resources = new ComponentResourceManager(typeof(ConfluencePlugin));
} catch (Exception ex) {
LOG.ErrorFormat("Problem in ConfluencePlugin.Initialize: {0}", ex.Message);
return false;
}
if (ConfluenceDestination.IsInitialized) if (ConfluenceDestination.IsInitialized)
{ {
SimpleServiceProvider.Current.AddService<IDestination>(new ConfluenceDestination()); SimpleServiceProvider.Current.AddService<IDestination>(new ConfluenceDestination());
} }
return true;
}
public void Shutdown() { return true;
LOG.Debug("Confluence Plugin shutdown."); }
if (_confluenceConnector != null) {
_confluenceConnector.Logout();
_confluenceConnector = null;
}
}
/// <summary> public void Shutdown()
/// Implementation of the IPlugin.Configure {
/// </summary> LOG.Debug("Confluence Plugin shutdown.");
public void Configure() { if (_confluenceConnector != null)
ConfluenceConfiguration clonedConfig = _config.Clone(); {
ConfluenceConfigurationForm configForm = new ConfluenceConfigurationForm(clonedConfig); _confluenceConnector.Logout();
string url = _config.Url; _confluenceConnector = null;
bool? dialogResult = configForm.ShowDialog(); }
if (dialogResult.HasValue && dialogResult.Value) { }
// copy the new object to the old...
clonedConfig.CloneTo(_config); /// <summary>
IniConfig.Save(); /// Implementation of the IPlugin.Configure
if (_confluenceConnector != null) { /// </summary>
if (!url.Equals(_config.Url)) { public void Configure()
if (_confluenceConnector.IsLoggedIn) { {
_confluenceConnector.Logout(); ConfluenceConfiguration clonedConfig = _config.Clone();
} ConfluenceConfigurationForm configForm = new ConfluenceConfigurationForm(clonedConfig);
_confluenceConnector = null; string url = _config.Url;
} bool? dialogResult = configForm.ShowDialog();
} if (dialogResult.HasValue && dialogResult.Value)
} {
} // copy the new object to the old...
} clonedConfig.CloneTo(_config);
IniConfig.Save();
if (_confluenceConnector != null)
{
if (!url.Equals(_config.Url))
{
if (_confluenceConnector.IsLoggedIn)
{
_confluenceConnector.Logout();
}
_confluenceConnector = null;
}
}
}
}
}
} }

View file

@ -26,126 +26,172 @@ using System.Text.RegularExpressions;
using System.Windows.Automation; using System.Windows.Automation;
using GreenshotPlugin.Core; using GreenshotPlugin.Core;
namespace Greenshot.Plugin.Confluence { namespace Greenshot.Plugin.Confluence
/// <summary> {
/// Description of ConfluenceUtils. /// <summary>
/// </summary> /// Description of ConfluenceUtils.
public class ConfluenceUtils { /// </summary>
private static readonly log4net.ILog LOG = log4net.LogManager.GetLogger(typeof(ConfluenceUtils)); public class ConfluenceUtils
{
private static readonly log4net.ILog LOG = log4net.LogManager.GetLogger(typeof(ConfluenceUtils));
public static List<Page> GetCurrentPages() { public static List<Page> GetCurrentPages()
List<Page> pages = new List<Page>(); {
Regex pageIdRegex = new Regex(@"pageId=(\d+)"); List<Page> pages = new List<Page>();
Regex spacePageRegex = new Regex(@"\/display\/([^\/]+)\/([^#]+)"); Regex pageIdRegex = new Regex(@"pageId=(\d+)");
foreach(string browserurl in GetBrowserUrls()) { Regex spacePageRegex = new Regex(@"\/display\/([^\/]+)\/([^#]+)");
string url; foreach (string browserurl in GetBrowserUrls())
try { {
url = Uri.UnescapeDataString(browserurl).Replace("+", " "); string url;
} catch { try
LOG.WarnFormat("Error processing URL: {0}", browserurl); {
continue; url = Uri.UnescapeDataString(browserurl).Replace("+", " ");
} }
MatchCollection pageIdMatch = pageIdRegex.Matches(url); catch
if (pageIdMatch != null && pageIdMatch.Count > 0) { {
long pageId = long.Parse(pageIdMatch[0].Groups[1].Value); LOG.WarnFormat("Error processing URL: {0}", browserurl);
try { continue;
bool pageDouble = false; }
foreach(Page page in pages) {
if (page.Id == pageId) {
pageDouble = true;
LOG.DebugFormat("Skipping double page with ID {0}", pageId);
break;
}
}
if (!pageDouble) {
Page page = ConfluencePlugin.ConfluenceConnector.GetPage(pageId);
LOG.DebugFormat("Adding page {0}", page.Title);
pages.Add(page);
}
continue;
} catch (Exception ex) {
// Preventing security problems
LOG.DebugFormat("Couldn't get page details for PageID {0}", pageId);
LOG.Warn(ex);
}
}
MatchCollection spacePageMatch = spacePageRegex.Matches(url);
if (spacePageMatch != null && spacePageMatch.Count > 0) {
if (spacePageMatch[0].Groups.Count >= 2) {
string space = spacePageMatch[0].Groups[1].Value;
string title = spacePageMatch[0].Groups[2].Value;
if (string.IsNullOrEmpty(title) || string.IsNullOrEmpty(space)) {
continue;
}
if (title.EndsWith("#")) {
title = title.Substring(0, title.Length-1);
}
try {
bool pageDouble = false;
foreach(Page page in pages) {
if (page.Title.Equals(title)) {
LOG.DebugFormat("Skipping double page with title {0}", title);
pageDouble = true;
break;
}
}
if (!pageDouble) {
Page page = ConfluencePlugin.ConfluenceConnector.GetPage(space, title);
LOG.DebugFormat("Adding page {0}", page.Title);
pages.Add(page);
} MatchCollection pageIdMatch = pageIdRegex.Matches(url);
} catch (Exception ex) { if (pageIdMatch != null && pageIdMatch.Count > 0)
// Preventing security problems {
LOG.DebugFormat("Couldn't get page details for space {0} / title {1}", space, title); long pageId = long.Parse(pageIdMatch[0].Groups[1].Value);
LOG.Warn(ex); try
} {
} bool pageDouble = false;
} foreach (Page page in pages)
} {
return pages; if (page.Id == pageId)
} {
pageDouble = true;
LOG.DebugFormat("Skipping double page with ID {0}", pageId);
break;
}
}
private static IEnumerable<string> GetBrowserUrls() { if (!pageDouble)
HashSet<string> urls = new HashSet<string>(); {
Page page = ConfluencePlugin.ConfluenceConnector.GetPage(pageId);
LOG.DebugFormat("Adding page {0}", page.Title);
pages.Add(page);
}
// FireFox continue;
foreach (WindowDetails window in WindowDetails.GetAllWindows("MozillaWindowClass")) { }
if (window.Text.Length == 0) { catch (Exception ex)
continue; {
} // Preventing security problems
AutomationElement currentElement = AutomationElement.FromHandle(window.Handle); LOG.DebugFormat("Couldn't get page details for PageID {0}", pageId);
Condition conditionCustom = new AndCondition(new PropertyCondition(AutomationElement.ControlTypeProperty, ControlType.Custom), new PropertyCondition(AutomationElement.IsOffscreenProperty, false)); LOG.Warn(ex);
for (int i = 5; i > 0 && currentElement != null; i--) { }
currentElement = currentElement.FindFirst(TreeScope.Children, conditionCustom); }
}
if (currentElement == null) {
continue;
}
Condition conditionDocument = new AndCondition(new PropertyCondition(AutomationElement.ControlTypeProperty, ControlType.Document), new PropertyCondition(AutomationElement.IsOffscreenProperty, false)); MatchCollection spacePageMatch = spacePageRegex.Matches(url);
AutomationElement docElement = currentElement.FindFirst(TreeScope.Children, conditionDocument); if (spacePageMatch != null && spacePageMatch.Count > 0)
if (docElement == null) { {
continue; if (spacePageMatch[0].Groups.Count >= 2)
} {
foreach (AutomationPattern pattern in docElement.GetSupportedPatterns()) { string space = spacePageMatch[0].Groups[1].Value;
if (pattern.ProgrammaticName != "ValuePatternIdentifiers.Pattern") { string title = spacePageMatch[0].Groups[2].Value;
continue; if (string.IsNullOrEmpty(title) || string.IsNullOrEmpty(space))
} {
string url = (docElement.GetCurrentPattern(pattern) as ValuePattern).Current.Value; continue;
if (!string.IsNullOrEmpty(url)) { }
urls.Add(url);
break;
}
}
}
foreach(string url in IEHelper.GetIEUrls().Distinct()) { if (title.EndsWith("#"))
urls.Add(url); {
} title = title.Substring(0, title.Length - 1);
}
return urls; try
} {
bool pageDouble = false;
foreach (Page page in pages)
{
if (page.Title.Equals(title))
{
LOG.DebugFormat("Skipping double page with title {0}", title);
pageDouble = true;
break;
}
}
} if (!pageDouble)
{
Page page = ConfluencePlugin.ConfluenceConnector.GetPage(space, title);
LOG.DebugFormat("Adding page {0}", page.Title);
pages.Add(page);
}
}
catch (Exception ex)
{
// Preventing security problems
LOG.DebugFormat("Couldn't get page details for space {0} / title {1}", space, title);
LOG.Warn(ex);
}
}
}
}
return pages;
}
private static IEnumerable<string> GetBrowserUrls()
{
HashSet<string> urls = new HashSet<string>();
// FireFox
foreach (WindowDetails window in WindowDetails.GetAllWindows("MozillaWindowClass"))
{
if (window.Text.Length == 0)
{
continue;
}
AutomationElement currentElement = AutomationElement.FromHandle(window.Handle);
Condition conditionCustom = new AndCondition(new PropertyCondition(AutomationElement.ControlTypeProperty, ControlType.Custom),
new PropertyCondition(AutomationElement.IsOffscreenProperty, false));
for (int i = 5; i > 0 && currentElement != null; i--)
{
currentElement = currentElement.FindFirst(TreeScope.Children, conditionCustom);
}
if (currentElement == null)
{
continue;
}
Condition conditionDocument = new AndCondition(new PropertyCondition(AutomationElement.ControlTypeProperty, ControlType.Document),
new PropertyCondition(AutomationElement.IsOffscreenProperty, false));
AutomationElement docElement = currentElement.FindFirst(TreeScope.Children, conditionDocument);
if (docElement == null)
{
continue;
}
foreach (AutomationPattern pattern in docElement.GetSupportedPatterns())
{
if (pattern.ProgrammaticName != "ValuePatternIdentifiers.Pattern")
{
continue;
}
string url = (docElement.GetCurrentPattern(pattern) as ValuePattern).Current.Value;
if (!string.IsNullOrEmpty(url))
{
urls.Add(url);
break;
}
}
}
foreach (string url in IEHelper.GetIEUrls().Distinct())
{
urls.Add(url);
}
return urls;
}
}
} }

View file

@ -28,70 +28,87 @@ using System.Reflection;
using System.Windows.Data; using System.Windows.Data;
using GreenshotPlugin.Core; using GreenshotPlugin.Core;
namespace Greenshot.Plugin.Confluence { namespace Greenshot.Plugin.Confluence
public class EnumDisplayer : IValueConverter { {
private Type _type; public class EnumDisplayer : IValueConverter
private IDictionary _displayValues; {
private IDictionary _reverseValues; private Type _type;
private IDictionary _displayValues;
private IDictionary _reverseValues;
public Type Type { public Type Type
get { return _type; } {
set { get { return _type; }
if (!value.IsEnum) { set
throw new ArgumentException("parameter is not an Enumerated type", nameof(value)); {
} if (!value.IsEnum)
_type = value; {
} throw new ArgumentException("parameter is not an Enumerated type", nameof(value));
} }
public ReadOnlyCollection<string> DisplayNames { _type = value;
get { }
var genericTypeDefinition = typeof(Dictionary<,>).GetGenericTypeDefinition(); }
if (genericTypeDefinition != null)
{
_reverseValues = (IDictionary) Activator.CreateInstance(genericTypeDefinition.MakeGenericType(typeof(string),_type));
}
var typeDefinition = typeof(Dictionary<,>).GetGenericTypeDefinition(); public ReadOnlyCollection<string> DisplayNames
if (typeDefinition != null) {
{ get
_displayValues = (IDictionary)Activator.CreateInstance(typeDefinition.MakeGenericType(_type, typeof(string))); {
} var genericTypeDefinition = typeof(Dictionary<,>).GetGenericTypeDefinition();
if (genericTypeDefinition != null)
{
_reverseValues = (IDictionary) Activator.CreateInstance(genericTypeDefinition.MakeGenericType(typeof(string), _type));
}
var fields = _type.GetFields(BindingFlags.Public | BindingFlags.Static); var typeDefinition = typeof(Dictionary<,>).GetGenericTypeDefinition();
foreach (var field in fields) { if (typeDefinition != null)
DisplayKeyAttribute[] a = (DisplayKeyAttribute[])field.GetCustomAttributes(typeof(DisplayKeyAttribute), false); {
_displayValues = (IDictionary) Activator.CreateInstance(typeDefinition.MakeGenericType(_type, typeof(string)));
}
string displayKey = GetDisplayKeyValue(a); var fields = _type.GetFields(BindingFlags.Public | BindingFlags.Static);
object enumValue = field.GetValue(null); foreach (var field in fields)
{
DisplayKeyAttribute[] a = (DisplayKeyAttribute[]) field.GetCustomAttributes(typeof(DisplayKeyAttribute), false);
string displayString; string displayKey = GetDisplayKeyValue(a);
if (displayKey != null && Language.HasKey(displayKey)) { object enumValue = field.GetValue(null);
displayString = Language.GetString(displayKey);
}
displayString = displayKey ?? enumValue.ToString();
_displayValues.Add(enumValue, displayString); string displayString;
_reverseValues.Add(displayString, enumValue); if (displayKey != null && Language.HasKey(displayKey))
} {
return new List<string>((IEnumerable<string>)_displayValues.Values).AsReadOnly(); displayString = Language.GetString(displayKey);
} }
}
private static string GetDisplayKeyValue(DisplayKeyAttribute[] a) { displayString = displayKey ?? enumValue.ToString();
if (a == null || a.Length == 0) {
return null;
}
DisplayKeyAttribute dka = a[0];
return dka.Value;
}
object IValueConverter.Convert(object value, Type targetType, object parameter, CultureInfo culture) { _displayValues.Add(enumValue, displayString);
return _displayValues[value]; _reverseValues.Add(displayString, enumValue);
} }
object IValueConverter.ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) { return new List<string>((IEnumerable<string>) _displayValues.Values).AsReadOnly();
return _reverseValues[value]; }
} }
}
private static string GetDisplayKeyValue(DisplayKeyAttribute[] a)
{
if (a == null || a.Length == 0)
{
return null;
}
DisplayKeyAttribute dka = a[0];
return dka.Value;
}
object IValueConverter.Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
return _displayValues[value];
}
object IValueConverter.ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
return _reverseValues[value];
}
}
} }

View file

@ -21,21 +21,25 @@
using System.Windows; using System.Windows;
namespace Greenshot.Plugin.Confluence.Forms { namespace Greenshot.Plugin.Confluence.Forms
/// <summary> {
/// Interaction logic for ConfluenceConfigurationForm.xaml /// <summary>
/// </summary> /// Interaction logic for ConfluenceConfigurationForm.xaml
public partial class ConfluenceConfigurationForm : Window { /// </summary>
public ConfluenceConfiguration Config { get; } public partial class ConfluenceConfigurationForm : Window
{
public ConfluenceConfiguration Config { get; }
public ConfluenceConfigurationForm(ConfluenceConfiguration config) { public ConfluenceConfigurationForm(ConfluenceConfiguration config)
DataContext = config; {
Config = config; DataContext = config;
InitializeComponent(); Config = config;
} InitializeComponent();
}
private void Button_OK_Click(object sender, RoutedEventArgs e) { private void Button_OK_Click(object sender, RoutedEventArgs e)
DialogResult = true; {
} DialogResult = true;
} }
}
} }

View file

@ -21,36 +21,44 @@
using System.Collections.Generic; using System.Collections.Generic;
namespace Greenshot.Plugin.Confluence.Forms { namespace Greenshot.Plugin.Confluence.Forms
/// <summary> {
/// Interaction logic for ConfluencePagePicker.xaml /// <summary>
/// </summary> /// Interaction logic for ConfluencePagePicker.xaml
public partial class ConfluencePagePicker /// </summary>
{ public partial class ConfluencePagePicker
private readonly ConfluenceUpload _confluenceUpload; {
private readonly ConfluenceUpload _confluenceUpload;
public ConfluencePagePicker(ConfluenceUpload confluenceUpload, List<Page> pagesToPick) { public ConfluencePagePicker(ConfluenceUpload confluenceUpload, List<Page> pagesToPick)
_confluenceUpload = confluenceUpload; {
DataContext = pagesToPick; _confluenceUpload = confluenceUpload;
InitializeComponent(); DataContext = pagesToPick;
} InitializeComponent();
}
private void PageListView_SelectionChanged(object sender, System.Windows.Controls.SelectionChangedEventArgs e) { private void PageListView_SelectionChanged(object sender, System.Windows.Controls.SelectionChangedEventArgs e)
SelectionChanged(); {
} SelectionChanged();
}
private void SelectionChanged() { private void SelectionChanged()
if (PageListView.HasItems && PageListView.SelectedItems.Count > 0) { {
_confluenceUpload.SelectedPage = (Page)PageListView.SelectedItem; if (PageListView.HasItems && PageListView.SelectedItems.Count > 0)
// Make sure the uploader knows we selected an already opened page {
_confluenceUpload.IsOpenPageSelected = true; _confluenceUpload.SelectedPage = (Page) PageListView.SelectedItem;
} else { // Make sure the uploader knows we selected an already opened page
_confluenceUpload.SelectedPage = null; _confluenceUpload.IsOpenPageSelected = true;
} }
} else
{
_confluenceUpload.SelectedPage = null;
}
}
private void Page_Loaded(object sender, System.Windows.RoutedEventArgs e) { private void Page_Loaded(object sender, System.Windows.RoutedEventArgs e)
SelectionChanged(); {
} SelectionChanged();
} }
}
} }

View file

@ -25,69 +25,88 @@ using System.Linq;
using System.Windows; using System.Windows;
using GreenshotPlugin.IniFile; using GreenshotPlugin.IniFile;
namespace Greenshot.Plugin.Confluence.Forms { namespace Greenshot.Plugin.Confluence.Forms
public partial class ConfluenceSearch {
{ public partial class ConfluenceSearch
private static readonly ConfluenceConfiguration ConfluenceConfig = IniConfig.GetIniSection<ConfluenceConfiguration>(); {
private readonly ConfluenceUpload _confluenceUpload; private static readonly ConfluenceConfiguration ConfluenceConfig = IniConfig.GetIniSection<ConfluenceConfiguration>();
private readonly ConfluenceUpload _confluenceUpload;
public IEnumerable<Space> Spaces => _confluenceUpload.Spaces; public IEnumerable<Space> Spaces => _confluenceUpload.Spaces;
public ObservableCollection<Page> Pages { get; } = new ObservableCollection<Page>(); public ObservableCollection<Page> Pages { get; } = new ObservableCollection<Page>();
public ConfluenceSearch(ConfluenceUpload confluenceUpload) { public ConfluenceSearch(ConfluenceUpload confluenceUpload)
_confluenceUpload = confluenceUpload; {
DataContext = this; _confluenceUpload = confluenceUpload;
InitializeComponent(); DataContext = this;
if (ConfluenceConfig.SearchSpaceKey == null) { InitializeComponent();
SpaceComboBox.SelectedItem = Spaces.FirstOrDefault(); if (ConfluenceConfig.SearchSpaceKey == null)
} else { {
foreach(var space in Spaces) { SpaceComboBox.SelectedItem = Spaces.FirstOrDefault();
if (space.Key.Equals(ConfluenceConfig.SearchSpaceKey)) { }
SpaceComboBox.SelectedItem = space; else
} {
} foreach (var space in Spaces)
} {
} if (space.Key.Equals(ConfluenceConfig.SearchSpaceKey))
{
SpaceComboBox.SelectedItem = space;
}
}
}
}
private void PageListView_SelectionChanged(object sender, System.Windows.Controls.SelectionChangedEventArgs e) { private void PageListView_SelectionChanged(object sender, System.Windows.Controls.SelectionChangedEventArgs e)
SelectionChanged(); {
} SelectionChanged();
}
private void SelectionChanged() { private void SelectionChanged()
if (PageListView.HasItems && PageListView.SelectedItems.Count > 0) { {
_confluenceUpload.SelectedPage = (Page)PageListView.SelectedItem; if (PageListView.HasItems && PageListView.SelectedItems.Count > 0)
} else { {
_confluenceUpload.SelectedPage = null; _confluenceUpload.SelectedPage = (Page) PageListView.SelectedItem;
} }
} else
{
_confluenceUpload.SelectedPage = null;
}
}
private void Search_Click(object sender, RoutedEventArgs e) { private void Search_Click(object sender, RoutedEventArgs e)
DoSearch(); {
} DoSearch();
}
private void DoSearch() { private void DoSearch()
string spaceKey = (string)SpaceComboBox.SelectedValue; {
ConfluenceConfig.SearchSpaceKey = spaceKey; string spaceKey = (string) SpaceComboBox.SelectedValue;
Pages.Clear(); ConfluenceConfig.SearchSpaceKey = spaceKey;
foreach(var page in ConfluencePlugin.ConfluenceConnector.SearchPages(searchText.Text, spaceKey).OrderBy(p => p.Title)) { Pages.Clear();
Pages.Add(page); foreach (var page in ConfluencePlugin.ConfluenceConnector.SearchPages(searchText.Text, spaceKey).OrderBy(p => p.Title))
} {
} Pages.Add(page);
}
}
private void SearchText_KeyDown(object sender, System.Windows.Input.KeyEventArgs e) { private void SearchText_KeyDown(object sender, System.Windows.Input.KeyEventArgs e)
if (e.Key == System.Windows.Input.Key.Return && Search.IsEnabled) { {
DoSearch(); if (e.Key == System.Windows.Input.Key.Return && Search.IsEnabled)
e.Handled = true; {
} DoSearch();
} e.Handled = true;
}
}
private void Page_Loaded(object sender, RoutedEventArgs e) { private void Page_Loaded(object sender, RoutedEventArgs e)
SelectionChanged(); {
} SelectionChanged();
}
private void SearchText_TextChanged(object sender, System.Windows.Controls.TextChangedEventArgs e) { private void SearchText_TextChanged(object sender, System.Windows.Controls.TextChangedEventArgs e)
Search.IsEnabled = !string.IsNullOrEmpty(searchText.Text); {
} Search.IsEnabled = !string.IsNullOrEmpty(searchText.Text);
} }
}
} }

View file

@ -27,101 +27,131 @@ using System.Windows.Controls;
using System.Windows.Input; using System.Windows.Input;
using System.Windows.Threading; using System.Windows.Threading;
namespace Greenshot.Plugin.Confluence.Forms { namespace Greenshot.Plugin.Confluence.Forms
/// <summary> {
/// Interaction logic for ConfluenceTreePicker.xaml /// <summary>
/// </summary> /// Interaction logic for ConfluenceTreePicker.xaml
public partial class ConfluenceTreePicker /// </summary>
{ public partial class ConfluenceTreePicker
private static readonly log4net.ILog Log = log4net.LogManager.GetLogger(typeof(ConfluenceTreePicker)); {
private readonly ConfluenceConnector _confluenceConnector; private static readonly log4net.ILog Log = log4net.LogManager.GetLogger(typeof(ConfluenceTreePicker));
private readonly ConfluenceUpload _confluenceUpload; private readonly ConfluenceConnector _confluenceConnector;
private bool _isInitDone; private readonly ConfluenceUpload _confluenceUpload;
private bool _isInitDone;
public ConfluenceTreePicker(ConfluenceUpload confluenceUpload) { public ConfluenceTreePicker(ConfluenceUpload confluenceUpload)
_confluenceConnector = ConfluencePlugin.ConfluenceConnector; {
_confluenceUpload = confluenceUpload; _confluenceConnector = ConfluencePlugin.ConfluenceConnector;
InitializeComponent(); _confluenceUpload = confluenceUpload;
} InitializeComponent();
}
private void PageTreeViewItem_DoubleClick(object sender, MouseButtonEventArgs eventArgs) { private void PageTreeViewItem_DoubleClick(object sender, MouseButtonEventArgs eventArgs)
Log.Debug("spaceTreeViewItem_MouseLeftButtonDown is called!"); {
TreeViewItem clickedItem = eventArgs.Source as TreeViewItem; Log.Debug("spaceTreeViewItem_MouseLeftButtonDown is called!");
if (clickedItem?.Tag is not Page page) { TreeViewItem clickedItem = eventArgs.Source as TreeViewItem;
return; if (clickedItem?.Tag is not Page page)
} {
if (clickedItem.HasItems) return;
{ }
return;
}
Log.Debug("Loading pages for page: " + page.Title);
new Thread(() => {
Dispatcher.BeginInvoke(DispatcherPriority.Normal, (ThreadStart)(() => {ShowBusy.Visibility = Visibility.Visible;}));
var pages = _confluenceConnector.GetPageChildren(page).OrderBy(p => p.Title);
Dispatcher.BeginInvoke(DispatcherPriority.Normal, (ThreadStart)(() => {
foreach(var childPage in pages) {
Log.Debug("Adding page: " + childPage.Title);
var pageTreeViewItem = new TreeViewItem
{
Header = childPage.Title,
Tag = childPage
};
clickedItem.Items.Add(pageTreeViewItem);
pageTreeViewItem.PreviewMouseDoubleClick += PageTreeViewItem_DoubleClick;
pageTreeViewItem.PreviewMouseLeftButtonDown += PageTreeViewItem_Click;
}
ShowBusy.Visibility = Visibility.Collapsed;
}));
}) { Name = "Loading childpages for confluence page " + page.Title }.Start();
}
private void PageTreeViewItem_Click(object sender, MouseButtonEventArgs eventArgs) { if (clickedItem.HasItems)
Log.Debug("pageTreeViewItem_PreviewMouseDoubleClick is called!"); {
if (eventArgs.Source is not TreeViewItem clickedItem) { return;
return; }
}
Page page = clickedItem.Tag as Page;
_confluenceUpload.SelectedPage = page;
if (page != null) {
Log.Debug("Page selected: " + page.Title);
}
}
private void Page_Loaded(object sender, RoutedEventArgs e) { Log.Debug("Loading pages for page: " + page.Title);
_confluenceUpload.SelectedPage = null; new Thread(() =>
if (_isInitDone) { {
return; Dispatcher.BeginInvoke(DispatcherPriority.Normal, (ThreadStart) (() => { ShowBusy.Visibility = Visibility.Visible; }));
} var pages = _confluenceConnector.GetPageChildren(page).OrderBy(p => p.Title);
ShowBusy.Visibility = Visibility.Visible; Dispatcher.BeginInvoke(DispatcherPriority.Normal, (ThreadStart) (() =>
new Thread(() => { {
Dispatcher.BeginInvoke(DispatcherPriority.Normal, (ThreadStart)(() => { foreach (var childPage in pages)
foreach (Space space in _confluenceUpload.Spaces) { {
TreeViewItem spaceTreeViewItem = new TreeViewItem Log.Debug("Adding page: " + childPage.Title);
{ var pageTreeViewItem = new TreeViewItem
Header = space.Name, {
Tag = space Header = childPage.Title,
}; Tag = childPage
};
clickedItem.Items.Add(pageTreeViewItem);
pageTreeViewItem.PreviewMouseDoubleClick += PageTreeViewItem_DoubleClick;
pageTreeViewItem.PreviewMouseLeftButtonDown += PageTreeViewItem_Click;
}
// Get homepage ShowBusy.Visibility = Visibility.Collapsed;
try { }));
Page page = _confluenceConnector.GetSpaceHomepage(space); })
TreeViewItem pageTreeViewItem = new TreeViewItem {
{ Name = "Loading childpages for confluence page " + page.Title
Header = page.Title, }.Start();
Tag = page }
};
pageTreeViewItem.PreviewMouseDoubleClick += PageTreeViewItem_DoubleClick; private void PageTreeViewItem_Click(object sender, MouseButtonEventArgs eventArgs)
pageTreeViewItem.PreviewMouseLeftButtonDown += PageTreeViewItem_Click; {
spaceTreeViewItem.Items.Add(pageTreeViewItem); Log.Debug("pageTreeViewItem_PreviewMouseDoubleClick is called!");
ConfluenceTreeView.Items.Add(spaceTreeViewItem); if (eventArgs.Source is not TreeViewItem clickedItem)
} catch (Exception ex) { {
Log.Error("Can't get homepage for space : " + space.Name + " (" + ex.Message + ")"); return;
} }
}
ShowBusy.Visibility = Visibility.Collapsed; Page page = clickedItem.Tag as Page;
_isInitDone = true; _confluenceUpload.SelectedPage = page;
})); if (page != null)
}) { Name = "Loading spaces for confluence"}.Start(); {
} Log.Debug("Page selected: " + page.Title);
} }
}
private void Page_Loaded(object sender, RoutedEventArgs e)
{
_confluenceUpload.SelectedPage = null;
if (_isInitDone)
{
return;
}
ShowBusy.Visibility = Visibility.Visible;
new Thread(() =>
{
Dispatcher.BeginInvoke(DispatcherPriority.Normal, (ThreadStart) (() =>
{
foreach (Space space in _confluenceUpload.Spaces)
{
TreeViewItem spaceTreeViewItem = new TreeViewItem
{
Header = space.Name,
Tag = space
};
// Get homepage
try
{
Page page = _confluenceConnector.GetSpaceHomepage(space);
TreeViewItem pageTreeViewItem = new TreeViewItem
{
Header = page.Title,
Tag = page
};
pageTreeViewItem.PreviewMouseDoubleClick += PageTreeViewItem_DoubleClick;
pageTreeViewItem.PreviewMouseLeftButtonDown += PageTreeViewItem_Click;
spaceTreeViewItem.Items.Add(pageTreeViewItem);
ConfluenceTreeView.Items.Add(spaceTreeViewItem);
}
catch (Exception ex)
{
Log.Error("Can't get homepage for space : " + space.Name + " (" + ex.Message + ")");
}
}
ShowBusy.Visibility = Visibility.Collapsed;
_isInitDone = true;
}));
})
{
Name = "Loading spaces for confluence"
}.Start();
}
}
} }

View file

@ -25,92 +25,117 @@ using System.Linq;
using System.Threading; using System.Threading;
using System.Windows; using System.Windows;
namespace Greenshot.Plugin.Confluence.Forms { namespace Greenshot.Plugin.Confluence.Forms
/// <summary> {
/// Interaction logic for ConfluenceUpload.xaml /// <summary>
/// </summary> /// Interaction logic for ConfluenceUpload.xaml
public partial class ConfluenceUpload : Window { /// </summary>
private System.Windows.Controls.Page _pickerPage; public partial class ConfluenceUpload : Window
public System.Windows.Controls.Page PickerPage { {
get { private System.Windows.Controls.Page _pickerPage;
if (_pickerPage == null) {
List<Page> pages = ConfluenceUtils.GetCurrentPages();
if (pages != null && pages.Count > 0) {
_pickerPage = new ConfluencePagePicker(this, pages);
}
}
return _pickerPage;
}
}
private System.Windows.Controls.Page _searchPage; public System.Windows.Controls.Page PickerPage
public System.Windows.Controls.Page SearchPage { {
get { return _searchPage ??= new ConfluenceSearch(this); } get
} {
if (_pickerPage == null)
{
List<Page> pages = ConfluenceUtils.GetCurrentPages();
if (pages != null && pages.Count > 0)
{
_pickerPage = new ConfluencePagePicker(this, pages);
}
}
private System.Windows.Controls.Page _browsePage; return _pickerPage;
public System.Windows.Controls.Page BrowsePage { }
get { return _browsePage ??= new ConfluenceTreePicker(this); } }
}
private Page _selectedPage; private System.Windows.Controls.Page _searchPage;
public Page SelectedPage {
get => _selectedPage;
set {
_selectedPage = value;
Upload.IsEnabled = _selectedPage != null;
IsOpenPageSelected = false;
}
}
public bool IsOpenPageSelected { public System.Windows.Controls.Page SearchPage
get; {
set; get { return _searchPage ??= new ConfluenceSearch(this); }
} }
public string Filename {
get;
set;
}
private static DateTime _lastLoad = DateTime.Now; private System.Windows.Controls.Page _browsePage;
private static IList<Space> _spaces;
public IList<Space> Spaces {
get {
UpdateSpaces();
while (_spaces == null) {
Thread.Sleep(300);
}
return _spaces;
}
}
public ConfluenceUpload(string filename) { public System.Windows.Controls.Page BrowsePage
Filename = filename; {
InitializeComponent(); get { return _browsePage ??= new ConfluenceTreePicker(this); }
DataContext = this; }
UpdateSpaces();
if (PickerPage == null) {
PickerTab.Visibility = Visibility.Collapsed;
SearchTab.IsSelected = true;
}
}
private void UpdateSpaces() { private Page _selectedPage;
if (_spaces != null && DateTime.Now.AddMinutes(-60).CompareTo(_lastLoad) > 0) {
// Reset
_spaces = null;
}
// Check if load is needed
if (_spaces == null) {
(new Thread(() => {
_spaces = ConfluencePlugin.ConfluenceConnector.GetSpaceSummaries().OrderBy(s => s.Name).ToList();
_lastLoad = DateTime.Now;
}) { Name = "Loading spaces for confluence"}).Start();
}
}
private void Upload_Click(object sender, RoutedEventArgs e) { public Page SelectedPage
DialogResult = true; {
} get => _selectedPage;
} set
{
_selectedPage = value;
Upload.IsEnabled = _selectedPage != null;
IsOpenPageSelected = false;
}
}
public bool IsOpenPageSelected { get; set; }
public string Filename { get; set; }
private static DateTime _lastLoad = DateTime.Now;
private static IList<Space> _spaces;
public IList<Space> Spaces
{
get
{
UpdateSpaces();
while (_spaces == null)
{
Thread.Sleep(300);
}
return _spaces;
}
}
public ConfluenceUpload(string filename)
{
Filename = filename;
InitializeComponent();
DataContext = this;
UpdateSpaces();
if (PickerPage == null)
{
PickerTab.Visibility = Visibility.Collapsed;
SearchTab.IsSelected = true;
}
}
private void UpdateSpaces()
{
if (_spaces != null && DateTime.Now.AddMinutes(-60).CompareTo(_lastLoad) > 0)
{
// Reset
_spaces = null;
}
// Check if load is needed
if (_spaces == null)
{
(new Thread(() =>
{
_spaces = ConfluencePlugin.ConfluenceConnector.GetSpaceSummaries().OrderBy(s => s.Name).ToList();
_lastLoad = DateTime.Now;
})
{
Name = "Loading spaces for confluence"
}).Start();
}
}
private void Upload_Click(object sender, RoutedEventArgs e)
{
DialogResult = true;
}
}
} }

View file

@ -19,10 +19,12 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>. * along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
namespace Greenshot.Plugin.Confluence { namespace Greenshot.Plugin.Confluence
public enum LangKey { {
login_error, public enum LangKey
upload_menu_item, {
communication_wait login_error,
} upload_menu_item,
communication_wait
}
} }

View file

@ -1,5 +1,7 @@
namespace Greenshot.Plugin.Confluence.Support { namespace Greenshot.Plugin.Confluence.Support
public interface ITranslationProvider { {
public interface ITranslationProvider
{
/// <summary> /// <summary>
/// Translates the specified key. /// Translates the specified key.
/// </summary> /// </summary>

View file

@ -22,13 +22,13 @@ namespace Greenshot.Plugin.Confluence.Support
protected override void StartListening(object source) protected override void StartListening(object source)
{ {
var manager = (TranslationManager)source; var manager = (TranslationManager) source;
manager.LanguageChanged += OnLanguageChanged; manager.LanguageChanged += OnLanguageChanged;
} }
protected override void StopListening(object source) protected override void StopListening(object source)
{ {
var manager = (TranslationManager)source; var manager = (TranslationManager) source;
manager.LanguageChanged -= OnLanguageChanged; manager.LanguageChanged -= OnLanguageChanged;
} }
@ -37,15 +37,15 @@ namespace Greenshot.Plugin.Confluence.Support
get get
{ {
Type managerType = typeof(LanguageChangedEventManager); Type managerType = typeof(LanguageChangedEventManager);
var manager = (LanguageChangedEventManager)GetCurrentManager(managerType); var manager = (LanguageChangedEventManager) GetCurrentManager(managerType);
if (manager == null) if (manager == null)
{ {
manager = new LanguageChangedEventManager(); manager = new LanguageChangedEventManager();
SetCurrentManager(managerType, manager); SetCurrentManager(managerType, manager);
} }
return manager; return manager;
} }
} }
} }
} }

View file

@ -1,18 +1,23 @@
using GreenshotPlugin.Core; using GreenshotPlugin.Core;
namespace Greenshot.Plugin.Confluence.Support { namespace Greenshot.Plugin.Confluence.Support
/// <summary> {
/// /// <summary>
/// </summary> ///
public class LanguageXMLTranslationProvider : ITranslationProvider { /// </summary>
public class LanguageXMLTranslationProvider : ITranslationProvider
{
/// <summary> /// <summary>
/// See <see cref="ITranslationProvider.Translate" /> /// See <see cref="ITranslationProvider.Translate" />
/// </summary> /// </summary>
public object Translate(string key) { public object Translate(string key)
if (Language.HasKey("confluence", key)) { {
return Language.GetString("confluence", key); if (Language.HasKey("confluence", key))
} {
return key; return Language.GetString("confluence", key);
} }
return key;
}
} }
} }

View file

@ -25,7 +25,7 @@ namespace Greenshot.Plugin.Confluence.Support
public string Key public string Key
{ {
get { return _key; } get { return _key; }
set { _key = value;} set { _key = value; }
} }
/// <summary> /// <summary>
@ -34,9 +34,9 @@ namespace Greenshot.Plugin.Confluence.Support
public override object ProvideValue(IServiceProvider serviceProvider) public override object ProvideValue(IServiceProvider serviceProvider)
{ {
var binding = new Binding("Value") var binding = new Binding("Value")
{ {
Source = new TranslationData(_key) Source = new TranslationData(_key)
}; };
return binding.ProvideValue(serviceProvider); return binding.ProvideValue(serviceProvider);
} }
} }

View file

@ -2,43 +2,48 @@
using System.ComponentModel; using System.ComponentModel;
using System.Windows; using System.Windows;
namespace Greenshot.Plugin.Confluence.Support { namespace Greenshot.Plugin.Confluence.Support
public class TranslationData : IWeakEventListener, INotifyPropertyChanged { {
public class TranslationData : IWeakEventListener, INotifyPropertyChanged
{
private readonly string _key; private readonly string _key;
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="TranslationData"/> class. /// Initializes a new instance of the <see cref="TranslationData"/> class.
/// </summary> /// </summary>
/// <param name="key">The key.</param> /// <param name="key">The key.</param>
public TranslationData( string key) { public TranslationData(string key)
_key = key; {
LanguageChangedEventManager.AddListener(TranslationManager.Instance, this); _key = key;
} LanguageChangedEventManager.AddListener(TranslationManager.Instance, this);
}
/// <summary> /// <summary>
/// Releases unmanaged resources and performs other cleanup operations before the /// Releases unmanaged resources and performs other cleanup operations before the
/// <see cref="TranslationData"/> is reclaimed by garbage collection. /// <see cref="TranslationData"/> is reclaimed by garbage collection.
/// </summary> /// </summary>
~TranslationData() { ~TranslationData()
LanguageChangedEventManager.RemoveListener(TranslationManager.Instance, this); {
} LanguageChangedEventManager.RemoveListener(TranslationManager.Instance, this);
}
public object Value => TranslationManager.Instance.Translate(_key); public object Value => TranslationManager.Instance.Translate(_key);
public bool ReceiveWeakEvent(Type managerType, object sender, EventArgs e) public bool ReceiveWeakEvent(Type managerType, object sender, EventArgs e)
{ {
if (managerType == typeof(LanguageChangedEventManager)) if (managerType == typeof(LanguageChangedEventManager))
{ {
OnLanguageChanged(sender, e); OnLanguageChanged(sender, e);
return true; return true;
} }
return false;
}
private void OnLanguageChanged(object sender, EventArgs e) return false;
{ }
PropertyChanged?.Invoke( this, new PropertyChangedEventArgs("Value"));
} private void OnLanguageChanged(object sender, EventArgs e)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("Value"));
}
public event PropertyChangedEventHandler PropertyChanged; public event PropertyChangedEventHandler PropertyChanged;
} }

View file

@ -1,40 +1,45 @@
using System; using System;
namespace Greenshot.Plugin.Confluence.Support { namespace Greenshot.Plugin.Confluence.Support
public class TranslationManager { {
private static TranslationManager _translationManager; public class TranslationManager
{
private static TranslationManager _translationManager;
public event EventHandler LanguageChanged; public event EventHandler LanguageChanged;
/*public CultureInfo CurrentLanguage { /*public CultureInfo CurrentLanguage {
get { return Thread.CurrentThread.CurrentUICulture; } get { return Thread.CurrentThread.CurrentUICulture; }
set { set {
if( value != Thread.CurrentThread.CurrentUICulture) { if( value != Thread.CurrentThread.CurrentUICulture) {
Thread.CurrentThread.CurrentUICulture = value; Thread.CurrentThread.CurrentUICulture = value;
OnLanguageChanged(); OnLanguageChanged();
} }
} }
} }
public IEnumerable<CultureInfo> Languages { public IEnumerable<CultureInfo> Languages {
get { get {
if( TranslationProvider != null) { if( TranslationProvider != null) {
return TranslationProvider.Languages; return TranslationProvider.Languages;
} }
return Enumerable.Empty<CultureInfo>(); return Enumerable.Empty<CultureInfo>();
} }
}*/ }*/
public static TranslationManager Instance => _translationManager ??= new TranslationManager(); public static TranslationManager Instance => _translationManager ??= new TranslationManager();
public ITranslationProvider TranslationProvider { get; set; } public ITranslationProvider TranslationProvider { get; set; }
public object Translate(string key) { public object Translate(string key)
object translatedValue = TranslationProvider?.Translate(key); {
if( translatedValue != null) { object translatedValue = TranslationProvider?.Translate(key);
return translatedValue; if (translatedValue != null)
} {
return $"!{key}!"; return translatedValue;
} }
}
return $"!{key}!";
}
}
} }

View file

@ -25,38 +25,48 @@ using GreenshotPlugin.Core;
using GreenshotPlugin.IniFile; using GreenshotPlugin.IniFile;
using GreenshotPlugin.Interfaces; using GreenshotPlugin.Interfaces;
namespace Greenshot.Plugin.Dropbox { namespace Greenshot.Plugin.Dropbox
internal class DropboxDestination : AbstractDestination { {
private static readonly DropboxPluginConfiguration DropboxConfig = IniConfig.GetIniSection<DropboxPluginConfiguration>(); internal class DropboxDestination : AbstractDestination
{
private static readonly DropboxPluginConfiguration DropboxConfig = IniConfig.GetIniSection<DropboxPluginConfiguration>();
private readonly DropboxPlugin _plugin; private readonly DropboxPlugin _plugin;
public DropboxDestination(DropboxPlugin plugin) {
_plugin = plugin;
}
public override string Designation => "Dropbox"; public DropboxDestination(DropboxPlugin plugin)
{
_plugin = plugin;
}
public override string Description => Language.GetString("dropbox", LangKey.upload_menu_item); public override string Designation => "Dropbox";
public override Image DisplayIcon { public override string Description => Language.GetString("dropbox", LangKey.upload_menu_item);
get {
ComponentResourceManager resources = new ComponentResourceManager(typeof(DropboxPlugin));
return (Image)resources.GetObject("Dropbox");
}
}
public override ExportInformation ExportCapture(bool manually, ISurface surface, ICaptureDetails captureDetails) { public override Image DisplayIcon
ExportInformation exportInformation = new ExportInformation(Designation, Description); {
bool uploaded = _plugin.Upload(captureDetails, surface, out var uploadUrl); get
if (uploaded) { {
exportInformation.Uri = uploadUrl; ComponentResourceManager resources = new ComponentResourceManager(typeof(DropboxPlugin));
exportInformation.ExportMade = true; return (Image) resources.GetObject("Dropbox");
if (DropboxConfig.AfterUploadLinkToClipBoard) { }
ClipboardHelper.SetClipboardData(uploadUrl); }
}
} public override ExportInformation ExportCapture(bool manually, ISurface surface, ICaptureDetails captureDetails)
ProcessExport(exportInformation, surface); {
return exportInformation; ExportInformation exportInformation = new ExportInformation(Designation, Description);
} bool uploaded = _plugin.Upload(captureDetails, surface, out var uploadUrl);
} if (uploaded)
{
exportInformation.Uri = uploadUrl;
exportInformation.ExportMade = true;
if (DropboxConfig.AfterUploadLinkToClipBoard)
{
ClipboardHelper.SetClipboardData(uploadUrl);
}
}
ProcessExport(exportInformation, surface);
return exportInformation;
}
}
} }

View file

@ -29,93 +29,101 @@ using GreenshotPlugin.IniFile;
using GreenshotPlugin.Interfaces; using GreenshotPlugin.Interfaces;
using GreenshotPlugin.Interfaces.Plugin; using GreenshotPlugin.Interfaces.Plugin;
namespace Greenshot.Plugin.Dropbox { namespace Greenshot.Plugin.Dropbox
/// <summary> {
/// This is the Dropbox base code /// <summary>
/// </summary> /// This is the Dropbox base code
/// </summary>
[Plugin("Dropbox", true)] [Plugin("Dropbox", true)]
public class DropboxPlugin : IGreenshotPlugin { public class DropboxPlugin : IGreenshotPlugin
private static readonly log4net.ILog Log = log4net.LogManager.GetLogger(typeof(DropboxPlugin)); {
private static DropboxPluginConfiguration _config; private static readonly log4net.ILog Log = log4net.LogManager.GetLogger(typeof(DropboxPlugin));
private ComponentResourceManager _resources; private static DropboxPluginConfiguration _config;
private ToolStripMenuItem _itemPlugInConfig; private ComponentResourceManager _resources;
private ToolStripMenuItem _itemPlugInConfig;
public void Dispose() { public void Dispose()
Dispose(true); {
GC.SuppressFinalize(this); Dispose(true);
} GC.SuppressFinalize(this);
}
private void Dispose(bool disposing) private void Dispose(bool disposing)
{ {
if (!disposing) return; if (!disposing) return;
if (_itemPlugInConfig == null) return; if (_itemPlugInConfig == null) return;
_itemPlugInConfig.Dispose(); _itemPlugInConfig.Dispose();
_itemPlugInConfig = null; _itemPlugInConfig = null;
} }
/// <summary> /// <summary>
/// Implementation of the IGreenshotPlugin.Initialize /// Implementation of the IGreenshotPlugin.Initialize
/// </summary> /// </summary>
public bool Initialize() { public bool Initialize()
{
// Register configuration (don't need the configuration itself) // Register configuration (don't need the configuration itself)
_config = IniConfig.GetIniSection<DropboxPluginConfiguration>(); _config = IniConfig.GetIniSection<DropboxPluginConfiguration>();
_resources = new ComponentResourceManager(typeof(DropboxPlugin)); _resources = new ComponentResourceManager(typeof(DropboxPlugin));
SimpleServiceProvider.Current.AddService<IDestination>(new DropboxDestination(this)); SimpleServiceProvider.Current.AddService<IDestination>(new DropboxDestination(this));
_itemPlugInConfig = new ToolStripMenuItem _itemPlugInConfig = new ToolStripMenuItem
{ {
Text = Language.GetString("dropbox", LangKey.Configure), Text = Language.GetString("dropbox", LangKey.Configure),
Image = (Image)_resources.GetObject("Dropbox") Image = (Image) _resources.GetObject("Dropbox")
}; };
_itemPlugInConfig.Click += ConfigMenuClick; _itemPlugInConfig.Click += ConfigMenuClick;
PluginUtils.AddToContextMenu(_itemPlugInConfig); PluginUtils.AddToContextMenu(_itemPlugInConfig);
Language.LanguageChanged += OnLanguageChanged; Language.LanguageChanged += OnLanguageChanged;
return true; return true;
} }
public void OnLanguageChanged(object sender, EventArgs e) { public void OnLanguageChanged(object sender, EventArgs e)
if (_itemPlugInConfig != null) { {
_itemPlugInConfig.Text = Language.GetString("dropbox", LangKey.Configure); if (_itemPlugInConfig != null)
} {
} _itemPlugInConfig.Text = Language.GetString("dropbox", LangKey.Configure);
}
}
public void Shutdown() { public void Shutdown()
Log.Debug("Dropbox Plugin shutdown."); {
} Log.Debug("Dropbox Plugin shutdown.");
}
/// <summary> /// <summary>
/// Implementation of the IPlugin.Configure /// Implementation of the IPlugin.Configure
/// </summary> /// </summary>
public void Configure() { public void Configure()
_config.ShowConfigDialog(); {
} _config.ShowConfigDialog();
}
public void ConfigMenuClick(object sender, EventArgs eventArgs) { public void ConfigMenuClick(object sender, EventArgs eventArgs)
_config.ShowConfigDialog(); {
} _config.ShowConfigDialog();
}
/// <summary> /// <summary>
/// This will be called when the menu item in the Editor is clicked /// This will be called when the menu item in the Editor is clicked
/// </summary> /// </summary>
public bool Upload(ICaptureDetails captureDetails, ISurface surfaceToUpload, out string uploadUrl) { public bool Upload(ICaptureDetails captureDetails, ISurface surfaceToUpload, out string uploadUrl)
uploadUrl = null; {
SurfaceOutputSettings outputSettings = new SurfaceOutputSettings(_config.UploadFormat, _config.UploadJpegQuality, false); uploadUrl = null;
try SurfaceOutputSettings outputSettings = new SurfaceOutputSettings(_config.UploadFormat, _config.UploadJpegQuality, false);
try
{ {
bool result = false; bool result = false;
new PleaseWaitForm().ShowAndWait("Dropbox", Language.GetString("dropbox", LangKey.communication_wait), new PleaseWaitForm().ShowAndWait("Dropbox", Language.GetString("dropbox", LangKey.communication_wait),
delegate delegate { result = DropboxUtils.UploadToDropbox(surfaceToUpload, outputSettings, captureDetails); }
{ );
result = DropboxUtils.UploadToDropbox(surfaceToUpload, outputSettings, captureDetails);
}
);
return result; return result;
} catch (Exception e) { }
Log.Error(e); catch (Exception e)
MessageBox.Show(Language.GetString("dropbox", LangKey.upload_failure) + " " + e.Message); {
return false; Log.Error(e);
} MessageBox.Show(Language.GetString("dropbox", LangKey.upload_failure) + " " + e.Message);
} return false;
} }
}
}
} }

View file

@ -25,25 +25,27 @@ using Greenshot.Plugin.Dropbox.Forms;
using GreenshotPlugin.Core; using GreenshotPlugin.Core;
using GreenshotPlugin.IniFile; using GreenshotPlugin.IniFile;
namespace Greenshot.Plugin.Dropbox { namespace Greenshot.Plugin.Dropbox
/// <summary> {
/// Description of ImgurConfiguration. /// <summary>
/// </summary> /// Description of ImgurConfiguration.
[IniSection("Dropbox", Description = "Greenshot Dropbox Plugin configuration")] /// </summary>
public class DropboxPluginConfiguration : IniSection { [IniSection("Dropbox", Description = "Greenshot Dropbox Plugin configuration")]
[IniProperty("UploadFormat", Description="What file type to use for uploading", DefaultValue="png")] public class DropboxPluginConfiguration : IniSection
public OutputFormat UploadFormat { get; set; } {
[IniProperty("UploadFormat", Description = "What file type to use for uploading", DefaultValue = "png")]
public OutputFormat UploadFormat { get; set; }
[IniProperty("UploadJpegQuality", Description="JPEG file save quality in %.", DefaultValue="80")] [IniProperty("UploadJpegQuality", Description = "JPEG file save quality in %.", DefaultValue = "80")]
public int UploadJpegQuality { get; set; } public int UploadJpegQuality { get; set; }
[IniProperty("AfterUploadLinkToClipBoard", Description = "After upload send Dropbox link to clipboard.", DefaultValue = "true")] [IniProperty("AfterUploadLinkToClipBoard", Description = "After upload send Dropbox link to clipboard.", DefaultValue = "true")]
public bool AfterUploadLinkToClipBoard { get; set; } public bool AfterUploadLinkToClipBoard { get; set; }
[IniProperty("RefreshToken", Description = "Dropbox refresh Token", Encrypted = true, ExcludeIfNull = true)] [IniProperty("RefreshToken", Description = "Dropbox refresh Token", Encrypted = true, ExcludeIfNull = true)]
public string RefreshToken { get; set; } public string RefreshToken { get; set; }
/// <summary> /// <summary>
/// AccessToken, not stored /// AccessToken, not stored
/// </summary> /// </summary>
public string AccessToken { get; set; } public string AccessToken { get; set; }
@ -53,16 +55,19 @@ namespace Greenshot.Plugin.Dropbox {
/// </summary> /// </summary>
public DateTimeOffset AccessTokenExpires { get; set; } public DateTimeOffset AccessTokenExpires { get; set; }
/// <summary> /// <summary>
/// A form for token /// A form for token
/// </summary> /// </summary>
/// <returns>bool true if OK was pressed, false if cancel</returns> /// <returns>bool true if OK was pressed, false if cancel</returns>
public bool ShowConfigDialog() { public bool ShowConfigDialog()
DialogResult result = new SettingsForm().ShowDialog(); {
if (result == DialogResult.OK) { DialogResult result = new SettingsForm().ShowDialog();
return true; if (result == DialogResult.OK)
} {
return false; return true;
} }
}
return false;
}
}
} }

View file

@ -29,19 +29,22 @@ using GreenshotPlugin.Interfaces;
using GreenshotPlugin.Interfaces.Plugin; using GreenshotPlugin.Interfaces.Plugin;
using Newtonsoft.Json; using Newtonsoft.Json;
namespace Greenshot.Plugin.Dropbox { namespace Greenshot.Plugin.Dropbox
/// <summary> {
/// Description of DropboxUtils. /// <summary>
/// </summary> /// Description of DropboxUtils.
public class DropboxUtils { /// </summary>
private static readonly log4net.ILog Log = log4net.LogManager.GetLogger(typeof(DropboxUtils)); public class DropboxUtils
private static readonly DropboxPluginConfiguration DropboxConfig = IniConfig.GetIniSection<DropboxPluginConfiguration>(); {
private static readonly log4net.ILog Log = log4net.LogManager.GetLogger(typeof(DropboxUtils));
private static readonly DropboxPluginConfiguration DropboxConfig = IniConfig.GetIniSection<DropboxPluginConfiguration>();
private DropboxUtils() { private DropboxUtils()
} {
}
public static bool UploadToDropbox(ISurface surfaceToUpload, SurfaceOutputSettings outputSettings, ICaptureDetails captureDetails) public static bool UploadToDropbox(ISurface surfaceToUpload, SurfaceOutputSettings outputSettings, ICaptureDetails captureDetails)
{ {
var oauth2Settings = new OAuth2Settings var oauth2Settings = new OAuth2Settings
{ {
AuthUrlPattern = "https://api.dropbox.com/oauth2/authorize?response_type=token&client_id={ClientId}&state={State}&redirect_uri={RedirectUrl}", AuthUrlPattern = "https://api.dropbox.com/oauth2/authorize?response_type=token&client_id={ClientId}&state={State}&redirect_uri={RedirectUrl}",
@ -51,24 +54,32 @@ namespace Greenshot.Plugin.Dropbox {
ClientId = DropBoxCredentials.CONSUMER_KEY, ClientId = DropBoxCredentials.CONSUMER_KEY,
ClientSecret = DropBoxCredentials.CONSUMER_SECRET, ClientSecret = DropBoxCredentials.CONSUMER_SECRET,
AuthorizeMode = OAuth2AuthorizeMode.JsonReceiver, AuthorizeMode = OAuth2AuthorizeMode.JsonReceiver,
RefreshToken = DropboxConfig.RefreshToken, RefreshToken = DropboxConfig.RefreshToken,
AccessToken = DropboxConfig.AccessToken, AccessToken = DropboxConfig.AccessToken,
AccessTokenExpires = DropboxConfig.AccessTokenExpires AccessTokenExpires = DropboxConfig.AccessTokenExpires
}; };
try try
{ {
string filename = Path.GetFileName(FilenameHelper.GetFilename(DropboxConfig.UploadFormat, captureDetails)); string filename = Path.GetFileName(FilenameHelper.GetFilename(DropboxConfig.UploadFormat, captureDetails));
SurfaceContainer image = new SurfaceContainer(surfaceToUpload, outputSettings, filename); SurfaceContainer image = new SurfaceContainer(surfaceToUpload, outputSettings, filename);
IDictionary<string, object> arguments = new Dictionary<string, object> IDictionary<string, object> arguments = new Dictionary<string, object>
{ {
{ "autorename", true }, {
{ "mute", true }, "autorename", true
{ "path", "/" + filename.Replace(Path.DirectorySeparatorChar, '\\')} },
{
"mute", true
},
{
"path", "/" + filename.Replace(Path.DirectorySeparatorChar, '\\')
}
}; };
IDictionary<string, object> headers = new Dictionary<string, object> IDictionary<string, object> headers = new Dictionary<string, object>
{ {
{ "Dropbox-API-Arg", JsonConvert.SerializeObject(arguments)} {
"Dropbox-API-Arg", JsonConvert.SerializeObject(arguments)
}
}; };
var webRequest = OAuth2Helper.CreateOAuth2WebRequest(HTTPMethod.POST, "https://content.dropboxapi.com/2/files/upload", oauth2Settings); var webRequest = OAuth2Helper.CreateOAuth2WebRequest(HTTPMethod.POST, "https://content.dropboxapi.com/2/files/upload", oauth2Settings);
@ -78,16 +89,19 @@ namespace Greenshot.Plugin.Dropbox {
var response = JsonConvert.DeserializeObject<IDictionary<string, string>>(responseString); var response = JsonConvert.DeserializeObject<IDictionary<string, string>>(responseString);
return response.ContainsKey("id"); return response.ContainsKey("id");
} }
catch (Exception ex) { catch (Exception ex)
Log.Error("Upload error: ", ex); {
throw; Log.Error("Upload error: ", ex);
} finally { throw;
}
finally
{
DropboxConfig.RefreshToken = oauth2Settings.RefreshToken; DropboxConfig.RefreshToken = oauth2Settings.RefreshToken;
DropboxConfig.AccessToken = oauth2Settings.AccessToken; DropboxConfig.AccessToken = oauth2Settings.AccessToken;
DropboxConfig.AccessTokenExpires = oauth2Settings.AccessTokenExpires; DropboxConfig.AccessTokenExpires = oauth2Settings.AccessTokenExpires;
DropboxConfig.IsDirty = true; DropboxConfig.IsDirty = true;
IniConfig.Save(); IniConfig.Save();
} }
} }
} }
} }

View file

@ -21,7 +21,9 @@
using GreenshotPlugin.Controls; using GreenshotPlugin.Controls;
namespace Greenshot.Plugin.Dropbox.Forms { namespace Greenshot.Plugin.Dropbox.Forms
public class DropboxForm : GreenshotForm { {
} public class DropboxForm : GreenshotForm
{
}
} }

View file

@ -19,19 +19,21 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>. * along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
namespace Greenshot.Plugin.Dropbox.Forms { namespace Greenshot.Plugin.Dropbox.Forms
/// <summary> {
/// Description of PasswordRequestForm. /// <summary>
/// </summary> /// Description of PasswordRequestForm.
public partial class SettingsForm : DropboxForm { /// </summary>
public SettingsForm() { public partial class SettingsForm : DropboxForm
// {
// The InitializeComponent() call is required for Windows Forms designer support. public SettingsForm()
// {
InitializeComponent(); //
AcceptButton = buttonOK; // The InitializeComponent() call is required for Windows Forms designer support.
CancelButton = buttonCancel; //
} InitializeComponent();
AcceptButton = buttonOK;
} CancelButton = buttonCancel;
}
}
} }

View file

@ -18,11 +18,14 @@
* You should have received a copy of the GNU General Public License * You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>. * along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
namespace Greenshot.Plugin.Dropbox {
public enum LangKey { namespace Greenshot.Plugin.Dropbox
upload_menu_item, {
upload_failure, public enum LangKey
communication_wait, {
Configure upload_menu_item,
upload_failure,
communication_wait,
Configure
} }
} }

View file

@ -25,122 +25,140 @@ using System.IO;
using GreenshotPlugin.Core; using GreenshotPlugin.Core;
using GreenshotPlugin.IniFile; using GreenshotPlugin.IniFile;
namespace Greenshot.Plugin.ExternalCommand { namespace Greenshot.Plugin.ExternalCommand
/// <summary> {
/// Description of FlickrConfiguration. /// <summary>
/// </summary> /// Description of FlickrConfiguration.
[IniSection("ExternalCommand", Description="Greenshot ExternalCommand Plugin configuration")] /// </summary>
public class ExternalCommandConfiguration : IniSection { [IniSection("ExternalCommand", Description = "Greenshot ExternalCommand Plugin configuration")]
[IniProperty("Commands", Description="The commands that are available.")] public class ExternalCommandConfiguration : IniSection
public List<string> Commands { get; set; } {
[IniProperty("Commands", Description = "The commands that are available.")]
public List<string> Commands { get; set; }
[IniProperty("RedirectStandardError", Description = "Redirect the standard error of all external commands, used to output as warning to the greenshot.log.", DefaultValue = "true")] [IniProperty("RedirectStandardError", Description = "Redirect the standard error of all external commands, used to output as warning to the greenshot.log.",
public bool RedirectStandardError { get; set; } DefaultValue = "true")]
public bool RedirectStandardError { get; set; }
[IniProperty("RedirectStandardOutput", Description = "Redirect the standard output of all external commands, used for different other functions (more below).", DefaultValue = "true")] [IniProperty("RedirectStandardOutput", Description = "Redirect the standard output of all external commands, used for different other functions (more below).",
public bool RedirectStandardOutput { get; set; } DefaultValue = "true")]
public bool RedirectStandardOutput { get; set; }
[IniProperty("ShowStandardOutputInLog", Description = "Depends on 'RedirectStandardOutput': Show standard output of all external commands to the Greenshot log, this can be usefull for debugging.", DefaultValue = "false")] [IniProperty("ShowStandardOutputInLog",
public bool ShowStandardOutputInLog { get; set; } Description = "Depends on 'RedirectStandardOutput': Show standard output of all external commands to the Greenshot log, this can be usefull for debugging.",
DefaultValue = "false")]
public bool ShowStandardOutputInLog { get; set; }
[IniProperty("ParseForUri", Description = "Depends on 'RedirectStandardOutput': Parse the output and take the first found URI, if a URI is found than clicking on the notify bubble goes there.", DefaultValue = "true")] [IniProperty("ParseForUri",
public bool ParseOutputForUri { get; set; } Description = "Depends on 'RedirectStandardOutput': Parse the output and take the first found URI, if a URI is found than clicking on the notify bubble goes there.",
DefaultValue = "true")]
public bool ParseOutputForUri { get; set; }
[IniProperty("OutputToClipboard", Description = "Depends on 'RedirectStandardOutput': Place the standard output on the clipboard.", DefaultValue = "false")] [IniProperty("OutputToClipboard", Description = "Depends on 'RedirectStandardOutput': Place the standard output on the clipboard.", DefaultValue = "false")]
public bool OutputToClipboard { get; set; } public bool OutputToClipboard { get; set; }
[IniProperty("UriToClipboard", Description = "Depends on 'RedirectStandardOutput' & 'ParseForUri': If an URI is found in the standard input, place it on the clipboard. (This overwrites the output from OutputToClipboard setting.)", DefaultValue = "true")] [IniProperty("UriToClipboard",
public bool UriToClipboard { get; set; } Description =
"Depends on 'RedirectStandardOutput' & 'ParseForUri': If an URI is found in the standard input, place it on the clipboard. (This overwrites the output from OutputToClipboard setting.)",
DefaultValue = "true")]
public bool UriToClipboard { get; set; }
[IniProperty("Commandline", Description="The commandline for the output command.")] [IniProperty("Commandline", Description = "The commandline for the output command.")]
public Dictionary<string, string> Commandline { get; set; } public Dictionary<string, string> Commandline { get; set; }
[IniProperty("Argument", Description="The arguments for the output command.")] [IniProperty("Argument", Description = "The arguments for the output command.")]
public Dictionary<string, string> Argument { get; set; } public Dictionary<string, string> Argument { get; set; }
[IniProperty("RunInbackground", Description = "Should the command be started in the background.")] [IniProperty("RunInbackground", Description = "Should the command be started in the background.")]
public Dictionary<string, bool> RunInbackground { get; set; } public Dictionary<string, bool> RunInbackground { get; set; }
[IniProperty("DeletedBuildInCommands", Description = "If a build in command was deleted manually, it should not be recreated.")] [IniProperty("DeletedBuildInCommands", Description = "If a build in command was deleted manually, it should not be recreated.")]
public List<string> DeletedBuildInCommands { get; set; } public List<string> DeletedBuildInCommands { get; set; }
private const string MsPaint = "MS Paint"; private const string MsPaint = "MS Paint";
private static readonly string PaintPath; private static readonly string PaintPath;
private static readonly bool HasPaint; private static readonly bool HasPaint;
private const string PaintDotNet = "Paint.NET"; private const string PaintDotNet = "Paint.NET";
private static readonly string PaintDotNetPath; private static readonly string PaintDotNetPath;
private static readonly bool HasPaintDotNet; private static readonly bool HasPaintDotNet;
static ExternalCommandConfiguration() {
try {
PaintPath = PluginUtils.GetExePath("pbrush.exe");
HasPaint = !string.IsNullOrEmpty(PaintPath) && File.Exists(PaintPath);
} catch {
// Ignore
}
try
{
PaintDotNetPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles), @"Paint.NET\PaintDotNet.exe");
HasPaintDotNet = !string.IsNullOrEmpty(PaintDotNetPath) && File.Exists(PaintDotNetPath);
}
catch
{
// Ignore
}
}
/// <summary> static ExternalCommandConfiguration()
/// Delete the configuration for the specified command {
/// </summary> try
/// <param name="command">string with command</param> {
public void Delete(string command) PaintPath = PluginUtils.GetExePath("pbrush.exe");
{ HasPaint = !string.IsNullOrEmpty(PaintPath) && File.Exists(PaintPath);
if (string.IsNullOrEmpty(command)) }
{ catch
return; {
} // Ignore
Commands.Remove(command); }
Commandline.Remove(command);
Argument.Remove(command);
RunInbackground.Remove(command);
if (MsPaint.Equals(command) || PaintDotNet.Equals(command))
{
if (!DeletedBuildInCommands.Contains(command))
{
DeletedBuildInCommands.Add(command);
}
}
}
public override void AfterLoad() try
{ {
base.AfterLoad(); PaintDotNetPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles), @"Paint.NET\PaintDotNet.exe");
HasPaintDotNet = !string.IsNullOrEmpty(PaintDotNetPath) && File.Exists(PaintDotNetPath);
}
catch
{
// Ignore
}
}
// Check if we need to add MsPaint /// <summary>
if (HasPaint && !Commands.Contains(MsPaint) && !DeletedBuildInCommands.Contains(MsPaint)) /// Delete the configuration for the specified command
{ /// </summary>
Commands.Add(MsPaint); /// <param name="command">string with command</param>
Commandline.Add(MsPaint, PaintPath); public void Delete(string command)
Argument.Add(MsPaint, "\"{0}\""); {
RunInbackground.Add(MsPaint, true); if (string.IsNullOrEmpty(command))
} {
return;
}
// Check if we need to add Paint.NET Commands.Remove(command);
if (HasPaintDotNet && !Commands.Contains(PaintDotNet) && !DeletedBuildInCommands.Contains(PaintDotNet)) Commandline.Remove(command);
{ Argument.Remove(command);
Commands.Add(PaintDotNet); RunInbackground.Remove(command);
Commandline.Add(PaintDotNet, PaintDotNetPath); if (MsPaint.Equals(command) || PaintDotNet.Equals(command))
Argument.Add(PaintDotNet, "\"{0}\""); {
RunInbackground.Add(PaintDotNet, true); if (!DeletedBuildInCommands.Contains(command))
} {
} DeletedBuildInCommands.Add(command);
}
}
}
/// <summary> public override void AfterLoad()
/// Supply values we can't put as defaults {
/// </summary> base.AfterLoad();
/// <param name="property">The property to return a default for</param>
/// <returns>object with the default value for the supplied property</returns> // Check if we need to add MsPaint
public override object GetDefault(string property) => if (HasPaint && !Commands.Contains(MsPaint) && !DeletedBuildInCommands.Contains(MsPaint))
{
Commands.Add(MsPaint);
Commandline.Add(MsPaint, PaintPath);
Argument.Add(MsPaint, "\"{0}\"");
RunInbackground.Add(MsPaint, true);
}
// Check if we need to add Paint.NET
if (HasPaintDotNet && !Commands.Contains(PaintDotNet) && !DeletedBuildInCommands.Contains(PaintDotNet))
{
Commands.Add(PaintDotNet);
Commandline.Add(PaintDotNet, PaintDotNetPath);
Argument.Add(PaintDotNet, "\"{0}\"");
RunInbackground.Add(PaintDotNet, true);
}
}
/// <summary>
/// Supply values we can't put as defaults
/// </summary>
/// <param name="property">The property to return a default for</param>
/// <returns>object with the default value for the supplied property</returns>
public override object GetDefault(string property) =>
property switch property switch
{ {
nameof(DeletedBuildInCommands) => (object) new List<string>(), nameof(DeletedBuildInCommands) => (object) new List<string>(),

View file

@ -31,146 +31,184 @@ using GreenshotPlugin.IniFile;
using GreenshotPlugin.Interfaces; using GreenshotPlugin.Interfaces;
using GreenshotPlugin.Interfaces.Plugin; using GreenshotPlugin.Interfaces.Plugin;
namespace Greenshot.Plugin.ExternalCommand { namespace Greenshot.Plugin.ExternalCommand
/// <summary> {
/// Description of OCRDestination. /// <summary>
/// </summary> /// Description of OCRDestination.
public class ExternalCommandDestination : AbstractDestination { /// </summary>
private static readonly log4net.ILog LOG = log4net.LogManager.GetLogger(typeof(ExternalCommandDestination)); public class ExternalCommandDestination : AbstractDestination
private static readonly Regex URI_REGEXP = new Regex(@"((([A-Za-z]{3,9}:(?:\/\/)?)(?:[\-;:&=\+\$,\w]+@)?[A-Za-z0-9\.\-]+|(?:www\.|[\-;:&=\+\$,\w]+@)[A-Za-z0-9\.\-]+)((?:\/[\+~%\/\.\w\-_]*)?\??(?:[\-\+=&;%@\.\w_]*)#?(?:[\.\!\/\\\w]*))?)"); {
private static readonly ExternalCommandConfiguration config = IniConfig.GetIniSection<ExternalCommandConfiguration>(); private static readonly log4net.ILog LOG = log4net.LogManager.GetLogger(typeof(ExternalCommandDestination));
private readonly string _presetCommand;
public ExternalCommandDestination(string commando) { private static readonly Regex URI_REGEXP =
_presetCommand = commando; new Regex(
} @"((([A-Za-z]{3,9}:(?:\/\/)?)(?:[\-;:&=\+\$,\w]+@)?[A-Za-z0-9\.\-]+|(?:www\.|[\-;:&=\+\$,\w]+@)[A-Za-z0-9\.\-]+)((?:\/[\+~%\/\.\w\-_]*)?\??(?:[\-\+=&;%@\.\w_]*)#?(?:[\.\!\/\\\w]*))?)");
public override string Designation => "External " + _presetCommand.Replace(',','_'); private static readonly ExternalCommandConfiguration config = IniConfig.GetIniSection<ExternalCommandConfiguration>();
private readonly string _presetCommand;
public override string Description => _presetCommand; public ExternalCommandDestination(string commando)
{
_presetCommand = commando;
}
public override IEnumerable<IDestination> DynamicDestinations() { public override string Designation => "External " + _presetCommand.Replace(',', '_');
yield break;
}
public override Image DisplayIcon => IconCache.IconForCommand(_presetCommand); public override string Description => _presetCommand;
public override ExportInformation ExportCapture(bool manuallyInitiated, ISurface surface, ICaptureDetails captureDetails) { public override IEnumerable<IDestination> DynamicDestinations()
ExportInformation exportInformation = new ExportInformation(Designation, Description); {
SurfaceOutputSettings outputSettings = new SurfaceOutputSettings(); yield break;
outputSettings.PreventGreenshotFormat(); }
if (_presetCommand != null) { public override Image DisplayIcon => IconCache.IconForCommand(_presetCommand);
if (!config.RunInbackground.ContainsKey(_presetCommand)) {
config.RunInbackground.Add(_presetCommand, true); public override ExportInformation ExportCapture(bool manuallyInitiated, ISurface surface, ICaptureDetails captureDetails)
} {
bool runInBackground = config.RunInbackground[_presetCommand]; ExportInformation exportInformation = new ExportInformation(Designation, Description);
string fullPath = captureDetails.Filename ?? ImageOutput.SaveNamedTmpFile(surface, captureDetails, outputSettings); SurfaceOutputSettings outputSettings = new SurfaceOutputSettings();
outputSettings.PreventGreenshotFormat();
if (_presetCommand != null)
{
if (!config.RunInbackground.ContainsKey(_presetCommand))
{
config.RunInbackground.Add(_presetCommand, true);
}
bool runInBackground = config.RunInbackground[_presetCommand];
string fullPath = captureDetails.Filename ?? ImageOutput.SaveNamedTmpFile(surface, captureDetails, outputSettings);
string output; string output;
string error; string error;
if (runInBackground) { if (runInBackground)
Thread commandThread = new Thread(delegate() {
{ Thread commandThread = new Thread(delegate()
CallExternalCommand(exportInformation, fullPath, out output, out error); {
ProcessExport(exportInformation, surface); CallExternalCommand(exportInformation, fullPath, out output, out error);
}) ProcessExport(exportInformation, surface);
{ })
Name = "Running " + _presetCommand, {
IsBackground = true Name = "Running " + _presetCommand,
}; IsBackground = true
commandThread.SetApartmentState(ApartmentState.STA); };
commandThread.Start(); commandThread.SetApartmentState(ApartmentState.STA);
exportInformation.ExportMade = true; commandThread.Start();
} else { exportInformation.ExportMade = true;
CallExternalCommand(exportInformation, fullPath, out output, out error); }
ProcessExport(exportInformation, surface); else
} {
} CallExternalCommand(exportInformation, fullPath, out output, out error);
return exportInformation; ProcessExport(exportInformation, surface);
} }
}
/// <summary> return exportInformation;
/// Wrapper method for the background and normal call, this does all the logic: }
/// Call the external command, parse for URI, place to clipboard and set the export information
/// </summary>
/// <param name="exportInformation"></param>
/// <param name="fullPath"></param>
/// <param name="output"></param>
/// <param name="error"></param>
private void CallExternalCommand(ExportInformation exportInformation, string fullPath, out string output, out string error) {
output = null;
error = null;
try {
if (CallExternalCommand(_presetCommand, fullPath, out output, out error) == 0) {
exportInformation.ExportMade = true;
if (!string.IsNullOrEmpty(output)) {
MatchCollection uriMatches = URI_REGEXP.Matches(output);
// Place output on the clipboard before the URI, so if one is found this overwrites
if (config.OutputToClipboard) {
ClipboardHelper.SetClipboardData(output);
}
if (uriMatches.Count > 0) {
exportInformation.Uri = uriMatches[0].Groups[1].Value;
LOG.InfoFormat("Got URI : {0} ", exportInformation.Uri);
if (config.UriToClipboard) {
ClipboardHelper.SetClipboardData(exportInformation.Uri);
}
}
}
} else {
LOG.WarnFormat("Error calling external command: {0} ", output);
exportInformation.ExportMade = false;
exportInformation.ErrorMessage = error;
}
} catch (Exception ex) {
exportInformation.ExportMade = false;
exportInformation.ErrorMessage = ex.Message;
LOG.WarnFormat("Error calling external command: {0} ", exportInformation.ErrorMessage);
}
}
/// <summary> /// <summary>
/// Wrapper to retry with a runas /// Wrapper method for the background and normal call, this does all the logic:
/// </summary> /// Call the external command, parse for URI, place to clipboard and set the export information
/// <param name="commando"></param> /// </summary>
/// <param name="fullPath"></param> /// <param name="exportInformation"></param>
/// <param name="output"></param> /// <param name="fullPath"></param>
/// <param name="error"></param> /// <param name="output"></param>
/// <returns></returns> /// <param name="error"></param>
private int CallExternalCommand(string commando, string fullPath, out string output, out string error) { private void CallExternalCommand(ExportInformation exportInformation, string fullPath, out string output, out string error)
try { {
return CallExternalCommand(commando, fullPath, null, out output, out error); output = null;
} catch (Win32Exception w32Ex) { error = null;
try { try
return CallExternalCommand(commando, fullPath, "runas", out output, out error); {
} catch { if (CallExternalCommand(_presetCommand, fullPath, out output, out error) == 0)
w32Ex.Data.Add("commandline", config.Commandline[_presetCommand]); {
w32Ex.Data.Add("arguments", config.Argument[_presetCommand]); exportInformation.ExportMade = true;
throw; if (!string.IsNullOrEmpty(output))
} {
} catch (Exception ex) { MatchCollection uriMatches = URI_REGEXP.Matches(output);
ex.Data.Add("commandline", config.Commandline[_presetCommand]); // Place output on the clipboard before the URI, so if one is found this overwrites
ex.Data.Add("arguments", config.Argument[_presetCommand]); if (config.OutputToClipboard)
throw; {
} ClipboardHelper.SetClipboardData(output);
} }
/// <summary> if (uriMatches.Count > 0)
/// The actual executing code for the external command {
/// </summary> exportInformation.Uri = uriMatches[0].Groups[1].Value;
/// <param name="commando"></param> LOG.InfoFormat("Got URI : {0} ", exportInformation.Uri);
/// <param name="fullPath"></param> if (config.UriToClipboard)
/// <param name="verb"></param> {
/// <param name="output"></param> ClipboardHelper.SetClipboardData(exportInformation.Uri);
/// <param name="error"></param> }
/// <returns></returns> }
private int CallExternalCommand(string commando, string fullPath, string verb, out string output, out string error) { }
string commandline = config.Commandline[commando]; }
string arguments = config.Argument[commando]; else
output = null; {
error = null; LOG.WarnFormat("Error calling external command: {0} ", output);
if (!string.IsNullOrEmpty(commandline)) exportInformation.ExportMade = false;
exportInformation.ErrorMessage = error;
}
}
catch (Exception ex)
{
exportInformation.ExportMade = false;
exportInformation.ErrorMessage = ex.Message;
LOG.WarnFormat("Error calling external command: {0} ", exportInformation.ErrorMessage);
}
}
/// <summary>
/// Wrapper to retry with a runas
/// </summary>
/// <param name="commando"></param>
/// <param name="fullPath"></param>
/// <param name="output"></param>
/// <param name="error"></param>
/// <returns></returns>
private int CallExternalCommand(string commando, string fullPath, out string output, out string error)
{
try
{
return CallExternalCommand(commando, fullPath, null, out output, out error);
}
catch (Win32Exception w32Ex)
{
try
{
return CallExternalCommand(commando, fullPath, "runas", out output, out error);
}
catch
{
w32Ex.Data.Add("commandline", config.Commandline[_presetCommand]);
w32Ex.Data.Add("arguments", config.Argument[_presetCommand]);
throw;
}
}
catch (Exception ex)
{
ex.Data.Add("commandline", config.Commandline[_presetCommand]);
ex.Data.Add("arguments", config.Argument[_presetCommand]);
throw;
}
}
/// <summary>
/// The actual executing code for the external command
/// </summary>
/// <param name="commando"></param>
/// <param name="fullPath"></param>
/// <param name="verb"></param>
/// <param name="output"></param>
/// <param name="error"></param>
/// <returns></returns>
private int CallExternalCommand(string commando, string fullPath, string verb, out string output, out string error)
{
string commandline = config.Commandline[commando];
string arguments = config.Argument[commando];
output = null;
error = null;
if (!string.IsNullOrEmpty(commandline))
{ {
using Process process = new Process(); using Process process = new Process();
// Fix variables // Fix variables
@ -183,39 +221,52 @@ namespace Greenshot.Plugin.ExternalCommand {
process.StartInfo.FileName = FilenameHelper.FillCmdVariables(commandline, true); process.StartInfo.FileName = FilenameHelper.FillCmdVariables(commandline, true);
process.StartInfo.Arguments = FormatArguments(arguments, fullPath); process.StartInfo.Arguments = FormatArguments(arguments, fullPath);
process.StartInfo.UseShellExecute = false; process.StartInfo.UseShellExecute = false;
if (config.RedirectStandardOutput) { if (config.RedirectStandardOutput)
{
process.StartInfo.RedirectStandardOutput = true; process.StartInfo.RedirectStandardOutput = true;
} }
if (config.RedirectStandardError) {
if (config.RedirectStandardError)
{
process.StartInfo.RedirectStandardError = true; process.StartInfo.RedirectStandardError = true;
} }
if (verb != null) {
if (verb != null)
{
process.StartInfo.Verb = verb; process.StartInfo.Verb = verb;
} }
LOG.InfoFormat("Starting : {0} {1}", process.StartInfo.FileName, process.StartInfo.Arguments); LOG.InfoFormat("Starting : {0} {1}", process.StartInfo.FileName, process.StartInfo.Arguments);
process.Start(); process.Start();
process.WaitForExit(); process.WaitForExit();
if (config.RedirectStandardOutput) { if (config.RedirectStandardOutput)
{
output = process.StandardOutput.ReadToEnd(); output = process.StandardOutput.ReadToEnd();
if (config.ShowStandardOutputInLog && output.Trim().Length > 0) { if (config.ShowStandardOutputInLog && output.Trim().Length > 0)
{
LOG.InfoFormat("Output:\n{0}", output); LOG.InfoFormat("Output:\n{0}", output);
} }
} }
if (config.RedirectStandardError) {
if (config.RedirectStandardError)
{
error = process.StandardError.ReadToEnd(); error = process.StandardError.ReadToEnd();
if (error.Trim().Length > 0) { if (error.Trim().Length > 0)
{
LOG.WarnFormat("Error:\n{0}", error); LOG.WarnFormat("Error:\n{0}", error);
} }
} }
LOG.InfoFormat("Finished : {0} {1}", process.StartInfo.FileName, process.StartInfo.Arguments); LOG.InfoFormat("Finished : {0} {1}", process.StartInfo.FileName, process.StartInfo.Arguments);
return process.ExitCode; return process.ExitCode;
} }
return -1;
}
public static string FormatArguments(string arguments, string fullpath) return -1;
{ }
return string.Format(arguments, fullpath);
} public static string FormatArguments(string arguments, string fullpath)
} {
return string.Format(arguments, fullpath);
}
}
} }

View file

@ -21,10 +21,12 @@
using GreenshotPlugin.Controls; using GreenshotPlugin.Controls;
namespace Greenshot.Plugin.ExternalCommand { namespace Greenshot.Plugin.ExternalCommand
/// <summary> {
/// This class is needed for design-time resolving of the language files /// <summary>
/// </summary> /// This class is needed for design-time resolving of the language files
public class ExternalCommandForm : GreenshotForm { /// </summary>
} public class ExternalCommandForm : GreenshotForm
{
}
} }

View file

@ -55,6 +55,7 @@ namespace Greenshot.Plugin.ExternalCommand
_itemPlugInRoot.Dispose(); _itemPlugInRoot.Dispose();
_itemPlugInRoot = null; _itemPlugInRoot = null;
} }
private IEnumerable<IDestination> Destinations() private IEnumerable<IDestination> Destinations()
{ {
foreach (string command in ExternalCommandConfig.Commands) foreach (string command in ExternalCommandConfig.Commands)
@ -77,17 +78,20 @@ namespace Greenshot.Plugin.ExternalCommand
// Fix it // Fix it
ExternalCommandConfig.RunInbackground.Add(command, true); ExternalCommandConfig.RunInbackground.Add(command, true);
} }
if (!ExternalCommandConfig.Argument.ContainsKey(command)) if (!ExternalCommandConfig.Argument.ContainsKey(command))
{ {
Log.WarnFormat("Found missing argument for {0}", command); Log.WarnFormat("Found missing argument for {0}", command);
// Fix it // Fix it
ExternalCommandConfig.Argument.Add(command, "{0}"); ExternalCommandConfig.Argument.Add(command, "{0}");
} }
if (!ExternalCommandConfig.Commandline.ContainsKey(command)) if (!ExternalCommandConfig.Commandline.ContainsKey(command))
{ {
Log.WarnFormat("Found missing commandline for {0}", command); Log.WarnFormat("Found missing commandline for {0}", command);
return false; return false;
} }
string commandline = FilenameHelper.FillVariables(ExternalCommandConfig.Commandline[command], true); string commandline = FilenameHelper.FillVariables(ExternalCommandConfig.Commandline[command], true);
commandline = FilenameHelper.FillCmdVariables(commandline, true); commandline = FilenameHelper.FillCmdVariables(commandline, true);
@ -96,8 +100,10 @@ namespace Greenshot.Plugin.ExternalCommand
Log.WarnFormat("Found 'invalid' commandline {0} for command {1}", ExternalCommandConfig.Commandline[command], command); Log.WarnFormat("Found 'invalid' commandline {0} for command {1}", ExternalCommandConfig.Commandline[command], command);
return false; return false;
} }
return true; return true;
} }
/// <summary> /// <summary>
/// Implementation of the IGreenshotPlugin.Initialize /// Implementation of the IGreenshotPlugin.Initialize
/// </summary> /// </summary>
@ -114,11 +120,13 @@ namespace Greenshot.Plugin.ExternalCommand
commandsToDelete.Add(command); commandsToDelete.Add(command);
} }
} }
// cleanup // cleanup
foreach (string command in commandsToDelete) foreach (string command in commandsToDelete)
{ {
ExternalCommandConfig.Delete(command); ExternalCommandConfig.Delete(command);
} }
SimpleServiceProvider.Current.AddService(Destinations()); SimpleServiceProvider.Current.AddService(Destinations());
_itemPlugInRoot = new ToolStripMenuItem(); _itemPlugInRoot = new ToolStripMenuItem();

View file

@ -25,23 +25,32 @@ using System.IO;
using GreenshotPlugin.Core; using GreenshotPlugin.Core;
using GreenshotPlugin.IniFile; using GreenshotPlugin.IniFile;
namespace Greenshot.Plugin.ExternalCommand { namespace Greenshot.Plugin.ExternalCommand
public static class IconCache { {
private static readonly ExternalCommandConfiguration config = IniConfig.GetIniSection<ExternalCommandConfiguration>(); public static class IconCache
private static readonly log4net.ILog LOG = log4net.LogManager.GetLogger(typeof(IconCache)); {
private static readonly ExternalCommandConfiguration config = IniConfig.GetIniSection<ExternalCommandConfiguration>();
private static readonly log4net.ILog LOG = log4net.LogManager.GetLogger(typeof(IconCache));
public static Image IconForCommand(string commandName) { public static Image IconForCommand(string commandName)
Image icon = null; {
if (commandName != null) { Image icon = null;
if (config.Commandline.ContainsKey(commandName) && File.Exists(config.Commandline[commandName])) { if (commandName != null)
try { {
icon = PluginUtils.GetCachedExeIcon(config.Commandline[commandName], 0); if (config.Commandline.ContainsKey(commandName) && File.Exists(config.Commandline[commandName]))
} catch (Exception ex) { {
LOG.Warn("Problem loading icon for " + config.Commandline[commandName], ex); try
} {
} icon = PluginUtils.GetCachedExeIcon(config.Commandline[commandName], 0);
} }
return icon; catch (Exception ex)
} {
} LOG.Warn("Problem loading icon for " + config.Commandline[commandName], ex);
}
}
}
return icon;
}
}
} }

View file

@ -24,103 +24,129 @@ using System.Drawing;
using System.Windows.Forms; using System.Windows.Forms;
using GreenshotPlugin.IniFile; using GreenshotPlugin.IniFile;
namespace Greenshot.Plugin.ExternalCommand { namespace Greenshot.Plugin.ExternalCommand
/// <summary> {
/// Description of SettingsForm. /// <summary>
/// </summary> /// Description of SettingsForm.
public partial class SettingsForm : ExternalCommandForm { /// </summary>
private static readonly ExternalCommandConfiguration ExternalCommandConfig = IniConfig.GetIniSection<ExternalCommandConfiguration>(); public partial class SettingsForm : ExternalCommandForm
{
private static readonly ExternalCommandConfiguration ExternalCommandConfig = IniConfig.GetIniSection<ExternalCommandConfiguration>();
public SettingsForm() { public SettingsForm()
// {
// The InitializeComponent() call is required for Windows Forms designer support. //
// // The InitializeComponent() call is required for Windows Forms designer support.
InitializeComponent(); //
AcceptButton = buttonOk; InitializeComponent();
CancelButton = buttonCancel; AcceptButton = buttonOk;
UpdateView(); CancelButton = buttonCancel;
} UpdateView();
}
private void ButtonOkClick(object sender, EventArgs e) { private void ButtonOkClick(object sender, EventArgs e)
IniConfig.Save(); {
} IniConfig.Save();
}
private void ButtonAddClick(object sender, EventArgs e) { private void ButtonAddClick(object sender, EventArgs e)
var form = new SettingsFormDetail(null); {
form.ShowDialog(); var form = new SettingsFormDetail(null);
form.ShowDialog();
UpdateView(); UpdateView();
} }
private void ButtonDeleteClick(object sender, EventArgs e) { private void ButtonDeleteClick(object sender, EventArgs e)
foreach(ListViewItem item in listView1.SelectedItems) { {
string commando = item.Tag as string; foreach (ListViewItem item in listView1.SelectedItems)
{
string commando = item.Tag as string;
ExternalCommandConfig.Delete(commando); ExternalCommandConfig.Delete(commando);
} }
UpdateView();
}
private void UpdateView() { UpdateView();
listView1.Items.Clear(); }
if(ExternalCommandConfig.Commands != null) {
listView1.ListViewItemSorter = new ListviewComparer();
ImageList imageList = new ImageList();
listView1.SmallImageList = imageList;
int imageNr = 0;
foreach(string commando in ExternalCommandConfig.Commands) {
ListViewItem item;
Image iconForExe = IconCache.IconForCommand(commando);
if(iconForExe != null) {
imageList.Images.Add(iconForExe);
item = new ListViewItem(commando, imageNr++);
} else {
item = new ListViewItem(commando);
}
item.Tag = commando;
listView1.Items.Add(item);
}
}
// Fix for bug #1484, getting an ArgumentOutOfRangeException as there is nothing selected but the edit button was still active.
button_edit.Enabled = listView1.SelectedItems.Count > 0;
}
private void ListView1ItemSelectionChanged(object sender, EventArgs e) { private void UpdateView()
button_edit.Enabled = listView1.SelectedItems.Count > 0; {
} listView1.Items.Clear();
if (ExternalCommandConfig.Commands != null)
{
listView1.ListViewItemSorter = new ListviewComparer();
ImageList imageList = new ImageList();
listView1.SmallImageList = imageList;
int imageNr = 0;
foreach (string commando in ExternalCommandConfig.Commands)
{
ListViewItem item;
Image iconForExe = IconCache.IconForCommand(commando);
if (iconForExe != null)
{
imageList.Images.Add(iconForExe);
item = new ListViewItem(commando, imageNr++);
}
else
{
item = new ListViewItem(commando);
}
private void ButtonEditClick(object sender, EventArgs e) { item.Tag = commando;
ListView1DoubleClick(sender, e); listView1.Items.Add(item);
} }
}
private void ListView1DoubleClick(object sender, EventArgs e) { // Fix for bug #1484, getting an ArgumentOutOfRangeException as there is nothing selected but the edit button was still active.
// Safety check for bug #1484 button_edit.Enabled = listView1.SelectedItems.Count > 0;
bool selectionActive = listView1.SelectedItems.Count > 0; }
if(!selectionActive) {
button_edit.Enabled = false;
return;
}
string commando = listView1.SelectedItems[0].Tag as string;
var form = new SettingsFormDetail(commando); private void ListView1ItemSelectionChanged(object sender, EventArgs e)
form.ShowDialog(); {
button_edit.Enabled = listView1.SelectedItems.Count > 0;
}
UpdateView(); private void ButtonEditClick(object sender, EventArgs e)
} {
} ListView1DoubleClick(sender, e);
}
public class ListviewComparer : System.Collections.IComparer { private void ListView1DoubleClick(object sender, EventArgs e)
public int Compare(object x, object y) { {
if(!(x is ListViewItem)) { // Safety check for bug #1484
return (0); bool selectionActive = listView1.SelectedItems.Count > 0;
} if (!selectionActive)
if(!(y is ListViewItem)) { {
return (0); button_edit.Enabled = false;
} return;
}
var l1 = (ListViewItem)x; string commando = listView1.SelectedItems[0].Tag as string;
var l2 = (ListViewItem)y;
return string.Compare(l1.Text, l2.Text, StringComparison.Ordinal); var form = new SettingsFormDetail(commando);
} form.ShowDialog();
}
UpdateView();
}
}
public class ListviewComparer : System.Collections.IComparer
{
public int Compare(object x, object y)
{
if (!(x is ListViewItem))
{
return (0);
}
if (!(y is ListViewItem))
{
return (0);
}
var l1 = (ListViewItem) x;
var l2 = (ListViewItem) y;
return string.Compare(l1.Text, l2.Text, StringComparison.Ordinal);
}
}
} }

View file

@ -26,141 +26,167 @@ using System.Windows.Forms;
using GreenshotPlugin.Core; using GreenshotPlugin.Core;
using GreenshotPlugin.IniFile; using GreenshotPlugin.IniFile;
namespace Greenshot.Plugin.ExternalCommand { namespace Greenshot.Plugin.ExternalCommand
/// <summary> {
/// Description of SettingsFormDetail. /// <summary>
/// </summary> /// Description of SettingsFormDetail.
public partial class SettingsFormDetail : ExternalCommandForm { /// </summary>
private static readonly log4net.ILog Log = log4net.LogManager.GetLogger(typeof(SettingsFormDetail)); public partial class SettingsFormDetail : ExternalCommandForm
private static readonly ExternalCommandConfiguration ExternalCommandConfig = IniConfig.GetIniSection<ExternalCommandConfiguration>(); {
private static readonly log4net.ILog Log = log4net.LogManager.GetLogger(typeof(SettingsFormDetail));
private static readonly ExternalCommandConfiguration ExternalCommandConfig = IniConfig.GetIniSection<ExternalCommandConfiguration>();
private readonly string _commando; private readonly string _commando;
private readonly int _commandIndex; private readonly int _commandIndex;
public SettingsFormDetail(string commando) { public SettingsFormDetail(string commando)
InitializeComponent(); {
AcceptButton = buttonOk; InitializeComponent();
CancelButton = buttonCancel; AcceptButton = buttonOk;
_commando = commando; CancelButton = buttonCancel;
_commando = commando;
if(commando != null) { if (commando != null)
textBox_name.Text = commando; {
textBox_commandline.Text = ExternalCommandConfig.Commandline[commando]; textBox_name.Text = commando;
textBox_arguments.Text = ExternalCommandConfig.Argument[commando]; textBox_commandline.Text = ExternalCommandConfig.Commandline[commando];
_commandIndex = ExternalCommandConfig.Commands.FindIndex(s => s == commando); textBox_arguments.Text = ExternalCommandConfig.Argument[commando];
} else { _commandIndex = ExternalCommandConfig.Commands.FindIndex(s => s == commando);
textBox_arguments.Text = "\"{0}\""; }
} else
OkButtonState(); {
} textBox_arguments.Text = "\"{0}\"";
}
private void ButtonOkClick(object sender, EventArgs e) { OkButtonState();
string commandName = textBox_name.Text; }
string commandLine = textBox_commandline.Text;
string arguments = textBox_arguments.Text;
if(_commando != null) {
ExternalCommandConfig.Commands[_commandIndex] = commandName;
ExternalCommandConfig.Commandline.Remove(_commando);
ExternalCommandConfig.Commandline.Add(commandName, commandLine);
ExternalCommandConfig.Argument.Remove(_commando);
ExternalCommandConfig.Argument.Add(commandName, arguments);
} else {
ExternalCommandConfig.Commands.Add(commandName);
ExternalCommandConfig.Commandline.Add(commandName, commandLine);
ExternalCommandConfig.Argument.Add(commandName, arguments);
}
}
private void Button3Click(object sender, EventArgs e) { private void ButtonOkClick(object sender, EventArgs e)
var openFileDialog = new OpenFileDialog {
{ string commandName = textBox_name.Text;
Filter = "Executables (*.exe, *.bat, *.com)|*.exe; *.bat; *.com|All files (*)|*", string commandLine = textBox_commandline.Text;
FilterIndex = 1, string arguments = textBox_arguments.Text;
CheckFileExists = true, if (_commando != null)
Multiselect = false {
}; ExternalCommandConfig.Commands[_commandIndex] = commandName;
string initialPath = null; ExternalCommandConfig.Commandline.Remove(_commando);
try ExternalCommandConfig.Commandline.Add(commandName, commandLine);
{ ExternalCommandConfig.Argument.Remove(_commando);
initialPath = Path.GetDirectoryName(textBox_commandline.Text); ExternalCommandConfig.Argument.Add(commandName, arguments);
} }
catch (Exception ex) else
{ {
Log.WarnFormat("Can't get the initial path via {0}", textBox_commandline.Text); ExternalCommandConfig.Commands.Add(commandName);
Log.Warn("Exception: ", ex); ExternalCommandConfig.Commandline.Add(commandName, commandLine);
} ExternalCommandConfig.Argument.Add(commandName, arguments);
if(initialPath != null && Directory.Exists(initialPath)) { }
openFileDialog.InitialDirectory = initialPath; }
} else {
initialPath = Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles);
openFileDialog.InitialDirectory = initialPath;
}
Log.DebugFormat("Starting OpenFileDialog at {0}", initialPath);
if(openFileDialog.ShowDialog() == DialogResult.OK) {
textBox_commandline.Text = openFileDialog.FileName;
}
}
private void OkButtonState() { private void Button3Click(object sender, EventArgs e)
// Assume OK {
buttonOk.Enabled = true; var openFileDialog = new OpenFileDialog
textBox_name.BackColor = Color.White; {
textBox_commandline.BackColor = Color.White; Filter = "Executables (*.exe, *.bat, *.com)|*.exe; *.bat; *.com|All files (*)|*",
textBox_arguments.BackColor = Color.White; FilterIndex = 1,
// Is there a text in the name field CheckFileExists = true,
if(string.IsNullOrEmpty(textBox_name.Text)) { Multiselect = false
buttonOk.Enabled = false; };
} string initialPath = null;
// Check if commandname is unique try
if(_commando == null && !string.IsNullOrEmpty(textBox_name.Text) && ExternalCommandConfig.Commands.Contains(textBox_name.Text)) { {
buttonOk.Enabled = false; initialPath = Path.GetDirectoryName(textBox_commandline.Text);
textBox_name.BackColor = Color.Red; }
} catch (Exception ex)
// Is there a text in the commandline field {
if(string.IsNullOrEmpty(textBox_commandline.Text)) { Log.WarnFormat("Can't get the initial path via {0}", textBox_commandline.Text);
buttonOk.Enabled = false; Log.Warn("Exception: ", ex);
} }
if (!string.IsNullOrEmpty(textBox_commandline.Text)) if (initialPath != null && Directory.Exists(initialPath))
{ {
// Added this to be more flexible, using the Greenshot var format openFileDialog.InitialDirectory = initialPath;
string cmdPath = FilenameHelper.FillVariables(textBox_commandline.Text, true); }
// And also replace the "DOS" Variables else
cmdPath = FilenameHelper.FillCmdVariables(cmdPath, true); {
// Is the command available? initialPath = Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles);
if (!File.Exists(cmdPath)) openFileDialog.InitialDirectory = initialPath;
{ }
buttonOk.Enabled = false;
textBox_commandline.BackColor = Color.Red;
}
}
// Are the arguments in a valid format?
try
{
string arguments = FilenameHelper.FillVariables(textBox_arguments.Text, false);
arguments = FilenameHelper.FillCmdVariables(arguments, false);
ExternalCommandDestination.FormatArguments(arguments, string.Empty); Log.DebugFormat("Starting OpenFileDialog at {0}", initialPath);
} if (openFileDialog.ShowDialog() == DialogResult.OK)
catch {
{ textBox_commandline.Text = openFileDialog.FileName;
buttonOk.Enabled = false; }
textBox_arguments.BackColor = Color.Red; }
}
}
private void textBox_name_TextChanged(object sender, EventArgs e) { private void OkButtonState()
OkButtonState(); {
} // Assume OK
buttonOk.Enabled = true;
textBox_name.BackColor = Color.White;
textBox_commandline.BackColor = Color.White;
textBox_arguments.BackColor = Color.White;
// Is there a text in the name field
if (string.IsNullOrEmpty(textBox_name.Text))
{
buttonOk.Enabled = false;
}
private void textBox_commandline_TextChanged(object sender, EventArgs e) { // Check if commandname is unique
OkButtonState(); if (_commando == null && !string.IsNullOrEmpty(textBox_name.Text) && ExternalCommandConfig.Commands.Contains(textBox_name.Text))
} {
buttonOk.Enabled = false;
textBox_name.BackColor = Color.Red;
}
private void textBox_arguments_TextChanged(object sender, EventArgs e) // Is there a text in the commandline field
{ if (string.IsNullOrEmpty(textBox_commandline.Text))
OkButtonState(); {
} buttonOk.Enabled = false;
}
} if (!string.IsNullOrEmpty(textBox_commandline.Text))
{
// Added this to be more flexible, using the Greenshot var format
string cmdPath = FilenameHelper.FillVariables(textBox_commandline.Text, true);
// And also replace the "DOS" Variables
cmdPath = FilenameHelper.FillCmdVariables(cmdPath, true);
// Is the command available?
if (!File.Exists(cmdPath))
{
buttonOk.Enabled = false;
textBox_commandline.BackColor = Color.Red;
}
}
// Are the arguments in a valid format?
try
{
string arguments = FilenameHelper.FillVariables(textBox_arguments.Text, false);
arguments = FilenameHelper.FillCmdVariables(arguments, false);
ExternalCommandDestination.FormatArguments(arguments, string.Empty);
}
catch
{
buttonOk.Enabled = false;
textBox_arguments.BackColor = Color.Red;
}
}
private void textBox_name_TextChanged(object sender, EventArgs e)
{
OkButtonState();
}
private void textBox_commandline_TextChanged(object sender, EventArgs e)
{
OkButtonState();
}
private void textBox_arguments_TextChanged(object sender, EventArgs e)
{
OkButtonState();
}
}
} }

View file

@ -24,59 +24,67 @@ using Greenshot.Plugin.Flickr.Forms;
using GreenshotPlugin.Core; using GreenshotPlugin.Core;
using GreenshotPlugin.IniFile; using GreenshotPlugin.IniFile;
namespace Greenshot.Plugin.Flickr { namespace Greenshot.Plugin.Flickr
public enum SafetyLevel { {
Safe = 1, public enum SafetyLevel
Moderate = 2, {
Restricted = 3 Safe = 1,
} Moderate = 2,
/// <summary> Restricted = 3
/// Description of FlickrConfiguration. }
/// </summary>
[IniSection("Flickr", Description = "Greenshot Flickr Plugin configuration")]
public class FlickrConfiguration : IniSection {
[IniProperty("flickrIsPublic", Description = "IsPublic.", DefaultValue = "true")]
public bool IsPublic { get; set; }
[IniProperty("flickrIsFamily", Description = "IsFamily.", DefaultValue = "true")] /// <summary>
public bool IsFamily { get; set; } /// Description of FlickrConfiguration.
/// </summary>
[IniSection("Flickr", Description = "Greenshot Flickr Plugin configuration")]
public class FlickrConfiguration : IniSection
{
[IniProperty("flickrIsPublic", Description = "IsPublic.", DefaultValue = "true")]
public bool IsPublic { get; set; }
[IniProperty("flickrIsFriend", Description = "IsFriend.", DefaultValue = "true")] [IniProperty("flickrIsFamily", Description = "IsFamily.", DefaultValue = "true")]
public bool IsFriend { get; set; } public bool IsFamily { get; set; }
[IniProperty("SafetyLevel", Description = "Safety level", DefaultValue = "Safe")] [IniProperty("flickrIsFriend", Description = "IsFriend.", DefaultValue = "true")]
public SafetyLevel SafetyLevel { get; set; } public bool IsFriend { get; set; }
[IniProperty("HiddenFromSearch", Description = "Hidden from search", DefaultValue = "false")] [IniProperty("SafetyLevel", Description = "Safety level", DefaultValue = "Safe")]
public bool HiddenFromSearch { get; set; } public SafetyLevel SafetyLevel { get; set; }
[IniProperty("UploadFormat", Description="What file type to use for uploading", DefaultValue="png")] [IniProperty("HiddenFromSearch", Description = "Hidden from search", DefaultValue = "false")]
public OutputFormat UploadFormat { get; set; } public bool HiddenFromSearch { get; set; }
[IniProperty("UploadJpegQuality", Description="JPEG file save quality in %.", DefaultValue="80")] [IniProperty("UploadFormat", Description = "What file type to use for uploading", DefaultValue = "png")]
public int UploadJpegQuality { get; set; } public OutputFormat UploadFormat { get; set; }
[IniProperty("AfterUploadLinkToClipBoard", Description = "After upload send flickr link to clipboard.", DefaultValue = "true")] [IniProperty("UploadJpegQuality", Description = "JPEG file save quality in %.", DefaultValue = "80")]
public bool AfterUploadLinkToClipBoard { get; set; } public int UploadJpegQuality { get; set; }
[IniProperty("UsePageLink", Description = "Use pagelink instead of direct link on the clipboard", DefaultValue = "False")] [IniProperty("AfterUploadLinkToClipBoard", Description = "After upload send flickr link to clipboard.", DefaultValue = "true")]
public bool UsePageLink { get; set; } public bool AfterUploadLinkToClipBoard { get; set; }
[IniProperty("FlickrToken", Description = "The Flickr token", Encrypted = true, ExcludeIfNull = true)] [IniProperty("UsePageLink", Description = "Use pagelink instead of direct link on the clipboard", DefaultValue = "False")]
public string FlickrToken { get; set; } public bool UsePageLink { get; set; }
[IniProperty("FlickrTokenSecret", Description = "The Flickr token secret", Encrypted = true, ExcludeIfNull = true)]
public string FlickrTokenSecret { get; set; }
/// <summary> [IniProperty("FlickrToken", Description = "The Flickr token", Encrypted = true, ExcludeIfNull = true)]
/// A form for token public string FlickrToken { get; set; }
/// </summary>
/// <returns>bool true if OK was pressed, false if cancel</returns> [IniProperty("FlickrTokenSecret", Description = "The Flickr token secret", Encrypted = true, ExcludeIfNull = true)]
public bool ShowConfigDialog() { public string FlickrTokenSecret { get; set; }
DialogResult result = new SettingsForm().ShowDialog();
if (result == DialogResult.OK) { /// <summary>
return true; /// A form for token
} /// </summary>
return false; /// <returns>bool true if OK was pressed, false if cancel</returns>
} public bool ShowConfigDialog()
} {
DialogResult result = new SettingsForm().ShowDialog();
if (result == DialogResult.OK)
{
return true;
}
return false;
}
}
} }

View file

@ -24,33 +24,42 @@ using System.Drawing;
using GreenshotPlugin.Core; using GreenshotPlugin.Core;
using GreenshotPlugin.Interfaces; using GreenshotPlugin.Interfaces;
namespace Greenshot.Plugin.Flickr { namespace Greenshot.Plugin.Flickr
public class FlickrDestination : AbstractDestination { {
private readonly FlickrPlugin _plugin; public class FlickrDestination : AbstractDestination
public FlickrDestination(FlickrPlugin plugin) { {
_plugin = plugin; private readonly FlickrPlugin _plugin;
}
public override string Designation => "Flickr"; public FlickrDestination(FlickrPlugin plugin)
{
_plugin = plugin;
}
public override string Description => Language.GetString("flickr", LangKey.upload_menu_item); public override string Designation => "Flickr";
public override Image DisplayIcon { public override string Description => Language.GetString("flickr", LangKey.upload_menu_item);
get {
ComponentResourceManager resources = new ComponentResourceManager(typeof(FlickrPlugin));
return (Image)resources.GetObject("flickr");
}
}
public override ExportInformation ExportCapture(bool manuallyInitiated, ISurface surface, ICaptureDetails captureDetails) { public override Image DisplayIcon
ExportInformation exportInformation = new ExportInformation(Designation, Description); {
get
{
ComponentResourceManager resources = new ComponentResourceManager(typeof(FlickrPlugin));
return (Image) resources.GetObject("flickr");
}
}
public override ExportInformation ExportCapture(bool manuallyInitiated, ISurface surface, ICaptureDetails captureDetails)
{
ExportInformation exportInformation = new ExportInformation(Designation, Description);
bool uploaded = _plugin.Upload(captureDetails, surface, out var uploadUrl); bool uploaded = _plugin.Upload(captureDetails, surface, out var uploadUrl);
if (uploaded) { if (uploaded)
exportInformation.ExportMade = true; {
exportInformation.Uri = uploadUrl; exportInformation.ExportMade = true;
} exportInformation.Uri = uploadUrl;
ProcessExport(exportInformation, surface); }
return exportInformation;
} ProcessExport(exportInformation, surface);
} return exportInformation;
}
}
} }

View file

@ -33,99 +33,122 @@ using log4net;
namespace Greenshot.Plugin.Flickr namespace Greenshot.Plugin.Flickr
{ {
/// <summary> /// <summary>
/// This is the Flickr base code /// This is the Flickr base code
/// </summary> /// </summary>
[Plugin("Flickr", true)] [Plugin("Flickr", true)]
public class FlickrPlugin : IGreenshotPlugin { public class FlickrPlugin : IGreenshotPlugin
private static readonly ILog Log = LogManager.GetLogger(typeof(FlickrPlugin)); {
private static FlickrConfiguration _config; private static readonly ILog Log = LogManager.GetLogger(typeof(FlickrPlugin));
private ComponentResourceManager _resources; private static FlickrConfiguration _config;
private ToolStripMenuItem _itemPlugInConfig; private ComponentResourceManager _resources;
private ToolStripMenuItem _itemPlugInConfig;
public void Dispose() { public void Dispose()
Dispose(true); {
GC.SuppressFinalize(this); Dispose(true);
} GC.SuppressFinalize(this);
}
protected void Dispose(bool disposing) { protected void Dispose(bool disposing)
if (!disposing) { {
return; if (!disposing)
} {
if (_itemPlugInConfig == null) { return;
return; }
}
_itemPlugInConfig.Dispose();
_itemPlugInConfig = null;
}
/// <summary> if (_itemPlugInConfig == null)
/// Implementation of the IGreenshotPlugin.Initialize {
/// </summary> return;
public bool Initialize() { }
// Register configuration (don't need the configuration itself)
_config = IniConfig.GetIniSection<FlickrConfiguration>();
_resources = new ComponentResourceManager(typeof(FlickrPlugin));
_itemPlugInConfig = new ToolStripMenuItem _itemPlugInConfig.Dispose();
{ _itemPlugInConfig = null;
Text = Language.GetString("flickr", LangKey.Configure), }
Image = (Image) _resources.GetObject("flickr")
}; /// <summary>
_itemPlugInConfig.Click += ConfigMenuClick; /// Implementation of the IGreenshotPlugin.Initialize
/// </summary>
public bool Initialize()
{
// Register configuration (don't need the configuration itself)
_config = IniConfig.GetIniSection<FlickrConfiguration>();
_resources = new ComponentResourceManager(typeof(FlickrPlugin));
_itemPlugInConfig = new ToolStripMenuItem
{
Text = Language.GetString("flickr", LangKey.Configure),
Image = (Image) _resources.GetObject("flickr")
};
_itemPlugInConfig.Click += ConfigMenuClick;
SimpleServiceProvider.Current.AddService<IDestination>(new FlickrDestination(this)); SimpleServiceProvider.Current.AddService<IDestination>(new FlickrDestination(this));
PluginUtils.AddToContextMenu(_itemPlugInConfig); PluginUtils.AddToContextMenu(_itemPlugInConfig);
Language.LanguageChanged += OnLanguageChanged; Language.LanguageChanged += OnLanguageChanged;
return true; return true;
} }
public void OnLanguageChanged(object sender, EventArgs e) { public void OnLanguageChanged(object sender, EventArgs e)
if (_itemPlugInConfig != null) { {
_itemPlugInConfig.Text = Language.GetString("flickr", LangKey.Configure); if (_itemPlugInConfig != null)
} {
} _itemPlugInConfig.Text = Language.GetString("flickr", LangKey.Configure);
}
}
public void Shutdown() { public void Shutdown()
Log.Debug("Flickr Plugin shutdown."); {
} Log.Debug("Flickr Plugin shutdown.");
}
/// <summary> /// <summary>
/// Implementation of the IPlugin.Configure /// Implementation of the IPlugin.Configure
/// </summary> /// </summary>
public void Configure() { public void Configure()
_config.ShowConfigDialog(); {
} _config.ShowConfigDialog();
}
public void ConfigMenuClick(object sender, EventArgs eventArgs) { public void ConfigMenuClick(object sender, EventArgs eventArgs)
_config.ShowConfigDialog(); {
} _config.ShowConfigDialog();
}
public bool Upload(ICaptureDetails captureDetails, ISurface surface, out string uploadUrl) { public bool Upload(ICaptureDetails captureDetails, ISurface surface, out string uploadUrl)
SurfaceOutputSettings outputSettings = new SurfaceOutputSettings(_config.UploadFormat, _config.UploadJpegQuality, false); {
uploadUrl = null; SurfaceOutputSettings outputSettings = new SurfaceOutputSettings(_config.UploadFormat, _config.UploadJpegQuality, false);
try { uploadUrl = null;
string flickrUrl = null; try
new PleaseWaitForm().ShowAndWait("Flickr", Language.GetString("flickr", LangKey.communication_wait), {
delegate { string flickrUrl = null;
string filename = Path.GetFileName(FilenameHelper.GetFilename(_config.UploadFormat, captureDetails)); new PleaseWaitForm().ShowAndWait("Flickr", Language.GetString("flickr", LangKey.communication_wait),
flickrUrl = FlickrUtils.UploadToFlickr(surface, outputSettings, captureDetails.Title, filename); delegate
} {
); string filename = Path.GetFileName(FilenameHelper.GetFilename(_config.UploadFormat, captureDetails));
flickrUrl = FlickrUtils.UploadToFlickr(surface, outputSettings, captureDetails.Title, filename);
}
);
if (flickrUrl == null) { if (flickrUrl == null)
return false; {
} return false;
uploadUrl = flickrUrl; }
if (_config.AfterUploadLinkToClipBoard) { uploadUrl = flickrUrl;
ClipboardHelper.SetClipboardData(flickrUrl);
} if (_config.AfterUploadLinkToClipBoard)
return true; {
} catch (Exception e) { ClipboardHelper.SetClipboardData(flickrUrl);
Log.Error("Error uploading.", e); }
MessageBox.Show(Language.GetString("flickr", LangKey.upload_failure) + " " + e.Message);
} return true;
return false; }
} catch (Exception e)
} {
Log.Error("Error uploading.", e);
MessageBox.Show(Language.GetString("flickr", LangKey.upload_failure) + " " + e.Message);
}
return false;
}
}
} }

View file

@ -30,140 +30,203 @@ using GreenshotPlugin.Interfaces;
using GreenshotPlugin.Interfaces.Plugin; using GreenshotPlugin.Interfaces.Plugin;
using log4net; using log4net;
namespace Greenshot.Plugin.Flickr { namespace Greenshot.Plugin.Flickr
/// <summary> {
/// Description of FlickrUtils. /// <summary>
/// </summary> /// Description of FlickrUtils.
public static class FlickrUtils { /// </summary>
private static readonly ILog LOG = LogManager.GetLogger(typeof(FlickrUtils)); public static class FlickrUtils
private static readonly FlickrConfiguration config = IniConfig.GetIniSection<FlickrConfiguration>(); {
private const string FLICKR_API_BASE_URL = "https://api.flickr.com/services/"; private static readonly ILog LOG = LogManager.GetLogger(typeof(FlickrUtils));
private const string FLICKR_UPLOAD_URL = FLICKR_API_BASE_URL + "upload/"; private static readonly FlickrConfiguration config = IniConfig.GetIniSection<FlickrConfiguration>();
// OAUTH private const string FLICKR_API_BASE_URL = "https://api.flickr.com/services/";
private const string FLICKR_OAUTH_BASE_URL = FLICKR_API_BASE_URL + "oauth/";
private const string FLICKR_ACCESS_TOKEN_URL = FLICKR_OAUTH_BASE_URL + "access_token";
private const string FLICKR_AUTHORIZE_URL = FLICKR_OAUTH_BASE_URL + "authorize";
private const string FLICKR_REQUEST_TOKEN_URL = FLICKR_OAUTH_BASE_URL + "request_token";
private const string FLICKR_FARM_URL = "https://farm{0}.staticflickr.com/{1}/{2}_{3}_o.{4}";
// REST
private const string FLICKR_REST_URL = FLICKR_API_BASE_URL + "rest/";
private const string FLICKR_GET_INFO_URL = FLICKR_REST_URL + "?method=flickr.photos.getInfo";
/// <summary> private const string FLICKR_UPLOAD_URL = FLICKR_API_BASE_URL + "upload/";
/// Do the actual upload to Flickr
/// For more details on the available parameters, see: http://flickrnet.codeplex.com
/// </summary>
/// <param name="surfaceToUpload"></param>
/// <param name="outputSettings"></param>
/// <param name="title"></param>
/// <param name="filename"></param>
/// <returns>url to image</returns>
public static string UploadToFlickr(ISurface surfaceToUpload, SurfaceOutputSettings outputSettings, string title, string filename) {
var oAuth = new OAuthSession(FlickrCredentials.ConsumerKey, FlickrCredentials.ConsumerSecret)
{
BrowserSize = new Size(520, 800),
CheckVerifier = false,
AccessTokenUrl = FLICKR_ACCESS_TOKEN_URL,
AuthorizeUrl = FLICKR_AUTHORIZE_URL,
RequestTokenUrl = FLICKR_REQUEST_TOKEN_URL,
LoginTitle = "Flickr authorization",
Token = config.FlickrToken,
TokenSecret = config.FlickrTokenSecret
};
if (string.IsNullOrEmpty(oAuth.Token)) {
if (!oAuth.Authorize()) {
return null;
}
if (!string.IsNullOrEmpty(oAuth.Token)) {
config.FlickrToken = oAuth.Token;
}
if (!string.IsNullOrEmpty(oAuth.TokenSecret)) {
config.FlickrTokenSecret = oAuth.TokenSecret;
}
IniConfig.Save();
}
try {
IDictionary<string, object> signedParameters = new Dictionary<string, object>
{
{ "content_type", "2" }, // Screenshot
{ "tags", "Greenshot" },
{ "is_public", config.IsPublic ? "1" : "0" },
{ "is_friend", config.IsFriend ? "1" : "0" },
{ "is_family", config.IsFamily ? "1" : "0" },
{ "safety_level", $"{(int)config.SafetyLevel}" },
{ "hidden", config.HiddenFromSearch ? "1" : "2" }
};
IDictionary<string, object> otherParameters = new Dictionary<string, object>
{
{ "photo", new SurfaceContainer(surfaceToUpload, outputSettings, filename) }
};
string response = oAuth.MakeOAuthRequest(HTTPMethod.POST, FLICKR_UPLOAD_URL, signedParameters, otherParameters, null);
string photoId = GetPhotoId(response);
// Get Photo Info // OAUTH
signedParameters = new Dictionary<string, object> { { "photo_id", photoId } }; private const string FLICKR_OAUTH_BASE_URL = FLICKR_API_BASE_URL + "oauth/";
string photoInfo = oAuth.MakeOAuthRequest(HTTPMethod.POST, FLICKR_GET_INFO_URL, signedParameters, null, null); private const string FLICKR_ACCESS_TOKEN_URL = FLICKR_OAUTH_BASE_URL + "access_token";
return GetUrl(photoInfo); private const string FLICKR_AUTHORIZE_URL = FLICKR_OAUTH_BASE_URL + "authorize";
} catch (Exception ex) { private const string FLICKR_REQUEST_TOKEN_URL = FLICKR_OAUTH_BASE_URL + "request_token";
LOG.Error("Upload error: ", ex);
throw;
} finally {
if (!string.IsNullOrEmpty(oAuth.Token)) {
config.FlickrToken = oAuth.Token;
}
if (!string.IsNullOrEmpty(oAuth.TokenSecret)) {
config.FlickrTokenSecret = oAuth.TokenSecret;
}
}
}
private static string GetUrl(string response) { private const string FLICKR_FARM_URL = "https://farm{0}.staticflickr.com/{1}/{2}_{3}_o.{4}";
try {
XmlDocument doc = new XmlDocument();
doc.LoadXml(response);
if (config.UsePageLink) {
XmlNodeList nodes = doc.GetElementsByTagName("url");
if (nodes.Count > 0) {
var xmlNode = nodes.Item(0);
if (xmlNode != null) {
return xmlNode.InnerText;
}
}
} else {
XmlNodeList nodes = doc.GetElementsByTagName("photo");
if (nodes.Count > 0) {
var item = nodes.Item(0);
if (item?.Attributes != null) {
string farmId = item.Attributes["farm"].Value;
string serverId = item.Attributes["server"].Value;
string photoId = item.Attributes["id"].Value;
string originalsecret = item.Attributes["originalsecret"].Value;
string originalFormat = item.Attributes["originalformat"].Value;
return string.Format(FLICKR_FARM_URL, farmId, serverId, photoId, originalsecret, originalFormat);
}
}
}
} catch (Exception ex) {
LOG.Error("Error parsing Flickr Response.", ex);
}
return null;
}
private static string GetPhotoId(string response) { // REST
try { private const string FLICKR_REST_URL = FLICKR_API_BASE_URL + "rest/";
XmlDocument doc = new XmlDocument(); private const string FLICKR_GET_INFO_URL = FLICKR_REST_URL + "?method=flickr.photos.getInfo";
doc.LoadXml(response);
XmlNodeList nodes = doc.GetElementsByTagName("photoid"); /// <summary>
if (nodes.Count > 0) { /// Do the actual upload to Flickr
var xmlNode = nodes.Item(0); /// For more details on the available parameters, see: http://flickrnet.codeplex.com
if (xmlNode != null) { /// </summary>
return xmlNode.InnerText; /// <param name="surfaceToUpload"></param>
} /// <param name="outputSettings"></param>
} /// <param name="title"></param>
} catch (Exception ex) { /// <param name="filename"></param>
LOG.Error("Error parsing Flickr Response.", ex); /// <returns>url to image</returns>
} public static string UploadToFlickr(ISurface surfaceToUpload, SurfaceOutputSettings outputSettings, string title, string filename)
return null; {
} var oAuth = new OAuthSession(FlickrCredentials.ConsumerKey, FlickrCredentials.ConsumerSecret)
} {
BrowserSize = new Size(520, 800),
CheckVerifier = false,
AccessTokenUrl = FLICKR_ACCESS_TOKEN_URL,
AuthorizeUrl = FLICKR_AUTHORIZE_URL,
RequestTokenUrl = FLICKR_REQUEST_TOKEN_URL,
LoginTitle = "Flickr authorization",
Token = config.FlickrToken,
TokenSecret = config.FlickrTokenSecret
};
if (string.IsNullOrEmpty(oAuth.Token))
{
if (!oAuth.Authorize())
{
return null;
}
if (!string.IsNullOrEmpty(oAuth.Token))
{
config.FlickrToken = oAuth.Token;
}
if (!string.IsNullOrEmpty(oAuth.TokenSecret))
{
config.FlickrTokenSecret = oAuth.TokenSecret;
}
IniConfig.Save();
}
try
{
IDictionary<string, object> signedParameters = new Dictionary<string, object>
{
{
"content_type", "2"
}, // Screenshot
{
"tags", "Greenshot"
},
{
"is_public", config.IsPublic ? "1" : "0"
},
{
"is_friend", config.IsFriend ? "1" : "0"
},
{
"is_family", config.IsFamily ? "1" : "0"
},
{
"safety_level", $"{(int) config.SafetyLevel}"
},
{
"hidden", config.HiddenFromSearch ? "1" : "2"
}
};
IDictionary<string, object> otherParameters = new Dictionary<string, object>
{
{
"photo", new SurfaceContainer(surfaceToUpload, outputSettings, filename)
}
};
string response = oAuth.MakeOAuthRequest(HTTPMethod.POST, FLICKR_UPLOAD_URL, signedParameters, otherParameters, null);
string photoId = GetPhotoId(response);
// Get Photo Info
signedParameters = new Dictionary<string, object>
{
{
"photo_id", photoId
}
};
string photoInfo = oAuth.MakeOAuthRequest(HTTPMethod.POST, FLICKR_GET_INFO_URL, signedParameters, null, null);
return GetUrl(photoInfo);
}
catch (Exception ex)
{
LOG.Error("Upload error: ", ex);
throw;
}
finally
{
if (!string.IsNullOrEmpty(oAuth.Token))
{
config.FlickrToken = oAuth.Token;
}
if (!string.IsNullOrEmpty(oAuth.TokenSecret))
{
config.FlickrTokenSecret = oAuth.TokenSecret;
}
}
}
private static string GetUrl(string response)
{
try
{
XmlDocument doc = new XmlDocument();
doc.LoadXml(response);
if (config.UsePageLink)
{
XmlNodeList nodes = doc.GetElementsByTagName("url");
if (nodes.Count > 0)
{
var xmlNode = nodes.Item(0);
if (xmlNode != null)
{
return xmlNode.InnerText;
}
}
}
else
{
XmlNodeList nodes = doc.GetElementsByTagName("photo");
if (nodes.Count > 0)
{
var item = nodes.Item(0);
if (item?.Attributes != null)
{
string farmId = item.Attributes["farm"].Value;
string serverId = item.Attributes["server"].Value;
string photoId = item.Attributes["id"].Value;
string originalsecret = item.Attributes["originalsecret"].Value;
string originalFormat = item.Attributes["originalformat"].Value;
return string.Format(FLICKR_FARM_URL, farmId, serverId, photoId, originalsecret, originalFormat);
}
}
}
}
catch (Exception ex)
{
LOG.Error("Error parsing Flickr Response.", ex);
}
return null;
}
private static string GetPhotoId(string response)
{
try
{
XmlDocument doc = new XmlDocument();
doc.LoadXml(response);
XmlNodeList nodes = doc.GetElementsByTagName("photoid");
if (nodes.Count > 0)
{
var xmlNode = nodes.Item(0);
if (xmlNode != null)
{
return xmlNode.InnerText;
}
}
}
catch (Exception ex)
{
LOG.Error("Error parsing Flickr Response.", ex);
}
return null;
}
}
} }

View file

@ -21,7 +21,9 @@
using GreenshotPlugin.Controls; using GreenshotPlugin.Controls;
namespace Greenshot.Plugin.Flickr.Forms { namespace Greenshot.Plugin.Flickr.Forms
public class FlickrForm : GreenshotForm { {
} public class FlickrForm : GreenshotForm
{
}
} }

View file

@ -19,19 +19,21 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>. * along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
namespace Greenshot.Plugin.Flickr.Forms { namespace Greenshot.Plugin.Flickr.Forms
/// <summary> {
/// Description of PasswordRequestForm. /// <summary>
/// </summary> /// Description of PasswordRequestForm.
public partial class SettingsForm : FlickrForm { /// </summary>
public SettingsForm() { public partial class SettingsForm : FlickrForm
// {
// The InitializeComponent() call is required for Windows Forms designer support. public SettingsForm()
// {
InitializeComponent(); //
CancelButton = buttonCancel; // The InitializeComponent() call is required for Windows Forms designer support.
AcceptButton = buttonOK; //
} InitializeComponent();
CancelButton = buttonCancel;
} AcceptButton = buttonOK;
}
}
} }

View file

@ -18,11 +18,14 @@
* You should have received a copy of the GNU General Public License * You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>. * along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
namespace Greenshot.Plugin.Flickr {
public enum LangKey { namespace Greenshot.Plugin.Flickr
upload_menu_item, {
upload_failure, public enum LangKey
communication_wait, {
Configure upload_menu_item,
upload_failure,
communication_wait,
Configure
} }
} }

View file

@ -20,7 +20,9 @@
using GreenshotPlugin.Controls; using GreenshotPlugin.Controls;
namespace Greenshot.Plugin.GooglePhotos.Forms { namespace Greenshot.Plugin.GooglePhotos.Forms
public class GooglePhotosForm : GreenshotForm { {
} public class GooglePhotosForm : GreenshotForm
{
}
} }

View file

@ -18,21 +18,21 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>. * along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
namespace Greenshot.Plugin.GooglePhotos.Forms { namespace Greenshot.Plugin.GooglePhotos.Forms
/// <summary> {
/// Description of PasswordRequestForm. /// <summary>
/// </summary> /// Description of PasswordRequestForm.
public partial class SettingsForm : GooglePhotosForm { /// </summary>
public partial class SettingsForm : GooglePhotosForm
public SettingsForm() {
{ public SettingsForm()
// {
// The InitializeComponent() call is required for Windows Forms designer support. //
// // The InitializeComponent() call is required for Windows Forms designer support.
InitializeComponent(); //
CancelButton = buttonCancel; InitializeComponent();
AcceptButton = buttonOK; CancelButton = buttonCancel;
} AcceptButton = buttonOK;
}
} }
} }

View file

@ -24,71 +24,58 @@ using Greenshot.Plugin.GooglePhotos.Forms;
using GreenshotPlugin.Core; using GreenshotPlugin.Core;
using GreenshotPlugin.IniFile; using GreenshotPlugin.IniFile;
namespace Greenshot.Plugin.GooglePhotos { namespace Greenshot.Plugin.GooglePhotos
/// <summary> {
/// Description of GooglePhotosConfiguration. /// <summary>
/// </summary> /// Description of GooglePhotosConfiguration.
[IniSection("GooglePhotos", Description = "Greenshot GooglePhotos Plugin configuration")] /// </summary>
public class GooglePhotosConfiguration : IniSection { [IniSection("GooglePhotos", Description = "Greenshot GooglePhotos Plugin configuration")]
[IniProperty("UploadFormat", Description="What file type to use for uploading", DefaultValue="png")] public class GooglePhotosConfiguration : IniSection
public OutputFormat UploadFormat { get; set; } {
[IniProperty("UploadFormat", Description = "What file type to use for uploading", DefaultValue = "png")]
public OutputFormat UploadFormat { get; set; }
[IniProperty("UploadJpegQuality", Description="JPEG file save quality in %.", DefaultValue="80")] [IniProperty("UploadJpegQuality", Description = "JPEG file save quality in %.", DefaultValue = "80")]
public int UploadJpegQuality { get; set; } public int UploadJpegQuality { get; set; }
[IniProperty("AfterUploadLinkToClipBoard", Description = "After upload send GooglePhotos link to clipboard.", DefaultValue = "true")] [IniProperty("AfterUploadLinkToClipBoard", Description = "After upload send GooglePhotos link to clipboard.", DefaultValue = "true")]
public bool AfterUploadLinkToClipBoard { get; set; } public bool AfterUploadLinkToClipBoard { get; set; }
[IniProperty("AddFilename", Description = "Is the filename passed on to GooglePhotos", DefaultValue = "False")]
public bool AddFilename {
get;
set;
}
[IniProperty("UploadUser", Description = "The GooglePhotos user to upload to", DefaultValue = "default")] [IniProperty("AddFilename", Description = "Is the filename passed on to GooglePhotos", DefaultValue = "False")]
public string UploadUser { public bool AddFilename { get; set; }
get;
set;
}
[IniProperty("UploadAlbum", Description = "The GooglePhotos album to upload to", DefaultValue = "default")] [IniProperty("UploadUser", Description = "The GooglePhotos user to upload to", DefaultValue = "default")]
public string UploadAlbum { public string UploadUser { get; set; }
get;
set;
}
[IniProperty("RefreshToken", Description = "GooglePhotos authorization refresh Token", Encrypted = true)] [IniProperty("UploadAlbum", Description = "The GooglePhotos album to upload to", DefaultValue = "default")]
public string RefreshToken { public string UploadAlbum { get; set; }
get;
set;
}
/// <summary> [IniProperty("RefreshToken", Description = "GooglePhotos authorization refresh Token", Encrypted = true)]
/// Not stored public string RefreshToken { get; set; }
/// </summary>
public string AccessToken {
get;
set;
}
/// <summary> /// <summary>
/// Not stored /// Not stored
/// </summary> /// </summary>
public DateTimeOffset AccessTokenExpires { public string AccessToken { get; set; }
get;
set;
}
/// <summary> /// <summary>
/// A form for token /// Not stored
/// </summary> /// </summary>
/// <returns>bool true if OK was pressed, false if cancel</returns> public DateTimeOffset AccessTokenExpires { get; set; }
public bool ShowConfigDialog() {
DialogResult result = new SettingsForm().ShowDialog();
if (result == DialogResult.OK) {
return true;
}
return false;
}
} /// <summary>
/// A form for token
/// </summary>
/// <returns>bool true if OK was pressed, false if cancel</returns>
public bool ShowConfigDialog()
{
DialogResult result = new SettingsForm().ShowDialog();
if (result == DialogResult.OK)
{
return true;
}
return false;
}
}
} }

View file

@ -23,33 +23,42 @@ using System.Drawing;
using GreenshotPlugin.Core; using GreenshotPlugin.Core;
using GreenshotPlugin.Interfaces; using GreenshotPlugin.Interfaces;
namespace Greenshot.Plugin.GooglePhotos { namespace Greenshot.Plugin.GooglePhotos
public class GooglePhotosDestination : AbstractDestination { {
private readonly GooglePhotosPlugin _plugin; public class GooglePhotosDestination : AbstractDestination
public GooglePhotosDestination(GooglePhotosPlugin plugin) { {
_plugin = plugin; private readonly GooglePhotosPlugin _plugin;
}
public override string Designation => "GooglePhotos"; public GooglePhotosDestination(GooglePhotosPlugin plugin)
{
_plugin = plugin;
}
public override string Description => Language.GetString("googlephotos", LangKey.upload_menu_item); public override string Designation => "GooglePhotos";
public override Image DisplayIcon { public override string Description => Language.GetString("googlephotos", LangKey.upload_menu_item);
get {
ComponentResourceManager resources = new ComponentResourceManager(typeof(GooglePhotosPlugin));
return (Image)resources.GetObject("GooglePhotos");
}
}
public override ExportInformation ExportCapture(bool manuallyInitiated, ISurface surface, ICaptureDetails captureDetails) { public override Image DisplayIcon
ExportInformation exportInformation = new ExportInformation(Designation, Description); {
get
{
ComponentResourceManager resources = new ComponentResourceManager(typeof(GooglePhotosPlugin));
return (Image) resources.GetObject("GooglePhotos");
}
}
public override ExportInformation ExportCapture(bool manuallyInitiated, ISurface surface, ICaptureDetails captureDetails)
{
ExportInformation exportInformation = new ExportInformation(Designation, Description);
bool uploaded = _plugin.Upload(captureDetails, surface, out var uploadUrl); bool uploaded = _plugin.Upload(captureDetails, surface, out var uploadUrl);
if (uploaded) { if (uploaded)
exportInformation.ExportMade = true; {
exportInformation.Uri = uploadUrl; exportInformation.ExportMade = true;
} exportInformation.Uri = uploadUrl;
ProcessExport(exportInformation, surface); }
return exportInformation;
} ProcessExport(exportInformation, surface);
} return exportInformation;
}
}
} }

View file

@ -29,97 +29,113 @@ using GreenshotPlugin.IniFile;
using GreenshotPlugin.Interfaces; using GreenshotPlugin.Interfaces;
using GreenshotPlugin.Interfaces.Plugin; using GreenshotPlugin.Interfaces.Plugin;
namespace Greenshot.Plugin.GooglePhotos { namespace Greenshot.Plugin.GooglePhotos
/// <summary> {
/// This is the GooglePhotos base code /// <summary>
/// </summary> /// This is the GooglePhotos base code
[Plugin("GooglePhotos", true)] /// </summary>
public class GooglePhotosPlugin : IGreenshotPlugin { [Plugin("GooglePhotos", true)]
private static readonly log4net.ILog Log = log4net.LogManager.GetLogger(typeof(GooglePhotosPlugin)); public class GooglePhotosPlugin : IGreenshotPlugin
private static GooglePhotosConfiguration _config; {
private ComponentResourceManager _resources; private static readonly log4net.ILog Log = log4net.LogManager.GetLogger(typeof(GooglePhotosPlugin));
private ToolStripMenuItem _itemPlugInRoot; private static GooglePhotosConfiguration _config;
private ComponentResourceManager _resources;
private ToolStripMenuItem _itemPlugInRoot;
public void Dispose() { public void Dispose()
Dispose(true); {
GC.SuppressFinalize(this); Dispose(true);
} GC.SuppressFinalize(this);
}
private void Dispose(bool disposing) private void Dispose(bool disposing)
{ {
if (!disposing) return; if (!disposing) return;
if (_itemPlugInRoot == null) return; if (_itemPlugInRoot == null) return;
_itemPlugInRoot.Dispose(); _itemPlugInRoot.Dispose();
_itemPlugInRoot = null; _itemPlugInRoot = null;
} }
/// <summary> /// <summary>
/// Implementation of the IGreenshotPlugin.Initialize /// Implementation of the IGreenshotPlugin.Initialize
/// </summary> /// </summary>
public bool Initialize() { public bool Initialize()
SimpleServiceProvider.Current.AddService<IDestination>(new GooglePhotosDestination(this)); {
SimpleServiceProvider.Current.AddService<IDestination>(new GooglePhotosDestination(this));
// Get configuration // Get configuration
_config = IniConfig.GetIniSection<GooglePhotosConfiguration>(); _config = IniConfig.GetIniSection<GooglePhotosConfiguration>();
_resources = new ComponentResourceManager(typeof(GooglePhotosPlugin)); _resources = new ComponentResourceManager(typeof(GooglePhotosPlugin));
_itemPlugInRoot = new ToolStripMenuItem _itemPlugInRoot = new ToolStripMenuItem
{ {
Text = Language.GetString("googlephotos", LangKey.Configure), Text = Language.GetString("googlephotos", LangKey.Configure),
Image = (Image) _resources.GetObject("GooglePhotos") Image = (Image) _resources.GetObject("GooglePhotos")
}; };
_itemPlugInRoot.Click += ConfigMenuClick; _itemPlugInRoot.Click += ConfigMenuClick;
PluginUtils.AddToContextMenu(_itemPlugInRoot); PluginUtils.AddToContextMenu(_itemPlugInRoot);
Language.LanguageChanged += OnLanguageChanged; Language.LanguageChanged += OnLanguageChanged;
return true; return true;
} }
public void OnLanguageChanged(object sender, EventArgs e) { public void OnLanguageChanged(object sender, EventArgs e)
if (_itemPlugInRoot != null) { {
_itemPlugInRoot.Text = Language.GetString("googlephotos", LangKey.Configure); if (_itemPlugInRoot != null)
} {
} _itemPlugInRoot.Text = Language.GetString("googlephotos", LangKey.Configure);
}
}
public void Shutdown() { public void Shutdown()
Log.Debug("GooglePhotos Plugin shutdown."); {
Language.LanguageChanged -= OnLanguageChanged; Log.Debug("GooglePhotos Plugin shutdown.");
//host.OnImageEditorOpen -= new OnImageEditorOpenHandler(ImageEditorOpened); Language.LanguageChanged -= OnLanguageChanged;
} //host.OnImageEditorOpen -= new OnImageEditorOpenHandler(ImageEditorOpened);
}
/// <summary> /// <summary>
/// Implementation of the IPlugin.Configure /// Implementation of the IPlugin.Configure
/// </summary> /// </summary>
public void Configure() { public void Configure()
_config.ShowConfigDialog(); {
} _config.ShowConfigDialog();
}
public void ConfigMenuClick(object sender, EventArgs eventArgs) { public void ConfigMenuClick(object sender, EventArgs eventArgs)
Configure(); {
} Configure();
}
public bool Upload(ICaptureDetails captureDetails, ISurface surfaceToUpload, out string uploadUrl) { public bool Upload(ICaptureDetails captureDetails, ISurface surfaceToUpload, out string uploadUrl)
SurfaceOutputSettings outputSettings = new SurfaceOutputSettings(_config.UploadFormat, _config.UploadJpegQuality); {
try { SurfaceOutputSettings outputSettings = new SurfaceOutputSettings(_config.UploadFormat, _config.UploadJpegQuality);
string url = null; try
new PleaseWaitForm().ShowAndWait("GooglePhotos", Language.GetString("googlephotos", LangKey.communication_wait), {
delegate string url = null;
{ new PleaseWaitForm().ShowAndWait("GooglePhotos", Language.GetString("googlephotos", LangKey.communication_wait),
string filename = Path.GetFileName(FilenameHelper.GetFilename(_config.UploadFormat, captureDetails)); delegate
url = GooglePhotosUtils.UploadToGooglePhotos(surfaceToUpload, outputSettings, captureDetails.Title, filename); {
} string filename = Path.GetFileName(FilenameHelper.GetFilename(_config.UploadFormat, captureDetails));
); url = GooglePhotosUtils.UploadToGooglePhotos(surfaceToUpload, outputSettings, captureDetails.Title, filename);
uploadUrl = url; }
);
uploadUrl = url;
if (uploadUrl != null && _config.AfterUploadLinkToClipBoard) { if (uploadUrl != null && _config.AfterUploadLinkToClipBoard)
ClipboardHelper.SetClipboardData(uploadUrl); {
} ClipboardHelper.SetClipboardData(uploadUrl);
return true; }
} catch (Exception e) {
Log.Error("Error uploading.", e); return true;
MessageBox.Show(Language.GetString("googlephotos", LangKey.upload_failure) + " " + e.Message); }
} catch (Exception e)
uploadUrl = null; {
return false; Log.Error("Error uploading.", e);
} MessageBox.Show(Language.GetString("googlephotos", LangKey.upload_failure) + " " + e.Message);
} }
uploadUrl = null;
return false;
}
}
} }

View file

@ -26,95 +26,118 @@ using GreenshotPlugin.IniFile;
using GreenshotPlugin.Interfaces; using GreenshotPlugin.Interfaces;
using GreenshotPlugin.Interfaces.Plugin; using GreenshotPlugin.Interfaces.Plugin;
namespace Greenshot.Plugin.GooglePhotos { namespace Greenshot.Plugin.GooglePhotos
/// <summary> {
/// Description of GooglePhotosUtils. /// <summary>
/// </summary> /// Description of GooglePhotosUtils.
public static class GooglePhotosUtils { /// </summary>
private const string GooglePhotosScope = "https://picasaweb.google.com/data/"; public static class GooglePhotosUtils
private static readonly log4net.ILog Log = log4net.LogManager.GetLogger(typeof(GooglePhotosUtils)); {
private static readonly GooglePhotosConfiguration Config = IniConfig.GetIniSection<GooglePhotosConfiguration>(); private const string GooglePhotosScope = "https://picasaweb.google.com/data/";
private const string AuthUrl = "https://accounts.google.com/o/oauth2/auth?response_type=code&client_id={ClientId}&redirect_uri={RedirectUrl}&state={State}&scope=" + GooglePhotosScope; private static readonly log4net.ILog Log = log4net.LogManager.GetLogger(typeof(GooglePhotosUtils));
private const string TokenUrl = "https://www.googleapis.com/oauth2/v3/token"; private static readonly GooglePhotosConfiguration Config = IniConfig.GetIniSection<GooglePhotosConfiguration>();
private const string UploadUrl = "https://picasaweb.google.com/data/feed/api/user/{0}/albumid/{1}";
/// <summary> private const string AuthUrl = "https://accounts.google.com/o/oauth2/auth?response_type=code&client_id={ClientId}&redirect_uri={RedirectUrl}&state={State}&scope=" +
/// Do the actual upload to GooglePhotos GooglePhotosScope;
/// </summary>
/// <param name="surfaceToUpload">Image to upload</param>
/// <param name="outputSettings">SurfaceOutputSettings</param>
/// <param name="title">string</param>
/// <param name="filename">string</param>
/// <returns>GooglePhotosResponse</returns>
public static string UploadToGooglePhotos(ISurface surfaceToUpload, SurfaceOutputSettings outputSettings, string title, string filename) {
// Fill the OAuth2Settings
var settings = new OAuth2Settings
{
AuthUrlPattern = AuthUrl,
TokenUrl = TokenUrl,
CloudServiceName = "GooglePhotos",
ClientId = GooglePhotosCredentials.ClientId,
ClientSecret = GooglePhotosCredentials.ClientSecret,
AuthorizeMode = OAuth2AuthorizeMode.JsonReceiver,
RefreshToken = Config.RefreshToken,
AccessToken = Config.AccessToken,
AccessTokenExpires = Config.AccessTokenExpires
};
// Copy the settings from the config, which is kept in memory and on the disk private const string TokenUrl = "https://www.googleapis.com/oauth2/v3/token";
private const string UploadUrl = "https://picasaweb.google.com/data/feed/api/user/{0}/albumid/{1}";
try { /// <summary>
var webRequest = OAuth2Helper.CreateOAuth2WebRequest(HTTPMethod.POST, string.Format(UploadUrl, Config.UploadUser, Config.UploadAlbum), settings); /// Do the actual upload to GooglePhotos
if (Config.AddFilename) { /// </summary>
webRequest.Headers.Add("Slug", NetworkHelper.EscapeDataString(filename)); /// <param name="surfaceToUpload">Image to upload</param>
} /// <param name="outputSettings">SurfaceOutputSettings</param>
SurfaceContainer container = new SurfaceContainer(surfaceToUpload, outputSettings, filename); /// <param name="title">string</param>
container.Upload(webRequest); /// <param name="filename">string</param>
/// <returns>GooglePhotosResponse</returns>
public static string UploadToGooglePhotos(ISurface surfaceToUpload, SurfaceOutputSettings outputSettings, string title, string filename)
{
// Fill the OAuth2Settings
var settings = new OAuth2Settings
{
AuthUrlPattern = AuthUrl,
TokenUrl = TokenUrl,
CloudServiceName = "GooglePhotos",
ClientId = GooglePhotosCredentials.ClientId,
ClientSecret = GooglePhotosCredentials.ClientSecret,
AuthorizeMode = OAuth2AuthorizeMode.JsonReceiver,
RefreshToken = Config.RefreshToken,
AccessToken = Config.AccessToken,
AccessTokenExpires = Config.AccessTokenExpires
};
string response = NetworkHelper.GetResponseAsString(webRequest); // Copy the settings from the config, which is kept in memory and on the disk
return ParseResponse(response); try
} finally { {
// Copy the settings back to the config, so they are stored. var webRequest = OAuth2Helper.CreateOAuth2WebRequest(HTTPMethod.POST, string.Format(UploadUrl, Config.UploadUser, Config.UploadAlbum), settings);
Config.RefreshToken = settings.RefreshToken; if (Config.AddFilename)
Config.AccessToken = settings.AccessToken; {
Config.AccessTokenExpires = settings.AccessTokenExpires; webRequest.Headers.Add("Slug", NetworkHelper.EscapeDataString(filename));
Config.IsDirty = true; }
IniConfig.Save();
}
}
/// <summary> SurfaceContainer container = new SurfaceContainer(surfaceToUpload, outputSettings, filename);
/// Parse the upload URL from the response container.Upload(webRequest);
/// </summary>
/// <param name="response"></param> string response = NetworkHelper.GetResponseAsString(webRequest);
/// <returns></returns>
public static string ParseResponse(string response) { return ParseResponse(response);
if (response == null) { }
return null; finally
} {
try { // Copy the settings back to the config, so they are stored.
XmlDocument doc = new XmlDocument(); Config.RefreshToken = settings.RefreshToken;
doc.LoadXml(response); Config.AccessToken = settings.AccessToken;
XmlNodeList nodes = doc.GetElementsByTagName("link", "*"); Config.AccessTokenExpires = settings.AccessTokenExpires;
if(nodes.Count > 0) { Config.IsDirty = true;
string url = null; IniConfig.Save();
foreach(XmlNode node in nodes) { }
if (node.Attributes != null) { }
url = node.Attributes["href"].Value;
string rel = node.Attributes["rel"].Value; /// <summary>
// Pictures with rel="http://schemas.google.com/photos/2007#canonical" are the direct link /// Parse the upload URL from the response
if (rel != null && rel.EndsWith("canonical")) { /// </summary>
break; /// <param name="response"></param>
} /// <returns></returns>
} public static string ParseResponse(string response)
} {
return url; if (response == null)
} {
} catch(Exception e) { return null;
Log.ErrorFormat("Could not parse GooglePhotos response due to error {0}, response was: {1}", e.Message, response); }
}
return null; try
} {
} XmlDocument doc = new XmlDocument();
doc.LoadXml(response);
XmlNodeList nodes = doc.GetElementsByTagName("link", "*");
if (nodes.Count > 0)
{
string url = null;
foreach (XmlNode node in nodes)
{
if (node.Attributes != null)
{
url = node.Attributes["href"].Value;
string rel = node.Attributes["rel"].Value;
// Pictures with rel="http://schemas.google.com/photos/2007#canonical" are the direct link
if (rel != null && rel.EndsWith("canonical"))
{
break;
}
}
}
return url;
}
}
catch (Exception e)
{
Log.ErrorFormat("Could not parse GooglePhotos response due to error {0}, response was: {1}", e.Message, response);
}
return null;
}
}
} }

View file

@ -18,12 +18,13 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>. * along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
namespace Greenshot.Plugin.GooglePhotos { namespace Greenshot.Plugin.GooglePhotos
public enum LangKey {
{ public enum LangKey
upload_menu_item, {
upload_failure, upload_menu_item,
communication_wait, upload_failure,
Configure communication_wait,
} Configure
}
} }

View file

@ -21,10 +21,12 @@
using GreenshotPlugin.Controls; using GreenshotPlugin.Controls;
namespace Greenshot.Plugin.Imgur.Forms { namespace Greenshot.Plugin.Imgur.Forms
/// <summary> {
/// This class is needed for design-time resolving of the language files /// <summary>
/// </summary> /// This class is needed for design-time resolving of the language files
public class ImgurForm : GreenshotForm { /// </summary>
} public class ImgurForm : GreenshotForm
{
}
} }

View file

@ -27,195 +27,237 @@ using GreenshotPlugin.Controls;
using GreenshotPlugin.Core; using GreenshotPlugin.Core;
using GreenshotPlugin.IniFile; using GreenshotPlugin.IniFile;
namespace Greenshot.Plugin.Imgur.Forms { namespace Greenshot.Plugin.Imgur.Forms
/// <summary> {
/// Imgur history form /// <summary>
/// </summary> /// Imgur history form
public sealed partial class ImgurHistory : ImgurForm { /// </summary>
private static readonly log4net.ILog Log = log4net.LogManager.GetLogger(typeof(ImgurHistory)); public sealed partial class ImgurHistory : ImgurForm
private readonly GreenshotColumnSorter _columnSorter; {
private static readonly object Lock = new object(); private static readonly log4net.ILog Log = log4net.LogManager.GetLogger(typeof(ImgurHistory));
private static readonly ImgurConfiguration Config = IniConfig.GetIniSection<ImgurConfiguration>(); private readonly GreenshotColumnSorter _columnSorter;
private static ImgurHistory _instance; private static readonly object Lock = new object();
private static readonly ImgurConfiguration Config = IniConfig.GetIniSection<ImgurConfiguration>();
private static ImgurHistory _instance;
public static void ShowHistory() { public static void ShowHistory()
lock (Lock) {
{ lock (Lock)
if (ImgurUtils.IsHistoryLoadingNeeded()) {
{ if (ImgurUtils.IsHistoryLoadingNeeded())
// Run upload in the background {
new PleaseWaitForm().ShowAndWait("Imgur " + Language.GetString("imgur", LangKey.history), Language.GetString("imgur", LangKey.communication_wait), // Run upload in the background
ImgurUtils.LoadHistory new PleaseWaitForm().ShowAndWait("Imgur " + Language.GetString("imgur", LangKey.history), Language.GetString("imgur", LangKey.communication_wait),
); ImgurUtils.LoadHistory
} );
}
// Make sure the history is loaded, will be done only once // Make sure the history is loaded, will be done only once
if (_instance == null) if (_instance == null)
{ {
_instance = new ImgurHistory(); _instance = new ImgurHistory();
} }
if (!_instance.Visible)
{
_instance.Show();
}
_instance.Redraw();
}
}
private ImgurHistory() { if (!_instance.Visible)
ManualLanguageApply = true; {
// _instance.Show();
// The InitializeComponent() call is required for Windows Forms designer support. }
//
InitializeComponent();
AcceptButton = finishedButton;
CancelButton = finishedButton;
// Init sorting
_columnSorter = new GreenshotColumnSorter();
listview_imgur_uploads.ListViewItemSorter = _columnSorter;
_columnSorter.SortColumn = 3;
_columnSorter.Order = SortOrder.Descending;
Redraw();
if (listview_imgur_uploads.Items.Count > 0) {
listview_imgur_uploads.Items[0].Selected = true;
}
ApplyLanguage();
if (Config.Credits > 0) {
Text = Text + " (" + Config.Credits + " credits)";
}
}
private void Redraw() { _instance.Redraw();
// Should fix Bug #3378699 }
pictureBox1.Image = pictureBox1.ErrorImage; }
listview_imgur_uploads.BeginUpdate();
listview_imgur_uploads.Items.Clear();
listview_imgur_uploads.Columns.Clear();
string[] columns = { "hash", "title", "deleteHash", "Date"};
foreach (string column in columns) {
listview_imgur_uploads.Columns.Add(column);
}
foreach (ImgurInfo imgurInfo in Config.runtimeImgurHistory.Values) {
var item = new ListViewItem(imgurInfo.Hash)
{
Tag = imgurInfo
};
item.SubItems.Add(imgurInfo.Title);
item.SubItems.Add(imgurInfo.DeleteHash);
item.SubItems.Add(imgurInfo.Timestamp.ToString("yyyy-MM-dd HH:mm:ss", DateTimeFormatInfo.InvariantInfo));
listview_imgur_uploads.Items.Add(item);
}
for (int i = 0; i < columns.Length; i++) {
listview_imgur_uploads.AutoResizeColumn(i, ColumnHeaderAutoResizeStyle.ColumnContent);
}
listview_imgur_uploads.EndUpdate(); private ImgurHistory()
listview_imgur_uploads.Refresh(); {
deleteButton.Enabled = false; ManualLanguageApply = true;
openButton.Enabled = false; //
clipboardButton.Enabled = false; // The InitializeComponent() call is required for Windows Forms designer support.
} //
InitializeComponent();
AcceptButton = finishedButton;
CancelButton = finishedButton;
// Init sorting
_columnSorter = new GreenshotColumnSorter();
listview_imgur_uploads.ListViewItemSorter = _columnSorter;
_columnSorter.SortColumn = 3;
_columnSorter.Order = SortOrder.Descending;
Redraw();
if (listview_imgur_uploads.Items.Count > 0)
{
listview_imgur_uploads.Items[0].Selected = true;
}
private void Listview_imgur_uploadsSelectedIndexChanged(object sender, EventArgs e) { ApplyLanguage();
pictureBox1.Image = pictureBox1.ErrorImage; if (Config.Credits > 0)
if (listview_imgur_uploads.SelectedItems.Count > 0) { {
deleteButton.Enabled = true; Text = Text + " (" + Config.Credits + " credits)";
openButton.Enabled = true; }
clipboardButton.Enabled = true; }
if (listview_imgur_uploads.SelectedItems.Count == 1) {
ImgurInfo imgurInfo = (ImgurInfo)listview_imgur_uploads.SelectedItems[0].Tag;
pictureBox1.Image = imgurInfo.Image;
}
} else {
pictureBox1.Image = pictureBox1.ErrorImage;
deleteButton.Enabled = false;
openButton.Enabled = false;
clipboardButton.Enabled = false;
}
}
private void DeleteButtonClick(object sender, EventArgs e) { private void Redraw()
if (listview_imgur_uploads.SelectedItems.Count > 0) { {
for (int i = 0; i < listview_imgur_uploads.SelectedItems.Count; i++) { // Should fix Bug #3378699
ImgurInfo imgurInfo = (ImgurInfo)listview_imgur_uploads.SelectedItems[i].Tag; pictureBox1.Image = pictureBox1.ErrorImage;
DialogResult result = MessageBox.Show(Language.GetFormattedString("imgur", LangKey.delete_question, imgurInfo.Title), Language.GetFormattedString("imgur", LangKey.delete_title, imgurInfo.Hash), MessageBoxButtons.YesNo, MessageBoxIcon.Question); listview_imgur_uploads.BeginUpdate();
if (result != DialogResult.Yes) listview_imgur_uploads.Items.Clear();
{ listview_imgur_uploads.Columns.Clear();
continue; string[] columns =
} {
// Should fix Bug #3378699 "hash", "title", "deleteHash", "Date"
pictureBox1.Image = pictureBox1.ErrorImage; };
try { foreach (string column in columns)
new PleaseWaitForm().ShowAndWait("Imgur", Language.GetString("imgur", LangKey.communication_wait), {
delegate { listview_imgur_uploads.Columns.Add(column);
ImgurUtils.DeleteImgurImage(imgurInfo); }
}
);
} catch (Exception ex) {
Log.Warn("Problem communicating with Imgur: ", ex);
}
imgurInfo.Dispose(); foreach (ImgurInfo imgurInfo in Config.runtimeImgurHistory.Values)
} {
} var item = new ListViewItem(imgurInfo.Hash)
Redraw(); {
} Tag = imgurInfo
};
item.SubItems.Add(imgurInfo.Title);
item.SubItems.Add(imgurInfo.DeleteHash);
item.SubItems.Add(imgurInfo.Timestamp.ToString("yyyy-MM-dd HH:mm:ss", DateTimeFormatInfo.InvariantInfo));
listview_imgur_uploads.Items.Add(item);
}
private void ClipboardButtonClick(object sender, EventArgs e) { for (int i = 0; i < columns.Length; i++)
StringBuilder links = new StringBuilder(); {
if (listview_imgur_uploads.SelectedItems.Count > 0) { listview_imgur_uploads.AutoResizeColumn(i, ColumnHeaderAutoResizeStyle.ColumnContent);
for (int i = 0; i < listview_imgur_uploads.SelectedItems.Count; i++) }
{
ImgurInfo imgurInfo = (ImgurInfo)listview_imgur_uploads.SelectedItems[i].Tag;
links.AppendLine(Config.UsePageLink ? imgurInfo.Page : imgurInfo.Original);
}
}
ClipboardHelper.SetClipboardData(links.ToString());
}
private void ClearHistoryButtonClick(object sender, EventArgs e) { listview_imgur_uploads.EndUpdate();
DialogResult result = MessageBox.Show(Language.GetString("imgur", LangKey.clear_question), "Imgur", MessageBoxButtons.YesNo, MessageBoxIcon.Question); listview_imgur_uploads.Refresh();
if (result == DialogResult.Yes) { deleteButton.Enabled = false;
Config.runtimeImgurHistory.Clear(); openButton.Enabled = false;
Config.ImgurUploadHistory.Clear(); clipboardButton.Enabled = false;
IniConfig.Save(); }
Redraw();
}
}
private void FinishedButtonClick(object sender, EventArgs e) private void Listview_imgur_uploadsSelectedIndexChanged(object sender, EventArgs e)
{ {
Hide(); pictureBox1.Image = pictureBox1.ErrorImage;
} if (listview_imgur_uploads.SelectedItems.Count > 0)
{
deleteButton.Enabled = true;
openButton.Enabled = true;
clipboardButton.Enabled = true;
if (listview_imgur_uploads.SelectedItems.Count == 1)
{
ImgurInfo imgurInfo = (ImgurInfo) listview_imgur_uploads.SelectedItems[0].Tag;
pictureBox1.Image = imgurInfo.Image;
}
}
else
{
pictureBox1.Image = pictureBox1.ErrorImage;
deleteButton.Enabled = false;
openButton.Enabled = false;
clipboardButton.Enabled = false;
}
}
private void OpenButtonClick(object sender, EventArgs e) { private void DeleteButtonClick(object sender, EventArgs e)
if (listview_imgur_uploads.SelectedItems.Count > 0) { {
for (int i = 0; i < listview_imgur_uploads.SelectedItems.Count; i++) { if (listview_imgur_uploads.SelectedItems.Count > 0)
ImgurInfo imgurInfo = (ImgurInfo)listview_imgur_uploads.SelectedItems[i].Tag; {
System.Diagnostics.Process.Start(imgurInfo.Page); for (int i = 0; i < listview_imgur_uploads.SelectedItems.Count; i++)
} {
} ImgurInfo imgurInfo = (ImgurInfo) listview_imgur_uploads.SelectedItems[i].Tag;
} DialogResult result = MessageBox.Show(Language.GetFormattedString("imgur", LangKey.delete_question, imgurInfo.Title),
Language.GetFormattedString("imgur", LangKey.delete_title, imgurInfo.Hash), MessageBoxButtons.YesNo, MessageBoxIcon.Question);
if (result != DialogResult.Yes)
{
continue;
}
private void listview_imgur_uploads_ColumnClick(object sender, ColumnClickEventArgs e) { // Should fix Bug #3378699
// Determine if clicked column is already the column that is being sorted. pictureBox1.Image = pictureBox1.ErrorImage;
if (e.Column == _columnSorter.SortColumn) { try
// Reverse the current sort direction for this column. {
_columnSorter.Order = _columnSorter.Order == SortOrder.Ascending ? SortOrder.Descending : SortOrder.Ascending; new PleaseWaitForm().ShowAndWait("Imgur", Language.GetString("imgur", LangKey.communication_wait),
} else { delegate { ImgurUtils.DeleteImgurImage(imgurInfo); }
// Set the column number that is to be sorted; default to ascending. );
_columnSorter.SortColumn = e.Column; }
_columnSorter.Order = SortOrder.Ascending; catch (Exception ex)
} {
Log.Warn("Problem communicating with Imgur: ", ex);
}
// Perform the sort with these new sort options. imgurInfo.Dispose();
listview_imgur_uploads.Sort(); }
} }
Redraw();
}
private void ClipboardButtonClick(object sender, EventArgs e)
{
StringBuilder links = new StringBuilder();
if (listview_imgur_uploads.SelectedItems.Count > 0)
{
for (int i = 0; i < listview_imgur_uploads.SelectedItems.Count; i++)
{
ImgurInfo imgurInfo = (ImgurInfo) listview_imgur_uploads.SelectedItems[i].Tag;
links.AppendLine(Config.UsePageLink ? imgurInfo.Page : imgurInfo.Original);
}
}
ClipboardHelper.SetClipboardData(links.ToString());
}
private void ClearHistoryButtonClick(object sender, EventArgs e)
{
DialogResult result = MessageBox.Show(Language.GetString("imgur", LangKey.clear_question), "Imgur", MessageBoxButtons.YesNo, MessageBoxIcon.Question);
if (result == DialogResult.Yes)
{
Config.runtimeImgurHistory.Clear();
Config.ImgurUploadHistory.Clear();
IniConfig.Save();
Redraw();
}
}
private void FinishedButtonClick(object sender, EventArgs e)
{
Hide();
}
private void OpenButtonClick(object sender, EventArgs e)
{
if (listview_imgur_uploads.SelectedItems.Count > 0)
{
for (int i = 0; i < listview_imgur_uploads.SelectedItems.Count; i++)
{
ImgurInfo imgurInfo = (ImgurInfo) listview_imgur_uploads.SelectedItems[i].Tag;
System.Diagnostics.Process.Start(imgurInfo.Page);
}
}
}
private void listview_imgur_uploads_ColumnClick(object sender, ColumnClickEventArgs e)
{
// Determine if clicked column is already the column that is being sorted.
if (e.Column == _columnSorter.SortColumn)
{
// Reverse the current sort direction for this column.
_columnSorter.Order = _columnSorter.Order == SortOrder.Ascending ? SortOrder.Descending : SortOrder.Ascending;
}
else
{
// Set the column number that is to be sorted; default to ascending.
_columnSorter.SortColumn = e.Column;
_columnSorter.Order = SortOrder.Ascending;
}
// Perform the sort with these new sort options.
listview_imgur_uploads.Sort();
}
private void ImgurHistoryFormClosing(object sender, FormClosingEventArgs e) private void ImgurHistoryFormClosing(object sender, FormClosingEventArgs e)
{ {
_instance = null; _instance = null;
} }
} }
} }

View file

@ -21,25 +21,28 @@
using System; using System;
namespace Greenshot.Plugin.Imgur.Forms { namespace Greenshot.Plugin.Imgur.Forms
/// <summary> {
/// Description of PasswordRequestForm. /// <summary>
/// </summary> /// Description of PasswordRequestForm.
public partial class SettingsForm : ImgurForm { /// </summary>
public SettingsForm() public partial class SettingsForm : ImgurForm
{ {
// public SettingsForm()
// The InitializeComponent() call is required for Windows Forms designer support. {
// //
InitializeComponent(); // The InitializeComponent() call is required for Windows Forms designer support.
CancelButton = buttonCancel; //
AcceptButton = buttonOK; InitializeComponent();
CancelButton = buttonCancel;
AcceptButton = buttonOK;
historyButton.Enabled = ImgurUtils.IsHistoryLoadingNeeded(); historyButton.Enabled = ImgurUtils.IsHistoryLoadingNeeded();
} }
private void ButtonHistoryClick(object sender, EventArgs e) { private void ButtonHistoryClick(object sender, EventArgs e)
ImgurHistory.ShowHistory(); {
} ImgurHistory.ShowHistory();
} }
}
} }

View file

@ -26,64 +26,70 @@ using Greenshot.Plugin.Imgur.Forms;
using GreenshotPlugin.Core; using GreenshotPlugin.Core;
using GreenshotPlugin.IniFile; using GreenshotPlugin.IniFile;
namespace Greenshot.Plugin.Imgur { namespace Greenshot.Plugin.Imgur
/// <summary> {
/// Description of ImgurConfiguration. /// <summary>
/// </summary> /// Description of ImgurConfiguration.
[IniSection("Imgur", Description="Greenshot Imgur Plugin configuration")] /// </summary>
public class ImgurConfiguration : IniSection { [IniSection("Imgur", Description = "Greenshot Imgur Plugin configuration")]
[IniProperty("ImgurApi3Url", Description = "Url to Imgur system.", DefaultValue = "https://api.imgur.com/3")] public class ImgurConfiguration : IniSection
public string ImgurApi3Url { get; set; } {
[IniProperty("ImgurApi3Url", Description = "Url to Imgur system.", DefaultValue = "https://api.imgur.com/3")]
public string ImgurApi3Url { get; set; }
[IniProperty("UploadFormat", Description="What file type to use for uploading", DefaultValue="png")] [IniProperty("UploadFormat", Description = "What file type to use for uploading", DefaultValue = "png")]
public OutputFormat UploadFormat { get; set; } public OutputFormat UploadFormat { get; set; }
[IniProperty("UploadJpegQuality", Description="JPEG file save quality in %.", DefaultValue="80")]
public int UploadJpegQuality { get; set; }
[IniProperty("UploadReduceColors", Description="Reduce color amount of the uploaded image to 256", DefaultValue="False")]
public bool UploadReduceColors { get; set; }
[IniProperty("CopyLinkToClipboard", Description = "Copy the link, which one is controlled by the UsePageLink, on the clipboard", DefaultValue = "True")]
public bool CopyLinkToClipboard { get; set; }
[IniProperty("UsePageLink", Description = "Use pagelink instead of direct link on the clipboard", DefaultValue = "False")]
public bool UsePageLink { get; set; }
[IniProperty("AnonymousAccess", Description = "Use anonymous access to Imgur", DefaultValue="true")]
public bool AnonymousAccess { get; set; }
[IniProperty("RefreshToken", Description = "Imgur refresh Token", Encrypted = true, ExcludeIfNull = true)] [IniProperty("UploadJpegQuality", Description = "JPEG file save quality in %.", DefaultValue = "80")]
public string RefreshToken { get; set; } public int UploadJpegQuality { get; set; }
/// <summary> [IniProperty("UploadReduceColors", Description = "Reduce color amount of the uploaded image to 256", DefaultValue = "False")]
/// AccessToken, not stored public bool UploadReduceColors { get; set; }
/// </summary>
public string AccessToken { get; set; }
/// <summary> [IniProperty("CopyLinkToClipboard", Description = "Copy the link, which one is controlled by the UsePageLink, on the clipboard", DefaultValue = "True")]
/// AccessTokenExpires, not stored public bool CopyLinkToClipboard { get; set; }
/// </summary>
public DateTimeOffset AccessTokenExpires { get; set; }
[IniProperty("AddTitle", Description = "Is the title passed on to Imgur", DefaultValue = "False")] [IniProperty("UsePageLink", Description = "Use pagelink instead of direct link on the clipboard", DefaultValue = "False")]
public bool AddTitle { get; set; } public bool UsePageLink { get; set; }
[IniProperty("AddFilename", Description = "Is the filename passed on to Imgur", DefaultValue = "False")]
public bool AddFilename { get; set; }
[IniProperty("FilenamePattern", Description = "Filename for the Imgur upload", DefaultValue = "${capturetime:d\"yyyyMMdd-HHmm\"}")]
public string FilenamePattern { get; set; }
[IniProperty("ImgurUploadHistory", Description="Imgur upload history (ImgurUploadHistory.hash=deleteHash)")] [IniProperty("AnonymousAccess", Description = "Use anonymous access to Imgur", DefaultValue = "true")]
public Dictionary<string, string> ImgurUploadHistory { get; set; } public bool AnonymousAccess { get; set; }
// Not stored, only run-time! [IniProperty("RefreshToken", Description = "Imgur refresh Token", Encrypted = true, ExcludeIfNull = true)]
public Dictionary<string, ImgurInfo> runtimeImgurHistory = new Dictionary<string, ImgurInfo>(); public string RefreshToken { get; set; }
public int Credits {
get;
set;
}
/// <summary> /// <summary>
/// Supply values we can't put as defaults /// AccessToken, not stored
/// </summary> /// </summary>
/// <param name="property">The property to return a default for</param> public string AccessToken { get; set; }
/// <returns>object with the default value for the supplied property</returns>
public override object GetDefault(string property) => /// <summary>
/// AccessTokenExpires, not stored
/// </summary>
public DateTimeOffset AccessTokenExpires { get; set; }
[IniProperty("AddTitle", Description = "Is the title passed on to Imgur", DefaultValue = "False")]
public bool AddTitle { get; set; }
[IniProperty("AddFilename", Description = "Is the filename passed on to Imgur", DefaultValue = "False")]
public bool AddFilename { get; set; }
[IniProperty("FilenamePattern", Description = "Filename for the Imgur upload", DefaultValue = "${capturetime:d\"yyyyMMdd-HHmm\"}")]
public string FilenamePattern { get; set; }
[IniProperty("ImgurUploadHistory", Description = "Imgur upload history (ImgurUploadHistory.hash=deleteHash)")]
public Dictionary<string, string> ImgurUploadHistory { get; set; }
// Not stored, only run-time!
public Dictionary<string, ImgurInfo> runtimeImgurHistory = new Dictionary<string, ImgurInfo>();
public int Credits { get; set; }
/// <summary>
/// Supply values we can't put as defaults
/// </summary>
/// <param name="property">The property to return a default for</param>
/// <returns>object with the default value for the supplied property</returns>
public override object GetDefault(string property) =>
property switch property switch
{ {
"ImgurUploadHistory" => new Dictionary<string, string>(), "ImgurUploadHistory" => new Dictionary<string, string>(),
@ -91,13 +97,14 @@ namespace Greenshot.Plugin.Imgur {
}; };
/// <summary> /// <summary>
/// A form for username/password /// A form for username/password
/// </summary> /// </summary>
/// <returns>bool true if OK was pressed, false if cancel</returns> /// <returns>bool true if OK was pressed, false if cancel</returns>
public bool ShowConfigDialog() { public bool ShowConfigDialog()
SettingsForm settingsForm = new SettingsForm(); {
DialogResult result = settingsForm.ShowDialog(); SettingsForm settingsForm = new SettingsForm();
return result == DialogResult.OK; DialogResult result = settingsForm.ShowDialog();
} return result == DialogResult.OK;
} }
}
} }

View file

@ -24,35 +24,42 @@ using System.Drawing;
using GreenshotPlugin.Core; using GreenshotPlugin.Core;
using GreenshotPlugin.Interfaces; using GreenshotPlugin.Interfaces;
namespace Greenshot.Plugin.Imgur { namespace Greenshot.Plugin.Imgur
/// <summary> {
/// Description of ImgurDestination. /// <summary>
/// </summary> /// Description of ImgurDestination.
public class ImgurDestination : AbstractDestination { /// </summary>
private readonly ImgurPlugin _plugin; public class ImgurDestination : AbstractDestination
{
private readonly ImgurPlugin _plugin;
public ImgurDestination(ImgurPlugin plugin) { public ImgurDestination(ImgurPlugin plugin)
_plugin = plugin; {
} _plugin = plugin;
}
public override string Designation => "Imgur"; public override string Designation => "Imgur";
public override string Description => Language.GetString("imgur", LangKey.upload_menu_item); public override string Description => Language.GetString("imgur", LangKey.upload_menu_item);
public override Image DisplayIcon { public override Image DisplayIcon
get { {
ComponentResourceManager resources = new ComponentResourceManager(typeof(ImgurPlugin)); get
return (Image)resources.GetObject("Imgur"); {
} ComponentResourceManager resources = new ComponentResourceManager(typeof(ImgurPlugin));
} return (Image) resources.GetObject("Imgur");
}
}
public override ExportInformation ExportCapture(bool manuallyInitiated, ISurface surface, ICaptureDetails captureDetails) { public override ExportInformation ExportCapture(bool manuallyInitiated, ISurface surface, ICaptureDetails captureDetails)
{
ExportInformation exportInformation = new ExportInformation(Designation, Description) ExportInformation exportInformation = new ExportInformation(Designation, Description)
{ {
ExportMade = _plugin.Upload(captureDetails, surface, out var uploadUrl), Uri = uploadUrl ExportMade = _plugin.Upload(captureDetails, surface, out var uploadUrl),
Uri = uploadUrl
}; };
ProcessExport(exportInformation, surface); ProcessExport(exportInformation, surface);
return exportInformation; return exportInformation;
} }
} }
} }

View file

@ -25,194 +25,172 @@ using System.Xml;
namespace Greenshot.Plugin.Imgur namespace Greenshot.Plugin.Imgur
{ {
/// <summary> /// <summary>
/// Description of ImgurInfo. /// Description of ImgurInfo.
/// </summary> /// </summary>
public class ImgurInfo : IDisposable public class ImgurInfo : IDisposable
{ {
private static readonly log4net.ILog Log = log4net.LogManager.GetLogger(typeof(ImgurInfo)); private static readonly log4net.ILog Log = log4net.LogManager.GetLogger(typeof(ImgurInfo));
public string Hash public string Hash { get; set; }
{
get;
set;
}
private string _deleteHash; private string _deleteHash;
public string DeleteHash
{
get { return _deleteHash; }
set
{
_deleteHash = value;
DeletePage = "https://imgur.com/delete/" + value;
}
}
public string Title public string DeleteHash
{ {
get; get { return _deleteHash; }
set; set
} {
_deleteHash = value;
DeletePage = "https://imgur.com/delete/" + value;
}
}
public string ImageType public string Title { get; set; }
{
get;
set;
}
public DateTime Timestamp public string ImageType { get; set; }
{
get;
set;
}
public string Original public DateTime Timestamp { get; set; }
{
get;
set;
}
public string Page public string Original { get; set; }
{
get;
set;
}
public string SmallSquare public string Page { get; set; }
{
get;
set;
}
public string LargeThumbnail public string SmallSquare { get; set; }
{
get;
set;
}
public string DeletePage public string LargeThumbnail { get; set; }
{
get;
set;
}
private Image _image; public string DeletePage { get; set; }
public Image Image
{
get { return _image; }
set
{
_image?.Dispose();
_image = value;
}
}
/// <summary> private Image _image;
/// The public accessible Dispose
/// Will call the GarbageCollector to SuppressFinalize, preventing being cleaned twice
/// </summary>
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
/// <summary> public Image Image
/// This Dispose is called from the Dispose and the Destructor. {
/// When disposing==true all non-managed resources should be freed too! get { return _image; }
/// </summary> set
/// <param name="disposing"></param> {
protected virtual void Dispose(bool disposing) _image?.Dispose();
{ _image = value;
if (disposing) }
{ }
_image?.Dispose();
}
_image = null;
}
public static ImgurInfo ParseResponse(string response)
{
Log.Debug(response);
// This is actually a hack for BUG-1695
// The problem is the (C) sign, we send it HTML encoded "&reg;" to Imgur and get it HTML encoded in the XML back
// Added all the encodings I found quickly, I guess these are not all... but it should fix the issue for now.
response = response.Replace("&cent;", "&#162;");
response = response.Replace("&pound;", "&#163;");
response = response.Replace("&yen;", "&#165;");
response = response.Replace("&euro;", "&#8364;");
response = response.Replace("&copy;", "&#169;");
response = response.Replace("&reg;", "&#174;");
ImgurInfo imgurInfo = new ImgurInfo(); /// <summary>
try /// The public accessible Dispose
{ /// Will call the GarbageCollector to SuppressFinalize, preventing being cleaned twice
XmlDocument doc = new XmlDocument(); /// </summary>
doc.LoadXml(response); public void Dispose()
XmlNodeList nodes = doc.GetElementsByTagName("id"); {
if (nodes.Count > 0) Dispose(true);
{ GC.SuppressFinalize(this);
imgurInfo.Hash = nodes.Item(0)?.InnerText; }
}
nodes = doc.GetElementsByTagName("hash"); /// <summary>
if (nodes.Count > 0) /// This Dispose is called from the Dispose and the Destructor.
{ /// When disposing==true all non-managed resources should be freed too!
imgurInfo.Hash = nodes.Item(0)?.InnerText; /// </summary>
} /// <param name="disposing"></param>
nodes = doc.GetElementsByTagName("deletehash"); protected virtual void Dispose(bool disposing)
if (nodes.Count > 0) {
{ if (disposing)
imgurInfo.DeleteHash = nodes.Item(0)?.InnerText; {
} _image?.Dispose();
nodes = doc.GetElementsByTagName("type"); }
if (nodes.Count > 0)
{ _image = null;
imgurInfo.ImageType = nodes.Item(0)?.InnerText; }
}
nodes = doc.GetElementsByTagName("title"); public static ImgurInfo ParseResponse(string response)
if (nodes.Count > 0) {
{ Log.Debug(response);
imgurInfo.Title = nodes.Item(0)?.InnerText; // This is actually a hack for BUG-1695
} // The problem is the (C) sign, we send it HTML encoded "&reg;" to Imgur and get it HTML encoded in the XML back
nodes = doc.GetElementsByTagName("datetime"); // Added all the encodings I found quickly, I guess these are not all... but it should fix the issue for now.
if (nodes.Count > 0) response = response.Replace("&cent;", "&#162;");
{ response = response.Replace("&pound;", "&#163;");
// Version 3 has seconds since Epoch response = response.Replace("&yen;", "&#165;");
response = response.Replace("&euro;", "&#8364;");
response = response.Replace("&copy;", "&#169;");
response = response.Replace("&reg;", "&#174;");
ImgurInfo imgurInfo = new ImgurInfo();
try
{
XmlDocument doc = new XmlDocument();
doc.LoadXml(response);
XmlNodeList nodes = doc.GetElementsByTagName("id");
if (nodes.Count > 0)
{
imgurInfo.Hash = nodes.Item(0)?.InnerText;
}
nodes = doc.GetElementsByTagName("hash");
if (nodes.Count > 0)
{
imgurInfo.Hash = nodes.Item(0)?.InnerText;
}
nodes = doc.GetElementsByTagName("deletehash");
if (nodes.Count > 0)
{
imgurInfo.DeleteHash = nodes.Item(0)?.InnerText;
}
nodes = doc.GetElementsByTagName("type");
if (nodes.Count > 0)
{
imgurInfo.ImageType = nodes.Item(0)?.InnerText;
}
nodes = doc.GetElementsByTagName("title");
if (nodes.Count > 0)
{
imgurInfo.Title = nodes.Item(0)?.InnerText;
}
nodes = doc.GetElementsByTagName("datetime");
if (nodes.Count > 0)
{
// Version 3 has seconds since Epoch
if (double.TryParse(nodes.Item(0)?.InnerText, out var secondsSince)) if (double.TryParse(nodes.Item(0)?.InnerText, out var secondsSince))
{ {
var epoch = new DateTimeOffset(1970, 1, 1, 0, 0, 0, TimeSpan.Zero); var epoch = new DateTimeOffset(1970, 1, 1, 0, 0, 0, TimeSpan.Zero);
imgurInfo.Timestamp = epoch.AddSeconds(secondsSince).DateTime; imgurInfo.Timestamp = epoch.AddSeconds(secondsSince).DateTime;
} }
} }
nodes = doc.GetElementsByTagName("original");
if (nodes.Count > 0) nodes = doc.GetElementsByTagName("original");
{ if (nodes.Count > 0)
imgurInfo.Original = nodes.Item(0)?.InnerText.Replace("http:", "https:"); {
} imgurInfo.Original = nodes.Item(0)?.InnerText.Replace("http:", "https:");
// Version 3 API only has Link }
nodes = doc.GetElementsByTagName("link");
if (nodes.Count > 0) // Version 3 API only has Link
{ nodes = doc.GetElementsByTagName("link");
imgurInfo.Original = nodes.Item(0)?.InnerText.Replace("http:", "https:"); if (nodes.Count > 0)
} {
nodes = doc.GetElementsByTagName("imgur_page"); imgurInfo.Original = nodes.Item(0)?.InnerText.Replace("http:", "https:");
if (nodes.Count > 0) }
{
imgurInfo.Page = nodes.Item(0)?.InnerText.Replace("http:", "https:"); nodes = doc.GetElementsByTagName("imgur_page");
} if (nodes.Count > 0)
else {
{ imgurInfo.Page = nodes.Item(0)?.InnerText.Replace("http:", "https:");
// Version 3 doesn't have a page link in the response }
imgurInfo.Page = $"https://imgur.com/{imgurInfo.Hash}"; else
} {
nodes = doc.GetElementsByTagName("small_square"); // Version 3 doesn't have a page link in the response
imgurInfo.SmallSquare = nodes.Count > 0 ? nodes.Item(0)?.InnerText : $"http://i.imgur.com/{imgurInfo.Hash}s.png"; imgurInfo.Page = $"https://imgur.com/{imgurInfo.Hash}";
} }
catch (Exception e)
{ nodes = doc.GetElementsByTagName("small_square");
Log.ErrorFormat("Could not parse Imgur response due to error {0}, response was: {1}", e.Message, response); imgurInfo.SmallSquare = nodes.Count > 0 ? nodes.Item(0)?.InnerText : $"http://i.imgur.com/{imgurInfo.Hash}s.png";
} }
return imgurInfo; catch (Exception e)
} {
} Log.ErrorFormat("Could not parse Imgur response due to error {0}, response was: {1}", e.Message, response);
}
return imgurInfo;
}
}
} }

View file

@ -32,186 +32,216 @@ using GreenshotPlugin.IniFile;
using GreenshotPlugin.Interfaces; using GreenshotPlugin.Interfaces;
using GreenshotPlugin.Interfaces.Plugin; using GreenshotPlugin.Interfaces.Plugin;
namespace Greenshot.Plugin.Imgur { namespace Greenshot.Plugin.Imgur
/// <summary> {
/// This is the ImgurPlugin code /// <summary>
/// </summary> /// This is the ImgurPlugin code
/// </summary>
[Plugin("Imgur", true)] [Plugin("Imgur", true)]
public class ImgurPlugin : IGreenshotPlugin { public class ImgurPlugin : IGreenshotPlugin
private static readonly log4net.ILog Log = log4net.LogManager.GetLogger(typeof(ImgurPlugin)); {
private static ImgurConfiguration _config; private static readonly log4net.ILog Log = log4net.LogManager.GetLogger(typeof(ImgurPlugin));
private ComponentResourceManager _resources; private static ImgurConfiguration _config;
private ToolStripMenuItem _historyMenuItem; private ComponentResourceManager _resources;
private ToolStripMenuItem _itemPlugInConfig; private ToolStripMenuItem _historyMenuItem;
private ToolStripMenuItem _itemPlugInConfig;
public void Dispose() { public void Dispose()
Dispose(true); {
GC.SuppressFinalize(this); Dispose(true);
} GC.SuppressFinalize(this);
}
protected virtual void Dispose(bool disposing) { protected virtual void Dispose(bool disposing)
if (disposing) { {
if (_historyMenuItem != null) { if (disposing)
_historyMenuItem.Dispose(); {
_historyMenuItem = null; if (_historyMenuItem != null)
} {
if (_itemPlugInConfig != null) { _historyMenuItem.Dispose();
_itemPlugInConfig.Dispose(); _historyMenuItem = null;
_itemPlugInConfig = null; }
}
}
}
private IEnumerable<IDestination> Destinations() { if (_itemPlugInConfig != null)
yield return new ImgurDestination(this); {
} _itemPlugInConfig.Dispose();
_itemPlugInConfig = null;
}
}
}
/// <summary> private IEnumerable<IDestination> Destinations()
/// Implementation of the IGreenshotPlugin.Initialize {
/// </summary> yield return new ImgurDestination(this);
/// <returns>true if plugin is initialized, false if not (doesn't show)</returns> }
public bool Initialize() {
// Get configuration
_config = IniConfig.GetIniSection<ImgurConfiguration>();
_resources = new ComponentResourceManager(typeof(ImgurPlugin));
ToolStripMenuItem itemPlugInRoot = new ToolStripMenuItem("Imgur") /// <summary>
{ /// Implementation of the IGreenshotPlugin.Initialize
Image = (Image) _resources.GetObject("Imgur") /// </summary>
}; /// <returns>true if plugin is initialized, false if not (doesn't show)</returns>
public bool Initialize()
{
// Get configuration
_config = IniConfig.GetIniSection<ImgurConfiguration>();
_resources = new ComponentResourceManager(typeof(ImgurPlugin));
ToolStripMenuItem itemPlugInRoot = new ToolStripMenuItem("Imgur")
{
Image = (Image) _resources.GetObject("Imgur")
};
// Provide the IDestination // Provide the IDestination
SimpleServiceProvider.Current.AddService(Destinations()); SimpleServiceProvider.Current.AddService(Destinations());
_historyMenuItem = new ToolStripMenuItem(Language.GetString("imgur", LangKey.history)); _historyMenuItem = new ToolStripMenuItem(Language.GetString("imgur", LangKey.history));
_historyMenuItem.Click += delegate { _historyMenuItem.Click += delegate { ImgurHistory.ShowHistory(); };
ImgurHistory.ShowHistory(); itemPlugInRoot.DropDownItems.Add(_historyMenuItem);
};
itemPlugInRoot.DropDownItems.Add(_historyMenuItem);
_itemPlugInConfig = new ToolStripMenuItem(Language.GetString("imgur", LangKey.configure)); _itemPlugInConfig = new ToolStripMenuItem(Language.GetString("imgur", LangKey.configure));
_itemPlugInConfig.Click += delegate { _itemPlugInConfig.Click += delegate { _config.ShowConfigDialog(); };
_config.ShowConfigDialog(); itemPlugInRoot.DropDownItems.Add(_itemPlugInConfig);
};
itemPlugInRoot.DropDownItems.Add(_itemPlugInConfig);
PluginUtils.AddToContextMenu(itemPlugInRoot); PluginUtils.AddToContextMenu(itemPlugInRoot);
Language.LanguageChanged += OnLanguageChanged; Language.LanguageChanged += OnLanguageChanged;
// Enable history if there are items available // Enable history if there are items available
UpdateHistoryMenuItem(); UpdateHistoryMenuItem();
return true; return true;
} }
public void OnLanguageChanged(object sender, EventArgs e) { public void OnLanguageChanged(object sender, EventArgs e)
if (_itemPlugInConfig != null) { {
_itemPlugInConfig.Text = Language.GetString("imgur", LangKey.configure); if (_itemPlugInConfig != null)
} {
if (_historyMenuItem != null) { _itemPlugInConfig.Text = Language.GetString("imgur", LangKey.configure);
_historyMenuItem.Text = Language.GetString("imgur", LangKey.history); }
}
}
private void UpdateHistoryMenuItem() { if (_historyMenuItem != null)
if (_historyMenuItem == null) {
{ _historyMenuItem.Text = Language.GetString("imgur", LangKey.history);
return; }
} }
try
private void UpdateHistoryMenuItem()
{
if (_historyMenuItem == null)
{
return;
}
try
{ {
var form = SimpleServiceProvider.Current.GetInstance<Form>(); var form = SimpleServiceProvider.Current.GetInstance<Form>();
form.BeginInvoke((MethodInvoker)delegate form.BeginInvoke((MethodInvoker) delegate
{ {
var historyMenuItem = _historyMenuItem; var historyMenuItem = _historyMenuItem;
if (historyMenuItem == null) if (historyMenuItem == null)
{ {
return; return;
} }
if (_config?.ImgurUploadHistory != null && _config.ImgurUploadHistory.Count > 0) {
if (_config?.ImgurUploadHistory != null && _config.ImgurUploadHistory.Count > 0)
{
historyMenuItem.Enabled = true; historyMenuItem.Enabled = true;
} else { }
else
{
historyMenuItem.Enabled = false; historyMenuItem.Enabled = false;
} }
}); });
} catch (Exception ex) { }
Log.Error("Error loading history", ex); catch (Exception ex)
} {
} Log.Error("Error loading history", ex);
}
}
public virtual void Shutdown() { public virtual void Shutdown()
Log.Debug("Imgur Plugin shutdown."); {
Language.LanguageChanged -= OnLanguageChanged; Log.Debug("Imgur Plugin shutdown.");
} Language.LanguageChanged -= OnLanguageChanged;
}
/// <summary> /// <summary>
/// Implementation of the IPlugin.Configure /// Implementation of the IPlugin.Configure
/// </summary> /// </summary>
public virtual void Configure() { public virtual void Configure()
_config.ShowConfigDialog(); {
} _config.ShowConfigDialog();
}
/// <summary> /// <summary>
/// Upload the capture to imgur /// Upload the capture to imgur
/// </summary> /// </summary>
/// <param name="captureDetails">ICaptureDetails</param> /// <param name="captureDetails">ICaptureDetails</param>
/// <param name="surfaceToUpload">ISurface</param> /// <param name="surfaceToUpload">ISurface</param>
/// <param name="uploadUrl">out string for the url</param> /// <param name="uploadUrl">out string for the url</param>
/// <returns>true if the upload succeeded</returns> /// <returns>true if the upload succeeded</returns>
public bool Upload(ICaptureDetails captureDetails, ISurface surfaceToUpload, out string uploadUrl) { public bool Upload(ICaptureDetails captureDetails, ISurface surfaceToUpload, out string uploadUrl)
SurfaceOutputSettings outputSettings = new SurfaceOutputSettings(_config.UploadFormat, _config.UploadJpegQuality, _config.UploadReduceColors); {
try { SurfaceOutputSettings outputSettings = new SurfaceOutputSettings(_config.UploadFormat, _config.UploadJpegQuality, _config.UploadReduceColors);
string filename = Path.GetFileName(FilenameHelper.GetFilenameFromPattern(_config.FilenamePattern, _config.UploadFormat, captureDetails)); try
ImgurInfo imgurInfo = null; {
string filename = Path.GetFileName(FilenameHelper.GetFilenameFromPattern(_config.FilenamePattern, _config.UploadFormat, captureDetails));
ImgurInfo imgurInfo = null;
// Run upload in the background // Run upload in the background
new PleaseWaitForm().ShowAndWait("Imgur plug-in", Language.GetString("imgur", LangKey.communication_wait), new PleaseWaitForm().ShowAndWait("Imgur plug-in", Language.GetString("imgur", LangKey.communication_wait),
delegate delegate
{ {
imgurInfo = ImgurUtils.UploadToImgur(surfaceToUpload, outputSettings, captureDetails.Title, filename); imgurInfo = ImgurUtils.UploadToImgur(surfaceToUpload, outputSettings, captureDetails.Title, filename);
if (imgurInfo != null && _config.AnonymousAccess) { if (imgurInfo != null && _config.AnonymousAccess)
Log.InfoFormat("Storing imgur upload for hash {0} and delete hash {1}", imgurInfo.Hash, imgurInfo.DeleteHash); {
_config.ImgurUploadHistory.Add(imgurInfo.Hash, imgurInfo.DeleteHash); Log.InfoFormat("Storing imgur upload for hash {0} and delete hash {1}", imgurInfo.Hash, imgurInfo.DeleteHash);
_config.runtimeImgurHistory.Add(imgurInfo.Hash, imgurInfo); _config.ImgurUploadHistory.Add(imgurInfo.Hash, imgurInfo.DeleteHash);
UpdateHistoryMenuItem(); _config.runtimeImgurHistory.Add(imgurInfo.Hash, imgurInfo);
} UpdateHistoryMenuItem();
} }
); }
);
if (imgurInfo != null) { if (imgurInfo != null)
// TODO: Optimize a second call for export {
using (Image tmpImage = surfaceToUpload.GetImageForExport()) { // TODO: Optimize a second call for export
imgurInfo.Image = ImageHelper.CreateThumbnail(tmpImage, 90, 90); using (Image tmpImage = surfaceToUpload.GetImageForExport())
} {
IniConfig.Save(); imgurInfo.Image = ImageHelper.CreateThumbnail(tmpImage, 90, 90);
}
if (_config.UsePageLink) IniConfig.Save();
{
uploadUrl = imgurInfo.Page;
}
else
{
uploadUrl = imgurInfo.Original;
}
if (!string.IsNullOrEmpty(uploadUrl) && _config.CopyLinkToClipboard)
{
try
{
ClipboardHelper.SetClipboardData(uploadUrl);
} if (_config.UsePageLink)
catch (Exception ex) {
{ uploadUrl = imgurInfo.Page;
Log.Error("Can't write to clipboard: ", ex); }
uploadUrl = null; else
} {
} uploadUrl = imgurInfo.Original;
return true; }
}
} catch (Exception e) { if (!string.IsNullOrEmpty(uploadUrl) && _config.CopyLinkToClipboard)
Log.Error("Error uploading.", e); {
MessageBox.Show(Language.GetString("imgur", LangKey.upload_failure) + " " + e.Message); try
} {
uploadUrl = null; ClipboardHelper.SetClipboardData(uploadUrl);
return false; }
} catch (Exception ex)
} {
Log.Error("Can't write to clipboard: ", ex);
uploadUrl = null;
}
}
return true;
}
}
catch (Exception e)
{
Log.Error("Error uploading.", e);
MessageBox.Show(Language.GetString("imgur", LangKey.upload_failure) + " " + e.Message);
}
uploadUrl = null;
return false;
}
}
} }

View file

@ -30,127 +30,157 @@ using GreenshotPlugin.IniFile;
using GreenshotPlugin.Interfaces; using GreenshotPlugin.Interfaces;
using GreenshotPlugin.Interfaces.Plugin; using GreenshotPlugin.Interfaces.Plugin;
namespace Greenshot.Plugin.Imgur { namespace Greenshot.Plugin.Imgur
/// <summary> {
/// A collection of Imgur helper methods /// <summary>
/// </summary> /// A collection of Imgur helper methods
public static class ImgurUtils { /// </summary>
private static readonly log4net.ILog Log = log4net.LogManager.GetLogger(typeof(ImgurUtils)); public static class ImgurUtils
private const string SmallUrlPattern = "http://i.imgur.com/{0}s.jpg"; {
private static readonly ImgurConfiguration Config = IniConfig.GetIniSection<ImgurConfiguration>(); private static readonly log4net.ILog Log = log4net.LogManager.GetLogger(typeof(ImgurUtils));
private const string SmallUrlPattern = "http://i.imgur.com/{0}s.jpg";
private static readonly ImgurConfiguration Config = IniConfig.GetIniSection<ImgurConfiguration>();
/// <summary> /// <summary>
/// Check if we need to load the history /// Check if we need to load the history
/// </summary> /// </summary>
/// <returns></returns> /// <returns></returns>
public static bool IsHistoryLoadingNeeded() public static bool IsHistoryLoadingNeeded()
{ {
Log.InfoFormat("Checking if imgur cache loading needed, configuration has {0} imgur hashes, loaded are {1} hashes.", Config.ImgurUploadHistory.Count, Config.runtimeImgurHistory.Count); Log.InfoFormat("Checking if imgur cache loading needed, configuration has {0} imgur hashes, loaded are {1} hashes.", Config.ImgurUploadHistory.Count,
return Config.runtimeImgurHistory.Count != Config.ImgurUploadHistory.Count; Config.runtimeImgurHistory.Count);
} return Config.runtimeImgurHistory.Count != Config.ImgurUploadHistory.Count;
}
/// <summary> /// <summary>
/// Load the complete history of the imgur uploads, with the corresponding information /// Load the complete history of the imgur uploads, with the corresponding information
/// </summary> /// </summary>
public static void LoadHistory() { public static void LoadHistory()
if (!IsHistoryLoadingNeeded()) {
{ if (!IsHistoryLoadingNeeded())
return; {
} return;
}
bool saveNeeded = false; bool saveNeeded = false;
// Load the ImUr history // Load the ImUr history
foreach (string hash in Config.ImgurUploadHistory.Keys.ToList()) { foreach (string hash in Config.ImgurUploadHistory.Keys.ToList())
if (Config.runtimeImgurHistory.ContainsKey(hash)) { {
// Already loaded if (Config.runtimeImgurHistory.ContainsKey(hash))
continue; {
} // Already loaded
continue;
}
try try
{ {
var deleteHash = Config.ImgurUploadHistory[hash]; var deleteHash = Config.ImgurUploadHistory[hash];
ImgurInfo imgurInfo = RetrieveImgurInfo(hash, deleteHash); ImgurInfo imgurInfo = RetrieveImgurInfo(hash, deleteHash);
if (imgurInfo != null) { if (imgurInfo != null)
RetrieveImgurThumbnail(imgurInfo); {
Config.runtimeImgurHistory[hash] = imgurInfo; RetrieveImgurThumbnail(imgurInfo);
} else { Config.runtimeImgurHistory[hash] = imgurInfo;
Log.InfoFormat("Deleting unknown ImgUr {0} from config, delete hash was {1}.", hash, deleteHash); }
Config.ImgurUploadHistory.Remove(hash); else
Config.runtimeImgurHistory.Remove(hash); {
saveNeeded = true; Log.InfoFormat("Deleting unknown ImgUr {0} from config, delete hash was {1}.", hash, deleteHash);
} Config.ImgurUploadHistory.Remove(hash);
} catch (WebException wE) { Config.runtimeImgurHistory.Remove(hash);
bool redirected = false; saveNeeded = true;
if (wE.Status == WebExceptionStatus.ProtocolError) { }
HttpWebResponse response = (HttpWebResponse)wE.Response; }
catch (WebException wE)
{
bool redirected = false;
if (wE.Status == WebExceptionStatus.ProtocolError)
{
HttpWebResponse response = (HttpWebResponse) wE.Response;
if (response.StatusCode == HttpStatusCode.Forbidden) if (response.StatusCode == HttpStatusCode.Forbidden)
{ {
Log.Error("Imgur loading forbidden", wE); Log.Error("Imgur loading forbidden", wE);
break; break;
} }
// Image no longer available?
if (response.StatusCode == HttpStatusCode.Redirect) {
Log.InfoFormat("ImgUr image for hash {0} is no longer available, removing it from the history", hash);
Config.ImgurUploadHistory.Remove(hash);
Config.runtimeImgurHistory.Remove(hash);
redirected = true;
}
}
if (!redirected) {
Log.Error("Problem loading ImgUr history for hash " + hash, wE);
}
} catch (Exception e) {
Log.Error("Problem loading ImgUr history for hash " + hash, e);
}
}
if (saveNeeded) {
// Save needed changes
IniConfig.Save();
}
}
/// <summary> // Image no longer available?
/// Use this to make sure Imgur knows from where the upload comes. if (response.StatusCode == HttpStatusCode.Redirect)
/// </summary> {
/// <param name="webRequest"></param> Log.InfoFormat("ImgUr image for hash {0} is no longer available, removing it from the history", hash);
private static void SetClientId(HttpWebRequest webRequest) { Config.ImgurUploadHistory.Remove(hash);
webRequest.Headers.Add("Authorization", "Client-ID " + ImgurCredentials.CONSUMER_KEY); Config.runtimeImgurHistory.Remove(hash);
} redirected = true;
}
}
/// <summary> if (!redirected)
/// Do the actual upload to Imgur {
/// For more details on the available parameters, see: http://api.imgur.com/resources_anon Log.Error("Problem loading ImgUr history for hash " + hash, wE);
/// </summary> }
/// <param name="surfaceToUpload">ISurface to upload</param> }
/// <param name="outputSettings">OutputSettings for the image file format</param> catch (Exception e)
/// <param name="title">Title</param> {
/// <param name="filename">Filename</param> Log.Error("Problem loading ImgUr history for hash " + hash, e);
/// <returns>ImgurInfo with details</returns> }
public static ImgurInfo UploadToImgur(ISurface surfaceToUpload, SurfaceOutputSettings outputSettings, string title, string filename) { }
IDictionary<string, object> otherParameters = new Dictionary<string, object>();
// add title
if (title != null && Config.AddTitle) {
otherParameters["title"]= title;
}
// add filename
if (filename != null && Config.AddFilename) {
otherParameters["name"] = filename;
}
string responseString = null;
if (Config.AnonymousAccess) {
// add key, we only use the other parameters for the AnonymousAccess
//otherParameters.Add("key", IMGUR_ANONYMOUS_API_KEY);
HttpWebRequest webRequest = NetworkHelper.CreateWebRequest(Config.ImgurApi3Url + "/upload.xml?" + NetworkHelper.GenerateQueryParameters(otherParameters), HTTPMethod.POST);
webRequest.ContentType = "image/" + outputSettings.Format;
webRequest.ServicePoint.Expect100Continue = false;
SetClientId(webRequest); if (saveNeeded)
try { {
using (var requestStream = webRequest.GetRequestStream()) { // Save needed changes
ImageOutput.SaveToStream(surfaceToUpload, requestStream, outputSettings); IniConfig.Save();
} }
}
/// <summary>
/// Use this to make sure Imgur knows from where the upload comes.
/// </summary>
/// <param name="webRequest"></param>
private static void SetClientId(HttpWebRequest webRequest)
{
webRequest.Headers.Add("Authorization", "Client-ID " + ImgurCredentials.CONSUMER_KEY);
}
/// <summary>
/// Do the actual upload to Imgur
/// For more details on the available parameters, see: http://api.imgur.com/resources_anon
/// </summary>
/// <param name="surfaceToUpload">ISurface to upload</param>
/// <param name="outputSettings">OutputSettings for the image file format</param>
/// <param name="title">Title</param>
/// <param name="filename">Filename</param>
/// <returns>ImgurInfo with details</returns>
public static ImgurInfo UploadToImgur(ISurface surfaceToUpload, SurfaceOutputSettings outputSettings, string title, string filename)
{
IDictionary<string, object> otherParameters = new Dictionary<string, object>();
// add title
if (title != null && Config.AddTitle)
{
otherParameters["title"] = title;
}
// add filename
if (filename != null && Config.AddFilename)
{
otherParameters["name"] = filename;
}
string responseString = null;
if (Config.AnonymousAccess)
{
// add key, we only use the other parameters for the AnonymousAccess
//otherParameters.Add("key", IMGUR_ANONYMOUS_API_KEY);
HttpWebRequest webRequest =
NetworkHelper.CreateWebRequest(Config.ImgurApi3Url + "/upload.xml?" + NetworkHelper.GenerateQueryParameters(otherParameters), HTTPMethod.POST);
webRequest.ContentType = "image/" + outputSettings.Format;
webRequest.ServicePoint.Expect100Continue = false;
SetClientId(webRequest);
try
{
using (var requestStream = webRequest.GetRequestStream())
{
ImageOutput.SaveToStream(surfaceToUpload, requestStream, outputSettings);
}
using WebResponse response = webRequest.GetResponse(); using WebResponse response = webRequest.GetResponse();
LogRateLimitInfo(response); LogRateLimitInfo(response);
@ -160,68 +190,76 @@ namespace Greenshot.Plugin.Imgur {
using StreamReader reader = new StreamReader(responseStream, true); using StreamReader reader = new StreamReader(responseStream, true);
responseString = reader.ReadToEnd(); responseString = reader.ReadToEnd();
} }
} catch (Exception ex) { }
Log.Error("Upload to imgur gave an exception: ", ex); catch (Exception ex)
throw; {
} Log.Error("Upload to imgur gave an exception: ", ex);
} else { throw;
}
}
else
{
var oauth2Settings = new OAuth2Settings
{
AuthUrlPattern = "https://api.imgur.com/oauth2/authorize?response_type=token&client_id={ClientId}&state={State}",
TokenUrl = "https://api.imgur.com/oauth2/token",
RedirectUrl = "https://getgreenshot.org/authorize/imgur",
CloudServiceName = "Imgur",
ClientId = ImgurCredentials.CONSUMER_KEY,
ClientSecret = ImgurCredentials.CONSUMER_SECRET,
AuthorizeMode = OAuth2AuthorizeMode.JsonReceiver,
RefreshToken = Config.RefreshToken,
AccessToken = Config.AccessToken,
AccessTokenExpires = Config.AccessTokenExpires
};
var oauth2Settings = new OAuth2Settings // Copy the settings from the config, which is kept in memory and on the disk
{
AuthUrlPattern = "https://api.imgur.com/oauth2/authorize?response_type=token&client_id={ClientId}&state={State}",
TokenUrl = "https://api.imgur.com/oauth2/token",
RedirectUrl = "https://getgreenshot.org/authorize/imgur",
CloudServiceName = "Imgur",
ClientId = ImgurCredentials.CONSUMER_KEY,
ClientSecret = ImgurCredentials.CONSUMER_SECRET,
AuthorizeMode = OAuth2AuthorizeMode.JsonReceiver,
RefreshToken = Config.RefreshToken,
AccessToken = Config.AccessToken,
AccessTokenExpires = Config.AccessTokenExpires
};
// Copy the settings from the config, which is kept in memory and on the disk try
{
var webRequest = OAuth2Helper.CreateOAuth2WebRequest(HTTPMethod.POST, Config.ImgurApi3Url + "/upload.xml", oauth2Settings);
otherParameters["image"] = new SurfaceContainer(surfaceToUpload, outputSettings, filename);
try NetworkHelper.WriteMultipartFormData(webRequest, otherParameters);
{
var webRequest = OAuth2Helper.CreateOAuth2WebRequest(HTTPMethod.POST, Config.ImgurApi3Url + "/upload.xml", oauth2Settings);
otherParameters["image"] = new SurfaceContainer(surfaceToUpload, outputSettings, filename);
NetworkHelper.WriteMultipartFormData(webRequest, otherParameters); responseString = NetworkHelper.GetResponseAsString(webRequest);
}
finally
{
// Copy the settings back to the config, so they are stored.
Config.RefreshToken = oauth2Settings.RefreshToken;
Config.AccessToken = oauth2Settings.AccessToken;
Config.AccessTokenExpires = oauth2Settings.AccessTokenExpires;
Config.IsDirty = true;
IniConfig.Save();
}
}
responseString = NetworkHelper.GetResponseAsString(webRequest); if (string.IsNullOrEmpty(responseString))
} {
finally return null;
{ }
// Copy the settings back to the config, so they are stored.
Config.RefreshToken = oauth2Settings.RefreshToken;
Config.AccessToken = oauth2Settings.AccessToken;
Config.AccessTokenExpires = oauth2Settings.AccessTokenExpires;
Config.IsDirty = true;
IniConfig.Save();
}
}
if (string.IsNullOrEmpty(responseString))
{
return null;
}
return ImgurInfo.ParseResponse(responseString);
}
/// <summary> return ImgurInfo.ParseResponse(responseString);
/// Retrieve the thumbnail of an imgur image }
/// </summary>
/// <param name="imgurInfo"></param> /// <summary>
public static void RetrieveImgurThumbnail(ImgurInfo imgurInfo) { /// Retrieve the thumbnail of an imgur image
if (imgurInfo.SmallSquare == null) { /// </summary>
Log.Warn("Imgur URL was null, not retrieving thumbnail."); /// <param name="imgurInfo"></param>
return; public static void RetrieveImgurThumbnail(ImgurInfo imgurInfo)
} {
Log.InfoFormat("Retrieving Imgur image for {0} with url {1}", imgurInfo.Hash, imgurInfo.SmallSquare); if (imgurInfo.SmallSquare == null)
HttpWebRequest webRequest = NetworkHelper.CreateWebRequest(string.Format(SmallUrlPattern, imgurInfo.Hash), HTTPMethod.GET); {
webRequest.ServicePoint.Expect100Continue = false; Log.Warn("Imgur URL was null, not retrieving thumbnail.");
// Not for getting the thumbnail, in anonymous mode return;
//SetClientId(webRequest); }
Log.InfoFormat("Retrieving Imgur image for {0} with url {1}", imgurInfo.Hash, imgurInfo.SmallSquare);
HttpWebRequest webRequest = NetworkHelper.CreateWebRequest(string.Format(SmallUrlPattern, imgurInfo.Hash), HTTPMethod.GET);
webRequest.ServicePoint.Expect100Continue = false;
// Not for getting the thumbnail, in anonymous mode
//SetClientId(webRequest);
using WebResponse response = webRequest.GetResponse(); using WebResponse response = webRequest.GetResponse();
LogRateLimitInfo(response); LogRateLimitInfo(response);
Stream responseStream = response.GetResponseStream(); Stream responseStream = response.GetResponseStream();
@ -231,20 +269,21 @@ namespace Greenshot.Plugin.Imgur {
} }
} }
/// <summary> /// <summary>
/// Retrieve information on an imgur image /// Retrieve information on an imgur image
/// </summary> /// </summary>
/// <param name="hash"></param> /// <param name="hash"></param>
/// <param name="deleteHash"></param> /// <param name="deleteHash"></param>
/// <returns>ImgurInfo</returns> /// <returns>ImgurInfo</returns>
public static ImgurInfo RetrieveImgurInfo(string hash, string deleteHash) { public static ImgurInfo RetrieveImgurInfo(string hash, string deleteHash)
string url = Config.ImgurApi3Url + "/image/" + hash + ".xml"; {
Log.InfoFormat("Retrieving Imgur info for {0} with url {1}", hash, url); string url = Config.ImgurApi3Url + "/image/" + hash + ".xml";
HttpWebRequest webRequest = NetworkHelper.CreateWebRequest(url, HTTPMethod.GET); Log.InfoFormat("Retrieving Imgur info for {0} with url {1}", hash, url);
webRequest.ServicePoint.Expect100Continue = false; HttpWebRequest webRequest = NetworkHelper.CreateWebRequest(url, HTTPMethod.GET);
SetClientId(webRequest); webRequest.ServicePoint.Expect100Continue = false;
string responseString = null; SetClientId(webRequest);
try string responseString = null;
try
{ {
using WebResponse response = webRequest.GetResponse(); using WebResponse response = webRequest.GetResponse();
LogRateLimitInfo(response); LogRateLimitInfo(response);
@ -254,95 +293,118 @@ namespace Greenshot.Plugin.Imgur {
using StreamReader reader = new StreamReader(responseStream, true); using StreamReader reader = new StreamReader(responseStream, true);
responseString = reader.ReadToEnd(); responseString = reader.ReadToEnd();
} }
} catch (WebException wE) { }
if (wE.Status == WebExceptionStatus.ProtocolError) { catch (WebException wE)
if (((HttpWebResponse)wE.Response).StatusCode == HttpStatusCode.NotFound) { {
return null; if (wE.Status == WebExceptionStatus.ProtocolError)
} {
} if (((HttpWebResponse) wE.Response).StatusCode == HttpStatusCode.NotFound)
throw; {
} return null;
ImgurInfo imgurInfo = null; }
if (responseString != null) }
{
Log.Debug(responseString);
imgurInfo = ImgurInfo.ParseResponse(responseString);
imgurInfo.DeleteHash = deleteHash;
}
return imgurInfo;
}
/// <summary> throw;
/// Delete an imgur image, this is done by specifying the delete hash }
/// </summary>
/// <param name="imgurInfo"></param>
public static void DeleteImgurImage(ImgurInfo imgurInfo) {
Log.InfoFormat("Deleting Imgur image for {0}", imgurInfo.DeleteHash);
try { ImgurInfo imgurInfo = null;
string url = Config.ImgurApi3Url + "/image/" + imgurInfo.DeleteHash + ".xml"; if (responseString != null)
HttpWebRequest webRequest = NetworkHelper.CreateWebRequest(url, HTTPMethod.DELETE); {
webRequest.ServicePoint.Expect100Continue = false; Log.Debug(responseString);
SetClientId(webRequest); imgurInfo = ImgurInfo.ParseResponse(responseString);
string responseString = null; imgurInfo.DeleteHash = deleteHash;
using (WebResponse response = webRequest.GetResponse()) { }
LogRateLimitInfo(response);
var responseStream = response.GetResponseStream(); return imgurInfo;
if (responseStream != null) }
/// <summary>
/// Delete an imgur image, this is done by specifying the delete hash
/// </summary>
/// <param name="imgurInfo"></param>
public static void DeleteImgurImage(ImgurInfo imgurInfo)
{
Log.InfoFormat("Deleting Imgur image for {0}", imgurInfo.DeleteHash);
try
{
string url = Config.ImgurApi3Url + "/image/" + imgurInfo.DeleteHash + ".xml";
HttpWebRequest webRequest = NetworkHelper.CreateWebRequest(url, HTTPMethod.DELETE);
webRequest.ServicePoint.Expect100Continue = false;
SetClientId(webRequest);
string responseString = null;
using (WebResponse response = webRequest.GetResponse())
{
LogRateLimitInfo(response);
var responseStream = response.GetResponseStream();
if (responseStream != null)
{ {
using StreamReader reader = new StreamReader(responseStream, true); using StreamReader reader = new StreamReader(responseStream, true);
responseString = reader.ReadToEnd(); responseString = reader.ReadToEnd();
} }
} }
Log.InfoFormat("Delete result: {0}", responseString);
} catch (WebException wE) {
// Allow "Bad request" this means we already deleted it
if (wE.Status == WebExceptionStatus.ProtocolError) {
if (((HttpWebResponse)wE.Response).StatusCode != HttpStatusCode.BadRequest) {
throw ;
}
}
}
// Make sure we remove it from the history, if no error occurred
Config.runtimeImgurHistory.Remove(imgurInfo.Hash);
Config.ImgurUploadHistory.Remove(imgurInfo.Hash);
imgurInfo.Image = null;
}
/// <summary> Log.InfoFormat("Delete result: {0}", responseString);
/// Helper for logging }
/// </summary> catch (WebException wE)
/// <param name="nameValues"></param> {
/// <param name="key"></param> // Allow "Bad request" this means we already deleted it
private static void LogHeader(IDictionary<string, string> nameValues, string key) { if (wE.Status == WebExceptionStatus.ProtocolError)
if (nameValues.ContainsKey(key)) { {
Log.InfoFormat("{0}={1}", key, nameValues[key]); if (((HttpWebResponse) wE.Response).StatusCode != HttpStatusCode.BadRequest)
} {
} throw;
}
}
}
/// <summary> // Make sure we remove it from the history, if no error occurred
/// Log the current rate-limit information Config.runtimeImgurHistory.Remove(imgurInfo.Hash);
/// </summary> Config.ImgurUploadHistory.Remove(imgurInfo.Hash);
/// <param name="response"></param> imgurInfo.Image = null;
private static void LogRateLimitInfo(WebResponse response) { }
IDictionary<string, string> nameValues = new Dictionary<string, string>();
foreach (string key in response.Headers.AllKeys) {
if (!nameValues.ContainsKey(key)) {
nameValues.Add(key, response.Headers[key]);
}
}
LogHeader(nameValues, "X-RateLimit-Limit");
LogHeader(nameValues, "X-RateLimit-Remaining");
LogHeader(nameValues, "X-RateLimit-UserLimit");
LogHeader(nameValues, "X-RateLimit-UserRemaining");
LogHeader(nameValues, "X-RateLimit-UserReset");
LogHeader(nameValues, "X-RateLimit-ClientLimit");
LogHeader(nameValues, "X-RateLimit-ClientRemaining");
// Update the credits in the config, this is shown in a form /// <summary>
if (nameValues.ContainsKey("X-RateLimit-Remaining") && int.TryParse(nameValues["X-RateLimit-Remaining"], out var credits)) { /// Helper for logging
Config.Credits = credits; /// </summary>
} /// <param name="nameValues"></param>
} /// <param name="key"></param>
} private static void LogHeader(IDictionary<string, string> nameValues, string key)
{
if (nameValues.ContainsKey(key))
{
Log.InfoFormat("{0}={1}", key, nameValues[key]);
}
}
/// <summary>
/// Log the current rate-limit information
/// </summary>
/// <param name="response"></param>
private static void LogRateLimitInfo(WebResponse response)
{
IDictionary<string, string> nameValues = new Dictionary<string, string>();
foreach (string key in response.Headers.AllKeys)
{
if (!nameValues.ContainsKey(key))
{
nameValues.Add(key, response.Headers[key]);
}
}
LogHeader(nameValues, "X-RateLimit-Limit");
LogHeader(nameValues, "X-RateLimit-Remaining");
LogHeader(nameValues, "X-RateLimit-UserLimit");
LogHeader(nameValues, "X-RateLimit-UserRemaining");
LogHeader(nameValues, "X-RateLimit-UserReset");
LogHeader(nameValues, "X-RateLimit-ClientLimit");
LogHeader(nameValues, "X-RateLimit-ClientRemaining");
// Update the credits in the config, this is shown in a form
if (nameValues.ContainsKey("X-RateLimit-Remaining") && int.TryParse(nameValues["X-RateLimit-Remaining"], out var credits))
{
Config.Credits = credits;
}
}
}
} }

View file

@ -19,15 +19,17 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>. * along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
namespace Greenshot.Plugin.Imgur { namespace Greenshot.Plugin.Imgur
public enum LangKey { {
upload_menu_item, public enum LangKey
upload_failure, {
communication_wait, upload_menu_item,
delete_question, upload_failure,
clear_question, communication_wait,
delete_title, delete_question,
history, clear_question,
configure delete_title,
} history,
configure
}
} }

View file

@ -27,164 +27,165 @@ using Dapplo.Log;
namespace Greenshot.Plugin.Jira namespace Greenshot.Plugin.Jira
{ {
/// <summary> /// <summary>
/// This abstract class builds a base for a simple async memory cache. /// This abstract class builds a base for a simple async memory cache.
/// </summary> /// </summary>
/// <typeparam name="TKey">Type for the key</typeparam> /// <typeparam name="TKey">Type for the key</typeparam>
/// <typeparam name="TResult">Type for the stored value</typeparam> /// <typeparam name="TResult">Type for the stored value</typeparam>
public abstract class AsyncMemoryCache<TKey, TResult> where TResult : class public abstract class AsyncMemoryCache<TKey, TResult> where TResult : class
{ {
private static readonly Task<TResult> EmptyValueTask = Task.FromResult<TResult>(null); private static readonly Task<TResult> EmptyValueTask = Task.FromResult<TResult>(null);
private readonly SemaphoreSlim _semaphoreSlim = new SemaphoreSlim(1, 1); private readonly SemaphoreSlim _semaphoreSlim = new SemaphoreSlim(1, 1);
private readonly MemoryCache _cache = new MemoryCache(Guid.NewGuid().ToString()); private readonly MemoryCache _cache = new MemoryCache(Guid.NewGuid().ToString());
private readonly LogSource _log = new LogSource(); private readonly LogSource _log = new LogSource();
/// <summary>
/// Set the timespan for items to expire.
/// </summary>
public TimeSpan? ExpireTimeSpan { get; set; }
/// <summary>
/// Set the timespan for items to slide.
/// </summary>
public TimeSpan? SlidingTimeSpan { get; set; }
/// <summary>
/// Specifies if the RemovedCallback needs to be called
/// If this is active, ActivateUpdateCallback should be false
/// </summary>
protected bool ActivateRemovedCallback { get; set; } = true;
/// <summary>
/// Specifies if the UpdateCallback needs to be called.
/// If this is active, ActivateRemovedCallback should be false
/// </summary>
protected bool ActivateUpdateCallback { get; set; } = false;
/// <summary>
/// Implement this method, it should create an instance of TResult via the supplied TKey.
/// </summary>
/// <param name="key">TKey</param>
/// <param name="cancellationToken">CancellationToken</param>
/// <returns>TResult</returns>
protected abstract Task<TResult> CreateAsync(TKey key, CancellationToken cancellationToken = default);
/// <summary>
/// Creates a key under which the object is stored or retrieved, default is a toString on the object.
/// </summary>
/// <param name="keyObject">TKey</param>
/// <returns>string</returns>
protected virtual string CreateKey(TKey keyObject)
{
return keyObject.ToString();
}
/// <summary> /// <summary>
/// Get a task element from the cache, if this is not available call the create function. /// Set the timespan for items to expire.
/// </summary> /// </summary>
/// <param name="keyObject">object for the key</param> public TimeSpan? ExpireTimeSpan { get; set; }
/// <param name="cancellationToken">CancellationToken</param>
/// <returns>Task with TResult</returns>
public Task<TResult> GetOrCreateAsync(TKey keyObject, CancellationToken cancellationToken = default)
{
var key = CreateKey(keyObject);
return _cache.Get(key) as Task<TResult> ?? GetOrCreateInternalAsync(keyObject, null, cancellationToken);
}
/// <summary> /// <summary>
/// This takes care of the real async part of the code. /// Set the timespan for items to slide.
/// </summary> /// </summary>
/// <param name="keyObject"></param> public TimeSpan? SlidingTimeSpan { get; set; }
/// <param name="cacheItemPolicy">CacheItemPolicy for when you want more control over the item</param>
/// <param name="cancellationToken">CancellationToken</param>
/// <returns>TResult</returns>
private async Task<TResult> GetOrCreateInternalAsync(TKey keyObject, CacheItemPolicy cacheItemPolicy = null, CancellationToken cancellationToken = default)
{
var key = CreateKey(keyObject);
var completionSource = new TaskCompletionSource<TResult>();
if (cacheItemPolicy == null) /// <summary>
{ /// Specifies if the RemovedCallback needs to be called
cacheItemPolicy = new CacheItemPolicy /// If this is active, ActivateUpdateCallback should be false
{ /// </summary>
AbsoluteExpiration = ExpireTimeSpan.HasValue ? DateTimeOffset.Now.Add(ExpireTimeSpan.Value) : ObjectCache.InfiniteAbsoluteExpiration, protected bool ActivateRemovedCallback { get; set; } = true;
SlidingExpiration = SlidingTimeSpan ?? ObjectCache.NoSlidingExpiration
}; /// <summary>
if (ActivateUpdateCallback) /// Specifies if the UpdateCallback needs to be called.
{ /// If this is active, ActivateRemovedCallback should be false
cacheItemPolicy.UpdateCallback = UpdateCallback; /// </summary>
} protected bool ActivateUpdateCallback { get; set; } = false;
if (ActivateRemovedCallback)
{ /// <summary>
cacheItemPolicy.RemovedCallback = RemovedCallback; /// Implement this method, it should create an instance of TResult via the supplied TKey.
} /// </summary>
} /// <param name="key">TKey</param>
/// <param name="cancellationToken">CancellationToken</param>
/// <returns>TResult</returns>
protected abstract Task<TResult> CreateAsync(TKey key, CancellationToken cancellationToken = default);
/// <summary>
/// Creates a key under which the object is stored or retrieved, default is a toString on the object.
/// </summary>
/// <param name="keyObject">TKey</param>
/// <returns>string</returns>
protected virtual string CreateKey(TKey keyObject)
{
return keyObject.ToString();
}
/// <summary>
/// Get a task element from the cache, if this is not available call the create function.
/// </summary>
/// <param name="keyObject">object for the key</param>
/// <param name="cancellationToken">CancellationToken</param>
/// <returns>Task with TResult</returns>
public Task<TResult> GetOrCreateAsync(TKey keyObject, CancellationToken cancellationToken = default)
{
var key = CreateKey(keyObject);
return _cache.Get(key) as Task<TResult> ?? GetOrCreateInternalAsync(keyObject, null, cancellationToken);
}
/// <summary>
/// This takes care of the real async part of the code.
/// </summary>
/// <param name="keyObject"></param>
/// <param name="cacheItemPolicy">CacheItemPolicy for when you want more control over the item</param>
/// <param name="cancellationToken">CancellationToken</param>
/// <returns>TResult</returns>
private async Task<TResult> GetOrCreateInternalAsync(TKey keyObject, CacheItemPolicy cacheItemPolicy = null, CancellationToken cancellationToken = default)
{
var key = CreateKey(keyObject);
var completionSource = new TaskCompletionSource<TResult>();
if (cacheItemPolicy == null)
{
cacheItemPolicy = new CacheItemPolicy
{
AbsoluteExpiration = ExpireTimeSpan.HasValue ? DateTimeOffset.Now.Add(ExpireTimeSpan.Value) : ObjectCache.InfiniteAbsoluteExpiration,
SlidingExpiration = SlidingTimeSpan ?? ObjectCache.NoSlidingExpiration
};
if (ActivateUpdateCallback)
{
cacheItemPolicy.UpdateCallback = UpdateCallback;
}
if (ActivateRemovedCallback)
{
cacheItemPolicy.RemovedCallback = RemovedCallback;
}
}
// Test if we got an existing object or our own // Test if we got an existing object or our own
if (_cache.AddOrGetExisting(key, completionSource.Task, cacheItemPolicy) is Task<TResult> result && !completionSource.Task.Equals(result)) if (_cache.AddOrGetExisting(key, completionSource.Task, cacheItemPolicy) is Task<TResult> result && !completionSource.Task.Equals(result))
{ {
return await result.ConfigureAwait(false); return await result.ConfigureAwait(false);
} }
await _semaphoreSlim.WaitAsync(cancellationToken).ConfigureAwait(false); await _semaphoreSlim.WaitAsync(cancellationToken).ConfigureAwait(false);
try try
{ {
result = _cache.AddOrGetExisting(key, completionSource.Task, cacheItemPolicy) as Task<TResult>; result = _cache.AddOrGetExisting(key, completionSource.Task, cacheItemPolicy) as Task<TResult>;
if (result != null && !completionSource.Task.Equals(result)) if (result != null && !completionSource.Task.Equals(result))
{ {
return await result.ConfigureAwait(false); return await result.ConfigureAwait(false);
} }
// Now, start the background task, which will set the completionSource with the correct response // Now, start the background task, which will set the completionSource with the correct response
// ReSharper disable once MethodSupportsCancellation // ReSharper disable once MethodSupportsCancellation
// ReSharper disable once UnusedVariable // ReSharper disable once UnusedVariable
var ignoreBackgroundTask = Task.Run(async () => var ignoreBackgroundTask = Task.Run(async () =>
{ {
try try
{ {
var backgroundResult = await CreateAsync(keyObject, cancellationToken).ConfigureAwait(false); var backgroundResult = await CreateAsync(keyObject, cancellationToken).ConfigureAwait(false);
completionSource.TrySetResult(backgroundResult); completionSource.TrySetResult(backgroundResult);
} }
catch (TaskCanceledException) catch (TaskCanceledException)
{ {
completionSource.TrySetCanceled(); completionSource.TrySetCanceled();
} }
catch (Exception ex) catch (Exception ex)
{ {
completionSource.TrySetException(ex); completionSource.TrySetException(ex);
} }
}); });
} }
finally finally
{ {
_semaphoreSlim.Release(); _semaphoreSlim.Release();
} }
return await completionSource.Task.ConfigureAwait(false); return await completionSource.Task.ConfigureAwait(false);
} }
/// <summary> /// <summary>
/// Override to know when an item is removed, make sure to configure ActivateUpdateCallback / ActivateRemovedCallback /// Override to know when an item is removed, make sure to configure ActivateUpdateCallback / ActivateRemovedCallback
/// </summary> /// </summary>
/// <param name="cacheEntryRemovedArguments">CacheEntryRemovedArguments</param> /// <param name="cacheEntryRemovedArguments">CacheEntryRemovedArguments</param>
protected void RemovedCallback(CacheEntryRemovedArguments cacheEntryRemovedArguments) protected void RemovedCallback(CacheEntryRemovedArguments cacheEntryRemovedArguments)
{ {
_log.Verbose().WriteLine("Item {0} removed due to {1}.", cacheEntryRemovedArguments.CacheItem.Key, cacheEntryRemovedArguments.RemovedReason); _log.Verbose().WriteLine("Item {0} removed due to {1}.", cacheEntryRemovedArguments.CacheItem.Key, cacheEntryRemovedArguments.RemovedReason);
if (cacheEntryRemovedArguments.CacheItem.Value is IDisposable disposable) if (cacheEntryRemovedArguments.CacheItem.Value is IDisposable disposable)
{ {
_log.Debug().WriteLine("Disposed cached item."); _log.Debug().WriteLine("Disposed cached item.");
disposable.Dispose(); disposable.Dispose();
} }
} }
/// <summary> /// <summary>
/// Override to modify the cache behaviour when an item is about to be removed, make sure to configure /// Override to modify the cache behaviour when an item is about to be removed, make sure to configure
/// ActivateUpdateCallback / ActivateRemovedCallback /// ActivateUpdateCallback / ActivateRemovedCallback
/// </summary> /// </summary>
/// <param name="cacheEntryUpdateArguments">CacheEntryUpdateArguments</param> /// <param name="cacheEntryUpdateArguments">CacheEntryUpdateArguments</param>
protected void UpdateCallback(CacheEntryUpdateArguments cacheEntryUpdateArguments) protected void UpdateCallback(CacheEntryUpdateArguments cacheEntryUpdateArguments)
{ {
_log.Verbose().WriteLine("Update request for {0} due to {1}.", cacheEntryUpdateArguments.Key, cacheEntryUpdateArguments.RemovedReason); _log.Verbose().WriteLine("Update request for {0} due to {1}.", cacheEntryUpdateArguments.Key, cacheEntryUpdateArguments.RemovedReason);
} }
} }
} }

View file

@ -30,207 +30,242 @@ using GreenshotPlugin.Controls;
using GreenshotPlugin.Core; using GreenshotPlugin.Core;
using GreenshotPlugin.IniFile; using GreenshotPlugin.IniFile;
namespace Greenshot.Plugin.Jira.Forms { namespace Greenshot.Plugin.Jira.Forms
public partial class JiraForm : Form { {
private static readonly log4net.ILog Log = log4net.LogManager.GetLogger(typeof(JiraForm)); public partial class JiraForm : Form
private readonly JiraConnector _jiraConnector; {
private Issue _selectedIssue; private static readonly log4net.ILog Log = log4net.LogManager.GetLogger(typeof(JiraForm));
private readonly GreenshotColumnSorter _columnSorter; private readonly JiraConnector _jiraConnector;
private static readonly CoreConfiguration CoreConfig = IniConfig.GetIniSection<CoreConfiguration>(); private Issue _selectedIssue;
private readonly GreenshotColumnSorter _columnSorter;
private static readonly CoreConfiguration CoreConfig = IniConfig.GetIniSection<CoreConfiguration>();
public JiraForm(JiraConnector jiraConnector) { public JiraForm(JiraConnector jiraConnector)
InitializeComponent(); {
Icon = GreenshotResources.GetGreenshotIcon(); InitializeComponent();
AcceptButton = uploadButton; Icon = GreenshotResources.GetGreenshotIcon();
CancelButton = cancelButton; AcceptButton = uploadButton;
CancelButton = cancelButton;
InitializeComponentText(); InitializeComponentText();
_columnSorter = new GreenshotColumnSorter(); _columnSorter = new GreenshotColumnSorter();
jiraListView.ListViewItemSorter = _columnSorter; jiraListView.ListViewItemSorter = _columnSorter;
_jiraConnector = jiraConnector; _jiraConnector = jiraConnector;
ChangeModus(false); ChangeModus(false);
uploadButton.Enabled = false; uploadButton.Enabled = false;
Load += OnLoad; Load += OnLoad;
} }
private async void OnLoad(object sender, EventArgs eventArgs) private async void OnLoad(object sender, EventArgs eventArgs)
{ {
try try
{ {
if (!_jiraConnector.IsLoggedIn) if (!_jiraConnector.IsLoggedIn)
{ {
await _jiraConnector.LoginAsync(); await _jiraConnector.LoginAsync();
} }
} }
catch (Exception e) catch (Exception e)
{ {
MessageBox.Show(Language.GetFormattedString("jira", LangKey.login_error, e.Message)); MessageBox.Show(Language.GetFormattedString("jira", LangKey.login_error, e.Message));
} }
if (_jiraConnector.IsLoggedIn)
{
var filters = await _jiraConnector.GetFavoriteFiltersAsync();
if (filters.Count > 0)
{
foreach (var filter in filters)
{
jiraFilterBox.Items.Add(filter);
}
jiraFilterBox.SelectedIndex = 0;
}
ChangeModus(true);
if (_jiraConnector.Monitor.RecentJiras.Any())
{
_selectedIssue = _jiraConnector.Monitor.RecentJiras.First().JiraIssue;
jiraKey.Text = _selectedIssue.Key;
uploadButton.Enabled = true;
}
}
}
private void InitializeComponentText() { if (_jiraConnector.IsLoggedIn)
label_jirafilter.Text = Language.GetString("jira", LangKey.label_jirafilter); {
label_comment.Text = Language.GetString("jira", LangKey.label_comment); var filters = await _jiraConnector.GetFavoriteFiltersAsync();
label_filename.Text = Language.GetString("jira", LangKey.label_filename); if (filters.Count > 0)
} {
foreach (var filter in filters)
{
jiraFilterBox.Items.Add(filter);
}
private void ChangeModus(bool enabled) { jiraFilterBox.SelectedIndex = 0;
jiraFilterBox.Enabled = enabled; }
jiraListView.Enabled = enabled;
jiraFilenameBox.Enabled = enabled;
jiraCommentBox.Enabled = enabled;
}
public void SetFilename(string filename) { ChangeModus(true);
jiraFilenameBox.Text = filename; if (_jiraConnector.Monitor.RecentJiras.Any())
} {
_selectedIssue = _jiraConnector.Monitor.RecentJiras.First().JiraIssue;
jiraKey.Text = _selectedIssue.Key;
uploadButton.Enabled = true;
}
}
}
public Issue GetJiraIssue() { private void InitializeComponentText()
return _selectedIssue; {
} label_jirafilter.Text = Language.GetString("jira", LangKey.label_jirafilter);
label_comment.Text = Language.GetString("jira", LangKey.label_comment);
label_filename.Text = Language.GetString("jira", LangKey.label_filename);
}
public async Task UploadAsync(IBinaryContainer attachment) { private void ChangeModus(bool enabled)
attachment.Filename = jiraFilenameBox.Text; {
await _jiraConnector.AttachAsync(_selectedIssue.Key, attachment); jiraFilterBox.Enabled = enabled;
jiraListView.Enabled = enabled;
jiraFilenameBox.Enabled = enabled;
jiraCommentBox.Enabled = enabled;
}
if (!string.IsNullOrEmpty(jiraCommentBox.Text)) { public void SetFilename(string filename)
await _jiraConnector.AddCommentAsync(_selectedIssue.Key, jiraCommentBox.Text); {
} jiraFilenameBox.Text = filename;
} }
private async void JiraFilterBox_SelectedIndexChanged(object sender, EventArgs e) { public Issue GetJiraIssue()
if (_jiraConnector.IsLoggedIn) { {
return _selectedIssue;
}
uploadButton.Enabled = false; public async Task UploadAsync(IBinaryContainer attachment)
var filter = (Filter)jiraFilterBox.SelectedItem; {
if (filter == null) { attachment.Filename = jiraFilenameBox.Text;
return; await _jiraConnector.AttachAsync(_selectedIssue.Key, attachment);
}
IList<Issue> issues = null;
try
{
issues = await _jiraConnector.SearchAsync(filter);
}
catch (Exception ex)
{
Log.Error(ex);
MessageBox.Show(this, ex.Message, "Error in filter", MessageBoxButtons.OK, MessageBoxIcon.Error);
}
jiraListView.Items.Clear(); if (!string.IsNullOrEmpty(jiraCommentBox.Text))
if (issues?.Count > 0) { {
jiraListView.Columns.Clear(); await _jiraConnector.AddCommentAsync(_selectedIssue.Key, jiraCommentBox.Text);
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) }
{
if (!Language.TryGetString("jira", column, out var translation))
{
translation = string.Empty;
}
jiraListView.Columns.Add(translation);
}
var scaledIconSize = DpiHelper.ScaleWithDpi(CoreConfig.IconSize, DpiHelper.GetDpi(Handle));
var imageList = new ImageList {
ImageSize = scaledIconSize
};
jiraListView.SmallImageList = imageList;
jiraListView.LargeImageList = imageList;
foreach (var issue in issues) { private async void JiraFilterBox_SelectedIndexChanged(object sender, EventArgs e)
var item = new ListViewItem {
{ if (_jiraConnector.IsLoggedIn)
Tag = issue {
}; uploadButton.Enabled = false;
try var filter = (Filter) jiraFilterBox.SelectedItem;
{ if (filter == null)
var issueIcon = await _jiraConnector.GetIssueTypeBitmapAsync(issue); {
imageList.Images.Add(issueIcon); return;
item.ImageIndex = imageList.Images.Count - 1; }
}
catch (Exception ex)
{
Log.Warn("Problem loading issue type, ignoring", ex);
}
item.SubItems.Add(issue.Key); IList<Issue> issues = null;
try
{
issues = await _jiraConnector.SearchAsync(filter);
}
catch (Exception ex)
{
Log.Error(ex);
MessageBox.Show(this, ex.Message, "Error in filter", MessageBoxButtons.OK, MessageBoxIcon.Error);
}
jiraListView.Items.Clear();
if (issues?.Count > 0)
{
jiraListView.Columns.Clear();
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)
{
if (!Language.TryGetString("jira", column, out var translation))
{
translation = string.Empty;
}
jiraListView.Columns.Add(translation);
}
var scaledIconSize = DpiHelper.ScaleWithDpi(CoreConfig.IconSize, DpiHelper.GetDpi(Handle));
var imageList = new ImageList
{
ImageSize = scaledIconSize
};
jiraListView.SmallImageList = imageList;
jiraListView.LargeImageList = imageList;
foreach (var issue in issues)
{
var item = new ListViewItem
{
Tag = issue
};
try
{
var issueIcon = await _jiraConnector.GetIssueTypeBitmapAsync(issue);
imageList.Images.Add(issueIcon);
item.ImageIndex = imageList.Images.Count - 1;
}
catch (Exception ex)
{
Log.Warn("Problem loading issue type, ignoring", ex);
}
item.SubItems.Add(issue.Key);
item.SubItems.Add(issue.Fields.Created.HasValue item.SubItems.Add(issue.Fields.Created.HasValue
? issue.Fields.Created.Value.ToString("d", DateTimeFormatInfo.InvariantInfo) ? issue.Fields.Created.Value.ToString("d", DateTimeFormatInfo.InvariantInfo)
: string.Empty); : string.Empty);
item.SubItems.Add(issue.Fields.Assignee?.DisplayName); item.SubItems.Add(issue.Fields.Assignee?.DisplayName);
item.SubItems.Add(issue.Fields.Reporter?.DisplayName); item.SubItems.Add(issue.Fields.Reporter?.DisplayName);
item.SubItems.Add(issue.Fields.Summary); item.SubItems.Add(issue.Fields.Summary);
jiraListView.Items.Add(item); jiraListView.Items.Add(item);
for (int i = 0; i < columns.Length; i++) for (int i = 0; i < columns.Length; i++)
{ {
jiraListView.AutoResizeColumn(i, ColumnHeaderAutoResizeStyle.ColumnContent); jiraListView.AutoResizeColumn(i, ColumnHeaderAutoResizeStyle.ColumnContent);
} }
jiraListView.Invalidate();
jiraListView.Update();
}
jiraListView.Refresh(); jiraListView.Invalidate();
} jiraListView.Update();
} }
}
private void JiraListView_SelectedIndexChanged(object sender, EventArgs e) { jiraListView.Refresh();
if (jiraListView.SelectedItems.Count > 0) { }
_selectedIssue = (Issue)jiraListView.SelectedItems[0].Tag; }
jiraKey.Text = _selectedIssue.Key; }
uploadButton.Enabled = true;
} else {
uploadButton.Enabled = false;
}
}
private void JiraListView_ColumnClick(object sender, ColumnClickEventArgs e) { private void JiraListView_SelectedIndexChanged(object sender, EventArgs e)
// Determine if clicked column is already the column that is being sorted. {
if (e.Column == _columnSorter.SortColumn) { if (jiraListView.SelectedItems.Count > 0)
// Reverse the current sort direction for this column. {
_columnSorter.Order = _columnSorter.Order == SortOrder.Ascending ? SortOrder.Descending : SortOrder.Ascending; _selectedIssue = (Issue) jiraListView.SelectedItems[0].Tag;
} else { jiraKey.Text = _selectedIssue.Key;
// Set the column number that is to be sorted; default to ascending. uploadButton.Enabled = true;
_columnSorter.SortColumn = e.Column; }
_columnSorter.Order = SortOrder.Ascending; else
} {
uploadButton.Enabled = false;
}
}
// Perform the sort with these new sort options. private void JiraListView_ColumnClick(object sender, ColumnClickEventArgs e)
jiraListView.Sort(); {
} // Determine if clicked column is already the column that is being sorted.
if (e.Column == _columnSorter.SortColumn)
{
// Reverse the current sort direction for this column.
_columnSorter.Order = _columnSorter.Order == SortOrder.Ascending ? SortOrder.Descending : SortOrder.Ascending;
}
else
{
// Set the column number that is to be sorted; default to ascending.
_columnSorter.SortColumn = e.Column;
_columnSorter.Order = SortOrder.Ascending;
}
private async void JiraKeyTextChanged(object sender, EventArgs e) { // Perform the sort with these new sort options.
string jiranumber = jiraKey.Text; jiraListView.Sort();
uploadButton.Enabled = false; }
int dashIndex = jiranumber.IndexOf('-');
if (dashIndex > 0 && jiranumber.Length > dashIndex+1) { private async void JiraKeyTextChanged(object sender, EventArgs e)
_selectedIssue = await _jiraConnector.GetIssueAsync(jiraKey.Text); {
if (_selectedIssue != null) { string jiranumber = jiraKey.Text;
uploadButton.Enabled = true; uploadButton.Enabled = false;
} int dashIndex = jiranumber.IndexOf('-');
} if (dashIndex > 0 && jiranumber.Length > dashIndex + 1)
} {
} _selectedIssue = await _jiraConnector.GetIssueAsync(jiraKey.Text);
if (_selectedIssue != null)
{
uploadButton.Enabled = true;
}
}
}
}
} }

View file

@ -21,7 +21,9 @@
using GreenshotPlugin.Controls; using GreenshotPlugin.Controls;
namespace Greenshot.Plugin.Jira.Forms { namespace Greenshot.Plugin.Jira.Forms
public class JiraFormBase : GreenshotForm { {
} public class JiraFormBase : GreenshotForm
{
}
} }

View file

@ -19,19 +19,21 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>. * along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
namespace Greenshot.Plugin.Jira.Forms { namespace Greenshot.Plugin.Jira.Forms
/// <summary> {
/// Description of PasswordRequestForm. /// <summary>
/// </summary> /// Description of PasswordRequestForm.
public partial class SettingsForm : JiraFormBase { /// </summary>
public SettingsForm() public partial class SettingsForm : JiraFormBase
{ {
// public SettingsForm()
// The InitializeComponent() call is required for Windows Forms designer support. {
// //
InitializeComponent(); // The InitializeComponent() call is required for Windows Forms designer support.
AcceptButton = buttonOK; //
CancelButton = buttonCancel; InitializeComponent();
} AcceptButton = buttonOK;
} CancelButton = buttonCancel;
}
}
} }

View file

@ -28,28 +28,28 @@ using Dapplo.Jira.Entities;
namespace Greenshot.Plugin.Jira namespace Greenshot.Plugin.Jira
{ {
/// <summary> /// <summary>
/// This is the bach for the IssueType bitmaps /// This is the bach for the IssueType bitmaps
/// </summary> /// </summary>
public class IssueTypeBitmapCache : AsyncMemoryCache<IssueType, Bitmap> public class IssueTypeBitmapCache : AsyncMemoryCache<IssueType, Bitmap>
{ {
private readonly IJiraClient _jiraClient; private readonly IJiraClient _jiraClient;
public IssueTypeBitmapCache(IJiraClient jiraClient) public IssueTypeBitmapCache(IJiraClient jiraClient)
{ {
_jiraClient = jiraClient; _jiraClient = jiraClient;
// Set the expire timeout to an hour // Set the expire timeout to an hour
ExpireTimeSpan = TimeSpan.FromHours(4); ExpireTimeSpan = TimeSpan.FromHours(4);
} }
protected override string CreateKey(IssueType keyObject) protected override string CreateKey(IssueType keyObject)
{ {
return keyObject.Name; return keyObject.Name;
} }
protected override async Task<Bitmap> CreateAsync(IssueType issueType, CancellationToken cancellationToken = new CancellationToken()) protected override async Task<Bitmap> CreateAsync(IssueType issueType, CancellationToken cancellationToken = new CancellationToken())
{ {
return await _jiraClient.Server.GetUriContentAsync<Bitmap>(issueType.IconUri, cancellationToken).ConfigureAwait(false); return await _jiraClient.Server.GetUriContentAsync<Bitmap>(issueType.IconUri, cancellationToken).ConfigureAwait(false);
} }
} }
} }

View file

@ -22,25 +22,27 @@
using GreenshotPlugin.Core; using GreenshotPlugin.Core;
using GreenshotPlugin.IniFile; using GreenshotPlugin.IniFile;
namespace Greenshot.Plugin.Jira { namespace Greenshot.Plugin.Jira
/// <summary> {
/// Description of JiraConfiguration. /// <summary>
/// </summary> /// Description of JiraConfiguration.
[IniSection("Jira", Description="Greenshot Jira Plugin configuration")] /// </summary>
public class JiraConfiguration : IniSection { [IniSection("Jira", Description = "Greenshot Jira Plugin configuration")]
public const string DefaultPrefix = "http://"; public class JiraConfiguration : IniSection
private const string DefaultUrl = DefaultPrefix + "jira"; {
public const string DefaultPrefix = "http://";
private const string DefaultUrl = DefaultPrefix + "jira";
[IniProperty("Url", Description="Base url to Jira system, without anything else", DefaultValue=DefaultUrl)] [IniProperty("Url", Description = "Base url to Jira system, without anything else", DefaultValue = DefaultUrl)]
public string Url { get; set; } public string Url { get; set; }
[IniProperty("UploadFormat", Description="What file type to use for uploading", DefaultValue="png")] [IniProperty("UploadFormat", Description = "What file type to use for uploading", DefaultValue = "png")]
public OutputFormat UploadFormat { get; set; } public OutputFormat UploadFormat { get; set; }
[IniProperty("UploadJpegQuality", Description="JPEG file save quality in %.", DefaultValue="80")] [IniProperty("UploadJpegQuality", Description = "JPEG file save quality in %.", DefaultValue = "80")]
public int UploadJpegQuality { get; set; } public int UploadJpegQuality { get; set; }
[IniProperty("UploadReduceColors", Description="Reduce color amount of the uploaded image to 256", DefaultValue="False")] [IniProperty("UploadReduceColors", Description = "Reduce color amount of the uploaded image to 256", DefaultValue = "False")]
public bool UploadReduceColors { get; set; } public bool UploadReduceColors { get; set; }
} }
} }

View file

@ -34,247 +34,280 @@ using Dapplo.Jira.SvgWinForms.Converters;
using GreenshotPlugin.Core; using GreenshotPlugin.Core;
using GreenshotPlugin.IniFile; using GreenshotPlugin.IniFile;
namespace Greenshot.Plugin.Jira { namespace Greenshot.Plugin.Jira
/// <summary> {
/// This encapsulates the JiraClient to make it possible to change as less old Greenshot code as needed /// <summary>
/// </summary> /// This encapsulates the JiraClient to make it possible to change as less old Greenshot code as needed
public sealed class JiraConnector : IDisposable { /// </summary>
private static readonly log4net.ILog Log = log4net.LogManager.GetLogger(typeof(JiraConnector)); public sealed class JiraConnector : IDisposable
private static readonly JiraConfiguration JiraConfig = IniConfig.GetIniSection<JiraConfiguration>(); {
private static readonly CoreConfiguration CoreConfig = IniConfig.GetIniSection<CoreConfiguration>(); private static readonly log4net.ILog Log = log4net.LogManager.GetLogger(typeof(JiraConnector));
// Used to remove the wsdl information from the old SOAP Uri private static readonly JiraConfiguration JiraConfig = IniConfig.GetIniSection<JiraConfiguration>();
public const string DefaultPostfix = "/rpc/soap/jirasoapservice-v2?wsdl";
private IJiraClient _jiraClient;
private IssueTypeBitmapCache _issueTypeBitmapCache;
/// <summary> private static readonly CoreConfiguration CoreConfig = IniConfig.GetIniSection<CoreConfiguration>();
/// Initialize some basic stuff, in the case the SVG to bitmap converter
/// </summary> // Used to remove the wsdl information from the old SOAP Uri
static JiraConnector() public const string DefaultPostfix = "/rpc/soap/jirasoapservice-v2?wsdl";
{ private IJiraClient _jiraClient;
CoreConfig.PropertyChanged += (sender, args) => private IssueTypeBitmapCache _issueTypeBitmapCache;
{
if (args.PropertyName == nameof(CoreConfig.IconSize)) /// <summary>
/// Initialize some basic stuff, in the case the SVG to bitmap converter
/// </summary>
static JiraConnector()
{
CoreConfig.PropertyChanged += (sender, args) =>
{
if (args.PropertyName == nameof(CoreConfig.IconSize))
{ {
var jiraConnector = SimpleServiceProvider.Current.GetInstance<JiraConnector>(); var jiraConnector = SimpleServiceProvider.Current.GetInstance<JiraConnector>();
jiraConnector._jiraClient?.Behaviour.SetConfig(new SvgConfiguration { Width = CoreConfig.ScaledIconSize.Width, Height = CoreConfig.ScaledIconSize.Height }); jiraConnector._jiraClient?.Behaviour.SetConfig(new SvgConfiguration
} {
}; Width = CoreConfig.ScaledIconSize.Width,
Height = CoreConfig.ScaledIconSize.Height
});
}
};
}
} /// <summary>
/// Dispose, logout the users
/// <summary> /// </summary>
/// Dispose, logout the users public void Dispose()
/// </summary> {
public void Dispose() { if (_jiraClient != null)
if (_jiraClient != null) {
{
Logout(); Logout();
} }
FavIcon?.Dispose();
}
/// <summary> FavIcon?.Dispose();
/// Constructor }
/// </summary>
public JiraConnector()
{
JiraConfig.Url = JiraConfig.Url.Replace(DefaultPostfix, string.Empty);
}
/// <summary> /// <summary>
/// Access the jira monitor /// Constructor
/// </summary> /// </summary>
public JiraMonitor Monitor { get; private set; } public JiraConnector()
{
JiraConfig.Url = JiraConfig.Url.Replace(DefaultPostfix, string.Empty);
}
public Bitmap FavIcon { get; private set; } /// <summary>
/// Access the jira monitor
/// </summary>
public JiraMonitor Monitor { get; private set; }
/// <summary> public Bitmap FavIcon { get; private set; }
/// Internal login which catches the exceptions
/// </summary> /// <summary>
/// <returns>true if login was done successfully</returns> /// Internal login which catches the exceptions
private async Task<bool> DoLoginAsync(string user, string password, CancellationToken cancellationToken = default) /// </summary>
{ /// <returns>true if login was done successfully</returns>
if (string.IsNullOrEmpty(user) || string.IsNullOrEmpty(password)) private async Task<bool> DoLoginAsync(string user, string password, CancellationToken cancellationToken = default)
{ {
return false; if (string.IsNullOrEmpty(user) || string.IsNullOrEmpty(password))
} {
_jiraClient = JiraClient.Create(new Uri(JiraConfig.Url)); return false;
_jiraClient.Behaviour.SetConfig(new SvgConfiguration { Width = CoreConfig.ScaledIconSize.Width, Height = CoreConfig.ScaledIconSize.Height }); }
_jiraClient = JiraClient.Create(new Uri(JiraConfig.Url));
_jiraClient.Behaviour.SetConfig(new SvgConfiguration
{
Width = CoreConfig.ScaledIconSize.Width,
Height = CoreConfig.ScaledIconSize.Height
});
_jiraClient.SetBasicAuthentication(user, password); _jiraClient.SetBasicAuthentication(user, password);
_issueTypeBitmapCache = new IssueTypeBitmapCache(_jiraClient); _issueTypeBitmapCache = new IssueTypeBitmapCache(_jiraClient);
try try
{ {
Monitor = new JiraMonitor(); Monitor = new JiraMonitor();
await Monitor.AddJiraInstanceAsync(_jiraClient, cancellationToken); await Monitor.AddJiraInstanceAsync(_jiraClient, cancellationToken);
var favIconUri = _jiraClient.JiraBaseUri.AppendSegments("favicon.ico"); var favIconUri = _jiraClient.JiraBaseUri.AppendSegments("favicon.ico");
try try
{ {
FavIcon = await _jiraClient.Server.GetUriContentAsync<Bitmap>(favIconUri, cancellationToken); FavIcon = await _jiraClient.Server.GetUriContentAsync<Bitmap>(favIconUri, cancellationToken);
} }
catch (Exception ex) catch (Exception ex)
{ {
Log.WarnFormat("Couldn't load favicon from {0}", favIconUri); Log.WarnFormat("Couldn't load favicon from {0}", favIconUri);
Log.Warn("Exception details: ", ex); Log.Warn("Exception details: ", ex);
} }
} }
catch (Exception ex2) catch (Exception ex2)
{ {
Log.WarnFormat("Couldn't connect to JIRA {0}", JiraConfig.Url); Log.WarnFormat("Couldn't connect to JIRA {0}", JiraConfig.Url);
Log.Warn("Exception details: ", ex2); Log.Warn("Exception details: ", ex2);
return false; return false;
} }
return true;
}
/// <summary> return true;
/// Use the credentials dialog, this will show if there are not correct credentials. }
/// If there are credentials, call the real login.
/// </summary>
/// <returns>Task</returns>
public async Task LoginAsync(CancellationToken cancellationToken = default) {
Logout();
try {
// Get the system name, so the user knows where to login to
var credentialsDialog = new CredentialsDialog(JiraConfig.Url)
{
Name = null
};
while (credentialsDialog.Show(credentialsDialog.Name) == DialogResult.OK) {
if (await DoLoginAsync(credentialsDialog.Name, credentialsDialog.Password, cancellationToken)) {
if (credentialsDialog.SaveChecked) {
credentialsDialog.Confirm(true);
}
IsLoggedIn = true;
return;
}
// Login failed, confirm this
try {
credentialsDialog.Confirm(false);
} catch (ApplicationException e) {
// exception handling ...
Log.Error("Problem using the credentials dialog", e);
}
// For every windows version after XP show an incorrect password baloon
credentialsDialog.IncorrectPassword = true;
// Make sure the dialog is display, the password was false!
credentialsDialog.AlwaysDisplay = true;
}
} catch (ApplicationException e) {
// exception handling ...
Log.Error("Problem using the credentials dialog", e);
}
} /// <summary>
/// Use the credentials dialog, this will show if there are not correct credentials.
/// If there are credentials, call the real login.
/// </summary>
/// <returns>Task</returns>
public async Task LoginAsync(CancellationToken cancellationToken = default)
{
Logout();
try
{
// Get the system name, so the user knows where to login to
var credentialsDialog = new CredentialsDialog(JiraConfig.Url)
{
Name = null
};
while (credentialsDialog.Show(credentialsDialog.Name) == DialogResult.OK)
{
if (await DoLoginAsync(credentialsDialog.Name, credentialsDialog.Password, cancellationToken))
{
if (credentialsDialog.SaveChecked)
{
credentialsDialog.Confirm(true);
}
/// <summary> IsLoggedIn = true;
/// End the session, if there was one return;
/// </summary> }
public void Logout() {
// Login failed, confirm this
try
{
credentialsDialog.Confirm(false);
}
catch (ApplicationException e)
{
// exception handling ...
Log.Error("Problem using the credentials dialog", e);
}
// For every windows version after XP show an incorrect password baloon
credentialsDialog.IncorrectPassword = true;
// Make sure the dialog is display, the password was false!
credentialsDialog.AlwaysDisplay = true;
}
}
catch (ApplicationException e)
{
// exception handling ...
Log.Error("Problem using the credentials dialog", e);
}
}
/// <summary>
/// End the session, if there was one
/// </summary>
public void Logout()
{
if (_jiraClient == null || !IsLoggedIn) return; if (_jiraClient == null || !IsLoggedIn) return;
Monitor.Dispose(); Monitor.Dispose();
IsLoggedIn = false; IsLoggedIn = false;
} }
/// <summary> /// <summary>
/// check the login credentials, to prevent timeouts of the session, or makes a login /// check the login credentials, to prevent timeouts of the session, or makes a login
/// Do not use ConfigureAwait to call this, as it will move await from the UI thread. /// Do not use ConfigureAwait to call this, as it will move await from the UI thread.
/// </summary> /// </summary>
/// <returns></returns> /// <returns></returns>
private async Task CheckCredentialsAsync(CancellationToken cancellationToken = default) { private async Task CheckCredentialsAsync(CancellationToken cancellationToken = default)
if (!IsLoggedIn) { {
await LoginAsync(cancellationToken); if (!IsLoggedIn)
} {
} await LoginAsync(cancellationToken);
}
}
/// <summary> /// <summary>
/// Get the favorite filters /// Get the favorite filters
/// </summary> /// </summary>
/// <returns>List with filters</returns> /// <returns>List with filters</returns>
public async Task<IList<Filter>> GetFavoriteFiltersAsync(CancellationToken cancellationToken = default) public async Task<IList<Filter>> GetFavoriteFiltersAsync(CancellationToken cancellationToken = default)
{ {
await CheckCredentialsAsync(cancellationToken); await CheckCredentialsAsync(cancellationToken);
return await _jiraClient.Filter.GetFavoritesAsync(cancellationToken).ConfigureAwait(false); return await _jiraClient.Filter.GetFavoritesAsync(cancellationToken).ConfigureAwait(false);
} }
/// <summary> /// <summary>
/// Get the issue for a key /// Get the issue for a key
/// </summary> /// </summary>
/// <param name="issueKey">Jira issue key</param> /// <param name="issueKey">Jira issue key</param>
/// <param name="cancellationToken">CancellationToken</param> /// <param name="cancellationToken">CancellationToken</param>
/// <returns>Issue</returns> /// <returns>Issue</returns>
public async Task<Issue> GetIssueAsync(string issueKey, CancellationToken cancellationToken = default) public async Task<Issue> GetIssueAsync(string issueKey, CancellationToken cancellationToken = default)
{ {
await CheckCredentialsAsync(cancellationToken); await CheckCredentialsAsync(cancellationToken);
try try
{ {
return await _jiraClient.Issue.GetAsync(issueKey, cancellationToken: cancellationToken).ConfigureAwait(false); return await _jiraClient.Issue.GetAsync(issueKey, cancellationToken: cancellationToken).ConfigureAwait(false);
} }
catch catch
{ {
return null; return null;
} }
} }
/// <summary> /// <summary>
/// Attach the content to the jira /// Attach the content to the jira
/// </summary> /// </summary>
/// <param name="issueKey"></param> /// <param name="issueKey"></param>
/// <param name="content">IBinaryContainer</param> /// <param name="content">IBinaryContainer</param>
/// <param name="cancellationToken"></param> /// <param name="cancellationToken"></param>
/// <returns></returns> /// <returns></returns>
public async Task AttachAsync(string issueKey, IBinaryContainer content, CancellationToken cancellationToken = default) public async Task AttachAsync(string issueKey, IBinaryContainer content, CancellationToken cancellationToken = default)
{ {
await CheckCredentialsAsync(cancellationToken); await CheckCredentialsAsync(cancellationToken);
using var memoryStream = new MemoryStream(); using var memoryStream = new MemoryStream();
content.WriteToStream(memoryStream); content.WriteToStream(memoryStream);
memoryStream.Seek(0, SeekOrigin.Begin); memoryStream.Seek(0, SeekOrigin.Begin);
await _jiraClient.Attachment.AttachAsync(issueKey, memoryStream, content.Filename, content.ContentType, cancellationToken).ConfigureAwait(false); await _jiraClient.Attachment.AttachAsync(issueKey, memoryStream, content.Filename, content.ContentType, cancellationToken).ConfigureAwait(false);
} }
/// <summary> /// <summary>
/// Add a comment to the supplied issue /// Add a comment to the supplied issue
/// </summary> /// </summary>
/// <param name="issueKey">Jira issue key</param> /// <param name="issueKey">Jira issue key</param>
/// <param name="body">text</param> /// <param name="body">text</param>
/// <param name="visibility">the visibility role</param> /// <param name="visibility">the visibility role</param>
/// <param name="cancellationToken">CancellationToken</param> /// <param name="cancellationToken">CancellationToken</param>
public async Task AddCommentAsync(string issueKey, string body, Visibility visibility = null, CancellationToken cancellationToken = default) public async Task AddCommentAsync(string issueKey, string body, Visibility visibility = null, CancellationToken cancellationToken = default)
{ {
await CheckCredentialsAsync(cancellationToken); await CheckCredentialsAsync(cancellationToken);
await _jiraClient.Issue.AddCommentAsync(issueKey, body, visibility, cancellationToken).ConfigureAwait(false); await _jiraClient.Issue.AddCommentAsync(issueKey, body, visibility, cancellationToken).ConfigureAwait(false);
} }
/// <summary> /// <summary>
/// Get the search results for the specified filter /// Get the search results for the specified filter
/// </summary> /// </summary>
/// <param name="filter">Filter</param> /// <param name="filter">Filter</param>
/// <param name="cancellationToken"></param> /// <param name="cancellationToken"></param>
/// <returns></returns> /// <returns></returns>
public async Task<IList<Issue>> SearchAsync(Filter filter, CancellationToken cancellationToken = default) public async Task<IList<Issue>> SearchAsync(Filter filter, CancellationToken cancellationToken = default)
{ {
await CheckCredentialsAsync(cancellationToken); await CheckCredentialsAsync(cancellationToken);
var searchResult = await _jiraClient.Issue.SearchAsync(filter.Jql, null, new[] { "summary", "reporter", "assignee", "created", "issuetype" }, null, cancellationToken).ConfigureAwait(false); var searchResult = await _jiraClient.Issue.SearchAsync(filter.Jql, null, new[]
return searchResult.Issues; {
} "summary", "reporter", "assignee", "created", "issuetype"
}, null, cancellationToken).ConfigureAwait(false);
return searchResult.Issues;
}
/// <summary> /// <summary>
/// Get the bitmap representing the issue type of an issue, from cache. /// Get the bitmap representing the issue type of an issue, from cache.
/// </summary> /// </summary>
/// <param name="issue">Issue</param> /// <param name="issue">Issue</param>
/// <param name="cancellationToken">CancellationToken</param> /// <param name="cancellationToken">CancellationToken</param>
/// <returns>Bitmap</returns> /// <returns>Bitmap</returns>
public async Task<Bitmap> GetIssueTypeBitmapAsync(Issue issue, CancellationToken cancellationToken = default) public async Task<Bitmap> GetIssueTypeBitmapAsync(Issue issue, CancellationToken cancellationToken = default)
{ {
return await _issueTypeBitmapCache.GetOrCreateAsync(issue.Fields.IssueType, cancellationToken).ConfigureAwait(false); return await _issueTypeBitmapCache.GetOrCreateAsync(issue.Fields.IssueType, cancellationToken).ConfigureAwait(false);
} }
/// <summary> /// <summary>
/// Get the base uri /// Get the base uri
/// </summary> /// </summary>
public Uri JiraBaseUri => _jiraClient.JiraBaseUri; public Uri JiraBaseUri => _jiraClient.JiraBaseUri;
/// <summary> /// <summary>
/// Is the user "logged in? /// Is the user "logged in?

View file

@ -34,127 +34,148 @@ using GreenshotPlugin.IniFile;
using GreenshotPlugin.Interfaces; using GreenshotPlugin.Interfaces;
using GreenshotPlugin.Interfaces.Plugin; using GreenshotPlugin.Interfaces.Plugin;
namespace Greenshot.Plugin.Jira { namespace Greenshot.Plugin.Jira
/// <summary> {
/// Description of JiraDestination. /// <summary>
/// </summary> /// Description of JiraDestination.
public class JiraDestination : AbstractDestination { /// </summary>
private static readonly log4net.ILog Log = log4net.LogManager.GetLogger(typeof(JiraDestination)); public class JiraDestination : AbstractDestination
private static readonly JiraConfiguration Config = IniConfig.GetIniSection<JiraConfiguration>(); {
private readonly Issue _jiraIssue; private static readonly log4net.ILog Log = log4net.LogManager.GetLogger(typeof(JiraDestination));
private static readonly JiraConfiguration Config = IniConfig.GetIniSection<JiraConfiguration>();
private readonly Issue _jiraIssue;
public JiraDestination(Issue jiraIssue = null) { public JiraDestination(Issue jiraIssue = null)
{
_jiraIssue = jiraIssue; _jiraIssue = jiraIssue;
} }
public override string Designation => "Jira"; public override string Designation => "Jira";
public override string Description { public override string Description
get {
{ get
if (_jiraIssue?.Fields?.Summary == null) { {
return Language.GetString("jira", LangKey.upload_menu_item); if (_jiraIssue?.Fields?.Summary == null)
} {
// Format the title of this destination return Language.GetString("jira", LangKey.upload_menu_item);
return _jiraIssue.Key + ": " + _jiraIssue.Fields.Summary.Substring(0, Math.Min(20, _jiraIssue.Fields.Summary.Length)); }
}
}
public override bool IsActive => base.IsActive && !string.IsNullOrEmpty(Config.Url); // Format the title of this destination
return _jiraIssue.Key + ": " + _jiraIssue.Fields.Summary.Substring(0, Math.Min(20, _jiraIssue.Fields.Summary.Length));
}
}
public override bool IsDynamic => true; public override bool IsActive => base.IsActive && !string.IsNullOrEmpty(Config.Url);
public override Image DisplayIcon { public override bool IsDynamic => true;
get
{ public override Image DisplayIcon
Image displayIcon = null; {
get
{
Image displayIcon = null;
var jiraConnector = SimpleServiceProvider.Current.GetInstance<JiraConnector>(); var jiraConnector = SimpleServiceProvider.Current.GetInstance<JiraConnector>();
if (jiraConnector != null) if (jiraConnector != null)
{ {
if (_jiraIssue != null) if (_jiraIssue != null)
{ {
// Try to get the issue type as icon // Try to get the issue type as icon
try try
{ {
displayIcon = jiraConnector.GetIssueTypeBitmapAsync(_jiraIssue).Result; displayIcon = jiraConnector.GetIssueTypeBitmapAsync(_jiraIssue).Result;
} }
catch (Exception ex) catch (Exception ex)
{ {
Log.Warn($"Problem loading issue type for {_jiraIssue.Key}, ignoring", ex); Log.Warn($"Problem loading issue type for {_jiraIssue.Key}, ignoring", ex);
} }
} }
if (displayIcon == null)
{
displayIcon = jiraConnector.FavIcon;
}
}
if (displayIcon == null)
{
var resources = new ComponentResourceManager(typeof(JiraPlugin));
displayIcon = (Image)resources.GetObject("Jira");
}
return displayIcon;
}
}
public override IEnumerable<IDestination> DynamicDestinations() if (displayIcon == null)
{ {
var jiraConnector = SimpleServiceProvider.Current.GetInstance<JiraConnector>(); displayIcon = jiraConnector.FavIcon;
if (jiraConnector == null || !jiraConnector.IsLoggedIn) { }
yield break; }
}
foreach(var jiraDetails in jiraConnector.Monitor.RecentJiras)
{
yield return new JiraDestination(jiraDetails.JiraIssue);
}
}
public override ExportInformation ExportCapture(bool manuallyInitiated, ISurface surfaceToUpload, ICaptureDetails captureDetails) { if (displayIcon == null)
ExportInformation exportInformation = new ExportInformation(Designation, Description); {
string filename = Path.GetFileName(FilenameHelper.GetFilename(Config.UploadFormat, captureDetails)); var resources = new ComponentResourceManager(typeof(JiraPlugin));
SurfaceOutputSettings outputSettings = new SurfaceOutputSettings(Config.UploadFormat, Config.UploadJpegQuality, Config.UploadReduceColors); displayIcon = (Image) resources.GetObject("Jira");
}
return displayIcon;
}
}
public override IEnumerable<IDestination> DynamicDestinations()
{
var jiraConnector = SimpleServiceProvider.Current.GetInstance<JiraConnector>(); var jiraConnector = SimpleServiceProvider.Current.GetInstance<JiraConnector>();
if (_jiraIssue != null) { if (jiraConnector == null || !jiraConnector.IsLoggedIn)
try { {
// Run upload in the background yield break;
new PleaseWaitForm().ShowAndWait(Description, Language.GetString("jira", LangKey.communication_wait), }
async () =>
{ foreach (var jiraDetails in jiraConnector.Monitor.RecentJiras)
var surfaceContainer = new SurfaceContainer(surfaceToUpload, outputSettings, filename); {
await jiraConnector.AttachAsync(_jiraIssue.Key, surfaceContainer); yield return new JiraDestination(jiraDetails.JiraIssue);
surfaceToUpload.UploadUrl = jiraConnector.JiraBaseUri.AppendSegments("browse", _jiraIssue.Key).AbsoluteUri; }
} }
);
Log.DebugFormat("Uploaded to Jira {0}", _jiraIssue.Key); public override ExportInformation ExportCapture(bool manuallyInitiated, ISurface surfaceToUpload, ICaptureDetails captureDetails)
exportInformation.ExportMade = true; {
exportInformation.Uri = surfaceToUpload.UploadUrl; ExportInformation exportInformation = new ExportInformation(Designation, Description);
} catch (Exception e) { string filename = Path.GetFileName(FilenameHelper.GetFilename(Config.UploadFormat, captureDetails));
MessageBox.Show(Language.GetString("jira", LangKey.upload_failure) + " " + e.Message); SurfaceOutputSettings outputSettings = new SurfaceOutputSettings(Config.UploadFormat, Config.UploadJpegQuality, Config.UploadReduceColors);
} var jiraConnector = SimpleServiceProvider.Current.GetInstance<JiraConnector>();
} else { if (_jiraIssue != null)
var jiraForm = new JiraForm(jiraConnector); {
jiraForm.SetFilename(filename); try
var dialogResult = jiraForm.ShowDialog(); {
if (dialogResult == DialogResult.OK) { // Run upload in the background
try { new PleaseWaitForm().ShowAndWait(Description, Language.GetString("jira", LangKey.communication_wait),
surfaceToUpload.UploadUrl = jiraConnector.JiraBaseUri.AppendSegments("browse", jiraForm.GetJiraIssue().Key).AbsoluteUri; async () =>
// Run upload in the background {
new PleaseWaitForm().ShowAndWait(Description, Language.GetString("jira", LangKey.communication_wait), var surfaceContainer = new SurfaceContainer(surfaceToUpload, outputSettings, filename);
async () => await jiraConnector.AttachAsync(_jiraIssue.Key, surfaceContainer);
{ surfaceToUpload.UploadUrl = jiraConnector.JiraBaseUri.AppendSegments("browse", _jiraIssue.Key).AbsoluteUri;
await jiraForm.UploadAsync(new SurfaceContainer(surfaceToUpload, outputSettings, filename)); }
} );
); Log.DebugFormat("Uploaded to Jira {0}", _jiraIssue.Key);
Log.DebugFormat("Uploaded to Jira {0}", jiraForm.GetJiraIssue().Key); exportInformation.ExportMade = true;
exportInformation.ExportMade = true; exportInformation.Uri = surfaceToUpload.UploadUrl;
exportInformation.Uri = surfaceToUpload.UploadUrl; }
} catch(Exception e) { catch (Exception e)
MessageBox.Show(Language.GetString("jira", LangKey.upload_failure) + " " + e.Message); {
} MessageBox.Show(Language.GetString("jira", LangKey.upload_failure) + " " + e.Message);
} }
} }
ProcessExport(exportInformation, surfaceToUpload); else
return exportInformation; {
} var jiraForm = new JiraForm(jiraConnector);
} jiraForm.SetFilename(filename);
var dialogResult = jiraForm.ShowDialog();
if (dialogResult == DialogResult.OK)
{
try
{
surfaceToUpload.UploadUrl = jiraConnector.JiraBaseUri.AppendSegments("browse", jiraForm.GetJiraIssue().Key).AbsoluteUri;
// Run upload in the background
new PleaseWaitForm().ShowAndWait(Description, Language.GetString("jira", LangKey.communication_wait),
async () => { await jiraForm.UploadAsync(new SurfaceContainer(surfaceToUpload, outputSettings, filename)); }
);
Log.DebugFormat("Uploaded to Jira {0}", jiraForm.GetJiraIssue().Key);
exportInformation.ExportMade = true;
exportInformation.Uri = surfaceToUpload.UploadUrl;
}
catch (Exception e)
{
MessageBox.Show(Language.GetString("jira", LangKey.upload_failure) + " " + e.Message);
}
}
}
ProcessExport(exportInformation, surfaceToUpload);
return exportInformation;
}
}
} }

View file

@ -24,48 +24,28 @@ using Dapplo.Jira.Entities;
namespace Greenshot.Plugin.Jira namespace Greenshot.Plugin.Jira
{ {
public class JiraDetails : IComparable<JiraDetails> public class JiraDetails : IComparable<JiraDetails>
{ {
public JiraDetails() public JiraDetails()
{ {
FirstSeenAt = SeenAt = DateTimeOffset.Now; FirstSeenAt = SeenAt = DateTimeOffset.Now;
} }
public string ProjectKey public string ProjectKey { get; set; }
{
get;
set;
}
public string Id public string Id { get; set; }
{
get;
set;
}
public string JiraKey => ProjectKey + "-" + Id; public string JiraKey => ProjectKey + "-" + Id;
public Issue JiraIssue public Issue JiraIssue { get; set; }
{
get;
set;
}
public DateTimeOffset FirstSeenAt public DateTimeOffset FirstSeenAt { get; private set; }
{
get;
private set;
}
public DateTimeOffset SeenAt public DateTimeOffset SeenAt { get; set; }
{
get;
set;
}
public int CompareTo(JiraDetails other) public int CompareTo(JiraDetails other)
{ {
return SeenAt.CompareTo(other.SeenAt); return SeenAt.CompareTo(other.SeenAt);
} }
} }
} }

View file

@ -23,18 +23,10 @@ using System;
namespace Greenshot.Plugin.Jira namespace Greenshot.Plugin.Jira
{ {
public class JiraEventArgs : EventArgs public class JiraEventArgs : EventArgs
{ {
public JiraEventTypes EventType public JiraEventTypes EventType { get; set; }
{
get;
set;
}
public JiraDetails Details public JiraDetails Details { get; set; }
{ }
get;
set;
}
}
} }

View file

@ -21,9 +21,9 @@
namespace Greenshot.Plugin.Jira namespace Greenshot.Plugin.Jira
{ {
public enum JiraEventTypes public enum JiraEventTypes
{ {
OrderChanged, OrderChanged,
DetectedNewJiraIssue DetectedNewJiraIssue
} }
} }

View file

@ -31,188 +31,203 @@ using GreenshotPlugin.Hooking;
namespace Greenshot.Plugin.Jira namespace Greenshot.Plugin.Jira
{ {
/// <summary> /// <summary>
/// This class will monitor all _jira activity by registering for title changes /// This class will monitor all _jira activity by registering for title changes
/// It keeps a list of the last "accessed" jiras, and makes it easy to upload to one. /// It keeps a list of the last "accessed" jiras, and makes it easy to upload to one.
/// Make sure this is instanciated on the UI thread! /// Make sure this is instanciated on the UI thread!
/// </summary> /// </summary>
public class JiraMonitor : IDisposable public class JiraMonitor : IDisposable
{ {
private static readonly LogSource Log = new LogSource(); private static readonly LogSource Log = new LogSource();
private readonly Regex _jiraKeyPattern = new Regex(@"[A-Z][A-Z0-9]+\-[0-9]+"); private readonly Regex _jiraKeyPattern = new Regex(@"[A-Z][A-Z0-9]+\-[0-9]+");
private readonly WindowsTitleMonitor _monitor; private readonly WindowsTitleMonitor _monitor;
private readonly IList<IJiraClient> _jiraInstances = new List<IJiraClient>(); private readonly IList<IJiraClient> _jiraInstances = new List<IJiraClient>();
private readonly IDictionary<string, IJiraClient> _projectJiraClientMap = new Dictionary<string, IJiraClient>(); private readonly IDictionary<string, IJiraClient> _projectJiraClientMap = new Dictionary<string, IJiraClient>();
private readonly int _maxEntries;
// TODO: Add issues from issueHistory (JQL -> Where.IssueKey.InIssueHistory())
private IDictionary<string, JiraDetails> _recentJiras = new Dictionary<string, JiraDetails>();
/// <summary> private readonly int _maxEntries;
/// Register to this event to get events when new jira issues are detected
/// </summary>
public event EventHandler<JiraEventArgs> JiraEvent;
public JiraMonitor(int maxEntries = 40) // TODO: Add issues from issueHistory (JQL -> Where.IssueKey.InIssueHistory())
{ private IDictionary<string, JiraDetails> _recentJiras = new Dictionary<string, JiraDetails>();
_maxEntries = maxEntries;
_monitor = new WindowsTitleMonitor();
_monitor.TitleChangeEvent += MonitorTitleChangeEvent;
}
/// <summary> /// <summary>
/// Dispose /// Register to this event to get events when new jira issues are detected
/// </summary> /// </summary>
public void Dispose() public event EventHandler<JiraEventArgs> JiraEvent;
{
Dispose(true);
GC.SuppressFinalize(this);
}
/// <summary> public JiraMonitor(int maxEntries = 40)
/// Dispose all managed resources {
/// </summary> _maxEntries = maxEntries;
/// <param name="disposing">when true is passed all managed resources are disposed.</param> _monitor = new WindowsTitleMonitor();
protected void Dispose(bool disposing) _monitor.TitleChangeEvent += MonitorTitleChangeEvent;
{ }
if (!disposing)
{
return;
}
// free managed resources
_monitor.TitleChangeEvent -= MonitorTitleChangeEvent;
_monitor.Dispose();
// free native resources if there are any.
}
/// <summary> /// <summary>
/// Retrieve the API belonging to a JiraDetails /// Dispose
/// </summary> /// </summary>
/// <param name="jiraDetails"></param> public void Dispose()
/// <returns>IJiraClient</returns> {
public IJiraClient GetJiraClientForKey(JiraDetails jiraDetails) Dispose(true);
{ GC.SuppressFinalize(this);
return _projectJiraClientMap[jiraDetails.ProjectKey]; }
}
/// <summary> /// <summary>
/// Get the "list" of recently seen Jiras /// Dispose all managed resources
/// </summary> /// </summary>
public IEnumerable<JiraDetails> RecentJiras => /// <param name="disposing">when true is passed all managed resources are disposed.</param>
(from jiraDetails in _recentJiras.Values protected void Dispose(bool disposing)
orderby jiraDetails.SeenAt descending {
select jiraDetails); if (!disposing)
{
return;
}
/// <summary> // free managed resources
/// Check if this monitor has active instances _monitor.TitleChangeEvent -= MonitorTitleChangeEvent;
/// </summary> _monitor.Dispose();
public bool HasJiraInstances => _jiraInstances.Count > 0; // free native resources if there are any.
}
/// <summary> /// <summary>
/// Add an instance of a JIRA system /// Retrieve the API belonging to a JiraDetails
/// </summary> /// </summary>
/// <param name="jiraInstance">IJiraClient</param> /// <param name="jiraDetails"></param>
/// <param name="token">CancellationToken</param> /// <returns>IJiraClient</returns>
public async Task AddJiraInstanceAsync(IJiraClient jiraInstance, CancellationToken token = default) public IJiraClient GetJiraClientForKey(JiraDetails jiraDetails)
{ {
_jiraInstances.Add(jiraInstance); return _projectJiraClientMap[jiraDetails.ProjectKey];
var projects = await jiraInstance.Project.GetAllAsync(cancellationToken: token).ConfigureAwait(false); }
if (projects != null)
{
foreach (var project in projects)
{
if (!_projectJiraClientMap.ContainsKey(project.Key))
{
_projectJiraClientMap.Add(project.Key, jiraInstance);
}
}
}
}
/// <summary> /// <summary>
/// This method will update details, like the title, and send an event to registed listeners of the JiraEvent /// Get the "list" of recently seen Jiras
/// </summary> /// </summary>
/// <param name="jiraDetails">Contains the jira key to retrieve the title (XYZ-1234)</param> public IEnumerable<JiraDetails> RecentJiras =>
/// <returns>Task</returns> (from jiraDetails in _recentJiras.Values
private async Task DetectedNewJiraIssueAsync(JiraDetails jiraDetails) orderby jiraDetails.SeenAt descending
{ select jiraDetails);
try
{ /// <summary>
/// Check if this monitor has active instances
/// </summary>
public bool HasJiraInstances => _jiraInstances.Count > 0;
/// <summary>
/// Add an instance of a JIRA system
/// </summary>
/// <param name="jiraInstance">IJiraClient</param>
/// <param name="token">CancellationToken</param>
public async Task AddJiraInstanceAsync(IJiraClient jiraInstance, CancellationToken token = default)
{
_jiraInstances.Add(jiraInstance);
var projects = await jiraInstance.Project.GetAllAsync(cancellationToken: token).ConfigureAwait(false);
if (projects != null)
{
foreach (var project in projects)
{
if (!_projectJiraClientMap.ContainsKey(project.Key))
{
_projectJiraClientMap.Add(project.Key, jiraInstance);
}
}
}
}
/// <summary>
/// This method will update details, like the title, and send an event to registed listeners of the JiraEvent
/// </summary>
/// <param name="jiraDetails">Contains the jira key to retrieve the title (XYZ-1234)</param>
/// <returns>Task</returns>
private async Task DetectedNewJiraIssueAsync(JiraDetails jiraDetails)
{
try
{
if (_projectJiraClientMap.TryGetValue(jiraDetails.ProjectKey, out var jiraClient)) if (_projectJiraClientMap.TryGetValue(jiraDetails.ProjectKey, out var jiraClient))
{ {
var issue = await jiraClient.Issue.GetAsync(jiraDetails.JiraKey).ConfigureAwait(false); var issue = await jiraClient.Issue.GetAsync(jiraDetails.JiraKey).ConfigureAwait(false);
jiraDetails.JiraIssue = issue; jiraDetails.JiraIssue = issue;
} }
// Send event
JiraEvent?.Invoke(this, new JiraEventArgs { Details = jiraDetails, EventType = JiraEventTypes.DetectedNewJiraIssue });
}
catch (Exception ex)
{
Log.Warn().WriteLine("Couldn't retrieve JIRA title: {0}", ex.Message);
}
}
/// <summary> // Send event
/// Handle title changes, check for JIRA JiraEvent?.Invoke(this, new JiraEventArgs
/// </summary> {
/// <param name="eventArgs"></param> Details = jiraDetails,
private void MonitorTitleChangeEvent(TitleChangeEventArgs eventArgs) EventType = JiraEventTypes.DetectedNewJiraIssue
{ });
string windowTitle = eventArgs.Title; }
if (string.IsNullOrEmpty(windowTitle)) catch (Exception ex)
{ {
return; Log.Warn().WriteLine("Couldn't retrieve JIRA title: {0}", ex.Message);
} }
var jiraKeyMatch = _jiraKeyPattern.Match(windowTitle); }
if (!jiraKeyMatch.Success)
{ /// <summary>
return; /// Handle title changes, check for JIRA
} /// </summary>
// Found a possible JIRA title /// <param name="eventArgs"></param>
var jiraKey = jiraKeyMatch.Value; private void MonitorTitleChangeEvent(TitleChangeEventArgs eventArgs)
var jiraKeyParts = jiraKey.Split('-'); {
var projectKey = jiraKeyParts[0]; string windowTitle = eventArgs.Title;
var jiraId = jiraKeyParts[1]; if (string.IsNullOrEmpty(windowTitle))
{
return;
}
var jiraKeyMatch = _jiraKeyPattern.Match(windowTitle);
if (!jiraKeyMatch.Success)
{
return;
}
// Found a possible JIRA title
var jiraKey = jiraKeyMatch.Value;
var jiraKeyParts = jiraKey.Split('-');
var projectKey = jiraKeyParts[0];
var jiraId = jiraKeyParts[1];
// Check if we have a JIRA instance with a project for this key // Check if we have a JIRA instance with a project for this key
if (_projectJiraClientMap.TryGetValue(projectKey, out var jiraClient)) if (_projectJiraClientMap.TryGetValue(projectKey, out var jiraClient))
{ {
// We have found a project for this _jira key, so it must be a valid & known JIRA // We have found a project for this _jira key, so it must be a valid & known JIRA
if (_recentJiras.TryGetValue(jiraKey, out var currentJiraDetails)) if (_recentJiras.TryGetValue(jiraKey, out var currentJiraDetails))
{ {
// update // update
currentJiraDetails.SeenAt = DateTimeOffset.Now; currentJiraDetails.SeenAt = DateTimeOffset.Now;
// Notify the order change // Notify the order change
JiraEvent?.Invoke(this, new JiraEventArgs { Details = currentJiraDetails, EventType = JiraEventTypes.OrderChanged }); JiraEvent?.Invoke(this, new JiraEventArgs
// Nothing else to do {
Details = currentJiraDetails,
EventType = JiraEventTypes.OrderChanged
});
// Nothing else to do
return; return;
} }
// We detected an unknown JIRA, so add it to our list
currentJiraDetails = new JiraDetails
{
Id = jiraId,
ProjectKey = projectKey
};
_recentJiras.Add(currentJiraDetails.JiraKey, currentJiraDetails);
// Make sure we don't collect _jira's until the memory is full // We detected an unknown JIRA, so add it to our list
if (_recentJiras.Count > _maxEntries) currentJiraDetails = new JiraDetails
{ {
// Add it to the list of recent Jiras Id = jiraId,
_recentJiras = (from jiraDetails in _recentJiras.Values.ToList() ProjectKey = projectKey
orderby jiraDetails.SeenAt descending };
select jiraDetails).Take(_maxEntries).ToDictionary(jd => jd.JiraKey, jd => jd); _recentJiras.Add(currentJiraDetails.JiraKey, currentJiraDetails);
}
// Now we can get the title from JIRA itself // Make sure we don't collect _jira's until the memory is full
// ReSharper disable once UnusedVariable if (_recentJiras.Count > _maxEntries)
var updateTitleTask = DetectedNewJiraIssueAsync(currentJiraDetails); {
} // Add it to the list of recent Jiras
else _recentJiras = (from jiraDetails in _recentJiras.Values.ToList()
{ orderby jiraDetails.SeenAt descending
Log.Info().WriteLine("Couldn't match possible JIRA key {0} to projects in a configured JIRA instance, ignoring", projectKey); select jiraDetails).Take(_maxEntries).ToDictionary(jd => jd.JiraKey, jd => jd);
} }
}
} // Now we can get the title from JIRA itself
// ReSharper disable once UnusedVariable
var updateTitleTask = DetectedNewJiraIssueAsync(currentJiraDetails);
}
else
{
Log.Info().WriteLine("Couldn't match possible JIRA key {0} to projects in a configured JIRA instance, ignoring", projectKey);
}
}
}
} }

View file

@ -30,109 +30,117 @@ using GreenshotPlugin.Interfaces;
using GreenshotPlugin.Interfaces.Plugin; using GreenshotPlugin.Interfaces.Plugin;
using log4net; using log4net;
namespace Greenshot.Plugin.Jira { namespace Greenshot.Plugin.Jira
/// <summary> {
/// This is the JiraPlugin base code /// <summary>
/// </summary> /// This is the JiraPlugin base code
/// </summary>
[Plugin("Jira", true)] [Plugin("Jira", true)]
public class JiraPlugin : IGreenshotPlugin { public class JiraPlugin : IGreenshotPlugin
private static readonly ILog Log = LogManager.GetLogger(typeof(JiraPlugin)); {
private JiraConfiguration _config; private static readonly ILog Log = LogManager.GetLogger(typeof(JiraPlugin));
private JiraConfiguration _config;
public void Dispose() { public void Dispose()
Dispose(true); {
GC.SuppressFinalize(this); Dispose(true);
} GC.SuppressFinalize(this);
}
protected void Dispose(bool disposing) { protected void Dispose(bool disposing)
if (disposing) {
if (disposing)
{ {
var jiraConnector = SimpleServiceProvider.Current.GetInstance<JiraConnector>(); var jiraConnector = SimpleServiceProvider.Current.GetInstance<JiraConnector>();
jiraConnector?.Dispose(); jiraConnector?.Dispose();
} }
} }
/// <summary> /// <summary>
/// Implementation of the IGreenshotPlugin.Initialize /// Implementation of the IGreenshotPlugin.Initialize
/// </summary> /// </summary>
/// <returns>true if plugin is initialized, false if not (doesn't show)</returns> /// <returns>true if plugin is initialized, false if not (doesn't show)</returns>
public bool Initialize() { public bool Initialize()
// Register configuration (don't need the configuration itself) {
_config = IniConfig.GetIniSection<JiraConfiguration>(); // Register configuration (don't need the configuration itself)
_config = IniConfig.GetIniSection<JiraConfiguration>();
// Provide the JiraConnector // Provide the JiraConnector
SimpleServiceProvider.Current.AddService(new JiraConnector()); SimpleServiceProvider.Current.AddService(new JiraConnector());
// Provide the IDestination // Provide the IDestination
SimpleServiceProvider.Current.AddService<IDestination>(new JiraDestination()); SimpleServiceProvider.Current.AddService<IDestination>(new JiraDestination());
// Make sure the log is enabled for the correct level. // Make sure the log is enabled for the correct level.
if (Log.IsDebugEnabled) if (Log.IsDebugEnabled)
{ {
LogSettings.RegisterDefaultLogger<Log4NetLogger>(LogLevels.Verbose); LogSettings.RegisterDefaultLogger<Log4NetLogger>(LogLevels.Verbose);
} }
else if (Log.IsInfoEnabled) else if (Log.IsInfoEnabled)
{ {
LogSettings.RegisterDefaultLogger<Log4NetLogger>(LogLevels.Info); LogSettings.RegisterDefaultLogger<Log4NetLogger>(LogLevels.Info);
} }
else if (Log.IsWarnEnabled) else if (Log.IsWarnEnabled)
{ {
LogSettings.RegisterDefaultLogger<Log4NetLogger>(LogLevels.Warn); LogSettings.RegisterDefaultLogger<Log4NetLogger>(LogLevels.Warn);
} }
else if (Log.IsErrorEnabled) else if (Log.IsErrorEnabled)
{ {
LogSettings.RegisterDefaultLogger<Log4NetLogger>(LogLevels.Error); LogSettings.RegisterDefaultLogger<Log4NetLogger>(LogLevels.Error);
} }
else if (Log.IsErrorEnabled) else if (Log.IsErrorEnabled)
{ {
LogSettings.RegisterDefaultLogger<Log4NetLogger>(LogLevels.Error); LogSettings.RegisterDefaultLogger<Log4NetLogger>(LogLevels.Error);
} }
else else
{ {
LogSettings.RegisterDefaultLogger<Log4NetLogger>(LogLevels.Fatal); LogSettings.RegisterDefaultLogger<Log4NetLogger>(LogLevels.Fatal);
} }
return true; return true;
} }
public void Shutdown() { public void Shutdown()
Log.Debug("Jira Plugin shutdown."); {
Log.Debug("Jira Plugin shutdown.");
var jiraConnector = SimpleServiceProvider.Current.GetInstance<JiraConnector>(); var jiraConnector = SimpleServiceProvider.Current.GetInstance<JiraConnector>();
jiraConnector?.Logout(); jiraConnector?.Logout();
} }
/// <summary> /// <summary>
/// Implementation of the IPlugin.Configure /// Implementation of the IPlugin.Configure
/// </summary> /// </summary>
public void Configure() { public void Configure()
string url = _config.Url; {
if (ShowConfigDialog()) { string url = _config.Url;
// check for re-login if (ShowConfigDialog())
{
// check for re-login
var jiraConnector = SimpleServiceProvider.Current.GetInstance<JiraConnector>(); var jiraConnector = SimpleServiceProvider.Current.GetInstance<JiraConnector>();
if (jiraConnector != null && jiraConnector.IsLoggedIn && !string.IsNullOrEmpty(url)) { if (jiraConnector != null && jiraConnector.IsLoggedIn && !string.IsNullOrEmpty(url))
if (!url.Equals(_config.Url)) { {
if (!url.Equals(_config.Url))
{
jiraConnector.Logout(); jiraConnector.Logout();
Task.Run(async () => Task.Run(async () => { await jiraConnector.LoginAsync(); });
{ }
await jiraConnector.LoginAsync(); }
}); }
} }
}
}
}
/// <summary> /// <summary>
/// A form for username/password /// A form for username/password
/// </summary> /// </summary>
/// <returns>bool true if OK was pressed, false if cancel</returns> /// <returns>bool true if OK was pressed, false if cancel</returns>
private bool ShowConfigDialog() private bool ShowConfigDialog()
{ {
var settingsForm = new SettingsForm(); var settingsForm = new SettingsForm();
var result = settingsForm.ShowDialog(); var result = settingsForm.ShowDialog();
if (result == DialogResult.OK) if (result == DialogResult.OK)
{ {
return true; return true;
} }
return false;
} return false;
} }
}
} }

View file

@ -19,20 +19,22 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>. * along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
namespace Greenshot.Plugin.Jira { namespace Greenshot.Plugin.Jira
public enum LangKey { {
upload_menu_item, public enum LangKey
column_assignee, {
column_created, upload_menu_item,
column_issueType, column_assignee,
column_id, column_created,
column_reporter, column_issueType,
column_summary, column_id,
label_comment, column_reporter,
label_filename, column_summary,
label_jirafilter, label_comment,
login_error, label_filename,
upload_failure, label_jirafilter,
communication_wait, login_error,
} upload_failure,
communication_wait,
}
} }

View file

@ -24,97 +24,98 @@ using log4net;
namespace Greenshot.Plugin.Jira namespace Greenshot.Plugin.Jira
{ {
/// <summary> /// <summary>
/// Used to make Dapplo.Log, used in Dapplo.Jira, write to Log4net /// Used to make Dapplo.Log, used in Dapplo.Jira, write to Log4net
/// </summary> /// </summary>
public class Log4NetLogger : AbstractLogger public class Log4NetLogger : AbstractLogger
{ {
private ILog GetLogger(LogSource logSource) private ILog GetLogger(LogSource logSource)
{ {
return logSource.SourceType != null ? LogManager.GetLogger(logSource.SourceType) : LogManager.GetLogger(logSource.Source); return logSource.SourceType != null ? LogManager.GetLogger(logSource.SourceType) : LogManager.GetLogger(logSource.Source);
} }
/// <summary> /// <summary>
/// Write the supplied information to a log4net.ILog /// Write the supplied information to a log4net.ILog
/// </summary> /// </summary>
/// <param name="logInfo">LogInfo</param> /// <param name="logInfo">LogInfo</param>
/// <param name="messageTemplate">string</param> /// <param name="messageTemplate">string</param>
/// <param name="propertyValues">params object[]</param> /// <param name="propertyValues">params object[]</param>
public override void Write(LogInfo logInfo, string messageTemplate, params object[] propertyValues) public override void Write(LogInfo logInfo, string messageTemplate, params object[] propertyValues)
{ {
var log = GetLogger(logInfo.Source); var log = GetLogger(logInfo.Source);
switch (logInfo.LogLevel) switch (logInfo.LogLevel)
{ {
case LogLevels.Verbose: case LogLevels.Verbose:
case LogLevels.Debug: case LogLevels.Debug:
if (propertyValues != null) if (propertyValues != null)
log.DebugFormat(messageTemplate, propertyValues); log.DebugFormat(messageTemplate, propertyValues);
else else
log.Debug(messageTemplate); log.Debug(messageTemplate);
break; break;
case LogLevels.Error: case LogLevels.Error:
if (propertyValues != null) if (propertyValues != null)
log.ErrorFormat(messageTemplate, propertyValues); log.ErrorFormat(messageTemplate, propertyValues);
else else
log.Error(messageTemplate); log.Error(messageTemplate);
break; break;
case LogLevels.Fatal: case LogLevels.Fatal:
if (propertyValues != null) if (propertyValues != null)
log.FatalFormat(messageTemplate, propertyValues); log.FatalFormat(messageTemplate, propertyValues);
else else
log.Fatal(messageTemplate); log.Fatal(messageTemplate);
break; break;
case LogLevels.Info: case LogLevels.Info:
if (propertyValues != null) if (propertyValues != null)
log.InfoFormat(messageTemplate, propertyValues); log.InfoFormat(messageTemplate, propertyValues);
else else
log.Info(messageTemplate); log.Info(messageTemplate);
break; break;
case LogLevels.Warn: case LogLevels.Warn:
if (propertyValues != null) if (propertyValues != null)
log.WarnFormat(messageTemplate, propertyValues); log.WarnFormat(messageTemplate, propertyValues);
else else
log.Warn(messageTemplate); log.Warn(messageTemplate);
break; break;
} }
} }
/// <summary> /// <summary>
/// Make sure there are no newlines passed /// Make sure there are no newlines passed
/// </summary> /// </summary>
/// <param name="logInfo"></param> /// <param name="logInfo"></param>
/// <param name="messageTemplate"></param> /// <param name="messageTemplate"></param>
/// <param name="logParameters"></param> /// <param name="logParameters"></param>
public override void WriteLine(LogInfo logInfo, string messageTemplate, params object[] logParameters) public override void WriteLine(LogInfo logInfo, string messageTemplate, params object[] logParameters)
{ {
Write(logInfo, messageTemplate, logParameters); Write(logInfo, messageTemplate, logParameters);
} }
/// <summary> /// <summary>
/// Test if a certain LogLevels enum is enabled /// Test if a certain LogLevels enum is enabled
/// </summary> /// </summary>
/// <param name="level">LogLevels value</param> /// <param name="level">LogLevels value</param>
/// <param name="logSource">LogSource to check for</param> /// <param name="logSource">LogSource to check for</param>
/// <returns>bool true if the LogLevels enum is enabled</returns> /// <returns>bool true if the LogLevels enum is enabled</returns>
public override bool IsLogLevelEnabled(LogLevels level, LogSource logSource = null) public override bool IsLogLevelEnabled(LogLevels level, LogSource logSource = null)
{ {
var log = GetLogger(logSource); var log = GetLogger(logSource);
switch (level) switch (level)
{ {
case LogLevels.Verbose: case LogLevels.Verbose:
case LogLevels.Debug: case LogLevels.Debug:
return log.IsDebugEnabled; return log.IsDebugEnabled;
case LogLevels.Error: case LogLevels.Error:
return log.IsErrorEnabled; return log.IsErrorEnabled;
case LogLevels.Fatal: case LogLevels.Fatal:
return log.IsFatalEnabled; return log.IsFatalEnabled;
case LogLevels.Info: case LogLevels.Info:
return log.IsInfoEnabled; return log.IsInfoEnabled;
case LogLevels.Warn: case LogLevels.Warn:
return log.IsWarnEnabled; return log.IsWarnEnabled;
} }
return false;
} return false;
} }
}
} }

View file

@ -1,5 +1,6 @@
// Copyright (c) Dapplo and contributors. All rights reserved. // Copyright (c) Dapplo and contributors. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information. // Licensed under the MIT license. See LICENSE file in the project root for full license information.
namespace Greenshot.Plugin.Office.Com namespace Greenshot.Plugin.Office.Com
{ {
/// <summary> /// <summary>

View file

@ -38,6 +38,7 @@ namespace Greenshot.Plugin.Office.Com
{ {
return; return;
} }
// Do not catch an exception from this. // Do not catch an exception from this.
// You may want to remove these guards depending on // You may want to remove these guards depending on
// what you think the semantics should be. // what you think the semantics should be.
@ -45,6 +46,7 @@ namespace Greenshot.Plugin.Office.Com
{ {
Marshal.ReleaseComObject(ComObject); Marshal.ReleaseComObject(ComObject);
} }
ComObject = default; ComObject = default;
} }
} }

View file

@ -24,6 +24,7 @@ namespace Greenshot.Plugin.Office.Com
{ {
return clsId; return clsId;
} }
return clsId; return clsId;
} }

View file

@ -23,7 +23,7 @@ namespace Greenshot.Plugin.Office.Com
{ {
if (GetActiveObject(ref clsId, IntPtr.Zero, out object comObject).Succeeded()) if (GetActiveObject(ref clsId, IntPtr.Zero, out object comObject).Succeeded())
{ {
return DisposableCom.Create((T)comObject); return DisposableCom.Create((T) comObject);
} }
return null; return null;

View file

@ -28,70 +28,89 @@ using GreenshotPlugin.Core;
using GreenshotPlugin.Interfaces; using GreenshotPlugin.Interfaces;
using GreenshotPlugin.Interfaces.Plugin; using GreenshotPlugin.Interfaces.Plugin;
namespace Greenshot.Plugin.Office.Destinations { namespace Greenshot.Plugin.Office.Destinations
/// <summary> {
/// Description of PowerpointDestination. /// <summary>
/// </summary> /// Description of PowerpointDestination.
public class ExcelDestination : AbstractDestination { /// </summary>
private const int IconApplication = 0; public class ExcelDestination : AbstractDestination
private const int IconWorkbook = 1; {
private static readonly string ExePath; private const int IconApplication = 0;
private readonly string _workbookName; private const int IconWorkbook = 1;
private static readonly string ExePath;
private readonly string _workbookName;
static ExcelDestination() { static ExcelDestination()
ExePath = PluginUtils.GetExePath("EXCEL.EXE"); {
if (ExePath != null && File.Exists(ExePath)) { ExePath = PluginUtils.GetExePath("EXCEL.EXE");
WindowDetails.AddProcessToExcludeFromFreeze("excel"); if (ExePath != null && File.Exists(ExePath))
} else { {
ExePath = null; WindowDetails.AddProcessToExcludeFromFreeze("excel");
} }
} else
{
ExePath = null;
}
}
public ExcelDestination() { public ExcelDestination()
} {
}
public ExcelDestination(string workbookName) { public ExcelDestination(string workbookName)
_workbookName = workbookName; {
} _workbookName = workbookName;
}
public override string Designation => "Excel"; public override string Designation => "Excel";
public override string Description => _workbookName ?? "Microsoft Excel"; public override string Description => _workbookName ?? "Microsoft Excel";
public override int Priority => 5; public override int Priority => 5;
public override bool IsDynamic => true; public override bool IsDynamic => true;
public override bool IsActive => base.IsActive && ExePath != null; public override bool IsActive => base.IsActive && ExePath != null;
public override Image DisplayIcon => PluginUtils.GetCachedExeIcon(ExePath, !string.IsNullOrEmpty(_workbookName) ? IconWorkbook : IconApplication); public override Image DisplayIcon => PluginUtils.GetCachedExeIcon(ExePath, !string.IsNullOrEmpty(_workbookName) ? IconWorkbook : IconApplication);
public override IEnumerable<IDestination> DynamicDestinations() { public override IEnumerable<IDestination> DynamicDestinations()
foreach (string workbookName in ExcelExporter.GetWorkbooks()) { {
yield return new ExcelDestination(workbookName); foreach (string workbookName in ExcelExporter.GetWorkbooks())
} {
} yield return new ExcelDestination(workbookName);
}
}
public override ExportInformation ExportCapture(bool manuallyInitiated, ISurface surface, ICaptureDetails captureDetails) { public override ExportInformation ExportCapture(bool manuallyInitiated, ISurface surface, ICaptureDetails captureDetails)
ExportInformation exportInformation = new ExportInformation(Designation, Description); {
bool createdFile = false; ExportInformation exportInformation = new ExportInformation(Designation, Description);
string imageFile = captureDetails.Filename; bool createdFile = false;
if (imageFile == null || surface.Modified || !Regex.IsMatch(imageFile, @".*(\.png|\.gif|\.jpg|\.jpeg|\.tiff|\.bmp)$")) { string imageFile = captureDetails.Filename;
imageFile = ImageOutput.SaveNamedTmpFile(surface, captureDetails, new SurfaceOutputSettings().PreventGreenshotFormat()); if (imageFile == null || surface.Modified || !Regex.IsMatch(imageFile, @".*(\.png|\.gif|\.jpg|\.jpeg|\.tiff|\.bmp)$"))
createdFile = true; {
} imageFile = ImageOutput.SaveNamedTmpFile(surface, captureDetails, new SurfaceOutputSettings().PreventGreenshotFormat());
if (_workbookName != null) { createdFile = true;
ExcelExporter.InsertIntoExistingWorkbook(_workbookName, imageFile, surface.Image.Size); }
} else {
ExcelExporter.InsertIntoNewWorkbook(imageFile, surface.Image.Size); if (_workbookName != null)
} {
exportInformation.ExportMade = true; ExcelExporter.InsertIntoExistingWorkbook(_workbookName, imageFile, surface.Image.Size);
ProcessExport(exportInformation, surface); }
// Cleanup imageFile if we created it here, so less tmp-files are generated and left else
if (createdFile) { {
ImageOutput.DeleteNamedTmpFile(imageFile); ExcelExporter.InsertIntoNewWorkbook(imageFile, surface.Image.Size);
} }
return exportInformation;
} exportInformation.ExportMade = true;
} ProcessExport(exportInformation, surface);
// Cleanup imageFile if we created it here, so less tmp-files are generated and left
if (createdFile)
{
ImageOutput.DeleteNamedTmpFile(imageFile);
}
return exportInformation;
}
}
} }

View file

@ -28,97 +28,117 @@ using Greenshot.Plugin.Office.OfficeExport.Entities;
using GreenshotPlugin.Core; using GreenshotPlugin.Core;
using GreenshotPlugin.Interfaces; using GreenshotPlugin.Interfaces;
namespace Greenshot.Plugin.Office.Destinations { namespace Greenshot.Plugin.Office.Destinations
public class OneNoteDestination : AbstractDestination { {
private static readonly log4net.ILog LOG = log4net.LogManager.GetLogger(typeof(WordDestination)); public class OneNoteDestination : AbstractDestination
private const int ICON_APPLICATION = 0; {
public const string DESIGNATION = "OneNote"; private static readonly log4net.ILog LOG = log4net.LogManager.GetLogger(typeof(WordDestination));
private static readonly string exePath; private const int ICON_APPLICATION = 0;
private readonly OneNotePage page; public const string DESIGNATION = "OneNote";
private readonly OneNoteExporter _oneNoteExporter = new OneNoteExporter(); private static readonly string exePath;
private readonly OneNotePage page;
private readonly OneNoteExporter _oneNoteExporter = new OneNoteExporter();
static OneNoteDestination() { static OneNoteDestination()
exePath = PluginUtils.GetExePath("ONENOTE.EXE"); {
if (exePath != null && File.Exists(exePath)) { exePath = PluginUtils.GetExePath("ONENOTE.EXE");
WindowDetails.AddProcessToExcludeFromFreeze("onenote"); if (exePath != null && File.Exists(exePath))
} else { {
exePath = null; WindowDetails.AddProcessToExcludeFromFreeze("onenote");
} }
} else
{
exePath = null;
}
}
public OneNoteDestination() { public OneNoteDestination()
{
}
} public OneNoteDestination(OneNotePage page)
{
this.page = page;
}
public OneNoteDestination(OneNotePage page) { public override string Designation
this.page = page; {
} get { return DESIGNATION; }
}
public override string Designation { public override string Description
get { {
return DESIGNATION; get
} {
} if (page == null)
{
return "Microsoft OneNote";
}
else
{
return page.DisplayName;
}
}
}
public override string Description { public override int Priority
get { {
if (page == null) { get { return 4; }
return "Microsoft OneNote"; }
} else {
return page.DisplayName;
}
}
}
public override int Priority { public override bool IsDynamic
get { {
return 4; get { return true; }
} }
}
public override bool IsDynamic { public override bool IsActive
get { {
return true; get { return base.IsActive && exePath != null; }
} }
}
public override bool IsActive { public override Image DisplayIcon
get { {
return base.IsActive && exePath != null; get { return PluginUtils.GetCachedExeIcon(exePath, ICON_APPLICATION); }
} }
}
public override Image DisplayIcon { public override IEnumerable<IDestination> DynamicDestinations()
get { {
return PluginUtils.GetCachedExeIcon(exePath, ICON_APPLICATION); foreach (OneNotePage page in _oneNoteExporter.GetPages())
} {
} yield return new OneNoteDestination(page);
}
}
public override IEnumerable<IDestination> DynamicDestinations() { public override ExportInformation ExportCapture(bool manuallyInitiated, ISurface surface, ICaptureDetails captureDetails)
foreach (OneNotePage page in _oneNoteExporter.GetPages()) { {
yield return new OneNoteDestination(page); ExportInformation exportInformation = new ExportInformation(Designation, Description);
}
}
public override ExportInformation ExportCapture(bool manuallyInitiated, ISurface surface, ICaptureDetails captureDetails) { if (page == null)
ExportInformation exportInformation = new ExportInformation(Designation, Description); {
try
{
exportInformation.ExportMade = _oneNoteExporter.ExportToNewPage(surface);
}
catch (Exception ex)
{
exportInformation.ErrorMessage = ex.Message;
LOG.Error(ex);
}
}
else
{
try
{
exportInformation.ExportMade = _oneNoteExporter.ExportToPage(surface, page);
}
catch (Exception ex)
{
exportInformation.ErrorMessage = ex.Message;
LOG.Error(ex);
}
}
if (page == null) { return exportInformation;
try { }
exportInformation.ExportMade = _oneNoteExporter.ExportToNewPage(surface); }
} catch(Exception ex) {
exportInformation.ErrorMessage = ex.Message;
LOG.Error(ex);
}
} else {
try {
exportInformation.ExportMade = _oneNoteExporter.ExportToPage(surface, page);
} catch(Exception ex) {
exportInformation.ErrorMessage = ex.Message;
LOG.Error(ex);
}
}
return exportInformation;
}
}
} }

View file

@ -32,152 +32,192 @@ using GreenshotPlugin.Interfaces.Plugin;
using Microsoft.Office.Interop.Outlook; using Microsoft.Office.Interop.Outlook;
using Microsoft.Win32; using Microsoft.Win32;
namespace Greenshot.Plugin.Office.Destinations { namespace Greenshot.Plugin.Office.Destinations
/// <summary> {
/// Description of OutlookDestination. /// <summary>
/// </summary> /// Description of OutlookDestination.
public class OutlookDestination : AbstractDestination { /// </summary>
private static readonly log4net.ILog Log = log4net.LogManager.GetLogger(typeof(OutlookDestination)); public class OutlookDestination : AbstractDestination
private const int IconApplication = 0; {
private const int IconMeeting = 2; private static readonly log4net.ILog Log = log4net.LogManager.GetLogger(typeof(OutlookDestination));
private const int IconApplication = 0;
private const int IconMeeting = 2;
private static readonly Image MailIcon = GreenshotResources.GetImage("Email.Image"); private static readonly Image MailIcon = GreenshotResources.GetImage("Email.Image");
private static readonly OfficeConfiguration OfficeConfig = IniConfig.GetIniSection<OfficeConfiguration>(); private static readonly OfficeConfiguration OfficeConfig = IniConfig.GetIniSection<OfficeConfiguration>();
private static readonly string ExePath; private static readonly string ExePath;
private static readonly bool IsActiveFlag; private static readonly bool IsActiveFlag;
private const string MapiClient = "Microsoft Outlook"; private const string MapiClient = "Microsoft Outlook";
private readonly string _outlookInspectorCaption; private readonly string _outlookInspectorCaption;
private readonly OlObjectClass _outlookInspectorType; private readonly OlObjectClass _outlookInspectorType;
private readonly OutlookEmailExporter _outlookEmailExporter = new(); private readonly OutlookEmailExporter _outlookEmailExporter = new();
static OutlookDestination() { static OutlookDestination()
if (HasOutlook()) { {
IsActiveFlag = true; if (HasOutlook())
} {
ExePath = PluginUtils.GetExePath("OUTLOOK.EXE"); IsActiveFlag = true;
if (ExePath != null && File.Exists(ExePath)) { }
WindowDetails.AddProcessToExcludeFromFreeze("outlook");
} else { ExePath = PluginUtils.GetExePath("OUTLOOK.EXE");
ExePath = GetOutlookExePath(); if (ExePath != null && File.Exists(ExePath))
} {
if (ExePath == null) { WindowDetails.AddProcessToExcludeFromFreeze("outlook");
IsActiveFlag = false; }
} else
} {
ExePath = GetOutlookExePath();
}
if (ExePath == null)
{
IsActiveFlag = false;
}
}
private static string GetOutlookExePath() => RegistryHive.LocalMachine.ReadKey64Or32(@"Microsoft\Windows\CurrentVersion\App Paths\OUTLOOK.EXE"); private static string GetOutlookExePath() => RegistryHive.LocalMachine.ReadKey64Or32(@"Microsoft\Windows\CurrentVersion\App Paths\OUTLOOK.EXE");
/// <summary> /// <summary>
/// Check if Outlook is installed /// Check if Outlook is installed
/// </summary> /// </summary>
/// <returns>Returns true if outlook is installed</returns> /// <returns>Returns true if outlook is installed</returns>
private static bool HasOutlook() private static bool HasOutlook()
{ {
string outlookPath = GetOutlookExePath(); string outlookPath = GetOutlookExePath();
if (outlookPath == null) if (outlookPath == null)
{ {
return false; return false;
} }
return File.Exists(outlookPath);
}
public OutlookDestination() { return File.Exists(outlookPath);
} }
public OutlookDestination(string outlookInspectorCaption, OlObjectClass outlookInspectorType) { public OutlookDestination()
_outlookInspectorCaption = outlookInspectorCaption; {
_outlookInspectorType = outlookInspectorType; }
}
public override string Designation => "Outlook"; public OutlookDestination(string outlookInspectorCaption, OlObjectClass outlookInspectorType)
{
_outlookInspectorCaption = outlookInspectorCaption;
_outlookInspectorType = outlookInspectorType;
}
public override string Description => _outlookInspectorCaption ?? MapiClient; public override string Designation => "Outlook";
public override int Priority => 3; public override string Description => _outlookInspectorCaption ?? MapiClient;
public override bool IsActive => base.IsActive && IsActiveFlag; public override int Priority => 3;
public override bool IsDynamic => true; public override bool IsActive => base.IsActive && IsActiveFlag;
public override Keys EditorShortcutKeys => Keys.Control | Keys.E; public override bool IsDynamic => true;
public override Image DisplayIcon { public override Keys EditorShortcutKeys => Keys.Control | Keys.E;
get
{
if (_outlookInspectorCaption == null)
{
return PluginUtils.GetCachedExeIcon(ExePath, IconApplication);
}
if (OlObjectClass.olAppointment.Equals(_outlookInspectorType)) {
// Make sure we loaded the icon, maybe the configuration has been changed!
return PluginUtils.GetCachedExeIcon(ExePath, IconMeeting);
}
return MailIcon;
}
}
public override IEnumerable<IDestination> DynamicDestinations() { public override Image DisplayIcon
IDictionary<string, OlObjectClass> inspectorCaptions = _outlookEmailExporter.RetrievePossibleTargets(); {
if (inspectorCaptions != null) { get
foreach (string inspectorCaption in inspectorCaptions.Keys) { {
yield return new OutlookDestination(inspectorCaption, inspectorCaptions[inspectorCaption]); if (_outlookInspectorCaption == null)
} {
} return PluginUtils.GetCachedExeIcon(ExePath, IconApplication);
} }
/// <summary> if (OlObjectClass.olAppointment.Equals(_outlookInspectorType))
/// Export the capture to outlook {
/// </summary> // Make sure we loaded the icon, maybe the configuration has been changed!
/// <param name="manuallyInitiated"></param> return PluginUtils.GetCachedExeIcon(ExePath, IconMeeting);
/// <param name="surface"></param> }
/// <param name="captureDetails"></param>
/// <returns></returns>
public override ExportInformation ExportCapture(bool manuallyInitiated, ISurface surface, ICaptureDetails captureDetails) {
ExportInformation exportInformation = new ExportInformation(Designation, Description);
// Outlook logic
string tmpFile = captureDetails.Filename;
if (tmpFile == null || surface.Modified || !Regex.IsMatch(tmpFile, @".*(\.png|\.gif|\.jpg|\.jpeg|\.tiff|\.bmp)$")) {
tmpFile = ImageOutput.SaveNamedTmpFile(surface, captureDetails, new SurfaceOutputSettings().PreventGreenshotFormat());
} else {
Log.InfoFormat("Using already available file: {0}", tmpFile);
}
// Create a attachment name for the image return MailIcon;
string attachmentName = captureDetails.Title; }
if (!string.IsNullOrEmpty(attachmentName)) { }
attachmentName = attachmentName.Trim();
}
// Set default if non is set
if (string.IsNullOrEmpty(attachmentName)) {
attachmentName = "Greenshot Capture";
}
// Make sure it's "clean" so it doesn't corrupt the header
attachmentName = Regex.Replace(attachmentName, @"[^\x20\d\w]", string.Empty);
if (_outlookInspectorCaption != null) { public override IEnumerable<IDestination> DynamicDestinations()
{
IDictionary<string, OlObjectClass> inspectorCaptions = _outlookEmailExporter.RetrievePossibleTargets();
if (inspectorCaptions != null)
{
foreach (string inspectorCaption in inspectorCaptions.Keys)
{
yield return new OutlookDestination(inspectorCaption, inspectorCaptions[inspectorCaption]);
}
}
}
/// <summary>
/// Export the capture to outlook
/// </summary>
/// <param name="manuallyInitiated"></param>
/// <param name="surface"></param>
/// <param name="captureDetails"></param>
/// <returns></returns>
public override ExportInformation ExportCapture(bool manuallyInitiated, ISurface surface, ICaptureDetails captureDetails)
{
ExportInformation exportInformation = new ExportInformation(Designation, Description);
// Outlook logic
string tmpFile = captureDetails.Filename;
if (tmpFile == null || surface.Modified || !Regex.IsMatch(tmpFile, @".*(\.png|\.gif|\.jpg|\.jpeg|\.tiff|\.bmp)$"))
{
tmpFile = ImageOutput.SaveNamedTmpFile(surface, captureDetails, new SurfaceOutputSettings().PreventGreenshotFormat());
}
else
{
Log.InfoFormat("Using already available file: {0}", tmpFile);
}
// Create a attachment name for the image
string attachmentName = captureDetails.Title;
if (!string.IsNullOrEmpty(attachmentName))
{
attachmentName = attachmentName.Trim();
}
// Set default if non is set
if (string.IsNullOrEmpty(attachmentName))
{
attachmentName = "Greenshot Capture";
}
// Make sure it's "clean" so it doesn't corrupt the header
attachmentName = Regex.Replace(attachmentName, @"[^\x20\d\w]", string.Empty);
if (_outlookInspectorCaption != null)
{
_outlookEmailExporter.ExportToInspector(_outlookInspectorCaption, tmpFile, attachmentName); _outlookEmailExporter.ExportToInspector(_outlookInspectorCaption, tmpFile, attachmentName);
exportInformation.ExportMade = true; exportInformation.ExportMade = true;
} else { }
if (!manuallyInitiated) { else
var inspectorCaptions = _outlookEmailExporter.RetrievePossibleTargets(); {
if (inspectorCaptions != null && inspectorCaptions.Count > 0) { if (!manuallyInitiated)
var destinations = new List<IDestination> {
{ var inspectorCaptions = _outlookEmailExporter.RetrievePossibleTargets();
new OutlookDestination() if (inspectorCaptions != null && inspectorCaptions.Count > 0)
}; {
foreach (string inspectorCaption in inspectorCaptions.Keys) { var destinations = new List<IDestination>
destinations.Add(new OutlookDestination(inspectorCaption, inspectorCaptions[inspectorCaption])); {
} new OutlookDestination()
// Return the ExportInformation from the picker without processing, as this indirectly comes from us self };
return ShowPickerMenu(false, surface, captureDetails, destinations); foreach (string inspectorCaption in inspectorCaptions.Keys)
} {
} else { destinations.Add(new OutlookDestination(inspectorCaption, inspectorCaptions[inspectorCaption]));
exportInformation.ExportMade = _outlookEmailExporter.ExportToOutlook(OfficeConfig.OutlookEmailFormat, tmpFile, FilenameHelper.FillPattern(OfficeConfig.EmailSubjectPattern, captureDetails, false), attachmentName, OfficeConfig.EmailTo, OfficeConfig.EmailCC, OfficeConfig.EmailBCC, null); }
}
} // Return the ExportInformation from the picker without processing, as this indirectly comes from us self
ProcessExport(exportInformation, surface); return ShowPickerMenu(false, surface, captureDetails, destinations);
return exportInformation; }
} }
} else
{
exportInformation.ExportMade = _outlookEmailExporter.ExportToOutlook(OfficeConfig.OutlookEmailFormat, tmpFile,
FilenameHelper.FillPattern(OfficeConfig.EmailSubjectPattern, captureDetails, false), attachmentName, OfficeConfig.EmailTo, OfficeConfig.EmailCC,
OfficeConfig.EmailBCC, null);
}
}
ProcessExport(exportInformation, surface);
return exportInformation;
}
}
} }

View file

@ -29,95 +29,127 @@ using GreenshotPlugin.Core;
using GreenshotPlugin.Interfaces; using GreenshotPlugin.Interfaces;
using GreenshotPlugin.Interfaces.Plugin; using GreenshotPlugin.Interfaces.Plugin;
namespace Greenshot.Plugin.Office.Destinations { namespace Greenshot.Plugin.Office.Destinations
/// <summary> {
/// Description of PowerpointDestination. /// <summary>
/// </summary> /// Description of PowerpointDestination.
public class PowerpointDestination : AbstractDestination { /// </summary>
private const int IconApplication = 0; public class PowerpointDestination : AbstractDestination
private const int IconPresentation = 1; {
private const int IconApplication = 0;
private const int IconPresentation = 1;
private static readonly string ExePath; private static readonly string ExePath;
private readonly string _presentationName; private readonly string _presentationName;
private readonly PowerpointExporter _powerpointExporter = new PowerpointExporter(); private readonly PowerpointExporter _powerpointExporter = new PowerpointExporter();
static PowerpointDestination() { static PowerpointDestination()
ExePath = PluginUtils.GetExePath("POWERPNT.EXE"); {
if (ExePath != null && File.Exists(ExePath)) { ExePath = PluginUtils.GetExePath("POWERPNT.EXE");
WindowDetails.AddProcessToExcludeFromFreeze("powerpnt"); if (ExePath != null && File.Exists(ExePath))
} else { {
ExePath = null; WindowDetails.AddProcessToExcludeFromFreeze("powerpnt");
} }
} else
{
ExePath = null;
}
}
public PowerpointDestination() { public PowerpointDestination()
} {
}
public PowerpointDestination(string presentationName) { public PowerpointDestination(string presentationName)
_presentationName = presentationName; {
} _presentationName = presentationName;
}
public override string Designation => "Powerpoint"; public override string Designation => "Powerpoint";
public override string Description { public override string Description
get {
{ get
if (_presentationName == null) { {
return "Microsoft Powerpoint"; if (_presentationName == null)
} {
return _presentationName; return "Microsoft Powerpoint";
} }
}
public override int Priority => 4; return _presentationName;
}
}
public override bool IsDynamic => true; public override int Priority => 4;
public override bool IsActive => base.IsActive && ExePath != null; public override bool IsDynamic => true;
public override Image DisplayIcon { public override bool IsActive => base.IsActive && ExePath != null;
get {
if (!string.IsNullOrEmpty(_presentationName)) {
return PluginUtils.GetCachedExeIcon(ExePath, IconPresentation);
}
return PluginUtils.GetCachedExeIcon(ExePath, IconApplication); public override Image DisplayIcon
} {
} get
{
if (!string.IsNullOrEmpty(_presentationName))
{
return PluginUtils.GetCachedExeIcon(ExePath, IconPresentation);
}
public override IEnumerable<IDestination> DynamicDestinations() { return PluginUtils.GetCachedExeIcon(ExePath, IconApplication);
foreach (string presentationName in _powerpointExporter.GetPowerpointPresentations()) { }
yield return new PowerpointDestination(presentationName); }
}
}
public override ExportInformation ExportCapture(bool manuallyInitiated, ISurface surface, ICaptureDetails captureDetails) { public override IEnumerable<IDestination> DynamicDestinations()
ExportInformation exportInformation = new ExportInformation(Designation, Description); {
string tmpFile = captureDetails.Filename; foreach (string presentationName in _powerpointExporter.GetPowerpointPresentations())
Size imageSize = Size.Empty; {
if (tmpFile == null || surface.Modified || !Regex.IsMatch(tmpFile, @".*(\.png|\.gif|\.jpg|\.jpeg|\.tiff|\.bmp)$")) { yield return new PowerpointDestination(presentationName);
tmpFile = ImageOutput.SaveNamedTmpFile(surface, captureDetails, new SurfaceOutputSettings().PreventGreenshotFormat()); }
imageSize = surface.Image.Size; }
}
if (_presentationName != null) { public override ExportInformation ExportCapture(bool manuallyInitiated, ISurface surface, ICaptureDetails captureDetails)
exportInformation.ExportMade = _powerpointExporter.ExportToPresentation(_presentationName, tmpFile, imageSize, captureDetails.Title); {
} else { ExportInformation exportInformation = new ExportInformation(Designation, Description);
if (!manuallyInitiated) { string tmpFile = captureDetails.Filename;
var presentations = _powerpointExporter.GetPowerpointPresentations().ToList(); Size imageSize = Size.Empty;
if (presentations != null && presentations.Count > 0) { if (tmpFile == null || surface.Modified || !Regex.IsMatch(tmpFile, @".*(\.png|\.gif|\.jpg|\.jpeg|\.tiff|\.bmp)$"))
var destinations = new List<IDestination> {new PowerpointDestination()}; {
foreach (string presentation in presentations) { tmpFile = ImageOutput.SaveNamedTmpFile(surface, captureDetails, new SurfaceOutputSettings().PreventGreenshotFormat());
destinations.Add(new PowerpointDestination(presentation)); imageSize = surface.Image.Size;
} }
// Return the ExportInformation from the picker without processing, as this indirectly comes from us self
return ShowPickerMenu(false, surface, captureDetails, destinations); if (_presentationName != null)
} {
} else if (!exportInformation.ExportMade) { exportInformation.ExportMade = _powerpointExporter.ExportToPresentation(_presentationName, tmpFile, imageSize, captureDetails.Title);
exportInformation.ExportMade = _powerpointExporter.InsertIntoNewPresentation(tmpFile, imageSize, captureDetails.Title); }
} else
} {
ProcessExport(exportInformation, surface); if (!manuallyInitiated)
return exportInformation; {
} var presentations = _powerpointExporter.GetPowerpointPresentations().ToList();
} if (presentations != null && presentations.Count > 0)
{
var destinations = new List<IDestination>
{
new PowerpointDestination()
};
foreach (string presentation in presentations)
{
destinations.Add(new PowerpointDestination(presentation));
}
// Return the ExportInformation from the picker without processing, as this indirectly comes from us self
return ShowPickerMenu(false, surface, captureDetails, destinations);
}
}
else if (!exportInformation.ExportMade)
{
exportInformation.ExportMade = _powerpointExporter.InsertIntoNewPresentation(tmpFile, imageSize, captureDetails.Title);
}
}
ProcessExport(exportInformation, surface);
return exportInformation;
}
}
} }

View file

@ -30,102 +30,134 @@ using GreenshotPlugin.Core;
using GreenshotPlugin.Interfaces; using GreenshotPlugin.Interfaces;
using GreenshotPlugin.Interfaces.Plugin; using GreenshotPlugin.Interfaces.Plugin;
namespace Greenshot.Plugin.Office.Destinations { namespace Greenshot.Plugin.Office.Destinations
/// <summary> {
/// Description of EmailDestination. /// <summary>
/// </summary> /// Description of EmailDestination.
public class WordDestination : AbstractDestination { /// </summary>
private static readonly log4net.ILog Log = log4net.LogManager.GetLogger(typeof(WordDestination)); public class WordDestination : AbstractDestination
private const int IconApplication = 0; {
private const int IconDocument = 1; private static readonly log4net.ILog Log = log4net.LogManager.GetLogger(typeof(WordDestination));
private static readonly string ExePath; private const int IconApplication = 0;
private readonly string _documentCaption; private const int IconDocument = 1;
private static readonly string ExePath;
private readonly string _documentCaption;
private readonly WordExporter _wordExporter = new WordExporter(); private readonly WordExporter _wordExporter = new WordExporter();
static WordDestination() {
ExePath = PluginUtils.GetExePath("WINWORD.EXE");
if (ExePath != null && !File.Exists(ExePath)) {
ExePath = null;
}
}
public WordDestination() { static WordDestination()
{
ExePath = PluginUtils.GetExePath("WINWORD.EXE");
if (ExePath != null && !File.Exists(ExePath))
{
ExePath = null;
}
}
} public WordDestination()
{
}
public WordDestination(string wordCaption) { public WordDestination(string wordCaption)
_documentCaption = wordCaption; {
} _documentCaption = wordCaption;
}
public override string Designation => "Word"; public override string Designation => "Word";
public override string Description => _documentCaption ?? "Microsoft Word"; public override string Description => _documentCaption ?? "Microsoft Word";
public override int Priority => 4; public override int Priority => 4;
public override bool IsDynamic => true; public override bool IsDynamic => true;
public override bool IsActive => base.IsActive && ExePath != null; public override bool IsActive => base.IsActive && ExePath != null;
public override Image DisplayIcon => PluginUtils.GetCachedExeIcon(ExePath, !string.IsNullOrEmpty(_documentCaption) ? IconDocument : IconApplication); public override Image DisplayIcon => PluginUtils.GetCachedExeIcon(ExePath, !string.IsNullOrEmpty(_documentCaption) ? IconDocument : IconApplication);
public override IEnumerable<IDestination> DynamicDestinations() { public override IEnumerable<IDestination> DynamicDestinations()
foreach (string wordCaption in _wordExporter.GetWordDocuments()) { {
yield return new WordDestination(wordCaption); foreach (string wordCaption in _wordExporter.GetWordDocuments())
} {
} yield return new WordDestination(wordCaption);
}
}
public override ExportInformation ExportCapture(bool manuallyInitiated, ISurface surface, ICaptureDetails captureDetails) { public override ExportInformation ExportCapture(bool manuallyInitiated, ISurface surface, ICaptureDetails captureDetails)
ExportInformation exportInformation = new ExportInformation(Designation, Description); {
string tmpFile = captureDetails.Filename; ExportInformation exportInformation = new ExportInformation(Designation, Description);
if (tmpFile == null || surface.Modified || !Regex.IsMatch(tmpFile, @".*(\.png|\.gif|\.jpg|\.jpeg|\.tiff|\.bmp)$")) { string tmpFile = captureDetails.Filename;
tmpFile = ImageOutput.SaveNamedTmpFile(surface, captureDetails, new SurfaceOutputSettings().PreventGreenshotFormat()); if (tmpFile == null || surface.Modified || !Regex.IsMatch(tmpFile, @".*(\.png|\.gif|\.jpg|\.jpeg|\.tiff|\.bmp)$"))
} {
if (_documentCaption != null) { tmpFile = ImageOutput.SaveNamedTmpFile(surface, captureDetails, new SurfaceOutputSettings().PreventGreenshotFormat());
try { }
if (_documentCaption != null)
{
try
{
_wordExporter.InsertIntoExistingDocument(_documentCaption, tmpFile); _wordExporter.InsertIntoExistingDocument(_documentCaption, tmpFile);
exportInformation.ExportMade = true; exportInformation.ExportMade = true;
} catch (Exception) { }
try { catch (Exception)
{
try
{
_wordExporter.InsertIntoExistingDocument(_documentCaption, tmpFile); _wordExporter.InsertIntoExistingDocument(_documentCaption, tmpFile);
exportInformation.ExportMade = true; exportInformation.ExportMade = true;
} catch (Exception ex) { }
Log.Error(ex); catch (Exception ex)
// TODO: Change to general logic in ProcessExport {
surface.SendMessageEvent(this, SurfaceMessageTyp.Error, Language.GetFormattedString("destination_exportfailed", Description)); Log.Error(ex);
} // TODO: Change to general logic in ProcessExport
} surface.SendMessageEvent(this, SurfaceMessageTyp.Error, Language.GetFormattedString("destination_exportfailed", Description));
} else { }
if (!manuallyInitiated) { }
var documents = _wordExporter.GetWordDocuments().ToList(); }
if (documents != null && documents.Count > 0) { else
var destinations = new List<IDestination> {
{ if (!manuallyInitiated)
new WordDestination() {
}; var documents = _wordExporter.GetWordDocuments().ToList();
foreach (string document in documents) { if (documents != null && documents.Count > 0)
destinations.Add(new WordDestination(document)); {
} var destinations = new List<IDestination>
// Return the ExportInformation from the picker without processing, as this indirectly comes from us self {
return ShowPickerMenu(false, surface, captureDetails, destinations); new WordDestination()
} };
} foreach (string document in documents)
try { {
destinations.Add(new WordDestination(document));
}
// Return the ExportInformation from the picker without processing, as this indirectly comes from us self
return ShowPickerMenu(false, surface, captureDetails, destinations);
}
}
try
{
_wordExporter.InsertIntoNewDocument(tmpFile, null, null); _wordExporter.InsertIntoNewDocument(tmpFile, null, null);
exportInformation.ExportMade = true; exportInformation.ExportMade = true;
} catch(Exception) { }
// Retry once, just in case catch (Exception)
try { {
// Retry once, just in case
try
{
_wordExporter.InsertIntoNewDocument(tmpFile, null, null); _wordExporter.InsertIntoNewDocument(tmpFile, null, null);
exportInformation.ExportMade = true; exportInformation.ExportMade = true;
} catch (Exception ex) { }
Log.Error(ex); catch (Exception ex)
// TODO: Change to general logic in ProcessExport {
surface.SendMessageEvent(this, SurfaceMessageTyp.Error, Language.GetFormattedString("destination_exportfailed", Description)); Log.Error(ex);
} // TODO: Change to general logic in ProcessExport
} surface.SendMessageEvent(this, SurfaceMessageTyp.Error, Language.GetFormattedString("destination_exportfailed", Description));
} }
ProcessExport(exportInformation, surface); }
return exportInformation; }
}
} ProcessExport(exportInformation, surface);
return exportInformation;
}
}
} }

View file

@ -23,32 +23,40 @@ using Greenshot.Plugin.Office.OfficeInterop;
using GreenshotPlugin.IniFile; using GreenshotPlugin.IniFile;
using Microsoft.Office.Interop.PowerPoint; using Microsoft.Office.Interop.PowerPoint;
namespace Greenshot.Plugin.Office { namespace Greenshot.Plugin.Office
{
/// <summary>
/// Description of CoreConfiguration.
/// </summary>
[IniSection("Office", Description = "Greenshot Office configuration")]
public class OfficeConfiguration : IniSection
{
[IniProperty("OutlookEmailFormat", Description = "Default type for emails. (Text, HTML)", DefaultValue = "HTML")]
public EmailFormat OutlookEmailFormat { get; set; }
/// <summary> [IniProperty("EmailSubjectPattern", Description = "Email subject pattern, works like the OutputFileFilenamePattern", DefaultValue = "${title}")]
/// Description of CoreConfiguration. public string EmailSubjectPattern { get; set; }
/// </summary>
[IniSection("Office", Description="Greenshot Office configuration")]
public class OfficeConfiguration : IniSection {
[IniProperty("OutlookEmailFormat", Description = "Default type for emails. (Text, HTML)", DefaultValue = "HTML")]
public EmailFormat OutlookEmailFormat { get; set; }
[IniProperty("EmailSubjectPattern", Description = "Email subject pattern, works like the OutputFileFilenamePattern", DefaultValue = "${title}")] [IniProperty("EmailTo", Description = "Default value for the to in emails that are created", DefaultValue = "")]
public string EmailSubjectPattern { get; set; } public string EmailTo { get; set; }
[IniProperty("EmailTo", Description = "Default value for the to in emails that are created", DefaultValue = "")]
public string EmailTo { get; set; }
[IniProperty("EmailCC", Description = "Default value for the CC in emails that are created", DefaultValue = "")]
public string EmailCC { get; set; }
[IniProperty("EmailBCC", Description = "Default value for the BCC in emails that are created", DefaultValue = "")]
public string EmailBCC { get; set; }
[IniProperty("OutlookAllowExportInMeetings", Description = "For Outlook: Allow export in meeting items", DefaultValue = "False")]
public bool OutlookAllowExportInMeetings { get; set; }
[IniProperty("WordLockAspectRatio", Description = "For Word: Lock the aspect ratio of the image", DefaultValue = "True")]
public bool WordLockAspectRatio { get; set; }
[IniProperty("PowerpointLockAspectRatio", Description = "For Powerpoint: Lock the aspect ratio of the image", DefaultValue = "True")]
public bool PowerpointLockAspectRatio { get; set; }
[IniProperty("PowerpointSlideLayout", Description = "For Powerpoint: Slide layout, changing this to a wrong value will fallback on ppLayoutBlank!!", DefaultValue = "ppLayoutPictureWithCaption")]
public PpSlideLayout PowerpointSlideLayout { get; set; }
} [IniProperty("EmailCC", Description = "Default value for the CC in emails that are created", DefaultValue = "")]
public string EmailCC { get; set; }
[IniProperty("EmailBCC", Description = "Default value for the BCC in emails that are created", DefaultValue = "")]
public string EmailBCC { get; set; }
[IniProperty("OutlookAllowExportInMeetings", Description = "For Outlook: Allow export in meeting items", DefaultValue = "False")]
public bool OutlookAllowExportInMeetings { get; set; }
[IniProperty("WordLockAspectRatio", Description = "For Word: Lock the aspect ratio of the image", DefaultValue = "True")]
public bool WordLockAspectRatio { get; set; }
[IniProperty("PowerpointLockAspectRatio", Description = "For Powerpoint: Lock the aspect ratio of the image", DefaultValue = "True")]
public bool PowerpointLockAspectRatio { get; set; }
[IniProperty("PowerpointSlideLayout", Description = "For Powerpoint: Slide layout, changing this to a wrong value will fallback on ppLayoutBlank!!",
DefaultValue = "ppLayoutPictureWithCaption")]
public PpSlideLayout PowerpointSlideLayout { get; set; }
}
} }

View file

@ -34,6 +34,7 @@ namespace Greenshot.Plugin.Office.OfficeExport.Entities
{ {
return string.Format("{0} / {1}", Parent.Name, Name); return string.Format("{0} / {1}", Parent.Name, Name);
} }
return string.Format("{0} / {1} / {2}", Parent.Parent.Name, Parent.Name, Name); return string.Format("{0} / {1} / {2}", Parent.Parent.Name, Parent.Name, Name);
} }
} }

View file

@ -53,10 +53,12 @@ namespace Greenshot.Plugin.Office.OfficeExport
// Ignore, probably no excel running // Ignore, probably no excel running
return null; return null;
} }
if (excelApplication?.ComObject != null) if (excelApplication?.ComObject != null)
{ {
InitializeVariables(excelApplication); InitializeVariables(excelApplication);
} }
return excelApplication; return excelApplication;
} }
@ -71,6 +73,7 @@ namespace Greenshot.Plugin.Office.OfficeExport
{ {
excelApplication = DisposableCom.Create(new Application()); excelApplication = DisposableCom.Create(new Application());
} }
InitializeVariables(excelApplication); InitializeVariables(excelApplication);
return excelApplication; return excelApplication;
} }
@ -108,10 +111,11 @@ namespace Greenshot.Plugin.Office.OfficeExport
{ {
return; return;
} }
if (!Version.TryParse(excelApplication.ComObject.Version, out _excelVersion)) if (!Version.TryParse(excelApplication.ComObject.Version, out _excelVersion))
{ {
LOG.Warn("Assuming Excel version 1997."); LOG.Warn("Assuming Excel version 1997.");
_excelVersion = new Version((int)OfficeVersions.Office97, 0, 0, 0); _excelVersion = new Version((int) OfficeVersions.Office97, 0, 0, 0);
} }
} }
@ -132,7 +136,7 @@ namespace Greenshot.Plugin.Office.OfficeExport
using var workbooks = DisposableCom.Create(excelApplication.ComObject.Workbooks); using var workbooks = DisposableCom.Create(excelApplication.ComObject.Workbooks);
for (int i = 1; i <= workbooks.ComObject.Count; i++) for (int i = 1; i <= workbooks.ComObject.Count; i++)
{ {
using var workbook = DisposableCom.Create((_Workbook)workbooks.ComObject[i]); using var workbook = DisposableCom.Create((_Workbook) workbooks.ComObject[i]);
if ((workbook != null) && workbook.ComObject.Name.StartsWith(workbookName)) if ((workbook != null) && workbook.ComObject.Name.StartsWith(workbookName))
{ {
InsertIntoExistingWorkbook(workbook, tmpFile, imageSize); InsertIntoExistingWorkbook(workbook, tmpFile, imageSize);
@ -191,9 +195,8 @@ namespace Greenshot.Plugin.Office.OfficeExport
excelApplication.ComObject.Visible = true; excelApplication.ComObject.Visible = true;
using var workbooks = DisposableCom.Create(excelApplication.ComObject.Workbooks); using var workbooks = DisposableCom.Create(excelApplication.ComObject.Workbooks);
using var workbook = DisposableCom.Create((_Workbook)workbooks.ComObject.Add()); using var workbook = DisposableCom.Create((_Workbook) workbooks.ComObject.Add());
InsertIntoExistingWorkbook(workbook, tmpFile, imageSize); InsertIntoExistingWorkbook(workbook, tmpFile, imageSize);
} }
} }
} }

View file

@ -38,7 +38,10 @@ namespace Greenshot.Plugin.Office.OfficeExport
public class OneNoteExporter public class OneNoteExporter
{ {
private const string XmlImageContent = "<one:Image format=\"png\"><one:Size width=\"{1}.0\" height=\"{2}.0\" isSetByUser=\"true\" /><one:Data>{0}</one:Data></one:Image>"; private const string XmlImageContent = "<one:Image format=\"png\"><one:Size width=\"{1}.0\" height=\"{2}.0\" isSetByUser=\"true\" /><one:Data>{0}</one:Data></one:Image>";
private const string XmlOutline = "<?xml version=\"1.0\"?><one:Page xmlns:one=\"{2}\" ID=\"{1}\"><one:Title><one:OE><one:T><![CDATA[{3}]]></one:T></one:OE></one:Title>{0}</one:Page>";
private const string XmlOutline =
"<?xml version=\"1.0\"?><one:Page xmlns:one=\"{2}\" ID=\"{1}\"><one:Title><one:OE><one:T><![CDATA[{3}]]></one:T></one:OE></one:Title>{0}</one:Page>";
private const string OnenoteNamespace2010 = "http://schemas.microsoft.com/office/onenote/2010/onenote"; private const string OnenoteNamespace2010 = "http://schemas.microsoft.com/office/onenote/2010/onenote";
private static readonly log4net.ILog LOG = log4net.LogManager.GetLogger(typeof(OneNoteExporter)); private static readonly log4net.ILog LOG = log4net.LogManager.GetLogger(typeof(OneNoteExporter));
@ -107,6 +110,7 @@ namespace Greenshot.Plugin.Office.OfficeExport
{ {
LOG.Warn("Unable to navigate to the target page", ex); LOG.Warn("Unable to navigate to the target page", ex);
} }
return true; return true;
} }
@ -126,6 +130,7 @@ namespace Greenshot.Plugin.Office.OfficeExport
// Ignore, probably no OneNote running // Ignore, probably no OneNote running
return null; return null;
} }
return oneNoteApplication; return oneNoteApplication;
} }
@ -140,6 +145,7 @@ namespace Greenshot.Plugin.Office.OfficeExport
{ {
oneNoteApplication = DisposableCom.Create(new Application()); oneNoteApplication = DisposableCom.Create(new Application());
} }
return oneNoteApplication; return oneNoteApplication;
} }
@ -183,6 +189,7 @@ namespace Greenshot.Plugin.Office.OfficeExport
}; };
} }
} }
if ("one:Section".Equals(xmlReader.Name)) if ("one:Section".Equals(xmlReader.Name))
{ {
string id = xmlReader.GetAttribute("ID"); string id = xmlReader.GetAttribute("ID");
@ -196,6 +203,7 @@ namespace Greenshot.Plugin.Office.OfficeExport
}; };
} }
} }
if ("one:Page".Equals(xmlReader.Name)) if ("one:Page".Equals(xmlReader.Name))
{ {
// Skip deleted items // Skip deleted items
@ -214,6 +222,7 @@ namespace Greenshot.Plugin.Office.OfficeExport
{ {
continue; continue;
} }
page.IsCurrentlyViewed = "true".Equals(xmlReader.GetAttribute("isCurrentlyViewed")); page.IsCurrentlyViewed = "true".Equals(xmlReader.GetAttribute("isCurrentlyViewed"));
pages.Add(page); pages.Add(page);
} }
@ -231,22 +240,26 @@ namespace Greenshot.Plugin.Office.OfficeExport
} }
catch (COMException cEx) catch (COMException cEx)
{ {
if (cEx.ErrorCode == unchecked((int)0x8002801D)) if (cEx.ErrorCode == unchecked((int) 0x8002801D))
{ {
LOG.Warn("Wrong registry keys, to solve this remove the OneNote key as described here: http://microsoftmercenary.com/wp/outlook-excel-interop-calls-breaking-solved/"); LOG.Warn(
"Wrong registry keys, to solve this remove the OneNote key as described here: http://microsoftmercenary.com/wp/outlook-excel-interop-calls-breaking-solved/");
} }
LOG.Warn("Problem retrieving onenote destinations, ignoring: ", cEx); LOG.Warn("Problem retrieving onenote destinations, ignoring: ", cEx);
} }
catch (Exception ex) catch (Exception ex)
{ {
LOG.Warn("Problem retrieving onenote destinations, ignoring: ", ex); LOG.Warn("Problem retrieving onenote destinations, ignoring: ", ex);
} }
pages.Sort((page1, page2) => pages.Sort((page1, page2) =>
{ {
if (page1.IsCurrentlyViewed || page2.IsCurrentlyViewed) if (page1.IsCurrentlyViewed || page2.IsCurrentlyViewed)
{ {
return page2.IsCurrentlyViewed.CompareTo(page1.IsCurrentlyViewed); return page2.IsCurrentlyViewed.CompareTo(page1.IsCurrentlyViewed);
} }
return string.Compare(page1.DisplayName, page2.DisplayName, StringComparison.Ordinal); return string.Compare(page1.DisplayName, page2.DisplayName, StringComparison.Ordinal);
}); });
return pages; return pages;
@ -264,6 +277,7 @@ namespace Greenshot.Plugin.Office.OfficeExport
{ {
return null; return null;
} }
// ReSharper disable once RedundantAssignment // ReSharper disable once RedundantAssignment
string unfiledNotesPath = ""; string unfiledNotesPath = "";
oneNoteApplication.ComObject.GetSpecialLocation(specialLocation, out unfiledNotesPath); oneNoteApplication.ComObject.GetSpecialLocation(specialLocation, out unfiledNotesPath);
@ -285,6 +299,7 @@ namespace Greenshot.Plugin.Office.OfficeExport
{ {
continue; continue;
} }
string id = xmlReader.GetAttribute("ID"); string id = xmlReader.GetAttribute("ID");
string path = xmlReader.GetAttribute("path"); string path = xmlReader.GetAttribute("path");
if (unfiledNotesPath.Equals(path)) if (unfiledNotesPath.Equals(path))
@ -301,6 +316,7 @@ namespace Greenshot.Plugin.Office.OfficeExport
} }
} }
} }
return null; return null;
} }
} }

View file

@ -47,7 +47,9 @@ namespace Greenshot.Plugin.Office.OfficeExport
private const string ProfilesKey = @"Software\Microsoft\Windows NT\CurrentVersion\Windows Messaging Subsystem\Profiles\"; private const string ProfilesKey = @"Software\Microsoft\Windows NT\CurrentVersion\Windows Messaging Subsystem\Profiles\";
private const string AccountKey = "9375CFF0413111d3B88A00104B2A6676"; private const string AccountKey = "9375CFF0413111d3B88A00104B2A6676";
private const string NewSignatureValue = "New Signature"; private const string NewSignatureValue = "New Signature";
private const string DefaultProfileValue = "DefaultProfile"; private const string DefaultProfileValue = "DefaultProfile";
// Schema definitions for the MAPI properties, see: http://msdn.microsoft.com/en-us/library/aa454438.aspx and: http://msdn.microsoft.com/en-us/library/bb446117.aspx // Schema definitions for the MAPI properties, see: http://msdn.microsoft.com/en-us/library/aa454438.aspx and: http://msdn.microsoft.com/en-us/library/bb446117.aspx
private const string AttachmentContentId = @"http://schemas.microsoft.com/mapi/proptag/0x3712001E"; private const string AttachmentContentId = @"http://schemas.microsoft.com/mapi/proptag/0x3712001E";
@ -73,10 +75,10 @@ namespace Greenshot.Plugin.Office.OfficeExport
} }
// The activeexplorer inline response only works with >= 2013, Microsoft Outlook 15.0 Object Library // The activeexplorer inline response only works with >= 2013, Microsoft Outlook 15.0 Object Library
if (_outlookVersion.Major >= (int)OfficeVersions.Office2013) if (_outlookVersion.Major >= (int) OfficeVersions.Office2013)
{ {
// Check inline "panel" for Outlook 2013 // Check inline "panel" for Outlook 2013
using var activeExplorer = DisposableCom.Create((_Explorer)outlookApplication.ComObject.ActiveExplorer()); using var activeExplorer = DisposableCom.Create((_Explorer) outlookApplication.ComObject.ActiveExplorer());
// Only if we have one and if the capture is the one we selected // Only if we have one and if the capture is the one we selected
if ((activeExplorer != null) && activeExplorer.ComObject.Caption.StartsWith(inspectorCaption)) if ((activeExplorer != null) && activeExplorer.ComObject.Caption.StartsWith(inspectorCaption))
{ {
@ -90,15 +92,17 @@ namespace Greenshot.Plugin.Office.OfficeExport
{ {
return ExportToInspector(null, activeExplorer, mailItem.Class, mailItem, tmpFile, attachmentName); return ExportToInspector(null, activeExplorer, mailItem.Class, mailItem, tmpFile, attachmentName);
} }
break; break;
case AppointmentItem appointmentItem: case AppointmentItem appointmentItem:
if ((_outlookVersion.Major >= (int)OfficeVersions.Office2010) && _officeConfiguration.OutlookAllowExportInMeetings) if ((_outlookVersion.Major >= (int) OfficeVersions.Office2010) && _officeConfiguration.OutlookAllowExportInMeetings)
{ {
if (!string.IsNullOrEmpty(appointmentItem.Organizer) && appointmentItem.Organizer.Equals(_currentUser)) if (!string.IsNullOrEmpty(appointmentItem.Organizer) && appointmentItem.Organizer.Equals(_currentUser))
{ {
return ExportToInspector(null, activeExplorer, appointmentItem.Class, null, tmpFile, attachmentName); return ExportToInspector(null, activeExplorer, appointmentItem.Class, null, tmpFile, attachmentName);
} }
} }
break; break;
} }
} }
@ -110,10 +114,11 @@ namespace Greenshot.Plugin.Office.OfficeExport
{ {
return false; return false;
} }
LOG.DebugFormat("Got {0} inspectors to check", inspectors.ComObject.Count); LOG.DebugFormat("Got {0} inspectors to check", inspectors.ComObject.Count);
for (int i = 1; i <= inspectors.ComObject.Count; i++) for (int i = 1; i <= inspectors.ComObject.Count; i++)
{ {
using var inspector = DisposableCom.Create((_Inspector)inspectors.ComObject[i]); using var inspector = DisposableCom.Create((_Inspector) inspectors.ComObject[i]);
string currentCaption = inspector.ComObject.Caption; string currentCaption = inspector.ComObject.Caption;
if (!currentCaption.StartsWith(inspectorCaption)) if (!currentCaption.StartsWith(inspectorCaption))
{ {
@ -130,6 +135,7 @@ namespace Greenshot.Plugin.Office.OfficeExport
{ {
continue; continue;
} }
try try
{ {
return ExportToInspector(inspector, null, mailItem.Class, mailItem, tmpFile, attachmentName); return ExportToInspector(inspector, null, mailItem.Class, mailItem, tmpFile, attachmentName);
@ -138,9 +144,10 @@ namespace Greenshot.Plugin.Office.OfficeExport
{ {
LOG.Error($"Export to {currentCaption} failed.", exExport); LOG.Error($"Export to {currentCaption} failed.", exExport);
} }
break; break;
case AppointmentItem appointmentItem: case AppointmentItem appointmentItem:
if ((_outlookVersion.Major >= (int)OfficeVersions.Office2010) && _officeConfiguration.OutlookAllowExportInMeetings) if ((_outlookVersion.Major >= (int) OfficeVersions.Office2010) && _officeConfiguration.OutlookAllowExportInMeetings)
{ {
if (!string.IsNullOrEmpty(appointmentItem.Organizer) && !appointmentItem.Organizer.Equals(_currentUser)) if (!string.IsNullOrEmpty(appointmentItem.Organizer) && !appointmentItem.Organizer.Equals(_currentUser))
{ {
@ -153,6 +160,7 @@ namespace Greenshot.Plugin.Office.OfficeExport
// skip, can't export to olAppointment // skip, can't export to olAppointment
continue; continue;
} }
try try
{ {
return ExportToInspector(inspector, null, appointmentItem.Class, null, tmpFile, attachmentName); return ExportToInspector(inspector, null, appointmentItem.Class, null, tmpFile, attachmentName);
@ -161,6 +169,7 @@ namespace Greenshot.Plugin.Office.OfficeExport
{ {
LOG.Error($"Export to {currentCaption} failed.", exExport); LOG.Error($"Export to {currentCaption} failed.", exExport);
} }
break; break;
default: default:
continue; continue;
@ -168,6 +177,7 @@ namespace Greenshot.Plugin.Office.OfficeExport
} }
} }
} }
return false; return false;
} }
@ -181,7 +191,8 @@ namespace Greenshot.Plugin.Office.OfficeExport
/// <param name="explorer"></param> /// <param name="explorer"></param>
/// <param name="itemClass"></param> /// <param name="itemClass"></param>
/// <returns></returns> /// <returns></returns>
private bool ExportToInspector(IDisposableCom<_Inspector> inspector, IDisposableCom<_Explorer> explorer, OlObjectClass itemClass, MailItem mailItem, string tmpFile, string attachmentName) private bool ExportToInspector(IDisposableCom<_Inspector> inspector, IDisposableCom<_Explorer> explorer, OlObjectClass itemClass, MailItem mailItem, string tmpFile,
string attachmentName)
{ {
bool isMail = OlObjectClass.olMail.Equals(itemClass); bool isMail = OlObjectClass.olMail.Equals(itemClass);
bool isAppointment = OlObjectClass.olAppointment.Equals(itemClass); bool isAppointment = OlObjectClass.olAppointment.Equals(itemClass);
@ -190,6 +201,7 @@ namespace Greenshot.Plugin.Office.OfficeExport
LOG.Warn("Item is no mail or appointment."); LOG.Warn("Item is no mail or appointment.");
return false; return false;
} }
try try
{ {
// Make sure the inspector is activated, only this way the word editor is active! // Make sure the inspector is activated, only this way the word editor is active!
@ -200,25 +212,27 @@ namespace Greenshot.Plugin.Office.OfficeExport
{ {
isTextFormat = OlBodyFormat.olFormatPlain.Equals(mailItem.BodyFormat); isTextFormat = OlBodyFormat.olFormatPlain.Equals(mailItem.BodyFormat);
} }
if (isAppointment || !isTextFormat) if (isAppointment || !isTextFormat)
{ {
// Check for wordmail, if so use the wordexporter // Check for wordmail, if so use the wordexporter
// http://msdn.microsoft.com/en-us/library/dd492012%28v=office.12%29.aspx // http://msdn.microsoft.com/en-us/library/dd492012%28v=office.12%29.aspx
// Earlier versions of Outlook also supported an Inspector.HTMLEditor object property, but since Internet Explorer is no longer the rendering engine for HTML messages and posts, HTMLEditor is no longer supported. // Earlier versions of Outlook also supported an Inspector.HTMLEditor object property, but since Internet Explorer is no longer the rendering engine for HTML messages and posts, HTMLEditor is no longer supported.
IDisposableCom<_Document> wordDocument = null; IDisposableCom<_Document> wordDocument = null;
if ((explorer != null) && (_outlookVersion.Major >= (int)OfficeVersions.Office2013)) if ((explorer != null) && (_outlookVersion.Major >= (int) OfficeVersions.Office2013))
{ {
// TODO: Needs to have the Microsoft Outlook 15.0 Object Library installed // TODO: Needs to have the Microsoft Outlook 15.0 Object Library installed
wordDocument = DisposableCom.Create((_Document)explorer.ComObject.ActiveInlineResponseWordEditor); wordDocument = DisposableCom.Create((_Document) explorer.ComObject.ActiveInlineResponseWordEditor);
} }
else if (inspector != null) else if (inspector != null)
{ {
if (inspector.ComObject.IsWordMail() && (inspector.ComObject.EditorType == OlEditorType.olEditorWord)) if (inspector.ComObject.IsWordMail() && (inspector.ComObject.EditorType == OlEditorType.olEditorWord))
{ {
var tmpWordDocument = (_Document)inspector.ComObject.WordEditor; var tmpWordDocument = (_Document) inspector.ComObject.WordEditor;
wordDocument = DisposableCom.Create(tmpWordDocument); wordDocument = DisposableCom.Create(tmpWordDocument);
} }
} }
if (wordDocument != null) if (wordDocument != null)
{ {
using (wordDocument) using (wordDocument)
@ -248,6 +262,7 @@ namespace Greenshot.Plugin.Office.OfficeExport
LOG.Info("Trying export for outlook < 2007."); LOG.Info("Trying export for outlook < 2007.");
} }
} }
// Only use mailitem as it should be filled!! // Only use mailitem as it should be filled!!
if (mailItem != null) if (mailItem != null)
{ {
@ -255,7 +270,7 @@ namespace Greenshot.Plugin.Office.OfficeExport
} }
string contentId; string contentId;
if (_outlookVersion.Major >= (int)OfficeVersions.Office2007) if (_outlookVersion.Major >= (int) OfficeVersions.Office2007)
{ {
contentId = Guid.NewGuid().ToString(); contentId = Guid.NewGuid().ToString();
} }
@ -283,7 +298,7 @@ namespace Greenshot.Plugin.Office.OfficeExport
var selection = document2.selection; var selection = document2.selection;
if (selection != null) if (selection != null)
{ {
var range = (IHTMLTxtRange)selection.createRange(); var range = (IHTMLTxtRange) selection.createRange();
if (range != null) if (range != null)
{ {
// First paste, than attach (otherwise the range is wrong!) // First paste, than attach (otherwise the range is wrong!)
@ -315,7 +330,7 @@ namespace Greenshot.Plugin.Office.OfficeExport
// Create the attachment (if inlined the attachment isn't visible as attachment!) // Create the attachment (if inlined the attachment isn't visible as attachment!)
using var attachments = DisposableCom.Create(mailItem.Attachments); using var attachments = DisposableCom.Create(mailItem.Attachments);
using var attachment = DisposableCom.Create(attachments.ComObject.Add(tmpFile, OlAttachmentType.olByValue, inlinePossible ? 0 : 1, attachmentName)); using var attachment = DisposableCom.Create(attachments.ComObject.Add(tmpFile, OlAttachmentType.olByValue, inlinePossible ? 0 : 1, attachmentName));
if (_outlookVersion.Major >= (int)OfficeVersions.Office2007) if (_outlookVersion.Major >= (int) OfficeVersions.Office2007)
{ {
// Add the content id to the attachment, this only works for Outlook >= 2007 // Add the content id to the attachment, this only works for Outlook >= 2007
try try
@ -341,9 +356,11 @@ namespace Greenshot.Plugin.Office.OfficeExport
{ {
caption = explorer.ComObject.Caption; caption = explorer.ComObject.Caption;
} }
LOG.Warn($"Problem while trying to add attachment to Item '{caption}'", ex); LOG.Warn($"Problem while trying to add attachment to Item '{caption}'", ex);
return false; return false;
} }
try try
{ {
if (inspector != null) if (inspector != null)
@ -360,6 +377,7 @@ namespace Greenshot.Plugin.Office.OfficeExport
LOG.Warn("Problem activating inspector/explorer: ", ex); LOG.Warn("Problem activating inspector/explorer: ", ex);
return false; return false;
} }
LOG.Debug("Finished!"); LOG.Debug("Finished!");
return true; return true;
} }
@ -376,27 +394,32 @@ namespace Greenshot.Plugin.Office.OfficeExport
/// <param name="cc"></param> /// <param name="cc"></param>
/// <param name="bcc"></param> /// <param name="bcc"></param>
/// <param name="url"></param> /// <param name="url"></param>
private void ExportToNewEmail(IDisposableCom<Application> outlookApplication, EmailFormat format, string tmpFile, string subject, string attachmentName, string to, string cc, string bcc, string url) private void ExportToNewEmail(IDisposableCom<Application> outlookApplication, EmailFormat format, string tmpFile, string subject, string attachmentName, string to,
string cc, string bcc, string url)
{ {
using var newItem = DisposableCom.Create((MailItem)outlookApplication.ComObject.CreateItem(OlItemType.olMailItem)); using var newItem = DisposableCom.Create((MailItem) outlookApplication.ComObject.CreateItem(OlItemType.olMailItem));
if (newItem == null) if (newItem == null)
{ {
return; return;
} }
var newMail = newItem.ComObject; var newMail = newItem.ComObject;
newMail.Subject = subject; newMail.Subject = subject;
if (!string.IsNullOrEmpty(to)) if (!string.IsNullOrEmpty(to))
{ {
newMail.To = to; newMail.To = to;
} }
if (!string.IsNullOrEmpty(cc)) if (!string.IsNullOrEmpty(cc))
{ {
newMail.CC = cc; newMail.CC = cc;
} }
if (!string.IsNullOrEmpty(bcc)) if (!string.IsNullOrEmpty(bcc))
{ {
newMail.BCC = bcc; newMail.BCC = bcc;
} }
newMail.BodyFormat = OlBodyFormat.olFormatHTML; newMail.BodyFormat = OlBodyFormat.olFormatHTML;
string bodyString = null; string bodyString = null;
// Read the default signature, if nothing found use empty email // Read the default signature, if nothing found use empty email
@ -408,6 +431,7 @@ namespace Greenshot.Plugin.Office.OfficeExport
{ {
LOG.Error("Problem reading signature!", e); LOG.Error("Problem reading signature!", e);
} }
switch (format) switch (format)
{ {
case EmailFormat.Text: case EmailFormat.Text:
@ -421,9 +445,11 @@ namespace Greenshot.Plugin.Office.OfficeExport
{ {
bodyString = ""; bodyString = "";
} }
newMail.Body = bodyString; newMail.Body = bodyString;
} }
} }
break; break;
default: default:
string contentId = Path.GetFileName(tmpFile); string contentId = Path.GetFileName(tmpFile);
@ -432,7 +458,7 @@ namespace Greenshot.Plugin.Office.OfficeExport
{ {
using var attachment = DisposableCom.Create(attachments.ComObject.Add(tmpFile, OlAttachmentType.olByValue, 0, attachmentName)); using var attachment = DisposableCom.Create(attachments.ComObject.Add(tmpFile, OlAttachmentType.olByValue, 0, attachmentName));
// add content ID to the attachment // add content ID to the attachment
if (_outlookVersion.Major >= (int)OfficeVersions.Office2007) if (_outlookVersion.Major >= (int) OfficeVersions.Office2007)
{ {
try try
{ {
@ -456,6 +482,7 @@ namespace Greenshot.Plugin.Office.OfficeExport
href = $"<A HREF=\"{url}\">"; href = $"<A HREF=\"{url}\">";
hrefEnd = "</A>"; hrefEnd = "</A>";
} }
string htmlImgEmbedded = $"<BR/>{href}<IMG border=0 hspace=0 alt=\"{attachmentName}\" align=baseline src=\"cid:{contentId}\">{hrefEnd}<BR/>"; string htmlImgEmbedded = $"<BR/>{href}<IMG border=0 hspace=0 alt=\"{attachmentName}\" align=baseline src=\"cid:{contentId}\">{hrefEnd}<BR/>";
string fallbackBody = $"<HTML><BODY>{htmlImgEmbedded}</BODY></HTML>"; string fallbackBody = $"<HTML><BODY>{htmlImgEmbedded}</BODY></HTML>";
if (bodyString == null) if (bodyString == null)
@ -482,13 +509,15 @@ namespace Greenshot.Plugin.Office.OfficeExport
bodyString = fallbackBody; bodyString = fallbackBody;
} }
} }
newMail.HTMLBody = bodyString; newMail.HTMLBody = bodyString;
break; break;
} }
// So not save, otherwise the email is always stored in Draft folder.. (newMail.Save();) // So not save, otherwise the email is always stored in Draft folder.. (newMail.Save();)
newMail.Display(false); newMail.Display(false);
using var inspector = DisposableCom.Create((_Inspector)newMail.GetInspector); using var inspector = DisposableCom.Create((_Inspector) newMail.GetInspector);
if (inspector != null) if (inspector != null)
{ {
try try
@ -528,12 +557,14 @@ namespace Greenshot.Plugin.Office.OfficeExport
exported = true; exported = true;
} }
} }
return exported; return exported;
} }
catch (Exception e) catch (Exception e)
{ {
LOG.Error("Error while creating an outlook mail item: ", e); LOG.Error("Error while creating an outlook mail item: ", e);
} }
return exported; return exported;
} }
@ -548,6 +579,7 @@ namespace Greenshot.Plugin.Office.OfficeExport
{ {
outlookApplication = DisposableCom.Create(new Application()); outlookApplication = DisposableCom.Create(new Application());
} }
InitializeVariables(outlookApplication); InitializeVariables(outlookApplication);
return outlookApplication; return outlookApplication;
} }
@ -568,10 +600,12 @@ namespace Greenshot.Plugin.Office.OfficeExport
// Ignore, probably no outlook running // Ignore, probably no outlook running
return null; return null;
} }
if ((outlookApplication != null) && (outlookApplication.ComObject != null)) if ((outlookApplication != null) && (outlookApplication.ComObject != null))
{ {
InitializeVariables(outlookApplication); InitializeVariables(outlookApplication);
} }
return outlookApplication; return outlookApplication;
} }
@ -587,7 +621,8 @@ namespace Greenshot.Plugin.Office.OfficeExport
{ {
return null; return null;
} }
string defaultProfile = (string)profilesKey.GetValue(DefaultProfileValue);
string defaultProfile = (string) profilesKey.GetValue(DefaultProfileValue);
LOG.DebugFormat("defaultProfile={0}", defaultProfile); LOG.DebugFormat("defaultProfile={0}", defaultProfile);
using var profileKey = profilesKey.OpenSubKey(defaultProfile + @"\" + AccountKey, false); using var profileKey = profilesKey.OpenSubKey(defaultProfile + @"\" + AccountKey, false);
if (profileKey != null) if (profileKey != null)
@ -599,19 +634,21 @@ namespace Greenshot.Plugin.Office.OfficeExport
using var numberKey = profileKey.OpenSubKey(number, false); using var numberKey = profileKey.OpenSubKey(number, false);
if (numberKey != null) if (numberKey != null)
{ {
byte[] val = (byte[])numberKey.GetValue(NewSignatureValue); byte[] val = (byte[]) numberKey.GetValue(NewSignatureValue);
if (val == null) if (val == null)
{ {
continue; continue;
} }
string signatureName = ""; string signatureName = "";
foreach (byte b in val) foreach (byte b in val)
{ {
if (b != 0) if (b != 0)
{ {
signatureName += (char)b; signatureName += (char) b;
} }
} }
LOG.DebugFormat("Found email signature: {0}", signatureName); LOG.DebugFormat("Found email signature: {0}", signatureName);
var extension = format switch var extension = format switch
{ {
@ -628,6 +665,7 @@ namespace Greenshot.Plugin.Office.OfficeExport
} }
} }
} }
return null; return null;
} }
@ -642,13 +680,15 @@ namespace Greenshot.Plugin.Office.OfficeExport
{ {
return; return;
} }
if (!Version.TryParse(outlookApplication.ComObject.Version, out _outlookVersion)) if (!Version.TryParse(outlookApplication.ComObject.Version, out _outlookVersion))
{ {
LOG.Warn("Assuming outlook version 1997."); LOG.Warn("Assuming outlook version 1997.");
_outlookVersion = new Version((int)OfficeVersions.Office97, 0, 0, 0); _outlookVersion = new Version((int) OfficeVersions.Office97, 0, 0, 0);
} }
// Preventing retrieval of currentUser if Outlook is older than 2007 // Preventing retrieval of currentUser if Outlook is older than 2007
if (_outlookVersion.Major >= (int)OfficeVersions.Office2007) if (_outlookVersion.Major >= (int) OfficeVersions.Office2007)
{ {
try try
{ {
@ -657,6 +697,7 @@ namespace Greenshot.Plugin.Office.OfficeExport
using var currentUser = DisposableCom.Create(mapiNamespace.ComObject.CurrentUser); using var currentUser = DisposableCom.Create(mapiNamespace.ComObject.CurrentUser);
_currentUser = currentUser.ComObject.Name; _currentUser = currentUser.ComObject.Name;
} }
LOG.InfoFormat("Current user: {0}", _currentUser); LOG.InfoFormat("Current user: {0}", _currentUser);
} }
catch (Exception exNs) catch (Exception exNs)
@ -682,7 +723,7 @@ namespace Greenshot.Plugin.Office.OfficeExport
} }
// The activeexplorer inline response only works with >= 2013, Microsoft Outlook 15.0 Object Library // The activeexplorer inline response only works with >= 2013, Microsoft Outlook 15.0 Object Library
if (_outlookVersion.Major >= (int)OfficeVersions.Office2013) if (_outlookVersion.Major >= (int) OfficeVersions.Office2013)
{ {
// Check inline "panel" for Outlook 2013 // Check inline "panel" for Outlook 2013
using var activeExplorer = DisposableCom.Create(outlookApplication.ComObject.ActiveExplorer()); using var activeExplorer = DisposableCom.Create(outlookApplication.ComObject.ActiveExplorer());
@ -701,15 +742,17 @@ namespace Greenshot.Plugin.Office.OfficeExport
{ {
inspectorCaptions.Add(caption, mailItem.Class); inspectorCaptions.Add(caption, mailItem.Class);
} }
break; break;
case AppointmentItem appointmentItem: case AppointmentItem appointmentItem:
if ((_outlookVersion.Major >= (int)OfficeVersions.Office2010) && _officeConfiguration.OutlookAllowExportInMeetings) if ((_outlookVersion.Major >= (int) OfficeVersions.Office2010) && _officeConfiguration.OutlookAllowExportInMeetings)
{ {
if (!string.IsNullOrEmpty(appointmentItem.Organizer) && appointmentItem.Organizer.Equals(_currentUser)) if (!string.IsNullOrEmpty(appointmentItem.Organizer) && appointmentItem.Organizer.Equals(_currentUser))
{ {
inspectorCaptions.Add(caption, appointmentItem.Class); inspectorCaptions.Add(caption, appointmentItem.Class);
} }
} }
break; break;
} }
} }
@ -740,10 +783,11 @@ namespace Greenshot.Plugin.Office.OfficeExport
{ {
continue; continue;
} }
inspectorCaptions.Add(caption, mailItem.Class); inspectorCaptions.Add(caption, mailItem.Class);
break; break;
case AppointmentItem appointmentItem: case AppointmentItem appointmentItem:
if ((_outlookVersion.Major >= (int)OfficeVersions.Office2010) && _officeConfiguration.OutlookAllowExportInMeetings) if ((_outlookVersion.Major >= (int) OfficeVersions.Office2010) && _officeConfiguration.OutlookAllowExportInMeetings)
{ {
if (!string.IsNullOrEmpty(appointmentItem.Organizer) && !appointmentItem.Organizer.Equals(_currentUser)) if (!string.IsNullOrEmpty(appointmentItem.Organizer) && !appointmentItem.Organizer.Equals(_currentUser))
{ {
@ -756,6 +800,7 @@ namespace Greenshot.Plugin.Office.OfficeExport
// skip, can't export to olAppointment // skip, can't export to olAppointment
continue; continue;
} }
inspectorCaptions.Add(caption, appointmentItem.Class); inspectorCaptions.Add(caption, appointmentItem.Class);
break; break;
default: default:
@ -769,6 +814,7 @@ namespace Greenshot.Plugin.Office.OfficeExport
{ {
LOG.Warn("Problem retrieving word destinations, ignoring: ", ex); LOG.Warn("Problem retrieving word destinations, ignoring: ", ex);
} }
return inspectorCaptions; return inspectorCaptions;
} }
} }

View file

@ -60,6 +60,7 @@ namespace Greenshot.Plugin.Office.OfficeExport
left = pageSetup.ComObject.SlideWidth / 2 - imageSize.Width / 2f; left = pageSetup.ComObject.SlideWidth / 2 - imageSize.Width / 2f;
top = pageSetup.ComObject.SlideHeight / 2 - imageSize.Height / 2f; top = pageSetup.ComObject.SlideHeight / 2 - imageSize.Height / 2f;
} }
float width = imageSize.Width; float width = imageSize.Width;
float height = imageSize.Height; float height = imageSize.Height;
IDisposableCom<Shape> shapeForCaption = null; IDisposableCom<Shape> shapeForCaption = null;
@ -86,6 +87,7 @@ namespace Greenshot.Plugin.Office.OfficeExport
{ {
shapeForLocation.ComObject.Left = left; shapeForLocation.ComObject.Left = left;
} }
shapeForLocation.ComObject.Width = imageSize.Width; shapeForLocation.ComObject.Width = imageSize.Width;
if (height > shapeForLocation.ComObject.Height) if (height > shapeForLocation.ComObject.Height)
@ -98,6 +100,7 @@ namespace Greenshot.Plugin.Office.OfficeExport
{ {
top = shapeForLocation.ComObject.Top + shapeForLocation.ComObject.Height / 2 - imageSize.Height / 2f; top = shapeForLocation.ComObject.Top + shapeForLocation.ComObject.Height / 2 - imageSize.Height / 2f;
} }
shapeForLocation.ComObject.Height = imageSize.Height; shapeForLocation.ComObject.Height = imageSize.Height;
} }
catch (Exception e) catch (Exception e)
@ -106,6 +109,7 @@ namespace Greenshot.Plugin.Office.OfficeExport
using var slides = DisposableCom.Create(presentation.ComObject.Slides); using var slides = DisposableCom.Create(presentation.ComObject.Slides);
slide = DisposableCom.Create(slides.ComObject.Add(slides.ComObject.Count + 1, PpSlideLayout.ppLayoutBlank)); slide = DisposableCom.Create(slides.ComObject.Add(slides.ComObject.Count + 1, PpSlideLayout.ppLayoutBlank));
} }
using (var shapes = DisposableCom.Create(slide.ComObject.Shapes)) using (var shapes = DisposableCom.Create(slide.ComObject.Shapes))
{ {
using var shape = DisposableCom.Create(shapes.ComObject.AddPicture(tmpFile, MsoTriState.msoFalse, MsoTriState.msoTrue, 0, 0, width, height)); using var shape = DisposableCom.Create(shapes.ComObject.AddPicture(tmpFile, MsoTriState.msoFalse, MsoTriState.msoTrue, 0, 0, width, height));
@ -117,20 +121,24 @@ namespace Greenshot.Plugin.Office.OfficeExport
{ {
shape.ComObject.LockAspectRatio = MsoTriState.msoFalse; shape.ComObject.LockAspectRatio = MsoTriState.msoFalse;
} }
shape.ComObject.ScaleHeight(1, MsoTriState.msoTrue, MsoScaleFrom.msoScaleFromMiddle); shape.ComObject.ScaleHeight(1, MsoTriState.msoTrue, MsoScaleFrom.msoScaleFromMiddle);
shape.ComObject.ScaleWidth(1, MsoTriState.msoTrue, MsoScaleFrom.msoScaleFromMiddle); shape.ComObject.ScaleWidth(1, MsoTriState.msoTrue, MsoScaleFrom.msoScaleFromMiddle);
if (hasScaledWidth) if (hasScaledWidth)
{ {
shape.ComObject.Width = width; shape.ComObject.Width = width;
} }
if (hasScaledHeight) if (hasScaledHeight)
{ {
shape.ComObject.Height = height; shape.ComObject.Height = height;
} }
shape.ComObject.Left = left; shape.ComObject.Left = left;
shape.ComObject.Top = top; shape.ComObject.Top = top;
shape.ComObject.AlternativeText = title; shape.ComObject.AlternativeText = title;
} }
if (shapeForCaption != null) if (shapeForCaption != null)
{ {
try try
@ -148,6 +156,7 @@ namespace Greenshot.Plugin.Office.OfficeExport
LOG.Warn("Problem setting the title to a text-range", ex); LOG.Warn("Problem setting the title to a text-range", ex);
} }
} }
// Activate/Goto the slide // Activate/Goto the slide
try try
{ {
@ -194,10 +203,12 @@ namespace Greenshot.Plugin.Office.OfficeExport
{ {
continue; continue;
} }
if (!presentation.ComObject.Name.StartsWith(presentationName)) if (!presentation.ComObject.Name.StartsWith(presentationName))
{ {
continue; continue;
} }
try try
{ {
AddPictureToPresentation(presentation, tmpFile, imageSize, title); AddPictureToPresentation(presentation, tmpFile, imageSize, title);
@ -209,6 +220,7 @@ namespace Greenshot.Plugin.Office.OfficeExport
} }
} }
} }
return false; return false;
} }
@ -223,6 +235,7 @@ namespace Greenshot.Plugin.Office.OfficeExport
{ {
powerPointApplication = DisposableCom.Create(new Application()); powerPointApplication = DisposableCom.Create(new Application());
} }
InitializeVariables(powerPointApplication); InitializeVariables(powerPointApplication);
return powerPointApplication; return powerPointApplication;
} }
@ -243,10 +256,12 @@ namespace Greenshot.Plugin.Office.OfficeExport
// Ignore, probably no PowerPoint running // Ignore, probably no PowerPoint running
return null; return null;
} }
if (powerPointApplication?.ComObject != null) if (powerPointApplication?.ComObject != null)
{ {
InitializeVariables(powerPointApplication); InitializeVariables(powerPointApplication);
} }
return powerPointApplication; return powerPointApplication;
} }
@ -271,10 +286,12 @@ namespace Greenshot.Plugin.Office.OfficeExport
{ {
continue; continue;
} }
if (presentation.ComObject.ReadOnly == MsoTriState.msoTrue) if (presentation.ComObject.ReadOnly == MsoTriState.msoTrue)
{ {
continue; continue;
} }
if (IsAfter2003()) if (IsAfter2003())
{ {
if (presentation.ComObject.Final) if (presentation.ComObject.Final)
@ -282,6 +299,7 @@ namespace Greenshot.Plugin.Office.OfficeExport
continue; continue;
} }
} }
yield return presentation.ComObject.Name; yield return presentation.ComObject.Name;
} }
} }
@ -296,10 +314,11 @@ namespace Greenshot.Plugin.Office.OfficeExport
{ {
return; return;
} }
if (!Version.TryParse(powerpointApplication.ComObject.Version, out _powerpointVersion)) if (!Version.TryParse(powerpointApplication.ComObject.Version, out _powerpointVersion))
{ {
LOG.Warn("Assuming Powerpoint version 1997."); LOG.Warn("Assuming Powerpoint version 1997.");
_powerpointVersion = new Version((int)OfficeVersions.Office97, 0, 0, 0); _powerpointVersion = new Version((int) OfficeVersions.Office97, 0, 0, 0);
} }
} }
@ -332,13 +351,13 @@ namespace Greenshot.Plugin.Office.OfficeExport
} }
} }
} }
return isPictureAdded; return isPictureAdded;
} }
private bool IsAfter2003() private bool IsAfter2003()
{ {
return _powerpointVersion.Major > (int)OfficeVersions.Office2003; return _powerpointVersion.Major > (int) OfficeVersions.Office2003;
} }
} }
} }

View file

@ -53,6 +53,7 @@ namespace Greenshot.Plugin.Office.OfficeExport
{ {
shape.ComObject.LockAspectRatio = MsoTriState.msoTrue; shape.ComObject.LockAspectRatio = MsoTriState.msoTrue;
} }
selection.ComObject.InsertAfter("\r\n"); selection.ComObject.InsertAfter("\r\n");
selection.ComObject.MoveDown(WdUnits.wdLine, 1, Type.Missing); selection.ComObject.MoveDown(WdUnits.wdLine, 1, Type.Missing);
return shape; return shape;
@ -69,6 +70,7 @@ namespace Greenshot.Plugin.Office.OfficeExport
{ {
wordApplication = DisposableCom.Create(new Application()); wordApplication = DisposableCom.Create(new Application());
} }
InitializeVariables(wordApplication); InitializeVariables(wordApplication);
return wordApplication; return wordApplication;
} }
@ -89,10 +91,12 @@ namespace Greenshot.Plugin.Office.OfficeExport
// Ignore, probably no word running // Ignore, probably no word running
return null; return null;
} }
if ((wordApplication != null) && (wordApplication.ComObject != null)) if ((wordApplication != null) && (wordApplication.ComObject != null))
{ {
InitializeVariables(wordApplication); InitializeVariables(wordApplication);
} }
return wordApplication; return wordApplication;
} }
@ -116,6 +120,7 @@ namespace Greenshot.Plugin.Office.OfficeExport
{ {
continue; continue;
} }
if (IsAfter2003()) if (IsAfter2003())
{ {
if (document.ComObject.Final) if (document.ComObject.Final)
@ -139,10 +144,11 @@ namespace Greenshot.Plugin.Office.OfficeExport
{ {
return; return;
} }
if (!Version.TryParse(wordApplication.ComObject.Version, out _wordVersion)) if (!Version.TryParse(wordApplication.ComObject.Version, out _wordVersion))
{ {
LOG.Warn("Assuming Word version 1997."); LOG.Warn("Assuming Word version 1997.");
_wordVersion = new Version((int)OfficeVersions.Office97, 0, 0, 0); _wordVersion = new Version((int) OfficeVersions.Office97, 0, 0, 0);
} }
} }
@ -164,7 +170,7 @@ namespace Greenshot.Plugin.Office.OfficeExport
using var documents = DisposableCom.Create(wordApplication.ComObject.Documents); using var documents = DisposableCom.Create(wordApplication.ComObject.Documents);
for (int i = 1; i <= documents.ComObject.Count; i++) for (int i = 1; i <= documents.ComObject.Count; i++)
{ {
using var wordDocument = DisposableCom.Create((_Document)documents.ComObject[i]); using var wordDocument = DisposableCom.Create((_Document) documents.ComObject[i]);
using var activeWindow = DisposableCom.Create(wordDocument.ComObject.ActiveWindow); using var activeWindow = DisposableCom.Create(wordDocument.ComObject.ActiveWindow);
if (activeWindow.ComObject.Caption.StartsWith(wordCaption)) if (activeWindow.ComObject.Caption.StartsWith(wordCaption))
{ {
@ -172,6 +178,7 @@ namespace Greenshot.Plugin.Office.OfficeExport
} }
} }
} }
return false; return false;
} }
@ -184,7 +191,8 @@ namespace Greenshot.Plugin.Office.OfficeExport
/// <param name="address">string</param> /// <param name="address">string</param>
/// <param name="tooltip">string with the tooltip of the image</param> /// <param name="tooltip">string with the tooltip of the image</param>
/// <returns>bool</returns> /// <returns>bool</returns>
internal bool InsertIntoExistingDocument(IDisposableCom<Application> wordApplication, IDisposableCom<_Document> wordDocument, string tmpFile, string address, string tooltip) internal bool InsertIntoExistingDocument(IDisposableCom<Application> wordApplication, IDisposableCom<_Document> wordDocument, string tmpFile, string address,
string tooltip)
{ {
// Bug #1517: image will be inserted into that document, where the focus was last. It will not inserted into the chosen one. // Bug #1517: image will be inserted into that document, where the focus was last. It will not inserted into the chosen one.
// Solution: Make sure the selected document is active, otherwise the insert will be made in a different document! // Solution: Make sure the selected document is active, otherwise the insert will be made in a different document!
@ -204,6 +212,7 @@ namespace Greenshot.Plugin.Office.OfficeExport
LOG.InfoFormat("No selection to insert {0} into found.", tmpFile); LOG.InfoFormat("No selection to insert {0} into found.", tmpFile);
return false; return false;
} }
// Add Picture // Add Picture
using (var shape = AddPictureToSelection(selection, tmpFile)) using (var shape = AddPictureToSelection(selection, tmpFile))
{ {
@ -214,6 +223,7 @@ namespace Greenshot.Plugin.Office.OfficeExport
{ {
screentip = tooltip; screentip = tooltip;
} }
try try
{ {
using var hyperlinks = DisposableCom.Create(wordDocument.ComObject.Hyperlinks); using var hyperlinks = DisposableCom.Create(wordDocument.ComObject.Hyperlinks);
@ -225,6 +235,7 @@ namespace Greenshot.Plugin.Office.OfficeExport
} }
} }
} }
try try
{ {
// When called for Outlook, the follow error is created: This object model command is not available in e-mail // When called for Outlook, the follow error is created: This object model command is not available in e-mail
@ -245,6 +256,7 @@ namespace Greenshot.Plugin.Office.OfficeExport
LOG.WarnFormat("Couldn't set zoom to 100, error: {0}", e.Message); LOG.WarnFormat("Couldn't set zoom to 100, error: {0}", e.Message);
} }
} }
try try
{ {
wordApplication.ComObject.Activate(); wordApplication.ComObject.Activate();
@ -254,6 +266,7 @@ namespace Greenshot.Plugin.Office.OfficeExport
{ {
LOG.Warn("Error activating word application", ex); LOG.Warn("Error activating word application", ex);
} }
try try
{ {
wordDocument.ComObject.Activate(); wordDocument.ComObject.Activate();
@ -263,6 +276,7 @@ namespace Greenshot.Plugin.Office.OfficeExport
{ {
LOG.Warn("Error activating word document", ex); LOG.Warn("Error activating word document", ex);
} }
return true; return true;
} }
@ -279,6 +293,7 @@ namespace Greenshot.Plugin.Office.OfficeExport
{ {
return; return;
} }
wordApplication.ComObject.Visible = true; wordApplication.ComObject.Visible = true;
wordApplication.ComObject.Activate(); wordApplication.ComObject.Activate();
// Create new Document // Create new Document
@ -299,6 +314,7 @@ namespace Greenshot.Plugin.Office.OfficeExport
{ {
screentip = tooltip; screentip = tooltip;
} }
try try
{ {
using var hyperlinks = DisposableCom.Create(wordDocument.ComObject.Hyperlinks); using var hyperlinks = DisposableCom.Create(wordDocument.ComObject.Hyperlinks);
@ -313,6 +329,7 @@ namespace Greenshot.Plugin.Office.OfficeExport
} }
} }
} }
try try
{ {
wordDocument.ComObject.Activate(); wordDocument.ComObject.Activate();
@ -322,6 +339,7 @@ namespace Greenshot.Plugin.Office.OfficeExport
{ {
LOG.Warn("Error activating word document", ex); LOG.Warn("Error activating word document", ex);
} }
try try
{ {
using var activeWindow = DisposableCom.Create(wordDocument.ComObject.ActiveWindow); using var activeWindow = DisposableCom.Create(wordDocument.ComObject.ActiveWindow);
@ -340,7 +358,7 @@ namespace Greenshot.Plugin.Office.OfficeExport
/// <returns></returns> /// <returns></returns>
private bool IsAfter2003() private bool IsAfter2003()
{ {
return _wordVersion.Major > (int)OfficeVersions.Office2003; return _wordVersion.Major > (int) OfficeVersions.Office2003;
} }
} }
} }

View file

@ -19,18 +19,19 @@
namespace Greenshot.Plugin.Office.OfficeInterop namespace Greenshot.Plugin.Office.OfficeInterop
{ {
/// <summary> /// <summary>
/// Specifies which EmailFormat the email needs to use /// Specifies which EmailFormat the email needs to use
/// </summary> /// </summary>
public enum EmailFormat public enum EmailFormat
{ {
/// <summary> /// <summary>
/// Use the plain text format /// Use the plain text format
/// </summary> /// </summary>
Text, Text,
/// <summary> /// <summary>
/// Use HTML format /// Use HTML format
/// </summary> /// </summary>
Html Html
} }
} }

View file

@ -19,38 +19,44 @@
namespace Greenshot.Plugin.Office.OfficeInterop namespace Greenshot.Plugin.Office.OfficeInterop
{ {
/// <summary> /// <summary>
/// A mapping between the version and a usable name /// A mapping between the version and a usable name
/// </summary> /// </summary>
public enum OfficeVersions public enum OfficeVersions
{ {
/// <summary> /// <summary>
/// Office 97 /// Office 97
/// </summary> /// </summary>
Office97 = 8, Office97 = 8,
/// <summary> /// <summary>
/// Office 2003 /// Office 2003
/// </summary> /// </summary>
Office2003 = 11, Office2003 = 11,
/// <summary> /// <summary>
/// Office 2007 /// Office 2007
/// </summary> /// </summary>
Office2007 = 12, Office2007 = 12,
/// <summary> /// <summary>
/// Office 2010 /// Office 2010
/// </summary> /// </summary>
Office2010 = 14, Office2010 = 14,
/// <summary> /// <summary>
/// Office 2013 /// Office 2013
/// </summary> /// </summary>
Office2013 = 15, Office2013 = 15,
/// <summary> /// <summary>
/// Office 2016 /// Office 2016
/// </summary> /// </summary>
Office2016 = 16, Office2016 = 16,
/// <summary> /// <summary>
/// Office 2019 /// Office 2019
/// </summary> /// </summary>
Office2019 = 17 Office2019 = 17
} }
} }

View file

@ -26,91 +26,123 @@ using GreenshotPlugin.Core;
using GreenshotPlugin.Interfaces; using GreenshotPlugin.Interfaces;
using GreenshotPlugin.Interfaces.Plugin; using GreenshotPlugin.Interfaces.Plugin;
namespace Greenshot.Plugin.Office { namespace Greenshot.Plugin.Office
/// <summary> {
/// This is the OfficePlugin base code /// <summary>
/// </summary> /// This is the OfficePlugin base code
/// </summary>
[Plugin("Office", false)] [Plugin("Office", false)]
public class OfficePlugin : IGreenshotPlugin public class OfficePlugin : IGreenshotPlugin
{ {
private static readonly log4net.ILog LOG = log4net.LogManager.GetLogger(typeof(OfficePlugin)); private static readonly log4net.ILog LOG = log4net.LogManager.GetLogger(typeof(OfficePlugin));
public void Dispose() { public void Dispose()
Dispose(true); {
GC.SuppressFinalize(this); Dispose(true);
} GC.SuppressFinalize(this);
}
protected void Dispose(bool disposing) { protected void Dispose(bool disposing)
// Do nothing {
} // Do nothing
}
private IEnumerable<IDestination> Destinations() { private IEnumerable<IDestination> Destinations()
IDestination destination; {
try { IDestination destination;
destination = new ExcelDestination(); try
} catch { {
destination = null; destination = new ExcelDestination();
} }
if (destination != null) { catch
yield return destination; {
} destination = null;
}
try { if (destination != null)
destination = new PowerpointDestination(); {
} catch { yield return destination;
destination = null; }
}
if (destination != null) {
yield return destination;
}
try { try
destination = new WordDestination(); {
} catch { destination = new PowerpointDestination();
destination = null; }
} catch
if (destination != null) { {
yield return destination; destination = null;
} }
try { if (destination != null)
destination = new OutlookDestination(); {
} catch { yield return destination;
destination = null; }
}
if (destination != null) {
yield return destination;
}
try { try
destination = new OneNoteDestination(); {
} catch { destination = new WordDestination();
destination = null; }
} catch
if (destination != null) { {
yield return destination; destination = null;
} }
}
if (destination != null)
{
yield return destination;
}
try
{
destination = new OutlookDestination();
}
catch
{
destination = null;
}
if (destination != null)
{
yield return destination;
}
try
{
destination = new OneNoteDestination();
}
catch
{
destination = null;
}
if (destination != null)
{
yield return destination;
}
}
/// <summary> /// <summary>
/// Implementation of the IGreenshotPlugin.Initialize /// Implementation of the IGreenshotPlugin.Initialize
/// </summary> /// </summary>
/// <returns>true if plugin is initialized, false if not (doesn't show)</returns> /// <returns>true if plugin is initialized, false if not (doesn't show)</returns>
public bool Initialize() { public bool Initialize()
{
SimpleServiceProvider.Current.AddService(Destinations()); SimpleServiceProvider.Current.AddService(Destinations());
return true; return true;
} }
public void Shutdown() { public void Shutdown()
LOG.Debug("Office Plugin shutdown."); {
} LOG.Debug("Office Plugin shutdown.");
}
/// <summary> /// <summary>
/// Implementation of the IPlugin.Configure /// Implementation of the IPlugin.Configure
/// </summary> /// </summary>
public void Configure() { public void Configure()
{
throw new NotImplementedException(); throw new NotImplementedException();
} }
} }
} }

View file

@ -19,10 +19,12 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>. * along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
namespace Greenshot.Plugin.Photobucket.Forms { namespace Greenshot.Plugin.Photobucket.Forms
/// <summary> {
/// This class is needed for design-time resolving of the language files /// <summary>
/// </summary> /// This class is needed for design-time resolving of the language files
public class PhotobucketForm : GreenshotPlugin.Controls.GreenshotForm { /// </summary>
} public class PhotobucketForm : GreenshotPlugin.Controls.GreenshotForm
{
}
} }

View file

@ -19,19 +19,21 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>. * along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
namespace Greenshot.Plugin.Photobucket.Forms { namespace Greenshot.Plugin.Photobucket.Forms
/// <summary> {
/// Description of PasswordRequestForm. /// <summary>
/// </summary> /// Description of PasswordRequestForm.
public partial class SettingsForm : PhotobucketForm { /// </summary>
public SettingsForm() public partial class SettingsForm : PhotobucketForm
{ {
// public SettingsForm()
// The InitializeComponent() call is required for Windows Forms designer support. {
// //
InitializeComponent(); // The InitializeComponent() call is required for Windows Forms designer support.
AcceptButton = buttonOK; //
CancelButton = buttonCancel; InitializeComponent();
} AcceptButton = buttonOK;
} CancelButton = buttonCancel;
}
}
} }

Some files were not shown because too many files have changed in this diff Show more