#ifndef CONFIG_H
#define CONFIG_H

/*
 * @(#)Config.h
 *
 * This file is part of webCDwriter - Network CD Writing.
 *
 * Copyright (C) 1999-2004 Jörg P. M. Haeger
 *
 * webCDwriter is free software. See CDWserver.cpp for details.
 *
 * Jörg Haeger, 25.05.1999
 */

#include <stdio.h>
#include <string.h>
#include "Boolean.h"
#include "Error.h"
#include "File.h"
#include "Integer.h"
#include "Process.h"
#include "String.h"
#include "Support.h"
#include "Types.h"
#include "Log.h"

#include "config.h"

const int maxNumOfWriters = 20;
const int PATH_COPY = 1, PATH_DUMMY = 2, PATH_ROOT = 4;

const char
	ATAPI[] = "ATAPI",
	BINDIR[] = "BINDIR",
	DEFAULTSPEED[] = "defaultSpeed",
	DEFAULTSPEEDAUDIO[] = "defaultSpeedAudio",
	DEFAULTSPEEDCDRW[] = "defaultSpeedCD-RW",
	DVDSUPPORT[] = "DVDSupport",
	PASSWORD[] = "password",
	SIMULATION[] = "simulation",
	TOOLSDIR[] = "TOOLSDIR";

/**
 * This class delivers the values of the configuration file.
 *
 * @version 20041012
 * @author Jörg P. M. Haeger
 */
class Config {
	public: String
		cdrdaoInfo,
		cdrecordInfo,
		growisofsInfo,
		mkisofsInfo,
		mpg123Info,
		ogg123Info,
		soxInfo;

	private: pthread_mutex_t mutex;
	String configDir, fileName;
	String firstError, firstErrorHelp;
	int firstErrorPriority;
	boolean hasReader;
	String mimeTypes;
	int numOfWriters;
	String text;
	boolean HTML;

	// values
	boolean accounts;
	String accountsFile;
	String debugCache;
	const char *eMailOnError;
	int gid;
	const char *group;
	boolean imageOnTheFly;
	String infoFile;
	boolean laserOn;
	int logLevelCache;
	int maxCopies;
	int maxMBytesInSpoolDirCache;
	int maxMBytesPerSessionCache;
	int maxSpeed;
	boolean mkisofsWithUTF8;
	const char *organization;
	boolean removeSessions;
	int reservedMBytesCache;
	unsigned short int statusPortNo;
	boolean supportFormCache;
	int uid;
	const char *user;
	bool userSave;
	int waitForCD;
	String waitForCDMsgFile;

	class Device {
		public: String dev, id, rawDevice, imageFile, info, supports;
		public: int CD, CDR, CDRW,
			DVD, DVDMinusR, DVDMinusRW, DVDPlusR, DVDPlusRW;
		Device(): dev(""), rawDevice(""), info("") {
			CD = CDR = CDRW = false;
			DVD = DVDMinusR = DVDMinusRW = DVDPlusR = DVDPlusRW = false;
		}
	};
	Device writers[maxNumOfWriters];

	class Item {
		public: String key;
		Object &defaultValue, &editor, &config, &active;
		String error;
		int lineInConfig;

		public: Item(const char *key, boolean value):
				key(key),
				defaultValue(*new Boolean(value)),
				editor(*new Boolean(value)),
				config(*new Boolean(value)),
				active(*new Boolean(value)),
				error("") {
			lineInConfig = -1;
		}

		public: Item(const char *key, int value):
				key(key),
				defaultValue(*new Integer(value)),
				editor(*new Integer(value)),
				config(*new Integer(value)),
				active(*new Integer(value)),
				error("") {
			lineInConfig = -1;
		}

		public: Item(const char *key, const char *value,
				const char *example = ""):
				key(key),
				defaultValue(*new String(value)),
				editor(*new String(value)),
				config(*new String(value)),
				active(*new String(value)),
				error("") {
			lineInConfig = -1;
		}

		public: void activateConfig() {
			if (active.instanceof("Boolean"))
				(Boolean &)active = (Boolean &)config;
			else
			if (active.instanceof("Integer"))
				(Integer &)active = (Integer &)config;
			else
				(String &)active = (String &)config;
		}

		public: bool isDefault() {
			if (active.instanceof("Boolean"))
				return ((Boolean &)config).booleanValue()
					== ((Boolean &)defaultValue).booleanValue();
			else
			if (active.instanceof("Integer"))
				return ((Integer &)config).intValue()
					== ((Integer &)defaultValue).intValue();
			else
				return ((String &)config).equals((String &)defaultValue);
		}

		public: void resetEditor() {
			if (editor.instanceof("Boolean"))
				(Boolean &)editor = (Boolean &)config;
			else
			if (editor.instanceof("Integer"))
				(Integer &)editor = (Integer &)config;
			else
				(String &)editor = (String &)config;
		}

		private: void setConfig(boolean value) {
			(Boolean &)config = new Boolean(value);
		}

		private: void setConfig(int value) {
			(Integer &)config = new Integer(value);
		}

		private: void setConfig2(const char *value) {
			(String &)config = new String(value);
		}

		public: void setConfig(const char *value) {
			if (active.instanceof("Boolean"))
				setConfig(Boolean::getBoolean(value));
			else
			if (active.instanceof("Integer"))
				setConfig(Integer::getInt(value));
			else
				setConfig2(value);
		}

		private: void setEditor(boolean value) {
			(Boolean &)editor = new Boolean(value);
		}

		private: void setEditor(int value) {
			(Integer &)editor = new Integer(value);
		}

		private: void setEditor2(String &value) {
			(String &)editor = value;
		}

		public: void setEditor(String &value) {
			if (active.instanceof("Boolean"))
				setEditor(Boolean::getBoolean(value));
			else
			if (active.instanceof("Integer"))
				setEditor(Integer::getInt(value.getBytes()));
			else
				setEditor2(value);
		}

		public: void updateConfig() {
			if (key.equals(PASSWORD))
				return;
			if (editor.instanceof("Boolean"))
				(Boolean &)config = (Boolean &)editor;
			else
			if (editor.instanceof("Integer"))
				(Integer &)config = (Integer &)editor;
			else
				(String &)config = (String &)editor;
		}
	};

	Item *items[200];
	int numOfItems;

	public: Config();

	public: void activateConfig() {
		for (int i = 0; i < numOfItems; i++)
			items[i]->activateConfig();
		debugCache = getActiveValue("debug").getBytes();
		logLevelCache = getActiveInt("logLevel");
		maxMBytesInSpoolDirCache = getActiveInt("maxMBytesInSpoolDir");
		maxMBytesPerSessionCache = getActiveInt("maxMBytesPerSession");
		reservedMBytesCache = getActiveInt("reservedMBytes");
		supportFormCache = getActiveBoolean("supportForm");
	}

	private: int add(Item *item) {
		const char *key = item->key.getBytes();
		int index = search(key);
		if (index >= 0)
			throw new Exception(S.e + key + " already exists");

		for (index = 0; index < numOfItems; index++)
			if (items[index]->key.compareToIgnoreCase(key) > 0)
				break;

		for (int i = numOfItems; i > index; i--)
			items[i] = items[i - 1];

		numOfItems++;
		items[index] = item;
		items[index]->key = key;
//		items[index]->isDefault = true;
		return index;
	}

	private: void add(const char *key, boolean value) {
		add(new Item(key, value));
	}

	private: void add(const char *key, const char *value) {
		add(new Item(key, value));
	}

	private: void add(const char *key, String *value) {
		add(new Item(key, value->getBytes()));
		delete value;
	}

	private: void add(const char *key, int value) {
		add(new Item(key, value));
	}

	private: void cdrecordInq(int writerNo);
	private: String *cdrecordInq(const char *dev);

	private: void cdrecordHelp() {
		Process p(getCdrecordPath());
		p.addArg("-help");
		p.start();

		while (1) {
			const char *lineStr = p.readLine();
			if (lineStr == NULL)
				break;

			if (strstr(lineStr, "gracetime=#") != NULL)
				setConfig("gracetime", "2");
		}
	}

	private: void cdrecordScanbus();
	private: void cdrecordScanbus2(Process &p, const char *driver = "");
	private: int cdrecordScanbus3(
		String &dev, String &rawDevice, String &line);
	private: void cdrecordVersion();

	private: void checkCopy(const char *bin) {
		String copy = bin;
		copy = copy + "InternalPath";
		copy = getActiveValue(copy.getBytes());

		FILE *in1 = fopen(copy.getBytes(), "r");
		if (in1 == NULL)
			throw new Exception(S.e
//				+ "Cannot read the copy of " + bin + " (" + copy + ")",
				+ "Cannot read the required tool " + copy,
				S.e + "/config/error/" + bin + ".html");

/*		String orig = bin;
		orig = orig + "Path";
		orig = getActiveValue(orig.getBytes());
		if (orig.length() == 0) {
			fclose(in1);
			return;
		}

		FILE *in2 = fopen(orig.getBytes(), "r");
		if (in2 == NULL) {
			fclose(in1);
			String url = "/config/error/";
			url = url + bin + ".html";
			setWarning(80, S.e
				+ "Cannot read " + bin + " (" + orig + ")",
				url.getBytes());
			return;
		}

		boolean differ = false;
		while (true) {
			int a = fgetc(in1);
			if (a == EOF) {
				differ = fgetc(in2) != EOF;
				break;
			}

			int b = fgetc(in2);
			differ = a != b;
			if (differ)
				break;
		}

		if (differ) {
			String url = "/config/error/";
			url = url + bin + ".html";
			setWarning(80, S.e
				+ "The copy of " + bin + " is not up to date",
				url.getBytes());
		}

		fclose(in2);
*/		fclose(in1);
	}

	private: void checkExecAsRoot(const char *fileName);
	private: void checkFile(const char *name, String &path);

	private: void checkForResMgrPatch(const char *bin) {
		String copy = getActiveBinPath(bin);
		log.put(3, S.e + "Config::checkForResMgrPatch " + copy);

		FILE *in = fopen(copy.getBytes(), "r");
		if (in == NULL)
			throw new Exception(S.e
				+ "Cannot read " + bin + " (" + copy + ")",
				S.e + "/config/error/" + bin + ".html");

		StringBuffer strBuf = new StringBuffer(256 * 1024, 128 * 1024);
		while (true) {
			int a = fgetc(in);
			if (a == EOF)
				break;
			strBuf.append((char)a);
		}

		fclose(in);
		if (strBuf.indexOf("resmgr") >= 0)
			throw new Exception(S.e + bin + " with resmgr patch",
				S.e + "/config/error/resmgrPatch.html");
	}

	private: void checkRawDevices() {
		if (getActiveBoolean(SIMULATION))
			return;

		for (int i = 0; i < numOfWriters; i++) {
			if (!File(writers[i].rawDevice).exists())
				throw (new Exception(S.e
					+ "The raw device "
					+ writers[i].rawDevice + " does not exist!"));

			Process p(getActiveValue("rootGatePath"));
			p.addArg("-prepareDevice");
			p.addArg(S.e + "dev=" + writers[i].rawDevice);
			p.addArg(S.e + "uid=" + uid);
			p.run();

			if (access(writers[i].rawDevice.getBytes(), R_OK | W_OK) == -1) {
				throw new Exception(S.e
					+ "Cannot read and/or write the raw device "
					+ writers[i].rawDevice + "!",
					S.e + "Try\n"
					+ createHTMLTable(S.e
						+ "$ chown " + user + " " + writers[i].rawDevice
						+ "\n$ chmod u+rw" + " " + writers[i].rawDevice));
			}
		}
	}

	private: String *createCheckbox(Device &device, const char *medium);
	private: String *createSpeedbox(Device &device, const char *medium);

	protected: String *createConfigLine(int index) {
		String value = items[index]->config.toString();
		if (value.equals("false"))
			value = "off";
		else
		if (value.equals("true"))
			value = "on";

		String str = "";
		str = str + items[index]->key + "=" + value;
		return new String(str);
	}

	public: static String *createHTMLTable(String &str) {
		String *tab = new String(
			"<table bgcolor=\"#FFFFF0\" cellpadding=\"10\" width=\"100%\">"
			"<tr><td><pre>");
		tab = &(*tab + str + "</pre></td></tr></table>");
		String::free(&str);
		return tab;
	}

	public: bool deviceSupports(int no, String &medium, int speed) {
		String m = medium;
		if (m.length() == 0)
			m = "CD-R";
		if (speed == 0)
			speed = 1;
		return (m.equals("CD") && writers[no].CD >= speed)
			|| (m.equals("CD-R") && writers[no].CDR >= speed)
			|| (m.equals("CD-RW") && writers[no].CDRW >= speed)
			|| (m.equals("DVD") && writers[no].DVD >= speed)
			|| (m.equals("DVD-R") && writers[no].DVDMinusR >= speed)
			|| (m.equals("DVD-RW") && writers[no].DVDMinusRW >= speed)
			|| (m.equals("DVD+R") && writers[no].DVDPlusR >= speed)
			|| (m.equals("DVD+RW") && writers[no].DVDPlusRW >= speed);
	}

	private: void evaluate(
			int lineNo, const char *str1, const char *str2);

	public: boolean getAccounts() {
		return accounts;
	}

	const char *getAccountsFile() {
		return accountsFile.getBytes();
	}

	public: const char *getActiveBinPath(const char *bin) {
		String str = bin;
		if (str.equals("mkisofs") || str.equals("readcd"))
			str = str + "Internal";
		else
		if (getActiveBoolean(SIMULATION)) {
			String str2 = str;
			str2 = str2 + "SimulationPath";
			if (search(str2.getBytes()) >= 0)
				str = str + "Simulation";
		}
		else
		if (str.equals("cdrdao") || str.equals("cdrecord"))
			str = str + "Internal";
		str = str + "Path";
		return getActiveValue(str.getBytes()).getBytes();
	}

	boolean getActiveBoolean(const char *key) {
		int index = indexOf(key);
		Object &object = items[index]->active;
		if (!object.instanceof("Boolean"))
			throw new Exception("invalid item in getActiveBoolean");
		return ((Boolean &)object).booleanValue();
	}

	int getActiveInt(const char *key) {
		int index = indexOf(key);
		Object &object = items[index]->active;
		if (!object.instanceof("Integer"))
			throw new Exception("invalid item in getActiveInt");
		return ((Integer &)object).intValue();
	}

	String &getActiveValue(const char *key) {
		int index = indexOf(key);
		Object &object = items[index]->active;
		if (!object.instanceof("String"))
			throw new Exception("invalid item in getActiveValue");
		return (String &)object;
	}

	boolean getAnonymousUse() {
		return getActiveBoolean("anonymousUse");
	}

	boolean getBurnFree() {
		return getActiveBoolean("useBurnFree");
	}

	boolean getCdrdao() {
		return getActiveBoolean("cdrdao");
	}

	const char *getCdrdaoArgs() {
		return getActiveValue("cdrdaoArgs").getBytes();
	}

	const char *getCdrdaoPath() {
		return getActiveBinPath("cdrdao");
	}

	const char *getCdrecordArgs() {
		return getActiveValue("cdrecordArgs").getBytes();
	}

	const char *getCdrecordPath() {
		return getActiveBinPath("cdrecord");
	}

	const char *getConfigDir() {
		return configDir.getBytes();
	}

	boolean getCopySupport() {
		return getActiveBoolean("copySupport") && hasReader;
	}

	const String &getDebug() {
		return debugCache;
	}

	const char *getDefaultFilesDir() {
		return getActiveValue("defaultFilesDir").getBytes();
	}

	public: boolean getEditorBoolean(String &key) {
		int index = indexOf(key.getBytes());
		Object &object = items[index]->editor;
		if (!object.instanceof("Boolean"))
			throw new Exception("invalid item in getEditorBoolean");
		if (key.equalsIgnoreCase("useUTF8")) {
			log.put(5, S.e + "mkisofsWithUTF8=" + mkisofsWithUTF8);
			return mkisofsWithUTF8;
		}
		return ((Boolean &)object).booleanValue();
	}

	public: String *getEditorValue(String &key) {
		int index = indexOf(key.getBytes());
		return items[index]->editor.toString();
	}

	String *getError(const char *key) {
		int index = indexOf(key);
		return new String(items[index]->error);
	}

	int getDefaultSpeed() {
		return getActiveInt(DEFAULTSPEED);
	}

	String &getDefaultValue(const char *key) {
		int index = indexOf(key);
		Object &object = items[index]->defaultValue;
		if (!object.instanceof("String"))
			throw new Exception("invalid item in getDefaultValue");
		return (String &)object;
	}

	boolean getEjectOnDone() {
		return getActiveBoolean("ejectOnDone");
	}

	boolean getEjectOnReservation() {
		return getActiveBoolean("ejectOnReservation");
	}

	const char *getEMailOnError() {
		return eMailOnError;
	}

	boolean getExportArchive() {
		return getActiveBoolean("exportArchive");
	}

	String &getFirstError() {
		return firstError;
	}

	String &getFirstErrorHelp() {
		return firstErrorHelp;
	}

	String *getFirstErrorURL() {
		if (firstErrorHelp.startsWith("/")
				|| firstErrorHelp.startsWith("http://"))
			return new String(firstErrorHelp);
		return new String("/config/error/help.html");
	}

	int getFirstErrorPriority() {
		return firstErrorPriority;
	}

	int getGid() {
		return gid;
	}

	const char *getGroup() {
		return group;
	}

	const char *getHttpDir() {
		return getActiveValue("HttpDir").getBytes();
	}

	const char *getImageFile(int writerNo) {
		return writers[writerNo].imageFile.getBytes();
	}

	boolean getImageOnTheFly() {
		return imageOnTheFly;
	}

	const char *getInfoFile() {
		return infoFile.getBytes();
	}

	int getLaserOn() {
		return laserOn;
	}

	const char *getLogDir() {
		return getActiveValue("logDir").getBytes();
	}

	String &getLogoURL() {
		return getActiveValue("logoURL");
	}

	int getLogLevel() {
		return logLevelCache;
	}

	int getMaxCopies() {
		return maxCopies;
	}

	int getMaxMBytesInSpoolDir() {
		return maxMBytesInSpoolDirCache;
	}

	int getMaxMBytesPerSession() {
		return maxMBytesPerSessionCache;
	}

	int getMaxOpenConnections() {
		return getActiveInt("maxOpenConnections");
	}

	int getMaxOpenSessions() {
		return getActiveInt("maxOpenSessions");
	}

	int getMaxSpeed() {
		return maxSpeed;
	}

	public: int getMaxSpeed(int no, const char *medium) {
		String str = writers[no].id;
		str = str + "." + medium;
		try {
			return getActiveInt(str.getBytes());
		}
		catch (Exception *e) {
			delete e;
			add(str.getBytes(), 0);
			return 0;
		}
	}

	public: String *getMimeType(File &file);

	public: const char *getMkisofsArgs() {
		return getActiveValue("mkisofsArgs").getBytes();
	}

	const char *getMkisofsPath() {
		return getActiveBinPath("mkisofs");
	}

	boolean getMkisofsWithUTF8() {
		return mkisofsWithUTF8;
	}

	private: String *getNameOfCopy(File &binary) {
		File copy = new File(binDir, binary.getName());
		return new String(copy.getPath());
	}

	public: int getNumOfWriters() {
		return numOfWriters;
	}

	const char *getOrganization() {
		return organization;
	}

	unsigned short int getPortNo() {
		return getActiveInt("portNo");
	}

	const char *getProjectsDir() {
		return getActiveValue("projectsDir").getBytes();
	}

	private: String *getRawDevice(
		int bus, int ID, int LUN, int no, const char *driver);

	public: const char *getReadcdPath() {
		return getActiveBinPath("readcd");
	}

	public: boolean getRemoveSessions() {
		return removeSessions;
	}

	int getReservedMBytes() {
		return reservedMBytesCache;
	}

	const char *getSetSchedulerPath() {
		return getActiveValue("setSchedulerPath").getBytes();
	}

	boolean getShowUsers() {
		return getActiveBoolean("showUsers");
	}

	const char *getSpoolDir() {
		return getActiveValue("spoolDir").getBytes();
	}

	unsigned short int getStatusPortNo() {
		return statusPortNo;
	}

	public: boolean getSupportForm() {
		return supportFormCache;
	}

	public: String *getSupportedMedia() {
		String media = "";
		for (int i = 0; i < numOfWriters; i++) {
			if (writers[i].DVDMinusR)
				media = media + "DVD-R ";
			if (writers[i].DVDMinusRW)
				media = media + "DVD-RW ";
			if (writers[i].DVDPlusR)
				media = media + "DVD+R ";
			if (writers[i].DVDPlusRW)
				media = media + "DVD+RW ";
		}
		log.put(5, S.e + "getSupportedMedia(): <" + media + ">");
		int len = media.length();
		if (len > 0)
			return media.substring(0, len - 1);
		return new String("");
	}

	int getUid() {
		return uid;
	}

	const char *getUser() {
		return user;
	}

	int getVerify() {
		return getActiveInt("verify");
	}

	const char *getVerifyPath() {
		return getActiveBinPath("verify");
	}

	int getWaitForCD() {
		return waitForCD;
	}

	const char *getWaitForCDMsgFile() {
		return waitForCDMsgFile.getBytes();
	}

	const char *getWriter(int no) {
		return writers[no].dev.getBytes();
	}

	String &getWriterInfo(int writerNo) {
		return writers[writerNo].info;
	}

	const char *getWriterRawDevice(int writerNo) {
		return writers[writerNo].rawDevice.getBytes();
	}

	String &getWriterSupports(int writerNo) {
		return writers[writerNo].supports;
	}

	public: String *getWritersForm();

	public: int hasDVDplusRWSupport(int writerNo) {
		return writers[writerNo].DVDMinusR || writers[writerNo].DVDMinusRW
			|| writers[writerNo].DVDPlusR || writers[writerNo].DVDPlusRW;
	}

	private: int indexOf(const char *key) {
		int index = search(key);
		if (index < 0)
			throw new Exception(S.e + key + " does not exist");
		return index;
	}

	public: int isDefault(const char *key) {
		int index = search(key);
		if (index < 0)
			throw new Exception(S.e + key + " does not exist");
		return items[index]->isDefault();
	}

	public: void load(String &aFileName);
	private: void load2(String &aFileName);

	public: static void parseLine(
			const char *lineStr, char *str1, char *str2);
	private: String *path(const char *env, const char *configure);
	private: String *path(
			const char *env, const char *configure, String command);

	private: void preparePath(const char *command, int mode = 0) {
		if (!getActiveBoolean(SIMULATION))
			if ((mode & PATH_COPY) > 0)
				checkCopy(command);

		String str = getActiveBinPath(command);
		File f = new File(str);
		if (!f.exists())
			throw new Exception(S.e
				+ "The tool " + command
				+ " (" + str + ") does not exist",
				S.e + "/config/tools.html#" + command);
/*		if ((mode & PATH_DUMMY) == 0) {
			String path = command;
			if ((mode & PATH_COPY) > 0)
				path = path + "Internal";
			path = path + "Path";
			str = getActiveValue(path.getBytes());
		}
*/		if (!getActiveBoolean(SIMULATION))
			if ((mode & (PATH_COPY | PATH_ROOT)) > 0)
				checkExecAsRoot(str.getBytes());

		log.put(2, S.e + command + " = " + str);
	}

	private: void readConfigFile();
	private: void readConfigFile(String &name);
	private: void readConfigFile(String &name, FILE *inStream);
	private: String *readPassword();
	public: void reload();

	public: void resetEditor() {
		for (int i = 0; i < numOfItems; i++) {
			items[i]->resetEditor();
			items[i]->error = "";
		}
	}

	private: void resetItems() {
		for (int i = 0; i < numOfItems; i++) {
			items[i]->config = items[i]->defaultValue;
			items[i]->active = items[i]->config;
			items[i]->lineInConfig = -1;
		}
		resetEditor();
	}

	public: void resetWarning() {
		if (firstErrorPriority < 100) {
			firstError = "";
			firstErrorHelp = "";
			firstErrorPriority = 0;
		}
	}

	public: bool runsOnLinux26() {
		bool linux26 = false;
		FILE *in = fopen("/proc/version", "r");
		if (in == NULL)
			return false;

		char str[1024];
		String line = fgets(str, sizeof str - 1, in);
		fclose(in);
		return line.startsWith("Linux version 2.6.");
	}

	private: int search(const char *key) {
		return search(key, 0, numOfItems - 1);
	}

	private: int search(const char *key, int a, int b) {
		if (a > b)
			return -1;

		int m = (a + b) / 2;
		int diff = items[m]->key.compareToIgnoreCase(key);
		if (diff > 0)
			return search(key, a, m - 1);
		else
			if (diff < 0)
				return search(key, m + 1, b);
			else
				return m;
	}

	private: void setConfig(const char *key, const char *value) {
		int index = indexOf(key);
		items[index]->setConfig(value);
	}

	private: void setDevice(String &device, const char *type, int speed) {
		String str = device;
		str = str + "." + type;
		if (search(str.getBytes()) < 0)
			add(str.getBytes(), speed);
	}

	public: void setEditor(const char *key, String &value) {
		int index = indexOf(key);
		items[index]->setEditor(value);
	}

	private: void setError(const char *key, const char *error) {
		int index = indexOf(key);
		items[index]->error = error;
	}

	public: void setPermissions();

	public: void setWarning(
			int priority, String &warning, const char *help) {
		pthread_mutex_lock(&mutex);
		try {
			if (priority > firstErrorPriority) {
				firstError = warning;
				firstErrorHelp = help;
				firstErrorPriority = priority;
				String::free(&warning);
			}
		}
		catch (Exception *e) {
			pthread_mutex_unlock(&mutex);
			throw e;
		}
		pthread_mutex_unlock(&mutex);
	}

	private: void testGrowisofs();
	private: void testMkisofs();
	private: void testMpg123();
	private: void testOgg123();
	private: void testSox();
	static int toGid(const char *groupName);
	static String *toGroup(int gid);
	public: String &toHTML();
	private: static const char *toString(boolean b);
	static int toUid(const char *userName);
	static String *toUser(int uid);

	private: void updateConfig() {
		for (int i = 0; i < numOfItems; i++)
			items[i]->updateConfig();
	}

	public: void updateConfigFile();
	public: void updateEditor(String &str);
	private: void write();
	void write(const char *str);
	void write(const char *str1, const char *str2);
	void write(const char *str, int number);
	void writeConfigFile();
	void writeInfoFile();
	private: void writePassword(String &password);
	void writeWaitForCDMsgFile();
};

extern Config config;

#endif
