diff --git a/src/managed/OpenLiveWriter.BlogClient/Clients/GoogleBloggerv3Client.cs b/src/managed/OpenLiveWriter.BlogClient/Clients/GoogleBloggerv3Client.cs index f9265131..6c52e795 100644 --- a/src/managed/OpenLiveWriter.BlogClient/Clients/GoogleBloggerv3Client.cs +++ b/src/managed/OpenLiveWriter.BlogClient/Clients/GoogleBloggerv3Client.cs @@ -144,9 +144,11 @@ namespace OpenLiveWriter.BlogClient.Clients private const string FEATURES_NS = "http://purl.org/atompub/features/1.0"; private const string MEDIA_NS = "http://search.yahoo.com/mrss/"; private const string LIVE_NS = "http://api.live.com/schemas"; + private const string GPHOTO_NS_URI = "http://schemas.google.com/photos/2007"; private static readonly Namespace atomNS = new Namespace(AtomProtocolVersion.V10DraftBlogger.NamespaceUri, "atom"); private static readonly Namespace pubNS = new Namespace(AtomProtocolVersion.V10DraftBlogger.PubNamespaceUri, "app"); + private static readonly Namespace photoNS = new Namespace(GPHOTO_NS_URI, "gphoto"); private IBlogClientOptions _clientOptions; private XmlNamespaceManager _nsMgr; @@ -173,6 +175,7 @@ namespace OpenLiveWriter.BlogClient.Clients _nsMgr = new XmlNamespaceManager(new NameTable()); _nsMgr.AddNamespace(atomNS.Prefix, atomNS.Uri); _nsMgr.AddNamespace(pubNS.Prefix, pubNS.Uri); + _nsMgr.AddNamespace(photoNS.Prefix, photoNS.Uri); _nsMgr.AddNamespace(AtomClient.xhtmlNS.Prefix, AtomClient.xhtmlNS.Uri); _nsMgr.AddNamespace(AtomClient.featuresNS.Prefix, AtomClient.featuresNS.Uri); _nsMgr.AddNamespace(AtomClient.mediaNS.Prefix, AtomClient.mediaNS.Uri); @@ -211,7 +214,7 @@ namespace OpenLiveWriter.BlogClient.Clients protected override TransientCredentials Login() { - var transientCredentials = Credentials.TransientCredentials as TransientCredentials ?? + var transientCredentials = Credentials.TransientCredentials as TransientCredentials ?? new TransientCredentials(Credentials.Username, Credentials.Password, null); VerifyAndRefreshCredentials(transientCredentials); Credentials.TransientCredentials = transientCredentials; @@ -406,7 +409,7 @@ namespace OpenLiveWriter.BlogClient.Clients allPosts = allPosts.Concat(draftRecentPosts).Concat(liveRecentPosts).Concat(scheduledRecentPosts); } while (allPosts.Count() < maxPosts && (draftRecentPosts.Count > 0 || liveRecentPosts.Count > 0 || scheduledRecentPosts.Count > 0)); - + return allPosts .OrderByDescending(p => p.Published) .Take(maxPosts) @@ -503,7 +506,7 @@ namespace OpenLiveWriter.BlogClient.Clients // We keep around the PageList returned by each request to support pagination. PageList draftPagesList = null; PageList livePagesList = null; - + // We break out of the following loop depending on which one of these two cases we hit: // (a) the number of all blog pages ever posted to this blog is greater than maxPages, so eventually // allPages.count() will exceed maxPages and we can stop making requests. @@ -615,7 +618,7 @@ namespace OpenLiveWriter.BlogClient.Clients string editUri = uploadContext.Settings.GetString(EDIT_MEDIA_LINK, null); if (editUri == null || editUri.Length == 0) { - PostNewImage(albumName, path, out srcUrl, out editUri); + PostNewImage(albumName, path, uploadContext.BlogId, out srcUrl, out editUri); } else { @@ -634,7 +637,7 @@ namespace OpenLiveWriter.BlogClient.Clients try { // couldn't update existing image? try posting a new one - PostNewImage(albumName, path, out srcUrl, out editUri); + PostNewImage(albumName, path, uploadContext.BlogId, out srcUrl, out editUri); success = true; } catch @@ -725,17 +728,25 @@ namespace OpenLiveWriter.BlogClient.Clients #region Picasa image uploading - stolen from BloggerAtomClient - public string GetBlogImagesAlbum(string albumName) + public string GetBlogImagesAlbum(string albumName, string blogId) { const string FEED_REL = "http://schemas.google.com/g/2005#feed"; - const string GPHOTO_NS_URI = "http://schemas.google.com/photos/2007"; Uri picasaUri = new Uri("https://picasaweb.google.com/data/feed/api/user/default"); + var picasaId = string.Empty; + try { Uri reqUri = picasaUri; XmlDocument albumListDoc = AtomClient.xmlRestRequestHelper.Get(ref reqUri, CreateAuthorizationFilter(), "kind", "album"); + var idNode = albumListDoc.SelectSingleNode(@"/atom:feed/gphoto:user", _nsMgr) as XmlElement; + if (idNode != null) + { + var id = AtomProtocolVersion.V10DraftBlogger.TextNodeToPlaintext(idNode); + picasaId = id; + } + foreach (XmlElement entryEl in albumListDoc.SelectNodes(@"/atom:feed/atom:entry", _nsMgr)) { XmlElement titleNode = entryEl.SelectSingleNode(@"atom:title", _nsMgr) as XmlElement; @@ -744,9 +755,7 @@ namespace OpenLiveWriter.BlogClient.Clients string titleText = AtomProtocolVersion.V10DraftBlogger.TextNodeToPlaintext(titleNode); if (titleText == albumName) { - XmlNamespaceManager nsMgr2 = new XmlNamespaceManager(new NameTable()); - nsMgr2.AddNamespace("gphoto", "http://schemas.google.com/photos/2007"); - XmlNode numPhotosRemainingNode = entryEl.SelectSingleNode("gphoto:numphotosremaining/text()", nsMgr2); + XmlNode numPhotosRemainingNode = entryEl.SelectSingleNode("gphoto:numphotosremaining/text()", _nsMgr); if (numPhotosRemainingNode != null) { int numPhotosRemaining; @@ -777,34 +786,62 @@ namespace OpenLiveWriter.BlogClient.Clients throw; } - XmlDocument newDoc = new XmlDocument(); - XmlElement newEntryEl = newDoc.CreateElement("atom", "entry", AtomProtocolVersion.V10DraftBlogger.NamespaceUri); - newDoc.AppendChild(newEntryEl); + try + { + XmlDocument newDoc = new XmlDocument(); + XmlElement newEntryEl = newDoc.CreateElement("atom", "entry", AtomProtocolVersion.V10DraftBlogger.NamespaceUri); + newDoc.AppendChild(newEntryEl); - XmlElement newTitleEl = newDoc.CreateElement("atom", "title", AtomProtocolVersion.V10DraftBlogger.NamespaceUri); - newTitleEl.SetAttribute("type", "text"); - newTitleEl.InnerText = albumName; - newEntryEl.AppendChild(newTitleEl); + XmlElement newTitleEl = newDoc.CreateElement("atom", "title", AtomProtocolVersion.V10DraftBlogger.NamespaceUri); + newTitleEl.SetAttribute("type", "text"); + newTitleEl.InnerText = albumName; + newEntryEl.AppendChild(newTitleEl); - XmlElement newSummaryEl = newDoc.CreateElement("atom", "summary", AtomProtocolVersion.V10DraftBlogger.NamespaceUri); - newSummaryEl.SetAttribute("type", "text"); - newSummaryEl.InnerText = Res.Get(StringId.BloggerImageAlbumDescription); - newEntryEl.AppendChild(newSummaryEl); + XmlElement newSummaryEl = newDoc.CreateElement("atom", "summary", AtomProtocolVersion.V10DraftBlogger.NamespaceUri); + newSummaryEl.SetAttribute("type", "text"); + newSummaryEl.InnerText = Res.Get(StringId.BloggerImageAlbumDescription); + newEntryEl.AppendChild(newSummaryEl); - XmlElement newAccessEl = newDoc.CreateElement("gphoto", "access", GPHOTO_NS_URI); - newAccessEl.InnerText = "private"; - newEntryEl.AppendChild(newAccessEl); + XmlElement newAccessEl = newDoc.CreateElement("gphoto", "access", photoNS.Uri); + newAccessEl.InnerText = "private"; + newEntryEl.AppendChild(newAccessEl); - XmlElement newCategoryEl = newDoc.CreateElement("atom", "category", AtomProtocolVersion.V10DraftBlogger.NamespaceUri); - newCategoryEl.SetAttribute("scheme", "http://schemas.google.com/g/2005#kind"); - newCategoryEl.SetAttribute("term", "http://schemas.google.com/photos/2007#album"); - newEntryEl.AppendChild(newCategoryEl); + XmlElement newCategoryEl = newDoc.CreateElement("atom", "category", AtomProtocolVersion.V10DraftBlogger.NamespaceUri); + newCategoryEl.SetAttribute("scheme", "http://schemas.google.com/g/2005#kind"); + newCategoryEl.SetAttribute("term", "http://schemas.google.com/photos/2007#album"); + newEntryEl.AppendChild(newCategoryEl); - Uri postUri = picasaUri; - XmlDocument newAlbumResult = AtomClient.xmlRestRequestHelper.Post(ref postUri, CreateAuthorizationFilter(), "application/atom+xml", newDoc, null); - XmlElement newAlbumResultEntryEl = newAlbumResult.SelectSingleNode("/atom:entry", _nsMgr) as XmlElement; - Debug.Assert(newAlbumResultEntryEl != null); - return AtomEntry.GetLink(newAlbumResultEntryEl, _nsMgr, FEED_REL, "application/atom+xml", null, postUri); + Uri postUri = picasaUri; + XmlDocument newAlbumResult = AtomClient.xmlRestRequestHelper.Post(ref postUri, CreateAuthorizationFilter(), "application/atom+xml", newDoc, null); + XmlElement newAlbumResultEntryEl = newAlbumResult.SelectSingleNode("/atom:entry", _nsMgr) as XmlElement; + Debug.Assert(newAlbumResultEntryEl != null); + return AtomEntry.GetLink(newAlbumResultEntryEl, _nsMgr, FEED_REL, "application/atom+xml", null, postUri); + } + catch (Exception) + { + // Ignore + } + + // If we've got this far, it means creating the Open Live Writer album has failed. + // We will now try and use the Blogger assigned folder. + if (!string.IsNullOrEmpty(picasaId)) + { + var service = GetService(); + + var userInfo = service.BlogUserInfos.Get("self", blogId).Execute(); + + // If the PhotosAlbumKey is "0", this means the user has never posted to Blogger from the + // Blogger web interface, which means the album has never been created and so there's nothing + // for us to use. + if (userInfo.BlogUserInfoValue.PhotosAlbumKey != "0") + { + var bloggerPicasaUrl = $"https://picasaweb.google.com/data/feed/api/user/{picasaId}/albumid/{userInfo.BlogUserInfoValue.PhotosAlbumKey}"; + return bloggerPicasaUrl; + } + } + + // If we've got this far, it means the user is going to have to manually create their own album. + throw new BlogClientFileTransferException("Unable to upload to Blogger", "BloggerError", "We were unable to create a folder for your images, please go to http://openlivewriter.org/tutorials/googlePhotoFix.html to see how to do this"); } private void ShowPicasaSignupPrompt(object sender, EventArgs e) @@ -815,14 +852,14 @@ namespace OpenLiveWriter.BlogClient.Clients } } - private void PostNewImage(string albumName, string filename, out string srcUrl, out string editUri) + private void PostNewImage(string albumName, string filename, string blogId, out string srcUrl, out string editUri) { for (int retry = 0; retry < MaxRetries; retry++) { var transientCredentials = Login(); try { - string albumUrl = GetBlogImagesAlbum(albumName); + string albumUrl = GetBlogImagesAlbum(albumName, blogId); HttpWebResponse response = RedirectHelper.GetResponse(albumUrl, new RedirectHelper.RequestFactory(new UploadFileRequestFactory(this, filename, "POST").Create)); using (Stream s = response.GetResponseStream()) { @@ -863,7 +900,7 @@ namespace OpenLiveWriter.BlogClient.Clients } catch (WebException we) { - if (retry < MaxRetries - 1 && + if (retry < MaxRetries - 1 && we.Response as HttpWebResponse != null) { if (((HttpWebResponse)we.Response).StatusCode == HttpStatusCode.Conflict)