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.
- checksums.yaml +7 -0
- data/.github/workflows/codeql-analysis.yml +42 -0
- data/.github/workflows/ruby.yml +44 -0
- data/.gitignore +12 -0
- data/.rspec +3 -0
- data/.rubocop.yml +41 -0
- data/.ruby-version +1 -0
- data/Gemfile +6 -0
- data/Gemfile.lock +174 -0
- data/README.md +319 -0
- data/Rakefile +12 -0
- data/bin/console +23 -0
- data/bin/install +22 -0
- data/bin/setup +8 -0
- data/codecov.yml +2 -0
- data/docs/DEVELOPMENT.md +42 -0
- data/exe/xxx_rename +12 -0
- data/lib/xxx_rename/actions/base_action.rb +20 -0
- data/lib/xxx_rename/actions/log_new_filename.rb +40 -0
- data/lib/xxx_rename/actions/resolver.rb +32 -0
- data/lib/xxx_rename/actions/stash_app_post_movie.rb +62 -0
- data/lib/xxx_rename/actors_helper.rb +117 -0
- data/lib/xxx_rename/cli.rb +211 -0
- data/lib/xxx_rename/client.rb +110 -0
- data/lib/xxx_rename/constants.rb +96 -0
- data/lib/xxx_rename/contract/config_contract.rb +241 -0
- data/lib/xxx_rename/contract/config_generator.rb +207 -0
- data/lib/xxx_rename/contract/file_rename_op_contract.rb +54 -0
- data/lib/xxx_rename/contract/types.rb +10 -0
- data/lib/xxx_rename/core_extensions/string.rb +39 -0
- data/lib/xxx_rename/data/base.rb +34 -0
- data/lib/xxx_rename/data/config.rb +97 -0
- data/lib/xxx_rename/data/file_rename_op.rb +42 -0
- data/lib/xxx_rename/data/file_rename_op_datastore.rb +111 -0
- data/lib/xxx_rename/data/naughty_america_database.rb +22 -0
- data/lib/xxx_rename/data/query_interface.rb +78 -0
- data/lib/xxx_rename/data/scene_data.rb +71 -0
- data/lib/xxx_rename/data/scene_datastore.rb +401 -0
- data/lib/xxx_rename/data/site_config.rb +84 -0
- data/lib/xxx_rename/data/types.rb +13 -0
- data/lib/xxx_rename/errors.rb +28 -0
- data/lib/xxx_rename/file_scanner.rb +49 -0
- data/lib/xxx_rename/file_utilities.rb +38 -0
- data/lib/xxx_rename/filename_generator.rb +173 -0
- data/lib/xxx_rename/integrations/base.rb +20 -0
- data/lib/xxx_rename/integrations/stash_app.rb +316 -0
- data/lib/xxx_rename/log.rb +26 -0
- data/lib/xxx_rename/migration_client.rb +139 -0
- data/lib/xxx_rename/processed_file.rb +203 -0
- data/lib/xxx_rename/search.rb +166 -0
- data/lib/xxx_rename/site_client_matcher.rb +299 -0
- data/lib/xxx_rename/site_clients/adult_time.rb +31 -0
- data/lib/xxx_rename/site_clients/algolia_common.rb +48 -0
- data/lib/xxx_rename/site_clients/algolia_v2.rb +181 -0
- data/lib/xxx_rename/site_clients/babes.rb +15 -0
- data/lib/xxx_rename/site_clients/base.rb +61 -0
- data/lib/xxx_rename/site_clients/blacked.rb +12 -0
- data/lib/xxx_rename/site_clients/blacked_raw.rb +12 -0
- data/lib/xxx_rename/site_clients/brazzers.rb +15 -0
- data/lib/xxx_rename/site_clients/configuration.rb +55 -0
- data/lib/xxx_rename/site_clients/digital_playground.rb +15 -0
- data/lib/xxx_rename/site_clients/elegant_angel.rb +168 -0
- data/lib/xxx_rename/site_clients/errors.rb +103 -0
- data/lib/xxx_rename/site_clients/evil_angel.rb +59 -0
- data/lib/xxx_rename/site_clients/goodporn.rb +109 -0
- data/lib/xxx_rename/site_clients/jules_jordan.rb +22 -0
- data/lib/xxx_rename/site_clients/jules_jordan_media.rb +175 -0
- data/lib/xxx_rename/site_clients/manuel_ferrara.rb +24 -0
- data/lib/xxx_rename/site_clients/mg_premium.rb +247 -0
- data/lib/xxx_rename/site_clients/mofos.rb +15 -0
- data/lib/xxx_rename/site_clients/naughty_america.rb +272 -0
- data/lib/xxx_rename/site_clients/nfbusty.rb +84 -0
- data/lib/xxx_rename/site_clients/query_generator/base.rb +89 -0
- data/lib/xxx_rename/site_clients/query_generator/evil_angel.rb +36 -0
- data/lib/xxx_rename/site_clients/query_generator/goodporn.rb +27 -0
- data/lib/xxx_rename/site_clients/query_generator/mg_premium.rb +26 -0
- data/lib/xxx_rename/site_clients/query_generator/naughty_america.rb +24 -0
- data/lib/xxx_rename/site_clients/query_generator/stash_db.rb +21 -0
- data/lib/xxx_rename/site_clients/query_generator/vixen.rb +27 -0
- data/lib/xxx_rename/site_clients/query_generator/whale.rb +39 -0
- data/lib/xxx_rename/site_clients/reality_kings.rb +14 -0
- data/lib/xxx_rename/site_clients/stash_db.rb +257 -0
- data/lib/xxx_rename/site_clients/tushy.rb +12 -0
- data/lib/xxx_rename/site_clients/tushy_raw.rb +12 -0
- data/lib/xxx_rename/site_clients/twistys.rb +15 -0
- data/lib/xxx_rename/site_clients/vixen.rb +12 -0
- data/lib/xxx_rename/site_clients/vixen_media.rb +130 -0
- data/lib/xxx_rename/site_clients/whale.rb +106 -0
- data/lib/xxx_rename/site_clients/wicked.rb +52 -0
- data/lib/xxx_rename/site_clients/x_empire.rb +51 -0
- data/lib/xxx_rename/site_clients/zero_tolerance.rb +36 -0
- data/lib/xxx_rename/utils.rb +81 -0
- data/lib/xxx_rename/version.rb +5 -0
- data/lib/xxx_rename.rb +60 -0
- data/output.png +0 -0
- data/xxx_rename.gemspec +42 -0
- 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
|