Switch to expectParams() rather than params()

Makes some code nicer, particularly date parsing
This commit is contained in:
mitteneer 2025-07-14 14:04:22 -04:00
parent 682eebc951
commit 280cba2f9a
2 changed files with 47 additions and 27 deletions

View file

@ -15,7 +15,17 @@ pub fn index(request: *jetzig.Request, data: *jetzig.Data) !jetzig.View {
pub fn post(request: *jetzig.Request) !jetzig.View { pub fn post(request: *jetzig.Request) !jetzig.View {
var root = try request.data(.object); var root = try request.data(.object);
const params = try request.params(); //const params = try request.params();
const UploadParams = struct {
source: enum { LFMW, LFMS, Spotify },
earliest_date: ?[]const u8,
latest_date: ?[]const u8,
username: ?[]const u8,
};
const params = (try request.expectParams(UploadParams)).?;
const rule_file = try (std.fs.cwd().openFile("rules.json", .{ .mode = .read_only }) catch |err| switch (err) { 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 }), error.FileNotFound => std.fs.cwd().createFile("rules.json", .{ .read = true }),
else => err, else => err,
@ -26,43 +36,39 @@ pub fn post(request: *jetzig.Request) !jetzig.View {
const rule_list = std.json.parseFromSliceLeaky([]Data.Rule, request.allocator, rule_file_content, .{}) catch null; const rule_list = std.json.parseFromSliceLeaky([]Data.Rule, request.allocator, rule_file_content, .{}) catch null;
//var job = try request.job("process_scrobbles"); //var job = try request.job("process_scrobbles");
var job = try request.job("process_scrobbles2"); var job = try request.job("process_scrobbles2");
const source = params.getT(.integer, "t").?; // This param is required in HTML
const latest_date = if (params.getT(.boolean, "adv-opt")) |_| blk: { // We can parse the dates better
const date = params.getT(.string, "latest-date").?; const latest_ts = if (params.latest_date) |ld|
break :blk try zeit.Time.fromISO8601(date); (try zeit.instant(.{ .source = .{ .iso8601 = ld } })).timestamp
} else (try zeit.instant(.{ .source = .now })).time(); else
(try zeit.instant(.{ .source = .now })).timestamp;
const earliest_date = if (params.getT(.boolean, "adv-opt")) |_| blk: { const earliest_ts = if (params.earliest_date) |ed|
const date = params.getT(.string, "earliest-date").?; (try zeit.instant(.{ .source = .{ .iso8601 = ed } })).timestamp
break :blk try zeit.Time.fromISO8601(date); else
} else (try zeit.instant(.{ .source = .{ .unix_timestamp = 0 } })).time(); (try zeit.instant(.{ .source = .{ .unix_timestamp = 0 } })).timestamp;
const earliest_timestamp = earliest_date.instant().unixTimestamp();
const latest_timestamp = latest_date.instant().unixTimestamp();
var view_params = try root.put("scrobbles", .array); var view_params = try root.put("scrobbles", .array);
//var job_params = try job.params.put("scrobbles", .array);
var skipped_tracks: u64 = 0; var skipped_tracks: u64 = 0;
var limited_tracks: u64 = 0; var limited_tracks: u64 = 0;
const imported_scrobbles: []Data.UnifiedScrobble = switch (source) { const imported_scrobbles: []Data.UnifiedScrobble = switch (params.source) {
0, 1 => try Utils.scrobbleIngest(request.allocator, if (try request.file("upload")) |file| file.content else unreachable), .LFMS, .Spotify => try Utils.scrobbleIngest(request.allocator, if (try request.file("upload")) |file| file.content else unreachable),
2 => blk: { .LFMW => blk: {
const user_agent: []const u8 = "Zuletzt/0.0.1"; const user_agent: []const u8 = "Zuletzt/0.0.1";
var client = Client{ .allocator = request.allocator }; var client = Client{ .allocator = request.allocator };
var lastfm_response_buffer = std.ArrayList(u8).init(request.allocator); var lastfm_response_buffer = std.ArrayList(u8).init(request.allocator);
var scrobble_buffer = std.ArrayList(Data.UnifiedScrobble).init(request.allocator); var scrobble_buffer = std.ArrayList(Data.UnifiedScrobble).init(request.allocator);
const username = if (params.getT(.string, "username")) |un| un else "VAOTM"; const username = if (params.username) |un| un else "VAOTM";
var page: usize = 1; var page: usize = 1;
//var max_pages: ?usize = null; //var max_pages: ?usize = null;
while (true) : (page += 1) { while (true) : (page += 1) {
if (page > 91) break; if (page > 91) break;
const query: []const u8 = try std.fmt.allocPrint(request.allocator, "https://ws.audioscrobbler.com/2.0/?method=user.getrecenttracks&user={s}&api_key=b0c410a48a6078a651e0832699e3cd41&from={}&to={}&page={}&limit=1000&format=json", .{ username, earliest_timestamp, latest_timestamp, page }); const query: []const u8 = try std.fmt.allocPrint(request.allocator, "https://ws.audioscrobbler.com/2.0/?method=user.getrecenttracks&user={s}&api_key=b0c410a48a6078a651e0832699e3cd41&from={}&to={}&page={}&limit=1000&format=json", .{ username, @divFloor(earliest_ts, std.time.ns_per_s), @divFloor(latest_ts, std.time.ns_per_s), page });
const r = try client.fetch(.{ .response_storage = .{ .dynamic = &lastfm_response_buffer }, .location = .{ .url = query }, .method = .GET, .headers = .{ .user_agent = .{ .override = user_agent } } }); const r = try client.fetch(.{ .response_storage = .{ .dynamic = &lastfm_response_buffer }, .location = .{ .url = query }, .method = .GET, .headers = .{ .user_agent = .{ .override = user_agent } } });
std.log.debug("{}: {}", .{ page, r }); std.log.debug("{}: {}", .{ page, r });
if (@intFromEnum(r.status) == 500) { if (@intFromEnum(r.status) == 500) {
@ -79,7 +85,6 @@ pub fn post(request: *jetzig.Request) !jetzig.View {
break :blk try scrobble_buffer.toOwnedSlice(); break :blk try scrobble_buffer.toOwnedSlice();
}, },
else => unreachable,
}; };
var artists = try job.params.put("artists", .object); var artists = try job.params.put("artists", .object);
@ -92,7 +97,7 @@ pub fn post(request: *jetzig.Request) !jetzig.View {
var hash_buffer = [_]u8{undefined} ** 20; // A minimum i64 needs 19 digits + 1 negative sign var hash_buffer = [_]u8{undefined} ** 20; // A minimum i64 needs 19 digits + 1 negative sign
appends: for (imported_scrobbles) |scrobble| { appends: for (imported_scrobbles) |scrobble| {
if (scrobble.date > latest_timestamp * std.time.ns_per_s or scrobble.date < earliest_timestamp * std.time.ns_per_s) { if (scrobble.date > latest_ts or scrobble.date < earliest_ts) {
limited_tracks += 1; limited_tracks += 1;
continue :appends; continue :appends;
} }

View file

@ -14,14 +14,29 @@
<label>Username</label> <label>Username</label>
<input type="text" name="username"/> <input type="text" name="username"/>
<fieldset> <fieldset>
<input type="radio" name="t" label="Last.fm" value="0" required>Last.fm</input> <input type="radio" name="source" label="Last.fm" value="LFMS" required>Last.fm</input>
<input type="radio" name="t" label="Spotify" value="1" required>Spotify</input> <input type="radio" name="source" label="Spotify" value="Spotify" required>Spotify</input>
<input type="radio" name="t" label="Last.fm (Web Auth)" value="2" required>Last.fm (WebAuth)</input> <input type="radio" name="source" label="Last.fm (Web Auth)" value="LFMW" required>Last.fm (WebAuth)</input>
<input type="checkbox" name="adv-opt" label="Advanced Options">Advanced Options</input> <input type="checkbox" id='adv-opt' name="adv-opt" label="Advanced Options">Advanced Options</input>
Limit to Scrobbles before: <input type="datetime-local" name="latest-date" label="date-before"></input> <div id="adv-opt-div" style="display: none;">
Limit to Scrobbles after: <input type="datetime-local" name="earliest-date" label="date-after"></input> Limit to Scrobbles before: <input type="datetime-local" name="latest_date" label="date-before"></input>
Limit to Scrobbles after: <input type="datetime-local" name="earliest_date" label="date-after"></input>
</div>
</fieldset> </fieldset>
</form> </form>
</body> </body>
</html> </html>
<script>
const check = document.getElementById('adv-opt');
const box = document.getElementById('adv-opt-div')
check.addEventListener('click', function handleClick() {
if (check.checked) {
box.style.display = 'block'; // Element is shown
} else {
box.style.display = 'none'; // Element is hidden
}
});
</script>