+/*----- Basic file operations ---------------------------------------------*/
+
+@native protected def unlink(path: CString);
+def unlink(path: String) { unlink(path.toCString); }
+@native protected def rmdir(path: CString);
+def rmdir(path: String) { rmdir(path.toCString); }
+@native protected def mkdir(path: CString, mode: Int);
+def mkdir(path: String, mode: Int) { mkdir(path.toCString, mode); }
+@native protected def chmod(path: CString, mode: Int);
+def chmod(path: String, mode: Int) { chmod(path.toCString, mode); }
+@native protected def mkfile(path: CString, mode: Int);
+def mkfile(path: String, mode: Int) { mkfile(path.toCString, mode); }
+@native protected def rename(from: CString, to: CString);
+def rename(from: String, to: String)
+ { rename(from.toCString, to.toCString); }
+
+@native def fdint(fd: FileDescriptor): Int;
+@native def newfd(fd: Int): FileDescriptor;
+@native def isatty(fd: FileDescriptor): Boolean;
+
+/*----- File status information -------------------------------------------*/
+
+/* These are the traditional values, but the C code carefully arranges to
+ * return them regardless of what your kernel actually thinks.
+ */
+final val S_IFMT = 0xf000;
+final val S_IFIFO = 0x1000;
+final val S_IFCHR = 0x2000;
+final val S_IFDIR = 0x4000;
+final val S_IFBLK = 0x6000;
+final val S_IFREG = 0x8000;
+final val S_IFLNK = 0xa000;
+final val S_IFSOCK = 0xc000;
+
+/* Primitive read-the-file-status calls. */
+@native protected def stat(path: CString): sys.FileInfo;
+def stat(path: String): sys.FileInfo = stat(path.toCString);
+@native protected def lstat(path: CString): sys.FileInfo;
+def lstat(path: String): sys.FileInfo = lstat(path.toCString);
+
+object FileInfo extends Enumeration {
+ /* A simple enumeration of things a file might be.
+ *
+ * `HDLNK' is a hard link, used in `tar' files.
+ */
+ val FIFO, CHR, DIR, BLK, REG, LNK, SOCK, HDLNK, UNK = Value;
+ type Type = Value;
+}
+import FileInfo._;
+
+class FileInfo private[this](val devMajor: Int, val devMinor: Int,
+ val ino: Long, val mode: Int, val nlink: Int,
+ val uid: Int, val gid: Int,
+ _rdevMinor: Int, _rdevMajor: Int,
+ val size: Long,
+ val blksize: Int, val blocks: Long,
+ val atime: Date, val mtime: Date,
+ val ctime: Date) {
+ /* Information about a file. This is constructed directly from native
+ * code.
+ */
+
+ private def this(devMajor: Int, devMinor: Int, ino: Long,
+ mode: Int, nlink: Int, uid: Int, gid: Int,
+ rdevMinor: Int, rdevMajor: Int,
+ size: Long, blksize: Int, blocks: Long,
+ atime: Long, mtime: Long, ctime: Long) {
+ /* Lightly cook the values from the underlying `struct stat'. */
+
+ this(devMajor, devMinor, ino, mode, nlink, uid, gid,
+ rdevMajor, rdevMinor, size, blksize, blocks,
+ new Date(atime), new Date(mtime), new Date(ctime));
+ }
+
+ /* Return the file permissions only. */
+ def perms: Int = mode&0xfff;
+
+ /* Return the filetype, as a `FileInfo.Type'. */
+ def ftype: Type = (mode&S_IFMT) match {
+ case S_IFIFO => FIFO
+ case S_IFCHR => CHR
+ case S_IFDIR => DIR
+ case S_IFBLK => BLK
+ case S_IFREG => REG
+ case S_IFLNK => LNK
+ case S_IFSOCK => SOCK
+ case _ => UNK
+ }
+
+ private[this] def mustBeDevice() {
+ /* Insist that you only ask for `rdev' fields on actual device nodes. */
+ ftype match {
+ case CHR | BLK => ok;
+ case _ => throw new IllegalArgumentException("Object is not a device");
+ }
+ }
+
+ /* Query the device-node numbers. */
+ def rdevMajor: Int = { mustBeDevice(); _rdevMajor }
+ def rdevMinor: Int = { mustBeDevice(); _rdevMinor }
+}
+
+/*----- Listing directories -----------------------------------------------*/
+
+/* Primitive operations. */
+@native protected def opendir(path: CString): Wrapper;
+@native protected def readdir(path: CString, dir: Wrapper): CString;
+@native protected def closedir(path: CString, dir: Wrapper);
+
+protected abstract class BaseDirIterator[T](cpath: CString)
+ extends LookaheadIterator[T] with Closeable {
+ /* The underlying machinery for directory iterators.
+ *
+ * Subclasses must define `mangle' to convert raw filenames into a T.
+ * We keep track of the path C-string, because we need to keep passing that
+ * back to C for inclusion in error messages. Recording higher-level
+ * things is left for subclasses.
+ */
+
+ /* Constructors from more convenient types. */
+ def this(path: String) { this(path.toCString); }
+ def this(dir: File) { this(dir.getPath); }
+
+ /* Cleaning up after ourselves. */
+ override def close() { closedir(cpath, dir); }
+ override protected def finalize() { super.finalize(); close(); }
+
+ /* Subclass responsibility. */
+ protected def mangle(file: String): T;
+
+ /* Main machinery. */
+ private[this] val dir = opendir(cpath);
+ override protected def fetch(): Option[T] = readdir(cpath, dir) match {
+ case null => None
+ case f => f.toJString match {
+ case "." | ".." => fetch()
+ case jf => Some(mangle(jf))
+ }
+ }
+}
+
+class DirIterator(val path: String) extends BaseDirIterator[String](path) {
+ /* Iterator over the basenames of files in a directory. */
+
+ def this(dir: File) { this(dir.getPath); }
+
+ override protected def mangle(file: String): String = file;
+}
+
+class DirFilesIterator private[this](val dir: File, cpath: CString)
+ extends BaseDirIterator[File](cpath) {
+ /* Iterator over full `File' objects in a directory. */
+
+ def this(dir: File) { this(dir, dir.getPath.toCString); }
+ def this(path: String) { this(new File(path), path.toCString); }
+
+ override protected def mangle(file: String): File = new File(dir, file);
+}
+
+/*----- File locking ------------------------------------------------------*/
+
+/* Primitive operations. The low `mode' bits are for the lock file if we
+ * have to create it.
+ */
+final val LKF_EXCL = 0x1000;
+final val LKF_WAIT = 0x2000;
+@native protected def lock(path: CString, mode: Int): Wrapper;
+@native protected def unlock(lock: Wrapper);
+
+class FileLock(path: String, flags: Int) extends Closeable {
+ /* A class which represents a held lock on a file. */
+
+ /* Constructors. The default is to take an exclusive lock or fail
+ * immediately.
+ */
+ def this(file: File, flags: Int) { this(file.getPath, flags); }
+ def this(path: String) { this(path, LKF_EXCL | 0x1b6); }
+ def this(file: File) { this(file.getPath, LKF_EXCL | 0x1b6); }
+
+ /* The low-level lock object, actually a file descriptor. */
+ private[this] val lk = lock(path.toCString, flags);
+
+ /* Making sure things get cleaned up. */
+ override def close() { unlock(lk); }
+ override protected def finalize() { super.finalize(); close(); }
+}
+
+/*----- File implicits ----------------------------------------------------*/
+
+object FileImplicits {
+ implicit class FileOps(file: File) {
+ /* Augment `File' with operations which throw informative (if low-level
+ * and system-specific) exceptions rather than returning unhelpful
+ * win/lose booleans. These have names ending with `_!' because they
+ * might explode.
+ *
+ * And some other useful methods.
+ */
+
+ /* Constructing names of files in a directory. Honestly, I'm surprised
+ * there isn't a method for this already.
+ */
+ def /(sub: String): File = new File(file, sub);
+
+ /* Simple file operations. */
+ def unlink_!() { unlink(file.getPath); }
+ def rmdir_!() { rmdir(file.getPath); }
+ def mkdir_!(mode: Int) { mkdir(file.getPath, mode); }
+ def mkdir_!() { mkdir_!(0x1ff); }
+ def chmod_!(mode: Int) { chmod(file.getPath, mode); }
+ def mkfile_!(mode: Int) { mkfile(file.getPath, mode); }
+ def mkfile_!() { mkfile_!(0x1b6); }
+ def rename_!(to: File) { rename(file.getPath, to.getPath); }
+
+ /* Listing directories. */
+ def withFilesIterator[T](body: DirFilesIterator => T): T = {
+ val iter = new DirFilesIterator(file.getPath);
+ try { body(iter) } finally { iter.close(); }
+ }
+ def foreachFile(fn: File => Unit) { withFilesIterator(_.foreach(fn)) }
+ def files_! : Seq[File] = withFilesIterator { _.toSeq };
+
+ /* Low-level lFile information. */
+ def stat_! : FileInfo = stat(file.getPath);
+ def lstat_! : FileInfo = lstat(file.getPath);
+
+ /* Specific file-status queries. */
+ private[this] def statish[T](statfn: String => FileInfo,
+ ifexists: FileInfo => T,
+ ifmissing: => T): T =
+ (try { statfn(file.getPath) }
+ catch { case SystemError(ENOENT, _) => null }) match {
+ case null => ifmissing
+ case st => ifexists(st)
+ };
+ def exists_! : Boolean = statish(stat _, _ => true, false);
+ def isfifo_! : Boolean = statish(stat _, _.ftype == FIFO, false);
+ def isblk_! : Boolean = statish(stat _, _.ftype == BLK, false);
+ def isdir_! : Boolean = statish(stat _, _.ftype == DIR, false);
+ def ischr_! : Boolean = statish(stat _, _.ftype == CHR, false);
+ def isreg_! : Boolean = statish(stat _, _.ftype == REG, false);
+ def islnk_! : Boolean = statish(lstat _, _.ftype == LNK, false);
+ def issock_! : Boolean = statish(stat _, _.ftype == SOCK, false);
+
+ /* Slightly more cooked file operations. */
+ def remove_!() {
+ /* Delete a file, or directory, whatever it is. */
+ try { unlink_!(); return; }
+ catch {
+ case SystemError(ENOENT, _) => return;
+ case SystemError(EISDIR, _) =>
+ try { rmdir_!(); return; }
+ catch { case SystemError(ENOENT, _) => return; }
+ }
+ }
+
+ def rmTree() {
+ /* Delete a thing recursively. */
+ def walk(f: File) {
+ if (f.isdir_!) f.foreachFile(walk _);
+ f.remove_!();
+ }
+ walk(file);
+ }
+
+ def mkdirNew_!() {
+ /* Make a directory if there's nothing there already. */
+ try { mkdir_!(); }
+ catch { case SystemError(EEXIST, _) => ok; }
+ }
+
+ /* File locking. */
+ def lock_!(flags: Int): FileLock = new FileLock(file.getPath, flags);
+ def lock_!(): FileLock = lock_!(LKF_EXCL | 0x1b6);
+ def withLock[T](flags: Int)(body: => T): T = {
+ val lk = lock_!(flags);
+ try { body } finally { lk.close(); }
+ }
+ def withLock[T](body: => T): T = withLock(LKF_EXCL | 0x1b6) { body };
+
+ /* Opening files. Again, I'm surprised this isn't here already. */
+ def open(): FileInputStream = new FileInputStream(file);
+ def openForOutput(): FileOutputStream = new FileOutputStream(file);
+ def reader(): BufferedReader = new BufferedReader(new FileReader(file));
+ def writer(): BufferedWriter = new BufferedWriter(new FileWriter(file));
+ def withInput[T](body: FileInputStream => T): T = {
+ val in = open();
+ try { body(in) }
+ finally { in.close(); }
+ }
+ def withOutput[T](body: FileOutputStream => T): T = {
+ val out = openForOutput();
+ try { body(out) } finally { out.close(); }
+ }
+ def withReader[T](body: BufferedReader => T): T = {
+ val r = reader();
+ try { body(r) }
+ finally { r.close(); }
+ }
+ def withWriter[T](body: BufferedWriter => T): T = {
+ val w = writer();
+ try { body(w) }
+ finally { w.close(); }
+ }
+ }
+}
+import FileImplicits._;
+
+/*----- Miscellaneous file hacks ------------------------------------------*/