Patch #5209
authorJohn Darrington <john@darrington.wattle.id.au>
Mon, 17 Jul 2006 10:45:42 +0000 (10:45 +0000)
committerJohn Darrington <john@darrington.wattle.id.au>
Mon, 17 Jul 2006 10:45:42 +0000 (10:45 +0000)
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.

32 files changed:
po/de.po
po/pspp.pot
src/automake.mk
src/data/ChangeLog
src/data/automake.mk
src/data/casefile-private.h [new file with mode: 0644]
src/data/casefile.c
src/data/casefile.h
src/data/fastfile.c [new file with mode: 0644]
src/data/fastfile.h [new file with mode: 0644]
src/data/procedure.c
src/data/scratch-writer.c
src/data/storage-stream.c
src/language/data-io/get.c
src/language/tests/casefile-test.c
src/math/sort.c
src/ui/ChangeLog
src/ui/automake.mk [new file with mode: 0644]
src/ui/flexifile.c [new file with mode: 0644]
src/ui/flexifile.h [new file with mode: 0644]
src/ui/gui/ChangeLog
src/ui/gui/automake.mk
src/ui/gui/menu-actions.c
src/ui/gui/psppire-case-file.c
src/ui/gui/psppire-case-file.h
src/ui/gui/psppire-data-store.c
src/ui/gui/psppire-dict.c
src/ui/gui/psppire-dict.h
src/ui/gui/psppire-variable.c
src/ui/gui/psppire.c
src/ui/gui/psppire.glade
src/ui/terminal/automake.mk

index c105fe7100c96881d3a498947e99d1007b493684..47c0180e2b8e6aaecfc01e7e55322db868783c93 100644 (file)
--- 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 <john@darrington.wattle.id.au>\n"
 "Language-Team: German <pspp-dev@gnu.org>\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"
index 803e416ec5ec30a1c29444b8846b554205f0f89d..add94089e094af2230ea7bea6c4e3eacd05d89a9 100644 (file)
@@ -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 <EMAIL@ADDRESS>\n"
 "Language-Team: LANGUAGE <LL@li.org>\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 ""
 
index b63a06919273f96a8b5a2c8741aaedfd72bd92cc..7c52887bf884d773171c4b5d3f9fb2e41be6bb44 100644 (file)
@@ -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)\"
index 0d10921c52e2dec462b75937dbf6021f3ebffb7d..694d35776aa581f190913960d343d2f63ac681b3 100644 (file)
@@ -1,3 +1,9 @@
+Mon Jul 17 18:26:21 WST 2006 John Darrington <john@darrington.wattle.id.au>
+
+       * 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  <blp@gnu.org>
 
        * procedure.c (internal_procedure): Create sink_case with only as
index d79828b763f3d110c21d892872fc9e480c986c2f..9cbe39e6212cc3950c6f7b69cfc3b8f5f6584812 100644 (file)
@@ -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 (file)
index 0000000..6e35300
--- /dev/null
@@ -0,0 +1,98 @@
+/* PSPP - computes sample statistics.
+   Copyright (C) 2004, 2006 Free Software Foundation, Inc.
+   Written by Ben Pfaff <blp@gnu.org>.
+
+   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 <config.h>
+#include <stdbool.h>
+#include <libpspp/ll.h>
+
+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
index 525611bfa140fed8d2adfd09ae9ad70c5c0eabca..95e0388c4e8733ed74b6a3e89ebd780a0dbbe771 100644 (file)
@@ -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 <blp@gnu.org>.
 
    This program is free software; you can redistribute it and/or
    02110-1301, USA. */
 
 #include <config.h>
-#include "casefile.h"
-#include <assert.h>
-#include <errno.h>
-#include <fcntl.h>
-#include <stdio.h>
+#include <stddef.h>
+#include <stdbool.h>
 #include <stdlib.h>
-#include <string.h>
-#include <unistd.h>
-#include <libpspp/alloc.h>
+#include <assert.h>
+
 #include "case.h"
-#include <libpspp/compiler.h>
-#include <libpspp/message.h>
-#include "full-read.h"
-#include "full-write.h"
-#include <libpspp/misc.h>
-#include <libpspp/str.h>
-#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);
 }
index ab60e192798cbea20d0a142cb2ef06727e03c492..1bddb2d24a19769224ad42ee8a85db56a8744538 100644 (file)
@@ -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 <blp@gnu.org>.
 
    This program is free software; you can redistribute it and/or
    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 <config.h>
 #include <stddef.h>
 #include <stdbool.h>
 
+
 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 (file)
index 0000000..9cdba15
--- /dev/null
@@ -0,0 +1,751 @@
+/* PSPP - computes sample statistics.
+   Copyright (C) 2004, 2006 Free Software Foundation, Inc.
+   Written by Ben Pfaff <blp@gnu.org>.
+
+   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 <config.h>
+#include "casefile.h"
+#include "casefile-private.h"
+#include "fastfile.h"
+#include <assert.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <libpspp/alloc.h>
+#include "case.h"
+#include <libpspp/compiler.h>
+#include <libpspp/message.h>
+#include "full-read.h"
+#include "full-write.h"
+#include <libpspp/misc.h>
+#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 (file)
index 0000000..8404a33
--- /dev/null
@@ -0,0 +1,27 @@
+/* PSPP - computes sample statistics.
+   Copyright (C) 2004, 2006 Free Software Foundation, Inc.
+   Written by Ben Pfaff <blp@gnu.org>.
+
+   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 <config.h>
+
+struct casefile *fastfile_create (size_t value_cnt);
+
+#endif
index 4dcce32b0f8ec8ac467423d52c1d25ebf4cdb951..5649a173a223446a6d27b0d659d39fb8a455889b 100644 (file)
@@ -28,6 +28,7 @@
 #include <data/case-sink.h>
 #include <data/case.h>
 #include <data/casefile.h>
+#include <data/fastfile.h>
 #include <data/dictionary.h>
 #include <data/file-handle-def.h>
 #include <data/procedure.h>
@@ -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;
index 4802f4b20363b5f946660e6c920c10631fbb89b8..13ccee8c6f0aef698c2542ca495dbf4280bf5677 100644 (file)
@@ -22,6 +22,7 @@
 #include <stdlib.h>
 #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;
index 731fea39ba782125c3c61e1652f7a0c19576be42..74769023e74749725183eddb7f6e477a3d1dcca6 100644 (file)
@@ -28,6 +28,7 @@
 #include <data/case-source.h>
 #include <data/case.h>
 #include <data/casefile.h> 
+#include <data/fastfile.h> 
 
 #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. */
index fc4d2c12911ef39123613286143b59f8c657581b..789d259e6cbb1a3a197788ff92ed64b7d3f439e8 100644 (file)
@@ -27,6 +27,7 @@
 #include <data/case-source.h>
 #include <data/case.h>
 #include <data/casefile.h>
+#include <data/fastfile.h>
 #include <data/dictionary.h>
 #include <data/por-file-writer.h>
 #include <data/procedure.h>
@@ -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));
 
index 432cf388e944f1d9add1345db209c62f510fe038..a90cbfb7e7a1dc4f5b26d622478e40d8b94ee522 100644 (file)
@@ -19,6 +19,8 @@
 
 #include <config.h>
 #include <data/casefile.h>
+#include <data/fastfile.h>
+
 #include <data/case.h>
 
 #include <gsl/gsl_randist.h>
@@ -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);
index 3ce5da5707cb2ab1d3508eadf3c48a7ad5b6f0a2..732c4b86555d30d7362bb0ac85d9199282235453 100644 (file)
@@ -30,6 +30,7 @@
 #include <data/case-source.h>
 #include <data/case.h>
 #include <data/casefile.h>
+#include <data/fastfile.h>
 #include <data/procedure.h>
 #include <data/settings.h>
 #include <data/variable.h>
@@ -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. */
index 4a96aeb941a9479876c7386203e97d30fc102f8b..f4a3751332c7930a8579c457a91203222a13c22a 100644 (file)
@@ -1,3 +1,7 @@
+Mon Jul 17 18:22:18 WST 2006 John Darrington <john@darrington.wattle.id.au>
+
+       * flexifile.c flexifile.h: New files. Implementations of casefiles.
+
 Thu Mar  2 08:40:33 WST 2006 John Darrington <john@darrington.wattle.id.au>
        
        * Moved files from src directory
diff --git a/src/ui/automake.mk b/src/ui/automake.mk
new file mode 100644 (file)
index 0000000..5bf7862
--- /dev/null
@@ -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 (file)
index 0000000..756f67a
--- /dev/null
@@ -0,0 +1,379 @@
+/* PSPP - computes sample statistics.
+
+   Copyright (C) 2006 Free Software Foundation, Inc.
+   Written by John Darrington <john@darrington.wattle.id.au>
+
+   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 <config.h>
+#include <xalloc.h>
+#include <assert.h>
+#include "flexifile.h"
+#include <data/casefile.h>
+#include <data/casefile-private.h>
+#include <data/case.h>
+
+
+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 (file)
index 0000000..673c7d5
--- /dev/null
@@ -0,0 +1,47 @@
+/* PSPP - computes sample statistics.
+   Copyright (C) 2006 Free Software Foundation, Inc.
+   Written by John Darrington <john@darrington.wattle.id.au>
+
+   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 <config.h>
+#include <stdbool.h>
+#include <stdlib.h>
+
+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
index e2555263cfcf552c77a2e72ccf1fede3a8e623a2..d24ce2883adcadbd8dd45e80f95c7e2b9c2b2c1c 100644 (file)
@@ -1,3 +1,9 @@
+Mon Jul 17 18:21:29 WST 2006 John Darrington <john@darrington.wattle.id.au>
+
+    * 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 <john@darrington.wattle.id.au>
 
    * psppire.c psppire.glade automake.mk icons/* : Added toolbar icons where 
index ca6c8fc97a6927e101a5a9422e3327d71fc12021..be59fed55165d323656e08978b3e77e7b84a9f6f 100644 (file)
@@ -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 \
index d1a76802840b8970b84f57b9fc1e596771853e9f..98d00f63af619c94c5c73504a024b3dce976ce6b 100644 (file)
@@ -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);
+}
+
+
index 0c8e52a93968430186eed573c37ffff0f46a8b0a..f869e4b0dbf6aaea22383e7735efa8bd56c49397 100644 (file)
 #include <gtksheet/gtkextra-marshal.h>
 
 #include <data/case.h>
+#include <ui/flexifile.h>
 #include <data/casefile.h>
 #include <data/data-in.h>
-
 #include <math/sort.h>
+#include <libpspp/misc.h>
 
 /* --- 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);
+}
+
index 652ee54106baccb28e36dd44027662251eb508e0..745b709898ea498f7e70255e27b67c47b3dabfde 100644 (file)
@@ -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 *);
 
index 6df379731695183763f4a39af44470d0c7e327f3..6c82a30bb5020e82cf90f09b53a932f9c3af206e 100644 (file)
@@ -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)
index 10de849c7b84ab20011a111ff1111a7607adeee1..435be90cb2a86d17cf0fe9f804f3c8d5c44f7da8 100644 (file)
@@ -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 */
 
index 218cae0d764aebd126ae3729db1266fa3b2a5b6b..e71ef20403daec009fe11456201c04f552211884 100644 (file)
@@ -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);
 
index 3fa71ea30b5330fe194df1e6fd8e4745f286b527..5c884a3fb616e558dc77f0766101209b71434924 100644 (file)
@@ -25,6 +25,8 @@
 #include <data/value-labels.h>
 #include <data/format.h>
 
+#include <libpspp/misc.h>
+
 #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;
 }
index 162f5bbcf7a045fa2cf2cd880b6ae397a13133b7..67ace893386b2854b9157115e7f71646636dda67 100644 (file)
@@ -111,11 +111,8 @@ main(int argc, char *argv[])
   glade_init();
 
 
-  settings_init();
 
-  /* 
-  set_pspp_locale("da_DK");
-  */
+  settings_init();
 
   message_dialog_init();
 
index 798e62168871255e281c0b6b7d04e77302b34808..5b17aa1d8a75f35134b91841aef328c2d9a280ac 100644 (file)
                  <child>
                    <widget class="GtkImageMenuItem" id="cut1">
                      <property name="visible">True</property>
+                     <property name="sensitive">False</property>
                      <property name="label">gtk-cut</property>
                      <property name="use_stock">True</property>
-                     <signal name="activate" handler="on_cut1_activate" last_modification_time="Tue, 23 Mar 2004 10:22:51 GMT"/>
                    </widget>
                  </child>
 
                  <child>
                    <widget class="GtkImageMenuItem" id="copy1">
                      <property name="visible">True</property>
+                     <property name="sensitive">False</property>
                      <property name="label">gtk-copy</property>
                      <property name="use_stock">True</property>
-                     <signal name="activate" handler="on_copy1_activate" last_modification_time="Tue, 23 Mar 2004 10:22:51 GMT"/>
                    </widget>
                  </child>
 
                  <child>
                    <widget class="GtkImageMenuItem" id="paste1">
                      <property name="visible">True</property>
+                     <property name="sensitive">False</property>
                      <property name="label">gtk-paste</property>
                      <property name="use_stock">True</property>
-                     <signal name="activate" handler="on_paste1_activate" last_modification_time="Tue, 23 Mar 2004 10:22:51 GMT"/>
                    </widget>
                  </child>
 
                  <child>
-                   <widget class="GtkImageMenuItem" id="delete1">
+                   <widget class="GtkMenuItem" id="paste_variables1">
                      <property name="visible">True</property>
-                     <property name="label">gtk-delete</property>
-                     <property name="use_stock">True</property>
-                     <signal name="activate" handler="on_delete1_activate" last_modification_time="Tue, 23 Mar 2004 10:22:51 GMT"/>
+                     <property name="sensitive">False</property>
+                     <property name="label" translatable="yes">Paste _Variables</property>
+                     <property name="use_underline">True</property>
                    </widget>
                  </child>
 
                  <child>
-                   <widget class="GtkMenuItem" id="insert1">
+                   <widget class="GtkMenuItem" id="clear1">
                      <property name="visible">True</property>
-                     <property name="label" translatable="yes">_Insert</property>
+                     <property name="label" translatable="yes">Cl_ear</property>
                      <property name="use_underline">True</property>
-                     <signal name="activate" handler="on_insert1_activate" last_modification_time="Mon, 02 Jan 2006 12:46:02 GMT"/>
+                     <signal name="activate" handler="on_clear_activate" last_modification_time="Sun, 02 Jul 2006 07:00:12 GMT"/>
+                     <accelerator key="Delete" modifiers="0" signal="activate"/>
+                   </widget>
+                 </child>
+
+                 <child>
+                   <widget class="GtkSeparatorMenuItem" id="separator6">
+                     <property name="visible">True</property>
+                   </widget>
+                 </child>
+
+                 <child>
+                   <widget class="GtkImageMenuItem" id="find1">
+                     <property name="visible">True</property>
+                     <property name="sensitive">False</property>
+                     <property name="label" translatable="yes">_Find</property>
+                     <property name="use_underline">True</property>
+
+                     <child internal-child="image">
+                       <widget class="GtkImage" id="image10">
+                         <property name="visible">True</property>
+                         <property name="stock">gtk-find</property>
+                         <property name="icon_size">1</property>
+                         <property name="xalign">0.5</property>
+                         <property name="yalign">0.5</property>
+                         <property name="xpad">0</property>
+                         <property name="ypad">0</property>
+                       </widget>
+                     </child>
                    </widget>
                  </child>
                </widget>
                <widget class="GtkMenu" id="menuitem4_menu">
 
                  <child>
-                   <widget class="GtkMenuItem" id="insert_variable1">
+                   <widget class="GtkMenuItem" id="insert-variable">
                      <property name="visible">True</property>
                      <property name="sensitive">False</property>
                      <property name="label" translatable="yes">Insert Variable</property>
                      <property name="use_underline">True</property>
+                     <signal name="activate" handler="on_insert_variable" />
                    </widget>
                  </child>
 
                  <child>
-                   <widget class="GtkMenuItem" id="insert_cases1">
+                   <widget class="GtkMenuItem" id="insert-cases">
                      <property name="visible">True</property>
-                     <property name="sensitive">False</property>
                      <property name="label" translatable="yes">Insert Cases</property>
                      <property name="use_underline">True</property>
+                     <signal name="activate" handler="on_insert_cases" last_modification_time="Sat, 01 Jul 2006 08:40:06 GMT"/>
                    </widget>
                  </child>
 
                      <signal name="activate" handler="on_go_to_case_activate" last_modification_time="Mon, 19 Jun 2006 10:29:37 GMT"/>
 
                      <child internal-child="image">
-                       <widget class="GtkImage" id="image1">
+                       <widget class="GtkImage" id="image11">
                          <property name="visible">True</property>
                          <property name="stock">gtk-jump-to</property>
                          <property name="icon_size">1</property>
                  <property name="visible_horizontal">True</property>
                  <property name="visible_vertical">True</property>
                  <property name="is_important">False</property>
+                 <signal name="clicked" handler="on_insert_case_clicked" last_modification_time="Sat, 01 Jul 2006 08:41:07 GMT"/>
                </widget>
                <packing>
                  <property name="expand">False</property>
index b40179bb5192067ef592793045c76d2793e239b1..ecdcb3c2e68d72c77533fe1e917ce12504840b34 100644 (file)
@@ -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 \