Merge branch 'ScottIsAFool-Blogger-Categories'
This commit is contained in:
commit
b293feecc7
|
@ -23,9 +23,9 @@ using Google.Apis.Util;
|
|||
using System.Globalization;
|
||||
using System.Diagnostics;
|
||||
using Google.Apis.Blogger.v3.Data;
|
||||
using System.Net.Http.Headers;
|
||||
using OpenLiveWriter.Controls;
|
||||
using System.Windows.Forms;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace OpenLiveWriter.BlogClient.Clients
|
||||
{
|
||||
|
@ -88,7 +88,7 @@ namespace OpenLiveWriter.BlogClient.Clients
|
|||
Permalink = post.Url,
|
||||
Contents = post.Content,
|
||||
DatePublished = post.Published.Value,
|
||||
Keywords = string.Join(new string(LabelDelimiter,1), post.Labels)
|
||||
Categories = post.Labels?.Select(x => new BlogPostCategory(x)).ToArray() ?? new BlogPostCategory[0]
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -106,10 +106,12 @@ namespace OpenLiveWriter.BlogClient.Clients
|
|||
|
||||
private static Post ConvertToGoogleBloggerPost(BlogPost post)
|
||||
{
|
||||
var labels = post.Categories?.Select(x => x.Name).ToList();
|
||||
labels?.AddRange(post.NewCategories?.Select(x => x.Name) ?? new List<string>());
|
||||
return new Post()
|
||||
{
|
||||
Content = post.Contents,
|
||||
Labels = post.Keywords?.Split(new char[] { LabelDelimiter }, StringSplitOptions.RemoveEmptyEntries).Select(k => k.Trim()).ToList(),
|
||||
Labels = labels ?? new List<string>(),
|
||||
// TODO:OLW - DatePublishedOverride didn't work quite right. Either the date published override was off by several hours,
|
||||
// needs to be normalized to UTC or the Blogger website thinks I'm in the wrong time zone.
|
||||
Published = post.HasDatePublishedOverride ? post?.DatePublishedOverride : null,
|
||||
|
@ -123,6 +125,8 @@ namespace OpenLiveWriter.BlogClient.Clients
|
|||
return new PageInfo(page.Id, page.Title, page.Published.GetValueOrDefault(DateTime.Now), string.Empty);
|
||||
}
|
||||
|
||||
private const int MaxRetries = 5;
|
||||
|
||||
private const string ENTRY_CONTENT_TYPE = "application/atom+xml;type=entry";
|
||||
private const string XHTML_NS = "http://www.w3.org/1999/xhtml";
|
||||
private const string FEATURES_NS = "http://purl.org/atompub/features/1.0";
|
||||
|
@ -140,15 +144,15 @@ namespace OpenLiveWriter.BlogClient.Clients
|
|||
{
|
||||
// configure client options
|
||||
BlogClientOptions clientOptions = new BlogClientOptions();
|
||||
clientOptions.SupportsCategories = false;
|
||||
clientOptions.SupportsMultipleCategories = false;
|
||||
clientOptions.SupportsNewCategories = false;
|
||||
clientOptions.SupportsCategories = true;
|
||||
clientOptions.SupportsMultipleCategories = true;
|
||||
clientOptions.SupportsNewCategories = true;
|
||||
clientOptions.SupportsCustomDate = true;
|
||||
clientOptions.SupportsExcerpt = false;
|
||||
clientOptions.SupportsSlug = false;
|
||||
clientOptions.SupportsFileUpload = true;
|
||||
clientOptions.SupportsKeywords = true;
|
||||
clientOptions.SupportsGetKeywords = true;
|
||||
clientOptions.SupportsKeywords = false;
|
||||
clientOptions.SupportsGetKeywords = false;
|
||||
clientOptions.SupportsPages = true;
|
||||
clientOptions.SupportsExtendedEntries = true;
|
||||
_clientOptions = clientOptions;
|
||||
|
@ -197,6 +201,7 @@ namespace OpenLiveWriter.BlogClient.Clients
|
|||
var transientCredentials = Credentials.TransientCredentials as TransientCredentials ??
|
||||
new TransientCredentials(Credentials.Username, Credentials.Password, null);
|
||||
VerifyAndRefreshCredentials(transientCredentials);
|
||||
Credentials.TransientCredentials = transientCredentials;
|
||||
return transientCredentials;
|
||||
}
|
||||
|
||||
|
@ -266,6 +271,14 @@ namespace OpenLiveWriter.BlogClient.Clients
|
|||
tc.Token = userCredential;
|
||||
}
|
||||
|
||||
private void RefreshAccessToken(TransientCredentials transientCredentials)
|
||||
{
|
||||
// Using the BloggerService automatically refreshes the access token, but we call the Picasa endpoint
|
||||
// directly and therefore need to force refresh the access token on occasion.
|
||||
var userCredential = transientCredentials.Token as UserCredential;
|
||||
userCredential?.RefreshTokenAsync(CancellationToken.None).Wait();
|
||||
}
|
||||
|
||||
private HttpRequestFilter CreateAuthorizationFilter()
|
||||
{
|
||||
var transientCredentials = Login();
|
||||
|
@ -289,13 +302,33 @@ namespace OpenLiveWriter.BlogClient.Clients
|
|||
public BlogInfo[] GetUsersBlogs()
|
||||
{
|
||||
var blogList = GetService().Blogs.ListByUser("self").Execute();
|
||||
return blogList.Items.Select(b => new BlogInfo(b.Id, b.Name, b.Url)).ToArray();
|
||||
return blogList.Items?.Select(b => new BlogInfo(b.Id, b.Name, b.Url)).ToArray() ?? new BlogInfo[0];
|
||||
}
|
||||
|
||||
private const string CategoriesEndPoint = "/feeds/posts/summary?alt=json&max-results=0";
|
||||
public BlogPostCategory[] GetCategories(string blogId)
|
||||
{
|
||||
// Google Blogger does not support categories
|
||||
return new BlogPostCategory[] { };
|
||||
var categories = new BlogPostCategory[0];
|
||||
var blog = GetService().Blogs.Get(blogId).Execute();
|
||||
|
||||
if (blog != null)
|
||||
{
|
||||
var categoriesUrl = string.Concat(blog.Url, CategoriesEndPoint);
|
||||
|
||||
var response = SendAuthenticatedHttpRequest(categoriesUrl, 30, CreateAuthorizationFilter());
|
||||
if (response != null)
|
||||
{
|
||||
using (var reader = new StreamReader(response.GetResponseStream()))
|
||||
{
|
||||
var json = reader.ReadToEnd();
|
||||
var item = JsonConvert.DeserializeObject<CategoryResponse>(json);
|
||||
var cats = item?.Feed?.CategoryArray.Select(x => new BlogPostCategory(x.Term));
|
||||
categories = cats?.ToArray() ?? new BlogPostCategory[0];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return categories;
|
||||
}
|
||||
|
||||
public BlogPostKeyword[] GetKeywords(string blogId)
|
||||
|
@ -317,7 +350,7 @@ namespace OpenLiveWriter.BlogClient.Clients
|
|||
recentPostsRequest.Status = PostsResource.ListRequest.StatusEnum.Live;
|
||||
|
||||
var recentPosts = recentPostsRequest.Execute();
|
||||
return recentPosts.Items.Select(p => ConvertToBlogPost(p)).ToArray();
|
||||
return recentPosts.Items?.Select(ConvertToBlogPost).ToArray() ?? new BlogPost[0];
|
||||
}
|
||||
|
||||
public string NewPost(string blogId, BlogPost post, INewCategoryContext newCategoryContext, bool publish, out string etag, out XmlDocument remotePost)
|
||||
|
@ -383,7 +416,7 @@ namespace OpenLiveWriter.BlogClient.Clients
|
|||
var getPagesRequest = GetService().Pages.List(blogId);
|
||||
|
||||
var pageList = getPagesRequest.Execute();
|
||||
return pageList.Items.Select(p => ConvertToPageInfo(p)).ToArray();
|
||||
return pageList.Items?.Select(ConvertToPageInfo).ToArray() ?? new PageInfo[0];
|
||||
}
|
||||
|
||||
public BlogPost[] GetPages(string blogId, int maxPages)
|
||||
|
@ -392,7 +425,7 @@ namespace OpenLiveWriter.BlogClient.Clients
|
|||
getPagesRequest.MaxResults = maxPages;
|
||||
|
||||
var pageList = getPagesRequest.Execute();
|
||||
return pageList.Items.Select(p => ConvertToBlogPost(p)).ToArray();
|
||||
return pageList.Items?.Select(ConvertToBlogPost).ToArray() ?? new BlogPost[0];
|
||||
}
|
||||
|
||||
public string NewPage(string blogId, BlogPost page, bool publish, out string etag, out XmlDocument remotePost)
|
||||
|
@ -672,18 +705,44 @@ namespace OpenLiveWriter.BlogClient.Clients
|
|||
|
||||
private void PostNewImage(string albumName, string filename, out string srcUrl, out string editUri)
|
||||
{
|
||||
Login();
|
||||
for (int retry = 0; retry < MaxRetries; retry++)
|
||||
{
|
||||
var transientCredentials = Login();
|
||||
try
|
||||
{
|
||||
string albumUrl = GetBlogImagesAlbum(albumName);
|
||||
HttpWebResponse response = RedirectHelper.GetResponse(albumUrl, new RedirectHelper.RequestFactory(new UploadFileRequestFactory(this, filename, "POST").Create));
|
||||
using (Stream s = response.GetResponseStream())
|
||||
{
|
||||
ParseMediaEntry(s, out srcUrl, out editUri);
|
||||
return;
|
||||
}
|
||||
}
|
||||
catch (WebException we)
|
||||
{
|
||||
if (retry < MaxRetries - 1 &&
|
||||
we.Response as HttpWebResponse != null &&
|
||||
((HttpWebResponse)we.Response).StatusCode == HttpStatusCode.Forbidden)
|
||||
{
|
||||
// HTTP 403 Forbidden means our OAuth access token is not valid.
|
||||
RefreshAccessToken(transientCredentials);
|
||||
}
|
||||
else
|
||||
{
|
||||
throw;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
string albumUrl = GetBlogImagesAlbum(albumName);
|
||||
HttpWebResponse response = RedirectHelper.GetResponse(albumUrl, new RedirectHelper.RequestFactory(new UploadFileRequestFactory(this, filename, "POST").Create));
|
||||
using (Stream s = response.GetResponseStream())
|
||||
ParseMediaEntry(s, out srcUrl, out editUri);
|
||||
Trace.Fail("Should never get here");
|
||||
throw new ApplicationException("Should never get here");
|
||||
}
|
||||
|
||||
private void UpdateImage(string editUri, string filename, out string srcUrl, out string newEditUri)
|
||||
{
|
||||
for (int retry = 5; retry > 0; retry--)
|
||||
for (int retry = 0; retry < MaxRetries; retry++)
|
||||
{
|
||||
var transientCredentials = Login();
|
||||
HttpWebResponse response;
|
||||
bool conflict = false;
|
||||
try
|
||||
|
@ -692,20 +751,35 @@ namespace OpenLiveWriter.BlogClient.Clients
|
|||
}
|
||||
catch (WebException we)
|
||||
{
|
||||
if (retry > 1
|
||||
&& we.Response as HttpWebResponse != null
|
||||
&& ((HttpWebResponse)we.Response).StatusCode == HttpStatusCode.Conflict)
|
||||
if (retry < MaxRetries - 1 &&
|
||||
we.Response as HttpWebResponse != null)
|
||||
{
|
||||
response = (HttpWebResponse)we.Response;
|
||||
conflict = true;
|
||||
if (((HttpWebResponse)we.Response).StatusCode == HttpStatusCode.Conflict)
|
||||
{
|
||||
response = (HttpWebResponse)we.Response;
|
||||
conflict = true;
|
||||
}
|
||||
else if (((HttpWebResponse)we.Response).StatusCode == HttpStatusCode.Forbidden)
|
||||
{
|
||||
// HTTP 403 Forbidden means our OAuth access token is not valid.
|
||||
RefreshAccessToken(transientCredentials);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
else
|
||||
throw;
|
||||
|
||||
throw;
|
||||
}
|
||||
|
||||
using (Stream s = response.GetResponseStream())
|
||||
{
|
||||
ParseMediaEntry(s, out srcUrl, out newEditUri);
|
||||
}
|
||||
|
||||
if (!conflict)
|
||||
{
|
||||
return; // success!
|
||||
}
|
||||
|
||||
editUri = newEditUri;
|
||||
}
|
||||
|
||||
|
@ -782,5 +856,22 @@ namespace OpenLiveWriter.BlogClient.Clients
|
|||
|
||||
#endregion
|
||||
|
||||
public class Category
|
||||
{
|
||||
[JsonProperty("term")]
|
||||
public string Term { get; set; }
|
||||
}
|
||||
|
||||
public class Feed
|
||||
{
|
||||
[JsonProperty("category")]
|
||||
public Category[] CategoryArray { get; set; }
|
||||
}
|
||||
|
||||
public class CategoryResponse
|
||||
{
|
||||
[JsonProperty("feed")]
|
||||
public Feed Feed { get; set; }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -10,6 +10,7 @@ using OpenLiveWriter.Api;
|
|||
using OpenLiveWriter.CoreServices;
|
||||
using OpenLiveWriter.Extensibility.BlogClient;
|
||||
using OpenLiveWriter.BlogClient.Providers;
|
||||
using System.Web;
|
||||
|
||||
// TODO: WP on outstanding issues
|
||||
/*
|
||||
|
@ -53,5 +54,48 @@ namespace OpenLiveWriter.BlogClient.Clients
|
|||
clientOptions.SupportsAuthor = true;
|
||||
}
|
||||
|
||||
public override BlogInfo[] GetUsersBlogs()
|
||||
{
|
||||
TransientCredentials tc = Login();
|
||||
return GetWPUsersBlogs(tc.Username, tc.Password);
|
||||
}
|
||||
private BlogInfo[] GetWPUsersBlogs(string username, string password)
|
||||
{
|
||||
// call method
|
||||
XmlNode result = CallMethod("wp.getUsersBlogs",
|
||||
new XmlRpcString(username),
|
||||
new XmlRpcString(password, true));
|
||||
|
||||
try
|
||||
{
|
||||
// parse results
|
||||
ArrayList blogs = new ArrayList();
|
||||
XmlNodeList dataValues = result.SelectNodes("array/data");
|
||||
foreach (XmlNode dataValue in dataValues)
|
||||
{
|
||||
XmlNodeList blogNodes = dataValue.SelectNodes("value/struct");
|
||||
foreach (XmlNode blogNode in blogNodes)
|
||||
{
|
||||
// get node values
|
||||
XmlNode idNode = blogNode.SelectSingleNode("member[name='blogid']/value");
|
||||
XmlNode nameNode = blogNode.SelectSingleNode("member[name='blogName']/value");
|
||||
XmlNode urlNode = blogNode.SelectSingleNode("member[name='url']/value");
|
||||
|
||||
// add to our list of blogs
|
||||
blogs.Add(new BlogInfo(idNode.InnerText, HttpUtility.HtmlDecode(NodeToText(nameNode)), urlNode.InnerText));
|
||||
}
|
||||
}
|
||||
|
||||
// return list of blogs
|
||||
return (BlogInfo[])blogs.ToArray(typeof(BlogInfo));
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
string response = result != null ? result.OuterXml : "(empty response)";
|
||||
Trace.Fail("Exception occurred while parsing GetUsersBlogs response: " + response + "\r\n" + ex.ToString());
|
||||
throw new BlogClientInvalidServerResponseException("wp.getUsersBlogs", ex.Message, response);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
@ -130,7 +130,7 @@
|
|||
<futurePublishDateWarning>No</futurePublishDateWarning>
|
||||
<defaultView>WebLayout</defaultView>
|
||||
<invalidPostIdFaultCodePattern>^404$</invalidPostIdFaultCodePattern>
|
||||
<supportsMultipleCategories>No</supportsMultipleCategories>
|
||||
<supportsMultipleCategories>Yes</supportsMultipleCategories>
|
||||
<supportsEmptyTitles>Yes</supportsEmptyTitles>
|
||||
<requiresHtmlTitles>Yes</requiresHtmlTitles>
|
||||
<usePicasaImgMaxAlways>Yes</usePicasaImgMaxAlways>
|
||||
|
|
|
@ -88,7 +88,7 @@ namespace OpenLiveWriter.CoreServices.Settings
|
|||
private bool haveLoggedFailedGetKey = false;
|
||||
|
||||
/// <summary>
|
||||
/// Low-level get (returns null if the value doesn't exist).
|
||||
/// Low-level get (returns null if the value doesn't exist).
|
||||
/// </summary>
|
||||
/// <param name="name">name</param>
|
||||
/// <returns>value (null if not present)</returns>
|
||||
|
@ -183,6 +183,10 @@ namespace OpenLiveWriter.CoreServices.Settings
|
|||
{
|
||||
using (RegistryKey key = GetKey(false))
|
||||
{
|
||||
if (key == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
using (RegistryKey subSettingsKey = key.OpenSubKey(subSettingsName))
|
||||
{
|
||||
return subSettingsKey != null;
|
||||
|
|
|
@ -232,6 +232,10 @@ namespace OpenLiveWriter.CoreServices
|
|||
/// <param name="progressHost">The progressHost to provide feedback to</param>
|
||||
private void DownloadReference(ReferenceToDownload reference, FileBasedSiteStorage fileStorage, IProgressHost progressHost)
|
||||
{
|
||||
if (IsBase64EmbededImage(reference.AbsoluteUrl))
|
||||
{
|
||||
return;
|
||||
}
|
||||
UrlDownloadToFile downloader;
|
||||
string fullPath;
|
||||
|
||||
|
@ -346,6 +350,12 @@ namespace OpenLiveWriter.CoreServices
|
|||
}
|
||||
}
|
||||
|
||||
private bool IsBase64EmbededImage(string url)
|
||||
{
|
||||
return url.StartsWith("data:image/", StringComparison.InvariantCultureIgnoreCase) &&
|
||||
url.ToLowerInvariant().Contains("base64");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Used to actually commit the HTML to disk
|
||||
/// </summary>
|
||||
|
|
|
@ -1186,7 +1186,7 @@ namespace OpenLiveWriter.HtmlEditor
|
|||
OnDocumentComplete(e);
|
||||
|
||||
// Remove null-attributed font tags, e.g. <p><font>blah</font></p> --> <p>blah</p>
|
||||
if (DocumentIsReady)
|
||||
if (DocumentIsReady && PostBodyElement != null)
|
||||
{
|
||||
MarkupRange bodyRange = MarkupServices.CreateMarkupRange(PostBodyElement);
|
||||
bodyRange.RemoveElementsByTagId(_ELEMENT_TAG_ID.TAGID_FONT, true);
|
||||
|
|
|
@ -141,7 +141,7 @@ namespace OpenLiveWriter.PostEditor
|
|||
|
||||
//ToDo: OLW Spell Checker
|
||||
// NOTE: hardcode to sentry spelling engine -- need to unroll this if we switch engines
|
||||
private static readonly string CONTEXT_DICTIONARY_FILE = string.Empty; // SentrySpellingChecker.ContextDictionaryFileName ;
|
||||
private static readonly string CONTEXT_DICTIONARY_FILE = "context.tlx";
|
||||
|
||||
private DirectoryInfo _storageDirectory;
|
||||
}
|
||||
|
|
|
@ -1 +1 @@
|
|||
0.5.1.2
|
||||
0.5.1.3
|
||||
|
|
Loading…
Reference in New Issue