1
0
mirror of https://github.com/aluxnimm/outlookcaldavsynchronizer.git synced 2025-10-05 16:02:49 +02:00

Workaround: Since DDay.iCal is not capable to parse events, which contain unsorted TimeZoneComponents, they must be sorted before parsing.

This commit is contained in:
Gerhard Zehetbauer
2015-09-30 19:18:04 +02:00
parent 8bc571c9ff
commit f644abe3b7
7 changed files with 552 additions and 3 deletions

View File

@@ -32,6 +32,10 @@
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<ItemGroup>
<Reference Include="DDay.iCal, Version=1.0.2.575, Culture=neutral, processorArchitecture=MSIL">
<SpecificVersion>False</SpecificVersion>
<HintPath>..\packages\DDay.iCal.1.0.2.575\lib\DDay.iCal.dll</HintPath>
</Reference>
<Reference Include="Microsoft.Threading.Tasks">
<HintPath>..\packages\Microsoft.Bcl.Async.1.0.168\lib\net40\Microsoft.Threading.Tasks.dll</HintPath>
</Reference>
@@ -66,6 +70,7 @@
<Reference Include="System.Xml" />
</ItemGroup>
<ItemGroup>
<Compile Include="DDayICalWorkaround\CalendarDataPreprocessorFixture.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
</ItemGroup>
<ItemGroup>
@@ -78,9 +83,7 @@
<Name>CalDavSynchronizer</Name>
</ProjectReference>
</ItemGroup>
<ItemGroup>
<Folder Include="Implementation\" />
</ItemGroup>
<ItemGroup />
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<Import Project="..\packages\Microsoft.Bcl.Build.1.0.21\build\Microsoft.Bcl.Build.targets" Condition="Exists('..\packages\Microsoft.Bcl.Build.1.0.21\build\Microsoft.Bcl.Build.targets')" />
<Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">

View File

@@ -0,0 +1,434 @@
using System;
using System.IO;
using CalDavSynchronizer.DDayICalWorkaround;
using DDay.iCal;
using DDay.iCal.Serialization.iCalendar;
using NUnit.Framework;
namespace CalDavSynchronizer.UnitTest.DDayICalWorkaround
{
[TestFixture]
public class CalendarDataPreprocessorFixture
{
[Test]
public void FixTimeZoneComponentOrder_TestRealEvent ()
{
string input = @"BEGIN:VCALENDAR
PRODID:-//bitfire web engineering//DAVdroid 0.8.4.1 (ical4j 2.0-beta1)//EN
VERSION:2.0
BEGIN:VTIMEZONE
TZID:Europe/Vienna
TZURL:http://tzurl.org/zoneinfo/Europe/Vienna
X-LIC-LOCATION:Europe/Vienna
BEGIN:DAYLIGHT
TZOFFSETFROM:+0100
TZOFFSETTO:+0200
TZNAME:CEST
DTSTART:19810329T020000
RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=-1SU
END:DAYLIGHT
BEGIN:STANDARD
TZOFFSETFROM:+0200
TZOFFSETTO:+0100
TZNAME:CET
DTSTART:19961027T030000
RRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=-1SU
END:STANDARD
BEGIN:STANDARD
TZOFFSETFROM:+010521
TZOFFSETTO:+0100
TZNAME:CET
DTSTART:18930401T000000
RDATE:18930401T000000
END:STANDARD
BEGIN:DAYLIGHT
TZOFFSETFROM:+0100
TZOFFSETTO:+0200
TZNAME:CEST
DTSTART:19160501T000000
RDATE:19160501T000000
RDATE:19170416T030000
RDATE:19180415T030000
RDATE:19200405T030000
RDATE:19400401T030000
RDATE:19430329T030000
RDATE:19440403T030000
RDATE:19450402T030000
RDATE:19460414T030000
RDATE:19470406T030000
RDATE:19480418T030000
RDATE:19800406T000000
END:DAYLIGHT
BEGIN:STANDARD
TZOFFSETFROM:+0200
TZOFFSETTO:+0100
TZNAME:CET
DTSTART:19161001T010000
RDATE:19161001T010000
RDATE:19170917T030000
RDATE:19180916T030000
RDATE:19200913T030000
RDATE:19421102T030000
RDATE:19431004T030000
RDATE:19441002T030000
RDATE:19450412T030000
RDATE:19461006T030000
RDATE:19471005T030000
RDATE:19481003T030000
RDATE:19800928T000000
RDATE:19810927T030000
RDATE:19820926T030000
RDATE:19830925T030000
RDATE:19840930T030000
RDATE:19850929T030000
RDATE:19860928T030000
RDATE:19870927T030000
RDATE:19880925T030000
RDATE:19890924T030000
RDATE:19900930T030000
RDATE:19910929T030000
RDATE:19920927T030000
RDATE:19930926T030000
RDATE:19940925T030000
RDATE:19950924T030000
END:STANDARD
BEGIN:STANDARD
TZOFFSETFROM:+0100
TZOFFSETTO:+0100
TZNAME:CET
DTSTART:19200101T000000
RDATE:19200101T000000
RDATE:19460101T000000
RDATE:19810101T000000
END:STANDARD
END:VTIMEZONE
BEGIN:VEVENT
DTSTAMP:20150925T061111Z
UID:566745d3-04ed-497a-9d1b-47db1bb12629
DTSTART;TZID=Europe/Vienna:20151125T133500
DTEND;TZID=Europe/Vienna:20151125T151500
SUMMARY:EZG-ILV EDV_F1.03 - MGS-1
LOCATION:EDV_F1.03
DESCRIPTION:EZG-ILV\nHesinaGe\nMGS-1 \nEDV_F1.03
CLASS:PUBLIC
LAST-MODIFIED:20150927T175324Z
TRANSP:OPAQUE
SEQUENCE:2
END:VEVENT
END:VCALENDAR
";
string expected = @"BEGIN:VCALENDAR
PRODID:-//bitfire web engineering//DAVdroid 0.8.4.1 (ical4j 2.0-beta1)//EN
VERSION:2.0
BEGIN:VTIMEZONE
TZID:Europe/Vienna
TZURL:http://tzurl.org/zoneinfo/Europe/Vienna
X-LIC-LOCATION:Europe/Vienna
BEGIN:STANDARD
TZOFFSETFROM:+010521
TZOFFSETTO:+0100
TZNAME:CET
DTSTART:18930401T000000
RDATE:18930401T000000
END:STANDARD
BEGIN:DAYLIGHT
TZOFFSETFROM:+0100
TZOFFSETTO:+0200
TZNAME:CEST
DTSTART:19160501T000000
RDATE:19160501T000000
RDATE:19170416T030000
RDATE:19180415T030000
RDATE:19200405T030000
RDATE:19400401T030000
RDATE:19430329T030000
RDATE:19440403T030000
RDATE:19450402T030000
RDATE:19460414T030000
RDATE:19470406T030000
RDATE:19480418T030000
RDATE:19800406T000000
END:DAYLIGHT
BEGIN:STANDARD
TZOFFSETFROM:+0200
TZOFFSETTO:+0100
TZNAME:CET
DTSTART:19161001T010000
RDATE:19161001T010000
RDATE:19170917T030000
RDATE:19180916T030000
RDATE:19200913T030000
RDATE:19421102T030000
RDATE:19431004T030000
RDATE:19441002T030000
RDATE:19450412T030000
RDATE:19461006T030000
RDATE:19471005T030000
RDATE:19481003T030000
RDATE:19800928T000000
RDATE:19810927T030000
RDATE:19820926T030000
RDATE:19830925T030000
RDATE:19840930T030000
RDATE:19850929T030000
RDATE:19860928T030000
RDATE:19870927T030000
RDATE:19880925T030000
RDATE:19890924T030000
RDATE:19900930T030000
RDATE:19910929T030000
RDATE:19920927T030000
RDATE:19930926T030000
RDATE:19940925T030000
RDATE:19950924T030000
END:STANDARD
BEGIN:STANDARD
TZOFFSETFROM:+0100
TZOFFSETTO:+0100
TZNAME:CET
DTSTART:19200101T000000
RDATE:19200101T000000
RDATE:19460101T000000
RDATE:19810101T000000
END:STANDARD
BEGIN:DAYLIGHT
TZOFFSETFROM:+0100
TZOFFSETTO:+0200
TZNAME:CEST
DTSTART:19810329T020000
RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=-1SU
END:DAYLIGHT
BEGIN:STANDARD
TZOFFSETFROM:+0200
TZOFFSETTO:+0100
TZNAME:CET
DTSTART:19961027T030000
RRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=-1SU
END:STANDARD
END:VTIMEZONE
BEGIN:VEVENT
DTSTAMP:20150925T061111Z
UID:566745d3-04ed-497a-9d1b-47db1bb12629
DTSTART;TZID=Europe/Vienna:20151125T133500
DTEND;TZID=Europe/Vienna:20151125T151500
SUMMARY:EZG-ILV EDV_F1.03 - MGS-1
LOCATION:EDV_F1.03
DESCRIPTION:EZG-ILV\nHesinaGe\nMGS-1 \nEDV_F1.03
CLASS:PUBLIC
LAST-MODIFIED:20150927T175324Z
TRANSP:OPAQUE
SEQUENCE:2
END:VEVENT
END:VCALENDAR
";
var processed = CalendarDataPreprocessor.FixTimeZoneComponentOrderNoThrow (input);
Assert.That (processed, Is.EqualTo (expected));
AssertCanDeserialize (processed);
}
[Test]
public void FixTimeZoneComponentOrder_EventHasNoTimeZones_DoesntChangeEvent ()
{
string input = @"BEGIN:VCALENDAR
PRODID:-//bitfire web engineering//DAVdroid 0.8.4.1 (ical4j 2.0-beta1)//EN
VERSION:2.0
BEGIN:VEVENT
DTSTAMP:20150925T061111Z
UID:566745d3-04ed-497a-9d1b-47db1bb12629
DTSTART;TZID=Europe/Vienna:20151125T133500
DTEND;TZID=Europe/Vienna:20151125T151500
SUMMARY:EZG-ILV EDV_F1.03 - MGS-1
LOCATION:EDV_F1.03
DESCRIPTION:EZG-ILV\nHesinaGe\nMGS-1 \nEDV_F1.03
CLASS:PUBLIC
LAST-MODIFIED:20150927T175324Z
TRANSP:OPAQUE
SEQUENCE:2
END:VEVENT
END:VCALENDAR
";
var processed = CalendarDataPreprocessor.FixTimeZoneComponentOrderNoThrow (input);
Assert.That (processed, Is.EqualTo (input));
AssertCanDeserialize (processed);
}
[Test]
public void FixTimeZoneComponentOrder_StartTimeOfOneComponentIsNotParseable_IgnoresComnponentAndAddsItAfterSortedConmponents ()
{
string input = @"BEGIN:VCALENDAR
BEGIN:VTIMEZONE
TZID:Europe/Vienna
X-LIC-LOCATION:Europe/Vienna
BEGIN:STANDARD
TZNAME:CET
DTSTART:19961027T030000
END:STANDARD
BEGIN:DAYLIGHT
TZNAME:CEST
DTSTART:19810329T020000
END:DAYLIGHT
BEGIN:STANDARD
TZNAME:CET
DTSTART:19200101T000000
END:STANDARD
BEGIN:DAYLIGHT
TZNAME:CEST
DTSTART:xxxxxxxxxxxxxxxxxxxxxxx
END:DAYLIGHT
BEGIN:STANDARD
TZNAME:CET
DTSTART:19161001T010000
END:STANDARD
BEGIN:STANDARD
TZNAME:CET
DTSTART:18930401T000000
END:STANDARD
END:VTIMEZONE
BEGIN:VEVENT
SUMMARY:Termin
END:VEVENT
END:VCALENDAR
";
string expected = @"BEGIN:VCALENDAR
BEGIN:VTIMEZONE
TZID:Europe/Vienna
X-LIC-LOCATION:Europe/Vienna
BEGIN:STANDARD
TZNAME:CET
DTSTART:18930401T000000
END:STANDARD
BEGIN:STANDARD
TZNAME:CET
DTSTART:19161001T010000
END:STANDARD
BEGIN:STANDARD
TZNAME:CET
DTSTART:19200101T000000
END:STANDARD
BEGIN:DAYLIGHT
TZNAME:CEST
DTSTART:19810329T020000
END:DAYLIGHT
BEGIN:STANDARD
TZNAME:CET
DTSTART:19961027T030000
END:STANDARD
BEGIN:DAYLIGHT
TZNAME:CEST
DTSTART:xxxxxxxxxxxxxxxxxxxxxxx
END:DAYLIGHT
END:VTIMEZONE
BEGIN:VEVENT
SUMMARY:Termin
END:VEVENT
END:VCALENDAR
";
var processed = CalendarDataPreprocessor.FixTimeZoneComponentOrderNoThrow (input);
Assert.That (processed, Is.EqualTo (expected));
}
[Test]
public void FixTimeZoneComponentOrder_EventHasMultipleTimeZomes ()
{
string input = @"BEGIN:VCALENDAR
BEGIN:VTIMEZONE
TZID:Europe/Vienna
X-LIC-LOCATION:Europe/Vienna
BEGIN:STANDARD
TZNAME:CET
DTSTART:19901027T030000
END:STANDARD
BEGIN:DAYLIGHT
TZNAME:CEST
DTSTART:19800329T020000
END:DAYLIGHT
END:VTIMEZONE
BEGIN:VTIMEZONE
TZID:Blablubb
X-LIC-LOCATION:SomewhereElse
BEGIN:STANDARD
TZNAME:CET
DTSTART:19601027T030000
END:STANDARD
BEGIN:DAYLIGHT
TZNAME:CEST
DTSTART:19500329T020000
END:DAYLIGHT
END:VTIMEZONE
BEGIN:VEVENT
SUMMARY:Termin
END:VEVENT
END:VCALENDAR
";
string expected = @"BEGIN:VCALENDAR
BEGIN:VTIMEZONE
TZID:Europe/Vienna
X-LIC-LOCATION:Europe/Vienna
BEGIN:DAYLIGHT
TZNAME:CEST
DTSTART:19800329T020000
END:DAYLIGHT
BEGIN:STANDARD
TZNAME:CET
DTSTART:19901027T030000
END:STANDARD
END:VTIMEZONE
BEGIN:VTIMEZONE
TZID:Blablubb
X-LIC-LOCATION:SomewhereElse
BEGIN:DAYLIGHT
TZNAME:CEST
DTSTART:19500329T020000
END:DAYLIGHT
BEGIN:STANDARD
TZNAME:CET
DTSTART:19601027T030000
END:STANDARD
END:VTIMEZONE
BEGIN:VEVENT
SUMMARY:Termin
END:VEVENT
END:VCALENDAR
";
var processed = CalendarDataPreprocessor.FixTimeZoneComponentOrderNoThrow (input);
Assert.That (processed, Is.EqualTo (expected));
}
[Test]
public void FixTimeZoneComponentOrder_CalenderDataIsNull_ReturnsNull ()
{
Assert.That (CalendarDataPreprocessor.FixTimeZoneComponentOrderNoThrow (null), Is.Null);
}
private static void AssertCanDeserialize (string iCalData)
{
Assert.That (
() => DeserializeICalendar (iCalData),
Throws.Nothing);
}
private static IICalendar DeserializeICalendar (string iCalData)
{
using (var reader = new StringReader (iCalData))
{
var calendarCollection = (iCalendarCollection) new iCalendarSerializer().Deserialize (reader);
return calendarCollection[0];
}
}
}
}

View File

@@ -1,5 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="DDay.iCal" version="1.0.2.575" targetFramework="net45" />
<package id="Microsoft.Bcl" version="1.1.10" targetFramework="net45" />
<package id="Microsoft.Bcl.Async" version="1.0.168" targetFramework="net45" />
<package id="Microsoft.Bcl.Build" version="1.0.21" targetFramework="net45" />

View File

@@ -215,6 +215,7 @@
<Compile Include="DataAccess\ProfileListDataAccess.cs" />
<Compile Include="DataAccess\WebRequestBasedClient\WebHeaderCollectionAdapter.cs" />
<Compile Include="DataAccess\WebRequestBasedClient\WebDavClient.cs" />
<Compile Include="DDayICalWorkaround\CalendarDataPreprocessor.cs" />
<Compile Include="Implementation\ComWrappers\TaskItemWrapper.cs" />
<Compile Include="Implementation\ComWrappers\AppointmentItemWrapper.cs" />
<Compile Include="Implementation\ComWrappers\ComEnumerableExtensions.cs" />

View File

@@ -0,0 +1,96 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using log4net;
namespace CalDavSynchronizer.DDayICalWorkaround
{
public static class CalendarDataPreprocessor
{
private static readonly ILog s_logger = LogManager.GetLogger (MethodInfo.GetCurrentMethod().DeclaringType);
public static string FixTimeZoneComponentOrderNoThrow (string iCalenderData)
{
if (string.IsNullOrEmpty (iCalenderData))
return iCalenderData;
try
{
var newCalenderData = iCalenderData;
for (
var timeZoneMatch = Regex.Match (iCalenderData, "BEGIN:VTIMEZONE\r\n(.|\n)*?END:VTIMEZONE\r\n", RegexOptions.RightToLeft);
timeZoneMatch.Success;
timeZoneMatch = timeZoneMatch.NextMatch())
{
var sections = new List<Tuple<Match, DateTime>>();
for (
var sectionMatch = Regex.Match (timeZoneMatch.Value, "BEGIN:STANDARD\r\n(.|\n)*?END:STANDARD\r\n", RegexOptions.RightToLeft);
sectionMatch.Success;
sectionMatch = sectionMatch.NextMatch())
{
var startMatch = Regex.Match (sectionMatch.Value, "DTSTART:(.*?)\r\n");
if (startMatch.Success)
{
DateTime date;
if (DateTime.TryParseExact (startMatch.Groups[1].Value, "yyyyMMdd'T'HHmmss", CultureInfo.InvariantCulture, DateTimeStyles.None, out date))
{
sections.Add (Tuple.Create (sectionMatch, date));
}
}
}
for (
var sectionMatch = Regex.Match (timeZoneMatch.Value, "BEGIN:DAYLIGHT\r\n(.|\n)*?END:DAYLIGHT\r\n", RegexOptions.RightToLeft);
sectionMatch.Success;
sectionMatch = sectionMatch.NextMatch())
{
var startMatch = Regex.Match (sectionMatch.Value, "DTSTART:(.*?)\r\n");
if (startMatch.Success)
{
DateTime date;
if (DateTime.TryParseExact (startMatch.Groups[1].Value, "yyyyMMdd'T'HHmmss", CultureInfo.InvariantCulture, DateTimeStyles.None, out date))
{
sections.Add (Tuple.Create (sectionMatch, date));
}
}
}
if (sections.Count > 0)
{
Match firstSection = null;
var newTimeZoneData = timeZoneMatch.Value;
foreach (var section in sections.OrderByDescending (s => s.Item1.Index))
{
newTimeZoneData = newTimeZoneData.Remove (section.Item1.Index, section.Item1.Length);
firstSection = section.Item1;
}
foreach (var section in sections.OrderByDescending (s => s.Item2))
{
newTimeZoneData = newTimeZoneData.Insert (firstSection.Index, section.Item1.Value);
}
newCalenderData = newCalenderData.Remove (timeZoneMatch.Index, timeZoneMatch.Length);
newCalenderData = newCalenderData.Insert (timeZoneMatch.Index, newTimeZoneData);
}
}
return newCalenderData;
}
catch (Exception x)
{
s_logger.Error ("Could not process calender data. Using original data", x);
return iCalenderData;
}
}
}
}

View File

@@ -20,6 +20,7 @@ using System.IO;
using System.Reflection;
using System.Threading.Tasks;
using CalDavSynchronizer.DataAccess;
using CalDavSynchronizer.DDayICalWorkaround;
using CalDavSynchronizer.Diagnostics;
using CalDavSynchronizer.Implementation.TimeRangeFiltering;
using DDay.iCal;
@@ -102,7 +103,19 @@ namespace CalDavSynchronizer.Implementation
IICalendar calendar;
if (TryDeserializeCalendar (serialized.Entity, out calendar, serialized.Id, threadLocal.Item1))
{
threadLocal.Item2.Add (Tuple.Create (serialized.Id, calendar));
}
else
{
// maybe deserialization failed because of the iCal-TimeZone-Bug => try to fix it
var fixedICalData = CalendarDataPreprocessor.FixTimeZoneComponentOrderNoThrow (serialized.Entity);
if (TryDeserializeCalendar (fixedICalData, out calendar, serialized.Id, threadLocal.Item1))
{
threadLocal.Item2.Add (Tuple.Create (serialized.Id, calendar));
}
}
return threadLocal;
},
threadLocal =>

View File

@@ -5,6 +5,7 @@
<repository path="..\CalDavSynchronizer.UnitTest\packages.config" />
<repository path="..\CalDavSynchronizer\packages.config" />
<repository path="..\CalDavSynchronizerTestAutomation\packages.config" />
<repository path="..\ConsoleApplication1\packages.config" />
<repository path="..\GenSync.UnitTests\packages.config" />
<repository path="..\GenSync\packages.config" />
<repository path="..\TestRunner\packages.config" />