Skip to content

Commit

Permalink
Change recording identifier values to work around Kodi issue
Browse files Browse the repository at this point in the history
  • Loading branch information
djp952 committed Aug 30, 2019
1 parent dacff2d commit fa9cc13
Show file tree
Hide file tree
Showing 3 changed files with 76 additions and 14 deletions.
4 changes: 3 additions & 1 deletion pvr.hdhomerundvr/changelog.txt
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
v2.2.2 (2019.08.xx)
v2.2.2 (2019.08.30)
- Restore shortened channel group names "HD channels, "SD channels", "Favorite channels", and "Demo channels"
- Treat "News" Recorded TV category mapping as "Series"; avoids creation of "News" folder in Recorded TV
- Change recording identifier values to work around Kodi issue where attempting to play a recording multiple times may fail

v2.2.1 (2019.08.02)
- Add Recorded TV category mapping for "News"
Expand Down
33 changes: 20 additions & 13 deletions src/database.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -344,7 +344,7 @@ void delete_recording(sqlite3* instance, char const* recordingid, bool rerecord)
"replace into recording select deviceid, discovered, "
"(select case when fullkey is null then recording.data else json_remove(recording.data, fullkey) end) as data "
"from httprequest, recording, "
"(select fullkey from recording, json_each(recording.data) as entry where json_extract(entry.value, '$.CmdURL') like ?1 limit 1)",
"(select fullkey from recording, json_each(recording.data) as entry where get_recording_id(json_extract(entry.value, '$.CmdURL')) like ?1 limit 1)",
recordingid, (rerecord) ? 1 : 0);
}

Expand Down Expand Up @@ -1571,7 +1571,7 @@ void enumerate_recordings(sqlite3* instance, bool episodeastitle, bool ignorecat

// recordingid | title | episodename | firstairing | originalairdate | seriesnumber | episodenumber | year | streamurl | directory | plot | channelname | thumbnailpath | recordingtime | duration | lastposition | channelid
auto sql = "select "
"json_extract(value, '$.CmdURL') as recordingid, "
"get_recording_id(json_extract(value, '$.CmdURL')) as recordingid, "
"case when ?1 then coalesce(json_extract(value, '$.EpisodeNumber'), json_extract(value, '$.Title')) else json_extract(value, '$.Title') end as title, "
"json_extract(value, '$.EpisodeTitle') as episodename, "
"coalesce(json_extract(value, '$.FirstAiring'), 0) as firstairing, "
Expand Down Expand Up @@ -1601,7 +1601,8 @@ void enumerate_recordings(sqlite3* instance, bool episodeastitle, bool ignorecat
if(result != SQLITE_OK) throw sqlite_exception(result);

// Execute the query and iterate over all returned rows
while(sqlite3_step(statement) == SQLITE_ROW) {
result = sqlite3_step(statement);
while(result == SQLITE_ROW) {

struct recording item;
item.recordingid = reinterpret_cast<char const*>(sqlite3_column_text(statement, 0));
Expand All @@ -1622,9 +1623,13 @@ void enumerate_recordings(sqlite3* instance, bool episodeastitle, bool ignorecat
item.lastposition = sqlite3_column_int(statement, 15);
item.channelid.value = static_cast<unsigned int>(sqlite3_column_int(statement, 16));

callback(item); // Invoke caller-supplied callback
callback(item); // Invoke caller-supplied callback
result = sqlite3_step(statement); // Move to the next row in the result set
}

// If the final result of the query was not SQLITE_DONE, something bad happened
if(result != SQLITE_DONE) throw sqlite_exception(result, sqlite3_errmsg(instance));

sqlite3_finalize(statement); // Finalize the SQLite statement
}

Expand Down Expand Up @@ -2228,7 +2233,7 @@ std::string get_recording_filename(sqlite3* instance, char const* recordingid, b
"else rtrim(clean_filename(json_extract(value, '$.Title')), ' .') end || '/' end || clean_filename(json_extract(value, '$.Title')) || ' ' || "
"coalesce(json_extract(value, '$.EpisodeNumber') || ' ', '') || coalesce(strftime('%Y%m%d', datetime(json_extract(value, '$.OriginalAirdate'), 'unixepoch')) || ' ', '') || "
"'[' || strftime('%Y%m%d-%H%M', datetime(json_extract(value, '$.StartTime'), 'unixepoch')) || ']' as filename "
"from recording, json_each(recording.data) where json_extract(value, '$.CmdURL') like ?2 limit 1";
"from recording, json_each(recording.data) where get_recording_id(json_extract(value, '$.CmdURL')) like ?2 limit 1";

result = sqlite3_prepare_v2(instance, sql, -1, &statement, nullptr);
if(result != SQLITE_OK) throw sqlite_exception(result, sqlite3_errmsg(instance));
Expand Down Expand Up @@ -2280,8 +2285,8 @@ int get_recording_lastposition(sqlite3* instance, char const* recordingid)
// Prepare a scalar result query to get the last played position of the recording from the storage engine. Limit the amount
// of JSON that needs to be sifted through by specifically asking for the series that this recording belongs to
auto sql = "with httprequest(response) as (select http_get(json_extract(device.data, '$.StorageURL') || '?SeriesID=' || "
"(select json_extract(value, '$.SeriesID') as seriesid from recording, json_each(recording.data) where json_extract(value, '$.CmdURL') like ?1)) from device where device.type = 'storage') "
"select coalesce(json_extract(entry.value, '$.Resume'), 0) as resume from httprequest, json_each(httprequest.response) as entry where json_extract(entry.value, '$.CmdURL') like ?1 limit 1";
"(select json_extract(value, '$.SeriesID') as seriesid from recording, json_each(recording.data) where get_recording_id(json_extract(value, '$.CmdURL')) like ?1)) from device where device.type = 'storage') "
"select coalesce(json_extract(entry.value, '$.Resume'), 0) as resume from httprequest, json_each(httprequest.response) as entry where get_recording_id(json_extract(entry.value, '$.CmdURL')) like ?1 limit 1";

result = sqlite3_prepare_v2(instance, sql, -1, &statement, nullptr);
if(result != SQLITE_OK) throw sqlite_exception(result, sqlite3_errmsg(instance));
Expand Down Expand Up @@ -2325,7 +2330,7 @@ std::string get_recording_stream_url(sqlite3* instance, char const* recordingid)

// Prepare a scalar result query to generate a stream URL for the specified recording
auto sql = "select json_extract(value, '$.PlayURL') as streamurl "
"from recording, json_each(recording.data) where json_extract(value, '$.CmdURL') like ?1";
"from recording, json_each(recording.data) where get_recording_id(json_extract(value, '$.CmdURL')) like ?1";

result = sqlite3_prepare_v2(instance, sql, -1, &statement, nullptr);
if(result != SQLITE_OK) throw sqlite_exception(result, sqlite3_errmsg(instance));
Expand Down Expand Up @@ -2919,12 +2924,14 @@ void set_recording_lastposition(sqlite3* instance, char const* recordingid, int
{
if((instance == nullptr) || (recordingid == nullptr)) return;

// Update the specified recording on the storage device and in the local database
execute_non_query(instance, "with httprequest(response) as (select http_post(?1 || '&cmd=set&Resume=' || ?2, null)) "
"replace into recording select deviceid, discovered, "
// Update the specified recording on the storage device
execute_non_query(instance, "select http_post(json_extract(entry.value, '$.CmdURL') || '&cmd=set&Resume=' || ?2, null) from recording, json_each(recording.data) as entry "
"where get_recording_id(json_extract(entry.value, '$.CmdURL')) like ?1 limit 1", recordingid, lastposition);

// Update the specified recording in the local database
execute_non_query(instance, "replace into recording select deviceid, discovered, "
"(select case when fullkey is null then recording.data else json_set(recording.data, fullkey || '.Resume', ?2) end) as data "
"from httprequest, recording, "
"(select fullkey from recording, json_each(recording.data) as entry where json_extract(entry.value, '$.CmdURL') like ?1 limit 1)",
"from recording, (select fullkey from recording, json_each(recording.data) as entry where get_recording_id(json_extract(entry.value, '$.CmdURL')) like ?1 limit 1)",
recordingid, lastposition);
}

Expand Down
53 changes: 53 additions & 0 deletions src/dbextension.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -816,6 +816,54 @@ void get_episode_number(sqlite3_context* context, int argc, sqlite3_value** argv
return sqlite3_result_int(context, -1);
}

//---------------------------------------------------------------------------
// get_recording_id
//
// SQLite scalar function to read the recording ID from a command URL
//
// Arguments:
//
// context - SQLite context object
// argc - Number of supplied arguments
// argv - Argument values

void get_recording_id(sqlite3_context* context, int argc, sqlite3_value** argv)
{
if((argc != 1) || (argv[0] == nullptr)) return sqlite3_result_error(context, "invalid argument", -1);

// This function accepts the command URL for the recording so that the identifier can be extracted
const char* url = reinterpret_cast<const char*>(sqlite3_value_text(argv[0]));
if((url == nullptr) || (*url == 0)) return sqlite3_result_error(context, "url argument is null or zero-length", -1);

// Allocate a CURLU instance to parse the command URL components
CURLU* curlu = curl_url();
if(curlu == nullptr) return sqlite3_result_error(context, "insufficient memory to allocate CURLU instance", -1);

// Apply the command URL to the CURLU instance
CURLUcode curluresult = curl_url_set(curlu, CURLUPart::CURLUPART_URL, url, 0);
if(curluresult == CURLUE_OK) {

// We are interested in the query string portion of the CmdURL
char* querystring = nullptr;
curluresult = curl_url_get(curlu, CURLUPART_QUERY, &querystring, 0);
if(curluresult == CURLUE_OK) {

// The query string must start with "id=", use the rest as-is. This will be OK for now, but
// a more robust solution would be parsing the entire query string and selecting just the id key/value
if(strncasecmp(querystring, "id=", 3) == 0) sqlite3_result_text(context, &querystring[3], -1, SQLITE_TRANSIENT);
else sqlite3_result_error(context, "unable to extract recording id from specified url", -1);

curl_free(querystring); // Release the allocated query string
}

else sqlite3_result_error(context, "unable to extract query string from specified url", -1);
}

else sqlite3_result_error(context, "unable to parse supplied url", -1);

curl_url_cleanup(curlu); // Release the CURLU instance
}

//---------------------------------------------------------------------------
// get_season_number
//
Expand Down Expand Up @@ -1079,6 +1127,11 @@ extern "C" int sqlite3_extension_init(sqlite3 *db, char** errmsg, const sqlite3_
result = sqlite3_create_function_v2(db, "get_episode_number", 1, SQLITE_UTF8 | SQLITE_DETERMINISTIC, nullptr, get_episode_number, nullptr, nullptr, nullptr);
if(result != SQLITE_OK) { *errmsg = sqlite3_mprintf("Unable to register scalar function get_episode_number"); return result; }

// get_recording_id function
//
result = sqlite3_create_function_v2(db, "get_recording_id", 1, SQLITE_UTF8 | SQLITE_DETERMINISTIC, nullptr, get_recording_id, nullptr, nullptr, nullptr);
if(result != SQLITE_OK) { *errmsg = sqlite3_mprintf("Unable to register scalar function get_recording_id"); return result; }

// get_season_number function
//
result = sqlite3_create_function_v2(db, "get_season_number", 1, SQLITE_UTF8 | SQLITE_DETERMINISTIC, nullptr, get_season_number, nullptr, nullptr, nullptr);
Expand Down

0 comments on commit fa9cc13

Please sign in to comment.