4 * A repository for files accessible via InstantCommons.
7 class ICRepo
extends LocalRepo
{
8 var $directory, $url, $hashLevels, $cache;
9 var $fileFactory = array( 'ICFile', 'newFromTitle' );
10 var $oldFileFactory = false;
12 function __construct( $info ) {
13 parent
::__construct( $info );
15 $this->directory
= $info['directory'];
16 $this->url
= $info['url'];
17 $this->hashLevels
= $info['hashLevels'];
18 if(isset($info['cache'])){
19 $this->cache
= getcwd().'/images/'.$info['cache'];
25 * A file loaded from InstantCommons
27 class ICFile
extends LocalFile
{
28 static function newFromTitle($title,$repo){
29 return new self($title, $repo);
33 * Returns true if the file comes from the local file repository.
42 if (!$this->dataLoaded
) {
43 if ( !$this->loadFromCache() ) {
44 if(!$this->loadFromDB()){
49 $this->dataLoaded
= true;
54 * Load file metadata from the DB
56 function loadFromDB() {
57 wfProfileIn( __METHOD__
);
59 # Unconditionally set loaded=true, we don't want the accessors constantly rechecking
60 $this->dataLoaded
= true;
62 $dbr = $this->repo
->getSlaveDB();
64 $row = $dbr->selectRow( 'ic_image', $this->getCacheFields( 'img_' ),
65 array( 'img_name' => $this->getName() ), __METHOD__
);
67 if (trim($row->img_media_type
)==NULL) {
69 $this->upgraded
= true;
71 $this->loadFromRow( $row );
72 //This means that these files are local so the repository locations are local
73 $this->setUrlPathLocal();
74 $this->fileExists
= true;
75 //var_dump($this); exit;
77 $this->fileExists
= false;
80 wfProfileOut( __METHOD__
);
82 return $this->fileExists
;
86 * Fix assorted version-related problems with the image row by reloading it from the file
88 function upgradeRow() {
89 wfProfileIn( __METHOD__
);
93 $dbw = $this->repo
->getMasterDB();
94 list( $major, $minor ) = self
::splitMime( $this->mime
);
96 wfDebug(__METHOD__
.': upgrading '.$this->getName()." to the current schema\n");
98 $dbw->update( 'ic_image',
100 'img_width' => $this->width
,
101 'img_height' => $this->height
,
102 'img_bits' => $this->bits
,
103 'img_media_type' => $this->type
,
104 'img_major_mime' => $major,
105 'img_minor_mime' => $minor,
106 'img_metadata' => $this->metadata
,
107 ), array( 'img_name' => $this->getName() ),
110 $this->saveToCache();
111 wfProfileOut( __METHOD__
);
116 return $this->fileExists
;
121 * Fetch the file from the repository. Check local ic_images table first. If not available, check remote server
123 function loadFromIC(){
124 # Unconditionally set loaded=true, we don't want the accessors constantly rechecking
125 $this->dataLoaded
= true;
126 $icUrl = $this->repo
->directory
.'&media='.$this->title
->mDbkeyform
;
127 if($h = @fopen
($icUrl, 'rb')){
128 $contents = fread($h, 3000);
129 $image = $this->api_xml_to_array($contents);
130 if($image['fileExists']){
131 foreach($image as $property=>$value){
132 if($property=="url"){$value=$this->repo
->url
.$value; }
133 $this->$property = $value;
135 if($this->curl_file_get_contents($this->repo
->url
.$image['url'], $this->repo
->cache
.'/'.$image['name'])){
137 $this->recordDownload("Downloaded with InstantCommons");
140 }else{//set fileExists back to false
141 $this->fileExists
= false;
148 function setUrlPathLocal(){
149 global $wgScriptPath;
150 $path = $wgScriptPath.'/'.substr($this->repo
->cache
, strlen($wgScriptPath));
151 $this->repo
->url
= $path;//.'/'.rawurlencode($this->title->mDbkeyform);
152 $this->repo
->directory
= $this->repo
->cache
;//.'/'.rawurlencode($this->title->mDbkeyform);
156 function getThumbPath( $suffix=false ){
157 $path = $this->repo
->cache
;
158 if ( $suffix !== false ) {
159 $path .= '/thumb/' . rawurlencode( $suffix );
163 function getThumbUrl( $suffix=false ){
164 global $wgScriptPath;
165 $path = $wgScriptPath.'/'.substr($this->repo
->cache
, strlen($wgScriptPath));
166 if ( $suffix !== false ) {
167 $path .= '/thumb/' . rawurlencode( $suffix );
173 * Convert the InstantCommons Server API XML Response to an associative array
175 function api_xml_to_array($xml){
176 preg_match("/<instantcommons><image(.*?)<\/instantcommons>/",$xml,$match);
177 preg_match_all("/(.*?=\".*?\")/",$match[1], $matches);
178 foreach($matches[1] as $match){
179 list($key,$value) = split("=",$match);
180 $image[trim($key,'<" ')]=trim($value,' "');
186 * Use cURL to read the content of a URL into a string
187 * ref: http://groups-beta.google.com/group/comp.lang.php/browse_thread/thread/8efbbaced3c45e3c/d63c7891cf8e380b?lnk=raot
188 * @param string $url - the URL to fetch
189 * @param resource $fp - filename to write file contents to
190 * @param boolean $bg - call cURL in the background (don't hang page until complete)
191 * @param int $timeout - cURL connect timeout
193 function curl_file_get_contents($url, $fp, $bg=TRUE, $timeout = 1) {
195 # Call curl in the background to download the file
196 $cmd = 'curl '.wfEscapeShellArg($url).' -o '.$fp.' &';
197 wfDebug('Curl download initiated='.$cmd );
199 $file_contents = array();
200 $file_contents['err'] = wfShellExec($cmd, $file_contents['return']);
201 if($file_contents['err']==0){//Success
208 function getMasterDB() {
209 if ( !isset( $this->dbConn
) ) {
210 $class = 'Database' . ucfirst( $this->dbType
);
211 $this->dbConn
= new $class( $this->dbServer
, $this->dbUser
,
212 $this->dbPassword
, $this->dbName
, false, $this->dbFlags
,
213 $this->tablePrefix
);
215 return $this->dbConn
;
219 * Record a file upload in the upload log and the image table
221 private function recordDownload($comment='', $timestamp = false ){
224 $dbw = $this->repo
->getMasterDB();
226 if ( $timestamp === false ) {
227 $timestamp = $dbw->timestamp();
229 list( $major, $minor ) = self
::splitMime( $this->mime
);
231 # Test to see if the row exists using INSERT IGNORE
232 # This avoids race conditions by locking the row until the commit, and also
233 # doesn't deadlock. SELECT FOR UPDATE causes a deadlock for every race condition.
234 $dbw->insert( 'ic_image',
236 'img_name' => $this->getName(),
237 'img_size'=> $this->size
,
238 'img_width' => intval( $this->width
),
239 'img_height' => intval( $this->height
),
240 'img_bits' => $this->bits
,
241 'img_media_type' => $this->type
,
242 'img_major_mime' => $major,
243 'img_minor_mime' => $minor,
244 'img_timestamp' => $timestamp,
245 'img_description' => $comment,
246 'img_user' => $wgUser->getID(),
247 'img_user_text' => $wgUser->getName(),
248 'img_metadata' => $this->metadata
,
254 if( $dbw->affectedRows() == 0 ) {
255 # Collision, this is an update of a file
256 # Update the current image row
257 $dbw->update( 'ic_image',
259 'img_size' => $this->size
,
260 'img_width' => intval( $this->width
),
261 'img_height' => intval( $this->height
),
262 'img_bits' => $this->bits
,
263 'img_media_type' => $this->media_type
,
264 'img_major_mime' => $this->major_mime
,
265 'img_minor_mime' => $this->minor_mime
,
266 'img_timestamp' => $timestamp,
267 'img_description' => $comment,
268 'img_user' => $wgUser->getID(),
269 'img_user_text' => $wgUser->getName(),
270 'img_metadata' => $this->metadata
,
271 ), array( /* WHERE */
272 'img_name' => $this->getName()
277 # Update the image count
278 $site_stats = $dbw->tableName( 'site_stats' );
279 $dbw->query( "UPDATE $site_stats SET ss_images=ss_images+1", __METHOD__
);
282 $descTitle = $this->getTitle();
283 $article = new Article( $descTitle );
286 $log = new LogPage( 'icdownload' );
287 $log->addEntry( 'InstantCommons download', $descTitle, $comment );
289 if( $descTitle->exists() ) {
290 # Create a null revision
291 $nullRevision = Revision
::newNullRevision( $dbw, $descTitle->getArticleId(), $log->getRcComment(), false );
292 $nullRevision->insertOn( $dbw );
293 $article->updateRevisionOn( $dbw, $nullRevision );
295 # Invalidate the cache for the description page
296 $descTitle->invalidateCache();
297 $descTitle->purgeSquid();
301 # Commit the transaction now, in case something goes wrong later
302 # The most important thing is that files don't get lost, especially archives
303 $dbw->immediateCommit();
305 # Invalidate cache for all pages using this file
306 $update = new HTMLCacheUpdate( $this->getTitle(), 'imagelinks' );