You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440
  1. /*
  2. * Copyright (c) 2006-2010 Chris Smith, Shane Mc Cormack, Gregory Holmes
  3. *
  4. * Permission is hereby granted, free of charge, to any person obtaining a copy
  5. * of this software and associated documentation files (the "Software"), to deal
  6. * in the Software without restriction, including without limitation the rights
  7. * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  8. * copies of the Software, and to permit persons to whom the Software is
  9. * furnished to do so, subject to the following conditions:
  10. *
  11. * The above copyright notice and this permission notice shall be included in
  12. * all copies or substantial portions of the Software.
  13. *
  14. * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  15. * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  16. * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  17. * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  18. * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  19. * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
  20. * SOFTWARE.
  21. */
  22. package com.dmdirc;
  23. import com.dmdirc.actions.ActionManager;
  24. import com.dmdirc.actions.CoreActionType;
  25. import com.dmdirc.commandline.CommandLineParser;
  26. import com.dmdirc.commandparser.CommandManager;
  27. import com.dmdirc.config.ConfigManager;
  28. import com.dmdirc.config.IdentityManager;
  29. import com.dmdirc.config.InvalidIdentityFileException;
  30. import com.dmdirc.logger.DMDircExceptionHandler;
  31. import com.dmdirc.logger.ErrorLevel;
  32. import com.dmdirc.logger.Logger;
  33. import com.dmdirc.plugins.PluginManager;
  34. import com.dmdirc.plugins.Service;
  35. import com.dmdirc.ui.themes.ThemeManager;
  36. import com.dmdirc.ui.interfaces.UIController;
  37. import com.dmdirc.ui.WarningDialog;
  38. import com.dmdirc.updater.UpdateChecker;
  39. import com.dmdirc.util.InvalidConfigFileException;
  40. import com.dmdirc.util.resourcemanager.ResourceManager;
  41. import java.awt.GraphicsEnvironment;
  42. import java.io.File;
  43. import java.io.IOException;
  44. import java.text.SimpleDateFormat;
  45. import java.util.Date;
  46. import java.util.List;
  47. import java.util.Map;
  48. import java.util.Timer;
  49. import java.util.TimerTask;
  50. import java.util.logging.Handler;
  51. import java.util.logging.Level;
  52. /**
  53. * Main class, handles initialisation.
  54. *
  55. * @author chris
  56. */
  57. public final class Main {
  58. /** Feedback nag delay. */
  59. private static final int FEEDBACK_DELAY = 30 * 60 * 1000;
  60. /** The UI to use for the client. */
  61. private static UIController controller;
  62. /** The config dir to use for the client. */
  63. private static String configdir;
  64. /**
  65. * Prevents creation of main.
  66. */
  67. private Main() {
  68. }
  69. /**
  70. * Entry procedure.
  71. *
  72. * @param args the command line arguments
  73. */
  74. public static void main(final String[] args) {
  75. try {
  76. init(args);
  77. } catch (Throwable ex) {
  78. Logger.appError(ErrorLevel.FATAL, "Exception while initialising", ex);
  79. }
  80. }
  81. /**
  82. * Initialises the client.
  83. *
  84. * @param args The command line arguments
  85. */
  86. private static void init(final String[] args) {
  87. Thread.setDefaultUncaughtExceptionHandler(new DMDircExceptionHandler());
  88. for (Handler handler : java.util.logging.Logger.getLogger("").getHandlers()) {
  89. handler.setLevel(Level.OFF); // Needs to be changed to enable debugging
  90. }
  91. // Enable finer debugging for specific components like so:
  92. //java.util.logging.Logger.getLogger("com.dmdirc.plugins").setLevel(Level.ALL);
  93. IdentityManager.loadVersion();
  94. final CommandLineParser clp = new CommandLineParser(args);
  95. try {
  96. IdentityManager.load();
  97. } catch (InvalidIdentityFileException iife) {
  98. final String date = new SimpleDateFormat("yyyyMMddkkmmss").format(new Date());
  99. final String message = "DMDirc has detected that your config file "
  100. + "has become corrupted.<br><br>DMDirc will now backup "
  101. + "your current config and try restarting with a default "
  102. + "config.<br><br>Your old config will be saved as:<br>"
  103. + "dmdirc.config." + date;
  104. if (!GraphicsEnvironment.isHeadless()) {
  105. new WarningDialog("Invalid Config File", message).displayBlocking();
  106. }
  107. // Let command-line users know what is happening.
  108. System.out.println(message.replaceAll("<br>", "\n"));
  109. final File configFile = new File(getConfigDir() + "dmdirc.config");
  110. final File newConfigFile = new File(getConfigDir() + "dmdirc.config." + date);
  111. if (configFile.renameTo(newConfigFile)) {
  112. try {
  113. IdentityManager.load();
  114. } catch (InvalidIdentityFileException iife2) {
  115. // This shouldn't happen!
  116. Logger.appError(ErrorLevel.FATAL, "Unable to load global config", iife2);
  117. }
  118. } else {
  119. final String newMessage = "DMDirc was unable to rename the "
  120. + "global config file and is unable to fix this issue.";
  121. if (!GraphicsEnvironment.isHeadless()) {
  122. new WarningDialog("Invalid Config File", newMessage).displayBlocking();
  123. }
  124. System.out.println(newMessage.replaceAll("<br>", "\n"));
  125. System.exit(1);
  126. }
  127. }
  128. final PluginManager pm = PluginManager.getPluginManager();
  129. ThemeManager.loadThemes();
  130. clp.applySettings();
  131. CommandManager.initCommands();
  132. for (String service : new String[]{"ui", "tabcompletion"}) {
  133. ensureExists(pm, service);
  134. }
  135. loadUI(pm, IdentityManager.getGlobalConfig());
  136. if (getUI() == null) {
  137. // Check to see if we have already tried this
  138. if (IdentityManager.getGlobalConfig().hasOptionBool("debug", "uiFixAttempted")) {
  139. System.out.println("DMDirc is unable to load any compatible UI plugins.");
  140. if (!GraphicsEnvironment.isHeadless()) {
  141. new WarningDialog(WarningDialog.NO_COMPAT_UIS_TITLE,
  142. WarningDialog.NO_RECOV_UIS).displayBlocking();
  143. }
  144. IdentityManager.getConfigIdentity().unsetOption("debug", "uiFixAttempted");
  145. System.exit(1);
  146. } else {
  147. // Try to extract the UIs again incase they changed between versions
  148. // and the user didn't update the UI plugin.
  149. extractCorePlugins("ui_");
  150. System.out.println("DMDirc has updated the UI plugins and needs to restart.");
  151. if (!GraphicsEnvironment.isHeadless()) {
  152. new WarningDialog(WarningDialog.NO_COMPAT_UIS_TITLE,
  153. WarningDialog.NO_COMPAT_UIS_BODY).displayBlocking();
  154. }
  155. // Allow the rebooted DMDirc to know that we have attempted restarting.
  156. IdentityManager.getConfigIdentity().setOption("debug", "uiFixAttempted", "true");
  157. // Force the UI to swing to prevent problematic 3rd party UIs.
  158. IdentityManager.getConfigIdentity().setOption("general", "ui", "swing");
  159. // Tell the launcher to restart!
  160. System.exit(42);
  161. }
  162. } else {
  163. // The fix worked!
  164. if (IdentityManager.getGlobalConfig().hasOptionBool("debug", "uiFixAttempted")) {
  165. IdentityManager.getConfigIdentity().unsetOption("debug", "uiFixAttempted");
  166. }
  167. }
  168. doFirstRun();
  169. ActionManager.init();
  170. pm.doAutoLoad();
  171. ActionManager.loadActions();
  172. getUI().getMainWindow();
  173. ActionManager.processEvent(CoreActionType.CLIENT_OPENED, null);
  174. UpdateChecker.init();
  175. clp.processArguments();
  176. GlobalWindow.init();
  177. Runtime.getRuntime().addShutdownHook(new Thread(new Runnable() {
  178. /** {@inheritDoc} */
  179. @Override
  180. public void run() {
  181. ActionManager.processEvent(CoreActionType.CLIENT_CLOSED, null);
  182. ServerManager.getServerManager().disconnectAll("Unexpected shutdown");
  183. IdentityManager.save();
  184. }
  185. }, "Shutdown thread"));
  186. }
  187. /**
  188. * Ensures that there is at least one provider of the specified
  189. * service type by extracting matching core plugins. Plugins must be named
  190. * so that their file name starts with the service type, and then an
  191. * underscore.
  192. *
  193. * @param pm The plugin manager to use to access services
  194. * @param serviceType The type of service that should exist
  195. */
  196. public static void ensureExists(final PluginManager pm, final String serviceType) {
  197. if (pm.getServicesByType(serviceType).isEmpty()) {
  198. extractCorePlugins(serviceType + "_");
  199. pm.getPossiblePluginInfos(true);
  200. }
  201. }
  202. /**
  203. * Attempts to find and activate a service which provides a UI that we
  204. * can use.
  205. *
  206. * @param pm The plugin manager to use to load plugins
  207. * @param cm The config manager to use to retrieve settings
  208. */
  209. protected static void loadUI(final PluginManager pm, final ConfigManager cm) {
  210. final List<Service> uis = pm.getServicesByType("ui");
  211. final String desired = cm.getOption("general", "ui");
  212. // First try: go for our desired service type
  213. for (Service service : uis) {
  214. if (service.getName().equals(desired) && service.activate()) {
  215. return;
  216. }
  217. }
  218. // Second try: go for any service type
  219. for (Service service : uis) {
  220. if (service.activate()) {
  221. return;
  222. }
  223. }
  224. if (!GraphicsEnvironment.isHeadless()) {
  225. // Show a dialog informing the user that no UI was found.
  226. new WarningDialog().displayBlocking();
  227. System.exit(2);
  228. }
  229. // Can't find any
  230. throw new IllegalStateException("No UIs could be loaded");
  231. }
  232. /**
  233. * Executes the first run or migration wizards as required.
  234. */
  235. private static void doFirstRun() {
  236. if (IdentityManager.getGlobalConfig().getOptionBool("general", "firstRun")) {
  237. IdentityManager.getConfigIdentity().setOption("general", "firstRun", "false");
  238. getUI().showFirstRunWizard();
  239. new Timer().schedule(new TimerTask() {
  240. /** {@inheritDoc} */
  241. @Override
  242. public void run() {
  243. getUI().showFeedbackNag();
  244. }
  245. }, FEEDBACK_DELAY);
  246. }
  247. }
  248. /**
  249. * Quits the client nicely, with the default closing message.
  250. */
  251. public static void quit() {
  252. quit(0);
  253. }
  254. /**
  255. * Quits the client nicely, with the default closing message.
  256. *
  257. * @param exitCode This is the exit code that will be returned to the
  258. * operating system when the client exits
  259. */
  260. public static void quit(final int exitCode) {
  261. quit(IdentityManager.getGlobalConfig().getOption("general",
  262. "closemessage"), exitCode);
  263. }
  264. /**
  265. * Quits the client nicely.
  266. *
  267. * @param reason The quit reason to send
  268. */
  269. public static void quit(final String reason) {
  270. quit(reason, 0);
  271. }
  272. /**
  273. * Quits the client nicely.
  274. *
  275. * @param reason The quit reason to send
  276. * @param exitCode This is the exit code that will be returned to the
  277. * operating system when the client exits
  278. */
  279. public static void quit(final String reason, final int exitCode) {
  280. ServerManager.getServerManager().disconnectAll(reason);
  281. System.exit(exitCode);
  282. }
  283. /**
  284. * Retrieves the UI controller that's being used by the client.
  285. *
  286. * @return The client's UI controller
  287. */
  288. public static UIController getUI() {
  289. return controller;
  290. }
  291. /**
  292. * Sets the UI controller that should be used by this client.
  293. *
  294. * @param newController The new UI Controller
  295. */
  296. public static synchronized void setUI(final UIController newController) {
  297. if (controller == null) {
  298. controller = newController;
  299. } else {
  300. throw new IllegalStateException("User interface is already set");
  301. }
  302. }
  303. /**
  304. * Returns the application's config directory.
  305. *
  306. * @return configuration directory
  307. */
  308. public static String getConfigDir() {
  309. if (configdir == null) {
  310. final String fs = System.getProperty("file.separator");
  311. final String osName = System.getProperty("os.name");
  312. if (osName.startsWith("Mac OS")) {
  313. configdir = System.getProperty("user.home") + fs + "Library"
  314. + fs + "Preferences" + fs + "DMDirc" + fs;
  315. } else if (osName.startsWith("Windows")) {
  316. if (System.getenv("APPDATA") == null) {
  317. configdir = System.getProperty("user.home") + fs + "DMDirc" + fs;
  318. } else {
  319. configdir = System.getenv("APPDATA") + fs + "DMDirc" + fs;
  320. }
  321. } else {
  322. configdir = System.getProperty("user.home") + fs + ".DMDirc" + fs;
  323. final File testFile = new File(configdir);
  324. if (!testFile.exists()) {
  325. final String configHome = System.getenv("XDG_CONFIG_HOME");
  326. configdir = configHome.isEmpty() ?
  327. System.getProperty("user.home") + fs + ".config" + fs :
  328. configHome;
  329. configdir += fs + "DMDirc" + fs;
  330. }
  331. }
  332. }
  333. return configdir;
  334. }
  335. /**
  336. * Sets the config directory for this client.
  337. *
  338. * @param newdir The new configuration directory
  339. */
  340. public static void setConfigDir(final String newdir) {
  341. configdir = newdir;
  342. }
  343. /**
  344. * Extracts plugins bundled with DMDirc to the user's profile's plugin
  345. * directory.
  346. *
  347. * @param prefix If non-null, only plugins whose file name starts with
  348. * this prefix will be extracted.
  349. */
  350. public static void extractCorePlugins(final String prefix) {
  351. final Map<String, byte[]> resources = ResourceManager.getResourceManager()
  352. .getResourcesStartingWithAsBytes("plugins");
  353. for (Map.Entry<String, byte[]> resource : resources.entrySet()) {
  354. try {
  355. final String resourceName = Main.getConfigDir() + "plugins"
  356. + resource.getKey().substring(7);
  357. if (prefix != null && !resource.getKey().substring(8).startsWith(prefix)) {
  358. continue;
  359. }
  360. final File newDir = new File(resourceName.substring(0,
  361. resourceName.lastIndexOf('/')) + "/");
  362. if (!newDir.exists()) {
  363. newDir.mkdirs();
  364. }
  365. final File newFile = new File(newDir,
  366. resourceName.substring(resourceName.lastIndexOf('/') + 1,
  367. resourceName.length()));
  368. if (!newFile.isDirectory()) {
  369. ResourceManager.getResourceManager().
  370. resourceToFile(resource.getValue(), newFile);
  371. }
  372. } catch (IOException ex) {
  373. Logger.userError(ErrorLevel.LOW, "Failed to extract plugins", ex);
  374. }
  375. }
  376. }
  377. }