outlook2gcal 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/CHANGELOG.rdoc +2 -0
- data/MIT-LICENSE +20 -0
- data/README.rdoc +85 -0
- data/bin/build_outlook2gcal_portable.cmd +6 -0
- data/bin/outlook2gcal +73 -0
- data/lib/outlook2gcal/counter.rb +36 -0
- data/lib/outlook2gcal/gcal4ruby_gateway.rb +66 -0
- data/lib/outlook2gcal/google_calendar.rb +45 -0
- data/lib/outlook2gcal/install.rb +86 -0
- data/lib/outlook2gcal/outlook_calendar.rb +234 -0
- data/lib/outlook2gcal/string.rb +24 -0
- data/lib/outlook2gcal/sync.rb +206 -0
- data/lib/outlook2gcal/sync_entry.rb +31 -0
- data/outlook2gcal.gemspec +33 -0
- metadata +208 -0
data/CHANGELOG.rdoc
ADDED
data/MIT-LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2012 Elias Kugler
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.rdoc
ADDED
@@ -0,0 +1,85 @@
|
|
1
|
+
=outlook2gcal
|
2
|
+
|
3
|
+
This app syncs your Outlook calendar with your (private) Google calendar. The synchronisation is only one-way: Outlook events are pushed to your Google Calendar. All types of events (including recurring events like anniversaries) are supported.
|
4
|
+
Note: no admin rights required
|
5
|
+
|
6
|
+
== Alternatives
|
7
|
+
|
8
|
+
GoogleCalendarSync worked fine for me until I had to switch from Windows XP to Windows 7. In Windows 7 GoogleCalendarSync requires admin rights to install it.
|
9
|
+
|
10
|
+
http://support.google.com/calendar/bin/answer.py?hl=en&answer=98563&topic=13948&ctx=topic
|
11
|
+
|
12
|
+
== Installation Instructions
|
13
|
+
|
14
|
+
gem install outlook2gcal
|
15
|
+
|
16
|
+
|
17
|
+
==Usage
|
18
|
+
|
19
|
+
Usage: outlook2gcal <command> <opts>
|
20
|
+
|
21
|
+
Commands: sync
|
22
|
+
|
23
|
+
Specific options:
|
24
|
+
-U, --gmail-username USERNAME Google user name
|
25
|
+
-P, --gmail-password PASSWORD Google user password
|
26
|
+
-C, --gmail-calendar CALENDAR Google calendar (default: 'Outlook'). Note: calendar must exist
|
27
|
+
-D, --days DAYS Do not sync events older then DAYS days
|
28
|
+
--sync-desc Sync event description (default: no)
|
29
|
+
--sync-alarm Sync event alarm notification (default: no)
|
30
|
+
--sync-names Sync attendees (default: no)"
|
31
|
+
|
32
|
+
|
33
|
+
Example:
|
34
|
+
outlook2gcal sync -U username@gmail.com -P 123secret -C Outlook -D 14
|
35
|
+
|
36
|
+
Common options:
|
37
|
+
-?, --help Show this message
|
38
|
+
|
39
|
+
== Prerequisites
|
40
|
+
|
41
|
+
1. Ruby 1.9.1 or later
|
42
|
+
1.1 Sqlite3 installed.
|
43
|
+
A description how to install Sqlite3 on Windows: http://www.skorks.com/2009/08/installing-and-using-sqlite-with-ruby-on-windows/
|
44
|
+
2. Outlook 2007
|
45
|
+
* might work with other versions too
|
46
|
+
3. Google Account
|
47
|
+
* use your gmail-account or create a new one
|
48
|
+
* create an additional calendar for your LotusNotes Calender entries. Name it as you like
|
49
|
+
(i.e. Outlook) but do not use whitespaces or any other special characters.
|
50
|
+
|
51
|
+
== Supported Platforms
|
52
|
+
|
53
|
+
This library is supported on Windows 2000 or later.
|
54
|
+
|
55
|
+
==Used libs:
|
56
|
+
* gcal4ruby (modified version)
|
57
|
+
* win32ole
|
58
|
+
* datamapper
|
59
|
+
* do_sqlite3
|
60
|
+
* log4r
|
61
|
+
...
|
62
|
+
|
63
|
+
|
64
|
+
== Licence/Copyright
|
65
|
+
|
66
|
+
Copyright (c) 2012 Elias Kugler
|
67
|
+
|
68
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
69
|
+
a copy of this software and associated documentation files (the
|
70
|
+
"Software"), to deal in the Software without restriction, including
|
71
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
72
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
73
|
+
permit persons to whom the Software is furnished to do so, subject to
|
74
|
+
the following conditions:
|
75
|
+
|
76
|
+
The above copyright notice and this permission notice shall be
|
77
|
+
included in all copies or substantial portions of the Software.
|
78
|
+
|
79
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
80
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
81
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
82
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
83
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
84
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
85
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/bin/outlook2gcal
ADDED
@@ -0,0 +1,73 @@
|
|
1
|
+
#$:.unshift '../test/mock/win32ole','../test/mock/gcal4ruby','../lib' #<< '../lib'
|
2
|
+
#$:.unshift '../test/mock/win32ole','../lib' #<< '../lib'
|
3
|
+
$:.unshift '../lib' #<< '../lib'
|
4
|
+
|
5
|
+
require 'rubygems'
|
6
|
+
require 'bundler/setup'
|
7
|
+
require 'optparse'
|
8
|
+
require 'outlook2gcal/sync'
|
9
|
+
require 'outlook2gcal/install'
|
10
|
+
|
11
|
+
conf = {}
|
12
|
+
|
13
|
+
opts = OptionParser.new do |opts|
|
14
|
+
opts.banner = "Usage: outlook2gcal <command> <opts> "
|
15
|
+
opts.separator ""
|
16
|
+
opts.separator "Commands: sync"
|
17
|
+
opts.separator ""
|
18
|
+
opts.separator "Specific options:"
|
19
|
+
opts.on("-U", "--gmail-username USERNAME", "Google mail user name") { |gmail_user| conf[:gmail_user]=gmail_user }
|
20
|
+
opts.on("-P", "--gmail-password PASSWORD", "Google mail password") { |gmail_password| conf[:gmail_password]= gmail_password}
|
21
|
+
opts.on("-C", "--gmail-calendar CALENDAR", "Google calendar (default: 'LotusNotes')") { |gmail_calendar|conf[:gmail_calendar]=gmail_calendar }
|
22
|
+
opts.on("-D", "--days DAYS", "Do not sync events older then DAYS days (default: no limit)") { |days| conf[:days]=days.to_i }
|
23
|
+
opts.on("--sync-desc", "Sync event description (default: no)") { |sync_desc| conf[:sync_desc]=sync_desc }
|
24
|
+
opts.on("--sync-alarm", "Sync alarm notification (default: no)") { |sync_alarm| conf[:sync_alarm]= sync_alarm }
|
25
|
+
opts.on("--sync-names", "Sync attendees (default: no)") { |sync_names| conf[:sync_names]=sync_names }
|
26
|
+
|
27
|
+
opts.separator ""
|
28
|
+
opts.separator "Example:"
|
29
|
+
opts.separator " outlook2gcal sync -U username@gmail.com -P top123secret -C Outlook -D 14"
|
30
|
+
opts.separator ""
|
31
|
+
opts.separator "Common options:"
|
32
|
+
|
33
|
+
# No argument, shows at tail. This will print an options summary.
|
34
|
+
opts.on_tail("-?", "--help", "Show this message") do
|
35
|
+
puts opts
|
36
|
+
exit
|
37
|
+
end
|
38
|
+
|
39
|
+
opts.on_tail("-v", "--version", "Show version") do
|
40
|
+
puts "outlook2gcal 0.0.1"
|
41
|
+
exit
|
42
|
+
end
|
43
|
+
|
44
|
+
opts.on_tail "\n(c) 2012 Elias Kugler"
|
45
|
+
|
46
|
+
end
|
47
|
+
|
48
|
+
opts.parse! ARGV
|
49
|
+
|
50
|
+
if defined?Ocra
|
51
|
+
exit
|
52
|
+
else
|
53
|
+
if ARGV.length != 1
|
54
|
+
puts opts
|
55
|
+
exit
|
56
|
+
end
|
57
|
+
conf[:command] = ARGV[0]
|
58
|
+
end
|
59
|
+
|
60
|
+
# --------------------------------------------------
|
61
|
+
#
|
62
|
+
# --------------------------------------------------
|
63
|
+
if conf[:command] == 'sync'
|
64
|
+
olsync = Outlook2GCal::OutlookGoogleSync.new(conf)
|
65
|
+
c = olsync.sync_events
|
66
|
+
c.show # show statistics
|
67
|
+
elsif conf[:command] == 'install'
|
68
|
+
Outlook2GCal::install(conf)
|
69
|
+
exit
|
70
|
+
else
|
71
|
+
puts opts
|
72
|
+
exit
|
73
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
module Outlook2GCal
|
2
|
+
|
3
|
+
class Counter
|
4
|
+
attr_accessor :updates, :inserts, :deletes, :selects, :ignored, :t_start, :t_end
|
5
|
+
|
6
|
+
def initialize
|
7
|
+
@updates = 0
|
8
|
+
@inserts = 0
|
9
|
+
@deletes = 0
|
10
|
+
@selects = 0
|
11
|
+
@ignored = 0
|
12
|
+
start
|
13
|
+
end
|
14
|
+
def runtime
|
15
|
+
@t_end - @t_start
|
16
|
+
end
|
17
|
+
def start
|
18
|
+
@t_start = Time.now
|
19
|
+
end
|
20
|
+
def end
|
21
|
+
@t_end = Time.now
|
22
|
+
end
|
23
|
+
def show
|
24
|
+
puts "\nStatistics:"
|
25
|
+
puts "-"*20
|
26
|
+
puts sprintf("Inserts : %05d", @inserts)
|
27
|
+
puts sprintf("Updates : %05d", @updates)
|
28
|
+
puts sprintf("Deletes : %05d", @deletes)
|
29
|
+
puts sprintf("Ingored : %05d", @ignored)
|
30
|
+
puts "-"*20
|
31
|
+
puts sprintf("Total : %05d", @selects)
|
32
|
+
puts "Runtime : #{runtime} sec"
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
end
|
@@ -0,0 +1,66 @@
|
|
1
|
+
#encoding: utf-8
|
2
|
+
require 'rubygems'
|
3
|
+
require 'gcal4ruby'
|
4
|
+
|
5
|
+
##
|
6
|
+
# This is an extension/modification of the classes
|
7
|
+
# in the gcal4ruby-module
|
8
|
+
#
|
9
|
+
# at the moment it is only used to allaw the usage
|
10
|
+
# of the user-relevant end-date in all-day events
|
11
|
+
#
|
12
|
+
# This module requires a modified version of gcal4ruby (based on 0.5.5)
|
13
|
+
#
|
14
|
+
module GCal4Ruby
|
15
|
+
DAY_SECS = 86400
|
16
|
+
class Event
|
17
|
+
|
18
|
+
#alias :start_time= :start=
|
19
|
+
#alias :start_time :start
|
20
|
+
alias :end_time_orig= :end_time=
|
21
|
+
alias :end_time_orig :end_time
|
22
|
+
|
23
|
+
def all_day=(is_all_day)
|
24
|
+
if self.end_time_orig and !(is_all_day == @all_day)
|
25
|
+
# Not necessary in Outlook (was/is a Lotus Notes issue)!!
|
26
|
+
# is_all_day ? self.end_time_orig=(self.end_time_orig()+DAY_SECS) : self.end_time_orig=(self.end_time_orig-DAY_SECS)
|
27
|
+
end
|
28
|
+
@all_day = is_all_day
|
29
|
+
end
|
30
|
+
##
|
31
|
+
# The event end date and time
|
32
|
+
# For all_day-events it is the day, when
|
33
|
+
# the event ends (and not the day after
|
34
|
+
# as in the google api)
|
35
|
+
#
|
36
|
+
def end_time=(t)
|
37
|
+
tc = convert_to_time(t)
|
38
|
+
# if @all_day
|
39
|
+
# self.end_time_orig = tc + DAY_SECS
|
40
|
+
# else
|
41
|
+
self.end_time_orig = tc
|
42
|
+
# end
|
43
|
+
end
|
44
|
+
#
|
45
|
+
# see end_time=()
|
46
|
+
def end_time
|
47
|
+
# if @all_day
|
48
|
+
# return (self.end_time_orig-DAY_SECS)
|
49
|
+
# else
|
50
|
+
return self.end_time_orig()
|
51
|
+
# end
|
52
|
+
end
|
53
|
+
|
54
|
+
def convert_to_time(t)
|
55
|
+
if t.is_a?String
|
56
|
+
return Time.parse(t)
|
57
|
+
elsif t.is_a?Time
|
58
|
+
return t
|
59
|
+
else
|
60
|
+
raise "Time must be either Time or String"
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
end
|
65
|
+
end
|
66
|
+
# ---
|
@@ -0,0 +1,45 @@
|
|
1
|
+
#encoding: utf-8
|
2
|
+
require 'rubygems'
|
3
|
+
require 'outlook2gcal/gcal4ruby_gateway'
|
4
|
+
|
5
|
+
|
6
|
+
module Outlook2GCal
|
7
|
+
|
8
|
+
class GoogleCalendar
|
9
|
+
attr_accessor :user, :password, :calendar
|
10
|
+
attr_reader :events
|
11
|
+
def initialize(params)
|
12
|
+
@user = params[:gmail_user]
|
13
|
+
@password = params[:gmail_password]
|
14
|
+
@calendar = params[:gmail_calendar] || "LotusNotes"
|
15
|
+
|
16
|
+
@service = GCal4Ruby::Service.new
|
17
|
+
@service.authenticate(@user, @password )
|
18
|
+
@cal = GCal4Ruby::Calendar.find(@service, @calendar, {:scope => :first})[0]
|
19
|
+
|
20
|
+
end
|
21
|
+
def find_event(id)
|
22
|
+
return GCal4Ruby::Event.find(@service, {:id=>id}, {:calendar=>@calendar})
|
23
|
+
end
|
24
|
+
def new_event
|
25
|
+
event = GCal4Ruby::Event.new(@service)
|
26
|
+
event.calendar = @cal
|
27
|
+
event
|
28
|
+
end
|
29
|
+
def del_event(id)
|
30
|
+
begin
|
31
|
+
event = find_event(id)
|
32
|
+
if event
|
33
|
+
return event.delete unless event == []
|
34
|
+
end
|
35
|
+
rescue StandardError => e
|
36
|
+
print 'X'
|
37
|
+
$logger.error DateTime.now.to_s
|
38
|
+
$logger.error id
|
39
|
+
$logger.error e
|
40
|
+
end
|
41
|
+
return false
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
end
|
@@ -0,0 +1,86 @@
|
|
1
|
+
#encoding: utf-8
|
2
|
+
require 'rubygems'
|
3
|
+
require 'log4r'
|
4
|
+
require 'fileutils'
|
5
|
+
|
6
|
+
module Outlook2GCal
|
7
|
+
|
8
|
+
def Outlook2GCal::install(conf)
|
9
|
+
raise "environment variable %APPDATA% not set" unless ENV['APPDATA']
|
10
|
+
puts File.realdirpath(__FILE__+ '/../..')
|
11
|
+
puts File.realdirpath($0+ '/../..')
|
12
|
+
puts "---"
|
13
|
+
|
14
|
+
src_dir = File.realdirpath($0 + '/../..') + '/.'
|
15
|
+
dest_dir = File.realdirpath(ENV['APPDATA']+"/outlook2gcal_standalone_0.0.1")
|
16
|
+
if !File.exists?(dest_dir)
|
17
|
+
copy_files(src_dir,dest_dir)
|
18
|
+
create_licence_file(dest_dir)
|
19
|
+
create_start_file(dest_dir)
|
20
|
+
else
|
21
|
+
puts "Dir (#{dest_dir}) already exists. "
|
22
|
+
end
|
23
|
+
|
24
|
+
end
|
25
|
+
|
26
|
+
def Outlook2GCal::copy_files(src_dir, dest_dir)
|
27
|
+
puts "Copying files from " + src_dir
|
28
|
+
puts "to " + dest_dir
|
29
|
+
FileUtils::mkdir_p(dest_dir)
|
30
|
+
FileUtils::cp_r(src_dir, dest_dir)
|
31
|
+
puts "Install successful!"
|
32
|
+
end
|
33
|
+
def Outlook2GCal::create_licence_file(dest_dir)
|
34
|
+
licence =<<__END__
|
35
|
+
Copyright (c) 2012 Elias Kugler
|
36
|
+
|
37
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
38
|
+
a copy of this software and associated documentation files (the
|
39
|
+
"Software"), to deal in the Software without restriction, including
|
40
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
41
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
42
|
+
permit persons to whom the Software is furnished to do so, subject to
|
43
|
+
the following conditions:
|
44
|
+
|
45
|
+
The above copyright notice and this permission notice shall be
|
46
|
+
included in all copies or substantial portions of the Software.
|
47
|
+
|
48
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
49
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
50
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
51
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
52
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
53
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
54
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
55
|
+
__END__
|
56
|
+
|
57
|
+
f= File.new(dest_dir+"/MIT-LICENSE",'w+')
|
58
|
+
f.write(licence)
|
59
|
+
f.close
|
60
|
+
end
|
61
|
+
|
62
|
+
def Outlook2GCal::create_start_file(dest_dir)
|
63
|
+
cmd =<<__END__
|
64
|
+
@goto :end ;@rem TODO: delete this line, edit the next one !!!
|
65
|
+
.\\bin\\ruby .\\src\\outlook2gcal_portable sync -U <google-user> -P <google-password> -C <calendar-name> -D 30 --sync-desc --sync-alarm
|
66
|
+
@echo.
|
67
|
+
@pause
|
68
|
+
@rem TODO: delete the following lines !!!
|
69
|
+
|
70
|
+
:end
|
71
|
+
@echo off
|
72
|
+
echo --------------------------
|
73
|
+
echo TODO!!! Edit this file!!
|
74
|
+
echo --------------------------
|
75
|
+
.\\bin\\ruby .\\src\\outlook2gcal_portable
|
76
|
+
echo --------------------------
|
77
|
+
echo TODO!!! Edit this file!!
|
78
|
+
echo --------------------------
|
79
|
+
@pause
|
80
|
+
__END__
|
81
|
+
|
82
|
+
f= File.new(dest_dir+"/start_outlook2gcal_portable.bat",'w+')
|
83
|
+
f.write(cmd)
|
84
|
+
f.close
|
85
|
+
end
|
86
|
+
end
|
@@ -0,0 +1,234 @@
|
|
1
|
+
#encoding: utf-8
|
2
|
+
require 'win32ole'
|
3
|
+
|
4
|
+
# Info:
|
5
|
+
# http://msdn.microsoft.com/en-us/library/dd469461(v=office.12).aspx
|
6
|
+
# http://msdn.microsoft.com/en-us/library/ff870661.aspx
|
7
|
+
|
8
|
+
module Outlook2GCal
|
9
|
+
class EventRepeat
|
10
|
+
attr_accessor :start_time, :end_time
|
11
|
+
def initialize
|
12
|
+
end
|
13
|
+
end
|
14
|
+
class OutlookCalendar
|
15
|
+
attr_accessor :server, :user, :password, :db
|
16
|
+
attr_reader :events
|
17
|
+
def initialize(params)
|
18
|
+
@server = params[:notes_server] || '' # local
|
19
|
+
@user = params[:notes_user]
|
20
|
+
@password = params[:notes_password]
|
21
|
+
@db = params[:notes_db]
|
22
|
+
|
23
|
+
created = false
|
24
|
+
@ol = nil
|
25
|
+
begin
|
26
|
+
@ol = WIN32OLE.connect("Outlook.Application")
|
27
|
+
rescue
|
28
|
+
@created = true
|
29
|
+
@ol = WIN32OLE.new("Outlook.Application")
|
30
|
+
end
|
31
|
+
ns = @ol.GetNameSpace("MAPI")
|
32
|
+
folder = ns.GetDefaultFolder(9) #olFolderCalendar
|
33
|
+
raise "olFolderCalendar View not found" unless folder
|
34
|
+
@events = OutlookEvents.new(folder)
|
35
|
+
|
36
|
+
end
|
37
|
+
def quit
|
38
|
+
@ol.Quit if @created # TODO
|
39
|
+
end
|
40
|
+
end
|
41
|
+
class OutlookEvents
|
42
|
+
def initialize(folder)
|
43
|
+
@calendar_items = folder.Items
|
44
|
+
@calendar_items.Sort("[Start]")
|
45
|
+
@calendar_items.IncludeRecurrences = true
|
46
|
+
end
|
47
|
+
def each
|
48
|
+
@calendar_items.each do |event|
|
49
|
+
begin
|
50
|
+
yield OutlookEvent.new(event)
|
51
|
+
rescue StandardError => e
|
52
|
+
print 'X'
|
53
|
+
$logger.error DateTime.now.to_s
|
54
|
+
$logger.error e
|
55
|
+
$logger.error event
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
class OutlookEvent
|
61
|
+
# Note: The value of the minutes can be any arbitrary number of minutes between 5 minutes to 4 weeks.
|
62
|
+
GCAL_MAX_REMINDER = 40320 # 4 Weeks in minutes
|
63
|
+
GCAL_MIN_REMINDER = 5
|
64
|
+
APPOINTMENTTYPE = {'0' =>:appointment,
|
65
|
+
'1' => :anniversary,
|
66
|
+
'2' => :all_day_event,
|
67
|
+
'3' => :meeting,
|
68
|
+
'4' => :reminder}
|
69
|
+
attr_accessor :uid,
|
70
|
+
:subject,
|
71
|
+
:where,
|
72
|
+
:start_time, :end_time,
|
73
|
+
:last_modified, #
|
74
|
+
:appointmenttype,
|
75
|
+
:content,
|
76
|
+
:repeats,
|
77
|
+
:alarm, :alarm_offset,
|
78
|
+
:required_names, :optional_names, :chair # todo ???
|
79
|
+
|
80
|
+
def initialize(outlook_event)
|
81
|
+
fill_alarm(outlook_event) #TODO
|
82
|
+
|
83
|
+
# Notes Id
|
84
|
+
@uid = outlook_event.GlobalAppointmentID
|
85
|
+
|
86
|
+
# Subject
|
87
|
+
if outlook_event.Subject
|
88
|
+
@subject = outlook_event.Subject
|
89
|
+
else
|
90
|
+
@subject = ''
|
91
|
+
$logger.warn 'no subject. uid: '+@uid
|
92
|
+
end
|
93
|
+
|
94
|
+
# Room/Location
|
95
|
+
@where = outlook_event.Location || ''
|
96
|
+
|
97
|
+
# start date + time
|
98
|
+
@start_time = outlook_event.Start.to_s
|
99
|
+
|
100
|
+
# end date + time
|
101
|
+
@end_time = outlook_event.End.to_s
|
102
|
+
|
103
|
+
# event type # TODO -> remove
|
104
|
+
@appointmenttype = APPOINTMENTTYPE['3'] # Meeting
|
105
|
+
|
106
|
+
#p outlook_event.LastModificationTime.to_s
|
107
|
+
@last_modified = DateTime.parse(outlook_event.LastModificationTime.to_s)
|
108
|
+
@content = outlook_event.Body
|
109
|
+
|
110
|
+
# -- neue
|
111
|
+
@all_day_event = outlook_event.AllDayEvent
|
112
|
+
|
113
|
+
# fill_repeats(outlook_event)
|
114
|
+
|
115
|
+
@chair = outlook_event.Organizer
|
116
|
+
@required_names = outlook_event.RequiredAttendees
|
117
|
+
@optional_names = outlook_event.OptionalAttendees
|
118
|
+
end
|
119
|
+
def all_day?
|
120
|
+
@all_day_event
|
121
|
+
end
|
122
|
+
def meeting?
|
123
|
+
true # TODO
|
124
|
+
end
|
125
|
+
|
126
|
+
def supported?
|
127
|
+
# anniversaries are now (v.0.0.7) supported
|
128
|
+
@appointmenttype #and (@appointmenttype != :anniversary)
|
129
|
+
end
|
130
|
+
def repeats?
|
131
|
+
@repeats.size > 1
|
132
|
+
end
|
133
|
+
def fill_alarm(outlook_event)
|
134
|
+
# Alarm
|
135
|
+
@alarm = false
|
136
|
+
@alarm_offset = 0
|
137
|
+
if outlook_event.ReminderSet
|
138
|
+
then
|
139
|
+
@alarm = true
|
140
|
+
@alarm_offset = outlook_event.ReminderMinutesBeforeStart
|
141
|
+
end
|
142
|
+
|
143
|
+
end
|
144
|
+
def fill_repeats(outlook_event)
|
145
|
+
@repeats = []
|
146
|
+
end
|
147
|
+
def formatted_names
|
148
|
+
names = "Chair: #{@chair}\nRequired: "
|
149
|
+
names += (@required_names || "")
|
150
|
+
names += "\nOptional: "+ (@optional_names || "")
|
151
|
+
names += "\n---\n"
|
152
|
+
#p names
|
153
|
+
names
|
154
|
+
end
|
155
|
+
|
156
|
+
|
157
|
+
# obsolete methods -> TODO remove!!
|
158
|
+
def _names_to_str_name_email(names)
|
159
|
+
(names.inject([]) do |x,y|
|
160
|
+
email, name = '', ''
|
161
|
+
email = "<#{y[:email]}>" if (y[:email] and y[:email] != '')
|
162
|
+
name = "\"#{y[:name]}\" " if (y[:name] and y[:name] != '')
|
163
|
+
|
164
|
+
x << name + email
|
165
|
+
x
|
166
|
+
end).join(', ')
|
167
|
+
end
|
168
|
+
|
169
|
+
def _names_to_str(names)
|
170
|
+
(names.inject([]) do |x,y|
|
171
|
+
if y[:email] and y[:email] != ''
|
172
|
+
x << y[:email] if y[:email]
|
173
|
+
elsif y[:name]
|
174
|
+
x << y[:name] if y[:name]
|
175
|
+
end
|
176
|
+
x
|
177
|
+
end).join(', ')
|
178
|
+
end
|
179
|
+
def _all_names
|
180
|
+
names = []
|
181
|
+
names += @chair || []
|
182
|
+
names += @required_names || []
|
183
|
+
names += @optional_names || []
|
184
|
+
|
185
|
+
names.uniq
|
186
|
+
end
|
187
|
+
def _fill_names(outlook_event)
|
188
|
+
@chair = outlook_event.Organizer
|
189
|
+
@required_names = outlook_event.RequiredAttendees
|
190
|
+
@optional_names = outlook_event.OptionalAttendees
|
191
|
+
end
|
192
|
+
def _fill_chair(outlook_event)
|
193
|
+
chair = []
|
194
|
+
chair << outlook_event.Organizer
|
195
|
+
chair
|
196
|
+
end
|
197
|
+
def _find_email(names, idx)
|
198
|
+
email = names[idx]
|
199
|
+
if email
|
200
|
+
email = nil if email == '.'
|
201
|
+
email = nil if email == ''
|
202
|
+
# email =~ /^CN=(.*)\/O=(.*)\/C=(.*)/
|
203
|
+
# email = $1 if $1
|
204
|
+
end
|
205
|
+
return email
|
206
|
+
end
|
207
|
+
|
208
|
+
def _fill_notes_names(outlook_event, notes_attr_name, notes_attr_email = nil)
|
209
|
+
names = []
|
210
|
+
notes_names = []
|
211
|
+
notes_names1 = outlook_event.GetFirstItem(notes_attr_name)
|
212
|
+
if notes_attr_email
|
213
|
+
notes_names2 = outlook_event.GetFirstItem(notes_attr_email)
|
214
|
+
notes_names2 = nil unless (notes_names2 and notes_names2.Values.size == notes_names1.Values.size)
|
215
|
+
end
|
216
|
+
if notes_names1
|
217
|
+
notes_names1.Values.each_with_index do |name, idx|
|
218
|
+
email = find_email(notes_names2.Values, idx) if notes_names2
|
219
|
+
#name =~ /^CN=(.*)\/O=(.*)\/C=(.*)/
|
220
|
+
#email ? short_name = name.split('/')[0] : short_name # use name+domain if email missing
|
221
|
+
short_name = name.split('/')[0]
|
222
|
+
# check if name is an email adress
|
223
|
+
if !email and short_name =~ /\A([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})\Z/i
|
224
|
+
email = short_name
|
225
|
+
short_name = ''
|
226
|
+
end
|
227
|
+
names << {:name => (short_name || ''), :email => (email || '')}
|
228
|
+
end
|
229
|
+
end
|
230
|
+
names
|
231
|
+
end
|
232
|
+
|
233
|
+
end
|
234
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
#encoding: utf-8
|
2
|
+
#
|
3
|
+
class String
|
4
|
+
def asciify()
|
5
|
+
str = self
|
6
|
+
# ----------------------------------------------------------------
|
7
|
+
# Umlauts
|
8
|
+
# ----------------------------------------------------------------
|
9
|
+
=begin
|
10
|
+
str = str.gsub(/\334/,"Ü") #
|
11
|
+
str = str.gsub(/\374/,"ü") #
|
12
|
+
str = str.gsub(/\326/,"Ö") #
|
13
|
+
str = str.gsub(/\366/,"ö") #
|
14
|
+
str = str.gsub(/\304/,"Ä") #
|
15
|
+
str = str.gsub(/\344/,"ä") #
|
16
|
+
str = str.gsub(/\337/,"ß") #
|
17
|
+
str = str.gsub(/>/,">")
|
18
|
+
str = str.gsub(/</,"<")
|
19
|
+
# bez_neu = Iconv.conv('UTF-8','CP850', bez_neu)
|
20
|
+
#str = str.gsub(/([^\d\w\s\.\[\]-])/, '')
|
21
|
+
=end
|
22
|
+
return str.encode('UTF-8')
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,206 @@
|
|
1
|
+
#encoding: utf-8
|
2
|
+
require 'rubygems'
|
3
|
+
require 'log4r'
|
4
|
+
require 'uri'
|
5
|
+
require 'date'
|
6
|
+
require 'fileutils'
|
7
|
+
require 'outlook2gcal/outlook_calendar'
|
8
|
+
require 'outlook2gcal/google_calendar'
|
9
|
+
require 'outlook2gcal/string'
|
10
|
+
require 'outlook2gcal/counter'
|
11
|
+
require 'outlook2gcal/sync_entry'
|
12
|
+
|
13
|
+
|
14
|
+
module Outlook2GCal
|
15
|
+
|
16
|
+
class OutlookGoogleSync
|
17
|
+
def initialize(params)
|
18
|
+
@params = params
|
19
|
+
@google_calendar = Outlook2GCal::GoogleCalendar.new(params)
|
20
|
+
|
21
|
+
@sync_time = nil
|
22
|
+
if params[:days]
|
23
|
+
@min_sync_time = DateTime.now-params[:days] #*86400
|
24
|
+
end
|
25
|
+
if params[:days_max]
|
26
|
+
@max_sync_time = DateTime.now+params[:days_max] #*86400
|
27
|
+
else
|
28
|
+
@max_sync_time = DateTime.now+400 #*86400
|
29
|
+
end
|
30
|
+
@max_time = DateTime.parse("2038-01-18")
|
31
|
+
# do not sync the description unless the users wants to
|
32
|
+
@sync_desc = params[:sync_desc] # || true
|
33
|
+
@sync_alarm = params[:sync_alarm]
|
34
|
+
@sync_names = params[:sync_names]
|
35
|
+
|
36
|
+
init_logger
|
37
|
+
end
|
38
|
+
def init_logger
|
39
|
+
FileUtils::mkdir_p('Outlook2GCal')
|
40
|
+
$logger = Log4r::Logger.new("sync_logger")
|
41
|
+
Log4r::FileOutputter.new('logfile',
|
42
|
+
:filename=>"#{Dir.pwd}/Outlook2GCal/Outlook2GCal.log",
|
43
|
+
:trunc=>false,
|
44
|
+
:level=>Log4r::WARN)
|
45
|
+
$logger.add('logfile')
|
46
|
+
end
|
47
|
+
def sync_events
|
48
|
+
@counter = Counter.new
|
49
|
+
@sync_time = DateTime.now
|
50
|
+
sleep(0)
|
51
|
+
@outlook_calendar = Outlook2GCal::OutlookCalendar.new(@params)
|
52
|
+
@outlook_calendar.events.each do |outlook_event|
|
53
|
+
@counter.selects += 1
|
54
|
+
# if outlook_event.repeats?
|
55
|
+
# outlook_event.repeats.each do |r|
|
56
|
+
# sync_event(outlook_event, r.start_time, r.end_time, outlook_event.uid+'_'+r.start_time)
|
57
|
+
# end
|
58
|
+
# else
|
59
|
+
#p outlook_event.repeats
|
60
|
+
sync_event(outlook_event, outlook_event.start_time, outlook_event.end_time, outlook_event.uid)
|
61
|
+
# end
|
62
|
+
end
|
63
|
+
@outlook_calendar.quit
|
64
|
+
del_events()
|
65
|
+
@counter.end
|
66
|
+
return @counter
|
67
|
+
end
|
68
|
+
|
69
|
+
def sync_event(outlook_event, start_time, end_time, key)
|
70
|
+
sdt = DateTime.parse(start_time)
|
71
|
+
return unless sdt < @max_time # workaround
|
72
|
+
|
73
|
+
if end_time
|
74
|
+
edt = DateTime.parse(end_time)
|
75
|
+
return unless edt < @max_time # workaround
|
76
|
+
end
|
77
|
+
|
78
|
+
#puts DateTime.parse(outlook_event.end_time)
|
79
|
+
|
80
|
+
if (@min_sync_time and end_time and
|
81
|
+
@min_sync_time > DateTime.parse(end_time)) or
|
82
|
+
(@max_sync_time and start_time and
|
83
|
+
@max_sync_time < DateTime.parse(start_time))
|
84
|
+
then
|
85
|
+
@counter.ignored +=1
|
86
|
+
else
|
87
|
+
#p key
|
88
|
+
sync_entry = Outlook2GCal::SyncEntry.first(:outlook_uid => key)
|
89
|
+
if sync_entry
|
90
|
+
then
|
91
|
+
#puts DateTime.parse(outlook_event.end_time)
|
92
|
+
#p sync_entry.outlook_last_modified.to_s
|
93
|
+
#p outlook_event.last_modified
|
94
|
+
if sync_entry.outlook_last_modified < outlook_event.last_modified
|
95
|
+
then
|
96
|
+
#!!insert_update(sync_entry,outlook_event)
|
97
|
+
insert_update(sync_entry,outlook_event, start_time, end_time, key)
|
98
|
+
else
|
99
|
+
print "."
|
100
|
+
@counter.ignored +=1
|
101
|
+
sync_entry.sync_time = @sync_time
|
102
|
+
sync_entry.sync_action = 'N' # none
|
103
|
+
sync_entry.save
|
104
|
+
end
|
105
|
+
else
|
106
|
+
add_event(outlook_event, start_time, end_time, key)
|
107
|
+
end
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
def del_events
|
112
|
+
Outlook2GCal::SyncEntry.all(:sync_time.lt => @sync_time).each do |sync_entry|
|
113
|
+
@counter.deletes += 1
|
114
|
+
if @google_calendar.del_event(sync_entry.gcal_id)
|
115
|
+
print "D"
|
116
|
+
sync_entry.destroy
|
117
|
+
else
|
118
|
+
sync_entry.sync_time = @sync_time
|
119
|
+
sync_entry.sync_action = 'E'
|
120
|
+
sync_entry.save
|
121
|
+
print "E"
|
122
|
+
end
|
123
|
+
end
|
124
|
+
end
|
125
|
+
def init_google_event(outlook_event,start_time,end_time)
|
126
|
+
event = @google_calendar.new_event
|
127
|
+
google_event= set_google_event_attrs(outlook_event, event,start_time,end_time )
|
128
|
+
google_event.start_time = start_time
|
129
|
+
google_event.end_time = end_time
|
130
|
+
return google_event
|
131
|
+
end
|
132
|
+
def set_google_event_attrs(outlook_event, google_event,start_time=nil,end_time=nil)
|
133
|
+
google_event.title = outlook_event.subject.asciify if outlook_event.subject
|
134
|
+
if start_time
|
135
|
+
google_event.start_time = start_time
|
136
|
+
else
|
137
|
+
google_event.start_time = outlook_event.start_time
|
138
|
+
end
|
139
|
+
if end_time
|
140
|
+
google_event.end_time = end_time
|
141
|
+
else
|
142
|
+
google_event.end_time = outlook_event.end_time
|
143
|
+
end
|
144
|
+
google_event.where = outlook_event.where.asciify if outlook_event.where
|
145
|
+
google_event.all_day = outlook_event.all_day?
|
146
|
+
|
147
|
+
if (@sync_desc || @sync_names)
|
148
|
+
content = ''
|
149
|
+
content += outlook_event.formatted_names.asciify if @sync_names
|
150
|
+
content += outlook_event.content.asciify if @sync_desc
|
151
|
+
google_event.content = content
|
152
|
+
#puts content
|
153
|
+
end
|
154
|
+
|
155
|
+
if @sync_alarm and outlook_event.alarm
|
156
|
+
google_event.reminder = [{:method =>'alert', :minutes => outlook_event.alarm_offset }]
|
157
|
+
end
|
158
|
+
|
159
|
+
return google_event
|
160
|
+
end
|
161
|
+
def get_sync_entry_by_notes_uid(uid)
|
162
|
+
e1 = Outlook2GCal::SyncEntry.first(:outlook_uid => uid)
|
163
|
+
return e1
|
164
|
+
end
|
165
|
+
def insert_update(sync_entry,outlook_event, start_time, end_time, key)
|
166
|
+
gcal_event = @google_calendar.find_event(sync_entry.gcal_id)
|
167
|
+
if gcal_event == []
|
168
|
+
$logger.warn "Event not found for update"
|
169
|
+
add_event(outlook_event,start_time, end_time,key)
|
170
|
+
else
|
171
|
+
update_event(sync_entry, outlook_event, gcal_event, start_time, end_time)
|
172
|
+
end
|
173
|
+
end
|
174
|
+
|
175
|
+
def add_event(outlook_event, start_time, end_time, key)
|
176
|
+
print "I"
|
177
|
+
google_event=init_google_event(outlook_event, start_time, end_time)
|
178
|
+
#p google_event
|
179
|
+
ret = google_event.save
|
180
|
+
$logger.fatal "insert: cannot save gcal event" unless ret
|
181
|
+
raise "cannot save gcal event" unless ret
|
182
|
+
@counter.inserts +=1
|
183
|
+
sync_entry = Outlook2GCal::SyncEntry.new
|
184
|
+
sync_entry.outlook_uid = key #outlook_event.uid
|
185
|
+
sync_entry.sync_time = @sync_time
|
186
|
+
sync_entry.outlook_last_modified = outlook_event.last_modified
|
187
|
+
sync_entry.gcal_id = google_event.id
|
188
|
+
sync_entry.sync_action = 'I' # insert
|
189
|
+
sync_entry.save
|
190
|
+
end
|
191
|
+
def update_event(sync_entry, outlook_event, gcal_event, start_time, end_time)
|
192
|
+
print "U"
|
193
|
+
@counter.updates +=1
|
194
|
+
set_google_event_attrs(outlook_event, gcal_event, start_time, end_time)
|
195
|
+
ret = gcal_event.save
|
196
|
+
$logger.fatal "update: cannot save gcal event" unless ret
|
197
|
+
raise "cannot save gcal event" unless ret
|
198
|
+
sync_entry.sync_time = @sync_time
|
199
|
+
sync_entry.gcal_id = gcal_event.id
|
200
|
+
sync_entry.outlook_last_modified = outlook_event.last_modified
|
201
|
+
sync_entry.sync_action = 'U' # none
|
202
|
+
sync_entry.save
|
203
|
+
end
|
204
|
+
end
|
205
|
+
|
206
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
#encoding: utf-8
|
2
|
+
require 'dm-core'
|
3
|
+
require 'dm-migrations'
|
4
|
+
require 'fileutils'
|
5
|
+
|
6
|
+
module Outlook2GCal
|
7
|
+
raise "environment variable %APPDATA% not set" unless ENV['APPDATA']
|
8
|
+
FileUtils::cd(ENV['APPDATA'])
|
9
|
+
FileUtils::mkdir_p('outlook2gcal')
|
10
|
+
|
11
|
+
db_file = "#{Dir.pwd}/outlook2gcal/outlook2gcal.sqlite"
|
12
|
+
DataMapper::setup(:default, "sqlite3://#{db_file}")
|
13
|
+
|
14
|
+
|
15
|
+
class SyncEntry
|
16
|
+
include DataMapper::Resource
|
17
|
+
#storage_names[:repo] = 'ncal2gal_sync_entries'
|
18
|
+
|
19
|
+
property :id, Serial
|
20
|
+
property :sync_time, DateTime
|
21
|
+
property :sync_action, String
|
22
|
+
|
23
|
+
property :outlook_uid, Text, :index=>true
|
24
|
+
property :outlook_last_modified, DateTime
|
25
|
+
property :gcal_id, Text
|
26
|
+
|
27
|
+
end
|
28
|
+
|
29
|
+
# automatically create the SyncEntry table
|
30
|
+
SyncEntry.auto_migrate! unless File.exists?(db_file) #SyncEntry.table_exists?
|
31
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
require "rubygems"
|
2
|
+
|
3
|
+
spec = Gem::Specification.new do |s|
|
4
|
+
s.name = %q{outlook2gcal}
|
5
|
+
s.version = "0.0.1"
|
6
|
+
s.authors = ["Elias Kugler"]
|
7
|
+
s.email = %q{groesser3@gmail.com}
|
8
|
+
s.files = Dir["lib/**/*"] + Dir["bin/**/*"] + Dir["*.rb"] + ["MIT-LICENSE","outlook2gcal.gemspec"]
|
9
|
+
s.platform = Gem::Platform::RUBY
|
10
|
+
s.has_rdoc = true
|
11
|
+
s.extra_rdoc_files = ["README.rdoc", "CHANGELOG.rdoc"]
|
12
|
+
s.require_paths = ["lib"]
|
13
|
+
s.summary = %q{Sync your Outlook calendar with your Google calendar}
|
14
|
+
s.description = %q{This lib/tool syncs your Outlook calendar with your (private) Google calendar. The synchronisation is only one-way: Outlook events are pushed to your Google Calendar. All types of events (including recurring events like anniversaries) are supported.}
|
15
|
+
s.files.reject! { |fn| fn.include? "CVS" }
|
16
|
+
s.require_path = "lib"
|
17
|
+
s.default_executable = %q{outlook2gcal}
|
18
|
+
s.executables = ["outlook2gcal"]
|
19
|
+
s.homepage = %q{http://rubyforge.org/projects/outlook2gcal/}
|
20
|
+
s.rubyforge_project = %q{outlook2gcal}
|
21
|
+
s.add_dependency("dm-core", ">= 1.2.0")
|
22
|
+
s.add_dependency("dm-migrations", ">= 1.2.0")
|
23
|
+
s.add_dependency("dm-do-adapter", ">= 1.2.0")
|
24
|
+
s.add_dependency("dm-sqlite-adapter", ">= 1.2.0")
|
25
|
+
s.add_dependency("do_sqlite3", ">= 0.10.8")
|
26
|
+
s.add_dependency("sqlite3", ">= 1.3.6")
|
27
|
+
s.add_dependency("gdata4ruby", "=0.1.5")
|
28
|
+
s.add_dependency("groesser3-gcal4ruby", "=0.5.51") #!!! you need a modified version of this gem !!!
|
29
|
+
s.add_dependency("log4r", ">=1.1.10")
|
30
|
+
|
31
|
+
end
|
32
|
+
|
33
|
+
|
metadata
ADDED
@@ -0,0 +1,208 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: outlook2gcal
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Elias Kugler
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2012-12-06 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: dm-core
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ! '>='
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: 1.2.0
|
22
|
+
type: :runtime
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ! '>='
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: 1.2.0
|
30
|
+
- !ruby/object:Gem::Dependency
|
31
|
+
name: dm-migrations
|
32
|
+
requirement: !ruby/object:Gem::Requirement
|
33
|
+
none: false
|
34
|
+
requirements:
|
35
|
+
- - ! '>='
|
36
|
+
- !ruby/object:Gem::Version
|
37
|
+
version: 1.2.0
|
38
|
+
type: :runtime
|
39
|
+
prerelease: false
|
40
|
+
version_requirements: !ruby/object:Gem::Requirement
|
41
|
+
none: false
|
42
|
+
requirements:
|
43
|
+
- - ! '>='
|
44
|
+
- !ruby/object:Gem::Version
|
45
|
+
version: 1.2.0
|
46
|
+
- !ruby/object:Gem::Dependency
|
47
|
+
name: dm-do-adapter
|
48
|
+
requirement: !ruby/object:Gem::Requirement
|
49
|
+
none: false
|
50
|
+
requirements:
|
51
|
+
- - ! '>='
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
version: 1.2.0
|
54
|
+
type: :runtime
|
55
|
+
prerelease: false
|
56
|
+
version_requirements: !ruby/object:Gem::Requirement
|
57
|
+
none: false
|
58
|
+
requirements:
|
59
|
+
- - ! '>='
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: 1.2.0
|
62
|
+
- !ruby/object:Gem::Dependency
|
63
|
+
name: dm-sqlite-adapter
|
64
|
+
requirement: !ruby/object:Gem::Requirement
|
65
|
+
none: false
|
66
|
+
requirements:
|
67
|
+
- - ! '>='
|
68
|
+
- !ruby/object:Gem::Version
|
69
|
+
version: 1.2.0
|
70
|
+
type: :runtime
|
71
|
+
prerelease: false
|
72
|
+
version_requirements: !ruby/object:Gem::Requirement
|
73
|
+
none: false
|
74
|
+
requirements:
|
75
|
+
- - ! '>='
|
76
|
+
- !ruby/object:Gem::Version
|
77
|
+
version: 1.2.0
|
78
|
+
- !ruby/object:Gem::Dependency
|
79
|
+
name: do_sqlite3
|
80
|
+
requirement: !ruby/object:Gem::Requirement
|
81
|
+
none: false
|
82
|
+
requirements:
|
83
|
+
- - ! '>='
|
84
|
+
- !ruby/object:Gem::Version
|
85
|
+
version: 0.10.8
|
86
|
+
type: :runtime
|
87
|
+
prerelease: false
|
88
|
+
version_requirements: !ruby/object:Gem::Requirement
|
89
|
+
none: false
|
90
|
+
requirements:
|
91
|
+
- - ! '>='
|
92
|
+
- !ruby/object:Gem::Version
|
93
|
+
version: 0.10.8
|
94
|
+
- !ruby/object:Gem::Dependency
|
95
|
+
name: sqlite3
|
96
|
+
requirement: !ruby/object:Gem::Requirement
|
97
|
+
none: false
|
98
|
+
requirements:
|
99
|
+
- - ! '>='
|
100
|
+
- !ruby/object:Gem::Version
|
101
|
+
version: 1.3.6
|
102
|
+
type: :runtime
|
103
|
+
prerelease: false
|
104
|
+
version_requirements: !ruby/object:Gem::Requirement
|
105
|
+
none: false
|
106
|
+
requirements:
|
107
|
+
- - ! '>='
|
108
|
+
- !ruby/object:Gem::Version
|
109
|
+
version: 1.3.6
|
110
|
+
- !ruby/object:Gem::Dependency
|
111
|
+
name: gdata4ruby
|
112
|
+
requirement: !ruby/object:Gem::Requirement
|
113
|
+
none: false
|
114
|
+
requirements:
|
115
|
+
- - '='
|
116
|
+
- !ruby/object:Gem::Version
|
117
|
+
version: 0.1.5
|
118
|
+
type: :runtime
|
119
|
+
prerelease: false
|
120
|
+
version_requirements: !ruby/object:Gem::Requirement
|
121
|
+
none: false
|
122
|
+
requirements:
|
123
|
+
- - '='
|
124
|
+
- !ruby/object:Gem::Version
|
125
|
+
version: 0.1.5
|
126
|
+
- !ruby/object:Gem::Dependency
|
127
|
+
name: groesser3-gcal4ruby
|
128
|
+
requirement: !ruby/object:Gem::Requirement
|
129
|
+
none: false
|
130
|
+
requirements:
|
131
|
+
- - '='
|
132
|
+
- !ruby/object:Gem::Version
|
133
|
+
version: 0.5.51
|
134
|
+
type: :runtime
|
135
|
+
prerelease: false
|
136
|
+
version_requirements: !ruby/object:Gem::Requirement
|
137
|
+
none: false
|
138
|
+
requirements:
|
139
|
+
- - '='
|
140
|
+
- !ruby/object:Gem::Version
|
141
|
+
version: 0.5.51
|
142
|
+
- !ruby/object:Gem::Dependency
|
143
|
+
name: log4r
|
144
|
+
requirement: !ruby/object:Gem::Requirement
|
145
|
+
none: false
|
146
|
+
requirements:
|
147
|
+
- - ! '>='
|
148
|
+
- !ruby/object:Gem::Version
|
149
|
+
version: 1.1.10
|
150
|
+
type: :runtime
|
151
|
+
prerelease: false
|
152
|
+
version_requirements: !ruby/object:Gem::Requirement
|
153
|
+
none: false
|
154
|
+
requirements:
|
155
|
+
- - ! '>='
|
156
|
+
- !ruby/object:Gem::Version
|
157
|
+
version: 1.1.10
|
158
|
+
description: ! 'This lib/tool syncs your Outlook calendar with your (private) Google
|
159
|
+
calendar. The synchronisation is only one-way: Outlook events are pushed to your
|
160
|
+
Google Calendar. All types of events (including recurring events like anniversaries)
|
161
|
+
are supported.'
|
162
|
+
email: groesser3@gmail.com
|
163
|
+
executables:
|
164
|
+
- outlook2gcal
|
165
|
+
extensions: []
|
166
|
+
extra_rdoc_files:
|
167
|
+
- README.rdoc
|
168
|
+
- CHANGELOG.rdoc
|
169
|
+
files:
|
170
|
+
- lib/outlook2gcal/counter.rb
|
171
|
+
- lib/outlook2gcal/gcal4ruby_gateway.rb
|
172
|
+
- lib/outlook2gcal/google_calendar.rb
|
173
|
+
- lib/outlook2gcal/install.rb
|
174
|
+
- lib/outlook2gcal/outlook_calendar.rb
|
175
|
+
- lib/outlook2gcal/string.rb
|
176
|
+
- lib/outlook2gcal/sync.rb
|
177
|
+
- lib/outlook2gcal/sync_entry.rb
|
178
|
+
- bin/build_outlook2gcal_portable.cmd
|
179
|
+
- bin/outlook2gcal
|
180
|
+
- MIT-LICENSE
|
181
|
+
- outlook2gcal.gemspec
|
182
|
+
- README.rdoc
|
183
|
+
- CHANGELOG.rdoc
|
184
|
+
homepage: http://rubyforge.org/projects/outlook2gcal/
|
185
|
+
licenses: []
|
186
|
+
post_install_message:
|
187
|
+
rdoc_options: []
|
188
|
+
require_paths:
|
189
|
+
- lib
|
190
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
191
|
+
none: false
|
192
|
+
requirements:
|
193
|
+
- - ! '>='
|
194
|
+
- !ruby/object:Gem::Version
|
195
|
+
version: '0'
|
196
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
197
|
+
none: false
|
198
|
+
requirements:
|
199
|
+
- - ! '>='
|
200
|
+
- !ruby/object:Gem::Version
|
201
|
+
version: '0'
|
202
|
+
requirements: []
|
203
|
+
rubyforge_project: outlook2gcal
|
204
|
+
rubygems_version: 1.8.24
|
205
|
+
signing_key:
|
206
|
+
specification_version: 3
|
207
|
+
summary: Sync your Outlook calendar with your Google calendar
|
208
|
+
test_files: []
|