diff --git a/Linphone/core/conference/ConferenceInfoCore.cpp b/Linphone/core/conference/ConferenceInfoCore.cpp index 865bf7237..c6c45a986 100644 --- a/Linphone/core/conference/ConferenceInfoCore.cpp +++ b/Linphone/core/conference/ConferenceInfoCore.cpp @@ -660,7 +660,7 @@ bool ConferenceInfoCore::isAllDayConf() const { } void ConferenceInfoCore::exportConferenceToICS() { - // Collect participant addresses to look up display names + // Collect participant addresses QStringList participantAddresses; for (const auto &participant : mParticipants) { auto map = participant.toMap(); @@ -679,159 +679,96 @@ void ConferenceInfoCore::exportConferenceToICS() { QDateTime dateTime = mDateTime; QDateTime endDateTime = mEndDateTime; - // Look up display names from friend list in model thread + // Generate ICS on model thread (for display name lookup) then open file App::postModelAsync( [participantAddresses, uri, organizerAddress, organizerName, subject, description, dateTime, endDateTime]() { - // Helper to extract phone number from SIP address - auto extractPhoneNumber = [](const QString &address) -> QString { - QString addr = address; - if (addr.startsWith("sip:", Qt::CaseInsensitive)) { - addr = addr.mid(4); - } else if (addr.startsWith("sips:", Qt::CaseInsensitive)) { - addr = addr.mid(5); - } - int atIndex = addr.indexOf('@'); - if (atIndex > 0) { - return addr.left(atIndex); - } - return addr; + // Helper lambda to escape special characters in ICS text fields + auto escapeIcsText = [](const QString &text) { + QString escaped = text; + escaped.replace("\\", "\\\\"); + escaped.replace(";", "\\;"); + escaped.replace(",", "\\,"); + escaped.replace("\n", "\\n"); + return escaped; }; - // Build map of address -> display name - QMap displayNames; - auto appFriendList = ToolModel::getAppFriendList(); + // Helper lambda to format datetime in ICS format (UTC) + auto formatIcsDateTime = [](const QDateTime &dt) { return dt.toUTC().toString("yyyyMMdd'T'HHmmss'Z'"); }; - for (const QString &address : participantAddresses) { - QString displayName; - - // First try standard lookup by address - auto linFriend = ToolModel::findFriendByAddress(address); - if (linFriend) { - displayName = Utils::coreStringToAppString(linFriend->getName()); - } - - // If not found, search by phone number in friend list - if (displayName.isEmpty() && appFriendList) { - QString phoneNumber = extractPhoneNumber(address); - if (!phoneNumber.isEmpty()) { - // Iterate through all friends and check their phone numbers - for (const auto &friendEntry : appFriendList->getFriends()) { - auto friendPhoneNumbers = friendEntry->getPhoneNumbers(); - for (const auto &friendPhone : friendPhoneNumbers) { - QString friendPhoneStr = Utils::coreStringToAppString(friendPhone); - // Normalize both numbers for comparison (digits only) - QString normalizedFriendPhone = friendPhoneStr; - QString normalizedSearchPhone = phoneNumber; - normalizedFriendPhone.remove(QRegularExpression("[^0-9]")); - normalizedSearchPhone.remove(QRegularExpression("[^0-9]")); - // Check if phone numbers match - if (friendPhoneStr == phoneNumber || normalizedFriendPhone == normalizedSearchPhone) { - displayName = Utils::coreStringToAppString(friendEntry->getName()); - break; - } - } - if (!displayName.isEmpty()) break; - } - } - } - - // Fallback to phone number if no display name found - if (displayName.isEmpty()) { - displayName = extractPhoneNumber(address); - } - - displayNames[address] = displayName; + // Generate a unique UID based on URI or datetime + organizer + QString uid; + if (!uri.isEmpty()) { + uid = uri; + uid.replace("sip:", "").replace("@", "-at-"); + } else { + uid = dateTime.toUTC().toString("yyyyMMddHHmmss") + "-" + organizerAddress; + uid.replace("sip:", "").replace("@", "-at-"); } - // Write ICS file in main thread - App::postCoreAsync([participantAddresses, displayNames, uri, organizerAddress, organizerName, subject, - description, dateTime, endDateTime]() { - QString filePath(Paths::getAppLocalDirPath() + "conference.ics"); - QFile file(filePath); - if (file.open(QIODevice::WriteOnly | QIODevice::Text)) { - QTextStream out(&file); + // Build the ICS content + QString icsContent; + QTextStream out(&icsContent); - // Helper lambda to escape special characters in ICS text fields - auto escapeIcsText = [](const QString &text) { - QString escaped = text; - escaped.replace("\\", "\\\\"); - escaped.replace(";", "\\;"); - escaped.replace(",", "\\,"); - escaped.replace("\n", "\\n"); - return escaped; - }; + out << "BEGIN:VCALENDAR\r\n"; + out << "VERSION:2.0\r\n"; + out << "PRODID:-//Titanium Comms//EN\r\n"; + out << "METHOD:REQUEST\r\n"; + out << "BEGIN:VEVENT\r\n"; - // Helper lambda to format datetime in ICS format (UTC) - auto formatIcsDateTime = [](const QDateTime &dt) { - return dt.toUTC().toString("yyyyMMdd'T'HHmmss'Z'"); - }; + // UID and timestamps + out << "UID:" << uid << "\r\n"; + out << "DTSTAMP:" << formatIcsDateTime(QDateTime::currentDateTimeUtc()) << "\r\n"; + out << "DTSTART:" << formatIcsDateTime(dateTime) << "\r\n"; + out << "DTEND:" << formatIcsDateTime(endDateTime) << "\r\n"; - // Generate a unique UID based on URI or datetime + organizer - QString uid; - if (!uri.isEmpty()) { - uid = uri; - uid.replace("sip:", "").replace("@", "-at-"); - } else { - uid = dateTime.toUTC().toString("yyyyMMddHHmmss") + "-" + organizerAddress; - uid.replace("sip:", "").replace("@", "-at-"); - } - - // Build the ICS content - out << "BEGIN:VCALENDAR\r\n"; - out << "VERSION:2.0\r\n"; - out << "PRODID:-//Titanium Comms//EN\r\n"; - out << "METHOD:REQUEST\r\n"; - out << "BEGIN:VEVENT\r\n"; - - // UID and timestamps - out << "UID:" << uid << "\r\n"; - out << "DTSTAMP:" << formatIcsDateTime(QDateTime::currentDateTimeUtc()) << "\r\n"; - out << "DTSTART:" << formatIcsDateTime(dateTime) << "\r\n"; - out << "DTEND:" << formatIcsDateTime(endDateTime) << "\r\n"; - - // Organizer - if (!organizerAddress.isEmpty()) { - out << "ORGANIZER"; - if (!organizerName.isEmpty()) { - out << ";CN=" << escapeIcsText(organizerName); - } - out << ":" << organizerAddress << "\r\n"; - } - - // Attendees/Participants - for (const QString &address : participantAddresses) { - QString displayName = displayNames.value(address); - out << "ATTENDEE;ROLE=REQ-PARTICIPANT;PARTSTAT=NEEDS-ACTION;RSVP=TRUE"; - if (!displayName.isEmpty()) { - out << ";CN=" << escapeIcsText(displayName); - } - out << ":" << address << "\r\n"; - } - - // Subject/Summary - if (!subject.isEmpty()) { - out << "SUMMARY:" << escapeIcsText(subject) << "\r\n"; - } - - // Description - if (!description.isEmpty()) { - out << "DESCRIPTION:" << escapeIcsText(description) << "\r\n"; - } - - // Location (conference URI) - if (!uri.isEmpty()) { - out << "LOCATION:" << uri << "\r\n"; - out << "URL:" << uri << "\r\n"; - } - - out << "STATUS:CONFIRMED\r\n"; - out << "SEQUENCE:0\r\n"; - out << "END:VEVENT\r\n"; - out << "END:VCALENDAR\r\n"; - - file.close(); + // Organizer + if (!organizerAddress.isEmpty()) { + out << "ORGANIZER"; + if (!organizerName.isEmpty()) { + out << ";CN=" << escapeIcsText(organizerName); } - QDesktopServices::openUrl(QUrl::fromLocalFile(filePath)); - }); + out << ":" << organizerAddress << "\r\n"; + } + + // Attendees/Participants + for (const QString &address : participantAddresses) { + QString displayName = ToolModel::getDisplayName(address); + out << "ATTENDEE;ROLE=REQ-PARTICIPANT;PARTSTAT=NEEDS-ACTION;RSVP=TRUE"; + if (!displayName.isEmpty()) { + out << ";CN=" << escapeIcsText(displayName); + } + out << ":" << address << "\r\n"; + } + + // Subject/Summary + if (!subject.isEmpty()) { + out << "SUMMARY:" << escapeIcsText(subject) << "\r\n"; + } + + // Description + if (!description.isEmpty()) { + out << "DESCRIPTION:" << escapeIcsText(description) << "\r\n"; + } + + // Location (conference URI) + if (!uri.isEmpty()) { + out << "LOCATION:" << uri << "\r\n"; + out << "URL:" << uri << "\r\n"; + } + + out << "STATUS:CONFIRMED\r\n"; + out << "SEQUENCE:0\r\n"; + out << "END:VEVENT\r\n"; + out << "END:VCALENDAR\r\n"; + + // Write the file and open it + QString filePath(Paths::getAppLocalDirPath() + "conference.ics"); + QFile file(filePath); + if (file.open(QIODevice::WriteOnly | QIODevice::Text)) { + QTextStream fileOut(&file); + fileOut << icsContent; + file.close(); + } + QDesktopServices::openUrl(QUrl::fromLocalFile(filePath)); }); }