dircat 0.1.12 → 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.
Files changed (88) hide show
  1. data/LICENSE.txt +1 -1
  2. data/README.md +31 -6
  3. data/bin/dircat +5 -1
  4. data/bin/scat +9 -0
  5. data/dircat.gemspec +48 -15
  6. data/examples/example.rb +2 -1
  7. data/lib/dircat.rb +29 -34
  8. data/lib/dircat/cat_on_sqlite/cat_on_sqlite.rb +177 -0
  9. data/lib/dircat/cat_on_sqlite/directory_visitor.rb +106 -0
  10. data/lib/dircat/cat_on_sqlite/migration/00_create_dircat_properties.rb +13 -0
  11. data/lib/dircat/cat_on_sqlite/migration/01_create_categories.rb +12 -0
  12. data/lib/dircat/cat_on_sqlite/migration/02_create_images.rb +16 -0
  13. data/lib/dircat/cat_on_sqlite/migration/03_create_items.rb +23 -0
  14. data/lib/dircat/cat_on_sqlite/migration/04_create_taggings.rb +13 -0
  15. data/lib/dircat/cat_on_sqlite/migration/05_create_tags.rb +13 -0
  16. data/lib/dircat/cat_on_sqlite/model/category.rb +16 -0
  17. data/lib/dircat/cat_on_sqlite/model/image.rb +24 -0
  18. data/lib/dircat/cat_on_sqlite/model/item.rb +19 -0
  19. data/lib/dircat/cat_on_sqlite/model/tag.rb +58 -0
  20. data/lib/dircat/cat_on_sqlite/model/tagging.rb +9 -0
  21. data/lib/dircat/cat_on_sqlite/simple_cataloger_error.rb +9 -0
  22. data/lib/dircat/cat_on_sqlite_cli/cli_cat.rb +25 -0
  23. data/lib/dircat/cat_on_sqlite_cli/cli_server.rb +65 -0
  24. data/lib/dircat/cat_on_sqlite_cli/cmd_create.rb +56 -0
  25. data/lib/dircat/cat_on_sqlite_cli/cmd_list.rb +72 -0
  26. data/lib/dircat/cat_on_sqlite_cli/cmd_server.rb +62 -0
  27. data/lib/dircat/cat_on_sqlite_cli/cmd_update.rb +49 -0
  28. data/lib/dircat/{cat.rb → cat_on_yaml/cat_on_yaml.rb} +22 -10
  29. data/lib/dircat/cat_on_yaml/entry.rb +64 -0
  30. data/lib/dircat/{cli → cat_on_yaml_cli}/cli_dircat.rb +0 -0
  31. data/lib/dircat/{cli → cat_on_yaml_cli}/command_build.rb +53 -12
  32. data/lib/dircat/{cli → cat_on_yaml_cli}/command_diff.rb +4 -4
  33. data/lib/dircat/{cli → cat_on_yaml_cli}/command_query.rb +16 -2
  34. data/lib/dircat/config.rb +25 -0
  35. data/lib/dircat/extensions.rb +26 -0
  36. data/lib/dircat/server/helpers.rb +60 -0
  37. data/lib/dircat/server/my_static.rb +40 -0
  38. data/lib/dircat/server/web_server.rb +176 -0
  39. data/lib/dircat/version.rb +2 -1
  40. data/lib/dircat_on_sqlite.rb +31 -0
  41. data/lib/dircat_on_sqlite_cli.rb +37 -0
  42. data/lib/simple_cataloger_dm/cli.rb +32 -0
  43. data/lib/simple_cataloger_dm/core.rb +43 -0
  44. data/lib/simple_cataloger_dm/core/catalog.rb +141 -0
  45. data/lib/simple_cataloger_dm/core/directory_visitor.rb +107 -0
  46. data/lib/simple_cataloger_dm/core/extensions.rb +26 -0
  47. data/lib/simple_cataloger_dm/core/simple_cataloger_error.rb +9 -0
  48. data/lib/simple_cataloger_dm/models/category.rb +21 -0
  49. data/lib/simple_cataloger_dm/models/image.rb +30 -0
  50. data/lib/simple_cataloger_dm/models/item.rb +24 -0
  51. data/lib/simple_cataloger_dm/models/tag.rb +62 -0
  52. data/lib/simple_cataloger_dm/models/tagging.rb +11 -0
  53. data/lib/simple_cataloger_dm/server/helpers.rb +60 -0
  54. data/lib/simple_cataloger_dm/server/my_static.rb +40 -0
  55. data/lib/simple_cataloger_dm/server/web_server.rb +171 -0
  56. data/spec/dircat/cat_on_sqlite/cat_on_sqlite_spec.rb +41 -0
  57. data/spec/dircat/cat_on_sqlite_web/web_server_spec.rb +30 -0
  58. data/spec/dircat/{cat_spec.rb → cat_on_yaml/cat_on_yaml_spec.rb} +20 -14
  59. data/spec/dircat/{cli → cat_on_yaml_cli}/cli_dircat_spec.rb +0 -0
  60. data/spec/dircat/{cli → cat_on_yaml_cli}/command_build_spec.rb +4 -4
  61. data/spec/dircat/{cli → cat_on_yaml_cli}/command_diff_spec.rb +0 -0
  62. data/spec/dircat/{cli → cat_on_yaml_cli}/command_query_spec.rb +1 -1
  63. data/spec/fixtures/certified_output/cat_dir1_20120811.yaml +25 -0
  64. data/spec/fixtures/certified_output/cat_dir2_20120811.yaml +34 -0
  65. data/spec/fixtures/certified_output/{dircat1.yaml → dircat1_version_0.1.yaml} +0 -0
  66. data/spec/fixtures/certified_output/{dircat2.yaml → dircat2_version_0.1.yaml} +0 -0
  67. data/spec/fixtures/dir4/file1.txt +1 -0
  68. data/spec/fixtures/dir4/file2.txt +1 -0
  69. data/spec/fixtures/dir4/subdir/file3.txt +1 -0
  70. data/spec/fixtures/films/A-Z/A/Arancia meccanica [Stanley Kubrick][1971]/folder.jpg +0 -0
  71. data/spec/fixtures/films/A-Z/P/Plan 9 [Edward Wood][1959]/folder.jpg +0 -0
  72. data/spec/fixtures/films/Directors/[Akira Kurosawa]/[1940] Dersu Uzala [5]/folder.jpg +0 -0
  73. data/spec/fixtures/films/Directors/[Akira Kurosawa]/[1965] Barbarossa [Toshiro Mifune][4]/folder.jpg +0 -0
  74. data/spec/fixtures/films/Directors/[Federico Fellini]/[1963] 8 e mezzo [Marcello Mastroianni][Claudia Cardinale][5]/folder.jpg +0 -0
  75. data/spec/fixtures/films/Directors/[Woody Allen]/[1977] Annie Hall [Diane Keaton][5]/folder.jpg +0 -0
  76. data/{.gemtest → spec/fixtures/readme.txt} +0 -0
  77. data/spec/fixtures/tmp/test_ar.sqlite3 +0 -0
  78. data/spec/fixtures/tmp/test_ar.yml +7 -0
  79. data/spec/generate_directories_to_catalog.rb +80 -0
  80. data/spec/generate_mysql_catalog.rb +124 -0
  81. data/spec/spec_helper.rb +29 -0
  82. data/tasks/rspec.rake +0 -2
  83. metadata +390 -112
  84. data/lib/dircat/entry.rb +0 -61
  85. data/lib/dircat/extension_md5.rb +0 -25
  86. data/lib/dircat/extension_numeric.rb +0 -15
  87. data/spec/dircat/md5_spec.rb +0 -8
  88. data/spec/dircat/numeric_spec.rb +0 -13
@@ -0,0 +1,43 @@
1
+ # -*- coding: utf-8 -*-
2
+ #
3
+ # std lib
4
+ #
5
+ require 'pp'
6
+ require 'optparse'
7
+ require 'fileutils'
8
+ require 'ostruct'
9
+
10
+ #
11
+ # rubygems
12
+ #
13
+ gem "treevisitor", "0.2.2"
14
+ require 'treevisitor'
15
+
16
+ gem "optparse-command", "0.1.7"
17
+ require 'optparse-command'
18
+
19
+ require 'dm-core'
20
+ require 'dm-migrations'
21
+
22
+ #
23
+ # simple catalog
24
+ #
25
+ require 'simple_cataloger/version'
26
+ require 'simple_cataloger/simple_cataloger_error'
27
+ require 'simple_cataloger/extensions'
28
+
29
+ #
30
+ # db models
31
+ #
32
+ require 'simple_cataloger/models/item'
33
+ require 'simple_cataloger/models/tag'
34
+ require 'simple_cataloger/models/tagging'
35
+ require 'simple_cataloger/models/category'
36
+ require 'simple_cataloger/models/image'
37
+
38
+ #
39
+ # app
40
+ #
41
+ require 'simple_cataloger/directory_visitor'
42
+ require 'simple_cataloger/catalog'
43
+
@@ -0,0 +1,141 @@
1
+ # -*- coding: utf-8 -*-
2
+ module SimpleCataloger
3
+ class Catalog
4
+
5
+ attr_reader :name
6
+
7
+ def initialize(name)
8
+ @name = name
9
+
10
+ # find directory where to store catalog data
11
+ home_dir = ENV['HOME'] || ENV['APPDATA']
12
+ if RUBY_PLATFORM =~ /linux/
13
+ catalogs_dir = File.expand_path(File.join(home_dir, ".simple_cataloger"))
14
+ else
15
+ catalogs_dir = File.expand_path(File.join(home_dir, "simple_cataloger"))
16
+ end
17
+ Dir.mkdir(catalogs_dir) unless File.directory?(catalogs_dir)
18
+
19
+ db_filepath = File.join(catalogs_dir, "#{name}.sqlite3")
20
+ db_log_filepath = File.join(catalogs_dir, "#{name}.log")
21
+ @config_filepath = File.join(catalogs_dir, "#{name}.yml")
22
+ setup_db(db_filepath, db_log_filepath)
23
+ end
24
+
25
+ #
26
+ # Create a new catalog
27
+ # @param array of directories
28
+ #
29
+ def create(*catalog_roots)
30
+ if File.exist? @config_filepath
31
+ raise SimpleCatalogerError, "cannot create already existent catalog '#{@name}'"
32
+ end
33
+ @config = {
34
+ :roots => catalog_roots,
35
+ :ignore => ['sub', 'subtitles', 'images'],
36
+ :version => SimpleCataloger::VERSION
37
+ }
38
+ write_config
39
+ update
40
+ end
41
+
42
+ #
43
+ # Rebuild catalog
44
+ #
45
+ def update
46
+ unless File.exist? @config_filepath
47
+ raise "cannot update catalog #{@name}"
48
+ end
49
+ read_config
50
+ #
51
+ # migrate db
52
+ #
53
+ DataMapper.auto_migrate!
54
+ #DataMapper.auto_upgrade!
55
+
56
+ #
57
+ # Initialize categories
58
+ #
59
+ if @config[:categories]
60
+ # pp @config[:categories]
61
+ @config[:categories].each_pair do |category, tags|
62
+ # puts category
63
+ cat = Category.first_or_create(:name => category)
64
+ tags.each do |name|
65
+ tag = Tag.first_or_create(:name => name)
66
+ tag.category = cat
67
+ tag.save
68
+ end
69
+ end
70
+ end
71
+
72
+ #
73
+ # read catalog root
74
+ #
75
+ @config[:roots].each do |root|
76
+ dtw = TreeRb::DirTreeWalker.new(root)
77
+ dtw.ignore /^\./
78
+ @config[:ignore].each do |i|
79
+ dtw.ignore i
80
+ end
81
+ dtw.visit_file=true
82
+ dtw.run(DirectoryVisitor.new(root))
83
+ end
84
+
85
+ #
86
+ # cache rating tag
87
+ #
88
+ rating = Category.first(:name => "rating")
89
+ if rating
90
+ Item.all.each do |item|
91
+ t = item.tags.find { |t| t.category == rating }
92
+ if t
93
+ item.rating = t.name.to_i
94
+ item.save
95
+ end
96
+ end
97
+ end
98
+ end
99
+
100
+ def setup_db(db_path, db_log_filepath)
101
+ #TODO: logger usabile per loggare altri eventi oltre a quelli del db
102
+ DataMapper::Logger.new(db_log_filepath, :debug)
103
+ DataMapper.setup(:default, "sqlite3:#{db_path}")
104
+ DataMapper.finalize
105
+ end
106
+
107
+ #
108
+ # array of roots
109
+ #
110
+ def roots
111
+ read_config unless @config
112
+ @config[:roots]
113
+ end
114
+
115
+ #
116
+ # update config file with tag category list and association from category to tag name,
117
+ # so to not lose the association next time the catalog is rebuilt (updated)
118
+ #
119
+ def update_categories
120
+ h = {}
121
+ Category.all.each do |category|
122
+ next if ["rating", "year", "unknown"].include?(category.name)
123
+ h[category.name] = category.tags.collect { |tag| tag.name }
124
+ end
125
+ @config[:categories] = h
126
+ end
127
+
128
+ def write_config
129
+ File.open(@config_filepath, "w") { |f| f.write @config.to_yaml }
130
+ end
131
+
132
+ private
133
+
134
+ def read_config
135
+ #noinspection RubyResolve
136
+ @config = YAML.load(File.open(@config_filepath))
137
+ end
138
+
139
+ end # class
140
+
141
+ end # module SimpleCatalog
@@ -0,0 +1,107 @@
1
+ module SimpleCataloger
2
+
3
+ #
4
+ # Find directory without subdirectories
5
+ #
6
+ class DirectoryVisitor
7
+
8
+ def initialize(catalog_root)
9
+ super
10
+ @stack = []
11
+ @files = []
12
+ @nr = 0
13
+ @catalog_root = catalog_root
14
+ end
15
+
16
+ def enter_node(pathname)
17
+ # each directory is associated to a number,
18
+ # so when exit_tree_node is called and the number @nr is not increased
19
+ # the directory doesn't have subdirectories
20
+ @nr += 1
21
+
22
+ dirname = File.basename(pathname)
23
+ tags = Tag.extract_tags(dirname)
24
+ tags = tags.concat(@stack.last.tags).uniq unless @stack.empty?
25
+ info = OpenStruct.new(:nr => @nr, :pathname => pathname, :tags => tags, :name => Tag.extract_name(dirname))
26
+
27
+ @files = []
28
+ @stack.push(info)
29
+ end
30
+
31
+ def exit_node(pathname)
32
+ info = @stack.pop
33
+
34
+ if info.nr == @nr
35
+ # this directory doesn't have subdirectories
36
+ name = info.name
37
+ tag_names = info.tags
38
+ puts name
39
+ # pp tags
40
+
41
+ # every name must be unique, two films cannot have the some name
42
+ if Item.first( :name => name )
43
+ raise "item #{name} is not unique path #{pathname}"
44
+ end
45
+
46
+ item = Item.create(
47
+ :name => name,
48
+ :added_at => File.lstat(pathname).ctime,
49
+ :path => pathname,
50
+ :path_from_catalog_root => pathname[File.dirname(@catalog_root).length..-1]
51
+ )
52
+
53
+ #
54
+ # Associate item and tags
55
+ #
56
+ unknown = Category.first_or_create(:name => "unknown")
57
+ tag_names.each do |tag_name|
58
+ tag = Tag.first(:name => tag_name)
59
+ unless tag
60
+ tag = Tag.match_category(tag_name)
61
+ unless tag
62
+ tag = Tag.first_or_create(
63
+ :name => tag_name,
64
+ :category => unknown
65
+ )
66
+ end
67
+ end
68
+ Tagging.first_or_create(:item=>item, :tag=> tag)
69
+ end
70
+
71
+ #
72
+ # Searches images
73
+ #
74
+ @files.each do |pathname|
75
+ next unless pathname.match /(jpg|jpeg)$/
76
+
77
+ image = Image.first_or_create(
78
+ :path => pathname,
79
+ :path_from_catalog_root => pathname[File.dirname(@catalog_root).length..-1]
80
+ )
81
+ item.images << image
82
+
83
+ filename = File.basename(pathname)
84
+ tag_names = Tag.extract_tags(filename)
85
+ tag_names.each do |tag_name|
86
+ tag = Tag.first(:name => tag_name)
87
+ if tag
88
+ tag.images << image
89
+ tag.save
90
+ else
91
+ puts "WARNING: tag '#{tag_name}' not found"
92
+ end
93
+ end
94
+ end
95
+ item.save
96
+ end
97
+ end
98
+
99
+
100
+ #
101
+ # called when visit leaf node
102
+ #
103
+ def visit_leaf(pathname)
104
+ @files << pathname
105
+ end
106
+ end # class
107
+ end # module SimpleCataloger
@@ -0,0 +1,26 @@
1
+ # -*- coding: utf-8 -*-
2
+ module SimpleCataloger
3
+ module CoreExtensions
4
+ module Array
5
+ def extract_options!
6
+ last.is_a?(::Hash) ? pop : {}
7
+ end
8
+ end
9
+ end
10
+ end
11
+
12
+ class Array
13
+ include SimpleCataloger::CoreExtensions::Array
14
+ end
15
+
16
+ module URI
17
+ class << self
18
+
19
+ def parse_with_safety(uri)
20
+ parse_without_safety uri.gsub('[', '%5B').gsub(']', '%5D')
21
+ end
22
+
23
+ # alias parse_without_safety parse
24
+ # alias parse parse_with_safety
25
+ end
26
+ end
@@ -0,0 +1,9 @@
1
+ # -*- coding: utf-8 -*-
2
+
3
+ module SimpleCataloger
4
+ class SimpleCatalogerError < StandardError
5
+ def initialize(msg)
6
+ super
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,21 @@
1
+ # -*- coding: utf-8 -*-
2
+ module SimpleCataloger
3
+
4
+ class Category
5
+ include DataMapper::Resource
6
+
7
+ property :id, Serial
8
+ property :name, String
9
+
10
+ has n, :tags, :order => [:name]
11
+
12
+ def self.order_by_name
13
+ all(:order => [:name])
14
+ end
15
+
16
+ def items
17
+ tags.collect { |t| t.items }.flatten
18
+ end
19
+ end
20
+
21
+ end
@@ -0,0 +1,30 @@
1
+ # -*- coding: utf-8 -*-
2
+ module SimpleCataloger
3
+
4
+ class Image
5
+ include DataMapper::Resource
6
+
7
+ property :id, Serial
8
+ property :path, String
9
+ property :path_from_catalog_root, String
10
+
11
+ belongs_to :item, :required => false
12
+ belongs_to :tag, :required => false
13
+
14
+ def to_json(*a)
15
+ {:item => if item then
16
+ item.name
17
+ else
18
+ nil
19
+ end,
20
+ :tag => if tag then
21
+ tag.name
22
+ else
23
+ nil
24
+ end,
25
+ :src => path_from_catalog_root}.to_json(*a)
26
+ end
27
+
28
+ end
29
+
30
+ end
@@ -0,0 +1,24 @@
1
+ # -*- coding: utf-8 -*-
2
+ module SimpleCataloger
3
+
4
+ class Item
5
+ include DataMapper::Resource
6
+ property :id, Serial
7
+ property :name, String
8
+
9
+ property :added_at, Date
10
+ property :path, String
11
+ property :path_from_catalog_root, String
12
+ property :rating, Integer
13
+ property :flagged_at, Date
14
+
15
+ has n, :taggings
16
+ has n, :tags, :through => :taggings
17
+ has n, :images
18
+
19
+ def to_s
20
+ @name
21
+ end
22
+ end
23
+
24
+ end
@@ -0,0 +1,62 @@
1
+ # -*- coding: utf-8 -*-
2
+ module SimpleCataloger
3
+
4
+ class Tag
5
+ include DataMapper::Resource
6
+
7
+ property :id, Serial
8
+ property :name, String
9
+
10
+ belongs_to :category, 'Category', :child_key => [:category_id]
11
+ has n, :taggings
12
+ has n, :items, :through => :taggings, :order => [:name]
13
+ has n, :images
14
+
15
+ ##################################################
16
+ #
17
+
18
+ RE_TAG = /\[([^\]]+)\]/
19
+
20
+ # extracts tags from directory or file name
21
+ def self.extract_tags(dir_or_file_name)
22
+ tags = []
23
+ match_data = dir_or_file_name.match(RE_TAG)
24
+ while match_data
25
+ tags << match_data[1]
26
+ match_data = dir_or_file_name.match(RE_TAG, match_data.end(0))
27
+ end
28
+ tags
29
+ end
30
+
31
+ def self.extract_name(dir_or_file_name)
32
+ dir_or_file_name.gsub(Tag::RE_TAG, '').strip
33
+ end
34
+
35
+ RE_RATING = /^\d$/
36
+ RE_YEAR = /^\d\d\d\d$/
37
+
38
+ MATCHES = [
39
+ [RE_RATING, "rating", proc { |tag_name| tag_name.to_i }],
40
+ [RE_YEAR, "year", proc { |tag_name| tag_name.to_i }]
41
+ ]
42
+
43
+ #
44
+ # decode standard tags
45
+ #
46
+ def self.match_category(tag_name)
47
+ MATCHES.each do |re, category_name, convert|
48
+ if re.match tag_name
49
+ category = Category.first_or_create(:name => category_name)
50
+ tag = Tag.first_or_create(
51
+ :name => convert.call(tag_name),
52
+ :category => category
53
+ )
54
+ return tag
55
+ end
56
+ end
57
+ nil
58
+ end
59
+
60
+ end # class Tag
61
+
62
+ end # module SimpleCatalog