policy change and work with where file meta info is stored

so i've been thinking... yes it's annoying but there are reasons.
it's a balance. time to change this as well as centralize it

meta info is stuff like the xxx.meta.efm files that can store icon x,y
and width x height (and any number of other things - they are .ini style
files). thumbnails are extra meta files that contain relevant info
that is auto-generated by efm's default backend. they can be large but
as it's auto-generated they are xxx.thumb.efm in these separate files

efm2 will now, if you own the dir and can write to it, store metadata
in /path/to/dir/.efm/xxx.*.efm - so either xxx.meta.efm or xxx.thumb.efm
with xxx being the matching filename in the dir. yes - this means efm
will create .efm dirs in dirs you own. first - it's a hidden dir - what are
you doing listing ALL files all the time and seeing it? dot files are
hidden by default and convention for a reason. to allow extra metadata
to live in your fs without you seeing it all the time! secondly - the
data has to go somewhere and 'find ~/ -name .efm -delete' is about as
easy (for such advanced users who are doing ls -a opr using find etc.)
as 'rm -rf ~/.e/e/efm/*' if you want to nuke the metadata.

now why do this? the principle of least surprise. you are a regular joe
user who uses a filemanager and then you run some tool (from the gui)
to zip/tar up some dir tree then unpack it somewhwre else... if efm
doesnt store the data in that tree then then unpacked files lose their
metadata. "suprise" for a user that they lost data they see visually.
surprising the user less if possible by design is a good thing.
creation of magic files automatically is also highly common - vim and
emacs save new file~ files - and people accept it because it's been
that way forever.. so a .efm dir is definitely directly in keeping
with history, precedent and the principle of least surprise. not to
mention .git, CVS and .svn dirs too.

there is a long history of this elsewhere too. windows has Thumbs.db
files. Macos has .DS_Store. AmigaOS had filename.info files. Good old
xv had .xvpics dirs. it's almost expected. the reason this is done is
to keep the metadata alongside the files where possible for least
surprise.

efm can also store it in ~/.e/e/efm/meta/ too. it will do this when
the above conditions are not met (you don't own the dir or you cannot
write to it). as the decision is made in one place with one function
to decide if you can write, this is a single point now where policy
could change this and ONLY EVER write metadata in ~/.e/e/efm/mneta no
matter if you can write or not. but that'd have to be an opt-in as it
then increases surprise loss of data for users as above. and i'ts a
"deal with it later" thing.

note - it may seem silly now like "just for some stupid thumbnails" or
"stupid x/y location i never use". this WILL be used for more later.
e.g. flagging files and/or dirs for use with rsync tooling later to
sync files/dirs or syncthing or backup tools to auto-backup changes
and so on etc. - actual functionality where you want more and more
metadata stored in your filesys5tem so tools know what to do with your
files.

if i could use xattrs for this... i would. but i can't. they don't
port between filesystems. they can't be set on symlinks and more. if
i could use xattrs - you'd never SEE this data and wouldn't complain
but as xattrs are limited this has to be stored in actual files. efm
will "enforce" hiding of these dirs as they are highly advanced stuff
in there and the only way you should (unless you like living on the
cmdline and poking around) deal with these is via the efm ui
(implicitly as you browse about and do things). if you live on a
cmdline and like to poke - you can.

oh .. i forgot.. this also adds the initla "mv" implementation... it
doesn't handle all errors and cases yet... no status info and so on...
This commit is contained in:
Carsten Haitzler 2024-03-24 21:20:02 +00:00
parent 6c06a10ba5
commit 06cea493e8
7 changed files with 390 additions and 38 deletions

View File

@ -34,3 +34,12 @@ executable('thumb', [
dependencies: deps,
install: true,
install_dir: dir)
executable('mv', [
'../../shared/sha.c',
'mv.c',
'meta.c'
],
include_directories: inc,
dependencies: deps,
install: true,
install_dir: dir)

View File

@ -3,6 +3,8 @@
#include <Efreet.h>
#include <Ecore.h>
#include <Ecore_File.h>
#include <fcntl.h>
#include "eina_types.h"
#include "sha.h"
#include "meta.h"
@ -27,6 +29,8 @@
// to allow scripts, tools etc. to work with and generate data etc. then
// this is the compromise choice taken.
#define META_WRITE_TIMEOUT 0.2
typedef struct _Meta_File
{
const char *path; // path of original file this metadata is for
@ -48,29 +52,41 @@ static Ecore_Timer *_meta_flush_timer = NULL;
///////////////////////////////////////////////////////////////////////////////
static char *
_meta_personal_overlay_file_get(Meta_File *mf)
{ // get secondary personal path to meta file for target path
_meta_personal_overlay_file_path_get(const char *path, const char *extn)
{
unsigned char dst[20];
char sha1[41], buf[PATH_MAX];
eina_sha1((unsigned char *)mf->path, strlen(mf->path), dst);
eina_sha1((unsigned char *)path, strlen(path), dst);
sha1_str(dst, sha1);
snprintf(buf, sizeof(buf), "%s/efm/meta/%c%c/%s.efm", _config_dir, sha1[0],
sha1[1], sha1 + 2);
snprintf(buf, sizeof(buf), "%s/efm/meta/%c%c/%s.%s",
_config_dir, sha1[0], sha1[1], sha1 + 2, extn);
return strdup(buf);
}
static char *
_meta_personal_overlay_file_get(Meta_File *mf)
{ // get secondary personal path to meta file for target path
return _meta_personal_overlay_file_path_get(mf->path, "meta.efm");
}
static char *
_meta_file_path_get(const char *path, const char *extn)
{ // get primary meta file for the target path
char *dir = ecore_file_dir_get(path);
char buf[PATH_MAX];
if (!dir) return NULL;
snprintf(buf, sizeof(buf), "%s/.efm/%s.%s",
dir, ecore_file_file_get(path), extn);
free(dir);
return strdup(buf);
}
static char *
_meta_file_get(Meta_File *mf)
{ // get primy meta file for the target path
char *dir = ecore_file_dir_get(mf->path);
char buf[PATH_MAX];
if (!dir) return NULL;
snprintf(buf, sizeof(buf), "%s/.efm/%s.efm", dir,
ecore_file_file_get(mf->path));
free(dir);
return strdup(buf);
{
return _meta_file_path_get(mf->path, "meta.efm");
}
static void
@ -162,7 +178,9 @@ static void
_meta_file_write_queue(Meta_File *mf)
{ // tag the meta file as changed and queue some writes
if (_meta_flush_timer) ecore_timer_reset(_meta_flush_timer);
else _meta_flush_timer = ecore_timer_add(0.2, _cb_meta_flush_timer, NULL);
else _meta_flush_timer = ecore_timer_add(META_WRITE_TIMEOUT,
_cb_meta_flush_timer,
NULL);
if (!mf->changed)
{
_meta_writes = eina_list_append(_meta_writes, mf);
@ -298,7 +316,7 @@ meta_shutdown(void)
void
meta_set(const char *path, const char *meta, const char *data)
{ // data = NULL -> delete meta
{ // set meta data key "meta" on ffile "Path", data = NULL -> delete meta
Meta_File *mf = _meta_file_find(path);
Eina_List *l;
Meta *m;
@ -332,7 +350,7 @@ meta_set(const char *path, const char *meta, const char *data)
Eina_Stringshare *
meta_get(const char *path, const char *meta)
{
{ // get meta data key "meta" associated with file "path"
Meta_File *mf = _meta_file_find(path);
Eina_List *l;
Meta *m;
@ -344,3 +362,82 @@ meta_get(const char *path, const char *meta)
}
return NULL;
}
char *
meta_path_find(const char *path, const char *extn)
{ // "path" is the file path to get the filesystem local meta file for
return _meta_file_path_get(path, extn);
}
char *
meta_path_user_find(const char *path, const char *extn)
{ // "path" is the target file path to get the user overlay file for
return _meta_personal_overlay_file_path_get(path, extn);
}
Eina_Bool
meta_path_prepare(const char *path)
{ // "path" is the path returned by meta_path_user_find() or meta_path_find()
Eina_Bool ret = EINA_FALSE;
char *dir = ecore_file_dir_get(path);
if (!dir) return ret;
if (ecore_file_is_dir(dir)) ret = EINA_TRUE;
else
{
if (ecore_file_mkdir(dir)) ret = EINA_TRUE;
else if (ecore_file_mkpath(dir)) ret = EINA_TRUE;
}
free(dir);
return ret;
}
Eina_Bool
meta_path_can_write(const char *path)
{ // can we write to the taget dir of "path" file for meta data?
// yes - this is racey. but then again anything is. we could make a
// .efm dir then have permission removed before we make the subdiors
// or meta files. anything that involves more than a single file and
// a single open is going to suffer - deal with it. as this is expensive
// and modifies the fs just to test it it is a good idea to only call this
// once when opening a dir (or doing something that may write to it)
char buf[PATH_MAX];
struct stat st;
Eina_Bool ret = EINA_FALSE;
int res = 0;
char *dir = ecore_file_dir_get(path);
uid_t uid = getuid();
if (!dir) return ret;
if (stat(dir, &st) == -1) goto err;
// policy - we only cosider direst owned by the user writable. want to
// avoid e.g. root browsing then modifying dirs owne by a user or dirs
// that might have group write access being written to by multiple users
if ((st.st_uid == uid) && (st.st_mode & S_IWUSR))
{
snprintf(buf, sizeof(buf), "%s/.efm", dir);
res = mkdir(buf, S_IRUSR | S_IWUSR | S_IXUSR | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH);
if (res == 0)
{ // we created the dir - remove it (if not empty) - might not need
rmdir(buf);
ret = EINA_TRUE;
}
else
{
if (errno == EEXIST)
{ // dir exists - test we can create a file in it
snprintf(buf, sizeof(buf), "%s/.efm/.t", dir);
res = open(buf, O_WRONLY | O_CREAT | O_CLOEXEC);
if (res >= 0)
{ // we can create and write to file - remove it now
unlink(buf);
close(res);
ret = EINA_TRUE;
}
}
}
}
err:
free(dir);
return ret;
}

View File

@ -1,4 +1,16 @@
void meta_init(const char *config_dir);
void meta_shutdown(void);
void meta_set(const char *path, const char *meta, const char *data);
#ifndef META_H
#define META_H
#include <Eina.h>
void meta_init(const char *config_dir);
void meta_shutdown(void);
void meta_set(const char *path, const char *meta, const char *data);
Eina_Stringshare *meta_get(const char *path, const char *meta);
char *meta_path_find(const char *path, const char *extn);
char *meta_path_user_find(const char *path, const char *extn);
Eina_Bool meta_path_prepare(const char *path);
Eina_Bool meta_path_can_write(const char *path);
#endif

176
src/backends/default/mv.c Normal file
View File

@ -0,0 +1,176 @@
#include <Eina.h>
#include <Ecore.h>
#include <Ecore_File.h>
#include <Efreet.h>
#include <Efreet_Mime.h>
#include <Eet.h>
#include <asm-generic/errno-base.h>
#include <asm-generic/errno.h>
#include <sys/types.h>
#include <pwd.h>
#include <grp.h>
#include "cmd.h"
#include "eina_strbuf.h"
#include "sha.h"
#include "meta.h"
static const char *config_dir = NULL;
int
main(int argc, char **argv)
{
// mv [src] [dst]
const char *src, *dst, *fname, *home_dir;
char *fdir;
Eina_Strbuf *buf = NULL, *buf2 = NULL;
if (argc < 3) return -1;
src = argv[1];
dst = argv[2];
eina_init();
eet_init();
ecore_init();
efreet_init();
config_dir = getenv("E_HOME_DIR");
home_dir = getenv("HOME");
if (!home_dir) return 77; // no $HOME? definitely an error!
if (!config_dir)
{
char buf[PATH_MAX];
snprintf(buf, sizeof(buf), "%s/.e/e", home_dir);
config_dir = eina_stringshare_add(buf);
}
meta_init(config_dir);
// src = SDIR/SFILE
// dst = DDIR/
//
// mv SDIR/SFILE DDIR/SFILE
// if errno == EXDEV
// cp SDIR/SFILE DDIR/SFILE
// rm SDIR/SFILE
// else if errno == 0
// if exists SDIR/.efm/SFILE.efm
// mkdir DDIR/.efm
// mv SDIR/.efm/SFILE.efm DDIR/.efm/SFILE.efm
// status in
// EFMDIR/status/uuid.status
//
// status file:
// uuid (UUID) # uuid repeated from filename
// pid (PID) @ pid of task
// type (operation type - mv, cp, rm, ...) # op type
// busy (0/1) # show a busy spinner instead of progress
// progress (0-100) # show a progress bar 0-100 percent
// title (some title string to show)
// label (somt string to show) # show this string
// icon (some icon name or full path to file)
// ---
//
// check /proc/PID/cmdline so argv[1] is same uuid
// write status in one write() as it'll be less than 4k
fname = ecore_file_file_get(src);
if (!fname) goto err;
buf = eina_strbuf_new();
// XXX: if buf is null?
eina_strbuf_append(buf, dst);
eina_strbuf_append(buf, "/");
eina_strbuf_append(buf, fname);
// XXX: write out status file and update it
// XXX: ecore_file_mv() ? <- look at it
if (rename(src, eina_strbuf_string_get(buf)) == -1)
{
fprintf(stderr, "MV: %s\n", strerror(errno));
switch (errno)
{
case EACCES:
case EPERM: // permission denied
break;
case EBUSY: // file or dir is busy
break;
case EDQUOT: // no quota left
break;
case EFAULT: // should not happen - but src or dst outside addr space
break;
case EINVAL: // moving into a sub path of itsefl - invalid
break;
case EISDIR: // moving file on top of dir
break;
case ELOOP: // too many symlinks in a loop
break;
case EMLINK: // max links exceeded
break;
case ENAMETOOLONG: // path too long (src or dst)
break;
case ENOENT: // src or dst doesn't exist
break;
case ENOMEM: // out of mem
break;
case ENOSPC: // out of space on disk
break;
case ENOTDIR: // moving dir on top of file
break;
case ENOTEMPTY:
case EEXIST: // dst is not an empty dir
break;
case EROFS: // read only disk
break;
case EXDEV: // revert to cp -par
break;
default: // WAT???
break;
}
}
else
{
fprintf(stderr, "MV: OK\n");
// XXX: use meta_path_find() or meta_path_user_find() based on
// XXX: meta_path_can_write() and prep the dest with meta_path_prepare()
fdir = ecore_file_dir_get(src);
if (fdir)
{
// mv the efm meta file if it exists...
buf2 = eina_strbuf_new();
// XXX" if buf2 is null?
eina_strbuf_append(buf2, dst);
eina_strbuf_append(buf2, "/.efm");
ecore_file_mkdir(eina_strbuf_string_get(buf2));
eina_strbuf_reset(buf2);
eina_strbuf_append(buf2, fdir);
eina_strbuf_append(buf2, "/.efm/");
eina_strbuf_append(buf2, fname);
eina_strbuf_append(buf2, ".efm");
eina_strbuf_reset(buf);
eina_strbuf_append(buf, dst);
eina_strbuf_append(buf, "/.efm/");
eina_strbuf_append(buf, fname);
eina_strbuf_append(buf, ".efm");
// XXX: what if the target meta file already exists???
rename(eina_strbuf_string_get(buf2), eina_strbuf_string_get(buf));
free(fdir);
// XXX: mv meta if in ~/.e/e/efm/meta
// XXX: mv thumb files in /.e/e/efm/thumbs
}
}
err:
if (buf) eina_strbuf_free(buf);
if (buf2) eina_strbuf_free(buf2);
// XXX: delete status file
meta_shutdown();
efreet_shutdown();
ecore_shutdown();
eet_shutdown();
eina_shutdown();
return 0;
}

View File

@ -6,12 +6,8 @@
// the front end that consumes the output of this should do NO file access
// itself at all and do everything via fs handlers for open, delete, rename
// copy, import, export etc.
#include "cmd.h"
#include "ecore_exe_eo.legacy.h"
#include "eina_stringshare.h"
#include "eina_types.h"
#include "sha.h"
#include "meta.h"
#include <Eina.h>
#include <Ecore.h>
#include <Ecore_File.h>
#include <Efreet.h>
#include <Efreet_Mime.h>
@ -21,6 +17,10 @@
#include <pwd.h>
#include <grp.h>
#include "cmd.h"
#include "eina_strbuf.h"
#include "sha.h"
#include "meta.h"
#include "thumb_check.h"
static const char *icon_theme = NULL;
@ -28,6 +28,7 @@ static const char *config_dir = NULL;
static const char *home_dir = NULL;
static Ecore_File_Monitor *mon = NULL;
static Eina_Bool can_write = EINA_FALSE;
typedef struct
{
@ -381,14 +382,13 @@ _file_thumb(const char *path EINA_UNUSED, const char *mime)
static char *
_file_thumb_find(const char *path, const char *mime EINA_UNUSED)
{ // find the thumb file
unsigned char sha[20];
char buf[PATH_MAX], shastr[41];
char *thumb;
eina_sha1((const unsigned char *)path, strlen(path), sha);
sha1_str(sha, shastr);
snprintf(buf, sizeof(buf), "%s/efm/thumbs/%c%c/%s.eet", config_dir, shastr[0],
shastr[1], shastr + 2);
return strdup(buf);
if (can_write) thumb = meta_path_find(path, "thumb.efm");
else thumb = meta_path_user_find(path, "thumb.efm");
if (!thumb) return NULL;
meta_path_prepare(thumb);
return thumb;
}
static Eina_Bool
@ -491,7 +491,13 @@ _file_thumb_handle(Eina_Strbuf *strbuf, const char *path, const char *mime,
if (!_file_thumb(path, mime)) return;
// get what the path to the target thumb should be
thumb = _file_thumb_find(path, mime);
thumb = meta_path_user_find(path, "thumb.efm");
if (!ecore_file_exists(thumb))
{
free(thumb);
thumb = meta_path_find(path, "thumb.efm");
}
if (thumb)
{ // open the thumb and let's see if the stat info is up to date
Eet_File *ef = eet_open(thumb, EET_FILE_MODE_READ);
@ -1185,12 +1191,18 @@ _monitor(const char *path)
if (abort_list) return;
strbuf = eina_strbuf_new();
eina_strbuf_append(strbuf, path);
eina_strbuf_append(strbuf, ".efm");
can_write = meta_path_can_write(eina_strbuf_string_get(strbuf));
if (strbuf) eina_strbuf_free(strbuf);
// tell the front end out listing is beginning
strbuf = cmd_strbuf_new("list-begin");
cmd_strbuf_print_consume(strbuf);
mon = ecore_file_monitor_add(path, _cb_mon, NULL);
it = eina_file_direct_ls(path);
it = eina_file_direct_ls(path);
if (!it)
{
// XXX: error output
@ -1209,7 +1221,7 @@ _path_in_mon_dir(const char *path)
{
Eina_Bool res = EINA_FALSE;
const char *mondir;
char *filedir = ecore_file_dir_get(path);
char *filedir = ecore_file_dir_get(path);
if (!filedir) return res;
if (!mon) goto done;
@ -1221,6 +1233,30 @@ _path_in_mon_dir(const char *path)
return res;
}
static void
_op_run(const char *op, const char *src, const char *dst)
{
Eina_Strbuf *buf;
const char *s;
buf = eina_strbuf_new();
s = getenv("EFM_BACKEND_DIR");
if (!s) return;
eina_strbuf_append(buf, s);
eina_strbuf_append(buf, "/");
eina_strbuf_append(buf, op);
eina_strbuf_append(buf, " ");
_strbuf_append_file_escaped(buf, src);
if (dst)
{
eina_strbuf_append(buf, " ");
_strbuf_append_file_escaped(buf, dst);
}
fprintf(stderr, "OP: [%s]\n", eina_strbuf_string_get(buf));
ecore_exe_run(eina_strbuf_string_get(buf), NULL);
eina_strbuf_free(buf);
}
static void
_handle_drop_paste(const char *over, const char *action, const char *path)
{
@ -1248,8 +1284,26 @@ _handle_drop_paste(const char *over, const char *action, const char *path)
{
fprintf(stderr, "DROP in [%s] action=[%s] > [%s]\n", mondir, action,
path);
// XXX: do the real operation here
// XXX: action = copy, move, ask, list, link, description
if ((!action) || (!strcmp(action, "copy")))
{
}
else if (!strcmp(action, "move"))
_op_run("mv", path, mondir);
else if (!strcmp(action, "ask"))
{
}
else if (!strcmp(action, "list"))
{
}
else if (!strcmp(action, "link"))
{
}
else if (!strcmp(action, "description"))
{
}
else
{
}
}
}

View File

@ -1,4 +1,5 @@
#include "thumb.h"
#include "meta.h"
Evas_Object *win = NULL;
Evas_Object *subwin = NULL;

View File

@ -1,3 +1,5 @@
#ifndef THUMB_H
#define THUMB_H
#include <Elementary.h>
#include "efm_config.h"
#include "sha.h"
@ -83,3 +85,4 @@ scale_out(int *w, int *h, int maxw, int maxh, Eina_Bool no_scale_up)
*w = ww;
*h = hh;
}
#endif