File indexing completed on 2025-01-19 04:56:36

0001 /*
0002  * SPDX-FileCopyrightText: 2014 Kevin Ottens <ervin@kde.org>
0003  * SPDX-FileCopyrightText: 2014 Rémi Benoit <r3m1.benoit@gmail.com>
0004  * SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
0005  */
0006 
0007 
0008 #include "akonadiserializer.h"
0009 
0010 #include <Akonadi/Collection>
0011 #include <Akonadi/EntityDisplayAttribute>
0012 #include <Akonadi/Item>
0013 #include <KCalendarCore/Todo>
0014 #if __has_include(<kcalcore_version.h>)
0015 #include <kcalcore_version.h>
0016 #else
0017 #include <kcalendarcore_version.h>
0018 #endif
0019 #include <QMimeDatabase>
0020 
0021 #include <numeric>
0022 
0023 #include "utils/mem_fn.h"
0024 
0025 #include "akonadi/akonadiapplicationselectedattribute.h"
0026 #include "akonadi/akonaditimestampattribute.h"
0027 
0028 using namespace Akonadi;
0029 
0030 Serializer::Serializer()
0031 {
0032 }
0033 
0034 Serializer::~Serializer()
0035 {
0036 }
0037 
0038 bool Serializer::representsCollection(SerializerInterface::QObjectPtr object, Collection collection)
0039 {
0040     return object->property("collectionId").toLongLong() == collection.id();
0041 }
0042 
0043 bool Serializer::representsItem(QObjectPtr object, Item item)
0044 {
0045     return object->property("itemId").toLongLong() == item.id();
0046 }
0047 
0048 QString Serializer::itemUid(const Item &item)
0049 {
0050     if (item.hasPayload<KCalendarCore::Todo::Ptr>()) {
0051         const auto todo = item.payload<KCalendarCore::Todo::Ptr>();
0052         return todo->uid();
0053     } else {
0054         return QString();
0055     }
0056 }
0057 
0058 Domain::DataSource::Ptr Serializer::createDataSourceFromCollection(Collection collection, DataSourceNameScheme naming)
0059 {
0060     if (!collection.isValid())
0061         return Domain::DataSource::Ptr();
0062 
0063     auto dataSource = Domain::DataSource::Ptr::create();
0064     updateDataSourceFromCollection(dataSource, collection, naming);
0065     return dataSource;
0066 }
0067 
0068 void Serializer::updateDataSourceFromCollection(Domain::DataSource::Ptr dataSource, Collection collection, DataSourceNameScheme naming)
0069 {
0070     if (!collection.isValid())
0071         return;
0072 
0073     QString name = collection.displayName();
0074 
0075     if (naming == FullPath) {
0076         auto parent = collection.parentCollection();
0077         while (parent.isValid() && parent != Akonadi::Collection::root()) {
0078             name = parent.displayName() + " » " + name;
0079             parent = parent.parentCollection();
0080         }
0081     }
0082 
0083     dataSource->setName(name);
0084 
0085     const auto mimeTypes = collection.contentMimeTypes();
0086     auto types = Domain::DataSource::ContentTypes();
0087     if (mimeTypes.contains(KCalendarCore::Todo::todoMimeType()))
0088         types |= Domain::DataSource::Tasks;
0089     dataSource->setContentTypes(types);
0090 
0091     if (collection.hasAttribute<Akonadi::EntityDisplayAttribute>()) {
0092         auto iconName = collection.attribute<Akonadi::EntityDisplayAttribute>()->iconName();
0093         dataSource->setIconName(iconName);
0094     }
0095 
0096     if (!collection.hasAttribute<Akonadi::ApplicationSelectedAttribute>()) {
0097         dataSource->setSelected(true);
0098     } else {
0099         auto isSelected = collection.attribute<Akonadi::ApplicationSelectedAttribute>()->isSelected();
0100         dataSource->setSelected(isSelected);
0101     }
0102 
0103     dataSource->setProperty("collectionId", collection.id());
0104 }
0105 
0106 Collection Serializer::createCollectionFromDataSource(Domain::DataSource::Ptr dataSource)
0107 {
0108     const auto id = dataSource->property("collectionId").value<Collection::Id>();
0109     auto collection = Collection(id);
0110 
0111     collection.attribute<Akonadi::TimestampAttribute>(Akonadi::Collection::AddIfMissing);
0112 
0113     auto selectedAttribute = collection.attribute<Akonadi::ApplicationSelectedAttribute>(Akonadi::Collection::AddIfMissing);
0114     selectedAttribute->setSelected(dataSource->isSelected());
0115 
0116     return collection;
0117 }
0118 
0119 bool Serializer::isSelectedCollection(Collection collection)
0120 {
0121     if (!isTaskCollection(collection))
0122         return false;
0123 
0124     if (!collection.hasAttribute<Akonadi::ApplicationSelectedAttribute>())
0125         return true;
0126 
0127     return collection.attribute<Akonadi::ApplicationSelectedAttribute>()->isSelected();
0128 }
0129 
0130 bool Akonadi::Serializer::isTaskCollection(Akonadi::Collection collection)
0131 {
0132     return collection.contentMimeTypes().contains(KCalendarCore::Todo::todoMimeType());
0133 }
0134 
0135 bool Serializer::isTaskItem(Item item)
0136 {
0137     if (!item.hasPayload<KCalendarCore::Todo::Ptr>())
0138         return false;
0139 
0140     return !isProjectItem(item) && !isContext(item);
0141 }
0142 
0143 Domain::Task::Ptr Serializer::createTaskFromItem(Item item)
0144 {
0145     if (!isTaskItem(item))
0146         return Domain::Task::Ptr();
0147 
0148     auto task = Domain::Task::Ptr::create();
0149     updateTaskFromItem(task, item);
0150     return task;
0151 }
0152 
0153 void Serializer::updateTaskFromItem(Domain::Task::Ptr task, Item item)
0154 {
0155     if (!isTaskItem(item))
0156         return;
0157 
0158     auto todo = item.payload<KCalendarCore::Todo::Ptr>();
0159 
0160     task->setTitle(todo->summary());
0161     task->setText(todo->description());
0162     task->setDone(todo->isCompleted());
0163     task->setDoneDate(todo->completed().toLocalTime().date());
0164     task->setStartDate(todo->dtStart().toLocalTime().date());
0165     task->setDueDate(todo->dtDue().toLocalTime().date());
0166     task->setProperty("itemId", item.id());
0167     task->setProperty("parentCollectionId", item.parentCollection().id());
0168     task->setProperty("todoUid", todo->uid());
0169     task->setProperty("relatedUid", todo->relatedTo());
0170     task->setRunning(todo->customProperty(Serializer::customPropertyAppName(), Serializer::customPropertyIsRunning()) == QLatin1StringView("1"));
0171     const auto contextUids = todo->customProperty(Serializer::customPropertyAppName(),
0172                                                   Serializer::customPropertyContextList()).split(',', Qt::SkipEmptyParts);
0173     task->setProperty("contextUids", contextUids);
0174 
0175     switch (todo->recurrence()->recurrenceType()) {
0176     case KCalendarCore::Recurrence::rDaily:
0177         task->setRecurrence(Domain::Task::RecursDaily);
0178         break;
0179     case KCalendarCore::Recurrence::rWeekly:
0180         task->setRecurrence(Domain::Task::RecursWeekly);
0181         break;
0182     case KCalendarCore::Recurrence::rMonthlyDay:
0183         task->setRecurrence(Domain::Task::RecursMonthly);
0184         break;
0185     case KCalendarCore::Recurrence::rYearlyMonth:
0186         task->setRecurrence(Domain::Task::RecursYearly);
0187         break;
0188     default:
0189         // Other cases are not supported for now and as such just ignored
0190         break;
0191     }
0192 
0193     QMimeDatabase mimeDb;
0194     const auto attachmentsInput = todo->attachments();
0195     Domain::Task::Attachments attachments;
0196     attachments.reserve(attachmentsInput.size());
0197     std::transform(attachmentsInput.cbegin(), attachmentsInput.cend(),
0198                    std::back_inserter(attachments),
0199                    [&mimeDb] (const KCalendarCore::Attachment &attach) {
0200                        Domain::Task::Attachment attachment;
0201                        if (attach.isUri())
0202                            attachment.setUri(QUrl(attach.uri()));
0203                        else
0204                            attachment.setData(attach.decodedData());
0205                        attachment.setLabel(attach.label());
0206                        attachment.setMimeType(attach.mimeType());
0207                        attachment.setIconName(mimeDb.mimeTypeForName(attach.mimeType()).iconName());
0208                        return attachment;
0209                    });
0210     task->setAttachments(attachments);
0211 }
0212 
0213 bool Serializer::isTaskChild(Domain::Task::Ptr task, Akonadi::Item item)
0214 {
0215     if (!isTaskItem(item))
0216         return false;
0217 
0218     auto todo = item.payload<KCalendarCore::Todo::Ptr>();
0219     if (todo->relatedTo() == task->property("todoUid"))
0220         return true;
0221 
0222     return false;
0223 }
0224 
0225 Akonadi::Item Serializer::createItemFromTask(Domain::Task::Ptr task)
0226 {
0227     auto todo = KCalendarCore::Todo::Ptr::create();
0228 
0229     todo->setSummary(task->title());
0230     todo->setDescription(task->text());
0231 
0232     // We only support all-day todos, so ignore timezone information and possible effect from timezone on dates
0233     // KCalendarCore reads "DUE;VALUE=DATE:20171130" as QDateTime(QDate(2017, 11, 30), QTime(), Qt::LocalTime), for lack of timezone information
0234     // so we should never call toUtc() on that, it would mess up the date.
0235     // If one day we want to support time information, we need to add a task->isAllDay()/setAllDay().
0236     todo->setDtStart(task->startDate().startOfDay());
0237     todo->setDtDue(task->dueDate().startOfDay());
0238     todo->setAllDay(true);
0239 
0240     if (task->property("todoUid").isValid()) {
0241         todo->setUid(task->property("todoUid").toString());
0242     }
0243 
0244     if (task->property("relatedUid").isValid()) {
0245         todo->setRelatedTo(task->property("relatedUid").toString());
0246     }
0247 
0248     if (task->property("contextUids").isValid()) {
0249         todo->setCustomProperty(customPropertyAppName(),
0250                                 customPropertyContextList(),
0251                                 task->property("contextUids").toStringList().join(','));
0252     }
0253 
0254     switch (task->recurrence()) {
0255     case Domain::Task::NoRecurrence:
0256         break;
0257     case Domain::Task::RecursDaily:
0258         todo->recurrence()->setDaily(1);
0259         break;
0260     case Domain::Task::RecursWeekly:
0261         todo->recurrence()->setWeekly(1);
0262         break;
0263     case Domain::Task::RecursMonthly:
0264         todo->recurrence()->setMonthly(1);
0265         break;
0266     case Domain::Task::RecursYearly:
0267         todo->recurrence()->setYearly(1);
0268         break;
0269     }
0270 
0271     for (const auto &attachment : task->attachments()) {
0272         KCalendarCore::Attachment attach(QByteArray{});
0273         if (attachment.isUri())
0274             attach.setUri(attachment.uri().toString());
0275         else
0276             attach.setDecodedData(attachment.data());
0277         attach.setMimeType(attachment.mimeType());
0278         attach.setLabel(attachment.label());
0279         todo->addAttachment(attach);
0280     }
0281 
0282     if (task->isRunning()) {
0283         todo->setCustomProperty(Serializer::customPropertyAppName(), Serializer::customPropertyIsRunning(), "1");
0284     } else {
0285         todo->removeCustomProperty(Serializer::customPropertyAppName(), Serializer::customPropertyIsRunning());
0286     }
0287 
0288     // Needs to be done after all other dates are positioned
0289     // since this applies the recurrence logic
0290     if (task->isDone())
0291         todo->setCompleted(QDateTime(task->doneDate(), QTime(), Qt::UTC));
0292     else
0293         todo->setCompleted(false);
0294 
0295     Akonadi::Item item;
0296     if (task->property("itemId").isValid()) {
0297         item.setId(task->property("itemId").value<Akonadi::Item::Id>());
0298     }
0299     if (task->property("parentCollectionId").isValid()) {
0300         auto parentId = task->property("parentCollectionId").value<Akonadi::Collection::Id>();
0301         item.setParentCollection(Akonadi::Collection(parentId));
0302     }
0303     item.setMimeType(KCalendarCore::Todo::todoMimeType());
0304     item.setPayload(todo);
0305     return item;
0306 }
0307 
0308 QString Serializer::relatedUidFromItem(Akonadi::Item item)
0309 {
0310     if (isTaskItem(item)) {
0311         const auto todo = item.payload<KCalendarCore::Todo::Ptr>();
0312         return todo->relatedTo();
0313     } else {
0314         return QString();
0315     }
0316 }
0317 
0318 void Serializer::updateItemParent(Akonadi::Item item, Domain::Task::Ptr parent)
0319 {
0320     if (!isTaskItem(item))
0321         return;
0322 
0323     auto todo = item.payload<KCalendarCore::Todo::Ptr>();
0324     todo->setRelatedTo(parent->property("todoUid").toString());
0325 }
0326 
0327 void Serializer::updateItemProject(Item item, Domain::Project::Ptr project)
0328 {
0329     if (isTaskItem(item)) {
0330         auto todo = item.payload<KCalendarCore::Todo::Ptr>();
0331         todo->setRelatedTo(project->property("todoUid").toString());
0332     }
0333 }
0334 
0335 void Serializer::removeItemParent(Akonadi::Item item)
0336 {
0337     if (!isTaskItem(item))
0338         return;
0339 
0340     auto todo = item.payload<KCalendarCore::Todo::Ptr>();
0341     todo->setRelatedTo(QString());
0342 }
0343 
0344 void Serializer::promoteItemToProject(Akonadi::Item item)
0345 {
0346     if (!isTaskItem(item))
0347         return;
0348 
0349     auto todo = item.payload<KCalendarCore::Todo::Ptr>();
0350     todo->setRelatedTo(QString());
0351     todo->setCustomProperty(Serializer::customPropertyAppName(), Serializer::customPropertyIsProject(), QStringLiteral("1"));
0352 }
0353 
0354 void Serializer::clearItem(Akonadi::Item *item)
0355 {
0356     Q_ASSERT(item);
0357     if (!isTaskItem(*item))
0358         return;
0359 
0360     auto todo = item->payload<KCalendarCore::Todo::Ptr>();
0361     todo->removeCustomProperty(Serializer::customPropertyAppName(), Serializer::customPropertyContextList());
0362 }
0363 
0364 Akonadi::Item::List Serializer::filterDescendantItems(const Akonadi::Item::List &potentialChildren, const Akonadi::Item &ancestorItem)
0365 {
0366     if (potentialChildren.isEmpty())
0367         return Akonadi::Item::List();
0368 
0369     Akonadi::Item::List itemsToProcess = potentialChildren;
0370     Q_ASSERT(ancestorItem.isValid() && ancestorItem.hasPayload<KCalendarCore::Todo::Ptr>());
0371     KCalendarCore::Todo::Ptr todo = ancestorItem.payload<KCalendarCore::Todo::Ptr>();
0372 
0373     const auto bound = std::partition(itemsToProcess.begin(), itemsToProcess.end(),
0374                                       [ancestorItem, todo](Akonadi::Item currentItem) {
0375                                           return (!currentItem.hasPayload<KCalendarCore::Todo::Ptr>()
0376                                                || currentItem == ancestorItem
0377                                                || currentItem.payload<KCalendarCore::Todo::Ptr>()->relatedTo() != todo->uid());
0378                                       });
0379 
0380     Akonadi::Item::List itemsRemoved;
0381     itemsRemoved.reserve(std::distance(itemsToProcess.begin(), bound));
0382     std::copy(itemsToProcess.begin(), bound, std::back_inserter(itemsRemoved));
0383     itemsToProcess.erase(itemsToProcess.begin(), bound);
0384 
0385     auto result = std::accumulate(itemsToProcess.begin(), itemsToProcess.end(), Akonadi::Item::List(),
0386                                  [this, itemsRemoved](Akonadi::Item::List result, Akonadi::Item currentItem) {
0387                                      result << currentItem;
0388                                      return result += filterDescendantItems(itemsRemoved, currentItem);
0389                                  });
0390 
0391     return result;
0392 }
0393 
0394 bool Serializer::isProjectItem(Item item)
0395 {
0396     if (!item.hasPayload<KCalendarCore::Todo::Ptr>())
0397         return false;
0398 
0399     auto todo = item.payload<KCalendarCore::Todo::Ptr>();
0400     return !todo->customProperty(Serializer::customPropertyAppName(), Serializer::customPropertyIsProject()).isEmpty();
0401 }
0402 
0403 Domain::Project::Ptr Serializer::createProjectFromItem(Item item)
0404 {
0405     if (!isProjectItem(item))
0406         return Domain::Project::Ptr();
0407 
0408     auto project = Domain::Project::Ptr::create();
0409     updateProjectFromItem(project, item);
0410     return project;
0411 }
0412 
0413 void Serializer::updateProjectFromItem(Domain::Project::Ptr project, Item item)
0414 {
0415     if (!isProjectItem(item))
0416         return;
0417 
0418     auto todo = item.payload<KCalendarCore::Todo::Ptr>();
0419 
0420     project->setName(todo->summary());
0421     project->setProperty("itemId", item.id());
0422     project->setProperty("parentCollectionId", item.parentCollection().id());
0423     project->setProperty("todoUid", todo->uid());
0424 }
0425 
0426 Item Serializer::createItemFromProject(Domain::Project::Ptr project)
0427 {
0428     auto todo = KCalendarCore::Todo::Ptr::create();
0429 
0430     todo->setSummary(project->name());
0431     todo->setCustomProperty(Serializer::customPropertyAppName(), Serializer::customPropertyIsProject(), QStringLiteral("1"));
0432 
0433     if (project->property("todoUid").isValid()) {
0434         todo->setUid(project->property("todoUid").toString());
0435     }
0436 
0437     Akonadi::Item item;
0438     if (project->property("itemId").isValid()) {
0439         item.setId(project->property("itemId").value<Akonadi::Item::Id>());
0440     }
0441     if (project->property("parentCollectionId").isValid()) {
0442         auto parentId = project->property("parentCollectionId").value<Akonadi::Collection::Id>();
0443         item.setParentCollection(Akonadi::Collection(parentId));
0444     }
0445     item.setMimeType(KCalendarCore::Todo::todoMimeType());
0446     item.setPayload(todo);
0447     return item;
0448 }
0449 
0450 bool Serializer::isProjectChild(Domain::Project::Ptr project, Item item)
0451 {
0452     const QString todoUid = project->property("todoUid").toString();
0453     const QString relatedUid = relatedUidFromItem(item);
0454 
0455     return !todoUid.isEmpty()
0456         && !relatedUid.isEmpty()
0457         && todoUid == relatedUid;
0458 }
0459 
0460 static QStringList extractContexts(KCalendarCore::Todo::Ptr todo)
0461 {
0462     const QString contexts = todo->customProperty(Serializer::customPropertyAppName(), Serializer::customPropertyContextList());
0463     return contexts.split(',', Qt::SkipEmptyParts);
0464 }
0465 
0466 bool Serializer::isContextChild(Domain::Context::Ptr context, Item item) const
0467 {
0468     if (!context->property("todoUid").isValid())
0469         return false;
0470 
0471     if (!item.hasPayload<KCalendarCore::Todo::Ptr>())
0472         return false;
0473 
0474     auto contextUid = context->property("todoUid").toString();
0475     auto todo = item.payload<KCalendarCore::Todo::Ptr>();
0476     const auto contextList = extractContexts(todo);
0477     return contextList.contains(contextUid);
0478 }
0479 
0480 bool Serializer::isContext(Item item)
0481 {
0482     if (!item.hasPayload<KCalendarCore::Todo::Ptr>())
0483         return false;
0484 
0485     auto todo = item.payload<KCalendarCore::Todo::Ptr>();
0486     return !todo->customProperty(Serializer::customPropertyAppName(), Serializer::customPropertyIsContext()).isEmpty();
0487 }
0488 
0489 Domain::Context::Ptr Serializer::createContextFromItem(Item item)
0490 {
0491     if (!isContext(item))
0492         return {};
0493 
0494     auto context = Domain::Context::Ptr::create();
0495     updateContextFromItem(context, item);
0496     return context;
0497 }
0498 
0499 Akonadi::Item Serializer::createItemFromContext(Domain::Context::Ptr context)
0500 {
0501     auto todo = KCalendarCore::Todo::Ptr::create();
0502 
0503     todo->setSummary(context->name());
0504     todo->setCustomProperty(Serializer::customPropertyAppName(), Serializer::customPropertyIsContext(), QStringLiteral("1"));
0505 
0506     if (context->property("todoUid").isValid()) {
0507         todo->setUid(context->property("todoUid").toString());
0508     }
0509 
0510     auto item = Akonadi::Item();
0511     if (context->property("itemId").isValid()) {
0512         item.setId(context->property("itemId").value<Akonadi::Item::Id>());
0513     }
0514     if (context->property("parentCollectionId").isValid()) {
0515         auto parentId = context->property("parentCollectionId").value<Akonadi::Collection::Id>();
0516         item.setParentCollection(Akonadi::Collection(parentId));
0517     }
0518     item.setMimeType(KCalendarCore::Todo::todoMimeType());
0519     item.setPayload(todo);
0520     return item;
0521 }
0522 
0523 void Serializer::updateContextFromItem(Domain::Context::Ptr context, Item item)
0524 {
0525     if (!isContext(item))
0526         return;
0527 
0528     auto todo = item.payload<KCalendarCore::Todo::Ptr>();
0529 
0530     context->setName(todo->summary());
0531     context->setProperty("itemId", item.id());
0532     context->setProperty("parentCollectionId", item.parentCollection().id());
0533     context->setProperty("todoUid", todo->uid());
0534 }
0535 
0536 void Serializer::addContextToTask(Domain::Context::Ptr context, Item item)
0537 {
0538     if (!isTaskItem(item)) {
0539         qWarning() << "Cannot add context to a non-task" << item.id();
0540         return;
0541     }
0542 
0543     auto todo = item.payload<KCalendarCore::Todo::Ptr>();
0544 
0545     if (!context->property("todoUid").isValid())
0546         return;
0547 
0548     auto contextUid = context->property("todoUid").toString();
0549     auto contextList = extractContexts(todo);
0550     if (!contextList.contains(contextUid))
0551         contextList.append(contextUid);
0552     todo->setCustomProperty(Serializer::customPropertyAppName(), Serializer::customPropertyContextList(), contextList.join(','));
0553 
0554     item.setPayload<KCalendarCore::Todo::Ptr>(todo);
0555 }
0556 
0557 void Serializer::removeContextFromTask(Domain::Context::Ptr context, Item item)
0558 {
0559     if (!isTaskItem(item)) {
0560         qWarning() << "Cannot remove context from a non-task" << item.id();
0561         return;
0562     }
0563 
0564     auto todo = item.payload<KCalendarCore::Todo::Ptr>();
0565 
0566     if (!context->property("todoUid").isValid())
0567         return;
0568 
0569     auto contextUid = context->property("todoUid").toString();
0570     QStringList contextList = extractContexts(todo);
0571     contextList.removeAll(contextUid);
0572     if (contextList.isEmpty())
0573         todo->removeCustomProperty(Serializer::customPropertyAppName(), Serializer::customPropertyContextList());
0574     else
0575         todo->setCustomProperty(Serializer::customPropertyAppName(), Serializer::customPropertyContextList(), contextList.join(','));
0576 
0577     item.setPayload<KCalendarCore::Todo::Ptr>(todo);
0578 }
0579 
0580 QString Serializer::contextUid(Item item)
0581 {
0582     if (!isContext(item))
0583         return {};
0584 
0585     auto todo = item.payload<KCalendarCore::Todo::Ptr>();
0586     return todo->uid();
0587 }
0588 
0589 // KCalendarCore's CustomProperties doesn't implement case insensitivity
0590 // and some CALDAV servers make everything uppercase. So do like most of kdepim
0591 // and use uppercase property names.
0592 
0593 QByteArray Serializer::customPropertyAppName()
0594 {
0595     return QByteArrayLiteral("ZANSHIN");
0596 }
0597 
0598 QByteArray Serializer::customPropertyIsProject()
0599 {
0600     return QByteArrayLiteral("ISPROJECT");
0601 }
0602 
0603 QByteArray Serializer::customPropertyIsContext()
0604 {
0605     return QByteArrayLiteral("ISCONTEXT");
0606 }
0607 
0608 QByteArray Serializer::customPropertyIsRunning()
0609 {
0610     return QByteArrayLiteral("ISRUNNING");
0611 }
0612 
0613 QByteArray Serializer::customPropertyContextList()
0614 {
0615     return QByteArrayLiteral("CONTEXTLIST");
0616 }