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,173 @@
1
+ # frozen_string_literal: true
2
+
3
+ module XxxRename
4
+ class FilenameGenerationError < StandardError
5
+ attr_reader :extra_tokens
6
+
7
+ def initialize(extra_tokens)
8
+ @extra_tokens = extra_tokens
9
+ super(message)
10
+ end
11
+
12
+ def message
13
+ "Format contains token(s) #{@extra_tokens.join(", ")}, but site client returned no values for these arguments."
14
+ end
15
+ end
16
+
17
+ class InvalidFormatError < StandardError
18
+ def initialize(extra_tokens: [], contains_extension: nil)
19
+ @extra_tokens = extra_tokens
20
+ @contains_extension = contains_extension
21
+ super(make_message)
22
+ end
23
+
24
+ def make_message
25
+ if @extra_tokens.length.positive?
26
+ "invalid token(s) #{@extra_tokens.join(", ")}"
27
+ elsif @contains_extension
28
+ "format should not contain extension but found #{@contains_extension}"
29
+ else
30
+ raise Errors::FatalError, "#{self.class.name} expects extra_tokens or contains_extension but received neither"
31
+ end
32
+ end
33
+ end
34
+
35
+ class FilenameGenerator
36
+ VALID_TOKENS = %w[%female_actors %male_actors %actors %collection %collection_tag %title %id %yyyy_mm_dd %dd %mm %yyyy].freeze
37
+ VALID_PREFIX_TOKENS = %w[%female_actors_prefix %male_actors_prefix %actors_prefix %title_prefix %id_prefix].freeze
38
+
39
+ VALID_INPUT_TOKENS = %w[%title %collection %collection_op %collection_tag_1 %collection_tag_2 %collection_tag_3
40
+ %female_actors %male_actors %male_actors_op %actors
41
+ %id %id_op
42
+ %ignore_1_word %ignore_2_words %ignore_2_words %ignore_3_words %ignore_4_words
43
+ %ignore_5_words %ignore_6_words %ignore_7_words %ignore_all
44
+ %yyyy_mm_dd %dd %mm %yyyy %yy].freeze
45
+
46
+ class << self
47
+ #
48
+ # Helper method for ConfigContract
49
+ # Validates the global.output_format and
50
+ # site.{site}.output_format attributes
51
+ #
52
+ # @param [String] format
53
+ # @return [Boolean]
54
+ # @raise [InvalidFormatError] if string contains invalid tokens
55
+ def validate_format!(format)
56
+ tokens_in_format = format.scan(/%\w+/)
57
+ invalid_tokens = tokens_in_format - VALID_TOKENS - VALID_PREFIX_TOKENS
58
+ return true if invalid_tokens.empty?
59
+
60
+ raise InvalidFormatError.new(extra_tokens: invalid_tokens)
61
+ end
62
+
63
+ #
64
+ # Helper method for ConfigContract
65
+ # Validates the site.{site}.file_source_format attribute
66
+ #
67
+ # @param [String] format
68
+ # @return [Boolean]
69
+ # @raise [InvalidFormatError] if string contains invalid tokens
70
+ def validate_input_format!(format)
71
+ tokens_in_format = format.scan(/%\w+/)
72
+ invalid_tokens = tokens_in_format - VALID_INPUT_TOKENS - VALID_PREFIX_TOKENS
73
+ return true if invalid_tokens.empty?
74
+
75
+ raise InvalidFormatError.new(extra_tokens: invalid_tokens)
76
+ end
77
+
78
+ def generate(scene_data, format, ext_name, prefix_hash)
79
+ new.generate_filename(scene_data, format, ext_name, prefix_hash)
80
+ end
81
+
82
+ def generate_with_multi_formats!(scene_data, ext_name, prefix_hash, *formats)
83
+ extra_tokens = []
84
+ formats.each do |format|
85
+ resp = new.generate_filename(scene_data, format, ext_name, prefix_hash)
86
+ return resp
87
+ rescue FilenameGenerationError => e
88
+ extra_tokens.push(*e.extra_tokens)
89
+ nil
90
+ end
91
+ raise FilenameGenerationError, extra_tokens.uniq
92
+ end
93
+ end
94
+
95
+ # @param [XxxRename::Data::SceneData] scene_data
96
+ # @param [String] format
97
+ # @param [String] ext_name
98
+ # @param [Hash] prefix_hash
99
+ def generate_filename(scene_data, format, ext_name, prefix_hash)
100
+ self.class.validate_format!(format) && validate_extension!(format, ext_name)
101
+
102
+ all_tokens_in_format = format.scan(/%\w+/)
103
+ tokens_in_format = all_tokens_in_format.reject { |x| x.end_with?("_prefix") }
104
+
105
+ format = sub_tokens(scene_data, tokens_in_format, format)
106
+ format = sub_prefix_tokens(scene_data, all_tokens_in_format, format, prefix_hash)
107
+
108
+ validate_after_replacement!(format)
109
+
110
+ "#{format.strip.remove_special_characters}#{ext_name}"
111
+ end
112
+
113
+ private
114
+
115
+ def sub_tokens(scene_data, tokens, format)
116
+ long_name_tokens = %w[%female_actors %male_actors %actors]
117
+
118
+ tokens.each do |token|
119
+ fn = token[1..].to_sym
120
+ value = scene_data.send(fn)
121
+ next if value.nil? || !value.to_s.presence
122
+
123
+ value = "[#{value}]" if fn == :collection_tag && value !~ /\[\w+\]/
124
+ next if long_name_tokens.include?(token)
125
+
126
+ format = format.gsub(/#{token}\b/, value.to_s)
127
+ end
128
+
129
+ format = safe_append_list(format, "%actors", scene_data.actors)
130
+ format = safe_append_list(format, "%female_actors", scene_data.female_actors)
131
+ safe_append_list(format, "%male_actors", scene_data.male_actors)
132
+ end
133
+
134
+ def safe_append_list(str, token, arr, max_len = FileUtilities::MAX_FILENAME_LEN)
135
+ return str if arr.empty? || !str.include?(token)
136
+
137
+ formatted_str = str.gsub(/#{token}\b/, arr.join(", ").to_s)
138
+ return formatted_str if formatted_str.length < max_len
139
+
140
+ safe_append_list(str, token, arr[0...-1], max_len)
141
+ end
142
+
143
+ def sub_prefix_tokens(data, tokens, format, prefix_hash)
144
+ tokens.each do |token|
145
+ next unless token.end_with?("_prefix")
146
+
147
+ value = data.send(token[1..].gsub("_prefix", "").to_sym)
148
+ format = if value.nil? || value.empty?
149
+ format.gsub(/#{token}\b/, "")
150
+ else
151
+ format.gsub(/#{token}\b/, prefix_hash[token[1..].to_sym])
152
+ end
153
+ end
154
+ format
155
+ end
156
+
157
+ def validate_extension!(format, ext_name)
158
+ raise InvalidFormatError.new(contains_extension: match[:ext]) if format.end_with?(ext_name)
159
+
160
+ return unless (match = /.*(?<ext>\w{3,4})$/.match(format)) && Constants::VIDEO_EXTENSIONS.include?(match[:ext])
161
+
162
+ raise InvalidFormatError.new(contains_extension: match[:ext])
163
+ end
164
+
165
+ # @param [String] file
166
+ def validate_after_replacement!(file)
167
+ tokens_in_format = file.scan(/%\w+/)
168
+ return if tokens_in_format.empty?
169
+
170
+ raise FilenameGenerationError, tokens_in_format
171
+ end
172
+ end
173
+ end
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "xxx_rename/utils"
4
+ require "httparty"
5
+
6
+ module XxxRename
7
+ module Integrations
8
+ class Base
9
+ include Utils
10
+ include HTTParty
11
+
12
+ attr_reader :config
13
+
14
+ def initialize(config)
15
+ @config = config
16
+ self.class.logger(XxxRename.logger, :debug)
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,316 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "base64"
4
+
5
+ require "xxx_rename/errors"
6
+ require "xxx_rename/integrations/base"
7
+ require "xxx_rename/file_utilities"
8
+
9
+ module XxxRename
10
+ module Integrations
11
+ class StashAPIError < StandardError
12
+ def initialize(errors)
13
+ @errors = errors
14
+ super(message)
15
+ end
16
+
17
+ def message
18
+ msg = "Stash API returned error:\n"
19
+ @errors.each do |e|
20
+ s = "\tMESSAGE #{e["message"]}\n"
21
+ s += "\tOPERATION #{e["path"]} \n"
22
+ msg += s
23
+ end
24
+ msg
25
+ end
26
+ end
27
+
28
+ class StashApp < Base
29
+ include FileUtilities
30
+
31
+ GRAPHQL_ENDPOINT = "/graphql"
32
+
33
+ def initialize(config)
34
+ super(config)
35
+ raise Errors::FatalError, "Stash App requires 'url'. Check your configuration." unless config.stash_app.url.presence
36
+
37
+ self.class.base_uri(config.stash_app.url)
38
+ self.class.headers("Content-Type" => "application/json")
39
+ end
40
+
41
+ def setup_credentials!
42
+ register_api_key(stash_app_config.api_token) if api_key_provided?
43
+
44
+ validate_credentials!
45
+ end
46
+
47
+ def fetch_studio(name)
48
+ response = handle_response! { self.class.post(GRAPHQL_ENDPOINT, body: find_studio_body(name)) }
49
+ response.dig("data", "findStudios", "studios")&.select { |x| x["name"].normalize == name.normalize }&.first
50
+ end
51
+
52
+ def fetch_movie(name)
53
+ response = handle_response! { self.class.post(GRAPHQL_ENDPOINT, body: fetch_movie_body(name)) }
54
+ response.dig("data", "findMovies", "movies")&.select { |x| x["name"].normalize == name.normalize }&.first
55
+ end
56
+
57
+ def create_movie(scene_data, studio_id, retried = false)
58
+ return if scene_data.movie.nil?
59
+
60
+ response = handle_response! { self.class.post(GRAPHQL_ENDPOINT, body: create_movie_body(scene_data.movie, studio_id)) }
61
+ raise StashAPIError, response["errors"] if response["errors"]
62
+
63
+ response.dig("data", "movieCreate")
64
+ rescue StashAPIError => e
65
+ # re-raise if already retried
66
+ raise e if retried
67
+
68
+ # as of current implementation, movie creation fails if the image url is invalid
69
+ # retry again and don't set the back image. consecutive failures will be raised
70
+ XxxRename.logger.error "[STASH APP MOVIE CREATION RETRY] without back image"
71
+ hash = scene_data.to_h.tap do |h|
72
+ h[:movie] = h[:movie].tap { |m_h| m_h.delete(:back_image) }
73
+ end
74
+ new_scene_data = Data::SceneData.new(hash)
75
+ create_movie(new_scene_data, studio_id, true)
76
+ end
77
+
78
+ def fetch_scene(path)
79
+ response = handle_response! { self.class.post(GRAPHQL_ENDPOINT, body: fetch_scene_body(path)) }
80
+ scenes = response.dig("data", "findScenes", "scenes")
81
+ scenes.length == 1 ? scenes.first : nil
82
+ end
83
+
84
+ def update_scene(scene_id, movie_id)
85
+ response = handle_response! { self.class.post(GRAPHQL_ENDPOINT, body: update_scene_body(scene_id, movie_id)) }
86
+ raise StashAPIError, response["errors"] if response["errors"]
87
+
88
+ response.dig("data", "sceneUpdate")
89
+ end
90
+
91
+ private
92
+
93
+ def update_scene_body(scene_id, movie_id)
94
+ {
95
+ operationName: "SceneUpdate",
96
+ variables: {
97
+ input: {
98
+ id: scene_id,
99
+ movies: [
100
+ { "movie_id": movie_id, "scene_index": nil }
101
+ ]
102
+ }
103
+ },
104
+
105
+ query: <<~GRAPHQL
106
+ mutation SceneUpdate($input: SceneUpdateInput!) {
107
+ sceneUpdate(input: $input) {
108
+ id
109
+ title
110
+ files {
111
+ path
112
+ }
113
+ movies {
114
+ movie {
115
+ id
116
+ name
117
+ front_image_path
118
+ }
119
+ scene_index
120
+ }
121
+ }
122
+ }
123
+ GRAPHQL
124
+ }.to_json
125
+ end
126
+
127
+ def fetch_scene_body(path)
128
+ {
129
+ operationName: "FindScenes",
130
+ variables: {
131
+ filter: {
132
+ per_page: 2
133
+ },
134
+ scene_filter: {
135
+ path: {
136
+ modifier: "INCLUDES",
137
+ value: "\"#{path}\""
138
+ }
139
+ }
140
+ },
141
+ query: <<~GRAPHQL
142
+ query FindScenes($filter: FindFilterType, $scene_filter: SceneFilterType, $scene_ids: [Int!]) {
143
+ findScenes(filter: $filter, scene_filter: $scene_filter, scene_ids: $scene_ids) {
144
+ scenes {
145
+ id
146
+ title
147
+ files {
148
+ path
149
+ }
150
+ movies {
151
+ movie {
152
+ id
153
+ name
154
+ front_image_path
155
+ }
156
+ scene_index
157
+ }
158
+ }
159
+ }
160
+ }
161
+ GRAPHQL
162
+ }.to_json
163
+ end
164
+
165
+ def create_movie_body(movie, studio_id)
166
+ variables = {}.tap do |h|
167
+ h[:name] = movie.name
168
+ h[:date] = movie.date&.strftime("%Y-%m-%d") if movie.date
169
+ h[:url] = movie.url if movie.url
170
+ h[:front_image] = movie.front_image
171
+ h[:back_image] = movie.back_image if movie.back_image
172
+ h[:studio_id] = studio_id if studio_id
173
+ h[:synopsis] = movie.synopsis if movie.synopsis
174
+ end
175
+
176
+ {
177
+ operationName: "MovieCreate",
178
+ variables: variables,
179
+ query: <<~GRAPHQL
180
+ mutation MovieCreate(
181
+ $name: String!
182
+ $aliases: String
183
+ $duration: Int
184
+ $date: String
185
+ $rating: Int
186
+ $studio_id: ID
187
+ $director: String
188
+ $synopsis: String
189
+ $url: String
190
+ $front_image: String
191
+ $back_image: String
192
+ ) {
193
+ movieCreate(
194
+ input: {
195
+ name: $name
196
+ aliases: $aliases
197
+ duration: $duration
198
+ date: $date
199
+ rating: $rating
200
+ studio_id: $studio_id
201
+ director: $director
202
+ synopsis: $synopsis
203
+ url: $url
204
+ front_image: $front_image
205
+ back_image: $back_image
206
+ }
207
+ ) {
208
+ id
209
+ name
210
+ duration
211
+ date
212
+ director
213
+ studio {
214
+ id
215
+ name
216
+ }
217
+ synopsis
218
+ url
219
+ front_image_path
220
+ back_image_path
221
+ }
222
+ }
223
+ GRAPHQL
224
+ }.to_json
225
+ end
226
+
227
+ def fetch_movie_body(name)
228
+ {
229
+ operationName: "FindMovies",
230
+ variables: {
231
+ filter: {
232
+ per_page: 5
233
+ },
234
+ movie_filter: {
235
+ name: {
236
+ modifier: "EQUALS",
237
+ value: name
238
+ }
239
+ }
240
+ },
241
+ query: <<~GRAPHQL
242
+ query FindMovies($filter: FindFilterType, $movie_filter: MovieFilterType) {
243
+ findMovies(filter: $filter, movie_filter: $movie_filter) {
244
+ movies {
245
+ id
246
+ name
247
+ scenes {
248
+ id
249
+ title
250
+ path
251
+ }
252
+ }
253
+ }
254
+ }
255
+ GRAPHQL
256
+ }.to_json
257
+ end
258
+
259
+ def find_studio_body(name)
260
+ {
261
+ operationName: "FindStudios",
262
+ variables: {
263
+ filter: {
264
+ per_page: 5
265
+ },
266
+ studio_filter: {
267
+ name: {
268
+ modifier: "EQUALS",
269
+ value: name
270
+ }
271
+ }
272
+ },
273
+ query: <<~GRAPHQL
274
+ query FindStudios($filter: FindFilterType, $studio_filter: StudioFilterType) {
275
+ findStudios(filter: $filter, studio_filter: $studio_filter) {
276
+ studios {
277
+ id
278
+ name
279
+ }
280
+ }
281
+ }
282
+ GRAPHQL
283
+ }.to_json
284
+ end
285
+
286
+ def register_api_key(api_token)
287
+ @api_key_set = true
288
+ self.class.headers "ApiKey" => api_token
289
+ end
290
+
291
+ def api_key_provided?
292
+ stash_app_config.api_token.to_s.presence.is_a?(String)
293
+ end
294
+
295
+ def validate_credentials!
296
+ body = {
297
+ operationName: "Version",
298
+ query: <<~GRAPHQL
299
+ query Version {
300
+ version {
301
+ version
302
+ }
303
+ }
304
+ GRAPHQL
305
+ }.to_json
306
+
307
+ resp = handle_response! { self.class.post(GRAPHQL_ENDPOINT, body: body) }
308
+ resp.dig("data", "version", "version")
309
+ end
310
+
311
+ def stash_app_config
312
+ config.stash_app
313
+ end
314
+ end
315
+ end
316
+ end
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "logger"
4
+
5
+ module XxxRename
6
+ class Log
7
+ attr_accessor :logger
8
+
9
+ def initialize(logdev = $stdout, level = "INFO", **opts)
10
+ @logger = Logger.new(logdev)
11
+ @logger.level = opts[:verbose] ? "DEBUG" : level
12
+ # @logger.level = "DEBUG"
13
+ @logger.formatter = proc do |severity, datetime, _progname, msg|
14
+ date_format = datetime.strftime("%H:%M:%S")
15
+ case severity
16
+ when "INFO" then "#{"[#{date_format}] [#{severity.ljust(5)}]".to_s.colorize(:blue)} #{msg}\n"
17
+ when "ERROR" then "#{"[#{date_format}] [#{severity.ljust(5)}]".to_s.colorize(:light_red)} #{msg}\n"
18
+ when "FATAL" then "#{"[#{date_format}] [#{severity.ljust(5)}]".to_s.colorize(:red)} #{msg}\n"
19
+ when "WARN" then "#{"[#{date_format}] [#{severity.ljust(5)}]".to_s.colorize(:yellow)} #{msg}\n"
20
+ when "DEBUG" then "#{"[#{date_format}] [#{severity.ljust(5)}]".to_s.colorize(:light_magenta)} #{msg}\n"
21
+ else "[#{date_format}] [#{severity.ljust(5)}] #{msg}\n"
22
+ end
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,139 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "active_support/core_ext/object/try"
4
+ require "yaml/store"
5
+
6
+ require "xxx_rename/errors"
7
+ require "xxx_rename/data/file_rename_op"
8
+ require "xxx_rename/data/file_rename_op_datastore"
9
+
10
+ module XxxRename
11
+ class MigrationClient
12
+ MIGRATION_FORMAT = /output_(?<version>\d{12})/x.freeze
13
+
14
+ attr_reader :config, :version
15
+
16
+ # @param [XxxRename::Data::Config] config
17
+ # @param [String] version
18
+ def initialize(config, version)
19
+ @version = version
20
+ @config = config
21
+ end
22
+
23
+ def version_file!
24
+ Dir.chdir(output_dir) do
25
+ all_output_files = Dir["output_*.yml"].sort.reverse!
26
+ raise Errors::FatalError, "[ERR NO MIGRATION FILES FOUND]" if all_output_files.empty?
27
+
28
+ file = match_file!(all_output_files)
29
+ File.join(Dir.pwd, file)
30
+ end
31
+ end
32
+
33
+ def datastore
34
+ @datastore ||= Data::FileRenameOpDatastore.new(store, config.mutex)
35
+ end
36
+
37
+ def apply
38
+ if datastore.migration_status
39
+ XxxRename.logger.info "[MIGRATION UP]"
40
+ return
41
+ end
42
+
43
+ XxxRename.logger.info "[RENAMING #{datastore.length} FILES]"
44
+ datastore.all.each do |op|
45
+ process_rename_op(op)
46
+ rescue SystemCallError => e
47
+ XxxRename.logger.error "[RENAME FAILURE] #{e.message}"
48
+ datastore.add_failure(op.key, e.message)
49
+ end
50
+ ensure
51
+ if datastore.failures.empty?
52
+ datastore.migration_status = 1
53
+ XxxRename.logger.info "[MIGRATION UP]"
54
+ end
55
+ end
56
+
57
+ def rollback
58
+ unless datastore.migration_status
59
+ XxxRename.logger.info "[MIGRATION DOWN]"
60
+ return
61
+ end
62
+
63
+ XxxRename.logger.info "[ROLLBACK RENAMING #{datastore.length} FILES]"
64
+ datastore.all.each do |op|
65
+ process_reverse_rename_op(op)
66
+ rescue SystemCallError => e
67
+ XxxRename.logger.error "[RENAME FAILURE] #{e.message}"
68
+ datastore.add_failure(op.key, e.message)
69
+ end
70
+ ensure
71
+ if datastore.failures.empty?
72
+ datastore.migration_status = 0
73
+ XxxRename.logger.info "[MIGRATION DOWN]"
74
+ end
75
+ end
76
+
77
+ private
78
+
79
+ def match_file!(all_output_files)
80
+ if version.downcase == "latest"
81
+ all_output_files.first
82
+ else
83
+ matched_file = all_output_files.select do |x|
84
+ match = x.match(MIGRATION_FORMAT)
85
+ next if match.nil?
86
+
87
+ match[:version] == version
88
+ end.compact.first
89
+ raise Errors::FatalError, "[ERR VERSION NOT EXIST] #{version}" if matched_file.nil?
90
+
91
+ matched_file
92
+ end
93
+ end
94
+
95
+ def process_rename_op(rename_op)
96
+ if rename_op.is_a?(XxxRename::Data::FileRenameOp)
97
+ rename_op.rename
98
+ register_new_file(rename_op)
99
+ else
100
+ XxxRename.logger.error "[INVALID RENAME OP] #{rename_op}"
101
+ end
102
+ end
103
+
104
+ def register_new_file(rename_op)
105
+ scene_data = config.scene_datastore.find_by_key?(rename_op.key)
106
+ return if scene_data.nil?
107
+
108
+ config.scene_datastore.register_file(scene_data,
109
+ File.join(rename_op.directory, rename_op.output_filename),
110
+ old_filename: File.join(rename_op.directory, rename_op.source_filename))
111
+ end
112
+
113
+ def process_reverse_rename_op(rename_op)
114
+ if rename_op.is_a?(XxxRename::Data::FileRenameOp)
115
+ rename_op.reverse_rename
116
+ register_old_filename(rename_op)
117
+ else
118
+ XxxRename.logger.error "[INVALID RENAME OP] #{rename_op}"
119
+ end
120
+ end
121
+
122
+ def register_old_filename(rename_op)
123
+ scene_data = config.scene_datastore.find_by_key?(rename_op.key)
124
+ return if scene_data.nil?
125
+
126
+ config.scene_datastore.register_file(scene_data,
127
+ File.join(rename_op.directory, rename_op.source_filename),
128
+ old_filename: File.join(rename_op.directory, rename_op.output_filename))
129
+ end
130
+
131
+ def store
132
+ @store ||= YAML::Store.new(version_file!)
133
+ end
134
+
135
+ def output_dir
136
+ @output_dir ||= File.join(config.generated_files_dir, "output")
137
+ end
138
+ end
139
+ end