From 73d47592aa0632f8aa035807e896cb6cbe8aee45 Mon Sep 17 00:00:00 2001 From: hyung-hwan Date: Tue, 14 Apr 2015 04:11:27 +0000 Subject: [PATCH] supported directory download --- codepot/src/codepot/controllers/code.php | 82 +++++++++++-------- .../src/codepot/helpers/codepot_helper.php | 76 +++++++++++++++++ .../src/codepot/models/subversionmodel.php | 79 +++++++++++++++--- codepot/src/codepot/views/code_folder.php | 7 +- 4 files changed, 197 insertions(+), 47 deletions(-) diff --git a/codepot/src/codepot/controllers/code.php b/codepot/src/codepot/controllers/code.php index 9b30a5f1..6c94fcc6 100644 --- a/codepot/src/codepot/controllers/code.php +++ b/codepot/src/codepot/controllers/code.php @@ -223,7 +223,7 @@ class Code extends Controller { $this->load->model ('ProjectModel', 'projects'); $this->load->model ('SubversionModel', 'subversion'); - + $login = $this->login->getUser (); if (CODEPOT_SIGNIN_COMPULSORY && $login['id'] == '') redirect ("main/signin/" . $this->converter->AsciiTohex(current_url())); @@ -665,7 +665,7 @@ class Code extends Controller if ($file === FALSE) { $data['project'] = $project; - $data['message'] = 'Failed to get file'; + $data['message'] = "Failed to get a file - $path"; $this->load->view ($this->VIEW_ERROR, $data); } else if ($file['type'] == 'file') @@ -682,33 +682,49 @@ class Code extends Controller } else { - $data['project'] = $project; - $data['headpath'] = $path; - $data['file'] = $file; + $forced_name = $projectid . $file['fullpath']; + $forced_name = str_replace ('/', '-', $forced_name); + //$tag = $this->subversion->getRevProp ( + // $projectid, $file['created_rev'], CODEPOT_SVN_TAG_PROPERTY); + //if ($tag === FALSE) $tag = ''; + //if (!empty($tag)) + //{ + // $forced_name = $forced_name . '-' . $tag; + //} + //else + //{ + $forced_name = $forced_name . '-r' . $file['created_rev']; + //} - $data['revision'] = $rev; - $data['prev_revision'] = - $this->subversion->getPrevRev ($projectid, $path, $rev); - $data['next_revision'] = - $this->subversion->getNextRev ($projectid, $path, $rev); - - $data['readme_text'] = ''; - $data['readme_file'] = ''; - foreach (explode(',', CODEPOT_CODE_FOLDER_README) as $rf) + $filename = $this->subversion->zipSubdir ($projectid, $path, $rev, $forced_name); + if ($filename === FALSE) { - $rf = trim($rf); - if (strlen($rf) > 0) - { - $readme = $this->subversion->getFile ($projectid, $path . '/' . $rf, $rev); - if ($readme !== FALSE) - { - $data['readme_text'] = $readme['content']; - $data['readme_file'] = $rf; - break; - } - } + $data['project'] = $project; + $data['message'] = "Failed to zip a directory for $path"; + $this->load->view ($this->VIEW_ERROR, $data); + } + else + { + $dir_name = $filename . '.d'; + $zip_name = $filename . '.zip'; + + $forced_zip_name = $forced_name . '.zip'; + + header ('Content-Description: File Transfer'); + header ('Content-Type: application/zip'); + header ('Content-Disposition: attachment; filename='. $forced_zip_name); + header ('Content-Transfer-Encoding: binary'); + header ('Content-Length: ' . filesize($zip_name)); + flush (); + + @readfile ($zip_name); + // meaningless to show the error page after headers + // have been sent event if readfile fails. + + codepot_delete_files ($dir_name, TRUE); + @unlink ($zip_name); + @unlink ($filename); } - $this->load->view ($this->VIEW_FOLDER, $data); } } } @@ -737,7 +753,7 @@ class Code extends Controller if ($file === FALSE) { $data['project'] = $project; - $data['message'] = "Failed to get file - %path"; + $data['message'] = "Failed to get file - $path"; $this->load->view ($this->VIEW_ERROR, $data); } @@ -982,8 +998,7 @@ class Code extends Controller $stats[$date] = 1; } } - - + ksort ($stats); $stats_count = count($stats); $idx = 1; @@ -994,24 +1009,23 @@ class Code extends Controller $min_year = substr($k, 0, 4); $min_month = substr($k, 5, 2); } - + if ($idx == $stats_count) { $max_year = substr($k, 0, 4); $max_month = substr($k, 5, 2); } - $idx++; $total_commits += $v; - } - + } + $total_months = 0; for ($year = $min_year; $year <= $max_year; $year++) { $month = ($year == $min_year)? $min_month: 1; $month_end = ($year == $max_year)? $max_month: 12; - + while ($month <= $month_end) { $date = sprintf ("%04d-%02d", $year, $month); diff --git a/codepot/src/codepot/helpers/codepot_helper.php b/codepot/src/codepot/helpers/codepot_helper.php index f1feca3c..1add5b24 100644 --- a/codepot/src/codepot/helpers/codepot_helper.php +++ b/codepot/src/codepot/helpers/codepot_helper.php @@ -143,6 +143,82 @@ if ( !function_exists ('codepot_delete_files')) } } +if ( !function_exists ('codepot_zip_dir')) +{ + // $output_file: zip file to create + // $path: directory to zip recursively + // $local_path: the leading $path part is translated to $local_path + // $exclude: file names to exclude. string or array of strings + function codepot_zip_dir ($output_file, $path, $local_path = NULL, $exclude = NULL) + { + $stack = array (); + + if (!is_dir($path)) return FALSE; + + array_push ($stack, $path); + $prefix = strlen($path); + + $zip = new ZipArchive(); + if (@$zip->open ($output_file, ZipArchive::OVERWRITE) === FALSE) return FALSE; + + while (!empty($stack)) + { + $dir = array_pop($stack); + + $d = @opendir ($dir); + if ($d === FALSE) continue; + + $new_path = empty($local_path)? $dir: substr_replace($dir, $local_path, 0, $prefix); + if (@$zip->addEmptyDir($new_path) == FALSE) + { + @closedir ($dir); + $zip->close (); + return FALSE; + } + + //printf (">> [%s] [%s]\n", $dir, $new_path); + while (($f = @readdir($d)) !== FALSE) + { + if ($f == '.' || $f == '..') continue; + if (!empty($exclude)) + { + $found = FALSE; + if (is_array($exclude)) + { + foreach ($exclude as $ex) + { + if (fnmatch ($ex, $f, FNM_PERIOD | FNM_PATHNAME)) + { + $found = TRUE; + break; + } + } + if ($found) continue; + } + else if (fnmatch($exclude, $f, FNM_PERIOD | FNM_PATHNAME)) continue; + } + + $full_path = $dir . DIRECTORY_SEPARATOR . $f; + if (is_dir($full_path)) + { + array_push ($stack, $full_path); + } + else + { + $new_path = empty($local_path)? $dir: substr_replace($full_path, $local_path, 0, $prefix); + @$zip->addFile ($full_path, $new_path); + //printf ("[%s] [%s]\n", $full_path, $new_path); + } + } + + @closedir ($dir); + } + + $zip->close (); + return TRUE; + } +} + if ( !function_exists ('codepot_find_longest_matching_sequence')) { function codepot_find_longest_matching_sequence ($old, $old_start, $old_len, $new, $new_start, $new_len) diff --git a/codepot/src/codepot/models/subversionmodel.php b/codepot/src/codepot/models/subversionmodel.php index 04c8bb10..fa88254e 100644 --- a/codepot/src/codepot/models/subversionmodel.php +++ b/codepot/src/codepot/models/subversionmodel.php @@ -921,25 +921,25 @@ class SubversionModel extends Model $cloc = @popen ($cloc_cmd, 'r'); if ($cloc === FALSE) { - codepot_delete_files ($actual_tfname, TRUE); - @unlink ($tfname); - return FALSE; + codepot_delete_files ($actual_tfname, TRUE); + @unlink ($tfname); + return FALSE; } $line_count = 0; $cloc_data = array (); while (!feof($cloc)) { - $line = @fgets ($cloc); - if ($line === FALSE) break; + $line = @fgets ($cloc); + if ($line === FALSE) break; - $line_count++; - $line = trim($line); - if ($line_count >= 3) - { - $counter = explode (':', $line); - $cloc_data[$counter[1]] = array ($counter[0], $counter[2], $counter[3], $counter[4]); - } + $line_count++; + $line = trim($line); + if ($line_count >= 3) + { + $counter = explode (':', $line); + $cloc_data[$counter[1]] = array ($counter[0], $counter[2], $counter[3], $counter[4]); + } } @pclose ($cloc); @@ -999,6 +999,61 @@ class SubversionModel extends Model return $cloc_data; } + + function zipSubdir ($projectid, $path, $rev, $topdir) + { + $orgurl = 'file://'.$this->_canonical_path(CODEPOT_SVNREPO_DIR."/{$projectid}/{$path}"); + + $workurl = ($path == '')? $orgurl: "{$orgurl}@"; // trailing @ for collision prevention + $info = @svn_info ($workurl, FALSE, $rev); + if ($info === FALSE || count($info) != 1) + { + // If a URL is at the root of repository and the url type is file:// + // (e.g. file:///svnrepo/codepot/ where /svnrepo/codepot is a project root), + // some versions of libsvn end up with an assertion failure like + // ... libvvn_subr/path.c:114: svn_path_join: Assertion `svn_path_is_canonical(base, pool)' failed. + // + // Since the root directory is guaranteed to exist at the head revision, + // the information can be acquired without using a peg revision. + // In this case, a normal operational revision is used to work around + // the assertion failure. Other functions that has to deal with + // the root directory should implement this check to work around it. + // + if ($rev == SVN_REVISION_HEAD || $path == '') return FALSE; + + // rebuild the URL with a peg revision and retry it. + $workurl = "{$orgurl}@{$rev}"; + $info = @svn_info ($workurl, FALSE, $rev); + if ($info === FALSE || count($info) != 1) return FALSE; + } + + // pass __FILE__ as the first argument so that tempnam creates a name + // in the system directory. __FILE__ can never be a valid directory. + $tfname = @tempnam(__FILE__, 'codepot-fetch-folder-'); + if ($tfname === FALSE) return FALSE; + + $actual_tfname = $tfname . '.d'; + codepot_delete_files ($actual_tfname, TRUE); // delete the directory in case it exists + + if (@svn_checkout ($workurl, $actual_tfname, $rev, 0) === FALSE) + { + codepot_delete_files ($actual_tfname, TRUE); + @unlink ($tfname); + return FALSE; + } + + //exec ("zip {$tfname}.zip -r {$actual_tfname}"); + if (codepot_zip_dir ("{$tfname}.zip", $actual_tfname, $topdir, array('.svn')) === FALSE) + { + codepot_delete_files ($actual_tfname, TRUE); + @unlink ($tfname); + @unlink ("{$tfname}.zip"); // delete potentially residual zip file + return FALSE; + } + + //codepot_delete_files ($actual_tfname, TRUE); // delete the directory in case it exists + return $tfname; + } } ?> diff --git a/codepot/src/codepot/views/code_folder.php b/codepot/src/codepot/views/code_folder.php index a4a3b76d..1ef3a242 100644 --- a/codepot/src/codepot/views/code_folder.php +++ b/codepot/src/codepot/views/code_folder.php @@ -282,7 +282,7 @@ $this->load->view (
- + id}/", 'id="code_folder_search_form"')?> @@ -350,6 +350,11 @@ $this->load->view ( else print anchor ("code/history/{$project->id}/{$xpar}", $this->lang->line('History')); + print ' | '; + print anchor ( + "code/fetch/{$project->id}/${xpar}{$revreq}", + $this->lang->line('Download')); + print '
'; usort ($file['content'], 'comp_files');