From 57b436a22d9ae0e395fb2e3ce101c2b5c2e6939e Mon Sep 17 00:00:00 2001 From: John Darrington Date: Mon, 17 Jul 2006 10:45:42 +0000 Subject: [PATCH] Patch #5209 Made casefile.c into an abstract base class, and moved the implementation to fastfile.c. Added a second implementation: flexifile.c which supports random access. Currently, it's a very naive implementation. Updated the gui to use flexifile. --- po/de.po | 267 +++++---- po/pspp.pot | 260 ++++---- src/automake.mk | 5 +- src/data/ChangeLog | 6 + src/data/automake.mk | 6 +- src/data/casefile-private.h | 98 +++ src/data/casefile.c | 925 ++++++----------------------- src/data/casefile.h | 59 +- src/data/fastfile.c | 751 +++++++++++++++++++++++ src/data/fastfile.h | 27 + src/data/procedure.c | 5 +- src/data/scratch-writer.c | 4 +- src/data/storage-stream.c | 3 +- src/language/data-io/get.c | 3 +- src/language/tests/casefile-test.c | 22 +- src/math/sort.c | 7 +- src/ui/ChangeLog | 4 + src/ui/automake.mk | 13 + src/ui/flexifile.c | 379 ++++++++++++ src/ui/flexifile.h | 47 ++ src/ui/gui/ChangeLog | 6 + src/ui/gui/automake.mk | 1 + src/ui/gui/menu-actions.c | 140 +++-- src/ui/gui/psppire-case-file.c | 160 ++++- src/ui/gui/psppire-case-file.h | 18 +- src/ui/gui/psppire-data-store.c | 74 +-- src/ui/gui/psppire-dict.c | 40 ++ src/ui/gui/psppire-dict.h | 4 + src/ui/gui/psppire-variable.c | 30 + src/ui/gui/psppire.c | 5 +- src/ui/gui/psppire.glade | 58 +- src/ui/terminal/automake.mk | 1 + 32 files changed, 2247 insertions(+), 1181 deletions(-) create mode 100644 src/data/casefile-private.h create mode 100644 src/data/fastfile.c create mode 100644 src/data/fastfile.h create mode 100644 src/ui/automake.mk create mode 100644 src/ui/flexifile.c create mode 100644 src/ui/flexifile.h diff --git a/po/de.po b/po/de.po index c105fe71..47c0180e 100644 --- a/po/de.po +++ b/po/de.po @@ -10,7 +10,7 @@ msgid "" msgstr "" "Project-Id-Version: PSPP 0.4.2\n" "Report-Msgid-Bugs-To: pspp-dev@gnu.org\n" -"POT-Creation-Date: 2006-07-15 12:13+0800\n" +"POT-Creation-Date: 2006-07-15 18:15+0800\n" "PO-Revision-Date: 2006-05-26 17:49+0800\n" "Last-Translator: John Darrington \n" "Language-Team: German \n" @@ -47,36 +47,6 @@ msgstr "Tag %d muß zwischen 0 bit 31 sein." msgid "Date %04d-%d-%d is before the earliest acceptable date of 1582-10-15." msgstr "" -#: src/data/casefile.c:269 -#, c-format -msgid "%s: Removing temporary file: %s." -msgstr "" - -#: src/data/casefile.c:437 -#, c-format -msgid "Error writing temporary file: %s." -msgstr "" - -#: src/data/casefile.c:600 -#, c-format -msgid "%s: Opening temporary file: %s." -msgstr "" - -#: src/data/casefile.c:643 -#, c-format -msgid "%s: Seeking temporary file: %s." -msgstr "" - -#: src/data/casefile.c:662 -#, c-format -msgid "%s: Reading temporary file: %s." -msgstr "" - -#: src/data/casefile.c:665 -#, c-format -msgid "%s: Temporary file ended unexpectedly." -msgstr "" - #: src/data/data-in.c:59 #, c-format msgid "(column %d" @@ -298,6 +268,36 @@ msgid "" "system-missing, zero, or negative. These case(s) were ignored." msgstr "" +#: src/data/fastfile.c:499 +#, c-format +msgid "%s: Removing temporary file: %s." +msgstr "" + +#: src/data/fastfile.c:623 +#, c-format +msgid "Error writing temporary file: %s." +msgstr "" + +#: src/data/fastfile.c:651 +#, c-format +msgid "%s: Opening temporary file: %s." +msgstr "" + +#: src/data/fastfile.c:695 +#, c-format +msgid "%s: Seeking temporary file: %s." +msgstr "" + +#: src/data/fastfile.c:717 +#, c-format +msgid "%s: Reading temporary file: %s." +msgstr "" + +#: src/data/fastfile.c:720 +#, c-format +msgid "%s: Temporary file ended unexpectedly." +msgstr "" + #: src/data/file-handle-def.c:304 #, c-format msgid "Can't open %s as a %s because it is already open as a %s." @@ -390,13 +390,13 @@ msgstr "" #: src/data/format.c:198 src/data/por-file-reader.c:481 #: src/data/sys-file-reader.c:1220 src/data/sys-file-reader.c:1229 -#: src/ui/gui/psppire.glade:1162 src/ui/gui/psppire-var-store.c:451 +#: src/ui/gui/psppire.glade:1192 src/ui/gui/psppire-var-store.c:451 msgid "String" msgstr "Zeichenkette" #: src/data/format.c:198 src/data/por-file-reader.c:481 #: src/data/sys-file-reader.c:1220 src/data/sys-file-reader.c:1229 -#: src/ui/gui/psppire.glade:1023 src/ui/gui/psppire-var-store.c:444 +#: src/ui/gui/psppire.glade:1053 src/ui/gui/psppire-var-store.c:444 msgid "Numeric" msgstr "Nummer" @@ -1357,21 +1357,21 @@ msgstr "" msgid "Handle for %s not allowed here." msgstr "" -#: src/language/data-io/get.c:115 +#: src/language/data-io/get.c:116 msgid "expecting COMM or TAPE" msgstr "" -#: src/language/data-io/get.c:357 src/language/data-io/get.c:371 -#: src/language/data-io/get.c:396 +#: src/language/data-io/get.c:358 src/language/data-io/get.c:372 +#: src/language/data-io/get.c:397 #, c-format msgid "expecting %s or %s" msgstr "" -#: src/language/data-io/get.c:605 src/language/data-io/print.c:186 +#: src/language/data-io/get.c:606 src/language/data-io/print.c:186 msgid "expecting a valid subcommand" msgstr "" -#: src/language/data-io/get.c:638 +#: src/language/data-io/get.c:639 #, c-format msgid "" "Cannot rename %s as %s because there already exists a variable named %s. To " @@ -1379,75 +1379,75 @@ msgid "" "as \"/RENAME (A=B)(B=C)(C=A)\", or equivalently, \"/RENAME (A B C=B C A)\"." msgstr "" -#: src/language/data-io/get.c:663 +#: src/language/data-io/get.c:664 msgid "`=' expected after variable list." msgstr "" -#: src/language/data-io/get.c:670 +#: src/language/data-io/get.c:671 #, c-format msgid "" "Number of variables on left side of `=' (%d) does not match number of " "variables on right side (%d), in parenthesized group %d of RENAME subcommand." msgstr "" -#: src/language/data-io/get.c:683 +#: src/language/data-io/get.c:684 #, c-format msgid "Requested renaming duplicates variable name %s." msgstr "" -#: src/language/data-io/get.c:713 +#: src/language/data-io/get.c:714 msgid "Cannot DROP all variables from dictionary." msgstr "" -#: src/language/data-io/get.c:890 +#: src/language/data-io/get.c:891 msgid "The active file may not be specified more than once." msgstr "" -#: src/language/data-io/get.c:898 +#: src/language/data-io/get.c:899 msgid "Cannot specify the active file since no active file has been defined." msgstr "" -#: src/language/data-io/get.c:905 +#: src/language/data-io/get.c:906 msgid "" "MATCH FILES may not be used after TEMPORARY when the active file is an input " "source. Temporary transformations will be made permanent." msgstr "" -#: src/language/data-io/get.c:941 +#: src/language/data-io/get.c:942 msgid "Multiple IN subcommands for a single FILE or TABLE." msgstr "" -#: src/language/data-io/get.c:961 +#: src/language/data-io/get.c:962 msgid "BY may appear at most once." msgstr "" -#: src/language/data-io/get.c:981 +#: src/language/data-io/get.c:982 #, c-format msgid "File %s lacks BY variable %s." msgstr "" -#: src/language/data-io/get.c:995 +#: src/language/data-io/get.c:996 msgid "FIRST may appear at most once." msgstr "" -#: src/language/data-io/get.c:1009 +#: src/language/data-io/get.c:1010 msgid "LAST may appear at most once." msgstr "" -#: src/language/data-io/get.c:1050 +#: src/language/data-io/get.c:1051 msgid "BY is required when TABLE is specified." msgstr "" -#: src/language/data-io/get.c:1055 +#: src/language/data-io/get.c:1056 msgid "BY is required when IN is specified." msgstr "" -#: src/language/data-io/get.c:1083 +#: src/language/data-io/get.c:1084 #, c-format msgid "IN variable name %s duplicates an existing variable name." msgstr "" -#: src/language/data-io/get.c:1537 +#: src/language/data-io/get.c:1538 #, c-format msgid "" "Variable %s in file %s (%s) has different type or width from the same " @@ -3441,7 +3441,7 @@ msgstr "" msgid "Coefficient Correlations" msgstr "" -#: src/language/stats/regression.q:1122 +#: src/language/stats/regression.q:1129 msgid "Dependent variable must be numeric." msgstr "" @@ -3855,7 +3855,7 @@ msgstr "" msgid "Empirical with averaging" msgstr "" -#: src/math/sort.c:439 +#: src/math/sort.c:440 #, c-format msgid "" "Out of memory. Could not allocate room for minimum of %d cases of %d bytes " @@ -4272,27 +4272,27 @@ msgstr "Unbetitelt" msgid "PSPP Data Editor" msgstr "PSPP Dateiaufbereiter" -#: src/ui/gui/menu-actions.c:222 src/ui/gui/psppire.glade:430 +#: src/ui/gui/menu-actions.c:220 src/ui/gui/psppire.glade:459 msgid "Open" msgstr "Öffen" -#: src/ui/gui/menu-actions.c:230 +#: src/ui/gui/menu-actions.c:228 msgid "System Files (*.sav)" msgstr "Systemedatein (*.sav)" -#: src/ui/gui/menu-actions.c:236 +#: src/ui/gui/menu-actions.c:234 msgid "Portable Files (*.por) " msgstr "Tragbardatein (*.por)" -#: src/ui/gui/menu-actions.c:242 +#: src/ui/gui/menu-actions.c:240 msgid "All Files" msgstr "Alle Datei" -#: src/ui/gui/menu-actions.c:274 +#: src/ui/gui/menu-actions.c:272 msgid "Save Data As" msgstr "Speichern unter" -#: src/ui/gui/menu-actions.c:566 +#: src/ui/gui/menu-actions.c:568 msgid "Font Selection" msgstr "Schriftwahlung" @@ -4320,11 +4320,11 @@ msgstr "Falshe Spannweitebeschreibung" msgid "Sorry. The help system hasn't yet been implemented." msgstr "Es gibt noch nicht kein Helpsysteme. Schade!" -#: src/ui/gui/psppire-data-store.c:693 +#: src/ui/gui/psppire-data-store.c:695 msgid "var" msgstr "" -#: src/ui/gui/psppire-data-store.c:777 src/ui/gui/psppire-var-store.c:518 +#: src/ui/gui/psppire-data-store.c:779 src/ui/gui/psppire-var-store.c:518 #: src/ui/gui/psppire-var-store.c:528 src/ui/gui/psppire-var-store.c:538 #: src/ui/gui/psppire-var-store.c:735 #, c-format @@ -4339,147 +4339,157 @@ msgstr "_Datei" msgid "_Edit" msgstr "_Bearbeiten" +#: src/ui/gui/psppire.glade:140 +#, fuzzy +msgid "Paste _Variables" +msgstr "Variableansicht" + #: src/ui/gui/psppire.glade:148 -msgid "_Insert" -msgstr "_Stecken" +msgid "Cl_ear" +msgstr "" -#: src/ui/gui/psppire.glade:161 +#: src/ui/gui/psppire.glade:165 +#, fuzzy +msgid "_Find" +msgstr "_Datei" + +#: src/ui/gui/psppire.glade:189 msgid "_View" msgstr "_Ansicht" -#: src/ui/gui/psppire.glade:170 +#: src/ui/gui/psppire.glade:198 msgid "Status Bar" msgstr "Statusleiste" -#: src/ui/gui/psppire.glade:179 +#: src/ui/gui/psppire.glade:207 msgid "Toolbars" msgstr "Werkzeugregal" -#: src/ui/gui/psppire.glade:193 +#: src/ui/gui/psppire.glade:221 msgid "Fonts" msgstr "Schrift" -#: src/ui/gui/psppire.glade:202 +#: src/ui/gui/psppire.glade:230 msgid "Grid Lines" msgstr "Glitten" -#: src/ui/gui/psppire.glade:212 src/ui/gui/psppire.glade:718 -#: src/ui/gui/psppire.glade:1597 src/ui/gui/psppire.glade:1874 +#: src/ui/gui/psppire.glade:240 src/ui/gui/psppire.glade:748 +#: src/ui/gui/psppire.glade:1627 src/ui/gui/psppire.glade:1904 msgid "Value Labels" msgstr "Werten" -#: src/ui/gui/psppire.glade:228 +#: src/ui/gui/psppire.glade:256 msgid "Data" msgstr "Daten" -#: src/ui/gui/psppire.glade:237 src/ui/gui/psppire.glade:549 +#: src/ui/gui/psppire.glade:265 src/ui/gui/psppire.glade:578 msgid "Variables" msgstr "Variableansicht" -#: src/ui/gui/psppire.glade:250 +#: src/ui/gui/psppire.glade:278 #, fuzzy msgid "_Data" msgstr "Daten" -#: src/ui/gui/psppire.glade:260 src/ui/gui/psppire.glade:624 +#: src/ui/gui/psppire.glade:288 src/ui/gui/psppire.glade:654 #, fuzzy msgid "Insert Variable" msgstr "Variableansicht" -#: src/ui/gui/psppire.glade:269 +#: src/ui/gui/psppire.glade:297 #, fuzzy msgid "Insert Cases" msgstr "_Stecken" -#: src/ui/gui/psppire.glade:277 src/ui/gui/psppire.glade:533 -#: src/ui/gui/psppire.glade:2439 +#: src/ui/gui/psppire.glade:306 src/ui/gui/psppire.glade:562 +#: src/ui/gui/psppire.glade:2469 msgid "Go To Case" msgstr "" -#: src/ui/gui/psppire.glade:304 src/ui/gui/psppire.glade:2550 +#: src/ui/gui/psppire.glade:333 src/ui/gui/psppire.glade:2580 msgid "Sort Cases" msgstr "" -#: src/ui/gui/psppire.glade:314 +#: src/ui/gui/psppire.glade:343 msgid "Transpose" msgstr "" -#: src/ui/gui/psppire.glade:323 +#: src/ui/gui/psppire.glade:352 msgid "Restructure" msgstr "" -#: src/ui/gui/psppire.glade:332 +#: src/ui/gui/psppire.glade:361 #, fuzzy msgid "Merge Files" msgstr "Alle Datei" -#: src/ui/gui/psppire.glade:341 +#: src/ui/gui/psppire.glade:370 msgid "Aggregate" msgstr "" -#: src/ui/gui/psppire.glade:356 src/ui/gui/psppire.glade:654 +#: src/ui/gui/psppire.glade:385 src/ui/gui/psppire.glade:684 #, fuzzy msgid "Split File" msgstr "Alle Datei" -#: src/ui/gui/psppire.glade:365 src/ui/gui/psppire.glade:688 +#: src/ui/gui/psppire.glade:394 src/ui/gui/psppire.glade:718 msgid "Select Cases" msgstr "" -#: src/ui/gui/psppire.glade:374 src/ui/gui/psppire.glade:671 +#: src/ui/gui/psppire.glade:403 src/ui/gui/psppire.glade:701 msgid "Weight Cases" msgstr "" -#: src/ui/gui/psppire.glade:386 +#: src/ui/gui/psppire.glade:415 msgid "_Help" msgstr "_Hilfe" -#: src/ui/gui/psppire.glade:395 +#: src/ui/gui/psppire.glade:424 msgid "_About" msgstr "_Info" -#: src/ui/gui/psppire.glade:446 +#: src/ui/gui/psppire.glade:475 msgid "Save" msgstr "Speichen" -#: src/ui/gui/psppire.glade:462 +#: src/ui/gui/psppire.glade:491 msgid "Print" msgstr "Drucken" -#: src/ui/gui/psppire.glade:490 +#: src/ui/gui/psppire.glade:519 msgid "Undo" msgstr "" -#: src/ui/gui/psppire.glade:505 +#: src/ui/gui/psppire.glade:534 msgid "Redo" msgstr "" -#: src/ui/gui/psppire.glade:579 +#: src/ui/gui/psppire.glade:608 msgid "Find" msgstr "" -#: src/ui/gui/psppire.glade:607 +#: src/ui/gui/psppire.glade:636 #, fuzzy msgid "Insert Case" msgstr "_Stecken" -#: src/ui/gui/psppire.glade:738 +#: src/ui/gui/psppire.glade:768 msgid "Use Sets" msgstr "" -#: src/ui/gui/psppire.glade:870 +#: src/ui/gui/psppire.glade:900 msgid "Data View" msgstr "Datenansicht" -#: src/ui/gui/psppire.glade:918 +#: src/ui/gui/psppire.glade:948 msgid "Variable View" msgstr "Variableansicht" -#: src/ui/gui/psppire.glade:964 +#: src/ui/gui/psppire.glade:994 msgid "This is pre-alpha software. It probably will not work." msgstr "Diese Software ist vor-Alpha. Wahrscheinlich Funktioniert es nicht." -#: src/ui/gui/psppire.glade:965 +#: src/ui/gui/psppire.glade:995 msgid "" " This program is free software; you can redistribute it and/or modify\n" " it under the terms of the GNU General Public License as published by\n" @@ -4497,107 +4507,107 @@ msgid "" " 02110-1301, USA.\n" msgstr "" -#: src/ui/gui/psppire.glade:990 +#: src/ui/gui/psppire.glade:1020 msgid "Variable Type" msgstr "Variableansicht" -#: src/ui/gui/psppire.glade:1042 src/ui/gui/psppire-var-store.c:445 +#: src/ui/gui/psppire.glade:1072 src/ui/gui/psppire-var-store.c:445 msgid "Comma" msgstr "Komma" -#: src/ui/gui/psppire.glade:1062 src/ui/gui/psppire-var-store.c:446 +#: src/ui/gui/psppire.glade:1092 src/ui/gui/psppire-var-store.c:446 msgid "Dot" msgstr "Punkt" -#: src/ui/gui/psppire.glade:1082 +#: src/ui/gui/psppire.glade:1112 msgid "Scientific notation" msgstr "Wissenschaftlichnotation" -#: src/ui/gui/psppire.glade:1102 src/ui/gui/psppire-var-store.c:448 +#: src/ui/gui/psppire.glade:1132 src/ui/gui/psppire-var-store.c:448 msgid "Date" msgstr "Datum" -#: src/ui/gui/psppire.glade:1122 src/ui/gui/psppire-var-store.c:449 +#: src/ui/gui/psppire.glade:1152 src/ui/gui/psppire-var-store.c:449 msgid "Dollar" msgstr "Euro" -#: src/ui/gui/psppire.glade:1142 +#: src/ui/gui/psppire.glade:1172 msgid "Custom currency" msgstr "Spezialwährung" -#: src/ui/gui/psppire.glade:1287 +#: src/ui/gui/psppire.glade:1317 msgid "positive" msgstr "positiv" -#: src/ui/gui/psppire.glade:1312 +#: src/ui/gui/psppire.glade:1342 msgid "negative" msgstr "negativ" -#: src/ui/gui/psppire.glade:1341 +#: src/ui/gui/psppire.glade:1371 msgid "Sample" msgstr "Muster" -#: src/ui/gui/psppire.glade:1419 +#: src/ui/gui/psppire.glade:1449 msgid "Decimal Places:" msgstr "Dezimalstellen:" -#: src/ui/gui/psppire.glade:1496 +#: src/ui/gui/psppire.glade:1526 msgid "Width:" msgstr "Große:" -#: src/ui/gui/psppire.glade:1715 +#: src/ui/gui/psppire.glade:1745 msgid "Value:" msgstr "Werte:" -#: src/ui/gui/psppire.glade:1743 +#: src/ui/gui/psppire.glade:1773 msgid "Value Label:" msgstr "Kennsatz:" -#: src/ui/gui/psppire.glade:1959 +#: src/ui/gui/psppire.glade:1989 msgid "Missing Values" msgstr "Lösewerten" -#: src/ui/gui/psppire.glade:2045 +#: src/ui/gui/psppire.glade:2075 msgid "_No missing values" msgstr "_Kein Lösewerten" -#: src/ui/gui/psppire.glade:2070 +#: src/ui/gui/psppire.glade:2100 msgid "_Discrete missing values" msgstr "_Diskret Lösewerten" -#: src/ui/gui/psppire.glade:2199 +#: src/ui/gui/psppire.glade:2229 msgid "_Range plus one optional discrete missing value" msgstr "Wertebereich und ein optional Lösewert" -#: src/ui/gui/psppire.glade:2236 +#: src/ui/gui/psppire.glade:2266 msgid "_Low:" msgstr "_Tief:" -#: src/ui/gui/psppire.glade:2295 +#: src/ui/gui/psppire.glade:2325 msgid "_High:" msgstr "_Hoch:" -#: src/ui/gui/psppire.glade:2369 +#: src/ui/gui/psppire.glade:2399 msgid "Di_screte value:" msgstr "Di_skretwerte" -#: src/ui/gui/psppire.glade:2495 +#: src/ui/gui/psppire.glade:2525 msgid "Case Number:" msgstr "" -#: src/ui/gui/psppire.glade:2667 src/ui/gui/sort-cases-dialog.c:282 +#: src/ui/gui/psppire.glade:2697 src/ui/gui/sort-cases-dialog.c:282 msgid "Ascending" msgstr "" -#: src/ui/gui/psppire.glade:2686 src/ui/gui/sort-cases-dialog.c:284 +#: src/ui/gui/psppire.glade:2716 src/ui/gui/sort-cases-dialog.c:284 msgid "Descending" msgstr "" -#: src/ui/gui/psppire.glade:2707 +#: src/ui/gui/psppire.glade:2737 msgid "Sort Order" msgstr "" -#: src/ui/gui/psppire.glade:2744 +#: src/ui/gui/psppire.glade:2774 msgid "Sort by:" msgstr "" @@ -4748,3 +4758,6 @@ msgstr "Fehler" #: src/ui/terminal/msg-ui.c:117 msgid "warning" msgstr "Warnung" + +#~ msgid "_Insert" +#~ msgstr "_Stecken" diff --git a/po/pspp.pot b/po/pspp.pot index 803e416e..add94089 100644 --- a/po/pspp.pot +++ b/po/pspp.pot @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: pspp-dev@gnu.org\n" -"POT-Creation-Date: 2006-07-15 12:13+0800\n" +"POT-Creation-Date: 2006-07-15 18:15+0800\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -46,36 +46,6 @@ msgstr "" msgid "Date %04d-%d-%d is before the earliest acceptable date of 1582-10-15." msgstr "" -#: src/data/casefile.c:269 -#, c-format -msgid "%s: Removing temporary file: %s." -msgstr "" - -#: src/data/casefile.c:437 -#, c-format -msgid "Error writing temporary file: %s." -msgstr "" - -#: src/data/casefile.c:600 -#, c-format -msgid "%s: Opening temporary file: %s." -msgstr "" - -#: src/data/casefile.c:643 -#, c-format -msgid "%s: Seeking temporary file: %s." -msgstr "" - -#: src/data/casefile.c:662 -#, c-format -msgid "%s: Reading temporary file: %s." -msgstr "" - -#: src/data/casefile.c:665 -#, c-format -msgid "%s: Temporary file ended unexpectedly." -msgstr "" - #: src/data/data-in.c:59 #, c-format msgid "(column %d" @@ -297,6 +267,36 @@ msgid "" "system-missing, zero, or negative. These case(s) were ignored." msgstr "" +#: src/data/fastfile.c:499 +#, c-format +msgid "%s: Removing temporary file: %s." +msgstr "" + +#: src/data/fastfile.c:623 +#, c-format +msgid "Error writing temporary file: %s." +msgstr "" + +#: src/data/fastfile.c:651 +#, c-format +msgid "%s: Opening temporary file: %s." +msgstr "" + +#: src/data/fastfile.c:695 +#, c-format +msgid "%s: Seeking temporary file: %s." +msgstr "" + +#: src/data/fastfile.c:717 +#, c-format +msgid "%s: Reading temporary file: %s." +msgstr "" + +#: src/data/fastfile.c:720 +#, c-format +msgid "%s: Temporary file ended unexpectedly." +msgstr "" + #: src/data/file-handle-def.c:304 #, c-format msgid "Can't open %s as a %s because it is already open as a %s." @@ -389,13 +389,13 @@ msgstr "" #: src/data/format.c:198 src/data/por-file-reader.c:481 #: src/data/sys-file-reader.c:1220 src/data/sys-file-reader.c:1229 -#: src/ui/gui/psppire.glade:1162 src/ui/gui/psppire-var-store.c:451 +#: src/ui/gui/psppire.glade:1192 src/ui/gui/psppire-var-store.c:451 msgid "String" msgstr "" #: src/data/format.c:198 src/data/por-file-reader.c:481 #: src/data/sys-file-reader.c:1220 src/data/sys-file-reader.c:1229 -#: src/ui/gui/psppire.glade:1023 src/ui/gui/psppire-var-store.c:444 +#: src/ui/gui/psppire.glade:1053 src/ui/gui/psppire-var-store.c:444 msgid "Numeric" msgstr "" @@ -1356,21 +1356,21 @@ msgstr "" msgid "Handle for %s not allowed here." msgstr "" -#: src/language/data-io/get.c:115 +#: src/language/data-io/get.c:116 msgid "expecting COMM or TAPE" msgstr "" -#: src/language/data-io/get.c:357 src/language/data-io/get.c:371 -#: src/language/data-io/get.c:396 +#: src/language/data-io/get.c:358 src/language/data-io/get.c:372 +#: src/language/data-io/get.c:397 #, c-format msgid "expecting %s or %s" msgstr "" -#: src/language/data-io/get.c:605 src/language/data-io/print.c:186 +#: src/language/data-io/get.c:606 src/language/data-io/print.c:186 msgid "expecting a valid subcommand" msgstr "" -#: src/language/data-io/get.c:638 +#: src/language/data-io/get.c:639 #, c-format msgid "" "Cannot rename %s as %s because there already exists a variable named %s. To " @@ -1378,75 +1378,75 @@ msgid "" "as \"/RENAME (A=B)(B=C)(C=A)\", or equivalently, \"/RENAME (A B C=B C A)\"." msgstr "" -#: src/language/data-io/get.c:663 +#: src/language/data-io/get.c:664 msgid "`=' expected after variable list." msgstr "" -#: src/language/data-io/get.c:670 +#: src/language/data-io/get.c:671 #, c-format msgid "" "Number of variables on left side of `=' (%d) does not match number of " "variables on right side (%d), in parenthesized group %d of RENAME subcommand." msgstr "" -#: src/language/data-io/get.c:683 +#: src/language/data-io/get.c:684 #, c-format msgid "Requested renaming duplicates variable name %s." msgstr "" -#: src/language/data-io/get.c:713 +#: src/language/data-io/get.c:714 msgid "Cannot DROP all variables from dictionary." msgstr "" -#: src/language/data-io/get.c:890 +#: src/language/data-io/get.c:891 msgid "The active file may not be specified more than once." msgstr "" -#: src/language/data-io/get.c:898 +#: src/language/data-io/get.c:899 msgid "Cannot specify the active file since no active file has been defined." msgstr "" -#: src/language/data-io/get.c:905 +#: src/language/data-io/get.c:906 msgid "" "MATCH FILES may not be used after TEMPORARY when the active file is an input " "source. Temporary transformations will be made permanent." msgstr "" -#: src/language/data-io/get.c:941 +#: src/language/data-io/get.c:942 msgid "Multiple IN subcommands for a single FILE or TABLE." msgstr "" -#: src/language/data-io/get.c:961 +#: src/language/data-io/get.c:962 msgid "BY may appear at most once." msgstr "" -#: src/language/data-io/get.c:981 +#: src/language/data-io/get.c:982 #, c-format msgid "File %s lacks BY variable %s." msgstr "" -#: src/language/data-io/get.c:995 +#: src/language/data-io/get.c:996 msgid "FIRST may appear at most once." msgstr "" -#: src/language/data-io/get.c:1009 +#: src/language/data-io/get.c:1010 msgid "LAST may appear at most once." msgstr "" -#: src/language/data-io/get.c:1050 +#: src/language/data-io/get.c:1051 msgid "BY is required when TABLE is specified." msgstr "" -#: src/language/data-io/get.c:1055 +#: src/language/data-io/get.c:1056 msgid "BY is required when IN is specified." msgstr "" -#: src/language/data-io/get.c:1083 +#: src/language/data-io/get.c:1084 #, c-format msgid "IN variable name %s duplicates an existing variable name." msgstr "" -#: src/language/data-io/get.c:1537 +#: src/language/data-io/get.c:1538 #, c-format msgid "" "Variable %s in file %s (%s) has different type or width from the same " @@ -3440,7 +3440,7 @@ msgstr "" msgid "Coefficient Correlations" msgstr "" -#: src/language/stats/regression.q:1122 +#: src/language/stats/regression.q:1129 msgid "Dependent variable must be numeric." msgstr "" @@ -3854,7 +3854,7 @@ msgstr "" msgid "Empirical with averaging" msgstr "" -#: src/math/sort.c:439 +#: src/math/sort.c:440 #, c-format msgid "" "Out of memory. Could not allocate room for minimum of %d cases of %d bytes " @@ -4271,27 +4271,27 @@ msgstr "" msgid "PSPP Data Editor" msgstr "" -#: src/ui/gui/menu-actions.c:222 src/ui/gui/psppire.glade:430 +#: src/ui/gui/menu-actions.c:220 src/ui/gui/psppire.glade:459 msgid "Open" msgstr "" -#: src/ui/gui/menu-actions.c:230 +#: src/ui/gui/menu-actions.c:228 msgid "System Files (*.sav)" msgstr "" -#: src/ui/gui/menu-actions.c:236 +#: src/ui/gui/menu-actions.c:234 msgid "Portable Files (*.por) " msgstr "" -#: src/ui/gui/menu-actions.c:242 +#: src/ui/gui/menu-actions.c:240 msgid "All Files" msgstr "" -#: src/ui/gui/menu-actions.c:274 +#: src/ui/gui/menu-actions.c:272 msgid "Save Data As" msgstr "" -#: src/ui/gui/menu-actions.c:566 +#: src/ui/gui/menu-actions.c:568 msgid "Font Selection" msgstr "" @@ -4319,11 +4319,11 @@ msgstr "" msgid "Sorry. The help system hasn't yet been implemented." msgstr "" -#: src/ui/gui/psppire-data-store.c:693 +#: src/ui/gui/psppire-data-store.c:695 msgid "var" msgstr "" -#: src/ui/gui/psppire-data-store.c:777 src/ui/gui/psppire-var-store.c:518 +#: src/ui/gui/psppire-data-store.c:779 src/ui/gui/psppire-var-store.c:518 #: src/ui/gui/psppire-var-store.c:528 src/ui/gui/psppire-var-store.c:538 #: src/ui/gui/psppire-var-store.c:735 #, c-format @@ -4338,141 +4338,149 @@ msgstr "" msgid "_Edit" msgstr "" +#: src/ui/gui/psppire.glade:140 +msgid "Paste _Variables" +msgstr "" + #: src/ui/gui/psppire.glade:148 -msgid "_Insert" +msgid "Cl_ear" +msgstr "" + +#: src/ui/gui/psppire.glade:165 +msgid "_Find" msgstr "" -#: src/ui/gui/psppire.glade:161 +#: src/ui/gui/psppire.glade:189 msgid "_View" msgstr "" -#: src/ui/gui/psppire.glade:170 +#: src/ui/gui/psppire.glade:198 msgid "Status Bar" msgstr "" -#: src/ui/gui/psppire.glade:179 +#: src/ui/gui/psppire.glade:207 msgid "Toolbars" msgstr "" -#: src/ui/gui/psppire.glade:193 +#: src/ui/gui/psppire.glade:221 msgid "Fonts" msgstr "" -#: src/ui/gui/psppire.glade:202 +#: src/ui/gui/psppire.glade:230 msgid "Grid Lines" msgstr "" -#: src/ui/gui/psppire.glade:212 src/ui/gui/psppire.glade:718 -#: src/ui/gui/psppire.glade:1597 src/ui/gui/psppire.glade:1874 +#: src/ui/gui/psppire.glade:240 src/ui/gui/psppire.glade:748 +#: src/ui/gui/psppire.glade:1627 src/ui/gui/psppire.glade:1904 msgid "Value Labels" msgstr "" -#: src/ui/gui/psppire.glade:228 +#: src/ui/gui/psppire.glade:256 msgid "Data" msgstr "" -#: src/ui/gui/psppire.glade:237 src/ui/gui/psppire.glade:549 +#: src/ui/gui/psppire.glade:265 src/ui/gui/psppire.glade:578 msgid "Variables" msgstr "" -#: src/ui/gui/psppire.glade:250 +#: src/ui/gui/psppire.glade:278 msgid "_Data" msgstr "" -#: src/ui/gui/psppire.glade:260 src/ui/gui/psppire.glade:624 +#: src/ui/gui/psppire.glade:288 src/ui/gui/psppire.glade:654 msgid "Insert Variable" msgstr "" -#: src/ui/gui/psppire.glade:269 +#: src/ui/gui/psppire.glade:297 msgid "Insert Cases" msgstr "" -#: src/ui/gui/psppire.glade:277 src/ui/gui/psppire.glade:533 -#: src/ui/gui/psppire.glade:2439 +#: src/ui/gui/psppire.glade:306 src/ui/gui/psppire.glade:562 +#: src/ui/gui/psppire.glade:2469 msgid "Go To Case" msgstr "" -#: src/ui/gui/psppire.glade:304 src/ui/gui/psppire.glade:2550 +#: src/ui/gui/psppire.glade:333 src/ui/gui/psppire.glade:2580 msgid "Sort Cases" msgstr "" -#: src/ui/gui/psppire.glade:314 +#: src/ui/gui/psppire.glade:343 msgid "Transpose" msgstr "" -#: src/ui/gui/psppire.glade:323 +#: src/ui/gui/psppire.glade:352 msgid "Restructure" msgstr "" -#: src/ui/gui/psppire.glade:332 +#: src/ui/gui/psppire.glade:361 msgid "Merge Files" msgstr "" -#: src/ui/gui/psppire.glade:341 +#: src/ui/gui/psppire.glade:370 msgid "Aggregate" msgstr "" -#: src/ui/gui/psppire.glade:356 src/ui/gui/psppire.glade:654 +#: src/ui/gui/psppire.glade:385 src/ui/gui/psppire.glade:684 msgid "Split File" msgstr "" -#: src/ui/gui/psppire.glade:365 src/ui/gui/psppire.glade:688 +#: src/ui/gui/psppire.glade:394 src/ui/gui/psppire.glade:718 msgid "Select Cases" msgstr "" -#: src/ui/gui/psppire.glade:374 src/ui/gui/psppire.glade:671 +#: src/ui/gui/psppire.glade:403 src/ui/gui/psppire.glade:701 msgid "Weight Cases" msgstr "" -#: src/ui/gui/psppire.glade:386 +#: src/ui/gui/psppire.glade:415 msgid "_Help" msgstr "" -#: src/ui/gui/psppire.glade:395 +#: src/ui/gui/psppire.glade:424 msgid "_About" msgstr "" -#: src/ui/gui/psppire.glade:446 +#: src/ui/gui/psppire.glade:475 msgid "Save" msgstr "" -#: src/ui/gui/psppire.glade:462 +#: src/ui/gui/psppire.glade:491 msgid "Print" msgstr "" -#: src/ui/gui/psppire.glade:490 +#: src/ui/gui/psppire.glade:519 msgid "Undo" msgstr "" -#: src/ui/gui/psppire.glade:505 +#: src/ui/gui/psppire.glade:534 msgid "Redo" msgstr "" -#: src/ui/gui/psppire.glade:579 +#: src/ui/gui/psppire.glade:608 msgid "Find" msgstr "" -#: src/ui/gui/psppire.glade:607 +#: src/ui/gui/psppire.glade:636 msgid "Insert Case" msgstr "" -#: src/ui/gui/psppire.glade:738 +#: src/ui/gui/psppire.glade:768 msgid "Use Sets" msgstr "" -#: src/ui/gui/psppire.glade:870 +#: src/ui/gui/psppire.glade:900 msgid "Data View" msgstr "" -#: src/ui/gui/psppire.glade:918 +#: src/ui/gui/psppire.glade:948 msgid "Variable View" msgstr "" -#: src/ui/gui/psppire.glade:964 +#: src/ui/gui/psppire.glade:994 msgid "This is pre-alpha software. It probably will not work." msgstr "" -#: src/ui/gui/psppire.glade:965 +#: src/ui/gui/psppire.glade:995 msgid "" " This program is free software; you can redistribute it and/or modify\n" " it under the terms of the GNU General Public License as published by\n" @@ -4490,107 +4498,107 @@ msgid "" " 02110-1301, USA.\n" msgstr "" -#: src/ui/gui/psppire.glade:990 +#: src/ui/gui/psppire.glade:1020 msgid "Variable Type" msgstr "" -#: src/ui/gui/psppire.glade:1042 src/ui/gui/psppire-var-store.c:445 +#: src/ui/gui/psppire.glade:1072 src/ui/gui/psppire-var-store.c:445 msgid "Comma" msgstr "" -#: src/ui/gui/psppire.glade:1062 src/ui/gui/psppire-var-store.c:446 +#: src/ui/gui/psppire.glade:1092 src/ui/gui/psppire-var-store.c:446 msgid "Dot" msgstr "" -#: src/ui/gui/psppire.glade:1082 +#: src/ui/gui/psppire.glade:1112 msgid "Scientific notation" msgstr "" -#: src/ui/gui/psppire.glade:1102 src/ui/gui/psppire-var-store.c:448 +#: src/ui/gui/psppire.glade:1132 src/ui/gui/psppire-var-store.c:448 msgid "Date" msgstr "" -#: src/ui/gui/psppire.glade:1122 src/ui/gui/psppire-var-store.c:449 +#: src/ui/gui/psppire.glade:1152 src/ui/gui/psppire-var-store.c:449 msgid "Dollar" msgstr "" -#: src/ui/gui/psppire.glade:1142 +#: src/ui/gui/psppire.glade:1172 msgid "Custom currency" msgstr "" -#: src/ui/gui/psppire.glade:1287 +#: src/ui/gui/psppire.glade:1317 msgid "positive" msgstr "" -#: src/ui/gui/psppire.glade:1312 +#: src/ui/gui/psppire.glade:1342 msgid "negative" msgstr "" -#: src/ui/gui/psppire.glade:1341 +#: src/ui/gui/psppire.glade:1371 msgid "Sample" msgstr "" -#: src/ui/gui/psppire.glade:1419 +#: src/ui/gui/psppire.glade:1449 msgid "Decimal Places:" msgstr "" -#: src/ui/gui/psppire.glade:1496 +#: src/ui/gui/psppire.glade:1526 msgid "Width:" msgstr "" -#: src/ui/gui/psppire.glade:1715 +#: src/ui/gui/psppire.glade:1745 msgid "Value:" msgstr "" -#: src/ui/gui/psppire.glade:1743 +#: src/ui/gui/psppire.glade:1773 msgid "Value Label:" msgstr "" -#: src/ui/gui/psppire.glade:1959 +#: src/ui/gui/psppire.glade:1989 msgid "Missing Values" msgstr "" -#: src/ui/gui/psppire.glade:2045 +#: src/ui/gui/psppire.glade:2075 msgid "_No missing values" msgstr "" -#: src/ui/gui/psppire.glade:2070 +#: src/ui/gui/psppire.glade:2100 msgid "_Discrete missing values" msgstr "" -#: src/ui/gui/psppire.glade:2199 +#: src/ui/gui/psppire.glade:2229 msgid "_Range plus one optional discrete missing value" msgstr "" -#: src/ui/gui/psppire.glade:2236 +#: src/ui/gui/psppire.glade:2266 msgid "_Low:" msgstr "" -#: src/ui/gui/psppire.glade:2295 +#: src/ui/gui/psppire.glade:2325 msgid "_High:" msgstr "" -#: src/ui/gui/psppire.glade:2369 +#: src/ui/gui/psppire.glade:2399 msgid "Di_screte value:" msgstr "" -#: src/ui/gui/psppire.glade:2495 +#: src/ui/gui/psppire.glade:2525 msgid "Case Number:" msgstr "" -#: src/ui/gui/psppire.glade:2667 src/ui/gui/sort-cases-dialog.c:282 +#: src/ui/gui/psppire.glade:2697 src/ui/gui/sort-cases-dialog.c:282 msgid "Ascending" msgstr "" -#: src/ui/gui/psppire.glade:2686 src/ui/gui/sort-cases-dialog.c:284 +#: src/ui/gui/psppire.glade:2716 src/ui/gui/sort-cases-dialog.c:284 msgid "Descending" msgstr "" -#: src/ui/gui/psppire.glade:2707 +#: src/ui/gui/psppire.glade:2737 msgid "Sort Order" msgstr "" -#: src/ui/gui/psppire.glade:2744 +#: src/ui/gui/psppire.glade:2774 msgid "Sort by:" msgstr "" diff --git a/src/automake.mk b/src/automake.mk index b63a0691..7c52887b 100644 --- a/src/automake.mk +++ b/src/automake.mk @@ -2,15 +2,12 @@ # PSPP -include $(top_srcdir)/src/ui/terminal/automake.mk include $(top_srcdir)/src/math/automake.mk include $(top_srcdir)/src/libpspp/automake.mk include $(top_srcdir)/src/data/automake.mk include $(top_srcdir)/src/output/automake.mk include $(top_srcdir)/src/language/automake.mk -if WITHGUI -include $(top_srcdir)/src/ui/gui/automake.mk -endif +include $(top_srcdir)/src/ui/automake.mk AM_CPPFLAGS += -I$(top_srcdir)/src -I$(top_srcdir)/lib -DPKGDATADIR=\"$(pkgdatadir)\" diff --git a/src/data/ChangeLog b/src/data/ChangeLog index 0d10921c..694d3577 100644 --- a/src/data/ChangeLog +++ b/src/data/ChangeLog @@ -1,3 +1,9 @@ +Mon Jul 17 18:26:21 WST 2006 John Darrington + + * casefile.c casefile.h: Converted to an abstract base class. + * casefile-private.h fastfile.c fastfile.h: New files. + * automake.mk procedure.c scratch-writer.c storage-stream.c + Wed Jul 12 21:02:26 2006 Ben Pfaff * procedure.c (internal_procedure): Create sink_case with only as diff --git a/src/data/automake.mk b/src/data/automake.mk index d79828b7..9cbe39e6 100644 --- a/src/data/automake.mk +++ b/src/data/automake.mk @@ -1,4 +1,3 @@ -## Process this file with automake to produce Makefile.in -*- makefile -*- noinst_LIBRARIES += src/data/libdata.a @@ -14,8 +13,11 @@ src_data_libdata_a_SOURCES = \ src/data/case-source.c \ src/data/case-source.h \ src/data/case.c \ - src/data/casefile.c \ src/data/casefile.h \ + src/data/casefile.c \ + src/data/casefile-private.h \ + src/data/fastfile.c \ + src/data/fastfile.h \ src/data/case.h \ src/data/category.c \ src/data/category.h \ diff --git a/src/data/casefile-private.h b/src/data/casefile-private.h new file mode 100644 index 00000000..6e35300b --- /dev/null +++ b/src/data/casefile-private.h @@ -0,0 +1,98 @@ +/* PSPP - computes sample statistics. + Copyright (C) 2004, 2006 Free Software Foundation, Inc. + Written by Ben Pfaff . + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. + + This program 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 + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301, USA. */ + +#ifndef CASEFILE_PRIVATE_H +#define CASEFILE_PRIVATE_H + +#include +#include +#include + +struct ccase; +struct casereader; +struct casefile; + +struct class_casefile +{ + void (*destroy) (struct casefile *) ; + + bool (*error) (const struct casefile *) ; + + size_t (*get_value_cnt) (const struct casefile *) ; + unsigned long (*get_case_cnt) (const struct casefile *) ; + + struct casereader * (*get_reader) (const struct casefile *) ; + + bool (*append) (struct casefile *, const struct ccase *) ; + + + bool (*in_core) (const struct casefile *) ; + bool (*to_disk) (const struct casefile *) ; + bool (*sleep) (const struct casefile *) ; +}; + +struct casefile +{ + const struct class_casefile *class ; /* Class pointer */ + + struct ll_list reader_list ; /* List of our readers. */ + struct ll ll ; /* Element in the class' list + of casefiles. */ + bool being_destroyed; /* A destructive reader exists */ +}; + + +struct class_casereader +{ + struct ccase * (*get_next_case) (struct casereader *); + + unsigned long (*cnum) (const struct casereader *); + + void (*destroy) (struct casereader * r); +}; + + +#define CLASS_CASEREADER(K) ( (struct class_casereader *) K) + +struct casereader +{ + const struct class_casereader *class; /* Class pointer */ + + struct casefile *cf; /* The casefile to which this reader belongs */ + struct ll ll; /* Element in the casefile's list of readers */ + bool destructive; /* True if this reader is destructive */ +}; + + +#define CASEFILE(C) ( (struct casefile *) C) +#define CONST_CASEFILE(C) ( (const struct casefile *) C) + +#define CASEFILEREADER(CR) ((struct casereader *) CR) + + +/* Functions for implementations' use only */ + +void casefile_register (struct casefile *cf, + const struct class_casefile *k); + +void casereader_register (struct casefile *cf, + struct casereader *reader, + const struct class_casereader *k); + +#endif diff --git a/src/data/casefile.c b/src/data/casefile.c index 525611bf..95e0388c 100644 --- a/src/data/casefile.c +++ b/src/data/casefile.c @@ -1,5 +1,5 @@ /* PSPP - computes sample statistics. - Copyright (C) 2004 Free Software Foundation, Inc. + Copyright (C) 2004, 2006 Free Software Foundation, Inc. Written by Ben Pfaff . This program is free software; you can redistribute it and/or @@ -18,545 +18,209 @@ 02110-1301, USA. */ #include -#include "casefile.h" -#include -#include -#include -#include +#include +#include #include -#include -#include -#include +#include + #include "case.h" -#include -#include -#include "full-read.h" -#include "full-write.h" -#include -#include -#include "make-file.h" -#include "settings.h" -#include "variable.h" - -#include "gettext.h" -#define _(msgid) gettext (msgid) - -#define IO_BUF_SIZE (8192 / sizeof (union value)) - -/* A casefile represents a sequentially accessible stream of - immutable cases. - - If workspace allows, a casefile is maintained in memory. If - workspace overflows, then the casefile is pushed to disk. In - either case the interface presented to callers is kept the - same. - - The life cycle of a casefile consists of up to three phases: - - 1. Writing. The casefile initially contains no cases. In - this phase, any number of cases may be appended to the - end of a casefile. (Cases are never inserted in the - middle or before the beginning of a casefile.) - - Use casefile_append() or casefile_append_xfer() to - append a case to a casefile. - - 2. Reading. The casefile may be read sequentially, - starting from the beginning, by "casereaders". Any - number of casereaders may be created, at any time, - during the reading phase. Each casereader has an - independent position in the casefile. - - Ordinary casereaders may only move forward. They - cannot move backward to arbitrary records or seek - randomly. Cloning casereaders is possible, but it is - not yet implemented. - - Use casefile_get_reader() to create a casereader for - use in phase 2. This also transitions from phase 1 to - phase 2. Calling casefile_mode_reader() makes the same - transition, without creating a casereader. - - Use casereader_read() or casereader_read_xfer() to read - a case from a casereader. Use casereader_destroy() to - discard a casereader when it is no longer needed. - - "Random" casereaders, which support a seek operation, - may also be created. These should not, generally, be - used for statistical procedures, because random access - is much slower than sequential access. They are - intended for use by the GUI. - - 3. Destruction. This phase is optional. The casefile is - also read with casereaders in this phase, but the - ability to create new casereaders is curtailed. - - In this phase, casereaders could still be cloned (once - we eventually implement cloning). - - To transition from phase 1 or 2 to phase 3 and create a - casereader, call casefile_get_destructive_reader(). - The same functions apply to the casereader obtained - this way as apply to casereaders obtained in phase 2. - - After casefile_get_destructive_reader() is called, no - more casereaders may be created with - casefile_get_reader() or - casefile_get_destructive_reader(). (If cloning of - casereaders were implemented, it would still be - possible.) - - The purpose of the limitations applied to casereaders - in phase 3 is to allow in-memory casefiles to fully - transfer ownership of cases to the casereaders, - avoiding the need for extra copies of case data. For - relatively static data sets with many variables, I - suspect (without evidence) that this may be a big - performance boost. +#include "casefile.h" +#include "casefile-private.h" + + +struct ccase; + +/* A casefile is an abstract class representing an array of cases. In + general, cases are accessible sequentially, and are immutable once + appended to the casefile. However some implementations may provide + special methods for case mutation or random access. + + Use casefile_append or casefile_append_xfer to append a case to a + casefile. + + The casefile may be read sequentially, + starting from the beginning, by "casereaders". Any + number of casereaders may be created, at any time. + Each casereader has an independent position in the casefile. + + Casereaders may only move forward. They cannot move backward to + arbitrary records or seek randomly. Cloning casereaders is + possible, but it is not yet implemented. + + Use casereader_read() or casereader_read_xfer() to read + a case from a casereader. Use casereader_destroy() to + discard a casereader when it is no longer needed. When a casefile is no longer needed, it may be destroyed with casefile_destroy(). This function will also destroy any remaining casereaders. */ -/* FIXME: should we implement compression? */ - -/* In-memory cases are arranged in an array of arrays. The top - level is variable size and the size of each bottom level array - is fixed at the number of cases defined here. */ -#define CASES_PER_BLOCK 128 - -/* A casefile. */ -struct casefile - { - /* Basic data. */ - struct casefile *next, *prev; /* Next, prev in global list. */ - size_t value_cnt; /* Case size in `union value's. */ - size_t case_acct_size; /* Case size for accounting. */ - unsigned long case_cnt; /* Number of cases stored. */ - enum { MEMORY, DISK } storage; /* Where cases are stored. */ - enum { WRITE, READ } mode; /* Is writing or reading allowed? */ - struct casereader *readers; /* List of our readers. */ - bool being_destroyed; /* Does a destructive reader exist? */ - bool ok; /* False after I/O error. */ - - /* Memory storage. */ - struct ccase **cases; /* Pointer to array of cases. */ - - /* Disk storage. */ - int fd; /* File descriptor, -1 if none. */ - char *file_name; /* File name. */ - union value *buffer; /* I/O buffer, NULL if none. */ - size_t buffer_used; /* Number of values used in buffer. */ - size_t buffer_size; /* Buffer size in values. */ - }; - -/* For reading out the cases in a casefile. */ -struct casereader - { - struct casereader *next, *prev; /* Next, prev in casefile's list. */ - struct casefile *cf; /* Our casefile. */ - unsigned long case_idx; /* Case number of current case. */ - bool destructive; /* Is this a destructive reader? */ - bool random; /* Is this a random reader? */ - - /* Disk storage. */ - int fd; /* File descriptor. */ - off_t file_ofs; /* Current position in fd. */ - off_t buffer_ofs; /* File offset of buffer start. */ - union value *buffer; /* I/O buffer. */ - size_t buffer_pos; /* Offset of buffer position. */ - struct ccase c; /* Current case. */ - }; +static struct ll_list all_casefiles = LL_INITIALIZER (all_casefiles); -/* Return the case number of the current case */ -unsigned long -casereader_cnum(const struct casereader *r) +static struct casefile * +ll_to_casefile (const struct ll *ll) { - return r->case_idx; + return ll_data (ll, struct casefile, ll); } -/* Doubly linked list of all casefiles. */ -static struct casefile *casefiles; - -/* Number of bytes of case allocated in in-memory casefiles. */ -static size_t case_bytes; - -static void register_atexit (void); -static void exit_handler (void); - -static void reader_open_file (struct casereader *); -static void write_case_to_disk (struct casefile *, const struct ccase *); -static void flush_buffer (struct casefile *); -static void seek_and_fill_buffer (struct casereader *); -static bool fill_buffer (struct casereader *); +static struct casereader * +ll_to_casereader (const struct ll *ll) +{ + return ll_data (ll, struct casereader, ll); +} -static void io_error (struct casefile *, const char *, ...) - PRINTF_FORMAT (2, 3); -static int safe_open (const char *file_name, int flags); -static int safe_close (int fd); -/* Creates and returns a casefile to store cases of VALUE_CNT - `union value's each. */ -struct casefile * -casefile_create (size_t value_cnt) +/* atexit() handler that closes and deletes our temporary + files. */ +static void +exit_handler (void) { - struct casefile *cf = xmalloc (sizeof *cf); - cf->next = casefiles; - cf->prev = NULL; - if (cf->next != NULL) - cf->next->prev = cf; - casefiles = cf; - cf->value_cnt = value_cnt; - cf->case_acct_size = (cf->value_cnt + 4) * sizeof *cf->buffer; - cf->case_cnt = 0; - cf->storage = MEMORY; - cf->mode = WRITE; - cf->readers = NULL; - cf->being_destroyed = 0; - cf->ok = true; - cf->cases = NULL; - cf->fd = -1; - cf->file_name = NULL; - cf->buffer = NULL; - cf->buffer_size = ROUND_UP (cf->value_cnt, IO_BUF_SIZE); - if (cf->value_cnt > 0 && cf->buffer_size % cf->value_cnt > 64) - cf->buffer_size = cf->value_cnt; - cf->buffer_used = 0; - register_atexit (); - return cf; + while (!ll_is_empty (&all_casefiles)) + casefile_destroy (ll_to_casefile (ll_head (&all_casefiles))); } -/* Destroys casefile CF. */ +/* Insert CF into the global list of casefiles */ void -casefile_destroy (struct casefile *cf) +casefile_register (struct casefile *cf, const struct class_casefile *class) { - if (cf != NULL) + static bool initialised ; + if ( !initialised ) { - if (cf->next != NULL) - cf->next->prev = cf->prev; - if (cf->prev != NULL) - cf->prev->next = cf->next; - if (casefiles == cf) - casefiles = cf->next; - - while (cf->readers != NULL) - casereader_destroy (cf->readers); - - if (cf->cases != NULL) - { - size_t idx, block_cnt; - - case_bytes -= cf->case_cnt * cf->case_acct_size; - for (idx = 0; idx < cf->case_cnt; idx++) - { - size_t block_idx = idx / CASES_PER_BLOCK; - size_t case_idx = idx % CASES_PER_BLOCK; - struct ccase *c = &cf->cases[block_idx][case_idx]; - case_destroy (c); - } - - block_cnt = DIV_RND_UP (cf->case_cnt, CASES_PER_BLOCK); - for (idx = 0; idx < block_cnt; idx++) - free (cf->cases[idx]); - - free (cf->cases); - } - - if (cf->fd != -1) - safe_close (cf->fd); - - if (cf->file_name != NULL && remove (cf->file_name) == -1) - io_error (cf, _("%s: Removing temporary file: %s."), - cf->file_name, strerror (errno)); - free (cf->file_name); - - free (cf->buffer); - - free (cf); + atexit (exit_handler); + initialised = true; } + + cf->class = class; + ll_push_head (&all_casefiles, &cf->ll); + ll_init (&cf->reader_list); } -/* Returns true if an I/O error has occurred in casefile CF. */ -bool -casefile_error (const struct casefile *cf) +/* Remove CF from the global list */ +static void +casefile_unregister(struct casefile *cf) { - return !cf->ok; + ll_remove (&cf->ll); } -/* Returns true only if casefile CF is stored in memory (instead of on - disk), false otherwise. */ -bool -casefile_in_core (const struct casefile *cf) +/* Return the casefile corresponding to this reader */ +struct casefile * +casereader_get_casefile (const struct casereader *r) { - assert (cf != NULL); - - return cf->storage == MEMORY; + return r->cf; } -/* Puts a casefile to "sleep", that is, minimizes the resources - needed for it by closing its file descriptor and freeing its - buffer. This is useful if we need so many casefiles that we - might not have enough memory and file descriptors to go - around. +/* Return the case number of the current case */ +unsigned long +casereader_cnum(const struct casereader *r) +{ + return r->class->cnum(r); +} - For simplicity, this implementation always converts the - casefile to reader mode. If this turns out to be a problem, - with a little extra work we could also support sleeping - writers. - Returns true if successful, false if an I/O error occurred. */ +/* Reads a copy of the next case from READER into C. + Caller is responsible for destroying C. + Returns true if successful, false at end of file. */ bool -casefile_sleep (const struct casefile *cf_) +casereader_read (struct casereader *reader, struct ccase *c) { - struct casefile *cf = (struct casefile *) cf_; - assert (cf != NULL); + struct casefile *cf = casereader_get_casefile (reader); - casefile_mode_reader (cf); - casefile_to_disk (cf); - flush_buffer (cf); + struct ccase *read_case = NULL; - if (cf->fd != -1) - { - safe_close (cf->fd); - cf->fd = -1; - } - if (cf->buffer != NULL) - { - free (cf->buffer); - cf->buffer = NULL; - } + if ( casefile_error (cf) ) + return false; - return cf->ok; -} + read_case = reader->class->get_next_case (reader); + if ( ! read_case ) return false; -/* Returns the number of `union value's in a case for CF. */ -size_t -casefile_get_value_cnt (const struct casefile *cf) -{ - assert (cf != NULL); + case_clone (c, read_case ); - return cf->value_cnt; + return true; } -/* Returns the number of cases in casefile CF. */ -unsigned long -casefile_get_case_cnt (const struct casefile *cf) -{ - assert (cf != NULL); - - return cf->case_cnt; -} -/* Appends a copy of case C to casefile CF. Not valid after any - reader for CF has been created. - Returns true if successful, false if an I/O error occurred. */ +/* Reads the next case from READER into C and transfers ownership + to the caller. Caller is responsible for destroying C. + Returns true if successful, false at end of file or on I/O + error. */ bool -casefile_append (struct casefile *cf, const struct ccase *c) +casereader_read_xfer (struct casereader *ffr, struct ccase *c) { - assert (cf != NULL); - assert (c != NULL); - assert (cf->mode == WRITE); + struct casefile *cf = casereader_get_casefile (ffr); - assert ( cf->value_cnt <= c->case_data->value_cnt ); + struct ccase *read_case = NULL ; - /* Try memory first. */ - if (cf->storage == MEMORY) - { - if (case_bytes < get_workspace ()) - { - size_t block_idx = cf->case_cnt / CASES_PER_BLOCK; - size_t case_idx = cf->case_cnt % CASES_PER_BLOCK; - struct ccase new_case; - - case_bytes += cf->case_acct_size; - case_clone (&new_case, c); - if (case_idx == 0) - { - if ((block_idx & (block_idx - 1)) == 0) - { - size_t block_cap = block_idx == 0 ? 1 : block_idx * 2; - cf->cases = xnrealloc (cf->cases, - block_cap, sizeof *cf->cases); - } - - cf->cases[block_idx] = xnmalloc (CASES_PER_BLOCK, - sizeof **cf->cases); - } - - case_move (&cf->cases[block_idx][case_idx], &new_case); - } - else - { - casefile_to_disk (cf); - assert (cf->storage == DISK); - write_case_to_disk (cf, c); - } - } + if ( casefile_error (cf) ) + return false; + + read_case = ffr->class->get_next_case (ffr); + if ( ! read_case ) return false; + + if ( ffr->destructive && casefile_in_core (cf) ) + case_move (c, read_case); else - write_case_to_disk (cf, c); + case_clone (c, read_case); - cf->case_cnt++; - return cf->ok; + return true; } -/* Appends case C to casefile CF, which takes over ownership of - C. Not valid after any reader for CF has been created. - Returns true if successful, false if an I/O error occurred. */ -bool -casefile_append_xfer (struct casefile *cf, struct ccase *c) +/* Destroys R. */ +void +casereader_destroy (struct casereader *r) { - casefile_append (cf, c); - case_destroy (c); - return cf->ok; + ll_remove (&r->ll); + + r->class->destroy(r); } -/* Writes case C to casefile CF's disk buffer, first flushing the buffer to - disk if it would otherwise overflow. - Returns true if successful, false if an I/O error occurred. */ -static void -write_case_to_disk (struct casefile *cf, const struct ccase *c) +/* Destroys casefile CF. */ +void +casefile_destroy(struct casefile *cf) { - if (!cf->ok) - return; + if (!cf) return; - case_to_values (c, cf->buffer + cf->buffer_used, cf->value_cnt); - cf->buffer_used += cf->value_cnt; - if (cf->buffer_used + cf->value_cnt > cf->buffer_size) - flush_buffer (cf); + assert(cf->class->destroy); + + while (!ll_is_empty (&cf->reader_list)) + casereader_destroy (ll_to_casereader (ll_head (&cf->reader_list))); + + casefile_unregister(cf); + + cf->class->destroy(cf); } -/* If any bytes in CF's output buffer are used, flush them to - disk. */ -static void -flush_buffer (struct casefile *cf) +/* Returns true if an I/O error has occurred in casefile CF. */ +bool +casefile_error (const struct casefile *cf) { - if (cf->ok && cf->buffer_used > 0) - { - if (!full_write (cf->fd, cf->buffer, - cf->buffer_size * sizeof *cf->buffer)) - io_error (cf, _("Error writing temporary file: %s."), - strerror (errno)); - cf->buffer_used = 0; - } + return cf->class->error(cf); } -/* If CF is currently stored in memory, writes it to disk. Readers, if any, - retain their current positions. - Returns true if successful, false if an I/O error occurred. */ -bool -casefile_to_disk (const struct casefile *cf_) +/* Returns the number of cases in casefile CF. */ +unsigned long +casefile_get_case_cnt (const struct casefile *cf) { - struct casefile *cf = (struct casefile *) cf_; - struct casereader *reader; - - assert (cf != NULL); - - if (cf->storage == MEMORY) - { - size_t idx, block_cnt; - - assert (cf->file_name == NULL); - assert (cf->fd == -1); - assert (cf->buffer_used == 0); - - if (!make_temp_file (&cf->fd, &cf->file_name)) - { - cf->ok = false; - return false; - } - cf->storage = DISK; - - cf->buffer = xnmalloc (cf->buffer_size, sizeof *cf->buffer); - memset (cf->buffer, 0, cf->buffer_size * sizeof *cf->buffer); - - case_bytes -= cf->case_cnt * cf->case_acct_size; - for (idx = 0; idx < cf->case_cnt; idx++) - { - size_t block_idx = idx / CASES_PER_BLOCK; - size_t case_idx = idx % CASES_PER_BLOCK; - struct ccase *c = &cf->cases[block_idx][case_idx]; - write_case_to_disk (cf, c); - case_destroy (c); - } - - block_cnt = DIV_RND_UP (cf->case_cnt, CASES_PER_BLOCK); - for (idx = 0; idx < block_cnt; idx++) - free (cf->cases[idx]); - - free (cf->cases); - cf->cases = NULL; - - if (cf->mode == READ) - flush_buffer (cf); - - for (reader = cf->readers; reader != NULL; reader = reader->next) - reader_open_file (reader); - } - return cf->ok; + return cf->class->get_case_cnt(cf); } -/* Changes CF to reader mode, ensuring that no more cases may be - added. Creating a casereader for CF has the same effect. */ -void -casefile_mode_reader (struct casefile *cf) +/* Returns the number of `union value's in a case for CF. */ +size_t +casefile_get_value_cnt (const struct casefile *cf) { - assert (cf != NULL); - cf->mode = READ; + return cf->class->get_value_cnt(cf); } /* Creates and returns a casereader for CF. A casereader can be used to sequentially read the cases in a casefile. */ struct casereader * -casefile_get_reader (const struct casefile *cf_) +casefile_get_reader (const struct casefile *cf) { - struct casefile *cf = (struct casefile *) cf_; - struct casereader *reader; - - assert (cf != NULL); - assert (!cf->being_destroyed); + struct casereader *r = cf->class->get_reader(cf); + r->cf = (struct casefile *) cf; - /* Flush the buffer to disk if it's not empty. */ - if (cf->mode == WRITE && cf->storage == DISK) - flush_buffer (cf); + assert (r->class); - cf->mode = READ; - - reader = xmalloc (sizeof *reader); - reader->next = cf->readers; - if (cf->readers != NULL) - reader->next->prev = reader; - cf->readers = reader; - reader->prev = NULL; - reader->cf = cf; - reader->case_idx = 0; - reader->destructive = 0; - reader->random = false; - reader->fd = -1; - reader->buffer = NULL; - reader->buffer_pos = 0; - case_nullify (&reader->c); - - if (reader->cf->storage == DISK) - reader_open_file (reader); - - return reader; -} - -/* Creates and returns a random casereader for CF. A random - casereader can be used to randomly read the cases in a - casefile. */ -struct casereader * -casefile_get_random_reader (const struct casefile *cf) -{ - struct casefile *mutable_casefile = (struct casefile*) cf; - struct casereader *reader; - - enum { WRITE, READ } mode = cf->mode ; - reader = casefile_get_reader (cf); - reader->random = true; - mutable_casefile->mode = mode; - - return reader; + return r; } /* Creates and returns a destructive casereader for CF. Like a @@ -568,303 +232,84 @@ casefile_get_random_reader (const struct casefile *cf) struct casereader * casefile_get_destructive_reader (struct casefile *cf) { - struct casereader *reader; - - assert (cf->readers == NULL); - reader = casefile_get_reader (cf); - reader->destructive = 1; - cf->being_destroyed = 1; - return reader; -} - -/* Opens a disk file for READER and seeks to the current position as indicated - by case_idx. Normally the current position is the beginning of the file, - but casefile_to_disk may cause the file to be opened at a different - position. */ -static void -reader_open_file (struct casereader *reader) -{ - struct casefile *cf = reader->cf; - if (!cf->ok || reader->case_idx >= cf->case_cnt) - return; - - if (cf->fd != -1) - { - reader->fd = cf->fd; - cf->fd = -1; - } - else - { - reader->fd = safe_open (cf->file_name, O_RDONLY); - if (reader->fd < 0) - io_error (cf, _("%s: Opening temporary file: %s."), - cf->file_name, strerror (errno)); - } - - if (cf->buffer != NULL) - { - reader->buffer = cf->buffer; - cf->buffer = NULL; - } - else - { - reader->buffer = xnmalloc (cf->buffer_size, sizeof *cf->buffer); - memset (reader->buffer, 0, cf->buffer_size * sizeof *cf->buffer); - } - - case_create (&reader->c, cf->value_cnt); - - reader->buffer_ofs = -1; - reader->file_ofs = -1; - seek_and_fill_buffer (reader); -} - -/* Seeks the backing file for READER to the proper position and - refreshes the buffer contents. */ -static void -seek_and_fill_buffer (struct casereader *reader) -{ - struct casefile *cf = reader->cf; - off_t new_ofs; - - if (cf->value_cnt != 0) - { - size_t buffer_case_cnt = cf->buffer_size / cf->value_cnt; - new_ofs = ((off_t) reader->case_idx / buffer_case_cnt - * cf->buffer_size * sizeof *cf->buffer); - reader->buffer_pos = (reader->case_idx % buffer_case_cnt - * cf->value_cnt); - } - else - new_ofs = 0; - if (new_ofs != reader->file_ofs) - { - if (lseek (reader->fd, new_ofs, SEEK_SET) != new_ofs) - io_error (cf, _("%s: Seeking temporary file: %s."), - cf->file_name, strerror (errno)); - else - reader->file_ofs = new_ofs; - } + struct casereader *r = cf->class->get_reader (cf); + r->cf = cf; + r->destructive = true; + cf->being_destroyed = true; - if (cf->case_cnt > 0 && cf->value_cnt > 0 && reader->buffer_ofs != new_ofs) - fill_buffer (reader); + return r; } -/* Fills READER's buffer by reading a block from disk. */ -static bool -fill_buffer (struct casereader *reader) -{ - if (reader->cf->ok) - { - int bytes = full_read (reader->fd, reader->buffer, - reader->cf->buffer_size * sizeof *reader->buffer); - if (bytes < 0) - io_error (reader->cf, _("%s: Reading temporary file: %s."), - reader->cf->file_name, strerror (errno)); - else if (bytes != reader->cf->buffer_size * sizeof *reader->buffer) - io_error (reader->cf, _("%s: Temporary file ended unexpectedly."), - reader->cf->file_name); - else - { - reader->buffer_ofs = reader->file_ofs; - reader->file_ofs += bytes; - } - } - return reader->cf->ok; -} - -/* Returns the casefile that READER reads. */ -const struct casefile * -casereader_get_casefile (const struct casereader *reader) -{ - assert (reader != NULL); - - return reader->cf; -} - -/* Reads a copy of the next case from READER into C. - Caller is responsible for destroying C. - Returns true if successful, false at end of file. */ -bool -casereader_read (struct casereader *reader, struct ccase *c) +/* Appends a copy of case C to casefile CF. + Returns true if successful, false if an I/O error occurred. */ +bool +casefile_append (struct casefile *cf, const struct ccase *c) { - assert (reader != NULL); - - if (!reader->cf->ok || reader->case_idx >= reader->cf->case_cnt) - return false; + assert (c->case_data->value_cnt >= casefile_get_value_cnt (cf)); - if (reader->cf->storage == MEMORY) - { - size_t block_idx = reader->case_idx / CASES_PER_BLOCK; - size_t case_idx = reader->case_idx % CASES_PER_BLOCK; - - case_clone (c, &reader->cf->cases[block_idx][case_idx]); - reader->case_idx++; - return true; - } - else - { - if (reader->buffer_pos + reader->cf->value_cnt > reader->cf->buffer_size) - { - if (!fill_buffer (reader)) - return false; - reader->buffer_pos = 0; - } - - case_from_values (&reader->c, reader->buffer + reader->buffer_pos, - reader->cf->value_cnt); - reader->buffer_pos += reader->cf->value_cnt; - reader->case_idx++; - - case_clone (c, &reader->c); - return true; - } + return cf->class->append(cf, c); } -/* Reads the next case from READER into C and transfers ownership - to the caller. Caller is responsible for destroying C. - Returns true if successful, false at end of file or on I/O - error. */ -bool -casereader_read_xfer (struct casereader *reader, struct ccase *c) +/* Appends case C to casefile CF, which takes over ownership of + C. + Returns true if successful, false if an I/O error occurred. */ +bool +casefile_append_xfer (struct casefile *cf, struct ccase *c) { - assert (reader != NULL); - - if (reader->destructive == 0 - || reader->case_idx >= reader->cf->case_cnt - || reader->cf->storage == DISK) - return casereader_read (reader, c); - else - { - size_t block_idx = reader->case_idx / CASES_PER_BLOCK; - size_t case_idx = reader->case_idx % CASES_PER_BLOCK; - struct ccase *read_case = &reader->cf->cases[block_idx][case_idx]; - - case_move (c, read_case); - reader->case_idx++; - return true; - } -} + assert (c->case_data->value_cnt >= casefile_get_value_cnt (cf)); -/* Sets the next case to be read by READER to CASE_IDX, - which must be less than the number of cases in the casefile. - Allowed only for random readers. */ -void -casereader_seek (struct casereader *reader, unsigned long case_idx) -{ - assert (reader != NULL); - assert (reader->random); - assert (case_idx < reader->cf->case_cnt); + cf->class->append (cf, c); + case_destroy (c); - reader->case_idx = case_idx; - if (reader->cf->storage == DISK) - seek_and_fill_buffer (reader); + return cf->class->error (cf); } -/* Destroys READER. */ -void -casereader_destroy (struct casereader *reader) -{ - assert (reader != NULL); - if (reader->next != NULL) - reader->next->prev = reader->prev; - if (reader->prev != NULL) - reader->prev->next = reader->next; - if (reader->cf->readers == reader) - reader->cf->readers = reader->next; - if (reader->cf->buffer == NULL) - reader->cf->buffer = reader->buffer; - else - free (reader->buffer); - if (reader->fd != -1) - { - if (reader->cf->fd == -1) - reader->cf->fd = reader->fd; - else - safe_close (reader->fd); - } +/* Puts a casefile to "sleep", that is, minimizes the resources + needed for it by closing its file descriptor and freeing its + buffer. This is useful if we need so many casefiles that we + might not have enough memory and file descriptors to go + around. - case_destroy (&reader->c); + Implementations may choose to silently ignore this function. - free (reader); -} - -/* Marks CF as having encountered an I/O error. - If this is the first error on CF, reports FORMAT to the user, - doing printf()-style substitutions. */ -static void -io_error (struct casefile *cf, const char *format, ...) + Returns true if successful, false if an I/O error occurred. */ +bool +casefile_sleep (const struct casefile *cf) { - if (cf->ok) - { - struct msg m; - va_list args; - - m.category = MSG_GENERAL; - m.severity = MSG_ERROR; - m.where.file_name = NULL; - m.where.line_number = -1; - va_start (args, format); - m.text = xvasprintf (format, args); - va_end (args); - - msg_emit (&m); - } - cf->ok = false; + return cf->class->sleep ? cf->class->sleep(cf) : true; } -/* Calls open(), passing FILE_NAME and FLAGS, repeating as necessary - to deal with interrupted calls. */ -static int -safe_open (const char *file_name, int flags) +/* Returns true only if casefile CF is stored in memory (instead of on + disk), false otherwise. +*/ +bool +casefile_in_core (const struct casefile *cf) { - int fd; - - do - { - fd = open (file_name, flags); - } - while (fd == -1 && errno == EINTR); - - return fd; + return cf->class->in_core(cf); } -/* Calls close(), passing FD, repeating as necessary to deal with - interrupted calls. */ -static int safe_close (int fd) -{ - int retval; - - do - { - retval = close (fd); - } - while (retval == -1 && errno == EINTR); +/* If CF is currently stored in memory, writes it to disk. Readers, if any, + retain their current positions. - return retval; -} + Implementations may choose to silently ignore this function. -/* Registers our exit handler with atexit() if it has not already - been registered. */ -static void -register_atexit (void) + Returns true if successful, false if an I/O error occurred. */ +bool +casefile_to_disk (const struct casefile *cf) { - static bool registered = false; - if (!registered) - { - registered = true; - atexit (exit_handler); - } + return cf->class->to_disk ? cf->class->to_disk(cf) : true; } -/* atexit() handler that closes and deletes our temporary - files. */ -static void -exit_handler (void) +void +casereader_register(struct casefile *cf, + struct casereader *reader, + const struct class_casereader *class) { - while (casefiles != NULL) - casefile_destroy (casefiles); + reader->class = class; + reader->cf = cf; + + ll_push_head (&cf->reader_list, &reader->ll); } diff --git a/src/data/casefile.h b/src/data/casefile.h index ab60e192..1bddb2d2 100644 --- a/src/data/casefile.h +++ b/src/data/casefile.h @@ -1,5 +1,5 @@ /* PSPP - computes sample statistics. - Copyright (C) 2004 Free Software Foundation, Inc. + Copyright (C) 2004, 2006 Free Software Foundation, Inc. Written by Ben Pfaff . This program is free software; you can redistribute it and/or @@ -17,42 +17,51 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -#ifndef HEADER_CASEFILE -#define HEADER_CASEFILE +#ifndef CASEFILE_H +#define CASEFILE_H +#include #include #include + struct ccase; -struct casefile; struct casereader; +struct casefile; + + + +struct casefile *casereader_get_casefile (const struct casereader *r); + +unsigned long casereader_cnum (const struct casereader *r); + +bool casereader_read (struct casereader *r, struct ccase *c); + +bool casereader_read_xfer (struct casereader *r, struct ccase *c); + +void casereader_destroy (struct casereader *r); + +void casefile_destroy (struct casefile *cf); + +bool casefile_error (const struct casefile *cf); + +unsigned long casefile_get_case_cnt (const struct casefile *cf); + +size_t casefile_get_value_cnt (const struct casefile *cf); -struct casefile *casefile_create (size_t value_cnt); -void casefile_destroy (struct casefile *); +struct casereader *casefile_get_reader (const struct casefile *cf); +struct casereader *casefile_get_destructive_reader (struct casefile *cf); -bool casefile_error (const struct casefile *); -bool casefile_in_core (const struct casefile *); -bool casefile_to_disk (const struct casefile *); -bool casefile_sleep (const struct casefile *); -size_t casefile_get_value_cnt (const struct casefile *); -unsigned long casefile_get_case_cnt (const struct casefile *); -bool casefile_append (struct casefile *, const struct ccase *); -bool casefile_append_xfer (struct casefile *, struct ccase *); +bool casefile_append (struct casefile *cf, const struct ccase *c); -void casefile_mode_reader (struct casefile *); -struct casereader *casefile_get_reader (const struct casefile *); -struct casereader *casefile_get_destructive_reader (struct casefile *); -struct casereader *casefile_get_random_reader (const struct casefile *); +bool casefile_append_xfer (struct casefile *cf, struct ccase *c); -const struct casefile *casereader_get_casefile (const struct casereader *); -bool casereader_read (struct casereader *, struct ccase *); -bool casereader_read_xfer (struct casereader *, struct ccase *); -void casereader_destroy (struct casereader *); +bool casefile_sleep (const struct casefile *cf); -void casereader_seek (struct casereader *, unsigned long case_idx); +bool casefile_in_core (const struct casefile *cf); -unsigned long casereader_cnum(const struct casereader *); +bool casefile_to_disk (const struct casefile *cf); -#endif /* casefile.h */ +#endif diff --git a/src/data/fastfile.c b/src/data/fastfile.c new file mode 100644 index 00000000..9cdba156 --- /dev/null +++ b/src/data/fastfile.c @@ -0,0 +1,751 @@ +/* PSPP - computes sample statistics. + Copyright (C) 2004, 2006 Free Software Foundation, Inc. + Written by Ben Pfaff . + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. + + This program 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 + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301, USA. */ + +#include +#include "casefile.h" +#include "casefile-private.h" +#include "fastfile.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include "case.h" +#include +#include +#include "full-read.h" +#include "full-write.h" +#include +#include "make-file.h" +#include "settings.h" +#include "variable.h" + +#include "gettext.h" +#define _(msgid) gettext (msgid) + +#define IO_BUF_SIZE (8192 / sizeof (union value)) + +/* A fastfile represents a sequentially accessible stream of + immutable cases. + + If workspace allows, a fastfile is maintained in memory. If + workspace overflows, then the fastfile is pushed to disk. In + either case the interface presented to callers is kept the + same. + + The life cycle of a fastfile consists of up to three phases: + + 1. Writing. The fastfile initially contains no cases. In + this phase, any number of cases may be appended to the + end of a fastfile. (Cases are never inserted in the + middle or before the beginning of a fastfile.) + + Use casefile_append or casefile_append_xfer to + append a case to a fastfile. + + 2. Reading. The fastfile may be read sequentially, + starting from the beginning, by "casereaders". Any + number of casereaders may be created, at any time, + during the reading phase. Each casereader has an + independent position in the fastfile. + + Ordinary casereaders may only move forward. They + cannot move backward to arbitrary records or seek + randomly. Cloning casereaders is possible, but it is + not yet implemented. + + Use casefile_get_reader to create a casereader for + use in phase 2. This also transitions from phase 1 to + phase 2. Calling fastfile_mode_reader makes the same + transition, without creating a casereader. + + Use casereader_read or casereader_read_xfer to read + a case from a casereader. Use casereader_destroy to + discard a casereader when it is no longer needed. + + 3. Destruction. This phase is optional. The fastfile is + also read with casereaders in this phase, but the + ability to create new casereaders is curtailed. + + In this phase, casereaders could still be cloned (once + we eventually implement cloning). + + To transition from phase 1 or 2 to phase 3 and create a + casereader, call casefile_get_destructive_reader(). + The same functions apply to the casereader obtained + this way as apply to casereaders obtained in phase 2. + + After casefile_get_destructive_reader is called, no + more casereaders may be created. (If cloning of + casereaders were implemented, it would still be + possible.) + + The purpose of the limitations applied to casereaders + in phase 3 is to allow in-memory fastfiles to fully + transfer ownership of cases to the casereaders, + avoiding the need for extra copies of case data. For + relatively static data sets with many variables, I + suspect (without evidence) that this may be a big + performance boost. + + When a fastfile is no longer needed, it may be destroyed with + casefile_destroy. This function will also destroy any + remaining casereaders. */ + +/* FIXME: should we implement compression? */ + +/* In-memory cases are arranged in an array of arrays. The top + level is variable size and the size of each bottom level array + is fixed at the number of cases defined here. */ +#define CASES_PER_BLOCK 128 + +static const struct class_casefile class; + +/* A fastfile. */ +struct fastfile +{ + struct casefile cf; /* Parent */ + + size_t value_cnt; /* Case size in `union value's. */ + size_t case_acct_size; /* Case size for accounting. */ + unsigned long case_cnt; /* Number of cases stored. */ + enum { MEMORY, DISK } storage; /* Where cases are stored. */ + enum { WRITE, READ } mode; /* Is writing or reading allowed? */ + + bool ok; /* False after I/O error. */ + + /* Memory storage. */ + struct ccase **cases; /* Pointer to array of cases. */ + + /* Disk storage. */ + int fd; /* File descriptor, -1 if none. */ + char *file_name; /* File name. */ + union value *buffer; /* I/O buffer, NULL if none. */ + size_t buffer_used; /* Number of values used in buffer. */ + size_t buffer_size; /* Buffer size in values. */ +}; + + +static const struct class_casereader class_reader; + +/* For reading out the cases in a fastfile. */ +struct fastfilereader +{ + struct casereader cr; /* Parent */ + + unsigned long case_idx; /* Case number of current case. */ + + /* Disk storage. */ + int fd; /* File descriptor. */ + off_t file_ofs; /* Current position in fd. */ + off_t buffer_ofs; /* File offset of buffer start. */ + union value *buffer; /* I/O buffer. */ + size_t buffer_pos; /* Offset of buffer position. */ + struct ccase c; /* Current case. */ +}; + + +static void io_error (struct fastfile *, const char *, ...) + PRINTF_FORMAT (2, 3); +static int safe_open (const char *file_name, int flags); +static int safe_close (int fd); +static void write_case_to_disk (struct fastfile *, const struct ccase *); +static void flush_buffer (struct fastfile *); + +static void reader_open_file (struct fastfilereader *); + +static void seek_and_fill_buffer (struct fastfilereader *); +static bool fill_buffer (struct fastfilereader *); + + +/* Number of bytes of case allocated in in-memory fastfiles. */ +static size_t case_bytes; + +/* Destroys READER. */ +static void fastfilereader_destroy (struct casereader *cr) +{ + struct fastfilereader *reader = (struct fastfilereader *) cr; + struct fastfile *ff = (struct fastfile *) casereader_get_casefile (cr); + + if (ff->buffer == NULL) + ff->buffer = reader->buffer; + else + free (reader->buffer); + + if (reader->fd != -1) + { + if (ff->fd == -1) + ff->fd = reader->fd; + else + safe_close (reader->fd); + } + + case_destroy (&reader->c); + + free (reader); +} + + + +/* Return the case number of the current case */ +static unsigned long +fastfilereader_cnum (const struct casereader *cr) +{ + const struct fastfilereader *ffr = (const struct fastfilereader *) cr; + return ffr->case_idx; +} + + +/* Returns the next case pointed to by FFR and increments + FFR's pointer. Returns NULL if FFR points beyond the last case. +*/ +static struct ccase * +fastfilereader_get_next_case (struct casereader *cr) +{ + struct fastfile *ff = (struct fastfile *) casereader_get_casefile (cr); + struct fastfilereader *ffr = (struct fastfilereader *) cr; + struct ccase *read_case = NULL ; + + if ( ffr->case_idx >= ff->case_cnt ) + return NULL ; + + if (ff->storage == MEMORY ) + { + size_t block_idx = ffr->case_idx / CASES_PER_BLOCK; + size_t case_idx = ffr->case_idx % CASES_PER_BLOCK; + read_case = &ff->cases[block_idx][case_idx]; + } + else + { + if (ffr->buffer_pos + ff->value_cnt > ff->buffer_size) + { + if (!fill_buffer (ffr)) + return NULL; + ffr->buffer_pos = 0; + } + + case_from_values (&ffr->c, ffr->buffer + ffr->buffer_pos, + ff->value_cnt); + ffr->buffer_pos += ff->value_cnt; + + read_case = &ffr->c; + } + ffr->case_idx++; + + return read_case; +} + +/* Creates and returns a casereader for CF. A casereader can be used to + sequentially read the cases in a fastfile. */ +static struct casereader * +fastfile_get_reader (const struct casefile *cf_) +{ + struct casefile *cf = (struct casefile *) cf_; + struct fastfilereader *ffr = xzalloc (sizeof *ffr); + struct casereader *reader = (struct casereader *) ffr; + struct fastfile *ff = (struct fastfile *) cf; + + assert (!cf->being_destroyed); + + /* Flush the buffer to disk if it's not empty. */ + if (ff->mode == WRITE && ff->storage == DISK) + flush_buffer (ff); + + ff->mode = READ; + + casereader_register (cf, reader, &class_reader); + + ffr->case_idx = 0; + reader->destructive = 0; + ffr->fd = -1; + ffr->buffer = NULL; + ffr->buffer_pos = 0; + case_nullify (&ffr->c); + + if (ff->storage == DISK) + reader_open_file (ffr); + + return reader; +} + +/* Returns the number of `union value's in a case for CF. */ +static size_t +fastfile_get_value_cnt (const struct casefile *cf) +{ + const struct fastfile *ff = (const struct fastfile *) cf; + return ff->value_cnt; +} + +/* Appends a copy of case C to fastfile CF. Not valid after any + reader for CF has been created. + Returns true if successful, false if an I/O error occurred. */ +static bool +fastfile_append (struct casefile *cf, const struct ccase *c) +{ + struct fastfile *ff = (struct fastfile *) cf; + assert (ff->mode == WRITE); + assert (c != NULL); + + /* Try memory first. */ + if (ff->storage == MEMORY) + { + if (case_bytes < get_workspace ()) + { + size_t block_idx = ff->case_cnt / CASES_PER_BLOCK; + size_t case_idx = ff->case_cnt % CASES_PER_BLOCK; + struct ccase new_case; + + case_bytes += ff->case_acct_size; + case_clone (&new_case, c); + if (case_idx == 0) + { + if ((block_idx & (block_idx - 1)) == 0) + { + size_t block_cap = block_idx == 0 ? 1 : block_idx * 2; + ff->cases = xnrealloc (ff->cases, + block_cap, sizeof *ff->cases); + } + + ff->cases[block_idx] = xnmalloc (CASES_PER_BLOCK, + sizeof **ff->cases); + } + + case_move (&ff->cases[block_idx][case_idx], &new_case); + } + else + { + casefile_to_disk (cf); + assert (ff->storage == DISK); + write_case_to_disk (ff, c); + } + } + else + write_case_to_disk (ff, c); + + ff->case_cnt++; + return ff->ok; +} + + +/* Returns the number of cases in fastfile CF. */ +static unsigned long +fastfile_get_case_cnt (const struct casefile *cf) +{ + const struct fastfile *ff = (const struct fastfile *) cf; + return ff->case_cnt; +} + + +/* Returns true only if fastfile CF is stored in memory (instead of on + disk), false otherwise. */ +static bool +fastfile_in_core (const struct casefile *cf) +{ + const struct fastfile *ff = (const struct fastfile *) cf; + return (ff->storage == MEMORY); +} + + +/* If CF is currently stored in memory, writes it to disk. Readers, if any, + retain their current positions. + Returns true if successful, false if an I/O error occurred. */ +static bool +fastfile_to_disk (const struct casefile *cf_) +{ + struct fastfile *ff = (struct fastfile *) cf_; + struct casefile *cf = &ff->cf; + + if (ff->storage == MEMORY) + { + size_t idx, block_cnt; + struct casereader *reader; + + assert (ff->file_name == NULL); + assert (ff->fd == -1); + assert (ff->buffer_used == 0); + + if (!make_temp_file (&ff->fd, &ff->file_name)) + { + ff->ok = false; + return false; + } + ff->storage = DISK; + + ff->buffer = xnmalloc (ff->buffer_size, sizeof *ff->buffer); + memset (ff->buffer, 0, ff->buffer_size * sizeof *ff->buffer); + + case_bytes -= ff->case_cnt * ff->case_acct_size; + for (idx = 0; idx < ff->case_cnt; idx++) + { + size_t block_idx = idx / CASES_PER_BLOCK; + size_t case_idx = idx % CASES_PER_BLOCK; + struct ccase *c = &ff->cases[block_idx][case_idx]; + write_case_to_disk (ff, c); + case_destroy (c); + } + + block_cnt = DIV_RND_UP (ff->case_cnt, CASES_PER_BLOCK); + for (idx = 0; idx < block_cnt; idx++) + free (ff->cases[idx]); + + free (ff->cases); + ff->cases = NULL; + + if (ff->mode == READ) + flush_buffer (ff); + + ll_for_each (reader, struct casereader, ll, &cf->reader_list) + reader_open_file ((struct fastfilereader *) reader); + + } + return ff->ok; +} + +/* Puts a fastfile to "sleep", that is, minimizes the resources + needed for it by closing its file descriptor and freeing its + buffer. This is useful if we need so many fastfiles that we + might not have enough memory and file descriptors to go + around. + + For simplicity, this implementation always converts the + fastfile to reader mode. If this turns out to be a problem, + with a little extra work we could also support sleeping + writers. + + Returns true if successful, false if an I/O error occurred. */ +static bool +fastfile_sleep (const struct casefile *cf_) +{ + struct fastfile *ff = (struct fastfile *) cf_; + struct casefile *cf = &ff->cf; + + fastfile_to_disk (cf); + flush_buffer (ff); + + if (ff->fd != -1) + { + safe_close (ff->fd); + ff->fd = -1; + } + if (ff->buffer != NULL) + { + free (ff->buffer); + ff->buffer = NULL; + } + + return ff->ok; +} + + +/* Returns true if an I/O error has occurred in fastfile CF. */ +static bool +fastfile_error (const struct casefile *cf) +{ + const struct fastfile *ff = (const struct fastfile *) cf; + return !ff->ok; +} + +/* Destroys fastfile CF. */ +static void +fastfile_destroy (struct casefile *cf) +{ + struct fastfile *ff = (struct fastfile *) cf; + + if (cf != NULL) + { + if (ff->cases != NULL) + { + size_t idx, block_cnt; + + case_bytes -= ff->case_cnt * ff->case_acct_size; + for (idx = 0; idx < ff->case_cnt; idx++) + { + size_t block_idx = idx / CASES_PER_BLOCK; + size_t case_idx = idx % CASES_PER_BLOCK; + struct ccase *c = &ff->cases[block_idx][case_idx]; + case_destroy (c); + } + + block_cnt = DIV_RND_UP (ff->case_cnt, CASES_PER_BLOCK); + for (idx = 0; idx < block_cnt; idx++) + free (ff->cases[idx]); + + free (ff->cases); + } + + if (ff->fd != -1) + safe_close (ff->fd); + + if (ff->file_name != NULL && remove (ff->file_name) == -1) + io_error (ff, _("%s: Removing temporary file: %s."), + ff->file_name, strerror (errno)); + free (ff->file_name); + + free (ff->buffer); + + free (ff); + } +} + + +/* Creates and returns a fastfile to store cases of VALUE_CNT + `union value's each. */ +struct casefile * +fastfile_create (size_t value_cnt) +{ + struct fastfile *ff = xzalloc (sizeof *ff); + struct casefile *cf = &ff->cf; + + casefile_register (cf, &class); + + ff->value_cnt = value_cnt; + ff->case_acct_size = (ff->value_cnt + 4) * sizeof *ff->buffer; + ff->case_cnt = 0; + ff->storage = MEMORY; + ff->mode = WRITE; + cf->being_destroyed = false; + ff->ok = true; + ff->cases = NULL; + ff->fd = -1; + ff->file_name = NULL; + ff->buffer = NULL; + ff->buffer_size = ROUND_UP (ff->value_cnt, IO_BUF_SIZE); + if (ff->value_cnt > 0 && ff->buffer_size % ff->value_cnt > 64) + ff->buffer_size = ff->value_cnt; + ff->buffer_used = 0; + + return cf; +} + + + +/* Marks FF as having encountered an I/O error. + If this is the first error on CF, reports FORMAT to the user, + doing printf()-style substitutions. */ +static void +io_error (struct fastfile *ff, const char *format, ...) +{ + if (ff->ok) + { + struct msg m; + va_list args; + + m.category = MSG_GENERAL; + m.severity = MSG_ERROR; + m.where.file_name = NULL; + m.where.line_number = -1; + va_start (args, format); + m.text = xvasprintf (format, args); + va_end (args); + + msg_emit (&m); + } + ff->ok = false; +} + +/* Calls open(), passing FILE_NAME and FLAGS, repeating as necessary + to deal with interrupted calls. */ +static int +safe_open (const char *file_name, int flags) +{ + int fd; + + do + { + fd = open (file_name, flags); + } + while (fd == -1 && errno == EINTR); + + return fd; +} + +/* Calls close(), passing FD, repeating as necessary to deal with + interrupted calls. */ +static int +safe_close (int fd) +{ + int retval; + + do + { + retval = close (fd); + } + while (retval == -1 && errno == EINTR); + + return retval; +} + + +/* Writes case C to fastfile CF's disk buffer, first flushing the buffer to + disk if it would otherwise overflow. + Returns true if successful, false if an I/O error occurred. */ +static void +write_case_to_disk (struct fastfile *ff, const struct ccase *c) +{ + if (!ff->ok) + return; + + case_to_values (c, ff->buffer + ff->buffer_used, ff->value_cnt); + ff->buffer_used += ff->value_cnt; + if (ff->buffer_used + ff->value_cnt > ff->buffer_size) + flush_buffer (ff); +} + + +/* If any bytes in FF's output buffer are used, flush them to + disk. */ +static void +flush_buffer (struct fastfile *ff) +{ + if (ff->ok && ff->buffer_used > 0) + { + if (!full_write (ff->fd, ff->buffer, + ff->buffer_size * sizeof *ff->buffer)) + io_error (ff, _("Error writing temporary file: %s."), + strerror (errno)); + ff->buffer_used = 0; + } +} + + +/* Opens a disk file for READER and seeks to the current position as indicated + by case_idx. Normally the current position is the beginning of the file, + but fastfile_to_disk may cause the file to be opened at a different + position. */ +static void +reader_open_file (struct fastfilereader *reader) +{ + struct casefile *cf = casereader_get_casefile(&reader->cr); + struct fastfile *ff = (struct fastfile *) cf; + if (!ff->ok || reader->case_idx >= ff->case_cnt) + return; + + if (ff->fd != -1) + { + reader->fd = ff->fd; + ff->fd = -1; + } + else + { + reader->fd = safe_open (ff->file_name, O_RDONLY); + if (reader->fd < 0) + io_error (ff, _("%s: Opening temporary file: %s."), + ff->file_name, strerror (errno)); + } + + if (ff->buffer != NULL) + { + reader->buffer = ff->buffer; + ff->buffer = NULL; + } + else + { + reader->buffer = xnmalloc (ff->buffer_size, sizeof *ff->buffer); + memset (reader->buffer, 0, ff->buffer_size * sizeof *ff->buffer); + } + + case_create (&reader->c, ff->value_cnt); + + reader->buffer_ofs = -1; + reader->file_ofs = -1; + seek_and_fill_buffer (reader); +} + +/* Seeks the backing file for READER to the proper position and + refreshes the buffer contents. */ +static void +seek_and_fill_buffer (struct fastfilereader *reader) +{ + struct casefile *cf = casereader_get_casefile(&reader->cr); + struct fastfile *ff = (struct fastfile *) cf; + off_t new_ofs; + + if (ff->value_cnt != 0) + { + size_t buffer_case_cnt = ff->buffer_size / ff->value_cnt; + new_ofs = ((off_t) reader->case_idx / buffer_case_cnt + * ff->buffer_size * sizeof *ff->buffer); + reader->buffer_pos = (reader->case_idx % buffer_case_cnt + * ff->value_cnt); + } + else + new_ofs = 0; + if (new_ofs != reader->file_ofs) + { + if (lseek (reader->fd, new_ofs, SEEK_SET) != new_ofs) + io_error (ff, _("%s: Seeking temporary file: %s."), + ff->file_name, strerror (errno)); + else + reader->file_ofs = new_ofs; + } + + if (ff->case_cnt > 0 && ff->value_cnt > 0 && reader->buffer_ofs != new_ofs) + fill_buffer (reader); +} + +/* Fills READER's buffer by reading a block from disk. */ +static bool +fill_buffer (struct fastfilereader *reader) +{ + struct casefile *cf = casereader_get_casefile(&reader->cr); + struct fastfile *ff = (struct fastfile *) cf; + if (ff->ok) + { + int bytes = full_read (reader->fd, reader->buffer, + ff->buffer_size * + sizeof *reader->buffer); + if (bytes < 0) + io_error (ff, _("%s: Reading temporary file: %s."), + ff->file_name, strerror (errno)); + else if (bytes != ff->buffer_size * sizeof *reader->buffer) + io_error (ff, _("%s: Temporary file ended unexpectedly."), + ff->file_name); + else + { + reader->buffer_ofs = reader->file_ofs; + reader->file_ofs += bytes; + } + } + return ff->ok; +} + +static const struct class_casefile class = + { + fastfile_destroy, + fastfile_error, + fastfile_get_value_cnt, + fastfile_get_case_cnt, + fastfile_get_reader, + fastfile_append, + + + fastfile_in_core, + fastfile_to_disk, + fastfile_sleep, + }; + +static const struct class_casereader class_reader = + { + fastfilereader_get_next_case, + fastfilereader_cnum, + fastfilereader_destroy, + }; diff --git a/src/data/fastfile.h b/src/data/fastfile.h new file mode 100644 index 00000000..8404a332 --- /dev/null +++ b/src/data/fastfile.h @@ -0,0 +1,27 @@ +/* PSPP - computes sample statistics. + Copyright (C) 2004, 2006 Free Software Foundation, Inc. + Written by Ben Pfaff . + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. + + This program 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 + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301, USA. */ + +#ifndef FASTFILE_H +#define FASTFILE_H + +#include + +struct casefile *fastfile_create (size_t value_cnt); + +#endif diff --git a/src/data/procedure.c b/src/data/procedure.c index 4dcce32b..5649a173 100644 --- a/src/data/procedure.c +++ b/src/data/procedure.c @@ -28,6 +28,7 @@ #include #include #include +#include #include #include #include @@ -168,7 +169,7 @@ multipass_procedure (bool (*proc_func) (const struct casefile *, void *aux), struct multipass_aux_data aux_data; bool ok; - aux_data.casefile = casefile_create (dict_get_next_value_idx (default_dict)); + aux_data.casefile = fastfile_create (dict_get_next_value_idx (default_dict)); aux_data.proc_func = proc_func; aux_data.aux = aux; @@ -618,7 +619,7 @@ multipass_split_case_func (const struct ccase *c, void *aux_) ok = multipass_split_output (aux); /* Start a new casefile. */ - aux->casefile = casefile_create (dict_get_next_value_idx (default_dict)); + aux->casefile = fastfile_create (dict_get_next_value_idx (default_dict)); } return casefile_append (aux->casefile, c) && ok; diff --git a/src/data/scratch-writer.c b/src/data/scratch-writer.c index 4802f4b2..13ccee8c 100644 --- a/src/data/scratch-writer.c +++ b/src/data/scratch-writer.c @@ -22,6 +22,7 @@ #include #include "case.h" #include "casefile.h" +#include "fastfile.h" #include "dictionary.h" #include "file-handle-def.h" #include "scratch-handle.h" @@ -72,7 +73,7 @@ scratch_writer_open (struct file_handle *fh, /* Create new contents. */ sh = xmalloc (sizeof *sh); sh->dictionary = scratch_dict; - sh->casefile = casefile_create (dict_get_next_value_idx (sh->dictionary)); + sh->casefile = fastfile_create (dict_get_next_value_idx (sh->dictionary)); /* Create writer. */ writer = xmalloc (sizeof *writer); @@ -115,7 +116,6 @@ scratch_writer_close (struct scratch_writer *writer) { struct casefile *cf = writer->handle->casefile; bool ok = casefile_error (cf); - casefile_mode_reader (cf); fh_close (writer->fh, "scratch file", "we"); free (writer); return ok; diff --git a/src/data/storage-stream.c b/src/data/storage-stream.c index 731fea39..74769023 100644 --- a/src/data/storage-stream.c +++ b/src/data/storage-stream.c @@ -28,6 +28,7 @@ #include #include #include +#include #include "xalloc.h" @@ -46,7 +47,7 @@ storage_sink_open (struct case_sink *sink) struct storage_stream_info *info; sink->aux = info = xmalloc (sizeof *info); - info->casefile = casefile_create (sink->value_cnt); + info->casefile = fastfile_create (sink->value_cnt); } /* Destroys storage stream represented by INFO. */ diff --git a/src/language/data-io/get.c b/src/language/data-io/get.c index fc4d2c12..789d259e 100644 --- a/src/language/data-io/get.c +++ b/src/language/data-io/get.c @@ -27,6 +27,7 @@ #include #include #include +#include #include #include #include @@ -1126,7 +1127,7 @@ cmd_match_files (void) discard_variables (); dict_compact_values (mtf.dict); - mtf.output = casefile_create (dict_get_next_value_idx (mtf.dict)); + mtf.output = fastfile_create (dict_get_next_value_idx (mtf.dict)); mtf.seq_nums = xcalloc (dict_get_var_cnt (mtf.dict), sizeof *mtf.seq_nums); case_create (&mtf.mtf_case, dict_get_next_value_idx (mtf.dict)); diff --git a/src/language/tests/casefile-test.c b/src/language/tests/casefile-test.c index 432cf388..a90cbfb7 100644 --- a/src/language/tests/casefile-test.c +++ b/src/language/tests/casefile-test.c @@ -19,6 +19,8 @@ #include #include +#include + #include #include @@ -88,7 +90,7 @@ test_casefile (int pattern, size_t value_cnt, size_t case_cnt) size_t i, j; rng = gsl_rng_alloc (gsl_rng_mt19937); - cf = casefile_create (value_cnt); + cf = fastfile_create (value_cnt); if (pattern == 5) casefile_to_disk (cf); for (i = 0; i < case_cnt; i++) @@ -136,24 +138,6 @@ test_casefile (int pattern, size_t value_cnt, size_t case_cnt) casereader_destroy (r1); if (pattern != 2) casereader_destroy (r2); - if (pattern > 3) - { - int *order; - r1 = casefile_get_random_reader (cf); - order = xmalloc (sizeof *order * case_cnt); - for (i = 0; i < case_cnt; i++) - order[i] = i; - if (case_cnt > 0) - gsl_ran_shuffle (rng, order, case_cnt, sizeof *order); - for (i = 0; i < case_cnt; i++) - { - int case_idx = order[i]; - casereader_seek (r1, case_idx); - read_and_verify_random_case (cf, r1, case_idx); - } - casereader_destroy (r1); - free (order); - } if (pattern > 2) { r1 = casefile_get_destructive_reader (cf); diff --git a/src/math/sort.c b/src/math/sort.c index 3ce5da57..732c4b86 100644 --- a/src/math/sort.c +++ b/src/math/sort.c @@ -30,6 +30,7 @@ #include #include #include +#include #include #include #include @@ -162,7 +163,7 @@ do_internal_sort (struct casereader *reader, return NULL; case_cnt = casefile_get_case_cnt (src); - dst = casefile_create (casefile_get_value_cnt (src)); + dst = fastfile_create (casefile_get_value_cnt (src)); if (case_cnt != 0) { struct indexed_case *cases = nmalloc (sizeof *cases, case_cnt); @@ -508,7 +509,7 @@ start_run (struct initial_run_state *irs) { irs->run++; irs->case_cnt = 0; - irs->casefile = casefile_create (irs->xsrt->value_cnt); + irs->casefile = fastfile_create (irs->xsrt->value_cnt); casefile_to_disk (irs->casefile); case_nullify (&irs->last_output); } @@ -673,7 +674,7 @@ merge_once (struct external_sort *xsrt, } /* Create output file. */ - output = casefile_create (xsrt->value_cnt); + output = fastfile_create (xsrt->value_cnt); casefile_to_disk (output); /* Merge. */ diff --git a/src/ui/ChangeLog b/src/ui/ChangeLog index 4a96aeb9..f4a37513 100644 --- a/src/ui/ChangeLog +++ b/src/ui/ChangeLog @@ -1,3 +1,7 @@ +Mon Jul 17 18:22:18 WST 2006 John Darrington + + * flexifile.c flexifile.h: New files. Implementations of casefiles. + Thu Mar 2 08:40:33 WST 2006 John Darrington * Moved files from src directory diff --git a/src/ui/automake.mk b/src/ui/automake.mk new file mode 100644 index 00000000..5bf78620 --- /dev/null +++ b/src/ui/automake.mk @@ -0,0 +1,13 @@ +## Process this file with automake to produce Makefile.in -*- makefile -*- + +include $(top_srcdir)/src/ui/terminal/automake.mk +if WITHGUI +include $(top_srcdir)/src/ui/gui/automake.mk +endif + + +noinst_LIBRARIES += src/ui/libuicommon.a + +src_ui_libuicommon_a_SOURCES = \ + src/ui/flexifile.c \ + src/ui/flexifile.h diff --git a/src/ui/flexifile.c b/src/ui/flexifile.c new file mode 100644 index 00000000..756f67a1 --- /dev/null +++ b/src/ui/flexifile.c @@ -0,0 +1,379 @@ +/* PSPP - computes sample statistics. + + Copyright (C) 2006 Free Software Foundation, Inc. + Written by John Darrington + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. + + This program 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 + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301, USA. */ + +#include +#include +#include +#include "flexifile.h" +#include +#include +#include + + +struct class_flexifile +{ + struct class_casefile parent; + + bool (*get_case) (const struct flexifile *, unsigned long, struct ccase *); + + bool (*insert_case) (struct flexifile *, struct ccase *, int ); + bool (*delete_cases) (struct flexifile *, int, int ); + + bool (*resize) (struct flexifile *, int, int ); +}; + +static const struct class_flexifile class; + +#define CLASS_FLEXIFILE(K) ((struct class_flexifile *) K) +#define CONST_CLASS_FLEXIFILE(K) ((const struct class_flexifile *) K) + + +/* A flexifile. */ +struct flexifile +{ + struct casefile cf; /* Parent */ + + size_t value_cnt; /* Case size in `union value's. */ + unsigned long case_cnt; /* Number of cases stored. */ + + + /* Memory storage. */ + struct ccase *cases; /* Pointer to array of cases. */ + unsigned long capacity; /* size of array in cases */ +}; + +struct class_flexifilereader +{ + struct class_casereader parent ; +}; + +static const struct class_flexifilereader class_reader; + +/* For reading out the cases in a flexifile. */ +struct flexifilereader +{ + struct casereader cr; /* Parent */ + + unsigned long case_idx; /* Case number of current case. */ + bool destructive; /* Is this a destructive reader? */ +}; + + + +#define CHUNK_SIZE 10 + +static bool +impl_get_case(const struct flexifile *ff, unsigned long casenum, + struct ccase *); +static bool +impl_insert_case (struct flexifile *ff, struct ccase *c, int posn); + +static bool +impl_delete_cases (struct flexifile *ff, int n_cases, int first); + +static bool +impl_resize (struct flexifile *ff, int n_values, int posn); + + +/* Gets a case, for which writing may not be safe */ +bool +flexifile_get_case(const struct flexifile *ff, unsigned long casenum, + struct ccase *c) +{ + const struct class_flexifile *class = + CONST_CLASS_FLEXIFILE (CONST_CASEFILE(ff)->class) ; + + return class->get_case(ff, casenum, c); +} + + +/* Insert N_VALUES before POSN. + If N_VALUES is negative, then deleted -N_VALUES instead +*/ +bool +flexifile_resize (struct flexifile *ff, int n_values, int posn) +{ + const struct class_flexifile *class = + CONST_CLASS_FLEXIFILE (CONST_CASEFILE(ff)->class) ; + + return class->resize(ff, n_values, posn); +} + + + +bool +flexifile_insert_case (struct flexifile *ff, struct ccase *c, int posn) +{ + const struct class_flexifile *class = + CONST_CLASS_FLEXIFILE (CONST_CASEFILE(ff)->class) ; + + return class->insert_case(ff, c, posn); +} + + +bool +flexifile_delete_cases (struct flexifile *ff, int n_cases, int first) +{ + const struct class_flexifile *class = + CONST_CLASS_FLEXIFILE (CONST_CASEFILE(ff)->class) ; + + return class->delete_cases (ff, n_cases, first); +} + + +static unsigned long +flexifile_get_case_cnt (const struct casefile *cf) +{ + return FLEXIFILE(cf)->case_cnt; +} + +static size_t +flexifile_get_value_cnt (const struct casefile *cf) +{ + return FLEXIFILE(cf)->value_cnt; +} + + +static void +flexifile_destroy (struct casefile *cf) +{ + int i ; + for ( i = 0 ; i < FLEXIFILE(cf)->case_cnt; ++i ) + case_destroy( &FLEXIFILE(cf)->cases[i]); + + free(FLEXIFILE(cf)->cases); +} + +static void +grow(struct flexifile *ff) +{ + ff->capacity += CHUNK_SIZE; + ff->cases = xrealloc(ff->cases, ff->capacity * sizeof ( *ff->cases) ); +} + +static bool +flexifile_append (struct casefile *cf, const struct ccase *c) +{ + struct flexifile *ff = FLEXIFILE(cf); + + if (ff->case_cnt >= ff->capacity) + grow(ff); + + case_clone (&ff->cases[ff->case_cnt++], c); + + return true; +} + + +static struct ccase * +flexifilereader_get_next_case (struct casereader *cr) +{ + struct flexifilereader *ffr = FLEXIFILEREADER(cr); + struct flexifile *ff = FLEXIFILE(casereader_get_casefile(cr)); + + if ( ffr->case_idx >= ff->case_cnt) + return NULL; + + return &ff->cases[ffr->case_idx++]; +} + +static void +flexifilereader_destroy(struct casereader *r) +{ + free(r); +} + +static struct casereader * +flexifile_get_reader (const struct casefile *cf_) +{ + struct casefile *cf = (struct casefile *) cf_; + struct flexifilereader *ffr = xzalloc (sizeof *ffr); + struct casereader *reader = (struct casereader *) ffr; + + casereader_register (cf, reader, CLASS_CASEREADER(&class_reader)); + + return reader; +} + +static bool +flexifile_in_core(const struct casefile *cf UNUSED) +{ + /* Always in memory */ + return true; +} + +static bool +flexifile_error (const struct casefile *cf UNUSED ) +{ + return false; +} + + +struct casefile * +flexifile_create (size_t value_cnt) +{ + struct flexifile *ff = xzalloc (sizeof *ff); + struct casefile *cf = (struct casefile *) ff; + + casefile_register (cf, (struct class_casefile *) &class); + + ff->value_cnt = value_cnt; + + ff->cases = xzalloc(sizeof (struct ccase *) * CHUNK_SIZE); + ff->capacity = CHUNK_SIZE; + + return cf; +} + +static const struct class_flexifile class = { + { + flexifile_destroy, + flexifile_error, + flexifile_get_value_cnt, + flexifile_get_case_cnt, + flexifile_get_reader, + flexifile_append, + + flexifile_in_core, + 0, /* to_disk */ + 0 /* sleep */ + }, + + impl_get_case , + impl_insert_case , + impl_delete_cases, + impl_resize, +}; + + +static const struct class_flexifilereader class_reader = + { + { + flexifilereader_get_next_case, + 0, /* cnum */ + flexifilereader_destroy + } + }; + + +/* Implementations of class methods */ + +static bool +impl_get_case(const struct flexifile *ff, unsigned long casenum, + struct ccase *c) +{ + if ( casenum >= ff->case_cnt) + return false; + + case_clone (c, &ff->cases[casenum]); + + return true; +} + +#if DEBUGGING +static void +dumpcasedata(struct ccase *c) +{ + int i; + for ( i = 0 ; i < c->case_data->value_cnt * MAX_SHORT_STRING; ++i ) + putchar(c->case_data->values->s[i]); + putchar('\n'); +} +#endif + +static bool +impl_resize (struct flexifile *ff, int n_values, int posn) +{ + int i; + + for( i = 0 ; i < ff->case_cnt ; ++i ) + { + struct ccase c; + case_create (&c, ff->value_cnt + n_values); + + case_copy (&c, 0, &ff->cases[i], 0, posn); + if ( n_values > 0 ) + memset (case_data_rw(&c, posn), ' ', n_values * MAX_SHORT_STRING) ; + case_copy (&c, posn + n_values, + &ff->cases[i], posn, ff->value_cnt - posn); + + case_destroy (&ff->cases[i]); + ff->cases[i] = c; + } + + ff->value_cnt += n_values; + + return true; +} + +static bool +impl_insert_case (struct flexifile *ff, struct ccase *c, int posn) +{ + int i; + struct ccase blank; + + assert (ff); + + if ( posn > ff->case_cnt ) + return false; + + if ( posn >= ff->capacity ) + grow(ff); + + case_create(&blank, ff->value_cnt); + + flexifile_append(CASEFILE(ff), &blank); + + case_destroy(&blank); + + /* Shift the existing cases down one */ + for ( i = ff->case_cnt ; i > posn; --i) + case_move(&ff->cases[i], &ff->cases[i-1]); + + case_clone (&ff->cases[posn], c); + + return true; +} + + +static bool +impl_delete_cases (struct flexifile *ff, int n_cases, int first) +{ + int i; + + if ( ff->case_cnt < first + n_cases ) + return false; + + for ( i = first ; i < first + n_cases; ++i ) + case_destroy (&ff->cases[i]); + + /* Shift the cases up by N_CASES */ + for ( i = first; i < ff->case_cnt - n_cases; ++i ) + { + case_move (&ff->cases[i], &ff->cases[i+ n_cases]); + } + + ff->case_cnt -= n_cases; + + return true; +} + + + diff --git a/src/ui/flexifile.h b/src/ui/flexifile.h new file mode 100644 index 00000000..673c7d51 --- /dev/null +++ b/src/ui/flexifile.h @@ -0,0 +1,47 @@ +/* PSPP - computes sample statistics. + Copyright (C) 2006 Free Software Foundation, Inc. + Written by John Darrington + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. + + This program 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 + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301, USA. */ + +#ifndef FLEXIFILE_H +#define FLEXIFILE_H + +#include +#include +#include + +struct ccase; +struct casefile; +struct casereader; +struct flexifile; +struct flexifilereader; + +#define FLEXIFILE(CF) ( (struct flexifile *) CF) +#define FLEXIFILEREADER(CR) ( (struct flexifilereader *) CR) + +struct casefile *flexifile_create (size_t value_cnt); + +bool flexifile_get_case(const struct flexifile *ff, unsigned long casenum, + struct ccase *const c); + +bool flexifile_resize (struct flexifile *ff, int n_values, int posn); + +bool flexifile_insert_case (struct flexifile *ff, struct ccase *c, int posn); +bool flexifile_delete_cases (struct flexifile *ff, int n_cases, int first); + + +#endif diff --git a/src/ui/gui/ChangeLog b/src/ui/gui/ChangeLog index e2555263..d24ce288 100644 --- a/src/ui/gui/ChangeLog +++ b/src/ui/gui/ChangeLog @@ -1,3 +1,9 @@ +Mon Jul 17 18:21:29 WST 2006 John Darrington + + * automake.mk menu-actions.c psppire-case-file.c psppire-case-file.h + psppire-data-store.c psppire-dict.c psppire-dict.h psppire-variable.c + psppire.c psppire.glade: Adjusted code to use the new flexifile object. + Sat Jul 15 11:27:15 WST 2006 John Darrington * psppire.c psppire.glade automake.mk icons/* : Added toolbar icons where diff --git a/src/ui/gui/automake.mk b/src/ui/gui/automake.mk index ca6c8fc9..be59fed5 100644 --- a/src/ui/gui/automake.mk +++ b/src/ui/gui/automake.mk @@ -11,6 +11,7 @@ src_ui_gui_psppire_LDADD = \ $(GTK_LIBS) \ $(GLADE_LIBS) \ $(top_builddir)/lib/gtksheet/libgtksheet.a \ + $(top_builddir)/src/ui/libuicommon.a \ $(top_builddir)/src/math/libpspp_math.a \ $(top_builddir)/src/data/libdata.a \ $(top_builddir)/src/libpspp/libpspp.a \ diff --git a/src/ui/gui/menu-actions.c b/src/ui/gui/menu-actions.c index d1a76802..98d00f63 100644 --- a/src/ui/gui/menu-actions.c +++ b/src/ui/gui/menu-actions.c @@ -196,9 +196,7 @@ load_system_file(const gchar *file_name) g_warning("Cannot write case to casefile\n"); break; } - case_destroy(&c); - } sfm_close_reader(reader); @@ -345,58 +343,61 @@ on_quit1_activate (GtkMenuItem *menuitem, void -on_cut1_activate (GtkMenuItem *menuitem, - gpointer user_data) -{ - -} - - -void -on_copy1_activate (GtkMenuItem *menuitem, - gpointer user_data) +on_clear_activate (GtkMenuItem *menuitem, + gpointer user_data) { + GtkNotebook *notebook = GTK_NOTEBOOK(get_widget_assert(xml, "notebook1")); + gint page = -1; -} - - -void -on_paste1_activate (GtkMenuItem *menuitem, - gpointer user_data) -{ - -} - -/* Fill a case with SYSMIS for numeric and whitespace for string - variables respectively */ -static gboolean -blank_case(struct ccase *cc, gpointer _dict) -{ - gint i; - PsppireDict *dict = _dict; + page = gtk_notebook_get_current_page(notebook); - for(i = 0 ; i < psppire_dict_get_var_cnt(dict); ++i ) + switch (page) { - union value *val ; - - const struct PsppireVariable *var = psppire_dict_get_variable(dict, i); - - gint idx = psppire_variable_get_fv(var); - - val = case_data_rw(cc, idx) ; + case PAGE_DATA_SHEET: + { + GtkSheet *data_sheet = GTK_SHEET(get_widget_assert(xml, "data_sheet")); + PsppireDataStore *data_store = + PSPPIRE_DATA_STORE(gtk_sheet_get_model(data_sheet)); + - if ( psppire_variable_get_type(var) == ALPHA ) - memset(val->s, ' ', psppire_variable_get_width(var)); - else - val->f = SYSMIS; + switch ( data_sheet->state ) + { + case GTK_SHEET_ROW_SELECTED: + psppire_case_file_delete_cases(data_store->case_file, + data_sheet->range.rowi + - data_sheet->range.row0 + 1, + data_sheet->range.row0); + break; + case GTK_SHEET_COLUMN_SELECTED: + { + gint fv; + struct PsppireVariable *pv = + psppire_dict_get_variable(the_dictionary, + data_sheet->range.col0); + fv = psppire_variable_get_fv(pv); + + + psppire_dict_delete_variables(the_dictionary, + data_sheet->range.col0, + 1); + + psppire_case_file_insert_values(data_store->case_file, + -1, fv); + } + break; + default: + gtk_sheet_cell_clear(data_sheet, + data_sheet->active_cell.row, + data_sheet->active_cell.col); + break; + } - case_unshare(cc); + } + break; } - return TRUE; } - void on_insert1_activate (GtkMenuItem *menuitem, gpointer user_data) @@ -435,6 +436,7 @@ on_insert1_activate (GtkMenuItem *menuitem, } } +#if 0 void on_delete1_activate (GtkMenuItem *menuitem, gpointer user_data) @@ -475,7 +477,7 @@ on_delete1_activate (GtkMenuItem *menuitem, break; } } - +#endif void on_about1_activate(GtkMenuItem *menuitem, @@ -601,15 +603,22 @@ static GtkNotebook *notebook = 0; static void switch_menus(gint page) { + GtkWidget *insert_variable = get_widget_assert(xml, "insert-variable"); + GtkWidget *insert_cases = get_widget_assert(xml, "insert-cases"); + switch (page) { case PAGE_VAR_SHEET: gtk_widget_hide(menuitems[PAGE_VAR_SHEET]); gtk_widget_show(menuitems[PAGE_DATA_SHEET]); + gtk_widget_set_sensitive(insert_variable, TRUE); + gtk_widget_set_sensitive(insert_cases, FALSE); break; case PAGE_DATA_SHEET: gtk_widget_show(menuitems[PAGE_VAR_SHEET]); gtk_widget_hide(menuitems[PAGE_DATA_SHEET]); + gtk_widget_set_sensitive(insert_variable, FALSE); + gtk_widget_set_sensitive(insert_cases, TRUE); break; default: g_assert_not_reached(); @@ -736,3 +745,44 @@ on_sort_cases_activate (GtkMenuItem *menuitem, break; } } + + +static void +insert_case(void) +{ + gint row, col; + PsppireDataStore *data_store ; + GtkSheet *data_sheet = GTK_SHEET(get_widget_assert(xml, "data_sheet")); + + data_store = PSPPIRE_DATA_STORE(gtk_sheet_get_model(data_sheet)); + + gtk_sheet_get_active_cell(data_sheet, &row, &col); + + psppire_case_file_insert_case(data_store->case_file, row); +} + +void +on_insert_case_clicked (GtkButton *button, gpointer user_data) +{ + insert_case(); +} + +void +on_insert_cases (GtkMenuItem *menuitem, gpointer user_data) +{ + insert_case(); +} + + +void +on_insert_variable (GtkMenuItem *menuitem, gpointer user_data) +{ + gint row, col; + GtkSheet *var_sheet = GTK_SHEET(get_widget_assert(xml, "variable_sheet")); + + gtk_sheet_get_active_cell(var_sheet, &row, &col); + + psppire_dict_insert_variable(the_dictionary, row, NULL); +} + + diff --git a/src/ui/gui/psppire-case-file.c b/src/ui/gui/psppire-case-file.c index 0c8e52a9..f869e4b0 100644 --- a/src/ui/gui/psppire-case-file.c +++ b/src/ui/gui/psppire-case-file.c @@ -28,10 +28,11 @@ #include #include +#include #include #include - #include +#include /* --- prototypes --- */ static void psppire_case_file_class_init (PsppireCaseFileClass *class); @@ -132,8 +133,8 @@ psppire_case_file_finalize (GObject *object) { PsppireCaseFile *cf = PSPPIRE_CASE_FILE (object); - if ( cf->casefile) - casefile_destroy(cf->casefile); + if ( cf->flexifile) + casefile_destroy(cf->flexifile); G_OBJECT_CLASS (parent_class)->finalize (object); } @@ -141,7 +142,7 @@ psppire_case_file_finalize (GObject *object) static void psppire_case_file_init (PsppireCaseFile *cf) { - cf->casefile = 0; + cf->flexifile = 0; } /** @@ -151,16 +152,55 @@ psppire_case_file_init (PsppireCaseFile *cf) * Creates a new #PsppireCaseFile. */ PsppireCaseFile* -psppire_case_file_new (gint var_cnt) +psppire_case_file_new (gint val_cnt) { PsppireCaseFile *cf = g_object_new (G_TYPE_PSPPIRE_CASE_FILE, NULL); - cf->casefile = casefile_create(var_cnt); + cf->flexifile = flexifile_create(val_cnt); return cf; } +gboolean +psppire_case_file_delete_cases(PsppireCaseFile *cf, gint n_cases, gint first) +{ + int result; + + g_return_val_if_fail(cf, FALSE); + g_return_val_if_fail(cf->flexifile, FALSE); + + result = flexifile_delete_cases(FLEXIFILE(cf->flexifile), n_cases, first); + + g_signal_emit(cf, signal[CASES_DELETED], 0, n_cases, first); + + return result; +} + +/* Insert a blank case to the case file */ +gboolean +psppire_case_file_insert_case(PsppireCaseFile *cf, + gint posn) +{ + bool result ; + struct ccase cc; + + g_return_val_if_fail(cf, FALSE); + g_return_val_if_fail(cf->flexifile, FALSE); + + case_create (&cc, casefile_get_value_cnt(cf->flexifile)); + + result = flexifile_insert_case(FLEXIFILE(cf->flexifile), &cc, posn); + + case_destroy (&cc); + + if ( result ) + g_signal_emit(cf, signal[CASE_INSERTED], 0, posn); + else + g_warning("Cannot insert case at position %d\n", posn); + + return result; +} /* Append a case to the case file */ gboolean @@ -171,11 +211,11 @@ psppire_case_file_append_case(PsppireCaseFile *cf, gint posn ; g_return_val_if_fail(cf, FALSE); - g_return_val_if_fail(cf->casefile, FALSE); + g_return_val_if_fail(cf->flexifile, FALSE); - posn = casefile_get_case_cnt(cf->casefile); + posn = casefile_get_case_cnt(cf->flexifile); - result = casefile_append(cf->casefile, c); + result = casefile_append(cf->flexifile, c); g_signal_emit(cf, signal[CASE_INSERTED], 0, posn); @@ -188,10 +228,10 @@ psppire_case_file_get_case_count(const PsppireCaseFile *cf) { g_return_val_if_fail(cf, FALSE); - if ( ! cf->casefile) + if ( ! cf->flexifile) return 0; - return casefile_get_case_cnt(cf->casefile); + return casefile_get_case_cnt(cf->flexifile); } /* Return the IDXth value from case CASENUM. @@ -202,14 +242,16 @@ psppire_case_file_get_value(const PsppireCaseFile *cf, gint casenum, gint idx) { const union value *v; struct ccase c; - struct casereader *reader = casefile_get_random_reader (cf->casefile); - casereader_seek(reader, casenum); + g_return_val_if_fail(cf, NULL); + g_return_val_if_fail(cf->flexifile, NULL); + + g_return_val_if_fail(idx < casefile_get_value_cnt(cf->flexifile), NULL); - casereader_read(reader, &c); + flexifile_get_case(FLEXIFILE(cf->flexifile), casenum, &c); v = case_data(&c, idx); - casereader_destroy(reader); + case_destroy(&c); return v; @@ -218,22 +260,56 @@ psppire_case_file_get_value(const PsppireCaseFile *cf, gint casenum, gint idx) void psppire_case_file_clear(PsppireCaseFile *cf) { - casefile_destroy(cf->casefile); - cf->casefile = 0; + casefile_destroy(cf->flexifile); + cf->flexifile = 0; g_signal_emit(cf, signal[CASES_DELETED], 0, 0, -1); } -/* Set the IDXth value of case C using FF and DATA */ +/* Set the IDXth value of case C to SYSMIS/EMPTY */ gboolean psppire_case_file_set_value(PsppireCaseFile *cf, gint casenum, gint idx, + union value *v, gint width) +{ + struct ccase cc ; + int bytes; + + g_return_val_if_fail(cf, FALSE); + g_return_val_if_fail(cf->flexifile, FALSE); + + g_return_val_if_fail(idx < casefile_get_value_cnt(cf->flexifile), FALSE); + + if ( ! flexifile_get_case(FLEXIFILE(cf->flexifile), casenum, &cc) ) + return FALSE; + + if ( width == 0 ) + bytes = MAX_SHORT_STRING; + else + bytes = DIV_RND_UP(width, MAX_SHORT_STRING) * MAX_SHORT_STRING ; + + /* Cast away const in flagrant abuse of the casefile */ + memcpy((union value *)case_data(&cc, idx), v, bytes); + + g_signal_emit(cf, signal[CASE_CHANGED], 0, casenum); + + return TRUE; +} + + + +/* Set the IDXth value of case C using D_IN */ +gboolean +psppire_case_file_data_in(PsppireCaseFile *cf, gint casenum, gint idx, struct data_in *d_in) { struct ccase cc ; - struct casereader *reader = casefile_get_random_reader (cf->casefile); + g_return_val_if_fail(cf, FALSE); + g_return_val_if_fail(cf->flexifile, FALSE); + + g_return_val_if_fail(idx < casefile_get_value_cnt(cf->flexifile), FALSE); - casereader_seek(reader, casenum); - casereader_read(reader, &cc); + if ( ! flexifile_get_case(FLEXIFILE(cf->flexifile), casenum, &cc) ) + return FALSE; /* Cast away const in flagrant abuse of the casefile */ d_in->v = (union value *) case_data(&cc, idx); @@ -241,9 +317,6 @@ psppire_case_file_set_value(PsppireCaseFile *cf, gint casenum, gint idx, if ( ! data_in(d_in) ) g_warning("Cant set value\n"); - case_destroy(&cc); - casereader_destroy(reader); - g_signal_emit(cf, signal[CASE_CHANGED], 0, casenum); return TRUE; @@ -253,12 +326,45 @@ psppire_case_file_set_value(PsppireCaseFile *cf, gint casenum, gint idx, void psppire_case_file_sort(PsppireCaseFile *cf, const struct sort_criteria *sc) { + struct ccase cc; gint c; - struct casereader *reader = casefile_get_reader(cf->casefile); - cf->casefile = sort_execute(reader, sc); + struct casefile *cfile; + struct casereader *reader = casefile_get_reader(cf->flexifile); + const int value_cnt = casefile_get_value_cnt(cf->flexifile); + + cfile = sort_execute(reader, sc); + + casefile_destroy(cf->flexifile); + + /* Copy casefile into flexifile */ + + reader = casefile_get_destructive_reader(cfile); + cf->flexifile = flexifile_create(value_cnt); + while(casereader_read(reader, &cc)) + casefile_append(cf->flexifile, &cc); + /* FIXME: Need to have a signal to change a range of cases, instead of calling a signal many times */ - for ( c = 0 ; c < casefile_get_case_cnt(cf->casefile) ; ++c ) + for ( c = 0 ; c < casefile_get_case_cnt(cf->flexifile) ; ++c ) g_signal_emit(cf, signal[CASE_CHANGED], 0, c); } + + +/* Resize the cases in the casefile, by inserting N_VALUES into every + one of them. */ +gboolean +psppire_case_file_insert_values(PsppireCaseFile *cf, + gint n_values, gint before) +{ + g_return_val_if_fail(cf, FALSE); + + if ( ! cf->flexifile ) + { + cf->flexifile = flexifile_create(n_values); + return TRUE; + } + + return flexifile_resize(FLEXIFILE(cf->flexifile), n_values, before); +} + diff --git a/src/ui/gui/psppire-case-file.h b/src/ui/gui/psppire-case-file.h index 652ee541..745b7098 100644 --- a/src/ui/gui/psppire-case-file.h +++ b/src/ui/gui/psppire-case-file.h @@ -47,13 +47,13 @@ typedef struct _PsppireCaseFile PsppireCaseFile; typedef struct _PsppireCaseFileClass PsppireCaseFileClass; struct ccase; -struct casefile; +struct casefilefile; struct _PsppireCaseFile { GObject parent; - struct casefile *casefile; + struct casefile *flexifile; }; @@ -79,11 +79,23 @@ const union value * psppire_case_file_get_value(const PsppireCaseFile *cf, struct data_in; -gboolean psppire_case_file_set_value(PsppireCaseFile *cf, gint c, gint idx, +gboolean psppire_case_file_data_in(PsppireCaseFile *cf, gint c, gint idx, struct data_in *d_in); +gboolean psppire_case_file_set_value(PsppireCaseFile *cf, gint casenum, + gint idx, union value *v, gint width); + void psppire_case_file_clear(PsppireCaseFile *cf); + +gboolean psppire_case_file_delete_cases(PsppireCaseFile *cf, gint n_rows, + gint first); + +gboolean psppire_case_file_insert_case(PsppireCaseFile *cf, gint row); + + +gboolean psppire_case_file_insert_values(PsppireCaseFile *cf, gint n_values, gint before); + struct sort_criteria; void psppire_case_file_sort(PsppireCaseFile *cf, const struct sort_criteria *); diff --git a/src/ui/gui/psppire-data-store.c b/src/ui/gui/psppire-data-store.c index 6df37973..6c82a30b 100644 --- a/src/ui/gui/psppire-data-store.c +++ b/src/ui/gui/psppire-data-store.c @@ -268,31 +268,49 @@ static void insert_variable_callback(GObject *obj, gint var_num, gpointer data) { PsppireDataStore *store; + gint posn; g_return_if_fail (data); store = PSPPIRE_DATA_STORE(data); - /* - g_sheet_model_range_changed (G_SHEET_MODEL(store), - casenum, -1, - psppire_case_array_get_n_cases(store->cases), - -1); - */ + if ( var_num > 0 ) + { + struct PsppireVariable *variable; + variable = psppire_dict_get_variable(store->dict, var_num); -#if 0 - psppire_case_array_resize(store->cases, - dict_get_next_value_idx (store->dict->dict)); -#endif + posn = psppire_variable_get_fv(variable); + } + else + { + posn = 0; + } + + psppire_case_file_insert_values(store->case_file, 1, posn); g_sheet_column_columns_changed(G_SHEET_COLUMN(store), var_num, 1); - g_sheet_model_columns_inserted (G_SHEET_MODEL(store), var_num, 1); } +static void +dict_size_change_callback(GObject *obj, + gint posn, gint adjustment, gpointer data) +{ + PsppireDataStore *store ; + + g_return_if_fail (data); + + store = PSPPIRE_DATA_STORE(data); + + /* + if ( adjustment > 0 ) + */ + psppire_case_file_insert_values (store->case_file, adjustment, posn); +} + /** @@ -356,7 +374,6 @@ psppire_data_store_set_dictionary(PsppireDataStore *data_store, PsppireDict *dic G_CALLBACK(changed_case_callback), data_store); - g_signal_connect(dict, "variable-inserted", G_CALLBACK(insert_variable_callback), data_store); @@ -365,6 +382,10 @@ psppire_data_store_set_dictionary(PsppireDataStore *data_store, PsppireDict *dic G_CALLBACK(delete_variables_callback), data_store); + g_signal_connect (dict, "dict-size-changed", + G_CALLBACK(dict_size_change_callback), + data_store); + /* The entire model has changed */ g_sheet_model_range_changed (G_SHEET_MODEL(data_store), -1, -1, -1, -1); @@ -406,6 +427,8 @@ psppire_data_store_get_string(const GSheetModel *model, gint row, gint column) v = psppire_case_file_get_value(store->case_file, row, idx); + g_return_val_if_fail(v, NULL); + if ( store->show_labels) { const struct val_labs * vl = psppire_variable_get_value_labels(pv); @@ -455,8 +478,8 @@ psppire_data_store_clear_datum(GSheetModel *model, else memcpy(v.s, "", MAX_SHORT_STRING); - psppire_case_file_set_value(store->case_file, row, index, &v); - + psppire_case_file_set_value(store->case_file, row, index, &v, + psppire_variable_get_width(pv)); return TRUE; } @@ -500,15 +523,7 @@ psppire_data_store_set_string(GSheetModel *model, d_in.format = * psppire_variable_get_write_spec(pv); d_in.flags = 0; - /* - if ( ! data_in(&d_in) ) - { - g_warning("Cannot encode string"); - return FALSE; - } - */ - - psppire_case_file_set_value(store->case_file, row, index, &d_in) ; + psppire_case_file_data_in(store->case_file, row, index, &d_in) ; } return TRUE; @@ -541,19 +556,6 @@ psppire_data_store_show_labels(PsppireDataStore *store, gboolean show_labels) -static gboolean -write_case(const struct ccase *cc, - gpointer aux) -{ - struct sfm_writer *writer = aux; - - if ( ! sfm_write_case(writer, cc) ) - return FALSE; - - - return TRUE; -} - void psppire_data_store_create_system_file(PsppireDataStore *store, struct file_handle *handle) diff --git a/src/ui/gui/psppire-dict.c b/src/ui/gui/psppire-dict.c index 10de849c..435be90c 100644 --- a/src/ui/gui/psppire-dict.c +++ b/src/ui/gui/psppire-dict.c @@ -48,6 +48,7 @@ static void dictionary_tree_model_init(GtkTreeModelIface *iface); static GObjectClass *parent_class = NULL; enum {VARIABLE_CHANGED, + VARIABLE_RESIZED, VARIABLE_INSERTED, VARIABLES_DELETED, n_SIGNALS}; @@ -146,6 +147,19 @@ psppire_dict_class_init (PsppireDictClass *class) G_TYPE_INT, G_TYPE_INT); + + signal[VARIABLE_RESIZED] = + g_signal_new ("dict-size-changed", + G_TYPE_FROM_CLASS(class), + G_SIGNAL_RUN_FIRST, + 0, + NULL, NULL, + gtkextra_VOID__INT_INT, + G_TYPE_NONE, + 2, + G_TYPE_INT, + G_TYPE_INT); + } static void @@ -299,6 +313,7 @@ psppire_dict_delete_variables(PsppireDict *d, gint first, gint n) var = dict_get_var(d->dict, first); dict_delete_var (d->dict, var); } + dict_compact_values(d->dict); g_signal_emit(d, signal[VARIABLES_DELETED], 0, first, idx ); } @@ -446,6 +461,31 @@ psppire_dict_get_next_value_idx (const PsppireDict *dict) } +void +psppire_dict_resize_variable(PsppireDict *d, const struct PsppireVariable *pv, + gint old_size, gint new_size) +{ + gint fv; + g_return_if_fail (d); + g_return_if_fail (d->dict); + + if ( old_size == new_size ) + return ; + + pv->v->nv = new_size; + + dict_compact_values(d->dict); + + fv = psppire_variable_get_fv(pv); + + g_signal_emit(d, signal[VARIABLE_RESIZED], 0, + fv + old_size, + new_size - old_size ); +} + + + + /* Tree Model Stuff */ diff --git a/src/ui/gui/psppire-dict.h b/src/ui/gui/psppire-dict.h index 218cae0d..e71ef204 100644 --- a/src/ui/gui/psppire-dict.h +++ b/src/ui/gui/psppire-dict.h @@ -102,6 +102,10 @@ void psppire_dict_delete_variables(PsppireDict *d, gint first, gint n); /* Insert a new variable at posn IDX */ void psppire_dict_insert_variable(PsppireDict *d, gint idx, const gchar *name); +void psppire_dict_resize_variable(PsppireDict *d, + const struct PsppireVariable *pv, + gint old_size, gint new_size); + gboolean psppire_dict_check_name(const PsppireDict *dict, const gchar *name, gboolean report); diff --git a/src/ui/gui/psppire-variable.c b/src/ui/gui/psppire-variable.c index 3fa71ea3..5c884a3f 100644 --- a/src/ui/gui/psppire-variable.c +++ b/src/ui/gui/psppire-variable.c @@ -25,6 +25,8 @@ #include #include +#include + #include "psppire-variable.h" #include "psppire-dict.h" @@ -115,8 +117,21 @@ psppire_variable_set_width(struct PsppireVariable *pv, gint width) fmt.w = width; if ( pv->v->type == ALPHA ) + { + gint old_var_cnt , new_var_cnt ; + + if ( pv->v->width == 0 ) + old_var_cnt = 1; + else + old_var_cnt = DIV_RND_UP(pv->v->width, MAX_SHORT_STRING); + + new_var_cnt = DIV_RND_UP(width, MAX_SHORT_STRING); pv->v->width = width; + psppire_dict_resize_variable(pv->dict, pv, + old_var_cnt, new_var_cnt); + } + return psppire_variable_set_format(pv, &fmt); } @@ -124,15 +139,30 @@ psppire_variable_set_width(struct PsppireVariable *pv, gint width) gboolean psppire_variable_set_type(struct PsppireVariable *pv, int type) { + gint old_var_cnt , new_var_cnt ; + g_return_val_if_fail(pv, FALSE); g_return_val_if_fail(pv->dict, FALSE); g_return_val_if_fail(pv->v, FALSE); pv->v->type = type; + if ( pv->v->width == 0 ) + old_var_cnt = 1; + else + old_var_cnt = DIV_RND_UP(pv->v->width, MAX_SHORT_STRING); + if ( type == NUMERIC ) pv->v->width = 0; + if ( pv->v->width == 0 ) + new_var_cnt = 1; + else + new_var_cnt = DIV_RND_UP(pv->v->width, MAX_SHORT_STRING); + + psppire_dict_resize_variable(pv->dict, pv, + old_var_cnt, new_var_cnt); + psppire_dict_var_changed(pv->dict, pv->v->index); return TRUE; } diff --git a/src/ui/gui/psppire.c b/src/ui/gui/psppire.c index 162f5bbc..67ace893 100644 --- a/src/ui/gui/psppire.c +++ b/src/ui/gui/psppire.c @@ -111,11 +111,8 @@ main(int argc, char *argv[]) glade_init(); - settings_init(); - /* - set_pspp_locale("da_DK"); - */ + settings_init(); message_dialog_init(); diff --git a/src/ui/gui/psppire.glade b/src/ui/gui/psppire.glade index 798e6216..5b17aa1d 100644 --- a/src/ui/gui/psppire.glade +++ b/src/ui/gui/psppire.glade @@ -109,45 +109,73 @@ True + False gtk-cut True - True + False gtk-copy True - True + False gtk-paste True - - + True - gtk-delete - True - + False + Paste _Variables + True - + True - _Insert + Cl_ear True - + + + + + + + + True + + + + + + True + False + _Find + True + + + + True + gtk-find + 1 + 0.5 + 0.5 + 0 + 0 + + @@ -254,20 +282,21 @@ - + True False Insert Variable True + - + True - False Insert Cases True + @@ -279,7 +308,7 @@ - + True gtk-jump-to 1 @@ -611,6 +640,7 @@ True True False + False diff --git a/src/ui/terminal/automake.mk b/src/ui/terminal/automake.mk index b40179bb..ecdcb3c2 100644 --- a/src/ui/terminal/automake.mk +++ b/src/ui/terminal/automake.mk @@ -20,6 +20,7 @@ src_ui_terminal_pspp_LDADD = \ src/ui/terminal/libui.a \ src/language/liblanguage.a \ src/language/tests/libtests.a \ + src/ui/libuicommon.a \ src/language/utilities/libutilities.a \ src/language/control/libcontrol.a \ src/language/expressions/libexpressions.a \ -- 2.30.2