X-Git-Url: http://git.rot13.org/?a=blobdiff_plain;f=lib%2FMojoFacets%2FData.pm;h=b8b7786925a3150fd6834f1b8c7abc103195c1e1;hb=a03f3d6c0f8a5ff3f05f3477038c872d87c81143;hp=910a1860d2365e6026a1ba022c2fa7cbc0f1b7f0;hpb=681f46ec7753a1dbd36cc82662eef51d04ed16ec;p=MojoFacets.git diff --git a/lib/MojoFacets/Data.pm b/lib/MojoFacets/Data.pm index 910a186..b8b7786 100644 --- a/lib/MojoFacets/Data.pm +++ b/lib/MojoFacets/Data.pm @@ -12,6 +12,7 @@ use locale; use File::Find; use Storable; use Time::HiRes qw(time); +use File::Path qw(mkpath); use MojoFacets::Import::File; use MojoFacets::Import::HTMLTable; @@ -38,7 +39,7 @@ sub index { $file =~ s/$data_dir\/*//; push @files, $file; } else { - warn "IGNORE: $file\n"; + #warn "IGNORE: $file\n"; } }, $data_dir); @@ -129,7 +130,7 @@ sub __stats { } if ( $unique ) { $stats->{$n}->{unique} = 1; - warn "# $n unique ",dump( $unique ); + #warn "# $n unique ",dump( $unique ); } } } @@ -216,16 +217,23 @@ sub _load_path { sub load { my $self = shift; + my $path = $self->param('path') || $self->redirect_to( '/data/index' ); + my @paths = $self->param('paths'); warn "# paths ", dump @paths; + + foreach my $p ( keys %$loaded ) { + next if grep { /^\Q$p\E$/ } @paths; + warn "remove $p from memory\n"; + delete $loaded->{$p}; + } + $self->_load_path( $_ ) foreach @paths; - my $path = $self->param('path') || $self->redirect_to( '/data/index' ); warn "# path $path\n"; $self->_load_path( $path ); $self->session( 'path' => $path ); - $self->session( 'modified' => $loaded->{$path}->{modified} ); my $redirect_to = '/data/items'; @@ -252,16 +260,33 @@ sub _loaded { my ( $self, $name ) = @_; my $path = $self->session('path') || $self->param('path'); $self->redirect_to('/data/index') unless $path; + + if ( defined $loaded->{$path}->{modified} && $loaded->{$path}->{modified} > 1 ) { + my $caller = (caller(1))[3]; + if ( $caller =~ m/::edit/ ) { + warn "rebuild stats for $path ignored caller $caller\n"; + } else { + warn "rebuild stats for $path FORCED by modified caller $caller\n"; + $loaded->{$path}->{stats} = __stats( $loaded->{$path}->{data}->{items} ); + $loaded->{$path}->{modified} = 1; + } + } + if ( ! defined $loaded->{$path}->{$name} ) { warn "$path $name isn't loaded\n"; $self->_load_path( $path ); - $self->redirect_to('/data/index') - unless defined $loaded->{$path}->{$name}; if ( ! defined $loaded->{$path}->{stats} ) { warn "rebuild stats for $path\n"; $loaded->{$path}->{stats} = __stats( $loaded->{$path}->{data}->{items} ); } + if ( ! defined $loaded->{$path}->{$name} ) { + warn "MISSING $name for $path\n"; + $self->redirect_to('/data/index') + } } + + $self->session( 'modified' => $loaded->{$path}->{modified} ); + return $loaded->{$path}->{$name}; } @@ -283,10 +308,12 @@ sub _permanent_path { sub _export_path { my $self = shift; my $path = $self->_param_or_session('path'); - my $dir = $self->app->home->rel_dir('public') . '/export/'; - mkdir $dir unless -e $dir; - $dir .= $path; - mkdir $dir unless -e $dir; + if ( ! $path ) { + warn "no path in param or session"; + return; + } + my $dir = $self->app->home->rel_dir('public') . "/export/$path"; + mkpath $dir unless -e $dir; $dir . '/' . join('.', @_); } @@ -353,7 +380,7 @@ sub _param_scalar { $self->session($name => $scalar); } - warn "# _perm_scalar $name ",dump $scalar; + warn "# _param_scalar $name ",dump $scalar; return $scalar; } @@ -404,7 +431,9 @@ sub _filter_on_data { foreach my $i ( 0 .. $#$items ) { if ( defined $items->[$i]->{$name} ) { - foreach my $v ( @{ $items->[$i]->{$name} } ) { + my $row = $items->[$i]->{$name}; + $row = [ $row ] unless ref $row eq 'ARRAY'; # FIXME probably wrong place + foreach my $v ( @$row ) { if ( defined $filter_hash->{ $v } ) { $filtered_items->{$i}++; } @@ -420,48 +449,6 @@ sub _filter_on_data { warn "filter $name with ", scalar keys %$filtered_items, " items created\n"; } -sub _data_items { - my ( $self, $all ) = @_; - my $data = $self->_loaded( 'data' ); - - return @{ $data->{items} } if $all == 1; - - my $filters = $self->_current_filters; - my $filter_value; - foreach my $f ( keys %$filters ) { - foreach my $n ( @{ $filters->{$f} } ) { - $filter_value->{$f}->{$n} = 1; - } - } - my @items = @{ $data->{items} }; - @items = grep { - my $i = $_; - my $pass = 1; - foreach my $n ( keys %$filter_value ) { - if ( ! exists $i->{$n} ) { - if ( defined $filter_value->{$n}->{_missing} ) { - $pass = 1; - next; - } else { - $pass = 0; - last; - } - } - # and match any of values in element - my $have_values = 0; - foreach my $v ( @{ $i->{$n} } ) { # FIXME not array? - $have_values ||= 1 if defined $filter_value->{$n}->{$v}; - } - if ( ! $have_values ) { - $pass = 0; - last; - } - } - $pass; - } @items if $filter_value; - return @items; -} - sub _current_filters { my $self = shift; @@ -469,7 +456,7 @@ sub _current_filters { $current_filters->{ $_ } = $filters->{ $_ } foreach ( grep { defined $filters->{ $_ } } - @{ $self->_loaded('header') } + @{ $self->_loaded('columns') } ); #warn "# current_filters ",dump($current_filters); return $current_filters; @@ -501,7 +488,7 @@ sub _data_sorted_by { } } map { [ $nr++, exists $_->{$order} ? join('', @{$_->{$order}}) : $missing ] - } @{ $data->{items} } + } grep { ref $_->{$order} eq 'ARRAY' } @{ $data->{items} } ; warn "sorted: $order numeric: $numeric items: ", $#sorted + 1, "\n"; @@ -514,8 +501,12 @@ sub _data_sorted_by { sub items { my $self = shift; + if ( my $show = $self->param('id') ) { + $self->param('show', $show); + warn "show $show\n"; + } + my $path = $self->session('path'); - $self->redirect_to('/data/index') unless defined $loaded->{ $path }; my @columns = $self->_param_array('columns'); $self->redirect_to('/data/columns') unless @columns; @@ -528,6 +519,10 @@ sub items { # fix offset when changing limit $offset = int( $offset / $limit ) * $limit; + if ( ! grep { /^\Q$order\E$/ } @columns ) { + $order = $columns[0]; + $self->session( order => $order ); + } my $sorted = $self->_data_sorted_by( $order ); my @filter_names; @@ -582,17 +577,70 @@ sub items { warn "all_filters $all_filters produced ", $#$filtered + 1, " items\n" if $filtered; - my $sorted_items; my $data = $self->_loaded('data'); + + my $code = $self->_param_scalar('code',''); + $code =~ s{[\r\n]+$}{}s; + + my $commit = $self->param('commit'); + my $test = $self->param('test'); + + my $cols_changed; + + if ( $code && ( $test || $commit ) ) { + # XXX find columns used in code snippet and show them to user + foreach my $column ( $code =~ m/\$row->{(.+?)}/g ) { + if ( $column =~ s/^(['"])// ) { + $column =~ s/$1$//; + } + $cols_changed->{$column}++; + next if grep { /$column/ } @columns; + $cols_changed->{$column}++; + unshift @columns, $column; + if ( $commit ) { + $self->session('columns', [ @columns ]); + $loaded->{$path}->{columns} = [ @columns ]; + __path_modified( $path, 2 ); + } + } + } + + if ( $commit ) { + warn "# commit on ", $#$filtered + 1, " items:\n$code\n"; + foreach ( 0 .. $#$filtered ) { + my $i = $filtered->[$_]; + my $row = $data->{items}->[$i]; + eval $code; + } + if ( my $description = $self->param('code_description') ) { + my $depends = $self->param('code_depends') || die "no code_depends?"; + my $path = $self->app->home->rel_dir('public') . "/code/$depends.$description.pl"; + write_file $path, $code; + warn "code $path ", -s $path, " bytes saved\n"; + } + $code = ''; + } + + my $sorted_items; my $from_end = $sort eq 'd' ? $#$filtered : 0; foreach ( 0 .. $limit ) { my $i = $_ + $offset; last unless defined $filtered->[$i]; $i = $from_end - $i if $from_end; my $id = $filtered->[$i]; - push @$sorted_items, - my $item = $data->{items}->[ $id ]; - $item->{_row_id} ||= $id; + my $row = $data->{items}->[ $id ]; + if ( $code && $test ) { + $row = Storable::dclone $row; + eval $code; + if ( $@ ) { + warn "ERROR evaling\n$code\n$@"; + $self->stash('eval_error', $@) if $@; + } else { + warn "EVAL ",dump($row); + } + } + $row->{_row_id} ||= $id; + push @$sorted_items, $row; } warn "# sorted_items ", $#$sorted_items + 1, " offset $offset limit $limit order $sort"; @@ -606,6 +654,14 @@ sub items { rows => $#$filtered + 1, numeric => { map { $_, $self->_is_numeric($_) } @columns }, filters => $self->_current_filters, + code => $code, + cols_changed => $cols_changed, + code_depends => + $self->param('code_depends') || + join(',', grep { defined $cols_changed->{$_} && $cols_changed->{$_} == 1 } @columns ), + code_description => + $self->param('code_description') || + join(',', grep { defined $cols_changed->{$_} && $cols_changed->{$_} == 2 } @columns ), ); } @@ -725,6 +781,30 @@ sub facet { ); } + +sub __invalidate_path_column { + my ( $path, $name ) = @_; + + if ( defined $loaded->{$path}->{sorted}->{$name} ) { + delete $loaded->{$path}->{sorted}->{$name}; + warn "# invalidate $path sorted $name\n"; + } + + foreach ( grep { m/$name/ } keys %{ $loaded->{$path}->{filtered} } ) { + delete $loaded->{$path}->{filtered}->{$_}; + warn "# invalidate $path filtered $_\n"; + } +} + +sub __path_modified { + my ( $path, $value ) = @_; + $value = 1 unless defined $value; + + $loaded->{$path}->{modified} = $value; + + warn "# __path_modified $path $value\n"; +} + sub edit { my $self = shift; my $new_content = $self->param('new_content'); @@ -777,19 +857,11 @@ sub edit { warn "# change $path $i $old -> $new\n"; $loaded->{$path}->{data}->{items}->[$i]->{$name} = $v; - if ( defined $loaded->{$path}->{sorted}->{$name} ) { - delete $loaded->{$path}->{sorted}->{$name}; - warn "# invalidate $path sorted $name\n"; - } - - foreach ( grep { m/$name/ } keys %{ $loaded->{$path}->{filtered} } ) { - delete $loaded->{$path}->{filtered}->{$_}; - warn "# invalidate $path filtered $_\n"; - } + __invalidate_path_column( $path, $name ); $status = 201; # created - $loaded->{$path}->{modified} = 1; - $self->session( 'modified' => 1 ); + # modified = 2 -- force rebuild of stats + __path_modified( $path, 2 ); $new_content = join("\xB6",@$v); @@ -815,16 +887,30 @@ sub save { my $self = shift; my $path = $self->_param_or_session('path'); my $dump_path = $self->_save( $path ); - $loaded->{$path}->{modified} = 0; - $self->session( 'modified' => 0 ); + __path_modified( $path, 0 ); $self->redirect_to( '/data/items' ); } sub export { my $self = shift; + + if ( my $import = $self->param('import') ) { + + if ( $import =~ m{/filter\.(.+?)\..+} ) { + my $name = $1; + my @vals = map { chomp; $_ } + read_file $self->app->home->rel_dir('public') . "/export/$import"; + $self->_remove_filter( $name ); + $self->_filter_on_data( $name, @vals ); + $self->session( 'offset' => 0 ); + $self->redirect_to('/data/items'); + } else { + warn "UNKNOWN IMPORT $import"; + } + } + $self->render( export => [ - map { s{^.+/public/export/}{}; $_ } glob( $self->_export_path . '*' ) ] ); }