• R/O
  • HTTP
  • SSH
  • HTTPS

open-tween: コミット

開発に使用するリポジトリ


コミットメタ情報

リビジョンdf79bfd8ca1177e3b166684f7f106e691cd69959 (tree)
日時2014-04-13 23:22:45
作者Kimura Youichi <kim.upsilon@bucy...>
コミッターKimura Youichi

ログメッセージ

Merge branch 'dotnet4.5.1'

変更サマリ

差分

--- a/.travis.yml
+++ b/.travis.yml
@@ -5,7 +5,7 @@ cache:
55 - monopkg/
66
77 env:
8- - MONO_VERSION=3.2.4
8+ - MONO_VERSION=3.2.7
99
1010 install:
1111 - mkdir -p monopkg
@@ -18,5 +18,4 @@ script:
1818 # Build
1919 - xbuild /verbosity:quiet
2020 # Run Tests
21- - if ! grep -q 'Version 11.00' OpenTween.sln; then echo 'OpenTween.sln is not compatible with Visual C# 2010 Express.'; false; fi
2221 - mono ./OpenTween.Tests/dlls/xunit.console.clr4.x86.exe ./OpenTween.Tests/OpenTween.Tests.xunit
--- a/OpenTween.Tests/OpenTween.Tests.csproj
+++ b/OpenTween.Tests/OpenTween.Tests.csproj
@@ -1,5 +1,5 @@
11 <?xml version="1.0" encoding="utf-8"?>
2-<Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
2+<Project ToolsVersion="12.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
33 <PropertyGroup>
44 <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
55 <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
@@ -10,8 +10,10 @@
1010 <AppDesignerFolder>Properties</AppDesignerFolder>
1111 <RootNamespace>OpenTween</RootNamespace>
1212 <AssemblyName>OpenTween.Tests</AssemblyName>
13- <TargetFrameworkVersion>v4.0</TargetFrameworkVersion>
13+ <TargetFrameworkVersion Condition="'$(OS)' == 'Windows_NT'">v4.5.1</TargetFrameworkVersion>
14+ <TargetFrameworkVersion Condition="'$(OS)' != 'Windows_NT'">v4.5</TargetFrameworkVersion><!-- Mono -->
1415 <FileAlignment>512</FileAlignment>
16+ <TargetFrameworkProfile />
1517 </PropertyGroup>
1618 <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
1719 <DebugSymbols>true</DebugSymbols>
@@ -21,6 +23,7 @@
2123 <DefineConstants>DEBUG;TRACE</DefineConstants>
2224 <ErrorReport>prompt</ErrorReport>
2325 <WarningLevel>4</WarningLevel>
26+ <Prefer32Bit>false</Prefer32Bit>
2427 </PropertyGroup>
2528 <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
2629 <DebugType>pdbonly</DebugType>
@@ -29,6 +32,7 @@
2932 <DefineConstants>TRACE</DefineConstants>
3033 <ErrorReport>prompt</ErrorReport>
3134 <WarningLevel>4</WarningLevel>
35+ <Prefer32Bit>false</Prefer32Bit>
3236 </PropertyGroup>
3337 <ItemGroup>
3438 <Reference Include="NSubstitute, Version=1.4.3.0, Culture=neutral, PublicKeyToken=92dd2e9066daa5ca, processorArchitecture=MSIL">
@@ -38,6 +42,7 @@
3842 <Reference Include="System" />
3943 <Reference Include="System.Core" />
4044 <Reference Include="System.Drawing" />
45+ <Reference Include="System.Net.Http" />
4146 <Reference Include="System.Runtime.Serialization" />
4247 <Reference Include="System.Windows.Forms" />
4348 <Reference Include="System.Xml.Linq" />
@@ -69,6 +74,8 @@
6974 <Compile Include="PostClassTest.cs" />
7075 <Compile Include="PostFilterRuleVersion113DeserializeTest.cs" />
7176 <Compile Include="Properties\AssemblyInfo.cs" />
77+ <Compile Include="RegexAsyncTest.cs" />
78+ <Compile Include="ShortUrlTest.cs" />
7279 <Compile Include="TabInformationTest.cs" />
7380 <Compile Include="TabsDialogTest.cs" />
7481 <Compile Include="TestUtils.cs" />
--- /dev/null
+++ b/OpenTween.Tests/RegexAsyncTest.cs
@@ -0,0 +1,46 @@
1+// OpenTween - Client of Twitter
2+// Copyright (c) 2014 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+using System;
23+using System.Collections.Generic;
24+using System.Linq;
25+using System.Text;
26+using System.Text.RegularExpressions;
27+using System.Threading.Tasks;
28+using Xunit;
29+using Xunit.Extensions;
30+
31+namespace OpenTween
32+{
33+ public class RegexAsyncTest
34+ {
35+ [Fact]
36+ public async Task ReplaceAsync_Test()
37+ {
38+ var origText = "123aaa456bbb";
39+
40+ var regex = new Regex(@"[A-Za-z]");
41+ var result = await regex.ReplaceAsync(origText, x => Task.FromResult(x.Value.ToUpper()));
42+
43+ Assert.Equal("123AAA456BBB", result);
44+ }
45+ }
46+}
--- /dev/null
+++ b/OpenTween.Tests/ShortUrlTest.cs
@@ -0,0 +1,296 @@
1+// OpenTween - Client of Twitter
2+// Copyright (c) 2014 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+using System;
23+using System.Collections.Generic;
24+using System.Linq;
25+using System.Net;
26+using System.Net.Http;
27+using System.Text;
28+using System.Threading;
29+using System.Threading.Tasks;
30+using Xunit;
31+using Xunit.Extensions;
32+
33+namespace OpenTween
34+{
35+ public class ShortUrlTest
36+ {
37+ class HttpMessageHandlerMock : HttpMessageHandler
38+ {
39+ public readonly Queue<Func<HttpRequestMessage, Task<HttpResponseMessage>>> Queue =
40+ new Queue<Func<HttpRequestMessage, Task<HttpResponseMessage>>>();
41+
42+ protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
43+ {
44+ var handler = this.Queue.Dequeue();
45+ return handler(request);
46+ }
47+ }
48+
49+#pragma warning disable 1998 // awaitが無いasyncラムダ式に対する警告を抑制
50+
51+ [Fact]
52+ public async Task ExpandUrlAsync_Test()
53+ {
54+ var handler = new HttpMessageHandlerMock();
55+ var shortUrl = new ShortUrl(new HttpClient(handler));
56+
57+ // http://t.co/hoge1 -> http://example.com/hoge2
58+ handler.Queue.Enqueue(async x =>
59+ {
60+ Assert.Equal(HttpMethod.Head, x.Method);
61+ Assert.Equal(new Uri("http://t.co/hoge1"), x.RequestUri);
62+
63+ return this.CreateRedirectResponse("http://example.com/hoge2");
64+ });
65+
66+ Assert.Equal(new Uri("http://example.com/hoge2"),
67+ await shortUrl.ExpandUrlAsync(new Uri("http://t.co/hoge1")));
68+ }
69+
70+ [Fact]
71+ public async Task ExpandUrlAsync_DisableExpandingTest()
72+ {
73+ var handler = new HttpMessageHandlerMock();
74+ var shortUrl = new ShortUrl(new HttpClient(handler));
75+
76+ shortUrl.DisableExpanding = true;
77+
78+ // http://t.co/hoge1 -> http://example.com/hoge2
79+ handler.Queue.Enqueue(async x =>
80+ {
81+ // このリクエストは実行されないはず
82+ Assert.True(false);
83+ return this.CreateRedirectResponse("http://example.com/hoge2");
84+ });
85+
86+ Assert.Equal(new Uri("http://t.co/hoge1"),
87+ await shortUrl.ExpandUrlAsync(new Uri("http://t.co/hoge1")));
88+ }
89+
90+ [Fact]
91+ public async Task ExpandUrlAsync_RecursiveTest()
92+ {
93+ var handler = new HttpMessageHandlerMock();
94+ var shortUrl = new ShortUrl(new HttpClient(handler));
95+
96+ // http://t.co/hoge1 -> http://bit.ly/hoge2
97+ handler.Queue.Enqueue(async x =>
98+ {
99+ Assert.Equal(HttpMethod.Head, x.Method);
100+ Assert.Equal(new Uri("http://t.co/hoge1"), x.RequestUri);
101+
102+ return this.CreateRedirectResponse("http://bit.ly/hoge2");
103+ });
104+
105+ // http://bit.ly/hoge2 -> http://example.com/hoge3
106+ handler.Queue.Enqueue(async x =>
107+ {
108+ Assert.Equal(HttpMethod.Head, x.Method);
109+ Assert.Equal(new Uri("http://bit.ly/hoge2"), x.RequestUri);
110+
111+ return this.CreateRedirectResponse("http://example.com/hoge3");
112+ });
113+
114+ Assert.Equal(new Uri("http://example.com/hoge3"),
115+ await shortUrl.ExpandUrlAsync(new Uri("http://t.co/hoge1")));
116+ }
117+
118+ [Fact]
119+ public async Task ExpandUrlAsync_RecursiveLimitTest()
120+ {
121+ var handler = new HttpMessageHandlerMock();
122+ var shortUrl = new ShortUrl(new HttpClient(handler));
123+
124+ // http://t.co/hoge1 -> http://bit.ly/hoge2
125+ handler.Queue.Enqueue(async x =>
126+ {
127+ Assert.Equal(HttpMethod.Head, x.Method);
128+ Assert.Equal(new Uri("http://t.co/hoge1"), x.RequestUri);
129+
130+ return this.CreateRedirectResponse("http://bit.ly/hoge2");
131+ });
132+
133+ // http://bit.ly/hoge2 -> http://tinyurl.com/hoge3
134+ handler.Queue.Enqueue(async x =>
135+ {
136+ Assert.Equal(HttpMethod.Head, x.Method);
137+ Assert.Equal(new Uri("http://bit.ly/hoge2"), x.RequestUri);
138+
139+ return this.CreateRedirectResponse("http://tinyurl.com/hoge3");
140+ });
141+
142+ // http://tinyurl.com/hoge3 -> http://example.com/hoge4
143+ handler.Queue.Enqueue(async x =>
144+ {
145+ // このリクエストは実行されないはず
146+ Assert.True(false);
147+ return this.CreateRedirectResponse("http://example.com/hoge4");
148+ });
149+
150+ Assert.Equal(new Uri("http://tinyurl.com/hoge3"),
151+ await shortUrl.ExpandUrlAsync(new Uri("http://t.co/hoge1"), redirectLimit: 2));
152+ }
153+
154+ [Fact]
155+ public async Task ExpandUrlStrAsync_Test()
156+ {
157+ var handler = new HttpMessageHandlerMock();
158+ var shortUrl = new ShortUrl(new HttpClient(handler));
159+
160+ // http://t.co/hoge1 -> http://example.com/hoge2
161+ handler.Queue.Enqueue(async x =>
162+ {
163+ Assert.Equal(HttpMethod.Head, x.Method);
164+ Assert.Equal(new Uri("http://t.co/hoge1"), x.RequestUri);
165+
166+ return this.CreateRedirectResponse("http://example.com/hoge2");
167+ });
168+
169+ Assert.Equal("http://example.com/hoge2",
170+ await shortUrl.ExpandUrlStrAsync("http://t.co/hoge1"));
171+ }
172+
173+ [Fact]
174+ public async Task ExpandUrlStrAsync_SchemeLessUrlTest()
175+ {
176+ var handler = new HttpMessageHandlerMock();
177+ var shortUrl = new ShortUrl(new HttpClient(handler));
178+
179+ // http://t.co/hoge1 -> http://example.com/hoge2
180+ handler.Queue.Enqueue(async x =>
181+ {
182+ Assert.Equal(HttpMethod.Head, x.Method);
183+ Assert.Equal(new Uri("http://t.co/hoge1"), x.RequestUri);
184+
185+ return this.CreateRedirectResponse("http://example.com/hoge2");
186+ });
187+
188+ // スキームが省略されたURL
189+ Assert.Equal("http://example.com/hoge2",
190+ await shortUrl.ExpandUrlStrAsync("t.co/hoge1"));
191+ }
192+
193+ [Fact]
194+ public async Task ExpandUrlStrAsync_InvalidUrlTest()
195+ {
196+ var handler = new HttpMessageHandlerMock();
197+ var shortUrl = new ShortUrl(new HttpClient(handler));
198+
199+ handler.Queue.Enqueue(async x =>
200+ {
201+ // リクエストは送信されないはず
202+ Assert.True(false);
203+ return this.CreateRedirectResponse("http://example.com/hoge2");
204+ });
205+
206+ // 不正なURL
207+ Assert.Equal("..hogehoge..", await shortUrl.ExpandUrlStrAsync("..hogehoge.."));
208+ }
209+
210+ [Fact]
211+ public async Task ExpandUrlAsync_HttpErrorTest()
212+ {
213+ var handler = new HttpMessageHandlerMock();
214+ var shortUrl = new ShortUrl(new HttpClient(handler));
215+
216+ // http://t.co/hoge1 -> 503 Service Unavailable
217+ handler.Queue.Enqueue(async x =>
218+ {
219+ return new HttpResponseMessage(HttpStatusCode.ServiceUnavailable);
220+ });
221+
222+ Assert.Equal(new Uri("http://t.co/hoge1"),
223+ await shortUrl.ExpandUrlAsync(new Uri("http://t.co/hoge1")));
224+ }
225+
226+ [Fact]
227+ public async Task ExpandUrlHtmlAsync_Test()
228+ {
229+ var handler = new HttpMessageHandlerMock();
230+ var shortUrl = new ShortUrl(new HttpClient(handler));
231+
232+ // http://t.co/hoge1 -> http://example.com/hoge2
233+ handler.Queue.Enqueue(async x =>
234+ {
235+ Assert.Equal(HttpMethod.Head, x.Method);
236+ Assert.Equal(new Uri("http://t.co/hoge1"), x.RequestUri);
237+
238+ return this.CreateRedirectResponse("http://example.com/hoge2");
239+ });
240+
241+ Assert.Equal("<a href=\"http://example.com/hoge2\">hogehoge</a>",
242+ await shortUrl.ExpandUrlHtmlAsync("<a href=\"http://t.co/hoge1\">hogehoge</a>"));
243+ }
244+
245+ private HttpResponseMessage CreateRedirectResponse(string uriStr)
246+ {
247+ var response = new HttpResponseMessage(HttpStatusCode.TemporaryRedirect);
248+ response.Headers.Location = new Uri(uriStr);
249+ return response;
250+ }
251+
252+ [Fact]
253+ public async Task ShortenUrlAsync_TinyUrlTest()
254+ {
255+ var handler = new HttpMessageHandlerMock();
256+ var shortUrl = new ShortUrl(new HttpClient(handler));
257+
258+ handler.Queue.Enqueue(async x =>
259+ {
260+ Assert.Equal(HttpMethod.Post, x.Method);
261+ Assert.Equal(new Uri("http://tinyurl.com/api-create.php"), x.RequestUri);
262+ Assert.Equal("url=http%3A%2F%2Fexample.com%2Fhogehoge", await x.Content.ReadAsStringAsync());
263+
264+ return new HttpResponseMessage(HttpStatusCode.OK)
265+ {
266+ Content = new ByteArrayContent(Encoding.UTF8.GetBytes("http://tinyurl.com/hoge")),
267+ };
268+ });
269+
270+ Assert.Equal(new Uri("http://tinyurl.com/hoge"),
271+ await shortUrl.ShortenUrlAsync(MyCommon.UrlConverter.TinyUrl, new Uri("http://example.com/hogehoge")));
272+ }
273+
274+ [Fact]
275+ public async Task ShortenUrlAsync_UxnuUrlTest()
276+ {
277+ var handler = new HttpMessageHandlerMock();
278+ var shortUrl = new ShortUrl(new HttpClient(handler));
279+
280+ handler.Queue.Enqueue(async x =>
281+ {
282+ Assert.Equal(HttpMethod.Get, x.Method);
283+ Assert.Equal("http://ux.nu/api/short?format=plain&url=http://example.com/hogehoge",
284+ x.RequestUri.ToString());
285+
286+ return new HttpResponseMessage(HttpStatusCode.OK)
287+ {
288+ Content = new ByteArrayContent(Encoding.UTF8.GetBytes("http://ux.nu/hoge")),
289+ };
290+ });
291+
292+ Assert.Equal(new Uri("http://ux.nu/hoge"),
293+ await shortUrl.ShortenUrlAsync(MyCommon.UrlConverter.Uxnu, new Uri("http://example.com/hogehoge")));
294+ }
295+ }
296+}
--- a/OpenTween.Tests/TestUtils.cs
+++ b/OpenTween.Tests/TestUtils.cs
@@ -24,8 +24,10 @@ using System.Collections.Generic;
2424 using System.Linq;
2525 using System.Reflection;
2626 using System.Text;
27+using System.Threading.Tasks;
2728 using System.Windows.Forms;
2829 using Xunit;
30+using Xunit.Sdk;
2931 using Xunit.Extensions;
3032
3133 namespace OpenTween
@@ -62,5 +64,28 @@ namespace OpenTween
6264
6365 method.Invoke(control, new[] { e });
6466 }
67+
68+ public static async Task<T> ThrowsAsync<T>(Func<Task> testCode) where T : Exception
69+ {
70+ var expectedType = typeof(T);
71+ Exception exception = null;
72+
73+ try
74+ {
75+ await testCode();
76+ }
77+ catch (Exception ex)
78+ {
79+ exception = ex;
80+ }
81+
82+ if (exception == null)
83+ throw new ThrowsException(expectedType);
84+
85+ if (!exception.GetType().Equals(expectedType))
86+ throw new ThrowsException(expectedType, exception);
87+
88+ return (T)exception;
89+ }
6590 }
6691 }
--- a/OpenTween.Tests/TweetThumbnailTest.cs
+++ b/OpenTween.Tests/TweetThumbnailTest.cs
@@ -70,7 +70,7 @@ namespace OpenTween
7070 {
7171 public override Task<MemoryImage> LoadThumbnailImageAsync(CancellationToken token)
7272 {
73- return Task.Factory.StartNew(() => MemoryImage.CopyFromBytes(File.ReadAllBytes("Resources/" + this.ThumbnailUrl)), token);
73+ return Task.Run(() => MemoryImage.CopyFromBytes(File.ReadAllBytes("Resources/" + this.ThumbnailUrl)), token);
7474 }
7575 }
7676 }
@@ -118,19 +118,32 @@ namespace OpenTween
118118 }
119119 }
120120
121- [Fact(Skip = "Mono環境でたまに InvaliOperationException: out of sync で異常終了する")]
122- public void CancelAsyncTest()
121+ [Fact]
122+ public async Task CancelAsyncTest()
123123 {
124- using (var thumbbox = new TweetThumbnail())
124+ var post = new PostClass
125125 {
126- var post = new PostClass();
126+ TextFromApi = "てすと http://foo.example.com/abcd",
127+ Media = new Dictionary<string, string>
128+ {
129+ {"http://foo.example.com/abcd", "http://foo.example.com/abcd"},
130+ },
131+ };
127132
133+ using (var thumbbox = new TweetThumbnail())
134+ using (var tokenSource = new CancellationTokenSource())
135+ {
128136 SynchronizationContext.SetSynchronizationContext(new SynchronizationContext());
129- var task = thumbbox.ShowThumbnailAsync(post);
137+ var task = thumbbox.ShowThumbnailAsync(post, tokenSource.Token);
130138
131- thumbbox.CancelAsync();
139+ tokenSource.Cancel();
140+
141+ // Mono 上で動作しているか否か
142+ if (Type.GetType("Mono.Runtime") != null)
143+ await TestUtils.ThrowsAsync<OperationCanceledException>(async () => await task);
144+ else
145+ await TestUtils.ThrowsAsync<TaskCanceledException>(async () => await task);
132146
133- Assert.Throws<AggregateException>(() => task.Wait());
134147 Assert.True(task.IsCanceled);
135148 }
136149 }
@@ -167,7 +180,7 @@ namespace OpenTween
167180 }
168181
169182 [Fact]
170- public void ShowThumbnailAsyncTest()
183+ public async Task ShowThumbnailAsyncTest()
171184 {
172185 var post = new PostClass
173186 {
@@ -181,7 +194,7 @@ namespace OpenTween
181194 using (var thumbbox = new TweetThumbnail())
182195 {
183196 SynchronizationContext.SetSynchronizationContext(new SynchronizationContext());
184- thumbbox.ShowThumbnailAsync(post).Wait();
197+ await thumbbox.ShowThumbnailAsync(post);
185198
186199 Assert.Equal(0, thumbbox.scrollBar.Maximum);
187200 Assert.False(thumbbox.scrollBar.Enabled);
@@ -200,7 +213,7 @@ namespace OpenTween
200213 }
201214
202215 [Fact]
203- public void ShowThumbnailAsyncTest2()
216+ public async Task ShowThumbnailAsyncTest2()
204217 {
205218 var post = new PostClass
206219 {
@@ -215,7 +228,7 @@ namespace OpenTween
215228 using (var thumbbox = new TweetThumbnail())
216229 {
217230 SynchronizationContext.SetSynchronizationContext(new SynchronizationContext());
218- thumbbox.ShowThumbnailAsync(post).Wait();
231+ await thumbbox.ShowThumbnailAsync(post);
219232
220233 Assert.Equal(1, thumbbox.scrollBar.Maximum);
221234 Assert.True(thumbbox.scrollBar.Enabled);
@@ -242,7 +255,7 @@ namespace OpenTween
242255 }
243256
244257 [Fact]
245- public void ThumbnailLoadingEventTest()
258+ public async Task ThumbnailLoadingEventTest()
246259 {
247260 using (var thumbbox = new TweetThumbnail())
248261 {
@@ -260,10 +273,12 @@ namespace OpenTween
260273 },
261274 };
262275 eventCalled = false;
263- thumbbox.ShowThumbnailAsync(post).Wait();
276+ await thumbbox.ShowThumbnailAsync(post);
264277
265278 Assert.False(eventCalled);
266279
280+ SynchronizationContext.SetSynchronizationContext(new SynchronizationContext());
281+
267282 var post2 = new PostClass
268283 {
269284 TextFromApi = "てすと http://foo.example.com/abcd",
@@ -273,14 +288,14 @@ namespace OpenTween
273288 },
274289 };
275290 eventCalled = false;
276- thumbbox.ShowThumbnailAsync(post2).Wait();
291+ await thumbbox.ShowThumbnailAsync(post2);
277292
278293 Assert.True(eventCalled);
279294 }
280295 }
281296
282297 [Fact]
283- public void ScrollTest()
298+ public async Task ScrollTest()
284299 {
285300 var post = new PostClass
286301 {
@@ -295,7 +310,7 @@ namespace OpenTween
295310 using (var thumbbox = new TweetThumbnail())
296311 {
297312 SynchronizationContext.SetSynchronizationContext(new SynchronizationContext());
298- thumbbox.ShowThumbnailAsync(post).Wait();
313+ await thumbbox.ShowThumbnailAsync(post);
299314
300315 Assert.Equal(0, thumbbox.scrollBar.Minimum);
301316 Assert.Equal(1, thumbbox.scrollBar.Maximum);
--- a/OpenTween.sln
+++ b/OpenTween.sln
@@ -1,6 +1,8 @@
11 
2-Microsoft Visual Studio Solution File, Format Version 11.00
3-# Visual Studio 2010
2+Microsoft Visual Studio Solution File, Format Version 12.00
3+# Visual Studio 2013
4+VisualStudioVersion = 12.0.30110.0
5+MinimumVisualStudioVersion = 10.0.40219.1
46 Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "OpenTween", "OpenTween\OpenTween.csproj", "{3D8995C7-BDF3-4273-9F9D-DDD902F6A101}"
57 EndProject
68 Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "OpenTween.Tests", "OpenTween.Tests\OpenTween.Tests.csproj", "{18A32642-A8F3-425B-978D-0C6F630EDDE8}"
--- a/OpenTween/ImageCache.cs
+++ b/OpenTween/ImageCache.cs
@@ -92,7 +92,7 @@ namespace OpenTween
9292 {
9393 var cancelToken = this.cancelTokenSource.Token;
9494
95- return Task.Factory.StartNew(() =>
95+ return Task.Run(() =>
9696 {
9797 Task<MemoryImage> cachedImageTask = null;
9898 lock (this.lockObject)
@@ -125,7 +125,7 @@ namespace OpenTween
125125 return imageTask;
126126 }
127127 }
128- }, cancelToken, TaskCreationOptions.None, TaskScheduler.Default).Unwrap();
128+ }, cancelToken);
129129 }
130130
131131 public MemoryImage TryGetFromCache(string address)
--- a/OpenTween/ImageListViewItem.cs
+++ b/OpenTween/ImageListViewItem.cs
@@ -38,6 +38,7 @@ namespace OpenTween
3838 protected readonly string imageUrl;
3939
4040 private WeakReference imageReference = new WeakReference(null);
41+ private Task imageTask = null;
4142
4243 public event EventHandler ImageDownloaded;
4344
@@ -56,42 +57,49 @@ namespace OpenTween
5657 {
5758 var image = imageCache.TryGetFromCache(imageUrl);
5859
59- if (image == null)
60- this.GetImageAsync();
61- else
60+ if (image != null)
6261 this.imageReference.Target = image;
6362 }
6463 }
6564
66- private Task GetImageAsync(bool force = false)
65+ public Task GetImageAsync(bool force = false)
66+ {
67+ if (this.imageTask == null || this.imageTask.IsCompleted)
68+ {
69+ this.imageTask = this.GetImageAsyncInternal(force);
70+ }
71+
72+ return this.imageTask;
73+ }
74+
75+ private async Task GetImageAsyncInternal(bool force)
6776 {
6877 if (string.IsNullOrEmpty(this.imageUrl))
69- return Task.Factory.StartNew(() => { });
78+ return;
7079
71- var uiScheduler = TaskScheduler.FromCurrentSynchronizationContext();
80+ if (!force && this.imageReference.Target != null)
81+ return;
7282
73- return this.imageCache.DownloadImageAsync(this.imageUrl, force)
74- .ContinueWith(t =>
75- {
76- if (t.IsFaulted)
77- {
78- t.Exception.Flatten().Handle(x => x is WebException || x is InvalidImageException || x is TaskCanceledException);
79- return;
80- }
83+ try
84+ {
85+ var image = await this.imageCache.DownloadImageAsync(this.imageUrl, force);
8186
82- this.imageReference.Target = t.Result;
87+ this.imageReference.Target = image;
8388
84- if (this.ListView == null || !this.ListView.Created || this.ListView.IsDisposed)
85- return;
89+ if (this.ListView == null || !this.ListView.Created || this.ListView.IsDisposed)
90+ return;
8691
87- if (this.Index < this.ListView.VirtualListSize)
88- {
89- this.ListView.RedrawItems(this.Index, this.Index, true);
92+ if (this.Index < this.ListView.VirtualListSize)
93+ {
94+ this.ListView.RedrawItems(this.Index, this.Index, true);
9095
91- if (this.ImageDownloaded != null)
92- this.ImageDownloaded(this, EventArgs.Empty);
93- }
94- }, uiScheduler);
96+ if (this.ImageDownloaded != null)
97+ this.ImageDownloaded(this, EventArgs.Empty);
98+ }
99+ }
100+ catch (WebException) { }
101+ catch (InvalidImageException) { }
102+ catch (TaskCanceledException) { }
95103 }
96104
97105 public MemoryImage Image
@@ -102,10 +110,10 @@ namespace OpenTween
102110 }
103111 }
104112
105- public void RefreshImage()
113+ public Task RefreshImageAsync()
106114 {
107115 this.imageReference.Target = null;
108- this.GetImageAsync(true);
116+ return this.GetImageAsync(true);
109117 }
110118 }
111119 }
--- a/OpenTween/MemoryImage.cs
+++ b/OpenTween/MemoryImage.cs
@@ -95,6 +95,21 @@ namespace OpenTween
9595 return MemoryImage.CopyFromStream(this.Stream);
9696 }
9797
98+ /// <summary>
99+ /// MemoryImage インスタンスを非同期に複製します
100+ /// </summary>
101+ /// <remarks>
102+ /// メソッド実行中にストリームのシークが行われないよう注意して下さい。
103+ /// 特に PictureBox で Gif アニメーションを表示している場合は Enabled に false をセットするなどして更新を止めて下さい。
104+ /// </remarks>
105+ /// <returns>複製された MemoryImage を返すタスク</returns>
106+ public Task<MemoryImage> CloneAsync()
107+ {
108+ this.Stream.Seek(0, SeekOrigin.Begin);
109+
110+ return MemoryImage.CopyFromStreamAsync(this.Stream);
111+ }
112+
98113 object ICloneable.Clone()
99114 {
100115 return this.Clone();
@@ -146,6 +161,25 @@ namespace OpenTween
146161 }
147162
148163 /// <summary>
164+ /// 指定された Stream から MemoryImage を非同期に作成します。
165+ /// </summary>
166+ /// <remarks>
167+ /// ストリームの内容はメモリ上に展開した後に使用されるため、
168+ /// 引数に指定した Stream を MemoryImage より先に破棄しても問題ありません。
169+ /// </remarks>
170+ /// <param name="stream">読み込む対象となる Stream</param>
171+ /// <returns>作成された MemoryImage を返すタスク</returns>
172+ /// <exception cref="InvalidImageException">不正な画像データが入力された場合</exception>
173+ public async static Task<MemoryImage> CopyFromStreamAsync(Stream stream)
174+ {
175+ var memstream = new MemoryStream();
176+
177+ await stream.CopyToAsync(memstream).ConfigureAwait(false);
178+
179+ return new MemoryImage(memstream);
180+ }
181+
182+ /// <summary>
149183 /// 指定されたバイト列から MemoryImage を作成します。
150184 /// </summary>
151185 /// <param name="bytes">読み込む対象となるバイト列</param>
--- a/OpenTween/MyCommon.cs
+++ b/OpenTween/MyCommon.cs
@@ -42,6 +42,8 @@ using System.Runtime.Serialization.Json;
4242 using System.Reflection;
4343 using System.Diagnostics;
4444 using System.Text.RegularExpressions;
45+using System.Net;
46+using System.Net.Http;
4547 using System.Net.NetworkInformation;
4648 using System.Runtime.InteropServices;
4749 using OpenTween.Api;
@@ -909,43 +911,54 @@ namespace OpenTween
909911 /// <returns>
910912 /// 生成されたバージョン番号の文字列
911913 /// </returns>
912- public static string GetReadableVersion(string fileVersion = null)
914+ public static string GetReadableVersion(string versionStr = null)
913915 {
914- if (fileVersion == null)
916+ if (versionStr == null)
915917 {
916- fileVersion = MyCommon.fileVersion;
917- }
918+ if (MyCommon.fileVersion == null)
919+ return null;
918920
919- if (string.IsNullOrEmpty(fileVersion))
920- {
921- return null;
921+ versionStr = MyCommon.fileVersion;
922922 }
923923
924- int[] version = fileVersion.Split('.')
925- .Select(x => int.Parse(x)).ToArray();
924+ return GetReadableVersion(Version.Parse(versionStr));
925+ }
926+
927+ /// <summary>
928+ /// 表示用のバージョン番号の文字列を生成する
929+ /// </summary>
930+ /// <remarks>
931+ /// バージョン1.0.0.1のように末尾が0でない(=開発版)の場合は「1.0.1-beta1」が出力される
932+ /// </remarks>
933+ /// <returns>
934+ /// 生成されたバージョン番号の文字列
935+ /// </returns>
936+ public static string GetReadableVersion(Version version)
937+ {
938+ var versionNum = new[] { version.Major, version.Minor, version.Build, version.Revision };
926939
927- if (version[3] == 0)
940+ if (versionNum[3] == 0)
928941 {
929- return string.Format("{0}.{1}.{2}", version[0], version[1], version[2]);
942+ return string.Format("{0}.{1}.{2}", versionNum[0], versionNum[1], versionNum[2]);
930943 }
931944 else
932945 {
933- version[2] = version[2] + 1;
946+ versionNum[2] = versionNum[2] + 1;
934947
935948 // 10を越えたら桁上げ
936- if (version[2] >= 10)
949+ if (versionNum[2] >= 10)
937950 {
938- version[1] += version[2] / 10;
939- version[2] %= 10;
951+ versionNum[1] += versionNum[2] / 10;
952+ versionNum[2] %= 10;
940953
941- if (version[1] >= 10)
954+ if (versionNum[1] >= 10)
942955 {
943- version[0] += version[1] / 10;
944- version[1] %= 10;
956+ versionNum[0] += versionNum[1] / 10;
957+ versionNum[1] %= 10;
945958 }
946959 }
947960
948- return string.Format("{0}.{1}.{2}-beta{3}", version[0], version[1], version[2], version[3]);
961+ return string.Format("{0}.{1}.{2}-beta{3}", versionNum[0], versionNum[1], versionNum[2], versionNum[3]);
949962 }
950963 }
951964
@@ -963,5 +976,40 @@ namespace OpenTween
963976 {
964977 return TwitterUrl + screenName + "/status/" + statusId.ToString();
965978 }
979+
980+ /// <summary>
981+ /// OpenTween 内で共通して使う設定を施した HttpClient インスタンスを作成します
982+ /// </summary>
983+ public static HttpClient CreateHttpClient()
984+ {
985+ return CreateHttpClient(new HttpClientHandler());
986+ }
987+
988+ /// <summary>
989+ /// OpenTween 内で共通して使う設定を施した HttpClient インスタンスを作成します
990+ /// </summary>
991+ public static HttpClient CreateHttpClient(HttpClientHandler handler)
992+ {
993+ switch (HttpConnection.proxyKind)
994+ {
995+ case HttpConnection.ProxyType.None:
996+ handler.UseProxy = false;
997+ break;
998+ case HttpConnection.ProxyType.Specified:
999+ handler.UseProxy = true;
1000+ handler.Proxy = HttpConnection.proxy;
1001+ break;
1002+ case HttpConnection.ProxyType.IE:
1003+ default:
1004+ handler.UseProxy = true;
1005+ handler.Proxy = WebRequest.GetSystemWebProxy();
1006+ break;
1007+ }
1008+
1009+ var client = new HttpClient(handler);
1010+ client.DefaultRequestHeaders.Add("User-Agent", MyCommon.GetUserAgentString());
1011+
1012+ return client;
1013+ }
9661014 }
9671015 }
--- a/OpenTween/OpenTween.csproj
+++ b/OpenTween/OpenTween.csproj
@@ -1,5 +1,5 @@
11 <?xml version="1.0" encoding="utf-8"?>
2-<Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
2+<Project ToolsVersion="12.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
33 <PropertyGroup>
44 <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
55 <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
@@ -10,9 +10,10 @@
1010 <AppDesignerFolder>Properties</AppDesignerFolder>
1111 <RootNamespace>OpenTween</RootNamespace>
1212 <AssemblyName>OpenTween</AssemblyName>
13- <TargetFrameworkVersion>v4.0</TargetFrameworkVersion>
13+ <TargetFrameworkVersion Condition="'$(OS)' == 'Windows_NT'">v4.5.1</TargetFrameworkVersion>
14+ <TargetFrameworkVersion Condition="'$(OS)' != 'Windows_NT'">v4.5</TargetFrameworkVersion><!-- Mono -->
1415 <FileAlignment>512</FileAlignment>
15- <TargetFrameworkProfile Condition="'$(OS)' == 'Windows_NT'">Client</TargetFrameworkProfile>
16+ <TargetFrameworkProfile />
1617 </PropertyGroup>
1718 <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
1819 <DebugSymbols>true</DebugSymbols>
@@ -23,6 +24,7 @@
2324 <ErrorReport>prompt</ErrorReport>
2425 <WarningLevel>4</WarningLevel>
2526 <GenerateSerializationAssemblies>Auto</GenerateSerializationAssemblies>
27+ <Prefer32Bit>false</Prefer32Bit>
2628 </PropertyGroup>
2729 <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
2830 <DebugType>pdbonly</DebugType>
@@ -33,6 +35,7 @@
3335 <WarningLevel>4</WarningLevel>
3436 <GenerateSerializationAssemblies>On</GenerateSerializationAssemblies>
3537 <UseVSHostingProcess>false</UseVSHostingProcess>
38+ <Prefer32Bit>false</Prefer32Bit>
3639 </PropertyGroup>
3740 <PropertyGroup>
3841 <StartupObject>OpenTween.MyApplication</StartupObject>
@@ -47,7 +50,9 @@
4750 <Reference Include="System" />
4851 <Reference Include="System.Core" />
4952 <Reference Include="System.Drawing" />
53+ <Reference Include="System.Net.Http" />
5054 <Reference Include="System.Runtime.Serialization" />
55+ <Reference Include="System.Web" />
5156 <Reference Include="System.Windows.Forms" />
5257 <Reference Include="System.Xml.Linq" />
5358 <Reference Include="System.Data.DataSetExtensions" />
@@ -150,6 +155,7 @@
150155 <Compile Include="OTWebClient.cs">
151156 <SubType>Component</SubType>
152157 </Compile>
158+ <Compile Include="RegexAsync.cs" />
153159 <Compile Include="Setting\Panel\ActionPanel.cs">
154160 <SubType>UserControl</SubType>
155161 </Compile>
--- /dev/null
+++ b/OpenTween/RegexAsync.cs
@@ -0,0 +1,52 @@
1+// OpenTween - Client of Twitter
2+// Copyright (c) 2014 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+using System;
23+using System.Collections.Concurrent;
24+using System.Collections.Generic;
25+using System.Linq;
26+using System.Text;
27+using System.Text.RegularExpressions;
28+using System.Threading.Tasks;
29+
30+namespace OpenTween
31+{
32+ public static class RegexAsync
33+ {
34+ public static async Task<string> ReplaceAsync(this Regex regex, string input, Func<Match, Task<string>> evaluator)
35+ {
36+ var matches = regex.Matches(input).Cast<Match>();
37+
38+ var evaluatorTasks = matches
39+ .GroupBy(x => x.Value) // ToDictionaryする際に key が重複しないようにする
40+ .Select(async x => new
41+ {
42+ Key = x.Key,
43+ Value = await evaluator(x.First()).ConfigureAwait(false),
44+ });
45+
46+ var replacements = (await Task.WhenAll(evaluatorTasks).ConfigureAwait(false))
47+ .ToDictionary(x => x.Key, x => x.Value);
48+
49+ return regex.Replace(input, x => replacements[x.Value]);
50+ }
51+ }
52+}
--- a/OpenTween/Resources/ChangeLog.txt
+++ b/OpenTween/Resources/ChangeLog.txt
@@ -1,9 +1,13 @@
11 更新履歴
22
33 ==== Ver 1.2.0(2014/xx/xx)
4+ * このバージョン以降のOpenTweenは .NET Framework 4.5.1 が必要となります
5+ * .NET Framework 4.5.1 は Windows XP にはインストールできないためご注意下さい
6+
47 * NEW: 検索ダイアログからの検索結果をタブとして表示(=振り分けタブを追加)できるようになりました
58 - 現状、Recentタブ内の検索時以外には使用できません
69 * NEW: 検索ダイアログからPublicSearchタブを追加できるようになりました
10+ * CHG: 使用する.NET Frameworkのバージョンが 4.0 から 4.5.1 に変更されました
711 * CHG: 画像ファイルをD&Dした際の動作を変更しました
812 - 投稿先がD&Dしたファイルに対応していない場合、勝手に投稿先を切り替えず、エラーダイアログを表示するだけにします
913
--- a/OpenTween/ShortUrl.cs
+++ b/OpenTween/ShortUrl.cs
@@ -28,8 +28,11 @@ using System;
2828 using System.Collections.Concurrent;
2929 using System.Collections.Generic;
3030 using System.Linq;
31+using System.Net.Http;
3132 using System.Text;
3233 using System.Text.RegularExpressions;
34+using System.Threading.Tasks;
35+using System.Web;
3336
3437 namespace OpenTween
3538 {
@@ -38,7 +41,7 @@ namespace OpenTween
3841 /// </summary>
3942 public class ShortUrl
4043 {
41- private static Lazy<ShortUrl> _instance = new Lazy<ShortUrl>(() => new ShortUrl(new HttpVarious()), true);
44+ private static Lazy<ShortUrl> _instance;
4245
4346 /// <summary>
4447 /// ShortUrl のインスタンスを取得します
@@ -58,16 +61,11 @@ namespace OpenTween
5861 /// </summary>
5962 public int PurgeCount { get; set; }
6063
61- /// <summary>
62- /// リダイレクトのタイムアウト時間 (単位: ms)
63- /// </summary>
64- public int RedirectTimeout { get; set; }
65-
6664 public string BitlyId { get; set; }
6765 public string BitlyKey { get; set; }
6866
69- private HttpVarious http;
70- private ConcurrentDictionary<string, string> urlCache = new ConcurrentDictionary<string, string>();
67+ private HttpClient http;
68+ private ConcurrentDictionary<Uri, Uri> urlCache = new ConcurrentDictionary<Uri, Uri>();
7169
7270 private static readonly Regex HtmlLinkPattern = new Regex(@"(<a href="")(.+?)("")");
7371
@@ -121,34 +119,62 @@ namespace OpenTween
121119 "youtu.be",
122120 };
123121
124- internal ShortUrl(HttpVarious http)
122+ static ShortUrl()
123+ {
124+ _instance = new Lazy<ShortUrl>(() =>
125+ {
126+ var handler = new HttpClientHandler
127+ {
128+ AllowAutoRedirect = false,
129+ };
130+
131+ var http = MyCommon.CreateHttpClient(handler);
132+ http.Timeout = new TimeSpan(0, 0, seconds: 5);
133+
134+ return new ShortUrl(http);
135+ }, true);
136+ }
137+
138+ internal ShortUrl(HttpClient http)
125139 {
126140 this.DisableExpanding = false;
127141 this.PurgeCount = 500;
128- this.RedirectTimeout = 1000;
129142 this.BitlyId = "";
130143 this.BitlyKey = "";
131144
132145 this.http = http;
133146 }
134147
148+ [Obsolete]
149+ public string ExpandUrl(string uri)
150+ {
151+ try
152+ {
153+ return this.ExpandUrlAsync(new Uri(uri), 10).Result.ToString();
154+ }
155+ catch (UriFormatException)
156+ {
157+ return uri;
158+ }
159+ }
160+
135161 /// <summary>
136- /// 短縮 URL を展開します
162+ /// 短縮 URL を非同期に展開します
137163 /// </summary>
138164 /// <param name="uri">展開するURL</param>
139- /// <returns>展開されたURL</returns>
140- public string ExpandUrl(string uri)
165+ /// <returns>URLの展開を行うタスク</returns>
166+ public Task<Uri> ExpandUrlAsync(Uri uri)
141167 {
142- return this.ExpandUrl(uri, 10);
168+ return this.ExpandUrlAsync(uri, 10);
143169 }
144170
145171 /// <summary>
146- /// 短縮 URL を展開します
172+ /// 短縮 URL を非同期に展開します
147173 /// </summary>
148174 /// <param name="uri">展開するURL</param>
149175 /// <param name="redirectLimit">再帰的に展開を試みる上限</param>
150- /// <returns>展開されたURL</returns>
151- public string ExpandUrl(string uri, int redirectLimit)
176+ /// <returns>URLの展開を行うタスク</returns>
177+ public async Task<Uri> ExpandUrlAsync(Uri uri, int redirectLimit)
152178 {
153179 if (this.DisableExpanding)
154180 return uri;
@@ -158,20 +184,32 @@ namespace OpenTween
158184
159185 try
160186 {
161- if (!ShortUrlHosts.Contains(new Uri(uri).Host))
187+ if (!ShortUrlHosts.Contains(uri.Host))
162188 return uri;
163189
164- string expanded;
190+ Uri expanded;
165191 if (this.urlCache.TryGetValue(uri, out expanded))
166192 return expanded;
167193
168194 if (this.urlCache.Count > this.PurgeCount)
169195 this.urlCache.Clear();
170196
171- expanded = this.http.GetRedirectTo(uri, this.RedirectTimeout);
197+ expanded = null;
198+ try
199+ {
200+ expanded = await this.GetRedirectTo(uri)
201+ .ConfigureAwait(false);
202+ }
203+ catch (TaskCanceledException) { }
204+ catch (HttpRequestException) { }
205+
206+ if (expanded == null || expanded == uri)
207+ return uri;
208+
172209 this.urlCache[uri] = expanded;
173210
174- var recursiveExpanded = this.ExpandUrl(expanded, redirectLimit--);
211+ var recursiveExpanded = await this.ExpandUrlAsync(expanded, --redirectLimit)
212+ .ConfigureAwait(false);
175213
176214 // URL1 -> URL2 -> URL3 のように再帰的に展開されたURLを URL1 -> URL3 としてキャッシュに格納する
177215 if (recursiveExpanded != expanded)
@@ -186,190 +224,242 @@ namespace OpenTween
186224 }
187225
188226 /// <summary>
189- /// HTML内に含まれるリンクのURLを展開する
227+ /// 短縮 URL を非同期に展開します
190228 /// </summary>
191- /// <param name="html">処理対象のHTML</param>
192- /// <returns>展開されたURLを含むHTML</returns>
229+ /// <param name="uriStr">展開するURL</param>
230+ /// <returns>URLの展開を行うタスク</returns>
231+ public async Task<string> ExpandUrlStrAsync(string uriStr)
232+ {
233+ Uri uri;
234+
235+ try
236+ {
237+ if (!uriStr.StartsWith("http", StringComparison.OrdinalIgnoreCase))
238+ uri = new Uri("http://" + uriStr);
239+ else
240+ uri = new Uri(uriStr);
241+ }
242+ catch (UriFormatException)
243+ {
244+ return uriStr;
245+ }
246+
247+ var expandedUri = await this.ExpandUrlAsync(uri, 10)
248+ .ConfigureAwait(false);
249+
250+ return expandedUri.OriginalString;
251+ }
252+
253+ [Obsolete]
193254 public string ExpandUrlHtml(string html)
194255 {
195- return this.ExpandUrlHtml(html, 10);
256+ return this.ExpandUrlHtmlAsync(html, 10).Result;
257+ }
258+
259+ /// <summary>
260+ /// HTML内に含まれるリンクのURLを非同期に展開する
261+ /// </summary>
262+ /// <param name="html">処理対象のHTML</param>
263+ /// <returns>URLの展開を行うタスク</returns>
264+ public Task<string> ExpandUrlHtmlAsync(string html)
265+ {
266+ return this.ExpandUrlHtmlAsync(html, 10);
196267 }
197268
198269 /// <summary>
199- /// HTML内に含まれるリンクのURLを展開する
270+ /// HTML内に含まれるリンクのURLを非同期に展開する
200271 /// </summary>
201272 /// <param name="html">処理対象のHTML</param>
202273 /// <param name="redirectLimit">再帰的に展開を試みる上限</param>
203- /// <returns>展開されたURLを含むHTML</returns>
204- public string ExpandUrlHtml(string html, int redirectLimit)
274+ /// <returns>URLの展開を行うタスク</returns>
275+ public Task<string> ExpandUrlHtmlAsync(string html, int redirectLimit)
205276 {
206277 if (this.DisableExpanding)
207- return html;
278+ return Task.FromResult(html);
208279
209- return HtmlLinkPattern.Replace(html, m => m.Groups[1].Value + this.ExpandUrl(m.Groups[2].Value, redirectLimit) + m.Groups[3].Value);
280+ return HtmlLinkPattern.ReplaceAsync(html, async m =>
281+ m.Groups[1].Value + await this.ExpandUrlAsync(new Uri(m.Groups[2].Value), redirectLimit).ConfigureAwait(false) + m.Groups[3].Value);
210282 }
211283
212284 /// <summary>
213285 /// 指定された短縮URLサービスを使用してURLを短縮します
214286 /// </summary>
215287 /// <param name="shortenerType">使用する短縮URLサービス</param>
216- /// <param name="srcUrl">短縮するURL</param>
288+ /// <param name="srcUri">短縮するURL</param>
217289 /// <returns>短縮されたURL</returns>
218- public string ShortenUrl(MyCommon.UrlConverter shortenerType, string srcUrl)
290+ public Task<Uri> ShortenUrlAsync(MyCommon.UrlConverter shortenerType, Uri srcUri)
219291 {
220- srcUrl = MyCommon.urlEncodeMultibyteChar(srcUrl);
221-
222292 // 既に短縮されている状態のURLであれば短縮しない
223- if (ShortUrlHosts.Contains(new Uri(srcUrl).Host))
224- return srcUrl;
293+ if (ShortUrlHosts.Contains(srcUri.Host))
294+ return Task.FromResult(srcUri);
225295
226- string result;
227296 switch (shortenerType)
228297 {
229298 case MyCommon.UrlConverter.TinyUrl:
230- result = this.ShortenByTinyUrl(srcUrl);
231- break;
299+ return this.ShortenByTinyUrlAsync(srcUri);
232300 case MyCommon.UrlConverter.Isgd:
233- result = this.ShortenByIsgd(srcUrl);
234- break;
301+ return this.ShortenByIsgdAsync(srcUri);
235302 case MyCommon.UrlConverter.Twurl:
236- result = this.ShortenByTwurl(srcUrl);
237- break;
303+ return this.ShortenByTwurlAsync(srcUri);
238304 case MyCommon.UrlConverter.Bitly:
239- result = this.ShortenByBitly(srcUrl, "bit.ly");
240- break;
305+ return this.ShortenByBitlyAsync(srcUri, "bit.ly");
241306 case MyCommon.UrlConverter.Jmp:
242- result = this.ShortenByBitly(srcUrl, "j.mp");
243- break;
307+ return this.ShortenByBitlyAsync(srcUri, "j.mp");
244308 case MyCommon.UrlConverter.Uxnu:
245- result = this.ShortenByUxnu(srcUrl);
246- break;
309+ return this.ShortenByUxnuAsync(srcUri);
247310 default:
248311 throw new ArgumentException("Unknown shortener.", "shortenerType");
249312 }
250-
251- // 短縮の結果逆に長くなった場合は短縮前のURLを返す
252- if (srcUrl.Length < result.Length)
253- result = srcUrl;
254-
255- return result;
256313 }
257314
258- private string ShortenByTinyUrl(string srcUrl)
315+ private async Task<Uri> ShortenByTinyUrlAsync(Uri srcUri)
259316 {
260317 // 明らかに長くなると推測できる場合は短縮しない
261- if ("http://tinyurl.com/xxxxxx".Length > srcUrl.Length)
262- return srcUrl;
318+ if ("http://tinyurl.com/xxxxxx".Length > srcUri.OriginalString.Length)
319+ return srcUri;
320+
321+ var content = new FormUrlEncodedContent(new[]
322+ {
323+ new KeyValuePair<string, string>("url", srcUri.OriginalString),
324+ });
263325
264- var param = new Dictionary<string, string>
326+ using (var response = await this.http.PostAsync("http://tinyurl.com/api-create.php", content).ConfigureAwait(false))
265327 {
266- {"url", srcUrl},
267- };
328+ response.EnsureSuccessStatusCode();
268329
269- string response;
270- if (!this.http.PostData("http://tinyurl.com/api-create.php", param, out response))
271- throw new WebApiException("Failed to create URL.", response);
330+ var result = await response.Content.ReadAsStringAsync()
331+ .ConfigureAwait(false);
272332
273- if (!Regex.IsMatch(response, @"^https?://"))
274- throw new WebApiException("Failed to create URL.", response);
333+ if (!Regex.IsMatch(result, @"^https?://"))
334+ throw new WebApiException("Failed to create URL.", result);
275335
276- return response.TrimEnd();
336+ return new Uri(result.TrimEnd());
337+ }
277338 }
278339
279- private string ShortenByIsgd(string srcUrl)
340+ private async Task<Uri> ShortenByIsgdAsync(Uri srcUri)
280341 {
281342 // 明らかに長くなると推測できる場合は短縮しない
282- if ("http://is.gd/xxxx".Length > srcUrl.Length)
283- return srcUrl;
343+ if ("http://is.gd/xxxx".Length > srcUri.OriginalString.Length)
344+ return srcUri;
284345
285- var param = new Dictionary<string, string>
346+ var content = new FormUrlEncodedContent(new[]
286347 {
287- {"format", "simple"},
288- {"url", srcUrl},
289- };
348+ new KeyValuePair<string, string>("format", "simple"),
349+ new KeyValuePair<string, string>("url", srcUri.OriginalString),
350+ });
290351
291- string response;
292- if (!this.http.PostData("http://is.gd/create.php", param, out response))
293- throw new WebApiException("Failed to create URL.", response);
352+ using (var response = await this.http.PostAsync("http://is.gd/create.php", content).ConfigureAwait(false))
353+ {
354+ response.EnsureSuccessStatusCode();
355+
356+ var result = await response.Content.ReadAsStringAsync()
357+ .ConfigureAwait(false);
294358
295- if (!Regex.IsMatch(response, @"^https?://"))
296- throw new WebApiException("Failed to create URL.", response);
359+ if (!Regex.IsMatch(result, @"^https?://"))
360+ throw new WebApiException("Failed to create URL.", result);
297361
298- return response.TrimEnd();
362+ return new Uri(result.TrimEnd());
363+ }
299364 }
300365
301- private string ShortenByTwurl(string srcUrl)
366+ private async Task<Uri> ShortenByTwurlAsync(Uri srcUri)
302367 {
303368 // 明らかに長くなると推測できる場合は短縮しない
304- if ("http://twurl.nl/xxxxxx".Length > srcUrl.Length)
305- return srcUrl;
369+ if ("http://twurl.nl/xxxxxx".Length > srcUri.OriginalString.Length)
370+ return srcUri;
371+
372+ var content = new FormUrlEncodedContent(new[]
373+ {
374+ new KeyValuePair<string, string>("link[url]", srcUri.OriginalString),
375+ });
306376
307- var param = new Dictionary<string, string>
377+ using (var response = await this.http.PostAsync("http://tweetburner.com/links", content).ConfigureAwait(false))
308378 {
309- {"link[url]", srcUrl},
310- };
379+ response.EnsureSuccessStatusCode();
311380
312- string response;
313- if (!this.http.PostData("http://tweetburner.com/links", param, out response))
314- throw new WebApiException("Failed to create URL.", response);
381+ var result = await response.Content.ReadAsStringAsync()
382+ .ConfigureAwait(false);
315383
316- if (!Regex.IsMatch(response, @"^https?://"))
317- throw new WebApiException("Failed to create URL.", response);
384+ if (!Regex.IsMatch(result, @"^https?://"))
385+ throw new WebApiException("Failed to create URL.", result);
318386
319- return response.TrimEnd();
387+ return new Uri(result.TrimEnd());
388+ }
320389 }
321390
322- private string ShortenByBitly(string srcUrl, string domain = "bit.ly")
391+ private async Task<Uri> ShortenByBitlyAsync(Uri srcUri, string domain = "bit.ly")
323392 {
324393 // 明らかに長くなると推測できる場合は短縮しない
325- if ("http://bit.ly/xxxx".Length > srcUrl.Length)
326- return srcUrl;
394+ if ("http://bit.ly/xxxx".Length > srcUri.OriginalString.Length)
395+ return srcUri;
327396
328397 // bit.ly 短縮機能実装のプライバシー問題の暫定対応
329398 // ログインIDとAPIキーが指定されていない場合は短縮せずにPOSTする
330399 // 参照: http://sourceforge.jp/projects/opentween/lists/archive/dev/2012-January/000020.html
331400 if (string.IsNullOrEmpty(this.BitlyId) || string.IsNullOrEmpty(this.BitlyKey))
332- return srcUrl;
401+ return srcUri;
333402
334- var param = new Dictionary<string, string>
403+ var query = HttpUtility.ParseQueryString(string.Empty);
404+ query["login"] = this.BitlyId;
405+ query["apiKey"] = this.BitlyKey;
406+ query["format"] = "txt";
407+ query["domain"] = domain;
408+ query["longUrl"] = srcUri.OriginalString;
409+
410+ using (var response = await this.http.GetAsync("https://api-ssl.bitly.com/v3/shorten?" + query).ConfigureAwait(false))
335411 {
336- {"login", this.BitlyId},
337- {"apiKey", this.BitlyKey},
338- {"format", "txt"},
339- {"domain", domain},
340- {"longUrl", srcUrl},
341- };
412+ response.EnsureSuccessStatusCode();
342413
343- string response;
344- if (!this.http.GetData("https://api-ssl.bitly.com/v3/shorten", param, out response))
345- throw new WebApiException("Failed to create URL.", response);
414+ var result = await response.Content.ReadAsStringAsync()
415+ .ConfigureAwait(false);
346416
347- if (!Regex.IsMatch(response, @"^https?://"))
348- throw new WebApiException("Failed to create URL.", response);
417+ if (!Regex.IsMatch(result, @"^https?://"))
418+ throw new WebApiException("Failed to create URL.", result);
349419
350- return response.TrimEnd();
420+ return new Uri(result.TrimEnd());
421+ }
351422 }
352423
353- private string ShortenByUxnu(string srcUrl)
424+ private async Task<Uri> ShortenByUxnuAsync(Uri srcUri)
354425 {
355426 // 明らかに長くなると推測できる場合は短縮しない
356- if ("http://ux.nx/xxxxxx".Length > srcUrl.Length)
357- return srcUrl;
427+ if ("http://ux.nx/xxxxxx".Length > srcUri.OriginalString.Length)
428+ return srcUri;
358429
359- var param = new Dictionary<string, string>
430+ var query = HttpUtility.ParseQueryString(string.Empty);
431+ query["format"] = "plain";
432+ query["url"] = srcUri.OriginalString;
433+
434+ using (var response = await this.http.GetAsync("http://ux.nu/api/short?" + query).ConfigureAwait(false))
360435 {
361- {"format", "plain"},
362- {"url", srcUrl},
363- };
436+ response.EnsureSuccessStatusCode();
437+
438+ var result = await response.Content.ReadAsStringAsync()
439+ .ConfigureAwait(false);
364440
365- string response;
366- if (!this.http.GetData("http://ux.nu/api/short", param, out response))
367- throw new WebApiException("Failed to create URL.", response);
441+ if (!Regex.IsMatch(result, @"^https?://"))
442+ throw new WebApiException("Failed to create URL.", result);
443+
444+ return new Uri(result.TrimEnd());
445+ }
446+ }
368447
369- if (!Regex.IsMatch(response, @"^https?://"))
370- throw new WebApiException("Failed to create URL.", response);
448+ private async Task<Uri> GetRedirectTo(Uri url)
449+ {
450+ var request = new HttpRequestMessage(HttpMethod.Head, url);
371451
372- return response.TrimEnd();
452+ using (var response = await this.http.SendAsync(request).ConfigureAwait(false))
453+ {
454+ if (!response.IsSuccessStatusCode)
455+ {
456+ // ステータスコードが 3xx であれば例外を発生させない
457+ if ((int)response.StatusCode / 100 != 3)
458+ response.EnsureSuccessStatusCode();
459+ }
460+
461+ return response.Headers.Location;
462+ }
373463 }
374464 }
375465 }
--- a/OpenTween/ShowUserInfo.cs
+++ b/OpenTween/ShowUserInfo.cs
@@ -31,6 +31,7 @@ using System.Data;
3131 using System.Drawing;
3232 using System.Linq;
3333 using System.Text;
34+using System.Threading.Tasks;
3435 using System.Windows.Forms;
3536 using System.Text.RegularExpressions;
3637 using System.Web;
@@ -49,7 +50,6 @@ namespace OpenTween
4950 private UserInfo _info = new UserInfo();
5051 private Image icondata = null;
5152 private List<string> atlist = new List<string>();
52- private string descriptionTxt;
5353 private string recentPostTxt;
5454
5555 private const string Mainpath = "https://twitter.com/";
@@ -124,23 +124,25 @@ namespace OpenTween
124124 return true;
125125 }
126126
127- private void SetLinklabelWeb(string data)
127+ private async Task SetLinklabelWebAsync(string data)
128128 {
129129 string webtext;
130130 string jumpto;
131131 webtext = MyOwner.TwitterInstance.PreProcessUrl("<a href=\"" + data + "\">Dummy</a>");
132- webtext = ShortUrl.Instance.ExpandUrlHtml(webtext);
132+ webtext = await ShortUrl.Instance.ExpandUrlHtmlAsync(webtext);
133133 jumpto = Regex.Match(webtext, @"<a href=""(?<url>.*?)""").Groups["url"].Value;
134134 ToolTip1.SetToolTip(LinkLabelWeb, jumpto);
135135 LinkLabelWeb.Tag = jumpto;
136136 LinkLabelWeb.Text = data;
137137 }
138138
139- private string MakeDescriptionBrowserText(string data)
139+ private async Task SetDescriptionAsync(string descriptionText)
140140 {
141- descriptionTxt = MyOwner.createDetailHtml(
142- MyOwner.TwitterInstance.CreateHtmlAnchor(WebUtility.HtmlEncode(data), atlist, null));
143- return descriptionTxt;
141+ var html = WebUtility.HtmlEncode(descriptionText);
142+ html = await this.MyOwner.TwitterInstance.CreateHtmlAnchorAsync(html, this.atlist, null);
143+ html = this.MyOwner.createDetailHtml(html);
144+
145+ this.DescriptionBrowser.DocumentText = html;
144146 }
145147
146148 private void ShowUserInfo_FormClosed(object sender, FormClosedEventArgs e)
@@ -149,7 +151,7 @@ namespace OpenTween
149151 //TweenMain.TopMost = !TweenMain.TopMost;
150152 }
151153
152- private void ShowUserInfo_Load(object sender, EventArgs e)
154+ private async void ShowUserInfo_Load(object sender, EventArgs e)
153155 {
154156 MyOwner = (TweenMain)this.Owner;
155157 if (!AnalizeUserInfo(userInfo))
@@ -159,6 +161,9 @@ namespace OpenTween
159161 return;
160162 }
161163
164+ // LabelScreenName のフォントを OTBaseForm.GlobalFont に変更
165+ this.LabelScreenName.Font = this.ReplaceToGlobalFont(this.LabelScreenName.Font);
166+
162167 //アイコンロード
163168 BackgroundWorkerImageLoader.RunWorkerAsync();
164169
@@ -171,10 +176,7 @@ namespace OpenTween
171176
172177 LabelLocation.Text = _info.Location;
173178
174- SetLinklabelWeb(_info.Url);
175-
176- DescriptionBrowser.Visible = false;
177- MakeDescriptionBrowserText(_info.Description);
179+ var linkTask = this.SetLinklabelWebAsync(this._info.Url);
178180
179181 RecentPostBrowser.Visible = false;
180182 if (_info.RecentPost != null)
@@ -219,8 +221,7 @@ namespace OpenTween
219221 ButtonBlockDestroy.Enabled = true;
220222 }
221223
222- // LabelScreenName のフォントを OTBaseForm.GlobalFont に変更
223- this.LabelScreenName.Font = this.ReplaceToGlobalFont(this.LabelScreenName.Font);
224+ await linkTask;
224225 }
225226
226227 private void ButtonClose_Click(object sender, EventArgs e)
@@ -376,10 +377,10 @@ namespace OpenTween
376377
377378 }
378379
379- private void ShowUserInfo_Shown(object sender, EventArgs e)
380+ private async void ShowUserInfo_Shown(object sender, EventArgs e)
380381 {
381- DescriptionBrowser.DocumentText = descriptionTxt;
382- DescriptionBrowser.Visible = true;
382+ var descriptionTask = this.SetDescriptionAsync(this._info.Description);
383+
383384 if (_info.RecentPost != null)
384385 {
385386 RecentPostBrowser.DocumentText = recentPostTxt;
@@ -390,6 +391,8 @@ namespace OpenTween
390391 LabelRecentPost.Text = Properties.Resources.ShowUserInfo2;
391392 }
392393 ButtonClose.Focus();
394+
395+ await descriptionTask;
393396 }
394397
395398 private void WebBrowser_Navigating(object sender, WebBrowserNavigatingEventArgs e)
@@ -546,11 +549,13 @@ namespace OpenTween
546549 private bool IsEditing = false;
547550 private string ButtonEditText = "";
548551
549- private void ButtonEdit_Click(object sender, EventArgs e)
552+ private async void ButtonEdit_Click(object sender, EventArgs e)
550553 {
551554 // 自分以外のプロフィールは変更できない
552555 if (MyOwner.TwitterInstance.Username != _info.ScreenName) return;
553556
557+ this.ButtonEdit.Enabled = false;
558+
554559 if (!IsEditing)
555560 {
556561 ButtonEditText = ButtonEdit.Text;
@@ -645,13 +650,14 @@ namespace OpenTween
645650 TextBoxLocation.Visible = false;
646651 LabelLocation.Visible = true;
647652
648- SetLinklabelWeb(TextBoxWeb.Text);
653+ var linkTask = this.SetLinklabelWebAsync(this.TextBoxWeb.Text);
649654 _info.Url = TextBoxWeb.Text;
650655 TextBoxWeb.TabStop = false;
651656 TextBoxWeb.Visible = false;
652657 LinkLabelWeb.Visible = true;
653658
654- DescriptionBrowser.DocumentText = MakeDescriptionBrowserText(TextBoxDescription.Text);
659+ var descriptionTask = this.SetDescriptionAsync(this.TextBoxDescription.Text);
660+
655661 _info.Description = TextBoxDescription.Text;
656662 TextBoxDescription.TabStop = false;
657663 TextBoxDescription.Visible = false;
@@ -660,8 +666,11 @@ namespace OpenTween
660666 ButtonEdit.Text = ButtonEditText;
661667
662668 IsEditing = false;
669+
670+ await Task.WhenAll(new[] { linkTask, descriptionTask });
663671 }
664672
673+ this.ButtonEdit.Enabled = true;
665674 }
666675
667676 class UpdateProfileImageArgs
--- a/OpenTween/Thumbnail/Services/Pixiv.cs
+++ b/OpenTween/Thumbnail/Services/Pixiv.cs
@@ -53,19 +53,18 @@ namespace OpenTween.Thumbnail.Services
5353
5454 public class Thumbnail : ThumbnailInfo
5555 {
56- public override Task<MemoryImage> LoadThumbnailImageAsync(CancellationToken token)
56+ public async override Task<MemoryImage> LoadThumbnailImageAsync(CancellationToken token)
5757 {
58- var client = new OTWebClient();
58+ using (var client = new OTWebClient())
59+ {
60+ client.UserAgent = MyCommon.GetUserAgentString(fakeMSIE: true);
61+ client.Headers[HttpRequestHeader.Referer] = this.ImageUrl;
5962
60- client.UserAgent = MyCommon.GetUserAgentString(fakeMSIE: true);
61- client.Headers[HttpRequestHeader.Referer] = this.ImageUrl;
63+ var imageBytes = await client.DownloadDataAsync(new Uri(this.ThumbnailUrl), token)
64+ .ConfigureAwait(false);
6265
63- var task = client.DownloadDataAsync(new Uri(this.ThumbnailUrl), token)
64- .ContinueWith(t => MemoryImage.CopyFromBytes(t.Result), TaskScheduler.Default);
65-
66- task.ContinueWith(_ => client.Dispose(), TaskScheduler.Default);
67-
68- return task;
66+ return MemoryImage.CopyFromBytes(imageBytes);
67+ }
6968 }
7069 }
7170 }
--- a/OpenTween/Thumbnail/Services/TonTwitterCom.cs
+++ b/OpenTween/Thumbnail/Services/TonTwitterCom.cs
@@ -61,7 +61,7 @@ namespace OpenTween.Thumbnail.Services
6161 {
6262 public override Task<MemoryImage> LoadThumbnailImageAsync(CancellationToken token)
6363 {
64- return Task.Factory.StartNew(() =>
64+ return Task.Run(() =>
6565 {
6666 var oauth = new HttpOAuthApiProxy();
6767 TonTwitterCom.InitializeOAuthToken(oauth);
@@ -72,12 +72,11 @@ namespace OpenTween.Thumbnail.Services
7272 using (response)
7373 {
7474 if (statusCode == HttpStatusCode.OK)
75- return MemoryImage.CopyFromStream(response);
75+ return MemoryImage.CopyFromStreamAsync(response);
7676 else
7777 throw new WebException(statusCode.ToString(), WebExceptionStatus.ProtocolError);
7878 }
79- },
80- token, TaskCreationOptions.None, TaskScheduler.Default); // 明示しないと TaskScheduler.Current になり UI スレッド上で実行されてしまう
79+ }, token);
8180 }
8281 }
8382 }
--- a/OpenTween/Thumbnail/ThumbnailInfo.cs
+++ b/OpenTween/Thumbnail/ThumbnailInfo.cs
@@ -40,16 +40,15 @@ namespace OpenTween.Thumbnail
4040 return this.LoadThumbnailImageAsync(CancellationToken.None);
4141 }
4242
43- public virtual Task<MemoryImage> LoadThumbnailImageAsync(CancellationToken token)
43+ public async virtual Task<MemoryImage> LoadThumbnailImageAsync(CancellationToken token)
4444 {
45- var client = new OTWebClient();
45+ using (var client = new OTWebClient())
46+ {
47+ var imageBytes = await client.DownloadDataAsync(new Uri(this.ThumbnailUrl), token)
48+ .ConfigureAwait(false);
4649
47- var task = client.DownloadDataAsync(new Uri(this.ThumbnailUrl), token)
48- .ContinueWith(t => MemoryImage.CopyFromBytes(t.Result), TaskScheduler.Default);
49-
50- task.ContinueWith(_ => client.Dispose(), TaskScheduler.Default);
51-
52- return task;
50+ return MemoryImage.CopyFromBytes(imageBytes);
51+ }
5352 }
5453 }
5554 }
--- a/OpenTween/Tween.cs
+++ b/OpenTween/Tween.cs
@@ -2148,7 +2148,7 @@ namespace OpenTween
21482148 return cl;
21492149 }
21502150
2151- private void PostButton_Click(object sender, EventArgs e)
2151+ private async void PostButton_Click(object sender, EventArgs e)
21522152 {
21532153 if (StatusText.Text.Trim().Length == 0)
21542154 {
@@ -2181,7 +2181,7 @@ namespace OpenTween
21812181 if (SettingDialog.Nicoms)
21822182 {
21832183 StatusText.SelectionStart = StatusText.Text.Length;
2184- UrlConvert(MyCommon.UrlConverter.Nicoms);
2184+ await UrlConvertAsync(MyCommon.UrlConverter.Nicoms);
21852185 }
21862186 //if (SettingDialog.UrlConvertAuto)
21872187 //{
@@ -2341,13 +2341,6 @@ namespace OpenTween
23412341
23422342 RunAsync(args);
23432343
2344- //Google検索(試験実装)
2345- if (StatusText.Text.StartsWith("Google:", StringComparison.OrdinalIgnoreCase) && StatusText.Text.Trim().Length > 7)
2346- {
2347- string tmp = string.Format(Properties.Resources.SearchItem2Url, Uri.EscapeUriString(StatusText.Text.Substring(7)));
2348- OpenUriAsync(tmp);
2349- }
2350-
23512344 _reply_to_id = null;
23522345 _reply_to_name = null;
23532346 StatusText.Text = "";
@@ -2357,6 +2350,13 @@ namespace OpenTween
23572350 ((Control)ListTab.SelectedTab.Tag).Focus();
23582351 urlUndoBuffer = null;
23592352 UrlUndoToolStripMenuItem.Enabled = false; //Undoをできないように設定
2353+
2354+ //Google検索(試験実装)
2355+ if (StatusText.Text.StartsWith("Google:", StringComparison.OrdinalIgnoreCase) && StatusText.Text.Trim().Length > 7)
2356+ {
2357+ string tmp = string.Format(Properties.Resources.SearchItem2Url, Uri.EscapeUriString(StatusText.Text.Substring(7)));
2358+ await this.OpenUriAsync(tmp);
2359+ }
23602360 }
23612361
23622362 private void EndToolStripMenuItem_Click(object sender, EventArgs e)
@@ -5086,34 +5086,33 @@ namespace OpenTween
50865086 finally { this.itemCacheLock.ExitUpgradeableReadLock(); }
50875087 }
50885088
5089- private void MyList_RetrieveVirtualItem(object sender, RetrieveVirtualItemEventArgs e)
5089+ private async void MyList_RetrieveVirtualItem(object sender, RetrieveVirtualItemEventArgs e)
50905090 {
5091- ListViewItem cacheItem = null;
5091+ ListViewItem item = null;
50925092 PostClass cacheItemPost = null;
50935093
5094- this.TryGetListViewItemCache(e.ItemIndex, out cacheItem, out cacheItemPost);
5094+ this.TryGetListViewItemCache(e.ItemIndex, out item, out cacheItemPost);
50955095
5096- if (cacheItem != null)
5097- {
5098- e.Item = cacheItem;
5099- }
5100- else
5096+ if (item == null)
51015097 {
51025098 //A cache miss, so create a new ListViewItem and pass it back.
51035099 TabPage tb = (TabPage)((DetailsListView)sender).Parent;
51045100 try
51055101 {
5106- e.Item = CreateItem(tb,
5107- _statuses[tb.Text, e.ItemIndex],
5108- e.ItemIndex);
5102+ item = this.CreateItem(tb, _statuses[tb.Text, e.ItemIndex], e.ItemIndex);
51095103 }
51105104 catch (Exception)
51115105 {
51125106 //不正な要求に対する間に合わせの応答
51135107 string[] sitem = {"", "", "", "", "", "", "", ""};
5114- e.Item = new ImageListViewItem(sitem);
5108+ item = new ImageListViewItem(sitem);
51155109 }
51165110 }
5111+
5112+ // e.Item に値をセットする前に await しないこと
5113+ e.Item = item;
5114+
5115+ await ((ImageListViewItem)item).GetImageAsync();
51175116 }
51185117
51195118 private void CreateCache(int StartIndex, int EndIndex)
@@ -5466,7 +5465,7 @@ namespace OpenTween
54665465 }
54675466 catch (ArgumentException)
54685467 {
5469- item.RefreshImage();
5468+ item.RefreshImageAsync();
54705469 }
54715470 }
54725471
@@ -5883,9 +5882,9 @@ namespace OpenTween
58835882 }
58845883 }
58855884
5886- private void VerUpMenuItem_Click(object sender, EventArgs e)
5885+ private async void VerUpMenuItem_Click(object sender, EventArgs e)
58875886 {
5888- CheckNewVersion();
5887+ await this.CheckNewVersion(false);
58895888 }
58905889
58915890 private void RunTweenUp()
@@ -5905,62 +5904,90 @@ namespace OpenTween
59055904 }
59065905 }
59075906
5908- private void CheckNewVersion(bool startup = false)
5907+ public class VersionInfo
59095908 {
5910- if (ApplicationSettings.VersionInfoUrl == null)
5911- return; // 更新チェック無効化
5909+ public Version Version { get; set; }
5910+ public Uri DownloadUri { get; set; }
5911+ public string ReleaseNote { get; set; }
5912+ }
59125913
5913- if (string.IsNullOrEmpty(MyCommon.fileVersion))
5914- {
5915- return;
5916- }
5914+ /// <summary>
5915+ /// OpenTween の最新バージョンの情報を取得します
5916+ /// </summary>
5917+ public async Task<VersionInfo> GetVersionInfoAsync()
5918+ {
5919+ var http = MyCommon.CreateHttpClient();
59175920
5918- string retMsg;
5919- try
5920- {
5921- retMsg = tw.GetVersionInfo();
5922- }
5923- catch
5924- {
5925- retMsg = "";
5926- }
5921+ var versionInfoUrl = new Uri(ApplicationSettings.VersionInfoUrl + "?" +
5922+ DateTime.Now.ToString("yyMMddHHmmss") + Environment.TickCount);
59275923
5928- if (string.IsNullOrEmpty(retMsg))
5929- {
5930- StatusLabel.Text = Properties.Resources.CheckNewVersionText9;
5931- if (!startup) MessageBox.Show(Properties.Resources.CheckNewVersionText10, MyCommon.ReplaceAppName(Properties.Resources.CheckNewVersionText2), MessageBoxButtons.OK, MessageBoxIcon.Exclamation, MessageBoxDefaultButton.Button2);
5932- return;
5933- }
5924+ var responseText = await http.GetStringAsync(versionInfoUrl)
5925+ .ConfigureAwait(false);
59345926
59355927 // 改行2つで前後パートを分割(前半がバージョン番号など、後半が詳細テキスト)
5936- string[] msgPart = retMsg.Split(new string[] {"\n\n", "\r\n\r\n"}, 2, StringSplitOptions.None);
5928+ var msgPart = responseText.Split(new[] { "\n\n", "\r\n\r\n" }, 2, StringSplitOptions.None);
59375929
5938- string[] msgHeader = msgPart[0].Split(new string[] {"\n", "\r\n"}, StringSplitOptions.None);
5939- string msgBody = msgPart.Length == 2 ? msgPart[1] : "";
5930+ var msgHeader = msgPart[0].Split(new[] { "\n", "\r\n" }, StringSplitOptions.None);
5931+ var msgBody = msgPart.Length == 2 ? msgPart[1] : "";
59405932
59415933 msgBody = Regex.Replace(msgBody, "(?<!\r)\n", "\r\n"); // LF -> CRLF
59425934
5943- string currentVersion = msgHeader[0];
5944- string downloadUrl = msgHeader[1];
5935+ return new VersionInfo
5936+ {
5937+ Version = Version.Parse(msgHeader[0]),
5938+ DownloadUri = new Uri(msgHeader[1]),
5939+ ReleaseNote = msgBody,
5940+ };
5941+ }
5942+
5943+ private async Task CheckNewVersion(bool startup = false)
5944+ {
5945+ if (ApplicationSettings.VersionInfoUrl == null)
5946+ return; // 更新チェック無効化
5947+
5948+ if (string.IsNullOrEmpty(MyCommon.fileVersion))
5949+ return;
59455950
5946- if (currentVersion.Replace(".", "").CompareTo(MyCommon.fileVersion.Replace(".", "")) > 0)
5951+ try
59475952 {
5953+ var versionInfo = await this.GetVersionInfoAsync();
5954+
5955+ if (versionInfo.Version <= Version.Parse(MyCommon.fileVersion))
5956+ {
5957+ // 更新不要
5958+ if (!startup)
5959+ {
5960+ var msgtext = string.Format(Properties.Resources.CheckNewVersionText7,
5961+ MyCommon.GetReadableVersion(), MyCommon.GetReadableVersion(versionInfo.Version));
5962+ msgtext = MyCommon.ReplaceAppName(msgtext);
5963+
5964+ MessageBox.Show(msgtext,
5965+ MyCommon.ReplaceAppName(Properties.Resources.CheckNewVersionText2),
5966+ MessageBoxButtons.OK, MessageBoxIcon.Information);
5967+ }
5968+ return;
5969+ }
5970+
59485971 using (var dialog = new UpdateDialog())
59495972 {
5950- dialog.SummaryText = string.Format(Properties.Resources.CheckNewVersionText3, MyCommon.GetReadableVersion(currentVersion));
5951- dialog.DetailsText = msgBody;
5973+ dialog.SummaryText = string.Format(Properties.Resources.CheckNewVersionText3,
5974+ MyCommon.GetReadableVersion(versionInfo.Version));
5975+ dialog.DetailsText = versionInfo.ReleaseNote;
5976+
59525977 if (dialog.ShowDialog(this) == DialogResult.Yes)
59535978 {
5954- this.OpenUriAsync(downloadUrl);
5979+ await this.OpenUriAsync(versionInfo.DownloadUri.OriginalString);
59555980 }
59565981 }
59575982 }
5958- else
5983+ catch (Exception)
59595984 {
5985+ this.StatusLabel.Text = Properties.Resources.CheckNewVersionText9;
59605986 if (!startup)
59615987 {
5962- var msgtext = MyCommon.ReplaceAppName(string.Format(Properties.Resources.CheckNewVersionText7, MyCommon.GetReadableVersion(), MyCommon.GetReadableVersion(currentVersion)));
5963- MessageBox.Show(msgtext, MyCommon.ReplaceAppName(Properties.Resources.CheckNewVersionText2), MessageBoxButtons.OK, MessageBoxIcon.Information);
5988+ MessageBox.Show(Properties.Resources.CheckNewVersionText10,
5989+ MyCommon.ReplaceAppName(Properties.Resources.CheckNewVersionText2),
5990+ MessageBoxButtons.OK, MessageBoxIcon.Exclamation, MessageBoxDefaultButton.Button2);
59645991 }
59655992 }
59665993 }
@@ -5996,7 +6023,7 @@ namespace OpenTween
59966023 return detailHtmlFormatHeader + orgdata + detailHtmlFormatFooter;
59976024 }
59986025
5999- private void DisplayItemImage_Downloaded(object sender, EventArgs e)
6026+ private async void DisplayItemImage_Downloaded(object sender, EventArgs e)
60006027 {
60016028 if (sender.Equals(displayItem))
60026029 {
@@ -6005,7 +6032,10 @@ namespace OpenTween
60056032 var img = displayItem.Image;
60066033 try
60076034 {
6008- UserPicture.Image = img != null ? img.Clone() : null;
6035+ if (img != null)
6036+ img = await img.CloneAsync();
6037+
6038+ UserPicture.Image = img;
60096039 }
60106040 catch (Exception)
60116041 {
@@ -6021,6 +6051,16 @@ namespace OpenTween
60216051
60226052 private static PostClass displaypost = new PostClass();
60236053
6054+ /// <summary>
6055+ /// サムネイルの表示処理を表すタスク
6056+ /// </summary>
6057+ private Task thumbnailTask = null;
6058+
6059+ /// <summary>
6060+ /// サムネイル表示に使用する CancellationToken の生成元
6061+ /// </summary>
6062+ private CancellationTokenSource thumbnailTokenSource = null;
6063+
60246064 private void DispSelectedPost(bool forceupdate)
60256065 {
60266066 if (_curList.SelectedIndices.Count == 0 || _curPost == null)
@@ -6170,7 +6210,19 @@ namespace OpenTween
61706210 this.SplitContainer3.Panel2Collapsed = true;
61716211
61726212 if (this.IsPreviewEnable)
6173- this.tweetThumbnail1.ShowThumbnailAsync(_curPost);
6213+ {
6214+ if (this.thumbnailTokenSource != null)
6215+ {
6216+ var oldTokenSource = this.thumbnailTokenSource;
6217+ oldTokenSource.Cancel();
6218+ this.thumbnailTask.ContinueWith(_ => oldTokenSource.Dispose());
6219+ }
6220+
6221+ this.thumbnailTokenSource = new CancellationTokenSource();
6222+
6223+ var token = this.thumbnailTokenSource.Token;
6224+ this.thumbnailTask = this.tweetThumbnail1.ShowThumbnailAsync(_curPost, token);
6225+ }
61746226 }
61756227 }
61766228 catch (System.Runtime.InteropServices.COMException)
@@ -7878,7 +7930,7 @@ namespace OpenTween
78787930 tabSetting.Save();
78797931 }
78807932
7881- private /* async */ void OpenURLFileMenuItem_Click(object sender, EventArgs e)
7933+ private async void OpenURLFileMenuItem_Click(object sender, EventArgs e)
78827934 {
78837935 string inputText;
78847936 var ret = InputDialog.Show(this, Properties.Resources.OpenURL_InputText, Properties.Resources.OpenURL_Caption, out inputText);
@@ -7894,40 +7946,40 @@ namespace OpenTween
78947946 }
78957947
78967948 var statusId = long.Parse(match.Groups["StatusId"].Value);
7897- var uiScheduler = TaskScheduler.FromCurrentSynchronizationContext();
78987949
7899- Task.Factory.StartNew(() =>
7950+ var post = this._statuses[statusId];
7951+ if (post == null)
7952+ {
7953+ try
79007954 {
7901- var post = this._statuses[statusId];
7902- if (post == null)
7955+ post = await Task.Run(() =>
79037956 {
7904- var err = this.tw.GetStatusApi(false, statusId, ref post);
7957+ PostClass newPost = null;
7958+
7959+ var err = this.tw.GetStatusApi(false, statusId, ref newPost);
79057960 if (!string.IsNullOrEmpty(err))
79067961 throw new WebApiException(err);
7907- }
7908- return post;
7909- }, CancellationToken.None, TaskCreationOptions.None, TaskScheduler.Default)
7910- .ContinueWith(t =>
7911- {
7912- if (t.IsFaulted)
7913- {
7914- t.Exception.Flatten().Handle(x => x is WebApiException);
79157962
7916- var message = t.Exception.InnerException.Message;
7917- MessageBox.Show(this, string.Format(Properties.Resources.OpenURL_LoadFailed, message),
7918- Properties.Resources.OpenURL_Caption, MessageBoxButtons.OK, MessageBoxIcon.Error);
7963+ return newPost;
7964+ });
7965+ }
7966+ catch (WebApiException ex)
7967+ {
7968+ var message = ex.InnerException.Message;
7969+ MessageBox.Show(this, string.Format(Properties.Resources.OpenURL_LoadFailed, message),
7970+ Properties.Resources.OpenURL_Caption, MessageBoxButtons.OK, MessageBoxIcon.Error);
7971+ return;
7972+ }
7973+ }
79197974
7920- return;
7921- }
7922- try
7923- {
7924- this.OpenRelatedTab(t.Result);
7925- }
7926- catch (TabException ex)
7927- {
7928- MessageBox.Show(this, ex.Message, Application.ProductName, MessageBoxButtons.OK, MessageBoxIcon.Error);
7929- }
7930- }, uiScheduler);
7975+ try
7976+ {
7977+ this.OpenRelatedTab(post);
7978+ }
7979+ catch (TabException ex)
7980+ {
7981+ MessageBox.Show(this, ex.Message, Application.ProductName, MessageBoxButtons.OK, MessageBoxIcon.Error);
7982+ }
79317983 }
79327984
79337985 private void SaveLogMenuItem_Click(object sender, EventArgs e)
@@ -9717,28 +9769,21 @@ namespace OpenTween
97179769 OpenUriAsync(name.Remove(name.LastIndexOf("_normal"), 7)); // "_normal".Length
97189770 }
97199771
9720- private /* async */ void ReloadIconToolStripMenuItem_Click(object sender, EventArgs e)
9772+ private async void ReloadIconToolStripMenuItem_Click(object sender, EventArgs e)
97219773 {
97229774 if (this._curPost == null) return;
97239775
97249776 var imageUrl = this._curPost.ImageUrl;
9725- var uiScheduler = TaskScheduler.FromCurrentSynchronizationContext();
9726-
9727- this.IconCache.DownloadImageAsync(imageUrl, force: true)
9728- .ContinueWith(t =>
9729- {
9730- this.ClearUserPicture();
9777+ try
9778+ {
9779+ var image = await this.IconCache.DownloadImageAsync(imageUrl, force: true);
97319780
9732- if (t.IsFaulted)
9733- {
9734- t.Exception.Flatten().Handle(x => x is WebException || x is InvalidImageException || x is TaskCanceledException);
9735- this.UserPicture.ShowErrorImage();
9736- }
9737- else
9738- {
9739- this.UserPicture.Image = t.Result.Clone() ;
9740- }
9741- }, uiScheduler);
9781+ this.ClearUserPicture();
9782+ this.UserPicture.Image = image.Clone();
9783+ }
9784+ catch (WebException) { this.UserPicture.ShowErrorImage(); }
9785+ catch (InvalidImageException) { this.UserPicture.ShowErrorImage(); }
9786+ catch (TaskCanceledException) { this.UserPicture.ShowErrorImage(); }
97429787 }
97439788
97449789 private void SaveOriginalSizeIconPictureToolStripMenuItem_Click(object sender, EventArgs e)
@@ -9822,7 +9867,7 @@ namespace OpenTween
98229867 _modifySettingLocal = true;
98239868 }
98249869
9825- private bool UrlConvert(MyCommon.UrlConverter Converter_Type)
9870+ private async Task<bool> UrlConvertAsync(MyCommon.UrlConverter Converter_Type)
98269871 {
98279872 //t.coで投稿時自動短縮する場合は、外部サービスでの短縮禁止
98289873 //if (SettingDialog.UrlConvertAuto && SettingDialog.ShortenTco) return;
@@ -9854,7 +9899,9 @@ namespace OpenTween
98549899 //短縮URL変換 日本語を含むかもしれないのでURLエンコードする
98559900 try
98569901 {
9857- result = ShortUrl.Instance.ShortenUrl(Converter_Type, tmp);
9902+ var srcUri = new Uri(MyCommon.urlEncodeMultibyteChar(tmp));
9903+ var resultUri = await ShortUrl.Instance.ShortenUrlAsync(Converter_Type, srcUri);
9904+ result = resultUri.ToString();
98589905 }
98599906 catch (WebApiException e)
98609907 {
@@ -9921,7 +9968,9 @@ namespace OpenTween
99219968 //短縮URL変換 日本語を含むかもしれないのでURLエンコードする
99229969 try
99239970 {
9924- result = ShortUrl.Instance.ShortenUrl(Converter_Type, tmp);
9971+ var srcUri = new Uri(MyCommon.urlEncodeMultibyteChar(tmp));
9972+ var resultUri = await ShortUrl.Instance.ShortenUrlAsync(Converter_Type, srcUri);
9973+ result = resultUri.ToString();
99259974 }
99269975 catch (WebApiException e)
99279976 {
@@ -9978,29 +10027,29 @@ namespace OpenTween
997810027 }
997910028 }
998010029
9981- private void TinyURLToolStripMenuItem_Click(object sender, EventArgs e)
10030+ private async void TinyURLToolStripMenuItem_Click(object sender, EventArgs e)
998210031 {
9983- UrlConvert(MyCommon.UrlConverter.TinyUrl);
10032+ await UrlConvertAsync(MyCommon.UrlConverter.TinyUrl);
998410033 }
998510034
9986- private void IsgdToolStripMenuItem_Click(object sender, EventArgs e)
10035+ private async void IsgdToolStripMenuItem_Click(object sender, EventArgs e)
998710036 {
9988- UrlConvert(MyCommon.UrlConverter.Isgd);
10037+ await UrlConvertAsync(MyCommon.UrlConverter.Isgd);
998910038 }
999010039
9991- private void TwurlnlToolStripMenuItem_Click(object sender, EventArgs e)
10040+ private async void TwurlnlToolStripMenuItem_Click(object sender, EventArgs e)
999210041 {
9993- UrlConvert(MyCommon.UrlConverter.Twurl);
10042+ await UrlConvertAsync(MyCommon.UrlConverter.Twurl);
999410043 }
999510044
9996- private void UxnuMenuItem_Click(object sender, EventArgs e)
10045+ private async void UxnuMenuItem_Click(object sender, EventArgs e)
999710046 {
9998- UrlConvert(MyCommon.UrlConverter.Uxnu);
10047+ await UrlConvertAsync(MyCommon.UrlConverter.Uxnu);
999910048 }
1000010049
10001- private void UrlConvertAutoToolStripMenuItem_Click(object sender, EventArgs e)
10050+ private async void UrlConvertAutoToolStripMenuItem_Click(object sender, EventArgs e)
1000210051 {
10003- if (!UrlConvert(SettingDialog.AutoShortUrlFirst))
10052+ if (!await UrlConvertAsync(SettingDialog.AutoShortUrlFirst))
1000410053 {
1000510054 MyCommon.UrlConverter svc = SettingDialog.AutoShortUrlFirst;
1000610055 Random rnd = new Random();
@@ -10010,7 +10059,7 @@ namespace OpenTween
1001010059 svc = (MyCommon.UrlConverter)rnd.Next(System.Enum.GetNames(typeof(MyCommon.UrlConverter)).Length);
1001110060 }
1001210061 while (svc == SettingDialog.AutoShortUrlFirst || svc == MyCommon.UrlConverter.Nicoms || svc == MyCommon.UrlConverter.Unu);
10013- UrlConvert(svc);
10062+ await UrlConvertAsync(svc);
1001410063 }
1001510064 }
1001610065
@@ -10524,7 +10573,7 @@ namespace OpenTween
1052410573
1052510574 public Task OpenUriAsync(string UriString)
1052610575 {
10527- return Task.Factory.StartNew(() =>
10576+ return Task.Run(() =>
1052810577 {
1052910578 string myPath = UriString;
1053010579
@@ -10740,7 +10789,7 @@ namespace OpenTween
1074010789 if (SettingDialog.UserstreamStartup) tw.StartUserStream();
1074110790 }
1074210791
10743- private void TweenMain_Shown(object sender, EventArgs e)
10792+ private async void TweenMain_Shown(object sender, EventArgs e)
1074410793 {
1074510794 try
1074610795 {
@@ -10780,21 +10829,17 @@ namespace OpenTween
1078010829 GetTimeline(MyCommon.WORKERTYPE.UserTimeline, 1, 0, ""); //tabname="":全タブ
1078110830 _waitLists = true;
1078210831 GetTimeline(MyCommon.WORKERTYPE.List, 1, 0, ""); //tabname="":全タブ
10783- int i = 0;
10784- int j = 0;
10785- while (IsInitialRead() && !MyCommon._endingFlag)
10832+
10833+ var i = 0;
10834+ while (this.IsInitialRead())
1078610835 {
10787- System.Threading.Thread.Sleep(100);
10788- Application.DoEvents();
10836+ await Task.Delay(5000);
10837+
1078910838 i += 1;
10790- j += 1;
10791- if (j > 1200) break; // 120秒間初期処理が終了しなかったら強制的に打ち切る
10792- if (i > 50)
10793- {
10794- if (MyCommon._endingFlag)
10795- return;
10796- i = 0;
10797- }
10839+ if (i > 24) break; // 120秒間初期処理が終了しなかったら強制的に打ち切る
10840+
10841+ if (MyCommon._endingFlag)
10842+ return;
1079810843 }
1079910844
1080010845 if (MyCommon._endingFlag) return;
@@ -10803,7 +10848,7 @@ namespace OpenTween
1080310848 {
1080410849 //バージョンチェック(引数:起動時チェックの場合はtrue・・・チェック結果のメッセージを表示しない)
1080510850 if (SettingDialog.StartupVersion)
10806- CheckNewVersion(true);
10851+ await this.CheckNewVersion(true);
1080710852 }
1080810853 else
1080910854 {
@@ -11052,14 +11097,14 @@ namespace OpenTween
1105211097 TabRename(ref _rclickTabName);
1105311098 }
1105411099
11055- private void BitlyToolStripMenuItem_Click(object sender, EventArgs e)
11100+ private async void BitlyToolStripMenuItem_Click(object sender, EventArgs e)
1105611101 {
11057- UrlConvert(MyCommon.UrlConverter.Bitly);
11102+ await UrlConvertAsync(MyCommon.UrlConverter.Bitly);
1105811103 }
1105911104
11060- private void JmpToolStripMenuItem_Click(object sender, EventArgs e)
11105+ private async void JmpToolStripMenuItem_Click(object sender, EventArgs e)
1106111106 {
11062- UrlConvert(MyCommon.UrlConverter.Jmp);
11107+ await UrlConvertAsync(MyCommon.UrlConverter.Jmp);
1106311108 }
1106411109
1106511110
--- a/OpenTween/TweetThumbnail.cs
+++ b/OpenTween/TweetThumbnail.cs
@@ -39,9 +39,6 @@ namespace OpenTween
3939 {
4040 protected internal List<OTPictureBox> pictureBox = new List<OTPictureBox>();
4141
42- private Task task = null;
43- private CancellationTokenSource cancelTokenSource;
44-
4542 public event EventHandler ThumbnailLoading;
4643 public event EventHandler<ThumbnailDoubleClickEventArgs> ThumbnailDoubleClick;
4744 public event EventHandler<ThumbnailImageSearchEventArgs> ThumbnailImageSearchClick;
@@ -56,76 +53,73 @@ namespace OpenTween
5653 public TweetThumbnail()
5754 {
5855 InitializeComponent();
59-
60- this.cancelTokenSource = new CancellationTokenSource();
6156 }
6257
6358 public Task ShowThumbnailAsync(PostClass post)
6459 {
65- this.CancelAsync();
66-
67- this.scrollBar.Enabled = false;
60+ return this.ShowThumbnailAsync(post, CancellationToken.None);
61+ }
6862
69- var cancelToken = this.cancelTokenSource.Token;
63+ public async Task ShowThumbnailAsync(PostClass post, CancellationToken cancelToken)
64+ {
7065 var uiScheduler = TaskScheduler.FromCurrentSynchronizationContext();
66+ var loadTasks = new List<Task>();
7167
72- this.task = Task.Factory.StartNew(() => this.GetThumbailInfo(post), cancelToken, TaskCreationOptions.None, TaskScheduler.Default)
73- .ContinueWith(t => /* await使いたい */
74- {
75- var thumbnails = t.Result;
68+ this.scrollBar.Enabled = false;
7669
77- lock (this.uiLockObj)
78- {
79- this.SetThumbnailCount(thumbnails.Count);
80- if (thumbnails.Count == 0) return;
70+ var thumbnails = await Task.Run(() => this.GetThumbailInfo(post), cancelToken);
8171
82- for (int i = 0; i < thumbnails.Count; i++)
83- {
84- var thumb = thumbnails[i];
85- var picbox = this.pictureBox[i];
72+ cancelToken.ThrowIfCancellationRequested();
8673
87- picbox.Tag = thumb;
88- picbox.ContextMenu = CreateContextMenu(thumb);
74+ lock (this.uiLockObj)
75+ {
76+ this.SetThumbnailCount(thumbnails.Count);
77+ if (thumbnails.Count == 0) return;
8978
90- picbox.ShowInitialImage();
79+ for (int i = 0; i < thumbnails.Count; i++)
80+ {
81+ var thumb = thumbnails[i];
82+ var picbox = this.pictureBox[i];
9183
92- thumb.LoadThumbnailImageAsync(cancelToken)
93- .ContinueWith(t2 =>
94- {
95- if (t2.IsFaulted)
96- t2.Exception.Flatten().Handle(x => x is WebException || x is InvalidImageException || x is TaskCanceledException);
84+ picbox.Tag = thumb;
85+ picbox.ContextMenu = CreateContextMenu(thumb);
9786
98- if (t2.IsFaulted || t2.IsCanceled)
99- {
100- picbox.ShowErrorImage();
101- return;
102- }
87+ picbox.ShowInitialImage();
10388
104- picbox.Image = t2.Result;
105- },
106- CancellationToken.None, TaskContinuationOptions.AttachedToParent, uiScheduler);
89+ var loadTask = thumb.LoadThumbnailImageAsync(cancelToken)
90+ .ContinueWith(t2 =>
91+ {
92+ if (t2.IsFaulted)
93+ t2.Exception.Flatten().Handle(x => x is WebException || x is InvalidImageException || x is TaskCanceledException);
10794
108- var tooltipText = thumb.TooltipText;
109- if (!string.IsNullOrEmpty(tooltipText))
95+ if (t2.IsFaulted || t2.IsCanceled)
11096 {
111- this.toolTip.SetToolTip(picbox, tooltipText);
97+ picbox.ShowErrorImage();
98+ return;
11299 }
113100
114- cancelToken.ThrowIfCancellationRequested();
115- }
101+ picbox.Image = t2.Result;
102+ }, uiScheduler);
103+
104+ loadTasks.Add(loadTask);
116105
117- if (thumbnails.Count > 1)
118- this.scrollBar.Enabled = true;
106+ var tooltipText = thumb.TooltipText;
107+ if (!string.IsNullOrEmpty(tooltipText))
108+ {
109+ this.toolTip.SetToolTip(picbox, tooltipText);
119110 }
120111
121- if (this.ThumbnailLoading != null)
122- this.ThumbnailLoading(this, EventArgs.Empty);
123- },
124- cancelToken,
125- TaskContinuationOptions.OnlyOnRanToCompletion,
126- uiScheduler);
112+ cancelToken.ThrowIfCancellationRequested();
113+ }
114+
115+ if (thumbnails.Count > 1)
116+ this.scrollBar.Enabled = true;
117+ }
118+
119+ if (this.ThumbnailLoading != null)
120+ this.ThumbnailLoading(this, EventArgs.Empty);
127121
128- return this.task;
122+ await Task.WhenAll(loadTasks).ConfigureAwait(false);
129123 }
130124
131125 private ContextMenu CreateContextMenu(ThumbnailInfo thumb)
@@ -170,17 +164,6 @@ namespace OpenTween
170164 return ThumbnailGenerator.GetThumbnails(post);
171165 }
172166
173- public void CancelAsync()
174- {
175- if (this.task == null || this.task.IsCompleted) return;
176-
177- var oldTokenSource = this.cancelTokenSource;
178- this.cancelTokenSource = new CancellationTokenSource();
179-
180- oldTokenSource.Cancel();
181- oldTokenSource.Dispose();
182- }
183-
184167 /// <summary>
185168 /// 表示するサムネイルの数を設定する
186169 /// </summary>
--- a/OpenTween/Twitter.cs
+++ b/OpenTween/Twitter.cs
@@ -35,6 +35,7 @@ using System.Runtime.Serialization.Json;
3535 using System.Text;
3636 using System.Text.RegularExpressions;
3737 using System.Threading;
38+using System.Threading.Tasks;
3839 using System.Web;
3940 using System.Xml;
4041 using System.Xml.Linq;
@@ -1137,16 +1138,6 @@ namespace OpenTween
11371138 }
11381139
11391140 #region "バージョンアップ"
1140- public string GetVersionInfo()
1141- {
1142- var content = "";
1143- if (!(new HttpVarious()).GetData(ApplicationSettings.VersionInfoUrl + "?" + DateTime.Now.ToString("yyMMddHHmmss") + Environment.TickCount.ToString(), null, out content, MyCommon.GetUserAgentString()))
1144- {
1145- throw new Exception("GetVersionInfo Failed");
1146- }
1147- return content;
1148- }
1149-
11501141 public string GetTweenBinary(string strVer)
11511142 {
11521143 try
@@ -2780,7 +2771,7 @@ namespace OpenTween
27802771 this.toIndex = toIndex;
27812772 }
27822773 }
2783- public string CreateHtmlAnchor(string Text, List<string> AtList, Dictionary<string, string> media)
2774+ public async Task<string> CreateHtmlAnchorAsync(string Text, List<string> AtList, Dictionary<string, string> media)
27842775 {
27852776 if (Text == null) return null;
27862777 var retStr = Text.Replace("&gt;", "<<<<<tweenだいなり>>>>>").Replace("&lt;", "<<<<<tweenしょうなり>>>>>");
@@ -2812,21 +2803,18 @@ namespace OpenTween
28122803 // qry +
28132804 // ")"
28142805 //絶対パス表現のUriをリンクに置換
2815- retStr = Regex.Replace(retStr,
2816- rgUrl,
2817- new MatchEvaluator((Match mu) =>
2818- {
2819- var sb = new StringBuilder(mu.Result("${before}<a href=\""));
2820- //if (mu.Result("${protocol}").StartsWith("w", StringComparison.OrdinalIgnoreCase))
2821- // sb.Append("http://");
2822- //}
2823- var url = mu.Result("${url}");
2824- var title = ShortUrl.Instance.ExpandUrl(url).ToString();
2825- sb.Append(url + "\" title=\"" + MyCommon.ConvertToReadableUrl(title) + "\">").Append(url).Append("</a>");
2826- if (media != null && !media.ContainsKey(url)) media.Add(url, title);
2827- return sb.ToString();
2828- }),
2829- RegexOptions.IgnoreCase);
2806+ retStr = await new Regex(rgUrl, RegexOptions.IgnoreCase).ReplaceAsync(retStr, async mu =>
2807+ {
2808+ var sb = new StringBuilder(mu.Result("${before}<a href=\""));
2809+ //if (mu.Result("${protocol}").StartsWith("w", StringComparison.OrdinalIgnoreCase))
2810+ // sb.Append("http://");
2811+ //}
2812+ var url = mu.Result("${url}");
2813+ var title = await ShortUrl.Instance.ExpandUrlStrAsync(url);
2814+ sb.Append(url + "\" title=\"" + MyCommon.ConvertToReadableUrl(title) + "\">").Append(url).Append("</a>");
2815+ if (media != null && !media.ContainsKey(url)) media.Add(url, title);
2816+ return sb.ToString();
2817+ });
28302818
28312819 //@先をリンクに置換(リスト)
28322820 retStr = Regex.Replace(retStr,
旧リポジトリブラウザで表示