feat: priority system

This commit is contained in:
LordMZTE 2023-07-09 14:22:31 +02:00
parent 65e6fbaf90
commit 2178cb181a
Signed by: LordMZTE
GPG key ID: B64802DC33A64FF6
5 changed files with 84 additions and 60 deletions

View file

@ -19,7 +19,7 @@ a:visited {
background-color: #181825;
}
#enqueue_form *:not([type="checkbox"]),
#enqueue_form *:not([type="checkbox"]):not([type="number"]),
#pause_btn {
display: block;
width: 100%;
@ -86,3 +86,15 @@ ul {
#head_div * {
padding: 5px;
}
.prio_low_txt {
color: #94e2d5;
}
.prio_medium_txt {
color: #a6e3a1;
}
.prio_high_txt {
color: #eba0ac;
}

View file

@ -4,14 +4,14 @@ import js.html.XMLHttpRequest;
class EnqueueTask {
var url:String;
var outname:Null<String>;
var prepend:Bool;
var priority:Int;
var description:Bool;
var audio_only:Bool;
public function new(url:String, outname:Null<String>, prepend:Bool, description:Bool, audio_only:Bool) {
public function new(url:String, outname:Null<String>, priority:Int, description:Bool, audio_only:Bool) {
this.url = url;
this.outname = outname;
this.prepend = prepend;
this.priority = priority;
this.description = description;
this.audio_only = audio_only;
}

View file

@ -37,7 +37,7 @@ function onEnqSubmit(e:Dynamic):Void {
new EnqueueTask(
url_inp.value,
outname_inp.value.length == 0 ? null : outname_inp.value,
cast(Browser.document.getElementById("prepend_switch"), InputElement).checked,
Std.parseInt(cast(Browser.document.getElementById("priority_num"), InputElement).value),
cast(Browser.document.getElementById("description_switch"), InputElement).checked,
cast(Browser.document.getElementById("audio_only_switch"), InputElement).checked
).send();

View file

@ -8,6 +8,9 @@ pub const Task = struct {
/// URL of the video
url: []const u8,
/// Priority to order queue by
priority: i32,
/// Optional output file name or yt-dlp format string
outname: ?[]const u8,
@ -17,21 +20,32 @@ pub const Task = struct {
/// Write description
description: bool,
pub fn copy(self: *const Task, alloc: std.mem.Allocator) !Task {
pub fn copy(self: Task, alloc: std.mem.Allocator) !Task {
const url_c = try alloc.dupe(u8, self.url);
errdefer alloc.free(url_c);
const outname_c = if (self.outname) |n| try alloc.dupe(u8, n) else null;
errdefer if (outname_c) |n| alloc.free(n);
return .{
.url = try alloc.dupe(u8, self.url),
.outname = blk: {
break :blk try alloc.dupe(u8, self.outname orelse break :blk null);
},
.url = url_c,
.priority = self.priority,
.outname = outname_c,
.description = self.description,
.audio_only = self.audio_only,
};
}
fn compare(ctx: void, a: Task, b: Task) std.math.Order {
_ = ctx;
return std.math.order(b.priority, a.priority);
}
};
const TaskQueue = std.PriorityQueue(Task, void, Task.compare);
state: *State,
alloc: std.mem.Allocator,
tasks: std.TailQueue(Task),
tasks: TaskQueue,
next_cond: std.Thread.Condition,
m: std.Thread.Mutex,
active_task: ?Task,
@ -43,8 +57,7 @@ pub fn spawn(alloc: std.mem.Allocator, state: *State) !*DownloadQueue {
const self = try alloc.create(DownloadQueue);
self.* = .{
.state = state,
.alloc = alloc,
.tasks = .{},
.tasks = TaskQueue.init(alloc, {}),
.next_cond = .{},
.m = .{},
.active_task = null,
@ -59,18 +72,17 @@ pub fn spawn(alloc: std.mem.Allocator, state: *State) !*DownloadQueue {
}
/// Adds a task to the queue. The task given is copied.
pub fn pushTask(self: *DownloadQueue, task: Task, prepend: bool) !void {
const node = try self.alloc.create(std.TailQueue(Task).Node);
node.* = .{ .data = try task.copy(self.alloc) };
pub fn pushTask(self: *DownloadQueue, task: Task) !void {
const task_copy = try task.copy(self.tasks.allocator);
errdefer {
self.tasks.allocator.free(task_copy.url);
if (task_copy.outname) |n| self.tasks.allocator.free(n);
}
self.m.lock();
defer self.m.unlock();
if (prepend) {
self.tasks.append(node);
} else {
self.tasks.prepend(node);
}
try self.tasks.add(task_copy);
self.next_cond.signal();
}
@ -92,36 +104,33 @@ fn run(self: *DownloadQueue) noreturn {
}
fn next(self: *DownloadQueue) !void {
var arena = std.heap.ArenaAllocator.init(self.alloc);
var arena = std.heap.ArenaAllocator.init(self.tasks.allocator);
defer arena.deinit();
const task = blk: {
self.m.lock();
defer self.m.unlock();
if (self.state.conf.autopause and self.tasks.len <= 0 and !self.paused) {
if (self.state.conf.autopause and self.tasks.count() <= 0 and !self.paused) {
std.log.info("queue done; autopausing", .{});
self.paused = true;
}
while (self.tasks.len <= 0 or self.paused) {
while (self.tasks.count() <= 0 or self.paused) {
self.next_cond.wait(&self.m);
}
const node = self.tasks.pop().?;
defer self.alloc.destroy(node);
self.active_task = node.data;
break :blk node.data;
const task = self.tasks.remove();
self.active_task = task;
break :blk task;
};
defer {
self.m.lock();
defer self.m.unlock();
self.alloc.free(task.url);
self.tasks.allocator.free(task.url);
if (task.outname) |outname| {
self.alloc.free(outname);
self.tasks.allocator.free(outname);
}
self.active_task = null;
}

View file

@ -82,20 +82,28 @@ pub fn indexRoute(
\\<table id="tasks_tbl"><tbody>
);
var task = state.downloads.tasks.last;
while (task) |t| {
var iter = state.downloads.tasks.iterator();
while (iter.next()) |t| {
try w.print(
\\<tr><td>{s}{s}{s}</td><td>{s}</td></tr>
\\<tr>
\\ <td>{s}{s}{s}</td>
\\ <td>{s}</td>
\\ <td><span class="prio_{s}_txt">{}</span></td>
\\</tr>
,
.{
if (t.data.description) description_prefix_html else "",
if (t.data.audio_only) audio_only_prefix_html else "",
t.data.url,
t.data.outname orelse inferred_html,
if (t.description) description_prefix_html else "",
if (t.audio_only) audio_only_prefix_html else "",
t.url,
t.outname orelse inferred_html,
switch (std.math.order(t.priority, 0)) {
.gt => "high",
.eq => "medium",
.lt => "low",
},
t.priority,
},
);
task = t.prev;
}
try w.writeAll(
@ -114,7 +122,7 @@ pub fn indexRoute(
\\<form id="enqueue_form">
\\ <input type="text" placeholder="URL" id="url_inp"><br>
\\ <input type="text" placeholder="output name" id="outname_inp"><br>
\\ <input type="checkbox" id="prepend_switch">Prepend<br>
\\ <input type="number" id="priority_num" min="-1000" max="1000" value="0"> Priority<br>
\\ <input type="checkbox" id="description_switch">Description<br>
\\ <input type="checkbox" id="audio_only_switch">Audio Only<br>
\\ <input type="submit" id="enqueue_btn" value="Enqueue">
@ -204,14 +212,12 @@ pub fn indexJsonRoute(
defer arena.deinit();
const arena_alloc = arena.allocator();
// TODO: optimize
const tasks = try arena_alloc.alloc(DownloadQueue.Task, state.downloads.tasks.len);
var cur_task = state.downloads.tasks.last;
var task_iter = state.downloads.tasks.iterator();
var i: usize = 0;
while (cur_task) |t| {
tasks[i] = t.data;
i += 1;
cur_task = t.prev;
}
while (task_iter.next()) |task| : (i += 1)
tasks[i] = task;
var vids_dir = try std.fs.cwd().openIterableDir(state.vids_dir, .{});
defer vids_dir.close();
@ -256,8 +262,7 @@ pub const ApiTask = struct {
outname: ?[]const u8,
audio_only: bool = false,
description: bool = false,
prepend: bool = false,
priority: i32 = 0,
};
pub fn vidsRoute(
@ -332,15 +337,13 @@ pub fn vidsRoute(
}
std.log.info("adding {s} to queue", .{task.url});
try state.downloads.pushTask(
.{
.url = task.url,
.outname = task.outname,
.audio_only = task.audio_only,
.description = task.description,
},
task.prepend,
);
try state.downloads.pushTask(.{
.url = task.url,
.outname = task.outname,
.audio_only = task.audio_only,
.description = task.description,
.priority = task.priority,
});
try status_response.sendJsonResponseText(res, .accepted);
},