royw-dvdprofiler2xbmc 0.0.6 → 0.0.8

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,10 +1,8 @@
1
1
  # == Synopsis
2
2
  # Media encapsulates information about a single media file
3
3
  class Media
4
- attr_reader :media_path, :image_files, :fanart_files, :year, :media_subdirs, :title, :title_with_year
5
- attr_accessor :isbn, :imdb_id
6
-
7
- DISC_NUMBER_REGEX = /\.(cd|part|disk|disc)\d+/i
4
+ attr_reader :media_path, :image_files, :fanart_files, :media_subdirs, :title, :title_with_year, :part, :extension
5
+ attr_accessor :isbn, :imdb_id, :resolution, :year
8
6
 
9
7
  def initialize(directory, media_file)
10
8
  @media_subdirs = File.dirname(media_file)
@@ -12,13 +10,19 @@ class Media
12
10
 
13
11
  cwd = File.expand_path(Dir.getwd)
14
12
  Dir.chdir(File.dirname(@media_path))
15
- @nfo_files = Dir.glob("*.{#{AppConfig[:nfo_extensions].join(',')}}")
16
- @image_files = Dir.glob("*.{#{AppConfig[:thumbnail_extension]}}")
17
- @fanart_files = Dir.glob("*fanart*}")
13
+ @nfo_files = Dir.glob("*.{#{AppConfig[:extensions][:nfo]}}")
14
+ @image_files = Dir.glob("*.{#{AppConfig[:extensions][:thumbnail]}}")
15
+ @fanart_files = Dir.glob("*#{AppConfig[:extensions][:fanart]}*}")
18
16
  Dir.chdir(cwd)
19
17
 
20
- @year = $1 if File.basename(@media_path) =~ /\s\-\s(\d{4})/
21
- @title = find_title(@media_path)
18
+ components = Media.parse(@media_path)
19
+ unless components.nil?
20
+ @year = components[:year]
21
+ @title = components[:title]
22
+ @part = components[:part]
23
+ @extension = components[:extension]
24
+ @resolution = components[:resolution]
25
+ end
22
26
  @title_with_year = find_title_with_year(@title, @year)
23
27
  end
24
28
 
@@ -32,11 +36,33 @@ class Media
32
36
  # path_to('nfo') => '/a/b/c.nfo'
33
37
  def path_to(type)
34
38
  # ditch all extensions (ex, a.b => a, a.cd1.b => a)
35
- new_path = File.basename(@media_path, ".*").gsub(DISC_NUMBER_REGEX, '')
36
- unless (type == :base) || AppConfig[type].nil?
37
- new_path += '.' + AppConfig[type]
39
+ DvdProfiler2Xbmc.generate_filespec(@media_path, type, :year => @year, :resolution => @resolution)
40
+ end
41
+
42
+ # parse the given filespec into a hash the consists of the
43
+ # found parts with keys:
44
+ # :title required
45
+ # :year optional
46
+ # :part optional
47
+ # :extension required
48
+ def self.parse(filespec)
49
+ result = nil
50
+ filename = File.basename(filespec)
51
+ AppConfig[:media_parsers].each do |parser|
52
+ match_data = parser.regex.match(filename)
53
+ unless match_data.nil?
54
+ if((match_data.length - 1) == parser.tokens.length)
55
+ index = 1
56
+ result = {}
57
+ parser.tokens.each do |token|
58
+ result[token] = match_data[index]
59
+ index += 1
60
+ end
61
+ break
62
+ end
63
+ end
38
64
  end
39
- File.join(File.dirname(@media_path), new_path)
65
+ result
40
66
  end
41
67
 
42
68
  def to_s
@@ -51,14 +77,7 @@ class Media
51
77
 
52
78
  # return the media's title extracted from the filename and cleaned up
53
79
  def find_title(media_path)
54
- # ditch extensions including disc number (ex, a.part2.b => a, a.cd1.b => a)
55
- title = File.basename(media_path, ".*").gsub(DISC_NUMBER_REGEX, '')
56
- title.gsub!(/\s\-\s\d{4}/, '') # remove year
57
- title.gsub!(/\s\-\s0/, '') # remove "- 0", i.e., bad year
58
- title.gsub!(/\(\d{4}\)/, '') # remove (year)
59
- title.gsub!(/\[.+\]/, '') # remove square brackets
60
- title.gsub!(/\s\s+/, ' ') # remove multiple whitespace
61
- title.strip # remove leading and trailing whitespace
80
+ Media.parse(media_path)[:title] rescue nil
62
81
  end
63
82
 
64
83
  # return the media's title but with the (year) appended
@@ -90,23 +90,11 @@ class TmdbProfile
90
90
  xml = self.to_xml
91
91
  unless xml.blank?
92
92
  AppConfig[:logger].debug { "saving #{filespec}" }
93
- save_to_file(filespec, xml)
93
+ DvdProfiler2Xbmc.save_to_file(filespec, xml)
94
94
  end
95
95
  rescue Exception => e
96
96
  AppConfig[:logger].error "Unable to save tmdb profile to #{filespec} - #{e.to_s}"
97
97
  end
98
98
  end
99
99
 
100
- def save_to_file(filespec, data)
101
- new_filespec = filespec + AppConfig[:new_extension]
102
- File.open(new_filespec, "w") do |file|
103
- file.puts(data)
104
- end
105
- backup_filespec = filespec + AppConfig[:backup_extension]
106
- File.delete(backup_filespec) if File.exist?(backup_filespec)
107
- File.rename(filespec, backup_filespec) if File.exist?(filespec)
108
- File.rename(new_filespec, filespec)
109
- File.delete(new_filespec) if File.exist?(new_filespec)
110
- end
111
-
112
100
  end
@@ -3,7 +3,7 @@
3
3
  #
4
4
  # Usage:
5
5
  #
6
- # profile = XbmcInfo.new(media.path_to(:nfo_extension))
6
+ # profile = XbmcInfo.new(media.path_to(:nfo))
7
7
  #
8
8
  # profile.movie['key'] = 'some value'
9
9
  # puts profile.movie['key']
@@ -58,7 +58,7 @@ class XbmcInfo
58
58
  xml = self.to_xml
59
59
  unless xml.blank?
60
60
  AppConfig[:logger].info { "updated #{@nfo_filespec}"}
61
- save_to_file(@nfo_filespec, xml)
61
+ DvdProfiler2Xbmc.save_to_file(@nfo_filespec, xml)
62
62
  end
63
63
  end
64
64
  rescue Exception => e
@@ -83,18 +83,6 @@ class XbmcInfo
83
83
  end
84
84
  end
85
85
 
86
- def save_to_file(filespec, data)
87
- new_filespec = filespec + AppConfig[:new_extension]
88
- File.open(new_filespec, "w") do |file|
89
- file.puts(data)
90
- end
91
- backup_filespec = filespec + AppConfig[:backup_extension]
92
- File.delete(backup_filespec) if File.exist?(backup_filespec)
93
- File.rename(filespec, backup_filespec) if File.exist?(filespec)
94
- File.rename(new_filespec, filespec)
95
- File.delete(new_filespec) if File.exist?(new_filespec)
96
- end
97
-
98
86
  # has any of the data changed?
99
87
  def dirty?
100
88
  result = false
@@ -1,5 +1,6 @@
1
+ require File.join(File.dirname(__FILE__), 'config_editor')
1
2
  require 'commandline/optionparser'
2
- include CommandLine
3
+ # include CommandLine
3
4
 
4
5
  # Command Line interface for the Dvdprofiler2Xbmc application.
5
6
  # All application output is via AppConfig[:logger] so we have
@@ -36,10 +37,21 @@ module Dvdprofiler2xbmc
36
37
  # parse the command line
37
38
  options = setup_parser()
38
39
  od = options.parse(arguments)
40
+ run_editor = !AppConfig.exist?
39
41
 
40
42
  setup_app_config(od, logger)
43
+ run_editor = true unless AppConfig.valid?
41
44
 
42
- unless od["--help"] || od["--version"]
45
+ logger.info(AppConfig.to_s) if od["--show_config"]
46
+
47
+ if run_editor || od["--edit_config"]
48
+ editor = ConfigEditor.new
49
+ editor.execute
50
+ end
51
+
52
+ skip_execution = false
53
+ %w(--help --version --show_config --edit_config).each {|flag| skip_execution = true if od[flag] || run_editor}
54
+ unless skip_execution
43
55
  # create and execute class instance here
44
56
  app = DvdProfiler2Xbmc.instance
45
57
  app.execute
@@ -65,7 +77,6 @@ module Dvdprofiler2xbmc
65
77
  reinitialize_logger(logger, od["--quiet"], od["--debug"])
66
78
  AppConfig.load
67
79
  AppConfig.save
68
- AppConfig[:imdb_query] = !od["--no_imdb_query"]
69
80
  AppConfig[:logfile] = od['--output'] if od['--output']
70
81
  AppConfig[:logfile_level] = od['--output_level'] if od['--output_level']
71
82
  reinitialize_logger(logger, od["--quiet"], od["--debug"])
@@ -80,7 +91,7 @@ module Dvdprofiler2xbmc
80
91
  # Setup the command line option parser
81
92
  # Returns:: OptionParser instances
82
93
  def self.setup_parser()
83
- options = OptionParser.new()
94
+ options = CommandLine::OptionParser.new()
84
95
 
85
96
  # flag options
86
97
  [
@@ -95,8 +106,12 @@ module Dvdprofiler2xbmc
95
106
  :opt_description => "This usage information"
96
107
  },
97
108
  {
98
- :names => %w(--no_imdb_query -n),
99
- :opt_description => 'Do not query IMDB.com'
109
+ :names => %w(--show_config -s),
110
+ :opt_description => "This is the current configuration information"
111
+ },
112
+ {
113
+ :names => %w(--edit_config -e),
114
+ :opt_description => "Edit the current configuration information"
100
115
  },
101
116
  {
102
117
  :names => %w(--quiet -q),
@@ -114,7 +129,7 @@ module Dvdprofiler2xbmc
114
129
  :names => %w(--reports -r),
115
130
  :opt_description => 'Display reports only. Do not do any updates.'
116
131
  }
117
- ].each { |opt| options << Option.new(:flag, opt) }
132
+ ].each { |opt| options << CommandLine::Option.new(:flag, opt) }
118
133
 
119
134
  # non-flag options
120
135
  [
@@ -123,16 +138,16 @@ module Dvdprofiler2xbmc
123
138
  :argument_arity => [1,1],
124
139
  :arg_description => 'logfile',
125
140
  :opt_description => 'Write log messages to file. Default = no log file',
126
- :opt_found => OptionParser::GET_ARGS
141
+ :opt_found => CommandLine::OptionParser::GET_ARGS
127
142
  },
128
143
  {
129
144
  :names => %w(--output_level -l),
130
145
  :argument_arity => [1,1],
131
146
  :arg_description => 'level',
132
147
  :opt_description => 'Output logging level: DEBUG, INFO, WARN, ERROR. Default = INFO',
133
- :opt_found => OptionParser::GET_ARGS
148
+ :opt_found => CommandLine::OptionParser::GET_ARGS
134
149
  }
135
- ].each { |opt| options << Option.new(opt) }
150
+ ].each { |opt| options << CommandLine::Option.new(opt) }
136
151
 
137
152
  options
138
153
  end
@@ -0,0 +1,289 @@
1
+ # require 'highline'
2
+ require "highline/import"
3
+
4
+ # monkey patch HighLine to get rid of the ugly message:
5
+ # Your answer isn't valid (must match #<Proc:0xb76cb378@/home/royw/views/dvdprofiler2xbmc/lib/dvdprofiler2xbmc/app_config.rb:180>)
6
+ # basically the problem is with inspecting a lambda validator, so just don't do it...
7
+ class HighLine
8
+ class Question
9
+ def build_responses( )
10
+ append_default unless default.nil?
11
+ @responses = { :ambiguous_completion =>
12
+ "Ambiguous choice. " +
13
+ "Please choose one of #{@answer_type.inspect}.",
14
+ :ask_on_error =>
15
+ "? ",
16
+ :invalid_type =>
17
+ "You must enter a valid #{@answer_type}.",
18
+ :no_completion =>
19
+ "You must choose one of " +
20
+ "#{@answer_type.inspect}.",
21
+ :not_in_range =>
22
+ "Your answer isn't within the expected range " +
23
+ "(#{expected_range}).",
24
+ :not_valid =>
25
+ "Your answer isn't valid " +
26
+ (@validate.kind_of?(Proc) ?
27
+ '' : "(must match #{@validate.inspect}).")
28
+ }.merge(@responses)
29
+ end
30
+ end
31
+ end
32
+
33
+ class ConfigEditor
34
+
35
+ def initialize
36
+ end
37
+
38
+ def execute
39
+ @saved_interrupt_message = DvdProfiler2Xbmc.interrupt_message
40
+ DvdProfiler2Xbmc.interrupt_message = ''
41
+ report_invalid_config_items
42
+ begin
43
+ AppConfig[:logger].info('Configuration Editor')
44
+
45
+ fields = AppConfig.navigation.collect do |page|
46
+ page.values.flatten.select do|field|
47
+ AppConfig.data_type[field]
48
+ end
49
+ end.flatten.uniq.compact
50
+ while(field = menu_select('field', fields))
51
+ begin
52
+ edit_field(field)
53
+ rescue
54
+ end
55
+ end
56
+ if agree("Save? yes/no") {|q| q.default = 'yes'}
57
+ AppConfig.save
58
+ end
59
+ rescue
60
+ end
61
+ DvdProfiler2Xbmc.interrupt_message = @saved_interrupt_message
62
+ end
63
+
64
+ def report_invalid_config_items
65
+ buf = []
66
+ AppConfig.validate.each do |field, value|
67
+ unless AppConfig.validate[field].call(AppConfig.config[field])
68
+ buf << field
69
+ end
70
+ end
71
+ unless buf.empty?
72
+ say 'The following config items are not valid and need to be changed:'
73
+ buf.each {|line| say " #{line}"}
74
+ end
75
+ end
76
+
77
+ def edit_field(field)
78
+ result = true
79
+ while(result)
80
+ field_header(field)
81
+
82
+ choose do |menu|
83
+ menu.prompt = "Please select: "
84
+ menu.index = :number
85
+ menu.index_suffix = ') '
86
+ menu.choice(:quit, 'Quit') {result = false}
87
+ case AppConfig.data_type[field]
88
+ when :BOOLEAN
89
+ menu.choice(:true, 'True') {AppConfig.config[field] = true}
90
+ menu.choice(:false, 'False') {AppConfig.config[field] = false}
91
+ else
92
+ menu.choice(:default, 'Default') {AppConfig.config[field] = AppConfig.initial[field]}
93
+ if AppConfig.data_type[field].to_s =~ /^ARRAY/
94
+ menu.choice(:add, 'Add') {array_add(field)}
95
+ menu.choice(:delete, 'Delete') {array_delete(field)}
96
+ elsif AppConfig.data_type[field].to_s =~ /^HASH/
97
+ menu.choice(:add, 'Add') {hash_add(field)}
98
+ menu.choice(:delete, 'Delete') {hash_delete(field)}
99
+ else
100
+ menu.choice(:edit, 'Edit') {object_edit(field)}
101
+ end
102
+ end
103
+ end
104
+ end
105
+ result
106
+ end
107
+
108
+ def field_header(field)
109
+ say "\n"
110
+ say "-------------------------------"
111
+ say field
112
+ say "\n"
113
+ say AppConfig.help[field]
114
+ say "\n"
115
+ say "Default:"
116
+ say prettify(AppConfig.initial[field])
117
+ say "\n"
118
+ say "Current:"
119
+ say prettify(AppConfig.config[field])
120
+ say "\n"
121
+ end
122
+
123
+ def object_edit(field)
124
+ value = data_type_editor(field, AppConfig.data_type[field], AppConfig.initial[field])
125
+ AppConfig.config[field] = value unless value.nil?
126
+ end
127
+
128
+ def hash_add(field)
129
+ value = data_type_editor(field, AppConfig.data_type[field], AppConfig.initial[field])
130
+ if value =~ /([^,]+)\s*,\s*(\S.*)/
131
+ AppConfig.config[field][$1.strip] = $2.strip
132
+ end
133
+ end
134
+
135
+ def hash_delete(field)
136
+ choose do |menu|
137
+ menu.prompt = "Please select to remove: "
138
+ menu.index = :number
139
+ menu.index_suffix = ') '
140
+ menu.choice(:quit, 'Quit') {result = false}
141
+ values = []
142
+ AppConfig.config[field].each do |key, value|
143
+ values << "#{key} => #{value}"
144
+ end
145
+ menu.choices(*values) do |value, details|
146
+ if value =~ /(.*\S)\s+=>/
147
+ AppConfig.config[field].delete($1)
148
+ end
149
+ end
150
+ end
151
+ end
152
+
153
+ def array_add(field)
154
+ value = data_type_editor(field, AppConfig.data_type[field], AppConfig.initial[field])
155
+ AppConfig.config[field] += [value].flatten unless value.nil?
156
+ AppConfig.config[field].uniq!
157
+ end
158
+
159
+ def array_delete(field)
160
+ choose do |menu|
161
+ menu.prompt = "Please select to remove: "
162
+ menu.index = :number
163
+ menu.index_suffix = ') '
164
+ menu.choice(:quit, 'Quit') {result = false}
165
+ menu.choices(*AppConfig.config[field]) do |extension, details|
166
+ AppConfig.config[field].delete(extension)
167
+ end
168
+ end
169
+ end
170
+
171
+ def prettify(obj)
172
+ str = obj.pretty_inspect
173
+ if obj.kind_of? Mash
174
+ str = obj.to_hash.pretty_inspect
175
+ end
176
+ if obj.kind_of? Array
177
+ str = obj.inspect
178
+ end
179
+ if obj.kind_of? Hash
180
+ key_length = 0
181
+ obj.keys.each do |key|
182
+ key_length = key.length if key.length > key_length
183
+ end
184
+ buf = []
185
+ obj.each do |key,value|
186
+ buf << sprintf("%-#{key_length}.#{key_length}s => %s", key, value)
187
+ end
188
+ str = buf.join("\n")
189
+ end
190
+ str
191
+ end
192
+
193
+ def data_type_editor(field, data_type, default_value=nil)
194
+ value = nil
195
+ case data_type
196
+ when :BOOLEAN
197
+ value = agree("Set #{field} to true: ") do |q|
198
+ q.default = (default_value ? 'yes' : 'no') unless default_value.nil?
199
+ end
200
+ when :FILESPEC
201
+ value = ask('New filespec: ') do |q|
202
+ # q.answer_type = File
203
+ q.validate = AppConfig.validate_item[field] unless AppConfig.validate_item[field].nil?
204
+ q.confirm = false
205
+ q.default = default_value unless default_value.nil?
206
+ end
207
+ when :PATHSPEC
208
+ value = ask('New pathspec: ') do |q|
209
+ # q.answer_type = Pathname
210
+ # q.validate = lambda { |v| File.exist?(v) && File.directory?(v) }
211
+ q.validate = AppConfig.validate_item[field] unless AppConfig.validate_item[field].nil?
212
+ q.confirm = false
213
+ q.default = default_value unless default_value.nil?
214
+ end
215
+ when :PERMISSIONS
216
+ value = ask('New value (octal): ') do |q|
217
+ # q.validate = lambda { |v| (v.to_i(8) >= 0) && (v.to_i(8) <= 07777) }
218
+ q.validate = AppConfig.validate_item[field] unless AppConfig.validate_item[field].nil?
219
+ q.default = default_value unless default_value.nil?
220
+ end
221
+ when :ARRAY_OF_STRINGS
222
+ value = ask('New values or a blank line to quit: ') do |q|
223
+ q.gather = ''
224
+ q.validate = AppConfig.validate_item[field] unless AppConfig.validate_item[field].nil?
225
+ # note, can not have both gather and default as there is no way to
226
+ # terminate the input loop
227
+ end
228
+ when :ARRAY_OF_PATHSPECS
229
+ # note, can not have both gather and default as there is no way to
230
+ # terminate the input loop
231
+ value = ask('New pathspecs or a blank line to quit: ') do |q|
232
+ q.gather = ''
233
+ # q.validate = lambda { |v| v.empty? || (File.exist?(v) && File.directory?(v)) }
234
+ q.validate = AppConfig.validate_item[field] unless AppConfig.validate_item[field].nil?
235
+ end
236
+ when :HASH_FIXED_SYMBOL_KEYS_STRING_VALUES
237
+ when :HASH_STRING_KEYS_STRING_VALUES
238
+ value = ask('New value as key,value: ') do |q|
239
+ # q.default = default_value unless default_value.nil?
240
+ end
241
+ else
242
+ value = ask('New value: ')
243
+ end
244
+ value
245
+ end
246
+
247
+ VALUE_LENGTH = 60
248
+
249
+ def menu_select(name, values)
250
+ result = false
251
+ say("\n#{name.capitalize} Selection")
252
+ choose do |menu|
253
+ menu.prompt = "Please select #{name}: "
254
+ menu.index = :number
255
+ menu.index_suffix = ') '
256
+ menu.choice(:quit, 'Quit') {result = false}
257
+ menu.choices(*(values.collect{|v| field_name_choice(v)})) do |command, details|
258
+ result = command.split(" ").first
259
+ end
260
+ end
261
+ result
262
+ end
263
+
264
+ def field_name_choice(value)
265
+ value_str = sprintf("%-#{VALUE_LENGTH}.#{VALUE_LENGTH}s", first_line(value))
266
+ if AppConfig.config[:color_enabled]
267
+ color = 'green'
268
+ unless AppConfig.validate[value].nil?
269
+ color = 'red' unless AppConfig.validate[value].call(AppConfig.config[value])
270
+ end
271
+ value_str = "<%= color('#{value_str}', :#{color})%>"
272
+ end
273
+ str = sprintf("%20s %s", value, value_str)
274
+ str
275
+ end
276
+
277
+ def first_line(value)
278
+ config_value = AppConfig.config[value]
279
+ if config_value.kind_of? Mash
280
+ config_value = config_value.to_hash
281
+ end
282
+ str = [config_value.inspect].flatten.first.split("\n").first
283
+ if str.length > VALUE_LENGTH
284
+ str = str[0..(VALUE_LENGTH - 4)] + '...'
285
+ end
286
+ str
287
+ end
288
+
289
+ end