Wednesday, August 18, 2010

Fun with detailed files - and stat

In a previous post, I tried to find a way to convert Unix user and group ids into their corresponding names. After I reported my findings to the use-revolution mailing list, Andre Garzia stepped in, suggesting to take a look at the stat command.

Calling 'stat' through the shell function for every file would be costly and slow; using it to fetch the information for an entire folder in one go wouldn't be so bad, but if you already use the detailed files function built into revTalk, it would seem like a waste of cycles. So I decided to rewrite my work handlers, employing a caching mechanism for the id-to-name mapping, and using 'stat' to pick up the mapping information not available in the cache.
--> script-local 'static' variables

local sGroupIdMap, sUserIdMap

--> public functions and commands

command LoadIdMaps
end LoadIdMaps

function GroupIdToName pGroupId, pFile
local tName, tShellCommand
put sGroupIdMap[pGroupId] into tName
if tName is empty and the platform is "MacOS" then
-- stat is only available on MacOS X
put p_GetFileStat(pFile, "%Sg") into tName
put tName into sGroupIdMap[pGroupId]
end if
return tName
end GroupIdToName

function UserIdToName pUserId, pFile
local tName, tShellCommand
put sUserIdMap[pUserId] into tName
if tName is empty and the platform is "MacOS" then
-- stat is only available on MacOS X
put p_GetFileStat(pFile, "%Su") into tName
put tName into sUserIdMap[pUserId]
end if
return tName
end UserIdToName

--> private functions and commands

private function p_GetFileStat pFile, pFormat
local tName, tShellCommand
-- make sure to escape spaces in the file name
replace space with backslash & space in pFile
put "stat -f" && pFormat && pFile into tShellCommand
put word 1 of shell(tShellCommand) into tName
return tName
end p_GetFileStat

private command p_LoadGroupIdMap
if the platform is "Win32" then exit p_LoadGroupIdMap
p_LoadFileIdMap "/etc/group", sGroupIdMap
end p_LoadGroupIdMap

private command p_LoadUserIdMap
if the platform is "Win32" then exit p_LoadUserIdMap
p_LoadFileIdMap "/etc/passwd", sUserIdMap
end p_LoadUserIdMap

private command p_LoadFileIdMap pFile, @pIdMap
local tData, tLine
put URL ("file:" & pFile) into tData
set the itemDelimiter to colon
repeat for each line tLine in tData
-- skip empty and comment lines
if tLine is empty \
or char 1 of tLine is "#" then next repeat
-- fill the id map
put item 1 of tLine into pIdMap[item 3 of tLine]
end repeat
end p_LoadFileIdMap

Armed with the above set of commands and functions, we can still use the detailed files function and obtain user and group names from a cache, calling into 'stat' as necessary. Let's update our sample button script:
on mouseUp
local tOldFolder, tNewFolder, tDetailedFiles
answer folder "Select a folder"
if the result is "Cancel" then exit mouseUp
put it into tNewFolder
--> extract the detailed files information
put the defaultFolder into tOldFolder
set the defaultFolder to tNewFolder
put the detailed files into tDetailedFiles
--> load the group and user id maps
--> build the extended file information
local tDetailedFile, tExtendedFiles
local tFile, tUserId, tGroupId
repeat for each line tDetailedFile in tDetailedFiles
-- skip hidden files
if char 1 of tDetailedFile is "." then next repeat
-- fill the extended file entry
put urlDecode(item 1 of tDetailedFile) into tFile
put item 8 of tDetailedFile into tUserId
put item 9 of tDetailedFile into tGroupId
put tFileName & tab & \
UserIdToName(tUserId, tFile) & tab & \
GroupIdToName(tGroupId, tFile) & return \
after tExtendedFiles
end repeat
--> wrap things up
set the defaultFolder to tOldFolder
put char 1 to -2 of tExtendedFiles into field "Files"
end mouseUp

The more observant amongst you may think "If all you needed was a file's user and group name, why not just 'stat' and get it over with?" - well, the detailed files function returns a lot of useful information in one go, including the size of the resource fork of a file on MacOS systems.
Just one more thing: the 'stat' function was added in MacOS X 10.4 Tiger - so if your application still needs to support MacOS X 10.3 Panther, you're going to have to merge in the 'nireport' call from the previous post. Consider it an exercise for the reader.

On to the next challenge!

No comments: