Mercurial > public > mercurial-scm > hg-stable
comparison mercurial/util.py @ 7890:e710f0f592b2
util: split out posix, windows, and win32 modules
author | Matt Mackall <mpm@selenic.com> |
---|---|
date | Thu, 26 Mar 2009 13:54:44 -0500 |
parents | 5c4026a289a4 |
children | 1b1b3dd630a5 |
comparison
equal
deleted
inserted
replaced
7889:5ac1a72e5b74 | 7890:e710f0f592b2 |
---|---|
11 This contains helper routines that are independent of the SCM core and hide | 11 This contains helper routines that are independent of the SCM core and hide |
12 platform-specific details from the core. | 12 platform-specific details from the core. |
13 """ | 13 """ |
14 | 14 |
15 from i18n import _ | 15 from i18n import _ |
16 import cStringIO, errno, getpass, re, shutil, sys, tempfile, traceback, error | 16 import cStringIO, errno, re, shutil, sys, tempfile, traceback, error |
17 import os, stat, threading, time, calendar, ConfigParser, locale, glob, osutil | 17 import os, stat, threading, time, calendar, ConfigParser, locale, glob, osutil |
18 import imp, unicodedata | 18 import imp, unicodedata |
19 | 19 |
20 # Python compatibility | 20 # Python compatibility |
21 | 21 |
336 class Abort(Exception): | 336 class Abort(Exception): |
337 """Raised if a command needs to print an error and exit.""" | 337 """Raised if a command needs to print an error and exit.""" |
338 | 338 |
339 def always(fn): return True | 339 def always(fn): return True |
340 def never(fn): return False | 340 def never(fn): return False |
341 | |
342 def expand_glob(pats): | |
343 '''On Windows, expand the implicit globs in a list of patterns''' | |
344 if os.name != 'nt': | |
345 return list(pats) | |
346 ret = [] | |
347 for p in pats: | |
348 kind, name = patkind(p, None) | |
349 if kind is None: | |
350 globbed = glob.glob(name) | |
351 if globbed: | |
352 ret.extend(globbed) | |
353 continue | |
354 # if we couldn't expand the glob, just keep it around | |
355 ret.append(p) | |
356 return ret | |
357 | 341 |
358 def patkind(name, default): | 342 def patkind(name, default): |
359 """Split a string into an optional pattern kind prefix and the | 343 """Split a string into an optional pattern kind prefix and the |
360 actual pattern.""" | 344 actual pattern.""" |
361 for prefix in 're', 'glob', 'path', 'relglob', 'relpath', 'relre': | 345 for prefix in 're', 'glob', 'path', 'relglob', 'relpath', 'relre': |
856 self.audited.add(path) | 840 self.audited.add(path) |
857 # only add prefixes to the cache after checking everything: we don't | 841 # only add prefixes to the cache after checking everything: we don't |
858 # want to add "foo/bar/baz" before checking if there's a "foo/.hg" | 842 # want to add "foo/bar/baz" before checking if there's a "foo/.hg" |
859 self.auditeddir.update(prefixes) | 843 self.auditeddir.update(prefixes) |
860 | 844 |
861 def _makelock_file(info, pathname): | 845 if os.name == 'nt': |
846 from windows import * | |
847 else: | |
848 from posix import * | |
849 | |
850 def makelock(info, pathname): | |
851 try: | |
852 return os.symlink(info, pathname) | |
853 except OSError, why: | |
854 if why.errno == errno.EEXIST: | |
855 raise | |
856 except AttributeError: # no symlink in os | |
857 pass | |
858 | |
862 ld = os.open(pathname, os.O_CREAT | os.O_WRONLY | os.O_EXCL) | 859 ld = os.open(pathname, os.O_CREAT | os.O_WRONLY | os.O_EXCL) |
863 os.write(ld, info) | 860 os.write(ld, info) |
864 os.close(ld) | 861 os.close(ld) |
865 | 862 |
866 def _readlock_file(pathname): | 863 def readlock(pathname): |
864 try: | |
865 return os.readlink(pathname) | |
866 except OSError, why: | |
867 if why.errno not in (errno.EINVAL, errno.ENOSYS): | |
868 raise | |
869 except AttributeError: # no symlink in os | |
870 pass | |
867 return posixfile(pathname).read() | 871 return posixfile(pathname).read() |
868 | 872 |
869 def nlinks(pathname): | 873 def nlinks(pathname): |
870 """Return number of hardlinks for the given file.""" | 874 """Return number of hardlinks for the given file.""" |
871 return os.lstat(pathname).st_nlink | 875 return os.lstat(pathname).st_nlink |
880 '''stat file object that may not have fileno method.''' | 884 '''stat file object that may not have fileno method.''' |
881 try: | 885 try: |
882 return os.fstat(fp.fileno()) | 886 return os.fstat(fp.fileno()) |
883 except AttributeError: | 887 except AttributeError: |
884 return os.stat(fp.name) | 888 return os.stat(fp.name) |
885 | |
886 posixfile = file | |
887 | |
888 def openhardlinks(): | |
889 '''return true if it is safe to hold open file handles to hardlinks''' | |
890 return True | |
891 | |
892 def _statfiles(files): | |
893 'Stat each file in files and yield stat or None if file does not exist.' | |
894 lstat = os.lstat | |
895 for nf in files: | |
896 try: | |
897 st = lstat(nf) | |
898 except OSError, err: | |
899 if err.errno not in (errno.ENOENT, errno.ENOTDIR): | |
900 raise | |
901 st = None | |
902 yield st | |
903 | |
904 def _statfiles_clustered(files): | |
905 '''Stat each file in files and yield stat or None if file does not exist. | |
906 Cluster and cache stat per directory to minimize number of OS stat calls.''' | |
907 ncase = os.path.normcase | |
908 sep = os.sep | |
909 dircache = {} # dirname -> filename -> status | None if file does not exist | |
910 for nf in files: | |
911 nf = ncase(nf) | |
912 pos = nf.rfind(sep) | |
913 if pos == -1: | |
914 dir, base = '.', nf | |
915 else: | |
916 dir, base = nf[:pos+1], nf[pos+1:] | |
917 cache = dircache.get(dir, None) | |
918 if cache is None: | |
919 try: | |
920 dmap = dict([(ncase(n), s) | |
921 for n, k, s in osutil.listdir(dir, True)]) | |
922 except OSError, err: | |
923 # handle directory not found in Python version prior to 2.5 | |
924 # Python <= 2.4 returns native Windows code 3 in errno | |
925 # Python >= 2.5 returns ENOENT and adds winerror field | |
926 # EINVAL is raised if dir is not a directory. | |
927 if err.errno not in (3, errno.ENOENT, errno.EINVAL, | |
928 errno.ENOTDIR): | |
929 raise | |
930 dmap = {} | |
931 cache = dircache.setdefault(dir, dmap) | |
932 yield cache.get(base, None) | |
933 | |
934 if sys.platform == 'win32': | |
935 statfiles = _statfiles_clustered | |
936 else: | |
937 statfiles = _statfiles | |
938 | |
939 getuser_fallback = None | |
940 | |
941 def getuser(): | |
942 '''return name of current user''' | |
943 try: | |
944 return getpass.getuser() | |
945 except ImportError: | |
946 # import of pwd will fail on windows - try fallback | |
947 if getuser_fallback: | |
948 return getuser_fallback() | |
949 # raised if win32api not available | |
950 raise Abort(_('user name not available - set USERNAME ' | |
951 'environment variable')) | |
952 | |
953 def username(uid=None): | |
954 """Return the name of the user with the given uid. | |
955 | |
956 If uid is None, return the name of the current user.""" | |
957 try: | |
958 import pwd | |
959 if uid is None: | |
960 uid = os.getuid() | |
961 try: | |
962 return pwd.getpwuid(uid)[0] | |
963 except KeyError: | |
964 return str(uid) | |
965 except ImportError: | |
966 return None | |
967 | |
968 def groupname(gid=None): | |
969 """Return the name of the group with the given gid. | |
970 | |
971 If gid is None, return the name of the current group.""" | |
972 try: | |
973 import grp | |
974 if gid is None: | |
975 gid = os.getgid() | |
976 try: | |
977 return grp.getgrgid(gid)[0] | |
978 except KeyError: | |
979 return str(gid) | |
980 except ImportError: | |
981 return None | |
982 | 889 |
983 # File system features | 890 # File system features |
984 | 891 |
985 def checkcase(path): | 892 def checkcase(path): |
986 """ | 893 """ |
1086 os.unlink(name) | 993 os.unlink(name) |
1087 return True | 994 return True |
1088 except (OSError, AttributeError): | 995 except (OSError, AttributeError): |
1089 return False | 996 return False |
1090 | 997 |
1091 _umask = os.umask(0) | |
1092 os.umask(_umask) | |
1093 | |
1094 def needbinarypatch(): | 998 def needbinarypatch(): |
1095 """return True if patches should be applied in binary mode by default.""" | 999 """return True if patches should be applied in binary mode by default.""" |
1096 return os.name == 'nt' | 1000 return os.name == 'nt' |
1097 | 1001 |
1098 def endswithsep(path): | 1002 def endswithsep(path): |
1111 '''Are we running in a GUI?''' | 1015 '''Are we running in a GUI?''' |
1112 return os.name == "nt" or os.name == "mac" or os.environ.get("DISPLAY") | 1016 return os.name == "nt" or os.name == "mac" or os.environ.get("DISPLAY") |
1113 | 1017 |
1114 def lookup_reg(key, name=None, scope=None): | 1018 def lookup_reg(key, name=None, scope=None): |
1115 return None | 1019 return None |
1116 | |
1117 # Platform specific variants | |
1118 if os.name == 'nt': | |
1119 import msvcrt | |
1120 nulldev = 'NUL:' | |
1121 | |
1122 class winstdout: | |
1123 '''stdout on windows misbehaves if sent through a pipe''' | |
1124 | |
1125 def __init__(self, fp): | |
1126 self.fp = fp | |
1127 | |
1128 def __getattr__(self, key): | |
1129 return getattr(self.fp, key) | |
1130 | |
1131 def close(self): | |
1132 try: | |
1133 self.fp.close() | |
1134 except: pass | |
1135 | |
1136 def write(self, s): | |
1137 try: | |
1138 # This is workaround for "Not enough space" error on | |
1139 # writing large size of data to console. | |
1140 limit = 16000 | |
1141 l = len(s) | |
1142 start = 0 | |
1143 while start < l: | |
1144 end = start + limit | |
1145 self.fp.write(s[start:end]) | |
1146 start = end | |
1147 except IOError, inst: | |
1148 if inst.errno != 0: raise | |
1149 self.close() | |
1150 raise IOError(errno.EPIPE, 'Broken pipe') | |
1151 | |
1152 def flush(self): | |
1153 try: | |
1154 return self.fp.flush() | |
1155 except IOError, inst: | |
1156 if inst.errno != errno.EINVAL: raise | |
1157 self.close() | |
1158 raise IOError(errno.EPIPE, 'Broken pipe') | |
1159 | |
1160 sys.stdout = winstdout(sys.stdout) | |
1161 | |
1162 def _is_win_9x(): | |
1163 '''return true if run on windows 95, 98 or me.''' | |
1164 try: | |
1165 return sys.getwindowsversion()[3] == 1 | |
1166 except AttributeError: | |
1167 return 'command' in os.environ.get('comspec', '') | |
1168 | |
1169 def openhardlinks(): | |
1170 return not _is_win_9x and "win32api" in locals() | |
1171 | |
1172 def system_rcpath(): | |
1173 try: | |
1174 return system_rcpath_win32() | |
1175 except: | |
1176 return [r'c:\mercurial\mercurial.ini'] | |
1177 | |
1178 def user_rcpath(): | |
1179 '''return os-specific hgrc search path to the user dir''' | |
1180 try: | |
1181 path = user_rcpath_win32() | |
1182 except: | |
1183 home = os.path.expanduser('~') | |
1184 path = [os.path.join(home, 'mercurial.ini'), | |
1185 os.path.join(home, '.hgrc')] | |
1186 userprofile = os.environ.get('USERPROFILE') | |
1187 if userprofile: | |
1188 path.append(os.path.join(userprofile, 'mercurial.ini')) | |
1189 path.append(os.path.join(userprofile, '.hgrc')) | |
1190 return path | |
1191 | |
1192 def parse_patch_output(output_line): | |
1193 """parses the output produced by patch and returns the file name""" | |
1194 pf = output_line[14:] | |
1195 if pf[0] == '`': | |
1196 pf = pf[1:-1] # Remove the quotes | |
1197 return pf | |
1198 | |
1199 def sshargs(sshcmd, host, user, port): | |
1200 '''Build argument list for ssh or Plink''' | |
1201 pflag = 'plink' in sshcmd.lower() and '-P' or '-p' | |
1202 args = user and ("%s@%s" % (user, host)) or host | |
1203 return port and ("%s %s %s" % (args, pflag, port)) or args | |
1204 | |
1205 def testpid(pid): | |
1206 '''return False if pid dead, True if running or not known''' | |
1207 return True | |
1208 | |
1209 def set_flags(f, l, x): | |
1210 pass | |
1211 | |
1212 def set_binary(fd): | |
1213 # When run without console, pipes may expose invalid | |
1214 # fileno(), usually set to -1. | |
1215 if hasattr(fd, 'fileno') and fd.fileno() >= 0: | |
1216 msvcrt.setmode(fd.fileno(), os.O_BINARY) | |
1217 | |
1218 def pconvert(path): | |
1219 return '/'.join(splitpath(path)) | |
1220 | |
1221 def localpath(path): | |
1222 return path.replace('/', '\\') | |
1223 | |
1224 def normpath(path): | |
1225 return pconvert(os.path.normpath(path)) | |
1226 | |
1227 makelock = _makelock_file | |
1228 readlock = _readlock_file | |
1229 | |
1230 def samestat(s1, s2): | |
1231 return False | |
1232 | |
1233 # A sequence of backslashes is special iff it precedes a double quote: | |
1234 # - if there's an even number of backslashes, the double quote is not | |
1235 # quoted (i.e. it ends the quoted region) | |
1236 # - if there's an odd number of backslashes, the double quote is quoted | |
1237 # - in both cases, every pair of backslashes is unquoted into a single | |
1238 # backslash | |
1239 # (See http://msdn2.microsoft.com/en-us/library/a1y7w461.aspx ) | |
1240 # So, to quote a string, we must surround it in double quotes, double | |
1241 # the number of backslashes that preceed double quotes and add another | |
1242 # backslash before every double quote (being careful with the double | |
1243 # quote we've appended to the end) | |
1244 _quotere = None | |
1245 def shellquote(s): | |
1246 global _quotere | |
1247 if _quotere is None: | |
1248 _quotere = re.compile(r'(\\*)("|\\$)') | |
1249 return '"%s"' % _quotere.sub(r'\1\1\\\2', s) | |
1250 | |
1251 def quotecommand(cmd): | |
1252 """Build a command string suitable for os.popen* calls.""" | |
1253 # The extra quotes are needed because popen* runs the command | |
1254 # through the current COMSPEC. cmd.exe suppress enclosing quotes. | |
1255 return '"' + cmd + '"' | |
1256 | |
1257 def popen(command, mode='r'): | |
1258 # Work around "popen spawned process may not write to stdout | |
1259 # under windows" | |
1260 # http://bugs.python.org/issue1366 | |
1261 command += " 2> %s" % nulldev | |
1262 return os.popen(quotecommand(command), mode) | |
1263 | |
1264 def explain_exit(code): | |
1265 return _("exited with status %d") % code, code | |
1266 | |
1267 # if you change this stub into a real check, please try to implement the | |
1268 # username and groupname functions above, too. | |
1269 def isowner(fp, st=None): | |
1270 return True | |
1271 | |
1272 def find_exe(command): | |
1273 '''Find executable for command searching like cmd.exe does. | |
1274 If command is a basename then PATH is searched for command. | |
1275 PATH isn't searched if command is an absolute or relative path. | |
1276 An extension from PATHEXT is found and added if not present. | |
1277 If command isn't found None is returned.''' | |
1278 pathext = os.environ.get('PATHEXT', '.COM;.EXE;.BAT;.CMD') | |
1279 pathexts = [ext for ext in pathext.lower().split(os.pathsep)] | |
1280 if os.path.splitext(command)[1].lower() in pathexts: | |
1281 pathexts = [''] | |
1282 | |
1283 def findexisting(pathcommand): | |
1284 'Will append extension (if needed) and return existing file' | |
1285 for ext in pathexts: | |
1286 executable = pathcommand + ext | |
1287 if os.path.exists(executable): | |
1288 return executable | |
1289 return None | |
1290 | |
1291 if os.sep in command: | |
1292 return findexisting(command) | |
1293 | |
1294 for path in os.environ.get('PATH', '').split(os.pathsep): | |
1295 executable = findexisting(os.path.join(path, command)) | |
1296 if executable is not None: | |
1297 return executable | |
1298 return None | |
1299 | |
1300 def set_signal_handler(): | |
1301 try: | |
1302 set_signal_handler_win32() | |
1303 except NameError: | |
1304 pass | |
1305 | |
1306 try: | |
1307 # override functions with win32 versions if possible | |
1308 from util_win32 import * | |
1309 if not _is_win_9x(): | |
1310 posixfile = posixfile_nt | |
1311 except ImportError: | |
1312 pass | |
1313 | |
1314 else: | |
1315 nulldev = '/dev/null' | |
1316 | |
1317 def rcfiles(path): | |
1318 rcs = [os.path.join(path, 'hgrc')] | |
1319 rcdir = os.path.join(path, 'hgrc.d') | |
1320 try: | |
1321 rcs.extend([os.path.join(rcdir, f) | |
1322 for f, kind in osutil.listdir(rcdir) | |
1323 if f.endswith(".rc")]) | |
1324 except OSError: | |
1325 pass | |
1326 return rcs | |
1327 | |
1328 def system_rcpath(): | |
1329 path = [] | |
1330 # old mod_python does not set sys.argv | |
1331 if len(getattr(sys, 'argv', [])) > 0: | |
1332 path.extend(rcfiles(os.path.dirname(sys.argv[0]) + | |
1333 '/../etc/mercurial')) | |
1334 path.extend(rcfiles('/etc/mercurial')) | |
1335 return path | |
1336 | |
1337 def user_rcpath(): | |
1338 return [os.path.expanduser('~/.hgrc')] | |
1339 | |
1340 def parse_patch_output(output_line): | |
1341 """parses the output produced by patch and returns the file name""" | |
1342 pf = output_line[14:] | |
1343 if os.sys.platform == 'OpenVMS': | |
1344 if pf[0] == '`': | |
1345 pf = pf[1:-1] # Remove the quotes | |
1346 else: | |
1347 if pf.startswith("'") and pf.endswith("'") and " " in pf: | |
1348 pf = pf[1:-1] # Remove the quotes | |
1349 return pf | |
1350 | |
1351 def sshargs(sshcmd, host, user, port): | |
1352 '''Build argument list for ssh''' | |
1353 args = user and ("%s@%s" % (user, host)) or host | |
1354 return port and ("%s -p %s" % (args, port)) or args | |
1355 | |
1356 def is_exec(f): | |
1357 """check whether a file is executable""" | |
1358 return (os.lstat(f).st_mode & 0100 != 0) | |
1359 | |
1360 def set_flags(f, l, x): | |
1361 s = os.lstat(f).st_mode | |
1362 if l: | |
1363 if not stat.S_ISLNK(s): | |
1364 # switch file to link | |
1365 data = file(f).read() | |
1366 os.unlink(f) | |
1367 try: | |
1368 os.symlink(data, f) | |
1369 except: | |
1370 # failed to make a link, rewrite file | |
1371 file(f, "w").write(data) | |
1372 # no chmod needed at this point | |
1373 return | |
1374 if stat.S_ISLNK(s): | |
1375 # switch link to file | |
1376 data = os.readlink(f) | |
1377 os.unlink(f) | |
1378 file(f, "w").write(data) | |
1379 s = 0666 & ~_umask # avoid restatting for chmod | |
1380 | |
1381 sx = s & 0100 | |
1382 if x and not sx: | |
1383 # Turn on +x for every +r bit when making a file executable | |
1384 # and obey umask. | |
1385 os.chmod(f, s | (s & 0444) >> 2 & ~_umask) | |
1386 elif not x and sx: | |
1387 # Turn off all +x bits | |
1388 os.chmod(f, s & 0666) | |
1389 | |
1390 def set_binary(fd): | |
1391 pass | |
1392 | |
1393 def pconvert(path): | |
1394 return path | |
1395 | |
1396 def localpath(path): | |
1397 return path | |
1398 | |
1399 normpath = os.path.normpath | |
1400 samestat = os.path.samestat | |
1401 | |
1402 def makelock(info, pathname): | |
1403 try: | |
1404 os.symlink(info, pathname) | |
1405 except OSError, why: | |
1406 if why.errno == errno.EEXIST: | |
1407 raise | |
1408 else: | |
1409 _makelock_file(info, pathname) | |
1410 | |
1411 def readlock(pathname): | |
1412 try: | |
1413 return os.readlink(pathname) | |
1414 except OSError, why: | |
1415 if why.errno in (errno.EINVAL, errno.ENOSYS): | |
1416 return _readlock_file(pathname) | |
1417 else: | |
1418 raise | |
1419 | |
1420 def shellquote(s): | |
1421 if os.sys.platform == 'OpenVMS': | |
1422 return '"%s"' % s | |
1423 else: | |
1424 return "'%s'" % s.replace("'", "'\\''") | |
1425 | |
1426 def quotecommand(cmd): | |
1427 return cmd | |
1428 | |
1429 def popen(command, mode='r'): | |
1430 return os.popen(command, mode) | |
1431 | |
1432 def testpid(pid): | |
1433 '''return False if pid dead, True if running or not sure''' | |
1434 if os.sys.platform == 'OpenVMS': | |
1435 return True | |
1436 try: | |
1437 os.kill(pid, 0) | |
1438 return True | |
1439 except OSError, inst: | |
1440 return inst.errno != errno.ESRCH | |
1441 | |
1442 def explain_exit(code): | |
1443 """return a 2-tuple (desc, code) describing a process's status""" | |
1444 if os.WIFEXITED(code): | |
1445 val = os.WEXITSTATUS(code) | |
1446 return _("exited with status %d") % val, val | |
1447 elif os.WIFSIGNALED(code): | |
1448 val = os.WTERMSIG(code) | |
1449 return _("killed by signal %d") % val, val | |
1450 elif os.WIFSTOPPED(code): | |
1451 val = os.WSTOPSIG(code) | |
1452 return _("stopped by signal %d") % val, val | |
1453 raise ValueError(_("invalid exit code")) | |
1454 | |
1455 def isowner(fp, st=None): | |
1456 """Return True if the file object f belongs to the current user. | |
1457 | |
1458 The return value of a util.fstat(f) may be passed as the st argument. | |
1459 """ | |
1460 if st is None: | |
1461 st = fstat(fp) | |
1462 return st.st_uid == os.getuid() | |
1463 | |
1464 def find_exe(command): | |
1465 '''Find executable for command searching like which does. | |
1466 If command is a basename then PATH is searched for command. | |
1467 PATH isn't searched if command is an absolute or relative path. | |
1468 If command isn't found None is returned.''' | |
1469 if sys.platform == 'OpenVMS': | |
1470 return command | |
1471 | |
1472 def findexisting(executable): | |
1473 'Will return executable if existing file' | |
1474 if os.path.exists(executable): | |
1475 return executable | |
1476 return None | |
1477 | |
1478 if os.sep in command: | |
1479 return findexisting(command) | |
1480 | |
1481 for path in os.environ.get('PATH', '').split(os.pathsep): | |
1482 executable = findexisting(os.path.join(path, command)) | |
1483 if executable is not None: | |
1484 return executable | |
1485 return None | |
1486 | |
1487 def set_signal_handler(): | |
1488 pass | |
1489 | 1020 |
1490 def mktempcopy(name, emptyok=False, createmode=None): | 1021 def mktempcopy(name, emptyok=False, createmode=None): |
1491 """Create a temporary file with the same contents from name | 1022 """Create a temporary file with the same contents from name |
1492 | 1023 |
1493 The permission bits are copied from the original file. | 1024 The permission bits are copied from the original file. |
1508 except OSError, inst: | 1039 except OSError, inst: |
1509 if inst.errno != errno.ENOENT: | 1040 if inst.errno != errno.ENOENT: |
1510 raise | 1041 raise |
1511 st_mode = createmode | 1042 st_mode = createmode |
1512 if st_mode is None: | 1043 if st_mode is None: |
1513 st_mode = ~_umask | 1044 st_mode = ~umask |
1514 st_mode &= 0666 | 1045 st_mode &= 0666 |
1515 os.chmod(temp, st_mode) | 1046 os.chmod(temp, st_mode) |
1516 if emptyok: | 1047 if emptyok: |
1517 return temp | 1048 return temp |
1518 try: | 1049 try: |