kaede 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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