9d42572185231ae550a3630477a0351c3535342a
[rhynodge.git] / src / main / java / net / pterodactylus / rhynodge / triggers / NewEpisodeTrigger.java
1 /*
2  * Rhynodge - NewEpisodeTrigger.java - Copyright © 2013 David Roden
3  *
4  * This program is free software: you can redistribute it and/or modify
5  * it under the terms of the GNU General Public License as published by
6  * the Free Software Foundation, either version 3 of the License, or
7  * (at your option) any later version.
8  *
9  * This program is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12  * GNU General Public License for more details.
13  *
14  * You should have received a copy of the GNU General Public License
15  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
16  */
17
18 package net.pterodactylus.rhynodge.triggers;
19
20 import static com.google.common.base.Preconditions.checkState;
21
22 import java.util.Collection;
23 import java.util.Map;
24 import java.util.Map.Entry;
25
26 import net.pterodactylus.rhynodge.Reaction;
27 import net.pterodactylus.rhynodge.State;
28 import net.pterodactylus.rhynodge.Trigger;
29 import net.pterodactylus.rhynodge.output.DefaultOutput;
30 import net.pterodactylus.rhynodge.output.Output;
31 import net.pterodactylus.rhynodge.states.EpisodeState;
32 import net.pterodactylus.rhynodge.states.EpisodeState.Episode;
33 import net.pterodactylus.rhynodge.states.TorrentState.TorrentFile;
34
35 import org.apache.commons.lang3.StringEscapeUtils;
36
37 import com.google.common.base.Function;
38 import com.google.common.collect.FluentIterable;
39 import com.google.common.collect.ImmutableMap;
40 import com.google.common.collect.Maps;
41 import com.google.common.collect.Ordering;
42 import com.google.common.collect.Sets;
43
44 /**
45  * {@link Trigger} implementation that compares two {@link EpisodeState}s for
46  * new and changed {@link Episode}s.
47  *
48  * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
49  */
50 public class NewEpisodeTrigger implements Trigger {
51
52         /** All episodes. */
53         private final Collection<Episode> allEpisodes = Sets.newHashSet();
54
55         /** All new episodes. */
56         private final Collection<Episode> newEpisodes = Sets.newHashSet();
57
58         /** All changed episodes. */
59         private final Collection<Episode> changedEpisodes = Sets.newHashSet();
60
61
62         //
63         // TRIGGER METHODS
64         //
65
66         /**
67          * {@inheritDocs}
68          */
69         @Override
70         public State mergeStates(State previousState, State currentState) {
71                 checkState(currentState instanceof EpisodeState, "currentState is not a EpisodeState but a %s", currentState.getClass().getName());
72                 checkState(previousState instanceof EpisodeState, "previousState is not a EpisodeState but a %s", currentState.getClass().getName());
73                 newEpisodes.clear();
74                 changedEpisodes.clear();
75                 this.allEpisodes.clear();
76                 Map<Episode, Episode> allEpisodes = Maps.newHashMap(FluentIterable.from(((EpisodeState) previousState).episodes()).toMap(new Function<Episode, Episode>() {
77
78                         @Override
79                         public Episode apply(Episode episode) {
80                                 return episode;
81                         }
82                 }));
83                 for (Episode episode : ((EpisodeState) currentState).episodes()) {
84                         if (!allEpisodes.containsKey(episode)) {
85                                 allEpisodes.put(episode, episode);
86                                 newEpisodes.add(episode);
87                         }
88                         for (TorrentFile torrentFile : episode.torrentFiles()) {
89                                 int oldSize = allEpisodes.get(episode).torrentFiles().size();
90                                 allEpisodes.get(episode).addTorrentFile(torrentFile);
91                                 int newSize = allEpisodes.get(episode).torrentFiles().size();
92                                 if (!newEpisodes.contains(episode) && (oldSize != newSize)) {
93                                         changedEpisodes.add(episode);
94                                 }
95                         }
96                 }
97                 this.allEpisodes.addAll(allEpisodes.values());
98                 return new EpisodeState(this.allEpisodes);
99         }
100
101         /**
102          * {@inheritDoc}
103          */
104         @Override
105         public boolean triggers() {
106                 return !newEpisodes.isEmpty() || !changedEpisodes.isEmpty();
107         }
108
109         /**
110          * {@inheritDoc}
111          */
112         @Override
113         public Output output(Reaction reaction) {
114                 String summary;
115                 if (!newEpisodes.isEmpty()) {
116                         if (!changedEpisodes.isEmpty()) {
117                                 summary = String.format("%d new and %d changed Torrent(s) for “%s!”", newEpisodes.size(), changedEpisodes.size(), reaction.name());
118                         } else {
119                                 summary = String.format("%d new Torrent(s) for “%s!”", newEpisodes.size(), reaction.name());
120                         }
121                 } else {
122                         summary = String.format("%d changed Torrent(s) for “%s!”", changedEpisodes.size(), reaction.name());
123                 }
124                 DefaultOutput output = new DefaultOutput(summary);
125                 output.addText("text/plain", generatePlainText(reaction, newEpisodes, changedEpisodes, allEpisodes));
126                 output.addText("text/html", generateHtmlText(reaction, newEpisodes, changedEpisodes, allEpisodes));
127                 return output;
128         }
129
130         //
131         // STATIC METHODS
132         //
133
134         /**
135          * Generates the plain text trigger output.
136          *
137          * @param reaction
138          *            The reaction that was triggered
139          * @param newEpisodes
140          *            The new episodes
141          * @param changedEpisodes
142          *            The changed episodes
143          * @param allEpisodes
144          *            All episodes
145          * @return The plain text output
146          */
147         private static String generatePlainText(Reaction reaction, Collection<Episode> newEpisodes, Collection<Episode> changedEpisodes, Collection<Episode> allEpisodes) {
148                 StringBuilder stringBuilder = new StringBuilder();
149                 if (!newEpisodes.isEmpty()) {
150                         stringBuilder.append(reaction.name()).append(" - New Episodes\n\n");
151                         for (Episode episode : newEpisodes) {
152                                 stringBuilder.append("- ").append(episode.identifier()).append("\n");
153                                 for (TorrentFile torrentFile : episode) {
154                                         stringBuilder.append("  - ").append(torrentFile.name()).append(", ").append(torrentFile.size()).append("\n");
155                                         if ((torrentFile.magnetUri() != null) && (torrentFile.magnetUri().length() > 0)) {
156                                                 stringBuilder.append("    Magnet: ").append(torrentFile.magnetUri()).append("\n");
157                                         }
158                                         if ((torrentFile.downloadUri() != null) && (torrentFile.downloadUri().length() > 0)) {
159                                                 stringBuilder.append("    Download: ").append(torrentFile.downloadUri()).append("\n");
160                                         }
161                                 }
162                         }
163                 }
164                 if (!changedEpisodes.isEmpty()) {
165                         stringBuilder.append(reaction.name()).append(" - Changed Episodes\n\n");
166                         for (Episode episode : changedEpisodes) {
167                                 stringBuilder.append("- ").append(episode.identifier()).append("\n");
168                                 for (TorrentFile torrentFile : episode) {
169                                         stringBuilder.append("  - ").append(torrentFile.name()).append(", ").append(torrentFile.size()).append("\n");
170                                         if ((torrentFile.magnetUri() != null) && (torrentFile.magnetUri().length() > 0)) {
171                                                 stringBuilder.append("    Magnet: ").append(torrentFile.magnetUri()).append("\n");
172                                         }
173                                         if ((torrentFile.downloadUri() != null) && (torrentFile.downloadUri().length() > 0)) {
174                                                 stringBuilder.append("    Download: ").append(torrentFile.downloadUri()).append("\n");
175                                         }
176                                 }
177                         }
178                 }
179                 /* list all known episodes. */
180                 stringBuilder.append(reaction.name()).append(" - All Known Episodes\n\n");
181                 ImmutableMap<Integer, Collection<Episode>> episodesBySeason = FluentIterable.from(allEpisodes).index(new Function<Episode, Integer>() {
182
183                         @Override
184                         public Integer apply(Episode episode) {
185                                 return episode.season();
186                         }
187                 }).asMap();
188                 for (Entry<Integer, Collection<Episode>> seasonEntry : episodesBySeason.entrySet()) {
189                         stringBuilder.append("  Season ").append(seasonEntry.getKey()).append("\n\n");
190                         for (Episode episode : Ordering.natural().sortedCopy(seasonEntry.getValue())) {
191                                 stringBuilder.append("    Episode ").append(episode.episode()).append("\n");
192                                 for (TorrentFile torrentFile : episode) {
193                                         stringBuilder.append("      Size: ").append(torrentFile.size());
194                                         stringBuilder.append(" in ").append(torrentFile.fileCount()).append(" file(s): ");
195                                         stringBuilder.append(torrentFile.magnetUri());
196                                 }
197                         }
198                 }
199
200                 return stringBuilder.toString();
201         }
202
203         /**
204          * Generates the HTML trigger output.
205          *
206          * @param reaction
207          *            The reaction that was triggered
208          * @param newEpisodes
209          *            The new episodes
210          * @param changedEpisodes
211          *            The changed episodes
212          * @param allEpisodes
213          *            All episodes
214          * @return The HTML output
215          */
216         private static String generateHtmlText(Reaction reaction, Collection<Episode> newEpisodes, Collection<Episode> changedEpisodes, Collection<Episode> allEpisodes) {
217                 StringBuilder htmlBuilder = new StringBuilder();
218                 htmlBuilder.append("<html><body>\n");
219                 htmlBuilder.append("<h1>").append(StringEscapeUtils.escapeHtml4(reaction.name())).append("</h1>\n");
220                 if (!newEpisodes.isEmpty()) {
221                         htmlBuilder.append("<h2>New Episodes</h2>\n");
222                         htmlBuilder.append("<ul>\n");
223                         for (Episode episode : newEpisodes) {
224                                 htmlBuilder.append("<li>Season ").append(episode.season()).append(", Episode ").append(episode.episode()).append("</li>\n");
225                                 htmlBuilder.append("<ul>\n");
226                                 for (TorrentFile torrentFile : episode) {
227                                         htmlBuilder.append("<li>").append(StringEscapeUtils.escapeHtml4(torrentFile.name())).append("</li>\n");
228                                         htmlBuilder.append("<div>");
229                                         htmlBuilder.append("<strong>").append(StringEscapeUtils.escapeHtml4(torrentFile.size())).append("</strong>, ");
230                                         htmlBuilder.append("<strong>").append(torrentFile.fileCount()).append("</strong> file(s), ");
231                                         htmlBuilder.append("<strong>").append(torrentFile.seedCount()).append("</strong> seed(s), ");
232                                         htmlBuilder.append("<strong>").append(torrentFile.leechCount()).append("</strong> leecher(s)</div>\n");
233                                         htmlBuilder.append("<div>");
234                                         if ((torrentFile.magnetUri() != null) && (torrentFile.magnetUri().length() > 0)) {
235                                                 htmlBuilder.append("<a href=\"").append(StringEscapeUtils.escapeHtml4(torrentFile.magnetUri())).append("\">Magnet</a> ");
236                                         }
237                                         if ((torrentFile.downloadUri() != null) && (torrentFile.downloadUri().length() > 0)) {
238                                                 htmlBuilder.append("<a href=\"").append(StringEscapeUtils.escapeHtml4(torrentFile.downloadUri())).append("\">Download</a>");
239                                         }
240                                         htmlBuilder.append("</div>\n");
241                                 }
242                                 htmlBuilder.append("</ul>\n");
243                         }
244                         htmlBuilder.append("</ul>\n");
245                 }
246                 if (!changedEpisodes.isEmpty()) {
247                         htmlBuilder.append("<h2>Changed Episodes</h2>\n");
248                         htmlBuilder.append("<ul>\n");
249                         for (Episode episode : changedEpisodes) {
250                                 htmlBuilder.append("<li>Season ").append(episode.season()).append(", Episode ").append(episode.episode()).append("</li>\n");
251                                 htmlBuilder.append("<ul>\n");
252                                 for (TorrentFile torrentFile : episode) {
253                                         htmlBuilder.append("<li>").append(StringEscapeUtils.escapeHtml4(torrentFile.name())).append("</li>\n");
254                                         htmlBuilder.append("<div>");
255                                         htmlBuilder.append("<strong>").append(StringEscapeUtils.escapeHtml4(torrentFile.size())).append("</strong>, ");
256                                         htmlBuilder.append("<strong>").append(torrentFile.fileCount()).append("</strong> file(s), ");
257                                         htmlBuilder.append("<strong>").append(torrentFile.seedCount()).append("</strong> seed(s), ");
258                                         htmlBuilder.append("<strong>").append(torrentFile.leechCount()).append("</strong> leecher(s)</div>\n");
259                                         htmlBuilder.append("<div>");
260                                         if ((torrentFile.magnetUri() != null) && (torrentFile.magnetUri().length() > 0)) {
261                                                 htmlBuilder.append("<a href=\"").append(StringEscapeUtils.escapeHtml4(torrentFile.magnetUri())).append("\">Magnet</a> ");
262                                         }
263                                         if ((torrentFile.downloadUri() != null) && (torrentFile.downloadUri().length() > 0)) {
264                                                 htmlBuilder.append("<a href=\"").append(StringEscapeUtils.escapeHtml4(torrentFile.downloadUri())).append("\">Download</a>");
265                                         }
266                                         htmlBuilder.append("</div>\n");
267                                 }
268                                 htmlBuilder.append("</ul>\n");
269                         }
270                         htmlBuilder.append("</ul>\n");
271                 }
272                 htmlBuilder.append("</body></html>\n");
273                 return htmlBuilder.toString();
274         }
275
276 }