// (c) 2012 Egtra (@egtra) <http://dev.activebasic.com/egtra/>
// (c) 2012 kim_upsilon (@kim_upsilon) <https://upsilo.net/~upsilon/>
// All rights reserved.
-//
+//
// This file is part of OpenTween.
-//
+//
// This program is free software; you can redistribute it and/or modify it
// under the terms of the GNU General public License as published by the Free
// Software Foundation; either version 3 of the License, or (at your option)
// any later version.
-//
+//
// This program is distributed in the hope that it will be useful, but
// WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
// or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General public License
// for more details.
-//
+//
// You should have received a copy of the GNU General public License along
// with this program. If not, see <http://www.gnu.org/licenses/>, or write to
// the Free Software Foundation, Inc., 51 Franklin Street - Fifth Floor,
// Boston, MA 02110-1301, USA.
+#nullable enable
+
using System;
-using System.Collections.Generic;
using System.IO;
-using System.Linq;
-using System.Diagnostics;
using System.Windows.Forms;
-using System.Text.RegularExpressions;
-using System.Threading;
-using System.Threading.Tasks;
-using System.Globalization;
-using System.Reflection;
+using OpenTween.Connection;
+using OpenTween.Setting;
namespace OpenTween
{
- internal class MyApplication
+ internal class ApplicationEvents
{
/// <summary>
/// 起動時に指定されたオプションを取得します
/// </summary>
- public static IDictionary<string, string> StartupOptions { get; private set; }
+ public static CommandLineArgs StartupOptions { get; private set; } = null!;
/// <summary>
/// アプリケーションのメイン エントリ ポイントです。
/// </summary>
[STAThread]
- static int Main(string[] args)
+ public static int Main(string[] args)
{
- StartupOptions = ParseArguments(args);
+ Application.EnableVisualStyles();
+ Application.SetCompatibleTextRenderingDefault(false);
- CheckSettingFilePath();
- InitCulture();
+ using var errorReportHandler = new ErrorReportHandler();
- string pt = Application.ExecutablePath.Replace("\\", "/") + "/" + Application.ProductName;
- using (Mutex mt = new Mutex(false, pt))
- {
- if (!mt.WaitOne(0, false))
- {
- var text = string.Format(MyCommon.ReplaceAppName(Properties.Resources.StartupText1), MyCommon.GetAssemblyName());
- MessageBox.Show(text, MyCommon.ReplaceAppName(Properties.Resources.StartupText2), MessageBoxButtons.OK, MessageBoxIcon.Information);
+ StartupOptions = new(args);
+ InitializeTraceFrag();
- ShowPreviousWindow();
- return 1;
- }
+ if (!ApplicationPreconditions.CheckAll())
+ return 1;
- TaskScheduler.UnobservedTaskException += (s, e) =>
- {
- e.SetObserved();
- OnUnhandledException(e.Exception.Flatten());
- };
- Application.ThreadException += (s, e) => OnUnhandledException(e.Exception);
- AppDomain.CurrentDomain.UnhandledException += (s, e) => OnUnhandledException((Exception)e.ExceptionObject);
+ if (!SetConfigDirectoryPath())
+ return 1;
- Application.EnableVisualStyles();
- Application.SetCompatibleTextRenderingDefault(false);
- Application.Run(new TweenMain());
+ using var container = new ApplicationContainer();
- mt.ReleaseMutex();
+ var settings = container.Settings;
+ settings.LoadAll();
- return 0;
- }
- }
+ var noLimit = StartupOptions.ContainsKey("nolimit");
+ settings.Common.Validate(noLimit);
- /// <summary>
- /// “/key:value”形式の起動オプションを解釈し IDictionary に変換する
- /// </summary>
- /// <remarks>
- /// 不正な形式のオプションは除外されます。
- /// また、重複したキーのオプションが入力された場合は末尾に書かれたオプションが採用されます。
- /// </remarks>
- internal static IDictionary<string, string> ParseArguments(IEnumerable<string> arguments)
- {
- var optionPattern = new Regex(@"^/(.+?)(?::(.*))?$");
+ ThemeManager.ApplyGlobalUIFont(settings.Local);
+ container.CultureService.Initialize();
- return arguments.Select(x => optionPattern.Match(x))
- .Where(x => x.Success)
- .GroupBy(x => x.Groups[1].Value)
- .ToDictionary(x => x.Key, x => x.Last().Groups[2].Value);
- }
+ Networking.Initialize();
+ settings.ApplySettings();
- private static void ShowPreviousWindow()
- {
- // 実行中の同じアプリケーションのウィンドウ・ハンドルの取得
- var prevProcess = Win32Api.GetPreviousProcess();
- if (prevProcess == null)
- return;
+ // 同じ設定ファイルを使用する OpenTween プロセスの二重起動を防止する
+ using var mutex = new ApplicationInstanceMutex(ApplicationSettings.AssemblyName, MyCommon.SettingPath);
- if (prevProcess.MainWindowHandle != IntPtr.Zero)
- {
- // 起動中のアプリケーションを最前面に表示
- Win32Api.WakeupWindow(prevProcess.MainWindowHandle);
- }
- else
+ if (mutex.InstanceExists)
{
- //プロセス特定は出来たが、ウィンドウハンドルが取得できなかった(アイコン化されている)
- //タスクトレイアイコンのクリックをエミュレート
- //注:アイコン特定はTooltipの文字列で行うため、多重起動時は先に見つけた物がアクティブになる
- Win32Api.ClickTasktrayIcon(Application.ProductName);
- }
- }
+ var text = string.Format(MyCommon.ReplaceAppName(Properties.Resources.StartupText1), ApplicationSettings.AssemblyName);
+ MessageBox.Show(text, MyCommon.ReplaceAppName(Properties.Resources.StartupText2), MessageBoxButtons.OK, MessageBoxIcon.Information);
- private static void OnUnhandledException(Exception ex)
- {
- if (MyCommon.ExceptionOut(ex))
- {
- Application.Exit();
+ mutex.TryActivatePreviousInstance();
+ return 1;
}
+
+ Application.Run(container.MainForm);
+
+ return 0;
}
- private static bool IsEqualCurrentCulture(string CultureName)
+ private static void InitializeTraceFrag()
{
- return Thread.CurrentThread.CurrentUICulture.Name.StartsWith(CultureName);
+ var traceFlag = false;
+
+#if DEBUG
+ traceFlag = true;
+#endif
+
+ if (StartupOptions.ContainsKey("d"))
+ traceFlag = true;
+
+ var version = Version.Parse(MyCommon.FileVersion);
+ if (version.Build != 0)
+ traceFlag = true;
+
+ MyCommon.TraceFlag = traceFlag;
}
- public static string CultureCode
+ private static bool SetConfigDirectoryPath()
{
- get
+ if (StartupOptions.TryGetValue("configDir", out var configDir) && !MyCommon.IsNullOrEmpty(configDir))
+ {
+ // 起動オプション /configDir で設定ファイルの参照先を変更できます
+ if (!Directory.Exists(configDir))
+ {
+ var text = string.Format(Properties.Resources.ConfigDirectoryNotExist, configDir);
+ MessageBox.Show(text, ApplicationSettings.ApplicationName, MessageBoxButtons.OK, MessageBoxIcon.Error);
+ return false;
+ }
+
+ MyCommon.SettingPath = Path.GetFullPath(configDir);
+ }
+ else
{
- if (MyCommon.cultureStr == null)
+ // OpenTween.exe と同じディレクトリに設定ファイルを配置する
+ MyCommon.SettingPath = Application.StartupPath;
+
+ SettingManager.Instance.LoadAll();
+
+ try
+ {
+ // 設定ファイルが書き込み可能な状態であるかテストする
+ SettingManager.Instance.SaveAll();
+ }
+ catch (UnauthorizedAccessException)
{
- var cfgCommon = SettingCommon.Load();
- MyCommon.cultureStr = cfgCommon.Language;
- if (MyCommon.cultureStr == "OS")
+ // 書き込みに失敗した場合 (Program Files 以下に配置されている場合など)
+
+ // 通常は C:\Users\ユーザー名\AppData\Roaming\OpenTween\ となる
+ var roamingDir = Path.Combine(new[]
{
- if (!IsEqualCurrentCulture("ja") &&
- !IsEqualCurrentCulture("en") &&
- !IsEqualCurrentCulture("zh-CN"))
+ Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData),
+ ApplicationSettings.ApplicationName,
+ });
+ Directory.CreateDirectory(roamingDir);
+
+ MyCommon.SettingPath = roamingDir;
+
+ /*
+ * 書き込みが制限されたディレクトリ内で起動された場合の設定ファイルの扱い
+ *
+ * (A) StartupPath に存在する設定ファイル
+ * (B) Roaming に存在する設定ファイル
+ *
+ * 1. A も B も存在しない場合
+ * => B を新規に作成する
+ *
+ * 2. A が存在し、B が存在しない場合
+ * => A の内容を B にコピーする (警告を表示)
+ *
+ * 3. A が存在せず、B が存在する場合
+ * => B を使用する
+ *
+ * 4. A も B も存在するが、A の方が更新日時が新しい場合
+ * => A の内容を B にコピーする (警告を表示)
+ *
+ * 5. A も B も存在するが、B の方が更新日時が新しい場合
+ * => B を使用する
+ */
+ var startupDirFile = new FileInfo(Path.Combine(Application.StartupPath, "SettingCommon.xml"));
+ var roamingDirFile = new FileInfo(Path.Combine(roamingDir, "SettingCommon.xml"));
+
+ if (roamingDirFile.Exists && (!startupDirFile.Exists || startupDirFile.LastWriteTime <= roamingDirFile.LastWriteTime))
+ {
+ // 既に Roaming に設定ファイルが存在し、Roaming 内のファイルの方が新しい場合は
+ // StartupPath に設定ファイルが存在しても無視する
+ SettingManager.Instance.LoadAll();
+ }
+ else
+ {
+ if (startupDirFile.Exists)
{
- MyCommon.cultureStr = "en";
+ // StartupPath に設定ファイルが存在し、Roaming 内のファイルよりも新しい場合のみ警告を表示する
+ var message = string.Format(Properties.Resources.SettingPath_Relocation, Application.StartupPath, MyCommon.SettingPath);
+ MessageBox.Show(message, ApplicationSettings.ApplicationName, MessageBoxButtons.OK, MessageBoxIcon.Information);
}
+
+ // Roaming に設定ファイルを作成 (StartupPath に読み込みに成功した設定ファイルがあれば内容がコピーされる)
+ SettingManager.Instance.SaveAll();
}
}
- return MyCommon.cultureStr;
- }
- }
-
- public static void InitCulture(string code)
- {
- try
- {
- Thread.CurrentThread.CurrentUICulture = new CultureInfo(code);
}
- catch (Exception)
- {
- }
- }
- public static void InitCulture()
- {
- try
- {
- if (CultureCode != "OS") Thread.CurrentThread.CurrentUICulture = new CultureInfo(CultureCode);
- }
- catch (Exception)
- {
- }
- }
- private static void CheckSettingFilePath()
- {
- if (File.Exists(Path.Combine(Application.StartupPath, "roaming")))
- {
- MyCommon.settingPath = MySpecialPath.UserAppDataPath();
- }
- else
- {
- MyCommon.settingPath = Application.StartupPath;
- }
+ return true;
}
}
}