royw-dvdprofiler2xbmc 0.0.17 → 0.0.18
Sign up to get free protection for your applications and to get access to all the features.
- data/VERSION.yml +1 -1
- data/lib/dvdprofiler2xbmc/app_config.rb +17 -1
- data/lib/dvdprofiler2xbmc/controllers/app.rb +52 -21
- data/lib/dvdprofiler2xbmc/models/media.rb +59 -2
- data/lib/dvdprofiler2xbmc/models/media_files.rb +5 -2
- data/lib/dvdprofiler2xbmc/models/xbmc_info.rb +30 -10
- data/lib/dvdprofiler2xbmc/views/cli.rb +18 -7
- data/lib/dvdprofiler2xbmc/views/config_editor.rb +51 -3
- data/spec/config_editor_spec.rb +18 -18
- data/spec/fanart_controller_spec.rb +2 -2
- metadata +2 -2
data/VERSION.yml
CHANGED
@@ -14,52 +14,65 @@ module AppConfig
|
|
14
14
|
# are mashes that share the same keys. For example @help.foo would be
|
15
15
|
# the help text for @config.foo
|
16
16
|
|
17
|
+
# == Synopsis
|
17
18
|
# the current config values
|
18
19
|
@config = Mash.new
|
19
20
|
|
21
|
+
# == Synopsis
|
20
22
|
# help about the config item
|
21
23
|
@help = Mash.new
|
22
24
|
|
25
|
+
# == Synopsis
|
23
26
|
# initial (default) config values
|
24
27
|
@initial = Mash.new
|
25
28
|
|
29
|
+
# == Synopsis
|
26
30
|
# data type constants used in the editor
|
27
31
|
@data_type = Mash.new
|
28
32
|
|
33
|
+
# == Synopsis
|
29
34
|
# validate the config item
|
30
35
|
@validate = Mash.new
|
31
36
|
|
37
|
+
# == Synopsis
|
32
38
|
# validate an entry for a config item, note usually need to allow
|
33
39
|
# empty string for either array entry termination or to accept
|
34
40
|
# a default value
|
35
41
|
@validate_item = Mash.new
|
36
42
|
|
43
|
+
# == Synopsis
|
37
44
|
# the @navigation array contains hashes where each hash is a "page"
|
38
45
|
# that contains an array of @config keys. This provides a natural
|
39
46
|
# organization for UIs.
|
40
47
|
@navigation = []
|
41
48
|
|
49
|
+
# make class variables readable
|
42
50
|
class << self
|
43
51
|
attr_reader :help, :initial, :navigation, :config, :data_type, :validate, :validate_item
|
44
52
|
end
|
45
53
|
|
54
|
+
# the user's config file
|
46
55
|
@yaml_filespec = File.join(ENV['HOME'], '.dvdprofiler2xbmcrc')
|
47
56
|
|
57
|
+
# == Synopsis
|
48
58
|
# shortcut accessor for @config items
|
49
59
|
def self.[](k)
|
50
60
|
@config[k]
|
51
61
|
end
|
52
62
|
|
63
|
+
# == Synopsis
|
53
64
|
# shortcut accessor for @config items
|
54
65
|
def self.[]=(k,v)
|
55
66
|
@config[k] = v
|
56
67
|
end
|
57
68
|
|
69
|
+
# == Synopsis
|
58
70
|
# does the config file exist?
|
59
71
|
def self.exist?
|
60
72
|
File.exist?(@yaml_filespec)
|
61
73
|
end
|
62
74
|
|
75
|
+
# == Synopsis
|
63
76
|
# load the config file, overwriting current values
|
64
77
|
def self.load
|
65
78
|
begin
|
@@ -88,6 +101,7 @@ module AppConfig
|
|
88
101
|
end
|
89
102
|
end
|
90
103
|
|
104
|
+
# == Synopsis
|
91
105
|
# save the config file
|
92
106
|
def self.save
|
93
107
|
begin
|
@@ -105,6 +119,7 @@ module AppConfig
|
|
105
119
|
end
|
106
120
|
end
|
107
121
|
|
122
|
+
# == Synopsis
|
108
123
|
# generate a string for displaying the current config
|
109
124
|
def self.to_s
|
110
125
|
buf = []
|
@@ -127,6 +142,7 @@ module AppConfig
|
|
127
142
|
buf.join("\n")
|
128
143
|
end
|
129
144
|
|
145
|
+
# == Synopsis
|
130
146
|
# is the current config valid?
|
131
147
|
def self.valid?
|
132
148
|
valid = true
|
@@ -138,6 +154,7 @@ module AppConfig
|
|
138
154
|
valid
|
139
155
|
end
|
140
156
|
|
157
|
+
# == Synopsis
|
141
158
|
# set the config to the default values
|
142
159
|
def self.default
|
143
160
|
# Note, all paths and extensions are case sensitive
|
@@ -330,7 +347,6 @@ module AppConfig
|
|
330
347
|
:no_tmdb_lookup => 'no_tmdb_lookup',
|
331
348
|
}
|
332
349
|
@config.extensions = @initial.extensions
|
333
|
-
# @data_type.extensions = :HASH_FIXED_SYMBOL_KEYS_STRING_VALUES
|
334
350
|
|
335
351
|
# substitutions:
|
336
352
|
# %t => movie title
|
@@ -8,15 +8,34 @@
|
|
8
8
|
class DvdProfiler2Xbmc
|
9
9
|
include Singleton
|
10
10
|
|
11
|
+
protected
|
12
|
+
|
13
|
+
# == Synopsis
|
14
|
+
# protected initializer because it is a Singleton class
|
15
|
+
def initialize
|
16
|
+
@media_files = nil
|
17
|
+
@multiple_profiles = []
|
18
|
+
end
|
19
|
+
|
20
|
+
public
|
21
|
+
|
22
|
+
# == Synopsis
|
23
|
+
# An Array of Strings that the external processing my write to to
|
24
|
+
# indicate that a given title has multiple ISBNs.
|
25
|
+
# HACK, this is a hack because I didn't see a way to cleanly pass
|
26
|
+
# the data up from the processing.
|
11
27
|
attr_accessor :multiple_profiles
|
12
28
|
|
13
29
|
@interrupted = false
|
14
30
|
@interrupt_message = "control-C detected, finishing current task"
|
15
31
|
|
16
32
|
class << self
|
33
|
+
# == Synopsis
|
34
|
+
# When ^C is pressed, this message is sent to stdout
|
17
35
|
attr_accessor :interrupt_message
|
18
36
|
end
|
19
37
|
|
38
|
+
# == Synopsis
|
20
39
|
# A trap("INT") in the Runner calls this to indicate that a ^C has been detected.
|
21
40
|
# Note, once set, it is never cleared
|
22
41
|
def self.interrupt
|
@@ -24,18 +43,15 @@ class DvdProfiler2Xbmc
|
|
24
43
|
@interrupted = true
|
25
44
|
end
|
26
45
|
|
46
|
+
# == Synopsis
|
27
47
|
# Long loops should poll this method to see if they should abort
|
28
48
|
# Returns:: true if the application has trapped an "INT", false otherwise
|
29
49
|
def self.interrupted?
|
30
50
|
@interrupted
|
31
51
|
end
|
32
52
|
|
33
|
-
|
34
|
-
|
35
|
-
@multiple_profiles = []
|
36
|
-
end
|
37
|
-
|
38
|
-
# the application's main execution loop
|
53
|
+
# == Synopsis
|
54
|
+
# the application's main execution loop that processes all of the media
|
39
55
|
def execute
|
40
56
|
AppConfig[:logger].info { "Media Directories:\n #{AppConfig[:directories].join("\n ")}" }
|
41
57
|
|
@@ -58,6 +74,7 @@ class DvdProfiler2Xbmc
|
|
58
74
|
AppConfig[:directories].each { |dir| set_permissions(dir) }
|
59
75
|
end
|
60
76
|
|
77
|
+
# == Synopsis
|
61
78
|
# generate the report.
|
62
79
|
# Note, must be ran after execute()
|
63
80
|
# returns an array of lines
|
@@ -75,21 +92,12 @@ class DvdProfiler2Xbmc
|
|
75
92
|
buf
|
76
93
|
end
|
77
94
|
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
buf << heading
|
85
|
-
buf += lines
|
86
|
-
end
|
87
|
-
rescue Exception => e
|
88
|
-
AppConfig[:logger].error { "Error generating #{name} report - #{e.to_s}" }
|
89
|
-
end
|
90
|
-
buf
|
91
|
-
end
|
92
|
-
|
95
|
+
# == Synopsis
|
96
|
+
# utility method that saves the given data to the filespec safely by:
|
97
|
+
# 1) writes the data to a new file,
|
98
|
+
# 2) deletes any previous backup file,
|
99
|
+
# 3) renames the old file to a backup,
|
100
|
+
# 4) renames the new file to the original filename.
|
93
101
|
def self.save_to_file(filespec, data)
|
94
102
|
new_filespec = filespec + AppConfig[:extensions][:new]
|
95
103
|
File.open(new_filespec, "w") do |file|
|
@@ -102,6 +110,7 @@ class DvdProfiler2Xbmc
|
|
102
110
|
File.delete(new_filespec) if File.exist?(new_filespec)
|
103
111
|
end
|
104
112
|
|
113
|
+
# == Synopsis
|
105
114
|
# options hash may have the following:
|
106
115
|
# :extension - an extension to append to the generated filespec
|
107
116
|
# :year - the production year
|
@@ -144,6 +153,23 @@ class DvdProfiler2Xbmc
|
|
144
153
|
|
145
154
|
protected
|
146
155
|
|
156
|
+
# == Synopsis
|
157
|
+
def gen_report(name, heading='')
|
158
|
+
buf = []
|
159
|
+
begin
|
160
|
+
lines = send("#{name}_report")
|
161
|
+
unless lines.empty?
|
162
|
+
buf << ''
|
163
|
+
buf << heading
|
164
|
+
buf += lines
|
165
|
+
end
|
166
|
+
rescue Exception => e
|
167
|
+
AppConfig[:logger].error { "Error generating #{name} report - #{e.to_s}" }
|
168
|
+
end
|
169
|
+
buf
|
170
|
+
end
|
171
|
+
|
172
|
+
# == Synopsis
|
147
173
|
# set the directory and file permissions for all files and directories under
|
148
174
|
# the given directory
|
149
175
|
def set_permissions(dir)
|
@@ -160,6 +186,7 @@ class DvdProfiler2Xbmc
|
|
160
186
|
end
|
161
187
|
end
|
162
188
|
|
189
|
+
# == Synopsis
|
163
190
|
# duplicate media file report
|
164
191
|
def duplicates_report
|
165
192
|
buf = []
|
@@ -175,6 +202,7 @@ class DvdProfiler2Xbmc
|
|
175
202
|
buf
|
176
203
|
end
|
177
204
|
|
205
|
+
# == Synopsis
|
178
206
|
# unable to find ISBN for these titles report
|
179
207
|
def missing_isbns_report
|
180
208
|
buf = []
|
@@ -198,6 +226,7 @@ class DvdProfiler2Xbmc
|
|
198
226
|
buf
|
199
227
|
end
|
200
228
|
|
229
|
+
# == Synopsis
|
201
230
|
def missing_imdb_ids_report
|
202
231
|
buf = []
|
203
232
|
@media_files.titles.each do |title, medias|
|
@@ -215,6 +244,7 @@ class DvdProfiler2Xbmc
|
|
215
244
|
buf
|
216
245
|
end
|
217
246
|
|
247
|
+
# == Synopsis
|
218
248
|
def missing_thumbnails_report
|
219
249
|
buf = []
|
220
250
|
@media_files.titles.each do |title, medias|
|
@@ -232,6 +262,7 @@ class DvdProfiler2Xbmc
|
|
232
262
|
buf
|
233
263
|
end
|
234
264
|
|
265
|
+
# == Synopsis
|
235
266
|
def multiple_profiles_report
|
236
267
|
@multiple_profiles
|
237
268
|
end
|
@@ -1,9 +1,60 @@
|
|
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, :media_subdirs, :title, :title_with_year, :part, :extension
|
5
|
-
attr_accessor :isbn, :imdb_id, :resolution, :year
|
6
4
|
|
5
|
+
# == Synopsis
|
6
|
+
# filespec to the media file
|
7
|
+
attr_reader :media_path
|
8
|
+
|
9
|
+
# == Synopsis
|
10
|
+
# Array of image filenames associated with the media
|
11
|
+
attr_reader :image_files
|
12
|
+
|
13
|
+
# == Synopsis
|
14
|
+
# Array of fanart filenames associated with the media
|
15
|
+
attr_reader :fanart_files
|
16
|
+
|
17
|
+
# == Synopsis
|
18
|
+
# The realative pathspec from the top level directory to the
|
19
|
+
# directory that contains the media
|
20
|
+
attr_reader :media_subdirs
|
21
|
+
|
22
|
+
# == Synopsis
|
23
|
+
# The media's title String
|
24
|
+
attr_reader :title
|
25
|
+
|
26
|
+
# == Synopsis
|
27
|
+
# The media's title and year String ("title (year)")
|
28
|
+
attr_reader :title_with_year
|
29
|
+
|
30
|
+
# == Synopsis
|
31
|
+
# nil or a String contain the media part (ex: cd1, disk2)
|
32
|
+
attr_reader :part
|
33
|
+
|
34
|
+
# == Synopsis
|
35
|
+
# The file extension for the media
|
36
|
+
attr_reader :extension
|
37
|
+
|
38
|
+
# == Synopsis
|
39
|
+
# The ISBN number in a String for the media
|
40
|
+
attr_accessor :isbn
|
41
|
+
|
42
|
+
# == Synopsis
|
43
|
+
# The IMDB ID in a String for the media
|
44
|
+
attr_accessor :imdb_id
|
45
|
+
|
46
|
+
# == Synopsis
|
47
|
+
# The video resolution as a String
|
48
|
+
attr_accessor :resolution
|
49
|
+
|
50
|
+
# == Synopsis
|
51
|
+
# The media's production year
|
52
|
+
attr_accessor :year
|
53
|
+
|
54
|
+
# == Synopsis
|
55
|
+
# directory => String containing pathspec to the top level directory of the media
|
56
|
+
# media_file => String containing relative pathspec from the top level
|
57
|
+
# directory of the media to the media file
|
7
58
|
def initialize(directory, media_file)
|
8
59
|
@media_subdirs = File.dirname(media_file)
|
9
60
|
@media_path = File.expand_path(File.join(directory, media_file))
|
@@ -26,6 +77,7 @@ class Media
|
|
26
77
|
@title_with_year = find_title_with_year(@title, @year)
|
27
78
|
end
|
28
79
|
|
80
|
+
# == Synopsis
|
29
81
|
# return a path to a file file based on the media's filespec
|
30
82
|
# but without any stacking parts and with the given extension
|
31
83
|
# instead of the media's extension.
|
@@ -39,6 +91,7 @@ class Media
|
|
39
91
|
DvdProfiler2Xbmc.generate_filespec(@media_path, type, :year => @year, :resolution => @resolution)
|
40
92
|
end
|
41
93
|
|
94
|
+
# == Synopsis
|
42
95
|
# parse the given filespec into a hash the consists of the
|
43
96
|
# found parts with keys:
|
44
97
|
# :title required
|
@@ -65,6 +118,8 @@ class Media
|
|
65
118
|
result
|
66
119
|
end
|
67
120
|
|
121
|
+
# == Synopsis
|
122
|
+
# return human readable string representation
|
68
123
|
def to_s
|
69
124
|
buf = []
|
70
125
|
buf << @media_path
|
@@ -75,11 +130,13 @@ class Media
|
|
75
130
|
|
76
131
|
protected
|
77
132
|
|
133
|
+
# == Synopsis
|
78
134
|
# return the media's title extracted from the filename and cleaned up
|
79
135
|
def find_title(media_path)
|
80
136
|
Media.parse(media_path)[:title] rescue nil
|
81
137
|
end
|
82
138
|
|
139
|
+
# == Synopsis
|
83
140
|
# return the media's title but with the (year) appended
|
84
141
|
def find_title_with_year(title, year)
|
85
142
|
name = title
|
@@ -3,8 +3,8 @@
|
|
3
3
|
class MediaFiles
|
4
4
|
attr_reader :medias, :titles, :duplicate_titles
|
5
5
|
|
6
|
-
#
|
7
|
-
# directories Array of String directory pathspecs
|
6
|
+
# == Synopsis
|
7
|
+
# directories => Array of String directory pathspecs
|
8
8
|
def initialize(directories)
|
9
9
|
@medias = find_medias(directories)
|
10
10
|
@titles = find_titles(@medias)
|
@@ -13,6 +13,7 @@ class MediaFiles
|
|
13
13
|
|
14
14
|
protected
|
15
15
|
|
16
|
+
# == Synopsis
|
16
17
|
# find all the media files in the given set of directories
|
17
18
|
def find_medias(directories)
|
18
19
|
medias = []
|
@@ -25,6 +26,7 @@ class MediaFiles
|
|
25
26
|
medias
|
26
27
|
end
|
27
28
|
|
29
|
+
# == Synopsis
|
28
30
|
# return a hash where the key is the media's title and
|
29
31
|
# the value is an Array of Media instances
|
30
32
|
def find_titles(medias)
|
@@ -37,6 +39,7 @@ class MediaFiles
|
|
37
39
|
titles
|
38
40
|
end
|
39
41
|
|
42
|
+
# == Synopsis
|
40
43
|
# find duplicate titles and return them in a hash
|
41
44
|
# where the key is the title and the value is an
|
42
45
|
# array of Media objects
|
@@ -1,3 +1,4 @@
|
|
1
|
+
# == Synopsis
|
1
2
|
# This is the model for the XBMC's Info profile which is used
|
2
3
|
# to manage a .nfo file
|
3
4
|
#
|
@@ -12,8 +13,8 @@
|
|
12
13
|
#
|
13
14
|
class XbmcInfo
|
14
15
|
|
15
|
-
|
16
|
-
|
16
|
+
# == Synopsis
|
17
|
+
# filespec => String pathspec to the .nfo file
|
17
18
|
def initialize(filespec)
|
18
19
|
@nfo_filespec = filespec
|
19
20
|
@movie = nil
|
@@ -21,28 +22,26 @@ class XbmcInfo
|
|
21
22
|
load
|
22
23
|
end
|
23
24
|
|
25
|
+
# == Synopsis
|
26
|
+
# return the movie hash that contains the media meta data
|
24
27
|
def movie
|
25
28
|
@movie ||= Hash.new
|
26
29
|
@movie
|
27
30
|
end
|
28
31
|
|
32
|
+
# == Synopsis
|
33
|
+
# set the movie hash
|
29
34
|
def movie=(other)
|
30
35
|
@movie = other
|
31
36
|
end
|
32
37
|
|
38
|
+
# == Synopsis
|
33
39
|
# convert the @movie hash into xml and return the xml as a String
|
34
40
|
def to_xml
|
35
41
|
xml = ''
|
36
42
|
begin
|
37
43
|
unless @movie.blank?
|
38
|
-
data = @movie.dup
|
39
|
-
data.delete_if { |key, value| value.nil? }
|
40
|
-
%w(plot tagline overview).each do |key|
|
41
|
-
if data[key].respond_to?('first')
|
42
|
-
data[key] = data[key].first
|
43
|
-
end
|
44
|
-
data[key] = data[key].gsub(FILTER_HTML, '') unless data[key].blank?
|
45
|
-
end
|
44
|
+
data = filter(@movie.dup)
|
46
45
|
xml = XmlSimple.xml_out(data, 'NoAttr' => true, 'RootName' => 'movie')
|
47
46
|
end
|
48
47
|
rescue Exception => e
|
@@ -52,6 +51,8 @@ class XbmcInfo
|
|
52
51
|
xml
|
53
52
|
end
|
54
53
|
|
54
|
+
# == Synopsis
|
55
|
+
# save the profile to the .nfo file, but only if it has changed
|
55
56
|
def save
|
56
57
|
begin
|
57
58
|
if dirty?
|
@@ -68,6 +69,24 @@ class XbmcInfo
|
|
68
69
|
|
69
70
|
protected
|
70
71
|
|
72
|
+
FILTER_HTML = /<[^>]*>/
|
73
|
+
|
74
|
+
# == Synopsis
|
75
|
+
# filter the given movie hash first collapsing (removing from Array)
|
76
|
+
# the plot, tagline, and overview values by removing, then removing
|
77
|
+
# any HTML tags such as <b></b>, <i></i>,...
|
78
|
+
def filter(data)
|
79
|
+
data.delete_if { |key, value| value.nil? }
|
80
|
+
%w(plot tagline overview).each do |key|
|
81
|
+
if data[key].respond_to?('first')
|
82
|
+
data[key] = data[key].first
|
83
|
+
end
|
84
|
+
data[key] = data[key].gsub(FILTER_HTML, '') unless data[key].blank?
|
85
|
+
end
|
86
|
+
data
|
87
|
+
end
|
88
|
+
|
89
|
+
# == Synopsis
|
71
90
|
# load the .nfo file into the @movie hash
|
72
91
|
def load
|
73
92
|
begin
|
@@ -83,6 +102,7 @@ class XbmcInfo
|
|
83
102
|
end
|
84
103
|
end
|
85
104
|
|
105
|
+
# == Synopsis
|
86
106
|
# has any of the data changed?
|
87
107
|
def dirty?
|
88
108
|
result = false
|
@@ -1,7 +1,8 @@
|
|
1
1
|
require File.join(File.dirname(__FILE__), 'config_editor')
|
2
2
|
require 'commandline/optionparser'
|
3
|
-
# include CommandLine
|
3
|
+
# can't include CommandLine thanks to HighLine polluting the namespace
|
4
4
|
|
5
|
+
# == Synopsis
|
5
6
|
# Command Line interface for the Dvdprofiler2Xbmc application.
|
6
7
|
# All application output is via AppConfig[:logger] so we have
|
7
8
|
# to set up the logger here.
|
@@ -19,9 +20,13 @@ module Dvdprofiler2xbmc
|
|
19
20
|
OK = 0
|
20
21
|
end
|
21
22
|
|
23
|
+
# == Synopsis
|
24
|
+
# The Command Line Interface
|
22
25
|
class CLI
|
23
26
|
include AppConfig
|
24
27
|
|
28
|
+
# == Synopsis
|
29
|
+
# Here's the main execution loop
|
25
30
|
def self.execute(stdout, arguments=[])
|
26
31
|
exit_code = ExitCode::OK
|
27
32
|
|
@@ -46,12 +51,15 @@ module Dvdprofiler2xbmc
|
|
46
51
|
|
47
52
|
if run_editor || od["--edit_config"]
|
48
53
|
editor = ConfigEditor.new
|
54
|
+
@saved_interrupt_message = DvdProfiler2Xbmc.interrupt_message
|
55
|
+
DvdProfiler2Xbmc.interrupt_message = ''
|
49
56
|
editor.execute
|
57
|
+
DvdProfiler2Xbmc.interrupt_message = @saved_interrupt_message
|
50
58
|
end
|
51
59
|
|
52
60
|
skip_execution = false
|
53
|
-
%w(--help --version --show_config --edit_config).each {|flag| skip_execution = true if od[flag]
|
54
|
-
unless skip_execution
|
61
|
+
%w(--help --version --show_config --edit_config).each {|flag| skip_execution = true if od[flag] }
|
62
|
+
unless skip_execution || run_editor
|
55
63
|
# create and execute class instance here
|
56
64
|
app = DvdProfiler2Xbmc.instance
|
57
65
|
app.execute
|
@@ -66,14 +74,14 @@ module Dvdprofiler2xbmc
|
|
66
74
|
exit_code
|
67
75
|
end
|
68
76
|
|
77
|
+
# == Synopsis
|
78
|
+
# the first reinitialize_logger adds the command line logging options to the default config
|
79
|
+
# then we load the config files
|
80
|
+
# then we run reinitialize_logger again to modify the logger for any logging options from the config files
|
69
81
|
def self.setup_app_config(od, logger)
|
70
82
|
# load config values
|
71
83
|
AppConfig.default
|
72
84
|
|
73
|
-
# the first reinitialize_logger adds the command line logging options to the default config
|
74
|
-
# then we load the config files
|
75
|
-
# then we run reinitialize_logger again to modify the logger for any logging options from the config files
|
76
|
-
|
77
85
|
reinitialize_logger(logger, od["--quiet"], od["--debug"])
|
78
86
|
AppConfig.load
|
79
87
|
AppConfig.save
|
@@ -88,6 +96,7 @@ module Dvdprofiler2xbmc
|
|
88
96
|
AppConfig[:logger].info { "logfile_level => #{AppConfig[:logfile_level].inspect}" } unless AppConfig[:logfile_level].nil?
|
89
97
|
end
|
90
98
|
|
99
|
+
# == Synopsis
|
91
100
|
# Setup the command line option parser
|
92
101
|
# Returns:: OptionParser instances
|
93
102
|
def self.setup_parser()
|
@@ -152,6 +161,7 @@ module Dvdprofiler2xbmc
|
|
152
161
|
options
|
153
162
|
end
|
154
163
|
|
164
|
+
# == Synopsis
|
155
165
|
# Initial setup of logger
|
156
166
|
def self.setup_logger
|
157
167
|
logger = Log4r::Logger.new('dvdprofiler2xbmc')
|
@@ -161,6 +171,7 @@ module Dvdprofiler2xbmc
|
|
161
171
|
logger
|
162
172
|
end
|
163
173
|
|
174
|
+
# == Synopsis
|
164
175
|
# Reinitialize the logger using the loaded config.
|
165
176
|
# logger:: logger for any user messages
|
166
177
|
# config:: is the application's config hash.
|
@@ -1,6 +1,7 @@
|
|
1
1
|
# require 'highline'
|
2
2
|
require "highline/import"
|
3
3
|
|
4
|
+
# == Synopsis
|
4
5
|
# monkey patch HighLine to get rid of the ugly message:
|
5
6
|
# Your answer isn't valid (must match #<Proc:0xb76cb378@/home/royw/views/dvdprofiler2xbmc/lib/dvdprofiler2xbmc/app_config.rb:180>)
|
6
7
|
# basically the problem is with inspecting a lambda validator, so just don't do it...
|
@@ -30,37 +31,51 @@ class HighLine
|
|
30
31
|
end
|
31
32
|
end
|
32
33
|
|
34
|
+
# == Synopsis
|
35
|
+
# This is a command line config editor
|
36
|
+
# == Usage
|
37
|
+
# editor = ConfigEditor.new
|
38
|
+
# editor.execute
|
33
39
|
class ConfigEditor
|
34
40
|
|
41
|
+
# == Synopsis
|
35
42
|
def initialize
|
36
43
|
end
|
37
44
|
|
45
|
+
# == Synopsis
|
46
|
+
# main execution loop for the config editor
|
38
47
|
def execute
|
39
|
-
@saved_interrupt_message = DvdProfiler2Xbmc.interrupt_message
|
40
|
-
DvdProfiler2Xbmc.interrupt_message = ''
|
41
48
|
report_invalid_config_items
|
42
49
|
begin
|
43
50
|
AppConfig[:logger].info('Configuration Editor')
|
44
51
|
|
52
|
+
# get the list of config fields
|
45
53
|
fields = AppConfig.navigation.collect do |page|
|
46
54
|
page.values.flatten.select do|field|
|
47
55
|
AppConfig.data_type[field]
|
48
56
|
end
|
49
57
|
end.flatten.uniq.compact
|
58
|
+
|
59
|
+
# edit the fields
|
50
60
|
while(field = menu_select('field', fields))
|
51
61
|
begin
|
52
62
|
edit_field(field)
|
53
63
|
rescue
|
54
64
|
end
|
55
65
|
end
|
66
|
+
|
67
|
+
# save changes?
|
56
68
|
if agree("Save? yes/no") {|q| q.default = 'yes'}
|
57
69
|
AppConfig.save
|
58
70
|
end
|
59
71
|
rescue
|
60
72
|
end
|
61
|
-
DvdProfiler2Xbmc.interrupt_message = @saved_interrupt_message
|
62
73
|
end
|
63
74
|
|
75
|
+
protected
|
76
|
+
|
77
|
+
# == Synopsis
|
78
|
+
# Reports the config items that are not valid
|
64
79
|
def report_invalid_config_items
|
65
80
|
buf = []
|
66
81
|
AppConfig.validate.each do |field, value|
|
@@ -74,6 +89,8 @@ class ConfigEditor
|
|
74
89
|
end
|
75
90
|
end
|
76
91
|
|
92
|
+
# == Synopsis
|
93
|
+
# Give a field name, let the use edit it via menus
|
77
94
|
def edit_field(field)
|
78
95
|
result = true
|
79
96
|
while(result)
|
@@ -105,6 +122,8 @@ class ConfigEditor
|
|
105
122
|
result
|
106
123
|
end
|
107
124
|
|
125
|
+
# == Synopsis
|
126
|
+
# display field information
|
108
127
|
def field_header(field)
|
109
128
|
say "\n"
|
110
129
|
say "-------------------------------"
|
@@ -120,11 +139,15 @@ class ConfigEditor
|
|
120
139
|
say "\n"
|
121
140
|
end
|
122
141
|
|
142
|
+
# == Synopsis
|
143
|
+
# wrapper for editing most objects
|
123
144
|
def object_edit(field)
|
124
145
|
value = data_type_editor(field, AppConfig.data_type[field], AppConfig.initial[field])
|
125
146
|
AppConfig.config[field] = value unless value.nil?
|
126
147
|
end
|
127
148
|
|
149
|
+
# == Synopsis
|
150
|
+
# wrapper for editing hashes
|
128
151
|
def hash_add(field)
|
129
152
|
value = data_type_editor(field, AppConfig.data_type[field], AppConfig.initial[field])
|
130
153
|
if value =~ /([^,]+)\s*,\s*(\S.*)/
|
@@ -132,6 +155,8 @@ class ConfigEditor
|
|
132
155
|
end
|
133
156
|
end
|
134
157
|
|
158
|
+
# == Synopsis
|
159
|
+
# delete a key/pair from a hash field
|
135
160
|
def hash_delete(field)
|
136
161
|
choose do |menu|
|
137
162
|
menu.prompt = "Please select to remove: "
|
@@ -150,12 +175,16 @@ class ConfigEditor
|
|
150
175
|
end
|
151
176
|
end
|
152
177
|
|
178
|
+
# == Synopsis
|
179
|
+
# add a value to an array field
|
153
180
|
def array_add(field)
|
154
181
|
value = data_type_editor(field, AppConfig.data_type[field], AppConfig.initial[field])
|
155
182
|
AppConfig.config[field] += [value].flatten unless value.nil?
|
156
183
|
AppConfig.config[field].uniq!
|
157
184
|
end
|
158
185
|
|
186
|
+
# == Synopsis
|
187
|
+
# delete a value from an array field
|
159
188
|
def array_delete(field)
|
160
189
|
choose do |menu|
|
161
190
|
menu.prompt = "Please select to remove: "
|
@@ -168,14 +197,23 @@ class ConfigEditor
|
|
168
197
|
end
|
169
198
|
end
|
170
199
|
|
200
|
+
# == Synopsis
|
201
|
+
# Get string containing pretty inspection of the given object.
|
202
|
+
# Some objects look better when inspected with pretty_inspect than
|
203
|
+
# with inspect and vice versa.
|
171
204
|
def prettify(obj)
|
205
|
+
# Most look best with pretty_inspect
|
172
206
|
str = obj.pretty_inspect
|
207
|
+
# Mashes need to be first converted to Hashes then pretty_inspect
|
173
208
|
if obj.kind_of? Mash
|
174
209
|
str = obj.to_hash.pretty_inspect
|
175
210
|
end
|
211
|
+
# For Arrays, pretty_inspect displays one value per line which
|
212
|
+
# uses up too much real estate
|
176
213
|
if obj.kind_of? Array
|
177
214
|
str = obj.inspect
|
178
215
|
end
|
216
|
+
# Manually format Hashes so keys and values each display in columns
|
179
217
|
if obj.kind_of? Hash
|
180
218
|
key_length = 0
|
181
219
|
obj.keys.each do |key|
|
@@ -190,6 +228,8 @@ class ConfigEditor
|
|
190
228
|
str
|
191
229
|
end
|
192
230
|
|
231
|
+
# == Synopsis
|
232
|
+
# data type specific editing of fields
|
193
233
|
def data_type_editor(field, data_type, default_value=nil)
|
194
234
|
value = nil
|
195
235
|
case data_type
|
@@ -246,6 +286,8 @@ class ConfigEditor
|
|
246
286
|
|
247
287
|
VALUE_LENGTH = 60
|
248
288
|
|
289
|
+
# == Synopsis
|
290
|
+
# a generic selection menu
|
249
291
|
def menu_select(name, values)
|
250
292
|
result = false
|
251
293
|
say("\n#{name.capitalize} Selection")
|
@@ -261,6 +303,9 @@ class ConfigEditor
|
|
261
303
|
result
|
262
304
|
end
|
263
305
|
|
306
|
+
# == Synopsis
|
307
|
+
# When displaying the fields to chose from, optionally display the
|
308
|
+
# fields data validity using color
|
264
309
|
def field_name_choice(value)
|
265
310
|
value_str = sprintf("%-#{VALUE_LENGTH}.#{VALUE_LENGTH}s", first_line(value))
|
266
311
|
if AppConfig.config[:color_enabled]
|
@@ -274,6 +319,9 @@ class ConfigEditor
|
|
274
319
|
str
|
275
320
|
end
|
276
321
|
|
322
|
+
# == Synopsis
|
323
|
+
# limit the length of a string to VALUE_LENGTH and append ellipses if the string
|
324
|
+
# is abbreviated.
|
277
325
|
def first_line(value)
|
278
326
|
config_value = AppConfig.config[value]
|
279
327
|
if config_value.kind_of? Mash
|
data/spec/config_editor_spec.rb
CHANGED
@@ -32,42 +32,42 @@ describe "ConfigEditor" do
|
|
32
32
|
it "should return true for boolean data type edits that recieve y" do
|
33
33
|
@input << "y\n"
|
34
34
|
@input.rewind
|
35
|
-
value = @editor.
|
35
|
+
value = @editor.send('data_type_editor', 'field_name', :BOOLEAN, false)
|
36
36
|
value.should be_true
|
37
37
|
end
|
38
38
|
|
39
39
|
it "should return false for boolean data type edits that recieve n" do
|
40
40
|
@input << "n\n"
|
41
41
|
@input.rewind
|
42
|
-
value = @editor.
|
42
|
+
value = @editor.send('data_type_editor', 'field_name', :BOOLEAN, true)
|
43
43
|
value.should be_false
|
44
44
|
end
|
45
45
|
|
46
46
|
it "should return true for boolean data type edits that recieve yes" do
|
47
47
|
@input << "yes\n"
|
48
48
|
@input.rewind
|
49
|
-
value = @editor.
|
49
|
+
value = @editor.send('data_type_editor', 'field_name', :BOOLEAN, false)
|
50
50
|
value.should be_true
|
51
51
|
end
|
52
52
|
|
53
53
|
it "should return false for boolean data type edits that recieve no" do
|
54
54
|
@input << "no\n"
|
55
55
|
@input.rewind
|
56
|
-
value = @editor.
|
56
|
+
value = @editor.send('data_type_editor', 'field_name', :BOOLEAN, true)
|
57
57
|
value.should be_false
|
58
58
|
end
|
59
59
|
|
60
60
|
it "should return false for boolean data type edits that default to false" do
|
61
61
|
@input << "\n\n"
|
62
62
|
@input.rewind
|
63
|
-
value = @editor.
|
63
|
+
value = @editor.send('data_type_editor', 'field_name', :BOOLEAN, false)
|
64
64
|
value.should be_false
|
65
65
|
end
|
66
66
|
|
67
67
|
it "should return true for boolean data type edits that default to true" do
|
68
68
|
@input << "\n"
|
69
69
|
@input.rewind
|
70
|
-
value = @editor.
|
70
|
+
value = @editor.send('data_type_editor', 'field_name', :BOOLEAN, true)
|
71
71
|
value.should be_true
|
72
72
|
end
|
73
73
|
|
@@ -95,7 +95,7 @@ describe "ConfigEditor" do
|
|
95
95
|
default_pathspec = File.expand_path(File.join(File.dirname(__FILE__), '..'))
|
96
96
|
@input << pathspec << "\n"
|
97
97
|
@input.rewind
|
98
|
-
value = @editor.
|
98
|
+
value = @editor.send('data_type_editor', 'images_dir', :PATHSPEC, default_pathspec)
|
99
99
|
value.should == pathspec
|
100
100
|
end
|
101
101
|
|
@@ -103,7 +103,7 @@ describe "ConfigEditor" do
|
|
103
103
|
default_pathspec = File.expand_path(File.join(File.dirname(__FILE__), '..'))
|
104
104
|
@input << "\n"
|
105
105
|
@input.rewind
|
106
|
-
value = @editor.
|
106
|
+
value = @editor.send('data_type_editor', 'images_dir', :PATHSPEC, default_pathspec)
|
107
107
|
value.should == default_pathspec
|
108
108
|
end
|
109
109
|
|
@@ -113,7 +113,7 @@ describe "ConfigEditor" do
|
|
113
113
|
default_pathspec = File.expand_path(File.join(File.dirname(__FILE__), '..'))
|
114
114
|
@input << badpathspec << "\n" << goodpathspec << "\n"
|
115
115
|
@input.rewind
|
116
|
-
value = @editor.
|
116
|
+
value = @editor.send('data_type_editor', 'images_dir', :PATHSPEC, default_pathspec)
|
117
117
|
value.should == goodpathspec
|
118
118
|
end
|
119
119
|
|
@@ -122,7 +122,7 @@ describe "ConfigEditor" do
|
|
122
122
|
default_filespec = File.expand_path(File.join(File.dirname(__FILE__), 'ruby_dragon'))
|
123
123
|
@input << filespec << "\n"
|
124
124
|
@input.rewind
|
125
|
-
value = @editor.
|
125
|
+
value = @editor.send('data_type_editor', 'collection_filespec', :FILESPEC, default_filespec)
|
126
126
|
value.should == filespec
|
127
127
|
end
|
128
128
|
|
@@ -132,28 +132,28 @@ describe "ConfigEditor" do
|
|
132
132
|
default_filespec = File.expand_path(File.join(File.dirname(__FILE__), 'ruby_dragon'))
|
133
133
|
@input << badfilespec << "\n" << goodfilespec << "\n"
|
134
134
|
@input.rewind
|
135
|
-
value = @editor.
|
135
|
+
value = @editor.send('data_type_editor', 'collection_filespec', :FILESPEC, default_filespec)
|
136
136
|
value.should == goodfilespec
|
137
137
|
end
|
138
138
|
|
139
139
|
it "should accept valid permission 0" do
|
140
140
|
@input << "0" << "\n"
|
141
141
|
@input.rewind
|
142
|
-
value = @editor.
|
142
|
+
value = @editor.send('data_type_editor', 'file_permissions', :PERMISSIONS, 0644.to_s(8))
|
143
143
|
value.should == 0.to_s(8)
|
144
144
|
end
|
145
145
|
|
146
146
|
it "should accept valid permission 7777" do
|
147
147
|
@input << "7777" << "\n"
|
148
148
|
@input.rewind
|
149
|
-
value = @editor.
|
149
|
+
value = @editor.send('data_type_editor', 'file_permissions', :PERMISSIONS, 0644.to_s(8))
|
150
150
|
value.should == 07777.to_s(8)
|
151
151
|
end
|
152
152
|
|
153
153
|
it "should reject permission > 7777" do
|
154
154
|
@input << "65432" << "\n" << "6543" << "\n"
|
155
155
|
@input.rewind
|
156
|
-
value = @editor.
|
156
|
+
value = @editor.send('data_type_editor', 'file_permissions', :PERMISSIONS, 0644.to_s(8))
|
157
157
|
value.should == 06543.to_s(8)
|
158
158
|
end
|
159
159
|
|
@@ -161,7 +161,7 @@ describe "ConfigEditor" do
|
|
161
161
|
strings = %w(foo bar howdy)
|
162
162
|
@input << strings.join("\n") << "\n\n"
|
163
163
|
@input.rewind
|
164
|
-
value = @editor.
|
164
|
+
value = @editor.send('data_type_editor', 'image_extensions', :ARRAY_OF_STRINGS)
|
165
165
|
value.should == strings
|
166
166
|
end
|
167
167
|
|
@@ -169,7 +169,7 @@ describe "ConfigEditor" do
|
|
169
169
|
strings = []
|
170
170
|
@input << strings.join("\n") << "\n\n"
|
171
171
|
@input.rewind
|
172
|
-
value = @editor.
|
172
|
+
value = @editor.send('data_type_editor', 'image_extensions', :ARRAY_OF_STRINGS)
|
173
173
|
value.should == strings
|
174
174
|
end
|
175
175
|
|
@@ -178,7 +178,7 @@ describe "ConfigEditor" do
|
|
178
178
|
pathspecs = [cwd, File.join(cwd, '..')]
|
179
179
|
@input << pathspecs.join("\n") << "\n\n\n\n"
|
180
180
|
@input.rewind
|
181
|
-
value = @editor.
|
181
|
+
value = @editor.send('data_type_editor', 'directories', :ARRAY_OF_PATHSPECS)
|
182
182
|
value.should == pathspecs
|
183
183
|
end
|
184
184
|
|
@@ -186,7 +186,7 @@ describe "ConfigEditor" do
|
|
186
186
|
pathspecs = []
|
187
187
|
@input << pathspecs.join("\n") << "\n\n\n"
|
188
188
|
@input.rewind
|
189
|
-
value = @editor.
|
189
|
+
value = @editor.send('data_type_editor', 'directories', :ARRAY_OF_PATHSPECS)
|
190
190
|
value.should == pathspecs
|
191
191
|
end
|
192
192
|
|
@@ -121,7 +121,7 @@ describe "FanartController" do
|
|
121
121
|
buf = []
|
122
122
|
%w(mid original thumb).each do |size|
|
123
123
|
filespec = File.join(TMPDIR, "Die Hard - 1988-fanart.#{size}.0.jpg")
|
124
|
-
buf << filespec unless
|
124
|
+
buf << "Missing: \"#{filespec}\"" unless File.exist?(filespec) && (File.size(filespec) > 0)
|
125
125
|
end
|
126
126
|
puts buf.join("\n") unless buf.empty?
|
127
127
|
buf.empty?.should be_true
|
@@ -141,7 +141,7 @@ describe "FanartController" do
|
|
141
141
|
controller.send('fetch_fanart', imdb_id)
|
142
142
|
%w(mid original thumb).each do |size|
|
143
143
|
filespec = File.join(TMPDIR, "#{title}-fanart.#{size}.0.jpg")
|
144
|
-
buf << filespec unless
|
144
|
+
buf << filespec unless File.exist?(filespec) && (File.size(filespec) > 0)
|
145
145
|
end
|
146
146
|
end
|
147
147
|
puts buf.join("\n") unless buf.empty?
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: royw-dvdprofiler2xbmc
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.18
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Roy Wright
|
@@ -9,7 +9,7 @@ autorequire:
|
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
11
|
|
12
|
-
date: 2009-04-
|
12
|
+
date: 2009-04-19 00:00:00 -07:00
|
13
13
|
default_executable: dvdprofiler2xbmc
|
14
14
|
dependencies:
|
15
15
|
- !ruby/object:Gem::Dependency
|