<commit_before>/***************************************************************************
            filter_thunderbird.cxx  -  Thunderbird mail import
                             -------------------
    begin                : Januar 26 2005
    copyright            : (C) 2005 by Danny Kukawka
    email                : danny.kukawka@web.de
 ***************************************************************************/

/***************************************************************************
 *                                                                         *
 *   This program is free software; you can redistribute it and/or modify  *
 *   it under the terms of the GNU General Public License as published by  *
 *   the Free Software Foundation; either version 2 of the License, or     *
 *   (at your option) any later version.                                   *
 *                                                                         *
 ***************************************************************************/

#include "filter_thunderbird.hxx"

#include <config.h>
#include <klocale.h>
#include <kfiledialog.h>
#include <ktempfile.h>

/** Default constructor. */
FilterThunderbird::FilterThunderbird(void) :
  Filter(i18n("Import Thunderbird Local Mails and Folder Structure"),
	 "Danny Kukawka",
	 i18n("
Thunderbird import filter
"
              "
Select your base Thunderbird mailfolder"
              " (usually ~/.thunderbird/*.default/Mail/Local Folders/).
"
              "
Note: Never choose a Folder, which does not contain mbox-files (for example"
              " a maildir). If you do it anyway, you will get many new folders.
"
              "
As it is currently impossible to recreate the folder structure, it will be "
	      "\"contained\" in the generated folder's names.
"))
{}

/** Destructor. */
FilterThunderbird::~FilterThunderbird(void) {
   endImport();
}

/** Recursive import of Evolution's mboxes. */
void FilterThunderbird::import(FilterInfo *info)
{
  /**
   * We ask the user to choose Evolution's root directory.
   * This should be usually ~/.thunderbird/xxxx.default/Mail/Local Folders/
   */
  QString mailDir = KFileDialog::getExistingDirectory(QDir::homeDirPath(), info->parent());
  info->setOverall(0);

  /** Recursive import of the MailArchives */
  QDir dir(mailDir);
  QStringList rootSubDirs = dir.entryList("[^\\.]*", QDir::Dirs, QDir::Name); // Removal of . and ..
  int currentDir = 1, numSubDirs = rootSubDirs.size();
  for(QStringList::Iterator filename = rootSubDirs.begin() ; filename != rootSubDirs.end() ; ++filename, ++currentDir) {
    importDirContents(info, dir.filePath(*filename), *filename, QString::null);
    info->setOverall((int) ((float) currentDir / numSubDirs * 100));
  }

  /** import last but not least all archives from the root-dir */
  QDir importDir (mailDir);
  QStringList files = importDir.entryList("[^\\.]*", QDir::Files, QDir::Name);
  for ( QStringList::Iterator mailFile = files.begin(); mailFile != files.end(); ++mailFile) {
    QString temp_mailfile = *mailFile;
    if (temp_mailfile.endsWith(".msf")) {}
    else {
        info->addLog( i18n("Start import file %1...").arg( temp_mailfile ) );
        importMBox(info, mailDir + "/" + temp_mailfile , temp_mailfile, QString::null);
    }
  }

  info->addLog( i18n("Finished importing emails from %1").arg( mailDir ));
  info->setCurrent(100);
  if(count_duplicates > 0) {
     info->addLog( i18n("1 duplicate message not imported", "%n duplicate messages not imported", count_duplicates));
  }
}

/**
 * Import of a directory contents.
 * @param info Information storage for the operation.
 * @param dirName The name of the directory to import.
 * @param KMailRootDir The directory's root directory in KMail's folder structure.
 * @param KMailSubDir The directory's direct ancestor in KMail's folder structure.
 */
void FilterThunderbird::importDirContents(FilterInfo *info, const QString& dirName, const QString& KMailRootDir, const QString& KMailSubDir)
{
    /** Here Import all archives in the current dir */
    QDir dir(dirName);

    QDir importDir (dirName);
    QStringList files = importDir.entryList("[^\\.]*", QDir::Files, QDir::Name);
    for ( QStringList::Iterator mailFile = files.begin(); mailFile != files.end(); ++mailFile) {
      QString temp_mailfile = *mailFile;
      if (temp_mailfile.endsWith(".msf")) {}
      else {
          info->addLog( i18n("Start import file %1...").arg( temp_mailfile ) );
          importMBox(info, (dirName + "/" + temp_mailfile) , KMailRootDir, KMailSubDir);
      }
    }

    /** If there are subfolders, we import them one by one */
    QDir subfolders(dirName);
    QStringList subDirs = subfolders.entryList("[^\\.]*", QDir::Dirs, QDir::Name);
    for(QStringList::Iterator filename = subDirs.begin() ; filename != subDirs.end() ; ++filename) {
      QString kSubDir;
      if(!KMailSubDir.isNull()) {
        kSubDir = KMailSubDir + "-" + *filename;
      } else {
        kSubDir = *filename;
      }
      importDirContents(info, subfolders.filePath(*filename), KMailRootDir, kSubDir);
    }
}

/**
 * Import of a MBox file.
 * @param info Information storage for the operation.
 * @param dirName The MBox's name.
 * @param KMailRootDir The directory's root directory in KMail's folder structure.
 * @param KMailSubDir The directory's equivalent in KMail's folder structure. *
 */
void FilterThunderbird::importMBox(FilterInfo *info, const QString& mboxName, const QString& rootDir, const QString& targetDir)
{
  QFile mbox(mboxName);
  if (!mbox.open(IO_ReadOnly)) {
    info->alert(i18n("Unable to open %1, skipping").arg(mboxName));
  } else {
    QFileInfo filenameInfo(mboxName);

    info->setCurrent(0);
    info->setFrom(mboxName);
    info->setTo(targetDir);

    while (!mbox.atEnd()) {
      KTempFile tmp;
      /** @todo check if the file is really a mbox, maybe search for 'from' string at start */
      /* comment by Danny:
      * Don't use QTextStream to read from mbox, etter use QDataStream. QTextStream only
      * support Unicode/Latin1/Locale. So you lost information from emails with
      * charset!=Unicode/Latin1/Locale (e.g. KOI8-R) and Content-Transfer-Encoding != base64
      * (e.g. 8Bit). It also not help to convert the QTextStream to Unicode. By this you
      * get Unicode/UTF-email but KMail can't detect the correct charset.
      */
      QByteArray input(MAX_LINE);
      QCString seperate;
      mbox.readLine(input.data(),MAX_LINE);

      long l = mbox.readLine( input.data(),MAX_LINE); // read the first line, prevent "From "
      tmp.file()->writeBlock( input, l );

      while ( ! mbox.atEnd() &&  (l = mbox.readLine(input.data(),MAX_LINE)) && ((seperate = input.data()).left(5) != "From ")) {
	tmp.file()->writeBlock( input, l );
      }
      tmp.close();
      QString destFolder = rootDir;
      QString _targetDir = targetDir;
      if(destFolder.contains(".sbd")) destFolder.remove(".sbd");
      if(!targetDir.isNull()){
         if(_targetDir.contains(".sbd")) _targetDir.remove(".sbd");
         destFolder += ("-" + _targetDir);
         destFolder += "-" + filenameInfo.baseName(TRUE);// mboxName;
      }

      if(info->removeDupMsg) addMessage( info, destFolder, tmp.name() );
      else addMessage_fastImport( info, destFolder, tmp.name() );

      tmp.unlink();
      int currentPercentage = (int) (((float) mbox.at() / filenameInfo.size()) * 100);
      info->setCurrent(currentPercentage);
      if (info->shouldTerminate()) return;
    }
    mbox.close();
  }

}
<commit_msg>- workaround for bug in kdelibs. Now a own filedialog is used to ask the user   for the loaction of the mail archive. [see Bug: #101034] - added check for wrong dirs e.g. homedir (no import need, prevent to import   wrong files) - set the overall processbars to 100% at end of import<commit_after>/***************************************************************************
            filter_thunderbird.cxx  -  Thunderbird mail import
                             -------------------
    begin                : Januar 26 2005
    copyright            : (C) 2005 by Danny Kukawka
    email                : danny.kukawka@web.de
 ***************************************************************************/

/***************************************************************************
 *                                                                         *
 *   This program is free software; you can redistribute it and/or modify  *
 *   it under the terms of the GNU General Public License as published by  *
 *   the Free Software Foundation; either version 2 of the License, or     *
 *   (at your option) any later version.                                   *
 *                                                                         *
 ***************************************************************************/

#include "filter_thunderbird.hxx"

#include <config.h>
#include <klocale.h>
#include <kfiledialog.h>
#include <ktempfile.h>

/** Default constructor. */
FilterThunderbird::FilterThunderbird(void) :
  Filter(i18n("Import Thunderbird Local Mails and Folder Structure"),
	 "Danny Kukawka",
	 i18n("
Thunderbird import filter
"
              "
Select your base Thunderbird mailfolder"
              " (usually ~/.thunderbird/*.default/Mail/Local Folders/).
"
              "
Note: Never choose a Folder, which does not contain mbox-files (for example"
              " a maildir). If you do it anyway, you will get many new folders.
"
              "
As it is currently impossible to recreate the folder structure, it will be "
	      "\"contained\" in the generated folder's names.
"))
{}

/** Destructor. */
FilterThunderbird::~FilterThunderbird(void) {
   endImport();
}

/** Recursive import of Evolution's mboxes. */
void FilterThunderbird::import(FilterInfo *info)
{
  /**
   * We ask the user to choose Evolution's root directory.
   * This should be usually ~/.thunderbird/xxxx.default/Mail/Local Folders/
   */
  QString thunderDir = QDir::homeDirPath() + "/.thunderbird/";
  QDir d( thunderDir );
  if ( !d.exists() ) {
    thunderDir = QDir::homeDirPath();
  }

  KFileDialog *kfd;
  kfd = new KFileDialog( thunderDir, "", 0, "kfiledialog", true );
  kfd->setMode(KFile::Directory | KFile::LocalOnly);
  kfd->exec();
  QString mailDir  = kfd->selectedFile();

   if (mailDir.isEmpty()) {
    info->alert(i18n("No directory selected."));
  }
  /**
   * If the user only select homedir no import needed because
   * there should be no files and we shurely import wrong files.
   */
  else if ( mailDir == QDir::homeDirPath() || mailDir == (QDir::homeDirPath() + "/")) {
    info->addLog(i18n("No files found for import."));
  }
  else {
    info->setOverall(0);

    /** Recursive import of the MailArchives */
    QDir dir(mailDir);
    QStringList rootSubDirs = dir.entryList("[^\\.]*", QDir::Dirs, QDir::Name); // Removal of . and ..
    int currentDir = 1, numSubDirs = rootSubDirs.size();
    for(QStringList::Iterator filename = rootSubDirs.begin() ; filename != rootSubDirs.end() ; ++filename, ++currentDir) {
      importDirContents(info, dir.filePath(*filename), *filename, QString::null);
      info->setOverall((int) ((float) currentDir / numSubDirs * 100));
    }

    /** import last but not least all archives from the root-dir */
    QDir importDir (mailDir);
    QStringList files = importDir.entryList("[^\\.]*", QDir::Files, QDir::Name);
    for ( QStringList::Iterator mailFile = files.begin(); mailFile != files.end(); ++mailFile) {
      QString temp_mailfile = *mailFile;
      if (temp_mailfile.endsWith(".msf")) {}
      else {
          info->addLog( i18n("Start import file %1...").arg( temp_mailfile ) );
          importMBox(info, mailDir + "/" + temp_mailfile , temp_mailfile, QString::null);
      }
    }

    info->addLog( i18n("Finished importing emails from %1").arg( mailDir ));
    if(count_duplicates > 0) {
      info->addLog( i18n("1 duplicate message not imported", "%n duplicate messages not imported", count_duplicates));
    }
  }
  info->setCurrent(100);
  info->setOverall(100);
}

/**
 * Import of a directory contents.
 * @param info Information storage for the operation.
 * @param dirName The name of the directory to import.
 * @param KMailRootDir The directory's root directory in KMail's folder structure.
 * @param KMailSubDir The directory's direct ancestor in KMail's folder structure.
 */
void FilterThunderbird::importDirContents(FilterInfo *info, const QString& dirName, const QString& KMailRootDir, const QString& KMailSubDir)
{
    /** Here Import all archives in the current dir */
    QDir dir(dirName);

    QDir importDir (dirName);
    QStringList files = importDir.entryList("[^\\.]*", QDir::Files, QDir::Name);
    for ( QStringList::Iterator mailFile = files.begin(); mailFile != files.end(); ++mailFile) {
      QString temp_mailfile = *mailFile;
      if (temp_mailfile.endsWith(".msf")) {}
      else {
          info->addLog( i18n("Start import file %1...").arg( temp_mailfile ) );
          importMBox(info, (dirName + "/" + temp_mailfile) , KMailRootDir, KMailSubDir);
      }
    }

    /** If there are subfolders, we import them one by one */
    QDir subfolders(dirName);
    QStringList subDirs = subfolders.entryList("[^\\.]*", QDir::Dirs, QDir::Name);
    for(QStringList::Iterator filename = subDirs.begin() ; filename != subDirs.end() ; ++filename) {
      QString kSubDir;
      if(!KMailSubDir.isNull()) {
        kSubDir = KMailSubDir + "-" + *filename;
      } else {
        kSubDir = *filename;
      }
      importDirContents(info, subfolders.filePath(*filename), KMailRootDir, kSubDir);
    }
}

/**
 * Import of a MBox file.
 * @param info Information storage for the operation.
 * @param dirName The MBox's name.
 * @param KMailRootDir The directory's root directory in KMail's folder structure.
 * @param KMailSubDir The directory's equivalent in KMail's folder structure. *
 */
void FilterThunderbird::importMBox(FilterInfo *info, const QString& mboxName, const QString& rootDir, const QString& targetDir)
{
  QFile mbox(mboxName);
  if (!mbox.open(IO_ReadOnly)) {
    info->alert(i18n("Unable to open %1, skipping").arg(mboxName));
  } else {
    QFileInfo filenameInfo(mboxName);

    info->setCurrent(0);
    info->setFrom(mboxName);
    info->setTo(targetDir);

    while (!mbox.atEnd()) {
      KTempFile tmp;
      /** @todo check if the file is really a mbox, maybe search for 'from' string at start */
      /* comment by Danny:
      * Don't use QTextStream to read from mbox, etter use QDataStream. QTextStream only
      * support Unicode/Latin1/Locale. So you lost information from emails with
      * charset!=Unicode/Latin1/Locale (e.g. KOI8-R) and Content-Transfer-Encoding != base64
      * (e.g. 8Bit). It also not help to convert the QTextStream to Unicode. By this you
      * get Unicode/UTF-email but KMail can't detect the correct charset.
      */
      QByteArray input(MAX_LINE);
      QCString seperate;
      mbox.readLine(input.data(),MAX_LINE);

      long l = mbox.readLine( input.data(),MAX_LINE); // read the first line, prevent "From "
      tmp.file()->writeBlock( input, l );

      while ( ! mbox.atEnd() &&  (l = mbox.readLine(input.data(),MAX_LINE)) && ((seperate = input.data()).left(5) != "From ")) {
	tmp.file()->writeBlock( input, l );
      }
      tmp.close();
      QString destFolder = rootDir;
      QString _targetDir = targetDir;
      if(destFolder.contains(".sbd")) destFolder.remove(".sbd");
      if(!targetDir.isNull()){
         if(_targetDir.contains(".sbd")) _targetDir.remove(".sbd");
         destFolder += ("-" + _targetDir);
         destFolder += "-" + filenameInfo.baseName(TRUE);// mboxName;
      }

      if(info->removeDupMsg) addMessage( info, destFolder, tmp.name() );
      else addMessage_fastImport( info, destFolder, tmp.name() );

      tmp.unlink();
      int currentPercentage = (int) (((float) mbox.at() / filenameInfo.size()) * 100);
      info->setCurrent(currentPercentage);
      if (info->shouldTerminate()) return;
    }
    mbox.close();
  }

}
<|endoftext|>


<commit_before>/*
    This file is part of KOrganizer.

    Copyright (c) 2001 Cornelius Schumacher <schumacher@kde.org>
    Copyright (C) 2004 Reinhold Kainhofer <reinhold@kainhofer.com>
    Copyright (C) 2005 Thomas Zander <zander@kde.org>

    This program is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation; either version 2 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program; if not, write to the Free Software
    Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.

    As a special exception, permission is given to link this program
    with any edition of Qt, and distribute the resulting executable,
    without including the source code for Qt in the source distribution.
*/

#include <qpushbutton.h>
#include <qcheckbox.h>
#include <qbuttongroup.h>
#include <qlineedit.h>
#include <qradiobutton.h>
#include <qlistbox.h>
#include <qwhatsthis.h>

#include <kdebug.h>
#include <klocale.h>
#include <kmessagebox.h>
#include <knuminput.h>

#include <libkcal/calfilter.h>
#include <libkdepim/categoryselectdialog.h>

#include "koprefs.h"
#include "filteredit_base.h"

#include "filtereditdialog.h"
#include "filtereditdialog.moc"

FilterEditDialog::FilterEditDialog( QPtrList<CalFilter> *filters,
                                    QWidget *parent, const char *name)
  : KDialogBase( parent, name, false, i18n("Edit Calendar Filters"),
                 Ok | Apply | Cancel )
{
  setMainWidget( mFilterEdit = new FilterEdit(filters, this));

  connect(mFilterEdit, SIGNAL(dataConsistent(bool)),
        SLOT(setDialogConsistent(bool)));
    updateFilterList();
    connect( mFilterEdit, SIGNAL( editCategories() ), SIGNAL( editCategories() ) );
    connect( mFilterEdit, SIGNAL( filterChanged() ), SIGNAL( filterChanged() ) );
}

FilterEditDialog::~FilterEditDialog()
{
  delete mFilterEdit;
  mFilterEdit = 0L;
}

void FilterEditDialog::updateFilterList()
{
  mFilterEdit->updateFilterList();
}

void FilterEditDialog::updateCategoryConfig()
{
  mFilterEdit->updateCategoryConfig();
}

void FilterEditDialog::slotApply()
{
  mFilterEdit->saveChanges();
}

void FilterEditDialog::slotOk()
{
  slotApply();
  accept();
}

void FilterEditDialog::setDialogConsistent(bool consistent) {
    enableButtonOK( consistent );
    enableButtonApply( consistent );
}

FilterEdit::FilterEdit(QPtrList<CalFilter> *filters, QWidget *parent)
  : FilterEdit_base( parent), current(0), mCategorySelectDialog( 0 )
{
  mFilters = filters;
  QWhatsThis::add( mNewButton, i18n( "Press this button to define a new filter." ) );
  QWhatsThis::add( mDeleteButton, i18n( "Press this button to remove the currently active filter." ) );

  connect(mRulesList, SIGNAL(selectionChanged()), this, SLOT(filterSelected()));
  connect( mNewButton, SIGNAL( clicked() ), SLOT( bNewPressed() ) );
  connect( mDeleteButton, SIGNAL( clicked() ), SLOT( bDeletePressed() ) );
  connect( mNameLineEdit, SIGNAL( textChanged(const QString &) ), SLOT( updateSelectedName(const QString &) ) );
  connect( mCatEditButton, SIGNAL( clicked() ), SLOT( editCategorySelection() ) );
}

FilterEdit::~FilterEdit() {
}

void FilterEdit::updateFilterList()
{
  mRulesList->clear();

  CalFilter *filter = mFilters->first();

  if ( !filter )
    emit(dataConsistent(false));
  else {
    while( filter ) {
      mRulesList->insertItem( filter->name() );
      filter = mFilters->next();
    }

    CalFilter *f = mFilters->at( mRulesList->currentItem() );
    if ( f ) filterSelected( f );

    emit(dataConsistent(true));
  }

  if(current == 0L && mFilters->count() > 0)
    filterSelected(mFilters->at(0));
  mDeleteButton->setEnabled( mFilters->count() > 1 );
}

void FilterEdit::saveChanges()
{
  if(current != 0L)
    filterSelected(current);
}

void FilterEdit::filterSelected()
{
  filterSelected(mFilters->at(mRulesList->currentItem()));
}

void FilterEdit::filterSelected(CalFilter *filter)
{
  if(filter == current) return;
  kdDebug(5850) << "Selected filter " << (filter!=0?filter->name():"") << endl;

  if(current != 0L) {
    // save the old values first.
    current->setName(mNameLineEdit->text());
    int criteria = 0;
    if ( mCompletedCheck->isChecked() ) criteria |= CalFilter::HideCompleted;
    if ( mRecurringCheck->isChecked() ) criteria |= CalFilter::HideRecurring;
    if ( mCatShowCheck->isChecked() ) criteria |= CalFilter::ShowCategories;
    if ( mHideInactiveTodosCheck->isChecked() ) criteria |= CalFilter::HideInactiveTodos;
    current->setCriteria( criteria );
    current->setCompletedTimeSpan( mCompletedTimeSpan->value() );

    QStringList categoryList;
    for( uint i = 0; i < mCatList->count(); ++i )
        categoryList.append( mCatList->text( i ) );
    current->setCategoryList( categoryList );
    emit filterChanged();
  }

  current = filter;
  mNameLineEdit->blockSignals(true);
  mNameLineEdit->setText(current->name());
  mNameLineEdit->blockSignals(false);
  mDetailsFrame->setEnabled(current != 0L);
  mCompletedCheck->setChecked( current->criteria() & CalFilter::HideCompleted );
  mCompletedTimeSpan->setValue( current->completedTimeSpan() );
  mRecurringCheck->setChecked( current->criteria() & CalFilter::HideRecurring );
  mHideInactiveTodosCheck->setChecked( current->criteria() & CalFilter::HideInactiveTodos );
  mCategoriesButtonGroup->setButton( (current->criteria() & CalFilter::ShowCategories)?0:1 );
  mCatList->clear();
  mCatList->insertStringList( current->categoryList() );
}

void FilterEdit::bNewPressed() {
  CalFilter *newFilter = new CalFilter( i18n("New Filter %1").arg(mFilters->count()) );
  mFilters->append( newFilter );
  updateFilterList();
  mRulesList->setSelected(mRulesList->count()-1, true);
  emit filterChanged();
}

void FilterEdit::bDeletePressed() {
  if ( mRulesList->currentItem() < 0 ) return; // nothing selected
  if ( mFilters->count() <= 1 ) return; // We need at least a default filter object.

  int result = KMessageBox::warningContinueCancel( this,
     i18n("This item will be permanently deleted."), i18n("Delete Confirmation"), KGuiItem(i18n("Delete"),"editdelete") );

  if ( result != KMessageBox::Continue )
    return;

  unsigned int selected = mRulesList->currentItem();
  mFilters->remove( selected );
  current = 0L;
  updateFilterList();
  mRulesList->setSelected(QMIN(mRulesList->count()-1, selected), true);
  emit filterChanged();
}

void FilterEdit::updateSelectedName(const QString &newText) {
  mRulesList->changeItem(newText, mRulesList->currentItem());
  bool allOk = true;
  CalFilter *filter = mFilters->first();
  while( allOk && filter ) {
    if(filter->name().isEmpty())
     allOk = false;
    filter = mFilters->next();
  }
  emit dataConsistent(allOk);
}

void FilterEdit::editCategorySelection()
{
  if( !current ) return;
  if ( !mCategorySelectDialog ) {
    mCategorySelectDialog = new KPIM::CategorySelectDialog( KOPrefs::instance(), this, "filterCatSelect" );
    connect( mCategorySelectDialog,
             SIGNAL( categoriesSelected( const QStringList & ) ),
             SLOT( updateCategorySelection( const QStringList & ) ) );
    connect( mCategorySelectDialog, SIGNAL( editCategories() ),
             SIGNAL( editCategories() ) );

  }
  mCategorySelectDialog->setSelected( current->categoryList() );

  mCategorySelectDialog->show();
}

void FilterEdit::updateCategorySelection( const QStringList &categories )
{
  mCatList->clear();
  mCatList->insertStringList(categories);
  current->setCategoryList(categories);
}

void FilterEdit::updateCategoryConfig()
{
  if ( mCategorySelectDialog ) mCategorySelectDialog->updateCategoryConfig();
}
<commit_msg>Make 'ok' also save the changes in the dialog<commit_after>/*
    This file is part of KOrganizer.

    Copyright (c) 2001 Cornelius Schumacher <schumacher@kde.org>
    Copyright (C) 2004 Reinhold Kainhofer <reinhold@kainhofer.com>
    Copyright (C) 2005 Thomas Zander <zander@kde.org>

    This program is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation; either version 2 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program; if not, write to the Free Software
    Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.

    As a special exception, permission is given to link this program
    with any edition of Qt, and distribute the resulting executable,
    without including the source code for Qt in the source distribution.
*/

#include <qpushbutton.h>
#include <qcheckbox.h>
#include <qbuttongroup.h>
#include <qlineedit.h>
#include <qradiobutton.h>
#include <qlistbox.h>
#include <qwhatsthis.h>

#include <kdebug.h>
#include <klocale.h>
#include <kmessagebox.h>
#include <knuminput.h>

#include <libkcal/calfilter.h>
#include <libkdepim/categoryselectdialog.h>

#include "koprefs.h"
#include "filteredit_base.h"

#include "filtereditdialog.h"
#include "filtereditdialog.moc"

FilterEditDialog::FilterEditDialog( QPtrList<CalFilter> *filters,
                                    QWidget *parent, const char *name)
  : KDialogBase( parent, name, false, i18n("Edit Calendar Filters"),
                 Ok | Apply | Cancel )
{
  setMainWidget( mFilterEdit = new FilterEdit(filters, this));

  connect(mFilterEdit, SIGNAL(dataConsistent(bool)),
        SLOT(setDialogConsistent(bool)));
    updateFilterList();
    connect( mFilterEdit, SIGNAL( editCategories() ), SIGNAL( editCategories() ) );
    connect( mFilterEdit, SIGNAL( filterChanged() ), SIGNAL( filterChanged() ) );
}

FilterEditDialog::~FilterEditDialog()
{
  delete mFilterEdit;
  mFilterEdit = 0L;
}

void FilterEditDialog::updateFilterList()
{
  mFilterEdit->updateFilterList();
}

void FilterEditDialog::updateCategoryConfig()
{
  mFilterEdit->updateCategoryConfig();
}

void FilterEditDialog::slotApply()
{
  mFilterEdit->saveChanges();
}

void FilterEditDialog::slotOk()
{
  slotApply();
  accept();
}

void FilterEditDialog::setDialogConsistent(bool consistent) {
    enableButtonOK( consistent );
    enableButtonApply( consistent );
}

FilterEdit::FilterEdit(QPtrList<CalFilter> *filters, QWidget *parent)
  : FilterEdit_base( parent), current(0), mCategorySelectDialog( 0 )
{
  mFilters = filters;
  QWhatsThis::add( mNewButton, i18n( "Press this button to define a new filter." ) );
  QWhatsThis::add( mDeleteButton, i18n( "Press this button to remove the currently active filter." ) );

  connect( mRulesList, SIGNAL(selectionChanged()), this, SLOT(filterSelected()) );
  connect( mNewButton, SIGNAL( clicked() ), SLOT( bNewPressed() ) );
  connect( mDeleteButton, SIGNAL( clicked() ), SLOT( bDeletePressed() ) );
  connect( mNameLineEdit, SIGNAL( textChanged(const QString &) ), SLOT( updateSelectedName(const QString &) ) );
  connect( mCatEditButton, SIGNAL( clicked() ), SLOT( editCategorySelection() ) );
}

FilterEdit::~FilterEdit() {
}

void FilterEdit::updateFilterList()
{
  mRulesList->clear();

  CalFilter *filter = mFilters->first();

  if ( !filter )
    emit(dataConsistent(false));
  else {
    while( filter ) {
      mRulesList->insertItem( filter->name() );
      filter = mFilters->next();
    }

    CalFilter *f = mFilters->at( mRulesList->currentItem() );
    if ( f ) filterSelected( f );

    emit(dataConsistent(true));
  }

  if(current == 0L && mFilters->count() > 0)
    filterSelected(mFilters->at(0));
  mDeleteButton->setEnabled( mFilters->count() > 1 );
}

void FilterEdit::saveChanges()
{
  if(current == 0L)
    return;

  current->setName(mNameLineEdit->text());
  int criteria = 0;
  if ( mCompletedCheck->isChecked() ) criteria |= CalFilter::HideCompleted;
  if ( mRecurringCheck->isChecked() ) criteria |= CalFilter::HideRecurring;
  if ( mCatShowCheck->isChecked() ) criteria |= CalFilter::ShowCategories;
  if ( mHideInactiveTodosCheck->isChecked() ) criteria |= CalFilter::HideInactiveTodos;
  current->setCriteria( criteria );
  current->setCompletedTimeSpan( mCompletedTimeSpan->value() );

  QStringList categoryList;
  for( uint i = 0; i < mCatList->count(); ++i )
      categoryList.append( mCatList->text( i ) );
  current->setCategoryList( categoryList );
  emit filterChanged();
}

void FilterEdit::filterSelected()
{
  filterSelected(mFilters->at(mRulesList->currentItem()));
}

void FilterEdit::filterSelected(CalFilter *filter)
{
  if(filter == current) return;
  kdDebug(5850) << "Selected filter " << (filter!=0?filter->name():"") << endl;
  saveChanges();

  current = filter;
  mNameLineEdit->blockSignals(true);
  mNameLineEdit->setText(current->name());
  mNameLineEdit->blockSignals(false);
  mDetailsFrame->setEnabled(current != 0L);
  mCompletedCheck->setChecked( current->criteria() & CalFilter::HideCompleted );
  mCompletedTimeSpan->setValue( current->completedTimeSpan() );
  mRecurringCheck->setChecked( current->criteria() & CalFilter::HideRecurring );
  mHideInactiveTodosCheck->setChecked( current->criteria() & CalFilter::HideInactiveTodos );
  mCategoriesButtonGroup->setButton( (current->criteria() & CalFilter::ShowCategories)?0:1 );
  mCatList->clear();
  mCatList->insertStringList( current->categoryList() );
}

void FilterEdit::bNewPressed() {
  CalFilter *newFilter = new CalFilter( i18n("New Filter %1").arg(mFilters->count()) );
  mFilters->append( newFilter );
  updateFilterList();
  mRulesList->setSelected(mRulesList->count()-1, true);
  emit filterChanged();
}

void FilterEdit::bDeletePressed() {
  if ( mRulesList->currentItem() < 0 ) return; // nothing selected
  if ( mFilters->count() <= 1 ) return; // We need at least a default filter object.

  int result = KMessageBox::warningContinueCancel( this,
     i18n("This item will be permanently deleted."), i18n("Delete Confirmation"), KGuiItem(i18n("Delete"),"editdelete") );

  if ( result != KMessageBox::Continue )
    return;

  unsigned int selected = mRulesList->currentItem();
  mFilters->remove( selected );
  current = 0L;
  updateFilterList();
  mRulesList->setSelected(QMIN(mRulesList->count()-1, selected), true);
  emit filterChanged();
}

void FilterEdit::updateSelectedName(const QString &newText) {
  mRulesList->blockSignals( true );
  mRulesList->changeItem(newText, mRulesList->currentItem());
  mRulesList->blockSignals( false );
  bool allOk = true;
  CalFilter *filter = mFilters->first();
  while( allOk && filter ) {
    if(filter->name().isEmpty())
     allOk = false;
    filter = mFilters->next();
  }
  emit dataConsistent(allOk);
}

void FilterEdit::editCategorySelection()
{
  if( !current ) return;
  if ( !mCategorySelectDialog ) {
    mCategorySelectDialog = new KPIM::CategorySelectDialog( KOPrefs::instance(), this, "filterCatSelect" );
    connect( mCategorySelectDialog,
             SIGNAL( categoriesSelected( const QStringList & ) ),
             SLOT( updateCategorySelection( const QStringList & ) ) );
    connect( mCategorySelectDialog, SIGNAL( editCategories() ),
             SIGNAL( editCategories() ) );

  }
  mCategorySelectDialog->setSelected( current->categoryList() );

  mCategorySelectDialog->show();
}

void FilterEdit::updateCategorySelection( const QStringList &categories )
{
  mCatList->clear();
  mCatList->insertStringList(categories);
  current->setCategoryList(categories);
}

void FilterEdit::updateCategoryConfig()
{
  if ( mCategorySelectDialog ) mCategorySelectDialog->updateCategoryConfig();
}
<|endoftext|>


<commit_before>/*
 * Copyright (C) 2001 Peter Kelly (pmk@post.com)
 * Copyright (C) 2001 Tobias Anton (anton@stud.fbi.fh-darmstadt.de)
 * Copyright (C) 2006 Samuel Weinig (sam.weinig@gmail.com)
 * Copyright (C) 2003, 2005, 2006, 2008 Apple Inc. All rights reserved.
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Library General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Library General Public License for more details.
 *
 * You should have received a copy of the GNU Library General Public License
 * along with this library; see the file COPYING.LIB.  If not, write to
 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
 * Boston, MA 02110-1301, USA.
 */

#include "config.h"
#include "core/events/Event.h"

#include "core/dom/StaticNodeList.h"
#include "core/events/EventTarget.h"
#include "core/frame/UseCounter.h"
#include "core/svg/SVGElement.h"
#include "wtf/CurrentTime.h"

namespace blink {

EventInit::EventInit()
    : bubbles(false)
    , cancelable(false)
{
}

Event::Event()
    : m_canBubble(false)
    , m_cancelable(false)
    , m_propagationStopped(false)
    , m_immediatePropagationStopped(false)
    , m_defaultPrevented(false)
    , m_defaultHandled(false)
    , m_cancelBubble(false)
    , m_eventPhase(0)
    , m_currentTarget(nullptr)
    , m_createTime(convertSecondsToDOMTimeStamp(currentTime()))
{
    ScriptWrappable::init(this);
}

Event::Event(const AtomicString& eventType, bool canBubbleArg, bool cancelableArg)
    : m_type(eventType)
    , m_canBubble(canBubbleArg)
    , m_cancelable(cancelableArg)
    , m_propagationStopped(false)
    , m_immediatePropagationStopped(false)
    , m_defaultPrevented(false)
    , m_defaultHandled(false)
    , m_cancelBubble(false)
    , m_eventPhase(0)
    , m_currentTarget(nullptr)
    , m_createTime(convertSecondsToDOMTimeStamp(currentTime()))
{
    ScriptWrappable::init(this);
}

Event::Event(const AtomicString& eventType, const EventInit& initializer)
    : m_type(eventType)
    , m_canBubble(initializer.bubbles)
    , m_cancelable(initializer.cancelable)
    , m_propagationStopped(false)
    , m_immediatePropagationStopped(false)
    , m_defaultPrevented(false)
    , m_defaultHandled(false)
    , m_cancelBubble(false)
    , m_eventPhase(0)
    , m_currentTarget(nullptr)
    , m_createTime(convertSecondsToDOMTimeStamp(currentTime()))
{
    ScriptWrappable::init(this);
}

Event::~Event()
{
}

void Event::initEvent(const AtomicString& eventTypeArg, bool canBubbleArg, bool cancelableArg)
{
    if (dispatched())
        return;

    m_propagationStopped = false;
    m_immediatePropagationStopped = false;
    m_defaultPrevented = false;

    m_type = eventTypeArg;
    m_canBubble = canBubbleArg;
    m_cancelable = cancelableArg;
}

bool Event::legacyReturnValue(ExecutionContext* executionContext) const
{
    bool returnValue = !defaultPrevented();
    if (returnValue)
        UseCounter::count(executionContext, UseCounter::EventGetReturnValueTrue);
    else
        UseCounter::count(executionContext, UseCounter::EventGetReturnValueFalse);
    return returnValue;
}

void Event::setLegacyReturnValue(ExecutionContext* executionContext, bool returnValue)
{
    if (returnValue)
        UseCounter::count(executionContext, UseCounter::EventSetReturnValueTrue);
    else
        UseCounter::count(executionContext, UseCounter::EventSetReturnValueFalse);
    setDefaultPrevented(!returnValue);
}

const AtomicString& Event::interfaceName() const
{
    return EventNames::Event;
}

bool Event::hasInterface(const AtomicString& name) const
{
    return interfaceName() == name;
}

bool Event::isUIEvent() const
{
    return false;
}

bool Event::isMouseEvent() const
{
    return false;
}

bool Event::isFocusEvent() const
{
    return false;
}

bool Event::isKeyboardEvent() const
{
    return false;
}

bool Event::isTouchEvent() const
{
    return false;
}

bool Event::isGestureEvent() const
{
    return false;
}

bool Event::isWheelEvent() const
{
    return false;
}

bool Event::isRelatedEvent() const
{
    return false;
}

bool Event::isDragEvent() const
{
    return false;
}

bool Event::isClipboardEvent() const
{
    return false;
}

bool Event::isBeforeTextInsertedEvent() const
{
    return false;
}

bool Event::isBeforeUnloadEvent() const
{
    return false;
}

void Event::setTarget(PassRefPtrWillBeRawPtr<EventTarget> target)
{
    if (m_target == target)
        return;

    m_target = target;
    if (m_target)
        receivedTarget();
}

void Event::receivedTarget()
{
}

void Event::setUnderlyingEvent(PassRefPtrWillBeRawPtr<Event> ue)
{
    // Prohibit creation of a cycle -- just do nothing in that case.
    for (Event* e = ue.get(); e; e = e->underlyingEvent())
        if (e == this)
            return;
    m_underlyingEvent = ue;
}

EventPath& Event::ensureEventPath()
{
    if (!m_eventPath)
        m_eventPath = adoptPtrWillBeNoop(new EventPath(this));
    return *m_eventPath;
}

PassRefPtrWillBeRawPtr<StaticNodeList> Event::path() const
{
    if (!m_currentTarget) {
        ASSERT(m_eventPhase == PhaseType::NONE);
        if (!m_eventPath) {
            // Before dispatching the event
            return StaticNodeList::createEmpty();
        }
        ASSERT(!m_eventPath->isEmpty());
        // After dispatching the event
        return m_eventPath->last().treeScopeEventContext().ensureEventPath(*m_eventPath);
    }
    if (!m_currentTarget->toNode())
        return StaticNodeList::createEmpty();
    Node* node = m_currentTarget->toNode();
    size_t eventPathSize = m_eventPath->size();
    for (size_t i = 0; i < eventPathSize; ++i) {
        if (node == (*m_eventPath)[i].node()) {
            return (*m_eventPath)[i].treeScopeEventContext().ensureEventPath(*m_eventPath);
        }
    }
    return StaticNodeList::createEmpty();
}

EventTarget* Event::currentTarget() const
{
    if (!m_currentTarget)
        return 0;
    Node* node = m_currentTarget->toNode();
    if (node && node->isSVGElement()) {
        if (SVGElement* svgElement = toSVGElement(node)->correspondingElement())
            return svgElement;
    }
    return m_currentTarget.get();
}

void Event::trace(Visitor* visitor)
{
    visitor->trace(m_currentTarget);
    visitor->trace(m_target);
    visitor->trace(m_underlyingEvent);
    visitor->trace(m_eventPath);
}

} // namespace blink
<commit_msg>use c++03 style enum scope<commit_after>/*
 * Copyright (C) 2001 Peter Kelly (pmk@post.com)
 * Copyright (C) 2001 Tobias Anton (anton@stud.fbi.fh-darmstadt.de)
 * Copyright (C) 2006 Samuel Weinig (sam.weinig@gmail.com)
 * Copyright (C) 2003, 2005, 2006, 2008 Apple Inc. All rights reserved.
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Library General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Library General Public License for more details.
 *
 * You should have received a copy of the GNU Library General Public License
 * along with this library; see the file COPYING.LIB.  If not, write to
 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
 * Boston, MA 02110-1301, USA.
 */

#include "config.h"
#include "core/events/Event.h"

#include "core/dom/StaticNodeList.h"
#include "core/events/EventTarget.h"
#include "core/frame/UseCounter.h"
#include "core/svg/SVGElement.h"
#include "wtf/CurrentTime.h"

namespace blink {

EventInit::EventInit()
    : bubbles(false)
    , cancelable(false)
{
}

Event::Event()
    : m_canBubble(false)
    , m_cancelable(false)
    , m_propagationStopped(false)
    , m_immediatePropagationStopped(false)
    , m_defaultPrevented(false)
    , m_defaultHandled(false)
    , m_cancelBubble(false)
    , m_eventPhase(0)
    , m_currentTarget(nullptr)
    , m_createTime(convertSecondsToDOMTimeStamp(currentTime()))
{
    ScriptWrappable::init(this);
}

Event::Event(const AtomicString& eventType, bool canBubbleArg, bool cancelableArg)
    : m_type(eventType)
    , m_canBubble(canBubbleArg)
    , m_cancelable(cancelableArg)
    , m_propagationStopped(false)
    , m_immediatePropagationStopped(false)
    , m_defaultPrevented(false)
    , m_defaultHandled(false)
    , m_cancelBubble(false)
    , m_eventPhase(0)
    , m_currentTarget(nullptr)
    , m_createTime(convertSecondsToDOMTimeStamp(currentTime()))
{
    ScriptWrappable::init(this);
}

Event::Event(const AtomicString& eventType, const EventInit& initializer)
    : m_type(eventType)
    , m_canBubble(initializer.bubbles)
    , m_cancelable(initializer.cancelable)
    , m_propagationStopped(false)
    , m_immediatePropagationStopped(false)
    , m_defaultPrevented(false)
    , m_defaultHandled(false)
    , m_cancelBubble(false)
    , m_eventPhase(0)
    , m_currentTarget(nullptr)
    , m_createTime(convertSecondsToDOMTimeStamp(currentTime()))
{
    ScriptWrappable::init(this);
}

Event::~Event()
{
}

void Event::initEvent(const AtomicString& eventTypeArg, bool canBubbleArg, bool cancelableArg)
{
    if (dispatched())
        return;

    m_propagationStopped = false;
    m_immediatePropagationStopped = false;
    m_defaultPrevented = false;

    m_type = eventTypeArg;
    m_canBubble = canBubbleArg;
    m_cancelable = cancelableArg;
}

bool Event::legacyReturnValue(ExecutionContext* executionContext) const
{
    bool returnValue = !defaultPrevented();
    if (returnValue)
        UseCounter::count(executionContext, UseCounter::EventGetReturnValueTrue);
    else
        UseCounter::count(executionContext, UseCounter::EventGetReturnValueFalse);
    return returnValue;
}

void Event::setLegacyReturnValue(ExecutionContext* executionContext, bool returnValue)
{
    if (returnValue)
        UseCounter::count(executionContext, UseCounter::EventSetReturnValueTrue);
    else
        UseCounter::count(executionContext, UseCounter::EventSetReturnValueFalse);
    setDefaultPrevented(!returnValue);
}

const AtomicString& Event::interfaceName() const
{
    return EventNames::Event;
}

bool Event::hasInterface(const AtomicString& name) const
{
    return interfaceName() == name;
}

bool Event::isUIEvent() const
{
    return false;
}

bool Event::isMouseEvent() const
{
    return false;
}

bool Event::isFocusEvent() const
{
    return false;
}

bool Event::isKeyboardEvent() const
{
    return false;
}

bool Event::isTouchEvent() const
{
    return false;
}

bool Event::isGestureEvent() const
{
    return false;
}

bool Event::isWheelEvent() const
{
    return false;
}

bool Event::isRelatedEvent() const
{
    return false;
}

bool Event::isDragEvent() const
{
    return false;
}

bool Event::isClipboardEvent() const
{
    return false;
}

bool Event::isBeforeTextInsertedEvent() const
{
    return false;
}

bool Event::isBeforeUnloadEvent() const
{
    return false;
}

void Event::setTarget(PassRefPtrWillBeRawPtr<EventTarget> target)
{
    if (m_target == target)
        return;

    m_target = target;
    if (m_target)
        receivedTarget();
}

void Event::receivedTarget()
{
}

void Event::setUnderlyingEvent(PassRefPtrWillBeRawPtr<Event> ue)
{
    // Prohibit creation of a cycle -- just do nothing in that case.
    for (Event* e = ue.get(); e; e = e->underlyingEvent())
        if (e == this)
            return;
    m_underlyingEvent = ue;
}

EventPath& Event::ensureEventPath()
{
    if (!m_eventPath)
        m_eventPath = adoptPtrWillBeNoop(new EventPath(this));
    return *m_eventPath;
}

PassRefPtrWillBeRawPtr<StaticNodeList> Event::path() const
{
    if (!m_currentTarget) {
        ASSERT(m_eventPhase == Event::NONE);
        if (!m_eventPath) {
            // Before dispatching the event
            return StaticNodeList::createEmpty();
        }
        ASSERT(!m_eventPath->isEmpty());
        // After dispatching the event
        return m_eventPath->last().treeScopeEventContext().ensureEventPath(*m_eventPath);
    }
    if (!m_currentTarget->toNode())
        return StaticNodeList::createEmpty();
    Node* node = m_currentTarget->toNode();
    size_t eventPathSize = m_eventPath->size();
    for (size_t i = 0; i < eventPathSize; ++i) {
        if (node == (*m_eventPath)[i].node()) {
            return (*m_eventPath)[i].treeScopeEventContext().ensureEventPath(*m_eventPath);
        }
    }
    return StaticNodeList::createEmpty();
}

EventTarget* Event::currentTarget() const
{
    if (!m_currentTarget)
        return 0;
    Node* node = m_currentTarget->toNode();
    if (node && node->isSVGElement()) {
        if (SVGElement* svgElement = toSVGElement(node)->correspondingElement())
            return svgElement;
    }
    return m_currentTarget.get();
}

void Event::trace(Visitor* visitor)
{
    visitor->trace(m_currentTarget);
    visitor->trace(m_target);
    visitor->trace(m_underlyingEvent);
    visitor->trace(m_eventPath);
}

} // namespace blink
<|endoftext|>


<commit_before>/*
 * Copyright (C) 2013 Google Inc. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are
 * met:
 *
 *     * Redistributions of source code must retain the above copyright
 * notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above
 * copyright notice, this list of conditions and the following disclaimer
 * in the documentation and/or other materials provided with the
 * distribution.
 *     * Neither the name of Google Inc. nor the names of its
 * contributors may be used to endorse or promote products derived from
 * this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

#include "config.h"
#include "wtf/PageAllocator.h"

#include "wtf/CryptographicallyRandomNumber.h"
#include "wtf/SpinLock.h"

#if OS(POSIX)

#include <sys/mman.h>

#ifndef MADV_FREE
#define MADV_FREE MADV_DONTNEED
#endif

#ifndef MAP_ANONYMOUS
#define MAP_ANONYMOUS MAP_ANON
#endif

#elif OS(WIN)

#include <windows.h>

#else
#error Unknown OS
#endif // OS(POSIX)

namespace WTF {

void* allocSuperPages(void* addr, size_t len)
{
    ASSERT(!(len & kSuperPageOffsetMask));
    ASSERT(!(reinterpret_cast<uintptr_t>(addr) & kSuperPageOffsetMask));
#if OS(POSIX)
    char* ptr = reinterpret_cast<char*>(mmap(addr, len, PROT_READ | PROT_WRITE, MAP_ANONYMOUS | MAP_PRIVATE, -1, 0));
    RELEASE_ASSERT(ptr != MAP_FAILED);
    // If our requested address collided with another mapping, there's a
    // chance we'll get back an unaligned address. We fix this by attempting
    // the allocation again, but with enough slack pages that we can find
    // correct alignment within the allocation.
    if (UNLIKELY(reinterpret_cast<uintptr_t>(ptr) & kSuperPageOffsetMask)) {
        int ret = munmap(ptr, len);
        ASSERT(!ret);
        ptr = reinterpret_cast<char*>(mmap(0, len + kSuperPageSize - kSystemPageSize, PROT_READ | PROT_WRITE, MAP_ANONYMOUS | MAP_PRIVATE, -1, 0));
        RELEASE_ASSERT(ptr != MAP_FAILED);
        int numSystemPagesToUnmap = kNumSystemPagesPerSuperPage - 1;
        int numSystemPagesBefore = (kNumSystemPagesPerSuperPage - ((reinterpret_cast<uintptr_t>(ptr) & kSuperPageOffsetMask) / kSystemPageSize)) % kNumSystemPagesPerSuperPage;
        ASSERT(numSystemPagesBefore <= numSystemPagesToUnmap);
        int numSystemPagesAfter = numSystemPagesToUnmap - numSystemPagesBefore;
        if (numSystemPagesBefore) {
            size_t beforeSize = kSystemPageSize * numSystemPagesBefore;
            ret = munmap(ptr, beforeSize);
            ASSERT(!ret);
            ptr += beforeSize;
        }
        if (numSystemPagesAfter) {
            ret = munmap(ptr + len, kSystemPageSize * numSystemPagesAfter);
            ASSERT(!ret);
        }
    }
    void* ret = ptr;
#else
    // Windows is a lot simpler because we've designed around its
    // coarser-grained alignement.
    void* ret = VirtualAlloc(addr, len, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE);
    if (!ret)
        ret = VirtualAlloc(0, len, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE);
    RELEASE_ASSERT(ret);
#endif // OS(POSIX)

    SuperPageBitmap::registerSuperPage(ret);
    return ret;
}

void freeSuperPages(void* addr, size_t len)
{
    ASSERT(!(reinterpret_cast<uintptr_t>(addr) & kSuperPageOffsetMask));
    ASSERT(!(len & kSuperPageOffsetMask));
#if OS(POSIX)
    int ret = munmap(addr, len);
    ASSERT(!ret);
#else
    BOOL ret = VirtualFree(addr, 0, MEM_RELEASE);
    ASSERT(ret);
#endif

    SuperPageBitmap::unregisterSuperPage(addr);
}

void setSystemPagesInaccessible(void* addr, size_t len)
{
    ASSERT(!(len & kSystemPageOffsetMask));
#if OS(POSIX)
    int ret = mprotect(addr, len, PROT_NONE);
    ASSERT(!ret);
#else
    BOOL ret = VirtualFree(addr, len, MEM_DECOMMIT);
    ASSERT(ret);
#endif
}

void decommitSystemPages(void* addr, size_t len)
{
    ASSERT(!(len & kSystemPageOffsetMask));
#if OS(POSIX)
    int ret = madvise(addr, len, MADV_FREE);
    ASSERT(!ret);
#else
    void* ret = VirtualAlloc(addr, len, MEM_RESET, PAGE_READWRITE);
    ASSERT(ret);
#endif
}

char* getRandomSuperPageBase()
{
    uintptr_t random;
    random = static_cast<uintptr_t>(cryptographicallyRandomNumber());
#if CPU(X86_64)
    random <<= 32UL;
    random |= static_cast<uintptr_t>(cryptographicallyRandomNumber());
    // This address mask gives a low liklihood of address space collisions.
    // We handle the situation gracefully if there is a collision.
#if OS(WIN)
    // 64-bit Windows has a bizarrely small 8TB user address space.
    // Allocates in the 1-5TB region.
    random &= (0x3ffffffffffUL & kSuperPageBaseMask);
    random += 0x10000000000UL;
#else
    random &= (0x3fffffffffffUL & kSuperPageBaseMask);
#endif
#else // !CPU(X86_64)
    // This is a good range on Windows, Linux and Mac.
    // Allocates in the 0.5-1.5GB region.
    random &= (0x3fffffff & kSuperPageBaseMask);
    random += 0x20000000;
#endif // CPU(X86_64)
    return reinterpret_cast<char*>(random);
}

#if CPU(32BIT)
unsigned char SuperPageBitmap::s_bitmap[1 << (32 - kSuperPageShift - 3)];

static int bitmapLock = 0;

void SuperPageBitmap::registerSuperPage(void* ptr)
{
    ASSERT(!isPointerInSuperPage(ptr));
    uintptr_t raw = reinterpret_cast<uintptr_t>(ptr);
    raw >>= kSuperPageShift;
    size_t byteIndex = raw >> 3;
    size_t bit = raw & 7;
    ASSERT(byteIndex < sizeof(s_bitmap));
    // The read/modify/write is not guaranteed atomic, so take a lock.
    spinLockLock(&bitmapLock);
    s_bitmap[byteIndex] |= (1 << bit);
    spinLockUnlock(&bitmapLock);
}

void SuperPageBitmap::unregisterSuperPage(void* ptr)
{
    ASSERT(isPointerInSuperPage(ptr));
    uintptr_t raw = reinterpret_cast<uintptr_t>(ptr);
    raw >>= kSuperPageShift;
    size_t byteIndex = raw >> 3;
    size_t bit = raw & 7;
    ASSERT(byteIndex < sizeof(s_bitmap));
    // The read/modify/write is not guaranteed atomic, so take a lock.
    spinLockLock(&bitmapLock);
    s_bitmap[byteIndex] &= ~(1 << bit);
    spinLockUnlock(&bitmapLock);
}
#endif

} // namespace WTF

<commit_msg>Remove dependency between PageAllocator and platform.<commit_after>/*
 * Copyright (C) 2013 Google Inc. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are
 * met:
 *
 *     * Redistributions of source code must retain the above copyright
 * notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above
 * copyright notice, this list of conditions and the following disclaimer
 * in the documentation and/or other materials provided with the
 * distribution.
 *     * Neither the name of Google Inc. nor the names of its
 * contributors may be used to endorse or promote products derived from
 * this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

#include "config.h"
#include "wtf/PageAllocator.h"

#include "wtf/ProcessID.h"
#include "wtf/SpinLock.h"

#if OS(POSIX)

#include <sys/mman.h>

#ifndef MADV_FREE
#define MADV_FREE MADV_DONTNEED
#endif

#ifndef MAP_ANONYMOUS
#define MAP_ANONYMOUS MAP_ANON
#endif

#elif OS(WIN)

#include <windows.h>

#else
#error Unknown OS
#endif // OS(POSIX)

namespace WTF {

void* allocSuperPages(void* addr, size_t len)
{
    ASSERT(!(len & kSuperPageOffsetMask));
    ASSERT(!(reinterpret_cast<uintptr_t>(addr) & kSuperPageOffsetMask));
#if OS(POSIX)
    char* ptr = reinterpret_cast<char*>(mmap(addr, len, PROT_READ | PROT_WRITE, MAP_ANONYMOUS | MAP_PRIVATE, -1, 0));
    RELEASE_ASSERT(ptr != MAP_FAILED);
    // If our requested address collided with another mapping, there's a
    // chance we'll get back an unaligned address. We fix this by attempting
    // the allocation again, but with enough slack pages that we can find
    // correct alignment within the allocation.
    if (UNLIKELY(reinterpret_cast<uintptr_t>(ptr) & kSuperPageOffsetMask)) {
        int ret = munmap(ptr, len);
        ASSERT(!ret);
        ptr = reinterpret_cast<char*>(mmap(0, len + kSuperPageSize - kSystemPageSize, PROT_READ | PROT_WRITE, MAP_ANONYMOUS | MAP_PRIVATE, -1, 0));
        RELEASE_ASSERT(ptr != MAP_FAILED);
        int numSystemPagesToUnmap = kNumSystemPagesPerSuperPage - 1;
        int numSystemPagesBefore = (kNumSystemPagesPerSuperPage - ((reinterpret_cast<uintptr_t>(ptr) & kSuperPageOffsetMask) / kSystemPageSize)) % kNumSystemPagesPerSuperPage;
        ASSERT(numSystemPagesBefore <= numSystemPagesToUnmap);
        int numSystemPagesAfter = numSystemPagesToUnmap - numSystemPagesBefore;
        if (numSystemPagesBefore) {
            size_t beforeSize = kSystemPageSize * numSystemPagesBefore;
            ret = munmap(ptr, beforeSize);
            ASSERT(!ret);
            ptr += beforeSize;
        }
        if (numSystemPagesAfter) {
            ret = munmap(ptr + len, kSystemPageSize * numSystemPagesAfter);
            ASSERT(!ret);
        }
    }
    void* ret = ptr;
#else
    // Windows is a lot simpler because we've designed around its
    // coarser-grained alignement.
    void* ret = VirtualAlloc(addr, len, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE);
    if (!ret)
        ret = VirtualAlloc(0, len, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE);
    RELEASE_ASSERT(ret);
#endif // OS(POSIX)

    SuperPageBitmap::registerSuperPage(ret);
    return ret;
}

void freeSuperPages(void* addr, size_t len)
{
    ASSERT(!(reinterpret_cast<uintptr_t>(addr) & kSuperPageOffsetMask));
    ASSERT(!(len & kSuperPageOffsetMask));
#if OS(POSIX)
    int ret = munmap(addr, len);
    ASSERT(!ret);
#else
    BOOL ret = VirtualFree(addr, 0, MEM_RELEASE);
    ASSERT(ret);
#endif

    SuperPageBitmap::unregisterSuperPage(addr);
}

void setSystemPagesInaccessible(void* addr, size_t len)
{
    ASSERT(!(len & kSystemPageOffsetMask));
#if OS(POSIX)
    int ret = mprotect(addr, len, PROT_NONE);
    ASSERT(!ret);
#else
    BOOL ret = VirtualFree(addr, len, MEM_DECOMMIT);
    ASSERT(ret);
#endif
}

void decommitSystemPages(void* addr, size_t len)
{
    ASSERT(!(len & kSystemPageOffsetMask));
#if OS(POSIX)
    int ret = madvise(addr, len, MADV_FREE);
    ASSERT(!ret);
#else
    void* ret = VirtualAlloc(addr, len, MEM_RESET, PAGE_READWRITE);
    ASSERT(ret);
#endif
}

// This is the same PRNG as used by tcmalloc for mapping address randomness;
// see http://burtleburtle.net/bob/rand/smallprng.html
struct ranctx {
    int lock;
    bool initialized;
    uint32_t a;
    uint32_t b;
    uint32_t c;
    uint32_t d;
};

#define rot(x, k) (((x) << (k)) | ((x) >> (32 - (k))))

uint32_t ranvalInternal(ranctx* x)
{
    uint32_t e = x->a - rot(x->b, 27);
    x->a = x->b ^ rot(x->c, 17);
    x->b = x->c + x->d;
    x->c = x->d + e;
    x->d = e + x->a;
    return x->d;
}

#undef rot

uint32_t ranval(ranctx* x)
{
    spinLockLock(&x->lock);
    if (UNLIKELY(!x->initialized)) {
        x->initialized = true;
        char c;
        uint32_t seed = static_cast<uint32_t>(reinterpret_cast<uintptr_t>(&c));
        seed ^= static_cast<uint32_t>(getCurrentProcessID());
        x->a = 0xf1ea5eed;
        x->b = x->c = x->d = seed;
        for (int i = 0; i < 20; ++i) {
            (void) ranvalInternal(x);
        }
    }
    uint32_t ret = ranvalInternal(x);
    spinLockUnlock(&x->lock);
    return ret;
}

char* getRandomSuperPageBase()
{
    static struct ranctx ranctx;

    uintptr_t random;
    random = static_cast<uintptr_t>(ranval(&ranctx));
#if CPU(X86_64)
    random <<= 32UL;
    random |= static_cast<uintptr_t>(ranval(&ranctx));
    // This address mask gives a low liklihood of address space collisions.
    // We handle the situation gracefully if there is a collision.
#if OS(WIN)
    // 64-bit Windows has a bizarrely small 8TB user address space.
    // Allocates in the 1-5TB region.
    random &= (0x3ffffffffffUL & kSuperPageBaseMask);
    random += 0x10000000000UL;
#else
    random &= (0x3fffffffffffUL & kSuperPageBaseMask);
#endif
#else // !CPU(X86_64)
    // This is a good range on Windows, Linux and Mac.
    // Allocates in the 0.5-1.5GB region.
    random &= (0x3fffffff & kSuperPageBaseMask);
    random += 0x20000000;
#endif // CPU(X86_64)
    return reinterpret_cast<char*>(random);
}

#if CPU(32BIT)
unsigned char SuperPageBitmap::s_bitmap[1 << (32 - kSuperPageShift - 3)];

static int bitmapLock = 0;

void SuperPageBitmap::registerSuperPage(void* ptr)
{
    ASSERT(!isPointerInSuperPage(ptr));
    uintptr_t raw = reinterpret_cast<uintptr_t>(ptr);
    raw >>= kSuperPageShift;
    size_t byteIndex = raw >> 3;
    size_t bit = raw & 7;
    ASSERT(byteIndex < sizeof(s_bitmap));
    // The read/modify/write is not guaranteed atomic, so take a lock.
    spinLockLock(&bitmapLock);
    s_bitmap[byteIndex] |= (1 << bit);
    spinLockUnlock(&bitmapLock);
}

void SuperPageBitmap::unregisterSuperPage(void* ptr)
{
    ASSERT(isPointerInSuperPage(ptr));
    uintptr_t raw = reinterpret_cast<uintptr_t>(ptr);
    raw >>= kSuperPageShift;
    size_t byteIndex = raw >> 3;
    size_t bit = raw & 7;
    ASSERT(byteIndex < sizeof(s_bitmap));
    // The read/modify/write is not guaranteed atomic, so take a lock.
    spinLockLock(&bitmapLock);
    s_bitmap[byteIndex] &= ~(1 << bit);
    spinLockUnlock(&bitmapLock);
}
#endif

} // namespace WTF

<|endoftext|>


<commit_before>/*
 * Copyright (C) 2013 Google Inc. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are
 * met:
 *
 *     * Redistributions of source code must retain the above copyright
 * notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above
 * copyright notice, this list of conditions and the following disclaimer
 * in the documentation and/or other materials provided with the
 * distribution.
 *     * Neither the name of Google Inc. nor the names of its
 * contributors may be used to endorse or promote products derived from
 * this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

#include "config.h"
#include "wtf/PageAllocator.h"

#include "wtf/Assertions.h"
#include "wtf/ProcessID.h"
#include "wtf/SpinLock.h"

#include <limits.h>

#if OS(POSIX)

#include <sys/mman.h>

#ifndef MADV_FREE
#define MADV_FREE MADV_DONTNEED
#endif

#ifndef MAP_ANONYMOUS
#define MAP_ANONYMOUS MAP_ANON
#endif

#elif OS(WIN)

#include <windows.h>

#else
#error Unknown OS
#endif // OS(POSIX)

namespace WTF {

// This simple internal function wraps the OS-specific page allocation call so
// that it behaves consistently: the address is a hint and if it cannot be used,
// the allocation will be placed elsewhere.
static void* systemAllocPages(void* addr, size_t len)
{
    ASSERT(!(len & kPageAllocationGranularityOffsetMask));
    ASSERT(!(reinterpret_cast<uintptr_t>(addr) & kPageAllocationGranularityOffsetMask));
    void* ret;
#if OS(WIN)
    ret = VirtualAlloc(addr, len, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE);
    if (!ret)
        ret = VirtualAlloc(0, len, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE);
#else
    ret = mmap(addr, len, PROT_READ | PROT_WRITE, MAP_ANONYMOUS | MAP_PRIVATE, -1, 0);
    if (ret == MAP_FAILED)
        ret = 0;
#endif
    return ret;
}

static bool trimMapping(void* baseAddr, size_t baseLen, void* trimAddr, size_t trimLen)
{
#if OS(WIN)
    return false;
#else
    char* basePtr = static_cast<char*>(baseAddr);
    char* trimPtr = static_cast<char*>(trimAddr);
    ASSERT(trimPtr >= basePtr);
    ASSERT(trimPtr + trimLen <= basePtr + baseLen);
    size_t preLen = trimPtr - basePtr;
    if (preLen) {
        int ret = munmap(basePtr, preLen);
        RELEASE_ASSERT(!ret);
    }
    size_t postLen = (basePtr + baseLen) - (trimPtr + trimLen);
    if (postLen) {
        int ret = munmap(trimPtr + trimLen, postLen);
        RELEASE_ASSERT(!ret);
    }
    return true;
#endif
}

// This is the same PRNG as used by tcmalloc for mapping address randomness;
// see http://burtleburtle.net/bob/rand/smallprng.html
struct ranctx {
    int lock;
    bool initialized;
    uint32_t a;
    uint32_t b;
    uint32_t c;
    uint32_t d;
};

#define rot(x, k) (((x) << (k)) | ((x) >> (32 - (k))))

uint32_t ranvalInternal(ranctx* x)
{
    uint32_t e = x->a - rot(x->b, 27);
    x->a = x->b ^ rot(x->c, 17);
    x->b = x->c + x->d;
    x->c = x->d + e;
    x->d = e + x->a;
    return x->d;
}

#undef rot

uint32_t ranval(ranctx* x)
{
    spinLockLock(&x->lock);
    if (UNLIKELY(!x->initialized)) {
        x->initialized = true;
        char c;
        uint32_t seed = static_cast<uint32_t>(reinterpret_cast<uintptr_t>(&c));
        seed ^= static_cast<uint32_t>(getCurrentProcessID());
        x->a = 0xf1ea5eed;
        x->b = x->c = x->d = seed;
        for (int i = 0; i < 20; ++i) {
            (void) ranvalInternal(x);
        }
    }
    uint32_t ret = ranvalInternal(x);
    spinLockUnlock(&x->lock);
    return ret;
}

static struct ranctx s_ranctx;

// This internal function calculates a random preferred mapping address.
// It is used when the client of allocPages() passes null as the address.
// In calculating an address, we balance good ASLR against not fragmenting the
// address space too badly.
static void* getRandomPageBase()
{
    uintptr_t random;
    random = static_cast<uintptr_t>(ranval(&s_ranctx));
#if CPU(X86_64)
    random <<= 32UL;
    random |= static_cast<uintptr_t>(ranval(&s_ranctx));
    // This address mask gives a low liklihood of address space collisions.
    // We handle the situation gracefully if there is a collision.
#if OS(WIN)
    // 64-bit Windows has a bizarrely small 8TB user address space.
    // Allocates in the 1-5TB region.
    random &= 0x3ffffffffffUL;
    random += 0x10000000000UL;
#else
    // Linux and OS X support the full 47-bit user space of x64 processors.
    random &= 0x3fffffffffffUL;
#endif
#else // !CPU(X86_64)
    // This is a good range on Windows, Linux and Mac.
    // Allocates in the 0.5-1.5GB region.
    random &= 0x3fffffff;
    random += 0x20000000;
#endif // CPU(X86_64)
    random &= kPageAllocationGranularityBaseMask;
    return reinterpret_cast<void*>(random);
}

void* allocPages(void* addr, size_t len, size_t align)
{
    ASSERT(len >= kPageAllocationGranularity);
    ASSERT(!(len & kPageAllocationGranularityOffsetMask));
    ASSERT(align >= kPageAllocationGranularity);
    ASSERT(!(align & kPageAllocationGranularityOffsetMask));
    ASSERT(!(reinterpret_cast<uintptr_t>(addr) & kPageAllocationGranularityOffsetMask));
    size_t alignOffsetMask = align - 1;
    size_t alignBaseMask = ~alignOffsetMask;
    ASSERT(!(reinterpret_cast<uintptr_t>(addr) & alignOffsetMask));
    // If the client passed null as the address, choose a good one.
    if (!addr) {
        addr = getRandomPageBase();
        addr = reinterpret_cast<void*>(reinterpret_cast<uintptr_t>(addr) & alignBaseMask);
    }

    // The common case, which is also the least work we can do, is that the
    // address and length are suitable. Just try it.
    void* ret = systemAllocPages(addr, len);
    // If the alignment is to our liking, we're done.
    if (!ret || !(reinterpret_cast<uintptr_t>(ret) & alignOffsetMask))
        return ret;

    // Annoying. Unmap and map a larger range to be sure to succeed on the
    // second, slower attempt.
    freePages(ret, len);

    size_t tryLen = len + (align - kPageAllocationGranularity);
    RELEASE_ASSERT(tryLen > len);

    // We loop to cater for the unlikely case where another thread maps on top
    // of the aligned location we choose.
    int count = 0;
    while (count++ < 100) {
        ret = systemAllocPages(addr, tryLen);
        if (!ret)
            return 0;
        // We can now try and trim out a subset of the mapping.
        addr = reinterpret_cast<void*>((reinterpret_cast<uintptr_t>(ret) + alignOffsetMask) & alignBaseMask);

        // On POSIX systems, we can trim the oversized mapping to fit exactly.
        // This will always work on POSIX systems.
        if (trimMapping(ret, tryLen, addr, len))
            return addr;

        // On Windows, you can't trim an existing mapping so we unmap and remap
        // a subset. We used to do for all platforms, but OSX 10.8 has a
        // broken mmap() that ignores address hints for valid, unused addresses.
        freePages(ret, tryLen);
        ret = systemAllocPages(addr, len);
        if (ret == addr || !ret)
            return ret;

        // Unlikely race / collision. Do the simple thing and just start again.
        freePages(ret, len);
        addr = getRandomPageBase();
        addr = reinterpret_cast<void*>(reinterpret_cast<uintptr_t>(addr) & alignBaseMask);
    }
    IMMEDIATE_CRASH();
    return 0;
}

void freePages(void* addr, size_t len)
{
    ASSERT(!(reinterpret_cast<uintptr_t>(addr) & kPageAllocationGranularityOffsetMask));
    ASSERT(!(len & kPageAllocationGranularityOffsetMask));
#if OS(POSIX)
    int ret = munmap(addr, len);
    RELEASE_ASSERT(!ret);
#else
    BOOL ret = VirtualFree(addr, 0, MEM_RELEASE);
    RELEASE_ASSERT(ret);
#endif
}

void setSystemPagesInaccessible(void* addr, size_t len)
{
    ASSERT(!(len & kSystemPageOffsetMask));
#if OS(POSIX)
    int ret = mprotect(addr, len, PROT_NONE);
    RELEASE_ASSERT(!ret);
#else
    BOOL ret = VirtualFree(addr, len, MEM_DECOMMIT);
    RELEASE_ASSERT(ret);
#endif
}

void setSystemPagesAccessible(void* addr, size_t len)
{
    ASSERT(!(len & kSystemPageOffsetMask));
#if OS(POSIX)
    int ret = mprotect(addr, len, PROT_READ | PROT_WRITE);
    RELEASE_ASSERT(!ret);
#else
    void* ret = VirtualAlloc(addr, len, MEM_COMMIT, PAGE_READWRITE);
    RELEASE_ASSERT(ret);
#endif
}

void decommitSystemPages(void* addr, size_t len)
{
    ASSERT(!(len & kSystemPageOffsetMask));
#if OS(POSIX)
    int ret = madvise(addr, len, MADV_FREE);
    RELEASE_ASSERT(!ret);
#else
    void* ret = VirtualAlloc(addr, len, MEM_RESET, PAGE_READWRITE);
    RELEASE_ASSERT(ret);
#endif
}

void recommitSystemPages(void* addr, size_t len)
{
    // FIXME: experiment with a Windows implementation that uses MEM_COMMIT
    // instead of just faulting a MEM_RESET page.
    (void) addr;
    ASSERT(!(len & kSystemPageOffsetMask));
}

} // namespace WTF

<commit_msg>ARM64 has 39-bits of address space.<commit_after>/*
 * Copyright (C) 2013 Google Inc. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are
 * met:
 *
 *     * Redistributions of source code must retain the above copyright
 * notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above
 * copyright notice, this list of conditions and the following disclaimer
 * in the documentation and/or other materials provided with the
 * distribution.
 *     * Neither the name of Google Inc. nor the names of its
 * contributors may be used to endorse or promote products derived from
 * this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

#include "config.h"
#include "wtf/PageAllocator.h"

#include "wtf/Assertions.h"
#include "wtf/ProcessID.h"
#include "wtf/SpinLock.h"

#include <limits.h>

#if OS(POSIX)

#include <sys/mman.h>

#ifndef MADV_FREE
#define MADV_FREE MADV_DONTNEED
#endif

#ifndef MAP_ANONYMOUS
#define MAP_ANONYMOUS MAP_ANON
#endif

#elif OS(WIN)

#include <windows.h>

#else
#error Unknown OS
#endif // OS(POSIX)

namespace WTF {

// This simple internal function wraps the OS-specific page allocation call so
// that it behaves consistently: the address is a hint and if it cannot be used,
// the allocation will be placed elsewhere.
static void* systemAllocPages(void* addr, size_t len)
{
    ASSERT(!(len & kPageAllocationGranularityOffsetMask));
    ASSERT(!(reinterpret_cast<uintptr_t>(addr) & kPageAllocationGranularityOffsetMask));
    void* ret;
#if OS(WIN)
    ret = VirtualAlloc(addr, len, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE);
    if (!ret)
        ret = VirtualAlloc(0, len, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE);
#else
    ret = mmap(addr, len, PROT_READ | PROT_WRITE, MAP_ANONYMOUS | MAP_PRIVATE, -1, 0);
    if (ret == MAP_FAILED)
        ret = 0;
#endif
    return ret;
}

static bool trimMapping(void* baseAddr, size_t baseLen, void* trimAddr, size_t trimLen)
{
#if OS(WIN)
    return false;
#else
    char* basePtr = static_cast<char*>(baseAddr);
    char* trimPtr = static_cast<char*>(trimAddr);
    ASSERT(trimPtr >= basePtr);
    ASSERT(trimPtr + trimLen <= basePtr + baseLen);
    size_t preLen = trimPtr - basePtr;
    if (preLen) {
        int ret = munmap(basePtr, preLen);
        RELEASE_ASSERT(!ret);
    }
    size_t postLen = (basePtr + baseLen) - (trimPtr + trimLen);
    if (postLen) {
        int ret = munmap(trimPtr + trimLen, postLen);
        RELEASE_ASSERT(!ret);
    }
    return true;
#endif
}

// This is the same PRNG as used by tcmalloc for mapping address randomness;
// see http://burtleburtle.net/bob/rand/smallprng.html
struct ranctx {
    int lock;
    bool initialized;
    uint32_t a;
    uint32_t b;
    uint32_t c;
    uint32_t d;
};

#define rot(x, k) (((x) << (k)) | ((x) >> (32 - (k))))

uint32_t ranvalInternal(ranctx* x)
{
    uint32_t e = x->a - rot(x->b, 27);
    x->a = x->b ^ rot(x->c, 17);
    x->b = x->c + x->d;
    x->c = x->d + e;
    x->d = e + x->a;
    return x->d;
}

#undef rot

uint32_t ranval(ranctx* x)
{
    spinLockLock(&x->lock);
    if (UNLIKELY(!x->initialized)) {
        x->initialized = true;
        char c;
        uint32_t seed = static_cast<uint32_t>(reinterpret_cast<uintptr_t>(&c));
        seed ^= static_cast<uint32_t>(getCurrentProcessID());
        x->a = 0xf1ea5eed;
        x->b = x->c = x->d = seed;
        for (int i = 0; i < 20; ++i) {
            (void) ranvalInternal(x);
        }
    }
    uint32_t ret = ranvalInternal(x);
    spinLockUnlock(&x->lock);
    return ret;
}

static struct ranctx s_ranctx;

// This internal function calculates a random preferred mapping address.
// It is used when the client of allocPages() passes null as the address.
// In calculating an address, we balance good ASLR against not fragmenting the
// address space too badly.
static void* getRandomPageBase()
{
    uintptr_t random;
    random = static_cast<uintptr_t>(ranval(&s_ranctx));
#if CPU(X86_64)
    random <<= 32UL;
    random |= static_cast<uintptr_t>(ranval(&s_ranctx));
    // This address mask gives a low liklihood of address space collisions.
    // We handle the situation gracefully if there is a collision.
#if OS(WIN)
    // 64-bit Windows has a bizarrely small 8TB user address space.
    // Allocates in the 1-5TB region.
    random &= 0x3ffffffffffUL;
    random += 0x10000000000UL;
#else
    // Linux and OS X support the full 47-bit user space of x64 processors.
    random &= 0x3fffffffffffUL;
#endif
#elif CPU(ARM64)
    // ARM64 on Linux has 39-bit user space.
    random &= 0x3fffffffffUL;
    random += 0x1000000000UL;
#else // !CPU(X86_64) && !CPU(ARM64)
    // This is a good range on Windows, Linux and Mac.
    // Allocates in the 0.5-1.5GB region.
    random &= 0x3fffffff;
    random += 0x20000000;
#endif // CPU(X86_64)
    random &= kPageAllocationGranularityBaseMask;
    return reinterpret_cast<void*>(random);
}

void* allocPages(void* addr, size_t len, size_t align)
{
    ASSERT(len >= kPageAllocationGranularity);
    ASSERT(!(len & kPageAllocationGranularityOffsetMask));
    ASSERT(align >= kPageAllocationGranularity);
    ASSERT(!(align & kPageAllocationGranularityOffsetMask));
    ASSERT(!(reinterpret_cast<uintptr_t>(addr) & kPageAllocationGranularityOffsetMask));
    size_t alignOffsetMask = align - 1;
    size_t alignBaseMask = ~alignOffsetMask;
    ASSERT(!(reinterpret_cast<uintptr_t>(addr) & alignOffsetMask));
    // If the client passed null as the address, choose a good one.
    if (!addr) {
        addr = getRandomPageBase();
        addr = reinterpret_cast<void*>(reinterpret_cast<uintptr_t>(addr) & alignBaseMask);
    }

    // The common case, which is also the least work we can do, is that the
    // address and length are suitable. Just try it.
    void* ret = systemAllocPages(addr, len);
    // If the alignment is to our liking, we're done.
    if (!ret || !(reinterpret_cast<uintptr_t>(ret) & alignOffsetMask))
        return ret;

    // Annoying. Unmap and map a larger range to be sure to succeed on the
    // second, slower attempt.
    freePages(ret, len);

    size_t tryLen = len + (align - kPageAllocationGranularity);
    RELEASE_ASSERT(tryLen > len);

    // We loop to cater for the unlikely case where another thread maps on top
    // of the aligned location we choose.
    int count = 0;
    while (count++ < 100) {
        ret = systemAllocPages(addr, tryLen);
        if (!ret)
            return 0;
        // We can now try and trim out a subset of the mapping.
        addr = reinterpret_cast<void*>((reinterpret_cast<uintptr_t>(ret) + alignOffsetMask) & alignBaseMask);

        // On POSIX systems, we can trim the oversized mapping to fit exactly.
        // This will always work on POSIX systems.
        if (trimMapping(ret, tryLen, addr, len))
            return addr;

        // On Windows, you can't trim an existing mapping so we unmap and remap
        // a subset. We used to do for all platforms, but OSX 10.8 has a
        // broken mmap() that ignores address hints for valid, unused addresses.
        freePages(ret, tryLen);
        ret = systemAllocPages(addr, len);
        if (ret == addr || !ret)
            return ret;

        // Unlikely race / collision. Do the simple thing and just start again.
        freePages(ret, len);
        addr = getRandomPageBase();
        addr = reinterpret_cast<void*>(reinterpret_cast<uintptr_t>(addr) & alignBaseMask);
    }
    IMMEDIATE_CRASH();
    return 0;
}

void freePages(void* addr, size_t len)
{
    ASSERT(!(reinterpret_cast<uintptr_t>(addr) & kPageAllocationGranularityOffsetMask));
    ASSERT(!(len & kPageAllocationGranularityOffsetMask));
#if OS(POSIX)
    int ret = munmap(addr, len);
    RELEASE_ASSERT(!ret);
#else
    BOOL ret = VirtualFree(addr, 0, MEM_RELEASE);
    RELEASE_ASSERT(ret);
#endif
}

void setSystemPagesInaccessible(void* addr, size_t len)
{
    ASSERT(!(len & kSystemPageOffsetMask));
#if OS(POSIX)
    int ret = mprotect(addr, len, PROT_NONE);
    RELEASE_ASSERT(!ret);
#else
    BOOL ret = VirtualFree(addr, len, MEM_DECOMMIT);
    RELEASE_ASSERT(ret);
#endif
}

void setSystemPagesAccessible(void* addr, size_t len)
{
    ASSERT(!(len & kSystemPageOffsetMask));
#if OS(POSIX)
    int ret = mprotect(addr, len, PROT_READ | PROT_WRITE);
    RELEASE_ASSERT(!ret);
#else
    void* ret = VirtualAlloc(addr, len, MEM_COMMIT, PAGE_READWRITE);
    RELEASE_ASSERT(ret);
#endif
}

void decommitSystemPages(void* addr, size_t len)
{
    ASSERT(!(len & kSystemPageOffsetMask));
#if OS(POSIX)
    int ret = madvise(addr, len, MADV_FREE);
    RELEASE_ASSERT(!ret);
#else
    void* ret = VirtualAlloc(addr, len, MEM_RESET, PAGE_READWRITE);
    RELEASE_ASSERT(ret);
#endif
}

void recommitSystemPages(void* addr, size_t len)
{
    // FIXME: experiment with a Windows implementation that uses MEM_COMMIT
    // instead of just faulting a MEM_RESET page.
    (void) addr;
    ASSERT(!(len & kSystemPageOffsetMask));
}

} // namespace WTF

<|endoftext|>


<commit_before>/*************************************************************************/
/*  audio_stream_ogg_vorbis.cpp                                          */
/*************************************************************************/
/*                       This file is part of:                           */
/*                           GODOT ENGINE                                */
/*                      https://godotengine.org                          */
/*************************************************************************/
/* Copyright (c) 2007-2017 Juan Linietsky, Ariel Manzur.                 */
/* Copyright (c) 2014-2017 Godot Engine contributors (cf. AUTHORS.md)    */
/*                                                                       */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the       */
/* "Software"), to deal in the Software without restriction, including   */
/* without limitation the rights to use, copy, modify, merge, publish,   */
/* distribute, sublicense, and/or sell copies of the Software, and to    */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions:                                             */
/*                                                                       */
/* The above copyright notice and this permission notice shall be        */
/* included in all copies or substantial portions of the Software.       */
/*                                                                       */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,       */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF    */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY  */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,  */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE     */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.                */
/*************************************************************************/
#include "audio_stream_ogg_vorbis.h"

#include "os/file_access.h"

#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wmaybe-uninitialized"
#include "thirdparty/misc/stb_vorbis.c"
#pragma GCC diagnostic pop

void AudioStreamPlaybackOGGVorbis::_mix_internal(AudioFrame *p_buffer, int p_frames) {

	ERR_FAIL_COND(!active);

	int todo = p_frames;

	while (todo && active) {

		int mixed = stb_vorbis_get_samples_float_interleaved(ogg_stream, 2, (float *)p_buffer, todo * 2);
		if (vorbis_stream->channels == 1 && mixed > 0) {
			//mix mono to stereo
			for (int i = 0; i < mixed; i++) {
				p_buffer[i].r = p_buffer[i].l;
			}
		}
		todo -= mixed;
		frames_mixed += mixed;

		if (todo) {
			//end of file!
			if (vorbis_stream->loop) {
				//loop
				seek(vorbis_stream->loop_offset);
				loops++;
			} else {
				for (int i = mixed; i < p_frames; i++) {
					p_buffer[i] = AudioFrame(0, 0);
				}
				active = false;
			}
		}
	}
}

float AudioStreamPlaybackOGGVorbis::get_stream_sampling_rate() {

	return vorbis_stream->sample_rate;
}

void AudioStreamPlaybackOGGVorbis::start(float p_from_pos) {

	active = true;
	seek(p_from_pos);
	loops = 0;
	_begin_resample();
}

void AudioStreamPlaybackOGGVorbis::stop() {

	active = false;
}
bool AudioStreamPlaybackOGGVorbis::is_playing() const {

	return active;
}

int AudioStreamPlaybackOGGVorbis::get_loop_count() const {

	return loops;
}

float AudioStreamPlaybackOGGVorbis::get_playback_position() const {

	return float(frames_mixed) / vorbis_stream->sample_rate;
}
void AudioStreamPlaybackOGGVorbis::seek(float p_time) {

	if (!active)
		return;

	if (p_time >= get_length()) {
		p_time = 0;
	}
	frames_mixed = uint32_t(vorbis_stream->sample_rate * p_time);

	stb_vorbis_seek(ogg_stream, frames_mixed);
}

float AudioStreamPlaybackOGGVorbis::get_length() const {

	return vorbis_stream->length;
}

AudioStreamPlaybackOGGVorbis::~AudioStreamPlaybackOGGVorbis() {
	if (ogg_alloc.alloc_buffer) {
		stb_vorbis_close(ogg_stream);
		AudioServer::get_singleton()->audio_data_free(ogg_alloc.alloc_buffer);
	}
}

Ref<AudioStreamPlayback> AudioStreamOGGVorbis::instance_playback() {

	Ref<AudioStreamPlaybackOGGVorbis> ovs;

	ERR_FAIL_COND_V(data == NULL, ovs);

	ovs.instance();
	ovs->vorbis_stream = Ref<AudioStreamOGGVorbis>(this);
	ovs->ogg_alloc.alloc_buffer = (char *)AudioServer::get_singleton()->audio_data_alloc(decode_mem_size);
	ovs->ogg_alloc.alloc_buffer_length_in_bytes = decode_mem_size;
	ovs->frames_mixed = 0;
	ovs->active = false;
	ovs->loops = 0;
	int error;
	ovs->ogg_stream = stb_vorbis_open_memory((const unsigned char *)data, data_len, &error, &ovs->ogg_alloc);
	if (!ovs->ogg_stream) {

		AudioServer::get_singleton()->audio_data_free(ovs->ogg_alloc.alloc_buffer);
		ovs->ogg_alloc.alloc_buffer = NULL;
		ERR_FAIL_COND_V(!ovs->ogg_stream, Ref<AudioStreamPlaybackOGGVorbis>());
	}

	return ovs;
}

String AudioStreamOGGVorbis::get_stream_name() const {

	return ""; //return stream_name;
}

void AudioStreamOGGVorbis::set_data(const PoolVector<uint8_t> &p_data) {

	int src_data_len = p_data.size();
#define MAX_TEST_MEM (1 << 20)

	uint32_t alloc_try = 1024;
	PoolVector<char> alloc_mem;
	PoolVector<char>::Write w;
	stb_vorbis *ogg_stream = NULL;
	stb_vorbis_alloc ogg_alloc;

	while (alloc_try < MAX_TEST_MEM) {

		alloc_mem.resize(alloc_try);
		w = alloc_mem.write();

		ogg_alloc.alloc_buffer = w.ptr();
		ogg_alloc.alloc_buffer_length_in_bytes = alloc_try;

		PoolVector<uint8_t>::Read src_datar = p_data.read();

		int error;
		ogg_stream = stb_vorbis_open_memory((const unsigned char *)src_datar.ptr(), src_data_len, &error, &ogg_alloc);

		if (!ogg_stream && error == VORBIS_outofmem) {
			w = PoolVector<char>::Write();
			alloc_try *= 2;
		} else {

			ERR_FAIL_COND(alloc_try == MAX_TEST_MEM);
			ERR_FAIL_COND(ogg_stream == NULL);

			stb_vorbis_info info = stb_vorbis_get_info(ogg_stream);

			channels = info.channels;
			sample_rate = info.sample_rate;
			decode_mem_size = alloc_try;
			//does this work? (it's less mem..)
			//decode_mem_size = ogg_alloc.alloc_buffer_length_in_bytes + info.setup_memory_required + info.temp_memory_required + info.max_frame_size;

			//print_line("succeeded "+itos(ogg_alloc.alloc_buffer_length_in_bytes)+" setup "+itos(info.setup_memory_required)+" setup temp "+itos(info.setup_temp_memory_required)+" temp "+itos(info.temp_memory_required)+" maxframe"+itos(info.max_frame_size));

			length = stb_vorbis_stream_length_in_seconds(ogg_stream);
			stb_vorbis_close(ogg_stream);

			data = AudioServer::get_singleton()->audio_data_alloc(src_data_len, src_datar.ptr());
			data_len = src_data_len;

			break;
		}
	}
}

PoolVector<uint8_t> AudioStreamOGGVorbis::get_data() const {

	PoolVector<uint8_t> vdata;

	if (data_len && data) {
		vdata.resize(data_len);
		{
			PoolVector<uint8_t>::Write w = vdata.write();
			copymem(w.ptr(), data, data_len);
		}
	}

	return vdata;
}

void AudioStreamOGGVorbis::set_loop(bool p_enable) {
	loop = p_enable;
}

bool AudioStreamOGGVorbis::has_loop() const {

	return loop;
}

void AudioStreamOGGVorbis::set_loop_offset(float p_seconds) {
	loop_offset = p_seconds;
}

float AudioStreamOGGVorbis::get_loop_offset() const {
	return loop_offset;
}

void AudioStreamOGGVorbis::_bind_methods() {

	ClassDB::bind_method(D_METHOD("set_data", "data"), &AudioStreamOGGVorbis::set_data);
	ClassDB::bind_method(D_METHOD("get_data"), &AudioStreamOGGVorbis::get_data);

	ClassDB::bind_method(D_METHOD("set_loop", "enable"), &AudioStreamOGGVorbis::set_loop);
	ClassDB::bind_method(D_METHOD("has_loop"), &AudioStreamOGGVorbis::has_loop);

	ClassDB::bind_method(D_METHOD("set_loop_offset", "seconds"), &AudioStreamOGGVorbis::set_loop_offset);
	ClassDB::bind_method(D_METHOD("get_loop_offset"), &AudioStreamOGGVorbis::get_loop_offset);

	ADD_PROPERTY(PropertyInfo(Variant::POOL_BYTE_ARRAY, "data", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR), "set_data", "get_data");
	ADD_PROPERTY(PropertyInfo(Variant::BOOL, "loop", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR), "set_loop", "has_loop");
	ADD_PROPERTY(PropertyInfo(Variant::REAL, "loop_offset", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR), "set_loop_offset", "get_loop_offset");
}

AudioStreamOGGVorbis::AudioStreamOGGVorbis() {

	data = NULL;
	length = 0;
	sample_rate = 1;
	channels = 1;
	loop_offset = 0;
	decode_mem_size = 0;
	loop = false;
}
<commit_msg>Fix ogg looping pop noise. Closes #11468<commit_after>/*************************************************************************/
/*  audio_stream_ogg_vorbis.cpp                                          */
/*************************************************************************/
/*                       This file is part of:                           */
/*                           GODOT ENGINE                                */
/*                      https://godotengine.org                          */
/*************************************************************************/
/* Copyright (c) 2007-2017 Juan Linietsky, Ariel Manzur.                 */
/* Copyright (c) 2014-2017 Godot Engine contributors (cf. AUTHORS.md)    */
/*                                                                       */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the       */
/* "Software"), to deal in the Software without restriction, including   */
/* without limitation the rights to use, copy, modify, merge, publish,   */
/* distribute, sublicense, and/or sell copies of the Software, and to    */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions:                                             */
/*                                                                       */
/* The above copyright notice and this permission notice shall be        */
/* included in all copies or substantial portions of the Software.       */
/*                                                                       */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,       */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF    */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY  */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,  */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE     */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.                */
/*************************************************************************/
#include "audio_stream_ogg_vorbis.h"

#include "os/file_access.h"

#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wmaybe-uninitialized"
#include "thirdparty/misc/stb_vorbis.c"
#pragma GCC diagnostic pop

void AudioStreamPlaybackOGGVorbis::_mix_internal(AudioFrame *p_buffer, int p_frames) {

	ERR_FAIL_COND(!active);

	int todo = p_frames;

	int start_buffer = 0;

	while (todo && active) {
		float *buffer = (float *)p_buffer;
		if (start_buffer > 0) {
			buffer = (buffer + start_buffer * 2);
		}
		int mixed = stb_vorbis_get_samples_float_interleaved(ogg_stream, 2, buffer, todo * 2);
		if (vorbis_stream->channels == 1 && mixed > 0) {
			//mix mono to stereo
			for (int i = start_buffer; i < mixed; i++) {
				p_buffer[i].r = p_buffer[i].l;
			}
		}
		todo -= mixed;
		frames_mixed += mixed;

		if (todo) {
			//end of file!
			if (vorbis_stream->loop) {
				//loop
				seek(vorbis_stream->loop_offset);
				loops++;
				// we still have buffer to fill, start from this element in the next iteration.
				start_buffer = p_frames - todo;
			} else {
				for (int i = p_frames - todo; i < p_frames; i++) {
					p_buffer[i] = AudioFrame(0, 0);
				}
				active = false;
				todo = 0;
			}
		}
	}
}

float AudioStreamPlaybackOGGVorbis::get_stream_sampling_rate() {

	return vorbis_stream->sample_rate;
}

void AudioStreamPlaybackOGGVorbis::start(float p_from_pos) {

	active = true;
	seek(p_from_pos);
	loops = 0;
	_begin_resample();
}

void AudioStreamPlaybackOGGVorbis::stop() {

	active = false;
}
bool AudioStreamPlaybackOGGVorbis::is_playing() const {

	return active;
}

int AudioStreamPlaybackOGGVorbis::get_loop_count() const {

	return loops;
}

float AudioStreamPlaybackOGGVorbis::get_playback_position() const {

	return float(frames_mixed) / vorbis_stream->sample_rate;
}
void AudioStreamPlaybackOGGVorbis::seek(float p_time) {

	if (!active)
		return;

	if (p_time >= get_length()) {
		p_time = 0;
	}
	frames_mixed = uint32_t(vorbis_stream->sample_rate * p_time);

	stb_vorbis_seek(ogg_stream, frames_mixed);
}

float AudioStreamPlaybackOGGVorbis::get_length() const {

	return vorbis_stream->length;
}

AudioStreamPlaybackOGGVorbis::~AudioStreamPlaybackOGGVorbis() {
	if (ogg_alloc.alloc_buffer) {
		stb_vorbis_close(ogg_stream);
		AudioServer::get_singleton()->audio_data_free(ogg_alloc.alloc_buffer);
	}
}

Ref<AudioStreamPlayback> AudioStreamOGGVorbis::instance_playback() {

	Ref<AudioStreamPlaybackOGGVorbis> ovs;

	ERR_FAIL_COND_V(data == NULL, ovs);

	ovs.instance();
	ovs->vorbis_stream = Ref<AudioStreamOGGVorbis>(this);
	ovs->ogg_alloc.alloc_buffer = (char *)AudioServer::get_singleton()->audio_data_alloc(decode_mem_size);
	ovs->ogg_alloc.alloc_buffer_length_in_bytes = decode_mem_size;
	ovs->frames_mixed = 0;
	ovs->active = false;
	ovs->loops = 0;
	int error;
	ovs->ogg_stream = stb_vorbis_open_memory((const unsigned char *)data, data_len, &error, &ovs->ogg_alloc);
	if (!ovs->ogg_stream) {

		AudioServer::get_singleton()->audio_data_free(ovs->ogg_alloc.alloc_buffer);
		ovs->ogg_alloc.alloc_buffer = NULL;
		ERR_FAIL_COND_V(!ovs->ogg_stream, Ref<AudioStreamPlaybackOGGVorbis>());
	}

	return ovs;
}

String AudioStreamOGGVorbis::get_stream_name() const {

	return ""; //return stream_name;
}

void AudioStreamOGGVorbis::set_data(const PoolVector<uint8_t> &p_data) {

	int src_data_len = p_data.size();
#define MAX_TEST_MEM (1 << 20)

	uint32_t alloc_try = 1024;
	PoolVector<char> alloc_mem;
	PoolVector<char>::Write w;
	stb_vorbis *ogg_stream = NULL;
	stb_vorbis_alloc ogg_alloc;

	while (alloc_try < MAX_TEST_MEM) {

		alloc_mem.resize(alloc_try);
		w = alloc_mem.write();

		ogg_alloc.alloc_buffer = w.ptr();
		ogg_alloc.alloc_buffer_length_in_bytes = alloc_try;

		PoolVector<uint8_t>::Read src_datar = p_data.read();

		int error;
		ogg_stream = stb_vorbis_open_memory((const unsigned char *)src_datar.ptr(), src_data_len, &error, &ogg_alloc);

		if (!ogg_stream && error == VORBIS_outofmem) {
			w = PoolVector<char>::Write();
			alloc_try *= 2;
		} else {

			ERR_FAIL_COND(alloc_try == MAX_TEST_MEM);
			ERR_FAIL_COND(ogg_stream == NULL);

			stb_vorbis_info info = stb_vorbis_get_info(ogg_stream);

			channels = info.channels;
			sample_rate = info.sample_rate;
			decode_mem_size = alloc_try;
			//does this work? (it's less mem..)
			//decode_mem_size = ogg_alloc.alloc_buffer_length_in_bytes + info.setup_memory_required + info.temp_memory_required + info.max_frame_size;

			//print_line("succeeded "+itos(ogg_alloc.alloc_buffer_length_in_bytes)+" setup "+itos(info.setup_memory_required)+" setup temp "+itos(info.setup_temp_memory_required)+" temp "+itos(info.temp_memory_required)+" maxframe"+itos(info.max_frame_size));

			length = stb_vorbis_stream_length_in_seconds(ogg_stream);
			stb_vorbis_close(ogg_stream);

			data = AudioServer::get_singleton()->audio_data_alloc(src_data_len, src_datar.ptr());
			data_len = src_data_len;

			break;
		}
	}
}

PoolVector<uint8_t> AudioStreamOGGVorbis::get_data() const {

	PoolVector<uint8_t> vdata;

	if (data_len && data) {
		vdata.resize(data_len);
		{
			PoolVector<uint8_t>::Write w = vdata.write();
			copymem(w.ptr(), data, data_len);
		}
	}

	return vdata;
}

void AudioStreamOGGVorbis::set_loop(bool p_enable) {
	loop = p_enable;
}

bool AudioStreamOGGVorbis::has_loop() const {

	return loop;
}

void AudioStreamOGGVorbis::set_loop_offset(float p_seconds) {
	loop_offset = p_seconds;
}

float AudioStreamOGGVorbis::get_loop_offset() const {
	return loop_offset;
}

void AudioStreamOGGVorbis::_bind_methods() {

	ClassDB::bind_method(D_METHOD("set_data", "data"), &AudioStreamOGGVorbis::set_data);
	ClassDB::bind_method(D_METHOD("get_data"), &AudioStreamOGGVorbis::get_data);

	ClassDB::bind_method(D_METHOD("set_loop", "enable"), &AudioStreamOGGVorbis::set_loop);
	ClassDB::bind_method(D_METHOD("has_loop"), &AudioStreamOGGVorbis::has_loop);

	ClassDB::bind_method(D_METHOD("set_loop_offset", "seconds"), &AudioStreamOGGVorbis::set_loop_offset);
	ClassDB::bind_method(D_METHOD("get_loop_offset"), &AudioStreamOGGVorbis::get_loop_offset);

	ADD_PROPERTY(PropertyInfo(Variant::POOL_BYTE_ARRAY, "data", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR), "set_data", "get_data");
	ADD_PROPERTY(PropertyInfo(Variant::BOOL, "loop", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR), "set_loop", "has_loop");
	ADD_PROPERTY(PropertyInfo(Variant::REAL, "loop_offset", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR), "set_loop_offset", "get_loop_offset");
}

AudioStreamOGGVorbis::AudioStreamOGGVorbis() {

	data = NULL;
	length = 0;
	sample_rate = 1;
	channels = 1;
	loop_offset = 0;
	decode_mem_size = 0;
	loop = false;
}
<|endoftext|>