{"action":"created","comment":{"url":"https://api.github.com/repos/KnowSheet/Alohalytics/pulls/comments/32140721","id":32140721,"diff_hunk":"@@ -0,0 +1,272 @@\n+/*******************************************************************************\n+The MIT License (MIT)\n+\n+Copyright (c) 2015 Alexander Zolotarev <me@alex.bio> from Minsk, Belarus\n+\n+Permission is hereby granted, free of charge, to any person obtaining a copy\n+of this software and associated documentation files (the \"Software\"), to deal\n+in the Software without restriction, including without limitation the rights\n+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n+copies of the Software, and to permit persons to whom the Software is\n+furnished to do so, subject to the following conditions:\n+\n+The above copyright notice and this permission notice shall be included in all\n+copies or substantial portions of the Software.\n+\n+THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\n+SOFTWARE.\n+*******************************************************************************/\n+\n+#ifndef MESSAGES_QUEUE_HPP\n+#define MESSAGES_QUEUE_HPP\n+\n+#include <condition_variable>  // condition_variable\n+#include <cstdio>              // rename, remove\n+#include <ctime>               // time, gmtime\n+#include <fstream>             // ofstream\n+#include <functional>          // bind, function\n+#include <list>                // list\n+#include <memory>              // unique_ptr\n+#include <mutex>               // mutex\n+#include <string>              // string\n+#include <thread>              // thread\n+\n+#include \"file_manager.h\"  // ForEachFileInDir\n+\n+namespace alohalytics {\n+\n+// Archiver function, should process first parameter's file, remove it and store result in the second parameter.\n+typedef std::function<void(const std::string & file_to_archive, const std::string & archived_file)> TFileArchiver;\n+// Processor should return true if file was processed successfully.\n+// If file_name_in_content is true, then second parameter is a full path to a\n+// file instead of a buffer.\n+typedef std::function<bool(bool file_name_in_content, const std::string & content)> TArchivedFileProcessor;\n+enum class ProcessingResult { EProcessedSuccessfully, EProcessingError, ENothingToProcess };\n+typedef std::function<void(ProcessingResult)> TFileProcessingFinishedCallback;\n+\n+// Default name for \"active\" file where we store messages.\n+constexpr char kCurrentFileName[] = \"alohalytics_messages\";\n+constexpr char kArchivedFilesExtension[] = \".archived\";\n+\n+class MessagesQueue {\n+ public:\n+  // Size limit (before gzip) when we archive \"current\" file and create a new\n+  // one for appending.\n+  // Optimal size is the one which (gzipped) can be POSTed to the server as one\n+  // HTTP request.\n+  const std::ofstream::pos_type kMaxFileSizeInBytes = 100 * 1024;\n+\n+  // Default archiving simply renames original file without any additional processing.\n+  static void ArchiveFileByRenamingIt(const std::string & original_file, const std::string & out_archive) {\n+    // TODO(AlexZ): Debug log if rename has failed.\n+    std::rename(original_file.c_str(), out_archive.c_str());\n+  }\n+\n+  //\n+  MessagesQueue(TFileArchiver file_archiver = &ArchiveFileByRenamingIt) : file_archiver_(file_archiver) {}\n+\n+  ~MessagesQueue() {\n+    {\n+      std::lock_guard<std::mutex> lock(mutex_);\n+      worker_thread_should_exit_ = true;\n+      condition_variable_.notify_all();\n+    }\n+    worker_thread_.join();\n+  }\n+\n+  // Sets working directory (and flushes in-memory messages into the file).\n+  // Executed on the WorkerThread.\n+  void SetStorageDirectory(std::string directory) {\n+    FileManager::AppendDirectorySlash(directory);\n+    std::lock_guard<std::mutex> lock(mutex_);\n+    commands_queue_.push_back(std::bind(&MessagesQueue::ProcessInitializeStorageCommand, this, directory));\n+    condition_variable_.notify_all();\n+  }\n+  // Stores message into a file archive (if SetStorageDirectory was called with\n+  // a valid directory),\n+  // otherwise stores messages in-memory.\n+  // Executed on the WorkerThread.\n+  void PushMessage(const std::string & message) {\n+    std::lock_guard<std::mutex> lock(mutex_);\n+    messages_buffer_.append(message);\n+    commands_queue_.push_back(std::bind(&MessagesQueue::ProcessMessageCommand, this));\n+    condition_variable_.notify_all();\n+  }\n+\n+  // Processor should return true if file was successfully processed (e.g.\n+  // uploaded to a server, etc.).\n+  // File is deleted if processor has returned true.\n+  // Processing stops if processor returns false.\n+  // Optional callback is called when all files are processed.\n+  // Executed on the WorkerThread.\n+  void ProcessArchivedFiles(TArchivedFileProcessor processor, TFileProcessingFinishedCallback callback) {\n+    std::lock_guard<std::mutex> lock(mutex_);\n+    commands_queue_.push_back(std::bind(&MessagesQueue::ProcessArchivedFilesCommand, this, processor, callback));\n+    condition_variable_.notify_all();\n+  }\n+\n+ private:\n+  // Returns full path to unique, non-existing file in the given directory.\n+  // Uses default extension for easier archives scan later.\n+  static std::string GenerateFullFilePathForArchive(const std::string & directory) {\n+    std::time_t now = std::time(nullptr);\n+    std::string generated;\n+    do {\n+      generated = directory + kCurrentFileName + \"-\" + std::to_string(now) + kArchivedFilesExtension;\n+      ++now;\n+    } while (std::ifstream(generated).good());\n+    return generated;\n+  }\n+\n+  // current_file_ is single-threaded.\n+  void ArchiveCurrentFile() {\n+    if (current_file_) {\n+      current_file_.reset(nullptr);\n+      const std::string current_file_path = storage_directory_ + kCurrentFileName;\n+      file_archiver_(current_file_path.c_str(), GenerateFullFilePathForArchive(storage_directory_).c_str());\n+      current_file_.reset(new std::ofstream(current_file_path, std::ios_base::app | std::ios_base::binary));\n+    }\n+  }\n+\n+  void StoreMessages(std::string const & messages_buffer) {\n+    if (current_file_) {\n+      *current_file_ << messages_buffer << std::flush;\n+      if (current_file_->tellp() > kMaxFileSizeInBytes) {\n+        ArchiveCurrentFile();\n+      }\n+    } else {\n+      messages_storage_.append(messages_buffer);\n+    }\n+  }\n+\n+  void ProcessInitializeStorageCommand(const std::string & directory) {\n+    current_file_.reset(nullptr);\n+    std::ofstream * new_current_file =\n+        new std::ofstream(directory + kCurrentFileName, std::ios_base::app | std::ios_base::binary);\n+    if (new_current_file->fail()) {\n+      // If file can't be created, fall back to the in-memory storage.\n+      storage_directory_.clear();\n+      // TODO(AlexZ): DebugLog it!\n+      delete new_current_file;\n+    } else {\n+      storage_directory_ = directory;\n+      current_file_.reset(new_current_file);\n+      // Also check if there are any messages in the memory storage, and save them\n+      // to file.\n+      if (!messages_storage_.empty()) {\n+        StoreMessages(messages_storage_);\n+        messages_storage_.clear();\n+      }\n+    }\n+  }\n+\n+  void ProcessMessageCommand() {\n+    std::string messages_buffer_copy;\n+    {\n+      std::lock_guard<std::mutex> lock(mutex_);\n+      if (!messages_buffer_.empty()) {\n+        messages_buffer_copy.swap(messages_buffer_);\n+      }\n+    }\n+    if (!messages_buffer_copy.empty()) {\n+      StoreMessages(messages_buffer_copy);\n+    }\n+  }\n+\n+  // If there is no file storage directory set, it should also process messages\n+  // from the memory buffer.\n+  void ProcessArchivedFilesCommand(TArchivedFileProcessor processor, TFileProcessingFinishedCallback callback) {\n+    ProcessingResult result = ProcessingResult::ENothingToProcess;\n+    // Process in-memory messages, if any.\n+    // TODO(AlexZ): Append user ID to every bunch of data sent to processor.\n+    if (!messages_storage_.empty()) {\n+      // TODO(AlexZ): gzip it before processing.\n+      if (processor(false /* in-memory buffer */, messages_storage_)) {\n+        messages_storage_.clear();\n+        result = ProcessingResult::EProcessedSuccessfully;\n+      } else {\n+        result = ProcessingResult::EProcessingError;\n+      }\n+      // If in-memory storage is used, then file storage directory was not set\n+      // and we can't process files.\n+      // So here we notify callback and return.\n+      // TODO(AlexZ): Do we need to use in-memory storage if storage is\n+      // initialized but full/not accessible?\n+      if (callback) {\n+        callback(result);\n+      }\n+      return;\n+    }\n+    if (current_file_ && current_file_->tellp() > 0) {\n+      ArchiveCurrentFile();\n+    }\n+    FileManager::ForEachFileInDir(storage_directory_, [&processor, &result](const std::string & full_path_to_file) {\n+      // Ignore non-archived files.\n+      if (full_path_to_file.find(kArchivedFilesExtension) == std::string::npos) {\n+        return true;\n+      }\n+      if (processor(true /* file path */, full_path_to_file)) {","path":"src/messages_queue.h","position":213,"original_position":213,"commit_id":"55a120ca6ddcb77efda87acc5570e9de2b75c3d1","original_commit_id":"55a120ca6ddcb77efda87acc5570e9de2b75c3d1","user":{"login":"dkorolev","id":2159447,"avatar_url":"https://avatars.githubusercontent.com/u/2159447?v=3","gravatar_id":"","url":"https://api.github.com/users/dkorolev","html_url":"https://github.com/dkorolev","followers_url":"https://api.github.com/users/dkorolev/followers","following_url":"https://api.github.com/users/dkorolev/following{/other_user}","gists_url":"https://api.github.com/users/dkorolev/gists{/gist_id}","starred_url":"https://api.github.com/users/dkorolev/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/dkorolev/subscriptions","organizations_url":"https://api.github.com/users/dkorolev/orgs","repos_url":"https://api.github.com/users/dkorolev/repos","events_url":"https://api.github.com/users/dkorolev/events{/privacy}","received_events_url":"https://api.github.com/users/dkorolev/received_events","type":"User","site_admin":false},"body":"This `/* file_path */` is confusing. Obsolete?","created_at":"2015-06-10T17:06:21Z","updated_at":"2015-06-10T17:06:21Z","html_url":"https://github.com/KnowSheet/Alohalytics/pull/36#discussion_r32140721","pull_request_url":"https://api.github.com/repos/KnowSheet/Alohalytics/pulls/36","_links":{"self":{"href":"https://api.github.com/repos/KnowSheet/Alohalytics/pulls/comments/32140721"},"html":{"href":"https://github.com/KnowSheet/Alohalytics/pull/36#discussion_r32140721"},"pull_request":{"href":"https://api.github.com/repos/KnowSheet/Alohalytics/pulls/36"}}},"pull_request":{"url":"https://api.github.com/repos/KnowSheet/Alohalytics/pulls/36","id":37350082,"html_url":"https://github.com/KnowSheet/Alohalytics/pull/36","diff_url":"https://github.com/KnowSheet/Alohalytics/pull/36.diff","patch_url":"https://github.com/KnowSheet/Alohalytics/pull/36.patch","issue_url":"https://api.github.com/repos/KnowSheet/Alohalytics/issues/36","number":36,"state":"open","locked":false,"title":"Core Messages Queue refactoring and many fixes/additions.","user":{"login":"deathbaba","id":170263,"avatar_url":"https://avatars.githubusercontent.com/u/170263?v=3","gravatar_id":"","url":"https://api.github.com/users/deathbaba","html_url":"https://github.com/deathbaba","followers_url":"https://api.github.com/users/deathbaba/followers","following_url":"https://api.github.com/users/deathbaba/following{/other_user}","gists_url":"https://api.github.com/users/deathbaba/gists{/gist_id}","starred_url":"https://api.github.com/users/deathbaba/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/deathbaba/subscriptions","organizations_url":"https://api.github.com/users/deathbaba/orgs","repos_url":"https://api.github.com/users/deathbaba/repos","events_url":"https://api.github.com/users/deathbaba/events{/privacy}","received_events_url":"https://api.github.com/users/deathbaba/received_events","type":"User","site_admin":false},"body":"","created_at":"2015-06-10T11:48:34Z","updated_at":"2015-06-10T17:06:21Z","closed_at":null,"merged_at":null,"merge_commit_sha":"2a9870f729a6b2f4b97f6b4ac49914c6af446351","assignee":{"login":"dkorolev","id":2159447,"avatar_url":"https://avatars.githubusercontent.com/u/2159447?v=3","gravatar_id":"","url":"https://api.github.com/users/dkorolev","html_url":"https://github.com/dkorolev","followers_url":"https://api.github.com/users/dkorolev/followers","following_url":"https://api.github.com/users/dkorolev/following{/other_user}","gists_url":"https://api.github.com/users/dkorolev/gists{/gist_id}","starred_url":"https://api.github.com/users/dkorolev/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/dkorolev/subscriptions","organizations_url":"https://api.github.com/users/dkorolev/orgs","repos_url":"https://api.github.com/users/dkorolev/repos","events_url":"https://api.github.com/users/dkorolev/events{/privacy}","received_events_url":"https://api.github.com/users/dkorolev/received_events","type":"User","site_admin":false},"milestone":null,"commits_url":"https://api.github.com/repos/KnowSheet/Alohalytics/pulls/36/commits","review_comments_url":"https://api.github.com/repos/KnowSheet/Alohalytics/pulls/36/comments","review_comment_url":"https://api.github.com/repos/KnowSheet/Alohalytics/pulls/comments{/number}","comments_url":"https://api.github.com/repos/KnowSheet/Alohalytics/issues/36/comments","statuses_url":"https://api.github.com/repos/KnowSheet/Alohalytics/statuses/55a120ca6ddcb77efda87acc5570e9de2b75c3d1","head":{"label":"KnowSheet:review","ref":"review","sha":"55a120ca6ddcb77efda87acc5570e9de2b75c3d1","user":{"login":"KnowSheet","id":9897562,"avatar_url":"https://avatars.githubusercontent.com/u/9897562?v=3","gravatar_id":"","url":"https://api.github.com/users/KnowSheet","html_url":"https://github.com/KnowSheet","followers_url":"https://api.github.com/users/KnowSheet/followers","following_url":"https://api.github.com/users/KnowSheet/following{/other_user}","gists_url":"https://api.github.com/users/KnowSheet/gists{/gist_id}","starred_url":"https://api.github.com/users/KnowSheet/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/KnowSheet/subscriptions","organizations_url":"https://api.github.com/users/KnowSheet/orgs","repos_url":"https://api.github.com/users/KnowSheet/repos","events_url":"https://api.github.com/users/KnowSheet/events{/privacy}","received_events_url":"https://api.github.com/users/KnowSheet/received_events","type":"Organization","site_admin":false},"repo":{"id":27983008,"name":"Alohalytics","full_name":"KnowSheet/Alohalytics","owner":{"login":"KnowSheet","id":9897562,"avatar_url":"https://avatars.githubusercontent.com/u/9897562?v=3","gravatar_id":"","url":"https://api.github.com/users/KnowSheet","html_url":"https://github.com/KnowSheet","followers_url":"https://api.github.com/users/KnowSheet/followers","following_url":"https://api.github.com/users/KnowSheet/following{/other_user}","gists_url":"https://api.github.com/users/KnowSheet/gists{/gist_id}","starred_url":"https://api.github.com/users/KnowSheet/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/KnowSheet/subscriptions","organizations_url":"https://api.github.com/users/KnowSheet/orgs","repos_url":"https://api.github.com/users/KnowSheet/repos","events_url":"https://api.github.com/users/KnowSheet/events{/privacy}","received_events_url":"https://api.github.com/users/KnowSheet/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/KnowSheet/Alohalytics","description":"","fork":false,"url":"https://api.github.com/repos/KnowSheet/Alohalytics","forks_url":"https://api.github.com/repos/KnowSheet/Alohalytics/forks","keys_url":"https://api.github.com/repos/KnowSheet/Alohalytics/keys{/key_id}","collaborators_url":"https://api.github.com/repos/KnowSheet/Alohalytics/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/KnowSheet/Alohalytics/teams","hooks_url":"https://api.github.com/repos/KnowSheet/Alohalytics/hooks","issue_events_url":"https://api.github.com/repos/KnowSheet/Alohalytics/issues/events{/number}","events_url":"https://api.github.com/repos/KnowSheet/Alohalytics/events","assignees_url":"https://api.github.com/repos/KnowSheet/Alohalytics/assignees{/user}","branches_url":"https://api.github.com/repos/KnowSheet/Alohalytics/branches{/branch}","tags_url":"https://api.github.com/repos/KnowSheet/Alohalytics/tags","blobs_url":"https://api.github.com/repos/KnowSheet/Alohalytics/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/KnowSheet/Alohalytics/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/KnowSheet/Alohalytics/git/refs{/sha}","trees_url":"https://api.github.com/repos/KnowSheet/Alohalytics/git/trees{/sha}","statuses_url":"https://api.github.com/repos/KnowSheet/Alohalytics/statuses/{sha}","languages_url":"https://api.github.com/repos/KnowSheet/Alohalytics/languages","stargazers_url":"https://api.github.com/repos/KnowSheet/Alohalytics/stargazers","contributors_url":"https://api.github.com/repos/KnowSheet/Alohalytics/contributors","subscribers_url":"https://api.github.com/repos/KnowSheet/Alohalytics/subscribers","subscription_url":"https://api.github.com/repos/KnowSheet/Alohalytics/subscription","commits_url":"https://api.github.com/repos/KnowSheet/Alohalytics/commits{/sha}","git_commits_url":"https://api.github.com/repos/KnowSheet/Alohalytics/git/commits{/sha}","comments_url":"https://api.github.com/repos/KnowSheet/Alohalytics/comments{/number}","issue_comment_url":"https://api.github.com/repos/KnowSheet/Alohalytics/issues/comments{/number}","contents_url":"https://api.github.com/repos/KnowSheet/Alohalytics/contents/{+path}","compare_url":"https://api.github.com/repos/KnowSheet/Alohalytics/compare/{base}...{head}","merges_url":"https://api.github.com/repos/KnowSheet/Alohalytics/merges","archive_url":"https://api.github.com/repos/KnowSheet/Alohalytics/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/KnowSheet/Alohalytics/downloads","issues_url":"https://api.github.com/repos/KnowSheet/Alohalytics/issues{/number}","pulls_url":"https://api.github.com/repos/KnowSheet/Alohalytics/pulls{/number}","milestones_url":"https://api.github.com/repos/KnowSheet/Alohalytics/milestones{/number}","notifications_url":"https://api.github.com/repos/KnowSheet/Alohalytics/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/KnowSheet/Alohalytics/labels{/name}","releases_url":"https://api.github.com/repos/KnowSheet/Alohalytics/releases{/id}","created_at":"2014-12-14T04:17:16Z","updated_at":"2015-04-24T14:49:50Z","pushed_at":"2015-06-10T11:48:34Z","git_url":"git://github.com/KnowSheet/Alohalytics.git","ssh_url":"git@github.com:KnowSheet/Alohalytics.git","clone_url":"https://github.com/KnowSheet/Alohalytics.git","svn_url":"https://github.com/KnowSheet/Alohalytics","homepage":null,"size":1555,"stargazers_count":0,"watchers_count":0,"language":"C++","has_issues":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":2,"mirror_url":null,"open_issues_count":1,"forks":2,"open_issues":1,"watchers":0,"default_branch":"master"}},"base":{"label":"KnowSheet:master","ref":"master","sha":"4bad20db3ef5db2abc2826bf7f57a61f410cb061","user":{"login":"KnowSheet","id":9897562,"avatar_url":"https://avatars.githubusercontent.com/u/9897562?v=3","gravatar_id":"","url":"https://api.github.com/users/KnowSheet","html_url":"https://github.com/KnowSheet","followers_url":"https://api.github.com/users/KnowSheet/followers","following_url":"https://api.github.com/users/KnowSheet/following{/other_user}","gists_url":"https://api.github.com/users/KnowSheet/gists{/gist_id}","starred_url":"https://api.github.com/users/KnowSheet/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/KnowSheet/subscriptions","organizations_url":"https://api.github.com/users/KnowSheet/orgs","repos_url":"https://api.github.com/users/KnowSheet/repos","events_url":"https://api.github.com/users/KnowSheet/events{/privacy}","received_events_url":"https://api.github.com/users/KnowSheet/received_events","type":"Organization","site_admin":false},"repo":{"id":27983008,"name":"Alohalytics","full_name":"KnowSheet/Alohalytics","owner":{"login":"KnowSheet","id":9897562,"avatar_url":"https://avatars.githubusercontent.com/u/9897562?v=3","gravatar_id":"","url":"https://api.github.com/users/KnowSheet","html_url":"https://github.com/KnowSheet","followers_url":"https://api.github.com/users/KnowSheet/followers","following_url":"https://api.github.com/users/KnowSheet/following{/other_user}","gists_url":"https://api.github.com/users/KnowSheet/gists{/gist_id}","starred_url":"https://api.github.com/users/KnowSheet/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/KnowSheet/subscriptions","organizations_url":"https://api.github.com/users/KnowSheet/orgs","repos_url":"https://api.github.com/users/KnowSheet/repos","events_url":"https://api.github.com/users/KnowSheet/events{/privacy}","received_events_url":"https://api.github.com/users/KnowSheet/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/KnowSheet/Alohalytics","description":"","fork":false,"url":"https://api.github.com/repos/KnowSheet/Alohalytics","forks_url":"https://api.github.com/repos/KnowSheet/Alohalytics/forks","keys_url":"https://api.github.com/repos/KnowSheet/Alohalytics/keys{/key_id}","collaborators_url":"https://api.github.com/repos/KnowSheet/Alohalytics/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/KnowSheet/Alohalytics/teams","hooks_url":"https://api.github.com/repos/KnowSheet/Alohalytics/hooks","issue_events_url":"https://api.github.com/repos/KnowSheet/Alohalytics/issues/events{/number}","events_url":"https://api.github.com/repos/KnowSheet/Alohalytics/events","assignees_url":"https://api.github.com/repos/KnowSheet/Alohalytics/assignees{/user}","branches_url":"https://api.github.com/repos/KnowSheet/Alohalytics/branches{/branch}","tags_url":"https://api.github.com/repos/KnowSheet/Alohalytics/tags","blobs_url":"https://api.github.com/repos/KnowSheet/Alohalytics/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/KnowSheet/Alohalytics/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/KnowSheet/Alohalytics/git/refs{/sha}","trees_url":"https://api.github.com/repos/KnowSheet/Alohalytics/git/trees{/sha}","statuses_url":"https://api.github.com/repos/KnowSheet/Alohalytics/statuses/{sha}","languages_url":"https://api.github.com/repos/KnowSheet/Alohalytics/languages","stargazers_url":"https://api.github.com/repos/KnowSheet/Alohalytics/stargazers","contributors_url":"https://api.github.com/repos/KnowSheet/Alohalytics/contributors","subscribers_url":"https://api.github.com/repos/KnowSheet/Alohalytics/subscribers","subscription_url":"https://api.github.com/repos/KnowSheet/Alohalytics/subscription","commits_url":"https://api.github.com/repos/KnowSheet/Alohalytics/commits{/sha}","git_commits_url":"https://api.github.com/repos/KnowSheet/Alohalytics/git/commits{/sha}","comments_url":"https://api.github.com/repos/KnowSheet/Alohalytics/comments{/number}","issue_comment_url":"https://api.github.com/repos/KnowSheet/Alohalytics/issues/comments{/number}","contents_url":"https://api.github.com/repos/KnowSheet/Alohalytics/contents/{+path}","compare_url":"https://api.github.com/repos/KnowSheet/Alohalytics/compare/{base}...{head}","merges_url":"https://api.github.com/repos/KnowSheet/Alohalytics/merges","archive_url":"https://api.github.com/repos/KnowSheet/Alohalytics/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/KnowSheet/Alohalytics/downloads","issues_url":"https://api.github.com/repos/KnowSheet/Alohalytics/issues{/number}","pulls_url":"https://api.github.com/repos/KnowSheet/Alohalytics/pulls{/number}","milestones_url":"https://api.github.com/repos/KnowSheet/Alohalytics/milestones{/number}","notifications_url":"https://api.github.com/repos/KnowSheet/Alohalytics/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/KnowSheet/Alohalytics/labels{/name}","releases_url":"https://api.github.com/repos/KnowSheet/Alohalytics/releases{/id}","created_at":"2014-12-14T04:17:16Z","updated_at":"2015-04-24T14:49:50Z","pushed_at":"2015-06-10T11:48:34Z","git_url":"git://github.com/KnowSheet/Alohalytics.git","ssh_url":"git@github.com:KnowSheet/Alohalytics.git","clone_url":"https://github.com/KnowSheet/Alohalytics.git","svn_url":"https://github.com/KnowSheet/Alohalytics","homepage":null,"size":1555,"stargazers_count":0,"watchers_count":0,"language":"C++","has_issues":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":2,"mirror_url":null,"open_issues_count":1,"forks":2,"open_issues":1,"watchers":0,"default_branch":"master"}},"_links":{"self":{"href":"https://api.github.com/repos/KnowSheet/Alohalytics/pulls/36"},"html":{"href":"https://github.com/KnowSheet/Alohalytics/pull/36"},"issue":{"href":"https://api.github.com/repos/KnowSheet/Alohalytics/issues/36"},"comments":{"href":"https://api.github.com/repos/KnowSheet/Alohalytics/issues/36/comments"},"review_comments":{"href":"https://api.github.com/repos/KnowSheet/Alohalytics/pulls/36/comments"},"review_comment":{"href":"https://api.github.com/repos/KnowSheet/Alohalytics/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/KnowSheet/Alohalytics/pulls/36/commits"},"statuses":{"href":"https://api.github.com/repos/KnowSheet/Alohalytics/statuses/55a120ca6ddcb77efda87acc5570e9de2b75c3d1"}}}}