Added PHYSFS_mountHandle(). Now you can do archives-in-archives!
authorRyan C. Gordon <icculus@icculus.org>
Mon, 30 Aug 2010 03:02:32 -0400
changeset 1123 6fdff9f9758d
parent 1122 a710d2a325a6
child 1124 6ef3d04af1c4
Added PHYSFS_mountHandle(). Now you can do archives-in-archives!
src/physfs.c
src/physfs.h
test/test_physfs.c
--- a/src/physfs.c	Sun Aug 29 21:36:38 2010 -0400
+++ b/src/physfs.c	Mon Aug 30 03:02:32 2010 -0400
@@ -451,6 +451,127 @@
 } /* __PHYSFS_createMemoryIo */
 
 
+/* PHYSFS_Io implementation for i/o to a PHYSFS_File... */
+
+static PHYSFS_sint64 handleIo_read(PHYSFS_Io *io, void *buf, PHYSFS_uint64 len)
+{
+    return PHYSFS_readBytes((PHYSFS_File *) io->opaque, buf, len);
+} /* handleIo_read */
+
+static PHYSFS_sint64 handleIo_write(PHYSFS_Io *io, const void *buffer,
+                                    PHYSFS_uint64 len)
+{
+    return PHYSFS_writeBytes((PHYSFS_File *) io->opaque, buffer, len);
+} /* handleIo_write */
+
+static int handleIo_seek(PHYSFS_Io *io, PHYSFS_uint64 offset)
+{
+    return PHYSFS_seek((PHYSFS_File *) io->opaque, offset);
+} /* handleIo_seek */
+
+static PHYSFS_sint64 handleIo_tell(PHYSFS_Io *io)
+{
+    return PHYSFS_tell((PHYSFS_File *) io->opaque);
+} /* handleIo_tell */
+
+static PHYSFS_sint64 handleIo_length(PHYSFS_Io *io)
+{
+    return PHYSFS_fileLength((PHYSFS_File *) io->opaque);
+} /* handleIo_length */
+
+static PHYSFS_Io *handleIo_duplicate(PHYSFS_Io *io)
+{
+    /*
+     * There's no duplicate at the PHYSFS_File level, so we break the
+     *  abstraction. We're allowed to: we're physfs.c!
+     */
+    FileHandle *origfh = (FileHandle *) io->opaque;
+    FileHandle *newfh = (FileHandle *) allocator.Malloc(sizeof (FileHandle));
+    PHYSFS_Io *retval = NULL;
+
+    GOTO_IF_MACRO(newfh == NULL, ERR_OUT_OF_MEMORY, handleIo_dupe_failed);
+    memset(newfh, '\0', sizeof (*newfh));
+
+    retval = (PHYSFS_Io *) allocator.Malloc(sizeof (PHYSFS_Io));
+    GOTO_IF_MACRO(retval == NULL, ERR_OUT_OF_MEMORY, handleIo_dupe_failed);
+
+#if 0  /* we don't buffer the duplicate, at least not at the moment. */
+    if (origfh->buffer != NULL)
+    {
+        newfh->buffer = (PHYSFS_uint8 *) allocator.Malloc(origfh->bufsize);
+        GOTO_IF_MACRO(!newfh->buffer, ERR_OUT_OF_MEMORY, handleIo_dupe_failed);
+        newfh->bufsize = origfh->bufsize;
+    } /* if */
+#endif
+
+    newfh->io = origfh->io->duplicate(origfh->io);
+    GOTO_IF_MACRO(newfh->io == NULL, NULL, handleIo_dupe_failed);
+
+    newfh->forReading = origfh->forReading;
+    newfh->dirHandle = origfh->dirHandle;
+
+    __PHYSFS_platformGrabMutex(stateLock);
+    if (newfh->forReading)
+    {
+        newfh->next = openReadList;
+        openReadList = newfh;
+    } /* if */
+    else
+    {
+        newfh->next = openWriteList;
+        openWriteList = newfh;
+    } /* else */
+    __PHYSFS_platformReleaseMutex(stateLock);
+
+    memcpy(retval, io, sizeof (PHYSFS_Io));
+    retval->opaque = newfh;
+    return retval;
+    
+handleIo_dupe_failed:
+    if (newfh)
+    {
+        if (newfh->io != NULL) newfh->io->destroy(newfh->io);
+        if (newfh->buffer != NULL) allocator.Free(newfh->buffer);
+        allocator.Free(newfh);
+    } /* if */
+
+    return NULL;
+} /* handleIo_duplicate */
+
+static int handleIo_flush(PHYSFS_Io *io)
+{
+    return PHYSFS_flush((PHYSFS_File *) io->opaque);
+} /* handleIo_flush */
+
+static void handleIo_destroy(PHYSFS_Io *io)
+{
+    if (io->opaque != NULL)
+        PHYSFS_close((PHYSFS_File *) io->opaque);
+    allocator.Free(io);
+} /* handleIo_destroy */
+
+static const PHYSFS_Io __PHYSFS_handleIoInterface =
+{
+    handleIo_read,
+    handleIo_write,
+    handleIo_seek,
+    handleIo_tell,
+    handleIo_length,
+    handleIo_duplicate,
+    handleIo_flush,
+    handleIo_destroy,
+    NULL
+};
+
+static PHYSFS_Io *__PHYSFS_createHandleIo(PHYSFS_File *f)
+{
+    PHYSFS_Io *io = (PHYSFS_Io *) allocator.Malloc(sizeof (PHYSFS_Io));
+    BAIL_IF_MACRO(io == NULL, ERR_OUT_OF_MEMORY, NULL);
+    memcpy(io, &__PHYSFS_handleIoInterface, sizeof (*io));
+    io->opaque = f;
+    return io;
+} /* __PHYSFS_createHandleIo */
+
 
 /* functions ... */
 
@@ -1356,6 +1477,28 @@
 } /* PHYSFS_mountMemory */
 
 
+int PHYSFS_mountHandle(PHYSFS_File *file, const char *fname,
+                       const char *mountPoint, int appendToPath)
+{
+    int retval = 0;
+    PHYSFS_Io *io = NULL;
+
+    BAIL_IF_MACRO(file == NULL, ERR_INVALID_ARGUMENT, 0);
+
+    io = __PHYSFS_createHandleIo(file);
+    BAIL_IF_MACRO(io == NULL, NULL, 0);
+    retval = doMount(io, fname, mountPoint, appendToPath);
+    if (!retval)
+    {
+        /* docs say not to destruct in case of failure, so cheat. */
+        io->opaque = NULL;
+        io->destroy(io);
+    } /* if */
+
+    return retval;
+} /* PHYSFS_mountHandle */
+
+
 int PHYSFS_mount(const char *newDir, const char *mountPoint, int appendToPath)
 {
     BAIL_IF_MACRO(newDir == NULL, ERR_INVALID_ARGUMENT, 0);
--- a/src/physfs.h	Sun Aug 29 21:36:38 2010 -0400
+++ b/src/physfs.h	Mon Aug 30 03:02:32 2010 -0400
@@ -2989,6 +2989,60 @@
                                    const char *mountPoint, int appendToPath);
 
 
+/**
+ * \fn int PHYSFS_mountHandle(PHYSFS_File *file, const char *fname, const char *mountPoint, int appendToPath)
+ * \brief Add an archive, contained in a PHYSFS_File handle, to the search path.
+ *
+ * \warning Unless you have some special, low-level need, you should be using
+ *          PHYSFS_mount() instead of this.
+ *
+ * \warning Archives-in-archives may be very slow! While a PHYSFS_File can
+ *          seek even when the data is compressed, it may do so by rewinding
+ *          to the start and decompressing everything before the seek point.
+ *          Normal archive usage may do a lot of seeking behind the scenes.
+ *          As such, you might find normal archive usage extremely painful
+ *          if mounted this way. Plan accordingly: if you, say, have a
+ *          self-extracting .zip file, and want to mount something in it,
+ *          compress the contents of the inner archive and make sure the outer
+ *          .zip file doesn't compress the inner archive too.
+ *
+ * This function operates just like PHYSFS_mount(), but takes a PHYSFS_File
+ *  handle instead of a pathname. This handle contains all the data of the
+ *  archive, and is used instead of a real file in the physical filesystem.
+ *  The PHYSFS_File may be backed by a real file in the physical filesystem,
+ *  but isn't necessarily. The most popular use for this is likely to mount
+ *  archives stored inside other archives.
+ *
+ * (filename) is only used here to optimize archiver selection (if you name it
+ *  XXXXX.zip, we might try the ZIP archiver first, for example). It doesn't
+ *  need to refer to a real file at all, and can even be NULL. If the filename
+ *  isn't helpful, the system will try every archiver until one works or none
+ *  of them do.
+ *
+ * (file) must remain until the archive is unmounted. When the archive is
+ *  unmounted, the system will call PHYSFS_close(file). If you need this
+ *  handle to survive, you will have to wrap this in a PHYSFS_Io and use
+ *  PHYSFS_mountIo() instead.
+ *
+ * If this function fails, PHYSFS_close(file) is not called.
+ *
+ *   \param file The PHYSFS_File handle containing archive data.
+ *   \param fname Filename that can represent this stream. Can be NULL.
+ *   \param mountPoint Location in the interpolated tree that this archive
+ *                     will be "mounted", in platform-independent notation.
+ *                     NULL or "" is equivalent to "/".
+ *   \param appendToPath nonzero to append to search path, zero to prepend.
+ *  \return nonzero if added to path, zero on failure (bogus archive, etc).
+ *                  Specifics of the error can be gleaned from
+ *                  PHYSFS_getLastError().
+ *
+ * \sa PHYSFS_unmount
+ * \sa PHYSFS_getSearchPath
+ * \sa PHYSFS_getMountPoint
+ */
+PHYSFS_DECL int PHYSFS_mountHandle(PHYSFS_File *file, const char *fname,
+                                   const char *mountPoint, int appendToPath);
+
 /* Everything above this line is part of the PhysicsFS 2.1 API. */
 
 
--- a/test/test_physfs.c	Sun Aug 29 21:36:38 2010 -0400
+++ b/test/test_physfs.c	Mon Aug 30 03:02:32 2010 -0400
@@ -134,8 +134,14 @@
     free(buf);
 } /* freeBuf */
 
+typedef enum
+{
+    MNTTYPE_PATH,
+    MNTTYPE_MEMORY,
+    MNTTYPE_HANDLE
+} MountType;
 
-static int cmd_mount_internal(char *args, const int fromMem)
+static int cmd_mount_internal(char *args, const MountType mnttype)
 {
     char *ptr;
     char *mntpoint = NULL;
@@ -180,9 +186,24 @@
 
     /*printf("[%s], [%s], [%d]\n", args, mntpoint, appending);*/
 
-    if (!fromMem)
+    if (mnttype == MNTTYPE_PATH)
         rc = PHYSFS_mount(args, mntpoint, appending);
-    else
+
+    else if (mnttype == MNTTYPE_HANDLE)
+    {
+        PHYSFS_File *f = PHYSFS_openRead(args);
+        if (f == NULL)
+        {
+            printf("PHYSFS_openRead('%s') failed. reason: %s.\n", args, PHYSFS_getLastError());
+            return 1;
+        } /* if */
+
+        rc = PHYSFS_mountHandle(f, args, mntpoint, appending);
+        if (!rc)
+            PHYSFS_close(f);
+    } /* else if */
+
+    else if (mnttype == MNTTYPE_MEMORY)
     {
         FILE *in = fopen(args, "rb");
         void *buf = NULL;
@@ -233,16 +254,22 @@
 
 static int cmd_mount(char *args)
 {
-    return cmd_mount_internal(args, 0);
+    return cmd_mount_internal(args, MNTTYPE_PATH);
 } /* cmd_mount */
 
 
 static int cmd_mount_mem(char *args)
 {
-    return cmd_mount_internal(args, 1);
+    return cmd_mount_internal(args, MNTTYPE_MEMORY);
 } /* cmd_mount_mem */
 
 
+static int cmd_mount_handle(char *args)
+{
+    return cmd_mount_internal(args, MNTTYPE_HANDLE);
+} /* cmd_mount_handle */
+
+
 static int cmd_removearchive(char *args)
 {
     if (*args == '\"')
@@ -1086,6 +1113,7 @@
     { "addarchive",     cmd_addarchive,     2, "<archiveLocation> <append>" },
     { "mount",          cmd_mount,          3, "<archiveLocation> <mntpoint> <append>" },
     { "mountmem",       cmd_mount_mem,      3, "<archiveLocation> <mntpoint> <append>" },
+    { "mounthandle",    cmd_mount_handle,   3, "<archiveLocation> <mntpoint> <append>" },
     { "removearchive",  cmd_removearchive,  1, "<archiveLocation>"          },
     { "unmount",        cmd_removearchive,  1, "<archiveLocation>"          },
     { "enumerate",      cmd_enumerate,      1, "<dirToEnumerate>"           },