/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*-
 *
 * The contents of this file are subject to the Netscape Public License
 * Version 1.0 (the "NPL"); you may not use this file except in
 * compliance with the NPL.  You may obtain a copy of the NPL at
 * http://www.mozilla.org/NPL/
 *
 * Software distributed under the NPL is distributed on an "AS IS" basis,
 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the NPL
 * for the specific language governing rights and limitations under the
 * NPL.
 *
 * The Initial Developer of this code under the NPL is Netscape
 * Communications Corporation.  Portions created by Netscape are
 * Copyright (C) 1998 Netscape Communications Corporation.  All Rights
 * Reserved.
 */
/* 
   DownloadFrame.cpp -- class definition for the save to disk window.
   Created: Chris Toshok <toshok@netscape.com>, 21-Feb-97
 */



#include "DownloadFrame.h"
#include "MozillaApp.h"
#include "Dashboard.h"
#include "ViewGlue.h"
#include "Logo.h"

#include "xlate.h"

#include <Xm/XmP.h>
#include <Xm/LabelG.h>
#include <Xm/TextF.h>
#include <Xm/Form.h>
#include <Xm/MessageB.h>
#include <Xm/PushB.h>

#include <Xfe/Xfe.h>

#include "xpgetstr.h"
#include "felocale.h"

extern int XFE_SAVE_AS_TYPE_ENCODING;
extern int XFE_SAVE_AS_TYPE;
extern int XFE_SAVE_AS_ENCODING;
extern int XFE_SAVE_AS;
extern int XFE_ERROR_OPENING_FILE;
extern int XFE_ERROR_READ_ONLY;
extern int XFE_COMMANDS_SAVE_AS_USAGE;
extern int XFE_COMMANDS_SAVE_AS_USAGE_TWO;

#ifdef DEBUG_toshok
#define D(x) x
#else
#define D(x)
#endif

/* Externs from dialog.c: */
extern "C" {
  void fe_set_scrolled_default_size(MWContext *context);
  int fe_await_synchronous_url (MWContext *context);
  int fe_GetSynchronousURL(MWContext *context,
						   Widget widget_to_grab,
						   const char *title,
						   URL_Struct *url,
						   int output_format,
						   void *call_data);
  void fe_save_top_frame_as_cb (Widget widget, XtPointer closure,
                                XtPointer call_data);
}

// Note:  isFileP - if true and the file_name is a directory,
//                  we return False...
//
//        existsP - if true, the file_name must exist or we return False,
//                  otherwise we just make sure we can read/write into the 
//                  directory specified in file_name...
//
XP_Bool
XFE_StatReadWrite(const char *file_name, XP_Bool isFileP, XP_Bool existsP) 
{
	// NOTE:  let's get the directory that this file resides in...
	//
	XP_StatStruct  dir_info;
	XP_StatStruct  file_info;
	XP_Bool        isDir = False;
	char*          p;
	char*          base_name = (char *) XP_ALLOC(XP_STRLEN(file_name) + 1);
	
	p = XP_STRRCHR(file_name, '/');
	
	if (p) {
		if (p == file_name) {
			XP_STRCPY(base_name, "/");
		}
		else {
			int32 len = p - file_name;

			if (len == XP_STRLEN(file_name)) {
				isDir = True;
				XP_STRCPY(base_name, file_name);
			}
			else {
				XP_MEMCPY(base_name, file_name, len);
				base_name[len] = '\0';
			}
		}
	}
	else {
		// WARNING... [ we may get into some trouble with this one ]
		//
		XP_STRCPY(base_name, ".");
	}
	
	// NOTE:  we need to verify that we can read/write the directory
	//        before we check the file...
	//
	//        - need to check all three mode groups... [ owner, group, other ]
	//
	if ( stat(base_name, &dir_info) >= 0 ) {
		XP_Bool gotcha = False;
		
		if ( isFileP && 
			 isDir
			) {
			// NOTE:  the specifed file_name is a directory... [ fail ]
			//
			XP_FREE(base_name);

			return False;
		}

		if (dir_info.st_uid == getuid()) {
			if ((dir_info.st_mode & S_IRUSR) &&
				(dir_info.st_mode & S_IWUSR)) {
				gotcha = True;
			}
		}
		
		if (!gotcha) {
			if (dir_info.st_gid == getgid()) {
				if ((dir_info.st_mode & S_IRGRP) &&
					(dir_info.st_mode & S_IWGRP)) {
					gotcha = True;
				}
			}
			
			if (!gotcha) {
				if ((dir_info.st_mode & S_IROTH) &&
					(dir_info.st_mode & S_IWOTH)) {
					gotcha = True;
				}
			}
		}

		if (gotcha) {
			if (isDir) {
				// NOTE: we're done... [ we can read/write this directory ]
				//
				XP_FREE(base_name);

				return True;
			}
			else {
				// NOTE:  let's do the same thing for the file...
				//
				if ( stat(file_name, &file_info) >= 0 ) {
					gotcha = False;
					
					if (isFileP &&
						(file_info.st_mode & S_IFDIR)) {
						// NOTE:  the specifed file_name is a directory...
						//
						XP_FREE(base_name);

						return False;
					}

					if (file_info.st_uid == getuid()) {
						if ((file_info.st_mode & S_IRUSR) &&
							(file_info.st_mode & S_IWUSR)) {
							gotcha = True;
						}
					}
					
					if (!gotcha) {
						if (file_info.st_gid == getgid()) {
							if ((file_info.st_mode & S_IRGRP) &&
								(file_info.st_mode & S_IWGRP)) {
								gotcha = True;
							}
						}
						
						if (!gotcha) {
							if ((file_info.st_mode & S_IROTH) &&
								(file_info.st_mode & S_IWOTH)) {
								gotcha = True;
							}
						}
					}

					if (gotcha) {
						// NOTE:  we can read/write this directory/file...
						//
						XP_FREE(base_name);
						
						return True;
					}
					else {
						// NOTE:  we failed the file check...
						//
						XP_FREE(base_name);
						
						return False;
					}
				}
				else {
					if (existsP) {
						// NOTE:  failed to stat the file...
						//
						XP_FREE(base_name);
					
						return False;
					}
					else {
						// NOTE:  we can read/write this directory...
						//
						XP_FREE(base_name);
					
						return True;
					}
				}
			}
		}
		else {
			// NOTE:  we failed the directory check...
			//
			XP_FREE(base_name);

			return False;
		}
	}
	else {
		// NOTE:  failed to stat the directory...
		//
		XP_FREE(base_name);

		return False;
	}
}


//////////////////////////////////////////////////////////////////////////
//
// XFE_DownloadView
//
//////////////////////////////////////////////////////////////////////////

XFE_DownloadFrame::XFE_DownloadFrame(Widget toplevel, XFE_Frame *parent_frame)
	: XFE_Frame("Download",			// name
				toplevel,			// top level
				parent_frame,		// parent frame
				FRAME_DOWNLOAD,		// frame type
				NULL,				// chrome
				False,				// have html display
				False,				// have menu bar
				False,				// have toolbar
				False,				// have dashboard (we'll make our own)
				True)				// destroy on close
{
	Widget msgb;
	XFE_Component *belowView;
	Arg av[10];
	int ac;

	XFE_DownloadView *v = new XFE_DownloadView(this, 
                                               getChromeParent(),
											   NULL, 
                                               m_context);

	ac = 0;
	XtSetArg(av[ac], XmNdialogType, XmDIALOG_TEMPLATE); ac++;
	msgb = XmCreateMessageBox(getChromeParent(), "messagebox", av, ac);

	/* We have to do this explicitly because of AIX versions come
	   with these buttons by default */
	fe_UnmanageChild_safe(XmMessageBoxGetChild(msgb, XmDIALOG_SEPARATOR));
	fe_UnmanageChild_safe(XmMessageBoxGetChild(msgb, XmDIALOG_OK_BUTTON));
	fe_UnmanageChild_safe(XmMessageBoxGetChild(msgb, XmDIALOG_CANCEL_BUTTON));
	fe_UnmanageChild_safe(XmMessageBoxGetChild(msgb, XmDIALOG_HELP_BUTTON));

	m_stopButton = XtVaCreateManagedWidget("stopLoading",
										   xmPushButtonWidgetClass,
										   msgb,
										   NULL);

	XtAddCallback(m_stopButton, XmNactivateCallback, activate_cb, this);

	belowView = new XFE_Component(msgb);

	setView(v);
	setBelowViewArea(belowView);

	m_dashboard = new XFE_Dashboard(this,				// top level
									getChromeParent(),	// parent
									this,				// parent frame
									False);				// have task bar

	m_dashboard->setShowStatusBar(True);
	m_dashboard->setShowProgressBar(True);

	fe_set_scrolled_default_size(m_context);
	v->show();
	belowView->show();


	m_dashboard->show();

	// Register the logo animation notifications with ourselves
	// We need to do this, cause the XFE_Frame class only does so
	// if the have_toolbars ctor parameter is true and in our case
	// it is not.
	registerInterest(XFE_Frame::logoStartAnimation,
					 this,
					 &XFE_Frame::logoAnimationStartNotice_cb);
	
	registerInterest(XFE_Frame::logoStopAnimation,
					 this,
					 &XFE_Frame::logoAnimationStopNotice_cb);
}

XFE_DownloadFrame::~XFE_DownloadFrame()
{
	// Unregister the logo animation notifications with ourselves
	// We need to do this, cause the XFE_Frame class only does so
	// if the have_toolbars ctor parameter is true and in our case
	// it is not.
	unregisterInterest(XFE_Frame::logoStartAnimation,
					   this,
					   &XFE_Frame::logoAnimationStartNotice_cb);
	
	unregisterInterest(XFE_Frame::logoStopAnimation,
					   this,
					   &XFE_Frame::logoAnimationStopNotice_cb);
}

XP_Bool
XFE_DownloadFrame::isCommandEnabled(CommandType cmd,
									void */*calldata*/, XFE_CommandInfo*)
{
	if (cmd == xfeCmdStopLoading)
		return fe_IsContextStoppable(m_context);
	else
		return False;
}

void
XFE_DownloadFrame::doCommand(CommandType cmd,
							 void */*calldata*/, XFE_CommandInfo*)
{
	if (cmd == xfeCmdStopLoading)
		{
			D( printf ("Canceling.\n"); )
			XP_InterruptContext(m_context);
			// allConnectionsComplete will destroy this frame.
		}
}

XP_Bool
XFE_DownloadFrame::handlesCommand(CommandType cmd,
								  void */*calldata*/, XFE_CommandInfo*)
{
	if (cmd == xfeCmdStopLoading)
		return True;
	else
		return False;
}

void
XFE_DownloadFrame::activate_cb(Widget w, XtPointer clientData, XtPointer)
{
	CommandType cmd = Command::intern(XtName(w));
	XFE_DownloadFrame *obj = (XFE_DownloadFrame*)clientData;

	if (obj->handlesCommand(cmd) && obj->isCommandEnabled(cmd))
		obj->doCommand(cmd);
}

void
XFE_DownloadFrame::allConnectionsComplete()
{
	D( printf ("in allConnectionsComplete\n");)
	XP_InterruptContext(m_context);

	delete_response();
}

void
XFE_DownloadFrame::setAddress(char *address)
{
	XFE_DownloadView *v = (XFE_DownloadView*)m_view;

	v->setAddress(address);
}

void
XFE_DownloadFrame::setDestination(char *destination)
{
	XFE_DownloadView *v = (XFE_DownloadView*)m_view;

	v->setDestination(destination);
}
//////////////////////////////////////////////////////////////////////////
XFE_Logo *
XFE_DownloadFrame::getLogo()
{
	XFE_Logo *			logo = NULL;
	XFE_DownloadView *	view = (XFE_DownloadView *) getView();
	
	if (view)
	{
		logo = view->getLogo();
	}

	return logo;
}
//////////////////////////////////////////////////////////////////////////
void
XFE_DownloadFrame::configureLogo()
{
}

//////////////////////////////////////////////////////////////////////////
//
// XFE_DownloadView
//
//////////////////////////////////////////////////////////////////////////
XFE_DownloadView::XFE_DownloadView(XFE_Component *toplevel, Widget parent,
								   XFE_View *parent_view, MWContext *context)
	: XFE_View (toplevel, parent_view, context)
{
	Widget form;
	Widget   pane = 0;
	Widget    saving_label, url_label;
	Arg av[10];
	int ac;

	form = XtVaCreateWidget("topArea",
							xmFormWidgetClass,
							parent,
							XmNrightAttachment,		XmATTACH_FORM,
							XmNtopAttachment,		XmATTACH_FORM,
							XmNleftAttachment,		XmATTACH_FORM,
							XmNbottomAttachment,	XmATTACH_NONE,
							NULL);

	CONTEXT_DATA(m_contextData)->top_area = form;
	

	m_logo = new XFE_Logo((XFE_Frame *) toplevel,form,"logo");
	
	pane = XtVaCreateManagedWidget("pane",
								   xmFormWidgetClass,
								   form,
								   NULL);
	url_label = XtVaCreateManagedWidget("downloadURLLabel",
										xmLabelGadgetClass,
										pane,
										NULL);
	ac = 0;
	XtSetArg (av [ac], XmNeditable, False); ac++;
	m_url_value = fe_CreateTextField (pane, "downloadURLValue", av, ac);
	XtManageChild(m_url_value);

	saving_label = XtVaCreateManagedWidget("downloadFileLabel",
										   xmLabelGadgetClass,
										   pane,
										   NULL);
	ac = 0;
	XtSetArg (av [ac], XmNeditable, False); ac++;
	m_saving_value = fe_CreateTextField(pane, "downloadFileValue", av, ac);
	XtManageChild(m_saving_value);

	/* =======================================================================
	   Attachments.  This has to be done after the widgets are created,
	   since they're interdependent, sigh.
	   =======================================================================
	   */
	XtVaSetValues(url_label,
				  XmNleftAttachment, XmATTACH_FORM,
				  XmNtopAttachment, XmATTACH_FORM,
				  0);
	XtVaSetValues(m_url_value,
				  XmNtopAttachment, XmATTACH_OPPOSITE_WIDGET,
				  XmNtopWidget, url_label,
				  XmNrightAttachment, XmATTACH_FORM,
				  XmNleftAttachment, XmATTACH_WIDGET,
				  XmNleftWidget, url_label,
				  0);
	XtVaSetValues(saving_label,
				  XmNleftAttachment, XmATTACH_FORM,
				  XmNtopAttachment, XmATTACH_WIDGET,
				  XmNtopWidget, m_url_value,
				  0);
	XtVaSetValues(m_saving_value,
				  XmNtopAttachment, XmATTACH_OPPOSITE_WIDGET,
				  XmNtopWidget, saving_label,
				  XmNleftAttachment, XmATTACH_WIDGET,
				  XmNleftWidget, saving_label,
				  XmNrightAttachment, XmATTACH_FORM,
				  0);
	XP_ASSERT(XfeWidth(url_label) > 0 && XfeWidth(saving_label) > 0);
	if (XfeWidth(url_label) < XfeWidth(saving_label))
		XtVaSetValues (url_label, XmNwidth, XfeWidth(saving_label), 0);
	else
		XtVaSetValues (saving_label, XmNwidth, XfeWidth(url_label), 0);
	
	XtVaSetValues (url_label, XmNheight, XfeHeight(m_url_value), 0);
	XtVaSetValues (saving_label, XmNheight, XfeHeight(m_saving_value), 0);

	XtVaSetValues (pane,
				   XmNtopAttachment, XmATTACH_FORM,
				   XmNleftAttachment, XmATTACH_FORM,
				   XmNbottomAttachment, XmATTACH_FORM,
				   XmNrightAttachment, XmATTACH_WIDGET,
				   XmNrightWidget, m_logo->getBaseWidget(),
				   0);
	XtVaSetValues (m_logo->getBaseWidget(),
				   XmNtopAttachment, XmATTACH_FORM,
				   XmNrightAttachment, XmATTACH_FORM,
				   XmNleftAttachment, XmATTACH_NONE,
				   XmNbottomAttachment, XmATTACH_NONE,
				   0);

	m_logo->show();
	setBaseWidget(form);
}

XFE_DownloadView::~XFE_DownloadView()
{
	// nothing to do here.
}

void
XFE_DownloadView::setAddress(char *address)
{
	fe_SetTextFieldAndCallBack(m_url_value, address);
}

void
XFE_DownloadView::setDestination(char *destination)
{
	fe_SetTextFieldAndCallBack(m_saving_value, destination);
}

XFE_Logo *
XFE_DownloadView::getLogo()
{
	return m_logo;
}

MWContext*
fe_showDownloadWindow(Widget toplevel, XFE_Frame *parent_frame)
{
	XFE_DownloadFrame *frame = new XFE_DownloadFrame(toplevel, parent_frame);

	frame->show();

	return frame->getContext();
}


/* here is the stuff ripped out of commands.c, which is used to actually
   set up the stream.  It needs to be here instead of commands.c because
   we have do set values of the text fields, through member functions. */
extern char **fe_encoding_extensions; /* gag.  used by mkcache.c. */

static struct save_as_data *
make_save_as_data (MWContext *context, Boolean allow_conversion_p,
		   int type, URL_Struct *url, const char *output_file)
{
  const char *file_name;
  struct save_as_data *sad;
  FILE *file = NULL;
  char *suggested_name = 0;
  Boolean use_dialog_p = False;
  Boolean exists_p = False;
  Boolean nuke_p = True;
  const char *address = url ? url->address : 0;
  const char *content_type = url ? url->content_type : 0;
  const char *content_encoding = url ? url->content_encoding : 0;
  const char *title;
  char buf [255];

  if (url)
    {
#ifdef MOZ_MAIL_NEWS
      if (!url->content_name)
	url->content_name = MimeGuessURLContentName(context, url->address);
#endif
      if (url->content_name)
    address = url->content_name;
    }

  if (output_file)
    file_name = output_file;
  else
    {
      Boolean really_allow_conversion_p = allow_conversion_p;
      if (content_type &&
	  (!strcasecomp (content_type, UNKNOWN_CONTENT_TYPE) || 
	   !strcasecomp (content_type, INTERNAL_PARSER)))
	content_type = 0;

      if (content_type && !*content_type) content_type = 0;
      if (content_encoding && !*content_encoding) content_encoding = 0;
      if (content_type && content_encoding)
	PR_snprintf (buf, sizeof (buf), XP_GetString(XFE_SAVE_AS_TYPE_ENCODING),
		 content_type, content_encoding);
      else if (content_type)
	PR_snprintf (buf, sizeof (buf),
		XP_GetString(XFE_SAVE_AS_TYPE), content_type);
      else if (content_encoding)
	PR_snprintf (buf, sizeof (buf),
		XP_GetString(XFE_SAVE_AS_ENCODING), content_encoding);
      else
	PR_snprintf (buf, sizeof (buf), XP_GetString(XFE_SAVE_AS));
      title = buf;

      if (fe_encoding_extensions)
	{
	  int L = strlen (address);
	  int i = 0;
	  while (fe_encoding_extensions [i])
	    {
	      int L2 = strlen (fe_encoding_extensions [i]);
	      if (L2 < L &&
		  !strncmp (address + L - L2,
			    fe_encoding_extensions [i],
			    L2))
		{

		  /* The URL ends in ".Z" or ".gz" (or whatever.)
		     Take off that extension, and ask netlib what the type
		     of the resulting file is - if it is text/html or
		     text/plain (meaning it ends in ".html" or ".htm" or
		     ".txt" or god-knows-what-else) then (and only then)
		     strip off the .Z or .gz.  That we need to check the
		     extension like that is a kludge, but since we haven't
		     opened the URL yet, we don't have a content-type to
		     know for sure.  And it's just for the default file name
		     anyway...
		   */
#ifdef NEW_DECODERS
		  NET_cinfo *cinfo;

		  /* If the file ends in .html.Z, the suggested file
		     name is .html, but we will allow it to be saved
		     as .txt (uncompressed.)

		     If the file ends in .ps.Z, the suggested file name
		     is .ps.Z, and we will ignore a selection which says
		     to save it as text. */
		  really_allow_conversion_p = False;

		  suggested_name = strdup (address);
		  suggested_name [L - L2] = 0;
		  /* don't free cinfo. */
		  cinfo = NET_cinfo_find_type (suggested_name);
		  if (cinfo &&
		      cinfo->type &&
		      (!strcasecomp (cinfo->type, TEXT_PLAIN) ||
		       !strcasecomp (cinfo->type, TEXT_HTML) ||
		       !strcasecomp (cinfo->type, TEXT_MDL) ||
		       /* always treat unknown content types as text/plain */
		       !strcasecomp (cinfo->type, UNKNOWN_CONTENT_TYPE)))
		    {
		      /* then that's ok */
		      really_allow_conversion_p = allow_conversion_p;
		      break;
		    }

		  /* otherwise, continue. */
		  free (suggested_name);
		  suggested_name = 0;
#else  /* !NEW_DECODERS */
		  suggested_name = strdup (address);
		  suggested_name [L - L2] = 0;
		  break;
#endif /* !NEW_DECODERS */
		}
	      i++;
	    }
	}

      file_name = fe_ReadFileName (context, title,
				   (suggested_name ? suggested_name : address),
				   False,
				   (really_allow_conversion_p ? &type : 0));

      if (suggested_name)
	free ((char *) suggested_name);

      use_dialog_p = True;
    }

  if (! file_name)
    return 0;

#ifdef EDITOR 
  if (context->type == MWContextEditor) {
	  // NOTE:  let's check to make sure we can write into this file/directory
	  //        before we start nuking data...
	  //
	  if ( !XFE_StatReadWrite(file_name, True, False) ) {
		  // NOTE:  we can't write into this directory or file... [ PUNT ]
		  //
		  char buf [1024];
		  PR_snprintf (buf, sizeof (buf),
					   XP_GetString(XFE_ERROR_READ_ONLY), file_name);
		  fe_perror (context, buf);
		  
		  return 0;
	  }
  }
#endif
  
  /* If the file exists, confirm overwriting it. */
  {
    XP_StatStruct st;
    if (!stat (file_name, &st))
      {
	char *bp;
	int size;

	exists_p = True;

	size = XP_STRLEN (file_name) + 1;
	size += XP_STRLEN (fe_globalData.overwrite_file_message) + 1;
	bp = (char *) XP_ALLOC (size);
	if (!bp) return 0;
	PR_snprintf (bp, size,
			fe_globalData.overwrite_file_message, file_name);
	if (!FE_Confirm (context, bp))
	  {
	    XP_FREE (bp);
	    return 0;
	  }
	XP_FREE (bp);
      }
  }

#ifdef EDITOR 
  if (exists_p && (context->type == MWContextEditor)) {
	  // NOTE:  don't open the file or we'll destroy the ability to 
	  //        create the backup file in EDT...
	  //
	  nuke_p = False;
  }
#endif

  if (nuke_p) {
	  file = fopen (file_name, "w");
  }

  if (!file && nuke_p)
    {
      char buf [1024];
      PR_snprintf (buf, sizeof (buf),
		    XP_GetString(XFE_ERROR_OPENING_FILE), file_name);
      fe_perror (context, buf);
      sad = 0;
    }
  else
    {
      sad = (struct save_as_data *) malloc (sizeof (struct save_as_data));
      sad->context = context;
      sad->name = (char *) file_name;
      sad->file = file;
      sad->type = type;
      sad->done = NULL;
      sad->insert_base_tag = FALSE;
      sad->use_dialog_p = use_dialog_p;
      sad->content_length = 0;
      sad->bytes_read = 0;
      sad->url = url;
    }
  return sad;
}

static void
fe_save_as_complete (PrintSetup *ps)
{
  MWContext *context = (MWContext *) ps->carg;

  fclose(ps->out);

  fe_LowerSynchronousURLDialog (context);

  free (ps->filename);
  NET_FreeURLStruct (ps->url);
}


static int
fe_save_as_stream_write_method (NET_StreamClass *stream, const char *str, int32 len)
{
  struct save_as_data *sad = (struct save_as_data *) stream->data_object;
  fwrite ((char *) str, 1, len, sad->file);

  sad->bytes_read += len;

  if (sad->content_length > 0)
    FE_SetProgressBarPercent (sad->context,
			      (sad->bytes_read * 100) /
			      sad->content_length);
#if 0
  /* Oops, this makes the size twice as large as it should be. */
  FE_GraphProgress (sad->context, 0 /* #### url_s */,
		    sad->bytes_read, len, sad->content_length);
#endif
  return 1;
}

static unsigned int
fe_save_as_stream_write_ready_method (NET_StreamClass *stream)
{
  return(MAX_WRITE_READY);
}

static void
fe_save_as_stream_complete_method (NET_StreamClass *stream)
{
  struct save_as_data *sad = (struct save_as_data *) stream->data_object;
  fclose (sad->file);
  if (sad->done)
    (*sad->done)(sad);
  sad->file = 0;
  if (sad->name)
    {
      free (sad->name);
      sad->name = 0;
    }
  if (sad->use_dialog_p)
    fe_LowerSynchronousURLDialog (sad->context);

  FE_GraphProgressDestroy (sad->context, 0 /* #### url */,
			   sad->content_length, sad->bytes_read);
  NET_RemoveURLFromCache(sad->url); /* cleanup */
  NET_RemoveDiskCacheObjects(0); /* kick the cache to notice the cleanup */
  free (sad);
  return;
}

static void
fe_save_as_stream_abort_method (NET_StreamClass *stream, int /*status*/)
{
  struct save_as_data *sad = (struct save_as_data *) stream->data_object;
  fclose (sad->file);
  sad->file = 0;
  if (sad->name)
    {
      if (!unlink (sad->name))
	{
#if 0
	  char buf [1024];
	  PR_snprintf (buf, sizeof (buf),
			XP_GetString(XFE_ERROR_DELETING_FILE), sad->name);
	  fe_perror (sad->context, buf);
#endif
	}
      free (sad->name);
      sad->name = 0;
    }
  if (sad->use_dialog_p)
    fe_LowerSynchronousURLDialog (sad->context);

  FE_GraphProgressDestroy (sad->context, 0 /* #### url */,
			   sad->content_length, sad->bytes_read);
  /* We do *not* remove the URL from the cache at this point. */
  free (sad);
}

/* Creates and returns a stream object which writes the data read to a
   file.  If the file has not been prompted for / opened, it prompts the
   user.
 */
extern "C" NET_StreamClass *
fe_MakeSaveAsStream (int /*format_out*/, void */*data_obj*/,
		     URL_Struct *url_struct, MWContext *context)
{
	struct save_as_data *sad;
	NET_StreamClass* stream;
	
	if (url_struct->fe_data)
		{
			sad = (struct save_as_data*)url_struct->fe_data;
		}
	else
		{
			Boolean text_p = (url_struct->content_type &&
							  (!strcasecomp (url_struct->content_type, TEXT_HTML) ||
							   !strcasecomp (url_struct->content_type, TEXT_MDL) ||
							   !strcasecomp (url_struct->content_type, TEXT_PLAIN)));
			sad = make_save_as_data (context, text_p, fe_FILE_TYPE_HTML, url_struct,
									 0);
			if (! sad) return 0;
		}

	url_struct->fe_data = 0;
	
	stream = (NET_StreamClass *) calloc (sizeof (NET_StreamClass), 1);
	if (!stream) return 0;
	
	stream->name           = "SaveAs";
	stream->complete       = fe_save_as_stream_complete_method;
	stream->abort          = fe_save_as_stream_abort_method;
	stream->put_block      = fe_save_as_stream_write_method;
	stream->is_write_ready = fe_save_as_stream_write_ready_method;
	stream->data_object    = sad;
	stream->window_id      = context;
	
	if (sad->insert_base_tag && XP_STRCMP(url_struct->content_type, TEXT_HTML)
		== 0)
		{
			/*
			** This is here to that any relative URL's in the document
			** will continue to work even though we are moving the document
			** to another world.
			*/
			fe_save_as_stream_write_method(stream, "<BASE HREF=", 11);
			fe_save_as_stream_write_method(stream, url_struct->address,
										   XP_STRLEN(url_struct->address));
			fe_save_as_stream_write_method(stream, ">\n", 2);
		}

	sad->content_length = url_struct->content_length;
	FE_SetProgressBarPercent (context, -1);
    if( url_struct->server_can_do_byteranges || url_struct->server_can_do_restart )
        url_struct->must_cache = TRUE;
#if 0
	/* Oops, this makes the size twice as large as it should be. */
	FE_GraphProgressInit (context, url_struct, sad->content_length);
#endif
	
	if (sad->use_dialog_p)
		{
			/* make sure it is safe to open a new context */
			if (NET_IsSafeForNewContext(url_struct))
				{
					/* create a download frame here */
					XFE_Frame *parent;
					XFE_DownloadFrame *new_frame;
					MWContext *new_context;

					parent = ViewGlue_getFrame(XP_GetNonGridContext(context));

					new_frame = new XFE_DownloadFrame(XtParent(parent->getBaseWidget()), parent);
					new_context = new_frame->getContext();

					/* Set the values for location and filename */
					if (url_struct->address)
						new_frame->setAddress(url_struct->address);
					if (sad && sad->name)
						new_frame->setDestination(sad->name);

					new_frame->show();

					/* Set the url_count and enable all animation */
					CONTEXT_DATA (new_context)->active_url_count = 1;
					fe_StartProgressGraph (new_context);
	  
					/* register this new context associated with this stream
					 * with the netlib
					 */
					NET_SetNewContext(url_struct, new_context, fe_url_exit);

					/* Change the fe_data's context */
					sad->context = new_context;
				}
			else
				fe_RaiseSynchronousURLDialog (context, CONTEXT_WIDGET (context),
											  "saving");
		}

  return stream;
}

extern "C" NET_StreamClass *
fe_MakeSaveAsStreamNoPrompt (int format_out, void *data_obj,
			     URL_Struct *url_struct, MWContext *context)
{
  struct save_as_data *sad;

  assert (context->prSetup);
  if (! context->prSetup)
    return 0;

  sad = (struct save_as_data *) malloc (sizeof (struct save_as_data));
  sad->context = context;
  sad->name = strdup (context->prSetup->filename);
  sad->file = context->prSetup->out;
  sad->type = fe_FILE_TYPE_HTML;
  sad->done = 0;
  sad->insert_base_tag = FALSE;
  sad->use_dialog_p = FALSE;

  url_struct->fe_data = sad;

  sad->content_length = url_struct->content_length;
  FE_GraphProgressInit (context, url_struct, sad->content_length);

  return fe_MakeSaveAsStream (format_out, data_obj, url_struct, context);
}

static void
fe_save_as_nastiness (MWContext *context, URL_Struct *url,
		      struct save_as_data *sad,int synchronous)
{
  SHIST_SavedData saved_data;
  assert (sad);
  if (! sad) return;

  /* Make sure layout saves the current state of form elements. */
  LO_SaveFormData(context);

  /* Hold on to the saved data. */
  XP_MEMCPY(&saved_data, &url->savedData, sizeof(SHIST_SavedData));

  /* make damn sure the form_data slot is zero'd or else all
   * hell will break loose
   */
  XP_MEMSET (&url->savedData, 0, sizeof (SHIST_SavedData));

  switch (sad->type)
    {
    case fe_FILE_TYPE_TEXT:
    case fe_FILE_TYPE_FORMATTED_TEXT:
      {
	PrintSetup p;
	fe_RaiseSynchronousURLDialog (context, CONTEXT_WIDGET (context),
				      "saving");
	XL_InitializeTextSetup (&p);
	p.out = sad->file;
	p.filename = sad->name;
	p.url = url;
	free(sad);
	p.completion = fe_save_as_complete;
	p.carg = context;
	XL_TranslateText (context, url, &p);
	fe_await_synchronous_url (context);
	break;
      }
    case fe_FILE_TYPE_PS:
      {
	PrintSetup p;
	fe_RaiseSynchronousURLDialog (context, CONTEXT_WIDGET (context),
				      "saving");
	XL_InitializePrintSetup (&p);
	p.out = sad->file;
	p.filename = sad->name;
	p.url = url;
	free (sad);
	p.completion = fe_save_as_complete;
	p.carg = context;
	XL_TranslatePostscript (context, url, &saved_data, &p);
	fe_await_synchronous_url (context);
	break;
      }

    case fe_FILE_TYPE_HTML:
      {
        if (!synchronous)
            fe_GetSecondaryURL (context, url, FO_CACHE_AND_SAVE_AS, sad, FALSE);
        else
            fe_GetSynchronousURL (context,
                                  CONTEXT_WIDGET (context),
                                  "saving",
                                  url,
                                  FO_CACHE_AND_SAVE_AS,
                                  (void *)sad);
	break;
      }
#ifdef SAVE_ALL
    case fe_FILE_TYPE_HTML_AND_IMAGES:
      {
	char *addr = strdup (url->address);
	char *filename = sad->name;
	FILE *file = sad->file;
	NET_FreeURLStruct (url);
	free (sad);
	SAVE_SaveTree (context, addr, filename, file);
	break;
      }
#endif /* SAVE_ALL */
    default:
      abort ();
    }
}


void
fe_SaveURL (MWContext *context, URL_Struct *url)
{
  struct save_as_data *sad;
  Boolean text_p = True;

  assert (url);
  if (!url) return;

#if 0
  if (url->content_type && *url->content_type)
    {
      if (! (!strcasecomp (url->content_type, TEXT_HTML) ||
	     !strcasecomp (url->content_type, TEXT_MDL) ||
	     !strcasecomp (url->content_type, TEXT_PLAIN)))
	text_p = False;
    }
#endif

  sad = (struct save_as_data*)make_save_as_data (context, text_p, fe_FILE_TYPE_HTML, url, 0);
  if (sad)
    fe_save_as_nastiness (context, url, sad, FALSE);
  else
    NET_FreeURLStruct (url);
}

void
fe_SaveSynchronousURL (MWContext *context, URL_Struct *url,const char *fname)
{
  struct save_as_data *sad;
  Boolean text_p = True;

  assert (url);
  if (!url) return;

  sad = (struct save_as_data*)make_save_as_data (context, text_p, fe_FILE_TYPE_HTML, url, fname);
  if (sad) {
      sad->use_dialog_p=FALSE;
      if (fname && strlen(fname)>0)
          sad->name = strdup((char *)fname);
      fe_save_as_nastiness (context, url, sad, TRUE);
  }
  else {
      NET_FreeURLStruct (url);
  }
}

#ifdef EDITOR

extern "C" Boolean
fe_SaveAsDialog(MWContext* context, char* buf, int type)
{
    URL_Struct*          url;
    struct save_as_data* sad;
	
    url = SHIST_CreateURLStructFromHistoryEntry(
					context,
					SHIST_GetCurrent(&context->hist)
					);

    if (url) 
		{
			sad = (struct save_as_data*)make_save_as_data(context, FALSE, type, url, 0);
			
			if (sad) 
				{
					// NOTE:  check to see if they opened the file...
					//
					if (sad->file) {
						fclose(sad->file);
					}
					
					strcpy(buf, sad->name);
					
					free(sad);
					
					return TRUE;
				}
		}
	
    return FALSE;
}

#endif

extern "C" void
fe_save_as_action (Widget widget, MWContext *context,
                   String *av, Cardinal *ac)
{
  /* See also fe_open_url_action() */
  XP_ASSERT (context);
  if (!context) return;
  fe_UserActivity (context);
  if (*ac && av[*ac-1] && !strcmp (av[*ac-1], "<remote>"))
    (*ac)--;
  if ((*ac == 1 || *ac == 2) && av[0])
    {
      URL_Struct *url;
      struct save_as_data *sad;
      char *file;
      int type = fe_FILE_TYPE_HTML;

      if (*ac == 2 && av[1])
	{
	  if (!XP_STRCASECMP(av[1], "source"))
	    type = fe_FILE_TYPE_HTML;
	  else if (!XP_STRCASECMP(av[1], "html"))
	    type = fe_FILE_TYPE_HTML;
#ifdef SAVE_ALL
	  else if (!XP_STRCASECMP(av[1], "tree"))
	    type = fe_FILE_TYPE_HTML_AND_IMAGES;
	  else if (!XP_STRCASECMP(av[1], "html-and-images"))
	    type = fe_FILE_TYPE_HTML_AND_IMAGES;
	  else if (!XP_STRCASECMP(av[1], "source-and-images"))
	    type = fe_FILE_TYPE_HTML_AND_IMAGES;
#endif /* SAVE_ALL */
	  else if (!XP_STRCASECMP(av[1], "text"))
	    type = fe_FILE_TYPE_TEXT;
	  else if (!XP_STRCASECMP(av[1], "formatted-text"))
	    type = fe_FILE_TYPE_FORMATTED_TEXT;
	  else if (!XP_STRCASECMP(av[1], "ps"))
	    type = fe_FILE_TYPE_PS;
	  else if (!XP_STRCASECMP(av[1], "postscript"))
	    type = fe_FILE_TYPE_PS;
	  else
	    {
	      fprintf (stderr, 
		       XP_GetString(XFE_COMMANDS_SAVE_AS_USAGE),
		       fe_progname);
	      return;
	    }
	}

      url = SHIST_CreateWysiwygURLStruct (context,
                                          SHIST_GetCurrent (&context->hist));
      file = strdup (av[0]);
      sad = make_save_as_data (context, False, type, url, file);
      if (sad)
	fe_save_as_nastiness (context, url, sad, TRUE);
      else
	NET_FreeURLStruct (url);
    }
  else if (*ac > 2)
    {
		fprintf (stderr, 
				 XP_GetString(XFE_COMMANDS_SAVE_AS_USAGE_TWO),
				 fe_progname);
    }
  else
    {
      fe_save_top_frame_as_cb (widget, (XtPointer)context, (XtPointer)0);
    }
}

