diff --git a/app/logical/danbooru.rb b/app/logical/danbooru.rb new file mode 100644 index 000000000..f312afdcd --- /dev/null +++ b/app/logical/danbooru.rb @@ -0,0 +1,32 @@ +# frozen_string_literal: true + +# The Danbooru module contains miscellaneous global helper functions. +module Danbooru + module EnumerableMethods + extend self + + # Sort a list of strings in natural order, e.g. with "file-2.txt" before "file-10.txt". + # + # @see https://en.wikipedia.org/wiki/Natural_sort_order + # @see https://stackoverflow.com/a/15170063 + # + # @param list [Enumerable] The list to sort. + # @return [Array] The sorted list. + def natural_sort(list) + natural_sort_by(list, &:to_s) + end + + # Sort a list of objects in natural order. The block should return a sort key, which is compared in natural order. + # + # @param list [Enumerable] The list to sort. + # @return [Array] The sorted list. + def natural_sort_by(list, &block) + list.sort_by do |element| + # "file-2022-10-01.txt" => ["file-", 2022, "-", 10, "-", 1, ".txt"] + yield(element).to_s.split(/(\d+)/).map { |str| str.match?(/\A\d+\z/) ? str.to_i : str } + end + end + end + + extend EnumerableMethods +end diff --git a/app/models/upload.rb b/app/models/upload.rb index f835ebdbf..d951bfcd3 100644 --- a/app/models/upload.rb +++ b/app/models/upload.rb @@ -206,7 +206,7 @@ class Upload < ApplicationRecord tmpdir, filenames = file.extract! tmpdirs << tmpdir - filenames.map do |filename| + Danbooru.natural_sort(filenames).map do |filename| name = "file://#{original_filename}/#{Pathname.new(filename).relative_path_from(tmpdir)}" # "file://foo.zip/foo/1.jpg" UploadMediaAsset.new(upload: self, file: filename, source_url: name) end diff --git a/config/initializers/core_extensions.rb b/config/initializers/core_extensions.rb index c98797b73..1dbf8c990 100644 --- a/config/initializers/core_extensions.rb +++ b/config/initializers/core_extensions.rb @@ -1,5 +1,7 @@ # frozen_string_literal: true +require "danbooru" + module Danbooru module Extensions module String diff --git a/test/files/archive/out-of-order.zip b/test/files/archive/out-of-order.zip new file mode 100644 index 000000000..b5d162673 Binary files /dev/null and b/test/files/archive/out-of-order.zip differ diff --git a/test/functional/uploads_controller_test.rb b/test/functional/uploads_controller_test.rb index 7c27de676..85b27eb8e 100644 --- a/test/functional/uploads_controller_test.rb +++ b/test/functional/uploads_controller_test.rb @@ -445,6 +445,19 @@ class UploadsControllerTest < ActionDispatch::IntegrationTest assert_equal(5, upload.upload_media_assets.size) assert_equal("file://ugoira.zip/000000.jpg", upload.upload_media_assets[0].source_url) end + + should "upload the files in filename order" do + upload = assert_successful_upload("test/files/archive/out-of-order.zip", user: @user) + + assert_equal(6, upload.media_asset_count) + assert_equal(6, upload.upload_media_assets.size) + assert_equal("file://out-of-order.zip/9/9.gif", upload.upload_media_assets[0].source_url) + assert_equal("file://out-of-order.zip/9/10.gif", upload.upload_media_assets[1].source_url) + assert_equal("file://out-of-order.zip/9/11.gif", upload.upload_media_assets[2].source_url) + assert_equal("file://out-of-order.zip/10/9.gif", upload.upload_media_assets[3].source_url) + assert_equal("file://out-of-order.zip/10/10.gif", upload.upload_media_assets[4].source_url) + assert_equal("file://out-of-order.zip/10/11.gif", upload.upload_media_assets[5].source_url) + end end context "uploading a .rar file from your computer" do