TokiCLI 0.2.1 → 0.3.0

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.
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