mirror of
https://github.com/hkalexling/Mango.git
synced 2026-01-24 00:03:14 -05:00
Compare commits
24 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ef1ab940f5 | ||
|
|
97a1c408d8 | ||
|
|
abbf77df13 | ||
|
|
3b4021f680 | ||
|
|
68b1923cb6 | ||
|
|
3cdd4b29a5 | ||
|
|
af84c0f6de | ||
|
|
85a65f84d0 | ||
|
|
5027a911cd | ||
|
|
ac63bf7599 | ||
|
|
30b0e0b8fb | ||
|
|
ddda058d8d | ||
|
|
46db25e8e0 | ||
|
|
c07f421322 | ||
|
|
99a77966ad | ||
|
|
d00b917575 | ||
|
|
4fd8334c37 | ||
|
|
3aa4630558 | ||
|
|
cde5af7066 | ||
|
|
eb528e1726 | ||
|
|
5e01cc38fe | ||
|
|
9a787ccbc3 | ||
|
|
8a83c0df4e | ||
|
|
87dea01917 |
13
.github/workflows/build.yml
vendored
13
.github/workflows/build.yml
vendored
@@ -19,13 +19,22 @@ jobs:
|
||||
- name: Install dependencies
|
||||
run: apk add --no-cache yarn yaml sqlite-static libarchive-dev libarchive-static acl-static expat-static zstd-static lz4-static bzip2-static
|
||||
- name: Build
|
||||
run: make static
|
||||
run: make static || make static
|
||||
- name: Linter
|
||||
run: make check
|
||||
- name: Run tests
|
||||
run: make test
|
||||
- name: Upload artifact
|
||||
- name: Upload binary
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: mango
|
||||
path: mango
|
||||
- name: build arm32v7 object file
|
||||
run: make arm32v7 || make arm32v7
|
||||
- name: build arm64v8 object file
|
||||
run: make arm64v8 || make arm64v8
|
||||
- name: Upload object files
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: object files
|
||||
path: ./*.o
|
||||
|
||||
13
Dockerfile.arm32v7
Normal file
13
Dockerfile.arm32v7
Normal file
@@ -0,0 +1,13 @@
|
||||
FROM arm32v7/ubuntu:18.04
|
||||
|
||||
RUN apt-get update && apt-get install -y wget git make llvm-8 llvm-8-dev g++ libsqlite3-dev libyaml-dev libgc-dev libssl-dev libcrypto++-dev libevent-dev libgmp-dev zlib1g-dev libpcre++-dev pkg-config libarchive-dev libxml2-dev libacl1-dev nettle-dev liblzo2-dev liblzma-dev libbz2-dev
|
||||
|
||||
RUN git clone https://github.com/crystal-lang/crystal && cd crystal && git checkout 0.34.0 && make deps && cd ..
|
||||
RUN git clone https://github.com/kostya/myhtml && cd myhtml/src/ext && git checkout v1.5.0 && make && cd ..
|
||||
RUN git clone https://github.com/jessedoyle/duktape.cr && cd duktape.cr/ext && git checkout v0.20.0 && make && cd ..
|
||||
|
||||
COPY mango-arm32v7.o .
|
||||
|
||||
RUN cc 'mango-arm32v7.o' -o 'mango' -rdynamic -lxml2 /myhtml/src/ext/modest-c/lib/libmodest_static.a -L/duktape.cr/src/.build/lib -L/duktape.cr/src/.build/include -lduktape -lm `pkg-config libarchive --libs` -lz `command -v pkg-config > /dev/null && pkg-config --libs --silence-errors libssl || printf %s '-lssl -lcrypto'` `command -v pkg-config > /dev/null && pkg-config --libs --silence-errors libcrypto || printf %s '-lcrypto'` -lgmp -lsqlite3 -lyaml -lpcre -lm /usr/lib/arm-linux-gnueabihf/libgc.so -lpthread /crystal/src/ext/libcrystal.a -levent -lrt -ldl -L/usr/bin/../lib/crystal/lib -L/usr/bin/../lib/crystal/lib
|
||||
|
||||
CMD ["./mango"]
|
||||
13
Dockerfile.arm64v8
Normal file
13
Dockerfile.arm64v8
Normal file
@@ -0,0 +1,13 @@
|
||||
FROM arm64v8/ubuntu:18.04
|
||||
|
||||
RUN apt-get update && apt-get install -y wget git make llvm-8 llvm-8-dev g++ libsqlite3-dev libyaml-dev libgc-dev libssl-dev libcrypto++-dev libevent-dev libgmp-dev zlib1g-dev libpcre++-dev pkg-config libarchive-dev libxml2-dev libacl1-dev nettle-dev liblzo2-dev liblzma-dev libbz2-dev
|
||||
|
||||
RUN git clone https://github.com/crystal-lang/crystal && cd crystal && git checkout 0.34.0 && make deps && cd ..
|
||||
RUN git clone https://github.com/kostya/myhtml && cd myhtml/src/ext && git checkout v1.5.0 && make && cd ..
|
||||
RUN git clone https://github.com/jessedoyle/duktape.cr && cd duktape.cr/ext && git checkout v0.20.0 && make && cd ..
|
||||
|
||||
COPY mango-arm64v8.o .
|
||||
|
||||
RUN cc 'mango-arm64v8.o' -o 'mango' -rdynamic -lxml2 /myhtml/src/ext/modest-c/lib/libmodest_static.a -L/duktape.cr/src/.build/lib -L/duktape.cr/src/.build/include -lduktape -lm `pkg-config libarchive --libs` -lz `command -v pkg-config > /dev/null && pkg-config --libs --silence-errors libssl || printf %s '-lssl -lcrypto'` `command -v pkg-config > /dev/null && pkg-config --libs --silence-errors libcrypto || printf %s '-lcrypto'` -lgmp -lsqlite3 -lyaml -lpcre -lm /usr/lib/aarch64-linux-gnu/libgc.so -lpthread /crystal/src/ext/libcrystal.a -levent -lrt -ldl -L/usr/bin/../lib/crystal/lib -L/usr/bin/../lib/crystal/lib
|
||||
|
||||
CMD ["./mango"]
|
||||
10
Makefile
10
Makefile
@@ -12,10 +12,10 @@ setup: libs
|
||||
yarn gulp dev
|
||||
|
||||
build: libs
|
||||
crystal build src/mango.cr --release --progress
|
||||
crystal build src/mango.cr --release --progress --error-trace
|
||||
|
||||
static: uglify | libs
|
||||
crystal build src/mango.cr --release --progress --static
|
||||
crystal build src/mango.cr --release --progress --static --error-trace
|
||||
|
||||
libs:
|
||||
shards install --production
|
||||
@@ -31,6 +31,12 @@ check:
|
||||
./bin/ameba
|
||||
./dev/linewidth.sh
|
||||
|
||||
arm32v7:
|
||||
crystal build src/mango.cr --release --progress --error-trace --cross-compile --target='arm-linux-gnueabihf' -o mango-arm32v7
|
||||
|
||||
arm64v8:
|
||||
crystal build src/mango.cr --release --progress --error-trace --cross-compile --target='aarch64-linux-gnu' -o mango-arm64v8
|
||||
|
||||
install:
|
||||
cp mango $(INSTALL_DIR)/mango
|
||||
|
||||
|
||||
@@ -51,7 +51,7 @@ The official docker images are available on [Dockerhub](https://hub.docker.com/r
|
||||
### CLI
|
||||
|
||||
```
|
||||
Mango - Manga Server and Web Reader. Version 0.10.0
|
||||
Mango - Manga Server and Web Reader. Version 0.11.0
|
||||
|
||||
Usage:
|
||||
|
||||
@@ -139,8 +139,12 @@ Mobile UI:
|
||||
|
||||

|
||||
|
||||
## Sponsors
|
||||
|
||||
<a href="https://casinoshunter.com/online-casinos/"><img src="https://i.imgur.com/EJb3wBo.png" width="150" height="auto"></a>
|
||||
|
||||
## Contributors
|
||||
|
||||
Please check the [development guideline](https://github.com/hkalexling/Mango/wiki/Development) if you are interest in code contributions.
|
||||
Please check the [development guideline](https://github.com/hkalexling/Mango/wiki/Development) if you are interested in code contributions.
|
||||
|
||||
[](https://sourcerer.io/fame/hkalexling/hkalexling/Mango/links/0)[](https://sourcerer.io/fame/hkalexling/hkalexling/Mango/links/1)[](https://sourcerer.io/fame/hkalexling/hkalexling/Mango/links/2)[](https://sourcerer.io/fame/hkalexling/hkalexling/Mango/links/3)[](https://sourcerer.io/fame/hkalexling/hkalexling/Mango/links/4)[](https://sourcerer.io/fame/hkalexling/hkalexling/Mango/links/5)[](https://sourcerer.io/fame/hkalexling/hkalexling/Mango/links/6)[](https://sourcerer.io/fame/hkalexling/hkalexling/Mango/links/7)
|
||||
|
||||
@@ -135,3 +135,20 @@ td>.uk-dropdown {
|
||||
#select-bar-controls a:hover {
|
||||
color: orange;
|
||||
}
|
||||
|
||||
#main-section {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
#totop-wrapper {
|
||||
position: absolute;
|
||||
top: 100vh;
|
||||
right: 2em;
|
||||
bottom: 0;
|
||||
}
|
||||
|
||||
#totop-wrapper a {
|
||||
position: fixed;
|
||||
position: sticky;
|
||||
top: calc(100vh - 5em);
|
||||
}
|
||||
|
||||
@@ -28,6 +28,10 @@ shards:
|
||||
github: crystal-loot/exception_page
|
||||
version: 0.1.4
|
||||
|
||||
http_proxy:
|
||||
github: mamantoha/http_proxy
|
||||
version: 0.7.1
|
||||
|
||||
kemal:
|
||||
github: kemalcr/kemal
|
||||
version: 0.26.1
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
name: mango
|
||||
version: 0.10.0
|
||||
version: 0.11.0
|
||||
|
||||
authors:
|
||||
- Alex Ling <hkalexling@gmail.com>
|
||||
@@ -32,3 +32,5 @@ dependencies:
|
||||
version: ~> 0.20.0
|
||||
myhtml:
|
||||
github: kostya/myhtml
|
||||
http_proxy:
|
||||
github: mamantoha/http_proxy
|
||||
|
||||
@@ -2,6 +2,7 @@ require "spec"
|
||||
require "../src/queue"
|
||||
require "../src/server"
|
||||
require "../src/config"
|
||||
require "../src/main_fiber"
|
||||
|
||||
class State
|
||||
@@hash = {} of String => String
|
||||
|
||||
@@ -52,12 +52,8 @@ class Config
|
||||
config.fill_defaults
|
||||
return config
|
||||
end
|
||||
puts "The config file #{cfg_path} does not exist." \
|
||||
" Do you want mango to dump the default config there? [Y/n]"
|
||||
input = gets
|
||||
if input && input.downcase == "n"
|
||||
abort "Aborting..."
|
||||
end
|
||||
puts "The config file #{cfg_path} does not exist. " \
|
||||
"Dumping the default config there."
|
||||
default = self.allocate
|
||||
default.path = path
|
||||
default.fill_defaults
|
||||
|
||||
@@ -92,7 +92,6 @@ class Library
|
||||
"Attempting to create it"
|
||||
Dir.mkdir_p @dir
|
||||
end
|
||||
@title_ids.clear
|
||||
|
||||
storage = Storage.new auto_close: false
|
||||
|
||||
@@ -103,6 +102,7 @@ class Library
|
||||
.map { |path| Title.new path, "", storage, self }
|
||||
.select { |title| !(title.entries.empty? && title.titles.empty?) }
|
||||
.sort { |a, b| a.title <=> b.title }
|
||||
.tap { |_| @title_ids.clear }
|
||||
.each do |title|
|
||||
@title_hash[title.id] = title
|
||||
@title_ids << title.id
|
||||
|
||||
34
src/main_fiber.cr
Normal file
34
src/main_fiber.cr
Normal file
@@ -0,0 +1,34 @@
|
||||
# On ARM, connecting to the SQLite DB from a spawned fiber would crash
|
||||
# https://github.com/crystal-lang/crystal-sqlite3/issues/30
|
||||
# This is a temporary workaround that forces the relevant code to run in the
|
||||
# main fiber
|
||||
|
||||
class MainFiber
|
||||
@@channel = Channel(-> Nil).new
|
||||
@@done = Channel(Bool).new
|
||||
@@main_fiber = Fiber.current
|
||||
|
||||
def self.start_and_block
|
||||
loop do
|
||||
if proc = @@channel.receive
|
||||
begin
|
||||
proc.call
|
||||
ensure
|
||||
@@done.send true
|
||||
end
|
||||
end
|
||||
Fiber.yield
|
||||
end
|
||||
end
|
||||
|
||||
def self.run(&block : -> Nil)
|
||||
if @@main_fiber == Fiber.current
|
||||
block.call
|
||||
else
|
||||
@@channel.send block
|
||||
until @@done.receive
|
||||
Fiber.yield
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -1,4 +1,3 @@
|
||||
require "http/client"
|
||||
require "json"
|
||||
require "csv"
|
||||
require "../rename"
|
||||
|
||||
@@ -27,14 +27,16 @@ module MangaDex
|
||||
|
||||
def pop : Queue::Job?
|
||||
job = nil
|
||||
DB.open "sqlite3://#{@queue.path}" do |db|
|
||||
begin
|
||||
db.query_one "select * from queue where id not like '%-%' " \
|
||||
"and (status = 0 or status = 1) " \
|
||||
"order by time limit 1" do |res|
|
||||
job = Queue::Job.from_query_result res
|
||||
MainFiber.run do
|
||||
DB.open "sqlite3://#{@queue.path}" do |db|
|
||||
begin
|
||||
db.query_one "select * from queue where id not like '%-%' " \
|
||||
"and (status = 0 or status = 1) " \
|
||||
"order by time limit 1" do |res|
|
||||
job = Queue::Job.from_query_result res
|
||||
end
|
||||
rescue
|
||||
end
|
||||
rescue
|
||||
end
|
||||
end
|
||||
job
|
||||
|
||||
37
src/mango.cr
37
src/mango.cr
@@ -1,12 +1,29 @@
|
||||
require "./config"
|
||||
require "./queue"
|
||||
require "./server"
|
||||
require "./main_fiber"
|
||||
require "./mangadex/*"
|
||||
require "option_parser"
|
||||
require "clim"
|
||||
require "./plugin/*"
|
||||
|
||||
MANGO_VERSION = "0.10.0"
|
||||
MANGO_VERSION = "0.11.0"
|
||||
|
||||
# From http://www.network-science.de/ascii/
|
||||
BANNER = %{
|
||||
|
||||
_| _|
|
||||
_|_| _|_| _|_|_| _|_|_| _|_|_| _|_|
|
||||
_| _| _| _| _| _| _| _| _| _| _|
|
||||
_| _| _| _| _| _| _| _| _| _|
|
||||
_| _| _|_|_| _| _| _|_|_| _|_|
|
||||
_|
|
||||
_|_|
|
||||
|
||||
|
||||
}
|
||||
|
||||
DESCRIPTION = "Mango - Manga Server and Web Reader. Version #{MANGO_VERSION}"
|
||||
|
||||
macro common_option
|
||||
option "-c PATH", "--config=PATH", type: String,
|
||||
@@ -22,20 +39,28 @@ end
|
||||
|
||||
class CLI < Clim
|
||||
main do
|
||||
desc "Mango - Manga Server and Web Reader. Version #{MANGO_VERSION}"
|
||||
desc DESCRIPTION
|
||||
usage "mango [sub_command] [options]"
|
||||
help short: "-h"
|
||||
version "Version #{MANGO_VERSION}", short: "-v"
|
||||
common_option
|
||||
run do |opts|
|
||||
puts BANNER
|
||||
puts DESCRIPTION
|
||||
puts
|
||||
|
||||
# empty ARGV so it won't be passed to Kemal
|
||||
ARGV.clear
|
||||
|
||||
Config.load(opts.config).set_current
|
||||
MangaDex::Downloader.default
|
||||
Plugin::Downloader.default
|
||||
|
||||
# empty ARGV so it won't be passed to Kemal
|
||||
ARGV.clear
|
||||
server = Server.new
|
||||
server.start
|
||||
spawn do
|
||||
Server.new.start
|
||||
end
|
||||
|
||||
MainFiber.start_and_block
|
||||
end
|
||||
|
||||
sub "admin" do
|
||||
|
||||
@@ -8,14 +8,16 @@ class Plugin
|
||||
|
||||
def pop : Queue::Job?
|
||||
job = nil
|
||||
DB.open "sqlite3://#{@queue.path}" do |db|
|
||||
begin
|
||||
db.query_one "select * from queue where id like '%-%' " \
|
||||
"and (status = 0 or status = 1) " \
|
||||
"order by time limit 1" do |res|
|
||||
job = Queue::Job.from_query_result res
|
||||
MainFiber.run do
|
||||
DB.open "sqlite3://#{@queue.path}" do |db|
|
||||
begin
|
||||
db.query_one "select * from queue where id like '%-%' " \
|
||||
"and (status = 0 or status = 1) " \
|
||||
"order by time limit 1" do |res|
|
||||
job = Queue::Job.from_query_result res
|
||||
end
|
||||
rescue
|
||||
end
|
||||
rescue
|
||||
end
|
||||
end
|
||||
job
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
require "duktape/runtime"
|
||||
require "myhtml"
|
||||
require "http"
|
||||
require "xml"
|
||||
|
||||
class Plugin
|
||||
|
||||
146
src/queue.cr
146
src/queue.cr
@@ -119,22 +119,24 @@ class Queue
|
||||
"Attepmting to create it"
|
||||
Dir.mkdir_p dir
|
||||
end
|
||||
DB.open "sqlite3://#{@path}" do |db|
|
||||
begin
|
||||
db.exec "create table if not exists queue " \
|
||||
"(id text, manga_id text, title text, manga_title " \
|
||||
"text, status integer, status_message text, " \
|
||||
"pages integer, success_count integer, " \
|
||||
"fail_count integer, time integer)"
|
||||
db.exec "create unique index if not exists id_idx " \
|
||||
"on queue (id)"
|
||||
db.exec "create index if not exists manga_id_idx " \
|
||||
"on queue (manga_id)"
|
||||
db.exec "create index if not exists status_idx " \
|
||||
"on queue (status)"
|
||||
rescue e
|
||||
Logger.error "Error when checking tables in DB: #{e}"
|
||||
raise e
|
||||
MainFiber.run do
|
||||
DB.open "sqlite3://#{@path}" do |db|
|
||||
begin
|
||||
db.exec "create table if not exists queue " \
|
||||
"(id text, manga_id text, title text, manga_title " \
|
||||
"text, status integer, status_message text, " \
|
||||
"pages integer, success_count integer, " \
|
||||
"fail_count integer, time integer)"
|
||||
db.exec "create unique index if not exists id_idx " \
|
||||
"on queue (id)"
|
||||
db.exec "create index if not exists manga_id_idx " \
|
||||
"on queue (manga_id)"
|
||||
db.exec "create index if not exists status_idx " \
|
||||
"on queue (status)"
|
||||
rescue e
|
||||
Logger.error "Error when checking tables in DB: #{e}"
|
||||
raise e
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -143,23 +145,27 @@ class Queue
|
||||
# inserted. Any job already exists in the queue will be ignored.
|
||||
def push(jobs : Array(Job))
|
||||
start_count = self.count
|
||||
DB.open "sqlite3://#{@path}" do |db|
|
||||
jobs.each do |job|
|
||||
db.exec "insert or ignore into queue values " \
|
||||
"(?, ?, ?, ?, ?, ?, ?, ?, ?, ?)",
|
||||
job.id, job.manga_id, job.title, job.manga_title,
|
||||
job.status.to_i, job.status_message, job.pages,
|
||||
job.success_count, job.fail_count, job.time.to_unix_ms
|
||||
MainFiber.run do
|
||||
DB.open "sqlite3://#{@path}" do |db|
|
||||
jobs.each do |job|
|
||||
db.exec "insert or ignore into queue values " \
|
||||
"(?, ?, ?, ?, ?, ?, ?, ?, ?, ?)",
|
||||
job.id, job.manga_id, job.title, job.manga_title,
|
||||
job.status.to_i, job.status_message, job.pages,
|
||||
job.success_count, job.fail_count, job.time.to_unix_ms
|
||||
end
|
||||
end
|
||||
end
|
||||
self.count - start_count
|
||||
end
|
||||
|
||||
def reset(id : String)
|
||||
DB.open "sqlite3://#{@path}" do |db|
|
||||
db.exec "update queue set status = 0, status_message = '', " \
|
||||
"pages = 0, success_count = 0, fail_count = 0 " \
|
||||
"where id = (?)", id
|
||||
MainFiber.run do
|
||||
DB.open "sqlite3://#{@path}" do |db|
|
||||
db.exec "update queue set status = 0, status_message = '', " \
|
||||
"pages = 0, success_count = 0, fail_count = 0 " \
|
||||
"where id = (?)", id
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -169,16 +175,20 @@ class Queue
|
||||
|
||||
# Reset all failed tasks (missing pages and error)
|
||||
def reset
|
||||
DB.open "sqlite3://#{@path}" do |db|
|
||||
db.exec "update queue set status = 0, status_message = '', " \
|
||||
"pages = 0, success_count = 0, fail_count = 0 " \
|
||||
"where status = 2 or status = 4"
|
||||
MainFiber.run do
|
||||
DB.open "sqlite3://#{@path}" do |db|
|
||||
db.exec "update queue set status = 0, status_message = '', " \
|
||||
"pages = 0, success_count = 0, fail_count = 0 " \
|
||||
"where status = 2 or status = 4"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def delete(id : String)
|
||||
DB.open "sqlite3://#{@path}" do |db|
|
||||
db.exec "delete from queue where id = (?)", id
|
||||
MainFiber.run do
|
||||
DB.open "sqlite3://#{@path}" do |db|
|
||||
db.exec "delete from queue where id = (?)", id
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -187,71 +197,89 @@ class Queue
|
||||
end
|
||||
|
||||
def delete_status(status : JobStatus)
|
||||
DB.open "sqlite3://#{@path}" do |db|
|
||||
db.exec "delete from queue where status = (?)", status.to_i
|
||||
MainFiber.run do
|
||||
DB.open "sqlite3://#{@path}" do |db|
|
||||
db.exec "delete from queue where status = (?)", status.to_i
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def count_status(status : JobStatus)
|
||||
num = 0
|
||||
DB.open "sqlite3://#{@path}" do |db|
|
||||
num = db.query_one "select count(*) from queue where " \
|
||||
"status = (?)", status.to_i, as: Int32
|
||||
MainFiber.run do
|
||||
DB.open "sqlite3://#{@path}" do |db|
|
||||
num = db.query_one "select count(*) from queue where " \
|
||||
"status = (?)", status.to_i, as: Int32
|
||||
end
|
||||
end
|
||||
num
|
||||
end
|
||||
|
||||
def count
|
||||
num = 0
|
||||
DB.open "sqlite3://#{@path}" do |db|
|
||||
num = db.query_one "select count(*) from queue", as: Int32
|
||||
MainFiber.run do
|
||||
DB.open "sqlite3://#{@path}" do |db|
|
||||
num = db.query_one "select count(*) from queue", as: Int32
|
||||
end
|
||||
end
|
||||
num
|
||||
end
|
||||
|
||||
def set_status(status : JobStatus, job : Job)
|
||||
DB.open "sqlite3://#{@path}" do |db|
|
||||
db.exec "update queue set status = (?) where id = (?)",
|
||||
status.to_i, job.id
|
||||
MainFiber.run do
|
||||
DB.open "sqlite3://#{@path}" do |db|
|
||||
db.exec "update queue set status = (?) where id = (?)",
|
||||
status.to_i, job.id
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def get_all
|
||||
jobs = [] of Job
|
||||
DB.open "sqlite3://#{@path}" do |db|
|
||||
jobs = db.query_all "select * from queue order by time" do |rs|
|
||||
Job.from_query_result rs
|
||||
MainFiber.run do
|
||||
DB.open "sqlite3://#{@path}" do |db|
|
||||
jobs = db.query_all "select * from queue order by time" do |rs|
|
||||
Job.from_query_result rs
|
||||
end
|
||||
end
|
||||
end
|
||||
jobs
|
||||
end
|
||||
|
||||
def add_success(job : Job)
|
||||
DB.open "sqlite3://#{@path}" do |db|
|
||||
db.exec "update queue set success_count = success_count + 1 " \
|
||||
"where id = (?)", job.id
|
||||
MainFiber.run do
|
||||
DB.open "sqlite3://#{@path}" do |db|
|
||||
db.exec "update queue set success_count = success_count + 1 " \
|
||||
"where id = (?)", job.id
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def add_fail(job : Job)
|
||||
DB.open "sqlite3://#{@path}" do |db|
|
||||
db.exec "update queue set fail_count = fail_count + 1 " \
|
||||
"where id = (?)", job.id
|
||||
MainFiber.run do
|
||||
DB.open "sqlite3://#{@path}" do |db|
|
||||
db.exec "update queue set fail_count = fail_count + 1 " \
|
||||
"where id = (?)", job.id
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def set_pages(pages : Int32, job : Job)
|
||||
DB.open "sqlite3://#{@path}" do |db|
|
||||
db.exec "update queue set pages = (?), success_count = 0, " \
|
||||
"fail_count = 0 where id = (?)", pages, job.id
|
||||
MainFiber.run do
|
||||
DB.open "sqlite3://#{@path}" do |db|
|
||||
db.exec "update queue set pages = (?), success_count = 0, " \
|
||||
"fail_count = 0 where id = (?)", pages, job.id
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def add_message(msg : String, job : Job)
|
||||
DB.open "sqlite3://#{@path}" do |db|
|
||||
db.exec "update queue set status_message = " \
|
||||
"status_message || (?) || (?) where id = (?)",
|
||||
"\n", msg, job.id
|
||||
MainFiber.run do
|
||||
DB.open "sqlite3://#{@path}" do |db|
|
||||
db.exec "update queue set status_message = " \
|
||||
"status_message || (?) || (?) where id = (?)",
|
||||
"\n", msg, job.id
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
216
src/storage.cr
216
src/storage.cr
@@ -32,38 +32,40 @@ class Storage
|
||||
"Attepmting to create it"
|
||||
Dir.mkdir_p dir
|
||||
end
|
||||
DB.open "sqlite3://#{@path}" do |db|
|
||||
begin
|
||||
# We create the `ids` table first. even if the uses has an
|
||||
# early version installed and has the `user` table only,
|
||||
# we will still be able to create `ids`
|
||||
db.exec "create table ids" \
|
||||
"(path text, id text, is_title integer)"
|
||||
db.exec "create unique index path_idx on ids (path)"
|
||||
db.exec "create unique index id_idx on ids (id)"
|
||||
MainFiber.run do
|
||||
DB.open "sqlite3://#{@path}" do |db|
|
||||
begin
|
||||
# We create the `ids` table first. even if the uses has an
|
||||
# early version installed and has the `user` table only,
|
||||
# we will still be able to create `ids`
|
||||
db.exec "create table ids" \
|
||||
"(path text, id text, is_title integer)"
|
||||
db.exec "create unique index path_idx on ids (path)"
|
||||
db.exec "create unique index id_idx on ids (id)"
|
||||
|
||||
db.exec "create table users" \
|
||||
"(username text, password text, token text, admin integer)"
|
||||
rescue e
|
||||
unless e.message.not_nil!.ends_with? "already exists"
|
||||
Logger.fatal "Error when checking tables in DB: #{e}"
|
||||
raise e
|
||||
db.exec "create table users" \
|
||||
"(username text, password text, token text, admin integer)"
|
||||
rescue e
|
||||
unless e.message.not_nil!.ends_with? "already exists"
|
||||
Logger.fatal "Error when checking tables in DB: #{e}"
|
||||
raise e
|
||||
end
|
||||
|
||||
# If the DB is initialized through CLI but no user is added, we need
|
||||
# to create the admin user when first starting the app
|
||||
user_count = db.query_one "select count(*) from users", as: Int32
|
||||
init_admin if init_user && user_count == 0
|
||||
else
|
||||
Logger.debug "Creating DB file at #{@path}"
|
||||
db.exec "create unique index username_idx on users (username)"
|
||||
db.exec "create unique index token_idx on users (token)"
|
||||
|
||||
init_admin if init_user
|
||||
end
|
||||
|
||||
# If the DB is initialized through CLI but no user is added, we need
|
||||
# to create the admin user when first starting the app
|
||||
user_count = db.query_one "select count(*) from users", as: Int32
|
||||
init_admin if init_user && user_count == 0
|
||||
else
|
||||
Logger.debug "Creating DB file at #{@path}"
|
||||
db.exec "create unique index username_idx on users (username)"
|
||||
db.exec "create unique index token_idx on users (token)"
|
||||
|
||||
init_admin if init_user
|
||||
end
|
||||
end
|
||||
unless @auto_close
|
||||
@db = DB.open "sqlite3://#{@path}"
|
||||
unless @auto_close
|
||||
@db = DB.open "sqlite3://#{@path}"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -87,37 +89,45 @@ class Storage
|
||||
end
|
||||
|
||||
def verify_user(username, password)
|
||||
get_db do |db|
|
||||
begin
|
||||
hash, token = db.query_one "select password, token from " \
|
||||
"users where username = (?)",
|
||||
username, as: {String, String?}
|
||||
unless verify_password hash, password
|
||||
Logger.debug "Password does not match the hash"
|
||||
return nil
|
||||
out_token = nil
|
||||
MainFiber.run do
|
||||
get_db do |db|
|
||||
begin
|
||||
hash, token = db.query_one "select password, token from " \
|
||||
"users where username = (?)",
|
||||
username, as: {String, String?}
|
||||
unless verify_password hash, password
|
||||
Logger.debug "Password does not match the hash"
|
||||
next
|
||||
end
|
||||
Logger.debug "User #{username} verified"
|
||||
if token
|
||||
out_token = token
|
||||
next
|
||||
end
|
||||
token = random_str
|
||||
Logger.debug "Updating token for #{username}"
|
||||
db.exec "update users set token = (?) where username = (?)",
|
||||
token, username
|
||||
out_token = token
|
||||
rescue e
|
||||
Logger.error "Error when verifying user #{username}: #{e}"
|
||||
end
|
||||
Logger.debug "User #{username} verified"
|
||||
return token if token
|
||||
token = random_str
|
||||
Logger.debug "Updating token for #{username}"
|
||||
db.exec "update users set token = (?) where username = (?)",
|
||||
token, username
|
||||
return token
|
||||
rescue e
|
||||
Logger.error "Error when verifying user #{username}: #{e}"
|
||||
return nil
|
||||
end
|
||||
end
|
||||
out_token
|
||||
end
|
||||
|
||||
def verify_token(token)
|
||||
username = nil
|
||||
get_db do |db|
|
||||
begin
|
||||
username = db.query_one "select username from users where " \
|
||||
"token = (?)", token, as: String
|
||||
rescue e
|
||||
Logger.debug "Unable to verify token"
|
||||
MainFiber.run do
|
||||
get_db do |db|
|
||||
begin
|
||||
username = db.query_one "select username from users where " \
|
||||
"token = (?)", token, as: String
|
||||
rescue e
|
||||
Logger.debug "Unable to verify token"
|
||||
end
|
||||
end
|
||||
end
|
||||
username
|
||||
@@ -125,12 +135,14 @@ class Storage
|
||||
|
||||
def verify_admin(token)
|
||||
is_admin = false
|
||||
get_db do |db|
|
||||
begin
|
||||
is_admin = db.query_one "select admin from users where " \
|
||||
"token = (?)", token, as: Bool
|
||||
rescue e
|
||||
Logger.debug "Unable to verify user as admin"
|
||||
MainFiber.run do
|
||||
get_db do |db|
|
||||
begin
|
||||
is_admin = db.query_one "select admin from users where " \
|
||||
"token = (?)", token, as: Bool
|
||||
rescue e
|
||||
Logger.debug "Unable to verify user as admin"
|
||||
end
|
||||
end
|
||||
end
|
||||
is_admin
|
||||
@@ -138,10 +150,12 @@ class Storage
|
||||
|
||||
def list_users
|
||||
results = Array(Tuple(String, Bool)).new
|
||||
get_db do |db|
|
||||
db.query "select username, admin from users" do |rs|
|
||||
rs.each do
|
||||
results << {rs.read(String), rs.read(Bool)}
|
||||
MainFiber.run do
|
||||
get_db do |db|
|
||||
db.query "select username, admin from users" do |rs|
|
||||
rs.each do
|
||||
results << {rs.read(String), rs.read(Bool)}
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -152,10 +166,12 @@ class Storage
|
||||
validate_username username
|
||||
validate_password password
|
||||
admin = (admin ? 1 : 0)
|
||||
get_db do |db|
|
||||
hash = hash_password password
|
||||
db.exec "insert into users values (?, ?, ?, ?)",
|
||||
username, hash, nil, admin
|
||||
MainFiber.run do
|
||||
get_db do |db|
|
||||
hash = hash_password password
|
||||
db.exec "insert into users values (?, ?, ?, ?)",
|
||||
username, hash, nil, admin
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -163,40 +179,48 @@ class Storage
|
||||
admin = (admin ? 1 : 0)
|
||||
validate_username username
|
||||
validate_password password unless password.empty?
|
||||
get_db do |db|
|
||||
if password.empty?
|
||||
db.exec "update users set username = (?), admin = (?) " \
|
||||
"where username = (?)",
|
||||
username, admin, original_username
|
||||
else
|
||||
hash = hash_password password
|
||||
db.exec "update users set username = (?), admin = (?)," \
|
||||
"password = (?) where username = (?)",
|
||||
username, admin, hash, original_username
|
||||
MainFiber.run do
|
||||
get_db do |db|
|
||||
if password.empty?
|
||||
db.exec "update users set username = (?), admin = (?) " \
|
||||
"where username = (?)",
|
||||
username, admin, original_username
|
||||
else
|
||||
hash = hash_password password
|
||||
db.exec "update users set username = (?), admin = (?)," \
|
||||
"password = (?) where username = (?)",
|
||||
username, admin, hash, original_username
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def delete_user(username)
|
||||
get_db do |db|
|
||||
db.exec "delete from users where username = (?)", username
|
||||
MainFiber.run do
|
||||
get_db do |db|
|
||||
db.exec "delete from users where username = (?)", username
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def logout(token)
|
||||
get_db do |db|
|
||||
begin
|
||||
db.exec "update users set token = (?) where token = (?)", nil, token
|
||||
rescue
|
||||
MainFiber.run do
|
||||
get_db do |db|
|
||||
begin
|
||||
db.exec "update users set token = (?) where token = (?)", nil, token
|
||||
rescue
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def get_id(path, is_title)
|
||||
id = nil
|
||||
get_db do |db|
|
||||
id = db.query_one? "select id from ids where path = (?)", path,
|
||||
as: {String}
|
||||
MainFiber.run do
|
||||
get_db do |db|
|
||||
id = db.query_one? "select id from ids where path = (?)", path,
|
||||
as: {String}
|
||||
end
|
||||
end
|
||||
id
|
||||
end
|
||||
@@ -206,20 +230,24 @@ class Storage
|
||||
end
|
||||
|
||||
def bulk_insert_ids
|
||||
get_db do |db|
|
||||
db.transaction do |tx|
|
||||
@insert_ids.each do |tp|
|
||||
tx.connection.exec "insert into ids values (?, ?, ?)", tp[:path],
|
||||
tp[:id], tp[:is_title] ? 1 : 0
|
||||
MainFiber.run do
|
||||
get_db do |db|
|
||||
db.transaction do |tx|
|
||||
@insert_ids.each do |tp|
|
||||
tx.connection.exec "insert into ids values (?, ?, ?)", tp[:path],
|
||||
tp[:id], tp[:is_title] ? 1 : 0
|
||||
end
|
||||
end
|
||||
end
|
||||
@insert_ids.clear
|
||||
end
|
||||
@insert_ids.clear
|
||||
end
|
||||
|
||||
def close
|
||||
unless @db.nil?
|
||||
@db.not_nil!.close
|
||||
MainFiber.run do
|
||||
unless @db.nil?
|
||||
@db.not_nil!.close
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
42
src/util/proxy.cr
Normal file
42
src/util/proxy.cr
Normal file
@@ -0,0 +1,42 @@
|
||||
require "http_proxy"
|
||||
|
||||
# Monkey-patch `HTTP::Client` to make it respect the `*_PROXY`
|
||||
# environment variables
|
||||
module HTTP
|
||||
class Client
|
||||
private def self.exec(uri : URI, tls : TLSContext = nil)
|
||||
Logger.debug "Using monkey-patched HTTP::Client"
|
||||
previous_def uri, tls do |client, path|
|
||||
client.set_proxy get_proxy uri
|
||||
yield client, path
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
private def get_proxy(uri : URI) : HTTP::Proxy::Client?
|
||||
no_proxy = ENV["no_proxy"]? || ENV["NO_PROXY"]?
|
||||
return if no_proxy &&
|
||||
no_proxy.split(",").any? &.== uri.hostname
|
||||
|
||||
case uri.scheme
|
||||
when "http"
|
||||
env_to_proxy "http_proxy"
|
||||
when "https"
|
||||
env_to_proxy "https_proxy"
|
||||
else
|
||||
nil
|
||||
end
|
||||
end
|
||||
|
||||
private def env_to_proxy(key : String) : HTTP::Proxy::Client?
|
||||
val = ENV[key.downcase]? || ENV[key.upcase]?
|
||||
return if val.nil?
|
||||
|
||||
begin
|
||||
uri = URI.parse val
|
||||
HTTP::Proxy::Client.new uri.hostname.not_nil!, uri.port.not_nil!
|
||||
rescue
|
||||
nil
|
||||
end
|
||||
end
|
||||
@@ -67,10 +67,13 @@
|
||||
</div>
|
||||
<div class="uk-section uk-section-small">
|
||||
</div>
|
||||
<div class="uk-section uk-section-small">
|
||||
<div class="uk-section uk-section-small" id="main-section">
|
||||
<div class="uk-container uk-container-small">
|
||||
<div id="alert"></div>
|
||||
<%= content %>
|
||||
<div class="uk-visible@m" id="totop-wrapper" x-data="{}" x-show="$('body').height() > 1.5 * $(window).height()">
|
||||
<a href="#" uk-totop uk-scroll></a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<script>
|
||||
|
||||
Reference in New Issue
Block a user