Posted in Linux

One of the most annoying things about SABnzbd is it’s failure to correctly repair broken downloads – especially when you download multiple repairable files in a single combined NZB. This often stems from SABnzbd not including all relevant par2 and part files during its repair – so the par2 command assumes far more missing files than there actually are. Below I’ll explain a method for easily including all relevant files for a (hopefully) more successful repair.

 

The Par2 Command

First let’s look at the par2 command:

1
2
3
4
5
6
7
8
$ par2
...
Usage:
 
  par2 c(reate) [options] <par2 file> [files] : Create PAR2 files
  par2 v(erify) [options] <par2 file> [files] : Verify files using PAR2 file
  par2 r(epair) [options] <par2 file> [files] : Repair files using PAR2 files
...

Based on the above we need to call

1
par2 r <list of par2 files> <list of mkv parts>

But how do we get those lists?

 

Your Download

Let’s say I’ve (legally) downloaded of episodes 8 and 9 of MyShow!. My failed download folder might include the following files:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
MyShow! - 08 [720p].mkv.001
MyShow! - 08 [720p].mkv.002
MyShow! - 08 [720p].mkv.003
MyShow! - 08 [720p].mkv.par2
MyShow! - 08 [720p].mkv.vol000+01.par2
MyShow! - 08 [720p].mkv.vol001+02.par2
MyShow! - 09 [720p].mkv.001
MyShow! - 09 [720p].mkv.002
MyShow! - 09 [720p].mkv.003
MyShow! - 09 [720p].mkv.004
MyShow! - 09 [720p].mkv.par2
MyShow! - 09 [720p].mkv.vol000+01.par2
MyShow! - 09 [720p].mkv.vol001+02.par2
MyShow! - 09 [720p].mkv.vol003+04.par2

We want to repair one episode at a time starting with episode 8. Here’s how I’d retrieve lists of just par2 then just the part files:

1
2
3
4
5
6
7
# Episode 8
ls *"- 08"*.par2 # Par2 files
ls *"- 08"*.[0-9][0-9][0-9] # Part files
 
# Episode 9
ls *"- 09"*.par2 # Par2 files
ls *"- 09"*.[0-9][0-9][0-9] # Part files

 

Putting it All Together

Combining par2 with my above lists results in:

1
2
par2 r *"- 08"*.par2 *"- 08"*.[0-9][0-9][0-9] # Ep 8
par2 r *"- 09"*.par2 *"- 09"*.[0-9][0-9][0-9] # Ep 9

You may also need to include *.mkv if that file exists but more often than not I’ve found it doesn’t.

Hope your repairs go a little smoother with this quick tip!

Read More »

Posted in PHP

Those of you with MAMP who work with laravel will be very familiar with this dreaded message when attempting to php artisan tinker:

Boris REPL not supported. Needs readline, posix, and pcnt extensions.

Inside where I explain how to install these tools.

Read More »

Posted in Uncategorized

Spent an hour grappling with this last night. Here’s how to get the PS3 controller working on Yosemite.

Pair the PS3 controller with your mac:

It seems Yosemite’s bluetooth device list is a little buggy and devices can show in the top bar bluetooth drop down but not in the system configuration bluetooth page for some devices. The PS3 controller is one of these devices.

After a while digging around the net I found some working instructions thanks to user DillingerEscapeHam on Reddit:

  • Plug PS3 controller into laptop
  • See Bluetooth device in status bar menu (though just a bt address, not named “Playstation Controller”)
  • Click to Connect device in status bar menu
  • Open System Preferences
  • No device listed
  • Unplug controller
  • Attempt to turn controller on without cable (no response, no flashing lights)
  • Controller was now listed correctly as Playstation controller in status bar menu and was listed as Connected
  • Disconnect controller via menu.
  • Unplug and start controller with PS button. It connects correctly.

After you get the controller paired you’ll need either a game that supports the controller or an app like Joystick Mapper (paid) or Enjoy2 (free) which allows binding of keyboard keys or mouse buttons/swipes to controller events. I recommend forking out the $6 for Joystick Mapper, as I’ve had issues with sensitivity on Enjoy2.

That should be all there is to it. I’m currently playing Hearthstone with a PS3 controller on Yosemite just fine!

Read More »

Posted (Updated ) in Uncategorized

It’s a pretty common problem. You have a file titled firefly – 02 – the train job.mkv when it should be Firefly – 02 – The Train Job.mkv. It’s slow and arduous to rename manually especially if you have a lot of files so below is a single command to do it all for you!

1
rename "s/ ([a-z])/ \U\1\E/g" *.mkv

In the above example:

  • \U means uppercase
  • \1 means the first match (in our case that’s the first alpha after a space)
  • \E means end uppercase
  • *.mkv applies the rename command to all files ending in .mkv in the current folder.

Thanks to akf from stackoverflow for his helpful answer on this one.

Read More »

Posted (Updated ) in PHP

If you want your WordPress site to handle large traffic spikes, loading as many assets as possible from a third party server is a no-brainer. One of the ways to do this is by pointing all asset URLs to a subdomain and having that subdomain load through a service like CloudFront using origin pull. This is done in 3 steps:

  1. Create the CloudFront distribution
  2. Add the subdomain to your DNS
  3. The use of a poorly documented WordPress constant.

In this tutorial I’ll be moving all my sites assets from www.flynsarmy.com to static.flynsarmy.com.

Read More »

Posted in PHP

I’ve been wanting to use iron.io queues with Illuminate/Queue and despite claiming otherwise in its readme, this doesn’t actually work with the provided example code – instead simply resulting in

Fatal error: Uncaught exception ‘ReflectionException’ with message ‘Class encrypter does not exist’ in /vendor/illuminate/container/Illuminate/Container/Container.php on line 485

I’ve tried numerous times to get a fix from Taylor and ignored/given the runaround every time. I even submitted a pull request with the solution but it was closed without merge or explanation.

Anyway, to ACTUALLY use iron.io with illuminate/queue outside of laravel, the following lines of code are required:

1
2
3
4
5
6
$queue->getContainer()->bind('encrypter', function() {
	return new Illuminate\Encryption\Encrypter('foobar');
});
$queue->getContainer()->bind('request', function() {
	return new Illuminate\Http\Request();
});

Drop them below $queue->addConnection and you’re good to go. Here’s complete example file:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
require 'vendor/autoload.php';
 
use Illuminate\Queue\Capsule\Manager as Queue;
use Carbon\Carbon;
 
$queue = new Queue;
 
$queue->addConnection(require __DIR__.'/config/queue.php');
 
// Make this Capsule instance available globally via static methods... (optional)
$queue->setAsGlobal();
 
$date = Carbon::now()->addMinutes(2);
$queue->getContainer()->bind('encrypter', function() {
	return new Illuminate\Encryption\Encrypter('foobar');
});
$queue->getContainer()->bind('request', function() {
	return new Illuminate\Http\Request();
});
Queue::push($date, 'EmailTest', array('foo' => 'bar'));
 
class EmailTest
{
	public function fire($job, $data)
	{
		mail('my@email.com', 'hiya', $data['foo']);
	}
}

Read More »

Posted (Updated ) in Javascript

Twitter Bootstrap (3.1.1 at the time of writing) has a handy piece of functionality whereby if you trigger a modal with data attributes on an anchor element, the modal will load its contents from the anchors href attribute. Being able to AJAX load in this way is great, however the first load caches so every subsequent click of the anchor element will show the same modal contents. This can present a problem in some use cases.

Here’s an example of the default functionality:

Notice the dynamically generated date isn’t being updated each time you open the modal? Well if you need the contents to always be reloaded each open, there’s a simple jQuery fix.

1
2
3
$('#myModal').on('hide.bs.modal', function(e) {
	$(this).removeData('bs.modal');
});

And a working demo:

Enjoy!

Read More »

Posted (Updated ) in Javascript, PHP

Imagine you’re running a WordPress Multisite subfolder instance (let’s call it MyNetwork) with a bunch of blogs like so:

mynetwork.com
mynetwork.com/foo
mynetwork.com/bar

Each blog needs to keep track of its page views (including the main blog) but you’d also like page view aggregated statistics for your entire network. This can be done and is surprisingly easy with Google Analytics views and filters. Just follow the below steps and you’ll be on your way.

Read More »

Posted (Updated ) in PHP

Update 2015-05-02: Now working for WP 4.2

In a project today we needed to attach a meta value to a comment based on the user commenting, then filter the comments list (both frontend and backend) by this value. The task turned out to be surprisingly difficult and the comment actions/filters are notoriously badly documented in WP so I thought I’d document the process here.

1. Attach a meta value as a visitor comments

A simple comment_post action (not documented) hook does the trick here.

1
2
3
4
5
6
/**
 * Adds a meta value to a comment as one is created
 */
add_action( 'comment_post', function($comment_id) {
	add_comment_meta( $comment_id, 'my_meta_key', 'my_meta_value' );
});

 

2. Filter the comments by meta (4.2+)

Until 4.2 this was done a little differently (See the bottom of this post for that code) however due to recent changes we’re now forced to use comments_clauses action like so:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
/**
 * Limits displayed comments on front and backend to just those with the
 * specified meta key/value pair.
 *
 * @param array $pieces
 * @param WP_Comment_Query $query
 * @return array
 */
add_action('comments_clauses', function(array $pieces, WP_Comment_Query $query) {
    global $wpdb;
 
    $meta_query = new WP_Meta_Query();
    $meta_query->parse_query_vars([
        'meta_key' => 'my_meta_key',        'meta_value' => 'my_meta_value',    ]);
 
    if ( !empty($meta_query->queries) )
    {
        $meta_query_clauses = $meta_query->get_sql( 'comment', $wpdb->comments, 'comment_ID', $query );
 
        if ( !empty($meta_query_clauses) )
        {
            $pieces['join'] .= $meta_query_clauses['join'];
 
            if ( $pieces['where'] )
                $pieces['where'] .= ' AND ';
            // Strip leading 'AND'.
            $pieces['where'] .= preg_replace( '/^\s*AND\s*/', '', $meta_query_clauses['where'] );
 
            if ( !$query->query_vars['count'] )
            {
                $pieces['groupby'] = "{$wpdb->comments}.comment_ID";
            }
        }
    }
 
    return $pieces;
}, 10, 2);

 

3. Making get_comments_number() work

get_comments_number() method commonly used in your themes comments.php file will return the total number of comments associated with a post. We need it filtered by our above meta query. Here’s an inefficient but effective way to make that happen:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/**
 * Filter get_comments_number() correctly by our meta query.
 *
 * @param int $count
 * @param int $post_id
 * @return int
 */
add_filter('get_comments_number', function($count, $post_id) {
    $query = new WP_Comment_Query(['post_id' => $post_id]);
 
    // Frontend users only see approved comments
    if ( !is_admin() )
        $comment_query['status'] = 'approve';
 
    return sizeof($query->get_comments());
}, 10, 2);

 

Conclusion

That should be it! If anything doesn’t work as expected let me know in the comments below.

 

Bonus: Filter the comments by meta (<4.2)

Filtering comments by meta in <4.2 is done in two steps. One for admin, and one for frontend.

Admin

This can be done with pre_get_comments action hook (again, not documented – but I did find a usage example here).

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/**
 * Limit visible comments to just those for the current subdomain.
 * Due to a bug in WP this will work for all backend users but only for 
 * logged out users on the frontend.
 */
add_action('pre_get_comments', function($query) {
	// meta_query is already an instance of WP_Comment_Query but other 
	// query_vars may have already been added, so re-initialize it
	$query->meta_query = new WP_Comment_Query();
 
	$query->query_vars['meta_key'] = 'my_meta_key';
	$query->query_vars['meta_value'] = 'my_meta_value';
 
	$query->meta_query->parse_query_vars( $query->query_vars );
});

Frontend

As the pre_get_comments comment mentions, there is a bug in WP whereby logged in users will not have the above filter applied, so we need to use another hook for those.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
/**
 * WP doesn't support pre_get_comments for logged in users, so for those
 * we need to manually remove all the comments that don't have  our meta
 * key
 *
 * See https://core.trac.wordpress.org/ticket/27018#ticket
 */
add_filter('comments_array', function($comments, $post_id) {
	global $wpdb, $user_ID;
 
	// If there's no user_ID and no commenter then the pre_get_comments filter
	// applied correctly and we don't need to do anything
	// See comments_template() method
	$commenter = wp_get_current_commenter();
	$comment_author = $commenter['comment_author']; // Escaped by sanitize_comment_cookies()
 
	if ( $user_ID || !empty($comment_author) )
		return;
 
	// Grab comment IDs for this current area
	$area_comment_ids = $wpdb->get_col($wpdb->prepare("
		SELECT $wpdb->comments.comment_ID
		FROM $wpdb->comments
		JOIN $wpdb->commentmeta ON $wpdb->commentmeta.comment_id=$wpdb->comments.comment_ID
		WHERE $wpdb->commentmeta.meta_key=%s AND $wpdb->commentmeta.meta_value=%s
	", 'my_meta_key', 'my_meta_value'));
 
	// Strip out any comments not belonging to the current area
	foreach ( $comments as $key => $comment )
		if ( !in_array($comment->comment_ID, $area_comment_ids) )
			unset($comments[$key]);
 
	return $comments;
}, 10, 2);

The above seems really messy and inefficient to me but I can’t think of a better way. It’ll grab all comments we want to show and filter out any others.

Read More »

Posted (Updated ) in Uncategorized

It seems OSX 10.9 comes with a “feature” (previously off by default) whereby after 2 minutes on battery (the default display off time) the machine goes to sleep. This of course kills the wi-fi resulting in constant dropping of SSH connections, reconnection to IM clients (spamming friends) and a host of other internet related issues. It was badly thought out, badly implemented and just an all around bad choice.

In previous iterations of OSX there were two sliders; one for how long before putting the computer to sleep and the other for putting the display to sleep. Here’s a screenshot of the Energy Saver options in 10.9. Noticed the two have been unhelpfully merged:

Energy Saver Preferences in OS-X 10.9

 

Fixing this behavior is thankfully very simple and requires only a single terminal command:

1
sudo pmset -a sleep 0

Here are my power management settings after the change:

$ pmset -g
Active Profiles:
Battery Power		-1*
AC Power		-1
Currently in use:
 standbydelay         10800
 standby              1
 halfdim              1
 hibernatefile        /var/vm/sleepimage
 darkwakes            0
 gpuswitch            2
 disksleep            10
 sleep                0
 autopoweroffdelay    14400
 hibernatemode        3
 autopoweroff         1
 ttyskeepawake        1
 displaysleep         2
 acwake               0
 lidwake              1

And with that you should be back to having a properly functioning laptop. If you ever need to revert the change for whatever reason, use:

1
sudo pmset -a sleep 1

Read More »