2018-04-27 15:14:23 +02:00
|
|
|
package cmd
|
|
|
|
|
|
|
|
import (
|
|
|
|
"encoding/json"
|
|
|
|
"fmt"
|
|
|
|
"io/ioutil"
|
|
|
|
"net/http"
|
2018-05-25 04:03:54 +02:00
|
|
|
url2 "net/url"
|
2018-04-27 15:14:23 +02:00
|
|
|
"os"
|
2018-05-17 02:08:52 +02:00
|
|
|
|
2018-05-25 04:03:54 +02:00
|
|
|
"os/user"
|
2018-05-24 02:32:11 +02:00
|
|
|
|
2018-05-17 01:42:06 +02:00
|
|
|
"github.com/lbryio/lbry.go/errors"
|
2018-04-27 15:14:23 +02:00
|
|
|
"github.com/lbryio/lbry.go/null"
|
|
|
|
"github.com/lbryio/lbry.go/util"
|
2018-05-17 01:42:06 +02:00
|
|
|
sync "github.com/lbryio/lbry.go/ytsync"
|
2018-04-27 15:14:23 +02:00
|
|
|
log "github.com/sirupsen/logrus"
|
|
|
|
"github.com/spf13/cobra"
|
|
|
|
)
|
|
|
|
|
|
|
|
func init() {
|
|
|
|
var selfSyncCmd = &cobra.Command{
|
2018-05-24 02:32:11 +02:00
|
|
|
Use: "selfsync <youtube_api_key> <auth_token>",
|
|
|
|
Args: cobra.RangeArgs(2, 2),
|
2018-04-27 15:14:23 +02:00
|
|
|
Short: "Publish youtube channels into LBRY network automatically.",
|
|
|
|
Run: selfSync,
|
|
|
|
}
|
|
|
|
selfSyncCmd.Flags().BoolVar(&stopOnError, "stop-on-error", false, "If a publish fails, stop all publishing and exit")
|
|
|
|
selfSyncCmd.Flags().IntVar(&maxTries, "max-tries", defaultMaxTries, "Number of times to try a publish that fails")
|
|
|
|
selfSyncCmd.Flags().BoolVar(&takeOverExistingChannel, "takeover-existing-channel", false, "If channel exists and we don't own it, take over the channel")
|
|
|
|
selfSyncCmd.Flags().IntVar(&limit, "limit", 0, "limit the amount of channels to sync")
|
2018-05-17 01:42:06 +02:00
|
|
|
selfSyncCmd.Flags().BoolVar(&skipSpaceCheck, "skip-space-check", false, "Do not perform free space check on startup")
|
2018-05-24 02:32:11 +02:00
|
|
|
selfSyncCmd.Flags().BoolVar(&syncUpdate, "update", false, "Update previously synced channels instead of syncing new ones (short for --status synced)")
|
2018-05-25 04:03:54 +02:00
|
|
|
selfSyncCmd.Flags().StringVar(&syncStatus, "status", StatusQueued, "Specify which queue to pull from. Overrides --update (Default: queued)")
|
2018-05-17 01:42:06 +02:00
|
|
|
|
2018-04-27 15:14:23 +02:00
|
|
|
RootCmd.AddCommand(selfSyncCmd)
|
|
|
|
}
|
|
|
|
|
|
|
|
type APIJobsResponse struct {
|
|
|
|
Success bool `json:"success"`
|
|
|
|
Error null.String `json:"error"`
|
|
|
|
Data []APIYoutubeChannel `json:"data"`
|
|
|
|
}
|
|
|
|
|
|
|
|
type APIYoutubeChannel struct {
|
|
|
|
ChannelId string `json:"channel_id"`
|
|
|
|
TotalVideos uint `json:"total_videos"`
|
|
|
|
DesiredChannelName string `json:"desired_channel_name"`
|
|
|
|
SyncServer null.String `json:"sync_server"`
|
|
|
|
}
|
|
|
|
|
2018-05-22 23:47:03 +02:00
|
|
|
func fetchChannels(authToken string, status string) ([]APIYoutubeChannel, error) {
|
2018-05-24 02:32:11 +02:00
|
|
|
url := "http://localhost:8080/yt/jobs"
|
|
|
|
res, _ := http.PostForm(url, url2.Values{
|
|
|
|
"auth_token": {authToken},
|
|
|
|
"sync_status": {status},
|
|
|
|
})
|
2018-04-27 15:14:23 +02:00
|
|
|
defer res.Body.Close()
|
|
|
|
body, _ := ioutil.ReadAll(res.Body)
|
|
|
|
var response APIJobsResponse
|
|
|
|
err := json.Unmarshal(body, &response)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
return response.Data, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
type APISyncUpdateResponse struct {
|
|
|
|
Success bool `json:"success"`
|
|
|
|
Error null.String `json:"error"`
|
|
|
|
Data null.String `json:"data"`
|
|
|
|
}
|
|
|
|
|
|
|
|
func setChannelSyncStatus(authToken string, channelID string, status string) error {
|
|
|
|
host, err := os.Hostname()
|
|
|
|
if err != nil {
|
|
|
|
return errors.Err("could not detect system hostname")
|
|
|
|
}
|
2018-05-24 02:32:11 +02:00
|
|
|
url := "http://localhost:8080/yt/sync_update"
|
|
|
|
|
|
|
|
res, _ := http.PostForm(url, url2.Values{
|
|
|
|
"channel_id": {channelID},
|
|
|
|
"sync_server": {host},
|
|
|
|
"auth_token": {authToken},
|
|
|
|
"sync_status": {status},
|
|
|
|
})
|
2018-04-27 15:14:23 +02:00
|
|
|
defer res.Body.Close()
|
|
|
|
body, _ := ioutil.ReadAll(res.Body)
|
|
|
|
var response APISyncUpdateResponse
|
|
|
|
err = json.Unmarshal(body, &response)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
if !response.Error.IsNull() {
|
|
|
|
return errors.Err(response.Error.String)
|
|
|
|
}
|
|
|
|
if !response.Data.IsNull() && response.Data.String == "ok" {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
return errors.Err("invalid API response")
|
|
|
|
}
|
|
|
|
|
2018-05-25 04:03:54 +02:00
|
|
|
func spaceCheck() error {
|
2018-05-17 01:42:06 +02:00
|
|
|
usr, err := user.Current()
|
|
|
|
if err != nil {
|
2018-05-25 04:03:54 +02:00
|
|
|
return err
|
2018-05-17 01:42:06 +02:00
|
|
|
}
|
|
|
|
usedPctile, err := util.GetUsedSpace(usr.HomeDir + "/.lbrynet/blobfiles/")
|
|
|
|
if err != nil {
|
2018-05-25 04:03:54 +02:00
|
|
|
return err
|
2018-05-17 01:42:06 +02:00
|
|
|
}
|
2018-05-24 02:32:11 +02:00
|
|
|
if usedPctile > 0.90 && !skipSpaceCheck {
|
2018-05-25 04:03:54 +02:00
|
|
|
return errors.Err("more than 90%% of the space has been used. use --skip-space-check to ignore. Used: %.1f%%", usedPctile*100)
|
2018-05-17 01:42:06 +02:00
|
|
|
}
|
2018-05-17 02:08:52 +02:00
|
|
|
util.SendToSlackInfo("disk usage: %.1f%%", usedPctile*100)
|
2018-05-25 04:03:54 +02:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func selfSync(cmd *cobra.Command, args []string) {
|
|
|
|
slackToken := os.Getenv("SLACK_TOKEN")
|
|
|
|
if slackToken == "" {
|
|
|
|
log.Error("A slack token was not present in env vars! Slack messages disabled!")
|
|
|
|
} else {
|
|
|
|
util.InitSlack(os.Getenv("SLACK_TOKEN"))
|
|
|
|
}
|
2018-04-27 15:14:23 +02:00
|
|
|
|
|
|
|
ytAPIKey := args[0]
|
|
|
|
authToken := args[1]
|
|
|
|
|
2018-05-24 02:32:11 +02:00
|
|
|
if !util.InSlice(syncStatus, SyncStatuses) {
|
|
|
|
log.Errorf("status must be one of the following: %v\n", SyncStatuses)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
if syncUpdate {
|
|
|
|
syncStatus = StatusSynced
|
2018-05-22 23:47:03 +02:00
|
|
|
}
|
2018-04-27 15:14:23 +02:00
|
|
|
if stopOnError && maxTries != defaultMaxTries {
|
|
|
|
log.Errorln("--stop-on-error and --max-tries are mutually exclusive")
|
|
|
|
return
|
|
|
|
}
|
|
|
|
if maxTries < 1 {
|
|
|
|
log.Errorln("setting --max-tries less than 1 doesn't make sense")
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
if limit < 0 {
|
|
|
|
log.Errorln("setting --limit less than 0 (unlimited) doesn't make sense")
|
|
|
|
return
|
|
|
|
}
|
2018-05-25 04:03:54 +02:00
|
|
|
syncCount := 0
|
|
|
|
if syncStatus == StatusQueued {
|
|
|
|
for {
|
|
|
|
//before processing the queued ones first clear the pending ones (if any)
|
|
|
|
//TODO: extract method
|
|
|
|
syncingChannels, err := fetchChannels(authToken, StatusSyncing)
|
|
|
|
if err != nil {
|
|
|
|
util.SendToSlackError("failed to fetch channels: %v", err)
|
|
|
|
break
|
|
|
|
}
|
|
|
|
interruptedByUser, err := syncChannels(syncingChannels, authToken, ytAPIKey, &syncCount)
|
|
|
|
if err != nil {
|
|
|
|
util.SendToSlackError("%v", err)
|
|
|
|
break
|
|
|
|
}
|
|
|
|
if interruptedByUser {
|
|
|
|
break
|
|
|
|
}
|
|
|
|
util.SendToSlackInfo("Finished syncing pending channels")
|
|
|
|
|
|
|
|
//process queued channels
|
|
|
|
queuedChannels, err := fetchChannels(authToken, StatusQueued)
|
|
|
|
if err != nil {
|
|
|
|
util.SendToSlackError("failed to fetch channels: %v", err)
|
|
|
|
break
|
|
|
|
}
|
|
|
|
interruptedByUser, err = syncChannels(queuedChannels, authToken, ytAPIKey, &syncCount)
|
|
|
|
if err != nil {
|
|
|
|
util.SendToSlackError("%v", err)
|
|
|
|
break
|
|
|
|
}
|
|
|
|
if interruptedByUser {
|
|
|
|
break
|
|
|
|
}
|
|
|
|
util.SendToSlackInfo("Finished syncing queued channels")
|
|
|
|
|
|
|
|
if syncUpdate {
|
|
|
|
//update synced channels
|
|
|
|
syncedChannels, err := fetchChannels(authToken, StatusSynced)
|
|
|
|
if err != nil {
|
|
|
|
util.SendToSlackError("failed to fetch channels: %v", err)
|
|
|
|
break
|
|
|
|
}
|
|
|
|
interruptedByUser, err = syncChannels(syncedChannels, authToken, ytAPIKey, &syncCount)
|
|
|
|
if err != nil {
|
|
|
|
util.SendToSlackError("%v", err)
|
|
|
|
break
|
|
|
|
}
|
|
|
|
if interruptedByUser {
|
|
|
|
break
|
|
|
|
}
|
|
|
|
util.SendToSlackInfo("Finished updating channels")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
// sync whatever was specified
|
|
|
|
channelsToSync, err := fetchChannels(authToken, syncStatus)
|
|
|
|
if err != nil {
|
|
|
|
util.SendToSlackError("failed to fetch channels: %v", err)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
interruptedByUser, err := syncChannels(channelsToSync, authToken, ytAPIKey, &syncCount)
|
|
|
|
if err != nil {
|
|
|
|
util.SendToSlackError("%v", err)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
if interruptedByUser {
|
|
|
|
return
|
|
|
|
}
|
2018-04-27 15:14:23 +02:00
|
|
|
}
|
2018-05-25 04:03:54 +02:00
|
|
|
util.SendToSlackInfo("Syncing process terminated!")
|
|
|
|
}
|
|
|
|
|
|
|
|
// syncChannels processes a slice of youtube channels (channelsToSync) and returns a bool that indicates whether
|
|
|
|
// the execution finished by itself or was interrupted by the user and an error if anything happened
|
|
|
|
func syncChannels(channelsToSync []APIYoutubeChannel, authToken string, ytAPIKey string, syncCount *int) (bool, error) {
|
2018-05-05 13:22:33 +02:00
|
|
|
host, err := os.Hostname()
|
|
|
|
if err != nil {
|
|
|
|
host = ""
|
|
|
|
}
|
2018-05-25 04:03:54 +02:00
|
|
|
for ; *syncCount < len(channelsToSync) && (limit == 0 || *syncCount < limit); *syncCount++ {
|
|
|
|
err = spaceCheck()
|
|
|
|
if err != nil {
|
|
|
|
return false, err
|
|
|
|
}
|
2018-04-27 15:14:23 +02:00
|
|
|
//avoid dereferencing
|
2018-05-25 04:03:54 +02:00
|
|
|
channel := channelsToSync[*syncCount]
|
2018-04-27 15:14:23 +02:00
|
|
|
channelID := channel.ChannelId
|
|
|
|
lbryChannelName := channel.DesiredChannelName
|
|
|
|
if channel.TotalVideos < 1 {
|
2018-05-24 02:32:11 +02:00
|
|
|
util.SendToSlackInfo("Channel %s has no videos. Skipping", lbryChannelName)
|
2018-04-27 15:14:23 +02:00
|
|
|
continue
|
|
|
|
}
|
2018-05-05 13:22:33 +02:00
|
|
|
if !channel.SyncServer.IsNull() && channel.SyncServer.String != host {
|
2018-05-24 02:32:11 +02:00
|
|
|
util.SendToSlackInfo("Channel %s is being synced by another server: %s", lbryChannelName, channel.SyncServer.String)
|
2018-04-27 15:14:23 +02:00
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
//acquire the lock on the channel
|
|
|
|
err := setChannelSyncStatus(authToken, channelID, StatusSyncing)
|
|
|
|
if err != nil {
|
2018-05-24 02:32:11 +02:00
|
|
|
util.SendToSlackError("Failed acquiring sync rights for channel %s: %v", lbryChannelName, err)
|
2018-04-27 15:14:23 +02:00
|
|
|
continue
|
|
|
|
}
|
2018-05-25 04:03:54 +02:00
|
|
|
util.SendToSlackInfo("Syncing %s to LBRY! (iteration %d)", lbryChannelName, *syncCount)
|
2018-04-27 15:14:23 +02:00
|
|
|
|
|
|
|
s := sync.Sync{
|
|
|
|
YoutubeAPIKey: ytAPIKey,
|
|
|
|
YoutubeChannelID: channelID,
|
|
|
|
LbryChannelName: lbryChannelName,
|
|
|
|
StopOnError: stopOnError,
|
|
|
|
MaxTries: maxTries,
|
|
|
|
ConcurrentVideos: 1,
|
|
|
|
TakeOverExistingChannel: takeOverExistingChannel,
|
|
|
|
Refill: refill,
|
|
|
|
}
|
|
|
|
|
|
|
|
err = s.FullCycle()
|
2018-05-05 13:22:33 +02:00
|
|
|
util.SendToSlackInfo("Syncing " + lbryChannelName + " reached an end.")
|
2018-04-27 15:14:23 +02:00
|
|
|
if err != nil {
|
2018-05-05 13:22:33 +02:00
|
|
|
util.SendToSlackError(errors.FullTrace(err))
|
2018-05-17 01:42:06 +02:00
|
|
|
fatalErrors := []string{
|
|
|
|
"default_wallet already exists",
|
|
|
|
"WALLET HAS NOT BEEN MOVED TO THE WALLET BACKUP DIR",
|
|
|
|
}
|
|
|
|
if util.InSliceContains(err.Error(), fatalErrors) {
|
2018-05-25 04:03:54 +02:00
|
|
|
return s.IsInterrupted(), errors.Prefix("@Nikooo777 this requires manual intervention! Exiting...", err)
|
2018-05-17 01:42:06 +02:00
|
|
|
}
|
2018-04-27 15:48:34 +02:00
|
|
|
//mark video as failed
|
|
|
|
err := setChannelSyncStatus(authToken, channelID, StatusFailed)
|
|
|
|
if err != nil {
|
2018-05-25 04:03:54 +02:00
|
|
|
msg := fmt.Sprintf("Failed setting failed state for channel %s. \n@Nikooo777 this requires manual intervention! Exiting...", lbryChannelName)
|
|
|
|
return s.IsInterrupted(), errors.Prefix(msg, err)
|
2018-04-27 15:48:34 +02:00
|
|
|
}
|
2018-05-17 01:42:06 +02:00
|
|
|
continue
|
2018-04-27 15:14:23 +02:00
|
|
|
}
|
2018-05-24 02:32:11 +02:00
|
|
|
if s.IsInterrupted() {
|
2018-05-25 04:03:54 +02:00
|
|
|
return true, nil
|
2018-05-24 02:32:11 +02:00
|
|
|
}
|
2018-04-27 15:48:34 +02:00
|
|
|
//mark video as synced
|
|
|
|
err = setChannelSyncStatus(authToken, channelID, StatusSynced)
|
|
|
|
if err != nil {
|
2018-05-25 04:03:54 +02:00
|
|
|
msg := fmt.Sprintf("Failed setting failed state for channel %s. \n@Nikooo777 this requires manual intervention! Exiting...", lbryChannelName)
|
|
|
|
return false, errors.Prefix(msg, err)
|
2018-04-27 15:48:34 +02:00
|
|
|
}
|
2018-04-27 15:14:23 +02:00
|
|
|
}
|
2018-05-25 04:03:54 +02:00
|
|
|
return false, nil
|
2018-04-27 15:14:23 +02:00
|
|
|
}
|