← Index
NYTProf Performance Profile   « block view • line view • sub view »
For /usr/local/src/github.com/foswiki/core/bin/view
  Run on Sun Dec 4 17:17:59 2011
Reported on Sun Dec 4 17:26:33 2011

Filename/usr/local/src/github.com/foswiki/core/lib/Foswiki/Store/VC/Handler.pm
StatementsExecuted 20739 statements in 222ms
Subroutines
Calls P F Exclusive
Time
Inclusive
Time
Subroutine
111108ms169msFoswiki::Store::VC::Handler::::getTopicNamesFoswiki::Store::VC::Handler::getTopicNames
172313133.0ms33.0msFoswiki::Store::VC::Handler::::CORE:matchFoswiki::Store::VC::Handler::CORE:match (opcode)
3471115.7ms20.0msFoswiki::Store::VC::Handler::::newFoswiki::Store::VC::Handler::new
11110.3ms10.3msFoswiki::Store::VC::Handler::::CORE:readdirFoswiki::Store::VC::Handler::CORE:readdir (opcode)
6896118.81ms8.81msFoswiki::Store::VC::Handler::::CORE:regcompFoswiki::Store::VC::Handler::CORE:regcomp (opcode)
1118.52ms8.52msFoswiki::Store::VC::Handler::::CORE:sortFoswiki::Store::VC::Handler::CORE:sort (opcode)
70114.58ms8.41msFoswiki::Store::VC::Handler::::readFileFoswiki::Store::VC::Handler::readFile
117434.18ms6.36msFoswiki::Store::VC::Handler::::noCheckinPendingFoswiki::Store::VC::Handler::noCheckinPending
617914.05ms4.05msFoswiki::Store::VC::Handler::::CORE:ftisFoswiki::Store::VC::Handler::CORE:ftis (opcode)
1113.76ms3.96msFoswiki::Store::VC::Handler::::BEGIN@31Foswiki::Store::VC::Handler::BEGIN@31
14113.28ms8.62msFoswiki::Store::VC::Handler::::_getTOPICINFOFoswiki::Store::VC::Handler::_getTOPICINFO
274213.27ms5.19msFoswiki::Store::VC::Handler::::storedDataExistsFoswiki::Store::VC::Handler::storedDataExists
70111.93ms10.8msFoswiki::Store::VC::Handler::::getRevisionFoswiki::Store::VC::Handler::getRevision
84211.83ms1.83msFoswiki::Store::VC::Handler::::CORE:openFoswiki::Store::VC::Handler::CORE:open (opcode)
84211.65ms1.65msFoswiki::Store::VC::Handler::::CORE:readlineFoswiki::Store::VC::Handler::CORE:readline (opcode)
21111.57ms22.5sFoswiki::Store::VC::Handler::::getInfoFoswiki::Store::VC::Handler::getInfo
15631804µs804µsFoswiki::Store::VC::Handler::::CORE:statFoswiki::Store::VC::Handler::CORE:stat (opcode)
111545µs688µsFoswiki::Store::VC::Handler::::BEGIN@38Foswiki::Store::VC::Handler::BEGIN@38
8421449µs449µsFoswiki::Store::VC::Handler::::CORE:closeFoswiki::Store::VC::Handler::CORE:close (opcode)
811386µs652µsFoswiki::Store::VC::Handler::::getTimestampFoswiki::Store::VC::Handler::getTimestamp
522270µs1.41sFoswiki::Store::VC::Handler::::getLatestRevisionIDFoswiki::Store::VC::Handler::getLatestRevisionID
7011200µs200µsFoswiki::Store::VC::Handler::::CORE:binmodeFoswiki::Store::VC::Handler::CORE:binmode (opcode)
211134µs620msFoswiki::Store::VC::Handler::::getRevisionHistoryFoswiki::Store::VC::Handler::getRevisionHistory
11183µs83µsFoswiki::Store::VC::Handler::::CORE:open_dirFoswiki::Store::VC::Handler::CORE:open_dir (opcode)
11142µs42µsFoswiki::Store::VC::Handler::::CORE:closedirFoswiki::Store::VC::Handler::CORE:closedir (opcode)
11125µs1.86msFoswiki::Store::VC::Handler::::BEGIN@34Foswiki::Store::VC::Handler::BEGIN@34
11124µs31µsFoswiki::Store::VC::Handler::::BEGIN@26Foswiki::Store::VC::Handler::BEGIN@26
11116µs33µsFoswiki::Store::VC::Handler::::BEGIN@27Foswiki::Store::VC::Handler::BEGIN@27
11116µs56µsFoswiki::Store::VC::Handler::::BEGIN@28Foswiki::Store::VC::Handler::BEGIN@28
11110µs10µsFoswiki::Store::VC::Handler::::BEGIN@36Foswiki::Store::VC::Handler::BEGIN@36
11110µs10µsFoswiki::Store::VC::Handler::::BEGIN@41Foswiki::Store::VC::Handler::BEGIN@41
11110µs10µsFoswiki::Store::VC::Handler::::BEGIN@32Foswiki::Store::VC::Handler::BEGIN@32
1119µs9µsFoswiki::Store::VC::Handler::::BEGIN@33Foswiki::Store::VC::Handler::BEGIN@33
1119µs9µsFoswiki::Store::VC::Handler::::BEGIN@37Foswiki::Store::VC::Handler::BEGIN@37
1119µs9µsFoswiki::Store::VC::Handler::::BEGIN@30Foswiki::Store::VC::Handler::BEGIN@30
0000s0sFoswiki::Store::VC::Handler::::_constructAttributesForAutoAttachedFoswiki::Store::VC::Handler::_constructAttributesForAutoAttached
0000s0sFoswiki::Store::VC::Handler::::_controlFileNameFoswiki::Store::VC::Handler::_controlFileName
0000s0sFoswiki::Store::VC::Handler::::_dirForTopicAttachmentsFoswiki::Store::VC::Handler::_dirForTopicAttachments
0000s0sFoswiki::Store::VC::Handler::::_epochToRcsDateTimeFoswiki::Store::VC::Handler::_epochToRcsDateTime
0000s0sFoswiki::Store::VC::Handler::::_getAttachmentStatsFoswiki::Store::VC::Handler::_getAttachmentStats
0000s0sFoswiki::Store::VC::Handler::::_mktempFoswiki::Store::VC::Handler::_mktemp
0000s0sFoswiki::Store::VC::Handler::::_rmtreeFoswiki::Store::VC::Handler::_rmtree
0000s0sFoswiki::Store::VC::Handler::::_saveDamageFoswiki::Store::VC::Handler::_saveDamage
0000s0sFoswiki::Store::VC::Handler::::addRevisionFromStreamFoswiki::Store::VC::Handler::addRevisionFromStream
0000s0sFoswiki::Store::VC::Handler::::addRevisionFromTextFoswiki::Store::VC::Handler::addRevisionFromText
0000s0sFoswiki::Store::VC::Handler::::ciFoswiki::Store::VC::Handler::ci
0000s0sFoswiki::Store::VC::Handler::::copyAttachmentFoswiki::Store::VC::Handler::copyAttachment
0000s0sFoswiki::Store::VC::Handler::::copyFileFoswiki::Store::VC::Handler::copyFile
0000s0sFoswiki::Store::VC::Handler::::copyTopicFoswiki::Store::VC::Handler::copyTopic
0000s0sFoswiki::Store::VC::Handler::::eachChangeFoswiki::Store::VC::Handler::eachChange
0000s0sFoswiki::Store::VC::Handler::::finishFoswiki::Store::VC::Handler::finish
0000s0sFoswiki::Store::VC::Handler::::getAttachmentListFoswiki::Store::VC::Handler::getAttachmentList
0000s0sFoswiki::Store::VC::Handler::::getLatestRevisionTimeFoswiki::Store::VC::Handler::getLatestRevisionTime
0000s0sFoswiki::Store::VC::Handler::::getLeaseFoswiki::Store::VC::Handler::getLease
0000s0sFoswiki::Store::VC::Handler::::getNextRevisionIDFoswiki::Store::VC::Handler::getNextRevisionID
0000s0sFoswiki::Store::VC::Handler::::getWebNamesFoswiki::Store::VC::Handler::getWebNames
0000s0sFoswiki::Store::VC::Handler::::hidePathFoswiki::Store::VC::Handler::hidePath
0000s0sFoswiki::Store::VC::Handler::::initFoswiki::Store::VC::Handler::init
0000s0sFoswiki::Store::VC::Handler::::isAsciiDefaultFoswiki::Store::VC::Handler::isAsciiDefault
0000s0sFoswiki::Store::VC::Handler::::isLockedFoswiki::Store::VC::Handler::isLocked
0000s0sFoswiki::Store::VC::Handler::::mkPathToFoswiki::Store::VC::Handler::mkPathTo
0000s0sFoswiki::Store::VC::Handler::::mkTmpFilenameFoswiki::Store::VC::Handler::mkTmpFilename
0000s0sFoswiki::Store::VC::Handler::::moveAttachmentFoswiki::Store::VC::Handler::moveAttachment
0000s0sFoswiki::Store::VC::Handler::::moveFileFoswiki::Store::VC::Handler::moveFile
0000s0sFoswiki::Store::VC::Handler::::moveTopicFoswiki::Store::VC::Handler::moveTopic
0000s0sFoswiki::Store::VC::Handler::::moveWebFoswiki::Store::VC::Handler::moveWeb
0000s0sFoswiki::Store::VC::Handler::::openStreamFoswiki::Store::VC::Handler::openStream
0000s0sFoswiki::Store::VC::Handler::::recordChangeFoswiki::Store::VC::Handler::recordChange
0000s0sFoswiki::Store::VC::Handler::::removeFoswiki::Store::VC::Handler::remove
0000s0sFoswiki::Store::VC::Handler::::removeSpuriousLeasesFoswiki::Store::VC::Handler::removeSpuriousLeases
0000s0sFoswiki::Store::VC::Handler::::repRevFoswiki::Store::VC::Handler::repRev
0000s0sFoswiki::Store::VC::Handler::::replaceRevisionFoswiki::Store::VC::Handler::replaceRevision
0000s0sFoswiki::Store::VC::Handler::::restoreLatestRevisionFoswiki::Store::VC::Handler::restoreLatestRevision
0000s0sFoswiki::Store::VC::Handler::::revisionExistsFoswiki::Store::VC::Handler::revisionExists
0000s0sFoswiki::Store::VC::Handler::::saveFileFoswiki::Store::VC::Handler::saveFile
0000s0sFoswiki::Store::VC::Handler::::saveStreamFoswiki::Store::VC::Handler::saveStream
0000s0sFoswiki::Store::VC::Handler::::setLeaseFoswiki::Store::VC::Handler::setLease
0000s0sFoswiki::Store::VC::Handler::::setLockFoswiki::Store::VC::Handler::setLock
0000s0sFoswiki::Store::VC::Handler::::stringifyFoswiki::Store::VC::Handler::stringify
0000s0sFoswiki::Store::VC::Handler::::synchroniseAttachmentsListFoswiki::Store::VC::Handler::synchroniseAttachmentsList
0000s0sFoswiki::Store::VC::Handler::::testFoswiki::Store::VC::Handler::test
0000s0sFoswiki::Store::_MemoryFile::::CLOSEFoswiki::Store::_MemoryFile::CLOSE
0000s0sFoswiki::Store::_MemoryFile::::READFoswiki::Store::_MemoryFile::READ
0000s0sFoswiki::Store::_MemoryFile::::READLINEFoswiki::Store::_MemoryFile::READLINE
0000s0sFoswiki::Store::_MemoryFile::::TIEHANDLEFoswiki::Store::_MemoryFile::TIEHANDLE
Call graph for these subroutines as a Graphviz dot language file.
Line State
ments
Time
on line
Calls Time
in subs
Code
1# See bottom of file for license and copyright information
2
3=begin TML
4
5---+ package Foswiki::Store::VC::Handler
6
7This class is PACKAGE PRIVATE to Store::VC, and should never be
8used from anywhere else. It is the base class of implementations of
9individual file handler objects used with stores that manipulate
10files stored in a version control system (phew!).
11
12The general contract of the methods on this class and its subclasses
13calls for errors to be signalled by Error::Simple exceptions.
14
15There are a number of references to RCS below; however this class is
16useful as a base class for handlers for all kinds of version control
17systems which use files on disk.
18
19For readers who are familiar with Foswiki version 1.0.0, this class
20is analagous to =Foswiki::Store::RcsFile=.
21
22=cut
23
24package Foswiki::Store::VC::Handler;
25
26245µs238µs
# spent 31µs (24+7) within Foswiki::Store::VC::Handler::BEGIN@26 which was called: # once (24µs+7µs) by Foswiki::Store::VC::RcsWrapHandler::BEGIN@22 at line 26
use strict;
# spent 31µs making 1 call to Foswiki::Store::VC::Handler::BEGIN@26 # spent 7µs making 1 call to strict::import
27242µs250µs
# spent 33µs (16+17) within Foswiki::Store::VC::Handler::BEGIN@27 which was called: # once (16µs+17µs) by Foswiki::Store::VC::RcsWrapHandler::BEGIN@22 at line 27
use warnings;
# spent 33µs making 1 call to Foswiki::Store::VC::Handler::BEGIN@27 # spent 17µs making 1 call to warnings::import
28245µs296µs
# spent 56µs (16+40) within Foswiki::Store::VC::Handler::BEGIN@28 which was called: # once (16µs+40µs) by Foswiki::Store::VC::RcsWrapHandler::BEGIN@22 at line 28
use Assert;
# spent 56µs making 1 call to Foswiki::Store::VC::Handler::BEGIN@28 # spent 40µs making 1 call to Assert::import
29
30243µs19µs
# spent 9µs within Foswiki::Store::VC::Handler::BEGIN@30 which was called: # once (9µs+0s) by Foswiki::Store::VC::RcsWrapHandler::BEGIN@22 at line 30
use IO::File ();
# spent 9µs making 1 call to Foswiki::Store::VC::Handler::BEGIN@30
312182µs13.96ms
# spent 3.96ms (3.76+204µs) within Foswiki::Store::VC::Handler::BEGIN@31 which was called: # once (3.76ms+204µs) by Foswiki::Store::VC::RcsWrapHandler::BEGIN@22 at line 31
use File::Copy ();
# spent 3.96ms making 1 call to Foswiki::Store::VC::Handler::BEGIN@31
32239µs110µs
# spent 10µs within Foswiki::Store::VC::Handler::BEGIN@32 which was called: # once (10µs+0s) by Foswiki::Store::VC::RcsWrapHandler::BEGIN@22 at line 32
use File::Spec ();
# spent 10µs making 1 call to Foswiki::Store::VC::Handler::BEGIN@32
33244µs19µs
# spent 9µs within Foswiki::Store::VC::Handler::BEGIN@33 which was called: # once (9µs+0s) by Foswiki::Store::VC::RcsWrapHandler::BEGIN@22 at line 33
use File::Path ();
# spent 9µs making 1 call to Foswiki::Store::VC::Handler::BEGIN@33
34254µs23.69ms
# spent 1.86ms (25µs+1.83) within Foswiki::Store::VC::Handler::BEGIN@34 which was called: # once (25µs+1.83ms) by Foswiki::Store::VC::RcsWrapHandler::BEGIN@22 at line 34
use Fcntl qw( :DEFAULT :flock SEEK_SET );
# spent 1.86ms making 1 call to Foswiki::Store::VC::Handler::BEGIN@34 # spent 1.83ms making 1 call to Exporter::import
35
36240µs110µs
# spent 10µs within Foswiki::Store::VC::Handler::BEGIN@36 which was called: # once (10µs+0s) by Foswiki::Store::VC::RcsWrapHandler::BEGIN@22 at line 36
use Foswiki::Store ();
# spent 10µs making 1 call to Foswiki::Store::VC::Handler::BEGIN@36
37239µs19µs
# spent 9µs within Foswiki::Store::VC::Handler::BEGIN@37 which was called: # once (9µs+0s) by Foswiki::Store::VC::RcsWrapHandler::BEGIN@22 at line 37
use Foswiki::Sandbox ();
# spent 9µs making 1 call to Foswiki::Store::VC::Handler::BEGIN@37
382193µs1688µs
# spent 688µs (545+143) within Foswiki::Store::VC::Handler::BEGIN@38 which was called: # once (545µs+143µs) by Foswiki::Store::VC::RcsWrapHandler::BEGIN@22 at line 38
use Foswiki::Iterator::NumberRangeIterator ();
# spent 688µs making 1 call to Foswiki::Store::VC::Handler::BEGIN@38
39
40# use the locale if required to ensure sort order is correct
41
# spent 10µs within Foswiki::Store::VC::Handler::BEGIN@41 which was called: # once (10µs+0s) by Foswiki::Store::VC::RcsWrapHandler::BEGIN@22 at line 46
BEGIN {
42110µs if ( $Foswiki::cfg{UseLocale} ) {
43 require locale;
44 import locale();
45 }
4619.05ms110µs}
# spent 10µs making 1 call to Foswiki::Store::VC::Handler::BEGIN@41
47
48=begin TML
49
50---++ ClassMethod new($store, $web, $topic, $attachment)
51
52Constructor. There is one object per stored file.
53
54$store is the Foswiki::VC::Store object that contains the cache for
55objects of this type. A cache is used because at some point we'll be
56smarter about the number of calls to RCS code we make.
57
58Note that $web, $topic and $attachment must be untainted!
59
60=cut
61
62
# spent 20.0ms (15.7+4.29) within Foswiki::Store::VC::Handler::new which was called 347 times, avg 58µs/call: # 347 times (15.7ms+4.29ms) by Foswiki::Store::VC::RcsWrapHandler::new at line 28 of /usr/local/src/github.com/foswiki/core/lib/Foswiki/Store/VC/RcsWrapHandler.pm, avg 58µs/call
sub new {
63377515.7ms my ( $class, $store, $web, $topic, $attachment ) = @_;
64
653471.20ms ASSERT( $store->isa('Foswiki::Store') ) if DEBUG;
# spent 1.20ms making 347 calls to Assert::ASSERTS_OFF, avg 3µs/call
66
673471.03ms if ( UNIVERSAL::isa( $web, 'Foswiki::Meta' ) ) {
# spent 1.03ms making 347 calls to UNIVERSAL::isa, avg 3µs/call
68
69 # $web refers to a meta object
70 $attachment = $topic;
7173538µs $topic = $web->topic();
# spent 538µs making 73 calls to Foswiki::Meta::topic, avg 7µs/call
7273526µs $web = $web->web();
# spent 526µs making 73 calls to Foswiki::Meta::web, avg 7µs/call
73 }
74
75 # Reuse is good
76 my $id = ( $web || 0 ) . '/' . ( $topic || 0 ) . '/' . ( $attachment || 0 );
77 if ( $store->{handler_cache} && $store->{handler_cache}->{$id} ) {
78 return $store->{handler_cache}->{$id};
79 }
80
81 my $this =
82 bless( { web => $web, topic => $topic, attachment => $attachment },
83 $class );
84
85 # Cache so we can re-use this object (it has no internal state
86 # so can safely be reused)
87 $store->{handler_cache}->{$id} = $this;
88
89 if ( $web && $topic ) {
90 my $rcsSubDir = ( $Foswiki::cfg{RCS}{useSubDir} ? '/RCS' : '' );
91
92147539µs ASSERT( UNTAINTED($web), "web $web is tainted!" ) if DEBUG;
# spent 539µs making 147 calls to Assert::ASSERTS_OFF, avg 4µs/call
93147456µs ASSERT( UNTAINTED($topic), "topic $topic is tainted!" ) if DEBUG;
# spent 456µs making 147 calls to Assert::ASSERTS_OFF, avg 3µs/call
94 if ($attachment) {
95 ASSERT( UNTAINTED($attachment) ) if DEBUG;
96 $this->{file} =
97 $Foswiki::cfg{PubDir} . '/'
98 . $web . '/'
99 . $topic . '/'
100 . $attachment;
101 $this->{rcsFile} =
102 $Foswiki::cfg{PubDir} . '/'
103 . $web . '/'
104 . $topic
105 . $rcsSubDir . '/'
106 . $attachment . ',v';
107
108 }
109 else {
110 $this->{file} =
111 $Foswiki::cfg{DataDir} . '/' . $web . '/' . $topic . '.txt';
112 $this->{rcsFile} =
113 $Foswiki::cfg{DataDir} . '/'
114 . $web
115 . $rcsSubDir . '/'
116 . $topic
117 . '.txt,v';
118 }
119 }
120
121 # Default to remembering changes for a month
122 $Foswiki::cfg{Store}{RememberChangesFor} ||= 31 * 24 * 60 * 60;
123
124 return $this;
125}
126
127=begin TML
128
129---++ ObjectMethod finish()
130Break circular references.
131
132=cut
133
134# Note to developers; please undef *all* fields in the object explicitly,
135# whether they are references or not. That way this method is "golden
136# documentation" of the live fields in the object.
137sub finish {
138 my $this = shift;
139 undef $this->{file};
140 undef $this->{rcsFile};
141 undef $this->{web};
142 undef $this->{topic};
143 undef $this->{attachment};
144}
145
146# Used in subclasses for late initialisation during object creation
147# (after the object is blessed into the subclass)
148sub init {
149 my $this = shift;
150
151 return unless $this->{topic};
152
153 unless ( -e $this->{file} ) {
154 if ( $this->{attachment} && !$this->isAsciiDefault() ) {
155 $this->initBinary();
156 }
157 else {
158 $this->initText();
159 }
160 }
161}
162
163# Make any missing paths on the way to this file
164sub mkPathTo {
165
166 my ( $this, $file ) = @_;
167
168 $file = Foswiki::Sandbox::untaintUnchecked($file);
169
170 ASSERT( File::Spec->file_name_is_absolute($file) ) if DEBUG;
171
172 my ( $volume, $path, undef ) = File::Spec->splitpath($file);
173 $path = File::Spec->catpath( $volume, $path, '' );
174
175# SMELL: Sites running Apache with SuexecUserGroup will have a forced "safe" umask
176# Override umask here to allow correct dirPermissions to be applied
177 umask( oct(777) - $Foswiki::cfg{RCS}{dirPermission} );
178
179 eval { File::Path::mkpath( $path, 0, $Foswiki::cfg{RCS}{dirPermission} ); };
180 if ($@) {
181 throw Error::Simple("VC::Handler: failed to create ${path}: $!");
182 }
183}
184
185sub _epochToRcsDateTime {
186 my ($dateTime) = @_;
187
188 # TODO: should this be gmtime or local time?
189 my ( $sec, $min, $hour, $mday, $mon, $year, $wday, $yday ) =
190 gmtime($dateTime);
191 $year += 1900 if ( $year > 99 );
192 my $rcsDateTime = sprintf '%d.%02d.%02d.%02d.%02d.%02d',
193 ( $year, $mon + 1, $mday, $hour, $min, $sec );
194 return $rcsDateTime;
195}
196
197# filenames for lock and lease files
198sub _controlFileName {
199 my ( $this, $type ) = @_;
200
201 my $fn = $this->{file} || '';
202 $fn =~ s/txt$/$type/;
203 return $fn;
204}
205
206=begin TML
207
208---++ ObjectMethod getInfo($version) -> \%info
209
210 * =$version= if 0 or undef, or out of range (version number > number of revs) will return info about the latest revision.
211
212Returns info where version is the number of the rev for which the info was recovered, date is the date of that rev (epoch s), user is the canonical user ID of the user who saved that rev, and comment is the comment associated with the rev.
213
214Designed to be overridden by subclasses, which can call up to this method
215if simple file-based rev info is required.
216
217=cut
218
219
# spent 22.5s (1.57ms+22.5) within Foswiki::Store::VC::Handler::getInfo which was called 21 times, avg 1.07s/call: # 21 times (1.57ms+22.5s) by Foswiki::Store::VC::RcsWrapHandler::getInfo at line 352 of /usr/local/src/github.com/foswiki/core/lib/Foswiki/Store/VC/RcsWrapHandler.pm, avg 1.07s/call
sub getInfo {
2202451.59ms my $this =
221 shift; # $version is not useful here, as we have no way to record history
222
223 # SMELL: this is only required for the constant
224 require Foswiki::Users::BaseUserMapping;
225
226 # We only arrive here if the implementation getInfo can't serve the info; this
227 # will usually be because the ,v is missing or the topic cache is newer.
228
229 # If there is a .txt file, grab the TOPICINFO from it.
230 # Note that we only peek at the first line of the file,
231 # which is where a "proper" save will have left the tag.
232 my $info = {};
233421.41ms if ( $this->noCheckinPending() ) {
# spent 1.33ms making 21 calls to Foswiki::Store::VC::Handler::noCheckinPending, avg 63µs/call # spent 83µs making 21 calls to Foswiki::Store::VC::Handler::CORE:ftis, avg 4µs/call
234
235 # TOPICINFO may be OK
236 $this->_getTOPICINFO($info);
237 }
238 elsif ( -e $this->{rcsFile} ) {
239
240 # There is a checkin pending, and there is an rcs file.
241 # Ignore TOPICINFO
242722.5s $info->{version} = $this->_numRevisions() + 1;
# spent 22.5s making 7 calls to Foswiki::Store::VC::RcsWrapHandler::_numRevisions, avg 3.21s/call
243 $info->{comment} = "pending";
244 }
245 else {
246
247# There is a checkin pending, but no RCS file. Make the best we can of TOPICINFO.
248148.62ms $this->_getTOPICINFO($info);
# spent 8.62ms making 14 calls to Foswiki::Store::VC::Handler::_getTOPICINFO, avg 616µs/call
249 $info->{version} = 1;
250 $info->{comment} = "pending";
251 }
2528652µs $info->{date} = $this->getTimestamp() unless defined $info->{date};
# spent 652µs making 8 calls to Foswiki::Store::VC::Handler::getTimestamp, avg 81µs/call
253 $info->{version} = 1 unless defined $info->{version};
254 $info->{comment} = '' unless defined $info->{comment};
255 $info->{author} ||= $Foswiki::Users::BaseUserMapping::UNKNOWN_USER_CUID;
256 return $info;
257}
258
259# Try and read TOPICINFO
260
# spent 8.62ms (3.28+5.34) within Foswiki::Store::VC::Handler::_getTOPICINFO which was called 14 times, avg 616µs/call: # 14 times (3.28ms+5.34ms) by Foswiki::Store::VC::Handler::getInfo at line 248, avg 616µs/call
sub _getTOPICINFO {
2611762.59ms my ( $this, $info ) = @_;
262 my $f;
263
26414268µs if ( open( $f, '<', $this->{file} ) ) {
# spent 268µs making 14 calls to Foswiki::Store::VC::Handler::CORE:open, avg 19µs/call
265 local $/ = "\n";
26614190µs my $ti = <$f>;
# spent 190µs making 14 calls to Foswiki::Store::VC::Handler::CORE:readline, avg 14µs/call
2671475µs close($f);
# spent 75µs making 14 calls to Foswiki::Store::VC::Handler::CORE:close, avg 5µs/call
26814258µs if ( defined $ti && $ti =~ /^%META:TOPICINFO{(.*)}%/ ) {
# spent 258µs making 14 calls to Foswiki::Store::VC::Handler::CORE:match, avg 18µs/call
269 require Foswiki::Attrs;
270133.82ms my $a = Foswiki::Attrs->new($1);
# spent 3.82ms making 13 calls to Foswiki::Attrs::new, avg 294µs/call
271
272 # Default bad revs to 1, not 0, because this is coming from
273 # a topic on disk, so we know it's a "real" rev.
27413468µs $info->{version} = Foswiki::Store::cleanUpRevID( $a->{version} )
# spent 468µs making 13 calls to Foswiki::Store::cleanUpRevID, avg 36µs/call
275 || 1;
276 $info->{date} = $a->{date};
277 $info->{author} = $a->{author};
278 $info->{comment} = $a->{comment};
279 }
280 }
281}
282
283# Check to see if there is a newer non-,v file waiting to be checked in. If there is, then
284# all rev numbers have to be incremented, as they will auto-increment when it is finally
285# checked in (usually as the result of a save). This is also used to test the validity of
286# TOPICINFO, as a pending checkin does not contain valid TOPICINFO.
287
# spent 6.36ms (4.18+2.18) within Foswiki::Store::VC::Handler::noCheckinPending which was called 117 times, avg 54µs/call: # 70 times (2.59ms+1.31ms) by Foswiki::Store::VC::Store::readTopic at line 112 of /usr/local/src/github.com/foswiki/core/lib/Foswiki/Store/VC/Store.pm, avg 56µs/call # 21 times (822µs+508µs) by Foswiki::Store::VC::Handler::getInfo at line 233, avg 63µs/call # 21 times (511µs+232µs) by Foswiki::Store::VC::RcsWrapHandler::getInfo at line 320 of /usr/local/src/github.com/foswiki/core/lib/Foswiki/Store/VC/RcsWrapHandler.pm, avg 35µs/call # 5 times (262µs+132µs) by Foswiki::Store::VC::Handler::getLatestRevisionID at line 453, avg 79µs/call
sub noCheckinPending {
2888816.40ms my $this = shift;
289 my $isValid = 0;
290
291117934µs if ( !-e $this->{file} ) {
# spent 934µs making 117 calls to Foswiki::Store::VC::Handler::CORE:ftis, avg 8µs/call
292 $isValid = 1; # Hmmmm......
293 }
294 else {
295117510µs if ( -e $this->{rcsFile} ) {
# spent 510µs making 117 calls to Foswiki::Store::VC::Handler::CORE:ftis, avg 4µs/call
296
297# Check the time on the rcs file; is the .txt newer?
298# Danger, Will Robinson! stat isn't reliable on all file systems, though [9] is claimed to be OK
299# See perldoc perlport for more on this.
300 local ${^WIN32_SLOPPY_STAT} =
301 1; # don't need to open the file on Win32
30274411µs my $rcsTime = ( stat( $this->{rcsFile} ) )[9];
# spent 411µs making 74 calls to Foswiki::Store::VC::Handler::CORE:stat, avg 6µs/call
30374328µs my $fileTime = ( stat( $this->{file} ) )[9];
# spent 328µs making 74 calls to Foswiki::Store::VC::Handler::CORE:stat, avg 4µs/call
304 $isValid = ( $rcsTime < $fileTime ) ? 0 : 1;
305 }
306 }
307 return $isValid;
308}
309
310# Must be implemented by subclasses
311sub ci {
312 die "Pure virtual method";
313}
314
315# Protected for use only in subclasses. Check that the object has a history
316# and the .txt is consistent with that history.
317sub _saveDamage {
318 my $this = shift;
319 return if $this->noCheckinPending();
320
321 # the version in the TOPICINFO may not be correct. We need
322 # to check the change in and update the TOPICINFO accordingly
323 my $t = $this->readFile( $this->{file} );
324
325 # If this is a topic, adjust the TOPICINFO
326 if ( defined $this->{topic} && !defined $this->{attachment} ) {
327 my $rev = -e $this->{rcsFile} ? $this->getLatestRevisionID() : 1;
328 $t =~ s/^%META:TOPICINFO{(.*)}%$//m;
329 $t =
330 '%META:TOPICINFO{author="'
331 . $Foswiki::Users::BaseUserMapping::UNKNOWN_USER_CUID
332 . '" comment="autosave" date="'
333 . time()
334 . '" format="1.1" version="'
335 . $rev . '"}%' . "\n$t";
336 }
337 $this->ci( 0, $t, 'autosave',
338 $Foswiki::Users::BaseUserMapping::UNKNOWN_USER_CUID, time() );
339}
340
341=begin TML
342
343---++ ObjectMethod addRevisionFromText($text, $comment, $cUID, $date)
344
345Add new revision. Replace file with text.
346 * =$text= of new revision
347 * =$comment= checkin comment
348 * =$cUID= is a cUID.
349 * =$date= in epoch seconds; may be ignored
350
351=cut
352
353sub addRevisionFromText {
354 my ( $this, $text, $comment, $user, $date ) = @_;
355 $this->init();
356
357 # Commit any out-of-band damage to .txt
358 $this->_saveDamage();
359 $this->ci( 0, $text, $comment, $user, $date );
360}
361
362=begin TML
363
364---++ ObjectMethod addRevisionFromStream($fh, $comment, $cUID, $date)
365
366Add new revision. Replace file with contents of stream.
367 * =$fh= filehandle for contents of new revision
368 * =$cUID= is a cUID.
369 * =$date= in epoch seconds; may be ignored
370
371=cut
372
373sub addRevisionFromStream {
374 my ( $this, $stream, $comment, $user, $date ) = @_;
375 $this->init();
376
377 # Commit any out-of-band damage to .txt
378 $this->_saveDamage();
379
380 $this->ci( 1, $stream, $comment, $user, $date );
381}
382
383=begin TML
384
385---++ ObjectMethod replaceRevision($text, $comment, $cUID, $date)
386
387Replace the top revision.
388 * =$text= is the new revision
389 * =$date= is in epoch seconds.
390 * =$cUID= is a cUID.
391 * =$comment= is a string
392
393=cut
394
395sub replaceRevision {
396 my $this = shift;
397 $this->_saveDamage();
398 $this->repRev(@_);
399}
400
401# Signature as for replaceRevision
402sub repRev {
403 die "Pure virtual method";
404}
405
406=begin TML
407
408---++ ObjectMethod getRevisionHistory() -> $iterator
409
410Get an iterator over the identifiers of revisions. Returns the most
411recent revision first.
412
413The default is to return an iterator from the current version number
414down to 1. Return rev 1 if the file exists without history. Return
415an empty iterator if the file does not exist.
416
417=cut
418
419
# spent 620ms (134µs+620) within Foswiki::Store::VC::Handler::getRevisionHistory which was called 2 times, avg 310ms/call: # 2 times (134µs+620ms) by Foswiki::Store::VC::Store::getRevisionHistory at line 289 of /usr/local/src/github.com/foswiki/core/lib/Foswiki/Store/VC/Store.pm, avg 310ms/call
sub getRevisionHistory {
42010142µs my $this = shift;
42126µs ASSERT( $this->{file} ) if DEBUG;
# spent 6µs making 2 calls to Assert::ASSERTS_OFF, avg 3µs/call
422222µs unless ( -e $this->{rcsFile} ) {
# spent 22µs making 2 calls to Foswiki::Store::VC::Handler::CORE:ftis, avg 11µs/call
423 require Foswiki::ListIterator;
424 if ( -e $this->{file} ) {
425 return Foswiki::ListIterator->new( [1] );
426 }
427 else {
428 return Foswiki::ListIterator->new( [] );
429 }
430 }
431
432 # SMELL: what happens with the working file?
4332620ms my $maxRev = $this->getLatestRevisionID();
# spent 620ms making 2 calls to Foswiki::Store::VC::Handler::getLatestRevisionID, avg 310ms/call
434282µs return Foswiki::Iterator::NumberRangeIterator->new( $maxRev, 1 );
# spent 82µs making 2 calls to Foswiki::Iterator::NumberRangeIterator::new, avg 41µs/call
435}
436
437=begin TML
438
439---++ ObjectMethod getLatestRevisionID() -> $id
440
441Get the ID of the most recent revision. This may return undef if there have
442been no revisions committed to the store.
443
444=cut
445
446
# spent 1.41s (270µs+1.41) within Foswiki::Store::VC::Handler::getLatestRevisionID which was called 5 times, avg 282ms/call: # 3 times (184µs+790ms) by Foswiki::Store::VC::Store::readTopic at line 132 of /usr/local/src/github.com/foswiki/core/lib/Foswiki/Store/VC/Store.pm, avg 263ms/call # 2 times (87µs+620ms) by Foswiki::Store::VC::Handler::getRevisionHistory at line 433, avg 310ms/call
sub getLatestRevisionID {
44725319µs my $this = shift;
448523µs return 0 unless -e $this->{file};
# spent 23µs making 5 calls to Foswiki::Store::VC::Handler::CORE:ftis, avg 5µs/call
44951.41s my $rev = $this->_numRevisions() || 1;
# spent 1.41s making 5 calls to Foswiki::Store::VC::RcsWrapHandler::_numRevisions, avg 282ms/call
450
451 # If there is a pending pseudo-revision, need n+1, but only if there is
452 # an existing history
4538411µs $rev++ unless $this->noCheckinPending() || !-e $this->{rcsFile};
# spent 393µs making 5 calls to Foswiki::Store::VC::Handler::noCheckinPending, avg 79µs/call # spent 18µs making 3 calls to Foswiki::Store::VC::Handler::CORE:ftis, avg 6µs/call
454 return $rev;
455}
456
457=begin TML
458
459---++ ObjectMethod getNextRevisionID() -> $id
460
461Get the ID of the next (as yet uncreated) revision. The handler is required
462to implement this because the store has to be able to embed the revision
463ID into TOPICINFO before the revision is actually created.
464
465If the file exists without revisions, then rev 1 does exist, so next rev
466should be rev 2, not rev 1, so the first change with missing history
467doesn't get merged into rev 1.
468
469=cut
470
471sub getNextRevisionID {
472 my $this = shift;
473 return $this->getLatestRevisionID() + 1;
474}
475
476=begin TML
477
478---++ ObjectMethod getLatestRevisionTime() -> $text
479
480Get the time of the most recent revision
481
482=cut
483
484sub getLatestRevisionTime {
485 my @e = stat( shift->{file} );
486 return $e[9] || 0;
487}
488
489=begin TML
490
491---++ ObjectMethod getTopicNames() -> @topics
492
493Get list of all topics in a web
494 * =$web= - Web name, required, e.g. ='Sandbox'=
495Return a topic list, e.g. =( 'WebChanges', 'WebHome', 'WebIndex', 'WebNotify' )=
496
497=cut
498
499
# spent 169ms (108+60.5) within Foswiki::Store::VC::Handler::getTopicNames which was called: # once (108ms+60.5ms) by Foswiki::Store::VC::Store::eachTopic at line 493 of /usr/local/src/github.com/foswiki/core/lib/Foswiki/Store/VC/Store.pm
sub getTopicNames {
50013752169ms my $this = shift;
501 my $dh;
502183µs opendir( $dh, "$Foswiki::cfg{DataDir}/$this->{web}" )
# spent 83µs making 1 call to Foswiki::Store::VC::Handler::CORE:open_dir
503 or return ();
504
505 # the name filter is used to ensure we don't return filenames
506 # that contain illegal characters as topic names.
50734259.73ms my @topicList =
# spent 9.73ms making 3425 calls to Foswiki::Store::VC::Handler::CORE:match, avg 3µs/call
5082068831.8ms map { /^(.*)\.txt$/; $1; }
# spent 23.0ms making 13792 calls to Foswiki::Store::VC::Handler::CORE:match, avg 2µs/call # spent 8.81ms making 6896 calls to Foswiki::Store::VC::Handler::CORE:regcomp, avg 1µs/call
509 sort
510218.9ms grep { !/$Foswiki::cfg{NameFilter}/ && /\.txt$/ } readdir($dh);
# spent 10.3ms making 1 call to Foswiki::Store::VC::Handler::CORE:readdir # spent 8.52ms making 1 call to Foswiki::Store::VC::Handler::CORE:sort
511142µs closedir($dh);
# spent 42µs making 1 call to Foswiki::Store::VC::Handler::CORE:closedir
512 return @topicList;
513}
514
515=begin TML
516
517---++ ObjectMethod revisionExists($rev) -> $boolean
518
519Determine if the identified revision actually exists in the object
520history.
521
522=cut
523
524sub revisionExists {
525 my ( $this, $rev ) = @_;
526
527 # Rev numbers run from 1 to numRevisions
528 return $rev && $rev <= $this->_numRevisions();
529}
530
531=begin TML
532
533---++ ObjectMethod getWebNames() -> @webs
534
535Gets a list of names of subwebs in the current web
536
537=cut
538
539sub getWebNames {
540 my $this = shift;
541 my $dir = $Foswiki::cfg{DataDir};
542 $dir .= '/' . $this->{web} if defined $this->{web};
543 my @tmpList;
544 my $dh;
545
546 if ( opendir( $dh, $dir ) ) {
547 @tmpList = map {
548 Foswiki::Sandbox::untaint( $_, \&Foswiki::Sandbox::validateWebName )
549 }
550
551 # The -e on the web preferences is used in preference to a
552 # -d to avoid having to validate the web name each time. Since
553 # the definition of a Web in this handler is "a directory with a
554 # WebPreferences.txt in it", this works.
555 grep { !/\./ && -e "$dir/$_/$Foswiki::cfg{WebPrefsTopicName}.txt" }
556 readdir($dh);
557 closedir($dh);
558 }
559
560 return @tmpList;
561}
562
563=begin TML
564
565---++ ObjectMethod moveWeb( $newWeb )
566
567Move a web.
568
569=cut
570
571sub moveWeb {
572 my ( $this, $newWeb ) = @_;
573 $this->moveFile(
574 $Foswiki::cfg{DataDir} . '/' . $this->{web},
575 $Foswiki::cfg{DataDir} . '/' . $newWeb
576 );
577 if ( -d $Foswiki::cfg{PubDir} . '/' . $this->{web} ) {
578 $this->moveFile(
579 $Foswiki::cfg{PubDir} . '/' . $this->{web},
580 $Foswiki::cfg{PubDir} . '/' . $newWeb
581 );
582 }
583}
584
585=begin TML
586
587---++ ObjectMethod getRevision($version) -> ($text, $isLatest)
588
589 * =$version= if 0 or undef, or out of range (version number > number of revs) will return the latest revision.
590
591Get the text of the given revision, and a flag indicating if this is the
592most recent revision.
593
594Designed to be overridden by subclasses, which can call up to this method
595if the main file revision is required.
596
597=cut
598
599
# spent 10.8ms (1.93+8.84) within Foswiki::Store::VC::Handler::getRevision which was called 70 times, avg 154µs/call: # 70 times (1.93ms+8.84ms) by Foswiki::Store::VC::RcsWrapHandler::getRevision at line 250 of /usr/local/src/github.com/foswiki/core/lib/Foswiki/Store/VC/RcsWrapHandler.pm, avg 154µs/call
sub getRevision {
6002101.91ms my ($this) = @_;
60170430µs if ( defined $this->{file} && -e $this->{file} ) {
# spent 430µs making 70 calls to Foswiki::Store::VC::Handler::CORE:ftis, avg 6µs/call
602708.41ms return ( readFile( $this, $this->{file} ), 1 );
# spent 8.41ms making 70 calls to Foswiki::Store::VC::Handler::readFile, avg 120µs/call
603 }
604 return ( undef, 0 );
605}
606
607=begin TML
608
609---++ ObjectMethod storedDataExists() -> $boolean
610
611Establishes if there is stored data associated with this handler.
612
613=cut
614
615
# spent 5.19ms (3.27+1.92) within Foswiki::Store::VC::Handler::storedDataExists which was called 274 times, avg 19µs/call: # 272 times (3.23ms+1.90ms) by Foswiki::Store::VC::Store::topicExists at line 463 of /usr/local/src/github.com/foswiki/core/lib/Foswiki/Store/VC/Store.pm, avg 19µs/call # 2 times (35µs+26µs) by Foswiki::Store::VC::Store::webExists at line 452 of /usr/local/src/github.com/foswiki/core/lib/Foswiki/Store/VC/Store.pm, avg 30µs/call
sub storedDataExists {
6168225.40ms my $this = shift;
617 return 0 unless $this->{file};
6182741.92ms return -e $this->{file};
# spent 1.92ms making 274 calls to Foswiki::Store::VC::Handler::CORE:ftis, avg 7µs/call
619}
620
621=begin TML
622
623---++ ObjectMethod restoreLatestRevision( $cUID )
624
625Restore the plaintext file from the revision at the head.
626
627=cut
628
629sub restoreLatestRevision {
630 my ( $this, $cUID ) = @_;
631
632 my $rev = $this->getLatestRevisionID();
633 my ($text) = $this->getRevision($rev);
634
635 # If there is no ,v, create it
636 unless ( -e $this->{rcsFile} ) {
637 $this->addRevisionFromText( $text, "restored", $cUID, time() );
638 }
639 else {
640 saveFile( $this, $this->{file}, $text );
641 }
642}
643
644=begin TML
645
646---++ ObjectMethod remove()
647
648Destroy, utterly. Remove the data and attachments in the web.
649
650Use with great care! No backup is taken!
651
652=cut
653
654sub remove {
655 my $this = shift;
656
657 if ( !$this->{topic} ) {
658
659 # Web
660 _rmtree( $Foswiki::cfg{DataDir} . '/' . $this->{web} );
661 _rmtree( $Foswiki::cfg{PubDir} . '/' . $this->{web} );
662 }
663 else {
664
665 # Topic or attachment
666 unlink( $this->{file} );
667 unlink( $this->{rcsFile} );
668 if ( !$this->{attachment} ) {
669 _rmtree($Foswiki::cfg{PubDir} . '/'
670 . $this->{web} . '/'
671 . $this->{topic} );
672 }
673 }
674}
675
676=begin TML
677
678---++ ObjectMethod moveTopic( $store, $newWeb, $newTopic )
679
680Move/rename a topic.
681
682=cut
683
684sub moveTopic {
685 my ( $this, $store, $newWeb, $newTopic ) = @_;
686
687 ASSERT( $store->isa('Foswiki::Store') ) if DEBUG;
688
689 my $oldWeb = $this->{web};
690 my $oldTopic = $this->{topic};
691
692 # Move data file
693 my $new = $store->getHandler( $newWeb, $newTopic );
694 $this->moveFile( $this->{file}, $new->{file} );
695
696 # Move history
697 $this->mkPathTo( $new->{rcsFile} );
698 if ( -e $this->{rcsFile} ) {
699 $this->moveFile( $this->{rcsFile}, $new->{rcsFile} );
700 }
701
702 # Move attachments
703 my $from =
704 $Foswiki::cfg{PubDir} . '/' . $this->{web} . '/' . $this->{topic};
705 if ( -e $from ) {
706 my $to = $Foswiki::cfg{PubDir} . '/' . $newWeb . '/' . $newTopic;
707 $this->moveFile( $from, $to );
708 }
709}
710
711=begin TML
712
713---++ ObjectMethod copyTopic( $store, $newWeb, $newTopic )
714
715Copy a topic.
716
717=cut
718
719sub copyTopic {
720 my ( $this, $store, $newWeb, $newTopic ) = @_;
721
722 ASSERT( $store->isa('Foswiki::Store') ) if DEBUG;
723
724 my $oldWeb = $this->{web};
725 my $oldTopic = $this->{topic};
726
727 my $new = $store->getHandler( $newWeb, $newTopic );
728
729 $this->copyFile( $this->{file}, $new->{file} );
730 if ( -e $this->{rcsFile} ) {
731 $this->copyFile( $this->{rcsFile}, $new->{rcsFile} );
732 }
733
734 my $dh;
735 if ( opendir( $dh, "$Foswiki::cfg{PubDir}/$this->{web}/$this->{topic}" ) ) {
736 for my $att ( grep { !/^\./ } readdir $dh ) {
737 $att = Foswiki::Sandbox::untaint( $att,
738 \&Foswiki::Sandbox::validateAttachmentName );
739 my $oldAtt =
740 $store->getHandler( $this->{web}, $this->{topic}, $att );
741 $oldAtt->copyAttachment( $store, $newWeb, $newTopic );
742 }
743
744 closedir $dh;
745 }
746}
747
748=begin TML
749
750---++ ObjectMethod moveAttachment( $store, $newWeb, $newTopic, $newAttachment )
751
752Move an attachment from one topic to another. The name is retained.
753
754=cut
755
756sub moveAttachment {
757 my ( $this, $store, $newWeb, $newTopic, $newAttachment ) = @_;
758
759 ASSERT( $store->isa('Foswiki::Store') ) if DEBUG;
760
761 # FIXME might want to delete old directories if empty
762 my $new = $store->getHandler( $newWeb, $newTopic, $newAttachment );
763
764 $this->moveFile( $this->{file}, $new->{file} );
765
766 if ( -e $this->{rcsFile} ) {
767 $this->moveFile( $this->{rcsFile}, $new->{rcsFile} );
768 }
769}
770
771=begin TML
772
773---++ ObjectMethod copyAttachment( $store, $newWeb, $newTopic, $newAttachment )
774
775Copy an attachment from one topic to another. The name is retained unless
776$newAttachment is defined.
777
778=cut
779
780sub copyAttachment {
781 my ( $this, $store, $newWeb, $newTopic, $attachment ) = @_;
782
783 ASSERT( $store->isa('Foswiki::Store') ) if DEBUG;
784
785 my $oldWeb = $this->{web};
786 my $oldTopic = $this->{topic};
787 $attachment ||= $this->{attachment};
788
789 my $new = $store->getHandler( $newWeb, $newTopic, $attachment );
790
791 $this->copyFile( $this->{file}, $new->{file} );
792
793 if ( -e $this->{rcsFile} ) {
794 $this->copyFile( $this->{rcsFile}, $new->{rcsFile} );
795 }
796}
797
798=begin TML
799
800---++ ObjectMethod isAsciiDefault ( ) -> $boolean
801
802Check if this file type is known to be an ascii type file.
803
804=cut
805
806sub isAsciiDefault {
807 my $this = shift;
808 return ( $this->{attachment} =~ /$Foswiki::cfg{RCS}{asciiFileSuffixes}/ );
809}
810
811=begin TML
812
813---++ ObjectMethod setLock($lock, $cUID)
814
815Set a lock on the topic, if $lock, otherwise clear it.
816$cUID is a cUID.
817
818SMELL: there is a tremendous amount of potential for race
819conditions using this locking approach.
820
821It would be nice to use flock to do this, but the API is unreliable
822(doesn't work on all platforms)
823
824=cut
825
826sub setLock {
827 my ( $this, $lock, $cUID ) = @_;
828
829 my $filename = _controlFileName( $this, 'lock' );
830 if ($lock) {
831 my $lockTime = time();
832 saveFile( $this, $filename, $cUID . "\n" . $lockTime );
833 }
834 else {
835 unlink $filename
836 || throw Error::Simple(
837 'VC::Handler: failed to delete ' . $filename . ': ' . $! );
838 }
839}
840
841=begin TML
842
843---++ ObjectMethod isLocked( ) -> ($cUID, $time)
844
845See if a lock exists. Return the lock user and lock time if it does.
846
847=cut
848
849sub isLocked {
850 my $this = shift;
851
852 my $filename = _controlFileName( $this, 'lock' );
853 if ( -e $filename ) {
854 my $t = readFile( $this, $filename );
855 return split( /\s+/, $t, 2 );
856 }
857 return ( undef, undef );
858}
859
860=begin TML
861
862---++ ObjectMethod setLease( $lease )
863
864 * =$lease= reference to lease hash, or undef if the existing lease is to be cleared.
865
866Set an lease on the topic.
867
868=cut
869
870sub setLease {
871 my ( $this, $lease ) = @_;
872
873 my $filename = _controlFileName( $this, 'lease' );
874 if ($lease) {
875 saveFile( $this, $filename, join( "\n", %$lease ) );
876 }
877 elsif ( -e $filename ) {
878 unlink $filename
879 || throw Error::Simple(
880 'VC::Handler: failed to delete ' . $filename . ': ' . $! );
881 }
882}
883
884=begin TML
885
886---++ ObjectMethod getLease() -> $lease
887
888Get the current lease on the topic.
889
890=cut
891
892sub getLease {
893 my ($this) = @_;
894
895 my $filename = _controlFileName( $this, 'lease' );
896 if ( -e $filename ) {
897 my $t = readFile( $this, $filename );
898 my $lease = { split( /\r?\n/, $t ) };
899 return $lease;
900 }
901 return;
902}
903
904=begin TML
905
906---++ ObjectMethod removeSpuriousLeases( $web )
907
908Remove leases that are not related to a topic. These can get left behind in
909some store implementations when a topic is created, but never saved.
910
911=cut
912
913sub removeSpuriousLeases {
914 my ($this) = @_;
915 my $web = $Foswiki::cfg{DataDir} . '/' . $this->{web} . '/';
916 if ( opendir( my $W, $web ) ) {
917 foreach my $f ( readdir($W) ) {
918 if ( $f =~ /^(.*)\.lease$/ ) {
919 if ( !-e "$1.txt,v" ) {
920 unlink($f);
921 }
922 }
923 }
924 closedir($W);
925 }
926}
927
928sub test {
929 my ( $this, $test ) = @_;
930 return eval "-$test '$this->{file}'";
931}
932
933# Used by subclasses
934sub saveStream {
935 my ( $this, $fh ) = @_;
936
937 ASSERT($fh) if DEBUG;
938
939 $this->mkPathTo( $this->{file} );
940 my $F;
941 open( $F, '>', $this->{file} )
942 || throw Error::Simple(
943 'VC::Handler: open ' . $this->{file} . ' failed: ' . $! );
944 binmode($F)
945 || throw Error::Simple(
946 'VC::Handler: failed to binmode ' . $this->{file} . ': ' . $! );
947 my $text;
948 while ( read( $fh, $text, 1024 ) ) {
949 print $F $text;
950 }
951 close($F)
952 || throw Error::Simple(
953 'VC::Handler: close ' . $this->{file} . ' failed: ' . $! );
954
955 chmod( $Foswiki::cfg{RCS}{filePermission}, $this->{file} );
956}
957
958sub copyFile {
959 my ( $this, $from, $to ) = @_;
960
961 $this->mkPathTo($to);
962 unless ( File::Copy::copy( $from, $to ) ) {
963 throw Error::Simple(
964 'VC::Handler: copy ' . $from . ' to ' . $to . ' failed: ' . $! );
965 }
966}
967
968sub moveFile {
969 my ( $this, $from, $to ) = @_;
970 ASSERT( -e $from ) if DEBUG;
971 $this->mkPathTo($to);
972 unless ( File::Copy::move( $from, $to ) ) {
973 throw Error::Simple(
974 'VC::Handler: move ' . $from . ' to ' . $to . ' failed: ' . $! );
975 }
976}
977
978# Used by subclasses
979sub saveFile {
980 my ( $this, $name, $text ) = @_;
981
982 $this->mkPathTo($name);
983 my $fh;
984 open( $fh, '>', $name )
985 or throw Error::Simple(
986 'VC::Handler: failed to create file ' . $name . ': ' . $! );
987 flock( $fh, LOCK_EX )
988 or throw Error::Simple(
989 'VC::Handler: failed to lock file ' . $name . ': ' . $! );
990 binmode($fh)
991 or throw Error::Simple(
992 'VC::Handler: failed to binmode ' . $name . ': ' . $! );
993 print $fh $text
994 or throw Error::Simple(
995 'VC::Handler: failed to print into ' . $name . ': ' . $! );
996 close($fh)
997 or throw Error::Simple(
998 'VC::Handler: failed to close file ' . $name . ': ' . $! );
999 return;
1000}
1001
1002# Used by subclasses
1003
# spent 8.41ms (4.58+3.83) within Foswiki::Store::VC::Handler::readFile which was called 70 times, avg 120µs/call: # 70 times (4.58ms+3.83ms) by Foswiki::Store::VC::Handler::getRevision at line 602, avg 120µs/call
sub readFile {
10047708.57ms my ( $this, $name ) = @_;
100570238µs ASSERT($name) if DEBUG;
# spent 238µs making 70 calls to Assert::ASSERTS_OFF, avg 3µs/call
1006 my $data;
1007 my $IN_FILE;
1008701.56ms if ( open( $IN_FILE, '<', $name ) ) {
# spent 1.56ms making 70 calls to Foswiki::Store::VC::Handler::CORE:open, avg 22µs/call
100970200µs binmode($IN_FILE);
# spent 200µs making 70 calls to Foswiki::Store::VC::Handler::CORE:binmode, avg 3µs/call
1010 local $/ = undef;
1011701.46ms $data = <$IN_FILE>;
# spent 1.46ms making 70 calls to Foswiki::Store::VC::Handler::CORE:readline, avg 21µs/call
101270374µs close($IN_FILE);
# spent 374µs making 70 calls to Foswiki::Store::VC::Handler::CORE:close, avg 5µs/call
1013 }
1014 $data ||= '';
1015 return $data;
1016}
1017
1018# Used by subclasses
1019sub mkTmpFilename {
1020 my $tmpdir = File::Spec->tmpdir();
1021 my $file = _mktemp( 'foswikiAttachmentXXXXXX', $tmpdir );
1022 return File::Spec->catfile( $tmpdir, $file );
1023}
1024
1025# Adapted from CPAN - File::MkTemp
1026sub _mktemp {
1027 my ( $template, $dir, $ext, $keepgen, $lookup );
1028 my ( @template, @letters );
1029
1030 ASSERT( @_ == 1 || @_ == 2 || @_ == 3 ) if DEBUG;
1031
1032 ( $template, $dir, $ext ) = @_;
1033 @template = split //, $template;
1034
1035 ASSERT( $template =~ /XXXXXX$/ ) if DEBUG;
1036
1037 if ($dir) {
1038 ASSERT( -e $dir ) if DEBUG;
1039 }
1040
1041 @letters =
1042 split( //, 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ' );
1043
1044 $keepgen = 1;
1045
1046 while ($keepgen) {
1047 for ( my $i = $#template ; $i >= 0 && ( $template[$i] eq 'X' ) ; $i-- )
1048 {
1049 $template[$i] = $letters[ int( rand 52 ) ];
1050 }
1051
1052 undef $template;
1053
1054 $template = pack 'a' x @template, @template;
1055
1056 $template = $template . $ext if ($ext);
1057
1058 if ($dir) {
1059 $lookup = File::Spec->catfile( $dir, $template );
1060 $keepgen = 0 unless ( -e $lookup );
1061 }
1062 else {
1063 $keepgen = 0;
1064 }
1065
1066 next if $keepgen == 0;
1067 }
1068
1069 return ($template);
1070}
1071
1072# remove a directory and all subdirectories.
1073sub _rmtree {
1074 my $root = shift;
1075 my $D;
1076
1077 if ( opendir( $D, $root ) ) {
1078 foreach my $entry ( grep { !/^\.+$/ } readdir($D) ) {
1079 $entry =~ /^(.*)$/;
1080 $entry = $root . '/' . $1;
1081 if ( -d $entry ) {
1082 _rmtree($entry);
1083 }
1084 elsif ( !unlink($entry) && -e $entry ) {
1085 if ( $Foswiki::cfg{OS} ne 'WINDOWS' ) {
1086 throw Error::Simple( 'VC::Handler: Failed to delete file '
1087 . $entry . ': '
1088 . $! );
1089 }
1090 else {
1091
1092 # Windows sometimes fails to delete files when
1093 # subprocesses haven't exited yet, because the
1094 # subprocess still has the file open. Live with it.
1095 print STDERR 'WARNING: Failed to delete file ',
1096 $entry, ": $!\n";
1097 }
1098 }
1099 }
1100 closedir($D);
1101
1102 if ( !rmdir($root) ) {
1103 if ( $Foswiki::cfg{OS} ne 'WINDOWS' ) {
1104 throw Error::Simple(
1105 'VC::Handler: Failed to delete ' . $root . ': ' . $! );
1106 }
1107 else {
1108 print STDERR 'WARNING: Failed to delete ' . $root . ': ' . $!,
1109 "\n";
1110 }
1111 }
1112 }
1113}
1114
1115{
1116
1117 # Package that ties a filehandle to a memory string for reading
111813µs package Foswiki::Store::_MemoryFile;
1119
1120 sub TIEHANDLE {
1121 my ( $class, $data ) = @_;
1122 return
1123 bless( { data => $data, size => length($data), ptr => 0 }, $class );
1124 }
1125
1126 sub READ {
1127 my $this = shift;
1128 my ( undef, $len, $offset ) = @_;
1129 if ( $this->{size} - $this->{ptr} < $len ) {
1130 $len = $this->{size} - $this->{ptr};
1131 }
1132 return 0 unless $len;
1133 $_[0] = substr( $this->{data}, $this->{ptr}, $len );
1134 $this->{ptr} += $len;
1135 return $len;
1136 }
1137
1138 sub READLINE {
1139 my $this = shift;
1140 return if $this->{ptr} == $this->{size};
1141 return substr( $this->{data}, $this->{ptr} ) if !defined $/;
1142 my $start = $this->{ptr};
1143 while ( $this->{ptr} < $this->{size}
1144 && substr( $this->{data}, $this->{ptr}, 1 ) ne $/ )
1145 {
1146 $this->{ptr}++;
1147 }
1148 $this->{ptr}++ if $this->{ptr} < $this->{size};
1149 return substr( $this->{data}, $start, $this->{ptr} - $start );
1150 }
1151
1152 sub CLOSE {
1153 my $this = shift;
1154 $this->{data} = undef;
1155 }
1156}
1157
1158=begin TML
1159
1160---++ ObjectMethod openStream($mode, %opts) -> $fh
1161
1162Opens a file handle onto the store. This method is primarily to
1163support virtual file systems.
1164
1165=$mode= can be '&lt;', '&gt;' or '&gt;&gt;' for read, write, and append
1166respectively. %
1167
1168=%opts= can take different settings depending on =$mode=.
1169 * =$mode='&lt;'=
1170 * =version= - revision of the object to open e.g. =version => 6=
1171 Default behaviour is to return the latest revision. Note that it is
1172 much more efficient to pass undef than to pass the number of the
1173 latest revision.
1174 * =$mode='&gt;'= or ='&gt;&gt;'
1175 * no options
1176
1177=cut
1178
1179sub openStream {
1180 my ( $this, $mode, %opts ) = @_;
1181 my $stream;
1182 if ( $mode eq '<' && $opts{version} ) {
1183
1184 # Bulk load the revision and tie a filehandle
1185 require Symbol;
1186 $stream = Symbol::gensym; # create an anonymous glob
1187 tie( *$stream, 'Foswiki::Store::_MemoryFile',
1188 $this->getRevision( $opts{version} ) );
1189 }
1190 else {
1191 if ( $mode =~ />/ ) {
1192 $this->mkPathTo( $this->{file} );
1193 }
1194 unless ( open( $stream, $mode, $this->{file} ) ) {
1195 throw Error::Simple( 'VC::Handler: stream open '
1196 . $this->{file}
1197 . ' failed: '
1198 . $! );
1199 }
1200 binmode $stream;
1201 }
1202 return $stream;
1203}
1204
1205# as long as stat is defined, return an emulated set of attributes for that
1206# attachment.
1207sub _constructAttributesForAutoAttached {
1208 my ( $file, $stat ) = @_;
1209
1210 my %pairs = (
1211 name => $file,
1212 version => '',
1213 path => $file,
1214 size => $stat->[7],
1215 date => $stat->[9],
1216
1217# user => 'UnknownUser', #safer _not_ to default - Foswiki will fill it in when it needs to
1218 comment => '',
1219 attr => '',
1220 autoattached => '1'
1221 );
1222
1223 if ( $#$stat > 0 ) {
1224 return \%pairs;
1225 }
1226 else {
1227 return;
1228 }
1229}
1230
1231=begin TML
1232
1233---++ ObjectMethod synchroniseAttachmentsList(\@old) -> @new
1234
1235Synchronise the attachment list from meta-data with what's actually
1236stored in the DB. Returns an ARRAY of FILEATTACHMENTs. These can be
1237put in the new tom.
1238
1239This function is only called when the {RCS}{AutoAttachPubFiles} configuration
1240option is set.
1241
1242=cut
1243
1244# IDEA On Windows machines where the underlying filesystem can store arbitary
1245# meta data against files, this might replace/fulfil the COMMENT purpose
1246#
1247# TODO consider logging when things are added to metadata
1248
1249sub synchroniseAttachmentsList {
1250 my ( $this, $attachmentsKnownInMeta ) = @_;
1251
1252 my %filesListedInPub = $this->_getAttachmentStats();
1253 my %filesListedInMeta = ();
1254
1255 # You need the following lines if you want metadata to supplement
1256 # the filesystem
1257 if ( defined $attachmentsKnownInMeta ) {
1258 %filesListedInMeta =
1259 map { $_->{name} => $_ } @$attachmentsKnownInMeta;
1260 }
1261
1262 foreach my $file ( keys %filesListedInPub ) {
1263 if ( $filesListedInMeta{$file} ) {
1264
1265 # Bring forward any missing yet wanted attributes
1266 foreach my $field (qw(comment attr user version)) {
1267 if ( $filesListedInMeta{$file}{$field} ) {
1268 $filesListedInPub{$file}{$field} =
1269 $filesListedInMeta{$file}{$field};
1270 }
1271 }
1272 }
1273 }
1274
1275 # A comparison of the keys of the $filesListedInMeta and %filesListedInPub
1276 # would show files that were in Meta but have disappeared from Pub.
1277
1278 # Do not change this from array to hash, you would lose the
1279 # proper attachment sequence
1280 my @deindexedBecauseMetaDoesnotIndexAttachments = values(%filesListedInPub);
1281
1282 return @deindexedBecauseMetaDoesnotIndexAttachments;
1283}
1284
1285=begin TML
1286
1287---++ ObjectMethod getAttachmentList() -> @list
1288
1289Get list of attachment names actually stored for topic.
1290
1291=cut
1292
1293sub getAttachmentList {
1294 my $this = shift;
1295 my $dir = "$Foswiki::cfg{PubDir}/$this->{web}/$this->{topic}";
1296 my $dh;
1297 opendir( $dh, $dir ) || return ();
1298 my @files = grep { !/^[.*_]/ && !/,v$/ } readdir($dh);
1299 closedir($dh);
1300 return @files;
1301}
1302
1303# returns {} of filename => { key => value, key2 => value }
1304# for any given web, topic
1305sub _getAttachmentStats {
1306 my $this = shift;
1307 my %attachmentList = ();
1308 my $dir = "$Foswiki::cfg{PubDir}/$this->{web}/$this->{topic}";
1309 foreach my $attachment ( $this->getAttachmentList() ) {
1310 my @stat = stat( $dir . "/" . $attachment );
1311 $attachmentList{$attachment} =
1312 _constructAttributesForAutoAttached( $attachment, \@stat );
1313 }
1314 return %attachmentList;
1315}
1316
1317sub _dirForTopicAttachments {
1318 my ( $web, $topic ) = @_;
1319}
1320
1321=begin TML
1322
1323---++ ObjectMethod stringify()
1324
1325Generate string representation for debugging
1326
1327=cut
1328
1329sub stringify {
1330 my $this = shift;
1331 my @reply;
1332 foreach my $key (qw(web topic attachment file rcsFile)) {
1333 if ( defined $this->{$key} ) {
1334 push( @reply, "$key=$this->{$key}" );
1335 }
1336 }
1337 return join( ',', @reply );
1338}
1339
1340# Chop out recognisable path components to prevent hacking based on error
1341# messages
1342sub hidePath {
1343 my ( $this, $erf ) = @_;
1344 $erf =~ s#.*(/\w+/\w+\.[\w,]*)$#...$1#;
1345 return $erf;
1346}
1347
1348=begin TML
1349
1350---++ ObjectMethod recordChange($cUID, $rev, $more)
1351Record that the file changed, and who changed it
1352
1353=cut
1354
1355sub recordChange {
1356 my ( $this, $cUID, $rev, $more ) = @_;
1357 $more ||= '';
1358 ASSERT($cUID) if DEBUG;
1359
1360 my $file = $Foswiki::cfg{DataDir} . '/' . $this->{web} . '/.changes';
1361
1362 my @changes =
1363 map {
1364 my @row = split( /\t/, $_, 5 );
1365 \@row
1366 }
1367 split( /[\r\n]+/, readFile( $this, $file ) );
1368
1369 # Forget old stuff
1370 my $cutoff = time() - $Foswiki::cfg{Store}{RememberChangesFor};
1371 while ( scalar(@changes) && $changes[0]->[2] < $cutoff ) {
1372 shift(@changes);
1373 }
1374
1375 # Add the new change to the end of the file
1376 push( @changes, [ $this->{topic} || '.', $cUID, time(), $rev, $more ] );
1377
1378 # Doing this using a Schwartzian transform sometimes causes a mysterious
1379 # undefined value, so had to unwrap it to a for loop.
1380 for ( my $i = 0 ; $i <= $#changes ; $i++ ) {
1381 $changes[$i] = join( "\t", @{ $changes[$i] } );
1382 }
1383
1384 my $text = join( "\n", @changes );
1385
1386 saveFile( $this, $file, $text );
1387}
1388
1389=begin TML
1390
1391---++ ObjectMethod eachChange($since) -> $iterator
1392
1393Return iterator over changes - see Store for details
1394
1395=cut
1396
1397sub eachChange {
1398 my ( $this, $since ) = @_;
1399 my $file = $Foswiki::cfg{DataDir} . '/' . $this->{web} . '/.changes';
1400 require Foswiki::ListIterator;
1401
1402 if ( -r $file ) {
1403
1404 # Could use a LineIterator to avoid reading the whole
1405 # file, but it hardly seems worth it.
1406 my @changes =
1407 map {
1408
1409 # Create a hash for this line
1410 {
1411 topic => Foswiki::Sandbox::untaint(
1412 $_->[0], \&Foswiki::Sandbox::validateTopicName
1413 ),
1414 user => $_->[1],
1415 time => $_->[2],
1416 revision => $_->[3],
1417 more => $_->[4]
1418 };
1419 }
1420 grep {
1421
1422 # Filter on time
1423 $_->[2] && $_->[2] >= $since
1424 }
1425 map {
1426
1427 # Split line into an array
1428 my @row = split( /\t/, $_, 5 );
1429 \@row;
1430 }
1431 reverse split( /[\r\n]+/, readFile( $this, $file ) );
1432
1433 return Foswiki::ListIterator->new( \@changes );
1434 }
1435 else {
1436 my $changes = [];
1437 return Foswiki::ListIterator->new($changes);
1438 }
1439}
1440
1441# ObjectMethod getTimestamp() -> $integer
1442# Get the timestamp of the file
1443# Returns 0 if no file, otherwise epoch seconds
1444# Used in subclasses
1445
1446
# spent 652µs (386+266) within Foswiki::Store::VC::Handler::getTimestamp which was called 8 times, avg 81µs/call: # 8 times (386µs+266µs) by Foswiki::Store::VC::Handler::getInfo at line 252, avg 81µs/call
sub getTimestamp {
144748527µs my ($this) = @_;
1448888µs ASSERT( $this->{file} ) if DEBUG;
# spent 88µs making 8 calls to Assert::ASSERTS_OFF, avg 11µs/call
1449
1450 my $date = 0;
14518112µs if ( -e $this->{file} ) {
# spent 112µs making 8 calls to Foswiki::Store::VC::Handler::CORE:ftis, avg 14µs/call
1452
1453 # If the stat fails, stamp it with some arbitrary static
1454 # time in the past (00:40:05 on 5th Jan 1989)
1455866µs $date = ( stat $this->{file} )[9] || 600000000;
# spent 66µs making 8 calls to Foswiki::Store::VC::Handler::CORE:stat, avg 8µs/call
1456 }
1457 return $date;
1458}
1459
146015µs1;
1461
1462__END__
 
# spent 200µs within Foswiki::Store::VC::Handler::CORE:binmode which was called 70 times, avg 3µs/call: # 70 times (200µs+0s) by Foswiki::Store::VC::Handler::readFile at line 1009, avg 3µs/call
sub Foswiki::Store::VC::Handler::CORE:binmode; # opcode
# spent 449µs within Foswiki::Store::VC::Handler::CORE:close which was called 84 times, avg 5µs/call: # 70 times (374µs+0s) by Foswiki::Store::VC::Handler::readFile at line 1012, avg 5µs/call # 14 times (75µs+0s) by Foswiki::Store::VC::Handler::_getTOPICINFO at line 267, avg 5µs/call
sub Foswiki::Store::VC::Handler::CORE:close; # opcode
# spent 42µs within Foswiki::Store::VC::Handler::CORE:closedir which was called: # once (42µs+0s) by Foswiki::Store::VC::Handler::getTopicNames at line 511
sub Foswiki::Store::VC::Handler::CORE:closedir; # opcode
# spent 4.05ms within Foswiki::Store::VC::Handler::CORE:ftis which was called 617 times, avg 7µs/call: # 274 times (1.92ms+0s) by Foswiki::Store::VC::Handler::storedDataExists at line 618, avg 7µs/call # 117 times (934µs+0s) by Foswiki::Store::VC::Handler::noCheckinPending at line 291, avg 8µs/call # 117 times (510µs+0s) by Foswiki::Store::VC::Handler::noCheckinPending at line 295, avg 4µs/call # 70 times (430µs+0s) by Foswiki::Store::VC::Handler::getRevision at line 601, avg 6µs/call # 21 times (83µs+0s) by Foswiki::Store::VC::Handler::getInfo at line 233, avg 4µs/call # 8 times (112µs+0s) by Foswiki::Store::VC::Handler::getTimestamp at line 1451, avg 14µs/call # 5 times (23µs+0s) by Foswiki::Store::VC::Handler::getLatestRevisionID at line 448, avg 5µs/call # 3 times (18µs+0s) by Foswiki::Store::VC::Handler::getLatestRevisionID at line 453, avg 6µs/call # 2 times (22µs+0s) by Foswiki::Store::VC::Handler::getRevisionHistory at line 422, avg 11µs/call
sub Foswiki::Store::VC::Handler::CORE:ftis; # opcode
# spent 33.0ms within Foswiki::Store::VC::Handler::CORE:match which was called 17231 times, avg 2µs/call: # 13792 times (23.0ms+0s) by Foswiki::Store::VC::Handler::getTopicNames at line 508, avg 2µs/call # 3425 times (9.73ms+0s) by Foswiki::Store::VC::Handler::getTopicNames at line 507, avg 3µs/call # 14 times (258µs+0s) by Foswiki::Store::VC::Handler::_getTOPICINFO at line 268, avg 18µs/call
sub Foswiki::Store::VC::Handler::CORE:match; # opcode
# spent 1.83ms within Foswiki::Store::VC::Handler::CORE:open which was called 84 times, avg 22µs/call: # 70 times (1.56ms+0s) by Foswiki::Store::VC::Handler::readFile at line 1008, avg 22µs/call # 14 times (268µs+0s) by Foswiki::Store::VC::Handler::_getTOPICINFO at line 264, avg 19µs/call
sub Foswiki::Store::VC::Handler::CORE:open; # opcode
# spent 83µs within Foswiki::Store::VC::Handler::CORE:open_dir which was called: # once (83µs+0s) by Foswiki::Store::VC::Handler::getTopicNames at line 502
sub Foswiki::Store::VC::Handler::CORE:open_dir; # opcode
# spent 10.3ms within Foswiki::Store::VC::Handler::CORE:readdir which was called: # once (10.3ms+0s) by Foswiki::Store::VC::Handler::getTopicNames at line 510
sub Foswiki::Store::VC::Handler::CORE:readdir; # opcode
# spent 1.65ms within Foswiki::Store::VC::Handler::CORE:readline which was called 84 times, avg 20µs/call: # 70 times (1.46ms+0s) by Foswiki::Store::VC::Handler::readFile at line 1011, avg 21µs/call # 14 times (190µs+0s) by Foswiki::Store::VC::Handler::_getTOPICINFO at line 266, avg 14µs/call
sub Foswiki::Store::VC::Handler::CORE:readline; # opcode
# spent 8.81ms within Foswiki::Store::VC::Handler::CORE:regcomp which was called 6896 times, avg 1µs/call: # 6896 times (8.81ms+0s) by Foswiki::Store::VC::Handler::getTopicNames at line 508, avg 1µs/call
sub Foswiki::Store::VC::Handler::CORE:regcomp; # opcode
# spent 8.52ms within Foswiki::Store::VC::Handler::CORE:sort which was called: # once (8.52ms+0s) by Foswiki::Store::VC::Handler::getTopicNames at line 510
sub Foswiki::Store::VC::Handler::CORE:sort; # opcode
# spent 804µs within Foswiki::Store::VC::Handler::CORE:stat which was called 156 times, avg 5µs/call: # 74 times (411µs+0s) by Foswiki::Store::VC::Handler::noCheckinPending at line 302, avg 6µs/call # 74 times (328µs+0s) by Foswiki::Store::VC::Handler::noCheckinPending at line 303, avg 4µs/call # 8 times (66µs+0s) by Foswiki::Store::VC::Handler::getTimestamp at line 1455, avg 8µs/call
sub Foswiki::Store::VC::Handler::CORE:stat; # opcode