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.
@@ -0,0 +1,2 @@
1
+ === Version 0.0.1
2
+ * Initial version. Code based on ncal2gcal 0.1.7 (c) 2012 Elias Kugler
@@ -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.
@@ -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.
@@ -0,0 +1,6 @@
1
+ rem build ncal2gcal portable using the ocra gem
2
+ rem installing ocra: gem install ocra
3
+ cp outlook2gcal "%appdata%\outlook2gcal_portable"
4
+ cd /d %appdata%
5
+ ocra outlook2gcal_portable
6
+ pause
@@ -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(/>/,"&gt;")
18
+ str = str.gsub(/</,"&lt;")
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: []