Re: Directory/file browser as TreeView



Hi Joe,

* Joe Smith <jes martnet com> [2006-10-03 09:15]:
Thanks for posting your version of the browser. I'm learning
a lot going through it--I think I almost see how it works ;-)

glad I can help. :-)

31:  $process->( $pathname, sort +( no_upwards @content ) );

What's the "+" for?

To force parsing as a value. Otherwise, Perl thinks these parens
belong to `sort`, as in

    sort( no_upwards @content )

in which case a special magic case kicks in wherein a bare sub
name is assumed to be a comparison routine which `sort` uses to
establish the order. The magic is so you can write

    sort by_price @product_record;

where somewhere else you define

    sub by_price { ... }

Unfortunately, thatâs not at all what I want in this case, and
for some unfathomable reason, Perl will decide you mean this even
if you use parens on the function call:

    sort no_upwards( @content ) # does not parse as expected!

That caught me completely by surprise. It took me half an hour to
find why the . and .. entries werenât getting removed (which of
course caused all sorts of problems with recursion).

You can use the unary plus more or less wherever a pair of
delimiters is ambiguous.

F.ex., sometimes itâs not clear whether a pair of curly braces is
supposed to be a code block or an anonymous hash constructor. (I
used to have a simple example of such a case but I canât remember
it right nowâ) In that case you can prefix the opening curly with
a plus to indicate that youâre looking for a hash reference.

Much more often, itâs not unambiguous whether a pair of parens is
supposed to delimit a functionâs argument list or is a way to
establish precedence or a sub-list. F.ex.: this wonât do what you
probably meant:

    print  ( 3 + 5 ) * 2;

Output: 8. Thatâs because it parses as

    ( print( 3 + 5 ) * 2 );

But you can use the unary plus to say âthis ainât an argument
list hereâ:

    print +( 3 + 5 ) * 2;

Works as expected. A circumstance where I use this often is

    my %hash = map +( $_ => func( $_ ) ), @list;

If you omit the plus, it parses like this:

    my %hash = ( map { $_ } func( $_ ) ), @list;

Of course in this case you can just as well use curlies to
disambiguate. I just prefer this notation for my own (irrelevant
for this discussion) reasons.

Also, is it necessary to pass the $process closure as that bare
code block (in populate_node) as you did, as opposed to a "sub
{ ...}"?

No.

Here's what I mean:

sub process_dir_at_iter(&;@) {
     my ( $process, $tree_model, $iter ) = @_;
     ...
}

sub populate_node {
     ...
     process_dir_at_iter {
       ...
     } $tree_store, $node;
}

This syntax is unfamiliar to me, first I'm not that familiar
with prototypes,

Donât worry about that. Prototypes are almost useless. There are
only two of them I ever use: `()` and `(&;@)`. The first says
âthis never takes any parametersâ, which clues the Perl parser in
about whether `func + 1` means `func( +1 )` or `func() + 1`. For
the other, see below.

then the call to process_dir_at_iter without a proper-looking
argument list (no comma after the code block--I guess the
prototype makes that acceptable).

Yes. The prototype lets you write

    someutil( sub { ... }, $more, @args );

a bit shorter as

    someutil { ... } $more, @args;

That is all. There is absolutely no difference in how the code
works. This special case exists *only* for prototypes which
specify a sub as the first parameter in the list. I wish it also
worked when the sub was the last, so that I could instead write

    someutil $arg, @for_code { ... };

as that would read a lot more naturally in many cases. No luck
thoughâ

Note that you donât *have* to use the short form, despite the
prototype. You can write the call with `sub { ... }` as you
normally would and Perl will accept it without complaint.

The `;@` in the prototype just means âaccept any number of
parameters that follow here.â I add this so that outside
accepting a bare block, calls to the sub will behave ordinarily.

Now if I follow what you're doing, you install an idle closure
for each directory, that reads one entry from that dir on each
idle pass. Once all the entries are read, they're added en
masse to the tree model. Once the closure is finished it
returns FALSE, so Gtk drops it from the idle list and the
closure goes *poof*

Precisely.

For me yours is a little smoother, but actually both versions
handle the largest directories I have
(~2k:/usr/bin..~6k:/usr/share/man/man3) quite well.

Yeah, the difference is not *huge* on local filesystems on recent
hardware, though itâs there â try /usr/lib sometime. To really
test it at all youâd need to browse a large Squid cache dir or
a big NNTP serverâs spool or something. However, do this on older
hardware or with slow filesystems like an NFS mount, and the
difference will be dramatic. Instead of sitting there like a dead
duck for 20 seconds or maybe several minutes, the browser will
remain responsive all along, even if the thing you were looking
for takes a while to show up.

Actually, now that I think about it, Iâm not sure of the order in
which glib schedules the callbacks. If it calls them in round
robin, which is likely, then my code will actually usually be
slower in total, because it will be reading from several
different locations on the disk concurrently, causing a lot of
head motion. (OTOH, when it happens to be reading from multiple
devices at once, then it may be faster.) If that were a serious
concern, one could introduce some sort of explicit queueing
mechanism to serialise the I/O.

It would also be useful to attempt to read, say, 10 entries on
every invocation of the callback instead of just one. That will
reduce scheduling overhead by a good deal.

But I was attempting to get the GUI side of things right while
keeping the code simple enough to be instructional, and I think
what I got is just barely within that constraint as is.

Regards,
-- 
Aristotle Pagaltzis // <http://plasmasturm.org/>



[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]