jeeves-pvr 0.2.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,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