Return an optional from the Sone parser.
[Sone.git] / src / main / java / net / pterodactylus / sone / core / SoneDownloader.java
1 /*
2  * Sone - SoneDownloader.java - Copyright © 2010–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.sone.core;
19
20 import static net.pterodactylus.sone.data.Sone.TO_FREENET_URI;
21
22 import java.io.InputStream;
23 import java.util.HashSet;
24 import java.util.Set;
25 import java.util.logging.Level;
26 import java.util.logging.Logger;
27
28 import net.pterodactylus.sone.core.FreenetInterface.Fetched;
29 import net.pterodactylus.sone.data.Sone;
30 import net.pterodactylus.sone.data.Sone.SoneStatus;
31 import net.pterodactylus.util.io.Closer;
32 import net.pterodactylus.util.logging.Logging;
33 import net.pterodactylus.util.service.AbstractService;
34
35 import freenet.client.FetchResult;
36 import freenet.keys.FreenetURI;
37 import freenet.support.api.Bucket;
38
39 import com.google.common.base.Optional;
40
41 /**
42  * The Sone downloader is responsible for download Sones as they are updated.
43  *
44  * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
45  */
46 public class SoneDownloader extends AbstractService {
47
48         /** The logger. */
49         private static final Logger logger = Logging.getLogger(SoneDownloader.class);
50
51         /** The maximum protocol version. */
52         private static final int MAX_PROTOCOL_VERSION = 0;
53
54         /** The core. */
55         private final Core core;
56
57         /** The Freenet interface. */
58         private final FreenetInterface freenetInterface;
59
60         /** The sones to update. */
61         private final Set<Sone> sones = new HashSet<Sone>();
62
63         /**
64          * Creates a new Sone downloader.
65          *
66          * @param core
67          *            The core
68          * @param freenetInterface
69          *            The Freenet interface
70          */
71         public SoneDownloader(Core core, FreenetInterface freenetInterface) {
72                 super("Sone Downloader", false);
73                 this.core = core;
74                 this.freenetInterface = freenetInterface;
75         }
76
77         //
78         // ACTIONS
79         //
80
81         /**
82          * Adds the given Sone to the set of Sones that will be watched for updates.
83          *
84          * @param sone
85          *            The Sone to add
86          */
87         public void addSone(Sone sone) {
88                 if (!sones.add(sone)) {
89                         freenetInterface.unregisterUsk(sone);
90                 }
91                 freenetInterface.registerUsk(sone, this);
92         }
93
94         /**
95          * Removes the given Sone from the downloader.
96          *
97          * @param sone
98          *            The Sone to stop watching
99          */
100         public void removeSone(Sone sone) {
101                 if (sones.remove(sone)) {
102                         freenetInterface.unregisterUsk(sone);
103                 }
104         }
105
106         /**
107          * Fetches the updated Sone. This method is a callback method for
108          * {@link FreenetInterface#registerUsk(Sone, SoneDownloader)}.
109          *
110          * @param sone
111          *            The Sone to fetch
112          */
113         public void fetchSone(Sone sone) {
114                 fetchSone(sone, TO_FREENET_URI.apply(sone).sskForUSK());
115         }
116
117         /**
118          * Fetches the updated Sone. This method can be used to fetch a Sone from a
119          * specific URI.
120          *
121          * @param sone
122          *            The Sone to fetch
123          * @param soneUri
124          *            The URI to fetch the Sone from
125          */
126         public void fetchSone(Sone sone, FreenetURI soneUri) {
127                 fetchSone(sone, soneUri, false);
128         }
129
130         /**
131          * Fetches the Sone from the given URI.
132          *
133          * @param sone
134          *            The Sone to fetch
135          * @param soneUri
136          *            The URI of the Sone to fetch
137          * @param fetchOnly
138          *            {@code true} to only fetch and parse the Sone, {@code false}
139          *            to {@link Core#updateSone(Sone) update} it in the core
140          * @return The downloaded Sone, or {@code null} if the Sone could not be
141          *         downloaded
142          */
143         public Optional<Sone> fetchSone(Sone sone, FreenetURI soneUri, boolean fetchOnly) {
144                 logger.log(Level.FINE, String.format("Starting fetch for Sone “%s” from %s…", sone, soneUri));
145                 FreenetURI requestUri = soneUri.setMetaString(new String[] { "sone.xml" });
146                 sone.setStatus(SoneStatus.downloading);
147                 try {
148                         Fetched fetchResults = freenetInterface.fetchUri(requestUri);
149                         if (fetchResults == null) {
150                                 /* TODO - mark Sone as bad. */
151                                 return null;
152                         }
153                         logger.log(Level.FINEST, String.format("Got %d bytes back.", fetchResults.getFetchResult().size()));
154                         Optional<Sone> parsedSone = parseSone(sone, fetchResults.getFetchResult(), fetchResults.getFreenetUri());
155                         if (parsedSone.isPresent()) {
156                                 if (!fetchOnly) {
157                                         parsedSone.get().setStatus((parsedSone.get().getTime() == 0) ? SoneStatus.unknown : SoneStatus.idle);
158                                         core.updateSone(parsedSone.get());
159                                         addSone(parsedSone.get());
160                                 }
161                         }
162                         return parsedSone;
163                 } finally {
164                         sone.setStatus((sone.getTime() == 0) ? SoneStatus.unknown : SoneStatus.idle);
165                 }
166         }
167
168         /**
169          * Parses a Sone from a fetch result.
170          *
171          * @param originalSone
172          *            The sone to parse, or {@code null} if the Sone is yet unknown
173          * @param fetchResult
174          *            The fetch result
175          * @param requestUri
176          *            The requested URI
177          * @return The parsed Sone, or {@code null} if the Sone could not be parsed
178          */
179         public Optional<Sone> parseSone(Sone originalSone, FetchResult fetchResult, FreenetURI requestUri) {
180                 logger.log(Level.FINEST, String.format("Parsing FetchResult (%d bytes, %s) for %s…", fetchResult.size(), fetchResult.getMimeType(), originalSone));
181                 Bucket soneBucket = fetchResult.asBucket();
182                 InputStream soneInputStream = null;
183                 try {
184                         soneInputStream = soneBucket.getInputStream();
185                         Optional<Sone> parsedSone = parseSone(originalSone, soneInputStream);
186                         if (parsedSone.isPresent()) {
187                                 parsedSone.get().modify().setLatestEdition(requestUri.getEdition()).update();
188                         }
189                         return parsedSone;
190                 } catch (Exception e1) {
191                         logger.log(Level.WARNING, String.format("Could not parse Sone from %s!", requestUri), e1);
192                 } finally {
193                         Closer.close(soneInputStream);
194                         soneBucket.free();
195                 }
196                 return null;
197         }
198
199         /**
200          * Parses a Sone from the given input stream and creates a new Sone from the
201          * parsed data.
202          *
203          * @param originalSone
204          *            The Sone to update
205          * @param soneInputStream
206          *            The input stream to parse the Sone from
207          * @return The parsed Sone
208          */
209         public Optional<Sone> parseSone(Sone originalSone, InputStream soneInputStream) {
210                 return new SoneParser().parseSone(core.getDatabase(), originalSone, soneInputStream);
211         }
212
213         //
214         // SERVICE METHODS
215         //
216
217         @Override
218         protected void serviceStop() {
219                 for (Sone sone : sones) {
220                         freenetInterface.unregisterUsk(sone);
221                 }
222         }
223
224 }