From cfa9a022cfd226c808b461b825d6f9d974378387 Mon Sep 17 00:00:00 2001 From: Kimura Youichi Date: Wed, 5 Jun 2024 09:35:06 +0900 Subject: [PATCH] =?UTF-8?q?ThumbnailInfo=E3=81=8B=E3=82=89=E7=94=BB?= =?UTF-8?q?=E5=83=8F=E3=83=80=E3=82=A6=E3=83=B3=E3=83=AD=E3=83=BC=E3=83=89?= =?UTF-8?q?=E5=87=A6=E7=90=86=E3=82=92=E5=88=86=E9=9B=A2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Thumbnail/Services/ImgAzyobuziNetTest.cs | 2 +- .../Services/MetaThumbnailServiceTest.cs | 14 ++-- .../Services/SimpleThumbnailServiceTest.cs | 2 +- .../Thumbnail/Services/TonTwitterComTest.cs | 12 ++-- .../Thumbnail/Services/TumblrTest.cs | 7 +- OpenTween.Tests/TweetThumbnailTest.cs | 41 +++++++----- OpenTween/Thumbnail/IThumbnailLoader.cs | 34 ++++++++++ OpenTween/Thumbnail/MapThumbGoogle.cs | 10 ++- OpenTween/Thumbnail/MapThumbOSM.cs | 13 ++-- .../Thumbnail/Services/ImgAzyobuziNet.cs | 5 +- .../Services/MetaThumbnailService.cs | 7 +- OpenTween/Thumbnail/Services/Nicovideo.cs | 4 +- OpenTween/Thumbnail/Services/PbsTwimgCom.cs | 6 +- OpenTween/Thumbnail/Services/Pixiv.cs | 24 ++++--- .../Services/SimpleThumbnailService.cs | 5 +- OpenTween/Thumbnail/Services/Tinami.cs | 6 +- OpenTween/Thumbnail/Services/TonTwitterCom.cs | 22 ++++--- OpenTween/Thumbnail/Services/Tumblr.cs | 7 +- .../Thumbnail/Services/TwitterComVideo.cs | 12 ++-- OpenTween/Thumbnail/Services/Vimeo.cs | 4 +- OpenTween/Thumbnail/Services/Youtube.cs | 5 +- OpenTween/Thumbnail/SimpleThumbnailLoader.cs | 65 +++++++++++++++++++ OpenTween/Thumbnail/ThumbnailInfo.cs | 62 +++++------------- OpenTween/TweetThumbnail.cs | 2 +- 24 files changed, 211 insertions(+), 160 deletions(-) create mode 100644 OpenTween/Thumbnail/IThumbnailLoader.cs create mode 100644 OpenTween/Thumbnail/SimpleThumbnailLoader.cs diff --git a/OpenTween.Tests/Thumbnail/Services/ImgAzyobuziNetTest.cs b/OpenTween.Tests/Thumbnail/Services/ImgAzyobuziNetTest.cs index 45283518b..9535a7bf1 100644 --- a/OpenTween.Tests/Thumbnail/Services/ImgAzyobuziNetTest.cs +++ b/OpenTween.Tests/Thumbnail/Services/ImgAzyobuziNetTest.cs @@ -117,7 +117,7 @@ public async Task MatchTest() Assert.NotNull(thumbinfo); Assert.Equal("http://example.com/abcd", thumbinfo!.MediaPageUrl); Assert.Equal("http://img.azyobuzi.net/api/redirect?size=large&uri=http%3A%2F%2Fexample.com%2Fabcd", thumbinfo.ThumbnailImageUrl); - Assert.Null(thumbinfo.TooltipText); + Assert.Equal("", thumbinfo.TooltipText); } [Fact] diff --git a/OpenTween.Tests/Thumbnail/Services/MetaThumbnailServiceTest.cs b/OpenTween.Tests/Thumbnail/Services/MetaThumbnailServiceTest.cs index 5b458e5b0..fabd10e62 100644 --- a/OpenTween.Tests/Thumbnail/Services/MetaThumbnailServiceTest.cs +++ b/OpenTween.Tests/Thumbnail/Services/MetaThumbnailServiceTest.cs @@ -72,7 +72,7 @@ public async Task OGPMetaTest() Assert.NotNull(thumbinfo); Assert.Equal("http://example.com/abcd", thumbinfo!.MediaPageUrl); Assert.Equal("http://img.example.com/abcd", thumbinfo.ThumbnailImageUrl); - Assert.Null(thumbinfo.TooltipText); + Assert.Equal("", thumbinfo.TooltipText); } [Fact] @@ -94,7 +94,7 @@ public async Task TwitterMetaTest() Assert.NotNull(thumbinfo); Assert.Equal("http://example.com/abcd", thumbinfo!.MediaPageUrl); Assert.Equal("http://img.example.com/abcd", thumbinfo.ThumbnailImageUrl); - Assert.Null(thumbinfo.TooltipText); + Assert.Equal("", thumbinfo.TooltipText); } [Fact] @@ -116,7 +116,7 @@ public async Task InvalidMetaTest() Assert.NotNull(thumbinfo); Assert.Equal("http://example.com/abcd", thumbinfo!.MediaPageUrl); Assert.Equal("http://img.example.com/abcd", thumbinfo.ThumbnailImageUrl); - Assert.Null(thumbinfo.TooltipText); + Assert.Equal("", thumbinfo.TooltipText); } [Fact] @@ -138,7 +138,7 @@ public async Task ReverseMetaTest() Assert.NotNull(thumbinfo); Assert.Equal("http://example.com/abcd", thumbinfo!.MediaPageUrl); Assert.Equal("http://img.example.com/abcd", thumbinfo.ThumbnailImageUrl); - Assert.Null(thumbinfo.TooltipText); + Assert.Equal("", thumbinfo.TooltipText); } [Fact] @@ -161,7 +161,7 @@ public async Task BadMetaTest() Assert.NotNull(thumbinfo); Assert.Equal("http://example.com/abcd", thumbinfo!.MediaPageUrl); Assert.Equal("http://img.example.com/abcd", thumbinfo.ThumbnailImageUrl); - Assert.Null(thumbinfo.TooltipText); + Assert.Equal("", thumbinfo.TooltipText); } [Fact] @@ -183,7 +183,7 @@ public async Task BadMetaOneLineTest() Assert.NotNull(thumbinfo); Assert.Equal("http://example.com/abcd", thumbinfo!.MediaPageUrl); Assert.Equal("http://img.example.com/abcd", thumbinfo.ThumbnailImageUrl); - Assert.Null(thumbinfo.TooltipText); + Assert.Equal("", thumbinfo.TooltipText); } [Fact] @@ -205,7 +205,7 @@ public async Task ReverseMetaOneLineTest() Assert.NotNull(thumbinfo); Assert.Equal("http://example.com/abcd", thumbinfo!.MediaPageUrl); Assert.Equal("http://img.example.com/abcd", thumbinfo.ThumbnailImageUrl); - Assert.Null(thumbinfo.TooltipText); + Assert.Equal("", thumbinfo.TooltipText); } [Fact] diff --git a/OpenTween.Tests/Thumbnail/Services/SimpleThumbnailServiceTest.cs b/OpenTween.Tests/Thumbnail/Services/SimpleThumbnailServiceTest.cs index a2ef8a54b..c4b79456f 100644 --- a/OpenTween.Tests/Thumbnail/Services/SimpleThumbnailServiceTest.cs +++ b/OpenTween.Tests/Thumbnail/Services/SimpleThumbnailServiceTest.cs @@ -46,7 +46,7 @@ public async Task RegexMatchTest() Assert.NotNull(thumbinfo); Assert.Equal("http://example.com/abcd", thumbinfo!.MediaPageUrl); Assert.Equal("http://img.example.com/abcd", thumbinfo.ThumbnailImageUrl); - Assert.Null(thumbinfo.TooltipText); + Assert.Equal("", thumbinfo.TooltipText); } [Fact] diff --git a/OpenTween.Tests/Thumbnail/Services/TonTwitterComTest.cs b/OpenTween.Tests/Thumbnail/Services/TonTwitterComTest.cs index 235c9c823..418562ef2 100644 --- a/OpenTween.Tests/Thumbnail/Services/TonTwitterComTest.cs +++ b/OpenTween.Tests/Thumbnail/Services/TonTwitterComTest.cs @@ -119,14 +119,12 @@ public async Task LoadThumbnailImageAsync_Test() .ReturnsAsync(response); var apiConnection = mock.Object; - var thumb = new TonTwitterCom.Thumbnail(apiConnection) - { - MediaPageUrl = "https://ton.twitter.com/1.1/ton/data/dm/123456/123456/abcdef.jpg:large", - FullSizeImageUrl = "https://ton.twitter.com/1.1/ton/data/dm/123456/123456/abcdef.jpg:large", - ThumbnailImageUrl = "https://ton.twitter.com/1.1/ton/data/dm/123456/123456/abcdef.jpg", - }; + var thumbLoader = new TonTwitterCom.ThumbnailLoader( + apiConnection, + new("https://ton.twitter.com/1.1/ton/data/dm/123456/123456/abcdef.jpg") + ); - var result = await thumb.LoadThumbnailImageAsync(CancellationToken.None); + var result = await thumbLoader.Load(null!, CancellationToken.None); Assert.Equal(image, result); mock.VerifyAll(); diff --git a/OpenTween.Tests/Thumbnail/Services/TumblrTest.cs b/OpenTween.Tests/Thumbnail/Services/TumblrTest.cs index 45ab7a3e9..b7e474789 100644 --- a/OpenTween.Tests/Thumbnail/Services/TumblrTest.cs +++ b/OpenTween.Tests/Thumbnail/Services/TumblrTest.cs @@ -49,12 +49,7 @@ public void ParsePostJson_Test() var expected = new[] { - new ThumbnailInfo - { - MediaPageUrl = "http://example.com/post/1234567", - ThumbnailImageUrl = "http://example.com/photo/1280/1234567/1/tumblr_hogehoge", - TooltipText = null, - }, + new ThumbnailInfo("http://example.com/post/1234567", "http://example.com/photo/1280/1234567/1/tumblr_hogehoge"), }; Assert.Equal(expected, thumbs); } diff --git a/OpenTween.Tests/TweetThumbnailTest.cs b/OpenTween.Tests/TweetThumbnailTest.cs index 635f8e847..923a3e8b6 100644 --- a/OpenTween.Tests/TweetThumbnailTest.cs +++ b/OpenTween.Tests/TweetThumbnailTest.cs @@ -51,19 +51,17 @@ private IThumbnailService CreateThumbnailService() .Setup( x => x.GetThumbnailInfoAsync("http://example.com/abcd", It.IsAny(), It.IsAny()) ) - .ReturnsAsync(new MockThumbnailInfo + .ReturnsAsync(new ThumbnailInfo("http://example.com/abcd", "http://img.example.com/abcd.png") { - MediaPageUrl = "http://example.com/abcd", - ThumbnailImageUrl = "http://img.example.com/abcd.png", + Loader = new FakeThumbnailLoader(), }); thumbnailServiceMock .Setup( x => x.GetThumbnailInfoAsync("http://example.com/efgh", It.IsAny(), It.IsAny()) ) - .ReturnsAsync(new MockThumbnailInfo + .ReturnsAsync(new ThumbnailInfo("http://example.com/efgh", "http://img.example.com/efgh.png") { - MediaPageUrl = "http://example.com/efgh", - ThumbnailImageUrl = "http://img.example.com/efgh.png", + Loader = new FakeThumbnailLoader(), }); return thumbnailServiceMock.Object; } @@ -124,7 +122,10 @@ public async Task PrepareThumbnails_CancelTest() .Returns(async () => { await Task.Delay(200); - return new MockThumbnailInfo(); + return new ThumbnailInfo("http://slow.example.com/abcd", "http://slow.example.com/abcd") + { + Loader = new FakeThumbnailLoader(), + }; }); var thumbnailGenerator = this.CreateThumbnailGenerator(); @@ -151,10 +152,10 @@ public async Task PrepareThumbnails_CancelTest() public async Task LoadSelectedThumbnail_Test() { using var image = TestUtils.CreateDummyImage(); - var thumbnailInfoMock = new Mock() { CallBase = true }; - thumbnailInfoMock + var thumbnailLoaderMock = new Mock(); + thumbnailLoaderMock .Setup( - x => x.LoadThumbnailImageAsync(It.IsAny(), It.IsAny()) + x => x.Load(It.IsAny(), It.IsAny()) ) .ReturnsAsync(image); @@ -163,7 +164,10 @@ public async Task LoadSelectedThumbnail_Test() .Setup( x => x.GetThumbnailInfoAsync("http://example.com/abcd", It.IsAny(), It.IsAny()) ) - .ReturnsAsync(thumbnailInfoMock.Object); + .ReturnsAsync(new ThumbnailInfo("http://example.com/abcd", "http://example.com/abcd") + { + Loader = thumbnailLoaderMock.Object, + }); var thumbnailGenerator = this.CreateThumbnailGenerator(); thumbnailGenerator.Services.Add(thumbnailServiceMock.Object); @@ -187,10 +191,10 @@ public async Task LoadSelectedThumbnail_Test() public async Task LoadSelectedThumbnail_RequestCollapsingTest() { var tsc = new TaskCompletionSource(); - var thumbnailInfoMock = new Mock() { CallBase = true }; - thumbnailInfoMock + var thumbnailLoaderMock = new Mock(); + thumbnailLoaderMock .Setup( - x => x.LoadThumbnailImageAsync(It.IsAny(), It.IsAny()) + x => x.Load(It.IsAny(), It.IsAny()) ) .Returns(tsc.Task); @@ -199,7 +203,10 @@ public async Task LoadSelectedThumbnail_RequestCollapsingTest() .Setup( x => x.GetThumbnailInfoAsync("http://example.com/abcd", It.IsAny(), It.IsAny()) ) - .ReturnsAsync(thumbnailInfoMock.Object); + .ReturnsAsync(new ThumbnailInfo("http://example.com/abcd", "http://example.com/abcd") + { + Loader = thumbnailLoaderMock.Object, + }); var thumbnailGenerator = this.CreateThumbnailGenerator(); thumbnailGenerator.Services.Add(thumbnailServiceMock.Object); @@ -354,9 +361,9 @@ public async Task Scroll_Test() Assert.Equal(0, tweetThumbnail.SelectedIndex); } - private class MockThumbnailInfo : ThumbnailInfo + private class FakeThumbnailLoader : IThumbnailLoader { - public override Task LoadThumbnailImageAsync(HttpClient http, CancellationToken cancellationToken) + public Task Load(HttpClient http, CancellationToken cancellationToken) => Task.FromResult(TestUtils.CreateDummyImage()); } } diff --git a/OpenTween/Thumbnail/IThumbnailLoader.cs b/OpenTween/Thumbnail/IThumbnailLoader.cs new file mode 100644 index 000000000..1c5605385 --- /dev/null +++ b/OpenTween/Thumbnail/IThumbnailLoader.cs @@ -0,0 +1,34 @@ +// OpenTween - Client of Twitter +// Copyright (c) 2024 kim_upsilon (@kim_upsilon) +// All rights reserved. +// +// This file is part of OpenTween. +// +// This program is free software; you can redistribute it and/or modify it +// under the terms of the GNU General Public License as published by the Free +// Software Foundation; either version 3 of the License, or (at your option) +// any later version. +// +// This program is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY +// or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License +// for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program. If not, see , or write to +// the Free Software Foundation, Inc., 51 Franklin Street - Fifth Floor, +// Boston, MA 02110-1301, USA. + +#nullable enable + +using System.Net.Http; +using System.Threading; +using System.Threading.Tasks; + +namespace OpenTween.Thumbnail +{ + public interface IThumbnailLoader + { + public Task Load(HttpClient http, CancellationToken cancellationToken); + } +} diff --git a/OpenTween/Thumbnail/MapThumbGoogle.cs b/OpenTween/Thumbnail/MapThumbGoogle.cs index 11a393412..f929a9435 100644 --- a/OpenTween/Thumbnail/MapThumbGoogle.cs +++ b/OpenTween/Thumbnail/MapThumbGoogle.cs @@ -36,12 +36,10 @@ public class MapThumbGoogle : MapThumb { public override Task GetThumbnailInfoAsync(PostClass.StatusGeo geo) { - var thumb = new ThumbnailInfo - { - MediaPageUrl = this.CreateMapLinkUrl(geo.Latitude, geo.Longitude), - ThumbnailImageUrl = this.CreateStaticMapUrl(geo.Latitude, geo.Longitude), - TooltipText = null, - }; + var mapUrl = this.CreateMapLinkUrl(geo.Latitude, geo.Longitude); + var staticImageUrl = this.CreateStaticMapUrl(geo.Latitude, geo.Longitude); + + var thumb = new ThumbnailInfo(mapUrl, staticImageUrl); return Task.FromResult(thumb); } diff --git a/OpenTween/Thumbnail/MapThumbOSM.cs b/OpenTween/Thumbnail/MapThumbOSM.cs index e0a979b74..2e10f3799 100644 --- a/OpenTween/Thumbnail/MapThumbOSM.cs +++ b/OpenTween/Thumbnail/MapThumbOSM.cs @@ -41,12 +41,13 @@ public override Task GetThumbnailInfoAsync(PostClass.StatusGeo ge var size = new Size(SettingManager.Instance.Common.MapThumbnailWidth, SettingManager.Instance.Common.MapThumbnailHeight); var zoom = SettingManager.Instance.Common.MapThumbnailZoom; - var thumb = new OSMThumbnailInfo(geo.Latitude, geo.Longitude, zoom, size) + var mapUrl = this.CreateMapLinkUrl(geo.Latitude, geo.Longitude); + var thumb = new ThumbnailInfo(mapUrl, null) { - MediaPageUrl = this.CreateMapLinkUrl(geo.Latitude, geo.Longitude), + Loader = new OSMThumbnailLoader(geo.Latitude, geo.Longitude, zoom, size), }; - return Task.FromResult((ThumbnailInfo)thumb); + return Task.FromResult(thumb); } public string CreateMapLinkUrl(double latitude, double longitude) @@ -57,7 +58,7 @@ public string CreateMapLinkUrl(double latitude, double longitude) } } - public class OSMThumbnailInfo : ThumbnailInfo + public class OSMThumbnailLoader : IThumbnailLoader { /// openstreetmap.org タイルサーバー public static readonly string TileServerBase = "https://a.tile.openstreetmap.org"; @@ -77,7 +78,7 @@ public class OSMThumbnailInfo : ThumbnailInfo /// 生成するサムネイル画像のサイズ (ピクセル単位) public Size ThumbnailSize { get; } - public OSMThumbnailInfo(double latitude, double longitude, int zoom, Size thumbSize) + public OSMThumbnailLoader(double latitude, double longitude, int zoom, Size thumbSize) { this.Latitude = latitude; this.Longitude = longitude; @@ -85,7 +86,7 @@ public OSMThumbnailInfo(double latitude, double longitude, int zoom, Size thumbS this.ThumbnailSize = thumbSize; } - public override async Task LoadThumbnailImageAsync(HttpClient http, CancellationToken cancellationToken) + public async Task Load(HttpClient http, CancellationToken cancellationToken) { // 画像中央に描画されるタイル (ピクセル単位ではなくタイル番号を表す) // タイル番号に小数部が含まれているが、これはタイル内の相対的な位置を表すためこのまま保持する diff --git a/OpenTween/Thumbnail/Services/ImgAzyobuziNet.cs b/OpenTween/Thumbnail/Services/ImgAzyobuziNet.cs index 3b9757be3..8c616b909 100644 --- a/OpenTween/Thumbnail/Services/ImgAzyobuziNet.cs +++ b/OpenTween/Thumbnail/Services/ImgAzyobuziNet.cs @@ -215,12 +215,9 @@ protected virtual async Task FetchRegexAsync(string apiBase) { if (regex.IsMatch(url)) { - return new ThumbnailInfo + return new ThumbnailInfo(url, this.apiBase + "redirect?size=large&uri=" + Uri.EscapeDataString(url)) { - MediaPageUrl = url, - ThumbnailImageUrl = this.apiBase + "redirect?size=large&uri=" + Uri.EscapeDataString(url), FullSizeImageUrl = this.apiBase + "redirect?size=full&uri=" + Uri.EscapeDataString(url), - TooltipText = null, }; } } diff --git a/OpenTween/Thumbnail/Services/MetaThumbnailService.cs b/OpenTween/Thumbnail/Services/MetaThumbnailService.cs index d5efd3c52..712b0fba3 100644 --- a/OpenTween/Thumbnail/Services/MetaThumbnailService.cs +++ b/OpenTween/Thumbnail/Services/MetaThumbnailService.cs @@ -91,12 +91,7 @@ public MetaThumbnailService(HttpClient? http, string urlPattern, string[]? propN var thumbnailUrl = this.GetThumbnailUrl(content); if (MyCommon.IsNullOrEmpty(thumbnailUrl)) return null; - return new ThumbnailInfo - { - MediaPageUrl = url, - ThumbnailImageUrl = thumbnailUrl, - TooltipText = null, - }; + return new ThumbnailInfo(url, thumbnailUrl); } catch (HttpRequestException) { diff --git a/OpenTween/Thumbnail/Services/Nicovideo.cs b/OpenTween/Thumbnail/Services/Nicovideo.cs index ca6a51fdc..feefabab8 100644 --- a/OpenTween/Thumbnail/Services/Nicovideo.cs +++ b/OpenTween/Thumbnail/Services/Nicovideo.cs @@ -72,10 +72,8 @@ public class Nicovideo : IThumbnailService if (thumbUrlElement == null) return null; - return new ThumbnailInfo + return new ThumbnailInfo(url, thumbUrlElement.Value) { - MediaPageUrl = url, - ThumbnailImageUrl = thumbUrlElement.Value, TooltipText = BuildTooltip(thumbElement), IsPlayable = true, }; diff --git a/OpenTween/Thumbnail/Services/PbsTwimgCom.cs b/OpenTween/Thumbnail/Services/PbsTwimgCom.cs index c7c025a31..571897dbb 100644 --- a/OpenTween/Thumbnail/Services/PbsTwimgCom.cs +++ b/OpenTween/Thumbnail/Services/PbsTwimgCom.cs @@ -71,11 +71,9 @@ public class PbsTwimgCom : IThumbnailService var media = post.Media.FirstOrDefault(x => x.Url == url); var altText = media?.AltText; - var thumb = new ThumbnailInfo + var thumb = new ThumbnailInfo(mediaOrig, mediaLarge) { - MediaPageUrl = mediaOrig, - ThumbnailImageUrl = mediaLarge, - TooltipText = altText, + TooltipText = altText ?? "", FullSizeImageUrl = mediaOrig, }; diff --git a/OpenTween/Thumbnail/Services/Pixiv.cs b/OpenTween/Thumbnail/Services/Pixiv.cs index 0f6ea02d8..5c84323d7 100644 --- a/OpenTween/Thumbnail/Services/Pixiv.cs +++ b/OpenTween/Thumbnail/Services/Pixiv.cs @@ -57,23 +57,29 @@ public Pixiv(HttpClient? http) if (thumb == null) return null; - return new Pixiv.Thumbnail + return thumb with { - MediaPageUrl = thumb.MediaPageUrl, - ThumbnailImageUrl = thumb.ThumbnailImageUrl, - TooltipText = thumb.TooltipText, - FullSizeImageUrl = thumb.FullSizeImageUrl, + Loader = new ThumbnailLoader(new(thumb.MediaPageUrl), new(thumb.ThumbnailImageUrl)), }; } - public class Thumbnail : ThumbnailInfo + public class ThumbnailLoader : IThumbnailLoader { - public async override Task LoadThumbnailImageAsync(HttpClient http, CancellationToken cancellationToken) + private readonly Uri mediaPageUri; + private readonly Uri thumbnailImageUri; + + public ThumbnailLoader(Uri mediaPageUri, Uri thumbnailImageUri) + { + this.mediaPageUri = mediaPageUri; + this.thumbnailImageUri = thumbnailImageUri; + } + + public async Task Load(HttpClient http, CancellationToken cancellationToken) { - var request = new HttpRequestMessage(HttpMethod.Get, this.ThumbnailImageUrl); + var request = new HttpRequestMessage(HttpMethod.Get, this.thumbnailImageUri); request.Headers.Add("User-Agent", Networking.GetUserAgentString(fakeMSIE: true)); - request.Headers.Referrer = new Uri(this.MediaPageUrl); + request.Headers.Referrer = this.mediaPageUri; using var response = await http.SendAsync(request, cancellationToken) .ConfigureAwait(false); diff --git a/OpenTween/Thumbnail/Services/SimpleThumbnailService.cs b/OpenTween/Thumbnail/Services/SimpleThumbnailService.cs index ca509f110..adaaaff4b 100644 --- a/OpenTween/Thumbnail/Services/SimpleThumbnailService.cs +++ b/OpenTween/Thumbnail/Services/SimpleThumbnailService.cs @@ -66,11 +66,8 @@ public SimpleThumbnailService(Regex regex, string replacement, string? file_repl var thumbnailUrl = this.ReplaceUrl(url); if (thumbnailUrl == null) return null; - return new ThumbnailInfo + return new ThumbnailInfo(url, thumbnailUrl) { - MediaPageUrl = url, - ThumbnailImageUrl = thumbnailUrl, - TooltipText = null, FullSizeImageUrl = this.ReplaceUrl(url, this.fullsizeReplacement), }; }, diff --git a/OpenTween/Thumbnail/Services/Tinami.cs b/OpenTween/Thumbnail/Services/Tinami.cs index 4fbf1d447..0278d5aa7 100644 --- a/OpenTween/Thumbnail/Services/Tinami.cs +++ b/OpenTween/Thumbnail/Services/Tinami.cs @@ -84,11 +84,9 @@ public Tinami(ApiKey apiKey, HttpClient? http) var descElm = xdoc.XPathSelectElement("/rsp/content/description"); - return new ThumbnailInfo + return new ThumbnailInfo(url, thumbUrlElm.Attribute("url").Value) { - MediaPageUrl = url, - ThumbnailImageUrl = thumbUrlElm.Attribute("url").Value, - TooltipText = descElm?.Value, + TooltipText = descElm?.Value ?? "", }; } catch (HttpRequestException) diff --git a/OpenTween/Thumbnail/Services/TonTwitterCom.cs b/OpenTween/Thumbnail/Services/TonTwitterCom.cs index 4e55b97f6..928ed4fe0 100644 --- a/OpenTween/Thumbnail/Services/TonTwitterCom.cs +++ b/OpenTween/Thumbnail/Services/TonTwitterCom.cs @@ -44,7 +44,7 @@ public class TonTwitterCom : IThumbnailService public override Task GetThumbnailInfoAsync(string url, PostClass post, CancellationToken token) { - return Task.Run(() => + return Task.Run(() => { if (GetApiConnection == null) return null; @@ -55,31 +55,33 @@ public class TonTwitterCom : IThumbnailService var largeUrl = url + ":large"; var apiConnection = GetApiConnection(); - return new TonTwitterCom.Thumbnail(apiConnection) + return new ThumbnailInfo(largeUrl, url) { - MediaPageUrl = largeUrl, - ThumbnailImageUrl = url, - TooltipText = null, FullSizeImageUrl = largeUrl, + Loader = new ThumbnailLoader(apiConnection, new(url)), }; }, token); } - public class Thumbnail : ThumbnailInfo + public class ThumbnailLoader : IThumbnailLoader { private readonly IApiConnection apiConnection; + private readonly Uri imageUri; - public Thumbnail(IApiConnection apiConnection) - => this.apiConnection = apiConnection; + public ThumbnailLoader(IApiConnection apiConnection, Uri imageUri) + { + this.apiConnection = apiConnection; + this.imageUri = imageUri; + } - public override Task LoadThumbnailImageAsync(HttpClient http, CancellationToken cancellationToken) + public Task Load(HttpClient http, CancellationToken cancellationToken) { return Task.Run(async () => { var request = new GetRequest { - RequestUri = new(this.ThumbnailImageUrl), + RequestUri = this.imageUri, }; using var response = await this.apiConnection.SendAsync(request) diff --git a/OpenTween/Thumbnail/Services/Tumblr.cs b/OpenTween/Thumbnail/Services/Tumblr.cs index 4e1fb4cd1..3bfd42e91 100644 --- a/OpenTween/Thumbnail/Services/Tumblr.cs +++ b/OpenTween/Thumbnail/Services/Tumblr.cs @@ -117,12 +117,7 @@ internal static ThumbnailInfo[] ParsePhotoPostJson(byte[] jsonBytes) var thumbs = from photoElm in item.XPathSelectElements("photos/item/alt_sizes/item[1]/url") - select new ThumbnailInfo - { - MediaPageUrl = postUrlElm.Value, - ThumbnailImageUrl = photoElm.Value, - TooltipText = null, - }; + select new ThumbnailInfo(postUrlElm.Value, photoElm.Value); return thumbs.ToArray(); } diff --git a/OpenTween/Thumbnail/Services/TwitterComVideo.cs b/OpenTween/Thumbnail/Services/TwitterComVideo.cs index 881bd5d80..358a1428b 100644 --- a/OpenTween/Thumbnail/Services/TwitterComVideo.cs +++ b/OpenTween/Thumbnail/Services/TwitterComVideo.cs @@ -56,11 +56,9 @@ public TwitterComVideo(HttpClient? http) var mediaInfo = post.Media.FirstOrDefault(x => x.Url == url); if (mediaInfo?.VideoUrl != null) { - return new ThumbnailInfo + return new ThumbnailInfo(mediaInfo.VideoUrl, url) { - MediaPageUrl = mediaInfo.VideoUrl, - ThumbnailImageUrl = url, - TooltipText = mediaInfo.AltText, + TooltipText = mediaInfo.AltText ?? "", IsPlayable = true, }; } @@ -71,8 +69,10 @@ public TwitterComVideo(HttpClient? http) if (thumbInfo != null) { - thumbInfo.IsPlayable = true; - return thumbInfo; + return thumbInfo with + { + IsPlayable = true, + }; } return null; diff --git a/OpenTween/Thumbnail/Services/Vimeo.cs b/OpenTween/Thumbnail/Services/Vimeo.cs index 41fc33910..a8c5ea71f 100644 --- a/OpenTween/Thumbnail/Services/Vimeo.cs +++ b/OpenTween/Thumbnail/Services/Vimeo.cs @@ -88,10 +88,8 @@ public Vimeo(HttpClient? http) tooltipText = string.Format("{0} ({1:00}:{2:00})", titleElm.Value, minute, second); } - return new ThumbnailInfo + return new ThumbnailInfo(url, thumbUrlElm.Value) { - MediaPageUrl = url, - ThumbnailImageUrl = thumbUrlElm.Value, TooltipText = tooltipText, IsPlayable = true, }; diff --git a/OpenTween/Thumbnail/Services/Youtube.cs b/OpenTween/Thumbnail/Services/Youtube.cs index 414a72825..86a07ca22 100644 --- a/OpenTween/Thumbnail/Services/Youtube.cs +++ b/OpenTween/Thumbnail/Services/Youtube.cs @@ -53,11 +53,8 @@ public class Youtube : IThumbnailService var videoId = match.Groups["videoId"].Value; var imgUrl = "https://i.ytimg.com/vi/" + videoId + "/hqdefault.jpg"; - return new ThumbnailInfo + return new ThumbnailInfo(url, imgUrl) { - MediaPageUrl = url, - ThumbnailImageUrl = imgUrl, - TooltipText = null, IsPlayable = true, }; }, diff --git a/OpenTween/Thumbnail/SimpleThumbnailLoader.cs b/OpenTween/Thumbnail/SimpleThumbnailLoader.cs new file mode 100644 index 000000000..538758530 --- /dev/null +++ b/OpenTween/Thumbnail/SimpleThumbnailLoader.cs @@ -0,0 +1,65 @@ +// OpenTween - Client of Twitter +// Copyright (c) 2024 kim_upsilon (@kim_upsilon) +// All rights reserved. +// +// This file is part of OpenTween. +// +// This program is free software; you can redistribute it and/or modify it +// under the terms of the GNU General Public License as published by the Free +// Software Foundation; either version 3 of the License, or (at your option) +// any later version. +// +// This program is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY +// or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License +// for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program. If not, see , or write to +// the Free Software Foundation, Inc., 51 Franklin Street - Fifth Floor, +// Boston, MA 02110-1301, USA. + +#nullable enable + +using System; +using System.Net.Http; +using System.Threading; +using System.Threading.Tasks; + +namespace OpenTween.Thumbnail +{ + public class SimpleThumbnailLoader : IThumbnailLoader + { + private readonly string imageUrl; + + public SimpleThumbnailLoader(string imageUrl) + => this.imageUrl = imageUrl; + + public async Task Load(HttpClient http, CancellationToken cancellationToken) + { + MemoryImage? image = null; + try + { + using var response = await http.GetAsync(this.imageUrl, cancellationToken) + .ConfigureAwait(false); + + response.EnsureSuccessStatusCode(); + + using var imageStream = await response.Content.ReadAsStreamAsync() + .ConfigureAwait(false); + + image = await MemoryImage.CopyFromStreamAsync(imageStream) + .ConfigureAwait(false); + + cancellationToken.ThrowIfCancellationRequested(); + + return image; + } + catch (OperationCanceledException) + { + image?.Dispose(); + throw; + } + } + } +} diff --git a/OpenTween/Thumbnail/ThumbnailInfo.cs b/OpenTween/Thumbnail/ThumbnailInfo.cs index 4dfe2010f..1bd1115ae 100644 --- a/OpenTween/Thumbnail/ThumbnailInfo.cs +++ b/OpenTween/Thumbnail/ThumbnailInfo.cs @@ -22,48 +22,50 @@ #nullable enable using System; -using System.Collections.Generic; -using System.Linq; using System.Net.Http; -using System.Text; using System.Threading; using System.Threading.Tasks; using OpenTween.Connection; namespace OpenTween.Thumbnail { - public class ThumbnailInfo : IEquatable + public record ThumbnailInfo( + string MediaPageUrl, + string? ThumbnailImageUrl + ) { /// サムネイルとして表示するメディアが掲載されている URL /// /// 例えば Youtube のサムネイルの場合、動画そのものの URL ではなく /// https://www.youtube.com/watch?v=****** 形式の URL が含まれる /// - public string MediaPageUrl { get; set; } = ""; + public string MediaPageUrl { get; init; } = MediaPageUrl; /// サムネイルとして表示する画像の URL /// /// ここに含まれる URL は直接画像として表示可能である必要がある /// - public string ThumbnailImageUrl { get; set; } = ""; + public string? ThumbnailImageUrl { get; init; } = ThumbnailImageUrl; /// 最も高解像度な画像の URL /// /// サムネイルとしては不適だが、より高解像度な画像を表示する場面に使用できる /// URL があればここに含まれる /// - public string? FullSizeImageUrl { get; set; } + public string? FullSizeImageUrl { get; init; } /// ツールチップとして表示するテキスト /// /// サムネイル画像にマウスオーバーした際に表示されるテキスト /// - public string? TooltipText { get; set; } + public string TooltipText { get; init; } = ""; /// /// 対象となるメディアが動画や音声など再生可能なものであるか否か /// - public bool IsPlayable { get; set; } + public bool IsPlayable { get; init; } + + public IThumbnailLoader? Loader { get; init; } public Task LoadThumbnailImageAsync() => this.LoadThumbnailImageAsync(CancellationToken.None); @@ -71,45 +73,15 @@ public Task LoadThumbnailImageAsync() public Task LoadThumbnailImageAsync(CancellationToken cancellationToken) => this.LoadThumbnailImageAsync(Networking.Http, cancellationToken); - public async virtual Task LoadThumbnailImageAsync(HttpClient http, CancellationToken cancellationToken) + public async Task LoadThumbnailImageAsync(HttpClient http, CancellationToken cancellationToken) { - MemoryImage? image = null; - try - { - using var response = await http.GetAsync(this.ThumbnailImageUrl, cancellationToken) - .ConfigureAwait(false); - - response.EnsureSuccessStatusCode(); - - using var imageStream = await response.Content.ReadAsStreamAsync() - .ConfigureAwait(false); - - image = await MemoryImage.CopyFromStreamAsync(imageStream) - .ConfigureAwait(false); + IThumbnailLoader CreateLoader() + => new SimpleThumbnailLoader(this.ThumbnailImageUrl ?? throw new InvalidOperationException($"{nameof(this.ThumbnailImageUrl)} is not set")); - cancellationToken.ThrowIfCancellationRequested(); + var loader = this.Loader ?? CreateLoader(); - return image; - } - catch (OperationCanceledException) - { - image?.Dispose(); - throw; - } + return await loader.Load(http, cancellationToken) + .ConfigureAwait(false); } - - public override bool Equals(object? obj) - => this.Equals(obj as ThumbnailInfo); - - public bool Equals(ThumbnailInfo? other) - => other != null && - other.MediaPageUrl == this.MediaPageUrl && - other.ThumbnailImageUrl == this.ThumbnailImageUrl && - other.TooltipText == this.TooltipText && - other.FullSizeImageUrl == this.FullSizeImageUrl && - other.IsPlayable == this.IsPlayable; - - public override int GetHashCode() - => this.MediaPageUrl.GetHashCode() ^ this.ThumbnailImageUrl.GetHashCode(); } } diff --git a/OpenTween/TweetThumbnail.cs b/OpenTween/TweetThumbnail.cs index 90c26219a..8cf3b1cfc 100644 --- a/OpenTween/TweetThumbnail.cs +++ b/OpenTween/TweetThumbnail.cs @@ -113,7 +113,7 @@ public Task LoadSelectedThumbnail() } public string GetUrlForImageSearch() - => this.CurrentThumbnail.FullSizeImageUrl ?? this.CurrentThumbnail.ThumbnailImageUrl; + => this.CurrentThumbnail.FullSizeImageUrl ?? this.CurrentThumbnail.ThumbnailImageUrl ?? this.CurrentThumbnail.MediaPageUrl; public Uri GetImageSearchUriGoogle() {