/** * Performs synchronisation of an external file if the previous one has expired. * * This function must be implemented for external repositories supporting * FILE_REFERENCE, it is called for existing aliases when their filesize, * contenthash or timemodified are requested. It is not called for internal * repositories (see {@link repository::has_moodle_files()}), references to * internal files are updated immediately when source is modified. * * Referenced files may optionally keep their content in Moodle filepool (for * thumbnail generation or to be able to serve cached copy). In this * case both contenthash and filesize need to be synchronized. Otherwise repositories * should use contenthash of empty file and correct filesize in bytes. * * Note that this function may be run for EACH file that needs to be synchronised at the * moment. If anything is being downloaded or requested from external sources there * should be a small timeout. The synchronisation is performed to update the size of * the file and/or to update image and re-generated image preview. There is nothing * fatal if syncronisation fails but it is fatal if syncronisation takes too long * and hangs the script generating a page. * * Note: If you wish to call $file->get_filesize(), $file->get_contenthash() or * $file->get_timemodified() make sure that recursion does not happen. * * Called from {@link stored_file::sync_external_file()} * * @uses stored_file::set_missingsource() * @uses stored_file::set_synchronized() * @param stored_file $file * @return bool false when file does not need synchronisation, true if it was synchronised */ public function sync_reference(stored_file $file) { if ($file->get_repository_id() != $this->id) { // This should not really happen because the function can be called from stored_file only. return false; } if ($this->has_moodle_files()) { // References to local files need to be synchronised only once. // Later they will be synchronised automatically when the source is changed. if ($file->get_referencelastsync()) { return false; } $fs = get_file_storage(); $params = file_storage::unpack_reference($file->get_reference(), true); if (!is_array($params) || !($storedfile = $fs->get_file($params['contextid'], $params['component'], $params['filearea'], $params['itemid'], $params['filepath'], $params['filename']))) { $file->set_missingsource(); } else { $file->set_synchronized($storedfile->get_contenthash(), $storedfile->get_filesize()); } return true; } // Backward compatibility (Moodle 2.3-2.5) implementation that calls // methods repository::get_reference_file_lifetime(), repository::sync_individual_file() // and repository::get_file_by_reference(). These methods are removed from the // base repository class but may still be implemented by the child classes. // THIS IS NOT A GOOD EXAMPLE of implementation. For good examples see the overwriting methods. if (!method_exists($this, 'get_file_by_reference')) { // Function get_file_by_reference() is not implemented. No synchronisation. return false; } // Check if the previous sync result is still valid. if (method_exists($this, 'get_reference_file_lifetime')) { $lifetime = $this->get_reference_file_lifetime($file->get_reference()); } else { // Default value that was hardcoded in Moodle 2.3 - 2.5. $lifetime = 60 * 60 * 24; } if (($lastsynced = $file->get_referencelastsync()) && $lastsynced + $lifetime >= time()) { return false; } $cache = cache::make('core', 'repositories'); if (($lastsyncresult = $cache->get('sync:'.$file->get_referencefileid())) !== false) { if ($lastsyncresult === true) { // We are in the process of synchronizing this reference. // Avoid recursion when calling $file->get_filesize() and $file->get_contenthash(). return false; } else { // We have synchronised the same reference inside this request already. // It looks like the object $file was created before the synchronisation and contains old data. if (!empty($lastsyncresult['missing'])) { $file->set_missingsource(); } else { $cache->set('sync:'.$file->get_referencefileid(), true); if ($file->get_contenthash() != $lastsyncresult['contenthash'] || $file->get_filesize() != $lastsyncresult['filesize']) { $file->set_synchronized($lastsyncresult['contenthash'], $lastsyncresult['filesize']); } $cache->set('sync:'.$file->get_referencefileid(), $lastsyncresult); } return true; } } // Weird function sync_individual_file() that was present in API in 2.3 - 2.5, default value was true. if (method_exists($this, 'sync_individual_file') && !$this->sync_individual_file($file)) { return false; } // Set 'true' into the cache to indicate that file is in the process of synchronisation. $cache->set('sync:'.$file->get_referencefileid(), true); // Create object with the structure that repository::get_file_by_reference() expects. $reference = new stdClass(); $reference->id = $file->get_referencefileid(); $reference->reference = $file->get_reference(); $reference->referencehash = sha1($file->get_reference()); $reference->lastsync = $file->get_referencelastsync(); $reference->lifetime = $lifetime; $fileinfo = $this->get_file_by_reference($reference); $contenthash = null; $filesize = null; $fs = get_file_storage(); if (!empty($fileinfo->filesize)) { // filesize returned if (!empty($fileinfo->contenthash) && $fs->content_exists($fileinfo->contenthash)) { // contenthash is specified and valid $contenthash = $fileinfo->contenthash; } else if ($fileinfo->filesize == $file->get_filesize()) { // we don't know the new contenthash but the filesize did not change, // assume the contenthash did not change either $contenthash = $file->get_contenthash(); } else { // we can't save empty contenthash so generate contenthash from empty string list($contenthash, $unused1, $unused2) = $fs->add_string_to_pool(''); } $filesize = $fileinfo->filesize; } else if (!empty($fileinfo->filepath)) { // File path returned list($contenthash, $filesize, $newfile) = $fs->add_file_to_pool($fileinfo->filepath); } else if (!empty($fileinfo->handle) && is_resource($fileinfo->handle)) { // File handle returned $contents = ''; while (!feof($fileinfo->handle)) { $contents .= fread($fileinfo->handle, 8192); } fclose($fileinfo->handle); list($contenthash, $filesize, $newfile) = $fs->add_string_to_pool($contents); } else if (isset($fileinfo->content)) { // File content returned list($contenthash, $filesize, $newfile) = $fs->add_string_to_pool($fileinfo->content); } if (!isset($contenthash) or !isset($filesize)) { $file->set_missingsource(null); $cache->set('sync:'.$file->get_referencefileid(), array('missing' => true)); } else { // update files table $file->set_synchronized($contenthash, $filesize); $cache->set('sync:'.$file->get_referencefileid(), array('contenthash' => $contenthash, 'filesize' => $filesize)); } return true; }
/** * Performs synchronisation of reference to an external file if the previous one has expired. * * @param stored_file $file * @param bool $resetsynchistory whether to reset all history of sync (used by phpunit) * @return bool success */ public static function sync_external_file($file, $resetsynchistory = false) { global $DB; // TODO MDL-25290 static should be replaced with MUC code. static $synchronized = array(); if ($resetsynchistory) { $synchronized = array(); } $fs = get_file_storage(); if (!$file || !$file->get_referencefileid()) { return false; } if (array_key_exists($file->get_id(), $synchronized)) { return $synchronized[$file->get_id()]; } // remember that we already cached in current request to prevent from querying again $synchronized[$file->get_id()] = false; if (!($reference = $DB->get_record('files_reference', array('id' => $file->get_referencefileid())))) { return false; } if (!empty($reference->lastsync) and $reference->lastsync + $reference->lifetime > time()) { $synchronized[$file->get_id()] = true; return true; } if (!($repository = self::get_repository_by_id($reference->repositoryid, SYSCONTEXTID))) { return false; } if (!$repository->sync_individual_file($file)) { return false; } $lifetime = $repository->get_reference_file_lifetime($reference); $fileinfo = $repository->get_file_by_reference($reference); if ($fileinfo === null) { // does not exist any more - set status to missing $file->set_missingsource($lifetime); $synchronized[$file->get_id()] = true; return true; } $contenthash = null; $filesize = null; if (!empty($fileinfo->filesize)) { // filesize returned if (!empty($fileinfo->contenthash) && $fs->content_exists($fileinfo->contenthash)) { // contenthash is specified and valid $contenthash = $fileinfo->contenthash; } else { if ($fileinfo->filesize == $file->get_filesize()) { // we don't know the new contenthash but the filesize did not change, // assume the contenthash did not change either $contenthash = $file->get_contenthash(); } else { // we can't save empty contenthash so generate contenthash from empty string $fs->add_string_to_pool(''); $contenthash = sha1(''); } } $filesize = $fileinfo->filesize; } else { if (!empty($fileinfo->filepath)) { // File path returned list($contenthash, $filesize, $newfile) = $fs->add_file_to_pool($fileinfo->filepath); } else { if (!empty($fileinfo->handle) && is_resource($fileinfo->handle)) { // File handle returned $contents = ''; while (!feof($fileinfo->handle)) { $contents .= fread($handle, 8192); } fclose($fileinfo->handle); list($contenthash, $filesize, $newfile) = $fs->add_string_to_pool($content); } else { if (isset($fileinfo->content)) { // File content returned list($contenthash, $filesize, $newfile) = $fs->add_string_to_pool($fileinfo->content); } } } } if (!isset($contenthash) or !isset($filesize)) { return false; } // update files table $file->set_synchronized($contenthash, $filesize, 0, $lifetime); $synchronized[$file->get_id()] = true; return true; }
/** * Call to request proxy file sync with repository source. * * @param stored_file $file * @return bool success */ public static function sync_external_file(stored_file $file) { global $DB; $fs = get_file_storage(); if (!$reference = $DB->get_record('files_reference', array('id'=>$file->get_referencefileid()))) { return false; } if (!empty($reference->lastsync) and ($reference->lastsync + $reference->lifetime > time())) { return false; } if (!$repository = self::get_repository_by_id($reference->repositoryid, SYSCONTEXTID)) { return false; } if (!$repository->sync_individual_file($file)) { return false; } $fileinfo = $repository->get_file_by_reference($reference); if ($fileinfo === null) { // does not exist any more - set status to missing $sql = "UPDATE {files} SET status = :missing WHERE referencefileid = :referencefileid"; $params = array('referencefileid'=>$reference->id, 'missing'=>666); $DB->execute($sql, $params); //TODO: purge content from pool if we set some other content hash and it is no used any more return true; } else if ($fileinfo === false) { // error return false; } $contenthash = null; $filesize = null; if (!empty($fileinfo->contenthash)) { // contenthash returned, file already in moodle $contenthash = $fileinfo->contenthash; $filesize = $fileinfo->filesize; } else if (!empty($fileinfo->filepath)) { // File path returned list($contenthash, $filesize, $newfile) = $fs->add_file_to_pool($fileinfo->filepath); } else if (!empty($fileinfo->handle) && is_resource($fileinfo->handle)) { // File handle returned $contents = ''; while (!feof($fileinfo->handle)) { $contents .= fread($handle, 8192); } fclose($fileinfo->handle); list($contenthash, $filesize, $newfile) = $fs->add_string_to_pool($content); } else if (isset($fileinfo->content)) { // File content returned list($contenthash, $filesize, $newfile) = $fs->add_string_to_pool($fileinfo->content); } if (!isset($contenthash) or !isset($filesize)) { return false; } $now = time(); // update files table $sql = "UPDATE {files} SET contenthash = :contenthash, filesize = :filesize, referencelastsync = :now, referencelifetime = :lifetime, timemodified = :now2 WHERE referencefileid = :referencefileid AND contenthash <> :contenthash2"; $params = array('contenthash'=>$contenthash, 'filesize'=>$filesize, 'now'=>$now, 'lifetime'=>$reference->lifetime, 'now2'=>$now, 'referencefileid'=>$reference->id, 'contenthash2'=>$contenthash); $DB->execute($sql, $params); $DB->set_field('files_reference', 'lastsync', $now, array('id'=>$reference->id)); return true; }
/** * Replaces the fields that might have changed when file was overriden in filepicker: * reference, contenthash, filesize, userid * * Note that field 'source' must be updated separately because * it has different format for draft and non-draft areas and * this function will usually be used to replace non-draft area * file with draft area file. * * @param stored_file $newfile * @throws coding_exception */ public function replace_file_with(stored_file $newfile) { if ($newfile->get_referencefileid() && $this->fs->get_references_count_by_storedfile($this)) { // The new file is a reference. // The current file has other local files referencing to it. // Double reference is not allowed. throw new moodle_exception('errordoublereference', 'repository'); } $filerecord = new stdClass(); $contenthash = $newfile->get_contenthash(); if ($this->fs->content_exists($contenthash)) { $filerecord->contenthash = $contenthash; } else { throw new file_exception('storedfileproblem', 'Invalid contenthash, content must be already in filepool', $contenthash); } $filerecord->filesize = $newfile->get_filesize(); $filerecord->referencefileid = $newfile->get_referencefileid(); $filerecord->userid = $newfile->get_userid(); $this->update($filerecord); }
/** * Call to request proxy file sync with repository source. * * @param stored_file $file * @param bool $resetsynchistory whether to reset all history of sync (used by phpunit) * @return bool success */ public static function sync_external_file($file, $resetsynchistory = false) { global $DB; // TODO MDL-25290 static should be replaced with MUC code. static $synchronized = array(); if ($resetsynchistory) { $synchronized = array(); } $fs = get_file_storage(); if (!$file || !$file->get_referencefileid()) { return false; } if (array_key_exists($file->get_id(), $synchronized)) { return $synchronized[$file->get_id()]; } // remember that we already cached in current request to prevent from querying again $synchronized[$file->get_id()] = false; if (!$reference = $DB->get_record('files_reference', array('id'=>$file->get_referencefileid()))) { return false; } if (!empty($reference->lastsync) and ($reference->lastsync + $reference->lifetime > time())) { $synchronized[$file->get_id()] = true; return true; } if (!$repository = self::get_repository_by_id($reference->repositoryid, SYSCONTEXTID)) { return false; } if (!$repository->sync_individual_file($file)) { return false; } $fileinfo = $repository->get_file_by_reference($reference); if ($fileinfo === null) { // does not exist any more - set status to missing $file->set_missingsource(); //TODO: purge content from pool if we set some other content hash and it is no used any more $synchronized[$file->get_id()] = true; return true; } $contenthash = null; $filesize = null; if (!empty($fileinfo->contenthash)) { // contenthash returned, file already in moodle $contenthash = $fileinfo->contenthash; $filesize = $fileinfo->filesize; } else if (!empty($fileinfo->filepath)) { // File path returned list($contenthash, $filesize, $newfile) = $fs->add_file_to_pool($fileinfo->filepath); } else if (!empty($fileinfo->handle) && is_resource($fileinfo->handle)) { // File handle returned $contents = ''; while (!feof($fileinfo->handle)) { $contents .= fread($handle, 8192); } fclose($fileinfo->handle); list($contenthash, $filesize, $newfile) = $fs->add_string_to_pool($content); } else if (isset($fileinfo->content)) { // File content returned list($contenthash, $filesize, $newfile) = $fs->add_string_to_pool($fileinfo->content); } if (!isset($contenthash) or !isset($filesize)) { return false; } // update files table $file->set_synchronized($contenthash, $filesize); $synchronized[$file->get_id()] = true; return true; }