file_indexer 1.0.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.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: b0df350bb38bc48552894171e4ed305c67f152cf4bcac83f46af44911ff659f0
4
+ data.tar.gz: 59d9c6aa03fc9fe4e6bc4311c6a5295384bff30410cfe17e4b847c7d13618f02
5
+ SHA512:
6
+ metadata.gz: a088e4f5918659b8f250ea09137a2b781b2b0978701b39783835c5afce08ea70586ab81f45af4e787e554c4b217e2ba26e5930d4a7db2b9e8aea62b0b186ac81
7
+ data.tar.gz: e806cec507fb7a9b8b8687e44671a03ae67c51e09c2398e9ce79de2b93ccc88096307a7d3f40556fe2eef3712c466c434f719c2e6c65501b4c1531010078031d
data/README.md ADDED
@@ -0,0 +1,34 @@
1
+ # File Indexer
2
+
3
+ A simple file indexer that allows you to keep track of all the files in a
4
+ directory while also extracting information about those files.
5
+
6
+ # What it can do:
7
+ - Keep an index of files in a directory
8
+ - Associate an object with each file, storing e.g. id3-tag data, md5 digests,
9
+ or anything else that is too costly to recompute over and over.
10
+ - Keep the index synchronized to the file system by using the listen gem.
11
+ - Use a persistent database file to avoid having to rescan the entire database
12
+ every time the program loads.
13
+
14
+ # Usage:
15
+
16
+ ```ruby
17
+ fi = FileIndexer.new(%w[~/music/], %w[mp3 wav], 'db') do |file|
18
+
19
+ # Use a mp3 tag reading library such as taglib-ruby
20
+
21
+ { artist: file.artist,
22
+ title: file.track,
23
+ ...
24
+ }
25
+ end
26
+
27
+ fi.watch
28
+ ```
29
+ # Documentation:
30
+
31
+ To generate documentation with e.g. yard, simply
32
+
33
+ cd file_indexer
34
+ yard server
@@ -0,0 +1,104 @@
1
+ # frozen_string_literal: true
2
+ require 'listen'
3
+ require 'observer'
4
+
5
+ module FileIndexer
6
+
7
+ # A simple file indexer, keeps a database of files and associated metadata
8
+ class Indexer
9
+ include Observable
10
+
11
+ # The directories that will be indexed
12
+ attr_accessor :dirs
13
+ # Only files with these extensions will be considered. Just use '*' for all
14
+ # files.
15
+ attr_accessor :exts
16
+ # This is a Proc that will be called for every file and its result will be
17
+ # stored in the database
18
+ attr_accessor :action
19
+ # This is the actual database of files, it is a Hash
20
+ attr_accessor :files
21
+ # This is the filename which will be used as the persistent database
22
+ attr_reader :db_file
23
+
24
+ def initialize(dirs, exts, db_file, &action)
25
+ @dirs = dirs
26
+ @exts = exts
27
+ @db_file = db_file
28
+ @action = action
29
+
30
+ @files = {}
31
+ @listener = nil
32
+
33
+ @files.merge!(read_db)
34
+ index_all!
35
+ end
36
+
37
+ # Index multiple directories
38
+ def index_all(dirs = @dirs, exts = @exts)
39
+ dirs.map { |dir| index(glob(dir, exts)) }.reduce(&:merge)
40
+ end
41
+
42
+ # @see #index_all
43
+ def index_all!(dirs = @dirs, exts = @exts)
44
+ @files = index_all(dirs, exts)
45
+ changed!
46
+ files
47
+ end
48
+
49
+ # Index a list of files
50
+ def index(files)
51
+ files.map do |file|
52
+ [file, action.(file)]
53
+ end.to_h
54
+ end
55
+
56
+ # @see #index
57
+ def index!(nf)
58
+ files.merge!(index(nf))
59
+ changed!
60
+ files
61
+ end
62
+
63
+ # Watch directories for changes using the listen gem, when called make sure
64
+ # to call #stop when done watching.
65
+ def watch(dirs = @dirs, exts = @exts)
66
+ @listener =
67
+ Listen.to(*dirs, only: /\.(#{exts.join('|')})$/) do |mod, add, del|
68
+ del.each { |f| files.delete(f) }
69
+ index!(mod + add)
70
+ end
71
+ @listener.start
72
+ end
73
+
74
+ # Stop watching directories for changes
75
+ def stop
76
+ @listener&.stop
77
+ end
78
+
79
+ # Write the databse to disk
80
+ def write_db
81
+ File.open(db_file, 'w') { |f| f.puts(Marshal.dump(files)) }
82
+ end
83
+
84
+ # Read the database from disk
85
+ def read_db
86
+ return {} unless File.exist?(db_file)
87
+ File.open(db_file, 'r') { |f| Marshal.load(f.read) }
88
+ end
89
+
90
+ private
91
+
92
+ # Glob all files with certain extensions in a directory
93
+ def glob(dir, exts = @exts)
94
+ Dir.glob("#{dir}**/*.{#{exts.join(',')}}")
95
+ end
96
+
97
+ # Notify observers of changes and rewrite the database file
98
+ def changed!(*args)
99
+ changed
100
+ write_db
101
+ notify_observers(*args)
102
+ end
103
+ end
104
+ end
@@ -0,0 +1,11 @@
1
+ require_relative 'file_indexer/indexer'
2
+
3
+ # Namespace for a simple file indexer
4
+ module FileIndexer
5
+ VERSION = [1, 0, 0]
6
+
7
+ # Equivalent to FileIndexer::Indexer.new(dirs, exts, db_file, &action)
8
+ def self.new(dirs, exts, db_file, &action)
9
+ Indexer.new(dirs, exts, db_file, &action)
10
+ end
11
+ end
@@ -0,0 +1,63 @@
1
+ require 'test/unit'
2
+ require_relative '../lib/file_indexer'
3
+
4
+ DIRS = [File.expand_path(File.join(__dir__, 'foo/')) + '/',
5
+ File.expand_path(File.join(__dir__, 'bar/')) + '/']
6
+ FILES = [File.join(DIRS[0], 'a.foo'),
7
+ File.join(DIRS[0], 'b.bar'),
8
+ File.join(DIRS[0], 'c.kek'),
9
+ File.join(DIRS[1], 'a.foo'),
10
+ File.join(DIRS[1], 'b.foo')]
11
+ EXTS = %w[foo bar]
12
+ DB_FILE = File.join(__dir__, 'db_file')
13
+ TMP_FILE = File.join(DIRS[0], 'd.foo')
14
+
15
+ class TestIndexer < Test::Unit::TestCase
16
+ def setup
17
+ DIRS.each { |d| FileUtils.mkdir_p(d) }
18
+ FILES.each { |f| FileUtils.touch(f) }
19
+ @indexer = FileIndexer.new(DIRS, EXTS, DB_FILE) do |f|
20
+ File.basename(f).upcase
21
+ end
22
+ end
23
+
24
+ def teardown
25
+ @indexer.stop
26
+ FileUtils.rm(DB_FILE) if File.exist?(DB_FILE)
27
+ FileUtils.rm(TMP_FILE) if File.exist?(TMP_FILE)
28
+ DIRS.each { |d| FileUtils.rm_rf(d) }
29
+ end
30
+
31
+ def test_glob
32
+ count = @indexer.dirs.map { |d| @indexer.send(:glob, d).length }.reduce(:+)
33
+ assert(4 == count, "Glob counts #{count} files, not 4")
34
+ end
35
+
36
+ def test_index_all
37
+ values = @indexer.index_all.values
38
+ assert(
39
+ ['A.FOO', 'B.BAR', 'B.FOO', 'A.FOO'] == values,
40
+ "Values are #{values}"
41
+ )
42
+ end
43
+
44
+ def test_index
45
+ assert(['A.FOO'] == @indexer.index([File.join(DIRS[0], 'a.foo')]).values)
46
+ end
47
+
48
+ def test_watch
49
+ @indexer.watch
50
+ sleep 0.1
51
+ FileUtils.touch(TMP_FILE)
52
+ sleep 1
53
+ assert(5 == @indexer.files.values.length)
54
+ end
55
+
56
+ def test_write_db
57
+ assert(true == File.exist?(DB_FILE))
58
+ end
59
+
60
+ def test_read_db
61
+ assert(4 == @indexer.read_db.values.length)
62
+ end
63
+ end
metadata ADDED
@@ -0,0 +1,67 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: file_indexer
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0
5
+ platform: ruby
6
+ authors:
7
+ - Stone Tickle
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2018-05-02 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: listen
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '3.1'
20
+ - - ">="
21
+ - !ruby/object:Gem::Version
22
+ version: 3.1.5
23
+ type: :runtime
24
+ prerelease: false
25
+ version_requirements: !ruby/object:Gem::Requirement
26
+ requirements:
27
+ - - "~>"
28
+ - !ruby/object:Gem::Version
29
+ version: '3.1'
30
+ - - ">="
31
+ - !ruby/object:Gem::Version
32
+ version: 3.1.5
33
+ description: Keep track of files in a directory
34
+ email: lattis@mochiro.moe
35
+ executables: []
36
+ extensions: []
37
+ extra_rdoc_files: []
38
+ files:
39
+ - "./README.md"
40
+ - "./lib/file_indexer.rb"
41
+ - "./lib/file_indexer/indexer.rb"
42
+ - "./test/test_indexer.rb"
43
+ homepage:
44
+ licenses:
45
+ - MIT
46
+ metadata: {}
47
+ post_install_message:
48
+ rdoc_options: []
49
+ require_paths:
50
+ - lib
51
+ required_ruby_version: !ruby/object:Gem::Requirement
52
+ requirements:
53
+ - - ">="
54
+ - !ruby/object:Gem::Version
55
+ version: '0'
56
+ required_rubygems_version: !ruby/object:Gem::Requirement
57
+ requirements:
58
+ - - ">="
59
+ - !ruby/object:Gem::Version
60
+ version: '0'
61
+ requirements: []
62
+ rubyforge_project:
63
+ rubygems_version: 2.7.6
64
+ signing_key:
65
+ specification_version: 4
66
+ summary: Simple file indexer
67
+ test_files: []