Initial commit: docker compose config
Release Docker multi arch / docker (push) Has been cancelled
Test Install Script / Test Script Syntax (push) Has been cancelled
Test Install Script / Test on almalinux-10 (default) (push) Has been cancelled
Test Install Script / Test on almalinux-10 (root) (push) Has been cancelled
Test Install Script / Test on almalinux-8 (default) (push) Has been cancelled
Test Install Script / Test on almalinux-8 (root) (push) Has been cancelled
Test Install Script / Test on almalinux-9 (default) (push) Has been cancelled
Test Install Script / Test on almalinux-9 (root) (push) Has been cancelled
Test Install Script / Test on amazonlinux-2 (default) (push) Has been cancelled
Test Install Script / Test on amazonlinux-2 (root) (push) Has been cancelled
Test Install Script / Test on debian-11 (default) (push) Has been cancelled
Test Install Script / Test on debian-11 (root) (push) Has been cancelled
Test Install Script / Test on debian-12 (default) (push) Has been cancelled
Test Install Script / Test on debian-12 (root) (push) Has been cancelled
Test Install Script / Test on debian-13 (default) (push) Has been cancelled
Test Install Script / Test on debian-13 (root) (push) Has been cancelled
Test Install Script / Test on fedora-latest (default) (push) Has been cancelled
Test Install Script / Test on fedora-latest (root) (push) Has been cancelled
Test Install Script / Test on rocky-10 (default) (push) Has been cancelled
Test Install Script / Test on rocky-10 (root) (push) Has been cancelled
Test Install Script / Test on rocky-8 (default) (push) Has been cancelled
Test Install Script / Test on rocky-8 (root) (push) Has been cancelled
Test Install Script / Test on rocky-9 (default) (push) Has been cancelled
Test Install Script / Test on rocky-9 (root) (push) Has been cancelled
Test Install Script / Test on ubuntu-22.04 (default) (push) Has been cancelled
Test Install Script / Test on ubuntu-22.04 (root) (push) Has been cancelled
Test Install Script / Test on ubuntu-24.04 (default) (push) Has been cancelled
Test Install Script / Test on ubuntu-24.04 (root) (push) Has been cancelled
Release Docker multi arch / docker (push) Has been cancelled
Test Install Script / Test Script Syntax (push) Has been cancelled
Test Install Script / Test on almalinux-10 (default) (push) Has been cancelled
Test Install Script / Test on almalinux-10 (root) (push) Has been cancelled
Test Install Script / Test on almalinux-8 (default) (push) Has been cancelled
Test Install Script / Test on almalinux-8 (root) (push) Has been cancelled
Test Install Script / Test on almalinux-9 (default) (push) Has been cancelled
Test Install Script / Test on almalinux-9 (root) (push) Has been cancelled
Test Install Script / Test on amazonlinux-2 (default) (push) Has been cancelled
Test Install Script / Test on amazonlinux-2 (root) (push) Has been cancelled
Test Install Script / Test on debian-11 (default) (push) Has been cancelled
Test Install Script / Test on debian-11 (root) (push) Has been cancelled
Test Install Script / Test on debian-12 (default) (push) Has been cancelled
Test Install Script / Test on debian-12 (root) (push) Has been cancelled
Test Install Script / Test on debian-13 (default) (push) Has been cancelled
Test Install Script / Test on debian-13 (root) (push) Has been cancelled
Test Install Script / Test on fedora-latest (default) (push) Has been cancelled
Test Install Script / Test on fedora-latest (root) (push) Has been cancelled
Test Install Script / Test on rocky-10 (default) (push) Has been cancelled
Test Install Script / Test on rocky-10 (root) (push) Has been cancelled
Test Install Script / Test on rocky-8 (default) (push) Has been cancelled
Test Install Script / Test on rocky-8 (root) (push) Has been cancelled
Test Install Script / Test on rocky-9 (default) (push) Has been cancelled
Test Install Script / Test on rocky-9 (root) (push) Has been cancelled
Test Install Script / Test on ubuntu-22.04 (default) (push) Has been cancelled
Test Install Script / Test on ubuntu-22.04 (root) (push) Has been cancelled
Test Install Script / Test on ubuntu-24.04 (default) (push) Has been cancelled
Test Install Script / Test on ubuntu-24.04 (root) (push) Has been cancelled
This commit is contained in:
@@ -0,0 +1,30 @@
|
||||
package state
|
||||
|
||||
import (
|
||||
"server/torr/state"
|
||||
)
|
||||
|
||||
type CacheState struct {
|
||||
Hash string
|
||||
Capacity int64
|
||||
Filled int64
|
||||
PiecesLength int64
|
||||
PiecesCount int
|
||||
Torrent *state.TorrentStatus
|
||||
Pieces map[int]ItemState
|
||||
Readers []*ReaderState
|
||||
}
|
||||
|
||||
type ItemState struct {
|
||||
Id int
|
||||
Length int64
|
||||
Size int64
|
||||
Completed bool
|
||||
Priority int
|
||||
}
|
||||
|
||||
type ReaderState struct {
|
||||
Start int
|
||||
End int
|
||||
Reader int
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
package storage
|
||||
|
||||
import (
|
||||
"github.com/anacrolix/torrent/metainfo"
|
||||
"github.com/anacrolix/torrent/storage"
|
||||
)
|
||||
|
||||
type Storage interface {
|
||||
storage.ClientImpl
|
||||
|
||||
CloseHash(hash metainfo.Hash)
|
||||
}
|
||||
@@ -0,0 +1,389 @@
|
||||
package torrstor
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/anacrolix/torrent"
|
||||
|
||||
"server/log"
|
||||
"server/settings"
|
||||
"server/torr/storage/state"
|
||||
"server/torr/utils"
|
||||
|
||||
"github.com/anacrolix/torrent/metainfo"
|
||||
"github.com/anacrolix/torrent/storage"
|
||||
)
|
||||
|
||||
type Cache struct {
|
||||
storage.TorrentImpl
|
||||
storage *Storage
|
||||
|
||||
capacity int64
|
||||
filled int64
|
||||
hash metainfo.Hash
|
||||
|
||||
pieceLength int64
|
||||
pieceCount int
|
||||
|
||||
pieces map[int]*Piece
|
||||
|
||||
readers map[*Reader]struct{}
|
||||
muReaders sync.Mutex
|
||||
|
||||
isRemove bool
|
||||
isClosed bool
|
||||
muRemove sync.Mutex
|
||||
torrent *torrent.Torrent
|
||||
}
|
||||
|
||||
func NewCache(capacity int64, storage *Storage) *Cache {
|
||||
ret := &Cache{
|
||||
capacity: capacity,
|
||||
filled: 0,
|
||||
pieces: make(map[int]*Piece),
|
||||
storage: storage,
|
||||
readers: make(map[*Reader]struct{}),
|
||||
}
|
||||
|
||||
return ret
|
||||
}
|
||||
|
||||
func (c *Cache) Init(info *metainfo.Info, hash metainfo.Hash) {
|
||||
log.TLogln("Create cache for:", info.Name, hash.HexString())
|
||||
if c.capacity == 0 {
|
||||
c.capacity = info.PieceLength * 4
|
||||
}
|
||||
|
||||
c.pieceLength = info.PieceLength
|
||||
c.pieceCount = info.NumPieces()
|
||||
c.hash = hash
|
||||
|
||||
if settings.BTsets.UseDisk {
|
||||
name := filepath.Join(settings.BTsets.TorrentsSavePath, hash.HexString())
|
||||
err := os.MkdirAll(name, 0o777)
|
||||
if err != nil {
|
||||
log.TLogln("Error create dir:", err)
|
||||
}
|
||||
}
|
||||
|
||||
for i := 0; i < c.pieceCount; i++ {
|
||||
c.pieces[i] = NewPiece(i, c)
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Cache) SetTorrent(torr *torrent.Torrent) {
|
||||
c.torrent = torr
|
||||
}
|
||||
|
||||
func (c *Cache) Piece(m metainfo.Piece) storage.PieceImpl {
|
||||
if val, ok := c.pieces[m.Index()]; ok {
|
||||
return val
|
||||
}
|
||||
return &PieceFake{}
|
||||
}
|
||||
|
||||
func (c *Cache) Close() error {
|
||||
if c.torrent != nil {
|
||||
log.TLogln("Close cache for:", c.torrent.Name(), c.hash)
|
||||
} else {
|
||||
log.TLogln("Close cache for:", c.hash)
|
||||
}
|
||||
c.isClosed = true
|
||||
|
||||
delete(c.storage.caches, c.hash)
|
||||
|
||||
if settings.BTsets.RemoveCacheOnDrop {
|
||||
name := filepath.Join(settings.BTsets.TorrentsSavePath, c.hash.HexString())
|
||||
if name != "" && name != "/" {
|
||||
for _, v := range c.pieces {
|
||||
if v.dPiece != nil {
|
||||
os.Remove(v.dPiece.name)
|
||||
}
|
||||
}
|
||||
os.Remove(name)
|
||||
}
|
||||
}
|
||||
|
||||
c.muReaders.Lock()
|
||||
c.readers = nil
|
||||
c.pieces = nil
|
||||
c.muReaders.Unlock()
|
||||
|
||||
utils.FreeOSMemGC()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Cache) removePiece(piece *Piece) {
|
||||
if !c.isClosed {
|
||||
piece.Release()
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Cache) AdjustRA(readahead int64) {
|
||||
if settings.BTsets.CacheSize == 0 {
|
||||
c.capacity = readahead * 3
|
||||
}
|
||||
if c.Readers() > 0 {
|
||||
c.muReaders.Lock()
|
||||
for r := range c.readers {
|
||||
r.SetReadahead(readahead)
|
||||
}
|
||||
c.muReaders.Unlock()
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Cache) GetState() *state.CacheState {
|
||||
cState := new(state.CacheState)
|
||||
|
||||
piecesState := make(map[int]state.ItemState, 0)
|
||||
var fill int64 = 0
|
||||
|
||||
if len(c.pieces) > 0 {
|
||||
for _, p := range c.pieces {
|
||||
if p.Size > 0 {
|
||||
fill += p.Size
|
||||
piecesState[p.Id] = state.ItemState{
|
||||
Id: p.Id,
|
||||
Size: p.Size,
|
||||
Length: c.pieceLength,
|
||||
Completed: p.Complete,
|
||||
Priority: int(c.torrent.PieceState(p.Id).Priority),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
readersState := make([]*state.ReaderState, 0)
|
||||
|
||||
if c.Readers() > 0 {
|
||||
c.muReaders.Lock()
|
||||
for r := range c.readers {
|
||||
rng := r.getPiecesRange()
|
||||
pc := r.getReaderPiece()
|
||||
readersState = append(readersState, &state.ReaderState{
|
||||
Start: rng.Start,
|
||||
End: rng.End,
|
||||
Reader: pc,
|
||||
})
|
||||
}
|
||||
c.muReaders.Unlock()
|
||||
}
|
||||
|
||||
c.filled = fill
|
||||
cState.Capacity = c.capacity
|
||||
cState.PiecesLength = c.pieceLength
|
||||
cState.PiecesCount = c.pieceCount
|
||||
cState.Hash = c.hash.HexString()
|
||||
cState.Filled = fill
|
||||
cState.Pieces = piecesState
|
||||
cState.Readers = readersState
|
||||
return cState
|
||||
}
|
||||
|
||||
func (c *Cache) cleanPieces() {
|
||||
if c.isRemove || c.isClosed {
|
||||
return
|
||||
}
|
||||
c.muRemove.Lock()
|
||||
if c.isRemove {
|
||||
c.muRemove.Unlock()
|
||||
return
|
||||
}
|
||||
c.isRemove = true
|
||||
defer func() { c.isRemove = false }()
|
||||
c.muRemove.Unlock()
|
||||
|
||||
remPieces := c.getRemPieces()
|
||||
if c.filled > c.capacity {
|
||||
rems := (c.filled-c.capacity)/c.pieceLength + 1
|
||||
for _, p := range remPieces {
|
||||
c.removePiece(p)
|
||||
rems--
|
||||
if rems <= 0 {
|
||||
utils.FreeOSMemGC()
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Cache) getRemPieces() []*Piece {
|
||||
piecesRemove := make([]*Piece, 0)
|
||||
fill := int64(0)
|
||||
|
||||
ranges := make([]Range, 0)
|
||||
c.muReaders.Lock()
|
||||
for r := range c.readers {
|
||||
r.checkReader()
|
||||
if r.isUse {
|
||||
ranges = append(ranges, r.getPiecesRange())
|
||||
}
|
||||
}
|
||||
c.muReaders.Unlock()
|
||||
ranges = mergeRange(ranges)
|
||||
|
||||
for id, p := range c.pieces {
|
||||
if p.Size > 0 {
|
||||
fill += p.Size
|
||||
}
|
||||
if len(ranges) > 0 {
|
||||
if !inRanges(ranges, id) {
|
||||
if p.Size > 0 && !c.isIdInFileBE(ranges, id) {
|
||||
piecesRemove = append(piecesRemove, p)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// on preload clean
|
||||
if p.Size > 0 && !c.isIdInFileBE(ranges, id) {
|
||||
piecesRemove = append(piecesRemove, p)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
c.clearPriority()
|
||||
c.setLoadPriority(ranges)
|
||||
|
||||
sort.Slice(piecesRemove, func(i, j int) bool {
|
||||
return piecesRemove[i].Accessed < piecesRemove[j].Accessed
|
||||
})
|
||||
|
||||
c.filled = fill
|
||||
return piecesRemove
|
||||
}
|
||||
|
||||
func (c *Cache) setLoadPriority(ranges []Range) {
|
||||
c.muReaders.Lock()
|
||||
for r := range c.readers {
|
||||
if !r.isUse {
|
||||
continue
|
||||
}
|
||||
if c.isIdInFileBE(ranges, r.getReaderPiece()) {
|
||||
continue
|
||||
}
|
||||
readerPos := r.getReaderPiece()
|
||||
readerRAHPos := r.getReaderRAHPiece()
|
||||
end := r.getPiecesRange().End
|
||||
count := settings.BTsets.ConnectionsLimit / len(c.readers) // max concurrent loading blocks
|
||||
limit := 0
|
||||
for i := readerPos; i < end && limit < count; i++ {
|
||||
if !c.pieces[i].Complete {
|
||||
if i == readerPos {
|
||||
c.torrent.Piece(i).SetPriority(torrent.PiecePriorityNow)
|
||||
} else if i == readerPos+1 {
|
||||
c.torrent.Piece(i).SetPriority(torrent.PiecePriorityNext)
|
||||
} else if i > readerPos && i <= readerRAHPos {
|
||||
c.torrent.Piece(i).SetPriority(torrent.PiecePriorityReadahead)
|
||||
} else if i > readerRAHPos && i <= readerRAHPos+5 && c.torrent.PieceState(i).Priority != torrent.PiecePriorityHigh {
|
||||
c.torrent.Piece(i).SetPriority(torrent.PiecePriorityHigh)
|
||||
} else if i > readerRAHPos+5 && c.torrent.PieceState(i).Priority != torrent.PiecePriorityNormal {
|
||||
c.torrent.Piece(i).SetPriority(torrent.PiecePriorityNormal)
|
||||
}
|
||||
limit++
|
||||
}
|
||||
}
|
||||
}
|
||||
c.muReaders.Unlock()
|
||||
}
|
||||
|
||||
func (c *Cache) isIdInFileBE(ranges []Range, id int) bool {
|
||||
// keep 8/16 MB
|
||||
FileRangeNotDelete := int64(c.pieceLength)
|
||||
if FileRangeNotDelete < 8<<20 {
|
||||
FileRangeNotDelete = 8 << 20
|
||||
}
|
||||
|
||||
for _, rng := range ranges {
|
||||
ss := int(rng.File.Offset() / c.pieceLength)
|
||||
se := int((rng.File.Offset() + FileRangeNotDelete) / c.pieceLength)
|
||||
|
||||
es := int((rng.File.Offset() + rng.File.Length() - FileRangeNotDelete) / c.pieceLength)
|
||||
ee := int((rng.File.Offset() + rng.File.Length()) / c.pieceLength)
|
||||
|
||||
if id >= ss && id < se || id > es && id <= ee {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
//////////////////
|
||||
// Reader section
|
||||
////////
|
||||
|
||||
func (c *Cache) NewReader(file *torrent.File) *Reader {
|
||||
return newReader(file, c)
|
||||
}
|
||||
|
||||
func (c *Cache) GetUseReaders() int {
|
||||
if c == nil {
|
||||
return 0
|
||||
}
|
||||
c.muReaders.Lock()
|
||||
defer c.muReaders.Unlock()
|
||||
readers := 0
|
||||
for reader := range c.readers {
|
||||
if reader.isUse {
|
||||
readers++
|
||||
}
|
||||
}
|
||||
return readers
|
||||
}
|
||||
|
||||
func (c *Cache) Readers() int {
|
||||
if c == nil {
|
||||
return 0
|
||||
}
|
||||
c.muReaders.Lock()
|
||||
defer c.muReaders.Unlock()
|
||||
if c.readers == nil {
|
||||
return 0
|
||||
}
|
||||
return len(c.readers)
|
||||
}
|
||||
|
||||
func (c *Cache) CloseReader(r *Reader) {
|
||||
r.cache.muReaders.Lock()
|
||||
r.Close()
|
||||
delete(r.cache.readers, r)
|
||||
r.cache.muReaders.Unlock()
|
||||
go c.clearPriority()
|
||||
}
|
||||
|
||||
func (c *Cache) clearPriority() {
|
||||
time.Sleep(time.Second)
|
||||
ranges := make([]Range, 0)
|
||||
c.muReaders.Lock()
|
||||
for r := range c.readers {
|
||||
r.checkReader()
|
||||
if r.isUse {
|
||||
ranges = append(ranges, r.getPiecesRange())
|
||||
}
|
||||
}
|
||||
c.muReaders.Unlock()
|
||||
ranges = mergeRange(ranges)
|
||||
|
||||
for id := range c.pieces {
|
||||
if len(ranges) > 0 {
|
||||
if !inRanges(ranges, id) {
|
||||
if c.torrent.PieceState(id).Priority != torrent.PiecePriorityNone {
|
||||
c.torrent.Piece(id).SetPriority(torrent.PiecePriorityNone)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if c.torrent.PieceState(id).Priority != torrent.PiecePriorityNone {
|
||||
c.torrent.Piece(id).SetPriority(torrent.PiecePriorityNone)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Cache) GetCapacity() int64 {
|
||||
if c == nil {
|
||||
return 0
|
||||
}
|
||||
return c.capacity
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
package torrstor
|
||||
|
||||
import (
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"server/log"
|
||||
"server/settings"
|
||||
)
|
||||
|
||||
type DiskPiece struct {
|
||||
piece *Piece
|
||||
|
||||
name string
|
||||
|
||||
mu sync.RWMutex
|
||||
}
|
||||
|
||||
func NewDiskPiece(p *Piece) *DiskPiece {
|
||||
name := filepath.Join(settings.BTsets.TorrentsSavePath, p.cache.hash.HexString(), strconv.Itoa(p.Id))
|
||||
ff, err := os.Stat(name)
|
||||
if err == nil {
|
||||
p.Size = ff.Size()
|
||||
p.Complete = ff.Size() == p.cache.pieceLength
|
||||
p.Accessed = ff.ModTime().Unix()
|
||||
}
|
||||
return &DiskPiece{piece: p, name: name}
|
||||
}
|
||||
|
||||
func (p *DiskPiece) WriteAt(b []byte, off int64) (n int, err error) {
|
||||
p.mu.Lock()
|
||||
defer p.mu.Unlock()
|
||||
|
||||
ff, err := os.OpenFile(p.name, os.O_RDWR|os.O_CREATE, 0o666)
|
||||
if err != nil {
|
||||
log.TLogln("Error open file:", err)
|
||||
return 0, err
|
||||
}
|
||||
defer ff.Close()
|
||||
n, err = ff.WriteAt(b, off)
|
||||
|
||||
p.piece.Size += int64(n)
|
||||
if p.piece.Size > p.piece.cache.pieceLength {
|
||||
p.piece.Size = p.piece.cache.pieceLength
|
||||
}
|
||||
p.piece.Accessed = time.Now().Unix()
|
||||
return
|
||||
}
|
||||
|
||||
func (p *DiskPiece) ReadAt(b []byte, off int64) (n int, err error) {
|
||||
p.mu.Lock()
|
||||
defer p.mu.Unlock()
|
||||
|
||||
ff, err := os.OpenFile(p.name, os.O_RDONLY, 0o666)
|
||||
if os.IsNotExist(err) {
|
||||
return 0, io.EOF
|
||||
}
|
||||
if err != nil {
|
||||
log.TLogln("Error open file:", err)
|
||||
return 0, err
|
||||
}
|
||||
defer ff.Close()
|
||||
|
||||
n, err = ff.ReadAt(b, off)
|
||||
|
||||
p.piece.Accessed = time.Now().Unix()
|
||||
if int64(len(b))+off >= p.piece.Size {
|
||||
go p.piece.cache.cleanPieces()
|
||||
}
|
||||
return n, nil
|
||||
}
|
||||
|
||||
func (p *DiskPiece) Release() {
|
||||
p.mu.Lock()
|
||||
defer p.mu.Unlock()
|
||||
|
||||
p.piece.Size = 0
|
||||
p.piece.Complete = false
|
||||
|
||||
os.Remove(p.name)
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
package torrstor
|
||||
|
||||
import (
|
||||
"io"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
type MemPiece struct {
|
||||
piece *Piece
|
||||
|
||||
buffer []byte
|
||||
mu sync.RWMutex
|
||||
}
|
||||
|
||||
func NewMemPiece(p *Piece) *MemPiece {
|
||||
return &MemPiece{piece: p}
|
||||
}
|
||||
|
||||
func (p *MemPiece) WriteAt(b []byte, off int64) (n int, err error) {
|
||||
p.mu.Lock()
|
||||
defer p.mu.Unlock()
|
||||
|
||||
if p.buffer == nil {
|
||||
go p.piece.cache.cleanPieces()
|
||||
p.buffer = make([]byte, p.piece.cache.pieceLength, p.piece.cache.pieceLength)
|
||||
}
|
||||
n = copy(p.buffer[off:], b[:])
|
||||
p.piece.Size += int64(n)
|
||||
if p.piece.Size > p.piece.cache.pieceLength {
|
||||
p.piece.Size = p.piece.cache.pieceLength
|
||||
}
|
||||
p.piece.Accessed = time.Now().Unix()
|
||||
return
|
||||
}
|
||||
|
||||
func (p *MemPiece) ReadAt(b []byte, off int64) (n int, err error) {
|
||||
p.mu.RLock()
|
||||
defer p.mu.RUnlock()
|
||||
|
||||
size := len(b)
|
||||
if size+int(off) > len(p.buffer) {
|
||||
size = len(p.buffer) - int(off)
|
||||
if size < 0 {
|
||||
size = 0
|
||||
}
|
||||
}
|
||||
if len(p.buffer) < int(off) || len(p.buffer) < int(off)+size {
|
||||
return 0, io.EOF
|
||||
}
|
||||
n = copy(b, p.buffer[int(off) : int(off)+size][:])
|
||||
p.piece.Accessed = time.Now().Unix()
|
||||
if int64(len(b))+off >= p.piece.Size {
|
||||
go p.piece.cache.cleanPieces()
|
||||
}
|
||||
if n == 0 {
|
||||
return 0, io.EOF
|
||||
}
|
||||
return n, nil
|
||||
}
|
||||
|
||||
func (p *MemPiece) Release() {
|
||||
p.mu.Lock()
|
||||
defer p.mu.Unlock()
|
||||
if p.buffer != nil {
|
||||
p.buffer = nil
|
||||
}
|
||||
p.piece.Size = 0
|
||||
p.piece.Complete = false
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
package torrstor
|
||||
|
||||
import (
|
||||
"github.com/anacrolix/torrent"
|
||||
"github.com/anacrolix/torrent/storage"
|
||||
"server/settings"
|
||||
)
|
||||
|
||||
type Piece struct {
|
||||
storage.PieceImpl `json:"-"`
|
||||
|
||||
Id int `json:"-"`
|
||||
Size int64 `json:"size"`
|
||||
|
||||
Complete bool `json:"complete"`
|
||||
Accessed int64 `json:"accessed"`
|
||||
|
||||
mPiece *MemPiece `json:"-"`
|
||||
dPiece *DiskPiece `json:"-"`
|
||||
|
||||
cache *Cache `json:"-"`
|
||||
}
|
||||
|
||||
func NewPiece(id int, cache *Cache) *Piece {
|
||||
p := &Piece{
|
||||
Id: id,
|
||||
cache: cache,
|
||||
}
|
||||
|
||||
if !settings.BTsets.UseDisk {
|
||||
p.mPiece = NewMemPiece(p)
|
||||
} else {
|
||||
p.dPiece = NewDiskPiece(p)
|
||||
}
|
||||
return p
|
||||
}
|
||||
|
||||
func (p *Piece) WriteAt(b []byte, off int64) (n int, err error) {
|
||||
if !settings.BTsets.UseDisk {
|
||||
return p.mPiece.WriteAt(b, off)
|
||||
} else {
|
||||
return p.dPiece.WriteAt(b, off)
|
||||
}
|
||||
}
|
||||
|
||||
func (p *Piece) ReadAt(b []byte, off int64) (n int, err error) {
|
||||
if !settings.BTsets.UseDisk {
|
||||
return p.mPiece.ReadAt(b, off)
|
||||
} else {
|
||||
return p.dPiece.ReadAt(b, off)
|
||||
}
|
||||
}
|
||||
|
||||
func (p *Piece) MarkComplete() error {
|
||||
p.Complete = true
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *Piece) MarkNotComplete() error {
|
||||
p.Complete = false
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *Piece) Completion() storage.Completion {
|
||||
return storage.Completion{
|
||||
Complete: p.Complete,
|
||||
Ok: true,
|
||||
}
|
||||
}
|
||||
|
||||
func (p *Piece) Release() {
|
||||
if !settings.BTsets.UseDisk {
|
||||
p.mPiece.Release()
|
||||
} else {
|
||||
p.dPiece.Release()
|
||||
}
|
||||
if !p.cache.isClosed {
|
||||
p.cache.torrent.Piece(p.Id).SetPriority(torrent.PiecePriorityNone)
|
||||
p.cache.torrent.Piece(p.Id).UpdateCompletion()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
package torrstor
|
||||
|
||||
import (
|
||||
"errors"
|
||||
|
||||
"github.com/anacrolix/torrent/storage"
|
||||
)
|
||||
|
||||
type PieceFake struct{}
|
||||
|
||||
func (PieceFake) ReadAt(p []byte, off int64) (n int, err error) {
|
||||
err = errors.New("fake")
|
||||
return
|
||||
}
|
||||
|
||||
func (PieceFake) WriteAt(p []byte, off int64) (n int, err error) {
|
||||
err = errors.New("fake")
|
||||
return
|
||||
}
|
||||
|
||||
func (PieceFake) MarkComplete() error {
|
||||
return errors.New("fake")
|
||||
}
|
||||
|
||||
func (PieceFake) MarkNotComplete() error {
|
||||
return errors.New("fake")
|
||||
}
|
||||
|
||||
func (PieceFake) Completion() storage.Completion {
|
||||
return storage.Completion{
|
||||
Complete: false,
|
||||
Ok: true,
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
package torrstor
|
||||
|
||||
import (
|
||||
"sort"
|
||||
|
||||
"github.com/anacrolix/torrent"
|
||||
)
|
||||
|
||||
type Range struct {
|
||||
Start, End int
|
||||
File *torrent.File
|
||||
}
|
||||
|
||||
func inRanges(ranges []Range, ind int) bool {
|
||||
for _, r := range ranges {
|
||||
if ind >= r.Start && ind <= r.End {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func mergeRange(ranges []Range) []Range {
|
||||
if len(ranges) <= 1 {
|
||||
return ranges
|
||||
}
|
||||
// copy ranges
|
||||
merged := append([]Range(nil), ranges...)
|
||||
|
||||
sort.Slice(merged, func(i, j int) bool {
|
||||
if merged[i].Start < merged[j].Start {
|
||||
return true
|
||||
}
|
||||
if merged[i].Start == merged[j].Start && merged[i].End < merged[j].End {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
})
|
||||
|
||||
j := 0
|
||||
for i := 1; i < len(merged); i++ {
|
||||
if merged[j].End >= merged[i].Start {
|
||||
if merged[j].End < merged[i].End {
|
||||
merged[j].End = merged[i].End
|
||||
}
|
||||
} else {
|
||||
j++
|
||||
merged[j] = merged[i]
|
||||
}
|
||||
}
|
||||
return merged[:j+1]
|
||||
}
|
||||
@@ -0,0 +1,205 @@
|
||||
package torrstor
|
||||
|
||||
import (
|
||||
"io"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/anacrolix/torrent"
|
||||
|
||||
"server/log"
|
||||
"server/settings"
|
||||
)
|
||||
|
||||
type Reader struct {
|
||||
torrent.Reader
|
||||
offset int64
|
||||
readahead int64
|
||||
file *torrent.File
|
||||
|
||||
cache *Cache
|
||||
isClosed bool
|
||||
|
||||
///Preload
|
||||
lastAccess int64
|
||||
isUse bool
|
||||
mu sync.Mutex
|
||||
}
|
||||
|
||||
func newReader(file *torrent.File, cache *Cache) *Reader {
|
||||
r := new(Reader)
|
||||
r.file = file
|
||||
r.Reader = file.NewReader()
|
||||
|
||||
r.SetReadahead(0)
|
||||
r.cache = cache
|
||||
r.isUse = true
|
||||
|
||||
cache.muReaders.Lock()
|
||||
cache.readers[r] = struct{}{}
|
||||
cache.muReaders.Unlock()
|
||||
return r
|
||||
}
|
||||
|
||||
func (r *Reader) Seek(offset int64, whence int) (n int64, err error) {
|
||||
if r.isClosed {
|
||||
return 0, io.EOF
|
||||
}
|
||||
switch whence {
|
||||
case io.SeekStart:
|
||||
r.offset = offset
|
||||
case io.SeekCurrent:
|
||||
r.offset += offset
|
||||
case io.SeekEnd:
|
||||
r.offset = r.file.Length() + offset
|
||||
}
|
||||
r.readerOn()
|
||||
n, err = r.Reader.Seek(offset, whence)
|
||||
r.offset = n
|
||||
r.lastAccess = time.Now().Unix()
|
||||
return
|
||||
}
|
||||
|
||||
func (r *Reader) Read(p []byte) (n int, err error) {
|
||||
err = io.EOF
|
||||
if r.isClosed {
|
||||
return
|
||||
}
|
||||
if r.file.Torrent() != nil && r.file.Torrent().Info() != nil {
|
||||
r.readerOn()
|
||||
n, err = r.Reader.Read(p)
|
||||
|
||||
// samsung tv fix xvid/divx
|
||||
//if r.offset == 0 && len(p) >= 192 {
|
||||
// str := strings.ToLower(string(p[112:116]))
|
||||
// if str == "xvid" || str == "divx" {
|
||||
// p[112] = 0x4D // M
|
||||
// p[113] = 0x50 // P
|
||||
// p[114] = 0x34 // 4
|
||||
// p[115] = 0x56 // V
|
||||
// }
|
||||
// str = strings.ToLower(string(p[188:192]))
|
||||
// if str == "xvid" || str == "divx" {
|
||||
// p[188] = 0x4D // M
|
||||
// p[189] = 0x50 // P
|
||||
// p[190] = 0x34 // 4
|
||||
// p[191] = 0x56 // V
|
||||
// }
|
||||
//}
|
||||
|
||||
r.offset += int64(n)
|
||||
r.lastAccess = time.Now().Unix()
|
||||
} else {
|
||||
log.TLogln("Torrent closed and readed")
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (r *Reader) SetReadahead(length int64) {
|
||||
if r.cache != nil && length > r.cache.capacity {
|
||||
length = r.cache.capacity
|
||||
}
|
||||
if r.isUse {
|
||||
r.Reader.SetReadahead(length)
|
||||
}
|
||||
r.readahead = length
|
||||
}
|
||||
|
||||
func (r *Reader) Offset() int64 {
|
||||
return r.offset
|
||||
}
|
||||
|
||||
func (r *Reader) Readahead() int64 {
|
||||
return r.readahead
|
||||
}
|
||||
|
||||
func (r *Reader) Close() {
|
||||
// file reader close in gotorrent
|
||||
// this struct close in cache
|
||||
r.isClosed = true
|
||||
if len(r.file.Torrent().Files()) > 0 {
|
||||
r.Reader.Close()
|
||||
}
|
||||
go r.cache.getRemPieces()
|
||||
}
|
||||
|
||||
func (r *Reader) getPiecesRange() Range {
|
||||
startOff, endOff := r.getOffsetRange()
|
||||
return Range{r.getPieceNum(startOff), r.getPieceNum(endOff), r.file}
|
||||
}
|
||||
|
||||
func (r *Reader) getReaderPiece() int {
|
||||
return r.getPieceNum(r.offset)
|
||||
}
|
||||
|
||||
func (r *Reader) getReaderRAHPiece() int {
|
||||
return r.getPieceNum(r.offset + r.readahead)
|
||||
}
|
||||
|
||||
func (r *Reader) getPieceNum(offset int64) int {
|
||||
return int((offset + r.file.Offset()) / r.cache.pieceLength)
|
||||
}
|
||||
|
||||
func (r *Reader) getOffsetRange() (int64, int64) {
|
||||
prc := int64(settings.BTsets.ReaderReadAHead)
|
||||
readers := int64(r.getUseReaders())
|
||||
if readers == 0 {
|
||||
readers = 1
|
||||
}
|
||||
|
||||
beginOffset := r.offset - (r.cache.capacity/readers)*(100-prc)/100
|
||||
endOffset := r.offset + (r.cache.capacity/readers)*prc/100
|
||||
|
||||
if beginOffset < 0 {
|
||||
beginOffset = 0
|
||||
}
|
||||
|
||||
if endOffset > r.file.Length() {
|
||||
endOffset = r.file.Length()
|
||||
}
|
||||
return beginOffset, endOffset
|
||||
}
|
||||
|
||||
func (r *Reader) checkReader() {
|
||||
if time.Now().Unix() > r.lastAccess+60 && len(r.cache.readers) > 1 {
|
||||
r.readerOff()
|
||||
} else {
|
||||
r.readerOn()
|
||||
}
|
||||
}
|
||||
|
||||
func (r *Reader) readerOn() {
|
||||
r.mu.Lock()
|
||||
defer r.mu.Unlock()
|
||||
if !r.isUse {
|
||||
if pos, err := r.Reader.Seek(0, io.SeekCurrent); err == nil && pos == 0 {
|
||||
r.Reader.Seek(r.offset, io.SeekStart)
|
||||
}
|
||||
r.SetReadahead(r.readahead)
|
||||
r.isUse = true
|
||||
}
|
||||
}
|
||||
|
||||
func (r *Reader) readerOff() {
|
||||
r.mu.Lock()
|
||||
defer r.mu.Unlock()
|
||||
if r.isUse {
|
||||
r.SetReadahead(0)
|
||||
r.isUse = false
|
||||
if r.offset > 0 {
|
||||
r.Reader.Seek(0, io.SeekStart)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (r *Reader) getUseReaders() int {
|
||||
readers := 0
|
||||
if r.cache != nil {
|
||||
for reader := range r.cache.readers {
|
||||
if reader.isUse {
|
||||
readers++
|
||||
}
|
||||
}
|
||||
}
|
||||
return readers
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
package torrstor
|
||||
|
||||
import (
|
||||
"sync"
|
||||
|
||||
"server/torr/storage"
|
||||
|
||||
"github.com/anacrolix/torrent/metainfo"
|
||||
ts "github.com/anacrolix/torrent/storage"
|
||||
)
|
||||
|
||||
type Storage struct {
|
||||
storage.Storage
|
||||
|
||||
caches map[metainfo.Hash]*Cache
|
||||
capacity int64
|
||||
mu sync.Mutex
|
||||
}
|
||||
|
||||
func NewStorage(capacity int64) *Storage {
|
||||
stor := new(Storage)
|
||||
stor.capacity = capacity
|
||||
stor.caches = make(map[metainfo.Hash]*Cache)
|
||||
return stor
|
||||
}
|
||||
|
||||
func (s *Storage) OpenTorrent(info *metainfo.Info, infoHash metainfo.Hash) (ts.TorrentImpl, error) {
|
||||
// capFunc := func() (int64, bool) { // NE
|
||||
// return s.capacity, true // NE
|
||||
// } // NE
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
ch := NewCache(s.capacity, s)
|
||||
ch.Init(info, infoHash)
|
||||
s.caches[infoHash] = ch
|
||||
return ch, nil // OE
|
||||
// return ts.TorrentImpl{ // NE
|
||||
// Piece: ch.Piece, // NE
|
||||
// Close: ch.Close, // NE
|
||||
// Capacity: &capFunc, // NE
|
||||
// }, nil // NE
|
||||
}
|
||||
|
||||
func (s *Storage) CloseHash(hash metainfo.Hash) {
|
||||
if s.caches == nil {
|
||||
return
|
||||
}
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
if ch, ok := s.caches[hash]; ok {
|
||||
ch.Close()
|
||||
delete(s.caches, hash)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Storage) Close() error {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
for _, ch := range s.caches {
|
||||
ch.Close()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Storage) GetCache(hash metainfo.Hash) *Cache {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
if cache, ok := s.caches[hash]; ok {
|
||||
return cache
|
||||
}
|
||||
return nil
|
||||
}
|
||||
Reference in New Issue
Block a user