/**
 * 	Samples/TcpIp/Http/GetFile.c
 * 	Z-world, 2002
 *
 * 	This sample allows one file to be uploaded with FTP.  The page is
 * 	then available as a web page.  This sample is rather large.  It runs
 * 	both HTTP and FTP servers.  See DATAFILE_NAME for the file name,
 * 	seen from both servers.
 *
 * 	This file is added dynamically (at runtime) by calling SSPEC's
 * 	sspec_addfsfile().  The file DATAFILE_NAME is created empty when
 * 	the program starts, and can be updated only through FTP.
 * 	Attempting to upload a file with a different name gives error
 * 	"550 File Not Found."  If the file is too big for the File System,
 * 	the client sees "452 Transfer incomplete" message.
 *
 * 	Always use the BINARY transfer mode.  Many FTP clients default to
 * 	ASCII mode.  This affects the newline chars.  If you upload a file,
 * 	then download it (PUT and GET), the size will be different.  The
 * 	file's contents have also been corrupted.
 *
 * 	From an FTP client program, you can display the file's latest contents
 * 	by doing "get data.txt -" , which sends it to STDOUT rather than
 * 	saving it locally.
 *
 * 	This application uses the file system, web server, FTP server and
 * 	SSPEC library.  This application is large, and uses two TCP server
 * 	libraries.  There are many configuration options.
 *
 * 	Since the file is stored using the File System, make sure there is
 * 	enough space there to store it!  (See below on adjust "RabbitBios.c")
 * 	When the FS refuses, fwrite() will return an error, and we return
 * 	an error indication back to the FTP server library.
 *
 * 	OVERRIDING FTP HANDLER FUNCTIONS:
 * 	=================================
 *
 * 	This program overrides many of the default FTP action handlers.  They
 * 	have names like my_ftp_write() and my_ftp_close().  The FTP library
 * 	calls these handlers with specific parameters.  In this application,
 * 	not all the parameters are used.  A limitation of DynamicC is that
 * 	a function pointer can't be given a parameter prototype.  The unused
 * 	parameters is not a mistake.
 *
 *
 * 	--------------->>>  NOTE  <<<---------------
 *
 * 	(0) If you have problems accessing memory for the file system, then
 * 		recompile the bios (^Y) and run Samples\FileSystem\FS2INFO.C
 *
 * 	(1) Upon startup, if you get the error message:
 *
 * 		"The specified device (LX# 0) does not exist.  Change LX_2_USE."
 *
 * 		Then you need to set aside memory for the File System.  This can
 * 		be done in one of two ways, depending on which memory area you
 * 		want the file system to use.  Modify your DCROOT\BIOS\RabbitBios.c
 * 		file with ONE of these changes:
 *
 * 			#define XMEM_RESERVE_SIZE 0x4000L // FLASH: Amount of space
 * 	..or..
 * 			#define FS2_RAM_RESERVE  4			 // SRAM: Number of 4096-byte blocks
 *
 * 	(2) If the server has problems uploading any files, please force a
 *			reformat.  Can do this with MY_FORCE_FORMAT define.  You might have
 * 		to run SAMPLES\FileSystem\FS2DEMO1.C to format _and_ initialize.
 *
 ************************************************************************/
#class auto

/***********************************
 * Configuration                   *
 * -------------                   *
 * All fields in this section must *
 * be altered to match your local  *
 * network settings.               *
 ***********************************/

/*
 * Pick the predefined TCP/IP configuration for this sample.  See
 * LIB\TCPIP\TCP_CONFIG.LIB for instructions on how to set the
 * configuration.
 */
#define TCPCONFIG 1

/*
 * Web server configuration
 */

/*
 * One HTTP server, and one FTP server
 */
#define HTTP_MAXSERVERS 1
#define MAX_TCP_SOCKET_BUFFERS 3

/** OVERRIDE **/
#define SAUTH_MAXUSERS 	2


/**
 *  FS_FORCE_FORMAT - Define non-zero to wipe clean the file system, or 0 to
 * 		use whatever is there.  FS2 is very good at  noticing RAM always
 *  		needs formatting, but Flash can hold its contents.
 */
#define  MY_FORCE_FORMAT	1


/**
 *   FTP_USER_NAME, FTP_USER_PASSWORD - Name and password for FTP user that
 * 	can update the data file.  These are compile-time constants, and the
 * 	password is part of the image in plaintext.  Name size is limited by
 * 	the SAUTH_MAXNAME define.  See ftp_set_anonymous() for how to set an
 * 	anonymous user.
 */
#define  FTP_USER_NAME  	 "samwise"
#define  FTP_USER_PASSWORD  "shire"

/**
 * 	FTP_WRITABLE_FILES - Permit some users to update files on the FTP site.
 * 	 Our init code calls sauth_setwritaccess() for those users.
 */
#define  FTP_WRITABLE_FILES 	1

// #define  FTP_DEBUG
// #define  FTP_VERBOSE


/**
 *   DATAFILE_NAME, DATA_FILENUMBER - The name and file number of where to
 * 	store the "data.txt" file.  Name shows up for web and FTP servers.
 * 	Choose some number. The high-byte can select an FS2 extent explicitly.
 */
#define  DATAFILE_NAME 	"data.txt"
#define  DATAFILE_NUMBER	(FileNumber)99



/**
 * 	FS2_USER_PROGRAM_FLASH - Set aside this many kilobytes in the program
 * 		flash.  You will also need to edit BIOS\RABBITBIOS.C to set an
 * 		appropriate value to XMEM_RESERVE_SIZE.  The definition here may
 * 		be larger or smaller - the minimum of both specifications will
 * 		be used.
 */
#define FS2_USE_PROGRAM_FLASH 	20

// #define FS_DEBUG
// #define FS_DEBUG_FLASH


//#define ZSERVER_DEBUG

// In addition, you can edit BIOS\RABBITBIOS.C to set a value for
// FS2_RAM_RESERVE to reserve some RAM for use by the filesystem.


/********************************
 * End of configuration section *
 ********************************/

#memmap xmem
#use "fs2.lib"  		//  Always pull in FS MkII.
#use "errno.lib"
#use "dcrtcp.lib"
#use "http.lib"
#use "ftp_server.lib"

#ximport "samples/tcpip/http/pages/getfile.html"   index_html
#ximport "samples/tcpip/http/pages/rabbit1.gif"    rabbit1_gif
#ximport "samples/tcpip/http/GetFile.c"            sourcecode_c

/**
 *   Set by LX_2_USE macro, used by fs_set_lx()
 */
FSLXnum	fs_ext;


/**
 *  In this case the .html is not the first type in the type table.
 *  This causes the default (no extension) to assume the shtml_handler().
 */

/* the default for unknown extensions must be first */
const HttpType http_types[] =
{
   { ".shtml", "text/html", shtml_handler}, // ssi
   { ".html", "text/html", NULL},           // html
   { ".c", "text/plain", NULL},             // C code
   { ".txt", "text/plain", NULL},           // generic text
   { ".cgi", "", NULL},                     // cgi
   { ".gif", "image/gif", NULL}
};


/* -------------------------------------------------------------------- */


const HttpSpec http_flashspec[] = 
{
   { HTTPSPEC_FILE,  "/",              index_html,    NULL, 0, NULL, NULL},
   	/*  "/index.html" and "data.txt" added dynamically  */
   { HTTPSPEC_FILE,  "/rabbit1.gif",   rabbit1_gif,   NULL, 0, NULL, NULL},
   { HTTPSPEC_FILE,  "/GetFile.c",  sourcecode_c,   NULL, 0, NULL, NULL},
};



/**
 * 	Replacement for default FTP write handler.  For DATAFILE_NAME,
 * 	updating it is permitted (this is checked here only).  Must do this
 *  	since the "File updatefile" structure is global.  If it were part
 * 	of state, then could handle more....
 *
 * 	Make sure writes are contiguous.  To save wear-and-tear on the file
 * 	storage (which could be RAM or Flash), close is done in my_ftp_close()
 * 	only.  File is deleted first, otherwise we'll just overwrite the file
 * 	bytes.
 */
char 		uf_opened;
File 		updatefile;

int my_ftp_write(int fd, char *buf, long offset, int len)
{
	auto ServerSpec * sp;
	auto long 		written;
	auto long 		filesize;
	static long 	last_offset;

	sp = & server_spec[ fd ];
	if( (FileNumber)sp->data != DATAFILE_NUMBER || sp->type != SSPEC_FSFILE ) {
			/* only a certain file is writable! */
			printf( "Error: Only file '%s' can be changed!\n", DATAFILE_NAME );
			return -1;
	}

	if( offset == 0 ) {
		if( uf_opened ) {
			/* ERROR: file already opened, can't open again! */
			return -2;
		}
		/*
		 * 	Delete file before updating it.  If we don't, then the fopen_wr()
		 * 	will overwrite what is there, not replace it (thus, file will not
		 * 	shrink in size).  fcreate() will open for writing the empty file.
		 * 	fcreate() will fail if the file already exists....
		 */
		fdelete( DATAFILE_NUMBER );
		if( fcreate( &updatefile, DATAFILE_NUMBER ) != 0 ) {
			/*  Uugghh... file not available.. */
			return -1;
		}
		last_offset = 0;
		uf_opened = TRUE;
	}
	if( offset  != last_offset ) {
		/*  Enforce append only!  CLOSE will now close file. */
		/*  This prevents resuming an ealier download (perhaps bad idea). */
		// if( !uf_opened )
		//   return -2;
		//  last_offset = offset;
		return -2;
	}

	written = fwrite( & updatefile, buf, len );
	if( written != len ) {
		/*
		 *  If FileSystem isn't accepting anymore, then abort the xfer.
		 *  FTP server will call my_ftp_close() to close off the file handle.
		 *  You might have to reformat the extent to make things work.
		 */
		printf( "Error: file system refusing write request, offset=%ld, errno=%d\n", offset, errno );
		return FTP_ERR_WRITING;
	}

	last_offset += len;
	return( (int) written );
}   /* end my_ftp_write() */


/**
 * 	Stop accessing a file.  If the file is our special writing file, then
 * 	call FS2 to close it.  Otherwise call the default handler (kinda like
 * 	overriding an object's method...)
 */
int my_ftp_close( int fd )
{
	if( (FileNumber)server_spec[fd].data != DATAFILE_NUMBER || !uf_opened ) {
		return ftp_dflt_close(fd);
	}

	uf_opened = FALSE;
	fclose( & updatefile );
	printf( "Note: just updated \"" DATAFILE_NAME "\"\n" );
	return 0;
}   /* end my_ftp_close() */


/*
 * (If the ftp_dflt_XXXX functions are unknown, you must upgrade to at
 *  least DCR 7.21 software.  The FTP server was improved then.)
 */
const FTPhandlers  myFtphandlers =
{		ftp_dflt_open, ftp_dflt_read, my_ftp_write, my_ftp_close,
		ftp_dflt_getfilesize, ftp_dflt_list,
		/* Following added in 7.20: */
		ftp_dflt_cd, ftp_dflt_pwd
};

/* -------------------------------------------------------------------- */

/**
 * 	Connect an SSPEC object to a named file in the file system.  It caller
 * 	wants, it can be marked writable.  First sets the FS2 extends wherein
 * 	it stores the data contents and meta-file information.
 */
void setup_fs_ftp_file( FileNumber fnumb, /*const*/ char * fname, int userID, int writable )
{
	auto File 	data_file;
	auto int 	index;

	/*
	 *  Default to use LX_2_USE extent as data and metadata for the shared
	 *  file.  When file is created, it is placed in these ex
	 */
	fs_set_lx(fs_ext, fs_ext);

	/*
	 *  The FTP and HTTP servers share a single file.  Make sure it exists.
	 * 	fcreate() will fail is already there, or succeed if not -- we
	 * 	don't care which occurs, as long as FS2 is working!
	 */
//	fdelete( fnumb );
   fcreate( & data_file, fnumb );
   fclose( & data_file );
	index = sspec_addfsfile(fname, fnumb,
									SERVER_HTTP | SERVER_FTP | (writable ? SERVER_WRITABLE : 0 ) );
	if( index < 0 ) {
		printf( "ERROR: Can't add data file \"%s\" (err=%d)!\n", fname, errno );
		exit(2);
	}

	/*  Set the owner and writing permission: */
	sspec_setuser( index, userID );
	sauth_setwriteaccess( userID, writable );

}   /* end setup_fs_ftp_file() */


/**
 * 	Connect an SSPEC object to a named file in extended memory.
 * 	Object is shared between FTP and HTTP servers, read only access.
 */
void
setup_xmem_ftp_file( /*const*/ char * fname, long xmemptr, int userID )
{
	auto int 	index;

	index = sspec_addxmemfile( fname, xmemptr,
									SERVER_HTTP | SERVER_FTP );
	if( index < 0 ) {
		printf( "ERROR: Can't add data file \"%s\" (err=%d)!\n", fname, errno );
		exit(2);
	}

	sspec_setuser( index, userID );

}   /* end setup_xmem_ftp_file() */


/**
 * 	LX_2_USE - You can use fs_get_flash_lx() (for the 2nd flash, or more
 * 		of first flash if only one installed); or fs_get_ram_lx() (for
 * 		SRAM if configured in BIOS\RABBITBIOS.C).  See manual for others.
 *  		(see 	fs_set_lx()  call below).
 * 	Abort if no place for file system.
 */
void
select_fs_extent()
{

	/*  First try Flash, then RAM if room for filesystem. */
	fs_ext = fs_get_flash_lx();
	if(0 == fs_ext) {
		fs_ext = fs_get_ram_lx();
		if( 0 == fs_ext ) {
			printf("ERROR: No extents have room for file system.\n");
			exit(2);
		}
	}

	printf("Using device LX# %d...\n", (int)fs_ext);
}   /* end setup_fs_extent() */


/* -------------------------------------------------------------------- */

void main()
{
	int 	index, ftpUserID;
	int 	rc;

	select_fs_extent();

	/*
	 *  fs_init() function performs complete consistency checks and,
	 *  if necessary, fixups for each LX.  It may take up to several
	 *  seconds to run.  It should only be called once at application
	 *  initialization time.
	 */
	rc = fs_init( 0, 0 );
	if( rc != 0 ) {
		printf( "Error: can't get filesystem library initialized.\n" );
		exit(2);
	}

	/*
	 * Format the filesystem if requested.  Note that formatting
	 * must be done _after_ the call to fs_init().
	 *
	 * This demo has compile-time constant of whether the flash should be
	 * formatted or not.
	 */
#if MY_FORCE_FORMAT
		printf( "Note: Formatting MkII File System.\n" );
		if( 0 != lx_format(fs_ext, 0) ) {
			printf( "ABORT: Can't Format MkII File System, errno=%d\n", errno );
			exit(2);
		}
#endif

	uf_opened = FALSE;
   sock_init();

   /*
    *  Create an "authorized user" into the system.  To update the file,
    *  external clients must use this name/password.  User "anonymous"
    *  can see the file, but not change its contents.
    */
	ftpUserID = sauth_adduser(FTP_USER_NAME, FTP_USER_PASSWORD, SERVER_FTP);
	if( ftpUserID < 0 ) {
		printf( "ERROR: Can't add FTP user \"" FTP_USER_NAME "\"!\n" );
		exit(2);
	}
	setup_fs_ftp_file( DATAFILE_NUMBER, DATAFILE_NAME, ftpUserID, TRUE );

	/* Put "index.html" under anonymous access. */
	ftpUserID = sauth_adduser( "anonymous", "", SERVER_FTP );
	if( ftpUserID < 0 ) {
		printf( "ERROR: Can't add anonymous FTP user!\n" );
		exit(2);
	}
	ftp_set_anonymous( ftpUserID );
	setup_xmem_ftp_file( "index.html", index_html, ftpUserID );

	/*
	 * 	Setup the FTP operation handlers.  Don't reserve the FTP command
	 * 	port.  "It's just to allow additional queued connections.  There
	 *  	is no real performance reason. [Using resserve_port()] depends
	 * 	on the application.  For HTTP servers you want the queue; for
	 *    sockets with human interaction you probably don't." (SJH)
	 */
   ftp_init(& myFtphandlers);  				// Use all default handlers.

   http_init();
	tcp_reserveport(80);
	printf( "setup complete.\n" );

   while (1) {
      http_handler();
      ftp_tick();
   }

}   /* end main() */
