Completed
Push — vendor/getid3 ( 1ec141...e63377 )
by Pauli
04:01
created

getID3_cached_sqlite3   A

Complexity

Total Complexity 21

Size/Duplication

Total Lines 206
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 1

Importance

Changes 0
Metric Value
dl 0
loc 206
rs 10
c 0
b 0
f 0
wmc 21
lcom 1
cbo 1

8 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 25 4
A __destruct() 0 4 1
A clear_cache() 0 11 1
A analyze() 0 36 3
A create_table() 0 5 1
A get_cached_dir() 0 12 2
B getQuery() 0 28 8
A __get() 0 3 1
1
<?php
2
/////////////////////////////////////////////////////////////////
3
/// getID3() by James Heinrich <[email protected]>               //
4
//  available at https://github.com/JamesHeinrich/getID3       //
5
//            or https://www.getid3.org                        //
6
//            or http://getid3.sourceforge.net                 //
7
//                                                             //
8
// extension.cache.mysqli.php - part of getID3()               //
9
// Please see readme.txt for more information                  //
10
//                                                             //
11
/////////////////////////////////////////////////////////////////
12
//                                                             //
13
// extension.cache.sqlite3.php - part of getID3()              //
14
// Please see readme.txt for more information                  //
15
//                                                             //
16
/////////////////////////////////////////////////////////////////
17
///                                                            //
18
// MySQL extension written by Allan Hansen <ahØartemis*dk>     //
19
// Table name mod by Carlo Capocasa <calroØcarlocapocasa*com>  //
20
// MySQL extension was reworked for SQLite3 by                 //
21
//   Karl G. Holz <newaeonØmac*com>                            //
22
//                                                            ///
23
/////////////////////////////////////////////////////////////////
24
25
/**
26
* This is a caching extension for getID3(). It works the exact same
27
* way as the getID3 class, but return cached information much faster
28
*
29
*    Normal getID3 usage (example):
30
*
31
*       require_once 'getid3/getid3.php';
32
*       $getID3 = new getID3;
33
*       $getID3->encoding = 'UTF-8';
34
*       $info1 = $getID3->analyze('file1.flac');
35
*       $info2 = $getID3->analyze('file2.wv');
36
*
37
*    getID3_cached usage:
38
*
39
*       require_once 'getid3/getid3.php';
40
*       require_once 'getid3/extension.cache.sqlite3.php';
41
*       // all parameters are optional, defaults are:
42
*       $getID3 = new getID3_cached_sqlite3($table='getid3_cache', $hide=FALSE);
43
*       $getID3->encoding = 'UTF-8';
44
*       $info1 = $getID3->analyze('file1.flac');
45
*       $info2 = $getID3->analyze('file2.wv');
46
*
47
*
48
* Supported Cache Types    (this extension)
49
*
50
*   SQL Databases:
51
*
52
*   cache_type          cache_options
53
*   -------------------------------------------------------------------
54
*   mysql               host, database, username, password
55
*
56
*   sqlite3             table='getid3_cache', hide=false        (PHP5)
57
*
58
*
59
* ***  database file will be stored in the same directory as this script,
60
* ***  webserver must have write access to that directory!
61
* ***  set $hide to TRUE to prefix db file with .ht to pervent access from web client
62
* ***  this is a default setting in the Apache configuration:
63
*
64
* The following lines prevent .htaccess and .htpasswd files from being viewed by Web clients.
65
*
66
* <Files ~ "^\.ht">
67
*     Order allow,deny
68
*     Deny from all
69
*     Satisfy all
70
* </Files>
71
*
72
********************************************************************************
73
*
74
*   -------------------------------------------------------------------
75
*   DBM-Style Databases:    (use extension.cache.dbm)
76
*
77
*   cache_type          cache_options
78
*   -------------------------------------------------------------------
79
*   gdbm                dbm_filename, lock_filename
80
*   ndbm                dbm_filename, lock_filename
81
*   db2                 dbm_filename, lock_filename
82
*   db3                 dbm_filename, lock_filename
83
*   db4                 dbm_filename, lock_filename  (PHP5 required)
84
*
85
*   PHP must have write access to both dbm_filename and lock_filename.
86
*
87
* Recommended Cache Types
88
*
89
*   Infrequent updates, many reads      any DBM
90
*   Frequent updates                    mysql
91
********************************************************************************
92
*
93
* IMHO this is still a bit slow, I'm using this with MP4/MOV/ M4v files
94
* there is a plan to add directory scanning and analyzing to make things work much faster
95
*
96
*
97
*/
98
class getID3_cached_sqlite3 extends getID3
99
{
100
	/**
101
	 * hold the sqlite db
102
	 *
103
	 * @var SQLite3 Resource
104
	 */
105
	private $db;
106
107
	/**
108
	 * table to use for caching
109
	 *
110
	 * @var string $table
111
	 */
112
	private $table;
113
114
	/**
115
	 * @param string  $table holds name of sqlite table
116
	 * @param boolean $hide
117
	 *
118
	 * @throws getid3_exception
119
	 * @throws Exception
120
	 */
121
	public function __construct($table='getid3_cache', $hide=false) {
122
		// Check for SQLite3 support
123
		if (!function_exists('sqlite_open')) {
124
			throw new Exception('PHP not compiled with SQLite3 support.');
125
		}
126
127
		$this->table = $table; // Set table
128
		$file = dirname(__FILE__).'/'.basename(__FILE__, 'php').'sqlite';
129
		if ($hide) {
130
			$file = dirname(__FILE__).'/.ht.'.basename(__FILE__, 'php').'sqlite';
131
		}
132
		$this->db = new SQLite3($file);
133
		$db = $this->db;
134
		$this->create_table();   // Create cache table if not exists
135
		$version = '';
136
		$sql = $this->getQuery('version_check');
137
		$stmt = $db->prepare($sql);
138
		$stmt->bindValue(':filename', getID3::VERSION, SQLITE3_TEXT);
139
		$result = $stmt->execute();
140
		list($version) = $result->fetchArray();
141
		if ($version != getID3::VERSION) { // Check version number and clear cache if changed
142
			$this->clear_cache();
143
		}
144
		parent::__construct();
145
	}
146
147
	/**
148
	 * close the database connection
149
	 */
150
	public function __destruct() {
151
		$db=$this->db;
152
		$db->close();
153
	}
154
155
	/**
156
	 * clear the cache
157
	 *
158
	 * @return SQLite3Result
159
	 */
160
	private function clear_cache() {
161
		$db = $this->db;
162
		$sql = $this->getQuery('delete_cache');
163
		$db->exec($sql);
164
		$sql = $this->getQuery('set_version');
165
		$stmt = $db->prepare($sql);
166
		$stmt->bindValue(':filename', getID3::VERSION, SQLITE3_TEXT);
167
		$stmt->bindValue(':dirname', getID3::VERSION, SQLITE3_TEXT);
168
		$stmt->bindValue(':val', getID3::VERSION, SQLITE3_TEXT);
169
		return $stmt->execute();
170
	}
171
172
	/**
173
	 * analyze file and cache them, if cached pull from the db
174
	 *
175
	 * @param string  $filename
176
	 * @param integer $filesize
177
	 * @param string  $original_filename
178
	 *
179
	 * @return mixed|false
180
	 */
181
	public function analyze($filename, $filesize=null, $original_filename='') {
182
		if (!file_exists($filename)) {
183
			return false;
0 ignored issues
show
Bug Best Practice introduced by
The return type of return false; (false) is incompatible with the return type of the parent method getID3::analyze of type array.

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

    public function __construct($name) {
        $this->name = $name;
    }

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
184
		}
185
		// items to track for caching
186
		$filetime = filemtime($filename);
187
		$filesize_real = filesize($filename);
188
		// this will be saved for a quick directory lookup of analized files
189
		// ... why do 50 seperate sql quries when you can do 1 for the same result
190
		$dirname  = dirname($filename);
191
		// Lookup file
192
		$db = $this->db;
193
		$sql = $this->getQuery('get_id3_data');
194
		$stmt = $db->prepare($sql);
195
		$stmt->bindValue(':filename', $filename,      SQLITE3_TEXT);
196
		$stmt->bindValue(':filesize', $filesize_real, SQLITE3_INTEGER);
197
		$stmt->bindValue(':filetime', $filetime,      SQLITE3_INTEGER);
198
		$res = $stmt->execute();
199
		list($result) = $res->fetchArray();
200
		if (count($result) > 0 ) {
201
			return unserialize(base64_decode($result));
202
		}
203
		// if it hasn't been analyzed before, then do it now
204
		$analysis = parent::analyze($filename, $filesize, $original_filename);
205
		// Save result
206
		$sql = $this->getQuery('cache_file');
207
		$stmt = $db->prepare($sql);
208
		$stmt->bindValue(':filename', $filename,                           SQLITE3_TEXT);
209
		$stmt->bindValue(':dirname',  $dirname,                            SQLITE3_TEXT);
210
		$stmt->bindValue(':filesize', $filesize_real,                      SQLITE3_INTEGER);
211
		$stmt->bindValue(':filetime', $filetime,                           SQLITE3_INTEGER);
212
		$stmt->bindValue(':atime',    time(),                              SQLITE3_INTEGER);
213
		$stmt->bindValue(':val',      base64_encode(serialize($analysis)), SQLITE3_TEXT);
214
		$res = $stmt->execute();
0 ignored issues
show
Unused Code introduced by
$res is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
215
		return $analysis;
216
	}
217
218
	/**
219
	 * create data base table
220
	 * this is almost the same as MySQL, with the exception of the dirname being added
221
	 *
222
	 * @return bool
223
	 */
224
	private function create_table() {
225
		$db = $this->db;
226
		$sql = $this->getQuery('make_table');
227
		return $db->exec($sql);
228
	}
229
230
	/**
231
	 * get cached directory
232
	 *
233
	 * This function is not in the MySQL extention, it's ment to speed up requesting multiple files
234
	 * which is ideal for podcasting, playlists, etc.
235
	 *
236
	 * @param string $dir directory to search the cache database for
237
	 *
238
	 * @return array return an array of matching id3 data
239
	 */
240
	public function get_cached_dir($dir) {
241
		$db = $this->db;
242
		$rows = array();
243
		$sql = $this->getQuery('get_cached_dir');
244
		$stmt = $db->prepare($sql);
245
		$stmt->bindValue(':dirname', $dir, SQLITE3_TEXT);
246
		$res = $stmt->execute();
247
		while ($row=$res->fetchArray()) {
248
			$rows[] = unserialize(base64_decode($row));
249
		}
250
		return $rows;
251
	}
252
253
	/**
254
	 * returns NULL if query is not found
255
	 *
256
	 * @param string $name
257
	 *
258
	 * @return null|string
259
	 */
260
	public function getQuery($name)
261
	{
262
		switch ($name) {
263
			case 'version_check':
264
				return "SELECT val FROM $this->table WHERE filename = :filename AND filesize = '-1' AND filetime = '-1' AND analyzetime = '-1'";
265
				break;
0 ignored issues
show
Unused Code introduced by
break is not strictly necessary here and could be removed.

The break statement is not necessary if it is preceded for example by a return statement:

switch ($x) {
    case 1:
        return 'foo';
        break; // This break is not necessary and can be left off.
}

If you would like to keep this construct to be consistent with other case statements, you can safely mark this issue as a false-positive.

Loading history...
266
			case 'delete_cache':
267
				return "DELETE FROM $this->table";
268
				break;
0 ignored issues
show
Unused Code introduced by
break is not strictly necessary here and could be removed.

The break statement is not necessary if it is preceded for example by a return statement:

switch ($x) {
    case 1:
        return 'foo';
        break; // This break is not necessary and can be left off.
}

If you would like to keep this construct to be consistent with other case statements, you can safely mark this issue as a false-positive.

Loading history...
269
			case 'set_version':
270
				return "INSERT INTO $this->table (filename, dirname, filesize, filetime, analyzetime, val) VALUES (:filename, :dirname, -1, -1, -1, :val)";
271
				break;
0 ignored issues
show
Unused Code introduced by
break is not strictly necessary here and could be removed.

The break statement is not necessary if it is preceded for example by a return statement:

switch ($x) {
    case 1:
        return 'foo';
        break; // This break is not necessary and can be left off.
}

If you would like to keep this construct to be consistent with other case statements, you can safely mark this issue as a false-positive.

Loading history...
272
			case 'get_id3_data':
273
				return "SELECT val FROM $this->table WHERE filename = :filename AND filesize = :filesize AND filetime = :filetime";
274
				break;
0 ignored issues
show
Unused Code introduced by
break is not strictly necessary here and could be removed.

The break statement is not necessary if it is preceded for example by a return statement:

switch ($x) {
    case 1:
        return 'foo';
        break; // This break is not necessary and can be left off.
}

If you would like to keep this construct to be consistent with other case statements, you can safely mark this issue as a false-positive.

Loading history...
275
			case 'cache_file':
276
				return "INSERT INTO $this->table (filename, dirname, filesize, filetime, analyzetime, val) VALUES (:filename, :dirname, :filesize, :filetime, :atime, :val)";
277
				break;
0 ignored issues
show
Unused Code introduced by
break is not strictly necessary here and could be removed.

The break statement is not necessary if it is preceded for example by a return statement:

switch ($x) {
    case 1:
        return 'foo';
        break; // This break is not necessary and can be left off.
}

If you would like to keep this construct to be consistent with other case statements, you can safely mark this issue as a false-positive.

Loading history...
278
			case 'make_table':
279
				return "CREATE TABLE IF NOT EXISTS $this->table (filename VARCHAR(255) DEFAULT '', dirname VARCHAR(255) DEFAULT '', filesize INT(11) DEFAULT '0', filetime INT(11) DEFAULT '0', analyzetime INT(11) DEFAULT '0', val text, PRIMARY KEY (filename, filesize, filetime))";
280
				break;
0 ignored issues
show
Unused Code introduced by
break is not strictly necessary here and could be removed.

The break statement is not necessary if it is preceded for example by a return statement:

switch ($x) {
    case 1:
        return 'foo';
        break; // This break is not necessary and can be left off.
}

If you would like to keep this construct to be consistent with other case statements, you can safely mark this issue as a false-positive.

Loading history...
281
			case 'get_cached_dir':
282
				return "SELECT val FROM $this->table WHERE dirname = :dirname";
283
				break;
0 ignored issues
show
Unused Code introduced by
break is not strictly necessary here and could be removed.

The break statement is not necessary if it is preceded for example by a return statement:

switch ($x) {
    case 1:
        return 'foo';
        break; // This break is not necessary and can be left off.
}

If you would like to keep this construct to be consistent with other case statements, you can safely mark this issue as a false-positive.

Loading history...
284
			default:
285
				return null;
286
		}
287
	}
288
289
	/**
290
	* use the magical __get() for sql queries
291
	*
292
	* access as easy as $this->{case name}, returns NULL if query is not found
293
	*
294
	* @param string $name
295
	*
296
	* @return string
297
	* @deprecated use getQuery() instead
298
	*/
299
	public function __get($name) {
300
		return $this->getQuery($name);
301
	}
302
303
}
304