############################################################################### # Codestriker: Copyright (c) 2001, 2002 David Sitsky. All rights reserved. # sits@users.sourceforge.net # # This program is free software; you can redistribute it and modify it under # the terms of the GPL. # Action object for handling the viewing of a file. package Codestriker::Action::ViewTopicFile; use strict; use Codestriker::Model::File; use Codestriker::Model::Comment; use Codestriker::Http::Render; use Codestriker::Repository::RepositoryFactory; # If the input is valid, display the topic. sub process($$$) { my ($type, $http_input, $http_response) = @_; # Retrieve the parameters for this action. my $query = $http_response->get_query(); my $topicid = $http_input->get('topic'); my $mode = $http_input->get('mode'); my $tabwidth = $http_input->get('tabwidth'); my $email = $http_input->get('email'); my $fn = $http_input->get('fn'); my $new = $http_input->get('new'); my $parallel = $http_input->get('parallel'); my $brmode = $http_input->get('brmode'); my $fview = $http_input->get('fview'); # Check if this action is allowed. if (scalar(@Codestriker::valid_repositories) == 0) { $http_response->error("This function has been disabled"); } # Retrieve the appropriate topic details. my $topic = Codestriker::Model::Topic->new($topicid); # Retrieve the corresponding repository object. my $repository = Codestriker::Repository::RepositoryFactory->get($topic->{repository}); # Retrieve the deltas corresponding to this file. my @deltas = Codestriker::Model::Delta->get_deltas($topicid, $fn); # We need to untaint the filename and revision values, as they will # potentially be used to launch an external program. $deltas[0]->{filename} =~ /^(.*)$/o; my $filename = $1; $deltas[0]->{revision} =~ /^(.*)$/o; my $revision = $1; # Retrieve the comment details for this topic. my @comments = $topic->read_comments(); # Load the appropriate original form of this file into memory. my ($filedata_max_line_length, @filedata); if (!_read_repository_file($filename, $revision, $tabwidth, $repository, \@filedata, \$filedata_max_line_length)) { # # 13-Sep-07 SJK: Provide some more info in this message, suggesting the likely root cause. # #$http_response->error("Couldn't get repository data for $filename " . # "$revision: $!"); $http_response->error("Couldn't get repository data for $filename " . "$revision: $!. Check repository $repository->{dynamic_view_name} can navigate to this file."); } # This could be done more efficiently, but for now, read through the # file, and determine the longest line length for the resulting # data that is to be viewed. Note it is not 100% accurate, but it will # do for now, to reduce the resulting page size. my $max_line_length = $filedata_max_line_length; for (my $d = 0; $d <= $#deltas; $d++) { my @difflines = split /\n/, $deltas[$d]->{text}; for (my $i = 0; $i <= $#difflines; $i++) { my $line = $difflines[$i]; if ($line =~ /^\s(.*)$/o || $line =~ /^\+(.*)$/o || $line =~ /^\-(.*)$/o) { my $line_length = length($1); if ($line_length > $max_line_length) { $max_line_length = $line_length; } } } } # Output the new file, with the deltas applied. my $title; if ($parallel) { $title = "View File: Parallel view of $filename v$revision"; } else { $title = $new ? "View File: New $filename" : "View File: $filename v$revision"; } $http_response->generate_header(topic=>$topic, comments=>\@comments, topic_title=>$title, mode=>$mode, tabwidth=>$tabwidth, fview=>$fview, repository=>$Codestriker::repository_name_map->{$topic->{repository}}, reload=>0, cache=>1); # Render the HTML header. my $vars = {}; $vars->{'closehead'} = 1; my $header = Codestriker::Http::Template->new("header"); $header->process($vars); my $max_digit_width = length($#filedata); # Create a new render object to perform the line rendering. my @toc_filenames = (); my @toc_revisions = (); my @toc_binaries = (); my $url_builder = Codestriker::Http::UrlBuilder->new($query); my $render = Codestriker::Http::Render->new($query, $url_builder, $parallel, $max_digit_width, $topicid, $mode, \@comments, $tabwidth, $repository, \@toc_filenames, \@toc_revisions, \@toc_binaries, undef, $max_line_length, $brmode, $fview); # Prepare the output. if ($parallel) { $render->print_coloured_table(); } else { print "
\n";
    }

    # Read through all the deltas, and apply them to the original form of the
    # file.
    my $delta = undef;
    for (my $delta_index = 0; $delta_index <= $#deltas; $delta_index++) {
	$delta = $deltas[$delta_index];

	# Output those lines leading up to the start of the next delta.
	# Build up a delta with no changes, and render it.
	my $delta_text = "";
	my $next_delta_linenumber = $delta->{old_linenumber};
	for (my $i = $render->{old_linenumber};
	     $i < $next_delta_linenumber; $i++) {
	    $delta_text .= " $filedata[$i]\n";
	}
	$render->delta_text($filename, $fn, $revision,
			    $render->{old_linenumber},
			    $render->{new_linenumber},
			    $delta_text, 0, $new, 0);
			    
	# Render the actual change delta.
	$render->delta_text($filename, $fn, $revision,
			    $delta->{old_linenumber},
			    $delta->{new_linenumber}, $delta->{text}, 1,
			    $new, 1);
    }

    # Render the tail part of the file, again by building up a delta.
    my $delta_text = "";
    for (my $i = $render->{old_linenumber}; $i <= $#filedata; $i++) {
	$delta_text .= " $filedata[$i]\n";
    }
    $render->delta_text($filename, $fn, $revision, $render->{old_linenumber},
			$render->{new_linenumber}, $delta_text, 0, $new, 0);
    
    # Close off the rendering.    
    if ($parallel) {
	print $query->end_table();
    }
    else {
	print "
\n"; } # Render the HTML trailer. my $trailer = Codestriker::Http::Template->new("trailer"); $trailer->process(); print $query->end_html(); $http_response->generate_footer(); # Fire the topic listener to indicate that the user has viewed the topic. Codestriker::TopicListeners::Manager::topic_viewed($email, $topic); } # Read the specified repository file and revision into memory. Return true if # successful, false otherwise. sub _read_repository_file ($$$$$$) { my ($filename, $revision, $tabwidth, $repository, $data_array_ref, $maxline_length_ref) = @_; # Read the file data. $repository->retrieve($filename, $revision, $data_array_ref); # # 13-Sep-07 SJK: Return false if no data read from repository # if ( $#$data_array_ref <= 0 ) { return 0; } # Determine the maximum line length, and replace tabs with spaces. $$maxline_length_ref = 0; for (my $i = 1; $i <= $#$data_array_ref; $i++) { $$data_array_ref[$i] = Codestriker::Http::Render::tabadjust($tabwidth, $$data_array_ref[$i], 0); my $line_length = length($$data_array_ref[$i]); if ($line_length > $$maxline_length_ref) { $$maxline_length_ref = $line_length; } } return 1; } 1;