Allow multiple conditions in rules.
Scrobble processing appears noticeably slower (according to the logs), so I think rules are going to be something to optimize later. Fortunately, they shouldn't need to be applied too often
This commit is contained in:
parent
77170a1e28
commit
e9c72041a5
6 changed files with 67 additions and 97 deletions
|
|
@ -17,8 +17,8 @@
|
||||||
// internet connectivity.
|
// internet connectivity.
|
||||||
.dependencies = .{
|
.dependencies = .{
|
||||||
.jetzig = .{
|
.jetzig = .{
|
||||||
.url = "https://github.com/jetzig-framework/jetzig/archive/2c52792217b9441ed5e91d67e7ec5a8959285307.tar.gz",
|
.url = "https://github.com/jetzig-framework/jetzig/archive/86d82026ab574d4e5c3c6cc3817dda84b510001a.tar.gz",
|
||||||
.hash = "jetzig-0.0.0-IpAgLbMeDwDYRqWu0OJixGbcDUoUbfAN0xGe1xsYRHTj",
|
.hash = "jetzig-0.0.0-IpAgLTkzDwDKmsY9MqM41EHDXWGkViiECa0lzV8xl17x",
|
||||||
},
|
},
|
||||||
.zeit = .{
|
.zeit = .{
|
||||||
.url = "https://github.com/rockorager/zeit/archive/refs/heads/main.tar.gz",
|
.url = "https://github.com/rockorager/zeit/archive/refs/heads/main.tar.gz",
|
||||||
|
|
|
||||||
|
|
@ -1,29 +1,14 @@
|
||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
const jetzig = @import("jetzig");
|
const jetzig = @import("jetzig");
|
||||||
|
const Data = @import("../../types.zig");
|
||||||
|
|
||||||
pub fn run(allocator: std.mem.Allocator, params: *jetzig.data.Value, env: jetzig.jobs.JobEnv) !void {
|
pub fn run(allocator: std.mem.Allocator, params: *jetzig.data.Value, env: jetzig.jobs.JobEnv) !void {
|
||||||
_ = env;
|
_ = env;
|
||||||
//_ = params;
|
//_ = params;
|
||||||
|
|
||||||
const Rule = struct {
|
std.log.debug("{s}", .{try params.toJson()});
|
||||||
name: []const u8,
|
|
||||||
conditionals: []struct {
|
|
||||||
match_on: []const u8,
|
|
||||||
match_cond: []const u8,
|
|
||||||
match_txt: []const u8,
|
|
||||||
},
|
|
||||||
actions: []struct {
|
|
||||||
action: []const u8,
|
|
||||||
action_on: []const u8,
|
|
||||||
action_txt: []const u8,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
const Rules = struct {
|
const rule = try std.json.parseFromSliceLeaky(Data.Rule, allocator, try params.toJson(), .{ .ignore_unknown_fields = true });
|
||||||
rules: []const Rule,
|
|
||||||
};
|
|
||||||
|
|
||||||
const rule = try std.json.parseFromSliceLeaky(Rule, allocator, try params.toJson(), .{ .ignore_unknown_fields = true });
|
|
||||||
|
|
||||||
const file_read: std.fs.File = std.fs.cwd().openFile("rules.json", .{}) catch |read_err| switch (read_err) {
|
const file_read: std.fs.File = std.fs.cwd().openFile("rules.json", .{}) catch |read_err| switch (read_err) {
|
||||||
error.FileNotFound => {
|
error.FileNotFound => {
|
||||||
|
|
@ -34,7 +19,7 @@ pub fn run(allocator: std.mem.Allocator, params: *jetzig.data.Value, env: jetzig
|
||||||
return;
|
return;
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
const out_rules = Rules{ .rules = &[_]Rule{rule} };
|
const out_rules = Data.Rules{ .rules = &[_]Data.Rule{rule} };
|
||||||
const out = try std.json.stringifyAlloc(allocator, out_rules, .{});
|
const out = try std.json.stringifyAlloc(allocator, out_rules, .{});
|
||||||
try file.writeAll(out);
|
try file.writeAll(out);
|
||||||
file.close();
|
file.close();
|
||||||
|
|
@ -46,17 +31,17 @@ pub fn run(allocator: std.mem.Allocator, params: *jetzig.data.Value, env: jetzig
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
var rules = std.ArrayList(Rule).init(allocator);
|
var rules = std.ArrayList(Data.Rule).init(allocator);
|
||||||
defer rules.deinit();
|
defer rules.deinit();
|
||||||
|
|
||||||
const file_content = try file_read.readToEndAlloc(allocator, 16_000_000);
|
const file_content = try file_read.readToEndAlloc(allocator, 16_000_000);
|
||||||
const content: Rules = try std.json.parseFromSliceLeaky(Rules, allocator, file_content, .{});
|
const content: Data.Rules = try std.json.parseFromSliceLeaky(Data.Rules, allocator, file_content, .{});
|
||||||
try rules.appendSlice(content.rules);
|
try rules.appendSlice(content.rules);
|
||||||
try rules.append(rule);
|
try rules.append(rule);
|
||||||
file_read.close();
|
file_read.close();
|
||||||
|
|
||||||
const file_write: std.fs.File = try std.fs.cwd().openFile("rules.json", .{ .mode = .write_only });
|
const file_write: std.fs.File = try std.fs.cwd().openFile("rules.json", .{ .mode = .write_only });
|
||||||
const out_rules = Rules{ .rules = rules.items };
|
const out_rules = Data.Rules{ .rules = rules.items };
|
||||||
const out = try std.json.stringifyAlloc(allocator, out_rules, .{});
|
const out = try std.json.stringifyAlloc(allocator, out_rules, .{});
|
||||||
|
|
||||||
try file_write.writeAll(out);
|
try file_write.writeAll(out);
|
||||||
|
|
|
||||||
|
|
@ -25,12 +25,17 @@ pub fn post(request: *jetzig.Request) !jetzig.View {
|
||||||
var job = try request.job("process_rule");
|
var job = try request.job("process_rule");
|
||||||
|
|
||||||
_ = try job.params.put("name", params.get("rule-title"));
|
_ = try job.params.put("name", params.get("rule-title"));
|
||||||
|
_ = try job.params.put("cond_req", params.get("cond-req"));
|
||||||
|
|
||||||
var conditionals = try job.params.put("conditionals", .array);
|
var conditionals = try job.params.put("conditionals", .array);
|
||||||
var cond0 = try conditionals.append(.object);
|
inline for (0..5) |i| {
|
||||||
try cond0.put("match_on", params.get("match-on"));
|
if (!std.mem.eql(u8, "", params.getT(.string, comptime std.fmt.comptimePrint("match-txt{}", .{i})).?)) {
|
||||||
try cond0.put("match_cond", params.get("match-cond"));
|
var cond = try conditionals.append(.object);
|
||||||
try cond0.put("match_txt", params.get("match-txt"));
|
try cond.put("match_on", params.get(comptime std.fmt.comptimePrint("match-on{}", .{i})));
|
||||||
|
try cond.put("match_cond", params.get(comptime std.fmt.comptimePrint("match-cond{}", .{i})));
|
||||||
|
try cond.put("match_txt", params.get(comptime std.fmt.comptimePrint("match-txt{}", .{i})));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
var actions = try job.params.put("actions", .array);
|
var actions = try job.params.put("actions", .array);
|
||||||
var act0 = try actions.append(.object);
|
var act0 = try actions.append(.object);
|
||||||
|
|
|
||||||
|
|
@ -12,74 +12,37 @@ Add a rule below.
|
||||||
<label for="rule-title">Rule Name:</label>
|
<label for="rule-title">Rule Name:</label>
|
||||||
<input type="text" name="rule-title" id="rule-title">
|
<input type="text" name="rule-title" id="rule-title">
|
||||||
<br>
|
<br>
|
||||||
|
Match
|
||||||
|
<select name="cond-req" id="cond-req">
|
||||||
|
<option value="any">any</option>
|
||||||
|
<option value="all">all</option>
|
||||||
|
</select>
|
||||||
|
conditonals.
|
||||||
|
<br>
|
||||||
If
|
If
|
||||||
<select name="match-on" id="match-on">
|
@for (0..5) |i| {
|
||||||
|
<select name="match-on{{i}}" id="match-on{{i}}">
|
||||||
<option value="artist">artist</option>
|
<option value="artist">artist</option>
|
||||||
<option value="album">album</option>
|
<option value="album">album</option>
|
||||||
<option value="track">song</option>
|
<option value="track">song</option>
|
||||||
</select>
|
</select>
|
||||||
<select name="match-cond" id="match-cond">
|
<select name="match-cond{{i}}" id="match-cond{{i}}">
|
||||||
<option value="is">is</option>
|
<option value="is">is</option>
|
||||||
<option value="contains">contains</option>
|
<option value="contains">contains</option>
|
||||||
<option value="matches">matches regex</option>
|
<option value="matches">matches regex</option>
|
||||||
</select>
|
</select>
|
||||||
<input type="text" name="match-txt" id="match-txt">
|
<input type="text" name="match-txt{{i}}" id="match-txt{{i}}">
|
||||||
<label for="case-sens">Toggle case sensitivity</label>
|
<label for="case-sens">Toggle case sensitivity</label>
|
||||||
<input type="checkbox" name="case-sens" id="case-sens">
|
<input type="checkbox" name="case-sens{{i}}" id="case-sens{{i}}">
|
||||||
<label for="accent-sens">Toggle diacritic sensitivity</label>
|
<label for="accent-sens">Toggle diacritic sensitivity</label>
|
||||||
<input type="checkbox" name="accent-sens" id="accent-sens">
|
<input type="checkbox" name="accent-sens{{i}}" id="accent-sens{{i}}">
|
||||||
|
<br>
|
||||||
|
}
|
||||||
<button type="button" onclick="condAdd()">
|
<button type="button" onclick="condAdd()">
|
||||||
Add Conditional
|
Add Conditional
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
function condAdd() {
|
|
||||||
const sep = document.getElementById("cond-ins");
|
|
||||||
const wrapper = document.createElement("div");
|
|
||||||
const cond = document.createElement("select");
|
|
||||||
const match_on = document.createElement("select");
|
|
||||||
const match_cond = document.createElement("select");
|
|
||||||
const match_txt = document.createElement("input");
|
|
||||||
|
|
||||||
const or_opt = document.createElement("option")
|
|
||||||
or_opt.value = "or";
|
|
||||||
or_opt.innerHTML = "or";
|
|
||||||
const and_opt = document.createElement("option")
|
|
||||||
and_opt.value = "and";
|
|
||||||
and_opt.innerHTML = "and";
|
|
||||||
const artist_opt = document.createElement("option")
|
|
||||||
artist_opt.value = "artist";
|
|
||||||
artist_opt.innerHTML = "artist";
|
|
||||||
const album_opt = document.createElement("option")
|
|
||||||
album_opt.value = "album"
|
|
||||||
album_opt.innerHTML = "album"
|
|
||||||
const song_opt = document.createElement("option")
|
|
||||||
song_opt.value = "song";
|
|
||||||
song_opt.innerHTML = "song";
|
|
||||||
const is_opt = document.createElement("option")
|
|
||||||
is_opt.value = "is";
|
|
||||||
is_opt.innerHTML = "is";
|
|
||||||
const contains_opt = document.createElement("option")
|
|
||||||
contains_opt.value = "contains"
|
|
||||||
contains_opt.innerHTML = "contains"
|
|
||||||
|
|
||||||
match_txt.setAttribute("type","text");
|
|
||||||
|
|
||||||
cond.appendChild(or_opt);
|
|
||||||
cond.appendChild(and_opt);
|
|
||||||
match_on.appendChild(artist_opt);
|
|
||||||
match_on.appendChild(album_opt);
|
|
||||||
match_on.appendChild(song_opt);
|
|
||||||
match_cond.appendChild(is_opt);
|
|
||||||
match_cond.appendChild(contains_opt);
|
|
||||||
|
|
||||||
wrapper.appendChild(cond);
|
|
||||||
wrapper.appendChild(match_on);
|
|
||||||
wrapper.appendChild(match_cond);
|
|
||||||
wrapper.appendChild(match_txt);
|
|
||||||
|
|
||||||
sep.appendChild(wrapper);
|
|
||||||
}
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div id="cond-ins"></div>
|
<div id="cond-ins"></div>
|
||||||
|
|
|
||||||
|
|
@ -2,17 +2,33 @@ const std = @import("std");
|
||||||
const Scrobble = @import("./types.zig").LastFMScrobble;
|
const Scrobble = @import("./types.zig").LastFMScrobble;
|
||||||
const Rules = @import("./types.zig").Rules;
|
const Rules = @import("./types.zig").Rules;
|
||||||
|
|
||||||
|
// Wrapper for containsAtLeast to make the switch below to work
|
||||||
|
fn containsAtLeastOne(haystack: []const u8, needle: []const u8) bool {
|
||||||
|
return std.mem.containsAtLeast(u8, haystack, 1, needle);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn eqlDecomped(haystack: []const u8, needle: []const u8) bool {
|
||||||
|
return std.mem.eql(u8, haystack, needle);
|
||||||
|
}
|
||||||
|
|
||||||
pub fn applyScrobbleRule(scrobble: Scrobble, rules: Rules) Scrobble {
|
pub fn applyScrobbleRule(scrobble: Scrobble, rules: Rules) Scrobble {
|
||||||
var match_found: bool = true;
|
|
||||||
var output_scrobble: Scrobble = scrobble;
|
var output_scrobble: Scrobble = scrobble;
|
||||||
for (rules.rules) |rule| {
|
for (rules.rules) |rule| {
|
||||||
|
var match_found: bool = switch (rule.cond_req) {
|
||||||
|
.any => false,
|
||||||
|
.all => true,
|
||||||
|
};
|
||||||
for (rule.conditionals) |cond| {
|
for (rule.conditionals) |cond| {
|
||||||
switch (cond.match_cond) {
|
const match_fn: *const fn ([]const u8, []const u8) bool = switch (cond.match_cond) {
|
||||||
.is => switch (cond.match_on) {
|
.is => eqlDecomped,
|
||||||
inline else => |on| match_found = match_found and std.mem.eql(u8, @field(scrobble, @tagName(on)), cond.match_txt),
|
.contains => containsAtLeastOne,
|
||||||
|
};
|
||||||
|
switch (rule.cond_req) {
|
||||||
|
.any => switch (cond.match_on) {
|
||||||
|
inline else => |on| match_found = match_found or match_fn(@field(scrobble, @tagName(on)), cond.match_txt),
|
||||||
},
|
},
|
||||||
.contains => switch (cond.match_on) {
|
.all => switch (cond.match_on) {
|
||||||
inline else => |on| match_found = match_found and std.mem.containsAtLeast(u8, @field(scrobble, @tagName(on)), 1, cond.match_txt),
|
inline else => |on| match_found = match_found and match_fn(@field(scrobble, @tagName(on)), cond.match_txt),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -35,8 +35,9 @@ pub const SpotifyScrobble = struct {
|
||||||
incognito_mode: ?bool,
|
incognito_mode: ?bool,
|
||||||
};
|
};
|
||||||
|
|
||||||
const Rule = struct {
|
pub const Rule = struct {
|
||||||
name: []const u8,
|
name: []const u8,
|
||||||
|
cond_req: enum { any, all },
|
||||||
conditionals: []struct {
|
conditionals: []struct {
|
||||||
match_on: ScrobbleFields,
|
match_on: ScrobbleFields,
|
||||||
match_cond: enum { is, contains },
|
match_cond: enum { is, contains },
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue