local db = require "lapis.db" local giflib = require "giflib" local lfs = require "lfs" local magick = require "magick" local Model = require("lapis.db.model").Model local Boards = Model:extend("boards", { relations = { { "announcements", has_many="Announcements" }, { "bans", has_many="Bans" }, { "posts", has_many="Posts" }, { "reports", has_many="Reports" }, { "threads", has_many="Threads", where={ archive=false }, order="sticky desc, last_active desc" }, { "archived", has_many="Threads", where={ archive=true }, order="last_active desc" }, } }) Boards.valid_record = { { "name", max_length=255, exists=true }, { "title", max_length=255, exists=true }, { "subtext", max_length=255 }, { "ban_message", max_length=255 }, { "anon_name", max_length=255 }, { "theme", max_length=255 }, { "pages", exists=true }, { "threads_per_page", exists=true }, { "thread_file_limit", exists=true }, { "post_limit", exists=true }, { "archive_time", exists=true }, { "group", exists=true } } --- Create a board -- @tparam table params Board parameters -- @treturn boolean success -- @treturn string error function Boards:new(params) local board = self:create(params) if not board then return false, { "err_create_board", { params.name, params.title } } end lfs.mkdir(string.format("./static/%s/", board.name)) return board end --- Modify a board -- @tparam table params Board parameters -- @tparam old_name Board's current short name -- @treturn boolean success -- @treturn string error function Boards:modify(params, old_name) local board = self:get(old_name) if not board then return false, { "err_create_board", { params.name, params.title } } -- FIXME: wrong error message end local success, err = board:update(params) if not success then return false, "FIXME: " .. tostring(err) end if board.name ~= old_name then local old = string.format("./static/%s/", old_name) local new = string.format("./static/%s/", board.name) os.rename(old, new) end return board end --- Delete a board -- @tparam string name Board's short name -- @treturn boolean success -- @treturn string error function Boards:delete(name) local board = self:get(name) if not board then return false, { "err_create_board", { name, name } } -- FIXME: wrong error message end local announcements = board:get_announcements() local bans = board:get_bans() local posts = board:get_posts() local reports = board:get_reports() local threads = board:get_threads() local dir = string.format("./static/%s/", board.name) -- Clear data for _, announcement in ipairs(announcements) do announcement:delete() end for _, ban in ipairs(bans) do ban:delete() end for _, post in ipairs(posts) do post:delete() end for _, report in ipairs(reports) do report:delete() end for _, thread in ipairs(threads) do thread:delete() end -- Clear filesystem if lfs.attributes(dir, "mode") == "directory" then -- Delete files for file in lfs.dir(dir) do os.remove(dir .. file) end -- Delete directory lfs.rmdir(dir) end -- Clear board local success = board:delete() return success and board or false, { "err_delete_board", { board.name, board.title } } end --- Get all boards -- @treturn table boards function Boards:get_all() local boards = self:select("order by boards.group asc, name asc") return boards and boards or false, "FIXME: ALART!" end --- Get board data -- @tparam string name Board's short name -- @treturn table board function Boards:get(name) local board = self:find { name=name } return board and board or false, "FIXME: ALART!" end --- Format board paramaters for DB insertion -- @tparam table params Board parameters function Boards.format_to_db(_, params) -- Convert archive_time from days to seconds params.archive_time = (tonumber(params.archive_time) or 0) * 86400 end --- Format board parameters for User consumption -- @tparam table params Board parameters function Boards.format_from_db(_, params) -- Convert archive_time from seconds to days params.archive_time = tonumber(params.archive_time) / 86400 end --- Regenerate thumbnails for all posts -- @treturn none function Boards.regen_thumbs(_) local sql = [[ select boards.name as board, posts.thread_id, posts.file_path, posts.file_width, posts.file_height, posts.file_type from posts left join boards on board_id = boards.id where file_path is not null and file_width is not null and file_height is not null and file_type = 'image' and file_spoiler = false order by board_id asc, thread_id asc, post_id asc ]] local dir local board local thread = 0 local results = db.query(sql) for _, result in ipairs(results) do -- Change board, reset thread counter and image directory if result.board ~= board then board = result.board thread = 0 dir = string.format("./static/%s/", board) end -- Filesystem paths local name, ext = result.file_path:match("^(.+)(%..+)$") ext = string.lower(ext) local full_path = dir .. result.file_path local thumb_path = dir .. "s" .. result.file_path -- Generate a thumbnail if ext == ".webm" then thumb_path = dir .. "s" .. name .. ".png" -- Create screenshot of first frame os.execute(string.format("ffmpeg -i %s -ss 00:00:01 -vframes 1 %s -y", full_path, thumb_path)) end -- Save thumbnail local w, h if result.thread_id > thread then thread = result.thread_id w = result.file_width < 250 and result.file_width or 250 h = result.file_height < 250 and result.file_height or 250 else w = result.file_width < 125 and result.file_width or 125 h = result.file_height < 125 and result.file_height or 125 end -- Grab first frame from video if ext == ".webm" then magick.thumb(thumb_path, string.format("%sx%s", w, h), thumb_path) elseif ext == ".svg" then thumb_path = dir .. "s" .. name .. ".png" os.execute(string.format("convert -background none -resize %dx%d %s %s", w, h, full_path, thumb_path)) elseif ext == ".gif" then -- Grab first frame of a gif instead of the last local gif, err = giflib.load_gif(full_path) if err then magick.thumb(full_path, string.format("%sx%s", w, h), thumb_path) else gif:write_first_frame(thumb_path) magick.thumb(thumb_path, string.format("%sx%s", w, h), thumb_path) end else magick.thumb(full_path, string.format("%sx%s", w, h), thumb_path) end end end return Boards