mirror of
https://github.com/haiwen/seafile.git
synced 2025-01-07 03:17:13 +08:00
9ab77f59d2
Co-authored-by: heran yang <heran.yang@seafile.com>
704 lines
20 KiB
C
704 lines
20 KiB
C
/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
|
|
|
|
#include "common.h"
|
|
#define DEBUG_FLAG SEAFILE_DEBUG_MERGE
|
|
#include "log.h"
|
|
#include "seafile-error.h"
|
|
|
|
#include <stdlib.h>
|
|
#include <sys/stat.h>
|
|
#include <glib/gstdio.h>
|
|
|
|
#include "seafile-session.h"
|
|
|
|
#include "utils.h"
|
|
#include "fs-mgr.h"
|
|
#include "vc-utils.h"
|
|
#include "vc-common.h"
|
|
#include "index/index.h"
|
|
|
|
static gint
|
|
compare_dirents (gconstpointer a, gconstpointer b)
|
|
{
|
|
const SeafDirent *denta = a, *dentb = b;
|
|
|
|
return strcmp (dentb->name, denta->name);
|
|
}
|
|
|
|
int
|
|
commit_trees_cb (const char *repo_id, int version,
|
|
const char *worktree,
|
|
struct cache_tree *it, struct cache_entry **cache,
|
|
int entries, const char *base, int baselen)
|
|
{
|
|
SeafDir *seaf_dir;
|
|
GList *dirents = NULL, *ptr;
|
|
int i;
|
|
|
|
for (i = 0; i < entries; i++) {
|
|
SeafDirent *seaf_dent;
|
|
char *name;
|
|
struct cache_entry *ce = cache[i];
|
|
struct cache_tree_sub *sub;
|
|
const char *path, *slash;
|
|
int pathlen, entlen;
|
|
const unsigned char *sha1;
|
|
char hex[41];
|
|
unsigned mode;
|
|
guint64 mtime;
|
|
gint64 size;
|
|
char *modifier;
|
|
|
|
if (ce->ce_flags & CE_REMOVE)
|
|
continue; /* entry being removed */
|
|
|
|
path = ce->name;
|
|
pathlen = ce_namelen(ce);
|
|
if (pathlen <= baselen || memcmp(base, path, baselen))
|
|
break; /* at the end of this level */
|
|
|
|
slash = strchr(path + baselen, '/');
|
|
if (slash) {
|
|
entlen = slash - (path + baselen);
|
|
sub = cache_tree_find_subtree(it, path + baselen, entlen, 0);
|
|
g_return_val_if_fail (sub != NULL, -1);
|
|
/* Skip cache entries in the sub level. */
|
|
i += sub->cache_tree->entry_count - 1;
|
|
|
|
sha1 = sub->cache_tree->sha1;
|
|
mtime = sub->cache_tree->mtime;
|
|
mode = S_IFDIR;
|
|
name = g_strndup(path + baselen, entlen);
|
|
|
|
rawdata_to_hex (sha1, hex, 20);
|
|
seaf_dent = seaf_dirent_new (dir_version_from_repo_version(version),
|
|
hex, mode, name, mtime, NULL, -1);
|
|
g_free(name);
|
|
|
|
dirents = g_list_prepend (dirents, seaf_dent);
|
|
} else {
|
|
sha1 = ce->sha1;
|
|
mode = ce->ce_mode;
|
|
mtime = ce->ce_mtime.sec;
|
|
size = ce->ce_size;
|
|
modifier = ce->modifier;
|
|
entlen = pathlen - baselen;
|
|
name = g_strndup(path + baselen, entlen);
|
|
rawdata_to_hex (sha1, hex, 20);
|
|
|
|
if (version > 0) {
|
|
seaf_dent =
|
|
seaf_dirent_new (dir_version_from_repo_version(version),
|
|
hex, mode, name, mtime, modifier, size);
|
|
} else {
|
|
seaf_dent = seaf_dirent_new (0, hex, mode, name, 0, NULL, -1);
|
|
}
|
|
|
|
g_free(name);
|
|
|
|
dirents = g_list_prepend (dirents, seaf_dent);
|
|
}
|
|
|
|
#if DEBUG
|
|
fprintf(stderr, "cache-tree update-one %o %.*s\n",
|
|
mode, entlen, path + baselen);
|
|
#endif
|
|
}
|
|
|
|
/* Sort dirents in descending order. */
|
|
dirents = g_list_sort (dirents, compare_dirents);
|
|
|
|
seaf_dir = seaf_dir_new (NULL, dirents, dir_version_from_repo_version(version));
|
|
hex_to_rawdata (seaf_dir->dir_id, it->sha1, 20);
|
|
|
|
/* Dir's mtime is the latest of all dir entires. */
|
|
guint64 dir_mtime = 0;
|
|
SeafDirent *dent;
|
|
for (ptr = dirents; ptr; ptr = ptr->next) {
|
|
dent = ptr->data;
|
|
if (dent->mtime > dir_mtime)
|
|
dir_mtime = dent->mtime;
|
|
}
|
|
it->mtime = dir_mtime;
|
|
|
|
if (!seaf_fs_manager_object_exists (seaf->fs_mgr,
|
|
repo_id, version,
|
|
seaf_dir->dir_id))
|
|
seaf_dir_save (seaf->fs_mgr, repo_id, version, seaf_dir);
|
|
|
|
#if DEBUG
|
|
for (p = dirents; p; p = p->next) {
|
|
SeafDirent *tmp = (SeafDirent *)p->data;
|
|
fprintf(stderr, "dump dirent name %s id %s\n", tmp->name, tmp->id);
|
|
}
|
|
#endif
|
|
|
|
seaf_dir_free (seaf_dir);
|
|
return 0;
|
|
}
|
|
|
|
int
|
|
update_index (struct index_state *istate, const char *index_path)
|
|
{
|
|
char index_shadow[SEAF_PATH_MAX];
|
|
int index_fd;
|
|
int ret = 0;
|
|
|
|
snprintf (index_shadow, SEAF_PATH_MAX, "%s.shadow", index_path);
|
|
index_fd = seaf_util_create (index_shadow, O_RDWR | O_CREAT | O_TRUNC | O_BINARY,
|
|
0666);
|
|
if (index_fd < 0) {
|
|
seaf_warning ("Failed to open shadow index: %s.\n", strerror(errno));
|
|
return -1;
|
|
}
|
|
|
|
if (write_index (istate, index_fd) < 0) {
|
|
seaf_warning ("Failed to write shadow index: %s.\n", strerror(errno));
|
|
close (index_fd);
|
|
return -1;
|
|
}
|
|
close (index_fd);
|
|
|
|
ret = seaf_util_rename (index_shadow, index_path);
|
|
if (ret < 0) {
|
|
seaf_warning ("Failed to update index errno=%d %s\n", errno, strerror(errno));
|
|
return -1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
#ifndef WIN32
|
|
|
|
int
|
|
seaf_remove_empty_dir (const char *path)
|
|
{
|
|
SeafStat st;
|
|
GDir *dir;
|
|
const char *dname;
|
|
char *full_path;
|
|
GError *error = NULL;
|
|
|
|
if (seaf_stat (path, &st) < 0 || !S_ISDIR(st.st_mode))
|
|
return 0;
|
|
|
|
if (seaf_util_rmdir (path) < 0) {
|
|
dir = g_dir_open (path, 0, &error);
|
|
if (!dir) {
|
|
seaf_warning ("Failed to open dir %s: %s.\n", path, error->message);
|
|
return -1;
|
|
}
|
|
|
|
/* Remove all ignored hidden files. */
|
|
while ((dname = g_dir_read_name (dir)) != NULL) {
|
|
if (seaf_repo_manager_is_ignored_hidden_file(dname)) {
|
|
full_path = g_build_path ("/", path, dname, NULL);
|
|
if (seaf_util_unlink (full_path) < 0)
|
|
seaf_warning ("Failed to remove file %s: %s.\n",
|
|
full_path, strerror(errno));
|
|
g_free (full_path);
|
|
}
|
|
}
|
|
|
|
g_dir_close (dir);
|
|
|
|
if (seaf_util_rmdir (path) < 0) {
|
|
seaf_warning ("Failed to remove dir %s: %s.\n", path, strerror(errno));
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
#else
|
|
|
|
static int
|
|
remove_hidden_file (wchar_t *parent, WIN32_FIND_DATAW *fdata,
|
|
void *user_data, gboolean *stop)
|
|
{
|
|
char *dname = NULL;
|
|
wchar_t *subpath_w = NULL;
|
|
char *subpath = NULL;
|
|
|
|
dname = g_utf16_to_utf8 (fdata->cFileName, -1, NULL, NULL, NULL);
|
|
|
|
if (!(fdata->dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) &&
|
|
(!dname || seaf_repo_manager_is_ignored_hidden_file(dname))) {
|
|
subpath_w = g_new0 (wchar_t, wcslen(parent) + wcslen(fdata->cFileName) + 2);
|
|
wcscpy (subpath_w, parent);
|
|
wcscat (subpath_w, L"\\");
|
|
wcscat (subpath_w, fdata->cFileName);
|
|
|
|
if (!DeleteFileW (subpath_w)) {
|
|
subpath = g_utf16_to_utf8 (subpath_w, -1, NULL, NULL, NULL);
|
|
seaf_warning ("Failed to remove file %s: %lu.\n", subpath, GetLastError());
|
|
g_free (subpath);
|
|
}
|
|
|
|
g_free (subpath_w);
|
|
}
|
|
|
|
g_free (dname);
|
|
return 0;
|
|
}
|
|
|
|
int
|
|
seaf_remove_empty_dir (const char *path)
|
|
{
|
|
wchar_t *path_w = NULL;
|
|
WIN32_FILE_ATTRIBUTE_DATA attrs;
|
|
int ret = 0;
|
|
|
|
path_w = win32_long_path (path);
|
|
|
|
if (!GetFileAttributesExW (path_w, GetFileExInfoStandard, &attrs)) {
|
|
goto out;
|
|
}
|
|
|
|
if (!(attrs.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)) {
|
|
goto out;
|
|
}
|
|
|
|
if (RemoveDirectoryW (path_w))
|
|
goto out;
|
|
|
|
if (GetLastError() == ERROR_DIR_NOT_EMPTY) {
|
|
traverse_directory_win32 (path_w, remove_hidden_file, NULL);
|
|
if (!RemoveDirectoryW (path_w)) {
|
|
seaf_warning ("Failed to remove dir %s: %lu.\n", path, GetLastError());
|
|
ret = -1;
|
|
}
|
|
} else {
|
|
seaf_warning ("Failed to remove dir %s: %lu.\n", path, GetLastError());
|
|
ret = -1;
|
|
}
|
|
|
|
out:
|
|
g_free (path_w);
|
|
return ret;
|
|
}
|
|
|
|
#endif /* WIN32 */
|
|
|
|
static int
|
|
compute_file_id_with_cdc (const char *path, SeafStat *st,
|
|
SeafileCrypt *crypt, int repo_version,
|
|
uint32_t blk_avg_size, uint32_t blk_min_size, uint32_t blk_max_size,
|
|
unsigned char sha1[])
|
|
{
|
|
CDCFileDescriptor cdc;
|
|
|
|
memset (&cdc, 0, sizeof(cdc));
|
|
cdc.block_sz = blk_avg_size;
|
|
cdc.block_min_sz = blk_min_size;
|
|
cdc.block_max_sz = blk_max_size;
|
|
cdc.write_block = seafile_write_chunk;
|
|
if (filename_chunk_cdc (path, &cdc, crypt, FALSE) < 0) {
|
|
seaf_warning ("Failed to chunk file.\n");
|
|
return -1;
|
|
}
|
|
|
|
if (repo_version > 0)
|
|
seaf_fs_manager_calculate_seafile_id_json (repo_version, &cdc, sha1);
|
|
else
|
|
memcpy (sha1, cdc.file_sum, 20);
|
|
|
|
if (cdc.blk_sha1s)
|
|
free (cdc.blk_sha1s);
|
|
|
|
return 0;
|
|
}
|
|
|
|
int
|
|
compare_file_content (const char *path, SeafStat *st, const unsigned char *ce_sha1,
|
|
SeafileCrypt *crypt, int repo_version)
|
|
{
|
|
unsigned char sha1[20];
|
|
|
|
if (st->st_size == 0) {
|
|
memset (sha1, 0, 20);
|
|
return hashcmp (sha1, ce_sha1);
|
|
} else {
|
|
if (seaf->cdc_average_block_size == 0) {
|
|
if (compute_file_id_with_cdc (path, st, crypt, repo_version,
|
|
CDC_AVERAGE_BLOCK_SIZE,
|
|
CDC_MIN_BLOCK_SIZE,
|
|
CDC_MAX_BLOCK_SIZE,
|
|
sha1) < 0) {
|
|
return -1;
|
|
}
|
|
} else {
|
|
if (compute_file_id_with_cdc (path, st, crypt, repo_version,
|
|
seaf->cdc_average_block_size,
|
|
seaf->cdc_average_block_size >> 1,
|
|
seaf->cdc_average_block_size << 1,
|
|
sha1) < 0) {
|
|
return -1;
|
|
}
|
|
}
|
|
if (hashcmp (sha1, ce_sha1) == 0)
|
|
return 0;
|
|
|
|
/* Compare with old cdc block size. */
|
|
uint32_t block_size = calculate_chunk_size (st->st_size);
|
|
if (compute_file_id_with_cdc (path, st, crypt, repo_version,
|
|
block_size,
|
|
block_size >> 2,
|
|
block_size << 2,
|
|
sha1) < 0) {
|
|
return -1;
|
|
}
|
|
return hashcmp (sha1, ce_sha1);
|
|
}
|
|
}
|
|
|
|
char *
|
|
build_checkout_path (const char *worktree, const char *ce_name, int len)
|
|
{
|
|
int base_len = strlen(worktree);
|
|
int full_len;
|
|
int offset;
|
|
SeafStat st;
|
|
|
|
if (!len) {
|
|
seaf_warning ("entry name should not be empty.\n");
|
|
return NULL;
|
|
}
|
|
|
|
GString *path = g_string_new ("");
|
|
|
|
g_string_append_printf (path, "%s/", worktree);
|
|
|
|
/* first create all leading directories. */
|
|
full_len = base_len + len + 1;
|
|
offset = base_len + 1;
|
|
while (offset < full_len) {
|
|
do {
|
|
g_string_append_c (path, ce_name[offset-base_len-1]);
|
|
offset++;
|
|
} while (offset < full_len && ce_name[offset-base_len-1] != '/');
|
|
if (offset >= full_len)
|
|
break;
|
|
|
|
if (seaf_stat (path->str, &st) == 0 && S_ISDIR(st.st_mode))
|
|
continue;
|
|
|
|
if (seaf_util_mkdir (path->str, 0777) < 0) {
|
|
g_string_free (path, TRUE);
|
|
seaf_warning ("Failed to create directory %s.\n", path->str);
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
return g_string_free (path, FALSE);
|
|
}
|
|
|
|
int
|
|
delete_path (const char *worktree, const char *name,
|
|
unsigned int mode, gint64 old_mtime)
|
|
{
|
|
char path[SEAF_PATH_MAX];
|
|
SeafStat st;
|
|
int len = strlen(name);
|
|
|
|
if (!len) {
|
|
seaf_warning ("entry name should not be empty.\n");
|
|
return -1;
|
|
}
|
|
|
|
snprintf (path, SEAF_PATH_MAX, "%s/%s", worktree, name);
|
|
|
|
if (!S_ISDIR(mode)) {
|
|
/* file doesn't exist in work tree */
|
|
if (seaf_stat (path, &st) < 0 || !S_ISREG(st.st_mode)) {
|
|
return 0;
|
|
}
|
|
|
|
/* file has been changed. */
|
|
if (!is_eml_file (name) && (old_mtime != st.st_mtime)) {
|
|
seaf_warning ("File %s is changed. Skip removing the file.\n", path);
|
|
return -1;
|
|
}
|
|
|
|
/* first unlink the file. */
|
|
if (seaf_util_unlink (path) < 0) {
|
|
seaf_warning ("Failed to remove %s: %s.\n", path, strerror(errno));
|
|
return -1;
|
|
}
|
|
} else {
|
|
if (seaf_remove_empty_dir (path) < 0) {
|
|
seaf_warning ("Failed to remove dir %s: %s.\n", path, strerror(errno));
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
/* then remove all empty directories upwards. */
|
|
/* offset = base_len + len; */
|
|
/* do { */
|
|
/* if (path[offset] == '/') { */
|
|
/* path[offset] = '\0'; */
|
|
/* int ret = seaf_remove_empty_dir (path); */
|
|
/* if (ret < 0) { */
|
|
/* break; */
|
|
/* } */
|
|
/* } */
|
|
/* } while (--offset > base_len); */
|
|
|
|
return 0;
|
|
}
|
|
|
|
#ifdef WIN32
|
|
|
|
static gboolean
|
|
check_file_locked (const wchar_t *path_w, gboolean locked_on_server)
|
|
{
|
|
HANDLE handle;
|
|
/* If the file is locked on server, its local access right has been set to
|
|
* read-only. So trying to test GENERIC_WRITE access will certainly return
|
|
* ACCESS_DENIED. In this case, we can only test for GENERIC_READ.
|
|
* MS Office seems to gain exclusive read/write access to the file. So even
|
|
* trying read access can return a SHARING_VIOLATION error.
|
|
*/
|
|
DWORD access_mode = locked_on_server ? GENERIC_READ : GENERIC_WRITE;
|
|
|
|
handle = CreateFileW (path_w,
|
|
access_mode,
|
|
0,
|
|
NULL,
|
|
OPEN_EXISTING,
|
|
0,
|
|
NULL);
|
|
if (handle != INVALID_HANDLE_VALUE) {
|
|
CloseHandle (handle);
|
|
} else if (GetLastError() == ERROR_SHARING_VIOLATION) {
|
|
return TRUE;
|
|
}
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
gboolean
|
|
do_check_file_locked (const char *path, const char *worktree, gboolean locked_on_server)
|
|
{
|
|
char *real_path;
|
|
wchar_t *real_path_w;
|
|
gboolean ret;
|
|
real_path = g_build_path(PATH_SEPERATOR, worktree, path, NULL);
|
|
real_path_w = win32_long_path (real_path);
|
|
ret = check_file_locked (real_path_w, locked_on_server);
|
|
g_free (real_path);
|
|
g_free (real_path_w);
|
|
return ret;
|
|
}
|
|
|
|
static gboolean
|
|
check_dir_locked (const wchar_t *path_w)
|
|
{
|
|
HANDLE handle;
|
|
|
|
handle = CreateFileW (path_w,
|
|
GENERIC_WRITE,
|
|
0,
|
|
NULL,
|
|
OPEN_EXISTING,
|
|
FILE_FLAG_BACKUP_SEMANTICS,
|
|
NULL);
|
|
if (handle != INVALID_HANDLE_VALUE) {
|
|
CloseHandle (handle);
|
|
} else if (GetLastError() == ERROR_SHARING_VIOLATION) {
|
|
return TRUE;
|
|
}
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
static gboolean
|
|
check_dir_locked_recursive (const wchar_t *path_w)
|
|
{
|
|
WIN32_FIND_DATAW fdata;
|
|
HANDLE handle;
|
|
wchar_t *pattern;
|
|
wchar_t *sub_path_w;
|
|
char *path;
|
|
int path_len_w;
|
|
DWORD error;
|
|
gboolean ret = FALSE;
|
|
|
|
if (check_dir_locked (path_w))
|
|
return TRUE;
|
|
|
|
path = g_utf16_to_utf8 (path_w, -1, NULL, NULL, NULL);
|
|
if (!path)
|
|
return FALSE;
|
|
|
|
path_len_w = wcslen(path_w);
|
|
|
|
pattern = g_new0 (wchar_t, (path_len_w + 3));
|
|
wcscpy (pattern, path_w);
|
|
wcscat (pattern, L"\\*");
|
|
|
|
handle = FindFirstFileW (pattern, &fdata);
|
|
if (handle == INVALID_HANDLE_VALUE) {
|
|
seaf_warning ("FindFirstFile failed %s: %lu.\n",
|
|
path, GetLastError());
|
|
goto out;
|
|
}
|
|
|
|
do {
|
|
if (wcscmp (fdata.cFileName, L".") == 0 ||
|
|
wcscmp (fdata.cFileName, L"..") == 0)
|
|
continue;
|
|
|
|
sub_path_w = g_new0 (wchar_t, path_len_w + wcslen(fdata.cFileName) + 2);
|
|
wcscpy (sub_path_w, path_w);
|
|
wcscat (sub_path_w, L"\\");
|
|
wcscat (sub_path_w, fdata.cFileName);
|
|
|
|
if (fdata.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
|
|
if (check_dir_locked_recursive (sub_path_w)) {
|
|
ret = TRUE;
|
|
g_free (sub_path_w);
|
|
goto out;
|
|
}
|
|
} else {
|
|
if (check_file_locked (sub_path_w, FALSE)) {
|
|
ret = TRUE;
|
|
g_free (sub_path_w);
|
|
goto out;
|
|
}
|
|
}
|
|
|
|
g_free (sub_path_w);
|
|
} while (FindNextFileW (handle, &fdata) != 0);
|
|
|
|
error = GetLastError();
|
|
if (error != ERROR_NO_MORE_FILES) {
|
|
seaf_warning ("FindNextFile failed %s: %lu.\n",
|
|
path, error);
|
|
}
|
|
|
|
out:
|
|
FindClose (handle);
|
|
g_free (path);
|
|
g_free (pattern);
|
|
return ret;
|
|
}
|
|
|
|
gboolean
|
|
do_check_dir_locked (const char *path, const char *worktree)
|
|
{
|
|
char *real_path = g_build_path (PATH_SEPERATOR, worktree, path, NULL);
|
|
wchar_t *real_path_w = win32_long_path (real_path);
|
|
gboolean ret = check_dir_locked_recursive (real_path_w);
|
|
g_free (real_path);
|
|
g_free (real_path_w);
|
|
return ret;
|
|
}
|
|
|
|
gboolean
|
|
files_locked_on_windows (struct index_state *index, const char *worktree)
|
|
{
|
|
gboolean ret = FALSE;
|
|
int i, entries;
|
|
struct cache_entry *ce;
|
|
|
|
entries = index->cache_nr;
|
|
for (i = 0; i < entries; ++i) {
|
|
ce = index->cache[i];
|
|
if (ce_stage(ce)) {
|
|
int mask = 0;
|
|
|
|
mask |= 1 << (ce_stage(ce) - 1);
|
|
while (i < entries) {
|
|
struct cache_entry *nce = index->cache[i];
|
|
|
|
if (strcmp(ce->name, nce->name))
|
|
break;
|
|
|
|
mask |= 1 << (ce_stage(nce) - 1);
|
|
i++;
|
|
}
|
|
i--;
|
|
|
|
/* Check unmerged cases that can potentially
|
|
update or remove current files in the worktree.
|
|
*/
|
|
if (mask == 7 || /* both changed */
|
|
mask == 6 || /* both added */
|
|
mask == 3) /* others removed */
|
|
{
|
|
if (do_check_file_locked (ce->name, worktree, FALSE)) {
|
|
ret = TRUE;
|
|
break;
|
|
}
|
|
}
|
|
} else if (ce->ce_flags & CE_UPDATE ||
|
|
ce->ce_flags & CE_WT_REMOVE) {
|
|
if (do_check_file_locked (ce->name, worktree, FALSE)) {
|
|
ret = TRUE;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
#endif /* WIN32 */
|
|
|
|
#ifdef __APPLE__
|
|
|
|
/* On Mac, we only check whether an office file is opened and locked.
|
|
* The detection is done by checking ~$* tmp file.
|
|
*/
|
|
gboolean
|
|
do_check_file_locked (const char *path, const char *worktree, gboolean locked_on_server)
|
|
{
|
|
#define OFFICE_FILE_PATTERN ".*\\.(docx|xlsx|pptx|doc|xls|ppt|vsdx)"
|
|
char *dir_name = NULL, *file_name = NULL;
|
|
char *fullpath = NULL;
|
|
char *tmp_name = NULL;
|
|
int ret = FALSE;
|
|
|
|
if (!g_regex_match_simple (OFFICE_FILE_PATTERN, path, 0, 0))
|
|
return FALSE;
|
|
|
|
dir_name = g_path_get_dirname (path);
|
|
if (strcmp (dir_name, ".") == 0) {
|
|
g_free (dir_name);
|
|
dir_name = g_strdup("");
|
|
}
|
|
file_name = g_path_get_basename (path);
|
|
|
|
tmp_name = g_strconcat ("~$", file_name, NULL);
|
|
fullpath = g_build_path ("/", worktree, dir_name, tmp_name, NULL);
|
|
if (g_file_test (fullpath, G_FILE_TEST_IS_REGULAR)) {
|
|
ret = TRUE;
|
|
goto out;
|
|
}
|
|
|
|
/* Sometimes the first two characters are replaced by ~$. */
|
|
|
|
if (g_utf8_strlen(file_name, -1) < 2)
|
|
goto out;
|
|
|
|
char *ptr = g_utf8_find_next_char(g_utf8_find_next_char (file_name, NULL), NULL);
|
|
g_free (tmp_name);
|
|
g_free (fullpath);
|
|
tmp_name = g_strconcat ("~$", ptr, NULL);
|
|
fullpath = g_build_path ("/", worktree, dir_name, tmp_name, NULL);
|
|
if (g_file_test (fullpath, G_FILE_TEST_IS_REGULAR)) {
|
|
ret = TRUE;
|
|
goto out;
|
|
}
|
|
|
|
out:
|
|
g_free (fullpath);
|
|
g_free (tmp_name);
|
|
g_free (dir_name);
|
|
g_free (file_name);
|
|
return ret;
|
|
}
|
|
|
|
#endif
|