Skip to content
Projects
Groups
Snippets
Help
This project
Loading...
Sign in / Register
Toggle navigation
M
mpd
Project
Project
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Registry
Registry
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
Иван Мажукин
mpd
Commits
77c9081f
Commit
77c9081f
authored
Apr 24, 2019
by
Max Kellermann
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
sticker/Database: wrap in class StickerDatabase
parent
c88d5616
Show whitespace changes
Inline
Side-by-side
Showing
9 changed files
with
218 additions
and
188 deletions
+218
-188
Instance.cxx
src/Instance.cxx
+2
-2
Instance.hxx
src/Instance.hxx
+11
-0
Main.cxx
src/Main.cxx
+11
-13
AllCommands.cxx
src/command/AllCommands.cxx
+2
-1
StickerCommands.cxx
src/command/StickerCommands.cxx
+20
-9
Database.cxx
src/sticker/Database.cxx
+87
-107
Database.hxx
src/sticker/Database.hxx
+55
-35
SongSticker.cxx
src/sticker/SongSticker.cxx
+18
-14
SongSticker.hxx
src/sticker/SongSticker.hxx
+12
-7
No files found.
src/Instance.cxx
View file @
77c9081f
...
@@ -107,9 +107,9 @@ Instance::OnDatabaseSongRemoved(const char *uri) noexcept
...
@@ -107,9 +107,9 @@ Instance::OnDatabaseSongRemoved(const char *uri) noexcept
#ifdef ENABLE_SQLITE
#ifdef ENABLE_SQLITE
/* if the song has a sticker, remove it */
/* if the song has a sticker, remove it */
if
(
sticker_enabled
())
{
if
(
HasStickerDatabase
())
{
try
{
try
{
sticker_song_delete
(
uri
);
sticker_song_delete
(
*
sticker_database
,
uri
);
}
catch
(...)
{
}
catch
(...)
{
}
}
}
}
...
...
src/Instance.hxx
View file @
77c9081f
...
@@ -53,6 +53,7 @@ class ClientList;
...
@@ -53,6 +53,7 @@ class ClientList;
struct
Partition
;
struct
Partition
;
class
StateFile
;
class
StateFile
;
class
RemoteTagCache
;
class
RemoteTagCache
;
class
StickerDatabase
;
/**
/**
* A utility class which, when used as the first base class, ensures
* A utility class which, when used as the first base class, ensures
...
@@ -125,6 +126,10 @@ struct Instance final
...
@@ -125,6 +126,10 @@ struct Instance final
StateFile
*
state_file
=
nullptr
;
StateFile
*
state_file
=
nullptr
;
#ifdef ENABLE_SQLITE
std
::
unique_ptr
<
StickerDatabase
>
sticker_database
;
#endif
Instance
();
Instance
();
~
Instance
()
noexcept
;
~
Instance
()
noexcept
;
...
@@ -166,6 +171,12 @@ struct Instance final
...
@@ -166,6 +171,12 @@ struct Instance final
const
Database
&
GetDatabaseOrThrow
()
const
;
const
Database
&
GetDatabaseOrThrow
()
const
;
#endif
#endif
#ifdef ENABLE_SQLITE
bool
HasStickerDatabase
()
noexcept
{
return
sticker_database
!=
nullptr
;
}
#endif
void
BeginShutdownUpdate
()
noexcept
;
void
BeginShutdownUpdate
()
noexcept
;
#ifdef ENABLE_CURL
#ifdef ENABLE_CURL
...
...
src/Main.cxx
View file @
77c9081f
...
@@ -237,23 +237,23 @@ InitDatabaseAndStorage(const ConfigData &config)
...
@@ -237,23 +237,23 @@ InitDatabaseAndStorage(const ConfigData &config)
#endif
#endif
#ifdef ENABLE_SQLITE
/**
/**
* Configure and initialize the sticker subsystem.
* Configure and initialize the sticker subsystem.
*/
*/
static
void
static
std
::
unique_ptr
<
StickerDatabase
>
glue_sticker_init
(
const
ConfigData
&
config
)
LoadStickerDatabase
(
const
ConfigData
&
config
)
{
{
#ifdef ENABLE_SQLITE
auto
sticker_file
=
config
.
GetPath
(
ConfigOption
::
STICKER_FILE
);
auto
sticker_file
=
config
.
GetPath
(
ConfigOption
::
STICKER_FILE
);
if
(
sticker_file
.
IsNull
())
if
(
sticker_file
.
IsNull
())
return
;
return
nullptr
;
sticker_global_init
(
std
::
move
(
sticker_file
));
return
std
::
make_unique
<
StickerDatabase
>
(
std
::
move
(
sticker_file
));
#else
(
void
)
config
;
#endif
}
}
#endif
static
void
static
void
glue_state_file_init
(
const
ConfigData
&
raw_config
)
glue_state_file_init
(
const
ConfigData
&
raw_config
)
{
{
...
@@ -513,7 +513,9 @@ mpd_main_after_fork(const ConfigData &raw_config, const Config &config)
...
@@ -513,7 +513,9 @@ mpd_main_after_fork(const ConfigData &raw_config, const Config &config)
const
bool
create_db
=
InitDatabaseAndStorage
(
raw_config
);
const
bool
create_db
=
InitDatabaseAndStorage
(
raw_config
);
#endif
#endif
glue_sticker_init
(
raw_config
);
#ifdef ENABLE_SQLITE
instance
->
sticker_database
=
LoadStickerDatabase
(
raw_config
);
#endif
command_init
();
command_init
();
...
@@ -625,10 +627,6 @@ mpd_main_after_fork(const ConfigData &raw_config, const Config &config)
...
@@ -625,10 +627,6 @@ mpd_main_after_fork(const ConfigData &raw_config, const Config &config)
}
}
#endif
#endif
#ifdef ENABLE_SQLITE
sticker_global_finish
();
#endif
return
EXIT_SUCCESS
;
return
EXIT_SUCCESS
;
}
}
...
...
src/command/AllCommands.cxx
View file @
77c9081f
...
@@ -38,6 +38,7 @@
...
@@ -38,6 +38,7 @@
#include "Permission.hxx"
#include "Permission.hxx"
#include "tag/Type.h"
#include "tag/Type.h"
#include "Partition.hxx"
#include "Partition.hxx"
#include "Instance.hxx"
#include "client/Client.hxx"
#include "client/Client.hxx"
#include "client/Response.hxx"
#include "client/Response.hxx"
#include "util/Macros.hxx"
#include "util/Macros.hxx"
...
@@ -216,7 +217,7 @@ command_available(gcc_unused const Partition &partition,
...
@@ -216,7 +217,7 @@ command_available(gcc_unused const Partition &partition,
{
{
#ifdef ENABLE_SQLITE
#ifdef ENABLE_SQLITE
if
(
StringIsEqual
(
cmd
->
cmd
,
"sticker"
))
if
(
StringIsEqual
(
cmd
->
cmd
,
"sticker"
))
return
sticker_enabled
();
return
partition
.
instance
.
HasStickerDatabase
();
#endif
#endif
#ifdef ENABLE_NEIGHBOR_PLUGINS
#ifdef ENABLE_NEIGHBOR_PLUGINS
...
...
src/command/StickerCommands.cxx
View file @
77c9081f
...
@@ -28,6 +28,7 @@
...
@@ -28,6 +28,7 @@
#include "client/Client.hxx"
#include "client/Client.hxx"
#include "client/Response.hxx"
#include "client/Response.hxx"
#include "Partition.hxx"
#include "Partition.hxx"
#include "Instance.hxx"
#include "util/StringAPI.hxx"
#include "util/StringAPI.hxx"
#include "util/ScopeExit.hxx"
#include "util/ScopeExit.hxx"
...
@@ -50,7 +51,9 @@ sticker_song_find_print_cb(const LightSong &song, const char *value,
...
@@ -50,7 +51,9 @@ sticker_song_find_print_cb(const LightSong &song, const char *value,
}
}
static
CommandResult
static
CommandResult
handle_sticker_song
(
Response
&
r
,
Partition
&
partition
,
Request
args
)
handle_sticker_song
(
Response
&
r
,
Partition
&
partition
,
StickerDatabase
&
sticker_database
,
Request
args
)
{
{
const
Database
&
db
=
partition
.
GetDatabaseOrThrow
();
const
Database
&
db
=
partition
.
GetDatabaseOrThrow
();
...
@@ -62,7 +65,8 @@ handle_sticker_song(Response &r, Partition &partition, Request args)
...
@@ -62,7 +65,8 @@ handle_sticker_song(Response &r, Partition &partition, Request args)
assert
(
song
!=
nullptr
);
assert
(
song
!=
nullptr
);
AtScopeExit
(
&
db
,
song
)
{
db
.
ReturnSong
(
song
);
};
AtScopeExit
(
&
db
,
song
)
{
db
.
ReturnSong
(
song
);
};
const
auto
value
=
sticker_song_get_value
(
*
song
,
args
[
3
]);
const
auto
value
=
sticker_song_get_value
(
sticker_database
,
*
song
,
args
[
3
]);
if
(
value
.
empty
())
{
if
(
value
.
empty
())
{
r
.
Error
(
ACK_ERROR_NO_EXIST
,
"no such sticker"
);
r
.
Error
(
ACK_ERROR_NO_EXIST
,
"no such sticker"
);
return
CommandResult
::
ERROR
;
return
CommandResult
::
ERROR
;
...
@@ -77,7 +81,7 @@ handle_sticker_song(Response &r, Partition &partition, Request args)
...
@@ -77,7 +81,7 @@ handle_sticker_song(Response &r, Partition &partition, Request args)
assert
(
song
!=
nullptr
);
assert
(
song
!=
nullptr
);
AtScopeExit
(
&
db
,
song
)
{
db
.
ReturnSong
(
song
);
};
AtScopeExit
(
&
db
,
song
)
{
db
.
ReturnSong
(
song
);
};
const
auto
sticker
=
sticker_song_get
(
*
song
);
const
auto
sticker
=
sticker_song_get
(
sticker_database
,
*
song
);
sticker_print
(
r
,
sticker
);
sticker_print
(
r
,
sticker
);
return
CommandResult
::
OK
;
return
CommandResult
::
OK
;
...
@@ -87,7 +91,8 @@ handle_sticker_song(Response &r, Partition &partition, Request args)
...
@@ -87,7 +91,8 @@ handle_sticker_song(Response &r, Partition &partition, Request args)
assert
(
song
!=
nullptr
);
assert
(
song
!=
nullptr
);
AtScopeExit
(
&
db
,
song
)
{
db
.
ReturnSong
(
song
);
};
AtScopeExit
(
&
db
,
song
)
{
db
.
ReturnSong
(
song
);
};
sticker_song_set_value
(
*
song
,
args
[
3
],
args
[
4
]);
sticker_song_set_value
(
sticker_database
,
*
song
,
args
[
3
],
args
[
4
]);
return
CommandResult
::
OK
;
return
CommandResult
::
OK
;
/* delete song song_id [key] */
/* delete song song_id [key] */
}
else
if
((
args
.
size
==
3
||
args
.
size
==
4
)
&&
}
else
if
((
args
.
size
==
3
||
args
.
size
==
4
)
&&
...
@@ -97,8 +102,9 @@ handle_sticker_song(Response &r, Partition &partition, Request args)
...
@@ -97,8 +102,9 @@ handle_sticker_song(Response &r, Partition &partition, Request args)
AtScopeExit
(
&
db
,
song
)
{
db
.
ReturnSong
(
song
);
};
AtScopeExit
(
&
db
,
song
)
{
db
.
ReturnSong
(
song
);
};
bool
ret
=
args
.
size
==
3
bool
ret
=
args
.
size
==
3
?
sticker_song_delete
(
*
song
)
?
sticker_song_delete
(
sticker_database
,
*
song
)
:
sticker_song_delete_value
(
*
song
,
args
[
3
]);
:
sticker_song_delete_value
(
sticker_database
,
*
song
,
args
[
3
]);
if
(
!
ret
)
{
if
(
!
ret
)
{
r
.
Error
(
ACK_ERROR_NO_EXIST
,
"no such sticker"
);
r
.
Error
(
ACK_ERROR_NO_EXIST
,
"no such sticker"
);
return
CommandResult
::
ERROR
;
return
CommandResult
::
ERROR
;
...
@@ -138,7 +144,7 @@ handle_sticker_song(Response &r, Partition &partition, Request args)
...
@@ -138,7 +144,7 @@ handle_sticker_song(Response &r, Partition &partition, Request args)
args
[
3
],
args
[
3
],
};
};
sticker_song_find
(
db
,
base_uri
,
data
.
name
,
sticker_song_find
(
sticker_database
,
db
,
base_uri
,
data
.
name
,
op
,
value
,
op
,
value
,
sticker_song_find_print_cb
,
&
data
);
sticker_song_find_print_cb
,
&
data
);
...
@@ -154,13 +160,18 @@ handle_sticker(Client &client, Request args, Response &r)
...
@@ -154,13 +160,18 @@ handle_sticker(Client &client, Request args, Response &r)
{
{
assert
(
args
.
size
>=
3
);
assert
(
args
.
size
>=
3
);
if
(
!
sticker_enabled
())
{
auto
&
instance
=
client
.
GetInstance
();
if
(
!
instance
.
HasStickerDatabase
())
{
r
.
Error
(
ACK_ERROR_UNKNOWN
,
"sticker database is disabled"
);
r
.
Error
(
ACK_ERROR_UNKNOWN
,
"sticker database is disabled"
);
return
CommandResult
::
ERROR
;
return
CommandResult
::
ERROR
;
}
}
auto
&
sticker_database
=
*
instance
.
sticker_database
;
if
(
StringIsEqual
(
args
[
1
],
"song"
))
if
(
StringIsEqual
(
args
[
1
],
"song"
))
return
handle_sticker_song
(
r
,
client
.
GetPartition
(),
args
);
return
handle_sticker_song
(
r
,
client
.
GetPartition
(),
sticker_database
,
args
);
else
{
else
{
r
.
Error
(
ACK_ERROR_ARG
,
"unknown sticker domain"
);
r
.
Error
(
ACK_ERROR_ARG
,
"unknown sticker domain"
);
return
CommandResult
::
ERROR
;
return
CommandResult
::
ERROR
;
...
...
src/sticker/Database.cxx
View file @
77c9081f
...
@@ -41,6 +41,7 @@ enum sticker_sql {
...
@@ -41,6 +41,7 @@ enum sticker_sql {
STICKER_SQL_FIND_VALUE
,
STICKER_SQL_FIND_VALUE
,
STICKER_SQL_FIND_LT
,
STICKER_SQL_FIND_LT
,
STICKER_SQL_FIND_GT
,
STICKER_SQL_FIND_GT
,
STICKER_SQL_COUNT
};
};
static
const
char
*
const
sticker_sql
[]
=
{
static
const
char
*
const
sticker_sql
[]
=
{
...
@@ -80,11 +81,7 @@ static const char sticker_sql_create[] =
...
@@ -80,11 +81,7 @@ static const char sticker_sql_create[] =
" sticker_value ON sticker(type, uri, name);"
" sticker_value ON sticker(type, uri, name);"
""
;
""
;
static
sqlite3
*
sticker_db
;
StickerDatabase
::
StickerDatabase
(
Path
path
)
static
sqlite3_stmt
*
sticker_stmt
[
ARRAY_SIZE
(
sticker_sql
)];
void
sticker_global_init
(
Path
path
)
{
{
assert
(
!
path
.
IsNull
());
assert
(
!
path
.
IsNull
());
...
@@ -92,20 +89,20 @@ sticker_global_init(Path path)
...
@@ -92,20 +89,20 @@ sticker_global_init(Path path)
/* open/create the sqlite database */
/* open/create the sqlite database */
ret
=
sqlite3_open
(
path
.
c_str
(),
&
sticker_
db
);
ret
=
sqlite3_open
(
path
.
c_str
(),
&
db
);
if
(
ret
!=
SQLITE_OK
)
{
if
(
ret
!=
SQLITE_OK
)
{
const
std
::
string
utf8
=
path
.
ToUTF8
();
const
std
::
string
utf8
=
path
.
ToUTF8
();
throw
SqliteError
(
sticker_
db
,
ret
,
throw
SqliteError
(
db
,
ret
,
(
"Failed to open sqlite database '"
+
(
"Failed to open sqlite database '"
+
utf8
+
"'"
).
c_str
());
utf8
+
"'"
).
c_str
());
}
}
/* create the table and index */
/* create the table and index */
ret
=
sqlite3_exec
(
sticker_
db
,
sticker_sql_create
,
ret
=
sqlite3_exec
(
db
,
sticker_sql_create
,
nullptr
,
nullptr
,
nullptr
);
nullptr
,
nullptr
,
nullptr
);
if
(
ret
!=
SQLITE_OK
)
if
(
ret
!=
SQLITE_OK
)
throw
SqliteError
(
sticker_
db
,
ret
,
throw
SqliteError
(
db
,
ret
,
"Failed to create sticker table"
);
"Failed to create sticker table"
);
/* prepare the statements we're going to use */
/* prepare the statements we're going to use */
...
@@ -113,38 +110,28 @@ sticker_global_init(Path path)
...
@@ -113,38 +110,28 @@ sticker_global_init(Path path)
for
(
unsigned
i
=
0
;
i
<
ARRAY_SIZE
(
sticker_sql
);
++
i
)
{
for
(
unsigned
i
=
0
;
i
<
ARRAY_SIZE
(
sticker_sql
);
++
i
)
{
assert
(
sticker_sql
[
i
]
!=
nullptr
);
assert
(
sticker_sql
[
i
]
!=
nullptr
);
st
icker_stmt
[
i
]
=
Prepare
(
sticker_
db
,
sticker_sql
[
i
]);
st
mt
[
i
]
=
Prepare
(
db
,
sticker_sql
[
i
]);
}
}
}
}
void
StickerDatabase
::~
StickerDatabase
()
noexcept
sticker_global_finish
()
noexcept
{
{
if
(
sticker_db
==
nullptr
)
assert
(
db
!=
nullptr
);
/* not configured */
return
;
for
(
unsigned
i
=
0
;
i
<
ARRAY_SIZE
(
st
icker_st
mt
);
++
i
)
{
for
(
unsigned
i
=
0
;
i
<
ARRAY_SIZE
(
stmt
);
++
i
)
{
assert
(
st
icker_st
mt
[
i
]
!=
nullptr
);
assert
(
stmt
[
i
]
!=
nullptr
);
sqlite3_finalize
(
st
icker_st
mt
[
i
]);
sqlite3_finalize
(
stmt
[
i
]);
}
}
sqlite3_close
(
sticker_db
);
sqlite3_close
(
db
);
}
bool
sticker_enabled
()
noexcept
{
return
sticker_db
!=
nullptr
;
}
}
std
::
string
std
::
string
sticker_load_v
alue
(
const
char
*
type
,
const
char
*
uri
,
const
char
*
name
)
StickerDatabase
::
LoadV
alue
(
const
char
*
type
,
const
char
*
uri
,
const
char
*
name
)
{
{
sqlite3_stmt
*
const
s
tmt
=
sticker_
stmt
[
STICKER_SQL_GET
];
sqlite3_stmt
*
const
s
=
stmt
[
STICKER_SQL_GET
];
assert
(
sticker_enabled
());
assert
(
type
!=
nullptr
);
assert
(
type
!=
nullptr
);
assert
(
uri
!=
nullptr
);
assert
(
uri
!=
nullptr
);
assert
(
name
!=
nullptr
);
assert
(
name
!=
nullptr
);
...
@@ -152,49 +139,48 @@ sticker_load_value(const char *type, const char *uri, const char *name)
...
@@ -152,49 +139,48 @@ sticker_load_value(const char *type, const char *uri, const char *name)
if
(
StringIsEmpty
(
name
))
if
(
StringIsEmpty
(
name
))
return
std
::
string
();
return
std
::
string
();
BindAll
(
s
tmt
,
type
,
uri
,
name
);
BindAll
(
s
,
type
,
uri
,
name
);
AtScopeExit
(
s
tmt
)
{
AtScopeExit
(
s
)
{
sqlite3_reset
(
s
tmt
);
sqlite3_reset
(
s
);
sqlite3_clear_bindings
(
s
tmt
);
sqlite3_clear_bindings
(
s
);
};
};
std
::
string
value
;
std
::
string
value
;
if
(
ExecuteRow
(
s
tmt
))
if
(
ExecuteRow
(
s
))
value
=
(
const
char
*
)
sqlite3_column_text
(
s
tmt
,
0
);
value
=
(
const
char
*
)
sqlite3_column_text
(
s
,
0
);
return
value
;
return
value
;
}
}
static
void
void
sticker_list_v
alues
(
std
::
map
<
std
::
string
,
std
::
string
>
&
table
,
StickerDatabase
::
ListV
alues
(
std
::
map
<
std
::
string
,
std
::
string
>
&
table
,
const
char
*
type
,
const
char
*
uri
)
const
char
*
type
,
const
char
*
uri
)
{
{
sqlite3_stmt
*
const
s
tmt
=
sticker_
stmt
[
STICKER_SQL_LIST
];
sqlite3_stmt
*
const
s
=
stmt
[
STICKER_SQL_LIST
];
assert
(
type
!=
nullptr
);
assert
(
type
!=
nullptr
);
assert
(
uri
!=
nullptr
);
assert
(
uri
!=
nullptr
);
assert
(
sticker_enabled
());
BindAll
(
s
tmt
,
type
,
uri
);
BindAll
(
s
,
type
,
uri
);
AtScopeExit
(
s
tmt
)
{
AtScopeExit
(
s
)
{
sqlite3_reset
(
s
tmt
);
sqlite3_reset
(
s
);
sqlite3_clear_bindings
(
s
tmt
);
sqlite3_clear_bindings
(
s
);
};
};
ExecuteForEach
(
s
tmt
,
[
stmt
,
&
table
](){
ExecuteForEach
(
s
,
[
s
,
&
table
](){
const
char
*
name
=
(
const
char
*
)
sqlite3_column_text
(
stmt
,
0
);
const
char
*
name
=
(
const
char
*
)
sqlite3_column_text
(
s
,
0
);
const
char
*
value
=
(
const
char
*
)
sqlite3_column_text
(
stmt
,
1
);
const
char
*
value
=
(
const
char
*
)
sqlite3_column_text
(
s
,
1
);
table
.
insert
(
std
::
make_pair
(
name
,
value
));
table
.
insert
(
std
::
make_pair
(
name
,
value
));
});
});
}
}
static
bool
bool
sticker_update_v
alue
(
const
char
*
type
,
const
char
*
uri
,
StickerDatabase
::
UpdateV
alue
(
const
char
*
type
,
const
char
*
uri
,
const
char
*
name
,
const
char
*
value
)
const
char
*
name
,
const
char
*
value
)
{
{
sqlite3_stmt
*
const
s
tmt
=
sticker_
stmt
[
STICKER_SQL_UPDATE
];
sqlite3_stmt
*
const
s
=
stmt
[
STICKER_SQL_UPDATE
];
assert
(
type
!=
nullptr
);
assert
(
type
!=
nullptr
);
assert
(
uri
!=
nullptr
);
assert
(
uri
!=
nullptr
);
...
@@ -202,27 +188,25 @@ sticker_update_value(const char *type, const char *uri,
...
@@ -202,27 +188,25 @@ sticker_update_value(const char *type, const char *uri,
assert
(
*
name
!=
0
);
assert
(
*
name
!=
0
);
assert
(
value
!=
nullptr
);
assert
(
value
!=
nullptr
);
assert
(
sticker_enabled
()
);
BindAll
(
s
,
value
,
type
,
uri
,
name
);
BindAll
(
stmt
,
value
,
type
,
uri
,
name
);
AtScopeExit
(
s
)
{
sqlite3_reset
(
s
);
AtScopeExit
(
stmt
)
{
sqlite3_clear_bindings
(
s
);
sqlite3_reset
(
stmt
);
sqlite3_clear_bindings
(
stmt
);
};
};
bool
modified
=
ExecuteModified
(
s
tmt
);
bool
modified
=
ExecuteModified
(
s
);
if
(
modified
)
if
(
modified
)
idle_add
(
IDLE_STICKER
);
idle_add
(
IDLE_STICKER
);
return
modified
;
return
modified
;
}
}
static
void
void
sticker_insert_v
alue
(
const
char
*
type
,
const
char
*
uri
,
StickerDatabase
::
InsertV
alue
(
const
char
*
type
,
const
char
*
uri
,
const
char
*
name
,
const
char
*
value
)
const
char
*
name
,
const
char
*
value
)
{
{
sqlite3_stmt
*
const
s
tmt
=
sticker_
stmt
[
STICKER_SQL_INSERT
];
sqlite3_stmt
*
const
s
=
stmt
[
STICKER_SQL_INSERT
];
assert
(
type
!=
nullptr
);
assert
(
type
!=
nullptr
);
assert
(
uri
!=
nullptr
);
assert
(
uri
!=
nullptr
);
...
@@ -230,24 +214,21 @@ sticker_insert_value(const char *type, const char *uri,
...
@@ -230,24 +214,21 @@ sticker_insert_value(const char *type, const char *uri,
assert
(
*
name
!=
0
);
assert
(
*
name
!=
0
);
assert
(
value
!=
nullptr
);
assert
(
value
!=
nullptr
);
assert
(
sticker_enabled
());
BindAll
(
s
,
type
,
uri
,
name
,
value
);
BindAll
(
stmt
,
type
,
uri
,
name
,
value
);
AtScopeExit
(
s
tmt
)
{
AtScopeExit
(
s
)
{
sqlite3_reset
(
s
tmt
);
sqlite3_reset
(
s
);
sqlite3_clear_bindings
(
s
tmt
);
sqlite3_clear_bindings
(
s
);
};
};
ExecuteCommand
(
s
tmt
);
ExecuteCommand
(
s
);
idle_add
(
IDLE_STICKER
);
idle_add
(
IDLE_STICKER
);
}
}
void
void
sticker_store_v
alue
(
const
char
*
type
,
const
char
*
uri
,
StickerDatabase
::
StoreV
alue
(
const
char
*
type
,
const
char
*
uri
,
const
char
*
name
,
const
char
*
value
)
const
char
*
name
,
const
char
*
value
)
{
{
assert
(
sticker_enabled
());
assert
(
type
!=
nullptr
);
assert
(
type
!=
nullptr
);
assert
(
uri
!=
nullptr
);
assert
(
uri
!=
nullptr
);
assert
(
name
!=
nullptr
);
assert
(
name
!=
nullptr
);
...
@@ -256,66 +237,66 @@ sticker_store_value(const char *type, const char *uri,
...
@@ -256,66 +237,66 @@ sticker_store_value(const char *type, const char *uri,
if
(
StringIsEmpty
(
name
))
if
(
StringIsEmpty
(
name
))
return
;
return
;
if
(
!
sticker_update_v
alue
(
type
,
uri
,
name
,
value
))
if
(
!
UpdateV
alue
(
type
,
uri
,
name
,
value
))
sticker_insert_v
alue
(
type
,
uri
,
name
,
value
);
InsertV
alue
(
type
,
uri
,
name
,
value
);
}
}
bool
bool
sticker_d
elete
(
const
char
*
type
,
const
char
*
uri
)
StickerDatabase
::
D
elete
(
const
char
*
type
,
const
char
*
uri
)
{
{
sqlite3_stmt
*
const
s
tmt
=
sticker_
stmt
[
STICKER_SQL_DELETE
];
sqlite3_stmt
*
const
s
=
stmt
[
STICKER_SQL_DELETE
];
assert
(
sticker_enabled
());
assert
(
type
!=
nullptr
);
assert
(
type
!=
nullptr
);
assert
(
uri
!=
nullptr
);
assert
(
uri
!=
nullptr
);
BindAll
(
s
tmt
,
type
,
uri
);
BindAll
(
s
,
type
,
uri
);
AtScopeExit
(
s
tmt
)
{
AtScopeExit
(
s
)
{
sqlite3_reset
(
s
tmt
);
sqlite3_reset
(
s
);
sqlite3_clear_bindings
(
s
tmt
);
sqlite3_clear_bindings
(
s
);
};
};
bool
modified
=
ExecuteModified
(
s
tmt
);
bool
modified
=
ExecuteModified
(
s
);
if
(
modified
)
if
(
modified
)
idle_add
(
IDLE_STICKER
);
idle_add
(
IDLE_STICKER
);
return
modified
;
return
modified
;
}
}
bool
bool
sticker_delete_value
(
const
char
*
type
,
const
char
*
uri
,
const
char
*
name
)
StickerDatabase
::
DeleteValue
(
const
char
*
type
,
const
char
*
uri
,
const
char
*
name
)
{
{
sqlite3_stmt
*
const
s
tmt
=
sticker_
stmt
[
STICKER_SQL_DELETE_VALUE
];
sqlite3_stmt
*
const
s
=
stmt
[
STICKER_SQL_DELETE_VALUE
];
assert
(
sticker_enabled
());
assert
(
type
!=
nullptr
);
assert
(
type
!=
nullptr
);
assert
(
uri
!=
nullptr
);
assert
(
uri
!=
nullptr
);
BindAll
(
s
tmt
,
type
,
uri
,
name
);
BindAll
(
s
,
type
,
uri
,
name
);
AtScopeExit
(
s
tmt
)
{
AtScopeExit
(
s
)
{
sqlite3_reset
(
s
tmt
);
sqlite3_reset
(
s
);
sqlite3_clear_bindings
(
s
tmt
);
sqlite3_clear_bindings
(
s
);
};
};
bool
modified
=
ExecuteModified
(
s
tmt
);
bool
modified
=
ExecuteModified
(
s
);
if
(
modified
)
if
(
modified
)
idle_add
(
IDLE_STICKER
);
idle_add
(
IDLE_STICKER
);
return
modified
;
return
modified
;
}
}
Sticker
Sticker
sticker_l
oad
(
const
char
*
type
,
const
char
*
uri
)
StickerDatabase
::
L
oad
(
const
char
*
type
,
const
char
*
uri
)
{
{
Sticker
s
;
Sticker
s
;
sticker_list_v
alues
(
s
.
table
,
type
,
uri
);
ListV
alues
(
s
.
table
,
type
,
uri
);
return
s
;
return
s
;
}
}
static
sqlite3_stmt
*
sqlite3_stmt
*
BindFind
(
const
char
*
type
,
const
char
*
base_uri
,
const
char
*
name
,
StickerDatabase
::
BindFind
(
const
char
*
type
,
const
char
*
base_uri
,
const
char
*
name
,
StickerOperator
op
,
const
char
*
value
)
StickerOperator
op
,
const
char
*
value
)
{
{
assert
(
type
!=
nullptr
);
assert
(
type
!=
nullptr
);
...
@@ -326,23 +307,23 @@ BindFind(const char *type, const char *base_uri, const char *name,
...
@@ -326,23 +307,23 @@ BindFind(const char *type, const char *base_uri, const char *name,
switch
(
op
)
{
switch
(
op
)
{
case
StickerOperator
:
:
EXISTS
:
case
StickerOperator
:
:
EXISTS
:
BindAll
(
st
icker_st
mt
[
STICKER_SQL_FIND
],
type
,
base_uri
,
name
);
BindAll
(
stmt
[
STICKER_SQL_FIND
],
type
,
base_uri
,
name
);
return
st
icker_st
mt
[
STICKER_SQL_FIND
];
return
stmt
[
STICKER_SQL_FIND
];
case
StickerOperator
:
:
EQUALS
:
case
StickerOperator
:
:
EQUALS
:
BindAll
(
st
icker_st
mt
[
STICKER_SQL_FIND_VALUE
],
BindAll
(
stmt
[
STICKER_SQL_FIND_VALUE
],
type
,
base_uri
,
name
,
value
);
type
,
base_uri
,
name
,
value
);
return
st
icker_st
mt
[
STICKER_SQL_FIND_VALUE
];
return
stmt
[
STICKER_SQL_FIND_VALUE
];
case
StickerOperator
:
:
LESS_THAN
:
case
StickerOperator
:
:
LESS_THAN
:
BindAll
(
st
icker_st
mt
[
STICKER_SQL_FIND_LT
],
BindAll
(
stmt
[
STICKER_SQL_FIND_LT
],
type
,
base_uri
,
name
,
value
);
type
,
base_uri
,
name
,
value
);
return
st
icker_st
mt
[
STICKER_SQL_FIND_LT
];
return
stmt
[
STICKER_SQL_FIND_LT
];
case
StickerOperator
:
:
GREATER_THAN
:
case
StickerOperator
:
:
GREATER_THAN
:
BindAll
(
st
icker_st
mt
[
STICKER_SQL_FIND_GT
],
BindAll
(
stmt
[
STICKER_SQL_FIND_GT
],
type
,
base_uri
,
name
,
value
);
type
,
base_uri
,
name
,
value
);
return
st
icker_st
mt
[
STICKER_SQL_FIND_GT
];
return
stmt
[
STICKER_SQL_FIND_GT
];
}
}
assert
(
false
);
assert
(
false
);
...
@@ -350,26 +331,25 @@ BindFind(const char *type, const char *base_uri, const char *name,
...
@@ -350,26 +331,25 @@ BindFind(const char *type, const char *base_uri, const char *name,
}
}
void
void
sticker_f
ind
(
const
char
*
type
,
const
char
*
base_uri
,
const
char
*
name
,
StickerDatabase
::
F
ind
(
const
char
*
type
,
const
char
*
base_uri
,
const
char
*
name
,
StickerOperator
op
,
const
char
*
value
,
StickerOperator
op
,
const
char
*
value
,
void
(
*
func
)(
const
char
*
uri
,
const
char
*
value
,
void
(
*
func
)(
const
char
*
uri
,
const
char
*
value
,
void
*
user_data
),
void
*
user_data
),
void
*
user_data
)
void
*
user_data
)
{
{
assert
(
func
!=
nullptr
);
assert
(
func
!=
nullptr
);
assert
(
sticker_enabled
());
sqlite3_stmt
*
const
s
tmt
=
BindFind
(
type
,
base_uri
,
name
,
op
,
value
);
sqlite3_stmt
*
const
s
=
BindFind
(
type
,
base_uri
,
name
,
op
,
value
);
assert
(
s
tmt
!=
nullptr
);
assert
(
s
!=
nullptr
);
AtScopeExit
(
s
tmt
)
{
AtScopeExit
(
s
)
{
sqlite3_reset
(
s
tmt
);
sqlite3_reset
(
s
);
sqlite3_clear_bindings
(
s
tmt
);
sqlite3_clear_bindings
(
s
);
};
};
ExecuteForEach
(
s
tmt
,
[
stmt
,
func
,
user_data
](){
ExecuteForEach
(
s
,
[
s
,
func
,
user_data
](){
func
((
const
char
*
)
sqlite3_column_text
(
s
tmt
,
0
),
func
((
const
char
*
)
sqlite3_column_text
(
s
,
0
),
(
const
char
*
)
sqlite3_column_text
(
s
tmt
,
1
),
(
const
char
*
)
sqlite3_column_text
(
s
,
1
),
user_data
);
user_data
);
});
});
}
}
src/sticker/Database.hxx
View file @
77c9081f
...
@@ -45,70 +45,77 @@
...
@@ -45,70 +45,77 @@
#include "Match.hxx"
#include "Match.hxx"
#include "util/Compiler.h"
#include "util/Compiler.h"
#include <sqlite3.h>
#include <map>
#include <string>
#include <string>
class
Path
;
class
Path
;
struct
Sticker
;
struct
Sticker
;
/**
class
StickerDatabase
{
* Opens the sticker database.
enum
SQL
{
*
SQL_GET
,
* Throws std::runtime_error on error.
SQL_LIST
,
*/
SQL_UPDATE
,
void
SQL_INSERT
,
sticker_global_init
(
Path
path
);
SQL_DELETE
,
SQL_DELETE_VALUE
,
SQL_FIND
,
SQL_FIND_VALUE
,
SQL_FIND_LT
,
SQL_FIND_GT
,
/**
SQL_COUNT
* Close the sticker database.
};
*/
void
sqlite3
*
db
;
sticker_global_finish
()
noexcept
;
sqlite3_stmt
*
stmt
[
SQL_COUNT
]
;
/**
public
:
* Returns true if the sticker database is configured and available.
/**
* Opens the sticker database.
*
* Throws on error.
*/
*/
gcc_const
StickerDatabase
(
Path
path
);
bool
~
StickerDatabase
()
noexcept
;
sticker_enabled
()
noexcept
;
/**
/**
* Returns one value from an object's sticker record. Returns an
* Returns one value from an object's sticker record. Returns an
* empty string if the value doesn't exist.
* empty string if the value doesn't exist.
*
*
* Throws #SqliteError on error.
* Throws #SqliteError on error.
*/
*/
std
::
string
std
::
string
LoadValue
(
const
char
*
type
,
const
char
*
uri
,
sticker_load_value
(
const
char
*
type
,
const
char
*
uri
,
const
char
*
name
);
const
char
*
name
);
/**
/**
* Sets a sticker value in the specified object. Overwrites existing
* Sets a sticker value in the specified object. Overwrites existing
* values.
* values.
*
*
* Throws #SqliteError on error.
* Throws #SqliteError on error.
*/
*/
void
void
StoreValue
(
const
char
*
type
,
const
char
*
uri
,
sticker_store_value
(
const
char
*
type
,
const
char
*
uri
,
const
char
*
name
,
const
char
*
value
);
const
char
*
name
,
const
char
*
value
);
/**
/**
* Deletes a sticker from the database. All sticker values of the
* Deletes a sticker from the database. All sticker values of the
* specified object are deleted.
* specified object are deleted.
*
*
* Throws #SqliteError on error.
* Throws #SqliteError on error.
*/
*/
bool
bool
Delete
(
const
char
*
type
,
const
char
*
uri
);
sticker_delete
(
const
char
*
type
,
const
char
*
uri
);
/**
/**
* Deletes a sticker value. Fails if no sticker with this name
* Deletes a sticker value. Fails if no sticker with this name
* exists.
* exists.
*
*
* Throws #SqliteError on error.
* Throws #SqliteError on error.
*/
*/
bool
bool
DeleteValue
(
const
char
*
type
,
const
char
*
uri
,
const
char
*
name
);
sticker_delete_value
(
const
char
*
type
,
const
char
*
uri
,
const
char
*
name
);
/**
/**
* Loads the sticker for the specified resource.
* Loads the sticker for the specified resource.
*
*
* Throws #SqliteError on error.
* Throws #SqliteError on error.
...
@@ -117,10 +124,9 @@ sticker_delete_value(const char *type, const char *uri, const char *name);
...
@@ -117,10 +124,9 @@ sticker_delete_value(const char *type, const char *uri, const char *name);
* @param uri the URI of the resource, e.g. the song path
* @param uri the URI of the resource, e.g. the song path
* @return a sticker object
* @return a sticker object
*/
*/
Sticker
Sticker
Load
(
const
char
*
type
,
const
char
*
uri
);
sticker_load
(
const
char
*
type
,
const
char
*
uri
);
/**
/**
* Finds stickers with the specified name below the specified URI.
* Finds stickers with the specified name below the specified URI.
*
*
* @param type the resource type, e.g. "song"
* @param type the resource type, e.g. "song"
...
@@ -130,11 +136,25 @@ sticker_load(const char *type, const char *uri);
...
@@ -130,11 +136,25 @@ sticker_load(const char *type, const char *uri);
* @param op the comparison operator
* @param op the comparison operator
* @param value the operand
* @param value the operand
*/
*/
void
void
Find
(
const
char
*
type
,
const
char
*
base_uri
,
const
char
*
name
,
sticker_find
(
const
char
*
type
,
const
char
*
base_uri
,
const
char
*
name
,
StickerOperator
op
,
const
char
*
value
,
StickerOperator
op
,
const
char
*
value
,
void
(
*
func
)(
const
char
*
uri
,
const
char
*
value
,
void
(
*
func
)(
const
char
*
uri
,
const
char
*
value
,
void
*
user_data
),
void
*
user_data
),
void
*
user_data
);
void
*
user_data
);
private
:
void
ListValues
(
std
::
map
<
std
::
string
,
std
::
string
>
&
table
,
const
char
*
type
,
const
char
*
uri
);
bool
UpdateValue
(
const
char
*
type
,
const
char
*
uri
,
const
char
*
name
,
const
char
*
value
);
void
InsertValue
(
const
char
*
type
,
const
char
*
uri
,
const
char
*
name
,
const
char
*
value
);
sqlite3_stmt
*
BindFind
(
const
char
*
type
,
const
char
*
base_uri
,
const
char
*
name
,
StickerOperator
op
,
const
char
*
value
);
};
#endif
#endif
src/sticker/SongSticker.cxx
View file @
77c9081f
...
@@ -29,44 +29,47 @@
...
@@ -29,44 +29,47 @@
#include <stdlib.h>
#include <stdlib.h>
std
::
string
std
::
string
sticker_song_get_value
(
const
LightSong
&
song
,
const
char
*
name
)
sticker_song_get_value
(
StickerDatabase
&
db
,
const
LightSong
&
song
,
const
char
*
name
)
{
{
const
auto
uri
=
song
.
GetURI
();
const
auto
uri
=
song
.
GetURI
();
return
sticker_load_v
alue
(
"song"
,
uri
.
c_str
(),
name
);
return
db
.
LoadV
alue
(
"song"
,
uri
.
c_str
(),
name
);
}
}
void
void
sticker_song_set_value
(
const
LightSong
&
song
,
sticker_song_set_value
(
StickerDatabase
&
db
,
const
LightSong
&
song
,
const
char
*
name
,
const
char
*
value
)
const
char
*
name
,
const
char
*
value
)
{
{
const
auto
uri
=
song
.
GetURI
();
const
auto
uri
=
song
.
GetURI
();
sticker_store_v
alue
(
"song"
,
uri
.
c_str
(),
name
,
value
);
db
.
StoreV
alue
(
"song"
,
uri
.
c_str
(),
name
,
value
);
}
}
bool
bool
sticker_song_delete
(
const
char
*
uri
)
sticker_song_delete
(
StickerDatabase
&
db
,
const
char
*
uri
)
{
{
return
sticker_d
elete
(
"song"
,
uri
);
return
db
.
D
elete
(
"song"
,
uri
);
}
}
bool
bool
sticker_song_delete
(
const
LightSong
&
song
)
sticker_song_delete
(
StickerDatabase
&
db
,
const
LightSong
&
song
)
{
{
return
sticker_song_delete
(
song
.
GetURI
().
c_str
());
return
sticker_song_delete
(
db
,
song
.
GetURI
().
c_str
());
}
}
bool
bool
sticker_song_delete_value
(
const
LightSong
&
song
,
const
char
*
name
)
sticker_song_delete_value
(
StickerDatabase
&
db
,
const
LightSong
&
song
,
const
char
*
name
)
{
{
const
auto
uri
=
song
.
GetURI
();
const
auto
uri
=
song
.
GetURI
();
return
sticker_delete_v
alue
(
"song"
,
uri
.
c_str
(),
name
);
return
db
.
DeleteV
alue
(
"song"
,
uri
.
c_str
(),
name
);
}
}
Sticker
Sticker
sticker_song_get
(
const
LightSong
&
song
)
sticker_song_get
(
StickerDatabase
&
db
,
const
LightSong
&
song
)
{
{
const
auto
uri
=
song
.
GetURI
();
const
auto
uri
=
song
.
GetURI
();
return
sticker_l
oad
(
"song"
,
uri
.
c_str
());
return
db
.
L
oad
(
"song"
,
uri
.
c_str
());
}
}
namespace
{
namespace
{
...
@@ -101,7 +104,8 @@ sticker_song_find_cb(const char *uri, const char *value, void *user_data)
...
@@ -101,7 +104,8 @@ sticker_song_find_cb(const char *uri, const char *value, void *user_data)
}
}
void
void
sticker_song_find
(
const
Database
&
db
,
const
char
*
base_uri
,
const
char
*
name
,
sticker_song_find
(
StickerDatabase
&
sticker_database
,
const
Database
&
db
,
const
char
*
base_uri
,
const
char
*
name
,
StickerOperator
op
,
const
char
*
value
,
StickerOperator
op
,
const
char
*
value
,
void
(
*
func
)(
const
LightSong
&
song
,
const
char
*
value
,
void
(
*
func
)(
const
LightSong
&
song
,
const
char
*
value
,
void
*
user_data
),
void
*
user_data
),
...
@@ -126,6 +130,6 @@ sticker_song_find(const Database &db, const char *base_uri, const char *name,
...
@@ -126,6 +130,6 @@ sticker_song_find(const Database &db, const char *base_uri, const char *name,
data
.
base_uri_length
=
strlen
(
data
.
base_uri
);
data
.
base_uri_length
=
strlen
(
data
.
base_uri
);
sticker_
f
ind
(
"song"
,
data
.
base_uri
,
name
,
op
,
value
,
sticker_
database
.
F
ind
(
"song"
,
data
.
base_uri
,
name
,
op
,
value
,
sticker_song_find_cb
,
&
data
);
sticker_song_find_cb
,
&
data
);
}
}
src/sticker/SongSticker.hxx
View file @
77c9081f
...
@@ -27,6 +27,7 @@
...
@@ -27,6 +27,7 @@
struct
LightSong
;
struct
LightSong
;
struct
Sticker
;
struct
Sticker
;
class
Database
;
class
Database
;
class
StickerDatabase
;
/**
/**
* Returns one value from a song's sticker record.
* Returns one value from a song's sticker record.
...
@@ -34,7 +35,8 @@ class Database;
...
@@ -34,7 +35,8 @@ class Database;
* Throws #SqliteError on error.
* Throws #SqliteError on error.
*/
*/
std
::
string
std
::
string
sticker_song_get_value
(
const
LightSong
&
song
,
const
char
*
name
);
sticker_song_get_value
(
StickerDatabase
&
db
,
const
LightSong
&
song
,
const
char
*
name
);
/**
/**
* Sets a sticker value in the specified song. Overwrites existing
* Sets a sticker value in the specified song. Overwrites existing
...
@@ -43,7 +45,8 @@ sticker_song_get_value(const LightSong &song, const char *name);
...
@@ -43,7 +45,8 @@ sticker_song_get_value(const LightSong &song, const char *name);
* Throws #SqliteError on error.
* Throws #SqliteError on error.
*/
*/
void
void
sticker_song_set_value
(
const
LightSong
&
song
,
sticker_song_set_value
(
StickerDatabase
&
db
,
const
LightSong
&
song
,
const
char
*
name
,
const
char
*
value
);
const
char
*
name
,
const
char
*
value
);
/**
/**
...
@@ -52,10 +55,10 @@ sticker_song_set_value(const LightSong &song,
...
@@ -52,10 +55,10 @@ sticker_song_set_value(const LightSong &song,
* Throws #SqliteError on error.
* Throws #SqliteError on error.
*/
*/
bool
bool
sticker_song_delete
(
const
char
*
uri
);
sticker_song_delete
(
StickerDatabase
&
db
,
const
char
*
uri
);
bool
bool
sticker_song_delete
(
const
LightSong
&
song
);
sticker_song_delete
(
StickerDatabase
&
db
,
const
LightSong
&
song
);
/**
/**
* Deletes a sticker value. Does nothing if the sticker did not
* Deletes a sticker value. Does nothing if the sticker did not
...
@@ -64,7 +67,8 @@ sticker_song_delete(const LightSong &song);
...
@@ -64,7 +67,8 @@ sticker_song_delete(const LightSong &song);
* Throws #SqliteError on error.
* Throws #SqliteError on error.
*/
*/
bool
bool
sticker_song_delete_value
(
const
LightSong
&
song
,
const
char
*
name
);
sticker_song_delete_value
(
StickerDatabase
&
db
,
const
LightSong
&
song
,
const
char
*
name
);
/**
/**
* Loads the sticker for the specified song.
* Loads the sticker for the specified song.
...
@@ -75,7 +79,7 @@ sticker_song_delete_value(const LightSong &song, const char *name);
...
@@ -75,7 +79,7 @@ sticker_song_delete_value(const LightSong &song, const char *name);
* @return a sticker object
* @return a sticker object
*/
*/
Sticker
Sticker
sticker_song_get
(
const
LightSong
&
song
);
sticker_song_get
(
StickerDatabase
&
db
,
const
LightSong
&
song
);
/**
/**
* Finds stickers with the specified name below the specified
* Finds stickers with the specified name below the specified
...
@@ -89,7 +93,8 @@ sticker_song_get(const LightSong &song);
...
@@ -89,7 +93,8 @@ sticker_song_get(const LightSong &song);
* @param name the name of the sticker
* @param name the name of the sticker
*/
*/
void
void
sticker_song_find
(
const
Database
&
db
,
const
char
*
base_uri
,
const
char
*
name
,
sticker_song_find
(
StickerDatabase
&
sticker_database
,
const
Database
&
db
,
const
char
*
base_uri
,
const
char
*
name
,
StickerOperator
op
,
const
char
*
value
,
StickerOperator
op
,
const
char
*
value
,
void
(
*
func
)(
const
LightSong
&
song
,
const
char
*
value
,
void
(
*
func
)(
const
LightSong
&
song
,
const
char
*
value
,
void
*
user_data
),
void
*
user_data
),
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment