View Javadoc

1   /*
2    *  XNap - A P2P framework and client.
3    *
4    *  See the file AUTHORS for copyright information.
5    *
6    *  This program is free software; you can redistribute it and/or modify
7    *  it under the terms of the GNU General Public License as published by
8    *  the Free Software Foundation.
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 org.xnap.plugin.opennap.net;
21  
22  import java.io.BufferedInputStream;
23  import java.io.BufferedOutputStream;
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.io.OutputStream;
30  import java.util.Iterator;
31  import java.util.LinkedList;
32  import java.util.List;
33  import java.util.Properties;
34  
35  import org.apache.log4j.Logger;
36  import org.xnap.util.FileHelper;
37  import org.xnap.util.Preferences;
38  
39  /***
40   * Provides a repository that handles the resume information for
41   * downloads in the incomplete directory.
42   * 
43   * <p>This class is mostly thread safe (except for {@link #iterator()}).
44   */
45  public class OpenNapResumeRepository {
46  
47      //--- Constant(s) ---
48  	
49  	/***
50  	 * The prefix used for resume files.
51  	 */
52  	public static final String FILENAME_PREFIX = ".xnap-openap-resume";
53      
54      //--- Data Field(s) ---
55  
56      private static Logger logger
57  		= Logger.getLogger(OpenNapResumeRepository.class);
58  
59      private List list = new LinkedList();
60  
61      //--- Constructor(s) ---
62      
63      public OpenNapResumeRepository()
64      {
65      }
66  
67      //--- Method(s) ---
68  
69      /***
70       * Adds a download to the repository. Creates a resume data file in path.
71  	 *
72  	 * @param path the incomplete directory
73       */
74      public synchronized void add
75  		(File path, OpenNapDownloadContainerData download)
76      {
77  		list.add(download);
78  		if (download.resumeFile == null) {
79  			try {
80  				download.resumeFile 
81  					= FileHelper.createUnique
82  					(path, 
83  					 FILENAME_PREFIX + "." + download.filename + ".xnap3");
84  				write(download);
85  			}
86  			catch(IOException e) {
87  				logger.error("Could not create resume file.", e);
88  			}
89  		}
90      }
91  
92      /***
93       * Returns an iterator over all items in the repository.
94       */
95      public Iterator iterator()
96      {
97  		return list.iterator();
98      }
99  
100     /***
101      * Removes a download from the repository, i.e. when the download
102      * has succeeded.
103 	 */
104     public synchronized void remove(OpenNapDownloadContainerData download)
105     {
106 		if (download.resumeFile != null) {
107 			download.resumeFile.delete();
108 		}
109 		list.remove(download);
110     }
111 
112     /***
113      * Restores a download from file.
114      */
115 	private synchronized OpenNapDownloadContainerData read(File file)
116 		throws IOException
117 	{
118 		InputStream in = new BufferedInputStream(new FileInputStream(file));
119 		try {
120 			Properties p = new Properties();
121 			p.load(in);
122 
123 			OpenNapDownloadContainerData data
124 				= new OpenNapDownloadContainerData();
125 			data.setAutoSearchingEnabled
126 				(p.getProperty
127 				 ("autoSearchingEnabled", 
128 				  Preferences.getInstance().getAlwaysAutoDownload() + ""));
129 			data.setFilename(p.getProperty("filename"));
130 			data.setFilesize(p.getProperty("filesize"));
131 			data.setRealm(p.getProperty("searchRealm"));
132 			data.setSearchText(p.getProperty("searchText"));
133 
134 			// restore segments
135 			List segments = new LinkedList();
136 			int i = 0;
137 			while (p.getProperty("segment." + i + ".filename") != null) {
138 				try {
139 					OpenNapSegmentData segment = new OpenNapSegmentData();
140 					segment.setFile
141 						(file.getParentFile(), 
142 						 p.getProperty("segment." + i + ".filename"));
143 					segment.setStart
144 						(p.getProperty("segment." + i + ".start"));
145 					segment.setMergeFailCount
146 						(p.getProperty("segment." + i + ".mergeFailCount"));
147 					
148 					segments.add(segment);
149 				}
150 				catch (IllegalArgumentException e) {
151 					// invalid segment, ignore
152 					logger.warn("Invalid segment: " + i + ", " 
153 								+ file.getAbsolutePath());
154 				}
155 				
156 				i++;
157 			}
158 			data.setSegments
159 				((OpenNapSegmentData[])segments.toArray(new OpenNapSegmentData[0]));
160 			
161 			data.resumeFile = file;
162 			return data;
163 		}
164 		catch (IllegalArgumentException e) {
165 			throw new IOException("Invalid data");
166 		}
167 		finally {
168 			try {
169 				in.close();
170 			}
171 			catch (IOException e) {
172 				// this exception gets lost
173 			}
174 		}
175 	}
176 	
177     /***
178      * Scans a directory for resume files and adds the found downloads.
179      *
180      * @param path the directory to scan
181      * @see #add(OpenNapDownloadContainerData)
182      */
183     public synchronized void restore(File path)
184     {
185 		logger.debug("Restoring resumes from " + path.getAbsolutePath());
186 		
187 		File[] files = path.listFiles();
188 		if (files != null) {
189 			for (int i = 0; i < files.length; i++) {
190 				if (!files[i].isDirectory() 
191 					&& files[i].getName().startsWith(FILENAME_PREFIX)) {
192 
193 					try {
194 						OpenNapDownloadContainerData data = read(files[i]);
195 						add(path, data);
196 					}
197 					catch (IOException e) {
198 						logger.warn("Could not restore incomplete file", e);
199 					}
200 				}
201 			}
202 		}
203     }
204 
205 
206     /***
207      * Writes repository content to <code>filename</code>.
208      */
209     public synchronized void save(File path) 
210     {
211 		for (Iterator i = list.iterator(); i.hasNext();) {
212 			try {
213 				OpenNapDownloadContainerData data
214 					= (OpenNapDownloadContainerData)i.next();
215 				write(data);
216 			}
217 			catch(IOException e) {
218 				logger.warn("Could not write resume data", e);
219 			}
220 		}
221     }
222 
223     /***
224      * Saves the resume information for data.
225      */
226 	private synchronized void write(OpenNapDownloadContainerData data)
227 		throws IOException
228 	{
229 		OutputStream out 
230 			= new BufferedOutputStream(new FileOutputStream(data.resumeFile));
231 		try {
232 			Properties p = new Properties();
233 
234 			p.setProperty("autoSearchingEnabled", 
235 						  data.autoSearchingEnabled + "");
236 			p.setProperty("filename", data.filename);
237 			p.setProperty("filesize", data.filesize + "");
238 			if (data.realm != null) {
239 				p.setProperty("searchRealm", data.realm);
240 			}
241 			p.setProperty("searchText", data.searchText);
242 
243 			// save segments
244 			if (data.segments != null) {
245 				int c = 0;
246 				for (int i = 0; i < data.segments.length; i++) {
247 					if (data.segments[i].file == null) {
248 						// no need to keep track of empty segments
249 						continue;
250 					}
251 
252 					p.setProperty
253 						("segment." + c + ".filename",
254 						 data.segments[i].file.getAbsolutePath());
255 					p.setProperty
256 						("segment." + c + ".start", 
257 						 data.segments[i].start + "");
258 					p.setProperty
259 						("segment." + c + ".mergeFailCount",
260 						 data.segments[i].mergeFailCount + "");
261 					c++;
262 				}
263 			}
264 
265 			p.store(out, "Automatically generated XNap OpenNap resume file - do not modify");
266 		}
267 		finally {
268 			try {
269 				out.close();
270 			}
271 			catch (IOException e) {
272 				// this exception gets lost
273 			}
274 		}
275 	}
276 
277 }
278