time_buffer 0.1.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 +7 -0
- data/.rspec +3 -0
- data/.solargraph.yml +1 -0
- data/.standard.yml +3 -0
- data/CHANGELOG.md +5 -0
- data/README.md +35 -0
- data/Rakefile +10 -0
- data/lib/time_buffer/application_handler.rb +20 -0
- data/lib/time_buffer/database_connector.rb +59 -0
- data/lib/time_buffer/osa_script.rb +14 -0
- data/lib/time_buffer/osa_script_app.rb +17 -0
- data/lib/time_buffer/osa_script_app_browser.rb +31 -0
- data/lib/time_buffer/osa_script_app_builder.rb +23 -0
- data/lib/time_buffer/session_handler.rb +30 -0
- data/lib/time_buffer/state_detector.rb +36 -0
- data/lib/time_buffer/version.rb +5 -0
- data/lib/time_buffer.rb +48 -0
- data/sig/time_buffer.rbs +4 -0
- metadata +77 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: fb5a16f7fd58ccfb39b6d84410ec6c4e1326f119024189f652674dacf590170f
|
4
|
+
data.tar.gz: 5cc84eaaff62ae9741a9008e4ed5b8193d8f40f8d8e110c1d2b40c8f87152e08
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 560ef5753cb3eb51bd1464df41dfa3d230a2f4676919af9d3ebb737d69c267bcd60adaeecf0b1477771fd5c34dc3f984c69592e8958bed8f42b51fc09ecb3dd8
|
7
|
+
data.tar.gz: '003745089e2980687ca331cebe91a3447c28d4e2262535a959c3fbc5a11d383ecac988e249a4da1634d7a0279a2088cf815ec270e18ffc70e902fec0a4fc47b6'
|
data/.rspec
ADDED
data/.solargraph.yml
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
reporters: []
|
data/.standard.yml
ADDED
data/CHANGELOG.md
ADDED
data/README.md
ADDED
@@ -0,0 +1,35 @@
|
|
1
|
+
# TimeBuffer
|
2
|
+
|
3
|
+
A gem that tracks your time.
|
4
|
+
|
5
|
+
## Installation
|
6
|
+
|
7
|
+
Install the gem and add to the application's Gemfile by executing:
|
8
|
+
|
9
|
+
$ bundle add time_buffer
|
10
|
+
|
11
|
+
If bundler is not being used to manage dependencies, install the gem by executing:
|
12
|
+
|
13
|
+
$ gem install time_buffer
|
14
|
+
|
15
|
+
## Usage
|
16
|
+
|
17
|
+
In IRB:
|
18
|
+
|
19
|
+
```rb
|
20
|
+
require "time_buffer"
|
21
|
+
|
22
|
+
TimeBuffer::Tracker.start
|
23
|
+
```
|
24
|
+
|
25
|
+
This will initialize the SQLite database and start the tracker.
|
26
|
+
|
27
|
+
## Development
|
28
|
+
|
29
|
+
After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
|
30
|
+
|
31
|
+
To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and the created tag, and push the `.gem` file to [rubygems.org](https://rubygems.org).
|
32
|
+
|
33
|
+
## Contributing
|
34
|
+
|
35
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/StevenElberger/time_buffer.
|
data/Rakefile
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module TimeBuffer
|
4
|
+
class ApplicationHandler
|
5
|
+
def initialize(app_data)
|
6
|
+
@app_data = app_data
|
7
|
+
end
|
8
|
+
|
9
|
+
def insert_application
|
10
|
+
TimeBuffer::DatabaseConnector.new.execute(
|
11
|
+
"INSERT OR IGNORE INTO applications (bundle_id, app_name) VALUES (?, ?)",
|
12
|
+
[app_data.bundle_id, app_data.name]
|
13
|
+
)
|
14
|
+
end
|
15
|
+
|
16
|
+
private
|
17
|
+
|
18
|
+
attr_reader :app_data
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,59 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module TimeBuffer
|
4
|
+
class DatabaseConnector
|
5
|
+
FILE_LOCATION = "usage_data.db"
|
6
|
+
|
7
|
+
def initialize
|
8
|
+
return if File.exist?(FILE_LOCATION)
|
9
|
+
|
10
|
+
# 1. Applications Table
|
11
|
+
connection.execute <<-SQL
|
12
|
+
CREATE TABLE IF NOT EXISTS applications (
|
13
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
14
|
+
bundle_id TEXT UNIQUE NOT NULL,
|
15
|
+
app_name TEXT NOT NULL
|
16
|
+
);
|
17
|
+
SQL
|
18
|
+
|
19
|
+
# 2. Time Sessions Table
|
20
|
+
connection.execute <<-SQL
|
21
|
+
CREATE TABLE IF NOT EXISTS time_sessions (
|
22
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
23
|
+
application_id INTEGER NOT NULL,
|
24
|
+
start_time DATETIME NOT NULL,
|
25
|
+
end_time DATETIME,
|
26
|
+
metadata JSONB,
|
27
|
+
FOREIGN KEY (application_id) REFERENCES applications(id)
|
28
|
+
);
|
29
|
+
SQL
|
30
|
+
|
31
|
+
# 3. Daily Summaries Table
|
32
|
+
connection.execute <<-SQL
|
33
|
+
CREATE TABLE IF NOT EXISTS daily_summaries (
|
34
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
35
|
+
application_id INTEGER NOT NULL,
|
36
|
+
date DATE NOT NULL,
|
37
|
+
total_duration INTEGER NOT NULL,
|
38
|
+
FOREIGN KEY (application_id) REFERENCES applications(id),
|
39
|
+
UNIQUE(application_id, date)
|
40
|
+
);
|
41
|
+
SQL
|
42
|
+
|
43
|
+
puts "Database initialized successfully."
|
44
|
+
end
|
45
|
+
|
46
|
+
def connect
|
47
|
+
@db = SQLite3::Database.new(FILE_LOCATION)
|
48
|
+
end
|
49
|
+
|
50
|
+
def connection
|
51
|
+
@db || connect
|
52
|
+
end
|
53
|
+
|
54
|
+
def execute(sql, *params)
|
55
|
+
# puts "Writing to db: #{params}"
|
56
|
+
connection.execute(sql, *params)
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module TimeBuffer
|
4
|
+
class OsaScript
|
5
|
+
class << self
|
6
|
+
def app_data
|
7
|
+
current_app_bundle_id = `osascript -e 'id of application (path to frontmost application as text)'`.strip
|
8
|
+
app = OsaScriptAppBuilder.build(current_app_bundle_id)
|
9
|
+
puts "app name: #{app.name} | bundle id: #{app.bundle_id} | class: #{app.class}"
|
10
|
+
app
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module TimeBuffer
|
4
|
+
class OsaScriptApp
|
5
|
+
attr_reader :bundle_id
|
6
|
+
|
7
|
+
def initialize(app_bundle_id)
|
8
|
+
@bundle_id = app_bundle_id
|
9
|
+
end
|
10
|
+
|
11
|
+
def name
|
12
|
+
@name ||= `osascript -e 'tell application "System Events" to get name of first application process whose frontmost is true'`.strip
|
13
|
+
end
|
14
|
+
|
15
|
+
def metadata = {}
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "osa_script_app_builder"
|
4
|
+
|
5
|
+
module TimeBuffer
|
6
|
+
class OsaScriptAppBrowser < OsaScriptApp
|
7
|
+
KNOWN_BROWSERS = %w[com.google.Chrome]
|
8
|
+
|
9
|
+
def self.register_known_browsers
|
10
|
+
KNOWN_BROWSERS.each do |bundle_id|
|
11
|
+
OsaScriptAppBuilder.register(app_bundle_id: bundle_id, app_class: self)
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
register_known_browsers
|
16
|
+
|
17
|
+
def initialize(app_bundle_id)
|
18
|
+
@bundle_id = app_bundle_id
|
19
|
+
end
|
20
|
+
|
21
|
+
def metadata
|
22
|
+
{
|
23
|
+
tab_title: tab_title
|
24
|
+
}
|
25
|
+
end
|
26
|
+
|
27
|
+
def tab_title
|
28
|
+
`osascript -e 'tell application "Google Chrome" to get title of active tab of front window'`.strip
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module TimeBuffer
|
4
|
+
class OsaScriptAppBuilder
|
5
|
+
class << self
|
6
|
+
REGISTRY = {}
|
7
|
+
|
8
|
+
def register(app_bundle_id:, app_class:)
|
9
|
+
REGISTRY[app_bundle_id] = app_class
|
10
|
+
end
|
11
|
+
|
12
|
+
def build(app_bundle_id)
|
13
|
+
app_class = REGISTRY[app_bundle_id]
|
14
|
+
|
15
|
+
if app_class.nil?
|
16
|
+
OsaScriptApp.new(app_bundle_id)
|
17
|
+
else
|
18
|
+
app_class.new(app_bundle_id)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "json"
|
4
|
+
|
5
|
+
module TimeBuffer
|
6
|
+
class SessionHandler
|
7
|
+
def initialize(start_time:, end_time:)
|
8
|
+
@start_time = start_time
|
9
|
+
@end_time = end_time
|
10
|
+
end
|
11
|
+
|
12
|
+
def insert_time_session(app_data)
|
13
|
+
application_id = TimeBuffer::DatabaseConnector.new.execute(
|
14
|
+
"SELECT id FROM applications WHERE bundle_id = ?",
|
15
|
+
[app_data.bundle_id]
|
16
|
+
).first&.first
|
17
|
+
|
18
|
+
puts "application ID: #{application_id}"
|
19
|
+
|
20
|
+
TimeBuffer::DatabaseConnector.new.execute(
|
21
|
+
"INSERT INTO time_sessions (application_id, start_time, end_time, metadata) VALUES (?, ?, ?, ?);",
|
22
|
+
[application_id, start_time.strftime("%Y-%m-%d %H:%M:%S"), end_time.strftime("%Y-%m-%d %H:%M:%S"), app_data.metadata.to_json]
|
23
|
+
)
|
24
|
+
end
|
25
|
+
|
26
|
+
private
|
27
|
+
|
28
|
+
attr_reader :start_time, :end_time
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module TimeBuffer
|
4
|
+
class StateDetector
|
5
|
+
attr_reader :current_app_data, :last_interaction_at, :previous_app, :previous_metadata
|
6
|
+
|
7
|
+
def initialize
|
8
|
+
@previous_app = nil
|
9
|
+
@previous_metadata = {}
|
10
|
+
@last_interaction_at = Time.now
|
11
|
+
@current_app_data = nil
|
12
|
+
end
|
13
|
+
|
14
|
+
def app_changed?
|
15
|
+
@current_app_data = OsaScript.app_data
|
16
|
+
@current_app_data.name != @previous_app
|
17
|
+
end
|
18
|
+
|
19
|
+
def metadata_changed?
|
20
|
+
metadata = current_app_data.metadata
|
21
|
+
metadata && metadata != previous_metadata && current_app_data.name == previous_app
|
22
|
+
end
|
23
|
+
|
24
|
+
def update_last_interaction_time(now)
|
25
|
+
@last_interaction_at = now
|
26
|
+
update_state
|
27
|
+
end
|
28
|
+
|
29
|
+
private
|
30
|
+
|
31
|
+
def update_state
|
32
|
+
@previous_app = current_app_data.name
|
33
|
+
@previous_metadata = current_app_data.metadata
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
data/lib/time_buffer.rb
ADDED
@@ -0,0 +1,48 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "time_buffer/application_handler"
|
4
|
+
require_relative "time_buffer/database_connector"
|
5
|
+
require_relative "time_buffer/osa_script"
|
6
|
+
require_relative "time_buffer/osa_script_app"
|
7
|
+
require_relative "time_buffer/osa_script_app_browser"
|
8
|
+
require_relative "time_buffer/osa_script_app_builder"
|
9
|
+
require_relative "time_buffer/session_handler"
|
10
|
+
require_relative "time_buffer/state_detector"
|
11
|
+
require_relative "time_buffer/version"
|
12
|
+
require "sqlite3"
|
13
|
+
module TimeBuffer
|
14
|
+
class Tracker
|
15
|
+
def initialize
|
16
|
+
@state_detector = StateDetector.new
|
17
|
+
end
|
18
|
+
|
19
|
+
def start
|
20
|
+
loop do
|
21
|
+
now = Time.now
|
22
|
+
|
23
|
+
if state_detector.app_changed?
|
24
|
+
handle_app_change(state_detector.current_app_data, now)
|
25
|
+
elsif state_detector.metadata_changed?
|
26
|
+
handle_metadata_change(state_detector.current_app_data, now)
|
27
|
+
end
|
28
|
+
|
29
|
+
sleep 1
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
private
|
34
|
+
|
35
|
+
attr_reader :state_detector
|
36
|
+
|
37
|
+
def handle_app_change(app_data, now)
|
38
|
+
ApplicationHandler.new(app_data).insert_application
|
39
|
+
SessionHandler.new(start_time: state_detector.last_interaction_at, end_time: now).insert_time_session(app_data)
|
40
|
+
state_detector.update_last_interaction_time(now)
|
41
|
+
end
|
42
|
+
|
43
|
+
def handle_metadata_change(app_data, now)
|
44
|
+
SessionHandler.new(start_time: state_detector.last_interaction_at, end_time: now).insert_time_session(app_data)
|
45
|
+
state_detector.update_last_interaction_time(now)
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
data/sig/time_buffer.rbs
ADDED
metadata
ADDED
@@ -0,0 +1,77 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: time_buffer
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Steven Elberger
|
8
|
+
autorequire:
|
9
|
+
bindir: exe
|
10
|
+
cert_chain: []
|
11
|
+
date: 2024-08-21 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: sqlite3
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '2.0'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '2.0'
|
27
|
+
description:
|
28
|
+
email:
|
29
|
+
- stevenelberger@gmail.com
|
30
|
+
executables: []
|
31
|
+
extensions: []
|
32
|
+
extra_rdoc_files: []
|
33
|
+
files:
|
34
|
+
- ".rspec"
|
35
|
+
- ".solargraph.yml"
|
36
|
+
- ".standard.yml"
|
37
|
+
- CHANGELOG.md
|
38
|
+
- README.md
|
39
|
+
- Rakefile
|
40
|
+
- lib/time_buffer.rb
|
41
|
+
- lib/time_buffer/application_handler.rb
|
42
|
+
- lib/time_buffer/database_connector.rb
|
43
|
+
- lib/time_buffer/osa_script.rb
|
44
|
+
- lib/time_buffer/osa_script_app.rb
|
45
|
+
- lib/time_buffer/osa_script_app_browser.rb
|
46
|
+
- lib/time_buffer/osa_script_app_builder.rb
|
47
|
+
- lib/time_buffer/session_handler.rb
|
48
|
+
- lib/time_buffer/state_detector.rb
|
49
|
+
- lib/time_buffer/version.rb
|
50
|
+
- sig/time_buffer.rbs
|
51
|
+
homepage: https://github.com/StevenElberger/time_buffer
|
52
|
+
licenses: []
|
53
|
+
metadata:
|
54
|
+
allowed_push_host: https://rubygems.org
|
55
|
+
homepage_uri: https://github.com/StevenElberger/time_buffer
|
56
|
+
source_code_uri: https://github.com/StevenElberger/time_buffer
|
57
|
+
changelog_uri: https://github.com/StevenElberger/time_buffer/blob/main/CHANGELOG.md
|
58
|
+
post_install_message:
|
59
|
+
rdoc_options: []
|
60
|
+
require_paths:
|
61
|
+
- lib
|
62
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
63
|
+
requirements:
|
64
|
+
- - ">="
|
65
|
+
- !ruby/object:Gem::Version
|
66
|
+
version: 3.0.0
|
67
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
68
|
+
requirements:
|
69
|
+
- - ">="
|
70
|
+
- !ruby/object:Gem::Version
|
71
|
+
version: '0'
|
72
|
+
requirements: []
|
73
|
+
rubygems_version: 3.5.17
|
74
|
+
signing_key:
|
75
|
+
specification_version: 4
|
76
|
+
summary: Tracks time spent on all applications
|
77
|
+
test_files: []
|