1 /****************************************************************
2 * Licensed to the Apache Software Foundation (ASF) under one *
3 * or more contributor license agreements. See the NOTICE file *
4 * distributed with this work for additional information *
5 * regarding copyright ownership. The ASF licenses this file *
6 * to you under the Apache License, Version 2.0 (the *
7 * "License"); you may not use this file except in compliance *
8 * with the License. You may obtain a copy of the License at *
9 * *
10 * http://www.apache.org/licenses/LICENSE-2.0 *
11 * *
12 * Unless required by applicable law or agreed to in writing, *
13 * software distributed under the License is distributed on an *
14 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY *
15 * KIND, either express or implied. See the License for the *
16 * specific language governing permissions and limitations *
17 * under the License. *
18 ****************************************************************/
19
20
21
22 package org.apache.james.nntpserver.repository;
23
24 import org.apache.avalon.framework.logger.AbstractLogEnabled;
25 import org.apache.james.nntpserver.DateSinceFileFilter;
26 import org.apache.james.util.io.AndFileFilter;
27 import org.apache.james.util.io.ExtensionFileFilter;
28 import org.apache.james.util.io.IOUtil;
29 import org.apache.james.util.io.InvertedFileFilter;
30
31 import java.io.File;
32 import java.io.FileOutputStream;
33 import java.io.InputStream;
34 import java.io.IOException;
35 import java.util.ArrayList;
36 import java.util.Date;
37 import java.util.Iterator;
38 import java.util.List;
39
40 /**
41 * Group is represented by a directory.
42 * Articles are stored in files with the name of file == article number
43 *
44 */
45 class NNTPGroupImpl extends AbstractLogEnabled implements NNTPGroup {
46
47 /**
48 * The directory to which this group maps.
49 */
50 private final File root;
51
52 /**
53 * The last article number in the group
54 */
55 private int lastArticle;
56
57 /**
58 * The last article number in the group
59 */
60 private int firstArticle;
61
62 /**
63 * The number of articles in the group.
64 */
65 private int numOfArticles;
66
67 /**
68 * Whether the first, last, and total number of articles in the
69 * group have been read from disk.
70 * An instance may collect range info once. This involves disk I/O
71 */
72 private boolean articleRangeInfoCollected = false;
73
74 /**
75 * The sole constructor for this particular NNTPGroupImpl.
76 *
77 * @param root the directory containing the articles
78 */
79 NNTPGroupImpl(File root) {
80 this.root = root;
81 }
82
83 /**
84 * @see org.apache.james.nntpserver.repository.NNTPGroup#getName()
85 */
86 public String getName() {
87 return root.getName();
88 }
89
90 /**
91 * @see org.apache.james.nntpserver.repository.NNTPGroup#getDescription()
92 */
93 public String getDescription() {
94 return getName();
95 }
96
97 /**
98 * @see org.apache.james.nntpserver.repository.NNTPGroup#isPostAllowed()
99 */
100 public boolean isPostAllowed() {
101 return true;
102 }
103
104 /**
105 * Generates the first, last, and number of articles from the
106 * information in the group directory.
107 */
108 private void collectArticleRangeInfo() {
109 if ( articleRangeInfoCollected ) {
110 return;
111 }
112 String[] list = root.list();
113 int first = -1;
114 int last = -1;
115 for ( int i = 0 ; i < list.length ; i++ ) {
116 int num = Integer.parseInt(list[i]);
117 if ( first == -1 || num < first ) {
118 first = num;
119 }
120 if ( num > last ) {
121 last = num;
122 }
123 }
124 numOfArticles = list.length;
125 firstArticle = Math.max(first,0);
126 lastArticle = Math.max(last,0);
127 articleRangeInfoCollected = true;
128 }
129
130 /**
131 * @see org.apache.james.nntpserver.repository.NNTPGroup#getNumberOfArticles()
132 */
133 public int getNumberOfArticles() {
134 collectArticleRangeInfo();
135 return numOfArticles;
136 }
137
138 /**
139 * @see org.apache.james.nntpserver.repository.NNTPGroup#getFirstArticleNumber()
140 */
141 public int getFirstArticleNumber() {
142 collectArticleRangeInfo();
143 return firstArticle;
144 }
145
146 /**
147 * @see org.apache.james.nntpserver.repository.NNTPGroup#getLastArticleNumber()
148 */
149 public int getLastArticleNumber() {
150 collectArticleRangeInfo();
151 return lastArticle;
152 }
153
154 /**
155 * @see org.apache.james.nntpserver.repository.NNTPGroup#getArticle(int)
156 */
157 public NNTPArticle getArticle(int number) {
158 File f = new File(root,number + "");
159 return f.exists() ? new NNTPArticleImpl(this, f) : null;
160 }
161
162 /**
163 * @see org.apache.james.nntpserver.repository.NNTPGroup#getArticlesSince(Date)
164 */
165 public Iterator getArticlesSince(Date dt) {
166 File[] f = root.listFiles(new AndFileFilter
167 (new DateSinceFileFilter(dt.getTime()),
168 new InvertedFileFilter(new ExtensionFileFilter(".id"))));
169 List list = new ArrayList();
170 for ( int i = 0 ; i < f.length ; i++ ) {
171 list.add(new NNTPArticleImpl(this, f[i]));
172 }
173 return list.iterator();
174 }
175
176 /**
177 * @see org.apache.james.nntpserver.repository.NNTPGroup#getArticles()
178 */
179 public Iterator getArticles() {
180 File[] f = root.listFiles();
181 List list = new ArrayList();
182 for ( int i = 0 ; i < f.length ; i++ )
183 list.add(new NNTPArticleImpl(this, f[i]));
184 return list.iterator();
185 }
186
187 /**
188 * @see org.apache.james.nntpserver.repository.NNTPGroup#getListFormat()
189 */
190 public String getListFormat() {
191 StringBuffer showBuffer =
192 new StringBuffer(128)
193 .append(getName())
194 .append(" ")
195 .append(getLastArticleNumber())
196 .append(" ")
197 .append(getFirstArticleNumber())
198 .append(" ")
199 .append((isPostAllowed() ? "y":"n"));
200 return showBuffer.toString();
201 }
202
203 /**
204 * @see org.apache.james.nntpserver.repository.NNTPGroup#getListNewsgroupsFormat()
205 */
206 public String getListNewsgroupsFormat() {
207 StringBuffer showBuffer =
208 new StringBuffer(128)
209 .append(getName())
210 .append(" ")
211 .append(getDescription());
212 return showBuffer.toString();
213 }
214
215 /**
216 * @see org.apache.james.nntpserver.repository.NNTPGroup#addArticle(InputStream)
217 */
218 public NNTPArticle addArticle(InputStream newsStream)
219 throws IOException {
220 File articleFile = null;
221 synchronized (this) {
222 int artNum;
223 if (numOfArticles < 0)
224 throw new IllegalStateException("NNTP Group is corrupt (articles < 0).");
225 else if (numOfArticles == 0) {
226 firstArticle = 1;
227 artNum = 1;
228 } else {
229 artNum = getLastArticleNumber() + 1;
230 }
231
232 articleFile = new File(root, artNum + "");
233 articleFile.createNewFile();
234 lastArticle = artNum;
235 numOfArticles++;
236 }
237 if (getLogger().isDebugEnabled()) {
238 getLogger().debug("Copying message to: "+articleFile.getAbsolutePath());
239 }
240 FileOutputStream fout = null;
241 try {
242 fout = new FileOutputStream(articleFile);
243 IOUtil.copy(newsStream,fout);
244 fout.flush();
245 } finally {
246 try {
247 if (fout != null) {
248 fout.close();
249 }
250 } catch (IOException ioe) {
251 // Ignore this exception so we don't
252 // trash any "real" exceptions
253 }
254 }
255 return new NNTPArticleImpl(this, articleFile);
256 }
257
258 // public NNTPArticle getArticleFromID(String id) {
259 // if ( id == null )
260 // return null;
261 // int idx = id.indexOf('@');
262 // if ( idx != -1 )
263 // id = id.substring(0,idx);
264 // File f = new File(root,id + ".id");
265 // if ( f.exists() == false )
266 // return null;
267 // try {
268 // FileInputStream fin = new FileInputStream(f);
269 // int count = fin.available();
270 // byte[] ba = new byte[count];
271 // fin.read(ba);
272 // fin.close();
273 // String str = new String(ba);
274 // int num = Integer.parseInt(str);
275 // return getArticle(num);
276 // } catch(IOException ioe) {
277 // throw new NNTPException("could not fectch article: "+id,ioe);
278 // }
279 // }
280
281 }