xxx_rename 0.0.1

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 (97) hide show
  1. checksums.yaml +7 -0
  2. data/.github/workflows/codeql-analysis.yml +42 -0
  3. data/.github/workflows/ruby.yml +44 -0
  4. data/.gitignore +12 -0
  5. data/.rspec +3 -0
  6. data/.rubocop.yml +41 -0
  7. data/.ruby-version +1 -0
  8. data/Gemfile +6 -0
  9. data/Gemfile.lock +174 -0
  10. data/README.md +319 -0
  11. data/Rakefile +12 -0
  12. data/bin/console +23 -0
  13. data/bin/install +22 -0
  14. data/bin/setup +8 -0
  15. data/codecov.yml +2 -0
  16. data/docs/DEVELOPMENT.md +42 -0
  17. data/exe/xxx_rename +12 -0
  18. data/lib/xxx_rename/actions/base_action.rb +20 -0
  19. data/lib/xxx_rename/actions/log_new_filename.rb +40 -0
  20. data/lib/xxx_rename/actions/resolver.rb +32 -0
  21. data/lib/xxx_rename/actions/stash_app_post_movie.rb +62 -0
  22. data/lib/xxx_rename/actors_helper.rb +117 -0
  23. data/lib/xxx_rename/cli.rb +211 -0
  24. data/lib/xxx_rename/client.rb +110 -0
  25. data/lib/xxx_rename/constants.rb +96 -0
  26. data/lib/xxx_rename/contract/config_contract.rb +241 -0
  27. data/lib/xxx_rename/contract/config_generator.rb +207 -0
  28. data/lib/xxx_rename/contract/file_rename_op_contract.rb +54 -0
  29. data/lib/xxx_rename/contract/types.rb +10 -0
  30. data/lib/xxx_rename/core_extensions/string.rb +39 -0
  31. data/lib/xxx_rename/data/base.rb +34 -0
  32. data/lib/xxx_rename/data/config.rb +97 -0
  33. data/lib/xxx_rename/data/file_rename_op.rb +42 -0
  34. data/lib/xxx_rename/data/file_rename_op_datastore.rb +111 -0
  35. data/lib/xxx_rename/data/naughty_america_database.rb +22 -0
  36. data/lib/xxx_rename/data/query_interface.rb +78 -0
  37. data/lib/xxx_rename/data/scene_data.rb +71 -0
  38. data/lib/xxx_rename/data/scene_datastore.rb +401 -0
  39. data/lib/xxx_rename/data/site_config.rb +84 -0
  40. data/lib/xxx_rename/data/types.rb +13 -0
  41. data/lib/xxx_rename/errors.rb +28 -0
  42. data/lib/xxx_rename/file_scanner.rb +49 -0
  43. data/lib/xxx_rename/file_utilities.rb +38 -0
  44. data/lib/xxx_rename/filename_generator.rb +173 -0
  45. data/lib/xxx_rename/integrations/base.rb +20 -0
  46. data/lib/xxx_rename/integrations/stash_app.rb +316 -0
  47. data/lib/xxx_rename/log.rb +26 -0
  48. data/lib/xxx_rename/migration_client.rb +139 -0
  49. data/lib/xxx_rename/processed_file.rb +203 -0
  50. data/lib/xxx_rename/search.rb +166 -0
  51. data/lib/xxx_rename/site_client_matcher.rb +299 -0
  52. data/lib/xxx_rename/site_clients/adult_time.rb +31 -0
  53. data/lib/xxx_rename/site_clients/algolia_common.rb +48 -0
  54. data/lib/xxx_rename/site_clients/algolia_v2.rb +181 -0
  55. data/lib/xxx_rename/site_clients/babes.rb +15 -0
  56. data/lib/xxx_rename/site_clients/base.rb +61 -0
  57. data/lib/xxx_rename/site_clients/blacked.rb +12 -0
  58. data/lib/xxx_rename/site_clients/blacked_raw.rb +12 -0
  59. data/lib/xxx_rename/site_clients/brazzers.rb +15 -0
  60. data/lib/xxx_rename/site_clients/configuration.rb +55 -0
  61. data/lib/xxx_rename/site_clients/digital_playground.rb +15 -0
  62. data/lib/xxx_rename/site_clients/elegant_angel.rb +168 -0
  63. data/lib/xxx_rename/site_clients/errors.rb +103 -0
  64. data/lib/xxx_rename/site_clients/evil_angel.rb +59 -0
  65. data/lib/xxx_rename/site_clients/goodporn.rb +109 -0
  66. data/lib/xxx_rename/site_clients/jules_jordan.rb +22 -0
  67. data/lib/xxx_rename/site_clients/jules_jordan_media.rb +175 -0
  68. data/lib/xxx_rename/site_clients/manuel_ferrara.rb +24 -0
  69. data/lib/xxx_rename/site_clients/mg_premium.rb +247 -0
  70. data/lib/xxx_rename/site_clients/mofos.rb +15 -0
  71. data/lib/xxx_rename/site_clients/naughty_america.rb +272 -0
  72. data/lib/xxx_rename/site_clients/nfbusty.rb +84 -0
  73. data/lib/xxx_rename/site_clients/query_generator/base.rb +89 -0
  74. data/lib/xxx_rename/site_clients/query_generator/evil_angel.rb +36 -0
  75. data/lib/xxx_rename/site_clients/query_generator/goodporn.rb +27 -0
  76. data/lib/xxx_rename/site_clients/query_generator/mg_premium.rb +26 -0
  77. data/lib/xxx_rename/site_clients/query_generator/naughty_america.rb +24 -0
  78. data/lib/xxx_rename/site_clients/query_generator/stash_db.rb +21 -0
  79. data/lib/xxx_rename/site_clients/query_generator/vixen.rb +27 -0
  80. data/lib/xxx_rename/site_clients/query_generator/whale.rb +39 -0
  81. data/lib/xxx_rename/site_clients/reality_kings.rb +14 -0
  82. data/lib/xxx_rename/site_clients/stash_db.rb +257 -0
  83. data/lib/xxx_rename/site_clients/tushy.rb +12 -0
  84. data/lib/xxx_rename/site_clients/tushy_raw.rb +12 -0
  85. data/lib/xxx_rename/site_clients/twistys.rb +15 -0
  86. data/lib/xxx_rename/site_clients/vixen.rb +12 -0
  87. data/lib/xxx_rename/site_clients/vixen_media.rb +130 -0
  88. data/lib/xxx_rename/site_clients/whale.rb +106 -0
  89. data/lib/xxx_rename/site_clients/wicked.rb +52 -0
  90. data/lib/xxx_rename/site_clients/x_empire.rb +51 -0
  91. data/lib/xxx_rename/site_clients/zero_tolerance.rb +36 -0
  92. data/lib/xxx_rename/utils.rb +81 -0
  93. data/lib/xxx_rename/version.rb +5 -0
  94. data/lib/xxx_rename.rb +60 -0
  95. data/output.png +0 -0
  96. data/xxx_rename.gemspec +42 -0
  97. metadata +411 -0
@@ -0,0 +1,401 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "pstore"
4
+ require "benchmark"
5
+ require "pathname"
6
+
7
+ require "xxx_rename/data/query_interface"
8
+
9
+ module XxxRename
10
+ module Data
11
+ DEFAULT_STORE_FILE = "xxx_rename_datastore.store"
12
+
13
+ METADATA_ROOT = "_m_"
14
+ REGISTERED_FILE_PATHS_PREFIX = "_sp_"
15
+
16
+ RecordStatus = Struct.new(:key, :scene_saved, :missing_keys, :conflicting_indexes, :expected_filename_key, keyword_init: true) do
17
+ def valid?
18
+ scene_saved && missing_keys.empty? && conflicting_indexes.empty?
19
+ end
20
+
21
+ def errors
22
+ self
23
+ end
24
+ end
25
+
26
+ class SceneDatastore
27
+ attr_reader :store
28
+
29
+ def initialize(dir, name = DEFAULT_STORE_FILE)
30
+ path = File.join(dir, name)
31
+ XxxRename.logger.info "#{"[DATASTORE INIT]".colorize(:green)} #{path} #{name}"
32
+
33
+ @store = PStore.new path
34
+ end
35
+ end
36
+
37
+ class SceneDatastoreQuery < QueryInterface
38
+ include FileUtilities
39
+
40
+ # @param [XxxRename::Data::SceneData] scene_data
41
+ # @raise [UniqueRecordViolation] is key already exists in the DB
42
+ def create!(scene_data, force: false)
43
+ benchmark("create!") do
44
+ semaphore.synchronize do
45
+ store.transaction do
46
+ unless force
47
+ existing_record = store.fetch(scene_data.key, nil)
48
+ raise UniqueRecordViolation, existing_record if existing_record
49
+ end
50
+
51
+ store[scene_data.key] = scene_data
52
+ create_indexes(scene_data.key, scene_data)
53
+ scene_data.key
54
+ end
55
+ end
56
+ end
57
+ end
58
+
59
+ # Find a scene using one of
60
+ # 1. collection_tag && id
61
+ # 2. collection_tag && title
62
+ # 3. title && actors
63
+ #
64
+ # @param [String] title
65
+ # @param [Array[String]] actors
66
+ # @param [String] collection_tag
67
+ # @param [String] id
68
+ def find(id: nil, collection_tag: nil, title: nil, actors: nil)
69
+ param = { id: id, collection_tag: collection_tag, title: title, actors: actors }.reject { |_k, v| v.nil? }
70
+ benchmark("find #{param}") do
71
+ validate_type_params!(id: id, collection_tag: collection_tag, title: title, actors: actors)
72
+ semaphore.synchronize do
73
+ store.transaction(read_only: true) do
74
+ keys = fetch_keys?(id: id, collection_tag: collection_tag, title: title, actors: actors)
75
+ keys.map do |key|
76
+ store[key]
77
+ end.compact
78
+ end
79
+ end
80
+ end
81
+ end
82
+
83
+ def find_by_abs_path?(path)
84
+ benchmark("find_by_abs_path?") do
85
+ store.transaction(read_only: true) do
86
+ index_key = generate_lookup_key(REGISTERED_FILE_PATHS_PREFIX, path)
87
+ key = store.fetch(index_key, nil)
88
+ return if key.nil?
89
+
90
+ store[key]
91
+ end
92
+ end
93
+ end
94
+
95
+ def find_by_key?(key)
96
+ benchmark("find_by_key? #{key}") do
97
+ semaphore.synchronize do
98
+ store.transaction(read_only: true) do
99
+ store.fetch(key, nil)
100
+ end
101
+ end
102
+ end
103
+ end
104
+
105
+ # @param [XxxRename::Data::SceneData] scene_data
106
+ # @param [String] filename
107
+ def register_file(scene_data, filename, old_filename: nil)
108
+ validate_file_paths!(filename, old_filename: old_filename)
109
+ benchmark("register_file") do
110
+ semaphore.synchronize do
111
+ store.transaction do
112
+ key = scene_data.key
113
+ new_index_key = generate_lookup_key(REGISTERED_FILE_PATHS_PREFIX, filename)
114
+ store[new_index_key] = key
115
+
116
+ if old_filename
117
+ old_index_key = generate_lookup_key(REGISTERED_FILE_PATHS_PREFIX, old_filename)
118
+ store.delete(old_index_key)
119
+ end
120
+
121
+ new_index_key
122
+ end
123
+ end
124
+ end
125
+ end
126
+
127
+ def exists?(key)
128
+ semaphore.synchronize do
129
+ store.transaction(true) do
130
+ store.root?(key)
131
+ end
132
+ end
133
+ end
134
+
135
+ alias exist? exists?
136
+
137
+ def destroy(scene_data, *filenames)
138
+ benchmark("destroy") do
139
+ semaphore.synchronize do
140
+ store.transaction do
141
+ key = scene_data.key
142
+ store.delete(key)
143
+ destroy_indexes(key, scene_data, *filenames)
144
+ key
145
+ end
146
+ end
147
+ end
148
+ end
149
+
150
+ def count
151
+ benchmark("count") do
152
+ semaphore.synchronize do
153
+ store.transaction(true) do
154
+ md5_regex = Regexp.new("^[a-f0-9]{32}$", Regexp::IGNORECASE)
155
+
156
+ store.roots.select { |x| x.match?(md5_regex) }.length
157
+ end
158
+ end
159
+ end
160
+ end
161
+
162
+ def all
163
+ benchmark("all") do
164
+ semaphore.synchronize do
165
+ store.transaction(true) do
166
+ md5_regex = Regexp.new("^[a-f0-9]{32}$", Regexp::IGNORECASE)
167
+
168
+ store.roots.select { |x| x.match?(md5_regex) }.map { |key| store[key] }
169
+ end
170
+ end
171
+ end
172
+ end
173
+
174
+ def metadata
175
+ semaphore.synchronize do
176
+ store.transaction(true) do
177
+ store.fetch(METADATA_ROOT, {})
178
+ end
179
+ end
180
+ end
181
+
182
+ def update_metadata(hash)
183
+ semaphore.synchronize do
184
+ store.transaction do
185
+ store[METADATA_ROOT] ||= {}
186
+ store[METADATA_ROOT] = store[METADATA_ROOT].merge(hash)
187
+ end
188
+ end
189
+ end
190
+
191
+ #
192
+ # Internal method for testing. Is of no use for a user
193
+ #
194
+ # rubocop:disable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
195
+ def valid?(scene_data, filepath: nil)
196
+ validate_file_paths!(filepath, old_filename: nil)
197
+
198
+ errors = {
199
+ key: scene_data.key,
200
+ scene_saved: true,
201
+ missing_keys: [],
202
+ conflicting_indexes: {},
203
+ expected_filename_key: nil
204
+ }
205
+
206
+ semaphore.synchronize do
207
+ store.transaction(true) do
208
+ key = scene_data.key
209
+ scene = store[key]
210
+ errors[:scene_saved] = false if scene.nil?
211
+
212
+ if scene_data.id
213
+ id_index_value = store[generate_lookup_key(scene_data.collection_tag, scene_data.id)]
214
+ errors[:missing_keys] << :id_index if id_index_value.nil?
215
+ errors[:conflicting_indexes][:id_index] = id_index_value if !id_index_value.nil? && id_index_value != key
216
+ end
217
+
218
+ # title_index_value = store[generate_lookup_key(scene_data.collection_tag, scene_data.title)]
219
+ # errors[:missing_keys] << :title_index if title_index_value.nil?
220
+ # errors[:conflicting_indexes][:title_index] = title_index_value if !title_index_value.nil? && title_index_value != key
221
+
222
+ collection_title_index_value = store[generate_lookup_key(scene_data.collection, scene_data.title)]
223
+ errors[:missing_keys] << :collection_title_index if collection_title_index_value.nil?
224
+ if !collection_title_index_value.nil? && collection_title_index_value != key
225
+ errors[:conflicting_indexes][:collection_title_index] = collection_title_index_value
226
+ end
227
+
228
+ # title_actor_index_value = store[generate_lookup_key(scene_data.title, scene_data.actors.sort.join("|"))]
229
+ # errors[:missing_keys] << :title_actors_index if title_actor_index_value.nil? || title_actor_index_value.empty?
230
+ # if title_actor_index_value && !title_actor_index_value.empty? && !title_actor_index_value.include?(key)
231
+ # errors[:conflicting_indexes][:title_actors_index] = title_actor_index_value
232
+ # end
233
+
234
+ if filepath
235
+ filename_value = store[generate_lookup_key(REGISTERED_FILE_PATHS_PREFIX, filepath)]
236
+ unless filename_value
237
+ errors[:missing_keys] << :path
238
+ errors[:expected_filename_key] = sanitize(filepath)
239
+ end
240
+ end
241
+
242
+ status = RecordStatus.new(**errors)
243
+ status.valid? ? true : status.errors
244
+ end
245
+ end
246
+ end
247
+ # rubocop:enable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
248
+
249
+ private
250
+
251
+ def validate_file_paths!(filename, old_filename: nil)
252
+ raise "non absolute path" unless Pathname.new(filename).absolute?
253
+
254
+ raise "non absolute path" if old_filename && !Pathname.new(old_filename).absolute?
255
+
256
+ raise "file not exist #{filename}" unless valid_file?(filename)
257
+
258
+ true
259
+ end
260
+
261
+ def validate_type_params!(id:, collection_tag:, title:, actors:)
262
+ raise ArgumentError, "no key provided for lookup" if [id, collection_tag, title, actors].none?
263
+
264
+ raise TypeError, "actors: wrong argument type #{actors.class} (expected Array)" if actors && !(actors.is_a? Array)
265
+ end
266
+
267
+ #
268
+ # This method id thread unsafe!
269
+ # Always call from a synchronized mutex and within a transaction
270
+ #
271
+ # @param [String] id
272
+ # @param [Array[String]] collection_tag
273
+ # @param [String] title
274
+ # @param [String] actors
275
+ # @return [Array[String]]
276
+ def fetch_keys?(id:, collection_tag:, title:, actors:)
277
+ if collection_tag && id
278
+ [store[generate_lookup_key(collection_tag, id)]]
279
+ elsif collection_tag && title
280
+ [store[generate_lookup_key(collection_tag, title)]]
281
+ elsif title && actors
282
+ store.fetch(generate_lookup_key(title, actors.sort.join("|")), []).to_a
283
+ end
284
+ end
285
+
286
+ #
287
+ # Create indexes for a scene for faster lookups.
288
+ # Comes at a cost of increased file size
289
+ # Supported indexes:
290
+ # 1. collection_tag -> id -> key // id index
291
+ # ~~2. collection_tag -> title -> key // title index~~
292
+ # 3. collection -> title -> key // title index
293
+ # ~~4. title + actors -> [keys] // title,actors index~~
294
+ # ** collection_tag -> title & title + actors are not created
295
+ # for performance reasons. They can be supported later if
296
+ # the need arises
297
+ # @param [String] key
298
+ # @param [XxxRename::Data::SceneData] scene_data
299
+ def create_indexes(key, scene_data)
300
+ create_id_index(key, scene_data)
301
+ # create_title_index(key, scene_data)
302
+ create_collection_title_index(key, scene_data)
303
+ # create_title_actors_index(key, scene_data)
304
+ end
305
+
306
+ # collection_tag -> id -> key // id index
307
+ # @param [String] key
308
+ # @param [XxxRename::Data::SceneData] scene_data
309
+ def create_id_index(key, scene_data)
310
+ return unless scene_data.id
311
+
312
+ index_key = generate_lookup_key(scene_data.collection_tag, scene_data.id)
313
+ store[index_key] = key
314
+ end
315
+
316
+ # # collection_tag -> title -> key // title index
317
+ # # @param [String] key
318
+ # # @param [XxxRename::Data::SceneData] scene_data
319
+ # def create_title_index(key, scene_data)
320
+ # index_key = generate_lookup_key(scene_data.collection_tag, scene_data.title)
321
+ # store[index_key] = key
322
+ # end
323
+
324
+ def create_collection_title_index(key, scene_data)
325
+ index_key = generate_lookup_key(scene_data.collection, scene_data.title)
326
+ store[index_key] = key
327
+ end
328
+
329
+ # # title + actors -> [keys] // title,actors index
330
+ # # @param [String] key
331
+ # # @param [XxxRename::Data::SceneData] scene_data
332
+ # def create_title_actors_index(key, scene_data)
333
+ # index_key = generate_lookup_key(scene_data.title, scene_data.actors.sort.join("|"))
334
+ #
335
+ # store[index_key] ||= Set.new
336
+ # store[index_key].add(key)
337
+ # end
338
+
339
+ # @param [String] key
340
+ # @param [XxxRename::Data::SceneData] scene_data
341
+ # @param [Array[String]] filenames
342
+ def destroy_indexes(key, scene_data, *filenames)
343
+ destroy_id_index(scene_data)
344
+ # destroy_title_index(scene_data)
345
+ destroy_collection_title_index(key, scene_data)
346
+ # destroy_title_actors_index(key, scene_data)
347
+ destroy_registered_filenames(filenames)
348
+ end
349
+
350
+ def destroy_id_index(scene_data)
351
+ return unless scene_data.id
352
+
353
+ index_key = generate_lookup_key(scene_data.collection_tag, scene_data.id)
354
+ store.delete(index_key)
355
+ end
356
+
357
+ # def destroy_title_index(scene_data)
358
+ # index_key = generate_lookup_key(scene_data.collection_tag, scene_data.title)
359
+ # store.delete(index_key)
360
+ # end
361
+
362
+ # def destroy_title_actors_index(key, scene_data)
363
+ # index_key = generate_lookup_key(scene_data.title, scene_data.actors.sort.join("|"))
364
+ #
365
+ # index_value = store[index_key]
366
+ # if index_value && index_value.length > 1
367
+ # index_value.delete(key)
368
+ # else
369
+ # store.delete(index_key)
370
+ # end
371
+ # end
372
+
373
+ def destroy_collection_title_index(_key, scene_data)
374
+ index_key = generate_lookup_key(scene_data.collection, scene_data.title)
375
+ store.delete(index_key)
376
+ end
377
+
378
+ def destroy_registered_filenames(filenames)
379
+ filenames.map do |file|
380
+ index_key = generate_lookup_key(REGISTERED_FILE_PATHS_PREFIX, file)
381
+ store.delete(index_key)
382
+ end
383
+ end
384
+
385
+ def title_actors_index_key(title, actors)
386
+ "<#{sanitize(title)}" \
387
+ "$#{sanitize(actors.sort.join("|"))}" \
388
+ ">"
389
+ end
390
+
391
+ def benchmark(opr = "unnamed")
392
+ raise "#benchmark called without block" unless block_given?
393
+
394
+ resp = nil
395
+ time = Benchmark.measure { resp = yield }
396
+ XxxRename.logger.debug "#{"[BENCHMARK]".colorize(:cyan)} #{self.class.name}##{opr}: #{time.real.round(3)}s"
397
+ resp
398
+ end
399
+ end
400
+ end
401
+ end
@@ -0,0 +1,84 @@
1
+ # frozen_string_literal: true
2
+
3
+ module XxxRename
4
+ module Data
5
+ class SimpleSiteConfig < Base
6
+ attribute :output_format, Types::Array.of(Types::String)
7
+ attribute :file_source_format, Types::Array.of(Types::String)
8
+ attribute :collection_tag, Types::String
9
+ end
10
+
11
+ class CredentialsConfig < Base
12
+ attribute? :username, Types::String.optional
13
+ attribute? :password, Types::String.optional
14
+ attribute? :cookie_file, Types::String.optional
15
+ attribute? :api_token, Types::String.optional
16
+ end
17
+
18
+ class DatabaseConfig < Base
19
+ attribute? :database, Types::String.optional
20
+ end
21
+
22
+ class AdultTimeConfig < Base
23
+ attributes_from Data::SimpleSiteConfig
24
+ end
25
+
26
+ class EvilAngelConfig < Base
27
+ attributes_from Data::SimpleSiteConfig
28
+ end
29
+
30
+ class ElegantAngelConfig < Base
31
+ attributes_from Data::SimpleSiteConfig
32
+ attributes_from Data::DatabaseConfig
33
+ end
34
+
35
+ class GoodpornConfig < Base
36
+ attributes_from Data::SimpleSiteConfig
37
+ end
38
+
39
+ class JulesJordanMediaConfig < Base
40
+ attributes_from Data::SimpleSiteConfig
41
+ attributes_from Data::CredentialsConfig
42
+ attributes_from Data::DatabaseConfig
43
+ end
44
+
45
+ class MgPremiumConfig < Base
46
+ attributes_from Data::SimpleSiteConfig
47
+ end
48
+
49
+ class NaughtyAmericaConfig < Base
50
+ attributes_from Data::SimpleSiteConfig
51
+ attributes_from Data::DatabaseConfig
52
+ end
53
+
54
+ class NfBustyConfig < Base
55
+ attributes_from Data::SimpleSiteConfig
56
+ attributes_from Data::DatabaseConfig
57
+ end
58
+
59
+ class StashDBConfig < Base
60
+ attributes_from Data::SimpleSiteConfig
61
+ attributes_from Data::CredentialsConfig
62
+ end
63
+
64
+ class VixenMediaConfig < Base
65
+ attributes_from Data::SimpleSiteConfig
66
+ end
67
+
68
+ class WhaleMediaConfig < Base
69
+ attributes_from Data::SimpleSiteConfig
70
+ end
71
+
72
+ class WickedConfig < Base
73
+ attributes_from Data::SimpleSiteConfig
74
+ end
75
+
76
+ class XEmpireConfig < Base
77
+ attributes_from Data::SimpleSiteConfig
78
+ end
79
+
80
+ class ZeroToleranceConfig < Base
81
+ attributes_from Data::SimpleSiteConfig
82
+ end
83
+ end
84
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "set"
4
+
5
+ module XxxRename
6
+ module Data
7
+ module Types
8
+ include Dry.Types()
9
+
10
+ Set = Types.Constructor(Set, Set.method(:new))
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ module XxxRename
4
+ module Errors
5
+ class SafeExit < StandardError; end
6
+
7
+ class ConfigValidationError < StandardError
8
+ attr_reader :errors
9
+
10
+ def initialize(errors)
11
+ @errors = errors
12
+ super(message)
13
+ end
14
+
15
+ def message
16
+ messages = []
17
+ errors.messages.each do |key|
18
+ messages << "#{key.path.join(".")}: #{key.text}"
19
+ end
20
+ messages.join(", ")
21
+ end
22
+ end
23
+
24
+ class ParsingError < StandardError; end
25
+ class FatalError < StandardError; end
26
+ class UnprocessedEntity < StandardError; end
27
+ end
28
+ end
@@ -0,0 +1,49 @@
1
+ # frozen_string_literal: true
2
+
3
+ module XxxRename
4
+ class FileScanner
5
+ #
6
+ # Create a new Scanner
7
+ #
8
+ # @param [String] dir Directory to be processed
9
+ # @param [Hash] options
10
+ # @option options [Boolean] :nested Specify if nested directories should be scanned
11
+ # @option options [String] extension Extension of filenames to be selected
12
+ def initialize(dir, **options)
13
+ @nested = options[:nested]
14
+ @extension = options[:extension].nil? ? "*.{mp4,f4v,mkv,avi,wmv,flv}" : options[:extension]
15
+ @dir = dir
16
+ end
17
+
18
+ # @param [Proc] block Block of code to be executed for each file
19
+ def each(&block)
20
+ @block = block
21
+ process_directory(@dir)
22
+ end
23
+
24
+ private
25
+
26
+ #
27
+ # Executes a block of code for all files in a given directory. Will scan files
28
+ # in sub-directories if @nested is passed as true
29
+ #
30
+ # @param [String] dir Directory path to scan
31
+ def process_directory(dir)
32
+ Dir.chdir(dir) do
33
+ # Process files in a given directory
34
+ XxxRename.logger.info "#{"[DIRECTORY SCAN]".colorize(:blue)} #{Dir.pwd}"
35
+
36
+ Dir.glob(@extension).sort.each { |file| @block.call(file) }
37
+
38
+ # Return unless we want to scan the directories inside
39
+ # the directory `dir`
40
+ return unless @nested
41
+
42
+ nested_dir = Dir["*"].select { |o| File.directory?(o) }
43
+ nested_dir.sort.each do |each|
44
+ process_directory(each)
45
+ end
46
+ end
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,38 @@
1
+ # frozen_string_literal: true
2
+
3
+ module XxxRename
4
+ module FileUtilities
5
+ MAX_FILENAME_LEN = 255
6
+
7
+ def valid_file?(file)
8
+ file && File.exist?(file) && File.file?(file)
9
+ end
10
+
11
+ def valid_dir?(dir)
12
+ dir && File.exist?(dir) && File.directory?(dir)
13
+ end
14
+
15
+ def read_file!(file)
16
+ return File.read(file).strip if valid_file?(file)
17
+
18
+ raise Errors::FatalError, "Unable to read file #{file}"
19
+ end
20
+
21
+ def read_yaml(file, default = {}, validate_type = nil)
22
+ read_yaml!(file, validate_type)
23
+ rescue Errors::FatalError
24
+ default
25
+ end
26
+
27
+ def read_yaml!(file, validate_type)
28
+ raise Errors::FatalError, "Unable to read yaml file #{file}" unless file && File.file?(file) && File.exist?(file)
29
+
30
+ yaml = YAML.load_file(file)
31
+ return yaml unless validate_type
32
+
33
+ return yaml if yaml.is_a?(validate_type)
34
+
35
+ raise Errors::FatalError, "#{file}: Invalid YAML contents. Was expecting #{validate_type}, but received #{yaml.class}"
36
+ end
37
+ end
38
+ end