<commit_before>// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//  http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

#ifndef __STOUT_INTERNAL_WINDOWS_DIRENT_HPP__
#define __STOUT_INTERNAL_WINDOWS_DIRENT_HPP__

#include <assert.h>
#include <malloc.h>

#include <stout/windows.hpp>

// Abbreviated version of the POSIX `dirent` struct. cf. specification[1].
//
// [1] http://www.gnu.org/software/libc/manual/html_node/Directory-Entries.html
struct dirent
{
  char d_name[MAX_PATH];
  unsigned short d_namlen;
};

// `DIR` is normally an opaque struct in the standard, we expose the
// implementation here because this header is intended for internal use only.
struct DIR
{
  struct dirent curr;
  char *d_name;
  WIN32_FIND_DATA fd;
  HANDLE handle;
};

// Avoid the C++-style name-mangling linkage, and use C-style instead to give
// the appearance that this code is part of the real C standard library.
extern "C" {
namespace internal {

void free_dir(DIR* directory);

bool open_dir_stream(DIR* directory);

bool reentrant_advance_dir_stream(DIR* directory);

} // namespace internal {

// Windows implementation of POSIX standard `opendir`. cf. specification[1].
//
// [1] http://www.gnu.org/software/libc/manual/html_node/Opening-a-Directory.html#Opening-a-Directory
inline DIR* opendir(const char* path)
{
  if (path == nullptr) {
    errno = ENOTDIR;
    return nullptr;
  }

  const size_t path_size = strlen(path);

  if (path_size == 0 || path_size >= MAX_PATH) {
    errno = ENOENT;
    return nullptr;
  }

  const char windows_folder_separator = '\\';
  const char windows_drive_separator = ':';
  const char wildcard[] = "*";
  const char dir_separator_and_wildcard[] = "\\*";

  // Allocate space for directory. Be sure to leave room at the end of
  // `directory->d_name` for a directory separator and a wildcard.
  DIR* directory = (DIR*) malloc(sizeof(DIR));

  if (directory == nullptr) {
    errno = ENOMEM;
    return nullptr;
  }

  directory->d_name =
    (char*) malloc(path_size + strlen(dir_separator_and_wildcard) + 1);

  if (directory->d_name == nullptr) {
    errno = ENOMEM;
    free(directory);
    return nullptr;
  }

  // Copy path over and append the appropriate postfix.
  strcpy(directory->d_name, path);

  const size_t last_char_in_name =
    directory->d_name[strlen(directory->d_name) - 1];

  if (last_char_in_name != windows_folder_separator &&
      last_char_in_name != windows_drive_separator) {
    strcat(directory->d_name, dir_separator_and_wildcard);
  } else {
    strcat(directory->d_name, wildcard);
  }

  if (!internal::open_dir_stream(directory)) {
    internal::free_dir(directory);
    return nullptr;
  }

  return directory;
}

// Implementation of the standard POSIX function. See documentation[1].
//
// On success: returns a pointer to the next directory entry, or `nullptr` if
// we've reached the end of the stream.
//
// On failure: returns `nullptr` and sets `errno`.
//
// NOTE: as with most POSIX implementations of this function, you must reset
// `errno` before calling `readdir`.
//
// [1] http://www.gnu.org/software/libc/manual/html_node/Reading_002fClosing-Directory.html#Reading_002fClosing-Directory
inline struct dirent* readdir(DIR* directory)
{
  if (directory == nullptr) {
    errno = EBADF;
    return nullptr;
  }

  if (!internal::reentrant_advance_dir_stream(directory)) {
    return nullptr;
  }

  return &directory->curr;
}

// Implementation of the standard POSIX function. See documentation[1].
//
// On success, return 0; on failure, return -1 and set `errno` appropriately.
//
// [1] http://www.gnu.org/software/libc/manual/html_node/Reading_002fClosing-Directory.html#Reading_002fClosing-Directory
inline int closedir(DIR* directory)
{
  if (directory == nullptr) {
    errno = EBADF;
    return -1;
  }

  BOOL search_closed = false;

  if (directory->handle != INVALID_HANDLE_VALUE) {
    search_closed = FindClose(directory->handle);
  }

  internal::free_dir(directory);

  return search_closed ? 0 : -1;
}

namespace internal {

inline void free_dir(DIR* directory)
{
  if (directory != nullptr) {
    free(directory->d_name);
  }

  free(directory);
}

inline bool open_dir_stream(DIR* directory)
{
  assert(directory != nullptr);

  directory->handle = FindFirstFile(directory->d_name, &directory->fd);

  if (directory->handle == INVALID_HANDLE_VALUE) {
    errno = ENOENT;
    return false;
  }

  // NOTE: `d_name` can be a statically-sized array of `MAX_PATH` size because
  // `cFileName` is. See[1]. This simplifies this copy operation because we
  // don't have to `malloc`.
  //
  // [1] https://msdn.microsoft.com/en-us/library/windows/desktop/aa365740(v=vs.85).aspx
  strcpy(directory->curr.d_name, directory->fd.cFileName);
  directory->curr.d_namlen = strlen(directory->curr.d_name);

  return true;
}

inline bool reentrant_advance_dir_stream(DIR* directory)
{
  assert(directory != nullptr);

  if (!FindNextFile(directory->handle, &directory->fd)) {
    return false;
  }

  // NOTE: `d_name` can be a statically-sized array of `MAX_PATH` size because
  // `cFileName` is. See[1]. This simplifies this copy operation because we
  // don't have to `malloc`.
  //
  // [1] https://msdn.microsoft.com/en-us/library/windows/desktop/aa365740(v=vs.85).aspx
  strcpy(directory->curr.d_name, directory->fd.cFileName);
  directory->curr.d_namlen = strlen(directory->curr.d_name);

  return true;
}

} // namespace internal {
} // extern "C" {

#endif // __STOUT_INTERNAL_WINDOWS_DIRENT_HPP__
<commit_msg>Windows: Fixed implicit cast warning in dirent.hpp.<commit_after>// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//  http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

#ifndef __STOUT_INTERNAL_WINDOWS_DIRENT_HPP__
#define __STOUT_INTERNAL_WINDOWS_DIRENT_HPP__

#include <assert.h>
#include <malloc.h>

#include <stout/windows.hpp>

// Abbreviated version of the POSIX `dirent` struct. cf. specification[1].
//
// [1] http://www.gnu.org/software/libc/manual/html_node/Directory-Entries.html
struct dirent
{
  char d_name[MAX_PATH];
  unsigned short d_namlen;
};

// `DIR` is normally an opaque struct in the standard, we expose the
// implementation here because this header is intended for internal use only.
struct DIR
{
  struct dirent curr;
  char *d_name;
  WIN32_FIND_DATA fd;
  HANDLE handle;
};

// Avoid the C++-style name-mangling linkage, and use C-style instead to give
// the appearance that this code is part of the real C standard library.
extern "C" {
namespace internal {

void free_dir(DIR* directory);

bool open_dir_stream(DIR* directory);

bool reentrant_advance_dir_stream(DIR* directory);

} // namespace internal {

// Windows implementation of POSIX standard `opendir`. cf. specification[1].
//
// [1] http://www.gnu.org/software/libc/manual/html_node/Opening-a-Directory.html#Opening-a-Directory
inline DIR* opendir(const char* path)
{
  if (path == nullptr) {
    errno = ENOTDIR;
    return nullptr;
  }

  const size_t path_size = strlen(path);

  if (path_size == 0 || path_size >= MAX_PATH) {
    errno = ENOENT;
    return nullptr;
  }

  const char windows_folder_separator = '\\';
  const char windows_drive_separator = ':';
  const char wildcard[] = "*";
  const char dir_separator_and_wildcard[] = "\\*";

  // Allocate space for directory. Be sure to leave room at the end of
  // `directory->d_name` for a directory separator and a wildcard.
  DIR* directory = (DIR*) malloc(sizeof(DIR));

  if (directory == nullptr) {
    errno = ENOMEM;
    return nullptr;
  }

  directory->d_name =
    (char*) malloc(path_size + strlen(dir_separator_and_wildcard) + 1);

  if (directory->d_name == nullptr) {
    errno = ENOMEM;
    free(directory);
    return nullptr;
  }

  // Copy path over and append the appropriate postfix.
  strcpy(directory->d_name, path);

  const size_t last_char_in_name =
    directory->d_name[strlen(directory->d_name) - 1];

  if (last_char_in_name != windows_folder_separator &&
      last_char_in_name != windows_drive_separator) {
    strcat(directory->d_name, dir_separator_and_wildcard);
  } else {
    strcat(directory->d_name, wildcard);
  }

  if (!internal::open_dir_stream(directory)) {
    internal::free_dir(directory);
    return nullptr;
  }

  return directory;
}

// Implementation of the standard POSIX function. See documentation[1].
//
// On success: returns a pointer to the next directory entry, or `nullptr` if
// we've reached the end of the stream.
//
// On failure: returns `nullptr` and sets `errno`.
//
// NOTE: as with most POSIX implementations of this function, you must reset
// `errno` before calling `readdir`.
//
// [1] http://www.gnu.org/software/libc/manual/html_node/Reading_002fClosing-Directory.html#Reading_002fClosing-Directory
inline struct dirent* readdir(DIR* directory)
{
  if (directory == nullptr) {
    errno = EBADF;
    return nullptr;
  }

  if (!internal::reentrant_advance_dir_stream(directory)) {
    return nullptr;
  }

  return &directory->curr;
}

// Implementation of the standard POSIX function. See documentation[1].
//
// On success, return 0; on failure, return -1 and set `errno` appropriately.
//
// [1] http://www.gnu.org/software/libc/manual/html_node/Reading_002fClosing-Directory.html#Reading_002fClosing-Directory
inline int closedir(DIR* directory)
{
  if (directory == nullptr) {
    errno = EBADF;
    return -1;
  }

  BOOL search_closed = false;

  if (directory->handle != INVALID_HANDLE_VALUE) {
    search_closed = FindClose(directory->handle);
  }

  internal::free_dir(directory);

  return search_closed ? 0 : -1;
}

namespace internal {

inline void free_dir(DIR* directory)
{
  if (directory != nullptr) {
    free(directory->d_name);
  }

  free(directory);
}

inline bool open_dir_stream(DIR* directory)
{
  assert(directory != nullptr);

  directory->handle = FindFirstFile(directory->d_name, &directory->fd);

  if (directory->handle == INVALID_HANDLE_VALUE) {
    errno = ENOENT;
    return false;
  }

  // NOTE: `d_name` can be a statically-sized array of `MAX_PATH` size because
  // `cFileName` is. See[1]. This simplifies this copy operation because we
  // don't have to `malloc`.
  //
  // [1] https://msdn.microsoft.com/en-us/library/windows/desktop/aa365740(v=vs.85).aspx
  strcpy(directory->curr.d_name, directory->fd.cFileName);
  directory->curr.d_namlen =
    static_cast<unsigned short>(strlen(directory->curr.d_name));

  return true;
}

inline bool reentrant_advance_dir_stream(DIR* directory)
{
  assert(directory != nullptr);

  if (!FindNextFile(directory->handle, &directory->fd)) {
    return false;
  }

  // NOTE: `d_name` can be a statically-sized array of `MAX_PATH` size because
  // `cFileName` is. See[1]. This simplifies this copy operation because we
  // don't have to `malloc`.
  //
  // [1] https://msdn.microsoft.com/en-us/library/windows/desktop/aa365740(v=vs.85).aspx
  strcpy(directory->curr.d_name, directory->fd.cFileName);
  directory->curr.d_namlen =
    static_cast<unsigned short>(strlen(directory->curr.d_name));

  return true;
}

} // namespace internal {
} // extern "C" {

#endif // __STOUT_INTERNAL_WINDOWS_DIRENT_HPP__
<|endoftext|>


<commit_before>////////////////////////////////////////////////////////////////////////////////
//
// Copyright 2006 - 2015, Paul Beckingham, Federico Hernandez.
//
// 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.
//
// http://www.opensource.org/licenses/mit-license.php
//
////////////////////////////////////////////////////////////////////////////////

#include <cmake.h>
#include <algorithm>
#include <stdlib.h>
#include <Context.h>
#include <FS.h>
#include <test.h>

Context context;

int main (int argc, char** argv)
{
  UnitTest t (108);

  // Ensure environment has no influence.
  unsetenv ("TASKDATA");
  unsetenv ("TASKRC");

  // Path ();
  Path p0;
  t.is (p0._data, "", "Path::Path");

  // Path (const Path&);
  Path p1 = Path ("foo");
  t.is (p1._data, Directory::cwd () + "/foo", "Path::operator=");

  // Path (const std::string&);
  Path p2 ("~");
  t.ok (p2._data != "~", "~ expanded to " + p2._data);

  Path p3 ("/tmp");
  t.ok (p3._data == "/tmp", "/tmp -> /tmp");

  // Path& operator= (const Path&);
  Path p3_copy (p3);
  t.is (p3._data, p3_copy._data, "Path::Path (Path&)");

  // operator (std::string) const;
  t.is ((std::string) p3, "/tmp", "Path::operator (std::string) const");

  // std::string name () const;
  Path p4 ("/a/b/c/file.ext");
  t.is (p4.name (), "file.ext",  "/a/b/c/file.ext name is file.ext");

  // std::string parent () const;
  t.is (p4.parent (), "/a/b/c",  "/a/b/c/file.ext parent is /a/b/c");

  // std::string extension () const;
  t.is (p4.extension (), "ext",  "/a/b/c/file.ext extension is ext");

  // bool exists () const;
  t.ok (p2.exists (), "~ exists");
  t.ok (p3.exists (), "/tmp exists");

  // bool is_directory () const;
  t.ok (p2.is_directory (), "~ is_directory");
  t.ok (p3.is_directory (), "/tmp is_directory");

  // bool readable () const;
  t.ok (p2.readable (), "~ readable");
  t.ok (p3.readable (), "/tmp readable");

  // bool writable () const;
  t.ok (p2.writable (), "~ writable");
  t.ok (p3.writable (), "/tmp writable");

  // bool executable () const;
  t.ok (p2.executable (), "~ executable");
  t.ok (p3.executable (), "/tmp executable");

  // static std::string expand (const std::string&);
  t.ok (Path::expand ("~") != "~", "Path::expand ~ != ~");
  t.ok (Path::expand ("~/") != "~/", "Path::expand ~/ != ~/");

  // static std::vector <std::string> glob (const std::string&);
  std::vector <std::string> out = Path::glob ("/tmp");
  t.ok (out.size () == 1, "/tmp -> 1 result");
  t.is (out[0], "/tmp", "/tmp -> /tmp");

  out = Path::glob ("/t?p");
  t.ok (out.size () == 1, "/t?p -> 1 result");
  t.is (out[0], "/tmp", "/t?p -> /tmp");

  out = Path::glob ("/[s-u]mp");
  t.ok (out.size () == 1, "/[s-u]mp -> 1 result");
  t.is (out[0], "/tmp", "/[s-u]mp -> /tmp");

  // bool is_absolute () const;
  t.notok (p0.is_absolute (), "'' !is_absolute");
  t.ok    (p1.is_absolute (), "foo is_absolute");
  t.ok    (p2.is_absolute (), "~ is_absolute (after expansion)");
  t.ok    (p3.is_absolute (), "/tmp is_absolute");
  t.ok    (p4.is_absolute (), "/a/b/c/file.ext is_absolute");

  Directory tmp ("tmp");
  tmp.create ();
  t.ok (tmp.exists (), "tmp dir created.");

  File::write ("tmp/file.t.txt", "This is a test\n");
  File f6 ("tmp/file.t.txt");
  t.ok (f6.size () == 15, "File::size tmp/file.t.txt good");
  t.ok (f6.mode () & S_IRUSR, "File::mode tmp/file.t.txt good");
  t.ok (File::remove ("tmp/file.t.txt"), "File::remove tmp/file.t.txt good");

  // operator (std::string) const;
  t.is ((std::string) f6, Directory::cwd () + "/tmp/file.t.txt", "File::operator (std::string) const");

  t.ok (File::create ("tmp/file.t.create"), "File::create tmp/file.t.create good");
  t.ok (File::remove ("tmp/file.t.create"), "File::remove tmp/file.t.create good");

  // basename (std::string) const;
  t.is (f6.name (), "file.t.txt", "File::basename tmp/file.t.txt --> file.t.txt");

  // dirname (std::string) const;
  t.is (f6.parent (), Directory::cwd () + "/tmp", "File::dirname tmp/file.t.txt --> tmp");

  // bool rename (const std::string&);
  File f7 ("tmp/file.t.2.txt");
  f7.append ("something\n");
  f7.close ();

  t.ok (f7.rename ("tmp/file.t.3.txt"),  "File::rename did not fail");
  t.is (f7._data, Directory::cwd () + "/tmp/file.t.3.txt",    "File::rename stored new name");
  t.ok (f7.exists (),                    "File::rename new file exists");
  t.ok (f7.remove (),                    "File::remove tmp/file.t.3.txt good");
  t.notok (f7.exists (),                 "File::remove new file no longer exists");

  // Test permissions.
  File f8 ("tmp/file.t.perm.txt");
  f8.create (0744);
  t.ok (f8.exists (),                    "File::create perm file exists");
  mode_t m = f8.mode ();
  t.ok    (m & S_IFREG,                  "File::mode tmp/file.t.perm.txt S_IFREG good");
  t.ok    (m & S_IRUSR,                  "File::mode tmp/file.t.perm.txt r-------- good");
  t.ok    (m & S_IWUSR,                  "File::mode tmp/file.t.perm.txt -w------- good");
  t.ok    (m & S_IXUSR,                  "File::mode tmp/file.t.perm.txt --x------ good");
  t.ok    (m & S_IRGRP,                  "File::mode tmp/file.t.perm.txt ---r----- good");
  t.notok (m & S_IWGRP,                  "File::mode tmp/file.t.perm.txt ----w---- good");
  t.notok (m & S_IXGRP,                  "File::mode tmp/file.t.perm.txt -----x--- good");
  t.ok    (m & S_IROTH,                  "File::mode tmp/file.t.perm.txt ------r-- good");
  t.notok (m & S_IWOTH,                  "File::mode tmp/file.t.perm.txt -------w- good");
  t.notok (m & S_IXOTH,                  "File::mode tmp/file.t.perm.txt --------x good");
  f8.remove ();
  t.notok (f8.exists (),                 "File::remove perm file no longer exists");

  tmp.remove ();
  t.notok (tmp.exists (),                "tmp dir removed.");
  tmp.create ();
  t.ok (tmp.exists (), "tmp dir created.");

  // Directory (const File&);
  // Directory (const Path&);
  Directory d0 (Path ("tmp"));
  Directory d1 (File ("tmp"));
  Directory d2 (File (Path ("tmp")));
  t.is (d0._data, d1._data, "Directory(std::string) == Directory (File&)");
  t.is (d0._data, d2._data, "Directory(std::string) == Directory (File (Path &))");
  t.is (d1._data, d2._data, "Directory(File&)) == Directory (File (Path &))");

  // Directory (const Directory&);
  Directory d3 (d2);
  t.is (d3._data, Directory::cwd () + "/tmp", "Directory (Directory&)");

  // Directory (const std::string&);
  Directory d4 ("tmp/test_directory");

  // Directory& operator= (const Directory&);
  Directory d5 = d4;
  t.is (d5._data, Directory::cwd () + "/tmp/test_directory", "Directory::operator=");

  // operator (std::string) const;
  t.is ((std::string) d3, Directory::cwd () + "/tmp", "Directory::operator (std::string) const");

  // virtual bool create ();
  t.ok (d5.create (), "Directory::create tmp/test_directory");
  t.ok (d5.exists (), "Directory::exists tmp/test_directory");

  Directory d6 (d5._data + "/dir");
  t.ok (d6.create (), "Directory::create tmp/test_directory/dir");

  File::create (d5._data + "/f0");
  File::create (d6._data + "/f1");

  // std::vector <std::string> list ();
  std::vector <std::string> files = d5.list ();
  std::sort (files.begin (), files.end ());
  t.is ((int)files.size (), 2, "Directory::list 1 file");
  t.is (files[0], Directory::cwd () + "/tmp/test_directory/dir", "file[0] is tmp/test_directory/dir");
  t.is (files[1], Directory::cwd () + "/tmp/test_directory/f0", "file[1] is tmp/test_directory/f0");

  // std::vector <std::string> listRecursive ();
  files = d5.listRecursive ();
  std::sort (files.begin (), files.end ());
  t.is ((int)files.size (), 2, "Directory::list 1 file");
  t.is (files[0], Directory::cwd () + "/tmp/test_directory/dir/f1", "file is tmp/test_directory/dir/f1");
  t.is (files[1], Directory::cwd () + "/tmp/test_directory/f0", "file is tmp/test_directory/f0");

  // virtual bool remove ();
  t.ok (File::remove (d5._data + "/f0"), "File::remove tmp/test_directory/f0");
  t.ok (File::remove (d6._data + "/f1"), "File::remove tmp/test_directory/dir/f1");

  t.ok (d6.remove (), "Directory::remove tmp/test_directory/dir");
  t.notok (d6.exists (), "Directory::exists tmp/test_directory/dir - no");

  t.ok (d5.remove (), "Directory::remove tmp/test_directory");
  t.notok (d5.exists (), "Directory::exists tmp/test_directory - no");

  // bool remove (const std::string&);
  Directory d7 ("tmp/to_be_removed");
  t.ok (d7.create (), "Directory::create tmp/to_be_removed");
  File::create (d7._data + "/f0");
  Directory d8 (d7._data + "/another");
  t.ok (d8.create (), "Directory::create tmp/to_be_removed/another");
  File::create (d8._data + "/f1");
  t.ok (d7.remove (), "Directory::remove tmp/to_be_removed");
  t.notok (d7.exists (), "Directory tmp/to_be_removed gone");

  // static std::string cwd ();
  std::string cwd = Directory::cwd ();
  t.ok (cwd.length () > 0, "Directory::cwd returned a value");

  // bool parent (std::string&) const;
  Directory d9 ("/one/two/three/four.txt");
  t.ok (d9.up (),                   "parent /one/two/three/four.txt --> true");
  t.is (d9._data, "/one/two/three", "parent /one/two/three/four.txt --> /one/two/three");
  t.ok (d9.up (),                   "parent /one/two/three --> true");
  t.is (d9._data, "/one/two",       "parent /one/two/three --> /one/two");
  t.ok (d9.up (),                   "parent /one/two --> true");
  t.is (d9._data, "/one",           "parent /one/two --> /one");
  t.ok (d9.up (),                   "parent /one --> true");
  t.is (d9._data, "/",              "parent /one --> /");
  t.notok (d9.up (),                "parent / --> false");

  // Test permissions.
  umask (0022);
  Directory d10 ("tmp/dir.perm");
  d10.create (0750);
  t.ok (d10.exists (),               "Directory::create perm file exists");
  m = d10.mode ();
  t.ok    (m & S_IFDIR,             "Directory::mode tmp/dir.perm S_IFDIR good");
  t.ok    (m & S_IRUSR,             "Directory::mode tmp/dir.perm r-------- good");
  t.ok    (m & S_IWUSR,             "Directory::mode tmp/dir.perm -w------- good");
  t.ok    (m & S_IXUSR,             "Directory::mode tmp/dir.perm --x------ good");
  t.ok    (m & S_IRGRP,             "Directory::mode tmp/dir.perm ---r----- good");
  t.notok (m & S_IWGRP,             "Directory::mode tmp/dir.perm ----w---- good");
  t.ok    (m & S_IXGRP,             "Directory::mode tmp/dir.perm -----x--- good");
  t.notok (m & S_IROTH,             "Directory::mode tmp/dir.perm ------r-- good");
  t.notok (m & S_IWOTH,             "Directory::mode tmp/dir.perm -------w- good");
  t.notok (m & S_IXOTH,             "Directory::mode tmp/dir.perm --------x good");
  d10.remove ();
  t.notok (d10.exists (),           "Directory::remove temp/dir.perm file no longer exists");

  tmp.remove ();
  t.notok (tmp.exists (),           "tmp dir removed.");

  return 0;
}

////////////////////////////////////////////////////////////////////////////////
<commit_msg>Test: Added Path::is_link test<commit_after>////////////////////////////////////////////////////////////////////////////////
//
// Copyright 2006 - 2015, Paul Beckingham, Federico Hernandez.
//
// 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.
//
// http://www.opensource.org/licenses/mit-license.php
//
////////////////////////////////////////////////////////////////////////////////

#include <cmake.h>
#include <algorithm>
#include <stdlib.h>
#include <Context.h>
#include <FS.h>
#include <test.h>

Context context;

int main (int argc, char** argv)
{
  UnitTest t (110);

  // Ensure environment has no influence.
  unsetenv ("TASKDATA");
  unsetenv ("TASKRC");

  // Path ();
  Path p0;
  t.is (p0._data, "", "Path::Path");

  // Path (const Path&);
  Path p1 = Path ("foo");
  t.is (p1._data, Directory::cwd () + "/foo", "Path::operator=");

  // Path (const std::string&);
  Path p2 ("~");
  t.ok (p2._data != "~", "~ expanded to " + p2._data);

  Path p3 ("/tmp");
  t.ok (p3._data == "/tmp", "/tmp -> /tmp");

  // operator==
  t.notok (p2 == p3, "p2 != p3");

  // Path& operator= (const Path&);
  Path p3_copy (p3);
  t.is (p3._data, p3_copy._data, "Path::Path (Path&)");

  // operator (std::string) const;
  t.is ((std::string) p3, "/tmp", "Path::operator (std::string) const");

  // std::string name () const;
  Path p4 ("/a/b/c/file.ext");
  t.is (p4.name (), "file.ext",  "/a/b/c/file.ext name is file.ext");

  // std::string parent () const;
  t.is (p4.parent (), "/a/b/c",  "/a/b/c/file.ext parent is /a/b/c");

  // std::string extension () const;
  t.is (p4.extension (), "ext",  "/a/b/c/file.ext extension is ext");

  // bool exists () const;
  t.ok (p2.exists (), "~ exists");
  t.ok (p3.exists (), "/tmp exists");

  // bool is_directory () const;
  t.ok (p2.is_directory (), "~ is_directory");
  t.ok (p3.is_directory (), "/tmp is_directory");

  // bool is_link () const;
  t.notok (p2.is_link (), "~ !is_link");

  // bool readable () const;
  t.ok (p2.readable (), "~ readable");
  t.ok (p3.readable (), "/tmp readable");

  // bool writable () const;
  t.ok (p2.writable (), "~ writable");
  t.ok (p3.writable (), "/tmp writable");

  // bool executable () const;
  t.ok (p2.executable (), "~ executable");
  t.ok (p3.executable (), "/tmp executable");

  // static std::string expand (const std::string&);
  t.ok (Path::expand ("~") != "~", "Path::expand ~ != ~");
  t.ok (Path::expand ("~/") != "~/", "Path::expand ~/ != ~/");

  // static std::vector <std::string> glob (const std::string&);
  std::vector <std::string> out = Path::glob ("/tmp");
  t.ok (out.size () == 1, "/tmp -> 1 result");
  t.is (out[0], "/tmp", "/tmp -> /tmp");

  out = Path::glob ("/t?p");
  t.ok (out.size () == 1, "/t?p -> 1 result");
  t.is (out[0], "/tmp", "/t?p -> /tmp");

  out = Path::glob ("/[s-u]mp");
  t.ok (out.size () == 1, "/[s-u]mp -> 1 result");
  t.is (out[0], "/tmp", "/[s-u]mp -> /tmp");

  // bool is_absolute () const;
  t.notok (p0.is_absolute (), "'' !is_absolute");
  t.ok    (p1.is_absolute (), "foo is_absolute");
  t.ok    (p2.is_absolute (), "~ is_absolute (after expansion)");
  t.ok    (p3.is_absolute (), "/tmp is_absolute");
  t.ok    (p4.is_absolute (), "/a/b/c/file.ext is_absolute");

  Directory tmp ("tmp");
  tmp.create ();
  t.ok (tmp.exists (), "tmp dir created.");

  File::write ("tmp/file.t.txt", "This is a test\n");
  File f6 ("tmp/file.t.txt");
  t.ok (f6.size () == 15, "File::size tmp/file.t.txt good");
  t.ok (f6.mode () & S_IRUSR, "File::mode tmp/file.t.txt good");
  t.ok (File::remove ("tmp/file.t.txt"), "File::remove tmp/file.t.txt good");

  // operator (std::string) const;
  t.is ((std::string) f6, Directory::cwd () + "/tmp/file.t.txt", "File::operator (std::string) const");

  t.ok (File::create ("tmp/file.t.create"), "File::create tmp/file.t.create good");
  t.ok (File::remove ("tmp/file.t.create"), "File::remove tmp/file.t.create good");

  // basename (std::string) const;
  t.is (f6.name (), "file.t.txt", "File::basename tmp/file.t.txt --> file.t.txt");

  // dirname (std::string) const;
  t.is (f6.parent (), Directory::cwd () + "/tmp", "File::dirname tmp/file.t.txt --> tmp");

  // bool rename (const std::string&);
  File f7 ("tmp/file.t.2.txt");
  f7.append ("something\n");
  f7.close ();

  t.ok (f7.rename ("tmp/file.t.3.txt"),  "File::rename did not fail");
  t.is (f7._data, Directory::cwd () + "/tmp/file.t.3.txt",    "File::rename stored new name");
  t.ok (f7.exists (),                    "File::rename new file exists");
  t.ok (f7.remove (),                    "File::remove tmp/file.t.3.txt good");
  t.notok (f7.exists (),                 "File::remove new file no longer exists");

  // Test permissions.
  File f8 ("tmp/file.t.perm.txt");
  f8.create (0744);
  t.ok (f8.exists (),                    "File::create perm file exists");
  mode_t m = f8.mode ();
  t.ok    (m & S_IFREG,                  "File::mode tmp/file.t.perm.txt S_IFREG good");
  t.ok    (m & S_IRUSR,                  "File::mode tmp/file.t.perm.txt r-------- good");
  t.ok    (m & S_IWUSR,                  "File::mode tmp/file.t.perm.txt -w------- good");
  t.ok    (m & S_IXUSR,                  "File::mode tmp/file.t.perm.txt --x------ good");
  t.ok    (m & S_IRGRP,                  "File::mode tmp/file.t.perm.txt ---r----- good");
  t.notok (m & S_IWGRP,                  "File::mode tmp/file.t.perm.txt ----w---- good");
  t.notok (m & S_IXGRP,                  "File::mode tmp/file.t.perm.txt -----x--- good");
  t.ok    (m & S_IROTH,                  "File::mode tmp/file.t.perm.txt ------r-- good");
  t.notok (m & S_IWOTH,                  "File::mode tmp/file.t.perm.txt -------w- good");
  t.notok (m & S_IXOTH,                  "File::mode tmp/file.t.perm.txt --------x good");
  f8.remove ();
  t.notok (f8.exists (),                 "File::remove perm file no longer exists");

  tmp.remove ();
  t.notok (tmp.exists (),                "tmp dir removed.");
  tmp.create ();
  t.ok (tmp.exists (), "tmp dir created.");

  // Directory (const File&);
  // Directory (const Path&);
  Directory d0 (Path ("tmp"));
  Directory d1 (File ("tmp"));
  Directory d2 (File (Path ("tmp")));
  t.is (d0._data, d1._data, "Directory(std::string) == Directory (File&)");
  t.is (d0._data, d2._data, "Directory(std::string) == Directory (File (Path &))");
  t.is (d1._data, d2._data, "Directory(File&)) == Directory (File (Path &))");

  // Directory (const Directory&);
  Directory d3 (d2);
  t.is (d3._data, Directory::cwd () + "/tmp", "Directory (Directory&)");

  // Directory (const std::string&);
  Directory d4 ("tmp/test_directory");

  // Directory& operator= (const Directory&);
  Directory d5 = d4;
  t.is (d5._data, Directory::cwd () + "/tmp/test_directory", "Directory::operator=");

  // operator (std::string) const;
  t.is ((std::string) d3, Directory::cwd () + "/tmp", "Directory::operator (std::string) const");

  // virtual bool create ();
  t.ok (d5.create (), "Directory::create tmp/test_directory");
  t.ok (d5.exists (), "Directory::exists tmp/test_directory");

  Directory d6 (d5._data + "/dir");
  t.ok (d6.create (), "Directory::create tmp/test_directory/dir");

  File::create (d5._data + "/f0");
  File::create (d6._data + "/f1");

  // std::vector <std::string> list ();
  std::vector <std::string> files = d5.list ();
  std::sort (files.begin (), files.end ());
  t.is ((int)files.size (), 2, "Directory::list 1 file");
  t.is (files[0], Directory::cwd () + "/tmp/test_directory/dir", "file[0] is tmp/test_directory/dir");
  t.is (files[1], Directory::cwd () + "/tmp/test_directory/f0", "file[1] is tmp/test_directory/f0");

  // std::vector <std::string> listRecursive ();
  files = d5.listRecursive ();
  std::sort (files.begin (), files.end ());
  t.is ((int)files.size (), 2, "Directory::list 1 file");
  t.is (files[0], Directory::cwd () + "/tmp/test_directory/dir/f1", "file is tmp/test_directory/dir/f1");
  t.is (files[1], Directory::cwd () + "/tmp/test_directory/f0", "file is tmp/test_directory/f0");

  // virtual bool remove ();
  t.ok (File::remove (d5._data + "/f0"), "File::remove tmp/test_directory/f0");
  t.ok (File::remove (d6._data + "/f1"), "File::remove tmp/test_directory/dir/f1");

  t.ok (d6.remove (), "Directory::remove tmp/test_directory/dir");
  t.notok (d6.exists (), "Directory::exists tmp/test_directory/dir - no");

  t.ok (d5.remove (), "Directory::remove tmp/test_directory");
  t.notok (d5.exists (), "Directory::exists tmp/test_directory - no");

  // bool remove (const std::string&);
  Directory d7 ("tmp/to_be_removed");
  t.ok (d7.create (), "Directory::create tmp/to_be_removed");
  File::create (d7._data + "/f0");
  Directory d8 (d7._data + "/another");
  t.ok (d8.create (), "Directory::create tmp/to_be_removed/another");
  File::create (d8._data + "/f1");
  t.ok (d7.remove (), "Directory::remove tmp/to_be_removed");
  t.notok (d7.exists (), "Directory tmp/to_be_removed gone");

  // static std::string cwd ();
  std::string cwd = Directory::cwd ();
  t.ok (cwd.length () > 0, "Directory::cwd returned a value");

  // bool parent (std::string&) const;
  Directory d9 ("/one/two/three/four.txt");
  t.ok (d9.up (),                   "parent /one/two/three/four.txt --> true");
  t.is (d9._data, "/one/two/three", "parent /one/two/three/four.txt --> /one/two/three");
  t.ok (d9.up (),                   "parent /one/two/three --> true");
  t.is (d9._data, "/one/two",       "parent /one/two/three --> /one/two");
  t.ok (d9.up (),                   "parent /one/two --> true");
  t.is (d9._data, "/one",           "parent /one/two --> /one");
  t.ok (d9.up (),                   "parent /one --> true");
  t.is (d9._data, "/",              "parent /one --> /");
  t.notok (d9.up (),                "parent / --> false");

  // Test permissions.
  umask (0022);
  Directory d10 ("tmp/dir.perm");
  d10.create (0750);
  t.ok (d10.exists (),               "Directory::create perm file exists");
  m = d10.mode ();
  t.ok    (m & S_IFDIR,             "Directory::mode tmp/dir.perm S_IFDIR good");
  t.ok    (m & S_IRUSR,             "Directory::mode tmp/dir.perm r-------- good");
  t.ok    (m & S_IWUSR,             "Directory::mode tmp/dir.perm -w------- good");
  t.ok    (m & S_IXUSR,             "Directory::mode tmp/dir.perm --x------ good");
  t.ok    (m & S_IRGRP,             "Directory::mode tmp/dir.perm ---r----- good");
  t.notok (m & S_IWGRP,             "Directory::mode tmp/dir.perm ----w---- good");
  t.ok    (m & S_IXGRP,             "Directory::mode tmp/dir.perm -----x--- good");
  t.notok (m & S_IROTH,             "Directory::mode tmp/dir.perm ------r-- good");
  t.notok (m & S_IWOTH,             "Directory::mode tmp/dir.perm -------w- good");
  t.notok (m & S_IXOTH,             "Directory::mode tmp/dir.perm --------x good");
  d10.remove ();
  t.notok (d10.exists (),           "Directory::remove temp/dir.perm file no longer exists");

  tmp.remove ();
  t.notok (tmp.exists (),           "tmp dir removed.");

  return 0;
}

////////////////////////////////////////////////////////////////////////////////
<|endoftext|>


<commit_before>/*******************************************************************************
    Taichi - Physically based Computer Graphics Library

    Copyright (c) 2016 Yuanming Hu <yuanmhu@gmail.com>

    All rights reserved. Use of this source code is governed by
    the MIT license as written in the LICENSE file.
*******************************************************************************/

#include <taichi/visual/scene.h>
#include <taichi/visual/surface_material.h>

#define TINYOBJLOADER_IMPLEMENTATION
#include <tiny_obj_loader.h>

TC_NAMESPACE_BEGIN

void Mesh::initialize(const Config &config) {
  transform = Matrix4(1.0_f);
  std::string filepath = config.get_string("filename");
  if (!filepath.empty())
    load_from_file(filepath, config.get("reverse_vertices", false));
  else {
    TC_ERROR("File name can not be empty");
  }
}

void Mesh::load_from_file(const std::string &file_path,
                          bool reverse_vertices) {
  std::string inputfile = file_path;
  tinyobj::attrib_t attrib;
  std::vector<tinyobj::shape_t> shapes;
  std::vector<tinyobj::material_t> materials;

  std::string err;
  bool ret =
      tinyobj::LoadObj(&attrib, &shapes, &materials, &err, inputfile.c_str());

  if (!err.empty()) {  // `err` may contain warning message.
    std::cerr << err << std::endl;
  }

  TC_ASSERT_INFO(ret, "Loading " + file_path + " failed");

  // Loop over shapes
  for (size_t s = 0; s < shapes.size(); s++) {
    // Loop over faces(polygon)
    size_t index_offset = 0;
    for (size_t f = 0; f < shapes[s].mesh.num_face_vertices.size(); f++) {
      int fv = shapes[s].mesh.num_face_vertices[f];

      // Loop over vertices in the face.
      TC_ASSERT_INFO(fv == 3, "Only triangles supported...");
      int i = (int)vertices.size(), j = i + 1, k = i + 2;
      if (reverse_vertices) {
        std::swap(j, k);
      }
      bool has_normal = false;
      for (int v = 0; v < fv; v++) {
        // access to vertex
        tinyobj::index_t idx = shapes[s].mesh.indices[index_offset + v];
        float vx = attrib.vertices[3 * idx.vertex_index + 0];
        float vy = attrib.vertices[3 * idx.vertex_index + 1];
        float vz = attrib.vertices[3 * idx.vertex_index + 2];
        vertices.push_back(Vector3(vx, vy, vz));
        if (idx.normal_index != -1) {
          float nx = attrib.normals[3 * idx.normal_index + 0];
          float ny = attrib.normals[3 * idx.normal_index + 1];
          float nz = attrib.normals[3 * idx.normal_index + 2];
          has_normal = true;
          normals.push_back(Vector3(nx, ny, nz));
        } else {
          // should be consistent
          assert(!has_normal);
          has_normal = false;
        }
        float tx = 0.0_f, ty = 0.0_f;
        if (idx.texcoord_index != -1) {
          tx = attrib.texcoords[2 * idx.texcoord_index + 0];
          ty = attrib.texcoords[2 * idx.texcoord_index + 1];
        }
        uvs.push_back(Vector2(tx, ty));
      }
      if (!has_normal) {
        Vector3 *a = &vertices[vertices.size()] - 3;
        Vector3 generated_normal = cross(a[1] - a[0], a[2] - a[0]);
        if (length(generated_normal) > 1e-6f) {
          generated_normal = normalize(generated_normal);
        }
        for (int v = 0; v < fv; v++) {
          normals.push_back(generated_normal);
        }
      }
      untransformed_triangles.push_back(
          Triangle(vertices[i], vertices[j], vertices[k], normals[i],
                   normals[j], normals[k], uvs[i], uvs[j], uvs[k], i / 3));
      index_offset += fv;
    }
  }
}

void Mesh::set_material(std::shared_ptr<SurfaceMaterial> material) {
  this->material = material;
  if (material->is_emissive()) {
    this->emission_color =
        Vector3(material->get_importance(Vector2(0.5f, 0.5f)));  // TODO
    this->emission = luminance(this->emission_color);
  } else {
    this->emission = 0.0_f;
    this->emission_color = Vector3(0);
  }
}

IntersectionInfo Scene::get_intersection_info(int triangle_id, Ray &ray) {
  IntersectionInfo inter;
  if (triangle_id == -1) {
    return inter;
  }
  inter.intersected = true;
  Triangle &t = triangles[triangle_id];
  real coord_u = ray.u, coord_v = ray.v;
  inter.tri_coord.x = coord_u;
  inter.tri_coord.y = coord_u;
  inter.pos =
      t.v[0] + coord_u * (t.v[1] - t.v[0]) + coord_v * (t.v[2] - t.v[0]);
  inter.front = dot(ray.orig - t.v[0], t.normal) > 0;
  // Verify interpolated normals can lead specular rays to go inside the object.
  Vector3 normal = t.get_normal(coord_u, coord_v);
  Vector2 uv = t.get_uv(coord_u, coord_v);
  inter.uv = uv;
  inter.geometry_normal = inter.front ? t.normal : -t.normal;
  inter.normal = inter.front ? normal : -normal;
  // TODO: why unused?
  // Mesh *mesh = triangle_id_to_mesh[t.id];
  inter.triangle_id = triangle_id;
  inter.dist = ray.dist;
  // inter.material = mesh->material.get();
  Vector3 u = normalized(t.v[1] - t.v[0]);
  real sgn = inter.front ? 1.0_f : -1.0_f;
  Vector3 v = normalized(
      cross(sgn * inter.normal,
            u));  // Due to shading normal, we have to normalize here...
  inter.dt_du = t.get_duv(u);
  inter.dt_dv = t.get_duv(v);
  // TODO: ...
  u = normalized(cross(v, inter.normal));
  inter.to_world = Matrix3(u, v, inter.normal);
  inter.to_local = transposed(inter.to_world);
  return inter;
}

void Scene::add_mesh(std::shared_ptr<Mesh> mesh) {
  meshes.push_back(*mesh);
}

void Scene::finalize_geometry() {
  int triangle_count = 0;
  for (auto &mesh : meshes) {
    triangle_id_start[&mesh] = triangle_count;
    auto sub = mesh.get_triangles();
    for (int i = 0; i < (int)sub.size(); i++) {
      sub[i].id = triangle_count + i;
    }
    triangle_count += (int)sub.size();
    triangles.insert(triangles.end(), sub.begin(), sub.end());
    if (mesh.emission > 0) {
      emissive_triangles.insert(emissive_triangles.end(), sub.begin(),
                                sub.end());
    }
    for (auto &tri : sub) {
      triangle_id_to_mesh[tri.id] = &mesh;
    }
  }
  num_triangles = triangle_count;
  printf("Scene loaded. Triangle count: %d\n", triangle_count);
};

void Scene::finalize_lighting() {
  if (!emissive_triangles.empty()) {
    update_emission_cdf();
    update_light_emission_cdf();
  } else {
    envmap_sample_prob = 1.0_f;
    assert_info(envmap != nullptr, "There should be light sources.");
  }
}

void Scene::finalize() {
  finalize_geometry();
  finalize_lighting();
}

TC_NAMESPACE_END
<commit_msg>Fixed render<commit_after>/*******************************************************************************
    Taichi - Physically based Computer Graphics Library

    Copyright (c) 2016 Yuanming Hu <yuanmhu@gmail.com>

    All rights reserved. Use of this source code is governed by
    the MIT license as written in the LICENSE file.
*******************************************************************************/

#include <taichi/visual/scene.h>
#include <taichi/visual/surface_material.h>

#define TINYOBJLOADER_IMPLEMENTATION
#include <tiny_obj_loader.h>

TC_NAMESPACE_BEGIN

void Mesh::initialize(const Config &config) {
  transform = Matrix4(1.0_f);
  std::string filepath = config.get_string("filename");
  if (!filepath.empty())
    load_from_file(filepath, config.get("reverse_vertices", false));
}

void Mesh::load_from_file(const std::string &file_path,
                          bool reverse_vertices) {
  std::string inputfile = file_path;
  tinyobj::attrib_t attrib;
  std::vector<tinyobj::shape_t> shapes;
  std::vector<tinyobj::material_t> materials;

  std::string err;
  bool ret =
      tinyobj::LoadObj(&attrib, &shapes, &materials, &err, inputfile.c_str());

  if (!err.empty()) {  // `err` may contain warning message.
    std::cerr << err << std::endl;
  }

  TC_ASSERT_INFO(ret, "Loading " + file_path + " failed");

  // Loop over shapes
  for (size_t s = 0; s < shapes.size(); s++) {
    // Loop over faces(polygon)
    size_t index_offset = 0;
    for (size_t f = 0; f < shapes[s].mesh.num_face_vertices.size(); f++) {
      int fv = shapes[s].mesh.num_face_vertices[f];

      // Loop over vertices in the face.
      TC_ASSERT_INFO(fv == 3, "Only triangles supported...");
      int i = (int)vertices.size(), j = i + 1, k = i + 2;
      if (reverse_vertices) {
        std::swap(j, k);
      }
      bool has_normal = false;
      for (int v = 0; v < fv; v++) {
        // access to vertex
        tinyobj::index_t idx = shapes[s].mesh.indices[index_offset + v];
        float vx = attrib.vertices[3 * idx.vertex_index + 0];
        float vy = attrib.vertices[3 * idx.vertex_index + 1];
        float vz = attrib.vertices[3 * idx.vertex_index + 2];
        vertices.push_back(Vector3(vx, vy, vz));
        if (idx.normal_index != -1) {
          float nx = attrib.normals[3 * idx.normal_index + 0];
          float ny = attrib.normals[3 * idx.normal_index + 1];
          float nz = attrib.normals[3 * idx.normal_index + 2];
          has_normal = true;
          normals.push_back(Vector3(nx, ny, nz));
        } else {
          // should be consistent
          assert(!has_normal);
          has_normal = false;
        }
        float tx = 0.0_f, ty = 0.0_f;
        if (idx.texcoord_index != -1) {
          tx = attrib.texcoords[2 * idx.texcoord_index + 0];
          ty = attrib.texcoords[2 * idx.texcoord_index + 1];
        }
        uvs.push_back(Vector2(tx, ty));
      }
      if (!has_normal) {
        Vector3 *a = &vertices[vertices.size()] - 3;
        Vector3 generated_normal = cross(a[1] - a[0], a[2] - a[0]);
        if (length(generated_normal) > 1e-6f) {
          generated_normal = normalize(generated_normal);
        }
        for (int v = 0; v < fv; v++) {
          normals.push_back(generated_normal);
        }
      }
      untransformed_triangles.push_back(
          Triangle(vertices[i], vertices[j], vertices[k], normals[i],
                   normals[j], normals[k], uvs[i], uvs[j], uvs[k], i / 3));
      index_offset += fv;
    }
  }
}

void Mesh::set_material(std::shared_ptr<SurfaceMaterial> material) {
  this->material = material;
  if (material->is_emissive()) {
    this->emission_color =
        Vector3(material->get_importance(Vector2(0.5f, 0.5f)));  // TODO
    this->emission = luminance(this->emission_color);
  } else {
    this->emission = 0.0_f;
    this->emission_color = Vector3(0);
  }
}

IntersectionInfo Scene::get_intersection_info(int triangle_id, Ray &ray) {
  IntersectionInfo inter;
  if (triangle_id == -1) {
    return inter;
  }
  inter.intersected = true;
  Triangle &t = triangles[triangle_id];
  real coord_u = ray.u, coord_v = ray.v;
  inter.tri_coord.x = coord_u;
  inter.tri_coord.y = coord_u;
  inter.pos =
      t.v[0] + coord_u * (t.v[1] - t.v[0]) + coord_v * (t.v[2] - t.v[0]);
  inter.front = dot(ray.orig - t.v[0], t.normal) > 0;
  // Verify interpolated normals can lead specular rays to go inside the object.
  Vector3 normal = t.get_normal(coord_u, coord_v);
  Vector2 uv = t.get_uv(coord_u, coord_v);
  inter.uv = uv;
  inter.geometry_normal = inter.front ? t.normal : -t.normal;
  inter.normal = inter.front ? normal : -normal;
  // TODO: why unused?
  // Mesh *mesh = triangle_id_to_mesh[t.id];
  inter.triangle_id = triangle_id;
  inter.dist = ray.dist;
  // inter.material = mesh->material.get();
  Vector3 u = normalized(t.v[1] - t.v[0]);
  real sgn = inter.front ? 1.0_f : -1.0_f;
  Vector3 v = normalized(
      cross(sgn * inter.normal,
            u));  // Due to shading normal, we have to normalize here...
  inter.dt_du = t.get_duv(u);
  inter.dt_dv = t.get_duv(v);
  // TODO: ...
  u = normalized(cross(v, inter.normal));
  inter.to_world = Matrix3(u, v, inter.normal);
  inter.to_local = transposed(inter.to_world);
  return inter;
}

void Scene::add_mesh(std::shared_ptr<Mesh> mesh) {
  meshes.push_back(*mesh);
}

void Scene::finalize_geometry() {
  int triangle_count = 0;
  for (auto &mesh : meshes) {
    triangle_id_start[&mesh] = triangle_count;
    auto sub = mesh.get_triangles();
    for (int i = 0; i < (int)sub.size(); i++) {
      sub[i].id = triangle_count + i;
    }
    triangle_count += (int)sub.size();
    triangles.insert(triangles.end(), sub.begin(), sub.end());
    if (mesh.emission > 0) {
      emissive_triangles.insert(emissive_triangles.end(), sub.begin(),
                                sub.end());
    }
    for (auto &tri : sub) {
      triangle_id_to_mesh[tri.id] = &mesh;
    }
  }
  num_triangles = triangle_count;
  printf("Scene loaded. Triangle count: %d\n", triangle_count);
};

void Scene::finalize_lighting() {
  if (!emissive_triangles.empty()) {
    update_emission_cdf();
    update_light_emission_cdf();
  } else {
    envmap_sample_prob = 1.0_f;
    assert_info(envmap != nullptr, "There should be light sources.");
  }
}

void Scene::finalize() {
  finalize_geometry();
  finalize_lighting();
}

TC_NAMESPACE_END
<|endoftext|>


<commit_before>/*!
    \file hashlog.cpp
    \brief Hash logs reader definition
    \author Ivan Shynkarenka
    \date 13.12.2021
    \copyright MIT License
*/

#include "logging/record.h"
#include "logging/layouts/hash_layout.h"
#include "logging/layouts/text_layout.h"
#include "logging/version.h"

#include "../source/logging/appenders/minizip/unzip.h"
#if defined(_WIN32) || defined(_WIN64)
#include "../source/logging/appenders/minizip/iowin32.h"
#endif

#include "errors/fatal.h"
#include "filesystem/file.h"
#include "system/stream.h"
#include "utility/countof.h"
#include "utility/resource.h"

#include <cstdio>
#include <cstring>
#include <iostream>
#include <memory>
#include <unordered_map>
#include <vector>

#include <OptionParser.h>

using namespace CppCommon;
using namespace CppLogging;

Path FindHashlog(const Path& path)
{
    // Try to find .hashlog in the current path
    Path hashlog = path.IsRegularFile() ? path : (path / ".hashlog");
    if (hashlog.IsExists())
        return hashlog;

    // Try to find .hashlog in the parent path
    Path parent = path.parent();
    if (parent)
        return FindHashlog(parent);

    // Cannot find .hashlog file
    return Path();
}

std::unordered_map<uint32_t, std::string> ReadHashlog(const Path& path)
{
    std::unordered_map<uint32_t, std::string> hashmap;

    File hashlog(path);

    // Check if .hashlog is exists
    if (!hashlog.IsExists())
        return hashmap;

    // Open .hashlog file
    hashlog.Open(true, false);

    // Check if .hashlog is opened
    if (!hashlog)
        return hashmap;

    // Read the hash map size
    uint32_t size;
    if (hashlog.Read(&size, sizeof(uint32_t)) != sizeof(uint32_t))
        return hashmap;

    hashmap.reserve(size);

    // Read the hash map content
    while (size-- > 0)
    {
        uint32_t hash;
        if (hashlog.Read(&hash, sizeof(uint32_t)) != sizeof(uint32_t))
            return hashmap;
        uint16_t length;
        if (hashlog.Read(&length, sizeof(uint16_t)) != sizeof(uint16_t))
            return hashmap;
        std::vector<uint8_t> buffer(length);
        if (hashlog.Read(buffer.data(), length) != length)
            return hashmap;
        std::string message(buffer.begin(), buffer.end());
        hashmap[hash] = message;
    }

    // Close .hashlog file
    hashlog.Close();

    return hashmap;
}

bool WriteHashlog(const Path& path, const std::unordered_map<uint32_t, std::string>& hashmap)
{
    File hashlog(path);

    // Open or create .hashlog file
    hashlog.OpenOrCreate(false, true, true);

    // Check if .hashlog is avaliable
    if (!hashlog)
        return false;

    // Write the hash map size
    uint32_t size = (uint32_t)hashmap.size();
    if (hashlog.Write(&size, sizeof(uint32_t)) != sizeof(uint32_t))
        return false;

    // Read the hash map content
    for (const auto& item : hashmap)
    {
        uint32_t hash = (uint32_t)item.first;
        if (hashlog.Write(&hash, sizeof(uint32_t)) != sizeof(uint32_t))
            return false;
        uint16_t length = (uint16_t)item.second.size();
        if (hashlog.Write(&length, sizeof(uint16_t)) != sizeof(uint16_t))
            return false;
        auto buffer = item.second.data();
        if (hashlog.Write(buffer, length) != length)
            return false;
    }

    // Close .hashlog file
    hashlog.Close();

    return true;
}

Path UnzipFile(const Path& path)
{
    // Open a zip archive
    unzFile unzf;
#if defined(_WIN32) || defined(_WIN64)
    zlib_filefunc64_def ffunc;
    fill_win32_filefunc64W(&ffunc);
    unzf = unzOpen2_64(path.wstring().c_str(), &ffunc);
#else
    unzf = unzOpen64(path.string().c_str());
#endif
    if (unzf == nullptr)
        throwex FileSystemException("Cannot open a zip archive!").Attach(path);

    // Smart resource cleaner pattern
    auto unzip = resource(unzf, [](unzFile handle) { unzClose(handle); });

    File destination(path + ".tmp");

    // Open the destination file for writing
    destination.Create(false, true);

    // Get info about the zip archive
    unz_global_info global_info;
    int result = unzGetGlobalInfo(unzf, &global_info);
    if (result != UNZ_OK)
        throwex FileSystemException("Cannot read a zip archive global info!").Attach(path);

    // Loop to extract all files from the zip archive
    uLong i;
    for (i = 0; i < global_info.number_entry; ++i)
    {
        unz_file_info file_info;
        char filename[1024];

        // Get info about the current file in the zip archive
        result = unzGetCurrentFileInfo(unzf, &file_info, filename, (unsigned)countof(filename), NULL, 0, NULL, 0);
        if (result != UNZ_OK)
            throwex FileSystemException("Cannot read a zip archive file info!").Attach(path);

        // Check if this entry is a file
        const size_t filename_length = strlen(filename);
        if (filename[filename_length - 1] != '/')
        {
            // Open the current file in the zip archive
            result = unzOpenCurrentFile(unzf);
            if (result != UNZ_OK)
                throwex FileSystemException("Cannot open a current file in the zip archive!").Attach(path);

            // Smart resource cleaner pattern
            auto unzip_file = resource(unzf, [](unzFile handle) { unzCloseCurrentFile(handle); });

            // Read data from the current file in the zip archive
            do
            {
                uint8_t buffer[16384];
                result = unzReadCurrentFile(unzf, buffer, (unsigned)countof(buffer));
                if (result > 0)
                    destination.Write(buffer, (size_t)result);
                else if (result < 0)
                    throwex FileSystemException("Cannot read the current file from the zip archive!").Attach(path);
            } while (result != UNZ_EOF);

            // Close the current file in the zip archive
            result = unzCloseCurrentFile(unzf);
            if (result != UNZ_OK)
                throwex FileSystemException("Cannot close the current file in the zip archive!").Attach(path);
            unzip_file.release();
        }
    }

    // Close the destination file
    destination.Close();

    // Close the zip archive
    result = unzClose(unzf);
    if (result != UNZ_OK)
        throwex FileSystemException("Cannot close the zip archive!").Attach(path);
    unzip.release();

    return std::move(destination);
}

bool InputRecord(Reader& input, Record& record)
{
    // Clear the logging record
    record.Clear();

    // Read the logging record size
    uint32_t size;
    if (input.Read(&size, sizeof(uint32_t)) != sizeof(uint32_t))
        return false;

    record.raw.resize(size);

    // Read the logging record raw data
    if (input.Read(record.raw.data(), size) != size)
    {
        std::cerr << "Failed to read from the input source!" << std::endl;
        return false;
    }

    // Get the buffer start position
    const uint8_t* buffer = record.raw.data();

    // Deserialize logging record
    std::memcpy(&record.timestamp, buffer, sizeof(uint64_t));
    buffer += sizeof(uint64_t);
    std::memcpy(&record.thread, buffer, sizeof(uint64_t));
    buffer += sizeof(uint64_t);
    std::memcpy(&record.level, buffer, sizeof(Level));
    buffer += sizeof(Level);

    // Deserialize the logger name
    uint8_t logger_size;
    std::memcpy(&logger_size, buffer, sizeof(uint8_t));
    buffer += sizeof(uint8_t);
    record.logger.insert(record.logger.begin(), buffer, buffer + logger_size);
    buffer += logger_size;

    // Deserialize the logging message
    uint16_t message_size;
    std::memcpy(&message_size, buffer, sizeof(uint16_t));
    buffer += sizeof(uint16_t);
    record.message.insert(record.message.begin(), buffer, buffer + message_size);
    buffer += message_size;

    // Deserialize the logging buffer
    uint32_t buffer_size;
    std::memcpy(&buffer_size, buffer, sizeof(uint32_t));
    buffer += sizeof(uint32_t);
    record.buffer.insert(record.buffer.begin(), buffer, buffer + buffer_size);
    buffer += buffer_size;

    // Skip the last zero byte
    ++buffer;

    return true;
}

bool InputRecord(Reader& input, Record& record, const std::unordered_map<uint32_t, std::string>& hashmap)
{
    // Clear the logging record
    record.Clear();

    // Read the logging record size
    uint32_t size;
    if (input.Read(&size, sizeof(uint32_t)) != sizeof(uint32_t))
        return false;

    record.raw.resize(size);

    // Read the logging record raw data
    if (input.Read(record.raw.data(), size) != size)
    {
        std::cerr << "Failed to read from the input source!" << std::endl;
        return false;
    }

    // Get the buffer start position
    const uint8_t* buffer = record.raw.data();

    // Deserialize logging record
    std::memcpy(&record.timestamp, buffer, sizeof(uint64_t));
    buffer += sizeof(uint64_t);
    std::memcpy(&record.thread, buffer, sizeof(uint64_t));
    buffer += sizeof(uint64_t);
    std::memcpy(&record.level, buffer, sizeof(Level));
    buffer += sizeof(Level);

    // Deserialize the logger name
    uint32_t logger_hash;
    std::memcpy(&logger_hash, buffer, sizeof(uint32_t));
    buffer += sizeof(uint32_t);
    const auto& logger = hashmap.find(logger_hash);
    record.logger.assign((logger != hashmap.end()) ? logger->second : format("0x{:X}",  logger_hash));

    // Deserialize the logging message
    uint32_t message_hash;
    std::memcpy(&message_hash, buffer, sizeof(uint32_t));
    buffer += sizeof(uint32_t);
    const auto& message = hashmap.find(message_hash);
    record.message.assign((message != hashmap.end()) ? message->second : format("0x{:X}",  message_hash));

    // Deserialize the logging buffer
    uint32_t buffer_size;
    std::memcpy(&buffer_size, buffer, sizeof(uint32_t));
    buffer += sizeof(uint32_t);
    record.buffer.insert(record.buffer.begin(), buffer, buffer + buffer_size);
    buffer += buffer_size;

    // Skip the last zero byte
    ++buffer;

    return true;
}

bool OutputRecord(Writer& output, Record& record)
{
    TextLayout layout;
    layout.LayoutRecord(record);

    size_t size = record.raw.size() - 1;
    if (output.Write(record.raw.data(), size) != size)
    {
        std::cerr << "Failed to write into the output source!" << std::endl;
        return false;
    }

    return true;
}

bool UpdateHashmap(std::unordered_map<uint32_t, std::string>& hashmap, std::string_view message, uint32_t message_hash)
{
    if (hashmap.find(message_hash) == hashmap.end())
    {
        std::cout << format("Discovered logging message: \"{}\" with hash = 0x{:8X}", message, message_hash) << std::endl;
        hashmap[message_hash] = message;
        return true;
    }
    else if (message != hashmap[message_hash])
    {
        std::cerr << format("Collision detected!") << std::endl;
        std::cerr << format("Previous logging message: \"{}\" with hash = 0x{:8X}", hashmap[message_hash], message_hash) << std::endl;
        std::cerr << format("Conflict logging message: \"{}\" with hash = 0x{:8X}", message, message_hash) << std::endl;
        throwex Exception("Collision detected!");
    }

    // Skip duplicates
    return false;
}

bool UpdateHashmap(std::unordered_map<uint32_t, std::string>& hashmap, Record& record)
{
    bool result = false;

    // Check the logger name
    result |= UpdateHashmap(hashmap, record.logger, HashLayout::Hash(record.logger));
    // Check the logging message
    result |= UpdateHashmap(hashmap, record.message, HashLayout::Hash(record.message));

    return result;
}

int main(int argc, char** argv)
{
    auto parser = optparse::OptionParser().version(version);

    parser.add_option("-x", "--hashlog").dest("hashlog").help("Hashlog file name");
    parser.add_option("-i", "--input").dest("input").help("Input file name");
    parser.add_option("-o", "--output").dest("output").help("Output file name");
    parser.add_option("-u", "--update").dest("update").help("Update .hashlog");

    optparse::Values options = parser.parse_args(argc, argv);

    // Print help
    if (options.get("help"))
    {
        parser.print_help();
        return 0;
    }

    try
    {
        // Open the hashlog file
        Path hashlog = FindHashlog(Path::current());
        if (options.is_set("hashlog"))
        {
            hashlog = Path(options.get("hashlog"));
            if (hashlog.IsDirectory())
                hashlog = FindHashlog(hashlog);
        }

        // Read .hashlog file and fill the logging messages hash map
        std::unordered_map<uint32_t, std::string> hashmap = ReadHashlog(hashlog);

        Path temp_file;

        // Open the input file or stdin
        std::unique_ptr<Reader> input(new StdInput());
        if (options.is_set("input") || options.is_set("update"))
        {
            Path path(options.is_set("input") ? options.get("input") : options.get("update"));
            if (path.IsRegularFile() && (path.extension() == ".zip"))
                temp_file = path = UnzipFile(path);
            File* file = new File(path);
            file->Open(true, false);
            input.reset(file);
        }

        // Open the output file or stdout
        std::unique_ptr<Writer> output(new StdOutput());
        if (options.is_set("output"))
        {
            File* file = new File(Path(options.get("output")));
            file->Open(false, true);
            output.reset(file);
        }

        if (options.is_set("update"))
        {
            bool store = false;

            // Update hashmap with data from all logging records
            Record record;
            while (InputRecord(*input, record))
                store |= UpdateHashmap(hashmap, record);

            // Store updated .hashlog
            if (store)
                WriteHashlog(hashlog, hashmap);
        }
        else
        {
            // Process all logging records
            Record record;
            while (InputRecord(*input, record, hashmap))
                if (!OutputRecord(*output, record))
                    break;
        }

        // Delete temporary file
        if (temp_file)
            Path::Remove(temp_file);

        return 0;
    }
    catch (const std::exception& ex)
    {
        std::cerr << ex.what() << std::endl;
        return -1;
    }
}
<commit_msg>Bugfixing<commit_after>/*!
    \file hashlog.cpp
    \brief Hash logs reader definition
    \author Ivan Shynkarenka
    \date 13.12.2021
    \copyright MIT License
*/

#include "logging/record.h"
#include "logging/layouts/hash_layout.h"
#include "logging/layouts/text_layout.h"
#include "logging/version.h"

#include "../source/logging/appenders/minizip/unzip.h"
#if defined(_WIN32) || defined(_WIN64)
#include "../source/logging/appenders/minizip/iowin32.h"
#endif

#include "errors/fatal.h"
#include "filesystem/file.h"
#include "system/stream.h"
#include "utility/countof.h"
#include "utility/resource.h"

#include <cstdio>
#include <cstring>
#include <iostream>
#include <memory>
#include <unordered_map>
#include <vector>

#include <OptionParser.h>

using namespace CppCommon;
using namespace CppLogging;

Path FindHashlog(const Path& path)
{
    // Try to find .hashlog in the current path
    Path hashlog = path.IsRegularFile() ? path : (path / ".hashlog");
    if (hashlog.IsExists())
        return hashlog;

    // Try to find .hashlog in the parent path
    Path parent = path.parent();
    if (parent)
        return FindHashlog(parent);

    // Cannot find .hashlog file
    return Path();
}

std::unordered_map<uint32_t, std::string> ReadHashlog(const Path& path)
{
    std::unordered_map<uint32_t, std::string> hashmap;

    File hashlog(path);

    // Check if .hashlog is exists
    if (!hashlog.IsExists())
        return hashmap;

    // Open .hashlog file
    hashlog.Open(true, false);

    // Check if .hashlog is opened
    if (!hashlog)
        return hashmap;

    // Read the hash map size
    uint32_t size;
    if (hashlog.Read(&size, sizeof(uint32_t)) != sizeof(uint32_t))
        return hashmap;

    hashmap.reserve(size);

    // Read the hash map content
    while (size-- > 0)
    {
        uint32_t hash;
        if (hashlog.Read(&hash, sizeof(uint32_t)) != sizeof(uint32_t))
            return hashmap;
        uint16_t length;
        if (hashlog.Read(&length, sizeof(uint16_t)) != sizeof(uint16_t))
            return hashmap;
        std::vector<uint8_t> buffer(length);
        if (hashlog.Read(buffer.data(), length) != length)
            return hashmap;
        std::string message(buffer.begin(), buffer.end());
        hashmap[hash] = message;
    }

    // Close .hashlog file
    hashlog.Close();

    return hashmap;
}

bool WriteHashlog(const Path& path, const std::unordered_map<uint32_t, std::string>& hashmap)
{
    File hashlog(path);

    // Open or create .hashlog file
    hashlog.OpenOrCreate(false, true, true);

    // Check if .hashlog is avaliable
    if (!hashlog)
        return false;

    // Write the hash map size
    uint32_t size = (uint32_t)hashmap.size();
    if (hashlog.Write(&size, sizeof(uint32_t)) != sizeof(uint32_t))
        return false;

    // Read the hash map content
    for (const auto& item : hashmap)
    {
        uint32_t hash = (uint32_t)item.first;
        if (hashlog.Write(&hash, sizeof(uint32_t)) != sizeof(uint32_t))
            return false;
        uint16_t length = (uint16_t)item.second.size();
        if (hashlog.Write(&length, sizeof(uint16_t)) != sizeof(uint16_t))
            return false;
        auto buffer = item.second.data();
        if (hashlog.Write(buffer, length) != length)
            return false;
    }

    // Close .hashlog file
    hashlog.Close();

    return true;
}

Path UnzipFile(const Path& path)
{
    // Open a zip archive
    unzFile unzf;
#if defined(_WIN32) || defined(_WIN64)
    zlib_filefunc64_def ffunc;
    fill_win32_filefunc64W(&ffunc);
    unzf = unzOpen2_64(path.wstring().c_str(), &ffunc);
#else
    unzf = unzOpen64(path.string().c_str());
#endif
    if (unzf == nullptr)
        throwex FileSystemException("Cannot open a zip archive!").Attach(path);

    // Smart resource cleaner pattern
    auto unzip = resource(unzf, [](unzFile handle) { unzClose(handle); });

    File destination(path + ".tmp");

    // Open the destination file for writing
    destination.Create(false, true);

    // Get info about the zip archive
    unz_global_info global_info;
    int result = unzGetGlobalInfo(unzf, &global_info);
    if (result != UNZ_OK)
        throwex FileSystemException("Cannot read a zip archive global info!").Attach(path);

    // Loop to extract all files from the zip archive
    uLong i;
    for (i = 0; i < global_info.number_entry; ++i)
    {
        unz_file_info file_info;
        char filename[1024];

        // Get info about the current file in the zip archive
        result = unzGetCurrentFileInfo(unzf, &file_info, filename, (unsigned)countof(filename), NULL, 0, NULL, 0);
        if (result != UNZ_OK)
            throwex FileSystemException("Cannot read a zip archive file info!").Attach(path);

        // Check if this entry is a file
        const size_t filename_length = strlen(filename);
        if (filename[filename_length - 1] != '/')
        {
            // Open the current file in the zip archive
            result = unzOpenCurrentFile(unzf);
            if (result != UNZ_OK)
                throwex FileSystemException("Cannot open a current file in the zip archive!").Attach(path);

            // Smart resource cleaner pattern
            auto unzip_file = resource(unzf, [](unzFile handle) { unzCloseCurrentFile(handle); });

            // Read data from the current file in the zip archive
            do
            {
                uint8_t buffer[16384];
                result = unzReadCurrentFile(unzf, buffer, (unsigned)countof(buffer));
                if (result > 0)
                    destination.Write(buffer, (size_t)result);
                else if (result < 0)
                    throwex FileSystemException("Cannot read the current file from the zip archive!").Attach(path);
            } while (result != UNZ_EOF);

            // Close the current file in the zip archive
            result = unzCloseCurrentFile(unzf);
            if (result != UNZ_OK)
                throwex FileSystemException("Cannot close the current file in the zip archive!").Attach(path);
            unzip_file.release();
        }
    }

    // Close the destination file
    destination.Close();

    // Close the zip archive
    result = unzClose(unzf);
    if (result != UNZ_OK)
        throwex FileSystemException("Cannot close the zip archive!").Attach(path);
    unzip.release();

    return std::move(destination);
}

bool InputRecord(Reader& input, Record& record)
{
    // Clear the logging record
    record.Clear();

    // Read the logging record size
    uint32_t size;
    if (input.Read(&size, sizeof(uint32_t)) != sizeof(uint32_t))
        return false;

    record.raw.resize(size);

    // Read the logging record raw data
    if (input.Read(record.raw.data(), size) != size)
    {
        std::cerr << "Failed to read from the input source!" << std::endl;
        return false;
    }

    // Get the buffer start position
    const uint8_t* buffer = record.raw.data();

    // Deserialize logging record
    std::memcpy(&record.timestamp, buffer, sizeof(uint64_t));
    buffer += sizeof(uint64_t);
    std::memcpy(&record.thread, buffer, sizeof(uint64_t));
    buffer += sizeof(uint64_t);
    std::memcpy(&record.level, buffer, sizeof(Level));
    buffer += sizeof(Level);

    // Deserialize the logger name
    uint8_t logger_size;
    std::memcpy(&logger_size, buffer, sizeof(uint8_t));
    buffer += sizeof(uint8_t);
    record.logger.insert(record.logger.begin(), buffer, buffer + logger_size);
    buffer += logger_size;

    // Deserialize the logging message
    uint16_t message_size;
    std::memcpy(&message_size, buffer, sizeof(uint16_t));
    buffer += sizeof(uint16_t);
    record.message.insert(record.message.begin(), buffer, buffer + message_size);
    buffer += message_size;

    // Deserialize the logging buffer
    uint32_t buffer_size;
    std::memcpy(&buffer_size, buffer, sizeof(uint32_t));
    buffer += sizeof(uint32_t);
    record.buffer.insert(record.buffer.begin(), buffer, buffer + buffer_size);
    buffer += buffer_size;

    // Skip the last zero byte
    ++buffer;

    return true;
}

bool InputRecord(Reader& input, Record& record, const std::unordered_map<uint32_t, std::string>& hashmap)
{
    // Clear the logging record
    record.Clear();

    // Read the logging record size
    uint32_t size;
    if (input.Read(&size, sizeof(uint32_t)) != sizeof(uint32_t))
        return false;

    record.raw.resize(size);

    // Read the logging record raw data
    if (input.Read(record.raw.data(), size) != size)
    {
        std::cerr << "Failed to read from the input source!" << std::endl;
        return false;
    }

    // Get the buffer start position
    const uint8_t* buffer = record.raw.data();

    // Deserialize logging record
    std::memcpy(&record.timestamp, buffer, sizeof(uint64_t));
    buffer += sizeof(uint64_t);
    std::memcpy(&record.thread, buffer, sizeof(uint64_t));
    buffer += sizeof(uint64_t);
    std::memcpy(&record.level, buffer, sizeof(Level));
    buffer += sizeof(Level);

    // Deserialize the logger name
    uint32_t logger_hash;
    std::memcpy(&logger_hash, buffer, sizeof(uint32_t));
    buffer += sizeof(uint32_t);
    const auto& logger = hashmap.find(logger_hash);
    record.logger.assign((logger != hashmap.end()) ? logger->second : format("0x{:X}",  logger_hash));

    // Deserialize the logging message
    uint32_t message_hash;
    std::memcpy(&message_hash, buffer, sizeof(uint32_t));
    buffer += sizeof(uint32_t);
    const auto& message = hashmap.find(message_hash);
    record.message.assign((message != hashmap.end()) ? message->second : format("0x{:X}",  message_hash));

    // Deserialize the logging buffer
    uint32_t buffer_size;
    std::memcpy(&buffer_size, buffer, sizeof(uint32_t));
    buffer += sizeof(uint32_t);
    record.buffer.insert(record.buffer.begin(), buffer, buffer + buffer_size);
    buffer += buffer_size;

    // Skip the last zero byte
    ++buffer;

    return true;
}

bool OutputRecord(Writer& output, Record& record)
{
    TextLayout layout;
    layout.LayoutRecord(record);

    size_t size = record.raw.size() - 1;
    if (output.Write(record.raw.data(), size) != size)
    {
        std::cerr << "Failed to write into the output source!" << std::endl;
        return false;
    }

    return true;
}

bool UpdateHashmap(std::unordered_map<uint32_t, std::string>& hashmap, std::string_view message, uint32_t message_hash)
{
    if (hashmap.find(message_hash) == hashmap.end())
    {
        std::cout << format("Discovered logging message: \"{}\" with hash = 0x{:08X}", message, message_hash) << std::endl;
        hashmap[message_hash] = message;
        return true;
    }
    else if (message != hashmap[message_hash])
    {
        std::cerr << format("Collision detected!") << std::endl;
        std::cerr << format("Previous logging message: \"{}\" with hash = 0x{:08X}", hashmap[message_hash], message_hash) << std::endl;
        std::cerr << format("Conflict logging message: \"{}\" with hash = 0x{:08X}", message, message_hash) << std::endl;
        throwex Exception("Collision detected!");
    }

    // Skip duplicates
    return false;
}

bool UpdateHashmap(std::unordered_map<uint32_t, std::string>& hashmap, Record& record)
{
    bool result = false;

    // Check the logger name
    result |= UpdateHashmap(hashmap, record.logger, HashLayout::Hash(record.logger));
    // Check the logging message
    result |= UpdateHashmap(hashmap, record.message, HashLayout::Hash(record.message));

    return result;
}

int main(int argc, char** argv)
{
    auto parser = optparse::OptionParser().version(version);

    parser.add_option("-x", "--hashlog").dest("hashlog").help("Hashlog file name");
    parser.add_option("-i", "--input").dest("input").help("Input file name");
    parser.add_option("-o", "--output").dest("output").help("Output file name");
    parser.add_option("-u", "--update").dest("update").help("Update .hashlog");

    optparse::Values options = parser.parse_args(argc, argv);

    // Print help
    if (options.get("help"))
    {
        parser.print_help();
        return 0;
    }

    try
    {
        // Open the hashlog file
        Path hashlog = FindHashlog(Path::current());
        if (options.is_set("hashlog"))
        {
            hashlog = Path(options.get("hashlog"));
            if (hashlog.IsDirectory())
                hashlog = FindHashlog(hashlog);
        }

        // Read .hashlog file and fill the logging messages hash map
        std::unordered_map<uint32_t, std::string> hashmap = ReadHashlog(hashlog);

        Path temp_file;

        // Open the input file or stdin
        std::unique_ptr<Reader> input(new StdInput());
        if (options.is_set("input") || options.is_set("update"))
        {
            Path path(options.is_set("input") ? options.get("input") : options.get("update"));
            if (path.IsRegularFile() && (path.extension() == ".zip"))
                temp_file = path = UnzipFile(path);
            File* file = new File(path);
            file->Open(true, false);
            input.reset(file);
        }

        // Open the output file or stdout
        std::unique_ptr<Writer> output(new StdOutput());
        if (options.is_set("output"))
        {
            File* file = new File(Path(options.get("output")));
            file->Open(false, true);
            output.reset(file);
        }

        if (options.is_set("update"))
        {
            bool store = false;

            // Update hashmap with data from all logging records
            Record record;
            while (InputRecord(*input, record))
                store |= UpdateHashmap(hashmap, record);

            // Store updated .hashlog
            if (store)
                WriteHashlog(hashlog, hashmap);
        }
        else
        {
            // Process all logging records
            Record record;
            while (InputRecord(*input, record, hashmap))
                if (!OutputRecord(*output, record))
                    break;
        }

        // Delete temporary file
        if (temp_file)
            Path::Remove(temp_file);

        return 0;
    }
    catch (const std::exception& ex)
    {
        std::cerr << ex.what() << std::endl;
        return -1;
    }
}
<|endoftext|>


<commit_before>/*
 * Copyright (C) 2009, 2010, Gostai S.A.S.
 *
 * This software is provided "as is" without warranty of any kind,
 * either expressed or implied, including but not limited to the
 * implied warranties of fitness for a particular purpose.
 *
 * See the LICENSE file for more information.
 */
#ifndef LIBPORT_XLTDL_HH
# define LIBPORT_XLTDL_HH

# include <stdexcept>

# include <libport/fwd.hh>
# include <libport/compiler.hh>
# include <libport/export.hh>
# include <libport/file-library.hh>

# include <ltdl.h>

namespace libport
{

  class LIBPORT_API xlt_advise
  {
  public:
    /// Exceptions thrown on errors.
    struct LIBPORT_API exception : std::runtime_error
    {
      exception(const std::string& msg);
    };

    xlt_advise() throw (exception);
    ~xlt_advise() throw (exception);

    xlt_advise& global(bool global) throw (exception);
    xlt_advise& ext() throw (exception);

    const file_library& path() const throw ();
    file_library& path() throw ();

    xlt_handle open(const std::string& s) throw(exception);

    /// Throw an exception, or exit with exit_status_ if nonnull.
    ATTRIBUTE_NORETURN
    static void fail(std::string msg) throw (exception);

  private:
    /// Does not use the search path.  Can return 0.
    lt_dlhandle dlopen_(const std::string& s) const throw (exception);

    lt_dladvise advise_;
    file_library path_;
  };

  class LIBPORT_API xlt_handle
  {
  public:
    typedef xlt_advise::exception exception;

    xlt_handle(lt_dlhandle h = 0);
    ~xlt_handle();

    /// Close the handle.
    void close() throw (exception);

    /// Detach so that destruction does not close.
    void detach();

    /// Detach so that destruction does not close.
    void attach(lt_dlhandle h);

    /// Wrapper around lt_dlsym that exits on failures.
    template <typename T>
    T sym(const std::string& s) throw (exception);

    /// Bounce to xlt_advise::fail.
    ATTRIBUTE_NORETURN
    static void fail(const std::string& msg) throw (exception);

    /// The handle.
    /// Exposed, as currently we don't cover the whole lt_ interface.
    lt_dlhandle handle;
  };

  /// Wrapper around lt_dlopenext that exits on failures.
  LIBPORT_API
  xlt_handle
  xlt_openext(const std::string& s, bool global, int exit_failure = 1)
    throw (xlt_advise::exception);

}

// This function is here only for the testsuite; it opens libport.so,
// and try to fetch and use this symbol.
extern "C"
{
  int libport_xlt_details_identity(int i);
}

# include <libport/xltdl.hxx>

#endif // ! LIBPORT_XLTDL_HH
<commit_msg>xltdl: export the test function.<commit_after>/*
 * Copyright (C) 2009, 2010, Gostai S.A.S.
 *
 * This software is provided "as is" without warranty of any kind,
 * either expressed or implied, including but not limited to the
 * implied warranties of fitness for a particular purpose.
 *
 * See the LICENSE file for more information.
 */
#ifndef LIBPORT_XLTDL_HH
# define LIBPORT_XLTDL_HH

# include <stdexcept>

# include <libport/fwd.hh>
# include <libport/compiler.hh>
# include <libport/export.hh>
# include <libport/file-library.hh>

# include <ltdl.h>

namespace libport
{

  class LIBPORT_API xlt_advise
  {
  public:
    /// Exceptions thrown on errors.
    struct LIBPORT_API exception : std::runtime_error
    {
      exception(const std::string& msg);
    };

    xlt_advise() throw (exception);
    ~xlt_advise() throw (exception);

    xlt_advise& global(bool global) throw (exception);
    xlt_advise& ext() throw (exception);

    const file_library& path() const throw ();
    file_library& path() throw ();

    xlt_handle open(const std::string& s) throw(exception);

    /// Throw an exception, or exit with exit_status_ if nonnull.
    ATTRIBUTE_NORETURN
    static void fail(std::string msg) throw (exception);

  private:
    /// Does not use the search path.  Can return 0.
    lt_dlhandle dlopen_(const std::string& s) const throw (exception);

    lt_dladvise advise_;
    file_library path_;
  };

  class LIBPORT_API xlt_handle
  {
  public:
    typedef xlt_advise::exception exception;

    xlt_handle(lt_dlhandle h = 0);
    ~xlt_handle();

    /// Close the handle.
    void close() throw (exception);

    /// Detach so that destruction does not close.
    void detach();

    /// Detach so that destruction does not close.
    void attach(lt_dlhandle h);

    /// Wrapper around lt_dlsym that exits on failures.
    template <typename T>
    T sym(const std::string& s) throw (exception);

    /// Bounce to xlt_advise::fail.
    ATTRIBUTE_NORETURN
    static void fail(const std::string& msg) throw (exception);

    /// The handle.
    /// Exposed, as currently we don't cover the whole lt_ interface.
    lt_dlhandle handle;
  };

  /// Wrapper around lt_dlopenext that exits on failures.
  LIBPORT_API
  xlt_handle
  xlt_openext(const std::string& s, bool global, int exit_failure = 1)
    throw (xlt_advise::exception);

}

// This function is here only for the testsuite; it opens libport.so,
// and try to fetch and use this symbol.
extern "C"
{
  LIBPORT_API
  int libport_xlt_details_identity(int i);
}

# include <libport/xltdl.hxx>

#endif // ! LIBPORT_XLTDL_HH
<|endoftext|>