version 0.4.8.1: set maxRetries to -1
[jSite.git] / src / de / todesbaum / jsite / application / ProjectInserter.java
1 /*
2  * jSite - a tool for uploading websites into Freenet
3  * Copyright (C) 2006 David Roden
4  *
5  * This program is free software; you can redistribute it and/or modify
6  * it under the terms of the GNU General Public License as published by
7  * the Free Software Foundation; either version 2 of the License, or
8  * (at your option) any later version.
9  *
10  * This program is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  * GNU General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License
16  * along with this program; if not, write to the Free Software
17  * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
18  */
19
20 package de.todesbaum.jsite.application;
21
22 import java.io.ByteArrayInputStream;
23 import java.io.ByteArrayOutputStream;
24 import java.io.File;
25 import java.io.FileInputStream;
26 import java.io.FileOutputStream;
27 import java.io.IOException;
28 import java.io.InputStream;
29 import java.util.ArrayList;
30 import java.util.HashMap;
31 import java.util.List;
32 import java.util.Map;
33 import java.util.zip.ZipEntry;
34 import java.util.zip.ZipOutputStream;
35
36 import de.todesbaum.jsite.gui.FileScanner;
37 import de.todesbaum.jsite.gui.FileScannerListener;
38 import de.todesbaum.util.freenet.fcp2.Client;
39 import de.todesbaum.util.freenet.fcp2.ClientPutComplexDir;
40 import de.todesbaum.util.freenet.fcp2.Connection;
41 import de.todesbaum.util.freenet.fcp2.DirectFileEntry;
42 import de.todesbaum.util.freenet.fcp2.FileEntry;
43 import de.todesbaum.util.freenet.fcp2.Message;
44 import de.todesbaum.util.freenet.fcp2.RedirectFileEntry;
45 import de.todesbaum.util.freenet.fcp2.Verbosity;
46 import de.todesbaum.util.io.ReplacingOutputStream;
47 import de.todesbaum.util.io.StreamCopier;
48
49 /**
50  * @author David Roden <droden@gmail.com>
51  * @version $Id$
52  */
53 public class ProjectInserter implements FileScannerListener, Runnable {
54
55         private static int counter = 0;
56         private boolean debug = false;
57         private List<InsertListener> insertListeners = new ArrayList<InsertListener>();
58         protected Freenet7Interface freenetInterface;
59         protected Project project;
60         private FileScanner fileScanner;
61         protected final Object lockObject = new Object();
62
63         public void addInsertListener(InsertListener insertListener) {
64                 insertListeners.add(insertListener);
65         }
66
67         public void removeInsertListener(InsertListener insertListener) {
68                 insertListeners.remove(insertListener);
69         }
70
71         protected void fireProjectInsertStarted() {
72                 for (InsertListener insertListener: insertListeners) {
73                         insertListener.projectInsertStarted(project);
74                 }
75         }
76         
77         protected void fireProjectURIGenerated(String uri) {
78                 for (InsertListener insertListener: insertListeners) {
79                         insertListener.projectURIGenerated(project, uri);
80                 }
81         }
82
83         protected void fireProjectInsertProgress(int succeeded, int failed, int fatal, int total, boolean finalized) {
84                 for (InsertListener insertListener: insertListeners) {
85                         insertListener.projectInsertProgress(project, succeeded, failed, fatal, total, finalized);
86                 }
87         }
88
89         protected void fireProjectInsertFinished(boolean success, Throwable cause) {
90                 for (InsertListener insertListener: insertListeners) {
91                         insertListener.projectInsertFinished(project, success, cause);
92                 }
93         }
94
95         /**
96          * @param debug
97          *            The debug to set.
98          */
99         public void setDebug(boolean debug) {
100                 this.debug = debug;
101         }
102
103         /**
104          * @param project
105          *            The project to set.
106          */
107         public void setProject(Project project) {
108                 this.project = project;
109         }
110
111         /**
112          * @param freenetInterface
113          *            The freenetInterface to set.
114          */
115         public void setFreenetInterface(Freenet7Interface freenetInterface) {
116                 this.freenetInterface = freenetInterface;
117         }
118
119         public void start() {
120                 fileScanner = new FileScanner(project);
121                 fileScanner.addFileScannerListener(this);
122                 new Thread(fileScanner).start();
123         }
124
125         private InputStream createFileInputStream(String filename, FileOption fileOption, int edition, long[] length) throws IOException {
126                 File file = new File(project.getLocalPath(), filename);
127                 length[0] = file.length();
128                 if (!fileOption.getReplaceEdition()) {
129                         return new FileInputStream(file);
130                 }
131                 ByteArrayOutputStream filteredByteOutputStream = new ByteArrayOutputStream(Math.min(Integer.MAX_VALUE, (int) length[0]));
132                 ReplacingOutputStream outputStream = new ReplacingOutputStream(filteredByteOutputStream);
133                 FileInputStream fileInput = new FileInputStream(file);
134                 outputStream.addReplacement("$[EDITION]", String.valueOf(edition));
135                 outputStream.addReplacement("$[URI]", project.getFinalRequestURI(0));
136                 for (int index = 1; index <= fileOption.getEditionRange(); index++) {
137                         outputStream.addReplacement("$[URI+" + index + "]", project.getFinalRequestURI(index));
138                         outputStream.addReplacement("$[EDITION+" + index + "]", String.valueOf(edition + index));
139                 }
140                 StreamCopier.copy(fileInput, outputStream, length[0]);
141                 outputStream.close();
142                 filteredByteOutputStream.close();
143                 byte[] filteredBytes = filteredByteOutputStream.toByteArray();
144                 length[0] = filteredBytes.length;
145                 return new ByteArrayInputStream(filteredBytes);
146         }
147
148         private InputStream createContainerInputStream(Map<String, List<String>> containerFiles, String containerName, int edition, long[] containerLength) throws IOException {
149                 File tempFile = File.createTempFile("jsite", ".zip");
150                 tempFile.deleteOnExit();
151                 FileOutputStream fileOutputStream = new FileOutputStream(tempFile);
152                 ZipOutputStream zipOutputStream = new ZipOutputStream(fileOutputStream);
153                 for (String filename: containerFiles.get(containerName)) {
154                         File dataFile = new File(project.getLocalPath(), filename);
155                         if (dataFile.exists()) {
156                                 ZipEntry zipEntry = new ZipEntry(filename);
157                                 long[] fileLength = new long[1];
158                                 InputStream wrappedInputStream = createFileInputStream(filename, project.getFileOption(filename), edition, fileLength);
159                                 zipOutputStream.putNextEntry(zipEntry);
160                                 StreamCopier.copy(wrappedInputStream, zipOutputStream, fileLength[0]);
161                                 zipOutputStream.closeEntry();
162                                 wrappedInputStream.close();
163                         }
164                 }
165                 zipOutputStream.closeEntry();
166
167                 /* FIXME - create metadata */
168                 // ZipEntry metadataEntry = new ZipEntry("metadata");
169                 // zipOutputStream.putNextEntry(metadataEntry);
170                 // Metadata zipMetadata = new Metadata();
171                 // for (String filename: containerFiles.get(containerName)) {
172                 // if (new File(project.getLocalPath(), filename).exists()) {
173                 // DocumentMetadata zipEntryMetadata = new DocumentMetadata();
174                 // zipEntryMetadata.setName(filename);
175                 // zipEntryMetadata.setFormat(project.getFileOption(filename).getMimeType());
176                 // zipMetadata.addDocument(zipEntryMetadata);
177                 // }
178                 // }
179                 // zipOutputStream.write(zipMetadata.toByteArray());
180                 // zipOutputStream.closeEntry();
181                 zipOutputStream.close();
182
183                 containerLength[0] = tempFile.length();
184                 return new FileInputStream(tempFile);
185         }
186
187         private FileEntry createFileEntry(String filename, int edition, Map<String, List<String>> containerFiles) {
188                 FileEntry fileEntry = null;
189                 FileOption fileOption = project.getFileOption(filename);
190                 if (filename.startsWith("/container/:")) {
191                         String containerName = filename.substring("/container/:".length());
192                         try {
193                                 long[] containerLength = new long[1];
194                                 InputStream containerInputStream = createContainerInputStream(containerFiles, containerName, edition, containerLength);
195                                 fileEntry = new DirectFileEntry(containerName + ".zip", "application/zip", containerInputStream, containerLength[0]);
196                         } catch (IOException ioe1) {
197                         }
198                 } else {
199                         if (fileOption.isInsert()) {
200                                 try {
201                                         long[] fileLength = new long[1];
202                                         InputStream fileEntryInputStream = createFileInputStream(filename, fileOption, edition, fileLength);
203                                         fileEntry = new DirectFileEntry(filename, project.getFileOption(filename).getMimeType(), fileEntryInputStream, fileLength[0]);
204                                 } catch (IOException ioe1) {
205                                 }
206                         } else {
207                                 fileEntry = new RedirectFileEntry(filename, fileOption.getMimeType(), fileOption.getCustomKey());
208                         }
209                 }
210                 return fileEntry;
211         }
212
213         private void createContainers(List<String> files, List<String> containers, Map<String, List<String>> containerFiles) {
214                 for (String filename: new ArrayList<String>(files)) {
215                         FileOption fileOption = project.getFileOption(filename);
216                         String containerName = fileOption.getContainer();
217                         if (!containerName.equals("")) {
218                                 if (!containers.contains(containerName)) {
219                                         containers.add(containerName);
220                                         containerFiles.put(containerName, new ArrayList<String>());
221                                         /* hmm. looks like a hack to me. */
222                                         files.add("/container/:" + containerName);
223                                 }
224                                 containerFiles.get(containerName).add(filename);
225                                 files.remove(filename);
226                         }
227                 }
228         }
229
230         /**
231          * {@inheritDoc}
232          */
233         public void run() {
234                 fireProjectInsertStarted();
235                 List<String> files = fileScanner.getFiles();
236
237                 /* create connection to node */
238                 Connection connection = freenetInterface.getConnection("project-insert-" + counter++);
239                 boolean connected = false;
240                 Throwable cause = null;
241                 try {
242                         connected = connection.connect();
243                 } catch (IOException e1) {
244                         cause = e1;
245                 }
246                 
247                 if (!connected) {
248                         fireProjectInsertFinished(false, cause);
249                         return;
250                 }
251                 
252                 Client client = new Client(connection);
253
254                 /* create containers */
255                 final List<String> containers = new ArrayList<String>();
256                 final Map<String, List<String>> containerFiles = new HashMap<String, List<String>>();
257                 createContainers(files, containers, containerFiles);
258
259                 /* collect files */
260                 int edition = project.getEdition();
261                 String dirURI = "freenet:USK@" + project.getInsertURI() + "/" + project.getPath() + "/" + edition + "/";
262                 ClientPutComplexDir putDir = new ClientPutComplexDir("dir-" + counter++, dirURI);
263                 putDir.setDefaultName(project.getIndexFile());
264                 putDir.setVerbosity(Verbosity.ALL);
265                 putDir.setMaxRetries(-1);
266                 for (String filename: files) {
267                         FileEntry fileEntry = createFileEntry(filename, edition, containerFiles);
268                         if (fileEntry != null) {
269                                 putDir.addFileEntry(fileEntry);
270                         }
271                 }
272
273                 /* start request */
274                 try {
275                         client.execute(putDir);
276                 } catch (IOException ioe1) {
277                         fireProjectInsertFinished(false, ioe1);
278                         return;
279                 }
280
281                 /* parse progress and success messages */
282                 String finalURI = null;
283                 boolean success = false;
284                 boolean finished = false;
285                 boolean disconnected = false;
286                 while (!finished) {
287                         Message message = client.readMessage();
288                         finished = (message == null) || (disconnected = client.isDisconnected());
289                         if (debug) {
290                                 System.out.println(message);
291                         }
292                         if (!finished) {
293                                 String messageName = message.getName();
294                                 if ("URIGenerated".equals(messageName)) {
295                                         finalURI = message.get("URI");
296                                         fireProjectURIGenerated(finalURI);
297                                 }
298                                 if ("SimpleProgress".equals(messageName)) {
299                                         int total = Integer.parseInt(message.get("Total"));
300                                         int succeeded = Integer.parseInt(message.get("Succeeded"));
301                                         int fatal = Integer.parseInt(message.get("FatallyFailed"));
302                                         int failed = Integer.parseInt(message.get("Failed"));
303                                         boolean finalized = Boolean.parseBoolean(message.get("FinalizedTotal"));
304                                         fireProjectInsertProgress(succeeded, failed, fatal, total, finalized);
305                                 }
306                                 success = "PutSuccessful".equals(messageName);
307                                 finished = success || "PutFailed".equals(messageName);
308                         }
309                 }
310
311                 /* post-insert work */
312                 fireProjectInsertFinished(success, disconnected ? new IOException("Connection terminated") : null);
313                 if (success) {
314                         String editionPart = finalURI.substring(finalURI.lastIndexOf('/') + 1);
315                         int newEdition = Integer.parseInt(editionPart);
316                         project.setEdition(newEdition);
317                 }
318         }
319
320         //
321         // INTERFACE FileScannerListener
322         //
323
324         /**
325          * {@inheritDoc}
326          */
327         public void fileScannerFinished(FileScanner fileScanner) {
328                 if (!fileScanner.isError()) {
329                         new Thread(this).start();
330                 } else {
331                         fireProjectInsertFinished(false, null);
332                 }
333                 fileScanner.removeFileScannerListener(this);
334         }
335
336 }