/*
* 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 */