TokiCLI 0.2.1 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (129) hide show
  1. checksums.yaml +4 -4
  2. data/.coveralls.yml +1 -0
  3. data/.gitignore +9 -17
  4. data/.rspec +3 -0
  5. data/.travis.yml +3 -0
  6. data/CHANGELOG.md +4 -0
  7. data/README.md +99 -163
  8. data/Rakefile +5 -0
  9. data/TokiCLI.gemspec +5 -4
  10. data/bin/toki +2 -2
  11. data/lib/API/helpers.rb +27 -170
  12. data/lib/API/toki_api.rb +227 -0
  13. data/lib/API/toki_db.rb +67 -0
  14. data/lib/TokiCLI.rb +270 -5
  15. data/lib/TokiCLI/adnimport.rb +202 -0
  16. data/lib/TokiCLI/fileops.rb +187 -0
  17. data/lib/TokiCLI/status.rb +49 -37
  18. data/lib/TokiCLI/version.rb +1 -1
  19. data/lib/TokiCLI/view.rb +133 -86
  20. data/lib/TokiServer/.bowerrc +3 -0
  21. data/lib/TokiServer/.gitignore +2 -0
  22. data/lib/TokiServer/Gemfile +1 -0
  23. data/lib/TokiServer/bower.json +0 -0
  24. data/lib/TokiServer/bower_components/fastclick/.bower.json +4 -4
  25. data/lib/TokiServer/bower_components/fastclick/bower.json +1 -1
  26. data/lib/TokiServer/bower_components/fastclick/lib/fastclick.js +33 -2
  27. data/lib/TokiServer/bower_components/foundation/.bower.json +5 -4
  28. data/lib/TokiServer/bower_components/foundation/bower.json +2 -1
  29. data/lib/TokiServer/bower_components/foundation/css/foundation.css +2173 -922
  30. data/lib/TokiServer/bower_components/foundation/css/foundation.css.map +7 -0
  31. data/lib/TokiServer/bower_components/foundation/css/normalize.css +53 -16
  32. data/lib/TokiServer/bower_components/foundation/css/normalize.css.map +7 -0
  33. data/lib/TokiServer/bower_components/foundation/js/foundation.js +782 -542
  34. data/lib/TokiServer/bower_components/foundation/js/foundation.min.js +4 -3
  35. data/lib/TokiServer/bower_components/foundation/js/foundation/foundation.abide.js +45 -31
  36. data/lib/TokiServer/bower_components/foundation/js/foundation/foundation.accordion.js +12 -6
  37. data/lib/TokiServer/bower_components/foundation/js/foundation/foundation.alert.js +5 -5
  38. data/lib/TokiServer/bower_components/foundation/js/foundation/foundation.clearing.js +34 -10
  39. data/lib/TokiServer/bower_components/foundation/js/foundation/foundation.dropdown.js +83 -29
  40. data/lib/TokiServer/bower_components/foundation/js/foundation/foundation.equalizer.js +3 -3
  41. data/lib/TokiServer/bower_components/foundation/js/foundation/foundation.interchange.js +25 -12
  42. data/lib/TokiServer/bower_components/foundation/js/foundation/foundation.joyride.js +112 -40
  43. data/lib/TokiServer/bower_components/foundation/js/foundation/foundation.js +19 -5
  44. data/lib/TokiServer/bower_components/foundation/js/foundation/foundation.magellan.js +22 -11
  45. data/lib/TokiServer/bower_components/foundation/js/foundation/foundation.offcanvas.js +52 -8
  46. data/lib/TokiServer/bower_components/foundation/js/foundation/foundation.orbit.js +133 -271
  47. data/lib/TokiServer/bower_components/foundation/js/foundation/foundation.reveal.js +27 -20
  48. data/lib/TokiServer/bower_components/foundation/js/foundation/foundation.slider.js +73 -33
  49. data/lib/TokiServer/bower_components/foundation/js/foundation/foundation.tab.js +88 -31
  50. data/lib/TokiServer/bower_components/foundation/js/foundation/foundation.tooltip.js +7 -5
  51. data/lib/TokiServer/bower_components/foundation/js/foundation/foundation.topbar.js +44 -24
  52. data/lib/TokiServer/bower_components/foundation/js/vendor/fastclick.js +2 -2
  53. data/lib/TokiServer/bower_components/foundation/js/vendor/modernizr.js +2 -2
  54. data/lib/TokiServer/bower_components/foundation/scss/foundation.scss +38 -38
  55. data/lib/TokiServer/bower_components/foundation/scss/foundation/_functions.scss +3 -3
  56. data/lib/TokiServer/bower_components/foundation/scss/foundation/_settings.scss +417 -271
  57. data/lib/TokiServer/bower_components/foundation/scss/foundation/components/_accordion.scss +110 -6
  58. data/lib/TokiServer/bower_components/foundation/scss/foundation/components/_alert-boxes.scss +2 -2
  59. data/lib/TokiServer/bower_components/foundation/scss/foundation/components/_block-grid.scss +2 -2
  60. data/lib/TokiServer/bower_components/foundation/scss/foundation/components/_breadcrumbs.scss +8 -3
  61. data/lib/TokiServer/bower_components/foundation/scss/foundation/components/_button-groups.scss +99 -9
  62. data/lib/TokiServer/bower_components/foundation/scss/foundation/components/_buttons.scss +66 -28
  63. data/lib/TokiServer/bower_components/foundation/scss/foundation/components/_clearing.scss +5 -5
  64. data/lib/TokiServer/bower_components/foundation/scss/foundation/components/_dropdown-buttons.scss +4 -4
  65. data/lib/TokiServer/bower_components/foundation/scss/foundation/components/_dropdown.scss +48 -35
  66. data/lib/TokiServer/bower_components/foundation/scss/foundation/components/_forms.scss +104 -32
  67. data/lib/TokiServer/bower_components/foundation/scss/foundation/components/_global.scss +48 -30
  68. data/lib/TokiServer/bower_components/foundation/scss/foundation/components/_grid.scss +19 -4
  69. data/lib/TokiServer/bower_components/foundation/scss/foundation/components/_icon-bar.scss +293 -0
  70. data/lib/TokiServer/bower_components/foundation/scss/foundation/components/_joyride.scss +11 -9
  71. data/lib/TokiServer/bower_components/foundation/scss/foundation/components/_keystrokes.scss +4 -4
  72. data/lib/TokiServer/bower_components/foundation/scss/foundation/components/_labels.scss +4 -2
  73. data/lib/TokiServer/bower_components/foundation/scss/foundation/components/_magellan.scss +1 -1
  74. data/lib/TokiServer/bower_components/foundation/scss/foundation/components/_offcanvas.scss +193 -35
  75. data/lib/TokiServer/bower_components/foundation/scss/foundation/components/_orbit.scss +92 -147
  76. data/lib/TokiServer/bower_components/foundation/scss/foundation/components/_pagination.scss +22 -10
  77. data/lib/TokiServer/bower_components/foundation/scss/foundation/components/_panels.scss +10 -7
  78. data/lib/TokiServer/bower_components/foundation/scss/foundation/components/_pricing-tables.scss +11 -11
  79. data/lib/TokiServer/bower_components/foundation/scss/foundation/components/_progress-bars.scss +2 -2
  80. data/lib/TokiServer/bower_components/foundation/scss/foundation/components/_range-slider.scss +29 -9
  81. data/lib/TokiServer/bower_components/foundation/scss/foundation/components/_reveal.scss +60 -56
  82. data/lib/TokiServer/bower_components/foundation/scss/foundation/components/_side-nav.scss +3 -2
  83. data/lib/TokiServer/bower_components/foundation/scss/foundation/components/_split-buttons.scss +2 -2
  84. data/lib/TokiServer/bower_components/foundation/scss/foundation/components/_sub-nav.scss +2 -2
  85. data/lib/TokiServer/bower_components/foundation/scss/foundation/components/_switches.scss +226 -0
  86. data/lib/TokiServer/bower_components/foundation/scss/foundation/components/_tables.scss +13 -7
  87. data/lib/TokiServer/bower_components/foundation/scss/foundation/components/_tabs.scss +22 -8
  88. data/lib/TokiServer/bower_components/foundation/scss/foundation/components/_thumbs.scss +2 -4
  89. data/lib/TokiServer/bower_components/foundation/scss/foundation/components/_toolbar.scss +70 -0
  90. data/lib/TokiServer/bower_components/foundation/scss/foundation/components/_tooltips.scss +9 -7
  91. data/lib/TokiServer/bower_components/foundation/scss/foundation/components/_top-bar.scss +77 -44
  92. data/lib/TokiServer/bower_components/foundation/scss/foundation/components/_type.scss +21 -9
  93. data/lib/TokiServer/bower_components/modernizr/.bower.json +4 -4
  94. data/lib/TokiServer/bower_components/modernizr/feature-detects/workers-blobworkers.js +2 -2
  95. data/lib/TokiServer/bower_components/modernizr/grunt.js +1 -1
  96. data/lib/TokiServer/bower_components/modernizr/modernizr.js +2 -2
  97. data/lib/TokiServer/config.rb +0 -0
  98. data/lib/TokiServer/humans.txt +0 -0
  99. data/lib/TokiServer/{itunesicon.rb → itunesicons.rb} +25 -23
  100. data/lib/TokiServer/js/app.js +0 -0
  101. data/lib/TokiServer/public/stylesheets/app.css +2636 -1610
  102. data/lib/TokiServer/robots.txt +0 -0
  103. data/lib/TokiServer/scss/_settings.scss +0 -0
  104. data/lib/TokiServer/scss/app.scss +7 -1
  105. data/lib/TokiServer/tokiserver.rb +239 -244
  106. data/lib/TokiServer/views/activity.erb +42 -0
  107. data/lib/TokiServer/views/apps_total.erb +8 -2
  108. data/lib/TokiServer/views/error.erb +13 -4
  109. data/lib/TokiServer/views/index.erb +36 -27
  110. data/lib/TokiServer/views/logs_total.erb +34 -0
  111. data/spec/TokiCLI_spec.rb +354 -0
  112. data/spec/mock/mock.sqlite3 +0 -0
  113. data/spec/spec_helper.rb +26 -0
  114. metadata +78 -23
  115. data/lib/API/dbapi.rb +0 -488
  116. data/lib/TokiCLI/app.rb +0 -389
  117. data/lib/TokiCLI/authorize.rb +0 -77
  118. data/lib/TokiCLI/export.rb +0 -81
  119. data/lib/TokiCLI/get_channels.rb +0 -22
  120. data/lib/TokiCLI/get_messages.rb +0 -32
  121. data/lib/TokiCLI/import.rb +0 -122
  122. data/lib/TokiCLI/scan.rb +0 -19
  123. data/lib/TokiCLI/search_messages.rb +0 -23
  124. data/lib/TokiServer/README.md +0 -37
  125. data/lib/TokiServer/bower_components/foundation/scss/foundation/components/_reveal-new.scss +0 -0
  126. data/lib/TokiServer/bower_components/foundation/scss/foundation/components/_switch.scss +0 -294
  127. data/lib/TokiServer/views/name_log.erb +0 -50
  128. data/lib/TokiServer/views/name_split.erb +0 -37
  129. data/lib/TokiServer/views/name_total.erb +0 -34
@@ -0,0 +1,67 @@
1
+ # encoding: utf-8
2
+
3
+ module TokiCLI
4
+
5
+ class TokiDB
6
+
7
+ def initialize(db_path)
8
+ @db = Amalgalite::Database.new(db_path)
9
+ @table = 'KKAppActivity'
10
+ end
11
+
12
+ def apps_total
13
+ @db.execute("SELECT bundleIdentifier,sum(totalSeconds) FROM #{@table} GROUP BY bundleIdentifier")
14
+ end
15
+
16
+ def apps_range(starting, ending)
17
+ @db.execute("SELECT bundleIdentifier,sum(totalSeconds) FROM #{@table} WHERE activeFrom >= #{starting} AND activeFrom < #{ending} GROUP BY bundleIdentifier")
18
+ end
19
+
20
+ def apps_since(day)
21
+ @db.execute("SELECT bundleIdentifier,sum(totalSeconds) FROM #{@table} WHERE activeFrom >= #{day} GROUP BY bundleIdentifier")
22
+ end
23
+
24
+ def apps_before(day)
25
+ @db.execute("SELECT bundleIdentifier,sum(totalSeconds) FROM #{@table} WHERE activeFrom < #{day} GROUP BY bundleIdentifier")
26
+ end
27
+
28
+ # Get the log for an app given its exact bundle identifier
29
+ def bundle_log(bundle_id)
30
+ @db.execute("SELECT * FROM #{@table} WHERE bundleIdentifier IS '#{bundle_id}'")
31
+ end
32
+ def bundle_log_since(bundle_id, starting)
33
+ @db.execute("SELECT * FROM #{@table} WHERE bundleIdentifier IS '#{bundle_id}' AND activeFrom >= #{starting}")
34
+ end
35
+ def bundle_log_before(bundle_id, date)
36
+ @db.execute("SELECT * FROM #{@table} WHERE bundleIdentifier IS '#{bundle_id}' AND activeFrom < #{date}")
37
+ end
38
+ def bundle_log_range(bundle_id, starting, ending)
39
+ @db.execute("SELECT * FROM #{@table} WHERE bundleIdentifier IS '#{bundle_id}' AND activeFrom >= #{starting} AND activeFrom < #{ending}")
40
+ end
41
+
42
+ # Get the total time for an app given its exact bundle identifier, since a specific day
43
+ def bundle_total_since(bundle_id, starting)
44
+ @db.execute("SELECT sum(totalSeconds) FROM #{@table} WHERE bundleIdentifier IS '#{bundle_id}' AND activeFrom >= #{starting}")
45
+ end
46
+
47
+ # Get the total time for an app given its exact bundle identifier, before a specific day
48
+ def bundle_total_before(bundle_id, ending)
49
+ @db.execute("SELECT sum(totalSeconds) FROM #{@table} WHERE bundleIdentifier IS '#{bundle_id}' AND activeFrom < #{ending}")
50
+ end
51
+
52
+ def log_since(day)
53
+ @db.execute("SELECT * FROM #{@table} WHERE activeFrom >= #{day}")
54
+ end
55
+ def log_range(starting, ending)
56
+ @db.execute("SELECT * FROM #{@table} WHERE activeFrom >= #{starting} AND activeFrom < #{ending}")
57
+ end
58
+
59
+ # ---
60
+
61
+ def delete_bundle(bundle_id)
62
+ @db.execute("DELETE FROM #{@table} WHERE bundleIdentifier IS '#{bundle_id}'")
63
+ end
64
+
65
+ end
66
+
67
+ end
@@ -1,7 +1,272 @@
1
1
  # encoding: utf-8
2
- require 'ostruct'
3
2
  require 'thor'
4
- require 'amalgalite'
5
- require 'terminal-table/import'
6
- require_relative "TokiCLI/version"
7
- require_relative "TokiCLI/app"
3
+
4
+ module TokiCLI
5
+ class App < Thor
6
+
7
+ package_name "TokiCLI"
8
+
9
+ require_relative 'API/toki_api'
10
+ %w{version status fileops view adnimport}.each {|r| require_relative "TokiCLI/#{r}"}
11
+
12
+ desc "version", "TokiCLI version number"
13
+ map "-v" => :version
14
+ def version
15
+ View.new.version
16
+ end
17
+
18
+ desc "scan", "Scan applications folders for full names"
19
+ def scan
20
+ puts Status.scanning
21
+ fileops = FileOps.new
22
+ fileops.save_bundles
23
+ puts Status.file_saved(fileops.bundles_file)
24
+ puts Status.next_launch_with_names
25
+ end
26
+
27
+ desc "total", "Show total for all apps"
28
+ option :json, aliases: '-J', type: :boolean, desc: 'Export the results in a JSON file'
29
+ option :csv, aliases: '-C', type: :boolean, desc: 'Export the results in a CSV file'
30
+ def total
31
+ # Initializes files and instances
32
+ ## Replace with init(true) to backup the db before using it
33
+ ## Not necessary for read-only commands like this one
34
+ init()
35
+ # Gets the JSON response from TokiAPI
36
+ ## Here the response is stocked locally in 'apps' but using a variable is optional:
37
+ ## the response is memoized in @toki.response when a @toki method is called
38
+ ## (see other commands)
39
+ apps = @toki.apps_total()
40
+ # Export or display
41
+ if options[:json] || options[:csv]
42
+ export(@toki, options)
43
+ else
44
+ # Title is optional: @view.apps_total(apps)
45
+ title = "Toki - Total usage of all apps"
46
+ @view.apps_total(apps, title)
47
+ end
48
+ end
49
+
50
+ desc "top", "Show total for most used apps"
51
+ option :number, aliases: '-n', type: :numeric, desc: 'Specify the number of apps'
52
+ option :json, aliases: '-J', type: :boolean, desc: 'Export the results in a JSON file'
53
+ option :csv, aliases: '-C', type: :boolean, desc: 'Export the results in a CSV file'
54
+ def top
55
+ init()
56
+ max = options[:number] || 5
57
+ @toki.apps_top(max)
58
+ if options[:json] || options[:csv]
59
+ export(@toki, options)
60
+ else
61
+ @view.apps(@toki.response, "Toki - Total usage of most used apps")
62
+ end
63
+ end
64
+
65
+ desc "day DATE", "Show total for apps used on a specific day"
66
+ option :json, aliases: '-J', type: :boolean, desc: 'Export the results in a JSON file'
67
+ option :csv, aliases: '-C', type: :boolean, desc: 'Export the results in a CSV file'
68
+ def day(*args)
69
+ init()
70
+ @toki.apps_day(args[0])
71
+ exit_with_msg_if_invalid_response()
72
+ if options[:json] || options[:csv]
73
+ export(@toki, options)
74
+ else
75
+ @view.apps(@toki.response, "Toki - All apps used on #{args[0]}")
76
+ end
77
+ end
78
+
79
+ desc "range DATE1 DATE2", "Show total for all apps used between two specific days"
80
+ option :json, aliases: '-J', type: :boolean, desc: 'Export the results in a JSON file'
81
+ option :csv, aliases: '-C', type: :boolean, desc: 'Export the results in a CSV file'
82
+ def range(*args)
83
+ init()
84
+ @toki.apps_range(args[0], args[1])
85
+ exit_with_msg_if_invalid_response()
86
+ if options[:json] || options[:csv]
87
+ export(@toki, options)
88
+ else
89
+ @view.apps(@toki.response, "Toki - All apps used between #{args[0]} and #{args[1]}")
90
+ end
91
+ end
92
+
93
+ desc "since DATE", "Show total for all apps used since a specific day"
94
+ option :json, aliases: '-J', type: :boolean, desc: 'Export the results in a JSON file'
95
+ option :csv, aliases: '-C', type: :boolean, desc: 'Export the results in a CSV file'
96
+ def since(*args)
97
+ init()
98
+ @toki.apps_since(args[0])
99
+ exit_with_msg_if_invalid_response()
100
+ if options[:json] || options[:csv]
101
+ export(@toki, options)
102
+ else
103
+ @view.apps(@toki.response, "Toki - All apps used since #{args[0]}")
104
+ end
105
+ end
106
+
107
+ desc "before DATE", "Show total for all apps used before a specific day"
108
+ option :json, aliases: '-J', type: :boolean, desc: 'Export the results in a JSON file'
109
+ option :csv, aliases: '-C', type: :boolean, desc: 'Export the results in a CSV file'
110
+ def before(*args)
111
+ init()
112
+ @toki.apps_before(args[0])
113
+ exit_with_msg_if_invalid_response()
114
+ if options[:json] || options[:csv]
115
+ export(@toki, options)
116
+ else
117
+ @view.apps(@toki.response, "Toki - All apps used before #{args[0]}")
118
+ end
119
+ end
120
+
121
+ desc "activity", "Shows recent log updates"
122
+ option :since, type: :string, desc: 'Request log starting on this date'
123
+ option :day, type: :string, desc: 'Request log for a specific day'
124
+ def activity
125
+ init()
126
+ if options.since?
127
+ @toki.log_since(options.since)
128
+ elsif options.day?
129
+ @toki.log_day(options.day)
130
+ else
131
+ @toki.log_since()
132
+ end
133
+ @view.log_activity(@toki.response)
134
+ end
135
+
136
+ desc "bundle BUNDLE_ID", "Show complete log for an app from its exact bundle id"
137
+ option :json, aliases: '-J', type: :boolean, desc: 'Export the results as a JSON file'
138
+ option :csv, aliases: '-C', type: :boolean, desc: 'Export the results as a CSV file'
139
+ option :before, type: :string, desc: 'Request log before this date'
140
+ option :since, type: :string, desc: 'Request log starting on this date'
141
+ option :day, type: :string, desc: 'Request log for a specific day'
142
+ option :range, type: :array, desc: 'Request log between two specific days'
143
+ def bundle(bundle_id)
144
+ init()
145
+ title = bundle_title(bundle_id, options)
146
+ bundle_log(bundle_id, options)
147
+ exit_with_msg_if_invalid_response(Status.no_data)
148
+ if options[:json] || options[:csv]
149
+ export(@toki, options)
150
+ elsif options[:since] || options[:before] || options[:day] || options[:range]
151
+ @view.log(@toki.response, title)
152
+ else
153
+ @view.log_total(@toki.response, title)
154
+ end
155
+ end
156
+
157
+ desc "app APP_NAME", "Show complete log for an app from (part of) its name"
158
+ option :json, aliases: '-J', type: :boolean, desc: 'Export the results as a JSON file'
159
+ option :csv, aliases: '-C', type: :boolean, desc: 'Export the results as a CSV file'
160
+ option :before, type: :string, desc: 'Request log before this date'
161
+ option :since, type: :string, desc: 'Request log starting on this date'
162
+ option :day, type: :string, desc: 'Request log for a specific day'
163
+ option :range, type: :array, desc: 'Request log between two specific days'
164
+ def app(*app_name)
165
+ init()
166
+ abort(Status.please_scan) if @toki.bundles.nil?
167
+ candidates = @fileops.get_bundle_from_name(app_name)
168
+ candidates.each.with_index(1) do |bundle_id, index|
169
+ say "\nApp N°#{'%.2d' % index}: #{bundle_id}" if candidates.length > 1
170
+ bundle_log(bundle_id, options)
171
+ if JSON.parse(@toki.response)['meta']['code'] != 200
172
+ say Status.no_data
173
+ else
174
+ if options[:json] || options[:csv]
175
+ export(@toki, options, bundle_id)
176
+ elsif options[:since] || options[:before] || options[:day] || options[:range]
177
+ @view.log(@toki.response, bundle_title(bundle_id, options))
178
+ else
179
+ @view.log_total(@toki.response, bundle_title(bundle_id, options))
180
+ end
181
+ end
182
+ end
183
+ end
184
+
185
+ desc "restore", "Restore your database from the App.net backup"
186
+ def restore
187
+ init(true)
188
+ adn = ADNImport.new(@fileops.data_path)
189
+ adn.restore
190
+ end
191
+
192
+ desc "serve", "Start a local Toki API server"
193
+ map "server" => :serve
194
+ def serve
195
+ require_relative '../lib/TokiServer/tokiserver'
196
+ puts "\n\n"
197
+ say_status :starting, "Toki API server"
198
+ say "\n\nPress [CTRL-C] to stop.\n\nThe index page URL is: http://localhost:4567\n\n"
199
+ TokiServer.run!
200
+ puts "\n\n"
201
+ say_status :halt, "Toki API server"
202
+ end
203
+
204
+ # ---
205
+
206
+ desc "delete BUNDLE_ID", "Permanently delete this application from the database"
207
+ option :'no-backup', aliases: '-X',type: :boolean, desc: 'Do not backup the database before processing'
208
+ def delete(bundle_id)
209
+ options['no-backup'] ? init() : init(true)
210
+ name = @toki.bundles[bundle_id]
211
+ confirm_delete(bundle_id, name)
212
+ say "\nDeleting entries..."
213
+ @toki.delete_bundle(bundle_id)
214
+ exit_with_msg_if_invalid_response(Status.wtf)
215
+ say "\nDone.\n"
216
+ end
217
+
218
+ private
219
+
220
+ def confirm_delete(bundle_id, name)
221
+ name.nil? ? insert = '' : insert = " (#{name})"
222
+ xx = yes?("\nAre you sure you want to remove all '#{bundle_id}'#{insert} entries from the database ?\n\n>> ")
223
+ abort("\nCanceled.\n\n") if xx == false
224
+ end
225
+
226
+ def bundle_log(bundle_id, options)
227
+ if options[:since]
228
+ @toki.bundle_log_since(bundle_id, options[:since])
229
+ elsif options[:before]
230
+ @toki.bundle_log_before(bundle_id, options[:before])
231
+ elsif options[:day]
232
+ @toki.bundle_log_day(bundle_id, options[:day])
233
+ elsif options[:range]
234
+ @toki.bundle_log_range(bundle_id, options[:range][0], options[:range][1])
235
+ else
236
+ @toki.bundle_log(bundle_id)
237
+ end
238
+ end
239
+
240
+ def bundle_title(bundle_id, options = {})
241
+ prefix = "Toki - Complete log for #{bundle_id}"
242
+ if options[:since]
243
+ "#{prefix} since #{options[:since]}"
244
+ elsif options[:before]
245
+ "#{prefix} before #{options[:before]}"
246
+ elsif options[:day]
247
+ "#{prefix} - #{options[:day]}"
248
+ elsif options[:range]
249
+ "#{prefix} - #{options[:range][0]}/#{options[:range][1]}"
250
+ else
251
+ prefix
252
+ end
253
+ end
254
+
255
+ def exit_with_msg_if_invalid_response(msg = Status.wtf)
256
+ abort(msg) if JSON.parse(@toki.response)['meta']['code'] != 200
257
+ end
258
+
259
+ def export(toki, options, title = nil)
260
+ @fileops.export(toki, options, title)
261
+ end
262
+
263
+ # Replaces usual initialize method
264
+ def init(backup = false)
265
+ @fileops = FileOps.new
266
+ @fileops.backup_db if backup == true
267
+ @toki = TokiAPI.new(@fileops.db_file, @fileops.bundles) # @fileops.bundles is optional
268
+ @view = View.new(@fileops.config) # @fileops.config is optional
269
+ end
270
+
271
+ end
272
+ end
@@ -0,0 +1,202 @@
1
+ # encoding: utf-8
2
+ module TokiCLI
3
+
4
+ class ADNImport
5
+
6
+ require 'rest-client'
7
+
8
+ CLIENT_ID = 'm6AccJFM56ENCn58Vde9cSg3uSpbvAAs'
9
+ CALLBACK_URL = 'http://aya.io/toki_cli/auth.html'
10
+
11
+ attr_reader :token, :config
12
+
13
+ def initialize(data_path)
14
+ @toki_db_file = "#{Dir.home}/Library/Containers/us.kkob.Toki/Data/Documents/toki_data.sqlite3"
15
+ @data_path = data_path
16
+ @user_file = "#{data_path}/user.json"
17
+ @shell = Thor::Shell::Basic.new
18
+ @auth_url = "https://account.app.net/oauth/authenticate?client_id=#{CLIENT_ID}&response_type=token&redirect_uri=#{CALLBACK_URL}&scope=basic,messages&include_marker=1"
19
+ end
20
+
21
+ def restore
22
+ please_quit()
23
+ if File.exist? @user_file
24
+ @shell.say_status :loading, "user infos"
25
+ @config = JSON.parse(File.read(@user_file))
26
+ @token = @config['token']
27
+ else
28
+ ask_token()
29
+ get_user_data()
30
+ @shell.say_status :analysing, "App.net channels"
31
+ channel = get_channel_id()
32
+ @shell.say_status :writing, "user file"
33
+ File.write(@user_file, @config.merge({'channel' => channel}).to_json)
34
+ end
35
+ adn_data = get_messages(@config['channel'])
36
+ @shell.say_status :writing, "App.net data file"
37
+ File.write("#{@data_path}/adn_backup.json", adn_data.to_json)
38
+ @shell.say_status :decoding, "App.net data file"
39
+ lines = decode(adn_data)
40
+ @shell.say_status :creating, "new database file"
41
+ db = create_db()
42
+ @shell.say_status :creating, "database table"
43
+ create_table(db)
44
+ @shell.say_status :populating, "database"
45
+ populate(db, lines)
46
+ @shell.say_status :replacing, "database file"
47
+ replace()
48
+ @shell.say_status :done, "Restore database from the App.net backup"
49
+ puts "\n\nDone. You may relaunch Toki.app now.\n\n"
50
+ end
51
+
52
+ private
53
+
54
+ def please_quit
55
+ resp = @shell.yes? "\nYou need to quit Toki.app before restoring data from App.net.\n\nPlease quit Toki.app then type 'Y' to continue.\n\n>> "
56
+ puts "\n"
57
+ abort("\nCanceled.\n\n") if resp == false
58
+ end
59
+
60
+ def ask_token
61
+ @shell.say "\nPlease click this URL, or copy/paste it in a browser:\n"
62
+ puts "\n#{@auth_url}\n\n"
63
+ @shell.say "then log in with your ADN credentials to authorize TokiCLI.\n\n"
64
+ @shell.say "You will then be redirected to a page showing a 'user token'.\n\n"
65
+ @token = @shell.ask "Copy the token then paste it here:\n\n>>"
66
+ puts "\n"
67
+ end
68
+
69
+ def get_user_data
70
+ @shell.say_status :connecting, "App.net"
71
+ @shell.say_status :downloading, "User infos"
72
+ resp = get_user()
73
+ @config = {
74
+ 'username' => resp['data']['username'],
75
+ 'name' => resp['data']['name'],
76
+ 'id' => resp['data']['id'],
77
+ 'handle' => "@#{resp['data']['username']}",
78
+ 'token' => @token
79
+ }
80
+ end
81
+
82
+ def get_user
83
+ JSON.parse(RestClient.get("https://api.app.net/users/me?access_token=#{@token}", :verify_ssl => OpenSSL::SSL::VERIFY_NONE) {|response, request, result| response })
84
+ end
85
+
86
+ def get_channel_id
87
+ get_channels().each do |ch|
88
+ return ch['id'].to_i if ch['type'] == 'us.kkob.toki.sync-b'
89
+ end
90
+ end
91
+
92
+ def get_channels
93
+ args = {:count => 200, :before_id => nil}
94
+ channels = []
95
+ loop do
96
+ url = "http://api.app.net/users/me/channels?access_token=#{@token}&include_machine=1&include_message_annotations=1&include_deleted=0&include_html=0&count=#{args[:count]}&before_id=#{args[:before_id]}"
97
+ resp = JSON.parse(RestClient.get(url))
98
+ resp['data'].each { |m| channels << m }
99
+ break unless resp['meta']['more']
100
+ args = {:count => 200, :before_id => resp['meta']['min_id']}
101
+ end
102
+ channels
103
+ end
104
+
105
+ def get_messages(channel_id)
106
+ @shell.say_status :connecting, "App.net"
107
+ @shell.say_status :downloading, "Toki infos"
108
+ args = {:count => 200, :before_id => nil}
109
+ @messages = []
110
+ @index = 1
111
+ @shell.say_status :downloading, "Toki sync objects"
112
+ loop do
113
+ begin
114
+ @shell.say_status :downloading, "page #{'%.2d' % @index}"
115
+ url = "http://api.app.net/channels/#{channel_id}/messages?access_token=#{@token}&include_machine=1&include_message_annotations=1&include_user_annotations=0&include_deleted=0&include_html=0&count=#{args[:count]}&before_id=#{args[:before_id]}"
116
+ data = RestClient.get(url)
117
+ resp = JSON.parse(data)
118
+ dates = []
119
+ resp['data'].each do |m|
120
+ dates << m['created_at'][0..9]
121
+ @messages << m
122
+ end
123
+ @shell.say_status :downloaded, "#{dates.uniq.join(', ')}"
124
+ break unless resp['meta']['more']
125
+ @index += 1
126
+ args = {:count => 200, :before_id => resp['meta']['min_id']}
127
+ rescue Interrupt
128
+ abort(Status.canceled)
129
+ end
130
+ end
131
+ @messages
132
+ end
133
+
134
+ def decode(adn_data)
135
+ adn_data.map do |obj|
136
+ {
137
+ 'type' => obj['annotations'][0]['type'],
138
+ 'table' => obj['annotations'][0]['value']['c'],
139
+ 'uuid' => obj['annotations'][0]['value']['d']['UUID'],
140
+ 'bundleIdentifier' => obj['annotations'][0]['value']['d']['bundleIdentifier'],
141
+ 'activeTo' => obj['annotations'][0]['value']['d']['activeTo'],
142
+ 'activeFrom' => obj['annotations'][0]['value']['d']['activeFrom'],
143
+ 'totalSeconds' => obj['annotations'][0]['value']['d']['totalSeconds'],
144
+ 'id' => obj['annotations'][0]['value']['d']['id']
145
+ }
146
+ end
147
+ end
148
+
149
+ def create_db
150
+ file = "#{@data_path}/db_from_adn.sqlite3"
151
+ File.rm(file) if File.exist?(file)
152
+ Amalgalite::Database.new(file)
153
+ end
154
+
155
+ def create_table db
156
+ db.execute_batch <<-SQL
157
+ CREATE TABLE KKAppActivity (
158
+ id INTEGER,
159
+ bundleIdentifier VARCHAR(256),
160
+ activeTo INTEGER,
161
+ activeFrom INTEGER,
162
+ totalSeconds INTEGER,
163
+ UUID VARCHAR(256),
164
+ synced INTEGER,
165
+ availableToSync INTEGER
166
+ );
167
+ SQL
168
+ db.reload_schema!
169
+ end
170
+
171
+ def populate(db, lines)
172
+ before = Time.now
173
+ @shell.say_status :processing, "#{lines.size} rows"
174
+ db.transaction do |db_in_transaction|
175
+ lines.each do |obj|
176
+ insert_data = {}
177
+ insert_data[':id'] = obj['id']
178
+ insert_data[':bundleIdentifier'] = obj['bundleIdentifier']
179
+ insert_data[':activeTo'] = obj['activeTo']
180
+ insert_data[':activeFrom'] = obj['activeFrom']
181
+ insert_data[':totalSeconds'] = obj['totalSeconds']
182
+ insert_data[':UUID'] = obj['uuid']
183
+ insert_data[':synced'] = 1
184
+ insert_data[':availableToSync'] = 1
185
+
186
+ db_in_transaction.prepare("INSERT INTO KKAppActivity(id, bundleIdentifier, activeTo, activeFrom, totalSeconds, UUID, synced, availableToSync) VALUES(:id, :bundleIdentifier, :activeTo, :activeFrom, :totalSeconds, :UUID, :synced, :availableToSync);") do |insert|
187
+ insert.execute(insert_data)
188
+ end
189
+
190
+ end
191
+ end
192
+ @shell.say_status :finished, "insertion of #{idx} rows in #{Time.now - before} seconds"
193
+ end
194
+
195
+ def replace
196
+ FileUtils.mv @toki_db_file, "#{Dir.home}/.Trash/"
197
+ FileUtils.mv "#{@data_path}/db_from_adn.sqlite3", @toki_db_file
198
+ end
199
+
200
+ end
201
+
202
+ end