OSDN Git Service

C# 8.0 のnull許容参照型を有効化
[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                         this.isImage = true;
134                     }
135                     catch (InvalidImageException)
136                     {
137                         this.isImage = false;
138                     }
139                 }
140
141                 return this.isImage.Value;
142             }
143         }
144
145         /// <summary>IsImage の検証結果をキャッシュする。未検証なら null</summary>
146         private bool? isImage = null;
147
148         public MemoryImage CreateImage()
149         {
150             using var fs = this.FileInfo.OpenRead();
151             return MemoryImage.CopyFromStream(fs);
152         }
153
154         public Stream OpenRead()
155             => this.FileInfo.OpenRead();
156
157         public void CopyTo(Stream stream)
158         {
159             using var fs = this.FileInfo.OpenRead();
160             fs.CopyTo(stream);
161         }
162     }
163
164     /// <summary>
165     /// MemoryImage 用の MediaItem クラス
166     /// </summary>
167     /// <remarks>
168     /// 用途の関係上、メモリ使用量が大きくなるため、不要になればできるだけ破棄すること
169     /// </remarks>
170     public class MemoryImageMediaItem : IMediaItem, IDisposable
171     {
172         public const string PathPrefix = "<>MemoryImage://";
173         private static int _fileNumber = 0;
174         private readonly MemoryImage _image;
175
176         public bool IsDisposed { get; private set; } = false;
177
178         public MemoryImageMediaItem(MemoryImage image)
179         {
180             this._image = image ?? throw new ArgumentNullException(nameof(image));
181
182             var num = Interlocked.Increment(ref _fileNumber);
183             this.Path = PathPrefix + num + this._image.ImageFormatExt;
184         }
185
186         public string Path { get; }
187         public string? AltText { get; set; }
188
189         public string Name
190             => this.Path.Substring(PathPrefix.Length);
191
192         public string Extension
193             => this._image.ImageFormatExt;
194
195         public bool Exists
196             => this._image != null;
197
198         public long Size
199             => this._image.Stream.Length;
200
201         public bool IsImage
202             => true;
203
204         public MemoryImage CreateImage()
205             => this._image.Clone();
206
207         public Stream OpenRead()
208         {
209             MemoryStream? memstream = null;
210             try
211             {
212                 // コピーを作成する
213                 memstream = new MemoryStream();
214
215                 this._image.Stream.WriteTo(memstream);
216                 memstream.Seek(0, SeekOrigin.Begin);
217
218                 return memstream;
219             }
220             catch
221             {
222                 memstream?.Dispose();
223                 throw;
224             }
225         }
226
227         public void CopyTo(Stream stream)
228             => this._image.Stream.WriteTo(stream);
229
230         protected virtual void Dispose(bool disposing)
231         {
232             if (this.IsDisposed) return;
233
234             if (disposing)
235             {
236                 this._image.Dispose();
237             }
238
239             this.IsDisposed = true;
240         }
241
242         public void Dispose()
243         {
244             this.Dispose(true);
245
246             // 明示的にDisposeが呼ばれた場合はファイナライザを使用しない
247             GC.SuppressFinalize(this);
248         }
249
250         ~MemoryImageMediaItem()
251             => this.Dispose(false);
252     }
253 }