browser/components/migration/nsEdgeReadingListExtractor.cpp
author Jorg K <mozilla@jorgk.com>
Sun, 11 Oct 2015 18:13:10 +0200
changeset 267227 2bc41a7237cc1ed2a01217a6d76523ecb6fb56b6
parent 263817 a0ecccc081ef104ea0de64f9c1c055d9dd48679c
child 269984 18e8b44eb5651cf3ffa7693cf3129a4af56266e6
permissions -rw-r--r--
Bug 1209414 - New test for manual dictionary change. r=ehsan

/* 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 "nsEdgeReadingListExtractor.h"

#include <windows.h>

#include "nsCOMPtr.h"
#include "nsIConsoleService.h"
#include "nsIMutableArray.h"
#include "nsIWritablePropertyBag2.h"
#include "nsNetUtil.h"
#include "nsServiceManagerUtils.h"
#include "nsWindowsMigrationUtils.h"

#define NS_HANDLE_JET_ERROR(err) { \
  if (err < JET_errSuccess) {	\
    rv = ConvertJETError(err); \
    goto CloseDB; \
  } \
}

#define MAX_URL_LENGTH 4168
#define MAX_TITLE_LENGTH 1024

NS_IMPL_ISUPPORTS(nsEdgeReadingListExtractor, nsIEdgeReadingListExtractor)

NS_IMETHODIMP
nsEdgeReadingListExtractor::Extract(const nsAString& aDBPath, nsIArray** aItems)
{
  nsresult rv = NS_OK;
  *aItems = nullptr;

  if (!aDBPath.Length()) {
    return NS_ERROR_FAILURE;
  }

  JET_ERR err;
  JET_INSTANCE instance;
  JET_SESID sesid;
  JET_DBID dbid;
  JET_TABLEID tableid;
  JET_COLUMNDEF urlColumnInfo = { 0 };
  JET_COLUMNDEF titleColumnInfo = { 0 };
  JET_COLUMNDEF addedDateColumnInfo = { 0 };
  FILETIME addedDate;
  wchar_t urlBuffer[MAX_URL_LENGTH] = { 0 };
  wchar_t titleBuffer[MAX_TITLE_LENGTH] = { 0 };

  // Need to ensure this happens before we skip ahead to CloseDB,
  // otherwise the compiler complains.
  nsCOMPtr<nsIMutableArray> items = do_CreateInstance(NS_ARRAY_CONTRACTID);

  // JET does not throw exceptions, and so error handling and ensuring we close
  // the DB is a bit finnicky. Keep track of how far we got so we guarantee closing
  // the right things
  bool instanceCreated, sessionCreated, dbOpened, tableOpened;

  // Check for the right page size and initialize with that
  unsigned long pageSize;
  err = JetGetDatabaseFileInfoW(static_cast<char16ptr_t>(aDBPath.BeginReading()),
                                &pageSize, sizeof(pageSize), JET_DbInfoPageSize);
  NS_HANDLE_JET_ERROR(err)
  err = JetSetSystemParameter(&instance, 0, JET_paramDatabasePageSize, pageSize, NULL);
  NS_HANDLE_JET_ERROR(err)

  // Turn off recovery, because otherwise we will create log files in either the cwd or
  // overwrite Edge's own logfiles, which is useless at best and at worst might mess with
  // Edge actually using the DB
  err = JetSetSystemParameter(&instance, 0, JET_paramRecovery, 0, "Off");
  NS_HANDLE_JET_ERROR(err)

  // Start our session:
  err = JetCreateInstance(&instance, "edge_readinglist_migration");
  NS_HANDLE_JET_ERROR(err)
  instanceCreated = true;

  err = JetInit(&instance);
  NS_HANDLE_JET_ERROR(err)
  err = JetBeginSession(instance, &sesid, 0, 0);
  NS_HANDLE_JET_ERROR(err)
  sessionCreated = true;

  // Actually open the DB, and make sure to do so readonly:
  err = JetAttachDatabaseW(sesid, static_cast<char16ptr_t>(aDBPath.BeginReading()),
                           JET_bitDbReadOnly);
  NS_HANDLE_JET_ERROR(err)
  dbOpened = true;
  err = JetOpenDatabaseW(sesid, static_cast<char16ptr_t>(aDBPath.BeginReading()),
                         NULL, &dbid, JET_bitDbReadOnly);
  NS_HANDLE_JET_ERROR(err)

  // Open the readinglist table and get information on the columns we are interested in:
  err = JetOpenTable(sesid, dbid, "ReadingList", NULL, 0, JET_bitTableReadOnly, &tableid);
  NS_HANDLE_JET_ERROR(err)
  tableOpened = true;
  err = JetGetColumnInfo(sesid, dbid, "ReadingList", "URL", &urlColumnInfo,
                         sizeof(urlColumnInfo), JET_ColInfo);
  NS_HANDLE_JET_ERROR(err)
  if (urlColumnInfo.cbMax > MAX_URL_LENGTH) {
    nsCOMPtr<nsIConsoleService> consoleService = do_GetService(NS_CONSOLESERVICE_CONTRACTID);
    if (consoleService) {
      consoleService->LogStringMessage(NS_LITERAL_STRING("Edge migration: URL column size increased").get());
    }
  }
  err = JetGetColumnInfo(sesid, dbid, "ReadingList", "Title", &titleColumnInfo,
                         sizeof(titleColumnInfo), JET_ColInfo);
  NS_HANDLE_JET_ERROR(err)
  if (titleColumnInfo.cbMax > MAX_TITLE_LENGTH) {
    nsCOMPtr<nsIConsoleService> consoleService = do_GetService(NS_CONSOLESERVICE_CONTRACTID);
    if (consoleService) {
      consoleService->LogStringMessage(NS_LITERAL_STRING("Edge migration: Title column size increased").get());
    }
  }
  err = JetGetColumnInfo(sesid, dbid, "ReadingList", "AddedDate", &addedDateColumnInfo,
                         sizeof(addedDateColumnInfo), JET_ColInfo);
  NS_HANDLE_JET_ERROR(err)

  // verify the column types are what we expect:
  if (urlColumnInfo.coltyp != JET_coltypLongText ||
      titleColumnInfo.coltyp != JET_coltypLongText ||
      addedDateColumnInfo.coltyp != JET_coltypLongLong) {
    rv = NS_ERROR_NOT_IMPLEMENTED;
    goto CloseDB;
  }

  // If we got here, we've found our table and column information

  err = JetMove(sesid, tableid, JET_MoveFirst, 0);
  // It's possible there are 0 items in this table, in which case we want to
  // not fail:
  if (err == JET_errNoCurrentRecord) {
    items.forget(aItems);
    goto CloseDB;
  }
  // Check for any other errors
  NS_HANDLE_JET_ERROR(err)

  do {
    err = JetRetrieveColumn(sesid, tableid, urlColumnInfo.columnid, &urlBuffer,
                            sizeof(urlBuffer), NULL, 0, NULL);
    NS_HANDLE_JET_ERROR(err)
    err = JetRetrieveColumn(sesid, tableid, titleColumnInfo.columnid, &titleBuffer,
                            sizeof(titleBuffer), NULL, 0, NULL);
    NS_HANDLE_JET_ERROR(err)
    err = JetRetrieveColumn(sesid, tableid, addedDateColumnInfo.columnid, &addedDate,
                            sizeof(addedDate), NULL, 0, NULL);
    NS_HANDLE_JET_ERROR(err)
    nsCOMPtr<nsIWritablePropertyBag2> pbag = do_CreateInstance("@mozilla.org/hash-property-bag;1");
    bool dateIsValid;
    PRTime prAddedDate = WinMigrationFileTimeToPRTime(&addedDate, &dateIsValid);
    nsDependentString url(urlBuffer);
    nsDependentString title(titleBuffer);
    pbag->SetPropertyAsAString(NS_LITERAL_STRING("uri"), url);
    pbag->SetPropertyAsAString(NS_LITERAL_STRING("title"), title);
    if (dateIsValid) {
      pbag->SetPropertyAsInt64(NS_LITERAL_STRING("time"), prAddedDate);
    }
    items->AppendElement(pbag, false);
    memset(urlBuffer, 0, sizeof(urlBuffer));
    memset(titleBuffer, 0, sizeof(titleBuffer));
  } while (JET_errSuccess == JetMove(sesid, tableid, JET_MoveNext, 0));

  items.forget(aItems);

CloseDB:
  // Terminate ESENT. This performs a clean shutdown.
  // Ignore errors while closing:
  if (tableOpened)
    JetCloseTable(sesid, tableid);
  if (dbOpened)
    JetCloseDatabase(sesid, dbid, 0);
  if (sessionCreated)
    JetEndSession(sesid, 0);
  if (instanceCreated)
    JetTerm(instance);

  return rv;
}

nsresult
nsEdgeReadingListExtractor::ConvertJETError(const JET_ERR &aError)
{
  switch (aError) {
    case JET_errPageSizeMismatch:
    case JET_errInvalidName:
    case JET_errColumnNotFound:
      // The DB format has changed and we haven't updated this migration code:
      return NS_ERROR_NOT_IMPLEMENTED;
    case JET_errDatabaseLocked:
      return NS_ERROR_FILE_IS_LOCKED;
    case JET_errPermissionDenied:
    case JET_errAccessDenied:
      return NS_ERROR_FILE_ACCESS_DENIED;
    case JET_errInvalidFilename:
      return NS_ERROR_FILE_INVALID_PATH;
    case JET_errFileNotFound:
      return NS_ERROR_FILE_NOT_FOUND;
    default:
      return NS_ERROR_FAILURE;
  }
}