file_save fails for remote streams on filesize()

aaron - November 9, 2009 - 22:16
Project:Drupal
Version:7.x-dev
Component:file system
Category:bug report
Priority:critical
Assigned:aaron
Status:closed
Issue tags:File API, stream wrappers
Description

Trying to create a stream wrapper class, at Media: YouTube. Making progress, except that when file_save() is called for the newly created file object, we receive a warning on

<?php
$file
->filesize = filesize($file->uri);
?>

Warning: filesize(): stat failed for youtube://v/-jubiv7QUco in file_save() (line 482 of /var/www/d7-media/includes/file.inc).

It looks like filesize() doesn't work with streams. This is obviously inconsistent with other stream wrapper file functions, as it apparently can't be overridden, and doesn't simply return FALSE (as does file_exists() or is_readable()).

I believe we should move the $file->filesize assignment outside this function, perhaps even to be handled by the stream wrappers somehow. Otherwise, the File API will fail for handling remote streams.

#1

pwolanin - November 9, 2009 - 22:22

Do we implement http://www.php.net/manual/en/streamwrapper.stream-stat.php? It seems so - maybe make sure you are returning some size in the array returned by stream_stat() from your implementation

#2

pwolanin - November 9, 2009 - 22:26

If that doesn't work, perhaps we should call stat() instead of filesize()

#3

aaron - November 10, 2009 - 11:56

No, I've implemented stream_stat(), but it still fails. Let me try using stat() instead of filesize().

#4

aaron - November 10, 2009 - 12:02

Hmm.. That fails too. Perhaps I'm implementing stream_stat incorrectly?

<?php
 
/**
   * Support for fstat().
   *
   * @return
   *   An array with file status, or FALSE in case of an error - see fstat()
   *   for a description of this array.
   */
 
public function stream_stat() {
    return array(
     
'dev' => 0,
     
'ino' => 0,
     
'mode' => 0,
     
'nlink' => 0,
     
'uid' => 0,
     
'gid' => 0,
     
'rdev' => 0,
     
'size' => 1000,
     
'atime' => 0,
     
'mtime' => 0,
     
'ctime' => 0,
     
'blksize' => 0,
     
'blocks' => 0,
    );
  }
?>

(I just have a fake value for size currently, obviously.)

#5

aaron - November 10, 2009 - 12:03

And my stat() implementation, for the record:

<?php
/**
* Save a file object to the database.
*
* If the $file->fid is not set a new record will be added. Re-saving an
* existing file will not change its status.
*
* @param $file
*   A file object returned by file_load().
* @return
*   The updated file object.
*
* @see hook_file_insert()
* @see hook_file_update()
*/
function file_save(stdClass $file) {
 
$file->timestamp = REQUEST_TIME;
 
$stat = stat($file->uri);
 
$file->filesize = $stat['size'];

  if (empty(
$file->fid)) {
   
drupal_write_record('file', $file);
   
// Inform modules about the newly added file.
   
module_invoke_all('file_insert', $file);
  }
  else {
   
drupal_write_record('file', $file, 'fid');
   
// Inform modules that the file has been updated.
   
module_invoke_all('file_update', $file);
  }

  return
$file;
}
?>

#6

pwolanin - November 10, 2009 - 15:54

from php.net re: stream_stat()

This method is called in response to fstat().

NOT stat().

see: http://www.php.net/manual/en/function.fstat.php

#7

aaron - November 10, 2009 - 16:24

@pwolanin: Unfortunately, that page also reads

Note: This function will not work on remote files as the file to be examined must be accessible via the server's filesystem.

I tried it anyway, and now get:

Warning: fstat(): supplied argument is not a valid stream resource in file_save() (line 482 of /var/www/d7-media/includes/file.inc).

What if we call the stream wrapper's ->stream_stat() instead?

#8

aaron - November 10, 2009 - 16:44

oh wait, just saw that's expecting a handler. not sure if i can wrangle that, but i'll work on it this afternoon...

#9

aaron - November 10, 2009 - 18:07
Assigned to:Anonymous» aaron
Status:active» needs review

OK, this takes care of the issue. We grab the stream and call ->stream_stat directly. Not sure why PHP isn't doing that natively; probably a PHP bug. Oh well.

AttachmentSizeStatusTest resultOperations
file.remote-file_save.628094.9.patch906 bytesIdleFailed: 14686 passes, 6 fails, 64 exceptionsView details | Re-test

#10

System Message - November 10, 2009 - 18:25
Status:needs review» needs work

The last submitted patch failed testing.

#11

pwolanin - November 10, 2009 - 18:26
Status:needs work» needs review

Odd indeed - how was this working even for local files like public://?

Quick look at the patch - looks reasonable.

#12

aaron - November 10, 2009 - 18:44
Status:needs review» needs work

local files worked because it was calling filesave() directly on the filename, rather than the stream.

this patch fails now for normal uploads:

Warning: fstat(): supplied argument is not a valid stream resource in DrupalLocalStreamWrapper->stream_stat() (line 392 of /var/www/d7-media/includes/stream_wrappers.inc).

. unfortunately, i guess we'll need to switch on the stream type (drupallocal vs others).

#13

aaron - November 10, 2009 - 19:17
Status:needs work» needs review

This one works fine with local and remote streams now.

AttachmentSizeStatusTest resultOperations
file.remote-file_save.628094.13.patch1.02 KBIdlePassed: 14708 passes, 0 fails, 0 exceptionsView details | Re-test

#14

aaron - November 10, 2009 - 19:20

this patch works by opening the stream before reading stream_stat, then closing it. i believe that filesave works the same way for local files, so this shouldn't add anything substantially different functionally. i'm still baffled by stat() not working on the remote stream; maybe dopry can clue us someday into why that's not working as i expected.

#15

c960657 - November 10, 2009 - 21:17

I have been using filesize() together with stream wrappers without problems, e.g. the stream wrapper included in the Services_Amazon_S3 PEAR package. I even have tests for it. The stream_stat() implementation is here.

Your stream_stat() should return an array similar to that returned by stat(). On my system, that array has both numerical and string keys:

<?php
array(26) {
  [
0]=>
 
int(769)
  [
1]=>
 
int(2606155)
  [
2]=>
 
int(33188)
  [
3]=>
 
int(1)
  [
4]=>
 
int(500)
  [
5]=>
 
int(1001)
  [
6]=>
 
int(0)
  [
7]=>
 
int(605)
  [
8]=>
 
int(1257696466)
  [
9]=>
 
int(1257696466)
  [
10]=>
 
int(1257696466)
  [
11]=>
 
int(4096)
  [
12]=>
 
int(8)
  [
"dev"]=>
 
int(769)
  [
"ino"]=>
 
int(2606155)
  [
"mode"]=>
 
int(33188)
  [
"nlink"]=>
 
int(1)
  [
"uid"]=>
 
int(500)
  [
"gid"]=>
 
int(1001)
  [
"rdev"]=>
 
int(0)
  [
"size"]=>
 
int(605)
  [
"atime"]=>
 
int(1257696466)
  [
"mtime"]=>
 
int(1257696466)
  [
"ctime"]=>
 
int(1257696466)
  [
"blksize"]=>
 
int(4096)
  [
"blocks"]=>
 
int(8)
}
?>

Perhaps this is what's causing you trouble?

#16

aaron - November 10, 2009 - 21:50

huh. well, i used that nearly verbatim, and it still didn't work. note that http://us3.php.net/manual/en/function.filesize.php writes:

As of PHP 5.0.0, this function can also be used with some URL wrappers. Refer to List of Supported Protocols/Wrappers for a listing of which wrappers support stat() family of functionality.

in fact, i'm still unable to get stat() working with my stream... :(

#17

aaron - November 10, 2009 - 22:04
Status:needs review» fixed

OK, figured it out. I had to implement ->url_stat(). Really confusing. Not sure where ->stream_stat() is even used; i'd earlier interpreted its documentation to mean it was called for stat() functions.

#18

c960657 - November 11, 2009 - 17:44

stream_stat() is used by fstat() (get info based on a filehandle) and url_stat() is used by stat() (get info based on a filename).

#19

System Message - November 25, 2009 - 17:50
Status:fixed» closed

Automatically closed -- issue fixed for 2 weeks with no activity.

 
 

Drupal is a registered trademark of Dries Buytaert.