<commit_before>
//
// IEEE754 text encode/decode of double values
//

#include <clutl/Serialise.h>
#include <clutl/Containers.h>

extern "C" double strtod(const char* s00, char** se);

namespace
{
	enum TokenType
	{
		TT_NONE,

		// Single character tokens match their character values for simpler switch code
		TT_LBRACE = '{',
		TT_RBRACE = '}',
		TT_COMMA = ',',
		TT_COLON = ':',
		TT_LBRACKET = '[',
		TT_RBRACKET = ']',

		TT_STRING,
		TT_INTEGER,
		TT_DECIMAL,
	};

	struct Token
	{
		Token()
			: type(TT_NONE)
			, position(0)
			, length(0)
		{
		}

		explicit Token(TokenType type, int position, int length)
			: type(type)
			, position(position)
			, length(length)
		{
		}

		TokenType type;
		int position;
		int length;

		struct
		{
			union
			{
				const char* string;
				__int64 integer;
				double decimal;
			};
		} val;
	};

	// TODO: buffer overflow checking without the data buffer asserting!

	bool isdigit(char c)
	{
		return c >= '0' && c <= '9';
	}
	bool ishexdigit(char c)
	{
		return (c >= '0' && c <= '9') || (c >= 'A' && c <= 'F') || (c >= 'a' && c <= 'f');
	}

	bool Parse32bitHexDigits(clutl::DataBuffer& in)
	{
		in.SeekRel(1);

		// TODO: verify there are enough bytes to read 4 characters
		// NOTE: \u is not valid - C has the equivalent \xhh and \xhhhh

		// Ensure the next 4 bytes are hex digits
		int pos = in.GetPosition();
		const char* digits = in.ReadAt(pos);
		return
			ishexdigit(digits[0]) &&
			ishexdigit(digits[1]) &&
			ishexdigit(digits[2]) &&
			ishexdigit(digits[3]);
	}

	bool ParseEscapeSequence(clutl::DataBuffer& in)
	{
		in.SeekRel(1);

		int pos = in.GetPosition();
		char c = *in.ReadAt(pos);

		switch (c)
		{
		// Pass all single character sequences
		case ('\"'):
		case ('\\'):
		case ('/'):
		case ('\b'):
		case ('\f'):
		case ('\n'):
		case ('\r'):
		case ('\t'):
			in.SeekRel(1);
			return true;

		// Parse the unicode hex digits
		case ('u'):
			return Parse32bitHexDigits(in);

		default:
			// ERROR: Invalid escape sequence
			return false;
		}
	}

	Token ParseString(clutl::DataBuffer& in)
	{
		// Start off construction of the string
		int pos = in.GetPosition();
		Token token(TT_STRING, pos, 0);
		in.SeekRel(1);
		token.val.string = in.ReadAt(pos);

		// The common case here is another character as opposed to quotes so
		// keep looping until that happens
		while (true)
		{
			pos = in.GetPosition();
			char c = *in.ReadAt(pos);

			switch (c)
			{
			// The string terminates with a quote
			case ('\"'):
				in.SeekRel(1);
				return token;

			// Escape sequence
			case ('\\'):
				if (!ParseEscapeSequence(in))
					return Token();
				token.length += in.GetPosition() - pos;
				break;

			// A typical string character
			default:
				in.SeekRel(1);
				token.length++;
			}
		}

		return token;
	}

	//
	// This will return an integer in the range [-9,223,372,036,854,775,808:9,223,372,036,854,775,807]
	//
	bool ParseInteger(clutl::DataBuffer& in, unsigned __int64& uintval)
	{
		// Consume the first digit
		int pos = in.GetPosition();
		char c = *in.ReadAt(pos);
		if (!isdigit(c))
			return false;

		uintval = 0;
		do
		{
			// Consume and accumulate the digit
			in.SeekRel(1);
			uintval = (uintval * 10) + (c - '0');

			// Peek at the next character and leave if its not a digit
			pos = in.GetPosition();
			c = *in.ReadAt(pos);
		} while (isdigit(c));

		return true;
	}

	Token ParseNumber(clutl::DataBuffer& in)
	{
		// Start off construction of an integer
		int start_pos = in.GetPosition();
		Token token(TT_INTEGER, start_pos, 0);

		// Consume negative
		bool is_negative = false;
		if (*in.ReadAt(start_pos) == '-')
		{
			is_negative = true;
			in.SeekRel(1);
		}

		// Parse the integer digits
		unsigned __int64 uintval;
		if (!ParseInteger(in, uintval))
			// TODO: error
			return Token();

		// Convert to signed integer
		if (is_negative)
			token.val.integer = 0ULL - uintval;
		else
			token.val.integer = uintval;

		// Is this a decimal?
		int pos = in.GetPosition();
		char c = *in.ReadAt(pos);
		if (c == '.')
		{
			// Re-evaluate as a decimal using the more expensive strtod function
			const char* decimal_start = in.ReadAt(start_pos);
			char* decimal_end;
			token.type = TT_DECIMAL;
			token.val.decimal = strtod(decimal_start, &decimal_end);

			// Skip over the parsed decimal
			in.SeekAbs(start_pos + (decimal_end - decimal_start));
		}

		return token;
	}

	Token ParseToken(clutl::DataBuffer& in)
	{
	start:
		unsigned int pos = in.GetPosition();
		char c = *in.ReadAt(pos);

		switch (c)
		{
		// Branch to the start only if it's a whitespace (the least-common case)
		case (' '):
		case ('\t'):
		case ('\n'):
		case ('\v'):
		case ('\f'):
		case ('\r'):
			in.SeekRel(1);
			goto start;

		// Structural single character tokens
		case ('{'):
		case ('}'):
		case (','):
		case ('['):
		case (']'):
			in.SeekRel(1);
			return Token((TokenType)c, pos, 1);

		// Strings
		case ('\"'):
			return ParseString(in);

		case ('-'):
		case ('0'):
		case ('1'):
		case ('2'):
		case ('3'):
		case ('4'):
		case ('5'):
		case ('6'):
		case ('7'):
		case ('8'):
		case ('9'):
			return ParseNumber(in);

		default:
			// sequence of chars
			break;
		}

		return Token();
	}
}<commit_msg>Implemented outline of the JSON parser.<commit_after>
//
// IEEE754 text encode/decode of double values
//

#include <clutl/Serialise.h>
#include <clutl/Containers.h>

extern "C" double strtod(const char* s00, char** se);

namespace
{
	enum TokenType
	{
		TT_NONE,

		// Single character tokens match their character values for simpler switch code
		TT_LBRACE = '{',
		TT_RBRACE = '}',
		TT_COMMA = ',',
		TT_COLON = ':',
		TT_LBRACKET = '[',
		TT_RBRACKET = ']',

		TT_STRING,

		TT_TRUE,
		TT_FALSE,
		TT_NULL,

		TT_INTEGER,
		TT_DECIMAL,
	};

	struct Token
	{
		Token()
			: type(TT_NONE)
			, position(0)
			, length(0)
		{
		}

		explicit Token(TokenType type, int position, int length)
			: type(type)
			, position(position)
			, length(length)
		{
		}

		TokenType type;
		int position;
		int length;

		struct
		{
			union
			{
				const char* string;
				__int64 integer;
				double decimal;
			};
		} val;
	};

	// TODO: buffer overflow checking without the data buffer asserting!

	bool isdigit(char c)
	{
		return c >= '0' && c <= '9';
	}
	bool ishexdigit(char c)
	{
		return (c >= '0' && c <= '9') || (c >= 'A' && c <= 'F') || (c >= 'a' && c <= 'f');
	}

	bool Lexer32bitHexDigits(clutl::DataBuffer& in)
	{
		in.SeekRel(1);

		// TODO: verify there are enough bytes to read 4 characters
		// NOTE: \u is not valid - C has the equivalent \xhh and \xhhhh

		// Ensure the next 4 bytes are hex digits
		int pos = in.GetPosition();
		const char* digits = in.ReadAt(pos);
		return
			ishexdigit(digits[0]) &&
			ishexdigit(digits[1]) &&
			ishexdigit(digits[2]) &&
			ishexdigit(digits[3]);
	}

	bool LexerEscapeSequence(clutl::DataBuffer& in)
	{
		in.SeekRel(1);

		int pos = in.GetPosition();
		char c = *in.ReadAt(pos);

		switch (c)
		{
		// Pass all single character sequences
		case ('\"'):
		case ('\\'):
		case ('/'):
		case ('\b'):
		case ('\f'):
		case ('\n'):
		case ('\r'):
		case ('\t'):
			in.SeekRel(1);
			return true;

		// Parse the unicode hex digits
		case ('u'):
			return Lexer32bitHexDigits(in);

		default:
			// ERROR: Invalid escape sequence
			return false;
		}
	}

	Token LexerString(clutl::DataBuffer& in)
	{
		// Start off construction of the string
		int pos = in.GetPosition();
		Token token(TT_STRING, pos, 0);
		in.SeekRel(1);
		token.val.string = in.ReadAt(pos);

		// The common case here is another character as opposed to quotes so
		// keep looping until that happens
		while (true)
		{
			pos = in.GetPosition();
			char c = *in.ReadAt(pos);

			switch (c)
			{
			// The string terminates with a quote
			case ('\"'):
				in.SeekRel(1);
				return token;

			// Escape sequence
			case ('\\'):
				if (!LexerEscapeSequence(in))
					return Token();
				token.length += in.GetPosition() - pos;
				break;

			// A typical string character
			default:
				in.SeekRel(1);
				token.length++;
			}
		}

		return token;
	}

	//
	// This will return an integer in the range [-9,223,372,036,854,775,808:9,223,372,036,854,775,807]
	//
	bool LexerInteger(clutl::DataBuffer& in, unsigned __int64& uintval)
	{
		// Consume the first digit
		int pos = in.GetPosition();
		char c = *in.ReadAt(pos);
		if (!isdigit(c))
			return false;

		uintval = 0;
		do
		{
			// Consume and accumulate the digit
			in.SeekRel(1);
			uintval = (uintval * 10) + (c - '0');

			// Peek at the next character and leave if its not a digit
			pos = in.GetPosition();
			c = *in.ReadAt(pos);
		} while (isdigit(c));

		return true;
	}

	Token LexerNumber(clutl::DataBuffer& in)
	{
		// Start off construction of an integer
		int start_pos = in.GetPosition();
		Token token(TT_INTEGER, start_pos, 0);

		// Consume negative
		bool is_negative = false;
		if (*in.ReadAt(start_pos) == '-')
		{
			is_negative = true;
			in.SeekRel(1);
		}

		// Parse the integer digits
		unsigned __int64 uintval;
		if (!LexerInteger(in, uintval))
			// TODO: error
			return Token();

		// Convert to signed integer
		if (is_negative)
			token.val.integer = 0ULL - uintval;
		else
			token.val.integer = uintval;

		// Is this a decimal?
		int pos = in.GetPosition();
		char c = *in.ReadAt(pos);
		if (c == '.')
		{
			// Re-evaluate as a decimal using the more expensive strtod function
			const char* decimal_start = in.ReadAt(start_pos);
			char* decimal_end;
			token.type = TT_DECIMAL;
			token.val.decimal = strtod(decimal_start, &decimal_end);

			// Skip over the parsed decimal
			in.SeekAbs(start_pos + (decimal_end - decimal_start));
		}

		return token;
	}

	Token LexerKeyword(clutl::DataBuffer& in, TokenType type, const char* keyword, int len)
	{
		// Consume the first letter
		int start_pos = in.GetPosition();
		in.SeekRel(1);

		// TODO: Overflow without buffer assert

		// Try to match the remaining letters of the keyword
		int pos = in.GetPosition();
		while (len && *in.ReadAt(pos) == *keyword)
		{
			keyword++;
			pos++;
		}

		if (len)
			// ERROR: Keyword didn't match
			return Token();

		return Token(type, start_pos, in.GetPosition() - start_pos);
	}

	Token LexerToken(clutl::DataBuffer& in)
	{
	start:
		unsigned int pos = in.GetPosition();
		char c = *in.ReadAt(pos);

		switch (c)
		{
		// Branch to the start only if it's a whitespace (the least-common case)
		case (' '):
		case ('\t'):
		case ('\n'):
		case ('\v'):
		case ('\f'):
		case ('\r'):
			in.SeekRel(1);
			goto start;

		// Structural single character tokens
		case ('{'):
		case ('}'):
		case (','):
		case ('['):
		case (']'):
			in.SeekRel(1);
			return Token((TokenType)c, pos, 1);

		// Strings
		case ('\"'):
			return LexerString(in);

		// Integer or floating point numbers
		case ('-'):
		case ('0'):
		case ('1'):
		case ('2'):
		case ('3'):
		case ('4'):
		case ('5'):
		case ('6'):
		case ('7'):
		case ('8'):
		case ('9'):
			return LexerNumber(in);

		// Keywords
		case ('t'): return LexerKeyword(in, TT_TRUE, "rue", 3);
		case ('f'): return LexerKeyword(in, TT_FALSE, "alse", 4);
		case ('n'): return LexerKeyword(in, TT_NULL, "ull", 3);

		default:
			// ERROR: Unexpected character
			break;
		}

		return Token();
	}

	void ParserValue(clutl::DataBuffer& in, Token& t);
	void ParserObject(clutl::DataBuffer& in, Token& t);

	Token Expect(clutl::DataBuffer& in, Token& t, TokenType type)
	{
		if (t.type != type)
			// ERROR
			return Token();
		Token old = t;
		t = LexerToken(in);
		return old;
	}

	void ParserString(const Token& t)
	{
	}

	void ParserInteger(const Token& t)
	{
	}

	void ParserDecimal(const Token& t)
	{
	}

	void ParserElements(clutl::DataBuffer& in, Token& t)
	{
		// Expect a value first
		ParserValue(in, t);

		if (t.type == TT_COMMA)
		{
			t = LexerToken(in);
			ParserElements(in, t);
		}
	}

	void ParserArray(clutl::DataBuffer& in, Token& t)
	{
		Expect(in, t, TT_LBRACKET);

		if (t.type == TT_RBRACKET)
		{
			t = LexerToken(in);
			return;
		}

		ParserElements(in, t);
		Expect(in, t, TT_RBRACKET);
	}

	void ParserLiteralValue(clutl::DataBuffer& in, Token& t, TokenType type, int val)
	{
		if (t.type != type)
			// ERROR
			return;
		// process integer
		t = LexerToken(in);
	}

	void ParserValue(clutl::DataBuffer& in, Token& t)
	{
		switch (t.type)
		{
		case (TT_STRING): return ParserString(Expect(in, t, TT_STRING));
		case (TT_INTEGER): return ParserInteger(Expect(in, t, TT_INTEGER));
		case (TT_DECIMAL): return ParserDecimal(Expect(in, t, TT_DECIMAL));
		case (TT_LBRACE): return ParserObject(in, t);
		case (TT_LBRACKET): return ParserArray(in, t);
		case (TT_TRUE): return ParserLiteralValue(in, t, TT_TRUE, 1);
		case (TT_FALSE): return ParserLiteralValue(in, t, TT_FALSE, 0);
		case (TT_NULL): return ParserLiteralValue(in, t, TT_NULL, 0);

		default:
			// ERROR unexpected token
			break;
		}
	}

	void ParserPair(clutl::DataBuffer& in, Token& t)
	{
		Expect(in, t, TT_STRING);
		Expect(in, t, TT_COLON);
		ParserValue(in, t);
	}

	void ParserMembers(clutl::DataBuffer& in, Token& t)
	{
		ParserPair(in, t);

		if (t.type == TT_COMMA)
		{
			t = LexerToken(in);
			ParserMembers(in, t);
		}
	}

	void ParserObject(clutl::DataBuffer& in, Token& t)
	{
		Expect(in, t, TT_LBRACE);

		if (t.type == TT_RBRACE)
		{
			t = LexerToken(in);
			return;
		}

		ParserMembers(in, t);
		Expect(in, t, TT_RBRACE);
	}
}<|endoftext|>


<commit_before>// sota.cpp : Defines the entry point for the console application.
//
#include <regex>
#include "stdafx.h"
#include "utils.h"
#include "lexer.h"
#include "token.h"
#include "stream.hpp"
#include <array>

using namespace sota;

std::vector<char> load(std::string filename) {
    std::vector<char> chars;
    std::ifstream file(filename, std::ios::in | std::ios::binary | std::ios::ate);
    if (!file.eof() && !file.fail() ) {
        file.seekg(0, std::ios_base::end);
        auto size = (unsigned int)file.tellg();
        chars.resize(size);
        file.seekg(0, std::ios_base::beg);
        file.read(&chars[0], (long int)size);
    }
    return chars;
}

int main(int argc, char* argv[])
{
    if (argc < 2)
        return 1;
    const char *filename = argv[1];
    std::cout << "sota parsing: " << filename << std::endl << std::endl;

    auto chars = load(filename);
    auto lexer = SotaLexer(chars);

    while(auto token = lexer.Scan()) {

        switch (token.Type) {
        case TokenType::EndOfLine:
            std::cout << lexer.Pretty(token) << std::endl;
            break;
        case TokenType::WhiteSpace:
            break;
        default:
            std::cout << lexer.Pretty(token) << " ";
            break;
        }

    }
    return 0;
}

<commit_msg>added some variable to help with debugging off by one error... -sai<commit_after>// sota.cpp : Defines the entry point for the console application.
//
#include <regex>
#include "stdafx.h"
#include "utils.h"
#include "lexer.h"
#include "token.h"
#include "stream.hpp"
#include <array>

using namespace sota;

std::vector<char> load(std::string filename) {
    std::vector<char> chars;
    std::ifstream file(filename, std::ios::in | std::ios::binary | std::ios::ate);
    if (!file.eof() && !file.fail() ) {
        file.seekg(0, std::ios_base::end);
        auto size = (unsigned int)file.tellg();
        chars.resize(size);
        file.seekg(0, std::ios_base::beg);
        file.read(&chars[0], (long int)size);
    }
    return chars;
}

int main(int argc, char* argv[])
{
    if (argc < 2)
        return 1;
    const char *filename = argv[1];
    std::cout << "sota parsing: " << filename << std::endl << std::endl;

    auto chars = load(filename);
    auto lexer = SotaLexer(chars);

    while(auto token = lexer.Scan()) {

        auto index = lexer.Index;
        auto curr = lexer.Curr;

        switch (token.Type) {
        case TokenType::EndOfLine:
            std::cout << lexer.Pretty(token) << std::endl;
            break;
        case TokenType::WhiteSpace:
            break;
        default:
            std::cout << lexer.Pretty(token) << " ";
            break;
        }

    }
    return 0;
}

<|endoftext|>


<commit_before>// Copyright (c) 2010 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "chrome/browser/chromeos/notifications/desktop_notifications_unittest.h"

namespace chromeos {

// static
std::string DesktopNotificationsTest::log_output_;

class MockNotificationUI : public BalloonCollectionImpl::NotificationUI {
 public:
  virtual void Add(Balloon* balloon) {}
  virtual bool Update(Balloon* balloon) { return false; }
  virtual void Remove(Balloon* balloon) {}
  virtual void ResizeNotification(Balloon* balloon,
                                  const gfx::Size& size) {}
};

MockBalloonCollection::MockBalloonCollection()
    : log_proxy_(new LoggingNotificationProxy()) {
  set_notification_ui(new MockNotificationUI());
}

void MockBalloonCollection::Add(const Notification& notification,
                                Profile* profile) {
  // Swap in the logging proxy for the purpose of logging calls that
  // would be made into javascript, then pass this down to the
  // balloon collection.
  Notification test_notification(notification.origin_url(),
                                 notification.content_url(),
                                 notification.display_source(),
                                 log_proxy_.get());
  BalloonCollectionImpl::Add(test_notification, profile);
}

bool MockBalloonCollection::Remove(const Notification& notification) {
  Notification test_notification(notification.origin_url(),
                                 notification.content_url(),
                                 notification.display_source(),
                                 log_proxy_.get());
  return BalloonCollectionImpl::Remove(test_notification);
}

Balloon* MockBalloonCollection::MakeBalloon(const Notification& notification,
                                            Profile* profile) {
  // Start with a normal balloon but mock out the view.
  Balloon* balloon = BalloonCollectionImpl::MakeBalloon(notification, profile);
  balloon->set_view(new MockBalloonView(balloon));
  balloons_.insert(balloon);
  return balloon;
}

void MockBalloonCollection::OnBalloonClosed(Balloon* source) {
  balloons_.erase(source);
  BalloonCollectionImpl::OnBalloonClosed(source);
}

int MockBalloonCollection::UppermostVerticalPosition() {
  int min = 0;
  std::set<Balloon*>::iterator iter;
  for (iter = balloons_.begin(); iter != balloons_.end(); ++iter) {
    int pos = (*iter)->position().y();
    if (iter == balloons_.begin() || pos < min)
      min = pos;
  }
  return min;
}

DesktopNotificationsTest::DesktopNotificationsTest()
    : ui_thread_(ChromeThread::UI, &message_loop_) {
}

DesktopNotificationsTest::~DesktopNotificationsTest() {
}

void DesktopNotificationsTest::SetUp() {
  profile_.reset(new TestingProfile());
  balloon_collection_ = new MockBalloonCollection();
  ui_manager_.reset(new NotificationUIManager());
  ui_manager_->Initialize(balloon_collection_);
  balloon_collection_->set_space_change_listener(ui_manager_.get());
  service_.reset(new DesktopNotificationService(profile(), ui_manager_.get()));
  log_output_.clear();
}

void DesktopNotificationsTest::TearDown() {
  profile_.reset(NULL);
  ui_manager_.reset(NULL);
  service_.reset(NULL);
}

TEST_F(DesktopNotificationsTest, TestShow) {
  EXPECT_TRUE(service_->ShowDesktopNotificationText(
      GURL("http://www.google.com"),
      GURL("/icon.png"), ASCIIToUTF16("Title"), ASCIIToUTF16("Text"),
      0, 0, DesktopNotificationService::PageNotification, 1));
  MessageLoopForUI::current()->RunAllPending();
  EXPECT_EQ(1, balloon_collection_->count());

  EXPECT_TRUE(service_->ShowDesktopNotification(
      GURL("http://www.google.com"),
      GURL("http://www.google.com/notification.html"),
      0, 0, DesktopNotificationService::PageNotification, 2));
  MessageLoopForUI::current()->RunAllPending();
  EXPECT_EQ(2, balloon_collection_->count());

  EXPECT_EQ("notification displayed\n"
            "notification displayed\n",
            log_output_);
}

TEST_F(DesktopNotificationsTest, TestClose) {
  // Request a notification; should open a balloon.
  EXPECT_TRUE(service_->ShowDesktopNotificationText(
      GURL("http://www.google.com"),
      GURL("/icon.png"), ASCIIToUTF16("Title"), ASCIIToUTF16("Text"),
      0, 0, DesktopNotificationService::PageNotification, 1));
  MessageLoopForUI::current()->RunAllPending();
  EXPECT_EQ(1, balloon_collection_->count());

  // Close all the open balloons.
  std::set<Balloon*> balloons = balloon_collection_->balloons();
  std::set<Balloon*>::iterator iter;
  for (iter = balloons.begin(); iter != balloons.end(); ++iter) {
    (*iter)->OnClose(true);
  }

  // Verify that the balloon collection is now empty.
  EXPECT_EQ(0, balloon_collection_->count());

  EXPECT_EQ("notification displayed\n"
            "notification closed by user\n",
            log_output_);
}

TEST_F(DesktopNotificationsTest, TestCancel) {
  int process_id = 0;
  int route_id = 0;
  int notification_id = 1;
  // Request a notification; should open a balloon.
  EXPECT_TRUE(service_->ShowDesktopNotificationText(
      GURL("http://www.google.com"),
      GURL("/icon.png"), ASCIIToUTF16("Title"), ASCIIToUTF16("Text"),
      process_id, route_id, DesktopNotificationService::PageNotification,
      notification_id));
  MessageLoopForUI::current()->RunAllPending();
  EXPECT_EQ(1, balloon_collection_->count());

  // Cancel the same notification
  service_->CancelDesktopNotification(process_id,
                                      route_id,
                                      notification_id);
  MessageLoopForUI::current()->RunAllPending();
  // Verify that the balloon collection is now empty.
  EXPECT_EQ(0, balloon_collection_->count());

  EXPECT_EQ("notification displayed\n"
            "notification closed by script\n",
            log_output_);
}

TEST_F(DesktopNotificationsTest, TestManyNotifications) {
  int process_id = 0;
  int route_id = 0;

  // Request lots of identical notifications.
  const int kLotsOfToasts = 20;
  for (int id = 1; id <= kLotsOfToasts; ++id) {
    SCOPED_TRACE(StringPrintf("Creation loop: id=%d", id));
    EXPECT_TRUE(service_->ShowDesktopNotificationText(
        GURL("http://www.google.com"),
        GURL("/icon.png"), ASCIIToUTF16("Title"), ASCIIToUTF16("Text"),
        process_id, route_id,
        DesktopNotificationService::PageNotification, id));
  }
  MessageLoopForUI::current()->RunAllPending();

  // Build up an expected log of what should be happening.
  std::string expected_log;
  for (int i = 0; i < kLotsOfToasts; ++i) {
    expected_log.append("notification displayed\n");
  }

  EXPECT_EQ(kLotsOfToasts, balloon_collection_->count());
  EXPECT_EQ(expected_log, log_output_);

  // Cancel half of the notifications from the start
  int id;
  int cancelled = kLotsOfToasts / 2;
  for (id = 1;
       id <= cancelled;
       ++id) {
    SCOPED_TRACE(StringPrintf("Cancel half of notifications: id=%d", id));
    service_->CancelDesktopNotification(process_id, route_id, id);
    MessageLoopForUI::current()->RunAllPending();
    expected_log.append("notification closed by script\n");
    EXPECT_EQ(kLotsOfToasts - id,
              balloon_collection_->count());
    EXPECT_EQ(expected_log, log_output_);
  }

  // Now cancel the rest.  It should empty the balloon space.
  for (; id <= kLotsOfToasts; ++id) {
    SCOPED_TRACE(StringPrintf("Cancel loop: id=%d", id));
    service_->CancelDesktopNotification(process_id, route_id, id);
    expected_log.append("notification closed by script\n");
    MessageLoopForUI::current()->RunAllPending();
    EXPECT_EQ(expected_log, log_output_);
  }

  // Verify that the balloon collection is now empty.
  EXPECT_EQ(0, balloon_collection_->count());
}

TEST_F(DesktopNotificationsTest, TestEarlyDestruction) {
  // Create some toasts and then prematurely delete the notification service,
  // just to make sure nothing crashes/leaks.
  for (int id = 0; id <= 3; ++id) {
    SCOPED_TRACE(StringPrintf("Show Text loop: id=%d", id));

    EXPECT_TRUE(service_->ShowDesktopNotificationText(
        GURL("http://www.google.com"),
        GURL("/icon.png"), ASCIIToUTF16("Title"), ASCIIToUTF16("Text"),
        0, 0, DesktopNotificationService::PageNotification, id));
  }
  service_.reset(NULL);
}

TEST_F(DesktopNotificationsTest, TestUserInputEscaping) {
  // Create a test script with some HTML; assert that it doesn't get into the
  // data:// URL that's produced for the balloon.
  EXPECT_TRUE(service_->ShowDesktopNotificationText(
      GURL("http://www.google.com"),
      GURL("/icon.png"),
      ASCIIToUTF16(""),
      ASCIIToUTF16("this text is in italics"),
      0, 0, DesktopNotificationService::PageNotification, 1));

  MessageLoopForUI::current()->RunAllPending();
  EXPECT_EQ(1, balloon_collection_->count());
  Balloon* balloon = (*balloon_collection_->balloons().begin());
  GURL data_url = balloon->notification().content_url();
  EXPECT_EQ(std::string::npos, data_url.spec().find(""),
      ASCIIToUTF16("this text is in italics"),
      0, 0, DesktopNotificationService::PageNotification, 1));

  MessageLoopForUI::current()->RunAllPending();
  EXPECT_EQ(1, balloon_collection_->count());
  Balloon* balloon = (*balloon_collection_->balloons().begin());
  GURL data_url = balloon->notification().content_url();
  EXPECT_EQ(std::string::npos, data_url.spec().find("


<commit_before>// Copyright (c) 2010 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "base/scoped_temp_dir.h"
#include "base/scoped_vector.h"
#include "chrome/browser/search_engines/template_url.h"
#include "chrome/browser/search_engines/template_url_prepopulate_data.h"
#include "chrome/common/pref_names.h"
#include "chrome/test/testing_profile.h"
#include "testing/gtest/include/gtest/gtest.h"

typedef testing::Test TemplateURLPrepopulateDataTest;

// Verifies the set of prepopulate data doesn't contain entries with duplicate
// ids.
TEST_F(TemplateURLPrepopulateDataTest, UniqueIDs) {
  int ids[] = { 'A'<<8|'D', 'A'<<8|'E', 'A'<<8|'F', 'A'<<8|'G', 'A'<<8|'I',
                'A'<<8|'L', 'A'<<8|'M', 'A'<<8|'N', 'A'<<8|'O', 'A'<<8|'Q',
                'A'<<8|'R', 'A'<<8|'S', 'A'<<8|'T', 'A'<<8|'U', 'A'<<8|'W',
                'A'<<8|'X', 'A'<<8|'Z', 'B'<<8|'A', 'B'<<8|'B', 'B'<<8|'D',
                'B'<<8|'E', 'B'<<8|'F', 'B'<<8|'G', 'B'<<8|'H', 'B'<<8|'I',
                'B'<<8|'J', 'B'<<8|'M', 'B'<<8|'N', 'B'<<8|'O', 'B'<<8|'R',
                'B'<<8|'S', 'B'<<8|'T', 'B'<<8|'V', 'B'<<8|'W', 'B'<<8|'Y',
                'B'<<8|'Z', 'C'<<8|'A', 'C'<<8|'C', 'C'<<8|'D', 'C'<<8|'F',
                'C'<<8|'G', 'C'<<8|'H', 'C'<<8|'I', 'C'<<8|'K', 'C'<<8|'L',
                'C'<<8|'M', 'C'<<8|'N', 'C'<<8|'O', 'C'<<8|'R', 'C'<<8|'U',
                'C'<<8|'V', 'C'<<8|'X', 'C'<<8|'Y', 'C'<<8|'Z', 'D'<<8|'E',
                'D'<<8|'J', 'D'<<8|'K', 'D'<<8|'M', 'D'<<8|'O', 'D'<<8|'Z',
                'E'<<8|'C', 'E'<<8|'E', 'E'<<8|'G', 'E'<<8|'R', 'E'<<8|'S',
                'E'<<8|'T', 'F'<<8|'I', 'F'<<8|'J', 'F'<<8|'K', 'F'<<8|'M',
                'F'<<8|'O', 'F'<<8|'R', 'G'<<8|'A', 'G'<<8|'B', 'G'<<8|'D',
                'G'<<8|'E', 'G'<<8|'F', 'G'<<8|'G', 'G'<<8|'H', 'G'<<8|'I',
                'G'<<8|'L', 'G'<<8|'M', 'G'<<8|'N', 'G'<<8|'P', 'G'<<8|'Q',
                'G'<<8|'R', 'G'<<8|'S', 'G'<<8|'T', 'G'<<8|'U', 'G'<<8|'W',
                'G'<<8|'Y', 'H'<<8|'K', 'H'<<8|'M', 'H'<<8|'N', 'H'<<8|'R',
                'H'<<8|'T', 'H'<<8|'U', 'I'<<8|'D', 'I'<<8|'E', 'I'<<8|'L',
                'I'<<8|'M', 'I'<<8|'N', 'I'<<8|'O', 'I'<<8|'P', 'I'<<8|'Q',
                'I'<<8|'R', 'I'<<8|'S', 'I'<<8|'T', 'J'<<8|'E', 'J'<<8|'M',
                'J'<<8|'O', 'J'<<8|'P', 'K'<<8|'E', 'K'<<8|'G', 'K'<<8|'H',
                'K'<<8|'I', 'K'<<8|'M', 'K'<<8|'N', 'K'<<8|'P', 'K'<<8|'R',
                'K'<<8|'W', 'K'<<8|'Y', 'K'<<8|'Z', 'L'<<8|'A', 'L'<<8|'B',
                'L'<<8|'C', 'L'<<8|'I', 'L'<<8|'K', 'L'<<8|'R', 'L'<<8|'S',
                'L'<<8|'T', 'L'<<8|'U', 'L'<<8|'V', 'L'<<8|'Y', 'M'<<8|'A',
                'M'<<8|'C', 'M'<<8|'D', 'M'<<8|'E', 'M'<<8|'G', 'M'<<8|'H',
                'M'<<8|'K', 'M'<<8|'L', 'M'<<8|'M', 'M'<<8|'N', 'M'<<8|'O',
                'M'<<8|'P', 'M'<<8|'Q', 'M'<<8|'R', 'M'<<8|'S', 'M'<<8|'T',
                'M'<<8|'U', 'M'<<8|'V', 'M'<<8|'W', 'M'<<8|'X', 'M'<<8|'Y',
                'M'<<8|'Z', 'N'<<8|'A', 'N'<<8|'C', 'N'<<8|'E', 'N'<<8|'F',
                'N'<<8|'G', 'N'<<8|'I', 'N'<<8|'L', 'N'<<8|'O', 'N'<<8|'P',
                'N'<<8|'R', 'N'<<8|'U', 'N'<<8|'Z', 'O'<<8|'M', 'P'<<8|'A',
                'P'<<8|'E', 'P'<<8|'F', 'P'<<8|'G', 'P'<<8|'H', 'P'<<8|'K',
                'P'<<8|'L', 'P'<<8|'M', 'P'<<8|'N', 'P'<<8|'R', 'P'<<8|'S',
                'P'<<8|'T', 'P'<<8|'W', 'P'<<8|'Y', 'Q'<<8|'A', 'R'<<8|'E',
                'R'<<8|'O', 'R'<<8|'S', 'R'<<8|'U', 'R'<<8|'W', 'S'<<8|'A',
                'S'<<8|'B', 'S'<<8|'C', 'S'<<8|'D', 'S'<<8|'E', 'S'<<8|'G',
                'S'<<8|'H', 'S'<<8|'I', 'S'<<8|'J', 'S'<<8|'K', 'S'<<8|'L',
                'S'<<8|'M', 'S'<<8|'N', 'S'<<8|'O', 'S'<<8|'R', 'S'<<8|'T',
                'S'<<8|'V', 'S'<<8|'Y', 'S'<<8|'Z', 'T'<<8|'C', 'T'<<8|'D',
                'T'<<8|'F', 'T'<<8|'G', 'T'<<8|'H', 'T'<<8|'J', 'T'<<8|'K',
                'T'<<8|'L', 'T'<<8|'M', 'T'<<8|'N', 'T'<<8|'O', 'T'<<8|'R',
                'T'<<8|'T', 'T'<<8|'V', 'T'<<8|'W', 'T'<<8|'Z', 'U'<<8|'A',
                'U'<<8|'G', 'U'<<8|'M', 'U'<<8|'S', 'U'<<8|'Y', 'U'<<8|'Z',
                'V'<<8|'A', 'V'<<8|'C', 'V'<<8|'E', 'V'<<8|'G', 'V'<<8|'I',
                'V'<<8|'N', 'V'<<8|'U', 'W'<<8|'F', 'W'<<8|'S', 'Y'<<8|'E',
                'Y'<<8|'T', 'Z'<<8|'A', 'Z'<<8|'M', 'Z'<<8|'W', -1 };
  TestingProfile profile;
  for (size_t i = 0; i < arraysize(ids); ++i) {
    profile.GetPrefs()->SetInteger(prefs::kCountryIDAtInstall, ids[i]);
    ScopedVector<TemplateURL> urls;
    size_t url_count;
    TemplateURLPrepopulateData::GetPrepopulatedEngines(
        profile.GetPrefs(), &(urls.get()), &url_count);
    std::set<int> unique_ids;
    for (size_t turl_i = 0; turl_i < urls.size(); ++turl_i) {
      ASSERT_TRUE(unique_ids.find(urls[turl_i]->prepopulate_id()) ==
                  unique_ids.end());
      unique_ids.insert(urls[turl_i]->prepopulate_id());
    }
  }
}

// Verifies that default search providers from the preferences file
// override the built-in ones.
TEST_F(TemplateURLPrepopulateDataTest, ProvidersFromPrefs) {
  const char pref_data[] =
      "{ "
      "  \"search_provider_overrides_version\":1,"
      "  \"search_provider_overrides\": ["
      "     { \"name\":\"foo\","
      "       \"keyword\":\"fook\","
      "       \"search_url\":\"http://foo.com/s?q={searchTerms}\","
      "       \"favicon_url\":\"http://foi.com/favicon.ico\","
      "       \"suggest_url\":\"\","
      "       \"encoding\":\"UTF-8\","
      "       \"id\":1001"
      "     }"
      "   ]"
      "}";

  ScopedTempDir temp_dir;
  ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
  FilePath preferences_file = temp_dir.path().AppendASCII("Preferences");
  file_util::WriteFile(preferences_file, pref_data, sizeof(pref_data));

  scoped_ptr<PrefService> prefs(
      PrefService::CreateUserPrefService(preferences_file));
  TemplateURLPrepopulateData::RegisterUserPrefs(prefs.get());

  int version = TemplateURLPrepopulateData::GetDataVersion(prefs.get());
  EXPECT_EQ(1, version);

  std::vector<TemplateURL*> t_urls;
  size_t default_index;
  TemplateURLPrepopulateData::GetPrepopulatedEngines(
      prefs.get(), &t_urls, &default_index);

  ASSERT_EQ(1u, t_urls.size());
  EXPECT_EQ(L"foo", t_urls[0]->short_name());
  EXPECT_EQ(L"fook", t_urls[0]->keyword());
  EXPECT_EQ("foo.com", t_urls[0]->url()->GetHost());
  EXPECT_EQ("foi.com", t_urls[0]->GetFavIconURL().host());
  EXPECT_EQ(1u, t_urls[0]->input_encodings().size());
  EXPECT_EQ(1001, t_urls[0]->prepopulate_id());
}
<commit_msg>Fix memory leak in unit test<commit_after>// Copyright (c) 2010 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "base/scoped_temp_dir.h"
#include "base/scoped_vector.h"
#include "chrome/browser/search_engines/template_url.h"
#include "chrome/browser/search_engines/template_url_prepopulate_data.h"
#include "chrome/common/pref_names.h"
#include "chrome/test/testing_profile.h"
#include "testing/gtest/include/gtest/gtest.h"

typedef testing::Test TemplateURLPrepopulateDataTest;

// Verifies the set of prepopulate data doesn't contain entries with duplicate
// ids.
TEST_F(TemplateURLPrepopulateDataTest, UniqueIDs) {
  int ids[] = { 'A'<<8|'D', 'A'<<8|'E', 'A'<<8|'F', 'A'<<8|'G', 'A'<<8|'I',
                'A'<<8|'L', 'A'<<8|'M', 'A'<<8|'N', 'A'<<8|'O', 'A'<<8|'Q',
                'A'<<8|'R', 'A'<<8|'S', 'A'<<8|'T', 'A'<<8|'U', 'A'<<8|'W',
                'A'<<8|'X', 'A'<<8|'Z', 'B'<<8|'A', 'B'<<8|'B', 'B'<<8|'D',
                'B'<<8|'E', 'B'<<8|'F', 'B'<<8|'G', 'B'<<8|'H', 'B'<<8|'I',
                'B'<<8|'J', 'B'<<8|'M', 'B'<<8|'N', 'B'<<8|'O', 'B'<<8|'R',
                'B'<<8|'S', 'B'<<8|'T', 'B'<<8|'V', 'B'<<8|'W', 'B'<<8|'Y',
                'B'<<8|'Z', 'C'<<8|'A', 'C'<<8|'C', 'C'<<8|'D', 'C'<<8|'F',
                'C'<<8|'G', 'C'<<8|'H', 'C'<<8|'I', 'C'<<8|'K', 'C'<<8|'L',
                'C'<<8|'M', 'C'<<8|'N', 'C'<<8|'O', 'C'<<8|'R', 'C'<<8|'U',
                'C'<<8|'V', 'C'<<8|'X', 'C'<<8|'Y', 'C'<<8|'Z', 'D'<<8|'E',
                'D'<<8|'J', 'D'<<8|'K', 'D'<<8|'M', 'D'<<8|'O', 'D'<<8|'Z',
                'E'<<8|'C', 'E'<<8|'E', 'E'<<8|'G', 'E'<<8|'R', 'E'<<8|'S',
                'E'<<8|'T', 'F'<<8|'I', 'F'<<8|'J', 'F'<<8|'K', 'F'<<8|'M',
                'F'<<8|'O', 'F'<<8|'R', 'G'<<8|'A', 'G'<<8|'B', 'G'<<8|'D',
                'G'<<8|'E', 'G'<<8|'F', 'G'<<8|'G', 'G'<<8|'H', 'G'<<8|'I',
                'G'<<8|'L', 'G'<<8|'M', 'G'<<8|'N', 'G'<<8|'P', 'G'<<8|'Q',
                'G'<<8|'R', 'G'<<8|'S', 'G'<<8|'T', 'G'<<8|'U', 'G'<<8|'W',
                'G'<<8|'Y', 'H'<<8|'K', 'H'<<8|'M', 'H'<<8|'N', 'H'<<8|'R',
                'H'<<8|'T', 'H'<<8|'U', 'I'<<8|'D', 'I'<<8|'E', 'I'<<8|'L',
                'I'<<8|'M', 'I'<<8|'N', 'I'<<8|'O', 'I'<<8|'P', 'I'<<8|'Q',
                'I'<<8|'R', 'I'<<8|'S', 'I'<<8|'T', 'J'<<8|'E', 'J'<<8|'M',
                'J'<<8|'O', 'J'<<8|'P', 'K'<<8|'E', 'K'<<8|'G', 'K'<<8|'H',
                'K'<<8|'I', 'K'<<8|'M', 'K'<<8|'N', 'K'<<8|'P', 'K'<<8|'R',
                'K'<<8|'W', 'K'<<8|'Y', 'K'<<8|'Z', 'L'<<8|'A', 'L'<<8|'B',
                'L'<<8|'C', 'L'<<8|'I', 'L'<<8|'K', 'L'<<8|'R', 'L'<<8|'S',
                'L'<<8|'T', 'L'<<8|'U', 'L'<<8|'V', 'L'<<8|'Y', 'M'<<8|'A',
                'M'<<8|'C', 'M'<<8|'D', 'M'<<8|'E', 'M'<<8|'G', 'M'<<8|'H',
                'M'<<8|'K', 'M'<<8|'L', 'M'<<8|'M', 'M'<<8|'N', 'M'<<8|'O',
                'M'<<8|'P', 'M'<<8|'Q', 'M'<<8|'R', 'M'<<8|'S', 'M'<<8|'T',
                'M'<<8|'U', 'M'<<8|'V', 'M'<<8|'W', 'M'<<8|'X', 'M'<<8|'Y',
                'M'<<8|'Z', 'N'<<8|'A', 'N'<<8|'C', 'N'<<8|'E', 'N'<<8|'F',
                'N'<<8|'G', 'N'<<8|'I', 'N'<<8|'L', 'N'<<8|'O', 'N'<<8|'P',
                'N'<<8|'R', 'N'<<8|'U', 'N'<<8|'Z', 'O'<<8|'M', 'P'<<8|'A',
                'P'<<8|'E', 'P'<<8|'F', 'P'<<8|'G', 'P'<<8|'H', 'P'<<8|'K',
                'P'<<8|'L', 'P'<<8|'M', 'P'<<8|'N', 'P'<<8|'R', 'P'<<8|'S',
                'P'<<8|'T', 'P'<<8|'W', 'P'<<8|'Y', 'Q'<<8|'A', 'R'<<8|'E',
                'R'<<8|'O', 'R'<<8|'S', 'R'<<8|'U', 'R'<<8|'W', 'S'<<8|'A',
                'S'<<8|'B', 'S'<<8|'C', 'S'<<8|'D', 'S'<<8|'E', 'S'<<8|'G',
                'S'<<8|'H', 'S'<<8|'I', 'S'<<8|'J', 'S'<<8|'K', 'S'<<8|'L',
                'S'<<8|'M', 'S'<<8|'N', 'S'<<8|'O', 'S'<<8|'R', 'S'<<8|'T',
                'S'<<8|'V', 'S'<<8|'Y', 'S'<<8|'Z', 'T'<<8|'C', 'T'<<8|'D',
                'T'<<8|'F', 'T'<<8|'G', 'T'<<8|'H', 'T'<<8|'J', 'T'<<8|'K',
                'T'<<8|'L', 'T'<<8|'M', 'T'<<8|'N', 'T'<<8|'O', 'T'<<8|'R',
                'T'<<8|'T', 'T'<<8|'V', 'T'<<8|'W', 'T'<<8|'Z', 'U'<<8|'A',
                'U'<<8|'G', 'U'<<8|'M', 'U'<<8|'S', 'U'<<8|'Y', 'U'<<8|'Z',
                'V'<<8|'A', 'V'<<8|'C', 'V'<<8|'E', 'V'<<8|'G', 'V'<<8|'I',
                'V'<<8|'N', 'V'<<8|'U', 'W'<<8|'F', 'W'<<8|'S', 'Y'<<8|'E',
                'Y'<<8|'T', 'Z'<<8|'A', 'Z'<<8|'M', 'Z'<<8|'W', -1 };
  TestingProfile profile;
  for (size_t i = 0; i < arraysize(ids); ++i) {
    profile.GetPrefs()->SetInteger(prefs::kCountryIDAtInstall, ids[i]);
    ScopedVector<TemplateURL> urls;
    size_t default_index;
    TemplateURLPrepopulateData::GetPrepopulatedEngines(
        profile.GetPrefs(), &(urls.get()), &default_index);
    std::set<int> unique_ids;
    for (size_t turl_i = 0; turl_i < urls.size(); ++turl_i) {
      ASSERT_TRUE(unique_ids.find(urls[turl_i]->prepopulate_id()) ==
                  unique_ids.end());
      unique_ids.insert(urls[turl_i]->prepopulate_id());
    }
  }
}

// Verifies that default search providers from the preferences file
// override the built-in ones.
TEST_F(TemplateURLPrepopulateDataTest, ProvidersFromPrefs) {
  const char pref_data[] =
      "{ "
      "  \"search_provider_overrides_version\":1,"
      "  \"search_provider_overrides\": ["
      "     { \"name\":\"foo\","
      "       \"keyword\":\"fook\","
      "       \"search_url\":\"http://foo.com/s?q={searchTerms}\","
      "       \"favicon_url\":\"http://foi.com/favicon.ico\","
      "       \"suggest_url\":\"\","
      "       \"encoding\":\"UTF-8\","
      "       \"id\":1001"
      "     }"
      "   ]"
      "}";

  ScopedTempDir temp_dir;
  ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
  FilePath preferences_file = temp_dir.path().AppendASCII("Preferences");
  file_util::WriteFile(preferences_file, pref_data, sizeof(pref_data));

  scoped_ptr<PrefService> prefs(
      PrefService::CreateUserPrefService(preferences_file));
  TemplateURLPrepopulateData::RegisterUserPrefs(prefs.get());

  int version = TemplateURLPrepopulateData::GetDataVersion(prefs.get());
  EXPECT_EQ(1, version);

  ScopedVector<TemplateURL> t_urls;
  size_t default_index;
  TemplateURLPrepopulateData::GetPrepopulatedEngines(
      prefs.get(), &(t_urls.get()), &default_index);

  ASSERT_EQ(1u, t_urls.size());
  EXPECT_EQ(L"foo", t_urls[0]->short_name());
  EXPECT_EQ(L"fook", t_urls[0]->keyword());
  EXPECT_EQ("foo.com", t_urls[0]->url()->GetHost());
  EXPECT_EQ("foi.com", t_urls[0]->GetFavIconURL().host());
  EXPECT_EQ(1u, t_urls[0]->input_encodings().size());
  EXPECT_EQ(1001, t_urls[0]->prepopulate_id());
}
<|endoftext|>


<commit_before>/*
 *  Copyright (c) 2013 The WebRTC project authors. All Rights Reserved.
 *
 *  Use of this source code is governed by a BSD-style license
 *  that can be found in the LICENSE file in the root of the source
 *  tree. An additional intellectual property rights grant can be found
 *  in the file PATENTS.  All contributing project authors may
 *  be found in the AUTHORS file in the root of the source tree.
 */

#include "webrtc/video/send_statistics_proxy.h"

#include <algorithm>
#include <map>

#include "webrtc/base/checks.h"

#include "webrtc/base/logging.h"
#include "webrtc/system_wrappers/interface/critical_section_wrapper.h"
#include "webrtc/system_wrappers/interface/metrics.h"

namespace webrtc {

const int SendStatisticsProxy::kStatsTimeoutMs = 5000;

SendStatisticsProxy::SendStatisticsProxy(Clock* clock,
                                         const VideoSendStream::Config& config)
    : clock_(clock),
      config_(config),
      input_frame_rate_tracker_(100u, 10u),
      sent_frame_rate_tracker_(100u, 10u),
      last_sent_frame_timestamp_(0),
      max_sent_width_per_timestamp_(0),
      max_sent_height_per_timestamp_(0) {
}

SendStatisticsProxy::~SendStatisticsProxy() {
  UpdateHistograms();
}

void SendStatisticsProxy::UpdateHistograms() {
  int input_fps =
      static_cast<int>(input_frame_rate_tracker_.ComputeTotalRate());
  if (input_fps > 0)
    RTC_HISTOGRAM_COUNTS_100("WebRTC.Video.InputFramesPerSecond", input_fps);
  int sent_fps =
      static_cast<int>(sent_frame_rate_tracker_.ComputeTotalRate());
  if (sent_fps > 0)
    RTC_HISTOGRAM_COUNTS_100("WebRTC.Video.SentFramesPerSecond", sent_fps);

  const int kMinRequiredSamples = 200;
  int in_width = input_width_counter_.Avg(kMinRequiredSamples);
  int in_height = input_height_counter_.Avg(kMinRequiredSamples);
  if (in_width != -1) {
    RTC_HISTOGRAM_COUNTS_10000("WebRTC.Video.InputWidthInPixels", in_width);
    RTC_HISTOGRAM_COUNTS_10000("WebRTC.Video.InputHeightInPixels", in_height);
  }
  int sent_width = sent_width_counter_.Avg(kMinRequiredSamples);
  int sent_height = sent_height_counter_.Avg(kMinRequiredSamples);
  if (sent_width != -1) {
    RTC_HISTOGRAM_COUNTS_10000("WebRTC.Video.SentWidthInPixels", sent_width);
    RTC_HISTOGRAM_COUNTS_10000("WebRTC.Video.SentHeightInPixels", sent_height);
  }
  int encode_ms = encode_time_counter_.Avg(kMinRequiredSamples);
  if (encode_ms != -1)
    RTC_HISTOGRAM_COUNTS_1000("WebRTC.Video.EncodeTimeInMs", encode_ms);

  int key_frames_permille = key_frame_counter_.Permille(kMinRequiredSamples);
  if (key_frames_permille != -1) {
    RTC_HISTOGRAM_COUNTS_1000("WebRTC.Video.KeyFramesSentInPermille",
        key_frames_permille);
  }
  int quality_limited =
      quality_limited_frame_counter_.Percent(kMinRequiredSamples);
  if (quality_limited != -1) {
    RTC_HISTOGRAM_PERCENTAGE("WebRTC.Video.QualityLimitedResolutionInPercent",
                             quality_limited);
  }
  int downscales = quality_downscales_counter_.Avg(kMinRequiredSamples);
  if (downscales != -1) {
    RTC_HISTOGRAM_ENUMERATION("WebRTC.Video.QualityLimitedResolutionDownscales",
                              downscales, 20);
  }
  int bw_limited = bw_limited_frame_counter_.Percent(kMinRequiredSamples);
  if (bw_limited != -1) {
    RTC_HISTOGRAM_PERCENTAGE("WebRTC.Video.BandwidthLimitedResolutionInPercent",
        bw_limited);
  }
  int num_disabled = bw_resolutions_disabled_counter_.Avg(kMinRequiredSamples);
  if (num_disabled != -1) {
    RTC_HISTOGRAM_ENUMERATION(
        "WebRTC.Video.BandwidthLimitedResolutionsDisabled", num_disabled, 10);
  }
}

void SendStatisticsProxy::OnOutgoingRate(uint32_t framerate, uint32_t bitrate) {
  rtc::CritScope lock(&crit_);
  stats_.encode_frame_rate = framerate;
  stats_.media_bitrate_bps = bitrate;
}

void SendStatisticsProxy::CpuOveruseMetricsUpdated(
    const CpuOveruseMetrics& metrics) {
  rtc::CritScope lock(&crit_);
  // TODO(asapersson): Change to use OnEncodedFrame() for avg_encode_time_ms.
  stats_.avg_encode_time_ms = metrics.avg_encode_time_ms;
  stats_.encode_usage_percent = metrics.encode_usage_percent;
}

void SendStatisticsProxy::OnSuspendChange(bool is_suspended) {
  rtc::CritScope lock(&crit_);
  stats_.suspended = is_suspended;
}

VideoSendStream::Stats SendStatisticsProxy::GetStats() {
  rtc::CritScope lock(&crit_);
  PurgeOldStats();
  stats_.input_frame_rate =
      static_cast<int>(input_frame_rate_tracker_.ComputeRate());
  return stats_;
}

void SendStatisticsProxy::PurgeOldStats() {
  int64_t old_stats_ms = clock_->TimeInMilliseconds() - kStatsTimeoutMs;
  for (std::map<uint32_t, VideoSendStream::StreamStats>::iterator it =
           stats_.substreams.begin();
       it != stats_.substreams.end(); ++it) {
    uint32_t ssrc = it->first;
    if (update_times_[ssrc].resolution_update_ms <= old_stats_ms) {
      it->second.width = 0;
      it->second.height = 0;
    }
  }
}

VideoSendStream::StreamStats* SendStatisticsProxy::GetStatsEntry(
    uint32_t ssrc) {
  std::map<uint32_t, VideoSendStream::StreamStats>::iterator it =
      stats_.substreams.find(ssrc);
  if (it != stats_.substreams.end())
    return &it->second;

  if (std::find(config_.rtp.ssrcs.begin(), config_.rtp.ssrcs.end(), ssrc) ==
          config_.rtp.ssrcs.end() &&
      std::find(config_.rtp.rtx.ssrcs.begin(),
                config_.rtp.rtx.ssrcs.end(),
                ssrc) == config_.rtp.rtx.ssrcs.end()) {
    return nullptr;
  }

  return &stats_.substreams[ssrc];  // Insert new entry and return ptr.
}

void SendStatisticsProxy::OnInactiveSsrc(uint32_t ssrc) {
  rtc::CritScope lock(&crit_);
  VideoSendStream::StreamStats* stats = GetStatsEntry(ssrc);
  if (stats == nullptr)
    return;

  stats->total_bitrate_bps = 0;
  stats->retransmit_bitrate_bps = 0;
  stats->height = 0;
  stats->width = 0;
}

void SendStatisticsProxy::OnSetRates(uint32_t bitrate_bps, int framerate) {
  rtc::CritScope lock(&crit_);
  stats_.target_media_bitrate_bps = bitrate_bps;
}

void SendStatisticsProxy::OnSendEncodedImage(
    const EncodedImage& encoded_image,
    const RTPVideoHeader* rtp_video_header) {
  size_t simulcast_idx =
      rtp_video_header != nullptr ? rtp_video_header->simulcastIdx : 0;
  if (simulcast_idx >= config_.rtp.ssrcs.size()) {
    LOG(LS_ERROR) << "Encoded image outside simulcast range (" << simulcast_idx
                  << " >= " << config_.rtp.ssrcs.size() << ").";
    return;
  }
  uint32_t ssrc = config_.rtp.ssrcs[simulcast_idx];

  rtc::CritScope lock(&crit_);
  VideoSendStream::StreamStats* stats = GetStatsEntry(ssrc);
  if (stats == nullptr)
    return;

  stats->width = encoded_image._encodedWidth;
  stats->height = encoded_image._encodedHeight;
  update_times_[ssrc].resolution_update_ms = clock_->TimeInMilliseconds();

  key_frame_counter_.Add(encoded_image._frameType == kVideoFrameKey);

  if (encoded_image.adapt_reason_.quality_resolution_downscales != -1) {
    bool downscaled =
        encoded_image.adapt_reason_.quality_resolution_downscales > 0;
    quality_limited_frame_counter_.Add(downscaled);
    if (downscaled) {
      quality_downscales_counter_.Add(
          encoded_image.adapt_reason_.quality_resolution_downscales);
    }
  }
  if (encoded_image.adapt_reason_.bw_resolutions_disabled != -1) {
    bool bw_limited = encoded_image.adapt_reason_.bw_resolutions_disabled > 0;
    bw_limited_frame_counter_.Add(bw_limited);
    if (bw_limited) {
      bw_resolutions_disabled_counter_.Add(
         encoded_image.adapt_reason_.bw_resolutions_disabled);
    }
  }

  // TODO(asapersson): This is incorrect if simulcast layers are encoded on
  // different threads and there is no guarantee that one frame of all layers
  // are encoded before the next start.
  if (last_sent_frame_timestamp_ > 0 &&
      encoded_image._timeStamp != last_sent_frame_timestamp_) {
    sent_frame_rate_tracker_.AddSamples(1);
    sent_width_counter_.Add(max_sent_width_per_timestamp_);
    sent_height_counter_.Add(max_sent_height_per_timestamp_);
    max_sent_width_per_timestamp_ = 0;
    max_sent_height_per_timestamp_ = 0;
  }
  last_sent_frame_timestamp_ = encoded_image._timeStamp;
  max_sent_width_per_timestamp_ =
      std::max(max_sent_width_per_timestamp_,
               static_cast<int>(encoded_image._encodedWidth));
  max_sent_height_per_timestamp_ =
      std::max(max_sent_height_per_timestamp_,
               static_cast<int>(encoded_image._encodedHeight));
}

void SendStatisticsProxy::OnIncomingFrame(int width, int height) {
  rtc::CritScope lock(&crit_);
  input_frame_rate_tracker_.AddSamples(1);
  input_width_counter_.Add(width);
  input_height_counter_.Add(height);
}

void SendStatisticsProxy::OnEncodedFrame(int encode_time_ms) {
  rtc::CritScope lock(&crit_);
  encode_time_counter_.Add(encode_time_ms);
}

void SendStatisticsProxy::RtcpPacketTypesCounterUpdated(
    uint32_t ssrc,
    const RtcpPacketTypeCounter& packet_counter) {
  rtc::CritScope lock(&crit_);
  VideoSendStream::StreamStats* stats = GetStatsEntry(ssrc);
  if (stats == nullptr)
    return;

  stats->rtcp_packet_type_counts = packet_counter;
}

void SendStatisticsProxy::StatisticsUpdated(const RtcpStatistics& statistics,
                                            uint32_t ssrc) {
  rtc::CritScope lock(&crit_);
  VideoSendStream::StreamStats* stats = GetStatsEntry(ssrc);
  if (stats == nullptr)
    return;

  stats->rtcp_stats = statistics;
}

void SendStatisticsProxy::CNameChanged(const char* cname, uint32_t ssrc) {
}

void SendStatisticsProxy::DataCountersUpdated(
    const StreamDataCounters& counters,
    uint32_t ssrc) {
  rtc::CritScope lock(&crit_);
  VideoSendStream::StreamStats* stats = GetStatsEntry(ssrc);
  RTC_DCHECK(stats != nullptr)
      << "DataCountersUpdated reported for unknown ssrc: " << ssrc;

  stats->rtp_stats = counters;
}

void SendStatisticsProxy::Notify(const BitrateStatistics& total_stats,
                                 const BitrateStatistics& retransmit_stats,
                                 uint32_t ssrc) {
  rtc::CritScope lock(&crit_);
  VideoSendStream::StreamStats* stats = GetStatsEntry(ssrc);
  if (stats == nullptr)
    return;

  stats->total_bitrate_bps = total_stats.bitrate_bps;
  stats->retransmit_bitrate_bps = retransmit_stats.bitrate_bps;
}

void SendStatisticsProxy::FrameCountUpdated(const FrameCounts& frame_counts,
                                            uint32_t ssrc) {
  rtc::CritScope lock(&crit_);
  VideoSendStream::StreamStats* stats = GetStatsEntry(ssrc);
  if (stats == nullptr)
    return;

  stats->frame_counts = frame_counts;
}

void SendStatisticsProxy::SendSideDelayUpdated(int avg_delay_ms,
                                               int max_delay_ms,
                                               uint32_t ssrc) {
  rtc::CritScope lock(&crit_);
  VideoSendStream::StreamStats* stats = GetStatsEntry(ssrc);
  if (stats == nullptr)
    return;
  stats->avg_delay_ms = avg_delay_ms;
  stats->max_delay_ms = max_delay_ms;
}

void SendStatisticsProxy::SampleCounter::Add(int sample) {
  sum += sample;
  ++num_samples;
}

int SendStatisticsProxy::SampleCounter::Avg(int min_required_samples) const {
  if (num_samples < min_required_samples || num_samples == 0)
    return -1;
  return (sum + (num_samples / 2)) / num_samples;
}

void SendStatisticsProxy::BoolSampleCounter::Add(bool sample) {
  if (sample)
    ++sum;
  ++num_samples;
}

int SendStatisticsProxy::BoolSampleCounter::Percent(
    int min_required_samples) const {
  return Fraction(min_required_samples, 100.0f);
}

int SendStatisticsProxy::BoolSampleCounter::Permille(
    int min_required_samples) const {
  return Fraction(min_required_samples, 1000.0f);
}

int SendStatisticsProxy::BoolSampleCounter::Fraction(
    int min_required_samples, float multiplier) const {
  if (num_samples < min_required_samples || num_samples == 0)
    return -1;
  return static_cast<int>((sum * multiplier / num_samples) + 0.5f);
}

}  // namespace webrtc
<commit_msg>Add stats for used video codec type for a sent video stream:<commit_after>/*
 *  Copyright (c) 2013 The WebRTC project authors. All Rights Reserved.
 *
 *  Use of this source code is governed by a BSD-style license
 *  that can be found in the LICENSE file in the root of the source
 *  tree. An additional intellectual property rights grant can be found
 *  in the file PATENTS.  All contributing project authors may
 *  be found in the AUTHORS file in the root of the source tree.
 */

#include "webrtc/video/send_statistics_proxy.h"

#include <algorithm>
#include <map>

#include "webrtc/base/checks.h"

#include "webrtc/base/logging.h"
#include "webrtc/system_wrappers/interface/critical_section_wrapper.h"
#include "webrtc/system_wrappers/interface/metrics.h"

namespace webrtc {
namespace {
// Used by histograms. Values of entries should not be changed.
enum HistogramCodecType {
  kVideoUnknown = 0,
  kVideoVp8 = 1,
  kVideoVp9 = 2,
  kVideoH264 = 3,
  kVideoMax = 64,
};

HistogramCodecType PayloadNameToHistogramCodecType(
    const std::string& payload_name) {
  if (payload_name == "VP8") {
    return kVideoVp8;
  } else if (payload_name == "VP9") {
    return kVideoVp9;
  } else if (payload_name == "H264") {
    return kVideoH264;
  } else {
    return kVideoUnknown;
  }
}

void UpdateCodecTypeHistogram(const std::string& payload_name) {
  RTC_HISTOGRAM_ENUMERATION("WebRTC.Video.Encoder.CodecType",
      PayloadNameToHistogramCodecType(payload_name), kVideoMax);
}
}  // namespace

const int SendStatisticsProxy::kStatsTimeoutMs = 5000;

SendStatisticsProxy::SendStatisticsProxy(Clock* clock,
                                         const VideoSendStream::Config& config)
    : clock_(clock),
      config_(config),
      input_frame_rate_tracker_(100u, 10u),
      sent_frame_rate_tracker_(100u, 10u),
      last_sent_frame_timestamp_(0),
      max_sent_width_per_timestamp_(0),
      max_sent_height_per_timestamp_(0) {
  UpdateCodecTypeHistogram(config_.encoder_settings.payload_name);
}

SendStatisticsProxy::~SendStatisticsProxy() {
  UpdateHistograms();
}

void SendStatisticsProxy::UpdateHistograms() {
  int input_fps =
      static_cast<int>(input_frame_rate_tracker_.ComputeTotalRate());
  if (input_fps > 0)
    RTC_HISTOGRAM_COUNTS_100("WebRTC.Video.InputFramesPerSecond", input_fps);
  int sent_fps =
      static_cast<int>(sent_frame_rate_tracker_.ComputeTotalRate());
  if (sent_fps > 0)
    RTC_HISTOGRAM_COUNTS_100("WebRTC.Video.SentFramesPerSecond", sent_fps);

  const int kMinRequiredSamples = 200;
  int in_width = input_width_counter_.Avg(kMinRequiredSamples);
  int in_height = input_height_counter_.Avg(kMinRequiredSamples);
  if (in_width != -1) {
    RTC_HISTOGRAM_COUNTS_10000("WebRTC.Video.InputWidthInPixels", in_width);
    RTC_HISTOGRAM_COUNTS_10000("WebRTC.Video.InputHeightInPixels", in_height);
  }
  int sent_width = sent_width_counter_.Avg(kMinRequiredSamples);
  int sent_height = sent_height_counter_.Avg(kMinRequiredSamples);
  if (sent_width != -1) {
    RTC_HISTOGRAM_COUNTS_10000("WebRTC.Video.SentWidthInPixels", sent_width);
    RTC_HISTOGRAM_COUNTS_10000("WebRTC.Video.SentHeightInPixels", sent_height);
  }
  int encode_ms = encode_time_counter_.Avg(kMinRequiredSamples);
  if (encode_ms != -1)
    RTC_HISTOGRAM_COUNTS_1000("WebRTC.Video.EncodeTimeInMs", encode_ms);

  int key_frames_permille = key_frame_counter_.Permille(kMinRequiredSamples);
  if (key_frames_permille != -1) {
    RTC_HISTOGRAM_COUNTS_1000("WebRTC.Video.KeyFramesSentInPermille",
        key_frames_permille);
  }
  int quality_limited =
      quality_limited_frame_counter_.Percent(kMinRequiredSamples);
  if (quality_limited != -1) {
    RTC_HISTOGRAM_PERCENTAGE("WebRTC.Video.QualityLimitedResolutionInPercent",
                             quality_limited);
  }
  int downscales = quality_downscales_counter_.Avg(kMinRequiredSamples);
  if (downscales != -1) {
    RTC_HISTOGRAM_ENUMERATION("WebRTC.Video.QualityLimitedResolutionDownscales",
                              downscales, 20);
  }
  int bw_limited = bw_limited_frame_counter_.Percent(kMinRequiredSamples);
  if (bw_limited != -1) {
    RTC_HISTOGRAM_PERCENTAGE("WebRTC.Video.BandwidthLimitedResolutionInPercent",
        bw_limited);
  }
  int num_disabled = bw_resolutions_disabled_counter_.Avg(kMinRequiredSamples);
  if (num_disabled != -1) {
    RTC_HISTOGRAM_ENUMERATION(
        "WebRTC.Video.BandwidthLimitedResolutionsDisabled", num_disabled, 10);
  }
}

void SendStatisticsProxy::OnOutgoingRate(uint32_t framerate, uint32_t bitrate) {
  rtc::CritScope lock(&crit_);
  stats_.encode_frame_rate = framerate;
  stats_.media_bitrate_bps = bitrate;
}

void SendStatisticsProxy::CpuOveruseMetricsUpdated(
    const CpuOveruseMetrics& metrics) {
  rtc::CritScope lock(&crit_);
  // TODO(asapersson): Change to use OnEncodedFrame() for avg_encode_time_ms.
  stats_.avg_encode_time_ms = metrics.avg_encode_time_ms;
  stats_.encode_usage_percent = metrics.encode_usage_percent;
}

void SendStatisticsProxy::OnSuspendChange(bool is_suspended) {
  rtc::CritScope lock(&crit_);
  stats_.suspended = is_suspended;
}

VideoSendStream::Stats SendStatisticsProxy::GetStats() {
  rtc::CritScope lock(&crit_);
  PurgeOldStats();
  stats_.input_frame_rate =
      static_cast<int>(input_frame_rate_tracker_.ComputeRate());
  return stats_;
}

void SendStatisticsProxy::PurgeOldStats() {
  int64_t old_stats_ms = clock_->TimeInMilliseconds() - kStatsTimeoutMs;
  for (std::map<uint32_t, VideoSendStream::StreamStats>::iterator it =
           stats_.substreams.begin();
       it != stats_.substreams.end(); ++it) {
    uint32_t ssrc = it->first;
    if (update_times_[ssrc].resolution_update_ms <= old_stats_ms) {
      it->second.width = 0;
      it->second.height = 0;
    }
  }
}

VideoSendStream::StreamStats* SendStatisticsProxy::GetStatsEntry(
    uint32_t ssrc) {
  std::map<uint32_t, VideoSendStream::StreamStats>::iterator it =
      stats_.substreams.find(ssrc);
  if (it != stats_.substreams.end())
    return &it->second;

  if (std::find(config_.rtp.ssrcs.begin(), config_.rtp.ssrcs.end(), ssrc) ==
          config_.rtp.ssrcs.end() &&
      std::find(config_.rtp.rtx.ssrcs.begin(),
                config_.rtp.rtx.ssrcs.end(),
                ssrc) == config_.rtp.rtx.ssrcs.end()) {
    return nullptr;
  }

  return &stats_.substreams[ssrc];  // Insert new entry and return ptr.
}

void SendStatisticsProxy::OnInactiveSsrc(uint32_t ssrc) {
  rtc::CritScope lock(&crit_);
  VideoSendStream::StreamStats* stats = GetStatsEntry(ssrc);
  if (stats == nullptr)
    return;

  stats->total_bitrate_bps = 0;
  stats->retransmit_bitrate_bps = 0;
  stats->height = 0;
  stats->width = 0;
}

void SendStatisticsProxy::OnSetRates(uint32_t bitrate_bps, int framerate) {
  rtc::CritScope lock(&crit_);
  stats_.target_media_bitrate_bps = bitrate_bps;
}

void SendStatisticsProxy::OnSendEncodedImage(
    const EncodedImage& encoded_image,
    const RTPVideoHeader* rtp_video_header) {
  size_t simulcast_idx =
      rtp_video_header != nullptr ? rtp_video_header->simulcastIdx : 0;
  if (simulcast_idx >= config_.rtp.ssrcs.size()) {
    LOG(LS_ERROR) << "Encoded image outside simulcast range (" << simulcast_idx
                  << " >= " << config_.rtp.ssrcs.size() << ").";
    return;
  }
  uint32_t ssrc = config_.rtp.ssrcs[simulcast_idx];

  rtc::CritScope lock(&crit_);
  VideoSendStream::StreamStats* stats = GetStatsEntry(ssrc);
  if (stats == nullptr)
    return;

  stats->width = encoded_image._encodedWidth;
  stats->height = encoded_image._encodedHeight;
  update_times_[ssrc].resolution_update_ms = clock_->TimeInMilliseconds();

  key_frame_counter_.Add(encoded_image._frameType == kVideoFrameKey);

  if (encoded_image.adapt_reason_.quality_resolution_downscales != -1) {
    bool downscaled =
        encoded_image.adapt_reason_.quality_resolution_downscales > 0;
    quality_limited_frame_counter_.Add(downscaled);
    if (downscaled) {
      quality_downscales_counter_.Add(
          encoded_image.adapt_reason_.quality_resolution_downscales);
    }
  }
  if (encoded_image.adapt_reason_.bw_resolutions_disabled != -1) {
    bool bw_limited = encoded_image.adapt_reason_.bw_resolutions_disabled > 0;
    bw_limited_frame_counter_.Add(bw_limited);
    if (bw_limited) {
      bw_resolutions_disabled_counter_.Add(
         encoded_image.adapt_reason_.bw_resolutions_disabled);
    }
  }

  // TODO(asapersson): This is incorrect if simulcast layers are encoded on
  // different threads and there is no guarantee that one frame of all layers
  // are encoded before the next start.
  if (last_sent_frame_timestamp_ > 0 &&
      encoded_image._timeStamp != last_sent_frame_timestamp_) {
    sent_frame_rate_tracker_.AddSamples(1);
    sent_width_counter_.Add(max_sent_width_per_timestamp_);
    sent_height_counter_.Add(max_sent_height_per_timestamp_);
    max_sent_width_per_timestamp_ = 0;
    max_sent_height_per_timestamp_ = 0;
  }
  last_sent_frame_timestamp_ = encoded_image._timeStamp;
  max_sent_width_per_timestamp_ =
      std::max(max_sent_width_per_timestamp_,
               static_cast<int>(encoded_image._encodedWidth));
  max_sent_height_per_timestamp_ =
      std::max(max_sent_height_per_timestamp_,
               static_cast<int>(encoded_image._encodedHeight));
}

void SendStatisticsProxy::OnIncomingFrame(int width, int height) {
  rtc::CritScope lock(&crit_);
  input_frame_rate_tracker_.AddSamples(1);
  input_width_counter_.Add(width);
  input_height_counter_.Add(height);
}

void SendStatisticsProxy::OnEncodedFrame(int encode_time_ms) {
  rtc::CritScope lock(&crit_);
  encode_time_counter_.Add(encode_time_ms);
}

void SendStatisticsProxy::RtcpPacketTypesCounterUpdated(
    uint32_t ssrc,
    const RtcpPacketTypeCounter& packet_counter) {
  rtc::CritScope lock(&crit_);
  VideoSendStream::StreamStats* stats = GetStatsEntry(ssrc);
  if (stats == nullptr)
    return;

  stats->rtcp_packet_type_counts = packet_counter;
}

void SendStatisticsProxy::StatisticsUpdated(const RtcpStatistics& statistics,
                                            uint32_t ssrc) {
  rtc::CritScope lock(&crit_);
  VideoSendStream::StreamStats* stats = GetStatsEntry(ssrc);
  if (stats == nullptr)
    return;

  stats->rtcp_stats = statistics;
}

void SendStatisticsProxy::CNameChanged(const char* cname, uint32_t ssrc) {
}

void SendStatisticsProxy::DataCountersUpdated(
    const StreamDataCounters& counters,
    uint32_t ssrc) {
  rtc::CritScope lock(&crit_);
  VideoSendStream::StreamStats* stats = GetStatsEntry(ssrc);
  RTC_DCHECK(stats != nullptr)
      << "DataCountersUpdated reported for unknown ssrc: " << ssrc;

  stats->rtp_stats = counters;
}

void SendStatisticsProxy::Notify(const BitrateStatistics& total_stats,
                                 const BitrateStatistics& retransmit_stats,
                                 uint32_t ssrc) {
  rtc::CritScope lock(&crit_);
  VideoSendStream::StreamStats* stats = GetStatsEntry(ssrc);
  if (stats == nullptr)
    return;

  stats->total_bitrate_bps = total_stats.bitrate_bps;
  stats->retransmit_bitrate_bps = retransmit_stats.bitrate_bps;
}

void SendStatisticsProxy::FrameCountUpdated(const FrameCounts& frame_counts,
                                            uint32_t ssrc) {
  rtc::CritScope lock(&crit_);
  VideoSendStream::StreamStats* stats = GetStatsEntry(ssrc);
  if (stats == nullptr)
    return;

  stats->frame_counts = frame_counts;
}

void SendStatisticsProxy::SendSideDelayUpdated(int avg_delay_ms,
                                               int max_delay_ms,
                                               uint32_t ssrc) {
  rtc::CritScope lock(&crit_);
  VideoSendStream::StreamStats* stats = GetStatsEntry(ssrc);
  if (stats == nullptr)
    return;
  stats->avg_delay_ms = avg_delay_ms;
  stats->max_delay_ms = max_delay_ms;
}

void SendStatisticsProxy::SampleCounter::Add(int sample) {
  sum += sample;
  ++num_samples;
}

int SendStatisticsProxy::SampleCounter::Avg(int min_required_samples) const {
  if (num_samples < min_required_samples || num_samples == 0)
    return -1;
  return (sum + (num_samples / 2)) / num_samples;
}

void SendStatisticsProxy::BoolSampleCounter::Add(bool sample) {
  if (sample)
    ++sum;
  ++num_samples;
}

int SendStatisticsProxy::BoolSampleCounter::Percent(
    int min_required_samples) const {
  return Fraction(min_required_samples, 100.0f);
}

int SendStatisticsProxy::BoolSampleCounter::Permille(
    int min_required_samples) const {
  return Fraction(min_required_samples, 1000.0f);
}

int SendStatisticsProxy::BoolSampleCounter::Fraction(
    int min_required_samples, float multiplier) const {
  if (num_samples < min_required_samples || num_samples == 0)
    return -1;
  return static_cast<int>((sum * multiplier / num_samples) + 0.5f);
}

}  // namespace webrtc
<|endoftext|>


<commit_before><commit_msg>Destroy HidServiceLinux::FileThreadHelper before DeviceManagerLinux.<commit_after><|endoftext|>


<commit_before>//==============================================================================
// SingleCellSimulation plugin
//==============================================================================

#include "singlecellsimulationplugin.h"
#include "cellmlfilemanager.h"

//==============================================================================

#include <QFileInfo>
#include <QMainWindow>
#include <QPen>
#include <QTime>
#include <QVector>

//==============================================================================

#include "qwt_plot.h"
#include "qwt_plot_grid.h"
#include "qwt_plot_curve.h"

//==============================================================================

#include "llvm/Module.h"
#include "llvm/ExecutionEngine/ExecutionEngine.h"

//==============================================================================

namespace OpenCOR {
namespace SingleCellSimulation {

//==============================================================================

PLUGININFO_FUNC SingleCellSimulationPluginInfo()
{
    Descriptions descriptions;

    descriptions.insert("en", "A plugin to run single cell simulations");
    descriptions.insert("fr", "Une extension pour excuter des simulations unicellulaires");

    return PluginInfo(PluginInfo::V001,
                      PluginInfo::Gui,
                      PluginInfo::Simulation,
                      true,
                      QStringList() << "CoreSimulation" << "CellMLSupport" << "Qwt",
                      descriptions);
}

//==============================================================================

Q_EXPORT_PLUGIN2(SingleCellSimulation, SingleCellSimulationPlugin)

//==============================================================================

SingleCellSimulationPlugin::SingleCellSimulationPlugin()
{
    // Set our settings

    mGuiSettings->addView(GuiViewSettings::Simulation, 0);
}

//==============================================================================

void SingleCellSimulationPlugin::initialize()
{
    // Create our simulation view widget

    mSimulationView = new QwtPlot(mMainWindow);

    // Hide our simulation view widget since it may not initially be shown in
    // our central widget

    mSimulationView->setVisible(false);

    // Customise our simulation view widget

    mSimulationView->setCanvasBackground(Qt::white);

    // Remove the canvas' border as it otherwise looks odd, not to say ugly,
    // with one

    mSimulationView->setCanvasLineWidth(0);

    // Add a grid to our simulation view widget

    QwtPlotGrid *grid = new QwtPlotGrid;

    grid->setMajPen(QPen(Qt::gray, 0, Qt::DotLine));

    grid->attach(mSimulationView);
}

//==============================================================================

void SingleCellSimulationPlugin::finalize()
{
    // Delete some internal objects

    resetCurves();
}
//==============================================================================

void SingleCellSimulationPlugin::resetCurves()
{
    // Remove any existing curve

    foreach (QwtPlotCurve *curve, mCurves) {
        curve->detach();

        delete curve;
    }

    mCurves.clear();

    // Refresh the view

    mSimulationView->replot();
}

//==============================================================================

QWidget * SingleCellSimulationPlugin::viewWidget(const QString &pFileName,
                                                 const int &)
{
    // Check that we are dealing with a CellML file and, if so, return our
    // generic simulation view widget

    CellMLSupport::CellmlFile *cellmlFile = CellMLSupport::CellmlFileManager::instance()->cellmlFile(pFileName);

    if (!cellmlFile)
        // We are not dealing with a CellML file, so...

        return 0;

//--- TESTING --- BEGIN ---

    qDebug("=======================================");
    qDebug("%s:", qPrintable(pFileName));

    // Check the file's validity

    if (cellmlFile->isValid()) {
        // The file is valid, but let's see whether warnings were generated

        int warningsCount = cellmlFile->issues().count();

        if (warningsCount)
            qDebug(" - The file was properly loaded:");
        else
            qDebug(" - The file was properly loaded.");
    } else {
        qDebug(" - The file was NOT properly loaded:");
    }

    // Output any warnings/errors that were generated

    foreach (const CellMLSupport::CellmlFileIssue &issue, cellmlFile->issues()) {
        QString type = QString((issue.type() == CellMLSupport::CellmlFileIssue::Error)?"Error":"Warning");
        QString message = issue.formattedMessage();
        uint32_t line = issue.line();
        uint32_t column = issue.column();
        QString importedFile = issue.importedFile();

        if (line && column) {
            if (importedFile.isEmpty())
                qDebug("    [%s at line %s column %s] %s", qPrintable(type),
                                                           qPrintable(QString::number(issue.line())),
                                                           qPrintable(QString::number(issue.column())),
                                                           qPrintable(message));
            else
                qDebug("    [%s at line %s column %s from imported file %s] %s", qPrintable(type),
                                                                                qPrintable(QString::number(issue.line())),
                                                                                qPrintable(QString::number(issue.column())),
                                                                                qPrintable(importedFile),
                                                                                qPrintable(message));
        } else {
            if (importedFile.isEmpty())
                qDebug("    [%s] %s", qPrintable(type),
                                      qPrintable(message));
            else
                qDebug("    [%s from imported file %s] %s", qPrintable(type),
                                                            qPrintable(importedFile),
                                                            qPrintable(message));
        }
    }

    // Get a runtime for the file

    CellMLSupport::CellmlFileRuntime *cellmlFileRuntime = cellmlFile->runtime();

    if (cellmlFileRuntime->isValid()) {
        qDebug(" - The file's runtime was properly generated.");
        qDebug("    [Information] Model type = %s", (cellmlFileRuntime->modelType() == CellMLSupport::CellmlFileRuntime::Ode)?"ODE":"DAE");
    } else {
        qDebug(" - The file's runtime was NOT properly generated:");

        foreach (const CellMLSupport::CellmlFileIssue &issue,
                 cellmlFileRuntime->issues())
            qDebug("    [%s] %s",
                   (issue.type() == CellMLSupport::CellmlFileIssue::Error)?"Error":"Warning",
                   qPrintable(issue.formattedMessage()));
    }

    // Remove any existing curve

    resetCurves();

    // Compute the model, if supported

    enum Model {
        Unknown,
        VanDerPol1928,
        Hodgkin1952,
        Noble1962,
        Noble1984,
        Noble1991,
        Noble1998,
        Zhang2000,
        Mitchell2003
    } model;

    QString fileBaseName = QFileInfo(pFileName).baseName();

    if (!fileBaseName.compare("van_der_pol_model_1928"))
        model = VanDerPol1928;
    else if (   !fileBaseName.compare("hodgkin_huxley_squid_axon_model_1952")
             || !fileBaseName.compare("hodgkin_huxley_squid_axon_model_1952_modified")
             || !fileBaseName.compare("hodgkin_huxley_squid_axon_model_1952_original"))
        model = Hodgkin1952;
    else if (!fileBaseName.compare("noble_model_1962"))
        model = Noble1962;
    else if (!fileBaseName.compare("noble_noble_SAN_model_1984"))
        model = Noble1984;
    else if (!fileBaseName.compare("noble_model_1991"))
        model = Noble1991;
    else if (!fileBaseName.compare("noble_model_1998"))
        model = Noble1998;
    else if (   !fileBaseName.compare("zhang_SAN_model_2000_0D_capable")
             || !fileBaseName.compare("zhang_SAN_model_2000_1D_capable")
             || !fileBaseName.compare("zhang_SAN_model_2000_all")
             || !fileBaseName.compare("zhang_SAN_model_2000_published"))
        model = Zhang2000;
    else if (!fileBaseName.compare("mitchell_schaeffer_2003"))
        model = Mitchell2003;
    else
        model = Unknown;

    if (cellmlFileRuntime->isValid() && (model != Unknown)) {
        typedef QVector<double> Doubles;

        int statesCount = cellmlFileRuntime->statesCount();

        Doubles xData;
        Doubles yData[statesCount];

        double voi = 0;   // ms
        double voiStep;   // ms
        double voiMax;    // ms
        double constants[cellmlFileRuntime->constantsCount()];
        double rates[cellmlFileRuntime->ratesCount()];
        double states[statesCount];
        double algebraic[cellmlFileRuntime->algebraicCount()];
        int voiCount = 0;
        int voiOutputCount;   // ms

        switch (model) {
        case Hodgkin1952:
            voiStep        = 0.01;   // ms
            voiMax         = 50;     // ms
            voiOutputCount = 10;

            break;
        case Noble1962:
        case Mitchell2003:
            voiStep        = 0.01;   // ms
            voiMax         = 1000;   // ms
            voiOutputCount = 100;

            break;
        case Noble1984:
        case Noble1991:
        case Noble1998:
        case Zhang2000:
            voiStep        = 0.00001;   // s
            voiMax         = 1;         // s
            voiOutputCount = 100;

            break;
        default:   // van der Pol 1928
            voiStep        = 0.01;   // s
            voiMax         = 10;     // s
            voiOutputCount = 1;
        }

        CellMLSupport::CellmlFileRuntimeOdeFunctions odeFunctions = cellmlFileRuntime->odeFunctions();

        QTime time;

        time.start();

        // Initialise the constants and compute the rates and variables

        odeFunctions.initializeConstants(constants, rates, states);
        odeFunctions.computeRates(voi, constants, rates, states, algebraic);
        odeFunctions.computeVariables(voi, constants, rates, states, algebraic);

        do {
            // Output the current data, if needed

            if(voiCount % voiOutputCount == 0) {
                xData.append(voi);

                for (int i = 0; i < statesCount; ++i)
                    yData[i].append(states[i]);
            }

            // Compute the rates and variables

            odeFunctions.computeRates(voi, constants, rates, states, algebraic);
            odeFunctions.computeVariables(voi, constants, rates, states, algebraic);

            // Go to the next voiStep and integrate the states

            voi = ++voiCount*voiStep;

            for (int i = 0; i < statesCount; ++i)
                states[i] += voiStep*rates[i];
        } while (voi < voiMax);

        xData.append(voi);

        for (int i = 0; i < statesCount; ++i)
            yData[i].append(states[i]);

        qDebug(" - Simulation time: %s s", qPrintable(QString::number(0.001*time.elapsed(), 'g', 3)));

        // Add some curves to our plotting area

        for (int i = 0, iMax = (model == VanDerPol1928)?statesCount:1; i < iMax; ++i) {
            QwtPlotCurve *curve = new QwtPlotCurve();

            curve->setRenderHint(QwtPlotItem::RenderAntialiased);
            curve->setPen(QPen(i%2?Qt::darkBlue:Qt::darkRed));

            curve->setSamples(xData, yData[i]);

            curve->attach(mSimulationView);

            // Keep track of the curve

            mCurves.append(curve);
        }

        // Make sure that the view is up-to-date

        mSimulationView->replot();
    }

//--- TESTING --- END ---

    // The expected file extension, so return our generic simulation view
    // widget

    return mSimulationView;
}

//==============================================================================

QString SingleCellSimulationPlugin::viewName(const int &pViewIndex)
{
    // We have only one view, so return its name otherwise call the GuiInterface
    // implementation of viewName

    switch (pViewIndex) {
    case 0:
        return tr("Single Cell");
    default:
        return GuiInterface::viewName(pViewIndex);
    }
}

//==============================================================================

}   // namespace SingleCellSimulation
}   // namespace OpenCOR

//==============================================================================
// End of file
//==============================================================================
<commit_msg>Minor editing.<commit_after>//==============================================================================
// SingleCellSimulation plugin
//==============================================================================

#include "singlecellsimulationplugin.h"
#include "cellmlfilemanager.h"

//==============================================================================

#include <QFileInfo>
#include <QMainWindow>
#include <QPen>
#include <QTime>
#include <QVector>

//==============================================================================

#include "qwt_plot.h"
#include "qwt_plot_grid.h"
#include "qwt_plot_curve.h"

//==============================================================================

#include "llvm/Module.h"
#include "llvm/ExecutionEngine/ExecutionEngine.h"

//==============================================================================

namespace OpenCOR {
namespace SingleCellSimulation {

//==============================================================================

PLUGININFO_FUNC SingleCellSimulationPluginInfo()
{
    Descriptions descriptions;

    descriptions.insert("en", "A plugin to run single cell simulations");
    descriptions.insert("fr", "Une extension pour excuter des simulations unicellulaires");

    return PluginInfo(PluginInfo::V001,
                      PluginInfo::Gui,
                      PluginInfo::Simulation,
                      true,
                      QStringList() << "CoreSimulation" << "CellMLSupport" << "Qwt",
                      descriptions);
}

//==============================================================================

Q_EXPORT_PLUGIN2(SingleCellSimulation, SingleCellSimulationPlugin)

//==============================================================================

SingleCellSimulationPlugin::SingleCellSimulationPlugin()
{
    // Set our settings

    mGuiSettings->addView(GuiViewSettings::Simulation, 0);
}

//==============================================================================

void SingleCellSimulationPlugin::initialize()
{
    // Create our simulation view widget

    mSimulationView = new QwtPlot(mMainWindow);

    // Hide our simulation view widget since it may not initially be shown in
    // our central widget

    mSimulationView->setVisible(false);

    // Customise our simulation view widget

    mSimulationView->setCanvasBackground(Qt::white);

    // Remove the canvas' border as it otherwise looks odd, not to say ugly,
    // with one

    mSimulationView->setCanvasLineWidth(0);

    // Add a grid to our simulation view widget

    QwtPlotGrid *grid = new QwtPlotGrid;

    grid->setMajPen(QPen(Qt::gray, 0, Qt::DotLine));

    grid->attach(mSimulationView);
}

//==============================================================================

void SingleCellSimulationPlugin::finalize()
{
    // Delete some internal objects

    resetCurves();
}
//==============================================================================

void SingleCellSimulationPlugin::resetCurves()
{
    // Remove any existing curve

    foreach (QwtPlotCurve *curve, mCurves) {
        curve->detach();

        delete curve;
    }

    mCurves.clear();

    // Refresh the view

    mSimulationView->replot();
}

//==============================================================================

QWidget * SingleCellSimulationPlugin::viewWidget(const QString &pFileName,
                                                 const int &)
{
    // Check that we are dealing with a CellML file and, if so, return our
    // generic simulation view widget

    CellMLSupport::CellmlFile *cellmlFile = CellMLSupport::CellmlFileManager::instance()->cellmlFile(pFileName);

    if (!cellmlFile)
        // We are not dealing with a CellML file, so...

        return 0;

//--- TESTING --- BEGIN ---

    qDebug("=======================================");
    qDebug("%s:", qPrintable(pFileName));

    // Check the file's validity

    if (cellmlFile->isValid()) {
        // The file is valid, but let's see whether warnings were generated

        int warningsCount = cellmlFile->issues().count();

        if (warningsCount)
            qDebug(" - The file was properly loaded:");
        else
            qDebug(" - The file was properly loaded.");
    } else {
        qDebug(" - The file was NOT properly loaded:");
    }

    // Output any warnings/errors that were generated

    foreach (const CellMLSupport::CellmlFileIssue &issue, cellmlFile->issues()) {
        QString type = QString((issue.type() == CellMLSupport::CellmlFileIssue::Error)?"Error":"Warning");
        QString message = issue.formattedMessage();
        uint32_t line = issue.line();
        uint32_t column = issue.column();
        QString importedFile = issue.importedFile();

        if (line && column) {
            if (importedFile.isEmpty())
                qDebug("    [%s at line %s column %s] %s", qPrintable(type),
                                                           qPrintable(QString::number(issue.line())),
                                                           qPrintable(QString::number(issue.column())),
                                                           qPrintable(message));
            else
                qDebug("    [%s at line %s column %s from imported file %s] %s", qPrintable(type),
                                                                                qPrintable(QString::number(issue.line())),
                                                                                qPrintable(QString::number(issue.column())),
                                                                                qPrintable(importedFile),
                                                                                qPrintable(message));
        } else {
            if (importedFile.isEmpty())
                qDebug("    [%s] %s", qPrintable(type),
                                      qPrintable(message));
            else
                qDebug("    [%s from imported file %s] %s", qPrintable(type),
                                                            qPrintable(importedFile),
                                                            qPrintable(message));
        }
    }

    // Get a runtime for the file

    CellMLSupport::CellmlFileRuntime *cellmlFileRuntime = cellmlFile->runtime();

    if (cellmlFileRuntime->isValid()) {
        qDebug(" - The file's runtime was properly generated.");
        qDebug("    [Information] Model type = %s", (cellmlFileRuntime->modelType() == CellMLSupport::CellmlFileRuntime::Ode)?"ODE":"DAE");
    } else {
        qDebug(" - The file's runtime was NOT properly generated:");

        foreach (const CellMLSupport::CellmlFileIssue &issue,
                 cellmlFileRuntime->issues())
            qDebug("    [%s] %s",
                   (issue.type() == CellMLSupport::CellmlFileIssue::Error)?"Error":"Warning",
                   qPrintable(issue.formattedMessage()));
    }

    // Remove any existing curve

    resetCurves();

    // Compute the model, if supported

    enum Model {
        Unknown,
        VanDerPol1928,
        Hodgkin1952,
        Noble1962,
        Noble1984,
        Noble1991,
        Noble1998,
        Zhang2000,
        Mitchell2003
    } model;

    QString fileBaseName = QFileInfo(pFileName).baseName();

    if (!fileBaseName.compare("van_der_pol_model_1928"))
        model = VanDerPol1928;
    else if (   !fileBaseName.compare("hodgkin_huxley_squid_axon_model_1952")
             || !fileBaseName.compare("hodgkin_huxley_squid_axon_model_1952_modified")
             || !fileBaseName.compare("hodgkin_huxley_squid_axon_model_1952_original"))
        model = Hodgkin1952;
    else if (!fileBaseName.compare("noble_model_1962"))
        model = Noble1962;
    else if (!fileBaseName.compare("noble_noble_SAN_model_1984"))
        model = Noble1984;
    else if (!fileBaseName.compare("noble_model_1991"))
        model = Noble1991;
    else if (!fileBaseName.compare("noble_model_1998"))
        model = Noble1998;
    else if (   !fileBaseName.compare("zhang_SAN_model_2000_0D_capable")
             || !fileBaseName.compare("zhang_SAN_model_2000_1D_capable")
             || !fileBaseName.compare("zhang_SAN_model_2000_all")
             || !fileBaseName.compare("zhang_SAN_model_2000_published"))
        model = Zhang2000;
    else if (!fileBaseName.compare("mitchell_schaeffer_2003"))
        model = Mitchell2003;
    else
        model = Unknown;

    if (cellmlFileRuntime->isValid() && (model != Unknown)) {
        typedef QVector<double> Doubles;

        int statesCount = cellmlFileRuntime->statesCount();

        Doubles xData;
        Doubles yData[statesCount];

        double voi = 0;   // ms
        double voiStep;   // ms
        double voiMax;    // ms
        double constants[cellmlFileRuntime->constantsCount()];
        double rates[cellmlFileRuntime->ratesCount()];
        double states[statesCount];
        double algebraic[cellmlFileRuntime->algebraicCount()];
        int voiCount = 0;
        int voiOutputCount;   // ms

        switch (model) {
        case Hodgkin1952:
            voiStep        = 0.01;   // ms
            voiMax         = 50;     // ms
            voiOutputCount = 10;

            break;
        case Noble1962:
        case Mitchell2003:
            voiStep        = 0.01;   // ms
            voiMax         = 1000;   // ms
            voiOutputCount = 100;

            break;
        case Noble1984:
        case Noble1991:
        case Noble1998:
        case Zhang2000:
            voiStep        = 0.00001;   // s
            voiMax         = 1;         // s
            voiOutputCount = 100;

            break;
        default:   // van der Pol 1928
            voiStep        = 0.01;   // s
            voiMax         = 10;     // s
            voiOutputCount = 1;
        }

        CellMLSupport::CellmlFileRuntimeOdeFunctions odeFunctions = cellmlFileRuntime->odeFunctions();

        // Initialise the constants and compute the rates and variables

        QTime time;

        time.start();

        odeFunctions.initializeConstants(constants, rates, states);
        odeFunctions.computeRates(voi, constants, rates, states, algebraic);
        odeFunctions.computeVariables(voi, constants, rates, states, algebraic);

        do {
            // Output the current data, if needed

            if(voiCount % voiOutputCount == 0) {
                xData.append(voi);

                for (int i = 0; i < statesCount; ++i)
                    yData[i].append(states[i]);
            }

            // Compute the rates and variables

            odeFunctions.computeRates(voi, constants, rates, states, algebraic);
            odeFunctions.computeVariables(voi, constants, rates, states, algebraic);

            // Go to the next voiStep and integrate the states

            voi = ++voiCount*voiStep;

            for (int i = 0; i < statesCount; ++i)
                states[i] += voiStep*rates[i];
        } while (voi < voiMax);

        xData.append(voi);

        for (int i = 0; i < statesCount; ++i)
            yData[i].append(states[i]);

        qDebug(" - Simulation time: %s s", qPrintable(QString::number(0.001*time.elapsed(), 'g', 3)));

        // Add some curves to our plotting area

        for (int i = 0, iMax = (model == VanDerPol1928)?statesCount:1; i < iMax; ++i) {
            QwtPlotCurve *curve = new QwtPlotCurve();

            curve->setRenderHint(QwtPlotItem::RenderAntialiased);
            curve->setPen(QPen(i%2?Qt::darkBlue:Qt::darkRed));

            curve->setSamples(xData, yData[i]);

            curve->attach(mSimulationView);

            // Keep track of the curve

            mCurves.append(curve);
        }

        // Make sure that the view is up-to-date

        mSimulationView->replot();
    }

//--- TESTING --- END ---

    // The expected file extension, so return our generic simulation view
    // widget

    return mSimulationView;
}

//==============================================================================

QString SingleCellSimulationPlugin::viewName(const int &pViewIndex)
{
    // We have only one view, so return its name otherwise call the GuiInterface
    // implementation of viewName

    switch (pViewIndex) {
    case 0:
        return tr("Single Cell");
    default:
        return GuiInterface::viewName(pViewIndex);
    }
}

//==============================================================================

}   // namespace SingleCellSimulation
}   // namespace OpenCOR

//==============================================================================
// End of file
//==============================================================================
<|endoftext|>