001: /*
002: * NewsGroupMessageBuffer.java
003: *
004: * Copyright (C) 2000-2003 Peter Graves
005: * $Id: NewsGroupMessageBuffer.java,v 1.12 2003/06/29 00:19:34 piso Exp $
006: *
007: * This program is free software; you can redistribute it and/or
008: * modify it under the terms of the GNU General Public License
009: * as published by the Free Software Foundation; either version 2
010: * of the License, or (at your option) any later version.
011: *
012: * This program is distributed in the hope that it will be useful,
013: * but WITHOUT ANY WARRANTY; without even the implied warranty of
014: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
015: * GNU General Public License for more details.
016: *
017: * You should have received a copy of the GNU General Public License
018: * along with this program; if not, write to the Free Software
019: * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
020: */
021:
022: package org.armedbear.j.mail;
023:
024: import java.awt.Image;
025: import java.awt.Rectangle;
026: import java.io.BufferedReader;
027: import java.io.BufferedWriter;
028: import java.io.IOException;
029: import java.io.OutputStreamWriter;
030: import java.io.StringReader;
031: import java.util.List;
032: import javax.swing.SwingUtilities;
033: import org.armedbear.j.BackgroundProcess;
034: import org.armedbear.j.Buffer;
035: import org.armedbear.j.Directories;
036: import org.armedbear.j.Editor;
037: import org.armedbear.j.EditorIterator;
038: import org.armedbear.j.FastStringBuffer;
039: import org.armedbear.j.File;
040: import org.armedbear.j.Headers;
041: import org.armedbear.j.ImageLine;
042: import org.armedbear.j.ImageLoader;
043: import org.armedbear.j.Line;
044: import org.armedbear.j.Log;
045: import org.armedbear.j.Platform;
046: import org.armedbear.j.ProgressNotifier;
047: import org.armedbear.j.Property;
048: import org.armedbear.j.Sidebar;
049: import org.armedbear.j.StatusBarProgressNotifier;
050: import org.armedbear.j.TextLine;
051: import org.armedbear.j.Utilities;
052:
053: public final class NewsGroupMessageBuffer extends MessageBuffer {
054: private NewsGroupSummary summary;
055: private boolean cancelled;
056:
057: public NewsGroupMessageBuffer(NewsGroupSummary summary,
058: NewsGroupSummaryEntry entry) {
059: super ();
060: this .mailbox = this .summary = summary;
061: setEntry(entry);
062: initializeUndo();
063: type = TYPE_NORMAL;
064: lineSeparator = "\n";
065: mode = MessageMode.getMode();
066: formatter = mode.getFormatter(this );
067: readOnly = true;
068: setLoaded(true);
069: setBusy(true);
070: new Thread(loadProcess).start();
071: }
072:
073: public NewsGroupSummary getSummary() {
074: return summary;
075: }
076:
077: public NewsGroupSummaryEntry getNewsGroupSummaryEntry() {
078: return (NewsGroupSummaryEntry) entry;
079: }
080:
081: private void setEntry(NewsGroupSummaryEntry entry) {
082: this .entry = entry;
083: reset();
084: title = entry.formatSubject();
085: Sidebar.setUpdateFlagInAllFrames(SIDEBAR_REPAINT_BUFFER_LIST);
086: }
087:
088: public void nextArticle() {
089: NewsGroupSummaryEntry nextEntry = (NewsGroupSummaryEntry) summary
090: .getNextUndeleted(entry);
091: if (nextEntry != null) {
092: empty();
093: for (EditorIterator it = new EditorIterator(); it.hasNext();) {
094: Editor ed = it.nextEditor();
095: if (ed.getBuffer() == this ) {
096: ed.setDot(null);
097: ed.setMark(null);
098: ed.setTopLine(null);
099: ed.repaintNow();
100: }
101: }
102: setBusy(true);
103: setEntry(nextEntry);
104: new Thread(loadProcess).start();
105: } else
106: Editor.currentEditor().status("Last article");
107: }
108:
109: public void previousArticle() {
110: NewsGroupSummaryEntry prevEntry = (NewsGroupSummaryEntry) summary
111: .getPreviousUndeleted(entry);
112: if (prevEntry != null) {
113: empty();
114: for (EditorIterator it = new EditorIterator(); it.hasNext();) {
115: Editor ed = it.nextEditor();
116: if (ed.getBuffer() == this ) {
117: ed.setDot(null);
118: ed.setMark(null);
119: ed.setTopLine(null);
120: ed.repaintNow();
121: }
122: }
123: setBusy(true);
124: setEntry(prevEntry);
125: new Thread(loadProcess).start();
126: } else
127: Editor.currentEditor().status("First article");
128: }
129:
130: private final BackgroundProcess loadProcess = new BackgroundProcess() {
131: private ProgressNotifier progressNotifier;
132:
133: public void run() {
134: setBackgroundProcess(this );
135: progressNotifier = new StatusBarProgressNotifier(
136: NewsGroupMessageBuffer.this );
137: cancelled = false;
138: loadMessage(progressNotifier);
139: progressNotifier.setText("");
140: progressNotifier.progressStop();
141: setBackgroundProcess(null);
142: }
143:
144: public void cancel() {
145: cancelled = true;
146: progressNotifier.cancel();
147: summary.getSession().abort();
148: setBusy(false);
149: kill();
150: }
151: };
152:
153: protected void loadMessage(ProgressNotifier progressNotifier) {
154: final String rawText = summary.getArticle(
155: ((NewsGroupSummaryEntry) entry).getArticleNumber(),
156: progressNotifier);
157: if (cancelled)
158: return;
159: message = new Message(rawText != null ? rawText : "");
160: parseMessage();
161: title = message.getHeaderValue(Headers.SUBJECT);
162: if (title == null)
163: title = "";
164: allHeaders = message.getAllHeaders();
165: defaultHeaders = getDefaultHeaders(allHeaders);
166: rawBody = message.getRawBody();
167: setText();
168: setLoaded(true);
169: formatter.parseBuffer();
170: entry.setFlags(entry.getFlags() | MailboxEntry.SEEN);
171: if (rawText == null)
172: entry.setFlags(entry.getFlags() | MailboxEntry.DELETED);
173: summary.updateEntry(entry);
174: final MailboxLine mailboxLine = summary.findLineForEntry(entry);
175: Runnable completionRunnable = new Runnable() {
176: public void run() {
177: setBusy(false);
178: if (rawText != null) {
179: for (EditorIterator it = new EditorIterator(); it
180: .hasNext();) {
181: Editor ed = it.nextEditor();
182: if (ed.getBuffer() == NewsGroupMessageBuffer.this ) {
183: ed.setDot(getFirstLine(), 0);
184: ed.moveCaretToDotCol();
185: ed.getDisplay().setTopLine(getFirstLine());
186: ed.setUpdateFlag(REPAINT);
187: ed.updateDisplay();
188: } else if (ed.getBuffer() == summary) {
189: if (ed.getDot() != null) {
190: if (mailboxLine != null) {
191: ed.updateDotLine();
192: ed.getDot().moveTo(mailboxLine, 0);
193: ed.updateDotLine();
194: ed.moveCaretToDotCol();
195: }
196: }
197: ed.clearStatusText();
198: ed.updateDisplay();
199: }
200: }
201: Sidebar.repaintBufferListInAllFrames();
202: }
203: }
204: };
205: SwingUtilities.invokeLater(completionRunnable);
206: }
207:
208: private static boolean containsBinary(String text) {
209: BufferedReader reader = new BufferedReader(new StringReader(
210: text));
211: try {
212: String s;
213: while ((s = reader.readLine()) != null) {
214: final int length = s.length();
215: if (length >= 9 && s.startsWith("begin "))
216: return true;
217: if (length > 7 && s.startsWith("=ybegin"))
218: return true;
219: if (length > 0 && s.charAt(0) == 0)
220: return true;
221: }
222: } catch (IOException e) {
223: Log.error(e);
224: }
225: return false;
226: }
227:
228: private void appendBody(String rawBody) {
229: BufferedReader reader = new BufferedReader(new StringReader(
230: rawBody));
231: try {
232: String s;
233: while ((s = reader.readLine()) != null) {
234: final int length = s.length();
235: if (length >= 9 && s.startsWith("begin ")
236: && haveUudecode()) {
237: // "begin 644 filename" or "begin 0644 filename"
238: // Skip "begin ".
239: String trim = s.substring(6).trim();
240: // Next token is permission.
241: String permission = null;
242: int index = trim.indexOf(' ');
243: if (index >= 0) {
244: permission = trim.substring(0, index);
245: trim = trim.substring(index + 1);
246: }
247: String extension = Utilities.getExtension(trim);
248: File encoded = Utilities.getTempFile(Directories
249: .getTempDirectory(), ".encoded");
250: File decoded = Utilities.getTempFile(Directories
251: .getTempDirectory(), extension);
252: FastStringBuffer sb = new FastStringBuffer(
253: "begin 644 ");
254: sb.append(decoded.getName());
255: BufferedWriter writer = new BufferedWriter(
256: new OutputStreamWriter(encoded
257: .getOutputStream(), "ISO-8859-1"));
258: writer.write(sb.toString());
259: writer.write('\n');
260: while ((s = reader.readLine()) != null) {
261: writer.write(s);
262: writer.write('\n');
263: if (s.equals("end")) {
264: writer.flush();
265: writer.close();
266: break;
267: }
268: }
269: if (decode(encoded, "uudecode"))
270: appendImageLine(decoded);
271: encoded.delete();
272: decoded.delete();
273: } else if (length > 7 && s.startsWith("=ybegin")
274: && haveYydecode()) {
275: final String lookFor = " name=";
276: int index = s.indexOf(lookFor);
277: if (index < 0) {
278: s = s.concat(lookFor);
279: index = s.length();
280: } else
281: index += lookFor.length();
282: String name = s.substring(index);
283: String extension = Utilities.getExtension(name);
284: File encoded = Utilities.getTempFile(Directories
285: .getTempDirectory(), ".encoded");
286: File decoded = Utilities.getTempFile(Directories
287: .getTempDirectory(), extension);
288: BufferedWriter writer = new BufferedWriter(
289: new OutputStreamWriter(encoded
290: .getOutputStream(), "ISO-8859-1"));
291: FastStringBuffer sb = new FastStringBuffer(s
292: .substring(0, index));
293: sb.append(decoded.getName());
294: writer.write(sb.toString());
295: writer.write('\n');
296: while ((s = reader.readLine()) != null) {
297: writer.write(s);
298: if (s.startsWith("=yend")) {
299: writer.flush();
300: writer.close();
301: break;
302: }
303: writer.write('\n');
304: }
305: if (decode(encoded, "yydecode -b"))
306: appendImageLine(decoded);
307: encoded.delete();
308: decoded.delete();
309: } else if (length > 0 && s.charAt(0) == 0) {
310: // Don't append a string composed entirely of null bytes.
311: boolean empty = true;
312: for (int i = length; i-- > 0;) {
313: if (s.charAt(i) != 0) {
314: empty = false;
315: break;
316: }
317: }
318: appendLine(empty ? "" : s);
319: } else {
320: // Normal text.
321: appendLine(s);
322: }
323: }
324: renumber();
325: invalidate();
326: } catch (IOException e) {
327: Log.error(e);
328: }
329: }
330:
331: private boolean decode(File encoded, String decodeCommand) {
332: FastStringBuffer sb = new FastStringBuffer("(\\cd \"");
333: sb.append(Directories.getTempDirectory().canonicalPath());
334: sb.append("\" && ");
335: sb.append(decodeCommand);
336: sb.append(" \"");
337: sb.append(encoded.getName());
338: sb.append("\")");
339: String[] cmdarray = { "/bin/sh", "-c", sb.toString() };
340: try {
341: Process process = Runtime.getRuntime().exec(cmdarray);
342: if (process != null) {
343: process.waitFor();
344: return true;
345: }
346: } catch (Throwable t) {
347: Log.error(t);
348: }
349: return false;
350: }
351:
352: private void appendImageLine(File decoded) {
353: ImageLoader loader = new ImageLoader(decoded);
354: Image image = loader.loadImage();
355: if (image != null) {
356: final int lineHeight = new TextLine("").getHeight();
357: final int imageHeight = image.getHeight(null);
358: final int imageWidth = image.getWidth(null);
359: int y = 0;
360: while (y < imageHeight) {
361: Rectangle r = new Rectangle(0, y, imageWidth, Math.min(
362: lineHeight, imageHeight - y));
363: appendLine(new ImageLine(image, r));
364: y += lineHeight;
365: }
366: }
367: }
368:
369: // We only look for uudecode and yydecode on Unix platforms.
370: private static int haveUudecode = Platform.isPlatformUnix() ? -1
371: : 0;
372: private static int haveYydecode = Platform.isPlatformUnix() ? -1
373: : 0;
374:
375: private static boolean haveUudecode() {
376: if (haveUudecode < 0)
377: haveUudecode = Utilities.have("uudecode -h") ? 1 : 0;
378: return haveUudecode == 1;
379: }
380:
381: private static boolean haveYydecode() {
382: if (haveYydecode < 0)
383: haveYydecode = Utilities.have("yydecode -h") ? 1 : 0;
384: return haveYydecode == 1;
385: }
386:
387: public void viewInline() {
388: Line line;
389: for (line = getFirstLine(); line != null; line = line.next()) {
390: if (line.length() == 0)
391: break; // Reached end of headers.
392: }
393: for (; line != null; line = line.next()) {
394: String s = line.getText();
395: if (s.startsWith("Inline: ")) {
396: String filename = s.substring(8);
397: File f = File.getInstance(Directories
398: .getTempDirectory(), filename);
399: if (f.isFile()) {
400: Editor editor = Editor.currentEditor();
401: Buffer buf = editor.openFile(f);
402: editor.makeNext(buf);
403: editor.activate(buf);
404: editor.maybeKillBuffer(this );
405: return;
406: }
407: }
408: }
409: }
410:
411: public void toggleHeaders() {
412: showFullHeaders = !showFullHeaders;
413: empty();
414: setText();
415: formatter.parseBuffer();
416: for (EditorIterator it = new EditorIterator(); it.hasNext();) {
417: Editor ed = it.nextEditor();
418: if (ed.getBuffer() == this ) {
419: ed.setDot(getFirstLine(), 0);
420: ed.setTopLine(ed.getDotLine());
421: ed.setMark(null);
422: ed.repaintDisplay();
423: }
424: }
425: }
426:
427: public void toggleRaw() {
428: showRawText = !showRawText;
429: empty();
430: setText();
431: formatter.parseBuffer();
432: for (EditorIterator it = new EditorIterator(); it.hasNext();) {
433: Editor ed = it.nextEditor();
434: if (ed.getBuffer() == this ) {
435: ed.setDot(getFirstLine(), 0);
436: ed.setTopLine(ed.getDotLine());
437: ed.setMark(null);
438: ed.repaintDisplay();
439: }
440: }
441: Editor.currentEditor().status(
442: "Raw mode ".concat((showRawText ? "on" : "off")));
443: }
444:
445: protected void setText() {
446: empty();
447: if (showRawText) {
448: super .setText();
449: return;
450: }
451: String contentType = message.getContentType();
452: if (contentType != null && contentType.startsWith("image/"))
453: super .setText();
454: else {
455: List parts = message.getParts();
456: if (parts != null && parts.size() > 0) {
457: super .setText();
458: } else {
459: // Not MIME multipart.
460: if (containsBinary(rawBody)) {
461: String headers;
462: if (showFullHeaders)
463: headers = allHeaders;
464: else if (Editor.preferences().getBooleanProperty(
465: Property.BEAUTIFY_HEADERS))
466: headers = getBeautifiedHeaders();
467: else
468: headers = defaultHeaders;
469: try {
470: lockWrite();
471: } catch (InterruptedException e) {
472: Log.error(e);
473: return;
474: }
475: try {
476: appendHeaderLines(headers);
477: headerLineCount = Utilities.countLines(headers);
478: appendHeaderLine("");
479: appendBody(rawBody);
480: } finally {
481: unlockWrite();
482: }
483: } else
484: super.setText();
485: }
486: }
487: }
488: }
|