The Multiple File Import tool in JMP can make a data table of the names/size/date of the files it found. The JSL in the attached add-in (JMP 16, Windows only) takes that data table and creates a nested tree view of the disk space used by the files. It is medium speed; it loaded 500K files from the root of the C drive in about 30 minutes (got bored, watched the news, not sure exactly...) There is a viewer for a few file types; JSL shown below.
Not starting at the root is faster.
The JSL starts with a prompt for a directory. This always seems clumsy to use because double click doesn't select a directory...it opens it. Highlight a directory and pick OK. The directory is given to MFI and the file list retrieved. A couple of checks for Cancel are made along the way.
root = Pick Directory( "show directory sizes", "$Desktop/.." );
If( root == "", Throw( "canceled" ));
mfi = Multiple File Import(
<<Set Folder( root ),
<<Set Show Hidden( 0 ),
<<Set Subfolders( 1 ),
<<Set Name Filter( "*.*;" ),
<<Set Name Enable( 0 ),
<<Set Size Filter( {-1, 5936772669} ),
<<Set Size Enable( 0 ),
<<Set Date Filter( {0, 3725849188.969} ),
<<Set Date Enable( 0 )
);
boxtop = mfi << createwindow;
dtFiles = boxtop[Table Box( 1 )] << MakeIntoDataTable( invisible( 1 ) );
boxtop << closewindow;
nRowsDtFiles = N Rows( dtFiles );
If( nRowsDtFiles == 1 & Is Missing( dtFiles:FileSize[1] ),
Throw( "canceled?" )
);
Some modified code from Progress Bar with Cancel Button that will recycle the progress bar window at the end.
progressBarWidth = 500;
cancel = 0;
New Window( "Directory Tree",
windowbox = V List Box(
t = Text Box( "", <<setwrap( 500 ) ),
H List Box(
left = Spacer Box( size( 0, 10 ), color( "light green" ) ),
right = Spacer Box( size( progressBarWidth, 10 ), color( "dark green" ) ),
<<padding( 5, 5, 5, 5 ),
<<backgroundcolor( "dark gray" )
),
cancelButton = Button Box( "Cancel", cancel = 1 )
)
);
Wait( 0 );
updateProgress = Function( {fractionComplete},
leftsize = Round( progressBarWidth * fractionComplete * .5 );
rightsize = progressBarWidth - leftsize;
left << width( leftsize );
right << width( rightsize );
t << settext( Eval Insert( "organizing data ^irow^ / ^nRowsDtFiles^" ) );
t << updatewindow;
);
And it is time to jump in. This code makes a pass over the data table, building a tree that mirrors the shape of the folder tree on disk. When done, the data table is closed because all the data lives in the tree that is in the associative array. The links that connect the tree together are actually keys in the associative array.
init = {{}, 0, 0.0};
directory = [=> ];
directory[root] = init;
nBytes = 3;
nDate = nCount = 2;
For( irow = 1, irow <= nRowsDtFiles & !cancel, irow += 1,
levels = Words( dtFiles:FileName[irow], "/" );
path = root;
size = dtFiles:FileSize[irow];
date = dtFiles:FileDate[irow];
directory[root][nCount] += 1;
directory[root][nBytes] += size;
For( ilevel = 1, ilevel < N Items( levels ), ilevel += 1,
parent = path;
path = path || levels[ilevel] || "\";
If( !Contains( directory, path ),
directory[path] = init;
Insert Into( directory[parent][1], Eval List( {Eval List( {levels[iLevel], .} )} ), 1 );
);
directory[path][nCount] += 1;
directory[path][nBytes] += size;
If( Mod( irow, 1000 ) == 0,
updateProgress( irow / nRowsDtFiles );
Wait( 0 );
);
);
If( Is Missing( date ),
Throw( "missing date?" )
);
Insert Into( directory[path][1], Eval List( {Eval List( {levels[iLevel], date, size} )} ) );
);
Close( dtFiles, nosave );
Check for cancel, create the pretty icons, set up the second half of the progress bar. Yes, making your own custom icons can be as simple as that! By changing both the left-to-right red-to-gray and the shade of red, the icons are a little more expressive about the huge range of file sizes.
If( cancel,
Beep();
left << color( "dark red" );
right << color( "red" );
cancelButton << setbuttonname( "Close" );
cancelButton << setscript( (cancelButton << closewindow) );
Stop();
,
updateProgress( 1.0 );
);
redcolor = {0.627450980392157, 0.0352941176470588, 0.133333333333333};
graycolor = {0.831372549019608, 0.831372549019608, 0.831372549019608};
ncons = 64;
For( i = 0, i <= ncons, i += 1,
H List Box(
Spacer Box( size( i, ncons ), color( RGB Color( redcolor * (i / ncons) + graycolor * (1 - i / ncons) ) ) ),
Spacer Box( size( ncons - i, ncons ), color( RGB Color( graycolor ) ) )
) << savepicture( "$temp/deletemeIcon" || Char( i ) || ".png" )
);
biggestsize = Log( directory[root][nBytes] + 1 );
icon = Function( {size},
size = Floor( ncons * Log( size + 1 ) / biggestsize );
"$temp/deletemeIcon" || Char( size ) || ".png";
);
updateProgress2 = Function( {fractionComplete},
leftsize = Round( .5 * progressBarWidth + progressBarWidth * fractionComplete * .5 );
rightsize = progressBarWidth - leftsize;
left << width( leftsize );
right << width( rightsize );
t << settext( Eval Insert( "finishing up display constuction ^filesDone^ / ^nRowsDtFiles^" ) );
t << updatewindow;
);
The explore function is recursive; it explores the tree in the associative array and builds a sorted tree node control.
explore = Function( {path, attachpoint},
{treeNode, list, childpath, nfiles, bytes, appendTnode, appendSizes, m, newbutton},
list = directory[path][1];
appendTnode = {};
appendSizes = {};
For Each( {namedata}, list,
If( Is Missing( namedata[nDate] ),
If( N Items( namedata ) != 2,
Throw( "dir? " || Char( namedata ) )
);
childpath = path || namedata[1] || "\";
nfiles = directory[childpath][nCount];
bytes = directory[childpath][nBytes];
treeNode = Tree Node(
Eval Insert(
"\[^Format(bytes, "Fixed Dec", Use thousands separator( 1 ), 30, 0 )^ bytes in directory with ^nfiles^ items ^namedata[1]^]\"
)
);
treeNode << seticon( icon( bytes ) );
treeNode << setdata( childpath );
explore( childpath, treeNode );
Insert Into( appendTnode, treeNode );
Insert Into( appendSizes, bytes );
,
treeNode = Tree Node(
Eval Insert(
"\[^Format(namedata[nBytes], "Fixed Dec", Use thousands separator( 1 ), 30, 0 )^ bytes updated ^round((today()-namedata[nDate])/inweeks(1))^ weeks ago ^namedata[1]^]\"
)
);
treeNode << seticon( icon( namedata[nBytes] ) );
childpath = path || namedata[1];
treeNode << setdata( childpath );
Insert Into( appendTnode, treeNode );
Insert Into( appendSizes, namedata[nBytes] );
filesDone += 1;
If( Mod( filesDone, 100 ) == 0,
Try( updateProgress2( filesDone / nRowsDtFiles ) );
Wait( 0 );
If( cancel,
Beep();
left << color( "dark red" );
right << color( "red" );
cancelButton << setbuttonname( "Close" );
cancelButton << setscript( (cancelButton << closewindow) );
Throw( "canceled" );
);
);
)
);
m = Rank( appendsizes );
For( j = N Items( m ), j >= 1, j--,
i = m[j];
attachpoint << append( appendTnode[i] );
);
);
The TreeClickHandler opens a simple viewer for a few file types.
treeClickHandler = Function( {thistree, thisnode},
{extension, data, tablename, nodedata, nodelabel},
nodelabel = thisnode << getlabel;
nodedata = thisnode << getdata;
line1 << settext( nodelabel );
line2 << setbuttonname( nodedata );
Try( (view << child) << delete, Show( exception_msg ) );
extension = Regex( nodedata, ".*?([^\.]*)$", "\1" );
If(
Contains( {"png", "jpg", "jpeg", "gif", "bmp"}, Lowercase( extension ) ),
view << append( New Image( nodedata ) );
,
Contains( {"txt", "jsl", "csv"}, Lowercase( extension ) ),
data = Load Text File( nodedata, blob( readlength( 2000 ) ) );
If( Length( data ) < 2000,
data = Blob To Char( data )
,
data = Blob To Char( data ) || "\!n\!n✁✂✃✄\!n\!ntruncated\!n\!n✁✂✃✄\!n"
);
view << append( Text Box( (data), <<setwrap( 1000 ) ) );
,
Contains( {"jrn"}, Lowercase( extension ) ),
view << append( Journal Box( Load Text File( nodedata ) ) );
,
Contains( {"jmp"}, Lowercase( extension ) ),
tablename = nodedata;
If( File Size( tablename ) < 1e6,
dt = Open( tablename, invisible );
view << append( Journal Box( Data Table Box( dt ) << get journal ) );
Close( dt, nosave );
,
view << append( Text Box( "table>1MB, too big for this viewer, click link above to open it" ) );
);
,
Ends With( extension, "\" ),
view << append( Text Box( "This is a directory. Click the link above to open it." ) );
,
view << append( Text Box( "Not sure what this is, don't click the link unless you want JMP to open() it." ) );
);
);
Finally, make the root node, pass it to explore(), make a window with a Tree Box to show the tree node control. There is a button that will open() files; you should be careful pressing it. There is (not shown) a short list of executable file extensions, but certainly not comprehensive, to try to keep you out of trouble...
rootTreeNode = Tree Node( Eval Insert( "\[^Format(directory[root][nBytes], "Fixed Dec", Use thousands separator( 1 ), 30, 0 )^ ^root^]\" ) );
rootTreeNode << seticon( icon( directory[root][nBytes] ) );
explore( root, rootTreeNode );
windowbox = windowbox << parent;
(windowbox << child) << delete;
windowbox << append(
H Splitter Box(
size( 1000, 600 ),
treebox = Tree Box( rootTreeNode ),
vlistbox = V List Box(
line1 = Text Box(),
line2 = Button Box( "",
setfunction(
Function( {this},
name = this << getbuttonname;
extension = Uppercase( Regex( name, ".*?([^\.]*)$", "\1" ) );
If( !Contains( executable, extension ),
Open( name, Add to Recent Files( 0 ) )
,
Beep()
);
)
)
),
scrollbox = Scroll Box( view = Border Box() )
)
)
);
windowbox << Set Window Title( "Directory Tree " || root );
line2 << UnderlineStyle( 1 );
line1 << Set Stretch( "fill", "off" );
line2 << Set Stretch( "fill", "off" );
scrollbox << Set Stretch( "off", "off" );
treebox << Set Auto Stretching( 1, 1 ) << Set Max Size( 10000, 10000 ) << User Resizable( {0, 0} );
scrollbox << Set Auto Stretching( 1, 1 ) << Set Max Size( 10000, 10000 ) << User Resizable( {0, 0} );
treebox << Set Node Select Script( treeClickHandler );
treebox << expand( rootTreeNode );
A picture
A table
A journal
If you install the add-in, you can peek at the JSL using view->addins. You can also download it and rename it with a .zip extension.
(First pass of this JSL used outline nodes, which allow for a lot more bells and whistles than the tree nodes. But outline nodes are not well suited to displaying 100K+ nested nodes; tree nodes are designed for that job. If you spot some left over outline references (I think I cleaned them up...) that's what happened.)