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
;
120 * Fetch the file from the repository. Check local ic_images table first. If not available, check remote server
122 function loadFromIC(){
123 # Unconditionally set loaded=true, we don't want the accessors constantly rechecking
124 $this->dataLoaded
= true;
125 $icUrl = $this->repo
->directory
.'&media='.$this->title
->mDbkeyform
;
126 if($h = @fopen
($icUrl, 'rb')){
127 $contents = fread($h, 3000);
128 $image = $this->api_xml_to_array($contents);
129 if($image['fileExists']){
130 foreach($image as $property=>$value){
131 if($property=="url"){$value=$this->repo
->url
.$value; }
132 $this->$property = $value;
134 if($this->curl_file_get_contents($this->repo
->url
.$image['url'], $this->repo
->cache
.'/'.$image['name'])){
136 $this->recordDownload("Downloaded with InstantCommons");
139 }else{//set fileExists back to false
140 $this->fileExists
= false;
146 function setUrlPathLocal(){
147 global $wgScriptPath;
148 $path = $wgScriptPath.'/'.substr($this->repo
->cache
, strlen($wgScriptPath));
149 $this->repo
->url
= $path;//.'/'.rawurlencode($this->title->mDbkeyform);
150 $this->repo
->directory
= $this->repo
->cache
;//.'/'.rawurlencode($this->title->mDbkeyform);
154 function getThumbPath( $suffix=false ){
155 $path = $this->repo
->cache
;
156 if ( $suffix !== false ) {
157 $path .= '/thumb/' . rawurlencode( $suffix );
161 function getThumbUrl( $suffix=false ){
162 global $wgScriptPath;
163 $path = $wgScriptPath.'/'.substr($this->repo
->cache
, strlen($wgScriptPath));
164 if ( $suffix !== false ) {
165 $path .= '/thumb/' . rawurlencode( $suffix );
171 * Convert the InstantCommons Server API XML Response to an associative array
173 function api_xml_to_array($xml){
174 preg_match("/<instantcommons><image(.*?)<\/instantcommons>/",$xml,$match);
175 preg_match_all("/(.*?=\".*?\")/",$match[1], $matches);
176 foreach($matches[1] as $match){
177 list($key,$value) = split("=",$match);
178 $image[trim($key,'<" ')]=trim($value,' "');
184 * Use cURL to read the content of a URL into a string
185 * ref: http://groups-beta.google.com/group/comp.lang.php/browse_thread/thread/8efbbaced3c45e3c/d63c7891cf8e380b?lnk=raot
186 * @param string $url - the URL to fetch
187 * @param resource $fp - filename to write file contents to
188 * @param boolean $bg - call cURL in the background (don't hang page until complete)
189 * @param int $timeout - cURL connect timeout
191 function curl_file_get_contents($url, $fp, $bg=TRUE, $timeout = 1) {
192 # Call curl in the background to download the file
193 $cmd = 'curl '.wfEscapeShellArg($url).' -o '.$fp.' &';
194 wfDebug('Curl download initiated='.$cmd );
196 $file_contents = array();
197 $file_contents['err'] = wfShellExec($cmd, $file_contents['return']);
198 if($file_contents['err']==0){//Success
204 function getMasterDB() {
205 if ( !isset( $this->dbConn
) ) {
206 $class = 'Database' . ucfirst( $this->dbType
);
207 $this->dbConn
= new $class( $this->dbServer
, $this->dbUser
,
208 $this->dbPassword
, $this->dbName
, false, $this->dbFlags
,
209 $this->tablePrefix
);
211 return $this->dbConn
;
215 * Record a file upload in the upload log and the image table
217 private function recordDownload($comment='', $timestamp = false ){
220 $dbw = $this->repo
->getMasterDB();
222 if ( $timestamp === false ) {
223 $timestamp = $dbw->timestamp();
225 list( $major, $minor ) = self
::splitMime( $this->mime
);
227 # Test to see if the row exists using INSERT IGNORE
228 # This avoids race conditions by locking the row until the commit, and also
229 # doesn't deadlock. SELECT FOR UPDATE causes a deadlock for every race condition.
230 $dbw->insert( 'ic_image',
232 'img_name' => $this->getName(),
233 'img_size'=> $this->size
,
234 'img_width' => intval( $this->width
),
235 'img_height' => intval( $this->height
),
236 'img_bits' => $this->bits
,
237 'img_media_type' => $this->type
,
238 'img_major_mime' => $major,
239 'img_minor_mime' => $minor,
240 'img_timestamp' => $timestamp,
241 'img_description' => $comment,
242 'img_user' => $wgUser->getID(),
243 'img_user_text' => $wgUser->getName(),
244 'img_metadata' => $this->metadata
,
250 if( $dbw->affectedRows() == 0 ) {
251 # Collision, this is an update of a file
252 # Update the current image row
253 $dbw->update( 'ic_image',
255 'img_size' => $this->size
,
256 'img_width' => intval( $this->width
),
257 'img_height' => intval( $this->height
),
258 'img_bits' => $this->bits
,
259 'img_media_type' => $this->media_type
,
260 'img_major_mime' => $this->major_mime
,
261 'img_minor_mime' => $this->minor_mime
,
262 'img_timestamp' => $timestamp,
263 'img_description' => $comment,
264 'img_user' => $wgUser->getID(),
265 'img_user_text' => $wgUser->getName(),
266 'img_metadata' => $this->metadata
,
267 ), array( /* WHERE */
268 'img_name' => $this->getName()
273 # Update the image count
274 $site_stats = $dbw->tableName( 'site_stats' );
275 $dbw->query( "UPDATE $site_stats SET ss_images=ss_images+1", __METHOD__
);
278 $descTitle = $this->getTitle();
279 $article = new Article( $descTitle );
282 $log = new LogPage( 'icdownload' );
283 $log->addEntry( 'InstantCommons download', $descTitle, $comment );
285 if( $descTitle->exists() ) {
286 # Create a null revision
287 $nullRevision = Revision
::newNullRevision( $dbw, $descTitle->getArticleId(), $log->getRcComment(), false );
288 $nullRevision->insertOn( $dbw );
289 $article->updateRevisionOn( $dbw, $nullRevision );
291 # Invalidate the cache for the description page
292 $descTitle->invalidateCache();
293 $descTitle->purgeSquid();
297 # Commit the transaction now, in case something goes wrong later
298 # The most important thing is that files don't get lost, especially archives
299 $dbw->immediateCommit();
301 # Invalidate cache for all pages using this file
302 $update = new HTMLCacheUpdate( $this->getTitle(), 'imagelinks' );