2019-01-10 21:02:26 -05:00
package manager
2018-07-17 12:54:22 -04:00
import (
"fmt"
2018-08-21 13:17:52 -04:00
"strings"
2018-07-17 14:58:47 -04:00
"syscall"
2018-08-21 13:17:52 -04:00
"time"
2018-07-17 14:58:47 -04:00
2019-07-10 15:46:54 +02:00
"github.com/lbryio/ytsync/blobs_reflector"
2018-10-08 16:19:17 -04:00
"github.com/lbryio/ytsync/namer"
"github.com/lbryio/ytsync/sdk"
2019-07-15 16:16:02 -04:00
logUtils "github.com/lbryio/ytsync/util"
2018-10-08 16:19:17 -04:00
2019-01-10 20:34:34 -05:00
"github.com/lbryio/lbry.go/extras/errors"
"github.com/lbryio/lbry.go/extras/util"
2019-06-06 23:25:31 +02:00
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/aws/credentials"
2018-07-17 12:54:22 -04:00
log "github.com/sirupsen/logrus"
)
type SyncManager struct {
2018-09-26 00:08:18 -04:00
stopOnError bool
maxTries int
takeOverExistingChannel bool
refill int
limit int
skipSpaceCheck bool
syncUpdate bool
concurrentJobs int
concurrentVideos int
blobsDir string
videosLimit int
maxVideoSize int
2019-01-03 19:55:27 +01:00
maxVideoLength float64
2018-09-26 00:08:18 -04:00
lbrycrdString string
awsS3ID string
awsS3Secret string
awsS3Region string
syncStatus string
awsS3Bucket string
singleRun bool
syncProperties * sdk . SyncProperties
apiConfig * sdk . APIConfig
2019-06-04 22:21:40 +02:00
removeDBUnpublished bool
2019-06-06 16:24:20 +02:00
upgradeMetadata bool
2018-09-18 15:20:34 -04:00
}
2018-09-26 00:08:18 -04:00
func NewSyncManager ( stopOnError bool , maxTries int , takeOverExistingChannel bool , refill int , limit int ,
skipSpaceCheck bool , syncUpdate bool , concurrentJobs int , concurrentVideos int , blobsDir string , videosLimit int ,
maxVideoSize int , lbrycrdString string , awsS3ID string , awsS3Secret string , awsS3Region string , awsS3Bucket string ,
2019-06-06 16:24:20 +02:00
syncStatus string , singleRun bool , syncProperties * sdk . SyncProperties , apiConfig * sdk . APIConfig , maxVideoLength float64 , removeDBUnpublished bool , upgradeMetadata bool ) * SyncManager {
2018-09-18 15:20:34 -04:00
return & SyncManager {
2018-09-26 00:08:18 -04:00
stopOnError : stopOnError ,
maxTries : maxTries ,
takeOverExistingChannel : takeOverExistingChannel ,
refill : refill ,
limit : limit ,
skipSpaceCheck : skipSpaceCheck ,
syncUpdate : syncUpdate ,
concurrentJobs : concurrentJobs ,
concurrentVideos : concurrentVideos ,
blobsDir : blobsDir ,
videosLimit : videosLimit ,
maxVideoSize : maxVideoSize ,
2019-01-03 19:55:27 +01:00
maxVideoLength : maxVideoLength ,
2018-09-26 00:08:18 -04:00
lbrycrdString : lbrycrdString ,
awsS3ID : awsS3ID ,
awsS3Secret : awsS3Secret ,
awsS3Region : awsS3Region ,
awsS3Bucket : awsS3Bucket ,
syncStatus : syncStatus ,
singleRun : singleRun ,
syncProperties : syncProperties ,
apiConfig : apiConfig ,
2019-06-04 22:21:40 +02:00
removeDBUnpublished : removeDBUnpublished ,
2019-06-06 16:24:20 +02:00
upgradeMetadata : upgradeMetadata ,
2018-09-18 15:20:34 -04:00
}
2018-07-17 12:54:22 -04:00
}
const (
2019-05-03 05:11:52 +02:00
StatusPending = "pending" // waiting for permission to sync
StatusPendingEmail = "pendingemail" // permission granted but missing email
StatusQueued = "queued" // in sync queue. will be synced soon
StatusPendingUpgrade = "pendingupgrade" // in sync queue. will be synced soon
StatusSyncing = "syncing" // syncing now
StatusSynced = "synced" // done
StatusFailed = "failed"
StatusFinalized = "finalized" // no more changes allowed
StatusAbandoned = "abandoned" // deleted on youtube or banned
2018-07-17 12:54:22 -04:00
)
2019-05-03 05:11:52 +02:00
var SyncStatuses = [ ] string { StatusPending , StatusPendingEmail , StatusPendingUpgrade , StatusQueued , StatusSyncing , StatusSynced , StatusFailed , StatusFinalized , StatusAbandoned }
2018-07-17 12:54:22 -04:00
2018-07-20 19:56:36 -04:00
const (
2019-06-10 21:59:42 +02:00
VideoStatusPublished = "published"
VideoStatusFailed = "failed"
VideoStatusUpgradeFailed = "upgradefailed"
VideoStatusUnpublished = "unpublished"
2018-07-20 19:56:36 -04:00
)
2018-08-17 10:05:54 -04:00
func ( s * SyncManager ) Start ( ) error {
2018-09-18 15:20:34 -04:00
2019-08-12 23:20:09 -04:00
if logUtils . ShouldCleanOnStartup ( ) {
2019-08-03 18:34:48 -04:00
err := logUtils . CleanForStartup ( )
if err != nil {
return err
}
}
2018-07-17 12:54:22 -04:00
syncCount := 0
for {
err := s . checkUsedSpace ( )
if err != nil {
2019-08-10 22:50:43 -04:00
return errors . Err ( err )
2018-07-17 12:54:22 -04:00
}
var syncs [ ] Sync
shouldInterruptLoop := false
2018-09-26 00:08:18 -04:00
isSingleChannelSync := s . syncProperties . YoutubeChannelID != ""
2018-07-17 12:54:22 -04:00
if isSingleChannelSync {
2018-09-26 00:08:18 -04:00
channels , err := s . apiConfig . FetchChannels ( "" , s . syncProperties )
2018-07-17 12:54:22 -04:00
if err != nil {
2019-08-10 22:50:43 -04:00
return errors . Err ( err )
2018-07-17 12:54:22 -04:00
}
if len ( channels ) != 1 {
return errors . Err ( "Expected 1 channel, %d returned" , len ( channels ) )
}
lbryChannelName := channels [ 0 ] . DesiredChannelName
syncs = make ( [ ] Sync , 1 )
syncs [ 0 ] = Sync {
2018-09-26 00:08:18 -04:00
APIConfig : s . apiConfig ,
YoutubeChannelID : s . syncProperties . YoutubeChannelID ,
2018-07-17 12:54:22 -04:00
LbryChannelName : lbryChannelName ,
2018-12-24 19:23:40 -05:00
lbryChannelID : channels [ 0 ] . ChannelClaimID ,
2018-09-26 00:08:18 -04:00
StopOnError : s . stopOnError ,
MaxTries : s . maxTries ,
ConcurrentVideos : s . concurrentVideos ,
TakeOverExistingChannel : s . takeOverExistingChannel ,
Refill : s . refill ,
2018-08-17 10:05:54 -04:00
Manager : s ,
2018-09-26 00:08:18 -04:00
LbrycrdString : s . lbrycrdString ,
AwsS3ID : s . awsS3ID ,
AwsS3Secret : s . awsS3Secret ,
AwsS3Region : s . awsS3Region ,
AwsS3Bucket : s . awsS3Bucket ,
2018-10-09 15:57:07 -04:00
namer : namer . NewNamer ( ) ,
2019-06-06 02:16:07 +02:00
Fee : channels [ 0 ] . Fee ,
2018-07-17 12:54:22 -04:00
}
shouldInterruptLoop = true
} else {
var queuesToSync [ ] string
2019-06-12 22:42:42 +02:00
//TODO: implement scrambling to avoid starvation of queues
2018-09-26 00:08:18 -04:00
if s . syncStatus != "" {
queuesToSync = append ( queuesToSync , s . syncStatus )
} else if s . syncUpdate {
2018-07-17 12:54:22 -04:00
queuesToSync = append ( queuesToSync , StatusSyncing , StatusSynced )
} else {
queuesToSync = append ( queuesToSync , StatusSyncing , StatusQueued )
}
for _ , q := range queuesToSync {
2019-07-22 16:16:23 -04:00
//temporary override for sync-until to give tom the time to review the channels
2019-07-22 16:24:13 -04:00
if q == StatusQueued {
s . syncProperties . SyncUntil = time . Now ( ) . AddDate ( 0 , 0 , - 1 ) . Unix ( )
}
2018-09-26 00:08:18 -04:00
channels , err := s . apiConfig . FetchChannels ( q , s . syncProperties )
2018-07-17 12:54:22 -04:00
if err != nil {
return err
}
2019-06-12 22:42:42 +02:00
log . Infof ( "There are %d channels in the \"%s\" queue" , len ( channels ) , q )
if len ( channels ) > 0 {
c := channels [ 0 ]
2018-07-17 12:54:22 -04:00
syncs = append ( syncs , Sync {
2018-09-26 00:08:18 -04:00
APIConfig : s . apiConfig ,
2018-07-17 12:54:22 -04:00
YoutubeChannelID : c . ChannelId ,
LbryChannelName : c . DesiredChannelName ,
2018-12-24 19:23:40 -05:00
lbryChannelID : c . ChannelClaimID ,
2018-09-26 00:08:18 -04:00
StopOnError : s . stopOnError ,
MaxTries : s . maxTries ,
ConcurrentVideos : s . concurrentVideos ,
TakeOverExistingChannel : s . takeOverExistingChannel ,
Refill : s . refill ,
2018-08-17 10:05:54 -04:00
Manager : s ,
2018-09-26 00:08:18 -04:00
LbrycrdString : s . lbrycrdString ,
AwsS3ID : s . awsS3ID ,
AwsS3Secret : s . awsS3Secret ,
AwsS3Region : s . awsS3Region ,
AwsS3Bucket : s . awsS3Bucket ,
2018-10-09 15:57:07 -04:00
namer : namer . NewNamer ( ) ,
2019-06-06 02:16:07 +02:00
Fee : c . Fee ,
2018-07-17 12:54:22 -04:00
} )
2019-06-24 04:14:27 -04:00
continue
2018-07-17 12:54:22 -04:00
}
}
}
if len ( syncs ) == 0 {
log . Infoln ( "No channels to sync. Pausing 5 minutes!" )
time . Sleep ( 5 * time . Minute )
}
2019-06-12 22:42:42 +02:00
for _ , sync := range syncs {
2018-08-21 13:17:52 -04:00
shouldNotCount := false
2019-07-15 16:16:02 -04:00
logUtils . SendInfoToSlack ( "Syncing %s (%s) to LBRY! total processed channels since startup: %d" , sync . LbryChannelName , sync . YoutubeChannelID , syncCount + 1 )
2018-07-17 12:54:22 -04:00
err := sync . FullCycle ( )
if err != nil {
fatalErrors := [ ] string {
"default_wallet already exists" ,
"WALLET HAS NOT BEEN MOVED TO THE WALLET BACKUP DIR" ,
"NotEnoughFunds" ,
"no space left on device" ,
2018-08-21 13:17:52 -04:00
"failure uploading wallet" ,
2019-01-03 08:08:45 -05:00
"the channel in the wallet is different than the channel in the database" ,
2019-01-03 19:55:27 +01:00
"this channel does not belong to this wallet!" ,
2018-07-17 12:54:22 -04:00
}
2018-07-23 20:01:35 -04:00
if util . SubstringInSlice ( err . Error ( ) , fatalErrors ) {
2018-07-17 12:54:22 -04:00
return errors . Prefix ( "@Nikooo777 this requires manual intervention! Exiting..." , err )
}
2018-08-21 13:17:52 -04:00
shouldNotCount = strings . Contains ( err . Error ( ) , "this youtube channel is being managed by another server" )
2018-08-22 10:56:02 -04:00
if ! shouldNotCount {
2019-07-15 16:16:02 -04:00
logUtils . SendInfoToSlack ( "A non fatal error was reported by the sync process. %s\nContinuing..." , err . Error ( ) )
2018-08-22 10:56:02 -04:00
}
2018-07-17 12:54:22 -04:00
}
2019-07-10 15:46:54 +02:00
err = blobs_reflector . ReflectAndClean ( )
if err != nil {
return errors . Prefix ( "@Nikooo777 something went wrong while reflecting blobs" , err )
}
2019-07-15 16:16:02 -04:00
logUtils . SendInfoToSlack ( "Syncing %s (%s) reached an end. total processed channels since startup: %d" , sync . LbryChannelName , sync . YoutubeChannelID , syncCount + 1 )
2018-08-21 13:17:52 -04:00
if ! shouldNotCount {
syncCount ++
}
2018-09-26 00:08:18 -04:00
if sync . IsInterrupted ( ) || ( s . limit != 0 && syncCount >= s . limit ) {
2018-07-17 12:54:22 -04:00
shouldInterruptLoop = true
break
}
}
2018-09-26 00:08:18 -04:00
if shouldInterruptLoop || s . singleRun {
2018-07-17 12:54:22 -04:00
break
}
}
return nil
}
2019-05-03 05:11:52 +02:00
func ( s * SyncManager ) GetS3AWSConfig ( ) aws . Config {
return aws . Config {
Credentials : credentials . NewStaticCredentials ( s . awsS3ID , s . awsS3Secret , "" ) ,
Region : & s . awsS3Region ,
}
}
2018-08-17 10:05:54 -04:00
func ( s * SyncManager ) checkUsedSpace ( ) error {
2019-08-03 18:34:48 -04:00
logUtils . SendInfoToSlack ( logUtils . GetBlobsDir ( ) )
usedPctile , err := GetUsedSpace ( logUtils . GetBlobsDir ( ) )
2018-07-17 12:54:22 -04:00
if err != nil {
2019-08-03 18:34:48 -04:00
return errors . Err ( err )
2018-07-17 12:54:22 -04:00
}
2018-09-26 00:08:18 -04:00
if usedPctile >= 0.90 && ! s . skipSpaceCheck {
2018-07-17 12:54:22 -04:00
return errors . Err ( fmt . Sprintf ( "more than 90%% of the space has been used. use --skip-space-check to ignore. Used: %.1f%%" , usedPctile * 100 ) )
}
2018-08-21 14:07:30 -04:00
log . Infof ( "disk usage: %.1f%%" , usedPctile * 100 )
2018-07-17 12:54:22 -04:00
return nil
}
2018-07-17 14:58:47 -04:00
// GetUsedSpace returns a value between 0 and 1, with 0 being completely empty and 1 being full, for the disk that holds the provided path
func GetUsedSpace ( path string ) ( float32 , error ) {
var stat syscall . Statfs_t
err := syscall . Statfs ( path , & stat )
if err != nil {
return 0 , err
}
// Available blocks * size per block = available space in bytes
all := stat . Blocks * uint64 ( stat . Bsize )
free := stat . Bfree * uint64 ( stat . Bsize )
used := all - free
return float32 ( used ) / float32 ( all ) , nil
}