jeeves-pvr 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,41 @@
1
+ #
2
+ #
3
+ # = Jeeves CLI
4
+ #
5
+ # == Listings related actions
6
+ #
7
+ # Author:: Robert Sharp
8
+ # Copyright:: Copyright (c) 2014 Robert Sharp
9
+ # License:: Open Software Licence v3.0
10
+ #
11
+ # This software is licensed for use under the Open Software Licence v. 3.0
12
+ # The terms of this licence can be found at http://www.opensource.org/licenses/osl-3.0.php
13
+ # and in the file copyright.txt. Under the terms of this licence, all derivative works
14
+ # must themselves be licensed under the Open Software Licence v. 3.0
15
+ #
16
+ #
17
+ #
18
+ require 'optplus/nested'
19
+
20
+ class ListingCLI < Optplus::NestedParser
21
+
22
+ usage "listings [update|clean]"
23
+
24
+ description "manage TV listings in Jeeves PVR"
25
+
26
+ def before_actions
27
+ @jeeves_config = @_parent.jeeves_config
28
+ end
29
+
30
+ describe :update, 'update TV listings'
31
+ help :update, 'download TV listings data and load into Jeeves'
32
+
33
+ def update
34
+ @jeeves_config[:jeeves_uri_timeout] = get_option(:timeout) if option?(:timeout)
35
+ @jeeves_config[:update_listings] = get_option(:update_listings) if option?(:update_listings)
36
+ Jeeves.update_listings(@jeeves_config)
37
+ rescue Jeeves::ListingError => e
38
+ exit_on_error "Error while updating listings #{e}"
39
+ end
40
+
41
+ end
@@ -0,0 +1,167 @@
1
+ #
2
+ #
3
+ # = Jeeves Store CLI
4
+ #
5
+ # == Jeeves
6
+ #
7
+ # Author:: Robert Sharp
8
+ # Copyright:: Copyright (c) 2014 Robert Sharp
9
+ # License:: Open Software Licence v3.0
10
+ #
11
+ # This software is licensed for use under the Open Software Licence v. 3.0
12
+ # The terms of this licence can be found at http://www.opensource.org/licenses/osl-3.0.php
13
+ # and in the file copyright.txt. Under the terms of this licence, all derivative works
14
+ # must themselves be licensed under the Open Software Licence v. 3.0
15
+ #
16
+ #
17
+ #
18
+
19
+ require 'optplus/nested'
20
+
21
+ class StoreCLI < Optplus::NestedParser
22
+
23
+ include Jeni::IO
24
+
25
+ usage "store command"
26
+
27
+ description "manage Jeeves file stores"
28
+
29
+ def before_actions
30
+ @jeeves_config = @_parent.jeeves_config
31
+ @logger = @_parent.logger
32
+ end
33
+
34
+ describe :status, 'show status of Jeeves file stores'
35
+ help :status, 'display basic information about the file stores',
36
+ 'currently available for Jeeves.'
37
+
38
+ def status
39
+ parts = Array.new
40
+ devices = Array.new
41
+ @jeeves_config[:add_partition].each_pair do |path, params|
42
+ part = Jeeves::Partition.new(path, params)
43
+ if part.ready? then
44
+ if devices.include?(part.device) then
45
+ part.duplicate = true
46
+ else
47
+ devices << part.device
48
+ end
49
+
50
+ end
51
+ parts << part.to_a
52
+ end
53
+ Jeeves.tabulate(%w{* 4c 4c 4c 8r 8r},
54
+ %w{Path Mnt Rdy Dup Total Free},
55
+ parts
56
+ )
57
+ end
58
+
59
+ describe :list, 'list pre-allocated files in the Jeeves store'
60
+ help :list, 'list all of the pre-allocated files',
61
+ 'that Jeeves currently knows about. These may',
62
+ 'include files that were not released and those',
63
+ 'that have not been recorded yet.'
64
+
65
+ def list
66
+ jestore = Jeeves::Store.new(@logger, @jeeves_config)
67
+ if jestore.files <= 0 then
68
+ puts "There are no files to show"
69
+ else
70
+ entries = Array.new
71
+ colours = Array.new
72
+ compare_time = Time.now
73
+ jestore.each_file do |path, params|
74
+ entries << [path, Jeeves.human_size(params[:space]), params[:date].strftime("%Y-%b-%d %H:%M")]
75
+ if params[:date] < compare_time then
76
+ if FileTest.exists?(path) then
77
+ colours << :yellow
78
+ else
79
+ colours << :red
80
+ end
81
+ else
82
+ colours << :green
83
+ end
84
+ end
85
+ Jeeves.tabulate(%w{* *r *c},
86
+ %w{Path Space Date},
87
+ entries, colours)
88
+ end
89
+ end
90
+
91
+
92
+ describe :clean, 'clean old records from the list of files'
93
+ help :clean, 'deletes any records in the Jeeves file store',
94
+ 'for files whose allocation date has now expired'
95
+
96
+ def clean
97
+ jestore = Jeeves::Store.new(@logger, @jeeves_config)
98
+ if get_option :delete then
99
+ # going to get rid of all file lists
100
+ if ask('Delete all file lists?', :no, 'yn') == :yes then
101
+
102
+ jestore.each_partition do |part|
103
+ if FileTest.exists?(part.file_list) then
104
+ puts "Deleting #{part.file_list}"
105
+ FileUtils.rm_f(part.file_list)
106
+ end
107
+ end
108
+
109
+ else
110
+
111
+ puts "OK, no files have been deleted"
112
+
113
+ end
114
+ return
115
+ end
116
+
117
+ # jeeves store clear
118
+ if jestore.files == 0 then
119
+ puts "The file list is empty, nothing to clean".yellow
120
+ return
121
+ end
122
+ if get_option :clean_all then
123
+ if ask('Clean all files from the list?', :no, 'yn') != :no then
124
+ jestore.clean(true)
125
+ puts "The file list is now empty".green
126
+ end
127
+ else
128
+ if jestore.stale_files == 0 then
129
+ puts "There are no files to clean!".yellow
130
+ return
131
+ end
132
+ if ask('Clean files from the list?', :no, 'yn') != :no then
133
+ jestore.clean
134
+ puts "Cleaned file list".green
135
+ end
136
+ end
137
+
138
+ end
139
+
140
+ describe :init, 'initialise a partition for use by Jeeves'
141
+ help :init, 'This will set up a partition to be used by Jeeves'
142
+
143
+ def init
144
+ new_store = next_argument_or_error("need to provide a path to initialise")
145
+
146
+ unless FileTest.exists?(new_store)
147
+ exit_on_error "path #{new_store} does not exist"
148
+ end
149
+
150
+ part = Jeeves::Partition.new(new_store)
151
+
152
+ if get_option :delete then
153
+ part.delete_key
154
+ return
155
+ end
156
+
157
+ force = get_option :force_init
158
+ puts "Forcing a new key!".red.bold if force
159
+ key = part.create_key(force)
160
+ puts ""
161
+ puts "# add this line to your config file"
162
+ puts "add_store :path=>'#{part.path}', :key=>'#{key}'"
163
+ rescue Jeeves::InvalidPartitionKey => err
164
+ exit_on_error err
165
+ end
166
+
167
+ end
@@ -0,0 +1,136 @@
1
+ #
2
+ #
3
+ # = Jeeves
4
+ #
5
+ # == Video CLI
6
+ #
7
+ # Author:: Robert Sharp
8
+ # Copyright:: Copyright (c) 2014 Robert Sharp
9
+ # License:: Open Software Licence v3.0
10
+ #
11
+ # This software is licensed for use under the Open Software Licence v. 3.0
12
+ # The terms of this licence can be found at http://www.opensource.org/licenses/osl-3.0.php
13
+ # and in the file copyright.txt. Under the terms of this licence, all derivative works
14
+ # must themselves be licensed under the Open Software Licence v. 3.0
15
+ #
16
+ #
17
+ #
18
+
19
+ require 'optplus/nested'
20
+
21
+ class VideosCLI < Optplus::NestedParser
22
+
23
+ include Jeni::IO
24
+
25
+ usage "videos <command> [<options>]"
26
+
27
+ description "Do things with videos, such as load them into Jeeves."
28
+
29
+ def before_actions
30
+ @jeeves_config = @_parent.jeeves_config
31
+ @logger = @_parent.logger
32
+ end
33
+
34
+
35
+ describe :show, 'show information about a Jeeves-ready video'
36
+
37
+ def show
38
+ file = next_argument_or_error "Need to provide a file to show"
39
+
40
+ unless FileTest.exists?(file)
41
+ exit_on_error "File does not exist: #{file}"
42
+ end
43
+
44
+ vid = Jeeves::Video.new(file, @logger, @jeeves_config)
45
+ tags = vid.get_tags
46
+ strs = Array.new
47
+
48
+ %w{programme subtitle category}.each do |key|
49
+ strs << [key.capitalize, tags.delete(key)] if tags.has_tag?(key)
50
+ end
51
+
52
+ unless tags.has_tag?('description')
53
+ exit_on_error "This file has not been tagged for Jeeves"
54
+ end
55
+
56
+ width = 80
57
+ descs = [tags.delete('description')]
58
+ if descs[0].length > width then
59
+ descs = descs[0].scan(/\S.{0,#{width}}\S(?=\s|$)|\S+/)
60
+ end
61
+
62
+ first = true
63
+ descs.each do |desc|
64
+ strs << [first ? 'Description' : '', desc]
65
+ first = false
66
+ end
67
+
68
+ strs << ['','']
69
+
70
+ strs << ['Start', Time.at(tags.delete('start').to_i).strftime('%a %-d %b %y, %H:%M')]
71
+ strs << ['Duration', Jeeves.hrs_mins(tags.delete('duration').to_i)]
72
+
73
+ tags.each_tag do |key, value|
74
+ strs << [key, value]
75
+ end
76
+
77
+ Jeeves.tabulate(%w{* 80}, %w{Key Contents}, strs)
78
+ end
79
+
80
+
81
+ describe :load, 'load an existing video into Jeeves'
82
+ help :load, 'Use load for a file that already has the tags relevant to Jeeves',
83
+ 'and just needs to be added to the database.'
84
+
85
+ def load
86
+ file = next_argument_or_error "Need to provide a file to load"
87
+
88
+ unless FileTest.exists?(file)
89
+ exit_on_error "File does not exist: #{file}"
90
+ end
91
+ vid = Jeeves::Video.new(file, @logger, @jeeves_config)
92
+
93
+ response = vid.upload_to_jeeves
94
+ vid.clean_up
95
+ if response.to_i == 0 then
96
+ puts "Jeeves OK".green
97
+
98
+ else
99
+ exit_on_error "Jeeves returned unexpected response: #{response}"
100
+ end
101
+ rescue Jeeves::JeevesError => e
102
+ exit_on_error "Error while loading video: #{e}"
103
+ end
104
+
105
+ describe :wrangle, 'tag and load a video into Jeeves'
106
+ help :load, 'Obtain metadata from Jeeves and tag a video with it',
107
+ 'before loading into Jeeves.'
108
+
109
+ def wrangle
110
+
111
+ file = next_argument_or_error "Need to provide a file to wrangle"
112
+ unless FileTest.exists?(file)
113
+ exit_on_error "File does not exist: #{file}"
114
+ end
115
+ pid = next_argument_or_error "Need to provide a programme id to wrangle"
116
+ vid = Jeeves::Video.new(file, @logger, @jeeves_config)
117
+ vid.tag_from_prog_id(pid)
118
+ response = vid.upload_to_jeeves
119
+ vid.clean_up
120
+ if response.to_i == 0 then
121
+ puts "Jeeves OK"
122
+
123
+ else
124
+ exit_on_error "Jeeves returned unexpected response: #{response}"
125
+ end
126
+ rescue Jeeves::JeevesError => e
127
+ exit_on_error "Error while wrangling video: #{e}"
128
+ end
129
+
130
+ describe :orphans, 'find files that have no entry in Jeeves'
131
+
132
+ def orphans
133
+
134
+ end
135
+
136
+ end
@@ -0,0 +1,174 @@
1
+ #
2
+ #
3
+ # = Jeeves Disk
4
+ #
5
+ # == a single area to store files and part of a set of disks making up a store
6
+ #
7
+ # Author:: Robert Sharp
8
+ # Copyright:: Copyright (c) 2013 Robert Sharp
9
+ # License:: Open Software Licence v3.0
10
+ #
11
+ # This software is licensed for use under the Open Software Licence v. 3.0
12
+ # The terms of this licence can be found at http://www.opensource.org/licenses/osl-3.0.php
13
+ # and in the file copyright.txt. Under the terms of this licence, all derivative works
14
+ # must themselves be licensed under the Open Software Licence v. 3.0
15
+ #
16
+ #
17
+ #
18
+ require 'jeeves/errors'
19
+ require 'jeeves/utils'
20
+
21
+ module Jeeves
22
+
23
+ # a unit of storage that can be added into a store
24
+ class Partition
25
+
26
+ #include Jeeves::Utils
27
+
28
+ # create a partition, regardless of what state it is in
29
+ def initialize(path, options={})
30
+ @path = File.expand_path(path)
31
+ @key = options[:key]
32
+ @key_file = File.join(@path, '.jeeves.key')
33
+ @file_list = File.join(@path, '.jeeves_store.rb')
34
+ @mount_point = options[:mount_point] || @path
35
+
36
+ # assume the disk is not ready
37
+ @mounted = false
38
+ @ready = false
39
+ # check status
40
+ if FileTest.writable?(@path) then
41
+ # seems to be there, but is it correct?
42
+ if FileTest.exists?(@key_file) then
43
+ # there is a key file so assume it is mounted
44
+ @mounted = true
45
+ @ready = key_ok?
46
+ end
47
+ else
48
+ unless @path == @mount_point then
49
+ # check if mount_point is mounted
50
+ if FileTest.writable?(@mount_point) then
51
+ @mounted = true
52
+ end
53
+ end
54
+ end #if
55
+
56
+ @total_space = 0
57
+ @free_space = 0
58
+ @device = ''
59
+ update_space
60
+
61
+ end
62
+
63
+ # path to the partition
64
+ attr_reader :path
65
+
66
+ # boolean flag to indicate if the partition is mounted
67
+ # @deprecated use {#mounted?} instead
68
+ attr_reader :mounted
69
+
70
+ # test if the partition is mounted
71
+ def mounted?
72
+ return @mounted
73
+ end
74
+
75
+ attr_reader :ready
76
+
77
+ # test if the partition is ready to use
78
+ def ready?
79
+ return @ready
80
+ end
81
+
82
+ # return the device on which the partition resides (e.g. /dev/sda1)
83
+ attr_reader :device
84
+
85
+ # return the total space in the partition (in 1K blocks)
86
+ attr_reader :total_space
87
+
88
+ # return the free space in the partitions (in 1K blocks)
89
+ attr_reader :free_space
90
+
91
+ # return tha path to where the file list would be or is
92
+ attr_reader :file_list
93
+
94
+ # flag the partition as a duplicate
95
+ attr_accessor :duplicate
96
+
97
+ # return the date of the file_list if it exists or nil otherwise
98
+ # The returned value is the modification time of the file
99
+ def file_list_date
100
+ return nil if @file_list.nil?
101
+ return File.mtime(@file_list)
102
+ end
103
+
104
+ # test if the file list exists
105
+ def file_list?
106
+ return FileTest.exists?(@file_list)
107
+ end
108
+
109
+ # test that the key given at init is the same as the key
110
+ # on the disk
111
+ def key_ok?
112
+ # get the key in the keyfile
113
+ return false if @key.nil?
114
+ return false unless File.exists?(@key_file)
115
+ file_key = File.readlines(@key_file).first.chomp
116
+ return file_key == @key
117
+ end
118
+
119
+ # return the free space in 1K blocks on the given path
120
+ def update_space
121
+ return unless @ready
122
+ # use df to get the free space for path
123
+ df_lines = `/bin/df -Pk #{@path}`.split("\n")
124
+ df_fields = df_lines[1].split
125
+ @device = df_fields[0]
126
+ @total_space = df_fields[1].to_i
127
+ @free_space = df_fields[3].to_i
128
+ end
129
+
130
+ def display(cols)
131
+ return @path.ljust(cols.shift) +
132
+ (@mounted ? 'OK' : 'No').ljust(cols.shift) +
133
+ (@ready ? 'OK': 'No').ljust(cols.shift) +
134
+ (@duplicate ? '*': '').ljust(cols.shift) +
135
+ Jeeves.human_size(@total_space).rjust(cols.shift) +
136
+ Jeeves.human_size(@free_space).rjust(cols.shift)
137
+ end
138
+
139
+ def to_a
140
+ return [@path,
141
+ (@mounted ? 'OK' : 'No'),
142
+ (@ready ? 'OK': 'No'),
143
+ (@duplicate ? '*': ''),
144
+ Jeeves.human_size(@total_space),
145
+ Jeeves.human_size(@free_space)]
146
+ end
147
+
148
+ # create a key and save it to the disk
149
+ #
150
+ # This will raise an exception if the key file already exists or cannot be written
151
+ # but it can be forced to create the key anyway
152
+ #
153
+ # @param [Boolean] force the key file to be created if one already exists
154
+ def create_key(force=false)
155
+ # check that path is writable
156
+ unless FileTest.writable?(@path)
157
+ raise Jeeves::InvalidPartition, "Cannot write to #{path}"
158
+ end
159
+
160
+ if FileTest.exists?(@key_file) && !force then
161
+ raise Jeeves::InvalidPartitionKey, "Keyfile already exists at #{path}"
162
+ end
163
+
164
+ @key = Digest::SHA1.hexdigest(Time.now.to_s + rand(12341234).to_s)
165
+
166
+ File.open(@key_file, 'w') do |kfile|
167
+ kfile.puts @key
168
+ end
169
+ return @key
170
+
171
+ end
172
+ end
173
+
174
+ end