e621_export_downloader 0.0.3 → 0.0.4

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: ef31343295188d4e90bc9c826cc0c1df810b9a93ee5a1ff99b616c45a08741c6
4
- data.tar.gz: 000ddd8a6db8c17687703730643a64eda21d8562a3fcef10ad2b4aaf7a90f383
3
+ metadata.gz: a0319ab366f0396847426a4d510dfe49c7a6a5beb49175001b3e4a56fe2e6aa9
4
+ data.tar.gz: f40878e3cb2253774caaaabb83aa0b0aa014ed648782835946b212f24a32a87f
5
5
  SHA512:
6
- metadata.gz: fdf9d2410b89b5420ca23cd7df1f35d124bccb86628e3a5c26afbdd863679f159172332f8f20131e279506c22ede1fce2452bc04514ce159ec3dc924c559ca27
7
- data.tar.gz: fd2d7e0d94eb0bc2c2f3ec4c9ff12e23185a58facc12fa01d2589315c27d5b1d0e05f90b2243ba6e5664d56901cbbafe76a29520bc478165e5997a0ef501c99d
6
+ metadata.gz: 1dbb3fea2fa77735294a73049b9684632e5fb96d1b03891199556d6fb5501999ec1b5e53b0d6a31bcc3ba60399fbba347c81a669faed08c1f59b031325297d1e
7
+ data.tar.gz: 8585b196a8cdb998a07444e4306e293ebde1f2f4ddb2bb08f26043f7d942cce3d1a041d523dccd15c0d9ca51d85902e3b91456e71e47821103754257e2d786c2
data/README.md CHANGED
@@ -23,51 +23,39 @@ client = E621ExportDownloader::Client.new
23
23
 
24
24
  # configure options after creation
25
25
  client.config do |c|
26
- c.cache = true # keep export files after reading, defaults to true
27
- c.rewind_on_not_found = 2 # decrease date by one day if no export is found for that date,
28
- # provide an integer to limit how many days can be rewound,
29
- # `true` is equivalent to `2`, defaults to false
26
+ c.cache = true # keep export files after reading, defaults to false
30
27
  end
31
28
 
32
29
  # or pass an Options struct directly
33
30
  client = E621ExportDownloader::Client.new(
34
- E621ExportDownloader::Client::Options.new(
35
- cache: true,
36
- rewind_on_not_found: 2,
37
- )
31
+ E621ExportDownloader::Client::Options.new(cache: true)
38
32
  )
39
33
 
40
- # get the helper for interacting with a type of export
41
- # types: pools, posts, tag_aliases, tag_implications, tags, wiki_pages
42
- helper = client.get("posts")
43
- # or use the named shorthand:
44
- helper = client.posts
34
+ # get the export for a type resolves the latest available export from the e621 API
35
+ # types: artists, bulk_update_requests, pools, posts, post_replacements, post_versions, tag_aliases, tag_implications, tags, wiki_pages
36
+ export = client.get("posts")
45
37
 
46
- # get a wrapper for a specific date (time components are ignored)
47
- # using this directly will not trigger rewinding regardless of rewind_on_not_found
48
- export = helper.get(Date.today)
38
+ # get a deferred export the API call is made lazily on first use
39
+ deferred = client.get_deferred("posts")
49
40
 
50
- # convenience shorthand on the client also skips rewinding
51
- export = client.get_posts(Date.today)
41
+ # get raw metadata for all exports from the e621 API
42
+ # returns an Array of E621ExportDownloader::APIExportData: { file_name, file_size, name, updated_at, url }
43
+ data = client.get_data
52
44
 
53
- # all of the following methods can also be called on the helper with a date argument;
54
- # if rewind_on_not_found is enabled the helper decrements the date by one day until it
55
- # finds an export or exhausts the rewind limit, at which point it raises ResolveError
45
+ # access the metadata for this specific export: { file_name, file_size, name, updated_at, url }
46
+ export.data
56
47
 
57
- # check whether the export exists for the date
48
+ # check if the export exists
58
49
  exists = export.exists?
59
- exists = helper.exists?(Date.today)
60
50
 
61
51
  # download the export, returns the file path — not required before reading
62
52
  # if you move or remove the file do not reuse the export object
63
53
  file = export.download
64
- file = helper.download(Date.today)
65
54
 
66
55
  # delete the downloaded file, if it exists
67
56
  export.delete
68
- helper.delete(Date.today)
69
57
 
70
- # get all of the records as a single array, DO NOT use this for large exports, arrays will millions of items do not perform well and will likely crash your process!
58
+ # get all of the records as a single array, DO NOT use this for large exports, arrays with millions of items do not perform well and will likely crash your process!
71
59
  # (not to mention that the posts export is more than 5 gigabytes)
72
60
  records = export.read_all
73
61
 
@@ -98,45 +86,52 @@ client.config do |c|
98
86
  end
99
87
  ```
100
88
 
89
+ Available parser keys: `artists`, `bulk_update_requests`, `pools`, `posts`, `post_replacements`, `post_versions`, `tag_aliases`, `tag_implications`, `tags`, `wiki_pages`
90
+
101
91
  ## Models
102
92
 
103
93
  Each export type is parsed into a corresponding model class:
104
94
 
105
- | Export type | Model class |
106
- |-------------------|---------------------------------------------------|
107
- | `pools` | `E621ExportDownloader::Models::Pool` |
108
- | `posts` | `E621ExportDownloader::Models::Post` |
109
- | `tag_aliases` | `E621ExportDownloader::Models::TagAlias` |
110
- | `tag_implications`| `E621ExportDownloader::Models::TagImplication` |
111
- | `tags` | `E621ExportDownloader::Models::Tag` |
112
- | `wiki_pages` | `E621ExportDownloader::Models::WikiPage` |
95
+ | Export type | Model class |
96
+ |------------------------|-------------------------------------------------------|
97
+ | `artists` | `E621ExportDownloader::Models::Artist` |
98
+ | `bulk_update_requests` | `E621ExportDownloader::Models::BulkUpdateRequest` |
99
+ | `pools` | `E621ExportDownloader::Models::Pool` |
100
+ | `posts` | `E621ExportDownloader::Models::Post` |
101
+ | `post_replacements` | `E621ExportDownloader::Models::PostReplacement` |
102
+ | `post_versions` | `E621ExportDownloader::Models::PostVersion` |
103
+ | `tag_aliases` | `E621ExportDownloader::Models::TagAlias` |
104
+ | `tag_implications` | `E621ExportDownloader::Models::TagImplication` |
105
+ | `tags` | `E621ExportDownloader::Models::Tag` |
106
+ | `wiki_pages` | `E621ExportDownloader::Models::WikiPage` |
113
107
 
114
108
  ## CLI
115
109
 
116
110
  ```bash
117
- # check if an export exists for a given date; date is optional and defaults to today
111
+ # get export metadata from the e621 API as a JSON array
112
+ # outputs { file_name, file_size, name, updated_at, url }[] with no trailing newline
113
+ e621-export-downloader data
114
+
115
+ # check if an export exists
118
116
  # outputs "true" or "false" with no trailing newline
119
- e621-export-downloader exists posts 2024-01-01
117
+ e621-export-downloader exists posts
120
118
 
121
- # download an export for a given date; date is optional and defaults to today
119
+ # download an export
122
120
  # outputs the path to the downloaded file with no trailing newline
123
- e621-export-downloader download posts 2024-01-01
121
+ e621-export-downloader download posts
124
122
 
125
- # read an export as individual JSON lines; date is optional and defaults to today
123
+ # read an export as individual JSON lines
126
124
  # outputs each record as a JSON object on its own line
127
- e621-export-downloader read posts 2024-01-01
125
+ e621-export-downloader read posts
128
126
 
129
- # read an export as a JSON array; date is optional and defaults to today
127
+ # read an export as a JSON array
130
128
  # streams the CSV internally, so it is safe to use with large exports
131
- e621-export-downloader read-all posts 2024-01-01
129
+ e621-export-downloader read-all posts
132
130
 
133
131
  e621-export-downloader --help
134
132
  e621-export-downloader --version
135
- e621-export-downloader --cache # enable caching (default)
136
- e621-export-downloader --no-cache # disable caching
137
- e621-export-downloader --rewind-on-not-found # enable rewinding (up to 2 days)
138
- e621-export-downloader --rewind-on-not-found 5 # enable rewinding (up to 5 days)
139
- e621-export-downloader --no-rewind-on-not-found # disable rewinding (default)
133
+ e621-export-downloader --cache # enable caching
134
+ e621-export-downloader --no-cache # disable caching (default)
140
135
  ```
141
136
 
142
137
  ## ActiveJob Integration
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+ # typed: strict
3
+
4
+ module E621ExportDownloader
5
+ class APIExportData < T::Struct
6
+ const(:file_name, String)
7
+ const(:file_size, Integer)
8
+ const(:name, Types)
9
+ const(:updated_at, String)
10
+ const(:url, String)
11
+ end
12
+ end
@@ -8,12 +8,24 @@ module E621ExportDownloader
8
8
  class Parsers
9
9
  extend(T::Sig)
10
10
 
11
+ sig { returns(Parser) }
12
+ attr_accessor(:artists)
13
+
14
+ sig { returns(Parser) }
15
+ attr_accessor(:bulk_update_requests)
16
+
11
17
  sig { returns(Parser) }
12
18
  attr_accessor(:pools)
13
19
 
14
20
  sig { returns(Parser) }
15
21
  attr_accessor(:posts)
16
22
 
23
+ sig { returns(Parser) }
24
+ attr_accessor(:post_replacements)
25
+
26
+ sig { returns(Parser) }
27
+ attr_accessor(:post_versions)
28
+
17
29
  sig { returns(Parser) }
18
30
  attr_accessor(:tag_aliases)
19
31
 
@@ -28,12 +40,16 @@ module E621ExportDownloader
28
40
 
29
41
  sig { params(defaults: Options::Parsers).void }
30
42
  def initialize(defaults = Options::Parsers.defaults)
31
- @pools = T.let(T.must(defaults.pools), Parser)
32
- @posts = T.let(T.must(defaults.posts), Parser)
33
- @tag_aliases = T.let(T.must(defaults.tag_aliases), Parser)
34
- @tag_implications = T.let(T.must(defaults.tag_implications), Parser)
35
- @tags = T.let(T.must(defaults.tags), Parser)
36
- @wiki_pages = T.let(T.must(defaults.wiki_pages), Parser)
43
+ @artists = T.let(T.must(defaults.artists), Parser)
44
+ @bulk_update_requests = T.let(T.must(defaults.bulk_update_requests), Parser)
45
+ @pools = T.let(T.must(defaults.pools), Parser)
46
+ @posts = T.let(T.must(defaults.posts), Parser)
47
+ @post_replacements = T.let(T.must(defaults.post_replacements), Parser)
48
+ @post_versions = T.let(T.must(defaults.post_versions), Parser)
49
+ @tag_aliases = T.let(T.must(defaults.tag_aliases), Parser)
50
+ @tag_implications = T.let(T.must(defaults.tag_implications), Parser)
51
+ @tags = T.let(T.must(defaults.tags), Parser)
52
+ @wiki_pages = T.let(T.must(defaults.wiki_pages), Parser)
37
53
  end
38
54
  end
39
55
  end
@@ -10,16 +10,12 @@ module E621ExportDownloader
10
10
  sig { returns(T::Boolean) }
11
11
  attr_accessor(:cache)
12
12
 
13
- sig { returns(T.any(T::Boolean, Integer)) }
14
- attr_accessor(:rewind_on_not_found)
15
-
16
13
  sig { params(parsers: Options::Parsers).returns(Options::Parsers) }
17
14
  attr_writer(:parsers)
18
15
 
19
16
  sig { params(defaults: Options).void }
20
17
  def initialize(defaults = Options.new)
21
18
  @cache = T.let(defaults.cache, T::Boolean)
22
- @rewind_on_not_found = T.let(defaults.rewind_on_not_found, T.any(T::Boolean, Integer))
23
19
  @parsers = T.let(defaults.parsers, Options::Parsers)
24
20
  end
25
21
 
@@ -30,12 +26,16 @@ module E621ExportDownloader
30
26
  block.call(parsers)
31
27
 
32
28
  @parsers = Options::Parsers.new(
33
- pools: parsers.pools,
34
- posts: parsers.posts,
35
- tag_aliases: parsers.tag_aliases,
36
- tag_implications: parsers.tag_implications,
37
- tags: parsers.tags,
38
- wiki_pages: parsers.wiki_pages,
29
+ artists: parsers.artists,
30
+ bulk_update_requests: parsers.bulk_update_requests,
31
+ pools: parsers.pools,
32
+ posts: parsers.posts,
33
+ post_replacements: parsers.post_replacements,
34
+ post_versions: parsers.post_versions,
35
+ tag_aliases: parsers.tag_aliases,
36
+ tag_implications: parsers.tag_implications,
37
+ tags: parsers.tags,
38
+ wiki_pages: parsers.wiki_pages,
39
39
  )
40
40
  end
41
41
  end
@@ -9,8 +9,12 @@ module E621ExportDownloader
9
9
  class Parsers < T::Struct
10
10
  extend(T::Sig)
11
11
 
12
+ const(:artists, T.nilable(Parser), default: nil)
13
+ const(:bulk_update_requests, T.nilable(Parser), default: nil)
12
14
  const(:pools, T.nilable(Parser), default: nil)
13
15
  const(:posts, T.nilable(Parser), default: nil)
16
+ const(:post_replacements, T.nilable(Parser), default: nil)
17
+ const(:post_versions, T.nilable(Parser), default: nil)
14
18
  const(:tag_aliases, T.nilable(Parser), default: nil)
15
19
  const(:tag_implications, T.nilable(Parser), default: nil)
16
20
  const(:tags, T.nilable(Parser), default: nil)
@@ -19,18 +23,21 @@ module E621ExportDownloader
19
23
  sig { returns(Parsers) }
20
24
  def self.defaults
21
25
  Parsers.new(
22
- pools: ->(record) { Models::Pool.new(record) },
23
- posts: ->(record) { Models::Post.new(record) },
24
- tag_aliases: ->(record) { Models::TagAlias.new(record) },
25
- tag_implications: ->(record) { Models::TagImplication.new(record) },
26
- tags: ->(record) { Models::Tag.new(record) },
27
- wiki_pages: ->(record) { Models::WikiPage.new(record) },
26
+ artists: ->(record) { Models::Artist.new(record) },
27
+ bulk_update_requests: ->(record) { Models::BulkUpdateRequest.new(record) },
28
+ pools: ->(record) { Models::Pool.new(record) },
29
+ posts: ->(record) { Models::Post.new(record) },
30
+ post_replacements: ->(record) { Models::PostReplacement.new(record) },
31
+ post_versions: ->(record) { Models::PostVersion.new(record) },
32
+ tag_aliases: ->(record) { Models::TagAlias.new(record) },
33
+ tag_implications: ->(record) { Models::TagImplication.new(record) },
34
+ tags: ->(record) { Models::Tag.new(record) },
35
+ wiki_pages: ->(record) { Models::WikiPage.new(record) },
28
36
  )
29
37
  end
30
38
  end
31
39
 
32
- const(:cache, T::Boolean, default: true)
33
- const(:rewind_on_not_found, T.any(T::Boolean, Integer), default: false)
40
+ const(:cache, T::Boolean, default: false)
34
41
  const(:parsers, Parsers, default: Parsers.defaults)
35
42
  end
36
43
  end
@@ -2,6 +2,7 @@
2
2
  # typed: strict
3
3
 
4
4
  require("faraday")
5
+ require("json")
5
6
 
6
7
  module E621ExportDownloader
7
8
  class Client
@@ -15,6 +16,7 @@ module E621ExportDownloader
15
16
  sig { params(options: T.nilable(Options)).void }
16
17
  def initialize(options = nil)
17
18
  @options = T.let(options || Options.new, Options)
19
+ @export_cache = T.let(nil, T.nilable(T::Array[APIExportData]))
18
20
  end
19
21
 
20
22
  sig { params(block: T.proc.params(arg0: Options::Builder).void).void }
@@ -24,7 +26,7 @@ module E621ExportDownloader
24
26
 
25
27
  sig { returns(Faraday::Connection) }
26
28
  def connection
27
- Faraday.new(url: Constants::BASE_URL, headers: { "User-Agent" => Constants::USER_AGENT })
29
+ Faraday.new(headers: { "User-Agent" => Constants::USER_AGENT })
28
30
  end
29
31
 
30
32
  sig { params(msg: String, header: T::Array[String]).void }
@@ -32,89 +34,60 @@ module E621ExportDownloader
32
34
  Logger.debug("[e621_export_downloader#{":#{header.join(':')}" unless header.empty?}] #{msg}")
33
35
  end
34
36
 
35
- sig { params(type: T.any(Types, String)).returns(ExportHelper[T.untyped]) }
37
+ sig { params(type: T.any(Types, String)).returns(Export[T.untyped]) }
36
38
  def get(type)
37
- if type.is_a?(String)
38
- case type
39
- when "pools"
40
- type = Types::Pools
41
- when "posts"
42
- type = Types::Posts
43
- when "tag_aliases"
44
- type = Types::TagAliases
45
- when "tag_implications"
46
- type = Types::TagImplications
47
- when "tags"
48
- type = Types::Tags
49
- when "wiki_pages"
50
- type = Types::WikiPages
51
- else
52
- raise(ArgumentError, "invalid type: #{type}")
53
- end
54
- end
55
-
39
+ type = string_to_type(type) if type.is_a?(String)
56
40
  T.assert_type!(type, Types)
57
- ExportHelper.new(client: self, type: type, parser: options.parsers.public_send(type.serialize))
58
- end
59
-
60
- sig { returns(ExportHelper[Models::Pool]) }
61
- def pools
62
- get(Types::Pools)
63
- end
64
-
65
- sig { params(date: T.any(Date, DateTime)).returns(Export[Models::Pool]) }
66
- def get_pools(date = Date.today)
67
- pools.get(date)
68
- end
69
-
70
- sig { returns(ExportHelper[Models::Post]) }
71
- def posts
72
- get(Types::Posts)
73
- end
74
-
75
- sig { params(date: T.any(Date, DateTime)).returns(Export[Models::Post]) }
76
- def get_posts(date = Date.today)
77
- posts.get(date)
78
- end
79
-
80
- sig { returns(ExportHelper[Models::TagAlias]) }
81
- def tag_aliases
82
- get(Types::TagAliases)
83
- end
84
-
85
- sig { params(date: T.any(Date, DateTime)).returns(Export[Models::TagAlias]) }
86
- def get_tag_aliases(date = Date.today)
87
- tag_aliases.get(date)
88
- end
89
-
90
- sig { returns(ExportHelper[Models::TagImplication]) }
91
- def tag_implications
92
- get(Types::TagImplications)
93
- end
94
-
95
- sig { params(date: T.any(Date, DateTime)).returns(Export[Models::TagImplication]) }
96
- def get_tag_implications(date = Date.today)
97
- tag_implications.get(date)
98
- end
99
-
100
- sig { returns(ExportHelper[Models::Tag]) }
101
- def tags
102
- get(Types::Tags)
41
+ data = get_data.find { |d| d.name == type }
42
+ raise(ResolveError, "Export data for \"#{type.serialize}\" not found") if data.nil?
43
+ debug("creating export for #{type.serialize}")
44
+ Export.new(data: data, client: self, type: type, parser: options.parsers.public_send(type.serialize))
103
45
  end
104
46
 
105
- sig { params(date: T.any(Date, DateTime)).returns(Export[Models::Tag]) }
106
- def get_tags(date = Date.today)
107
- tags.get(date)
108
- end
109
-
110
- sig { returns(ExportHelper[Models::WikiPage]) }
111
- def wiki_pages
112
- get(Types::WikiPages)
113
- end
114
-
115
- sig { params(date: T.any(Date, DateTime)).returns(Export[Models::WikiPage]) }
116
- def get_wiki_pages(date = Date.today)
117
- wiki_pages.get(date)
47
+ sig { params(type: T.any(Types, String)).returns(ExportHelper[T.untyped]) }
48
+ def get_deferred(type)
49
+ type = string_to_type(type) if type.is_a?(String)
50
+ T.assert_type!(type, Types)
51
+ debug("creating deferred export for #{type.serialize}")
52
+ ExportHelper.new(type: type, client: self)
53
+ end
54
+
55
+ sig { returns(T::Array[APIExportData]) }
56
+ def get_data
57
+ return @export_cache unless @export_cache.nil?
58
+ debug("fetching export data from api")
59
+ res = connection.get(Constants::API_URL)
60
+ raise(ResolveError, "Failed to fetch exports: #{res.status} #{res.reason_phrase}") unless res.success?
61
+ data = JSON.parse(T.unsafe(res).body)
62
+ result = T.let(data.map do |d|
63
+ APIExportData.new(
64
+ file_name: d["file_name"],
65
+ file_size: d["file_size"].to_i,
66
+ name: Types.deserialize(d["name"]),
67
+ updated_at: d["updated_at"],
68
+ url: d["url"],
69
+ )
70
+ end, T::Array[APIExportData])
71
+ @export_cache = result
72
+ end
73
+
74
+ private
75
+
76
+ sig { params(type: String).returns(Types) }
77
+ def string_to_type(type)
78
+ case type
79
+ when "artists" then Types::Artists
80
+ when "bulk_update_requests" then Types::BulkUpdateRequests
81
+ when "pools" then Types::Pools
82
+ when "posts" then Types::Posts
83
+ when "post_replacements" then Types::PostReplacements
84
+ when "post_versions" then Types::PostVersions
85
+ when "tag_aliases" then Types::TagAliases
86
+ when "tag_implications" then Types::TagImplications
87
+ when "tags" then Types::Tags
88
+ when "wiki_pages" then Types::WikiPages
89
+ else raise(ArgumentError, "invalid type: #{type}")
90
+ end
118
91
  end
119
92
  end
120
93
  end
@@ -9,8 +9,9 @@ module E621ExportDownloader
9
9
  module Constants
10
10
  extend(T::Sig)
11
11
 
12
+ API_URL = "https://e621.net/db_exports.json"
12
13
  BASE_URL = "https://e621.net/db_export/"
13
- EXPORT_NAMES = %w[pools posts tag_aliases tag_implications tags wiki_pages].freeze
14
+ EXPORT_NAMES = %w[artists bulk_update_requests pools posts post_replacements post_versions tag_aliases tag_implications tags wiki_pages].freeze
14
15
  TEMP_DIR = T.let(File.join(Dir.tmpdir, "e621-export-downloader"), String)
15
16
  USER_AGENT = T.let("E621ExportDownloader.rb/#{VERSION} (#{WEBSITE})", String)
16
17
  end
@@ -10,26 +10,31 @@ module E621ExportDownloader
10
10
 
11
11
  Model = type_member
12
12
 
13
- sig { returns(Date) }
14
- attr_accessor(:date)
13
+ sig { returns(APIExportData) }
14
+ attr_accessor(:data)
15
15
 
16
- sig { returns(ExportHelper[Model]) }
17
- attr_accessor(:helper)
16
+ sig { returns(Client) }
17
+ attr_accessor(:client)
18
+
19
+ sig { returns(Types) }
20
+ attr_accessor(:type)
18
21
 
19
22
  sig { returns(T.nilable(T::Boolean)) }
20
- # If nil, no check has been performed yet
21
23
  attr_accessor(:downloaded)
22
24
 
23
- sig { params(date: T.any(Date, DateTime), helper: ExportHelper[Model]).void }
24
- def initialize(date:, helper:)
25
- @date = T.let(date.is_a?(DateTime) ? date.to_date : date, Date)
26
- @helper = helper
25
+ sig { params(data: APIExportData, client: Client, type: Types, parser: Client::Options::Parser).void }
26
+ def initialize(data:, client:, type:, parser:)
27
+ @data = T.let(data, APIExportData)
28
+ @client = T.let(client, Client)
29
+ @type = T.let(type, Types)
30
+ @parser = T.let(parser, Client::Options::Parser)
27
31
  @downloaded = T.let(nil, T.nilable(T::Boolean))
28
32
  end
29
33
 
30
34
  sig { returns(T::Boolean) }
31
35
  def delete
32
36
  return false unless check_downloaded
37
+ client.debug("deleting cached export", header: ["export:#{type.serialize}"])
33
38
  FileUtils.rm(file_path)
34
39
  @downloaded = false
35
40
  true
@@ -37,19 +42,19 @@ module E621ExportDownloader
37
42
 
38
43
  sig { returns(String) }
39
44
  def download
40
- raise(ResolveError, "Export #{helper.type} for #{helper.format_date(date)} does not exist") unless exists?
45
+ raise(ResolveError, "Export #{type.serialize} does not exist") unless exists?
41
46
  if check_downloaded
42
- helper.client.debug("using cached export for #{helper.type.serialize}", header: %W[export #{helper.format_date(date)}])
47
+ client.debug("using cached export", header: ["export:#{type.serialize}"])
43
48
  return file_path
44
49
  end
45
50
 
46
- helper.client.debug("downloading export for #{helper.type.serialize}", header: %W[export #{helper.format_date(date)}])
51
+ client.debug("downloading export", header: ["export:#{type.serialize}"])
47
52
 
48
53
  FileUtils.mkdir_p(Constants::TEMP_DIR)
49
54
  File.open(file_path, "wb") do |file|
50
55
  inflater = Zlib::Inflate.new(Zlib::MAX_WBITS + 16)
51
56
 
52
- res = helper.client.connection.get("#{file_name}.gz") do |req|
57
+ res = client.connection.get(data.url) do |req|
53
58
  req.options.on_data = proc do |chunk, _total|
54
59
  decompressed = inflater.inflate(chunk)
55
60
  file.write(decompressed) if decompressed && !decompressed.empty?
@@ -61,7 +66,7 @@ module E621ExportDownloader
61
66
  file.close
62
67
 
63
68
  unless res.success?
64
- raise(ResolveError, "Failed to download export #{helper.type.serialize} for #{helper.format_date(date)}: #{res.status} #{res.reason_phrase}")
69
+ raise(ResolveError, "Failed to download export #{type.serialize}: #{res.status} #{res.reason_phrase}")
65
70
  end
66
71
  rescue # rubocop:disable Style/RescueStandardError
67
72
  FileUtils.rm_f(file_path)
@@ -69,30 +74,34 @@ module E621ExportDownloader
69
74
  end
70
75
 
71
76
  @downloaded = true
77
+ client.debug("download complete: #{file_path}", header: ["export:#{type.serialize}"])
72
78
  file_path
73
79
  end
74
80
 
75
81
  sig { returns(T::Boolean) }
76
82
  def exists?
77
- helper.client.connection.head("#{file_name}.gz").success?
83
+ result = client.connection.head(data.url).success?
84
+ client.debug("checked export existence: #{result}", header: ["export:#{type.serialize}"])
85
+ result
78
86
  end
79
87
 
80
88
  sig { params(block: T.proc.params(arg0: Model, arg1: Integer).void).void }
81
89
  def read(&block)
82
90
  download unless check_downloaded
83
- helper.client.debug("reading export for #{helper.type.serialize}", header: %W[export #{helper.format_date(date)}])
91
+ client.debug("reading export", header: ["export:#{type.serialize}"])
84
92
  total = line_count
85
93
  CSV.foreach(file_path, headers: true) do |row|
86
- args = [helper.parser.call(T.cast(row, CSV::Row).to_hash), total]
94
+ args = [@parser.call(T.cast(row, CSV::Row).to_hash), total]
87
95
  args = args.slice(0, block.arity) if block.arity != -1
88
96
  block.call(*T.unsafe(args))
89
97
  end
98
+ client.debug("finished reading export", header: ["export:#{type.serialize}"])
99
+ delete unless client.options.cache
90
100
  end
91
101
 
92
102
  sig { returns(T::Array[Model]) }
93
103
  def read_all
94
- download unless check_downloaded
95
- helper.client.debug("reading all records for #{helper.type.serialize}", header: %W[export #{helper.format_date(date)}])
104
+ client.debug("reading all records", header: ["export:#{type.serialize}"])
96
105
  results = []
97
106
  read do |record|
98
107
  results << record
@@ -102,6 +111,11 @@ module E621ExportDownloader
102
111
 
103
112
  private
104
113
 
114
+ sig { returns(String) }
115
+ def format_date
116
+ DateTime.parse(data.updated_at).strftime("%Y-%m-%d")
117
+ end
118
+
105
119
  sig { returns(Integer) }
106
120
  def line_count
107
121
  total = 0
@@ -113,6 +127,8 @@ module E621ExportDownloader
113
127
  def check_downloaded
114
128
  return @downloaded unless @downloaded.nil?
115
129
  @downloaded = File.exist?(file_path)
130
+ client.debug("checked downloaded state: #{@downloaded}", header: ["export:#{type.serialize}"])
131
+ @downloaded
116
132
  end
117
133
 
118
134
  sig { returns(String) }
@@ -122,7 +138,7 @@ module E621ExportDownloader
122
138
 
123
139
  sig { returns(String) }
124
140
  def file_name
125
- "#{helper.type.serialize}-#{helper.format_date(date)}.csv"
141
+ "#{type.serialize}-#{format_date}.csv"
126
142
  end
127
143
  end
128
144
  end
@@ -11,73 +11,51 @@ module E621ExportDownloader
11
11
  sig { returns(Types) }
12
12
  attr_reader(:type)
13
13
 
14
- sig { returns(Client::Options::Parser) }
15
- attr_reader(:parser)
16
-
17
14
  sig { returns(Client) }
18
15
  attr_reader(:client)
19
16
 
20
- sig { params(type: Types, parser: Client::Options::Parser, client: Client).void }
21
- def initialize(type:, parser:, client:)
17
+ sig { params(type: Types, client: Client).void }
18
+ def initialize(type:, client:)
22
19
  @type = type
23
- @parser = parser
24
20
  @client = client
25
- @rewind_count = T.let(0, Integer)
21
+ @export = T.let(nil, T.nilable(Export[Model]))
26
22
  end
27
23
 
28
- sig { params(date: T.any(Date, DateTime)).returns(String) }
29
- def format_date(date)
30
- date.strftime("%Y-%m-%d")
24
+ sig { returns(T::Boolean) }
25
+ def delete
26
+ _get.delete
31
27
  end
32
28
 
33
- sig { params(date: T.any(Date, DateTime)).returns(T::Boolean) }
34
- def delete(date)
35
- client.debug("deleting export for #{type.serialize}", header: %W[helper #{format_date(date)}])
36
- _get(date).delete
29
+ sig { returns(String) }
30
+ def download
31
+ _get.download
37
32
  end
38
33
 
39
- sig { params(date: T.any(Date, DateTime)).returns(String) }
40
- def download(date)
41
- client.debug("downloading export for #{type.serialize}", header: %W[helper #{format_date(date)}])
42
- _get(date).download
34
+ sig { returns(T::Boolean) }
35
+ def exists?
36
+ _get.exists?
43
37
  end
44
38
 
45
- sig { params(date: T.any(Date, DateTime)).returns(T::Boolean) }
46
- def exists?(date)
47
- client.debug("checking export existence for #{type.serialize}", header: %W[helper #{format_date(date)}])
48
- _get(date).exists?
39
+ sig { returns(Export[Model]) }
40
+ def get
41
+ _get
49
42
  end
50
43
 
51
- sig { params(date: T.any(Date, DateTime)).returns(Export[Model]) }
52
- def get(date)
53
- client.debug("creating export handle for #{type.serialize}", header: %W[helper #{format_date(date)}])
54
- Export.new(date: date, helper: self)
44
+ sig { params(block: T.proc.params(arg0: Model, arg1: Integer).void).void }
45
+ def read(&block)
46
+ _get.read(&block)
55
47
  end
56
48
 
57
- private
58
-
59
- sig { returns(Integer) }
60
- def max_rewind_count
61
- return 0 if client.options.rewind_on_not_found == false
62
- return 2 if client.options.rewind_on_not_found == true
63
- T.cast(client.options.rewind_on_not_found, Integer)
49
+ sig { returns(T::Array[Model]) }
50
+ def read_all
51
+ _get.read_all
64
52
  end
65
53
 
66
- sig { params(date: T.any(Date, DateTime), original_date: T.any(Date, DateTime)).returns(Export[Model]) }
67
- def _get(date, original_date = date)
68
- export = get(date)
69
- if export.exists?
70
- client.debug("resolved export for #{type.serialize}", header: %w[helper])
71
- return export
72
- end
73
-
74
- if @rewind_count < max_rewind_count
75
- @rewind_count += 1
76
- client.debug("rewinding export lookup for #{type.serialize} from #{format_date(original_date)} (attempt #{@rewind_count}/#{max_rewind_count})", header: %w[helper])
77
- return _get(date.to_date + 1, original_date)
78
- end
54
+ private
79
55
 
80
- raise(ResolveError, "Export #{type.serialize} for #{format_date(original_date)} does not exist, and either rewinding is not allowed or the maximum rewind limit has been reached")
56
+ sig { returns(Export[Model]) }
57
+ def _get
58
+ @export ||= client.get(type)
81
59
  end
82
60
  end
83
61
  end
@@ -0,0 +1,77 @@
1
+ # frozen_string_literal: true
2
+ # typed: strict
3
+
4
+ module E621ExportDownloader
5
+ module Models
6
+ class Artist
7
+ extend(T::Sig)
8
+
9
+ sig { returns(DateTime) }
10
+ attr_reader(:created_at)
11
+
12
+ sig { returns(Integer) }
13
+ attr_reader(:creator_id)
14
+
15
+ sig { returns(T.nilable(String)) }
16
+ attr_reader(:group_name)
17
+
18
+ sig { returns(Integer) }
19
+ attr_reader(:id)
20
+
21
+ sig { returns(T::Boolean) }
22
+ attr_reader(:is_active)
23
+
24
+ sig { returns(T::Boolean) }
25
+ attr_reader(:is_locked)
26
+
27
+ sig { returns(T.nilable(Integer)) }
28
+ attr_reader(:linked_user_id)
29
+
30
+ sig { returns(String) }
31
+ attr_reader(:name)
32
+
33
+ sig { returns(T::Array[String]) }
34
+ attr_reader(:other_names)
35
+
36
+ sig { returns(DateTime) }
37
+ attr_reader(:updated_at)
38
+
39
+ sig { returns(T::Array[String]) }
40
+ attr_reader(:urls)
41
+
42
+ sig { params(record: T::Hash[String, String]).void }
43
+ def initialize(record)
44
+ @record = T.let(record, T::Hash[String, String])
45
+ @created_at = T.let(DateTime.parse(record["created_at"]), DateTime)
46
+ @creator_id = T.let(record["creator_id"].to_i, Integer)
47
+ @group_name = T.let(T.must(record["group_name"]).empty? ? nil : record["group_name"], T.nilable(String))
48
+ @id = T.let(record["id"].to_i, Integer)
49
+ @is_active = T.let(record["is_active"] == "t", T::Boolean)
50
+ @is_locked = T.let(record["is_locked"] == "t", T::Boolean)
51
+ @linked_user_id = T.let(T.must(record["linked_user_id"]).empty? ? nil : record["linked_user_id"].to_i, T.nilable(Integer))
52
+ @name = T.let(T.must(record["name"]), String)
53
+ inner = T.must(T.must(record["other_names"])[1..-2])
54
+ @other_names = T.let(inner.empty? ? [] : inner.split(","), T::Array[String])
55
+ @updated_at = T.let(DateTime.parse(record["updated_at"]), DateTime)
56
+ @urls = T.let(T.must(record["urls"]).split("\n"), T::Array[String])
57
+ end
58
+
59
+ sig { params(_args: T.untyped).returns(String) }
60
+ def to_json(*_args)
61
+ {
62
+ created_at: @created_at,
63
+ creator_id: @creator_id,
64
+ group_name: @group_name,
65
+ id: @id,
66
+ is_active: @is_active,
67
+ is_locked: @is_locked,
68
+ linked_user_id: @linked_user_id,
69
+ name: @name,
70
+ other_names: @other_names,
71
+ updated_at: @updated_at,
72
+ urls: @urls,
73
+ }.to_json
74
+ end
75
+ end
76
+ end
77
+ end
@@ -0,0 +1,66 @@
1
+ # frozen_string_literal: true
2
+ # typed: strict
3
+
4
+ module E621ExportDownloader
5
+ module Models
6
+ class BulkUpdateRequest
7
+ extend(T::Sig)
8
+
9
+ sig { returns(T.nilable(Integer)) }
10
+ attr_reader(:approver_id)
11
+
12
+ sig { returns(DateTime) }
13
+ attr_reader(:created_at)
14
+
15
+ sig { returns(T.nilable(Integer)) }
16
+ attr_reader(:forum_topic_id)
17
+
18
+ sig { returns(Integer) }
19
+ attr_reader(:id)
20
+
21
+ sig { returns(String) }
22
+ attr_reader(:script)
23
+
24
+ sig { returns(String) }
25
+ attr_reader(:status)
26
+
27
+ sig { returns(T.nilable(String)) }
28
+ attr_reader(:title)
29
+
30
+ sig { returns(DateTime) }
31
+ attr_reader(:updated_at)
32
+
33
+ sig { returns(Integer) }
34
+ attr_reader(:user_id)
35
+
36
+ sig { params(record: T::Hash[String, String]).void }
37
+ def initialize(record)
38
+ @record = T.let(record, T::Hash[String, String])
39
+ @approver_id = T.let(T.must(record["approver_id"]).empty? ? nil : record["approver_id"].to_i, T.nilable(Integer))
40
+ @created_at = T.let(DateTime.parse(record["created_at"]), DateTime)
41
+ @forum_topic_id = T.let(T.must(record["forum_topic_id"]).empty? ? nil : record["forum_topic_id"].to_i, T.nilable(Integer))
42
+ @id = T.let(record["id"].to_i, Integer)
43
+ @script = T.let(T.must(record["script"]), String)
44
+ @status = T.let(T.must(record["status"]), String)
45
+ @title = T.let(T.must(record["title"]).empty? ? nil : record["title"], T.nilable(String))
46
+ @updated_at = T.let(DateTime.parse(record["updated_at"]), DateTime)
47
+ @user_id = T.let(record["user_id"].to_i, Integer)
48
+ end
49
+
50
+ sig { params(_args: T.untyped).returns(String) }
51
+ def to_json(*_args)
52
+ {
53
+ approver_id: @approver_id,
54
+ created_at: @created_at,
55
+ forum_topic_id: @forum_topic_id,
56
+ id: @id,
57
+ script: @script,
58
+ status: @status,
59
+ title: @title,
60
+ updated_at: @updated_at,
61
+ user_id: @user_id,
62
+ }.to_json
63
+ end
64
+ end
65
+ end
66
+ end
@@ -0,0 +1,96 @@
1
+ # frozen_string_literal: true
2
+ # typed: strict
3
+
4
+ module E621ExportDownloader
5
+ module Models
6
+ class PostReplacement
7
+ extend(T::Sig)
8
+
9
+ sig { returns(T.nilable(Integer)) }
10
+ attr_reader(:approver_id)
11
+
12
+ sig { returns(DateTime) }
13
+ attr_reader(:created_at)
14
+
15
+ sig { returns(Integer) }
16
+ attr_reader(:creator_id)
17
+
18
+ sig { returns(String) }
19
+ attr_reader(:file_ext)
20
+
21
+ sig { returns(String) }
22
+ attr_reader(:file_name)
23
+
24
+ sig { returns(Integer) }
25
+ attr_reader(:file_size)
26
+
27
+ sig { returns(Integer) }
28
+ attr_reader(:id)
29
+
30
+ sig { returns(Integer) }
31
+ attr_reader(:image_height)
32
+
33
+ sig { returns(Integer) }
34
+ attr_reader(:image_width)
35
+
36
+ sig { returns(String) }
37
+ attr_reader(:md5)
38
+
39
+ sig { returns(Integer) }
40
+ attr_reader(:post_id)
41
+
42
+ sig { returns(String) }
43
+ attr_reader(:reason)
44
+
45
+ sig { returns(T.nilable(String)) }
46
+ attr_reader(:source)
47
+
48
+ sig { returns(String) }
49
+ attr_reader(:status)
50
+
51
+ sig { returns(DateTime) }
52
+ attr_reader(:updated_at)
53
+
54
+ sig { params(record: T::Hash[String, String]).void }
55
+ def initialize(record)
56
+ @record = T.let(record, T::Hash[String, String])
57
+ @approver_id = T.let(T.must(record["approver_id"]).empty? ? nil : record["approver_id"].to_i, T.nilable(Integer))
58
+ @created_at = T.let(DateTime.parse(record["created_at"]), DateTime)
59
+ @creator_id = T.let(record["creator_id"].to_i, Integer)
60
+ @file_ext = T.let(T.must(record["file_ext"]), String)
61
+ @file_name = T.let(T.must(record["file_name"]), String)
62
+ @file_size = T.let(record["file_size"].to_i, Integer)
63
+ @id = T.let(record["id"].to_i, Integer)
64
+ @image_height = T.let(record["image_height"].to_i, Integer)
65
+ @image_width = T.let(record["image_width"].to_i, Integer)
66
+ @md5 = T.let(T.must(record["md5"]), String)
67
+ @post_id = T.let(record["post_id"].to_i, Integer)
68
+ @reason = T.let(T.must(record["reason"]), String)
69
+ @source = T.let(T.must(record["source"]).empty? ? nil : record["source"], T.nilable(String))
70
+ @status = T.let(T.must(record["status"]), String)
71
+ @updated_at = T.let(DateTime.parse(record["updated_at"]), DateTime)
72
+ end
73
+
74
+ sig { params(_args: T.untyped).returns(String) }
75
+ def to_json(*_args)
76
+ {
77
+ approver_id: @approver_id,
78
+ created_at: @created_at,
79
+ creator_id: @creator_id,
80
+ file_ext: @file_ext,
81
+ file_name: @file_name,
82
+ file_size: @file_size,
83
+ id: @id,
84
+ image_height: @image_height,
85
+ image_width: @image_width,
86
+ md5: @md5,
87
+ post_id: @post_id,
88
+ reason: @reason,
89
+ source: @source,
90
+ status: @status,
91
+ updated_at: @updated_at,
92
+ }.to_json
93
+ end
94
+ end
95
+ end
96
+ end
@@ -0,0 +1,120 @@
1
+ # frozen_string_literal: true
2
+ # typed: strict
3
+
4
+ module E621ExportDownloader
5
+ module Models
6
+ class PostVersion
7
+ extend(T::Sig)
8
+
9
+ sig { returns(T::Array[String]) }
10
+ attr_reader(:added_locked_tags)
11
+
12
+ sig { returns(T::Array[String]) }
13
+ attr_reader(:added_tags)
14
+
15
+ sig { returns(T.nilable(String)) }
16
+ attr_reader(:description)
17
+
18
+ sig { returns(T::Boolean) }
19
+ attr_reader(:description_changed)
20
+
21
+ sig { returns(Integer) }
22
+ attr_reader(:id)
23
+
24
+ sig { returns(T.nilable(String)) }
25
+ attr_reader(:locked_tags)
26
+
27
+ sig { returns(T::Boolean) }
28
+ attr_reader(:parent_changed)
29
+
30
+ sig { returns(T.nilable(Integer)) }
31
+ attr_reader(:parent_id)
32
+
33
+ sig { returns(T.nilable(String)) }
34
+ attr_reader(:rating)
35
+
36
+ sig { returns(T::Boolean) }
37
+ attr_reader(:rating_changed)
38
+
39
+ sig { returns(T.nilable(String)) }
40
+ attr_reader(:reason)
41
+
42
+ sig { returns(T::Array[String]) }
43
+ attr_reader(:removed_locked_tags)
44
+
45
+ sig { returns(T::Array[String]) }
46
+ attr_reader(:removed_tags)
47
+
48
+ sig { returns(T.nilable(String)) }
49
+ attr_reader(:source)
50
+
51
+ sig { returns(T::Boolean) }
52
+ attr_reader(:source_changed)
53
+
54
+ sig { returns(T.nilable(String)) }
55
+ attr_reader(:tags)
56
+
57
+ sig { returns(DateTime) }
58
+ attr_reader(:updated_at)
59
+
60
+ sig { returns(Integer) }
61
+ attr_reader(:updater_id)
62
+
63
+ sig { returns(Integer) }
64
+ attr_reader(:version)
65
+
66
+ sig { params(record: T::Hash[String, String]).void }
67
+ def initialize(record)
68
+ @record = T.let(record, T::Hash[String, String])
69
+ inner = T.must(T.must(record["added_locked_tags"])[1..-2])
70
+ @added_locked_tags = T.let(inner.empty? ? [] : inner.split(","), T::Array[String])
71
+ inner = T.must(T.must(record["added_tags"])[1..-2])
72
+ @added_tags = T.let(inner.empty? ? [] : inner.split(","), T::Array[String])
73
+ @description = T.let(T.must(record["description"]).empty? ? nil : record["description"], T.nilable(String))
74
+ @description_changed = T.let(record["description_changed"] == "t", T::Boolean)
75
+ @id = T.let(record["id"].to_i, Integer)
76
+ @locked_tags = T.let(T.must(record["locked_tags"]).empty? ? nil : record["locked_tags"], T.nilable(String))
77
+ @parent_changed = T.let(record["parent_changed"] == "t", T::Boolean)
78
+ @parent_id = T.let(T.must(record["parent_id"]).empty? ? nil : record["parent_id"].to_i, T.nilable(Integer))
79
+ @rating = T.let(T.must(record["rating"]).empty? ? nil : record["rating"], T.nilable(String))
80
+ @rating_changed = T.let(record["rating_changed"] == "t", T::Boolean)
81
+ @reason = T.let(T.must(record["reason"]).empty? ? nil : record["reason"], T.nilable(String))
82
+ inner = T.must(T.must(record["removed_locked_tags"])[1..-2])
83
+ @removed_locked_tags = T.let(inner.empty? ? [] : inner.split(","), T::Array[String])
84
+ inner = T.must(T.must(record["removed_tags"])[1..-2])
85
+ @removed_tags = T.let(inner.empty? ? [] : inner.split(","), T::Array[String])
86
+ @source = T.let(T.must(record["source"]).empty? ? nil : record["source"], T.nilable(String))
87
+ @source_changed = T.let(record["source_changed"] == "t", T::Boolean)
88
+ @tags = T.let(T.must(record["tags"]).empty? ? nil : record["tags"], T.nilable(String))
89
+ @updated_at = T.let(DateTime.parse(record["updated_at"]), DateTime)
90
+ @updater_id = T.let(record["updater_id"].to_i, Integer)
91
+ @version = T.let(record["version"].to_i, Integer)
92
+ end
93
+
94
+ sig { params(_args: T.untyped).returns(String) }
95
+ def to_json(*_args)
96
+ {
97
+ added_locked_tags: @added_locked_tags,
98
+ added_tags: @added_tags,
99
+ description: @description,
100
+ description_changed: @description_changed,
101
+ id: @id,
102
+ locked_tags: @locked_tags,
103
+ parent_changed: @parent_changed,
104
+ parent_id: @parent_id,
105
+ rating: @rating,
106
+ rating_changed: @rating_changed,
107
+ reason: @reason,
108
+ removed_locked_tags: @removed_locked_tags,
109
+ removed_tags: @removed_tags,
110
+ source: @source,
111
+ source_changed: @source_changed,
112
+ tags: @tags,
113
+ updated_at: @updated_at,
114
+ updater_id: @updater_id,
115
+ version: @version,
116
+ }.to_json
117
+ end
118
+ end
119
+ end
120
+ end
@@ -3,8 +3,12 @@
3
3
  module E621ExportDownloader
4
4
  class Types < T::Enum
5
5
  enums do
6
+ Artists = new("artists")
7
+ BulkUpdateRequests = new("bulk_update_requests")
6
8
  Pools = new("pools")
7
9
  Posts = new("posts")
10
+ PostReplacements = new("post_replacements")
11
+ PostVersions = new("post_versions")
8
12
  TagAliases = new("tag_aliases")
9
13
  TagImplications = new("tag_implications")
10
14
  Tags = new("tags")
@@ -4,7 +4,7 @@
4
4
  # loaded by bundler
5
5
  module E621ExportDownloader
6
6
  module Constants
7
- VERSION = "0.0.3"
7
+ VERSION = "0.0.4"
8
8
  WEBSITE = "https://github.com/DonovanDMC/E621ExportDownloader.rb"
9
9
  end
10
10
  end
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: e621_export_downloader
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.3
4
+ version: 0.0.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - Donovan_DMC
8
8
  bindir: exe
9
9
  cert_chain: []
10
- date: 2026-05-19 00:00:00.000000000 Z
10
+ date: 2026-06-12 00:00:00.000000000 Z
11
11
  dependencies:
12
12
  - !ruby/object:Gem::Dependency
13
13
  name: csv
@@ -80,6 +80,7 @@ files:
80
80
  - Rakefile
81
81
  - exe/e621-export-downloader
82
82
  - lib/e621_export_downloader.rb
83
+ - lib/e621_export_downloader/api_export_data.rb
83
84
  - lib/e621_export_downloader/client.rb
84
85
  - lib/e621_export_downloader/client/options.rb
85
86
  - lib/e621_export_downloader/client/options/builder.rb
@@ -87,8 +88,12 @@ files:
87
88
  - lib/e621_export_downloader/constants.rb
88
89
  - lib/e621_export_downloader/export.rb
89
90
  - lib/e621_export_downloader/export_helper.rb
91
+ - lib/e621_export_downloader/models/artist.rb
92
+ - lib/e621_export_downloader/models/bulk_update_request.rb
90
93
  - lib/e621_export_downloader/models/pool.rb
91
94
  - lib/e621_export_downloader/models/post.rb
95
+ - lib/e621_export_downloader/models/post_replacement.rb
96
+ - lib/e621_export_downloader/models/post_version.rb
92
97
  - lib/e621_export_downloader/models/tag.rb
93
98
  - lib/e621_export_downloader/models/tag_alias.rb
94
99
  - lib/e621_export_downloader/models/tag_implication.rb