kaede 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.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 763474045525030a9ac8d0ef492f763f325dee9b
4
+ data.tar.gz: 03949c0dfd3f2c3e6c7764b36c08c2c9943d79e7
5
+ SHA512:
6
+ metadata.gz: a513df57390c5a072726e9c9aeeaffe62400f3fe69dd0a2d91f191d2bf8d058edda6b0c7487d8168ce48eadd20345c1dc69a5a2d0937021966d80217c774e8cc
7
+ data.tar.gz: b5dc2c6b26e45d3b81e7bf34b72b1ac161d2b63567a8684e55b49b38b274af1aab7d04d7298861441cf30514627691b1b78ae802ede2378405b5623de21967b0
@@ -0,0 +1,17 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --color
2
+ --require spec_helper
@@ -0,0 +1,14 @@
1
+ language: ruby
2
+ rvm:
3
+ - 1.9.3
4
+ - 2.0.0
5
+ - 2.1.1
6
+ - ruby-head
7
+ before_install:
8
+ - export NOKOGIRI_USE_SYSTEM_LIBRARIES=1
9
+ before_script:
10
+ - bundle exec bin/kaede dbus-policy $USER > kaede.conf
11
+ - sudo mv kaede.conf /etc/dbus-1/system.d/kaede.conf
12
+ matrix:
13
+ allow_failures:
14
+ - rvm: ruby-head
@@ -0,0 +1,4 @@
1
+ # ChangeLog
2
+
3
+ ## 0.1.0 (2014-05-06)
4
+ - Initial release
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in kaede.gemspec
4
+ gemspec
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2014 Kohei Suzuki
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,109 @@
1
+ # Kaede
2
+ [![Build Status](https://api.travis-ci.org/eagletmt/kaede.svg)](https://travis-ci.org/eagletmt/kaede)
3
+ [![Coverage Status](https://coveralls.io/repos/eagletmt/kaede/badge.png)](https://coveralls.io/r/eagletmt/kaede)
4
+ [![Code Climate](https://codeclimate.com/github/eagletmt/kaede.png)](https://codeclimate.com/github/eagletmt/kaede)
5
+
6
+ Scheduler for recpt1 recorder using [Syoboi Calendar](http://cal.syoboi.jp/).
7
+
8
+ ## Installation
9
+
10
+ Add this line to your application's Gemfile:
11
+
12
+ gem 'kaede'
13
+
14
+ And then execute:
15
+
16
+ $ bundle
17
+
18
+ Or install it yourself as:
19
+
20
+ $ gem install kaede
21
+
22
+ ## Usage
23
+ ### Requirements
24
+ - sqlite3
25
+ - redis
26
+ - dbus
27
+ - recpt1
28
+ - b25
29
+ - [statvfs](https://github.com/eagletmt/eagletmt-recutils/tree/master/statvfs)
30
+ - [clean-ts](https://github.com/eagletmt/eagletmt-recutils/tree/master/clean-ts)
31
+ - [assdumper](https://github.com/eagletmt/eagletmt-recutils/tree/master/assdumper)
32
+
33
+ Some of them should be optional, though.
34
+
35
+ ### Setup
36
+ ```sh
37
+ kaede dbus-policy $KAEDE_USER > kaede.conf
38
+ sudo mv kaede.conf /etc/dbus-1/system.d/kaede.conf
39
+
40
+ cp kaede.rb.sample kaede.rb
41
+ vim kaede.rb
42
+
43
+ cp kaede.service.sample kaede.service
44
+ vim kaede.service
45
+
46
+ sudo cp kaede.service /etc/systemd/system/kaede.service
47
+ sudo systemctl enable kaede.service
48
+ sudo systemctl start kaede.service
49
+ ```
50
+
51
+ Add your available channels.
52
+
53
+ ```sh
54
+ kaede add-channel MX -c kaede.rb --recorder 16 --syoboi 19
55
+ kaede add-channel BS11 -c kaede.rb --recorder 211 --syoboi 128
56
+ ...
57
+ ```
58
+
59
+ Add your favorite anime tids.
60
+
61
+ ```sh
62
+ kaede add-tid -c kaede.rb 3331
63
+ ...
64
+ ```
65
+
66
+ ### Operations
67
+ Update programs and schedules. It supposed to be run periodically (by cron or systemd.timer).
68
+
69
+ ```sh
70
+ kaede update -c kaede.rb
71
+ ```
72
+
73
+ List schedules.
74
+
75
+ ```sh
76
+ gdbus introspect --system --dest cc.wanko.kaede1 --object-path /cc/wanko/kaede1/program -r
77
+ ```
78
+
79
+ Reload schedules (usually not needed).
80
+
81
+ ```sh
82
+ dbus-send --system --dest=cc.wanko.kaede1 /cc/wanko/kaede1/scheduler cc.wanko.kaede1.Scheduler.Reload
83
+ ```
84
+
85
+ Stop scheduler. The current scheduler process exits after all the running recorders finish.
86
+
87
+ ```sh
88
+ dbus-send --system --dest=cc.wanko.kaede1 /cc/wanko/kaede1/scheduler cc.wanko.kaede1.Scheduler.Stop
89
+ ```
90
+
91
+ ## What recorder does
92
+ 1. Post the earlier tweet (optional).
93
+ 2. Record the program into `record_dir` by recpt1.
94
+ - At the same time, decode into `cache_dir` by b25.
95
+ - At the same time, dump ass into `cache_dir` by assdumper.
96
+ 3. Post the later tweet (optional).
97
+ 4. Clean the recorded TS (in `cache_dir`) into `cabinet_dir`.
98
+ 5. Move dumped ass (in `cache_dir`) into `cabinet_dir`.
99
+ 6. Enqueue the filename into `redis_queue`.
100
+ - Use it as an encoder queue.
101
+ - My usage: https://github.com/eagletmt/eagletmt-recutils/tree/master/encoder
102
+
103
+ ## Contributing
104
+
105
+ 1. Fork it ( https://github.com/eagletmt/kaede/fork )
106
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
107
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
108
+ 4. Push to the branch (`git push origin my-new-feature`)
109
+ 5. Create new Pull Request
@@ -0,0 +1,6 @@
1
+ require "bundler/gem_tasks"
2
+
3
+ task :default => :spec
4
+
5
+ require 'rspec/core/rake_task'
6
+ RSpec::Core::RakeTask.new(:spec)
@@ -0,0 +1,4 @@
1
+ #!/usr/bin/env ruby
2
+ require 'kaede/cli'
3
+
4
+ Kaede::CLI.start(ARGV)
@@ -0,0 +1,36 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'kaede/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "kaede"
8
+ spec.version = Kaede::VERSION
9
+ spec.authors = ["Kohei Suzuki"]
10
+ spec.email = ["eagletmt@gmail.com"]
11
+ spec.summary = %q{Scheduler for recpt1 recorder using Syoboi Calendar}
12
+ spec.description = %q{Scheduler for recpt1 recorder using Syoboi Calendar}
13
+ spec.homepage = "https://github.com/eagletmt/kaede"
14
+ spec.license = "MIT"
15
+
16
+ spec.files = `git ls-files -z`.split("\x0")
17
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
+ spec.require_paths = ["lib"]
20
+
21
+ spec.add_development_dependency "bundler"
22
+ spec.add_development_dependency "coveralls"
23
+ spec.add_development_dependency "rake"
24
+ spec.add_development_dependency "rspec", "~> 3.0.0.beta2"
25
+ spec.add_development_dependency "simplecov"
26
+ spec.add_development_dependency "timecop"
27
+ spec.add_development_dependency "vcr"
28
+ spec.add_development_dependency "webmock"
29
+ spec.add_dependency "ruby-dbus"
30
+ spec.add_dependency "nokogiri"
31
+ spec.add_dependency "redis"
32
+ spec.add_dependency "sleepy_penguin"
33
+ spec.add_dependency "sqlite3"
34
+ spec.add_dependency "thor"
35
+ spec.add_dependency "twitter"
36
+ end
@@ -0,0 +1,25 @@
1
+ require 'redis'
2
+ require 'twitter'
3
+
4
+ Kaede.configure do |config|
5
+ config.b25 = '/usr/bin/b25'
6
+ config.recpt1 = '/usr/bin/recpt1'
7
+ config.assdumper = '/home/eagletmt/bin/assdumper'
8
+ config.clean_ts = '/home/eagletmt/bin/clean-ts'
9
+ config.statvfs = '/home/eagletmt/bin/statvfs'
10
+ config.database_path = '/home/eagletmt/work/kaede/kaede.db'
11
+ config.record_dir = '/home/pt'
12
+ config.cache_dir = '/home/pt/cache'
13
+ config.cabinet_dir = '/home/pt'
14
+ config.redis = Redis.new(db: 1)
15
+ config.redis_queue = 'jobs'
16
+ config.twitter = Twitter::REST::Client.new do |config|
17
+ config.consumer_key = 'CONSUMER_KEY'
18
+ config.consumer_secret = 'CONSUMER_SECRET'
19
+ config.access_token = 'ACCESS_TOKEN'
20
+ config.access_token_secret = 'ACCESS_TOKEN_SECRET'
21
+ end
22
+ config.twitter_target = 'eagletmt'
23
+ end
24
+
25
+ # vim: set ft=ruby:
@@ -0,0 +1,16 @@
1
+ [Unit]
2
+ Description=Kaede Recorder
3
+
4
+ [Service]
5
+ WorkingDirectory=/home/eagletmt/work/kaede
6
+ User=eagletmt
7
+ ExecStart=/usr/bin/bundle exec bin/kaede scheduler -c kaede.rb
8
+ ExecReload=/usr/bin/kill -HUP $MAINPID
9
+ ExecStop=/usr/bin/dbus-send --print-reply --system --dest=cc.wanko.kaede1 /cc/wanko/kaede1/scheduler cc.wanko.kaede1.Scheduler.Stop
10
+ Restart=always
11
+ KillMode=none
12
+ StandardOutput=journal
13
+ StandardError=journal
14
+
15
+ [Install]
16
+ WantedBy=multi-user.target
@@ -0,0 +1,12 @@
1
+ require 'kaede/config'
2
+ require 'kaede/version'
3
+
4
+ module Kaede
5
+ def self.configure(&block)
6
+ block.call(config)
7
+ end
8
+
9
+ def self.config
10
+ @config ||= Config.new
11
+ end
12
+ end
@@ -0,0 +1,4 @@
1
+ module Kaede
2
+ class Channel < Struct.new(:id, :name, :for_recorder, :for_syoboi)
3
+ end
4
+ end
@@ -0,0 +1,81 @@
1
+ require 'thor'
2
+
3
+ module Kaede
4
+ class CLI < Thor
5
+ package_name 'kaede'
6
+
7
+ class_option :config,
8
+ desc: 'Path to config file',
9
+ banner: 'PATH',
10
+ type: :string,
11
+ aliases: :c
12
+
13
+ desc 'scheduler', 'Start scheduler'
14
+ def scheduler
15
+ require 'kaede/database'
16
+ require 'kaede/scheduler'
17
+ load_config
18
+
19
+ db = Kaede::Database.new(Kaede.config.database_path)
20
+ Kaede::Scheduler.setup(db)
21
+ Kaede::Scheduler.start
22
+ end
23
+
24
+ desc 'add-channel NAME', 'Add available channel'
25
+ option :recorder,
26
+ desc: 'Channel number for the recorder',
27
+ banner: 'CH',
28
+ type: :numeric,
29
+ required: true
30
+ option :syoboi,
31
+ desc: 'Channel number for Syoboi Calendar',
32
+ banner: 'CH',
33
+ type: :numeric,
34
+ required: true
35
+ def add_channel(name)
36
+ require 'kaede/database'
37
+ require 'kaede/channel'
38
+ load_config
39
+
40
+ db = Kaede::Database.new(Kaede.config.database_path)
41
+ db.add_channel(Channel.new(nil, name, options[:recorder], options[:syoboi]))
42
+ end
43
+
44
+ desc 'add-tid TID', 'Add tracking title'
45
+ def add_tid(tid)
46
+ require 'kaede/database'
47
+ load_config
48
+
49
+ db = Kaede::Database.new(Kaede.config.database_path)
50
+ db.add_tracking_title(tid.to_i)
51
+ end
52
+
53
+ desc 'update', 'Update jobs and programs by Syoboi Calendar'
54
+ def update
55
+ require 'kaede/database'
56
+ require 'kaede/syoboi_calendar'
57
+ require 'kaede/updater'
58
+ load_config
59
+
60
+ db = Kaede::Database.new(Kaede.config.database_path)
61
+ syobocal = Kaede::SyoboiCalendar.new
62
+ Kaede::Updater.new(db, syobocal).update
63
+ end
64
+
65
+ desc 'dbus-policy USER', 'Generate dbus policy file'
66
+ def dbus_policy(user)
67
+ require 'kaede/dbus/generator'
68
+
69
+ puts DBus::Generator.new.generate_policy(user)
70
+ end
71
+
72
+ private
73
+
74
+ def load_config
75
+ require 'kaede'
76
+ if path = options[:config]
77
+ load File.realpath(path)
78
+ end
79
+ end
80
+ end
81
+ end
@@ -0,0 +1,33 @@
1
+ require 'pathname'
2
+ require 'redis'
3
+
4
+ module Kaede
5
+ class Config
6
+ attr_accessor :redis, :redis_queue, :twitter, :twitter_target
7
+
8
+ path_attrs = [:b25, :recpt1, :assdumper, :clean_ts, :statvfs, :database_path, :record_dir, :cache_dir, :cabinet_dir]
9
+ attr_reader *path_attrs
10
+ path_attrs.each do |attr|
11
+ define_method("#{attr}=") do |arg|
12
+ instance_variable_set("@#{attr}", Pathname.new(arg))
13
+ end
14
+ end
15
+
16
+ def initialize
17
+ self.b25 = '/usr/bin/b25'
18
+ self.recpt1 = '/usr/bin/recpt1'
19
+ self.assdumper = '/usr/bin/assdumper'
20
+ self.clean_ts = '/usr/bin/clean-ts'
21
+ self.statvfs = '/usr/bin/statvfs'
22
+ basedir = Pathname.new(ENV['HOME']).join('kaede')
23
+ self.database_path = basedir.join('kaede.db')
24
+ self.record_dir = basedir.join('records')
25
+ self.cache_dir = basedir.join('cache')
26
+ self.cabinet_dir = basedir.join('cabinet')
27
+ self.twitter = nil
28
+ self.twitter_target = nil
29
+ self.redis = Redis.new
30
+ self.redis_queue = 'jobs'
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,146 @@
1
+ require 'forwardable'
2
+ require 'sqlite3'
3
+ require 'kaede/channel'
4
+ require 'kaede/program'
5
+
6
+ module Kaede
7
+ class Database
8
+ extend Forwardable
9
+ def_delegators :@db, :transaction
10
+
11
+ def initialize(path)
12
+ @db = SQLite3::Database.new(path.to_s)
13
+ @db.send(:set_boolean_pragma, 'foreign_keys', true)
14
+ prepare_tables
15
+ end
16
+
17
+ def prepare_tables
18
+ @db.execute_batch <<-SQL
19
+ CREATE TABLE IF NOT EXISTS channels (
20
+ id integer PRIMARY KEY AUTOINCREMENT,
21
+ name varchar(255) NOT NULL UNIQUE,
22
+ for_recorder integer NOT NULL UNIQUE,
23
+ for_syoboi integer NOT NULL UNIQUE
24
+ );
25
+ CREATE TABLE IF NOT EXISTS programs (
26
+ pid integer PRIMARY KEY ON CONFLICT REPLACE,
27
+ tid integer NOT NULL,
28
+ start_time datetime NOT NULL,
29
+ end_time datetime NOT NULL,
30
+ channel_id integer NOT NULL,
31
+ count varchar(16),
32
+ start_offset integer NOT NULL,
33
+ subtitle varchar(255),
34
+ title varchar(255),
35
+ comment varchar(255),
36
+ FOREIGN KEY(channel_id) REFERENCES channels(id)
37
+ );
38
+ CREATE TABLE IF NOT EXISTS jobs (
39
+ pid integer PRIMARY KEY,
40
+ enqueued_at datetime NOT NULL,
41
+ finished_at datetime,
42
+ created_at datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
43
+ FOREIGN KEY(pid) REFERENCES programs(pid)
44
+ );
45
+ CREATE TABLE IF NOT EXISTS tracking_titles (
46
+ tid integer NOT NULL UNIQUE,
47
+ created_at datetime NOT NULL DEFAULT CURRENT_TIMESTAMP
48
+ );
49
+ SQL
50
+ end
51
+ private :prepare_tables
52
+
53
+ DATETIME_FORMAT = '%Y-%m-%d %H:%M:%S'
54
+
55
+ def to_db_datetime(time)
56
+ time.utc.strftime(DATETIME_FORMAT)
57
+ end
58
+
59
+ def from_db_datetime(str)
60
+ Time.parse("#{str} UTC").localtime
61
+ end
62
+
63
+ def current_timestamp
64
+ to_db_datetime(Time.now)
65
+ end
66
+ private :current_timestamp
67
+
68
+ def get_jobs
69
+ @db.execute('SELECT pid, enqueued_at FROM jobs WHERE finished_at IS NULL AND enqueued_at >= ? ORDER BY enqueued_at', [current_timestamp]).map do |pid, enqueued_at|
70
+ { pid: pid, enqueued_at: from_db_datetime(enqueued_at) }
71
+ end
72
+ end
73
+
74
+ def update_job(pid, enqueued_at)
75
+ @db.execute('INSERT OR REPLACE INTO jobs (pid, enqueued_at, created_at) VALUES (?, ?, ?)', [pid, to_db_datetime(enqueued_at), current_timestamp])
76
+ end
77
+
78
+ def delete_job(pid)
79
+ @db.execute('DELETE FROM jobs WHERE pid = ?', pid)
80
+ end
81
+
82
+ def get_program(pid)
83
+ get_programs([pid])[pid]
84
+ end
85
+
86
+ def get_programs(pids)
87
+ rows = @db.execute(<<-SQL)
88
+ SELECT pid, tid, start_time, end_time, channels.name, for_syoboi, for_recorder, count, start_offset, subtitle, title, comment
89
+ FROM programs
90
+ INNER JOIN channels ON programs.channel_id = channels.id
91
+ WHERE programs.pid IN (#{pids.join(', ')})
92
+ SQL
93
+ programs = {}
94
+ rows.each do |row|
95
+ program = Program.new(*row)
96
+ program.start_time = from_db_datetime(program.start_time)
97
+ program.end_time = from_db_datetime(program.end_time)
98
+ programs[program.pid] = program
99
+ end
100
+ programs
101
+ end
102
+
103
+ def mark_finished(pid)
104
+ @db.execute('UPDATE jobs SET finished_at = ? WHERE pid = ?', [current_timestamp, pid])
105
+ end
106
+
107
+ def get_channels
108
+ @db.execute('SELECT * FROM channels').map do |row|
109
+ Channel.new(*row)
110
+ end
111
+ end
112
+
113
+ def add_channel(channel)
114
+ @db.execute('INSERT INTO channels (name, for_recorder, for_syoboi) VALUES (?, ?, ?)', [channel.name, channel.for_recorder, channel.for_syoboi])
115
+ end
116
+
117
+ def update_program(program, channel)
118
+ row = [
119
+ program.pid,
120
+ program.tid,
121
+ to_db_datetime(program.start_time),
122
+ to_db_datetime(program.end_time),
123
+ channel.id,
124
+ program.count,
125
+ program.start_offset,
126
+ program.subtitle,
127
+ program.title,
128
+ program.comment,
129
+ ]
130
+ @db.execute(<<-SQL, row)
131
+ INSERT INTO programs (pid, tid, start_time, end_time, channel_id, count, start_offset, subtitle, title, comment)
132
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
133
+ SQL
134
+ end
135
+
136
+ def add_tracking_title(tid)
137
+ @db.execute('INSERT INTO tracking_titles (tid, created_at) VALUES (?, ?)', [tid, current_timestamp])
138
+ end
139
+
140
+ def get_tracking_titles
141
+ @db.execute('SELECT tid FROM tracking_titles').map do |row|
142
+ row[0]
143
+ end
144
+ end
145
+ end
146
+ end