zuletzt/src/app/views/upload.zig
mitteneer 0631ded115 Work on add artist action in rules
Really close to having it work, but there seems to be an error when uploading files, which causes particularly annoying problems on WSL when testing, so I'm commiting and trying on my desktop.
2025-04-23 19:32:32 -04:00

180 lines
9 KiB
Zig

const std = @import("std");
const jetzig = @import("jetzig");
const jetquery = @import("jetzig").jetquery;
const zeit = @import("zeit");
const rules = @import("../../apply_rule.zig");
const Data = @import("../../types.zig");
pub fn index(request: *jetzig.Request, data: *jetzig.Data) !jetzig.View {
_ = data;
return request.render(.ok);
}
pub fn get(id: []const u8, request: *jetzig.Request, data: *jetzig.Data) !jetzig.View {
_ = data;
_ = id;
return request.render(.ok);
}
pub fn post(request: *jetzig.Request) !jetzig.View {
var root = try request.data(.object);
if (try request.file("upload")) |file| {
const params = try request.params();
const source = try std.fmt.parseInt(u8, params.get("t").?.string.value, 10); // This param is required in HTML
const before_limiter: bool = if (params.get("bbool")) |_| true else false;
const after_limiter: bool = if (params.get("abool")) |_| true else false;
var scrobbles_view = try root.put("scrobbles", .array);
var job = try request.job("process_scrobbles");
var scrobbles_data = try job.params.put("scrobbles", .array);
var skipped_tracks: u64 = 0;
var limited_tracks: u64 = 0;
const rule_file = try (std.fs.cwd().openFile("rules.json", .{ .mode = .read_only }) catch |err| switch (err) {
error.FileNotFound => std.fs.cwd().createFile("rules.json", .{ .read = true }),
else => err,
});
defer rule_file.close();
const file_content = try rule_file.readToEndAlloc(request.allocator, 16_000_000);
const rule_list = std.json.parseFromSliceLeaky(Data.Rules, request.allocator, file_content, .{}) catch null;
switch (source) {
0 => {
const content: Data.LastFM = try std.json.parseFromSliceLeaky(Data.LastFM, request.allocator, file.content, .{});
const before_limiting_date = if (before_limiter and params.get("b") != null) (try zeit.instant(.{ .source = .{ .iso8601 = params.get("b").?.string.value } })).unixTimestamp() * 1000 else 0;
const after_limiting_date = if (after_limiter and params.get("a") != null) (try zeit.instant(.{ .source = .{ .iso8601 = params.get("a").?.string.value } })).unixTimestamp() * 1000 else 9_223_372_036_854_775_807;
appends: for (content.scrobbles) |scrobble| {
// We can short-circuit on the limiter bools
if ((before_limiter or after_limiter) and (scrobble.date > before_limiting_date or scrobble.date < after_limiting_date)) continue :appends;
const formatted_scrobble = if (rule_list) |rl|
rules.applyScrobbleRule(request.allocator, scrobble, rl)
else
Data.Scrobble{
.album = scrobble.album,
.artists_album = &[_][]const u8{scrobble.artist},
.track = scrobble.track,
.artists_track = &[_][]const u8{scrobble.artist},
.date = scrobble.date,
};
var scrobble_view = try scrobbles_view.append(.object);
var artists = try scrobble_view.put("artists", .array);
try scrobble_view.put("track", formatted_scrobble.track);
try scrobble_view.put("album", formatted_scrobble.album);
for (formatted_scrobble.artists_track) |artist| {
try artists.append(artist);
}
try scrobble_view.put("date", formatted_scrobble.date);
var scrobble_data = try scrobbles_data.append(.object);
var artists_album = try scrobble_data.put("artists_album", .array);
var artists_track = try scrobble_data.put("artists_track", .array);
try scrobble_data.put("track", formatted_scrobble.track);
try scrobble_data.put("album", formatted_scrobble.album);
for (formatted_scrobble.artists_album) |artist| {
try artists_album.append(artist);
}
for (formatted_scrobble.artists_track) |artist| {
try artists_track.append(artist);
}
try scrobble_data.put("date", formatted_scrobble.date);
}
},
1 => {
const content: []Data.SpotifyScrobble = try std.json.parseFromSliceLeaky([]Data.SpotifyScrobble, request.allocator, file.content, .{ .ignore_unknown_fields = true });
const before_limiting_date: zeit.Time = if (before_limiter) (try zeit.Time.fromISO8601(params.get("b").?.string.value)) else (try zeit.instant(.{})).time();
const after_limiting_date: zeit.Time = if (after_limiter) (try zeit.Time.fromISO8601(params.get("a").?.string.value)) else (try zeit.instant(.{ .source = .{ .unix_nano = 0 } })).time();
appends: for (content) |scrobble| {
if (scrobble.ms_played < 30_000 and (scrobble.reason_end == null or !std.mem.eql(u8, scrobble.reason_end.?, "trackdone"))) {
skipped_tracks += 1;
continue :appends;
}
if (scrobble.master_metadata_album_artist_name == null or scrobble.master_metadata_track_name == null) {
skipped_tracks += 1;
continue :appends;
}
const iso_ts = try zeit.Time.fromISO8601(scrobble.ts);
if ((before_limiter or after_limiter) and (iso_ts.after(before_limiting_date) or iso_ts.before(after_limiting_date))) {
limited_tracks += 1;
continue :appends;
}
const pre_formatted_scrobble: Data.ImportedScrobble = .{ .track = scrobble.master_metadata_track_name.?, .album = scrobble.master_metadata_album_album_name.?, .artist = scrobble.master_metadata_album_artist_name.?, .date = (try zeit.instant(.{ .source = .{ .iso8601 = scrobble.ts } })).unixTimestamp() * 1000 };
const formatted_scrobble = if (rule_list) |rl|
rules.applyScrobbleRule(request.allocator, pre_formatted_scrobble, rl)
else
Data.Scrobble{
.album = pre_formatted_scrobble.album,
.artists_album = &[_][]const u8{pre_formatted_scrobble.artist},
.track = pre_formatted_scrobble.track,
.artists_track = &[_][]const u8{pre_formatted_scrobble.artist},
.date = pre_formatted_scrobble.date,
};
var scrobble_view = try scrobbles_view.append(.object);
var artists = try scrobble_view.put("artists", .array);
try scrobble_view.put("track", formatted_scrobble.track);
try scrobble_view.put("album", formatted_scrobble.album);
for (formatted_scrobble.artists_track) |artist| {
try artists.append(artist);
}
try scrobble_view.put("date", formatted_scrobble.date);
var scrobble_data = try scrobbles_data.append(.object);
var artists_album = try scrobble_data.put("artists_album", .array);
var artists_track = try scrobble_data.put("artists_track", .array);
try scrobble_data.put("track", formatted_scrobble.track);
try scrobble_data.put("album", formatted_scrobble.album);
for (formatted_scrobble.artists_album) |artist| {
try artists_album.append(artist);
}
for (formatted_scrobble.artists_track) |artist| {
try artists_track.append(artist);
}
try scrobble_data.put("date", formatted_scrobble.date);
}
},
else => unreachable,
}
try job.schedule();
std.log.debug("Skipped {} tracks", .{skipped_tracks});
std.log.debug("Filtered {} tracks", .{limited_tracks});
}
var upload_table = try root.put("upload_table", .array);
try upload_table.append("Track");
try upload_table.append("Artist");
try upload_table.append("Album");
try upload_table.append("Date");
return request.render(.created);
}
pub fn put(id: []const u8, request: *jetzig.Request, data: *jetzig.Data) !jetzig.View {
_ = data;
_ = id;
return request.render(.ok);
}
pub fn patch(id: []const u8, request: *jetzig.Request, data: *jetzig.Data) !jetzig.View {
_ = data;
_ = id;
return request.render(.ok);
}
pub fn delete(id: []const u8, request: *jetzig.Request, data: *jetzig.Data) !jetzig.View {
_ = data;
_ = id;
return request.render(.ok);
}