🔀 Merge branch 'website/epic-games' into next
[rhynodge.git] / src / main / java / net / pterodactylus / rhynodge / states / TorrentState.java
1 /*
2  * Rhynodge - TorrentState.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.states;
19
20 import java.nio.charset.StandardCharsets;
21 import java.util.Collection;
22 import java.util.Collections;
23 import java.util.HashSet;
24 import java.util.Iterator;
25 import java.util.List;
26 import java.util.Optional;
27 import java.util.Set;
28
29 import javax.annotation.Nonnull;
30
31 import net.pterodactylus.rhynodge.Reaction;
32 import net.pterodactylus.rhynodge.State;
33 import net.pterodactylus.rhynodge.output.DefaultOutput;
34 import net.pterodactylus.rhynodge.output.Output;
35 import net.pterodactylus.rhynodge.states.TorrentState.TorrentFile;
36
37 import com.fasterxml.jackson.annotation.JsonProperty;
38 import com.google.common.collect.Lists;
39 import com.google.common.collect.Ordering;
40 import org.apache.commons.lang3.StringEscapeUtils;
41 import org.apache.http.NameValuePair;
42 import org.apache.http.client.utils.URLEncodedUtils;
43 import org.jetbrains.annotations.NotNull;
44 import org.jetbrains.annotations.Nullable;
45
46 import static com.google.common.collect.Ordering.from;
47 import static java.lang.String.format;
48
49 /**
50  * {@link State} that contains information about an arbitrary number of torrent
51  * files.
52  *
53  * @author <a href="mailto:bombe@pterodactylus.net">David â€˜Bombe’ Roden</a>
54  */
55 public class TorrentState extends AbstractState implements Iterable<TorrentFile> {
56
57         /** The torrent files. */
58         @JsonProperty
59         private List<TorrentFile> files = Lists.newArrayList();
60
61         private final Set<TorrentFile> newTorrentFiles = new HashSet<>();
62
63         /**
64          * Creates a new torrent state without torrent files.
65          */
66         public TorrentState() {
67                 this(Collections.<TorrentFile>emptySet());
68         }
69
70         /**
71          * Creates a new torrent state containing the given torrent files.
72          *
73          * @param torrentFiles
74          *            The torrent files
75          */
76         public TorrentState(Collection<TorrentFile> torrentFiles) {
77                 files.addAll(torrentFiles);
78         }
79
80         public TorrentState(Collection<TorrentFile> torrentFiles, Collection<TorrentFile> newTorrentFiles) {
81                 files.addAll(torrentFiles);
82                 this.newTorrentFiles.addAll(newTorrentFiles);
83         }
84
85         //
86         // ACCESSORS
87         //
88
89         @Override
90         public boolean isEmpty() {
91                 return files.isEmpty();
92         }
93
94         @Override
95         public boolean triggered() {
96                 return !newTorrentFiles.isEmpty();
97         }
98
99         /**
100          * Returns all torrent files of this state.
101          *
102          * @return All torrent files of this state
103          */
104         public Collection<TorrentFile> torrentFiles() {
105                 return Collections.unmodifiableList(files);
106         }
107
108         /**
109          * Adds a torrent file to this state.
110          *
111          * @param torrentFile
112          *            The torrent file to add
113          * @return This state
114          */
115         public TorrentState addTorrentFile(TorrentFile torrentFile) {
116                 files.add(torrentFile);
117                 return this;
118         }
119
120         @Nonnull
121         @Override
122         protected String summary(Reaction reaction) {
123                 return format("Found %d new Torrent(s) for â€œ%s!”", newTorrentFiles.size(), reaction.name());
124         }
125
126         @Nonnull
127         @Override
128         protected String plainText() {
129                 StringBuilder plainText = new StringBuilder();
130                 plainText.append("New Torrents:\n\n");
131                 for (TorrentFile torrentFile : newTorrentFiles) {
132                         plainText.append(torrentFile.name()).append('\n');
133                         plainText.append('\t').append(torrentFile.size()).append(" in ").append(torrentFile.fileCount()).append(" file(s)\n");
134                         plainText.append('\t').append(torrentFile.seedCount()).append(" seed(s), ").append(torrentFile.leechCount()).append(" leecher(s)\n");
135                         if ((torrentFile.magnetUri() != null) && (torrentFile.magnetUri().length() > 0)) {
136                                 plainText.append('\t').append(torrentFile.magnetUri()).append('\n');
137                         }
138                         if ((torrentFile.downloadUri() != null) && (torrentFile.downloadUri().length() > 0)) {
139                                 plainText.append('\t').append(torrentFile.downloadUri()).append('\n');
140                         }
141                         plainText.append('\n');
142                 }
143                 return plainText.toString();
144         }
145
146         @Nullable
147         @Override
148         protected String htmlText() {
149                 StringBuilder htmlBuilder = new StringBuilder();
150                 htmlBuilder.append("<html><body>\n");
151                 htmlBuilder.append("<table>\n<caption>All Known Torrents</caption>\n");
152                 htmlBuilder.append("<thead>\n");
153                 htmlBuilder.append("<tr>");
154                 htmlBuilder.append("<th>Filename</th>");
155                 htmlBuilder.append("<th>Size</th>");
156                 htmlBuilder.append("<th>File(s)</th>");
157                 htmlBuilder.append("<th>Seeds</th>");
158                 htmlBuilder.append("<th>Leechers</th>");
159                 htmlBuilder.append("<th>Magnet</th>");
160                 htmlBuilder.append("<th>Download</th>");
161                 htmlBuilder.append("</tr>\n");
162                 htmlBuilder.append("</thead>\n");
163                 htmlBuilder.append("<tbody>\n");
164                 for (TorrentFile torrentFile : sortNewFirst().sortedCopy(files)) {
165                         if (newTorrentFiles.contains(torrentFile)) {
166                                 htmlBuilder.append("<tr style=\"color: #008000; font-weight: bold;\">");
167                         } else {
168                                 htmlBuilder.append("<tr>");
169                         }
170                         htmlBuilder.append("<td>").append(StringEscapeUtils.escapeHtml4(torrentFile.name())).append("</td>");
171                         htmlBuilder.append("<td>").append(StringEscapeUtils.escapeHtml4(torrentFile.size())).append("</td>");
172                         htmlBuilder.append("<td>").append(torrentFile.fileCount()).append("</td>");
173                         htmlBuilder.append("<td>").append(torrentFile.seedCount()).append("</td>");
174                         htmlBuilder.append("<td>").append(torrentFile.leechCount()).append("</td>");
175                         htmlBuilder.append("<td><a href=\"").append(StringEscapeUtils.escapeHtml4(torrentFile.magnetUri())).append("\">Link</a></td>");
176                         htmlBuilder.append("<td><a href=\"").append(StringEscapeUtils.escapeHtml4(torrentFile.downloadUri())).append("\">Link</a></td>");
177                         htmlBuilder.append("</tr>\n");
178                 }
179                 htmlBuilder.append("</tbody>\n");
180                 htmlBuilder.append("</table>\n");
181                 htmlBuilder.append("</body></html>\n");
182                 return htmlBuilder.toString();
183         }
184
185         /**
186          * Returns an ordering that sorts torrent files by whether they are new
187          * (according to {@link #files}) or not. New files will be sorted
188          * first.
189          *
190          * @return An ordering for â€œnew files first”
191          */
192         private Ordering<TorrentFile> sortNewFirst() {
193                 return from((TorrentFile leftTorrentFile, TorrentFile rightTorrentFile) -> {
194                         if (newTorrentFiles.contains(leftTorrentFile) && !newTorrentFiles.contains(rightTorrentFile)) {
195                                 return -1;
196                         }
197                         if (!newTorrentFiles.contains(leftTorrentFile) && newTorrentFiles.contains(rightTorrentFile)) {
198                                 return 1;
199                         }
200                         return 0;
201                 });
202         }
203
204         //
205         // ITERABLE METHODS
206         //
207
208         /**
209          * {@inheritDoc}
210          */
211         @Override
212         public Iterator<TorrentFile> iterator() {
213                 return files.iterator();
214         }
215
216         //
217         // OBJECT METHODS
218         //
219
220         /**
221          * {@inheritDoc}
222          */
223         @Override
224         public String toString() {
225                 return format("%s[files=%s]", getClass().getSimpleName(), files);
226         }
227
228         /**
229          * Container for torrent file data.
230          *
231          * @author <a href="mailto:bombe@pterodactylus.net">David â€˜Bombe’ Roden</a>
232          */
233         public static class TorrentFile {
234
235                 /** The name of the file. */
236                 @JsonProperty
237                 private final String name;
238
239                 /** The size of the file. */
240                 @JsonProperty
241                 private final String size;
242
243                 /** The magnet URI of the file. */
244                 @JsonProperty
245                 private final String magnetUri;
246
247                 /** The download URI of the file. */
248                 @JsonProperty
249                 private final String downloadUri;
250
251                 /** The number of files in this torrent. */
252                 @JsonProperty
253                 private final int fileCount;
254
255                 /** The number of seeds connected to this torrent. */
256                 @JsonProperty
257                 private final int seedCount;
258
259                 /** The number of leechers connected to this torrent. */
260                 @JsonProperty
261                 private final int leechCount;
262
263                 /**
264                  * No-arg constructor for deserialization.
265                  */
266                 @SuppressWarnings("unused")
267                 private TorrentFile() {
268                         this(null, null, null, null, 0, 0, 0);
269                 }
270
271                 /**
272                  * Creates a new torrent file.
273                  *
274                  * @param name
275                  *            The name of the file
276                  * @param size
277                  *            The size of the file
278                  * @param magnetUri
279                  *            The magnet URI of the file
280                  * @param downloadUri
281                  *            The download URI of the file
282                  * @param fileCount
283                  *            The number of files
284                  * @param seedCount
285                  *            The number of connected seeds
286                  * @param leechCount
287                  *            The number of connected leechers
288                  */
289                 public TorrentFile(String name, String size, String magnetUri, String downloadUri, int fileCount, int seedCount, int leechCount) {
290                         this.name = name;
291                         this.size = size;
292                         this.magnetUri = magnetUri;
293                         this.downloadUri = downloadUri;
294                         this.fileCount = fileCount;
295                         this.seedCount = seedCount;
296                         this.leechCount = leechCount;
297                 }
298
299                 //
300                 // ACCESSORS
301                 //
302
303                 /**
304                  * Returns the name of the file.
305                  *
306                  * @return The name of the file
307                  */
308                 public String name() {
309                         return name;
310                 }
311
312                 /**
313                  * Returns the size of the file. The returned size may included
314                  * non-numeric information, such as units (e. g. â€œ860.46 MB”).
315                  *
316                  * @return The size of the file
317                  */
318                 public String size() {
319                         return size;
320                 }
321
322                 /**
323                  * Returns the magnet URI of the file.
324                  *
325                  * @return The magnet URI of the file, or {@code null} if there is no
326                  *         magnet URI for this torrent file
327                  */
328                 public String magnetUri() {
329                         return magnetUri;
330                 }
331
332                 /**
333                  * Returns the download URI of the file.
334                  *
335                  * @return The download URI of the file, or {@code null} if there is no
336                  *         download URI for this torrent file
337                  */
338                 public String downloadUri() {
339                         return downloadUri;
340                 }
341
342                 /**
343                  * Returns the number of files in this torrent.
344                  *
345                  * @return The number of files in this torrent
346                  */
347                 public int fileCount() {
348                         return fileCount;
349                 }
350
351                 /**
352                  * Returns the number of seeds connected to this torrent.
353                  *
354                  * @return The number of connected seeds
355                  */
356                 public int seedCount() {
357                         return seedCount;
358                 }
359
360                 /**
361                  * Returns the number of leechers connected to this torrent.
362                  *
363                  * @return The number of connected leechers
364                  */
365                 public int leechCount() {
366                         return leechCount;
367                 }
368
369                 //
370                 // PRIVATE METHODS
371                 //
372
373                 /**
374                  * Generates an ID for this file. If a {@link #magnetUri} is set, an ID
375                  * is {@link #extractId(String) extracted} from it. Otherwise the magnet
376                  * URI is used. If the {@link #magnetUri} is not set, the
377                  * {@link #downloadUri} is used. If that is not set either, the name of
378                  * the file is returned.
379                  *
380                  * @return The generated ID
381                  */
382                 private String generateId() {
383                         if (magnetUri != null) {
384                                 return extractId(magnetUri).orElse(magnetUri);
385                         }
386                         return (downloadUri != null) ? downloadUri : name;
387                 }
388
389                 //
390                 // STATIC METHODS
391                 //
392
393                 /**
394                  * Tries to extract the â€œexact target” of a magnet URI.
395                  *
396                  * @param magnetUri
397                  *            The magnet URI to extract the â€œxt” from
398                  * @return The extracted ID, or {@code null} if no ID could be found
399                  */
400                 private static Optional<String> extractId(String magnetUri) {
401                         if ((magnetUri == null) || (magnetUri.length() < 8)) {
402                                 return Optional.empty();
403                         }
404                         List<NameValuePair> parameters = URLEncodedUtils.parse(magnetUri.substring("magnet:?".length()), StandardCharsets.UTF_8);
405                         for (NameValuePair parameter : parameters) {
406                                 if (parameter.getName().equals("xt")) {
407                                         return Optional.of(parameter.getValue().toLowerCase());
408                                 }
409                         }
410                         return Optional.empty();
411                 }
412
413                 //
414                 // OBJECT METHODS
415                 //
416
417                 /**
418                  * {@inheritDoc}
419                  */
420                 @Override
421                 public int hashCode() {
422                         return (generateId() != null) ? generateId().hashCode() : 0;
423                 }
424
425                 /**
426                  * {@inheritDoc}
427                  */
428                 @Override
429                 public boolean equals(Object object) {
430                         if (!(object instanceof TorrentFile)) {
431                                 return false;
432                         }
433                         if (generateId() != null) {
434                                 return generateId().equals(((TorrentFile) object).generateId());
435                         }
436                         return false;
437                 }
438
439                 /**
440                  * {@inheritDoc}
441                  */
442                 @Override
443                 public String toString() {
444                         return format("%s(%s,%s,%s)", name(), size(), magnetUri(), downloadUri());
445                 }
446
447         }
448
449 }