mirror of
https://github.com/hkalexling/Mango.git
synced 2025-08-03 03:15:31 -04:00
Break util.cr into multiple files
This commit is contained in:
parent
2208f90d8e
commit
0d11cb59e9
@ -1,10 +1,10 @@
|
|||||||
require "./spec_helper"
|
require "./spec_helper"
|
||||||
|
|
||||||
describe "compare_alphanumerically" do
|
describe "compare_numerically" do
|
||||||
it "sorts filenames with leading zeros correctly" do
|
it "sorts filenames with leading zeros correctly" do
|
||||||
ary = ["010.jpg", "001.jpg", "002.png"]
|
ary = ["010.jpg", "001.jpg", "002.png"]
|
||||||
ary.sort! { |a, b|
|
ary.sort! { |a, b|
|
||||||
compare_alphanumerically a, b
|
compare_numerically a, b
|
||||||
}
|
}
|
||||||
ary.should eq ["001.jpg", "002.png", "010.jpg"]
|
ary.should eq ["001.jpg", "002.png", "010.jpg"]
|
||||||
end
|
end
|
||||||
@ -12,7 +12,7 @@ describe "compare_alphanumerically" do
|
|||||||
it "sorts filenames without leading zeros correctly" do
|
it "sorts filenames without leading zeros correctly" do
|
||||||
ary = ["10.jpg", "1.jpg", "0.png", "0100.jpg"]
|
ary = ["10.jpg", "1.jpg", "0.png", "0100.jpg"]
|
||||||
ary.sort! { |a, b|
|
ary.sort! { |a, b|
|
||||||
compare_alphanumerically a, b
|
compare_numerically a, b
|
||||||
}
|
}
|
||||||
ary.should eq ["0.png", "1.jpg", "10.jpg", "0100.jpg"]
|
ary.should eq ["0.png", "1.jpg", "10.jpg", "0100.jpg"]
|
||||||
end
|
end
|
||||||
@ -22,7 +22,7 @@ describe "compare_alphanumerically" do
|
|||||||
ary = ["2", "12", "200000", "1000000", "a", "a12", "b2", "text2",
|
ary = ["2", "12", "200000", "1000000", "a", "a12", "b2", "text2",
|
||||||
"text2a", "text2a2", "text2a12", "text2ab", "text12", "text12a"]
|
"text2a", "text2a2", "text2a12", "text2ab", "text12", "text12a"]
|
||||||
ary.reverse.sort { |a, b|
|
ary.reverse.sort { |a, b|
|
||||||
compare_alphanumerically a, b
|
compare_numerically a, b
|
||||||
}.should eq ary
|
}.should eq ary
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -30,7 +30,7 @@ describe "compare_alphanumerically" do
|
|||||||
it "handles numbers larger than Int32" do
|
it "handles numbers larger than Int32" do
|
||||||
ary = ["14410155591588.jpg", "21410155591588.png", "104410155591588.jpg"]
|
ary = ["14410155591588.jpg", "21410155591588.png", "104410155591588.jpg"]
|
||||||
ary.reverse.sort { |a, b|
|
ary.reverse.sort { |a, b|
|
||||||
compare_alphanumerically a, b
|
compare_numerically a, b
|
||||||
}.should eq ary
|
}.should eq ary
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
require "kemal"
|
require "kemal"
|
||||||
require "../storage"
|
require "../storage"
|
||||||
require "../util"
|
require "../util/*"
|
||||||
|
|
||||||
class AuthHandler < Kemal::Handler
|
class AuthHandler < Kemal::Handler
|
||||||
# Some of the code is copied form kemalcr/kemal-basic-auth on GitHub
|
# Some of the code is copied form kemalcr/kemal-basic-auth on GitHub
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
require "baked_file_system"
|
require "baked_file_system"
|
||||||
require "kemal"
|
require "kemal"
|
||||||
require "../util"
|
require "../util/*"
|
||||||
|
|
||||||
class FS
|
class FS
|
||||||
extend BakedFileSystem
|
extend BakedFileSystem
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
require "kemal"
|
require "kemal"
|
||||||
require "../util"
|
require "../util/*"
|
||||||
|
|
||||||
class UploadHandler < Kemal::Handler
|
class UploadHandler < Kemal::Handler
|
||||||
def initialize(@upload_dir : String)
|
def initialize(@upload_dir : String)
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
require "mime"
|
require "mime"
|
||||||
require "json"
|
require "json"
|
||||||
require "uri"
|
require "uri"
|
||||||
require "./util"
|
require "./util/*"
|
||||||
require "./archive"
|
require "./archive"
|
||||||
|
|
||||||
SUPPORTED_IMG_TYPES = ["image/jpeg", "image/png", "image/webp"]
|
SUPPORTED_IMG_TYPES = ["image/jpeg", "image/png", "image/webp"]
|
||||||
@ -87,7 +87,7 @@ class Entry
|
|||||||
MIME.from_filename? e.filename
|
MIME.from_filename? e.filename
|
||||||
}
|
}
|
||||||
.sort { |a, b|
|
.sort { |a, b|
|
||||||
compare_alphanumerically a.filename, b.filename
|
compare_numerically a.filename, b.filename
|
||||||
}
|
}
|
||||||
.[page_num - 1]
|
.[page_num - 1]
|
||||||
data = file.read_entry page
|
data = file.read_entry page
|
||||||
@ -236,11 +236,11 @@ class Title
|
|||||||
@mtime = mtimes.max
|
@mtime = mtimes.max
|
||||||
|
|
||||||
@title_ids.sort! do |a, b|
|
@title_ids.sort! do |a, b|
|
||||||
compare_alphanumerically @library.title_hash[a].title,
|
compare_numerically @library.title_hash[a].title,
|
||||||
@library.title_hash[b].title
|
@library.title_hash[b].title
|
||||||
end
|
end
|
||||||
@entries.sort! do |a, b|
|
@entries.sort! do |a, b|
|
||||||
compare_alphanumerically a.title, b.title
|
compare_numerically a.title, b.title
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -2,7 +2,7 @@ require "kemal"
|
|||||||
require "kemal-session"
|
require "kemal-session"
|
||||||
require "./library"
|
require "./library"
|
||||||
require "./handlers/*"
|
require "./handlers/*"
|
||||||
require "./util"
|
require "./util/*"
|
||||||
require "./routes/*"
|
require "./routes/*"
|
||||||
|
|
||||||
class Context
|
class Context
|
||||||
|
@ -2,7 +2,7 @@ require "sqlite3"
|
|||||||
require "crypto/bcrypt"
|
require "crypto/bcrypt"
|
||||||
require "uuid"
|
require "uuid"
|
||||||
require "base64"
|
require "base64"
|
||||||
require "./util"
|
require "./util/*"
|
||||||
|
|
||||||
def hash_password(pw)
|
def hash_password(pw)
|
||||||
Crypto::Bcrypt::Password.create(pw).to_s
|
Crypto::Bcrypt::Password.create(pw).to_s
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
require "./util"
|
require "./util/*"
|
||||||
|
|
||||||
class Upload
|
class Upload
|
||||||
def initialize(@dir : String)
|
def initialize(@dir : String)
|
||||||
|
169
src/util.cr
169
src/util.cr
@ -1,169 +0,0 @@
|
|||||||
require "big"
|
|
||||||
|
|
||||||
IMGS_PER_PAGE = 5
|
|
||||||
UPLOAD_URL_PREFIX = "/uploads"
|
|
||||||
STATIC_DIRS = ["/css", "/js", "/img", "/favicon.ico"]
|
|
||||||
|
|
||||||
def requesting_static_file(env)
|
|
||||||
request_path_startswith env, STATIC_DIRS
|
|
||||||
end
|
|
||||||
|
|
||||||
macro layout(name)
|
|
||||||
base_url = Config.current.base_url
|
|
||||||
begin
|
|
||||||
is_admin = false
|
|
||||||
if token = env.session.string? "token"
|
|
||||||
is_admin = @context.storage.verify_admin token
|
|
||||||
end
|
|
||||||
page = {{name}}
|
|
||||||
render "src/views/#{{{name}}}.html.ecr", "src/views/layout.html.ecr"
|
|
||||||
rescue e
|
|
||||||
message = e.to_s
|
|
||||||
@context.error message
|
|
||||||
render "src/views/message.html.ecr", "src/views/layout.html.ecr"
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
macro send_img(env, img)
|
|
||||||
send_file {{env}}, {{img}}.data, {{img}}.mime
|
|
||||||
end
|
|
||||||
|
|
||||||
macro get_username(env)
|
|
||||||
# if the request gets here, it has gone through the auth handler, and
|
|
||||||
# we can be sure that a valid token exists, so we can use not_nil! here
|
|
||||||
token = env.session.string "token"
|
|
||||||
(@context.storage.verify_token token).not_nil!
|
|
||||||
end
|
|
||||||
|
|
||||||
def send_json(env, json)
|
|
||||||
env.response.content_type = "application/json"
|
|
||||||
env.response.print json
|
|
||||||
end
|
|
||||||
|
|
||||||
def send_attachment(env, path)
|
|
||||||
send_file env, path, filename: File.basename(path), disposition: "attachment"
|
|
||||||
end
|
|
||||||
|
|
||||||
def hash_to_query(hash)
|
|
||||||
hash.map { |k, v| "#{k}=#{v}" }.join("&")
|
|
||||||
end
|
|
||||||
|
|
||||||
def request_path_startswith(env, ary)
|
|
||||||
ary.each do |prefix|
|
|
||||||
if env.request.path.starts_with? prefix
|
|
||||||
return true
|
|
||||||
end
|
|
||||||
end
|
|
||||||
false
|
|
||||||
end
|
|
||||||
|
|
||||||
def is_numeric(str)
|
|
||||||
/^\d+/.match(str) != nil
|
|
||||||
end
|
|
||||||
|
|
||||||
def split_by_alphanumeric(str)
|
|
||||||
arr = [] of String
|
|
||||||
str.scan(/([^\d\n\r]*)(\d*)([^\d\n\r]*)/) do |match|
|
|
||||||
arr += match.captures.select { |s| s != "" }
|
|
||||||
end
|
|
||||||
arr
|
|
||||||
end
|
|
||||||
|
|
||||||
def compare_alphanumerically(c, d)
|
|
||||||
is_c_bigger = c.size <=> d.size
|
|
||||||
if c.size > d.size
|
|
||||||
d += [nil] * (c.size - d.size)
|
|
||||||
elsif c.size < d.size
|
|
||||||
c += [nil] * (d.size - c.size)
|
|
||||||
end
|
|
||||||
c.zip(d) do |a, b|
|
|
||||||
return -1 if a.nil?
|
|
||||||
return 1 if b.nil?
|
|
||||||
if is_numeric(a) && is_numeric(b)
|
|
||||||
compare = a.to_big_i <=> b.to_big_i
|
|
||||||
return compare if compare != 0
|
|
||||||
else
|
|
||||||
compare = a <=> b
|
|
||||||
return compare if compare != 0
|
|
||||||
end
|
|
||||||
end
|
|
||||||
is_c_bigger
|
|
||||||
end
|
|
||||||
|
|
||||||
def compare_alphanumerically(a : String, b : String)
|
|
||||||
compare_alphanumerically split_by_alphanumeric(a), split_by_alphanumeric(b)
|
|
||||||
end
|
|
||||||
|
|
||||||
def validate_archive(path : String) : Exception?
|
|
||||||
file = nil
|
|
||||||
begin
|
|
||||||
file = ArchiveFile.new path
|
|
||||||
file.check
|
|
||||||
file.close
|
|
||||||
return
|
|
||||||
rescue e
|
|
||||||
file.close unless file.nil?
|
|
||||||
e
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def random_str
|
|
||||||
UUID.random.to_s.gsub "-", ""
|
|
||||||
end
|
|
||||||
|
|
||||||
def redirect(env, path)
|
|
||||||
base = Config.current.base_url
|
|
||||||
env.redirect File.join base, path
|
|
||||||
end
|
|
||||||
|
|
||||||
def validate_username(username)
|
|
||||||
if username.size < 3
|
|
||||||
raise "Username should contain at least 3 characters"
|
|
||||||
end
|
|
||||||
if (username =~ /^[A-Za-z0-9_]+$/).nil?
|
|
||||||
raise "Username should contain alphanumeric characters " \
|
|
||||||
"and underscores only"
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def validate_password(password)
|
|
||||||
if password.size < 6
|
|
||||||
raise "Password should contain at least 6 characters"
|
|
||||||
end
|
|
||||||
if (password =~ /^[[:ascii:]]+$/).nil?
|
|
||||||
raise "password should contain ASCII characters only"
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
macro render_xml(path)
|
|
||||||
base_url = Config.current.base_url
|
|
||||||
send_file env, ECR.render({{path}}).to_slice, "application/xml"
|
|
||||||
end
|
|
||||||
|
|
||||||
macro render_component(filename)
|
|
||||||
render "src/views/components/#{{{filename}}}.html.ecr"
|
|
||||||
end
|
|
||||||
|
|
||||||
# Works in all Unix systems. Follows https://github.com/crystal-lang/crystal/
|
|
||||||
# blob/master/src/crystal/system/unix/file_info.cr#L42-L48
|
|
||||||
def ctime(file_path : String) : Time
|
|
||||||
res = LibC.stat(file_path, out stat)
|
|
||||||
raise "Unable to get ctime of file #{file_path}" if res != 0
|
|
||||||
|
|
||||||
{% if flag?(:darwin) %}
|
|
||||||
Time.new stat.st_ctimespec, Time::Location::UTC
|
|
||||||
{% else %}
|
|
||||||
Time.new stat.st_ctim, Time::Location::UTC
|
|
||||||
{% end %}
|
|
||||||
end
|
|
||||||
|
|
||||||
def register_mime_types
|
|
||||||
{
|
|
||||||
".zip" => "application/zip",
|
|
||||||
".rar" => "application/x-rar-compressed",
|
|
||||||
".cbz" => "application/vnd.comicbook+zip",
|
|
||||||
".cbr" => "application/vnd.comicbook-rar",
|
|
||||||
}.each do |k, v|
|
|
||||||
MIME.register k, v
|
|
||||||
end
|
|
||||||
end
|
|
40
src/util/numeric_sort.cr
Normal file
40
src/util/numeric_sort.cr
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
# Properly sort alphanumeric strings
|
||||||
|
# Used to sort the images files inside the archives
|
||||||
|
# https://github.com/hkalexling/Mango/issues/12
|
||||||
|
|
||||||
|
def is_numeric(str)
|
||||||
|
/^\d+/.match(str) != nil
|
||||||
|
end
|
||||||
|
|
||||||
|
def split_by_alphanumeric(str)
|
||||||
|
arr = [] of String
|
||||||
|
str.scan(/([^\d\n\r]*)(\d*)([^\d\n\r]*)/) do |match|
|
||||||
|
arr += match.captures.select { |s| s != "" }
|
||||||
|
end
|
||||||
|
arr
|
||||||
|
end
|
||||||
|
|
||||||
|
def compare_numerically(c, d)
|
||||||
|
is_c_bigger = c.size <=> d.size
|
||||||
|
if c.size > d.size
|
||||||
|
d += [nil] * (c.size - d.size)
|
||||||
|
elsif c.size < d.size
|
||||||
|
c += [nil] * (d.size - c.size)
|
||||||
|
end
|
||||||
|
c.zip(d) do |a, b|
|
||||||
|
return -1 if a.nil?
|
||||||
|
return 1 if b.nil?
|
||||||
|
if is_numeric(a) && is_numeric(b)
|
||||||
|
compare = a.to_big_i <=> b.to_big_i
|
||||||
|
return compare if compare != 0
|
||||||
|
else
|
||||||
|
compare = a <=> b
|
||||||
|
return compare if compare != 0
|
||||||
|
end
|
||||||
|
end
|
||||||
|
is_c_bigger
|
||||||
|
end
|
||||||
|
|
||||||
|
def compare_numerically(a : String, b : String)
|
||||||
|
compare_numerically split_by_alphanumeric(a), split_by_alphanumeric(b)
|
||||||
|
end
|
33
src/util/util.cr
Normal file
33
src/util/util.cr
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
require "big"
|
||||||
|
|
||||||
|
IMGS_PER_PAGE = 5
|
||||||
|
UPLOAD_URL_PREFIX = "/uploads"
|
||||||
|
STATIC_DIRS = ["/css", "/js", "/img", "/favicon.ico"]
|
||||||
|
|
||||||
|
def random_str
|
||||||
|
UUID.random.to_s.gsub "-", ""
|
||||||
|
end
|
||||||
|
|
||||||
|
# Works in all Unix systems. Follows https://github.com/crystal-lang/crystal/
|
||||||
|
# blob/master/src/crystal/system/unix/file_info.cr#L42-L48
|
||||||
|
def ctime(file_path : String) : Time
|
||||||
|
res = LibC.stat(file_path, out stat)
|
||||||
|
raise "Unable to get ctime of file #{file_path}" if res != 0
|
||||||
|
|
||||||
|
{% if flag?(:darwin) %}
|
||||||
|
Time.new stat.st_ctimespec, Time::Location::UTC
|
||||||
|
{% else %}
|
||||||
|
Time.new stat.st_ctim, Time::Location::UTC
|
||||||
|
{% end %}
|
||||||
|
end
|
||||||
|
|
||||||
|
def register_mime_types
|
||||||
|
{
|
||||||
|
".zip" => "application/zip",
|
||||||
|
".rar" => "application/x-rar-compressed",
|
||||||
|
".cbz" => "application/vnd.comicbook+zip",
|
||||||
|
".cbr" => "application/vnd.comicbook-rar",
|
||||||
|
}.each do |k, v|
|
||||||
|
MIME.register k, v
|
||||||
|
end
|
||||||
|
end
|
31
src/util/validation.cr
Normal file
31
src/util/validation.cr
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
def validate_username(username)
|
||||||
|
if username.size < 3
|
||||||
|
raise "Username should contain at least 3 characters"
|
||||||
|
end
|
||||||
|
if (username =~ /^[A-Za-z0-9_]+$/).nil?
|
||||||
|
raise "Username should contain alphanumeric characters " \
|
||||||
|
"and underscores only"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def validate_password(password)
|
||||||
|
if password.size < 6
|
||||||
|
raise "Password should contain at least 6 characters"
|
||||||
|
end
|
||||||
|
if (password =~ /^[[:ascii:]]+$/).nil?
|
||||||
|
raise "password should contain ASCII characters only"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def validate_archive(path : String) : Exception?
|
||||||
|
file = nil
|
||||||
|
begin
|
||||||
|
file = ArchiveFile.new path
|
||||||
|
file.check
|
||||||
|
file.close
|
||||||
|
return
|
||||||
|
rescue e
|
||||||
|
file.close unless file.nil?
|
||||||
|
e
|
||||||
|
end
|
||||||
|
end
|
68
src/util/web.cr
Normal file
68
src/util/web.cr
Normal file
@ -0,0 +1,68 @@
|
|||||||
|
# Web related helper functions/macros
|
||||||
|
|
||||||
|
macro layout(name)
|
||||||
|
base_url = Config.current.base_url
|
||||||
|
begin
|
||||||
|
is_admin = false
|
||||||
|
if token = env.session.string? "token"
|
||||||
|
is_admin = @context.storage.verify_admin token
|
||||||
|
end
|
||||||
|
page = {{name}}
|
||||||
|
render "src/views/#{{{name}}}.html.ecr", "src/views/layout.html.ecr"
|
||||||
|
rescue e
|
||||||
|
message = e.to_s
|
||||||
|
@context.error message
|
||||||
|
render "src/views/message.html.ecr", "src/views/layout.html.ecr"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
macro send_img(env, img)
|
||||||
|
send_file {{env}}, {{img}}.data, {{img}}.mime
|
||||||
|
end
|
||||||
|
|
||||||
|
macro get_username(env)
|
||||||
|
# if the request gets here, it has gone through the auth handler, and
|
||||||
|
# we can be sure that a valid token exists, so we can use not_nil! here
|
||||||
|
token = env.session.string "token"
|
||||||
|
(@context.storage.verify_token token).not_nil!
|
||||||
|
end
|
||||||
|
|
||||||
|
def send_json(env, json)
|
||||||
|
env.response.content_type = "application/json"
|
||||||
|
env.response.print json
|
||||||
|
end
|
||||||
|
|
||||||
|
def send_attachment(env, path)
|
||||||
|
send_file env, path, filename: File.basename(path), disposition: "attachment"
|
||||||
|
end
|
||||||
|
|
||||||
|
def redirect(env, path)
|
||||||
|
base = Config.current.base_url
|
||||||
|
env.redirect File.join base, path
|
||||||
|
end
|
||||||
|
|
||||||
|
def hash_to_query(hash)
|
||||||
|
hash.map { |k, v| "#{k}=#{v}" }.join("&")
|
||||||
|
end
|
||||||
|
|
||||||
|
def request_path_startswith(env, ary)
|
||||||
|
ary.each do |prefix|
|
||||||
|
if env.request.path.starts_with? prefix
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
false
|
||||||
|
end
|
||||||
|
|
||||||
|
def requesting_static_file(env)
|
||||||
|
request_path_startswith env, STATIC_DIRS
|
||||||
|
end
|
||||||
|
|
||||||
|
macro render_xml(path)
|
||||||
|
base_url = Config.current.base_url
|
||||||
|
send_file env, ECR.render({{path}}).to_slice, "application/xml"
|
||||||
|
end
|
||||||
|
|
||||||
|
macro render_component(filename)
|
||||||
|
render "src/views/components/#{{{filename}}}.html.ecr"
|
||||||
|
end
|
Loading…
x
Reference in New Issue
Block a user