i2gcal 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: e816956ee9ffd5bbeecc454dfa1f9141d285e38c
4
+ data.tar.gz: 26adbf650a050e5e4f5e97c62480700480b332bc
5
+ SHA512:
6
+ metadata.gz: a06fe005b4292998eeee5eea41f3bac0456c58e4dff15c0388297753ea6f4214832b4529752d6bfdc4d6127976e872b0d5e45bf38c055e2acd7e4f5637613ebb
7
+ data.tar.gz: c21768e42977f4ad315dacb18bb78ddd97925af866e954ec10974f4e7f2752fb4a3243df86a58d975bdc00bcec7c436c40ff83255fd6543178d61970a55a2bdc
@@ -0,0 +1,3 @@
1
+ module I2GCal
2
+ VERSION = '0.0.1'.freeze
3
+ end
data/lib/i2gcal.rb ADDED
@@ -0,0 +1 @@
1
+ require 'synchrograph'
@@ -0,0 +1,24 @@
1
+ require 'google/api_client'
2
+
3
+ class APIClientBuilder
4
+
5
+ def self.new_client(client_secrets)
6
+ client = Google::APIClient.new(application_name:'stupidpupil_icalendar', application_version:'0.0.1')
7
+ client.authorization.scope = 'https://www.googleapis.com/auth/calendar'
8
+
9
+ client.authorization.client_id = client_secrets.client_id
10
+ client.authorization.client_secret = client_secrets.client_secret
11
+ client.authorization.redirect_uri = client_secrets.redirect_uris.first
12
+
13
+ return client
14
+ end
15
+
16
+ def self.new_client_with_refresh_token(refresh_token, client_secrets)
17
+ client = new_client(client_secrets)
18
+ client.authorization.refresh_token = refresh_token
19
+ client.authorization.fetch_access_token!
20
+
21
+ return client
22
+ end
23
+
24
+ end
@@ -0,0 +1,16 @@
1
+ class BaseFilter
2
+
3
+ def self.call(ical_event, representation)
4
+ {
5
+ 'summary' => ical_event.summary,
6
+ 'start' => ical_event.dtstart.google_calendar_representation,
7
+ 'end' => ical_event.dtend.google_calendar_representation,
8
+ 'location' => ical_event.location,
9
+ 'description' => ical_event.description,
10
+ 'id' => ical_event.google_calendar_id,
11
+ 'status' => 'confirmed',
12
+ 'recurrence' => ical_event.rrule.map {|r| "RRULE:#{r.value_ical}"} + ical_event.rdates + ical_event.exdates
13
+ }.delete_if {|k,v| v.nil? }
14
+ end
15
+
16
+ end
@@ -0,0 +1,14 @@
1
+ class CatToColFilter
2
+
3
+ def initialize(mapping)
4
+ @mapping = mapping
5
+ end
6
+
7
+ def call(ical_event, representation)
8
+ @mapping.each_pair do |cat, col|
9
+ representation['colorId'] = col.to_s if ical_event.categories.any? {|c| c.to_sym == cat}
10
+ end
11
+ representation
12
+ end
13
+
14
+ end
@@ -0,0 +1,130 @@
1
+ require 'synchrograph/filters/base'
2
+ require 'synchrograph/filters/categories_to_colours'
3
+
4
+ # Handles generating fetching the existing list of events in GCal
5
+ # and generating requests to correct this list
6
+ class GCalendar
7
+
8
+ attr_reader :client, :gcal_id
9
+
10
+ def initialize(api_client, gcal_identifier)
11
+ @client = api_client
12
+ @gcal_id = gcal_identifier
13
+ @pending_requests = []
14
+
15
+ #categories = icalendar.first.events.map {|e| e.categories}.flatten.uniq { |u| u.to_s}
16
+ #cat_to_col_mapping = {}
17
+ #categories.each_with_index do |c, i|
18
+ # cat_to_col_mapping[c] = i%10
19
+ #end
20
+
21
+ @filters = [
22
+ BaseFilter,
23
+ CatToColFilter.new({
24
+ "Protected time for Specific Task & Finish Work": 6,
25
+ "Work Related Travel (mark as Out of Office)": 3,
26
+ "Just for Information (mark as Free)": 8,
27
+ "Authorised Leave (annual maternity etc)": 5,
28
+ "Formal Event or Workshop": 10
29
+ })
30
+ ]
31
+
32
+ end
33
+
34
+ def gcal_api
35
+ @gcal_api ||= client.discovered_api('calendar', 'v3')
36
+ end
37
+
38
+ ###
39
+
40
+ def events
41
+ result = client.execute(api_method:gcal_api.events.list, parameters: {'calendarId' => gcal_id, 'showDeleted' => true})
42
+ gcal_events = result.data.items
43
+
44
+ while result.next_page_token
45
+ result = client.execute(result.next_page)
46
+ gcal_events += result.data.items
47
+ end
48
+
49
+ return gcal_events
50
+ end
51
+
52
+ def event_for_id(id)
53
+ result = client.execute(api_method:gcal_api.events.get, parameters: {'calendarId' => gcal_id, 'eventId' => id})
54
+ return result.data
55
+ end
56
+
57
+ def instances_for_event_id(event_id)
58
+ result = client.execute(api_method:gcal_api.events.instances, parameters: {'calendarId' => gcal_id, 'eventId' => event_id})
59
+ gcal_instances = result.data.items
60
+
61
+ while result.next_page_token
62
+ result = client.execute(result.next_page)
63
+ gcal_instances += result.data.items
64
+ end
65
+
66
+ return gcal_instances
67
+ end
68
+
69
+ ###
70
+
71
+ def add_request_for_gcal_event_and_ical_event(gcal_event, ical_event)
72
+ @pending_requests << insert(ical_event) if gcal_event.nil?
73
+ @pending_requests << delete(gcal_event) if ical_event.nil? and gcal_event['recurring_event_id'].nil? and (gcal_event['status'] != 'cancelled')
74
+ @pending_requests << update(gcal_event, ical_event) if gcal_event and ical_event
75
+ end
76
+
77
+ def google_calendar_representation_of_ical_event(ical_event)
78
+ representation = {}
79
+
80
+ @filters.each do |f|
81
+ representation = f.call(ical_event, representation)
82
+ end
83
+
84
+ return representation
85
+ end
86
+
87
+ def insert(ical_event)
88
+ {
89
+ api_method: gcal_api.events.insert,
90
+ headers: {'Content-Type' => 'application/json'},
91
+ parameters: {'calendarId' => gcal_id},
92
+ body:JSON.dump(google_calendar_representation_of_ical_event(ical_event))
93
+ }
94
+ end
95
+
96
+ def delete(gcal_event)
97
+ {
98
+ api_method: gcal_api.events.delete,
99
+ parameters: {'calendarId' => gcal_id, 'eventId' => gcal_event['id']},
100
+ }
101
+ end
102
+
103
+ def update(gcal_event, ical_event)
104
+ new_rep = google_calendar_representation_of_ical_event(ical_event)
105
+
106
+ {
107
+ api_method: gcal_api.events.update,
108
+ headers: {'Content-Type' => 'application/json'},
109
+ parameters: {'calendarId' => gcal_id, 'eventId' => ical_event.google_calendar_id},
110
+ body:JSON.dump(new_rep)
111
+ }
112
+ end
113
+
114
+ ###
115
+
116
+ def execute_requests
117
+ while @pending_requests.any?
118
+ batch = Google::APIClient::BatchRequest.new
119
+
120
+ @pending_requests.slice!(0,950).each do |r|
121
+ batch.add r
122
+ end
123
+
124
+ @pending_requests = []
125
+ return @client.execute(batch)
126
+ end
127
+ end
128
+
129
+
130
+ end
@@ -0,0 +1,30 @@
1
+ module Icalendar
2
+ module Values
3
+ module BetterTimezones
4
+
5
+ regex = /<!-- (\(UTC.+?)\s+-->[\s|$]+<mapZone .+? type="(.+?)"\/>/
6
+ wz_path = File.expand_path('../../../../data/windowsZones.xml', __FILE__)
7
+ wz = File.read(wz_path)
8
+ WINDOWS_TZID_MAP = Hash[wz.scan(regex)]
9
+
10
+ def tzinfo
11
+ return time_zone.tzinfo if self.respond_to? :time_zone
12
+
13
+ tzid = ical_params['tzid']
14
+ tzid = tzid.first if tzid and tzid.any?
15
+
16
+ if tzid
17
+ return TZInfo::Timezone.get(WINDOWS_TZID_MAP[tzid]) if WINDOWS_TZID_MAP.has_key? tzid
18
+ return TZInfo::Timezone.get(tzid) if TZInfo::Timezone.all_identifiers.include? tzid
19
+ end
20
+
21
+ return TZInfo::Timezone.get('Etc/UTC')
22
+ end
23
+
24
+ def value_with_tz
25
+ ActiveSupportTimeWithZoneAdapter.new nil, tzinfo, value
26
+ end
27
+
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,28 @@
1
+ module Icalendar
2
+ module Values
3
+
4
+ class Date
5
+ include BetterTimezones
6
+
7
+ def google_calendar_representation
8
+ {
9
+ 'date' => self.strftime('%F'),
10
+ 'timeZone' => tzinfo.name
11
+ }
12
+ end
13
+
14
+ end
15
+
16
+ class DateTime
17
+ include BetterTimezones
18
+
19
+ def google_calendar_representation
20
+ {
21
+ 'dateTime' => self.strftime('%FT%T'),
22
+ 'timeZone' => tzinfo.name
23
+ }
24
+ end
25
+ end
26
+ end
27
+
28
+ end
@@ -0,0 +1,39 @@
1
+ module Icalendar
2
+ class Event
3
+
4
+ def google_calendar_id=(gcal_id)
5
+ @gcal_id = gcal_id
6
+ end
7
+
8
+ def google_calendar_id
9
+ @gcal_id ||= UUIDTools::UUID.sha1_create(UUID_ICAL_TO_GCAL_NAMESPACE,uid).hexdigest
10
+ end
11
+
12
+ def exdates
13
+ exdate.map {|r| "EXDATE:#{[r].flatten.map {|s| s.value_with_tz.utc.strftime('%Y%m%dT%H%M%S')+'Z'}.join(',')}"}
14
+ end
15
+
16
+ def rdates
17
+ rdate.map {|r| "RDATE:#{[r].flatten.map {|s| s.value_with_tz.utc.strftime('%Y%m%dT%H%M%S')+'Z'}.join(',')}"}
18
+ end
19
+
20
+
21
+
22
+ ###
23
+
24
+ def parent_component
25
+ return nil if recurrence_id.nil?
26
+ @parent_component ||= parent.events.find {|p| p.recurrence_id.nil? and (p.uid == self.uid)}
27
+ end
28
+
29
+ def update_from_parent_component!
30
+ return if parent_component.nil?
31
+
32
+ [:summary, :description, :location, :geo].each do |desc_prop|
33
+ self.send("#{desc_prop}=".to_sym, parent_component.send(desc_prop)) if self.send(desc_prop).nil?
34
+ end
35
+
36
+ end
37
+
38
+ end
39
+ end
@@ -0,0 +1,12 @@
1
+ require 'icalendar'
2
+ require 'uuidtools'
3
+
4
+ module Icalendar
5
+
6
+ UUID_ICAL_TO_GCAL_NAMESPACE = UUIDTools::UUID.parse('88cc3c53-51c9-4550-a469-7aefb4d066dc')
7
+
8
+ end
9
+
10
+ require 'synchrograph/icalendar_monkey_patches/better_timezones'
11
+ require 'synchrograph/icalendar_monkey_patches/dates_and_datetimes'
12
+ require 'synchrograph/icalendar_monkey_patches/event'
@@ -0,0 +1,60 @@
1
+ require 'icalendar'
2
+
3
+ require 'synchrograph/icalendar_monkey_patches'
4
+ require 'synchrograph/api_client_builder'
5
+ require 'synchrograph/gcalendar'
6
+
7
+ # Glues together the API Client, the GCalendar and the ICalendar
8
+ # to create the right requests, in the right order and submit them
9
+ class Synchrograph
10
+
11
+ def initialize(api_client)
12
+ @client = api_client
13
+ end
14
+
15
+
16
+ def synchronise(gcalendar, icalendar)
17
+
18
+ ical_events = icalendar.first.events.find_all {|ie| ie.recurrence_id.nil?}
19
+ gcal_events = gcalendar.events
20
+
21
+ response_body = ""
22
+
23
+ #Insert or update
24
+ ical_events.each do |ie|
25
+ ge = gcal_events.find {|ge| ge['id'] == ie.google_calendar_id}
26
+
27
+ gcalendar.add_request_for_gcal_event_and_ical_event ge, ie
28
+
29
+ end
30
+
31
+ (gcal_events.find_all {|ge| ge['recurring_event_id'].nil? and not ical_events.map {|ie| ie.google_calendar_id}.include?(ge['id'])}).each do |ge|
32
+ gcalendar.add_request_for_gcal_event_and_ical_event ge, nil
33
+ end
34
+
35
+ response_body << gcalendar.execute_requests.body
36
+
37
+ #Now deal with recurrence exceptions
38
+ ical_events = icalendar.first.events.find_all {|ie| not ie.recurrence_id.nil?}
39
+
40
+ ical_events.group_by {|ie| ie.google_calendar_id}.each_pair do |ge_id, re_e|
41
+ gcal_instances = gcalendar.instances_for_event_id ge_id
42
+
43
+ re_e.each do |ie|
44
+ instance = gcal_instances.find {|i| i.start.date_time == ie.recurrence_id}
45
+ next if instance.nil?
46
+
47
+ ie.update_from_parent_component!
48
+
49
+ ie.google_calendar_id = instance.id
50
+ gcalendar.add_request_for_gcal_event_and_ical_event(instance, ie)
51
+ end
52
+ end
53
+
54
+ er = gcalendar.execute_requests
55
+ response_body << er.body if er
56
+
57
+ return response_body
58
+ end
59
+
60
+ end
metadata ADDED
@@ -0,0 +1,125 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: i2gcal
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Adam Watkins
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2016-07-31 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: google-api-client
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - '='
18
+ - !ruby/object:Gem::Version
19
+ version: '0.8'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - '='
25
+ - !ruby/object:Gem::Version
26
+ version: '0.8'
27
+ - !ruby/object:Gem::Dependency
28
+ name: activesupport
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '5.0'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '5.0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: icalendar
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '2.4'
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '2.4'
55
+ - !ruby/object:Gem::Dependency
56
+ name: uuidtools
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '2.1'
62
+ type: :runtime
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '2.1'
69
+ - !ruby/object:Gem::Dependency
70
+ name: bacon
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: '1.2'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: '1.2'
83
+ description:
84
+ email:
85
+ executables: []
86
+ extensions: []
87
+ extra_rdoc_files: []
88
+ files:
89
+ - lib/i2gcal.rb
90
+ - lib/i2gcal/version.rb
91
+ - lib/synchrograph.rb
92
+ - lib/synchrograph/api_client_builder.rb
93
+ - lib/synchrograph/filters/base.rb
94
+ - lib/synchrograph/filters/categories_to_colours.rb
95
+ - lib/synchrograph/gcalendar.rb
96
+ - lib/synchrograph/icalendar_monkey_patches.rb
97
+ - lib/synchrograph/icalendar_monkey_patches/better_timezones.rb
98
+ - lib/synchrograph/icalendar_monkey_patches/dates_and_datetimes.rb
99
+ - lib/synchrograph/icalendar_monkey_patches/event.rb
100
+ homepage: https://github.com/stupidpupil/i2gcal
101
+ licenses:
102
+ - AGPL-3.0
103
+ metadata: {}
104
+ post_install_message:
105
+ rdoc_options: []
106
+ require_paths:
107
+ - lib
108
+ required_ruby_version: !ruby/object:Gem::Requirement
109
+ requirements:
110
+ - - ">="
111
+ - !ruby/object:Gem::Version
112
+ version: '0'
113
+ required_rubygems_version: !ruby/object:Gem::Requirement
114
+ requirements:
115
+ - - ">="
116
+ - !ruby/object:Gem::Version
117
+ version: '0'
118
+ requirements: []
119
+ rubyforge_project:
120
+ rubygems_version: 2.5.1
121
+ signing_key:
122
+ specification_version: 4
123
+ summary: One-way sync an iCalendar file to Google Calendar
124
+ test_files: []
125
+ has_rdoc: