This is the first of the embedding patch series. It adds functions zipFiles and unzipToDir to src/support/filetools.[h|cpp]. This allows the handling of zipped .lyx file.
src/support/lyxlib.[h|cpp]: add function makedir that recursively create a directory. Function adapted from zlib/contrib/minizip/miniunz.c src/support/filetools.[h|cpp]: add functions zipFiles and unzipToDir. Adapted from zlib/contrib/minizip/minizip.c and miniunz.c development/scons/* build static library minizip.a from zlib/contrib/minizip/ioapi.c, zip.c and unzip.c (also iowin32.c under windows). Problems: 1. no autotools or cmake support. 2. the adapted functions are in c style, and may be optimized/simplified for our simpler usages. 3. support::unzipFile can be rewritten (it calls gunzip now). 2 and 3 can be left for later. I would appreciate it if someone can add support for other build systems (basically building and linking minizip.a). I am also wondering if we can add zip/contrib/minizip (four source files) to lyx/svn to make our life a bit easier. Because this patch does not interfere with any existing code. I will submit tomorrow if there is no objection. Cheers, Bo
Index: src/support/lyxlib.h =================================================================== --- src/support/lyxlib.h (revision 19663) +++ src/support/lyxlib.h (working copy) @@ -44,7 +44,12 @@ /// FIXME: same here void abort(); /// create the given directory with the given mode +/// \ret return 0 if the directory is successfully created int mkdir(FileName const & pathname, unsigned long int mode); +/// create the given directory with the given mode, create all +/// intermediate directories if necessary +/// \ret return 0 if the directory is successfully created +int makedir(char * pathname, unsigned long int mode=0755); /// unlink the given file int unlink(FileName const & file); /// (securely) create a temporary file in the given dir with the given mask Index: src/support/filetools.cpp =================================================================== --- src/support/filetools.cpp (revision 19663) +++ src/support/filetools.cpp (working copy) @@ -50,6 +50,28 @@ #include <fstream> #include <sstream> +#ifdef unix +# include <unistd.h> +# include <utime.h> +# include <sys/types.h> +# include <sys/stat.h> +#else +# include <direct.h> +# include <io.h> +#endif + +#include "zip.h" +#include "unzip.h" + +#ifdef WIN32 +#define USEWIN32IOAPI +#include "iowin32.h" +#endif + +#define WRITEBUFFERSIZE (16384) +#define MAXFILENAME (256) + + #ifndef CXX_GLOBAL_CSTD using std::fgetc; #endif @@ -61,6 +83,7 @@ using std::ifstream; using std::ostringstream; using std::vector; +using std::pair; namespace fs = boost::filesystem; @@ -1250,5 +1273,392 @@ return cmp; } +// the following is adapted from zlib-1.2.3/contrib/minizip.c +// and miniunz.c, except that +// 1. mkdir, makedir is replaced by lyx' own version +// 2. printf is replaced by lyxerr + +#ifdef WIN32 +uLong filetime(const char * f, tm_zip * tmzip, uLong * dt) +{ + int ret = 0; + { + FILETIME ftLocal; + HANDLE hFind; + WIN32_FIND_DATA ff32; + + hFind = FindFirstFile(f,&ff32); + if (hFind != INVALID_HANDLE_VALUE) + { + FileTimeToLocalFileTime(&(ff32.ftLastWriteTime),&ftLocal); + FileTimeToDosDateTime(&ftLocal,((LPWORD)dt)+1,((LPWORD)dt)+0); + FindClose(hFind); + ret = 1; + } + } + return ret; +} + +#else +#ifdef unix +uLong filetime(const char * f, tm_zip * tmzip, uLong * dt) +{ + int ret=0; + struct stat s; /* results of stat() */ + struct tm* filedate; + time_t tm_t=0; + + if (strcmp(f,"-")!=0) { + char name[MAXFILENAME+1]; + int len = strlen(f); + if (len > MAXFILENAME) + len = MAXFILENAME; + + strncpy(name, f,MAXFILENAME-1); + /* strncpy doesnt append the trailing NULL, of the string is too long. */ + name[ MAXFILENAME ] = '\0'; + + if (name[len - 1] == '/') + name[len - 1] = '\0'; + /* not all systems allow stat'ing a file with / appended */ + if (stat(name,&s)==0) { + tm_t = s.st_mtime; + ret = 1; + } + } + filedate = localtime(&tm_t); + + tmzip->tm_sec = filedate->tm_sec; + tmzip->tm_min = filedate->tm_min; + tmzip->tm_hour = filedate->tm_hour; + tmzip->tm_mday = filedate->tm_mday; + tmzip->tm_mon = filedate->tm_mon ; + tmzip->tm_year = filedate->tm_year; + + return ret; +} + +#else + +uLong filetime(const char * f, tm_zip * tmzip, uLong * dt) +{ + return 0; +} +#endif +#endif + +bool zipFiles(DocFileName const & zipfile, vector<pair<string, string> > const & files) +{ + int err = 0; + zipFile zf; + int errclose; + void * buf = NULL; + + int size_buf = WRITEBUFFERSIZE; + buf = (void*)malloc(size_buf); + if (buf==NULL) { + lyxerr << "Error allocating memory" << endl; + return false; + } + string const zfile = zipfile.toFilesystemEncoding(); + const char * fname = zfile.c_str(); + +#ifdef USEWIN32IOAPI + zlib_filefunc_def ffunc; + fill_win32_filefunc(&ffunc); + // false: not append + zf = zipOpen2(fname, false, NULL, &ffunc); +#else + zf = zipOpen(fname, false); +#endif + + if (zf == NULL) { + lyxerr << "error opening " << zipfile << endl; + return false; + } + + for (vector<pair<string, string> >::const_iterator it = files.begin(); it != files.end(); ++it) { + FILE * fin; + int size_read; + zip_fileinfo zi; + const char * diskfilename = it->first.c_str(); + const char * filenameinzip = it->second.c_str(); + unsigned long crcFile=0; + + zi.tmz_date.tm_sec = zi.tmz_date.tm_min = zi.tmz_date.tm_hour = + zi.tmz_date.tm_mday = zi.tmz_date.tm_mon = zi.tmz_date.tm_year = 0; + zi.dosDate = 0; + zi.internal_fa = 0; + zi.external_fa = 0; + filetime(filenameinzip, &zi.tmz_date, &zi.dosDate); + + err = zipOpenNewFileInZip3(zf, filenameinzip, &zi, + NULL,0,NULL,0,NULL /* comment*/, + Z_DEFLATED, + Z_DEFAULT_COMPRESSION, // compression level + 0, + /* -MAX_WBITS, DEF_MEM_LEVEL, Z_DEFAULT_STRATEGY, */ + -MAX_WBITS, DEF_MEM_LEVEL, Z_DEFAULT_STRATEGY, + NULL, // password, + crcFile); + + if (err != ZIP_OK) { + lyxerr << "error in opening " << filenameinzip << " in zipfile" << endl; + return false; + } + fin = fopen(diskfilename, "rb"); + if (fin==NULL) { + lyxerr << "error in opening " << diskfilename << " for reading" << endl; + return false; + } + + do { + err = ZIP_OK; + size_read = (int)fread(buf, 1, size_buf, fin); + if (size_read < size_buf) + if (feof(fin)==0) { + lyxerr << "error in reading " << filenameinzip << endl; + return false; + } + + if (size_read>0) { + err = zipWriteInFileInZip (zf, buf, size_read); + if (err<0) { + lyxerr << "error in writing " << filenameinzip << " in the zipfile" << endl; + return false; + } + } + } while ((err == ZIP_OK) && (size_read>0)); + + if (fin) + fclose(fin); + + err = zipCloseFileInZip(zf); + if (err != ZIP_OK) { + lyxerr << "error in closing " << filenameinzip << "in the zipfile" << endl; + return false; + } + } + errclose = zipClose(zf, NULL); + if (errclose != ZIP_OK) { + lyxerr << "error in closing " << zipfile << endl; + return false; + } + free(buf); + return true; +} + +// adapted from miniunz.c + +/* change_file_date : change the date/time of a file + filename : the filename of the file where date/time must be modified + dosdate : the new date at the MSDos format (4 bytes) + tmu_date : the SAME new date at the tm_unz format */ +void change_file_date(const char * filename, uLong dosdate, tm_unz tmu_date) +{ +#ifdef WIN32 + HANDLE hFile; + FILETIME ftm,ftLocal,ftCreate,ftLastAcc,ftLastWrite; + + hFile = CreateFile(filename,GENERIC_READ | GENERIC_WRITE, + 0,NULL,OPEN_EXISTING,0,NULL); + GetFileTime(hFile,&ftCreate,&ftLastAcc,&ftLastWrite); + DosDateTimeToFileTime((WORD)(dosdate>>16),(WORD)dosdate,&ftLocal); + LocalFileTimeToFileTime(&ftLocal,&ftm); + SetFileTime(hFile,&ftm,&ftLastAcc,&ftm); + CloseHandle(hFile); +#else +#ifdef unix + struct utimbuf ut; + struct tm newdate; + + newdate.tm_sec = tmu_date.tm_sec; + newdate.tm_min=tmu_date.tm_min; + newdate.tm_hour=tmu_date.tm_hour; + newdate.tm_mday=tmu_date.tm_mday; + newdate.tm_mon=tmu_date.tm_mon; + if (tmu_date.tm_year > 1900) + newdate.tm_year=tmu_date.tm_year - 1900; + else + newdate.tm_year=tmu_date.tm_year ; + newdate.tm_isdst=-1; + + ut.actime=ut.modtime=mktime(&newdate); + utime(filename,&ut); +#endif +#endif +} + + + +int do_extract_currentfile(unzFile uf, + const int * popt_extract_without_path, + int * popt_overwrite, + const char * password, + const char * dirname) +{ + char filename_inzip[256]; + char* filename_withoutpath; + char* p; + int err=UNZ_OK; + FILE *fout=NULL; + void* buf; + uInt size_buf; + + unz_file_info file_info; + uLong ratio=0; + err = unzGetCurrentFileInfo(uf,&file_info,filename_inzip,sizeof(filename_inzip),NULL,0,NULL,0); + + if (err!=UNZ_OK) { + lyxerr << "error " << err << " with zipfile in unzGetCurrentFileInfo" << endl; + return err; + } + + size_buf = WRITEBUFFERSIZE; + buf = (void*)malloc(size_buf); + if (buf==NULL) { + lyxerr << "Error allocating memory" << endl; + return UNZ_INTERNALERROR; + } + + p = filename_withoutpath = filename_inzip; + while ((*p) != '\0') { + if (((*p)=='/') || ((*p)=='\\')) + filename_withoutpath = p+1; + p++; + } + // this is a directory + if ((*filename_withoutpath)=='\0') { + if ((*popt_extract_without_path)==0) + makedir(filename_inzip); + } + // this is a filename + else { + char write_filename[1024]; + + strcpy(write_filename, dirname); + int len = strlen(write_filename); + if (write_filename[len-1] != '\\' && + write_filename[len-1] != '/') + strcat(write_filename, "/"); + + if ((*popt_extract_without_path)==0) + strcat(write_filename, filename_inzip); + else + strcat(write_filename, filename_withoutpath); + + err = unzOpenCurrentFilePassword(uf,password); + if (err!=UNZ_OK) { + lyxerr << "error " << err << " with zipfile in unzOpenCurrentFilePassword" << endl; + } else { + fout=fopen(write_filename, "wb"); + + /* some zipfile don't contain directory alone before file */ + if ((fout==NULL) && ((*popt_extract_without_path)==0) && + (filename_withoutpath!=(char*)filename_inzip)) { + char c=*(filename_withoutpath-1); + *(filename_withoutpath-1)='\0'; + makedir(write_filename); + *(filename_withoutpath-1)=c; + fout=fopen(write_filename,"wb"); + } + + if (fout==NULL) { + lyxerr << "error opening " << write_filename << endl; + } + } + + if (fout!=NULL) { + LYXERR(Debug::FILES) << " extracting: " << write_filename << endl; + + do { + err = unzReadCurrentFile(uf,buf,size_buf); + if (err<0) { + lyxerr << "error " << err << " with zipfile in unzReadCurrentFile" << endl; + break; + } + if (err>0) + if (fwrite(buf,err,1,fout)!=1) { + lyxerr << "error in writing extracted file" << endl; + err=UNZ_ERRNO; + break; + } + } while (err>0); + if (fout) + fclose(fout); + + if (err==0) + change_file_date(write_filename,file_info.dosDate, + file_info.tmu_date); + } + + if (err==UNZ_OK) { + err = unzCloseCurrentFile (uf); + if (err!=UNZ_OK) { + lyxerr << "error " << err << " with zipfile in unzCloseCurrentFile" << endl; + } + } + else + unzCloseCurrentFile(uf); /* don't lose the error */ + } + + free(buf); + return err; +} + + +bool unzipToDir(string const & zipfile, string const & dirname) +{ + unzFile uf=NULL; +#ifdef USEWIN32IOAPI + zlib_filefunc_def ffunc; +#endif + + const char * zipfilename = zipfile.c_str(); + +#ifdef USEWIN32IOAPI + fill_win32_filefunc(&ffunc); + uf = unzOpen2(zipfilename, &ffunc); +#else + uf = unzOpen(zipfilename); +#endif + + if (uf==NULL) { + lyxerr << "Cannot open " << zipfile << " or " << zipfile << ".zip" << endl; + return false; + } + + uLong i; + unz_global_info gi; + int err; + FILE* fout=NULL; + int opt_extract_without_path = 0; + int opt_overwrite = 1; + char * password = NULL; + + err = unzGetGlobalInfo (uf, &gi); + if (err != UNZ_OK) { + lyxerr << "error " << err << " with zipfile in unzGetGlobalInfo " << endl; + return false; + } + + for (i=0; i < gi.number_entry; i++) { + if (do_extract_currentfile(uf, &opt_extract_without_path, + &opt_overwrite, + password, dirname.c_str()) != UNZ_OK) + break; + + if ((i+1)<gi.number_entry) { + err = unzGoToNextFile(uf); + if (err != UNZ_OK) { + lyxerr << "error " << err << " with zipfile in unzGoToNextFile" << endl;; + break; + } + } + } + + unzCloseCurrentFile(uf); +} + } //namespace support } // namespace lyx Index: src/support/filetools.h =================================================================== --- src/support/filetools.h (revision 19663) +++ src/support/filetools.h (working copy) @@ -314,6 +314,12 @@ cmd_ret const runCommand(std::string const & cmd); +/// zip several files to a zipfile. In-zip filenames are also specified +bool zipFiles(DocFileName const & zipfile, std::vector<std::pair<std::string, std::string> > const & files); + +/// Unzip a zip file to a directory +bool unzipToDir(std::string const & zipfile, std::string const & path); + } // namespace support } // namespace lyx Index: src/support/mkdir.cpp =================================================================== --- src/support/mkdir.cpp (revision 19663) +++ src/support/mkdir.cpp (working copy) @@ -34,30 +34,77 @@ namespace support { -int mkdir(FileName const & pathname, unsigned long int mode) +int mymkdir(char const * pathname, unsigned long int mode) { // FIXME: why don't we have mode_t in lyx::mkdir prototype ?? #if HAVE_MKDIR # if MKDIR_TAKES_ONE_ARG // MinGW32 - return ::mkdir(pathname.toFilesystemEncoding().c_str()); + return ::mkdir(pathname); // FIXME: "Permissions of created directories are ignored on this system." # else // POSIX - return ::mkdir(pathname.toFilesystemEncoding().c_str(), mode_t(mode)); + return ::mkdir(pathname, mode_t(mode)); # endif #elif defined(_WIN32) // plain Windows 32 - return CreateDirectory(pathname.toFilesystemEncoding().c_str(), 0) != 0 ? 0 : -1; + return CreateDirectory(pathname, 0) != 0 ? 0 : -1; // FIXME: "Permissions of created directories are ignored on this system." #elif HAVE__MKDIR - return ::_mkdir(pathname.toFilesystemEncoding().c_str()); + return ::_mkdir(pathname); // FIXME: "Permissions of created directories are ignored on this system." #else # error "Don't know how to create a directory on this system." #endif + } +int mkdir(FileName const & pathname, unsigned long int mode) +{ + return mymkdir(pathname.toFilesystemEncoding().c_str(), mode); +} + +// adapted from zlib-1.2.3/contrib/minizip/miniunz.c +int makedir(char * newdir, unsigned long int mode) +{ + char *buffer; + char *p; + int len = (int)strlen(newdir); + + if (len <= 0) + return 1; + + buffer = (char*)malloc(len+1); + strcpy(buffer,newdir); + + if (buffer[len-1] == '/') + buffer[len-1] = '\0'; + if (mymkdir(buffer, mode) == 0) { + free(buffer); + return 0; + } + + p = buffer + 1; + while (1) { + char hold; + + while(*p && *p != '\\' && *p != '/') + p++; + hold = *p; + *p = 0; + if (mymkdir(buffer, mode) != 0) { + free(buffer); + return 1; + } + if (hold == 0) + break; + *p++ = hold; + } + free(buffer); + return 0; +} + + } // namespace support } // namespace lyx Index: development/scons/scons_manifest.py =================================================================== --- development/scons/scons_manifest.py (revision 19663) +++ development/scons/scons_manifest.py (working copy) @@ -413,6 +413,16 @@ ''') +zlib_contrib_minizip_files = Split(''' + ioapi.c + zip.c + unzip.c +''') + + +zlib_contrib_minizip_windows_files = ['iowin32.c'] + + src_graphics_header_files = Split(''' GraphicsCache.h GraphicsCacheItem.h Index: development/scons/SConstruct =================================================================== --- development/scons/SConstruct (revision 19663) +++ development/scons/SConstruct (working copy) @@ -177,6 +177,8 @@ PathOption('qt_inc_path', 'Path to qt include directory', None), # PathOption('qt_lib_path', 'Path to qt library directory', None), + # + PathOption('zlib_src_path', 'Path to zlib source file', None), # extra include and libpath PathOption('extra_inc_path', 'Extra include path', None), # @@ -613,6 +615,19 @@ print 'Please check config.log for more information.' Exit(1) +# check for zlib source directory, we actually need minizip source file +if env.has_key('zlib_src_path') and os.path.isdir(env['zlib_src_path']): + env['ZLIB_SRC_DIR'] = env['zlib_src_path'] +elif os.path.isdir(os.path.join(env['TOP_SRCDIR'], 'zlib-1.2.3')): + env['ZLIB_SRC_DIR'] = '$TOP_SRCDIR/zlib-1.2.3' +else: + print 'Please specify zlib source directory using option zlib_src_path' + Exit(1) + +if not os.path.isfile(os.path.join(env.Dir('$ZLIB_SRC_DIR').abspath, 'zconf.h')): + print 'zconf.h is not found. Please configure zlib first' + Exit(1) + # pkg-config? (if not, we use hard-coded options) if conf.CheckPkgConfig('0.15.0'): env['HAS_PKG_CONFIG'] = True @@ -1283,7 +1298,7 @@ # BUILDDIR/common: for config.h # TOP_SRCDIR/src: for support/* etc # -env['CPPPATH'] += ['$BUILDDIR/common', '$TOP_SRCDIR/src'] +env['CPPPATH'] += ['$BUILDDIR/common', '$TOP_SRCDIR/src', '$ZLIB_SRC_DIR/contrib/minizip'] # # Separating boost directories from CPPPATH stops scons from building # the dependency tree for boost header files, and effectively reduce @@ -1501,6 +1516,7 @@ or build_install or 'all' in targets build_boost = (included_boost and not libExists('boost_regex')) or 'boost' in targets build_intl = (included_gettext and not libExists('included_intl')) or 'intl' in targets +build_minizip = 'minizip' in targets or not libExists('minizip') build_support = build_lyx or True in [x in targets for x in ['support', 'client', 'tex2lyx']] build_mathed = build_lyx or 'mathed' in targets build_insets = build_lyx or 'insets' in targets @@ -1532,6 +1548,7 @@ # do not change the original value else: return old_value + build_minizip = ifBuildLib('minizip', 'minizip', build_minizip) build_boost = ifBuildLib('boost', 'included_boost_filesystem', build_boost) build_intl = ifBuildLib('intl', 'included_intl', build_intl) build_support = ifBuildLib('support', 'support', build_support) @@ -1625,6 +1642,24 @@ Alias('intl', intl) +if build_minizip: + # + # minizip library, build from minizip source, plus files from contrib/minizip + # + minizipenv = env.Copy() + # + env.BuildDir('$BUILDDIR/minizip', '$ZLIB_SRC_DIR', duplicate=0) + # we need the original C compiler for these files + minizipenv['CC'] = C_COMPILER + minizipenv['CCFLAGS'] = C_CCFLAGS + + miniziplib = minizipenv.StaticLibrary( + target = '$LOCALLIBPATH/minizip', + source = ['$BUILDDIR/minizip/contrib/minizip/%s' % x for x in zlib_contrib_minizip_files] + \ + ['$BUILDDIR/minizip/contrib/minizip/%s' % x for x in zlib_contrib_minizip_windows_files if platform_name == 'win32'] + ) + Alias('minizip', miniziplib) + # # Now, src code under src/ # @@ -1776,7 +1811,7 @@ if env['HAVE_FCNTL']: client = frontend_env.Program( target = '$BUILDDIR/common/client/lyxclient', - LIBS = ['support'] + intl_libs + system_libs + + LIBS = ['support', 'minizip'] + intl_libs + system_libs + socket_libs + boost_libraries + qtcore_lib, source = ['$BUILDDIR/common/client/%s' % x for x in src_client_files] + \ utils.createResFromIcon(frontend_env, 'lyx_32x32.ico', '$LOCALLIBPATH/client.rc') @@ -1807,7 +1842,7 @@ tex2lyx = frontend_env.Program( target = '$BUILDDIR/common/tex2lyx/tex2lyx', - LIBS = ['support'] + boost_libraries + intl_libs + system_libs + qtcore_lib, + LIBS = ['support', 'minizip'] + boost_libraries + intl_libs + system_libs + qtcore_lib, source = ['$BUILDDIR/common/tex2lyx/%s' % x for x in src_tex2lyx_files + src_tex2lyx_copied_files] + \ utils.createResFromIcon(frontend_env, 'lyx_32x32.ico', '$LOCALLIBPATH/tex2lyx.rc'), CPPPATH = ['$BUILDDIR/common/tex2lyx', '$CPPPATH'], @@ -1867,6 +1902,7 @@ 'support', 'lyxbase_post', ] + + ['minizip'] + boost_libraries + frontend_libs + intl_libs +