diff --git a/twander.py b/twander.py index bf249cd..62fda8e 100755 --- a/twander.py +++ b/twander.py @@ -6,7 +6,7 @@ # Program Information PROGNAME = "twander" -RCSID = "$Id: twander.py,v 3.105 2003/02/25 08:28:27 tundra Exp $" +RCSID = "$Id: twander.py,v 3.106 2003/02/25 23:09:02 tundra Exp $" VERSION = RCSID.split()[2] # Copyright Information @@ -266,6 +266,8 @@ NONAVIGATE = FALSE # TRUE means that all directory navigation is prevented REFRESHINT = 3000 # Interval (ms) for automatic refresh QUOTECHAR = '\"' # Character to use when quoting Built-In Variables +SORTREVERSE = FALSE # Reverse specified sort order? +SORTBYFIELD = 7 # Field to use as sort key USETHREADS = TRUE # Use threads on Unix USEWIN32ALL = TRUE # Use win32all features if available WARN = TRUE # Warnings on @@ -2643,21 +2645,21 @@ # haven't screwed up the widget's current # contents or program state. - try: - contents = BuildDirList(newdir) - except: - # If CurrentDir set, we're still there: error w/ recovery - if UI.CurrentDir: - ErrMsg(eDIRRD % newdir) - return - - # If not, we failed on the initial directory: error & abort - else: - ErrMsg(eINITDIRBAD % newdir) - sys.exit(1) - - # Push last directory visited onto the visited stack - +# try: + contents = BuildDirList(newdir) +# except: +# # If CurrentDir set, we're still there: error w/ recovery +# if UI.CurrentDir: +# ErrMsg(eDIRRD % newdir) +# return +# +# # If not, we failed on the initial directory: error & abort +# else: +# ErrMsg(eINITDIRBAD % newdir) +# sys.exit(1) +# +# # Push last directory visited onto the visited stack +# # We do NOT save this to the stack if: # # 1) We've been told not to. - Passed when we're called (save=FALSE). @@ -2731,7 +2733,8 @@ def BuildDirList(currentdir): global UI - dList, fList = [], [] + UI.TotalSize = 0 + dList, fList, detlist = [], [], [] # Indicate where in each display string the actual file name # can be found. This is used both in the code in this routine @@ -2757,33 +2760,20 @@ # Walk the directory separate subdirs and files for file in os.listdir(currentdir): - if os.path.isdir(os.path.join(currentdir, file)): + + d = FileDetails(file, currentdir) + detlist.append(d) + + if d[0] == 'd': dList.append(file + PSEP) else: fList.append(file) - # On Win32, do case-insensitive sorting since case - # is irrelevant in file names on these systems - # On Win32 we also have to promote the file name to unicode. - # This is because, if the file name contains non-ASCII - # characters, Python will throw an error if we attempt - # to concatentate it to a unicode string later. Since - # the file name gets used in a variety of places in - # the code, we do this promotion here to insure correct - # behavior. - + # Sort as requested - if OSNAME == 'nt': - - for l in (dList, fList): - lowerlist = [(x.lower(), x) for x in l] - lowerlist.sort() - l[:] = [] - [l.append(x[1]) for x in lowerlist] - - else: - dList.sort() - fList.sort() + dList.sort() + fList.sort() + detlist.sort() # Entry to move up one directory is always first, # no matter what the sort. This is necessary because @@ -2791,17 +2781,26 @@ # sorts before "." dList.insert(0, ".." + PSEP) + detlist.insert(0, FileDetails(".." + PSEP, currentdir)) - # Save the complete list for details extraction below - - all = dList + fList + # Return appropriate list - w/ or w/o details. + + if UI.DetailsOn: + return detlist + else: + return dList + fList # The user requested Drive List View. - else: - UI.TotalSize = 0 + drivelist = GetWin32Drives() + # Sort and, optionally, reverse the order + + drivelist.sort() + if SORTREVERSE: + drivelist.reverse() + # If details are off, just return the list of drives if not UI.DetailsOn: return drivelist @@ -2868,221 +2867,220 @@ return details - # Get details on directory contents - - detlist = [] - UI.TotalSize = 0 - for index in range(len(all)): - - # Make room for the new detailed entry - detlist.append("") - - # Condition the name - - fn = os.path.join(currentdir, all[index]) - if fn[-1] == PSEP: - fn =fn[:-1] - - # Get file details from OS - try: - stinfo = os.lstat(fn) - - # 'lstat' failed - provide entry with some indication of this - - except: - pad = (UI.NameFirst - len(iNOSTAT) - 1) * " " - detlist[index] = pad + iNOSTAT + " " + all[index] - - # Done with this file, but keep going - continue - - # Do Win32-specific mode if win32all is loaded - if WIN32ALL and USEWIN32ALL and WIN32ALLON: - modlen = len(win32all_mode) - - try: - win32stat = GetFileAttributes(fn) - mode = "" - except: - mode = UNAVAILABLE - - if not mode: - - # Test each attribute and set symbol in respective - # position, if attribute is true. - - for i in range(modlen): - mask, sym = win32all_mode[i] - if win32stat & mask: - mode += sym - else: - mode += '-' - - # We're either on Unix or Win32 w/o win32all available - else: - # Mode - 1st get into octal string - - mode = stinfo[ST_MODE] - modestr = str("%06o" % mode) - - # Set the permission bits - - mode = "" - for x in [-3, -2, -1]: - mode += ST_PERMIT[int(modestr[x])] - - # Deal with the special permissions - - sp = int(modestr[-4]) - - # Sticky Bit - - if sp & STICKY_MASK: - if mode[-1] == "x": - mode = mode[:-1] + "t" - else: - mode = mode[:-1] + "T" - - # Setgid Bit - - if sp & SETGID_MASK: - if mode[-4] == "x": - mode = mode[:-4] + "s" + mode[-3:] - else: - mode = mode[:-4] + "S" + mode[-3:] - - # Setuid Bit - - if sp & SETUID_MASK: - if mode[-7] == "x": - mode = mode[:-7] + "s" + mode[-6:] - else: - mode = mode[:-7] + "S" + mode[-6:] - - # Pickup the special file types - mode = ST_SPECIALS.get(modestr[0:2], "?") + mode - - - # Pad the result for column alignment - detlist[index] += PadString(mode, ST_SZMODE) - - # Number of links to entry - detlist[index] += PadString(str(stinfo[ST_NLINK]), ST_SZNLINK) - - # Get first ST_SZxNAME chars of owner and group names on unix - - if OSNAME == 'posix': - - # Convert UID to name, if possible - try: - owner = pwd.getpwuid(stinfo[ST_UID])[0][:ST_SZUNAME-1] - - # No valid name associated with UID, so use number instead - except: - owner = str(stinfo[ST_UID]) - - # Convert GID to name, if possible - try: - group = grp.getgrgid(stinfo[ST_GID])[0][:ST_SZGNAME-1] - - # No valid name associated with GID, so use number instead - except: - group = str(stinfo[ST_GID]) - - - # Handle Win32 systems - elif OSNAME == 'nt': - - # Defaults - owner = WIN32OWNER - group = WIN32GROUP - - if WIN32ALL and USEWIN32ALL and WIN32ALLON: - try: - # Get the internal Win32 security information for this file. - ho = GetFileSecurity(fn, OWNER_SECURITY_INFORMATION) - hg = GetFileSecurity(fn, GROUP_SECURITY_INFORMATION) - sido = ho.GetSecurityDescriptorOwner() - sidg = hg.GetSecurityDescriptorGroup() - - # We have to know who is hosting the filesytem for this file - - drive = fn[0:3] - if GetDriveType(drive) == win32con.DRIVE_REMOTE: - fnhost = WNetGetUniversalName(drive, 1).split('\\')[2] - else: - fnhost = WIN32HOST - - # Now we can translate the sids into names - - owner = LookupAccountSid(fnhost, sido)[0] - group = LookupAccountSid(fnhost, sidg)[0] - - # We assume the values are unavailable on any error - except: - owner = UNAVAILABLE - group = UNAVAILABLE - - # Default names for all other OSs - else: - owner = OSNAME + FILEOWNER - group = OSNAME + FILEGROUP - - # Add them to the detail - - detlist[index] += PadString(owner, ST_SZUNAME) - detlist[index] += PadString(group, ST_SZGNAME) - - # Length - - flen = FileLength(stinfo[ST_SIZE]) - UI.TotalSize += stinfo[ST_SIZE] - detlist[index] += PadString(flen, ST_SZLEN) - - # mtime - - # Get the whole time value - ftime = time.ctime(stinfo[ST_MTIME]).split()[1:] - - # Pad single-digit dates with leading space - - if len(ftime[1]) == 1: - ftime[1] = " " + ftime[1] - - # Drop the seconds - ftime[-2] = ":".join(ftime[-2].split(":")[:-1]) - - # Turn into a single string - ftime = " ".join(ftime) - - detlist[index] += PadString(ftime, ST_SZMTIME) - - # Add the File Name - detlist[index] += all[index] - - # Include symlink details as necessary - if detlist[index][0] == 'l': - - # If the symlink points to a file - # in the same directory, just show - # the filename and not the whole path - - f = os.path.realpath(currentdir + all[index]) - r = os.path.split(f) - if r[0] == currentdir[:-1]: - f = r[1] - - detlist[index] += SYMPTR + f - - if UI.DetailsOn: - return detlist - else: - return dList + fList - # End of 'BuildDirList()' ##### +# Return Details For A File Or Directory +##### + +def FileDetails(name, currentdir): + + details = "" + + # Condition the name + + fn = os.path.join(currentdir, name) + if fn[-1] == PSEP: + fn =fn[:-1] + + # Get file details from OS + try: + stinfo = os.lstat(fn) + + # 'lstat' failed - provide entry with some indication of this + + except: + pad = (UI.NameFirst - len(iNOSTAT) - 1) * " " + details = pad + iNOSTAT + " " + name + + # Done with this file + return details + + # Do Win32-specific mode if win32all is loaded + if WIN32ALL and USEWIN32ALL and WIN32ALLON: + modlen = len(win32all_mode) + + try: + win32stat = GetFileAttributes(fn) + mode = "" + except: + mode = UNAVAILABLE + + if not mode: + + # Test each attribute and set symbol in respective + # position, if attribute is true. + + for i in range(modlen): + mask, sym = win32all_mode[i] + if win32stat & mask: + mode += sym + else: + mode += '-' + + # We're either on Unix or Win32 w/o win32all available + else: + # Mode - 1st get into octal string + + mode = stinfo[ST_MODE] + modestr = str("%06o" % mode) + + # Set the permission bits + + mode = "" + for x in [-3, -2, -1]: + mode += ST_PERMIT[int(modestr[x])] + + # Deal with the special permissions + + sp = int(modestr[-4]) + + # Sticky Bit + + if sp & STICKY_MASK: + if mode[-1] == "x": + mode = mode[:-1] + "t" + else: + mode = mode[:-1] + "T" + + # Setgid Bit + + if sp & SETGID_MASK: + if mode[-4] == "x": + mode = mode[:-4] + "s" + mode[-3:] + else: + mode = mode[:-4] + "S" + mode[-3:] + + # Setuid Bit + + if sp & SETUID_MASK: + if mode[-7] == "x": + mode = mode[:-7] + "s" + mode[-6:] + else: + mode = mode[:-7] + "S" + mode[-6:] + + # Pickup the special file types + mode = ST_SPECIALS.get(modestr[0:2], "?") + mode + + + # Pad the result for column alignment + details += PadString(mode, ST_SZMODE) + + # Number of links to entry + details += PadString(str(stinfo[ST_NLINK]), ST_SZNLINK) + + # Get first ST_SZxNAME chars of owner and group names on unix + + if OSNAME == 'posix': + + # Convert UID to name, if possible + try: + owner = pwd.getpwuid(stinfo[ST_UID])[0][:ST_SZUNAME-1] + + # No valid name associated with UID, so use number instead + except: + owner = str(stinfo[ST_UID]) + + # Convert GID to name, if possible + try: + group = grp.getgrgid(stinfo[ST_GID])[0][:ST_SZGNAME-1] + + # No valid name associated with GID, so use number instead + except: + group = str(stinfo[ST_GID]) + + + # Handle Win32 systems + elif OSNAME == 'nt': + + # Defaults + owner = WIN32OWNER + group = WIN32GROUP + + if WIN32ALL and USEWIN32ALL and WIN32ALLON: + try: + # Get the internal Win32 security information for this file. + ho = GetFileSecurity(fn, OWNER_SECURITY_INFORMATION) + hg = GetFileSecurity(fn, GROUP_SECURITY_INFORMATION) + sido = ho.GetSecurityDescriptorOwner() + sidg = hg.GetSecurityDescriptorGroup() + + # We have to know who is hosting the filesytem for this file + + drive = fn[0:3] + if GetDriveType(drive) == win32con.DRIVE_REMOTE: + fnhost = WNetGetUniversalName(drive, 1).split('\\')[2] + else: + fnhost = WIN32HOST + + # Now we can translate the sids into names + + owner = LookupAccountSid(fnhost, sido)[0] + group = LookupAccountSid(fnhost, sidg)[0] + + # We assume the values are unavailable on any error + except: + owner = UNAVAILABLE + group = UNAVAILABLE + + # Default names for all other OSs + else: + owner = OSNAME + FILEOWNER + group = OSNAME + FILEGROUP + + # Add them to the detail + + details += PadString(owner, ST_SZUNAME) + details += PadString(group, ST_SZGNAME) + + # Length + + flen = FileLength(stinfo[ST_SIZE]) + UI.TotalSize += stinfo[ST_SIZE] + details += PadString(flen, ST_SZLEN) + + # mtime + + # Get the whole time value + ftime = time.ctime(stinfo[ST_MTIME]).split()[1:] + + # Pad single-digit dates with leading space + + if len(ftime[1]) == 1: + ftime[1] = " " + ftime[1] + + # Drop the seconds + ftime[-2] = ":".join(ftime[-2].split(":")[:-1]) + + # Turn into a single string + ftime = " ".join(ftime) + + details += PadString(ftime, ST_SZMTIME) + + # Add the File Name + details += name + + # Include symlink details as necessary + if details[0] == 'l': + + # If the symlink points to a file + # in the same directory, just show + # the filename and not the whole path + + f = os.path.realpath(currentdir + name) + r = os.path.split(f) + if r[0] == currentdir[:-1]: + f = r[1] + + details += SYMPTR + f + + return details + +# End of 'FileDetails()' + + +##### # Process A Command Line Containing Built-In Variables ##### @@ -3616,16 +3614,16 @@ # Options (and their default values) which can be set in the configuration file UI.OptionsBoolean = {"AUTOREFRESH":AUTOREFRESH, "NODETAILS":NODETAILS, "NONAVIGATE":NONAVIGATE, - "USETHREADS":USETHREADS, "USEWIN32ALL":USEWIN32ALL, "WARN":WARN} + "SORTREVERSE":SORTREVERSE, "USETHREADS":USETHREADS, "USEWIN32ALL":USEWIN32ALL, "WARN":WARN} UI.OptionsNumeric = {"DEBUGLEVEL":DEBUGLEVEL, "FSZ":FSZ, "MFSZ":MFSZ, "HFSZ":HFSZ, "HEIGHT":HEIGHT, "MAXMENU":MAXMENU, "MAXMENUBUF":MAXMENUBUF, "MAXNESTING":MAXNESTING, - "REFRESHINT":REFRESHINT, "STARTX":STARTX, "STARTY":STARTY, "WIDTH":WIDTH} + "REFRESHINT":REFRESHINT, "SORTBYFIELD":SORTBYFIELD, "STARTX":STARTX, "STARTY":STARTY, "WIDTH":WIDTH} UI.OptionsString = {"BCOLOR":BCOLOR, "FCOLOR":FCOLOR, "FNAME":FNAME, "FWT":FWT, # Main Font/Colors "MBCOLOR":MBCOLOR, "MFCOLOR":MFCOLOR, "MFNAME":MFNAME, "MFWT":MFWT, # Menu Font/Colors "HBCOLOR":HBCOLOR, "HFCOLOR":HFCOLOR, "HFNAME":HFNAME, "HFWT":HFWT, # Help Font/Colors - "MBARCOL":MBARCOL, "QUOTECHAR":QUOTECHAR, "STARTDIR":STARTDIR, "CMDSHELL":CMDSHELL} # Other + "MBARCOL":MBARCOL, "QUOTECHAR":QUOTECHAR, "STARTDIR":STARTDIR, "CMDSHELL":CMDSHELL} # Other # Prepare storage for key bindings UI.KeyBindings = {}