| 1 | #----------------------------------------------------------------------- |
|---|
| 2 | # TITLE: |
|---|
| 3 | # notebookdb.tcl |
|---|
| 4 | # |
|---|
| 5 | # AUTHOR: |
|---|
| 6 | # Will Duquette |
|---|
| 7 | # |
|---|
| 8 | # DESCRIPTION: |
|---|
| 9 | # Notebook application's notebook page database. Create a new |
|---|
| 10 | # page database by calling notebookdb::notebookdb. Any number |
|---|
| 11 | # may be created; a single database may be shared by multiple |
|---|
| 12 | # clients. Note: no file locking is done. |
|---|
| 13 | # |
|---|
| 14 | # ERROR CODES: |
|---|
| 15 | # notebookdb defines the following error codes: |
|---|
| 16 | # |
|---|
| 17 | # ::notebook::loaderror |
|---|
| 18 | # The requested -dbfile could not be loaded or created. |
|---|
| 19 | # |
|---|
| 20 | # ::notebook::saveerror |
|---|
| 21 | # The -dbfile could not be saved. |
|---|
| 22 | # |
|---|
| 23 | # These are environmental errors, which the caller can't |
|---|
| 24 | # reasonably check before requesting the failed action, so |
|---|
| 25 | # the codes are defined to ease error recovery. |
|---|
| 26 | # |
|---|
| 27 | # All other errors are avoidable, and so the error code is not set. |
|---|
| 28 | # |
|---|
| 29 | # LICENSE: |
|---|
| 30 | # Copyright (C) 2002,2003,2004,2005 by William H. Duquette. |
|---|
| 31 | # This file may be used subject to the terms in license.txt. |
|---|
| 32 | # |
|---|
| 33 | #----------------------------------------------------------------------- |
|---|
| 34 | |
|---|
| 35 | #----------------------------------------------------------------------- |
|---|
| 36 | # Provided package |
|---|
| 37 | |
|---|
| 38 | package provide notebookdb 1.0 |
|---|
| 39 | |
|---|
| 40 | package require trycatch |
|---|
| 41 | package require snit |
|---|
| 42 | package require markupparser |
|---|
| 43 | |
|---|
| 44 | namespace eval ::notebookdb::notebookdb:: { |
|---|
| 45 | namespace import -force ::trycatch::* |
|---|
| 46 | } |
|---|
| 47 | |
|---|
| 48 | #----------------------------------------------------------------------- |
|---|
| 49 | # Public Functions |
|---|
| 50 | |
|---|
| 51 | namespace eval ::notebookdb:: { |
|---|
| 52 | namespace export notebookdb |
|---|
| 53 | } |
|---|
| 54 | |
|---|
| 55 | snit::type ::notebookdb::notebookdb { |
|---|
| 56 | #------------------------------------------------------------------- |
|---|
| 57 | # Creation Options |
|---|
| 58 | |
|---|
| 59 | # The database file name. It must be specified when the database |
|---|
| 60 | # is created. |
|---|
| 61 | option -dbfile "" |
|---|
| 62 | |
|---|
| 63 | onconfigure -dbfile {value} { |
|---|
| 64 | if {"" != $options(-dbfile)} { |
|---|
| 65 | error "-dbfile cannot be changed after the notebookdb is created" |
|---|
| 66 | } |
|---|
| 67 | set options(-dbfile) $value |
|---|
| 68 | } |
|---|
| 69 | |
|---|
| 70 | #------------------------------------------------------------------ |
|---|
| 71 | # Instance Variables |
|---|
| 72 | |
|---|
| 73 | # Array of database contents, indexed on page ID. The page ID is |
|---|
| 74 | # the page name in lower case. |
|---|
| 75 | # |
|---|
| 76 | # id-$id The ID itself. |
|---|
| 77 | # name-$id The canonical casing for the page name. |
|---|
| 78 | # lock-$id 1 if the page is locked, 0 (or nonexistent) otherwise. |
|---|
| 79 | # page-$id The page's text. |
|---|
| 80 | # time-$id The page's modification time. |
|---|
| 81 | variable db |
|---|
| 82 | variable dbfields {id name lock page time} |
|---|
| 83 | |
|---|
| 84 | # Observers array. index is observer name, value is command to execute. |
|---|
| 85 | variable observers |
|---|
| 86 | |
|---|
| 87 | # Flag: notify observers only if true. |
|---|
| 88 | variable notifyObservers 1 |
|---|
| 89 | |
|---|
| 90 | # |
|---|
| 91 | # CONSTRUCTOR |
|---|
| 92 | # |
|---|
| 93 | |
|---|
| 94 | constructor {args} { |
|---|
| 95 | global auto_path |
|---|
| 96 | |
|---|
| 97 | # Save the options |
|---|
| 98 | $self configurelist $args |
|---|
| 99 | |
|---|
| 100 | set filename $options(-dbfile) |
|---|
| 101 | |
|---|
| 102 | if {"" == $filename} { |
|---|
| 103 | error "required option -dbfile is missing." |
|---|
| 104 | } |
|---|
| 105 | |
|---|
| 106 | # Next, try to load the file from disk, if it exists. |
|---|
| 107 | if {[file exists $filename]} { |
|---|
| 108 | interp create $self.loader |
|---|
| 109 | $self.loader alias page $self Loader.Page |
|---|
| 110 | |
|---|
| 111 | try { |
|---|
| 112 | try { |
|---|
| 113 | $self.loader eval [list source $filename] |
|---|
| 114 | } catch -msg result { |
|---|
| 115 | throw notebookdb::loaderror \ |
|---|
| 116 | "Could not load notebook file '$filename': $result" |
|---|
| 117 | } |
|---|
| 118 | } finally { |
|---|
| 119 | interp delete $self.loader |
|---|
| 120 | } |
|---|
| 121 | } |
|---|
| 122 | |
|---|
| 123 | # NEXT, create the database's slave interpreter and populate it. |
|---|
| 124 | interp create $self.interp |
|---|
| 125 | $self.interp eval [list set auto_path $auto_path] |
|---|
| 126 | |
|---|
| 127 | $self.interp eval { |
|---|
| 128 | # Create an expander in the slave, but put it in the ::db:: |
|---|
| 129 | # namespace so that it's somewhat hidden. |
|---|
| 130 | package require textutil::expander |
|---|
| 131 | |
|---|
| 132 | namespace eval ::db:: { |
|---|
| 133 | ::textutil::expander PageExpander |
|---|
| 134 | PageExpander setbrackets {[@} {@]} |
|---|
| 135 | PageExpander errmode macro |
|---|
| 136 | } |
|---|
| 137 | |
|---|
| 138 | # Alias its expand function back into the global namespace of |
|---|
| 139 | # the slave. |
|---|
| 140 | interp alias {} expand {} ::db::PageExpander expand |
|---|
| 141 | } |
|---|
| 142 | |
|---|
| 143 | # Alias the database object itself into the slave. |
|---|
| 144 | $self.interp alias ::db::db $self |
|---|
| 145 | } |
|---|
| 146 | |
|---|
| 147 | # Destroys the interpreter; all other cleanup is done by snit. |
|---|
| 148 | destructor { |
|---|
| 149 | if {[info commands $self.interp] != ""} { |
|---|
| 150 | interp delete $self.interp |
|---|
| 151 | } |
|---|
| 152 | } |
|---|
| 153 | |
|---|
| 154 | # |
|---|
| 155 | # Methods |
|---|
| 156 | # |
|---|
| 157 | |
|---|
| 158 | # Evaluates its args in the database's slave interpreter |
|---|
| 159 | method eval {args} { |
|---|
| 160 | return [uplevel $self.interp eval $args] |
|---|
| 161 | } |
|---|
| 162 | |
|---|
| 163 | # Aliases a command into the database's slave interpreter. |
|---|
| 164 | # |
|---|
| 165 | # newcmd The new command to alias into the slave interpreter. |
|---|
| 166 | # targetcmd The command being aliased in. |
|---|
| 167 | # args Any additional args to targetcmd. |
|---|
| 168 | |
|---|
| 169 | method alias {newcmd targetcmd args} { |
|---|
| 170 | eval $self.interp alias $newcmd $targetcmd $args |
|---|
| 171 | } |
|---|
| 172 | |
|---|
| 173 | # Tries to save the database to disk |
|---|
| 174 | method save {} { |
|---|
| 175 | set fname $options(-dbfile) |
|---|
| 176 | |
|---|
| 177 | try { |
|---|
| 178 | $self SaveDatabase $fname |
|---|
| 179 | } catch -msg errmsg { |
|---|
| 180 | throw notebookdb::saveerror \ |
|---|
| 181 | "Could not save notebook file:\n\n'$fname'\n\n$errmsg" |
|---|
| 182 | } |
|---|
| 183 | } |
|---|
| 184 | |
|---|
| 185 | method backupname {} { |
|---|
| 186 | set fname $options(-dbfile) |
|---|
| 187 | |
|---|
| 188 | return [file rootname $fname].bak |
|---|
| 189 | } |
|---|
| 190 | |
|---|
| 191 | # Checks whether there's a page with a given name or not, and returns |
|---|
| 192 | # 1 if so and 0 otherwise |
|---|
| 193 | method exists {name} { |
|---|
| 194 | set id [string tolower $name] |
|---|
| 195 | return [info exists db(id-$id)] |
|---|
| 196 | } |
|---|
| 197 | |
|---|
| 198 | # Retrieves the text of the named page. The text is returned as is, |
|---|
| 199 | # with no expansion. |
|---|
| 200 | method get {name} { |
|---|
| 201 | set id [string tolower $name] |
|---|
| 202 | return $db(page-$id) |
|---|
| 203 | } |
|---|
| 204 | |
|---|
| 205 | # Retrieves the text of the named page, with macros expanded. |
|---|
| 206 | method pageexpand {name} { |
|---|
| 207 | $self expand [$self get $name] |
|---|
| 208 | } |
|---|
| 209 | |
|---|
| 210 | # Expands macros in the text in the context of the database. |
|---|
| 211 | method expand {text} { |
|---|
| 212 | $self.interp eval [list expand $text] |
|---|
| 213 | } |
|---|
| 214 | |
|---|
| 215 | # Retrieves the canonical casing of the page name. |
|---|
| 216 | method name {name} { |
|---|
| 217 | set id [string tolower $name] |
|---|
| 218 | return $db(name-$id) |
|---|
| 219 | } |
|---|
| 220 | |
|---|
| 221 | # If no format string is given, retrieves the timestamp (in seconds) |
|---|
| 222 | # of the named page. Otherwise formats the time according to the format. |
|---|
| 223 | # |
|---|
| 224 | # name The page name |
|---|
| 225 | # fmt A [clock format] format string. |
|---|
| 226 | method pagetime {name {fmt ""}} { |
|---|
| 227 | set id [string tolower $name] |
|---|
| 228 | |
|---|
| 229 | if {"" == $fmt} { |
|---|
| 230 | return $db(time-$id) |
|---|
| 231 | } else { |
|---|
| 232 | return [clock format $db(time-$id) -format $fmt] |
|---|
| 233 | } |
|---|
| 234 | } |
|---|
| 235 | |
|---|
| 236 | # Returns a sorted list of pages, or "" if none match the pattern. |
|---|
| 237 | # |
|---|
| 238 | # pattern Optional; the pattern to match. Defaults to * |
|---|
| 239 | method pages {{pattern *}} { |
|---|
| 240 | set plist {} |
|---|
| 241 | |
|---|
| 242 | foreach ndx [array names db "name-*"] { |
|---|
| 243 | if {[string match -nocase $pattern $db($ndx)]} { |
|---|
| 244 | lappend plist $db($ndx) |
|---|
| 245 | } |
|---|
| 246 | } |
|---|
| 247 | |
|---|
| 248 | return [lsort -dictionary $plist] |
|---|
| 249 | } |
|---|
| 250 | |
|---|
| 251 | # Returns a sorted list of page IDs, or "" if none match the pattern. |
|---|
| 252 | # |
|---|
| 253 | # pattern Optional; the pattern to match. Defaults to * |
|---|
| 254 | method pageids {{pattern *}} { |
|---|
| 255 | set plist {} |
|---|
| 256 | |
|---|
| 257 | foreach ndx [array names db "id-*"] { |
|---|
| 258 | if {[string match -nocase $pattern $db($ndx)]} { |
|---|
| 259 | lappend plist $db($ndx) |
|---|
| 260 | } |
|---|
| 261 | } |
|---|
| 262 | |
|---|
| 263 | return [lsort -dictionary $plist] |
|---|
| 264 | } |
|---|
| 265 | |
|---|
| 266 | # Returns a list of pages, sorted by time starting with the most |
|---|
| 267 | # recent, or "" if none match the pattern. |
|---|
| 268 | # |
|---|
| 269 | # pattern Optional; the pattern to match. Defaults to * |
|---|
| 270 | # |
|---|
| 271 | # TBD: Consider keeping a list of recent edits in the database. |
|---|
| 272 | method pagesbytime {{pattern *}} { |
|---|
| 273 | set plist {} |
|---|
| 274 | |
|---|
| 275 | foreach ndx [array names db "name-*"] { |
|---|
| 276 | if {[string match -nocase $pattern $db($ndx)]} { |
|---|
| 277 | lappend plist $db($ndx) |
|---|
| 278 | } |
|---|
| 279 | } |
|---|
| 280 | |
|---|
| 281 | return [lsort -decreasing \ |
|---|
| 282 | -command [list [myproc TimeCmp] $self] $plist] |
|---|
| 283 | } |
|---|
| 284 | |
|---|
| 285 | # lsort comparison function for pagesbytime. |
|---|
| 286 | proc TimeCmp {self p1 p2} { |
|---|
| 287 | set t1 [$self pagetime $p1] |
|---|
| 288 | set t2 [$self pagetime $p2] |
|---|
| 289 | |
|---|
| 290 | if {$t1 < $t2} { |
|---|
| 291 | return -1 |
|---|
| 292 | } elseif {$t2 > $t1} { |
|---|
| 293 | return 1 |
|---|
| 294 | } else { |
|---|
| 295 | return 0 |
|---|
| 296 | } |
|---|
| 297 | } |
|---|
| 298 | |
|---|
| 299 | # Returns a list of pages whose names or bodies contain the searchText. |
|---|
| 300 | method search {searchText} { |
|---|
| 301 | $self searchx byname all $searchText |
|---|
| 302 | } |
|---|
| 303 | |
|---|
| 304 | # Returns a list of pages whose names or bodies contain the searchText. |
|---|
| 305 | # in time order. |
|---|
| 306 | method searchbytime {searchText} { |
|---|
| 307 | $self searchx bytime all $searchText |
|---|
| 308 | } |
|---|
| 309 | |
|---|
| 310 | # Returns an unsorted list of matches |
|---|
| 311 | method searchx {sort mode searchText} { |
|---|
| 312 | set plist {} |
|---|
| 313 | |
|---|
| 314 | set contentFlag [string equal $mode all] |
|---|
| 315 | |
|---|
| 316 | foreach ndx [array names db "name-*"] { |
|---|
| 317 | set name $db($ndx) |
|---|
| 318 | |
|---|
| 319 | # TBD: search strings containing special characters are |
|---|
| 320 | # problematic. I need to fix that. |
|---|
| 321 | if {[string match -nocase "*$searchText*" $name]} { |
|---|
| 322 | lappend plist $name |
|---|
| 323 | continue |
|---|
| 324 | } |
|---|
| 325 | |
|---|
| 326 | if {$contentFlag && |
|---|
| 327 | [string match -nocase "*$searchText*" [$self get $name]]} { |
|---|
| 328 | lappend plist $name |
|---|
| 329 | continue |
|---|
| 330 | } |
|---|
| 331 | } |
|---|
| 332 | |
|---|
| 333 | |
|---|
| 334 | return [$self $sort $plist] |
|---|
| 335 | } |
|---|
| 336 | |
|---|
| 337 | method byname {pagelist} { |
|---|
| 338 | lsort -dictionary $pagelist |
|---|
| 339 | } |
|---|
| 340 | |
|---|
| 341 | method bytime {pagelist} { |
|---|
| 342 | lsort -decreasing \ |
|---|
| 343 | -command [list [myproc TimeCmp] $self] $pagelist |
|---|
| 344 | } |
|---|
| 345 | |
|---|
| 346 | # Tries to lock the page. A locked page cannot be edited (at least, |
|---|
| 347 | # not by another user of this notebookdb). Returns 1 on |
|---|
| 348 | # success and 0 if it's already locked. |
|---|
| 349 | method lock {name} { |
|---|
| 350 | set id [string tolower $name] |
|---|
| 351 | |
|---|
| 352 | if {[info exists db(lock-$id)]} { |
|---|
| 353 | return 0 |
|---|
| 354 | } |
|---|
| 355 | |
|---|
| 356 | set db(lock-$id) 1 |
|---|
| 357 | } |
|---|
| 358 | |
|---|
| 359 | # Unlocks the page, whether it was locked or not. |
|---|
| 360 | method unlock {name} { |
|---|
| 361 | set id [string tolower $name] |
|---|
| 362 | |
|---|
| 363 | if {[info exists db(lock-$id)]} { |
|---|
| 364 | unset db(lock-$id) |
|---|
| 365 | } |
|---|
| 366 | } |
|---|
| 367 | |
|---|
| 368 | # Returns 1 if the page is locked, and 0 otherwise. |
|---|
| 369 | method locked {name} { |
|---|
| 370 | set id [string tolower $name] |
|---|
| 371 | return [info exists db(lock-$id)] |
|---|
| 372 | } |
|---|
| 373 | |
|---|
| 374 | # Saves the text for the page. The page must not be locked. Does not |
|---|
| 375 | # write the database to disk. Any trailing whitespace |
|---|
| 376 | # is trimmed, and a final newline is always added. Also, sets the |
|---|
| 377 | # page's timestamp. |
|---|
| 378 | # |
|---|
| 379 | # name The page name |
|---|
| 380 | # text The new page text. |
|---|
| 381 | method set {name text} { |
|---|
| 382 | set id [string tolower $name] |
|---|
| 383 | |
|---|
| 384 | if {[$self locked $name]} { |
|---|
| 385 | error "Error, '$name' is locked." |
|---|
| 386 | } |
|---|
| 387 | |
|---|
| 388 | set db(id-$id) $id |
|---|
| 389 | set db(name-$id) $name |
|---|
| 390 | set db(page-$id) "[string trimright $text]\n" |
|---|
| 391 | set db(time-$id) [clock seconds] |
|---|
| 392 | |
|---|
| 393 | $self Notify set $name |
|---|
| 394 | } |
|---|
| 395 | |
|---|
| 396 | # Deletes the page, which must not be locked. Does not |
|---|
| 397 | # write the database to disk. |
|---|
| 398 | method delete {name} { |
|---|
| 399 | set id [string tolower $name] |
|---|
| 400 | |
|---|
| 401 | if {[$self locked $name]} { |
|---|
| 402 | error "Error, '$name' is locked." |
|---|
| 403 | } |
|---|
| 404 | |
|---|
| 405 | # NOTE: Originally, this did a loop on |
|---|
| 406 | # [array names db(*-$id)]. Unfortunately, this means that |
|---|
| 407 | # if you delete Fred you also delete *-Fred. |
|---|
| 408 | foreach field $dbfields { |
|---|
| 409 | if {[info exists db($field-$id)]} { |
|---|
| 410 | unset db($field-$id) |
|---|
| 411 | } |
|---|
| 412 | } |
|---|
| 413 | |
|---|
| 414 | $self Notify delete $name |
|---|
| 415 | } |
|---|
| 416 | |
|---|
| 417 | # Renames a page, if possible. If the newName maps to the same ID as |
|---|
| 418 | # the oldName, it's simply saved for later use. If it already names some |
|---|
| 419 | # other existing page, an error is thrown. Otherwise, finds all pages that |
|---|
| 420 | # might include a link to the old name, and renames the link in each one. |
|---|
| 421 | # finally, a new page is created with the old name, and the old page is |
|---|
| 422 | # deleted. |
|---|
| 423 | # |
|---|
| 424 | # It's an error if the page is locked. |
|---|
| 425 | |
|---|
| 426 | method rename {oldName newName} { |
|---|
| 427 | set oldId [string tolower $oldName] |
|---|
| 428 | set newId [string tolower $newName] |
|---|
| 429 | |
|---|
| 430 | # First, if the page doesn't exist, throw an error. |
|---|
| 431 | if {![$self exists $oldName]} { |
|---|
| 432 | error "Cannot rename '$oldName'; no such page." |
|---|
| 433 | } |
|---|
| 434 | |
|---|
| 435 | # Next, if the page is locked, throw an error. |
|---|
| 436 | if {[$self locked $oldName]} { |
|---|
| 437 | error "Error, '$oldName' is locked." |
|---|
| 438 | } |
|---|
| 439 | |
|---|
| 440 | # Next, if it's just changing the capitalization, change it and |
|---|
| 441 | # return. |
|---|
| 442 | if {[string equal $oldId $newId]} { |
|---|
| 443 | set db(name-$oldId) $newName |
|---|
| 444 | $self Notify set $newName |
|---|
| 445 | return |
|---|
| 446 | } |
|---|
| 447 | |
|---|
| 448 | # Next, is the new name already in use? |
|---|
| 449 | if {[$self exists $newName]} { |
|---|
| 450 | error "The name '$newName' is already in use." |
|---|
| 451 | } |
|---|
| 452 | |
|---|
| 453 | set notifyObservers 0 |
|---|
| 454 | |
|---|
| 455 | # Next, create the new page and delete the old one. |
|---|
| 456 | $self set $newName [$self get $oldName] |
|---|
| 457 | $self delete $oldName |
|---|
| 458 | |
|---|
| 459 | # Next, update links. Do this after creating the new page, as it |
|---|
| 460 | # guarantees that the new page exists when the other pages are |
|---|
| 461 | # updated. |
|---|
| 462 | foreach pname [$self search "$oldName"] { |
|---|
| 463 | set text [$self get $pname] |
|---|
| 464 | set text [::markupparser::renameLink $text $oldName $newName] |
|---|
| 465 | $self set $pname $text |
|---|
| 466 | } |
|---|
| 467 | |
|---|
| 468 | set notifyObservers 1 |
|---|
| 469 | $self Notify rename [list $oldName $newName] |
|---|
| 470 | } |
|---|
| 471 | |
|---|
| 472 | # Calls pages to get a list of page names that match the pattern. If the |
|---|
| 473 | # specified name is in the list, and has a predecessor, the name of the |
|---|
| 474 | # predecessor is returned; otherwise "". |
|---|
| 475 | # |
|---|
| 476 | # name The page name |
|---|
| 477 | # pattern A pattern for retrieving page names. |
|---|
| 478 | method prevpage {name {pattern "*"}} { |
|---|
| 479 | set id [string tolower $name] |
|---|
| 480 | set ids [$self pageids $pattern] |
|---|
| 481 | |
|---|
| 482 | set ndx [lsearch $ids $id] |
|---|
| 483 | |
|---|
| 484 | set pred [expr {$ndx - 1}] |
|---|
| 485 | |
|---|
| 486 | if {$pred < 0} { |
|---|
| 487 | return "" |
|---|
| 488 | } |
|---|
| 489 | |
|---|
| 490 | return [$self name [lindex $ids $pred]] |
|---|
| 491 | } |
|---|
| 492 | |
|---|
| 493 | # Calls pages to get a list of page names that match the pattern. If the |
|---|
| 494 | # specified name is in the list, and has a successor, the name of the |
|---|
| 495 | # successor is returned; otherwise "". |
|---|
| 496 | # |
|---|
| 497 | # name The page name |
|---|
| 498 | # pattern A pattern for retrieving page names. |
|---|
| 499 | |
|---|
| 500 | method nextpage {name {pattern "*"}} { |
|---|
| 501 | set id [string tolower $name] |
|---|
| 502 | set ids [$self pageids $pattern] |
|---|
| 503 | |
|---|
| 504 | set ndx [lsearch $ids $id] |
|---|
| 505 | |
|---|
| 506 | if {$ndx == -1} { |
|---|
| 507 | return "" |
|---|
| 508 | } |
|---|
| 509 | |
|---|
| 510 | set succ [expr {$ndx + 1}] |
|---|
| 511 | |
|---|
| 512 | if {$succ == [llength $ids]} { |
|---|
| 513 | return "" |
|---|
| 514 | } |
|---|
| 515 | |
|---|
| 516 | return [$self name [lindex $ids $succ]] |
|---|
| 517 | } |
|---|
| 518 | |
|---|
| 519 | |
|---|
| 520 | # Register observer. obj is the observing object; command is the |
|---|
| 521 | # command to execute. |
|---|
| 522 | # |
|---|
| 523 | # Registered observers are notified when the notebookdb's contents is |
|---|
| 524 | # modified. Two arguments will be appended to their command. |
|---|
| 525 | # The first indicates the nature of the change; the second contains |
|---|
| 526 | # the relevant data, as shown: |
|---|
| 527 | # |
|---|
| 528 | # Operation Data |
|---|
| 529 | # --------- ----------------------- |
|---|
| 530 | # set string: Page name |
|---|
| 531 | # delete string: Page name |
|---|
| 532 | # rename list: oldname newname |
|---|
| 533 | # |
|---|
| 534 | # Note that renames look like several sets followed by a delete. |
|---|
| 535 | |
|---|
| 536 | method register {obj command} { |
|---|
| 537 | set observers($obj) $command |
|---|
| 538 | } |
|---|
| 539 | |
|---|
| 540 | # Unregister observer. obj is the observing object. |
|---|
| 541 | method unregister {obj} { |
|---|
| 542 | unset observers($obj) |
|---|
| 543 | } |
|---|
| 544 | |
|---|
| 545 | |
|---|
| 546 | # |
|---|
| 547 | # Private Methods |
|---|
| 548 | # |
|---|
| 549 | |
|---|
| 550 | # Notify |
|---|
| 551 | # |
|---|
| 552 | # Notifies observers, if any. |
|---|
| 553 | method Notify {operation page} { |
|---|
| 554 | if {!$notifyObservers} { |
|---|
| 555 | return |
|---|
| 556 | } |
|---|
| 557 | |
|---|
| 558 | foreach name [array names observers] { |
|---|
| 559 | set cmd $observers($name) |
|---|
| 560 | lappend cmd $operation $page |
|---|
| 561 | uplevel \#0 $cmd |
|---|
| 562 | } |
|---|
| 563 | |
|---|
| 564 | return |
|---|
| 565 | } |
|---|
| 566 | |
|---|
| 567 | # SaveDatabase fname |
|---|
| 568 | # |
|---|
| 569 | # Save the database to the file. |
|---|
| 570 | |
|---|
| 571 | method SaveDatabase {fname} { |
|---|
| 572 | if {[file exists $fname]} { |
|---|
| 573 | file rename -force -- $fname [$self backupname] |
|---|
| 574 | } |
|---|
| 575 | |
|---|
| 576 | set f [open $fname w] |
|---|
| 577 | |
|---|
| 578 | puts $f "# Notebook Database File" |
|---|
| 579 | |
|---|
| 580 | foreach name [$self pages] { |
|---|
| 581 | puts $f "\n#--------------------------------------------------" |
|---|
| 582 | puts $f "# $name\n" |
|---|
| 583 | |
|---|
| 584 | puts $f [list page $name [$self get $name] [$self pagetime $name]] |
|---|
| 585 | puts $f "" |
|---|
| 586 | } |
|---|
| 587 | |
|---|
| 588 | puts $f "\n# End of Notebook Database File" |
|---|
| 589 | close $f |
|---|
| 590 | } |
|---|
| 591 | |
|---|
| 592 | # |
|---|
| 593 | # Database Loading Commands |
|---|
| 594 | # |
|---|
| 595 | # These commands are aliased into a temporary slave interpreter that's |
|---|
| 596 | # used to load the notebook file. |
|---|
| 597 | # |
|---|
| 598 | |
|---|
| 599 | # Creates a page in the database. Aliased to "page". |
|---|
| 600 | # TBD: combine this with method set in some way. |
|---|
| 601 | method Loader.Page {name text {time 0}} { |
|---|
| 602 | set id [string tolower $name] |
|---|
| 603 | |
|---|
| 604 | set db(id-$id) $id |
|---|
| 605 | set db(name-$id) $name |
|---|
| 606 | set db(page-$id) [string trimright $text] |
|---|
| 607 | |
|---|
| 608 | if {$time == 0} { |
|---|
| 609 | set time [clock seconds] |
|---|
| 610 | } |
|---|
| 611 | set db(time-$id) $time |
|---|
| 612 | } |
|---|
| 613 | } |
|---|