summaryrefslogblamecommitdiffstats
path: root/file.c
blob: 2c0b9f9391cbefbe092b00b361e585a78ed07201 (plain) (tree)
1
2
3
4
5
6
7
8
9


                     
                   
                  
                    
                
                 
                 
                 
 



                        
                                                        
 
                    
                       
                  


                           
                                                               









                                 
                                     
                       
                 
                         
                          
                               
                                       
                          
                     











                             
 
                                                                 


                                     
                                                                    


                                         
                      
                                                                 
                  
 
                                                                      
                        
                                                                           
                                                                                      

                          
                                           

                                                                               
                                                 

                                         
                                          
         

                       
                                                                                            

                  
                                                                                                

                                                                     
                                                
           
                                                                    
                          





                                                         




                                                                                                                
                                                                                        







                                                                                   
                                  

                               
                                  
                                                                      
         


                 




                                                                      
                                                                     
 
                        
                                                                                                                                      



                                                 
                                                                                                           

                         

                                                                        
                                                                                                 





                                                                               
                                                                                                        


                                      

                              
 
 
                                        













                                                          
                     














                                                          

                    










                                                                                    
                                                                     























                                                                                            
                         
                          
                                














                                       
                       






                                      
                                                                     



                              
                                            
                                                   
                                  
                       
                                            
                                
                            



                          
                                                                                        
 
                                                                                          
                                                      
 
                       
                                    
                         
 
                             
                                                                        
                                                          
      
 






                                                                    

                 
                                                                         
                                           
                                                            
                       

                                       
                                                                              
 
                                    
 
                                   
                            
                  
 
                                                   
                         
 
                                           
                                                                                    
                                                                                        
                                 
 
                                                                                                
         
                                     
                                                                                 
                                                      
                                         
                                 
                 
 
                                          
                         
                 
 

                      
                                                                                                                                                                
                            
                    
                         

                                    
                                  
                                  
                                        
                                   
                                           


                         
 
                                                                                                                                     
 

                                                       
                                                   
                                                   
                                                               
                                                              
                                                       





                                                                               
 




                                     
                                   
                                   
                                         


                                
                                
                                         
                             
                             
                                                       
 
                                                              
 
                                                                                             
                         
                 
 
 
                                                                                                                                                                                                                            
                            
                    


















                                           
                                                                                                                                                                                                                                                                               
 























































                                                                               
                                                              
 
                                                              
 
                                                                                             
                         
                 
 
#include <unistd.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include "gettext.h"
#include <zip.h>
#include <time.h>

#include "dive.h"
#include "file.h"

/* Crazy windows sh*t */
#ifndef O_BINARY
#define O_BINARY 0
#endif

int readfile(const char *filename, struct memblock *mem)
{
	int ret, fd;
	struct stat st;
	char *buf;

	mem->buffer = NULL;
	mem->size = 0;

	fd = subsurface_open(filename, O_RDONLY | O_BINARY, 0);
	if (fd < 0)
		return fd;
	ret = fstat(fd, &st);
	if (ret < 0)
		goto out;
	ret = -EINVAL;
	if (!S_ISREG(st.st_mode))
		goto out;
	ret = 0;
	if (!st.st_size)
		goto out;
	buf = malloc(st.st_size + 1);
	ret = -1;
	errno = ENOMEM;
	if (!buf)
		goto out;
	mem->buffer = buf;
	mem->size = st.st_size;
	ret = read(fd, buf, mem->size);
	if (ret < 0)
		goto free;
	buf[ret] = 0;
	if (ret == mem->size)
		goto out;
	errno = EIO;
	ret = -1;
free:
	free(mem->buffer);
	mem->buffer = NULL;
	mem->size = 0;
out:
	close(fd);
	return ret;
}


static void zip_read(struct zip_file *file, const char *filename)
{
	int size = 1024, n, read = 0;
	char *mem = malloc(size);

	while ((n = zip_fread(file, mem + read, size - read)) > 0) {
		read += n;
		size = read * 3 / 2;
		mem = realloc(mem, size);
	}
	mem[read] = 0;
	parse_xml_buffer(filename, mem, read, &dive_table, NULL);
	free(mem);
}

static int try_to_open_zip(const char *filename, struct memblock *mem)
{
	int success = 0;
	/* Grr. libzip needs to re-open the file, it can't take a buffer */
	struct zip *zip = subsurface_zip_open_readonly(filename, ZIP_CHECKCONS, NULL);

	if (zip) {
		int index;
		for (index = 0;; index++) {
			struct zip_file *file = zip_fopen_index(zip, index, 0);
			if (!file)
				break;
			zip_read(file, filename);
			zip_fclose(file);
			success++;
		}
		subsurface_zip_close(zip);
	}
	return success;
}

static int try_to_xslt_open_csv(const char *filename, struct memblock *mem, const char *tag)
{
	char *buf;

	if (readfile(filename, mem) < 0)
		return report_error(translate("gettextFromC", "Failed to read '%s'"), filename);

	/* Surround the CSV file content with XML tags to enable XSLT
	 * parsing
	 *
	 * Tag markers take: strlen("<></>") = 5
	 */
	buf = realloc(mem->buffer, mem->size + 5 + strlen(tag) * 2);
	if (buf != NULL) {
		char *starttag = NULL;
		char *endtag = NULL;

		starttag = malloc(3 + strlen(tag));
		endtag = malloc(4 + strlen(tag));

		if (starttag == NULL || endtag == NULL) {
			/* this is fairly silly - so the malloc fails, but we strdup the error?
			 * let's complete the silliness by freeing the two pointers in case one malloc succeeded
			 *  and the other one failed - this will make static analysis tools happy */
			free(starttag);
			free(endtag);
			free(buf);
			return report_error("Memory allocation failed in %s", __func__);
		}

		sprintf(starttag, "<%s>", tag);
		sprintf(endtag, "</%s>", tag);

		memmove(buf + 2 + strlen(tag), buf, mem->size);
		memcpy(buf, starttag, 2 + strlen(tag));
		memcpy(buf + mem->size + 2 + strlen(tag), endtag, 4 + strlen(tag));
		mem->size += (5 + 2 * strlen(tag));
		mem->buffer = buf;

		free(starttag);
		free(endtag);
	} else {
		free(mem->buffer);
		return report_error("realloc failed in %s", __func__);
	}

	return 0;
}

int db_test_func(void *param, int columns, char **data, char **column)
{
	return *data[0] == '0';
}


static int try_to_open_db(const char *filename, struct memblock *mem)
{
	sqlite3 *handle;
	char dm4_test[] = "select count(*) from sqlite_master where type='table' and name='Dive' and sql like '%ProfileBlob%'";
	char shearwater_test[] = "select count(*) from sqlite_master where type='table' and name='system' and sql like '%dbVersion%'";
	int retval;

	retval = sqlite3_open(filename, &handle);

	if (retval) {
		fprintf(stderr, translate("gettextFromC", "Database connection failed '%s'.\n"), filename);
		return 1;
	}

	/* Testing if DB schema resembles Suunto DM4 database format */
	retval = sqlite3_exec(handle, dm4_test, &db_test_func, 0, NULL);
	if (!retval) {
		retval = parse_dm4_buffer(handle, filename, mem->buffer, mem->size, &dive_table);
		sqlite3_close(handle);
		return retval;
	}

	/* Testing if DB schema resembles Shearwater database format */
	retval = sqlite3_exec(handle, shearwater_test, &db_test_func, 0, NULL);
	if (!retval) {
		retval = parse_shearwater_buffer(handle, filename, mem->buffer, mem->size, &dive_table);
		sqlite3_close(handle);
		return retval;
	}

	sqlite3_close(handle);

	return retval;
}

timestamp_t parse_date(const char *date)
{
	int hour, min, sec;
	struct tm tm;
	char *p;

	memset(&tm, 0, sizeof(tm));
	tm.tm_mday = strtol(date, &p, 10);
	if (tm.tm_mday < 1 || tm.tm_mday > 31)
		return 0;
	for (tm.tm_mon = 0; tm.tm_mon < 12; tm.tm_mon++) {
		if (!memcmp(p, monthname(tm.tm_mon), 3))
			break;
	}
	if (tm.tm_mon > 11)
		return 0;
	date = p + 3;
	tm.tm_year = strtol(date, &p, 10);
	if (date == p)
		return 0;
	if (tm.tm_year < 70)
		tm.tm_year += 2000;
	if (tm.tm_year < 100)
		tm.tm_year += 1900;
	if (sscanf(p, "%d:%d:%d", &hour, &min, &sec) != 3)
		return 0;
	tm.tm_hour = hour;
	tm.tm_min = min;
	tm.tm_sec = sec;
	return utc_mktime(&tm);
}

enum csv_format {
	CSV_DEPTH,
	CSV_TEMP,
	CSV_PRESSURE
};

static void add_sample_data(struct sample *sample, enum csv_format type, double val)
{
	switch (type) {
	case CSV_DEPTH:
		sample->depth.mm = feet_to_mm(val);
		break;
	case CSV_TEMP:
		sample->temperature.mkelvin = F_to_mkelvin(val);
		break;
	case CSV_PRESSURE:
		sample->cylinderpressure.mbar = psi_to_mbar(val * 4);
		break;
	}
}

/*
 * Cochran comma-separated values: depth in feet, temperature in F, pressure in psi.
 *
 * They start with eight comma-separated fields like:
 *
 *   filename: {C:\Analyst4\can\T036785.can},{C:\Analyst4\can\K031892.can}
 *   divenr: %d
 *   datetime: {03Sep11 16:37:22},{15Dec11 18:27:02}
 *   ??: 1
 *   serialnr??: {CCI134},{CCI207}
 *   computer??: {GeminiII},{CommanderIII}
 *   computer??: {GeminiII},{CommanderIII}
 *   ??: 1
 *
 * Followed by the data values (all comma-separated, all one long line).
 */
static int try_to_open_csv(const char *filename, struct memblock *mem, enum csv_format type)
{
	char *p = mem->buffer;
	char *header[8];
	int i, time;
	timestamp_t date;
	struct dive *dive;
	struct divecomputer *dc;

	for (i = 0; i < 8; i++) {
		header[i] = p;
		p = strchr(p, ',');
		if (!p)
			return 0;
		p++;
	}

	date = parse_date(header[2]);
	if (!date)
		return 0;

	dive = alloc_dive();
	dive->when = date;
	dive->number = atoi(header[1]);
	dc = &dive->dc;

	time = 0;
	for (;;) {
		char *end;
		double val;
		struct sample *sample;

		errno = 0;
		val = strtod(p, &end); // FIXME == localization issue
		if (end == p)
			break;
		if (errno)
			break;

		sample = prepare_sample(dc);
		sample->time.seconds = time;
		add_sample_data(sample, type, val);
		finish_sample(dc);

		time++;
		dc->duration.seconds = time;
		if (*end != ',')
			break;
		p = end + 1;
	}
	record_dive(dive);
	return 1;
}

static int open_by_filename(const char *filename, const char *fmt, struct memblock *mem)
{
	/* Suunto Dive Manager files: SDE, ZIP; divelogs.de files: DLD */
	if (!strcasecmp(fmt, "SDE") || !strcasecmp(fmt, "ZIP") || !strcasecmp(fmt, "DLD"))
		return try_to_open_zip(filename, mem);

	/* CSV files */
	if (!strcasecmp(fmt, "CSV"))
		return 1;

#if ONCE_COCHRAN_IS_SUPPORTED
	/* Truly nasty intentionally obfuscated Cochran Anal software */
	if (!strcasecmp(fmt, "CAN"))
		return try_to_open_cochran(filename, mem);
#endif

	/* Cochran export comma-separated-value files */
	if (!strcasecmp(fmt, "DPT"))
		return try_to_open_csv(filename, mem, CSV_DEPTH);
	if (!strcasecmp(fmt, "TMP"))
		return try_to_open_csv(filename, mem, CSV_TEMP);
	if (!strcasecmp(fmt, "HP1"))
		return try_to_open_csv(filename, mem, CSV_PRESSURE);

	return 0;
}

static void parse_file_buffer(const char *filename, struct memblock *mem)
{
	char *fmt = strrchr(filename, '.');
	if (fmt && open_by_filename(filename, fmt + 1, mem))
		return;

	if (!mem->size || !mem->buffer)
		return;

	parse_xml_buffer(filename, mem->buffer, mem->size, &dive_table, NULL);
}

int parse_file(const char *filename)
{
	struct git_repository *git;
	const char *branch;
	struct memblock mem;
	char *fmt;

	git = is_git_repository(filename, &branch);
	if (git && !git_load_dives(git, branch))
		return 0;

	if (readfile(filename, &mem) < 0) {
		/* we don't want to display an error if this was the default file */
		if (prefs.default_filename && !strcmp(filename, prefs.default_filename))
			return 0;

		return report_error(translate("gettextFromC", "Failed to read '%s'"), filename);
	}

	fmt = strrchr(filename, '.');
	if (fmt && (!strcasecmp(fmt + 1, "DB") || !strcasecmp(fmt + 1, "BAK"))) {
		if (!try_to_open_db(filename, &mem)) {
			free(mem.buffer);
			return 0;
		}
	}

	parse_file_buffer(filename, &mem);
	free(mem.buffer);
	return 0;
}

#define MAXCOLDIGITS 3
#define MAXCOLS 100
int parse_csv_file(const char *filename, int timef, int depthf, int tempf, int po2f, int cnsf, int stopdepthf, int sepidx, const char *csvtemplate, int unitidx)
{
	struct memblock mem;
	int pnr = 0;
	char *params[21];
	char timebuf[MAXCOLDIGITS];
	char depthbuf[MAXCOLDIGITS];
	char tempbuf[MAXCOLDIGITS];
	char po2buf[MAXCOLDIGITS];
	char cnsbuf[MAXCOLDIGITS];
	char stopdepthbuf[MAXCOLDIGITS];
	char unitbuf[MAXCOLDIGITS];
	char separator_index[MAXCOLDIGITS];
	time_t now;
	struct tm *timep;
	char curdate[9];
	char curtime[6];

	if (timef >= MAXCOLS || depthf >= MAXCOLS || tempf >= MAXCOLS || po2f >= MAXCOLS || cnsf >= MAXCOLS || stopdepthf >= MAXCOLS)
		return report_error(translate("gettextFromC", "Maximum number of supported columns on CSV import is %d"), MAXCOLS);

	snprintf(timebuf, MAXCOLDIGITS, "%d", timef);
	snprintf(depthbuf, MAXCOLDIGITS, "%d", depthf);
	snprintf(tempbuf, MAXCOLDIGITS, "%d", tempf);
	snprintf(po2buf, MAXCOLDIGITS, "%d", po2f);
	snprintf(cnsbuf, MAXCOLDIGITS, "%d", cnsf);
	snprintf(stopdepthbuf, MAXCOLDIGITS, "%d", stopdepthf);
	snprintf(separator_index, MAXCOLDIGITS, "%d", sepidx);
	snprintf(unitbuf, MAXCOLDIGITS, "%d", unitidx);
	time(&now);
	timep = localtime(&now);
	strftime(curdate, sizeof(curdate), "%Y%m%d", timep);

	/* As the parameter is numeric, we need to ensure that the leading zero
	* is not discarded during the transform, thus prepend time with 1 */
	strftime(curtime, sizeof(curtime), "1%H%M", timep);

	params[pnr++] = "timeField";
	params[pnr++] = timebuf;
	params[pnr++] = "depthField";
	params[pnr++] = depthbuf;
	params[pnr++] = "tempField";
	params[pnr++] = tempbuf;
	params[pnr++] = "po2Field";
	params[pnr++] = po2buf;
	params[pnr++] = "cnsField";
	params[pnr++] = cnsbuf;
	params[pnr++] = "stopdepthField";
	params[pnr++] = stopdepthbuf;
	params[pnr++] = "date";
	params[pnr++] = curdate;
	params[pnr++] = "time";
	params[pnr++] = curtime;
	params[pnr++] = "units";
	params[pnr++] = unitbuf;
	params[pnr++] = "separatorIndex";
	params[pnr++] = separator_index;
	params[pnr++] = NULL;

	if (filename == NULL)
		return report_error("No CSV filename");

	if (try_to_xslt_open_csv(filename, &mem, csvtemplate))
		return -1;

	parse_xml_buffer(filename, mem.buffer, mem.size, &dive_table, (const char **)params);
	free(mem.buffer);
	return 0;
}

int parse_manual_file(const char *filename, int sepidx, int units, int numberf, int datef, int timef, int durationf, int locationf, int gpsf, int maxdepthf, int meandepthf, int buddyf, int notesf, int weightf, int tagsf)
{
	struct memblock mem;
	int pnr = 0;
	char *params[33];
	char numberbuf[MAXCOLDIGITS];
	char datebuf[MAXCOLDIGITS];
	char timebuf[MAXCOLDIGITS];
	char durationbuf[MAXCOLDIGITS];
	char locationbuf[MAXCOLDIGITS];
	char gpsbuf[MAXCOLDIGITS];
	char maxdepthbuf[MAXCOLDIGITS];
	char meandepthbuf[MAXCOLDIGITS];
	char buddybuf[MAXCOLDIGITS];
	char notesbuf[MAXCOLDIGITS];
	char weightbuf[MAXCOLDIGITS];
	char tagsbuf[MAXCOLDIGITS];
	char separator_index[MAXCOLDIGITS];
	char unit[MAXCOLDIGITS];
	time_t now;
	struct tm *timep;
	char curdate[9];
	char curtime[6];

	if (numberf >= MAXCOLS || datef >= MAXCOLS || timef >= MAXCOLS || durationf >= MAXCOLS || locationf >= MAXCOLS || gpsf >= MAXCOLS || maxdepthf >= MAXCOLS || meandepthf >= MAXCOLS || buddyf >= MAXCOLS || notesf >= MAXCOLS || weightf >= MAXCOLS || tagsf >= MAXCOLS)
		return report_error(translate("gettextFromC", "Maximum number of supported columns on CSV import is %d"), MAXCOLS);

	snprintf(numberbuf, MAXCOLDIGITS, "%d", numberf);
	snprintf(datebuf, MAXCOLDIGITS, "%d", datef);
	snprintf(timebuf, MAXCOLDIGITS, "%d", timef);
	snprintf(durationbuf, MAXCOLDIGITS, "%d", durationf);
	snprintf(locationbuf, MAXCOLDIGITS, "%d", locationf);
	snprintf(gpsbuf, MAXCOLDIGITS, "%d", gpsf);
	snprintf(maxdepthbuf, MAXCOLDIGITS, "%d", maxdepthf);
	snprintf(meandepthbuf, MAXCOLDIGITS, "%d", meandepthf);
	snprintf(buddybuf, MAXCOLDIGITS, "%d", buddyf);
	snprintf(notesbuf, MAXCOLDIGITS, "%d", notesf);
	snprintf(weightbuf, MAXCOLDIGITS, "%d", weightf);
	snprintf(tagsbuf, MAXCOLDIGITS, "%d", tagsf);
	snprintf(separator_index, MAXCOLDIGITS, "%d", sepidx);
	snprintf(unit, MAXCOLDIGITS, "%d", units);
	time(&now);
	timep = localtime(&now);
	strftime(curdate, sizeof(curdate), "%Y%m%d", timep);

	/* As the parameter is numeric, we need to ensure that the leading zero
	* is not discarded during the transform, thus prepend time with 1 */
	strftime(curtime, sizeof(curtime), "1%H%M", timep);

	params[pnr++] = "numberField";
	params[pnr++] = numberbuf;
	params[pnr++] = "dateField";
	params[pnr++] = datebuf;
	params[pnr++] = "timeField";
	params[pnr++] = timebuf;
	params[pnr++] = "durationField";
	params[pnr++] = durationbuf;
	params[pnr++] = "locationField";
	params[pnr++] = locationbuf;
	params[pnr++] = "gpsField";
	params[pnr++] = gpsbuf;
	params[pnr++] = "maxDepthField";
	params[pnr++] = maxdepthbuf;
	params[pnr++] = "meanDepthField";
	params[pnr++] = meandepthbuf;
	params[pnr++] = "buddyField";
	params[pnr++] = buddybuf;
	params[pnr++] = "notesField";
	params[pnr++] = notesbuf;
	params[pnr++] = "weightField";
	params[pnr++] = weightbuf;
	params[pnr++] = "tagsField";
	params[pnr++] = tagsbuf;
	params[pnr++] = "date";
	params[pnr++] = curdate;
	params[pnr++] = "time";
	params[pnr++] = curtime;
	params[pnr++] = "separatorIndex";
	params[pnr++] = separator_index;
	params[pnr++] = "units";
	params[pnr++] = unit;
	params[pnr++] = NULL;

	if (filename == NULL)
		return report_error("No manual CSV filename");

	if (try_to_xslt_open_csv(filename, &mem, "manualCSV"))
		return -1;

	parse_xml_buffer(filename, mem.buffer, mem.size, &dive_table, (const char **)params);
	free(mem.buffer);
	return 0;
}