|
1
|
## -*- tcl -*- |
|
2
|
# # ## ### ##### ######## ############# ##################### |
|
3
|
## Copyright (c) 2007 Andreas Kupries. |
|
4
|
# |
|
5
|
# This software is licensed as described in the file LICENSE, which |
|
6
|
# you should have received as part of this distribution. |
|
7
|
# |
|
8
|
# This software consists of voluntary contributions made by many |
|
9
|
# individuals. For exact contribution history, see the revision |
|
10
|
# history and logs, available at http://fossil-scm.hwaci.com/fossil |
|
11
|
# # ## ### ##### ######## ############# ##################### |
|
12
|
|
|
13
|
## State manager. Maintains the sqlite database used by all the other |
|
14
|
## parts of the system, especially the passes and their support code, |
|
15
|
## to persist and restore their state across invokations. |
|
16
|
|
|
17
|
# # ## ### ##### ######## ############# ##################### |
|
18
|
## Requirements |
|
19
|
|
|
20
|
package require Tcl 8.4 ; # Required runtime. |
|
21
|
package require snit ; # OO system. |
|
22
|
package require fileutil ; # File operations. |
|
23
|
package require sqlite3 ; # Database access. |
|
24
|
package require vc::tools::trouble ; # Error reporting. |
|
25
|
package require vc::tools::log ; # User feedback. |
|
26
|
|
|
27
|
# # ## ### ##### ######## ############# ##################### |
|
28
|
## |
|
29
|
|
|
30
|
snit::type ::vc::fossil::import::cvs::state { |
|
31
|
# # ## ### ##### ######## ############# |
|
32
|
## Public API |
|
33
|
|
|
34
|
typemethod usedb {path} { |
|
35
|
# Immediate validation. There are are two possibilities to |
|
36
|
# consider. The path exists or it doesn't. |
|
37
|
|
|
38
|
# In the first case it has to be a readable and writable file, |
|
39
|
# and it has to be a proper sqlite database. Further checks |
|
40
|
# regarding the required tables will be done later, by the |
|
41
|
# passes, during their setup. |
|
42
|
|
|
43
|
# In the second case we have to be able to create the file, |
|
44
|
# and check that. This is done by opening it, sqlite will then |
|
45
|
# try to create it, and may fail. |
|
46
|
|
|
47
|
if {[::file exists $path]} { |
|
48
|
if {![fileutil::test $path frw msg {cvs2fossil state}]} { |
|
49
|
trouble fatal $msg |
|
50
|
return |
|
51
|
} |
|
52
|
} |
|
53
|
|
|
54
|
if {[catch { |
|
55
|
sqlite3 ${type}::TEMP $path |
|
56
|
${type}::TEMP eval {PRAGMA synchronous=OFF;} |
|
57
|
} res]} { |
|
58
|
trouble fatal $res |
|
59
|
return |
|
60
|
} |
|
61
|
|
|
62
|
# A previously defined state database is closed before |
|
63
|
# committing to the new definition. We do not store the path |
|
64
|
# itself, this ensures that the file is _not_ cleaned up after |
|
65
|
# a run. |
|
66
|
|
|
67
|
set mystate ${type}::STATE |
|
68
|
set mypath {} |
|
69
|
|
|
70
|
catch { $mystate close } |
|
71
|
rename ${type}::TEMP $mystate |
|
72
|
|
|
73
|
log write 2 state "is $path" |
|
74
|
return |
|
75
|
} |
|
76
|
|
|
77
|
typemethod setup {} { |
|
78
|
# If, and only if no state database was defined by the user |
|
79
|
# then it is now the time to create our own using a tempfile. |
|
80
|
|
|
81
|
if {$mystate ne ""} return |
|
82
|
|
|
83
|
set mypath [fileutil::tempfile cvs2fossil_state_] |
|
84
|
set mystate ${type}::STATE |
|
85
|
sqlite3 $mystate $mypath |
|
86
|
$mystate eval {PRAGMA synchronous=OFF;} |
|
87
|
|
|
88
|
log write 2 state "using $mypath" |
|
89
|
return |
|
90
|
} |
|
91
|
|
|
92
|
typemethod release {} { |
|
93
|
log write 2 state release |
|
94
|
${type}::STATE close |
|
95
|
if {$mypath eq ""} return |
|
96
|
::file delete $mypath |
|
97
|
return |
|
98
|
} |
|
99
|
|
|
100
|
# Declare a table needed for the storing of persistent state, and |
|
101
|
# its structure. A possibly previously existing definition is |
|
102
|
# dropped. To be used when a table is needed and not assumed to |
|
103
|
# exist from previous passes. |
|
104
|
|
|
105
|
typemethod extend {name definition {indices {}}} { |
|
106
|
log write 5 state "extend $name" |
|
107
|
Save "extend $name ================================" |
|
108
|
|
|
109
|
$mystate transaction { |
|
110
|
catch { $mystate eval "DROP TABLE $name" } |
|
111
|
$mystate eval "CREATE TABLE $name ( $definition )" |
|
112
|
|
|
113
|
set id 0 |
|
114
|
foreach columns $indices { |
|
115
|
log write 5 state "index $name$id" |
|
116
|
|
|
117
|
$mystate eval "CREATE INDEX ${name}$id ON ${name} ( [join $columns ,] )" |
|
118
|
incr id |
|
119
|
} |
|
120
|
} |
|
121
|
return |
|
122
|
} |
|
123
|
|
|
124
|
# Declare that a table is needed for reading from and/or storing |
|
125
|
# to persistent state, and is assumed to already exist. A missing |
|
126
|
# table is an internal error causing an immediate exit. |
|
127
|
|
|
128
|
typemethod use {name} { |
|
129
|
log write 5 state "use $name" |
|
130
|
Save "use $name ===================================" |
|
131
|
|
|
132
|
set found [llength [$mystate eval { |
|
133
|
SELECT name |
|
134
|
FROM sqlite_master |
|
135
|
WHERE type = 'table' |
|
136
|
AND name = $name |
|
137
|
; |
|
138
|
}]] |
|
139
|
|
|
140
|
# No assert, would cause cycle in package dependencies |
|
141
|
if {$found} return |
|
142
|
trouble internal "The required table \"$name\" is not defined." |
|
143
|
# Not reached |
|
144
|
return |
|
145
|
} |
|
146
|
|
|
147
|
typemethod discard {name} { |
|
148
|
# Method for a user to remove outdated information from the |
|
149
|
# persistent state, table by table. |
|
150
|
|
|
151
|
log write 5 state "discard $name" |
|
152
|
|
|
153
|
$mystate transaction { |
|
154
|
catch { $mystate eval "DROP TABLE $name" } |
|
155
|
} |
|
156
|
return |
|
157
|
} |
|
158
|
|
|
159
|
typemethod run {args} { |
|
160
|
Save $args |
|
161
|
return [uplevel 1 [linsert $args 0 $mystate eval]] |
|
162
|
} |
|
163
|
|
|
164
|
typemethod foreachrow {sql script} { |
|
165
|
Save $sql |
|
166
|
uplevel 1 [list $mystate eval $sql $script] |
|
167
|
return |
|
168
|
} |
|
169
|
|
|
170
|
typemethod one {args} { |
|
171
|
Save $args |
|
172
|
return [uplevel 1 [linsert $args 0 $mystate onecolumn]] |
|
173
|
} |
|
174
|
|
|
175
|
typemethod transaction {script} { |
|
176
|
return [uplevel 1 [list $mystate transaction $script]] |
|
177
|
} |
|
178
|
|
|
179
|
typemethod id {} { |
|
180
|
return [$mystate last_insert_rowid] |
|
181
|
} |
|
182
|
|
|
183
|
typemethod savequeriesto {path} { |
|
184
|
set mysavepath $path |
|
185
|
return |
|
186
|
} |
|
187
|
|
|
188
|
# # ## ### ##### ######## ############# |
|
189
|
|
|
190
|
proc Save {text} { |
|
191
|
::variable mysavepath |
|
192
|
if {$mysavepath eq ""} return |
|
193
|
fileutil::appendToFile $mysavepath $text\n\n |
|
194
|
return |
|
195
|
} |
|
196
|
|
|
197
|
# # ## ### ##### ######## ############# |
|
198
|
## State |
|
199
|
|
|
200
|
typevariable mystate {} ; # Sqlite database (command) holding the converter state. |
|
201
|
typevariable mypath {} ; # Path to the database, for cleanup of a temp database. |
|
202
|
typevariable mysavepath {} ; # Path where to save queries for introspection. |
|
203
|
|
|
204
|
# # ## ### ##### ######## ############# |
|
205
|
## Internal methods |
|
206
|
|
|
207
|
|
|
208
|
# # ## ### ##### ######## ############# |
|
209
|
## Configuration |
|
210
|
|
|
211
|
pragma -hasinstances no ; # singleton |
|
212
|
pragma -hastypeinfo no ; # no introspection |
|
213
|
pragma -hastypedestroy no ; # immortal |
|
214
|
|
|
215
|
# # ## ### ##### ######## ############# |
|
216
|
} |
|
217
|
|
|
218
|
namespace eval ::vc::fossil::import::cvs { |
|
219
|
namespace export state |
|
220
|
namespace eval state { |
|
221
|
namespace import ::vc::tools::trouble |
|
222
|
namespace import ::vc::tools::log |
|
223
|
log register state |
|
224
|
} |
|
225
|
} |
|
226
|
|
|
227
|
# # ## ### ##### ######## ############# ##################### |
|
228
|
## Ready |
|
229
|
|
|
230
|
package provide vc::fossil::import::cvs::state 1.0 |
|
231
|
return |
|
232
|
|