fixed some go vet warnings, fixed formatting
This commit is contained in:
parent
e4bb7b5b35
commit
4b9b354171
13
Makefile
13
Makefile
@ -7,3 +7,16 @@ magneticow:
|
|||||||
# TODO: minify files!
|
# TODO: minify files!
|
||||||
go-bindata -o="magneticow/bindata.go" -prefix="magneticow/data/" magneticow/data/...
|
go-bindata -o="magneticow/bindata.go" -prefix="magneticow/data/" magneticow/data/...
|
||||||
go install magnetico/magneticow
|
go install magnetico/magneticow
|
||||||
|
|
||||||
|
test:
|
||||||
|
go test github.com/boramalper/magnetico/cmd/magneticod/...
|
||||||
|
@echo
|
||||||
|
go test github.com/boramalper/magnetico/cmd/magneticow/...
|
||||||
|
@echo
|
||||||
|
go test github.com/boramalper/magnetico/pkg/persistence/...
|
||||||
|
|
||||||
|
format:
|
||||||
|
gofmt -w cmd/magneticod
|
||||||
|
gofmt -w cmd/magneticow
|
||||||
|
gofmt -w pkg/persistence
|
||||||
|
|
||||||
|
@ -37,12 +37,12 @@ type extDict struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type Leech struct {
|
type Leech struct {
|
||||||
infoHash [20]byte
|
infoHash [20]byte
|
||||||
peerAddr *net.TCPAddr
|
peerAddr *net.TCPAddr
|
||||||
ev LeechEventHandlers
|
ev LeechEventHandlers
|
||||||
|
|
||||||
conn *net.TCPConn
|
conn *net.TCPConn
|
||||||
clientID [20]byte
|
clientID [20]byte
|
||||||
|
|
||||||
ut_metadata uint8
|
ut_metadata uint8
|
||||||
metadataReceived, metadataSize uint
|
metadataReceived, metadataSize uint
|
||||||
@ -50,8 +50,8 @@ type Leech struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type LeechEventHandlers struct {
|
type LeechEventHandlers struct {
|
||||||
OnSuccess func(Metadata) // must be supplied. args: metadata
|
OnSuccess func(Metadata) // must be supplied. args: metadata
|
||||||
OnError func([20]byte, error) // must be supplied. args: infohash, error
|
OnError func([20]byte, error) // must be supplied. args: infohash, error
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewLeech(infoHash [20]byte, peerAddr *net.TCPAddr, ev LeechEventHandlers) *Leech {
|
func NewLeech(infoHash [20]byte, peerAddr *net.TCPAddr, ev LeechEventHandlers) *Leech {
|
||||||
@ -86,7 +86,9 @@ func (l *Leech) doBtHandshake() error {
|
|||||||
))
|
))
|
||||||
|
|
||||||
// ASSERTION
|
// ASSERTION
|
||||||
if len(lHandshake) != 68 { panic(fmt.Sprintf("len(lHandshake) == %d", len(lHandshake))) }
|
if len(lHandshake) != 68 {
|
||||||
|
panic(fmt.Sprintf("len(lHandshake) == %d", len(lHandshake)))
|
||||||
|
}
|
||||||
|
|
||||||
err := l.writeAll(lHandshake)
|
err := l.writeAll(lHandshake)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -148,15 +150,15 @@ func (l *Leech) doExHandshake() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (l *Leech) requestAllPieces() error {
|
func (l *Leech) requestAllPieces() error {
|
||||||
/*
|
// reqq
|
||||||
* reqq
|
// An integer, the number of outstanding request messages this client supports without
|
||||||
* An integer, the number of outstanding request messages this client supports without
|
// dropping any. The default in in libtorrent is 250.
|
||||||
* dropping any. The default in in libtorrent is 250.
|
//
|
||||||
* "handshake message" @ "Extension Protocol" @ http://www.bittorrent.org/beps/bep_0010.html
|
// "handshake message" @ "Extension Protocol" @ http://www.bittorrent.org/beps/bep_0010.html
|
||||||
*
|
//
|
||||||
* TODO: maybe by requesting all pieces at once we are exceeding this limit? maybe we should
|
// TODO: maybe by requesting all pieces at once we are exceeding this limit? maybe we should
|
||||||
* request as we receive pieces?
|
// request as we receive pieces?
|
||||||
*/
|
|
||||||
// Request all the pieces of metadata
|
// Request all the pieces of metadata
|
||||||
nPieces := int(math.Ceil(float64(l.metadataSize) / math.Pow(2, 14)))
|
nPieces := int(math.Ceil(float64(l.metadataSize) / math.Pow(2, 14)))
|
||||||
for piece := 0; piece < nPieces; piece++ {
|
for piece := 0; piece < nPieces; piece++ {
|
||||||
@ -166,13 +168,13 @@ func (l *Leech) requestAllPieces() error {
|
|||||||
MsgType: 0,
|
MsgType: 0,
|
||||||
Piece: piece,
|
Piece: piece,
|
||||||
})
|
})
|
||||||
if err != nil { // ASSERT
|
if err != nil { // ASSERT
|
||||||
panic(errors.Wrap(err, "marshal extDict"))
|
panic(errors.Wrap(err, "marshal extDict"))
|
||||||
}
|
}
|
||||||
|
|
||||||
err = l.writeAll([]byte(fmt.Sprintf(
|
err = l.writeAll([]byte(fmt.Sprintf(
|
||||||
"%s\x14%s%s",
|
"%s\x14%s%s",
|
||||||
toBigEndian(uint(2 + len(extDictDump)), 4),
|
toBigEndian(uint(2+len(extDictDump)), 4),
|
||||||
toBigEndian(uint(l.ut_metadata), 1),
|
toBigEndian(uint(l.ut_metadata), 1),
|
||||||
extDictDump,
|
extDictDump,
|
||||||
)))
|
)))
|
||||||
@ -213,7 +215,7 @@ func (l *Leech) readExMessage() ([]byte, error) {
|
|||||||
|
|
||||||
// Every extension message has at least 2 bytes.
|
// Every extension message has at least 2 bytes.
|
||||||
if len(rMessage) < 2 {
|
if len(rMessage) < 2 {
|
||||||
continue;
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
// We are interested only in extension messages, whose first byte is always 20
|
// We are interested only in extension messages, whose first byte is always 20
|
||||||
@ -343,7 +345,7 @@ func (l *Leech) Do(deadline time.Time) {
|
|||||||
l.OnError(fmt.Errorf("remote peer rejected sending metadata"))
|
l.OnError(fmt.Errorf("remote peer rejected sending metadata"))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if rExtDict.MsgType == 1 { // data
|
if rExtDict.MsgType == 1 { // data
|
||||||
// Get the unread bytes!
|
// Get the unread bytes!
|
||||||
metadataPiece := rMessageBuf.Bytes()
|
metadataPiece := rMessageBuf.Bytes()
|
||||||
@ -358,12 +360,12 @@ func (l *Leech) Do(deadline time.Time) {
|
|||||||
l.OnError(fmt.Errorf("metadataPiece > 16kiB"))
|
l.OnError(fmt.Errorf("metadataPiece > 16kiB"))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
piece := rExtDict.Piece
|
piece := rExtDict.Piece
|
||||||
// metadata[piece * 2**14: piece * 2**14 + len(metadataPiece)] = metadataPiece is how it'd be done in Python
|
// metadata[piece * 2**14: piece * 2**14 + len(metadataPiece)] = metadataPiece is how it'd be done in Python
|
||||||
copy(l.metadata[piece*int(math.Pow(2, 14)):piece*int(math.Pow(2, 14))+len(metadataPiece)], metadataPiece)
|
copy(l.metadata[piece*int(math.Pow(2, 14)):piece*int(math.Pow(2, 14))+len(metadataPiece)], metadataPiece)
|
||||||
l.metadataReceived += uint(len(metadataPiece))
|
l.metadataReceived += uint(len(metadataPiece))
|
||||||
|
|
||||||
// ... if the length of @metadataPiece is less than 16kiB AND metadata is NOT
|
// ... if the length of @metadataPiece is less than 16kiB AND metadata is NOT
|
||||||
// complete then we err.
|
// complete then we err.
|
||||||
if len(metadataPiece) < 16*1024 && l.metadataReceived != l.metadataSize {
|
if len(metadataPiece) < 16*1024 && l.metadataReceived != l.metadataSize {
|
||||||
@ -377,14 +379,14 @@ func (l *Leech) Do(deadline time.Time) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Verify the checksum
|
// Verify the checksum
|
||||||
sha1Sum := sha1.Sum(l.metadata)
|
sha1Sum := sha1.Sum(l.metadata)
|
||||||
if !bytes.Equal(sha1Sum[:], l.infoHash[:]) {
|
if !bytes.Equal(sha1Sum[:], l.infoHash[:]) {
|
||||||
l.OnError(fmt.Errorf("infohash mismatch"))
|
l.OnError(fmt.Errorf("infohash mismatch"))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check the info dictionary
|
// Check the info dictionary
|
||||||
info := new(metainfo.Info)
|
info := new(metainfo.Info)
|
||||||
err = bencode.Unmarshal(l.metadata, info)
|
err = bencode.Unmarshal(l.metadata, info)
|
||||||
@ -420,7 +422,7 @@ func (l *Leech) Do(deadline time.Time) {
|
|||||||
l.OnError(fmt.Errorf("file size less than zero"))
|
l.OnError(fmt.Errorf("file size less than zero"))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
totalSize += uint64(file.Size)
|
totalSize += uint64(file.Size)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -483,4 +485,3 @@ func toBigEndian(i uint, n int) []byte {
|
|||||||
|
|
||||||
return b
|
return b
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -7,23 +7,23 @@ import (
|
|||||||
"github.com/anacrolix/torrent/bencode"
|
"github.com/anacrolix/torrent/bencode"
|
||||||
)
|
)
|
||||||
|
|
||||||
var operationsTest_instances = []struct{
|
var operationsTest_instances = []struct {
|
||||||
dump []byte
|
dump []byte
|
||||||
surplus []byte
|
surplus []byte
|
||||||
}{
|
}{
|
||||||
// No Surplus
|
// No Surplus
|
||||||
{
|
{
|
||||||
dump: []byte("d1:md11:ut_metadatai1ee13:metadata_sizei22528ee"),
|
dump: []byte("d1:md11:ut_metadatai1ee13:metadata_sizei22528ee"),
|
||||||
surplus: []byte(""),
|
surplus: []byte(""),
|
||||||
},
|
},
|
||||||
// Surplus is an ASCII string
|
// Surplus is an ASCII string
|
||||||
{
|
{
|
||||||
dump: []byte("d1:md11:ut_metadatai1ee13:metadata_sizei22528eeDENEME"),
|
dump: []byte("d1:md11:ut_metadatai1ee13:metadata_sizei22528eeDENEME"),
|
||||||
surplus: []byte("DENEME"),
|
surplus: []byte("DENEME"),
|
||||||
},
|
},
|
||||||
// Surplus is a bencoded dictionary
|
// Surplus is a bencoded dictionary
|
||||||
{
|
{
|
||||||
dump: []byte("d1:md11:ut_metadatai1ee13:metadata_sizei22528eed3:inti1337ee"),
|
dump: []byte("d1:md11:ut_metadatai1ee13:metadata_sizei22528eed3:inti1337ee"),
|
||||||
surplus: []byte("d3:inti1337ee"),
|
surplus: []byte("d3:inti1337ee"),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@ -31,14 +31,14 @@ var operationsTest_instances = []struct{
|
|||||||
func TestDecoder(t *testing.T) {
|
func TestDecoder(t *testing.T) {
|
||||||
for i, instance := range operationsTest_instances {
|
for i, instance := range operationsTest_instances {
|
||||||
buf := bytes.NewBuffer(instance.dump)
|
buf := bytes.NewBuffer(instance.dump)
|
||||||
err := bencode.NewDecoder(buf).Decode(&struct {}{})
|
err := bencode.NewDecoder(buf).Decode(&struct{}{})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Errorf("Couldn't decode the dump #%d! %s", i + 1, err.Error())
|
t.Errorf("Couldn't decode the dump #%d! %s", i+1, err.Error())
|
||||||
}
|
}
|
||||||
|
|
||||||
bufSurplus := buf.Bytes()
|
bufSurplus := buf.Bytes()
|
||||||
if !bytes.Equal(bufSurplus, instance.surplus) {
|
if !bytes.Equal(bufSurplus, instance.surplus) {
|
||||||
t.Errorf("Surplus #%d is not equal to what we expected! `%s`", i + 1, bufSurplus)
|
t.Errorf("Surplus #%d is not equal to what we expected! `%s`", i+1, bufSurplus)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -64,7 +64,7 @@ func (ms *Sink) Sink(res mainline.TrawlingResult) {
|
|||||||
|
|
||||||
leech := NewLeech(res.InfoHash, res.PeerAddr, LeechEventHandlers{
|
leech := NewLeech(res.InfoHash, res.PeerAddr, LeechEventHandlers{
|
||||||
OnSuccess: ms.flush,
|
OnSuccess: ms.flush,
|
||||||
OnError: ms.onLeechError,
|
OnError: ms.onLeechError,
|
||||||
})
|
})
|
||||||
go leech.Do(time.Now().Add(ms.deadline))
|
go leech.Do(time.Now().Add(ms.deadline))
|
||||||
|
|
||||||
|
@ -258,7 +258,6 @@ func (e *Error) UnmarshalBencode(b []byte) (err error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if len(matches[2]) != msgLen {
|
if len(matches[2]) != msgLen {
|
||||||
return
|
|
||||||
return fmt.Errorf("error message have different lengths (%d vs %d) \"%s\"!", len(matches[2]), msgLen, matches[2])
|
return fmt.Errorf("error message have different lengths (%d vs %d) \"%s\"!", len(matches[2]), msgLen, matches[2])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -221,9 +221,8 @@ func (p *Protocol) CalculateToken(address net.IP) []byte {
|
|||||||
func (p *Protocol) VerifyToken(address net.IP, token []byte) bool {
|
func (p *Protocol) VerifyToken(address net.IP, token []byte) bool {
|
||||||
p.tokenLock.Lock()
|
p.tokenLock.Lock()
|
||||||
defer p.tokenLock.Unlock()
|
defer p.tokenLock.Unlock()
|
||||||
|
// TODO: implement VerifyToken()
|
||||||
panic("VerifyToken() not implemented yet!")
|
panic("VerifyToken() not implemented yet!")
|
||||||
// TODO
|
|
||||||
return false
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *Protocol) updateTokenSecret() {
|
func (p *Protocol) updateTokenSecret() {
|
||||||
|
@ -113,7 +113,7 @@ func (s *TrawlingService) bootstrap() {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
zap.L().Error("Could NOT resolve (UDP) address of the bootstrapping node!",
|
zap.L().Error("Could NOT resolve (UDP) address of the bootstrapping node!",
|
||||||
zap.String("node", node))
|
zap.String("node", node))
|
||||||
continue;
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
s.protocol.SendMessage(NewFindNodeQuery(s.trueNodeID, target), addr)
|
s.protocol.SendMessage(NewFindNodeQuery(s.trueNodeID, target), addr)
|
||||||
@ -208,11 +208,11 @@ func (s *TrawlingService) onCongestion() {
|
|||||||
*
|
*
|
||||||
* In case of congestion, decrease the maximum number of nodes to the 90% of the current value.
|
* In case of congestion, decrease the maximum number of nodes to the 90% of the current value.
|
||||||
*/
|
*/
|
||||||
if s.maxNeighbors < 200 {
|
if s.maxNeighbors < 200 {
|
||||||
zap.L().Warn("Max. number of neighbours are < 200 and there is still congestion!" +
|
zap.L().Warn("Max. number of neighbours are < 200 and there is still congestion!" +
|
||||||
"(check your network connection if this message recurs)")
|
"(check your network connection if this message recurs)")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
s.maxNeighbors = uint(float32(s.maxNeighbors) * 0.9)
|
s.maxNeighbors = uint(float32(s.maxNeighbors) * 0.9)
|
||||||
}
|
}
|
||||||
|
@ -36,8 +36,8 @@ func NewTransport(laddr string, onMessage func(*Message, *net.UDPAddr), onConges
|
|||||||
*
|
*
|
||||||
* https://en.wikipedia.org/wiki/User_Datagram_Protocol
|
* https://en.wikipedia.org/wiki/User_Datagram_Protocol
|
||||||
*/
|
*/
|
||||||
t.buffer = make([]byte, 65507)
|
t.buffer = make([]byte, 65507)
|
||||||
t.onMessage = onMessage
|
t.onMessage = onMessage
|
||||||
t.onCongestion = onCongestion
|
t.onCongestion = onCongestion
|
||||||
|
|
||||||
var err error
|
var err error
|
||||||
@ -83,7 +83,7 @@ func (t *Transport) Start() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (t *Transport) Terminate() {
|
func (t *Transport) Terminate() {
|
||||||
unix.Close(t.fd);
|
unix.Close(t.fd)
|
||||||
}
|
}
|
||||||
|
|
||||||
// readMessages is a goroutine!
|
// readMessages is a goroutine!
|
||||||
@ -131,7 +131,7 @@ func (t *Transport) WriteMessages(msg *Message, addr *net.UDPAddr) {
|
|||||||
if addrSA == nil {
|
if addrSA == nil {
|
||||||
zap.L().Debug("Wrong net address for the remote peer!",
|
zap.L().Debug("Wrong net address for the remote peer!",
|
||||||
zap.String("addr", addr.String()))
|
zap.String("addr", addr.String()))
|
||||||
return;
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
err = unix.Sendto(t.fd, data, 0, addrSA)
|
err = unix.Sendto(t.fd, data, 0, addrSA)
|
||||||
|
@ -37,7 +37,7 @@ type opFlags struct {
|
|||||||
TrawlerMlInterval time.Duration
|
TrawlerMlInterval time.Duration
|
||||||
|
|
||||||
Verbosity int
|
Verbosity int
|
||||||
Profile string
|
Profile string
|
||||||
}
|
}
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
@ -76,7 +76,7 @@ func main() {
|
|||||||
|
|
||||||
switch opFlags.Profile {
|
switch opFlags.Profile {
|
||||||
case "cpu":
|
case "cpu":
|
||||||
file, err := os.OpenFile("magneticod_cpu.prof", os.O_CREATE | os.O_WRONLY, 0755)
|
file, err := os.OpenFile("magneticod_cpu.prof", os.O_CREATE|os.O_WRONLY, 0755)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
zap.L().Panic("Could not open the cpu profile file!", zap.Error(err))
|
zap.L().Panic("Could not open the cpu profile file!", zap.Error(err))
|
||||||
}
|
}
|
||||||
@ -145,9 +145,9 @@ func parseFlags() (*opFlags, error) {
|
|||||||
if cmdF.DatabaseURL == "" {
|
if cmdF.DatabaseURL == "" {
|
||||||
opF.DatabaseURL =
|
opF.DatabaseURL =
|
||||||
"sqlite3://" +
|
"sqlite3://" +
|
||||||
appdirs.UserDataDir("magneticod", "", "", false) +
|
appdirs.UserDataDir("magneticod", "", "", false) +
|
||||||
"/database.sqlite3" +
|
"/database.sqlite3" +
|
||||||
"?_journal_mode=WAL" // https://github.com/mattn/go-sqlite3#connection-string
|
"?_journal_mode=WAL" // https://github.com/mattn/go-sqlite3#connection-string
|
||||||
} else {
|
} else {
|
||||||
opF.DatabaseURL = cmdF.DatabaseURL
|
opF.DatabaseURL = cmdF.DatabaseURL
|
||||||
}
|
}
|
||||||
|
@ -16,9 +16,8 @@ import (
|
|||||||
func apiTorrentsHandler(w http.ResponseWriter, r *http.Request) {
|
func apiTorrentsHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
// @lastOrderedValue AND @lastID are either both supplied or neither of them should be supplied
|
// @lastOrderedValue AND @lastID are either both supplied or neither of them should be supplied
|
||||||
// at all; and if that is NOT the case, then return an error.
|
// at all; and if that is NOT the case, then return an error.
|
||||||
if q := r.URL.Query(); !(
|
if q := r.URL.Query(); !((q.Get("lastOrderedValue") != "" && q.Get("lastID") != "") ||
|
||||||
(q.Get("lastOrderedValue") != "" && q.Get("lastID") != "") ||
|
(q.Get("lastOrderedValue") == "" && q.Get("lastID") == "")) {
|
||||||
(q.Get("lastOrderedValue") == "" && q.Get("lastID") == "")) {
|
|
||||||
respondError(w, 400, "`lastOrderedValue`, `lastID` must be supplied altogether, if supplied.")
|
respondError(w, 400, "`lastOrderedValue`, `lastID` must be supplied altogether, if supplied.")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -43,7 +42,7 @@ func apiTorrentsHandler(w http.ResponseWriter, r *http.Request) {
|
|||||||
|
|
||||||
if tq.Epoch == nil {
|
if tq.Epoch == nil {
|
||||||
tq.Epoch = new(int64)
|
tq.Epoch = new(int64)
|
||||||
*tq.Epoch = time.Now().Unix() // epoch, if not supplied, is NOW.
|
*tq.Epoch = time.Now().Unix() // epoch, if not supplied, is NOW.
|
||||||
} else if *tq.Epoch <= 0 {
|
} else if *tq.Epoch <= 0 {
|
||||||
respondError(w, 400, "epoch must be greater than 0")
|
respondError(w, 400, "epoch must be greater than 0")
|
||||||
return
|
return
|
||||||
@ -213,4 +212,4 @@ func parseOrderBy(s string) (persistence.OrderingCriteria, error) {
|
|||||||
default:
|
default:
|
||||||
return persistence.ByDiscoveredOn, fmt.Errorf("unknown orderBy string: %s", s)
|
return persistence.ByDiscoveredOn, fmt.Errorf("unknown orderBy string: %s", s)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -10,7 +10,6 @@ import (
|
|||||||
"github.com/gorilla/mux"
|
"github.com/gorilla/mux"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
// DONE
|
// DONE
|
||||||
func rootHandler(w http.ResponseWriter, r *http.Request) {
|
func rootHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
nTorrents, err := database.GetNumberOfTorrents()
|
nTorrents, err := database.GetNumberOfTorrents()
|
||||||
@ -128,7 +127,7 @@ func feedHandler(w http.ResponseWriter, r *http.Request) {
|
|||||||
Title string
|
Title string
|
||||||
Torrents []persistence.TorrentMetadata
|
Torrents []persistence.TorrentMetadata
|
||||||
}{
|
}{
|
||||||
Title: title,
|
Title: title,
|
||||||
Torrents: torrents,
|
Torrents: torrents,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -38,10 +38,10 @@ var decoder = schema.NewDecoder()
|
|||||||
var templates map[string]*template.Template
|
var templates map[string]*template.Template
|
||||||
var database persistence.Database
|
var database persistence.Database
|
||||||
|
|
||||||
var opts struct{
|
var opts struct {
|
||||||
Addr string
|
Addr string
|
||||||
Database string
|
Database string
|
||||||
Credentials map[string][]byte // TODO: encapsulate credentials and mutex for safety
|
Credentials map[string][]byte // TODO: encapsulate credentials and mutex for safety
|
||||||
CredentialsRWMutex sync.RWMutex
|
CredentialsRWMutex sync.RWMutex
|
||||||
CredentialsPath string
|
CredentialsPath string
|
||||||
}
|
}
|
||||||
@ -77,9 +77,9 @@ func main() {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
opts.Credentials = make(map[string][]byte) // Clear opts.Credentials
|
opts.Credentials = make(map[string][]byte) // Clear opts.Credentials
|
||||||
opts.CredentialsRWMutex.Unlock()
|
opts.CredentialsRWMutex.Unlock()
|
||||||
if err := loadCred(opts.CredentialsPath); err != nil { // Reload credentials
|
if err := loadCred(opts.CredentialsPath); err != nil { // Reload credentials
|
||||||
zap.L().Warn("couldn't load credentials", zap.Error(err))
|
zap.L().Warn("couldn't load credentials", zap.Error(err))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -175,7 +175,6 @@ func respondError(w http.ResponseWriter, statusCode int, format string, a ...int
|
|||||||
w.Write([]byte(fmt.Sprintf(format, a...)))
|
w.Write([]byte(fmt.Sprintf(format, a...)))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
func mustAsset(name string) []byte {
|
func mustAsset(name string) []byte {
|
||||||
data, err := Asset(name)
|
data, err := Asset(name)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -186,10 +185,10 @@ func mustAsset(name string) []byte {
|
|||||||
|
|
||||||
func parseFlags() error {
|
func parseFlags() error {
|
||||||
var cmdFlags struct {
|
var cmdFlags struct {
|
||||||
Addr string `short:"a" long:"addr" description:"Address (host:port) to serve on" default:":8080"`
|
Addr string `short:"a" long:"addr" description:"Address (host:port) to serve on" default:":8080"`
|
||||||
Database string `short:"d" long:"database" description:"URL of the (magneticod) database"`
|
Database string `short:"d" long:"database" description:"URL of the (magneticod) database"`
|
||||||
Cred string `short:"c" long:"credentials" description:"Path to the credentials file"`
|
Cred string `short:"c" long:"credentials" description:"Path to the credentials file"`
|
||||||
NoAuth bool ` long:"no-auth" description:"Disables authorisation"`
|
NoAuth bool ` long:"no-auth" description:"Disables authorisation"`
|
||||||
}
|
}
|
||||||
|
|
||||||
if _, err := flags.Parse(&cmdFlags); err != nil {
|
if _, err := flags.Parse(&cmdFlags); err != nil {
|
||||||
@ -205,9 +204,9 @@ func parseFlags() error {
|
|||||||
if cmdFlags.Database == "" {
|
if cmdFlags.Database == "" {
|
||||||
opts.Database =
|
opts.Database =
|
||||||
"sqlite3://" +
|
"sqlite3://" +
|
||||||
appdirs.UserDataDir("magneticod", "", "", false) +
|
appdirs.UserDataDir("magneticod", "", "", false) +
|
||||||
"/database.sqlite3" +
|
"/database.sqlite3" +
|
||||||
"?_journal_mode=WAL" // https://github.com/mattn/go-sqlite3#connection-string
|
"?_journal_mode=WAL" // https://github.com/mattn/go-sqlite3#connection-string
|
||||||
} else {
|
} else {
|
||||||
opts.Database = cmdFlags.Database
|
opts.Database = cmdFlags.Database
|
||||||
}
|
}
|
||||||
@ -249,12 +248,12 @@ func loadCred(cred string) error {
|
|||||||
line, err := reader.ReadBytes('\n')
|
line, err := reader.ReadBytes('\n')
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if err == io.EOF {
|
if err == io.EOF {
|
||||||
break;
|
break
|
||||||
}
|
}
|
||||||
return fmt.Errorf("error while reading line %d: %s", lineno, err.Error())
|
return fmt.Errorf("error while reading line %d: %s", lineno, err.Error())
|
||||||
}
|
}
|
||||||
|
|
||||||
line = line[:len(line) - 1] // strip '\n'
|
line = line[:len(line)-1] // strip '\n'
|
||||||
|
|
||||||
/* The following regex checks if the line satisfies the following conditions:
|
/* The following regex checks if the line satisfies the following conditions:
|
||||||
*
|
*
|
||||||
@ -292,7 +291,7 @@ func loadCred(cred string) error {
|
|||||||
func BasicAuth(handler http.HandlerFunc, realm string) http.HandlerFunc {
|
func BasicAuth(handler http.HandlerFunc, realm string) http.HandlerFunc {
|
||||||
return func(w http.ResponseWriter, r *http.Request) {
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
username, password, ok := r.BasicAuth()
|
username, password, ok := r.BasicAuth()
|
||||||
if !ok { // No credentials provided
|
if !ok { // No credentials provided
|
||||||
authenticate(w, realm)
|
authenticate(w, realm)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -300,12 +299,12 @@ func BasicAuth(handler http.HandlerFunc, realm string) http.HandlerFunc {
|
|||||||
opts.CredentialsRWMutex.RLock()
|
opts.CredentialsRWMutex.RLock()
|
||||||
hashedPassword, ok := opts.Credentials[username]
|
hashedPassword, ok := opts.Credentials[username]
|
||||||
opts.CredentialsRWMutex.RUnlock()
|
opts.CredentialsRWMutex.RUnlock()
|
||||||
if !ok { // User not found
|
if !ok { // User not found
|
||||||
authenticate(w, realm)
|
authenticate(w, realm)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := bcrypt.CompareHashAndPassword(hashedPassword, []byte(password)); err != nil { // Wrong password
|
if err := bcrypt.CompareHashAndPassword(hashedPassword, []byte(password)); err != nil { // Wrong password
|
||||||
authenticate(w, realm)
|
authenticate(w, realm)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -16,7 +16,7 @@ type schemaStruct struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type schemaRStruct struct {
|
type schemaRStruct struct {
|
||||||
Uint64 uint64 `schema:"ruint64,required"` // https://github.com/gorilla/schema/pull/68
|
Uint64 uint64 `schema:"ruint64,required"` // https://github.com/gorilla/schema/pull/68
|
||||||
}
|
}
|
||||||
|
|
||||||
// TestSchemaUnsuppliedNil tests that unsupplied values yield nil.
|
// TestSchemaUnsuppliedNil tests that unsupplied values yield nil.
|
||||||
@ -26,9 +26,15 @@ func TestSchemaUnsuppliedNil(t *testing.T) {
|
|||||||
t.Error("decoding error", err.Error())
|
t.Error("decoding error", err.Error())
|
||||||
}
|
}
|
||||||
|
|
||||||
if ss.PString != nil { t.Error("PString is not nil") }
|
if ss.PString != nil {
|
||||||
if ss.PUint64 != nil { t.Error("PUint64 is not nil") }
|
t.Error("PString is not nil")
|
||||||
if ss.PBool != nil { t.Error("PBool is not nil") }
|
}
|
||||||
|
if ss.PUint64 != nil {
|
||||||
|
t.Error("PUint64 is not nil")
|
||||||
|
}
|
||||||
|
if ss.PBool != nil {
|
||||||
|
t.Error("PBool is not nil")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TestSchemaInvalidUint64 tests that an invalid uint64 value yields nil.
|
// TestSchemaInvalidUint64 tests that an invalid uint64 value yields nil.
|
||||||
@ -38,7 +44,9 @@ func TestSchemaInvalidUint64(t *testing.T) {
|
|||||||
|
|
||||||
ss := new(schemaStruct)
|
ss := new(schemaStruct)
|
||||||
err := decoder.Decode(ss, dict)
|
err := decoder.Decode(ss, dict)
|
||||||
if err == nil { t.Error("err is nil") }
|
if err == nil {
|
||||||
|
t.Error("err is nil")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TestSchemaInvalidBool tests that an invalid bool value yields nil.
|
// TestSchemaInvalidBool tests that an invalid bool value yields nil.
|
||||||
@ -48,18 +56,22 @@ func TestSchemaInvalidBool(t *testing.T) {
|
|||||||
|
|
||||||
ss := new(schemaStruct)
|
ss := new(schemaStruct)
|
||||||
err := decoder.Decode(ss, dict)
|
err := decoder.Decode(ss, dict)
|
||||||
if err == nil { t.Error("err is nil") }
|
if err == nil {
|
||||||
|
t.Error("err is nil")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TestSchemaOverflow tests that integer values greater than the maximum value a field can store
|
// TestSchemaOverflow tests that integer values greater than the maximum value a field can store
|
||||||
// leads to decoding errors, rather than silent overflows.
|
// leads to decoding errors, rather than silent overflows.
|
||||||
func TestSchemaOverflow(t *testing.T) {
|
func TestSchemaOverflow(t *testing.T) {
|
||||||
dict := make(map[string][]string)
|
dict := make(map[string][]string)
|
||||||
dict["puint64"] = []string{"18446744073709551616"} // 18,446,744,073,709,551,615 + 1
|
dict["puint64"] = []string{"18446744073709551616"} // 18,446,744,073,709,551,615 + 1
|
||||||
|
|
||||||
ss := new(schemaStruct)
|
ss := new(schemaStruct)
|
||||||
err := decoder.Decode(ss, dict)
|
err := decoder.Decode(ss, dict)
|
||||||
if err == nil { t.Error("err is nil") }
|
if err == nil {
|
||||||
|
t.Error("err is nil")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TestSchemaEmptyString tests that empty string yields nil.
|
// TestSchemaEmptyString tests that empty string yields nil.
|
||||||
@ -72,7 +84,9 @@ func TestSchemaEmptyString(t *testing.T) {
|
|||||||
t.Error("decoding error", err.Error())
|
t.Error("decoding error", err.Error())
|
||||||
}
|
}
|
||||||
|
|
||||||
if ss.PString != nil { t.Error("PString is not nil") }
|
if ss.PString != nil {
|
||||||
|
t.Error("PString is not nil")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TestSchemaDefault tests if unsupplied values defaults to "zero" and doesn't err
|
// TestSchemaDefault tests if unsupplied values defaults to "zero" and doesn't err
|
||||||
@ -82,14 +96,22 @@ func TestSchemaDefault(t *testing.T) {
|
|||||||
t.Error("decoding error", err.Error())
|
t.Error("decoding error", err.Error())
|
||||||
}
|
}
|
||||||
|
|
||||||
if ss.String != "" { t.Error("String is not empty") }
|
if ss.String != "" {
|
||||||
if ss.Uint64 != 0 { t.Error("Uint64 is not 0") }
|
t.Error("String is not empty")
|
||||||
if ss.Bool != false { t.Error("Bool is not false") }
|
}
|
||||||
|
if ss.Uint64 != 0 {
|
||||||
|
t.Error("Uint64 is not 0")
|
||||||
|
}
|
||||||
|
if ss.Bool != false {
|
||||||
|
t.Error("Bool is not false")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestSchemaRequired(t *testing.T) {
|
func TestSchemaRequired(t *testing.T) {
|
||||||
rs := new(schemaRStruct)
|
rs := new(schemaRStruct)
|
||||||
err := decoder.Decode(rs, make(map[string][]string))
|
err := decoder.Decode(rs, make(map[string][]string))
|
||||||
if err == nil { t.Error("err is nil") }
|
if err == nil {
|
||||||
|
t.Error("err is nil")
|
||||||
|
}
|
||||||
fmt.Printf(err.Error())
|
fmt.Printf(err.Error())
|
||||||
}
|
}
|
||||||
|
@ -42,6 +42,7 @@ type Database interface {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type OrderingCriteria uint8
|
type OrderingCriteria uint8
|
||||||
|
|
||||||
const (
|
const (
|
||||||
ByRelevance OrderingCriteria = iota
|
ByRelevance OrderingCriteria = iota
|
||||||
ByTotalSize
|
ByTotalSize
|
||||||
@ -51,17 +52,19 @@ const (
|
|||||||
ByNLeechers
|
ByNLeechers
|
||||||
ByUpdatedOn
|
ByUpdatedOn
|
||||||
)
|
)
|
||||||
|
|
||||||
// TODO: search `swtich (orderBy)` and see if all cases are covered all the time
|
// TODO: search `swtich (orderBy)` and see if all cases are covered all the time
|
||||||
|
|
||||||
type databaseEngine uint8
|
type databaseEngine uint8
|
||||||
|
|
||||||
const (
|
const (
|
||||||
Sqlite3 databaseEngine = 1
|
Sqlite3 databaseEngine = 1
|
||||||
)
|
)
|
||||||
|
|
||||||
type Statistics struct {
|
type Statistics struct {
|
||||||
NDiscovered map[string]uint64 `json:"nDiscovered"`
|
NDiscovered map[string]uint64 `json:"nDiscovered"`
|
||||||
NFiles map[string]uint64 `json:"nFiles"`
|
NFiles map[string]uint64 `json:"nFiles"`
|
||||||
TotalSize map[string]uint64 `json:"totalSize"`
|
TotalSize map[string]uint64 `json:"totalSize"`
|
||||||
|
|
||||||
// All these slices below have the exact length equal to the Period.
|
// All these slices below have the exact length equal to the Period.
|
||||||
//NDiscovered []uint64 `json:"nDiscovered"`
|
//NDiscovered []uint64 `json:"nDiscovered"`
|
||||||
@ -69,18 +72,18 @@ type Statistics struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type File struct {
|
type File struct {
|
||||||
Size int64 `json:"size"`
|
Size int64 `json:"size"`
|
||||||
Path string `json:"path"`
|
Path string `json:"path"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type TorrentMetadata struct {
|
type TorrentMetadata struct {
|
||||||
ID uint64 `json:"id"`
|
ID uint64 `json:"id"`
|
||||||
InfoHash []byte `json:"infoHash"` // marshalled differently
|
InfoHash []byte `json:"infoHash"` // marshalled differently
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
Size uint64 `json:"size"`
|
Size uint64 `json:"size"`
|
||||||
DiscoveredOn int64 `json:"discoveredOn"`
|
DiscoveredOn int64 `json:"discoveredOn"`
|
||||||
NFiles uint `json:"nFiles"`
|
NFiles uint `json:"nFiles"`
|
||||||
Relevance float64 `json:"relevance"`
|
Relevance float64 `json:"relevance"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (tm *TorrentMetadata) MarshalJSON() ([]byte, error) {
|
func (tm *TorrentMetadata) MarshalJSON() ([]byte, error) {
|
||||||
@ -122,7 +125,7 @@ func MakeDatabase(rawURL string, logger *zap.Logger) (Database, error) {
|
|||||||
func NewStatistics() (s *Statistics) {
|
func NewStatistics() (s *Statistics) {
|
||||||
s = new(Statistics)
|
s = new(Statistics)
|
||||||
s.NDiscovered = make(map[string]uint64)
|
s.NDiscovered = make(map[string]uint64)
|
||||||
s.NFiles = make(map[string]uint64)
|
s.NFiles = make(map[string]uint64)
|
||||||
s.TotalSize = make(map[string]uint64)
|
s.TotalSize = make(map[string]uint64)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -7,13 +7,14 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
var yearRE = regexp.MustCompile(`^(\d{4})$`)
|
var yearRE = regexp.MustCompile(`^(\d{4})$`)
|
||||||
var monthRE = regexp.MustCompile(`^(\d{4})-(\d{2})$`)
|
var monthRE = regexp.MustCompile(`^(\d{4})-(\d{2})$`)
|
||||||
var weekRE = regexp.MustCompile(`^(\d{4})-W(\d{2})$`)
|
var weekRE = regexp.MustCompile(`^(\d{4})-W(\d{2})$`)
|
||||||
var dayRE = regexp.MustCompile(`^(\d{4})-(\d{2})-(\d{2})$`)
|
var dayRE = regexp.MustCompile(`^(\d{4})-(\d{2})-(\d{2})$`)
|
||||||
var hourRE = regexp.MustCompile(`^(\d{4})-(\d{2})-(\d{2})T(\d{2})$`)
|
var hourRE = regexp.MustCompile(`^(\d{4})-(\d{2})-(\d{2})T(\d{2})$`)
|
||||||
|
|
||||||
type Granularity int
|
type Granularity int
|
||||||
|
|
||||||
const (
|
const (
|
||||||
Year Granularity = iota
|
Year Granularity = iota
|
||||||
Month
|
Month
|
||||||
@ -50,7 +51,7 @@ func ParseISO8601(s string) (*time.Time, Granularity, error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, -1, err
|
return nil, -1, err
|
||||||
}
|
}
|
||||||
t := time.Date(year, time.January, week * 7, 23, 59, 59, 0, time.UTC)
|
t := time.Date(year, time.January, week*7, 23, 59, 59, 0, time.UTC)
|
||||||
return &t, Week, nil
|
return &t, Week, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -122,15 +123,15 @@ func daysOfMonth(month time.Month, year int) int {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func isLeap(year int) bool {
|
func isLeap(year int) bool {
|
||||||
if year % 4 != 0 {
|
if year%4 != 0 {
|
||||||
return false
|
return false
|
||||||
} else if year % 100 != 0 {
|
} else if year%100 != 0 {
|
||||||
return true
|
return true
|
||||||
} else if year % 400 != 0 {
|
} else if year%400 != 0 {
|
||||||
return false
|
return false
|
||||||
} else {
|
} else {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func atoi(s string) int {
|
func atoi(s string) int {
|
||||||
@ -153,7 +154,7 @@ func parseYear(s string) (int, error) {
|
|||||||
|
|
||||||
func parseMonth(s string) (time.Month, error) {
|
func parseMonth(s string) (time.Month, error) {
|
||||||
month := atoi(s)
|
month := atoi(s)
|
||||||
if month <= 0 || month >= 13 {
|
if month <= 0 || month >= 13 {
|
||||||
return time.Month(-1), fmt.Errorf("month is not in range [01, 12]")
|
return time.Month(-1), fmt.Errorf("month is not in range [01, 12]")
|
||||||
}
|
}
|
||||||
return time.Month(month), nil
|
return time.Month(month), nil
|
||||||
|
@ -147,7 +147,7 @@ func (db *sqlite3Database) GetNumberOfTorrents() (uint, error) {
|
|||||||
defer rows.Close()
|
defer rows.Close()
|
||||||
|
|
||||||
if rows.Next() != true {
|
if rows.Next() != true {
|
||||||
fmt.Errorf("No rows returned from `SELECT MAX(ROWID)`")
|
return 0, fmt.Errorf("No rows returned from `SELECT MAX(ROWID)`")
|
||||||
}
|
}
|
||||||
|
|
||||||
var n uint
|
var n uint
|
||||||
@ -174,7 +174,7 @@ func (db *sqlite3Database) QueryTorrents(
|
|||||||
return nil, fmt.Errorf("lastOrderedValue and lastID should be supplied together, if supplied")
|
return nil, fmt.Errorf("lastOrderedValue and lastID should be supplied together, if supplied")
|
||||||
}
|
}
|
||||||
|
|
||||||
doJoin := query != ""
|
doJoin := query != ""
|
||||||
firstPage := lastID == nil
|
firstPage := lastID == nil
|
||||||
|
|
||||||
// executeTemplate is used to prepare the SQL query, WITH PLACEHOLDERS FOR USER INPUT.
|
// executeTemplate is used to prepare the SQL query, WITH PLACEHOLDERS FOR USER INPUT.
|
||||||
@ -206,14 +206,14 @@ func (db *sqlite3Database) QueryTorrents(
|
|||||||
ORDER BY {{.OrderOn}} {{AscOrDesc .Ascending}}, id {{AscOrDesc .Ascending}}
|
ORDER BY {{.OrderOn}} {{AscOrDesc .Ascending}}, id {{AscOrDesc .Ascending}}
|
||||||
LIMIT ?;
|
LIMIT ?;
|
||||||
`, struct {
|
`, struct {
|
||||||
DoJoin bool
|
DoJoin bool
|
||||||
FirstPage bool
|
FirstPage bool
|
||||||
OrderOn string
|
OrderOn string
|
||||||
Ascending bool
|
Ascending bool
|
||||||
}{
|
}{
|
||||||
DoJoin: doJoin,
|
DoJoin: doJoin,
|
||||||
FirstPage: firstPage,
|
FirstPage: firstPage,
|
||||||
OrderOn: orderOn(orderBy),
|
OrderOn: orderOn(orderBy),
|
||||||
Ascending: ascending,
|
Ascending: ascending,
|
||||||
}, template.FuncMap{
|
}, template.FuncMap{
|
||||||
"GTEorLTE": func(ascending bool) string {
|
"GTEorLTE": func(ascending bool) string {
|
||||||
@ -347,24 +347,24 @@ func (db *sqlite3Database) GetStatistics(from string, n uint) (*Statistics, erro
|
|||||||
}
|
}
|
||||||
|
|
||||||
var toTime time.Time
|
var toTime time.Time
|
||||||
var timef string // time format: https://www.sqlite.org/lang_datefunc.html
|
var timef string // time format: https://www.sqlite.org/lang_datefunc.html
|
||||||
|
|
||||||
switch gran {
|
switch gran {
|
||||||
case Year:
|
case Year:
|
||||||
toTime = fromTime.AddDate(int(n), 0, 0)
|
toTime = fromTime.AddDate(int(n), 0, 0)
|
||||||
timef = "%Y"
|
timef = "%Y"
|
||||||
case Month:
|
case Month:
|
||||||
toTime = fromTime.AddDate(0, int(n), 0)
|
toTime = fromTime.AddDate(0, int(n), 0)
|
||||||
timef = "%Y-%m"
|
timef = "%Y-%m"
|
||||||
case Week:
|
case Week:
|
||||||
toTime = fromTime.AddDate(0, 0, int(n) * 7)
|
toTime = fromTime.AddDate(0, 0, int(n)*7)
|
||||||
timef = "%Y-%W"
|
timef = "%Y-%W"
|
||||||
case Day:
|
case Day:
|
||||||
toTime = fromTime.AddDate(0, 0, int(n))
|
toTime = fromTime.AddDate(0, 0, int(n))
|
||||||
timef = "%Y-%m-%d"
|
timef = "%Y-%m-%d"
|
||||||
case Hour:
|
case Hour:
|
||||||
toTime = fromTime.Add(time.Duration(n) * time.Hour)
|
toTime = fromTime.Add(time.Duration(n) * time.Hour)
|
||||||
timef = "%Y-%m-%dT%H"
|
timef = "%Y-%m-%dT%H"
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: make it faster!
|
// TODO: make it faster!
|
||||||
@ -378,7 +378,7 @@ func (db *sqlite3Database) GetStatistics(from string, n uint) (*Statistics, erro
|
|||||||
AND discovered_on >= ?
|
AND discovered_on >= ?
|
||||||
AND discovered_on <= ?
|
AND discovered_on <= ?
|
||||||
GROUP BY dt;`,
|
GROUP BY dt;`,
|
||||||
timef),
|
timef),
|
||||||
fromTime.Unix(), toTime.Unix())
|
fromTime.Unix(), toTime.Unix())
|
||||||
defer rows.Close()
|
defer rows.Close()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -478,7 +478,7 @@ func (db *sqlite3Database) setupDatabase() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
switch userVersion {
|
switch userVersion {
|
||||||
case 0: // FROZEN.
|
case 0: // FROZEN.
|
||||||
// Upgrade from user_version 0 to 1
|
// Upgrade from user_version 0 to 1
|
||||||
// Changes:
|
// Changes:
|
||||||
// * `info_hash_index` is recreated as UNIQUE.
|
// * `info_hash_index` is recreated as UNIQUE.
|
||||||
@ -493,7 +493,7 @@ func (db *sqlite3Database) setupDatabase() error {
|
|||||||
}
|
}
|
||||||
fallthrough
|
fallthrough
|
||||||
|
|
||||||
case 1: // FROZEN.
|
case 1: // FROZEN.
|
||||||
// Upgrade from user_version 1 to 2
|
// Upgrade from user_version 1 to 2
|
||||||
// Changes:
|
// Changes:
|
||||||
// * Added `n_seeders`, `n_leechers`, and `updated_on` columns to the `torrents` table, and
|
// * Added `n_seeders`, `n_leechers`, and `updated_on` columns to the `torrents` table, and
|
||||||
@ -538,7 +538,7 @@ func (db *sqlite3Database) setupDatabase() error {
|
|||||||
}
|
}
|
||||||
fallthrough
|
fallthrough
|
||||||
|
|
||||||
case 2: // NOT FROZEN! (subject to change or complete removal)
|
case 2: // NOT FROZEN! (subject to change or complete removal)
|
||||||
// Upgrade from user_version 2 to 3
|
// Upgrade from user_version 2 to 3
|
||||||
// Changes:
|
// Changes:
|
||||||
// * Created `torrents_idx` FTS5 virtual table.
|
// * Created `torrents_idx` FTS5 virtual table.
|
||||||
|
Loading…
Reference in New Issue
Block a user