replace zxq.co/ripple/hanayo
This commit is contained in:
144
vendor/github.com/osuripple/cheesegull/api/api.go
generated
vendored
Normal file
144
vendor/github.com/osuripple/cheesegull/api/api.go
generated
vendored
Normal file
@@ -0,0 +1,144 @@
|
||||
// Package api contains the general framework for writing handlers in the
|
||||
// CheeseGull API.
|
||||
package api
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"log"
|
||||
"net/http"
|
||||
"os"
|
||||
"runtime/debug"
|
||||
"time"
|
||||
|
||||
raven "github.com/getsentry/raven-go"
|
||||
"github.com/julienschmidt/httprouter"
|
||||
|
||||
"github.com/osuripple/cheesegull/downloader"
|
||||
"github.com/osuripple/cheesegull/housekeeper"
|
||||
)
|
||||
|
||||
// Context is the information that is passed to all request handlers in relation
|
||||
// to the request, and how to answer it.
|
||||
type Context struct {
|
||||
Request *http.Request
|
||||
DB *sql.DB
|
||||
SearchDB *sql.DB
|
||||
House *housekeeper.House
|
||||
DLClient *downloader.Client
|
||||
writer http.ResponseWriter
|
||||
params httprouter.Params
|
||||
}
|
||||
|
||||
// Write writes content to the response body.
|
||||
func (c *Context) Write(b []byte) (int, error) {
|
||||
return c.writer.Write(b)
|
||||
}
|
||||
|
||||
// ReadHeader reads a header from the request.
|
||||
func (c *Context) ReadHeader(s string) string {
|
||||
return c.Request.Header.Get(s)
|
||||
}
|
||||
|
||||
// WriteHeader sets a header in the response.
|
||||
func (c *Context) WriteHeader(key, value string) {
|
||||
c.writer.Header().Set(key, value)
|
||||
}
|
||||
|
||||
// Code sets the response's code.
|
||||
func (c *Context) Code(i int) {
|
||||
c.writer.WriteHeader(i)
|
||||
}
|
||||
|
||||
// Param retrieves a parameter in the URL's path.
|
||||
func (c *Context) Param(s string) string {
|
||||
return c.params.ByName(s)
|
||||
}
|
||||
|
||||
// WriteJSON writes JSON to the response.
|
||||
func (c *Context) WriteJSON(code int, v interface{}) error {
|
||||
c.WriteHeader("Content-Type", "application/json; charset=utf-8")
|
||||
c.Code(code)
|
||||
return json.NewEncoder(c.writer).Encode(v)
|
||||
}
|
||||
|
||||
var envSentryDSN = os.Getenv("SENTRY_DSN")
|
||||
|
||||
// Err attempts to log an error to Sentry, as well as stdout.
|
||||
func (c *Context) Err(err error) {
|
||||
if err == nil {
|
||||
return
|
||||
}
|
||||
if envSentryDSN != "" {
|
||||
raven.CaptureError(err, nil, raven.NewHttp(c.Request))
|
||||
}
|
||||
log.Println(err)
|
||||
}
|
||||
|
||||
type handlerPath struct {
|
||||
method, path string
|
||||
f func(c *Context)
|
||||
}
|
||||
|
||||
var handlers []handlerPath
|
||||
|
||||
// GET registers a handler for a GET request.
|
||||
func GET(path string, f func(c *Context)) {
|
||||
handlers = append(handlers, handlerPath{"GET", path, f})
|
||||
}
|
||||
|
||||
// POST registers a handler for a POST request.
|
||||
func POST(path string, f func(c *Context)) {
|
||||
handlers = append(handlers, handlerPath{"POST", path, f})
|
||||
}
|
||||
|
||||
// CreateHandler creates a new http.Handler using the handlers registered
|
||||
// through GET and POST.
|
||||
func CreateHandler(db, searchDB *sql.DB, house *housekeeper.House, dlc *downloader.Client) http.Handler {
|
||||
r := httprouter.New()
|
||||
for _, h := range handlers {
|
||||
// Create local copy that we know won't change as the loop proceeds.
|
||||
h := h
|
||||
r.Handle(h.method, h.path, func(w http.ResponseWriter, r *http.Request, p httprouter.Params) {
|
||||
start := time.Now()
|
||||
ctx := &Context{
|
||||
Request: r,
|
||||
DB: db,
|
||||
SearchDB: searchDB,
|
||||
House: house,
|
||||
DLClient: dlc,
|
||||
writer: w,
|
||||
params: p,
|
||||
}
|
||||
defer func() {
|
||||
err := recover()
|
||||
if err == nil {
|
||||
return
|
||||
}
|
||||
switch err := err.(type) {
|
||||
case error:
|
||||
ctx.Err(err)
|
||||
case stringer:
|
||||
ctx.Err(errors.New(err.String()))
|
||||
case string:
|
||||
ctx.Err(errors.New(err))
|
||||
default:
|
||||
log.Println("PANIC", err)
|
||||
}
|
||||
debug.PrintStack()
|
||||
}()
|
||||
h.f(ctx)
|
||||
log.Printf("[R] %-10s %-4s %s\n",
|
||||
time.Since(start).String(),
|
||||
r.Method,
|
||||
r.URL.Path,
|
||||
)
|
||||
})
|
||||
}
|
||||
return r
|
||||
}
|
||||
|
||||
type stringer interface {
|
||||
String() string
|
||||
}
|
133
vendor/github.com/osuripple/cheesegull/api/download/download.go
generated
vendored
Normal file
133
vendor/github.com/osuripple/cheesegull/api/download/download.go
generated
vendored
Normal file
@@ -0,0 +1,133 @@
|
||||
// Package download handles the API call to download an osu! beatmap set.
|
||||
package download
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/osuripple/cheesegull/api"
|
||||
"github.com/osuripple/cheesegull/downloader"
|
||||
"github.com/osuripple/cheesegull/housekeeper"
|
||||
"github.com/osuripple/cheesegull/models"
|
||||
)
|
||||
|
||||
func errorMessage(c *api.Context, code int, err string) {
|
||||
c.WriteHeader("Content-Type", "text/plain; charset=utf-8")
|
||||
c.Code(code)
|
||||
c.Write([]byte(err))
|
||||
}
|
||||
|
||||
func existsQueryKey(c *api.Context, s string) bool {
|
||||
_, ok := c.Request.URL.Query()[s]
|
||||
return ok
|
||||
}
|
||||
|
||||
// Download is the handler for a request to download a beatmap
|
||||
func Download(c *api.Context) {
|
||||
// get the beatmap ID
|
||||
id, err := strconv.Atoi(c.Param("id"))
|
||||
if err != nil {
|
||||
errorMessage(c, 400, "Malformed ID")
|
||||
return
|
||||
}
|
||||
|
||||
// fetch beatmap set and make sure it exists.
|
||||
set, err := models.FetchSet(c.DB, id, false)
|
||||
if err != nil {
|
||||
c.Err(err)
|
||||
errorMessage(c, 500, "Could not fetch set")
|
||||
return
|
||||
}
|
||||
if set == nil {
|
||||
errorMessage(c, 404, "Set not found")
|
||||
return
|
||||
}
|
||||
|
||||
// use novideo only when we are requested to get a beatmap having a video
|
||||
// and novideo is in the request
|
||||
noVideo := set.HasVideo && existsQueryKey(c, "novideo")
|
||||
|
||||
cbm, shouldDownload := c.House.AcquireBeatmap(&housekeeper.CachedBeatmap{
|
||||
ID: id,
|
||||
NoVideo: noVideo,
|
||||
LastUpdate: set.LastUpdate,
|
||||
})
|
||||
|
||||
if shouldDownload {
|
||||
err := downloadBeatmap(c.DLClient, cbm, c.House)
|
||||
if err != nil {
|
||||
c.Err(err)
|
||||
errorMessage(c, 500, "Internal error")
|
||||
return
|
||||
}
|
||||
} else {
|
||||
cbm.MustBeDownloaded()
|
||||
}
|
||||
|
||||
cbm.SetLastRequested(time.Now())
|
||||
|
||||
if cbm.FileSize() == 0 {
|
||||
errorMessage(c, 504, "The beatmap could not be downloaded (probably got deleted from the osu! website)")
|
||||
return
|
||||
}
|
||||
|
||||
f, err := cbm.File()
|
||||
if err != nil {
|
||||
c.Err(err)
|
||||
errorMessage(c, 500, "Internal error")
|
||||
return
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
c.WriteHeader("Content-Type", "application/octet-stream")
|
||||
c.WriteHeader("Content-Disposition", fmt.Sprintf("attachment; filename=%q", fmt.Sprintf("%d %s - %s.osz", set.ID, set.Artist, set.Title)))
|
||||
c.WriteHeader("Content-Length", strconv.FormatUint(uint64(cbm.FileSize()), 10))
|
||||
c.Code(200)
|
||||
|
||||
_, err = io.Copy(c, f)
|
||||
if err != nil {
|
||||
c.Err(err)
|
||||
}
|
||||
}
|
||||
|
||||
func downloadBeatmap(c *downloader.Client, b *housekeeper.CachedBeatmap, house *housekeeper.House) error {
|
||||
log.Println("[⬇️]", b.String())
|
||||
|
||||
var fileSize uint64
|
||||
defer func() {
|
||||
// We need to wrap this inside a function because this way the arguments
|
||||
// to DownloadCompleted are actually evaluated during the defer call.
|
||||
b.DownloadCompleted(fileSize, house)
|
||||
}()
|
||||
|
||||
// Start downloading.
|
||||
r, err := c.Download(b.ID, b.NoVideo)
|
||||
if err != nil {
|
||||
if err == downloader.ErrNoRedirect {
|
||||
return nil
|
||||
}
|
||||
return err
|
||||
}
|
||||
defer r.Close()
|
||||
|
||||
// open the file we will write the beatmap into
|
||||
f, err := b.CreateFile()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
fSizeRaw, err := io.Copy(f, r)
|
||||
fileSize = uint64(fSizeRaw)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func init() {
|
||||
api.GET("/d/:id", Download)
|
||||
}
|
21
vendor/github.com/osuripple/cheesegull/api/index.go
generated
vendored
Normal file
21
vendor/github.com/osuripple/cheesegull/api/index.go
generated
vendored
Normal file
@@ -0,0 +1,21 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"expvar"
|
||||
)
|
||||
|
||||
func index(c *Context) {
|
||||
c.WriteHeader("Content-Type", "text/plain; charset=utf-8")
|
||||
c.Write([]byte("CheeseGull v2.x Woo\nFor more information: https://github.com/osuripple/cheesegull"))
|
||||
}
|
||||
|
||||
var _evh = expvar.Handler()
|
||||
|
||||
func expvarHandler(c *Context) {
|
||||
_evh.ServeHTTP(c.writer, c.Request)
|
||||
}
|
||||
|
||||
func init() {
|
||||
GET("/", index)
|
||||
GET("/expvar", expvarHandler)
|
||||
}
|
121
vendor/github.com/osuripple/cheesegull/api/metadata/single.go
generated
vendored
Normal file
121
vendor/github.com/osuripple/cheesegull/api/metadata/single.go
generated
vendored
Normal file
@@ -0,0 +1,121 @@
|
||||
// Package metadata handles API request that search for metadata regarding osu!
|
||||
// beatmaps.
|
||||
package metadata
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/osuripple/cheesegull/api"
|
||||
"github.com/osuripple/cheesegull/models"
|
||||
)
|
||||
|
||||
// Beatmap handles requests to retrieve single beatmaps.
|
||||
func Beatmap(c *api.Context) {
|
||||
id, _ := strconv.Atoi(strings.TrimSuffix(c.Param("id"), ".json"))
|
||||
if id == 0 {
|
||||
c.WriteJSON(404, nil)
|
||||
return
|
||||
}
|
||||
|
||||
bms, err := models.FetchBeatmaps(c.DB, id)
|
||||
if err != nil {
|
||||
c.Err(err)
|
||||
c.WriteJSON(500, nil)
|
||||
return
|
||||
}
|
||||
if len(bms) == 0 {
|
||||
c.WriteJSON(404, nil)
|
||||
return
|
||||
}
|
||||
|
||||
c.WriteJSON(200, bms[0])
|
||||
}
|
||||
|
||||
// Set handles requests to retrieve single beatmap sets.
|
||||
func Set(c *api.Context) {
|
||||
id, _ := strconv.Atoi(strings.TrimSuffix(c.Param("id"), ".json"))
|
||||
if id == 0 {
|
||||
c.WriteJSON(404, nil)
|
||||
return
|
||||
}
|
||||
|
||||
set, err := models.FetchSet(c.DB, id, true)
|
||||
if err != nil {
|
||||
c.Err(err)
|
||||
c.WriteJSON(500, nil)
|
||||
return
|
||||
}
|
||||
if set == nil {
|
||||
c.WriteJSON(404, nil)
|
||||
return
|
||||
}
|
||||
|
||||
c.WriteJSON(200, set)
|
||||
}
|
||||
|
||||
func mustInt(s string) int {
|
||||
i, _ := strconv.Atoi(s)
|
||||
return i
|
||||
}
|
||||
|
||||
func mustPositive(i int) int {
|
||||
if i < 0 {
|
||||
return 0
|
||||
}
|
||||
return i
|
||||
}
|
||||
|
||||
func intWithBounds(i, min, max, def int) int {
|
||||
if i == 0 {
|
||||
return def
|
||||
}
|
||||
if i < min {
|
||||
return min
|
||||
}
|
||||
if i > max {
|
||||
return max
|
||||
}
|
||||
return i
|
||||
}
|
||||
|
||||
func sIntWithBounds(strs []string, min, max int) []int {
|
||||
sInt := make([]int, 0, len(strs))
|
||||
for _, s := range strs {
|
||||
i, err := strconv.Atoi(s)
|
||||
if err != nil || i < min || i > max {
|
||||
continue
|
||||
}
|
||||
sInt = append(sInt, i)
|
||||
}
|
||||
return sInt
|
||||
}
|
||||
|
||||
// Search does a search on the sets available in the database.
|
||||
func Search(c *api.Context) {
|
||||
query := c.Request.URL.Query()
|
||||
sets, err := models.SearchSets(c.DB, c.SearchDB, models.SearchOptions{
|
||||
Status: sIntWithBounds(query["status"], -2, 4),
|
||||
Query: query.Get("query"),
|
||||
Mode: sIntWithBounds(query["mode"], 0, 3),
|
||||
|
||||
Amount: intWithBounds(mustInt(query.Get("amount")), 1, 100, 50),
|
||||
Offset: mustPositive(mustInt(query.Get("offset"))),
|
||||
})
|
||||
if err != nil {
|
||||
c.Err(err)
|
||||
c.WriteJSON(500, nil)
|
||||
return
|
||||
}
|
||||
|
||||
c.WriteJSON(200, sets)
|
||||
}
|
||||
|
||||
func init() {
|
||||
api.GET("/api/b/:id", Beatmap)
|
||||
api.GET("/b/:id", Beatmap)
|
||||
api.GET("/api/s/:id", Set)
|
||||
api.GET("/s/:id", Set)
|
||||
|
||||
api.GET("/api/search", Search)
|
||||
}
|
Reference in New Issue
Block a user