2016-12-17 16:59:59 +01:00
< ? php
namespace App\Services ;
2017-01-06 09:40:27 +01:00
use App\AlternativeTitle ;
2017-02-02 17:38:07 +01:00
use App\Item ;
2017-01-25 14:44:51 +01:00
use App\Services\Models\EpisodeService ;
2017-01-23 22:26:04 +01:00
use App\Services\Models\ItemService ;
2017-01-06 13:00:18 +01:00
use App\Setting ;
use Carbon\Carbon ;
2017-02-07 16:16:06 +01:00
use GuzzleHttp\Client ;
2017-01-31 10:03:58 +01:00
use Illuminate\Support\Facades\DB ;
use Symfony\Component\HttpFoundation\Response ;
2016-12-17 16:59:59 +01:00
class FileParser {
2017-01-25 14:44:51 +01:00
const ADDED = 'added' ;
const REMOVED = 'removed' ;
2017-01-27 16:06:18 +01:00
const UPDATED = 'updated' ;
2017-02-21 19:15:31 +01:00
// [field in local file => field in database]
2017-02-02 17:38:07 +01:00
const SUPPORTED_FIELDS = [ 'src' => 'src' , 'subtitles' => 'subtitles' , 'name' => 'fp_name' ];
2017-01-25 14:44:51 +01:00
private $itemService ;
private $episodeService ;
2017-01-06 09:40:27 +01:00
private $tmdb ;
private $alternativeTitle ;
private $itemCategory ;
2017-02-07 16:16:06 +01:00
private $client ;
2016-12-17 16:59:59 +01:00
2017-01-23 22:26:04 +01:00
public function __construct (
2017-01-25 14:44:51 +01:00
ItemService $itemService ,
EpisodeService $episodeService ,
2017-01-23 22:26:04 +01:00
TMDB $tmdb ,
2017-02-07 16:16:06 +01:00
AlternativeTitle $alternativeTitle ,
Client $client
2017-01-23 22:26:04 +01:00
){
2017-01-25 14:44:51 +01:00
$this -> itemService = $itemService ;
$this -> episodeService = $episodeService ;
2017-01-06 09:40:27 +01:00
$this -> tmdb = $tmdb ;
$this -> alternativeTitle = $alternativeTitle ;
2017-02-07 16:16:06 +01:00
$this -> client = $client ;
2016-12-17 16:59:59 +01:00
}
2017-01-06 09:40:27 +01:00
/**
* Make a request to flox - file - parser and get local files data .
*
* @ return array
*/
public function fetch ()
2016-12-17 16:59:59 +01:00
{
2017-02-07 16:16:06 +01:00
$timestamp = $this -> lastFetched ();
$fpUrl = config ( 'services.fp.host' ) . ':' . config ( 'services.fp.port' );
$fpUri = '/fetch/' . $timestamp ;
$response = $this -> client -> get ( $fpUrl . $fpUri );
return json_decode ( $response -> getBody ());
2016-12-17 16:59:59 +01:00
}
2017-01-06 09:40:27 +01:00
/**
2017-01-25 14:44:51 +01:00
* Loop over all local files .
2017-01-06 09:40:27 +01:00
*
* @ param $files
2017-01-31 10:03:58 +01:00
* @ return \Illuminate\Http\JsonResponse
2017-01-06 09:40:27 +01:00
*/
2017-01-25 14:44:51 +01:00
public function updateDatabase ( $files )
2016-12-17 16:59:59 +01:00
{
2017-01-31 10:03:58 +01:00
DB :: beginTransaction ();
2017-02-07 16:16:06 +01:00
$this -> updateLastFetched ();
2017-01-31 10:03:58 +01:00
2017-02-19 01:55:33 +01:00
foreach (( array ) $files as $type => $items ) {
2017-01-06 09:40:27 +01:00
$this -> itemCategory = $type ;
2016-12-17 16:59:59 +01:00
2017-01-06 09:40:27 +01:00
foreach ( $items as $item ) {
2017-01-31 10:03:58 +01:00
try {
$this -> handleStatus ( $item );
} catch ( \Exception $e ) {
return response () -> json ( $e -> getMessage (), Response :: HTTP_BAD_REQUEST );
}
2017-01-25 14:44:51 +01:00
}
}
2017-01-31 10:03:58 +01:00
DB :: commit ();
2017-01-25 14:44:51 +01:00
}
2017-01-06 09:40:27 +01:00
2017-01-25 14:44:51 +01:00
/**
* Check which status the file has .
2017-01-31 10:03:58 +01:00
* If we can ' t handle the status , throw an exception .
2017-01-25 14:44:51 +01:00
*
* @ param $item
* @ return bool | mixed | void
*/
private function handleStatus ( $item )
{
switch ( $item -> status ) {
case self :: ADDED :
2017-01-26 16:14:32 +01:00
return $this -> validateStore ( $item );
2017-01-27 16:06:18 +01:00
case self :: UPDATED :
return $this -> validateUpdate ( $item );
2017-01-25 14:44:51 +01:00
case self :: REMOVED :
2017-01-26 16:14:32 +01:00
return $this -> remove ( $item );
2017-01-31 10:03:58 +01:00
default :
2017-02-02 17:38:07 +01:00
$this -> abortParser ( $item );
2017-01-25 14:44:51 +01:00
}
}
2017-01-06 09:40:27 +01:00
2017-01-25 14:44:51 +01:00
/**
2017-02-13 18:47:30 +01:00
* See if it can find the item in our database , filtered by media_type .
2017-02-02 17:38:07 +01:00
* Otherwise search in TMDb or create an empty item .
2017-01-25 14:44:51 +01:00
*
* @ param $item
* @ return bool | mixed
*/
2017-01-26 16:14:32 +01:00
private function validateStore ( $item )
2017-01-25 14:44:51 +01:00
{
// See if file is already in our database.
2017-02-13 18:47:30 +01:00
if ( $found = $this -> itemService -> findBy ( 'title_strict' , $item -> name , $this -> itemCategory )) {
2017-01-26 16:14:32 +01:00
return $this -> store ( $item , $found -> tmdb_id );
2017-01-06 09:40:27 +01:00
}
2017-01-25 14:44:51 +01:00
// Otherwise make a new TMDb request.
2017-02-02 17:38:07 +01:00
$found = $this -> searchTmdb ( $item );
if ( ! $found ) {
// Create an empty item if nothing is found.
return $this -> createEmptyItem ( $item );
}
return $this -> findOrCreateItem ( $found , $item );
2017-01-27 16:06:18 +01:00
}
/**
2017-02-13 18:47:30 +01:00
* See if it can find the item in our database , filtered by media_type .
2017-02-02 17:38:07 +01:00
* Check if it is an empty item and search against TMDb and update them .
* Otherwise create an empty item .
2017-01-27 16:06:18 +01:00
*
* @ param $item
* @ return mixed
*/
private function validateUpdate ( $item )
{
// See if file is already in our database.
2017-02-21 19:15:31 +01:00
if ( $found = $this -> findItemByFPName ( $item )) {
2017-02-02 17:38:07 +01:00
if ( ! $found -> tmdb_id ) {
return $this -> searchTmdbAndUpdateEmptyItem ( $found , $item );
}
return $this -> update ( $item , $found -> tmdb_id );
2017-01-27 16:06:18 +01:00
}
2017-02-02 17:38:07 +01:00
// Create an empty item if nothing is found.
return $this -> createEmptyItem ( $item );
}
/**
* If result was found in TMDb , remove empty item and re - create from TMDb .
* Otherwise remove and re - create empty item .
*
* @ param $emptyItem
* @ param $file
* @ return Item | mixed
*/
private function searchTmdbAndUpdateEmptyItem ( $emptyItem , $file )
{
$found = $this -> searchTmdb ( $file );
// Remove the empty item, because we create a new empty or from TMDb.
$this -> itemService -> remove ( $emptyItem -> id );
if ( ! $found ) {
return $this -> createEmptyItem ( $file );
}
// Create a new item with TMDb specific values.
$created = $this -> itemService -> create ( $found );
// We are searching for the changed name (if available) in the next iteration.
if ( $this -> itemCategory == 'tv' ) {
$created -> update ([ 'fp_name' => $this -> getFileName ( $file )]);
}
2017-01-27 16:06:18 +01:00
2017-02-02 17:38:07 +01:00
// Update FP specific values.
return $this -> update ( $file , $created -> tmdb_id );
2017-01-06 09:40:27 +01:00
}
/**
2017-02-02 17:38:07 +01:00
* Make a new request to TMDb and check against the database .
* Otherwise create a new item .
2017-01-06 09:40:27 +01:00
*
* @ param $item
2017-01-23 22:26:04 +01:00
* @ return bool | mixed
2017-01-06 09:40:27 +01:00
*/
2017-02-02 17:38:07 +01:00
private function searchTmdb ( $item )
2017-01-06 09:40:27 +01:00
{
2017-02-13 18:47:30 +01:00
$found = $this -> tmdb -> search ( $this -> getFileName ( $item ), $this -> itemCategory );
2017-01-06 09:40:27 +01:00
2017-02-02 17:38:07 +01:00
if ( ! $found ) {
2017-01-06 09:40:27 +01:00
return false ;
}
2017-02-02 17:38:07 +01:00
return $found [ 0 ];
}
/**
* If TMDb can ' t find anything , create a simple item with data from local file .
*
* @ param $item
* @ return mixed
*/
private function createEmptyItem ( $item )
{
return $this -> itemService -> createEmpty ( $item , $this -> itemCategory );
2017-01-06 09:40:27 +01:00
}
/**
* Check tmdb_id against the database or create a new item .
*
* @ param $firstResult
* @ param $item
2017-01-25 14:44:51 +01:00
* @ return mixed
2017-01-06 09:40:27 +01:00
*/
private function findOrCreateItem ( $firstResult , $item )
{
$tmdbId = $firstResult [ 'tmdb_id' ];
// Check against our database.
2017-01-25 14:44:51 +01:00
if ( $this -> itemService -> findBy ( 'tmdb_id' , $tmdbId )) {
2017-01-26 16:14:32 +01:00
return $this -> store ( $item , $tmdbId );
2017-01-06 09:40:27 +01:00
}
// Otherwise create a new item from the result.
2017-01-25 14:44:51 +01:00
$created = $this -> itemService -> create ( $firstResult );
2017-01-06 09:40:27 +01:00
2017-01-26 16:14:32 +01:00
return $this -> store ( $item , $created -> tmdb_id );
2017-01-06 09:40:27 +01:00
}
/**
2017-01-27 16:06:18 +01:00
* Store current supported fields from local file into our database .
2017-01-06 09:40:27 +01:00
*
* @ param $item
2017-01-25 14:44:51 +01:00
* @ param $tmdbId
2017-01-06 09:40:27 +01:00
* @ return mixed
*/
2017-01-26 16:14:32 +01:00
private function store ( $item , $tmdbId )
2017-01-06 09:40:27 +01:00
{
2017-01-27 16:06:18 +01:00
if ( $model = $this -> findItem ( $item , $tmdbId )) {
2017-02-02 17:38:07 +01:00
foreach ( self :: SUPPORTED_FIELDS as $fromFile => $toDatabase ) {
$model -> { $toDatabase } = $item -> { $fromFile };
2017-01-27 16:06:18 +01:00
}
2017-01-25 14:44:51 +01:00
2017-02-02 17:38:07 +01:00
return $model -> save ();
2017-01-27 16:06:18 +01:00
}
}
/**
* Iterate over all changed properties and update them in our database .
*
* @ param $item
2017-02-21 19:15:31 +01:00
* @ param $tmdbId
2017-01-27 16:06:18 +01:00
* @ return mixed
*/
2017-02-02 17:38:07 +01:00
private function update ( $item , $tmdbId )
2017-01-27 16:06:18 +01:00
{
2017-02-02 17:38:07 +01:00
if ( $model = $this -> findItem ( $item , $tmdbId )) {
2017-02-21 19:15:31 +01:00
// Remove all fields, so we can start from scratch.
$this -> remove ( $item );
2017-02-02 17:38:07 +01:00
foreach ( $item -> changed as $field => $value ) {
if ( array_key_exists ( $field , self :: SUPPORTED_FIELDS )) {
$model -> { self :: SUPPORTED_FIELDS [ $field ]} = $value ;
}
2017-01-27 16:06:18 +01:00
}
2017-02-02 17:38:07 +01:00
return $model -> save ();
}
2017-01-06 09:40:27 +01:00
}
/**
2017-01-27 16:06:18 +01:00
* Reset all supported fields for local file from our database .
2017-01-06 09:40:27 +01:00
*
* @ param $item
2017-01-25 14:44:51 +01:00
* @ return mixed
2017-01-06 09:40:27 +01:00
*/
2017-01-26 16:14:32 +01:00
private function remove ( $item )
2017-01-25 14:44:51 +01:00
{
2017-01-27 16:06:18 +01:00
if ( $model = $this -> findItemBySrc ( $item )) {
2017-02-02 17:38:07 +01:00
foreach ( self :: SUPPORTED_FIELDS as $fromFile => $toDatabase ) {
$model -> { $toDatabase } = null ;
2017-01-27 16:06:18 +01:00
}
2017-01-25 14:44:51 +01:00
2017-01-27 16:06:18 +01:00
$model -> save ();
2017-01-25 14:44:51 +01:00
}
}
2017-01-31 10:03:58 +01:00
/**
* Cancel the complete fetch and make a rollback of fetched files .
*
* @ param $item
* @ throws \Exception
*/
private function abortParser ( $item )
{
DB :: rollBack ();
$itemAsString = json_encode ( $item );
throw new \Exception ( " Failed to parse file ' $item->name ' with status ' $item->status '. Please open an issue: https://github.com/devfake/flox/issues and include the following content: \n \n $itemAsString " );
}
2017-01-25 14:44:51 +01:00
/**
* @ param $item
* @ param $tmdbId
* @ return \Illuminate\Support\Collection | mixed
*/
private function findItem ( $item , $tmdbId )
2017-01-06 09:40:27 +01:00
{
if ( $this -> itemCategory == 'tv' ) {
2017-01-25 14:44:51 +01:00
return $this -> episodeService -> findBy ( 'episode' , $tmdbId , $item );
2017-01-06 09:40:27 +01:00
}
2017-01-25 14:44:51 +01:00
return $this -> itemService -> findBy ( 'tmdb_id' , $tmdbId );
}
2017-02-21 19:15:31 +01:00
/**
* @ param $item
* @ return mixed
*/
private function findItemByFPName ( $item )
{
$found = $this -> itemService -> findBy ( 'fp_name' , $item , $this -> itemCategory );
// Search against episodes if no empty item for a tv show was found.
if ( ! $found && $this -> itemCategory == 'tv' ) {
$episode = $this -> episodeService -> findBy ( 'fp_name' , $item );
if ( $episode ) {
$found = $this -> itemService -> findBy ( 'tmdb_id' , $episode -> tmdb_id );
}
}
return $found ;
}
2017-01-25 14:44:51 +01:00
/**
* @ param $item
* @ return \Illuminate\Support\Collection | mixed
*/
private function findItemBySrc ( $item )
{
if ( $this -> itemCategory == 'tv' ) {
return $this -> episodeService -> findBy ( 'src' , $item -> src );
}
2017-02-21 19:15:31 +01:00
2017-01-25 14:44:51 +01:00
return $this -> itemService -> findBy ( 'src' , $item -> src );
2016-12-17 16:59:59 +01:00
}
2017-01-06 13:00:18 +01:00
2017-02-02 17:38:07 +01:00
/**
* @ param $file
* @ return string
*/
private function getFileName ( $file )
{
return isset ( $file -> changed -> name ) ? $file -> changed -> name : $file -> name ;
}
2017-01-06 13:00:18 +01:00
/**
* Update last time we fetched flox - file - parser .
*/
2017-02-07 16:16:06 +01:00
private function updateLastFetched ()
2017-01-06 13:00:18 +01:00
{
Setting :: first () -> update ([
'last_fetch_to_file_parser' => Carbon :: now (),
]);
}
2017-02-07 16:16:06 +01:00
/**
* @ return mixed
*/
private function lastFetched ()
{
$lastFetch = Setting :: first () -> last_fetch_to_file_parser ;
return $lastFetch ? $lastFetch -> getTimestamp () : 0 ;
}
2017-01-24 16:11:47 +01:00
}