Commit b6d0e5a0 authored by Clément Stenac's avatar Clément Stenac

Rebuild the array of currently playing items as a background task.

This array is now usable as a reliable source of data for size
Use playlist_CurrentSize(p_playlist) to retrieve the size of the current playset.
parent 36a14991
...@@ -259,7 +259,7 @@ enum ...@@ -259,7 +259,7 @@ enum
STATS_DISPLAYED_PICTURES, STATS_DISPLAYED_PICTURES,
STATS_LOST_PICTURES, STATS_LOST_PICTURES,
STATS_TIMER_PLAYLIST_WALK, STATS_TIMER_PLAYLIST_BUILD,
STATS_TIMER_ML_LOAD, STATS_TIMER_ML_LOAD,
STATS_TIMER_ML_DUMP, STATS_TIMER_ML_DUMP,
STATS_TIMER_INTERACTION, STATS_TIMER_INTERACTION,
......
...@@ -111,6 +111,7 @@ struct playlist_t ...@@ -111,6 +111,7 @@ struct playlist_t
int i_current_index; /**< Index in current array */ int i_current_index; /**< Index in current array */
/** Reset current item ? */ /** Reset current item ? */
vlc_bool_t b_reset_currently_playing; vlc_bool_t b_reset_currently_playing;
mtime_t last_rebuild_date;
int i_last_playlist_id; /**< Last id to an item */ int i_last_playlist_id; /**< Last id to an item */
int i_last_input_id ; /**< Last id on an input */ int i_last_input_id ; /**< Last id on an input */
...@@ -433,6 +434,12 @@ static inline vlc_bool_t playlist_IsEmpty( playlist_t * p_playlist ) ...@@ -433,6 +434,12 @@ static inline vlc_bool_t playlist_IsEmpty( playlist_t * p_playlist )
return( b_empty ); return( b_empty );
} }
/** Tell the number of items in the current playing context */
static inline int playlist_CurrentSize( vlc_object_t *p_this )
{
return p_this->p_libvlc->p_playlist->current.i_size;
}
/** Ask the playlist to do some work */ /** Ask the playlist to do some work */
static inline void playlist_Signal( playlist_t *p_playlist ) static inline void playlist_Signal( playlist_t *p_playlist )
{ {
......
...@@ -796,16 +796,14 @@ static void PlayBookmark( intf_thread_t *p_intf, int i_num ) ...@@ -796,16 +796,14 @@ static void PlayBookmark( intf_thread_t *p_intf, int i_num )
var_Get( p_intf, psz_bookmark_name, &val ); var_Get( p_intf, psz_bookmark_name, &val );
char *psz_bookmark = strdup( val.psz_string ); char *psz_bookmark = strdup( val.psz_string );
for( i = 0; i < p_playlist->items.i_size; i++) PL_LOCK;
{ FOREACH_ARRAY( playlist_item_t *p_item, p_playlist->items )
if( !strcmp( psz_bookmark, if( !strcmp( psz_bookmark, p_item->p_input->psz_uri ) )
ARRAY_VAL( p_playlist->items,i )->p_input->psz_uri ) )
{ {
playlist_LockControl( p_playlist, PLAYLIST_VIEWPLAY, NULL, playlist_Control( p_playlist, PLAYLIST_VIEWPLAY, NULL, p_item );
ARRAY_VAL( p_playlist->items, i ) );
break; break;
} }
} FOREACH_END();
vlc_object_release( p_playlist ); vlc_object_release( p_playlist );
} }
......
...@@ -842,7 +842,7 @@ void InterfaceWindow::UpdateInterface() ...@@ -842,7 +842,7 @@ void InterfaceWindow::UpdateInterface()
b_playlist_update = false; b_playlist_update = false;
} }
#endif #endif
p_mediaControl->SetEnabled( p_playlist->i_size ); p_mediaControl->SetEnabled( !playlist_IsEmpty( p_playlist ) );
} }
if( p_input ) if( p_input )
......
...@@ -988,14 +988,7 @@ PlaylistView::SetPlaying( bool playing ) ...@@ -988,14 +988,7 @@ PlaylistView::SetPlaying( bool playing )
void void
PlaylistView::RebuildList() PlaylistView::RebuildList()
{ {
playlist_t * p_playlist; playlist_t * p_playlist = pl_Yield( p_intf );
p_playlist = (playlist_t *) vlc_object_find( p_intf,
VLC_OBJECT_PLAYLIST, FIND_ANYWHERE );
if( !p_playlist )
{
return;
}
// remove all items // remove all items
BListItem * item; BListItem * item;
...@@ -1004,10 +997,11 @@ PlaylistView::RebuildList() ...@@ -1004,10 +997,11 @@ PlaylistView::RebuildList()
delete item; delete item;
// rebuild listview from VLC's playlist // rebuild listview from VLC's playlist
vlc_mutex_lock( &p_playlist->object_lock ); PL_LOCK;
for( int i = 0; i < p_playlist->i_size; i++ ) FOREACH_ARRAY( playlist_item_t *p_item, p_playlist->items )
AddItem( new PlaylistItem( p_playlist->pp_items[i]->input.psz_name ) ); AddItem( new PlaylistItem( p_item->p_input->psz_name ) );
vlc_mutex_unlock( &p_playlist->object_lock ); FOREACH_END();
PL_UNLOCK;
vlc_object_release( p_playlist ); vlc_object_release( p_playlist );
} }
......
...@@ -464,11 +464,11 @@ NSLog( @"expandable" ); ...@@ -464,11 +464,11 @@ NSLog( @"expandable" );
playlist_t *p_playlist = pl_Yield( VLCIntf ); playlist_t *p_playlist = pl_Yield( VLCIntf );
/** \todo fix i_size use */ if( playlist_CurrentSize( p_playlist ) >= 2 )
if( p_playlist->items.i_size >= 2 )
{ {
[o_status_field setStringValue: [NSString stringWithFormat: [o_status_field setStringValue: [NSString stringWithFormat:
_NS("%i items in the playlist"), p_playlist->items.i_size]]; _NS("%i items in the playlist"),
playlist_CurrentSize( p_playlist )]];
} }
else else
{ {
...@@ -1365,13 +1365,11 @@ NSLog( @"expandable" ); ...@@ -1365,13 +1365,11 @@ NSLog( @"expandable" );
id o_value = [super outlineView: outlineView child: index ofItem: item]; id o_value = [super outlineView: outlineView child: index ofItem: item];
playlist_t *p_playlist = pl_Yield( VLCIntf ); playlist_t *p_playlist = pl_Yield( VLCIntf );
/* FIXME: playlist->i_size doesn't provide the correct number of items anymore if( playlist_CurrentSize( p_playlist ) >= 2 )
* check the playlist API for the fixed function, once zorglub implemented it -- fpk, 9/17/06 */
/** \todo fix i_size use */
if( p_playlist->items.i_size >= 2 )
{ {
[o_status_field setStringValue: [NSString stringWithFormat: [o_status_field setStringValue: [NSString stringWithFormat:
_NS("%i items in the playlist"), p_playlist->items.i_size]]; _NS("%i items in the playlist"),
playlist_CurrentSize( p_playlist )]];
} }
else else
{ {
......
...@@ -938,7 +938,7 @@ void Playlist::OnSave( wxCommandEvent& WXUNUSED(event) ) ...@@ -938,7 +938,7 @@ void Playlist::OnSave( wxCommandEvent& WXUNUSED(event) )
wxString filter = wxT(""); wxString filter = wxT("");
if( p_playlist->i_size == 0 ) if( playlist_IsEmpty( p_playlist ) )
{ {
wxMessageBox( wxU(_("Playlist is empty") ), wxU(_("Can't save")), wxMessageBox( wxU(_("Playlist is empty") ), wxU(_("Can't save")),
wxICON_WARNING | wxOK, this ); wxICON_WARNING | wxOK, this );
......
...@@ -582,7 +582,7 @@ wizInputPage::wizInputPage( wxWizard *parent, wxWizardPage *prev, intf_thread_t ...@@ -582,7 +582,7 @@ wizInputPage::wizInputPage( wxWizard *parent, wxWizardPage *prev, intf_thread_t
if( p_playlist ) if( p_playlist )
{ {
if( p_playlist->i_size > 0) if( !playlist_IsEmpty( p_playlist ) )
{ {
listview = new wxListView( this, ListView_Event, listview = new wxListView( this, ListView_Event,
wxDefaultPosition, wxDefaultSize, wxDefaultPosition, wxDefaultSize,
...@@ -591,19 +591,6 @@ wizInputPage::wizInputPage( wxWizard *parent, wxWizardPage *prev, intf_thread_t ...@@ -591,19 +591,6 @@ wizInputPage::wizInputPage( wxWizard *parent, wxWizardPage *prev, intf_thread_t
listview->InsertColumn( 1, wxU(_("URI")) ); listview->InsertColumn( 1, wxU(_("URI")) );
listview->SetColumnWidth( 0, 250 ); listview->SetColumnWidth( 0, 250 );
listview->SetColumnWidth( 1, 100 ); listview->SetColumnWidth( 1, 100 );
#if 0
for( int i=0 ; i < p_playlist->i_size ; i++ )
{
wxString filename = wxL2U( p_playlist->pp_items[i]->input.
psz_name );
listview->InsertItem( i, filename );
listview->SetItem( i, 1, wxL2U( p_playlist->pp_items[i]->
input.psz_uri) );
listview->SetItemData( i,
(long)p_playlist->pp_items[i]->input.i_id );
}
listview->Select( p_playlist->i_index , TRUE);
#endif
mainSizer->Add( listview, 1, wxALL|wxEXPAND, 5 ); mainSizer->Add( listview, 1, wxALL|wxEXPAND, 5 );
listview->Hide(); listview->Hide();
......
...@@ -271,7 +271,7 @@ int IntfAutoMenuBuilder( intf_thread_t *p_intf, ArrayOfInts &ri_objects, ...@@ -271,7 +271,7 @@ int IntfAutoMenuBuilder( intf_thread_t *p_intf, ArrayOfInts &ri_objects,
} \ } \
else \ else \
{ \ { \
if( p_playlist && p_playlist->i_size ) \ if( p_playlist && !playlist_IsEmpty( p_playlist->i_size ) ) \
{ \ { \
popupmenu.InsertSeparator( 0 ); \ popupmenu.InsertSeparator( 0 ); \
popupmenu.Insert( 0, Play_Event, wxU(_("Play")) ); \ popupmenu.Insert( 0, Play_Event, wxU(_("Play")) ); \
......
...@@ -278,10 +278,12 @@ static void ResyncCurrentIndex(playlist_t *p_playlist, playlist_item_t *p_cur ) ...@@ -278,10 +278,12 @@ static void ResyncCurrentIndex(playlist_t *p_playlist, playlist_item_t *p_cur )
PL_DEBUG("%s is at %i", PLI_NAME(p_cur), p_playlist->i_current_index ); PL_DEBUG("%s is at %i", PLI_NAME(p_cur), p_playlist->i_current_index );
} }
static void ResetCurrentlyPlaying( playlist_t *p_playlist, vlc_bool_t b_random, void ResetCurrentlyPlaying( playlist_t *p_playlist, vlc_bool_t b_random,
playlist_item_t *p_cur ) playlist_item_t *p_cur )
{ {
playlist_item_t *p_next = NULL; playlist_item_t *p_next = NULL;
stats_TimerStart( p_playlist, "Items array build",
STATS_TIMER_PLAYLIST_BUILD );
PL_DEBUG("rebuilding array of current - root %s", PL_DEBUG("rebuilding array of current - root %s",
PLI_NAME(p_playlist->status.p_node) ); PLI_NAME(p_playlist->status.p_node) );
ARRAY_RESET(p_playlist->current); ARRAY_RESET(p_playlist->current);
...@@ -319,6 +321,7 @@ static void ResetCurrentlyPlaying( playlist_t *p_playlist, vlc_bool_t b_random, ...@@ -319,6 +321,7 @@ static void ResetCurrentlyPlaying( playlist_t *p_playlist, vlc_bool_t b_random,
} }
} }
p_playlist->b_reset_currently_playing = VLC_FALSE; p_playlist->b_reset_currently_playing = VLC_FALSE;
stats_TimerStop( p_playlist, STATS_TIMER_PLAYLIST_BUILD );
} }
/** This function calculates the next playlist item, depending /** This function calculates the next playlist item, depending
...@@ -389,6 +392,7 @@ playlist_item_t * playlist_NextItem( playlist_t *p_playlist ) ...@@ -389,6 +392,7 @@ playlist_item_t * playlist_NextItem( playlist_t *p_playlist )
i_skip++; i_skip++;
if( p_playlist->b_reset_currently_playing ) if( p_playlist->b_reset_currently_playing )
/* A bit too bad to reset twice ... */
ResetCurrentlyPlaying( p_playlist, b_random, p_new ); ResetCurrentlyPlaying( p_playlist, b_random, p_new );
else if( p_new ) else if( p_new )
ResyncCurrentIndex( p_playlist, p_new ); ResyncCurrentIndex( p_playlist, p_new );
......
...@@ -38,6 +38,7 @@ static int RandomCallback( vlc_object_t *p_this, char const *psz_cmd, ...@@ -38,6 +38,7 @@ static int RandomCallback( vlc_object_t *p_this, char const *psz_cmd,
vlc_value_t oldval, vlc_value_t newval, void *a ) vlc_value_t oldval, vlc_value_t newval, void *a )
{ {
((playlist_t*)p_this)->b_reset_currently_playing = VLC_TRUE; ((playlist_t*)p_this)->b_reset_currently_playing = VLC_TRUE;
playlist_Signal( ((playlist_t*)p_this) );
return VLC_SUCCESS; return VLC_SUCCESS;
} }
...@@ -80,6 +81,7 @@ playlist_t * playlist_Create( vlc_object_t *p_parent ) ...@@ -80,6 +81,7 @@ playlist_t * playlist_Create( vlc_object_t *p_parent )
p_playlist->i_current_index = 0; p_playlist->i_current_index = 0;
p_playlist->b_reset_currently_playing = VLC_TRUE; p_playlist->b_reset_currently_playing = VLC_TRUE;
p_playlist->last_rebuild_date = 0;
i_tree = var_CreateGetBool( p_playlist, "playlist-tree" ); i_tree = var_CreateGetBool( p_playlist, "playlist-tree" );
p_playlist->b_always_tree = (i_tree == 1); p_playlist->b_always_tree = (i_tree == 1);
...@@ -244,20 +246,24 @@ void playlist_MainLoop( playlist_t *p_playlist ) ...@@ -244,20 +246,24 @@ void playlist_MainLoop( playlist_t *p_playlist )
vlc_bool_t b_playexit = var_GetBool( p_playlist, "play-and-exit" ); vlc_bool_t b_playexit = var_GetBool( p_playlist, "play-and-exit" );
PL_LOCK; PL_LOCK;
/* First, check if we have something to do */ if( p_playlist->b_reset_currently_playing &&
if( p_playlist->request.b_request ) mdate() - p_playlist->last_rebuild_date > 30000 ) // 30 ms
{ {
/* Stop the existing input */ ResetCurrentlyPlaying( p_playlist, var_GetBool( p_playlist, "random"),
if( p_playlist->p_input && !p_playlist->p_input->b_die ) p_playlist->status.p_item );
{ p_playlist->last_rebuild_date = mdate();
PL_DEBUG( "incoming request - stopping current input" );
input_StopThread( p_playlist->p_input );
}
} }
check_input: check_input:
/* If there is an input, check that it doesn't need to die. */ /* If there is an input, check that it doesn't need to die. */
if( p_playlist->p_input ) if( p_playlist->p_input )
{ {
if( p_playlist->request.b_request && !p_playlist->p_input->b_die )
{
PL_DEBUG( "incoming request - stopping current input" );
input_StopThread( p_playlist->p_input );
}
/* This input is dead. Remove it ! */ /* This input is dead. Remove it ! */
if( p_playlist->p_input->b_dead ) if( p_playlist->p_input->b_dead )
{ {
...@@ -340,10 +346,7 @@ check_input: ...@@ -340,10 +346,7 @@ check_input:
p_playlist->request.i_status != PLAYLIST_STOPPED ) ) p_playlist->request.i_status != PLAYLIST_STOPPED ) )
{ {
msg_Dbg( p_playlist, "starting new item" ); msg_Dbg( p_playlist, "starting new item" );
stats_TimerStart( p_playlist, "Playlist walk",
STATS_TIMER_PLAYLIST_WALK );
p_item = playlist_NextItem( p_playlist ); p_item = playlist_NextItem( p_playlist );
stats_TimerStop( p_playlist, STATS_TIMER_PLAYLIST_WALK );
if( p_item == NULL ) if( p_item == NULL )
{ {
......
...@@ -404,6 +404,7 @@ playlist_item_t *playlist_ItemToNode( playlist_t *p_playlist, ...@@ -404,6 +404,7 @@ playlist_item_t *playlist_ItemToNode( playlist_t *p_playlist,
p_playlist->p_root_onelevel, VLC_FALSE ); p_playlist->p_root_onelevel, VLC_FALSE );
} }
p_playlist->b_reset_currently_playing = VLC_TRUE; p_playlist->b_reset_currently_playing = VLC_TRUE;
vlc_cond_signal( &p_playlist->object_wait );
var_SetInteger( p_playlist, "item-change", p_item_in_category-> var_SetInteger( p_playlist, "item-change", p_item_in_category->
p_input->i_id ); p_input->i_id );
return p_item_in_category; return p_item_in_category;
...@@ -492,6 +493,7 @@ static int TreeMove( playlist_t *p_playlist, playlist_item_t *p_item, ...@@ -492,6 +493,7 @@ static int TreeMove( playlist_t *p_playlist, playlist_item_t *p_item,
int playlist_TreeMove( playlist_t * p_playlist, playlist_item_t *p_item, int playlist_TreeMove( playlist_t * p_playlist, playlist_item_t *p_item,
playlist_item_t *p_node, int i_newpos ) playlist_item_t *p_node, int i_newpos )
{ {
int i_ret;
/* Drop on a top level node. Move in the two trees */ /* Drop on a top level node. Move in the two trees */
if( p_node->p_parent == p_playlist->p_root_category || if( p_node->p_parent == p_playlist->p_root_category ||
p_node->p_parent == p_playlist->p_root_onelevel ) p_node->p_parent == p_playlist->p_root_onelevel )
...@@ -529,10 +531,13 @@ int playlist_TreeMove( playlist_t * p_playlist, playlist_item_t *p_item, ...@@ -529,10 +531,13 @@ int playlist_TreeMove( playlist_t * p_playlist, playlist_item_t *p_item,
if( p_node_category && p_item_category ) if( p_node_category && p_item_category )
TreeMove( p_playlist, p_item_category, p_node_category, 0 ); TreeMove( p_playlist, p_item_category, p_node_category, 0 );
} }
return VLC_SUCCESS; i_ret = VLC_SUCCESS;
} }
else else
return TreeMove( p_playlist, p_item, p_node, i_newpos ); i_ret = TreeMove( p_playlist, p_item, p_node, i_newpos );
p_playlist->b_reset_currently_playing = VLC_TRUE;
vlc_cond_signal( &p_playlist->object_wait );
return i_ret;
} }
/** Send a notification that an item has been added to a node */ /** Send a notification that an item has been added to a node */
...@@ -545,6 +550,7 @@ void playlist_SendAddNotify( playlist_t *p_playlist, int i_item_id, ...@@ -545,6 +550,7 @@ void playlist_SendAddNotify( playlist_t *p_playlist, int i_item_id,
p_add->i_node = i_node_id; p_add->i_node = i_node_id;
val.p_address = p_add; val.p_address = p_add;
p_playlist->b_reset_currently_playing = VLC_TRUE; p_playlist->b_reset_currently_playing = VLC_TRUE;
vlc_cond_signal( &p_playlist->object_wait );
var_Set( p_playlist, "item-append", val ); var_Set( p_playlist, "item-append", val );
free( p_add ); free( p_add );
} }
......
...@@ -67,6 +67,8 @@ void playlist_LastLoop( playlist_t * ); ...@@ -67,6 +67,8 @@ void playlist_LastLoop( playlist_t * );
void playlist_PreparseLoop( playlist_preparse_t * ); void playlist_PreparseLoop( playlist_preparse_t * );
void playlist_SecondaryPreparseLoop( playlist_secondary_preparse_t * ); void playlist_SecondaryPreparseLoop( playlist_secondary_preparse_t * );
void ResetCurrentlyPlaying( playlist_t *, vlc_bool_t, playlist_item_t * );
/* Control */ /* Control */
playlist_item_t * playlist_NextItem ( playlist_t * ); playlist_item_t * playlist_NextItem ( playlist_t * );
int playlist_PlayItem ( playlist_t *, playlist_item_t * ); int playlist_PlayItem ( playlist_t *, playlist_item_t * );
...@@ -98,7 +100,8 @@ playlist_item_t *playlist_GetLastLeaf( playlist_t *p_playlist, ...@@ -98,7 +100,8 @@ playlist_item_t *playlist_GetLastLeaf( playlist_t *p_playlist,
* @} * @}
*/ */
#define PLAYLIST_DEBUG 1 //#define PLAYLIST_DEBUG 1
#undef PLAYLIST_DEBUG
#ifdef PLAYLIST_DEBUG #ifdef PLAYLIST_DEBUG
#define PL_DEBUG( msg, args... ) msg_Dbg( p_playlist, msg, ## args ) #define PL_DEBUG( msg, args... ) msg_Dbg( p_playlist, msg, ## args )
......
...@@ -95,5 +95,6 @@ int playlist_LiveSearchUpdate( playlist_t *p_playlist, playlist_item_t *p_root, ...@@ -95,5 +95,6 @@ int playlist_LiveSearchUpdate( playlist_t *p_playlist, playlist_item_t *p_root,
else else
p_item->i_flags |= PLAYLIST_DBL_FLAG; p_item->i_flags |= PLAYLIST_DBL_FLAG;
} }
vlc_cond_signal( &p_playlist->object_wait );
return VLC_SUCCESS; return VLC_SUCCESS;
} }
...@@ -36,11 +36,9 @@ static void RunPreparse( playlist_preparse_t * ); ...@@ -36,11 +36,9 @@ static void RunPreparse( playlist_preparse_t * );
static void RunSecondaryPreparse( playlist_secondary_preparse_t * ); static void RunSecondaryPreparse( playlist_secondary_preparse_t * );
static playlist_t * CreatePlaylist( vlc_object_t *p_parent ); static playlist_t * CreatePlaylist( vlc_object_t *p_parent );
static void HandlePlaylist( playlist_t * );
static void EndPlaylist( playlist_t * ); static void EndPlaylist( playlist_t * );
static void DestroyPlaylist( playlist_t * ); static void DestroyPlaylist( playlist_t * );
static void HandleInteraction( playlist_t * );
static void DestroyInteraction( playlist_t * ); static void DestroyInteraction( playlist_t * );
/***************************************************************************** /*****************************************************************************
...@@ -174,8 +172,10 @@ static void RunControlThread ( playlist_t *p_playlist ) ...@@ -174,8 +172,10 @@ static void RunControlThread ( playlist_t *p_playlist )
{ {
i_loops++; i_loops++;
HandleInteraction( p_playlist ); if( p_playlist->p_interaction )
HandlePlaylist( p_playlist ); intf_InteractionManage( p_playlist );
playlist_MainLoop( p_playlist );
if( p_playlist->b_cant_sleep ) if( p_playlist->b_cant_sleep )
{ {
/* 100 ms is an acceptable delay for playlist operations */ /* 100 ms is an acceptable delay for playlist operations */
...@@ -206,11 +206,6 @@ static void DestroyPlaylist( playlist_t *p_playlist ) ...@@ -206,11 +206,6 @@ static void DestroyPlaylist( playlist_t *p_playlist )
playlist_Destroy( p_playlist ); playlist_Destroy( p_playlist );
} }
static void HandlePlaylist( playlist_t *p_playlist )
{
playlist_MainLoop( p_playlist );
}
static void EndPlaylist( playlist_t *p_playlist ) static void EndPlaylist( playlist_t *p_playlist )
{ {
playlist_LastLoop( p_playlist ); playlist_LastLoop( p_playlist );
...@@ -245,14 +240,3 @@ static void DestroyInteraction( playlist_t *p_playlist ) ...@@ -245,14 +240,3 @@ static void DestroyInteraction( playlist_t *p_playlist )
p_playlist->p_interaction = NULL; p_playlist->p_interaction = NULL;
} }
} }
static void HandleInteraction( playlist_t *p_playlist )
{
if( p_playlist->p_interaction )
{
stats_TimerStart( p_playlist, "Interaction thread",
STATS_TIMER_INTERACTION );
intf_InteractionManage( p_playlist );
stats_TimerStop( p_playlist, STATS_TIMER_INTERACTION );
}
}
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment