Move all command implementations into a single 'commands' directory.
[pspp] / src / language / commands / data-writer.c
diff --git a/src/language/commands/data-writer.c b/src/language/commands/data-writer.c
new file mode 100644 (file)
index 0000000..4c4a823
--- /dev/null
@@ -0,0 +1,260 @@
+/* PSPP - a program for statistical analysis.
+   Copyright (C) 1997-2004, 2006, 2010, 2011, 2012, 2013 Free Software Foundation, Inc.
+
+   This program is free software: you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation, either version 3 of the License, or
+   (at your option) any later version.
+
+   This program is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program.  If not, see <http://www.gnu.org/licenses/>. */
+
+#include <config.h>
+
+#include "language/commands/data-writer.h"
+
+#include <assert.h>
+#include <errno.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <sys/stat.h>
+
+#include "data/file-name.h"
+#include "data/make-file.h"
+#include "language/commands/file-handle.h"
+#include "libpspp/assertion.h"
+#include "libpspp/encoding-guesser.h"
+#include "libpspp/integer-format.h"
+#include "libpspp/i18n.h"
+#include "libpspp/message.h"
+#include "libpspp/str.h"
+
+#include "gl/minmax.h"
+#include "gl/xalloc.h"
+
+#include "gettext.h"
+#define _(msgid) gettext (msgid)
+#define N_(msgid) (msgid)
+
+/* Data file writer. */
+struct dfm_writer
+  {
+    struct file_handle *fh;     /* File handle. */
+    struct fh_lock *lock;       /* Exclusive access to file. */
+    FILE *file;                 /* Associated file. */
+    struct replace_file *rf;    /* Atomic file replacement support. */
+    char *encoding;             /* Encoding. */
+    enum fh_line_ends line_ends; /* Line ends for text files. */
+
+    int unit;                   /* Unit width, in bytes. */
+    char cr[MAX_UNIT];          /* \r in encoding, 'unit' bytes long. */
+    char lf[MAX_UNIT];          /* \n in encoding, 'unit' bytes long. */
+    char spaces[32];            /* 32 bytes worth of ' ' in encoding. */
+  };
+
+/* Opens a file handle for writing as a data file.
+
+   The encoding of the file written is by default that of FH itself.  If
+   ENCODING is nonnull, then it overrides the default encoding.
+
+   *However*: ENCODING directly affects only text strings written by the data
+   writer code itself, that is, new-lines in FH_MODE_TEXT and space padding in
+   FH_MODE_FIXED mode.  The client must do its own encoding translation for the
+   data that it writes.  (This is unavoidable because sometimes the data
+   written includes binary data that reencoding would mangle.)  The client can
+   obtain the encoding to re-encode into with dfm_writer_get_encoding(). */
+struct dfm_writer *
+dfm_open_writer (struct file_handle *fh, const char *encoding)
+{
+  struct encoding_info ei;
+  struct dfm_writer *w;
+  struct fh_lock *lock;
+  int ofs;
+
+  lock = fh_lock (fh, FH_REF_FILE, N_("data file"), FH_ACC_WRITE, false);
+  if (lock == NULL)
+    return NULL;
+
+  w = fh_lock_get_aux (lock);
+  if (w != NULL)
+    return w;
+
+  encoding = encoding_guess_parse_encoding (encoding != NULL
+                                            ? encoding
+                                            : fh_get_encoding (fh));
+  get_encoding_info (&ei, encoding);
+
+  w = xmalloc (sizeof *w);
+  w->fh = fh_ref (fh);
+  w->lock = lock;
+  w->rf = replace_file_start (w->fh, "wb", 0666, &w->file);
+  w->encoding = xstrdup (encoding);
+  w->line_ends = fh_get_line_ends (fh);
+  w->unit = ei.unit;
+  memcpy (w->cr, ei.cr, sizeof w->cr);
+  memcpy (w->lf, ei.lf, sizeof w->lf);
+  for (ofs = 0; ofs + ei.unit <= sizeof w->spaces; ofs += ei.unit)
+    memcpy (&w->spaces[ofs], ei.space, ei.unit);
+
+  if (w->rf == NULL)
+    {
+      msg (ME, _("An error occurred while opening `%s' for writing "
+                 "as a data file: %s."),
+           fh_get_file_name (w->fh), strerror (errno));
+      dfm_close_writer (w);
+      return NULL;
+    }
+  fh_lock_set_aux (lock, w);
+
+  return w;
+}
+
+/* Returns true if an I/O error occurred on WRITER, false otherwise. */
+bool
+dfm_write_error (const struct dfm_writer *writer)
+{
+  return ferror (writer->file);
+}
+
+/* Writes record REC (which need not be null-terminated) having
+   length LEN to the file corresponding to HANDLE.  Adds any
+   needed formatting, such as a trailing new-line.  Returns true
+   on success, false on failure. */
+bool
+dfm_put_record (struct dfm_writer *w, const char *rec, size_t len)
+{
+  assert (w != NULL);
+
+  if (dfm_write_error (w))
+    return false;
+
+  switch (fh_get_mode (w->fh))
+    {
+    case FH_MODE_TEXT:
+      fwrite (rec, len, 1, w->file);
+      if (w->line_ends == FH_END_CRLF)
+        fwrite (w->cr, w->unit, 1, w->file);
+      fwrite (w->lf, w->unit, 1, w->file);
+      break;
+
+    case FH_MODE_FIXED:
+      {
+        size_t record_width = fh_get_record_width (w->fh);
+        size_t write_bytes = MIN (len, record_width);
+        size_t pad_bytes = record_width - write_bytes;
+        fwrite (rec, write_bytes, 1, w->file);
+        while (pad_bytes > 0)
+          {
+            size_t chunk = MIN (pad_bytes, sizeof w->spaces);
+            fwrite (w->spaces, chunk, 1, w->file);
+            pad_bytes -= chunk;
+          }
+      }
+      break;
+
+    case FH_MODE_VARIABLE:
+      {
+        uint32_t size = len;
+        integer_convert (INTEGER_NATIVE, &size, INTEGER_LSB_FIRST, &size,
+                         sizeof size);
+        fwrite (&size, sizeof size, 1, w->file);
+        fwrite (rec, len, 1, w->file);
+        fwrite (&size, sizeof size, 1, w->file);
+      }
+      break;
+
+    case FH_MODE_360_VARIABLE:
+    case FH_MODE_360_SPANNED:
+      {
+        size_t ofs = 0;
+        if (fh_get_mode (w->fh) == FH_MODE_360_VARIABLE)
+          len = MIN (65527, len);
+        while (ofs < len)
+          {
+            size_t chunk = MIN (65527, len - ofs);
+            uint32_t bdw = (chunk + 8) << 16;
+            int scc = (ofs == 0 && chunk == len ? 0
+                       : ofs == 0 ? 1
+                       : ofs + chunk == len ? 2
+                       : 3);
+            uint32_t rdw = ((chunk + 4) << 16) | (scc << 8);
+
+            integer_convert (INTEGER_NATIVE, &bdw, INTEGER_MSB_FIRST, &bdw,
+                             sizeof bdw);
+            integer_convert (INTEGER_NATIVE, &rdw, INTEGER_MSB_FIRST, &rdw,
+                             sizeof rdw);
+            fwrite (&bdw, 1, sizeof bdw, w->file);
+            fwrite (&rdw, 1, sizeof rdw, w->file);
+            fwrite (rec + ofs, 1, chunk, w->file);
+            ofs += chunk;
+          }
+      }
+      break;
+
+    default:
+      NOT_REACHED ();
+    }
+
+  return !dfm_write_error (w);
+}
+
+/* Writes record REC (which need not be null-terminated) having length LEN to
+   the file corresponding to HANDLE.  REC is encoded in UTF-8, which this
+   function recodes to the correct encoding for W before writing.  Adds any
+   needed formatting, such as a trailing new-line.  Returns true on success,
+   false on failure. */
+bool
+dfm_put_record_utf8 (struct dfm_writer *w, const char *rec, size_t len)
+{
+  if (is_encoding_utf8 (w->encoding))
+    return dfm_put_record (w, rec, len);
+  else
+    {
+      char *recoded = recode_string (w->encoding, UTF8, rec, len);
+      bool ok = dfm_put_record (w, recoded, strlen (recoded));
+      free (recoded);
+      return ok;
+    }
+}
+
+/* Closes data file writer W. */
+bool
+dfm_close_writer (struct dfm_writer *w)
+{
+  bool ok;
+
+  if (w == NULL)
+    return true;
+  if (fh_unlock (w->lock))
+    return true;
+
+  ok = true;
+  if (w->file != NULL)
+    {
+      const char *file_name = fh_get_file_name (w->fh);
+      ok = !dfm_write_error (w) && !fn_close (w->fh, w->file);
+
+      if (!ok)
+        msg (ME, _("I/O error occurred writing data file `%s'."), file_name);
+
+      if (ok ? !replace_file_commit (w->rf) : !replace_file_abort (w->rf))
+        ok = false;
+    }
+  fh_unref (w->fh);
+  free (w->encoding);
+  free (w);
+
+  return ok;
+}
+
+/* Returns the encoding of data written to WRITER. */
+const char *
+dfm_writer_get_encoding (const struct dfm_writer *writer)
+{
+  return writer->encoding;
+}