/* Copyright 1997 Acorn Computers Ltd
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
/***************************************************/
/* File   : Save.c                                 */
/*                                                 */
/* Purpose: Save functions for the browser.        */
/*                                                 */
/* Author : A.D.Hodgkinson                         */
/*                                                 */
/* History: 04-Dec-96: Created.                    */
/***************************************************/

#include <stdlib.h>
#include <stdio.h>
#include <string.h>

#include "swis.h"
#include "flex.h"

#include "HTMLLib.h" /* HTML library API, Which will include html2_ext.h, tags.h and struct.h */

#include "wimp.h"
#include "wimplib.h"
#include "event.h"

#include "toolbox.h"
#include "window.h"
#include "menu.h"
#include "saveas.h"

#include "svcprint.h"
#include "Global.h"
#include "Utils.h"

#include "Fetch.h" /* (Which itself includes URLstat.h) */
#include "Filetypes.h"
#include "Hotlist.h"
#include "Protocols.h"
#include "URLutils.h"

#include "Save.h"

/*************************************************/
/* save_save_source()                            */
/*                                               */
/* Saves the document source for a given browser */
/* to the given path, setting the filetype as    */
/* HTML or text as appropriate.                  */
/*                                               */
/* Parameters: Pointer to a null-terminated      */
/*             pathname to save to;              */
/*                                               */
/*             Pointer to a browser_data struct  */
/*             owning the source to save.        */
/*************************************************/

_kernel_oserror * save_save_source(char * path, browser_data * b)
{
  _kernel_oserror * e;

  /* Sanity checks */

  if (!b || !b->source || !path || !*path)
  {
    #ifdef TRACE

      erb.errnum = Utils_Error_Custom_Normal;

      sprintf(erb.errmess,
              "Invalid parameters to save_save_source: %p, %p (source %p)",
              path,
              b,
              b ? b->source : NULL);

      return &erb;

    #else

      return NULL;

    #endif
  }

  /* Save the file - lock flex heap to ensure save transfer */

  flex_set_budge(0);

  e = _swix(OS_File,
            _INR(0,2) | _INR(4,5),

            10, /* Save block of memory as a typed file */
            path,
            b->page_is_text ? FileType_TEXT : FileType_HTML,
            b->source,
            ((char *) b->source) + flex_size((flex_ptr) &b->source));

  /* Unlock flex */

  flex_set_budge(1);

  return e;
}

/*************************************************/
/* save_transfer_source()                        */
/*                                               */
/* Save a browser's page source as an HTML file, */
/* through a RAM transfer buffer with            */
/* Wimp_TransferBlock.                           */
/*                                               */
/* Intended to be called as a response to a      */
/* Message_RAMFetch from another task.           */
/*                                               */
/* Parameters: Pointer to a browser_data struct  */
/*             relevant to the document source;  */
/*                                               */
/*             Pointer to an int, in which the   */
/*             amount of data transferred so     */
/*             far should be stored on entry;    */
/*                                               */
/*             Pointer to the WimpMessage struct */
/*             corresponding to the              */
/*             Message_RAMFetch that led to this */
/*             function being called.            */
/*                                               */
/* Returns:    The contents of the int holding   */
/*             the amount of data transferred    */
/*             prior to the function call are    */
/*             updated with the new amount       */
/*             transferred. Callers should use   */
/*             this in any future calls, and it  */
/*             *must* be checked for a value of  */
/*             -1, which indicates the transfer  */
/*             is complete (so any tidying up    */
/*             should be done if there is an     */
/*             error returned, or if the int is  */
/*             filled in with a value of -1).    */
/*                                               */
/* Assumes:    The various pointers may be NULL, */
/*             though if they are the function   */
/*             does nothing (it just exits).     */
/*************************************************/

_kernel_oserror * save_transfer_source(browser_data * b, int * transferred, WimpMessage * m)
{
  _kernel_oserror * e     = NULL;
  int               size  = save_source_size(b);
  int               left;
  int               write;

  /* Sanity check */

  if (!b || !b->source || !transferred || !m)
  {
    #ifdef TRACE

      erb.errnum = Utils_Error_Custom_Normal;

      sprintf(erb.errmess,
              "Invalid parameters to save_transfer_source: %p (source %p), %p, %p",
              b,
              b ? b->source : NULL,
              transferred,
              m);

      return &erb;

    #else

      return NULL;

    #endif
  }

  left = size - *transferred;

  /* If we have data to transfer, do so */

  if (left >= 0)
  {
    /* Use either the buffer size, or the bytes left, whichever */
    /* is the smallest.                                         */

    write = left > m->data.ram_fetch.buffer_size ? m->data.ram_fetch.buffer_size : left;

    if (write)
    {
      /* Transfer the data - must lock the flex heap for the */
      /* duration of this...                                 */

      flex_set_budge(0);

      e = wimp_transfer_block(task_handle,
                              (char *) b->source + (*transferred),
                              m->hdr.sender,
                              m->data.ram_fetch.buffer,
                              write);

      /* Unlock flex and report any errors */

      flex_set_budge(1);

      if (e) return e;
    }

    /* If we have any data left to send, reply with a Message_RAMTransmit */

    e = protocols_atats_send_ram_transmit(m, write, write < m->data.ram_fetch.buffer_size);

    /* Increment the transferred counter */

    *transferred += write;
    left         -= write;
  }

  /* Finished */

  return e;
}

/*************************************************/
/* save_source_size()                            */
/*                                               */
/* Returns the size that a given HTML source     */
/* document would take on disc.                  */
/*                                               */
/* Parameters: Pointer to a browser_data struct  */
/*             owning the source to save.        */
/*                                               */
/* Returns:    Size the file will be.            */
/*************************************************/

int save_source_size(browser_data * b)
{
  /* Sanity check */

  if (!b || !b->source)
  {
    #ifdef TRACE

      erb.errnum = Utils_Error_Custom_Normal;

      sprintf(erb.errmess,
              "Invalid parameters to save_source_size: %p (source %p)",
              b,
              b ? b->source : NULL);

      show_error_ret(&erb);

    #endif

    return 0;
  }

  /* For now this is trivially simple, but in future it */
  /* could take account of, for example, inserting a    */
  /* <BASE> tag into the document.                      */

  return flex_size((flex_ptr) &b->source);
}

/*************************************************/
/* save_save_object()                            */
/*                                               */
/* Called when a fetch is to be spooled to disc. */
/* Handles saving as much data as is already     */
/* fetched, and setting the relevant parts of    */
/* the given browser_data structure up with the  */
/* output file details.                          */
/*                                               */
/* Parameters: Pointer to a null-terminated      */
/*             pathname to save to;              */
/*                                               */
/*             Pointer to a browser_data struct  */
/*             relevant to the object to save.   */
/*************************************************/

_kernel_oserror * save_save_object(char * path, browser_data * b)
{
  /* Sanity check */

  if (!b || !path || !*path)
  {
    #ifdef TRACE

      erb.errnum = Utils_Error_Custom_Normal;

      sprintf(erb.errmess,
              "Invalid parameters to save_save_object: %p, %p",
              path,
              b);

      return &erb;

    #else

      return NULL;

    #endif
  }

  /* If using a small fetch window, set the title to the save pathname */

  if (b->small_fetch)
  {
    char title[Limits_Title];

    StrNCpy0(title, path);

    /* Don't treat any errors here as fatal */

    show_error_ret(window_set_title(0, b->self_id, title));
  }

  /* Open the file */

  b->save_file = fopen(path, "wb");

  if (!b->save_file)
  {
    fetch_stop(b, 0);
    RetLastE;
  }
  else if (b->source)
  {
    int bytes;

    /* Set the filetype to DEADDEAD, to represent an incomplete */
    /* file (particularly good on later Filers, which display   */
    /* a special sprite for this).                              */

    _swix(OS_File,
          _INR(0,2),

          2, /* Set load address */
          path,
          0xdeaddead);

    _swix(OS_File,
          _INR(0,1) | _IN(3),

          3, /* Set exec address */
          path,
          0xdeaddead);

    /* Any data in the source store represents already */
    /* fetched bits of the file. Must lock flex down   */
    /* over the save to make sure the heap doesn't     */
    /* shift over the call to fwrite.                  */

    flex_set_budge(0);

    bytes = fwrite(b->source,
                   1,
                   flex_size((flex_ptr) &b->source),
                   b->save_file);

    flex_set_budge(1);

    /* If we didn't transfer as much as we expected, complain */

    if (bytes != flex_size((flex_ptr) &b->source))
    {
      /* Report any errors */

      fetch_stop(b, 0);
      RetLastE;
    }
    else
    {
      /* Otherwise, get rid of the data in the source store */
      /* as it's been written to the file.                  */

      flex_free((flex_ptr) &b->source);
      b->source = NULL;
    }
  }

  return NULL;
}

/*************************************************/
/* save_object_size()                            */
/*                                               */
/* Returns the estimated size that a given       */
/* object will be after being spooled through    */
/* the fetcher.                                  */
/*                                               */
/* Parameters: Pointer to a browser_data struct  */
/*             relevant to the object to save.   */
/*                                               */
/* Returns:    *Estimated* size of the object.   */
/*************************************************/

int save_object_size(browser_data * b)
{
  /* For now, we just don't know this information... */

  return 4096;
}

/*************************************************/
/* save_save_uri()                               */
/*                                               */
/* Save the contents of a link as a URI file.    */
/*                                               */
/* Parameters: Pointer to a null-terminated      */
/*             pathname to save to;              */
/*                                               */
/*             Pointer to the URL to save;       */
/*                                               */
/*             0 to save as a URI file, else     */
/*             save as a non-terminated string   */
/*             with type FileType_URL (ANT       */
/*             suite URL file).                  */
/*************************************************/

_kernel_oserror * save_save_uri(char * path, char * url, int write_url)
{
  _kernel_oserror * e = NULL;
  FILE            * file;

  /* Sanity check */

  if (!path || !*path || !url || !*url)
  {
    #ifdef TRACE

      erb.errnum = Utils_Error_Custom_Normal;

      sprintf(erb.errmess,
              "Invalid parameters to save_save_uri: %p, %p, %d",
              path,
              url,
              write_url);

      return &erb;

    #else

      return NULL;

    #endif
  }

  /* Try and open the file */

  file = fopen(path, "wb");
  if (!file) RetLastE;

  /* Write the contents */

  if (!write_url)
  {
    /* URI file */

    if (
         fprintf(file,
                 "URI\t100\r\t# %s v",
                 lookup_token("_TaskName",1,0)) < 0
       )
       e = _kernel_last_oserror(), erb = *e;

    else if (
              fprintf(file,
                      "%s\r\r\t%s",
                      lookup_token("Version:(Unknown!)",0,0),
                      url) < 0
            )
            e = _kernel_last_oserror(), erb = *e;
  }
  else
  {
    /* URL file */

    if (fprintf(file, url) < 0) e = _kernel_last_oserror(), erb = *e;
  }

  /* Close the file and return any error that may have */
  /* happened during the file writing stage            */

  fclose(file);

  if (e) return &erb;

  /* Exit via. setting the filetype */

  return _swix(OS_File,
               _INR(0,2),

               18, /* Set type of named object */
               path,
               write_url ? FileType_URL : FileType_URI);
}

/*************************************************/
/* save_transfer_uri()                           */
/*                                               */
/* Save the contents of a link as a URI file,    */
/* through a RAM transfer buffer with            */
/* Wimp_TransferBlock.                           */
/*                                               */
/* Intended to be called as a response to a      */
/* Message_RAMFetch from another task.           */
/*                                               */
/* Parameters: Pointer to the URL to save;       */
/*                                               */
/*             0 to save as a URI file, else     */
/*             save as a non-terminated string   */
/*             with type FileType_URL (ANT       */
/*             suite URL file);                  */
/*                                               */
/*             Pointer to an int, in which the   */
/*             amount of data transferred so     */
/*             far should be stored on entry;    */
/*                                               */
/*             Pointer to the WimpMessage struct */
/*             corresponding to the              */
/*             Message_RAMFetch that led to this */
/*             function being called.            */
/*                                               */
/* Returns:    The contents of the int holding   */
/*             the amount of data transferred    */
/*             prior to the function call are    */
/*             updated with the new amount       */
/*             transferred. Callers should use   */
/*             this in any future calls, and it  */
/*             *must* be checked for a value of  */
/*             -1, which indicates the transfer  */
/*             is complete (so any tidying up    */
/*             should be done if there is an     */
/*             error returned, or if the int is  */
/*             filled in with a value of -1).    */
/*                                               */
/* Assumes:    The various pointers may be NULL, */
/*             though if they are the function   */
/*             does nothing (it just exits).     */
/*************************************************/

_kernel_oserror * save_transfer_uri(char * url, int write_url, int * transferred, WimpMessage * m)
{
  _kernel_oserror * e        = NULL;
  char            * uri_file = NULL;
  int               size     = save_uri_size(url, write_url);
  int               left;
  int               write;

  /* Sanity check */

  if (!url || !*url || !transferred || !m)
  {
    #ifdef TRACE

      erb.errnum = Utils_Error_Custom_Normal;

      sprintf(erb.errmess,
              "Invalid parameters to save_transfer_uri: %p, %d, %p, %p",
              url,
              write_url,
              transferred,
              m);

      return &erb;

    #else

      return NULL;

    #endif
  }

  left = size - *transferred;

  /* Each time the function is called, rebuild the URI file in a */
  /* temporary buffer.                                           */

  if (size > 0 && left > 0)
  {
    uri_file = malloc(size + 1); /* + 1 to account for terminators */

    if (!uri_file)
    {
      e = make_no_memory_error(10);
      goto save_transfer_uri_exit;
    }

    /* Build the URI or URL file */

    if (!write_url)
    {
      int written;

      /* URI file */

      written = sprintf(uri_file,
                        "URI\t100\r\t# %s v",
                        lookup_token("_TaskName",1,0));

      sprintf(uri_file + written,
              "%s\r\r\t%s",
              lookup_token("Version:(Unknown!)",0,0),
              url);
    }
    else
    {
      /* URL file */

      strcpy(uri_file, url);
    }
  }

  /* If we have data to transfer, do so */

  if (left >= 0)
  {
    /* Use either the buffer size, or the bytes left, whichever */
    /* is the smallest.                                         */

    write = left > m->data.ram_fetch.buffer_size ? m->data.ram_fetch.buffer_size : left;

    if (write)
    {
      /* Transfer the data */

      e = wimp_transfer_block(task_handle,
                              uri_file + (*transferred),
                              m->hdr.sender,
                              m->data.ram_fetch.buffer,
                              write);

      if (e) goto save_transfer_uri_exit;
    }

    /* If we have any data left to send, reply with a Message_RAMTransmit */

    e = protocols_atats_send_ram_transmit(m, write, write < m->data.ram_fetch.buffer_size);

    /* Increment the transferred counter */

    *transferred += write;
    left         -= write;
  }

  /* Finished */

save_transfer_uri_exit:

  /* Free the URI file buffer, if we claimed one */

  if (uri_file) free (uri_file);

  return e;
}

/*************************************************/
/* save_uri_size()                               */
/*                                               */
/* Returns the size, in bytes, that a URI or URL */
/* file will be.                                 */
/*                                               */
/* Parameters: Pointer to the URL that would be  */
/*             saved;                            */
/*                                               */
/*             0 to find the length of a URI     */
/*             file, 1 to find the length of a   */
/*             URL file (as in save_save_uri).   */
/*                                               */
/* Returns:    The size, in bytes, that the file */
/*             will be.                          */
/*************************************************/

int save_uri_size(char * url, int write_url)
{
  int len = 0;

  /* Sanity check */

  if (!url || !*url)
  {
    #ifdef TRACE

      erb.errnum = Utils_Error_Custom_Normal;

      sprintf(erb.errmess,
              "Invalid parameters to save_uri_size: %p, %d",
              url,
              write_url);

      show_error_ret(&erb);

    #endif

    return 0;
  }

  if (!write_url)
  {
    /* URI file */

    len =  strlen("URI\t100\r\t# ");
    len += strlen(lookup_token("_TaskName",1,0));

    len += strlen(" v");
    len += strlen(lookup_token("Version:(Unknown!)",0,0));

    len += strlen("\r\r\t");
    len += strlen(url);
  }
  else
  {
    /* URL file */

    len = strlen(url); /* (No terminator needed in URL files, so no '+ 1') */
  }

  return len;
}