static private void GetEvents(Journal.OptionsRow or, ILJServer iLJ, ref SyncItemCollection sic, ref SyncItemCollection deletedsic, ref EventCollection ec) { // for an explanation of this algorithm, see // http://www.livejournal.com/community/lj_clients/143312.html // note that this is a very painful algorithm. it will loop an extra time for each // deleted syncitem that getevents doesn't return an event for. if LJ decides to revise // how they return syncitems, this algorithm can be made more efficient. int total = sic.Count; while (sic.Count > 0) { SyncItem oldest = sic.GetOldest(); DateTime oldestTime = DateTime.Parse(oldest.time, CultureInfo.InvariantCulture).AddSeconds(-1); GetChallengeResponse gcr = iLJ.GetChallenge(); string auth_response = MD5Hasher.Compute(gcr.challenge + or.HPassword); GetEventsParams gep = new GetEventsParams(or.UserName, "challenge", gcr.challenge, auth_response, 1, 0, 0, 0, "syncitems", oldestTime.ToString(_datetimeformat), 0, 0, 0, 0, string.Empty, 0, "unix", (or.IsUseJournalNull() ? string.Empty : or.UseJournal)); GetEventsResponse ger; socb(new SyncOperationEventArgs(SyncOperation.GetEvents, total - sic.Count, total)); ger = iLJ.GetEvents(gep); // remove this item in case it isn't returned by getevents // this signifies that the item has been deleted // this also ensures we don't get stuck in an endless loop sic.Remove(oldest); sic.RemoveDownloaded(ger.events); deletedsic.RemoveDownloaded(ger.events); ec.AddRange(ger.events); } }
static private void SyncItems(Journal.OptionsRow or, ILJServer iLJ, ref SyncItemCollection sic, ref SyncItemCollection deletedsic, ref DateTime lastSync) { // syncitems returns a "meta" list of what events have changed since the last time we called syncitems // note that syncitems may be called more than once GetChallengeResponse gcr; string auth_response; SyncItemsParams sip; SyncItemsResponse sir; int total = -1, count = 0; lastSync = (or.IsLastSyncNull() ? DateTime.MinValue : or.LastSync); do { string lastSyncString = (lastSync == DateTime.MinValue ? string.Empty : lastSync.ToString(_datetimeformat)); gcr = iLJ.GetChallenge(); auth_response = MD5Hasher.Compute(gcr.challenge + or.HPassword); sip = new SyncItemsParams(or.UserName, "challenge", gcr.challenge, auth_response, 1, lastSyncString, (or.IsUseJournalNull() ? string.Empty : or.UseJournal)); sir = iLJ.SyncItems(sip); total = (total == -1 ? sir.total : total); count += sir.count; sic.AddRangeLog(sir.syncitems); deletedsic.AddRangeLog(sir.syncitems); if (sic.GetMostRecentTime() > lastSync) { lastSync = sic.GetMostRecentTime(); } socb(new SyncOperationEventArgs(SyncOperation.SyncItems, count, total)); } while (sir.count < sir.total); }
static private void SessionGenerate(Journal.OptionsRow or, ILJServer iLJ, ref SessionGenerateResponse sgr) { // a session needs to be generated to talk to the livejournal web server // right now there is no export comments method on xmlrpc, so we get comments the ol' fashioned way - // with a web request GetChallengeResponse gcr = iLJ.GetChallenge(); string auth_response = MD5Hasher.Compute(gcr.challenge + or.HPassword); SessionGenerateParams sgp = new SessionGenerateParams(or.UserName, "challenge", gcr.challenge, auth_response, 1, "long", 0); sgr = iLJ.SessionGenerate(sgp); }
static private void Login(Journal.OptionsRow or, ILJServer iLJ, ref LoginResponse lr, ref string communityPicURL) { // logging in to the server gets back a lot of assorted metadata we need to store GetChallengeResponse gcr; string auth_response; LoginParams lp; gcr = iLJ.GetChallenge(); auth_response = MD5Hasher.Compute(gcr.challenge + or.HPassword); lp = new LoginParams(or.UserName, "challenge", gcr.challenge, auth_response, 1, clientVersion, j.GetMaxMoodID(), 0, 1, 1); lr = iLJ.Login(lp); // if downloading a community, we want the community's default user pic, not the user's if (!or.IsUseJournalNull()) { communityPicURL = Server.GetDefaultPicURL(or.UseJournal, or.ServerURL, true); } }
static private void ExportCommentsBody(Journal.OptionsRow or, ILJServer iLJ, SessionGenerateResponse sgr, int serverMaxID, int localMaxID, CommentCollection cc) { // note that the export comments body web request may be called more than once int maxID = localMaxID; while (maxID < serverMaxID) { Uri uri = new Uri(new Uri(or.ServerURL), string.Format(_exportcommentsbodypath, maxID + 1)); if (!or.IsUseJournalNull()) { uri = new Uri(uri.AbsoluteUri + string.Format("&authas={0}", or.UseJournal)); } HttpWebRequest w = HttpWebRequestFactory.Create(uri.AbsoluteUri, sgr.ljsession); socb(new SyncOperationEventArgs(SyncOperation.ExportCommentsBody, maxID - localMaxID, serverMaxID - localMaxID)); using (Stream s = w.GetResponse().GetResponseStream()) { System.Text.Encoding ec; if (System.Environment.Version.Major == 1) // .NET 2.0 utf8 cleans strings, so we don't have to { ec = new UTF8Clean(); } else { ec = System.Text.Encoding.UTF8; } StreamReader sr = new StreamReader(s, ec); XmlCommentReader xcr = new XmlCommentReader(sr); while (xcr.Read()) { cc.Add(xcr.Comment); } xcr.Close(); } maxID = cc.GetMaxID(); } }
static private void ExportCommentsMeta(Journal.OptionsRow or, ILJServer iLJ, SessionGenerateResponse sgr, ref int serverMaxID, int localMaxID, UserMapCollection umc, CommentCollection cc) { // this is a an unfortunately necessary step // we call export comments meta is to get the user map and to check if comment state has changed // it would be better to be able to provide a lastsync, but alas // see http://www.livejournal.com/developer/exporting.bml for more info int maxID = -1; while (maxID < serverMaxID) { Uri uri = new Uri(new Uri(or.ServerURL), string.Format(_exportcommentsmetapath, maxID + 1)); if (!or.IsUseJournalNull()) { uri = new Uri(uri.AbsoluteUri + string.Format("&authas={0}", or.UseJournal)); } HttpWebRequest w = HttpWebRequestFactory.Create(uri.AbsoluteUri, sgr.ljsession); using (Stream s = w.GetResponse().GetResponseStream()) { XmlTextReader xtr = new XmlTextReader(s); while (xtr.Read()) { if (xtr.NodeType == XmlNodeType.Element && xtr.Name == "usermap") { string id = xtr.GetAttribute("id"); string user = xtr.GetAttribute("user"); if (id != null && user != null && !umc.ContainsID(XmlConvert.ToInt32(id))) { umc.Add(new UserMap(XmlConvert.ToInt32(id), user)); } } else if (xtr.NodeType == XmlNodeType.Element && xtr.Name == "maxid") { xtr.Read(); serverMaxID = XmlConvert.ToInt32(xtr.Value); socb(new SyncOperationEventArgs(SyncOperation.ExportCommentsMeta, Math.Max(maxID, 0), serverMaxID)); } else if (xtr.NodeType == XmlNodeType.Element && xtr.Name == "comment") { string id = xtr.GetAttribute("id"); string posterid = xtr.GetAttribute("posterid"); string state = xtr.GetAttribute("state"); if (posterid == null) { posterid = "0"; } if (state == null) { state = "A"; } if (id != null) { cc.Add(new Comment(XmlConvert.ToInt32(id), XmlConvert.ToInt32(posterid), state, 0, 0, string.Empty, string.Empty, DateTime.MinValue)); } } else if (xtr.NodeType == XmlNodeType.Element && xtr.Name == "h2") { xtr.Read(); if (xtr.Value == "Error" && !or.IsUseJournalNull()) { throw new ExpectedSyncException(ExpectedError.CommunityAccessDenied, null); } } } xtr.Close(); } maxID = cc.GetMaxID(); } }
static private void ThreadStart() { // The main threaded execution body for performing a sync. // This method is chopped up into smaller methods for clarity and structure. ILJServer iLJ; Journal.OptionsRow or = null; SyncItemCollection sic = null, deletedsic = null; EventCollection ec = null; CommentCollection ccbody = null, ccmeta = null; UserMapCollection umc = null; LoginResponse lr = new LoginResponse(); string communityPicURL = null; DateTime lastSync = DateTime.MinValue; SessionGenerateResponse sgr; int serverMaxID, localMaxID; try { // STEP 1: Initialize socb(new SyncOperationEventArgs(SyncOperation.Initialize, 0, 0)); syncException = null; or = j.Options; iLJ = LJServerFactory.Create(or.ServerURL); sic = new SyncItemCollection(); deletedsic = new SyncItemCollection(); ec = new EventCollection(); ccmeta = new CommentCollection(); ccbody = new CommentCollection(); umc = new UserMapCollection(); // STEP 2: Login socb(new SyncOperationEventArgs(SyncOperation.Login, 0, 0)); lr = new LoginResponse(); Login(or, iLJ, ref lr, ref communityPicURL); // STEP 3: SyncItems socb(new SyncOperationEventArgs(SyncOperation.SyncItems, 0, 0)); lastSync = DateTime.MinValue; SyncItems(or, iLJ, ref sic, ref deletedsic, ref lastSync); // STEP 4: GetEvents socb(new SyncOperationEventArgs(SyncOperation.GetEvents, 0, 0)); GetEvents(or, iLJ, ref sic, ref deletedsic, ref ec); if (or.GetComments) { // STEP 5: SessionGenerate socb(new SyncOperationEventArgs(SyncOperation.SessionGenerate, 0, 0)); sgr = new SessionGenerateResponse(); SessionGenerate(or, iLJ, ref sgr); // STEP 6: ExportCommentsMeta socb(new SyncOperationEventArgs(SyncOperation.ExportCommentsMeta, 0, 0)); localMaxID = serverMaxID = j.GetMaxCommentID(); ExportCommentsMeta(or, iLJ, sgr, ref serverMaxID, localMaxID, umc, ccmeta); // STEP 7: ExportCommentsBody socb(new SyncOperationEventArgs(SyncOperation.ExportCommentsBody, 0, 0)); ExportCommentsBody(or, iLJ, sgr, serverMaxID, localMaxID, ccbody); } } catch (Exception ex) { ParseException(ex, ref syncException); if (ex.GetType() == typeof(ThreadAbortException)) { socb(new SyncOperationEventArgs(SyncOperation.Failure, 0, 0)); // do this before thread terminates return; } } // STEP 8: Merge try { if (syncException == null) { socb(new SyncOperationEventArgs(SyncOperation.Merge, 0, 0)); Merge(j, ec, ccmeta, ccbody, umc, deletedsic, lr, communityPicURL, lastSync); socb(new SyncOperationEventArgs(SyncOperation.Success, ec.Count, ccbody.Count)); } else if (syncException.GetType() == typeof(ExpectedSyncException) && (((ExpectedSyncException)syncException).ExpectedError == ExpectedError.ServerNotResponding || ((ExpectedSyncException)syncException).ExpectedError == ExpectedError.ExportCommentsNotSupported || ((ExpectedSyncException)syncException).ExpectedError == ExpectedError.CommunityAccessDenied) && lr.moods != null) { socb(new SyncOperationEventArgs(SyncOperation.Merge, 0, 0)); if (sic.Count > 0) { lastSync = DateTime.Parse(sic.GetOldest().time, CultureInfo.InvariantCulture).AddSeconds(-1); } Merge(j, ec, ccmeta, ccbody, umc, deletedsic, lr, communityPicURL, lastSync); socb(new SyncOperationEventArgs(SyncOperation.PartialSync, ec.Count, ccbody.Count)); } else { socb(new SyncOperationEventArgs(SyncOperation.Failure, 0, 0)); } } catch (Exception ex) { syncException = ex; socb(new SyncOperationEventArgs(SyncOperation.Failure, 0, 0)); } }