161 lines
4.3 KiB
Go
161 lines
4.3 KiB
Go
|
package models
|
||
|
|
||
|
import (
|
||
|
"database/sql"
|
||
|
"time"
|
||
|
)
|
||
|
|
||
|
// Set represents a set of beatmaps usually sharing the same song.
|
||
|
type Set struct {
|
||
|
ID int `json:"SetID"`
|
||
|
ChildrenBeatmaps []Beatmap
|
||
|
RankedStatus int
|
||
|
ApprovedDate time.Time
|
||
|
LastUpdate time.Time
|
||
|
LastChecked time.Time
|
||
|
Artist string
|
||
|
Title string
|
||
|
Creator string
|
||
|
Source string
|
||
|
Tags string
|
||
|
HasVideo bool
|
||
|
Genre int
|
||
|
Language int
|
||
|
Favourites int
|
||
|
}
|
||
|
|
||
|
const setFields = `id, ranked_status, approved_date, last_update, last_checked,
|
||
|
artist, title, creator, source, tags, has_video, genre,
|
||
|
language, favourites`
|
||
|
|
||
|
// FetchSetsForBatchUpdate fetches limit sets from the database, sorted by
|
||
|
// LastChecked (asc, older first). Results are further filtered: if the set's
|
||
|
// RankedStatus is 3, 0 or -1 (qualified, pending or WIP), at least 30 minutes
|
||
|
// must have passed from LastChecked. For all other statuses, at least 4 days
|
||
|
// must have passed from LastChecked.
|
||
|
func FetchSetsForBatchUpdate(db *sql.DB, limit int) ([]Set, error) {
|
||
|
n := time.Now()
|
||
|
rows, err := db.Query(`
|
||
|
SELECT `+setFields+` FROM sets
|
||
|
WHERE (ranked_status IN (3, 0, -1) AND last_checked <= ?) OR last_checked <= ?
|
||
|
ORDER BY last_checked ASC
|
||
|
LIMIT ?`,
|
||
|
n.Add(-time.Minute*30),
|
||
|
n.Add(-time.Hour*24*4),
|
||
|
limit,
|
||
|
)
|
||
|
if err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
|
||
|
sets := make([]Set, 0, limit)
|
||
|
for rows.Next() {
|
||
|
var s Set
|
||
|
err = rows.Scan(
|
||
|
&s.ID, &s.RankedStatus, &s.ApprovedDate, &s.LastUpdate, &s.LastChecked,
|
||
|
&s.Artist, &s.Title, &s.Creator, &s.Source, &s.Tags, &s.HasVideo, &s.Genre,
|
||
|
&s.Language, &s.Favourites,
|
||
|
)
|
||
|
if err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
sets = append(sets, s)
|
||
|
}
|
||
|
|
||
|
return sets, rows.Err()
|
||
|
}
|
||
|
|
||
|
// FetchSet retrieves a single set to show, alongside its children beatmaps.
|
||
|
func FetchSet(db *sql.DB, id int, withChildren bool) (*Set, error) {
|
||
|
var s Set
|
||
|
err := db.QueryRow(`SELECT `+setFields+` FROM sets WHERE id = ? LIMIT 1`, id).Scan(
|
||
|
&s.ID, &s.RankedStatus, &s.ApprovedDate, &s.LastUpdate, &s.LastChecked,
|
||
|
&s.Artist, &s.Title, &s.Creator, &s.Source, &s.Tags, &s.HasVideo, &s.Genre,
|
||
|
&s.Language, &s.Favourites,
|
||
|
)
|
||
|
switch err {
|
||
|
case nil:
|
||
|
break // carry on
|
||
|
case sql.ErrNoRows:
|
||
|
// silently ignore no rows, and just don't return anything
|
||
|
return nil, nil
|
||
|
default:
|
||
|
return nil, err
|
||
|
}
|
||
|
|
||
|
if !withChildren {
|
||
|
return &s, nil
|
||
|
}
|
||
|
|
||
|
rows, err := db.Query(`SELECT `+beatmapFields+` FROM beatmaps WHERE parent_set_id = ?`, s.ID)
|
||
|
if err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
s.ChildrenBeatmaps, err = readBeatmapsFromRows(rows, 8)
|
||
|
return &s, err
|
||
|
}
|
||
|
|
||
|
// DeleteSet deletes a set from the database, removing also its children
|
||
|
// beatmaps.
|
||
|
func DeleteSet(db *sql.DB, set int) error {
|
||
|
_, err := db.Exec("DELETE FROM beatmaps WHERE parent_set_id = ?", set)
|
||
|
if err != nil {
|
||
|
return err
|
||
|
}
|
||
|
_, err = db.Exec("DELETE FROM sets WHERE id = ?", set)
|
||
|
return err
|
||
|
}
|
||
|
|
||
|
// createSetModes will generate the correct value for setModes, which is
|
||
|
// basically a bitwise enum containing the modes that are on a certain set.
|
||
|
func createSetModes(bms []Beatmap) (setModes uint8) {
|
||
|
for _, bm := range bms {
|
||
|
m := bm.Mode
|
||
|
if m < 0 || m >= 4 {
|
||
|
continue
|
||
|
}
|
||
|
setModes |= 1 << uint(m)
|
||
|
}
|
||
|
return setModes
|
||
|
}
|
||
|
|
||
|
// CreateSet creates (and updates) a beatmap set in the database.
|
||
|
func CreateSet(db *sql.DB, s Set) error {
|
||
|
// delete existing set, if any.
|
||
|
// This is mostly a lazy way to make sure updates work as well.
|
||
|
err := DeleteSet(db, s.ID)
|
||
|
if err != nil {
|
||
|
return err
|
||
|
}
|
||
|
|
||
|
_, err = db.Exec(`
|
||
|
INSERT INTO sets(
|
||
|
id, ranked_status, approved_date, last_update, last_checked,
|
||
|
artist, title, creator, source, tags, has_video, genre,
|
||
|
language, favourites, set_modes
|
||
|
)
|
||
|
VALUES (
|
||
|
?, ?, ?, ?, ?,
|
||
|
?, ?, ?, ?, ?, ?, ?,
|
||
|
?, ?, ?
|
||
|
)`, s.ID, s.RankedStatus, s.ApprovedDate, s.LastUpdate, s.LastChecked,
|
||
|
s.Artist, s.Title, s.Creator, s.Source, s.Tags, s.HasVideo, s.Genre,
|
||
|
s.Language, s.Favourites, createSetModes(s.ChildrenBeatmaps))
|
||
|
if err != nil {
|
||
|
return err
|
||
|
}
|
||
|
|
||
|
return CreateBeatmaps(db, s.ChildrenBeatmaps...)
|
||
|
}
|
||
|
|
||
|
// BiggestSetID retrieves the biggest set ID in the sets database. This is used
|
||
|
// by discovery to have a starting point from which to discover new beatmaps.
|
||
|
func BiggestSetID(db *sql.DB) (int, error) {
|
||
|
var i int
|
||
|
err := db.QueryRow("SELECT id FROM sets ORDER BY id DESC LIMIT 1").Scan(&i)
|
||
|
if err == sql.ErrNoRows {
|
||
|
return 0, nil
|
||
|
}
|
||
|
return i, err
|
||
|
}
|