/* * Copyright (C) 2012-2015 Robin Haberkorn * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #include #include #include #include #include #include #include #include #include #include #include "sciteco.h" #include "interface.h" #include "undo.h" #include "error.h" #include "ioview.h" #ifdef HAVE_WINDOWS_H /* here it shouldn't cause conflicts with other headers */ #include /* still need to clean up */ #undef interface #endif namespace SciTECO { #ifdef G_OS_WIN32 typedef DWORD FileAttributes; /* INVALID_FILE_ATTRIBUTES already defined */ static inline FileAttributes get_file_attributes(const gchar *filename) { return GetFileAttributes((LPCTSTR)filename); } static inline void set_file_attributes(const gchar *filename, FileAttributes attrs) { SetFileAttributes((LPCTSTR)filename, attrs); } #else typedef int FileAttributes; #define INVALID_FILE_ATTRIBUTES (-1) static inline FileAttributes get_file_attributes(const gchar *filename) { struct stat buf; return g_stat(filename, &buf) ? INVALID_FILE_ATTRIBUTES : buf.st_mode; } static inline void set_file_attributes(const gchar *filename, FileAttributes attrs) { g_chmod(filename, attrs); } #endif /* !G_OS_WIN32 */ /* * The following simple implementation of file reading is actually the * most efficient and useful in the common case of editing small files, * since * a) it works with minimal number of syscalls and * b) small files cause little temporary memory overhead. * Reading large files however could be very inefficient since the file * must first be read into memory and then copied in-memory. Also it could * result in thrashing. * Alternatively we could iteratively read into a smaller buffer trading * in speed against (temporary) memory consumption. * The best way to do it could be memory mapping the file as we could * let Scintilla copy from the file's virtual memory directly. * Unfortunately since every page of the mapped file is * only touched once by Scintilla TLB caching is useless and the TLB is * effectively thrashed with entries of the mapped file. * This results in the doubling of page faults and weighs out the other * advantages of memory mapping (has been benchmarked). * * So in the future, the following approach could be implemented: * 1.) On Unix/Posix, mmap() one page at a time, hopefully preventing * TLB thrashing. * 2.) On other platforms read into and copy from a statically sized buffer * (perhaps page-sized) */ void IOView::load(const gchar *filename) { gchar *contents; gsize size; GError *gerror = NULL; if (!g_file_get_contents(filename, &contents, &size, &gerror)) throw GlibError(gerror); ssm(SCI_BEGINUNDOACTION); ssm(SCI_CLEARALL); ssm(SCI_APPENDTEXT, size, (sptr_t)contents); ssm(SCI_ENDUNDOACTION); g_free(contents); } #if 0 /* * TODO: on UNIX it may be better to open() the current file, unlink() it * and keep the file descriptor in the UndoToken. * When the operation is undone, the file descriptor's contents are written to * the file (which should be efficient enough because it is written to the same * filesystem). This way we could avoid messing around with save point files. */ #else static gint savepoint_id = 0; class UndoTokenRestoreSavePoint : public UndoToken { gchar *savepoint; gchar *filename; #ifdef G_OS_WIN32 FileAttributes orig_attrs; #endif public: UndoTokenRestoreSavePoint(gchar *_savepoint, const gchar *_filename) : savepoint(_savepoint), filename(g_strdup(_filename)) { #ifdef G_OS_WIN32 orig_attrs = get_file_attributes(filename); if (orig_attrs != INVALID_FILE_ATTRIBUTES) set_file_attributes(savepoint, orig_attrs | FILE_ATTRIBUTE_HIDDEN); #endif } ~UndoTokenRestoreSavePoint() { if (savepoint) { g_unlink(savepoint); g_free(savepoint); } g_free(filename); savepoint_id--; } void run(void) { if (!g_rename(savepoint, filename)) { g_free(savepoint); savepoint = NULL; #ifdef G_OS_WIN32 if (orig_attrs != INVALID_FILE_ATTRIBUTES) set_file_attributes(filename, orig_attrs); #endif } else { interface.msg(InterfaceCurrent::MSG_WARNING, "Unable to restore save point file \"%s\"", savepoint); } } gsize get_size(void) const { gsize ret = sizeof(*this) + strlen(filename) + 1; if (savepoint) ret += strlen(savepoint) + 1; return ret; } }; static void make_savepoint(const gchar *filename) { gchar *dirname, *basename, *savepoint; gchar savepoint_basename[FILENAME_MAX]; basename = g_path_get_basename(filename); g_snprintf(savepoint_basename, sizeof(savepoint_basename), ".teco-%d-%s~", savepoint_id, basename); g_free(basename); dirname = g_path_get_dirname(filename); savepoint = g_build_filename(dirname, savepoint_basename, NIL); g_free(dirname); if (g_rename(filename, savepoint)) { interface.msg(InterfaceCurrent::MSG_WARNING, "Unable to create save point file \"%s\"", savepoint); g_free(savepoint); return; } savepoint_id++; /* NOTE: passes ownership of savepoint string to undo token */ undo.push(new UndoTokenRestoreSavePoint(savepoint, filename)); } #endif /* !G_OS_UNIX */ void IOView::save(const gchar *filename) { const void *buffer; sptr_t gap; size_t size; FILE *file; #ifdef G_OS_UNIX GStatBuf file_stat; file_stat.st_uid = -1; file_stat.st_gid = -1; #endif FileAttributes attributes = INVALID_FILE_ATTRIBUTES; if (undo.enabled) { if (g_file_test(filename, G_FILE_TEST_IS_REGULAR)) { #ifdef G_OS_UNIX g_stat(filename, &file_stat); #endif attributes = get_file_attributes(filename); make_savepoint(filename); } else { undo.push(new UndoTokenRemoveFile(filename)); } } /* leaves access mode intact if file still exists */ file = g_fopen(filename, "w"); if (!file) /* hopefully, errno is also always set on Windows */ throw Error("Error opening file \"%s\" for writing: %s", filename, strerror(errno)); /* write part of buffer before gap */ gap = ssm(SCI_GETGAPPOSITION); if (gap > 0) { buffer = (const void *)ssm(SCI_GETRANGEPOINTER, 0, gap); if (!fwrite(buffer, (size_t)gap, 1, file)) { fclose(file); throw Error("Error writing file \"%s\"", filename); } } /* write part of buffer after gap */ size = ssm(SCI_GETLENGTH) - gap; if (size > 0) { buffer = (const void *)ssm(SCI_GETRANGEPOINTER, gap, size); if (!fwrite(buffer, size, 1, file)) { fclose(file); throw Error("Error writing file \"%s\"", filename); } } /* if file existed but has been renamed, restore attributes */ if (attributes != INVALID_FILE_ATTRIBUTES) set_file_attributes(filename, attributes); #ifdef G_OS_UNIX /* * only a good try to inherit owner since process user must have * CHOWN capability traditionally reserved to root only. * That's why we don't handle the return value and are spammed * with unused-result warnings by GCC. There is NO sane way to avoid * this warning except, adding -Wno-unused-result which disabled all * such warnings. */ fchown(fileno(file), file_stat.st_uid, file_stat.st_gid); #endif fclose(file); } /* * Auxiliary functions */ #ifdef G_OS_UNIX gchar * get_absolute_path(const gchar *path) { gchar buf[PATH_MAX]; gchar *resolved; if (!path) return NULL; if (!realpath(path, buf)) { if (g_path_is_absolute(path)) { resolved = g_strdup(path); } else { gchar *cwd = g_get_current_dir(); resolved = g_build_filename(cwd, path, NIL); g_free(cwd); } } else { resolved = g_strdup(buf); } return resolved; } bool file_is_visible(const gchar *path) { gchar *basename = g_path_get_basename(path); bool ret = *basename != '.'; g_free(basename); return ret; } #elif defined(G_OS_WIN32) gchar * get_absolute_path(const gchar *path) { TCHAR buf[MAX_PATH]; gchar *resolved = NULL; if (path && GetFullPathName(path, sizeof(buf), buf, NULL)) resolved = g_strdup(buf); return resolved; } bool file_is_visible(const gchar *path) { return !(get_file_attributes(path) & FILE_ATTRIBUTE_HIDDEN); } #else /* * FIXME: I doubt that works on any platform... */ gchar * get_absolute_path(const gchar *path) { return path ? g_file_read_link(path, NULL) : NULL; } /* * There's no platform-independant way to determine if a file * is visible/hidden, so we just assume that all files are * visible. */ bool file_is_visible(const gchar *path) { return true; } #endif /* !G_OS_UNIX && !G_OS_WIN32 */ } /* namespace SciTECO */