Skip to content

Commit

Permalink
MDL-37641 files: Improve file name suggestion and performance
Browse files Browse the repository at this point in the history
  • Loading branch information
Frederic Massart committed Feb 20, 2013
1 parent 1dd6835 commit d7d6939
Show file tree
Hide file tree
Showing 4 changed files with 111 additions and 24 deletions.
83 changes: 83 additions & 0 deletions lib/filestorage/file_storage.php
Original file line number Diff line number Diff line change
Expand Up @@ -180,6 +180,89 @@ public function get_file_preview(stored_file $file, $mode) {
return $preview;
}

/**
* Return an available file name.
*
* This will return the next available file name in the area, adding/incrementing a suffix
* of the file, ie: file.txt > file (1).txt > file (2).txt > etc...
*
* If the file name passed is available without modification, it is returned as is.
*
* @param int $contextid context ID.
* @param string $component component.
* @param string $filearea file area.
* @param int $itemid area item ID.
* @param string $filepath the file path.
* @param string $filename the file name.
* @return string available file name.
* @throws coding_exception if the file name is invalid.
* @since 2.5
*/
public function get_unused_filename($contextid, $component, $filearea, $itemid, $filepath, $filename) {
global $DB;

// Do not accept '.' or an empty file name (zero is acceptable).
if ($filename == '.' || (empty($filename) && !is_numeric($filename))) {
throw new coding_exception('Invalid file name passed', $filename);
}

// The file does not exist, we return the same file name.
if (!$this->file_exists($contextid, $component, $filearea, $itemid, $filepath, $filename)) {
return $filename;
}

// Trying to locate a file name using the used pattern. We remove the used pattern from the file name first.
$pathinfo = pathinfo($filename);
$basename = $pathinfo['filename'];
$matches = array();
if (preg_match('~^(.+) \(([0-9]+)\)$~', $basename, $matches)) {
$basename = $matches[1];
}

$filenamelike = $DB->sql_like_escape($basename) . ' (%)';
if (isset($pathinfo['extension'])) {
$filenamelike .= '.' . $DB->sql_like_escape($pathinfo['extension']);
}

$filenamelikesql = $DB->sql_like('f.filename', ':filenamelike');
$filenamelen = $DB->sql_length('f.filename');
$sql = "SELECT filename
FROM {files} f
WHERE
f.contextid = :contextid AND
f.component = :component AND
f.filearea = :filearea AND
f.itemid = :itemid AND
f.filepath = :filepath AND
$filenamelikesql
ORDER BY
$filenamelen DESC,
f.filename DESC";
$params = array('contextid' => $contextid, 'component' => $component, 'filearea' => $filearea, 'itemid' => $itemid,
'filepath' => $filepath, 'filenamelike' => $filenamelike);
$results = $DB->get_fieldset_sql($sql, $params, IGNORE_MULTIPLE);

// Loop over the results to make sure we are working on a valid file name. Because 'file (1).txt' and 'file (copy).txt'
// would both be returned, but only the one only containing digits should be used.
$number = 1;
foreach ($results as $result) {
$resultbasename = pathinfo($result, PATHINFO_FILENAME);
$matches = array();
if (preg_match('~^(.+) \(([0-9]+)\)$~', $resultbasename, $matches)) {
$number = $matches[2] + 1;
break;
}
}

// Constructing the new filename.
$newfilename = $basename . ' (' . $number . ')';
if (isset($pathinfo['extension'])) {
$newfilename .= '.' . $pathinfo['extension'];
}

return $newfilename;
}

/**
* Generates a preview image for the stored file
*
Expand Down
10 changes: 5 additions & 5 deletions lib/form/dndupload.js
Original file line number Diff line number Diff line change
Expand Up @@ -796,20 +796,20 @@ M.form_dndupload.init = function(Y, options) {
extension = filename.substr(dotpos, filename.length);
}

// Look to see if the name already has _NN at the end of it.
// Look to see if the name already has (NN) at the end of it.
var number = 0;
var hasnumber = basename.match(/^(.*)_(\d+)$/);
if (hasnumber != null) {
var hasnumber = basename.match(/^(.*) \((\d+)\)$/);
if (hasnumber !== null) {
// Note the current number & remove it from the basename.
number = parseInt(hasnumber[2]);
number = parseInt(hasnumber[2], 10);
basename = hasnumber[1];
}

// Loop through increating numbers until a unique name is found.
var newname;
do {
number++;
newname = basename + '_' + number + extension;
newname = basename + ' (' + number + ')' + extension;
} while (this.has_name_clash(newname));

return newname;
Expand Down
37 changes: 18 additions & 19 deletions repository/lib.php
Original file line number Diff line number Diff line change
Expand Up @@ -636,23 +636,19 @@ public final function check_capability() {
}

/**
* Check if file already exists in draft area
* Check if file already exists in draft area.
*
* @static
* @param int $itemid
* @param string $filepath
* @param string $filename
* @param int $itemid of the draft area.
* @param string $filepath path to the file.
* @param string $filename file name.
* @return bool
*/
public static function draftfile_exists($itemid, $filepath, $filename) {
global $USER;
$fs = get_file_storage();
$usercontext = context_user::instance($USER->id);
if ($fs->get_file($usercontext->id, 'user', 'draft', $itemid, $filepath, $filename)) {
return true;
} else {
return false;
}
return $fs->file_exists($usercontext->id, 'user', 'draft', $itemid, $filepath, $filename);
}

/**
Expand Down Expand Up @@ -769,31 +765,34 @@ public function copy_to_area($source, $filerecord, $maxbytes = -1, $areamaxbytes
}

/**
* Get unused filename by appending suffix
* Get an unused filename from the current draft area.
*
* Will check if the file ends with ([0-9]) and increase the number.
*
* @static
* @param int $itemid
* @param string $filepath
* @param string $filename
* @return string
* @param int $itemid draft item ID.
* @param string $filepath path to the file.
* @param string $filename name of the file.
* @return string an unused file name.
*/
public static function get_unused_filename($itemid, $filepath, $filename) {
global $USER;
$contextid = context_user::instance($USER->id)->id;
$fs = get_file_storage();
while (repository::draftfile_exists($itemid, $filepath, $filename)) {
$filename = repository::append_suffix($filename);
}
return $filename;
return $fs->get_unused_filename($contextid, 'user', 'draft', $itemid, $filepath, $filename);
}

/**
* Append a suffix to filename
* Append a suffix to filename.
*
* @static
* @param string $filename
* @return string
* @deprecated since 2.5
*/
public static function append_suffix($filename) {
debugging('The function repository::append_suffix() has been deprecated. Use repository::get_unused_filename() instead.',
DEBUG_DEVELOPER);
$pathinfo = pathinfo($filename);
if (empty($pathinfo['extension'])) {
return $filename . RENAME_SUFFIX;
Expand Down
5 changes: 5 additions & 0 deletions repository/upgrade.txt
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,11 @@ information provided here is intended especially for developers. Full
details of the repository API are available on Moodle docs:
http://docs.moodle.org/dev/Repository_API

=== 2.5 ===

* repository::append_suffix() has been deprecated, use repository::get_unused_filename() if you need
to get a file name which has not yet been used in the draft area.

=== 2.4 ===

* copy_to_area() can receive a new parameter called $areamaxbytes which controls the maximum
Expand Down

0 comments on commit d7d6939

Please sign in to comment.