#include #include #include #include #include #include #include #include #include #include #include #include "database.hpp" #include "forumgen.hpp" #include "arrindex.hpp" #include "errlist.hpp" #include "fileops.hpp" #include "fpublish.hpp" #include "generate.hpp" // * architecture decision * // PLEASE REMEMBER!!! // all substitutions must (yes, MUST) be done within the database object, // because (!) variable names are a part of the database format! static FILE* start_file(const ScriptVariable &fname, int chmod_val, const ScriptVariable &diag_id, ErrorList **err) { make_directory_path(fname.c_str(), 1); FILE* f = fopen(fname.c_str(), "w"); if(!f) { ScriptVariable s(63, "Can't create file %s for %s [%s], skipping", fname.c_str(), diag_id.c_str(), strerror(errno)); ErrorList::AddError(err, s); return 0; } if(chmod_val) fchmod(fileno(f), chmod_val); return f; } static bool output_file(const ScriptVariable &fname, int chmod_val, const ScriptVariable &diag_id, ErrorList **err, const char *s1, const char *s2 = 0, const char *s3 = 0) { FILE *f = start_file(fname, chmod_val, diag_id, err); if(!f) return false; fputs(s1, f); if(s2) fputs(s2, f); if(s3) fputs(s3, f); fclose(f); return true; } ////////////////////////////////////////////////////////////////////////// // Genfiles // defined by ''genfile'' ini section, generated outside of the doctree // void generate_genfile(const ScriptVariable &id, Database& database, ErrorList **err) { ScriptVariable path, content; int chmod_val; if(!database.BuildGenericFile(id, path, chmod_val, content)) { ScriptVariable s(63, "Skipping generic file %s, check configs", id.c_str()); ErrorList::AddError(err, s); return; } ScriptVariable diag_id("-"); output_file(path, chmod_val, diag_id, err, content.c_str()); } void generate_all_genfiles(Database& database, ErrorList **err) { ScriptVector gfnames; database.GetGenfiles(gfnames); int i; for(i = 0; i < gfnames.Length(); i++) { generate_genfile(gfnames[i], database, err); } } ////////////////////////////////////////////////////////////////////////// // Pages // defined by ''page'' ini section, which includes all needed data // // pages can NOT provide a comment forum // bool generate_page(const ScriptVariable& id, Database& database, ErrorList **err) { ScriptVariable page_filename; page_filename = database.GetPageFilename(id); if(page_filename.IsInvalid() || page_filename == "") return true; /* suppressed, but it's not an error */ ScriptVariable s = database.BuildPage(id); if(s.IsInvalid()) { ScriptVariable s(63, "Page %s not configured", id.c_str()); ErrorList::AddError(err, s); return false; } int chmod_val = database.GetPageChmod(id); ScriptVariable diag_id("page "); diag_id += id; return output_file(page_filename, chmod_val, diag_id, err, s.c_str()); } void generate_all_pages(Database& database, ErrorList **errlst) { ScriptVector pagenames; database.GetPages(pagenames); int i; for(i = 0; i < pagenames.Length(); i++) { generate_page(pagenames[i], database, errlst); } } ////////////////////////////////////////////////////////////////////////// // Lists // defined by ''list'' ini section, // represented by ini section group named within the pagelist section // static void output_list_head(FILE *listf, const ListData &listdata, Database &database, ErrorList **err) { ScriptVariable s = database.BuildListHead(listdata); fputs(s.c_str(), listf); } static void output_list_tail(FILE *listf, const ListData &listdata, Database &database, ErrorList **err) { ScriptVariable s = database.BuildListTail(listdata); fputs(s.c_str(), listf); } static void output_list_item(FILE *listf, const ListData &listdata, const ListItemData &itemdata, Database &database, ErrorList **err) { ScriptVariable s = database.BuildListItem(listdata, itemdata); fputs(s.c_str(), listf); } static void generate_list_item_page(const ListData &listdata, const ListItemData &itemdata, Database &database, ErrorList **err) { ScriptVariable diag_id = listdata.id + "::" + itemdata.item_id; ScriptVariable mainpg, tail; ForumGenerator *fg; database.BuildListItemPage(listdata, itemdata, mainpg, tail, &fg); int pgcnt; bool reverse; bool multipage_by_comments = false; if(fg) { bool ok = fg->GetArrayInfo(pgcnt, reverse); if(ok && pgcnt > 1) multipage_by_comments = true; } if(!multipage_by_comments) { // very simple case ArrayData *save = database.InstallArrayData(0); ScriptVariable fname = database.GetListItemFilename(listdata.id, itemdata.item_id, 0); ScriptVariable cmts(""); if(fg) cmts = fg->Build(); output_file(fname, 0, diag_id, err, mainpg.c_str(), cmts.c_str(), tail.c_str()); database.InstallArrayData(save); if(fg) delete fg; return; } ScriptVariable href_main = database.GetListItemHref(listdata.id, itemdata.item_id, 0); //////////////////////////// ArrayData arrayd; arrayd.page_count = pgcnt; arrayd.reverse = reverse; arrayd.href_array[0] = href_main; int i; for(i = 1; i <= pgcnt; i++) { int idxval = fg->GetPageIdxValue(i); arrayd.href_array[i] = database.GetListItemHref(listdata.id, itemdata.item_id, idxval); } if(reverse) { arrayd.href_last = href_main; } else { arrayd.href_first = href_main; arrayd.href_array[1] = href_main; } arrayd.current_page = -1; ArrayData *ad_save = database.InstallArrayData(&arrayd); //////////////////////////// fg->SetMainHref(href_main); bool ok; do { int idxf, idx_array; fg->GetIndices(idxf, idx_array); if(idx_array < 0 || idx_array > pgcnt) idx_array = 0; arrayd.current_page = idx_array; fg->SetHref(arrayd.href_array[idx_array]); ScriptVariable compage = fg->Build(); ScriptVariable fname = database.GetListItemFilename(listdata.id, itemdata.item_id, idxf); output_file(fname, 0, diag_id, err, mainpg.c_str(), compage.c_str(), tail.c_str()); ok = fg->Next(); } while(ok); ScriptVariable cmap = fg->MakeCommentMap(); delete fg; database.InstallArrayData(ad_save); if(cmap.IsValid() && cmap != "") { ScriptVariable cmap_fname = database.GetListItemCommentmapFname(listdata.id, itemdata.item_id); if(cmap_fname.IsValid()) output_file(cmap_fname, 0, diag_id, err, cmap.c_str()); } } static void generate_all_list_item_pages(const ListData &list_data, Database& database, ErrorList **err) { int max = list_data.items.Length(); int i; for(i = 0; i < max; i++) { ListItemData itemdata; if(!database.GetListItemData(list_data, i, itemdata)) { ErrorList::AddError(err, ScriptVariable("No data for ") + list_data.id + "/" + list_data.items[i]); continue; } database.SetMacroData(&list_data, &itemdata); generate_list_item_page(list_data, itemdata, database, err); database.ForgetMacroData(); } } void generate_single_list_item_page(const ScriptVariable &list_id, const ScriptVariable &item_id, Database &database, ErrorList **err) { ListData list_data; if(!database.GetListData(list_id, list_data)) { ScriptVariable s(63, "Config. error for list %s\n", list_id.c_str()); ErrorList::AddError(err, s); return; } if(!list_data.pages) { ScriptVariable s(63, "List %s has no item pages\n", list_id.c_str()); ErrorList::AddError(err, s); return; } int idx = -1; int max = list_data.items.Length(); int i; for(i = 0; i < max; i++) { if(list_data.items[i] == item_id) { idx = i; break; } } if(idx == -1) { ScriptVariable s(63, "Item %s not found in list %s\n", item_id.c_str(), list_id.c_str()); ErrorList::AddError(err, s); return; } ListItemData itemdata; bool ok = database.GetListItemData(list_data, idx, itemdata); if(!ok) { ErrorList::AddError(err, ScriptVariable("No data for ") + list_data.id + "/" + list_data.items[i]); return; } database.SetMacroData(&list_data, &itemdata); generate_list_item_page(list_data, itemdata, database, err); database.ForgetMacroData(); } static void generate_empty_list(const ListData &list_data, const ScriptVariable &filename, const ScriptVariable &diag_id, Database& database, ErrorList **err) { FILE *listf = start_file(filename, 0, diag_id, err); if(!listf) return; database.SetMacroData(&list_data, 0); output_list_head(listf, list_data, database, err); output_list_tail(listf, list_data, database, err); database.ForgetMacroData(); fclose(listf); } static void generate_list_segment(const ListData &list_data, const ScriptVariable &filename, int pgnum, int startidx, int endidx, const ScriptVariable &diag_id, Database& database, ErrorList **err) { FILE *listf = start_file(filename, 0, diag_id, err); if(!listf) return; database.SetMacroData(&list_data, 0); output_list_head(listf, list_data, database, err); int order = startidx < endidx ? 1 : -1; int i; for(i = startidx; (order==1) ? (i<=endidx) : (i>=endidx); i += order) { ListItemData itemdata; bool ok; if(list_data.srctype == listsrc_ini) { ok = database.GetListItemData(list_data, i, itemdata); } else { ok = database.GetSetItemDataById(list_data.srcname, list_data.items[i], &itemdata); } if(!ok) { ErrorList::AddError(err, ScriptVariable("No data for ") + list_data.id + "/" + list_data.items[i] + " [ " + itemdata.title + " ]"); continue; } if(itemdata.HasFlag("hidden")) continue; database.SetMacroData(&list_data, &itemdata); output_list_item(listf, list_data, itemdata, database, err); database.SetMacroData(&list_data, 0); } output_list_tail(listf, list_data, database, err); database.ForgetMacroData(); fclose(listf); } static int start_num_for_main_list_page(int itemcnt, int perpage) { if(perpage == 0) return 0; if(itemcnt < 2 * perpage) return 0; // return itemcnt - (itemcnt % perpage + perpage); return itemcnt - perpage; } template void swapvars(T &a, T&b) { T c = a; a = b; b = c; } #if 0 static ScriptVariable make_uri(ScriptVariable &base, const ScriptVariable &u) { bool need_slash = base.Range(-1, 1)[0] != '/' && u[0] != '/'; const char *sep = need_slash ? "/" : ""; return base + sep + u; } #endif void generate_list(const ScriptVariable& id, Database& database, ErrorList **err) { int i; ListData list_data; if(!database.GetListData(id, list_data)) { ScriptVariable s(63, "Configuration error for list %s\n", id.c_str()); ErrorList::AddError(err, s); return; } if(list_data.pages) generate_all_list_item_pages(list_data, database, err); if(list_data.embedded) return; ScriptVariable diag_id = "list "; diag_id += id; bool rev = list_data.reverse; int perpage = list_data.items_per_listpage; int itemcnt = list_data.items.Length(); int pagecnt = perpage <= 0 ? 1 : (itemcnt ? (itemcnt-1)/perpage+1 : 0); //////////////////////////////// ArrayData array_data; array_data.page_count = pagecnt; array_data.reverse = rev; for(i = 0; i <= pagecnt; i++) array_data.href_array[i] = database.GetListHref(id, i); if(rev) array_data.href_last = array_data.href_array[0]; else array_data.href_first = array_data.href_array[0]; array_data.current_page = rev ? -1 : 1; //////////////////////////////// ArrayData *ad_save = database.InstallArrayData(&array_data); ScriptVariable filename; filename = database.GetListFilename(id, -1); if(itemcnt < 1) { // this case is special because parameters of // generate_list_segment don't allow to specify // zero-length segment generate_empty_list(list_data, filename, diag_id, database, err); } else { int start_num = rev ? itemcnt - 1 : 0; int end_num = rev ? start_num_for_main_list_page(itemcnt, perpage) : (perpage > 0 ? perpage-1 : itemcnt-1); generate_list_segment(list_data, filename, 0, start_num, end_num, diag_id, database, err); if(pagecnt > 1 || (pagecnt == 1 && perpage > 0 && rev)) { // reverse multipage with only one page is special case // to make the ``permanent links'' work // otherwise, we're already done for(i = rev ? 1 : 2; i <= pagecnt; i++) { array_data.current_page = i; start_num = (i - 1) * perpage; end_num = i * perpage - 1; if(end_num >= itemcnt) end_num = itemcnt - 1; if(rev) swapvars(start_num, end_num); filename = database.GetListFilename(id, i); generate_list_segment(list_data, filename, i, start_num, end_num, diag_id, database, err); } } } database.InstallArrayData(ad_save); } void generate_all_lists(Database& database, ErrorList **errlst) { ScriptVector listnames; database.GetLists(listnames); int i; for(i = 0; i < listnames.Length(); i++) generate_list(listnames[i], database, errlst); } ////////////////////////////////////////////////////////////////////////// // Sets // defined by 'pageset' section // represented with a directory containing files and/or subdirs, one per page static void build_set_page(const PageSetData &data, int idx, Database& database, ErrorList **err) { ScriptVariable whatfor = data.id + "::" + data.page_ids[idx]; ListItemData lid; if(!database.GetSetItemData(data, idx, &lid)) { ScriptVariable s(63, "Can't get set page data %s, skipping", whatfor.c_str()); ErrorList::AddError(err, s); return; } if(lid.HasFlag("hidden")) return; database.SetMacroData(0, &lid); bool separ_dir; switch(data.make_subdirs) { case pageset_subdir_always: separ_dir = true; break; case pageset_subdir_never: separ_dir = false; break; case pageset_subdir_bysource: default: separ_dir = lid.make_separate_directory; } ScriptVariable destdir, destidx, main_href, cmapfname; database.GetSetItemFilenames(data, separ_dir, destdir, destidx, main_href, cmapfname); if(!provide_dest_dir(destdir, whatfor, err)) return; ScriptVariable mainpg, tail; ForumGenerator *fg; database.BuildSetItemPage(data, lid, mainpg, tail, &fg); int pgcnt; bool reverse; bool multipage_by_comments = false; if(fg) { bool ok = fg->GetArrayInfo(pgcnt, reverse); if(ok && pgcnt > 1) multipage_by_comments = true; } if(!multipage_by_comments) { // very simple case ArrayData *save = database.InstallArrayData(0); ScriptVariable cmts(""); if(fg) cmts = fg->Build(); output_file(destidx, 0, whatfor, err, mainpg.c_str(), cmts.c_str(), tail.c_str()); database.InstallArrayData(save); } else { //////////////////////////// ArrayData arrayd; arrayd.page_count = pgcnt; arrayd.reverse = reverse; arrayd.href_array[0] = main_href; ScriptVector sub_filename; sub_filename[0] = destidx; int i; for(i = 1; i <= pgcnt; i++) { int idxval = fg->GetPageIdxValue(i); ScriptVariable sfn, hrf; database.GetSetSubitemFilename(data, separ_dir, idxval, sfn, hrf); sub_filename[i] = sfn; arrayd.href_array[i] = hrf; } if(reverse) arrayd.href_last = main_href; else arrayd.href_first = main_href; arrayd.current_page = -1; ArrayData *ad_save = database.InstallArrayData(&arrayd); //////////////////////////// fg->SetMainHref(main_href); bool ok; do { int idxf, idx_array; fg->GetIndices(idxf, idx_array); if(idx_array < 0 || idx_array > pgcnt) idx_array = 0; arrayd.current_page = idx_array; fg->SetHref(arrayd.href_array[idx_array]); ScriptVariable compage = fg->Build(); output_file(sub_filename[idx_array], 0, whatfor, err, mainpg.c_str(), compage.c_str(), tail.c_str()); ok = fg->Next(); } while(ok); database.InstallArrayData(ad_save); ScriptVariable cmap = fg->MakeCommentMap(); if(cmap.IsValid() && cmap != "" && cmapfname.IsValid()) output_file(cmapfname, 0, whatfor, err, cmap.c_str()); } if(fg) delete fg; publish_files(data.source_dir + "/" + data.page_ids[idx], destdir, lid.files, data.publish_method, whatfor, err); database.ForgetMacroData(); } void generate_single_set_page(const ScriptVariable& set_id, const ScriptVariable& page_id, Database& database, ErrorList **err) { PageSetData data; if(!database.GetSetData(set_id, data)) { ErrorList::AddError(err, ScriptVariable("No data for ") + set_id); return; } data.page_ids[0] = page_id; build_set_page(data, 0, database, err); } void generate_set(const ScriptVariable& id, Database& database, ErrorList **err) { PageSetData data; if(!database.GetSetData(id, data)) { ErrorList::AddError(err, ScriptVariable("No data for ") + id); return; } database.ScanSetDirectory(data); int pgcount = data.page_ids.Length(); int i; for(i = 0; i < pgcount; i++) build_set_page(data, i, database, err); } void generate_all_sets(Database& database, ErrorList **errlst) { ScriptVector setnames; database.GetSets(setnames); int i; for(i = 0; i < setnames.Length(); i++) { generate_set(setnames[i], database, errlst); } } ////////////////////////////////////////////////////////////////////////// // Collections // Defined by [collection id] sections // Generally, it is just a directory with files void publish_collection(const ScriptVariable& id, Database& database, ErrorList **err) { ScriptVariable srcdir, dstdir; int method; database.GetCollectionData(id, srcdir, dstdir, method); publish_dir(srcdir, dstdir, method, id, err); } void publish_all_collections(Database& database, ErrorList **errlst) { ScriptVector collnames; database.GetCollections(collnames); int i; for(i = 0; i < collnames.Length(); i++) { publish_collection(collnames[i], database, errlst); } } ////////////////////////////////////////////////////////////////////////// // Binaries // Defined by [binary name] sections // It is a single file (not necessarily binary) of arbitrary nature void publish_binary(const ScriptVariable& id, Database& database, ErrorList **err) { ScriptVariable src, dst; int method; database.GetBinaryData(id, src, dst, method); ScriptVariable whatfor("binary "); whatfor += id; if(src.IsInvalid() || src == "") src = id; else if(src.Range(-1, 1)[0] == '/') src += id; else { FileStat fs(src.c_str()); if(!fs.Exists()) { ScriptVariable s(63, "Source %s doesn't exist [%s]", src.c_str(), whatfor.c_str()); ErrorList::AddError(err, s); return; } if(fs.IsDir()) { src += '/'; src += id; } } publish_given_file(src, dst, method, whatfor, err); } void publish_all_binaries(Database& database, ErrorList **errlst) { ScriptVector binnames; database.GetBinaries(binnames); int i; for(i = 0; i < binnames.Length(); i++) { publish_binary(binnames[i], database, errlst); } } ////////////////////////////////////////////////////////////////////////// // Alias sections // Defined by [aliases id] sections // In the present version, these are implemented as symlinks, except for // the directories explicitly listed, which are implemented using a // file (either .htaccess, or index.html that causes redirect) // static void generate_single_alias(const ScriptVariable &alias, const ScriptVariable &original, const ScriptVariable &whatfor, Database& database, ErrorList **err) { int r = make_symlink(original, alias); if(r == -1) { ScriptVariable s(127, "couldn't alias %s to %s [%s]", alias.c_str(), original.c_str(), whatfor.c_str()); ErrorList::AddError(err, s); } } static void form_aliased_directory(const ScriptVariable &dirpath, const ScriptVariable &target_uri, const ScriptVariable §_id, Database& database, ErrorList **err) { make_directory_path(dirpath.c_str(), 0); ScriptVariable fname = database.GetAliasDirFilename(sect_id); ScriptVariable body = database.BuildAliasDirFile(sect_id, target_uri); output_file(dirpath + "/" + fname, 0, sect_id, err, body.c_str()); } static bool svec_has_elem(const ScriptVector &v, const ScriptVariable &el) { int i; for(i = 0; i < v.Length(); i++) { if(v[i] == el) return true; } return false; } void generate_aliases(const ScriptVariable& id, Database& database, ErrorList **err) { ScriptVariable fbase = database.GetFilePrefix(); ScriptVector aliasvec, forced_dirs; database.GetAliases(id, aliasvec); database.GetAliasDirs(id, forced_dirs); int i; int n = aliasvec.Length(); for(i = 0; i < n - 1; i+=2) { ScriptVariable src = aliasvec[i]; ScriptVariable dst = aliasvec[i+1]; if(svec_has_elem(forced_dirs, src)) { form_aliased_directory(fbase + src, dst, id, database, err); } else { generate_single_alias(fbase + src, fbase + dst, id, database, err); } } } void generate_all_alias_sections(Database& database, ErrorList **errlst) { ScriptVector names; database.GetAliasSections(names); int i; for(i = 0; i < names.Length(); i++) { generate_aliases(names[i], database, errlst); } } ////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////// // database isn't const, 'cause generate_list affects the object ErrorList* generate_everything(Database& database) { ErrorList *errls = 0; make_directory_path(database.GetFilePrefix().c_str(), 0); generate_all_genfiles(database, &errls); generate_all_pages(database, &errls); publish_all_collections(database, &errls); publish_all_binaries(database, &errls); generate_all_lists(database, &errls); generate_all_sets(database, &errls); /* _sets should be after _lists to have access to some list info */ generate_all_alias_sections(database, &errls); return errls; }