Merge branch 'meteor-rebuild-server' of github.com:antobinary/bigbluebutton into meteor-rebuild-server
This commit is contained in:
commit
41f6c5c3d5
1
.gitignore
vendored
1
.gitignore
vendored
@ -4,7 +4,6 @@ record-and-playback/playback-web/playback-web-0.1.war
|
|||||||
record-and-playback/.project
|
record-and-playback/.project
|
||||||
push_to_git.py
|
push_to_git.py
|
||||||
*/.gradle
|
*/.gradle
|
||||||
.gitignore
|
|
||||||
bbb-lti/.classpath
|
bbb-lti/.classpath
|
||||||
bbb-lti/.project
|
bbb-lti/.project
|
||||||
bbb-lti/bin
|
bbb-lti/bin
|
||||||
|
@ -1,17 +1,18 @@
|
|||||||
//usePlugin 'war'
|
apply plugin: 'war'
|
||||||
usePlugin 'war'
|
|
||||||
version = '0.8'
|
version = '0.8'
|
||||||
|
|
||||||
repositories {
|
repositories {
|
||||||
mavenCentral()
|
mavenCentral()
|
||||||
}
|
}
|
||||||
|
|
||||||
task resolveDeps(dependsOn: configurations.default.buildArtifacts, type: Copy) {
|
task resolveDeps(type: Copy) {
|
||||||
into('lib')
|
into('lib')
|
||||||
from configurations.default
|
from configurations.default
|
||||||
from configurations.default.allArtifacts*.file
|
from configurations.default.allArtifacts.file
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
war {
|
war {
|
||||||
baseName = 'demo'
|
baseName = 'demo'
|
||||||
version = ''
|
version = ''
|
||||||
|
@ -1,7 +1,5 @@
|
|||||||
#utf-8
|
#Grails Metadata file
|
||||||
#Wed Oct 10 08:34:02 PDT 2012
|
#Thu Mar 20 10:48:08 PDT 2014
|
||||||
app.version=0.1.1
|
app.grails.version=2.3.6
|
||||||
app.servlet.version=2.4
|
|
||||||
app.grails.version=1.1.1
|
|
||||||
plugins.hibernate=1.1.1
|
|
||||||
app.name=lti
|
app.name=lti
|
||||||
|
app.version=0.1.2
|
||||||
|
23
bbb-lti/grails-app/conf/ApplicationResources.groovy
Normal file
23
bbb-lti/grails-app/conf/ApplicationResources.groovy
Normal 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'
|
||||||
|
}
|
||||||
|
}
|
@ -15,13 +15,10 @@
|
|||||||
You should have received a copy of the GNU Lesser General Public License along
|
You should have received a copy of the GNU Lesser General Public License along
|
||||||
with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
|
with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
class BootStrap {
|
class BootStrap {
|
||||||
|
|
||||||
def init = { servletContext ->
|
def init = { servletContext ->
|
||||||
log.debug "Bootstrapping bbb-lti"
|
}
|
||||||
}
|
def destroy = {
|
||||||
|
}
|
||||||
def destroy = {
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
|
73
bbb-lti/grails-app/conf/BuildConfig.groovy
Normal file
73
bbb-lti/grails-app/conf/BuildConfig.groovy
Normal 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'
|
||||||
|
}
|
||||||
|
}
|
@ -1,93 +1,139 @@
|
|||||||
/*
|
/*
|
||||||
BigBlueButton open source conferencing system - http://www.bigbluebutton.org/
|
BigBlueButton open source conferencing system - http://www.bigbluebutton.org/
|
||||||
|
|
||||||
Copyright (c) 2012 BigBlueButton Inc. and by respective authors (see below).
|
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
|
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
|
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
|
Foundation; either version 3.0 of the License, or (at your option) any later
|
||||||
version.
|
version.
|
||||||
|
|
||||||
BigBlueButton is distributed in the hope that it will be useful, but WITHOUT ANY
|
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
|
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
|
||||||
PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
|
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
|
You should have received a copy of the GNU Lesser General Public License along
|
||||||
with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
|
with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
// locations to search for config files that get merged into the main config
|
// locations to search for config files that get merged into the main config;
|
||||||
// config files can either be Java properties files or ConfigSlurper scripts
|
// config files can be ConfigSlurper scripts, Java properties files, or classes
|
||||||
|
// in the classpath in ConfigSlurper format
|
||||||
// grails.config.locations = [ "classpath:${appName}-config.properties",
|
|
||||||
// "classpath:${appName}-config.groovy",
|
grails.config.locations = [ "classpath:lti.properties"]
|
||||||
// "file:${userHome}/.grails/${appName}-config.properties",
|
// grails.config.locations = [ "classpath:${appName}-config.properties",
|
||||||
// "file:${userHome}/.grails/${appName}-config.groovy"]
|
// "classpath:${appName}-config.groovy",
|
||||||
|
// "file:${userHome}/.grails/${appName}-config.properties",
|
||||||
grails.config.locations = [ "classpath:lti.properties"]
|
// "file:${userHome}/.grails/${appName}-config.groovy"]
|
||||||
|
|
||||||
// if(System.properties["${appName}.config.location"]) {
|
// if (System.properties["${appName}.config.location"]) {
|
||||||
// grails.config.locations << "file:" + 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.project.groupId = appName // change this to alter the default package name and Maven publishing destination
|
||||||
grails.mime.types = [ html: ['text/html','application/xhtml+xml'],
|
|
||||||
xml: ['text/xml', 'application/xml'],
|
grails.mime.file.extensions = true // enables the parsing of file extensions from URLs into the request format
|
||||||
text: 'text/plain',
|
grails.mime.use.accept.header = false
|
||||||
js: 'text/javascript',
|
// The ACCEPT header will not be used for content negotiation for user agents containing the following strings (defaults to the 4 major rendering engines)
|
||||||
rss: 'application/rss+xml',
|
grails.mime.disable.accept.header.userAgents = ['Gecko', 'WebKit', 'Presto', 'Trident']
|
||||||
atom: 'application/atom+xml',
|
grails.mime.types = [ // the first one is the default format
|
||||||
css: 'text/css',
|
all: '*/*', // 'all' maps to '*' or the first available format in withFormat
|
||||||
csv: 'text/csv',
|
atom: 'application/atom+xml',
|
||||||
all: '*/*',
|
css: 'text/css',
|
||||||
json: ['application/json','text/json'],
|
csv: 'text/csv',
|
||||||
form: 'application/x-www-form-urlencoded',
|
form: 'application/x-www-form-urlencoded',
|
||||||
multipartForm: 'multipart/form-data'
|
html: ['text/html','application/xhtml+xml'],
|
||||||
]
|
js: 'text/javascript',
|
||||||
// The default codec used to encode data with ${}
|
json: ['application/json', 'text/json'],
|
||||||
grails.views.default.codec="none" // none, html, base64
|
multipartForm: 'multipart/form-data',
|
||||||
grails.views.gsp.encoding="UTF-8"
|
rss: 'application/rss+xml',
|
||||||
grails.converters.encoding="UTF-8"
|
text: 'text/plain',
|
||||||
|
hal: ['application/hal+json','application/hal+xml'],
|
||||||
// enabled native2ascii conversion of i18n properties files
|
xml: ['text/xml', 'application/xml']
|
||||||
grails.enable.native2ascii = true
|
]
|
||||||
|
|
||||||
// set per-environment serverURL stem for creating absolute links
|
// URL Mapping Cache Max Size, defaults to 5000
|
||||||
environments {
|
//grails.urlmapping.cache.maxsize = 1000
|
||||||
production {
|
|
||||||
grails.serverURL = "http://localhost:8080/${appName}"
|
// What URL patterns should be processed by the resources plugin
|
||||||
}
|
grails.resources.adhoc.patterns = ['/images/*', '/css/*', '/js/*', '/plugins/*']
|
||||||
development {
|
grails.resources.adhoc.excludes = ['/WEB-INF/**']
|
||||||
grails.serverURL = "http://localhost:8080/${appName}"
|
|
||||||
}
|
// Legacy setting for codec used to encode data with ${}
|
||||||
test {
|
grails.views.default.codec = "html"
|
||||||
grails.serverURL = "http://localhost:8080/${appName}"
|
|
||||||
}
|
// The default scope for controllers. May be prototype, session or singleton.
|
||||||
|
// If unspecified, controllers are prototype scoped.
|
||||||
}
|
grails.controllers.defaultScope = 'singleton'
|
||||||
|
|
||||||
// log4j configuration
|
// GSP settings
|
||||||
log4j = {
|
grails {
|
||||||
appenders {
|
views {
|
||||||
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')
|
gsp {
|
||||||
console name:'console', layout:pattern(conversionPattern: '%d{[dd.MM.yy HH:mm:ss.SSS]} %-5p %c %x - %m%n')
|
encoding = 'UTF-8'
|
||||||
'null' name:'stacktrace'
|
htmlcodec = 'xml' // use xml escaping instead of HTML4 escaping
|
||||||
}
|
codecs {
|
||||||
debug logfile:"grails.app"
|
expression = 'html' // escapes values inside ${}
|
||||||
|
scriptlet = 'html' // escapes output from scriptlets in GSPs
|
||||||
error 'org.codehaus.groovy.grails.web.servlet', // controllers
|
taglib = 'none' // escapes output from taglibs
|
||||||
'org.codehaus.groovy.grails.web.pages', // GSP
|
staticparts = 'none' // escapes output from static template parts
|
||||||
'org.codehaus.groovy.grails.web.sitemesh', // layouts
|
}
|
||||||
'org.codehaus.groovy.grails.web.mapping.filter', // URL mapping
|
}
|
||||||
'org.codehaus.groovy.grails.web.mapping', // URL mapping
|
// escapes all not-encoded output at final stage of outputting
|
||||||
'org.codehaus.groovy.grails.commons', // core / classloading
|
// filteringCodecForContentType.'text/html' = 'html'
|
||||||
'org.codehaus.groovy.grails.plugins', // plugins
|
}
|
||||||
'org.codehaus.groovy.grails.orm.hibernate', // hibernate integration
|
}
|
||||||
'org.springframework',
|
|
||||||
'org.hibernate'
|
|
||||||
|
grails.converters.encoding = "UTF-8"
|
||||||
warn 'org.mortbay.log'
|
// 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'
|
||||||
|
|
||||||
|
}
|
||||||
|
@ -17,34 +17,102 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
dataSource {
|
dataSource {
|
||||||
pooled = true
|
pooled = true
|
||||||
driverClassName = "org.hsqldb.jdbcDriver"
|
jmxExport = true
|
||||||
username = "sa"
|
driverClassName = "org.h2.Driver"
|
||||||
password = ""
|
username = "sa"
|
||||||
|
password = ""
|
||||||
}
|
}
|
||||||
hibernate {
|
hibernate {
|
||||||
cache.use_second_level_cache=true
|
cache.use_second_level_cache = true
|
||||||
cache.use_query_cache=true
|
cache.use_query_cache = false
|
||||||
cache.provider_class='com.opensymphony.oscache.hibernate.OSCacheProvider'
|
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
|
// environment specific settings
|
||||||
environments {
|
environments {
|
||||||
development {
|
development {
|
||||||
dataSource {
|
dataSource {
|
||||||
dbCreate = "create-drop" // one of 'create', 'create-drop','update'
|
dbCreate = "create-drop" // one of 'create', 'create-drop', 'update', 'validate', ''
|
||||||
url = "jdbc:hsqldb:mem:devDB"
|
url = "jdbc:h2:mem:devDb;MVCC=TRUE;LOCK_TIMEOUT=10000;DB_CLOSE_ON_EXIT=FALSE"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
test {
|
test {
|
||||||
dataSource {
|
dataSource {
|
||||||
dbCreate = "update"
|
dbCreate = "update"
|
||||||
url = "jdbc:hsqldb:mem:testDb"
|
url = "jdbc:h2:mem:testDb;MVCC=TRUE;LOCK_TIMEOUT=10000;DB_CLOSE_ON_EXIT=FALSE"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
production {
|
production {
|
||||||
dataSource {
|
dataSource {
|
||||||
dbCreate = "update"
|
dbCreate = "update"
|
||||||
url = "jdbc:hsqldb:mem:prodDb"
|
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
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -17,13 +17,15 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
class UrlMappings {
|
class UrlMappings {
|
||||||
static mappings = {
|
|
||||||
"/$controller/$action?/$id?"{
|
static mappings = {
|
||||||
constraints {
|
"/$controller/$action?/$id"{
|
||||||
// apply constraints here
|
constraints {
|
||||||
}
|
// apply constraints here
|
||||||
}
|
}
|
||||||
"/"(view:"/index")
|
}
|
||||||
"500"(view:'/error')
|
|
||||||
|
"/"(view:"/index")
|
||||||
|
"500"(view:'/error')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -31,7 +31,8 @@ bigbluebuttonSalt=bbb_salt
|
|||||||
ltiEndPoint=http://localhost/lti/tool
|
ltiEndPoint=http://localhost/lti/tool
|
||||||
# The list of consumers allowed to access this lti service.
|
# The list of consumers allowed to access this lti service.
|
||||||
# Format: {consumerId1:sharedSecret1}
|
# Format: {consumerId1:sharedSecret1}
|
||||||
ltiConsumers=bbb:lti_secret
|
##ltiConsumers=bbb:bbb_salt
|
||||||
|
ltiConsumers=bbb:welcome
|
||||||
# The mode used to interact with BigBlueButton
|
# The mode used to interact with BigBlueButton
|
||||||
# Format: [<simple>|extended]
|
# Format: [<simple>|extended]
|
||||||
ltiMode=extended
|
ltiMode=extended
|
||||||
|
@ -1,21 +1,3 @@
|
|||||||
/*
|
// Place your Spring DSL code here
|
||||||
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 = {
|
beans = {
|
||||||
|
}
|
||||||
}
|
|
||||||
|
@ -41,19 +41,20 @@ class ToolController {
|
|||||||
LtiService ltiService
|
LtiService ltiService
|
||||||
BigbluebuttonService bigbluebuttonService
|
BigbluebuttonService bigbluebuttonService
|
||||||
|
|
||||||
def index = {
|
def index = {
|
||||||
if( ltiService.consumerMap == null) ltiService.initConsumerMap()
|
if( ltiService.consumerMap == null) ltiService.initConsumerMap()
|
||||||
log.debug CONTROLLER_NAME + "#index"
|
log.debug CONTROLLER_NAME + "#index"
|
||||||
|
|
||||||
setLocalization(params)
|
setLocalization(params)
|
||||||
|
|
||||||
params.put(REQUEST_METHOD, request.getMethod().toUpperCase())
|
params.put(REQUEST_METHOD, request.getMethod().toUpperCase())
|
||||||
log.debug "params: " + params
|
ltiService.logParameters(params)
|
||||||
|
|
||||||
if( request.post ){
|
if( request.post ){
|
||||||
Map<String, String> result = new HashMap<String, String>()
|
Map<String, String> result = new HashMap<String, String>()
|
||||||
ArrayList<String> missingParams = new ArrayList<String>()
|
ArrayList<String> missingParams = new ArrayList<String>()
|
||||||
log.debug "Checking for required parameters"
|
log.debug "Checking for required parameters"
|
||||||
|
|
||||||
if (hasAllRequiredParams(params, missingParams)) {
|
if (hasAllRequiredParams(params, missingParams)) {
|
||||||
def sanitizedParams = sanitizePrametersForBaseString(params)
|
def sanitizedParams = sanitizePrametersForBaseString(params)
|
||||||
def consumer = ltiService.getConsumer(params.get(Parameter.CONSUMER_ID))
|
def consumer = ltiService.getConsumer(params.get(Parameter.CONSUMER_ID))
|
||||||
@ -74,6 +75,7 @@ class ToolController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
|
log.debug "The message has NOT a valid signature."
|
||||||
result.put("resultMessageKey", "InvalidSignature")
|
result.put("resultMessageKey", "InvalidSignature")
|
||||||
result.put("resultMessage", "Invalid signature (" + params.get(Parameter.OAUTH_SIGNATURE) + ").")
|
result.put("resultMessage", "Invalid signature (" + params.get(Parameter.OAUTH_SIGNATURE) + ").")
|
||||||
}
|
}
|
||||||
@ -91,12 +93,11 @@ class ToolController {
|
|||||||
result.put("resultMessageKey", "MissingRequiredParameter")
|
result.put("resultMessageKey", "MissingRequiredParameter")
|
||||||
result.put("resultMessage", "Missing parameters [$missingStr]")
|
result.put("resultMessage", "Missing parameters [$missingStr]")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if( result.containsKey("resultMessageKey") ) {
|
||||||
if( result != null && result.containsKey("resultMessageKey") ) {
|
|
||||||
log.debug "Error [resultMessageKey:'" + result.get("resultMessageKey") + "', resultMessage:'" + result.get("resultMessage") + "']"
|
log.debug "Error [resultMessageKey:'" + result.get("resultMessageKey") + "', resultMessage:'" + result.get("resultMessage") + "']"
|
||||||
render(view: "error", model: ['resultMessageKey': result.get("resultMessageKey"), 'resultMessage': result.get("resultMessage")])
|
render(view: "error", model: ['resultMessageKey': result.get("resultMessageKey"), 'resultMessage': result.get("resultMessage")])
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
session["params"] = params
|
session["params"] = params
|
||||||
List<Object> recordings = bigbluebuttonService.getRecordings(params)
|
List<Object> recordings = bigbluebuttonService.getRecordings(params)
|
||||||
@ -110,23 +111,23 @@ class ToolController {
|
|||||||
/// Add duration
|
/// Add duration
|
||||||
recording.put("duration", duration )
|
recording.put("duration", duration )
|
||||||
}
|
}
|
||||||
|
|
||||||
render(view: "index", model: ['params': params, 'recordingList': recordings, 'ismoderator': bigbluebuttonService.isModerator(params)])
|
render(view: "index", model: ['params': params, 'recordingList': recordings, 'ismoderator': bigbluebuttonService.isModerator(params)])
|
||||||
}
|
}
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
render(text: getCartridgeXML(), contentType: "text/xml", encoding: "UTF-8")
|
render(text: getCartridgeXML(), contentType: "text/xml", encoding: "UTF-8")
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
def join = {
|
def join = {
|
||||||
if( ltiService.consumerMap == null) ltiService.initConsumerMap()
|
if( ltiService.consumerMap == null) ltiService.initConsumerMap()
|
||||||
log.debug CONTROLLER_NAME + "#join"
|
log.debug CONTROLLER_NAME + "#join"
|
||||||
Map<String, String> result
|
Map<String, String> result
|
||||||
|
|
||||||
def sessionParams = session["params"]
|
def sessionParams = session["params"]
|
||||||
|
|
||||||
if( sessionParams != null ) {
|
if( sessionParams != null ) {
|
||||||
log.debug "params: " + params
|
log.debug "params: " + params
|
||||||
log.debug "sessionParams: " + sessionParams
|
log.debug "sessionParams: " + sessionParams
|
||||||
@ -136,7 +137,7 @@ class ToolController {
|
|||||||
result.put("resultMessageKey", "InvalidSession")
|
result.put("resultMessageKey", "InvalidSession")
|
||||||
result.put("resultMessage", "Invalid session. User can not execute this action.")
|
result.put("resultMessage", "Invalid session. User can not execute this action.")
|
||||||
}
|
}
|
||||||
|
|
||||||
if( result.containsKey("resultMessageKey")) {
|
if( result.containsKey("resultMessageKey")) {
|
||||||
log.debug "Error [resultMessageKey:'" + result.get("resultMessageKey") + "', resultMessage:'" + result.get("resultMessage") + "']"
|
log.debug "Error [resultMessageKey:'" + result.get("resultMessageKey") + "', resultMessage:'" + result.get("resultMessage") + "']"
|
||||||
render(view: "error", model: ['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 = {
|
def publish = {
|
||||||
log.debug CONTROLLER_NAME + "#publish"
|
log.debug CONTROLLER_NAME + "#publish"
|
||||||
Map<String, String> result
|
Map<String, String> result
|
||||||
|
|
||||||
def sessionParams = session["params"]
|
def sessionParams = session["params"]
|
||||||
|
|
||||||
if( sessionParams == null ) {
|
if( sessionParams == null ) {
|
||||||
result = new HashMap<String, String>()
|
result = new HashMap<String, String>()
|
||||||
result.put("resultMessageKey", "InvalidSession")
|
result.put("resultMessageKey", "InvalidSession")
|
||||||
@ -165,7 +166,7 @@ class ToolController {
|
|||||||
//Execute the publish command
|
//Execute the publish command
|
||||||
result = bigbluebuttonService.doPublishRecordings(params)
|
result = bigbluebuttonService.doPublishRecordings(params)
|
||||||
}
|
}
|
||||||
|
|
||||||
if( result.containsKey("resultMessageKey")) {
|
if( result.containsKey("resultMessageKey")) {
|
||||||
log.debug "Error [resultMessageKey:'" + result.get("resultMessageKey") + "', resultMessage:'" + result.get("resultMessage") + "']"
|
log.debug "Error [resultMessageKey:'" + result.get("resultMessageKey") + "', resultMessage:'" + result.get("resultMessage") + "']"
|
||||||
render(view: "error", model: ['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)])
|
render(view: "index", model: ['params': sessionParams, 'recordingList': recordings, 'ismoderator': bigbluebuttonService.isModerator(sessionParams)])
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@ -191,9 +192,9 @@ class ToolController {
|
|||||||
def delete = {
|
def delete = {
|
||||||
log.debug CONTROLLER_NAME + "#delete"
|
log.debug CONTROLLER_NAME + "#delete"
|
||||||
Map<String, String> result
|
Map<String, String> result
|
||||||
|
|
||||||
def sessionParams = session["params"]
|
def sessionParams = session["params"]
|
||||||
|
|
||||||
if( sessionParams == null ) {
|
if( sessionParams == null ) {
|
||||||
result = new HashMap<String, String>()
|
result = new HashMap<String, String>()
|
||||||
result.put("resultMessageKey", "InvalidSession")
|
result.put("resultMessageKey", "InvalidSession")
|
||||||
@ -209,7 +210,7 @@ class ToolController {
|
|||||||
//Execute the delete command
|
//Execute the delete command
|
||||||
result = bigbluebuttonService.doDeleteRecordings(params)
|
result = bigbluebuttonService.doDeleteRecordings(params)
|
||||||
}
|
}
|
||||||
|
|
||||||
if( result.containsKey("resultMessageKey")) {
|
if( result.containsKey("resultMessageKey")) {
|
||||||
log.debug "Error [resultMessageKey:'" + result.get("resultMessageKey") + "', resultMessage:'" + result.get("resultMessage") + "']"
|
log.debug "Error [resultMessageKey:'" + result.get("resultMessageKey") + "', resultMessage:'" + result.get("resultMessage") + "']"
|
||||||
render(view: "error", model: ['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
|
/// Add duration
|
||||||
recording.put("duration", duration )
|
recording.put("duration", duration )
|
||||||
}
|
}
|
||||||
|
|
||||||
render(view: "index", model: ['params': sessionParams, 'recordingList': recordings, 'ismoderator': bigbluebuttonService.isModerator(sessionParams)])
|
render(view: "index", model: ['params': sessionParams, 'recordingList': recordings, 'ismoderator': bigbluebuttonService.isModerator(sessionParams)])
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void setLocalization(params){
|
private void setLocalization(params){
|
||||||
|
|
||||||
String locale = params.get(Parameter.LAUNCH_LOCALE)
|
String locale = params.get(Parameter.LAUNCH_LOCALE)
|
||||||
locale = (locale == null || locale.equals("")?"en":locale)
|
locale = (locale == null || locale.equals("")?"en":locale)
|
||||||
log.debug "Locale code =" + locale
|
log.debug "Locale code =" + locale
|
||||||
@ -251,15 +251,28 @@ class ToolController {
|
|||||||
Map<String, String> result = new HashMap<String, String>()
|
Map<String, String> result = new HashMap<String, String>()
|
||||||
|
|
||||||
setLocalization(params)
|
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 + "]"
|
log.debug "Localized default welcome message: [" + welcome + "]"
|
||||||
|
|
||||||
// Check for [custom_]welcome parameter being passed from the LTI
|
// Check for [custom_]welcome parameter being passed from the LTI
|
||||||
if (params.get(Parameter.CUSTOM_WELCOME) != null) {
|
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 + "]"
|
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)
|
String destinationURL = bigbluebuttonService.getJoinURL(params, welcome, ltiService.mode)
|
||||||
log.debug "redirecting to " + destinationURL
|
log.debug "redirecting to " + destinationURL
|
||||||
|
|
||||||
@ -335,13 +348,16 @@ class ToolController {
|
|||||||
* @return - TRUE if the signatures matches the calculated signature
|
* @return - TRUE if the signatures matches the calculated signature
|
||||||
*/
|
*/
|
||||||
private boolean checkValidSignature(String method, String URL, String conSecret, Object postProp, String signature) {
|
private boolean checkValidSignature(String method, String URL, String conSecret, Object postProp, String signature) {
|
||||||
OAuthMessage oam = new OAuthMessage(method, URL, ((Properties)postProp).entrySet());
|
log.debug( "Starting checkValidSignature()" )
|
||||||
HMAC_SHA1 hmac = new HMAC_SHA1();
|
OAuthMessage oam = new OAuthMessage(method, URL, ((Properties)postProp).entrySet())
|
||||||
hmac.setConsumerSecret(conSecret);
|
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))
|
String calculatedSignature = hmac.getSignature(hmac.getBaseString(oam))
|
||||||
log.debug("Calculated: " + calculatedSignature + " Received: " + signature);
|
log.debug("Calculated: " + calculatedSignature + " Received: " + signature)
|
||||||
return calculatedSignature.equals(signature)
|
return calculatedSignature.equals(signature)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -16,8 +16,14 @@
|
|||||||
# with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
|
# 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.title=BigBlueButton LTI Interface
|
||||||
tool.view.join=Join Meeting
|
tool.view.join=Join Meeting
|
||||||
tool.view.recording=Recording
|
tool.view.recording=Recording
|
||||||
|
@ -16,8 +16,12 @@
|
|||||||
# with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
|
# 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ón esta siendo grabada
|
||||||
|
bigbluebutton.welcome.duration=La duración maxima para esta sesión es de {0} minutos
|
||||||
|
|
||||||
|
tool.view.app=BigBlueButton
|
||||||
tool.view.title=Interface LTI para BigBlueButton
|
tool.view.title=Interface LTI para BigBlueButton
|
||||||
tool.view.join=Ingresar a la sesión
|
tool.view.join=Ingresar a la sesión
|
||||||
tool.view.recording=Grabación
|
tool.view.recording=Grabación
|
||||||
|
@ -16,8 +16,10 @@
|
|||||||
# with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
|
# 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éo</u></a>.<br><br>Pour activer l'audio cliquez sur l'icône du casque à écouteurs (coin supérieur gauche). <b>S'il vous plaît utiliser le casque pour é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éo</u></a>.<br><br>Pour activer l'audio cliquez sur l'icône du casque à écouteurs (coin supérieur gauche). <b>S'il vous plaît utiliser le casque pour éviter de causer du bruit.</b>
|
||||||
|
|
||||||
|
tool.view.app=BigBlueButton
|
||||||
tool.view.title=LTI Interface pour BigBlueButton
|
tool.view.title=LTI Interface pour BigBlueButton
|
||||||
tool.view.join=Saisie de la réunion
|
tool.view.join=Saisie de la réunion
|
||||||
tool.view.recording=Enregistrement
|
tool.view.recording=Enregistrement
|
||||||
|
@ -27,9 +27,9 @@ class LtiService {
|
|||||||
boolean transactional = false
|
boolean transactional = false
|
||||||
|
|
||||||
def endPoint = "http://localhost/lti/tool"
|
def endPoint = "http://localhost/lti/tool"
|
||||||
|
|
||||||
def consumers = "demo:welcome"
|
def consumers = "demo:welcome"
|
||||||
def mode = "simple"
|
def mode = "simple"
|
||||||
|
|
||||||
Map<String, String> consumerMap
|
Map<String, String> consumerMap
|
||||||
|
|
||||||
def retrieveIconEndpoint() {
|
def retrieveIconEndpoint() {
|
||||||
@ -88,4 +88,10 @@ class LtiService {
|
|||||||
private String encodeBase64(byte[] signBytes) {
|
private String encodeBase64(byte[] signBytes) {
|
||||||
return Base64.encodeBase64URLSafeString(signBytes)
|
return Base64.encodeBase64URLSafeString(signBytes)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
def logParameters(Object params) {
|
||||||
|
log.debug "----------------------------------"
|
||||||
|
for( param in params ) log.debug "${param.getKey()}=${param.getValue()}"
|
||||||
|
log.debug "----------------------------------"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,54 +1,18 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
<html>
|
<html>
|
||||||
<head>
|
<head>
|
||||||
<title>Runtime Exception</title>
|
<title><g:if env="development">Grails Runtime Exception</g:if><g:else>Error</g:else></title>
|
||||||
<style type="text/css">
|
<meta name="layout" content="main">
|
||||||
.message {
|
<g:if env="development"><link rel="stylesheet" href="${resource(dir: 'css', file: 'errors.css')}" type="text/css"></g:if>
|
||||||
border: 1px solid black;
|
</head>
|
||||||
padding: 5px;
|
<body>
|
||||||
background-color:#E9E9E9;
|
<g:if env="development">
|
||||||
}
|
<g:renderException exception="${exception}" />
|
||||||
.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>
|
|
||||||
</g:if>
|
</g:if>
|
||||||
</div>
|
<g:else>
|
||||||
<g:if test="${exception}">
|
<ul class="errors">
|
||||||
<h2>Stack Trace</h2>
|
<li>An error has occurred</li>
|
||||||
<div class="stack">
|
</ul>
|
||||||
<pre><g:each in="${exception.stackTraceLines}">${it.encodeAsHTML()}<br/></g:each></pre>
|
</g:else>
|
||||||
</div>
|
</body>
|
||||||
</g:if>
|
</html>
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
|
@ -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…"/></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>
|
||||||
|
@ -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>
|
|
@ -1,16 +1,32 @@
|
|||||||
<html>
|
<!DOCTYPE html>
|
||||||
<head>
|
<!--[if lt IE 7 ]> <html lang="en" class="no-js ie6"> <![endif]-->
|
||||||
<title><g:layoutTitle default="<g:message code="tool.view.title" />" /></title>
|
<!--[if IE 7 ]> <html lang="en" class="no-js ie7"> <![endif]-->
|
||||||
<link rel="stylesheet" href="${resource(dir:'css',file:'main.css')}" />
|
<!--[if IE 8 ]> <html lang="en" class="no-js ie8"> <![endif]-->
|
||||||
<link rel="shortcut icon" href="${resource(dir:'images',file:'favicon.ico')}" type="image/x-icon" />
|
<!--[if IE 9 ]> <html lang="en" class="no-js ie9"> <![endif]-->
|
||||||
<g:layoutHead />
|
<!--[if (gt IE 9)|!(IE)]><!--> <html lang="en" class="no-js"><!--<![endif]-->
|
||||||
<g:javascript library="application" />
|
<head>
|
||||||
</head>
|
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
|
||||||
<body>
|
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
|
||||||
<div id="spinner" class="spinner" style="display:none;">
|
<title><g:message code="tool.view.title" /></title>
|
||||||
<img src="${resource(dir:'images',file:'spinner.gif')}" alt="Spinner" />
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
</div>
|
<link rel="shortcut icon" href="${resource(dir: 'images', file: 'favicon.ico')}" type="image/x-icon">
|
||||||
<div class="logo"><img src="${resource(dir:'images',file:'bbb_logo.jpg')}" alt="BigBlueButton" /></div>
|
<link rel="apple-touch-icon" href="${resource(dir: 'images', file: 'apple-touch-icon.png')}">
|
||||||
<g:layoutBody />
|
<link rel="apple-touch-icon" sizes="114x114" href="${resource(dir: 'images', file: 'apple-touch-icon-retina.png')}">
|
||||||
</body>
|
<link rel="stylesheet" href="${resource(dir: 'css', file: 'main.css')}" type="text/css">
|
||||||
</html>
|
<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…"/></div>
|
||||||
|
<r:layoutResources />
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
@ -7,6 +7,7 @@
|
|||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div class="body">
|
<div class="body">
|
||||||
|
<!-- tool.error -->
|
||||||
<g:if test="${ (resultMessageKey == 'InvalidEPortfolioUserId')}">
|
<g:if test="${ (resultMessageKey == 'InvalidEPortfolioUserId')}">
|
||||||
${resultMessage}
|
${resultMessage}
|
||||||
</g:if>
|
</g:if>
|
||||||
@ -14,12 +15,12 @@
|
|||||||
Connection could not be established.
|
Connection could not be established.
|
||||||
</g:else>
|
</g:else>
|
||||||
</div>
|
</div>
|
||||||
<!-- {
|
<!-- {
|
||||||
"error": {
|
"error": {
|
||||||
"messageKey": "${resultMessageKey}",
|
"messageKey": "${resultMessageKey}",
|
||||||
"message": "${resultMessage}"
|
"message": "${resultMessage}"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
-->
|
-->
|
||||||
<br/><br/>
|
<br/><br/>
|
||||||
</body>
|
</body>
|
||||||
|
@ -5,8 +5,10 @@
|
|||||||
<link rel="shortcut icon" href="${resource(dir:'images',file:'favicon.ico')}" type="image/x-icon" />
|
<link rel="shortcut icon" href="${resource(dir:'images',file:'favicon.ico')}" type="image/x-icon" />
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<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>
|
<br><br>
|
||||||
|
<div class="table-responsive">
|
||||||
<table class="table table-striped table-bordered table-condensed">
|
<table class="table table-striped table-bordered table-condensed">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<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 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>
|
<th class="header c4" style="text-align:center;" scope="col"><g:message code="tool.view.duration" /></th>
|
||||||
<g:if test="${ismoderator}">
|
<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>
|
</g:if>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
@ -34,14 +36,14 @@
|
|||||||
<td class="cell c3" style="text-align:center;">${new Date( Long.valueOf(r.startTime).longValue() )}</td>
|
<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>
|
<td class="cell c4" style="text-align:center;">${r.duration}</td>
|
||||||
<g:if test="${ismoderator}">
|
<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'}">
|
<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:if>
|
||||||
<g:else>
|
<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>
|
</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>
|
</td>
|
||||||
</g:if>
|
</g:if>
|
||||||
</tr>
|
</tr>
|
||||||
@ -49,6 +51,6 @@
|
|||||||
</g:each>
|
</g:each>
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
|
</div>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
@ -59,7 +59,11 @@ public class Proxy {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void setUrl(String url){
|
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){
|
public void setSalt(String salt){
|
||||||
|
@ -1,47 +1,34 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<beans xmlns="http://www.springframework.org/schema/beans"
|
<beans xmlns="http://www.springframework.org/schema/beans"
|
||||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
xsi:schemaLocation="
|
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
|
||||||
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd">
|
|
||||||
|
|
||||||
<bean id="grailsApplication" class="org.codehaus.groovy.grails.commons.GrailsApplicationFactoryBean">
|
<bean id="grailsApplication" class="org.codehaus.groovy.grails.commons.GrailsApplicationFactoryBean">
|
||||||
<description>Grails application factory bean</description>
|
<description>Grails application factory bean</description>
|
||||||
<property name="grailsDescriptor" value="/WEB-INF/grails.xml" />
|
<property name="grailsDescriptor" value="/WEB-INF/grails.xml" />
|
||||||
<property name="grailsResourceLoader" ref="grailsResourceLoader" />
|
<property name="grailsResourceLoader" ref="grailsResourceLoader" />
|
||||||
</bean>
|
</bean>
|
||||||
|
|
||||||
<bean id="pluginManager" class="org.codehaus.groovy.grails.plugins.GrailsPluginManagerFactoryBean">
|
<bean id="pluginManager" class="org.codehaus.groovy.grails.plugins.GrailsPluginManagerFactoryBean">
|
||||||
<description>A bean that manages Grails plugins</description>
|
<description>A bean that manages Grails plugins</description>
|
||||||
<property name="grailsDescriptor" value="/WEB-INF/grails.xml" />
|
<property name="grailsDescriptor" value="/WEB-INF/grails.xml" />
|
||||||
<property name="application" ref="grailsApplication" />
|
<property name="application" ref="grailsApplication" />
|
||||||
</bean>
|
</bean>
|
||||||
|
|
||||||
<bean id="pluginMetaManager" class="org.codehaus.groovy.grails.plugins.DefaultPluginMetaManager">
|
<bean id="grailsConfigurator" class="org.codehaus.groovy.grails.commons.spring.GrailsRuntimeConfigurator">
|
||||||
<property name="grailsApplication" ref="grailsApplication" />
|
<constructor-arg>
|
||||||
<property name="resourcePattern" value="/WEB-INF/plugins/*/plugin.xml" />
|
<ref bean="grailsApplication" />
|
||||||
</bean>
|
</constructor-arg>
|
||||||
|
<property name="pluginManager" ref="pluginManager" />
|
||||||
|
</bean>
|
||||||
|
|
||||||
<bean id="grailsConfigurator" class="org.codehaus.groovy.grails.commons.spring.GrailsRuntimeConfigurator">
|
<bean id="grailsResourceLoader" class="org.codehaus.groovy.grails.commons.GrailsResourceLoaderFactoryBean" />
|
||||||
<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="characterEncodingFilter" class="org.springframework.web.filter.CharacterEncodingFilter">
|
||||||
<property name="grailsResourceHolder" ref="grailsResourceHolder" />
|
<property name="encoding">
|
||||||
</bean>
|
<value>utf-8</value>
|
||||||
|
</property>
|
||||||
|
</bean>
|
||||||
|
|
||||||
<bean id="grailsResourceHolder" scope="prototype" class="org.codehaus.groovy.grails.commons.spring.GrailsResourceHolder">
|
<bean id="conversionService" class="org.springframework.context.support.ConversionServiceFactoryBean" />
|
||||||
<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>
|
|
||||||
</beans>
|
</beans>
|
@ -1,11 +1,11 @@
|
|||||||
<sitemesh>
|
<sitemesh>
|
||||||
<page-parsers>
|
<page-parsers>
|
||||||
<parser content-type="text/html"
|
<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"
|
<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"
|
<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>
|
</page-parsers>
|
||||||
|
|
||||||
<decorator-mappers>
|
<decorator-mappers>
|
||||||
|
@ -1,13 +1,13 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8" ?>
|
<?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"
|
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"
|
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-jsptaglibrary_2_1.xsd"
|
||||||
version="2.0">
|
version="2.1">
|
||||||
|
|
||||||
<description>JSTL 1.1 core library</description>
|
<description>JSTL 1.2 core library</description>
|
||||||
<display-name>JSTL core</display-name>
|
<display-name>JSTL core</display-name>
|
||||||
<tlib-version>1.1</tlib-version>
|
<tlib-version>1.2</tlib-version>
|
||||||
<short-name>c</short-name>
|
<short-name>c</short-name>
|
||||||
<uri>http://java.sun.com/jsp/jstl/core</uri>
|
<uri>http://java.sun.com/jsp/jstl/core</uri>
|
||||||
|
|
||||||
@ -74,7 +74,7 @@ not the body content should be processed.
|
|||||||
<description>
|
<description>
|
||||||
Name of the exported scoped variable for the
|
Name of the exported scoped variable for the
|
||||||
resulting value of the test condition. The type
|
resulting value of the test condition. The type
|
||||||
of the scoped variable is Boolean.
|
of the scoped variable is Boolean.
|
||||||
</description>
|
</description>
|
||||||
<name>var</name>
|
<name>var</name>
|
||||||
<required>false</required>
|
<required>false</required>
|
||||||
@ -174,6 +174,9 @@ Collection of items to iterate over.
|
|||||||
<required>false</required>
|
<required>false</required>
|
||||||
<rtexprvalue>true</rtexprvalue>
|
<rtexprvalue>true</rtexprvalue>
|
||||||
<type>java.lang.Object</type>
|
<type>java.lang.Object</type>
|
||||||
|
<deferred-value>
|
||||||
|
<type>java.lang.Object</type>
|
||||||
|
</deferred-value>
|
||||||
</attribute>
|
</attribute>
|
||||||
<attribute>
|
<attribute>
|
||||||
<description>
|
<description>
|
||||||
@ -253,6 +256,9 @@ String of tokens to iterate over.
|
|||||||
<required>true</required>
|
<required>true</required>
|
||||||
<rtexprvalue>true</rtexprvalue>
|
<rtexprvalue>true</rtexprvalue>
|
||||||
<type>java.lang.String</type>
|
<type>java.lang.String</type>
|
||||||
|
<deferred-value>
|
||||||
|
<type>java.lang.String</type>
|
||||||
|
</deferred-value>
|
||||||
</attribute>
|
</attribute>
|
||||||
<attribute>
|
<attribute>
|
||||||
<description>
|
<description>
|
||||||
@ -322,7 +328,7 @@ visibility.
|
|||||||
<tag>
|
<tag>
|
||||||
<description>
|
<description>
|
||||||
Like <%= ... >, but for expressions.
|
Like <%= ... >, but for expressions.
|
||||||
</description>
|
</description>
|
||||||
<name>out</name>
|
<name>out</name>
|
||||||
<tag-class>org.apache.taglibs.standard.tag.rt.core.OutTag</tag-class>
|
<tag-class>org.apache.taglibs.standard.tag.rt.core.OutTag</tag-class>
|
||||||
<body-content>JSP</body-content>
|
<body-content>JSP</body-content>
|
||||||
@ -467,6 +473,9 @@ Expression to be evaluated.
|
|||||||
<name>value</name>
|
<name>value</name>
|
||||||
<required>false</required>
|
<required>false</required>
|
||||||
<rtexprvalue>true</rtexprvalue>
|
<rtexprvalue>true</rtexprvalue>
|
||||||
|
<deferred-value>
|
||||||
|
<type>java.lang.Object</type>
|
||||||
|
</deferred-value>
|
||||||
</attribute>
|
</attribute>
|
||||||
<attribute>
|
<attribute>
|
||||||
<description>
|
<description>
|
||||||
|
@ -1,13 +1,13 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8" ?>
|
<?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"
|
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"
|
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-jsptaglibrary_2_1.xsd"
|
||||||
version="2.0">
|
version="2.1">
|
||||||
|
|
||||||
<description>JSTL 1.1 i18n-capable formatting library</description>
|
<description>JSTL 1.2 i18n-capable formatting library</description>
|
||||||
<display-name>JSTL fmt</display-name>
|
<display-name>JSTL fmt</display-name>
|
||||||
<tlib-version>1.1</tlib-version>
|
<tlib-version>1.2</tlib-version>
|
||||||
<short-name>fmt</short-name>
|
<short-name>fmt</short-name>
|
||||||
<uri>http://java.sun.com/jsp/jstl/fmt</uri>
|
<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).
|
country code (as defined by ISO-3166).
|
||||||
Language and country codes must be
|
Language and country codes must be
|
||||||
separated by hyphen (-) or underscore
|
separated by hyphen (-) or underscore
|
||||||
(_).
|
(_).
|
||||||
</description>
|
</description>
|
||||||
<name>value</name>
|
<name>value</name>
|
||||||
<required>true</required>
|
<required>true</required>
|
||||||
@ -496,7 +496,7 @@ Date and/or time to be formatted.
|
|||||||
<description>
|
<description>
|
||||||
Specifies whether the time, the date, or both
|
Specifies whether the time, the date, or both
|
||||||
the time and date components of the given
|
the time and date components of the given
|
||||||
date are to be formatted.
|
date are to be formatted.
|
||||||
</description>
|
</description>
|
||||||
<name>type</name>
|
<name>type</name>
|
||||||
<required>false</required>
|
<required>false</required>
|
||||||
|
10270
bbb-lti/web-app/css/bootstrap.css
vendored
10270
bbb-lti/web-app/css/bootstrap.css
vendored
File diff suppressed because it is too large
Load Diff
109
bbb-lti/web-app/css/errors.css
Normal file
109
bbb-lti/web-app/css/errors.css
Normal 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;
|
||||||
|
}
|
@ -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 * {
|
html * {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
/*padding: 0; SELECT NOT DISPLAYED CORRECTLY IN FIREFOX */
|
}
|
||||||
|
|
||||||
|
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 */
|
/* 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 {
|
.spinner {
|
||||||
padding: 5px;
|
background: url(../images/spinner.gif) 50% 50% no-repeat transparent;
|
||||||
|
height: 16px;
|
||||||
|
width: 16px;
|
||||||
|
padding: 0.5em;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
right: 0;
|
right: 0;
|
||||||
}
|
top: 0;
|
||||||
|
text-indent: -9999px;
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* NAVIGATION MENU */
|
/* NAVIGATION MENU */
|
||||||
|
|
||||||
.nav {
|
.nav {
|
||||||
background: #fff url(../images/skin/shadow.jpg) bottom repeat-x;
|
background-color: #efefef;
|
||||||
border: 1px solid #ccc;
|
padding: 0.5em 0.75em;
|
||||||
border-style: solid none solid none;
|
-moz-box-shadow: 0 0 3px 1px #aaaaaa;
|
||||||
margin-top: 5px;
|
-webkit-box-shadow: 0 0 3px 1px #aaaaaa;
|
||||||
padding: 7px 12px;
|
box-shadow: 0 0 3px 1px #aaaaaa;
|
||||||
|
zoom: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
.menuButton {
|
.nav ul {
|
||||||
font-size: 10px;
|
overflow: hidden;
|
||||||
padding: 0 5px;
|
padding-left: 0;
|
||||||
|
zoom: 1;
|
||||||
}
|
}
|
||||||
.menuButton a {
|
|
||||||
color: #333;
|
.nav li {
|
||||||
padding: 4px 6px;
|
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;
|
.nav a {
|
||||||
color: #333;
|
color: #666666;
|
||||||
padding-left: 25px;
|
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;
|
.nav a:active, .nav a:visited {
|
||||||
color: #333;
|
color: #666666;
|
||||||
padding-left: 25px;
|
|
||||||
}
|
}
|
||||||
.menuButton a.create {
|
|
||||||
background: url(../images/skin/database_add.png) center left no-repeat;
|
.nav a:focus, .nav a:hover {
|
||||||
color: #333;
|
background-color: #999999;
|
||||||
padding-left: 25px;
|
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 */
|
/* MESSAGES AND ERRORS */
|
||||||
|
|
||||||
|
.errors,
|
||||||
.message {
|
.message {
|
||||||
background: #f3f8fc url(../images/skin/information.png) 8px 50% no-repeat;
|
font-size: 0.8em;
|
||||||
border: 1px solid #b2d1ff;
|
line-height: 2;
|
||||||
color: #006dba;
|
margin: 1em 2em;
|
||||||
margin: 10px 0 5px 0;
|
padding: 0.25em;
|
||||||
padding: 5px 5px 5px 30px
|
|
||||||
}
|
}
|
||||||
|
|
||||||
div.errors {
|
.message {
|
||||||
background: #fff3f3;
|
background: #f3f3ff;
|
||||||
border: 1px solid red;
|
border: 1px solid #b2d1ff;
|
||||||
color: #cc0000;
|
color: #006dba;
|
||||||
margin: 10px 0 5px 0;
|
-moz-box-shadow: 0 0 0.25em #b2d1ff;
|
||||||
padding: 5px 0 5px 0;
|
-webkit-box-shadow: 0 0 0.25em #b2d1ff;
|
||||||
}
|
box-shadow: 0 0 0.25em #b2d1ff;
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
td.errors select {
|
.errors {
|
||||||
border: 1px solid red;
|
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 */
|
/* TABLES */
|
||||||
|
|
||||||
table {
|
table {
|
||||||
border: 1px solid #ccc;
|
border-top: 1px solid #DFDFDF;
|
||||||
width: 100%
|
border-collapse: collapse;
|
||||||
|
width: 100%;
|
||||||
|
margin-bottom: 1em;
|
||||||
}
|
}
|
||||||
|
|
||||||
tr {
|
tr {
|
||||||
border: 0;
|
border: 0;
|
||||||
}
|
}
|
||||||
td, th {
|
|
||||||
font: 11px verdana, arial, helvetica, sans-serif;
|
tr>td:first-child, tr>th:first-child {
|
||||||
line-height: 12px;
|
padding-left: 1.25em;
|
||||||
padding: 5px 6px;
|
|
||||||
text-align: left;
|
|
||||||
vertical-align: top;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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 {
|
th {
|
||||||
background: #fff url(../images/skin/shadow.jpg);
|
background-color: #efefef;
|
||||||
color: #666;
|
background-image: -moz-linear-gradient(top, #ffffff, #eaeaea);
|
||||||
font-size: 11px;
|
background-image: -webkit-gradient(linear, left top, left bottom, color-stop(0, #ffffff), color-stop(1, #eaeaea));
|
||||||
font-weight: bold;
|
filter: progid:DXImageTransform.Microsoft.gradient(startColorStr = '#ffffff', EndColorStr = '#eaeaea');
|
||||||
line-height: 17px;
|
-ms-filter: "progid:DXImageTransform.Microsoft.gradient(startColorStr='#ffffff', EndColorStr='#eaeaea')";
|
||||||
padding: 2px 6px;
|
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;
|
thead th {
|
||||||
display: block;
|
white-space: nowrap;
|
||||||
font-size: 10px;
|
|
||||||
text-decoration: none;
|
|
||||||
width: 100%;
|
|
||||||
}
|
}
|
||||||
th.asc a, th.desc a {
|
|
||||||
background-position: right;
|
th a {
|
||||||
background-repeat: no-repeat;
|
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 {
|
th.asc a {
|
||||||
background-image: url(../images/skin/sorted_asc.gif);
|
background-image: url(../images/skin/sorted_asc.gif);
|
||||||
}
|
}
|
||||||
|
|
||||||
th.desc a {
|
th.desc a {
|
||||||
background-image: url(../images/skin/sorted_desc.gif);
|
background-image: url(../images/skin/sorted_desc.gif);
|
||||||
}
|
}
|
||||||
|
|
||||||
.odd {
|
.odd {
|
||||||
background: #f7f7f7;
|
background: #f7f7f7;
|
||||||
}
|
}
|
||||||
|
|
||||||
.even {
|
.even {
|
||||||
background: #fff;
|
background: #ffffff;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* LIST */
|
th:hover, tr:hover {
|
||||||
|
background: #E1F2B6;
|
||||||
.list table {
|
|
||||||
border-collapse: collapse;
|
|
||||||
}
|
|
||||||
.list th, .list td {
|
|
||||||
border-left: 1px solid #ddd;
|
|
||||||
}
|
|
||||||
.list th:hover, .list tr:hover {
|
|
||||||
background: #b2d1ff;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* PAGINATION */
|
/* PAGINATION */
|
||||||
|
|
||||||
.paginateButtons {
|
.pagination {
|
||||||
background: #fff url(../images/skin/shadow.jpg) bottom repeat-x;
|
border-top: 0;
|
||||||
border: 1px solid #ccc;
|
margin: 0;
|
||||||
border-top: 0;
|
padding: 0.3em 0.2em;
|
||||||
color: #666;
|
text-align: center;
|
||||||
font-size: 10px;
|
-moz-box-shadow: 0 0 3px 1px #AAAAAA;
|
||||||
overflow: hidden;
|
-webkit-box-shadow: 0 0 3px 1px #AAAAAA;
|
||||||
padding: 10px 3px;
|
box-shadow: 0 0 3px 1px #AAAAAA;
|
||||||
}
|
background-color: #EFEFEF;
|
||||||
.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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* DIALOG */
|
.pagination a,
|
||||||
|
.pagination .currentStep {
|
||||||
.dialog table {
|
color: #666666;
|
||||||
padding: 5px 0;
|
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 {
|
.pagination a:hover, .pagination a:focus,
|
||||||
padding: 5px;
|
.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;
|
.no-borderradius .pagination a:hover, .no-borderradius .pagination a:focus,
|
||||||
width: 15%;
|
.no-borderradius .pagination .currentStep {
|
||||||
white-space: nowrap;
|
background-color: transparent;
|
||||||
}
|
color: #444444;
|
||||||
.prop .value {
|
text-decoration: underline;
|
||||||
text-align: left;
|
|
||||||
width: 85%;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ACTION BUTTONS */
|
/* ACTION BUTTONS */
|
||||||
|
|
||||||
.buttons {
|
.buttons {
|
||||||
background: #fff url(../images/skin/shadow.jpg) bottom repeat-x;
|
background-color: #efefef;
|
||||||
border: 1px solid #ccc;
|
overflow: hidden;
|
||||||
color: #666;
|
padding: 0.3em;
|
||||||
font-size: 10px;
|
-moz-box-shadow: 0 0 3px 1px #aaaaaa;
|
||||||
margin-top: 5px;
|
-webkit-box-shadow: 0 0 3px 1px #aaaaaa;
|
||||||
overflow: hidden;
|
box-shadow: 0 0 3px 1px #aaaaaa;
|
||||||
padding: 0;
|
margin: 0.1em 0 0 0;
|
||||||
|
border: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.buttons input {
|
.buttons input,
|
||||||
background: #fff;
|
.buttons a {
|
||||||
border: 0;
|
background-color: transparent;
|
||||||
color: #333;
|
border: 0;
|
||||||
cursor: pointer;
|
color: #666666;
|
||||||
font-size: 10px;
|
cursor: pointer;
|
||||||
font-weight: bold;
|
display: inline-block;
|
||||||
margin-left: 3px;
|
margin: 0 0.25em 0;
|
||||||
overflow: visible;
|
overflow: visible;
|
||||||
padding: 2px 6px;
|
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;
|
.buttons input:hover, .buttons input:focus,
|
||||||
padding-left: 28px;
|
.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;
|
.no-borderradius .buttons input:hover, .no-borderradius .buttons input:focus,
|
||||||
padding-left: 28px;
|
.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;
|
.buttons .delete, .buttons .edit, .buttons .save {
|
||||||
padding-left: 28px;
|
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;
|
||||||
}
|
}
|
||||||
|
82
bbb-lti/web-app/css/mobile.css
Normal file
82
bbb-lti/web-app/css/mobile.css
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
BIN
bbb-lti/web-app/fonts/glyphicons-halflings-regular.eot
Normal file
BIN
bbb-lti/web-app/fonts/glyphicons-halflings-regular.eot
Normal file
Binary file not shown.
229
bbb-lti/web-app/fonts/glyphicons-halflings-regular.svg
Normal file
229
bbb-lti/web-app/fonts/glyphicons-halflings-regular.svg
Normal 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="
" />
|
||||||
|
<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=" " />
|
||||||
|
<glyph unicode=" " horiz-adv-x="652" />
|
||||||
|
<glyph unicode=" " horiz-adv-x="1304" />
|
||||||
|
<glyph unicode=" " horiz-adv-x="652" />
|
||||||
|
<glyph unicode=" " horiz-adv-x="1304" />
|
||||||
|
<glyph unicode=" " horiz-adv-x="434" />
|
||||||
|
<glyph unicode=" " horiz-adv-x="326" />
|
||||||
|
<glyph unicode=" " horiz-adv-x="217" />
|
||||||
|
<glyph unicode=" " horiz-adv-x="217" />
|
||||||
|
<glyph unicode=" " horiz-adv-x="163" />
|
||||||
|
<glyph unicode=" " horiz-adv-x="260" />
|
||||||
|
<glyph unicode=" " horiz-adv-x="72" />
|
||||||
|
<glyph unicode=" " horiz-adv-x="260" />
|
||||||
|
<glyph unicode=" " horiz-adv-x="326" />
|
||||||
|
<glyph unicode="€" 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="−" d="M200 400h900v300h-900v-300z" />
|
||||||
|
<glyph unicode="◼" horiz-adv-x="500" d="M0 0z" />
|
||||||
|
<glyph unicode="☁" 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="✉" d="M0 100l400 400l200 -200l200 200l400 -400h-1200zM0 300v600l300 -300zM0 1100l600 -603l600 603h-1200zM900 600l300 300v-600z" />
|
||||||
|
<glyph unicode="✏" 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="" d="M0 1200h1200l-500 -550v-550h300v-100h-800v100h300v550z" />
|
||||||
|
<glyph unicode="" 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="" 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="" 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="" d="M-72 800h479l146 400h2l146 -400h472l-382 -278l145 -449l-384 275l-382 -275l146 447zM168 71l2 1z" />
|
||||||
|
<glyph unicode="" 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="" 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="" 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="" 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="" 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="" 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="" d="M29 454l419 -420l818 820l-212 212l-607 -607l-206 207z" />
|
||||||
|
<glyph unicode="" d="M106 318l282 282l-282 282l212 212l282 -282l282 282l212 -212l-282 -282l282 -282l-212 -212l-282 282l-282 -282z" />
|
||||||
|
<glyph unicode="" 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="" 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="" 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="" d="M100 1h200v300h-200v-300zM400 1v500h200v-500h-200zM700 1v800h200v-800h-200zM1000 1v1200h200v-1200h-200z" />
|
||||||
|
<glyph unicode="" 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="" 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="" d="M1 601l656 644l644 -644h-200v-600h-300v400h-300v-400h-300v600h-200z" />
|
||||||
|
<glyph unicode="" 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="" 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="" d="M-100 0l431 1200h209l-21 -300h162l-20 300h208l431 -1200h-538l-41 400h-242l-40 -400h-539zM488 500h224l-27 300h-170z" />
|
||||||
|
<glyph unicode="" d="M0 0v400h490l-290 300h200v500h300v-500h200l-290 -300h490v-400h-1100zM813 200h175v100h-175v-100z" />
|
||||||
|
<glyph unicode="" 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="" 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="" 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="" 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="" 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="" 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="" 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="" 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="" 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="" 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="" 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="" 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="" 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="" 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="" 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="" 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="" 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="" d="M100 0v1025l175 175h925v-1000l-100 -100v1000h-750l-100 -100h750v-1000h-900z" />
|
||||||
|
<glyph unicode="" d="M200 0l450 444l450 -443v1150q0 20 -14.5 35t-35.5 15h-800q-21 0 -35.5 -15t-14.5 -35v-1151z" />
|
||||||
|
<glyph unicode="" 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="" 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="" 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="" 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="" 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="" 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="" 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="" 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="" 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="" 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="" 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="" 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="" 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="" 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="" 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="" 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="" 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="" 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="" 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="" 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="" 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="" 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="" d="M0 603l300 296v-198h200v200h-200l300 300l295 -300h-195v-200h200v198l300 -296l-300 -300v198h-200v-200h195l-295 -300l-300 300h200v200h-200v-198z" />
|
||||||
|
<glyph unicode="" 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="" 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="" d="M136 550l564 550v-487l500 487v-1100l-500 488v-488z" />
|
||||||
|
<glyph unicode="" d="M200 0l900 550l-900 550v-1100z" />
|
||||||
|
<glyph unicode="" 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="" 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="" d="M0 0v1100l500 -487v487l564 -550l-564 -550v488z" />
|
||||||
|
<glyph unicode="" 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="" 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="" 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="" d="M185 599l592 -592l240 240l-353 353l353 353l-240 240z" />
|
||||||
|
<glyph unicode="" d="M272 194l353 353l-353 353l241 240l572 -571l21 -22l-1 -1v-1l-592 -591z" />
|
||||||
|
<glyph unicode="" 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="" 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="" 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="" 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="" 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="" 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="" 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="" 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="" 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="" 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="" d="M0 547l600 453v-300h600v-300h-600v-301z" />
|
||||||
|
<glyph unicode="" d="M0 400v300h600v300l600 -453l-600 -448v301h-600z" />
|
||||||
|
<glyph unicode="" d="M204 600l450 600l444 -600h-298v-600h-300v600h-296z" />
|
||||||
|
<glyph unicode="" d="M104 600h296v600h300v-600h298l-449 -600z" />
|
||||||
|
<glyph unicode="" 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="" d="M0 0v400l129 -129l294 294l142 -142l-294 -294l129 -129h-400zM635 777l142 -142l294 294l129 -129v400h-400l129 -129z" />
|
||||||
|
<glyph unicode="" d="M34 176l295 295l-129 129h400v-400l-129 130l-295 -295zM600 600v400l129 -129l295 295l142 -141l-295 -295l129 -130h-400z" />
|
||||||
|
<glyph unicode="" 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="" 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="" 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="" 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="" 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="" 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="" 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="" 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="" 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="" 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="" 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="" 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="" d="M-30 411l227 -227l352 353l353 -353l226 227l-578 579z" />
|
||||||
|
<glyph unicode="" d="M70 797l580 -579l578 579l-226 227l-353 -353l-352 353z" />
|
||||||
|
<glyph unicode="" d="M-198 700l299 283l300 -283h-203v-400h385l215 -200h-800v600h-196zM402 1000l215 -200h381v-400h-198l299 -283l299 283h-200v600h-796z" />
|
||||||
|
<glyph unicode="" 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="" 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="" 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="" d="M302 300h198v600h-198l298 300l298 -300h-198v-600h198l-298 -300z" />
|
||||||
|
<glyph unicode="" d="M0 600l300 298v-198h600v198l300 -298l-300 -297v197h-600v-197z" />
|
||||||
|
<glyph unicode="" 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="" 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="" 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="" 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="" 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="" 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="" 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="" 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="" 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="" 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="" 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="" 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="" 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="" 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="" 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="" 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="" 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="" d="M100 1100v100h1000v-100h-1000zM150 1000h900l-350 -500v-300l-200 -200v500z" />
|
||||||
|
<glyph unicode="" 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="" 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="" 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="" 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="" 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="" 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="" 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="" 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="" 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="" 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="" d="M2 300l298 -300l298 300h-198v900h-200v-900h-198zM602 900l298 300l298 -300h-198v-900h-200v900h-198z" />
|
||||||
|
<glyph unicode="" 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="" 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="" d="M2 300l298 -300l298 300h-198v900h-200v-900h-198zM800 100v400h300v-500h-100v100h-200zM800 1100v100h200v-500h-100v400h-100zM901 200h100v200h-100v-200z" />
|
||||||
|
<glyph unicode="" d="M2 300l298 -300l298 300h-198v900h-200v-900h-198zM800 400v100h200v-500h-100v400h-100zM800 800v400h300v-500h-100v100h-200zM901 900h100v200h-100v-200z" />
|
||||||
|
<glyph unicode="" d="M2 300l298 -300l298 300h-198v900h-200v-900h-198zM700 100v200h500v-200h-500zM700 400v200h400v-200h-400zM700 700v200h300v-200h-300zM700 1000v200h200v-200h-200z" />
|
||||||
|
<glyph unicode="" d="M2 300l298 -300l298 300h-198v900h-200v-900h-198zM700 100v200h200v-200h-200zM700 400v200h300v-200h-300zM700 700v200h400v-200h-400zM700 1000v200h500v-200h-500z" />
|
||||||
|
<glyph unicode="" 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="" 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="" 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="" 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="" 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="" 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="" 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="" 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="" 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="" 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="" d="M0 0v275q0 11 7 18t18 7h1048q11 0 19 -7.5t8 -17.5v-275h-1100zM100 700h300v-300h300v300h295l-445 500zM900 150h100v50h-100v-50z" />
|
||||||
|
<glyph unicode="" 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="" 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="" 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="" d="M23 415l1177 784v-1079l-475 272l-310 -393v416h-392zM494 210l672 938l-672 -712v-226z" />
|
||||||
|
<glyph unicode="" 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="" 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="" 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="" 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="" 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="" 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="" 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="" 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="" 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="" 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="" 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="" 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="" d="M100 0v100h1100v-100h-1100zM175 200h950l-125 150v250l100 100v400h-100v-200h-100v200h-200v-200h-100v200h-200v-200h-100v200h-100v-400l100 -100v-250z" />
|
||||||
|
<glyph unicode="" 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="" 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="" 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="" 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="" 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="" 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="" 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="" 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="" 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="" 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="" 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="" 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="" 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="" d="M100 200h400v-155l-75 -45h350l-75 45v155h400l-270 300h170l-270 300h170l-300 333l-300 -333h170l-270 -300h170z" />
|
||||||
|
<glyph unicode="" 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 |
BIN
bbb-lti/web-app/fonts/glyphicons-halflings-regular.ttf
Normal file
BIN
bbb-lti/web-app/fonts/glyphicons-halflings-regular.ttf
Normal file
Binary file not shown.
BIN
bbb-lti/web-app/fonts/glyphicons-halflings-regular.woff
Normal file
BIN
bbb-lti/web-app/fonts/glyphicons-halflings-regular.woff
Normal file
Binary file not shown.
BIN
bbb-lti/web-app/images/apple-touch-icon-retina.png
Normal file
BIN
bbb-lti/web-app/images/apple-touch-icon-retina.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 15 KiB |
BIN
bbb-lti/web-app/images/apple-touch-icon.png
Normal file
BIN
bbb-lti/web-app/images/apple-touch-icon.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 5.3 KiB |
BIN
bbb-lti/web-app/images/favicon.ico
Normal file
BIN
bbb-lti/web-app/images/favicon.ico
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.1 KiB |
BIN
bbb-lti/web-app/images/grails_logo.png
Normal file
BIN
bbb-lti/web-app/images/grails_logo.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 9.9 KiB |
BIN
bbb-lti/web-app/images/springsource.png
Normal file
BIN
bbb-lti/web-app/images/springsource.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 8.9 KiB |
@ -1,13 +1,9 @@
|
|||||||
var Ajax;
|
if (typeof jQuery !== 'undefined') {
|
||||||
if (Ajax && (Ajax != null)) {
|
(function($) {
|
||||||
Ajax.Responders.register({
|
$('#spinner').ajaxStart(function() {
|
||||||
onCreate: function() {
|
$(this).fadeIn();
|
||||||
if($('spinner') && Ajax.activeRequestCount>0)
|
}).ajaxStop(function() {
|
||||||
Effect.Appear('spinner',{duration:0.5,queue:'end'});
|
$(this).fadeOut();
|
||||||
},
|
});
|
||||||
onComplete: function() {
|
})(jQuery);
|
||||||
if($('spinner') && Ajax.activeRequestCount==0)
|
|
||||||
Effect.Fade('spinner',{duration:0.5,queue:'end'});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
1951
bbb-lti/web-app/js/bootstrap.js
vendored
Normal file
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
57
bbb-video/build.gradle
Executable file → Normal file
@ -1,27 +1,15 @@
|
|||||||
usePlugin 'java'
|
apply plugin: 'java'
|
||||||
usePlugin 'war'
|
apply plugin: 'war'
|
||||||
usePlugin 'eclipse'
|
apply plugin: 'eclipse'
|
||||||
|
|
||||||
version = '0.7'
|
version = '0.7'
|
||||||
jar.enabled = true
|
jar.enabled = true
|
||||||
archivesBaseName = 'video'
|
archivesBaseName = 'video'
|
||||||
|
|
||||||
task dependencies(type: Copy) {
|
task resolveDeps(type: Copy) {
|
||||||
into('lib')
|
into('lib')
|
||||||
from configurations.default
|
from configurations.default
|
||||||
from configurations.default.allArtifacts*.file
|
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
|
|
||||||
}
|
}
|
||||||
|
|
||||||
repositories {
|
repositories {
|
||||||
@ -61,8 +49,14 @@ repositories {
|
|||||||
m2compatible = true
|
m2compatible = true
|
||||||
addArtifactPattern "http://repository.springsource.com/maven/bundles/external/[organisation]/[module]/[revision]/[artifact]-[revision].[ext]"
|
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]"
|
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 {
|
dependencies {
|
||||||
@ -75,25 +69,26 @@ dependencies {
|
|||||||
providedCompile 'org.apache.mina:mina-integration-jmx:2.0.7@jar'
|
providedCompile 'org.apache.mina:mina-integration-jmx:2.0.7@jar'
|
||||||
|
|
||||||
// Spring
|
// Spring
|
||||||
providedCompile 'org.springframework:spring-web:3.1.1.RELEASE@jar'
|
providedCompile 'org.springframework:spring-web:4.0.3.RELEASE@jar'
|
||||||
providedCompile 'org.springframework:spring-beans:3.1.1.RELEASE@jar'
|
providedCompile 'org.springframework:spring-beans:4.0.3.RELEASE@jar'
|
||||||
providedCompile 'org.springframework:spring-context:3.1.1.RELEASE@jar'
|
providedCompile 'org.springframework:spring-context:4.0.3.RELEASE@jar'
|
||||||
providedCompile 'org.springframework:spring-core:3.1.1.RELEASE@jar'
|
providedCompile 'org.springframework:spring-core:4.0.3.RELEASE@jar'
|
||||||
|
|
||||||
// Red5
|
// 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
|
// Logging
|
||||||
providedCompile 'ch.qos.logback:logback-core:1.0.9@jar'
|
providedCompile 'ch.qos.logback:logback-core:1.0.13@jar'
|
||||||
providedCompile 'ch.qos.logback:logback-classic:1.0.9@jar'
|
providedCompile 'ch.qos.logback:logback-classic:1.0.13@jar'
|
||||||
providedCompile 'org.slf4j:log4j-over-slf4j:1.7.2@jar'
|
providedCompile 'org.slf4j:log4j-over-slf4j:1.7.5@jar'
|
||||||
providedCompile 'org.slf4j:jcl-over-slf4j:1.7.2@jar'
|
providedCompile 'org.slf4j:jcl-over-slf4j:1.7.5@jar'
|
||||||
providedCompile 'org.slf4j:jul-to-slf4j:1.7.2@jar'
|
providedCompile 'org.slf4j:jul-to-slf4j:1.7.5@jar'
|
||||||
providedCompile 'org.slf4j:slf4j-api:1.7.2@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.
|
// 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.
|
// 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'
|
compile 'aopalliance:aopalliance:1.0@jar'
|
||||||
|
|
||||||
// Java Concurrency In Practice
|
// Java Concurrency In Practice
|
||||||
|
0
bbb-video/src/main/java/org/bigbluebutton/app/video/VideoStreamListener.java
Executable file → Normal file
0
bbb-video/src/main/java/org/bigbluebutton/app/video/VideoStreamListener.java
Executable file → Normal file
@ -123,6 +123,7 @@
|
|||||||
<param name="caller-id-name" value="$${outbound_caller_name}"/>
|
<param name="caller-id-name" value="$${outbound_caller_name}"/>
|
||||||
<param name="caller-id-number" value="$${outbound_caller_id}"/>
|
<param name="caller-id-number" value="$${outbound_caller_id}"/>
|
||||||
<param name="comfort-noise" value="true"/>
|
<param name="comfort-noise" value="true"/>
|
||||||
|
<param name="min-required-recording-participants" value="1"/>
|
||||||
<!--<param name="tts-engine" value="flite"/>-->
|
<!--<param name="tts-engine" value="flite"/>-->
|
||||||
<!--<param name="tts-voice" value="kal16"/>-->
|
<!--<param name="tts-voice" value="kal16"/>-->
|
||||||
</profile>
|
</profile>
|
||||||
|
70
bbb-voice/build.gradle
Executable file → Normal file
70
bbb-voice/build.gradle
Executable file → Normal file
@ -1,27 +1,15 @@
|
|||||||
usePlugin 'java'
|
apply plugin: 'java'
|
||||||
usePlugin 'war'
|
apply plugin: 'war'
|
||||||
usePlugin 'eclipse'
|
apply plugin: 'eclipse'
|
||||||
|
|
||||||
version = '0.7'
|
version = '0.7'
|
||||||
jar.enabled = true
|
jar.enabled = true
|
||||||
archivesBaseName = 'sip'
|
archivesBaseName = 'sip'
|
||||||
|
|
||||||
task dependencies(type: Copy) {
|
task resolveDeps(type: Copy) {
|
||||||
into('lib')
|
into('lib')
|
||||||
from configurations.default
|
from configurations.default
|
||||||
from configurations.default.allArtifacts*.file
|
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
|
|
||||||
}
|
}
|
||||||
|
|
||||||
repositories {
|
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/external/[organisation]/[module]/[revision]/[artifact]-[revision].[ext]"
|
||||||
addArtifactPattern "http://repository.springsource.com/maven/bundles/release/[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 {
|
dependencies {
|
||||||
@ -74,26 +68,27 @@ dependencies {
|
|||||||
providedCompile 'org.apache.mina:mina-integration-beans: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'
|
providedCompile 'org.apache.mina:mina-integration-jmx:2.0.7@jar'
|
||||||
|
|
||||||
// Spring
|
// Spring
|
||||||
providedCompile 'org.springframework:spring-web:3.1.1.RELEASE@jar'
|
providedCompile 'org.springframework:spring-web:4.0.3.RELEASE@jar'
|
||||||
providedCompile 'org.springframework:spring-beans:3.1.1.RELEASE@jar'
|
providedCompile 'org.springframework:spring-beans:4.0.3.RELEASE@jar'
|
||||||
providedCompile 'org.springframework:spring-context:3.1.1.RELEASE@jar'
|
providedCompile 'org.springframework:spring-context:4.0.3.RELEASE@jar'
|
||||||
providedCompile 'org.springframework:spring-core:3.1.1.RELEASE@jar'
|
providedCompile 'org.springframework:spring-core:4.0.3.RELEASE@jar'
|
||||||
|
|
||||||
// Red5
|
// 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'
|
// Logging
|
||||||
providedCompile 'ch.qos.logback:logback-classic:1.0.9@jar'
|
providedCompile 'ch.qos.logback:logback-core:1.0.13@jar'
|
||||||
providedCompile 'org.slf4j:log4j-over-slf4j:1.7.2@jar'
|
providedCompile 'ch.qos.logback:logback-classic:1.0.13@jar'
|
||||||
providedCompile 'org.slf4j:jcl-over-slf4j:1.7.2@jar'
|
providedCompile 'org.slf4j:log4j-over-slf4j:1.7.5@jar'
|
||||||
providedCompile 'org.slf4j:jul-to-slf4j:1.7.2@jar'
|
providedCompile 'org.slf4j:jcl-over-slf4j:1.7.5@jar'
|
||||||
providedCompile 'org.slf4j:slf4j-api:1.7.2@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.
|
// 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.
|
// 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'
|
compile 'aopalliance:aopalliance:1.0@jar'
|
||||||
|
|
||||||
// Java Concurrency In Practice
|
// Java Concurrency In Practice
|
||||||
@ -109,7 +104,10 @@ dependencies {
|
|||||||
|
|
||||||
compile 'javax/media:jmf:2.1.1e@jar'
|
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'
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
88
bbb-voice/src/main/java/org/bigbluebutton/voiceconf/messaging/Constants.java
Executable file
88
bbb-voice/src/main/java/org/bigbluebutton/voiceconf/messaging/Constants.java
Executable 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";
|
||||||
|
}
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,6 @@
|
|||||||
|
package org.bigbluebutton.voiceconf.messaging;
|
||||||
|
|
||||||
|
public interface IMessagingService {
|
||||||
|
void userConnectedToGlobalAudio(String voiceConf, String callerIdName);
|
||||||
|
void userDisconnectedFromGlobalAudio(String voiceConf, String callerIdName);
|
||||||
|
}
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -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);
|
||||||
|
}
|
@ -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.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
@ -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";
|
||||||
|
|
||||||
|
|
||||||
|
}
|
@ -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;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
@ -43,16 +43,22 @@ private static Logger log = Red5LoggerFactory.getLogger(ClientConnection.class,
|
|||||||
|
|
||||||
public void onJoinConferenceSuccess(String publishName, String playName, String codec) {
|
public void onJoinConferenceSuccess(String publishName, String playName, String codec) {
|
||||||
log.debug("Notify client that {} [{}] has joined the conference.", username, userid);
|
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() {
|
public void onJoinConferenceFail() {
|
||||||
log.debug("Notify client that {} [{}] failed to join the conference.", username, userid);
|
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() {
|
public void onLeaveConference() {
|
||||||
log.debug("Notify client that {} [{}] left the conference.", username, userid);
|
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"});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -25,6 +25,7 @@ import org.bigbluebutton.voiceconf.sip.SipPeerManager;
|
|||||||
import org.red5.logging.Red5LoggerFactory;
|
import org.red5.logging.Red5LoggerFactory;
|
||||||
import org.red5.server.api.IConnection;
|
import org.red5.server.api.IConnection;
|
||||||
import org.red5.server.api.Red5;
|
import org.red5.server.api.Red5;
|
||||||
|
import org.bigbluebutton.voiceconf.sip.GlobalCall;
|
||||||
|
|
||||||
public class Service {
|
public class Service {
|
||||||
private static Logger log = Red5LoggerFactory.getLogger(Service.class, "sip");
|
private static Logger log = Red5LoggerFactory.getLogger(Service.class, "sip");
|
||||||
@ -33,6 +34,27 @@ public class Service {
|
|||||||
|
|
||||||
private MessageFormat callExtensionPattern = new MessageFormat("{0}");
|
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) {
|
public Boolean call(String peerId, String callerName, String destination) {
|
||||||
String clientId = Red5.getConnectionLocal().getClient().getId();
|
String clientId = Red5.getConnectionLocal().getClient().getId();
|
||||||
String userid = getUserId();
|
String userid = getUserId();
|
||||||
|
@ -26,7 +26,8 @@ import org.red5.logging.Red5LoggerFactory;
|
|||||||
import org.red5.server.api.event.IEvent;
|
import org.red5.server.api.event.IEvent;
|
||||||
import org.red5.server.api.scope.IScope;
|
import org.red5.server.api.scope.IScope;
|
||||||
import org.red5.server.api.stream.IBroadcastStream;
|
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.IStreamListener;
|
||||||
import org.red5.server.api.stream.ResourceExistException;
|
import org.red5.server.api.stream.ResourceExistException;
|
||||||
import org.red5.server.api.stream.ResourceNotFoundException;
|
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.messaging.PipeConnectionEvent;
|
||||||
import org.red5.server.net.rtmp.event.IRTMPEvent;
|
import org.red5.server.net.rtmp.event.IRTMPEvent;
|
||||||
import org.red5.server.net.rtmp.event.Notify;
|
import org.red5.server.net.rtmp.event.Notify;
|
||||||
import org.red5.server.stream.codec.StreamCodecInfo;
|
|
||||||
import org.red5.server.stream.message.RTMPMessage;
|
import org.red5.server.stream.message.RTMPMessage;
|
||||||
|
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
|
|
||||||
import org.red5.server.api.stream.IStreamPacket;;
|
import org.red5.server.api.stream.IStreamPacket;;
|
||||||
|
|
||||||
public class AudioBroadcastStream implements IBroadcastStream, IProvider, IPipeConnectionListener {
|
public class AudioBroadcastStream implements IBroadcastStream, IProvider, IPipeConnectionListener {
|
||||||
|
@ -37,7 +37,7 @@ public class CallStream implements StreamObserver {
|
|||||||
|
|
||||||
private FlashToSipAudioStream userTalkStream;
|
private FlashToSipAudioStream userTalkStream;
|
||||||
private SipToFlashAudioStream userListenStream;
|
private SipToFlashAudioStream userListenStream;
|
||||||
private final Codec sipCodec;
|
public final Codec sipCodec;
|
||||||
private final SipConnectInfo connInfo;
|
private final SipConnectInfo connInfo;
|
||||||
private final IScope scope;
|
private final IScope scope;
|
||||||
private CallStreamObserver callStreamObserver;
|
private CallStreamObserver callStreamObserver;
|
||||||
@ -80,6 +80,10 @@ public class CallStream implements StreamObserver {
|
|||||||
public String getListenStreamName() {
|
public String getListenStreamName() {
|
||||||
return userListenStream.getStreamName();
|
return userListenStream.getStreamName();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Codec getSipCodec() {
|
||||||
|
return sipCodec;
|
||||||
|
}
|
||||||
|
|
||||||
public void startTalkStream(IBroadcastStream broadcastStream, IScope scope) throws StreamException {
|
public void startTalkStream(IBroadcastStream broadcastStream, IScope scope) throws StreamException {
|
||||||
log.debug("userTalkStream setup");
|
log.debug("userTalkStream setup");
|
||||||
|
@ -18,11 +18,14 @@
|
|||||||
*/
|
*/
|
||||||
package org.bigbluebutton.voiceconf.sip;
|
package org.bigbluebutton.voiceconf.sip;
|
||||||
|
|
||||||
|
import java.util.regex.Matcher;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
import org.zoolu.sip.call.*;
|
import org.zoolu.sip.call.*;
|
||||||
import org.zoolu.sip.provider.SipProvider;
|
import org.zoolu.sip.provider.SipProvider;
|
||||||
import org.zoolu.sip.provider.SipStack;
|
import org.zoolu.sip.provider.SipStack;
|
||||||
import org.zoolu.sip.message.*;
|
import org.zoolu.sip.message.*;
|
||||||
import org.zoolu.sdp.*;
|
import org.zoolu.sdp.*;
|
||||||
|
import org.bigbluebutton.voiceconf.messaging.IMessagingService;
|
||||||
import org.bigbluebutton.voiceconf.red5.CallStreamFactory;
|
import org.bigbluebutton.voiceconf.red5.CallStreamFactory;
|
||||||
import org.bigbluebutton.voiceconf.red5.ClientConnectionManager;
|
import org.bigbluebutton.voiceconf.red5.ClientConnectionManager;
|
||||||
import org.bigbluebutton.voiceconf.red5.media.CallStream;
|
import org.bigbluebutton.voiceconf.red5.media.CallStream;
|
||||||
@ -55,7 +58,11 @@ public class CallAgent extends CallListenerAdapter implements CallStreamObserver
|
|||||||
private ClientConnectionManager clientConnManager;
|
private ClientConnectionManager clientConnManager;
|
||||||
private final String clientId;
|
private final String clientId;
|
||||||
private final AudioConferenceProvider portProvider;
|
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 {
|
private enum CallState {
|
||||||
UA_IDLE(0), UA_INCOMING_CALL(1), UA_OUTGOING_CALL(2), UA_ONCALL(3);
|
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;
|
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.sipProvider = sipProvider;
|
||||||
this.clientRtpIp = sipClientRtpIp;
|
this.clientRtpIp = sipClientRtpIp;
|
||||||
this.userProfile = userProfile;
|
this.userProfile = userProfile;
|
||||||
this.portProvider = portProvider;
|
this.portProvider = portProvider;
|
||||||
this.clientId = clientId;
|
this.clientId = clientId;
|
||||||
|
this.messagingService = messagingService;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getCallId() {
|
public String getCallId() {
|
||||||
@ -79,7 +92,7 @@ public class CallAgent extends CallListenerAdapter implements CallStreamObserver
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void initSessionDescriptor() {
|
private void initSessionDescriptor() {
|
||||||
log.debug("initSessionDescriptor");
|
log.debug("initSessionDescriptor");
|
||||||
SessionDescriptor newSdp = SdpUtils.createInitialSdp(userProfile.username,
|
SessionDescriptor newSdp = SdpUtils.createInitialSdp(userProfile.username,
|
||||||
this.clientRtpIp, userProfile.audioPort,
|
this.clientRtpIp, userProfile.audioPort,
|
||||||
userProfile.videoPort, userProfile.audioCodecsPrecedence );
|
userProfile.videoPort, userProfile.audioCodecsPrecedence );
|
||||||
@ -87,7 +100,13 @@ public class CallAgent extends CallListenerAdapter implements CallStreamObserver
|
|||||||
log.debug("localSession Descriptor = " + localSession );
|
log.debug("localSession Descriptor = " + localSession );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Boolean isListeningToGlobal() {
|
||||||
|
return listeningToGlobal;
|
||||||
|
}
|
||||||
|
|
||||||
public void call(String callerName, String destination) {
|
public void call(String callerName, String destination) {
|
||||||
|
_callerName = callerName;
|
||||||
|
_destination = destination;
|
||||||
log.debug("{} making a call to {}", callerName, destination);
|
log.debug("{} making a call to {}", callerName, destination);
|
||||||
try {
|
try {
|
||||||
localSocket = getLocalAudioSocket();
|
localSocket = getLocalAudioSocket();
|
||||||
@ -131,11 +150,15 @@ public class CallAgent extends CallListenerAdapter implements CallStreamObserver
|
|||||||
|
|
||||||
/** Closes an ongoing, incoming, or pending call */
|
/** Closes an ongoing, incoming, or pending call */
|
||||||
public void hangup() {
|
public void hangup() {
|
||||||
|
if (callState == CallState.UA_IDLE) return;
|
||||||
log.debug("hangup");
|
log.debug("hangup");
|
||||||
|
if (listeningToGlobal) {
|
||||||
if (callState == CallState.UA_IDLE) return;
|
log.debug("Hanging up of a call connected to the global audio stream");
|
||||||
closeVoiceStreams();
|
notifyListenersOfOnCallClosed();
|
||||||
if (call != null) call.hangup();
|
} else {
|
||||||
|
closeVoiceStreams();
|
||||||
|
if (call != null) call.hangup();
|
||||||
|
}
|
||||||
callState = CallState.UA_IDLE;
|
callState = CallState.UA_IDLE;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -163,6 +186,10 @@ public class CallAgent extends CallListenerAdapter implements CallStreamObserver
|
|||||||
|
|
||||||
return socket;
|
return socket;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private boolean isGlobalAudioStream() {
|
||||||
|
return (_callerName != null && _callerName.startsWith("GLOBAL_AUDIO_"));
|
||||||
|
}
|
||||||
|
|
||||||
private void createVoiceStreams() {
|
private void createVoiceStreams() {
|
||||||
if (callStream != null) {
|
if (callStream != null) {
|
||||||
@ -187,7 +214,11 @@ public class CallAgent extends CallListenerAdapter implements CallStreamObserver
|
|||||||
callStream = callStreamFactory.createCallStream(sipCodec, connInfo);
|
callStream = callStreamFactory.createCallStream(sipCodec, connInfo);
|
||||||
callStream.addCallStreamObserver(this);
|
callStream.addCallStreamObserver(this);
|
||||||
callStream.start();
|
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) {
|
} catch (Exception e) {
|
||||||
log.error("Failed to create Call Stream.");
|
log.error("Failed to create Call Stream.");
|
||||||
System.out.println(StackTraceUtil.getStackTrace(e));
|
System.out.println(StackTraceUtil.getStackTrace(e));
|
||||||
@ -214,15 +245,41 @@ public class CallAgent extends CallListenerAdapter implements CallStreamObserver
|
|||||||
callStream.stopTalkStream(broadcastStream, scope);
|
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() {
|
private void closeVoiceStreams() {
|
||||||
log.debug("Shutting down the voice streams.");
|
log.debug("Shutting down the voice streams.");
|
||||||
if (callStream != null) {
|
if (callStream != null) {
|
||||||
callStream.stop();
|
callStream.stop();
|
||||||
callStream = null;
|
callStream = null;
|
||||||
} else {
|
} else {
|
||||||
log.debug("Can't shutdown voice stream. callstream is NULL");
|
log.debug("Can't shutdown voice stream. callstream is NULL");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ********************** Call callback functions **********************
|
// ********************** Call callback functions **********************
|
||||||
@ -260,10 +317,7 @@ public class CallAgent extends CallListenerAdapter implements CallStreamObserver
|
|||||||
public void onCallAccepted(Call call, String sdp, Message resp) {
|
public void onCallAccepted(Call call, String sdp, Message resp) {
|
||||||
log.debug("Received 200/OK. So user has successfully joined the conference.");
|
log.debug("Received 200/OK. So user has successfully joined the conference.");
|
||||||
if (!isCurrentCall(call)) return;
|
if (!isCurrentCall(call)) return;
|
||||||
|
|
||||||
log.debug("ACCEPTED/CALL.");
|
|
||||||
callState = CallState.UA_ONCALL;
|
callState = CallState.UA_ONCALL;
|
||||||
|
|
||||||
setupSdpAndCodec(sdp);
|
setupSdpAndCodec(sdp);
|
||||||
|
|
||||||
if (userProfile.noOffer) {
|
if (userProfile.noOffer) {
|
||||||
@ -330,18 +384,20 @@ public class CallAgent extends CallListenerAdapter implements CallStreamObserver
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void notifyListenersOfOnCallClosed() {
|
private void notifyListenersOfOnCallClosed() {
|
||||||
|
if (callState == CallState.UA_IDLE) return;
|
||||||
|
|
||||||
log.debug("notifyListenersOfOnCallClosed for {}", clientId);
|
log.debug("notifyListenersOfOnCallClosed for {}", clientId);
|
||||||
clientConnManager.leaveConference(clientId);
|
clientConnManager.leaveConference(clientId);
|
||||||
cleanup();
|
cleanup();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void cleanup() {
|
private void cleanup() {
|
||||||
log.debug("Closing local audio port {}", localSocket.getLocalPort());
|
if (localSocket == null) return;
|
||||||
if (localSocket != null) {
|
|
||||||
localSocket.close();
|
log.debug("Closing local audio port {}", localSocket.getLocalPort());
|
||||||
} else {
|
if (!listeningToGlobal) {
|
||||||
log.debug("Trying to close un-allocated port {}", localSocket.getLocalPort());
|
localSocket.close();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Callback function called when arriving a BYE request */
|
/** Callback function called when arriving a BYE request */
|
||||||
|
83
bbb-voice/src/main/java/org/bigbluebutton/voiceconf/sip/GlobalCall.java
Executable file
83
bbb-voice/src/main/java/org/bigbluebutton/voiceconf/sip/GlobalCall.java
Executable 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);
|
||||||
|
}
|
||||||
|
}
|
@ -145,13 +145,11 @@ public class KeepAliveUdp extends Thread
|
|||||||
{ try
|
{ try
|
||||||
{ while(!stop)
|
{ while(!stop)
|
||||||
{ sendToken();
|
{ sendToken();
|
||||||
//System.out.print(".");
|
|
||||||
Thread.sleep(delta_time);
|
Thread.sleep(delta_time);
|
||||||
if (expire>0 && System.currentTimeMillis()>expire) halt();
|
if (expire>0 && System.currentTimeMillis()>expire) halt();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (Exception e) { e.printStackTrace(); }
|
catch (Exception e) { e.printStackTrace(); }
|
||||||
//System.out.println("o");
|
|
||||||
udp_socket=null;
|
udp_socket=null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -165,4 +163,4 @@ public class KeepAliveUdp extends Thread
|
|||||||
return str+" ("+delta_time+"ms)";
|
return str+" ("+delta_time+"ms)";
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -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.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
14
bbb-voice/src/main/java/org/bigbluebutton/voiceconf/sip/ListenOnlyUser.java
Executable file
14
bbb-voice/src/main/java/org/bigbluebutton/voiceconf/sip/ListenOnlyUser.java
Executable 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;
|
||||||
|
}
|
||||||
|
}
|
@ -20,10 +20,10 @@ package org.bigbluebutton.voiceconf.sip;
|
|||||||
|
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.Iterator;
|
import java.util.Iterator;
|
||||||
import java.util.concurrent.atomic.AtomicInteger;
|
|
||||||
import org.zoolu.sip.provider.*;
|
import org.zoolu.sip.provider.*;
|
||||||
import org.zoolu.net.SocketAddress;
|
import org.zoolu.net.SocketAddress;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
|
import org.bigbluebutton.voiceconf.messaging.IMessagingService;
|
||||||
import org.bigbluebutton.voiceconf.red5.CallStreamFactory;
|
import org.bigbluebutton.voiceconf.red5.CallStreamFactory;
|
||||||
import org.bigbluebutton.voiceconf.red5.ClientConnectionManager;
|
import org.bigbluebutton.voiceconf.red5.ClientConnectionManager;
|
||||||
import org.red5.logging.Red5LoggerFactory;
|
import org.red5.logging.Red5LoggerFactory;
|
||||||
@ -43,7 +43,7 @@ public class SipPeer implements SipRegisterAgentListener {
|
|||||||
private CallStreamFactory callStreamFactory;
|
private CallStreamFactory callStreamFactory;
|
||||||
|
|
||||||
private CallManager callManager = new CallManager();
|
private CallManager callManager = new CallManager();
|
||||||
|
private IMessagingService messagingService;
|
||||||
private SipProvider sipProvider;
|
private SipProvider sipProvider;
|
||||||
private String clientRtpIp;
|
private String clientRtpIp;
|
||||||
private SipRegisterAgent registerAgent;
|
private SipRegisterAgent registerAgent;
|
||||||
@ -53,9 +53,12 @@ public class SipPeer implements SipRegisterAgentListener {
|
|||||||
private boolean registered = false;
|
private boolean registered = false;
|
||||||
private SipPeerProfile registeredProfile;
|
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.id = id;
|
||||||
this.clientRtpIp = sipClientRtpIp;
|
this.clientRtpIp = sipClientRtpIp;
|
||||||
|
this.messagingService = messagingService;
|
||||||
audioconfProvider = new AudioConferenceProvider(host, sipPort, startAudioPort, stopAudioPort);
|
audioconfProvider = new AudioConferenceProvider(host, sipPort, startAudioPort, stopAudioPort);
|
||||||
initSipProvider(host, sipPort);
|
initSipProvider(host, sipPort);
|
||||||
}
|
}
|
||||||
@ -98,7 +101,6 @@ public class SipPeer implements SipRegisterAgentListener {
|
|||||||
log.debug( "SIPUser register : {}", fromURL );
|
log.debug( "SIPUser register : {}", fromURL );
|
||||||
log.debug( "SIPUser register : {}", registeredProfile.contactUrl );
|
log.debug( "SIPUser register : {}", registeredProfile.contactUrl );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public void call(String clientId, String callerName, String destination) {
|
public void call(String clientId, String callerName, String destination) {
|
||||||
if (!registered) {
|
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);
|
log.warn("We are not registered to FreeSWITCH. However, we will allow {} to call {}.", callerName, destination);
|
||||||
// return;
|
// return;
|
||||||
}
|
}
|
||||||
|
|
||||||
SipPeerProfile callerProfile = SipPeerProfile.copy(registeredProfile);
|
CallAgent ca = createCallAgent(clientId);
|
||||||
CallAgent ca = new CallAgent(this.clientRtpIp, sipProvider, callerProfile, audioconfProvider, 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.setClientConnectionManager(clientConnManager);
|
||||||
ca.setCallStreamFactory(callStreamFactory);
|
ca.setCallStreamFactory(callStreamFactory);
|
||||||
callManager.add(ca);
|
callManager.add(ca);
|
||||||
ca.call(callerName, destination);
|
|
||||||
|
return ca;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void close() {
|
public void close() {
|
||||||
@ -134,11 +149,30 @@ public class SipPeer implements SipRegisterAgentListener {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void hangup(String clientId) {
|
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) {
|
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();
|
CallAgent ca = (CallAgent) iter.next();
|
||||||
ca.hangup();
|
ca.hangup();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (registerAgent != null) {
|
if (registerAgent != null) {
|
||||||
registerAgent.unregister();
|
registerAgent.unregister();
|
||||||
registerAgent = null;
|
registerAgent = null;
|
||||||
|
@ -20,6 +20,7 @@ package org.bigbluebutton.voiceconf.sip;
|
|||||||
|
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.zoolu.sip.provider.SipStack;
|
import org.zoolu.sip.provider.SipStack;
|
||||||
|
import org.bigbluebutton.voiceconf.messaging.IMessagingService;
|
||||||
import org.bigbluebutton.voiceconf.red5.CallStreamFactory;
|
import org.bigbluebutton.voiceconf.red5.CallStreamFactory;
|
||||||
import org.bigbluebutton.voiceconf.red5.ClientConnectionManager;
|
import org.bigbluebutton.voiceconf.red5.ClientConnectionManager;
|
||||||
import org.red5.logging.Red5LoggerFactory;
|
import org.red5.logging.Red5LoggerFactory;
|
||||||
@ -38,6 +39,7 @@ public final class SipPeerManager {
|
|||||||
|
|
||||||
private ClientConnectionManager clientConnManager;
|
private ClientConnectionManager clientConnManager;
|
||||||
private CallStreamFactory callStreamFactory;
|
private CallStreamFactory callStreamFactory;
|
||||||
|
private IMessagingService messagingService;
|
||||||
|
|
||||||
private Map<String, SipPeer> sipPeers;
|
private Map<String, SipPeer> sipPeers;
|
||||||
private int sipStackDebugLevel = 8;
|
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) {
|
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.setClientConnectionManager(clientConnManager);
|
||||||
sipPeer.setCallStreamFactory(callStreamFactory);
|
sipPeer.setCallStreamFactory(callStreamFactory);
|
||||||
sipPeers.put(peerId, sipPeer);
|
sipPeers.put(peerId, sipPeer);
|
||||||
@ -65,7 +67,7 @@ public final class SipPeerManager {
|
|||||||
if (sipPeer == null) throw new PeerNotFoundException("Can't find sip peer " + peerId);
|
if (sipPeer == null) throw new PeerNotFoundException("Can't find sip peer " + peerId);
|
||||||
sipPeer.call(clientId, callerName, destination);
|
sipPeer.call(clientId, callerName, destination);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void unregister(String userid) {
|
public void unregister(String userid) {
|
||||||
SipPeer sipUser = sipPeers.get(userid);
|
SipPeer sipUser = sipPeers.get(userid);
|
||||||
if (sipUser != null) {
|
if (sipUser != null) {
|
||||||
@ -100,6 +102,13 @@ public final class SipPeerManager {
|
|||||||
sipPeers.remove(userid);
|
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) {
|
public void close(String userid) {
|
||||||
SipPeer sipUser = sipPeers.get(userid);
|
SipPeer sipUser = sipPeers.get(userid);
|
||||||
if (sipUser != null) {
|
if (sipUser != null) {
|
||||||
@ -141,4 +150,8 @@ public final class SipPeerManager {
|
|||||||
public void setClientConnectionManager(ClientConnectionManager ccm) {
|
public void setClientConnectionManager(ClientConnectionManager ccm) {
|
||||||
clientConnManager = ccm;
|
clientConnManager = ccm;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setMessagingService(IMessagingService service) {
|
||||||
|
messagingService = service;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -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();
|
||||||
|
}
|
||||||
|
}
|
@ -8,7 +8,8 @@ import org.red5.logging.Red5LoggerFactory;
|
|||||||
import org.red5.server.api.event.IEvent;
|
import org.red5.server.api.event.IEvent;
|
||||||
import org.red5.server.api.scope.IScope;
|
import org.red5.server.api.scope.IScope;
|
||||||
import org.red5.server.api.stream.IBroadcastStream;
|
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.IStreamListener;
|
||||||
import org.red5.server.api.stream.ResourceExistException;
|
import org.red5.server.api.stream.ResourceExistException;
|
||||||
import org.red5.server.api.stream.ResourceNotFoundException;
|
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.messaging.PipeConnectionEvent;
|
||||||
import org.red5.server.net.rtmp.event.IRTMPEvent;
|
import org.red5.server.net.rtmp.event.IRTMPEvent;
|
||||||
import org.red5.server.net.rtmp.event.Notify;
|
import org.red5.server.net.rtmp.event.Notify;
|
||||||
import org.red5.server.stream.codec.StreamCodecInfo;
|
|
||||||
import org.red5.server.stream.message.RTMPMessage;
|
import org.red5.server.stream.message.RTMPMessage;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.red5.server.api.stream.IStreamPacket;;
|
import org.red5.server.api.stream.IStreamPacket;;
|
||||||
|
58
bbb-voice/src/main/webapp/WEB-INF/bbb-redis-messaging.xml
Executable file
58
bbb-voice/src/main/webapp/WEB-INF/bbb-redis-messaging.xml
Executable 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>
|
66
bbb-voice/src/main/webapp/WEB-INF/bbb-redis-pool.xml
Executable file
66
bbb-voice/src/main/webapp/WEB-INF/bbb-redis-pool.xml
Executable 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>
|
@ -17,10 +17,10 @@ freeswitch.port=5060
|
|||||||
startAudioPort=15000
|
startAudioPort=15000
|
||||||
stopAudioPort=16383
|
stopAudioPort=16383
|
||||||
|
|
||||||
# An extension pattern, in case your asterisk extensions.conf
|
redis.host=127.0.0.1
|
||||||
# uses a naming convetion for your meeting rooms
|
redis.port=6379
|
||||||
# e.g. conf-85115 instead of just 85115
|
redis.pass=
|
||||||
callExtensionPattern={0}
|
|
||||||
|
|
||||||
# If you want mjsip stack (red5/log/*access*.log) to minimize the amount of logs it
|
# 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).
|
# generates, set this to a lower value (e.g. 3).
|
||||||
|
@ -22,8 +22,10 @@ with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
|
|||||||
<beans xmlns="http://www.springframework.org/schema/beans"
|
<beans xmlns="http://www.springframework.org/schema/beans"
|
||||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
xmlns:lang="http://www.springframework.org/schema/lang"
|
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
|
xsi:schemaLocation="http://www.springframework.org/schema/beans
|
||||||
http://www.springframework.org/schema/lang http://www.springframework.org/schema/lang/spring-lang-2.0.xsd">
|
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">
|
<bean id="placeholderConfig" class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
|
||||||
<property name="locations">
|
<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">
|
<bean id="sipPeerManager" class="org.bigbluebutton.voiceconf.sip.SipPeerManager">
|
||||||
<property name="sipStackDebugLevel" value="${sipStackDebugLevel}"/>
|
<property name="sipStackDebugLevel" value="${sipStackDebugLevel}"/>
|
||||||
<property name="sipRemotePort" value="${freeswitch.port}"/>
|
<property name="sipRemotePort" value="${freeswitch.port}"/>
|
||||||
|
<property name="messagingService" ref="messagingService"/>
|
||||||
|
|
||||||
</bean>
|
</bean>
|
||||||
|
|
||||||
<bean id="clientConnectionManager" class="org.bigbluebutton.voiceconf.red5.ClientConnectionManager"/>
|
<bean id="clientConnectionManager" class="org.bigbluebutton.voiceconf.red5.ClientConnectionManager"/>
|
||||||
|
|
||||||
|
<import resource="bbb-redis-pool.xml"/>
|
||||||
|
<import resource="bbb-redis-messaging.xml"/>
|
||||||
|
|
||||||
</beans>
|
</beans>
|
||||||
|
40
bbb.sh
Executable file
40
bbb.sh
Executable 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
200
bigbluebutton-apps/build.gradle
Executable file → Normal file
@ -1,7 +1,7 @@
|
|||||||
usePlugin 'scala'
|
apply plugin: 'scala'
|
||||||
usePlugin 'java'
|
apply plugin: 'java'
|
||||||
usePlugin 'war'
|
apply plugin: 'war'
|
||||||
usePlugin 'eclipse'
|
apply plugin: 'eclipse'
|
||||||
|
|
||||||
version = '0.8'
|
version = '0.8'
|
||||||
jar.enabled = true
|
jar.enabled = true
|
||||||
@ -10,112 +10,121 @@ def appName = 'bigbluebutton'
|
|||||||
|
|
||||||
archivesBaseName = appName
|
archivesBaseName = appName
|
||||||
|
|
||||||
task resolveDeps(dependsOn: configurations.default.buildArtifacts, type: Copy) {
|
task resolveDeps(type: Copy) {
|
||||||
into('lib')
|
into('lib')
|
||||||
from configurations.default
|
from configurations.default
|
||||||
from configurations.default.allArtifacts*.file
|
from configurations.default.allArtifacts.file
|
||||||
}
|
}
|
||||||
|
|
||||||
repositories {
|
repositories {
|
||||||
add(new org.apache.ivy.plugins.resolver.ChainResolver()) {
|
add(new org.apache.ivy.plugins.resolver.ChainResolver()) {
|
||||||
name = 'remote'
|
name = 'remote'
|
||||||
returnFirst = true
|
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.URLResolver()) {
|
add(new org.apache.ivy.plugins.resolver.URLResolver()) {
|
||||||
name = "maven2-central"
|
name = "googlecode"
|
||||||
m2compatible = true
|
addArtifactPattern "http://red5.googlecode.com/svn/repository/[artifact](-[revision]).[ext]"
|
||||||
addArtifactPattern "http://repo1.maven.org/maven2/[organisation]/[module]/[revision]/[artifact](-[revision]).[ext]"
|
addArtifactPattern "http://red5.googlecode.com/svn/repository/[organisation]/[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()) {
|
add(new org.apache.ivy.plugins.resolver.URLResolver()) {
|
||||||
name = "spring-bundles"
|
name = "blindside-repos"
|
||||||
m2compatible = true
|
addArtifactPattern "http://blindside.googlecode.com/svn/repository/[artifact](-[revision]).[ext]"
|
||||||
addArtifactPattern "http://repository.springsource.com/maven/bundles/external/[organisation]/[module]/[revision]/[artifact]-[revision].[ext]"
|
addArtifactPattern "http://blindside.googlecode.com/svn/repository/[organisation]/[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 = "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 {
|
dependencies {
|
||||||
// Servlet
|
// Servlet
|
||||||
providedCompile 'javax.servlet:servlet-api:2.5@jar'
|
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'
|
|
||||||
|
|
||||||
// Needed for the JVM shutdown hook but needs to be put into red5/lib dir.
|
// Mina
|
||||||
// Otherwise we get exception on aop utils class not found.
|
providedCompile 'org.apache.mina:mina-core:2.0.7@jar'
|
||||||
providedCompile 'org.springframework:spring-aop:3.0.6.RELEASE@jar'
|
providedCompile 'org.apache.mina:mina-integration-beans:2.0.7@jar'
|
||||||
compile 'aopalliance:aopalliance:1.0@jar'
|
providedCompile 'org.apache.mina:mina-integration-jmx:2.0.7@jar'
|
||||||
|
|
||||||
// Java Concurrency In Practice
|
// Spring
|
||||||
providedCompile 'net.jcip:jcip-annotations:1.0@jar'
|
providedCompile 'org.springframework:spring-web:4.0.3.RELEASE@jar'
|
||||||
|
providedCompile 'org.springframework:spring-beans:4.0.3.RELEASE@jar'
|
||||||
// Testing
|
providedCompile 'org.springframework:spring-context:4.0.3.RELEASE@jar'
|
||||||
compile 'org.testng:testng:5.8@jar'
|
providedCompile 'org.springframework:spring-core:4.0.3.RELEASE@jar'
|
||||||
compile 'org.easymock:easymock:2.4@jar'
|
|
||||||
|
// Red5
|
||||||
|
providedCompile 'org/red5:red5:1.0.2@jar'
|
||||||
|
|
||||||
//redis
|
// Logging
|
||||||
compile 'redis.clients:jedis:2.0.0'
|
providedCompile 'ch.qos.logback:logback-core:1.0.13@jar'
|
||||||
providedCompile 'commons-pool:commons-pool:1.5.6'
|
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
|
// Libraries needed to run the scala tools
|
||||||
scalaTools 'org.scala-lang:scala-compiler:2.9.2'
|
scalaTools 'org.scala-lang:scala-compiler:2.9.2'
|
||||||
scalaTools 'org.scala-lang:scala-library:2.9.2'
|
scalaTools 'org.scala-lang:scala-library:2.9.2'
|
||||||
|
|
||||||
// Libraries needed for scala api
|
// Libraries needed for scala api
|
||||||
compile 'org.scala-lang:scala-library:2.9.2'
|
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
|
// Freeswitch ESL Client
|
||||||
compile 'org/freeswitch:fs-esl-client:0.8.2@jar'
|
compile 'org/freeswitch:fs-esl-client:0.8.2@jar'
|
||||||
compile 'org.jboss.netty:netty:3.2.1.Final@jar'
|
compile 'org.jboss.netty:netty:3.2.1.Final@jar'
|
||||||
compile 'com.google.code.gson:gson:1.7.1'
|
compile 'com.google.code.gson:gson:1.7.1'
|
||||||
providedCompile 'org.apache.commons:commons-lang3:3.1'
|
providedCompile 'org.apache.commons:commons-lang3:3.1'
|
||||||
compile 'commons-lang:commons-lang:2.5'
|
//compile 'commons-lang:commons-lang:2.5'
|
||||||
}
|
}
|
||||||
|
|
||||||
test {
|
test {
|
||||||
useTestNG()
|
useTestNG()
|
||||||
}
|
}
|
||||||
|
|
||||||
war.doLast {
|
war.doLast {
|
||||||
@ -124,13 +133,12 @@ war.doLast {
|
|||||||
|
|
||||||
|
|
||||||
task deploy() << {
|
task deploy() << {
|
||||||
def red5AppsDir = '/usr/share/red5/webapps'
|
def red5AppsDir = '/usr/share/red5/webapps'
|
||||||
def bbbDir = new File("${red5AppsDir}/$appName")
|
def bbbDir = new File("${red5AppsDir}/$appName")
|
||||||
println "Deleting $bbbDir"
|
ant.delete(dir: bbbDir)
|
||||||
ant.delete(dir: bbbDir)
|
ant.mkdir(dir: bbbDir)
|
||||||
ant.mkdir(dir: bbbDir)
|
ant.copy(todir: bbbDir) {
|
||||||
ant.copy(todir: bbbDir) {
|
fileset(dir: "$buildDir/$appName")
|
||||||
fileset(dir: "$buildDir/$appName")
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
16
bigbluebutton-apps/gradle.properties
Normal file
16
bigbluebutton-apps/gradle.properties
Normal 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
|
||||||
|
|
||||||
|
|
49
bigbluebutton-apps/src/main/java/org/bigbluebutton/conference/BigBlueButtonApplication.java
Executable file → Normal file
49
bigbluebutton-apps/src/main/java/org/bigbluebutton/conference/BigBlueButtonApplication.java
Executable file → Normal file
@ -22,9 +22,11 @@ import java.util.HashMap;
|
|||||||
import java.util.Iterator;
|
import java.util.Iterator;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Set;
|
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.participants.ParticipantsApplication;
|
||||||
import org.bigbluebutton.conference.service.recorder.RecorderApplication;
|
import org.bigbluebutton.conference.service.recorder.RecorderApplication;
|
||||||
|
import org.bigbluebutton.core.api.IBigBlueButtonInGW;
|
||||||
import org.red5.logging.Red5LoggerFactory;
|
import org.red5.logging.Red5LoggerFactory;
|
||||||
import org.red5.server.adapter.IApplication;
|
import org.red5.server.adapter.IApplication;
|
||||||
import org.red5.server.adapter.MultiThreadedApplicationAdapter;
|
import org.red5.server.adapter.MultiThreadedApplicationAdapter;
|
||||||
@ -44,6 +46,7 @@ public class BigBlueButtonApplication extends MultiThreadedApplicationAdapter {
|
|||||||
private RecorderApplication recorderApplication;
|
private RecorderApplication recorderApplication;
|
||||||
private AbstractApplicationContext appCtx;
|
private AbstractApplicationContext appCtx;
|
||||||
private ConnectionInvokerService connInvokerService;
|
private ConnectionInvokerService connInvokerService;
|
||||||
|
private IBigBlueButtonInGW bbbGW;
|
||||||
|
|
||||||
private static final String APP = "BBB";
|
private static final String APP = "BBB";
|
||||||
|
|
||||||
@ -92,15 +95,14 @@ public class BigBlueButtonApplication extends MultiThreadedApplicationAdapter {
|
|||||||
appCtx.registerShutdownHook();
|
appCtx.registerShutdownHook();
|
||||||
super.appStart(app);
|
super.appStart(app);
|
||||||
|
|
||||||
connInvokerService.start();
|
connInvokerService.setAppScope(app);
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void appStop(IScope app) {
|
public void appStop(IScope app) {
|
||||||
log.debug("***** " + APP + " [ " + " appStop [ " + scope.getName() + "] *********");
|
log.debug("***** " + APP + " [ " + " appStop [ " + scope.getName() + "] *********");
|
||||||
connInvokerService.stop();
|
|
||||||
super.appStop(app);
|
super.appStop(app);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -116,7 +118,8 @@ public class BigBlueButtonApplication extends MultiThreadedApplicationAdapter {
|
|||||||
public void roomStop(IScope room) {
|
public void roomStop(IScope room) {
|
||||||
log.debug("***** " + APP + " [ " + " roomStop [ " + scope.getName() + "] *********");
|
log.debug("***** " + APP + " [ " + " roomStop [ " + scope.getName() + "] *********");
|
||||||
|
|
||||||
participantsApplication.destroyRoom(room.getName());
|
// bbbGW.destroyMeeting(room.getName());
|
||||||
|
|
||||||
recorderApplication.destroyRecordSession(room.getName());
|
recorderApplication.destroyRecordSession(room.getName());
|
||||||
connInvokerService.removeScope(room.getName());
|
connInvokerService.removeScope(room.getName());
|
||||||
|
|
||||||
@ -156,9 +159,7 @@ public class BigBlueButtonApplication extends MultiThreadedApplicationAdapter {
|
|||||||
lsMap = new HashMap<String, Boolean>();
|
lsMap = new HashMap<String, Boolean>();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
if (record == true) {
|
if (record == true) {
|
||||||
recorderApplication.createRecordSession(room);
|
recorderApplication.createRecordSession(room);
|
||||||
}
|
}
|
||||||
@ -166,11 +167,13 @@ public class BigBlueButtonApplication extends MultiThreadedApplicationAdapter {
|
|||||||
BigBlueButtonSession bbbSession = new BigBlueButtonSession(room, internalUserID, username, role,
|
BigBlueButtonSession bbbSession = new BigBlueButtonSession(room, internalUserID, username, role,
|
||||||
voiceBridge, record, externalUserID, muted);
|
voiceBridge, record, externalUserID, muted);
|
||||||
connection.setAttribute(Constants.SESSION, bbbSession);
|
connection.setAttribute(Constants.SESSION, bbbSession);
|
||||||
|
connection.setAttribute("INTERNAL_USER_ID", internalUserID);
|
||||||
|
|
||||||
String debugInfo = "internalUserID=" + internalUserID + ",username=" + username + ",role=" + role + "," +
|
String debugInfo = "internalUserID=" + internalUserID + ",username=" + username + ",role=" + role + "," +
|
||||||
",voiceConf=" + voiceBridge + ",room=" + room + ",externalUserid=" + externalUserID;
|
",voiceConf=" + voiceBridge + ",room=" + room + ",externalUserid=" + externalUserID;
|
||||||
log.debug("User [{}] connected to room [{}]", debugInfo, room);
|
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);
|
connInvokerService.addConnection(bbbSession.getInternalUserID(), connection);
|
||||||
|
|
||||||
@ -191,13 +194,32 @@ public class BigBlueButtonApplication extends MultiThreadedApplicationAdapter {
|
|||||||
|
|
||||||
BigBlueButtonSession bbbSession = (BigBlueButtonSession) Red5.getConnectionLocal().getAttribute(Constants.SESSION);
|
BigBlueButtonSession bbbSession = (BigBlueButtonSession) Red5.getConnectionLocal().getAttribute(Constants.SESSION);
|
||||||
log.info("User [" + bbbSession.getUsername() + "] disconnected from room [" + bbbSession.getRoom() +"]");
|
log.info("User [" + bbbSession.getUsername() + "] disconnected from room [" + bbbSession.getRoom() +"]");
|
||||||
|
|
||||||
|
bbbGW.userLeft(bbbSession.getRoom(), getBbbSession().getInternalUserID());
|
||||||
|
|
||||||
super.roomDisconnect(conn);
|
super.roomDisconnect(conn);
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getMyUserId() {
|
public void validateToken(String token) {
|
||||||
BigBlueButtonSession bbbSession = (BigBlueButtonSession) Red5.getConnectionLocal().getAttribute(Constants.SESSION);
|
BigBlueButtonSession bbbSession = (BigBlueButtonSession) Red5.getConnectionLocal().getAttribute(Constants.SESSION);
|
||||||
assert bbbSession != null;
|
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) {
|
public void setParticipantsApplication(ParticipantsApplication a) {
|
||||||
@ -223,13 +245,16 @@ public class BigBlueButtonApplication extends MultiThreadedApplicationAdapter {
|
|||||||
this.connInvokerService = connInvokerService;
|
this.connInvokerService = connInvokerService;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setBigBlueButtonInGW(IBigBlueButtonInGW bbbGW) {
|
||||||
|
this.bbbGW = bbbGW;
|
||||||
|
}
|
||||||
|
|
||||||
private class ShutdownHookListener implements ApplicationListener<ApplicationEvent> {
|
private class ShutdownHookListener implements ApplicationListener<ApplicationEvent> {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onApplicationEvent(ApplicationEvent event) {
|
public void onApplicationEvent(ApplicationEvent event) {
|
||||||
if (event instanceof org.springframework.context.event.ContextStoppedEvent) {
|
if (event instanceof org.springframework.context.event.ContextStoppedEvent) {
|
||||||
log.info("Received shutdown event. Red5 is shutting down. Destroying all rooms.");
|
log.info("Received shutdown event. Red5 is shutting down. Destroying all rooms.");
|
||||||
participantsApplication.destroyAllRooms();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -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());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -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);
|
|
||||||
}
|
|
@ -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());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
@ -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") );
|
|
||||||
}
|
|
||||||
}
|
|
@ -16,32 +16,24 @@
|
|||||||
* with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
|
* 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;
|
import java.util.Map;
|
||||||
|
|
||||||
public class ClientMessage {
|
public class BroadcastClientMessage implements ClientMessage {
|
||||||
public static final String BROADCAST = "broadcast";
|
|
||||||
public static final String DIRECT = "direct";
|
private String meetingID;
|
||||||
|
|
||||||
private String type;
|
|
||||||
private String dest;
|
|
||||||
private Map<String, Object> message;
|
private Map<String, Object> message;
|
||||||
private String messageName;
|
private String messageName;
|
||||||
|
|
||||||
public ClientMessage(String type, String dest, String messageName, Map<String, Object> message) {
|
public BroadcastClientMessage(String meetingID, String messageName, Map<String, Object> message) {
|
||||||
this.type = type;
|
this.meetingID = meetingID;
|
||||||
this.dest = dest;
|
|
||||||
this.message = message;
|
this.message = message;
|
||||||
this.messageName = messageName;
|
this.messageName = messageName;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getType() {
|
public String getMeetingID() {
|
||||||
return type;
|
return meetingID;
|
||||||
}
|
|
||||||
|
|
||||||
public String getDest() {
|
|
||||||
return dest;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getMessageName() {
|
public String getMessageName() {
|
@ -16,8 +16,9 @@
|
|||||||
* with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
|
* 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 {
|
||||||
|
|
||||||
}
|
}
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
@ -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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -18,99 +18,33 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
package org.bigbluebutton.conference.service.chat;
|
package org.bigbluebutton.conference.service.chat;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.red5.logging.Red5LoggerFactory;
|
import org.red5.logging.Red5LoggerFactory;
|
||||||
import org.red5.server.api.Red5;
import org.bigbluebutton.conference.BigBlueButtonSession;
|
import org.bigbluebutton.core.api.IBigBlueButtonInGW;
|
||||||
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;
|
|
||||||
|
|
||||||
|
|
||||||
public class ChatApplication {
|
public class ChatApplication {
|
||||||
|
|
||||||
private static Logger log = Red5LoggerFactory.getLogger( ChatApplication.class, "bigbluebutton" );
|
private static Logger log = Red5LoggerFactory.getLogger( ChatApplication.class, "bigbluebutton" );
|
||||||
|
|
||||||
private ChatRoomsManager roomsManager;
|
|
||||||
public ChatHandler handler;
|
|
||||||
private ConnectionInvokerService connInvokerService;
|
|
||||||
|
|
||||||
public boolean createRoom(String name) {
|
private IBigBlueButtonInGW bbbInGW;
|
||||||
roomsManager.addRoom(new ChatRoom(name));
|
|
||||||
return true;
|
public void setBigBlueButtonInGW(IBigBlueButtonInGW inGW) {
|
||||||
|
bbbInGW = inGW;
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean destroyRoom(String name) {
|
public void sendPublicChatHistory(String meetingID, String requesterID) {
|
||||||
if (roomsManager.hasRoom(name)) {
|
// Just hardcode as we don't really need it for flash client. (ralam may 7, 2014)
|
||||||
roomsManager.removeRoom(name);
|
String replyTo = meetingID + "/" + requesterID;
|
||||||
}
|
bbbInGW.getChatHistory(meetingID, requesterID, replyTo);
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean hasRoom(String name) {
|
public void sendPublicMessage(String meetingID, String requesterID, Map<String, String> message) {
|
||||||
return roomsManager.hasRoom(name);
|
bbbInGW.sendPublicMessage(meetingID, requesterID, message);
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean addRoomListener(String room, IChatRoomListener listener) {
|
public void sendPrivateMessage(String meetingID, String requesterID, Map<String, String> message) {
|
||||||
if (roomsManager.hasRoom(room)){
|
bbbInGW.sendPrivateMessage(meetingID, requesterID, message);
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -26,7 +26,6 @@ import org.slf4j.Logger;
|
|||||||
import org.red5.logging.Red5LoggerFactory;
|
import org.red5.logging.Red5LoggerFactory;
|
||||||
import org.red5.server.api.scope.IScope;
|
import org.red5.server.api.scope.IScope;
|
||||||
import org.bigbluebutton.conference.service.recorder.RecorderApplication;
|
import org.bigbluebutton.conference.service.recorder.RecorderApplication;
|
||||||
import org.bigbluebutton.conference.service.recorder.chat.ChatEventRecorder;
|
|
||||||
|
|
||||||
public class ChatHandler implements IApplication{
|
public class ChatHandler implements IApplication{
|
||||||
private static Logger log = Red5LoggerFactory.getLogger( ChatHandler.class, "bigbluebutton" );
|
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) {
|
public boolean roomConnect(IConnection connection, Object[] params) {
|
||||||
log.debug("***** " + APP + " [ " + " roomConnect [ " + connection.getScope().getName() + "] *********");
|
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;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -106,13 +100,11 @@ public class ChatHandler implements IApplication{
|
|||||||
@Override
|
@Override
|
||||||
public void roomStop(IScope scope) {
|
public void roomStop(IScope scope) {
|
||||||
log.debug("***** " + APP + " [ " + " roomStop [ " + scope.getName() + "] *********");
|
log.debug("***** " + APP + " [ " + " roomStop [ " + scope.getName() + "] *********");
|
||||||
chatApplication.destroyRoom(scope.getName());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setChatApplication(ChatApplication a) {
|
public void setChatApplication(ChatApplication a) {
|
||||||
log.debug("Setting chat application");
|
log.debug("Setting chat application");
|
||||||
chatApplication = a;
|
chatApplication = a;
|
||||||
chatApplication.handler = this;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setRecorderApplication(RecorderApplication a) {
|
public void setRecorderApplication(RecorderApplication a) {
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user