<commit_before>#include "rtkDbf.h"
#include "rtkMacro.h"

namespace rtk
{

DbfField::DbfField(std::string name, char type, unsigned char length, short recOffset):
  m_Name(name),
  m_Type(type),
  m_Length(length),
  m_RecOffset(recOffset)
{
}

DbfFile::DbfFile(std::string fileName)
{
  m_Stream.open(fileName, std::ios_base::in | std::ios_base::binary);
  if(!m_Stream.is_open())
    return;

  // Ignore version and date
  m_Stream.seekg(4);

  // Read number of records, header size, record size
  m_Stream.read((char*)&m_NumRecords, sizeof(m_NumRecords));
  m_Stream.read((char*)&m_HeaderSize, sizeof(m_HeaderSize));
  m_Stream.read((char*)&m_RecordSize, sizeof(m_RecordSize));

  // Record allocation
  m_Record = new char[m_RecordSize];

  // The number of fields depends on the header size (32 are the blocks
  // describing the global part and each field, 1 is the terminator length)
  const unsigned int numFields = (m_HeaderSize - 32 - 1) / 32;

  // First byte is the deletion character. Then the field data are concatenated
  short fldRecOffset = 1;
  for(unsigned int i=0; i<numFields; i++){

    // Go to beginning of current field structure description
    m_Stream.seekg(32 * (1 + i));

    // Field names
    char fldName[11];
    m_Stream.read(fldName, 11);

    // Field types
    char fldType;
    m_Stream.read((char*)&fldType, sizeof(fldType));

    // Skip displacement of field in record?
    m_Stream.seekg(4, std::ios_base::cur);

    // Field length
    unsigned char fldLength;
    m_Stream.read((char*)&fldLength, sizeof(fldLength));

    // Add field and go to next
    m_Fields.push_back(DbfField(fldName, fldType, fldLength, fldRecOffset));
    m_MapFieldNameIndex[m_Fields.back().GetName()] = i;

    fldRecOffset+=fldLength;
  }

  // Seek to first record
  m_Stream.seekg(m_HeaderSize);
}

DbfFile::~DbfFile()
{
  free(m_Record);
}

bool DbfFile::ReadNextRecord()
{
//  do{
    m_Stream.read(m_Record, m_RecordSize);
//  }
//  while(m_Stream.gcount() == m_RecordSize /*&& m_Record[0] == '*'*/);
  return m_Stream.gcount() == m_RecordSize;
}

std::string DbfFile::GetFieldAsString(std::string fldName){
  DbfField & field = m_Fields[ m_MapFieldNameIndex[fldName] ];
  std::string result( m_Record + field.GetRecOffset(), field.GetLength() );

  // Revome begin/end spaces
  std::string::size_type pos = result.find_last_not_of(' ');
  if(pos != std::string::npos){
    result.erase(pos + 1);
    pos = result.find_first_not_of(' ');
    if(pos != std::string::npos)
      result.erase(0, pos);
  }
  else
    result.erase(result.begin(), result.end());

  return result;
}

}<commit_msg>Account for delete flag of each record<commit_after>#include "rtkDbf.h"
#include "rtkMacro.h"

namespace rtk
{

DbfField::DbfField(std::string name, char type, unsigned char length, short recOffset):
  m_Name(name),
  m_Type(type),
  m_Length(length),
  m_RecOffset(recOffset)
{
}

DbfFile::DbfFile(std::string fileName)
{
  m_Stream.open(fileName, std::ios_base::in | std::ios_base::binary);
  if(!m_Stream.is_open())
    return;

  // Ignore version and date
  m_Stream.seekg(4);

  // Read number of records, header size, record size
  m_Stream.read((char*)&m_NumRecords, sizeof(m_NumRecords));
  m_Stream.read((char*)&m_HeaderSize, sizeof(m_HeaderSize));
  m_Stream.read((char*)&m_RecordSize, sizeof(m_RecordSize));

  // Record allocation
  m_Record = new char[m_RecordSize];

  // The number of fields depends on the header size (32 are the blocks
  // describing the global part and each field, 1 is the terminator length)
  const unsigned int numFields = (m_HeaderSize - 32 - 1) / 32;

  // First byte is the deletion character. Then the field data are concatenated
  short fldRecOffset = 1;
  for(unsigned int i=0; i<numFields; i++){

    // Go to beginning of current field structure description
    m_Stream.seekg(32 * (1 + i));

    // Field names
    char fldName[11];
    m_Stream.read(fldName, 11);

    // Field types
    char fldType;
    m_Stream.read((char*)&fldType, sizeof(fldType));

    // Skip displacement of field in record?
    m_Stream.seekg(4, std::ios_base::cur);

    // Field length
    unsigned char fldLength;
    m_Stream.read((char*)&fldLength, sizeof(fldLength));

    // Add field and go to next
    m_Fields.push_back(DbfField(fldName, fldType, fldLength, fldRecOffset));
    m_MapFieldNameIndex[m_Fields.back().GetName()] = i;

    fldRecOffset+=fldLength;
  }

  // Seek to first record
  m_Stream.seekg(m_HeaderSize);
}

DbfFile::~DbfFile()
{
  free(m_Record);
}

bool DbfFile::ReadNextRecord()
{
  do
    {
    m_Stream.read(m_Record, m_RecordSize);
    }
  while(m_Stream.gcount() == m_RecordSize && m_Record[0] == 0x2A);
  return m_Stream.gcount() == m_RecordSize;
}

std::string DbfFile::GetFieldAsString(std::string fldName){
  DbfField & field = m_Fields[ m_MapFieldNameIndex[fldName] ];
  std::string result( m_Record + field.GetRecOffset(), field.GetLength() );

  // Revome begin/end spaces
  std::string::size_type pos = result.find_last_not_of(' ');
  if(pos != std::string::npos){
    result.erase(pos + 1);
    pos = result.find_first_not_of(' ');
    if(pos != std::string::npos)
      result.erase(0, pos);
  }
  else
    result.erase(result.begin(), result.end());

  return result;
}

}<|endoftext|>


<commit_before>/*-------------------------------------------------------------------------
 *
 *   FILE
 *	cursor.cxx
 *
 *   DESCRIPTION
 *      implementation of libpqxx STL-style cursor classes.
 *   These classes wrap SQL cursors in STL-like interfaces
 *
 * Copyright (c) 2004-2007, Jeroen T. Vermeulen <jtv@xs4all.nl>
 *
 * See COPYING for copyright license.  If you did not receive a file called
 * COPYING with this source code, please notify the distributor of this mistake,
 * or contact the author.
 *
 *-------------------------------------------------------------------------
 */
#include "pqxx/compiler-internal.hxx"

#include <cstdlib>

#include "pqxx/cursor"
#include "pqxx/result"
#include "pqxx/transaction"

using namespace PGSTD;

namespace
{
/// Compute actual displacement based on requested and reported displacements
pqxx::cursor_base::difference_type adjust(
    pqxx::cursor_base::difference_type d,
    pqxx::cursor_base::difference_type r)
{
  const pqxx::cursor_base::difference_type hoped = labs(d);
  pqxx::cursor_base::difference_type actual = r;
  if (hoped < 0 || r < hoped) ++actual;
  return (d < 0) ? -actual : actual;
}
}

pqxx::cursor_base::cursor_base(transaction_base *context,
    const PGSTD::string &Name,
    bool embellish_name) :
  m_context(context),
  m_done(false),
  m_name(embellish_name ? context->conn().adorn_name(Name) : Name),
  m_adopted(false),
  m_ownership(loose),
  m_lastfetch(),
  m_lastmove()
{
}

string pqxx::cursor_base::stridestring(difference_type n)
{
  /* Some special-casing for ALL and BACKWARD ALL here.  We used to use numeric
   * "infinities" for difference_type for this (the highest and lowest possible
   * values for "long"), but for PostgreSQL 8.0 at least, the backend appears to
   * expect a 32-bit number and fails to parse large 64-bit numbers.
   * We could change the typedef to match this behaviour, but that would break
   * if/when Postgres is changed to accept 64-bit displacements.
   */
  static const string All("ALL"), BackAll("BACKWARD ALL");
  if (n == all()) return All;
  else if (n == backward_all()) return BackAll;
  return to_string(n);
}

namespace
{
/// Is this character a "useless trailing character" in a query?
/** A character is "useless" at the end of a query if it is either whitespace or
 * a semicolon.
 */
inline bool useless_trail(char c)
{
  return isspace(c) || c==';';
}

}

void pqxx::cursor_base::declare(const PGSTD::string &query,
    accesspolicy ap,
    updatepolicy up,
    ownershippolicy op,
    bool hold)
{
  stringstream cq, qn;

  /* Strip trailing semicolons (and whitespace, as side effect) off query.  The
   * whitespace is stripped because it might otherwise mask a semicolon.  After
   * this, the remaining useful query will be the sequence defined by
   * query.begin() and last, i.e. last may be equal to query.end() or point to
   * the first useless trailing character.
   */
  string::const_iterator last = query.end();
  // TODO: May break on multibyte encodings!
  for (--last; last!=query.begin() && useless_trail(*last); --last);
  if (last==query.begin() && useless_trail(*last))
    throw invalid_argument("Cursor created on empty query");
  ++last;

  cq << "DECLARE \"" << name() << "\" ";

  m_context->conn().activate();
  if (m_context->conn().supports(connection_base::cap_cursor_scroll))
  {
    if (ap == forward_only) cq << "NO ";
    cq << "SCROLL ";
  }

  cq << "CURSOR ";

  if (hold)
  {
    if (!m_context->conn().supports(connection_base::cap_cursor_with_hold))
      throw runtime_error("Cursor " + name() + " "
	  "created for use outside of its originating transaction, "
	  "but this backend version does not support that.");
    cq << "WITH HOLD ";
  }

  cq << "FOR " << string(query.begin(),last) << ' ';

  if (up != update) cq << "FOR READ ONLY ";
  else if (!m_context->conn().supports(connection_base::cap_cursor_update))
    throw runtime_error("Cursor " + name() + " "
	"created as updatable, "
	"but this backend version does not support that.");
  else cq << "FOR UPDATE ";

  qn << "[DECLARE " << name() << ']';
  m_context->exec(cq, qn.str());

  // If we're creating a WITH HOLD cursor, noone is going to destroy it until
  // after this transaction.  That means the connection cannot be deactivated
  // without losing the cursor.
  m_ownership = op;
  if (op==loose) m_context->m_reactivation_avoidance.add(1);
}

void pqxx::cursor_base::adopt(ownershippolicy op)
{
  // If we take responsibility for destroying the cursor, that's one less reason
  // not to allow the connection to be deactivated and reactivated.
  if (op==owned) m_context->m_reactivation_avoidance.add(-1);
  m_adopted = true;
  m_ownership = op;
}

void pqxx::cursor_base::close() throw ()
{
  if (m_ownership==owned)
  {
    try
    {
      m_context->exec("CLOSE \"" + name() + "\"");
    }
    catch (const exception &)
    {
    }

    if (m_adopted) m_context->m_reactivation_avoidance.add(-1);
    m_ownership = loose;
  }
}

pqxx::result pqxx::cursor_base::fetch(difference_type n)
{
  result r;
  if (n)
  {
    // We cache the last-executed fetch query.  If the current fetch uses the
    // same distance, we just re-use the cached string instead of composing a
    // new one.
    const string fq(
	(n == m_lastfetch.dist) ?
         m_lastfetch.query :
         "FETCH " + stridestring(n) + " IN \"" + name() + "\"");

    // Set m_done on exception (but no need for try/catch)
    m_done = true;
    r = m_context->exec(fq);
    if (!r.empty()) m_done = false;
  }
  return r;
}

pqxx::result pqxx::cursor_base::fetch(difference_type n, difference_type &d)
{
  result r(cursor_base::fetch(n));
  d = adjust(n, r.size());
  return r;
}

template<> void
pqxx::cursor_base::check_displacement<pqxx::cursor_base::forward_only>(
    difference_type d) const
{
  if (d < 0)
    throw logic_error("Attempt to move cursor " + name() + " "
	"backwards (this cursor is only allowed to move forwards)");
}

pqxx::cursor_base::difference_type pqxx::cursor_base::move(difference_type n)
{
  if (!n) return 0;

  const string mq(
      (n == m_lastmove.dist) ?
      m_lastmove.query :
      "MOVE " + stridestring(n) + " IN \"" + name() + "\"");

  // Set m_done on exception (but no need for try/catch)
  m_done = true;
  const result r(m_context->exec(mq));

  // Starting with the libpq in PostgreSQL 7.4, PQcmdTuples() (which we call
  // indirectly here) also returns the number of rows skipped by a MOVE
  difference_type d = r.affected_rows();

  // We may not have PQcmdTuples(), or this may be a libpq version that doesn't
  // implement it for MOVE yet.  We'll also get zero if we decide to use a
  // prepared statement for the MOVE.
  if (!d)
  {
    static const string StdResponse("MOVE ");
    if (strncmp(r.CmdStatus(), StdResponse.c_str(), StdResponse.size()) != 0)
      throw internal_error("cursor MOVE returned "
	  "'" + string(r.CmdStatus()) + "' "
	  "(expected '" + StdResponse + "')");

    from_string(r.CmdStatus()+StdResponse.size(), d);
  }
  m_done = (d != n);
  return d;
}

pqxx::cursor_base::difference_type
pqxx::cursor_base::move(difference_type n, difference_type &d)
{
  const difference_type got = cursor_base::move(n);
  d = adjust(n, got);
  return got;
}

pqxx::icursorstream::icursorstream(pqxx::transaction_base &context,
    const PGSTD::string &query,
    const PGSTD::string &basename,
    difference_type Stride) :
  super(&context, query, basename),
  m_stride(Stride),
  m_realpos(0),
  m_reqpos(0),
  m_iterators(0)
{
  set_stride(Stride);
}

pqxx::icursorstream::icursorstream(transaction_base &Context,
    const result::field &Name,
    difference_type Stride) :
  super(&Context, Name.c_str()),
  m_stride(Stride),
  m_realpos(0),
  m_reqpos(0),
  m_iterators(0)
{
  set_stride(Stride);
}

void pqxx::icursorstream::set_stride(difference_type n)
{
  if (n < 1)
    throw invalid_argument("Attempt to set cursor stride to " + to_string(n));
  m_stride = n;
}

pqxx::result pqxx::icursorstream::fetchblock()
{
  const result r(fetch(m_stride));
  m_realpos += r.size();
  return r;
}

pqxx::icursorstream &pqxx::icursorstream::ignore(PGSTD::streamsize n)
{
  m_realpos += move(n);
  return *this;
}

pqxx::icursorstream::size_type pqxx::icursorstream::forward(size_type n)
{
  m_reqpos += n*m_stride;
  return m_reqpos;
}

void pqxx::icursorstream::insert_iterator(icursor_iterator *i) throw ()
{
  i->m_next = m_iterators;
  if (m_iterators) m_iterators->m_prev = i;
  m_iterators = i;
}

void pqxx::icursorstream::remove_iterator(icursor_iterator *i) const throw ()
{
  if (i == m_iterators)
  {
    m_iterators = i->m_next;
    if (m_iterators) m_iterators->m_prev = 0;
  }
  else
  {
    i->m_prev->m_next = i->m_next;
    if (i->m_next) i->m_next->m_prev = i->m_prev;
  }
  i->m_prev = 0;
  i->m_next = 0;
}

void pqxx::icursorstream::service_iterators(difference_type topos)
{
  if (topos < m_realpos) return;

  typedef multimap<difference_type,icursor_iterator*> todolist;
  todolist todo;
  for (icursor_iterator *i = m_iterators; i; i = i->m_next)
    if (i->m_pos >= m_realpos && i->m_pos <= topos)
      todo.insert(todolist::value_type(i->m_pos, i));
  const todolist::const_iterator todo_end(todo.end());
  for (todolist::const_iterator i = todo.begin(); i != todo_end; )
  {
    const difference_type readpos = i->first;
    if (readpos > m_realpos) ignore(readpos - m_realpos);
    const result r = fetchblock();
    for ( ; i != todo_end && i->first == readpos; ++i)
      i->second->fill(r);
  }
}

pqxx::icursor_iterator::icursor_iterator() throw () :
  m_stream(0),
  m_here(),
  m_pos(0),
  m_prev(0),
  m_next(0)
{
}

pqxx::icursor_iterator::icursor_iterator(istream_type &s) throw () :
  m_stream(&s),
  m_here(),
  m_pos(s.forward(0)),
  m_prev(0),
  m_next(0)
{
  s.insert_iterator(this);
}

pqxx::icursor_iterator::icursor_iterator(const icursor_iterator &rhs) throw () :
  m_stream(rhs.m_stream),
  m_here(rhs.m_here),
  m_pos(rhs.m_pos),
  m_prev(0),
  m_next(0)
{
  if (m_stream) m_stream->insert_iterator(this);
}

pqxx::icursor_iterator::~icursor_iterator() throw ()
{
  if (m_stream) m_stream->remove_iterator(this);
}

pqxx::icursor_iterator pqxx::icursor_iterator::operator++(int)
{
  icursor_iterator old(*this);
  m_pos = m_stream->forward();
  m_here.clear();
  return old;
}

pqxx::icursor_iterator &pqxx::icursor_iterator::operator++()
{
  m_pos = m_stream->forward();
  m_here.clear();
  return *this;
}

pqxx::icursor_iterator &pqxx::icursor_iterator::operator+=(difference_type n)
{
  if (n <= 0)
  {
    if (!n) return *this;
    throw invalid_argument("Advancing icursor_iterator by negative offset");
  }
  m_pos = m_stream->forward(n);
  m_here.clear();
  return *this;
}

pqxx::icursor_iterator &
pqxx::icursor_iterator::operator=(const icursor_iterator &rhs) throw ()
{
  if (rhs.m_stream == m_stream)
  {
    m_here = rhs.m_here;
    m_pos = rhs.m_pos;
  }
  else
  {
    if (m_stream) m_stream->remove_iterator(this);
    m_here = rhs.m_here;
    m_pos = rhs.m_pos;
    m_stream = rhs.m_stream;
    if (m_stream) m_stream->insert_iterator(this);
  }
  return *this;
}

bool pqxx::icursor_iterator::operator==(const icursor_iterator &rhs) const
{
  if (m_stream == rhs.m_stream) return pos() == rhs.pos();
  if (m_stream && rhs.m_stream) return false;
  refresh();
  rhs.refresh();
  return m_here.empty() && rhs.m_here.empty();
}

bool pqxx::icursor_iterator::operator<(const icursor_iterator &rhs) const
{
  if (m_stream == rhs.m_stream) return pos() < rhs.pos();
  refresh();
  rhs.refresh();
  return !m_here.empty();
}

void pqxx::icursor_iterator::refresh() const
{
  if (m_stream) m_stream->service_iterators(pos());
}

void pqxx::icursor_iterator::fill(const result &r)
{
  m_here = r;
}

<commit_msg>Added missing <cstring> include, thanks Roger Leigh<commit_after>/*-------------------------------------------------------------------------
 *
 *   FILE
 *	cursor.cxx
 *
 *   DESCRIPTION
 *      implementation of libpqxx STL-style cursor classes.
 *   These classes wrap SQL cursors in STL-like interfaces
 *
 * Copyright (c) 2004-2007, Jeroen T. Vermeulen <jtv@xs4all.nl>
 *
 * See COPYING for copyright license.  If you did not receive a file called
 * COPYING with this source code, please notify the distributor of this mistake,
 * or contact the author.
 *
 *-------------------------------------------------------------------------
 */
#include "pqxx/compiler-internal.hxx"

#include <cstdlib>
#include <cstring>

#include "pqxx/cursor"
#include "pqxx/result"
#include "pqxx/transaction"

using namespace PGSTD;

namespace
{
/// Compute actual displacement based on requested and reported displacements
pqxx::cursor_base::difference_type adjust(
    pqxx::cursor_base::difference_type d,
    pqxx::cursor_base::difference_type r)
{
  const pqxx::cursor_base::difference_type hoped = labs(d);
  pqxx::cursor_base::difference_type actual = r;
  if (hoped < 0 || r < hoped) ++actual;
  return (d < 0) ? -actual : actual;
}
}

pqxx::cursor_base::cursor_base(transaction_base *context,
    const PGSTD::string &Name,
    bool embellish_name) :
  m_context(context),
  m_done(false),
  m_name(embellish_name ? context->conn().adorn_name(Name) : Name),
  m_adopted(false),
  m_ownership(loose),
  m_lastfetch(),
  m_lastmove()
{
}

string pqxx::cursor_base::stridestring(difference_type n)
{
  /* Some special-casing for ALL and BACKWARD ALL here.  We used to use numeric
   * "infinities" for difference_type for this (the highest and lowest possible
   * values for "long"), but for PostgreSQL 8.0 at least, the backend appears to
   * expect a 32-bit number and fails to parse large 64-bit numbers.
   * We could change the typedef to match this behaviour, but that would break
   * if/when Postgres is changed to accept 64-bit displacements.
   */
  static const string All("ALL"), BackAll("BACKWARD ALL");
  if (n == all()) return All;
  else if (n == backward_all()) return BackAll;
  return to_string(n);
}

namespace
{
/// Is this character a "useless trailing character" in a query?
/** A character is "useless" at the end of a query if it is either whitespace or
 * a semicolon.
 */
inline bool useless_trail(char c)
{
  return isspace(c) || c==';';
}

}

void pqxx::cursor_base::declare(const PGSTD::string &query,
    accesspolicy ap,
    updatepolicy up,
    ownershippolicy op,
    bool hold)
{
  stringstream cq, qn;

  /* Strip trailing semicolons (and whitespace, as side effect) off query.  The
   * whitespace is stripped because it might otherwise mask a semicolon.  After
   * this, the remaining useful query will be the sequence defined by
   * query.begin() and last, i.e. last may be equal to query.end() or point to
   * the first useless trailing character.
   */
  string::const_iterator last = query.end();
  // TODO: May break on multibyte encodings!
  for (--last; last!=query.begin() && useless_trail(*last); --last);
  if (last==query.begin() && useless_trail(*last))
    throw invalid_argument("Cursor created on empty query");
  ++last;

  cq << "DECLARE \"" << name() << "\" ";

  m_context->conn().activate();
  if (m_context->conn().supports(connection_base::cap_cursor_scroll))
  {
    if (ap == forward_only) cq << "NO ";
    cq << "SCROLL ";
  }

  cq << "CURSOR ";

  if (hold)
  {
    if (!m_context->conn().supports(connection_base::cap_cursor_with_hold))
      throw runtime_error("Cursor " + name() + " "
	  "created for use outside of its originating transaction, "
	  "but this backend version does not support that.");
    cq << "WITH HOLD ";
  }

  cq << "FOR " << string(query.begin(),last) << ' ';

  if (up != update) cq << "FOR READ ONLY ";
  else if (!m_context->conn().supports(connection_base::cap_cursor_update))
    throw runtime_error("Cursor " + name() + " "
	"created as updatable, "
	"but this backend version does not support that.");
  else cq << "FOR UPDATE ";

  qn << "[DECLARE " << name() << ']';
  m_context->exec(cq, qn.str());

  // If we're creating a WITH HOLD cursor, noone is going to destroy it until
  // after this transaction.  That means the connection cannot be deactivated
  // without losing the cursor.
  m_ownership = op;
  if (op==loose) m_context->m_reactivation_avoidance.add(1);
}

void pqxx::cursor_base::adopt(ownershippolicy op)
{
  // If we take responsibility for destroying the cursor, that's one less reason
  // not to allow the connection to be deactivated and reactivated.
  if (op==owned) m_context->m_reactivation_avoidance.add(-1);
  m_adopted = true;
  m_ownership = op;
}

void pqxx::cursor_base::close() throw ()
{
  if (m_ownership==owned)
  {
    try
    {
      m_context->exec("CLOSE \"" + name() + "\"");
    }
    catch (const exception &)
    {
    }

    if (m_adopted) m_context->m_reactivation_avoidance.add(-1);
    m_ownership = loose;
  }
}

pqxx::result pqxx::cursor_base::fetch(difference_type n)
{
  result r;
  if (n)
  {
    // We cache the last-executed fetch query.  If the current fetch uses the
    // same distance, we just re-use the cached string instead of composing a
    // new one.
    const string fq(
	(n == m_lastfetch.dist) ?
         m_lastfetch.query :
         "FETCH " + stridestring(n) + " IN \"" + name() + "\"");

    // Set m_done on exception (but no need for try/catch)
    m_done = true;
    r = m_context->exec(fq);
    if (!r.empty()) m_done = false;
  }
  return r;
}

pqxx::result pqxx::cursor_base::fetch(difference_type n, difference_type &d)
{
  result r(cursor_base::fetch(n));
  d = adjust(n, r.size());
  return r;
}

template<> void
pqxx::cursor_base::check_displacement<pqxx::cursor_base::forward_only>(
    difference_type d) const
{
  if (d < 0)
    throw logic_error("Attempt to move cursor " + name() + " "
	"backwards (this cursor is only allowed to move forwards)");
}

pqxx::cursor_base::difference_type pqxx::cursor_base::move(difference_type n)
{
  if (!n) return 0;

  const string mq(
      (n == m_lastmove.dist) ?
      m_lastmove.query :
      "MOVE " + stridestring(n) + " IN \"" + name() + "\"");

  // Set m_done on exception (but no need for try/catch)
  m_done = true;
  const result r(m_context->exec(mq));

  // Starting with the libpq in PostgreSQL 7.4, PQcmdTuples() (which we call
  // indirectly here) also returns the number of rows skipped by a MOVE
  difference_type d = r.affected_rows();

  // We may not have PQcmdTuples(), or this may be a libpq version that doesn't
  // implement it for MOVE yet.  We'll also get zero if we decide to use a
  // prepared statement for the MOVE.
  if (!d)
  {
    static const string StdResponse("MOVE ");
    if (strncmp(r.CmdStatus(), StdResponse.c_str(), StdResponse.size()) != 0)
      throw internal_error("cursor MOVE returned "
	  "'" + string(r.CmdStatus()) + "' "
	  "(expected '" + StdResponse + "')");

    from_string(r.CmdStatus()+StdResponse.size(), d);
  }
  m_done = (d != n);
  return d;
}

pqxx::cursor_base::difference_type
pqxx::cursor_base::move(difference_type n, difference_type &d)
{
  const difference_type got = cursor_base::move(n);
  d = adjust(n, got);
  return got;
}

pqxx::icursorstream::icursorstream(pqxx::transaction_base &context,
    const PGSTD::string &query,
    const PGSTD::string &basename,
    difference_type Stride) :
  super(&context, query, basename),
  m_stride(Stride),
  m_realpos(0),
  m_reqpos(0),
  m_iterators(0)
{
  set_stride(Stride);
}

pqxx::icursorstream::icursorstream(transaction_base &Context,
    const result::field &Name,
    difference_type Stride) :
  super(&Context, Name.c_str()),
  m_stride(Stride),
  m_realpos(0),
  m_reqpos(0),
  m_iterators(0)
{
  set_stride(Stride);
}

void pqxx::icursorstream::set_stride(difference_type n)
{
  if (n < 1)
    throw invalid_argument("Attempt to set cursor stride to " + to_string(n));
  m_stride = n;
}

pqxx::result pqxx::icursorstream::fetchblock()
{
  const result r(fetch(m_stride));
  m_realpos += r.size();
  return r;
}

pqxx::icursorstream &pqxx::icursorstream::ignore(PGSTD::streamsize n)
{
  m_realpos += move(n);
  return *this;
}

pqxx::icursorstream::size_type pqxx::icursorstream::forward(size_type n)
{
  m_reqpos += n*m_stride;
  return m_reqpos;
}

void pqxx::icursorstream::insert_iterator(icursor_iterator *i) throw ()
{
  i->m_next = m_iterators;
  if (m_iterators) m_iterators->m_prev = i;
  m_iterators = i;
}

void pqxx::icursorstream::remove_iterator(icursor_iterator *i) const throw ()
{
  if (i == m_iterators)
  {
    m_iterators = i->m_next;
    if (m_iterators) m_iterators->m_prev = 0;
  }
  else
  {
    i->m_prev->m_next = i->m_next;
    if (i->m_next) i->m_next->m_prev = i->m_prev;
  }
  i->m_prev = 0;
  i->m_next = 0;
}

void pqxx::icursorstream::service_iterators(difference_type topos)
{
  if (topos < m_realpos) return;

  typedef multimap<difference_type,icursor_iterator*> todolist;
  todolist todo;
  for (icursor_iterator *i = m_iterators; i; i = i->m_next)
    if (i->m_pos >= m_realpos && i->m_pos <= topos)
      todo.insert(todolist::value_type(i->m_pos, i));
  const todolist::const_iterator todo_end(todo.end());
  for (todolist::const_iterator i = todo.begin(); i != todo_end; )
  {
    const difference_type readpos = i->first;
    if (readpos > m_realpos) ignore(readpos - m_realpos);
    const result r = fetchblock();
    for ( ; i != todo_end && i->first == readpos; ++i)
      i->second->fill(r);
  }
}

pqxx::icursor_iterator::icursor_iterator() throw () :
  m_stream(0),
  m_here(),
  m_pos(0),
  m_prev(0),
  m_next(0)
{
}

pqxx::icursor_iterator::icursor_iterator(istream_type &s) throw () :
  m_stream(&s),
  m_here(),
  m_pos(s.forward(0)),
  m_prev(0),
  m_next(0)
{
  s.insert_iterator(this);
}

pqxx::icursor_iterator::icursor_iterator(const icursor_iterator &rhs) throw () :
  m_stream(rhs.m_stream),
  m_here(rhs.m_here),
  m_pos(rhs.m_pos),
  m_prev(0),
  m_next(0)
{
  if (m_stream) m_stream->insert_iterator(this);
}

pqxx::icursor_iterator::~icursor_iterator() throw ()
{
  if (m_stream) m_stream->remove_iterator(this);
}

pqxx::icursor_iterator pqxx::icursor_iterator::operator++(int)
{
  icursor_iterator old(*this);
  m_pos = m_stream->forward();
  m_here.clear();
  return old;
}

pqxx::icursor_iterator &pqxx::icursor_iterator::operator++()
{
  m_pos = m_stream->forward();
  m_here.clear();
  return *this;
}

pqxx::icursor_iterator &pqxx::icursor_iterator::operator+=(difference_type n)
{
  if (n <= 0)
  {
    if (!n) return *this;
    throw invalid_argument("Advancing icursor_iterator by negative offset");
  }
  m_pos = m_stream->forward(n);
  m_here.clear();
  return *this;
}

pqxx::icursor_iterator &
pqxx::icursor_iterator::operator=(const icursor_iterator &rhs) throw ()
{
  if (rhs.m_stream == m_stream)
  {
    m_here = rhs.m_here;
    m_pos = rhs.m_pos;
  }
  else
  {
    if (m_stream) m_stream->remove_iterator(this);
    m_here = rhs.m_here;
    m_pos = rhs.m_pos;
    m_stream = rhs.m_stream;
    if (m_stream) m_stream->insert_iterator(this);
  }
  return *this;
}

bool pqxx::icursor_iterator::operator==(const icursor_iterator &rhs) const
{
  if (m_stream == rhs.m_stream) return pos() == rhs.pos();
  if (m_stream && rhs.m_stream) return false;
  refresh();
  rhs.refresh();
  return m_here.empty() && rhs.m_here.empty();
}

bool pqxx::icursor_iterator::operator<(const icursor_iterator &rhs) const
{
  if (m_stream == rhs.m_stream) return pos() < rhs.pos();
  refresh();
  rhs.refresh();
  return !m_here.empty();
}

void pqxx::icursor_iterator::refresh() const
{
  if (m_stream) m_stream->service_iterators(pos());
}

void pqxx::icursor_iterator::fill(const result &r)
{
  m_here = r;
}

<|endoftext|>


<commit_before>/*
    daemon.cpp

    Copyright (c) 2013, Nikita Karnauhov

    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.

    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 <string>
#include <stdexcept>
#include <memory>
#include <iostream>
#include <sstream>
#include <iomanip>
#include <fstream>
#include <list>
#include <set>
#include <thread>
#include <mutex>
#include <unordered_map>

#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <fcntl.h>
#include <netinet/in.h>
#include <netdb.h>
#include <poll.h>
#include <arpa/inet.h>
#include <errno.h>
#include <string.h>
#include <getopt.h>
#include <signal.h>

#include <alsa/asoundlib.h>

#include <lansink.pb.h>
#include "log.h"
#include "alsa.h"
#include "player.h"
#include "settings.h"

// Signal flags.
static int g_nExitSignal = 0;

static
void _print_usage(std::ostream &_os) {
    static const Settings c_defaults;

    _os << "d, network audio receiver.\n" <<
            "Usage: " LANSINK_PROGRAM_NAME " [OPTION]...\n\n" <<
            "  -h, --help                       Print this message and exit.\n" <<
            "  -v, --version                    Display the version and exit.\n" <<
            "  -c, --config-path PATH           Set configuration file location.\n" <<
            "  -l, --log-path PATH              Set log file location (default: " << c_defaults.strLogPath << ").\n" <<
            "  -L, --log-level NUMBER           Set log verbosity level (default: " << c_defaults.nLogLevel << ", maximum: 4).\n" <<
            "  -d, --daemon                     Run as a daemon.\n" <<
            "  -n, --no-daemon                  Don't run as a daemon, display log messages (default).\n" <<
            "  -p, --pid-path PATH              PID file location (default: " << c_defaults.strPIDPath << ").\n" <<
            "  -H, --host NAME                  Server host.\n" <<
            "  -P, --port NUMBER                Server port (default: " << c_defaults.nPort << ").\n" <<
            "  -D, --alsa-device NAME           Output ALSA device.\n" <<
            "  -x, --exclusive                  Do not try to open device if it is already in use.\n" <<
            "      --no-exclusive               Try to open device for every new connection (default).\n" <<
            "      --recovery-timeout SECONDS   Seconds to wait after for stream data to reappear (default: " << c_defaults.nRecoveryTimeout << ").\n" <<
            "      --open-timeout MILLISECONDS  Milliseconds to wait between attempts to open device (default: " << c_defaults.nOpenTimeout << ").\n" <<
            "      --buffered-packets NUMBER    Number of packets preserved if failed to open device (default: " << c_defaults.nBufferedPackets << ").\n" <<
            "" << std::flush;
}

static
void _print_version() {
    std::cout << LANSINK_PROGRAM_NAME << ", network audio receiver.\n" <<
            "Version 0.1" << std::endl;
}

static
void _parse_options(int _nArgs, char *const _pArgs[]) {
    static struct option options[] = {
            {"help", 0, 0, 'h'},
            {"version", 0, 0, 'v'},
            {"config-path", required_argument, 0, 'c'},

            // These options have equivalents in settings file.
            {"log-path", required_argument, 0, 'l'},
            {"log-level", required_argument, 0, 'L'},
            {"daemon", 0, 0, 'd'},
            {"no-daemon", 0, 0, 'n'},
            {"pid-path", required_argument, 0, 'p'},
            {"host", required_argument, 0, 'H'},
            {"port", required_argument, 0, 'P'},
            {"alsa-device", required_argument, 0, 'D'},
            {"recovery-timeout", required_argument, 0, 500},
            {"open-timeout", required_argument, 0, 501},
            {"exclusive", 0, 0, 'x'},
            {"no-exclusive", 0, 0, 502},
            {0, 0, 0, 0}
    };

    const char *strOptions = "hvc:l:L:dnp:H:P:D:x";
    int nOption = 0;
    SettingsParser sp;
    std::map<std::string, std::string> kvs;
    std::unordered_map<int, std::string> longNames;

    for (auto &option : options)
        if (option.name)
            longNames[option.val] = option.name;

    // Handle --help, --version and --config-path options.
    while (true) {
        const int c = getopt_long(_nArgs, _pArgs, strOptions, options, &nOption);

        if (c == -1) {
            if (optind < _nArgs)
                throw RuntimeError("Redundant argument: %s", _pArgs[optind]);
            break;
        }

        switch (c) {
            case 'h':
                _print_usage(std::cout);
                exit(EXIT_SUCCESS);

            case 'v':
                _print_version();
                exit(EXIT_SUCCESS);

            case 'c':
                sp.parse_file(optarg);
                break;

            case 'd':
                kvs["daemon"] = "true";
                break;

            case 'n':
                kvs["daemon"] = "false";
                break;

            case 'x':
                kvs["exclusive"] = "true";
                break;

            case 502:
                kvs["exclusive"] = "false";
                break;

            case '?':
                _print_usage(std::cerr);
                exit(EXIT_FAILURE);

            default:
                if (!optarg)
                    throw LogicError("Option '%c' requires argument", c);

                kvs[longNames[c]] = optarg;
                break;
        }
    }

    for (auto &kv : kvs)
        sp.parse_option(kv.first, kv.second);

    g_settings = sp.get();

    if (g_settings.nLogLevel < llSilent || g_settings.nLogLevel > llDebug)
        throw RuntimeError("Invalid log level %d (must be between %d and %d)",
                g_settings.nLogLevel, llSilent, llDebug);
}

static
std::string _in_addr_to_string(struct sockaddr *_pAddr) {
    char buf[INET6_ADDRSTRLEN];
    void *pInAddr = nullptr;
    int nPort = 0;

    if (_pAddr->sa_family == AF_INET) {
        auto p = (struct sockaddr_in *)_pAddr;
        pInAddr = &p->sin_addr;
        nPort = p->sin_port;
    } else {
        auto p = (struct sockaddr_in6 *)_pAddr;
        pInAddr = &p->sin6_addr;
        nPort = p->sin6_port;
    }

    inet_ntop(_pAddr->sa_family, pInAddr, buf, sizeof(buf));

    return std::string(buf) + ":" + std::to_string(ntohs(nPort));
}

static
void _main(Log &_log) {
    std::string strPort = format(16, "%d", g_settings.nPort);
    struct addrinfo hints = {};
    struct addrinfo *pServerInfo = nullptr;

    hints.ai_family = AF_UNSPEC;
    hints.ai_socktype = SOCK_DGRAM;
    hints.ai_flags = AI_PASSIVE;

    if (getaddrinfo(g_settings.strHost.empty() ? NULL : g_settings.strHost.c_str(),
            strPort.c_str(), &hints, &pServerInfo) < 0)
        throw SystemError("getaddrinfo()");

    const int nSocket = socket(pServerInfo->ai_family,
            pServerInfo->ai_socktype, pServerInfo->ai_protocol);

    if (bind(nSocket, pServerInfo->ai_addr, pServerInfo->ai_addrlen) < 0)
        throw SystemError("bind()");

    _log.info("Server listening on %s",
            _in_addr_to_string(pServerInfo->ai_addr).c_str(), g_settings.nPort);

    freeaddrinfo(pServerInfo);

    struct pollfd fd{nSocket, POLLIN, 0};
    constexpr size_t cBufferSize = 1024*100; // Should be enough, right?
    auto pBuf = std::unique_ptr<char[]>(new char[cBufferSize]);
    std::list<lansink::Packet> packets;

    typedef std::chrono::steady_clock Clock;
    typedef std::chrono::duration<int, std::milli> Duration;
    typedef std::chrono::time_point<Clock> TimePoint;
    TimePoint m_lastOpenAttempt;

    do {
        try {
            if (poll(&fd, 1, 1000) < 0)
                throw SystemError("poll()");

            if (fd.revents & POLLIN) {
                struct sockaddr_storage sender = {};
                socklen_t cSendSize = sizeof(sender);

                bzero(&sender, sizeof(sender));

                const int nPacketSize = recvfrom(
                        fd.fd, pBuf.get(), cBufferSize, 0, (struct sockaddr *)&sender, &cSendSize);

                if (nPacketSize < 0)
                    throw SystemError("recvfrom()");

                lansink::Packet &packet = *packets.emplace(packets.end());

                if (!packet.ParseFromArray(pBuf.get(), nPacketSize)) {
                    _log.debug("Broken packet from %s",
                            _in_addr_to_string((struct sockaddr *)&sender).c_str());
                    continue;
                }

                if (packets.size() > (size_t)g_settings.nBufferedPackets)
                    packets.pop_front();

                if (m_lastOpenAttempt.time_since_epoch().count() > 0) {
                    // Last attempt at open() failed, check if timeout expired.
                    Duration ms(std::chrono::duration_cast<Duration>(Clock::now() - m_lastOpenAttempt));
                    if (ms.count() < g_settings.nOpenTimeout)
                        continue;
                }

                Player::TimePoint errorTime;
                Player *pPlayer = Player::get(packet, _log, errorTime);

                if (!pPlayer) {
                    _log.info("Dropping connection from %s: device is busy",
                            _in_addr_to_string((struct sockaddr *)&sender).c_str());
                    m_lastOpenAttempt = TimePoint(errorTime.time_since_epoch());
                    continue;
                }

                if (!pPlayer->is_prepared()) {
                    _log.info("New connection from %s",
                            _in_addr_to_string((struct sockaddr *)&sender).c_str());
                    pPlayer->init(packet);

                    if (pPlayer->is_prepared()) {
                        m_lastOpenAttempt = TimePoint();
                        pPlayer->run();
                    } else {
                        m_lastOpenAttempt = Clock::now();
                        continue;
                    }
                }

                for (auto iPacket = packets.begin(); iPacket != packets.end();)
                    if (iPacket->stream() == pPlayer->get_id()) {
                        pPlayer->play(*iPacket);
                        iPacket = packets.erase(iPacket);
                    } else
                        ++iPacket;
            }
        } catch (SystemError &se) {
            if (se.get_error() != EINTR)
                throw;
        }

        if (g_nExitSignal != 0)
            _log.info("Got signal %d, exiting gracefully.", g_nExitSignal);
    } while (g_nExitSignal == 0);
}

static
bool _write_pid(Log &_log) {
    // Specify empty PID path to disable writing PID.
    if (g_settings.strPIDPath.empty())
        return false;

    std::ofstream os(g_settings.strPIDPath);

    if (!os.good())
        throw RuntimeError("Cannot write PID file: %s", g_settings.strPIDPath.c_str());

    os << getpid() << std::endl;
    _log.info("PID file written to %s", g_settings.strPIDPath.c_str());

    return true;
}

static
void _handle_signal(int _nSignal, siginfo_t *_pSigInfo, void *_pContext) {
    g_nExitSignal = _nSignal;
}

static
void _init_signals() {
    struct sigaction action = {};
    const int exitSignals[]{
            SIGTERM, SIGINT, SIGPIPE, SIGALRM, SIGUSR1, SIGUSR2, SIGPOLL,
            SIGPROF, SIGVTALRM
    };

    action.sa_sigaction = &_handle_signal;
    action.sa_flags = SA_SIGINFO;

    for (int nSignal : exitSignals)
        if (sigaction(nSignal, &action, NULL) < 0)
            throw SystemError("sigaction()");

}

int main(int _nArgs, char *const _pArgs[]) {
    Log log("");
    bool bPIDWritten = false;

    log.setLevel(llError);

    try {
        _parse_options(_nArgs, _pArgs);
        log.setLevel(LogLevel(g_settings.nLogLevel));

        if (!g_settings.strLogPath.empty()) {
            log.open(g_settings.strLogPath);
            log.info("Logging to %s", g_settings.strLogPath.c_str());
        }

        if (g_settings.bDaemon) {
            log.close("");

            if (daemon(true, false) != 0)
                throw SystemError("daemon()");
        }

        _init_signals();
        bPIDWritten = _write_pid(log);
        _main(log);
    } catch (std::exception &e) {
        log.error(e.what());
        return EXIT_FAILURE;
    }

    if (bPIDWritten)
        unlink(g_settings.strPIDPath.c_str());

    return EXIT_SUCCESS;
}
<commit_msg>Fix log level.<commit_after>/*
    daemon.cpp

    Copyright (c) 2013, Nikita Karnauhov

    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.

    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 <string>
#include <stdexcept>
#include <memory>
#include <iostream>
#include <sstream>
#include <iomanip>
#include <fstream>
#include <list>
#include <set>
#include <thread>
#include <mutex>
#include <unordered_map>

#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <fcntl.h>
#include <netinet/in.h>
#include <netdb.h>
#include <poll.h>
#include <arpa/inet.h>
#include <errno.h>
#include <string.h>
#include <getopt.h>
#include <signal.h>

#include <alsa/asoundlib.h>

#include <lansink.pb.h>
#include "log.h"
#include "alsa.h"
#include "player.h"
#include "settings.h"

// Signal flags.
static int g_nExitSignal = 0;

static
void _print_usage(std::ostream &_os) {
    static const Settings c_defaults;

    _os << "d, network audio receiver.\n" <<
            "Usage: " LANSINK_PROGRAM_NAME " [OPTION]...\n\n" <<
            "  -h, --help                       Print this message and exit.\n" <<
            "  -v, --version                    Display the version and exit.\n" <<
            "  -c, --config-path PATH           Set configuration file location.\n" <<
            "  -l, --log-path PATH              Set log file location (default: " << c_defaults.strLogPath << ").\n" <<
            "  -L, --log-level NUMBER           Set log verbosity level (default: " << c_defaults.nLogLevel << ", maximum: 4).\n" <<
            "  -d, --daemon                     Run as a daemon.\n" <<
            "  -n, --no-daemon                  Don't run as a daemon, display log messages (default).\n" <<
            "  -p, --pid-path PATH              PID file location (default: " << c_defaults.strPIDPath << ").\n" <<
            "  -H, --host NAME                  Server host.\n" <<
            "  -P, --port NUMBER                Server port (default: " << c_defaults.nPort << ").\n" <<
            "  -D, --alsa-device NAME           Output ALSA device.\n" <<
            "  -x, --exclusive                  Do not try to open device if it is already in use.\n" <<
            "      --no-exclusive               Try to open device for every new connection (default).\n" <<
            "      --recovery-timeout SECONDS   Seconds to wait after for stream data to reappear (default: " << c_defaults.nRecoveryTimeout << ").\n" <<
            "      --open-timeout MILLISECONDS  Milliseconds to wait between attempts to open device (default: " << c_defaults.nOpenTimeout << ").\n" <<
            "      --buffered-packets NUMBER    Number of packets preserved if failed to open device (default: " << c_defaults.nBufferedPackets << ").\n" <<
            "" << std::flush;
}

static
void _print_version() {
    std::cout << LANSINK_PROGRAM_NAME << ", network audio receiver.\n" <<
            "Version 0.1" << std::endl;
}

static
void _parse_options(int _nArgs, char *const _pArgs[]) {
    static struct option options[] = {
            {"help", 0, 0, 'h'},
            {"version", 0, 0, 'v'},
            {"config-path", required_argument, 0, 'c'},

            // These options have equivalents in settings file.
            {"log-path", required_argument, 0, 'l'},
            {"log-level", required_argument, 0, 'L'},
            {"daemon", 0, 0, 'd'},
            {"no-daemon", 0, 0, 'n'},
            {"pid-path", required_argument, 0, 'p'},
            {"host", required_argument, 0, 'H'},
            {"port", required_argument, 0, 'P'},
            {"alsa-device", required_argument, 0, 'D'},
            {"recovery-timeout", required_argument, 0, 500},
            {"open-timeout", required_argument, 0, 501},
            {"exclusive", 0, 0, 'x'},
            {"no-exclusive", 0, 0, 502},
            {0, 0, 0, 0}
    };

    const char *strOptions = "hvc:l:L:dnp:H:P:D:x";
    int nOption = 0;
    SettingsParser sp;
    std::map<std::string, std::string> kvs;
    std::unordered_map<int, std::string> longNames;

    for (auto &option : options)
        if (option.name)
            longNames[option.val] = option.name;

    // Handle --help, --version and --config-path options.
    while (true) {
        const int c = getopt_long(_nArgs, _pArgs, strOptions, options, &nOption);

        if (c == -1) {
            if (optind < _nArgs)
                throw RuntimeError("Redundant argument: %s", _pArgs[optind]);
            break;
        }

        switch (c) {
            case 'h':
                _print_usage(std::cout);
                exit(EXIT_SUCCESS);

            case 'v':
                _print_version();
                exit(EXIT_SUCCESS);

            case 'c':
                sp.parse_file(optarg);
                break;

            case 'd':
                kvs["daemon"] = "true";
                break;

            case 'n':
                kvs["daemon"] = "false";
                break;

            case 'x':
                kvs["exclusive"] = "true";
                break;

            case 502:
                kvs["exclusive"] = "false";
                break;

            case '?':
                _print_usage(std::cerr);
                exit(EXIT_FAILURE);

            default:
                if (!optarg)
                    throw LogicError("Option '%c' requires argument", c);

                kvs[longNames[c]] = optarg;
                break;
        }
    }

    for (auto &kv : kvs)
        sp.parse_option(kv.first, kv.second);

    g_settings = sp.get();

    if (g_settings.nLogLevel < llSilent || g_settings.nLogLevel > llDebug)
        throw RuntimeError("Invalid log level %d (must be between %d and %d)",
                g_settings.nLogLevel, llSilent, llDebug);
}

static
std::string _in_addr_to_string(struct sockaddr *_pAddr) {
    char buf[INET6_ADDRSTRLEN];
    void *pInAddr = nullptr;
    int nPort = 0;

    if (_pAddr->sa_family == AF_INET) {
        auto p = (struct sockaddr_in *)_pAddr;
        pInAddr = &p->sin_addr;
        nPort = p->sin_port;
    } else {
        auto p = (struct sockaddr_in6 *)_pAddr;
        pInAddr = &p->sin6_addr;
        nPort = p->sin6_port;
    }

    inet_ntop(_pAddr->sa_family, pInAddr, buf, sizeof(buf));

    return std::string(buf) + ":" + std::to_string(ntohs(nPort));
}

static
void _main(Log &_log) {
    std::string strPort = format(16, "%d", g_settings.nPort);
    struct addrinfo hints = {};
    struct addrinfo *pServerInfo = nullptr;

    hints.ai_family = AF_UNSPEC;
    hints.ai_socktype = SOCK_DGRAM;
    hints.ai_flags = AI_PASSIVE;

    if (getaddrinfo(g_settings.strHost.empty() ? NULL : g_settings.strHost.c_str(),
            strPort.c_str(), &hints, &pServerInfo) < 0)
        throw SystemError("getaddrinfo()");

    const int nSocket = socket(pServerInfo->ai_family,
            pServerInfo->ai_socktype, pServerInfo->ai_protocol);

    if (bind(nSocket, pServerInfo->ai_addr, pServerInfo->ai_addrlen) < 0)
        throw SystemError("bind()");

    _log.info("Server listening on %s",
            _in_addr_to_string(pServerInfo->ai_addr).c_str(), g_settings.nPort);

    freeaddrinfo(pServerInfo);

    struct pollfd fd{nSocket, POLLIN, 0};
    constexpr size_t cBufferSize = 1024*100; // Should be enough, right?
    auto pBuf = std::unique_ptr<char[]>(new char[cBufferSize]);
    std::list<lansink::Packet> packets;

    typedef std::chrono::steady_clock Clock;
    typedef std::chrono::duration<int, std::milli> Duration;
    typedef std::chrono::time_point<Clock> TimePoint;
    TimePoint m_lastOpenAttempt;

    do {
        try {
            if (poll(&fd, 1, 1000) < 0)
                throw SystemError("poll()");

            if (fd.revents & POLLIN) {
                struct sockaddr_storage sender = {};
                socklen_t cSendSize = sizeof(sender);

                bzero(&sender, sizeof(sender));

                const int nPacketSize = recvfrom(
                        fd.fd, pBuf.get(), cBufferSize, 0, (struct sockaddr *)&sender, &cSendSize);

                if (nPacketSize < 0)
                    throw SystemError("recvfrom()");

                lansink::Packet &packet = *packets.emplace(packets.end());

                if (!packet.ParseFromArray(pBuf.get(), nPacketSize)) {
                    _log.warning("Broken packet from %s",
                            _in_addr_to_string((struct sockaddr *)&sender).c_str());
                    continue;
                }

                if (packets.size() > (size_t)g_settings.nBufferedPackets)
                    packets.pop_front();

                if (m_lastOpenAttempt.time_since_epoch().count() > 0) {
                    // Last attempt at open() failed, check if timeout expired.
                    Duration ms(std::chrono::duration_cast<Duration>(Clock::now() - m_lastOpenAttempt));
                    if (ms.count() < g_settings.nOpenTimeout)
                        continue;
                }

                Player::TimePoint errorTime;
                Player *pPlayer = Player::get(packet, _log, errorTime);

                if (!pPlayer) {
                    _log.info("Dropping connection from %s: device is busy",
                            _in_addr_to_string((struct sockaddr *)&sender).c_str());
                    m_lastOpenAttempt = TimePoint(errorTime.time_since_epoch());
                    continue;
                }

                if (!pPlayer->is_prepared()) {
                    _log.info("New connection from %s",
                            _in_addr_to_string((struct sockaddr *)&sender).c_str());
                    pPlayer->init(packet);

                    if (pPlayer->is_prepared()) {
                        m_lastOpenAttempt = TimePoint();
                        pPlayer->run();
                    } else {
                        m_lastOpenAttempt = Clock::now();
                        continue;
                    }
                }

                for (auto iPacket = packets.begin(); iPacket != packets.end();)
                    if (iPacket->stream() == pPlayer->get_id()) {
                        pPlayer->play(*iPacket);
                        iPacket = packets.erase(iPacket);
                    } else
                        ++iPacket;
            }
        } catch (SystemError &se) {
            if (se.get_error() != EINTR)
                throw;
        }

        if (g_nExitSignal != 0)
            _log.info("Got signal %d, exiting gracefully.", g_nExitSignal);
    } while (g_nExitSignal == 0);
}

static
bool _write_pid(Log &_log) {
    // Specify empty PID path to disable writing PID.
    if (g_settings.strPIDPath.empty())
        return false;

    std::ofstream os(g_settings.strPIDPath);

    if (!os.good())
        throw RuntimeError("Cannot write PID file: %s", g_settings.strPIDPath.c_str());

    os << getpid() << std::endl;
    _log.info("PID file written to %s", g_settings.strPIDPath.c_str());

    return true;
}

static
void _handle_signal(int _nSignal, siginfo_t *_pSigInfo, void *_pContext) {
    g_nExitSignal = _nSignal;
}

static
void _init_signals() {
    struct sigaction action = {};
    const int exitSignals[]{
            SIGTERM, SIGINT, SIGPIPE, SIGALRM, SIGUSR1, SIGUSR2, SIGPOLL,
            SIGPROF, SIGVTALRM
    };

    action.sa_sigaction = &_handle_signal;
    action.sa_flags = SA_SIGINFO;

    for (int nSignal : exitSignals)
        if (sigaction(nSignal, &action, NULL) < 0)
            throw SystemError("sigaction()");

}

int main(int _nArgs, char *const _pArgs[]) {
    Log log("");
    bool bPIDWritten = false;

    log.setLevel(llError);

    try {
        _parse_options(_nArgs, _pArgs);
        log.setLevel(LogLevel(g_settings.nLogLevel));

        if (!g_settings.strLogPath.empty()) {
            log.open(g_settings.strLogPath);
            log.info("Logging to %s", g_settings.strLogPath.c_str());
        }

        if (g_settings.bDaemon) {
            log.close("");

            if (daemon(true, false) != 0)
                throw SystemError("daemon()");
        }

        _init_signals();
        bPIDWritten = _write_pid(log);
        _main(log);
    } catch (std::exception &e) {
        log.error(e.what());
        return EXIT_FAILURE;
    }

    if (bPIDWritten)
        unlink(g_settings.strPIDPath.c_str());

    return EXIT_SUCCESS;
}
<|endoftext|>


<commit_before>/*
* This file is part of the QPackageKit project
* Copyright (C) 2008 Adrien Bustany <madcat@mymadcat.com>
* Copyright (C) 2010-2011 Daniel Nicoletti <dantti85-pk@yahoo.com.br>
*
* 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 <QtSql>
#include <QDebug>

#include "daemon.h"
#include "daemonprivate.h"
#include "daemonproxy.h"

#include "common.h"

using namespace PackageKit;

Daemon* Daemon::m_global = 0;

Daemon* Daemon::global()
{
    if(!m_global) {
        m_global = new Daemon(qApp);
    }

    return m_global;
}

Daemon::Daemon(QObject *parent) :
    QObject(parent),
    d_ptr(new DaemonPrivate(this))
{
    Q_D(Daemon);
    d->daemon = new ::DaemonProxy(QLatin1String(PK_NAME),
                                  QLatin1String(PK_PATH),
                                  QDBusConnection::systemBus(),
                                  this);

    connect(d->daemon, SIGNAL(Changed()),
            this, SIGNAL(changed()));
    connect(d->daemon, SIGNAL(RepoListChanged()),
            this, SIGNAL(repoListChanged()));
    connect(d->daemon, SIGNAL(RestartSchedule()),
            this, SIGNAL(restartScheduled()));
    connect(d->daemon, SIGNAL(TransactionListChanged(QStringList)),
            this, SIGNAL(transactionListChanged(QStringList)));
    connect(d->daemon, SIGNAL(UpdatesChanged()),
            this, SIGNAL(updatesChanged()));

    // Set up database for desktop files
    QSqlDatabase db;
    db = QSqlDatabase::addDatabase("QSQLITE", PK_DESKTOP_DEFAULT_DATABASE);
    db.setDatabaseName(PK_DESKTOP_DEFAULT_DATABASE);
    if (!db.open()) {
        qDebug() << "Failed to initialize the desktop files database";
    }
}

Daemon::~Daemon()
{
}

Transaction::Roles Daemon::actions()
{
    Q_D(const Daemon);
    return d->daemon->roles();
}

QString Daemon::backendName()
{
    Q_D(const Daemon);
    return d->daemon->backendName();
}

QString Daemon::backendDescription()
{
    Q_D(const Daemon);
    return d->daemon->backendDescription();
}

QString Daemon::backendAuthor()
{
    Q_D(const Daemon);
    return d->daemon->backendAuthor();
}

Transaction::Filters Daemon::filters()
{
    Q_D(const Daemon);
    return static_cast<Transaction::Filters>(d->daemon->filters());
}

Transaction::Groups Daemon::groups()
{
    Q_D(const Daemon);
    return static_cast<Transaction::Groups>(d->daemon->groups());
}

bool Daemon::locked()
{
    Q_D(const Daemon);
    return d->daemon->locked();
}

QStringList Daemon::mimeTypes()
{
    Q_D(const Daemon);
    return d->daemon->mimeTypes();
}

Daemon::Network Daemon::networkState()
{
    Q_D(const Daemon);
    return static_cast<Daemon::Network>(d->daemon->networkState());
}

QString Daemon::distroID()
{
    Q_D(const Daemon);
    return d->daemon->distroId();
}

Daemon::Authorize Daemon::canAuthorize(const QString &actionId)
{
    Q_D(const Daemon);
    uint ret;
    ret = d->daemon->CanAuthorize(actionId);
    return static_cast<Daemon::Authorize>(ret);
}

QDBusObjectPath Daemon::getTid()
{
    Q_D(const Daemon);
    return d->daemon->CreateTransaction();
}

uint Daemon::getTimeSinceAction(Transaction::Role role)
{
    Q_D(const Daemon);
    return d->daemon->GetTimeSinceAction(role);
}

QList<QDBusObjectPath> Daemon::getTransactionList()
{
    Q_D(const Daemon);
    QDBusPendingReply<QList<QDBusObjectPath> > reply = d->daemon->GetTransactionList();
    if (reply.isValid()) {
        return reply.value();
    }
    return QList<QDBusObjectPath>();
}

QList<Transaction*> Daemon::getTransactionObjects(QObject *parent)
{
    Q_D(Daemon);
    return d->transactions(getTransactionList(), parent);
}

void Daemon::setHints(const QStringList &hints)
{
    Q_D(Daemon);
    d->hints = hints;
}

void Daemon::setHints(const QString &hints)
{
    Q_D(Daemon);
    d->hints = QStringList() << hints;
}

QStringList Daemon::hints()
{
    Q_D(const Daemon);
    return d->hints;
}

Transaction::InternalError Daemon::setProxy(const QString& http_proxy, const QString& https_proxy, const QString& ftp_proxy, const QString& socks_proxy, const QString& no_proxy, const QString& pac)
{
    Q_D(const Daemon);
    QDBusPendingReply<> r = d->daemon->SetProxy(http_proxy, https_proxy, ftp_proxy, socks_proxy, no_proxy, pac);
    r.waitForFinished();
    if (r.isError ()) {
        return Transaction::parseError(r.error().name());
    } else {
        return Transaction::InternalErrorNone;
    }
}

void Daemon::stateHasChanged(const QString& reason)
{
    Q_D(const Daemon);
    d->daemon->StateHasChanged(reason);
}

void Daemon::suggestDaemonQuit()
{
    Q_D(const Daemon);
    d->daemon->SuggestDaemonQuit();
}

uint Daemon::versionMajor()
{
    Q_D(const Daemon);
    return d->daemon->versionMajor();
}

uint Daemon::versionMinor()
{
    Q_D(const Daemon);
    return d->daemon->versionMinor();
}

uint Daemon::versionMicro()
{
    Q_D(const Daemon);
    return d->daemon->versionMicro();
}

QString Daemon::packageName(const QString &packageID)
{
    return packageID.section(QLatin1Char(';'), 0, 0);
}

QString Daemon::packageVersion(const QString &packageID)
{
    return packageID.section(QLatin1Char(';'), 1, 1);
}

QString Daemon::packageArch(const QString &packageID)
{
    return packageID.section(QLatin1Char(';'), 2, 2);
}

QString Daemon::packageData(const QString &packageID)
{
    return packageID.section(QLatin1Char(';'), 3, 3);
}

QString Daemon::packageIcon(const QString &packageID)
{
    return Transaction::packageIcon(packageID);
}

#include "daemon.moc"
<commit_msg>Try to fix a crash<commit_after>/*
* This file is part of the QPackageKit project
* Copyright (C) 2008 Adrien Bustany <madcat@mymadcat.com>
* Copyright (C) 2010-2011 Daniel Nicoletti <dantti85-pk@yahoo.com.br>
*
* 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 <QtSql>
#include <QDebug>

#include "daemon.h"
#include "daemonprivate.h"
#include "daemonproxy.h"

#include "common.h"

using namespace PackageKit;

Daemon* Daemon::m_global = 0;

Daemon* Daemon::global()
{
    if(!m_global) {
        m_global = new Daemon(qApp);
    }

    return m_global;
}

Daemon::Daemon(QObject *parent) :
    QObject(parent),
    d_ptr(new DaemonPrivate(this))
{
    Q_D(Daemon);
    d->daemon = new ::DaemonProxy(QLatin1String(PK_NAME),
                                  QLatin1String(PK_PATH),
                                  QDBusConnection::systemBus(),
                                  this);

    connect(d->daemon, SIGNAL(Changed()),
            this, SIGNAL(changed()));
    connect(d->daemon, SIGNAL(RepoListChanged()),
            this, SIGNAL(repoListChanged()));
    connect(d->daemon, SIGNAL(RestartSchedule()),
            this, SIGNAL(restartScheduled()));
    connect(d->daemon, SIGNAL(TransactionListChanged(QStringList)),
            this, SIGNAL(transactionListChanged(QStringList)));
    connect(d->daemon, SIGNAL(UpdatesChanged()),
            this, SIGNAL(updatesChanged()));

    // Set up database for desktop files
    QSqlDatabase db;
    db = QSqlDatabase::addDatabase("QSQLITE", PK_DESKTOP_DEFAULT_DATABASE);
    db.setDatabaseName(PK_DESKTOP_DEFAULT_DATABASE);
    if (!db.open()) {
        qDebug() << "Failed to initialize the desktop files database";
    }
}

Daemon::~Daemon()
{
}

Transaction::Roles Daemon::actions()
{
    Q_D(const Daemon);
    return d->daemon->roles();
}

QString Daemon::backendName()
{
    Q_D(const Daemon);
    return d->daemon->backendName();
}

QString Daemon::backendDescription()
{
    Q_D(const Daemon);
    return d->daemon->backendDescription();
}

QString Daemon::backendAuthor()
{
    Q_D(const Daemon);
    return d->daemon->backendAuthor();
}

Transaction::Filters Daemon::filters()
{
    Q_D(const Daemon);
    return static_cast<Transaction::Filters>(d->daemon->filters());
}

Transaction::Groups Daemon::groups()
{
    Q_D(const Daemon);
    return static_cast<Transaction::Groups>(d->daemon->groups());
}

bool Daemon::locked()
{
    Q_D(const Daemon);
    return d->daemon->locked();
}

QStringList Daemon::mimeTypes()
{
    Q_D(const Daemon);
    return d->daemon->mimeTypes();
}

Daemon::Network Daemon::networkState()
{
    Q_D(const Daemon);
    return static_cast<Daemon::Network>(d->daemon->networkState());
}

QString Daemon::distroID()
{
    Q_D(const Daemon);
    return d->daemon->distroId();
}

Daemon::Authorize Daemon::canAuthorize(const QString &actionId)
{
    Q_D(const Daemon);
    uint ret;
    ret = d->daemon->CanAuthorize(actionId);
    return static_cast<Daemon::Authorize>(ret);
}

QDBusObjectPath Daemon::getTid()
{
    Q_D(const Daemon);
    QDBusPendingReply<QDBusObjectPath> reply = d->daemon->CreateTransaction();
    reply.waitForFinished();
    if (reply.isValid()) {
        return reply.value();
    }
    return QDBusObjectPath();
}

uint Daemon::getTimeSinceAction(Transaction::Role role)
{
    Q_D(const Daemon);
    return d->daemon->GetTimeSinceAction(role);
}

QList<QDBusObjectPath> Daemon::getTransactionList()
{
    Q_D(const Daemon);
    QDBusPendingReply<QList<QDBusObjectPath> > reply = d->daemon->GetTransactionList();
    reply.waitForFinished();
    if (reply.isValid()) {
        return reply.value();
    }
    return QList<QDBusObjectPath>();
}

QList<Transaction*> Daemon::getTransactionObjects(QObject *parent)
{
    Q_D(Daemon);
    return d->transactions(getTransactionList(), parent);
}

void Daemon::setHints(const QStringList &hints)
{
    Q_D(Daemon);
    d->hints = hints;
}

void Daemon::setHints(const QString &hints)
{
    Q_D(Daemon);
    d->hints = QStringList() << hints;
}

QStringList Daemon::hints()
{
    Q_D(const Daemon);
    return d->hints;
}

Transaction::InternalError Daemon::setProxy(const QString& http_proxy, const QString& https_proxy, const QString& ftp_proxy, const QString& socks_proxy, const QString& no_proxy, const QString& pac)
{
    Q_D(const Daemon);
    QDBusPendingReply<> r = d->daemon->SetProxy(http_proxy, https_proxy, ftp_proxy, socks_proxy, no_proxy, pac);
    r.waitForFinished();
    if (r.isError ()) {
        return Transaction::parseError(r.error().name());
    } else {
        return Transaction::InternalErrorNone;
    }
}

void Daemon::stateHasChanged(const QString& reason)
{
    Q_D(const Daemon);
    d->daemon->StateHasChanged(reason);
}

void Daemon::suggestDaemonQuit()
{
    Q_D(const Daemon);
    d->daemon->SuggestDaemonQuit();
}

uint Daemon::versionMajor()
{
    Q_D(const Daemon);
    return d->daemon->versionMajor();
}

uint Daemon::versionMinor()
{
    Q_D(const Daemon);
    return d->daemon->versionMinor();
}

uint Daemon::versionMicro()
{
    Q_D(const Daemon);
    return d->daemon->versionMicro();
}

QString Daemon::packageName(const QString &packageID)
{
    return packageID.section(QLatin1Char(';'), 0, 0);
}

QString Daemon::packageVersion(const QString &packageID)
{
    return packageID.section(QLatin1Char(';'), 1, 1);
}

QString Daemon::packageArch(const QString &packageID)
{
    return packageID.section(QLatin1Char(';'), 2, 2);
}

QString Daemon::packageData(const QString &packageID)
{
    return packageID.section(QLatin1Char(';'), 3, 3);
}

QString Daemon::packageIcon(const QString &packageID)
{
    return Transaction::packageIcon(packageID);
}

#include "daemon.moc"
<|endoftext|>


<commit_before>/*
 *  This file is part of Poedit (http://www.poedit.net)
 *
 *  Copyright (C) 2000-2005 Vaclav Slavik
 *
 *  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 <wx/string.h>
#include <wx/config.h>
#include <wx/dir.h>
#include <wx/ffile.h>

#include "digger.h"
#include "parser.h"
#include "catalog.h"
#include "progressinfo.h"
#include "gexecute.h"

namespace
{

// concatenates catalogs using msgcat, returns name of newly created
// temporary file with the results
wxString ConcatCatalogs(const wxArrayString& files)
{
    wxString tempfile = wxGetTempFileName(_T("poedit"));

    wxString list;
    for ( wxArrayString::const_iterator i = files.begin();
          i != files.end(); ++i )
    {
        list += wxString::Format(_T(" \"%s\""), i->c_str());
    }

    wxString cmd =
        wxString::Format(_T("msgcat --force-po --use-first -o \"%s\" %s"),
                         tempfile.c_str(),
                         list.c_str());
    bool succ = ExecuteGettext(cmd);

    if ( !succ )
    {
        wxLogError(_("Failed command: %s"), cmd.c_str());
        wxLogError(_("Failed to merge gettext catalogs."));
        wxRemoveFile(tempfile);
        return wxEmptyString;
    }

    return tempfile;
}

void RemoveTempFiles(const wxArrayString& files)
{
    for ( wxArrayString::const_iterator i = files.begin();
          i != files.end(); ++i )
    {
        wxRemoveFile(*i);
    }
}

// fixes gettext header by replacing "CHARSET" with "iso-8859-1" -- msgcat
// doesn't like CHARSET value
bool FixCharsetInfo(const wxString& filename)
{
    wxFFile file;
    wxString data;

    if ( !file.Open(filename) ||
         !file.ReadAll(&data, wxConvISO8859_1) ||
         !file.Close() )
    {
        wxLogError(_("Failed to read extracted catalog."));
        return false;
    }

    data.Replace(_T("CHARSET"),
                 _T("iso-8859-1"));

    if ( !file.Open(filename, _T("w")) ||
         !file.Write(data, wxConvISO8859_1) ||
         !file.Close() )
    {
        wxLogError(_("Failed to read extracted catalog."));
        return false;
    }

    return true;
}

} // anonymous namespace

Catalog *SourceDigger::Dig(const wxArrayString& paths,
                           const wxArrayString& keywords,
                           const wxString& charset)
{
    ParsersDB pdb;
    pdb.Read(wxConfig::Get());

    m_progressInfo->UpdateMessage(_("Scanning files..."));

    wxArrayString *all_files = FindFiles(paths, pdb);
    if (all_files == NULL)
        return NULL;

    wxArrayString partials;

    for (size_t i = 0; i < pdb.GetCount(); i++)
    {
        if ( all_files[i].empty() )
            continue; // no files of this kind

        m_progressInfo->UpdateMessage(
            wxString::Format(_("Parsing %s files..."), pdb[i].Name.c_str()));
        if (!DigFiles(partials, all_files[i], pdb[i], keywords, charset))
        {
            delete[] all_files;
            RemoveTempFiles(partials);
            return NULL;
        }
    }

    delete[] all_files;

    if ( partials.empty() )
        return NULL; // couldn't parse any source files

    wxString mergedFile = ConcatCatalogs(partials);
    RemoveTempFiles(partials);

    if ( mergedFile.empty() )
        return NULL;

    Catalog *c = new Catalog(mergedFile);
    wxRemoveFile(mergedFile);

    if ( !c->IsOk() )
    {
        wxLogError(_("Failed to load extracted catalog."));
        delete c;
        return NULL;
    }

    c->CreateNewHeader();

    return c;
}

// cmdline's length is limited by OS/shell, this is maximal number
// of files we'll pass to the parser at one run:
#define BATCH_SIZE  16

bool SourceDigger::DigFiles(wxArrayString& outFiles,
                            const wxArrayString& files,
                            Parser &parser, const wxArrayString& keywords,
                            const wxString& charset)
{
    wxArrayString batchfiles;
    wxArrayString tempfiles;
    size_t i, last = 0;

    while (last < files.GetCount())
    {
        batchfiles.clear();
        for (i = last; i < last + BATCH_SIZE && i < files.size(); i++)
            batchfiles.Add(files[i]);
        last = i;

        wxString tempfile = wxGetTempFileName(_T("poedit"));
        if (!ExecuteGettext(
                    parser.GetCommand(batchfiles, keywords, tempfile, charset)))
        {
            return false;
        }

        if ( !FixCharsetInfo(tempfile) )
            return false;

        tempfiles.push_back(tempfile);

        m_progressInfo->UpdateGauge(batchfiles.GetCount());

        if (m_progressInfo->Cancelled())
            return false;
    }

    if ( tempfiles.empty() )
        return false; // failed to parse any source files

    wxString outfile = ConcatCatalogs(tempfiles);
    RemoveTempFiles(tempfiles);
    if ( outfile.empty() )
        return false;

    outFiles.push_back(outfile);
    return true;
}

wxArrayString *SourceDigger::FindFiles(const wxArrayString& paths,
                                       ParsersDB& pdb)
{
    if (pdb.GetCount() == 0) return NULL;
    wxArrayString *p_files = new wxArrayString[pdb.GetCount()];
    wxArrayString files;
    size_t i;

    for (i = 0; i < paths.GetCount(); i++)
        if (!FindInDir(paths[i], files))
    {
        delete[] p_files;
        return NULL;
    }

    size_t filescnt = 0;
    for (i = 0; i < pdb.GetCount(); i++)
    {
        p_files[i] = pdb[i].SelectParsable(files);
        filescnt += p_files[i].GetCount();
    }
    m_progressInfo->SetGaugeMax(filescnt);

    if (filescnt == 0)
    {
        for (i = 0; i < paths.GetCount(); i++)
            wxLogWarning(_("No files found in: ") + paths[i]);
        wxLogError(_("Poedit did not find any files in scanned directories."));
    }

    return p_files;
}

bool SourceDigger::FindInDir(const wxString& dirname, wxArrayString& files)
{
    wxDir dir(dirname);
    if (!dir.IsOpened())
        return false;
    bool cont;
    wxString filename;

    cont = dir.GetFirst(&filename, wxEmptyString, wxDIR_FILES);
    while (cont)
    {
        files.Add(dirname + _T("/") + filename);
        cont = dir.GetNext(&filename);
    }

    cont = dir.GetFirst(&filename, wxEmptyString, wxDIR_DIRS);
    while (cont)
    {
        if (!FindInDir(dirname + _T("/") + filename, files))
            return false;
        cont = dir.GetNext(&filename);
    }
    return true;
}

<commit_msg>Don't pass --use-first to msgcat.<commit_after>/*
 *  This file is part of Poedit (http://www.poedit.net)
 *
 *  Copyright (C) 2000-2005 Vaclav Slavik
 *
 *  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 <wx/string.h>
#include <wx/config.h>
#include <wx/dir.h>
#include <wx/ffile.h>

#include "digger.h"
#include "parser.h"
#include "catalog.h"
#include "progressinfo.h"
#include "gexecute.h"

namespace
{

// concatenates catalogs using msgcat, returns name of newly created
// temporary file with the results
wxString ConcatCatalogs(const wxArrayString& files)
{
    wxString tempfile = wxGetTempFileName(_T("poedit"));

    wxString list;
    for ( wxArrayString::const_iterator i = files.begin();
          i != files.end(); ++i )
    {
        list += wxString::Format(_T(" \"%s\""), i->c_str());
    }

    wxString cmd =
        wxString::Format(_T("msgcat --force-po -o \"%s\" %s"),
                         tempfile.c_str(),
                         list.c_str());
    bool succ = ExecuteGettext(cmd);

    if ( !succ )
    {
        wxLogError(_("Failed command: %s"), cmd.c_str());
        wxLogError(_("Failed to merge gettext catalogs."));
        wxRemoveFile(tempfile);
        return wxEmptyString;
    }

    return tempfile;
}

void RemoveTempFiles(const wxArrayString& files)
{
    for ( wxArrayString::const_iterator i = files.begin();
          i != files.end(); ++i )
    {
        wxRemoveFile(*i);
    }
}

// fixes gettext header by replacing "CHARSET" with "iso-8859-1" -- msgcat
// doesn't like CHARSET value
bool FixCharsetInfo(const wxString& filename)
{
    wxFFile file;
    wxString data;

    if ( !file.Open(filename) ||
         !file.ReadAll(&data, wxConvISO8859_1) ||
         !file.Close() )
    {
        wxLogError(_("Failed to read extracted catalog."));
        return false;
    }

    data.Replace(_T("CHARSET"),
                 _T("iso-8859-1"));

    if ( !file.Open(filename, _T("w")) ||
         !file.Write(data, wxConvISO8859_1) ||
         !file.Close() )
    {
        wxLogError(_("Failed to read extracted catalog."));
        return false;
    }

    return true;
}

} // anonymous namespace

Catalog *SourceDigger::Dig(const wxArrayString& paths,
                           const wxArrayString& keywords,
                           const wxString& charset)
{
    ParsersDB pdb;
    pdb.Read(wxConfig::Get());

    m_progressInfo->UpdateMessage(_("Scanning files..."));

    wxArrayString *all_files = FindFiles(paths, pdb);
    if (all_files == NULL)
        return NULL;

    wxArrayString partials;

    for (size_t i = 0; i < pdb.GetCount(); i++)
    {
        if ( all_files[i].empty() )
            continue; // no files of this kind

        m_progressInfo->UpdateMessage(
            wxString::Format(_("Parsing %s files..."), pdb[i].Name.c_str()));
        if (!DigFiles(partials, all_files[i], pdb[i], keywords, charset))
        {
            delete[] all_files;
            RemoveTempFiles(partials);
            return NULL;
        }
    }

    delete[] all_files;

    if ( partials.empty() )
        return NULL; // couldn't parse any source files

    wxString mergedFile = ConcatCatalogs(partials);
    RemoveTempFiles(partials);

    if ( mergedFile.empty() )
        return NULL;

    Catalog *c = new Catalog(mergedFile);
    wxRemoveFile(mergedFile);

    if ( !c->IsOk() )
    {
        wxLogError(_("Failed to load extracted catalog."));
        delete c;
        return NULL;
    }

    c->CreateNewHeader();

    return c;
}

// cmdline's length is limited by OS/shell, this is maximal number
// of files we'll pass to the parser at one run:
#define BATCH_SIZE  16

bool SourceDigger::DigFiles(wxArrayString& outFiles,
                            const wxArrayString& files,
                            Parser &parser, const wxArrayString& keywords,
                            const wxString& charset)
{
    wxArrayString batchfiles;
    wxArrayString tempfiles;
    size_t i, last = 0;

    while (last < files.GetCount())
    {
        batchfiles.clear();
        for (i = last; i < last + BATCH_SIZE && i < files.size(); i++)
            batchfiles.Add(files[i]);
        last = i;

        wxString tempfile = wxGetTempFileName(_T("poedit"));
        if (!ExecuteGettext(
                    parser.GetCommand(batchfiles, keywords, tempfile, charset)))
        {
            return false;
        }

        if ( !FixCharsetInfo(tempfile) )
            return false;

        tempfiles.push_back(tempfile);

        m_progressInfo->UpdateGauge(batchfiles.GetCount());

        if (m_progressInfo->Cancelled())
            return false;
    }

    if ( tempfiles.empty() )
        return false; // failed to parse any source files

    wxString outfile = ConcatCatalogs(tempfiles);
    RemoveTempFiles(tempfiles);
    if ( outfile.empty() )
        return false;

    outFiles.push_back(outfile);
    return true;
}

wxArrayString *SourceDigger::FindFiles(const wxArrayString& paths,
                                       ParsersDB& pdb)
{
    if (pdb.GetCount() == 0) return NULL;
    wxArrayString *p_files = new wxArrayString[pdb.GetCount()];
    wxArrayString files;
    size_t i;

    for (i = 0; i < paths.GetCount(); i++)
        if (!FindInDir(paths[i], files))
    {
        delete[] p_files;
        return NULL;
    }

    size_t filescnt = 0;
    for (i = 0; i < pdb.GetCount(); i++)
    {
        p_files[i] = pdb[i].SelectParsable(files);
        filescnt += p_files[i].GetCount();
    }
    m_progressInfo->SetGaugeMax(filescnt);

    if (filescnt == 0)
    {
        for (i = 0; i < paths.GetCount(); i++)
            wxLogWarning(_("No files found in: ") + paths[i]);
        wxLogError(_("Poedit did not find any files in scanned directories."));
    }

    return p_files;
}

bool SourceDigger::FindInDir(const wxString& dirname, wxArrayString& files)
{
    wxDir dir(dirname);
    if (!dir.IsOpened())
        return false;
    bool cont;
    wxString filename;

    cont = dir.GetFirst(&filename, wxEmptyString, wxDIR_FILES);
    while (cont)
    {
        files.Add(dirname + _T("/") + filename);
        cont = dir.GetNext(&filename);
    }

    cont = dir.GetFirst(&filename, wxEmptyString, wxDIR_DIRS);
    while (cont)
    {
        if (!FindInDir(dirname + _T("/") + filename, files))
            return false;
        cont = dir.GetNext(&filename);
    }
    return true;
}

<|endoftext|>