///<summary>Viewに使うアイコンのURLを返す(ホスト名より後だけ) ///user_idとprofile_image_urlが必要</summary> public static string ProfileImageUrl(TweetData._user User, bool IsDefaultProfileImage) { if (User.local_profile_image_url == null) { return(null); } if (IsDefaultProfileImage) { return("/twimg/profile_image/" + '_' + Path.GetFileName(User.local_profile_image_url)); } else { return("/twimg/profile_image/" + User.user_id.ToString() + Path.GetExtension(User.local_profile_image_url)); } }
/// <summary> /// Userテーブルを取得してオブジェクトに詰めるだけのやっつけメソッド /// cmd次第で何アカウント分でも取得できる /// </summary> /// <param name="cmd">GetUsersHeadで始まるSQL文を含むMySqlCommand</param> /// <returns></returns> async Task <TweetData._user[]> GetUsers(MySqlCommand cmd) { var users = new List <TweetData._user>(); await ExecuteReader(cmd, (r) => { var tmpuser = new TweetData._user() { user_id = r.GetInt64(0), name = r.GetString(1), screen_name = r.GetString(2), isprotected = r.GetBoolean(3), local_profile_image_url = r.GetString(4), is_default_profile_image = r.GetBoolean(5), location = r.IsDBNull(6) ? null : r.GetString(6), description_html = r.IsDBNull(7) ? null : LocalText.TextToLink(r.GetString(7)) }; tmpuser.local_profile_image_url = LocalText.ProfileImageUrl(tmpuser, r.GetBoolean(5)); users.Add(tmpuser); }).ConfigureAwait(false); return(users.ToArray()); }
const int MultipleMediaOffset = 3; //複画は今のところ4枚まで これを同ページに収めたいマン /// <summary> /// 指定したユーザーのタイムライン上のツイートをずらりと /// 鍵かつフォロー外なら何も出ない /// </summary> /// <param name="target_user_id"></param> /// <param name="login_user_id"></param> /// <param name="LastTweet"></param> /// <param name="TweetCount">最大件数(複画がラストに来ると増える)</param> /// <param name="SimilarLimit">類似画像の枚数上限(古い順)</param> /// <param name="GetRetweet">RTを含める</param> /// <param name="ShowNoDup">類似画像がない</param> /// <param name="Before">true→LastTweetより古いツイートを検索する/false→(同文)新しいツイート(同文)</param> /// <returns></returns> public async Task <SimilarMediaTweet[]> SimilarMediaTimeline(long target_user_id, long?login_user_id, long LastTweet, int TweetCount, int SimilarLimit, bool GetRetweet, bool ShowNoDup, bool Before) { //鍵垢のTLはフォローしてない限り表示しない //未登録のアカウントもここで弾かれる TweetData._user TargetUserInfo = await SelectUser(target_user_id).ConfigureAwait(false); if (TargetUserInfo != null && TargetUserInfo.isprotected && login_user_id != target_user_id) { if (login_user_id == null) { return(new SimilarMediaTweet[0]); } using (MySqlCommand cmd = new MySqlCommand(@"SELECT COUNT(*) FROM friend WHERE user_id = @login_user_id AND friend_id = @target_user_id")) { cmd.Parameters.Add("@login_user_id", MySqlDbType.Int64).Value = login_user_id; cmd.Parameters.Add("@target_user_id", MySqlDbType.Int64).Value = target_user_id; switch (await SelectCount(cmd).ConfigureAwait(false)) { case 0: return(new SimilarMediaTweet[0]); case -1: throw new Exception("SelectCount() failed."); //それ以外は↓の処理を続行する } } } const long QueryRangeSnowFlake = 90 * 1000 * SnowFlake.msinSnowFlake; long QuerySnowFlake = LastTweet; long NowSnowFlake = SnowFlake.Now(true); long NoTweetSnowFlake = 0; const long NoTweetLimitSnowFlake = 86400 * 1000 * SnowFlake.msinSnowFlake; const int GiveupMilliSeconds = 5000; Stopwatch sw = new Stopwatch(); sw.Start(); CancellationTokenSource CancelToken = new CancellationTokenSource(); ExecutionDataflowBlockOptions op = new ExecutionDataflowBlockOptions() { CancellationToken = CancelToken.Token, MaxDegreeOfParallelism = Environment.ProcessorCount, SingleProducerConstrained = true }; string QueryText; if (GetRetweet) { QueryText = SimilarMediaHeadRT + @" FROM friend f JOIN user ou ON f.friend_id = ou.user_id JOIN tweet o USE INDEX (PRIMARY) ON ou.user_id = o.user_id LEFT JOIN tweet rt ON o.retweet_id = rt.tweet_id LEFT JOIN user ru ON rt.user_id = ru.user_id JOIN tweet_media t ON COALESCE(rt.tweet_id, o.tweet_id) = t.tweet_id JOIN media m ON t.media_id = m.media_id LEFT JOIN tweet_text ot ON o.tweet_id = ot.tweet_id LEFT JOIN tweet_text rtt ON rt.tweet_id = rtt.tweet_id LEFT JOIN media_text mt ON m.media_id = mt.media_id WHERE " + (ShowNoDup ? "" : @"( EXISTS (SELECT * FROM media WHERE dcthash = m.dcthash AND media_id != m.media_id) OR EXISTS (SELECT * FROM dcthashpairslim WHERE hash_small = m.dcthash) OR EXISTS (SELECT * FROM dcthashpairslim WHERE hash_large = m.dcthash) ) AND") + @" o.tweet_id BETWEEN " + (Before ? "@time - @timerange AND @time" : "@time AND @time + @timerange") + @" AND f.user_id = @target_user_id AND (@login_user_id = @target_user_id OR ou.isprotected = 0 OR ou.user_id = @login_user_id OR EXISTS (SELECT * FROM friend WHERE user_id = @login_user_id AND friend_id = ou.user_id) ) AND NOT EXISTS (SELECT * FROM block WHERE user_id = @login_user_id AND target_id = rt.user_id) AND NOT EXISTS( SELECT * FROM friend fs JOIN user ous ON fs.user_id = ous.user_id JOIN tweet os ON ous.user_id = os.user_id WHERE os.retweet_id = rt.tweet_id AND fs.user_id = @target_user_id AND (@login_user_id = @target_user_id OR (ous.isprotected = 0 OR ous.user_id = @login_user_id OR EXISTS (SELECT * FROM friend WHERE user_id = @login_user_id AND friend_id = ous.user_id)) ) AND o.tweet_id < os.tweet_id ) ORDER BY o.tweet_id " + (Before ? "DESC" : "ASC") + " LIMIT @limitplus;"; } else { QueryText = SimilarMediaHeadnoRT + @" FROM friend f JOIN user ou ON f.friend_id = ou.user_id JOIN tweet o USE INDEX (PRIMARY) ON ou.user_id = o.user_id JOIN tweet_media t ON o.tweet_id = t.tweet_id JOIN media m ON t.media_id = m.media_id LEFT JOIN tweet_text ot ON o.tweet_id = ot.tweet_id LEFT JOIN media_text mt ON m.media_id = mt.media_id WHERE " + (ShowNoDup ? "" : @"( EXISTS (SELECT * FROM media WHERE dcthash = m.dcthash AND media_id != m.media_id) OR EXISTS (SELECT * FROM dcthashpairslim WHERE hash_small = m.dcthash) OR EXISTS (SELECT * FROM dcthashpairslim WHERE hash_large = m.dcthash) ) AND") + @" f.user_id = @target_user_id AND o.tweet_id BETWEEN " + (Before ? "@time - @timerange AND @time" : "@time AND @time + @timerange") + @" AND o.retweet_id IS NULL AND (@login_user_id = @target_user_id OR ou.user_id = @login_user_id OR ou.isprotected = 0 OR EXISTS (SELECT * FROM friend WHERE user_id = @login_user_id AND friend_id = ou.user_id) ) ORDER BY o.tweet_id " + (Before ? "DESC" : "ASC") + " LIMIT @limitplus;"; } var GetTimelineBlock = new TransformBlock <int, SimilarMediaTweet[]>( async(int i) => { using (MySqlCommand cmd = new MySqlCommand(QueryText)) { cmd.Parameters.Add("@target_user_id", MySqlDbType.Int64).Value = target_user_id; cmd.Parameters.Add("@login_user_id", MySqlDbType.Int64).Value = login_user_id; cmd.Parameters.Add("@time", MySqlDbType.Int64).Value = (Before ? QuerySnowFlake - QueryRangeSnowFlake * i : QuerySnowFlake + QueryRangeSnowFlake * i); cmd.Parameters.Add("@timerange", MySqlDbType.Int64).Value = QueryRangeSnowFlake; //類似画像が表示できない画像を弾くときだけ多めに取得する cmd.Parameters.Add("@limitplus", MySqlDbType.Int64).Value = ShowNoDup ? TweetCount : TweetCount + MultipleMediaOffset; return(await TableToTweet(cmd, login_user_id, SimilarLimit, ShowNoDup).ConfigureAwait(false)); } }, op); int PostedCount = 0; for (; PostedCount <= Environment.ProcessorCount; PostedCount++) { GetTimelineBlock.Post(PostedCount); } var ret = new List <SimilarMediaTweet>(); int RecievedCount = 0; do { var Tweets = await GetTimelineBlock.ReceiveAsync().ConfigureAwait(false); RecievedCount++; //ツイートがない期間が続いたら打ち切る if (Tweets.Length > 0) { NoTweetSnowFlake = 0; } else { NoTweetSnowFlake += QueryRangeSnowFlake; } //必要数が取得できたら打ち切る 複画の分だけ多めに取得する foreach (var tweet in Tweets) { ret.Add(tweet); if (ret.Count >= TweetCount + MultipleMediaOffset) { break; } } if (Before || QuerySnowFlake + QueryRangeSnowFlake * (PostedCount - 1) < NowSnowFlake) //未来は取得しない { GetTimelineBlock.Post(PostedCount); PostedCount++; } } while (PostedCount > RecievedCount && (ret.Count < TweetCount) && NoTweetSnowFlake < NoTweetLimitSnowFlake && sw.ElapsedMilliseconds < GiveupMilliSeconds); CancelToken.Cancel(); if (!Before) { ret.Reverse(); } //TableToTweetで類似画像が表示できないやつが削られるので //多めに拾ってきて溢れた分を捨てる //あと複画は件数超えても同ページに入れる for (int i = TweetCount; i < ret.Count; i++) { if (ret[i].tweet.tweet_id != ret[TweetCount - 1].tweet.tweet_id) { return(ret.Take(i - 1).ToArray()); } } return(ret.ToArray()); }