2024-06-05 11:53:09 +00:00
//go:build !disable_torrent
// +build !disable_torrent
2024-06-04 21:55:36 +00:00
2024-06-06 09:09:58 +00:00
// Attribution to Pascal Precht, for further context please view the below issues
// - https://github.com/status-im/status-go/issues/2563
// - https://github.com/status-im/status-go/issues/2565
// - https://github.com/status-im/status-go/issues/2567
// - https://github.com/status-im/status-go/issues/2568
2024-05-29 15:09:00 +00:00
package communities
import (
"crypto/ecdsa"
2024-05-31 14:20:36 +00:00
"errors"
2024-05-29 15:09:00 +00:00
"fmt"
"net"
"os"
"path"
"sort"
"sync"
"time"
2024-09-26 22:37:32 +00:00
"github.com/status-im/status-go/common"
2024-05-29 15:09:00 +00:00
"github.com/status-im/status-go/eth-node/types"
"github.com/status-im/status-go/params"
"github.com/status-im/status-go/protocol/encryption"
"github.com/status-im/status-go/protocol/transport"
"github.com/status-im/status-go/signal"
2024-06-03 23:15:00 +00:00
"github.com/anacrolix/torrent"
"github.com/anacrolix/torrent/metainfo"
"go.uber.org/zap"
2024-05-29 15:09:00 +00:00
)
2024-05-29 19:25:47 +00:00
type archiveMDSlice [ ] * archiveMetadata
type archiveMetadata struct {
hash string
from uint64
}
func ( md archiveMDSlice ) Len ( ) int {
return len ( md )
}
func ( md archiveMDSlice ) Swap ( i , j int ) {
md [ i ] , md [ j ] = md [ j ] , md [ i ]
}
func ( md archiveMDSlice ) Less ( i , j int ) bool {
return md [ i ] . from > md [ j ] . from
}
type EncodedArchiveData struct {
padding int
bytes [ ] byte
}
2024-06-06 14:59:46 +00:00
type ArchiveManager struct {
2024-05-29 15:09:00 +00:00
torrentConfig * params . TorrentConfig
torrentClient * torrent . Client
torrentTasks map [ string ] metainfo . Hash
historyArchiveDownloadTasks map [ string ] * HistoryArchiveDownloadTask
historyArchiveTasksWaitGroup sync . WaitGroup
historyArchiveTasks sync . Map // stores `chan struct{}`
2024-06-06 13:59:27 +00:00
logger * zap . Logger
2024-05-30 15:04:47 +00:00
persistence * Persistence
transport * transport . Transport
identity * ecdsa . PrivateKey
encryptor * encryption . Protocol
2024-06-06 14:59:46 +00:00
* ArchiveFileManager
2024-05-30 15:04:47 +00:00
publisher Publisher
2024-05-29 15:09:00 +00:00
}
2024-06-06 14:59:46 +00:00
// NewArchiveManager this function is only built and called when the "disable_torrent" build tag is not set
// In this case this version of NewArchiveManager will return the full Desktop ArchiveManager ensuring that the
2024-06-04 21:55:36 +00:00
// build command will import and build the torrent deps for the Desktop OSes.
2024-06-06 15:37:20 +00:00
// NOTE: It is intentional that this file contains the identical function name as in "manager_archive_nop.go"
2024-06-07 09:27:32 +00:00
func NewArchiveManager ( amc * ArchiveManagerConfig ) * ArchiveManager {
2024-06-06 14:59:46 +00:00
return & ArchiveManager {
2024-06-07 09:27:32 +00:00
torrentConfig : amc . TorrentConfig ,
2024-05-29 15:09:00 +00:00
torrentTasks : make ( map [ string ] metainfo . Hash ) ,
historyArchiveDownloadTasks : make ( map [ string ] * HistoryArchiveDownloadTask ) ,
2024-05-30 15:04:47 +00:00
2024-06-07 09:27:32 +00:00
logger : amc . Logger ,
persistence : amc . Persistence ,
transport : amc . Transport ,
identity : amc . Identity ,
encryptor : amc . Encryptor ,
2024-05-30 15:04:47 +00:00
2024-06-07 09:27:32 +00:00
publisher : amc . Publisher ,
ArchiveFileManager : NewArchiveFileManager ( amc ) ,
2024-06-06 13:59:27 +00:00
}
2024-05-29 15:09:00 +00:00
}
2024-06-06 14:59:46 +00:00
func ( m * ArchiveManager ) SetOnline ( online bool ) {
2024-05-29 19:25:47 +00:00
if online {
2024-05-31 19:58:33 +00:00
if m . torrentConfig != nil && m . torrentConfig . Enabled && ! m . torrentClientStarted ( ) {
2024-05-29 19:25:47 +00:00
err := m . StartTorrentClient ( )
if err != nil {
2024-06-06 13:59:27 +00:00
m . logger . Error ( "couldn't start torrent client" , zap . Error ( err ) )
2024-05-29 19:25:47 +00:00
}
}
}
}
2024-06-06 14:59:46 +00:00
func ( m * ArchiveManager ) SetTorrentConfig ( config * params . TorrentConfig ) {
2024-05-29 19:25:47 +00:00
m . torrentConfig = config
2024-06-06 20:28:52 +00:00
m . ArchiveFileManager . torrentConfig = config
2024-05-29 19:25:47 +00:00
}
2024-05-29 15:09:00 +00:00
// getTCPandUDPport will return the same port number given if != 0,
// otherwise, it will attempt to find a free random tcp and udp port using
// the same number for both protocols
2024-06-06 14:59:46 +00:00
func ( m * ArchiveManager ) getTCPandUDPport ( portNumber int ) ( int , error ) {
2024-05-29 15:09:00 +00:00
if portNumber != 0 {
return portNumber , nil
}
// Find free port
for i := 0 ; i < 10 ; i ++ {
port := func ( ) int {
tcpAddr , err := net . ResolveTCPAddr ( "tcp" , net . JoinHostPort ( "localhost" , "0" ) )
if err != nil {
m . logger . Warn ( "unable to resolve tcp addr: %v" , zap . Error ( err ) )
return 0
}
tcpListener , err := net . ListenTCP ( "tcp" , tcpAddr )
if err != nil {
m . logger . Warn ( "unable to listen on addr" , zap . Stringer ( "addr" , tcpAddr ) , zap . Error ( err ) )
return 0
}
defer tcpListener . Close ( )
port := tcpListener . Addr ( ) . ( * net . TCPAddr ) . Port
udpAddr , err := net . ResolveUDPAddr ( "udp" , net . JoinHostPort ( "localhost" , fmt . Sprintf ( "%d" , port ) ) )
if err != nil {
m . logger . Warn ( "unable to resolve udp addr: %v" , zap . Error ( err ) )
return 0
}
udpListener , err := net . ListenUDP ( "udp" , udpAddr )
if err != nil {
m . logger . Warn ( "unable to listen on addr" , zap . Stringer ( "addr" , udpAddr ) , zap . Error ( err ) )
return 0
}
defer udpListener . Close ( )
return port
} ( )
if port != 0 {
return port , nil
}
}
return 0 , fmt . Errorf ( "no free port found" )
}
2024-06-06 14:59:46 +00:00
func ( m * ArchiveManager ) StartTorrentClient ( ) error {
2024-05-29 15:09:00 +00:00
if m . torrentConfig == nil {
return fmt . Errorf ( "can't start torrent client: missing torrentConfig" )
}
2024-05-31 19:58:33 +00:00
if m . torrentClientStarted ( ) {
2024-05-29 15:09:00 +00:00
return nil
}
port , err := m . getTCPandUDPport ( m . torrentConfig . Port )
if err != nil {
return err
}
config := torrent . NewDefaultClientConfig ( )
config . SetListenAddr ( ":" + fmt . Sprint ( port ) )
config . Seed = true
config . DataDir = m . torrentConfig . DataDir
if _ , err := os . Stat ( m . torrentConfig . DataDir ) ; os . IsNotExist ( err ) {
err := os . MkdirAll ( m . torrentConfig . DataDir , 0700 )
if err != nil {
return err
}
}
m . logger . Info ( "Starting torrent client" , zap . Any ( "port" , port ) )
// Instantiating the client will make it bootstrap and listen eagerly,
// so no go routine is needed here
client , err := torrent . NewClient ( config )
if err != nil {
return err
}
m . torrentClient = client
return nil
}
2024-06-06 14:59:46 +00:00
func ( m * ArchiveManager ) Stop ( ) error {
2024-05-31 19:58:33 +00:00
if m . torrentClientStarted ( ) {
m . stopHistoryArchiveTasksIntervals ( )
2024-05-29 15:09:00 +00:00
m . logger . Info ( "Stopping torrent client" )
errs := m . torrentClient . Close ( )
if len ( errs ) > 0 {
2024-05-31 14:20:36 +00:00
return errors . Join ( errs ... )
2024-05-29 15:09:00 +00:00
}
m . torrentClient = nil
}
2024-05-31 14:20:36 +00:00
return nil
2024-05-29 15:09:00 +00:00
}
2024-06-06 14:59:46 +00:00
func ( m * ArchiveManager ) torrentClientStarted ( ) bool {
2024-05-29 15:09:00 +00:00
return m . torrentClient != nil
}
2024-06-06 14:59:46 +00:00
func ( m * ArchiveManager ) IsReady ( ) bool {
2024-05-31 14:43:44 +00:00
// Simply checking for `torrentConfig.Enabled` isn't enough
// as there's a possibility that the torrent client couldn't
// be instantiated (for example in case of port conflicts)
return m . torrentConfig != nil &&
m . torrentConfig . Enabled &&
2024-05-31 19:58:33 +00:00
m . torrentClientStarted ( )
2024-05-31 14:43:44 +00:00
}
2024-06-06 14:59:46 +00:00
func ( m * ArchiveManager ) GetCommunityChatsFilters ( communityID types . HexBytes ) ( [ ] * transport . Filter , error ) {
2024-05-29 19:25:47 +00:00
chatIDs , err := m . persistence . GetCommunityChatIDs ( communityID )
if err != nil {
return nil , err
}
filters := [ ] * transport . Filter { }
for _ , cid := range chatIDs {
filters = append ( filters , m . transport . FilterByChatID ( cid ) )
}
return filters , nil
}
2024-06-06 14:59:46 +00:00
func ( m * ArchiveManager ) GetCommunityChatsTopics ( communityID types . HexBytes ) ( [ ] types . TopicType , error ) {
2024-05-29 19:25:47 +00:00
filters , err := m . GetCommunityChatsFilters ( communityID )
if err != nil {
return nil , err
}
topics := [ ] types . TopicType { }
for _ , filter := range filters {
topics = append ( topics , filter . ContentTopic )
}
return topics , nil
}
2024-06-06 14:59:46 +00:00
func ( m * ArchiveManager ) getOldestWakuMessageTimestamp ( topics [ ] types . TopicType ) ( uint64 , error ) {
2024-05-29 19:25:47 +00:00
return m . persistence . GetOldestWakuMessageTimestamp ( topics )
}
2024-06-06 14:59:46 +00:00
func ( m * ArchiveManager ) getLastMessageArchiveEndDate ( communityID types . HexBytes ) ( uint64 , error ) {
2024-05-29 19:25:47 +00:00
return m . persistence . GetLastMessageArchiveEndDate ( communityID )
}
2024-06-06 14:59:46 +00:00
func ( m * ArchiveManager ) GetHistoryArchivePartitionStartTimestamp ( communityID types . HexBytes ) ( uint64 , error ) {
2024-05-29 19:25:47 +00:00
filters , err := m . GetCommunityChatsFilters ( communityID )
if err != nil {
2024-06-06 13:59:27 +00:00
m . logger . Error ( "failed to get community chats filters" , zap . Error ( err ) )
2024-05-29 19:25:47 +00:00
return 0 , err
}
if len ( filters ) == 0 {
// If we don't have chat filters, we likely don't have any chats
// associated to this community, which means there's nothing more
// to do here
return 0 , nil
}
topics := [ ] types . TopicType { }
for _ , filter := range filters {
topics = append ( topics , filter . ContentTopic )
}
2024-05-31 19:58:33 +00:00
lastArchiveEndDateTimestamp , err := m . getLastMessageArchiveEndDate ( communityID )
2024-05-29 19:25:47 +00:00
if err != nil {
2024-06-06 13:59:27 +00:00
m . logger . Error ( "failed to get last archive end date" , zap . Error ( err ) )
2024-05-29 19:25:47 +00:00
return 0 , err
}
if lastArchiveEndDateTimestamp == 0 {
// If we don't have a tracked last message archive end date, it
// means we haven't created an archive before, which means
// the next thing to look at is the oldest waku message timestamp for
// this community
2024-05-31 19:58:33 +00:00
lastArchiveEndDateTimestamp , err = m . getOldestWakuMessageTimestamp ( topics )
2024-05-29 19:25:47 +00:00
if err != nil {
2024-06-06 13:59:27 +00:00
m . logger . Error ( "failed to get oldest waku message timestamp" , zap . Error ( err ) )
2024-05-29 19:25:47 +00:00
return 0 , err
}
if lastArchiveEndDateTimestamp == 0 {
// This means there's no waku message stored for this community so far
// (even after requesting possibly missed messages), so no messages exist yet that can be archived
2024-06-06 13:59:27 +00:00
m . logger . Debug ( "can't find valid `lastArchiveEndTimestamp`" )
2024-05-29 19:25:47 +00:00
return 0 , nil
}
}
return lastArchiveEndDateTimestamp , nil
}
2024-06-06 14:59:46 +00:00
func ( m * ArchiveManager ) CreateAndSeedHistoryArchive ( communityID types . HexBytes , topics [ ] types . TopicType , startDate time . Time , endDate time . Time , partition time . Duration , encrypt bool ) error {
2024-05-29 19:25:47 +00:00
m . UnseedHistoryArchiveTorrent ( communityID )
2024-06-06 14:59:46 +00:00
_ , err := m . ArchiveFileManager . CreateHistoryArchiveTorrentFromDB ( communityID , topics , startDate , endDate , partition , encrypt )
2024-05-29 19:25:47 +00:00
if err != nil {
return err
}
return m . SeedHistoryArchiveTorrent ( communityID )
}
2024-06-06 14:59:46 +00:00
func ( m * ArchiveManager ) StartHistoryArchiveTasksInterval ( community * Community , interval time . Duration ) {
2024-09-26 22:37:32 +00:00
defer common . LogOnPanic ( )
2024-05-29 19:25:47 +00:00
id := community . IDString ( )
if _ , exists := m . historyArchiveTasks . Load ( id ) ; exists {
2024-06-06 13:59:27 +00:00
m . logger . Error ( "history archive tasks interval already in progress" , zap . String ( "id" , id ) )
2024-05-29 19:25:47 +00:00
return
}
cancel := make ( chan struct { } )
m . historyArchiveTasks . Store ( id , cancel )
m . historyArchiveTasksWaitGroup . Add ( 1 )
ticker := time . NewTicker ( interval )
defer ticker . Stop ( )
2024-06-06 13:59:27 +00:00
m . logger . Debug ( "starting history archive tasks interval" , zap . String ( "id" , id ) )
2024-05-29 19:25:47 +00:00
for {
select {
case <- ticker . C :
2024-06-06 13:59:27 +00:00
m . logger . Debug ( "starting archive task..." , zap . String ( "id" , id ) )
2024-05-29 19:25:47 +00:00
lastArchiveEndDateTimestamp , err := m . GetHistoryArchivePartitionStartTimestamp ( community . ID ( ) )
if err != nil {
2024-06-06 13:59:27 +00:00
m . logger . Error ( "failed to get last archive end date" , zap . Error ( err ) )
2024-05-29 19:25:47 +00:00
continue
}
if lastArchiveEndDateTimestamp == 0 {
// This means there are no waku messages for this community,
// so nothing to do here
2024-06-06 13:59:27 +00:00
m . logger . Debug ( "couldn't determine archive start date - skipping" )
2024-05-29 19:25:47 +00:00
continue
}
topics , err := m . GetCommunityChatsTopics ( community . ID ( ) )
if err != nil {
2024-06-06 13:59:27 +00:00
m . logger . Error ( "failed to get community chat topics " , zap . Error ( err ) )
2024-05-29 19:25:47 +00:00
continue
}
ts := time . Now ( ) . Unix ( )
to := time . Unix ( ts , 0 )
lastArchiveEndDate := time . Unix ( int64 ( lastArchiveEndDateTimestamp ) , 0 )
err = m . CreateAndSeedHistoryArchive ( community . ID ( ) , topics , lastArchiveEndDate , to , interval , community . Encrypted ( ) )
if err != nil {
2024-06-06 13:59:27 +00:00
m . logger . Error ( "failed to create and seed history archive" , zap . Error ( err ) )
2024-05-29 19:25:47 +00:00
continue
}
case <- cancel :
m . UnseedHistoryArchiveTorrent ( community . ID ( ) )
m . historyArchiveTasks . Delete ( id )
m . historyArchiveTasksWaitGroup . Done ( )
return
}
}
}
2024-06-06 14:59:46 +00:00
func ( m * ArchiveManager ) stopHistoryArchiveTasksIntervals ( ) {
2024-05-29 19:25:47 +00:00
m . historyArchiveTasks . Range ( func ( _ , task interface { } ) bool {
close ( task . ( chan struct { } ) ) // Need to cast to the chan
return true
} )
// Stoping archive interval tasks is async, so we need
// to wait for all of them to be closed before we shutdown
// the torrent client
m . historyArchiveTasksWaitGroup . Wait ( )
}
2024-06-06 14:59:46 +00:00
func ( m * ArchiveManager ) StopHistoryArchiveTasksInterval ( communityID types . HexBytes ) {
2024-05-29 19:25:47 +00:00
task , exists := m . historyArchiveTasks . Load ( communityID . String ( ) )
if exists {
m . logger . Info ( "Stopping history archive tasks interval" , zap . Any ( "id" , communityID . String ( ) ) )
close ( task . ( chan struct { } ) ) // Need to cast to the chan
}
}
2024-06-06 14:59:46 +00:00
func ( m * ArchiveManager ) SeedHistoryArchiveTorrent ( communityID types . HexBytes ) error {
2024-05-29 15:09:00 +00:00
m . UnseedHistoryArchiveTorrent ( communityID )
id := communityID . String ( )
2024-06-03 14:55:58 +00:00
torrentFile := torrentFile ( m . torrentConfig . TorrentDir , id )
2024-05-29 15:09:00 +00:00
metaInfo , err := metainfo . LoadFromFile ( torrentFile )
if err != nil {
return err
}
info , err := metaInfo . UnmarshalInfo ( )
if err != nil {
return err
}
hash := metaInfo . HashInfoBytes ( )
m . torrentTasks [ id ] = hash
if err != nil {
return err
}
torrent , err := m . torrentClient . AddTorrent ( metaInfo )
if err != nil {
return err
}
torrent . DownloadAll ( )
2024-05-30 15:04:47 +00:00
m . publisher . publish ( & Subscription {
2024-05-29 15:09:00 +00:00
HistoryArchivesSeedingSignal : & signal . HistoryArchivesSeedingSignal {
CommunityID : communityID . String ( ) ,
} ,
} )
magnetLink := metaInfo . Magnet ( nil , & info ) . String ( )
2024-06-06 13:59:27 +00:00
m . logger . Debug ( "seeding torrent" , zap . String ( "id" , id ) , zap . String ( "magnetLink" , magnetLink ) )
2024-05-29 15:09:00 +00:00
return nil
}
2024-06-06 14:59:46 +00:00
func ( m * ArchiveManager ) UnseedHistoryArchiveTorrent ( communityID types . HexBytes ) {
2024-05-29 15:09:00 +00:00
id := communityID . String ( )
hash , exists := m . torrentTasks [ id ]
if exists {
torrent , ok := m . torrentClient . Torrent ( hash )
if ok {
m . logger . Debug ( "Unseeding and dropping torrent for community: " , zap . Any ( "id" , id ) )
torrent . Drop ( )
delete ( m . torrentTasks , id )
2024-05-30 15:04:47 +00:00
m . publisher . publish ( & Subscription {
2024-05-29 15:09:00 +00:00
HistoryArchivesUnseededSignal : & signal . HistoryArchivesUnseededSignal {
CommunityID : id ,
} ,
} )
}
}
}
2024-06-06 14:59:46 +00:00
func ( m * ArchiveManager ) IsSeedingHistoryArchiveTorrent ( communityID types . HexBytes ) bool {
2024-05-29 15:09:00 +00:00
id := communityID . String ( )
hash := m . torrentTasks [ id ]
torrent , ok := m . torrentClient . Torrent ( hash )
return ok && torrent . Seeding ( )
}
2024-06-06 14:59:46 +00:00
func ( m * ArchiveManager ) GetHistoryArchiveDownloadTask ( communityID string ) * HistoryArchiveDownloadTask {
2024-05-29 19:25:47 +00:00
return m . historyArchiveDownloadTasks [ communityID ]
}
2024-06-06 14:59:46 +00:00
func ( m * ArchiveManager ) AddHistoryArchiveDownloadTask ( communityID string , task * HistoryArchiveDownloadTask ) {
2024-05-29 19:25:47 +00:00
m . historyArchiveDownloadTasks [ communityID ] = task
}
2024-06-06 14:59:46 +00:00
func ( m * ArchiveManager ) DownloadHistoryArchivesByMagnetlink ( communityID types . HexBytes , magnetlink string , cancelTask chan struct { } ) ( * HistoryArchiveDownloadTaskInfo , error ) {
2024-05-29 15:09:00 +00:00
id := communityID . String ( )
ml , err := metainfo . ParseMagnetUri ( magnetlink )
if err != nil {
return nil , err
}
m . logger . Debug ( "adding torrent via magnetlink for community" , zap . String ( "id" , id ) , zap . String ( "magnetlink" , magnetlink ) )
torrent , err := m . torrentClient . AddMagnet ( magnetlink )
if err != nil {
return nil , err
}
downloadTaskInfo := & HistoryArchiveDownloadTaskInfo {
TotalDownloadedArchivesCount : 0 ,
TotalArchivesCount : 0 ,
Cancelled : false ,
}
m . torrentTasks [ id ] = ml . InfoHash
timeout := time . After ( 20 * time . Second )
2024-06-06 13:59:27 +00:00
m . logger . Debug ( "fetching torrent info" , zap . String ( "magnetlink" , magnetlink ) )
2024-05-29 15:09:00 +00:00
select {
case <- timeout :
return nil , ErrTorrentTimedout
case <- cancelTask :
2024-06-06 13:59:27 +00:00
m . logger . Debug ( "cancelled fetching torrent info" )
2024-05-29 15:09:00 +00:00
downloadTaskInfo . Cancelled = true
return downloadTaskInfo , nil
case <- torrent . GotInfo ( ) :
files := torrent . Files ( )
i , ok := findIndexFile ( files )
if ! ok {
// We're dealing with a malformed torrent, so don't do anything
return nil , errors . New ( "malformed torrent data" )
}
indexFile := files [ i ]
indexFile . Download ( )
2024-06-06 13:59:27 +00:00
m . logger . Debug ( "downloading history archive index" )
2024-05-29 15:09:00 +00:00
ticker := time . NewTicker ( 100 * time . Millisecond )
defer ticker . Stop ( )
for {
select {
case <- cancelTask :
2024-06-06 13:59:27 +00:00
m . logger . Debug ( "cancelled downloading archive index" )
2024-05-29 15:09:00 +00:00
downloadTaskInfo . Cancelled = true
return downloadTaskInfo , nil
case <- ticker . C :
if indexFile . BytesCompleted ( ) == indexFile . Length ( ) {
2024-06-06 14:59:46 +00:00
index , err := m . ArchiveFileManager . LoadHistoryArchiveIndexFromFile ( m . identity , communityID )
2024-05-29 15:09:00 +00:00
if err != nil {
return nil , err
}
existingArchiveIDs , err := m . persistence . GetDownloadedMessageArchiveIDs ( communityID )
if err != nil {
return nil , err
}
if len ( existingArchiveIDs ) == len ( index . Archives ) {
2024-06-06 13:59:27 +00:00
m . logger . Debug ( "download cancelled, no new archives" )
2024-05-29 15:09:00 +00:00
return downloadTaskInfo , nil
}
downloadTaskInfo . TotalDownloadedArchivesCount = len ( existingArchiveIDs )
downloadTaskInfo . TotalArchivesCount = len ( index . Archives )
archiveHashes := make ( archiveMDSlice , 0 , downloadTaskInfo . TotalArchivesCount )
for hash , metadata := range index . Archives {
archiveHashes = append ( archiveHashes , & archiveMetadata { hash : hash , from : metadata . Metadata . From } )
}
sort . Sort ( sort . Reverse ( archiveHashes ) )
2024-05-30 15:04:47 +00:00
m . publisher . publish ( & Subscription {
2024-05-29 15:09:00 +00:00
DownloadingHistoryArchivesStartedSignal : & signal . DownloadingHistoryArchivesStartedSignal {
CommunityID : communityID . String ( ) ,
} ,
} )
for _ , hd := range archiveHashes {
hash := hd . hash
hasArchive := false
for _ , existingHash := range existingArchiveIDs {
if existingHash == hash {
hasArchive = true
break
}
}
if hasArchive {
continue
}
metadata := index . Archives [ hash ]
startIndex := int ( metadata . Offset ) / pieceLength
endIndex := startIndex + int ( metadata . Size ) / pieceLength
downloadMsg := fmt . Sprintf ( "downloading data for message archive (%d/%d)" , downloadTaskInfo . TotalDownloadedArchivesCount + 1 , downloadTaskInfo . TotalArchivesCount )
2024-06-06 13:59:27 +00:00
m . logger . Debug ( downloadMsg , zap . String ( "hash" , hash ) )
m . logger . Debug ( "pieces (start, end)" , zap . Any ( "startIndex" , startIndex ) , zap . Any ( "endIndex" , endIndex - 1 ) )
2024-05-29 15:09:00 +00:00
torrent . DownloadPieces ( startIndex , endIndex )
piecesCompleted := make ( map [ int ] bool )
for i = startIndex ; i < endIndex ; i ++ {
piecesCompleted [ i ] = false
}
psc := torrent . SubscribePieceStateChanges ( )
downloadTicker := time . NewTicker ( 1 * time . Second )
defer downloadTicker . Stop ( )
downloadLoop :
for {
select {
case <- downloadTicker . C :
done := true
for i = startIndex ; i < endIndex ; i ++ {
piecesCompleted [ i ] = torrent . PieceState ( i ) . Complete
if ! piecesCompleted [ i ] {
done = false
}
}
if done {
psc . Close ( )
break downloadLoop
}
case <- cancelTask :
2024-06-06 13:59:27 +00:00
m . logger . Debug ( "downloading archive data interrupted" )
2024-05-29 15:09:00 +00:00
downloadTaskInfo . Cancelled = true
return downloadTaskInfo , nil
}
}
downloadTaskInfo . TotalDownloadedArchivesCount ++
err = m . persistence . SaveMessageArchiveID ( communityID , hash )
if err != nil {
2024-06-06 13:59:27 +00:00
m . logger . Error ( "couldn't save message archive ID" , zap . Error ( err ) )
2024-05-29 15:09:00 +00:00
continue
}
2024-05-30 15:04:47 +00:00
m . publisher . publish ( & Subscription {
2024-05-29 15:09:00 +00:00
HistoryArchiveDownloadedSignal : & signal . HistoryArchiveDownloadedSignal {
CommunityID : communityID . String ( ) ,
From : int ( metadata . Metadata . From ) ,
To : int ( metadata . Metadata . To ) ,
} ,
} )
}
2024-05-30 15:04:47 +00:00
m . publisher . publish ( & Subscription {
2024-05-29 15:09:00 +00:00
HistoryArchivesSeedingSignal : & signal . HistoryArchivesSeedingSignal {
CommunityID : communityID . String ( ) ,
} ,
} )
2024-06-06 13:59:27 +00:00
m . logger . Debug ( "finished downloading archives" )
2024-05-29 15:09:00 +00:00
return downloadTaskInfo , nil
}
}
}
}
}
2024-06-06 14:59:46 +00:00
func ( m * ArchiveManager ) TorrentFileExists ( communityID string ) bool {
2024-06-03 14:55:58 +00:00
_ , err := os . Stat ( torrentFile ( m . torrentConfig . TorrentDir , communityID ) )
2024-05-29 15:09:00 +00:00
return err == nil
}
2024-05-29 19:25:47 +00:00
func topicsAsByteArrays ( topics [ ] types . TopicType ) [ ] [ ] byte {
var topicsAsByteArrays [ ] [ ] byte
for _ , t := range topics {
topic := types . TopicTypeToByteArray ( t )
topicsAsByteArrays = append ( topicsAsByteArrays , topic )
2024-05-29 15:09:00 +00:00
}
2024-05-29 19:25:47 +00:00
return topicsAsByteArrays
2024-05-29 15:09:00 +00:00
}
func findIndexFile ( files [ ] * torrent . File ) ( index int , ok bool ) {
for i , f := range files {
if f . DisplayPath ( ) == "index" {
return i , true
}
}
return 0 , false
}
2024-06-03 14:55:58 +00:00
func torrentFile ( torrentDir , communityID string ) string {
return path . Join ( torrentDir , communityID + ".torrent" )
}