//===============================================================================
// Goal Book.
// Copyright © 2009 Mark Brownsword.
//===============================================================================
#region Using Statements
using System;
using System.Linq;
using System.Text;
using System.Xml.Linq;
using GoalBook.Infrastructure.Constants;
using GoalBook.Infrastructure.Converters;
using GoalBook.Infrastructure.ObjectModel;
#endregion
namespace GoalBook.Synchronisation.ToodleDo
{
internal class NoteSynchronisor : Synchronisor<NoteList>
{
#region Constants and Enums
//Note constants
private const string NOTE = "note";
private const string NOTE_ID = "id";
private const string NOTE_TITLE = "title";
private const string NOTE_TEXT = "text";
private const string NOTE_FOLDER = "folder";
private const string NOTE_ADDED = "added";
private const string NOTE_MODIFIED = "modified";
private const string NOTE_PRIVATE = "private";
//URL constants
private const string GET_DELETED_NOTES_URL = @"http://api.toodledo.com/api.php?method=getDeletedNotes;key={0};after={1}";
private const string GET_NOTES_URL = @"http://api.toodledo.com/api.php?method=getNotes;key={0};modafter={1}";
private const string ADD_NOTE_URL = @"http://api.toodledo.com/api.php?method=addNote;key={0};folder={1};title={2};note={3};addedon={4}";
private const string EDIT_NOTE_URL = @"http://api.toodledo.com/api.php?method=editNote;key={0};id={1};folder={2};title={3};note={4}";
private const string DELETE_NOTE_URL = @"http://api.toodledo.com/api.php?method=deleteNote;key={0};id={1}";
/*
getNotes (id, modbefore, modafter)
addNote (folder, title, note, addedon)
editNote (id, folder, title, note)
deleteNote (id)
getDeletedNotes (after)
*/
#endregion
#region Inner Classes and Structures
#endregion
#region Delegates and Events
#endregion
#region Instance and Shared Fields
/// <summary>
/// List of folders.
/// </summary>
private FolderList folders;
/// <summary>
/// Declaration for ToodledoToHtmlConverter.
/// </summary>
private ToodledoToXamlConverter converter;
#endregion
#region Constructors
/// <summary>
/// Constructor.
/// </summary>
internal NoteSynchronisor(ToodledoConnector connector, FolderList folders)
: base(connector)
{
this.folders = folders;
this.converter = new ToodledoToXamlConverter();
}
#endregion
#region Properties
#endregion
#region Private and Protected Methods
/// <summary>
/// Get Note from list.
/// </summary>
/// <param name="noteList">NoteList</param>
/// <param name="id">Identifier for the note</param>
/// <returns>Note</returns>
private Note GetNote(NoteList noteList, int id)
{
var targetList = from g in noteList where g.ExternalIdentifier == id select g;
foreach (Note note in targetList)
{
return note; //One only can exist.
}
return null;
}
/// <summary>
/// Get FolderID for specified identifier.
/// </summary>
/// <param name="id">id parameter</param>
/// <returns>Folder Guid</returns>
private Guid GetFolderID(int id)
{
var targetList = from g in folders where g.ExternalIdentifier == id select g;
foreach (Folder folder in targetList)
{
return folder.FolderID; //One only can exist.
}
return Guid.Empty;
}
/// <summary>
/// Get Folder ExternalIdentifier.
/// </summary>
/// <param name="guid">FolderID parameter</param>
/// <returns>External Identifier for specified folder</returns>
private int GetFolderExternalIdentifier(Guid folderID)
{
var targetList = from g in folders where g.FolderID == folderID select g;
foreach (Folder folder in targetList)
{
return folder.ExternalIdentifier; //One only can exist.
}
return 0;
}
/// <summary>
/// Get Note Text. Formatted for GoalBook.
/// </summary>
/// <param name="text"></param>
/// <returns></returns>
private string ConvertNoteText(string text)
{
const string Break = "<BR />";
const string Return = "\r";
const string NewLine = "\n";
StringBuilder builder = new StringBuilder(text);
// Remove \r and Replace \n with <BR /> - Notes may have Unix line ending (\n).
builder.Replace(Return, string.Empty).Replace(NewLine, Break);
// Convert.
return this.converter.Convert(builder.ToString(), null, null, null).ToString();
}
/// <summary>
/// Get Note Text. Formatted for Toodledo.
/// </summary>
/// <param name="text"></param>
/// <returns></returns>
private string ConvertBackNoteText(string text)
{
return this.converter.ConvertBack(text, null, null, null).ToString();
}
/// <summary>
/// Sync Notes Client. Upload local changes to Toodledo.
/// </summary>
/// <param name="noteList">NoteList parameter</param>
private void SyncNotesClient(NoteList noteList)
{
// Delete.
var deleteList = from g in noteList.DeletedPendingSync select g;
foreach (Note note in deleteList.ToArray())
{
string deleteNoteURL = string.Format(DELETE_NOTE_URL, Connector.GetSessionKey(), note.ExternalIdentifier);
XDocument result = Connector.MakeServerCall(deleteNoteURL, ToodledoConnector.SyncMethod.Post);
if (result.Element(SUCCESS) != null)
{
int deleteResult = 0;
if (int.TryParse(result.Element(SUCCESS).Value, out deleteResult) && deleteResult == 1)
{
noteList.DeletedPendingSync.Remove(note);
}
}
else
{
/*<error>Invalid note ID number</error>*/
}
}
// Add.
var addList = from g in noteList where g.ExternalIdentifier == 0 select g;
foreach (Note note in addList)
{
string addNoteURL = string.Format(
ADD_NOTE_URL,
Connector.GetSessionKey(),
Connector.UrlEncodeParameterValue(this.GetFolderExternalIdentifier(note.FolderID)),
Connector.UrlEncodeParameterValue(note.Title),
Connector.UrlEncodeParameterValue(this.ConvertBackNoteText(note.Text)),
Connector.UrlEncodeParameterValue(Connector.AdjustToServerTime(note.Added).ToString(SynchronisationConstants.SYNC_DATE_FORMAT)));
XDocument result = this.Connector.MakeServerCall(addNoteURL, ToodledoConnector.SyncMethod.Post);
if (result.Element(ADDED) != null)
{
int addResult = 0;
if (int.TryParse(result.Element(ADDED).Value, out addResult) && addResult > 0)
{
note.ExternalIdentifier = addResult;
note.SyncRequired = false;
}
}
else
{
/*TODO: Log <error></error>*/
}
}
// Edit
var editList = from g in noteList where g.ExternalIdentifier > 0 && g.SyncRequired select g;
foreach (Note note in editList)
{
string editNoteURL = string.Format(
EDIT_NOTE_URL,
Connector.GetSessionKey(),
note.ExternalIdentifier,
Connector.UrlEncodeParameterValue(this.GetFolderExternalIdentifier(note.FolderID)),
Connector.UrlEncodeParameterValue(note.Title),
Connector.UrlEncodeParameterValue(this.ConvertBackNoteText(note.Text)));
XDocument result = this.Connector.MakeServerCall(editNoteURL, ToodledoConnector.SyncMethod.Post);
if (result.Element(SUCCESS) != null)
{
int editResult = 0;
if (int.TryParse(result.Element(SUCCESS).Value, out editResult) && editResult == 1)
{
note.SyncRequired = false;
}
}
else
{
/*TODO: Log <error></error>*/
}
}
}
/// <summary>
/// Sync Notes Server. Retrieves the notes from Toodledo.
/// </summary>
/// <param name="noteList">NoteList</param>
private void SyncNotesServer(NoteList noteList)
{
// Delete
string deletedNotesURL = string.Format(GET_DELETED_NOTES_URL, Connector.GetSessionKey(), Connector.AdjustToServerTime(noteList.LastServerNoteEdit).ToString(SynchronisationConstants.SYNC_DATE_FORMAT));
XDocument serverDeletedList = Connector.MakeServerCall(deletedNotesURL);
var orderedServerDeletedList = from s in serverDeletedList.Descendants(NOTE) orderby s.Element(NOTE_ID).Value select s;
foreach (XElement element in orderedServerDeletedList)
{
Note target = GetNote(noteList, int.Parse(element.Element(NOTE_ID).Value));
noteList.Remove(target);
}
// Add, Edit.
string notesURL = string.Format(GET_NOTES_URL, Connector.GetSessionKey(), Connector.AdjustToServerTime(noteList.LastServerNoteEdit).ToString(SynchronisationConstants.SYNC_DATE_FORMAT));
XDocument serverList = Connector.MakeServerCall(notesURL);
var orderedServerList = from s in serverList.Descendants(NOTE) orderby s.Element(NOTE_ID).Value select s;
foreach (XElement element in orderedServerList)
{
Note target = GetNote(noteList, int.Parse(element.Element(NOTE_ID).Value));
if (target == null)
{
// Add
Guid folderID = GetFolderID(int.Parse(element.Element(NOTE_FOLDER).Value));
DateTime added = Connector.AdjustFromServerTime(Convert.ToDateTime(element.Element(NOTE_ADDED).Value));
DateTime modified = Connector.AdjustFromServerTime(Convert.ToDateTime(element.Element(NOTE_MODIFIED).Value));
string title = element.Element(NOTE_TITLE).Value;
string text = element.Element(NOTE_TEXT).Value;
bool isPrivate = element.Element(NOTE_PRIVATE).Value == NUMERIC_TRUE;
Note addNote = new Note(Guid.NewGuid(), title, this.ConvertNoteText(text), folderID, added, modified, isPrivate);
addNote.ExternalIdentifier = int.Parse(element.Element(NOTE_ID).Value);
addNote.SyncRequired = false;
noteList.Add(addNote);
noteList.EndNew(noteList.IndexOf(addNote));
}
else
{
// Edit
Note editNote = target.Clone();
DateTime modified = Connector.AdjustFromServerTime(Convert.ToDateTime(element.Element(NOTE_MODIFIED).Value));
if (modified > editNote.Modified)
{
editNote.FolderID = GetFolderID(int.Parse(element.Element(NOTE_FOLDER).Value));
editNote.Added = Connector.AdjustFromServerTime(Convert.ToDateTime(element.Element(NOTE_ADDED).Value));
editNote.Modified = modified;
editNote.Title = element.Element(NOTE_TITLE).Value;
editNote.Text = this.ConvertNoteText(element.Element(NOTE_TEXT).Value);
editNote.IsPrivate = element.Element(NOTE_PRIVATE).Value == NUMERIC_TRUE;
noteList[noteList.IndexOf(target)].MapFields(editNote);
noteList[noteList.IndexOf(target)].SyncRequired = false;
}
}
}
}
#endregion
#region Public and internal Methods
#endregion
#region Event Handlers
#endregion
#region Base Class Overrides
/// <summary>
/// Sync the NoteList.
/// </summary>
/// <param name="list">NoteList</param>
/// <returns>SyncTokenInfo</returns>
internal override void Sync(NoteList noteList)
{
DateTime savedLastServerNoteEdit = noteList.LastServerNoteEdit;
if (noteList.SyncRequired)
{
// Client has changes.
SyncNotesClient(noteList);
}
if (Connector.LastNoteEdit > savedLastServerNoteEdit)
{
// Server has changes.
SyncNotesServer(noteList);
}
}
#endregion
}
}