Add random access to casefiles, for use in GUI.
[pspp-builds.git] / src / data / casefile.c
index 66baba96b918e13d83ec4e53c96bd4963e16b611..9e31d431ce39410b0953515e79d1de5b12be0311 100644 (file)
    casefile_destroy().  This function will also destroy any
    remaining casereaders. */
 
+/* FIXME: should we implement compression? */
+
 /* In-memory cases are arranged in an array of arrays.  The top
    level is variable size and the size of each bottom level array
    is fixed at the number of cases defined here.  */
@@ -127,7 +129,7 @@ struct casefile
     enum { MEMORY, DISK } storage;      /* Where cases are stored. */
     enum { WRITE, READ } mode;          /* Is writing or reading allowed? */
     struct casereader *readers;         /* List of our readers. */
-    int being_destroyed;                /* Does a destructive reader exist? */
+    bool being_destroyed;               /* Does a destructive reader exist? */
     bool ok;                            /* False after I/O error. */
 
     /* Memory storage. */
@@ -147,10 +149,13 @@ struct casereader
     struct casereader *next, *prev;     /* Next, prev in casefile's list. */
     struct casefile *cf;                /* Our casefile. */
     unsigned long case_idx;             /* Case number of current case. */
-    int destructive;                    /* Is this a destructive reader? */
+    bool destructive;                   /* Is this a destructive reader? */
+    bool random;                        /* Is this a random reader? */
 
     /* Disk storage. */
     int fd;                             /* File descriptor. */
+    off_t file_ofs;                     /* Current position in fd. */
+    off_t buffer_ofs;                   /* File offset of buffer start. */
     union value *buffer;                /* I/O buffer. */
     size_t buffer_pos;                  /* Offset of buffer position. */
     struct ccase c;                     /* Current case. */
@@ -172,10 +177,11 @@ static size_t case_bytes;
 static void register_atexit (void);
 static void exit_handler (void);
 
-static void reader_open_file (struct casereader *reader);
-static void write_case_to_disk (struct casefile *cf, const struct ccase *c);
-static void flush_buffer (struct casefile *cf);
-static bool fill_buffer (struct casereader *reader);
+static void reader_open_file (struct casereader *);
+static void write_case_to_disk (struct casefile *, const struct ccase *);
+static void flush_buffer (struct casefile *);
+static void seek_and_fill_buffer (struct casereader *);
+static bool fill_buffer (struct casereader *);
 
 static void io_error (struct casefile *, const char *, ...)
      PRINTF_FORMAT (2, 3);
@@ -270,9 +276,9 @@ casefile_error (const struct casefile *cf)
   return !cf->ok;
 }
 
-/* Returns nonzero only if casefile CF is stored in memory (instead of on
-   disk). */
-int
+/* Returns true only if casefile CF is stored in memory (instead of on
+   disk), false otherwise. */
+bool
 casefile_in_core (const struct casefile *cf) 
 {
   assert (cf != NULL);
@@ -421,8 +427,6 @@ flush_buffer (struct casefile *cf)
                        cf->buffer_size * sizeof *cf->buffer))
         io_error (cf, _("Error writing temporary file: %s."),
                   strerror (errno));
-
-
       cf->buffer_used = 0;
     }
 }
@@ -517,6 +521,7 @@ casefile_get_reader (const struct casefile *cf_)
   reader->cf = cf;
   reader->case_idx = 0;
   reader->destructive = 0;
+  reader->random = false;
   reader->fd = -1;
   reader->buffer = NULL;
   reader->buffer_pos = 0;
@@ -528,6 +533,17 @@ casefile_get_reader (const struct casefile *cf_)
   return reader;
 }
 
+/* Creates and returns a random casereader for CF.  A random
+   casereader can be used to randomly read the cases in a
+   casefile. */
+struct casereader *
+casefile_get_random_reader (const struct casefile *cf) 
+{
+  struct casereader *reader = casefile_get_reader (cf);
+  reader->random = true;
+  return reader;
+}
+
 /* Creates and returns a destructive casereader for CF.  Like a
    normal casereader, a destructive casereader sequentially reads
    the cases in a casefile.  Unlike a normal casereader, a
@@ -554,8 +570,6 @@ static void
 reader_open_file (struct casereader *reader) 
 {
   struct casefile *cf = reader->cf;
-  off_t file_ofs;
-
   if (!cf->ok || reader->case_idx >= cf->case_cnt)
     return;
 
@@ -583,24 +597,42 @@ reader_open_file (struct casereader *reader)
       memset (reader->buffer, 0, cf->buffer_size * sizeof *cf->buffer); 
     }
 
+  case_create (&reader->c, cf->value_cnt);
+
+  reader->buffer_ofs = -1;
+  reader->file_ofs = -1;
+  seek_and_fill_buffer (reader);
+}
+
+/* Seeks the backing file for READER to the proper position and
+   refreshes the buffer contents. */
+static void
+seek_and_fill_buffer (struct casereader *reader) 
+{
+  struct casefile *cf = reader->cf;
+  off_t new_ofs;
+
   if (cf->value_cnt != 0) 
     {
       size_t buffer_case_cnt = cf->buffer_size / cf->value_cnt;
-      file_ofs = ((off_t) reader->case_idx / buffer_case_cnt
+      new_ofs = ((off_t) reader->case_idx / buffer_case_cnt
                   * cf->buffer_size * sizeof *cf->buffer);
       reader->buffer_pos = (reader->case_idx % buffer_case_cnt
                             * cf->value_cnt);
     }
   else 
-    file_ofs = 0;
-  if (lseek (reader->fd, file_ofs, SEEK_SET) != file_ofs)
-    io_error (cf, _("%s: Seeking temporary file: %s."),
-              cf->file_name, strerror (errno));
+    new_ofs = 0;
+  if (new_ofs != reader->file_ofs) 
+    {
+      if (lseek (reader->fd, new_ofs, SEEK_SET) != new_ofs)
+        io_error (cf, _("%s: Seeking temporary file: %s."),
+                  cf->file_name, strerror (errno));
+      else
+        reader->file_ofs = new_ofs;
+    }
 
-  if (cf->case_cnt > 0 && cf->value_cnt > 0)
+  if (cf->case_cnt > 0 && cf->value_cnt > 0 && reader->buffer_ofs != new_ofs)
     fill_buffer (reader);
-
-  case_create (&reader->c, cf->value_cnt);
 }
 
 /* Fills READER's buffer by reading a block from disk. */
@@ -616,7 +648,12 @@ fill_buffer (struct casereader *reader)
                   reader->cf->file_name, strerror (errno));
       else if (bytes != reader->cf->buffer_size * sizeof *reader->buffer) 
         io_error (reader->cf, _("%s: Temporary file ended unexpectedly."),
-                  reader->cf->file_name); 
+                  reader->cf->file_name);
+      else 
+        {
+          reader->buffer_ofs = reader->file_ofs;
+          reader->file_ofs += bytes; 
+        }
     }
   return reader->cf->ok;
 }
@@ -633,13 +670,13 @@ casereader_get_casefile (const struct casereader *reader)
 /* Reads a copy of the next case from READER into C.
    Caller is responsible for destroying C.
    Returns true if successful, false at end of file. */
-int
+bool
 casereader_read (struct casereader *reader, struct ccase *c) 
 {
   assert (reader != NULL);
   
   if (!reader->cf->ok || reader->case_idx >= reader->cf->case_cnt) 
-    return 0;
+    return false;
 
   if (reader->cf->storage == MEMORY) 
     {
@@ -648,14 +685,14 @@ casereader_read (struct casereader *reader, struct ccase *c)
 
       case_clone (c, &reader->cf->cases[block_idx][case_idx]);
       reader->case_idx++;
-      return 1;
+      return true;
     }
   else 
     {
       if (reader->buffer_pos + reader->cf->value_cnt > reader->cf->buffer_size)
         {
           if (!fill_buffer (reader))
-            return 0;
+            return false;
           reader->buffer_pos = 0;
         }
 
@@ -665,7 +702,7 @@ casereader_read (struct casereader *reader, struct ccase *c)
       reader->case_idx++;
 
       case_clone (c, &reader->c);
-      return 1;
+      return true;
     }
 }
 
@@ -673,7 +710,7 @@ casereader_read (struct casereader *reader, struct ccase *c)
    to the caller.  Caller is responsible for destroying C.
    Returns true if successful, false at end of file or on I/O
    error. */
-int
+bool
 casereader_read_xfer (struct casereader *reader, struct ccase *c)
 {
   assert (reader != NULL);
@@ -690,10 +727,25 @@ casereader_read_xfer (struct casereader *reader, struct ccase *c)
 
       case_move (c, read_case);
       reader->case_idx++;
-      return 1;
+      return true;
     }
 }
 
+/* Sets the next case to be read by READER to CASE_IDX,
+   which must be less than the number of cases in the casefile.
+   Allowed only for random readers. */
+void
+casereader_seek (struct casereader *reader, unsigned long case_idx) 
+{
+  assert (reader != NULL);
+  assert (reader->random);
+  assert (case_idx < reader->cf->case_cnt);
+
+  reader->case_idx = case_idx;
+  if (reader->cf->storage == DISK)
+    seek_and_fill_buffer (reader);
+}
+
 /* Destroys READER. */
 void
 casereader_destroy (struct casereader *reader)
@@ -733,18 +785,18 @@ io_error (struct casefile *cf, const char *format, ...)
 {
   if (cf->ok) 
     {
-      struct error e;
+      struct msg m;
       va_list args;
 
-      e.category = MSG_GENERAL;
-      e.severity = MSG_ERROR;
-      e.where.file_name = NULL;
-      e.where.line_number = -1;
+      m.category = MSG_GENERAL;
+      m.severity = MSG_ERROR;
+      m.where.file_name = NULL;
+      m.where.line_number = -1;
       va_start (args, format);
-      e.text = xvasprintf (format, args);
+      m.text = xvasprintf (format, args);
       va_end (args);
       
-      err_msg (&e);
+      msg_emit (&m);
     }
   cf->ok = false;
 }
@@ -785,16 +837,14 @@ static int safe_close (int fd)
 static void
 register_atexit (void) 
 {
-  static int registered = 0;
+  static bool registered = false;
   if (!registered) 
     {
-      registered = 1;
+      registered = true;
       atexit (exit_handler);
     }
 }
 
-
-
 /* atexit() handler that closes and deletes our temporary
    files. */
 static void