diff --git a/pd/src/g_array.c b/pd/src/g_array.c index a2b133bcfede06c5caa7e6f9a828e38467b4575f..661dc13b6b4023df4be8d15c94d414e381cd021b 100644 --- a/pd/src/g_array.c +++ b/pd/src/g_array.c @@ -7,6 +7,7 @@ #include <stdio.h> /* for read/write to files */ #include "m_pd.h" #include "g_canvas.h" +#include "g_all_guis.h" #include <math.h> #include <ctype.h> @@ -134,6 +135,7 @@ struct _garray char x_saveit; /* true if we should save this with parent */ char x_joc; /* true if we should "jump on click" in a graph */ char x_hidename; /* don't print name above graph */ + char x_edit; /* true if we can edit the array */ int x_style; /* so much simpler to keep it here */ t_symbol *x_send; /* send_changed hook */ t_symbol *x_fillcolor; /* filled area of bar in bar graph */ @@ -145,8 +147,8 @@ t_pd *garray_floattemplatecanvas; static char garray_arraytemplatefile[] = "\ #N canvas 0 0 458 153 10;\n\ #X obj 43 31 struct _float_array array z float float style\n\ -float linewidth float color symbol fillcolor symbol outlinecolor;\n\ -#X obj 43 70 plot z color linewidth 0 0 1 style fillcolor outlinecolor;\n\ +float linewidth float color symbol fillcolor symbol outlinecolor float v;\n\ +#X obj 43 70 plot -v v z color linewidth 0 0 1 style fillcolor outlinecolor;\n\ "; static char garray_floattemplatefile[] = "\ #N canvas 0 0 458 153 10;\n\ @@ -196,6 +198,7 @@ static t_garray *graph_scalar(t_glist *gl, t_symbol *s, t_symbol *templatesym, x->x_outlinecolor = outline; x->x_scalar = scalar_new(gl, templatesym); x->x_name = s; + x->x_edit = 1; x->x_realname = canvas_realizedollar(gl, s); pd_bind(&x->x_gobj.g_pd, x->x_realname); x->x_usedindsp = 0; @@ -508,6 +511,7 @@ t_garray *graph_array(t_glist *gl, t_symbol *s, int argc, t_atom *argv) x->x_style, 1); template_setfloat(template, gensym("linewidth"), x->x_scalar->sc_vec, ((x->x_style == PLOTSTYLE_POINTS) ? 2 : 1), 1); + template_setfloat(template, gensym("v"), x->x_scalar->sc_vec, 1, 1); template_setsymbol(template, gensym("fillcolor"), x->x_scalar->sc_vec, fill, 1); template_setsymbol(template, gensym("outlinecolor"), x->x_scalar->sc_vec, @@ -1392,6 +1396,8 @@ static int garray_click(t_gobj *z, t_glist *glist, int xpix, int ypix, int shift, int alt, int dbl, int doit) { t_garray *x = (t_garray *)z; + if (!x->x_edit) + return 0; array_garray = x; return (gobj_click(&x->x_scalar->sc_gobj, glist, xpix, ypix, shift, alt, dbl, doit)); @@ -1826,6 +1832,62 @@ static void garray_ylabel(t_garray *x, t_symbol *s, int argc, t_atom *argv) { typedmess(&x->x_glist->gl_pd, s, argc, argv); } + +static void garray_style(t_garray *x, t_floatarg fstyle) { + x->x_style = (int)fstyle; + template_setfloat(template_findbyname(x->x_scalar->sc_template), gensym("style"), x->x_scalar->sc_vec, (t_float)x->x_style, 0); + glist_redraw(x->x_glist); +} + +static t_symbol* garray_convertcolor(int argc, t_atom *argv) { + if(argv->a_type == A_SYMBOL) { + return argv->a_w.w_symbol; + } else { + int col = argv->a_w.w_float; + int bitsPerChannel; + char* hexCol[8]; + + if(col >= 0) { + col = iemgui_color_hex[col % 30]; + bitsPerChannel = 8; + } else { + col = -1 - col; + bitsPerChannel = 6; + } + + int r = (col & ((1 << bitsPerChannel) - 1)) << (8 - bitsPerChannel); + int g = (col & ((1 << (bitsPerChannel * 2)) - 1)) << (8 - bitsPerChannel) * 2; + int b = (col & ((1 << (bitsPerChannel * 3)) - 1)) << (8 - bitsPerChannel) * 3; + + snprintf(hexCol, sizeof(hexCol), "#%06x", (r | g | b) & 0xFFFFFF); + + return gensym(hexCol); + } +} + +static void garray_fillcolor(t_garray *x, t_symbol *s, int argc, t_atom *argv) { + x->x_fillcolor = garray_convertcolor(argc, argv); + template_setsymbol(template_findbyname(x->x_scalar->sc_template), gensym("fillcolor"), x->x_scalar->sc_vec, x->x_fillcolor, 1); + glist_redraw(x->x_glist); +} + +static void garray_outlinecolor(t_garray *x, t_symbol *s, int argc, t_atom *argv) { + x->x_outlinecolor = garray_convertcolor(argc, argv); + template_setsymbol(template_findbyname(x->x_scalar->sc_template), gensym("outlinecolor"), x->x_scalar->sc_vec, x->x_outlinecolor, 1); + glist_redraw(x->x_glist); +} + +static void garray_vis_msg(t_garray *x, t_floatarg fvis) +{ + template_setfloat(template_findbyname(x->x_scalar->sc_template), gensym("v"), x->x_scalar->sc_vec, fvis, 0); + glist_redraw(x->x_glist); +} + +static void garray_width(t_garray *x, t_floatarg fvis) +{ + template_setfloat(template_findbyname(x->x_scalar->sc_template), gensym("linewidth"), x->x_scalar->sc_vec, fvis, 0); + glist_redraw(x->x_glist); +} /* change the name of a garray. */ static void garray_rename(t_garray *x, t_symbol *s) { @@ -1943,6 +2005,11 @@ void garray_resize(t_garray *x, t_floatarg f) garray_resize_long(x, f); } +static void garray_edit(t_garray *x, t_floatarg f) +{ + x->x_edit = (int)f; +} + static void garray_print(t_garray *x) { t_array *array = garray_getarray(x); @@ -1974,6 +2041,18 @@ void g_array_setup(void) A_FLOAT, A_FLOAT, A_FLOAT, 0); class_addmethod(garray_class, (t_method)garray_ylabel, gensym("ylabel"), A_GIMME, 0); + class_addmethod(garray_class, (t_method)garray_style, gensym("style"), + A_FLOAT, 0); + class_addmethod(garray_class, (t_method)garray_width, gensym("width"), + A_FLOAT, 0); + class_addmethod(garray_class, (t_method)garray_outlinecolor, gensym("color"), + A_GIMME, 0); // This is duplicated alongside outlinecolor for pure-data compatibility + class_addmethod(garray_class, (t_method)garray_outlinecolor, gensym("outlinecolor"), + A_GIMME, 0); + class_addmethod(garray_class, (t_method)garray_fillcolor, gensym("fillcolor"), + A_GIMME, 0); + class_addmethod(garray_class, (t_method)garray_vis_msg, gensym("vis"), + A_FLOAT, 0); class_addmethod(garray_class, (t_method)garray_rename, gensym("rename"), A_SYMBOL, 0); class_addmethod(garray_class, (t_method)garray_read, gensym("read"), @@ -1982,6 +2061,8 @@ void g_array_setup(void) A_SYMBOL, A_NULL); class_addmethod(garray_class, (t_method)garray_resize, gensym("resize"), A_FLOAT, A_NULL); + class_addmethod(garray_class, (t_method)garray_edit, gensym("edit"), + A_FLOAT, 0); class_addmethod(garray_class, (t_method)garray_print, gensym("print"), A_NULL); class_addmethod(garray_class, (t_method)garray_sinesum, gensym("sinesum"), diff --git a/pd/src/g_canvas.c b/pd/src/g_canvas.c index 3f844260063db6fb7677418587ed474e28461116..19516a3269c6b1a853ef19b9197b9436c72bc981 100644 --- a/pd/src/g_canvas.c +++ b/pd/src/g_canvas.c @@ -151,6 +151,28 @@ static void canvas_takeofflist(t_canvas *x) } } +/* turn message tracing on and off globally */ + +void obj_dosettracing(t_object *ob, int onoff); +static void canvas_dosettracing(t_canvas *x, int onoff) +{ + t_gobj *y; + for (y = x->gl_list; y; y = y->g_next) + { + t_object *ob; + if (pd_class(&y->g_pd) == canvas_class) + canvas_dosettracing((t_canvas *)y, onoff); + if ((ob = pd_checkobject(&y->g_pd))) + obj_dosettracing(ob, onoff); + } +} + +void canvas_settracing(int onoff) +{ + t_glist *gl; + for (gl = pd_this->pd_canvaslist; gl; gl = gl->gl_next) + canvas_dosettracing(gl, onoff); +} void canvas_setargs(int argc, t_atom *argv) { diff --git a/pd/src/m_conf.c b/pd/src/m_conf.c index a7c7d3e22f9a0c62b5fc59c28d19a12c2cd1fd17..dbed1e26eb4d02ff5ab794e650865c7160ffdd9f 100644 --- a/pd/src/m_conf.c +++ b/pd/src/m_conf.c @@ -36,6 +36,7 @@ void x_array_setup(void); void x_midi_setup(void); void x_misc_setup(void); void x_net_setup(void); +void x_file_setup(void); void x_qlist_setup(void); void x_gui_setup(void); void x_list_setup(void); @@ -89,6 +90,7 @@ void conf_init(void) x_midi_setup(); x_misc_setup(); x_net_setup(); + x_file_setup(); x_qlist_setup(); x_gui_setup(); x_list_setup(); diff --git a/pd/src/m_glob.c b/pd/src/m_glob.c index 0986bb93ada9e00b26b6d7b05515df23cdc090d8..cec9231fb4e610cfa6f1a50087063b0cf6b67714 100644 --- a/pd/src/m_glob.c +++ b/pd/src/m_glob.c @@ -47,6 +47,7 @@ void glob_forward_files_from_secondary_instance(void); void glob_recent_files(t_pd *dummy); void glob_add_recent_file(t_pd *dummy, t_symbol *s); void glob_clear_recent_files(t_pd *dummy); +void glob_settracing(void *dummy, t_float f); void glob_open(t_pd *ignore, t_symbol *name, t_symbol *dir, t_floatarg f); void alsa_resync( void); @@ -233,6 +234,8 @@ void glob_init(void) gensym("clear-recent-files"), 0); class_addmethod(glob_pdobject, (t_method)glob_gui_busy, gensym("gui-busy"), A_DEFFLOAT, 0); + class_addmethod(glob_pdobject, (t_method)glob_settracing, + gensym("set-tracing"), A_FLOAT, 0); #ifdef UNIX class_addmethod(glob_pdobject, (t_method)glob_watchdog, gensym("watchdog"), 0); diff --git a/pd/src/m_obj.c b/pd/src/m_obj.c index 3ea7d647ab277ac66eea593dd95a50aadfdfbc59..e9dfcf7c4f105799f16f05be7200d65d036557f7 100644 --- a/pd/src/m_obj.c +++ b/pd/src/m_obj.c @@ -9,6 +9,7 @@ behavior for "gobjs" appears at the end of this file. */ #include "m_pd.h" #include "m_imp.h" #include <stdio.h> +#include <string.h> #ifdef _WIN32 # include <malloc.h> /* MSVC or mingw on windows */ @@ -354,48 +355,72 @@ void obj_list(t_object *x, t_symbol *s, int argc, t_atom *argv) else pd_symbol(&x->ob_pd, argv->a_w.w_symbol); } -void obj_init(void) -{ - inlet_class = class_new(gensym("inlet"), 0, 0, - sizeof(t_inlet), CLASS_PD, 0); - class_addbang(inlet_class, inlet_bang); - class_addpointer(inlet_class, inlet_pointer); - class_addfloat(inlet_class, inlet_float); - class_addsymbol(inlet_class, inlet_symbol); - class_addblob(inlet_class, inlet_blob); /* MP 20061226 blob type */ - class_addlist(inlet_class, inlet_list); - class_addanything(inlet_class, inlet_anything); +/* --------------------------- outlets ------------------------------ */ - pointerinlet_class = class_new(gensym("inlet"), 0, 0, - sizeof(t_inlet), CLASS_PD, 0); - class_addpointer(pointerinlet_class, pointerinlet_pointer); - class_addanything(pointerinlet_class, inlet_wrong); - - floatinlet_class = class_new(gensym("inlet"), 0, 0, - sizeof(t_inlet), CLASS_PD, 0); - class_addfloat(floatinlet_class, (t_method)floatinlet_float); - class_addanything(floatinlet_class, inlet_wrong); +struct _outconnect +{ + struct _outconnect *oc_next; + t_pd *oc_to; + int oc_vis; // whether the connection should be visible +}; - symbolinlet_class = class_new(gensym("inlet"), 0, 0, - sizeof(t_inlet), CLASS_PD, 0); - class_addsymbol(symbolinlet_class, symbolinlet_symbol); - class_addanything(symbolinlet_class, inlet_wrong); +int outconnect_visible(t_outconnect *oc) +{ + return oc->oc_vis; +} +void outconnect_setvisible(t_outconnect *oc, int vis) +{ + oc->oc_vis = vis; } -/* --------------------------- outlets ------------------------------ */ +struct _outlet +{ + t_object *o_owner; + struct _outlet *o_next; + t_outconnect *o_connections; + t_symbol *o_sym; +}; + +/* ------- backtracer - keep track of stack for backtracing --------- */ + +#define NARGS 5 +typedef struct _msgstack +{ + struct _backtracer *m_owner; + t_symbol *m_sel; + int m_argc; + t_atom m_argv[NARGS]; + struct _msgstack *m_next; +} t_msgstack; + +typedef struct _backtracer +{ + t_pd b_pd; + t_outconnect *b_connections; + t_pd *b_owner; +} t_backtracer; + +static t_msgstack *backtracer_stack; +int backtracer_cantrace = 0; +int backtracer_tracing; +t_class *backtracer_class; static int stackcount = 0; /* iteration counter */ #define STACKITER 1000 /* maximum iterations allowed */ static int outlet_eventno; - /* set a stack limit (on each incoming event that can set off messages) - for the outlet functions to check to prevent stack overflow from message - recursion */ + /* initialize stack depth count on each incoming event that can set off + messages so that the outlet functions can check to prevent stack overflow] + from message recursion. Also count message initiations. */ void outlet_setstacklim(void) { + t_msgstack *m; + while ((m = backtracer_stack)) + backtracer_stack = m->m_next; t_freebytes(m, sizeof (*m)); + stackcount = 0; outlet_eventno++; } @@ -405,30 +430,155 @@ int sched_geteventno( void) return (outlet_eventno); } -struct _outconnect + /* get pointer to connection list for an outlet (for editing/traversing) */ +static t_outconnect **outlet_getconnectionpointer(t_outlet *x) { - struct _outconnect *oc_next; - t_pd *oc_to; - int oc_vis; // whether the connection should be visible -}; + if (x->o_connections && *(x->o_connections->oc_to) == backtracer_class) + return (&((t_backtracer *)(x->o_connections->oc_to))->b_connections); + else return (&x->o_connections); +} -int outconnect_visible(t_outconnect *oc) +static void backtracer_printmsg(t_pd *who, t_symbol *s, + int argc, t_atom *argv) { - return oc->oc_vis; + char msgbuf[104]; + int nprint = (argc > NARGS ? NARGS : argc), nchar, i; + snprintf(msgbuf, 100, "%s: %s ", class_getname(*who), s->s_name); + nchar = strlen(msgbuf); + for (i = 0; i < nprint && nchar < 100; i++) + if (nchar < 100) + { + char buf[100]; + atom_string(&argv[i], buf, 100); + snprintf(msgbuf + nchar, 100-nchar, " %s", buf); + nchar = strlen(msgbuf); + } + if (argc > nprint && nchar < 100) + sprintf(msgbuf + nchar, "..."); + else memcpy(msgbuf+100, "...", 4); /* in case we didn't finish */ + logpost(who, 2, "%s", msgbuf); } -void outconnect_setvisible(t_outconnect *oc, int vis) +static void backtracer_anything(t_backtracer *x, t_symbol *s, + int argc, t_atom *argv) { - oc->oc_vis = vis; + t_msgstack *m = (t_msgstack *)t_getbytes(sizeof(t_msgstack)); + t_outconnect *oc; + int ncopy = (argc > NARGS ? NARGS : argc), i; + m->m_next = backtracer_stack; + backtracer_stack = m; + m->m_sel = s; + m->m_argc = argc; + for (i = 0; i < ncopy; i++) + m->m_argv[i] = argv[i]; + m->m_owner = x; + if (backtracer_tracing) + backtracer_printmsg(x->b_owner, s, argc, argv); + for (oc = x->b_connections; oc; oc = oc->oc_next) + typedmess(oc->oc_to, s, argc, argv); + backtracer_stack = m->m_next; + t_freebytes(m, sizeof(*m)); } -struct _outlet +t_backtracer *backtracer_new(t_pd *owner) { - t_object *o_owner; - struct _outlet *o_next; - t_outconnect *o_connections; - t_symbol *o_sym; -}; + t_backtracer *x = (t_backtracer *)pd_new(backtracer_class); + x->b_connections = 0; + x->b_owner = owner; + return (x); +} + +int backtracer_settracing(void *x, int tracing) +{ + if (tracing) + { + if (backtracer_tracing) + { + pd_error(x, "trace: already tracing"); + return (0); + } + else + { + backtracer_tracing = 1; + return (1); + } + } + else /* when stopping, print backtrace to here */ + { + t_msgstack *m = backtracer_stack; + post("backtrace:"); + while (m) + { + backtracer_printmsg(m->m_owner->b_owner, m->m_sel, + m->m_argc, m->m_argv); + m = m->m_next; + } + backtracer_tracing = 0; + return (0); + } +} + +void canvas_settracing(int onoff); +static t_clock *backtrace_unsetclock; + +static void backtrace_dounsettracing(void *dummy) +{ + canvas_settracing(0); + backtracer_cantrace = 0; + clock_free(backtrace_unsetclock); + backtrace_unsetclock = 0; +} + + /* globally turn tracing on and off. */ +void glob_settracing(void *dummy, t_float f) +{ +#ifndef PDINSTANCE /* this won't work with pd instances so just don't */ + if (f != 0) + { + if (backtracer_cantrace) + post("pd: tracing already enabled"); + else canvas_settracing(1); + backtracer_cantrace = 1; + } + else + { + if (!backtracer_cantrace) + post("pd: tracing already disabled"); + else if (!backtrace_unsetclock) + { + backtrace_unsetclock = clock_new(dummy, + (t_method)backtrace_dounsettracing); + clock_delay(backtrace_unsetclock, 0); + } + } +#endif +} + + /* this is called on every object, via canvas_settracing() call above */ +void obj_dosettracing(t_object *ob, int onoff) +{ + t_outlet *o; + for (o = ob->ob_outlet; o; o = o->o_next) + { + if (onoff) + { + t_backtracer *b = backtracer_new(&ob->ob_pd); + b->b_connections = o->o_connections; + o->o_connections = (t_outconnect *)t_getbytes(sizeof(t_outconnect)); + o->o_connections->oc_next = 0; + o->o_connections->oc_to = &b->b_pd; + } + else if (o->o_connections && + (*o->o_connections->oc_to == backtracer_class)) + { + t_backtracer *b = (t_backtracer *)o->o_connections->oc_to; + t_freebytes(o->o_connections, sizeof(*o->o_connections)); + o->o_connections = b->b_connections; + t_freebytes(b, sizeof(*b)); + } + else bug("obj_dosettracing"); + } +} t_outlet *outlet_new(t_object *owner, t_symbol *s) { @@ -441,7 +591,14 @@ t_outlet *outlet_new(t_object *owner, t_symbol *s) y->o_next = x; } else owner->ob_outlet = x; - x->o_connections = 0; + if (backtracer_cantrace) + { + t_backtracer *b = backtracer_new(&owner->ob_pd); + x->o_connections = (t_outconnect *)t_getbytes(sizeof(t_outconnect)); + x->o_connections->oc_next = 0; + x->o_connections->oc_to = &b->b_pd; + } + else x->o_connections = 0; x->o_sym = s; return (x); } @@ -560,7 +717,7 @@ t_outconnect *obj_connect(t_object *source, int outno, t_inlet *i; t_outlet *o; t_pd *to; - t_outconnect *oc, *oc2; + t_outconnect *oc, *oc2, **ochead; /* ignore attempts to connect to the same object this occurs sometimes using undo/redo */ @@ -583,17 +740,19 @@ t_outconnect *obj_connect(t_object *source, int outno, if (!i) return (0); to = &i->i_pd; doit: + ochead = outlet_getconnectionpointer(o); oc = (t_outconnect *)t_getbytes(sizeof(*oc)); oc->oc_next = 0; oc->oc_to = to; /* append it to the end of the list */ /* LATER we might cache the last "oc" to make this faster. */ - if ((oc2 = o->o_connections)) + if ((oc2 = *ochead)) { - while (oc2->oc_next) oc2 = oc2->oc_next; + while (oc2->oc_next) + oc2 = oc2->oc_next; oc2->oc_next = oc; } - else o->o_connections = oc; + else *ochead = oc; if (o->o_sym == &s_signal) canvas_update_dsp(); return (oc); @@ -604,7 +763,7 @@ void obj_disconnect(t_object *source, int outno, t_object *sink, int inno) t_inlet *i; t_outlet *o; t_pd *to; - t_outconnect *oc, *oc2; + t_outconnect *oc, *oc2, **ochead; for (o = source->ob_outlet; o && outno; o = o->o_next, outno--) if (!o) return; if (sink->ob_pd->c_firstin) @@ -620,10 +779,11 @@ void obj_disconnect(t_object *source, int outno, t_object *sink, int inno) if (!i) return; to = &i->i_pd; doit: - if (!o || !(oc = o->o_connections)) return; + ochead = outlet_getconnectionpointer(o); + if (!(oc = *ochead)) return; if (oc->oc_to == to) { - o->o_connections = oc->oc_next; + *ochead = oc->oc_next; freebytes(oc, sizeof(*oc)); goto done; } @@ -680,7 +840,8 @@ t_outconnect *obj_starttraverseoutlet(t_object *x, t_outlet **op, int nout) t_outlet *o = x->ob_outlet; while (nout-- && o) o = o->o_next; *op = o; - if (o) return (o->o_connections); + if (o) + return (*outlet_getconnectionpointer(o)); else return (0); } @@ -880,3 +1041,36 @@ void obj_sendinlet(t_object *x, int n, t_symbol *s, int argc, t_atom *argv) typedmess(&i->i_pd, s, argc, argv); else bug("obj_sendinlet"); } + +/* ------------------- setup routine, somewhat misnamed */ +void obj_init(void) +{ + inlet_class = class_new(gensym("inlet"), 0, 0, + sizeof(t_inlet), CLASS_PD, 0); + class_addbang(inlet_class, inlet_bang); + class_addpointer(inlet_class, inlet_pointer); + class_addfloat(inlet_class, inlet_float); + class_addsymbol(inlet_class, inlet_symbol); + class_addlist(inlet_class, inlet_list); + class_addanything(inlet_class, inlet_anything); + + pointerinlet_class = class_new(gensym("inlet"), 0, 0, + sizeof(t_inlet), CLASS_PD, 0); + class_addpointer(pointerinlet_class, pointerinlet_pointer); + class_addanything(pointerinlet_class, inlet_wrong); + + floatinlet_class = class_new(gensym("inlet"), 0, 0, + sizeof(t_inlet), CLASS_PD, 0); + class_addfloat(floatinlet_class, (t_method)floatinlet_float); + class_addanything(floatinlet_class, inlet_wrong); + + symbolinlet_class = class_new(gensym("inlet"), 0, 0, + sizeof(t_inlet), CLASS_PD, 0); + class_addsymbol(symbolinlet_class, symbolinlet_symbol); + class_addanything(symbolinlet_class, inlet_wrong); + + backtracer_class = class_new(gensym("backtracer"), 0, 0, + sizeof(t_backtracer), CLASS_PD, 0); + class_addanything(backtracer_class, backtracer_anything); + +} \ No newline at end of file diff --git a/pd/src/m_private_utils.h b/pd/src/m_private_utils.h new file mode 100644 index 0000000000000000000000000000000000000000..8ba72f9eeffee4e3866b43b7d5f818366f0414d8 --- /dev/null +++ b/pd/src/m_private_utils.h @@ -0,0 +1,110 @@ +/* Copyright (c) 1997-1999 Miller Puckette and others. +* For information on usage and redistribution, and for a DISCLAIMER OF ALL +* WARRANTIES, see the file, "LICENSE.txt," in this distribution. */ + +/* This file contains some small header-only utilities to be used by Pd's code. */ + +/* NOTE: this file is an implementation detail of Pd, not to be in externals */ + +#ifndef M_PRIVATE_UTILS_H +#define M_PRIVATE_UTILS_H + +#ifndef PD_INTERNAL +# error m_private_utils.h is a PRIVATE header. do *not* use it in your externals +#endif + +#ifdef HAVE_CONFIG_H +/* autotools might put all the HAVE_... defines into "config.h" */ +# include "config.h" +#endif + + +/* --------------------------- stack allocation helpers --------------------- */ +/* alloca helpers + * - ALLOCA(type, array, nmemb, maxnmemb) + * allocates <nmemb> elements of <type> and points <array> to the newly + * allocated memory. if <nmemb> exceeds <maxnmemb>, allocation is done on + * the heap, otherwise on the stack + * - FREEA(type, array, nmemb, maxnmemb) + * frees the <array> allocated by ALLOCA() (unless allocation was done on + * the heap) + * if DONT_USE_ALLOCA is defined (and true), always allocates on the heap + * + * usage example: + * { + * t_atom*outv; + * ALLOCA(t_atom, outv, argc, 100); + * // do something with 'outv' + * FREEA(t_atom, outv, argc, 100); + * } + */ +# ifdef ALLOCA +# undef ALLOCA +# endif +# ifdef FREEA +# undef FREEA +# endif + +#if DONT_USE_ALLOCA +/* heap versions */ +# define ALLOCA(type, array, nmemb, maxnmemb) ((array) = (type *)getbytes((nmemb) * sizeof(type))) +# define FREEA(type, array, nmemb, maxnmemb) (freebytes((array), (nmemb) * sizeof(type))) + +#else /* !DONT_USE_ALLOCA */ +/* stack version (unless <nmemb> exceeds <maxnmemb>) */ + +# if defined __linux__ || defined __APPLE__ +# include <alloca.h> /* linux, mac, mingw, cygwin,... */ +# elif defined _WIN32 +# include <malloc.h> /* MSVC or mingw on windows */ +# else +# include <stdlib.h> /* BSDs for example */ +# endif + +# define ALLOCA(type, array, nmemb, maxnmemb) ((array) = (type *)((nmemb) < (maxnmemb) ? \ + alloca((nmemb) * sizeof(type)) : getbytes((nmemb) * sizeof(type)))) +# define FREEA(type, array, nmemb, maxnmemb) ( \ + ((nmemb) < (maxnmemb) || (freebytes((array), (nmemb) * sizeof(type)), 0))) +#endif /* !DONT_USE_ALLOCA */ + + +/* --------------------------- endianness helpers --------------------- */ +#ifdef HAVE_MACHINE_ENDIAN_H +# include <machine/endian.h> +#elif defined HAVE_ENDIAN_H +# include <endian.h> +#endif + +#ifdef __MINGW32__ +# include <sys/param.h> +#endif + +/* BSD has deprecated BYTE_ORDER in favour of _BYTE_ORDER + * others might follow... + */ +#if !defined(BYTE_ORDER) && defined(_BYTE_ORDER) +# define BYTE_ORDER _BYTE_ORDER +#endif +#if !defined(LITTLE_ENDIAN) && defined(_LITTLE_ENDIAN) +# define LITTLE_ENDIAN _LITTLE_ENDIAN +#endif + +#ifdef _MSC_VER +/* _MSVC lacks BYTE_ORDER and LITTLE_ENDIAN */ +# if !defined(LITTLE_ENDIAN) +# define LITTLE_ENDIAN 0x0001 +# endif +# if !defined(BYTE_ORDER) +# define BYTE_ORDER LITTLE_ENDIAN +# endif +#endif + +#if !defined(BYTE_ORDER) || !defined(LITTLE_ENDIAN) +# if defined(__GNUC__) && defined(_XOPEN_SOURCE) +# warning unable to detect endianness (continuing anyhow) +# else +# error unable to detect endianness +# endif +#endif + +#endif /* M_PRIVATE_UTILS_H */ diff --git a/pd/src/makefile.in b/pd/src/makefile.in index c26e61330358bd7bbf7142ea6a851f51bad01ee4..6d7c3e1b879132fbd0493a1d53dafd9cb2fde3a0 100644 --- a/pd/src/makefile.in +++ b/pd/src/makefile.in @@ -47,7 +47,7 @@ WARN_CFLAGS = -Wall -W -Wstrict-prototypes \ -Wno-unused-parameter -Wno-parentheses -Wno-switch -Wno-cast-function-type #WARN_CFLAGS += -Werror=implicit-function-declaration -ARCH_CFLAGS = -DPD +ARCH_CFLAGS = -DPD -DPD_INTERNAL CFLAGS = @CFLAGS@ $(ARCH_CFLAGS) $(WARN_CFLAGS) $(CPPFLAGS) $(MORECFLAGS) @@ -76,7 +76,7 @@ OPT_SAFE_SRC = g_canvas.c g_graph.c g_text.c g_rtext.c g_array.c g_template.c g_ d_ugen.c d_arithmetic.c d_dac.c d_misc.c \ d_fft.c d_global.c \ d_resample.c \ - x_arithmetic.c x_connective.c x_interface.c x_midi.c x_misc.c \ + x_arithmetic.c x_connective.c x_interface.c x_midi.c x_misc.c x_file.c \ x_time.c x_acoustics.c x_net.c x_text.c x_gui.c x_list.c x_array.c x_preset.c\ x_scalar.c x_vexp.c x_vexp_if.c x_vexp_fun.c import.c \ $(SYSSRC) diff --git a/pd/src/makefile.mingw b/pd/src/makefile.mingw index ee9935213005e9d811e1b03461af7960487262c8..f449342fb4addd82244389ddb0669d3b088cb842 100755 --- a/pd/src/makefile.mingw +++ b/pd/src/makefile.mingw @@ -123,7 +123,7 @@ SRC = g_canvas.c g_graph.c g_text.c g_rtext.c g_array.c g_template.c g_io.c \ s_print.c s_loader.c s_path.c s_entry.c s_audio.c s_midi.c s_net.c \ d_ugen.c d_ctl.c d_arithmetic.c d_osc.c d_filter.c d_dac.c d_misc.c \ d_math.c d_fft.c d_fft_mayer.c d_fftroutine.c d_array.c d_global.c \ - d_delay.c d_resample.c x_arithmetic.c x_connective.c x_interface.c \ + d_delay.c d_resample.c x_arithmetic.c x_connective.c x_interface.c x_file.c \ x_midi.c x_misc.c x_time.c x_acoustics.c x_net.c x_text.c x_gui.c \ x_list.c x_array.c d_soundfile.c g_slider.c import.c \ x_scalar.c x_vexp.c x_vexp_if.c x_vexp_fun.c \ diff --git a/pd/src/makefile.nt b/pd/src/makefile.nt index e3af34af6608e5e91ce58807f71afe962f2fd5c0..e20c382b45c7c518c3074f895b28f1a35f5f3fe9 100644 --- a/pd/src/makefile.nt +++ b/pd/src/makefile.nt @@ -19,7 +19,7 @@ LIB = /NODEFAULTLIB:libcmt /NODEFAULTLIB:oldnames /NODEFAULTLIB:libc \ $(LD2)\libcmt.lib $(LD2)\oldnames.lib CFLAGS = /nologo /W3 /DMSW /DNT /DPD /DPD_INTERNAL /DWIN32 /DWINDOWS /Ox \ - -DPA_LITTLE_ENDIAN -DUSEAPI_MMIO -DUSEAPI_PORTAUDIO -D__i386__ -DPA19 \ + -DPD -DPD_INTERNAL -DPA_LITTLE_ENDIAN -DUSEAPI_MMIO -DUSEAPI_PORTAUDIO -D__i386__ -DPA19 \ -D_CRT_SECURE_NO_WARNINGS LFLAGS = /nologo @@ -37,7 +37,7 @@ SRC = g_canvas.c g_graph.c g_text.c g_rtext.c g_array.c g_template.c g_io.c \ d_ugen.c d_ctl.c d_arithmetic.c d_osc.c d_filter.c d_dac.c d_misc.c \ d_math.c d_fft.c d_fft_mayer.c d_fftroutine.c d_array.c d_global.c \ d_delay.c d_resample.c \ - x_arithmetic.c x_connective.c x_interface.c x_midi.c x_misc.c \ + x_arithmetic.c x_connective.c x_interface.c x_midi.c x_misc.c x_file.c \ x_time.c x_acoustics.c x_net.c x_text.c x_gui.c x_list.c x_array.c d_soundfile.c \ x_scalar.c x_vexp.c x_vexp_if.c x_vexp_fun.c $(SYSSRC) diff --git a/pd/src/s_print.c b/pd/src/s_print.c index 52d86e9a3239b25aa2e409f6cd187bccd0b5b9f4..3339282ee471f4d43c942c787421a3e86e8b69bb 100644 --- a/pd/src/s_print.c +++ b/pd/src/s_print.c @@ -17,6 +17,58 @@ t_printhook sys_printhook; t_printhook sys_printhook_error; int sys_printtostderr; +#ifdef _WIN32 + + /* NB: Unlike vsnprintf(), _vsnprintf() does *not* null-terminate + the output if the resulting string is too large to fit into the buffer. + Also, it just returns -1 instead of the required number of bytes. + Strictly speaking, the UCRT in Windows 10 actually contains a standard- + conforming vsnprintf() function that is not just an alias for _vsnprintf(). + However, MinGW traditionally links against the old msvcrt.dll runtime library. + Recent versions of MinGW seem to have their own (standard-conformating) + implementation of vsnprintf(), but to ensure portability we rather use our + own implementation for all Windows builds. */ +int pd_vsnprintf(char *buf, size_t size, const char *fmt, va_list argptr) +{ + int ret = _vsnprintf(buf, size, fmt, argptr); + if (ret < 0) + { + /* null-terminate the buffer and get the required number of bytes. */ + ret = _vscprintf(fmt, argptr); + buf[size - 1] = '\0'; + } + return ret; +} + +int pd_snprintf(char *buf, size_t size, const char *fmt, ...) +{ + int ret; + va_list ap; + va_start(ap, fmt); + ret = pd_vsnprintf(buf, size, fmt, ap); + va_end(ap); + return ret; +} + +#else + +int pd_vsnprintf(char *buf, size_t size, const char *fmt, va_list argptr) +{ + return vsnprintf(buf, size, fmt, argptr); +} + +int pd_snprintf(char *buf, size_t size, const char *fmt, ...) +{ + int ret; + va_list ap; + va_start(ap, fmt); + ret = vsnprintf(buf, size, fmt, ap); + va_end(ap); + return ret; +} + +#endif + static char* strnpointerid(char *dest, const void *pointer, size_t len) { *dest=0; diff --git a/pd/src/s_stuff.h b/pd/src/s_stuff.h index 53af1dad7a8a2ad5906ae158de7b6387954926e1..a37edd030e259764340c93cce7e20eb857adfd61 100644 --- a/pd/src/s_stuff.h +++ b/pd/src/s_stuff.h @@ -443,4 +443,9 @@ EXTERN void alist_anything(t_alist *x, t_symbol *s, int argc, t_atom *argv); EXTERN void alist_toatoms(t_alist *x, t_atom *to, int onset, int count); EXTERN void alist_clone(t_alist *x, t_alist *y, int onset, int count); +/* safe cross-platform alternatives to snprintf and vsnprintf. */ +EXTERN int pd_snprintf(char *buf, size_t size, const char *fmt, ...); +EXTERN int pd_vsnprintf(char *buf, size_t size, const char *fmt, + va_list argptr); + #endif /* __s_stuff_h_ */ diff --git a/pd/src/x_file.c b/pd/src/x_file.c new file mode 100644 index 0000000000000000000000000000000000000000..b658bc26533b0deda87e351460f622b56ddb30e1 --- /dev/null +++ b/pd/src/x_file.c @@ -0,0 +1,1964 @@ +/* Copyright (c) 2021 IOhannes m zmölnig. + * For information on usage and redistribution, and for a DISCLAIMER OF ALL + * WARRANTIES, see the file, "LICENSE.txt," in this distribution. */ +/* The "file" object. */ +#define _XOPEN_SOURCE 600 +#define _DEFAULT_SOURCE + +#include "m_pd.h" +#include "g_canvas.h" +#include "s_stuff.h" +#include "s_utf8.h" + +#define PD_INTERNAL +#include "m_private_utils.h" + +#include <string.h> +#include <stdio.h> +#include <stdlib.h> + +#include <errno.h> +#ifdef HAVE_UNISTD_H +# include <unistd.h> +#endif + +#include <sys/types.h> +#include <sys/stat.h> +#include <fcntl.h> +#include <time.h> + +#ifdef _WIN32 +# include <windows.h> +# include <direct.h> +# include <io.h> +#else +# include <glob.h> +# include <ftw.h> +#endif + +#ifdef _MSC_VER +# include <BaseTsd.h> +typedef unsigned int mode_t; +typedef SSIZE_T ssize_t; +# define wstat _wstat +#endif + +#ifndef S_ISREG + #define S_ISREG(mode) (((mode) & S_IFMT) == S_IFREG) +#endif +#ifndef S_ISDIR +# define S_ISDIR(mode) (((mode) & S_IFMT) == S_IFDIR) +#endif + +#ifdef _WIN32 +static int do_delete_ucs2(wchar_t*pathname) { + struct stat sb; + if (!wstat(pathname, &sb) && (S_ISDIR(sb.st_mode))) { + /* a directory */ + return !(RemoveDirectoryW(pathname)); + } else { + /* probably a file */ + return !(DeleteFileW(pathname)); + } +} + +static int sys_stat(const char *pathname, struct stat *statbuf) { + uint16_t ucs2buf[MAX_PATH]; + u8_utf8toucs2(ucs2buf, MAX_PATH, pathname, strlen(pathname)); + return wstat(ucs2buf, statbuf); +} + +static int sys_rename(const char *oldpath, const char *newpath) { + uint16_t src[MAX_PATH], dst[MAX_PATH]; + u8_utf8toucs2(src, MAX_PATH, oldpath, MAX_PATH); + u8_utf8toucs2(dst, MAX_PATH, newpath, MAX_PATH); + return _wrename(src, dst); +} +static int sys_mkdir(const char *pathname, mode_t mode) { + uint16_t ucs2name[MAX_PATH]; + (void)mode; + u8_utf8toucs2(ucs2name, MAX_PATH, pathname, MAX_PATH); + return !(CreateDirectoryW(ucs2name, 0)); +} +static int sys_remove(const char *pathname) { + uint16_t ucs2buf[MAXPDSTRING]; + u8_utf8toucs2(ucs2buf, MAXPDSTRING, pathname, MAXPDSTRING); + return do_delete_ucs2(ucs2buf); +} +static char* sys_getcwd(char *buf) { + uint16_t ucs2buf[MAXPDSTRING]; + memset(ucs2buf, 0, sizeof(ucs2buf)); + if (!_wgetcwd(ucs2buf, MAXPDSTRING)) + return 0; + + u8_ucs2toutf8(buf, MAXPDSTRING-1, ucs2buf, -1); + buf[MAXPDSTRING-1] = 0; + sys_unbashfilename(buf, buf); + return buf; +} +static int sys_chdir(const char *path) { + uint16_t ucs2buf[MAXPDSTRING]; + u8_utf8toucs2(ucs2buf, MAXPDSTRING, path, MAXPDSTRING); + return _wchdir(ucs2buf); +} +#else +static int sys_stat(const char *pathname, struct stat *statbuf) { + return stat(pathname, statbuf); +} + +static int sys_rename(const char *oldpath, const char *newpath) { + return rename(oldpath, newpath); +} +static int sys_mkdir(const char *pathname, mode_t mode) { + return mkdir(pathname, mode); +} +static int sys_remove(const char *pathname) { + return remove(pathname); +} +static char* sys_getcwd(char *buf) { + return getcwd(buf, MAXPDSTRING); +} +static int sys_chdir(const char *path) { + return chdir(path); +} +#endif + + /* expand env vars and ~ at the beginning of a path and make a copy to return */ +static char*do_expandpath(const char *from, char *to, int bufsize) +{ + if ((strlen(from) == 1 && from[0] == '~') || (strncmp(from,"~/", 2) == 0)) + { +#ifdef _WIN32 + const char *home = getenv("USERPROFILE"); +#else + const char *home = getenv("HOME"); +#endif + if (home) + { + strncpy(to, home, bufsize); + to[bufsize-1] = 0; + strncpy(to + strlen(to), from + 1, bufsize - strlen(to)); + to[bufsize-1] = 0; + } + else *to = 0; + } + else + { + strncpy(to, from, bufsize); + to[bufsize-1] = 0; + } +#ifdef _WIN32 + { + char *buf = alloca(bufsize); + ExpandEnvironmentStrings(to, buf, bufsize-1); + buf[bufsize-1] = 0; + strncpy(to, buf, bufsize); + to[bufsize-1] = 0; + } +#endif + return to; +} + + /* unbash '\' to '/', and drop duplicate '/' */ +static char*do_pathnormalize(const char *from, char *to) { + const char *rp; + char *wp, c; + sys_unbashfilename(from, to); + rp=wp=to; + while((*wp++=c=*rp++)) { + if('/' == c) { + while('/' == *rp++); + rp--; + } + } + return to; +} + +static char*do_expandunbash(const char *from, char *to, int bufsize) { + do_expandpath(from, to, bufsize); + to[bufsize-1]=0; + sys_unbashfilename(to, to); + to[bufsize-1]=0; + return to; +} + +static int str_endswith(char* str, char* end){ + size_t strsize = strlen(str), endsize = strlen(end); + if(strsize<endsize) return 0; + return strcmp(str + strsize - endsize, end) == 0; +} + +static t_symbol*do_splitpath(const char*path, int*argc, t_atom**argv) { + t_symbol*slashsym = gensym("/"); + t_atom*outv; + int outc=0, outsize=1; + char buffer[MAXPDSTRING], *pathname=buffer; + sys_unbashfilename(path, buffer); + buffer[MAXPDSTRING-1] = 0; + + /* first count the number of path components */ + while(*pathname) + outsize += ('/'==*pathname++); + pathname=buffer; + outv = (t_atom*)getbytes(outsize * sizeof(*outv)); + + if('/' == *pathname) + SETSYMBOL(outv+outc, slashsym), outc++; + + while(*pathname) { + char*pathsep; + while('/' == *pathname) + pathname++; + pathsep=strchr(pathname, '/'); + if(!pathsep) { + if(*pathname) + SETSYMBOL(outv+outc, gensym(pathname)), outc++; + break; + } + *pathsep=0; + SETSYMBOL(outv+outc, gensym(pathname)), outc++; + pathname=pathsep+1; + } + + if(outc != outsize) { + t_atom*a=resizebytes(outv, outsize * sizeof(*outv), outc * sizeof(*outv)); + if(!a) { + freebytes(outv, outsize * sizeof(*outv)); + outsize = outc = 0; + } + outv = a; + } + *argc = outc; + *argv = outv; + return (*pathname)?0:slashsym; +} + +/* joins up all the path-components (using '/' as the path delimiter) + * the (optional) prefix is prepended to the string + (to be used for Windows volumes) + * the (optional) suffix is present in the result (and appended if required to the string) + (to be used for trailing slash) + * atoms that are A_NULL are skipped + */ +static t_symbol*do_joinpath(t_symbol*prefix, int argc, t_atom*argv, t_symbol*suffix) { + /* luckily for us, the path-separator in Pd is always '/' */ + const char pathseparator = '/'; + size_t alen, bufsize = 0; + char buffer[MAXPDSTRING]; + t_symbol*result = 0; + int needseparator = 0; + int i; + + memset(buffer, 0, sizeof(buffer)); + + if(prefix) { + strcpy(buffer+bufsize, prefix->s_name); + bufsize += strlen(prefix->s_name); + } + + for(i=0; i<argc; i++) { + char sbuf[MAXPDSTRING]; + const char*abuf=sbuf; + t_atom*a = argv+i; + switch(a->a_type) { + case A_NULL: + abuf = 0; + break; + case A_SYMBOL: + abuf = atom_getsymbol(a)->s_name; + break; + default: + atom_string(a, sbuf, MAXPDSTRING); + abuf = sbuf; + break; + } + if(!abuf || !(alen = strlen(abuf))) continue; + if(pathseparator == abuf[alen-1]) + needseparator = 0; + if(bufsize+alen+needseparator >= sizeof(buffer)) + break; + if(needseparator) { + buffer[bufsize]=pathseparator; + bufsize++; + } + needseparator=1; + + strcpy(buffer+bufsize, abuf); + bufsize+=alen; + } + + if(suffix && (alen=strlen(suffix->s_name)) && (bufsize+alen)<sizeof(buffer)) { + strcpy(buffer+bufsize, suffix->s_name); + bufsize+=alen; + } + + result = gensym(do_pathnormalize(buffer, buffer)); + return result; +} + + +#ifdef _WIN32 +static const char*do_errmsg(char*buffer, size_t bufsize) { + char errcode[10]; + char*s; + wchar_t wbuf[MAXPDSTRING]; + DWORD err = GetLastError(); + DWORD count = FormatMessageW(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, + 0, err, MAKELANGID (LANG_NEUTRAL, SUBLANG_DEFAULT), wbuf, MAXPDSTRING, NULL); + if (!count || !WideCharToMultiByte(CP_UTF8, 0, wbuf, count+1, buffer, bufsize, 0, 0)) + *buffer = '\0'; + s=buffer + strlen(buffer)-1; + while(('\r' == *s || '\n' == *s) && s>buffer) + *s--=0; + pd_snprintf(errcode, sizeof(errcode), " [%ld]", err); + errcode[sizeof(errcode)-1] = 0; + strcat(buffer, errcode); + return buffer; +} +#else /* !_WIN32 */ +static const char*do_errmsg(char*buffer, size_t bufsize) { + (void)buffer; (void)bufsize; + return strerror(errno); +} +#endif /* !_WIN32 */ + +typedef struct _file_handler { + int fh_fd; + int fh_mode; /* 0..read, 1..write */ +} t_file_handler; +#define x_fd x_fhptr->fh_fd +#define x_mode x_fhptr->fh_mode +typedef struct _file_handle { + t_object x_obj; + t_file_handler x_fh; + t_file_handler*x_fhptr; + t_symbol*x_fcname; /* multiple [file handle] object can refer to the same [file define] */ + + mode_t x_creationmode; /* default: 0666, 0777 */ + int x_verbose; /* default: 0 */ + + t_canvas*x_canvas; + t_outlet*x_dataout; + t_outlet*x_infoout; + +} t_file_handle; + + +static int do_checkpathname(t_file_handle*x, const char*path) { + int err_count = 0, warn_count = 0; + char buf[4]; + const char*s; + + /* check for illegal characters */ + for(s = path; *s; s++) { + const char*ill = "illegal"; + int oops = 0; + switch(*s) { + case ':': case '\\': + ill = "reserved"; + /* falls through */ + case '<': case '>': + case '|': + case '"': + //case '/': + case '?': case '*': + oops++; + break; + default: + if(*s<32) + oops++; + } + + if(oops) { +#ifdef _WIN32 + if(x->x_verbose) + pd_error(x, "the path \"%s\" contains the character '%c', which is %s.", path, *s, ill); + err_count++; +#else + if(x->x_verbose) + logpost(x, 3, "cross-platform issue: the path \"%s\" contains the character '%c', which is %s on MSW.", path, *s, ill); + warn_count++; +#endif + goto fail; + } + } + + /* check for illegal names */ + strncpy(buf, path, sizeof(buf)); + buf[3] = 0; + if(buf[2]) { + const char* forbidden_names0[] = {"AUX", "CON", "NUL", "PRN", 0}; + const char* forbidden_names1[] = {"COM", "LPT", 0}; + const char**ref; + /* upper-case the string */ + int i; + for(i=0; i<3; i++) { + if (buf[i] >= 'a' && buf[i] <= 'z') + buf[i] = buf[i] - ('a' - 'A'); + } + /* AUX, CON, NULL, PRN */ + for(ref = forbidden_names0; *ref; ref++) { + if(!strcmp(*ref, buf) + && (!path[3] || '.' == path[3])) { +#ifdef _WIN32 + if(x->x_verbose) + pd_error(x, "the path \"%s\" contains the reserved name '%s'.", path, *ref); + err_count++; +#else + if(x->x_verbose) + logpost(x, 3, "cross-platform issue: the path \"%s\" contains the name '%s' which is reserved on MSW.", path, *ref); + warn_count++; +#endif + goto fail; + } + } + /* COM[1-9], LPT[1-9] */ + for(ref = forbidden_names1; *ref; ref++) { + if(!strcmp(*ref, buf) + && path[3] > '0' && path[3] <= '9' + && (!path[4] || '.' == path[4])) { +#ifdef _WIN32 + if(x->x_verbose) + pd_error(x, "the path \"%s\" contains the reserved name '%s[1-9]'.", path, *ref); + err_count++; +#else + if(x->x_verbose) + logpost(x, 3, "cross-platform issue: the path \"%s\" contains the name '%s[1-9]' which is reserved on MSW.", path, *ref); + warn_count++; +#endif + goto fail; + } + } + } + return 0; + + fail: + if(err_count>0) + return 2; + if(warn_count>0) + return 1; + return 0; +} + +t_canvas*do_getparentcanvas(t_file_handle*x, int parentlevel, int*effectivelevel) { + t_canvas *c = x->x_canvas; + int i, level = 0; + for (i = 0; i < parentlevel; i++) + { + while (!c->gl_env) /* back up to containing canvas or abstraction */ + c = c->gl_owner; + if (c->gl_owner) /* back up one more into an owner if any */ + { + c = c->gl_owner; + level++; + } + } + if(effectivelevel) + *effectivelevel = level; + return c; +} + + +static int do_parse_creationmode(t_atom*ap) { + const char*s; + if(A_FLOAT==ap->a_type) + return atom_getfloat(ap); + if(A_SYMBOL!=ap->a_type) + return -1; /* oopsie */ + s = atom_getsymbol(ap)->s_name; + if(!strncmp(s, "0o", 2)) { + /* octal mode */ + char*endptr; + long mode = strtol(s+2, &endptr, 8); + return (*endptr)?-1:(int)mode; + } else if(!strncmp(s, "0x", 2)) { + /* hex mode: nobody sane uses this... */ + char*endptr; + long mode = strtol(s+2, &endptr, 16); + return (*endptr)?-1:(int)mode; + } else { + /* free form mode: a+rwx,go-w */ + /* not supported yet */ + return -1; + } + return -1; +} + +static void do_parse_args(t_file_handle*x, int argc, t_atom*argv) { + /* + * -q: quiet mode + * -v: verbose mode + * -m <mode>: creation mode + */ + t_symbol*flag_m = gensym("-m"); + t_symbol*flag_q = gensym("-q"); + t_symbol*flag_v = gensym("-v"); + x->x_fcname = 0; + while(argc--) { + const t_symbol*flag = atom_getsymbol(argv); + if (0); + else if (flag == flag_q) { + x->x_verbose--; + } else if (flag == flag_v) { + x->x_verbose++; + } else if (flag == flag_m) { + int mode; + if(!argc) { + pd_error(x, "'-m' requires an argument"); + break; + } + argc--; + argv++; + mode = do_parse_creationmode(argv); + if(mode<0) { + char buf[MAXPDSTRING]; + atom_string(argv, buf, MAXPDSTRING); + pd_error(x, "invalid creation mode '%s'", buf); + break; + } else { + x->x_creationmode = mode; + } + } else { + int filearg = (!argc); + if(filearg) { + x->x_fcname = (t_symbol*)flag; + } else { + pd_error(x, "unknown flag %s", flag->s_name); + } + break; + } + argv++; + } + x->x_verbose = x->x_verbose > 0; +} + +static t_file_handle* do_file_handle_new(t_class*cls, t_symbol*s, int argc, t_atom*argv, int verbose, mode_t creationmode) { + t_file_handle*x = (t_file_handle*)pd_new(cls); + (void)s; + x->x_fhptr = &x->x_fh; + x->x_fd = -1; + x->x_canvas = canvas_getcurrent(); + x->x_creationmode = creationmode; + x->x_verbose = verbose; + + x->x_dataout = outlet_new(&x->x_obj, 0); + x->x_infoout = outlet_new(&x->x_obj, 0); + do_parse_args(x, argc, argv); + return x; +} + +static int do_file_open(t_file_handle*x, const char* filename, int mode) { + char expandbuf[MAXPDSTRING+1]; + int fd = sys_open(do_expandpath(filename, expandbuf, MAXPDSTRING), mode, x?x->x_creationmode:0666); + if(x) { + x->x_fd = fd; + if(fd<0) { + if(x->x_verbose) + pd_error(x, "unable to open '%s': %s", filename, strerror(errno)); + if(x->x_infoout) + outlet_bang(x->x_infoout); + } + } + return fd; +} + + +static void file_set_verbosity(t_file_handle*x, t_float f) { + x->x_verbose = (f>0.5); +} +static void file_set_creationmode(t_file_handle*x, t_symbol*s, int argc, t_atom*argv) { + if(argc!=1) { + pd_error(x, "usage: '%s <mode>'", s->s_name); + return; + } + x->x_creationmode = do_parse_creationmode(argv); +} + + /* ================ [file handle] ====================== */ +t_class *file_define_class; +static int file_handle_getdefine(t_file_handle*x) { + if(x->x_fcname) { + t_file_handle *y = (t_file_handle *)pd_findbyclass(x->x_fcname, + file_define_class); + if(y) { + x->x_fhptr=&y->x_fh; + return 1; + } + return 0; + } + x->x_fhptr = &x->x_fh; + return 1; +} + +static void file_handle_close(t_file_handle*x) { + if (x->x_fd>=0) + sys_close(x->x_fd); + x->x_fd = -1; +} +static int file_handle_checkopen(t_file_handle*x, const char*cmd) { + if(x->x_fcname) { + if(!file_handle_getdefine(x)) { + pd_error(x, "file handle: couldn't find file-define '%s'", x->x_fcname->s_name); + return 0; + } + } + if(x->x_fd<0) { + if(!cmd)cmd=(x->x_mode)?"write":"read"; + pd_error(x, "'%s' without prior 'open'", cmd); + outlet_bang(x->x_infoout); + return 0; + } + return 1; +} +static void file_handle_do_read(t_file_handle*x, t_float f) { + t_atom*outv; + unsigned char*buf; + ssize_t n, len, outc=f; + if(outc<1) { + pd_error(x, "cannot read %d bytes", (int)outc); + return; + } + ALLOCA(unsigned char, buf, outc, 100); + ALLOCA(t_atom, outv, outc, 100); + if(buf && outv) { + len = read(x->x_fd, buf, outc); + for(n=0; n<len; n++) { + SETFLOAT(outv+n, (t_float)buf[n]); + } + if(len>0) { + outlet_list(x->x_dataout, gensym("list"), len, outv); + } else if (!len) { + file_handle_close(x); + outlet_bang(x->x_infoout); + } else { + if(x->x_verbose) + pd_error(x, "read failed: %s", strerror(errno)); + file_handle_close(x); + outlet_bang(x->x_infoout); + } + } else { + pd_error(x, "couldn't allocate buffer for %d bytes", (int)outc); + } + FREEA(unsigned char, buf, outc, 100); + FREEA(t_atom, outv, outc, 100); +} +static void file_handle_do_write(t_file_handle*x, int argc, t_atom*argv) { + unsigned char*buf; + size_t len = (argc>0)?argc:0; + ALLOCA(unsigned char, buf, argc, 100); + if(buf) { + ssize_t n; + for(n=0; n<argc; n++) { + buf[n] = atom_getfloat(argv+n); + } + n = write(x->x_fd, buf, len); + if(n >= 0 && (size_t)n < len) { + n = write(x->x_fd, buf+n, len-n); + } + if (n<0) { + pd_error(x, "write failed: %s", strerror(errno)); + file_handle_close(x); + outlet_bang(x->x_infoout); + } + } else { + pd_error(x, "could not allocate %d bytes for writing", argc); + } + FREEA(unsigned char, buf, argc, 100); +} +static void file_handle_list(t_file_handle*x, t_symbol*s, int argc, t_atom*argv) { + (void)s; + if(!file_handle_checkopen(x, 0)) + return; + if(x->x_mode) { + /* write_mode */ + file_handle_do_write(x, argc, argv); + } else { + /* read mode */ + if(1==argc && A_FLOAT==argv->a_type) { + file_handle_do_read(x, atom_getfloat(argv)); + } else { + pd_error(x, "no way to handle 'list' messages while reading file"); + } + } +} +static void file_handle_set(t_file_handle*x, t_symbol*s) { + if (gensym("") == s) + s=0; + if (s && x->x_fhptr == &x->x_fh && x->x_fh.fh_fd >= 0) { + /* trying to set a name, even though we have an fd open... */ + pd_error(x, "file handle: shadowing local file descriptor with '%s'", s->s_name); + } else if (!s && x->x_fhptr != &x->x_fh && x->x_fh.fh_fd >= 0) { + logpost(x, 3, "file handle: unshadowing local file descriptor"); + } + x->x_fcname = s; + file_handle_getdefine(x); +} +static void file_handle_seek(t_file_handle*x, t_symbol*s, int argc, t_atom*argv) { + off_t offset=0; + int whence = SEEK_SET; + t_atom a[1]; + switch(argc) { + case 0: + /* just output the current position */ + whence=SEEK_CUR; + break; + case 2: { + if (A_SYMBOL!=argv[1].a_type) + goto usage; + s=atom_getsymbol(argv+1); + switch(*s->s_name) { + case 'S': case 's': case 0: + whence = SEEK_SET; + break; + case 'E': case 'e': + whence = SEEK_END; + break; + case 'C': case 'c': case 'R': case 'r': + whence = SEEK_CUR; + break; + default: + pd_error(x, "seek mode must be 'set', 'end' or 'current' (resp. 'relative')"); + return; + } + } + /* falls through */ + case 1: + if (A_FLOAT!=argv[0].a_type) + goto usage; + offset = (int)atom_getfloat(argv); + break; + } + + if(!file_handle_checkopen(x, "seek")) + return; + offset = lseek(x->x_fd, offset, whence); + SETFLOAT(a, offset); + outlet_anything(x->x_infoout, gensym("seek"), 1, a); + return; + usage: + pd_error(x, "usage: seek [<int:offset> [<symbol:mode>]]"); +} +static void file_handle_open(t_file_handle*x, t_symbol*file, t_symbol*smode) { + int mode = O_RDONLY; + if (x->x_fd>=0) { + pd_error(x, "'open' without prior 'close'"); + return; + } + if(!file_handle_getdefine(x)) { + pd_error(x, "file handle: couldn't find file-define '%s'", x->x_fcname->s_name); + return; + } + if(smode && smode!=&s_) { + switch(smode->s_name[0]) { + case 'r': /* read */ + mode = O_RDONLY; + break; + case 'w': /* write */ + mode = O_WRONLY; + break; + case 'a': /* append */ + mode = O_WRONLY | O_APPEND; + break; + case 'c': /* create */ + mode = O_WRONLY | O_TRUNC; + break; + } + } + if(mode & O_WRONLY) { + mode |= O_CREAT; + } + if(do_file_open(x, file->s_name, mode)>=0) { + /* check if we haven't accidentally opened a directory */ + struct stat sb; + if(fstat(x->x_fd, &sb)) { + file_handle_close(x); + if(x->x_verbose) + pd_error(x, "unable to stat '%s': %s", file->s_name, strerror(errno)); + outlet_bang(x->x_infoout); + return; + } + if(S_ISDIR(sb.st_mode)) { + file_handle_close(x); + if(x->x_verbose) + pd_error(x, "unable to open directory '%s' as file", file->s_name); + outlet_bang(x->x_infoout); + return; + } + x->x_mode = (mode&O_WRONLY)?1:0; + } +} + +static void file_handle_free(t_file_handle*x) { + /* close our own file handle (if any) */ + x->x_fhptr = &x->x_fh; + file_handle_close(x); +} + + /* ================ [file stat] ====================== */ +static int do_file_stat(t_file_handle*x, const char*filename, struct stat*sb, int*is_symlink) { + int result = -1; + int fd = -1; + char buf[MAXPDSTRING+1]; + do_expandpath(filename, buf, MAXPDSTRING); + + if(is_symlink) { + *is_symlink=0; +#ifdef S_IFLNK + if(!lstat(buf, sb)) { + *is_symlink = !!(S_ISLNK(sb->st_mode)); + } +#endif + } + result = sys_stat(buf, sb); + if(!result) + return result; + + fd = do_file_open(0, filename, 0); + + if(fd >= 0) { + result = fstat(fd, sb); + sys_close(fd); + } else + result = -1; + + if(x) { + x->x_fd = -1; + if(result && x->x_verbose) { + pd_error(x, "could not stat on '%s': %s", filename, strerror(errno)); + } + } + return result; +} +static void do_dataout_symbol(t_file_handle*x, const char*selector, t_symbol*s) { + t_atom ap[1]; + SETSYMBOL(ap, s); + outlet_anything(x->x_dataout, gensym(selector), 1, ap); +} +static void do_dataout_float(t_file_handle*x, const char*selector, t_float f) { + t_atom ap[1]; + SETFLOAT(ap, f); + outlet_anything(x->x_dataout, gensym(selector), 1, ap); +} +static void do_dataout_time(t_file_handle*x, const char*selector, time_t t) { + t_atom ap[7]; + struct tm *ts = localtime(&t); + if(!ts) { + pd_error(x, "unable to convert timestamp %ld", (long int)t); + } + SETFLOAT(ap+0, ts->tm_year + 1900); + SETFLOAT(ap+1, ts->tm_mon + 1); + SETFLOAT(ap+2, ts->tm_mday); + SETFLOAT(ap+3, ts->tm_hour); + SETFLOAT(ap+4, ts->tm_min); + SETFLOAT(ap+5, ts->tm_sec); + SETFLOAT(ap+6, ts->tm_isdst); + outlet_anything(x->x_dataout, gensym(selector), 7, ap); +} +static void file_stat_symbol(t_file_handle*x, t_symbol*filename) { + /* get all the info for the given file */ + struct stat sb; + t_symbol*s; + int is_symlink=0; + int readable=0, writable=0, executable=0, owned=-1; + char buf[MAXPDSTRING+1]; + + if(do_file_stat(x, filename->s_name, &sb, &is_symlink) < 0) { + outlet_bang(x->x_infoout); + return; + } + + /* this is wrong: readable/writable/executable are supposed to report + * on the *current* user, not the *owner* + */ + readable = !!(sb.st_mode & 0400); + writable = !!(sb.st_mode & 0200); + executable = !!(sb.st_mode & 0100); +#ifdef HAVE_UNISTD_H + /* this is the right way */ + do_expandpath(filename->s_name, buf, MAXPDSTRING); + + readable = !(access(buf, R_OK)); + writable = !(access(buf, W_OK)); + executable = !(access(buf, X_OK)); +#ifndef _WIN32 + owned = (geteuid() == sb.st_uid); +#endif +#endif + + switch (sb.st_mode & S_IFMT) { + case S_IFREG: +#ifdef S_IFLNK + case S_IFLNK: +#endif + do_dataout_float(x, "size", (int)(sb.st_size)); + break; + case S_IFDIR: + do_dataout_float(x, "size", 0); + break; + default: + do_dataout_float(x, "size", -1); + break; + } + do_dataout_float(x, "readable", readable); + do_dataout_float(x, "writable", writable); + do_dataout_float(x, "executable", executable); + do_dataout_float(x, "owned", owned); + + do_dataout_float(x, "isfile", !!(S_ISREG(sb.st_mode))); + do_dataout_float(x, "isdirectory", !!(S_ISDIR(sb.st_mode))); + do_dataout_float(x, "issymlink", is_symlink); + + do_dataout_float(x, "uid", (int)(sb.st_uid)); + do_dataout_float(x, "gid", (int)(sb.st_gid)); + do_dataout_float(x, "permissions", (int)(sb.st_mode & 0777)); + switch (sb.st_mode & S_IFMT) { + case S_IFREG: s = gensym("file"); break; + case S_IFDIR: s = gensym("directory"); break; +#ifdef S_IFBLK + case S_IFBLK: s = gensym("blockdevice"); break; +#endif +#ifdef S_IFCHR + case S_IFCHR: s = gensym("characterdevice"); break; +#endif +#ifdef S_IFIFO + case S_IFIFO: s = gensym("pipe"); break; +#endif +#ifdef S_IFLNK + case S_IFLNK: s = gensym("symlink"); break; +#endif +#ifdef S_IFSOCK + case S_IFSOCK: s = gensym("socket"); break; +#endif + default: s = 0; break; + } + if(s) + do_dataout_symbol(x, "type", s); + else + do_dataout_symbol(x, "type", gensym("unknown")); + + do_dataout_time(x, "atime", sb.st_atime); + do_dataout_time(x, "mtime", sb.st_mtime); +} + +static void file_size_symbol(t_file_handle*x, t_symbol*filename) { + struct stat sb; + if(do_file_stat(x, filename->s_name, &sb, 0) < 0) { + outlet_bang(x->x_infoout); + } else { + switch (sb.st_mode & S_IFMT) { + case S_IFREG: +#ifdef S_IFLNK + case S_IFLNK: +#endif + outlet_float(x->x_dataout, (int)(sb.st_size)); + break; + case S_IFDIR: + outlet_float(x->x_dataout, 0); + break; + default: + outlet_float(x->x_dataout, -1); + break; + } + } +} +static void file_isfile_symbol(t_file_handle*x, t_symbol*filename) { + struct stat sb; + if(do_file_stat(x, filename->s_name, &sb, 0) < 0) { + outlet_bang(x->x_infoout); + } else { + outlet_float(x->x_dataout, !!(S_ISREG(sb.st_mode))); + } +} +static void file_isdirectory_symbol(t_file_handle*x, t_symbol*filename) { + struct stat sb; + if(do_file_stat(x, filename->s_name, &sb, 0) < 0) { + outlet_bang(x->x_infoout); + } else { + outlet_float(x->x_dataout, !!(S_ISDIR(sb.st_mode))); + } +} + + /* ================ [file glob] ====================== */ +#ifdef _WIN32 +/* idiosyncrasies: + * - cases are ignored ('a*' matches 'A.txt' and 'a.txt'), even with wine on ext4 + * - only the filename component is returned (must prefix path separately) + * - non-ASCII needs special handling + * - '*?' seems to be illegal (e.g. 'f*?.txt'); '?*' seems to be fine though + * - "*" matches files starting with '.' (including '.', '..', but also .gitignore) + * - if the pattern includes '*.', it matches a trailing '~' + * - wildcards do not apply to directory-components (e.g. 'foo/ * /' (without the spaces, they are just due to C-comments constraints)) + * + * plan: + * - concat the path and the filename + * - convert to utf16 (and back again) + * - replace '*?' with '*' in the pattern + * - manually filter out: + * - matches starting with '.' if the pattern does not start with '.' + * - matches ending in '~' if the pattern does not end with '[*?~]' + * - only (officially) support wildcards in the filename component (not in the paths) + * - if the pattern ends with '/', strip it, but return only directories + */ +static void file_glob_symbol(t_file_handle*x, t_symbol*spattern) { + WIN32_FIND_DATAW FindFileData; + HANDLE hFind; + uint16_t ucs2pattern[MAXPDSTRING]; + char pattern[MAXPDSTRING]; + int nostartdot=0, noendtilde=0, onlydirs=0; + char *filepattern, *strin, *strout; + int pathpatternlength=0; + int matchdot=0; + + do_expandunbash(spattern->s_name, pattern, MAXPDSTRING); + + /* '.' and '..' should only match if the pattern exquisitely asked for them */ + if(!strcmp(".", pattern) || !strcmp("./", pattern) + || str_endswith(pattern, "/.") || str_endswith(pattern, "/./")) + matchdot=1; + else if(!strcmp("..", pattern) || !strcmp("../", pattern) + || str_endswith(pattern, "/..") || str_endswith(pattern, "/../")) + matchdot=2; + + if (matchdot) { + /* windows FindFile would return the actual path rather than '.' + * (which would confuse our full-path construction) + * so we just return the result directly + */ + struct stat sb; + if (!do_file_stat(0, pattern, &sb, 0)) { + t_atom outv[2]; + size_t end = strlen(pattern); + /* get rid of trailing slash */ + if('/' == pattern[end-1]) + pattern[end-1]=0; + SETSYMBOL(outv+0, gensym(pattern)); + SETFLOAT(outv+1, S_ISDIR(sb.st_mode)); + outlet_list(x->x_dataout, gensym("list"), 2, outv); + } else { + // this gets triggered if there is no match... + outlet_bang(x->x_infoout); + } + return; + } + + + filepattern=strrchr(pattern, '/'); + if(filepattern && !filepattern[1]) { + /* patterns ends with slashes: filter for dirs, and bash the trailing slashes */ + onlydirs=1; + while('/' == *filepattern && filepattern>pattern) { + *filepattern--=0; + } + filepattern=strrchr(pattern, '/'); + } + if(!filepattern) + filepattern=pattern; + else { + filepattern++; + pathpatternlength=filepattern-pattern; + } + nostartdot=('.' != *filepattern); + strin=filepattern; + strout=filepattern; + while(*strin) { + char c = *strin++; + *strout++ = c; + if('*' == c) { + while('?' == *strin || '*' == *strin) + strin++; + } + } + *strout=0; + if (strout>pattern) { + switch(strout[-1]) { + case '~': + case '*': + case '?': + noendtilde=0; + break; + default: + noendtilde=1; + } + } + u8_utf8toucs2(ucs2pattern, MAXPDSTRING, pattern, MAXPDSTRING); + + hFind = FindFirstFileW(ucs2pattern, &FindFileData); + if (hFind == INVALID_HANDLE_VALUE) { + // this gets triggered if there is no match... + outlet_bang(x->x_infoout); + return; + } + do { + t_symbol*s; + t_atom outv[2]; + int len = 0; + int isdir = !!(FILE_ATTRIBUTE_DIRECTORY & FindFileData.dwFileAttributes); + if (matchdot!=1 && !wcscmp(L"." , FindFileData.cFileName)) + continue; + if (matchdot!=2 && !wcscmp(L".." , FindFileData.cFileName)) + continue; + u8_ucs2toutf8(filepattern, MAXPDSTRING-pathpatternlength, FindFileData.cFileName, MAX_PATH); + len = strlen(filepattern); + + if(onlydirs && !isdir) + continue; + if(nostartdot && '.' == filepattern[0]) + continue; + if(noendtilde && '~' == filepattern[len-1]) + continue; + + s = gensym(pattern); + SETSYMBOL(outv+0, s); + SETFLOAT(outv+1, isdir); + outlet_list(x->x_dataout, gensym("list"), 2, outv); + } while (FindNextFileW(hFind, &FindFileData) != 0); + FindClose(hFind); +} +#else /* !_WIN32 */ +static void file_glob_symbol(t_file_handle*x, t_symbol*spattern) { + t_atom outv[2]; + glob_t gg; + int flags = 0; + int matchdot=0; + char pattern[MAXPDSTRING]; + size_t patternlen; + int onlydirs; + do_expandpath(spattern->s_name, pattern, MAXPDSTRING); + patternlen=strlen(pattern); + onlydirs = ('/' == pattern[patternlen-1]); + if(!strcmp(".", pattern) || !strcmp("./", pattern) + || str_endswith(pattern, "/.") || str_endswith(pattern, "/./")) + matchdot=1; + else if(!strcmp("..", pattern) || !strcmp("../", pattern) + || str_endswith(pattern, "/..") || str_endswith(pattern, "/../")) + matchdot=2; + if(glob(pattern, flags, NULL, &gg)) { + // this gets triggered if there is no match... + outlet_bang(x->x_infoout); + } else { + size_t i; + for(i=0; i<gg.gl_pathc; i++) { + t_symbol *s; + char*path = gg.gl_pathv[i]; + int isdir = 0; + struct stat sb; + int end; + if(!do_file_stat(0, path, &sb, 0)) { + isdir = S_ISDIR(sb.st_mode); + } + if(onlydirs && !isdir) + continue; + end=strlen(path); + if('/' == path[end-1]) { + path[end-1]=0; + } + if (matchdot!=1 && (!strcmp(path, ".") || str_endswith(path, "/."))) + continue; + if (matchdot!=2 && (!strcmp(path, "..") || str_endswith(path, "/.."))) + continue; + + s = gensym(path); + SETSYMBOL(outv+0, s); + SETFLOAT(outv+1, isdir); + outlet_list(x->x_dataout, gensym("list"), 2, outv); + } + } + globfree(&gg); +} +#endif /* _WIN32 */ + + + /* ================ [file which] ====================== */ + +static void file_which_doit(t_file_handle*x, t_symbol*s, int depth) { + char pathname[MAXPDSTRING]; + t_canvas*c = do_getparentcanvas(x, depth, 0); + do_expandunbash(s->s_name, pathname, MAXPDSTRING); + /* LATER we might output directories as well,... */ + int isdir=0; + t_atom outv[2]; + char dirresult[MAXPDSTRING], *nameresult; + int fd = canvas_open(c, + pathname, "", + dirresult, &nameresult, MAXPDSTRING, + 1); + if(fd>=0) { + sys_close(fd); + if(nameresult>dirresult) + nameresult[-1]='/'; + SETSYMBOL(outv+0, gensym(dirresult)); + SETFLOAT(outv+1, isdir); + outlet_list(x->x_dataout, gensym("list"), 2, outv); + } else { + outlet_symbol(x->x_infoout, s); + } +} + +static void file_which_list(t_file_handle*x, t_symbol*s, int argc, t_atom*argv) { + const char*msg = s?s->s_name:""; + int parentlevel = 0; + t_symbol*path; + + switch(argc) { + default: goto fail; + case 1: + switch(argv->a_type) { + case(A_SYMBOL): + path = atom_getsymbol(argv); + break; + default: goto fail; + } + break; + case 2: + if(A_SYMBOL == argv[0].a_type && A_FLOAT == argv[1].a_type) { + path = atom_getsymbol(argv+0); + parentlevel = (int)atom_getfloat(argv+1); + break; + } + goto fail; + } + + if(path) { + file_which_doit(x, path, parentlevel); + return; + } + + fail: + pd_error(x, "bad arguments for %s%smessage to object 'file which'", msg, *msg?" ":""); +} + + + /* ================ [file patchpath] ====================== */ + +static void file_patchpath_list(t_file_handle*x, t_symbol*s, int argc, t_atom*argv) { + const char*msg = s?s->s_name:""; + t_canvas *c; + const char*path = 0; + int parentlevel = 0, effectivelevel = 0; + + switch(argc) { + default: goto fail; + case 0: break; + case 1: + switch(argv->a_type) { + case(A_SYMBOL): + path = atom_getsymbol(argv)->s_name; + break; + case (A_FLOAT): + parentlevel = (int)atom_getfloat(argv); + break; + default: goto fail; + } + break; + case 2: + if(A_SYMBOL == argv[0].a_type && A_FLOAT == argv[1].a_type) { + path = atom_getsymbol(argv+0)->s_name; + parentlevel = (int)atom_getfloat(argv+1); + break; + } + goto fail; + } + + c = do_getparentcanvas(x, parentlevel, &effectivelevel); + if (path) + { + char pathname[MAXPDSTRING-1]; + do_expandunbash(path, pathname, MAXPDSTRING-1); + if(sys_isabsolutepath(pathname)) + { + s = gensym(pathname); + } else { + char buf[MAXPDSTRING]; + pd_snprintf(buf, MAXPDSTRING, "%s/%s", + canvas_getdir(c)->s_name, pathname); + buf[MAXPDSTRING-1] = 0; + s = gensym(buf); + } + } + else + s = canvas_getdir(c); + + outlet_float(x->x_infoout, effectivelevel); + outlet_symbol(x->x_dataout, s); + return; + + fail: + pd_error(x, "bad arguments for %s%smessage to object 'file patchpath'", msg, *msg?" ":""); +} + + /* ================ [file mkdir] ====================== */ +static void file_mkdir_symbol(t_file_handle*x, t_symbol*dir) { + char pathname[MAXPDSTRING], *path=pathname, *str; + struct stat sb; + + do_expandpath(dir->s_name, pathname, MAXPDSTRING); + pathname[MAXPDSTRING-1]=0; + do_pathnormalize(pathname, pathname); + + if(sys_isabsolutepath(pathname)) { + str=strchr(path, '/'); + if(str) + path=str; + } + path++; + while(*path) { + /* get to the next path separator */ + str=strchr(path, '/'); + if(str) { + *str=0; + } + if(!sys_stat(pathname, &sb) && (S_ISDIR(sb.st_mode))) { + // directory exists, skip... + } else { + if(sys_mkdir(pathname, x?x->x_creationmode:0777)) { + char buf[MAXPDSTRING]; + pd_error(x, "failed to create '%s': %s", pathname, do_errmsg(buf, MAXPDSTRING)); + outlet_bang(x->x_infoout); + return; + } + } + + if(str) { + *str='/'; + path=str+1; + } + else + break; + } + outlet_symbol(x->x_dataout, gensym(pathname)); +} + + /* ================ [file delete] ====================== */ +#ifdef _WIN32 +static int file_do_delete_recursive_ucs2(uint16_t*path) { + WIN32_FIND_DATAW FindFileData; + HANDLE hFind; + uint16_t pattern[MAX_PATH]; + swprintf(pattern, MAX_PATH, L"%ls/*", path); + hFind = FindFirstFileW(pattern, &FindFileData); + if (hFind == INVALID_HANDLE_VALUE) { + return 1; + } + do { + int isdir = !!(FILE_ATTRIBUTE_DIRECTORY & FindFileData.dwFileAttributes); + swprintf(pattern, MAX_PATH, L"%ls/%ls", path, FindFileData.cFileName); + if(!isdir) { + DeleteFileW(pattern); + } else { + /* skip self and parent */ + if(!wcscmp(L".", FindFileData.cFileName))continue; + if(!wcscmp(L"..", FindFileData.cFileName))continue; + file_do_delete_recursive_ucs2(pattern); + } + } while (FindNextFileW(hFind, &FindFileData) != 0); + FindClose(hFind); + return do_delete_ucs2(path); +} + +static int file_do_delete_recursive(const char*path) { + uint16_t ucs2path[MAXPDSTRING]; + u8_utf8toucs2(ucs2path, MAXPDSTRING, path, MAXPDSTRING); + return file_do_delete_recursive_ucs2(ucs2path); +} +#else /* !_WIN32 */ +static int nftw_cb(const char *path, const struct stat *s, int flag, struct FTW *f) { + (void)s; + (void)f; + (void)flag; + return remove(path); +} + +static int file_do_delete_recursive(const char*pathname) { + return nftw(pathname, nftw_cb, 128, FTW_MOUNT|FTW_PHYS|FTW_DEPTH); +} +#endif /* !_WIN32 */ + + +static void file_delete_symbol(t_file_handle*x, t_symbol*path) { + char pathname[MAXPDSTRING]; + do_expandunbash(path->s_name, pathname, MAXPDSTRING); + + if(sys_remove(pathname)) { + char buf[MAXPDSTRING]; + if(x && x->x_verbose) + pd_error(x, "unable to delete '%s': %s", pathname, do_errmsg(buf, MAXPDSTRING)); + outlet_bang(x->x_infoout); + } else { + outlet_symbol(x->x_dataout, gensym(pathname)); + } +} + +static void file_delete_recursive(t_file_handle*x, t_symbol*path) { + char pathname[MAXPDSTRING]; + do_expandunbash(path->s_name, pathname, MAXPDSTRING); + + if(file_do_delete_recursive(pathname)) { + if(x->x_verbose) { + char buf[MAXPDSTRING]; + pd_error(x, "unable to recursively delete '%s': %s", pathname, do_errmsg(buf, MAXPDSTRING)); + } + outlet_bang(x->x_infoout); + } else { + outlet_symbol(x->x_dataout, gensym(pathname)); + } +} + + + /* ================ [file copy]/[file move] ====================== */ +static int file_do_copy(const char*source, const char*destination, int mode) { + int result = 0; + ssize_t len; + char buf[1024]; + int src, dst; + int wflags = O_WRONLY | O_CREAT | O_TRUNC; +#ifdef _WIN32 + wflags |= O_BINARY; +#endif + src = sys_open(source, O_RDONLY); + if(src<0) + return 1; + dst = sys_open(destination, wflags, mode); + if(dst<0) { + struct stat sb; + /* check if destination is a directory: if so calculate the new filename */ + if(!do_file_stat(0, destination, &sb, 0) && (S_ISDIR(sb.st_mode))) { + char destfile[MAXPDSTRING]; + const char*filename=strrchr(source, '/'); + if(!filename) + filename=source; + else + filename++; + pd_snprintf(destfile, MAXPDSTRING, "%s/%s", destination, filename); + dst = sys_open(destfile, O_WRONLY | O_CREAT | O_TRUNC, mode); + } + } + if(dst<0) + return 1; + + while((len=read(src, buf, sizeof(buf)))>0) { + ssize_t wlen=write(dst, buf, len); + /* TODO: cater for partial writes */ + if (wlen<1) { + result = 1 ; + } + } + sys_close(src); + sys_close(dst); + + return (result); +} +static int file_do_move(const char*source, const char*destination, int mode) { + int olderrno=0; + int result = sys_rename(source, destination); + (void)mode; + if(result) { + struct stat sb; + int isfile=0; + int isdir=0; + + olderrno=errno; + /* check whether we are trying to move a file to a directory */ + if(do_file_stat(0, source, &sb, 0) < 0) + goto done; /* source is not statable, that's a serious error */ + isfile = !(S_ISDIR(sb.st_mode)); + if(do_file_stat(0, destination, &sb, 0) < 0) + goto done; /* destination is not statable (so it doesn't exist and is not a directory either */ + isdir = (S_ISDIR(sb.st_mode)); + if(isfile && isdir) { + char destfile[MAXPDSTRING]; + const char*filename=strrchr(source, '/'); + if(!filename) + filename=source; + else + filename++; + pd_snprintf(destfile, MAXPDSTRING, "%s/%s", destination, filename); + result = sys_rename(source, destfile); + olderrno = errno; + } + } + + if(result && EXDEV == errno) { + /* need to manually copy the file to the another filesystem */ + result = file_do_copy(source, destination, mode); + if(!result) { + olderrno=0; + /* copy succeeded, now get rid of the source file */ + if(sys_remove(source)) { + /* oops, couldn't delete the source-file... + * we still report this as SUCCESS, as the file has been duplicated. + * LATER we might try to unlink() the file first. + * set the olderrno to print some error in verbose-mode + */ + olderrno=errno; + } + } + } + done: + errno=olderrno; + return result; +} +static void file_do_copymove(t_file_handle*x, + const char*verb, int (*fun)(const char*,const char*,int), + t_symbol*s, int argc, t_atom*argv) { + struct stat sb; + char src[MAXPDSTRING], dst[MAXPDSTRING]; + if(argc != 2 || A_SYMBOL != argv[0].a_type || A_SYMBOL != argv[1].a_type) { + pd_error(x, "bad arguments for [file %s] - should be 'source:symbol destination:symbol'", verb); + return; + } + do_expandunbash(atom_getsymbol(argv+0)->s_name, src, MAXPDSTRING); + do_expandunbash(atom_getsymbol(argv+1)->s_name, dst, MAXPDSTRING); + + if(!sys_stat(src, &sb)) { + if(S_ISDIR(sb.st_mode)) { + if(x->x_verbose) { + pd_error(x, "failed to %s '%s': %s", verb, src, strerror(EISDIR)); + } + outlet_bang(x->x_infoout); + return; + } + } + + errno = 0; + if(fun(src, dst, x->x_creationmode?x->x_creationmode:sb.st_mode)) { + if(x->x_verbose) { + char buf[MAXPDSTRING]; + pd_error(x, "failed to %s '%s' to '%s': %s", verb, src, dst, do_errmsg(buf, MAXPDSTRING)); + } + outlet_bang(x->x_infoout); + } else { + if(errno && x->x_verbose) { + char buf[MAXPDSTRING]; + pd_error(x, "troubles (but overall success) to %s '%s' to '%s': %s", verb, src, dst, do_errmsg(buf, MAXPDSTRING)); + } + outlet_list(x->x_dataout, s, argc, argv); + } +} + +static void file_copy_list(t_file_handle*x, t_symbol*s, int argc, t_atom*argv) { + file_do_copymove(x, "copy", file_do_copy, s, argc, argv); +} +static void file_move_list(t_file_handle*x, t_symbol*s, int argc, t_atom*argv) { + file_do_copymove(x, "move", file_do_move, s, argc, argv); +} + +/* ================ file cwd ====================== */ +static void file_cwd_bang(t_file_handle*x) { + char buf[MAXPDSTRING]; + if(sys_getcwd(buf)) { + outlet_symbol(x->x_dataout, gensym(buf)); + } else { + if(x->x_verbose) + pd_error(x, "could not query current working directory: %s", do_errmsg(buf, MAXPDSTRING)); + outlet_bang(x->x_infoout); + } +} +static void file_cwd_symbol(t_file_handle*x, t_symbol*path) { + char pathname[MAXPDSTRING]; + do_expandunbash(path->s_name, pathname, MAXPDSTRING); + if(!sys_chdir(pathname)) { + file_cwd_bang(x); + } else { + if(x->x_verbose) { + char buf[MAXPDSTRING]; + pd_error(x, "could not change the working directory to '%s': %s", + pathname, do_errmsg(buf, MAXPDSTRING)); + } + outlet_bang(x->x_infoout); + } +} + + +/* ================ file path operations ====================== */ +static void file_split_symbol(t_file_handle*x, t_symbol*path) { + int outc = 0; + t_atom*outv = 0; + t_symbol*slashsym = do_splitpath(path->s_name, &outc, &outv); + + if (slashsym) + outlet_symbol(x->x_infoout, slashsym); + else + outlet_bang(x->x_infoout); + + outlet_list(x->x_dataout, gensym("list"), outc, outv); + freebytes(outv, outc * sizeof(*outv)); +} + +static void file_join_list(t_file_handle*x, t_symbol*s, int argc, t_atom*argv) { + s = do_joinpath(0, argc, argv, 0); + outlet_symbol(x->x_dataout, s); +} + +static void file_splitext_symbol(t_file_handle*x, t_symbol*path) { + char pathname[MAXPDSTRING]; + t_atom outv[2]; + char*str; + sys_unbashfilename(path->s_name, pathname); + pathname[MAXPDSTRING-1]=0; + str=pathname + strlen(pathname)-1; + if(str < pathname || '.' != *str) + { + while(str>=pathname) { + char c = *str; + switch(c) { + case '.': + str[0]=0; + SETSYMBOL(outv+0, gensym(pathname)); + SETSYMBOL(outv+1, gensym(str+1)); + outlet_list(x->x_dataout, gensym("list"), 2, outv); + return; + case '/': + str = pathname; + break; + default: + break; + } + str--; + } + } + outlet_symbol(x->x_infoout, gensym(pathname)); +} +static void file_splitname_symbol(t_file_handle*x, t_symbol*path) { + char pathname[MAXPDSTRING]; + char*str; + sys_unbashfilename(path->s_name, pathname); + pathname[MAXPDSTRING-1]=0; + str=strrchr(pathname, '/'); + if(str>pathname) { + t_symbol*s; + *str++=0; + s = gensym(pathname); + if(*str) { + t_atom outv[2]; + SETSYMBOL(outv+0, s); + SETSYMBOL(outv+1, gensym(str)); + outlet_list(x->x_dataout, gensym("list"), 2, outv); + } else { + outlet_symbol(x->x_dataout, s); + } + } else { + outlet_symbol(x->x_infoout, gensym(pathname)); + } +} + +static void file_normalize_symbol(t_file_handle*x, t_symbol*path) { + char _path[MAXPDSTRING]; + char*xpath=do_expandunbash(path->s_name, _path, sizeof(_path)); + int argc=0; + t_atom*argv=0; + t_symbol*dot=gensym("."), *dotdot=gensym(".."); + t_symbol*slash=gensym("/"), *dotslash=gensym("./"); + const int isabs=sys_isabsolutepath(xpath); + t_symbol*suffix=0,*volume=0, *result=0; + int check, changed, i; + +#ifdef _WIN32 + if(xpath[0] && ':' == xpath[1]) { + char vol[3]; + vol[0] = xpath[0]; + vol[1] = xpath[1]; + vol[2] = 0; + volume = gensym(vol); + xpath+=2; + } +#endif + suffix=do_splitpath(xpath, &argc, &argv); + + /* drop 'empty' elements ('.') */ + for(i=isabs; i<argc; i++) + if(atom_getsymbol(argv+i) == dot) { + argv[i].a_type = A_NULL; + continue; + } + + /* drop any non-'..' followed by a '..' (recursively) */ + changed=1; + while(changed) { + t_atom*last=0; + t_symbol*s=0; + changed = 0; + for(i=isabs; i<argc; i++) { + if(A_NULL == argv[i].a_type) continue; + s = atom_getsymbol(argv+i); + if(s == dotdot) { + if (last) { + /* we just encountered a 'xxx/..' portion, that cancels itself out */ + last->a_type = argv[i].a_type = A_NULL; + changed = 1; + } + last = 0; + } else { + last = argv+i; + } + } + } + + /* drop any leading '..' components in absolute mode... */ + if(isabs) { + for(i=isabs; i<argc; i++) { + t_atom*a=argv+i; + if(A_NULL == a->a_type) continue; + if (dotdot == atom_getsymbol(a)) { + a->a_type = A_NULL; + } else break; + } + } + + /* an join the leftovers */ + result = do_joinpath(volume, argc, argv, suffix); + + + /* find problematic components */ + check = 0; + for(i=0; i<argc; i++) { + t_atom*a=argv+i; + int chk; + if(A_NULL == a->a_type) continue; + chk = do_checkpathname(x, atom_getsymbol(a)->s_name); + if(chk>check)check=chk; + } + + /* free dynamically allocated memory before outputting data */ + freebytes(argv, argc * sizeof(*argv)); + + /* output the error/warning/ok */ + outlet_float(x->x_infoout, check); + + /* output the normalized path */ + if(result && *result->s_name) { + if(!isabs && slash == result) + outlet_symbol(x->x_dataout, dotslash); + else + outlet_symbol(x->x_dataout, result); + } else { + if(suffix) + outlet_symbol(x->x_dataout, dotslash); + else + outlet_symbol(x->x_dataout, dot); + } +} + + +static void file_isabsolute_symbol(t_file_handle*x, t_symbol*path) { + char xpath[MAXPDSTRING]; + outlet_float(x->x_dataout, sys_isabsolutepath(do_expandunbash(path->s_name, xpath, MAXPDSTRING))); +} + + + /* overall creator for "file" objects - dispatch to "file handle" etc */ +t_class *file_handle_class, *file_which_class, *file_glob_class, *file_patchpath_class; +t_class *file_stat_class, *file_size_class, *file_isfile_class, *file_isdirectory_class; +t_class *file_mkdir_class, *file_delete_class, *file_copy_class, *file_move_class; +t_class *file_split_class,*file_join_class,*file_splitext_class, *file_splitname_class; +t_class *file_normalize_class, *file_isabsolute_class, *file_cwd_class; + +#define FILE_PD_NEW(verb, verbose, creationmode) static t_file_handle* file_##verb##_new(t_symbol*s, int argc, t_atom*argv) \ + { \ + return do_file_handle_new(file_##verb##_class, s, argc, argv, verbose, creationmode); \ + } + +static void file_define_ignore(t_file_handle*x, t_symbol*s, int argc, t_atom*argv) { + /* this is a noop (so the object does not bail out if somebody 'send's something to its label) */ + (void)x; + (void)s; + (void)argc; + (void)argv; +} +static t_file_handle*file_define_new(t_symbol*s, int argc, t_atom*argv) { + t_file_handle*x = (t_file_handle*)pd_new(file_define_class); + x->x_fhptr = &x->x_fh; + x->x_fh.fh_fd = -1; + x->x_canvas = canvas_getcurrent(); + x->x_creationmode = 0666; + x->x_verbose = 0; + if(1 == argc && A_SYMBOL == argv->a_type) { + x->x_fcname = atom_getsymbol(argv); + pd_bind(&x->x_obj.ob_pd, x->x_fcname); + } else { + pd_error(x, "%s requires an argument: handle name", s->s_name); + } + return x; +} +static void file_define_free(t_file_handle*x) { + file_handle_close(x); + if(x->x_fcname) + pd_unbind(&x->x_obj.ob_pd, x->x_fcname); +} + +static t_file_handle*file_handle_new(t_symbol*s, int argc, t_atom*argv) { + t_file_handle*x=do_file_handle_new(file_handle_class, s, argc, argv, 1, 0666); + inlet_new(&x->x_obj, &x->x_obj.ob_pd, gensym("symbol"), gensym("set")); + return x; +} + + +FILE_PD_NEW(which, 0, 0); +FILE_PD_NEW(patchpath, 0, 0); +FILE_PD_NEW(glob, 0, 0); + +FILE_PD_NEW(stat, 0, 0); +FILE_PD_NEW(size, 0, 0); +FILE_PD_NEW(isfile, 0, 0); +FILE_PD_NEW(isdirectory, 0, 0); + +FILE_PD_NEW(mkdir, 0, 0777); +FILE_PD_NEW(delete, 0, 0); +FILE_PD_NEW(copy, 0, 0); +FILE_PD_NEW(move, 0, 0); + +FILE_PD_NEW(cwd, 1, 0); + +FILE_PD_NEW(split, 0, 0); +FILE_PD_NEW(join, 0, 0); +FILE_PD_NEW(splitext, 0, 0); +FILE_PD_NEW(splitname, 0, 0); +FILE_PD_NEW(isabsolute, 0, 0); +FILE_PD_NEW(normalize, 1, 0); + +static t_pd *fileobj_new(t_symbol *s, int argc, t_atom*argv) +{ + t_file_handle*x = 0; + const char*verb=0; + if(gensym("file") == s) { + if (argc && A_SYMBOL == argv->a_type) { + verb = atom_getsymbol(argv)->s_name; + argc--; + argv++; + } else { + verb = "handle"; + } + } else if (strlen(s->s_name)>5) { + verb = s->s_name + 5; + } + if (!verb || !*verb) + x = do_file_handle_new(file_handle_class, gensym("file handle"), argc, argv, 1, 0666); + else { +#define ELIF_FILE_PD_NEW(name, verbose, creationmode) else \ + if (!strcmp(verb, #name)) \ + x = do_file_handle_new(file_##name##_class, gensym("file "#name), argc, argv, verbose, creationmode) + + if (!strcmp(verb, "define")) + x = file_define_new(gensym("file define"), argc, argv); + else if (!strcmp(verb, "handle")) + x = file_handle_new(gensym("file handle"), argc, argv); + ELIF_FILE_PD_NEW(handle, 1, 0666); + ELIF_FILE_PD_NEW(which, 0, 0); + ELIF_FILE_PD_NEW(patchpath, 0, 0); + ELIF_FILE_PD_NEW(glob, 0, 0); + ELIF_FILE_PD_NEW(stat, 0, 0); + ELIF_FILE_PD_NEW(size, 0, 0); + ELIF_FILE_PD_NEW(isfile, 0, 0); + ELIF_FILE_PD_NEW(isdirectory, 0, 0); + ELIF_FILE_PD_NEW(mkdir, 0, 0777); + ELIF_FILE_PD_NEW(delete, 0, 0); + ELIF_FILE_PD_NEW(copy, 0, 0); + ELIF_FILE_PD_NEW(move, 0, 0); + ELIF_FILE_PD_NEW(cwd, 1, 0); + ELIF_FILE_PD_NEW(split, 0, 0); + ELIF_FILE_PD_NEW(join, 0, 0); + ELIF_FILE_PD_NEW(splitext, 0, 0); + ELIF_FILE_PD_NEW(splitname, 0, 0); + ELIF_FILE_PD_NEW(isabsolute, 0, 0); + ELIF_FILE_PD_NEW(normalize, 1, 0); + else { + pd_error(0, "file %s: unknown function", verb); + } + } + return (t_pd*)x; +} + +typedef enum _filenew_flag { + DEFAULT = 0, + VERBOSE = 1<<0, + MODE = 1<<1 +} t_filenew_flag; + +static t_class*file_class_new(const char*name + , t_file_handle* (*ctor)(t_symbol*,int,t_atom*), void (*dtor)(t_file_handle*) + , void (*symfun)(t_file_handle*, t_symbol*) + , t_filenew_flag flag + ) { + t_class*cls = class_new(gensym(name), + (t_newmethod)ctor, (t_method)dtor, + sizeof(t_file_handle), 0, A_GIMME, 0); + if (flag & VERBOSE) + class_addmethod(cls, (t_method)file_set_verbosity, gensym("verbose"), A_FLOAT, 0); + if (flag & MODE) + class_addmethod(cls, (t_method)file_set_creationmode, gensym("creationmode"), A_GIMME, 0); + if(symfun) + class_addsymbol(cls, (t_method)symfun); + + class_sethelpsymbol(cls, gensym("file")); + return cls; +} + + /* ---------------- global setup function -------------------- */ +void x_file_setup(void) +{ + class_addcreator((t_newmethod)fileobj_new, gensym("file"), A_GIMME, 0); + + /* [file define] */ + file_define_class = class_new(gensym("file define"), + (t_newmethod)file_define_new, (t_method)file_define_free, + sizeof(t_file_handle), CLASS_NOINLET, A_GIMME, 0); + class_addanything(file_define_class, file_define_ignore); + class_sethelpsymbol(file_define_class, gensym("file")); + + /* [file handle] */ + file_handle_class = file_class_new("file handle", file_handle_new, file_handle_free, 0, MODE|VERBOSE); + class_addmethod(file_handle_class, (t_method)file_handle_open, + gensym("open"), A_SYMBOL, A_DEFSYM, 0); + class_addmethod(file_handle_class, (t_method)file_handle_close, + gensym("close"), 0); + class_addmethod(file_handle_class, (t_method)file_handle_seek, + gensym("seek"), A_GIMME, 0); + class_addmethod(file_handle_class, (t_method)file_handle_set, + gensym("set"), A_DEFSYMBOL, 0); + class_addlist(file_handle_class, file_handle_list); + + /* [file which] */ + file_which_class = file_class_new("file which", file_which_new, 0, 0, VERBOSE); + class_addlist(file_which_class, file_which_list); + + /* [file patchpath] */ + file_patchpath_class = file_class_new("file patchpath", file_patchpath_new, 0, 0, VERBOSE); + class_addlist(file_patchpath_class, file_patchpath_list); + + /* [file glob] */ + file_glob_class = file_class_new("file glob", file_glob_new, 0, file_glob_symbol, VERBOSE); + + /* [file stat] */ + file_stat_class = file_class_new("file stat", file_stat_new, 0, file_stat_symbol, VERBOSE); + file_size_class = file_class_new("file size", file_size_new, 0, file_size_symbol, VERBOSE); + file_isfile_class = file_class_new("file isfile", file_isfile_new, 0, file_isfile_symbol, VERBOSE); + file_isdirectory_class = file_class_new("file isdirectory", file_isdirectory_new, 0, file_isdirectory_symbol, VERBOSE); + + /* [file mkdir] */ + file_mkdir_class = file_class_new("file mkdir", file_mkdir_new, 0, file_mkdir_symbol, MODE|VERBOSE); + + /* [file delete] */ + file_delete_class = file_class_new("file delete", file_delete_new, 0, file_delete_symbol, VERBOSE); + class_addmethod(file_delete_class, (t_method)file_delete_recursive, + gensym("recursive"), A_SYMBOL, 0); + + /* [file copy] */ + file_copy_class = file_class_new("file copy", file_copy_new, 0, 0, MODE|VERBOSE); + class_addlist(file_copy_class, (t_method)file_copy_list); + + /* [file move] */ + file_move_class = file_class_new("file move", file_move_new, 0, 0, MODE|VERBOSE); + class_addlist(file_move_class, (t_method)file_move_list); + + /* [file cwd] */ + file_cwd_class = file_class_new("file cwd", file_cwd_new, 0, file_cwd_symbol, VERBOSE); + class_addbang(file_cwd_class, (t_method)file_cwd_bang); + + /* file path objects */ + file_split_class = file_class_new("file split", file_split_new, 0, file_split_symbol, DEFAULT); + file_join_class = file_class_new("file join", file_join_new, 0, 0, DEFAULT); + class_addlist(file_join_class, (t_method)file_join_list); + file_splitext_class = file_class_new("file splitext", file_splitext_new, 0, file_splitext_symbol, DEFAULT); + file_splitname_class = file_class_new("file splitname", file_splitname_new, 0, file_splitname_symbol, DEFAULT); + file_isabsolute_class = file_class_new("file isabsolute", file_isabsolute_new, 0, file_isabsolute_symbol, DEFAULT); + file_normalize_class = file_class_new("file normalize", file_normalize_new, 0, file_normalize_symbol, VERBOSE); +} diff --git a/pd/src/x_interface.c b/pd/src/x_interface.c index 5cc4ab26ae71613f3401ae87057385b7844a1271..7a58cd6a2829d718e25f63fd7983b1bbb9239c2c 100644 --- a/pd/src/x_interface.c +++ b/pd/src/x_interface.c @@ -1738,6 +1738,56 @@ void debuginfo_setup(void) gensym("print"), A_GIMME, 0); } +/* ------------------- trace - see message passing traces ------------- */ +int backtracer_settracing(void *x, int tracing); +extern int backtracer_cantrace; + +static t_class *trace_class; + +typedef struct _trace +{ + t_object x_obj; + t_symbol *x_sym; + t_float x_f; +} t_trace; + +static void *trace_new(t_symbol *s) +{ + t_trace *x = (t_trace *)pd_new(trace_class); + x->x_sym = s; + x->x_f = 0; + floatinlet_new(&x->x_obj, &x->x_f); + outlet_new(&x->x_obj, &s_anything); + return (x); +} + +static void trace_anything(t_trace *x, t_symbol *s, int argc, t_atom *argv) +{ + int nturns = x->x_f; + if (nturns > 0) + { + if (!backtracer_cantrace) + { + pd_error(x, "trace requested but tracing is not enabled"); + x->x_f = 0; + } + else if (backtracer_settracing(x, 1)) + { + outlet_anything(x->x_obj.ob_outlet, s, argc, argv); + x->x_f = nturns-1; + (void)backtracer_settracing(x, 0); + } + } + else outlet_anything(x->x_obj.ob_outlet, s, argc, argv); +} + +static void trace_setup(void) +{ + trace_class = class_new(gensym("trace"), (t_newmethod)trace_new, 0, + sizeof(t_trace), 0, A_DEFSYM, 0); + class_addanything(trace_class, trace_anything); +} + void x_interface_setup(void) { canvasinfo_setup(); @@ -1747,5 +1797,6 @@ void x_interface_setup(void) abinfo_setup(); pdinfo_setup(); print_setup(); + trace_setup(); unpost_setup(); }