Skip to content

Commit

Permalink
Add "watch" functionality (fix koel#213)
Browse files Browse the repository at this point in the history
  • Loading branch information
An Phan committed Feb 2, 2016
1 parent 3e6922f commit 46f6141
Show file tree
Hide file tree
Showing 8 changed files with 382 additions and 16 deletions.
35 changes: 34 additions & 1 deletion app/Console/Commands/SyncMedia.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@ class SyncMedia extends Command
*
* @var string
*/
protected $signature = 'koel:sync';
protected $signature = 'koel:sync
{record? : A single fswatch record. Consult Wiki for more info.}';

protected $ignored = 0;
protected $invalid = 0;
Expand Down Expand Up @@ -47,6 +48,20 @@ public function handle()
return;
}

if (!$record = $this->argument('record')) {
$this->syncAll();

return;
}

$this->syngle($record);
}

/**
* Sync all files in the configured media path.
*/
protected function syncAll()
{
$this->info('Koel syncing started. All we need now is just a little patience…');

Media::sync(null, $this);
Expand All @@ -56,6 +71,24 @@ public function handle()
."and <comment>{$this->invalid} invalid file(s)</comment>.");
}

/**
* SYNc a sinGLE file or directory. See my awesome pun?
*
* @param string|FSWatchRecord $record An fswatch record, in this format:
* "<changed_path> <event_flag_1>::<event_flag_2>::<event_flag_n>"
* The fswatch command should look like this:
* ``` bash
* $ fswatch -0x --event-flag-separator="::" $MEDIA_PATH \
* | xargs -0 -n1 -I record php artisan koel:sync record
* ```
*
* @link https://github.com/emcrisostomo/fswatch/wiki/How-to-Use-fswatch
*/
public function syngle($record)
{
Media::syncFSWatchRecord($record, $this);
}

/**
* Log a song's sync status to console.
*/
Expand Down
23 changes: 23 additions & 0 deletions app/Events/LibraryChanged.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
<?php

namespace App\Events;

class LibraryChanged extends Event
{
/**
* Create a new event instance.
*/
public function __construct()
{
}

/**
* Get the channels the event should be broadcast on.
*
* @return array
*/
public function broadcastOn()
{
return [];
}
}
101 changes: 101 additions & 0 deletions app/Helpers/FSWatchRecord.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
<?php

namespace App\Helpers;

class FSWatchRecord
{
/**
* The event separator used in our fswatch command.
*/
const FSWATCH_FLAG_SEPARATOR = '::';

/**
* Path of the file/directory that triggers the fswatch event.
*
* @var string
*/
protected $path;

/**
* The flags of the fswatch event.
*
* @var array
*/
protected $eventFlags;

/**
* Construct an FSWatchRecord object for a record string.
*
* @param string $string The record string, e.g.
* "/full/path/to/changed/file Renamed::IsFile"
*/
public function __construct($string)
{
$parts = explode(' ', $string);
$this->eventFlags = explode(self::FSWATCH_FLAG_SEPARATOR, array_pop($parts));
$this->path = implode(' ', $parts);
}

/**
* Determine if the file/directory is deleted from the system.
* We can't rely on fswatch, since the event is OS-dependent.
* For example, deleting on OSX will be reported as "Renamed", as
* the file/directory is "renamed" into the Trash folder.
*
* @return boolean
*/
public function isDeleted()
{
return !file_exists($this->path);
}

/**
* Determine if the object is renamed.
*
* @return boolean
*/
public function isRenamed()
{
return in_array('Renamed', $this->eventFlags);
}

/**
* Determine if the changed object is a file.
*
* @return bool
*/
public function isFile()
{
return in_array('IsFile', $this->eventFlags);
}

/**
* Determine if the changed object is a directory.
*
* @return bool
*/
public function isDir()
{
return in_array('IsDir', $this->eventFlags);
}

/**
* Get the full path of the changed file/directory.
*
* @return string
*/
public function getPath()
{
return $this->path;
}

/**
* Get the event flags of the fswatch record.
*
* @return array
*/
public function getEventFlags()
{
return $this->eventFlags;
}
}
32 changes: 32 additions & 0 deletions app/Listeners/TidyLibrary.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
<?php

namespace App\Listeners;

use App\Models\Album;
use App\Models\Artist;
use App\Models\Song;

class TidyLibrary
{
/**
* Create the event listener.
*/
public function __construct()
{
}

/**
* Fired every time a LibraryChanged event is triggered.
* Remove empty albums and artists from our system.
*/
public function handle()
{
$inUseAlbums = Song::select('album_id')->groupBy('album_id')->get()->lists('album_id');
$inUseAlbums[] = Album::UNKNOWN_ID;
Album::whereNotIn('id', $inUseAlbums)->delete();

$inUseArtists = Album::select('artist_id')->groupBy('artist_id')->get()->lists('artist_id');
$inUseArtists[] = Artist::UNKNOWN_ID;
Artist::whereNotIn('id', $inUseArtists)->delete();
}
}
33 changes: 31 additions & 2 deletions app/Models/Song.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

use Illuminate\Database\Eloquent\Model;
use Lastfm;
use Media;

/**
* @property string path
Expand Down Expand Up @@ -49,7 +50,7 @@ public function playlists()
* Scrobble the song using Last.fm service.
*
* @param string $timestamp The UNIX timestamp in which the song started playing.
*
*
* @return mixed
*/
public function scrobble($timestamp)
Expand All @@ -73,6 +74,34 @@ public function scrobble($timestamp)
);
}

/**
* Get a Song record using its path.
*
* @param string $path
*
* @return Song|null
*/
public static function byPath($path)
{
return self::find(Media::getHash($path));
}

/**
* Scope a query to only include songs in a given directory.
*
* @param \Illuminate\Database\Eloquent\Builder $query
* @param string $path Full path of the directory
*
* @return \Illuminate\Database\Eloquent\Builder
*/
public function scopeInDirectory($query, $path)
{
// Make sure the path ends with a directory separator.
$path = rtrim(trim($path), DIRECTORY_SEPARATOR).DIRECTORY_SEPARATOR;

return $query->where('path', 'LIKE', "$path%");
}

/**
* Sometimes the tags extracted from getID3 are HTML entity encoded.
* This makes sure they are always sane.
Expand All @@ -87,7 +116,7 @@ public function setTitleAttribute($value)
/**
* Some songs don't have a title.
* Fall back to the file name (without extension) for such.
*
*
* @param $value
*
* @return string
Expand Down
5 changes: 5 additions & 0 deletions app/Providers/EventServiceProvider.php
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,14 @@ class EventServiceProvider extends ServiceProvider
'App\Events\SongLikeToggled' => [
'App\Listeners\LoveTrackOnLastfm',
],

'App\Events\SongStartedPlaying' => [
'App\Listeners\UpdateLastfmNowPlaying',
],

'App\Events\LibraryChanged' => [
'App\Listeners\TidyLibrary',
],
];

/**
Expand Down
Loading

0 comments on commit 46f6141

Please sign in to comment.