scene-toolkit 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore ADDED
@@ -0,0 +1,4 @@
1
+ .DS_Store
2
+ coverage
3
+ rdoc
4
+ pkg
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2009 Víctor Martínez
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.rdoc ADDED
@@ -0,0 +1,17 @@
1
+ = Scene-Toolkit
2
+
3
+ Tool to assist scene MP3 library maintenance
4
+
5
+ == Note on Patches/Pull Requests
6
+
7
+ * Fork the project.
8
+ * Make your feature addition or bug fix.
9
+ * Add tests for it. This is important so I don't break it in a
10
+ future version unintentionally.
11
+ * Commit, do not mess with rakefile, version, or history.
12
+ (if you want to have your own version, that is fine but bump version in a commit by itself I can ignore when I pull)
13
+ * Send me a pull request. Bonus points for topic branches.
14
+
15
+ == Copyright
16
+
17
+ Copyright (c) 2010 Víctor Martínez. See LICENSE for details.
data/Rakefile ADDED
@@ -0,0 +1,20 @@
1
+ require 'rubygems'
2
+ require 'rake'
3
+
4
+ begin
5
+ require 'jeweler'
6
+ Jeweler::Tasks.new do |gem|
7
+ gem.name = "scene-toolkit"
8
+ gem.summary = %Q{Tool to assist scene MP3 library maintenance}
9
+ gem.description = %Q{Tool to assist scene MP3 library maintenance}
10
+ gem.email = "knoopx@gmail.com"
11
+ gem.homepage = "http://github.com/knoopx/scene-toolkit"
12
+ gem.authors = ["Víctor Martínez"]
13
+ gem.add_dependency "optitron", ">= 0.0.7"
14
+ gem.add_dependency "activesupport", ">= 2.3.5"
15
+ gem.add_dependency "rainbow", ">= 1.1"
16
+ end
17
+ Jeweler::GemcutterTasks.new
18
+ rescue LoadError
19
+ puts "Jeweler (or a dependency) not available. Install it with: gem install jeweler"
20
+ end
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.1.0
data/bin/scene-toolkit ADDED
@@ -0,0 +1,22 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ $:.unshift(File.expand_path(File.join(File.dirname(__FILE__), '..', 'lib')))
4
+
5
+ require 'rubygems'
6
+ require 'optitron'
7
+ require 'scene_toolkit'
8
+
9
+
10
+ Optitron.dispatch(SceneToolkit::CLI.new) do
11
+
12
+ cmd "verify", "Verify library or release. Executes all validations if none specified" do
13
+ arg "directory", "The root directory to verify"
14
+ opt "name", "Validate release name"
15
+ opt "required-files", "Validate inclusion of required files"
16
+ opt "checksum", "Validate release CRC-32 checksum"
17
+ opt "display-valid", "Display valid releases too"
18
+ opt "flush-cache", "Flush file modification cache"
19
+ opt "move-invalid-to", "Move INVALID releases to specified folder", :type => :string
20
+ opt "move-valid-to", "Move VALID releases to specified folder", :type => :string
21
+ end
22
+ end
@@ -0,0 +1,8 @@
1
+ require 'active_support'
2
+
3
+ module SceneToolkit
4
+ require 'scene_toolkit/cache'
5
+ require 'scene_toolkit/ext'
6
+ require 'scene_toolkit/release'
7
+ require 'scene_toolkit/cli'
8
+ end
@@ -0,0 +1,4 @@
1
+ module SceneToolkit::Cache
2
+ require 'scene_toolkit/cache/base'
3
+ require 'scene_toolkit/cache/releases'
4
+ end
@@ -0,0 +1,10 @@
1
+ require 'pstore'
2
+
3
+ class SceneToolkit::Cache::Base < PStore
4
+ attr_reader :releases
5
+
6
+ def initialize
7
+ super(File.expand_path(File.join("~", ".scene-toolkit")))
8
+ @releases = SceneToolkit::Cache::Releases.new(self)
9
+ end
10
+ end
@@ -0,0 +1,65 @@
1
+ class SceneToolkit::Cache::Releases
2
+ def initialize(cache, cache_key = :releases)
3
+ @cache = cache
4
+ @cache_key = cache_key
5
+ @cache.transaction do
6
+ @cache[@cache_key] ||= {}
7
+ end
8
+ end
9
+
10
+ def modified?(release)
11
+ @cache.transaction(true) do
12
+ if @cache[@cache_key].has_key?(release.path)
13
+ @cache[@cache_key][release.path][:files].each do |filename, mtime|
14
+ file_path = File.join(release.path, filename)
15
+ unless File.exists?(file_path) and (@cache[@cache_key][release.path][:files].has_key?(filename) and File.stat(file_path).mtime.eql?(mtime))
16
+ flush(release)
17
+ return true
18
+ end
19
+ end
20
+ false
21
+ else
22
+ true
23
+ end
24
+ end
25
+ end
26
+
27
+ def errors(release)
28
+ @cache.transaction(true) do
29
+ if @cache[@cache_key].has_key?(release.path)
30
+ @cache[@cache_key][release.path][:errors]
31
+ else
32
+ raise RuntimeError.new("Release not catched")
33
+ end
34
+ end
35
+ end
36
+
37
+ def files(release)
38
+ @cache.transaction(true) do
39
+ @cache[@cache_key][release.path][:files]
40
+ end
41
+ end
42
+
43
+ def flush(release)
44
+ @cache.transaction do
45
+ @cache[@cache_key].delete(release.path) if @cache[@cache_key].has_key?(release.path)
46
+ end
47
+ end
48
+
49
+ def flush_all
50
+ @cache.transaction do
51
+ @cache[@cache_key] = {}
52
+ end
53
+ end
54
+
55
+ def store(release)
56
+ @cache.transaction do
57
+ @cache[@cache_key][release.path] = {}
58
+ @cache[@cache_key][release.path][:errors] = release.errors
59
+ @cache[@cache_key][release.path][:files] = Dir.glob(File.join(release.path, "*")).inject({}) do |collection, f|
60
+ collection[File.basename(f)] = File.stat(f).mtime
61
+ collection
62
+ end
63
+ end
64
+ end
65
+ end
@@ -0,0 +1,118 @@
1
+ require 'fileutils'
2
+ require 'rainbow'
3
+
4
+ class SceneToolkit::CLI
5
+ def initialize
6
+ @cache = SceneToolkit::Cache::Base.new
7
+ end
8
+
9
+ def flush_cache
10
+ @cache.releases.flush_all
11
+ end
12
+
13
+ def verify(directory, opts)
14
+ opts.underscore_and_symbolize_keys!
15
+ validations = []
16
+ SceneToolkit::Release::VALIDATIONS.each do |validation|
17
+ validations << validation if opts.delete(validation).eql?(true)
18
+ end
19
+
20
+ if validations.none?
21
+ validations = SceneToolkit::Release::VALIDATIONS
22
+ end
23
+
24
+ invalid_target_directory = opts.delete(:move_invalid_to)
25
+ unless invalid_target_directory.nil?
26
+ raise ArgumentError.new("#{invalid_target_directory} does not exist") unless File.directory?(invalid_target_directory)
27
+ end
28
+
29
+
30
+ valid_target_directory = opts.delete(:move_valid_to)
31
+ unless valid_target_directory.nil?
32
+ raise ArgumentError.new("#{invalid_target_directory} does not exist") unless File.directory?(valid_target_directory)
33
+ end
34
+
35
+ flush_cache if opts.delete(:flush_cache)
36
+
37
+ release_count = 0
38
+ valid_releases = 0
39
+ invalid_releases = 0
40
+
41
+ each_release(directory) do |release|
42
+ release_count += 1
43
+
44
+ if release.valid?(validations)
45
+ valid_releases += 1
46
+ if opts[:display_valid]
47
+ puts release.name.foreground(:green)
48
+ puts release.path
49
+ puts
50
+ end
51
+
52
+ unless valid_target_directory.nil?
53
+ move_release(release, valid_target_directory)
54
+ end
55
+ else
56
+ invalid_releases += 1
57
+ puts release.name.foreground(:red)
58
+ puts release.path
59
+ release.errors.each do |error|
60
+ puts " - #{error}"
61
+ end
62
+
63
+ puts
64
+
65
+ unless invalid_target_directory.nil?
66
+ move_release(release, invalid_target_directory)
67
+ end
68
+
69
+ puts
70
+ end
71
+ end
72
+
73
+ puts "#{valid_releases} of #{release_count} releases valid".foreground(:yellow)
74
+ puts "#{invalid_releases} of #{release_count} releases invalid".foreground(:yellow)
75
+ end
76
+
77
+ protected
78
+
79
+ def move_release(release, destination)
80
+ target_dir = File.join(destination, release.name)
81
+ puts "Moving release to #{target_dir}".foreground(:yellow)
82
+
83
+ if File.directory?(target_dir)
84
+ puts "Target directory already exists. Skipping.".foreground(:red)
85
+ else
86
+ begin
87
+ FileUtils.mv(release.path, target_dir)
88
+ rescue => e
89
+ puts e.message.foreground(:red)
90
+ end
91
+ end
92
+ puts
93
+ end
94
+
95
+ def each_release(source, &block)
96
+ raise ArgumentError unless block_given?
97
+ raise ArgumentError("#{source} is not a directory") unless File.directory?(source)
98
+
99
+ releases = []
100
+ uids = {}
101
+
102
+ Dir.glob(File.join(source, "**", "*.mp3")).each do |file|
103
+ release_path = File.expand_path(File.dirname(file))
104
+
105
+ unless releases.include?(release_path)
106
+ release = SceneToolkit::Release.new(release_path, @cache)
107
+ releases << release_path
108
+ yield(release)
109
+
110
+ if uids.has_key?(release.uid)
111
+ release.errors << "Duplicate release of #{uids[release.uid]}"
112
+ else
113
+ uids[release.uid] = release.path
114
+ end
115
+ end
116
+ end
117
+ end
118
+ end
@@ -0,0 +1,3 @@
1
+ module SceneToolkit::Ext
2
+ require 'scene_toolkit/ext/hash'
3
+ end
@@ -0,0 +1,10 @@
1
+ module SceneToolkit::Ext::Hash
2
+ def underscore_and_symbolize_keys!(specials={})
3
+ self.each_key do |k|
4
+ self[specials.has_key?(k) ? specials[k] : k.underscore] = self.delete(k)
5
+ end
6
+ self.symbolize_keys!
7
+ end
8
+ end
9
+
10
+ Hash.send(:include, SceneToolkit::Ext::Hash)
@@ -0,0 +1,93 @@
1
+ require 'digest/md5'
2
+ require 'zlib'
3
+
4
+ class SceneToolkit::Release
5
+ REQUIRED_FILES = [:sfv, :nfo, :m3u]
6
+ VALIDATIONS = [:name, :required_files, :checksum]
7
+
8
+ attr_accessor :name, :path, :uid
9
+ attr_accessor :errors
10
+
11
+ class << self
12
+ def has_files_with_extension(*exts)
13
+ exts.each do |ext|
14
+ define_method "#{ext}_files" do
15
+ files.select { |f| File.extname(f).downcase.eql?(".#{ext}") }
16
+ end
17
+ end
18
+ end
19
+ end
20
+ has_files_with_extension :mp3, :sfv, :nfo, :m3u
21
+
22
+ def initialize(path, cache)
23
+ @cache = cache
24
+ @path = path
25
+ @name = File.basename(path)
26
+ @uid = Digest::MD5.hexdigest(@name.downcase.gsub(/[^A-Z0-9]/i, ' ').gsub(/\s+/, ' '))
27
+ @errors = []
28
+ end
29
+
30
+ def valid?(validate = VALIDATIONS)
31
+ if @cache.releases.modified?(self)
32
+ @errors = []
33
+ validate.each do |validation|
34
+ send("valid_#{validation}?")
35
+ end
36
+ @cache.releases.store(self)
37
+ else
38
+ @errors = @cache.releases.errors(self)
39
+ end
40
+ @errors.none?
41
+ end
42
+
43
+ def valid_required_files?
44
+ REQUIRED_FILES.each do |ext|
45
+ file_count = send("#{ext}_files")
46
+ @errors << "No #{ext} found." if file_count.none?
47
+ @errors << "Multiple #{ext} found." if file_count.size > 1
48
+ end
49
+ end
50
+
51
+ def files
52
+ Dir.glob(File.join(@path, "*"))
53
+ end
54
+
55
+ def valid_checksum?
56
+ sfv_file = sfv_files.first
57
+
58
+ return if sfv_file.nil?
59
+
60
+ files_to_check = files.inject({}) do |collection, file|
61
+ collection[File.basename(file).downcase] = File.expand_path(file)
62
+ collection
63
+ end
64
+
65
+ #todo: set warning if no matches were found on sfv file
66
+ matched_something = false
67
+
68
+ File.read(sfv_file).split(/[\r\n]+/).each do |line|
69
+ # if (/(generated|raped)/i =~ line and not /MorGoTH/i =~ line)
70
+ # @errors << "Possibly tampered SFV: #{line.strip}"
71
+ # end
72
+
73
+ if match = /^(.+?)\s+([\dA-Fa-f]{8})$/.match(line)
74
+ filename, checksum = match.captures
75
+ if files_to_check.has_key?(filename.downcase)
76
+ unless Zlib.crc32(File.read(files_to_check[filename.downcase])).eql?(checksum.hex)
77
+ @errors << "#{filename} is corrupted"
78
+ end
79
+ else
80
+ @errors << "File #{filename} not found"
81
+ end
82
+ matched_something = true
83
+ end
84
+ end
85
+ end
86
+
87
+ def valid_name?
88
+ @errors << "Release name is not a valid scene release name" unless @name =~ /^([A-Z0-9\-_.()&]+)\-(\d{4}|\d{3}x|\d{2}xx)\-([A-Z0-9_]+)$/i
89
+ @errors << "Release name is lowercased" if @name.eql?(@name.downcase)
90
+ @errors << "Release name is uppercased" if @name.eql?(@name.upcase)
91
+ end
92
+ end
93
+
metadata ADDED
@@ -0,0 +1,127 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: scene-toolkit
3
+ version: !ruby/object:Gem::Version
4
+ hash: 27
5
+ prerelease: false
6
+ segments:
7
+ - 0
8
+ - 1
9
+ - 0
10
+ version: 0.1.0
11
+ platform: ruby
12
+ authors:
13
+ - "V\xC3\xADctor Mart\xC3\xADnez"
14
+ autorequire:
15
+ bindir: bin
16
+ cert_chain: []
17
+
18
+ date: 2010-08-18 00:00:00 +02:00
19
+ default_executable: scene-toolkit
20
+ dependencies:
21
+ - !ruby/object:Gem::Dependency
22
+ name: optitron
23
+ prerelease: false
24
+ requirement: &id001 !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ">="
28
+ - !ruby/object:Gem::Version
29
+ hash: 17
30
+ segments:
31
+ - 0
32
+ - 0
33
+ - 7
34
+ version: 0.0.7
35
+ type: :runtime
36
+ version_requirements: *id001
37
+ - !ruby/object:Gem::Dependency
38
+ name: activesupport
39
+ prerelease: false
40
+ requirement: &id002 !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - ">="
44
+ - !ruby/object:Gem::Version
45
+ hash: 9
46
+ segments:
47
+ - 2
48
+ - 3
49
+ - 5
50
+ version: 2.3.5
51
+ type: :runtime
52
+ version_requirements: *id002
53
+ - !ruby/object:Gem::Dependency
54
+ name: rainbow
55
+ prerelease: false
56
+ requirement: &id003 !ruby/object:Gem::Requirement
57
+ none: false
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ hash: 13
62
+ segments:
63
+ - 1
64
+ - 1
65
+ version: "1.1"
66
+ type: :runtime
67
+ version_requirements: *id003
68
+ description: Tool to assist scene MP3 library maintenance
69
+ email: knoopx@gmail.com
70
+ executables:
71
+ - scene-toolkit
72
+ extensions: []
73
+
74
+ extra_rdoc_files:
75
+ - LICENSE
76
+ - README.rdoc
77
+ files:
78
+ - .gitignore
79
+ - LICENSE
80
+ - README.rdoc
81
+ - Rakefile
82
+ - VERSION
83
+ - bin/scene-toolkit
84
+ - lib/scene_toolkit.rb
85
+ - lib/scene_toolkit/cache.rb
86
+ - lib/scene_toolkit/cache/base.rb
87
+ - lib/scene_toolkit/cache/releases.rb
88
+ - lib/scene_toolkit/cli.rb
89
+ - lib/scene_toolkit/ext.rb
90
+ - lib/scene_toolkit/ext/hash.rb
91
+ - lib/scene_toolkit/release.rb
92
+ has_rdoc: true
93
+ homepage: http://github.com/knoopx/scene-toolkit
94
+ licenses: []
95
+
96
+ post_install_message:
97
+ rdoc_options:
98
+ - --charset=UTF-8
99
+ require_paths:
100
+ - lib
101
+ required_ruby_version: !ruby/object:Gem::Requirement
102
+ none: false
103
+ requirements:
104
+ - - ">="
105
+ - !ruby/object:Gem::Version
106
+ hash: 3
107
+ segments:
108
+ - 0
109
+ version: "0"
110
+ required_rubygems_version: !ruby/object:Gem::Requirement
111
+ none: false
112
+ requirements:
113
+ - - ">="
114
+ - !ruby/object:Gem::Version
115
+ hash: 3
116
+ segments:
117
+ - 0
118
+ version: "0"
119
+ requirements: []
120
+
121
+ rubyforge_project:
122
+ rubygems_version: 1.3.7
123
+ signing_key:
124
+ specification_version: 3
125
+ summary: Tool to assist scene MP3 library maintenance
126
+ test_files: []
127
+