summaryrefslogtreecommitdiffstats
path: root/mailnews/import/text/src/nsTextAddress.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'mailnews/import/text/src/nsTextAddress.cpp')
-rw-r--r--mailnews/import/text/src/nsTextAddress.cpp471
1 files changed, 471 insertions, 0 deletions
diff --git a/mailnews/import/text/src/nsTextAddress.cpp b/mailnews/import/text/src/nsTextAddress.cpp
new file mode 100644
index 000000000..6b0b82ed1
--- /dev/null
+++ b/mailnews/import/text/src/nsTextAddress.cpp
@@ -0,0 +1,471 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsTextAddress.h"
+#include "nsIAddrDatabase.h"
+#include "nsNativeCharsetUtils.h"
+#include "nsIFile.h"
+#include "nsIInputStream.h"
+#include "nsILineInputStream.h"
+#include "nsNetUtil.h"
+#include "nsMsgI18N.h"
+#include "nsMsgUtils.h"
+#include "mdb.h"
+#include "nsIConverterInputStream.h"
+#include "nsIUnicharLineInputStream.h"
+#include "nsMsgUtils.h"
+
+#include "TextDebugLog.h"
+#include "plstr.h"
+#include "msgCore.h"
+#include <algorithm>
+
+#ifndef MOZILLA_INTERNAL_API
+#include "nsMsgI18N.h"
+#define NS_CopyNativeToUnicode(source, dest) \
+ nsMsgI18NConvertToUnicode(nsMsgI18NFileSystemCharset(), source, dest)
+#endif
+
+#define kWhitespace " \t\b\r\n"
+
+nsTextAddress::nsTextAddress()
+{
+ m_database = nullptr;
+ m_fieldMap = nullptr;
+ m_LFCount = 0;
+ m_CRCount = 0;
+}
+
+nsTextAddress::~nsTextAddress()
+{
+ NS_IF_RELEASE(m_database);
+ NS_IF_RELEASE(m_fieldMap);
+}
+
+nsresult nsTextAddress::GetUnicharLineStreamForFile(nsIFile *aFile,
+ nsIInputStream *aInputStream,
+ nsIUnicharLineInputStream **aStream)
+{
+ nsAutoCString charset;
+ nsresult rv = MsgDetectCharsetFromFile(aFile, charset);
+ if (NS_FAILED(rv)) {
+ charset.Assign(nsMsgI18NFileSystemCharset());
+ }
+
+ nsCOMPtr<nsIConverterInputStream> converterStream =
+ do_CreateInstance("@mozilla.org/intl/converter-input-stream;1", &rv);
+ if (NS_SUCCEEDED(rv)) {
+ rv = converterStream->Init(aInputStream,
+ charset.get(),
+ 8192,
+ nsIConverterInputStream::DEFAULT_REPLACEMENT_CHARACTER);
+ }
+
+ return CallQueryInterface(converterStream, aStream);
+}
+
+nsresult nsTextAddress::ImportAddresses(bool *pAbort, const char16_t *pName, nsIFile *pSrc, nsIAddrDatabase *pDb, nsIImportFieldMap *fieldMap, nsString& errors, uint32_t *pProgress)
+{
+ // Open the source file for reading, read each line and process it!
+ NS_IF_RELEASE(m_database);
+ NS_IF_RELEASE(m_fieldMap);
+ m_database = pDb;
+ m_fieldMap = fieldMap;
+ NS_ADDREF(m_fieldMap);
+ NS_ADDREF(m_database);
+
+ nsCOMPtr<nsIInputStream> inputStream;
+ nsresult rv = NS_NewLocalFileInputStream(getter_AddRefs(inputStream), pSrc);
+ if (NS_FAILED(rv)) {
+ IMPORT_LOG0("*** Error opening address file for reading\n");
+ return rv;
+ }
+
+ // Here we use this to work out the size of the file, so we can update
+ // an integer as we go through the file which will update a progress
+ // bar if required by the caller.
+ uint64_t bytesLeft = 0;
+ rv = inputStream->Available(&bytesLeft);
+ if (NS_FAILED(rv)) {
+ IMPORT_LOG0("*** Error checking address file for size\n");
+ inputStream->Close();
+ return rv;
+ }
+
+ uint64_t totalBytes = bytesLeft;
+ bool skipRecord = false;
+
+ rv = m_fieldMap->GetSkipFirstRecord(&skipRecord);
+ if (NS_FAILED(rv)) {
+ IMPORT_LOG0("*** Error checking to see if we should skip the first record\n");
+ return rv;
+ }
+
+ nsCOMPtr<nsIUnicharLineInputStream> lineStream;
+ rv = GetUnicharLineStreamForFile(pSrc, inputStream, getter_AddRefs(lineStream));
+ if (NS_FAILED(rv)) {
+ IMPORT_LOG0("*** Error opening converter stream for importer\n");
+ return rv;
+ }
+
+ bool more = true;
+ nsAutoString line;
+
+ // Skip the first record if the user has requested it.
+ if (skipRecord)
+ rv = ReadRecord(lineStream, line, &more);
+
+ while (!(*pAbort) && more && NS_SUCCEEDED(rv)) {
+ // Read the line in
+ rv = ReadRecord(lineStream, line, &more);
+ if (NS_SUCCEEDED(rv)) {
+ // Now proces it to add it to the database
+ rv = ProcessLine(line, errors);
+
+ if (NS_FAILED(rv)) {
+ IMPORT_LOG0("*** Error processing text record.\n");
+ }
+ }
+ if (NS_SUCCEEDED(rv) && pProgress) {
+ // This won't be totally accurate, but its the best we can do
+ // considering that lineStream won't give us how many bytes
+ // are actually left.
+ bytesLeft -= line.Length();
+ *pProgress = std::min(totalBytes - bytesLeft, uint64_t(PR_UINT32_MAX));
+ }
+ }
+
+ inputStream->Close();
+
+ if (NS_FAILED(rv)) {
+ IMPORT_LOG0("*** Error reading the address book - probably incorrect ending\n");
+ return NS_ERROR_FAILURE;
+ }
+
+ return pDb->Commit(nsAddrDBCommitType::kLargeCommit);
+}
+
+nsresult nsTextAddress::ReadRecord(nsIUnicharLineInputStream *aLineStream,
+ nsAString &aLine,
+ bool *aMore)
+{
+ bool more = true;
+ uint32_t numQuotes = 0;
+ nsresult rv;
+ nsAutoString line;
+
+ // ensure aLine is empty
+ aLine.Truncate();
+
+ do {
+ if (!more) {
+ // No more, so we must have an incorrect file.
+ rv = NS_ERROR_FAILURE;
+ }
+ else {
+ // Read the line and append it
+ rv = aLineStream->ReadLine(line, &more);
+ if (NS_SUCCEEDED(rv)) {
+ if (!aLine.IsEmpty())
+ aLine.AppendLiteral(MSG_LINEBREAK);
+ aLine.Append(line);
+
+ numQuotes += MsgCountChar(line, char16_t('"'));
+ }
+ }
+ // Continue whilst everything is ok, and we have an odd number of quotes.
+ } while (NS_SUCCEEDED(rv) && (numQuotes % 2 != 0));
+
+ *aMore = more;
+ return rv;
+}
+
+nsresult nsTextAddress::ReadRecordNumber(nsIFile *aSrc, nsAString &aLine, int32_t rNum)
+{
+ nsCOMPtr<nsIInputStream> inputStream;
+ nsresult rv = NS_NewLocalFileInputStream(getter_AddRefs(inputStream), aSrc);
+ if (NS_FAILED(rv)) {
+ IMPORT_LOG0("*** Error opening address file for reading\n");
+ return rv;
+ }
+
+ int32_t rIndex = 0;
+ uint64_t bytesLeft = 0;
+
+ rv = inputStream->Available(&bytesLeft);
+ if (NS_FAILED(rv)) {
+ IMPORT_LOG0("*** Error checking address file for eof\n");
+ inputStream->Close();
+ return rv;
+ }
+
+ nsCOMPtr<nsIUnicharLineInputStream> lineStream;
+ rv = GetUnicharLineStreamForFile(aSrc, inputStream, getter_AddRefs(lineStream));
+ if (NS_FAILED(rv)) {
+ IMPORT_LOG0("*** Error opening converter stream for importer\n");
+ return rv;
+ }
+
+ bool more = true;
+
+ while (more && (rIndex <= rNum)) {
+ rv = ReadRecord(lineStream, aLine, &more);
+ if (NS_FAILED(rv)) {
+ inputStream->Close();
+ return rv;
+ }
+ if (rIndex == rNum) {
+ inputStream->Close();
+ return NS_OK;
+ }
+
+ rIndex++;
+ }
+
+ return NS_ERROR_FAILURE;
+}
+
+int32_t nsTextAddress::CountFields(const nsAString &aLine, char16_t delim)
+{
+ int32_t pos = 0;
+ int32_t maxLen = aLine.Length();
+ int32_t count = 0;
+ char16_t tab = char16_t('\t');
+ char16_t doubleQuote = char16_t('"');
+
+ if (delim == tab)
+ tab = char16_t('\0');
+
+ while (pos < maxLen) {
+ while (((aLine[pos] == char16_t(' ')) || (aLine[pos] == tab)) &&
+ (pos < maxLen)) {
+ pos++;
+ }
+ if ((pos < maxLen) && (aLine[pos] == doubleQuote)) {
+ pos++;
+ while ((pos < maxLen) && (aLine[pos] != doubleQuote)) {
+ pos++;
+ if (((pos + 1) < maxLen) &&
+ (aLine[pos] == doubleQuote) &&
+ (aLine[pos + 1] == doubleQuote)) {
+ pos += 2;
+ }
+ }
+ if (pos < maxLen)
+ pos++;
+ }
+ while ((pos < maxLen) && (aLine[pos] != delim))
+ pos++;
+
+ count++;
+ pos++;
+ }
+
+ return count;
+}
+
+bool nsTextAddress::GetField(const nsAString &aLine,
+ int32_t index,
+ nsString &field,
+ char16_t delim)
+{
+ bool result = false;
+ int32_t pos = 0;
+ int32_t maxLen = aLine.Length();
+ char16_t tab = char16_t('\t');
+ char16_t doubleQuote = char16_t('"');
+
+ field.Truncate();
+
+ if (delim == tab)
+ tab = 0;
+
+ while (index && (pos < maxLen)) {
+ while (((aLine[pos] == char16_t(' ')) || (aLine[pos] == tab)) &&
+ (pos < maxLen)) {
+ pos++;
+ }
+ if (pos >= maxLen)
+ break;
+ if (aLine[pos] == doubleQuote) {
+ do {
+ pos++;
+ if (((pos + 1) < maxLen) &&
+ (aLine[pos] == doubleQuote) &&
+ (aLine[pos + 1] == doubleQuote)) {
+ pos += 2;
+ }
+ } while ((pos < maxLen) && (aLine[pos] != doubleQuote));
+ if (pos < maxLen)
+ pos++;
+ }
+ if (pos >= maxLen)
+ break;
+
+ while ((pos < maxLen) && (aLine[pos] != delim))
+ pos++;
+
+ if (pos >= maxLen)
+ break;
+
+ index--;
+ pos++;
+ }
+
+ if (pos >= maxLen)
+ return result;
+
+ result = true;
+
+ while ((pos < maxLen) && ((aLine[pos] == ' ') || (aLine[pos] == tab)))
+ pos++;
+
+ int32_t fLen = 0;
+ int32_t startPos = pos;
+ bool quoted = false;
+ if (aLine[pos] == '"') {
+ startPos++;
+ fLen = -1;
+ do {
+ pos++;
+ fLen++;
+ if (((pos + 1) < maxLen) &&
+ (aLine[pos] == doubleQuote) &&
+ (aLine[pos + 1] == doubleQuote)) {
+ quoted = true;
+ pos += 2;
+ fLen += 2;
+ }
+ } while ((pos < maxLen) && (aLine[pos] != doubleQuote));
+ }
+ else {
+ while ((pos < maxLen) && (aLine[pos] != delim)) {
+ pos++;
+ fLen++;
+ }
+ }
+
+ if (!fLen) {
+ return result;
+ }
+
+ field.Append(nsDependentSubstring(aLine, startPos, fLen));
+ field.Trim(kWhitespace);
+
+ if (quoted) {
+ int32_t offset = field.Find("\"\"");
+ while (offset != -1) {
+ field.Cut(offset, 1);
+ offset = MsgFind(field, "\"\"", false, offset + 1);
+ }
+ }
+
+ return result;
+}
+
+nsresult nsTextAddress::DetermineDelim(nsIFile *aSrc)
+{
+ nsCOMPtr<nsIInputStream> inputStream;
+ nsresult rv = NS_NewLocalFileInputStream(getter_AddRefs(inputStream), aSrc);
+ if (NS_FAILED(rv)) {
+ IMPORT_LOG0("*** Error opening address file for reading\n");
+ return rv;
+ }
+
+ int32_t lineCount = 0;
+ int32_t tabCount = 0;
+ int32_t commaCount = 0;
+ int32_t tabLines = 0;
+ int32_t commaLines = 0;
+ nsAutoString line;
+ bool more = true;
+
+ nsCOMPtr<nsIUnicharLineInputStream> lineStream;
+ rv = GetUnicharLineStreamForFile(aSrc, inputStream, getter_AddRefs(lineStream));
+ if (NS_FAILED(rv)) {
+ IMPORT_LOG0("*** Error opening converter stream for importer\n");
+ return rv;
+ }
+
+ while (more && NS_SUCCEEDED(rv) && (lineCount < 100)) {
+ rv = lineStream->ReadLine(line, &more);
+ if (NS_SUCCEEDED(rv)) {
+ tabCount = CountFields(line, char16_t('\t'));
+ commaCount = CountFields(line, char16_t(','));
+ if (tabCount > commaCount)
+ tabLines++;
+ else if (commaCount)
+ commaLines++;
+ }
+ lineCount++;
+ }
+
+ rv = inputStream->Close();
+
+ if (tabLines > commaLines)
+ m_delim = char16_t('\t');
+ else
+ m_delim = char16_t(',');
+
+ IMPORT_LOG2( "Tab count = %d, Comma count = %d\n", tabLines, commaLines);
+
+ return rv;
+}
+
+/*
+ This is where the real work happens!
+ Go through the field map and set the data in a new database row
+*/
+nsresult nsTextAddress::ProcessLine(const nsAString &aLine, nsString& errors)
+{
+ if (!m_fieldMap) {
+ IMPORT_LOG0("*** Error, text import needs a field map\n");
+ return NS_ERROR_FAILURE;
+ }
+
+ nsresult rv;
+
+ // Wait until we get our first non-empty field, then create a new row,
+ // fill in the data, then add the row to the database.
+ nsCOMPtr<nsIMdbRow> newRow;
+ nsAutoString fieldVal;
+ int32_t fieldNum;
+ int32_t numFields = 0;
+ bool active;
+ rv = m_fieldMap->GetMapSize(&numFields);
+ for (int32_t i = 0; (i < numFields) && NS_SUCCEEDED(rv); i++) {
+ active = false;
+ rv = m_fieldMap->GetFieldMap(i, &fieldNum);
+ if (NS_SUCCEEDED(rv))
+ rv = m_fieldMap->GetFieldActive(i, &active);
+ if (NS_SUCCEEDED(rv) && active) {
+ if (GetField(aLine, i, fieldVal, m_delim)) {
+ if (!fieldVal.IsEmpty()) {
+ if (!newRow) {
+ rv = m_database->GetNewRow(getter_AddRefs(newRow));
+ if (NS_FAILED(rv)) {
+ IMPORT_LOG0("*** Error getting new address database row\n");
+ }
+ }
+ if (newRow) {
+ rv = m_fieldMap->SetFieldValue(m_database, newRow, fieldNum, fieldVal.get());
+ }
+ }
+ }
+ else
+ break;
+ }
+ else if (active) {
+ IMPORT_LOG1("*** Error getting field map for index %ld\n", (long) i);
+ }
+ }
+
+ if (NS_SUCCEEDED(rv) && newRow)
+ rv = m_database->AddCardRowToDB(newRow);
+
+ return rv;
+}
+