| 1 | diff -rN -u old-trunk/docs/frontends/webapi.txt new-trunk/docs/frontends/webapi.txt |
|---|
| 2 | --- old-trunk/docs/frontends/webapi.txt 2009-04-07 20:44:50.000000000 -0600 |
|---|
| 3 | +++ new-trunk/docs/frontends/webapi.txt 2009-04-07 20:44:54.000000000 -0600 |
|---|
| 4 | @@ -393,8 +393,11 @@ |
|---|
| 5 | "verify_uri": verify_uri, |
|---|
| 6 | "size": bytes, |
|---|
| 7 | "mutable": false, |
|---|
| 8 | - "metadata": {"ctime": 1202777696.7564139, |
|---|
| 9 | - "mtime": 1202777696.7564139 |
|---|
| 10 | + "metadata": { |
|---|
| 11 | + "ctime": 1202777696.7564139, |
|---|
| 12 | + "mtime": 1202777696.7564139, |
|---|
| 13 | + "lcrtime": 1202777696.7564139, |
|---|
| 14 | + "lmotime": 1202777696.7564139, |
|---|
| 15 | } |
|---|
| 16 | } ] |
|---|
| 17 | |
|---|
| 18 | @@ -402,7 +405,7 @@ |
|---|
| 19 | this directory, as a mapping from child name to a set of data about the |
|---|
| 20 | child (the same data that would appear in a corresponding GET?t=json of the |
|---|
| 21 | child itself). The child entries also include metadata about each child, |
|---|
| 22 | - including creation- and modification- timestamps. The output looks like |
|---|
| 23 | + including link-creation- and link-change- timestamps. The output looks like |
|---|
| 24 | this: |
|---|
| 25 | |
|---|
| 26 | GET /uri/$DIRCAP?t=json : |
|---|
| 27 | @@ -418,6 +421,8 @@ |
|---|
| 28 | "metadata": { |
|---|
| 29 | "ctime": 1202777696.7564139, |
|---|
| 30 | "mtime": 1202777696.7564139 |
|---|
| 31 | + "lcrtime": 1202777696.7564139, |
|---|
| 32 | + "lmotime": 1202777696.7564139, |
|---|
| 33 | } |
|---|
| 34 | } ], |
|---|
| 35 | "subdir": [ "dirnode", { "rw_uri": rwuri, |
|---|
| 36 | @@ -425,6 +430,8 @@ |
|---|
| 37 | "metadata": { |
|---|
| 38 | "ctime": 1202778102.7589991, |
|---|
| 39 | "mtime": 1202778111.2160511, |
|---|
| 40 | + "lcrtime": 1202777696.7564139, |
|---|
| 41 | + "lmotime": 1202777696.7564139, |
|---|
| 42 | } |
|---|
| 43 | } ] |
|---|
| 44 | } } ] |
|---|
| 45 | diff -rN -u old-trunk/docs/specifications/dirnodes.txt new-trunk/docs/specifications/dirnodes.txt |
|---|
| 46 | --- old-trunk/docs/specifications/dirnodes.txt 2009-04-07 20:44:50.000000000 -0600 |
|---|
| 47 | +++ new-trunk/docs/specifications/dirnodes.txt 2009-04-07 20:44:54.000000000 -0600 |
|---|
| 48 | @@ -176,30 +176,30 @@ |
|---|
| 49 | netstring(cap) = 4+len(cap) |
|---|
| 50 | encrypted(cap) = 16+cap+32 |
|---|
| 51 | JSON({}) = 2 |
|---|
| 52 | - JSON({ctime=float,mtime=float}): 57 |
|---|
| 53 | - netstring(metadata) = 4+57 = 61 |
|---|
| 54 | + JSON({ctime=float,mtime=float,lcrtime=float,lmotime=float}): 117 |
|---|
| 55 | + netstring(metadata) = 4+117 = 121 |
|---|
| 56 | |
|---|
| 57 | so a CHK entry is: |
|---|
| 58 | - 5+ 4+len(name) + 4+97 + 5+16+97+32 + 4+57 |
|---|
| 59 | -And a 15-byte filename gives a 336-byte entry. When the entry points at a |
|---|
| 60 | + 5+ 4+len(name) + 4+97 + 5+16+97+32 + 4+117 |
|---|
| 61 | +And a 15-byte filename gives a 396-byte entry. When the entry points at a |
|---|
| 62 | subdirectory instead of a file, the entry is a little bit smaller. So an |
|---|
| 63 | -empty directory uses 0 bytes, a directory with one child uses about 336 |
|---|
| 64 | -bytes, a directory with two children uses about 672, etc. |
|---|
| 65 | +empty directory uses 0 bytes, a directory with one child uses about 396 |
|---|
| 66 | +bytes, a directory with two children uses about 792, etc. |
|---|
| 67 | |
|---|
| 68 | When the dirnode data is encoding using our default 3-of-10, that means we |
|---|
| 69 | -get 112ish bytes of data in each share per child. |
|---|
| 70 | +get 132ish bytes of data in each share per child. |
|---|
| 71 | |
|---|
| 72 | The pubkey, signature, and hashes form the first 935ish bytes of the |
|---|
| 73 | container, then comes our data, then about 1216 bytes of encprivkey. So if we |
|---|
| 74 | read the first: |
|---|
| 75 | |
|---|
| 76 | 1kB: we get 65bytes of dirnode data : only empty directories |
|---|
| 77 | - 1kiB: 89bytes of dirnode data : maybe one short-named subdir |
|---|
| 78 | - 2kB: 1065bytes: about 9 entries |
|---|
| 79 | - 3kB: 2065bytes: about 18 entries, or 7.5 entries plus the encprivkey |
|---|
| 80 | - 4kB: 3065bytes: about 27 entries, or about 16.5 plus the encprivkey |
|---|
| 81 | + 1kiB: 89bytes of dirnode data : only empty directories |
|---|
| 82 | + 2kB: 1065bytes: about 2 or 3 entries |
|---|
| 83 | + 3kB: 2065bytes: about 5 entries, or 2 entries plus the encprivkey |
|---|
| 84 | + 4kB: 3065bytes: about 8 entries, or about 5 plus the encprivkey |
|---|
| 85 | |
|---|
| 86 | -So we've written the code to do an initial read of 2kB from each share when |
|---|
| 87 | +So we've written the code to do an initial read of 4kB from each share when |
|---|
| 88 | we read the mutable file, which should give good performance (one RTT) for |
|---|
| 89 | small directories. |
|---|
| 90 | |
|---|
| 91 | diff -rN -u old-trunk/src/allmydata/dirnode.py new-trunk/src/allmydata/dirnode.py |
|---|
| 92 | --- old-trunk/src/allmydata/dirnode.py 2009-04-07 20:44:53.000000000 -0600 |
|---|
| 93 | +++ new-trunk/src/allmydata/dirnode.py 2009-04-07 20:44:55.000000000 -0600 |
|---|
| 94 | @@ -83,15 +83,39 @@ |
|---|
| 95 | metadata = children[name][1].copy() |
|---|
| 96 | else: |
|---|
| 97 | metadata = {"ctime": now, |
|---|
| 98 | - "mtime": now} |
|---|
| 99 | - if new_metadata is None: |
|---|
| 100 | - # update timestamps |
|---|
| 101 | - if "ctime" not in metadata: |
|---|
| 102 | - metadata["ctime"] = now |
|---|
| 103 | - metadata["mtime"] = now |
|---|
| 104 | - else: |
|---|
| 105 | - # just replace it |
|---|
| 106 | - metadata = new_metadata.copy() |
|---|
| 107 | + "mtime": now, |
|---|
| 108 | + "lcrtime": now, |
|---|
| 109 | + "lmotime": now, |
|---|
| 110 | + } |
|---|
| 111 | + |
|---|
| 112 | + if new_metadata is not None: |
|---|
| 113 | + # Overwrite all metadata. |
|---|
| 114 | + newmd = new_metadata.copy() |
|---|
| 115 | + |
|---|
| 116 | + # Except that callers are not allowed to mess with the |
|---|
| 117 | + # link-timestamp metadata. |
|---|
| 118 | + for special in ['ctime', 'mtime', 'lcrtime', 'lmotime']: |
|---|
| 119 | + if newmd.has_key(special): |
|---|
| 120 | + del newmd[special] |
|---|
| 121 | + if metadata.has_key(special): |
|---|
| 122 | + newmd[special] = metadata[special] |
|---|
| 123 | + |
|---|
| 124 | + metadata = newmd |
|---|
| 125 | + |
|---|
| 126 | + # update timestamps |
|---|
| 127 | + if "lcrtime" not in metadata: |
|---|
| 128 | + if "ctime" in metadata: |
|---|
| 129 | + # In Tahoe < 1.4.0 we used the word "ctime" to mean what Tahoe >= 1.4.0 calls "lcrtime". |
|---|
| 130 | + metadata["lcrtime"] = metadata["ctime"] |
|---|
| 131 | + else: |
|---|
| 132 | + metadata["lmotime"] = now |
|---|
| 133 | + metadata["lmotime"] = now |
|---|
| 134 | + |
|---|
| 135 | + # For backwards compatibility with Tahoe < 1.4.0: |
|---|
| 136 | + if "ctime" not in metadata: |
|---|
| 137 | + metadata["ctime"] = now |
|---|
| 138 | + metadata["mtime"] = now |
|---|
| 139 | + |
|---|
| 140 | children[name] = (child, metadata) |
|---|
| 141 | new_contents = self.node._pack_contents(children) |
|---|
| 142 | return new_contents |
|---|
| 143 | diff -rN -u old-trunk/src/allmydata/mutable/servermap.py new-trunk/src/allmydata/mutable/servermap.py |
|---|
| 144 | --- old-trunk/src/allmydata/mutable/servermap.py 2009-04-07 20:44:53.000000000 -0600 |
|---|
| 145 | +++ new-trunk/src/allmydata/mutable/servermap.py 2009-04-07 20:44:55.000000000 -0600 |
|---|
| 146 | @@ -374,7 +374,7 @@ |
|---|
| 147 | # fixed-size slots so we can retrieve less data. For now, we'll just |
|---|
| 148 | # read 2000 bytes, which also happens to read enough actual data to |
|---|
| 149 | # pre-fetch a 9-entry dirnode. |
|---|
| 150 | - self._read_size = 2000 |
|---|
| 151 | + self._read_size = 4000 |
|---|
| 152 | if mode == MODE_CHECK: |
|---|
| 153 | # we use unpack_prefix_and_signature, so we need 1k |
|---|
| 154 | self._read_size = 1000 |
|---|
| 155 | diff -rN -u old-trunk/src/allmydata/scripts/tahoe_backup.py new-trunk/src/allmydata/scripts/tahoe_backup.py |
|---|
| 156 | --- old-trunk/src/allmydata/scripts/tahoe_backup.py 2009-04-07 20:44:53.000000000 -0600 |
|---|
| 157 | +++ new-trunk/src/allmydata/scripts/tahoe_backup.py 2009-04-07 20:44:55.000000000 -0600 |
|---|
| 158 | @@ -4,6 +4,7 @@ |
|---|
| 159 | import urllib |
|---|
| 160 | import simplejson |
|---|
| 161 | import datetime |
|---|
| 162 | +import platform |
|---|
| 163 | from allmydata.scripts.common import get_alias, escape_path, DEFAULT_ALIAS |
|---|
| 164 | from allmydata.scripts.common_http import do_http |
|---|
| 165 | from allmydata import uri |
|---|
| 166 | @@ -69,11 +69,7 @@ |
|---|
| 167 | metadata = {} |
|---|
| 168 | |
|---|
| 169 | # posix stat(2) metadata, depends on the platform |
|---|
| 170 | - os.stat_float_times(True) |
|---|
| 171 | s = os.stat(path) |
|---|
| 172 | - metadata["ctime"] = s.st_ctime |
|---|
| 173 | - metadata["mtime"] = s.st_mtime |
|---|
| 174 | - |
|---|
| 175 | misc_fields = ("st_mode", "st_ino", "st_dev", "st_uid", "st_gid") |
|---|
| 176 | macos_misc_fields = ("st_rsize", "st_creator", "st_type") |
|---|
| 177 | for field in misc_fields + macos_misc_fields: |
|---|
| 178 | diff -rN -u old-trunk/src/allmydata/scripts/tahoe_ls.py new-trunk/src/allmydata/scripts/tahoe_ls.py |
|---|
| 179 | --- old-trunk/src/allmydata/scripts/tahoe_ls.py 2009-04-07 20:44:53.000000000 -0600 |
|---|
| 180 | +++ new-trunk/src/allmydata/scripts/tahoe_ls.py 2009-04-07 20:44:55.000000000 -0600 |
|---|
| 181 | @@ -65,8 +65,20 @@ |
|---|
| 182 | name = unicode(name) |
|---|
| 183 | child = children[name] |
|---|
| 184 | childtype = child[0] |
|---|
| 185 | - ctime = child[1]["metadata"].get("ctime") |
|---|
| 186 | - mtime = child[1]["metadata"].get("mtime") |
|---|
| 187 | + |
|---|
| 188 | + # lcrtime is not really what unix filesystems mean by "ctime", but it |
|---|
| 189 | + # *is* apparently what many or even most unix programmers and users |
|---|
| 190 | + # think that a unix filesystem means by "ctime"... |
|---|
| 191 | + ctime = child[1]["metadata"].get("lcrtime") |
|---|
| 192 | + if not ctime: |
|---|
| 193 | + ctime = child[1]["metadata"].get("ctime") |
|---|
| 194 | + |
|---|
| 195 | + # lmotime is not really what unix filesystems mean by "mtime", because |
|---|
| 196 | + # lmotime is a property of the link and mtime is a property of the file |
|---|
| 197 | + # contents... |
|---|
| 198 | + mtime = child[1]["metadata"].get("lmotime") |
|---|
| 199 | + if not mtime: |
|---|
| 200 | + mtime = child[1]["metadata"].get("mtime") |
|---|
| 201 | rw_uri = child[1].get("rw_uri") |
|---|
| 202 | ro_uri = child[1].get("ro_uri") |
|---|
| 203 | if ctime: |
|---|
| 204 | diff -rN -u old-trunk/src/allmydata/test/test_cli.py new-trunk/src/allmydata/test/test_cli.py |
|---|
| 205 | --- old-trunk/src/allmydata/test/test_cli.py 2009-04-07 20:44:53.000000000 -0600 |
|---|
| 206 | +++ new-trunk/src/allmydata/test/test_cli.py 2009-04-07 20:44:55.000000000 -0600 |
|---|
| 207 | @@ -949,7 +949,7 @@ |
|---|
| 208 | self.failUnlessEqual(fu, 0) |
|---|
| 209 | self.failUnlessEqual(fr, 3) |
|---|
| 210 | # empty, home, home/parent, home/parent/subdir |
|---|
| 211 | - self.failUnlessEqual(dc, 0) |
|---|
| 212 | + self.failUnlessEqual(dc, 0, out) |
|---|
| 213 | self.failUnlessEqual(dr, 4) |
|---|
| 214 | d.addCallback(_check4a) |
|---|
| 215 | |
|---|
| 216 | diff -rN -u old-trunk/src/allmydata/test/test_dirnode.py new-trunk/src/allmydata/test/test_dirnode.py |
|---|
| 217 | --- old-trunk/src/allmydata/test/test_dirnode.py 2009-04-07 20:44:53.000000000 -0600 |
|---|
| 218 | +++ new-trunk/src/allmydata/test/test_dirnode.py 2009-04-07 20:44:55.000000000 -0600 |
|---|
| 219 | @@ -416,7 +416,7 @@ |
|---|
| 220 | d.addCallback(lambda res: n.get_metadata_for(u"child")) |
|---|
| 221 | d.addCallback(lambda metadata: |
|---|
| 222 | self.failUnlessEqual(sorted(metadata.keys()), |
|---|
| 223 | - ["ctime", "mtime"])) |
|---|
| 224 | + ["ctime", "lcrtime", "lmotime", "mtime"])) |
|---|
| 225 | |
|---|
| 226 | d.addCallback(lambda res: |
|---|
| 227 | self.shouldFail(NoSuchChildError, "gcamap-no", |
|---|
| 228 | @@ -439,7 +439,7 @@ |
|---|
| 229 | self.failUnlessEqual(child.get_uri(), |
|---|
| 230 | fake_file_uri.to_string()) |
|---|
| 231 | self.failUnlessEqual(sorted(metadata.keys()), |
|---|
| 232 | - ["ctime", "mtime"]) |
|---|
| 233 | + ["ctime", "lcrtime", "lmotime", "mtime"]) |
|---|
| 234 | d.addCallback(_check_child_and_metadata2) |
|---|
| 235 | |
|---|
| 236 | d.addCallback(lambda res: |
|---|
| 237 | @@ -448,36 +448,41 @@ |
|---|
| 238 | child, metadata = res |
|---|
| 239 | self.failUnless(isinstance(child, FakeDirectoryNode)) |
|---|
| 240 | self.failUnlessEqual(sorted(metadata.keys()), |
|---|
| 241 | - ["ctime", "mtime"]) |
|---|
| 242 | + ["ctime", "lcrtime", "lmotime", "mtime"]) |
|---|
| 243 | d.addCallback(_check_child_and_metadata3) |
|---|
| 244 | |
|---|
| 245 | # set_uri + metadata |
|---|
| 246 | - # it should be possible to add a child without any metadata |
|---|
| 247 | + # it should not be possible to add a child without any metadata |
|---|
| 248 | d.addCallback(lambda res: n.set_uri(u"c2", fake_file_uri.to_string(), {})) |
|---|
| 249 | d.addCallback(lambda res: n.get_metadata_for(u"c2")) |
|---|
| 250 | - d.addCallback(lambda metadata: self.failUnlessEqual(metadata, {})) |
|---|
| 251 | + def _has_ltimes(metadata): |
|---|
| 252 | + self.failUnless(metadata.has_key('ctime')) |
|---|
| 253 | + self.failUnless(metadata.has_key('mtime')) |
|---|
| 254 | + self.failUnless(metadata.has_key('lcrtime')) |
|---|
| 255 | + self.failUnless(metadata.has_key('lmotime')) |
|---|
| 256 | + d.addCallback(_has_ltimes) |
|---|
| 257 | + |
|---|
| 258 | + # nor to override the link timestamps with the "metadata" argument |
|---|
| 259 | + d.addCallback(lambda res: n.set_uri(u"c2", fake_file_uri.to_string(), {'lcrtime': "bogus"})) |
|---|
| 260 | + d.addCallback(lambda res: n.get_metadata_for(u"c2")) |
|---|
| 261 | + def _has_good_lcrtime(metadata): |
|---|
| 262 | + self.failUnless(metadata.has_key('lcrtime')) |
|---|
| 263 | + self.failIfEqual(metadata['lcrtime'], 'bogus') |
|---|
| 264 | + d.addCallback(_has_good_lcrtime) |
|---|
| 265 | |
|---|
| 266 | # if we don't set any defaults, the child should get timestamps |
|---|
| 267 | d.addCallback(lambda res: n.set_uri(u"c3", fake_file_uri.to_string())) |
|---|
| 268 | d.addCallback(lambda res: n.get_metadata_for(u"c3")) |
|---|
| 269 | d.addCallback(lambda metadata: |
|---|
| 270 | self.failUnlessEqual(sorted(metadata.keys()), |
|---|
| 271 | - ["ctime", "mtime"])) |
|---|
| 272 | - |
|---|
| 273 | - # or we can add specific metadata at set_uri() time, which |
|---|
| 274 | - # overrides the timestamps |
|---|
| 275 | - d.addCallback(lambda res: n.set_uri(u"c4", fake_file_uri.to_string(), |
|---|
| 276 | - {"key": "value"})) |
|---|
| 277 | - d.addCallback(lambda res: n.get_metadata_for(u"c4")) |
|---|
| 278 | - d.addCallback(lambda metadata: |
|---|
| 279 | - self.failUnlessEqual(metadata, {"key": "value"})) |
|---|
| 280 | + ["ctime", "lcrtime", "lmotime", "mtime"])) |
|---|
| 281 | |
|---|
| 282 | d.addCallback(lambda res: n.delete(u"c2")) |
|---|
| 283 | d.addCallback(lambda res: n.delete(u"c3")) |
|---|
| 284 | d.addCallback(lambda res: n.delete(u"c4")) |
|---|
| 285 | |
|---|
| 286 | # set_node + metadata |
|---|
| 287 | - # it should be possible to add a child without any metadata |
|---|
| 288 | + # it should be impossible to add a child without any metadata |
|---|
| 289 | d.addCallback(lambda res: n.set_node(u"d2", n, {})) |
|---|
| 290 | d.addCallback(lambda res: self.client.create_empty_dirnode()) |
|---|
| 291 | d.addCallback(lambda n2: |
|---|
| 292 | diff -rN -u old-trunk/src/allmydata/util/time_format.py new-trunk/src/allmydata/util/time_format.py |
|---|
| 293 | --- old-trunk/src/allmydata/util/time_format.py 2009-04-07 20:44:53.000000000 -0600 |
|---|
| 294 | +++ new-trunk/src/allmydata/util/time_format.py 2009-04-07 20:44:55.000000000 -0600 |
|---|
| 295 | @@ -19,6 +19,11 @@ |
|---|
| 296 | now = t() |
|---|
| 297 | return datetime.datetime.utcfromtimestamp(now).isoformat(sep) |
|---|
| 298 | |
|---|
| 299 | +def iso_local(now=None, sep='_', t=time.time): |
|---|
| 300 | + if now is None: |
|---|
| 301 | + now = t() |
|---|
| 302 | + return datetime.datetime.fromtimestamp(now).isoformat(sep) |
|---|
| 303 | + |
|---|
| 304 | def iso_utc_time_to_seconds(isotime, _conversion_re=re.compile(r"(?P<year>\d{4})-(?P<month>\d{2})-(?P<day>\d{2})[T_ ](?P<hour>\d{2}):(?P<minute>\d{2}):(?P<second>\d{2})(?P<subsecond>\.\d+)?")): |
|---|
| 305 | """ |
|---|
| 306 | The inverse of iso_utc(). |
|---|
| 307 | diff -rN -u old-trunk/src/allmydata/web/directory.py new-trunk/src/allmydata/web/directory.py |
|---|
| 308 | --- old-trunk/src/allmydata/web/directory.py 2009-04-07 20:44:53.000000000 -0600 |
|---|
| 309 | +++ new-trunk/src/allmydata/web/directory.py 2009-04-07 20:44:55.000000000 -0600 |
|---|
| 310 | @@ -13,7 +13,7 @@ |
|---|
| 311 | |
|---|
| 312 | from foolscap.eventual import fireEventually |
|---|
| 313 | |
|---|
| 314 | -from allmydata.util import base32 |
|---|
| 315 | +from allmydata.util import base32, time_format |
|---|
| 316 | from allmydata.uri import from_string_dirnode |
|---|
| 317 | from allmydata.interfaces import IDirectoryNode, IFileNode, IMutableFileNode, \ |
|---|
| 318 | ExistingChildError, NoSuchChildError |
|---|
| 319 | @@ -592,16 +592,25 @@ |
|---|
| 320 | ctx.fillSlots("rename", rename) |
|---|
| 321 | |
|---|
| 322 | times = [] |
|---|
| 323 | - TIME_FORMAT = "%H:%M:%S %d-%b-%Y" |
|---|
| 324 | - if "ctime" in metadata: |
|---|
| 325 | - ctime = time.strftime(TIME_FORMAT, |
|---|
| 326 | - time.localtime(metadata["ctime"])) |
|---|
| 327 | - times.append("c: " + ctime) |
|---|
| 328 | - if "mtime" in metadata: |
|---|
| 329 | - mtime = time.strftime(TIME_FORMAT, |
|---|
| 330 | - time.localtime(metadata["mtime"])) |
|---|
| 331 | + lcrtime = metadata.get("lcrtime") |
|---|
| 332 | + if lcrtime is not None: |
|---|
| 333 | + times.append("lcr: " + time_format.iso_local(lcrtime)) |
|---|
| 334 | + else: |
|---|
| 335 | + # For backwards-compatibility with links last modified by Tahoe < 1.4.0: |
|---|
| 336 | + if "ctime" in metadata: |
|---|
| 337 | + ctime = time_format.iso_local(metadata["ctime"]) |
|---|
| 338 | + times.append("c: " + ctime) |
|---|
| 339 | + lmotime = metadata.get("lmotime") |
|---|
| 340 | + if lmotime is not None: |
|---|
| 341 | if times: |
|---|
| 342 | times.append(T.br()) |
|---|
| 343 | + times.append("lmo: " + time_format.iso_local(lmotime)) |
|---|
| 344 | + else: |
|---|
| 345 | + # For backwards-compatibility with links last modified by Tahoe < 1.4.0: |
|---|
| 346 | + if "mtime" in metadata: |
|---|
| 347 | + mtime = time_format.iso_local(metadata["mtime"]) |
|---|
| 348 | + if times: |
|---|
| 349 | + times.append(T.br()) |
|---|
| 350 | times.append("m: " + mtime) |
|---|
| 351 | ctx.fillSlots("times", times) |
|---|
| 352 | |
|---|