OSDN Git Service

ブロックの括弧を独立した行に書く (SA1500, SA1501, SA1502)
[opentween/open-tween.git] / OpenTween / MediaItem.cs
1 // OpenTween - Client of Twitter
2 // Copyright (c) 2015 spx (@5px)
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.Drawing;
26 using System.IO;
27 using System.Linq;
28 using System.Threading;
29
30 namespace OpenTween
31 {
32     public interface IMediaItem
33     {
34         /// <summary>
35         /// メディアへの絶対パス
36         /// </summary>
37         string Path { get; }
38
39         /// <summary>
40         /// メディア名
41         /// </summary>
42         string Name { get; }
43
44         /// <summary>
45         /// メディアの拡張子
46         /// </summary>
47         string Extension { get; }
48
49         /// <summary>
50         /// メディアが存在するかどうかを示す真偽値
51         /// </summary>
52         bool Exists { get; }
53
54         /// <summary>
55         /// メディアのサイズ(バイト単位)
56         /// </summary>
57         long Size { get; }
58
59         /// <summary>
60         /// メディアが画像であるかどうかを示す真偽値
61         /// </summary>
62         bool IsImage { get; }
63
64         /// <summary>
65         /// 代替テキスト (アップロード先が対応している必要がある)
66         /// </summary>
67         string? AltText { get; set; }
68
69         /// <summary>
70         /// 表示用の MemoryImage を作成する
71         /// </summary>
72         /// <remarks>
73         /// 呼び出し側にて破棄すること
74         /// </remarks>
75         MemoryImage CreateImage();
76
77         /// <summary>
78         /// メディアの内容を読み込むための Stream を開く
79         /// </summary>
80         /// <remarks>
81         /// 呼び出し側にて閉じること
82         /// </remarks>
83         Stream OpenRead();
84
85         /// <summary>
86         /// メディアの内容を Stream へ書き込む
87         /// </summary>
88         void CopyTo(Stream stream);
89     }
90
91     /// <summary>
92     /// ファイル用の MediaItem クラス
93     /// </summary>
94     public class FileMediaItem : IMediaItem
95     {
96         public FileInfo FileInfo { get; }
97         public string? AltText { get; set; }
98
99         public FileMediaItem(string path)
100             => this.FileInfo = new FileInfo(path);
101
102         public FileMediaItem(FileInfo fileInfo)
103             : this(fileInfo.FullName)
104         {
105         }
106
107         public string Path
108             => this.FileInfo.FullName;
109
110         public string Name
111             => this.FileInfo.Name;
112
113         public string Extension
114             => this.FileInfo.Extension;
115
116         public bool Exists
117             => this.FileInfo.Exists;
118
119         public long Size
120             => this.FileInfo.Length;
121
122         public bool IsImage
123         {
124             get
125             {
126                 if (this.isImage == null)
127                 {
128                     try
129                     {
130                         // MemoryImage が生成できるかを検証する
131                         using (var image = this.CreateImage())
132                         {
133                         }
134
135                         this.isImage = true;
136                     }
137                     catch (InvalidImageException)
138                     {
139                         this.isImage = false;
140                     }
141                 }
142
143                 return this.isImage.Value;
144             }
145         }
146
147         /// <summary>IsImage の検証結果をキャッシュする。未検証なら null</summary>
148         private bool? isImage = null;
149
150         public MemoryImage CreateImage()
151         {
152             using var fs = this.FileInfo.OpenRead();
153             return MemoryImage.CopyFromStream(fs);
154         }
155
156         public Stream OpenRead()
157             => this.FileInfo.OpenRead();
158
159         public void CopyTo(Stream stream)
160         {
161             using var fs = this.FileInfo.OpenRead();
162             fs.CopyTo(stream);
163         }
164     }
165
166     /// <summary>
167     /// MemoryImage 用の MediaItem クラス
168     /// </summary>
169     /// <remarks>
170     /// 用途の関係上、メモリ使用量が大きくなるため、不要になればできるだけ破棄すること
171     /// </remarks>
172     public class MemoryImageMediaItem : IMediaItem, IDisposable
173     {
174         public const string PathPrefix = "<>MemoryImage://";
175         private static int fileNumber = 0;
176         private readonly MemoryImage image;
177
178         public bool IsDisposed { get; private set; } = false;
179
180         public MemoryImageMediaItem(MemoryImage image)
181         {
182             this.image = image ?? throw new ArgumentNullException(nameof(image));
183
184             var num = Interlocked.Increment(ref fileNumber);
185             this.Path = PathPrefix + num + this.image.ImageFormatExt;
186         }
187
188         public string Path { get; }
189         public string? AltText { get; set; }
190
191         public string Name
192             => this.Path.Substring(PathPrefix.Length);
193
194         public string Extension
195             => this.image.ImageFormatExt;
196
197         public bool Exists
198             => this.image != null;
199
200         public long Size
201             => this.image.Stream.Length;
202
203         public bool IsImage
204             => true;
205
206         public MemoryImage CreateImage()
207             => this.image.Clone();
208
209         public Stream OpenRead()
210         {
211             MemoryStream? memstream = null;
212             try
213             {
214                 // コピーを作成する
215                 memstream = new MemoryStream();
216
217                 this.image.Stream.WriteTo(memstream);
218                 memstream.Seek(0, SeekOrigin.Begin);
219
220                 return memstream;
221             }
222             catch
223             {
224                 memstream?.Dispose();
225                 throw;
226             }
227         }
228
229         public void CopyTo(Stream stream)
230             => this.image.Stream.WriteTo(stream);
231
232         protected virtual void Dispose(bool disposing)
233         {
234             if (this.IsDisposed) return;
235
236             if (disposing)
237             {
238                 this.image.Dispose();
239             }
240
241             this.IsDisposed = true;
242         }
243
244         public void Dispose()
245         {
246             this.Dispose(true);
247
248             // 明示的にDisposeが呼ばれた場合はファイナライザを使用しない
249             GC.SuppressFinalize(this);
250         }
251
252         ~MemoryImageMediaItem()
253             => this.Dispose(false);
254     }
255 }