Add "added" timestamp to song database

- added is set to current time, if a new song is added to the database.
- GetAdded falls back to mtime.

Code for proxy plugin is missing, this needs a patch for libmpdclient.

closes #378
This commit is contained in:
jcorporation 2023-10-05 19:27:22 +02:00
parent 97da29cc90
commit 7bf43a9712
15 changed files with 86 additions and 0 deletions

View File

@ -77,6 +77,9 @@ song_print_info(Response &r, const LightSong &song, bool base) noexcept
if (!IsNegative(song.mtime)) if (!IsNegative(song.mtime))
time_print(r, "Last-Modified", song.mtime); time_print(r, "Last-Modified", song.mtime);
if (!IsNegative(song.added))
time_print(r, "Added", song.added);
if (song.audio_format.IsDefined()) if (song.audio_format.IsDefined())
r.Fmt(FMT_STRING("Format: {}\n"), song.audio_format); r.Fmt(FMT_STRING("Format: {}\n"), song.audio_format);
@ -100,6 +103,9 @@ song_print_info(Response &r, const DetachedSong &song, bool base) noexcept
if (!IsNegative(song.GetLastModified())) if (!IsNegative(song.GetLastModified()))
time_print(r, "Last-Modified", song.GetLastModified()); time_print(r, "Last-Modified", song.GetLastModified());
if (!IsNegative(song.GetAdded()))
time_print(r, "Added", song.GetAdded());
if (const auto &f = song.GetAudioFormat(); f.IsDefined()) if (const auto &f = song.GetAudioFormat(); f.IsDefined())
r.Fmt(FMT_STRING("Format: {}\n"), f); r.Fmt(FMT_STRING("Format: {}\n"), f);

View File

@ -22,6 +22,7 @@
#include <stdlib.h> #include <stdlib.h>
#define SONG_MTIME "mtime" #define SONG_MTIME "mtime"
#define SONG_ADDED "added"
#define SONG_END "song_end" #define SONG_END "song_end"
static void static void
@ -54,6 +55,10 @@ song_save(BufferedOutputStream &os, const Song &song)
if (!IsNegative(song.mtime)) if (!IsNegative(song.mtime))
os.Fmt(FMT_STRING(SONG_MTIME ": {}\n"), os.Fmt(FMT_STRING(SONG_MTIME ": {}\n"),
std::chrono::system_clock::to_time_t(song.mtime)); std::chrono::system_clock::to_time_t(song.mtime));
if (!IsNegative(song.added))
os.Fmt(FMT_STRING(SONG_ADDED ": {}\n"),
std::chrono::system_clock::to_time_t(song.added));
os.Write(SONG_END "\n"); os.Write(SONG_END "\n");
} }
@ -69,6 +74,10 @@ song_save(BufferedOutputStream &os, const DetachedSong &song)
if (!IsNegative(song.GetLastModified())) if (!IsNegative(song.GetLastModified()))
os.Fmt(FMT_STRING(SONG_MTIME ": {}\n"), os.Fmt(FMT_STRING(SONG_MTIME ": {}\n"),
std::chrono::system_clock::to_time_t(song.GetLastModified())); std::chrono::system_clock::to_time_t(song.GetLastModified()));
if (!IsNegative(song.GetAdded()))
os.Fmt(FMT_STRING(SONG_ADDED ": {}\n"),
std::chrono::system_clock::to_time_t(song.GetAdded()));
os.Write(SONG_END "\n"); os.Write(SONG_END "\n");
} }
@ -109,6 +118,8 @@ song_load(LineReader &file, const char *uri,
tag.SetHasPlaylist(StringIsEqual(value, "yes")); tag.SetHasPlaylist(StringIsEqual(value, "yes"));
} else if (StringIsEqual(line, SONG_MTIME)) { } else if (StringIsEqual(line, SONG_MTIME)) {
song.SetLastModified(std::chrono::system_clock::from_time_t(atoi(value))); song.SetLastModified(std::chrono::system_clock::from_time_t(atoi(value)));
} else if (StringIsEqual(line, SONG_ADDED)) {
song.SetAdded(std::chrono::system_clock::from_time_t(atoi(value)));
} else if (StringIsEqual(line, "Range")) { } else if (StringIsEqual(line, "Range")) {
char *endptr; char *endptr;

View File

@ -51,6 +51,9 @@ ParseSortTag(const char *s)
if (StringIsEqualIgnoreCase(s, "Last-Modified")) if (StringIsEqualIgnoreCase(s, "Last-Modified"))
return TagType(SORT_TAG_LAST_MODIFIED); return TagType(SORT_TAG_LAST_MODIFIED);
if (StringIsEqualIgnoreCase(s, "Added"))
return TagType(SORT_TAG_ADDED);
TagType tag = tag_name_parse_i(s); TagType tag = tag_name_parse_i(s);
if (tag == TAG_NUM_OF_ITEM_TYPES) if (tag == TAG_NUM_OF_ITEM_TYPES)
throw ProtocolError(ACK_ERROR_ARG, "Unknown sort tag"); throw ProtocolError(ACK_ERROR_ARG, "Unknown sort tag");

View File

@ -276,6 +276,9 @@ ParseSortTag(const char *s)
if (StringIsEqualIgnoreCase(s, "Last-Modified")) if (StringIsEqualIgnoreCase(s, "Last-Modified"))
return TagType(SORT_TAG_LAST_MODIFIED); return TagType(SORT_TAG_LAST_MODIFIED);
if (StringIsEqualIgnoreCase(s, "Added"))
return TagType(SORT_TAG_ADDED);
if (StringIsEqualIgnoreCase(s, "prio")) if (StringIsEqualIgnoreCase(s, "prio"))
return TagType(SORT_TAG_PRIO); return TagType(SORT_TAG_PRIO);

View File

@ -60,6 +60,13 @@ DatabaseVisitorHelper::Commit()
? a.GetLastModified() > b.GetLastModified() ? a.GetLastModified() > b.GetLastModified()
: a.GetLastModified() < b.GetLastModified(); : a.GetLastModified() < b.GetLastModified();
}); });
else if (sort == TagType(SORT_TAG_ADDED))
std::stable_sort(songs.begin(), songs.end(),
[descending](const DetachedSong &a, const DetachedSong &b){
return descending
? a.GetAdded() > b.GetAdded()
: a.GetAdded() < b.GetAdded();
});
else else
std::stable_sort(songs.begin(), songs.end(), std::stable_sort(songs.begin(), songs.end(),
[sort, descending](const DetachedSong &a, [sort, descending](const DetachedSong &a,

View File

@ -19,6 +19,7 @@ Song::Song(DetachedSong &&other, Directory &_parent) noexcept
filename(other.GetURI()), filename(other.GetURI()),
tag(std::move(other.WritableTag())), tag(std::move(other.WritableTag())),
mtime(other.GetLastModified()), mtime(other.GetLastModified()),
added(other.GetAdded()),
start_time(other.GetStartTime()), start_time(other.GetStartTime()),
end_time(other.GetEndTime()), end_time(other.GetEndTime()),
audio_format(other.GetAudioFormat()) audio_format(other.GetAudioFormat())
@ -117,6 +118,9 @@ Song::Export() const noexcept
dest.mtime = IsNegative(mtime) && target_song != nullptr dest.mtime = IsNegative(mtime) && target_song != nullptr
? target_song->mtime ? target_song->mtime
: mtime; : mtime;
dest.added = IsNegative(added) && target_song != nullptr
? target_song->added
: added;
dest.start_time = start_time.IsZero() && target_song != nullptr dest.start_time = start_time.IsZero() && target_song != nullptr
? target_song->start_time ? target_song->start_time
: start_time; : start_time;

View File

@ -56,6 +56,13 @@ struct Song : IntrusiveListHook<> {
std::chrono::system_clock::time_point mtime = std::chrono::system_clock::time_point mtime =
std::chrono::system_clock::time_point::min(); std::chrono::system_clock::time_point::min();
/**
* The time stamp when the file was added. A negative
* value means that this is unknown/unavailable.
*/
std::chrono::system_clock::time_point added =
std::chrono::system_clock::time_point::min();
/** /**
* Start of this sub-song within the file. * Start of this sub-song within the file.
*/ */

View File

@ -50,6 +50,7 @@ try {
} }
new_song->mark = true; new_song->mark = true;
new_song->added = std::chrono::system_clock::now();
{ {
const ScopeDatabaseLock protect; const ScopeDatabaseLock protect;

View File

@ -25,6 +25,7 @@ merge_song_metadata(DetachedSong &add, const DetachedSong &base) noexcept
} }
add.SetLastModified(base.GetLastModified()); add.SetLastModified(base.GetLastModified());
add.SetAdded(base.GetAdded());
if (add.GetStartTime().IsZero()) { if (add.GetStartTime().IsZero()) {
add.SetStartTime(base.GetStartTime()); add.SetStartTime(base.GetStartTime());

View File

@ -33,6 +33,7 @@ UpdatePlaylistSong(const Database &db, DetachedSong &song)
} }
song.SetLastModified(original->mtime); song.SetLastModified(original->mtime);
song.SetAdded(original->added);
song.SetTag(original->tag); song.SetTag(original->tag);
db.ReturnSong(original); db.ReturnSong(original);

View File

@ -127,6 +127,17 @@ PrintSortedQueue(Response &r, const Queue &queue,
return a.GetLastModified() < b.GetLastModified(); return a.GetLastModified() < b.GetLastModified();
}); });
else if (sort == TagType(SORT_TAG_ADDED))
std::stable_sort(v.begin(), v.end(),
[&queue, descending](unsigned a_pos, unsigned b_pos){
if (descending)
std::swap(a_pos, b_pos);
const auto &a = queue.Get(a_pos);
const auto &b = queue.Get(b_pos);
return a.GetAdded() < b.GetAdded();
});
else if (sort == TagType(SORT_TAG_PRIO)) else if (sort == TagType(SORT_TAG_PRIO))
std::stable_sort(v.begin(), v.end(), std::stable_sort(v.begin(), v.end(),
[&queue, descending](unsigned a_pos, unsigned b_pos){ [&queue, descending](unsigned a_pos, unsigned b_pos){

View File

@ -11,6 +11,7 @@ DetachedSong::DetachedSong(const LightSong &other) noexcept
real_uri(other.real_uri != nullptr ? other.real_uri : ""), real_uri(other.real_uri != nullptr ? other.real_uri : ""),
tag(other.tag), tag(other.tag),
mtime(other.mtime), mtime(other.mtime),
added(other.added),
start_time(other.start_time), start_time(other.start_time),
end_time(other.end_time), end_time(other.end_time),
audio_format(other.audio_format) {} audio_format(other.audio_format) {}
@ -21,6 +22,7 @@ DetachedSong::operator LightSong() const noexcept
result.directory = nullptr; result.directory = nullptr;
result.real_uri = real_uri.empty() ? nullptr : real_uri.c_str(); result.real_uri = real_uri.empty() ? nullptr : real_uri.c_str();
result.mtime = mtime; result.mtime = mtime;
result.added = added;
result.start_time = start_time; result.start_time = start_time;
result.end_time = end_time; result.end_time = end_time;
return result; return result;

View File

@ -7,6 +7,7 @@
#include "tag/Tag.hxx" #include "tag/Tag.hxx"
#include "pcm/AudioFormat.hxx" #include "pcm/AudioFormat.hxx"
#include "Chrono.hxx" #include "Chrono.hxx"
#include "time/ChronoUtil.hxx"
#include <chrono> #include <chrono>
#include <string> #include <string>
@ -58,6 +59,13 @@ class DetachedSong {
std::chrono::system_clock::time_point mtime = std::chrono::system_clock::time_point mtime =
std::chrono::system_clock::time_point::min(); std::chrono::system_clock::time_point::min();
/**
* The time stamp when the file was added to db. A negative
* value means that this is unknown/unavailable.
*/
std::chrono::system_clock::time_point added =
std::chrono::system_clock::time_point::min();
/** /**
* Start of this sub-song within the file. * Start of this sub-song within the file.
*/ */
@ -212,6 +220,16 @@ public:
mtime = _value; mtime = _value;
} }
std::chrono::system_clock::time_point GetAdded() const noexcept {
return IsNegative(added)
? mtime
: added;
}
void SetAdded(std::chrono::system_clock::time_point _value) noexcept {
added = _value;
}
SongTime GetStartTime() const noexcept { SongTime GetStartTime() const noexcept {
return start_time; return start_time;
} }

View File

@ -21,6 +21,11 @@
*/ */
#define SORT_TAG_PRIO (TAG_NUM_OF_ITEM_TYPES + 4) #define SORT_TAG_PRIO (TAG_NUM_OF_ITEM_TYPES + 4)
/**
* Special value for the db_selection_print() sort parameter.
*/
#define SORT_TAG_ADDED (TAG_NUM_OF_ITEM_TYPES + 5)
enum TagType : uint8_t; enum TagType : uint8_t;
struct LightSong; struct LightSong;

View File

@ -51,6 +51,12 @@ struct LightSong {
*/ */
std::chrono::system_clock::time_point mtime = std::chrono::system_clock::time_point::min(); std::chrono::system_clock::time_point mtime = std::chrono::system_clock::time_point::min();
/**
* The time stamp when the file was added. A negative
* value means that this is unknown/unavailable.
*/
std::chrono::system_clock::time_point added = std::chrono::system_clock::time_point::min();
/** /**
* Start of this sub-song within the file. * Start of this sub-song within the file.
*/ */