OSDN Git Service

PostRequestクラスを追加
[opentween/open-tween.git] / OpenTween / TweetThumbnail.cs
1 // OpenTween - Client of Twitter
2 // Copyright (c) 2023 kim_upsilon (@kim_upsilon) <https://upsilo.net/~upsilon/>
3 // All rights reserved.
4 //
5 // This file is part of OpenTween.
6 //
7 // This program is free software; you can redistribute it and/or modify it
8 // under the terms of the GNU General Public License as published by the Free
9 // Software Foundation; either version 3 of the License, or (at your option)
10 // any later version.
11 //
12 // This program is distributed in the hope that it will be useful, but
13 // WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
14 // or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
15 // for more details.
16 //
17 // You should have received a copy of the GNU General Public License along
18 // with this program. If not, see <http://www.gnu.org/licenses/>, or write to
19 // the Free Software Foundation, Inc., 51 Franklin Street - Fifth Floor,
20 // Boston, MA 02110-1301, USA.
21
22 #nullable enable
23
24 using System;
25 using System.Collections.Generic;
26 using System.Linq;
27 using System.Text;
28 using System.Threading;
29 using System.Threading.Tasks;
30 using OpenTween.Models;
31 using OpenTween.Thumbnail;
32
33 namespace OpenTween
34 {
35     public class TweetThumbnail : NotifyPropertyChangedBase
36     {
37         private ThumbnailGenerator? thumbGenerator;
38         private bool thumbnailAvailable;
39         private PostId? currentPostId;
40         private ThumbnailInfo[] thumbnails = Array.Empty<ThumbnailInfo>();
41         private Task<MemoryImage>?[] loadImageTasks = Array.Empty<Task<MemoryImage>?>();
42         private int selectedIndex = 0;
43
44         public bool ThumbnailAvailable
45         {
46             get => this.thumbnailAvailable;
47             set => this.SetProperty(ref this.thumbnailAvailable, value);
48         }
49
50         public int SelectedIndex
51         {
52             get => this.selectedIndex;
53             set
54             {
55                 if (this.ThumbnailAvailable)
56                 {
57                     if (value < 0 || value >= this.Thumbnails.Length)
58                         throw new ArgumentOutOfRangeException(nameof(value));
59                 }
60                 else
61                 {
62                     if (value != 0)
63                         throw new ArgumentOutOfRangeException(nameof(value));
64                 }
65                 this.SetProperty(ref this.selectedIndex, value);
66             }
67         }
68
69         public ThumbnailInfo[] Thumbnails
70             => this.ThumbnailAvailable ? this.thumbnails : throw new InvalidOperationException("Thumbnail is not available.");
71
72         public ThumbnailInfo CurrentThumbnail
73             => this.Thumbnails[this.selectedIndex];
74
75         private ThumbnailGenerator ThumbGenerator
76             => this.thumbGenerator ?? throw this.NotInitializedException();
77
78         public void Initialize(ThumbnailGenerator thumbnailGenerator)
79             => this.thumbGenerator = thumbnailGenerator;
80
81         public async Task PrepareThumbnails(PostClass post, CancellationToken token)
82         {
83             if (this.currentPostId == post.StatusId)
84                 return;
85
86             this.currentPostId = post.StatusId;
87             this.ThumbnailAvailable = false;
88
89             var thumbnails = (await this.GetThumbailInfoAsync(post, token)).ToArray();
90
91             if (this.currentPostId != post.StatusId)
92                 return;
93
94             this.DisposeImages();
95             this.SelectedIndex = 0;
96             this.thumbnails = thumbnails;
97             this.loadImageTasks = new Task<MemoryImage>?[thumbnails.Length];
98
99             if (thumbnails.Length > 0)
100                 this.ThumbnailAvailable = true;
101         }
102
103         public Task<MemoryImage> LoadSelectedThumbnail()
104         {
105             var runningTask = this.loadImageTasks[this.selectedIndex];
106             if (runningTask != null)
107                 return runningTask;
108
109             var newTask = Task.Run(() => this.thumbnails[this.selectedIndex].LoadThumbnailImageAsync());
110             this.loadImageTasks[this.selectedIndex] = newTask;
111
112             return newTask;
113         }
114
115         public string GetUrlForImageSearch()
116             => this.CurrentThumbnail.FullSizeImageUrl ?? this.CurrentThumbnail.ThumbnailImageUrl;
117
118         public Uri GetImageSearchUriGoogle()
119         {
120             var imageUrl = this.GetUrlForImageSearch();
121             return new(@"https://lens.google.com/uploadbyurl?url=" + Uri.EscapeDataString(imageUrl));
122         }
123
124         public Uri GetImageSearchUriSauceNao()
125         {
126             var imageUrl = this.GetUrlForImageSearch();
127             return new(@"https://saucenao.com/search.php?url=" + Uri.EscapeDataString(imageUrl));
128         }
129
130         public void ScrollUp()
131         {
132             if (!this.ThumbnailAvailable)
133                 return;
134             if (this.SelectedIndex == 0)
135                 return;
136
137             this.SelectedIndex--;
138         }
139
140         public void ScrollDown()
141         {
142             if (!this.ThumbnailAvailable)
143                 return;
144             if (this.SelectedIndex == this.Thumbnails.Length - 1)
145                 return;
146
147             this.SelectedIndex++;
148         }
149
150         private Exception NotInitializedException()
151             => new InvalidOperationException("Cannot call before initialization");
152
153         private async Task<IEnumerable<ThumbnailInfo>> GetThumbailInfoAsync(PostClass post, CancellationToken token)
154             => await Task.Run(() => this.ThumbGenerator.GetThumbnailsAsync(post, token));
155
156         private void DisposeImages()
157         {
158             var oldImageTasks = this.loadImageTasks.OfType<Task<MemoryImage>>().ToArray();
159
160             static async Task DisposeTaskResults(Task<MemoryImage>[] tasks)
161             {
162                 try
163                 {
164                     await Task.WhenAll(tasks).ConfigureAwait(false);
165                 }
166                 catch
167                 {
168                 }
169
170                 foreach (var task in tasks)
171                 {
172                     if (task.IsFaulted || task.IsCanceled)
173                         continue;
174
175                     task.Result.Dispose();
176                 }
177             }
178
179             _ = DisposeTaskResults(oldImageTasks);
180         }
181     }
182 }