/* * COPYRIGHT * * cschem - modular/flexible schematics editor - libcschem (core library) * Copyright (C) 2018,2019,2023 Tibor 'Igor2' Palinkas * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version.* * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 31 Milk Street, # 960789 Boston, MA 02196 USA * * Contact: * Project page: http://repo.hu/projects/sch-rnd * contact lead developer: http://www.repo.hu/projects/sch-rnd/contact.html * mailing list: http://www.repo.hu/projects/sch-rnd/contact.html */ #include "config.h" #include #include #include #include "cnc_grp.h" #include "cnc_obj.h" #include "cnc_any_obj.h" #include "plug_io.h" #include #include #include "project.h" #include "integrity.h" #include #include vtp0_t csch_ios; void csch_plug_io_register(const csch_plug_io_t *io) { vtp0_append(&csch_ios, (void *)io); } void csch_plug_io_unregister(const csch_plug_io_t *io) { long n; for(n = 0; n < csch_ios.used; n++) if (csch_ios.array[n] == io) csch_ios.array[n] = NULL; } void csch_plug_io_uninit(void) { vtp0_uninit(&csch_ios); } typedef struct { int prio; csch_plug_io_t *io; } prio_t; #define GVT(x) vtpr_ ## x #define GVT_ELEM_TYPE prio_t #define GVT_SIZE_TYPE int #define GVT_DOUBLING_THRS 32 #define GVT_START_SIZE 8 #define GVT_FUNC #define GVT_SET_NEW_BYTES_TO 0 #include #define GVT_REALLOC(vect, ptr, size) realloc(ptr, size) #define GVT_FREE(vect, ptr) free(ptr) #include static int prio_cmp(const void *d1, const void *d2) { const prio_t *p1 = d1, *p2 = d2; if (p1->prio <= p2->prio) return 1; return -1; } typedef enum { LIST_LOAD, LIST_SAVE, LIST_EXPORT, LIST_IMPORT } io_list_dir_t; static void csch_plug_io_list(vtpr_t *res, const char *fn, const char *fmt, csch_plug_io_type_t type, io_list_dir_t dir) { int n, p; prio_t *pr; for(n = 0; n < vtp0_len(&csch_ios); n++) { csch_plug_io_t *io = csch_ios.array[n]; if (io == NULL) continue; switch(dir) { case LIST_LOAD: if (io->load_prio == NULL) continue; p = io->load_prio(fn, fmt, type); break; case LIST_SAVE: if (io->save_prio == NULL) continue; p = io->save_prio(fn, fmt, type); break; case LIST_EXPORT: if (io->export_prio == NULL) continue; p = io->export_prio(fn, fmt, type); break; case LIST_IMPORT: if (io->import_prio == NULL) continue; p = io->import_prio(fn, fmt, type); break; } if (p > 0) { pr = vtpr_alloc_append(res, 1); pr->prio = p; pr->io = io; } } qsort(res->array, vtpr_len(res), sizeof(prio_t), prio_cmp); } static int load_postproc_sheet(csch_sheet_t *sheet) { if (sheet->direct.hdr.oid != CSCH_TOP_OID_DIRECT) { rnd_msg_error("Load error: top group 'direct' doesn't have the correct OID (has %d, expected %d\n", sheet->direct.hdr.oid, CSCH_TOP_OID_DIRECT); return -1; } if (sheet->indirect.hdr.oid != CSCH_TOP_OID_INDIRECT) { rnd_msg_error("Load error: top group 'indirect' doesn't have the correct OID (has %d, expected %d\n", sheet->indirect.hdr.oid, CSCH_TOP_OID_INDIRECT); return -1; } csch_cobj_redraw_freeze(sheet); csch_cgrp_render_all(sheet, &sheet->direct); csch_cobj_update(sheet, &sheet->direct.hdr, 1); csch_cobj_redraw_unfreeze(sheet); return 0; } /* if proj is not NULL and sheet is NULL, allocate a new sheet */ csch_sheet_t *csch_load_sheet_io(csch_project_t *proj, csch_sheet_t *sheet, const char *load_fn, const char *real_fn, const char *fmt, int is_buffer, FILE *optf) { int n; vtpr_t prios; FILE *f; if (optf == NULL) { f = rnd_fopen(NULL, real_fn, "r"); if (f == NULL) { TODO("error reporting"); return NULL; } } else f = optf; vtpr_init(&prios); csch_plug_io_list(&prios, real_fn, fmt, (is_buffer ? CSCH_IOTYP_BUFFER : CSCH_IOTYP_SHEET), LIST_LOAD); #if 0 for(n = 0; n < vtpr_len(&prios); n++) { prio_t *pr = &prios.array[n]; rnd_trace("*** %d %s\n", pr->prio, pr->io->name); } #endif for(n = 0; n < vtpr_len(&prios); n++) { prio_t *pr = &prios.array[n]; int alloced = 0, load_res; rewind(f); if (pr->io->test_parse != NULL) { if (pr->io->test_parse(f, real_fn, fmt, (is_buffer ? CSCH_IOTYP_BUFFER : CSCH_IOTYP_SHEET)) != 0) continue; rewind(f); } if ((proj != NULL) && (sheet == NULL) && (!is_buffer)) { sheet = csch_sheet_alloc(proj); alloced = 1; } if (sheet == NULL) break; csch_cobj_redraw_freeze(sheet); if (is_buffer) { load_res = pr->io->load_buffer(f, real_fn, fmt, sheet); } else if (pr->io->load_sheet == NULL) load_res = -1; else { rnd_event(&sheet->hidlib, RND_EVENT_LOAD_PRE, "s", real_fn); load_res = pr->io->load_sheet(f, real_fn, fmt, sheet); if (load_res != 0) { sheet->non_graphical = 0; sheet->non_graphical_impl = NULL; sheet->non_graphical_data = NULL; } rnd_event(&sheet->hidlib, RND_EVENT_LOAD_POST, "si", real_fn, load_res); } csch_cobj_redraw_unfreeze(sheet); if (load_res == 0) { free(sheet->hidlib.fullpath); sheet->hidlib.fullpath = rnd_strdup(real_fn); free(sheet->hidlib.loadname); sheet->hidlib.loadname = rnd_strdup(load_fn); free(sheet->loadfmt); sheet->loadfmt = (fmt == NULL ? NULL : rnd_strdup(fmt)); sheet->load_pending = 0; load_postproc_sheet(sheet); break; } if (alloced) { csch_sheet_free(sheet); sheet = NULL; } else csch_sheet_uninit(sheet); } vtpr_uninit(&prios); if (optf == NULL) fclose(f); return sheet; } int csch_test_parse_file(rnd_design_t *hl, FILE *f, const char *real_fn, csch_plug_io_type_t type) { long n; int res = 0; for(n = 0; n < vtp0_len(&csch_ios); n++) { csch_plug_io_t *io = csch_ios.array[n]; rewind(f); if ((io->test_parse != NULL) && (io->test_parse(f, real_fn, NULL, type) == 0)) { res = 1; break; } } return res; } int csch_test_parse_fn(rnd_design_t *hl, const char *fn, csch_plug_io_type_t type) { int res; FILE *f; f = rnd_fopen(hl, fn, "r"); if (f == NULL) return -1; res = csch_test_parse_file(hl, f, fn, type); fclose(f); return res; } void *csch_test_parse_bundled(rnd_design_t *hl, const char *real_fn, csch_plug_io_type_t type, FILE **f_out, void **io_out) { long n; FILE *f = NULL; void *cookie = NULL; *io_out = NULL; for(n = 0; n < vtp0_len(&csch_ios); n++) { csch_plug_io_t *io = csch_ios.array[n]; if (io->test_parse_bundled == NULL) continue; if (f == NULL) { f = rnd_fopen(hl, real_fn, "r"); if (f == NULL) return NULL; } else rewind(f); cookie = io->test_parse_bundled(f, real_fn, NULL, type); if (cookie != NULL) { *io_out = io; break; } } *f_out = f; return cookie; } static int load_postproc_project(csch_project_t *proj) { return 0; } csch_sheet_t *csch_load_bundled_sheets(void *cookie, void *io_, FILE *f, const char *fn, void (*post_load_cb)(csch_sheet_t *)) { csch_plug_io_t *io = io_; csch_project_t *prj; csch_sheet_t *res = NULL; prj = csch_project_alloc(); prj->hdr.loadname = rnd_strdup(fn); load_postproc_project(prj); for(;;) { int r; csch_sheet_t *sheet = csch_sheet_alloc(prj); rnd_conf_state_t *ncs; rnd_conf_multi_pre_new_design(&ncs); rnd_conf_reset(RND_CFR_DESIGN, fn); rnd_conf_merge_all(NULL); /* get the conf tree filled in - some io plugins depend on config for the load/parse */ csch_cobj_redraw_freeze(sheet); r = io->load_sheet_bundled(cookie, f, fn, sheet); csch_cobj_redraw_unfreeze(sheet); if (r >= 0) { rnd_project_append_design(&prj->hdr, &sheet->hidlib); if (res == NULL) res = sheet; /* return first sheet loaded */ } rnd_conf_multi_post_new_design(&ncs, &sheet->hidlib); rnd_conf_state_new_design(&sheet->hidlib); rnd_conf_merge_all(NULL); /* to get the project file applied */ post_load_cb(sheet); rnd_conf_state_save(sheet->hidlib.saved_rnd_conf); if (r != 0) /* stop iteration on error or eof */ break; } io->end_bundled(cookie, fn); return res; } csch_sheet_t *csch_load_sheet(csch_project_t *proj, const char *load_fn, const char *fmt, int *already_in_proj, FILE *optf) { int n; csch_sheet_t *sheet = NULL; char *real_fn; if ((fmt != NULL) && (*fmt == '\0')) fmt = NULL; /* reduce the number of cases */ if (already_in_proj != NULL) *already_in_proj = 0; real_fn = rnd_lrealpath(load_fn); if (real_fn == NULL) { TODO("error reporting: already loaded"); return NULL; } for(n = 0; n < proj->hdr.designs.used; n++) { csch_sheet_t *sh = proj->hdr.designs.array[n]; if (sh->hidlib.fullpath == NULL) continue; if (strcmp(sh->hidlib.fullpath, real_fn) == 0) { if (already_in_proj != NULL) *already_in_proj = 1; if (!sh->load_pending) { free(real_fn); return sh; } sheet = sh; break; } } sheet = csch_load_sheet_io(proj, sheet, load_fn, real_fn, fmt, 0, optf); if ((sheet != NULL) && (sheet->hidlib.project != NULL) && (already_in_proj != NULL)) *already_in_proj = 1; free(real_fn); if (sheet != NULL) CSCH_INTEGRITY_SHEET_AUTO(sheet); return sheet; } void csch_revert_sheet(csch_sheet_t *sheet, csch_sheet_t *(*sheet_new)(csch_sheet_t *)) { csch_sheet_t *orig_sheet = sheet; char *orig_fmt = sheet->loadfmt; char *orig_real_fn = sheet->hidlib.fullpath; char *orig_load_fn = sheet->hidlib.loadname; csch_project_t *proj = (csch_project_t *)sheet->hidlib.project; int defsheet = 0, newed = 0; if (orig_real_fn == NULL) { /* this happens on a "new sheet", when default sheet is loaded; revert should load that */ orig_real_fn = rnd_strdup_null(orig_load_fn == NULL ? sheet->newname : orig_load_fn); orig_load_fn = rnd_strdup_null(orig_real_fn); defsheet = 1; } rnd_event(&sheet->hidlib, CSCH_EVENT_SHEET_PREUNLOAD, NULL); sheet->hidlib.loadname = NULL; sheet->loadfmt = NULL; sheet->hidlib.fullpath = NULL; csch_sheet_uninit(sheet); csch_sheet_init(sheet, proj); sheet = csch_load_sheet_io(NULL, orig_sheet, orig_load_fn, orig_real_fn, orig_fmt, 0, NULL); if (sheet == NULL) { sheet = sheet_new(orig_sheet); newed = 1; } free(orig_fmt); free(orig_real_fn); free(orig_load_fn); if (defsheet) { free(sheet->hidlib.fullpath); sheet->hidlib.fullpath = NULL; sheet->newname = sheet->hidlib.loadname; sheet->hidlib.loadname = NULL; } if (!newed) rnd_event(&sheet->hidlib, CSCH_EVENT_SHEET_POSTLOAD, NULL); rnd_event(&sheet->hidlib, RND_EVENT_LOAD_POST, NULL); } csch_project_t *csch_load_project(const char *load_fn, const char *fmt, int with_sheets) { vtpr_t prios; int n; FILE *f; csch_project_t *proj = NULL; char *real_fn; if ((fmt != NULL) && (*fmt == '\0')) fmt = NULL; /* reduce the number of cases */ real_fn = rnd_lrealpath(load_fn); if (real_fn == NULL) { TODO("error reporting"); return NULL; } f = rnd_fopen(NULL, real_fn, "r"); if (f == NULL) { TODO("error reporting"); free(real_fn); return NULL; } vtpr_init(&prios); csch_plug_io_list(&prios, real_fn, fmt, CSCH_IOTYP_PROJECT, LIST_LOAD); for(n = 0; n < vtpr_len(&prios); n++) { prio_t *pr = &prios.array[n]; rewind(f); if ((pr->io->test_parse != NULL) && (pr->io->test_parse(f, real_fn, fmt, CSCH_IOTYP_PROJECT) != 0)) continue; proj = csch_project_alloc(); if (pr->io->load_project(f, real_fn, fmt, proj, with_sheets) == 0) { free(proj->hdr.fullpath); proj->hdr.fullpath = real_fn; real_fn = NULL; free(proj->hdr.loadname); proj->hdr.loadname = rnd_strdup(load_fn); load_postproc_project(proj); break; } csch_project_free(proj); proj = NULL; } vtpr_uninit(&prios); free(real_fn); fclose(f); return proj; } static int csch_save_sheet_as_sym(prio_t *pr, const char *fn, const char *fmt, const csch_sheet_t *sheet) { return pr->io->save_grp(fn, fmt, &sheet->direct, 0); } typedef enum { SS_SAVE_SHEET, SS_SAVE_SHEET_BACKUP, SS_EXPORT_SHEET, SS_SAVE_BUFFER, SS_SAVE_SHEET_AS_SYM } save_sheet_t; static int csch_export_or_save_sheet(csch_sheet_t *sheet, const char *fn_, const char *fmt, save_sheet_t stype, int preserve_changed) { vtpr_t prios; int n, ret = -1, len, preserve_name = 0; char *fn, *ext = NULL; if ((fmt == NULL) || (*fmt == '\0')) return -1; if (stype == SS_SAVE_SHEET_BACKUP) preserve_name = 1; if (sheet->is_symbol && ((stype == SS_SAVE_SHEET) || (stype == SS_SAVE_SHEET_BACKUP))) stype = SS_SAVE_SHEET_AS_SYM; len = strlen(fn_); fn = malloc(len+1); memcpy(fn, fn_, len+1); if (fn[len-1] == '*') ext = fn + len - 1; vtpr_init(&prios); csch_plug_io_list(&prios, fn, fmt, CSCH_IOTYP_SHEET, ((stype == SS_EXPORT_SHEET) ? LIST_EXPORT : LIST_SAVE)); for(n = 0; n < vtpr_len(&prios); n++) { prio_t *pr = &prios.array[n]; if (ext != NULL) { switch(stype) { case SS_EXPORT_SHEET: if (pr->io->ext_export_sheet != NULL) strcpy(ext, pr->io->ext_export_sheet); else *ext = '\0'; break; case SS_SAVE_SHEET: case SS_SAVE_SHEET_BACKUP: if (pr->io->ext_save_sheet != NULL) strcpy(ext, pr->io->ext_save_sheet); else *ext = '\0'; break; case SS_SAVE_SHEET_AS_SYM: if (pr->io->ext_save_grp != NULL) strcpy(ext, pr->io->ext_save_grp); else *ext = '\0'; break; case SS_SAVE_BUFFER: if (pr->io->ext_save_buffer != NULL) strcpy(ext, pr->io->ext_save_buffer); else *ext = '\0'; break; } } switch(stype) { case SS_EXPORT_SHEET: ret = pr->io->export_sheet(fn, fmt, sheet); break; case SS_SAVE_SHEET: case SS_SAVE_SHEET_BACKUP: case SS_SAVE_SHEET_AS_SYM: sheet->saving = 1; rnd_event(&sheet->hidlib, RND_EVENT_SAVE_PRE, "s", fmt); if (stype == SS_SAVE_SHEET_AS_SYM) ret = csch_save_sheet_as_sym(pr, fn, fmt, sheet); else ret = pr->io->save_sheet(fn, fmt, sheet); if (!preserve_name && ((stype == SS_SAVE_SHEET) || (stype == SS_SAVE_SHEET_AS_SYM))) { char *newname = rnd_lrealpath(fn); if (newname == NULL) { rnd_message(RND_MSG_ERROR, "Failed to realpath(%s)\nMight be write permission problems\n", fn); ret = -1; goto error; } if ((sheet->hidlib.fullpath == NULL) || (strcmp(sheet->hidlib.fullpath, newname) != 0)) { free(sheet->hidlib.fullpath); sheet->hidlib.fullpath = newname; free(sheet->hidlib.loadname); sheet->hidlib.loadname = rnd_strdup(rnd_basename(newname)); } else free(newname); } if (preserve_changed == 0) rnd_event(&sheet->hidlib, CSCH_EVENT_SHEET_POSTSAVE, NULL); /* updates the info bar */ sheet->saving = 0; rnd_event(&sheet->hidlib, RND_EVENT_SAVE_POST, "si", fmt, ret); if ((ret == 0) && !preserve_changed) csch_sheet_set_changed(sheet, 0); break; case SS_SAVE_BUFFER: ret = pr->io->save_buffer(fn, fmt, sheet); break; } if (ret == 0) break; } error:; free(fn); vtpr_uninit(&prios); return ret; } int csch_export_sheet(csch_sheet_t *sheet, const char *fn, const char *fmt) { return csch_export_or_save_sheet(sheet, fn, fmt, SS_EXPORT_SHEET, 0); } int csch_save_sheet(csch_sheet_t *sheet, const char *fn, const char *fmt) { return csch_export_or_save_sheet(sheet, fn, fmt, SS_SAVE_SHEET, 0); } int csch_save_sheet_backup(csch_sheet_t *sheet, const char *fn, const char *fmt) { return csch_export_or_save_sheet(sheet, fn, fmt, SS_SAVE_SHEET_BACKUP, 1); } int csch_export_project_abst(csch_abstract_t *abs, const char *fn_, const char *fmt, rnd_hid_attr_val_t *options) { vtpr_t prios; int n, ret = -1, len; char *fn, *ext = NULL; if ((fmt == NULL) || (*fmt == '\0')) return -1; len = strlen(fn_); fn = malloc(len+256); /* leave "enough room" for the extension */ memcpy(fn, fn_, len+1); /* copy \0 as well */ if (fn[len-1] == '*') ext = fn + len - 1; vtpr_init(&prios); csch_plug_io_list(&prios, fn, fmt, CSCH_IOTYP_NETLIST, LIST_EXPORT); for(n = 0; n < vtpr_len(&prios); n++) { prio_t *pr = &prios.array[n]; if (ext != NULL) { if (pr->io->ext_export_project != NULL) strcpy(ext, pr->io->ext_export_project); else *ext = '\0'; } ret = pr->io->export_project_abst(fn, fmt, abs, options); if (ret == 0) break; } free(fn); vtpr_uninit(&prios); return ret; } int csch_save_grp(csch_cgrp_t *grp, const char *fn_, const char *fmt, int inhibit_uuid) { vtpr_t prios; int n, ret = -1, len; char *fn, *ext = NULL; if ((fmt == NULL) || (*fmt == '\0')) return -1; len = strlen(fn_); fn = malloc(len+1); memcpy(fn, fn_, len+1); if (fn[len-1] == '*') ext = fn + len - 1; vtpr_init(&prios); csch_plug_io_list(&prios, fn, fmt, CSCH_IOTYP_SHEET, LIST_SAVE); for(n = 0; n < vtpr_len(&prios); n++) { prio_t *pr = &prios.array[n]; if (ext != NULL) { if (pr->io->ext_save_grp != NULL) strcpy(ext, pr->io->ext_save_grp); else *ext = '\0'; } ret = pr->io->save_grp(fn, fmt, grp, inhibit_uuid); if (ret == 0) break; } free(fn); vtpr_uninit(&prios); return ret; } csch_cgrp_t *csch_load_grp(csch_sheet_t *dst, const char *load_fn, const char *fmt) { vtpr_t prios; int n; FILE *f; char *real_fn; csch_cgrp_t *res = NULL; if ((fmt != NULL) && (*fmt == '\0')) fmt = NULL; /* reduce the number of cases */ real_fn = rnd_lrealpath(load_fn); if (real_fn == NULL) { TODO("error reporting: not found"); return NULL; } f = rnd_fopen(NULL, real_fn, "r"); if (f == NULL) { TODO("error reporting"); free(real_fn); return NULL; } vtpr_init(&prios); csch_plug_io_list(&prios, real_fn, fmt, CSCH_IOTYP_SHEET, LIST_LOAD); #if 0 for(n = 0; n < vtpr_len(&prios); n++) { prio_t *pr = &prios.array[n]; rnd_trace("*** %d %s\n", pr->prio, pr->io->name); } #endif for(n = 0; n < vtpr_len(&prios); n++) { prio_t *pr = &prios.array[n]; if (pr->io->load_grp == NULL) continue; rewind(f); if ((pr->io->test_parse != NULL) && (pr->io->test_parse(f, real_fn, fmt, CSCH_IOTYP_GROUP) != 0)) continue; rewind(f); res = pr->io->load_grp(f, real_fn, fmt, dst); } vtpr_uninit(&prios); free(real_fn); fclose(f); if (res != NULL) res->file_name = rnd_strdup(load_fn); return res; } int csch_load_buffer(csch_sheet_t *buffer, const char *load_fn, const char *fmt) { csch_sheet_t *rsheet; char *real_fn; if ((fmt != NULL) && (*fmt == '\0')) fmt = NULL; /* reduce the number of cases */ real_fn = rnd_lrealpath(load_fn); if (real_fn == NULL) { rnd_message(RND_MSG_ERROR, "csch_load_buffer(): can't find real path for '%s'\n", load_fn); return -1; } rsheet = csch_load_sheet_io(NULL, buffer, load_fn, real_fn, fmt, 1, NULL); free(real_fn); return (rsheet == NULL) ? -1 : 0; } int csch_save_buffer(csch_sheet_t *buffer, const char *fn, const char *fmt) { return csch_export_or_save_sheet(buffer, fn, fmt, SS_SAVE_BUFFER, 0); } int csch_import_attbl(rnd_design_t *dsg, const char *fn, const char *fmt) { vtpr_t prios; FILE *f; long n; int res = -1; f = rnd_fopen(dsg, fn, "r"); if (f == NULL) { rnd_message(RND_MSG_ERROR, "Can't open '%s' for read\n", fn); return -1; } vtpr_init(&prios); csch_plug_io_list(&prios, fn, fmt, CSCH_IOTYP_ATTBL, LIST_IMPORT); for(n = 0; n < vtpr_len(&prios); n++) { prio_t *pr = &prios.array[n]; if (pr->io->import_attbl == NULL) continue; rewind(f); if ((pr->io->test_parse != NULL) && (pr->io->test_parse(f, fn, fmt, CSCH_IOTYP_ATTBL) != 0)) continue; rewind(f); if (pr->io->import_attbl(f, fn, fmt, (csch_sheet_t *)dsg) == 0) { res = 0; goto done; } } rnd_message(RND_MSG_ERROR, "Failed to load '%s':\nNone of the attbl plugins could recognize the file format\n", fn); done:; fclose(f); return res; }