25 #include "blogcomment.h"
27 #include <syndication/loader.h>
28 #include <syndication/item.h>
29 #include <syndication/category.h>
31 #include <kio/netaccess.h>
34 #include <KLocalizedString>
39 #include <QDomDocument>
43 using namespace KBlog;
46 :
Blog( server, *new GDataPrivate, parent )
66 return d_func()->mFullName;
79 return d_func()->mProfileId;
93 KIO::StoredTransferJob *job = KIO::storedGet(
url(), KIO::NoReload, KIO::HideProgressInfo );
95 connect( job, SIGNAL(result(KJob*)),
96 this, SLOT(slotFetchProfileId(KJob*)) );
102 Syndication::Loader *loader = Syndication::Loader::create();
104 SIGNAL(loadingComplete(Syndication::Loader*,Syndication::FeedPtr,Syndication::ErrorCode)),
106 SLOT(slotListBlogs(Syndication::Loader*,Syndication::FeedPtr,Syndication::ErrorCode)) );
111 const KDateTime &upMinTime,
const KDateTime &upMaxTime,
112 const KDateTime &pubMinTime,
const KDateTime &pubMaxTime )
117 if ( ! labels.
empty() ) {
120 kDebug() <<
"listRecentPosts()";
121 KUrl
url( urlString );
123 if ( !upMinTime.isNull() ) {
124 url.addQueryItem(
QLatin1String(
"updated-min"), upMinTime.toString() );
127 if ( !upMaxTime.isNull() ) {
128 url.addQueryItem(
QLatin1String(
"updated-max"), upMaxTime.toString() );
131 if ( !pubMinTime.isNull() ) {
132 url.addQueryItem(
QLatin1String(
"published-min"), pubMinTime.toString() );
135 if ( !pubMaxTime.isNull() ) {
136 url.addQueryItem(
QLatin1String(
"published-max"), pubMaxTime.toString() );
139 Syndication::Loader *loader = Syndication::Loader::create();
141 d->mListRecentPostsMap[ loader ] = number;
144 SIGNAL(loadingComplete(Syndication::Loader*,Syndication::FeedPtr,Syndication::ErrorCode)),
146 SLOT(slotListRecentPosts(Syndication::Loader*,Syndication::FeedPtr,Syndication::ErrorCode)) );
147 loader->loadFrom( url.url() );
160 Syndication::Loader *loader = Syndication::Loader::create();
161 d->mListCommentsMap[ loader ] = post;
163 SIGNAL(loadingComplete(Syndication::Loader*,Syndication::FeedPtr,Syndication::ErrorCode)),
165 SLOT(slotListComments(Syndication::Loader*,Syndication::FeedPtr,Syndication::ErrorCode)) );
173 Syndication::Loader *loader = Syndication::Loader::create();
175 SIGNAL(loadingComplete(Syndication::Loader*,Syndication::FeedPtr,Syndication::ErrorCode)),
177 SLOT(slotListAllComments(Syndication::Loader*,Syndication::FeedPtr,Syndication::ErrorCode)) );
187 kError() <<
"post is null pointer";
192 Syndication::Loader *loader = Syndication::Loader::create();
193 d->mFetchPostMap[ loader ] = post;
195 SIGNAL(loadingComplete(Syndication::Loader*,Syndication::FeedPtr,Syndication::ErrorCode)),
197 SLOT(slotFetchPost(Syndication::Loader*,Syndication::FeedPtr,Syndication::ErrorCode)) );
207 kError() <<
"post is null pointer";
211 if ( !d->authenticate() ) {
212 kError() <<
"Authentication failed.";
213 emit
errorPost(
Atom, i18n(
"Authentication failed." ), post );
224 atomMarkup +=
QLatin1String(
"<app:control xmlns:app='http://purl.org/atom/app#'>");
225 atomMarkup +=
QLatin1String(
"<app:draft>yes</app:draft></app:control>");
228 atomMarkup +=
QLatin1String(
"<div xmlns='http://www.w3.org/1999/xhtml'>");
233 for ( ; it != end; ++it ) {
244 QDataStream stream( &postData, QIODevice::WriteOnly );
247 KIO::StoredTransferJob *job = KIO::storedHttpPost( postData,
249 KIO::HideProgressInfo );
253 d->mModifyPostMap[ job ] = post;
259 QLatin1String(
"Authorization: GoogleLogin auth=") + d->mAuthenticationString +
262 connect( job, SIGNAL(result(KJob*)),
263 this, SLOT(slotModifyPost(KJob*)) );
272 kError() <<
"post is null pointer";
276 if ( !d->authenticate() ) {
277 kError() <<
"Authentication failed.";
278 emit
errorPost(
Atom, i18n(
"Authentication failed." ), post );
285 atomMarkup +=
QLatin1String(
"<app:control xmlns:app='http://purl.org/atom/app#'>");
286 atomMarkup +=
QLatin1String(
"<app:draft>yes</app:draft></app:control>");
289 atomMarkup +=
QLatin1String(
"<div xmlns='http://www.w3.org/1999/xhtml'>");
294 for ( ; it != end; ++it ) {
306 QDataStream stream( &postData, QIODevice::WriteOnly );
309 KIO::StoredTransferJob *job = KIO::storedHttpPost( postData,
311 KIO::HideProgressInfo );
314 d->mCreatePostMap[ job ] = post;
320 QLatin1String(
"Authorization: GoogleLogin auth=") + d->mAuthenticationString );
322 connect( job, SIGNAL(result(KJob*)),
323 this, SLOT(slotCreatePost(KJob*)) );
332 kError() <<
"post is null pointer";
336 if ( !d->authenticate() ) {
337 kError() <<
"Authentication failed.";
338 emit
errorPost(
Atom, i18n(
"Authentication failed." ), post );
344 KIO::StoredTransferJob *job = KIO::storedHttpPost( postData,
346 KIO::HideProgressInfo );
348 d->mRemovePostMap[ job ] = post;
351 kWarning() <<
"Unable to create KIO job for http://www.blogger.com/feeds/"
358 QLatin1String(
"Authorization: GoogleLogin auth=") + d->mAuthenticationString +
361 connect( job, SIGNAL(result(KJob*)),
362 this, SLOT(slotRemovePost(KJob*)) );
370 kError() <<
"comment is null pointer";
375 kError() <<
"post is null pointer";
380 if ( !d->authenticate() ) {
381 kError() <<
"Authentication failed.";
394 kDebug() << postData;
395 QDataStream stream( &postData, QIODevice::WriteOnly );
398 KIO::StoredTransferJob *job = KIO::storedHttpPost( postData,
400 KIO::HideProgressInfo );
402 d->mCreateCommentMap[ job ][post] = comment;
405 kWarning() <<
"Unable to create KIO job for http://www.blogger.com/feeds/"
406 <<
blogId() <<
"/" << post->
postId() <<
"/comments/default";
412 QLatin1String(
"Authorization: GoogleLogin auth=") + d->mAuthenticationString );
415 connect( job, SIGNAL(result(KJob*)),
416 this, SLOT(slotCreateComment(KJob*)) );
426 kError() <<
"comment is null pointer";
431 kError() <<
"post is null pointer";
435 if ( !d->authenticate() ) {
436 kError() <<
"Authentication failed.";
443 KIO::StoredTransferJob *job = KIO::storedHttpPost(postData,
446 d->mRemoveCommentMap[ job ][ post ] = comment;
449 kWarning() <<
"Unable to create KIO job for http://www.blogger.com/feeds/"
451 <<
"/comments/default/" << comment->
commentId();
458 d->mAuthenticationString +
QLatin1String(
"\r\nX-HTTP-Method-Override: DELETE") );
460 connect( job, SIGNAL(result(KJob*)),
461 this, SLOT(slotRemoveComment(KJob*)) );
464 GDataPrivate::GDataPrivate():mAuthenticationString(), mAuthenticationTime()
469 GDataPrivate::~GDataPrivate()
474 bool GDataPrivate::authenticate()
479 KUrl authGateway(
QLatin1String(
"https://www.google.com/accounts/ClientLogin") );
480 authGateway.addQueryItem(
QLatin1String(
"Email"), q->username() );
481 authGateway.addQueryItem(
QLatin1String(
"Passwd"), q->password() );
482 authGateway.addQueryItem(
QLatin1String(
"source"), q->userAgent() );
484 if ( !mAuthenticationTime.isValid() ||
486 mAuthenticationString.isEmpty() ) {
487 KIO::Job *job = KIO::http_post( authGateway,
QByteArray(), KIO::HideProgressInfo );
488 if ( KIO::NetAccess::synchronousRun( job, (
QWidget*)0, &data, &authGateway ) ) {
491 kDebug() <<
"RegExp got authentication string:" << rx.cap( 1 );
492 mAuthenticationString = rx.cap( 1 );
502 void GDataPrivate::slotFetchProfileId( KJob *job )
506 kError() <<
"job is a null pointer.";
510 KIO::StoredTransferJob *stj = qobject_cast<KIO::StoredTransferJob*>( job );
512 if ( !job->error() ) {
514 if ( pid.indexIn( data ) != -1 ) {
515 q->setProfileId( pid.cap( 1 ) );
516 kDebug() <<
"QRegExp bid( 'http://www.blogger.com/profile/(\\d+)' matches" << pid.cap( 1 );
517 emit q->fetchedProfileId( pid.cap( 1 ) );
519 kError() <<
"QRegExp bid( 'http://www.blogger.com/profile/(\\d+)' "
520 <<
" could not regexp the Profile ID";
521 emit q->error(
GData::Other, i18n(
"Could not regexp the Profile ID." ) );
522 emit q->fetchedProfileId(
QString() );
525 kError() <<
"Job Error: " << job->errorString();
527 emit q->fetchedProfileId(
QString() );
531 void GDataPrivate::slotListBlogs( Syndication::Loader *loader,
532 Syndication::FeedPtr feed,
533 Syndication::ErrorCode status ) {
537 kError() <<
"loader is a null pointer.";
540 if ( status != Syndication::Success ) {
541 emit q->error(
GData::Atom, i18n(
"Could not get blogs." ) );
550 for ( ; it != end; ++it ) {
553 if ( rx.indexIn( ( *it )->id() ) != -1 ) {
554 kDebug() <<
"QRegExp rx( 'blog-(\\d+)' matches" << rx.cap( 1 );
559 blogsList << blogInfo;
561 kError() <<
"QRegExp rx( 'blog-(\\d+)' does not match anything in:"
563 emit q->error(
GData::Other, i18n(
"Could not regexp the blog id path." ) );
566 kDebug() <<
"Emitting listedBlogs(); ";
567 emit q->listedBlogs( blogsList );
570 void GDataPrivate::slotListComments( Syndication::Loader *loader,
571 Syndication::FeedPtr feed,
572 Syndication::ErrorCode status )
577 kError() <<
"loader is a null pointer.";
580 BlogPost *post = mListCommentsMap[ loader ];
581 mListCommentsMap.remove( loader );
583 if ( status != Syndication::Success ) {
584 emit q->errorPost(
GData::Atom, i18n(
"Could not get comments." ), post );
593 for ( ; it != end; ++it ) {
596 if ( rx.indexIn( ( *it )->id() ) == -1 ) {
597 kError() <<
"QRegExp rx( 'post-(\\d+)' does not match" << rx.cap( 1 );
598 emit q->error(
GData::Other, i18n(
"Could not regexp the comment id path." ) );
602 kDebug() <<
"QRegExp rx( 'post-(\\d+)' matches" << rx.cap( 1 );
603 comment.
setTitle( ( *it )->title() );
608 KDateTime::Spec::UTC() ) );
611 KDateTime::Spec::UTC() ) );
612 commentList.
append( comment );
614 kDebug() <<
"Emitting listedComments()";
615 emit q->listedComments( post, commentList );
618 void GDataPrivate::slotListAllComments( Syndication::Loader *loader,
619 Syndication::FeedPtr feed,
620 Syndication::ErrorCode status )
625 kError() <<
"loader is a null pointer.";
629 if ( status != Syndication::Success ) {
630 emit q->error(
GData::Atom, i18n(
"Could not get comments." ) );
639 for ( ; it != end; ++it ) {
642 if ( rx.indexIn( ( *it )->id() ) == -1 ) {
643 kError() <<
"QRegExp rx( 'post-(\\d+)' does not match" << rx.cap( 1 );
644 emit q->error(
GData::Other, i18n(
"Could not regexp the comment id path." ) );
649 kDebug() <<
"QRegExp rx( 'post-(\\d+)' matches" << rx.cap( 1 );
650 comment.
setTitle( ( *it )->title() );
655 KDateTime::Spec::UTC() ) );
658 KDateTime::Spec::UTC() ) );
659 commentList.
append( comment );
661 kDebug() <<
"Emitting listedAllComments()";
662 emit q->listedAllComments( commentList );
665 void GDataPrivate::slotListRecentPosts( Syndication::Loader *loader,
666 Syndication::FeedPtr feed,
667 Syndication::ErrorCode status ) {
671 kError() <<
"loader is a null pointer.";
675 if ( status != Syndication::Success ) {
676 emit q->error(
GData::Atom, i18n(
"Could not get posts." ) );
681 if ( mListRecentPostsMap.contains( loader ) ) {
682 number = mListRecentPostsMap[ loader ];
684 mListRecentPostsMap.remove( loader );
691 for ( ; it != end; ++it ) {
694 if ( rx.indexIn( ( *it )->id() ) == -1 ) {
695 kError() <<
"QRegExp rx( 'post-(\\d+)' does not match" << rx.cap( 1 );
696 emit q->error(
GData::Other, i18n(
"Could not regexp the post id path." ) );
701 kDebug() <<
"QRegExp rx( 'post-(\\d+)' matches" << rx.cap( 1 );
704 post.
setLink( ( *it )->link() );
706 int catCount = ( *it )->categories().
count();
708 for (
int i=0; i < catCount; ++i ) {
709 if ( cats[i].
get()->label().isEmpty() ) {
710 labels.
append( cats[i].
get()->term() );
712 labels.
append( cats[i].
get()->label() );
719 KDateTime::Spec::UTC() ).toLocalZone() );
722 KDateTime::Spec::UTC() ).toLocalZone() );
725 if ( number-- == 0 ) {
729 kDebug() <<
"Emitting listedRecentPosts()";
730 emit q->listedRecentPosts( postList );
733 void GDataPrivate::slotFetchPost( Syndication::Loader *loader,
734 Syndication::FeedPtr feed,
735 Syndication::ErrorCode status )
740 kError() <<
"loader is a null pointer.";
744 bool success =
false;
746 BlogPost *post = mFetchPostMap.take( loader );
747 kError() <<
"Post" << post;
750 if ( status != Syndication::Success ) {
751 emit q->errorPost(
GData::Atom, i18n(
"Could not get posts." ), post );
759 for ( ; it != end; ++it ) {
761 if ( rx.indexIn( ( *it )->id() ) != -1 &&
762 rx.cap( 1 ) == postId ) {
763 kDebug() <<
"QRegExp rx( 'post-(\\d+)' matches" << rx.cap( 1 );
768 post->
setLink( ( *it )->link() );
771 KDateTime::Spec::UTC() ).toLocalZone() );
774 KDateTime::Spec::UTC() ).toLocalZone() );
775 kDebug() <<
"Emitting fetchedPost( postId=" << postId <<
");";
777 emit q->fetchedPost( post );
782 kError() <<
"QRegExp rx( 'post-(\\d+)' does not match"
783 << mFetchPostMap[ loader ]->postId() <<
".";
784 emit q->errorPost(
GData::Other, i18n(
"Could not regexp the blog id path." ), post );
788 void GDataPrivate::slotCreatePost( KJob *job )
792 kError() <<
"job is a null pointer.";
795 KIO::StoredTransferJob *stj = qobject_cast<KIO::StoredTransferJob*>( job );
801 mCreatePostMap.remove( job );
803 if ( job->error() != 0 ) {
804 kError() <<
"slotCreatePost error:" << job->errorString();
805 emit q->errorPost(
GData::Atom, job->errorString(), post );
810 if ( rxId.indexIn( data ) == -1 ) {
811 kError() <<
"Could not regexp the id out of the result:" << data;
813 i18n(
"Could not regexp the id out of the result." ), post );
816 kDebug() <<
"QRegExp rx( 'post-(\\d+)' ) matches" << rxId.cap( 1 );
819 if ( rxPub.indexIn( data ) == -1 ) {
820 kError() <<
"Could not regexp the published time out of the result:" << data;
822 i18n(
"Could not regexp the published time out of the result." ), post );
825 kDebug() <<
"QRegExp rx( '<published>(.+)</published>' ) matches" << rxPub.cap( 1 );
828 if ( rxUp.indexIn( data ) == -1 ) {
829 kError() <<
"Could not regexp the update time out of the result:" << data;
831 i18n(
"Could not regexp the update time out of the result." ), post );
834 kDebug() <<
"QRegExp rx( '<updated>(.+)</updated>' ) matches" << rxUp.cap( 1 );
840 kDebug() <<
"Emitting createdPost()";
841 emit q->createdPost( post );
844 void GDataPrivate::slotModifyPost( KJob *job )
848 kError() <<
"job is a null pointer.";
851 KIO::StoredTransferJob *stj = qobject_cast<KIO::StoredTransferJob*>( job );
855 mModifyPostMap.remove( job );
857 if ( job->error() != 0 ) {
858 kError() <<
"slotModifyPost error:" << job->errorString();
859 emit q->errorPost(
GData::Atom, job->errorString(), post );
864 if ( rxId.indexIn( data ) == -1 ) {
865 kError() <<
"Could not regexp the id out of the result:" << data;
867 i18n(
"Could not regexp the id out of the result." ), post );
870 kDebug() <<
"QRegExp rx( 'post-(\\d+)' ) matches" << rxId.cap( 1 );
873 if ( rxPub.indexIn( data ) == -1 ) {
874 kError() <<
"Could not regexp the published time out of the result:" << data;
876 i18n(
"Could not regexp the published time out of the result." ), post );
879 kDebug() <<
"QRegExp rx( '<published>(.+)</published>' ) matches" << rxPub.cap( 1 );
882 if ( rxUp.indexIn( data ) == -1 ) {
883 kError() <<
"Could not regexp the update time out of the result:" << data;
885 i18n(
"Could not regexp the update time out of the result." ), post );
888 kDebug() <<
"QRegExp rx( '<updated>(.+)</updated>' ) matches" << rxUp.cap( 1 );
889 post->setPostId( rxId.cap( 1 ) );
890 post->setCreationDateTime( KDateTime().fromString( rxPub.cap( 1 ) ) );
891 post->setModificationDateTime( KDateTime().fromString( rxUp.cap( 1 ) ) );
893 emit q->modifiedPost( post );
896 void GDataPrivate::slotRemovePost( KJob *job )
900 kError() <<
"job is a null pointer.";
903 KIO::StoredTransferJob *stj = qobject_cast<KIO::StoredTransferJob*>( job );
907 mRemovePostMap.remove( job );
909 if ( job->error() != 0 ) {
910 kError() <<
"slotRemovePost error:" << job->errorString();
911 emit q->errorPost(
GData::Atom, job->errorString(), post );
916 kDebug() <<
"Emitting removedPost()";
917 emit q->removedPost( post );
920 void GDataPrivate::slotCreateComment( KJob *job )
924 kError() <<
"job is a null pointer.";
927 KIO::StoredTransferJob *stj = qobject_cast<KIO::StoredTransferJob*>( job );
929 kDebug() <<
"Dump data: " << data;
935 mCreateCommentMap.remove( job );
937 if ( job->error() != 0 ) {
938 kError() <<
"slotCreateComment error:" << job->errorString();
939 emit q->errorComment(
GData::Atom, job->errorString(), post, comment );
945 if ( rxId.indexIn( data ) == -1 ) {
946 kError() <<
"Could not regexp the id out of the result:" << data;
948 i18n(
"Could not regexp the id out of the result." ), post );
951 kDebug() <<
"QRegExp rx( 'post-(\\d+)' ) matches" << rxId.cap( 1 );
954 if ( rxPub.indexIn( data ) == -1 ) {
955 kError() <<
"Could not regexp the published time out of the result:" << data;
957 i18n(
"Could not regexp the published time out of the result." ), post );
960 kDebug() <<
"QRegExp rx( '<published>(.+)</published>' ) matches" << rxPub.cap( 1 );
963 if ( rxUp.indexIn( data ) == -1 ) {
964 kError() <<
"Could not regexp the update time out of the result:" << data;
966 i18n(
"Could not regexp the update time out of the result." ), post );
969 kDebug() <<
"QRegExp rx( '<updated>(.+)</updated>' ) matches" << rxUp.cap( 1 );
974 kDebug() <<
"Emitting createdComment()";
975 emit q->createdComment( post, comment );
978 void GDataPrivate::slotRemoveComment( KJob *job )
982 kError() <<
"job is a null pointer.";
985 KIO::StoredTransferJob *stj = qobject_cast<KIO::StoredTransferJob*>( job );
992 mRemoveCommentMap.remove( job );
994 if ( job->error() != 0 ) {
995 kError() <<
"slotRemoveComment error:" << job->errorString();
996 emit q->errorComment(
GData::Atom, job->errorString(), post, comment );
1001 kDebug() <<
"Emitting removedComment()";
1002 emit q->removedComment( post, comment );
1005 #include "moc_gdata.cpp"
QString title() const
Returns the title.
virtual void setUrl(const KUrl &url)
Sets the URL for the blog's XML-RPC interface.
KDateTime creationDateTime() const
Returns the creation date time.
void modifyPost(KBlog::BlogPost *post)
Modify a post on server.
Status of a successfully fetched post.
void setLink(const KUrl &link) const
Set the link path.
void removePost(KBlog::BlogPost *post)
Remove a post from the server.
virtual void listBlogs()
List the blogs available for this authentication on the server.
QStringList tags() const
Returns the tags list as a QStringList.
QString profileId() const
Returns the profile id of the blog.
Status of a successfully created post.
A class that can be used for access to GData blogs.
GData(const KUrl &server, QObject *parent=0)
Create an object for GData.
QString join(const QString &separator) const
void setTitle(const QString &title)
Sets the title.
virtual void removeComment(KBlog::BlogPost *post, KBlog::BlogComment *comment)
Remove a comment from the server.
void setPostId(const QString &postId)
Sets the post id value.
QString username() const
Returns the username used in blog authentication.
virtual void listAllComments()
List the all comments available for this authentication on the server.
QDateTime fromTime_t(uint seconds)
void setCreationDateTime(const KDateTime &datetime)
Sets the creation time.
int count(const T &value) const
void append(const T &value)
QString fromUtf8(const char *str, int size)
virtual void setProfileId(const QString &pid)
Get the profile's id of the blog.
This file is part of the for accessing Blog Servers and defines the GData class.
virtual void createComment(KBlog::BlogPost *post, KBlog::BlogComment *comment)
Create a comment on the server.
void errorPost(KBlog::Blog::ErrorType type, const QString &errorMessage, KBlog::BlogPost *post)
This signal is emitted when an error occurs with XML parsing or a structural problem in an operation ...
virtual void setFullName(const QString &fullName)
Sets the user's name for the blog.
void setContent(const QString &content)
Sets the content.
KDateTime modificationDateTime() const
Returns the modification date time.
void setModificationDateTime(const KDateTime &datetime)
Sets the modification time.
QString blogId() const
Returns the unique ID for the specific blog on the server.
void setStatus(Status status)
Sets the status.
Status of a successfully modified post.
KUrl url() const
Get the URL for the blog's XML-RPC interface.
void listRecentPosts(int number)
List recent posts on the server.
void fetchPost(KBlog::BlogPost *post)
Fetch the Post with a specific id.
QString fullName() const
Returns the full name of user of the blog.
QString content() const
Returns the content.
virtual void listComments(KBlog::BlogPost *post)
List the comments available for this post on the server.
QDateTime currentDateTime()
An error in the syndication client.
A class that represents a blog post on the server.
QString interfaceName() const
Returns the of the inherited object.
int writeRawData(const char *s, int len)
QString fromLatin1(const char *str, int size)
void fetchProfileId()
Get information about the profile from the blog.
void setTags(const QStringList &tags)
Set the tags list.
const_iterator constEnd() const
const_iterator constBegin() const
A class that provides methods to call functions on a supported blog web application.
bool connect(const QObject *sender, const char *signal, const QObject *receiver, const char *method, Qt::ConnectionType type)
Status of a successfully removed post.
void createPost(KBlog::BlogPost *post)
Create a new post on server.
void errorComment(KBlog::Blog::ErrorType type, const QString &errorMessage, KBlog::BlogPost *post, KBlog::BlogComment *comment)
This signal is emitted when an error occurs with XML parsing or a structural problem in an operation ...
bool isPrivate() const
Returns if the post is published or not.
Any other miscellaneous error.
QString postId() const
Returns the postId.
QString userAgent() const
Returns the HTTP user agent string used to make the HTTP requests.
QByteArray toUtf8() const