wunderlist-rb 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/COPYING ADDED
@@ -0,0 +1,14 @@
1
+ Copyright (C) 2010-2011 by 6 Wunderkinder (6 Wunderkinder GmbH, Germany)
2
+ Copyright (C) 2011 by Fred Emmott (copyright@fredemmott.co.uk)
3
+
4
+ Licensed under the Apache License, Version 2.0 (the "License");
5
+ you may not use this file except in compliance with the License.
6
+ You may obtain a copy of the License at
7
+
8
+ http://www.apache.org/licenses/LICENSE-2.0
9
+
10
+ Unless required by applicable law or agreed to in writing, software
11
+ distributed under the License is distributed on an "AS IS" BASIS,
12
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ See the License for the specific language governing permissions and
14
+ limitations under the License.
@@ -0,0 +1,27 @@
1
+ = Overview
2
+
3
+ This is a *very* rough work in progress. 'Sync' is not yet accurate - it's
4
+ only reliable for a one-off fetch.
5
+
6
+ = Usage
7
+
8
+ cd lib
9
+ irb
10
+ ruby-1.9.2-p180 :001 > email = 'foo'
11
+ => 'foo'
12
+ ruby-1.9.2-p180 :002 > password = 'bar'
13
+ => "bar"
14
+ ruby-1.9.2-p180 :003 > $LOAD_PATH.push('.')
15
+ => [...]
16
+ ruby-1.9.2-p180 :004 > require 'wunderlist'
17
+ => true
18
+ ruby-1.9.2-p180 :005 > wl = Wunderlist::Sync.new(email, password)
19
+ [...]/wunderlist-rb/lib/wunderlist/sync.rb:20: Please set :app_name and :app_version to non-default values.
20
+ => #<Wunderlist::Sync:0x00000000a69e60 [...] >
21
+ ruby-1.9.2-p180 :006 > wl.sync
22
+ => nil
23
+ ruby-1.9.2-p180 :007 > wl.tasks.size
24
+ => 13
25
+ ruby-1.9.2-p180 :008 > wl.lists.size
26
+ => 3
27
+ ruby-1.9.2-p180 :009 >
@@ -0,0 +1,27 @@
1
+ module Wunderlist
2
+ DEFAULTS = {
3
+ :sync_url => 'https://sync.wunderlist.net/1.2.0',
4
+ :app_name => 'wunderlist-rb',
5
+ :app_version => '0.0.1',
6
+ }
7
+
8
+ # *Not* returned as HTTP status codes.
9
+ #
10
+ # These are returned as a 'code' attribute in a JSON object returned by
11
+ # the server.
12
+ module StatusCodes
13
+ SUCCESS = 300
14
+ FAILURE = 301
15
+ DENIED = 302
16
+ NOT_EXIST = 303
17
+ end
18
+
19
+ class Error < RuntimeError; end
20
+ class FailureError < Wunderlist::Error; end
21
+ class DeniedError < Wunderlist::Error; end
22
+ class NotExistError < Wunderlist::Error; end
23
+ end
24
+
25
+ require 'wunderlist/list'
26
+ require 'wunderlist/task'
27
+ require 'wunderlist/sync'
@@ -0,0 +1,65 @@
1
+ module Wunderlist
2
+ class List
3
+ ATTRIBUTES = [
4
+ :deleted,
5
+ :inbox,
6
+ :name,
7
+ :online_id,
8
+ :position,
9
+ :shared,
10
+ :user_id,
11
+ :version,
12
+ ]
13
+ attr_accessor *ATTRIBUTES
14
+ alias :deleted? :deleted
15
+ alias :inbox? :inbox
16
+ alias :shared? :shared
17
+ def online_id?
18
+ online_id && online_id != 0
19
+ end
20
+
21
+ def initialize data = {}
22
+ data.each do |k,v|
23
+ self.send("%s=" % k, v) if ATTRIBUTES.include? k
24
+ end
25
+ self.version ||= 0
26
+ end
27
+
28
+ def hash
29
+ data = Hash.new
30
+ ATTRIBUTES.each do |key|
31
+ data[key] = self.send(key)
32
+ end
33
+ data
34
+ end
35
+
36
+ def sync_data
37
+ data = self.hash
38
+ web_data = Hash.new
39
+ data.each do |k,v|
40
+ case k
41
+ when :deleted, :inbox, :shared
42
+ web_data[k] = v ? 1 : 0
43
+ else
44
+ web_data[k] = v
45
+ end
46
+ end
47
+ web_data
48
+ end
49
+
50
+ def self.from_sync_data web_data
51
+ data = Hash.new
52
+ web_data.each do |k,v|
53
+ case k.to_s
54
+ when 'deleted', 'inbox', 'shared'
55
+ data[k.to_sym] = v != '0'
56
+ when 'name'
57
+ data[:name] = v.to_s
58
+ else
59
+ data[k.to_sym] = v.to_i
60
+ end
61
+ end
62
+ Wunderlist::List.new(data)
63
+ end
64
+ end
65
+ end
@@ -0,0 +1,152 @@
1
+ require 'curb'
2
+ require 'json'
3
+ require 'openssl'
4
+ require 'uri'
5
+
6
+ module Wunderlist
7
+ # Strongly based on js/backend/wunderlist.sync.js
8
+ #
9
+ # This is where the 'steps' come from.
10
+ class Sync
11
+ # Debug only, may be removed without warning
12
+ attr_reader :step_1_response
13
+ attr_reader :user_id
14
+
15
+ def initialize email, password, options = {}
16
+ @options = Wunderlist::DEFAULTS.merge(options)
17
+
18
+ if @options[:app_name] == Wunderlist::DEFAULTS[:app_name]
19
+ STDERR.write "%s:%d: %s\n" % [
20
+ File.expand_path(__FILE__), __LINE__,
21
+ 'Please set :app_name and :app_version to non-default values.'
22
+ ]
23
+ end
24
+
25
+ @email = email
26
+ if options[:hashed_password]
27
+ @password_md5 = password
28
+ else
29
+ @password_md5 = OpenSSL::Digest.hexdigest('md5', password)
30
+ end
31
+ end
32
+
33
+ def sync
34
+ # Sends:
35
+ # - Tasks & lists: already synced, id & version
36
+ # - TODO: New lists
37
+ # Receives:
38
+ # - New and updated tasks
39
+ # - New and updated lists
40
+ # - TODO: delete_tasks
41
+ # - Current user id
42
+ run_step_1
43
+ # Process step 1 data
44
+ run_step_2
45
+ nil
46
+ end
47
+
48
+ def lists
49
+ @lists ||= Array.new
50
+ end
51
+
52
+ def tasks
53
+ @tasks ||= Array.new
54
+ end
55
+ attr_writer :lists, :tasks
56
+
57
+ protected
58
+ def run_step_1
59
+ data = {
60
+ :step => 1,
61
+ :sync_table => {}
62
+ }
63
+
64
+ data[:sync_table][:lists] = lists.select do |list|
65
+ list.online_id?
66
+ end.map do |list|
67
+ {
68
+ :online_id => list.online_id,
69
+ :version => list.version,
70
+ }
71
+ end
72
+
73
+ [:lists, :tasks].each do |table|
74
+ data[:sync_table][table] = web_data_list(
75
+ self.send(table),
76
+ [:online_id, :version]
77
+ ){|it| it.online_id?}
78
+ end
79
+
80
+ response = make_call(data)
81
+ @step_1_response = response
82
+ end
83
+
84
+ # Just process the data we received in step 1
85
+ def run_step_2
86
+ response = @step_1_response
87
+ @user_id = response['user_id'].to_i
88
+ # FIXME: sync_table (and subs) might not be defined
89
+ # FIXME: need to merge; this incldues updated lists + tasks too
90
+ response['sync_table']['new_lists'].each do |list|
91
+ lists.push Wunderlist::List.from_sync_data(list)
92
+ end
93
+ response['sync_table']['new_tasks'].each do |task|
94
+ tasks.push Wunderlist::Task.from_sync_data(task)
95
+ end
96
+ end
97
+
98
+ def make_call data
99
+ post_data = URI.encode_www_form(formify_keys({
100
+ :email => @email,
101
+ :password => @password_md5,
102
+ :device => @options[:app_name],
103
+ :version => @options[:app_version],
104
+ }.merge(data)))
105
+
106
+ response = JSON.parse(
107
+ Curl::Easy.http_post(
108
+ @options[:sync_url],
109
+ post_data
110
+ ).body_str
111
+ )
112
+ case response['code'].to_i
113
+ when Wunderlist::StatusCodes::SUCCESS
114
+ response
115
+ when Wunderlist::StatusCodes::FAILURE
116
+ raise FailureError.new(response)
117
+ when Wunderlist::StatusCodes::DENIED
118
+ raise DeniedError.new(response)
119
+ when Wunderlist::StatusCodes::NOT_EXIST
120
+ raise NotExistError.new(response)
121
+ end
122
+ end
123
+
124
+ private
125
+ def web_data_list values, keys = nil, &test
126
+ values.select{|x| test.call(x)}.map do |x|
127
+ if keys
128
+ test.sync_data.select{|k,v| keys.include? k}
129
+ else
130
+ test.sync_data
131
+ end
132
+ end
133
+ end
134
+
135
+ def formify_keys data_in, pattern = '%s'
136
+ data_out = Hash.new
137
+ data_in.each do |k,v|
138
+ unless v.is_a? Hash
139
+ data_out[pattern % k] = v
140
+ else
141
+ data_out.merge!(
142
+ formify_keys(
143
+ v,
144
+ "%s[%%s]" % [pattern % k]
145
+ )
146
+ )
147
+ end
148
+ end
149
+ data_out
150
+ end
151
+ end
152
+ end
@@ -0,0 +1,76 @@
1
+ module Wunderlist
2
+ class Task
3
+ ATTRIBUTES = [
4
+ :date,
5
+ :deleted,
6
+ :done,
7
+ :done_date,
8
+ :important,
9
+ :list_id,
10
+ :name,
11
+ :note,
12
+ :online_id,
13
+ :position,
14
+ :push,
15
+ :push_ts,
16
+ :user_id,
17
+ :version,
18
+ ]
19
+ attr_accessor *ATTRIBUTES
20
+ alias :deleted? :deleted
21
+ alias :done? :done
22
+ alias :important? :important
23
+
24
+ def online_id?
25
+ online_id && online_id != 0
26
+ end
27
+
28
+ def initialize data = {}
29
+ data.each do |k,v|
30
+ self.send("%s=" % k, v) if ATTRIBUTES.include? k
31
+ end
32
+ self.version ||= 0
33
+ end
34
+
35
+ def hash
36
+ data = Hash.new
37
+ ATTRIBUTES.each do |key|
38
+ data[key] = self.send(key)
39
+ end
40
+ data
41
+ end
42
+
43
+ def sync_data
44
+ data = self.hash
45
+ web_data = Hash.new
46
+ data.each do |k,v|
47
+ case k
48
+ when :deleted, :done, :important, :shared
49
+ web_data[k] = v ? 1 : 0
50
+ when :done_date
51
+ web_data[k] = v ? v.to_i : 0
52
+ else
53
+ web_data[k] = v
54
+ end
55
+ end
56
+ web_data
57
+ end
58
+
59
+ def self.from_sync_data web_data
60
+ data = Hash.new
61
+ web_data.each do |k,v|
62
+ case k.to_s
63
+ when 'deleted', 'done', 'important', 'shared'
64
+ data[k.to_sym] = v != '0'
65
+ when 'done_date'
66
+ data[k.to_sym] = v == '0' ? nil : Time.at(v.to_i)
67
+ when 'name', 'note'
68
+ data[k.to_sym] = v.to_s
69
+ else
70
+ data[k.to_sym] = v.to_i
71
+ end
72
+ end
73
+ Wunderlist::Task.new(data)
74
+ end
75
+ end
76
+ end
metadata ADDED
@@ -0,0 +1,70 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: wunderlist-rb
3
+ version: !ruby/object:Gem::Version
4
+ prerelease:
5
+ version: 0.0.1
6
+ platform: ruby
7
+ authors:
8
+ - Fred Emmott
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+
13
+ date: 2011-08-23 00:00:00 Z
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: curb
17
+ prerelease: false
18
+ requirement: &id001 !ruby/object:Gem::Requirement
19
+ none: false
20
+ requirements:
21
+ - - ">="
22
+ - !ruby/object:Gem::Version
23
+ version: "0"
24
+ type: :runtime
25
+ version_requirements: *id001
26
+ description: I'll find something to put here
27
+ email:
28
+ - gems@fredemmott.co.uk
29
+ executables: []
30
+
31
+ extensions: []
32
+
33
+ extra_rdoc_files: []
34
+
35
+ files:
36
+ - COPYING
37
+ - README.rdoc
38
+ - lib/wunderlist.rb
39
+ - lib/wunderlist/sync.rb
40
+ - lib/wunderlist/task.rb
41
+ - lib/wunderlist/list.rb
42
+ homepage: https://github.com/fredemmott/wunderlist-rb
43
+ licenses: []
44
+
45
+ post_install_message:
46
+ rdoc_options: []
47
+
48
+ require_paths:
49
+ - lib
50
+ required_ruby_version: !ruby/object:Gem::Requirement
51
+ none: false
52
+ requirements:
53
+ - - ">="
54
+ - !ruby/object:Gem::Version
55
+ version: "0"
56
+ required_rubygems_version: !ruby/object:Gem::Requirement
57
+ none: false
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: "0"
62
+ requirements: []
63
+
64
+ rubyforge_project:
65
+ rubygems_version: 1.8.6
66
+ signing_key:
67
+ specification_version: 3
68
+ summary: WIP gem for interacting with Wunderlist
69
+ test_files: []
70
+