39
pkg/qbit/handlers.go
Normal file
39
pkg/qbit/handlers.go
Normal file
@@ -0,0 +1,39 @@
|
||||
package qbit
|
||||
|
||||
import (
|
||||
"github.com/go-chi/chi/v5"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
func (q *QBit) AddRoutes(r chi.Router) http.Handler {
|
||||
r.Route("/api/v2", func(r chi.Router) {
|
||||
r.Post("/auth/login", q.handleLogin)
|
||||
|
||||
r.Group(func(r chi.Router) {
|
||||
r.Use(q.authMiddleware)
|
||||
r.Route("/torrents", func(r chi.Router) {
|
||||
r.Use(HashesCtx)
|
||||
r.Get("/info", q.handleTorrentsInfo)
|
||||
r.Post("/add", q.handleTorrentsAdd)
|
||||
r.Post("/delete", q.handleTorrentsDelete)
|
||||
r.Get("/categories", q.handleCategories)
|
||||
r.Post("/createCategory", q.handleCreateCategory)
|
||||
|
||||
r.Get("/pause", q.handleTorrentsPause)
|
||||
r.Get("/resume", q.handleTorrentsResume)
|
||||
r.Get("/recheck", q.handleTorrentRecheck)
|
||||
r.Get("/properties", q.handleTorrentProperties)
|
||||
})
|
||||
|
||||
r.Route("/app", func(r chi.Router) {
|
||||
r.Get("/version", q.handleVersion)
|
||||
r.Get("/webapiVersion", q.handleWebAPIVersion)
|
||||
r.Get("/preferences", q.handlePreferences)
|
||||
r.Get("/buildInfo", q.handleBuildInfo)
|
||||
r.Get("/shutdown", q.shutdown)
|
||||
})
|
||||
})
|
||||
|
||||
})
|
||||
return r
|
||||
}
|
||||
42
pkg/qbit/handlers_app.go
Normal file
42
pkg/qbit/handlers_app.go
Normal file
@@ -0,0 +1,42 @@
|
||||
package qbit
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"path/filepath"
|
||||
)
|
||||
|
||||
func (q *QBit) handleVersion(w http.ResponseWriter, r *http.Request) {
|
||||
_, _ = w.Write([]byte("v4.3.2"))
|
||||
w.WriteHeader(http.StatusOK)
|
||||
}
|
||||
|
||||
func (q *QBit) handleWebAPIVersion(w http.ResponseWriter, r *http.Request) {
|
||||
_, _ = w.Write([]byte("2.7"))
|
||||
w.WriteHeader(http.StatusOK)
|
||||
}
|
||||
|
||||
func (q *QBit) handlePreferences(w http.ResponseWriter, r *http.Request) {
|
||||
preferences := NewAppPreferences()
|
||||
|
||||
preferences.WebUiUsername = q.Username
|
||||
preferences.SavePath = q.DownloadFolder
|
||||
preferences.TempPath = filepath.Join(q.DownloadFolder, "temp")
|
||||
|
||||
JSONResponse(w, preferences, http.StatusOK)
|
||||
}
|
||||
|
||||
func (q *QBit) handleBuildInfo(w http.ResponseWriter, r *http.Request) {
|
||||
res := BuildInfo{
|
||||
Bitness: 64,
|
||||
Boost: "1.75.0",
|
||||
Libtorrent: "1.2.11.0",
|
||||
Openssl: "1.1.1i",
|
||||
Qt: "5.15.2",
|
||||
Zlib: "1.2.11",
|
||||
}
|
||||
JSONResponse(w, res, http.StatusOK)
|
||||
}
|
||||
|
||||
func (q *QBit) shutdown(w http.ResponseWriter, r *http.Request) {
|
||||
w.WriteHeader(http.StatusOK)
|
||||
}
|
||||
50
pkg/qbit/handlers_auth.go
Normal file
50
pkg/qbit/handlers_auth.go
Normal file
@@ -0,0 +1,50 @@
|
||||
package qbit
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"time"
|
||||
)
|
||||
|
||||
func (q *QBit) handleLogin(w http.ResponseWriter, r *http.Request) {
|
||||
err := r.ParseForm()
|
||||
if err != nil {
|
||||
http.Error(w, "Failed to parse form data", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
username := r.Form.Get("username")
|
||||
password := r.Form.Get("password")
|
||||
|
||||
// In a real implementation, you'd verify credentials here
|
||||
// For this mock, we'll accept any non-empty username and password
|
||||
if username == "" || password == "" {
|
||||
http.Error(w, "Invalid credentials", http.StatusUnauthorized)
|
||||
return
|
||||
}
|
||||
if username != q.Username || password != q.Password {
|
||||
http.Error(w, "Invalid credentials", http.StatusUnauthorized)
|
||||
return
|
||||
}
|
||||
|
||||
// Generate a new SID
|
||||
sid, err := generateSID()
|
||||
if err != nil {
|
||||
http.Error(w, "Failed to generate session ID", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
// Set the SID cookie
|
||||
http.SetCookie(w, &http.Cookie{
|
||||
Name: cookieName,
|
||||
Value: sid,
|
||||
Path: "/",
|
||||
HttpOnly: true,
|
||||
MaxAge: 315360000,
|
||||
})
|
||||
|
||||
// Store the session
|
||||
sessions.Store(sid, time.Now().Add(24*time.Hour))
|
||||
|
||||
w.WriteHeader(http.StatusOK)
|
||||
w.Write([]byte("Ok."))
|
||||
}
|
||||
171
pkg/qbit/handlers_torrent.go
Normal file
171
pkg/qbit/handlers_torrent.go
Normal file
@@ -0,0 +1,171 @@
|
||||
package qbit
|
||||
|
||||
import (
|
||||
"goBlack/common"
|
||||
"io"
|
||||
"net/http"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func (q *QBit) handleTorrentsInfo(w http.ResponseWriter, r *http.Request) {
|
||||
//log all url params
|
||||
ctx := r.Context()
|
||||
category := strings.Trim(r.URL.Query().Get("category"), "")
|
||||
filter := strings.Trim(r.URL.Query().Get("filter"), "")
|
||||
hashes, _ := ctx.Value("hashes").([]string)
|
||||
torrents := q.storage.GetAll(category, filter, hashes)
|
||||
JSONResponse(w, torrents, http.StatusOK)
|
||||
}
|
||||
|
||||
func (q *QBit) handleTorrentsAdd(w http.ResponseWriter, r *http.Request) {
|
||||
contentType := strings.Split(r.Header.Get("Content-Type"), ";")[0]
|
||||
switch contentType {
|
||||
case "multipart/form-data":
|
||||
err := r.ParseMultipartForm(32 << 20) // 32MB max memory
|
||||
if err != nil {
|
||||
q.logger.Printf("Error parsing form: %v\n", err)
|
||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
case "application/x-www-form-urlencoded":
|
||||
err := r.ParseForm()
|
||||
if err != nil {
|
||||
q.logger.Printf("Error parsing form: %v\n", err)
|
||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
files := r.MultipartForm.File["torrents"]
|
||||
urls := r.FormValue("urls")
|
||||
category := r.FormValue("category")
|
||||
|
||||
if len(files) == 0 && urls == "" {
|
||||
http.Error(w, "No torrent provided", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
var urlList []string
|
||||
if urls != "" {
|
||||
urlList = strings.Split(urls, "\n")
|
||||
}
|
||||
|
||||
for _, url := range urlList {
|
||||
magnet, err := common.GetMagnetFromUrl(url)
|
||||
if err != nil {
|
||||
q.logger.Printf("Error parsing magnet link: %v\n", err)
|
||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
go q.Process(magnet, category)
|
||||
|
||||
}
|
||||
|
||||
for _, fileHeader := range files {
|
||||
file, _ := fileHeader.Open()
|
||||
defer file.Close()
|
||||
var reader io.Reader = file
|
||||
magnet, err := common.GetMagnetFromFile(reader, fileHeader.Filename)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||
q.logger.Printf("Error reading file: %s", fileHeader.Filename)
|
||||
return
|
||||
}
|
||||
go q.Process(magnet, category)
|
||||
}
|
||||
w.WriteHeader(http.StatusOK)
|
||||
}
|
||||
|
||||
func (q *QBit) handleTorrentsDelete(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := r.Context()
|
||||
hashes, _ := ctx.Value("hashes").([]string)
|
||||
if len(hashes) == 0 {
|
||||
http.Error(w, "No hashes provided", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
for _, hash := range hashes {
|
||||
q.storage.Delete(hash)
|
||||
}
|
||||
|
||||
w.WriteHeader(http.StatusOK)
|
||||
}
|
||||
|
||||
func (q *QBit) handleTorrentsPause(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := r.Context()
|
||||
hashes, _ := ctx.Value("hashes").([]string)
|
||||
for _, hash := range hashes {
|
||||
torrent := q.storage.Get(hash)
|
||||
if torrent == nil {
|
||||
continue
|
||||
}
|
||||
go q.PauseTorrent(torrent)
|
||||
}
|
||||
|
||||
w.WriteHeader(http.StatusOK)
|
||||
}
|
||||
|
||||
func (q *QBit) handleTorrentsResume(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := r.Context()
|
||||
hashes, _ := ctx.Value("hashes").([]string)
|
||||
for _, hash := range hashes {
|
||||
torrent := q.storage.Get(hash)
|
||||
if torrent == nil {
|
||||
continue
|
||||
}
|
||||
go q.ResumeTorrent(torrent)
|
||||
}
|
||||
|
||||
w.WriteHeader(http.StatusOK)
|
||||
}
|
||||
|
||||
func (q *QBit) handleTorrentRecheck(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := r.Context()
|
||||
hashes, _ := ctx.Value("hashes").([]string)
|
||||
for _, hash := range hashes {
|
||||
torrent := q.storage.Get(hash)
|
||||
if torrent == nil {
|
||||
continue
|
||||
}
|
||||
go q.RefreshTorrent(torrent)
|
||||
}
|
||||
|
||||
w.WriteHeader(http.StatusOK)
|
||||
}
|
||||
|
||||
func (q *QBit) handleCategories(w http.ResponseWriter, r *http.Request) {
|
||||
var categories = map[string]TorrentCategory{}
|
||||
for _, cat := range q.Categories {
|
||||
path := filepath.Join(q.DownloadFolder, cat)
|
||||
categories[cat] = TorrentCategory{
|
||||
Name: cat,
|
||||
SavePath: path,
|
||||
}
|
||||
}
|
||||
JSONResponse(w, categories, http.StatusOK)
|
||||
}
|
||||
|
||||
func (q *QBit) handleCreateCategory(w http.ResponseWriter, r *http.Request) {
|
||||
err := r.ParseForm()
|
||||
if err != nil {
|
||||
http.Error(w, "Failed to parse form data", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
name := r.Form.Get("category")
|
||||
if name == "" {
|
||||
http.Error(w, "No name provided", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
q.Categories = append(q.Categories, name)
|
||||
|
||||
JSONResponse(w, nil, http.StatusOK)
|
||||
}
|
||||
|
||||
func (q *QBit) handleTorrentProperties(w http.ResponseWriter, r *http.Request) {
|
||||
hash := r.URL.Query().Get("hash")
|
||||
torrent := q.storage.Get(hash)
|
||||
properties := q.GetTorrentProperties(torrent)
|
||||
JSONResponse(w, properties, http.StatusOK)
|
||||
}
|
||||
67
pkg/qbit/main.go
Normal file
67
pkg/qbit/main.go
Normal file
@@ -0,0 +1,67 @@
|
||||
package qbit
|
||||
|
||||
import (
|
||||
"cmp"
|
||||
"fmt"
|
||||
"github.com/go-chi/chi/v5"
|
||||
"github.com/go-chi/chi/v5/middleware"
|
||||
"goBlack/common"
|
||||
"goBlack/pkg/debrid"
|
||||
"log"
|
||||
"net/http"
|
||||
"os"
|
||||
"sync"
|
||||
)
|
||||
|
||||
type QBit struct {
|
||||
Username string `json:"username"`
|
||||
Password string `json:"password"`
|
||||
Port string `json:"port"`
|
||||
DownloadFolder string `json:"download_folder"`
|
||||
Categories []string `json:"categories"`
|
||||
debrid debrid.Service
|
||||
cache *common.Cache
|
||||
storage *TorrentStorage
|
||||
debug bool
|
||||
logger *log.Logger
|
||||
}
|
||||
|
||||
var (
|
||||
sessions sync.Map
|
||||
)
|
||||
|
||||
const (
|
||||
sidLength = 32
|
||||
cookieName = "SID"
|
||||
)
|
||||
|
||||
func NewQBit(config *common.Config, deb debrid.Service, cache *common.Cache) *QBit {
|
||||
cfg := config.QBitTorrent
|
||||
storage := NewTorrentStorage("torrents.json")
|
||||
port := cmp.Or(cfg.Port, os.Getenv("QBIT_PORT"), "8182")
|
||||
return &QBit{
|
||||
Username: cfg.Username,
|
||||
Password: cfg.Password,
|
||||
Port: port,
|
||||
DownloadFolder: cfg.DownloadFolder,
|
||||
Categories: cfg.Categories,
|
||||
debrid: deb,
|
||||
cache: cache,
|
||||
debug: cfg.Debug,
|
||||
storage: storage,
|
||||
logger: common.NewLogger("QBit", os.Stdout),
|
||||
}
|
||||
}
|
||||
|
||||
func (q *QBit) Start() {
|
||||
|
||||
r := chi.NewRouter()
|
||||
r.Use(middleware.Logger)
|
||||
r.Use(middleware.Recoverer)
|
||||
|
||||
q.AddRoutes(r)
|
||||
|
||||
q.logger.Printf("Starting QBit server on :%s", q.Port)
|
||||
port := fmt.Sprintf(":%s", q.Port)
|
||||
q.logger.Fatal(http.ListenAndServe(port, r))
|
||||
}
|
||||
41
pkg/qbit/middleware.go
Normal file
41
pkg/qbit/middleware.go
Normal file
@@ -0,0 +1,41 @@
|
||||
package qbit
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/subtle"
|
||||
"github.com/go-chi/chi/v5"
|
||||
"net/http"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func (q *QBit) authMiddleware(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
user, pass, ok := r.BasicAuth()
|
||||
if !ok {
|
||||
http.Error(w, "Unauthorized", http.StatusUnauthorized)
|
||||
return
|
||||
}
|
||||
if subtle.ConstantTimeCompare([]byte(user), []byte(q.Username)) != 1 || subtle.ConstantTimeCompare([]byte(pass), []byte(q.Password)) != 1 {
|
||||
http.Error(w, "Unauthorized", http.StatusUnauthorized)
|
||||
return
|
||||
}
|
||||
next.ServeHTTP(w, r)
|
||||
})
|
||||
}
|
||||
|
||||
func HashesCtx(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
_hashes := chi.URLParam(r, "hashes")
|
||||
var hashes []string
|
||||
if _hashes != "" {
|
||||
hashes = strings.Split(_hashes, "|")
|
||||
}
|
||||
if hashes == nil {
|
||||
// Get hashes from form
|
||||
_ = r.ParseForm()
|
||||
hashes = r.Form["hashes"]
|
||||
}
|
||||
ctx := context.WithValue(r.Context(), "hashes", hashes)
|
||||
next.ServeHTTP(w, r.WithContext(ctx))
|
||||
})
|
||||
}
|
||||
101
pkg/qbit/qbit.go
Normal file
101
pkg/qbit/qbit.go
Normal file
@@ -0,0 +1,101 @@
|
||||
package qbit
|
||||
|
||||
import (
|
||||
"github.com/google/uuid"
|
||||
"goBlack/common"
|
||||
"goBlack/pkg/debrid"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
func (q *QBit) Process(magnet *common.Magnet, category string) (*Torrent, error) {
|
||||
torrent := q.CreateTorrentFromMagnet(magnet, category)
|
||||
go q.storage.AddOrUpdate(torrent)
|
||||
debridTorrent, err := debrid.ProcessQBitTorrent(q.debrid, magnet, category)
|
||||
if err != nil || debridTorrent == nil {
|
||||
// Mark as failed
|
||||
q.MarkAsFailed(torrent)
|
||||
return torrent, err
|
||||
}
|
||||
torrent.ID = debridTorrent.Id
|
||||
q.processFiles(torrent, debridTorrent)
|
||||
return torrent, nil
|
||||
}
|
||||
|
||||
func (q *QBit) CreateTorrentFromMagnet(magnet *common.Magnet, category string) *Torrent {
|
||||
torrent := &Torrent{
|
||||
ID: uuid.NewString(),
|
||||
Hash: strings.ToLower(magnet.InfoHash),
|
||||
Name: magnet.Name,
|
||||
Size: magnet.Size,
|
||||
Category: category,
|
||||
State: "downloading",
|
||||
AddedOn: time.Now().Unix(),
|
||||
MagnetUri: magnet.Link,
|
||||
|
||||
Tracker: "udp://tracker.opentrackr.org:1337",
|
||||
UpLimit: -1,
|
||||
DlLimit: -1,
|
||||
FlPiecePrio: false,
|
||||
ForceStart: false,
|
||||
AutoTmm: false,
|
||||
Availability: 2,
|
||||
MaxRatio: -1,
|
||||
MaxSeedingTime: -1,
|
||||
NumComplete: 10,
|
||||
NumIncomplete: 0,
|
||||
NumLeechs: 1,
|
||||
Ratio: 1,
|
||||
RatioLimit: 1,
|
||||
}
|
||||
return torrent
|
||||
}
|
||||
|
||||
func (q *QBit) processFiles(torrent *Torrent, debridTorrent *debrid.Torrent) {
|
||||
var wg sync.WaitGroup
|
||||
files := debridTorrent.Files
|
||||
ready := make(chan debrid.TorrentFile, len(files))
|
||||
|
||||
q.logger.Printf("Checking %d files...", len(files))
|
||||
rCloneMountPath := q.debrid.GetMountPath()
|
||||
path := filepath.Join(q.DownloadFolder, debridTorrent.Arr.CompletedFolder, debridTorrent.Folder) // /mnt/symlinks/{category}/MyTVShow/
|
||||
err := os.MkdirAll(path, os.ModePerm)
|
||||
if err != nil {
|
||||
q.logger.Printf("Failed to create directory: %s\n", path)
|
||||
}
|
||||
|
||||
for _, file := range files {
|
||||
wg.Add(1)
|
||||
go checkFileLoop(&wg, rCloneMountPath, file, ready)
|
||||
}
|
||||
|
||||
go func() {
|
||||
wg.Wait()
|
||||
close(ready)
|
||||
}()
|
||||
|
||||
for f := range ready {
|
||||
q.logger.Println("File is ready:", f.Path)
|
||||
q.createSymLink(path, debridTorrent, f)
|
||||
|
||||
}
|
||||
// Update the torrent when all files are ready
|
||||
q.UpdateTorrent(torrent, debridTorrent)
|
||||
q.logger.Printf("%s COMPLETED \n", debridTorrent.Name)
|
||||
}
|
||||
|
||||
func (q *QBit) createSymLink(path string, torrent *debrid.Torrent, file debrid.TorrentFile) {
|
||||
|
||||
// Combine the directory and filename to form a full path
|
||||
fullPath := filepath.Join(path, file.Name) // /mnt/symlinks/{category}/MyTVShow/MyTVShow.S01E01.720p.mkv
|
||||
// Create a symbolic link if file doesn't exist
|
||||
torrentMountPath := filepath.Join(q.debrid.GetMountPath(), torrent.Folder, file.Name) // debridFolder/MyTVShow/MyTVShow.S01E01.720p.mkv
|
||||
_ = os.Symlink(torrentMountPath, fullPath)
|
||||
// Check if the file exists
|
||||
if !fileReady(fullPath) {
|
||||
q.logger.Printf("Failed to create symlink: %s\n", fullPath)
|
||||
}
|
||||
}
|
||||
137
pkg/qbit/storage.go
Normal file
137
pkg/qbit/storage.go
Normal file
@@ -0,0 +1,137 @@
|
||||
package qbit
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"os"
|
||||
"sync"
|
||||
)
|
||||
|
||||
type TorrentStorage struct {
|
||||
torrents map[string]*Torrent
|
||||
mu sync.RWMutex
|
||||
order []string
|
||||
}
|
||||
|
||||
func loadTorrentsFromJSON(filename string) (map[string]*Torrent, error) {
|
||||
data, err := os.ReadFile(filename)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
torrents := make(map[string]*Torrent)
|
||||
if err := json.Unmarshal(data, &torrents); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return torrents, nil
|
||||
}
|
||||
|
||||
func NewTorrentStorage(filename string) *TorrentStorage {
|
||||
// Open the json file and read the data
|
||||
torrents, err := loadTorrentsFromJSON(filename)
|
||||
if err != nil {
|
||||
torrents = make(map[string]*Torrent)
|
||||
}
|
||||
order := make([]string, 0, len(torrents))
|
||||
for id := range torrents {
|
||||
order = append(order, id)
|
||||
}
|
||||
// Create a new TorrentStorage
|
||||
return &TorrentStorage{
|
||||
torrents: torrents,
|
||||
order: order,
|
||||
}
|
||||
}
|
||||
|
||||
func (ts *TorrentStorage) Add(torrent *Torrent) {
|
||||
ts.mu.Lock()
|
||||
defer ts.mu.Unlock()
|
||||
ts.torrents[torrent.Hash] = torrent
|
||||
ts.order = append(ts.order, torrent.Hash)
|
||||
}
|
||||
|
||||
func (ts *TorrentStorage) AddOrUpdate(torrent *Torrent) {
|
||||
ts.mu.Lock()
|
||||
defer ts.mu.Unlock()
|
||||
if _, exists := ts.torrents[torrent.Hash]; !exists {
|
||||
ts.order = append(ts.order, torrent.Hash)
|
||||
}
|
||||
ts.torrents[torrent.Hash] = torrent
|
||||
}
|
||||
|
||||
func (ts *TorrentStorage) GetByID(id string) *Torrent {
|
||||
ts.mu.RLock()
|
||||
defer ts.mu.RUnlock()
|
||||
for _, torrent := range ts.torrents {
|
||||
if torrent.ID == id {
|
||||
return torrent
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (ts *TorrentStorage) Get(hash string) *Torrent {
|
||||
ts.mu.RLock()
|
||||
defer ts.mu.RUnlock()
|
||||
return ts.torrents[hash]
|
||||
}
|
||||
|
||||
func (ts *TorrentStorage) GetAll(category string, filter string, hashes []string) []*Torrent {
|
||||
ts.mu.RLock()
|
||||
defer ts.mu.RUnlock()
|
||||
torrents := make([]*Torrent, 0, len(ts.torrents))
|
||||
for _, id := range ts.order {
|
||||
torrent := ts.torrents[id]
|
||||
if category != "" && torrent.Category != category {
|
||||
continue
|
||||
}
|
||||
if filter != "" && torrent.State != filter {
|
||||
continue
|
||||
}
|
||||
torrents = append(torrents, torrent)
|
||||
}
|
||||
if len(hashes) > 0 {
|
||||
filtered := make([]*Torrent, 0, len(torrents))
|
||||
for _, hash := range hashes {
|
||||
if torrent := ts.torrents[hash]; torrent != nil {
|
||||
filtered = append(filtered, torrent)
|
||||
}
|
||||
}
|
||||
return filtered
|
||||
}
|
||||
return torrents
|
||||
}
|
||||
|
||||
func (ts *TorrentStorage) Update(torrent *Torrent) {
|
||||
ts.mu.Lock()
|
||||
defer ts.mu.Unlock()
|
||||
ts.torrents[torrent.Hash] = torrent
|
||||
}
|
||||
|
||||
func (ts *TorrentStorage) Delete(hash string) {
|
||||
ts.mu.Lock()
|
||||
defer ts.mu.Unlock()
|
||||
torrent, exists := ts.torrents[hash]
|
||||
if !exists {
|
||||
return
|
||||
}
|
||||
delete(ts.torrents, hash)
|
||||
for i, id := range ts.order {
|
||||
if id == hash {
|
||||
ts.order = append(ts.order[:i], ts.order[i+1:]...)
|
||||
break
|
||||
}
|
||||
}
|
||||
// Delete the torrent folder
|
||||
if torrent.ContentPath != "" {
|
||||
os.RemoveAll(torrent.ContentPath)
|
||||
}
|
||||
}
|
||||
|
||||
func (ts *TorrentStorage) Save(filename string) error {
|
||||
ts.mu.RLock()
|
||||
defer ts.mu.RUnlock()
|
||||
data, err := json.Marshal(ts.torrents)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return os.WriteFile(filename, data, 0644)
|
||||
}
|
||||
404
pkg/qbit/structs.go
Normal file
404
pkg/qbit/structs.go
Normal file
@@ -0,0 +1,404 @@
|
||||
package qbit
|
||||
|
||||
type BuildInfo struct {
|
||||
Libtorrent string `json:"libtorrent"`
|
||||
Bitness int `json:"bitness"`
|
||||
Boost string `json:"boost"`
|
||||
Openssl string `json:"openssl"`
|
||||
Qt string `json:"qt"`
|
||||
Zlib string `json:"zlib"`
|
||||
}
|
||||
|
||||
type AppPreferences struct {
|
||||
AddTrackers string `json:"add_trackers"`
|
||||
AddTrackersEnabled bool `json:"add_trackers_enabled"`
|
||||
AltDlLimit int64 `json:"alt_dl_limit"`
|
||||
AltUpLimit int64 `json:"alt_up_limit"`
|
||||
AlternativeWebuiEnabled bool `json:"alternative_webui_enabled"`
|
||||
AlternativeWebuiPath string `json:"alternative_webui_path"`
|
||||
AnnounceIp string `json:"announce_ip"`
|
||||
AnnounceToAllTiers bool `json:"announce_to_all_tiers"`
|
||||
AnnounceToAllTrackers bool `json:"announce_to_all_trackers"`
|
||||
AnonymousMode bool `json:"anonymous_mode"`
|
||||
AsyncIoThreads int64 `json:"async_io_threads"`
|
||||
AutoDeleteMode int64 `json:"auto_delete_mode"`
|
||||
AutoTmmEnabled bool `json:"auto_tmm_enabled"`
|
||||
AutorunEnabled bool `json:"autorun_enabled"`
|
||||
AutorunProgram string `json:"autorun_program"`
|
||||
BannedIPs string `json:"banned_IPs"`
|
||||
BittorrentProtocol int64 `json:"bittorrent_protocol"`
|
||||
BypassAuthSubnetWhitelist string `json:"bypass_auth_subnet_whitelist"`
|
||||
BypassAuthSubnetWhitelistEnabled bool `json:"bypass_auth_subnet_whitelist_enabled"`
|
||||
BypassLocalAuth bool `json:"bypass_local_auth"`
|
||||
CategoryChangedTmmEnabled bool `json:"category_changed_tmm_enabled"`
|
||||
CheckingMemoryUse int64 `json:"checking_memory_use"`
|
||||
CreateSubfolderEnabled bool `json:"create_subfolder_enabled"`
|
||||
CurrentInterfaceAddress string `json:"current_interface_address"`
|
||||
CurrentNetworkInterface string `json:"current_network_interface"`
|
||||
Dht bool `json:"dht"`
|
||||
DiskCache int64 `json:"disk_cache"`
|
||||
DiskCacheTtl int64 `json:"disk_cache_ttl"`
|
||||
DlLimit int64 `json:"dl_limit"`
|
||||
DontCountSlowTorrents bool `json:"dont_count_slow_torrents"`
|
||||
DyndnsDomain string `json:"dyndns_domain"`
|
||||
DyndnsEnabled bool `json:"dyndns_enabled"`
|
||||
DyndnsPassword string `json:"dyndns_password"`
|
||||
DyndnsService int64 `json:"dyndns_service"`
|
||||
DyndnsUsername string `json:"dyndns_username"`
|
||||
EmbeddedTrackerPort int64 `json:"embedded_tracker_port"`
|
||||
EnableCoalesceReadWrite bool `json:"enable_coalesce_read_write"`
|
||||
EnableEmbeddedTracker bool `json:"enable_embedded_tracker"`
|
||||
EnableMultiConnectionsFromSameIp bool `json:"enable_multi_connections_from_same_ip"`
|
||||
EnableOsCache bool `json:"enable_os_cache"`
|
||||
EnablePieceExtentAffinity bool `json:"enable_piece_extent_affinity"`
|
||||
EnableSuperSeeding bool `json:"enable_super_seeding"`
|
||||
EnableUploadSuggestions bool `json:"enable_upload_suggestions"`
|
||||
Encryption int64 `json:"encryption"`
|
||||
ExportDir string `json:"export_dir"`
|
||||
ExportDirFin string `json:"export_dir_fin"`
|
||||
FilePoolSize int64 `json:"file_pool_size"`
|
||||
IncompleteFilesExt bool `json:"incomplete_files_ext"`
|
||||
IpFilterEnabled bool `json:"ip_filter_enabled"`
|
||||
IpFilterPath string `json:"ip_filter_path"`
|
||||
IpFilterTrackers bool `json:"ip_filter_trackers"`
|
||||
LimitLanPeers bool `json:"limit_lan_peers"`
|
||||
LimitTcpOverhead bool `json:"limit_tcp_overhead"`
|
||||
LimitUtpRate bool `json:"limit_utp_rate"`
|
||||
ListenPort int64 `json:"listen_port"`
|
||||
Locale string `json:"locale"`
|
||||
Lsd bool `json:"lsd"`
|
||||
MailNotificationAuthEnabled bool `json:"mail_notification_auth_enabled"`
|
||||
MailNotificationEmail string `json:"mail_notification_email"`
|
||||
MailNotificationEnabled bool `json:"mail_notification_enabled"`
|
||||
MailNotificationPassword string `json:"mail_notification_password"`
|
||||
MailNotificationSender string `json:"mail_notification_sender"`
|
||||
MailNotificationSmtp string `json:"mail_notification_smtp"`
|
||||
MailNotificationSslEnabled bool `json:"mail_notification_ssl_enabled"`
|
||||
MailNotificationUsername string `json:"mail_notification_username"`
|
||||
MaxActiveDownloads int64 `json:"max_active_downloads"`
|
||||
MaxActiveTorrents int64 `json:"max_active_torrents"`
|
||||
MaxActiveUploads int64 `json:"max_active_uploads"`
|
||||
MaxConnec int64 `json:"max_connec"`
|
||||
MaxConnecPerTorrent int64 `json:"max_connec_per_torrent"`
|
||||
MaxRatio int64 `json:"max_ratio"`
|
||||
MaxRatioAct int64 `json:"max_ratio_act"`
|
||||
MaxRatioEnabled bool `json:"max_ratio_enabled"`
|
||||
MaxSeedingTime int64 `json:"max_seeding_time"`
|
||||
MaxSeedingTimeEnabled bool `json:"max_seeding_time_enabled"`
|
||||
MaxUploads int64 `json:"max_uploads"`
|
||||
MaxUploadsPerTorrent int64 `json:"max_uploads_per_torrent"`
|
||||
OutgoingPortsMax int64 `json:"outgoing_ports_max"`
|
||||
OutgoingPortsMin int64 `json:"outgoing_ports_min"`
|
||||
Pex bool `json:"pex"`
|
||||
PreallocateAll bool `json:"preallocate_all"`
|
||||
ProxyAuthEnabled bool `json:"proxy_auth_enabled"`
|
||||
ProxyIp string `json:"proxy_ip"`
|
||||
ProxyPassword string `json:"proxy_password"`
|
||||
ProxyPeerConnections bool `json:"proxy_peer_connections"`
|
||||
ProxyPort int64 `json:"proxy_port"`
|
||||
ProxyTorrentsOnly bool `json:"proxy_torrents_only"`
|
||||
ProxyType int64 `json:"proxy_type"`
|
||||
ProxyUsername string `json:"proxy_username"`
|
||||
QueueingEnabled bool `json:"queueing_enabled"`
|
||||
RandomPort bool `json:"random_port"`
|
||||
RecheckCompletedTorrents bool `json:"recheck_completed_torrents"`
|
||||
ResolvePeerCountries bool `json:"resolve_peer_countries"`
|
||||
RssAutoDownloadingEnabled bool `json:"rss_auto_downloading_enabled"`
|
||||
RssMaxArticlesPerFeed int64 `json:"rss_max_articles_per_feed"`
|
||||
RssProcessingEnabled bool `json:"rss_processing_enabled"`
|
||||
RssRefreshInterval int64 `json:"rss_refresh_interval"`
|
||||
SavePath string `json:"save_path"`
|
||||
SavePathChangedTmmEnabled bool `json:"save_path_changed_tmm_enabled"`
|
||||
SaveResumeDataInterval int64 `json:"save_resume_data_interval"`
|
||||
ScanDirs ScanDirs `json:"scan_dirs"`
|
||||
ScheduleFromHour int64 `json:"schedule_from_hour"`
|
||||
ScheduleFromMin int64 `json:"schedule_from_min"`
|
||||
ScheduleToHour int64 `json:"schedule_to_hour"`
|
||||
ScheduleToMin int64 `json:"schedule_to_min"`
|
||||
SchedulerDays int64 `json:"scheduler_days"`
|
||||
SchedulerEnabled bool `json:"scheduler_enabled"`
|
||||
SendBufferLowWatermark int64 `json:"send_buffer_low_watermark"`
|
||||
SendBufferWatermark int64 `json:"send_buffer_watermark"`
|
||||
SendBufferWatermarkFactor int64 `json:"send_buffer_watermark_factor"`
|
||||
SlowTorrentDlRateThreshold int64 `json:"slow_torrent_dl_rate_threshold"`
|
||||
SlowTorrentInactiveTimer int64 `json:"slow_torrent_inactive_timer"`
|
||||
SlowTorrentUlRateThreshold int64 `json:"slow_torrent_ul_rate_threshold"`
|
||||
SocketBacklogSize int64 `json:"socket_backlog_size"`
|
||||
StartPausedEnabled bool `json:"start_paused_enabled"`
|
||||
StopTrackerTimeout int64 `json:"stop_tracker_timeout"`
|
||||
TempPath string `json:"temp_path"`
|
||||
TempPathEnabled bool `json:"temp_path_enabled"`
|
||||
TorrentChangedTmmEnabled bool `json:"torrent_changed_tmm_enabled"`
|
||||
UpLimit int64 `json:"up_limit"`
|
||||
UploadChokingAlgorithm int64 `json:"upload_choking_algorithm"`
|
||||
UploadSlotsBehavior int64 `json:"upload_slots_behavior"`
|
||||
Upnp bool `json:"upnp"`
|
||||
UpnpLeaseDuration int64 `json:"upnp_lease_duration"`
|
||||
UseHttps bool `json:"use_https"`
|
||||
UtpTcpMixedMode int64 `json:"utp_tcp_mixed_mode"`
|
||||
WebUiAddress string `json:"web_ui_address"`
|
||||
WebUiBanDuration int64 `json:"web_ui_ban_duration"`
|
||||
WebUiClickjackingProtectionEnabled bool `json:"web_ui_clickjacking_protection_enabled"`
|
||||
WebUiCsrfProtectionEnabled bool `json:"web_ui_csrf_protection_enabled"`
|
||||
WebUiDomainList string `json:"web_ui_domain_list"`
|
||||
WebUiHostHeaderValidationEnabled bool `json:"web_ui_host_header_validation_enabled"`
|
||||
WebUiHttpsCertPath string `json:"web_ui_https_cert_path"`
|
||||
WebUiHttpsKeyPath string `json:"web_ui_https_key_path"`
|
||||
WebUiMaxAuthFailCount int64 `json:"web_ui_max_auth_fail_count"`
|
||||
WebUiPort int64 `json:"web_ui_port"`
|
||||
WebUiSecureCookieEnabled bool `json:"web_ui_secure_cookie_enabled"`
|
||||
WebUiSessionTimeout int64 `json:"web_ui_session_timeout"`
|
||||
WebUiUpnp bool `json:"web_ui_upnp"`
|
||||
WebUiUsername string `json:"web_ui_username"`
|
||||
WebUiPassword string `json:"web_ui_password"`
|
||||
SSLKey string `json:"ssl_key"`
|
||||
SSLCert string `json:"ssl_cert"`
|
||||
RSSDownloadRepack string `json:"rss_download_repack_proper_episodes"`
|
||||
RSSSmartEpisodeFilters string `json:"rss_smart_episode_filters"`
|
||||
WebUiUseCustomHttpHeaders bool `json:"web_ui_use_custom_http_headers"`
|
||||
WebUiUseCustomHttpHeadersEnabled bool `json:"web_ui_use_custom_http_headers_enabled"`
|
||||
}
|
||||
|
||||
type ScanDirs struct{}
|
||||
|
||||
type TorrentCategory struct {
|
||||
Name string `json:"name"`
|
||||
SavePath string `json:"savePath"`
|
||||
}
|
||||
|
||||
type Torrent struct {
|
||||
ID string `json:"-"`
|
||||
|
||||
AddedOn int64 `json:"added_on,omitempty"`
|
||||
AmountLeft int64 `json:"amount_left,omitempty"`
|
||||
AutoTmm bool `json:"auto_tmm"`
|
||||
Availability float64 `json:"availability"`
|
||||
Category string `json:"category,omitempty"`
|
||||
Completed int64 `json:"completed,omitempty"`
|
||||
CompletionOn int64 `json:"completion_on,omitempty"`
|
||||
ContentPath string `json:"content_path,omitempty"`
|
||||
DlLimit int64 `json:"dl_limit,omitempty"`
|
||||
Dlspeed int64 `json:"dlspeed,omitempty"`
|
||||
Downloaded int64 `json:"downloaded,omitempty"`
|
||||
DownloadedSession int64 `json:"downloaded_session,omitempty"`
|
||||
Eta int64 `json:"eta,omitempty"`
|
||||
FlPiecePrio bool `json:"f_l_piece_prio"`
|
||||
ForceStart bool `json:"force_start"`
|
||||
Hash string `json:"hash"`
|
||||
LastActivity int64 `json:"last_activity,omitempty"`
|
||||
MagnetUri string `json:"magnet_uri,omitempty"`
|
||||
MaxRatio int64 `json:"max_ratio,omitempty"`
|
||||
MaxSeedingTime int64 `json:"max_seeding_time,omitempty"`
|
||||
Name string `json:"name,omitempty"`
|
||||
NumComplete int64 `json:"num_complete,omitempty"`
|
||||
NumIncomplete int64 `json:"num_incomplete,omitempty"`
|
||||
NumLeechs int64 `json:"num_leechs,omitempty"`
|
||||
NumSeeds int64 `json:"num_seeds,omitempty"`
|
||||
Priority int64 `json:"priority,omitempty"`
|
||||
Progress float32 `json:"progress"`
|
||||
Ratio int64 `json:"ratio,omitempty"`
|
||||
RatioLimit int64 `json:"ratio_limit,omitempty"`
|
||||
SavePath string `json:"save_path,omitempty"`
|
||||
SeedingTimeLimit int64 `json:"seeding_time_limit,omitempty"`
|
||||
SeenComplete int64 `json:"seen_complete,omitempty"`
|
||||
SeqDl bool `json:"seq_dl"`
|
||||
Size int64 `json:"size,omitempty"`
|
||||
State string `json:"state,omitempty"`
|
||||
SuperSeeding bool `json:"super_seeding"`
|
||||
Tags string `json:"tags,omitempty"`
|
||||
TimeActive int64 `json:"time_active,omitempty"`
|
||||
TotalSize int64 `json:"total_size,omitempty"`
|
||||
Tracker string `json:"tracker,omitempty"`
|
||||
UpLimit int64 `json:"up_limit,omitempty"`
|
||||
Uploaded int64 `json:"uploaded,omitempty"`
|
||||
UploadedSession int64 `json:"uploaded_session,omitempty"`
|
||||
Upspeed int64 `json:"upspeed,omitempty"`
|
||||
}
|
||||
|
||||
type TorrentProperties struct {
|
||||
AdditionDate int64 `json:"addition_date,omitempty"`
|
||||
Comment string `json:"comment,omitempty"`
|
||||
CompletionDate int64 `json:"completion_date,omitempty"`
|
||||
CreatedBy string `json:"created_by,omitempty"`
|
||||
CreationDate int64 `json:"creation_date,omitempty"`
|
||||
DlLimit int64 `json:"dl_limit,omitempty"`
|
||||
DlSpeed int64 `json:"dl_speed,omitempty"`
|
||||
DlSpeedAvg int64 `json:"dl_speed_avg,omitempty"`
|
||||
Eta int64 `json:"eta,omitempty"`
|
||||
LastSeen int64 `json:"last_seen,omitempty"`
|
||||
NbConnections int64 `json:"nb_connections,omitempty"`
|
||||
NbConnectionsLimit int64 `json:"nb_connections_limit,omitempty"`
|
||||
Peers int64 `json:"peers,omitempty"`
|
||||
PeersTotal int64 `json:"peers_total,omitempty"`
|
||||
PieceSize int64 `json:"piece_size,omitempty"`
|
||||
PiecesHave int64 `json:"pieces_have,omitempty"`
|
||||
PiecesNum int64 `json:"pieces_num,omitempty"`
|
||||
Reannounce int64 `json:"reannounce,omitempty"`
|
||||
SavePath string `json:"save_path,omitempty"`
|
||||
SeedingTime int64 `json:"seeding_time,omitempty"`
|
||||
Seeds int64 `json:"seeds,omitempty"`
|
||||
SeedsTotal int64 `json:"seeds_total,omitempty"`
|
||||
ShareRatio int64 `json:"share_ratio,omitempty"`
|
||||
TimeElapsed int64 `json:"time_elapsed,omitempty"`
|
||||
TotalDownloaded int64 `json:"total_downloaded,omitempty"`
|
||||
TotalDownloadedSession int64 `json:"total_downloaded_session,omitempty"`
|
||||
TotalSize int64 `json:"total_size,omitempty"`
|
||||
TotalUploaded int64 `json:"total_uploaded,omitempty"`
|
||||
TotalUploadedSession int64 `json:"total_uploaded_session,omitempty"`
|
||||
TotalWasted int64 `json:"total_wasted,omitempty"`
|
||||
UpLimit int64 `json:"up_limit,omitempty"`
|
||||
UpSpeed int64 `json:"up_speed,omitempty"`
|
||||
UpSpeedAvg int64 `json:"up_speed_avg,omitempty"`
|
||||
}
|
||||
|
||||
func NewAppPreferences() *AppPreferences {
|
||||
preferences := &AppPreferences{
|
||||
AddTrackers: "",
|
||||
AddTrackersEnabled: false,
|
||||
AltDlLimit: 10240,
|
||||
AltUpLimit: 10240,
|
||||
AlternativeWebuiEnabled: false,
|
||||
AlternativeWebuiPath: "",
|
||||
AnnounceIp: "",
|
||||
AnnounceToAllTiers: true,
|
||||
AnnounceToAllTrackers: false,
|
||||
AnonymousMode: false,
|
||||
AsyncIoThreads: 4,
|
||||
AutoDeleteMode: 0,
|
||||
AutoTmmEnabled: false,
|
||||
AutorunEnabled: false,
|
||||
AutorunProgram: "",
|
||||
BannedIPs: "",
|
||||
BittorrentProtocol: 0,
|
||||
BypassAuthSubnetWhitelist: "",
|
||||
BypassAuthSubnetWhitelistEnabled: false,
|
||||
BypassLocalAuth: false,
|
||||
CategoryChangedTmmEnabled: false,
|
||||
CheckingMemoryUse: 32,
|
||||
CreateSubfolderEnabled: true,
|
||||
CurrentInterfaceAddress: "",
|
||||
CurrentNetworkInterface: "",
|
||||
Dht: true,
|
||||
DiskCache: -1,
|
||||
DiskCacheTtl: 60,
|
||||
DlLimit: 0,
|
||||
DontCountSlowTorrents: false,
|
||||
DyndnsDomain: "changeme.dyndns.org",
|
||||
DyndnsEnabled: false,
|
||||
DyndnsPassword: "",
|
||||
DyndnsService: 0,
|
||||
DyndnsUsername: "",
|
||||
EmbeddedTrackerPort: 9000,
|
||||
EnableCoalesceReadWrite: true,
|
||||
EnableEmbeddedTracker: false,
|
||||
EnableMultiConnectionsFromSameIp: false,
|
||||
EnableOsCache: true,
|
||||
EnablePieceExtentAffinity: false,
|
||||
EnableSuperSeeding: false,
|
||||
EnableUploadSuggestions: false,
|
||||
Encryption: 0,
|
||||
ExportDir: "",
|
||||
ExportDirFin: "",
|
||||
FilePoolSize: 40,
|
||||
IncompleteFilesExt: false,
|
||||
IpFilterEnabled: false,
|
||||
IpFilterPath: "",
|
||||
IpFilterTrackers: false,
|
||||
LimitLanPeers: true,
|
||||
LimitTcpOverhead: false,
|
||||
LimitUtpRate: true,
|
||||
ListenPort: 31193,
|
||||
Locale: "en",
|
||||
Lsd: true,
|
||||
MailNotificationAuthEnabled: false,
|
||||
MailNotificationEmail: "",
|
||||
MailNotificationEnabled: false,
|
||||
MailNotificationPassword: "",
|
||||
MailNotificationSender: "qBittorrentNotification@example.com",
|
||||
MailNotificationSmtp: "smtp.changeme.com",
|
||||
MailNotificationSslEnabled: false,
|
||||
MailNotificationUsername: "",
|
||||
MaxActiveDownloads: 3,
|
||||
MaxActiveTorrents: 5,
|
||||
MaxActiveUploads: 3,
|
||||
MaxConnec: 500,
|
||||
MaxConnecPerTorrent: 100,
|
||||
MaxRatio: -1,
|
||||
MaxRatioAct: 0,
|
||||
MaxRatioEnabled: false,
|
||||
MaxSeedingTime: -1,
|
||||
MaxSeedingTimeEnabled: false,
|
||||
MaxUploads: -1,
|
||||
MaxUploadsPerTorrent: -1,
|
||||
OutgoingPortsMax: 0,
|
||||
OutgoingPortsMin: 0,
|
||||
Pex: true,
|
||||
PreallocateAll: false,
|
||||
ProxyAuthEnabled: false,
|
||||
ProxyIp: "0.0.0.0",
|
||||
ProxyPassword: "",
|
||||
ProxyPeerConnections: false,
|
||||
ProxyPort: 8080,
|
||||
ProxyTorrentsOnly: false,
|
||||
ProxyType: 0,
|
||||
ProxyUsername: "",
|
||||
QueueingEnabled: false,
|
||||
RandomPort: false,
|
||||
RecheckCompletedTorrents: false,
|
||||
ResolvePeerCountries: true,
|
||||
RssAutoDownloadingEnabled: false,
|
||||
RssMaxArticlesPerFeed: 50,
|
||||
RssProcessingEnabled: false,
|
||||
RssRefreshInterval: 30,
|
||||
SavePathChangedTmmEnabled: false,
|
||||
SaveResumeDataInterval: 60,
|
||||
ScanDirs: ScanDirs{},
|
||||
ScheduleFromHour: 8,
|
||||
ScheduleFromMin: 0,
|
||||
ScheduleToHour: 20,
|
||||
ScheduleToMin: 0,
|
||||
SchedulerDays: 0,
|
||||
SchedulerEnabled: false,
|
||||
SendBufferLowWatermark: 10,
|
||||
SendBufferWatermark: 500,
|
||||
SendBufferWatermarkFactor: 50,
|
||||
SlowTorrentDlRateThreshold: 2,
|
||||
SlowTorrentInactiveTimer: 60,
|
||||
SlowTorrentUlRateThreshold: 2,
|
||||
SocketBacklogSize: 30,
|
||||
StartPausedEnabled: false,
|
||||
StopTrackerTimeout: 1,
|
||||
TempPathEnabled: false,
|
||||
TorrentChangedTmmEnabled: true,
|
||||
UpLimit: 0,
|
||||
UploadChokingAlgorithm: 1,
|
||||
UploadSlotsBehavior: 0,
|
||||
Upnp: true,
|
||||
UpnpLeaseDuration: 0,
|
||||
UseHttps: false,
|
||||
UtpTcpMixedMode: 0,
|
||||
WebUiAddress: "*",
|
||||
WebUiBanDuration: 3600,
|
||||
WebUiClickjackingProtectionEnabled: true,
|
||||
WebUiCsrfProtectionEnabled: true,
|
||||
WebUiDomainList: "*",
|
||||
WebUiHostHeaderValidationEnabled: true,
|
||||
WebUiHttpsCertPath: "",
|
||||
WebUiHttpsKeyPath: "",
|
||||
WebUiMaxAuthFailCount: 5,
|
||||
WebUiPort: 8080,
|
||||
WebUiSecureCookieEnabled: true,
|
||||
WebUiSessionTimeout: 3600,
|
||||
WebUiUpnp: false,
|
||||
|
||||
// Fields in the struct but not in the JSON (set to zero values):
|
||||
WebUiPassword: "",
|
||||
SSLKey: "",
|
||||
SSLCert: "",
|
||||
RSSDownloadRepack: "",
|
||||
RSSSmartEpisodeFilters: "",
|
||||
WebUiUseCustomHttpHeaders: false,
|
||||
WebUiUseCustomHttpHeadersEnabled: false,
|
||||
}
|
||||
return preferences
|
||||
}
|
||||
102
pkg/qbit/torrent.go
Normal file
102
pkg/qbit/torrent.go
Normal file
@@ -0,0 +1,102 @@
|
||||
package qbit
|
||||
|
||||
import (
|
||||
"cmp"
|
||||
"goBlack/pkg/debrid"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"time"
|
||||
)
|
||||
|
||||
// All torrent related helpers goes here
|
||||
|
||||
func (q *QBit) MarkAsFailed(t *Torrent) *Torrent {
|
||||
t.State = "error"
|
||||
q.storage.AddOrUpdate(t)
|
||||
return t
|
||||
}
|
||||
|
||||
func (q *QBit) UpdateTorrent(t *Torrent, debridTorrent *debrid.Torrent) *Torrent {
|
||||
if debridTorrent == nil && t.ID != "" {
|
||||
debridTorrent, _ = q.debrid.GetTorrent(t.ID)
|
||||
}
|
||||
if debridTorrent == nil {
|
||||
q.logger.Printf("Torrent with ID %s not found in %s", t.ID, q.debrid.GetName())
|
||||
return t
|
||||
}
|
||||
totalSize := cmp.Or(debridTorrent.Bytes, 1)
|
||||
progress := int64(cmp.Or(debridTorrent.Progress, 100))
|
||||
progress = progress / 100.0
|
||||
|
||||
sizeCompleted := totalSize * progress
|
||||
savePath := filepath.Join(q.DownloadFolder, t.Category) + string(os.PathSeparator)
|
||||
torrentPath := filepath.Join(savePath, debridTorrent.Folder) + string(os.PathSeparator)
|
||||
|
||||
var speed int64
|
||||
if debridTorrent.Speed != 0 {
|
||||
speed = int64(debridTorrent.Speed)
|
||||
}
|
||||
var eta int64
|
||||
if speed != 0 {
|
||||
eta = (totalSize - sizeCompleted) / speed
|
||||
}
|
||||
|
||||
t.Name = debridTorrent.Name
|
||||
t.Size = debridTorrent.Bytes
|
||||
t.Completed = sizeCompleted
|
||||
t.Downloaded = sizeCompleted
|
||||
t.DownloadedSession = sizeCompleted
|
||||
t.Uploaded = sizeCompleted
|
||||
t.UploadedSession = sizeCompleted
|
||||
t.AmountLeft = totalSize - sizeCompleted
|
||||
t.Progress = 100
|
||||
t.SavePath = savePath
|
||||
t.ContentPath = torrentPath
|
||||
t.Eta = eta
|
||||
t.Dlspeed = speed
|
||||
t.Upspeed = speed
|
||||
|
||||
if t.AmountLeft == 0 {
|
||||
t.State = "pausedUP"
|
||||
}
|
||||
|
||||
go q.storage.AddOrUpdate(t)
|
||||
return t
|
||||
}
|
||||
|
||||
func (q *QBit) ResumeTorrent(t *Torrent) bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func (q *QBit) PauseTorrent(t *Torrent) bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func (q *QBit) RefreshTorrent(t *Torrent) bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func (q *QBit) GetTorrentProperties(t *Torrent) *TorrentProperties {
|
||||
return &TorrentProperties{
|
||||
AdditionDate: t.AddedOn,
|
||||
Comment: "Debrid Blackhole <https://github.com/sirrobot01/debrid-blackhole>",
|
||||
CreatedBy: "Debrid Blackhole <https://github.com/sirrobot01/debrid-blackhole>",
|
||||
CreationDate: t.AddedOn,
|
||||
DlLimit: -1,
|
||||
UpLimit: -1,
|
||||
DlSpeed: t.Dlspeed,
|
||||
UpSpeed: t.Upspeed,
|
||||
TotalSize: t.Size,
|
||||
TotalUploaded: t.Uploaded,
|
||||
TotalDownloaded: t.Downloaded,
|
||||
TotalUploadedSession: t.UploadedSession,
|
||||
TotalDownloadedSession: t.DownloadedSession,
|
||||
LastSeen: time.Now().Unix(),
|
||||
NbConnectionsLimit: 100,
|
||||
Peers: 0,
|
||||
PeersTotal: 2,
|
||||
SeedingTime: 1,
|
||||
Seeds: 100,
|
||||
ShareRatio: 100,
|
||||
}
|
||||
}
|
||||
48
pkg/qbit/utils.go
Normal file
48
pkg/qbit/utils.go
Normal file
@@ -0,0 +1,48 @@
|
||||
package qbit
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"goBlack/pkg/debrid"
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
func generateSID() (string, error) {
|
||||
bytes := make([]byte, sidLength)
|
||||
if _, err := rand.Read(bytes); err != nil {
|
||||
return "", err
|
||||
}
|
||||
return hex.EncodeToString(bytes), nil
|
||||
}
|
||||
|
||||
func JSONResponse(w http.ResponseWriter, data interface{}, code int) {
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.WriteHeader(code)
|
||||
json.NewEncoder(w).Encode(data)
|
||||
}
|
||||
|
||||
func fileReady(path string) bool {
|
||||
_, err := os.Stat(path)
|
||||
return !os.IsNotExist(err) // Returns true if the file exists
|
||||
}
|
||||
|
||||
func checkFileLoop(wg *sync.WaitGroup, dir string, file debrid.TorrentFile, ready chan<- debrid.TorrentFile) {
|
||||
defer wg.Done()
|
||||
ticker := time.NewTicker(1 * time.Second) // Check every second
|
||||
defer ticker.Stop()
|
||||
path := filepath.Join(dir, file.Path)
|
||||
for {
|
||||
select {
|
||||
case <-ticker.C:
|
||||
if fileReady(path) {
|
||||
ready <- file
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user