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.
- checksums.yaml +4 -4
- data/.coveralls.yml +1 -0
- data/.gitignore +9 -17
- data/.rspec +3 -0
- data/.travis.yml +3 -0
- data/CHANGELOG.md +4 -0
- data/README.md +99 -163
- data/Rakefile +5 -0
- data/TokiCLI.gemspec +5 -4
- data/bin/toki +2 -2
- data/lib/API/helpers.rb +27 -170
- data/lib/API/toki_api.rb +227 -0
- data/lib/API/toki_db.rb +67 -0
- data/lib/TokiCLI.rb +270 -5
- data/lib/TokiCLI/adnimport.rb +202 -0
- data/lib/TokiCLI/fileops.rb +187 -0
- data/lib/TokiCLI/status.rb +49 -37
- data/lib/TokiCLI/version.rb +1 -1
- data/lib/TokiCLI/view.rb +133 -86
- data/lib/TokiServer/.bowerrc +3 -0
- data/lib/TokiServer/.gitignore +2 -0
- data/lib/TokiServer/Gemfile +1 -0
- data/lib/TokiServer/bower.json +0 -0
- data/lib/TokiServer/bower_components/fastclick/.bower.json +4 -4
- data/lib/TokiServer/bower_components/fastclick/bower.json +1 -1
- data/lib/TokiServer/bower_components/fastclick/lib/fastclick.js +33 -2
- data/lib/TokiServer/bower_components/foundation/.bower.json +5 -4
- data/lib/TokiServer/bower_components/foundation/bower.json +2 -1
- data/lib/TokiServer/bower_components/foundation/css/foundation.css +2173 -922
- data/lib/TokiServer/bower_components/foundation/css/foundation.css.map +7 -0
- data/lib/TokiServer/bower_components/foundation/css/normalize.css +53 -16
- data/lib/TokiServer/bower_components/foundation/css/normalize.css.map +7 -0
- data/lib/TokiServer/bower_components/foundation/js/foundation.js +782 -542
- data/lib/TokiServer/bower_components/foundation/js/foundation.min.js +4 -3
- data/lib/TokiServer/bower_components/foundation/js/foundation/foundation.abide.js +45 -31
- data/lib/TokiServer/bower_components/foundation/js/foundation/foundation.accordion.js +12 -6
- data/lib/TokiServer/bower_components/foundation/js/foundation/foundation.alert.js +5 -5
- data/lib/TokiServer/bower_components/foundation/js/foundation/foundation.clearing.js +34 -10
- data/lib/TokiServer/bower_components/foundation/js/foundation/foundation.dropdown.js +83 -29
- data/lib/TokiServer/bower_components/foundation/js/foundation/foundation.equalizer.js +3 -3
- data/lib/TokiServer/bower_components/foundation/js/foundation/foundation.interchange.js +25 -12
- data/lib/TokiServer/bower_components/foundation/js/foundation/foundation.joyride.js +112 -40
- data/lib/TokiServer/bower_components/foundation/js/foundation/foundation.js +19 -5
- data/lib/TokiServer/bower_components/foundation/js/foundation/foundation.magellan.js +22 -11
- data/lib/TokiServer/bower_components/foundation/js/foundation/foundation.offcanvas.js +52 -8
- data/lib/TokiServer/bower_components/foundation/js/foundation/foundation.orbit.js +133 -271
- data/lib/TokiServer/bower_components/foundation/js/foundation/foundation.reveal.js +27 -20
- data/lib/TokiServer/bower_components/foundation/js/foundation/foundation.slider.js +73 -33
- data/lib/TokiServer/bower_components/foundation/js/foundation/foundation.tab.js +88 -31
- data/lib/TokiServer/bower_components/foundation/js/foundation/foundation.tooltip.js +7 -5
- data/lib/TokiServer/bower_components/foundation/js/foundation/foundation.topbar.js +44 -24
- data/lib/TokiServer/bower_components/foundation/js/vendor/fastclick.js +2 -2
- data/lib/TokiServer/bower_components/foundation/js/vendor/modernizr.js +2 -2
- data/lib/TokiServer/bower_components/foundation/scss/foundation.scss +38 -38
- data/lib/TokiServer/bower_components/foundation/scss/foundation/_functions.scss +3 -3
- data/lib/TokiServer/bower_components/foundation/scss/foundation/_settings.scss +417 -271
- data/lib/TokiServer/bower_components/foundation/scss/foundation/components/_accordion.scss +110 -6
- data/lib/TokiServer/bower_components/foundation/scss/foundation/components/_alert-boxes.scss +2 -2
- data/lib/TokiServer/bower_components/foundation/scss/foundation/components/_block-grid.scss +2 -2
- data/lib/TokiServer/bower_components/foundation/scss/foundation/components/_breadcrumbs.scss +8 -3
- data/lib/TokiServer/bower_components/foundation/scss/foundation/components/_button-groups.scss +99 -9
- data/lib/TokiServer/bower_components/foundation/scss/foundation/components/_buttons.scss +66 -28
- data/lib/TokiServer/bower_components/foundation/scss/foundation/components/_clearing.scss +5 -5
- data/lib/TokiServer/bower_components/foundation/scss/foundation/components/_dropdown-buttons.scss +4 -4
- data/lib/TokiServer/bower_components/foundation/scss/foundation/components/_dropdown.scss +48 -35
- data/lib/TokiServer/bower_components/foundation/scss/foundation/components/_forms.scss +104 -32
- data/lib/TokiServer/bower_components/foundation/scss/foundation/components/_global.scss +48 -30
- data/lib/TokiServer/bower_components/foundation/scss/foundation/components/_grid.scss +19 -4
- data/lib/TokiServer/bower_components/foundation/scss/foundation/components/_icon-bar.scss +293 -0
- data/lib/TokiServer/bower_components/foundation/scss/foundation/components/_joyride.scss +11 -9
- data/lib/TokiServer/bower_components/foundation/scss/foundation/components/_keystrokes.scss +4 -4
- data/lib/TokiServer/bower_components/foundation/scss/foundation/components/_labels.scss +4 -2
- data/lib/TokiServer/bower_components/foundation/scss/foundation/components/_magellan.scss +1 -1
- data/lib/TokiServer/bower_components/foundation/scss/foundation/components/_offcanvas.scss +193 -35
- data/lib/TokiServer/bower_components/foundation/scss/foundation/components/_orbit.scss +92 -147
- data/lib/TokiServer/bower_components/foundation/scss/foundation/components/_pagination.scss +22 -10
- data/lib/TokiServer/bower_components/foundation/scss/foundation/components/_panels.scss +10 -7
- data/lib/TokiServer/bower_components/foundation/scss/foundation/components/_pricing-tables.scss +11 -11
- data/lib/TokiServer/bower_components/foundation/scss/foundation/components/_progress-bars.scss +2 -2
- data/lib/TokiServer/bower_components/foundation/scss/foundation/components/_range-slider.scss +29 -9
- data/lib/TokiServer/bower_components/foundation/scss/foundation/components/_reveal.scss +60 -56
- data/lib/TokiServer/bower_components/foundation/scss/foundation/components/_side-nav.scss +3 -2
- data/lib/TokiServer/bower_components/foundation/scss/foundation/components/_split-buttons.scss +2 -2
- data/lib/TokiServer/bower_components/foundation/scss/foundation/components/_sub-nav.scss +2 -2
- data/lib/TokiServer/bower_components/foundation/scss/foundation/components/_switches.scss +226 -0
- data/lib/TokiServer/bower_components/foundation/scss/foundation/components/_tables.scss +13 -7
- data/lib/TokiServer/bower_components/foundation/scss/foundation/components/_tabs.scss +22 -8
- data/lib/TokiServer/bower_components/foundation/scss/foundation/components/_thumbs.scss +2 -4
- data/lib/TokiServer/bower_components/foundation/scss/foundation/components/_toolbar.scss +70 -0
- data/lib/TokiServer/bower_components/foundation/scss/foundation/components/_tooltips.scss +9 -7
- data/lib/TokiServer/bower_components/foundation/scss/foundation/components/_top-bar.scss +77 -44
- data/lib/TokiServer/bower_components/foundation/scss/foundation/components/_type.scss +21 -9
- data/lib/TokiServer/bower_components/modernizr/.bower.json +4 -4
- data/lib/TokiServer/bower_components/modernizr/feature-detects/workers-blobworkers.js +2 -2
- data/lib/TokiServer/bower_components/modernizr/grunt.js +1 -1
- data/lib/TokiServer/bower_components/modernizr/modernizr.js +2 -2
- data/lib/TokiServer/config.rb +0 -0
- data/lib/TokiServer/humans.txt +0 -0
- data/lib/TokiServer/{itunesicon.rb → itunesicons.rb} +25 -23
- data/lib/TokiServer/js/app.js +0 -0
- data/lib/TokiServer/public/stylesheets/app.css +2636 -1610
- data/lib/TokiServer/robots.txt +0 -0
- data/lib/TokiServer/scss/_settings.scss +0 -0
- data/lib/TokiServer/scss/app.scss +7 -1
- data/lib/TokiServer/tokiserver.rb +239 -244
- data/lib/TokiServer/views/activity.erb +42 -0
- data/lib/TokiServer/views/apps_total.erb +8 -2
- data/lib/TokiServer/views/error.erb +13 -4
- data/lib/TokiServer/views/index.erb +36 -27
- data/lib/TokiServer/views/logs_total.erb +34 -0
- data/spec/TokiCLI_spec.rb +354 -0
- data/spec/mock/mock.sqlite3 +0 -0
- data/spec/spec_helper.rb +26 -0
- metadata +78 -23
- data/lib/API/dbapi.rb +0 -488
- data/lib/TokiCLI/app.rb +0 -389
- data/lib/TokiCLI/authorize.rb +0 -77
- data/lib/TokiCLI/export.rb +0 -81
- data/lib/TokiCLI/get_channels.rb +0 -22
- data/lib/TokiCLI/get_messages.rb +0 -32
- data/lib/TokiCLI/import.rb +0 -122
- data/lib/TokiCLI/scan.rb +0 -19
- data/lib/TokiCLI/search_messages.rb +0 -23
- data/lib/TokiServer/README.md +0 -37
- data/lib/TokiServer/bower_components/foundation/scss/foundation/components/_reveal-new.scss +0 -0
- data/lib/TokiServer/bower_components/foundation/scss/foundation/components/_switch.scss +0 -294
- data/lib/TokiServer/views/name_log.erb +0 -50
- data/lib/TokiServer/views/name_split.erb +0 -37
- data/lib/TokiServer/views/name_total.erb +0 -34
data/lib/API/toki_db.rb
ADDED
|
@@ -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
|
data/lib/TokiCLI.rb
CHANGED
|
@@ -1,7 +1,272 @@
|
|
|
1
1
|
# encoding: utf-8
|
|
2
|
-
require 'ostruct'
|
|
3
2
|
require 'thor'
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
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
|