Merge branch 'meteor-rebuild-server' of github.com:antobinary/bigbluebutton into meteor-rebuild-server

This commit is contained in:
Anton Georgiev 2014-06-12 17:19:48 +00:00
commit 41f6c5c3d5
1561 changed files with 115624 additions and 43988 deletions

1
.gitignore vendored
View File

@ -4,7 +4,6 @@ record-and-playback/playback-web/playback-web-0.1.war
record-and-playback/.project
push_to_git.py
*/.gradle
.gitignore
bbb-lti/.classpath
bbb-lti/.project
bbb-lti/bin

View File

@ -1,17 +1,18 @@
//usePlugin 'war'
usePlugin 'war'
apply plugin: 'war'
version = '0.8'
repositories {
mavenCentral()
}
task resolveDeps(dependsOn: configurations.default.buildArtifacts, type: Copy) {
task resolveDeps(type: Copy) {
into('lib')
from configurations.default
from configurations.default.allArtifacts*.file
from configurations.default.allArtifacts.file
}
war {
baseName = 'demo'
version = ''

View File

@ -1,7 +1,5 @@
#utf-8
#Wed Oct 10 08:34:02 PDT 2012
app.version=0.1.1
app.servlet.version=2.4
app.grails.version=1.1.1
plugins.hibernate=1.1.1
#Grails Metadata file
#Thu Mar 20 10:48:08 PDT 2014
app.grails.version=2.3.6
app.name=lti
app.version=0.1.2

View File

@ -0,0 +1,23 @@
/*
BigBlueButton open source conferencing system - http://www.bigbluebutton.org/
Copyright (c) 2012 BigBlueButton Inc. and by respective authors (see below).
This program is free software; you can redistribute it and/or modify it under the
terms of the GNU Lesser General Public License as published by the Free Software
Foundation; either version 3.0 of the License, or (at your option) any later
version.
BigBlueButton is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License along
with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
*/
modules = {
application {
resource url:'js/application.js'
}
}

View File

@ -15,13 +15,10 @@
You should have received a copy of the GNU Lesser General Public License along
with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
*/
class BootStrap {
def init = { servletContext ->
log.debug "Bootstrapping bbb-lti"
}
def destroy = {
}
}
def init = { servletContext ->
}
def destroy = {
}
}

View File

@ -0,0 +1,73 @@
/*
BigBlueButton open source conferencing system - http://www.bigbluebutton.org/
Copyright (c) 2012 BigBlueButton Inc. and by respective authors (see below).
This program is free software; you can redistribute it and/or modify it under the
terms of the GNU Lesser General Public License as published by the Free Software
Foundation; either version 3.0 of the License, or (at your option) any later
version.
BigBlueButton is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License along
with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
*/
grails.servlet.version = "3.0"
grails.project.class.dir = "target/classes"
grails.project.test.class.dir = "target/test-classes"
grails.project.test.reports.dir = "target/test-reports"
grails.project.work.dir = "target/work"
grails.project.target.level = 1.6
grails.project.source.level = 1.6
//grails.project.war.file = "target/${appName}-${appVersion}.war"
grails.project.fork = [
test: [maxMemory: 768, minMemory: 64, debug: false, maxPerm: 256, daemon:true],
run: [maxMemory: 768, minMemory: 64, debug: false, maxPerm: 256, forkReserve:false],
war: [maxMemory: 768, minMemory: 64, debug: false, maxPerm: 256, forkReserve:false],
console: [maxMemory: 768, minMemory: 64, debug: false, maxPerm: 256]
]
grails.project.dependency.resolver = "maven" // or ivy
grails.project.dependency.resolution = {
inherits("global") {
}
log "error"
checksums true
legacyResolve false
repositories {
inherits true
grailsPlugins()
grailsHome()
mavenLocal()
grailsCentral()
mavenCentral()
mavenRepo "http://snapshots.repository.codehaus.org"
mavenRepo "http://repository.codehaus.org"
mavenRepo "http://download.java.net/maven/2/"
mavenRepo "http://repository.jboss.com/maven2/"
//mavenRepo "https://raw.github.com/blindsidenetworks/oauth/mvn-repo/"
}
dependencies {
//runtime "commons-net:commons-net:3.0.1"
//runtime "net.oauth:oauth:1.0.1"
}
plugins {
// plugins for the build system only
build ":tomcat:7.0.50.1"
runtime ":database-migration:1.3.8"
runtime ":jquery:1.11.0"
runtime ":resources:1.2.1"
runtime ':twitter-bootstrap:3.1.1'
}
}

View File

@ -1,93 +1,139 @@
/*
BigBlueButton open source conferencing system - http://www.bigbluebutton.org/
Copyright (c) 2012 BigBlueButton Inc. and by respective authors (see below).
This program is free software; you can redistribute it and/or modify it under the
terms of the GNU Lesser General Public License as published by the Free Software
Foundation; either version 3.0 of the License, or (at your option) any later
version.
BigBlueButton is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License along
with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
*/
// locations to search for config files that get merged into the main config
// config files can either be Java properties files or ConfigSlurper scripts
// grails.config.locations = [ "classpath:${appName}-config.properties",
// "classpath:${appName}-config.groovy",
// "file:${userHome}/.grails/${appName}-config.properties",
// "file:${userHome}/.grails/${appName}-config.groovy"]
grails.config.locations = [ "classpath:lti.properties"]
// if(System.properties["${appName}.config.location"]) {
// grails.config.locations << "file:" + System.properties["${appName}.config.location"]
// }
grails.mime.file.extensions = true // enables the parsing of file extensions from URLs into the request format
grails.mime.use.accept.header = false
grails.mime.types = [ html: ['text/html','application/xhtml+xml'],
xml: ['text/xml', 'application/xml'],
text: 'text/plain',
js: 'text/javascript',
rss: 'application/rss+xml',
atom: 'application/atom+xml',
css: 'text/css',
csv: 'text/csv',
all: '*/*',
json: ['application/json','text/json'],
form: 'application/x-www-form-urlencoded',
multipartForm: 'multipart/form-data'
]
// The default codec used to encode data with ${}
grails.views.default.codec="none" // none, html, base64
grails.views.gsp.encoding="UTF-8"
grails.converters.encoding="UTF-8"
// enabled native2ascii conversion of i18n properties files
grails.enable.native2ascii = true
// set per-environment serverURL stem for creating absolute links
environments {
production {
grails.serverURL = "http://localhost:8080/${appName}"
}
development {
grails.serverURL = "http://localhost:8080/${appName}"
}
test {
grails.serverURL = "http://localhost:8080/${appName}"
}
}
// log4j configuration
log4j = {
appenders {
rollingFile name:"logfile", maxFileSize:1000000, file:"/var/log/bigbluebutton/bbb-lti.log", layout:pattern(conversionPattern: '%d{[dd.MM.yy HH:mm:ss.SSS]} %-5p %c %x - %m%n')
console name:'console', layout:pattern(conversionPattern: '%d{[dd.MM.yy HH:mm:ss.SSS]} %-5p %c %x - %m%n')
'null' name:'stacktrace'
}
debug logfile:"grails.app"
error 'org.codehaus.groovy.grails.web.servlet', // controllers
'org.codehaus.groovy.grails.web.pages', // GSP
'org.codehaus.groovy.grails.web.sitemesh', // layouts
'org.codehaus.groovy.grails.web.mapping.filter', // URL mapping
'org.codehaus.groovy.grails.web.mapping', // URL mapping
'org.codehaus.groovy.grails.commons', // core / classloading
'org.codehaus.groovy.grails.plugins', // plugins
'org.codehaus.groovy.grails.orm.hibernate', // hibernate integration
'org.springframework',
'org.hibernate'
warn 'org.mortbay.log'
}
/*
BigBlueButton open source conferencing system - http://www.bigbluebutton.org/
Copyright (c) 2012 BigBlueButton Inc. and by respective authors (see below).
This program is free software; you can redistribute it and/or modify it under the
terms of the GNU Lesser General Public License as published by the Free Software
Foundation; either version 3.0 of the License, or (at your option) any later
version.
BigBlueButton is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License along
with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
*/
// locations to search for config files that get merged into the main config;
// config files can be ConfigSlurper scripts, Java properties files, or classes
// in the classpath in ConfigSlurper format
grails.config.locations = [ "classpath:lti.properties"]
// grails.config.locations = [ "classpath:${appName}-config.properties",
// "classpath:${appName}-config.groovy",
// "file:${userHome}/.grails/${appName}-config.properties",
// "file:${userHome}/.grails/${appName}-config.groovy"]
// if (System.properties["${appName}.config.location"]) {
// grails.config.locations << "file:" + System.properties["${appName}.config.location"]
// }
grails.project.groupId = appName // change this to alter the default package name and Maven publishing destination
grails.mime.file.extensions = true // enables the parsing of file extensions from URLs into the request format
grails.mime.use.accept.header = false
// The ACCEPT header will not be used for content negotiation for user agents containing the following strings (defaults to the 4 major rendering engines)
grails.mime.disable.accept.header.userAgents = ['Gecko', 'WebKit', 'Presto', 'Trident']
grails.mime.types = [ // the first one is the default format
all: '*/*', // 'all' maps to '*' or the first available format in withFormat
atom: 'application/atom+xml',
css: 'text/css',
csv: 'text/csv',
form: 'application/x-www-form-urlencoded',
html: ['text/html','application/xhtml+xml'],
js: 'text/javascript',
json: ['application/json', 'text/json'],
multipartForm: 'multipart/form-data',
rss: 'application/rss+xml',
text: 'text/plain',
hal: ['application/hal+json','application/hal+xml'],
xml: ['text/xml', 'application/xml']
]
// URL Mapping Cache Max Size, defaults to 5000
//grails.urlmapping.cache.maxsize = 1000
// What URL patterns should be processed by the resources plugin
grails.resources.adhoc.patterns = ['/images/*', '/css/*', '/js/*', '/plugins/*']
grails.resources.adhoc.excludes = ['/WEB-INF/**']
// Legacy setting for codec used to encode data with ${}
grails.views.default.codec = "html"
// The default scope for controllers. May be prototype, session or singleton.
// If unspecified, controllers are prototype scoped.
grails.controllers.defaultScope = 'singleton'
// GSP settings
grails {
views {
gsp {
encoding = 'UTF-8'
htmlcodec = 'xml' // use xml escaping instead of HTML4 escaping
codecs {
expression = 'html' // escapes values inside ${}
scriptlet = 'html' // escapes output from scriptlets in GSPs
taglib = 'none' // escapes output from taglibs
staticparts = 'none' // escapes output from static template parts
}
}
// escapes all not-encoded output at final stage of outputting
// filteringCodecForContentType.'text/html' = 'html'
}
}
grails.converters.encoding = "UTF-8"
// scaffolding templates configuration
grails.scaffolding.templates.domainSuffix = 'Instance'
// Set to false to use the new Grails 1.2 JSONBuilder in the render method
grails.json.legacy.builder = false
// enabled native2ascii conversion of i18n properties files
grails.enable.native2ascii = true
// packages to include in Spring bean scanning
grails.spring.bean.packages = []
// whether to disable processing of multi part requests
grails.web.disable.multipart=false
// request parameters to mask when logging exceptions
grails.exceptionresolver.params.exclude = ['password']
// configure auto-caching of queries by default (if false you can cache individual queries with 'cache: true')
grails.hibernate.cache.queries = false
environments {
development {
grails.logging.jul.usebridge = true
}
production {
grails.logging.jul.usebridge = false
}
}
// log4j configuration
log4j = {
appenders {
rollingFile name:"logfile", maxFileSize:1000000, file:"/var/log/bigbluebutton/bbb-lti.log", layout:pattern(conversionPattern: '%d{[dd.MM.yy HH:mm:ss.SSS]} %-5p %c %x - %m%n')
console name:'console', layout:pattern(conversionPattern: '%d{[dd.MM.yy HH:mm:ss.SSS]} %-5p %c %x - %m%n')
'null' name:'stacktrace'
}
debug logfile:"grails.app"
error 'org.codehaus.groovy.grails.web.servlet', // controllers
'org.codehaus.groovy.grails.web.pages', // GSP
'org.codehaus.groovy.grails.web.sitemesh', // layouts
'org.codehaus.groovy.grails.web.mapping.filter', // URL mapping
'org.codehaus.groovy.grails.web.mapping', // URL mapping
'org.codehaus.groovy.grails.commons', // core / classloading
'org.codehaus.groovy.grails.plugins', // plugins
'org.codehaus.groovy.grails.orm.hibernate', // hibernate integration
'org.springframework',
'org.hibernate',
'net.sf.ehcache.hibernate'
warn 'org.mortbay.log'
}

View File

@ -17,34 +17,102 @@
*/
dataSource {
pooled = true
driverClassName = "org.hsqldb.jdbcDriver"
username = "sa"
password = ""
pooled = true
jmxExport = true
driverClassName = "org.h2.Driver"
username = "sa"
password = ""
}
hibernate {
cache.use_second_level_cache=true
cache.use_query_cache=true
cache.provider_class='com.opensymphony.oscache.hibernate.OSCacheProvider'
cache.use_second_level_cache = true
cache.use_query_cache = false
cache.region.factory_class = 'net.sf.ehcache.hibernate.EhCacheRegionFactory' // Hibernate 3
// cache.region.factory_class = 'org.hibernate.cache.ehcache.EhCacheRegionFactory' // Hibernate 4
}
// environment specific settings
environments {
development {
dataSource {
dbCreate = "create-drop" // one of 'create', 'create-drop','update'
url = "jdbc:hsqldb:mem:devDB"
}
}
test {
dataSource {
dbCreate = "update"
url = "jdbc:hsqldb:mem:testDb"
}
}
production {
dataSource {
dbCreate = "update"
url = "jdbc:hsqldb:mem:prodDb"
}
}
}
development {
dataSource {
dbCreate = "create-drop" // one of 'create', 'create-drop', 'update', 'validate', ''
url = "jdbc:h2:mem:devDb;MVCC=TRUE;LOCK_TIMEOUT=10000;DB_CLOSE_ON_EXIT=FALSE"
}
}
test {
dataSource {
dbCreate = "update"
url = "jdbc:h2:mem:testDb;MVCC=TRUE;LOCK_TIMEOUT=10000;DB_CLOSE_ON_EXIT=FALSE"
}
}
production {
dataSource {
dbCreate = "update"
url = "jdbc:h2:prodDb;MVCC=TRUE;LOCK_TIMEOUT=10000;DB_CLOSE_ON_EXIT=FALSE"
properties {
// Documentation for Tomcat JDBC Pool
// http://tomcat.apache.org/tomcat-7.0-doc/jdbc-pool.html#Common_Attributes
// https://tomcat.apache.org/tomcat-7.0-doc/api/org/apache/tomcat/jdbc/pool/PoolConfiguration.html
jmxEnabled = true
initialSize = 5
maxActive = 50
minIdle = 5
maxIdle = 25
maxWait = 10000
maxAge = 10 * 60000
timeBetweenEvictionRunsMillis = 5000
minEvictableIdleTimeMillis = 60000
validationQuery = "SELECT 1"
validationQueryTimeout = 3
validationInterval = 15000
testOnBorrow = true
testWhileIdle = true
testOnReturn = false
ignoreExceptionOnPreLoad = true
// http://tomcat.apache.org/tomcat-7.0-doc/jdbc-pool.html#JDBC_interceptors
jdbcInterceptors = "ConnectionState;StatementCache(max=200)"
defaultTransactionIsolation = java.sql.Connection.TRANSACTION_READ_COMMITTED // safe default
// controls for leaked connections
abandonWhenPercentageFull = 100 // settings are active only when pool is full
removeAbandonedTimeout = 120000
removeAbandoned = true
// use JMX console to change this setting at runtime
logAbandoned = false // causes stacktrace recording overhead, use only for debugging
/*
// JDBC driver properties
// Mysql as example
dbProperties {
// Mysql specific driver properties
// http://dev.mysql.com/doc/connector-j/en/connector-j-reference-configuration-properties.html
// let Tomcat JDBC Pool handle reconnecting
autoReconnect=false
// truncation behaviour
jdbcCompliantTruncation=false
// mysql 0-date conversion
zeroDateTimeBehavior='convertToNull'
// Tomcat JDBC Pool's StatementCache is used instead, so disable mysql driver's cache
cachePrepStmts=false
cacheCallableStmts=false
// Tomcat JDBC Pool's StatementFinalizer keeps track
dontTrackOpenResources=true
// performance optimization: reduce number of SQLExceptions thrown in mysql driver code
holdResultsOpenOverStatementClose=true
// enable MySQL query cache - using server prep stmts will disable query caching
useServerPrepStmts=false
// metadata caching
cacheServerConfiguration=true
cacheResultSetMetadata=true
metadataCacheSize=100
// timeouts for TCP/IP
connectTimeout=15000
socketTimeout=120000
// timer tuning (disable)
maintainTimeStats=false
enableQueryTimeouts=false
// misc tuning
noDatetimeStringSync=true
}
*/
}
}
}
}

View File

@ -17,13 +17,15 @@
*/
class UrlMappings {
static mappings = {
"/$controller/$action?/$id?"{
constraints {
// apply constraints here
}
}
"/"(view:"/index")
"500"(view:'/error')
static mappings = {
"/$controller/$action?/$id"{
constraints {
// apply constraints here
}
}
"/"(view:"/index")
"500"(view:'/error')
}
}

View File

@ -31,7 +31,8 @@ bigbluebuttonSalt=bbb_salt
ltiEndPoint=http://localhost/lti/tool
# The list of consumers allowed to access this lti service.
# Format: {consumerId1:sharedSecret1}
ltiConsumers=bbb:lti_secret
##ltiConsumers=bbb:bbb_salt
ltiConsumers=bbb:welcome
# The mode used to interact with BigBlueButton
# Format: [<simple>|extended]
ltiMode=extended

View File

@ -1,21 +1,3 @@
/*
BigBlueButton open source conferencing system - http://www.bigbluebutton.org/
Copyright (c) 2012 BigBlueButton Inc. and by respective authors (see below).
This program is free software; you can redistribute it and/or modify it under the
terms of the GNU Lesser General Public License as published by the Free Software
Foundation; either version 3.0 of the License, or (at your option) any later
version.
BigBlueButton is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License along
with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
*/
// Place your Spring DSL code here
beans = {
}
}

View File

@ -41,19 +41,20 @@ class ToolController {
LtiService ltiService
BigbluebuttonService bigbluebuttonService
def index = {
def index = {
if( ltiService.consumerMap == null) ltiService.initConsumerMap()
log.debug CONTROLLER_NAME + "#index"
setLocalization(params)
params.put(REQUEST_METHOD, request.getMethod().toUpperCase())
log.debug "params: " + params
ltiService.logParameters(params)
if( request.post ){
Map<String, String> result = new HashMap<String, String>()
ArrayList<String> missingParams = new ArrayList<String>()
log.debug "Checking for required parameters"
if (hasAllRequiredParams(params, missingParams)) {
def sanitizedParams = sanitizePrametersForBaseString(params)
def consumer = ltiService.getConsumer(params.get(Parameter.CONSUMER_ID))
@ -74,6 +75,7 @@ class ToolController {
}
} else {
log.debug "The message has NOT a valid signature."
result.put("resultMessageKey", "InvalidSignature")
result.put("resultMessage", "Invalid signature (" + params.get(Parameter.OAUTH_SIGNATURE) + ").")
}
@ -91,12 +93,11 @@ class ToolController {
result.put("resultMessageKey", "MissingRequiredParameter")
result.put("resultMessage", "Missing parameters [$missingStr]")
}
if( result != null && result.containsKey("resultMessageKey") ) {
if( result.containsKey("resultMessageKey") ) {
log.debug "Error [resultMessageKey:'" + result.get("resultMessageKey") + "', resultMessage:'" + result.get("resultMessage") + "']"
render(view: "error", model: ['resultMessageKey': result.get("resultMessageKey"), 'resultMessage': result.get("resultMessage")])
} else {
session["params"] = params
List<Object> recordings = bigbluebuttonService.getRecordings(params)
@ -110,23 +111,23 @@ class ToolController {
/// Add duration
recording.put("duration", duration )
}
render(view: "index", model: ['params': params, 'recordingList': recordings, 'ismoderator': bigbluebuttonService.isModerator(params)])
}
} else {
render(text: getCartridgeXML(), contentType: "text/xml", encoding: "UTF-8")
}
}
def join = {
if( ltiService.consumerMap == null) ltiService.initConsumerMap()
log.debug CONTROLLER_NAME + "#join"
Map<String, String> result
def sessionParams = session["params"]
if( sessionParams != null ) {
log.debug "params: " + params
log.debug "sessionParams: " + sessionParams
@ -136,7 +137,7 @@ class ToolController {
result.put("resultMessageKey", "InvalidSession")
result.put("resultMessage", "Invalid session. User can not execute this action.")
}
if( result.containsKey("resultMessageKey")) {
log.debug "Error [resultMessageKey:'" + result.get("resultMessageKey") + "', resultMessage:'" + result.get("resultMessage") + "']"
render(view: "error", model: ['resultMessageKey': result.get("resultMessageKey"), 'resultMessage': result.get("resultMessage")])
@ -147,9 +148,9 @@ class ToolController {
def publish = {
log.debug CONTROLLER_NAME + "#publish"
Map<String, String> result
def sessionParams = session["params"]
if( sessionParams == null ) {
result = new HashMap<String, String>()
result.put("resultMessageKey", "InvalidSession")
@ -165,7 +166,7 @@ class ToolController {
//Execute the publish command
result = bigbluebuttonService.doPublishRecordings(params)
}
if( result.containsKey("resultMessageKey")) {
log.debug "Error [resultMessageKey:'" + result.get("resultMessageKey") + "', resultMessage:'" + result.get("resultMessage") + "']"
render(view: "error", model: ['resultMessageKey': result.get("resultMessageKey"), 'resultMessage': result.get("resultMessage")])
@ -183,7 +184,7 @@ class ToolController {
}
render(view: "index", model: ['params': sessionParams, 'recordingList': recordings, 'ismoderator': bigbluebuttonService.isModerator(sessionParams)])
}
}
@ -191,9 +192,9 @@ class ToolController {
def delete = {
log.debug CONTROLLER_NAME + "#delete"
Map<String, String> result
def sessionParams = session["params"]
if( sessionParams == null ) {
result = new HashMap<String, String>()
result.put("resultMessageKey", "InvalidSession")
@ -209,7 +210,7 @@ class ToolController {
//Execute the delete command
result = bigbluebuttonService.doDeleteRecordings(params)
}
if( result.containsKey("resultMessageKey")) {
log.debug "Error [resultMessageKey:'" + result.get("resultMessageKey") + "', resultMessage:'" + result.get("resultMessage") + "']"
render(view: "error", model: ['resultMessageKey': result.get("resultMessageKey"), 'resultMessage': result.get("resultMessage")])
@ -225,14 +226,13 @@ class ToolController {
/// Add duration
recording.put("duration", duration )
}
render(view: "index", model: ['params': sessionParams, 'recordingList': recordings, 'ismoderator': bigbluebuttonService.isModerator(sessionParams)])
}
}
private void setLocalization(params){
String locale = params.get(Parameter.LAUNCH_LOCALE)
locale = (locale == null || locale.equals("")?"en":locale)
log.debug "Locale code =" + locale
@ -251,15 +251,28 @@ class ToolController {
Map<String, String> result = new HashMap<String, String>()
setLocalization(params)
String welcome = message(code: "bigbluebutton.welcome", args: ["\"{0}\"", "\"{1}\""])
String welcome = message(code: "bigbluebutton.welcome.header", args: ["\"{0}\"", "\"{1}\""]) + "<br>"
log.debug "Localized default welcome message: [" + welcome + "]"
// Check for [custom_]welcome parameter being passed from the LTI
if (params.get(Parameter.CUSTOM_WELCOME) != null) {
welcome = params.get(Parameter.CUSTOM_WELCOME)
welcome = params.get(Parameter.CUSTOM_WELCOME) + "<br>"
log.debug "Overriding default welcome message with: [" + welcome + "]"
}
if ( Boolean.parseBoolean(params.get(Parameter.CUSTOM_RECORD)) ) {
welcome += "<br><b>" + message(code: "bigbluebutton.welcome.record") + "</b><br>"
log.debug "Adding record warning to welcome message, welcome is now: [" + welcome + "]"
}
if ( Integer.parseInt(params.get(Parameter.CUSTOM_DURATION)) > 0 ) {
welcome += "<br><b>" + message(code: "bigbluebutton.welcome.duration", args: [params.get(Parameter.CUSTOM_DURATION)]) + "</b><br>"
log.debug "Adding duration warning to welcome message, welcome is now: [" + welcome + "]"
}
welcome += "<br>" + message(code: "bigbluebutton.welcome.footer") + "<br>"
log.debug "Localized default welcome message including footer: [" + welcome + "]"
String destinationURL = bigbluebuttonService.getJoinURL(params, welcome, ltiService.mode)
log.debug "redirecting to " + destinationURL
@ -335,13 +348,16 @@ class ToolController {
* @return - TRUE if the signatures matches the calculated signature
*/
private boolean checkValidSignature(String method, String URL, String conSecret, Object postProp, String signature) {
OAuthMessage oam = new OAuthMessage(method, URL, ((Properties)postProp).entrySet());
HMAC_SHA1 hmac = new HMAC_SHA1();
hmac.setConsumerSecret(conSecret);
log.debug( "Starting checkValidSignature()" )
OAuthMessage oam = new OAuthMessage(method, URL, ((Properties)postProp).entrySet())
log.debug( "OAuthMessage oam = " + oam.toString() )
HMAC_SHA1 hmac = new HMAC_SHA1()
log.debug( "HMAC_SHA1 hmac = " + hmac.toString() )
hmac.setConsumerSecret(conSecret)
log.debug("Base Message String = [ " + hmac.getBaseString(oam) + " ]\n");
log.debug("Base Message String = [ " + hmac.getBaseString(oam) + " ]\n")
String calculatedSignature = hmac.getSignature(hmac.getBaseString(oam))
log.debug("Calculated: " + calculatedSignature + " Received: " + signature);
log.debug("Calculated: " + calculatedSignature + " Received: " + signature)
return calculatedSignature.equals(signature)
}

View File

@ -16,8 +16,14 @@
# with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
#
bigbluebutton.welcome=<br>Welcome to <b>{0}</b>!<br><br>To understand how BigBlueButton works see our <a href=\"event:http://www.bigbluebutton.org/content/videos\"><u>tutorial videos</u></a>.<br><br>To join the audio bridge click the headset icon (upper-left hand corner). <b>Please use a headset to avoid causing noise for others.</b>
# The welcome.header can be static, however if you want the name of the activity (meeting) to be injected use {0} as part of the text
# {1} can be used to inject the name of the course
bigbluebutton.welcome.header=Welcome to <b>{0}</b>!
bigbluebutton.welcome.footer=To understand how BigBlueButton works see our <a href=\"event:http://www.bigbluebutton.org/content/videos\"><u>tutorial videos</u></a>.<br><br>To join the audio bridge click the headset icon (upper-left hand corner). <b>Please use a headset to avoid causing noise for others.
bigbluebutton.welcome.record=This meeting is being recorded
bigbluebutton.welcome.duration=The maximum duration for this meeting is {0} minutes
tool.view.app=BigBlueButton
tool.view.title=BigBlueButton LTI Interface
tool.view.join=Join Meeting
tool.view.recording=Recording

View File

@ -16,8 +16,12 @@
# with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
#
bigbluebutton.welcome=<br>Bienvenido a <b>{0}</b>!<br><br>Para entender como funciona BigBlueButton consulte estos <a href=\"event:http://www.bigbluebutton.org/content/videos\"><u>videos tutoriales</u></a>.<br><br>Para activar el audio haga click en el icono de auricular (equina superior izquierda). <b>Por favor utilice auricular para evitar causar ruido.</b>
bigbluebutton.welcome.header=Bienvenido a <b>{0}</b>!
bigbluebutton.welcome.footer=Para entender como funciona BigBlueButton consulte estos <a href=\"event:http://www.bigbluebutton.org/content/videos\"><u>videos tutoriales</u></a>.<br><br>Para activar el audio haga click en el icono de auricular (equina superior izquierda). <b>Por favor utilice auricular para evitar causar ruido.
bigbluebutton.welcome.record=Esta sesi&#243;n esta siendo grabada
bigbluebutton.welcome.duration=La duraci&#243;n maxima para esta sesi&#243;n es de {0} minutos
tool.view.app=BigBlueButton
tool.view.title=Interface LTI para BigBlueButton
tool.view.join=Ingresar a la sesi&#243;n
tool.view.recording=Grabaci&#243;n

View File

@ -16,8 +16,10 @@
# with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
#
bigbluebutton.welcome=<br>Bienvenue au <b>{0}</b>!<br><br>Pour comprendre comment fonctionne BigBlueButton, consultez les <a href=\"event:http://www.bigbluebutton.org/content/videos\"><u>didacticiels vid&#233;o</u></a>.<br><br>Pour activer l'audio cliquez sur l'ic&#244;ne du casque &#224; &#233;couteurs (coin sup&#233;rieur gauche). <b>S'il vous plaît utiliser le casque pour &#233;viter de causer du bruit.</b>
bigbluebutton.welcome.header=<br>Bienvenue au <b>{0}</b>!<br>
bigbluebutton.welcome.footer=<br>Pour comprendre comment fonctionne BigBlueButton, consultez les <a href=\"event:http://www.bigbluebutton.org/content/videos\"><u>didacticiels vid&#233;o</u></a>.<br><br>Pour activer l'audio cliquez sur l'ic&#244;ne du casque &#224; &#233;couteurs (coin sup&#233;rieur gauche). <b>S'il vous plaît utiliser le casque pour &#233;viter de causer du bruit.</b>
tool.view.app=BigBlueButton
tool.view.title=LTI Interface pour BigBlueButton
tool.view.join=Saisie de la r&#233;union
tool.view.recording=Enregistrement

View File

@ -27,9 +27,9 @@ class LtiService {
boolean transactional = false
def endPoint = "http://localhost/lti/tool"
def consumers = "demo:welcome"
def mode = "simple"
Map<String, String> consumerMap
def retrieveIconEndpoint() {
@ -88,4 +88,10 @@ class LtiService {
private String encodeBase64(byte[] signBytes) {
return Base64.encodeBase64URLSafeString(signBytes)
}
def logParameters(Object params) {
log.debug "----------------------------------"
for( param in params ) log.debug "${param.getKey()}=${param.getValue()}"
log.debug "----------------------------------"
}
}

View File

@ -1,54 +1,18 @@
<!DOCTYPE html>
<html>
<head>
<title>Runtime Exception</title>
<style type="text/css">
.message {
border: 1px solid black;
padding: 5px;
background-color:#E9E9E9;
}
.stack {
border: 1px solid black;
padding: 5px;
overflow:auto;
height: 300px;
}
.snippet {
padding: 5px;
background-color:white;
border:1px solid black;
margin:3px;
font-family:courier;
}
</style>
</head>
<body>
<h1>Runtime Exception</h1>
<h2>Error Details</h2>
<div class="message">
<strong>Error ${request.'javax.servlet.error.status_code'}:</strong> ${request.'javax.servlet.error.message'.encodeAsHTML()}<br/>
<strong>Servlet:</strong> ${request.'javax.servlet.error.servlet_name'}<br/>
<strong>URI:</strong> ${request.'javax.servlet.error.request_uri'}<br/>
<g:if test="${exception}">
<strong>Exception Message:</strong> ${exception.message?.encodeAsHTML()} <br />
<strong>Caused by:</strong> ${exception.cause?.message?.encodeAsHTML()} <br />
<strong>Class:</strong> ${exception.className} <br />
<strong>At Line:</strong> [${exception.lineNumber}] <br />
<strong>Code Snippet:</strong><br />
<div class="snippet">
<g:each var="cs" in="${exception.codeSnippet}">
${cs?.encodeAsHTML()}<br />
</g:each>
</div>
<head>
<title><g:if env="development">Grails Runtime Exception</g:if><g:else>Error</g:else></title>
<meta name="layout" content="main">
<g:if env="development"><link rel="stylesheet" href="${resource(dir: 'css', file: 'errors.css')}" type="text/css"></g:if>
</head>
<body>
<g:if env="development">
<g:renderException exception="${exception}" />
</g:if>
</div>
<g:if test="${exception}">
<h2>Stack Trace</h2>
<div class="stack">
<pre><g:each in="${exception.stackTraceLines}">${it.encodeAsHTML()}<br/></g:each></pre>
</div>
</g:if>
</body>
</html>
<g:else>
<ul class="errors">
<li>An error has occurred</li>
</ul>
</g:else>
</body>
</html>

View File

@ -1 +1,123 @@
<%response.sendRedirect(request.getContextPath()+"/tool");%>
<!DOCTYPE html>
<html>
<head>
<meta name="layout" content="main"/>
<title>Welcome to Grails</title>
<style type="text/css" media="screen">
#status {
background-color: #eee;
border: .2em solid #fff;
margin: 2em 2em 1em;
padding: 1em;
width: 12em;
float: left;
-moz-box-shadow: 0px 0px 1.25em #ccc;
-webkit-box-shadow: 0px 0px 1.25em #ccc;
box-shadow: 0px 0px 1.25em #ccc;
-moz-border-radius: 0.6em;
-webkit-border-radius: 0.6em;
border-radius: 0.6em;
}
.ie6 #status {
display: inline; /* float double margin fix http://www.positioniseverything.net/explorer/doubled-margin.html */
}
#status ul {
font-size: 0.9em;
list-style-type: none;
margin-bottom: 0.6em;
padding: 0;
}
#status li {
line-height: 1.3;
}
#status h1 {
text-transform: uppercase;
font-size: 1.1em;
margin: 0 0 0.3em;
}
#page-body {
margin: 2em 1em 1.25em 18em;
}
h2 {
margin-top: 1em;
margin-bottom: 0.3em;
font-size: 1em;
}
p {
line-height: 1.5;
margin: 0.25em 0;
}
#controller-list ul {
list-style-position: inside;
}
#controller-list li {
line-height: 1.3;
list-style-position: inside;
margin: 0.25em 0;
}
@media screen and (max-width: 480px) {
#status {
display: none;
}
#page-body {
margin: 0 1em 1em;
}
#page-body h1 {
margin-top: 0;
}
}
</style>
</head>
<body>
<!-- index -->
<a href="#page-body" class="skip"><g:message code="default.link.skip.label" default="Skip to content&hellip;"/></a>
<div id="status" role="complementary">
<h1>Application Status</h1>
<ul>
<li>App version: <g:meta name="app.version"/></li>
<li>Grails version: <g:meta name="app.grails.version"/></li>
<li>Groovy version: ${GroovySystem.getVersion()}</li>
<li>JVM version: ${System.getProperty('java.version')}</li>
<li>Reloading active: ${grails.util.Environment.reloadingAgentEnabled}</li>
<li>Controllers: ${grailsApplication.controllerClasses.size()}</li>
<li>Domains: ${grailsApplication.domainClasses.size()}</li>
<li>Services: ${grailsApplication.serviceClasses.size()}</li>
<li>Tag Libraries: ${grailsApplication.tagLibClasses.size()}</li>
</ul>
<h1>Installed Plugins</h1>
<ul>
<g:each var="plugin" in="${applicationContext.getBean('pluginManager').allPlugins}">
<li>${plugin.name} - ${plugin.version}</li>
</g:each>
</ul>
</div>
<div id="page-body" role="main">
<h1>Welcome to Grails</h1>
<p>Congratulations, you have successfully started your first Grails application! At the moment
this is the default page, feel free to modify it to either redirect to a controller or display whatever
content you may choose. Below is a list of controllers that are currently deployed in this application,
click on each to execute its default action:</p>
<div id="controller-list" role="navigation">
<h2>Available Controllers:</h2>
<ul>
<g:each var="c" in="${grailsApplication.controllerClasses.sort { it.fullName } }">
<li class="controller"><g:link controller="${c.logicalPropertyName}">${c.fullName}</g:link></li>
</g:each>
</ul>
</div>
</div>
</body>
</html>

View File

@ -1,12 +0,0 @@
<html>
<head>
<title><g:layoutTitle default="<g:message code="tool.view.title" />" /></title>
<link rel="stylesheet" href="${resource(dir:'css',file:'main.css')}" />
<link rel="shortcut icon" href="${resource(dir:'images',file:'favicon.ico')}" type="image/x-icon" />
<g:layoutHead />
<g:javascript library="application" />
</head>
<body>
<g:layoutBody />
</body>
</html>

View File

@ -1,16 +1,32 @@
<html>
<head>
<title><g:layoutTitle default="<g:message code="tool.view.title" />" /></title>
<link rel="stylesheet" href="${resource(dir:'css',file:'main.css')}" />
<link rel="shortcut icon" href="${resource(dir:'images',file:'favicon.ico')}" type="image/x-icon" />
<g:layoutHead />
<g:javascript library="application" />
</head>
<body>
<div id="spinner" class="spinner" style="display:none;">
<img src="${resource(dir:'images',file:'spinner.gif')}" alt="Spinner" />
</div>
<div class="logo"><img src="${resource(dir:'images',file:'bbb_logo.jpg')}" alt="BigBlueButton" /></div>
<g:layoutBody />
</body>
</html>
<!DOCTYPE html>
<!--[if lt IE 7 ]> <html lang="en" class="no-js ie6"> <![endif]-->
<!--[if IE 7 ]> <html lang="en" class="no-js ie7"> <![endif]-->
<!--[if IE 8 ]> <html lang="en" class="no-js ie8"> <![endif]-->
<!--[if IE 9 ]> <html lang="en" class="no-js ie9"> <![endif]-->
<!--[if (gt IE 9)|!(IE)]><!--> <html lang="en" class="no-js"><!--<![endif]-->
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
<title><g:message code="tool.view.title" /></title>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="shortcut icon" href="${resource(dir: 'images', file: 'favicon.ico')}" type="image/x-icon">
<link rel="apple-touch-icon" href="${resource(dir: 'images', file: 'apple-touch-icon.png')}">
<link rel="apple-touch-icon" sizes="114x114" href="${resource(dir: 'images', file: 'apple-touch-icon-retina.png')}">
<link rel="stylesheet" href="${resource(dir: 'css', file: 'main.css')}" type="text/css">
<link rel="stylesheet" href="${resource(dir: 'css', file: 'mobile.css')}" type="text/css">
<g:layoutHead/>
<g:javascript library="application"/>
<r:layoutResources />
</head>
<body>
<div id="spinner" class="spinner" style="display:none;">
<img src="${resource(dir:'images',file:'spinner.gif')}" alt="Spinner" />
</div>
<div class="logo" id="logo" role="banner"><img src="${resource(dir: 'images', file: 'bbb_logo.jpg')}" alt="<g:message code="tool.view.app" />" /></div>
<g:layoutBody />
<div class="footer" role="contentinfo"></div>
<div id="spinner" class="spinner" style="display:none;"><g:message code="spinner.alt" default="Loading&hellip;"/></div>
<r:layoutResources />
</body>
</html>

View File

@ -7,6 +7,7 @@
</head>
<body>
<div class="body">
<!-- tool.error -->
<g:if test="${ (resultMessageKey == 'InvalidEPortfolioUserId')}">
${resultMessage}
</g:if>
@ -14,12 +15,12 @@
Connection could not be established.
</g:else>
</div>
<!-- {
"error": {
"messageKey": "${resultMessageKey}",
"message": "${resultMessage}"
}
}
<!-- {
"error": {
"messageKey": "${resultMessageKey}",
"message": "${resultMessage}"
}
}
-->
<br/><br/>
</body>

View File

@ -5,8 +5,10 @@
<link rel="shortcut icon" href="${resource(dir:'images',file:'favicon.ico')}" type="image/x-icon" />
</head>
<body>
<h1 style="margin-left:20px; text-align: center;"><a title="<g:message code="tool.view.join" />" class="btn btn-primary btn-large" href="${createLink(controller:'tool',action:'join')}"><g:message code="tool.view.join" /></a></h1>
<!-- tool.index -->
<h1 style="margin-left:20px; text-align: center;"><a title="<g:message code="tool.view.join" />" class="btn btn-primary btn-large" href="${createLink(controller:'tool', action:'join', id: '0')}"><g:message code="tool.view.join" /></a></h1>
<br><br>
<div class="table-responsive">
<table class="table table-striped table-bordered table-condensed">
<thead>
<tr>
@ -16,7 +18,7 @@
<th class="header c3" style="text-align:center;" scope="col"><g:message code="tool.view.date" /></th>
<th class="header c4" style="text-align:center;" scope="col"><g:message code="tool.view.duration" /></th>
<g:if test="${ismoderator}">
<th class="header c5 lastcol" style="text-align:left;" scope="col"><g:message code="tool.view.actions" /></th>
<th class="header c5 lastcol" style="text-align:center;" scope="col"><g:message code="tool.view.actions" /></th>
</g:if>
</tr>
</thead>
@ -34,14 +36,14 @@
<td class="cell c3" style="text-align:center;">${new Date( Long.valueOf(r.startTime).longValue() )}</td>
<td class="cell c4" style="text-align:center;">${r.duration}</td>
<g:if test="${ismoderator}">
<td class="cell c5 lastcol" style="text-align:left;">
<td class="cell c5 lastcol" style="text-align:center;">
<g:if test="${r.published == 'true'}">
<a title="<g:message code="tool.view.unPublishRecording" />" class="action-icon" href="${createLink(controller:'tool',action:'publish')}?bbb_recording_published=${r.published}&bbb_recording_id=${r.recordID}"><img title="<g:message code="tool.view.unpublishRecording" />" alt="<g:message code="tool.view.unpublishRecording" />" class="smallicon" src="${resource(dir:'images',file:'hide.gif')}" /></a>
<button class="btn btn-default btn-xs" name="unpublish_recording" type="submit" value="${r.recordID}" onClick="window.location='${createLink(controller:'tool',action:'publish',id: '0')}?bbb_recording_published=${r.published}&bbb_recording_id=${r.recordID}'; return false;"><g:message code="tool.view.unpublishRecording" /></button>
</g:if>
<g:else>
<a title="<g:message code="tool.view.publishRecording" />" class="action-icon" href="${createLink(controller:'tool',action:'publish')}?bbb_recording_published=${r.published}&bbb_recording_id=${r.recordID}"><img title="<g:message code="tool.view.publishRecording" />" alt="<g:message code="tool.view.publishRecording" />" class="smallicon" src="${resource(dir:'images',file:'show.gif')}" /></a>
<button class="btn btn-default btn-xs" name="publish_recording" type="submit" value="${r.recordID}" onClick="window.location='${createLink(controller:'tool',action:'publish',id: '0')}?bbb_recording_published=${r.published}&bbb_recording_id=${r.recordID}'; return false;"><g:message code="tool.view.publishRecording" /></button>
</g:else>
<a title="<g:message code="tool.view.deleteRecording" />" class="action-icon" onClick="if(confirm('<g:message code="tool.view.deleteRecordingConfirmation" />')) window.location='${createLink(controller:'tool',action:'delete')}?bbb_recording_id=${r.recordID}'; return false;" href="#"><img title="<g:message code="tool.view.deleteRecording" />" alt="<g:message code="tool.view.deleteRecording" />" class="smallicon" src="${resource(dir:'images',file:'delete.gif')}" /></a>
<button class="btn btn-danger btn-xs" name="delete_recording" type="submit" value="${r.recordID}" onClick="if(confirm('<g:message code="tool.view.deleteRecordingConfirmation" />')) window.location='${createLink(controller:'tool',action:'delete',id: '0')}?bbb_recording_id=${r.recordID}'; return false;"><g:message code="tool.view.deleteRecording" /></button>
</td>
</g:if>
</tr>
@ -49,6 +51,6 @@
</g:each>
</tbody>
</table>
</div>
</body>
</html>

View File

@ -59,7 +59,11 @@ public class Proxy {
}
public void setUrl(String url){
this.url = url;
if( url.substring(url.length()-1).equals("/") )
this.url = url.substring(0, url.length()-1);
else
this.url = url;
//this.url = url;
}
public void setSalt(String salt){

View File

@ -1,47 +1,34 @@
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd">
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="grailsApplication" class="org.codehaus.groovy.grails.commons.GrailsApplicationFactoryBean">
<description>Grails application factory bean</description>
<property name="grailsDescriptor" value="/WEB-INF/grails.xml" />
<property name="grailsResourceLoader" ref="grailsResourceLoader" />
<property name="grailsDescriptor" value="/WEB-INF/grails.xml" />
<property name="grailsResourceLoader" ref="grailsResourceLoader" />
</bean>
<bean id="pluginManager" class="org.codehaus.groovy.grails.plugins.GrailsPluginManagerFactoryBean">
<description>A bean that manages Grails plugins</description>
<property name="grailsDescriptor" value="/WEB-INF/grails.xml" />
<property name="application" ref="grailsApplication" />
<property name="grailsDescriptor" value="/WEB-INF/grails.xml" />
<property name="application" ref="grailsApplication" />
</bean>
<bean id="pluginMetaManager" class="org.codehaus.groovy.grails.plugins.DefaultPluginMetaManager">
<property name="grailsApplication" ref="grailsApplication" />
<property name="resourcePattern" value="/WEB-INF/plugins/*/plugin.xml" />
</bean>
<bean id="grailsConfigurator" class="org.codehaus.groovy.grails.commons.spring.GrailsRuntimeConfigurator">
<constructor-arg>
<ref bean="grailsApplication" />
</constructor-arg>
<property name="pluginManager" ref="pluginManager" />
</bean>
<bean id="grailsConfigurator" class="org.codehaus.groovy.grails.commons.spring.GrailsRuntimeConfigurator">
<constructor-arg>
<ref bean="grailsApplication" />
</constructor-arg>
<property name="pluginManager" ref="pluginManager" />
</bean>
<bean id="grailsResourceLoader" class="org.codehaus.groovy.grails.commons.GrailsResourceLoaderFactoryBean" />
<bean id="grailsResourceLoader" class="org.codehaus.groovy.grails.commons.GrailsResourceLoaderFactoryBean">
<property name="grailsResourceHolder" ref="grailsResourceHolder" />
</bean>
<bean id="characterEncodingFilter" class="org.springframework.web.filter.CharacterEncodingFilter">
<property name="encoding">
<value>utf-8</value>
</property>
</bean>
<bean id="grailsResourceHolder" scope="prototype" class="org.codehaus.groovy.grails.commons.spring.GrailsResourceHolder">
<property name="resources">
<value>classpath*:**/grails-app/**/*.groovy</value>
</property>
</bean>
<bean id="characterEncodingFilter"
class="org.springframework.web.filter.CharacterEncodingFilter">
<property name="encoding">
<value>utf-8</value>
</property>
</bean>
<bean id="conversionService" class="org.springframework.context.support.ConversionServiceFactoryBean" />
</beans>

View File

@ -1,11 +1,11 @@
<sitemesh>
<page-parsers>
<parser content-type="text/html"
class="com.opensymphony.module.sitemesh.parser.HTMLPageParser" />
class="org.codehaus.groovy.grails.web.sitemesh.GrailsHTMLPageParser" />
<parser content-type="text/html;charset=ISO-8859-1"
class="com.opensymphony.module.sitemesh.parser.HTMLPageParser" />
class="org.codehaus.groovy.grails.web.sitemesh.GrailsHTMLPageParser" />
<parser content-type="text/html;charset=UTF-8"
class="com.opensymphony.module.sitemesh.parser.HTMLPageParser" />
class="org.codehaus.groovy.grails.web.sitemesh.GrailsHTMLPageParser" />
</page-parsers>
<decorator-mappers>

View File

@ -1,13 +1,13 @@
<?xml version="1.0" encoding="UTF-8" ?>
<taglib xmlns="http://java.sun.com/xml/ns/j2ee"
<taglib xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-jsptaglibrary_2_0.xsd"
version="2.0">
<description>JSTL 1.1 core library</description>
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-jsptaglibrary_2_1.xsd"
version="2.1">
<description>JSTL 1.2 core library</description>
<display-name>JSTL core</display-name>
<tlib-version>1.1</tlib-version>
<tlib-version>1.2</tlib-version>
<short-name>c</short-name>
<uri>http://java.sun.com/jsp/jstl/core</uri>
@ -74,7 +74,7 @@ not the body content should be processed.
<description>
Name of the exported scoped variable for the
resulting value of the test condition. The type
of the scoped variable is Boolean.
of the scoped variable is Boolean.
</description>
<name>var</name>
<required>false</required>
@ -174,6 +174,9 @@ Collection of items to iterate over.
<required>false</required>
<rtexprvalue>true</rtexprvalue>
<type>java.lang.Object</type>
<deferred-value>
<type>java.lang.Object</type>
</deferred-value>
</attribute>
<attribute>
<description>
@ -253,6 +256,9 @@ String of tokens to iterate over.
<required>true</required>
<rtexprvalue>true</rtexprvalue>
<type>java.lang.String</type>
<deferred-value>
<type>java.lang.String</type>
</deferred-value>
</attribute>
<attribute>
<description>
@ -322,7 +328,7 @@ visibility.
<tag>
<description>
Like &lt;%= ... &gt;, but for expressions.
</description>
</description>
<name>out</name>
<tag-class>org.apache.taglibs.standard.tag.rt.core.OutTag</tag-class>
<body-content>JSP</body-content>
@ -467,6 +473,9 @@ Expression to be evaluated.
<name>value</name>
<required>false</required>
<rtexprvalue>true</rtexprvalue>
<deferred-value>
<type>java.lang.Object</type>
</deferred-value>
</attribute>
<attribute>
<description>

View File

@ -1,13 +1,13 @@
<?xml version="1.0" encoding="UTF-8" ?>
<taglib xmlns="http://java.sun.com/xml/ns/j2ee"
<taglib xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-jsptaglibrary_2_0.xsd"
version="2.0">
<description>JSTL 1.1 i18n-capable formatting library</description>
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-jsptaglibrary_2_1.xsd"
version="2.1">
<description>JSTL 1.2 i18n-capable formatting library</description>
<display-name>JSTL fmt</display-name>
<tlib-version>1.1</tlib-version>
<tlib-version>1.2</tlib-version>
<short-name>fmt</short-name>
<uri>http://java.sun.com/jsp/jstl/fmt</uri>
@ -55,7 +55,7 @@ and may contain a two-letter (upper-case)
country code (as defined by ISO-3166).
Language and country codes must be
separated by hyphen (-) or underscore
(_).
(_).
</description>
<name>value</name>
<required>true</required>
@ -496,7 +496,7 @@ Date and/or time to be formatted.
<description>
Specifies whether the time, the date, or both
the time and date components of the given
date are to be formatted.
date are to be formatted.
</description>
<name>type</name>
<required>false</required>

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,109 @@
h1, h2 {
margin: 10px 25px 5px;
}
h2 {
font-size: 1.1em;
}
.filename {
font-style: italic;
}
.exceptionMessage {
margin: 10px;
border: 1px solid #000;
padding: 5px;
background-color: #E9E9E9;
}
.stack,
.snippet {
margin: 0 25px 10px;
}
.stack,
.snippet {
border: 1px solid #ccc;
-mox-box-shadow: 0 0 2px rgba(0,0,0,0.2);
-webkit-box-shadow: 0 0 2px rgba(0,0,0,0.2);
box-shadow: 0 0 2px rgba(0,0,0,0.2);
}
/* error details */
.error-details {
border-top: 1px solid #FFAAAA;
-mox-box-shadow: 0 0 2px rgba(0,0,0,0.2);
-webkit-box-shadow: 0 0 2px rgba(0,0,0,0.2);
box-shadow: 0 0 2px rgba(0,0,0,0.2);
border-bottom: 1px solid #FFAAAA;
-mox-box-shadow: 0 0 2px rgba(0,0,0,0.2);
-webkit-box-shadow: 0 0 2px rgba(0,0,0,0.2);
box-shadow: 0 0 2px rgba(0,0,0,0.2);
background-color:#FFF3F3;
line-height: 1.5;
overflow: hidden;
padding: 5px;
padding-left:25px;
}
.error-details dt {
clear: left;
float: left;
font-weight: bold;
margin-right: 5px;
}
.error-details dt:after {
content: ":";
}
.error-details dd {
display: block;
}
/* stack trace */
.stack {
padding: 5px;
overflow: auto;
height: 150px;
}
/* code snippet */
.snippet {
background-color: #fff;
font-family: monospace;
}
.snippet .line {
display: block;
}
.snippet .lineNumber {
background-color: #ddd;
color: #999;
display: inline-block;
margin-right: 5px;
padding: 0 3px;
text-align: right;
width: 3em;
}
.snippet .error {
background-color: #fff3f3;
font-weight: bold;
}
.snippet .error .lineNumber {
background-color: #faa;
color: #333;
font-weight: bold;
}
.snippet .line:first-child .lineNumber {
padding-top: 5px;
}
.snippet .line:last-child .lineNumber {
padding-bottom: 5px;
}

View File

@ -1,267 +1,596 @@
/* FONT STACK */
body,
input, select, textarea {
font-family: "HelveticaNeue-Light", "Helvetica Neue Light", "Helvetica Neue", Helvetica, Arial, "Lucida Grande", sans-serif;
}
h1, h2, h3, h4, h5, h6 {
line-height: 1.1;
}
/* BASE LAYOUT */
html {
background-color: #ddd;
background-image: -moz-linear-gradient(center top, #aaa, #ddd);
background-image: -webkit-gradient(linear, left top, left bottom, color-stop(0, #aaa), color-stop(1, #ddd));
background-image: linear-gradient(top, #aaa, #ddd);
filter: progid:DXImageTransform.Microsoft.gradient(startColorStr = '#aaaaaa', EndColorStr = '#dddddd');
background-repeat: no-repeat;
height: 100%;
/* change the box model to exclude the padding from the calculation of 100% height (IE8+) */
-webkit-box-sizing: border-box;
-moz-box-sizing: border-box;
box-sizing: border-box;
}
html.no-cssgradients {
background-color: #aaa;
}
.ie6 html {
height: 100%;
}
html * {
margin: 0;
/*padding: 0; SELECT NOT DISPLAYED CORRECTLY IN FIREFOX */
margin: 0;
}
body {
background: #ffffff;
color: #333333;
margin: 0 auto;
max-width: 960px;
overflow-x: hidden; /* prevents box-shadow causing a horizontal scrollbar in firefox when viewport < 960px wide */
-moz-box-shadow: 0 0 0.3em #255b17;
-webkit-box-shadow: 0 0 0.3em #255b17;
box-shadow: 0 0 0.3em #255b17;
}
#grailsLogo {
background-color: #abbf78;
}
/* replace with .no-boxshadow body if you have modernizr available */
.ie6 body,
.ie7 body,
.ie8 body {
border-color: #255b17;
border-style: solid;
border-width: 0 1px;
}
.ie6 body {
height: 100%;
}
a:link, a:visited, a:hover {
color: #48802c;
}
a:hover, a:active {
outline: none; /* prevents outline in webkit on active links but retains it for tab focus */
}
h1 {
color: #48802c;
font-weight: normal;
font-size: 1.25em;
margin: 0.8em 0 0.3em 0;
}
ul {
padding: 0;
}
img {
border: 0;
}
/* GENERAL */
#grailsLogo a {
display: inline-block;
margin: 1em;
}
.content {
}
.content h1 {
border-bottom: 1px solid #CCCCCC;
margin: 0.8em 1em 0.3em;
padding: 0 0.25em;
}
.scaffold-list h1 {
border: none;
}
.footer {
background: #abbf78;
color: #000;
clear: both;
font-size: 0.8em;
margin-top: 1.5em;
padding: 1em;
min-height: 1em;
}
.footer a {
color: #255b17;
}
.spinner {
padding: 5px;
background: url(../images/spinner.gif) 50% 50% no-repeat transparent;
height: 16px;
width: 16px;
padding: 0.5em;
position: absolute;
right: 0;
}
body {
background: #fff;
color: #333;
font: 11px verdana, arial, helvetica, sans-serif;
}
a:link, a:visited, a:hover {
color: #666;
font-weight: bold;
text-decoration: none;
}
h1 {
color: #006dba;
font-weight: normal;
font-size: 16px;
margin: .8em 0 .3em 0;
}
ul {
padding-left: 15px;
}
input, select, textarea {
background-color: #fcfcfc;
border: 1px solid #ccc;
font: 11px verdana, arial, helvetica, sans-serif;
margin: 2px 0;
padding: 2px 4px;
}
select {
padding: 2px 2px 2px 0;
}
textarea {
width: 250px;
height: 150px;
vertical-align: top;
}
input:focus, select:focus, textarea:focus {
border: 1px solid #b2d1ff;
}
.body {
float: left;
margin: 0 15px 10px 15px;
top: 0;
text-indent: -9999px;
}
/* NAVIGATION MENU */
.nav {
background: #fff url(../images/skin/shadow.jpg) bottom repeat-x;
border: 1px solid #ccc;
border-style: solid none solid none;
margin-top: 5px;
padding: 7px 12px;
background-color: #efefef;
padding: 0.5em 0.75em;
-moz-box-shadow: 0 0 3px 1px #aaaaaa;
-webkit-box-shadow: 0 0 3px 1px #aaaaaa;
box-shadow: 0 0 3px 1px #aaaaaa;
zoom: 1;
}
.menuButton {
font-size: 10px;
padding: 0 5px;
.nav ul {
overflow: hidden;
padding-left: 0;
zoom: 1;
}
.menuButton a {
color: #333;
padding: 4px 6px;
.nav li {
display: block;
float: left;
list-style-type: none;
margin-right: 0.5em;
padding: 0;
}
.menuButton a.home {
background: url(../images/skin/house.png) center left no-repeat;
color: #333;
padding-left: 25px;
.nav a {
color: #666666;
display: block;
padding: 0.25em 0.7em;
text-decoration: none;
-moz-border-radius: 0.3em;
-webkit-border-radius: 0.3em;
border-radius: 0.3em;
}
.menuButton a.list {
background: url(../images/skin/database_table.png) center left no-repeat;
color: #333;
padding-left: 25px;
.nav a:active, .nav a:visited {
color: #666666;
}
.menuButton a.create {
background: url(../images/skin/database_add.png) center left no-repeat;
color: #333;
padding-left: 25px;
.nav a:focus, .nav a:hover {
background-color: #999999;
color: #ffffff;
outline: none;
text-shadow: 1px 1px 1px rgba(0, 0, 0, 0.8);
}
.no-borderradius .nav a:focus, .no-borderradius .nav a:hover {
background-color: transparent;
color: #444444;
text-decoration: underline;
}
.nav a.home, .nav a.list, .nav a.create {
background-position: 0.7em center;
background-repeat: no-repeat;
text-indent: 25px;
}
.nav a.home {
background-image: url(../images/skin/house.png);
}
.nav a.list {
background-image: url(../images/skin/database_table.png);
}
.nav a.create {
background-image: url(../images/skin/database_add.png);
}
/* CREATE/EDIT FORMS AND SHOW PAGES */
fieldset,
.property-list {
margin: 0.6em 1.25em 0 1.25em;
padding: 0.3em 1.8em 1.25em;
position: relative;
zoom: 1;
border: none;
}
.property-list .fieldcontain {
list-style: none;
overflow: hidden;
zoom: 1;
}
.fieldcontain {
margin-top: 1em;
}
.fieldcontain label,
.fieldcontain .property-label {
color: #666666;
text-align: right;
width: 25%;
}
.fieldcontain .property-label {
float: left;
}
.fieldcontain .property-value {
display: block;
margin-left: 27%;
}
label {
cursor: pointer;
display: inline-block;
margin: 0 0.25em 0 0;
}
input, select, textarea {
background-color: #fcfcfc;
border: 1px solid #cccccc;
font-size: 1em;
padding: 0.2em 0.4em;
}
select {
padding: 0.2em 0.2em 0.2em 0;
}
select[multiple] {
vertical-align: top;
}
textarea {
width: 250px;
height: 150px;
overflow: auto; /* IE always renders vertical scrollbar without this */
vertical-align: top;
}
input[type=checkbox], input[type=radio] {
background-color: transparent;
border: 0;
padding: 0;
}
input:focus, select:focus, textarea:focus {
background-color: #ffffff;
border: 1px solid #eeeeee;
outline: 0;
-moz-box-shadow: 0 0 0.5em #ffffff;
-webkit-box-shadow: 0 0 0.5em #ffffff;
box-shadow: 0 0 0.5em #ffffff;
}
.required-indicator {
color: #48802C;
display: inline-block;
font-weight: bold;
margin-left: 0.3em;
position: relative;
top: 0.1em;
}
ul.one-to-many {
display: inline-block;
list-style-position: inside;
vertical-align: top;
}
.ie6 ul.one-to-many, .ie7 ul.one-to-many {
display: inline;
zoom: 1;
}
ul.one-to-many li.add {
list-style-type: none;
}
/* EMBEDDED PROPERTIES */
fieldset.embedded {
background-color: transparent;
border: 1px solid #CCCCCC;
margin-left: 0;
margin-right: 0;
padding-left: 0;
padding-right: 0;
-moz-box-shadow: none;
-webkit-box-shadow: none;
box-shadow: none;
}
fieldset.embedded legend {
margin: 0 1em;
}
/* MESSAGES AND ERRORS */
.errors,
.message {
background: #f3f8fc url(../images/skin/information.png) 8px 50% no-repeat;
border: 1px solid #b2d1ff;
color: #006dba;
margin: 10px 0 5px 0;
padding: 5px 5px 5px 30px
font-size: 0.8em;
line-height: 2;
margin: 1em 2em;
padding: 0.25em;
}
div.errors {
background: #fff3f3;
border: 1px solid red;
color: #cc0000;
margin: 10px 0 5px 0;
padding: 5px 0 5px 0;
}
div.errors ul {
list-style: none;
padding: 0;
}
div.errors li {
background: url(../images/skin/exclamation.png) 8px 0% no-repeat;
line-height: 16px;
padding-left: 30px;
.message {
background: #f3f3ff;
border: 1px solid #b2d1ff;
color: #006dba;
-moz-box-shadow: 0 0 0.25em #b2d1ff;
-webkit-box-shadow: 0 0 0.25em #b2d1ff;
box-shadow: 0 0 0.25em #b2d1ff;
}
td.errors select {
border: 1px solid red;
.errors {
background: #fff3f3;
border: 1px solid #ffaaaa;
color: #cc0000;
-moz-box-shadow: 0 0 0.25em #ff8888;
-webkit-box-shadow: 0 0 0.25em #ff8888;
box-shadow: 0 0 0.25em #ff8888;
}
td.errors input {
border: 1px solid red;
.errors ul,
.message {
padding: 0;
}
.errors li {
list-style: none;
background: transparent url(../images/skin/exclamation.png) 0.5em 50% no-repeat;
text-indent: 2.2em;
}
.message {
background: transparent url(../images/skin/information.png) 0.5em 50% no-repeat;
text-indent: 2.2em;
}
/* form fields with errors */
.error input, .error select, .error textarea {
background: #fff3f3;
border-color: #ffaaaa;
color: #cc0000;
}
.error input:focus, .error select:focus, .error textarea:focus {
-moz-box-shadow: 0 0 0.5em #ffaaaa;
-webkit-box-shadow: 0 0 0.5em #ffaaaa;
box-shadow: 0 0 0.5em #ffaaaa;
}
/* same effects for browsers that support HTML5 client-side validation (these have to be specified separately or IE will ignore the entire rule) */
input:invalid, select:invalid, textarea:invalid {
background: #fff3f3;
border-color: #ffaaaa;
color: #cc0000;
}
input:invalid:focus, select:invalid:focus, textarea:invalid:focus {
-moz-box-shadow: 0 0 0.5em #ffaaaa;
-webkit-box-shadow: 0 0 0.5em #ffaaaa;
box-shadow: 0 0 0.5em #ffaaaa;
}
/* TABLES */
table {
border: 1px solid #ccc;
width: 100%
border-top: 1px solid #DFDFDF;
border-collapse: collapse;
width: 100%;
margin-bottom: 1em;
}
tr {
border: 0;
border: 0;
}
td, th {
font: 11px verdana, arial, helvetica, sans-serif;
line-height: 12px;
padding: 5px 6px;
text-align: left;
vertical-align: top;
tr>td:first-child, tr>th:first-child {
padding-left: 1.25em;
}
tr>td:last-child, tr>th:last-child {
padding-right: 1.25em;
}
td, th {
line-height: 1.5em;
padding: 0.5em 0.6em;
text-align: left;
vertical-align: top;
}
th {
background: #fff url(../images/skin/shadow.jpg);
color: #666;
font-size: 11px;
font-weight: bold;
line-height: 17px;
padding: 2px 6px;
background-color: #efefef;
background-image: -moz-linear-gradient(top, #ffffff, #eaeaea);
background-image: -webkit-gradient(linear, left top, left bottom, color-stop(0, #ffffff), color-stop(1, #eaeaea));
filter: progid:DXImageTransform.Microsoft.gradient(startColorStr = '#ffffff', EndColorStr = '#eaeaea');
-ms-filter: "progid:DXImageTransform.Microsoft.gradient(startColorStr='#ffffff', EndColorStr='#eaeaea')";
color: #666666;
font-weight: bold;
line-height: 1.7em;
padding: 0.2em 0.6em;
}
th a:link, th a:visited, th a:hover {
color: #333;
display: block;
font-size: 10px;
text-decoration: none;
width: 100%;
thead th {
white-space: nowrap;
}
th.asc a, th.desc a {
background-position: right;
background-repeat: no-repeat;
th a {
display: block;
text-decoration: none;
}
th a:link, th a:visited {
color: #666666;
}
th a:hover, th a:focus {
color: #333333;
}
th.sortable a {
background-position: right;
background-repeat: no-repeat;
padding-right: 1.1em;
}
th.asc a {
background-image: url(../images/skin/sorted_asc.gif);
background-image: url(../images/skin/sorted_asc.gif);
}
th.desc a {
background-image: url(../images/skin/sorted_desc.gif);
background-image: url(../images/skin/sorted_desc.gif);
}
.odd {
background: #f7f7f7;
background: #f7f7f7;
}
.even {
background: #fff;
background: #ffffff;
}
/* LIST */
.list table {
border-collapse: collapse;
}
.list th, .list td {
border-left: 1px solid #ddd;
}
.list th:hover, .list tr:hover {
background: #b2d1ff;
th:hover, tr:hover {
background: #E1F2B6;
}
/* PAGINATION */
.paginateButtons {
background: #fff url(../images/skin/shadow.jpg) bottom repeat-x;
border: 1px solid #ccc;
border-top: 0;
color: #666;
font-size: 10px;
overflow: hidden;
padding: 10px 3px;
}
.paginateButtons a {
background: #fff;
border: 1px solid #ccc;
border-color: #ccc #aaa #aaa #ccc;
color: #666;
margin: 0 3px;
padding: 2px 6px;
}
.paginateButtons span {
padding: 2px 3px;
.pagination {
border-top: 0;
margin: 0;
padding: 0.3em 0.2em;
text-align: center;
-moz-box-shadow: 0 0 3px 1px #AAAAAA;
-webkit-box-shadow: 0 0 3px 1px #AAAAAA;
box-shadow: 0 0 3px 1px #AAAAAA;
background-color: #EFEFEF;
}
/* DIALOG */
.dialog table {
padding: 5px 0;
.pagination a,
.pagination .currentStep {
color: #666666;
display: inline-block;
margin: 0 0.1em;
padding: 0.25em 0.7em;
text-decoration: none;
-moz-border-radius: 0.3em;
-webkit-border-radius: 0.3em;
border-radius: 0.3em;
}
.prop {
padding: 5px;
.pagination a:hover, .pagination a:focus,
.pagination .currentStep {
background-color: #999999;
color: #ffffff;
outline: none;
text-shadow: 1px 1px 1px rgba(0, 0, 0, 0.8);
}
.prop .name {
text-align: left;
width: 15%;
white-space: nowrap;
}
.prop .value {
text-align: left;
width: 85%;
.no-borderradius .pagination a:hover, .no-borderradius .pagination a:focus,
.no-borderradius .pagination .currentStep {
background-color: transparent;
color: #444444;
text-decoration: underline;
}
/* ACTION BUTTONS */
.buttons {
background: #fff url(../images/skin/shadow.jpg) bottom repeat-x;
border: 1px solid #ccc;
color: #666;
font-size: 10px;
margin-top: 5px;
overflow: hidden;
padding: 0;
background-color: #efefef;
overflow: hidden;
padding: 0.3em;
-moz-box-shadow: 0 0 3px 1px #aaaaaa;
-webkit-box-shadow: 0 0 3px 1px #aaaaaa;
box-shadow: 0 0 3px 1px #aaaaaa;
margin: 0.1em 0 0 0;
border: none;
}
.buttons input {
background: #fff;
border: 0;
color: #333;
cursor: pointer;
font-size: 10px;
font-weight: bold;
margin-left: 3px;
overflow: visible;
padding: 2px 6px;
.buttons input,
.buttons a {
background-color: transparent;
border: 0;
color: #666666;
cursor: pointer;
display: inline-block;
margin: 0 0.25em 0;
overflow: visible;
padding: 0.25em 0.7em;
text-decoration: none;
-moz-border-radius: 0.3em;
-webkit-border-radius: 0.3em;
border-radius: 0.3em;
}
.buttons input.delete {
background: transparent url(../images/skin/database_delete.png) 5px 50% no-repeat;
padding-left: 28px;
.buttons input:hover, .buttons input:focus,
.buttons a:hover, .buttons a:focus {
background-color: #999999;
color: #ffffff;
outline: none;
text-shadow: 1px 1px 1px rgba(0, 0, 0, 0.8);
-moz-box-shadow: none;
-webkit-box-shadow: none;
box-shadow: none;
}
.buttons input.edit {
background: transparent url(../images/skin/database_edit.png) 5px 50% no-repeat;
padding-left: 28px;
.no-borderradius .buttons input:hover, .no-borderradius .buttons input:focus,
.no-borderradius .buttons a:hover, .no-borderradius .buttons a:focus {
background-color: transparent;
color: #444444;
text-decoration: underline;
}
.buttons input.save {
background: transparent url(../images/skin/database_save.png) 5px 50% no-repeat;
padding-left: 28px;
.buttons .delete, .buttons .edit, .buttons .save {
background-position: 0.7em center;
background-repeat: no-repeat;
text-indent: 25px;
}
.ie6 .buttons input.delete, .ie6 .buttons input.edit, .ie6 .buttons input.save,
.ie7 .buttons input.delete, .ie7 .buttons input.edit, .ie7 .buttons input.save {
padding-left: 36px;
}
.buttons .delete {
background-image: url(../images/skin/database_delete.png);
}
.buttons .edit {
background-image: url(../images/skin/database_edit.png);
}
.buttons .save {
background-image: url(../images/skin/database_save.png);
}
a.skip {
position: absolute;
left: -9999px;
}

View File

@ -0,0 +1,82 @@
/* Styles for mobile devices */
@media screen and (max-width: 480px) {
.nav {
padding: 0.5em;
}
.nav li {
margin: 0 0.5em 0 0;
padding: 0.25em;
}
/* Hide individual steps in pagination, just have next & previous */
.pagination .step, .pagination .currentStep {
display: none;
}
.pagination .prevLink {
float: left;
}
.pagination .nextLink {
float: right;
}
/* pagination needs to wrap around floated buttons */
.pagination {
overflow: hidden;
}
/* slightly smaller margin around content body */
fieldset,
.property-list {
padding: 0.3em 1em 1em;
}
input, textarea {
width: 100%;
-moz-box-sizing: border-box;
-webkit-box-sizing: border-box;
-ms-box-sizing: border-box;
box-sizing: border-box;
}
select, input[type=checkbox], input[type=radio], input[type=submit], input[type=button], input[type=reset] {
width: auto;
}
/* hide all but the first column of list tables */
.scaffold-list td:not(:first-child),
.scaffold-list th:not(:first-child) {
display: none;
}
.scaffold-list thead th {
text-align: center;
}
/* stack form elements */
.fieldcontain {
margin-top: 0.6em;
}
.fieldcontain label,
.fieldcontain .property-label,
.fieldcontain .property-value {
display: block;
float: none;
margin: 0 0 0.25em 0;
text-align: left;
width: auto;
}
.errors ul,
.message p {
margin: 0.5em;
}
.error ul {
margin-left: 0;
}
}

Binary file not shown.

View File

@ -0,0 +1,229 @@
<?xml version="1.0" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd" >
<svg xmlns="http://www.w3.org/2000/svg">
<metadata></metadata>
<defs>
<font id="glyphicons_halflingsregular" horiz-adv-x="1200" >
<font-face units-per-em="1200" ascent="960" descent="-240" />
<missing-glyph horiz-adv-x="500" />
<glyph />
<glyph />
<glyph unicode="&#xd;" />
<glyph unicode=" " />
<glyph unicode="*" d="M100 500v200h259l-183 183l141 141l183 -183v259h200v-259l183 183l141 -141l-183 -183h259v-200h-259l183 -183l-141 -141l-183 183v-259h-200v259l-183 -183l-141 141l183 183h-259z" />
<glyph unicode="+" d="M0 400v300h400v400h300v-400h400v-300h-400v-400h-300v400h-400z" />
<glyph unicode="&#xa0;" />
<glyph unicode="&#x2000;" horiz-adv-x="652" />
<glyph unicode="&#x2001;" horiz-adv-x="1304" />
<glyph unicode="&#x2002;" horiz-adv-x="652" />
<glyph unicode="&#x2003;" horiz-adv-x="1304" />
<glyph unicode="&#x2004;" horiz-adv-x="434" />
<glyph unicode="&#x2005;" horiz-adv-x="326" />
<glyph unicode="&#x2006;" horiz-adv-x="217" />
<glyph unicode="&#x2007;" horiz-adv-x="217" />
<glyph unicode="&#x2008;" horiz-adv-x="163" />
<glyph unicode="&#x2009;" horiz-adv-x="260" />
<glyph unicode="&#x200a;" horiz-adv-x="72" />
<glyph unicode="&#x202f;" horiz-adv-x="260" />
<glyph unicode="&#x205f;" horiz-adv-x="326" />
<glyph unicode="&#x20ac;" d="M100 500l100 100h113q0 47 5 100h-218l100 100h135q37 167 112 257q117 141 297 141q242 0 354 -189q60 -103 66 -209h-181q0 55 -25.5 99t-63.5 68t-75 36.5t-67 12.5q-24 0 -52.5 -10t-62.5 -32t-65.5 -67t-50.5 -107h379l-100 -100h-300q-6 -46 -6 -100h406l-100 -100 h-300q9 -74 33 -132t52.5 -91t62 -54.5t59 -29t46.5 -7.5q29 0 66 13t75 37t63.5 67.5t25.5 96.5h174q-31 -172 -128 -278q-107 -117 -274 -117q-205 0 -324 158q-36 46 -69 131.5t-45 205.5h-217z" />
<glyph unicode="&#x2212;" d="M200 400h900v300h-900v-300z" />
<glyph unicode="&#x25fc;" horiz-adv-x="500" d="M0 0z" />
<glyph unicode="&#x2601;" d="M-14 494q0 -80 56.5 -137t135.5 -57h750q120 0 205 86.5t85 207.5t-85 207t-205 86q-46 0 -90 -14q-44 97 -134.5 156.5t-200.5 59.5q-152 0 -260 -107.5t-108 -260.5q0 -25 2 -37q-66 -14 -108.5 -67.5t-42.5 -122.5z" />
<glyph unicode="&#x2709;" d="M0 100l400 400l200 -200l200 200l400 -400h-1200zM0 300v600l300 -300zM0 1100l600 -603l600 603h-1200zM900 600l300 300v-600z" />
<glyph unicode="&#x270f;" d="M-13 -13l333 112l-223 223zM187 403l214 -214l614 614l-214 214zM887 1103l214 -214l99 92q13 13 13 32.5t-13 33.5l-153 153q-15 13 -33 13t-33 -13z" />
<glyph unicode="&#xe001;" d="M0 1200h1200l-500 -550v-550h300v-100h-800v100h300v550z" />
<glyph unicode="&#xe002;" d="M14 84q18 -55 86 -75.5t147 5.5q65 21 109 69t44 90v606l600 155v-521q-64 16 -138 -7q-79 -26 -122.5 -83t-25.5 -111q18 -55 86 -75.5t147 4.5q70 23 111.5 63.5t41.5 95.5v881q0 10 -7 15.5t-17 2.5l-752 -193q-10 -3 -17 -12.5t-7 -19.5v-689q-64 17 -138 -7 q-79 -25 -122.5 -82t-25.5 -112z" />
<glyph unicode="&#xe003;" d="M23 693q0 200 142 342t342 142t342 -142t142 -342q0 -142 -78 -261l300 -300q7 -8 7 -18t-7 -18l-109 -109q-8 -7 -18 -7t-18 7l-300 300q-119 -78 -261 -78q-200 0 -342 142t-142 342zM176 693q0 -136 97 -233t234 -97t233.5 96.5t96.5 233.5t-96.5 233.5t-233.5 96.5 t-234 -97t-97 -233z" />
<glyph unicode="&#xe005;" d="M100 784q0 64 28 123t73 100.5t104.5 64t119 20.5t120 -38.5t104.5 -104.5q48 69 109.5 105t121.5 38t118.5 -20.5t102.5 -64t71 -100.5t27 -123q0 -57 -33.5 -117.5t-94 -124.5t-126.5 -127.5t-150 -152.5t-146 -174q-62 85 -145.5 174t-149.5 152.5t-126.5 127.5 t-94 124.5t-33.5 117.5z" />
<glyph unicode="&#xe006;" d="M-72 800h479l146 400h2l146 -400h472l-382 -278l145 -449l-384 275l-382 -275l146 447zM168 71l2 1z" />
<glyph unicode="&#xe007;" d="M-72 800h479l146 400h2l146 -400h472l-382 -278l145 -449l-384 275l-382 -275l146 447zM168 71l2 1zM237 700l196 -142l-73 -226l192 140l195 -141l-74 229l193 140h-235l-77 211l-78 -211h-239z" />
<glyph unicode="&#xe008;" d="M0 0v143l400 257v100q-37 0 -68.5 74.5t-31.5 125.5v200q0 124 88 212t212 88t212 -88t88 -212v-200q0 -51 -31.5 -125.5t-68.5 -74.5v-100l400 -257v-143h-1200z" />
<glyph unicode="&#xe009;" d="M0 0v1100h1200v-1100h-1200zM100 100h100v100h-100v-100zM100 300h100v100h-100v-100zM100 500h100v100h-100v-100zM100 700h100v100h-100v-100zM100 900h100v100h-100v-100zM300 100h600v400h-600v-400zM300 600h600v400h-600v-400zM1000 100h100v100h-100v-100z M1000 300h100v100h-100v-100zM1000 500h100v100h-100v-100zM1000 700h100v100h-100v-100zM1000 900h100v100h-100v-100z" />
<glyph unicode="&#xe010;" d="M0 50v400q0 21 14.5 35.5t35.5 14.5h400q21 0 35.5 -14.5t14.5 -35.5v-400q0 -21 -14.5 -35.5t-35.5 -14.5h-400q-21 0 -35.5 14.5t-14.5 35.5zM0 650v400q0 21 14.5 35.5t35.5 14.5h400q21 0 35.5 -14.5t14.5 -35.5v-400q0 -21 -14.5 -35.5t-35.5 -14.5h-400 q-21 0 -35.5 14.5t-14.5 35.5zM600 50v400q0 21 14.5 35.5t35.5 14.5h400q21 0 35.5 -14.5t14.5 -35.5v-400q0 -21 -14.5 -35.5t-35.5 -14.5h-400q-21 0 -35.5 14.5t-14.5 35.5zM600 650v400q0 21 14.5 35.5t35.5 14.5h400q21 0 35.5 -14.5t14.5 -35.5v-400 q0 -21 -14.5 -35.5t-35.5 -14.5h-400q-21 0 -35.5 14.5t-14.5 35.5z" />
<glyph unicode="&#xe011;" d="M0 50v200q0 21 14.5 35.5t35.5 14.5h200q21 0 35.5 -14.5t14.5 -35.5v-200q0 -21 -14.5 -35.5t-35.5 -14.5h-200q-21 0 -35.5 14.5t-14.5 35.5zM0 450v200q0 21 14.5 35.5t35.5 14.5h200q21 0 35.5 -14.5t14.5 -35.5v-200q0 -21 -14.5 -35.5t-35.5 -14.5h-200 q-21 0 -35.5 14.5t-14.5 35.5zM0 850v200q0 21 14.5 35.5t35.5 14.5h200q21 0 35.5 -14.5t14.5 -35.5v-200q0 -21 -14.5 -35.5t-35.5 -14.5h-200q-21 0 -35.5 14.5t-14.5 35.5zM400 50v200q0 21 14.5 35.5t35.5 14.5h200q21 0 35.5 -14.5t14.5 -35.5v-200q0 -21 -14.5 -35.5 t-35.5 -14.5h-200q-21 0 -35.5 14.5t-14.5 35.5zM400 450v200q0 21 14.5 35.5t35.5 14.5h200q21 0 35.5 -14.5t14.5 -35.5v-200q0 -21 -14.5 -35.5t-35.5 -14.5h-200q-21 0 -35.5 14.5t-14.5 35.5zM400 850v200q0 21 14.5 35.5t35.5 14.5h200q21 0 35.5 -14.5t14.5 -35.5 v-200q0 -21 -14.5 -35.5t-35.5 -14.5h-200q-21 0 -35.5 14.5t-14.5 35.5zM800 50v200q0 21 14.5 35.5t35.5 14.5h200q21 0 35.5 -14.5t14.5 -35.5v-200q0 -21 -14.5 -35.5t-35.5 -14.5h-200q-21 0 -35.5 14.5t-14.5 35.5zM800 450v200q0 21 14.5 35.5t35.5 14.5h200 q21 0 35.5 -14.5t14.5 -35.5v-200q0 -21 -14.5 -35.5t-35.5 -14.5h-200q-21 0 -35.5 14.5t-14.5 35.5zM800 850v200q0 21 14.5 35.5t35.5 14.5h200q21 0 35.5 -14.5t14.5 -35.5v-200q0 -21 -14.5 -35.5t-35.5 -14.5h-200q-21 0 -35.5 14.5t-14.5 35.5z" />
<glyph unicode="&#xe012;" d="M0 50v200q0 21 14.5 35.5t35.5 14.5h200q21 0 35.5 -14.5t14.5 -35.5v-200q0 -21 -14.5 -35.5t-35.5 -14.5h-200q-21 0 -35.5 14.5t-14.5 35.5zM0 450q0 -21 14.5 -35.5t35.5 -14.5h200q21 0 35.5 14.5t14.5 35.5v200q0 21 -14.5 35.5t-35.5 14.5h-200q-21 0 -35.5 -14.5 t-14.5 -35.5v-200zM0 850v200q0 21 14.5 35.5t35.5 14.5h200q21 0 35.5 -14.5t14.5 -35.5v-200q0 -21 -14.5 -35.5t-35.5 -14.5h-200q-21 0 -35.5 14.5t-14.5 35.5zM400 50v200q0 21 14.5 35.5t35.5 14.5h700q21 0 35.5 -14.5t14.5 -35.5v-200q0 -21 -14.5 -35.5 t-35.5 -14.5h-700q-21 0 -35.5 14.5t-14.5 35.5zM400 450v200q0 21 14.5 35.5t35.5 14.5h700q21 0 35.5 -14.5t14.5 -35.5v-200q0 -21 -14.5 -35.5t-35.5 -14.5h-700q-21 0 -35.5 14.5t-14.5 35.5zM400 850v200q0 21 14.5 35.5t35.5 14.5h700q21 0 35.5 -14.5t14.5 -35.5 v-200q0 -21 -14.5 -35.5t-35.5 -14.5h-700q-21 0 -35.5 14.5t-14.5 35.5z" />
<glyph unicode="&#xe013;" d="M29 454l419 -420l818 820l-212 212l-607 -607l-206 207z" />
<glyph unicode="&#xe014;" d="M106 318l282 282l-282 282l212 212l282 -282l282 282l212 -212l-282 -282l282 -282l-212 -212l-282 282l-282 -282z" />
<glyph unicode="&#xe015;" d="M23 693q0 200 142 342t342 142t342 -142t142 -342q0 -142 -78 -261l300 -300q7 -8 7 -18t-7 -18l-109 -109q-8 -7 -18 -7t-18 7l-300 300q-119 -78 -261 -78q-200 0 -342 142t-142 342zM176 693q0 -136 97 -233t234 -97t233.5 96.5t96.5 233.5t-96.5 233.5t-233.5 96.5 t-234 -97t-97 -233zM300 600v200h100v100h200v-100h100v-200h-100v-100h-200v100h-100z" />
<glyph unicode="&#xe016;" d="M23 694q0 200 142 342t342 142t342 -142t142 -342q0 -141 -78 -262l300 -299q7 -7 7 -18t-7 -18l-109 -109q-8 -8 -18 -8t-18 8l-300 300q-119 -78 -261 -78q-200 0 -342 142t-142 342zM176 694q0 -136 97 -233t234 -97t233.5 97t96.5 233t-96.5 233t-233.5 97t-234 -97 t-97 -233zM300 601h400v200h-400v-200z" />
<glyph unicode="&#xe017;" d="M23 600q0 183 105 331t272 210v-166q-103 -55 -165 -155t-62 -220q0 -177 125 -302t302 -125t302 125t125 302q0 120 -62 220t-165 155v166q167 -62 272 -210t105 -331q0 -118 -45.5 -224.5t-123 -184t-184 -123t-224.5 -45.5t-224.5 45.5t-184 123t-123 184t-45.5 224.5 zM500 750q0 -21 14.5 -35.5t35.5 -14.5h100q21 0 35.5 14.5t14.5 35.5v400q0 21 -14.5 35.5t-35.5 14.5h-100q-21 0 -35.5 -14.5t-14.5 -35.5v-400z" />
<glyph unicode="&#xe018;" d="M100 1h200v300h-200v-300zM400 1v500h200v-500h-200zM700 1v800h200v-800h-200zM1000 1v1200h200v-1200h-200z" />
<glyph unicode="&#xe019;" d="M26 601q0 -33 6 -74l151 -38l2 -6q14 -49 38 -93l3 -5l-80 -134q45 -59 105 -105l133 81l5 -3q45 -26 94 -39l5 -2l38 -151q40 -5 74 -5q27 0 74 5l38 151l6 2q46 13 93 39l5 3l134 -81q56 44 104 105l-80 134l3 5q24 44 39 93l1 6l152 38q5 40 5 74q0 28 -5 73l-152 38 l-1 6q-16 51 -39 93l-3 5l80 134q-44 58 -104 105l-134 -81l-5 3q-45 25 -93 39l-6 1l-38 152q-40 5 -74 5q-27 0 -74 -5l-38 -152l-5 -1q-50 -14 -94 -39l-5 -3l-133 81q-59 -47 -105 -105l80 -134l-3 -5q-25 -47 -38 -93l-2 -6l-151 -38q-6 -48 -6 -73zM385 601 q0 88 63 151t152 63t152 -63t63 -151q0 -89 -63 -152t-152 -63t-152 63t-63 152z" />
<glyph unicode="&#xe020;" d="M100 1025v50q0 10 7.5 17.5t17.5 7.5h275v100q0 41 29.5 70.5t70.5 29.5h300q41 0 70.5 -29.5t29.5 -70.5v-100h275q10 0 17.5 -7.5t7.5 -17.5v-50q0 -11 -7 -18t-18 -7h-1050q-11 0 -18 7t-7 18zM200 100v800h900v-800q0 -41 -29.5 -71t-70.5 -30h-700q-41 0 -70.5 30 t-29.5 71zM300 100h100v700h-100v-700zM500 100h100v700h-100v-700zM500 1100h300v100h-300v-100zM700 100h100v700h-100v-700zM900 100h100v700h-100v-700z" />
<glyph unicode="&#xe021;" d="M1 601l656 644l644 -644h-200v-600h-300v400h-300v-400h-300v600h-200z" />
<glyph unicode="&#xe022;" d="M100 25v1150q0 11 7 18t18 7h475v-500h400v-675q0 -11 -7 -18t-18 -7h-850q-11 0 -18 7t-7 18zM700 800v300l300 -300h-300z" />
<glyph unicode="&#xe023;" d="M4 600q0 162 80 299t217 217t299 80t299 -80t217 -217t80 -299t-80 -299t-217 -217t-299 -80t-299 80t-217 217t-80 299zM186 600q0 -171 121.5 -292.5t292.5 -121.5t292.5 121.5t121.5 292.5t-121.5 292.5t-292.5 121.5t-292.5 -121.5t-121.5 -292.5zM500 500v400h100 v-300h200v-100h-300z" />
<glyph unicode="&#xe024;" d="M-100 0l431 1200h209l-21 -300h162l-20 300h208l431 -1200h-538l-41 400h-242l-40 -400h-539zM488 500h224l-27 300h-170z" />
<glyph unicode="&#xe025;" d="M0 0v400h490l-290 300h200v500h300v-500h200l-290 -300h490v-400h-1100zM813 200h175v100h-175v-100z" />
<glyph unicode="&#xe026;" d="M1 600q0 122 47.5 233t127.5 191t191 127.5t233 47.5t233 -47.5t191 -127.5t127.5 -191t47.5 -233t-47.5 -233t-127.5 -191t-191 -127.5t-233 -47.5t-233 47.5t-191 127.5t-127.5 191t-47.5 233zM188 600q0 -170 121 -291t291 -121t291 121t121 291t-121 291t-291 121 t-291 -121t-121 -291zM350 600h150v300h200v-300h150l-250 -300z" />
<glyph unicode="&#xe027;" d="M4 600q0 162 80 299t217 217t299 80t299 -80t217 -217t80 -299t-80 -299t-217 -217t-299 -80t-299 80t-217 217t-80 299zM186 600q0 -171 121.5 -292.5t292.5 -121.5t292.5 121.5t121.5 292.5t-121.5 292.5t-292.5 121.5t-292.5 -121.5t-121.5 -292.5zM350 600l250 300 l250 -300h-150v-300h-200v300h-150z" />
<glyph unicode="&#xe028;" d="M0 25v475l200 700h800l199 -700l1 -475q0 -11 -7 -18t-18 -7h-1150q-11 0 -18 7t-7 18zM200 500h200l50 -200h300l50 200h200l-97 500h-606z" />
<glyph unicode="&#xe029;" d="M4 600q0 162 80 299t217 217t299 80t299 -80t217 -217t80 -299t-80 -299t-217 -217t-299 -80t-299 80t-217 217t-80 299zM186 600q0 -172 121.5 -293t292.5 -121t292.5 121t121.5 293q0 171 -121.5 292.5t-292.5 121.5t-292.5 -121.5t-121.5 -292.5zM500 397v401 l297 -200z" />
<glyph unicode="&#xe030;" d="M23 600q0 -118 45.5 -224.5t123 -184t184 -123t224.5 -45.5t224.5 45.5t184 123t123 184t45.5 224.5h-150q0 -177 -125 -302t-302 -125t-302 125t-125 302t125 302t302 125q136 0 246 -81l-146 -146h400v400l-145 -145q-157 122 -355 122q-118 0 -224.5 -45.5t-184 -123 t-123 -184t-45.5 -224.5z" />
<glyph unicode="&#xe031;" d="M23 600q0 118 45.5 224.5t123 184t184 123t224.5 45.5q198 0 355 -122l145 145v-400h-400l147 147q-112 80 -247 80q-177 0 -302 -125t-125 -302h-150zM100 0v400h400l-147 -147q112 -80 247 -80q177 0 302 125t125 302h150q0 -118 -45.5 -224.5t-123 -184t-184 -123 t-224.5 -45.5q-198 0 -355 122z" />
<glyph unicode="&#xe032;" d="M100 0h1100v1200h-1100v-1200zM200 100v900h900v-900h-900zM300 200v100h100v-100h-100zM300 400v100h100v-100h-100zM300 600v100h100v-100h-100zM300 800v100h100v-100h-100zM500 200h500v100h-500v-100zM500 400v100h500v-100h-500zM500 600v100h500v-100h-500z M500 800v100h500v-100h-500z" />
<glyph unicode="&#xe033;" d="M0 100v600q0 41 29.5 70.5t70.5 29.5h100v200q0 82 59 141t141 59h300q82 0 141 -59t59 -141v-200h100q41 0 70.5 -29.5t29.5 -70.5v-600q0 -41 -29.5 -70.5t-70.5 -29.5h-900q-41 0 -70.5 29.5t-29.5 70.5zM400 800h300v150q0 21 -14.5 35.5t-35.5 14.5h-200 q-21 0 -35.5 -14.5t-14.5 -35.5v-150z" />
<glyph unicode="&#xe034;" d="M100 0v1100h100v-1100h-100zM300 400q60 60 127.5 84t127.5 17.5t122 -23t119 -30t110 -11t103 42t91 120.5v500q-40 -81 -101.5 -115.5t-127.5 -29.5t-138 25t-139.5 40t-125.5 25t-103 -29.5t-65 -115.5v-500z" />
<glyph unicode="&#xe035;" d="M0 275q0 -11 7 -18t18 -7h50q11 0 18 7t7 18v300q0 127 70.5 231.5t184.5 161.5t245 57t245 -57t184.5 -161.5t70.5 -231.5v-300q0 -11 7 -18t18 -7h50q11 0 18 7t7 18v300q0 116 -49.5 227t-131 192.5t-192.5 131t-227 49.5t-227 -49.5t-192.5 -131t-131 -192.5 t-49.5 -227v-300zM200 20v460q0 8 6 14t14 6h160q8 0 14 -6t6 -14v-460q0 -8 -6 -14t-14 -6h-160q-8 0 -14 6t-6 14zM800 20v460q0 8 6 14t14 6h160q8 0 14 -6t6 -14v-460q0 -8 -6 -14t-14 -6h-160q-8 0 -14 6t-6 14z" />
<glyph unicode="&#xe036;" d="M0 400h300l300 -200v800l-300 -200h-300v-400zM688 459l141 141l-141 141l71 71l141 -141l141 141l71 -71l-141 -141l141 -141l-71 -71l-141 141l-141 -141z" />
<glyph unicode="&#xe037;" d="M0 400h300l300 -200v800l-300 -200h-300v-400zM700 857l69 53q111 -135 111 -310q0 -169 -106 -302l-67 54q86 110 86 248q0 146 -93 257z" />
<glyph unicode="&#xe038;" d="M0 401v400h300l300 200v-800l-300 200h-300zM702 858l69 53q111 -135 111 -310q0 -170 -106 -303l-67 55q86 110 86 248q0 145 -93 257zM889 951l7 -8q123 -151 123 -344q0 -189 -119 -339l-7 -8l81 -66l6 8q142 178 142 405q0 230 -144 408l-6 8z" />
<glyph unicode="&#xe039;" d="M0 0h500v500h-200v100h-100v-100h-200v-500zM0 600h100v100h400v100h100v100h-100v300h-500v-600zM100 100v300h300v-300h-300zM100 800v300h300v-300h-300zM200 200v100h100v-100h-100zM200 900h100v100h-100v-100zM500 500v100h300v-300h200v-100h-100v-100h-200v100 h-100v100h100v200h-200zM600 0v100h100v-100h-100zM600 1000h100v-300h200v-300h300v200h-200v100h200v500h-600v-200zM800 800v300h300v-300h-300zM900 0v100h300v-100h-300zM900 900v100h100v-100h-100zM1100 200v100h100v-100h-100z" />
<glyph unicode="&#xe040;" d="M0 200h100v1000h-100v-1000zM100 0v100h300v-100h-300zM200 200v1000h100v-1000h-100zM500 0v91h100v-91h-100zM500 200v1000h200v-1000h-200zM700 0v91h100v-91h-100zM800 200v1000h100v-1000h-100zM900 0v91h200v-91h-200zM1000 200v1000h200v-1000h-200z" />
<glyph unicode="&#xe041;" d="M0 700l1 475q0 10 7.5 17.5t17.5 7.5h474l700 -700l-500 -500zM148 953q0 -42 29 -71q30 -30 71.5 -30t71.5 30q29 29 29 71t-29 71q-30 30 -71.5 30t-71.5 -30q-29 -29 -29 -71z" />
<glyph unicode="&#xe042;" d="M1 700l1 475q0 11 7 18t18 7h474l700 -700l-500 -500zM148 953q0 -42 30 -71q29 -30 71 -30t71 30q30 29 30 71t-30 71q-29 30 -71 30t-71 -30q-30 -29 -30 -71zM701 1200h100l700 -700l-500 -500l-50 50l450 450z" />
<glyph unicode="&#xe043;" d="M100 0v1025l175 175h925v-1000l-100 -100v1000h-750l-100 -100h750v-1000h-900z" />
<glyph unicode="&#xe044;" d="M200 0l450 444l450 -443v1150q0 20 -14.5 35t-35.5 15h-800q-21 0 -35.5 -15t-14.5 -35v-1151z" />
<glyph unicode="&#xe045;" d="M0 100v700h200l100 -200h600l100 200h200v-700h-200v200h-800v-200h-200zM253 829l40 -124h592l62 124l-94 346q-2 11 -10 18t-18 7h-450q-10 0 -18 -7t-10 -18zM281 24l38 152q2 10 11.5 17t19.5 7h500q10 0 19.5 -7t11.5 -17l38 -152q2 -10 -3.5 -17t-15.5 -7h-600 q-10 0 -15.5 7t-3.5 17z" />
<glyph unicode="&#xe046;" d="M0 200q0 -41 29.5 -70.5t70.5 -29.5h1000q41 0 70.5 29.5t29.5 70.5v600q0 41 -29.5 70.5t-70.5 29.5h-150q-4 8 -11.5 21.5t-33 48t-53 61t-69 48t-83.5 21.5h-200q-41 0 -82 -20.5t-70 -50t-52 -59t-34 -50.5l-12 -20h-150q-41 0 -70.5 -29.5t-29.5 -70.5v-600z M356 500q0 100 72 172t172 72t172 -72t72 -172t-72 -172t-172 -72t-172 72t-72 172zM494 500q0 -44 31 -75t75 -31t75 31t31 75t-31 75t-75 31t-75 -31t-31 -75zM900 700v100h100v-100h-100z" />
<glyph unicode="&#xe047;" d="M53 0h365v66q-41 0 -72 11t-49 38t1 71l92 234h391l82 -222q16 -45 -5.5 -88.5t-74.5 -43.5v-66h417v66q-34 1 -74 43q-18 19 -33 42t-21 37l-6 13l-385 998h-93l-399 -1006q-24 -48 -52 -75q-12 -12 -33 -25t-36 -20l-15 -7v-66zM416 521l178 457l46 -140l116 -317h-340 z" />
<glyph unicode="&#xe048;" d="M100 0v89q41 7 70.5 32.5t29.5 65.5v827q0 28 -1 39.5t-5.5 26t-15.5 21t-29 14t-49 14.5v71l471 -1q120 0 213 -88t93 -228q0 -55 -11.5 -101.5t-28 -74t-33.5 -47.5t-28 -28l-12 -7q8 -3 21.5 -9t48 -31.5t60.5 -58t47.5 -91.5t21.5 -129q0 -84 -59 -156.5t-142 -111 t-162 -38.5h-500zM400 200h161q89 0 153 48.5t64 132.5q0 90 -62.5 154.5t-156.5 64.5h-159v-400zM400 700h139q76 0 130 61.5t54 138.5q0 82 -84 130.5t-239 48.5v-379z" />
<glyph unicode="&#xe049;" d="M200 0v57q77 7 134.5 40.5t65.5 80.5l173 849q10 56 -10 74t-91 37q-6 1 -10.5 2.5t-9.5 2.5v57h425l2 -57q-33 -8 -62 -25.5t-46 -37t-29.5 -38t-17.5 -30.5l-5 -12l-128 -825q-10 -52 14 -82t95 -36v-57h-500z" />
<glyph unicode="&#xe050;" d="M-75 200h75v800h-75l125 167l125 -167h-75v-800h75l-125 -167zM300 900v300h150h700h150v-300h-50q0 29 -8 48.5t-18.5 30t-33.5 15t-39.5 5.5t-50.5 1h-200v-850l100 -50v-100h-400v100l100 50v850h-200q-34 0 -50.5 -1t-40 -5.5t-33.5 -15t-18.5 -30t-8.5 -48.5h-49z " />
<glyph unicode="&#xe051;" d="M33 51l167 125v-75h800v75l167 -125l-167 -125v75h-800v-75zM100 901v300h150h700h150v-300h-50q0 29 -8 48.5t-18 30t-33.5 15t-40 5.5t-50.5 1h-200v-650l100 -50v-100h-400v100l100 50v650h-200q-34 0 -50.5 -1t-39.5 -5.5t-33.5 -15t-18.5 -30t-8 -48.5h-50z" />
<glyph unicode="&#xe052;" d="M0 50q0 -20 14.5 -35t35.5 -15h1100q21 0 35.5 15t14.5 35v100q0 21 -14.5 35.5t-35.5 14.5h-1100q-21 0 -35.5 -14.5t-14.5 -35.5v-100zM0 350q0 -20 14.5 -35t35.5 -15h800q21 0 35.5 15t14.5 35v100q0 21 -14.5 35.5t-35.5 14.5h-800q-21 0 -35.5 -14.5t-14.5 -35.5 v-100zM0 650q0 -20 14.5 -35t35.5 -15h1000q21 0 35.5 15t14.5 35v100q0 21 -14.5 35.5t-35.5 14.5h-1000q-21 0 -35.5 -14.5t-14.5 -35.5v-100zM0 950q0 -20 14.5 -35t35.5 -15h600q21 0 35.5 15t14.5 35v100q0 21 -14.5 35.5t-35.5 14.5h-600q-21 0 -35.5 -14.5 t-14.5 -35.5v-100z" />
<glyph unicode="&#xe053;" d="M0 50q0 -20 14.5 -35t35.5 -15h1100q21 0 35.5 15t14.5 35v100q0 21 -14.5 35.5t-35.5 14.5h-1100q-21 0 -35.5 -14.5t-14.5 -35.5v-100zM0 650q0 -20 14.5 -35t35.5 -15h1100q21 0 35.5 15t14.5 35v100q0 21 -14.5 35.5t-35.5 14.5h-1100q-21 0 -35.5 -14.5t-14.5 -35.5 v-100zM200 350q0 -20 14.5 -35t35.5 -15h700q21 0 35.5 15t14.5 35v100q0 21 -14.5 35.5t-35.5 14.5h-700q-21 0 -35.5 -14.5t-14.5 -35.5v-100zM200 950q0 -20 14.5 -35t35.5 -15h700q21 0 35.5 15t14.5 35v100q0 21 -14.5 35.5t-35.5 14.5h-700q-21 0 -35.5 -14.5 t-14.5 -35.5v-100z" />
<glyph unicode="&#xe054;" d="M0 50v100q0 21 14.5 35.5t35.5 14.5h1100q21 0 35.5 -14.5t14.5 -35.5v-100q0 -20 -14.5 -35t-35.5 -15h-1100q-21 0 -35.5 15t-14.5 35zM100 650v100q0 21 14.5 35.5t35.5 14.5h1000q21 0 35.5 -14.5t14.5 -35.5v-100q0 -20 -14.5 -35t-35.5 -15h-1000q-21 0 -35.5 15 t-14.5 35zM300 350v100q0 21 14.5 35.5t35.5 14.5h800q21 0 35.5 -14.5t14.5 -35.5v-100q0 -20 -14.5 -35t-35.5 -15h-800q-21 0 -35.5 15t-14.5 35zM500 950v100q0 21 14.5 35.5t35.5 14.5h600q21 0 35.5 -14.5t14.5 -35.5v-100q0 -20 -14.5 -35t-35.5 -15h-600 q-21 0 -35.5 15t-14.5 35z" />
<glyph unicode="&#xe055;" d="M0 50v100q0 21 14.5 35.5t35.5 14.5h1100q21 0 35.5 -14.5t14.5 -35.5v-100q0 -20 -14.5 -35t-35.5 -15h-1100q-21 0 -35.5 15t-14.5 35zM0 350v100q0 21 14.5 35.5t35.5 14.5h1100q21 0 35.5 -14.5t14.5 -35.5v-100q0 -20 -14.5 -35t-35.5 -15h-1100q-21 0 -35.5 15 t-14.5 35zM0 650v100q0 21 14.5 35.5t35.5 14.5h1100q21 0 35.5 -14.5t14.5 -35.5v-100q0 -20 -14.5 -35t-35.5 -15h-1100q-21 0 -35.5 15t-14.5 35zM0 950v100q0 21 14.5 35.5t35.5 14.5h1100q21 0 35.5 -14.5t14.5 -35.5v-100q0 -20 -14.5 -35t-35.5 -15h-1100 q-21 0 -35.5 15t-14.5 35z" />
<glyph unicode="&#xe056;" d="M0 50v100q0 21 14.5 35.5t35.5 14.5h100q21 0 35.5 -14.5t14.5 -35.5v-100q0 -20 -14.5 -35t-35.5 -15h-100q-21 0 -35.5 15t-14.5 35zM0 350v100q0 21 14.5 35.5t35.5 14.5h100q21 0 35.5 -14.5t14.5 -35.5v-100q0 -20 -14.5 -35t-35.5 -15h-100q-21 0 -35.5 15 t-14.5 35zM0 650v100q0 21 14.5 35.5t35.5 14.5h100q21 0 35.5 -14.5t14.5 -35.5v-100q0 -20 -14.5 -35t-35.5 -15h-100q-21 0 -35.5 15t-14.5 35zM0 950v100q0 21 14.5 35.5t35.5 14.5h100q21 0 35.5 -14.5t14.5 -35.5v-100q0 -20 -14.5 -35t-35.5 -15h-100q-21 0 -35.5 15 t-14.5 35zM300 50v100q0 21 14.5 35.5t35.5 14.5h800q21 0 35.5 -14.5t14.5 -35.5v-100q0 -20 -14.5 -35t-35.5 -15h-800q-21 0 -35.5 15t-14.5 35zM300 350v100q0 21 14.5 35.5t35.5 14.5h800q21 0 35.5 -14.5t14.5 -35.5v-100q0 -20 -14.5 -35t-35.5 -15h-800 q-21 0 -35.5 15t-14.5 35zM300 650v100q0 21 14.5 35.5t35.5 14.5h800q21 0 35.5 -14.5t14.5 -35.5v-100q0 -20 -14.5 -35t-35.5 -15h-800q-21 0 -35.5 15t-14.5 35zM300 950v100q0 21 14.5 35.5t35.5 14.5h800q21 0 35.5 -14.5t14.5 -35.5v-100q0 -20 -14.5 -35t-35.5 -15 h-800q-21 0 -35.5 15t-14.5 35z" />
<glyph unicode="&#xe057;" d="M-101 500v100h201v75l166 -125l-166 -125v75h-201zM300 0h100v1100h-100v-1100zM500 50q0 -20 14.5 -35t35.5 -15h600q20 0 35 15t15 35v100q0 21 -15 35.5t-35 14.5h-600q-21 0 -35.5 -14.5t-14.5 -35.5v-100zM500 350q0 -20 14.5 -35t35.5 -15h300q20 0 35 15t15 35 v100q0 21 -15 35.5t-35 14.5h-300q-21 0 -35.5 -14.5t-14.5 -35.5v-100zM500 650q0 -20 14.5 -35t35.5 -15h500q20 0 35 15t15 35v100q0 21 -15 35.5t-35 14.5h-500q-21 0 -35.5 -14.5t-14.5 -35.5v-100zM500 950q0 -20 14.5 -35t35.5 -15h100q20 0 35 15t15 35v100 q0 21 -15 35.5t-35 14.5h-100q-21 0 -35.5 -14.5t-14.5 -35.5v-100z" />
<glyph unicode="&#xe058;" d="M1 50q0 -20 14.5 -35t35.5 -15h600q20 0 35 15t15 35v100q0 21 -15 35.5t-35 14.5h-600q-21 0 -35.5 -14.5t-14.5 -35.5v-100zM1 350q0 -20 14.5 -35t35.5 -15h300q20 0 35 15t15 35v100q0 21 -15 35.5t-35 14.5h-300q-21 0 -35.5 -14.5t-14.5 -35.5v-100zM1 650 q0 -20 14.5 -35t35.5 -15h500q20 0 35 15t15 35v100q0 21 -15 35.5t-35 14.5h-500q-21 0 -35.5 -14.5t-14.5 -35.5v-100zM1 950q0 -20 14.5 -35t35.5 -15h100q20 0 35 15t15 35v100q0 21 -15 35.5t-35 14.5h-100q-21 0 -35.5 -14.5t-14.5 -35.5v-100zM801 0v1100h100v-1100 h-100zM934 550l167 -125v75h200v100h-200v75z" />
<glyph unicode="&#xe059;" d="M0 275v650q0 31 22 53t53 22h750q31 0 53 -22t22 -53v-650q0 -31 -22 -53t-53 -22h-750q-31 0 -53 22t-22 53zM900 600l300 300v-600z" />
<glyph unicode="&#xe060;" d="M0 44v1012q0 18 13 31t31 13h1112q19 0 31.5 -13t12.5 -31v-1012q0 -18 -12.5 -31t-31.5 -13h-1112q-18 0 -31 13t-13 31zM100 263l247 182l298 -131l-74 156l293 318l236 -288v500h-1000v-737zM208 750q0 56 39 95t95 39t95 -39t39 -95t-39 -95t-95 -39t-95 39t-39 95z " />
<glyph unicode="&#xe062;" d="M148 745q0 124 60.5 231.5t165 172t226.5 64.5q123 0 227 -63t164.5 -169.5t60.5 -229.5t-73 -272q-73 -114 -166.5 -237t-150.5 -189l-57 -66q-10 9 -27 26t-66.5 70.5t-96 109t-104 135.5t-100.5 155q-63 139 -63 262zM342 772q0 -107 75.5 -182.5t181.5 -75.5 q107 0 182.5 75.5t75.5 182.5t-75.5 182t-182.5 75t-182 -75.5t-75 -181.5z" />
<glyph unicode="&#xe063;" d="M1 600q0 122 47.5 233t127.5 191t191 127.5t233 47.5t233 -47.5t191 -127.5t127.5 -191t47.5 -233t-47.5 -233t-127.5 -191t-191 -127.5t-233 -47.5t-233 47.5t-191 127.5t-127.5 191t-47.5 233zM173 600q0 -177 125.5 -302t301.5 -125v854q-176 0 -301.5 -125 t-125.5 -302z" />
<glyph unicode="&#xe064;" d="M117 406q0 94 34 186t88.5 172.5t112 159t115 177t87.5 194.5q21 -71 57.5 -142.5t76 -130.5t83 -118.5t82 -117t70 -116t50 -125.5t18.5 -136q0 -89 -39 -165.5t-102 -126.5t-140 -79.5t-156 -33.5q-114 6 -211.5 53t-161.5 139t-64 210zM243 414q14 -82 59.5 -136 t136.5 -80l16 98q-7 6 -18 17t-34 48t-33 77q-15 73 -14 143.5t10 122.5l9 51q-92 -110 -119.5 -185t-12.5 -156z" />
<glyph unicode="&#xe065;" d="M0 400v300q0 165 117.5 282.5t282.5 117.5q366 -6 397 -14l-186 -186h-311q-41 0 -70.5 -29.5t-29.5 -70.5v-500q0 -41 29.5 -70.5t70.5 -29.5h500q41 0 70.5 29.5t29.5 70.5v125l200 200v-225q0 -165 -117.5 -282.5t-282.5 -117.5h-300q-165 0 -282.5 117.5 t-117.5 282.5zM436 341l161 50l412 412l-114 113l-405 -405zM995 1015l113 -113l113 113l-21 85l-92 28z" />
<glyph unicode="&#xe066;" d="M0 400v300q0 165 117.5 282.5t282.5 117.5h261l2 -80q-133 -32 -218 -120h-145q-41 0 -70.5 -29.5t-29.5 -70.5v-500q0 -41 29.5 -70.5t70.5 -29.5h500q41 0 70.5 29.5t29.5 70.5l200 153v-53q0 -165 -117.5 -282.5t-282.5 -117.5h-300q-165 0 -282.5 117.5t-117.5 282.5 zM423 524q30 38 81.5 64t103 35.5t99 14t77.5 3.5l29 -1v-209l360 324l-359 318v-216q-7 0 -19 -1t-48 -8t-69.5 -18.5t-76.5 -37t-76.5 -59t-62 -88t-39.5 -121.5z" />
<glyph unicode="&#xe067;" d="M0 400v300q0 165 117.5 282.5t282.5 117.5h300q61 0 127 -23l-178 -177h-349q-41 0 -70.5 -29.5t-29.5 -70.5v-500q0 -41 29.5 -70.5t70.5 -29.5h500q41 0 70.5 29.5t29.5 70.5v69l200 200v-169q0 -165 -117.5 -282.5t-282.5 -117.5h-300q-165 0 -282.5 117.5 t-117.5 282.5zM342 632l283 -284l567 567l-137 137l-430 -431l-146 147z" />
<glyph unicode="&#xe068;" d="M0 603l300 296v-198h200v200h-200l300 300l295 -300h-195v-200h200v198l300 -296l-300 -300v198h-200v-200h195l-295 -300l-300 300h200v200h-200v-198z" />
<glyph unicode="&#xe069;" d="M200 50v1000q0 21 14.5 35.5t35.5 14.5h100q21 0 35.5 -14.5t14.5 -35.5v-437l500 487v-1100l-500 488v-438q0 -21 -14.5 -35.5t-35.5 -14.5h-100q-21 0 -35.5 14.5t-14.5 35.5z" />
<glyph unicode="&#xe070;" d="M0 50v1000q0 21 14.5 35.5t35.5 14.5h100q21 0 35.5 -14.5t14.5 -35.5v-437l500 487v-487l500 487v-1100l-500 488v-488l-500 488v-438q0 -21 -14.5 -35.5t-35.5 -14.5h-100q-21 0 -35.5 14.5t-14.5 35.5z" />
<glyph unicode="&#xe071;" d="M136 550l564 550v-487l500 487v-1100l-500 488v-488z" />
<glyph unicode="&#xe072;" d="M200 0l900 550l-900 550v-1100z" />
<glyph unicode="&#xe073;" d="M200 150q0 -21 14.5 -35.5t35.5 -14.5h200q21 0 35.5 14.5t14.5 35.5v800q0 21 -14.5 35.5t-35.5 14.5h-200q-21 0 -35.5 -14.5t-14.5 -35.5v-800zM600 150q0 -21 14.5 -35.5t35.5 -14.5h200q21 0 35.5 14.5t14.5 35.5v800q0 21 -14.5 35.5t-35.5 14.5h-200 q-21 0 -35.5 -14.5t-14.5 -35.5v-800z" />
<glyph unicode="&#xe074;" d="M200 150q0 -20 14.5 -35t35.5 -15h800q21 0 35.5 15t14.5 35v800q0 21 -14.5 35.5t-35.5 14.5h-800q-21 0 -35.5 -14.5t-14.5 -35.5v-800z" />
<glyph unicode="&#xe075;" d="M0 0v1100l500 -487v487l564 -550l-564 -550v488z" />
<glyph unicode="&#xe076;" d="M0 0v1100l500 -487v487l500 -487v437q0 21 14.5 35.5t35.5 14.5h100q21 0 35.5 -14.5t14.5 -35.5v-1000q0 -21 -14.5 -35.5t-35.5 -14.5h-100q-21 0 -35.5 14.5t-14.5 35.5v438l-500 -488v488z" />
<glyph unicode="&#xe077;" d="M300 0v1100l500 -487v437q0 21 14.5 35.5t35.5 14.5h100q21 0 35.5 -14.5t14.5 -35.5v-1000q0 -21 -14.5 -35.5t-35.5 -14.5h-100q-21 0 -35.5 14.5t-14.5 35.5v438z" />
<glyph unicode="&#xe078;" d="M100 250v100q0 21 14.5 35.5t35.5 14.5h1000q21 0 35.5 -14.5t14.5 -35.5v-100q0 -21 -14.5 -35.5t-35.5 -14.5h-1000q-21 0 -35.5 14.5t-14.5 35.5zM100 500h1100l-550 564z" />
<glyph unicode="&#xe079;" d="M185 599l592 -592l240 240l-353 353l353 353l-240 240z" />
<glyph unicode="&#xe080;" d="M272 194l353 353l-353 353l241 240l572 -571l21 -22l-1 -1v-1l-592 -591z" />
<glyph unicode="&#xe081;" d="M3 600q0 162 80 299.5t217.5 217.5t299.5 80t299.5 -80t217.5 -217.5t80 -299.5t-80 -299.5t-217.5 -217.5t-299.5 -80t-299.5 80t-217.5 217.5t-80 299.5zM300 500h200v-200h200v200h200v200h-200v200h-200v-200h-200v-200z" />
<glyph unicode="&#xe082;" d="M3 600q0 162 80 299.5t217.5 217.5t299.5 80t299.5 -80t217.5 -217.5t80 -299.5t-80 -299.5t-217.5 -217.5t-299.5 -80t-299.5 80t-217.5 217.5t-80 299.5zM300 500h600v200h-600v-200z" />
<glyph unicode="&#xe083;" d="M3 600q0 162 80 299.5t217.5 217.5t299.5 80t299.5 -80t217.5 -217.5t80 -299.5t-80 -299.5t-217.5 -217.5t-299.5 -80t-299.5 80t-217.5 217.5t-80 299.5zM246 459l213 -213l141 142l141 -142l213 213l-142 141l142 141l-213 212l-141 -141l-141 142l-212 -213l141 -141 z" />
<glyph unicode="&#xe084;" d="M3 600q0 162 80 299.5t217.5 217.5t299.5 80t299.5 -80t217.5 -217.5t80 -299.5t-80 -299.5t-217.5 -217.5t-299.5 -80t-299.5 80t-217.5 217.5t-80 299.5zM270 551l276 -277l411 411l-175 174l-236 -236l-102 102z" />
<glyph unicode="&#xe085;" d="M3 600q0 162 80 299.5t217.5 217.5t299.5 80t299.5 -80t217.5 -217.5t80 -299.5t-80 -299.5t-217.5 -217.5t-299.5 -80t-299.5 80t-217.5 217.5t-80 299.5zM364 700h143q4 0 11.5 -1t11 -1t6.5 3t3 9t1 11t3.5 8.5t3.5 6t5.5 4t6.5 2.5t9 1.5t9 0.5h11.5h12.5 q19 0 30 -10t11 -26q0 -22 -4 -28t-27 -22q-5 -1 -12.5 -3t-27 -13.5t-34 -27t-26.5 -46t-11 -68.5h200q5 3 14 8t31.5 25.5t39.5 45.5t31 69t14 94q0 51 -17.5 89t-42 58t-58.5 32t-58.5 15t-51.5 3q-50 0 -90.5 -12t-75 -38.5t-53.5 -74.5t-19 -114zM500 300h200v100h-200 v-100z" />
<glyph unicode="&#xe086;" d="M3 600q0 162 80 299.5t217.5 217.5t299.5 80t299.5 -80t217.5 -217.5t80 -299.5t-80 -299.5t-217.5 -217.5t-299.5 -80t-299.5 80t-217.5 217.5t-80 299.5zM400 300h400v100h-100v300h-300v-100h100v-200h-100v-100zM500 800h200v100h-200v-100z" />
<glyph unicode="&#xe087;" d="M0 500v200h195q31 125 98.5 199.5t206.5 100.5v200h200v-200q54 -20 113 -60t112.5 -105.5t71.5 -134.5h203v-200h-203q-25 -102 -116.5 -186t-180.5 -117v-197h-200v197q-140 27 -208 102.5t-98 200.5h-194zM290 500q24 -73 79.5 -127.5t130.5 -78.5v206h200v-206 q149 48 201 206h-201v200h200q-25 74 -75.5 127t-124.5 77v-204h-200v203q-75 -23 -130 -77t-79 -126h209v-200h-210z" />
<glyph unicode="&#xe088;" d="M4 600q0 162 80 299t217 217t299 80t299 -80t217 -217t80 -299t-80 -299t-217 -217t-299 -80t-299 80t-217 217t-80 299zM186 600q0 -171 121.5 -292.5t292.5 -121.5t292.5 121.5t121.5 292.5t-121.5 292.5t-292.5 121.5t-292.5 -121.5t-121.5 -292.5zM356 465l135 135 l-135 135l109 109l135 -135l135 135l109 -109l-135 -135l135 -135l-109 -109l-135 135l-135 -135z" />
<glyph unicode="&#xe089;" d="M4 600q0 162 80 299t217 217t299 80t299 -80t217 -217t80 -299t-80 -299t-217 -217t-299 -80t-299 80t-217 217t-80 299zM186 600q0 -171 121.5 -292.5t292.5 -121.5t292.5 121.5t121.5 292.5t-121.5 292.5t-292.5 121.5t-292.5 -121.5t-121.5 -292.5zM322 537l141 141 l87 -87l204 205l142 -142l-346 -345z" />
<glyph unicode="&#xe090;" d="M4 600q0 162 80 299t217 217t299 80t299 -80t217 -217t80 -299t-80 -299t-217 -217t-299 -80t-299 80t-217 217t-80 299zM186 600q0 -115 62 -215l568 567q-100 62 -216 62q-171 0 -292.5 -121.5t-121.5 -292.5zM391 245q97 -59 209 -59q171 0 292.5 121.5t121.5 292.5 q0 112 -59 209z" />
<glyph unicode="&#xe091;" d="M0 547l600 453v-300h600v-300h-600v-301z" />
<glyph unicode="&#xe092;" d="M0 400v300h600v300l600 -453l-600 -448v301h-600z" />
<glyph unicode="&#xe093;" d="M204 600l450 600l444 -600h-298v-600h-300v600h-296z" />
<glyph unicode="&#xe094;" d="M104 600h296v600h300v-600h298l-449 -600z" />
<glyph unicode="&#xe095;" d="M0 200q6 132 41 238.5t103.5 193t184 138t271.5 59.5v271l600 -453l-600 -448v301q-95 -2 -183 -20t-170 -52t-147 -92.5t-100 -135.5z" />
<glyph unicode="&#xe096;" d="M0 0v400l129 -129l294 294l142 -142l-294 -294l129 -129h-400zM635 777l142 -142l294 294l129 -129v400h-400l129 -129z" />
<glyph unicode="&#xe097;" d="M34 176l295 295l-129 129h400v-400l-129 130l-295 -295zM600 600v400l129 -129l295 295l142 -141l-295 -295l129 -130h-400z" />
<glyph unicode="&#xe101;" d="M23 600q0 118 45.5 224.5t123 184t184 123t224.5 45.5t224.5 -45.5t184 -123t123 -184t45.5 -224.5t-45.5 -224.5t-123 -184t-184 -123t-224.5 -45.5t-224.5 45.5t-184 123t-123 184t-45.5 224.5zM456 851l58 -302q4 -20 21.5 -34.5t37.5 -14.5h54q20 0 37.5 14.5 t21.5 34.5l58 302q4 20 -8 34.5t-32 14.5h-207q-21 0 -33 -14.5t-8 -34.5zM500 300h200v100h-200v-100z" />
<glyph unicode="&#xe102;" d="M0 800h100v-200h400v300h200v-300h400v200h100v100h-111q1 1 1 6.5t-1.5 15t-3.5 17.5l-34 172q-11 39 -41.5 63t-69.5 24q-32 0 -61 -17l-239 -144q-22 -13 -40 -35q-19 24 -40 36l-238 144q-33 18 -62 18q-39 0 -69.5 -23t-40.5 -61l-35 -177q-2 -8 -3 -18t-1 -15v-6 h-111v-100zM100 0h400v400h-400v-400zM200 900q-3 0 14 48t36 96l18 47l213 -191h-281zM700 0v400h400v-400h-400zM731 900l202 197q5 -12 12 -32.5t23 -64t25 -72t7 -28.5h-269z" />
<glyph unicode="&#xe103;" d="M0 -22v143l216 193q-9 53 -13 83t-5.5 94t9 113t38.5 114t74 124q47 60 99.5 102.5t103 68t127.5 48t145.5 37.5t184.5 43.5t220 58.5q0 -189 -22 -343t-59 -258t-89 -181.5t-108.5 -120t-122 -68t-125.5 -30t-121.5 -1.5t-107.5 12.5t-87.5 17t-56.5 7.5l-99 -55z M238.5 300.5q19.5 -6.5 86.5 76.5q55 66 367 234q70 38 118.5 69.5t102 79t99 111.5t86.5 148q22 50 24 60t-6 19q-7 5 -17 5t-26.5 -14.5t-33.5 -39.5q-35 -51 -113.5 -108.5t-139.5 -89.5l-61 -32q-369 -197 -458 -401q-48 -111 -28.5 -117.5z" />
<glyph unicode="&#xe104;" d="M111 408q0 -33 5 -63q9 -56 44 -119.5t105 -108.5q31 -21 64 -16t62 23.5t57 49.5t48 61.5t35 60.5q32 66 39 184.5t-13 157.5q79 -80 122 -164t26 -184q-5 -33 -20.5 -69.5t-37.5 -80.5q-10 -19 -14.5 -29t-12 -26t-9 -23.5t-3 -19t2.5 -15.5t11 -9.5t19.5 -5t30.5 2.5 t42 8q57 20 91 34t87.5 44.5t87 64t65.5 88.5t47 122q38 172 -44.5 341.5t-246.5 278.5q22 -44 43 -129q39 -159 -32 -154q-15 2 -33 9q-79 33 -120.5 100t-44 175.5t48.5 257.5q-13 -8 -34 -23.5t-72.5 -66.5t-88.5 -105.5t-60 -138t-8 -166.5q2 -12 8 -41.5t8 -43t6 -39.5 t3.5 -39.5t-1 -33.5t-6 -31.5t-13.5 -24t-21 -20.5t-31 -12q-38 -10 -67 13t-40.5 61.5t-15 81.5t10.5 75q-52 -46 -83.5 -101t-39 -107t-7.5 -85z" />
<glyph unicode="&#xe105;" d="M-61 600l26 40q6 10 20 30t49 63.5t74.5 85.5t97 90t116.5 83.5t132.5 59t145.5 23.5t145.5 -23.5t132.5 -59t116.5 -83.5t97 -90t74.5 -85.5t49 -63.5t20 -30l26 -40l-26 -40q-6 -10 -20 -30t-49 -63.5t-74.5 -85.5t-97 -90t-116.5 -83.5t-132.5 -59t-145.5 -23.5 t-145.5 23.5t-132.5 59t-116.5 83.5t-97 90t-74.5 85.5t-49 63.5t-20 30zM120 600q7 -10 40.5 -58t56 -78.5t68 -77.5t87.5 -75t103 -49.5t125 -21.5t123.5 20t100.5 45.5t85.5 71.5t66.5 75.5t58 81.5t47 66q-1 1 -28.5 37.5t-42 55t-43.5 53t-57.5 63.5t-58.5 54 q49 -74 49 -163q0 -124 -88 -212t-212 -88t-212 88t-88 212q0 85 46 158q-102 -87 -226 -258zM377 656q49 -124 154 -191l105 105q-37 24 -75 72t-57 84l-20 36z" />
<glyph unicode="&#xe106;" d="M-61 600l26 40q6 10 20 30t49 63.5t74.5 85.5t97 90t116.5 83.5t132.5 59t145.5 23.5q61 0 121 -17l37 142h148l-314 -1200h-148l37 143q-82 21 -165 71.5t-140 102t-109.5 112t-72 88.5t-29.5 43zM120 600q210 -282 393 -336l37 141q-107 18 -178.5 101.5t-71.5 193.5 q0 85 46 158q-102 -87 -226 -258zM377 656q49 -124 154 -191l47 47l23 87q-30 28 -59 69t-44 68l-14 26zM780 161l38 145q22 15 44.5 34t46 44t40.5 44t41 50.5t33.5 43.5t33 44t24.5 34q-97 127 -140 175l39 146q67 -54 131.5 -125.5t87.5 -103.5t36 -52l26 -40l-26 -40 q-7 -12 -25.5 -38t-63.5 -79.5t-95.5 -102.5t-124 -100t-146.5 -79z" />
<glyph unicode="&#xe107;" d="M-97.5 34q13.5 -34 50.5 -34h1294q37 0 50.5 35.5t-7.5 67.5l-642 1056q-20 34 -48 36.5t-48 -29.5l-642 -1066q-21 -32 -7.5 -66zM155 200l445 723l445 -723h-345v100h-200v-100h-345zM500 600l100 -300l100 300v100h-200v-100z" />
<glyph unicode="&#xe108;" d="M100 262v41q0 20 11 44.5t26 38.5l363 325v339q0 62 44 106t106 44t106 -44t44 -106v-339l363 -325q15 -14 26 -38.5t11 -44.5v-41q0 -20 -12 -26.5t-29 5.5l-359 249v-263q100 -91 100 -113v-64q0 -20 -13 -28.5t-32 0.5l-94 78h-222l-94 -78q-19 -9 -32 -0.5t-13 28.5 v64q0 22 100 113v263l-359 -249q-17 -12 -29 -5.5t-12 26.5z" />
<glyph unicode="&#xe109;" d="M0 50q0 -20 14.5 -35t35.5 -15h1000q21 0 35.5 15t14.5 35v750h-1100v-750zM0 900h1100v150q0 21 -14.5 35.5t-35.5 14.5h-150v100h-100v-100h-500v100h-100v-100h-150q-21 0 -35.5 -14.5t-14.5 -35.5v-150zM100 100v100h100v-100h-100zM100 300v100h100v-100h-100z M100 500v100h100v-100h-100zM300 100v100h100v-100h-100zM300 300v100h100v-100h-100zM300 500v100h100v-100h-100zM500 100v100h100v-100h-100zM500 300v100h100v-100h-100zM500 500v100h100v-100h-100zM700 100v100h100v-100h-100zM700 300v100h100v-100h-100zM700 500 v100h100v-100h-100zM900 100v100h100v-100h-100zM900 300v100h100v-100h-100zM900 500v100h100v-100h-100z" />
<glyph unicode="&#xe110;" d="M0 200v200h259l600 600h241v198l300 -295l-300 -300v197h-159l-600 -600h-341zM0 800h259l122 -122l141 142l-181 180h-341v-200zM678 381l141 142l122 -123h159v198l300 -295l-300 -300v197h-241z" />
<glyph unicode="&#xe111;" d="M0 400v600q0 41 29.5 70.5t70.5 29.5h1000q41 0 70.5 -29.5t29.5 -70.5v-600q0 -41 -29.5 -70.5t-70.5 -29.5h-596l-304 -300v300h-100q-41 0 -70.5 29.5t-29.5 70.5z" />
<glyph unicode="&#xe112;" d="M100 600v200h300v-250q0 -113 6 -145q17 -92 102 -117q39 -11 92 -11q37 0 66.5 5.5t50 15.5t36 24t24 31.5t14 37.5t7 42t2.5 45t0 47v25v250h300v-200q0 -42 -3 -83t-15 -104t-31.5 -116t-58 -109.5t-89 -96.5t-129 -65.5t-174.5 -25.5t-174.5 25.5t-129 65.5t-89 96.5 t-58 109.5t-31.5 116t-15 104t-3 83zM100 900v300h300v-300h-300zM800 900v300h300v-300h-300z" />
<glyph unicode="&#xe113;" d="M-30 411l227 -227l352 353l353 -353l226 227l-578 579z" />
<glyph unicode="&#xe114;" d="M70 797l580 -579l578 579l-226 227l-353 -353l-352 353z" />
<glyph unicode="&#xe115;" d="M-198 700l299 283l300 -283h-203v-400h385l215 -200h-800v600h-196zM402 1000l215 -200h381v-400h-198l299 -283l299 283h-200v600h-796z" />
<glyph unicode="&#xe116;" d="M18 939q-5 24 10 42q14 19 39 19h896l38 162q5 17 18.5 27.5t30.5 10.5h94q20 0 35 -14.5t15 -35.5t-15 -35.5t-35 -14.5h-54l-201 -961q-2 -4 -6 -10.5t-19 -17.5t-33 -11h-31v-50q0 -20 -14.5 -35t-35.5 -15t-35.5 15t-14.5 35v50h-300v-50q0 -20 -14.5 -35t-35.5 -15 t-35.5 15t-14.5 35v50h-50q-21 0 -35.5 15t-14.5 35q0 21 14.5 35.5t35.5 14.5h535l48 200h-633q-32 0 -54.5 21t-27.5 43z" />
<glyph unicode="&#xe117;" d="M0 0v800h1200v-800h-1200zM0 900v100h200q0 41 29.5 70.5t70.5 29.5h300q41 0 70.5 -29.5t29.5 -70.5h500v-100h-1200z" />
<glyph unicode="&#xe118;" d="M1 0l300 700h1200l-300 -700h-1200zM1 400v600h200q0 41 29.5 70.5t70.5 29.5h300q41 0 70.5 -29.5t29.5 -70.5h500v-200h-1000z" />
<glyph unicode="&#xe119;" d="M302 300h198v600h-198l298 300l298 -300h-198v-600h198l-298 -300z" />
<glyph unicode="&#xe120;" d="M0 600l300 298v-198h600v198l300 -298l-300 -297v197h-600v-197z" />
<glyph unicode="&#xe121;" d="M0 100v100q0 41 29.5 70.5t70.5 29.5h1000q41 0 70.5 -29.5t29.5 -70.5v-100q0 -41 -29.5 -70.5t-70.5 -29.5h-1000q-41 0 -70.5 29.5t-29.5 70.5zM31 400l172 739q5 22 23 41.5t38 19.5h672q19 0 37.5 -22.5t23.5 -45.5l172 -732h-1138zM800 100h100v100h-100v-100z M1000 100h100v100h-100v-100z" />
<glyph unicode="&#xe122;" d="M-101 600v50q0 24 25 49t50 38l25 13v-250l-11 5.5t-24 14t-30 21.5t-24 27.5t-11 31.5zM100 500v250v8v8v7t0.5 7t1.5 5.5t2 5t3 4t4.5 3.5t6 1.5t7.5 0.5h200l675 250v-850l-675 200h-38l47 -276q2 -12 -3 -17.5t-11 -6t-21 -0.5h-8h-83q-20 0 -34.5 14t-18.5 35 q-55 337 -55 351zM1100 200v850q0 21 14.5 35.5t35.5 14.5q20 0 35 -14.5t15 -35.5v-850q0 -20 -15 -35t-35 -15q-21 0 -35.5 15t-14.5 35z" />
<glyph unicode="&#xe123;" d="M74 350q0 21 13.5 35.5t33.5 14.5h18l117 173l63 327q15 77 76 140t144 83l-18 32q-6 19 3 32t29 13h94q20 0 29 -10.5t3 -29.5q-18 -36 -18 -37q83 -19 144 -82.5t76 -140.5l63 -327l118 -173h17q20 0 33.5 -14.5t13.5 -35.5q0 -20 -13 -40t-31 -27q-8 -3 -23 -8.5 t-65 -20t-103 -25t-132.5 -19.5t-158.5 -9q-125 0 -245.5 20.5t-178.5 40.5l-58 20q-18 7 -31 27.5t-13 40.5zM497 110q12 -49 40 -79.5t63 -30.5t63 30.5t39 79.5q-48 -6 -102 -6t-103 6z" />
<glyph unicode="&#xe124;" d="M21 445l233 -45l-78 -224l224 78l45 -233l155 179l155 -179l45 233l224 -78l-78 224l234 45l-180 155l180 156l-234 44l78 225l-224 -78l-45 233l-155 -180l-155 180l-45 -233l-224 78l78 -225l-233 -44l179 -156z" />
<glyph unicode="&#xe125;" d="M0 200h200v600h-200v-600zM300 275q0 -75 100 -75h61q124 -100 139 -100h250q46 0 83 57l238 344q29 31 29 74v100q0 44 -30.5 84.5t-69.5 40.5h-328q28 118 28 125v150q0 44 -30.5 84.5t-69.5 40.5h-50q-27 0 -51 -20t-38 -48l-96 -198l-145 -196q-20 -26 -20 -63v-400z M400 300v375l150 213l100 212h50v-175l-50 -225h450v-125l-250 -375h-214l-136 100h-100z" />
<glyph unicode="&#xe126;" d="M0 400v600h200v-600h-200zM300 525v400q0 75 100 75h61q124 100 139 100h250q46 0 83 -57l238 -344q29 -31 29 -74v-100q0 -44 -30.5 -84.5t-69.5 -40.5h-328q28 -118 28 -125v-150q0 -44 -30.5 -84.5t-69.5 -40.5h-50q-27 0 -51 20t-38 48l-96 198l-145 196 q-20 26 -20 63zM400 525l150 -212l100 -213h50v175l-50 225h450v125l-250 375h-214l-136 -100h-100v-375z" />
<glyph unicode="&#xe127;" d="M8 200v600h200v-600h-200zM308 275v525q0 17 14 35.5t28 28.5l14 9l362 230q14 6 25 6q17 0 29 -12l109 -112q14 -14 14 -34q0 -18 -11 -32l-85 -121h302q85 0 138.5 -38t53.5 -110t-54.5 -111t-138.5 -39h-107l-130 -339q-7 -22 -20.5 -41.5t-28.5 -19.5h-341 q-7 0 -90 81t-83 94zM408 289l100 -89h293l131 339q6 21 19.5 41t28.5 20h203q16 0 25 15t9 36q0 20 -9 34.5t-25 14.5h-457h-6.5h-7.5t-6.5 0.5t-6 1t-5 1.5t-5.5 2.5t-4 4t-4 5.5q-5 12 -5 20q0 14 10 27l147 183l-86 83l-339 -236v-503z" />
<glyph unicode="&#xe128;" d="M-101 651q0 72 54 110t139 38l302 -1l-85 121q-11 16 -11 32q0 21 14 34l109 113q13 12 29 12q11 0 25 -6l365 -230q7 -4 17 -10.5t26.5 -26t16.5 -36.5v-526q0 -13 -86 -93.5t-94 -80.5h-341q-16 0 -29.5 20t-19.5 41l-130 339h-107q-84 0 -139 39t-55 111zM-1 601h222 q15 0 28.5 -20.5t19.5 -40.5l131 -339h293l107 89v502l-343 237l-87 -83l145 -184q10 -11 10 -26q0 -11 -5 -20q-1 -3 -3.5 -5.5l-4 -4t-5 -2.5t-5.5 -1.5t-6.5 -1t-6.5 -0.5h-7.5h-6.5h-476v-100zM1000 201v600h200v-600h-200z" />
<glyph unicode="&#xe129;" d="M97 719l230 -363q4 -6 10.5 -15.5t26 -25t36.5 -15.5h525q13 0 94 83t81 90v342q0 15 -20 28.5t-41 19.5l-339 131v106q0 84 -39 139t-111 55t-110 -53.5t-38 -138.5v-302l-121 84q-15 12 -33.5 11.5t-32.5 -13.5l-112 -110q-22 -22 -6 -53zM172 739l83 86l183 -146 q22 -18 47 -5q3 1 5.5 3.5l4 4t2.5 5t1.5 5.5t1 6.5t0.5 6.5v7.5v6.5v456q0 22 25 31t50 -0.5t25 -30.5v-202q0 -16 20 -29.5t41 -19.5l339 -130v-294l-89 -100h-503zM400 0v200h600v-200h-600z" />
<glyph unicode="&#xe130;" d="M2 585q-16 -31 6 -53l112 -110q13 -13 32 -13.5t34 10.5l121 85q0 -51 -0.5 -153.5t-0.5 -148.5q0 -84 38.5 -138t110.5 -54t111 55t39 139v106l339 131q20 6 40.5 19.5t20.5 28.5v342q0 7 -81 90t-94 83h-525q-17 0 -35.5 -14t-28.5 -28l-10 -15zM77 565l236 339h503 l89 -100v-294l-340 -130q-20 -6 -40 -20t-20 -29v-202q0 -22 -25 -31t-50 0t-25 31v456v14.5t-1.5 11.5t-5 12t-9.5 7q-24 13 -46 -5l-184 -146zM305 1104v200h600v-200h-600z" />
<glyph unicode="&#xe131;" d="M5 597q0 122 47.5 232.5t127.5 190.5t190.5 127.5t232.5 47.5q162 0 299.5 -80t217.5 -218t80 -300t-80 -299.5t-217.5 -217.5t-299.5 -80t-300 80t-218 217.5t-80 299.5zM298 701l2 -201h300l-2 -194l402 294l-402 298v-197h-300z" />
<glyph unicode="&#xe132;" d="M0 597q0 122 47.5 232.5t127.5 190.5t190.5 127.5t231.5 47.5q122 0 232.5 -47.5t190.5 -127.5t127.5 -190.5t47.5 -232.5q0 -162 -80 -299.5t-218 -217.5t-300 -80t-299.5 80t-217.5 217.5t-80 299.5zM200 600l402 -294l-2 194h300l2 201h-300v197z" />
<glyph unicode="&#xe133;" d="M5 597q0 122 47.5 232.5t127.5 190.5t190.5 127.5t232.5 47.5q162 0 299.5 -80t217.5 -218t80 -300t-80 -299.5t-217.5 -217.5t-299.5 -80t-300 80t-218 217.5t-80 299.5zM300 600h200v-300h200v300h200l-300 400z" />
<glyph unicode="&#xe134;" d="M5 597q0 122 47.5 232.5t127.5 190.5t190.5 127.5t232.5 47.5q162 0 299.5 -80t217.5 -218t80 -300t-80 -299.5t-217.5 -217.5t-299.5 -80t-300 80t-218 217.5t-80 299.5zM300 600l300 -400l300 400h-200v300h-200v-300h-200z" />
<glyph unicode="&#xe135;" d="M5 597q0 122 47.5 232.5t127.5 190.5t190.5 127.5t232.5 47.5q121 0 231.5 -47.5t190.5 -127.5t127.5 -190.5t47.5 -232.5q0 -162 -80 -299.5t-217.5 -217.5t-299.5 -80t-300 80t-218 217.5t-80 299.5zM254 780q-8 -33 5.5 -92.5t7.5 -87.5q0 -9 17 -44t16 -60 q12 0 23 -5.5t23 -15t20 -13.5q24 -12 108 -42q22 -8 53 -31.5t59.5 -38.5t57.5 -11q8 -18 -15 -55t-20 -57q42 -71 87 -80q0 -6 -3 -15.5t-3.5 -14.5t4.5 -17q104 -3 221 112q30 29 47 47t34.5 49t20.5 62q-14 9 -37 9.5t-36 7.5q-14 7 -49 15t-52 19q-9 0 -39.5 -0.5 t-46.5 -1.5t-39 -6.5t-39 -16.5q-50 -35 -66 -12q-4 2 -3.5 25.5t0.5 25.5q-6 13 -26.5 17t-24.5 7q2 22 -2 41t-16.5 28t-38.5 -20q-23 -25 -42 4q-19 28 -8 58q6 16 22 22q6 -1 26 -1.5t33.5 -4t19.5 -13.5q12 -19 32 -37.5t34 -27.5l14 -8q0 3 9.5 39.5t5.5 57.5 q-4 23 14.5 44.5t22.5 31.5q5 14 10 35t8.5 31t15.5 22.5t34 21.5q-6 18 10 37q8 0 23.5 -1.5t24.5 -1.5t20.5 4.5t20.5 15.5q-10 23 -30.5 42.5t-38 30t-49 26.5t-43.5 23q11 39 2 44q31 -13 58 -14.5t39 3.5l11 4q7 36 -16.5 53.5t-64.5 28.5t-56 23q-19 -3 -37 0 q-15 -12 -36.5 -21t-34.5 -12t-44 -8t-39 -6q-15 -3 -45.5 0.5t-45.5 -2.5q-21 -7 -52 -26.5t-34 -34.5q-3 -11 6.5 -22.5t8.5 -18.5q-3 -34 -27.5 -90.5t-29.5 -79.5zM518 916q3 12 16 30t16 25q10 -10 18.5 -10t14 6t14.5 14.5t16 12.5q0 -24 17 -66.5t17 -43.5 q-9 2 -31 5t-36 5t-32 8t-30 14zM692 1003h1h-1z" />
<glyph unicode="&#xe136;" d="M0 164.5q0 21.5 15 37.5l600 599q-33 101 6 201.5t135 154.5q164 92 306 -9l-259 -138l145 -232l251 126q13 -175 -151 -267q-123 -70 -253 -23l-596 -596q-15 -16 -36.5 -16t-36.5 16l-111 110q-15 15 -15 36.5z" />
<glyph unicode="&#xe137;" horiz-adv-x="1220" d="M0 196v100q0 41 29.5 70.5t70.5 29.5h1000q41 0 70.5 -29.5t29.5 -70.5v-100q0 -41 -29.5 -70.5t-70.5 -29.5h-1000q-41 0 -70.5 29.5t-29.5 70.5zM0 596v100q0 41 29.5 70.5t70.5 29.5h1000q41 0 70.5 -29.5t29.5 -70.5v-100q0 -41 -29.5 -70.5t-70.5 -29.5h-1000 q-41 0 -70.5 29.5t-29.5 70.5zM0 996v100q0 41 29.5 70.5t70.5 29.5h1000q41 0 70.5 -29.5t29.5 -70.5v-100q0 -41 -29.5 -70.5t-70.5 -29.5h-1000q-41 0 -70.5 29.5t-29.5 70.5zM600 596h500v100h-500v-100zM800 196h300v100h-300v-100zM900 996h200v100h-200v-100z" />
<glyph unicode="&#xe138;" d="M100 1100v100h1000v-100h-1000zM150 1000h900l-350 -500v-300l-200 -200v500z" />
<glyph unicode="&#xe139;" d="M0 200v200h1200v-200q0 -41 -29.5 -70.5t-70.5 -29.5h-1000q-41 0 -70.5 29.5t-29.5 70.5zM0 500v400q0 41 29.5 70.5t70.5 29.5h300v100q0 41 29.5 70.5t70.5 29.5h200q41 0 70.5 -29.5t29.5 -70.5v-100h300q41 0 70.5 -29.5t29.5 -70.5v-400h-500v100h-200v-100h-500z M500 1000h200v100h-200v-100z" />
<glyph unicode="&#xe140;" d="M0 0v400l129 -129l200 200l142 -142l-200 -200l129 -129h-400zM0 800l129 129l200 -200l142 142l-200 200l129 129h-400v-400zM729 329l142 142l200 -200l129 129v-400h-400l129 129zM729 871l200 200l-129 129h400v-400l-129 129l-200 -200z" />
<glyph unicode="&#xe141;" d="M0 596q0 162 80 299t217 217t299 80t299 -80t217 -217t80 -299t-80 -299t-217 -217t-299 -80t-299 80t-217 217t-80 299zM182 596q0 -172 121.5 -293t292.5 -121t292.5 121t121.5 293q0 171 -121.5 292.5t-292.5 121.5t-292.5 -121.5t-121.5 -292.5zM291 655 q0 23 15.5 38.5t38.5 15.5t39 -16t16 -38q0 -23 -16 -39t-39 -16q-22 0 -38 16t-16 39zM400 850q0 22 16 38.5t39 16.5q22 0 38 -16t16 -39t-16 -39t-38 -16q-23 0 -39 16.5t-16 38.5zM514 609q0 32 20.5 56.5t51.5 29.5l122 126l1 1q-9 14 -9 28q0 22 16 38.5t39 16.5 q22 0 38 -16t16 -39t-16 -39t-38 -16q-14 0 -29 10l-55 -145q17 -22 17 -51q0 -36 -25.5 -61.5t-61.5 -25.5t-61.5 25.5t-25.5 61.5zM800 655q0 22 16 38t39 16t38.5 -15.5t15.5 -38.5t-16 -39t-38 -16q-23 0 -39 16t-16 39z" />
<glyph unicode="&#xe142;" d="M-40 375q-13 -95 35 -173q35 -57 94 -89t129 -32q63 0 119 28q33 16 65 40.5t52.5 45.5t59.5 64q40 44 57 61l394 394q35 35 47 84t-3 96q-27 87 -117 104q-20 2 -29 2q-46 0 -78.5 -16.5t-67.5 -51.5l-389 -396l-7 -7l69 -67l377 373q20 22 39 38q23 23 50 23 q38 0 53 -36q16 -39 -20 -75l-547 -547q-52 -52 -125 -52q-55 0 -100 33t-54 96q-5 35 2.5 66t31.5 63t42 50t56 54q24 21 44 41l348 348q52 52 82.5 79.5t84 54t107.5 26.5q25 0 48 -4q95 -17 154 -94.5t51 -175.5q-7 -101 -98 -192l-252 -249l-253 -256l7 -7l69 -60 l517 511q67 67 95 157t11 183q-16 87 -67 154t-130 103q-69 33 -152 33q-107 0 -197 -55q-40 -24 -111 -95l-512 -512q-68 -68 -81 -163z" />
<glyph unicode="&#xe143;" d="M80 784q0 131 98.5 229.5t230.5 98.5q143 0 241 -129q103 129 246 129q129 0 226 -98.5t97 -229.5q0 -46 -17.5 -91t-61 -99t-77 -89.5t-104.5 -105.5q-197 -191 -293 -322l-17 -23l-16 23q-43 58 -100 122.5t-92 99.5t-101 100q-71 70 -104.5 105.5t-77 89.5t-61 99 t-17.5 91zM250 784q0 -27 30.5 -70t61.5 -75.5t95 -94.5l22 -22q93 -90 190 -201q82 92 195 203l12 12q64 62 97.5 97t64.5 79t31 72q0 71 -48 119.5t-105 48.5q-74 0 -132 -83l-118 -171l-114 174q-51 80 -123 80q-60 0 -109.5 -49.5t-49.5 -118.5z" />
<glyph unicode="&#xe144;" d="M57 353q0 -95 66 -159l141 -142q68 -66 159 -66q93 0 159 66l283 283q66 66 66 159t-66 159l-141 141q-8 9 -19 17l-105 -105l212 -212l-389 -389l-247 248l95 95l-18 18q-46 45 -75 101l-55 -55q-66 -66 -66 -159zM269 706q0 -93 66 -159l141 -141q7 -7 19 -17l105 105 l-212 212l389 389l247 -247l-95 -96l18 -17q47 -49 77 -100l29 29q35 35 62.5 88t27.5 96q0 93 -66 159l-141 141q-66 66 -159 66q-95 0 -159 -66l-283 -283q-66 -64 -66 -159z" />
<glyph unicode="&#xe145;" d="M200 100v953q0 21 30 46t81 48t129 38t163 15t162 -15t127 -38t79 -48t29 -46v-953q0 -41 -29.5 -70.5t-70.5 -29.5h-600q-41 0 -70.5 29.5t-29.5 70.5zM300 300h600v700h-600v-700zM496 150q0 -43 30.5 -73.5t73.5 -30.5t73.5 30.5t30.5 73.5t-30.5 73.5t-73.5 30.5 t-73.5 -30.5t-30.5 -73.5z" />
<glyph unicode="&#xe146;" d="M0 0l303 380l207 208l-210 212h300l267 279l-35 36q-15 14 -15 35t15 35q14 15 35 15t35 -15l283 -282q15 -15 15 -36t-15 -35q-14 -15 -35 -15t-35 15l-36 35l-279 -267v-300l-212 210l-208 -207z" />
<glyph unicode="&#xe148;" d="M295 433h139q5 -77 48.5 -126.5t117.5 -64.5v335q-6 1 -15.5 4t-11.5 3q-46 14 -79 26.5t-72 36t-62.5 52t-40 72.5t-16.5 99q0 92 44 159.5t109 101t144 40.5v78h100v-79q38 -4 72.5 -13.5t75.5 -31.5t71 -53.5t51.5 -84t24.5 -118.5h-159q-8 72 -35 109.5t-101 50.5 v-307l64 -14q34 -7 64 -16.5t70 -31.5t67.5 -52t47.5 -80.5t20 -112.5q0 -139 -89 -224t-244 -96v-77h-100v78q-152 17 -237 104q-40 40 -52.5 93.5t-15.5 139.5zM466 889q0 -29 8 -51t16.5 -34t29.5 -22.5t31 -13.5t38 -10q7 -2 11 -3v274q-61 -8 -97.5 -37.5t-36.5 -102.5 zM700 237q170 18 170 151q0 64 -44 99.5t-126 60.5v-311z" />
<glyph unicode="&#xe149;" d="M100 600v100h166q-24 49 -44 104q-10 26 -14.5 55.5t-3 72.5t25 90t68.5 87q97 88 263 88q129 0 230 -89t101 -208h-153q0 52 -34 89.5t-74 51.5t-76 14q-37 0 -79 -14.5t-62 -35.5q-41 -44 -41 -101q0 -28 16.5 -69.5t28 -62.5t41.5 -72h241v-100h-197q8 -50 -2.5 -115 t-31.5 -94q-41 -59 -99 -113q35 11 84 18t70 7q33 1 103 -16t103 -17q76 0 136 30l50 -147q-41 -25 -80.5 -36.5t-59 -13t-61.5 -1.5q-23 0 -128 33t-155 29q-39 -4 -82 -17t-66 -25l-24 -11l-55 145l16.5 11t15.5 10t13.5 9.5t14.5 12t14.5 14t17.5 18.5q48 55 54 126.5 t-30 142.5h-221z" />
<glyph unicode="&#xe150;" d="M2 300l298 -300l298 300h-198v900h-200v-900h-198zM602 900l298 300l298 -300h-198v-900h-200v900h-198z" />
<glyph unicode="&#xe151;" d="M2 300h198v900h200v-900h198l-298 -300zM700 0v200h100v-100h200v-100h-300zM700 400v100h300v-200h-99v-100h-100v100h99v100h-200zM700 700v500h300v-500h-100v100h-100v-100h-100zM801 900h100v200h-100v-200z" />
<glyph unicode="&#xe152;" d="M2 300h198v900h200v-900h198l-298 -300zM700 0v500h300v-500h-100v100h-100v-100h-100zM700 700v200h100v-100h200v-100h-300zM700 1100v100h300v-200h-99v-100h-100v100h99v100h-200zM801 200h100v200h-100v-200z" />
<glyph unicode="&#xe153;" d="M2 300l298 -300l298 300h-198v900h-200v-900h-198zM800 100v400h300v-500h-100v100h-200zM800 1100v100h200v-500h-100v400h-100zM901 200h100v200h-100v-200z" />
<glyph unicode="&#xe154;" d="M2 300l298 -300l298 300h-198v900h-200v-900h-198zM800 400v100h200v-500h-100v400h-100zM800 800v400h300v-500h-100v100h-200zM901 900h100v200h-100v-200z" />
<glyph unicode="&#xe155;" d="M2 300l298 -300l298 300h-198v900h-200v-900h-198zM700 100v200h500v-200h-500zM700 400v200h400v-200h-400zM700 700v200h300v-200h-300zM700 1000v200h200v-200h-200z" />
<glyph unicode="&#xe156;" d="M2 300l298 -300l298 300h-198v900h-200v-900h-198zM700 100v200h200v-200h-200zM700 400v200h300v-200h-300zM700 700v200h400v-200h-400zM700 1000v200h500v-200h-500z" />
<glyph unicode="&#xe157;" d="M0 400v300q0 165 117.5 282.5t282.5 117.5h300q162 0 281 -118.5t119 -281.5v-300q0 -165 -118.5 -282.5t-281.5 -117.5h-300q-165 0 -282.5 117.5t-117.5 282.5zM200 300q0 -41 29.5 -70.5t70.5 -29.5h500q41 0 70.5 29.5t29.5 70.5v500q0 41 -29.5 70.5t-70.5 29.5 h-500q-41 0 -70.5 -29.5t-29.5 -70.5v-500z" />
<glyph unicode="&#xe158;" d="M0 400v300q0 163 119 281.5t281 118.5h300q165 0 282.5 -117.5t117.5 -282.5v-300q0 -165 -117.5 -282.5t-282.5 -117.5h-300q-163 0 -281.5 117.5t-118.5 282.5zM200 300q0 -41 29.5 -70.5t70.5 -29.5h500q41 0 70.5 29.5t29.5 70.5v500q0 41 -29.5 70.5t-70.5 29.5 h-500q-41 0 -70.5 -29.5t-29.5 -70.5v-500zM400 300l333 250l-333 250v-500z" />
<glyph unicode="&#xe159;" d="M0 400v300q0 163 117.5 281.5t282.5 118.5h300q163 0 281.5 -119t118.5 -281v-300q0 -165 -117.5 -282.5t-282.5 -117.5h-300q-165 0 -282.5 117.5t-117.5 282.5zM200 300q0 -41 29.5 -70.5t70.5 -29.5h500q41 0 70.5 29.5t29.5 70.5v500q0 41 -29.5 70.5t-70.5 29.5 h-500q-41 0 -70.5 -29.5t-29.5 -70.5v-500zM300 700l250 -333l250 333h-500z" />
<glyph unicode="&#xe160;" d="M0 400v300q0 165 117.5 282.5t282.5 117.5h300q165 0 282.5 -117.5t117.5 -282.5v-300q0 -162 -118.5 -281t-281.5 -119h-300q-165 0 -282.5 118.5t-117.5 281.5zM200 300q0 -41 29.5 -70.5t70.5 -29.5h500q41 0 70.5 29.5t29.5 70.5v500q0 41 -29.5 70.5t-70.5 29.5 h-500q-41 0 -70.5 -29.5t-29.5 -70.5v-500zM300 400h500l-250 333z" />
<glyph unicode="&#xe161;" d="M0 400v300h300v200l400 -350l-400 -350v200h-300zM500 0v200h500q41 0 70.5 29.5t29.5 70.5v500q0 41 -29.5 70.5t-70.5 29.5h-500v200h400q165 0 282.5 -117.5t117.5 -282.5v-300q0 -165 -117.5 -282.5t-282.5 -117.5h-400z" />
<glyph unicode="&#xe162;" d="M217 519q8 -19 31 -19h302q-155 -438 -160 -458q-5 -21 4 -32l9 -8h9q14 0 26 15q11 13 274.5 321.5t264.5 308.5q14 19 5 36q-8 17 -31 17l-301 -1q1 4 78 219.5t79 227.5q2 15 -5 27l-9 9h-9q-15 0 -25 -16q-4 -6 -98 -111.5t-228.5 -257t-209.5 -237.5q-16 -19 -6 -41 z" />
<glyph unicode="&#xe163;" d="M0 400q0 -165 117.5 -282.5t282.5 -117.5h300q47 0 100 15v185h-500q-41 0 -70.5 29.5t-29.5 70.5v500q0 41 29.5 70.5t70.5 29.5h500v185q-14 4 -114 7.5t-193 5.5l-93 2q-165 0 -282.5 -117.5t-117.5 -282.5v-300zM600 400v300h300v200l400 -350l-400 -350v200h-300z " />
<glyph unicode="&#xe164;" d="M0 400q0 -165 117.5 -282.5t282.5 -117.5h300q163 0 281.5 117.5t118.5 282.5v98l-78 73l-122 -123v-148q0 -41 -29.5 -70.5t-70.5 -29.5h-500q-41 0 -70.5 29.5t-29.5 70.5v500q0 41 29.5 70.5t70.5 29.5h156l118 122l-74 78h-100q-165 0 -282.5 -117.5t-117.5 -282.5 v-300zM496 709l353 342l-149 149h500v-500l-149 149l-342 -353z" />
<glyph unicode="&#xe165;" d="M4 600q0 162 80 299t217 217t299 80t299 -80t217 -217t80 -299t-80 -299t-217 -217t-299 -80t-299 80t-217 217t-80 299zM186 600q0 -171 121.5 -292.5t292.5 -121.5t292.5 121.5t121.5 292.5t-121.5 292.5t-292.5 121.5t-292.5 -121.5t-121.5 -292.5zM406 600 q0 80 57 137t137 57t137 -57t57 -137t-57 -137t-137 -57t-137 57t-57 137z" />
<glyph unicode="&#xe166;" d="M0 0v275q0 11 7 18t18 7h1048q11 0 19 -7.5t8 -17.5v-275h-1100zM100 800l445 -500l450 500h-295v400h-300v-400h-300zM900 150h100v50h-100v-50z" />
<glyph unicode="&#xe167;" d="M0 0v275q0 11 7 18t18 7h1048q11 0 19 -7.5t8 -17.5v-275h-1100zM100 700h300v-300h300v300h295l-445 500zM900 150h100v50h-100v-50z" />
<glyph unicode="&#xe168;" d="M0 0v275q0 11 7 18t18 7h1048q11 0 19 -7.5t8 -17.5v-275h-1100zM100 705l305 -305l596 596l-154 155l-442 -442l-150 151zM900 150h100v50h-100v-50z" />
<glyph unicode="&#xe169;" d="M0 0v275q0 11 7 18t18 7h1048q11 0 19 -7.5t8 -17.5v-275h-1100zM100 988l97 -98l212 213l-97 97zM200 400l697 1l3 699l-250 -239l-149 149l-212 -212l149 -149zM900 150h100v50h-100v-50z" />
<glyph unicode="&#xe170;" d="M0 0v275q0 11 7 18t18 7h1048q11 0 19 -7.5t8 -17.5v-275h-1100zM200 612l212 -212l98 97l-213 212zM300 1200l239 -250l-149 -149l212 -212l149 148l249 -237l-1 697zM900 150h100v50h-100v-50z" />
<glyph unicode="&#xe171;" d="M23 415l1177 784v-1079l-475 272l-310 -393v416h-392zM494 210l672 938l-672 -712v-226z" />
<glyph unicode="&#xe172;" d="M0 150v1000q0 20 14.5 35t35.5 15h250v-300h500v300h100l200 -200v-850q0 -21 -15 -35.5t-35 -14.5h-150v400h-700v-400h-150q-21 0 -35.5 14.5t-14.5 35.5zM600 1000h100v200h-100v-200z" />
<glyph unicode="&#xe173;" d="M0 150v1000q0 20 14.5 35t35.5 15h250v-300h500v300h100l200 -200v-218l-276 -275l-120 120l-126 -127h-378v-400h-150q-21 0 -35.5 14.5t-14.5 35.5zM581 306l123 123l120 -120l353 352l123 -123l-475 -476zM600 1000h100v200h-100v-200z" />
<glyph unicode="&#xe174;" d="M0 150v1000q0 20 14.5 35t35.5 15h250v-300h500v300h100l200 -200v-269l-103 -103l-170 170l-298 -298h-329v-400h-150q-21 0 -35.5 14.5t-14.5 35.5zM600 1000h100v200h-100v-200zM700 133l170 170l-170 170l127 127l170 -170l170 170l127 -128l-170 -169l170 -170 l-127 -127l-170 170l-170 -170z" />
<glyph unicode="&#xe175;" d="M0 150v1000q0 20 14.5 35t35.5 15h250v-300h500v300h100l200 -200v-300h-400v-200h-500v-400h-150q-21 0 -35.5 14.5t-14.5 35.5zM600 300l300 -300l300 300h-200v300h-200v-300h-200zM600 1000v200h100v-200h-100z" />
<glyph unicode="&#xe176;" d="M0 150v1000q0 20 14.5 35t35.5 15h250v-300h500v300h100l200 -200v-402l-200 200l-298 -298h-402v-400h-150q-21 0 -35.5 14.5t-14.5 35.5zM600 300h200v-300h200v300h200l-300 300zM600 1000v200h100v-200h-100z" />
<glyph unicode="&#xe177;" d="M0 250q0 -21 14.5 -35.5t35.5 -14.5h1100q21 0 35.5 14.5t14.5 35.5v550h-1200v-550zM0 900h1200v150q0 21 -14.5 35.5t-35.5 14.5h-1100q-21 0 -35.5 -14.5t-14.5 -35.5v-150zM100 300v200h400v-200h-400z" />
<glyph unicode="&#xe178;" d="M0 400l300 298v-198h400v-200h-400v-198zM100 800v200h100v-200h-100zM300 800v200h100v-200h-100zM500 800v200h400v198l300 -298l-300 -298v198h-400zM800 300v200h100v-200h-100zM1000 300h100v200h-100v-200z" />
<glyph unicode="&#xe179;" d="M100 700v400l50 100l50 -100v-300h100v300l50 100l50 -100v-300h100v300l50 100l50 -100v-400l-100 -203v-447q0 -21 -14.5 -35.5t-35.5 -14.5h-200q-21 0 -35.5 14.5t-14.5 35.5v447zM800 597q0 -29 10.5 -55.5t25 -43t29 -28.5t25.5 -18l10 -5v-397q0 -21 14.5 -35.5 t35.5 -14.5h200q21 0 35.5 14.5t14.5 35.5v1106q0 31 -18 40.5t-44 -7.5l-276 -116q-25 -17 -43.5 -51.5t-18.5 -65.5v-359z" />
<glyph unicode="&#xe180;" d="M100 0h400v56q-75 0 -87.5 6t-12.5 44v394h500v-394q0 -38 -12.5 -44t-87.5 -6v-56h400v56q-4 0 -11 0.5t-24 3t-30 7t-24 15t-11 24.5v888q0 22 25 34.5t50 13.5l25 2v56h-400v-56q75 0 87.5 -6t12.5 -44v-394h-500v394q0 38 12.5 44t87.5 6v56h-400v-56q4 0 11 -0.5 t24 -3t30 -7t24 -15t11 -24.5v-888q0 -22 -25 -34.5t-50 -13.5l-25 -2v-56z" />
<glyph unicode="&#xe181;" d="M0 300q0 -41 29.5 -70.5t70.5 -29.5h300q41 0 70.5 29.5t29.5 70.5v500q0 41 -29.5 70.5t-70.5 29.5h-300q-41 0 -70.5 -29.5t-29.5 -70.5v-500zM100 100h400l200 200h105l295 98v-298h-425l-100 -100h-375zM100 300v200h300v-200h-300zM100 600v200h300v-200h-300z M100 1000h400l200 -200v-98l295 98h105v200h-425l-100 100h-375zM700 402v163l400 133v-163z" />
<glyph unicode="&#xe182;" d="M16.5 974.5q0.5 -21.5 16 -90t46.5 -140t104 -177.5t175 -208q103 -103 207.5 -176t180 -103.5t137 -47t92.5 -16.5l31 1l163 162q17 18 13.5 41t-22.5 37l-192 136q-19 14 -45 12t-42 -19l-118 -118q-142 101 -268 227t-227 268l118 118q17 17 20 41.5t-11 44.5 l-139 194q-14 19 -36.5 22t-40.5 -14l-162 -162q-1 -11 -0.5 -32.5z" />
<glyph unicode="&#xe183;" d="M0 50v212q0 20 10.5 45.5t24.5 39.5l365 303v50q0 4 1 10.5t12 22.5t30 28.5t60 23t97 10.5t97 -10t60 -23.5t30 -27.5t12 -24l1 -10v-50l365 -303q14 -14 24.5 -39.5t10.5 -45.5v-212q0 -21 -14.5 -35.5t-35.5 -14.5h-1100q-20 0 -35 14.5t-15 35.5zM0 712 q0 -21 14.5 -33.5t34.5 -8.5l202 33q20 4 34.5 21t14.5 38v146q141 24 300 24t300 -24v-146q0 -21 14.5 -38t34.5 -21l202 -33q20 -4 34.5 8.5t14.5 33.5v200q-6 8 -19 20.5t-63 45t-112 57t-171 45t-235 20.5q-92 0 -175 -10.5t-141.5 -27t-108.5 -36.5t-81.5 -40 t-53.5 -36.5t-31 -27.5l-9 -10v-200z" />
<glyph unicode="&#xe184;" d="M100 0v100h1100v-100h-1100zM175 200h950l-125 150v250l100 100v400h-100v-200h-100v200h-200v-200h-100v200h-200v-200h-100v200h-100v-400l100 -100v-250z" />
<glyph unicode="&#xe185;" d="M100 0h300v400q0 41 -29.5 70.5t-70.5 29.5h-100q-41 0 -70.5 -29.5t-29.5 -70.5v-400zM500 0v1000q0 41 29.5 70.5t70.5 29.5h100q41 0 70.5 -29.5t29.5 -70.5v-1000h-300zM900 0v700q0 41 29.5 70.5t70.5 29.5h100q41 0 70.5 -29.5t29.5 -70.5v-700h-300z" />
<glyph unicode="&#xe186;" d="M-100 300v500q0 124 88 212t212 88h700q124 0 212 -88t88 -212v-500q0 -124 -88 -212t-212 -88h-700q-124 0 -212 88t-88 212zM100 200h900v700h-900v-700zM200 300h300v300h-200v100h200v100h-300v-300h200v-100h-200v-100zM600 300h200v100h100v300h-100v100h-200v-500 zM700 400v300h100v-300h-100z" />
<glyph unicode="&#xe187;" d="M-100 300v500q0 124 88 212t212 88h700q124 0 212 -88t88 -212v-500q0 -124 -88 -212t-212 -88h-700q-124 0 -212 88t-88 212zM100 200h900v700h-900v-700zM200 300h100v200h100v-200h100v500h-100v-200h-100v200h-100v-500zM600 300h200v100h100v300h-100v100h-200v-500 zM700 400v300h100v-300h-100z" />
<glyph unicode="&#xe188;" d="M-100 300v500q0 124 88 212t212 88h700q124 0 212 -88t88 -212v-500q0 -124 -88 -212t-212 -88h-700q-124 0 -212 88t-88 212zM100 200h900v700h-900v-700zM200 300h300v100h-200v300h200v100h-300v-500zM600 300h300v100h-200v300h200v100h-300v-500z" />
<glyph unicode="&#xe189;" d="M-100 300v500q0 124 88 212t212 88h700q124 0 212 -88t88 -212v-500q0 -124 -88 -212t-212 -88h-700q-124 0 -212 88t-88 212zM100 200h900v700h-900v-700zM200 550l300 -150v300zM600 400l300 150l-300 150v-300z" />
<glyph unicode="&#xe190;" d="M-100 300v500q0 124 88 212t212 88h700q124 0 212 -88t88 -212v-500q0 -124 -88 -212t-212 -88h-700q-124 0 -212 88t-88 212zM100 200h900v700h-900v-700zM200 300v500h700v-500h-700zM300 400h130q41 0 68 42t27 107t-28.5 108t-66.5 43h-130v-300zM575 549 q0 -65 27 -107t68 -42h130v300h-130q-38 0 -66.5 -43t-28.5 -108z" />
<glyph unicode="&#xe191;" d="M-100 300v500q0 124 88 212t212 88h700q124 0 212 -88t88 -212v-500q0 -124 -88 -212t-212 -88h-700q-124 0 -212 88t-88 212zM100 200h900v700h-900v-700zM200 300h300v300h-200v100h200v100h-300v-300h200v-100h-200v-100zM601 300h100v100h-100v-100zM700 700h100 v-400h100v500h-200v-100z" />
<glyph unicode="&#xe192;" d="M-100 300v500q0 124 88 212t212 88h700q124 0 212 -88t88 -212v-500q0 -124 -88 -212t-212 -88h-700q-124 0 -212 88t-88 212zM100 200h900v700h-900v-700zM200 300h300v400h-200v100h-100v-500zM301 400v200h100v-200h-100zM601 300h100v100h-100v-100zM700 700h100 v-400h100v500h-200v-100z" />
<glyph unicode="&#xe193;" d="M-100 300v500q0 124 88 212t212 88h700q124 0 212 -88t88 -212v-500q0 -124 -88 -212t-212 -88h-700q-124 0 -212 88t-88 212zM100 200h900v700h-900v-700zM200 700v100h300v-300h-99v-100h-100v100h99v200h-200zM201 300v100h100v-100h-100zM601 300v100h100v-100h-100z M700 700v100h200v-500h-100v400h-100z" />
<glyph unicode="&#xe194;" d="M4 600q0 162 80 299t217 217t299 80t299 -80t217 -217t80 -299t-80 -299t-217 -217t-299 -80t-299 80t-217 217t-80 299zM186 600q0 -171 121.5 -292.5t292.5 -121.5t292.5 121.5t121.5 292.5t-121.5 292.5t-292.5 121.5t-292.5 -121.5t-121.5 -292.5zM400 500v200 l100 100h300v-100h-300v-200h300v-100h-300z" />
<glyph unicode="&#xe195;" d="M0 600q0 162 80 299t217 217t299 80t299 -80t217 -217t80 -299t-80 -299t-217 -217t-299 -80t-299 80t-217 217t-80 299zM182 600q0 -171 121.5 -292.5t292.5 -121.5t292.5 121.5t121.5 292.5t-121.5 292.5t-292.5 121.5t-292.5 -121.5t-121.5 -292.5zM400 400v400h300 l100 -100v-100h-100v100h-200v-100h200v-100h-200v-100h-100zM700 400v100h100v-100h-100z" />
<glyph unicode="&#xe197;" d="M-14 494q0 -80 56.5 -137t135.5 -57h222v300h400v-300h128q120 0 205 86.5t85 207.5t-85 207t-205 86q-46 0 -90 -14q-44 97 -134.5 156.5t-200.5 59.5q-152 0 -260 -107.5t-108 -260.5q0 -25 2 -37q-66 -14 -108.5 -67.5t-42.5 -122.5zM300 200h200v300h200v-300h200 l-300 -300z" />
<glyph unicode="&#xe198;" d="M-14 494q0 -80 56.5 -137t135.5 -57h8l414 414l403 -403q94 26 154.5 104.5t60.5 178.5q0 120 -85 206.5t-205 86.5q-46 0 -90 -14q-44 97 -134.5 156.5t-200.5 59.5q-152 0 -260 -107.5t-108 -260.5q0 -25 2 -37q-66 -14 -108.5 -67.5t-42.5 -122.5zM300 200l300 300 l300 -300h-200v-300h-200v300h-200z" />
<glyph unicode="&#xe199;" d="M100 200h400v-155l-75 -45h350l-75 45v155h400l-270 300h170l-270 300h170l-300 333l-300 -333h170l-270 -300h170z" />
<glyph unicode="&#xe200;" d="M121 700q0 -53 28.5 -97t75.5 -65q-4 -16 -4 -38q0 -74 52.5 -126.5t126.5 -52.5q56 0 100 30v-306l-75 -45h350l-75 45v306q46 -30 100 -30q74 0 126.5 52.5t52.5 126.5q0 24 -9 55q50 32 79.5 83t29.5 112q0 90 -61.5 155.5t-150.5 71.5q-26 89 -99.5 145.5 t-167.5 56.5q-116 0 -197.5 -81.5t-81.5 -197.5q0 -4 1 -11.5t1 -11.5q-14 2 -23 2q-74 0 -126.5 -52.5t-52.5 -126.5z" />
</font>
</defs></svg>

After

Width:  |  Height:  |  Size: 62 KiB

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.9 KiB

View File

@ -1,13 +1,9 @@
var Ajax;
if (Ajax && (Ajax != null)) {
Ajax.Responders.register({
onCreate: function() {
if($('spinner') && Ajax.activeRequestCount>0)
Effect.Appear('spinner',{duration:0.5,queue:'end'});
},
onComplete: function() {
if($('spinner') && Ajax.activeRequestCount==0)
Effect.Fade('spinner',{duration:0.5,queue:'end'});
}
});
if (typeof jQuery !== 'undefined') {
(function($) {
$('#spinner').ajaxStart(function() {
$(this).fadeIn();
}).ajaxStop(function() {
$(this).fadeOut();
});
})(jQuery);
}

1951
bbb-lti/web-app/js/bootstrap.js vendored Normal file

File diff suppressed because it is too large Load Diff

57
bbb-video/build.gradle Executable file → Normal file
View File

@ -1,27 +1,15 @@
usePlugin 'java'
usePlugin 'war'
usePlugin 'eclipse'
apply plugin: 'java'
apply plugin: 'war'
apply plugin: 'eclipse'
version = '0.7'
jar.enabled = true
archivesBaseName = 'video'
task dependencies(type: Copy) {
task resolveDeps(type: Copy) {
into('lib')
from configurations.default
from configurations.default.allArtifacts*.file
}
task resolveDeps(dependsOn: configurations.default.buildArtifacts, type: Copy) {
into('lib')
from configurations.default
from configurations.default.allArtifacts*.file
}
task resolveDependencies(dependsOn: configurations.default.buildArtifacts, type: Copy) {
into('lib')
from configurations.default
from configurations.default.allArtifacts*.file
from configurations.default.allArtifacts.file
}
repositories {
@ -61,8 +49,14 @@ repositories {
m2compatible = true
addArtifactPattern "http://repository.springsource.com/maven/bundles/external/[organisation]/[module]/[revision]/[artifact]-[revision].[ext]"
addArtifactPattern "http://repository.springsource.com/maven/bundles/release/[organisation]/[module]/[revision]/[artifact]-[revision].[ext]"
}
}
}
add(new org.apache.ivy.plugins.resolver.URLResolver()) {
name = "Red5"
m2compatible = true
addArtifactPattern "http://red5.googlecode.com/svn/repository/[organisation]/[module]/[revision]/[artifact]-[revision].[ext]"
addArtifactPattern "http://red5.googlecode.com/svn/repository/[organisation]/[module]/[revision]/[artifact]-[revision].[ext]"
}
}
}
dependencies {
@ -75,25 +69,26 @@ dependencies {
providedCompile 'org.apache.mina:mina-integration-jmx:2.0.7@jar'
// Spring
providedCompile 'org.springframework:spring-web:3.1.1.RELEASE@jar'
providedCompile 'org.springframework:spring-beans:3.1.1.RELEASE@jar'
providedCompile 'org.springframework:spring-context:3.1.1.RELEASE@jar'
providedCompile 'org.springframework:spring-core:3.1.1.RELEASE@jar'
providedCompile 'org.springframework:spring-web:4.0.3.RELEASE@jar'
providedCompile 'org.springframework:spring-beans:4.0.3.RELEASE@jar'
providedCompile 'org.springframework:spring-context:4.0.3.RELEASE@jar'
providedCompile 'org.springframework:spring-core:4.0.3.RELEASE@jar'
// Red5
providedCompile 'org/red5:red5:1.0r4643@jar'
providedCompile 'org/red5:red5:1.0.2@jar'
providedCompile 'org.red5:red5-io:1.0.3@jar'
// Logging
providedCompile 'ch.qos.logback:logback-core:1.0.9@jar'
providedCompile 'ch.qos.logback:logback-classic:1.0.9@jar'
providedCompile 'org.slf4j:log4j-over-slf4j:1.7.2@jar'
providedCompile 'org.slf4j:jcl-over-slf4j:1.7.2@jar'
providedCompile 'org.slf4j:jul-to-slf4j:1.7.2@jar'
providedCompile 'org.slf4j:slf4j-api:1.7.2@jar'
providedCompile 'ch.qos.logback:logback-core:1.0.13@jar'
providedCompile 'ch.qos.logback:logback-classic:1.0.13@jar'
providedCompile 'org.slf4j:log4j-over-slf4j:1.7.5@jar'
providedCompile 'org.slf4j:jcl-over-slf4j:1.7.5@jar'
providedCompile 'org.slf4j:jul-to-slf4j:1.7.5@jar'
providedCompile 'org.slf4j:slf4j-api:1.7.5@jar'
// Needed for the JVM shutdown hook but needs to be put into red5/lib dir.
// Otherwise we get exception on aop utils class not found.
providedCompile 'org.springframework:spring-aop:3.0.6.RELEASE@jar'
providedCompile 'org.springframework:spring-aop:4.0.3.RELEASE@jar'
compile 'aopalliance:aopalliance:1.0@jar'
// Java Concurrency In Practice

View File

@ -123,6 +123,7 @@
<param name="caller-id-name" value="$${outbound_caller_name}"/>
<param name="caller-id-number" value="$${outbound_caller_id}"/>
<param name="comfort-noise" value="true"/>
<param name="min-required-recording-participants" value="1"/>
<!--<param name="tts-engine" value="flite"/>-->
<!--<param name="tts-voice" value="kal16"/>-->
</profile>

70
bbb-voice/build.gradle Executable file → Normal file
View File

@ -1,27 +1,15 @@
usePlugin 'java'
usePlugin 'war'
usePlugin 'eclipse'
apply plugin: 'java'
apply plugin: 'war'
apply plugin: 'eclipse'
version = '0.7'
jar.enabled = true
archivesBaseName = 'sip'
task dependencies(type: Copy) {
task resolveDeps(type: Copy) {
into('lib')
from configurations.default
from configurations.default.allArtifacts*.file
}
task resolveDeps(dependsOn: configurations.default.buildArtifacts, type: Copy) {
into('lib')
from configurations.default
from configurations.default.allArtifacts*.file
}
task resolveDependencies(dependsOn: configurations.default.buildArtifacts, type: Copy) {
into('lib')
from configurations.default
from configurations.default.allArtifacts*.file
from configurations.default.allArtifacts.file
}
repositories {
@ -62,7 +50,13 @@ repositories {
addArtifactPattern "http://repository.springsource.com/maven/bundles/external/[organisation]/[module]/[revision]/[artifact]-[revision].[ext]"
addArtifactPattern "http://repository.springsource.com/maven/bundles/release/[organisation]/[module]/[revision]/[artifact]-[revision].[ext]"
}
}
add(new org.apache.ivy.plugins.resolver.URLResolver()) {
name = "Red5"
m2compatible = true
addArtifactPattern "http://red5.googlecode.com/svn/repository/[organisation]/[module]/[revision]/[artifact]-[revision].[ext]"
addArtifactPattern "http://red5.googlecode.com/svn/repository/[organisation]/[module]/[revision]/[artifact]-[revision].[ext]"
}
}
}
dependencies {
@ -74,26 +68,27 @@ dependencies {
providedCompile 'org.apache.mina:mina-integration-beans:2.0.7@jar'
providedCompile 'org.apache.mina:mina-integration-jmx:2.0.7@jar'
// Spring
providedCompile 'org.springframework:spring-web:3.1.1.RELEASE@jar'
providedCompile 'org.springframework:spring-beans:3.1.1.RELEASE@jar'
providedCompile 'org.springframework:spring-context:3.1.1.RELEASE@jar'
providedCompile 'org.springframework:spring-core:3.1.1.RELEASE@jar'
// Red5
providedCompile 'org/red5:red5:1.0r4643@jar'
// Logging
providedCompile 'ch.qos.logback:logback-core:1.0.9@jar'
providedCompile 'ch.qos.logback:logback-classic:1.0.9@jar'
providedCompile 'org.slf4j:log4j-over-slf4j:1.7.2@jar'
providedCompile 'org.slf4j:jcl-over-slf4j:1.7.2@jar'
providedCompile 'org.slf4j:jul-to-slf4j:1.7.2@jar'
providedCompile 'org.slf4j:slf4j-api:1.7.2@jar'
// Spring
providedCompile 'org.springframework:spring-web:4.0.3.RELEASE@jar'
providedCompile 'org.springframework:spring-beans:4.0.3.RELEASE@jar'
providedCompile 'org.springframework:spring-context:4.0.3.RELEASE@jar'
providedCompile 'org.springframework:spring-core:4.0.3.RELEASE@jar'
// Red5
providedCompile 'org/red5:red5:1.0.2@jar'
providedCompile 'org.red5:red5-io:1.0.3@jar'
// Logging
providedCompile 'ch.qos.logback:logback-core:1.0.13@jar'
providedCompile 'ch.qos.logback:logback-classic:1.0.13@jar'
providedCompile 'org.slf4j:log4j-over-slf4j:1.7.5@jar'
providedCompile 'org.slf4j:jcl-over-slf4j:1.7.5@jar'
providedCompile 'org.slf4j:jul-to-slf4j:1.7.5@jar'
providedCompile 'org.slf4j:slf4j-api:1.7.5@jar'
// Needed for the JVM shutdown hook but needs to be put into red5/lib dir.
// Otherwise we get exception on aop utils class not found.
providedCompile 'org.springframework:spring-aop:3.0.6.RELEASE@jar'
providedCompile 'org.springframework:spring-aop:4.0.3.RELEASE@jar'
compile 'aopalliance:aopalliance:1.0@jar'
// Java Concurrency In Practice
@ -109,7 +104,10 @@ dependencies {
compile 'javax/media:jmf:2.1.1e@jar'
// Redis pubsub
compile "redis.clients:jedis:2.1.0"
providedCompile 'commons-pool:commons-pool:1.5.6'
compile 'com.google.code.gson:gson:1.7.1'
}

View File

@ -0,0 +1,88 @@
package org.bigbluebutton.voiceconf.messaging;
public class Constants {
public static final String NAME = "name";
public static final String HEADER = "header";
public static final String PAYLOAD = "payload";
public static final String MEETING_ID = "meeting_id";
public static final String TIMESTAMP = "timestamp";
public static final String USER_ID = "userid";
public static final String RECORDED = "recorded";
public static final String MEETING_NAME = "meeting_name";
public static final String VOICE_CONF = "voice_conf";
public static final String DURATION = "duration";
public static final String AUTH_TOKEN = "auth_token";
public static final String ROLE = "role";
public static final String EXT_USER_ID = "external_user_id";
public static final String REQUESTER_ID = "requester_id";
public static final String REPLY_TO = "reply_to";
public static final String LOWERED_BY = "lowered_by";
public static final String STREAM = "stream";
public static final String LOCKED = "locked";
public static final String SETTINGS = "settings";
public static final String LOCK = "lock";
public static final String EXCEPT_USERS = "except_users";
public static final String STATUS = "status";
public static final String VALUE = "value";
public static final String NEW_PRESENTER_ID = "new_presenter_id";
public static final String NEW_PRESENTER_NAME = "new_presenter_name";
public static final String ASSIGNED_BY = "assigned_by";
public static final String RECORDING = "recording";
public static final String LAYOUT_ID = "layout_id";
public static final String POLL = "poll";
public static final String POLL_ID = "poll_id";
public static final String FORCE = "force";
public static final String RESPONSE = "response";
public static final String PRESENTATION_ID = "presentation_id";
public static final String X_OFFSET = "x_offset";
public static final String Y_OFFSET = "y_offset";
public static final String WIDTH_RATIO = "width_ratio";
public static final String HEIGHT_RATIO = "height_ratio";
public static final String PAGE = "page";
public static final String SHARE = "share";
public static final String PRESENTATIONS = "presentations";
public static final String MESSAGE_KEY = "message_key";
public static final String CODE = "code";
public static final String PRESENTATION_NAME = "presentation_name";
public static final String NUM_PAGES = "num_pages";
public static final String MAX_NUM_PAGES = "max_num_pages";
public static final String PAGES_COMPLETED = "pages_completed";
public static final String MUTE = "mute";
public static final String CALLER_ID_NUM = "caller_id_num";
public static final String CALLER_ID_NAME = "caller_id_name";
public static final String TALKING = "talking";
public static final String USER = "user";
public static final String MUTED = "muted";
public static final String VOICE_USER = "voice_user";
public static final String RECORDING_FILE = "recording_file";
public static final String ANNOTATION = "annotation";
public static final String WHITEBOARD_ID = "whiteboard_id";
public static final String ENABLE = "enable";
public static final String PRESENTER = "presenter";
public static final String USERS = "users";
public static final String RAISE_HAND = "raise_hand";
public static final String HAS_STREAM = "has_stream";
public static final String WEBCAM_STREAM = "webcam_stream";
public static final String PHONE_USER = "phone_user";
public static final String PERMISSIONS = "permissions";
public static final String VALID = "valid";
public static final String CHAT_HISTORY = "chat_history";
public static final String MESSAGE = "message";
public static final String SET_BY_USER_ID = "set_by_user_id";
public static final String POLLS = "polls";
public static final String REASON = "reason";
public static final String RESPONDER = "responder";
public static final String PRESENTATION_INFO = "presentation_info";
public static final String SHAPES = "shapes";
public static final String SHAPE = "shape";
public static final String SHAPE_ID = "shape_id";
public static final String PRESENTATION = "presentation";
public static final String ID = "id";
public static final String CURRENT = "current";
public static final String PAGES = "pages";
public static final String WEB_USER_ID = "web_user_id";
public static final String JOINED = "joined";
public static final String X_PERCENT = "x_percent";
public static final String Y_PERCENT = "y_percent";
public static final String KEEP_ALIVE_ID = "keep_alive_id";
}

View File

@ -0,0 +1,142 @@
/**
* BigBlueButton open source conferencing system - http://www.bigbluebutton.org/
*
* Copyright (c) 2012 BigBlueButton Inc. and by respective authors (see below).
*
* This program is free software; you can redistribute it and/or modify it under the
* terms of the GNU Lesser General Public License as published by the Free Software
* Foundation; either version 3.0 of the License, or (at your option) any later
* version.
*
* BigBlueButton is distributed in the hope that it will be useful, but WITHOUT ANY
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
* PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License along
* with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
*
*/
package org.bigbluebutton.voiceconf.messaging;
import org.apache.commons.pool.impl.GenericObjectPool;
public class GenericObjectPoolConfigWrapper {
private final GenericObjectPool.Config config;
public GenericObjectPoolConfigWrapper() {
this.config = new GenericObjectPool.Config();
}
public GenericObjectPool.Config getConfig() {
return config;
}
public int getMaxIdle() {
return this.config.maxIdle;
}
public void setMaxIdle(int maxIdle) {
this.config.maxIdle = maxIdle;
}
public int getMinIdle() {
return this.config.minIdle;
}
public void setMinIdle(int minIdle) {
this.config.minIdle = minIdle;
}
public int getMaxActive() {
return this.config.maxActive;
}
public void setMaxActive(int maxActive) {
this.config.maxActive = maxActive;
}
public long getMaxWait() {
return this.config.maxWait;
}
public void setMaxWait(long maxWait) {
this.config.maxWait = maxWait;
}
public byte getWhenExhaustedAction() {
return this.config.whenExhaustedAction;
}
public void setWhenExhaustedAction(byte whenExhaustedAction) {
this.config.whenExhaustedAction = whenExhaustedAction;
}
public boolean isTestOnBorrow() {
return this.config.testOnBorrow;
}
public void setTestOnBorrow(boolean testOnBorrow) {
this.config.testOnBorrow = testOnBorrow;
}
public boolean isTestOnReturn() {
return this.config.testOnReturn;
}
public void setTestOnReturn(boolean testOnReturn) {
this.config.testOnReturn = testOnReturn;
}
public boolean isTestWhileIdle() {
return this.config.testWhileIdle;
}
public void setTestWhileIdle(boolean testWhileIdle) {
this.config.testWhileIdle = testWhileIdle;
}
public long getTimeBetweenEvictionRunsMillis() {
return this.config.timeBetweenEvictionRunsMillis;
}
public void setTimeBetweenEvictionRunsMillis(
long timeBetweenEvictionRunsMillis) {
this.config.timeBetweenEvictionRunsMillis =
timeBetweenEvictionRunsMillis;
}
public int getNumTestsPerEvictionRun() {
return this.config.numTestsPerEvictionRun;
}
public void setNumTestsPerEvictionRun(int numTestsPerEvictionRun) {
this.config.numTestsPerEvictionRun = numTestsPerEvictionRun;
}
public long getMinEvictableIdleTimeMillis() {
return this.config.minEvictableIdleTimeMillis;
}
public void setMinEvictableIdleTimeMillis(long minEvictableIdleTimeMillis) {
this.config.minEvictableIdleTimeMillis = minEvictableIdleTimeMillis;
}
public long getSoftMinEvictableIdleTimeMillis() {
return this.config.softMinEvictableIdleTimeMillis;
}
public void setSoftMinEvictableIdleTimeMillis(
long softMinEvictableIdleTimeMillis) {
this.config.softMinEvictableIdleTimeMillis =
softMinEvictableIdleTimeMillis;
}
public boolean isLifo() {
return this.config.lifo;
}
public void setLifo(boolean lifo) {
this.config.lifo = lifo;
}
}

View File

@ -0,0 +1,6 @@
package org.bigbluebutton.voiceconf.messaging;
public interface IMessagingService {
void userConnectedToGlobalAudio(String voiceConf, String callerIdName);
void userDisconnectedFromGlobalAudio(String voiceConf, String callerIdName);
}

View File

@ -0,0 +1,36 @@
package org.bigbluebutton.voiceconf.messaging;
import java.util.concurrent.TimeUnit;
import com.google.gson.Gson;
public class MessageBuilder {
public final static String VERSION = "version";
public static long generateTimestamp() {
return TimeUnit.NANOSECONDS.toMillis(System.nanoTime());
}
public static java.util.HashMap<String, Object> buildHeader(String name, String version, String replyTo) {
java.util.HashMap<String, Object> header = new java.util.HashMap<String, Object>();
header.put(Constants.NAME, name);
header.put(VERSION, version);
header.put(Constants.TIMESTAMP, generateTimestamp());
if (replyTo != null && replyTo != "")
header.put(Constants.REPLY_TO, replyTo);
return header;
}
public static String buildJson(java.util.HashMap<String, Object> header,
java.util.HashMap<String, Object> payload) {
java.util.HashMap<String, java.util.HashMap<String, Object>> message = new java.util.HashMap<String, java.util.HashMap<String, Object>>();
message.put(Constants.HEADER, header);
message.put(Constants.PAYLOAD, payload);
Gson gson = new Gson();
return gson.toJson(message);
}
}

View File

@ -0,0 +1,25 @@
package org.bigbluebutton.voiceconf.messaging;
import java.util.Set;
public class MessageDistributor {
private ReceivedMessageHandler handler;
private Set<MessageHandler> listeners;
public void setMessageListeners(Set<MessageHandler> listeners) {
this.listeners = listeners;
}
public void setMessageHandler(ReceivedMessageHandler handler) {
this.handler = handler;
if (handler != null) {
handler.setMessageDistributor(this);
}
}
public void notifyListeners(String pattern, String channel, String message) {
for (MessageHandler listener : listeners) {
listener.handleMessage(pattern, channel, message);
}
}
}

View File

@ -0,0 +1,23 @@
/**
* BigBlueButton open source conferencing system - http://www.bigbluebutton.org/
*
* Copyright (c) 2012 BigBlueButton Inc. and by respective authors (see below).
*
* This program is free software; you can redistribute it and/or modify it under the
* terms of the GNU Lesser General Public License as published by the Free Software
* Foundation; either version 3.0 of the License, or (at your option) any later
* version.
*
* BigBlueButton is distributed in the hope that it will be useful, but WITHOUT ANY
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
* PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License along
* with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
*
*/
package org.bigbluebutton.voiceconf.messaging;
public interface MessageHandler {
void handleMessage(String pattern, String channel, String message);
}

View File

@ -0,0 +1,88 @@
package org.bigbluebutton.voiceconf.messaging;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
import org.red5.logging.Red5LoggerFactory;
import org.slf4j.Logger;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.JedisPubSub;
public class MessageReceiver {
private static Logger log = Red5LoggerFactory.getLogger(MessageReceiver.class, "bigbluebutton");
private ReceivedMessageHandler handler;
private JedisPool redisPool;
private volatile boolean receiveMessage = false;
private final Executor msgReceiverExec = Executors.newSingleThreadExecutor();
public void stop() {
receiveMessage = false;
}
public void start() {
log.info("Ready to receive messages from Redis pubsub.");
try {
receiveMessage = true;
final Jedis jedis = redisPool.getResource();
Runnable messageReceiver = new Runnable() {
public void run() {
if (receiveMessage) {
jedis.psubscribe(new PubSubListener(), MessagingConstants.TO_BBB_APPS_PATTERN);
}
}
};
msgReceiverExec.execute(messageReceiver);
} catch (Exception e) {
log.error("Error subscribing to channels: " + e.getMessage());
}
}
public void setRedisPool(JedisPool redisPool){
this.redisPool = redisPool;
}
public void setMessageHandler(ReceivedMessageHandler handler) {
this.handler = handler;
}
private class PubSubListener extends JedisPubSub {
public PubSubListener() {
super();
}
@Override
public void onMessage(String channel, String message) {
// Not used.
}
@Override
public void onPMessage(String pattern, String channel, String message) {
handler.handleMessage(pattern, channel, message);
}
@Override
public void onPSubscribe(String pattern, int subscribedChannels) {
log.debug("Subscribed to the pattern: " + pattern);
}
@Override
public void onPUnsubscribe(String pattern, int subscribedChannels) {
// Not used.
}
@Override
public void onSubscribe(String channel, int subscribedChannels) {
// Not used.
}
@Override
public void onUnsubscribe(String channel, int subscribedChannels) {
// Not used.
}
}
}

View File

@ -0,0 +1,67 @@
package org.bigbluebutton.voiceconf.messaging;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
import java.util.concurrent.LinkedBlockingQueue;
import org.red5.logging.Red5LoggerFactory;
import org.slf4j.Logger;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
public class MessageSender {
private static Logger log = Red5LoggerFactory.getLogger(MessageSender.class, "bigbluebutton");
private JedisPool redisPool;
private volatile boolean sendMessage = false;
private final Executor msgSenderExec = Executors.newSingleThreadExecutor();
private BlockingQueue<MessageToSend> messages = new LinkedBlockingQueue<MessageToSend>();
public void stop() {
sendMessage = false;
}
public void start() {
log.info("Redis message publisher starting!");
try {
sendMessage = true;
Runnable messageSender = new Runnable() {
public void run() {
while (sendMessage) {
try {
MessageToSend msg = messages.take();
publish(msg.getChannel(), msg.getMessage());
} catch (InterruptedException e) {
log.warn("Failed to get message from queue.");
}
}
}
};
msgSenderExec.execute(messageSender);
} catch (Exception e) {
log.error("Error subscribing to channels: " + e.getMessage());
}
}
public void send(String channel, String message) {
MessageToSend msg = new MessageToSend(channel, message);
messages.add(msg);
}
private void publish(String channel, String message) {
Jedis jedis = redisPool.getResource();
try {
jedis.publish(channel, message);
} catch(Exception e){
log.warn("Cannot publish the message to redis", e);
} finally {
redisPool.returnResource(jedis);
}
}
public void setRedisPool(JedisPool redisPool){
this.redisPool = redisPool;
}
}

View File

@ -0,0 +1,19 @@
package org.bigbluebutton.voiceconf.messaging;
public class MessageToSend {
private final String channel;
private final String message;
public MessageToSend(String channel, String message) {
this.channel = channel;
this.message = message;
}
public String getChannel() {
return channel;
}
public String getMessage() {
return message;
}
}

View File

@ -0,0 +1,47 @@
/**
* BigBlueButton open source conferencing system - http://www.bigbluebutton.org/
*
* Copyright (c) 2012 BigBlueButton Inc. and by respective authors (see below).
*
* This program is free software; you can redistribute it and/or modify it under the
* terms of the GNU Lesser General Public License as published by the Free Software
* Foundation; either version 3.0 of the License, or (at your option) any later
* version.
*
* BigBlueButton is distributed in the hope that it will be useful, but WITHOUT ANY
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
* PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License along
* with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
*
*/
package org.bigbluebutton.voiceconf.messaging;
public class MessagingConstants {
public static final String FROM_BBB_APPS_CHANNEL = "bigbluebutton:from-bbb-apps";
public static final String FROM_BBB_APPS_PATTERN = FROM_BBB_APPS_CHANNEL + ":*";
public static final String FROM_SYSTEM_CHANNEL = FROM_BBB_APPS_CHANNEL + ":system";
public static final String FROM_MEETING_CHANNEL = FROM_BBB_APPS_CHANNEL + ":meeting";
public static final String FROM_PRESENTATION_CHANNEL = FROM_BBB_APPS_CHANNEL + ":presentation";
public static final String FROM_POLLING_CHANNEL = FROM_BBB_APPS_CHANNEL + ":polling";
public static final String FROM_USERS_CHANNEL = FROM_BBB_APPS_CHANNEL + ":users";
public static final String FROM_CHAT_CHANNEL = FROM_BBB_APPS_CHANNEL + ":chat";
public static final String FROM_WHITEBOARD_CHANNEL = FROM_BBB_APPS_CHANNEL + ":whiteboard";
public static final String TO_BBB_APPS_CHANNEL = "bigbluebutton:to-bbb-apps";
public static final String TO_BBB_APPS_PATTERN = TO_BBB_APPS_CHANNEL + ":*";
public static final String TO_MEETING_CHANNEL = TO_BBB_APPS_CHANNEL + ":meeting";
public static final String TO_SYSTEM_CHANNEL = TO_BBB_APPS_CHANNEL + ":system";
public static final String TO_PRESENTATION_CHANNEL = TO_BBB_APPS_CHANNEL + ":presentation";
public static final String TO_POLLING_CHANNEL = TO_BBB_APPS_CHANNEL + ":polling";
public static final String TO_USERS_CHANNEL = TO_BBB_APPS_CHANNEL + ":users";
public static final String TO_CHAT_CHANNEL = TO_BBB_APPS_CHANNEL + ":chat";
public static final String USER_DISCONNECTED_FROM_GLOBAL_AUDIO = "user_disconnected_from_global_audio";
}

View File

@ -0,0 +1,28 @@
package org.bigbluebutton.voiceconf.messaging;
public class ReceivedMessage {
private final String pattern;
private final String channel;
private final String message;
public ReceivedMessage(String pattern, String channel, String message) {
this.pattern = pattern;
this.channel = channel;
this.message = message;
}
public String getPattern() {
return pattern;
}
public String getChannel() {
return channel;
}
public String getMessage() {
return message;
}
}

View File

@ -0,0 +1,69 @@
package org.bigbluebutton.voiceconf.messaging;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
import java.util.concurrent.LinkedBlockingQueue;
import org.red5.logging.Red5LoggerFactory;
import org.slf4j.Logger;
public class ReceivedMessageHandler {
private static Logger log = Red5LoggerFactory.getLogger(ReceivedMessageHandler.class, "bigbluebutton");
private BlockingQueue<ReceivedMessage> receivedMessages = new LinkedBlockingQueue<ReceivedMessage>();
private volatile boolean processMessage = false;
private final Executor msgProcessorExec = Executors.newSingleThreadExecutor();
private MessageDistributor handler;
public void stop() {
processMessage = false;
}
public void start() {
log.info("Ready to handle messages from Redis pubsub!");
try {
processMessage = true;
Runnable messageProcessor = new Runnable() {
public void run() {
while (processMessage) {
try {
ReceivedMessage msg = receivedMessages.take();
processMessage(msg);
} catch (InterruptedException e) {
log.warn("Error while taking received message from queue.");
}
}
}
};
msgProcessorExec.execute(messageProcessor);
} catch (Exception e) {
log.error("Error subscribing to channels: " + e.getMessage());
}
}
private void processMessage(ReceivedMessage msg) {
if (handler != null) {
log.debug("Let's process this message: " + msg.getMessage());
handler.notifyListeners(msg.getPattern(), msg.getChannel(), msg.getMessage());
} else {
log.warn("No listeners interested in messages from Redis!");
}
}
public void handleMessage(String pattern, String channel, String message) {
ReceivedMessage rm = new ReceivedMessage(pattern, channel, message);
receivedMessages.add(rm);
}
public void setMessageDistributor(MessageDistributor h) {
this.handler = h;
}
}

View File

@ -0,0 +1,51 @@
package org.bigbluebutton.voiceconf.messaging;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.bigbluebutton.voiceconf.messaging.messages.UserConnectedToGlobalAudio;
import org.bigbluebutton.voiceconf.messaging.messages.UserDisconnectedFromGlobalAudio;
import org.red5.logging.Red5LoggerFactory;
import org.slf4j.Logger;
public class RedisMessagingService implements IMessagingService {
private static Logger log = Red5LoggerFactory.getLogger(RedisMessagingService.class, "sip");
private static final Pattern CALLERNAME_PATTERN = Pattern.compile("(.*)-bbbID-(.*)$");
private MessageSender sender;
@Override
public void userConnectedToGlobalAudio(String voiceConf, String callerIdName) {
Matcher matcher = CALLERNAME_PATTERN.matcher(callerIdName);
if (matcher.matches()) {
String userid = matcher.group(1).trim();
String name = matcher.group(2).trim();
String json = new UserConnectedToGlobalAudio(voiceConf, userid, name).toJson();
sender.send(MessagingConstants.TO_MEETING_CHANNEL, json);
} else {
log.warn("Invalid calleridname [{}] in userConnectedToGlobalAudio as it does not match pattern (.*)-bbbID-(.*)");
String json = new UserConnectedToGlobalAudio(voiceConf, callerIdName, callerIdName).toJson();
sender.send(MessagingConstants.TO_MEETING_CHANNEL, json);
}
}
@Override
public void userDisconnectedFromGlobalAudio(String voiceConf, String callerIdName) {
Matcher matcher = CALLERNAME_PATTERN.matcher(callerIdName);
if (matcher.matches()) {
String userid = matcher.group(1).trim();
String name = matcher.group(2).trim();
String json = new UserDisconnectedFromGlobalAudio(voiceConf, userid, name).toJson();
sender.send(MessagingConstants.TO_MEETING_CHANNEL, json);
} else {
log.warn("Invalid calleridname [{}] in userDisconnectedFromGlobalAudio as it does not match pattern (.*)-bbbID-(.*)");
String json = new UserDisconnectedFromGlobalAudio(voiceConf, callerIdName, callerIdName).toJson();
sender.send(MessagingConstants.TO_MEETING_CHANNEL, json);
}
}
public void setRedisMessageSender(MessageSender sender) {
this.sender = sender;
}
}

View File

@ -0,0 +1,62 @@
package org.bigbluebutton.voiceconf.messaging.messages;
import java.util.HashMap;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import org.bigbluebutton.voiceconf.messaging.Constants;
import org.bigbluebutton.voiceconf.messaging.MessageBuilder;
public class UserConnectedToGlobalAudio {
public static final String USER_CONNECTED_TO_GLOBAL_AUDIO = "user_connected_to_global_audio";
public static final String VERSION = "0.0.1";
public final String voiceConf;
public final String name;
public final String userid;
public UserConnectedToGlobalAudio(String voiceConf, String userid, String name) {
this.voiceConf = voiceConf;
this.userid = userid;
this.name = name;
}
public String toJson() {
HashMap<String, Object> payload = new HashMap<String, Object>();
payload.put(Constants.VOICE_CONF, voiceConf);
payload.put(Constants.USER_ID, userid);
payload.put(Constants.NAME, name);
java.util.HashMap<String, Object> header = MessageBuilder.buildHeader(USER_CONNECTED_TO_GLOBAL_AUDIO, VERSION, null);
return MessageBuilder.buildJson(header, payload);
}
public static UserConnectedToGlobalAudio fromJson(String message) {
JsonParser parser = new JsonParser();
JsonObject obj = (JsonObject) parser.parse(message);
if (obj.has("header") && obj.has("payload")) {
JsonObject header = (JsonObject) obj.get("header");
JsonObject payload = (JsonObject) obj.get("payload");
if (header.has("name")) {
String messageName = header.get("name").getAsString();
if (messageName == USER_CONNECTED_TO_GLOBAL_AUDIO) {
if (payload.has(Constants.VOICE_CONF)
&& payload.has(Constants.USER_ID)
&& payload.has(Constants.NAME)) {
String voiceConf = payload.get(Constants.VOICE_CONF).getAsString();
String userid = payload.get(Constants.USER_ID).getAsString();
String name = payload.get(Constants.NAME).getAsString();
return new UserConnectedToGlobalAudio(voiceConf, userid, name);
}
}
}
}
return null;
}
}

View File

@ -0,0 +1,60 @@
package org.bigbluebutton.voiceconf.messaging.messages;
import java.util.HashMap;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import org.bigbluebutton.voiceconf.messaging.Constants;
import org.bigbluebutton.voiceconf.messaging.MessageBuilder;
public class UserDisconnectedFromGlobalAudio {
public static final String USER_DISCONNECTED_FROM_GLOBAL_AUDIO = "user_disconnected_from_global_audio";
public static final String VERSION = "0.0.1";
public final String voiceConf;
public final String name;
public final String userid;
public UserDisconnectedFromGlobalAudio(String voiceConf, String userid, String name) {
this.voiceConf = voiceConf;
this.userid = userid;
this.name = name;
}
public String toJson() {
HashMap<String, Object> payload = new HashMap<String, Object>();
payload.put(Constants.VOICE_CONF, voiceConf);
payload.put(Constants.USER_ID, userid);
payload.put(Constants.NAME, name);
java.util.HashMap<String, Object> header = MessageBuilder.buildHeader(USER_DISCONNECTED_FROM_GLOBAL_AUDIO, VERSION, null);
return MessageBuilder.buildJson(header, payload);
}
public static UserDisconnectedFromGlobalAudio fromJson(String message) {
JsonParser parser = new JsonParser();
JsonObject obj = (JsonObject) parser.parse(message);
if (obj.has("header") && obj.has("payload")) {
JsonObject header = (JsonObject) obj.get("header");
JsonObject payload = (JsonObject) obj.get("payload");
if (header.has("name")) {
String messageName = header.get("name").getAsString();
if (messageName == USER_DISCONNECTED_FROM_GLOBAL_AUDIO) {
if (payload.has(Constants.VOICE_CONF)
&& payload.has(Constants.USER_ID)
&& payload.has(Constants.NAME)) {
String voiceConf = payload.get(Constants.VOICE_CONF).getAsString();
String userid = payload.get(Constants.USER_ID).getAsString();
String name = payload.get(Constants.NAME).getAsString();
return new UserDisconnectedFromGlobalAudio(voiceConf, userid, name);
}
}
}
}
return null;
}
}

View File

@ -43,16 +43,22 @@ private static Logger log = Red5LoggerFactory.getLogger(ClientConnection.class,
public void onJoinConferenceSuccess(String publishName, String playName, String codec) {
log.debug("Notify client that {} [{}] has joined the conference.", username, userid);
connection.invoke("successfullyJoinedVoiceConferenceCallback", new Object[] {publishName, playName, codec});
if (connection.isConnected()) {
connection.invoke("successfullyJoinedVoiceConferenceCallback", new Object[] {publishName, playName, codec});
}
}
public void onJoinConferenceFail() {
log.debug("Notify client that {} [{}] failed to join the conference.", username, userid);
connection.invoke("failedToJoinVoiceConferenceCallback", new Object[] {"onUaCallFailed"});
if (connection.isConnected()) {
connection.invoke("failedToJoinVoiceConferenceCallback", new Object[] {"onUaCallFailed"});
}
}
public void onLeaveConference() {
log.debug("Notify client that {} [{}] left the conference.", username, userid);
connection.invoke("disconnectedFromJoinVoiceConferenceCallback", new Object[] {"onUaCallClosed"});
if (connection.isConnected()) {
connection.invoke("disconnectedFromJoinVoiceConferenceCallback", new Object[] {"onUaCallClosed"});
}
}
}

View File

@ -25,6 +25,7 @@ import org.bigbluebutton.voiceconf.sip.SipPeerManager;
import org.red5.logging.Red5LoggerFactory;
import org.red5.server.api.IConnection;
import org.red5.server.api.Red5;
import org.bigbluebutton.voiceconf.sip.GlobalCall;
public class Service {
private static Logger log = Red5LoggerFactory.getLogger(Service.class, "sip");
@ -33,6 +34,27 @@ public class Service {
private MessageFormat callExtensionPattern = new MessageFormat("{0}");
public Boolean call(String peerId, String callerName, String destination, Boolean listenOnly) {
if (listenOnly) {
if (GlobalCall.reservePlaceToCreateGlobal(destination)) {
String extension = callExtensionPattern.format(new String[] { destination });
try {
sipPeerManager.call(peerId, destination, "GLOBAL_AUDIO_" + destination, extension);
Red5.getConnectionLocal().setAttribute("VOICE_CONF_PEER", peerId);
} catch (PeerNotFoundException e) {
log.error("PeerNotFound {}", peerId);
return false;
}
}
sipPeerManager.connectToGlobalStream(peerId, getClientId(), callerName, destination);
Red5.getConnectionLocal().setAttribute("VOICE_CONF_PEER", peerId);
return true;
} else {
Boolean result = call(peerId, callerName, destination);
return result;
}
}
public Boolean call(String peerId, String callerName, String destination) {
String clientId = Red5.getConnectionLocal().getClient().getId();
String userid = getUserId();

View File

@ -26,7 +26,8 @@ import org.red5.logging.Red5LoggerFactory;
import org.red5.server.api.event.IEvent;
import org.red5.server.api.scope.IScope;
import org.red5.server.api.stream.IBroadcastStream;
import org.red5.server.api.stream.IStreamCodecInfo;
import org.red5.codec.IStreamCodecInfo;
import org.red5.codec.StreamCodecInfo;
import org.red5.server.api.stream.IStreamListener;
import org.red5.server.api.stream.ResourceExistException;
import org.red5.server.api.stream.ResourceNotFoundException;
@ -38,11 +39,8 @@ import org.red5.server.messaging.OOBControlMessage;
import org.red5.server.messaging.PipeConnectionEvent;
import org.red5.server.net.rtmp.event.IRTMPEvent;
import org.red5.server.net.rtmp.event.Notify;
import org.red5.server.stream.codec.StreamCodecInfo;
import org.red5.server.stream.message.RTMPMessage;
import org.slf4j.Logger;
import org.red5.server.api.stream.IStreamPacket;;
public class AudioBroadcastStream implements IBroadcastStream, IProvider, IPipeConnectionListener {

View File

@ -37,7 +37,7 @@ public class CallStream implements StreamObserver {
private FlashToSipAudioStream userTalkStream;
private SipToFlashAudioStream userListenStream;
private final Codec sipCodec;
public final Codec sipCodec;
private final SipConnectInfo connInfo;
private final IScope scope;
private CallStreamObserver callStreamObserver;
@ -80,6 +80,10 @@ public class CallStream implements StreamObserver {
public String getListenStreamName() {
return userListenStream.getStreamName();
}
public Codec getSipCodec() {
return sipCodec;
}
public void startTalkStream(IBroadcastStream broadcastStream, IScope scope) throws StreamException {
log.debug("userTalkStream setup");

View File

@ -18,11 +18,14 @@
*/
package org.bigbluebutton.voiceconf.sip;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.zoolu.sip.call.*;
import org.zoolu.sip.provider.SipProvider;
import org.zoolu.sip.provider.SipStack;
import org.zoolu.sip.message.*;
import org.zoolu.sdp.*;
import org.bigbluebutton.voiceconf.messaging.IMessagingService;
import org.bigbluebutton.voiceconf.red5.CallStreamFactory;
import org.bigbluebutton.voiceconf.red5.ClientConnectionManager;
import org.bigbluebutton.voiceconf.red5.media.CallStream;
@ -55,7 +58,11 @@ public class CallAgent extends CallListenerAdapter implements CallStreamObserver
private ClientConnectionManager clientConnManager;
private final String clientId;
private final AudioConferenceProvider portProvider;
private DatagramSocket localSocket;
private DatagramSocket localSocket = null;
private String _callerName;
private String _destination;
private Boolean listeningToGlobal = false;
private IMessagingService messagingService;
private enum CallState {
UA_IDLE(0), UA_INCOMING_CALL(1), UA_OUTGOING_CALL(2), UA_ONCALL(3);
@ -66,12 +73,18 @@ public class CallAgent extends CallListenerAdapter implements CallStreamObserver
private CallState callState;
public CallAgent(String sipClientRtpIp, SipProvider sipProvider, SipPeerProfile userProfile, AudioConferenceProvider portProvider, String clientId) {
public String getDestination() {
return _destination;
}
public CallAgent(String sipClientRtpIp, SipProvider sipProvider, SipPeerProfile userProfile,
AudioConferenceProvider portProvider, String clientId, IMessagingService messagingService) {
this.sipProvider = sipProvider;
this.clientRtpIp = sipClientRtpIp;
this.userProfile = userProfile;
this.portProvider = portProvider;
this.clientId = clientId;
this.messagingService = messagingService;
}
public String getCallId() {
@ -79,7 +92,7 @@ public class CallAgent extends CallListenerAdapter implements CallStreamObserver
}
private void initSessionDescriptor() {
log.debug("initSessionDescriptor");
log.debug("initSessionDescriptor");
SessionDescriptor newSdp = SdpUtils.createInitialSdp(userProfile.username,
this.clientRtpIp, userProfile.audioPort,
userProfile.videoPort, userProfile.audioCodecsPrecedence );
@ -87,7 +100,13 @@ public class CallAgent extends CallListenerAdapter implements CallStreamObserver
log.debug("localSession Descriptor = " + localSession );
}
public Boolean isListeningToGlobal() {
return listeningToGlobal;
}
public void call(String callerName, String destination) {
_callerName = callerName;
_destination = destination;
log.debug("{} making a call to {}", callerName, destination);
try {
localSocket = getLocalAudioSocket();
@ -131,11 +150,15 @@ public class CallAgent extends CallListenerAdapter implements CallStreamObserver
/** Closes an ongoing, incoming, or pending call */
public void hangup() {
if (callState == CallState.UA_IDLE) return;
log.debug("hangup");
if (callState == CallState.UA_IDLE) return;
closeVoiceStreams();
if (call != null) call.hangup();
if (listeningToGlobal) {
log.debug("Hanging up of a call connected to the global audio stream");
notifyListenersOfOnCallClosed();
} else {
closeVoiceStreams();
if (call != null) call.hangup();
}
callState = CallState.UA_IDLE;
}
@ -163,6 +186,10 @@ public class CallAgent extends CallListenerAdapter implements CallStreamObserver
return socket;
}
private boolean isGlobalAudioStream() {
return (_callerName != null && _callerName.startsWith("GLOBAL_AUDIO_"));
}
private void createVoiceStreams() {
if (callStream != null) {
@ -187,7 +214,11 @@ public class CallAgent extends CallListenerAdapter implements CallStreamObserver
callStream = callStreamFactory.createCallStream(sipCodec, connInfo);
callStream.addCallStreamObserver(this);
callStream.start();
notifyListenersOnCallConnected(callStream.getTalkStreamName(), callStream.getListenStreamName());
if (isGlobalAudioStream()) {
GlobalCall.addGlobalAudioStream(_destination, callStream.getListenStreamName(), sipCodec, connInfo);
} else {
notifyListenersOnCallConnected(callStream.getTalkStreamName(), callStream.getListenStreamName());
}
} catch (Exception e) {
log.error("Failed to create Call Stream.");
System.out.println(StackTraceUtil.getStackTrace(e));
@ -214,15 +245,41 @@ public class CallAgent extends CallListenerAdapter implements CallStreamObserver
callStream.stopTalkStream(broadcastStream, scope);
}
}
public void connectToGlobalStream(String clientId, String callerIdName, String voiceConf) {
listeningToGlobal = true;
_destination = voiceConf;
String globalAudioStreamName = GlobalCall.getGlobalAudioStream(voiceConf);
while (globalAudioStreamName.equals("reserved")) {
try {
Thread.sleep(100);
} catch (Exception e) {
}
globalAudioStreamName = GlobalCall.getGlobalAudioStream(voiceConf);
}
GlobalCall.addUser(clientId, callerIdName, _destination);
sipCodec = GlobalCall.getRoomCodec(voiceConf);
callState = CallState.UA_ONCALL;
notifyListenersOnCallConnected("", globalAudioStreamName);
log.info("User is has connected to global audio, user=[" + callerIdName + "] voiceConf = [" + voiceConf + "]");
messagingService.userConnectedToGlobalAudio(voiceConf, callerIdName);
}
private void closeVoiceStreams() {
log.debug("Shutting down the voice streams.");
if (callStream != null) {
callStream.stop();
callStream = null;
} else {
log.debug("Can't shutdown voice stream. callstream is NULL");
}
if (callStream != null) {
callStream.stop();
callStream = null;
} else {
log.debug("Can't shutdown voice stream. callstream is NULL");
}
}
// ********************** Call callback functions **********************
@ -260,10 +317,7 @@ public class CallAgent extends CallListenerAdapter implements CallStreamObserver
public void onCallAccepted(Call call, String sdp, Message resp) {
log.debug("Received 200/OK. So user has successfully joined the conference.");
if (!isCurrentCall(call)) return;
log.debug("ACCEPTED/CALL.");
callState = CallState.UA_ONCALL;
setupSdpAndCodec(sdp);
if (userProfile.noOffer) {
@ -330,18 +384,20 @@ public class CallAgent extends CallListenerAdapter implements CallStreamObserver
}
private void notifyListenersOfOnCallClosed() {
if (callState == CallState.UA_IDLE) return;
log.debug("notifyListenersOfOnCallClosed for {}", clientId);
clientConnManager.leaveConference(clientId);
cleanup();
}
private void cleanup() {
log.debug("Closing local audio port {}", localSocket.getLocalPort());
if (localSocket != null) {
localSocket.close();
} else {
log.debug("Trying to close un-allocated port {}", localSocket.getLocalPort());
}
if (localSocket == null) return;
log.debug("Closing local audio port {}", localSocket.getLocalPort());
if (!listeningToGlobal) {
localSocket.close();
}
}
/** Callback function called when arriving a BYE request */

View File

@ -0,0 +1,83 @@
package org.bigbluebutton.voiceconf.sip;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import org.red5.app.sip.codecs.Codec;
import org.red5.logging.Red5LoggerFactory;
import org.slf4j.Logger;
public class GlobalCall {
private static final Logger log = Red5LoggerFactory.getLogger( GlobalCall.class, "sip" );
private static Map<String,String> roomToStreamMap = new ConcurrentHashMap<String, String>();
private static Map<String,Codec> roomToCodecMap = new ConcurrentHashMap<String, Codec>();
private static Map<String,KeepGlobalAudioAlive> globalAudioKeepAliverMap = new ConcurrentHashMap<String, KeepGlobalAudioAlive>();
private static Map<String, VoiceConfToListenOnlyUsersMap> voiceConfToListenOnlyUsersMap = new ConcurrentHashMap<String, VoiceConfToListenOnlyUsersMap>();
public static synchronized boolean reservePlaceToCreateGlobal(String roomName) {
if (roomToStreamMap.containsKey(roomName)) {
log.debug("There's already a global audio stream for room {}, no need to create a new one", roomName);
return false;
} else {
log.debug("Reserving the place to create a global audio stream for room {}", roomName);
roomToStreamMap.put(roomName, "reserved");
return true;
}
}
public static synchronized void addGlobalAudioStream(String voiceConf, String globalAudioStreamName, Codec sipCodec, SipConnectInfo connInfo) {
log.debug("Adding a global audio stream to room {}", voiceConf);
roomToStreamMap.put(voiceConf, globalAudioStreamName);
roomToCodecMap.put(voiceConf, sipCodec);
voiceConfToListenOnlyUsersMap.put(voiceConf, new VoiceConfToListenOnlyUsersMap(voiceConf));
KeepGlobalAudioAlive globalAudioKeepAlive = new KeepGlobalAudioAlive(connInfo.getSocket(), connInfo, sipCodec.getCodecId());
globalAudioKeepAliverMap.put(voiceConf, globalAudioKeepAlive);
globalAudioKeepAlive.start();
}
public static synchronized String getGlobalAudioStream(String voiceConf) {
return roomToStreamMap.get(voiceConf);
}
public static synchronized boolean removeRoomIfUnused(String voiceConf) {
if (voiceConfToListenOnlyUsersMap.containsKey(voiceConf) && voiceConfToListenOnlyUsersMap.get(voiceConf).numUsers() <= 0) {
removeRoom(voiceConf);
return true;
} else {
return false;
}
}
private static void removeRoom(String voiceConf) {
log.debug("Removing global audio stream of room {}", voiceConf);
roomToStreamMap.remove(voiceConf);
voiceConfToListenOnlyUsersMap.remove(voiceConf);
roomToCodecMap.remove(voiceConf);
KeepGlobalAudioAlive globalAudioKeepAlive = globalAudioKeepAliverMap.get(voiceConf);
globalAudioKeepAlive.halt();
globalAudioKeepAliverMap.remove(voiceConf);
}
public static synchronized void addUser(String clientId, String callerIdName, String voiceConf) {
if (voiceConfToListenOnlyUsersMap.containsKey(voiceConf)) {
VoiceConfToListenOnlyUsersMap map = voiceConfToListenOnlyUsersMap.get(voiceConf);
map.addUser(clientId, callerIdName);
int numUsers = map.numUsers();
log.debug("Adding new user to voiceConf [{}], current number of users on global stream is {}", voiceConf, numUsers);
}
}
public static synchronized ListenOnlyUser removeUser(String clientId, String voiceConf) {
if (voiceConfToListenOnlyUsersMap.containsKey(voiceConf)) {
return voiceConfToListenOnlyUsersMap.get(voiceConf).removeUser(clientId);
}
return null;
}
public static Codec getRoomCodec(String roomName) {
return roomToCodecMap.get(roomName);
}
}

View File

@ -145,13 +145,11 @@ public class KeepAliveUdp extends Thread
{ try
{ while(!stop)
{ sendToken();
//System.out.print(".");
Thread.sleep(delta_time);
if (expire>0 && System.currentTimeMillis()>expire) halt();
}
}
catch (Exception e) { e.printStackTrace(); }
//System.out.println("o");
udp_socket=null;
}
@ -165,4 +163,4 @@ public class KeepAliveUdp extends Thread
return str+" ("+delta_time+"ms)";
}
}
}

View File

@ -0,0 +1,130 @@
/**
*
* BigBlueButton open source conferencing system - http://www.bigbluebutton.org/
*
* Copyright (c) 2010 BigBlueButton Inc. and by respective authors (see below).
*
* This program is free software; you can redistribute it and/or modify it under the
* terms of the GNU General Public License as published by the Free Software
* Foundation; either version 2.1 of the License, or (at your option) any later
* version.
*
* BigBlueButton is distributed in the hope that it will be useful, but WITHOUT ANY
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
* PARTICULAR PURPOSE. See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
*
**/
package org.bigbluebutton.voiceconf.sip;
import java.io.IOException;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.Random;
import org.bigbluebutton.voiceconf.red5.media.*;
import org.slf4j.Logger;
import org.bigbluebutton.voiceconf.red5.media.net.RtpPacket;
import org.bigbluebutton.voiceconf.red5.media.net.RtpSocket;
import org.bigbluebutton.voiceconf.sip.SipConnectInfo;
import org.bigbluebutton.voiceconf.util.StackTraceUtil;
import org.red5.logging.Red5LoggerFactory;
import java.lang.InterruptedException;
public class KeepGlobalAudioAlive extends Thread {
// Time is in milliseconds
// This time should be less than rtp-timeout-sec to prevent freeswitch from kicking out
// the global audio
private static final long DELTA_TIME_TO_SEND_KEEPALIVE = 60000;
private static final int RTP_HEADER_SIZE = 12;
private RtpSocket rtpSocket = null;
private int sequenceNum = 0;
private final DatagramSocket srcSocket;
private final SipConnectInfo connInfo;
private boolean marked = false;
private long startTimestamp = 0;
boolean stop=false;
int codecId;
private static Logger log = Red5LoggerFactory.getLogger(KeepGlobalAudioAlive.class, "sip");
public KeepGlobalAudioAlive(DatagramSocket srcSocket, SipConnectInfo connInfo, int codecId) {
this.srcSocket = srcSocket;
this.connInfo = connInfo;
this.codecId = codecId;
connect();
}
public void connect() {
try {
rtpSocket = new RtpSocket(srcSocket, InetAddress.getByName(connInfo.getRemoteAddr()), connInfo.getRemotePort());
Random rgen = new Random();
sequenceNum = rgen.nextInt();
} catch (UnknownHostException e) {
log.error("Failed to connect to {}", connInfo.getRemoteAddr());
log.error(StackTraceUtil.getStackTrace(e));
log.error("Rtp sender failed to connect to " + connInfo.getRemoteAddr() + ".");
}
}
public void run() {
try
{
while(!stop)
{
byte array[]= new byte[]{0,0,0,0};
sendAudio(array, startTimestamp);
startTimestamp++;
Thread.sleep(DELTA_TIME_TO_SEND_KEEPALIVE);
}
}
catch (InterruptedException e) {
log.error("Failed to sleep time in keepAlive");
}
}
public void halt()
{
stop=true;
}
public void sendAudio(byte[] audioData, long timestamp) {
byte[] transcodedAudioDataBuffer = new byte[audioData.length + RTP_HEADER_SIZE];
System.arraycopy(audioData, 0, transcodedAudioDataBuffer, RTP_HEADER_SIZE, audioData.length);
RtpPacket rtpPacket = new RtpPacket(transcodedAudioDataBuffer, transcodedAudioDataBuffer.length);
if (!marked) {
rtpPacket.setMarker(true);
marked = true;
startTimestamp = System.currentTimeMillis();
}
rtpPacket.setPadding(false);
rtpPacket.setExtension(false);
rtpPacket.setPayloadType(codecId);
rtpPacket.setSeqNum(sequenceNum++);
rtpPacket.setTimestamp(timestamp);
rtpPacket.setPayloadLength(audioData.length);
try {
rtpSocketSend(rtpPacket);
} catch (StreamException e) {
// TODO Auto-generated catch block
e.printStackTrace();
log.error("Failed to send data to server.");
}
}
private synchronized void rtpSocketSend(RtpPacket rtpPacket) throws StreamException {
try {
rtpSocket.send(rtpPacket);
} catch (IOException e) {
throw new StreamException("Failed to send data to server.");
}
}
}

View File

@ -0,0 +1,14 @@
package org.bigbluebutton.voiceconf.sip;
public class ListenOnlyUser {
public final String clientId;
public final String callerIdName;
public final String voiceConf;
public ListenOnlyUser(String clientId, String callerIdName, String voiceConf) {
this.clientId = clientId;
this.callerIdName = callerIdName;
this.voiceConf = voiceConf;
}
}

View File

@ -20,10 +20,10 @@ package org.bigbluebutton.voiceconf.sip;
import java.util.Collection;
import java.util.Iterator;
import java.util.concurrent.atomic.AtomicInteger;
import org.zoolu.sip.provider.*;
import org.zoolu.net.SocketAddress;
import org.slf4j.Logger;
import org.bigbluebutton.voiceconf.messaging.IMessagingService;
import org.bigbluebutton.voiceconf.red5.CallStreamFactory;
import org.bigbluebutton.voiceconf.red5.ClientConnectionManager;
import org.red5.logging.Red5LoggerFactory;
@ -43,7 +43,7 @@ public class SipPeer implements SipRegisterAgentListener {
private CallStreamFactory callStreamFactory;
private CallManager callManager = new CallManager();
private IMessagingService messagingService;
private SipProvider sipProvider;
private String clientRtpIp;
private SipRegisterAgent registerAgent;
@ -53,9 +53,12 @@ public class SipPeer implements SipRegisterAgentListener {
private boolean registered = false;
private SipPeerProfile registeredProfile;
public SipPeer(String id, String sipClientRtpIp, String host, int sipPort, int startAudioPort, int stopAudioPort) {
public SipPeer(String id, String sipClientRtpIp, String host, int sipPort,
int startAudioPort, int stopAudioPort, IMessagingService messagingService) {
this.id = id;
this.clientRtpIp = sipClientRtpIp;
this.messagingService = messagingService;
audioconfProvider = new AudioConferenceProvider(host, sipPort, startAudioPort, stopAudioPort);
initSipProvider(host, sipPort);
}
@ -98,7 +101,6 @@ public class SipPeer implements SipRegisterAgentListener {
log.debug( "SIPUser register : {}", fromURL );
log.debug( "SIPUser register : {}", registeredProfile.contactUrl );
}
public void call(String clientId, String callerName, String destination) {
if (!registered) {
@ -112,13 +114,26 @@ public class SipPeer implements SipRegisterAgentListener {
log.warn("We are not registered to FreeSWITCH. However, we will allow {} to call {}.", callerName, destination);
// return;
}
SipPeerProfile callerProfile = SipPeerProfile.copy(registeredProfile);
CallAgent ca = new CallAgent(this.clientRtpIp, sipProvider, callerProfile, audioconfProvider, clientId);
CallAgent ca = createCallAgent(clientId);
ca.call(callerName, destination);
}
public void connectToGlobalStream(String clientId, String callerIdName, String destination) {
CallAgent ca = createCallAgent(clientId);
ca.connectToGlobalStream(clientId, callerIdName, destination);
}
private CallAgent createCallAgent(String clientId) {
SipPeerProfile callerProfile = SipPeerProfile.copy(registeredProfile);
CallAgent ca = new CallAgent(this.clientRtpIp, sipProvider, callerProfile, audioconfProvider, clientId, messagingService);
ca.setClientConnectionManager(clientConnManager);
ca.setCallStreamFactory(callStreamFactory);
callManager.add(ca);
ca.call(callerName, destination);
return ca;
}
public void close() {
@ -134,11 +149,30 @@ public class SipPeer implements SipRegisterAgentListener {
}
public void hangup(String clientId) {
log.debug( "SIPUser hangup" );
log.debug( "SIPUser hangup" );
CallAgent ca = callManager.remove(clientId);
CallAgent ca = callManager.remove(clientId);
if (ca != null) {
ca.hangup();
if (ca.isListeningToGlobal()) {
String destination = ca.getDestination();
ListenOnlyUser lou = GlobalCall.removeUser(clientId, destination);
if (lou != null) {
log.info("User has disconnected from global audio, user [{}] voiceConf {}", lou.callerIdName, lou.voiceConf);
messagingService.userDisconnectedFromGlobalAudio(lou.voiceConf, lou.callerIdName);
}
ca.hangup();
boolean roomRemoved = GlobalCall.removeRoomIfUnused(destination);
log.debug("Should the global connection be removed? {}", roomRemoved? "yes": "no");
if (roomRemoved) {
log.debug("Hanging up the global audio call {}", destination);
CallAgent caGlobal = callManager.remove(destination);
caGlobal.hangup();
}
} else {
ca.hangup();
}
}
}
@ -150,7 +184,7 @@ public class SipPeer implements SipRegisterAgentListener {
CallAgent ca = (CallAgent) iter.next();
ca.hangup();
}
if (registerAgent != null) {
registerAgent.unregister();
registerAgent = null;

View File

@ -20,6 +20,7 @@ package org.bigbluebutton.voiceconf.sip;
import org.slf4j.Logger;
import org.zoolu.sip.provider.SipStack;
import org.bigbluebutton.voiceconf.messaging.IMessagingService;
import org.bigbluebutton.voiceconf.red5.CallStreamFactory;
import org.bigbluebutton.voiceconf.red5.ClientConnectionManager;
import org.red5.logging.Red5LoggerFactory;
@ -38,6 +39,7 @@ public final class SipPeerManager {
private ClientConnectionManager clientConnManager;
private CallStreamFactory callStreamFactory;
private IMessagingService messagingService;
private Map<String, SipPeer> sipPeers;
private int sipStackDebugLevel = 8;
@ -48,7 +50,7 @@ public final class SipPeerManager {
}
public void createSipPeer(String peerId, String clientRtpIp, String host, int sipPort, int startRtpPort, int stopRtpPort) {
SipPeer sipPeer = new SipPeer(peerId, clientRtpIp, host, sipPort, startRtpPort, stopRtpPort);
SipPeer sipPeer = new SipPeer(peerId, clientRtpIp, host, sipPort, startRtpPort, stopRtpPort, messagingService);
sipPeer.setClientConnectionManager(clientConnManager);
sipPeer.setCallStreamFactory(callStreamFactory);
sipPeers.put(peerId, sipPeer);
@ -65,7 +67,7 @@ public final class SipPeerManager {
if (sipPeer == null) throw new PeerNotFoundException("Can't find sip peer " + peerId);
sipPeer.call(clientId, callerName, destination);
}
public void unregister(String userid) {
SipPeer sipUser = sipPeers.get(userid);
if (sipUser != null) {
@ -100,6 +102,13 @@ public final class SipPeerManager {
sipPeers.remove(userid);
}
public void connectToGlobalStream(String peerId, String clientId, String callerIdName, String destination) {
SipPeer sipUser = sipPeers.get(peerId);
if (sipUser != null) {
sipUser.connectToGlobalStream(clientId, callerIdName, destination);
}
}
public void close(String userid) {
SipPeer sipUser = sipPeers.get(userid);
if (sipUser != null) {
@ -141,4 +150,8 @@ public final class SipPeerManager {
public void setClientConnectionManager(ClientConnectionManager ccm) {
clientConnManager = ccm;
}
public void setMessagingService(IMessagingService service) {
messagingService = service;
}
}

View File

@ -0,0 +1,26 @@
package org.bigbluebutton.voiceconf.sip;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
public class VoiceConfToListenOnlyUsersMap {
private Map<String, ListenOnlyUser> listenOnlyUsers = new ConcurrentHashMap<String, ListenOnlyUser>();
public final String voiceConf;
public VoiceConfToListenOnlyUsersMap(String voiceConf) {
this.voiceConf = voiceConf;
}
public void addUser(String clientId, String callerIdName) {
listenOnlyUsers.put(clientId, new ListenOnlyUser(clientId, callerIdName, voiceConf));
}
public ListenOnlyUser removeUser(String clientId) {
return listenOnlyUsers.remove(clientId);
}
public int numUsers() {
return listenOnlyUsers.size();
}
}

View File

@ -8,7 +8,8 @@ import org.red5.logging.Red5LoggerFactory;
import org.red5.server.api.event.IEvent;
import org.red5.server.api.scope.IScope;
import org.red5.server.api.stream.IBroadcastStream;
import org.red5.server.api.stream.IStreamCodecInfo;
import org.red5.codec.IStreamCodecInfo;
import org.red5.codec.StreamCodecInfo;
import org.red5.server.api.stream.IStreamListener;
import org.red5.server.api.stream.ResourceExistException;
import org.red5.server.api.stream.ResourceNotFoundException;
@ -20,7 +21,6 @@ import org.red5.server.messaging.OOBControlMessage;
import org.red5.server.messaging.PipeConnectionEvent;
import org.red5.server.net.rtmp.event.IRTMPEvent;
import org.red5.server.net.rtmp.event.Notify;
import org.red5.server.stream.codec.StreamCodecInfo;
import org.red5.server.stream.message.RTMPMessage;
import org.slf4j.Logger;
import org.red5.server.api.stream.IStreamPacket;;

View File

@ -0,0 +1,58 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
BigBlueButton open source conferencing system - http://www.bigbluebutton.org/
Copyright (c) 2012 BigBlueButton Inc. and by respective authors (see below).
This program is free software; you can redistribute it and/or modify it under the
terms of the GNU Lesser General Public License as published by the Free Software
Foundation; either version 3.0 of the License, or (at your option) any later
version.
BigBlueButton is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License along
with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
-->
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:util="http://www.springframework.org/schema/util"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
http://www.springframework.org/schema/util
http://www.springframework.org/schema/util/spring-util-2.0.xsd
">
<bean id="messagingService" class="org.bigbluebutton.voiceconf.messaging.RedisMessagingService">
<property name="redisMessageSender"> <ref bean="redisMessageSender"/></property>
</bean>
<bean id="redisMessageSender" class="org.bigbluebutton.voiceconf.messaging.MessageSender"
init-method="start" destroy-method="stop">
<property name="redisPool"> <ref bean="redisPool"/></property>
</bean>
<!--
<bean id="redisMessageReceiver" class="org.bigbluebutton.voiceconf.messaging.MessageReceiver"
init-method="start" destroy-method="stop">
<property name="redisPool"> <ref bean="redisPool"/></property>
<property name="messageHandler"> <ref local="redisMessageHandler"/> </property>
</bean>
<bean id="redisMessageHandler" class="org.bigbluebutton.voiceconf.messaging.ReceivedMessageHandler"
init-method="start" destroy-method="stop">
<property name="messageDistributor"><ref bean="redisMessageDistributor" /></property>
</bean>
<bean id="redisMessageDistributor" class="org.bigbluebutton.voiceconf.messaging.MessageDistributor">
<property name="messageHandler"> <ref local="redisMessageHandler"/> </property>
<property name="messageListeners">
<set>
</set>
</property>
</bean>
-->
</beans>

View File

@ -0,0 +1,66 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
BigBlueButton open source conferencing system - http://www.bigbluebutton.org/
Copyright (c) 2012 BigBlueButton Inc. and by respective authors (see below).
This program is free software; you can redistribute it and/or modify it under the
terms of the GNU Lesser General Public License as published by the Free Software
Foundation; either version 3.0 of the License, or (at your option) any later
version.
BigBlueButton is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License along
with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
-->
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:util="http://www.springframework.org/schema/util"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
http://www.springframework.org/schema/util
http://www.springframework.org/schema/util/spring-util-2.0.xsd
">
<bean id="redisPool" class="redis.clients.jedis.JedisPool">
<constructor-arg index="0">
<bean factory-bean="config" factory-method="getConfig" />
</constructor-arg>
<constructor-arg index="1" value="${redis.host}"/>
<constructor-arg index="2" value="${redis.port}"/>
</bean>
<bean id="config" class="org.bigbluebutton.voiceconf.messaging.GenericObjectPoolConfigWrapper">
<!-- Action to take when trying to acquire a connection and all connections are taken -->
<property name="whenExhaustedAction">
<!-- Fail-fast behaviour, we don't like to keep the kids waiting -->
<util:constant static-field="org.apache.commons.pool.impl.GenericObjectPool.WHEN_EXHAUSTED_FAIL" />
<!-- Default behaviour, block the caller until a resource becomes available -->
<!--<util:constant static-field="org.apache.commons.pool.impl.GenericObjectPool.WHEN_EXHAUSTED_BLOCK" />-->
</property>
<!-- Maximum active connections to Redis instance -->
<property name="maxActive" value="12" />
<!-- Number of connections to Redis that just sit there and do nothing -->
<property name="maxIdle" value="6" />
<!-- Minimum number of idle connections to Redis - these can be seen as always open and ready to serve -->
<property name="minIdle" value="1" />
<!-- Tests whether connection is dead when connection retrieval method is called -->
<property name="testOnBorrow" value="true" />
<!-- Tests whether connection is dead when returning a connection to the pool -->
<property name="testOnReturn" value="true" />
<!-- Tests whether connections are dead during idle periods -->
<property name="testWhileIdle" value="true" />
<!-- Maximum number of connections to test in each idle check -->
<property name="numTestsPerEvictionRun" value="12" />
<!-- Idle connection checking period -->
<property name="timeBetweenEvictionRunsMillis" value="60000" />
<!-- Maximum time, in milliseconds, to wait for a resource when exausted action is set to WHEN_EXAUSTED_BLOCK -->
<property name="maxWait" value="5000" />
</bean>
</beans>

View File

@ -17,10 +17,10 @@ freeswitch.port=5060
startAudioPort=15000
stopAudioPort=16383
# An extension pattern, in case your asterisk extensions.conf
# uses a naming convetion for your meeting rooms
# e.g. conf-85115 instead of just 85115
callExtensionPattern={0}
redis.host=127.0.0.1
redis.port=6379
redis.pass=
# If you want mjsip stack (red5/log/*access*.log) to minimize the amount of logs it
# generates, set this to a lower value (e.g. 3).

View File

@ -22,8 +22,10 @@ with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:lang="http://www.springframework.org/schema/lang"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.0.xsd
http://www.springframework.org/schema/lang http://www.springframework.org/schema/lang/spring-lang-2.0.xsd">
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-2.0.xsd
http://www.springframework.org/schema/lang
http://www.springframework.org/schema/lang/spring-lang-2.0.xsd">
<bean id="placeholderConfig" class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
<property name="locations">
@ -67,8 +69,13 @@ with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
<bean id="sipPeerManager" class="org.bigbluebutton.voiceconf.sip.SipPeerManager">
<property name="sipStackDebugLevel" value="${sipStackDebugLevel}"/>
<property name="sipRemotePort" value="${freeswitch.port}"/>
<property name="messagingService" ref="messagingService"/>
</bean>
<bean id="clientConnectionManager" class="org.bigbluebutton.voiceconf.red5.ClientConnectionManager"/>
<import resource="bbb-redis-pool.xml"/>
<import resource="bbb-redis-messaging.xml"/>
</beans>

40
bbb.sh Executable file
View File

@ -0,0 +1,40 @@
#!/bin/bash
set -x
RED5_DIR=/usr/share/red5
BBB_DIR=$(pwd)
cd $BBB_DIR
DESKSHARE=$BBB_DIR/deskshare
VOICE=$BBB_DIR/bbb-voice
VIDEO=$BBB_DIR/bbb-video
APPS=$BBB_DIR/bigbluebutton-apps
echo "Building apps"
cd $APPS
gradle resolveDeps
gradle clean war deploy
echo "Building voice"
cd $VOICE
gradle resolveDeps
gradle clean war deploy
echo "Building video"
cd $VIDEO
gradle resolveDeps
gradle clean war deploy
echo "Building deskshare"
cd $DESKSHARE
gradle resolveDeps
cd $DESKSHARE/app
gradle clean war deploy
cd $BBB_DIR
sudo chown -R red5.adm $RED5_DIR
sudo chmod -R 777 $RED5_DIR/webapps/

200
bigbluebutton-apps/build.gradle Executable file → Normal file
View File

@ -1,7 +1,7 @@
usePlugin 'scala'
usePlugin 'java'
usePlugin 'war'
usePlugin 'eclipse'
apply plugin: 'scala'
apply plugin: 'java'
apply plugin: 'war'
apply plugin: 'eclipse'
version = '0.8'
jar.enabled = true
@ -10,112 +10,121 @@ def appName = 'bigbluebutton'
archivesBaseName = appName
task resolveDeps(dependsOn: configurations.default.buildArtifacts, type: Copy) {
task resolveDeps(type: Copy) {
into('lib')
from configurations.default
from configurations.default.allArtifacts*.file
from configurations.default.allArtifacts.file
}
repositories {
add(new org.apache.ivy.plugins.resolver.ChainResolver()) {
name = 'remote'
returnFirst = true
add(new org.apache.ivy.plugins.resolver.URLResolver()) {
name = "googlecode"
addArtifactPattern "http://red5.googlecode.com/svn/repository/[artifact](-[revision]).[ext]"
addArtifactPattern "http://red5.googlecode.com/svn/repository/[organisation]/[artifact](-[revision]).[ext]"
}
add(new org.apache.ivy.plugins.resolver.URLResolver()) {
name = "blindside-repos"
addArtifactPattern "http://blindside.googlecode.com/svn/repository/[artifact](-[revision]).[ext]"
addArtifactPattern "http://blindside.googlecode.com/svn/repository/[organisation]/[artifact](-[revision]).[ext]"
}
add(new org.apache.ivy.plugins.resolver.ChainResolver()) {
name = 'remote'
returnFirst = true
add(new org.apache.ivy.plugins.resolver.URLResolver()) {
name = "maven2-central"
m2compatible = true
addArtifactPattern "http://repo1.maven.org/maven2/[organisation]/[module]/[revision]/[artifact](-[revision]).[ext]"
addArtifactPattern "http://repo1.maven.org/maven2/[organisation]/[artifact]/[revision]/[artifact](-[revision]).[ext]"
}
add(new org.apache.ivy.plugins.resolver.URLResolver()) {
name = "testng_ibiblio_maven2"
m2compatible = true
addArtifactPattern "http://repo1.maven.org/maven2/[organisation]/[module]/[revision]/[artifact](-[revision])-jdk15.[ext]"
addArtifactPattern "http://repo1.maven.org/maven2/[organisation]/[artifact]/[revision]/[artifact](-[revision])-jdk15.[ext]"
}
add(new org.apache.ivy.plugins.resolver.URLResolver()) {
name = "netty-dependency"
m2compatible = true
addArtifactPattern "http://repository.jboss.org/nexus/content/groups/public-jboss/[organisation]/[module]/[revision]/[artifact](-[revision]).[ext]"
addArtifactPattern "http://repo1.maven.org/maven2/[organisation]/[artifact]/[revision]/[artifact](-[revision]).[ext]"
name = "googlecode"
addArtifactPattern "http://red5.googlecode.com/svn/repository/[artifact](-[revision]).[ext]"
addArtifactPattern "http://red5.googlecode.com/svn/repository/[organisation]/[artifact](-[revision]).[ext]"
}
add(new org.apache.ivy.plugins.resolver.URLResolver()) {
name = "spring-bundles"
m2compatible = true
addArtifactPattern "http://repository.springsource.com/maven/bundles/external/[organisation]/[module]/[revision]/[artifact]-[revision].[ext]"
addArtifactPattern "http://repository.springsource.com/maven/bundles/release/[organisation]/[module]/[revision]/[artifact]-[revision].[ext]"
}
}
add(new org.apache.ivy.plugins.resolver.URLResolver()) {
name = "blindside-repos"
addArtifactPattern "http://blindside.googlecode.com/svn/repository/[artifact](-[revision]).[ext]"
addArtifactPattern "http://blindside.googlecode.com/svn/repository/[organisation]/[artifact](-[revision]).[ext]"
}
add(new org.apache.ivy.plugins.resolver.URLResolver()) {
name = "maven2-central"
m2compatible = true
addArtifactPattern "http://repo1.maven.org/maven2/[organisation]/[module]/[revision]/[artifact](-[revision]).[ext]"
addArtifactPattern "http://repo1.maven.org/maven2/[organisation]/[artifact]/[revision]/[artifact](-[revision]).[ext]"
}
add(new org.apache.ivy.plugins.resolver.URLResolver()) {
name = "testng_ibiblio_maven2"
m2compatible = true
addArtifactPattern "http://repo1.maven.org/maven2/[organisation]/[module]/[revision]/[artifact](-[revision])-jdk15.[ext]"
addArtifactPattern "http://repo1.maven.org/maven2/[organisation]/[artifact]/[revision]/[artifact](-[revision])-jdk15.[ext]"
}
add(new org.apache.ivy.plugins.resolver.URLResolver()) {
name = "netty-dependency"
m2compatible = true
addArtifactPattern "http://repository.jboss.org/nexus/content/groups/public-jboss/[organisation]/[module]/[revision]/[artifact](-[revision]).[ext]"
addArtifactPattern "http://repo1.maven.org/maven2/[organisation]/[artifact]/[revision]/[artifact](-[revision]).[ext]"
}
add(new org.apache.ivy.plugins.resolver.URLResolver()) {
name = "spring-bundles"
m2compatible = true
addArtifactPattern "http://repository.springsource.com/maven/bundles/external/[organisation]/[module]/[revision]/[artifact]-[revision].[ext]"
addArtifactPattern "http://repository.springsource.com/maven/bundles/release/[organisation]/[module]/[revision]/[artifact]-[revision].[ext]"
}
}
}
dependencies {
// Servlet
providedCompile 'javax.servlet:servlet-api:2.5@jar'
// Mina
providedCompile 'org.apache.mina:mina-core:2.0.7@jar'
providedCompile 'org.apache.mina:mina-integration-beans:2.0.7@jar'
providedCompile 'org.apache.mina:mina-integration-jmx:2.0.7@jar'
// Spring
providedCompile 'org.springframework:spring-web:3.1.1.RELEASE@jar'
providedCompile 'org.springframework:spring-beans:3.1.1.RELEASE@jar'
providedCompile 'org.springframework:spring-context:3.1.1.RELEASE@jar'
providedCompile 'org.springframework:spring-core:3.1.1.RELEASE@jar'
// Red5
providedCompile 'org/red5:red5:1.0r4643@jar'
// Logging
providedCompile 'ch.qos.logback:logback-core:1.0.9@jar'
providedCompile 'ch.qos.logback:logback-classic:1.0.9@jar'
providedCompile 'org.slf4j:log4j-over-slf4j:1.7.2@jar'
providedCompile 'org.slf4j:jcl-over-slf4j:1.7.2@jar'
providedCompile 'org.slf4j:jul-to-slf4j:1.7.2@jar'
providedCompile 'org.slf4j:slf4j-api:1.7.2@jar'
// Servlet
providedCompile 'javax.servlet:servlet-api:2.5@jar'
// Needed for the JVM shutdown hook but needs to be put into red5/lib dir.
// Otherwise we get exception on aop utils class not found.
providedCompile 'org.springframework:spring-aop:3.0.6.RELEASE@jar'
compile 'aopalliance:aopalliance:1.0@jar'
// Java Concurrency In Practice
providedCompile 'net.jcip:jcip-annotations:1.0@jar'
// Testing
compile 'org.testng:testng:5.8@jar'
compile 'org.easymock:easymock:2.4@jar'
// Mina
providedCompile 'org.apache.mina:mina-core:2.0.7@jar'
providedCompile 'org.apache.mina:mina-integration-beans:2.0.7@jar'
providedCompile 'org.apache.mina:mina-integration-jmx:2.0.7@jar'
// Spring
providedCompile 'org.springframework:spring-web:4.0.3.RELEASE@jar'
providedCompile 'org.springframework:spring-beans:4.0.3.RELEASE@jar'
providedCompile 'org.springframework:spring-context:4.0.3.RELEASE@jar'
providedCompile 'org.springframework:spring-core:4.0.3.RELEASE@jar'
// Red5
providedCompile 'org/red5:red5:1.0.2@jar'
//redis
compile 'redis.clients:jedis:2.0.0'
providedCompile 'commons-pool:commons-pool:1.5.6'
// Logging
providedCompile 'ch.qos.logback:logback-core:1.0.13@jar'
providedCompile 'ch.qos.logback:logback-classic:1.0.13@jar'
providedCompile 'org.slf4j:log4j-over-slf4j:1.7.5@jar'
providedCompile 'org.slf4j:jcl-over-slf4j:1.7.5@jar'
providedCompile 'org.slf4j:jul-to-slf4j:1.7.5@jar'
providedCompile 'org.slf4j:slf4j-api:1.7.5@jar'
compile "redis.clients:jedis:2.1.0"
compile "org.codehaus.jackson:jackson-core-asl:$jacksonVersion"
compile "org.codehaus.jackson:jackson-mapper-asl:$jacksonVersion"
compile "javax.servlet:com.springsource.javax.servlet.jsp.jstl:1.2.0"
compile ("org.springframework.data:spring-data-redis:$springRedisVersion") {
exclude group: 'commons-logging'
}
// Needed for the JVM shutdown hook but needs to be put into red5/lib dir.
// Otherwise we get exception on aop utils class not found.
providedCompile 'org.springframework:spring-aop:4.0.3.RELEASE@jar'
compile 'aopalliance:aopalliance:1.0@jar'
// Testing
compile 'org.testng:testng:5.8@jar'
compile 'org.easymock:easymock:2.4@jar'
//redis
//compile 'redis.clients:jedis:2.0.0'
providedCompile 'commons-pool:commons-pool:1.5.6'
// Libraries needed to run the scala tools
scalaTools 'org.scala-lang:scala-compiler:2.9.2'
scalaTools 'org.scala-lang:scala-library:2.9.2'
// Libraries needed to run the scala tools
scalaTools 'org.scala-lang:scala-compiler:2.9.2'
scalaTools 'org.scala-lang:scala-library:2.9.2'
// Libraries needed for scala api
compile 'org.scala-lang:scala-library:2.9.2'
// Libraries needed for scala api
compile 'org.scala-lang:scala-library:2.9.2'
// workaround for http://issues.gradle.org/browse/GRADLE-1273
//compileScala.classpath = sourceSets.main.compileClasspath + files(sourceSets.main.classesDir)
//compileTestScala.classpath = sourceSets.test.compileClasspath + files(sourceSets.test.classesDir)
// Freeswitch ESL Client
compile 'org/freeswitch:fs-esl-client:0.8.2@jar'
compile 'org.jboss.netty:netty:3.2.1.Final@jar'
compile 'com.google.code.gson:gson:1.7.1'
providedCompile 'org.apache.commons:commons-lang3:3.1'
compile 'commons-lang:commons-lang:2.5'
//compile 'commons-lang:commons-lang:2.5'
}
test {
useTestNG()
useTestNG()
}
war.doLast {
@ -124,13 +133,12 @@ war.doLast {
task deploy() << {
def red5AppsDir = '/usr/share/red5/webapps'
def bbbDir = new File("${red5AppsDir}/$appName")
println "Deleting $bbbDir"
ant.delete(dir: bbbDir)
ant.mkdir(dir: bbbDir)
ant.copy(todir: bbbDir) {
fileset(dir: "$buildDir/$appName")
}
def red5AppsDir = '/usr/share/red5/webapps'
def bbbDir = new File("${red5AppsDir}/$appName")
ant.delete(dir: bbbDir)
ant.mkdir(dir: bbbDir)
ant.copy(todir: bbbDir) {
fileset(dir: "$buildDir/$appName")
}
}

View File

@ -0,0 +1,16 @@
## Dependecies Version
# Logging
log4jVersion = 1.2.17
slf4jVersion = 1.6.6
# Common libraries
springVersion = 3.1.4.RELEASE
springRedisVersion = 1.1.0.RELEASE
jacksonVersion = 1.8.3
# Testing
junitVersion = 4.8.1
mockitoVersion = 1.8.5

View File

@ -22,9 +22,11 @@ import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import org.red5.server.api.Red5; import org.bigbluebutton.conference.service.lock.LockSettings;
import org.red5.server.api.Red5;
import org.bigbluebutton.conference.meeting.messaging.red5.ConnectionInvokerService;
import org.bigbluebutton.conference.service.participants.ParticipantsApplication;
import org.bigbluebutton.conference.service.recorder.RecorderApplication;
import org.bigbluebutton.core.api.IBigBlueButtonInGW;
import org.red5.logging.Red5LoggerFactory;
import org.red5.server.adapter.IApplication;
import org.red5.server.adapter.MultiThreadedApplicationAdapter;
@ -44,6 +46,7 @@ public class BigBlueButtonApplication extends MultiThreadedApplicationAdapter {
private RecorderApplication recorderApplication;
private AbstractApplicationContext appCtx;
private ConnectionInvokerService connInvokerService;
private IBigBlueButtonInGW bbbGW;
private static final String APP = "BBB";
@ -92,15 +95,14 @@ public class BigBlueButtonApplication extends MultiThreadedApplicationAdapter {
appCtx.registerShutdownHook();
super.appStart(app);
connInvokerService.start();
connInvokerService.setAppScope(app);
return true;
}
@Override
public void appStop(IScope app) {
log.debug("***** " + APP + " [ " + " appStop [ " + scope.getName() + "] *********");
connInvokerService.stop();
super.appStop(app);
}
@ -116,7 +118,8 @@ public class BigBlueButtonApplication extends MultiThreadedApplicationAdapter {
public void roomStop(IScope room) {
log.debug("***** " + APP + " [ " + " roomStop [ " + scope.getName() + "] *********");
participantsApplication.destroyRoom(room.getName());
// bbbGW.destroyMeeting(room.getName());
recorderApplication.destroyRecordSession(room.getName());
connInvokerService.removeScope(room.getName());
@ -156,9 +159,7 @@ public class BigBlueButtonApplication extends MultiThreadedApplicationAdapter {
lsMap = new HashMap<String, Boolean>();
}
}
if (record == true) {
recorderApplication.createRecordSession(room);
}
@ -166,11 +167,13 @@ public class BigBlueButtonApplication extends MultiThreadedApplicationAdapter {
BigBlueButtonSession bbbSession = new BigBlueButtonSession(room, internalUserID, username, role,
voiceBridge, record, externalUserID, muted);
connection.setAttribute(Constants.SESSION, bbbSession);
connection.setAttribute("INTERNAL_USER_ID", internalUserID);
String debugInfo = "internalUserID=" + internalUserID + ",username=" + username + ",role=" + role + "," +
",voiceConf=" + voiceBridge + ",room=" + room + ",externalUserid=" + externalUserID;
log.debug("User [{}] connected to room [{}]", debugInfo, room);
participantsApplication.createRoom(room, locked, new LockSettings(lsMap));
bbbGW.initLockSettings(room, locked, lsMap);
connInvokerService.addConnection(bbbSession.getInternalUserID(), connection);
@ -191,13 +194,32 @@ public class BigBlueButtonApplication extends MultiThreadedApplicationAdapter {
BigBlueButtonSession bbbSession = (BigBlueButtonSession) Red5.getConnectionLocal().getAttribute(Constants.SESSION);
log.info("User [" + bbbSession.getUsername() + "] disconnected from room [" + bbbSession.getRoom() +"]");
bbbGW.userLeft(bbbSession.getRoom(), getBbbSession().getInternalUserID());
super.roomDisconnect(conn);
}
public String getMyUserId() {
public void validateToken(String token) {
BigBlueButtonSession bbbSession = (BigBlueButtonSession) Red5.getConnectionLocal().getAttribute(Constants.SESSION);
assert bbbSession != null;
return bbbSession.getInternalUserID();
String userId = bbbSession.getInternalUserID();
String meetingId = Red5.getConnectionLocal().getScope().getName();
bbbGW.validateAuthToken(meetingId, userId, token, meetingId + "/" + userId);
}
public void joinMeeting(String userId) {
BigBlueButtonSession bbbSession = getBbbSession();
if (bbbSession != null) {
String userid = bbbSession.getInternalUserID();
String username = bbbSession.getUsername();
String role = bbbSession.getRole();
String meetingId = bbbSession.getRoom();
log.debug(APP + ":joinMeeting - [" + meetingId + "] [" + userid + ", " + username + ", " + role + "]");
bbbGW.userJoin(meetingId, userid);
}
}
public void setParticipantsApplication(ParticipantsApplication a) {
@ -223,13 +245,16 @@ public class BigBlueButtonApplication extends MultiThreadedApplicationAdapter {
this.connInvokerService = connInvokerService;
}
public void setBigBlueButtonInGW(IBigBlueButtonInGW bbbGW) {
this.bbbGW = bbbGW;
}
private class ShutdownHookListener implements ApplicationListener<ApplicationEvent> {
@Override
public void onApplicationEvent(ApplicationEvent event) {
if (event instanceof org.springframework.context.event.ContextStoppedEvent) {
log.info("Received shutdown event. Red5 is shutting down. Destroying all rooms.");
participantsApplication.destroyAllRooms();
}
}

View File

@ -1,52 +0,0 @@
/**
* BigBlueButton open source conferencing system - http://www.bigbluebutton.org/
*
* Copyright (c) 2012 BigBlueButton Inc. and by respective authors (see below).
*
* This program is free software; you can redistribute it and/or modify it under the
* terms of the GNU Lesser General Public License as published by the Free Software
* Foundation; either version 3.0 of the License, or (at your option) any later
* version.
*
* BigBlueButton is distributed in the hope that it will be useful, but WITHOUT ANY
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
* PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License along
* with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
*
*/
package org.bigbluebutton.conference;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import org.red5.server.api.Red5;
public class BigBlueButtonService {
private ConnectionInvokerService connInvokerService;
public void sendMessage(HashMap<String, Object> params) {
Map<String, Object> messageToSend = new HashMap<String, Object>();
for (Iterator<String> it = params.keySet().iterator(); it.hasNext();) {
String key = it.next();
messageToSend.put(key, params.get(key));
}
ClientMessage m = new ClientMessage(ClientMessage.BROADCAST, getMeetingId(), (String) params.get("messageID"), messageToSend);
connInvokerService.sendMessage(m);
}
private String getMeetingId(){
return Red5.getConnectionLocal().getScope().getName();
}
public void setConnInvokerService(ConnectionInvokerService connInvokerService) {
this.connInvokerService = connInvokerService;
}
}

View File

@ -1,129 +0,0 @@
/**
* BigBlueButton open source conferencing system - http://www.bigbluebutton.org/
*
* Copyright (c) 2012 BigBlueButton Inc. and by respective authors (see below).
*
* This program is free software; you can redistribute it and/or modify it under the
* terms of the GNU Lesser General Public License as published by the Free Software
* Foundation; either version 3.0 of the License, or (at your option) any later
* version.
*
* BigBlueButton is distributed in the hope that it will be useful, but WITHOUT ANY
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
* PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License along
* with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
*
*/
package org.bigbluebutton.conference;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
import java.util.concurrent.LinkedBlockingQueue;
import org.red5.server.api.IConnection;
import org.red5.server.api.scope.IScope;
import org.red5.server.api.service.ServiceUtils;
public class ConnectionInvokerService {
private static final int NTHREADS = 1;
private static final Executor exec = Executors.newFixedThreadPool(NTHREADS);
private BlockingQueue<ClientMessage> messages;
private ConcurrentHashMap<String, IConnection> connections;
private ConcurrentHashMap<String, IScope> scopes;
private volatile boolean sendMessages = false;
public ConnectionInvokerService() {
messages = new LinkedBlockingQueue<ClientMessage>();
connections = new ConcurrentHashMap<String, IConnection>();
scopes = new ConcurrentHashMap<String, IScope>();
}
public void addConnection(String id, IConnection conn) {
if (connections == null) {
System.out.println("Connections is null!!!!");
return;
}
if (id == null) {
System.out.println("CONN ID IS NULL!!!");
}
if (conn == null) {
System.out.println("CONN IS NULL");
}
connections.putIfAbsent(id, conn);
}
public void start() {
sendMessages = true;
Runnable sender = new Runnable() {
public void run() {
while (sendMessages) {
ClientMessage message;
try {
message = messages.take();
sendMessageToClient(message);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
};
exec.execute(sender);
}
public void stop() {
sendMessages = false;
}
public void removeConnection(String id) {
connections.remove(id);
}
public void addScope(String id, IScope scope) {
scopes.putIfAbsent(id, scope);
}
public void removeScope(String id) {
scopes.remove(id);
}
public void sendMessage(final ClientMessage message) {
messages.offer(message);
}
private void sendMessageToClient(ClientMessage message) {
if (message.getType().equals(ClientMessage.BROADCAST)) {
IScope scope = scopes.get(message.getDest());
if (scope != null) {
List<Object> params = new ArrayList<Object>();
params.add(message.getMessageName());
params.add(message.getMessage());
ServiceUtils.invokeOnAllScopeConnections(scope, "onMessageFromServer", params.toArray(), null);
}
} else if (message.getType().equals(ClientMessage.DIRECT)) {
IConnection conn = connections.get(message.getDest());
if (conn != null) {
if (conn.isConnected()) {
List<Object> params = new ArrayList<Object>();
params.add(message.getMessageName());
params.add(message.getMessage());
ServiceUtils.invokeOnConnection(conn, "onMessageFromServer", params.toArray());
}
}
}
}
}

View File

@ -1,35 +0,0 @@
/**
* BigBlueButton open source conferencing system - http://www.bigbluebutton.org/
*
* Copyright (c) 2012 BigBlueButton Inc. and by respective authors (see below).
*
* This program is free software; you can redistribute it and/or modify it under the
* terms of the GNU Lesser General Public License as published by the Free Software
* Foundation; either version 3.0 of the License, or (at your option) any later
* version.
*
* BigBlueButton is distributed in the hope that it will be useful, but WITHOUT ANY
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
* PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License along
* with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
*
*/
package org.bigbluebutton.conference;
import java.util.ArrayList;
import java.util.Map;
public interface IRoomListener {
public String getName();
public void lockSettingsChange(Map<String, Boolean> lockSettings);
public void participantStatusChange(User p, String status, Object value);
public void participantJoined(User participant);
public void participantLeft(User participant);
public void assignPresenter(ArrayList<String> presenter);
public void endAndKickAll();
public void recordingStatusChange(User p, Boolean recording);
}

View File

@ -1,123 +0,0 @@
/**
* BigBlueButton open source conferencing system - http://www.bigbluebutton.org/
*
* Copyright (c) 2012 BigBlueButton Inc. and by respective authors (see below).
*
* This program is free software; you can redistribute it and/or modify it under the
* terms of the GNU Lesser General Public License as published by the Free Software
* Foundation; either version 3.0 of the License, or (at your option) any later
* version.
*
* BigBlueButton is distributed in the hope that it will be useful, but WITHOUT ANY
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
* PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License along
* with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
*
*/
package org.bigbluebutton.conference;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
import org.bigbluebutton.conference.service.messaging.MessagingConstants;
import org.bigbluebutton.conference.service.messaging.MessagingService;
import org.red5.logging.Red5LoggerFactory;
import org.slf4j.Logger;
import com.google.gson.Gson;
public class ParticipantUpdatingRoomListener implements IRoomListener{
private static Logger log = Red5LoggerFactory.getLogger(ParticipantUpdatingRoomListener.class, "bigbluebutton");
MessagingService messagingService;
private Room room;
public ParticipantUpdatingRoomListener(Room room, MessagingService messagingService) {
this.room = room;
this.messagingService=messagingService;
}
public String getName() {
return "PARTICIPANT:UPDATE:ROOM";
}
public void participantStatusChange(User p, String status, Object value){
if (messagingService != null) {
HashMap<String,String> map= new HashMap<String, String>();
map.put("meetingId", this.room.getName());
map.put("messageId", MessagingConstants.USER_STATUS_CHANGE_EVENT);
map.put("internalUserId", p.getInternalUserID());
map.put("status", status);
map.put("value", value.toString());
Gson gson= new Gson();
messagingService.send(MessagingConstants.PARTICIPANTS_CHANNEL, gson.toJson(map));
log.debug("Publishing a status change in: " + this.room.getName());
}
}
public void participantJoined(User p) {
if (messagingService != null) {
HashMap<String,String> map= new HashMap<String, String>();
map.put("meetingId", this.room.getName());
map.put("messageId", MessagingConstants.USER_JOINED_EVENT);
map.put("internalUserId", p.getInternalUserID());
map.put("externalUserId", p.getExternalUserID());
map.put("fullname", p.getName());
map.put("role", p.getRole());
Gson gson= new Gson();
messagingService.send(MessagingConstants.PARTICIPANTS_CHANNEL, gson.toJson(map));
log.debug("Publishing message participant joined in " + this.room.getName());
}
}
public void participantLeft(User p) {
if (messagingService != null) {
HashMap<String,String> map= new HashMap<String, String>();
map.put("meetingId", this.room.getName());
map.put("messageId", MessagingConstants.USER_LEFT_EVENT);
map.put("internalUserId", p.getInternalUserID());
Gson gson= new Gson();
messagingService.send(MessagingConstants.PARTICIPANTS_CHANNEL, gson.toJson(map));
log.debug("Publishing message participant left in " + this.room.getName());
}
}
public void assignPresenter(ArrayList<String> presenter) {
// Do nothing.
}
public void endAndKickAll() {
// no-op
}
@Override
public void lockSettingsChange(Map<String, Boolean> lockSettings) {
// Do nothing
}
public void recordingStatusChange(User p, Boolean recording){
if (messagingService != null) {
HashMap<String,String> map= new HashMap<String, String>();
map.put("meetingId", this.room.getName());
map.put("messageId", MessagingConstants.RECORD_STATUS_EVENT);
map.put("internalUserId", p.getInternalUserID());
map.put("value", recording.toString());
Gson gson= new Gson();
messagingService.send(MessagingConstants.PARTICIPANTS_CHANNEL, gson.toJson(map));
log.debug("Publishing a recording status change in: " + this.room.getName());
}
}
}

View File

@ -1,240 +0,0 @@
/**
* BigBlueButton open source conferencing system - http://www.bigbluebutton.org/
*
* Copyright (c) 2012 BigBlueButton Inc. and by respective authors (see below).
*
* This program is free software; you can redistribute it and/or modify it under the
* terms of the GNU Lesser General Public License as published by the Free Software
* Foundation; either version 3.0 of the License, or (at your option) any later
* version.
*
* BigBlueButton is distributed in the hope that it will be useful, but WITHOUT ANY
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
* PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License along
* with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
*
*/
package org.bigbluebutton.conference;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import net.jcip.annotations.ThreadSafe;
import org.bigbluebutton.conference.service.lock.LockSettings;
import org.red5.logging.Red5LoggerFactory;
import org.slf4j.Logger;
/**
* Contains information about a Room and it's Participants.
* Encapsulates Participants and RoomListeners.
*/
@ThreadSafe
public class Room implements Serializable {
private static Logger log = Red5LoggerFactory.getLogger( Room.class, "bigbluebutton" );
ArrayList<String> currentPresenter = null;
private String name;
private Map <String, User> participants;
private Boolean locked;
private LockSettings lockSettings = null;
private Boolean recording = false;
// these should stay transient so they're not serialized in ActiveMQ messages:
//private transient Map <Long, Participant> unmodifiableMap;
private transient final Map<String, IRoomListener> listeners;
public Room(String name, Boolean locked, LockSettings lockSettings) {
this.name = name;
this.locked = locked;
this.lockSettings = lockSettings;
participants = new ConcurrentHashMap<String, User>();
//unmodifiableMap = Collections.unmodifiableMap(participants);
listeners = new ConcurrentHashMap<String, IRoomListener>();
}
public String getName() {
return name;
}
public void addRoomListener(IRoomListener listener) {
if (! listeners.containsKey(listener.getName())) {
log.debug("adding room listener");
listeners.put(listener.getName(), listener);
}
}
public void removeRoomListener(IRoomListener listener) {
log.debug("removing room listener");
listeners.remove(listener);
}
public void addParticipant(User participant) {
synchronized (this) {
log.debug("adding participant " + participant.getInternalUserID());
participants.put(participant.getInternalUserID(), participant);
// unmodifiableMap = Collections.unmodifiableMap(participants)
}
log.debug("Informing roomlisteners " + listeners.size());
for (Iterator it = listeners.values().iterator(); it.hasNext();) {
IRoomListener listener = (IRoomListener) it.next();
log.debug("calling participantJoined on listener " + listener.getName());
listener.participantJoined(participant);
}
}
public void removeParticipant(String userid) {
boolean present = false;
User p = null;
synchronized (this) {
present = participants.containsKey(userid);
if (present) {
log.debug("removing participant");
p = participants.remove(userid);
}
}
if (present) {
for (Iterator it = listeners.values().iterator(); it.hasNext();) {
IRoomListener listener = (IRoomListener) it.next();
log.debug("calling participantLeft on listener " + listener.getName());
listener.participantLeft(p);
}
}
// When the last participant leaves the conference, if it's still recording
// we will finish the recording. The problem it will avoid is that when the last
// participant leaves the conference, the Room is cleaned up and the recording
// flag is lost. If a user joins after that, but before the meeting get cleaned up
// by the server, there's no way to detect that the previous part of the session
// was being recorded.
if (participants.isEmpty() && recording) {
changeRecordingStatus(p, false);
}
}
public void changeParticipantStatus(String userid, String status, Object value) {
boolean present = false;
User p = null;
synchronized (this) {
present = participants.containsKey(userid);
if (present) {
log.debug("change participant status");
p = participants.get(userid);
p.setStatus(status, value);
//participants.put(userid, p);
//unmodifiableMap = Collections.unmodifiableMap(participants);
}
}
if (present) {
for (Iterator it = listeners.values().iterator(); it.hasNext();) {
IRoomListener listener = (IRoomListener) it.next();
log.debug("calling participantStatusChange on listener " + listener.getName());
listener.participantStatusChange(p, status, value);
}
}
}
public void endAndKickAll() {
for (Iterator it = listeners.values().iterator(); it.hasNext();) {
IRoomListener listener = (IRoomListener) it.next();
log.debug("calling endAndKickAll on listener " + listener.getName());
listener.endAndKickAll();
}
}
public Map<String, User> getParticipants() {
return participants;//unmodifiableMap;
}
public Collection<User> getParticipantCollection() {
return participants.values();
}
public int getNumberOfParticipants() {
log.debug("Returning number of participants: " + participants.size());
return participants.size();
}
public int getNumberOfModerators() {
int sum = 0;
for (Iterator<User> it = participants.values().iterator(); it.hasNext(); ) {
User part = it.next();
if (part.isModerator()) {
sum++;
}
}
log.debug("Returning number of moderators: " + sum);
return sum;
}
public ArrayList<String> getCurrentPresenter() {
return currentPresenter;
}
public void assignPresenter(ArrayList<String> presenter){
currentPresenter = presenter;
for (Iterator iter = listeners.values().iterator(); iter.hasNext();) {
log.debug("calling on listener");
IRoomListener listener = (IRoomListener) iter.next();
log.debug("calling sendUpdateMessage on listener " + listener.getName());
listener.assignPresenter(presenter);
}
}
public void setLocked(Boolean locked) {
this.locked = locked;
}
public boolean isLocked() {
return locked;
}
public LockSettings getLockSettings() {
return lockSettings;
}
public void setLockSettings(LockSettings lockSettings) {
this.lockSettings = lockSettings;
for (Iterator it = listeners.values().iterator(); it.hasNext();) {
IRoomListener listener = (IRoomListener) it.next();
log.debug("calling setLockSettings on listener " + listener.getName());
listener.lockSettingsChange(lockSettings.toMap());
}
}
public void changeRecordingStatus(String userid, Boolean recording) {
boolean present = false;
User p = null;
synchronized (this) {
present = participants.containsKey(userid);
if (present) {
p = participants.get(userid);
}
}
if (present && recording != this.recording) {
changeRecordingStatus(p, recording);
}
}
private void changeRecordingStatus(User p, Boolean recording) {
log.debug("Changed recording status to " + recording);
this.recording = recording;
for (Iterator it = listeners.values().iterator(); it.hasNext();) {
IRoomListener listener = (IRoomListener) it.next();
log.debug("calling recordingStatusChange on listener " + listener.getName());
listener.recordingStatusChange(p, recording);
}
}
public Boolean getRecordingStatus() {
return recording;
}
}

View File

@ -1,85 +0,0 @@
/**
* BigBlueButton open source conferencing system - http://www.bigbluebutton.org/
*
* Copyright (c) 2012 BigBlueButton Inc. and by respective authors (see below).
*
* This program is free software; you can redistribute it and/or modify it under the
* terms of the GNU Lesser General Public License as published by the Free Software
* Foundation; either version 3.0 of the License, or (at your option) any later
* version.
*
* BigBlueButton is distributed in the hope that it will be useful, but WITHOUT ANY
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
* PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License along
* with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
*
*/
package org.bigbluebutton.conference;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import org.red5.server.api.so.ISharedObject;
public class RoomListener implements IRoomListener{
private ISharedObject so;
public RoomListener(ISharedObject so) {
this.so = so;
}
public String getName() {
return "TEMPNAME";
}
@SuppressWarnings("unchecked")
public void participantStatusChange(User p, String status, Object value){
List list = new ArrayList();
list.add(p.getInternalUserID());
list.add(status);
list.add(value);
so.sendMessage("participantStatusChange", list);
}
@SuppressWarnings("unchecked")
public void participantJoined(User p) {
List args = new ArrayList();
args.add(p.toMap());
so.sendMessage("participantJoined", args);
}
@SuppressWarnings("unchecked")
public void participantLeft(User p) {
List args = new ArrayList();
args.add(p.getInternalUserID());
so.sendMessage("participantLeft", args);
}
public void assignPresenter(ArrayList<String> presenter) {
so.sendMessage("assignPresenterCallback", presenter);
}
public void endAndKickAll() {
// no-op
}
@Override
public void lockSettingsChange(Map<String, Boolean> lockSettings) {
List list = new ArrayList();
list.add(lockSettings);
so.sendMessage("lockSettingsChange", list);
}
@SuppressWarnings("unchecked")
public void recordingStatusChange(User p, Boolean recording){
List list = new ArrayList();
list.add(p.getInternalUserID());
list.add(recording);
so.sendMessage("recordingStatusChange", list);
}
}

View File

@ -1,242 +0,0 @@
/**
* BigBlueButton open source conferencing system - http://www.bigbluebutton.org/
*
* Copyright (c) 2012 BigBlueButton Inc. and by respective authors (see below).
*
* This program is free software; you can redistribute it and/or modify it under the
* terms of the GNU Lesser General Public License as published by the Free Software
* Foundation; either version 3.0 of the License, or (at your option) any later
* version.
*
* BigBlueButton is distributed in the hope that it will be useful, but WITHOUT ANY
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
* PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License along
* with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
*
*/
package org.bigbluebutton.conference;
import org.slf4j.Logger;
import org.bigbluebutton.conference.service.messaging.MessageListener;
import org.bigbluebutton.conference.service.messaging.MessagingConstants;
import org.bigbluebutton.conference.service.messaging.MessagingService;
import org.bigbluebutton.conference.service.presentation.ConversionUpdatesMessageListener;
import org.red5.logging.Red5LoggerFactory;
import com.google.gson.Gson;
import net.jcip.annotations.ThreadSafe;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
/**
* This encapsulates access to Room and Participant. This class must be threadsafe.
*/
@ThreadSafe
public class RoomsManager {
private static Logger log = Red5LoggerFactory.getLogger(RoomsManager.class, "bigbluebutton");
private final Map <String, Room> rooms;
MessagingService messagingService;
ConversionUpdatesMessageListener conversionUpdatesMessageListener;
public RoomsManager() {
rooms = new ConcurrentHashMap<String, Room>();
}
public void addRoom(Room room) {
log.debug("Adding room " + room.getName());
room.addRoomListener(new ParticipantUpdatingRoomListener(room,messagingService));
if (checkPublisher()) {
HashMap<String,String> map = new HashMap<String,String>();
map.put("meetingId", room.getName());
map.put("messageId", MessagingConstants.MEETING_STARTED_EVENT);
Gson gson = new Gson();
messagingService.send(MessagingConstants.SYSTEM_CHANNEL, gson.toJson(map));
log.debug("Notified event listener of conference start");
}
rooms.put(room.getName(), room);
}
public void removeRoom(String name) {
log.debug("Remove room " + name);
Room room = rooms.remove(name);
if (checkPublisher() && room != null) {
room.endAndKickAll();
HashMap<String,String> map = new HashMap<String,String>();
map.put("meetingId", room.getName());
map.put("messageId", MessagingConstants.MEETING_ENDED_EVENT);
Gson gson = new Gson();
messagingService.send(MessagingConstants.SYSTEM_CHANNEL, gson.toJson(map));
log.debug("Notified event listener of conference end");
}
}
public void destroyAllRooms() {
for (Map.Entry<String,Room> entry : rooms.entrySet()) {
Room room = entry.getValue();
room.endAndKickAll();
}
}
private boolean checkPublisher() {
return messagingService != null;
}
public boolean hasRoom(String name) {
return rooms.containsKey(name);
}
public int numberOfRooms() {
return rooms.size();
}
/**
* Keeping getRoom private so that all access to Room goes through here.
*/
//TODO: this method becomes public for ParticipantsApplication, ask if it's right?
public Room getRoom(String name) {
log.debug("Get room " + name);
return rooms.get(name);
}
public Map<String, User> getParticipants(String roomName) {
Room r = getRoom(roomName);
if (r != null) {
return r.getParticipants();
}
log.warn("Getting participants from a non-existing room " + roomName);
return null;
}
public void addRoomListener(String roomName, IRoomListener listener) {
Room r = getRoom(roomName);
if (r != null) {
r.addRoomListener(listener);
return;
}
log.warn("Adding listener to a non-existing room " + roomName);
}
// TODO: this must be broken, right? where is roomName? (JRT: 9/25/2009)
// public void removeRoomListener(IRoomListener listener) {
//
// Room r = getRoom(roomName);
// if (r != null) {
// r.removeRoomListener(listener)
// return
// }
// log.warn("Removing listener from a non-existing room ${roomName}")
// }
public void addParticipant(String roomName, User participant) {
log.debug("Add participant " + participant.getName());
Room r = getRoom(roomName);
if (r != null) {
r.addParticipant(participant);
return;
}
log.warn("Adding participant to a non-existing room " + roomName);
}
public void removeParticipant(String roomName, String userid) {
log.debug("Remove participant " + userid + " from " + roomName);
Room r = getRoom(roomName);
if (r != null) {
if (checkPublisher()) {
//conferenceEventListener.participantsUpdated(r);
//missing method()?
}
r.removeParticipant(userid);
return;
}
log.warn("Removing listener from a non-existing room " + roomName);
}
public void changeParticipantStatus(String roomName, String userid, String status, Object value) {
log.debug("Change participant status " + userid + " - " + status + " [" + value + "]");
Room r = getRoom(roomName);
if (r != null) {
r.changeParticipantStatus(userid, status, value);
return;
}
log.warn("Changing participant status on a non-existing room " + roomName);
}
public void setMessagingService(MessagingService messagingService) {
this.messagingService = messagingService;
this.messagingService.addListener(new RoomsManagerListener());
this.messagingService.start();
}
public ArrayList<String> getCurrentPresenter( String room){
Room r = getRoom(room);
if (r != null) {
return r.getCurrentPresenter();
}
log.warn("Getting presenter from a non-existing room " + room);
return null;
}
public void assignPresenter(String room, ArrayList presenter){
Room r = getRoom(room);
if (r != null) {
r.assignPresenter(presenter);
return;
}
log.warn("Assigning presenter to a non-existing room " + room);
}
public void setConversionUpdatesMessageListener(ConversionUpdatesMessageListener conversionUpdatesMessageListener) {
this.conversionUpdatesMessageListener = conversionUpdatesMessageListener;
}
private class RoomsManagerListener implements MessageListener{
@Override
public void endMeetingRequest(String meetingId) {
log.debug("End meeting request for room: " + meetingId);
Room room = getRoom(meetingId); // must do this because the room coming in is serialized (no transient values are present)
if (room != null)
room.endAndKickAll();
else
log.debug("Could not find room " + meetingId);
}
@Override
public void presentationUpdates(HashMap<String, String> map) {
conversionUpdatesMessageListener.handleReceivedMessage(map);
}
}
public void changeRecordingStatus(String roomName, String userid, Boolean recording) {
log.debug("Change recording status " + userid + " [" + recording + "]");
Room r = getRoom(roomName);
if (r != null) {
r.changeRecordingStatus(userid, recording);
return;
}
log.warn("Changing recording status on a non-existing room " + roomName);
}
public Boolean getRecordingStatus(String roomName) {
Room r = getRoom(roomName);
if (r != null) {
return r.getRecordingStatus();
}
log.warn("Getting recording status from a non-existing room " + roomName);
return null;
}
}

View File

@ -1,124 +0,0 @@
/**
* BigBlueButton open source conferencing system - http://www.bigbluebutton.org/
*
* Copyright (c) 2012 BigBlueButton Inc. and by respective authors (see below).
*
* This program is free software; you can redistribute it and/or modify it under the
* terms of the GNU Lesser General Public License as published by the Free Software
* Foundation; either version 3.0 of the License, or (at your option) any later
* version.
*
* BigBlueButton is distributed in the hope that it will be useful, but WITHOUT ANY
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
* PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License along
* with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
*
*/
package org.bigbluebutton.conference;
import net.jcip.annotations.ThreadSafe;
import java.util.concurrent.ConcurrentHashMap;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.io.Serializable;
/**
* Contains information for a Participant. Encapsulates status and the
* only way to change/add status is through setStatus;
*/
@ThreadSafe
public class User implements Serializable {
private String internalUserID;
private String name;
private String role = "VIEWER";
private String externalUserID;
private final Map status;
private Map<String, Object> unmodifiableStatus;
public User(String internalUserID, String name, String role, String externalUserID, Map<String, Object> status, Boolean locked) {
this.internalUserID = internalUserID;
this.name = name;
this.role = role;
this.externalUserID = externalUserID;
this.status = new ConcurrentHashMap<String, Object>(status);
unmodifiableStatus = Collections.unmodifiableMap(status);
setStatus("locked", locked);
}
public boolean isModerator() {
return "MODERATOR".equals(role);
}
public String getName() {
return name;
}
public String getInternalUserID() {
return internalUserID;
}
public String getRole() {
return role;
}
public String getExternalUserID() {
return externalUserID;
}
/**
* Returns that status for this participant. However, the status cannot
* be modified. To do that, setStatus(...) must be used.
*/
public Map getStatus() {
return unmodifiableStatus;
}
public void setStatus(String statusName, Object value) {
// Should we sychronize?
synchronized (this) {
status.put(statusName, value);
/**
* Update unmodifiableStatus as it does not get synched with status.
* Not sure it it should auto-syc, so just sync it.
* Not sure if this is the right way to do it (ralam 2/26/2009).
*/
unmodifiableStatus = Collections.unmodifiableMap(status);
}
}
public void removeStatus(String statusName) {
// Should we sychronize?
synchronized (this) {
status.remove(statusName);
/**
* Update unmodifiableStatus as it does not get synched with status.
* Not sure it it should auto-syc, so just sync it.
* Not sure if this is the right way to do it (ralam 2/26/2009).
*/
unmodifiableStatus = Collections.unmodifiableMap(status);
}
}
public Map toMap() {
Map m = new HashMap();
m.put("userid", internalUserID);
m.put("externUserID", externalUserID);
m.put("name", name);
m.put("role", role);
/**
* Create a copy of the status instead of returning the
* unmodifiableMap. This way callers can still manipulate it
* for their own purpose but our copy still remains unmodified.
*/
m.put("status", new HashMap(unmodifiableStatus));
return m;
}
public Boolean isLocked() {
return ((Boolean) getStatus().get("locked") );
}
}

View File

@ -16,32 +16,24 @@
* with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
*
*/
package org.bigbluebutton.conference;
package org.bigbluebutton.conference.meeting.messaging.red5;
import java.util.Map;
public class ClientMessage {
public static final String BROADCAST = "broadcast";
public static final String DIRECT = "direct";
private String type;
private String dest;
public class BroadcastClientMessage implements ClientMessage {
private String meetingID;
private Map<String, Object> message;
private String messageName;
public ClientMessage(String type, String dest, String messageName, Map<String, Object> message) {
this.type = type;
this.dest = dest;
public BroadcastClientMessage(String meetingID, String messageName, Map<String, Object> message) {
this.meetingID = meetingID;
this.message = message;
this.messageName = messageName;
}
public String getType() {
return type;
}
public String getDest() {
return dest;
public String getMeetingID() {
return meetingID;
}
public String getMessageName() {

View File

@ -16,8 +16,9 @@
* with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
*
*/
package org.bigbluebutton.conference.service.whiteboard.shapes;
package org.bigbluebutton.conference.meeting.messaging.red5;
public class Rectangle {
public interface ClientMessage {
}

View File

@ -0,0 +1,234 @@
/**
* BigBlueButton open source conferencing system - http://www.bigbluebutton.org/
*
* Copyright (c) 2012 BigBlueButton Inc. and by respective authors (see below).
*
* This program is free software; you can redistribute it and/or modify it under the
* terms of the GNU Lesser General Public License as published by the Free Software
* Foundation; either version 3.0 of the License, or (at your option) any later
* version.
*
* BigBlueButton is distributed in the hope that it will be useful, but WITHOUT ANY
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
* PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License along
* with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
*
*/
package org.bigbluebutton.conference.meeting.messaging.red5;
import java.util.Set;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
import java.util.concurrent.LinkedBlockingQueue;
import org.red5.logging.Red5LoggerFactory;
import org.red5.server.api.IConnection;
import org.red5.server.api.scope.IScope;
import org.red5.server.api.scope.ScopeType;
import org.red5.server.api.service.ServiceUtils;
import org.red5.server.api.so.ISharedObject;
import org.red5.server.api.so.ISharedObjectService;
import org.red5.server.so.SharedObjectService;
import org.red5.server.util.ScopeUtils;
import org.slf4j.Logger;
public class ConnectionInvokerService {
private static Logger log = Red5LoggerFactory.getLogger(ConnectionInvokerService.class, "bigbluebutton");
private static final int NTHREADS = 1;
private static final Executor exec = Executors.newFixedThreadPool(NTHREADS);
private BlockingQueue<ClientMessage> messages;
private ConcurrentHashMap<String, IConnection> connections;
private ConcurrentHashMap<String, IScope> scopes;
private volatile boolean sendMessages = false;
private IScope bbbAppScope;
public ConnectionInvokerService() {
messages = new LinkedBlockingQueue<ClientMessage>();
connections = new ConcurrentHashMap<String, IConnection>();
scopes = new ConcurrentHashMap<String, IScope>();
}
public void setAppScope(IScope scope) {
bbbAppScope = scope;
}
public void addConnection(String id, IConnection conn) {
if (connections == null) {
System.out.println("Connections is null!!!!");
return;
}
if (id == null) {
System.out.println("CONN ID IS NULL!!!");
}
if (conn == null) {
System.out.println("CONN IS NULL");
}
connections.putIfAbsent(id, conn);
}
public void start() {
sendMessages = true;
Runnable sender = new Runnable() {
public void run() {
while (sendMessages) {
ClientMessage message;
try {
message = messages.take();
sendMessageToClient(message);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
};
exec.execute(sender);
}
public void stop() {
sendMessages = false;
}
public void removeConnection(String id) {
connections.remove(id);
}
public void addScope(String id, IScope scope) {
scopes.putIfAbsent(id, scope);
}
public void removeScope(String id) {
scopes.remove(id);
}
public void sendMessage(final ClientMessage message) {
messages.offer(message);
}
private void sendMessageToClient(ClientMessage message) {
if (message instanceof BroadcastClientMessage) {
sendBroadcastMessage((BroadcastClientMessage) message);
} else if (message instanceof DirectClientMessage) {
sendDirectMessage((DirectClientMessage) message);
} else if (message instanceof SharedObjectClientMessage) {
sendSharedObjectMessage((SharedObjectClientMessage) message);
} else if (message instanceof DisconnectClientMessage) {
handlDisconnectClientMessage((DisconnectClientMessage) message);
} else if (message instanceof DisconnectAllClientsMessage) {
handleDisconnectAllClientsMessage((DisconnectAllClientsMessage) message);
}
}
private void handleDisconnectAllClientsMessage(DisconnectAllClientsMessage msg) {
IScope meetingScope = getScope(msg.getMeetingId());
if (meetingScope != null) {
Set<IConnection> conns = meetingScope.getClientConnections();
for (IConnection conn : conns) {
if (conn.isConnected()) {
String connId = (String) conn.getAttribute("INTERNAL_USER_ID");
log.info("Disconnecting client=[{}] from meeting=[{}]", connId, msg.getMeetingId());
conn.close();
}
}
}
}
private void handlDisconnectClientMessage(DisconnectClientMessage msg) {
IScope meetingScope = getScope(msg.getMeetingId());
if (meetingScope != null) {
IConnection conn = getConnection(meetingScope, msg.getUserId());
if (conn != null) {
if (conn.isConnected()) {
log.info("Disconnecting user=[{}] from meeting=[{}]", msg.getUserId(), msg.getMeetingId());
conn.close();
}
}
}
}
private void sendSharedObjectMessage(SharedObjectClientMessage msg) {
System.out.println("*********** Request to send [" + msg.getMessageName() + "] using shared object.");
IScope meetingScope = getScope(msg.getMeetingID());
if (meetingScope != null) {
if (meetingScope.hasChildScope(ScopeType.SHARED_OBJECT, msg.getSharedObjectName())) {
ISharedObject so = getSharedObject(meetingScope, msg.getSharedObjectName());
if (so != null) {
System.out.println("*********** Sending [" + msg.getMessageName() + "] using shared object.");
so.sendMessage(msg.getMessageName(), msg.getMessage());
} else {
System.out.println("**** Cannot get SO for [" + msg.getSharedObjectName() + "]");
}
} else {
System.out.println("**** No SO scope for [" + msg.getSharedObjectName() + "]");
}
} else {
System.out.println("**** No Meeting scope for [" + msg.getMeetingID() + "]");
}
}
private void sendDirectMessage(DirectClientMessage msg) {
IScope meetingScope = getScope(msg.getMeetingID());
if (meetingScope != null) {
IConnection conn = getConnection(meetingScope, msg.getUserID());
if (conn != null) {
if (conn.isConnected()) {
List<Object> params = new ArrayList<Object>();
params.add(msg.getMessageName());
params.add(msg.getMessage());
ServiceUtils.invokeOnConnection(conn, "onMessageFromServer", params.toArray());
}
}
}
}
private void sendBroadcastMessage(BroadcastClientMessage msg) {
IScope meetingScope = getScope(msg.getMeetingID());
if (meetingScope != null) {
List<Object> params = new ArrayList<Object>();
params.add(msg.getMessageName());
params.add(msg.getMessage());
ServiceUtils.invokeOnAllScopeConnections(meetingScope, "onMessageFromServer", params.toArray(), null);
}
}
private IConnection getConnection(IScope scope, String userID) {
Set<IConnection> conns = scope.getClientConnections();
for (IConnection conn : conns) {
String connID = (String) conn.getAttribute("INTERNAL_USER_ID");
if (connID != null && connID.equals(userID)) {
return conn;
}
}
return null;
}
public IScope getScope(String meetingID) {
if (bbbAppScope != null) {
return bbbAppScope.getContext().resolveScope("bigbluebutton/" + meetingID);
} else {
log.error("BigBlueButton Scope not initialized. No messages are going to the Flash client!");
}
return null;
}
private ISharedObject getSharedObject(IScope scope, String name) {
ISharedObjectService service = (ISharedObjectService) ScopeUtils.getScopeService(scope, ISharedObjectService.class, SharedObjectService.class, false);
return service.getSharedObject(scope, name);
}
}

View File

@ -0,0 +1,61 @@
/**
* BigBlueButton open source conferencing system - http://www.bigbluebutton.org/
*
* Copyright (c) 2012 BigBlueButton Inc. and by respective authors (see below).
*
* This program is free software; you can redistribute it and/or modify it under the
* terms of the GNU Lesser General Public License as published by the Free Software
* Foundation; either version 3.0 of the License, or (at your option) any later
* version.
*
* BigBlueButton is distributed in the hope that it will be useful, but WITHOUT ANY
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
* PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License along
* with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
*
*/
package org.bigbluebutton.conference.meeting.messaging.red5;
import java.util.Map;
public class DirectClientMessage implements ClientMessage {
private String meetingID;
private String userID;
private Map<String, Object> message;
private String messageName;
private String sharedObjectName;
public DirectClientMessage(String meetingID, String userID, String messageName, Map<String, Object> message) {
this.meetingID = meetingID;
this.userID = userID;
this.message = message;
this.messageName = messageName;
}
public void setSharedObjectName(String name) {
sharedObjectName = name;
}
public String getSharedObjectName() {
return sharedObjectName;
}
public String getMeetingID() {
return meetingID;
}
public String getUserID() {
return userID;
}
public String getMessageName() {
return messageName;
}
public Map<String, Object> getMessage() {
return message;
}
}

View File

@ -0,0 +1,14 @@
package org.bigbluebutton.conference.meeting.messaging.red5;
public class DisconnectAllClientsMessage implements ClientMessage {
private final String meetingId;
public DisconnectAllClientsMessage(String meetingId) {
this.meetingId = meetingId;
}
public String getMeetingId() {
return meetingId;
}
}

View File

@ -0,0 +1,20 @@
package org.bigbluebutton.conference.meeting.messaging.red5;
public class DisconnectClientMessage implements ClientMessage {
private final String meetingId;
private final String userId;
public DisconnectClientMessage(String meetingId, String userId) {
this.meetingId = meetingId;
this.userId = userId;
}
public String getMeetingId() {
return meetingId;
}
public String getUserId() {
return userId;
}
}

View File

@ -0,0 +1,59 @@
/**
* BigBlueButton open source conferencing system - http://www.bigbluebutton.org/
*
* Copyright (c) 2012 BigBlueButton Inc. and by respective authors (see below).
*
* This program is free software; you can redistribute it and/or modify it under the
* terms of the GNU Lesser General Public License as published by the Free Software
* Foundation; either version 3.0 of the License, or (at your option) any later
* version.
*
* BigBlueButton is distributed in the hope that it will be useful, but WITHOUT ANY
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
* PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License along
* with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
*
*/
package org.bigbluebutton.conference.meeting.messaging.red5;
import java.util.ArrayList;
public class SharedObjectClientMessage implements ClientMessage {
public static final String BROADCAST = "broadcast";
public static final String DIRECT = "direct";
public static final String SHAREDOBJECT = "sharedobject";
private String meetingID;
private String sharedObjectName;
private ArrayList<Object> message;
private String messageName;
public SharedObjectClientMessage(String meetingID, String sharedObjectName, String messageName, ArrayList<Object> message) {
this.meetingID = meetingID;
this.message = message;
this.sharedObjectName = sharedObjectName;
this.messageName = messageName;
}
public void setSharedObjectName(String name) {
sharedObjectName = name;
}
public String getSharedObjectName() {
return sharedObjectName;
}
public String getMeetingID() {
return meetingID;
}
public String getMessageName() {
return messageName;
}
public ArrayList<Object> getMessage() {
return message;
}
}

View File

@ -0,0 +1,82 @@
package org.bigbluebutton.conference.meeting.messaging.redis;
import java.util.HashMap;
import java.util.Map;
import org.bigbluebutton.conference.service.messaging.CreateMeetingMessage;
import org.bigbluebutton.conference.service.messaging.DestroyMeetingMessage;
import org.bigbluebutton.conference.service.messaging.EndMeetingMessage;
import org.bigbluebutton.conference.service.messaging.IMessage;
import org.bigbluebutton.conference.service.messaging.KeepAliveMessage;
import org.bigbluebutton.conference.service.messaging.MessageFromJsonConverter;
import org.bigbluebutton.conference.service.messaging.MessagingConstants;
import org.bigbluebutton.conference.service.messaging.RegisterUserMessage;
import org.bigbluebutton.conference.service.messaging.UserConnectedToGlobalAudio;
import org.bigbluebutton.conference.service.messaging.UserDisconnectedFromGlobalAudio;
import org.bigbluebutton.conference.service.messaging.ValidateAuthTokenMessage;
import org.bigbluebutton.conference.service.messaging.redis.MessageHandler;
import org.bigbluebutton.core.api.IBigBlueButtonInGW;
import org.red5.logging.Red5LoggerFactory;
import org.slf4j.Logger;
public class MeetingMessageHandler implements MessageHandler {
private static Logger log = Red5LoggerFactory.getLogger(MeetingMessageHandler.class, "bigbluebutton");
private IBigBlueButtonInGW bbbGW;
@Override
public void handleMessage(String pattern, String channel, String message) {
System.out.println("Checking message: " + pattern + " " + channel + " " + message);
if (channel.equalsIgnoreCase(MessagingConstants.TO_MEETING_CHANNEL)) {
System.out.println("Meeting message: " + channel + " " + message);
IMessage msg = MessageFromJsonConverter.convert(message);
if (msg != null) {
if (msg instanceof EndMeetingMessage) {
EndMeetingMessage emm = (EndMeetingMessage) msg;
log.info("Received end meeting request. Meeting id [{}]", emm.meetingId);
bbbGW.endMeeting(emm.meetingId);
} else if (msg instanceof CreateMeetingMessage) {
CreateMeetingMessage emm = (CreateMeetingMessage) msg;
bbbGW.createMeeting2(emm.id, emm.name, emm.record, emm.voiceBridge, emm.duration);
} else if (msg instanceof RegisterUserMessage) {
RegisterUserMessage emm = (RegisterUserMessage) msg;
bbbGW.registerUser(emm.meetingID, emm.internalUserId, emm.fullname, emm.role, emm.externUserID, emm.authToken);
} else if (msg instanceof DestroyMeetingMessage) {
DestroyMeetingMessage emm = (DestroyMeetingMessage) msg;
log.info("Received destroy meeting request. Meeting id [{}]", emm.meetingId);
bbbGW.destroyMeeting(emm.meetingId);
} else if (msg instanceof ValidateAuthTokenMessage) {
ValidateAuthTokenMessage emm = (ValidateAuthTokenMessage) msg;
log.info("Received ValidateAuthTokenMessage token request. Meeting id [{}]", emm.meetingId);
bbbGW.validateAuthToken(emm.meetingId, emm.userId, emm.token, emm.replyTo);
} else if (msg instanceof UserConnectedToGlobalAudio) {
UserConnectedToGlobalAudio emm = (UserConnectedToGlobalAudio) msg;
log.info("Received UserConnectedToGlobalAudio toekn request. user id [{}]", emm.name);
bbbGW.userConnectedToGlobalAudio(emm.voiceConf, emm.userid, emm.name);
} else if (msg instanceof UserDisconnectedFromGlobalAudio) {
UserDisconnectedFromGlobalAudio emm = (UserDisconnectedFromGlobalAudio) msg;
log.info("Received UserDisconnectedFromGlobalAudio toekn request. Meeting id [{}]", emm.name);
bbbGW.userConnectedToGlobalAudio(emm.voiceConf, emm.userid, emm.name);
}
}
} else if (channel.equalsIgnoreCase(MessagingConstants.TO_SYSTEM_CHANNEL)) {
IMessage msg = MessageFromJsonConverter.convert(message);
if (msg != null) {
if (msg instanceof KeepAliveMessage) {
KeepAliveMessage emm = (KeepAliveMessage) msg;
log.info("Received KeepAliveMessage request. Meeting id [{}]", emm.keepAliveId);
bbbGW.isAliveAudit(emm.keepAliveId);
}
}
}
}
public void setBigBlueButtonInGW(IBigBlueButtonInGW bbbGW) {
this.bbbGW = bbbGW;
}
}

View File

@ -18,99 +18,33 @@
*/
package org.bigbluebutton.conference.service.chat;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.slf4j.Logger;
import org.red5.logging.Red5LoggerFactory;
import org.red5.server.api.Red5; import org.bigbluebutton.conference.BigBlueButtonSession;
import org.bigbluebutton.conference.ClientMessage;
import org.bigbluebutton.conference.ConnectionInvokerService;
import org.bigbluebutton.conference.Constants;
import org.bigbluebutton.conference.service.chat.ChatRoomsManager;
import org.bigbluebutton.conference.service.chat.ChatRoom; import org.bigbluebutton.conference.service.chat.IChatRoomListener;
import org.red5.logging.Red5LoggerFactory;
import org.bigbluebutton.core.api.IBigBlueButtonInGW;
public class ChatApplication {
private static Logger log = Red5LoggerFactory.getLogger( ChatApplication.class, "bigbluebutton" );
private ChatRoomsManager roomsManager;
public ChatHandler handler;
private ConnectionInvokerService connInvokerService;
public boolean createRoom(String name) {
roomsManager.addRoom(new ChatRoom(name));
return true;
private IBigBlueButtonInGW bbbInGW;
public void setBigBlueButtonInGW(IBigBlueButtonInGW inGW) {
bbbInGW = inGW;
}
public boolean destroyRoom(String name) {
if (roomsManager.hasRoom(name)) {
roomsManager.removeRoom(name);
}
return true;
public void sendPublicChatHistory(String meetingID, String requesterID) {
// Just hardcode as we don't really need it for flash client. (ralam may 7, 2014)
String replyTo = meetingID + "/" + requesterID;
bbbInGW.getChatHistory(meetingID, requesterID, replyTo);
}
public boolean hasRoom(String name) {
return roomsManager.hasRoom(name);
public void sendPublicMessage(String meetingID, String requesterID, Map<String, String> message) {
bbbInGW.sendPublicMessage(meetingID, requesterID, message);
}
public boolean addRoomListener(String room, IChatRoomListener listener) {
if (roomsManager.hasRoom(room)){
roomsManager.addRoomListener(room, listener);
return true;
}
log.warn("Adding listener to a non-existant room " + room);
return false;
}
public void sendPublicChatHistory(String meetingID) {
List<ChatMessageVO> messages = roomsManager.getChatMessages(meetingID);
List<Map<String, Object>> msgs = new ArrayList<Map<String, Object>>();
for (ChatMessageVO v : messages) {
msgs.add(v.toMap());
}
Map<String, Object> messageToSend = new HashMap<String, Object>();
messageToSend.put("count", new Integer(msgs.size()));
messageToSend.put("messages", msgs);
ClientMessage m = new ClientMessage(ClientMessage.DIRECT, getBbbSession().getInternalUserID(), "ChatRequestMessageHistoryReply", messageToSend);
connInvokerService.sendMessage(m);
}
public void sendPublicMessage(String room, ChatMessageVO chatobj) {
roomsManager.sendMessage(room, chatobj);
ClientMessage m = new ClientMessage(ClientMessage.BROADCAST, getMeetingId(), "ChatReceivePublicMessageCommand", chatobj.toMap());
connInvokerService.sendMessage(m);
}
public void sendPrivateMessage(ChatMessageVO chatobj) {
ClientMessage m = new ClientMessage(ClientMessage.DIRECT, chatobj.toUserID, "ChatReceivePrivateMessageCommand", chatobj.toMap());
connInvokerService.sendMessage(m);
ClientMessage m2 = new ClientMessage(ClientMessage.DIRECT, chatobj.fromUserID, "ChatReceivePrivateMessageCommand", chatobj.toMap());
connInvokerService.sendMessage(m2);
}
public void setRoomsManager(ChatRoomsManager r) {
log.debug("Setting room manager");
roomsManager = r;
}
private String getMeetingId(){
return Red5.getConnectionLocal().getScope().getName();
}
private BigBlueButtonSession getBbbSession() {
return (BigBlueButtonSession) Red5.getConnectionLocal().getAttribute(Constants.SESSION);
}
public void setConnInvokerService(ConnectionInvokerService connInvokerService) {
this.connInvokerService = connInvokerService;
public void sendPrivateMessage(String meetingID, String requesterID, Map<String, String> message) {
bbbInGW.sendPrivateMessage(meetingID, requesterID, message);
}
}

View File

@ -26,7 +26,6 @@ import org.slf4j.Logger;
import org.red5.logging.Red5LoggerFactory;
import org.red5.server.api.scope.IScope;
import org.bigbluebutton.conference.service.recorder.RecorderApplication;
import org.bigbluebutton.conference.service.recorder.chat.ChatEventRecorder;
public class ChatHandler implements IApplication{
private static Logger log = Red5LoggerFactory.getLogger( ChatHandler.class, "bigbluebutton" );
@ -89,11 +88,6 @@ public class ChatHandler implements IApplication{
public boolean roomConnect(IConnection connection, Object[] params) {
log.debug("***** " + APP + " [ " + " roomConnect [ " + connection.getScope().getName() + "] *********");
chatApplication.createRoom(connection.getScope().getName());
ChatEventRecorder recorder = new ChatEventRecorder(connection.getScope().getName(), recorderApplication);
chatApplication.addRoomListener(connection.getScope().getName(), recorder);
return true;
}
@ -106,13 +100,11 @@ public class ChatHandler implements IApplication{
@Override
public void roomStop(IScope scope) {
log.debug("***** " + APP + " [ " + " roomStop [ " + scope.getName() + "] *********");
chatApplication.destroyRoom(scope.getName());
}
public void setChatApplication(ChatApplication a) {
log.debug("Setting chat application");
chatApplication = a;
chatApplication.handler = this;
}
public void setRecorderApplication(RecorderApplication a) {

Some files were not shown because too many files have changed in this diff Show More