001: package vqwiki.file;
002:
003: import org.apache.log4j.Logger;
004: import vqwiki.*;
005: import vqwiki.db.DBDate;
006: import vqwiki.utils.TextFileFilter;
007: import vqwiki.utils.Utilities;
008:
009: import java.io.*;
010: import java.util.*;
011:
012: /*
013: Very Quick Wiki - WikiWikiWeb clone
014: Copyright (C) 2001-2002 Gareth Cronin
015:
016: This program is free software; you can redistribute it and/or modify
017: it under the terms of the latest version of the GNU Lesser General
018: Public License as published by the Free Software Foundation;
019:
020: This program is distributed in the hope that it will be useful,
021: but WITHOUT ANY WARRANTY; without even the implied warranty of
022: MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
023: GNU Lesser General Public License for more details.
024:
025: You should have received a copy of the GNU Lesser General Public License
026: along with this program (gpl.txt); if not, write to the Free Software
027: Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
028: */
029:
030: public class FileHandler implements Handler {
031:
032: public static final String VERSION_DIR = "versions";
033: public final static String EXT = ".txt";
034: private static final Logger logger = Logger
035: .getLogger(FileHandler.class);
036: // the read-only topics
037: protected Map readOnlyTopics;
038: // file used for storing read-only topics
039: private final static String READ_ONLY_FILE = "ReadOnlyTopics";
040: public static final String VIRTUAL_WIKI_LIST = "virtualwikis.lst";
041: public static final String TEMPLATES_DIR = "templates";
042: private File file;
043: private final String LOCK_EXTENSION = ".lock";
044:
045: /**
046: *
047: */
048: public FileHandler() {
049: this .readOnlyTopics = new HashMap();
050: createDefaults(Locale.ENGLISH);
051: }
052:
053: /**
054: * Set up the file system and default topics if necessary
055: */
056: public void createDefaults(Locale locale) {
057: // create wiki home if necessary
058: File dirCheck = new File(fileBase(""));
059: //logger.info( "Using filebase: " + dirCheck );
060: dirCheck.mkdir();
061: // create default virtual wiki versions directory if necessary
062: File versionDirCheck = new File(fileBase("") + VERSION_DIR);
063: versionDirCheck.mkdir();
064: // create the virtual wiki list file if necessary
065: File virtualList = new File(fileBase("") + VIRTUAL_WIKI_LIST);
066: // resources for i18n
067: ResourceBundle messages = ResourceBundle.getBundle(
068: "ApplicationResources", locale);
069: // get the virtual wiki list and set up the file system
070: try {
071: if (!virtualList.exists()) {
072: createVirtualWikiList(virtualList);
073: }
074: BufferedReader in = new BufferedReader(
075: new InputStreamReader(new FileInputStream(
076: virtualList), Environment.getInstance()
077: .getFileEncoding()));
078: boolean lastOne = false;
079: while (true) {
080: String vWiki = in.readLine();
081: if (vWiki == null) {
082: if (lastOne) {
083: break;
084: } else {
085: // default Wiki (no sub-directory)
086: vWiki = "";
087: lastOne = true;
088: }
089: }
090: logger.debug("Creating defaults for " + vWiki);
091: File dummy;
092: // create the directories for the virtual wiki
093: dummy = getPathFor(vWiki, "");
094: dummy.mkdir();
095: dummy = getPathFor(vWiki, VERSION_DIR);
096: dummy.mkdir();
097: // write out default topics
098: setupSpecialPage(vWiki, messages
099: .getString("specialpages.startingpoints"));
100: setupSpecialPage(vWiki, messages
101: .getString("specialpages.textformattingrules"));
102: setupSpecialPage(vWiki, messages
103: .getString("specialpages.leftMenu"));
104: setupSpecialPage(vWiki, messages
105: .getString("specialpages.topArea"));
106: setupSpecialPage(vWiki, messages
107: .getString("specialpages.bottomArea"));
108: setupSpecialPage(vWiki, messages
109: .getString("specialpages.stylesheet"));
110: setupSpecialPage(vWiki, messages
111: .getString("specialpages.adminonlytopics"));
112: loadReadOnlyTopics(vWiki);
113: }
114: in.close();
115: } catch (Exception ex) {
116: logger.error(ex);
117: ex.printStackTrace();
118: }
119: }
120:
121: /**
122: *
123: */
124: private void setupSpecialPage(String vWiki, String specialPage)
125: throws Exception {
126: File dummy = getPathFor(vWiki, specialPage + ".txt");
127: if (!dummy.exists()) {
128: Writer writer = new OutputStreamWriter(
129: new FileOutputStream(dummy), Environment
130: .getInstance().getFileEncoding());
131: writer.write(WikiBase.readDefaultTopic(specialPage));
132: writer.close();
133: }
134: }
135:
136: /**
137: *
138: */
139: private void createVirtualWikiList(File virtualList)
140: throws IOException {
141: PrintWriter writer = getNewPrintWriter(virtualList, true);
142: writer.println(WikiBase.DEFAULT_VWIKI);
143: writer.close();
144: }
145:
146: /**
147: *
148: */
149: public static File getPathFor(String virtualWiki, String dir,
150: String fileName) {
151: StringBuffer buffer = new StringBuffer();
152: if (virtualWiki == null
153: || virtualWiki.equals(WikiBase.DEFAULT_VWIKI)) {
154: virtualWiki = "";
155: }
156: buffer.append(fileBase(virtualWiki));
157: buffer.append(File.separator);
158: if (dir != null) {
159: buffer.append(dir);
160: buffer.append(File.separator);
161: }
162: if (fileName != null) {
163: buffer.append(Utilities.encodeSafeFileName(fileName));
164: }
165: return new File(buffer.toString());
166: }
167:
168: /**
169: *
170: */
171: public static File getPathFor(String virtualWiki, String fileName) {
172: return getPathFor(virtualWiki, null, fileName);
173: }
174:
175: /**
176: * Reads a file from disk
177: */
178: public String read(String virtualWiki, String topicName)
179: throws Exception {
180: if (topicName.indexOf(System.getProperty("file.separator")) >= 0) {
181: throw new WikiException(
182: "WikiNames may not contain special characters:"
183: + topicName);
184: }
185: StringBuffer buffer = new StringBuffer();
186: buffer.append(topicName);
187: buffer.append(EXT);
188: File file = getPathFor(virtualWiki, buffer.toString());
189: StringBuffer contents = read(file);
190: return contents.toString();
191: }
192:
193: /**
194: *
195: */
196: public StringBuffer read(File file) throws IOException {
197: StringBuffer contents = new StringBuffer();
198: if (file.exists()) {
199: Reader in = new BufferedReader(new InputStreamReader(
200: new FileInputStream(file), Environment
201: .getInstance().getFileEncoding()));
202: //CR=13=\r LF=10=\n
203: boolean cr = false;
204: while (true) {
205: int c = in.read();
206: if (c == -1)
207: break;
208: if (c == 13)
209: cr = true;
210: if (cr && c == 10) {
211: cr = false;
212: contents.append((char) 10);
213: } else {
214: if (c == 13) {
215: // do nothing
216: } else if (cr) {
217: contents.append((char) 13);
218: contents.append((char) c);
219: cr = false;
220: } else {
221: contents.append((char) c);
222: }
223: }
224: }
225: in.close();
226: } else {
227: logger
228: .debug("File does not exist, returning default contents: "
229: + file);
230: contents.append("This is a new topic");
231: }
232: return contents;
233: }
234:
235: /**
236: * Checks if lock exists
237: */
238: public synchronized boolean holdsLock(String virtualWiki,
239: String topicName, String key) throws IOException {
240: File lockFile = makeLockFile(virtualWiki, topicName);
241: if (lockFile.exists()) {
242: String lockKey = readLockFileKey(lockFile);
243: // key is guaranteed non-null, but lockKey might not be (backwards compatibility), so compare this way
244: if (key.equals(lockKey))
245: return true;
246: } else {
247: // File is not locked, so user can save content without problems.. lock it now and return outcome.
248: return lockTopic(virtualWiki, topicName, key);
249: }
250: return false;
251: }
252:
253: /**
254: * Locks a file for editing
255: */
256: public synchronized boolean lockTopic(String virtualWiki,
257: String topicName, String key) throws IOException {
258: File lockFile = makeLockFile(virtualWiki, topicName);
259: logger.debug("Locking " + topicName);
260: Date currentDate = new Date();
261: logger.debug("Edit timeout in minutes is "
262: + Environment.getInstance().getEditTimeOut());
263: long fiveMinutesAgo = currentDate.getTime() - 60000
264: * Environment.getInstance().getEditTimeOut();
265: if (lockFile.exists()) {
266: long mDate = lockFile.lastModified();
267: logger.debug("Lock exists for " + topicName + " modified "
268: + mDate);
269: if (mDate < fiveMinutesAgo) {
270: logger.debug("Lock has expired (timeout "
271: + fiveMinutesAgo + ")");
272: lockFile.delete();
273: } else {
274: String lockKey = readLockFileKey(lockFile);
275: // key is guaranteed non-null, but lockKey might not be (backwards compatibility), so compare this way
276: if (key.equals(lockKey))
277: lockFile.delete();
278: }
279: }
280: if (!lockFile.createNewFile())
281: return false;
282: Writer writer = new OutputStreamWriter(new FileOutputStream(
283: lockFile), Environment.getInstance().getFileEncoding());
284: writer.write(key);
285: writer.close();
286: return true;
287: }
288:
289: /**
290: *
291: */
292: public boolean exists(String virtualWiki, String topicName)
293: throws Exception {
294: File checkFile = getPathFor(virtualWiki, topicName + ".txt");
295: return checkFile.exists();
296: }
297:
298: /**
299: * Create a lock file of the format topicName.lock
300: */
301: private File makeLockFile(String virtualWiki, String topicName) {
302: StringBuffer buffer = new StringBuffer();
303: if (virtualWiki.equals(WikiBase.DEFAULT_VWIKI))
304: virtualWiki = "";
305: buffer.append(fileBase(virtualWiki));
306: buffer.append(File.separator);
307: buffer.append(Utilities.encodeSafeFileName(topicName));
308: buffer.append(LOCK_EXTENSION);
309: return new File(buffer.toString());
310: }
311:
312: /**
313: * Reads the key from a lockFile
314: */
315: private synchronized String readLockFileKey(File lockFile)
316: throws IOException {
317: BufferedReader reader = new BufferedReader(
318: new InputStreamReader(new FileInputStream(lockFile),
319: Environment.getInstance().getFileEncoding()));
320: String lockKey = reader.readLine();
321: reader.close();
322: return lockKey;
323: }
324:
325: /**
326: * Unlocks a locked file
327: */
328: public synchronized void unlockTopic(String virtualWiki,
329: String topicName) throws IOException {
330: File lockFile = getPathFor(virtualWiki, topicName
331: + LOCK_EXTENSION);
332: if (!lockFile.exists()) {
333: logger
334: .warn("attempt to unlock topic by deleting lock file failed (file does not exist): "
335: + lockFile);
336: }
337: lockFile.delete();
338: }
339:
340: /**
341: * Write contents to file
342: * Write to version file if versioning is on
343: */
344: public synchronized void write(String virtualWiki, String contents,
345: boolean convertTabs, String topicName) throws Exception {
346: if (topicName.indexOf(System.getProperty("file.separator")) >= 0) {
347: throw new WikiException(
348: "WikiNames may not contain special characters:"
349: + topicName);
350: }
351: File versionFile = getPathFor(virtualWiki, VERSION_DIR,
352: topicName + EXT + "."
353: + Utilities.fileFriendlyDate(new Date()));
354: File file = getPathFor(virtualWiki, topicName + EXT);
355: PrintWriter writer = getNewPrintWriter(file, true);
356: PrintWriter versionWriter = null;
357: if (Environment.getInstance().isVersioningOn()) {
358: versionWriter = getNewPrintWriter(versionFile, true);
359: }
360: if (convertTabs) {
361: contents = Utilities.convertTabs(contents);
362: }
363: if (Environment.getInstance().isVersioningOn()) {
364: logger.debug("Writing version: " + versionFile);
365: versionWriter.print(contents);
366: versionWriter.close();
367: }
368: logger.debug("Writing topic: " + file);
369: writer.print(contents);
370: writer.close();
371: }
372:
373: /**
374: * returns a printwriter using utf-8 encoding
375: *
376: */
377: private PrintWriter getNewPrintWriter(File file, boolean autoflush)
378: throws IOException {
379: return new PrintWriter(new OutputStreamWriter(
380: new FileOutputStream(file), Environment.getInstance()
381: .getFileEncoding()), autoflush);
382: }
383:
384: /**
385: * Write the read-only list out to disk
386: */
387: protected synchronized void saveReadOnlyTopics(String virtualWiki)
388: throws IOException {
389: File roFile = getPathFor(virtualWiki, READ_ONLY_FILE);
390: logger.debug("Saving read-only topics to " + roFile);
391: Writer out = new OutputStreamWriter(
392: new FileOutputStream(roFile), Environment.getInstance()
393: .getFileEncoding());
394: Iterator it = ((Collection) this .readOnlyTopics
395: .get(virtualWiki)).iterator();
396: while (it.hasNext()) {
397: out.write((String) it.next()
398: + System.getProperty("line.separator"));
399: }
400: out.close();
401: logger.debug("Saved read-only topics: " + this .readOnlyTopics);
402: }
403:
404: /**
405: * Makes check to see if the specified topic is read-only. The check is case-insensitive.
406: * @param virtualWiki the virtual wiki it appears in
407: * @param topicName the name of the topic
408: * @return
409: * @throws Exception
410: */
411: public boolean isTopicReadOnly(String virtualWiki, String topicName)
412: throws Exception {
413: logger.debug("isTopicReadonly: " + virtualWiki + "/"
414: + topicName);
415: if (readOnlyTopics == null) {
416: return false;
417: } else {
418: if (readOnlyTopics.get(virtualWiki) == null) {
419: return false;
420: }
421: Collection readOnlyTopicsForVWiki = ((Collection) readOnlyTopics
422: .get(virtualWiki));
423: for (Iterator iterator = readOnlyTopicsForVWiki.iterator(); iterator
424: .hasNext();) {
425: String readOnlyTopicName = (String) iterator.next();
426: if (topicName.equalsIgnoreCase(readOnlyTopicName)) {
427: return true;
428: }
429: }
430: return false;
431: }
432: }
433:
434: /**
435: * Return a list of all read-only topics
436: */
437: public Collection getReadOnlyTopics(String virtualWiki)
438: throws Exception {
439: logger.debug("Returning read only topics for " + virtualWiki);
440: return (Collection) this .readOnlyTopics.get(virtualWiki);
441: }
442:
443: /**
444: * Read the read-only topics from disk
445: */
446: protected synchronized void loadReadOnlyTopics(String virtualWiki) {
447: logger.debug("Loading read only topics for " + virtualWiki);
448: Collection roTopics = new ArrayList();
449: File roFile = getPathFor(virtualWiki, READ_ONLY_FILE);
450: if (!roFile.exists()) {
451: logger.debug("Empty read only topics for " + virtualWiki);
452: if (virtualWiki == null || virtualWiki.equals("")) {
453: virtualWiki = WikiBase.DEFAULT_VWIKI;
454: }
455: this .readOnlyTopics.put(virtualWiki, roTopics);
456: return;
457: }
458: logger.debug("Loading read-only topics from " + roFile);
459: BufferedReader in = null;
460: try {
461: roFile.createNewFile();
462: in = new BufferedReader(new InputStreamReader(
463: new FileInputStream(roFile), Environment
464: .getInstance().getFileEncoding()));
465: } catch (IOException e) {
466: logger.error(e);
467: }
468: while (true) {
469: String line = null;
470: try {
471: line = in.readLine();
472: } catch (IOException e) {
473: logger.error(e);
474: }
475: if (line == null)
476: break;
477: roTopics.add(line);
478: }
479: try {
480: in.close();
481: } catch (IOException e) {
482: logger.error(e);
483: }
484: if (virtualWiki.equals("")) {
485: virtualWiki = WikiBase.DEFAULT_VWIKI;
486: }
487: this .readOnlyTopics.put(virtualWiki, roTopics);
488: }
489:
490: /**
491: *
492: */
493: public static String fileBase(String virtualWiki) {
494: return Environment.getInstance().getHomeDir() + Utilities.sep()
495: + virtualWiki;
496: }
497:
498: /**
499: *
500: */
501: public void addReadOnlyTopic(String virtualWiki, String topicName)
502: throws Exception {
503: logger.debug("Adding read-only topic: " + topicName);
504: Collection roTopics = (Collection) this .readOnlyTopics
505: .get(virtualWiki);
506: roTopics.add(topicName);
507: this .saveReadOnlyTopics(virtualWiki);
508: }
509:
510: /**
511: *
512: */
513: public void removeReadOnlyTopic(String virtualWiki, String topicName)
514: throws Exception {
515: logger.debug("Removing read-only topic: " + topicName);
516: ((Collection) this .readOnlyTopics.get(virtualWiki))
517: .remove(topicName);
518: this .saveReadOnlyTopics(virtualWiki);
519: }
520:
521: /**
522: *
523: */
524: public void initialise(Locale locale) throws Exception {
525: this .createDefaults(locale);
526: }
527:
528: /**
529: *
530: */
531: public Collection getVirtualWikiList() throws Exception {
532: Collection all = new ArrayList();
533: File file = getPathFor("", VIRTUAL_WIKI_LIST);
534: BufferedReader in = new BufferedReader(new InputStreamReader(
535: new FileInputStream(file), Environment.getInstance()
536: .getFileEncoding()));
537: while (true) {
538: String line = in.readLine();
539: if (line == null)
540: break;
541: all.add(line);
542: }
543: in.close();
544: if (!all.contains(WikiBase.DEFAULT_VWIKI)) {
545: all.add(WikiBase.DEFAULT_VWIKI);
546: }
547: return all;
548: }
549:
550: /**
551: *
552: */
553: public Collection getTemplateNames(String virtualWiki)
554: throws Exception {
555: File file = getPathFor(virtualWiki, TEMPLATES_DIR);
556: file.mkdir();
557: Collection all = new ArrayList();
558: String[] filenames = file.list(new TextFileFilter());
559: if (filenames != null) {
560: for (int i = 0; i < filenames.length; i++) {
561: String filename = filenames[i];
562: all.add(filename.substring(0, filename.length() - 4));
563: }
564: }
565: return all;
566: }
567:
568: /**
569: *
570: */
571: public String getTemplate(String virtualWiki, String templateName)
572: throws Exception {
573: File dir = getPathFor(virtualWiki, TEMPLATES_DIR);
574: File file = new File(dir, templateName + EXT);
575: StringBuffer buffer = new StringBuffer();
576: BufferedInputStream in = new BufferedInputStream(
577: new FileInputStream(file));
578: int nextByte;
579: while (-1 != (nextByte = in.read())) {
580: char nextChar = (char) nextByte;
581: buffer.append(nextChar);
582: }
583: in.close();
584: return buffer.toString();
585: }
586:
587: /**
588: *
589: */
590: public void addVirtualWiki(String virtualWiki) throws Exception {
591: Collection all = new ArrayList();
592: File file = getPathFor("", VIRTUAL_WIKI_LIST);
593: BufferedReader in = new BufferedReader(new InputStreamReader(
594: new FileInputStream(file), Environment.getInstance()
595: .getFileEncoding()));
596: while (true) {
597: String line = in.readLine();
598: if (line == null)
599: break;
600: all.add(line);
601: }
602: in.close();
603: PrintWriter writer = new PrintWriter(new OutputStreamWriter(
604: new FileOutputStream(file), Environment.getInstance()
605: .getFileEncoding()));
606: for (Iterator iterator = all.iterator(); iterator.hasNext();) {
607: String s = (String) iterator.next();
608: writer.println(s);
609: }
610: writer.println(virtualWiki);
611: writer.close();
612: }
613:
614: /**
615: *
616: */
617: public Collection purgeDeletes(String virtualWiki) throws Exception {
618: Collection all = new ArrayList();
619: file = getPathFor(virtualWiki, "");
620: File[] files = file.listFiles(new TextFileFilter());
621: for (int i = 0; i < files.length; i++) {
622: BufferedReader reader = new BufferedReader(
623: new InputStreamReader(
624: new FileInputStream(files[i]), Environment
625: .getInstance().getFileEncoding()));
626: String line = reader.readLine();
627: reader.close();
628: if (line != null) {
629: if (line.trim().equals("delete")) {
630: files[i].delete();
631: String name = files[i].getName();
632: all.add(Utilities.decodeSafeFileName(name
633: .substring(0, name.length() - 4)));
634: }
635: }
636: }
637: return all;
638: }
639:
640: /**
641: *
642: */
643: public void purgeVersionsOlderThan(String virtualWiki, DBDate date)
644: throws Exception {
645: throw new UnsupportedOperationException(
646: "New version purging available for file handler yet");
647: }
648:
649: /**
650: *
651: */
652: public void saveAsTemplate(String virtualWiki, String templateName,
653: String contents) throws Exception {
654: File dir = getPathFor(virtualWiki, TEMPLATES_DIR);
655: logger.debug("saving template: " + templateName + " to " + dir);
656: dir.mkdir();
657: File file = new File(dir, templateName + EXT);
658: PrintWriter writer = new PrintWriter(new OutputStreamWriter(
659: new FileOutputStream(file), Environment.getInstance()
660: .getFileEncoding()));
661: writer.print(contents);
662: writer.close();
663: }
664:
665: /**
666: *
667: */
668: public List getLockList(String virtualWiki) throws Exception {
669: if (virtualWiki == null)
670: virtualWiki = "";
671: List all = new ArrayList();
672: File path = getPathFor(virtualWiki, "");
673: File[] files = path.listFiles(new FileExtensionFilter(
674: LOCK_EXTENSION));
675: for (int i = 0; i < files.length; i++) {
676: File file = files[i];
677: String fileName = file.getName();
678: logger.debug("filename: " + fileName);
679: String topicName = fileName.substring(0, fileName
680: .indexOf("."));
681: DBDate lockedAt = new DBDate(new Date(file.lastModified()));
682: all.add(new TopicLock(virtualWiki, topicName, lockedAt,
683: readLockFileKey(file)));
684: }
685: return all;
686: }
687: }
|