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.

DMDircCheckStrategy.java 8.1KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222
  1. /*
  2. * Copyright (c) 2006-2017 DMDirc Developers
  3. *
  4. * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
  5. * documentation files (the "Software"), to deal in the Software without restriction, including without limitation the
  6. * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to
  7. * permit persons to whom the Software is furnished to do so, subject to the following conditions:
  8. *
  9. * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the
  10. * Software.
  11. *
  12. * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
  13. * WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS
  14. * OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
  15. * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
  16. */
  17. package com.dmdirc.updater.checking;
  18. import com.dmdirc.config.binding.ConfigBinding;
  19. import com.dmdirc.config.GlobalConfig;
  20. import com.dmdirc.config.provider.AggregateConfigProvider;
  21. import com.dmdirc.updater.UpdateChannel;
  22. import com.dmdirc.updater.UpdateComponent;
  23. import com.dmdirc.updater.Version;
  24. import com.dmdirc.util.LogUtils;
  25. import com.dmdirc.util.io.Downloader;
  26. import org.slf4j.Logger;
  27. import org.slf4j.LoggerFactory;
  28. import javax.inject.Inject;
  29. import java.io.IOException;
  30. import java.net.MalformedURLException;
  31. import java.net.URL;
  32. import java.util.Collection;
  33. import java.util.HashMap;
  34. import java.util.List;
  35. import java.util.Map;
  36. /**
  37. * A strategy which sends a request to the DMDirc update service for information.
  38. */
  39. public class DMDircCheckStrategy implements UpdateCheckStrategy {
  40. private static final Logger LOG = LoggerFactory.getLogger(DMDircCheckStrategy.class);
  41. /** The URL to request to check for updates. */
  42. private static final String UPDATE_URL = "https://updates.dmdirc.com/";
  43. /** The update channel to check for updates on. */
  44. private UpdateChannel channel;
  45. /** Downloader to download files. */
  46. private final Downloader downloader;
  47. /**
  48. * Creates a new instance of {@link DMDircCheckStrategy}.
  49. *
  50. * @param configProvider The provider to use to retrieve update channel information.
  51. * @param downloader Used to download files
  52. */
  53. @Inject
  54. public DMDircCheckStrategy(@GlobalConfig final AggregateConfigProvider configProvider,
  55. final Downloader downloader) {
  56. configProvider.getBinder().bind(this, DMDircCheckStrategy.class);
  57. this.downloader = downloader;
  58. }
  59. /**
  60. * Sets the channel which will be used by the {@link DMDircCheckStrategy}.
  61. *
  62. * @param channel The new channel to use
  63. */
  64. @ConfigBinding(domain = "updater", key = "channel")
  65. public void setChannel(final String channel) {
  66. LOG.info("Changing channel to {}", channel);
  67. try {
  68. this.channel = UpdateChannel.valueOf(channel.toUpperCase());
  69. } catch (IllegalArgumentException ex) {
  70. LOG.warn("Unknown channel {}", channel, ex);
  71. }
  72. }
  73. @Override
  74. public Map<UpdateComponent, UpdateCheckResult> checkForUpdates(
  75. final Collection<UpdateComponent> components) {
  76. final Map<UpdateComponent, UpdateCheckResult> res = new HashMap<>();
  77. final Map<String, UpdateComponent> names = getComponentsByName(components);
  78. try {
  79. final List<String> response = downloader.getPage(UPDATE_URL, getPayload(components));
  80. LOG.trace("Response from update server: {}", response);
  81. for (String line : response) {
  82. final UpdateComponent component = names.get(getComponent(line));
  83. if (component == null) {
  84. LOG.warn("Unable to extract component from line: {}", line);
  85. continue;
  86. }
  87. final UpdateCheckResult result = parseResponse(component, line);
  88. if (result != null) {
  89. res.put(component, result);
  90. }
  91. }
  92. } catch (IOException ex) {
  93. LOG.warn("I/O exception when checking for updates", ex);
  94. }
  95. return res;
  96. }
  97. /**
  98. * Builds the data payload which will be sent to the update server. Specifically, iterates over
  99. * each component and appends their name, the channel name, and the component's version number.
  100. *
  101. * @param components The components to be added to the payload
  102. *
  103. * @return A string which can be posted to the DMDirc update server
  104. */
  105. private String getPayload(final Collection<UpdateComponent> components) {
  106. final StringBuilder data = new StringBuilder("data=");
  107. for (UpdateComponent component : components) {
  108. LOG.trace("Adding payload info for component {} (version {})", component.getName(),
  109. component.getVersion());
  110. data.append(component.getName());
  111. data.append(',');
  112. data.append(channel.name());
  113. data.append(',');
  114. data.append(component.getVersion());
  115. data.append(';');
  116. }
  117. LOG.debug("Constructed update payload: {}", data);
  118. return data.toString();
  119. }
  120. /**
  121. * Extracts the name of the component a given response line contains.
  122. *
  123. * @param line The line to be parsed
  124. *
  125. * @return The name of the component extracted from the given line
  126. */
  127. private String getComponent(final String line) {
  128. final String[] parts = line.split(" ");
  129. if (parts.length >= 2 && "outofdate".equals(parts[0])) {
  130. return parts[1];
  131. }
  132. return parts.length >= 3 ? parts[2] : null;
  133. }
  134. /**
  135. * Checks the specified line to determine the message from the update server.
  136. *
  137. * @param component The component the line refers to
  138. * @param line The line to be checked
  139. */
  140. private UpdateCheckResult parseResponse(final UpdateComponent component,
  141. final String line) {
  142. final String[] parts = line.split(" ");
  143. switch (parts[0]) {
  144. case "outofdate":
  145. return parseOutOfDateResponse(component, parts);
  146. case "uptodate":
  147. return new BaseCheckResult(component);
  148. case "error":
  149. LOG.warn("Error received from update server: {}", line);
  150. break;
  151. default:
  152. LOG.error("Unknown update line received from server: {}", line);
  153. break;
  154. }
  155. return null;
  156. }
  157. /**
  158. * Parses an "outofdate" response from the server. Extracts the URL, remote version and remote
  159. * friendly version into a {@link BaseDownloadableResult}.
  160. *
  161. * @param parts The tokenised parts of the response line
  162. *
  163. * @return A corresponding {@link UpdateCheckResult} or null on failure
  164. */
  165. private UpdateCheckResult parseOutOfDateResponse(
  166. final UpdateComponent component, final String[] parts) {
  167. try {
  168. return new BaseDownloadableResult(component, new URL(parts[5]),
  169. parts[4], new Version(parts[3]));
  170. } catch (MalformedURLException ex) {
  171. LOG.error(LogUtils.APP_ERROR, "Unable to construct URL for update. Parts: {}", parts,
  172. ex);
  173. return null;
  174. }
  175. }
  176. /**
  177. * Builds a mapping of components' names to their actual component objects.
  178. *
  179. * @param components A collection of components to be mapped
  180. *
  181. * @return A corresponding Map containing a single entry for each component, which the
  182. * component's name as a key and the component itself as a value.
  183. */
  184. private Map<String, UpdateComponent> getComponentsByName(
  185. final Collection<UpdateComponent> components) {
  186. final Map<String, UpdateComponent> res = new HashMap<>();
  187. for (UpdateComponent component : components) {
  188. res.put(component.getName(), component);
  189. }
  190. return res;
  191. }
  192. }