//  This file is part of par2cmdline (a PAR 2.0 compatible file verification and
//  repair tool). See http://parchive.sourceforge.net for details of PAR 2.0.
//
//  Copyright (c) 2003 Peter Brian Clements
//
//  par2cmdline 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.
//
//  par2cmdline 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

#include "par2cmdline.h"

#ifdef _MSC_VER
#ifdef _DEBUG
#undef THIS_FILE
static char THIS_FILE[]=__FILE__;
#define new DEBUG_NEW
#endif
#endif


#ifdef WIN32
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////

#define OffsetType __int64
#define MaxOffset 0x7fffffffffffffffI64
#define LengthType unsigned int
#define MaxLength 0xffffffffUL

DiskFile::DiskFile(void)
{
  filesize = 0;
  offset = 0;

  hFile = INVALID_HANDLE_VALUE;

  exists = false;
}

DiskFile::~DiskFile(void)
{
  if (hFile != INVALID_HANDLE_VALUE)
    ::CloseHandle(hFile);
}

// Open the file

bool DiskFile::Open(string _filename, u64 _filesize)
{
  assert(hFile == INVALID_HANDLE_VALUE);

  filename = _filename;
  filesize = _filesize;

  hFile = ::CreateFileA(_filename.c_str(), GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, NULL);
  if (hFile == INVALID_HANDLE_VALUE)
  {
    DWORD error = ::GetLastError();

    switch (error)
    {
    case ERROR_FILE_NOT_FOUND:
    case ERROR_PATH_NOT_FOUND:
      break;
    default:
      cerr << "Could not open \"" << _filename << "\": " << ErrorMessage(error) << endl;
    }

    return false;
  }

  offset = 0;
  exists = true;

  return true;
}

// Read some data from disk

bool DiskFile::Read(u64 _offset, void *buffer, size_t length)
{
  assert(hFile != INVALID_HANDLE_VALUE);

  if (offset != _offset)
  {
    LONG lowoffset = ((LONG*)&_offset)[0];
    LONG highoffset = ((LONG*)&_offset)[1];

    // Seek to the required offset
    if (INVALID_SET_FILE_POINTER == SetFilePointer(hFile, lowoffset, &highoffset, FILE_BEGIN))
    {
      DWORD error = ::GetLastError();

      cerr << "Could not read " << (u64)length << " bytes from \"" << filename << "\" at offset " << _offset << ": " << ErrorMessage(error) << endl;

      return false;
    }
    offset = _offset;
  }

  if (length > MaxLength)
  {
    cerr << "Could not read " << (u64)length << " bytes from \"" << filename << "\" at offset " << _offset << ": " << "Read too long" << endl;

    return false;
  }

  DWORD want = (LengthType)length;
  DWORD got;

  // Read the data
  if (!::ReadFile(hFile, buffer, want, &got, NULL))
  {
    DWORD error = ::GetLastError();

    cerr << "Could not read " << (u64)length << " bytes from \"" << filename << "\" at offset " << _offset << ": " << ErrorMessage(error) << endl;

    return false;
  }

  offset += length;

  return true;
}

void DiskFile::Close(void)
{
  if (hFile != INVALID_HANDLE_VALUE)
  {
    ::CloseHandle(hFile);
    hFile = INVALID_HANDLE_VALUE;
  }
}

string DiskFile::GetCanonicalPathname(string filename)
{
  char fullname[MAX_PATH];
  char *filepart;

  // Resolve a relative path to a full path
  int length = ::GetFullPathName(filename.c_str(), sizeof(fullname), fullname, &filepart);
  if (length <= 0 || sizeof(fullname) < length)
    return filename;

  // Make sure the drive letter is upper case.
  fullname[0] = toupper(fullname[0]);

  // Translate all /'s to \'s
  char *current = strchr(fullname, '/');
  while (current)
  {
    *current++ = '\\';
    current  = strchr(current, '/');
  }

  // Copy the root directory to the output string
  string longname(fullname, 3);

  // Start processing at the first path component
  current = &fullname[3];
  char *limit = &fullname[length];

  // Process until we reach the end of the full name
  while (current < limit)
  {
    char *tail;

    // Find the next \, or the end of the string
    (tail = strchr(current, '\\')) || (tail = limit);
    *tail = 0;

    // Create a wildcard to search for the path
    string wild = longname + current;
    WIN32_FIND_DATA finddata;
    HANDLE hFind = ::FindFirstFile(wild.c_str(), &finddata);
    if (hFind == INVALID_HANDLE_VALUE)
    {
      // If the component was not found then just copy the rest of the path to the
      // output buffer verbatim.
      longname += current;
      break;
    }
    ::FindClose(hFind);

    // Copy the component found to the output
    longname += finddata.cFileName;

    current = tail + 1;

    // If we have not reached the end of the name, add a "\"
    if (current < limit)
      longname += '\\';
  }

  return longname;
}

list<string>* DiskFile::FindFiles(string path, string wildcard)
{
  list<string> *matches = new list<string>;

  wildcard = path + wildcard;
  WIN32_FIND_DATA fd;
  HANDLE h = ::FindFirstFile(wildcard.c_str(), &fd);
  if (h != INVALID_HANDLE_VALUE)
  {
    do
    {
      if (0 == (fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY))
      {
        matches->push_back(path + fd.cFileName);
      }
    } while (::FindNextFile(h, &fd));
    ::FindClose(h);
  }

  return matches;
}




/////////////////////////////////////////////////////////////////////////////////////////////////////////////////
#else // !WIN32
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////

#define OffsetType long
#define MaxOffset 0x7fffffffL
#define LengthType unsigned int
#define MaxLength 0xffffffffUL

DiskFile::DiskFile(void)
{
  //filename;
  filesize = 0;
  offset = 0;

  file = 0;

  exists = false;
}

DiskFile::~DiskFile(void)
{
  if (file != 0)
    fclose(file);
}

// Open the file

bool DiskFile::Open(string _filename, u64 _filesize)
{
  assert(file == 0);

  filename = _filename;
  filesize = _filesize;

  if (_filesize > MaxOffset)
  {
    cerr << "File size for " << _filename << " is too large." << endl;
    return false;
  }

  file = fopen(filename.c_str(), "rb");
  if (file == 0)
  {
    return false;
  }

  offset = 0;
  exists = true;

  return true;
}

// Read some data from disk

bool DiskFile::Read(u64 _offset, void *buffer, size_t length)
{
  assert(file != 0);

  if (offset != _offset)
  {
    if (_offset > MaxOffset)
    {
      cerr << "Could not read " << (u64)length << " bytes from " << filename << " at offset " << _offset << endl;
      return false;
    }


    if (fseek(file, (OffsetType)_offset, SEEK_SET))
    {
      cerr << "Could not read " << (u64)length << " bytes from " << filename << " at offset " << _offset << endl;
      return false;
    }
    offset = _offset;
  }

  if (length > MaxLength)
  {
    cerr << "Could not read " << (u64)length << " bytes from " << filename << " at offset " << _offset << endl;
    return false;
  }

  if (1 != fread(buffer, (LengthType)length, 1, file))
  {
    cerr << "Could not read " << (u64)length << " bytes from " << filename << " at offset " << _offset << endl;
    return false;
  }

  offset += length;

  return true;
}

void DiskFile::Close(void)
{
  if (file != 0)
  {
    fclose(file);
    file = 0;
  }
}

// Attempt to get the full pathname of the file
string DiskFile::GetCanonicalPathname(string filename)
{
  // Is the supplied path already an absolute one
  if (filename.size() == 0 || filename[0] == '/')
    return filename;

  // Get the current directory
  char curdir[1000];
  if (0 == getcwd(curdir, sizeof(curdir)))
  {
    return filename;
  }


  // Allocate a work buffer and copy the resulting full path into it.
  char *work = new char[strlen(curdir) + filename.size() + 2];
  strcpy(work, curdir);
  if (work[strlen(work)-1] != '/')
    strcat(work, "/");
  strcat(work, filename.c_str());

  char *in = work;
  char *out = work;

  while (*in)
  {
    if (*in == '/')
    {
      if (in[1] == '.' && in[2] == '/')
      {
        // skip the input past /./
        in += 2;
      }
      else if (in[1] == '.' && in[2] == '.' && in[3] == '/')
      {
        // backtrack the output if /../ was found on the input
        in += 3;
        if (out > work)
        {
          do
          {
            out--;
          } while (out > work && *out != '/');
        }
      }
      else
      {
        *out++ = *in++;
      }
    }
    else
    {
      *out++ = *in++;
    }
  }
  *out = 0;

  string result = work;
  delete [] work;

  return result;
}

list<string>* DiskFile::FindFiles(string path, string wildcard)
{
  list<string> *matches = new list<string>;

  string::size_type where;

  if ((where = wildcard.find_first_of('*')) != string::npos ||
      (where = wildcard.find_first_of('?')) != string::npos)
  {
    string front = wildcard.substr(0, where);
    bool multiple = wildcard[where] == '*';
    string back = wildcard.substr(where+1);

    DIR *dirp = opendir(path.c_str());
    if (dirp != 0)
    {
      struct dirent *d;
      while ((d = readdir(dirp)) != 0)
      {
        string name = d->d_name;

        if (name == "." || name == "..")
          continue;

        if (multiple)
        {
          if (name.size() >= wildcard.size() &&
              name.substr(0, where) == front &&
              name.substr(name.size()-back.size()) == back)
          {
            matches->push_back(path + name);
          }
        }
        else
        {
          if (name.size() == wildcard.size())
          {
            string::const_iterator pw = wildcard.begin();
            string::const_iterator pn = name.begin();
            while (pw != wildcard.end())
            {
              if (*pw != '?' && *pw != *pn)
                break;
              ++pw;
              ++pn;
            }

            if (pw == wildcard.end())
            {
              matches->push_back(path + name);
            }
          }
        }

      }
      closedir(dirp);
    }
  }
  else
  {
    struct stat st;
    string fn = path + wildcard;
    if (stat(fn.c_str(), &st) == 0)
    {
      matches->push_back(path + wildcard);
    }
  }

  return matches;
}

/////////////////////////////////////////////////////////////////////////////////////////////////////////////////
#endif























bool DiskFile::Open(void)
{
  string _filename = filename;

  return Open(_filename);
}

bool DiskFile::Open(string _filename)
{
  return Open(_filename, GetFileSize(_filename));
}














//string DiskFile::GetPathFromFilename(string filename)
//{
//  string::size_type where;
//
//  if (string::npos != (where = filename.find_last_of('/')) ||
//      string::npos != (where = filename.find_last_of('\\')))
//  {
//    return filename.substr(0, where+1);
//  }
//  else
//  {
//    return "." PATHSEP;
//  }
//}

void DiskFile::SplitFilename(string filename, string &path, string &name)
{
  string::size_type where;

  if (string::npos != (where = filename.find_last_of('/')) ||
      string::npos != (where = filename.find_last_of('\\')))
  {
    path = filename.substr(0, where+1);
    name = filename.substr(where+1);
  }
  else
  {
    path = "." SPATHSEP;
    name = filename;
  }
}

bool DiskFile::FileExists(string filename)
{
  struct stat st;
  return ((0 == stat(filename.c_str(), &st)) && (0 != (st.st_mode & S_IFREG)));
}

u64 DiskFile::GetFileSize(string filename)
{
  struct stat st;
  if ((0 == stat(filename.c_str(), &st)) && (0 != (st.st_mode & S_IFREG)))
  {
    return st.st_size;
  }
  else
  {
    return 0;
  }
}



// Take a filename from a PAR2 file and replace any characters
// which would be illegal for a file on disk
string DiskFile::TranslateFilename(string filename)
{
  string result;

  string::iterator p = filename.begin();
  while (p != filename.end())
  {
    unsigned char ch = *p;

    bool ok = true;
#ifdef WIN32
    if (ch < 32)
    {
      ok = false;
    }
    else
    {
      switch (ch)
      {
      case '"':
      case '*':
      case '/':
      case ':':
      case '<':
      case '>':
      case '?':
      case '\\':
      case '|':
        ok = false;
      }
    }
#else
    if (ch < 32)
    {
      ok = false;
    }
    else
    {
      switch (ch)
      {
      case '/':
        ok = false;
      }
    }
#endif


    if (ok)
    {
      result += ch;
    }
    else
    {
      // convert problem characters to hex
      result += ((ch >> 4) < 10) ? (ch >> 4) + '0' : (ch >> 4) + 'A'-10;
      result += ((ch & 0xf) < 10) ? (ch & 0xf) + '0' : (ch & 0xf) + 'A'-10;
    }

    ++p;
  }

  return result;
}

#ifdef WIN32
string DiskFile::ErrorMessage(DWORD error)
{
  string result;

  LPVOID lpMsgBuf;
  if (::FormatMessageA(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS,
                       NULL,
                       error,
                       MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
                       (LPSTR)&lpMsgBuf,
                       0,
                       NULL))
  {
    result = (char*)lpMsgBuf;
    LocalFree(lpMsgBuf);
  }
  else
  {
    char message[40];
    _snprintf(message, sizeof(message), "Unknown error code (%d)", error);
    result = message;
  }

  return result;
}
#endif

DiskFileMap::DiskFileMap(void)
{
}

DiskFileMap::~DiskFileMap(void)
{
  map<string, DiskFile*>::iterator fi = diskfilemap.begin();
  while (fi != diskfilemap.end())
  {
    delete (*fi).second;

    ++fi;
  }
}

bool DiskFileMap::Insert(DiskFile *diskfile)
{
  string filename = diskfile->FileName();
  assert(filename.length() != 0);

  pair<map<string,DiskFile*>::const_iterator,bool> location = diskfilemap.insert(pair<string,DiskFile*>(filename, diskfile));

  return location.second;
}

void DiskFileMap::Remove(DiskFile *diskfile)
{
  string filename = diskfile->FileName();
  assert(filename.length() != 0);

  diskfilemap.erase(filename);
}

DiskFile* DiskFileMap::Find(string filename) const
{
  assert(filename.length() != 0);

  map<string, DiskFile*>::const_iterator f = diskfilemap.find(filename);

  return (f != diskfilemap.end()) ?  f->second : 0;
}


syntax highlighted by Code2HTML, v. 0.9.1