using System.Net.NetworkInformation;
using System.Runtime.InteropServices;
using OpenTween.Api;
+using OpenTween.Models;
+using OpenTween.Setting;
namespace OpenTween
{
{
private static readonly object LockObj = new object();
public static bool _endingFlag; //終了フラグ
- public static string cultureStr = null;
public static string settingPath;
public enum IconSizes
{
TinyUrl,
Isgd,
- Twurl,
Bitly,
Jmp,
Uxnu,
Nicoms,
//廃止
Unu = -1,
+ Twurl = -1,
}
public enum HITRESULT
public enum HttpTimeOut
{
MinValue = 10,
- MaxValue = 120,
+ MaxValue = 1000,
DefaultValue = 20,
}
public const string REPLY = "Reply";
public const string DM = "Direct";
public const string FAV = "Favorites";
+ public static readonly string MUTE = Properties.Resources.MuteTabName;
//private string dummy;
BlinkIcon,
}
- [FlagsAttribute()]
+ [Flags]
public enum EVENTTYPE
{
None = 0,
ListDestroyed = 16384,
Mute = 32768,
Unmute = 65536,
+ QuotedTweet = 131072,
+ Retweet = 262144,
All = (None | Favorite | Unfavorite | Follow | ListMemberAdded | ListMemberRemoved |
Block | Unblock | UserUpdate | Deleted | ListCreated | ListUpdated | Unfollow |
ListUserSubscribed | ListUserUnsubscribed | ListDestroyed |
- Mute | Unmute),
+ Mute | Unmute | QuotedTweet | Retweet),
}
public static _Assembly EntryAssembly { get; internal set; }
}
public static string GetErrorLogPath()
- {
- return Path.Combine(Path.GetDirectoryName(MyCommon.EntryAssembly.Location), "ErrorLogs");
- }
+ => Path.Combine(Path.GetDirectoryName(MyCommon.EntryAssembly.Location), "ErrorLogs");
public static void TraceOut(WebApiException ex)
{
}
public static void TraceOut(string Message)
- {
- TraceOut(TraceFlag, Message);
- }
+ => TraceOut(TraceFlag, Message);
public static void TraceOut(bool OutputFlag, string Message)
{
if (!Directory.Exists(logPath))
Directory.CreateDirectory(logPath);
- var now = DateTime.Now;
- var fileName = string.Format("{0}Trace-{1:0000}{2:00}{3:00}-{4:00}{5:00}{6:00}.log", GetAssemblyName(), now.Year, now.Month, now.Day, now.Hour, now.Minute, now.Second);
+ var now = DateTimeUtc.Now;
+ var fileName = $"{ApplicationSettings.AssemblyName}Trace-{now.ToLocalTime():yyyyMMdd-HHmmss}.log";
fileName = Path.Combine(logPath, fileName);
using (var writer = new StreamWriter(fileName))
{
- writer.WriteLine("**** TraceOut: {0} ****", DateTime.Now.ToString());
+ writer.WriteLine("**** TraceOut: {0} ****", now.ToLocalTimeString());
writer.WriteLine(Properties.Resources.TraceOutText1, ApplicationSettings.FeedbackEmailAddress);
writer.WriteLine(Properties.Resources.TraceOutText2, ApplicationSettings.FeedbackTwitterName);
writer.WriteLine();
writer.WriteLine(Properties.Resources.TraceOutText3);
writer.WriteLine(Properties.Resources.TraceOutText4, Environment.OSVersion.VersionString);
- writer.WriteLine(Properties.Resources.TraceOutText5, Environment.Version.ToString());
- writer.WriteLine(Properties.Resources.TraceOutText6, MyCommon.GetAssemblyName(), FileVersion);
+ writer.WriteLine(Properties.Resources.TraceOutText5, Environment.Version);
+ writer.WriteLine(Properties.Resources.TraceOutText6, ApplicationSettings.AssemblyName, FileVersion);
writer.WriteLine(Message);
writer.WriteLine();
}
public static string ExceptionOutMessage(Exception ex)
{
- bool IsTerminatePermission = true;
+ var IsTerminatePermission = true;
return ExceptionOutMessage(ex, ref IsTerminatePermission);
}
{
var IsTerminatePermission = true;
- var logPath = MyCommon.GetErrorLogPath();
- if (!Directory.Exists(logPath))
- Directory.CreateDirectory(logPath);
-
- var now = DateTime.Now;
- var fileName = string.Format("{0}-{1:0000}{2:00}{3:00}-{4:00}{5:00}{6:00}.log", GetAssemblyName(), now.Year, now.Month, now.Day, now.Hour, now.Minute, now.Second);
- fileName = Path.Combine(logPath, fileName);
+ var ident = WindowsIdentity.GetCurrent();
+ var princ = new WindowsPrincipal(ident);
+ var now = DateTimeUtc.Now;
- using (var writer = new StreamWriter(fileName))
- {
- var ident = WindowsIdentity.GetCurrent();
- var princ = new WindowsPrincipal(ident);
+ var errorReport = string.Join(Environment.NewLine,
+ string.Format(Properties.Resources.UnhandledExceptionText1, now.ToLocalTimeString()),
- writer.WriteLine(Properties.Resources.UnhandledExceptionText1, DateTime.Now.ToString());
- writer.WriteLine(Properties.Resources.UnhandledExceptionText2, ApplicationSettings.FeedbackEmailAddress);
- writer.WriteLine(Properties.Resources.UnhandledExceptionText3, ApplicationSettings.FeedbackTwitterName);
// 権限書き出し
- writer.WriteLine(Properties.Resources.UnhandledExceptionText11 + princ.IsInRole(WindowsBuiltInRole.Administrator).ToString());
- writer.WriteLine(Properties.Resources.UnhandledExceptionText12 + princ.IsInRole(WindowsBuiltInRole.User).ToString());
- writer.WriteLine();
+ string.Format(Properties.Resources.UnhandledExceptionText11 + princ.IsInRole(WindowsBuiltInRole.Administrator)),
+ string.Format(Properties.Resources.UnhandledExceptionText12 + princ.IsInRole(WindowsBuiltInRole.User)),
+ "",
+
// OSVersion,AppVersion書き出し
- writer.WriteLine(Properties.Resources.UnhandledExceptionText4);
- writer.WriteLine(Properties.Resources.UnhandledExceptionText5, Environment.OSVersion.VersionString);
- writer.WriteLine(Properties.Resources.UnhandledExceptionText6, Environment.Version.ToString());
- writer.WriteLine(Properties.Resources.UnhandledExceptionText7, MyCommon.GetAssemblyName(), FileVersion);
+ string.Format(Properties.Resources.UnhandledExceptionText4),
+ string.Format(Properties.Resources.UnhandledExceptionText5, Environment.OSVersion.VersionString),
+ string.Format(Properties.Resources.UnhandledExceptionText6, Environment.Version),
+ string.Format(Properties.Resources.UnhandledExceptionText7, ApplicationSettings.AssemblyName, FileVersion),
- writer.Write(ExceptionOutMessage(ex, ref IsTerminatePermission));
- writer.Flush();
- }
+ ExceptionOutMessage(ex, ref IsTerminatePermission));
+
+ var logPath = MyCommon.GetErrorLogPath();
+ if (!Directory.Exists(logPath))
+ Directory.CreateDirectory(logPath);
- switch (MessageBox.Show(MyCommon.ReplaceAppName(string.Format(Properties.Resources.UnhandledExceptionText9, fileName, ApplicationSettings.FeedbackEmailAddress, ApplicationSettings.FeedbackTwitterName, Environment.NewLine)),
- Properties.Resources.UnhandledExceptionText10, MessageBoxButtons.YesNoCancel, MessageBoxIcon.Error))
+ var fileName = $"{ApplicationSettings.AssemblyName}-{now.ToLocalTime():yyyyMMdd-HHmmss}.log";
+ using (var writer = new StreamWriter(Path.Combine(logPath, fileName)))
{
- case DialogResult.Yes:
- Process.Start(fileName);
- return false;
- case DialogResult.No:
- return false;
- case DialogResult.Cancel:
- default:
- return IsTerminatePermission;
+ writer.Write(errorReport);
}
- }
- }
-
- /// <summary>
- /// URLに含まれているマルチバイト文字列を%xx形式でエンコードします。
- /// <newpara>
- /// マルチバイト文字のコードはUTF-8またはUnicodeで自動的に判断します。
- /// </newpara>
- /// </summary>
- /// <param name="_input">エンコード対象のURL</param>
- /// <returns>マルチバイト文字の部分をUTF-8/%xx形式でエンコードした文字列を返します。</returns>
- public static string urlEncodeMultibyteChar(string _input)
- {
- Uri uri = null;
- var sb = new StringBuilder(256);
- var result = "";
- var c_ = 'd';
- foreach (var c in _input)
- {
- c_ = c;
- if (Convert.ToInt32(c) > 127 || c == '%') break;
- }
- if (Convert.ToInt32(c_) <= 127 && c_ != '%') return _input;
+ var settings = SettingManager.Common;
+ var mainForm = Application.OpenForms.OfType<TweenMain>().FirstOrDefault();
- var input = Uri.UnescapeDataString(_input);
- retry:
- foreach (char c in input)
- {
- if (Convert.ToInt32(c) > 255)
- {
- // Unicodeの場合(1charが複数のバイトで構成されている)
- // Uriクラスをnewして再構成し、入力をPathAndQueryのみとしてやり直す
- foreach (var b in Encoding.UTF8.GetBytes(c.ToString()))
- {
- sb.AppendFormat("%{0:X2}", b);
- }
- }
- else if (Convert.ToInt32(c) > 127 || c == '%')
- {
- // UTF-8の場合
- // Uriクラスをnewして再構成し、入力をinputからAuthority部分を除去してやり直す
- if (uri == null)
- {
- uri = new Uri(input);
- input = input.Remove(0, uri.GetLeftPart(UriPartial.Authority).Length);
- sb.Length = 0;
- goto retry;
- }
- else
- {
- sb.Append("%" + Convert.ToInt16(c).ToString("X2").ToUpper());
- }
- }
+ ErrorReport report;
+ if (mainForm != null && !mainForm.IsDisposed)
+ report = new ErrorReport(mainForm.TwitterInstance, errorReport);
else
+ report = new ErrorReport(errorReport);
+
+ report.AnonymousReport = settings.ErrorReportAnonymous;
+
+ OpenErrorReportDialog(mainForm, report);
+
+ // ダイアログ内で設定が変更されていれば保存する
+ if (settings.ErrorReportAnonymous != report.AnonymousReport)
{
- sb.Append(c);
+ settings.ErrorReportAnonymous = report.AnonymousReport;
+ settings.Save();
}
+
+ return false;
}
+ }
- if (uri == null)
+ private static void OpenErrorReportDialog(Form owner, ErrorReport report)
+ {
+ if (owner != null && owner.InvokeRequired)
{
- result = sb.ToString();
+ owner.Invoke((Action)(() => OpenErrorReportDialog(owner, report)));
+ return;
}
- else
+
+ using (var dialog = new SendErrorReportForm())
{
- result = uri.GetLeftPart(UriPartial.Authority) + sb.ToString();
+ dialog.ErrorReport = report;
+ dialog.ShowDialog(owner);
}
-
- return result;
}
/// <summary>
/// ドメインラベルの区切り文字はFULLSTOP(.、U002E)に置き換えられます。
/// </para>
/// </summary>
- /// <param name="input">展開対象のURL</param>
+ /// <param name="inputUrl">展開対象のURL</param>
/// <returns>IDNが含まれていた場合はPunycodeに展開したURLをを返します。Punycode展開時にエラーが発生した場合はnullを返します。</returns>
public static string IDNEncode(string inputUrl)
{
var idnConverter = new IdnMapping();
uriBuilder.Host = idnConverter.GetAscii(uriBuilder.Host);
- return uriBuilder.Uri.ToString();
+ return uriBuilder.Uri.AbsoluteUri;
}
catch (Exception)
{
uriBuilder.Host = idnConverter.GetUnicode(uriBuilder.Host);
}
- return uriBuilder.Uri.ToString();
+ return uriBuilder.Uri.AbsoluteUri;
}
catch (Exception)
{
- return null;
+ return inputUrl;
}
}
// Punycodeをデコードする
outputUrl = MyCommon.IDNDecode(outputUrl);
- if (outputUrl == null)
- return inputUrl;
// URL内で特殊な意味を持つ記号は元の文字に変換されることを避けるために二重エスケープする
// 参考: Firefoxの losslessDecodeURI() 関数
desdecrypt = des.CreateEncryptor();
//書き込むためのCryptoStreamの作成
- using (CryptoStream cryptStream = new CryptoStream(msOut, desdecrypt, CryptoStreamMode.Write))
+ using (var cryptStream = new CryptoStream(msOut, desdecrypt, CryptoStreamMode.Write))
{
//Disposeが重複して呼ばれないようにする
- MemoryStream msTmp = msOut;
+ var msTmp = msOut;
msOut = null;
desdecrypt = null;
}
finally
{
- if (msOut != null) msOut.Dispose();
- if (desdecrypt != null) desdecrypt.Dispose();
+ msOut?.Dispose();
+ desdecrypt?.Dispose();
}
}
}
desdecrypt = null;
//復号化されたデータを取得するためのStreamReader
- using (StreamReader srOut = new StreamReader(cryptStreem, Encoding.UTF8))
+ using (var srOut = new StreamReader(cryptStreem, Encoding.UTF8))
{
//Disposeが重複して呼ばれないようにする
cryptStreem = null;
}
finally
{
- if (msIn != null) msIn.Dispose();
- if (desdecrypt != null) desdecrypt.Dispose();
- if (cryptStreem != null) cryptStreem.Dispose();
+ msIn?.Dispose();
+ desdecrypt?.Dispose();
+ cryptStreem?.Dispose();
}
}
}
return newBytes;
}
- public static bool IsNT6()
- {
- //NT6 kernelかどうか検査
- return Environment.OSVersion.Platform == PlatformID.Win32NT && Environment.OSVersion.Version.Major == 6;
- }
-
- [FlagsAttribute()]
+ [Flags]
public enum TabUsageType
{
Undefined = 0,
Lists = 256,
Related = 512,
UserTimeline = 1024,
+ Mute = 2048,
+ SearchResults = 4096,
//RTMyTweet
//RTByOthers
//RTByMe
}
finally
{
- if (img != null) img.Dispose();
+ img?.Dispose();
}
}
- public static DateTime DateTimeParse(string input)
+ public static DateTimeUtc DateTimeParse(string input)
{
- DateTime rslt;
- string[] format = {
+ var formats = new[] {
"ddd MMM dd HH:mm:ss zzzz yyyy",
"ddd, d MMM yyyy HH:mm:ss zzzz",
};
- foreach (var fmt in format)
- {
- if (DateTime.TryParseExact(input,
- fmt,
- DateTimeFormatInfo.InvariantInfo,
- DateTimeStyles.None,
- out rslt))
- {
- return rslt;
- }
- else
- {
- continue;
- }
- }
+
+ if (DateTimeUtc.TryParseExact(input, formats, DateTimeFormatInfo.InvariantInfo, out var result))
+ return result;
+
TraceOut("Parse Error(DateTimeFormat) : " + input);
- return new DateTime();
+
+ return DateTimeUtc.Now;
}
public static T CreateDataFromJson<T>(string content)
var buf = Encoding.Unicode.GetBytes(content);
using (var stream = new MemoryStream(buf))
{
- data = (T)((new DataContractJsonSerializer(typeof(T))).ReadObject(stream));
+ var settings = new DataContractJsonSerializerSettings
+ {
+ UseSimpleDictionaryFormat = true,
+ };
+ data = (T)((new DataContractJsonSerializer(typeof(T), settings)).ReadObject(stream));
}
return data;
}
/// <param name="keys">状態を調べるキー</param>
/// <returns><paramref name="keys"/> で指定された修飾キーがすべて押されている状態であれば true。それ以外であれば false。</returns>
public static bool IsKeyDown(params Keys[] keys)
- {
- return MyCommon._IsKeyDown(Control.ModifierKeys, keys);
- }
+ => MyCommon._IsKeyDown(Control.ModifierKeys, keys);
internal static bool _IsKeyDown(Keys modifierKeys, Keys[] targetKeys)
{
- foreach (Keys key in targetKeys)
+ foreach (var key in targetKeys)
{
if ((modifierKeys & key) != key)
{
/// </remarks>
/// <returns>アプリケーションのアセンブリ名</returns>
public static string GetAssemblyName()
- {
- return MyCommon.EntryAssembly.GetName().Name;
- }
+ => MyCommon.EntryAssembly.GetName().Name;
/// <summary>
/// 文字列中に含まれる %AppName% をアプリケーション名に置換する
/// <param name="orig">対象となる文字列</param>
/// <returns>置換後の文字列</returns>
public static string ReplaceAppName(string orig)
- {
- return MyCommon.ReplaceAppName(orig, Application.ProductName);
- }
+ => MyCommon.ReplaceAppName(orig, ApplicationSettings.ApplicationName);
/// <summary>
/// 文字列中に含まれる %AppName% をアプリケーション名に置換する
/// <param name="appname">アプリケーション名</param>
/// <returns>置換後の文字列</returns>
public static string ReplaceAppName(string orig, string appname)
- {
- return orig.Replace("%AppName%", appname);
- }
+ => orig.Replace("%AppName%", appname);
/// <summary>
/// 表示用のバージョン番号の文字列を生成する
/// 表示用のバージョン番号の文字列を生成する
/// </summary>
/// <remarks>
- /// バージョン1.0.0.1のように末尾が0でない(=開発版)の場合は「1.0.1-beta1」が出力される
+ /// バージョン1.0.0.1のように末尾が0でない(=開発版)の場合は「1.0.1-dev」のように出力される
/// </remarks>
/// <returns>
/// 生成されたバージョン番号の文字列
{
versionNum[2] = versionNum[2] + 1;
- // 10を越えたら桁上げ
- if (versionNum[2] >= 10)
- {
- versionNum[1] += versionNum[2] / 10;
- versionNum[2] %= 10;
-
- if (versionNum[1] >= 10)
- {
- versionNum[0] += versionNum[1] / 10;
- versionNum[1] %= 10;
- }
- }
-
- return string.Format("{0}.{1}.{2}-beta{3}", versionNum[0], versionNum[1], versionNum[2], versionNum[3]);
+ if (versionNum[3] == 1)
+ return string.Format("{0}.{1}.{2}-dev", versionNum[0], versionNum[1], versionNum[2]);
+ else
+ return string.Format("{0}.{1}.{2}-dev+build.{3}", versionNum[0], versionNum[1], versionNum[2], versionNum[3]);
}
}
}
public static string GetStatusUrl(string screenName, long statusId)
- {
- return TwitterUrl + screenName + "/status/" + statusId.ToString();
- }
+ => TwitterUrl + screenName + "/status/" + statusId;
/// <summary>
/// 指定された IDictionary を元にクエリ文字列を生成します
/// </summary>
/// <param name="param">生成するクエリの key-value コレクション</param>
- public static string BuildQueryString(IDictionary<string, string> param)
+ public static string BuildQueryString(IEnumerable<KeyValuePair<string, string>> param)
{
- if (param == null || param.Count == 0)
+ if (param == null)
return string.Empty;
var query = param
return sb.ToString();
}
+
+ /// <summary>
+ /// 指定された範囲の整数を昇順に列挙します
+ /// </summary>
+ /// <remarks>
+ /// start, start + 1, start + 2, ..., end の範囲の数列を生成します
+ /// </remarks>
+ /// <param name="from">数列の先頭の値 (最小値)</param>
+ /// <param name="to">数列の末尾の値 (最大値)</param>
+ /// <returns>整数を列挙する IEnumerable インスタンス</returns>
+ public static IEnumerable<int> CountUp(int from, int to)
+ {
+ if (from > to)
+ return Enumerable.Empty<int>();
+
+ return Enumerable.Range(from, to - from + 1);
+ }
+
+ /// <summary>
+ /// 指定された範囲の整数を降順に列挙します
+ /// </summary>
+ /// <remarks>
+ /// start, start - 1, start - 2, ..., end の範囲の数列を生成します
+ /// </remarks>
+ /// <param name="from">数列の先頭の値 (最大値)</param>
+ /// <param name="to">数列の末尾の値 (最小値)</param>
+ /// <returns>整数を列挙する IEnumerable インスタンス</returns>
+ public static IEnumerable<int> CountDown(int from, int to)
+ {
+ for (var i = from; i >= to; i--)
+ yield return i;
+ }
+
+ public static IEnumerable<int> CircularCountUp(int length, int startIndex)
+ {
+ if (length < 1)
+ throw new ArgumentOutOfRangeException(nameof(length));
+ if (startIndex < 0 || startIndex >= length)
+ throw new ArgumentOutOfRangeException(nameof(startIndex));
+
+ // startindex ... 末尾
+ var indices = MyCommon.CountUp(startIndex, length - 1);
+
+ // 先頭 ... (startIndex - 1)
+ if (startIndex != 0)
+ indices = indices.Concat(MyCommon.CountUp(0, startIndex - 1));
+
+ return indices;
+ }
+
+ public static IEnumerable<int> CircularCountDown(int length, int startIndex)
+ {
+ if (length < 1)
+ throw new ArgumentOutOfRangeException(nameof(length));
+ if (startIndex < 0 || startIndex >= length)
+ throw new ArgumentOutOfRangeException(nameof(startIndex));
+
+ // startIndex ... 先頭
+ var indices = MyCommon.CountDown(startIndex, 0);
+
+ // 末尾 ... (startIndex + 1)
+ if (startIndex != length - 1)
+ indices = indices.Concat(MyCommon.CountDown(length - 1, startIndex + 1));
+
+ return indices;
+ }
+
+ /// <summary>
+ /// 2バイト文字も考慮したUrlエンコード
+ /// </summary>
+ /// <param name="stringToEncode">エンコードする文字列</param>
+ /// <returns>エンコード結果文字列</returns>
+ public static string UrlEncode(string stringToEncode)
+ {
+ const string UnreservedChars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_.~";
+ var sb = new StringBuilder();
+ var bytes = Encoding.UTF8.GetBytes(stringToEncode);
+
+ foreach (var b in bytes)
+ {
+ if (UnreservedChars.IndexOf((char)b) != -1)
+ sb.Append((char)b);
+ else
+ sb.AppendFormat("%{0:X2}", b);
+ }
+ return sb.ToString();
+ }
}
}