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.

IdentityManager.java 23KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683
  1. /*
  2. * Copyright (c) 2006-2011 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.config;
  23. import com.dmdirc.Main;
  24. import com.dmdirc.Precondition;
  25. import com.dmdirc.logger.ErrorLevel;
  26. import com.dmdirc.logger.Logger;
  27. import com.dmdirc.updater.Version;
  28. import com.dmdirc.util.ConfigFile;
  29. import com.dmdirc.util.InvalidConfigFileException;
  30. import com.dmdirc.util.MapList;
  31. import com.dmdirc.util.WeakMapList;
  32. import com.dmdirc.util.resourcemanager.ResourceManager;
  33. import java.io.File;
  34. import java.io.IOException;
  35. import java.util.ArrayList;
  36. import java.util.Collections;
  37. import java.util.HashMap;
  38. import java.util.LinkedHashSet;
  39. import java.util.List;
  40. import java.util.Map;
  41. import java.util.Set;
  42. import java.util.logging.Level;
  43. /**
  44. * The identity manager manages all known identities, providing easy methods
  45. * to access them.
  46. *
  47. * @author chris
  48. */
  49. public final class IdentityManager {
  50. /**
  51. * The identities that have been loaded into this manager.
  52. *
  53. * Standard identities are inserted with a <code>null</code> key, custom
  54. * identities use their custom type as the key.
  55. */
  56. private static final MapList<String, Identity> IDENTITIES
  57. = new MapList<String, Identity>();
  58. /**
  59. * The {@link IdentityListener}s that have registered with this manager.
  60. *
  61. * Listeners for standard identities are inserted with a <code>null</code>
  62. * key, listeners for a specific custom type use their type as the key.
  63. */
  64. private static final MapList<String, IdentityListener> LISTENERS
  65. = new WeakMapList<String, IdentityListener>();
  66. /** A logger for this class. */
  67. private static final java.util.logging.Logger LOGGER = java.util.logging
  68. .Logger.getLogger(IdentityManager.class.getName());
  69. /** The identity file used for the global config. */
  70. private static Identity config;
  71. /** The identity file used for addon defaults. */
  72. private static Identity addonConfig;
  73. /** The identity file bundled with the client containing version info. */
  74. private static Identity versionConfig;
  75. /** The config manager used for global settings. */
  76. private static ConfigManager globalconfig;
  77. /** Creates a new instance of IdentityManager. */
  78. private IdentityManager() {
  79. }
  80. /**
  81. * Loads all identity files.
  82. *
  83. * @throws InvalidIdentityFileException If there is an error with the config
  84. * file.
  85. */
  86. public static void load() throws InvalidIdentityFileException {
  87. IDENTITIES.clear();
  88. loadVersion();
  89. loadDefaults();
  90. loadUser();
  91. loadConfig();
  92. if (getCustomIdentities("profile").isEmpty()) {
  93. try {
  94. Identity.buildProfile("Default Profile");
  95. } catch (IOException ex) {
  96. Logger.userError(ErrorLevel.FATAL, "Unable to write default profile", ex);
  97. }
  98. }
  99. // Set up the identity used for the addons defaults
  100. final ConfigTarget target = new ConfigTarget();
  101. target.setGlobalDefault();
  102. target.setOrder(500000);
  103. final ConfigFile addonConfigFile = new ConfigFile((File) null);
  104. final Map<String, String> addonSettings = new HashMap<String, String>();
  105. addonSettings.put("name", "Addon defaults");
  106. addonConfigFile.addDomain("identity", addonSettings);
  107. addonConfig = new Identity(addonConfigFile, target);
  108. IdentityManager.addIdentity(addonConfig);
  109. if (!getGlobalConfig().hasOptionString("identity", "defaultsversion")) {
  110. Logger.userError(ErrorLevel.FATAL, "Default settings "
  111. + "could not be loaded");
  112. }
  113. }
  114. /** Loads the default (built in) identities. */
  115. private static void loadDefaults() {
  116. final String[] targets = {"default", "modealiases"};
  117. final String dir = getDirectory();
  118. for (String target : targets) {
  119. final File file = new File(dir + target);
  120. if (file.exists() && !file.isDirectory()) {
  121. boolean success = false;
  122. for (int i = 0; i < 10 && !success; i++) {
  123. final String suffix = ".old" + (i > 0 ? "-" + i : "");
  124. success = file.renameTo(new File(file.getParentFile(), target + suffix));
  125. }
  126. if (!success) {
  127. Logger.userError(ErrorLevel.HIGH, "Unable to create directory for "
  128. + "default settings folder (" + target + ")", "A file "
  129. + "with that name already exists, and couldn't be renamed."
  130. + " Rename or delete " + file.getAbsolutePath());
  131. continue;
  132. }
  133. }
  134. if (!file.exists() || file.listFiles() == null || file.listFiles().length == 0) {
  135. file.mkdirs();
  136. extractIdentities(target);
  137. }
  138. loadUser(file);
  139. }
  140. extractFormatters();
  141. // If the bundled defaults are newer than the ones the user is
  142. // currently using, extract them.
  143. if (getGlobalConfig().hasOptionString("identity", "defaultsversion")
  144. && getGlobalConfig().hasOptionString("updater", "bundleddefaultsversion")) {
  145. final Version installedVersion = new Version(getGlobalConfig()
  146. .getOption("identity", "defaultsversion"));
  147. final Version bundledVersion = new Version(getGlobalConfig()
  148. .getOption("updater", "bundleddefaultsversion"));
  149. if (bundledVersion.compareTo(installedVersion) > 0) {
  150. extractIdentities("default");
  151. loadUser(new File(dir, "default"));
  152. }
  153. }
  154. }
  155. private static void extractFormatters() {
  156. try {
  157. ResourceManager.getResourceManager().extractResource(
  158. "com/dmdirc/config/defaults/default/formatter",
  159. getDirectory() + "default/", false);
  160. } catch (IOException ex) {
  161. Logger.userError(ErrorLevel.MEDIUM, "Unable to extract default "
  162. + "formatters: " + ex.getMessage());
  163. }
  164. }
  165. /**
  166. * Extracts the specific set of default identities to the user's identity
  167. * folder.
  168. *
  169. * @param target The target to be extracted
  170. */
  171. private static void extractIdentities(final String target) {
  172. try {
  173. ResourceManager.getResourceManager().extractResources(
  174. "com/dmdirc/config/defaults/" + target,
  175. getDirectory() + target, false);
  176. } catch (IOException ex) {
  177. Logger.userError(ErrorLevel.MEDIUM, "Unable to extract default "
  178. + "identities: " + ex.getMessage());
  179. }
  180. }
  181. /**
  182. * Retrieves the directory used to store identities in.
  183. *
  184. * @return The identity directory path
  185. */
  186. public static String getDirectory() {
  187. return Main.getConfigDir() + "identities" + System.getProperty("file.separator");
  188. }
  189. /** Loads user-defined identity files. */
  190. public static void loadUser() {
  191. final File dir = new File(getDirectory());
  192. if (!dir.exists()) {
  193. try {
  194. dir.mkdirs();
  195. dir.createNewFile();
  196. } catch (IOException ex) {
  197. Logger.userError(ErrorLevel.MEDIUM, "Unable to create identity dir");
  198. }
  199. }
  200. loadUser(dir);
  201. }
  202. /**
  203. * Recursively loads files from the specified directory.
  204. *
  205. * @param dir The directory to be loaded
  206. */
  207. @Precondition({
  208. "The specified File is not null",
  209. "The specified File is a directory"
  210. })
  211. private static void loadUser(final File dir) {
  212. Logger.assertTrue(dir != null);
  213. Logger.assertTrue(dir.isDirectory());
  214. if (dir.listFiles() == null) {
  215. Logger.userError(ErrorLevel.MEDIUM,
  216. "Unable to load user identity files from "
  217. + dir.getAbsolutePath());
  218. } else {
  219. for (File file : dir.listFiles()) {
  220. if (file.isDirectory()) {
  221. loadUser(file);
  222. } else {
  223. loadIdentity(file);
  224. }
  225. }
  226. }
  227. }
  228. /**
  229. * Loads an identity from the specified file. If the identity already
  230. * exists, it is told to reload instead.
  231. *
  232. * @param file The file to load the identity from.
  233. */
  234. private static void loadIdentity(final File file) {
  235. synchronized (IDENTITIES) {
  236. for (Identity identity : getAllIdentities()) {
  237. if (identity.isFile(file)) {
  238. try {
  239. identity.reload();
  240. } catch (IOException ex) {
  241. Logger.userError(ErrorLevel.MEDIUM,
  242. "I/O error when reloading identity file: "
  243. + file.getAbsolutePath() + " (" + ex.getMessage() + ")");
  244. } catch (InvalidConfigFileException ex) {
  245. // Do nothing
  246. }
  247. return;
  248. }
  249. }
  250. }
  251. try {
  252. addIdentity(new Identity(file, false));
  253. } catch (InvalidIdentityFileException ex) {
  254. Logger.userError(ErrorLevel.MEDIUM,
  255. "Invalid identity file: " + file.getAbsolutePath()
  256. + " (" + ex.getMessage() + ")");
  257. } catch (IOException ex) {
  258. Logger.userError(ErrorLevel.MEDIUM,
  259. "I/O error when reading identity file: "
  260. + file.getAbsolutePath());
  261. }
  262. }
  263. /**
  264. * Retrieves all known identities.
  265. *
  266. * @return A set of all known identities
  267. * @since 0.6.4
  268. */
  269. private static Set<Identity> getAllIdentities() {
  270. final Set<Identity> res = new LinkedHashSet<Identity>();
  271. for (Map.Entry<String, List<Identity>> entry : IDENTITIES.entrySet()) {
  272. res.addAll(entry.getValue());
  273. }
  274. return res;
  275. }
  276. /**
  277. * Returns the "group" to which the specified identity belongs. For custom
  278. * identities this is the custom identity type, otherwise this is
  279. * <code>null</code>.
  280. *
  281. * @param identity The identity whose group is being retrieved
  282. * @return The group of the specified identity
  283. * @since 0.6.4
  284. */
  285. private static String getGroup(final Identity identity) {
  286. return identity.getTarget().getType() == ConfigTarget.TYPE.CUSTOM
  287. ? identity.getTarget().getData() : null;
  288. }
  289. /** Loads the version information. */
  290. public static void loadVersion() {
  291. try {
  292. versionConfig = new Identity(Main.class.getResourceAsStream("version.config"), false);
  293. addIdentity(versionConfig);
  294. } catch (IOException ex) {
  295. Logger.appError(ErrorLevel.FATAL, "Unable to load version information", ex);
  296. } catch (InvalidIdentityFileException ex) {
  297. Logger.appError(ErrorLevel.FATAL, "Unable to load version information", ex);
  298. }
  299. }
  300. /**
  301. * Loads the config identity.
  302. *
  303. * @throws InvalidIdentityFileException if there is a problem with the
  304. * config file.
  305. */
  306. private static void loadConfig() throws InvalidIdentityFileException {
  307. try {
  308. final File file = new File(Main.getConfigDir() + "dmdirc.config");
  309. if (!file.exists()) {
  310. file.createNewFile();
  311. }
  312. config = new Identity(file, true);
  313. config.setOption("identity", "name", "Global config");
  314. addIdentity(config);
  315. } catch (IOException ex) {
  316. Logger.userError(ErrorLevel.FATAL, "I/O error when loading global config: "
  317. + ex.getMessage(), ex);
  318. }
  319. }
  320. /**
  321. * Retrieves the identity used for the global config.
  322. *
  323. * @return The global config identity
  324. */
  325. public static Identity getConfigIdentity() {
  326. return config;
  327. }
  328. /**
  329. * Retrieves the identity used for addons defaults.
  330. *
  331. * @return The addons defaults identity
  332. */
  333. public static Identity getAddonIdentity() {
  334. return addonConfig;
  335. }
  336. /**
  337. * Retrieves the identity bundled with the DMDirc client containing
  338. * version information.
  339. *
  340. * @return The version identity
  341. * @since 0.6.3m2
  342. */
  343. public static Identity getVersionIdentity() {
  344. return versionConfig;
  345. }
  346. /**
  347. * Saves all modified identity files to disk.
  348. */
  349. public static void save() {
  350. synchronized (IDENTITIES) {
  351. for (Identity identity : getAllIdentities()) {
  352. identity.save();
  353. }
  354. }
  355. }
  356. /**
  357. * Adds the specific identity to this manager.
  358. * @param identity The identity to be added
  359. */
  360. @Precondition("The specified Identity is not null")
  361. public static void addIdentity(final Identity identity) {
  362. Logger.assertTrue(identity != null);
  363. final String target = getGroup(identity);
  364. if (IDENTITIES.containsValue(target, identity)) {
  365. removeIdentity(identity);
  366. }
  367. synchronized (IDENTITIES) {
  368. IDENTITIES.add(target, identity);
  369. }
  370. LOGGER.log(Level.FINER, "Adding identity: {0} (group: {1})",
  371. new Object[]{identity, target});
  372. synchronized (LISTENERS) {
  373. for (IdentityListener listener : LISTENERS.safeGet(target)) {
  374. listener.identityAdded(identity);
  375. }
  376. }
  377. }
  378. /**
  379. * Removes an identity from this manager.
  380. * @param identity The identity to be removed
  381. */
  382. @Precondition({
  383. "The specified Identity is not null",
  384. "The specified Identity has previously been added and not removed"
  385. })
  386. public static void removeIdentity(final Identity identity) {
  387. Logger.assertTrue(identity != null);
  388. final String group = getGroup(identity);
  389. Logger.assertTrue(IDENTITIES.containsValue(group, identity));
  390. synchronized (IDENTITIES) {
  391. IDENTITIES.remove(group, identity);
  392. }
  393. synchronized (LISTENERS) {
  394. for (IdentityListener listener : LISTENERS.safeGet(group)) {
  395. listener.identityRemoved(identity);
  396. }
  397. }
  398. }
  399. /**
  400. * Adds a config manager to this manager.
  401. *
  402. * @param manager The ConfigManager to add
  403. * @deprecated Use {@link #addIdentityListener(com.dmdirc.config.IdentityListener)}
  404. */
  405. @Deprecated
  406. @Precondition("The specified ConfigManager is not null")
  407. public static void addConfigManager(final ConfigManager manager) {
  408. addIdentityListener(manager);
  409. }
  410. /**
  411. * Adds a new identity listener which will be informed of all settings
  412. * identities which are added to this manager.
  413. *
  414. * @param listener The listener to be added
  415. * @since 0.6.4
  416. */
  417. @Precondition("The specified listener is not null")
  418. public static void addIdentityListener(final IdentityListener listener) {
  419. addIdentityListener(null, listener);
  420. }
  421. /**
  422. * Adds a new identity listener which will be informed of all identities
  423. * of the specified custom type which are added to this manager.
  424. *
  425. * @param type The type of identities to listen for
  426. * @param listener The listener to be added
  427. * @since 0.6.4
  428. */
  429. @Precondition("The specified listener is not null")
  430. public static void addIdentityListener(final String type, final IdentityListener listener) {
  431. Logger.assertTrue(listener != null);
  432. synchronized (LISTENERS) {
  433. LISTENERS.add(type, listener);
  434. }
  435. }
  436. /**
  437. * Retrieves a list of identities that serve as profiles.
  438. *
  439. * @return A list of profiles
  440. * @deprecated Use {@link #getCustomIdentities(java.lang.String)} with
  441. * an argument of <code>profile</code> to retrieve profiles.
  442. */
  443. @Deprecated
  444. public static List<Identity> getProfiles() {
  445. return getCustomIdentities("profile");
  446. }
  447. /**
  448. * Retrieves a list of identities that belong to the specified custom type.
  449. *
  450. * @param type The type of identity to search for
  451. * @return A list of matching identities
  452. * @since 0.6.4
  453. */
  454. public static List<Identity> getCustomIdentities(final String type) {
  455. return Collections.unmodifiableList(IDENTITIES.safeGet(type));
  456. }
  457. /**
  458. * Retrieves a list of all config sources that should be applied to the
  459. * specified config manager.
  460. *
  461. * @param manager The manager requesting sources
  462. * @return A list of all matching config sources
  463. */
  464. public static List<Identity> getSources(final ConfigManager manager) {
  465. final List<Identity> sources = new ArrayList<Identity>();
  466. synchronized (IDENTITIES) {
  467. for (Identity identity : IDENTITIES.safeGet(null)) {
  468. if (manager.identityApplies(identity)) {
  469. sources.add(identity);
  470. }
  471. }
  472. }
  473. Collections.sort(sources);
  474. return sources;
  475. }
  476. /**
  477. * Retrieves the global config manager.
  478. *
  479. * @return The global config manager
  480. */
  481. public static synchronized ConfigManager getGlobalConfig() {
  482. if (globalconfig == null) {
  483. globalconfig = new ConfigManager("", "", "", "");
  484. }
  485. return globalconfig;
  486. }
  487. /**
  488. * Retrieves the config for the specified channel@network. The config is
  489. * created if it doesn't exist.
  490. *
  491. * @param network The name of the network
  492. * @param channel The name of the channel
  493. * @return A config source for the channel
  494. */
  495. @Precondition({
  496. "The specified network is non-null and not empty",
  497. "The specified channel is non-null and not empty"
  498. })
  499. public static Identity getChannelConfig(final String network, final String channel) {
  500. if (network == null || network.isEmpty()) {
  501. throw new IllegalArgumentException("getChannelConfig called "
  502. + "with null or empty network\n\nNetwork: " + network);
  503. }
  504. if (channel == null || channel.isEmpty()) {
  505. throw new IllegalArgumentException("getChannelConfig called "
  506. + "with null or empty channel\n\nChannel: " + channel);
  507. }
  508. final String myTarget = (channel + "@" + network).toLowerCase();
  509. synchronized (IDENTITIES) {
  510. for (Identity identity : IDENTITIES.safeGet(null)) {
  511. if (identity.getTarget().getType() == ConfigTarget.TYPE.CHANNEL
  512. && identity.getTarget().getData().equalsIgnoreCase(myTarget)) {
  513. return identity;
  514. }
  515. }
  516. }
  517. // We need to create one
  518. final ConfigTarget target = new ConfigTarget();
  519. target.setChannel(myTarget);
  520. try {
  521. return Identity.buildIdentity(target);
  522. } catch (IOException ex) {
  523. Logger.userError(ErrorLevel.HIGH, "Unable to create channel identity", ex);
  524. return null;
  525. }
  526. }
  527. /**
  528. * Retrieves the config for the specified network. The config is
  529. * created if it doesn't exist.
  530. *
  531. * @param network The name of the network
  532. * @return A config source for the network
  533. */
  534. @Precondition("The specified network is non-null and not empty")
  535. public static Identity getNetworkConfig(final String network) {
  536. if (network == null || network.isEmpty()) {
  537. throw new IllegalArgumentException("getNetworkConfig called "
  538. + "with null or empty network\n\nNetwork:" + network);
  539. }
  540. final String myTarget = network.toLowerCase();
  541. synchronized (IDENTITIES) {
  542. for (Identity identity : IDENTITIES.safeGet(null)) {
  543. if (identity.getTarget().getType() == ConfigTarget.TYPE.NETWORK
  544. && identity.getTarget().getData().equalsIgnoreCase(myTarget)) {
  545. return identity;
  546. }
  547. }
  548. }
  549. // We need to create one
  550. final ConfigTarget target = new ConfigTarget();
  551. target.setNetwork(myTarget);
  552. try {
  553. return Identity.buildIdentity(target);
  554. } catch (IOException ex) {
  555. Logger.userError(ErrorLevel.HIGH, "Unable to create network identity", ex);
  556. return null;
  557. }
  558. }
  559. /**
  560. * Retrieves the config for the specified server. The config is
  561. * created if it doesn't exist.
  562. *
  563. * @param server The name of the server
  564. * @return A config source for the server
  565. */
  566. @Precondition("The specified server is non-null and not empty")
  567. public static Identity getServerConfig(final String server) {
  568. if (server == null || server.isEmpty()) {
  569. throw new IllegalArgumentException("getServerConfig called "
  570. + "with null or empty server\n\nServer: " + server);
  571. }
  572. final String myTarget = server.toLowerCase();
  573. synchronized (IDENTITIES) {
  574. for (Identity identity : IDENTITIES.safeGet(null)) {
  575. if (identity.getTarget().getType() == ConfigTarget.TYPE.SERVER
  576. && identity.getTarget().getData().equalsIgnoreCase(myTarget)) {
  577. return identity;
  578. }
  579. }
  580. }
  581. // We need to create one
  582. final ConfigTarget target = new ConfigTarget();
  583. target.setServer(myTarget);
  584. try {
  585. return Identity.buildIdentity(target);
  586. } catch (IOException ex) {
  587. Logger.userError(ErrorLevel.HIGH, "Unable to create network identity", ex);
  588. return null;
  589. }
  590. }
  591. }