kopano-ol-extension/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Features/Notes/FeatureNotes.cs

292 lines
12 KiB
C#

/// Copyright 2016 Kopano b.v.
///
/// This program is free software: you can redistribute it and/or modify
/// it under the terms of the GNU Affero General Public License, version 3,
/// as published by the Free Software Foundation.
///
/// This program is distributed in the hope that it will be useful,
/// but WITHOUT ANY WARRANTY; without even the implied warranty of
/// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.See the
/// GNU Affero General Public License for more details.
///
/// You should have received a copy of the GNU Affero General Public License
/// along with this program.If not, see<http://www.gnu.org/licenses/>.
///
/// Consult LICENSE file for details
using Acacia.Stubs;
using Acacia.Stubs.OutlookWrappers;
using Acacia.Utils;
using Acacia.ZPush;
using Microsoft.Office.Interop.Outlook;
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using static Acacia.DebugOptions;
namespace Acacia.Features.Notes
{
[AcaciaOption("Provides the possibility to synchronise Notes to and from a Z-Push server.")]
public class FeatureNotes : Feature
{
public FeatureNotes()
{
}
public override void Startup()
{
Watcher.WatchFolder(new FolderRegistrationTyped(this, ItemType.NoteItem),
OnNotesFolderDiscovered, OnNotesFolderChanged);
}
#region Debug options
[AcaciaOption("Disables the patching of Notes folders types. Without this, Outlook will not recognise " +
"these folders as being Notes folders, and contents will not be synchronised.")]
public bool PatchFolders
{
get { return GetOption(OPTION_PATCH_FOLDERS); }
set { SetOption(OPTION_PATCH_FOLDERS, value); }
}
private static readonly BoolOption OPTION_PATCH_FOLDERS = new BoolOption("PatchFolders", true);
[AcaciaOption("Disables the patching of Note item types. Without this, Outlook will not recognise " +
"these items as being Notes, and they may appear in unusual states.")]
public bool PatchItems
{
get { return GetOption(OPTION_PATCH_ITEMS); }
set { SetOption(OPTION_PATCH_ITEMS, value); }
}
private static readonly BoolOption OPTION_PATCH_ITEMS = new BoolOption("PatchItems", true);
#endregion
private void OnNotesFolderDiscovered(IFolder folder)
{
Logger.Instance.Debug(this, "NOTES FOLDER: {0}", folder);
// Always watch the folder. Any notes being synced in indicate the server supports notes,
// and otherwise there's no harm done.
Watcher.WatchItems<IItem>(folder, PatchNote, true);
// Patch the folder if needed
PatchIfConfirmed(folder);
}
private void OnNotesFolderChanged(IFolder folder)
{
Logger.Instance.Debug(this, "NOTES FOLDER CHANGED: {0}, type={1}", folder, folder.GetProperty(OutlookConstants.PR_EAS_SYNCTYPE));
// Outlook sometimes changes the type back. Patch again if needed.
PatchIfConfirmed(folder);
}
private bool IsNotesFolder(OutlookConstants.SyncType type)
{
return type == OutlookConstants.SyncType.Note || type == OutlookConstants.SyncType.UserNote;
}
private void PatchIfConfirmed(IFolder folder)
{
// Only patch if on a ZPush server that supports notes. Store the folder as entryId, there have been some
// issues with the folder object being disposed in the past
string folderId = folder.EntryId;
ZPushAccount zpush = Watcher.Accounts.GetAccount(folder);
if (zpush != null)
{
zpush.ConfirmedChanged += (z) =>
{
if (zpush.Confirmed == ZPushAccount.ConfirmationType.IsZPush &&
zpush.Capabilities.Has(Constants.ZPUSH_CAPABILITY_NOTES))
{
PatchFolder(folderId);
}
else if (zpush.Confirmed != ZPushAccount.ConfirmationType.Unknown)
{
// The server is either not a Z-Push server, or it does not support notes
// Restore any patched notes folder
UnpatchFolder(folderId);
}
};
}
}
private void PatchFolder(string folderId)
{
if (!PatchFolders)
return;
Logger.Instance.Trace(this, "PatchFolder: {0}", folderId);
try
{
using (IFolder folder = Mapping.GetFolderFromID(folderId))
{
if (folder == null)
return;
// Patch if needed
OutlookConstants.SyncType type = FolderUtils.GetFolderSyncType(folder);
Logger.Instance.Trace(this, "Notes folder type: {0}", type);
if (IsNotesFolder(type))
{
Logger.Instance.Debug(this, "Patching Notes folder type: {0}", type);
// Change to task folder
folder.SetProperties(new string[]
{
OutlookConstants.PR_NET_FOLDER_FLAGS,
OutlookConstants.PR_EAS_SYNCTYPE,
OutlookConstants.PR_EAS_SYNC1,
OutlookConstants.PR_EAS_SYNC2
}, new object[]
{
0, (int)OutlookConstants.SyncType.UserAppointment, true, true
});
if (type == OutlookConstants.SyncType.Note)
{
// Local notes, change name
PatchFolderName(folder);
}
}
}
}
finally
{
Logger.Instance.Trace(this, "PatchFolder done");
}
}
private void UnpatchFolder(string folderId)
{
if (!PatchFolders)
return;
Logger.Instance.Trace(this, "UnpatchFolder: {0}", folderId);
try
{
using (IFolder folder = Mapping.GetFolderFromID(folderId))
{
if (folder == null)
return;
// Unpatch if needed
OutlookConstants.SyncType type = FolderUtils.GetFolderSyncType(folder, true);
Logger.Instance.Trace(this, "Notes folder type: {0}", type);
// Unpatch only if the original type is a notes folder, but the current type isn't
if (IsNotesFolder(type) && !IsNotesFolder(FolderUtils.GetFolderSyncType(folder)))
{
Logger.Instance.Debug(this, "Unpatching Notes folder type: {0}", type);
// Change to original notes folder
folder.SetProperties(new string[]
{
OutlookConstants.PR_EAS_SYNCTYPE,
OutlookConstants.PR_EAS_SYNC1,
OutlookConstants.PR_EAS_SYNC2
}, new object[]
{
(int)type,
false,
false
});
if (type == OutlookConstants.SyncType.Note)
{
// Local notes, change name
UnpatchFolderName(folder);
}
}
}
}
finally
{
Logger.Instance.Trace(this, "PatchFolder done");
}
}
private void PatchNote(IItem item)
{
if (!PatchItems)
return;
Logger.Instance.Trace(this, "NOTE ITEM: Subject='{0}', Class={1}",
item.GetProperty(OutlookConstants.PR_SUBJECT),
item.GetProperty(OutlookConstants.PR_MESSAGE_CLASS));
try
{
if ((int)item.GetProperty(OutlookConstants.PR_ICON_INDEX) != 771)
{
Logger.Instance.Trace(this, "Patching item: {0}", item.EntryId);
// Patch standard properties
item.SetProperties(
new string[] { OutlookConstants.PR_MESSAGE_CLASS, OutlookConstants.PR_ICON_INDEX, OutlookConstants.PR_NOTE_COLOR },
new object[] { OutlookConstants.MESSAGE_CLASS_NOTES, 771, 3 }
);
// Set sizes if not set, they get crappy defaults
try
{
// This causes an exception if nothing is set
item.GetProperty(OutlookConstants.PR_NOTE_WIDTH);
}
catch (System.Exception)
{
Logger.Instance.Trace(this, "Setting default sizes");
item.SetProperty(OutlookConstants.PR_NOTE_WIDTH, 200);
item.SetProperty(OutlookConstants.PR_NOTE_HEIGHT, 166);
item.SetProperty(OutlookConstants.PR_NOTE_X, 80);
item.SetProperty(OutlookConstants.PR_NOTE_Y, 80);
}
item.Save();
}
}
finally
{
Logger.Instance.Trace(this, "PatchNote done");
}
}
private void PatchFolderName(IFolder folder)
{
// Remove parenthesised (this computer only) or localised equivalent
string oldName = folder.Name;
int open = oldName.IndexOf('(');
int close = oldName.IndexOf(')');
if (open >= 0 && close >= 0)
{
string newName = oldName.Substring(0, Math.Min(open, close)) + oldName.Substring(Math.Max(open, close) + 1);
newName = newName.Trim();
// Set the new name, and keep the old name in subject in case of a revert
folder.SetProperties(new string[]
{
OutlookConstants.PR_DISPLAY_NAME, OutlookConstants.PR_SUBJECT
}, new object[]
{
newName, oldName
});
}
}
private void UnpatchFolderName(IFolder folder)
{
try
{
string oldName = (string)folder.GetProperty(OutlookConstants.PR_SUBJECT);
// Parentheses are not allowed in names (even though they were there originally)
// Replace with square brackets.
oldName = oldName.Replace('(', '[');
oldName = oldName.Replace(')', ']');
folder.SetProperty(OutlookConstants.PR_DISPLAY_NAME, oldName);
}
catch(System.Exception e)
{
Logger.Instance.Warning(this, "Exception in UnpatchFolderName, leaving name: {0}", e);
}
}
}
}