/// <summary> /// 指定したアカウントのツイート, フォロー等を取得する /// 新規ログイン向け /// 常に最大数のツイートを取得する /// </summary> /// <param name="user_id"></param> /// <returns></returns> public async Task OneAccount(long user_id) { var timeOut = Task.Run(async() => { await Task.Delay(60000).ConfigureAwait(false); Environment.Exit(1); }); var t = await db.SelectUserStreamerSetting(user_id).ConfigureAwait(false); if (!t.HasValue) { return; } //無条件でAPIの最大数のツイートを取得するためにToken以外は捨てる var s = new UserStreamer(new UserStreamer.UserStreamerSetting() { Token = t.Value.Token }); var friend = s.RestFriend(); var block = s.RestBlock(); var tweet = s.RestMyTweetMax(); var cred = s.VerifyCredentials(); var timeline = s.RestTimeline(); await Task.WhenAll(friend, block, tweet, cred, timeline).ConfigureAwait(false); Console.WriteLine("{0}: API access completed.", user_id); //ツイート等の取得を行ったことをDBに保存する var savesetting = s.Setting; savesetting.rest_my_tweet = false; var settingTask = db.StoreUserStreamerSetting(savesetting); var crawlInfoTask = s.LastReceivedTweetId != 0 ? db.StoreCrawlInfo_Timeline(s.Token.UserId, s.LastMessageTime.ToUnixTimeSeconds()) : Task.CompletedTask; await Task.WhenAll(settingTask, crawlInfoTask, UserStreamerStatic.Complete()).ConfigureAwait(false); Console.WriteLine("{0}: Profile stored\t{1}, {2}", user_id, friend.Result, block.Result); Counter.PrintReset(); }
/// <summary> /// サインインした全アカウントのツイート, フォロー等を取得する /// 常に最大数のツイートを取得する /// </summary> /// <returns></returns> public async Task <int> Proceed(bool getTweet = false) { var tokens = (await db.SelectUserStreamerSetting(DBHandler.SelectTokenMode.All).ConfigureAwait(false)).ToArray(); if (tokens.Length > 0) { Console.WriteLine("App: {0} Accounts to REST", tokens.Length); } var RestProcess = new ActionBlock <Tokens>(async(t) => { //無条件でAPIの最大数のツイートを取得するためにToken以外は捨てる var s = new UserStreamer(new UserStreamer.UserStreamerSetting() { Token = t }); await s.RestFriend().ConfigureAwait(false); await s.RestBlock().ConfigureAwait(false); if (getTweet) { await s.RestMyTweet().ConfigureAwait(false); } await s.VerifyCredentials().ConfigureAwait(false); }, new ExecutionDataflowBlockOptions() { MaxDegreeOfParallelism = config.crawl.RestTweetThreads, BoundedCapacity = config.crawl.RestTweetThreads << 1 }); var sw = Stopwatch.StartNew(); foreach (var t in tokens) { await RestProcess.SendAsync(t.Token).ConfigureAwait(false); while (true) { if (sw.ElapsedMilliseconds > 60000) { Counter.PrintReset(); GCSettings.LargeObjectHeapCompactionMode = GCLargeObjectHeapCompactionMode.CompactOnce; //これは毎回必要らしい GC.Collect(); sw.Restart(); } //ツイートが詰まってたら休む if (UserStreamerStatic.NeedConnectPostpone()) { await Task.Delay(1000).ConfigureAwait(false); } else { break; } } } RestProcess.Complete(); await RestProcess.Completion.ConfigureAwait(false); await UserStreamerStatic.Complete().ConfigureAwait(false); Counter.PrintReset(); return(tokens.Length); }
/// <summary> /// これを定期的に呼んで再接続やFriendの取得をやらせる /// 1分経過しても終わらないときは打ち切る /// 一度も取得していないアカウント→取得待ちが多い順 に処理する /// </summary> public async Task ConnectStreamers() { //アカウントが1個も割り当てられなくなってたら自殺する if (!await db.ExistThisPid().ConfigureAwait(false)) { Environment.Exit(1); } var LastCrawlTime = new Dictionary <long, long>(); while (ConnectBlockProceeeded.TryTake(out var proceeded)) { //ツイートの取得時刻を保存する(たぶん) //ここで(前回のConnectStreamers()で)REST取得したやつとUser streamに接続済みのやつだけ処理する(もうないけど) if (proceeded.LastReceivedTweetId != 0 || proceeded.NeedConnect() == UserStreamer.NeedConnectResult.StreamConnected) { LastCrawlTime[proceeded.Token.UserId] = proceeded.LastMessageTime.ToUnixTimeSeconds(); } } var StoreLastCrawlTimeTask = db.StoreCrawlInfo_Timeline(LastCrawlTime); Counter.PrintReset(); //TLがある程度進んでいるアカウントと一定時間取得してないアカウントだけ接続する var now = DateTimeOffset.UtcNow; var StreamersSelected = Streamers.Select(s => s.Value) .Where(s => s.EstimatedTweetToReceive >= config.crawl.StreamSpeedTweets || (now - s.LastMessageTime).TotalSeconds > config.crawl.MaxRestInterval) .OrderByDescending(s => s.EstimatedTweetToReceive) .ThenBy(s => s.LastMessageTime) .ToArray(); // UserStreamerの再接続やRESTによるツイート取得などを行うやつ var ConnectBlock = new ActionBlock <UserStreamer>(async(Streamer) => { try { var NeedConnect = Streamer.NeedConnect(); //VerifyCredentialsを実行して成功したらtrue bool RevokeCheckSuccess = false; //初回とRevoke疑いのときだけVerifyCredentials()する //プロフィールを取得したい if (NeedConnect == UserStreamer.NeedConnectResult.Postponed) { return; } else if (NeedConnect == UserStreamer.NeedConnectResult.First || RevokeRetryUserID.ContainsKey(Streamer.Token.UserId)) { switch (await Streamer.VerifyCredentials().ConfigureAwait(false)) { case UserStreamer.TokenStatus.Locked: Streamer.PostponeConnect(); return; case UserStreamer.TokenStatus.Revoked: MarkRevoked(Streamer); return; case UserStreamer.TokenStatus.Failure: return; case UserStreamer.TokenStatus.Success: RevokeCheckSuccess = true; break; } } //Streamに接続したりRESTだけにしたり if (NeedConnect == UserStreamer.NeedConnectResult.StreamConnected) { if (Streamer.NeedStreamSpeed() == UserStreamer.NeedStreamResult.RestOnly) { Streamer.DisconnectStream(); return; } else { Counter.ActiveStreamers.Increment(); } } else { //TLの速度を測定して必要ならStreamに接続 Counter.RestedStreamers.Increment(); switch (await Streamer.RestTimeline().ConfigureAwait(false)) { case UserStreamer.TokenStatus.Revoked: //長期間ログインされていないアカウントはVerifyCredentialsしか通らなくなるっぽい //またVerifyCredentialsに失敗するまで放っとく if (RevokeCheckSuccess) { Streamer.PostponeConnect(); } //普通のRevoke疑い else { MarkRevoked(Streamer); } break; case UserStreamer.TokenStatus.Locked: if (RevokeCheckSuccess) { UnmarkRevoked(Streamer); } Streamer.PostponeConnect(); break; default: if (RevokeCheckSuccess) { UnmarkRevoked(Streamer); } UserStreamer.NeedStreamResult NeedStream = Streamer.NeedStreamSpeed(); if (NeedStream == UserStreamer.NeedStreamResult.Stream) { Streamer.RecieveStream(); Counter.ActiveStreamers.Increment(); } //DBが求めていればToken読み込み直後だけ自分のツイートも取得(初回サインイン狙い if (Streamer.NeedRestMyTweet) { await Streamer.RestMyTweet().ConfigureAwait(false); //User streamに繋がない場合はこっちでフォローを取得する必要がある if (NeedStream != UserStreamer.NeedStreamResult.Stream) { await Streamer.RestFriend().ConfigureAwait(false); } await Streamer.RestBlock().ConfigureAwait(false); Streamer.NeedRestMyTweet = false; } break; } } ConnectBlockProceeeded.Add(Streamer); } catch (Exception e) { Console.WriteLine("ConnectBlock Faulted: {0}", e); } }, new ExecutionDataflowBlockOptions() { MaxDegreeOfParallelism = ConnectBlockConcurrency, BoundedCapacity = ConnectBlockConcurrency + 1, //これでもawaitする SingleProducerConstrained = true, }); var connectStopWatch = Stopwatch.StartNew(); bool connectTimedout = false; using (var cancel = new CancellationTokenSource(60000)) { try { foreach (var s in StreamersSelected) { if (!await ConnectBlock.SendAsync(s, cancel.Token).ConfigureAwait(false)) { break; } //そんなバナナ //ツイートが詰まってたら休む while (UserStreamerStatic.NeedConnectPostpone() && !cancel.Token.IsCancellationRequested) { await Task.Delay(1000, cancel.Token).ConfigureAwait(false); } } ConnectBlock.Complete(); await ConnectBlock.Completion.ConfigureAwait(false); } catch (TaskCanceledException) { ConnectBlock.Complete(); connectTimedout = true; } } //ConnectBlockの同時接続数を調整する connectStopWatch.Stop(); int oldConnectBlockConcurrency = ConnectBlockConcurrency; if (connectTimedout) { ConnectBlockConcurrency = config.crawl.MaxReconnectThreads; } else if (connectStopWatch.ElapsedMilliseconds < 40000) { ConnectBlockConcurrency = Math.Max(1, ConnectBlockConcurrency - 1); } else if (50000 < connectStopWatch.ElapsedMilliseconds) { ConnectBlockConcurrency = Math.Clamp((int)Math.Ceiling(ConnectBlockConcurrency * (connectStopWatch.ElapsedMilliseconds / 50000d)), 1, config.crawl.MaxReconnectThreads); } if (oldConnectBlockConcurrency != ConnectBlockConcurrency) { Console.WriteLine("ConnectBlockConcurrency: {0} -> {1}", oldConnectBlockConcurrency, ConnectBlockConcurrency); } await StoreLastCrawlTimeTask.ConfigureAwait(false); //Revoke後再試行にも失敗したTokenはここで消す await RemoveRevokedTokens().ConfigureAwait(false); //接続が全然進まないときは殺してもらう if (0 < Counter.ActiveStreamers.Get() + Counter.RestedStreamers.Get()) { await WatchDogUdp.SendAsync(BitConverter.GetBytes(ThisPid), sizeof(int), WatchDogEndPoint).ConfigureAwait(false); } UserStreamerStatic.ShowCount(); }