remove unused flag

always go through syncing first
don't sync videos shorter than 7 seconds
refactor code in video error handling
add interface to handle hard video failures (incomplete)
This commit is contained in:
Niko Storni 2020-11-03 21:41:39 +01:00
parent beade71aa6
commit fecf67118c
5 changed files with 108 additions and 95 deletions

View file

@ -42,7 +42,6 @@ func main() {
Args: cobra.RangeArgs(0, 0), Args: cobra.RangeArgs(0, 0),
} }
cmd.Flags().BoolVar(&cliFlags.StopOnError, "stop-on-error", false, "If a publish fails, stop all publishing and exit")
cmd.Flags().IntVar(&cliFlags.MaxTries, "max-tries", defaultMaxTries, "Number of times to try a publish that fails") cmd.Flags().IntVar(&cliFlags.MaxTries, "max-tries", defaultMaxTries, "Number of times to try a publish that fails")
cmd.Flags().BoolVar(&cliFlags.TakeOverExistingChannel, "takeover-existing-channel", false, "If channel exists and we don't own it, take over the channel") cmd.Flags().BoolVar(&cliFlags.TakeOverExistingChannel, "takeover-existing-channel", false, "If channel exists and we don't own it, take over the channel")
cmd.Flags().IntVar(&cliFlags.Limit, "limit", 0, "limit the amount of channels to sync") cmd.Flags().IntVar(&cliFlags.Limit, "limit", 0, "limit the amount of channels to sync")
@ -93,10 +92,6 @@ func ytSync(cmd *cobra.Command, args []string) {
return return
} }
if cliFlags.StopOnError && cliFlags.MaxTries != defaultMaxTries {
log.Errorln("--stop-on-error and --max-tries are mutually exclusive")
return
}
if cliFlags.MaxTries < 1 { if cliFlags.MaxTries < 1 {
log.Errorln("setting --max-tries less than 1 doesn't make sense") log.Errorln("setting --max-tries less than 1 doesn't make sense")
return return

View file

@ -3,6 +3,7 @@ package manager
import ( import (
"fmt" "fmt"
"strings" "strings"
"sync"
"syscall" "syscall"
"time" "time"
@ -43,6 +44,9 @@ func (s *SyncManager) enqueueChannel(channel *shared.YoutubeChannel) {
DbChannelData: channel, DbChannelData: channel,
Manager: s, Manager: s,
namer: namer.NewNamer(), namer: namer.NewNamer(),
hardVideoFailure: hardVideoFailure{
lock: &sync.Mutex{},
},
}) })
} }
@ -78,7 +82,7 @@ func (s *SyncManager) Start() error {
} else { } else {
var queuesToSync []string var queuesToSync []string
if s.CliFlags.Status != "" { if s.CliFlags.Status != "" {
queuesToSync = append(queuesToSync, s.CliFlags.Status) queuesToSync = append(queuesToSync, shared.StatusSyncing, s.CliFlags.Status)
} else if s.CliFlags.SyncUpdate { } else if s.CliFlags.SyncUpdate {
queuesToSync = append(queuesToSync, shared.StatusSyncing, shared.StatusSynced) queuesToSync = append(queuesToSync, shared.StatusSyncing, shared.StatusSynced)
} else { } else {

View file

@ -53,6 +53,23 @@ type Sync struct {
walletMux *sync.RWMutex walletMux *sync.RWMutex
queue chan ytapi.Video queue chan ytapi.Video
defaultAccountID string defaultAccountID string
hardVideoFailure hardVideoFailure
}
type hardVideoFailure struct {
lock *sync.Mutex
failed bool
failureReason string
}
func (hv *hardVideoFailure) flagFailure(reason string) {
hv.lock.Lock()
defer hv.lock.Unlock()
if hv.failed {
return
}
hv.failed = true
hv.failureReason = reason
} }
func (s *Sync) AppendSyncedVideo(videoID string, published bool, failureReason string, claimName string, claimID string, metadataVersion int8, size int64) { func (s *Sync) AppendSyncedVideo(videoID string, published bool, failureReason string, claimName string, claimID string, metadataVersion int8, size int64) {
@ -690,10 +707,6 @@ func (s *Sync) doSync() error {
} }
} }
if s.Manager.CliFlags.StopOnError {
log.Println("Will stop publishing if an error is detected")
}
for i := 0; i < s.Manager.CliFlags.ConcurrentJobs; i++ { for i := 0; i < s.Manager.CliFlags.ConcurrentJobs; i++ {
s.grp.Add(1) s.grp.Add(1)
go func(i int) { go func(i int) {
@ -712,26 +725,74 @@ func (s *Sync) doSync() error {
return err return err
} }
func (s *Sync) startWorker(workerNum int) { func (s *Sync) startWorker(workerNum int) error {
var v ytapi.Video var v ytapi.Video
var more bool var more bool
fatalErrors := []string{
":5279: read: connection reset by peer",
"no space left on device",
"NotEnoughFunds",
"Cannot publish using channel",
"cannot concatenate 'str' and 'NoneType' objects",
"more than 90% of the space has been used.",
"Couldn't find private key for id",
"You already have a stream claim published under the name",
"Missing inputs",
}
errorsNoRetry := []string{
"non 200 status code received",
"This video contains content from",
"dont know which claim to update",
"uploader has not made this video available in your country",
"download error: AccessDenied: Access Denied",
"Playback on other websites has been disabled by the video owner",
"Error in daemon: Cannot publish empty file",
"Error extracting sts from embedded url response",
"Unable to extract signature tokens",
"Client.Timeout exceeded while awaiting headers",
"the video is too big to sync, skipping for now",
"video is too long to process",
"video is too short to process",
"no compatible format available for this video",
"Watch this video on YouTube.",
"have blocked it on copyright grounds",
"the video must be republished as we can't get the right size",
"HTTP Error 403",
"giving up after 0 fragment retries",
"Sorry about that",
"This video is not available",
"requested format not available",
"interrupted by user",
"Sign in to confirm your age",
"This video is unavailable",
"video is a live stream and hasn't completed yet",
}
walletErrors := []string{
"Not enough funds to cover this transaction",
"failed: Not enough funds",
"Error in daemon: Insufficient funds, please deposit additional LBC",
//"Missing inputs",
}
blockchainErrors := []string{
"txn-mempool-conflict",
"too-long-mempool-chain",
}
for { for {
select { select {
case <-s.grp.Ch(): case <-s.grp.Ch():
log.Printf("Stopping worker %d", workerNum) log.Printf("Stopping worker %d", workerNum)
return return nil
default: default:
} }
select { select {
case v, more = <-s.queue: case v, more = <-s.queue:
if !more { if !more {
return return nil
} }
case <-s.grp.Ch(): case <-s.grp.Ch():
log.Printf("Stopping worker %d", workerNum) log.Printf("Stopping worker %d", workerNum)
return return nil
} }
log.Println("================================================================================") log.Println("================================================================================")
@ -741,95 +802,43 @@ func (s *Sync) startWorker(workerNum int) {
select { // check again inside the loop so this dies faster select { // check again inside the loop so this dies faster
case <-s.grp.Ch(): case <-s.grp.Ch():
log.Printf("Stopping worker %d", workerNum) log.Printf("Stopping worker %d", workerNum)
return return nil
default: default:
} }
tryCount++ tryCount++
err := s.processVideo(v) err := s.processVideo(v)
if err != nil { if err != nil {
util.SendToSlack("Tried to process %s. Error: %v", v.ID(), err) logUtils.SendErrorToSlack("error processing video %s: %s", v.ID(), err.Error())
logMsg := fmt.Sprintf("error processing video %s: %s", v.ID(), err.Error()) shouldRetry := s.Manager.CliFlags.MaxTries > 1 && !util.SubstringInSlice(err.Error(), errorsNoRetry) && tryCount < s.Manager.CliFlags.MaxTries
log.Errorln(logMsg)
if strings.Contains(strings.ToLower(err.Error()), "interrupted by user") { if strings.Contains(strings.ToLower(err.Error()), "interrupted by user") {
return
}
fatalErrors := []string{
":5279: read: connection reset by peer",
"no space left on device",
"NotEnoughFunds",
"Cannot publish using channel",
"cannot concatenate 'str' and 'NoneType' objects",
"more than 90% of the space has been used.",
"Couldn't find private key for id",
"You already have a stream claim published under the name",
"Missing inputs",
}
if util.SubstringInSlice(err.Error(), fatalErrors) || s.Manager.CliFlags.StopOnError {
s.grp.Stop() s.grp.Stop()
} else if s.Manager.CliFlags.MaxTries > 1 { } else if util.SubstringInSlice(err.Error(), fatalErrors) {
errorsNoRetry := []string{ s.grp.Stop()
"non 200 status code received", } else if shouldRetry {
"This video contains content from", if util.SubstringInSlice(err.Error(), blockchainErrors) {
"dont know which claim to update", log.Println("waiting for a block before retrying")
"uploader has not made this video available in your country", err := s.waitForNewBlock()
"download error: AccessDenied: Access Denied", if err != nil {
"Playback on other websites has been disabled by the video owner", s.grp.Stop()
"Error in daemon: Cannot publish empty file", logUtils.SendErrorToSlack("something went wrong while waiting for a block: %s", errors.FullTrace(err))
"Error extracting sts from embedded url response", break
"Unable to extract signature tokens",
"Client.Timeout exceeded while awaiting headers",
"the video is too big to sync, skipping for now",
"video is too long to process",
"no compatible format available for this video",
"Watch this video on YouTube.",
"have blocked it on copyright grounds",
"the video must be republished as we can't get the right size",
"HTTP Error 403",
"giving up after 0 fragment retries",
"Sorry about that",
"This video is not available",
"requested format not available",
"interrupted by user",
"Sign in to confirm your age",
"This video is unavailable",
"video is a live stream and hasn't completed yet",
}
if util.SubstringInSlice(err.Error(), errorsNoRetry) {
log.Println("This error should not be retried at all")
} else if tryCount < s.Manager.CliFlags.MaxTries {
if util.SubstringInSlice(err.Error(), []string{
"txn-mempool-conflict",
"too-long-mempool-chain",
}) {
log.Println("waiting for a block before retrying")
err := s.waitForNewBlock()
if err != nil {
s.grp.Stop()
logUtils.SendErrorToSlack("something went wrong while waiting for a block: %s", errors.FullTrace(err))
break
}
} else if util.SubstringInSlice(err.Error(), []string{
"Not enough funds to cover this transaction",
"failed: Not enough funds",
"Error in daemon: Insufficient funds, please deposit additional LBC",
//"Missing inputs",
}) {
log.Println("checking funds and UTXOs before retrying...")
err := s.walletSetup()
if err != nil {
s.grp.Stop()
logUtils.SendErrorToSlack("failed to setup the wallet for a refill: %s", errors.FullTrace(err))
break
}
} else if strings.Contains(err.Error(), "Error in daemon: 'str' object has no attribute 'get'") {
time.Sleep(5 * time.Second)
} }
log.Println("Retrying") } else if util.SubstringInSlice(err.Error(), walletErrors) {
continue log.Println("checking funds and UTXOs before retrying...")
err := s.walletSetup()
if err != nil {
s.grp.Stop()
logUtils.SendErrorToSlack("failed to setup the wallet for a refill: %s", errors.FullTrace(err))
break
}
} else if strings.Contains(err.Error(), "Error in daemon: 'str' object has no attribute 'get'") {
time.Sleep(5 * time.Second)
} }
logUtils.SendErrorToSlack("Video failed after %d retries, skipping. Stack: %s", tryCount, logMsg) log.Println("Retrying")
continue
} }
logUtils.SendErrorToSlack("Video %s failed after %d retries, skipping. Stack: %s", tryCount, v.ID(), errors.FullTrace(err))
s.syncedVideosMux.RLock() s.syncedVideosMux.RLock()
existingClaim, ok := s.syncedVideos[v.ID()] existingClaim, ok := s.syncedVideos[v.ID()]
s.syncedVideosMux.RUnlock() s.syncedVideosMux.RUnlock()

View file

@ -33,6 +33,7 @@ var NeverRetryFailures = []string{
"Unable to extract signature tokens", "Unable to extract signature tokens",
"the video is too big to sync, skipping for now", "the video is too big to sync, skipping for now",
"video is too long to process", "video is too long to process",
"video is too short to process",
"This video contains content from", "This video contains content from",
"no compatible format available for this video", "no compatible format available for this video",
"Watch this video on YouTube.", "Watch this video on YouTube.",
@ -42,7 +43,6 @@ var NeverRetryFailures = []string{
} }
type SyncFlags struct { type SyncFlags struct {
StopOnError bool
TakeOverExistingChannel bool TakeOverExistingChannel bool
SkipSpaceCheck bool SkipSpaceCheck bool
SyncUpdate bool SyncUpdate bool

View file

@ -471,11 +471,16 @@ func (v *YoutubeVideo) downloadAndPublish(daemon *jsonrpc.Client, params SyncPar
var err error var err error
dur := time.Duration(v.youtubeInfo.Duration) * time.Second dur := time.Duration(v.youtubeInfo.Duration) * time.Second
minDuration := 7 * time.Second
if dur > v.maxVideoLength { if dur > v.maxVideoLength {
logUtils.SendErrorToSlack("%s is %s long and the limit is %s", v.id, dur.String(), v.maxVideoLength.String()) logUtils.SendErrorToSlack("%s is %s long and the limit is %s", v.id, dur.String(), v.maxVideoLength.String())
return nil, errors.Err("video is too long to process") return nil, errors.Err("video is too long to process")
} }
if dur < minDuration {
logUtils.SendErrorToSlack("%s is %s long and the minimum is %s", v.id, dur.String(), minDuration.String())
return nil, errors.Err("video is too short to process")
}
for { for {
err = v.download() err = v.download()
if err != nil && strings.Contains(err.Error(), "HTTP Error 429") { if err != nil && strings.Contains(err.Error(), "HTTP Error 429") {