diff --git a/.circleci/config.yml b/.circleci/config.yml index 99ecb22b2..48d678850 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -5,13 +5,13 @@ jobs: - image: ossrs/srs:dev steps: - checkout - - run: cd trunk && ./configure && make + - run: cd trunk && ./configure --with-utest && make test: docker: - image: ossrs/srs:dev steps: - checkout - - run: cd trunk && ./configure && make && ./objs/srs_utest + - run: cd trunk && ./configure --with-utest --gcov && make && ./objs/srs_utest && bash auto/coverage.sh workflows: version: 2 build_and_test: diff --git a/.github/ISSUE_TEMPLATE b/.github/ISSUE_TEMPLATE new file mode 100644 index 000000000..fa6f266ec --- /dev/null +++ b/.github/ISSUE_TEMPLATE @@ -0,0 +1,34 @@ +--- +name: File bug +about: File bug to improve SRS +title: '' +labels: '' +assignees: '' + +--- + +**描述(Description)** + +> 描述你遇到了什么问题(Please description your issue here) + +1. SRS版本(Version): `xxxxxx` +1. SRS的日志如下(Log): +``` +xxxxxxxxxxxx +``` +1. SRS的配置如下(Config): +``` +xxxxxxxxxxxx +``` + +**重现(Replay)** + +> 重现Bug的步骤(How to replay bug?) + +1. `xxxxxx` +1. `xxxxxx` +1. `xxxxxx` + +**期望行为(Expect)** + +> 描述你期望发生的事情(Please describe your expectation) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 000000000..574217ba5 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,75 @@ +name: "Release" + +on: + push: + tags: + - v3* + +jobs: + k8s: + name: actions-release-k8s + runs-on: ubuntu-20.04 + + steps: + - name: Checkout repository + uses: actions/checkout@v2 + + # The github.ref is, for example, refs/tags/v3.0.145 or refs/tags/v3.0-r8 + # Generate variables like: + # SRS_TAG=v3.0.145 + # SRS_TAG=v3.0-r8 + # SRS_MAJOR=3 + # @see https://docs.github.com/en/actions/reference/workflow-commands-for-github-actions#setting-an-environment-variable + - name: Generate varaiables + shell: bash + run: | + SRS_TAG=$(echo ${{ github.ref }}| awk -F '/' '{print $3}') + echo "SRS_TAG=$SRS_TAG" >> $GITHUB_ENV + SRS_MAJOR=$(echo $SRS_TAG| cut -c 2) + echo "SRS_MAJOR=$SRS_MAJOR" >> $GITHUB_ENV + + - name: Build SRS + shell: bash + run: | + echo "Release ossrs/srs:$SRS_TAG" + docker build --tag ossrs/srs:$SRS_TAG -f trunk/Dockerfile . + + - name: Login docker hub + uses: docker/login-action@v1 + with: + username: "${{ secrets.DOCKER_USERNAME }}" + password: "${{ secrets.DOCKER_PASSWORD }}" + - name: Push to docker hub + shell: bash + run: | + docker push ossrs/srs:$SRS_TAG + docker tag ossrs/srs:$SRS_TAG ossrs/srs:$SRS_MAJOR + docker push ossrs/srs:$SRS_MAJOR + + - name: Login Aliyun docker hub + uses: aliyun/acr-login@v1 + with: + login-server: https://registry.cn-hangzhou.aliyuncs.com + username: "${{ secrets.ACR_USERNAME }}" + password: "${{ secrets.ACR_PASSWORD }}" + - name: Push to Aliyun docker hub + shell: bash + run: | + docker tag ossrs/srs:$SRS_TAG registry.cn-hangzhou.aliyuncs.com/ossrs/srs:$SRS_TAG + docker push registry.cn-hangzhou.aliyuncs.com/ossrs/srs:$SRS_TAG + docker tag ossrs/srs:$SRS_TAG registry.cn-hangzhou.aliyuncs.com/ossrs/srs:$SRS_MAJOR + docker push registry.cn-hangzhou.aliyuncs.com/ossrs/srs:$SRS_MAJOR + + - name: Setup KUBCONFIG for Aliyun ACK + shell: bash + run: |- + KUBECONFIG=$RUNNER_TEMP/kubeconfig_$(date +%s) + echo "${{ secrets.KUBCONFIG }}" > $KUBECONFIG + echo "KUBECONFIG=$KUBECONFIG" >> $GITHUB_ENV + + - name: Release SRS 3.0 to Aliyun ACK + shell: bash + if: ${{ startsWith(github.ref, 'refs/tags/v3') }} + run: |- + kubectl set image deploy/srs3-deploy srs=registry.cn-hangzhou.aliyuncs.com/ossrs/srs:$SRS_TAG + kubectl describe deploy/srs3-deploy \ No newline at end of file diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 000000000..291ee14f4 --- /dev/null +++ b/.github/workflows/test.yml @@ -0,0 +1,47 @@ +name: "Test" + +on: [push, pull_request] + +jobs: + utest: + name: actions-test-utest + runs-on: ubuntu-20.04 + + steps: + - name: Checkout repository + uses: actions/checkout@v2 + + ################################################################ + # Tests + - name: Build test image + run: docker build --tag srs:test -f trunk/Dockerfile.test . + # For utest + - name: Run SRS utest + run: docker run --rm srs:test bash -c 'make && ./objs/srs_utest' + coverage: + name: actions-test-coverage + runs-on: ubuntu-20.04 + + steps: + - name: Checkout repository + uses: actions/checkout@v2 + + ################################################################ + # Tests + - name: Build coverage image + run: docker build --tag srs:cov -f trunk/Dockerfile.cov . + # For coverage + - name: Run SRS covergae + if: ${{ startsWith(github.ref, 'refs/heads/') || startsWith(github.ref, 'refs/pull/') }} + run: | + # The hash of commit. + SRS_SHA=${{ github.sha }} + # Note that the root of SRS, must contains .git, for report fixing. + SRS_PROJECT=/srs + # The github.ref is, for example, refs/heads/3.0release + SRS_BRANCH=$(echo ${{ github.ref }}| awk -F 'refs/heads/' '{print $2}'| awk -F '/' '{print $1}') + # The github.ref is, for example, refs/pull/2536/merge + SRS_PR=$(echo ${{ github.ref }}| awk -F 'refs/pull/' '{print $2}'| awk -F '/' '{print $1}') + echo "For ref=${{ github.ref }}, sha=${{ github.sha }}, SRS_BRANCH=$SRS_BRANCH, SRS_PR=$SRS_PR, SRS_SHA=$SRS_SHA, SRS_PROJECT=$SRS_PROJECT" + docker run --rm --env SRS_BRANCH=$SRS_BRANCH --env SRS_PR=$SRS_PR --env SRS_SHA=$SRS_SHA --env SRS_PROJECT=$SRS_PROJECT \ + srs:cov bash -c 'make && ./objs/srs_utest && bash auto/coverage.sh' \ No newline at end of file diff --git a/.gitignore b/.gitignore index 99d59c8ef..df44b9255 100644 --- a/.gitignore +++ b/.gitignore @@ -15,13 +15,13 @@ # by winlin *.pyc *.swp +.DS_Store /trunk/Makefile /trunk/objs /trunk/src/build-qt-Desktop-Debug /trunk/research/librtmp/objs /trunk/3rdparty/ccache/ccache-3.1.9 /trunk/3rdparty/gprof/graphviz-2.36.0 -/trunk/research/api-server/static-dir/crossdomain.xml /trunk/research/api-server/static-dir/forward /trunk/research/api-server/static-dir/live /trunk/research/api-server/static-dir/players diff --git a/AUTHORS.txt b/AUTHORS.txt index 28b4c8526..3c50cd4ca 100644 --- a/AUTHORS.txt +++ b/AUTHORS.txt @@ -1,30 +1,57 @@ -There are three types of people that have contributed to the SRS project: -1. AUTHORS: Contribute important features. Names of all PRIMARY response in NetConnection.connect and metadata. -2. CONTRIBUTORS: Submit patches, report bugs, add translations, help answer newbie questions, and generally make SRS that much better. - -AUTHORS ordered by first contribution. -* winlin "Plan, arch, implement SRS1.0 and SRS2.0" -* wenjie.zhao<740936897@qq.com> "The bandwidth test module, HDS and bug fixed." - CONTRIBUTORS ordered by first contribution. -* xiangcheng.liu "Bug fixed" -* naijia.liu "Performance benchmark" -* alcoholyi "Bug fixed" -* byteman "Bug fixed" -* chad.wang "Bug fixed" -* suhetao "Bug fixed" -* Johnny "Create domain ossrs.net" -* karthikeyan "Bug fixed" -* StevenLiu "Build SRS on Darwin OSX" -* zhengfl "Bug fixed" -* tufang14 "Bug fixed" -* allspace "The srs-librtmp windows support" -* niesongsong "Configure support relative path" -* rudeb0t "Bug fixed" -* CallMeNP "Bug fixed" -* synote "Bug fixed" -* lovecat "Bug fixed" -* panda1986<542638787@qq.com> "Bug fixed" -* YueHonghui "Bug fixed" -* JuntaoLiu "Bug fixed" - +* winlin +* wenjie.zhao<740936897@qq.com> +* xiangcheng.liu +* naijia.liu +* alcoholyi +* byteman +* chad.wang +* suhetao +* Johnny +* karthikeyan +* StevenLiu +* zhengfl +* tufang14 +* allspace +* niesongsong +* rudeb0t +* CallMeNP +* synote +* lovecat +* panda1986<542638787@qq.com> +* YueHonghui +* ThomasDreibholz +* JuntaoLiu +* RocFang +* MakarovYaroslav +* MirkoVelic +* HuiZhang(huzhang2) +* OtterWa +* walkermi<172192667@qq.com> +* haofz +* ME_Kun_Han +* ljx0305 +* cenxinwei +* StarBrilliant +* xubin +* intliang +* flowerwrong +* YLX<568414379@qq.com> +* J +* Harlan +* hankun +* JonathanBarratt +* KeeganH +* StevenLiu +* liuxc0116 +* ChengdongZhang +* lovacat +* qiang.li +* HungMingWu +* Himer +* xialixin +* alphonsetai +* Michael.Ma +* lam2003 +* l<22312935+lam2003@users.noreply.github.com> +* xfalcon diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md deleted file mode 100644 index a8d5e24b7..000000000 --- a/CODE_OF_CONDUCT.md +++ /dev/null @@ -1,46 +0,0 @@ -# Contributor Covenant Code of Conduct - -## Our Pledge - -In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation. - -## Our Standards - -Examples of behavior that contributes to creating a positive environment include: - -* Using welcoming and inclusive language -* Being respectful of differing viewpoints and experiences -* Gracefully accepting constructive criticism -* Focusing on what is best for the community -* Showing empathy towards other community members - -Examples of unacceptable behavior by participants include: - -* The use of sexualized language or imagery and unwelcome sexual attention or advances -* Trolling, insulting/derogatory comments, and personal or political attacks -* Public or private harassment -* Publishing others' private information, such as a physical or electronic address, without explicit permission -* Other conduct which could reasonably be considered inappropriate in a professional setting - -## Our Responsibilities - -Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior. - -Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful. - -## Scope - -This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers. - -## Enforcement - -Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at winlin@vip.126.com. The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately. - -Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership. - -## Attribution - -This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version] - -[homepage]: http://contributor-covenant.org -[version]: http://contributor-covenant.org/version/1/4/ diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md deleted file mode 100644 index f5de7090b..000000000 --- a/CONTRIBUTING.md +++ /dev/null @@ -1,3 +0,0 @@ -First of all, thank you for considering contributing to SRS. It's people like you that make SRS a great live streaming cluster. - -Pelease read https://github.com/activeadmin/activeadmin/blob/master/CONTRIBUTING.md diff --git a/DONATIONS.txt b/DONATIONS.txt deleted file mode 100644 index 582b3dd64..000000000 --- a/DONATIONS.txt +++ /dev/null @@ -1,62 +0,0 @@ -Donations ordered by first donation. - -=========================================================== -2015 - -RMB 10000+ -* [2015-03-03 13:25] 郭强 - -RMB 1000-9999 -* [2015-05-07 11:28] 冯彬 -* [2015-04-29 09:20] 王光辉 -* [2015-04-04 16:19] 蔡汉城 - -RMB 500-999 -* [2015-04-11 12:48] 丁一 - -RMB 100-499 -* [2015-05-07 23:56] 余世兵 -* [2015-05-07 11:34] 王隆萼 -* [2015-05-06 22:04] 姜庆东 -* [2015-04-10 19:52] 阳成飞 -* [2015-03-30 13:34] 扶凯 -* [2015-03-29 11-07] 姚伟斌 -* [2015-03-14 20:21] 万伟 -* [2015-03-11 09:44] 叶发养 -* [2015-02-08 21:10] 韩友洪 -* [2015-01-09 16:08] 李理 - -RMB 50-99 -* [2015-03-03 17:30] flybird - -RMB 0.01-49 -* [2015-05-07 11:34] 周鹤 - -=========================================================== -2014 - -RMB 1000+ -* [2014-04-25 13:21] 刘连响 -* [2014-04-25 13:31] 郭强 -* [2014-07-30 11:29] 周凯 -* [2014-08-15 10:55] 雷健 - -RMB 500-999 -* [2014-07-24 08:52] 黄英才 - -RMB 100-499 -* [2014-04-25 13:25] 张瑞圣 -* [2014-05-12 10:22] 陈晨 -* [2014-08-07 22:56] 陈亮 -* [2014-08-19 20:00] Matthew -* [2014-09-05 16:13] 于冰 -* [2014-11-19 22:38] 夏江龙 - -RMB 50-99 -* [2014-06-17 17:57] 陈江兵 -* [2014-08-04 10:47] 宋志 -* [2014-08-15 13:31] ZACH - -RMB 0.01-49 -* [2014-08-20 20:13] 林瑞潮 - diff --git a/LICENSE b/LICENSE index ce79e6ca5..ed59f8cfc 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ The MIT License (MIT) -Copyright (c) 2013-2015 SRS(ossrs) +Copyright (c) 2013-2020 Winlin Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in diff --git a/README.md b/README.md index 27f770e4a..39d262cf4 100755 --- a/README.md +++ b/README.md @@ -1,346 +1,343 @@ -# Simple-RTMP-Server +# SRS(Simple Realtime Server) -![](http://ossrs.net:8000/gif/v1/sls.gif?site=github.com&path=/srs/srs2) -[![CircleCI](https://circleci.com/gh/ossrs/srs/tree/master.svg?style=svg&circle-token=1ef1d5b5b0cde6c8c282ed856a18199f9e8f85a9)](https://circleci.com/gh/ossrs/srs/tree/master) -[![Wechat](https://cloud.githubusercontent.com/assets/2777660/22814959/c51cbe72-ef92-11e6-81cc-32b657b285d5.png)](https://github.com/ossrs/srs/wiki/v1_CN_Contact#wechat) -[Skype](https://github.com/ossrs/srs/wiki/v1_EN_Contact#skype-or-gitter) +![](http://ossrs.net/gif/v1/sls.gif?site=github.com&path=/srs/srs3) +[![](https://github.com/ossrs/srs/actions/workflows/test.yml/badge.svg?branch=3.0release)](https://github.com/ossrs/srs/actions?query=workflow%3ATest+branch%3A3.0release) +[![](https://github.com/ossrs/srs/actions/workflows/release.yml/badge.svg)](https://github.com/ossrs/srs/actions?query=workflow%3ARelease) +[![](https://codecov.io/gh/ossrs/srs/branch/3.0release/graph/badge.svg)](https://codecov.io/gh/ossrs/srs/branch/3.0release) +[![](https://cloud.githubusercontent.com/assets/2777660/22814959/c51cbe72-ef92-11e6-81cc-32b657b285d5.png)](https://github.com/ossrs/srs/wiki/v1_CN_Contact#wechat) -SRS/2.0, [ZhouGuowen][release2] +SRS/3.0,[OuXuli][release3],是一个简单高效的实时视频服务器,支持RTMP/HLS/HTTP-FLV。 -SRS定位是运营级的互联网直播服务器集群,追求更好的概念完整性和最简单实现的代码。
-SRS is an industrial-strength live streaming cluster, with the best conceptual integrity and the simplest implementation. +SRS is a simple, high efficiency and realtime video server, supports RTMP/HLS/HTTP-FLV. -Download from github.io: [Centos6-x86_64][centos0], [more...][more0]
-Download from ossrs.net: [Centos6-x86_64][centos1], [more...][more1]
-Website for SRS/2.0, read SRS 2.0 [Chinese][srs_CN] or [English][srs_EN]. - -## Why SRS? - -1. We completely rewrote HLS following m3u8/ts spec, and HLS support h.264+aac/mp3. -1. Highly efficient RTMP streaming, supporting 7k+ clients concurrency, vhost based, both origin and edge. -1. Embedded simplified media HTTP server for HLS, api and HTTP flv/ts/mp3/aac streaming. -1. Variety input: RTMP, pull by ingest file or stream(HTTP/RTMP/RTSP), push by stream caster -RTSP/MPEGTS-over-UDP. -1. Popular internet delivery: RTMP/HDS for flash, HLS for mobile(IOS/IPad/MAC/Android), HTTP -flv/ts/mp3/aac streaming for user prefered. -1. Enhanced DVR and hstrs: segment/session/append plan, customer path and HTTP callback. -the hstrs(http stream trigger rtmp source) enable the http-flv stream standby util encoder -start publish, similar to rtmp, which will trigger edge to fetch from origin. -1. Multiple feature: transcode, forward, ingest, http hooks, dvr, hls, rtsp, http streaming, -http api, refer, log, bandwith test and srs-librtmp. -1. Best maintainess: simple arch over state-threads(coroutine), single thread, single process -and for linux/osx platform, common server x86-64/i386/arm/mips cpus, rich comments, strictly -follows RTMP/HLS/RTSP spec. -1. Easy to use: both English and Chinese wiki, typically config files in trunk/conf, traceable -and session based log, linux service script and install script. -1. MIT license, open source with product management and evolution. - -Enjoy it! - -## About - -SRS(Simple RTMP Server) over state-threads created on 2013.10. - -SRS delivers rtmp/hls/http/hds live on x86/x64/arm/mips linux/osx, -supports origin/edge/vhost and transcode/ingest and dvr/forward -and http-api/http-callback/reload, introduces tracable -session-oriented log, exports client srs-librtmp, -with stream caster to push MPEGTS-over-UDP/RTSP to SRS, -provides EN/CN wiki and the most simple architecture. - -## AUTHORS - -There are two types of people that have contributed to the SRS project: -* AUTHORS: Contribute important features. Names of all -PRIMARY response in NetConnection.connect and metadata. -* CONTRIBUTORS: Submit patches, report bugs, add translations, help answer -newbie questions, and generally make SRS that much better. - -About all PRIMARY, AUTHORS and CONTRIBUTORS, read [AUTHORS.txt][authors]. - -A big THANK YOU goes to: -* All friends of SRS for [big supports][bigthanks]. -* Genes amd Mabbott for creating [st][st]([state-threads][st2]). -* Michael Talyanksy for introducing us to use st. -* Roman Arutyunyan for creating [nginx-rtmp][nginx-rtmp] for SRS to refer to. -* Joyent for creating [http-parser][http-parser] for http-api for SRS. -* Igor Sysoev for creating [nginx][nginx] for SRS to refer to. -* [FFMPEG][FFMPEG] and [libx264][libx264] group for SRS to use to transcode. -* Guido van Rossum for creating Python for api-server for SRS. - -## Mirrors - -Github: [https://github.com/ossrs/srs][srs], the GIT usage([CN][v1_CN_Git], [EN][v1_EN_Git]) - -``` -git clone https://github.com/ossrs/srs.git -``` - -OSChina: [http://git.oschina.net/winlinvip/srs.oschina][oschina], the GIT usage([CN][v1_CN_Git], [EN][v1_EN_Git]) - -``` -git clone https://git.oschina.net/winlinvip/srs.oschina.git -``` - -Gitlab: [https://gitlab.com/winlinvip/srs-gitlab][gitlab], the GIT usage([CN][v1_CN_Git], [EN][v1_EN_Git]) - -``` -git clone https://gitlab.com/winlinvip/srs-gitlab.git -``` +> Remark: Although SRS is licenced under [MIT][LICENSE], but there are some depended libraries which are distributed using their own licenses, please read [License Mixing][LicenseMixing]. + ## Usage -Step 1: get SRS +Build SRS from source, please read **Wiki: Gettting Started( [EN](https://github.com/ossrs/srs/wiki/v3_EN_Home#getting-started) / [CN](https://github.com/ossrs/srs/wiki/v3_CN_Home#getting-started) )**: -
-git clone https://github.com/ossrs/srs &&
-cd srs/trunk
-
+``` +git clone -b 3.0release https://gitee.com/ossrs/srs.git && +cd srs/trunk && ./configure && make && ./objs/srs -c conf/srs.conf +``` -Step 2: build SRS, -Requires Centos6.x/Ubuntu12 32/64bits, for others OS recommend [docker][docker], see Build([CN][v2_CN_Build],[EN][v2_EN_Build]). +Open [http://localhost:8080/](http://localhost:8080/) to check it, then publish +by [FFmpeg](https://ffmpeg.org/download.html) or [OBS](https://obsproject.com/download) as: -
-./configure && make
-
+```bash +ffmpeg -re -i ./doc/source.flv -c copy -f flv -y rtmp://localhost/live/livestream +``` -Step 3: start SRS +Play the following streams by [players](https://ossrs.net): -
-./objs/srs -c conf/srs.conf
-
+* VLC(RTMP): rtmp://localhost/live/livestream +* H5(HTTP-FLV): [http://localhost:8080/live/livestream.flv](http://localhost:8080/players/srs_player.html?autostart=true&stream=livestream.flv&port=8080&schema=http) +* H5(HLS): [http://localhost:8080/live/livestream.m3u8](http://localhost:8080/players/srs_player.html?autostart=true&stream=livestream.m3u8&port=8080&schema=http) -See also: -* Usage: How to delivery RTMP?([CN][v1_CN_SampleRTMP], [EN][v1_EN_SampleRTMP]) -* Usage: How to delivery RTMP Cluster?([CN][v1_CN_SampleRTMPCluster], [EN][v1_EN_SampleRTMPCluster]) -* Usage: How to delivery HTTP FLV Live Streaming?([CN][v2_CN_SampleHttpFlv], [EN][v2_EN_SampleHttpFlv]) -* Usage: How to delivery HTTP FLV Live Streaming Cluster?([CN][v2_CN_SampleHttpFlvCluster], [EN][v2_EN_SampleHttpFlvCluster]) -* Usage: How to delivery HLS?([CN][v2_CN_SampleHLS], [EN][v2_EN_SampleHLS]) -* Usage: How to delivery HLS for other codec?([CN][v2_CN_SampleTranscode2HLS], [EN][v2_EN_SampleTranscode2HLS]) -* Usage: How to transode RTMP stream by SRS?([CN][v2_CN_SampleFFMPEG], [EN][v2_EN_SampleFFMPEG]) -* Usage: How to forward stream to other server?([CN][v1_CN_SampleForward], [EN][v1_EN_SampleForward]) -* Usage: How to deploy low lantency application?([CN][v2_CN_SampleRealtime], [EN][v2_EN_SampleRealtime]) -* Usage: How to deploy SRS on ARM?([CN][v1_CN_SampleARM], [EN][v1_EN_SampleARM]) -* Usage: How to ingest file/stream/device to SRS?([CN][v1_CN_SampleIngest], [EN][v1_EN_SampleIngest]) -* Usage: How to use SRS-HTTP-server to delivery HTTP/HLS stream?([CN][v2_CN_SampleHTTP], [EN][v2_EN_SampleHTTP]) -* Usage: How to show the demo of ([CN][v1_CN_SampleDemo], [EN][v1_EN_SampleDemo]) -* Usage: How to publish h.264 raw stream to [CN][v2_CN_SrsLibrtmp2], [EN][v2_EN_SrsLibrtmp2]) -* Usage: Solution using SRS?([CN][v1_CN_Sample], [EN][v1_EN_Sample]) -* Usage: Why SRS?([CN][v1_CN_Product], [EN][v1_EN_Product]) + + -## Wiki +From here, please read wikis: -SRS 1.0 wiki +* [Getting Started](https://github.com/ossrs/srs/wiki/v3_EN_Home#getting-started), please read Wiki first. +* [中文文档:起步](https://github.com/ossrs/srs/wiki/v3_CN_Home#getting-started),不读Wiki一定扑街,不读文档请不要提Issue,不读文档请不要提问题,任何文档中明确说过的疑问都不会解答。 -Please select your language: -* [SRS 1.0 English][v1_EN_Home] -* [SRS 1.0 Chinese][v1_CN_Home] +Fast index for Wikis: -SRS 2.0 wiki +* Overview? ([CN][v3_CN_Home], [EN][v3_EN_Home]) +* How to deliver RTMP streaming?([CN][v1_CN_SampleRTMP], [EN][v1_EN_SampleRTMP]) +* How to build RTMP Edge-Cluster?([CN][v3_CN_SampleRTMPCluster], [EN][v3_EN_SampleRTMPCluster]) +* How to build RTMP Origin-Cluster?([CN][v3_CN_SampleOriginCluster], [EN][v3_EN_SampleOriginCluster]) +* How to deliver HTTP-FLV streaming?([CN][v3_CN_SampleHttpFlv], [EN][v3_EN_SampleHttpFlv]) +* How to deliver HLS streaming?([CN][v3_CN_SampleHLS], [EN][v3_EN_SampleHLS]) +* How to deliver low-latency streaming?([CN][v3_CN_SampleRealtime], [EN][v3_EN_SampleRealtime]) -Please select your language: -* [SRS 2.0 English][v2_EN_Home] -* [SRS 2.0 Chinese][v2_CN_Home] +Other important wiki: -## System Requirements -Supported operating systems and hardware: -* All Linux , both 32 and 64 bits -* Apple OSX(Darwin), both 32 and 64bits. -* All hardware with x86/x86_64/arm/mips cpu. +* Usage: How to transode RTMP stream by FFMPEG?([CN][v2_CN_SampleFFMPEG], [EN][v2_EN_SampleFFMPEG]) +* Usage: How to delivery HTTP FLV Live Streaming Cluster?([CN][v3_CN_SampleHttpFlvCluster], [EN][v3_EN_SampleHttpFlvCluster]) +* Usage: How to ingest file/stream/device to RTMP?([CN][v1_CN_SampleIngest], [EN][v1_EN_SampleIngest]) +* Usage: How to forward stream to other servers?([CN][v3_CN_SampleForward], [EN][v3_EN_SampleForward]) +* Usage: How to improve edge performance for multiple CPUs? ([CN][v3_CN_REUSEPORT], [EN][v3_EN_REUSEPORT]) +* Usage: How to file a bug or contact us? ([CN][v1_CN_Contact], [EN][v1_EN_Contact]) ## Features -1. Simple, also stable enough. -1. High-performance([CN][v1_CN_Performance], [EN][v1_EN_Performance]): single-thread, async socket, event/st-thread driven. -1. High-concurrency([CN][v1_CN_Performance], [EN][v1_EN_Performance]), 6000+ connections(500kbps), 900Mbps, CPU 90.2%, 41MB -1. Support RTMP Origin Server([CN][v1_CN_DeliveryRTMP],[EN][v1_EN_DeliveryRTMP]) -1. Support RTMP Edge Server([CN][v2_CN_Edge], [EN][v2_EN_Edge]) for CDN, push/pull stream from any RTMP server -1. Support single process; no multiple processes. -1. Support Vhost([CN][v1_CN_RtmpUrlVhost], [EN][v1_EN_RtmpUrlVhost]), support \_\_defaultVhost\_\_. -1. Support RTMP([CN][v1_CN_DeliveryRTMP], [EN][v1_EN_DeliveryRTMP]) live streaming; no vod streaming. -1. Support Apple HLS(m3u8)([CN][v2_CN_DeliveryHLS], [EN][v2_EN_DeliveryHLS]) live streaming. -1. Support HLS audio-only([CN][v2_CN_DeliveryHLS2], [EN][v2_EN_DeliveryHLS2]) live streaming. -1. Support Reload([CN][v1_CN_Reload], [EN][v1_EN_Reload]) config to enable changes. -1. Support cache last gop([CN][v2_CN_LowLatency2], [EN][v2_EN_LowLatency2]) for flash player to fast startup. -1. Support listen at multiple ports. -1. Support long time(>4.6hours) publish/play. -1. Support Forward([CN][v1_CN_Forward], [EN][v1_EN_Forward]) in master-slave mode. -1. Support live stream Transcoding([CN][v2_CN_FFMPEG], [EN][v2_EN_FFMPEG]) by ffmpeg. -1. Support ffmpeg([CN][v2_CN_FFMPEG], [EN][v2_EN_FFMPEG]) filters(logo/overlay/crop), x264 params, copy/vn/an. -1. Support audio transcode([CN][v2_CN_FFMPEG], [EN][v2_EN_FFMPEG]) only, speex/mp3 to aac -1. Support http callback api hooks([CN][v2_CN_HTTPCallback], [EN][v2_EN_HTTPCallback])(for authentication and injection). -1. Support bandwidth test([CN][v1_CN_BandwidthTestTool], [EN][v1_EN_BandwidthTestTool]) api and flash client. -1. Player, publisher(encoder), and demo pages(jquery+bootstrap)([CN][v1_CN_SampleDemo],[EN][v1_EN_SampleDemo]). -1. Demo([CN][v1_CN_SampleDemo], [EN][v1_EN_SampleDemo]) video meeting or chat(SRS+cherrypy+jquery+bootstrap). -1. Full documents in wiki([CN][v2_CN_Home], [EN][v2_EN_Home]), both Chinese and English. -1. Support RTMP(play-publish) library: srs-librtmp([CN][v2_CN_SrsLibrtmp], [EN][v2_EN_SrsLibrtmp]) -1. Support ARM cpu arch([CN][v1_CN_SrsLinuxArm], [EN][v1_EN_SrsLinuxArm]) with rtmp/ssl/hls/librtmp. -1. Support init.d([CN][v1_CN_LinuxService], [EN][v1_EN_LinuxService]) and packge script, log to file. -1. Support RTMP ATC([CN][v1_CN_RTMP-ATC], [EN][v1_EN_RTMP-ATC]) for HLS/HDS to support backup(failover) -1. Support HTTP RESTful management api([CN][v2_CN_HTTPApi], [EN][v2_EN_HTTPApi]). -1. Support Ingest([CN][v1_CN_Ingest], [EN][v1_EN_Ingest]) FILE/HTTP/RTMP/RTSP(RTP, SDP) to RTMP using external tools(e.g ffmepg). -1. Support DVR([CN][v2_CN_DVR], [EN][v2_EN_DVR]), record live to flv file for vod. -1. Support tracable log, session based log([CN][v1_CN_SrsLog], [EN][v1_EN_SrsLog]). -1. Support DRM token traverse([CN][v1_CN_DRM2], [EN][v1_EN_DRM2]) for fms origin authenticate. -1. Support system full utest on gtest. -1. Support embeded HTTP server([CN][v2_CN_SampleHTTP], [EN][v2_EN_SampleHTTP]) for hls(live/vod) -1. Support vod stream(http flv/hls vod stream)([CN][v2_CN_FlvVodStream], [EN][v2_EN_FlvVodStream]). -1. Stable [1.0release branch][branch1]. -1. Support publish h264 raw stream([CN][v2_CN_SrsLibrtmp2], [EN][v2_EN_SrsLibrtmp2]) by srs-librtmp. -1. Support [6k+ clients][bug #194], 3Gbps per process. -1. Suppport [English wiki][v2_EN_Home]. -1. Research and simplify st, [bug #182][bug #182]. -1. Support compile [srs-librtmp on windows][srs-librtmp], [bug #213][bug #213]. -1. Support [10k+ clients][bug #251], 4Gbps per process. -1. Support publish aac adts raw stream([CN][v2_CN_SrsLibrtmp3], [EN][v2_EN_SrsLibrtmp3]) by srs-librtmp. -1. Support 0.1s+ latency, read [bug #257][bug #257]. -1. Support allow/deny publish/play for all or specified ip([CN][v2_CN_Security], [EN][v2_EN_Security]). -1. Support custom dvr path and http callback, read [bug #179][bug #179] and [bug #274][bug #274]. -1. Support rtmp remux to http flv/mp3/aac/ts live stream, read [bug #293][bug #293]([CN][v2_CN_DeliveryHttpStream], [EN][v2_CN_DeliveryHttpStream]). -1. Support HLS(h.264+mp3) streaming, read [bug #301][bug #301]. -1. Rewrite HLS(h.264+aac/mp3) streaming, read [bug #304][bug #304]. -1. [dev] Realease [2.0release branch][branch2]. -1. [experiment] Support Adobe HDS(f4m)([CN][v2_CN_DeliveryHDS], [EN][v2_EN_DeliveryHDS]) dynamic streaming. -1. [experiment] Support push MPEG-TS over UDP to SRS, read [bug #250][bug #250]. -1. [experiment] Support push RTSP to SRS, read [bug #133][bug #133]. -1. [experiment] Support push flv stream over HTTP POST to SRS, read [wiki]([CN][v2_CN_Streamer2], [EN][v2_EN_Streamer2]). -1. [experiment] Support [srs-dolphin][srs-dolphin], the multiple-process SRS. -1. [experiment] Support [remote console][console], read [srs-ngb][srs-ngb]. +- [x] Using coroutine by ST, it's really simple and stupid enough. +- [x] Support cluster which consists of origin ([CN][v1_CN_DeliveryRTMP],[EN][v1_EN_DeliveryRTMP]) and edge([CN][v3_CN_Edge], [EN][v3_EN_Edge]) server and uses RTMP as default transport protocol. +- [x] Origin server supports remuxing RTMP to HTTP-FLV([CN][v3_CN_SampleHttpFlv], [EN][v3_EN_SampleHttpFlv]) and HLS([CN][v3_CN_DeliveryHLS], [EN][v3_EN_DeliveryHLS]). +- [x] Edge server supports remuxing RTMP to HTTP-FLV([CN][v3_CN_SampleHttpFlv], [EN][v3_EN_SampleHttpFlv]). As for HLS([CN][v3_CN_DeliveryHLS], [EN][v3_EN_DeliveryHLS]) edge server, recomment to use HTTP edge server, such as [NGINX](http://nginx.org/). +- [x] Support HLS with audio-only([CN][v3_CN_DeliveryHLS2], [EN][v3_EN_DeliveryHLS2]), which need to build the timestamp from AAC samples, so we enhanced it please read [#547][bug #547]. +- [x] Support HLS with mp3(h.264+mp3) audio codec, please read [bug #301][bug #301]. +- [x] Support remuxing RTMP to http FLV/MP3/AAC/TS live streaming, please read wiki([CN][v2_CN_DeliveryHttpStream], [EN][v2_CN_DeliveryHttpStream]). +- [x] Support ingesting([CN][v1_CN_Ingest], [EN][v1_EN_Ingest]) other protocols to SRS by FFMPEG. +- [x] Support RTMP long time(>4.6hours) publishing/playing, with the timestamp corrected. +- [x] Support publishing h264 raw stream([CN][v3_CN_SrsLibrtmp2], [EN][v3_EN_SrsLibrtmp2]) by srs-librtmp([CN][v3_CN_SrsLibrtmp], [EN][v3_EN_SrsLibrtmp]). +- [x] Support publishing aac adts raw stream([CN][v3_CN_SrsLibrtmp3], [EN][v3_EN_SrsLibrtmp3]) by srs-librtmp([CN][v3_CN_SrsLibrtmp], [EN][v3_EN_SrsLibrtmp]). +- [x] Support native HTTP server([CN][v3_CN_SampleHTTP], [EN][v3_EN_SampleHTTP]) for http api and http live streaming. +- [x] Support HTTP CORS for js in http api and http live streaming. +- [x] Support HTTP API([CN][v3_CN_HTTPApi], [EN][v3_EN_HTTPApi]) for system management. +- [x] Support HTTP callback([CN][v3_CN_HTTPCallback], [EN][v3_EN_HTTPCallback]) for authentication and integration. +- [x] Support DVR([CN][v3_CN_DVR], [EN][v3_EN_DVR]) to record live streaming to FLV file. +- [x] Support DVR control module like NGINX-RTMP, please read [#459][bug #459]. +- [x] Support EXEC like NGINX-RTMP, please read [bug #367][bug #367]. +- [x] Support security strategy including allow/deny publish/play IP([CN][v2_CN_Security], [EN][v2_EN_Security]). +- [x] Support low latency(0.1s+) transport model, please read [bug #257][bug #257]. +- [x] Support gop-cache([CN][v3_CN_LowLatency2], [EN][v3_EN_LowLatency2]) for player fast startup. +- [x] Support Vhost([CN][v1_CN_RtmpUrlVhost], [EN][v1_EN_RtmpUrlVhost]) and \_\_defaultVhost\_\_. +- [x] Support reloading([CN][v1_CN_Reload], [EN][v1_EN_Reload]) to apply changes of config. +- [x] Support listening at multiple ports. +- [x] Support forwarding([CN][v3_CN_Forward], [EN][v3_EN_Forward]) from master to slave server. +- [x] Support transcoding([CN][v3_CN_FFMPEG], [EN][v3_EN_FFMPEG]) live streaming by FFMPEG. +- [x] All wikis are writen in [Chinese][v3_CN_Home] and [English][v3_EN_Home]. +- [x] Enhanced json, replace NXJSON(LGPL) with json-parser(BSD), read [#904][bug #904]. +- [x] Support valgrind and latest ARM by patching ST, read [ST#1](https://github.com/ossrs/state-threads/issues/1) and [ST#2](https://github.com/ossrs/state-threads/issues/2). +- [x] Support tracable and session-based log([CN][v1_CN_SrsLog], [EN][v1_EN_SrsLog]). +- [x] High concurrency and performance([CN][v1_CN_Performance], [EN][v1_EN_Performance]), 6000+ connections(200kbps), CPU 82%, 203MB. +- [x] Enhanced complex error code with description and stack, read [#913][bug #913]. +- [x] Enhanced RTMP url which supports vhost in stream, read [#1059][bug #1059]. +- [x] Support origin cluster, please read [#464][bug #464], [RTMP 302][bug #92]. +- [x] Support listen at IPv4 and IPv6, read [#460][bug #460]. +- [x] Support SO_REUSEPORT, to improve edge server performance, read [#775][bug #775]. +- [x] Improve test coverage for core/kernel/protocol/service. +- [x] [Experimental] Support docker by [srs-docker](https://github.com/ossrs/srs-docker). +- [x] [Experimental] Support DVR in MP4 format, read [#738][bug #738]. +- [x] [Experimental] Support MPEG-DASH, the future live streaming protocol, read [#299][bug #299]. +- [x] [Experimental] Support pushing MPEG-TS over UDP, please read [bug #250][bug #250]. +- [x] [Experimental] Support pushing RTSP, please read [bug #133][bug #133]. +- [x] [Experimental] Support pushing FLV over HTTP POST, please read [wiki]([CN][v2_CN_Streamer2], [EN][v2_EN_Streamer2]). +- [x] [Experimental] Support multiple processes by [dolphin][srs-dolphin] or [oryx][oryx]. +- [x] [Experimental] Support a simple [mgmt console][console], please read [srs-ngb][srs-ngb]. +- [x] [Experimental] Support RTMP client library: srs-librtmp([CN][v3_CN_SrsLibrtmp], [EN][v3_EN_SrsLibrtmp]) +- [x] [Experimental] Support HTTP RAW API, please read [#459][bug #459], [#470][bug #470], [#319][bug #319]. +- [x] [Deprecated] Support Adobe HDS(f4m), please read wiki([CN][v2_CN_DeliveryHDS], [EN][v2_EN_DeliveryHDS]) and [#1535][bug #1535]. +- [x] [Deprecated] Support bandwidth testing([CN][v1_CN_BandwidthTestTool], [EN][v1_EN_BandwidthTestTool]), please read [#1535][bug #1535]. +- [x] [Deprecated] Support Adobe FMS/AMS token traverse([CN][v3_CN_DRM2], [EN][v3_EN_DRM2]) authentication, please read [#1535][bug #1535]. +- [ ] Enhanced forwarding with vhost and variables. +- [ ] Support source cleanup for idle streams. +- [ ] Support H.265 by pushing H.265 over RTMP, deliverying in HLS, read [#465][bug #465]. +- [ ] Support HLS+, the HLS edge server, please read [#466][bug #466] and [#468][bug #468]. +- [ ] Support UDP protocol such as QUIC or KCP in cluster. +- [ ] Support H.264+Opus codec for WebRTC. +- [ ] Support publishing stream by WebRTC. +- [ ] Support playing stream by WebRTC. -## Compare +> Remark: About the milestone and product plan, please read ([CN][v1_CN_Product], [EN][v1_EN_Product]) wiki. -Compare SRS with other media server. + + -### Stream Delivery +## V3 changes -| Feature | SRS | NGINX | CRTMPD | FMS | WOWZA | -| ----------- | ------- | ----- | --------- | -------- | ------ | -| RTMP | Stable | Stable | Stable | Stable | Stable | -| HLS | Stable | Stable | X | Stable | Stable | -| HDS | Experiment| X | X | Stable | Stable | -| HTTP FLV | Stable | X | X | X | X | -| HLS(aonly) | Stable | X | X | Stable | Stable | -| HTTP Server | Stable | Stable | X | X | Stable | +* v3.0, 2021-10-08, Fix [#2606](https://github.com/ossrs/srs/issues/2606): Memory leak for RTMP client, pick from 4.0. v3.0.170 +* v3.0, 2021-08-14, [3.0 release8(3.0.168)](https://github.com/ossrs/srs/releases/tag/v3.0-r8) released. 124469 lines. +* v3.0, 2021-07-04, [3.0 release7(3.0.164)](https://github.com/ossrs/srs/releases/tag/v3.0-r7) released. 123463 lines. +* v3.0, 2021-07-04, For [#2424](https://github.com/ossrs/srs/issues/2424), use srandom/random to generate. 3.0.164 +* v3.0, 2021-06-26, [3.0 release6(3.0.163)](https://github.com/ossrs/srs/releases/tag/v3.0-r6) released. 123011 lines. +* v3.0, 2021-06-26, For [#2424](https://github.com/ossrs/srs/issues/2424), query the latest available version. 3.0.163 +* v3.0, 2021-05-12, Fix [#2311][bug #2311], Copy the request for stat client. 3.0.162 +* v3.0, 2021-04-28, [3.0 release5(3.0.161)][r3.0r5] released. 122750 lines. +* v3.0, 2021-04-28, Upgrade players. 3.0.161 +* v3.0, 2021-04-24, [3.0 release4(3.0.160)][r3.0r4] released. 122750 lines. +* v3.0, 2021-04-24, Package players and console to zip and docker. 3.0.160 +* v3.0, 2021-04-24, Add srs-console to research/console. 3.0.159 +* v3.0, 2021-03-05, Refine usage to docker by default. 3.0.158 +* v3.0, 2021-01-07, Change id from int to string for the statistics. 3.0.157 +* v3.0, 2021-01-02, [3.0 release3(3.0.156)][r3.0r3] released. 122736 lines. +* v3.0, 2020-12-26, For RTMP edge/forward, pass vhost in tcUrl, not in stream. 3.0.156 +* v3.0, 2020-12-17, Fix [#1694][bug #1694], Support DVR 2GB+ MP4 file. 3.0.155 +* v3.0, 2020-12-17, Fix [#1548][bug #1548], Add edts in MP4 for Windows10. 3.0.154 +* v3.0, 2020-10-31, [3.0 release2(3.0.153)][r3.0r2] released. 122663 lines. +* v3.0, 2020-10-31, Fix [#509][bug #509], Always malloc stack on heap. 3.0.153 +* v3.0, 2020-10-31, Remove some global elements for debugging. 3.0.152 +* v3.0, 2020-10-31, Use global _srs_server for debugging. 3.0.151 +* v3.0, 2020-10-31, Refine source cid, track previous one. 3.0.150 +* v3.0, 2020-10-25, Add hls.realtime.conf for low-latency HLS. 3.0.149 +* v3.0, 2020-10-24, Refine script and startup logs. 3.0.148 +* v3.0, 2020-10-23, Allow FFmpeg if exists at /usr/local/bin/ffmpeg. 3.0.147 +* v3.0, 2020-10-23, Refine build script, use libssl in docker. 3.0.146 +* v3.0, 2020-10-14, Fix [#1987][bug #1987], Fix Kbps resample bug. 3.0.145 +* v3.0, 2020-10-10, [3.0 release1(3.0.144)][r3.0r1] released. 122674 lines. +* v3.0, 2020-10-10, Fix [#1780][bug #1780], build fail on Ubuntu20(focal). 3.0.144 +* v3.0, 2020-09-14, Prevent stop ingest for multiple times. 3.0.143 +* v3.0, 2020-09-10, RTC: Change SO_REUSEPORT fail to warning. 3.0.142 +* v3.0, 2020-06-27, [3.0 release0(3.0.141)][r3.0r0] released. 122674 lines. +* v3.0, 2020-03-30, For [#1672][bug #1672], fix dvr close file failed bug. 3.0.140 +* v3.0, 2020-03-29, [3.0 beta4(3.0.139)][r3.0b4] released. 122674 lines. +* v3.0, 2020-03-28, Support multiple OS/Platform build cache. 3.0.139 +* v3.0, 2020-03-28, For [#1250][bug #1250], support macOS, OSX, MacbookPro, Apple Darwin. 3.0.138 +* v3.0, 2020-03-21, For [#1629][bug #1629], fix kickoff FLV client bug. 3.0.137 +* v3.0, 2020-03-21, For [#1619][bug #1619], configure without utest by default. 3.0.136 +* v3.0, 2020-03-21, For [#1651][bug #1651], fix return pnwrite of srs_write_large_iovs. 3.0.135 +* v3.0, 2020-03-18, [3.0 beta3(3.0.134)][r3.0b3] released. 122509 lines. +* v3.0, 2020-03-12, For [#1635][bug #1635], inotify watch ConfigMap for reload. 3.0.134 +* v3.0, 2020-03-12, For [#1635][bug #1635], support auto reaload config by inotify. 3.0.129 +* v3.0, 2020-03-12, For [#1630][bug #1630], disable cache for stream changing, and drop dup header. 3.0.128 +* v3.0, 2020-03-12, For [#1594][bug #1594], detect and disable daemon for docker. 3.0.127 +* v3.0, 2020-03-12, For [#1634][bug #1634], always check status in thread loop. 3.0.126 +* v3.0, 2020-03-11, For [#1634][bug #1634], refactor output with datetime for ingest/encoder/exec. 3.0.125 +* v3.0, 2020-03-11, For [#1634][bug #1634], fix quit by accident SIGTERM while killing FFMPEG. 3.0.124 +* v3.0, 2020-03-05, [3.0 beta2(3.0.123)][r3.0b2] released. 122170 lines. +* v3.0, 2020-02-21, For [#1598][bug #1598], support SLB health checking by TCP. 3.0.123 +* v3.0, 2020-02-21, Fix bug for librtmp client ipv4/ipv6 socket. 3.0.122 +* v3.0, 2020-02-18, For [#1579][bug #1579], support start/final wait for gracefully quit. 3.0.121 +* v3.0, 2020-02-18, For [#1579][bug #1579], support force gracefully quit. 3.0.120 +* v3.0, 2020-02-18, For [#1579][bug #1579], support gracefully quit. 3.0.119 +* v3.0, 2020-02-17, For [#1601][bug #1601], flush async on_dvr/on_hls events before stop. 3.0.118 +* v3.0, 2020-02-14, [3.0 beta1(3.0.117)][r3.0b1] released. 121964 lines. +* v3.0, 2020-02-14, For [#1595][bug #1595], migrating streaming from ossrs.net to r.ossrs.net. 3.0.117 +* v3.0, 2020-02-05, For [#665][bug #665], fix HTTP-FLV reloading bug. 3.0.116 +* v3.0, 2020-02-05, For [#1592][bug #1592], fix terminal echo off by redirect process stdin. 3.0.115 +* v3.0, 2020-02-04, For [#1186][bug #1186], refactor security check. 3.0.114 +* v3.0, 2020-02-04, Fix [#939][bug #939], response right A/V flag in FLV header. 3.0.113 +* v3.0, 2020-02-04, For [#939][bug #939], always enable fast FLV streaming. +* v3.0, 2020-02-02, [3.0 beta0(3.0.112)][r3.0b0] released. 121709 lines. +* v3.0, 2020-01-29, Support isolate version file. 3.0.112 +* v3.0, 2020-01-29, Fix [#1206][bug #1206], dispose ingester while server quiting. 3.0.111 +* v3.0, 2020-01-28, Fix [#1230][bug #1230], racing condition in source fetch or create. 3.0.110 +* v3.0, 2020-01-27, Fix [#1303][bug #1303], do not dispatch previous meta when not publishing. 3.0.109 +* v3.0, 2020-01-26, Allow use libst.so for ST is MPL license. +* v3.0, 2020-01-26, Fix [#607][bug #607], set RTMP identifying recursive depth to 3. +* v3.0, 2020-01-25, Fix [#878][bug #878], remove deprecated #EXT-X-ALLOW-CACHE for HLS. 3.0.108 +* v3.0, 2020-01-25, Fix [#703][bug #703], drop video data util sps/pps. 3.0.107 +* v3.0, 2020-01-25, Fix [#1108][bug #1108], reap DVR tmp file when unpublish. 3.0.106 +* v3.0, 2020-01-21, [3.0 alpha9(3.0.105)][r3.0a9] released. 121577 lines. +* v3.0, 2020-01-21, Fix [#1221][bug #1221], remove complex configure options. 3.0.104 +* v3.0, 2020-01-21, Fix [#1547][bug #1547], support crossbuild for ARM/MIPS. +* v3.0, 2020-01-21, For [#1547][bug #1547], support setting cc/cxx/ar/ld/randlib tools. 3.0.103 +* v3.0, 2020-01-19, For [#1580][bug #1580], fix cid range problem. 3.0.102 +* v3.0, 2020-01-19, For [#1070][bug #1070], define FLV CodecID for [AV1][bug #1070] and [opus][bug #307]. 3.0.101 +* v3.0, 2020-01-16, For [#1575][bug #1575], correct RTMP redirect as tcUrl, add redirect2 as RTMP URL. 3.0.100 +* v3.0, 2020-01-15, For [#1509][bug #1509], decrease the fast vector init size from 64KB to 64B. 3.0.99 +* v3.0, 2020-01-15, For [#1509][bug #1509], release coroutine when source is idle. 3.0.98 +* v3.0, 2020-01-10, [3.0 alpha8(3.0.97)][r3.0a8] released. 121555 lines. +* v3.0, 2020-01-09, For [#1042][bug #1042], improve test coverage for service. 3.0.97 +* v3.0, 2020-01-08, Merge [#1554][bug #1554], support logrotate copytruncate. 3.0.96 +* v3.0, 2020-01-05, Always use string instance to avoid crash risk. 3.0.95 +* v3.0, 2020-01-05, For [#460][bug #460], fix ipv6 hostport parsing bug. 3.0.94 +* v3.0, 2020-01-05, For [#460][bug #460], fix ipv6 intranet address filter bug. 3.0.93 +* v3.0, 2020-01-05, For [#1543][bug #1543], use getpeername to retrieve client ip. 3.0.92 +* v3.0, 2020-01-02, For [#1042][bug #1042], improve test coverage for config. 3.0.91 +* v3.0, 2019-12-30, Fix mp4 security issue, check buffer when required size is variable. +* v3.0, 2019-12-29, [3.0 alpha7(3.0.90)][r3.0a7] released. 116356 lines. +* v3.0, 2019-12-29, For [#1255][bug #1255], support vhost/domain in query string for HTTP streaming. 3.0.90 +* v3.0, 2019-12-29, For [#299][bug #299], increase dash segment size for avsync issue. 3.0.89 +* v3.0, 2019-12-27, For [#299][bug #299], fix some bugs in dash, it works now. 3.0.88 +* v3.0, 2019-12-27, For [#1544][bug #1544], fix memory leaking for complex error. 3.0.87 +* v3.0, 2019-12-27, Add links for flv.js, hls.js and dash.js. +* v3.0, 2019-12-26, For [#1105][bug #1105], http server support mp4 range. +* v3.0, 2019-12-26, For [#1105][bug #1105], dvr mp4 supports playing on Chrome/Safari/Firefox. 3.0.86 +* v3.0, 2019-12-26, [3.0 alpha6(3.0.85)][r3.0a6] released. 116056 lines. +* v3.0, 2019-12-26, For [#1488][bug #1488], pass client ip to http callback. 3.0.85 +* v3.0, 2019-12-25, For [#1537][bug #1537], [#1282][bug #1282], support aarch64 for armv8. 3.0.84 +* v3.0, 2019-12-25, For [#1538][bug #1538], fresh chunk allow fmt=0 or fmt=1. 3.0.83 +* v3.0, 2019-12-25, Remove FFMPEG and NGINX, please use [srs-docker](https://github.com/ossrs/srs-docker) instead. 3.0.82 +* v3.0, 2019-12-25, For [#1537][bug #1537], remove cross-build, not used patches, directly build st. +* v3.0, 2019-12-24, For [#1508][bug #1508], support chunk length and content in multiple parts. +* v3.0, 2019-12-23, Merge SRS2 for running srs-librtmp on Windows. 3.0.80 +* v3.0, 2019-12-23, For [#1535][bug #1535], deprecate Adobe FMS/AMS edge token traversing([CN][v3_CN_DRM2], [EN][v3_EN_DRM2]) authentication. 3.0.79 +* v3.0, 2019-12-23, For [#1535][bug #1535], deprecate BWT(bandwidth testing)([CN][v1_CN_BandwidthTestTool], [EN][v1_EN_BandwidthTestTool]). 3.0.78 +* v3.0, 2019-12-23, For [#1535][bug #1535], deprecate Adobe HDS(f4m)([CN][v2_CN_DeliveryHDS], [EN][v2_EN_DeliveryHDS]). 3.0.77 +* v3.0, 2019-12-20, Fix [#1508][bug #1508], http-client support read chunked response. 3.0.76 +* v3.0, 2019-12-20, For [#1508][bug #1508], refactor srs_is_digital, support all zeros. +* v3.0, 2019-12-19, [3.0 alpha5(3.0.75)][r3.0a5] released. 115362 lines. +* v3.0, 2019-12-19, Refine the RTMP iovs cache increasing to much faster. +* v3.0, 2019-12-19, Fix [#1524][bug #1524], memory leak for amf0 strict array. 3.0.75 +* v3.0, 2019-12-19, Fix random build failed bug for modules. +* v3.0, 2019-12-19, Fix [#1520][bug #1520] and [#1223][bug #1223], bug for origin cluster 3+ servers. 3.0.74 +* v3.0, 2019-12-18, For [#1042][bug #1042], add test for RAW AVC protocol. +* v3.0, 2019-12-18, Detect whether flash enabled for srs-player. 3.0.73 +* v3.0, 2019-12-17, Fix HTTP CORS bug when sending response for OPTIONS. 3.0.72 +* v3.0, 2019-12-17, Enhance HTTP response write for final_request. +* v3.0, 2019-12-17, Refactor HTTP stream to disconnect client when unpublish. +* v3.0, 2019-12-17, Fix HTTP-FLV and VOD-FLV conflicting bug. +* v3.0, 2019-12-17, Refactor HttpResponseWriter.write, default to single text mode. +* v3.0, 2019-12-16, For [#1042][bug #1042], add test for HTTP protocol. +* v3.0, 2019-12-13, [3.0 alpha4(3.0.71)][r3.0a4] released. 112928 lines. +* v3.0, 2019-12-12, For [#547][bug #547], [#1506][bug #1506], default hls_dts_directly to on. 3.0.71 +* v3.0, 2019-12-12, SrsPacket supports converting to message, so can be sent by one API. +* v3.0, 2019-12-11, For [#1042][bug #1042], cover RTMP client/server protocol. +* v3.0, 2019-12-11, Fix [#1445][bug #1445], limit the createStream recursive depth. 3.0.70 +* v3.0, 2019-12-11, For [#1042][bug #1042], cover RTMP handshake protocol. +* v3.0, 2019-12-11, Fix [#1229][bug #1229], fix the security risk in logger. 3.0.69 +* v3.0, 2019-12-11, For [#1229][bug #1229], fix the security risk in HDS. 3.0.69 +* v3.0, 2019-12-05, Fix [#1506][bug #1506], support directly turn FLV timestamp to TS DTS. 3.0.68 +* v3.0, 2019-11-30, [3.0 alpha3(3.0.67)][r3.0a3] released. 110864 lines. +* v3.0, 2019-12-01, Fix [#1501][bug #1501], use request coworker for origin cluster. 3.0.67 +* v3.0, 2019-11-30, [3.0 alpha2(3.0.66)][r3.0a2] released. 110831 lines. +* v3.0, 2019-11-30, Fix [#1501][bug #1501], use request coworker for origin cluster. 3.0.66 +* v3.0, 2019-11-30, Random tid for docker. 3.0.65 +* v3.0, 2019-11-30, Refine debug info for edge. 3.0.64 +* v3.0, 2019-10-30, Cover protocol stack RTMP. 3.0.63 +* v3.0, 2019-10-23, Cover JSON codec. 3.0.62 +* v3.0, 2019-10-13, Use http://ossrs.net as homepage. +* v3.0, 2019-10-10, Cover AMF0 codec. 3.0.61 +* v3.0, 2019-10-07, [3.0 alpha1(3.0.60)][r3.0a1] released. 107962 lines. +* v3.0, 2019-10-06, Support log rotate by init.d command. 3.0.60 +* v3.0, 2019-10-06, We prefer ipv4, only use ipv6 if ipv4 is disabled. 3.0.59 +* v3.0, 2019-10-05, Support systemctl service for CentOS7. 3.0.58 +* v3.0, 2019-10-04, Disable SO_REUSEPORT if not supported. 3.0.57 +* v3.0, 2019-10-04, [3.0 alpha0(3.0.56)][r3.0a0] released. 107946 lines. +* v3.0, 2019-10-04, Support go-oryx rtmplb with [proxy protocol](https://github.com/ossrs/go-oryx/wiki/RtmpProxy). 3.0.56 +* v3.0, 2019-10-03, Fix [#775][bug #775], Support SO_REUSEPORT to improve edge performance. 3.0.54 +* v3.0, 2019-10-03, For [#467][bug #467], Remove KAFKA producer. 3.0.53 +* v3.0, 2019-05-14, Covert Kernel File reader/writer. 3.0.52 +* v3.0, 2019-04-30, Refine typo in files. 3.0.51 +* v3.0, 2019-04-25, Upgrade http-parser from 2.1 to 2.9.2 and cover it. 3.0.50 +* v3.0, 2019-04-22, Refine in time unit. 3.0.49 +* v3.0, 2019-04-07, Cover ST Coroutine and time unit. 3.0.48 +* v3.0, 2019-04-06, Merge [#1304][bug #1304], Fix ST coroutine pull error. 3.0.47 +* v3.0, 2019-04-05, Merge [#1339][bug #1339], Support HTTP-FLV params. 3.0.46 +* v3.0, 2018-11-11, Merge [#1261][bug #1261], Support `_definst_` for Wowza. 3.0.44 +* v3.0, 2018-08-26, SRS [console](https://github.com/ossrs/srs-ngb) support both [Chinese](http://ossrs.net:1985/console/ng_index.html) and [English](http://ossrs.net:1985/console/en_index.html). +* v3.0, 2018-08-25, Fix [#1093][bug #1093], Support HLS encryption. 3.0.42 +* v3.0, 2018-08-25, Fix [#1051][bug #1051], Drop ts when republishing stream. 3.0.41 +* v3.0, 2018-08-12, For [#1202][bug #1202], Support edge/forward to Aliyun CDN. 3.0.40 +* v3.0, 2018-08-11, For [#910][bug #910], Support HTTP FLV with HTTP callback. 3.0.39 +* v3.0, 2018-08-05, Refine HTTP-FLV latency, support realtime mode.3.0.38 +* v3.0, 2018-08-05, Fix [#1087][bug #1087], Ignore iface without address. 3.0.37 +* v3.0, 2018-08-04, For [#1110][bug #1110], Support params in http callback. 3.0.36 +* v3.0, 2018-08-02, Always use vhost in stream query, the unify uri. 3.0.35 +* v3.0, 2018-08-02, For [#1031][bug #1031], SRS edge support douyu.com. 3.0.34 +* v3.0, 2018-07-22, Replace hex to string to match MIT license. 3.0.33 +* v3.0, 2018-07-22, Replace base64 to match MIT license. 3.0.32 +* v3.0, 2018-07-22, Replace crc32 IEEE and MPEG by pycrc to match MIT license. 3.0.31 +* v3.0, 2018-07-21, Replace crc32 IEEE by golang to match MIT license. 3.0.30 +* v3.0, 2018-02-16, Fix [#464][bug #464], support RTMP origin cluster. 3.0.29 +* v3.0, 2018-02-13, Fix [#1057][bug #1057], switch to simple handshake. 3.0.28 +* v3.0, 2018-02-13, Fix [#1059][bug #1059], merge from 2.0, supports url with vhost in stream. 3.0.27 +* v3.0, 2018-01-01, Fix [#913][bug #913], support complex error. 3.0.26 +* v3.0, 2017-06-04, Fix [#299][bug #299], support experimental MPEG-DASH. 3.0.25 +* v3.0, 2017-05-30, Fix [#821][bug #821], support MP4 file parser. 3.0.24 +* v3.0, 2017-05-30, Fix [#904][bug #904], replace NXJSON(LGPL) with json-parser(BSD). 3.0.23 +* v3.0, 2017-04-16, Fix [#547][bug #547], support HLS audio in TS. 3.0.22 +* v3.0, 2017-03-26, Fix [#820][bug #820], extract service for modules. 3.0.21 +* v3.0, 2017-03-02, Fix [#786][bug #786], simply don't reuse object. 3.0.20 +* v3.0, 2017-03-01, For [#110][bug #110], refine thread object. 3.0.19 +* v3.0, 2017-02-12, Fix [#301][bug #301], user must config the codec in right way for HLS. 3.0.18 +* v3.0, 2017-02-07, fix [#738][bug #738], support DVR general mp4. 3.0.17 +* v3.0, 2017-01-19, for [#742][bug #742], refine source, meta and origin hub. 3.0.16 +* v3.0, 2017-01-17, for [#742][bug #742], refine source, timeout, live cycle. 3.0.15 +* v3.0, 2017-01-11, fix [#735][bug #735], config transform refer_publish invalid. 3.0.14 +* v3.0, 2017-01-06, for [#730][bug #730], support config in/out ack size. 3.0.13 +* v3.0, 2017-01-06, for [#711][bug #711], support perfile for transcode. 3.0.12 +* v3.0, 2017-01-05, Fix [#727][bug #727], patch ST for valgrind and ARM. 3.0.11 +* v3.0, 2017-01-05, for [#324][bug #324], always enable hstrs. 3.0.10 +* v3.0, 2016-12-15, fix [#717][bug #717], [#691][bug #691], http api/static/stream support cors. 3.0.9 +* v3.0, 2016-12-08, Fix [#105][bug #105], support log rotate signal SIGUSR1. 3.0.8 +* v3.0, 2016-12-07, fix typo and refine grammar. 3.0.7 +* v3.0, 2015-10-20, fix [#502][bug #502], support snapshot with http-callback or transcoder. 3.0.5 +* v3.0, 2015-09-19, support amf0 and json to convert with each other. +* v3.0, 2015-09-19, json objects support dumps to string. +* v3.0, 2015-09-14, fix [#459][bug #459], support dvr raw api. 3.0.4 +* v3.0, 2015-09-14, fix [#459][bug #459], dvr support apply filter for ng-control dvr module. +* v3.0, 2015-09-14, fix [#319][bug #319], http raw api support update global and vhost. 3.0.3 +* v3.0, 2015-08-31, fix [#319][bug #319], http raw api support query global and vhost. +* v3.0, 2015-08-28, fix [#471][bug #471], api response the width and height. 3.0.2 +* v3.0, 2015-08-25, fix [#367][bug #367], support nginx-rtmp exec. 3.0.1 -### Cluster - -| Feature | SRS | NGINX | CRTMPD | FMS | WOWZA | -| ----------- | ------- | ----- | --------- | -------- | ------ | -| RTMP Edge | Stable | X | X | Stable | X | -| RTMP Backup | Stable | X | X | X | X | -| VHOST | Stable | X | X | Stable | Stable | -| Reload | Stable | X | X | X | X | -| Forward | Stable | X | X | X | X | -| ATC | Stable | X | X | X | X | - -### Stream Service - -| Feature | SRS | NGINX | CRTMPD | FMS | WOWZA | -| ----------- | ------- | ----- | --------- | -------- | ------ | -| DVR | Stable | Stable | X | X | Stable | -| Transcode | Stable | X | X | X | Stable | -| HTTP API | Stable | Stable | X | X | Stable | -| HTTP hooks | Stable | X | X | X | X | -| GopCache | Stable | X | X | Stable | X | -| Security | Stable | Stable | X | X | Stable | -| Token Traverse| Stable | X | X | Stable | X | - -### Efficiency - -| Feature | SRS | NGINX | CRTMPD | FMS | WOWZA | -| ----------- | ------- | ----- | --------- | -------- | ------ | -| Concurrency | 7.5k | 3k | 2k | 2k | 3k | -|MultipleProcess| Experiment| Stable | X | X | X | -| RTMP Latency| 0.1s | 3s | 3s | 3s | 3s | -| HLS Latency | 10s | 30s | X | 30s | 30s | - -### Stream Caster - -| Feature | SRS | NGINX | CRTMPD | FMS | WOWZA | -| ----------- | ------- | ----- | --------- | -------- | ------ | -| Ingest | Stable | X | X | X | X | -| Push MPEGTS | Experiment| X | X | X | Stable | -| Push RTSP | Experiment| X | Stable | X | Stable | -| Push HTTP FLV | Experiment| X | X | X | X | - -### Debug System - -| Feature | SRS | NGINX | CRTMPD | FMS | WOWZA | -| ----------- | ------- | ----- | --------- | -------- | ------ | -| BW check | Stable | X | X | X | X | -| Tracable Log | Stable | X | X | X | X | - -### Docs - -| Feature | SRS | NGINX | CRTMPD | FMS | WOWZA | -| ----------- | ------- | ----- | --------- | -------- | ------ | -| Demos | Stable | X | X | X | X | -| WIKI(EN+CN) | Stable | EN only | X | X | Stable | - -### Others - -| Feature | SRS | NGINX | CRTMPD | FMS | WOWZA | -| ----------- | ------- | ----- | --------- | -------- | ------ | -| ARM/MIPS | Stable | Stable | X | X | X | -| Client Library| Stable | X | X | X | X | - -Remark: - -1. Concurrency: The concurrency of single process. -1. MultipleProcess: SRS is single process, while [srs-dolphin][srs-dolphin] is a MultipleProcess SRS. -1. HLS aonly: The HLS audio only streaming delivery. -1. BW check: The bandwidth check. -1. Security: To allow or deny stream publish or play. -1. Reload: Nginx supports reload, but not nginx-rtmp. - -## Releases - -* 2021-08-14, [Release v2.0-r11](https://github.com/ossrs/srs/releases/tag/v2.0-r11), 2.0 release11, 2.0.276, 89013 lines. -* 2021-07-04, [Release v2.0-r10](https://github.com/ossrs/srs/releases/tag/v2.0-r10), 2.0 release10, 2.0.274, 87575 lines. -* 2021-06-26, [Release v2.0-r9](https://github.com/ossrs/srs/releases/tag/v2.0-r9), 2.0 release9, 2.0.273, 87552 lines. -* 2020-01-25, [Release v2.0-r8][r2.0r8], 2.0 release8, 2.0.272, 87292 lines. -* 2018-11-29, [Release v2.0-r7][r2.0r7], 2.0 release7, 2.0.265, 86994 lines. -* 2018-10-28, [Release v2.0-r6][r2.0r6], 2.0 release6, 2.0.263, 86994 lines. -* 2018-10-28, [Release v2.0-r5][r2.0r5], 2.0 release5, 2.0.258, 86916 lines. -* 2018-08-12, [Release v2.0-r4][r2.0r4], 2.0 release4, 2.0.255, 86915 lines. -* 2018-07-18, [Release v2.0-r3][r2.0r3], 2.0 release3, 2.0.248, 86775 lines. -* 2017-06-10, [Release v2.0-r2][r2.0r2], 2.0 release2, 2.0.243, 86670 lines. -* 2017-04-18, [Release v2.0-r1][r2.0r1], 2.0 release1, 2.0.239, 86515 lines. -* 2017-03-03, [Release v2.0-r0][r2.0r0], 2.0 release0, 2.0.234, 86373 lines. -* 2017-01-18, [Release v2.0-b4][r2.0b4], 2.0 beta4, 2.0.230, 86334 lines. -* 2016-11-13, [Release v2.0-b3][r2.0b3], 2.0 beta3, 2.0.223, 86685 lines. -* 2016-11-09, [Release v2.0-b2][r2.0b2], 2.0 beta2, 2.0.221, 86691 lines. -* 2016-09-09, [Release v2.0-b1][r2.0b1], 2.0 beta1, 2.0.215, 89941 lines. -* 2016-08-06, [Release v2.0-b0][r2.0b0], 2.0 beta0, 2.0.210, 89704 lines. -* 2015-12-23, [Release v2.0-a3][r2.0a3], 2.0 alpha3, 2.0.205, 89544 lines. -* 2015-10-08, [Release v2.0-a2][r2.0a2], 2.0 alpha2, 2.0.195, 89358 lines. -* 2015-09-14, [Release v2.0-a1][r2.0a1], 2.0 alpha1, 2.0.189, 89269 lines. -* 2015-08-23, [Release v2.0-a0][r2.0a0], 2.0 alpha0, 2.0.185, 89022 lines. -* 2015-05-23, [Release v1.0-r4][r1.0r4], bug fixed, 1.0.32, 59509 lines. -* 2015-03-19, [Release v1.0-r3][r1.0r3], bug fixed, 1.0.30, 59511 lines. -* 2015-02-12, [Release v1.0-r2][r1.0r2], bug fixed, 1.0.27, 59507 lines. -* 2015-01-15, [Release v1.0-r1][r1.0r1], bug fixed, 1.0.21, 59472 lines. -* 2014-12-05, [Release v1.0-r0][r1.0r0], all bug fixed, 1.0.10, 59391 lines. -* 2014-10-09, [Release v0.9.8][r1.0b0], all bug fixed, 1.0.0, 59316 lines. -* 2014-08-03, [Release v0.9.7][r1.0a7], config utest, all bug fixed. 57432 lines. -* 2014-07-13, [Release v0.9.6][r1.0a6], core/kernel/rtmp utest, refine bandwidth(as/js/srslibrtmp library). 50029 lines. -* 2014-06-27, [Release v0.9.5][r1.0a5], refine perf 3k+ clients, edge token traverse, [srs monitor](http://ossrs.net:1977), 30days online. 41573 lines. -* 2014-05-28, [Release v0.9.4][r1.0a4], support heartbeat, tracable log, fix mem leak and bugs. 39200 lines. -* 2014-05-18, [Release v0.9.3][r1.0a3], support mips, fms origin, json(http-api). 37594 lines. -* 2014-04-28, [Release v0.9.2][r1.0a2], support [dvr][v2_CN_DVR], android, [edge][v2_CN_Edge]. 35255 lines. -* 2014-04-07, [Release v0.9.1][r1.0a0], support [arm][v1_CN_SrsLinuxArm], [init.d][v1_CN_LinuxService], http [server][v2_CN_HTTPServer]/[api][v2_CN_HTTPApi], [ingest][v1_CN_SampleIngest]. 30000 lines. -* 2013-12-25, [Release v0.9.0][r0.9], support bandwidth test, player/encoder/chat [demos][v1_CN_SampleDemo]. 20926 lines. -* 2013-12-08, [Release v0.8.0][r0.8], support [http hooks callback][v2_CN_HTTPCallback], update [SB][srs-bench]. 19186 lines. -* 2013-12-03, [Release v0.7.0][r0.7], support [live stream transcoding][v2_CN_FFMPEG]. 17605 lines. -* 2013-11-29, [Release v0.6.0][r0.6], support [forward][v1_CN_Forward] stream to origin/edge. 16094 lines. -* 2013-11-26, [Release v0.5.0][r0.5], support [HLS(m3u8)][v2_CN_DeliveryHLS], fragment and window. 14449 lines. -* 2013-11-10, [Release v0.4.0][r0.4], support [reload][v1_CN_Reload] config, pause, longtime publish/play. 12500 lines. -* 2013-11-04, [Release v0.3.0][r0.3], support [vhost][v1_CN_RtmpUrlVhost], refer, gop cache, listen multiple ports. 11773 lines. -* 2013-10-25, [Release v0.2.0][r0.2], support [rtmp][v1_CN_RTMPHandshake] flash publish, h264, time jitter correct. 10125 lines. -* 2013-10-23, [Release v0.1.0][r0.1], support [rtmp FMLE/FFMPEG publish][v1_CN_DeliveryRTMP], vp6. 8287 lines. -* 2013-10-17, Created. - -## History +## V2 changes * v2.0, 2021-08-14, [2.0 release11(2.0.276)](https://github.com/ossrs/srs/releases/tag/v2.0-r11) released. 89013 lines. * v2.0, 2021-07-04, [2.0 release10(2.0.274)](https://github.com/ossrs/srs/releases/tag/v2.0-r10) released. 87575 lines. @@ -555,7 +552,7 @@ Remark: * v2.0, 2014-12-22, hotfix [#264][bug #264], ignore NALU when sequence header to make HLS happy. 2.0.76 * v2.0, 2014-12-20, hotfix [#264][bug #264], support disconnect publish connect when hls error. 2.0.75 * v2.0, 2014-12-12, fix [#257][bug #257], support 0.1s+ latency. 2.0.70 -* v2.0, 2014-12-08, update wiki for mr([EN][v2_EN_LowLatency#merged-read], [CN][v2_CN_LowLatency#merged-read]) and mw([EN][v2_EN_LowLatency#merged-write], [CN][v2_CN_LowLatency#merged-write]). +* v2.0, 2014-12-08, update wiki for mr([EN][v3_EN_LowLatency#merged-read], [CN][v3_CN_LowLatency#merged-read]) and mw([EN][v3_EN_LowLatency#merged-write], [CN][v3_CN_LowLatency#merged-write]). * v2.0, 2014-12-07, fix [#251][bug #251], 10k+ clients, use queue cond wait and fast vector. 2.0.67 * v2.0, 2014-12-05, fix [#251][bug #251], 9k+ clients, use fast cache for msgs queue. 2.0.57 * v2.0, 2014-12-04, fix [#241][bug #241], add mw(merged-write) config. 2.0.53 @@ -592,6 +589,9 @@ Remark: * v2.0, 2014-10-19, fix [#184][bug #184], support AnnexB in RTMP body for HLS. 2.0.2 * v2.0, 2014-10-18, remove supports for OSX(darwin). 2.0.1. * v2.0, 2014-10-16, revert github srs README to English. 2.0.0. + +## V1 changes + * v1.0, 2014-12-05, [1.0 release(1.0.10)][r1.0r0] released. 59391 lines. * v1.0, 2014-10-09, [1.0 beta(1.0.0)][r1.0b0] released. 59316 lines. * v1.0, 2014-10-08, fix [#151][bug #151], always reap ts whatever audio or video packet. 0.9.223. @@ -637,7 +637,7 @@ Remark: * v1.0, 2014-06-25, fix [#108][bug #108], support config time jitter for encoder non-monotonical stream. 0.9.133 * v1.0, 2014-06-23, support report summaries in heartbeat. 0.9.132 * v1.0, 2014-06-22, performance refine, support [3k+][v1_CN_Performance#performancereport4k] connections(270kbps). 0.9.130 -* v1.0, 2014-06-21, support edge [token traverse][v1_CN_DRM#tokentraverse], fix [#104][bug #104]. 0.9.129 +* v1.0, 2014-06-21, support edge [token traverse][v3_CN_DRM#tokentraverse], fix [#104][bug #104]. 0.9.129 * v1.0, 2014-06-19, add connections count to api summaries. 0.9.127 * v1.0, 2014-06-19, add srs bytes and kbps to api summaries. 0.9.126 * v1.0, 2014-06-18, add network bytes to api summaries. 0.9.125 @@ -680,7 +680,7 @@ Remark: * v1.0, 2014-04-10, support reload ingesters(add/remov/update). 0.9.57 * v1.0, 2014-04-07, [1.0 mainline(0.9.55)][r1.0a0] released. 30000 lines. * v1.0, 2014-04-07, support [ingest][v1_CN_SampleIngest] file/stream/device. -* v1.0, 2014-04-05, support [http api][v2_CN_HTTPApi] and [http server][v2_CN_HTTPServer]. +* v1.0, 2014-04-05, support [http api][v3_CN_HTTPApi] and [http server][v2_CN_HTTPServer]. * v1.0, 2014-04-03, implements http framework and api/v1/version. * v1.0, 2014-03-30, fix bug for st detecting epoll failed, force st to use epoll. * v1.0, 2014-03-29, add wiki [Performance for RaspberryPi][v1_CN_RaspberryPi]. @@ -698,16 +698,16 @@ Remark: * v1.0, 2014-03-12, finish utest for amf0 codec. * v1.0, 2014-03-06, add gperftools for mem leak detect, mem/cpu profile. * v1.0, 2014-03-04, add gest framework for utest, build success. -* v1.0, 2014-03-02, add wiki [srs-librtmp][v2_CN_SrsLibrtmp], [SRS for arm][v1_CN_SrsLinuxArm], [product][v1_CN_Product] +* v1.0, 2014-03-02, add wiki [srs-librtmp][v3_CN_SrsLibrtmp], [SRS for arm][v1_CN_SrsLinuxArm], [product][v1_CN_Product] * v1.0, 2014-03-02, srs-librtmp, client publish/play library like librtmp. * v1.0, 2014-03-01, modularity, extract core/kernel/rtmp/app/main module. * v1.0, 2014-02-28, support arm build(SRS/ST), add ssl to 3rdparty package. -* v1.0, 2014-02-28, add wiki [BuildArm][v2_CN_Build], [FFMPEG][v2_CN_FFMPEG], [Reload][v1_CN_Reload] -* v1.0, 2014-02-27, add wiki [LowLatency][v2_CN_LowLatency], [HTTPCallback][v2_CN_HTTPCallback], [ServerSideScript][v1_CN_ServerSideScript], [IDE][v2_CN_IDE] -* v1.0, 2014-01-19, add wiki [DeliveryHLS][v2_CN_DeliveryHLS] +* v1.0, 2014-02-28, add wiki [BuildArm][v3_CN_Build], [FFMPEG][v3_CN_FFMPEG], [Reload][v1_CN_Reload] +* v1.0, 2014-02-27, add wiki [LowLatency][v3_CN_LowLatency], [HTTPCallback][v3_CN_HTTPCallback], [ServerSideScript][v1_CN_ServerSideScript], [IDE][v2_CN_IDE] +* v1.0, 2014-01-19, add wiki [DeliveryHLS][v3_CN_DeliveryHLS] * v1.0, 2014-01-12, add wiki [HowToAskQuestion][v1_CN_HowToAskQuestion], [RtmpUrlVhost][v1_CN_RtmpUrlVhost] * v1.0, 2014-01-11, fix jw/flower player pause bug, which send closeStream actually. -* v1.0, 2014-01-05, add wiki [Build][v2_CN_Build], [Performance][v1_CN_Performance], [Forward][v1_CN_Forward] +* v1.0, 2014-01-05, add wiki [Build][v3_CN_Build], [Performance][v1_CN_Performance], [Forward][v3_CN_Forward] * v1.0, 2014-01-01, change listen(512), chunk-size(60000), to improve performance. * v1.0, 2013-12-27, merge from wenjie, the bandwidth test feature. * v0.9, 2013-12-25, [v0.9][r0.9] released. 20926 lines. @@ -715,7 +715,7 @@ Remark: * v0.9, 2013-12-22, demo video meeting or chat(SRS+cherrypy+jquery+bootstrap). * v0.9, 2013-12-22, merge from wenjie, support banwidth test. * v0.9, 2013-12-22, merge from wenjie: support set chunk size at vhost level -* v0.9, 2013-12-21, add [players](http://demo.srs.com/players) for play and publish. +* v0.9, 2013-12-21, add [players][player] for play and publish. * v0.9, 2013-12-15, ensure the HLS(ts) is continous when republish stream. * v0.9, 2013-12-15, fix the hls reload bug, feed it the sequence header. * v0.9, 2013-12-15, refine protocol, use int64_t timestamp for ts and jitter. @@ -725,7 +725,7 @@ Remark: * v0.9, 2013-12-14, refine the thread model for the retry threads. * v0.9, 2013-12-10, auto install depends tools/libs on centos/ubuntu. * v0.8, 2013-12-08, [v0.8][r0.8] released. 19186 lines. -* v0.8, 2013-12-08, support [http hooks][v2_CN_HTTPCallback]: on_connect/close/publish/unpublish/play/stop. +* v0.8, 2013-12-08, support [http hooks][v3_CN_HTTPCallback]: on_connect/close/publish/unpublish/play/stop. * v0.8, 2013-12-08, support multiple http hooks for a event. * v0.8, 2013-12-07, support http callback hooks, on_connect. * v0.8, 2013-12-07, support network based cli and json result, add CherryPy 3.2.4. @@ -780,83 +780,247 @@ Remark: * v0.1, 2013-10-18, support rtmp message2chunk protocol(send\_message). * v0.1, 2013-10-17, support rtmp chunk2message protocol(recv\_message). +## Releases + +* 2021-08-14, [Release v3.0-r8](https://github.com/ossrs/srs/releases/tag/v3.0-r8), 3.0 release8, 3.0.168, 124469 lines. +* 2021-07-04, [Release v3.0-r7](https://github.com/ossrs/srs/releases/tag/v3.0-r7), 3.0 release7, 3.0.167, 123463 lines. +* 2021-06-26, [Release v3.0-r6](https://github.com/ossrs/srs/releases/tag/v3.0-r6), 3.0 release6, 3.0.163, 123011 lines. +* 2021-04-28, [Release v3.0-r5][r3.0r5], 3.0 release5, 3.0.161, 122750 lines. +* 2021-04-24, [Release v3.0-r4][r3.0r4], 3.0 release4, 3.0.160, 122750 lines. +* 2021-01-02, [Release v3.0-r3][r3.0r3], 3.0 release3, 3.0.156, 122736 lines. +* 2020-10-31, [Release v3.0-r2][r3.0r2], 3.0 release2, 3.0.153, 122663 lines. +* 2020-10-10, [Release v3.0-r1][r3.0r1], 3.0 release1, 3.0.144, 122674 lines. +* 2020-06-27, [Release v3.0-r0][r3.0r0], 3.0 release0, 3.0.141, 122674 lines. +* 2020-03-29, [Release v3.0-b3][r3.0b4], 3.0 beta4, 3.0.139, 122674 lines. +* 2020-03-18, [Release v3.0-b3][r3.0b3], 3.0 beta3, 3.0.134, 122509 lines. +* 2020-03-05, [Release v3.0-b2][r3.0b2], 3.0 beta2, 3.0.123, 122170 lines. +* 2020-02-14, [Release v3.0-b1][r3.0b1], 3.0 beta1, 3.0.117, 121964 lines. +* 2020-02-02, [Release v3.0-b0][r3.0b0], 3.0 beta0, 3.0.112, 121709 lines. +* 2020-01-21, [Release v3.0-a9][r3.0a9], 3.0 alpha9, 3.0.105, 121577 lines. +* 2020-01-10, [Release v3.0-a8][r3.0a8], 3.0 alpha8, 3.0.97, 121555 lines. +* 2019-12-29, [Release v3.0-a7][r3.0a7], 3.0 alpha7, 3.0.90, 116356 lines. +* 2019-12-26, [Release v3.0-a6][r3.0a6], 3.0 alpha6, 3.0.85, 116056 lines. +* 2019-12-19, [Release v3.0-a5][r3.0a5], 3.0 alpha5, 3.0.75, 115362 lines. +* 2019-12-13, [Release v3.0-a4][r3.0a4], 3.0 alpha4, 3.0.71, 112928 lines. +* 2019-11-30, [Release v3.0-a3][r3.0a3], 3.0 alpha3, 3.0.67, 110864 lines. +* 2019-11-30, [Release v3.0-a2][r3.0a2], 3.0 alpha2, 3.0.66, 110831 lines. +* 2019-10-07, [Release v3.0-a1][r3.0a1], 3.0 alpha1, 3.0.60, 107962 lines. +* 2019-10-04, [Release v3.0-a0][r3.0a0], 3.0 alpha0, 3.0.56, 107946 lines. +* 2017-03-03, [Release v2.0-r0][r2.0r0], 2.0 release0, 2.0.234, 86373 lines. +* 2016-11-13, [Release v2.0-b3][r2.0b3], 2.0 beta3, 2.0.223, 86685 lines. +* 2016-11-09, [Release v2.0-b2][r2.0b2], 2.0 beta2, 2.0.221, 86691 lines. +* 2016-09-09, [Release v2.0-b1][r2.0b1], 2.0 beta1, 2.0.215, 89941 lines. +* 2016-08-06, [Release v2.0-b0][r2.0b0], 2.0 beta0, 2.0.210, 89704 lines. +* 2015-12-23, [Release v2.0-a3][r2.0a3], 2.0 alpha3, 2.0.205, 89544 lines. +* 2015-10-08, [Release v2.0-a2][r2.0a2], 2.0 alpha2, 2.0.195, 89358 lines. +* 2015-09-14, [Release v2.0-a1][r2.0a1], 2.0 alpha1, 2.0.189, 89269 lines. +* 2015-08-23, [Release v2.0-a0][r2.0a0], 2.0 alpha0, 2.0.185, 89022 lines. +* 2015-05-23, [Release v1.0-r4][r1.0r4], bug fixed, 1.0.32, 59509 lines. +* 2015-03-19, [Release v1.0-r3][r1.0r3], bug fixed, 1.0.30, 59511 lines. +* 2015-02-12, [Release v1.0-r2][r1.0r2], bug fixed, 1.0.27, 59507 lines. +* 2015-01-15, [Release v1.0-r1][r1.0r1], bug fixed, 1.0.21, 59472 lines. +* 2014-12-05, [Release v1.0-r0][r1.0r0], all bug fixed, 1.0.10, 59391 lines. +* 2014-10-09, [Release v0.9.8][r1.0b0], all bug fixed, 1.0.0, 59316 lines. +* 2014-08-03, [Release v0.9.7][r1.0a7], config utest, all bug fixed. 57432 lines. +* 2014-07-13, [Release v0.9.6][r1.0a6], core/kernel/rtmp utest, refine bandwidth(as/js/srslibrtmp library). 50029 lines. +* 2014-06-27, [Release v0.9.5][r1.0a5], refine perf 3k+ clients, edge token traverse, 30days online. 41573 lines. +* 2014-05-28, [Release v0.9.4][r1.0a4], support heartbeat, tracable log, fix mem leak and bugs. 39200 lines. +* 2014-05-18, [Release v0.9.3][r1.0a3], support mips, fms origin, json(http-api). 37594 lines. +* 2014-04-28, [Release v0.9.2][r1.0a2], support [dvr][v3_CN_DVR], android, [edge][v3_CN_Edge]. 35255 lines. +* 2014-04-07, [Release v0.9.1][r1.0a0], support [arm][v1_CN_SrsLinuxArm], [init.d][v1_CN_LinuxService], http [server][v2_CN_HTTPServer]/[api][v3_CN_HTTPApi], [ingest][v1_CN_SampleIngest]. 30000 lines. +* 2013-12-25, [Release v0.9.0][r0.9], support bandwidth test, player/encoder/chat [demos][v1_CN_SampleDemo]. 20926 lines. +* 2013-12-08, [Release v0.8.0][r0.8], support [http hooks callback][v3_CN_HTTPCallback], update [SB][srs-bench]. 19186 lines. +* 2013-12-03, [Release v0.7.0][r0.7], support [live stream transcoding][v3_CN_FFMPEG]. 17605 lines. +* 2013-11-29, [Release v0.6.0][r0.6], support [forward][v3_CN_Forward] stream to origin/edge. 16094 lines. +* 2013-11-26, [Release v0.5.0][r0.5], support [HLS(m3u8)][v3_CN_DeliveryHLS], fragment and window. 14449 lines. +* 2013-11-10, [Release v0.4.0][r0.4], support [reload][v1_CN_Reload] config, pause, longtime publish/play. 12500 lines. +* 2013-11-04, [Release v0.3.0][r0.3], support [vhost][v1_CN_RtmpUrlVhost], refer, gop cache, listen multiple ports. 11773 lines. +* 2013-10-25, [Release v0.2.0][r0.2], support [rtmp][v1_CN_RTMPHandshake] flash publish, h264, time jitter correct. 10125 lines. +* 2013-10-23, [Release v0.1.0][r0.1], support [rtmp FMLE/FFMPEG publish][v1_CN_DeliveryRTMP], vp6. 8287 lines. +* 2013-10-17, Created. + +## Compare + +Comparing with other media servers, SRS is much better and stronger, for details please read Product([CN][v1_CN_Compare]/[EN][v1_EN_Compare]). + + +**Stream Delivery** + +| Feature | SRS | NGINX | CRTMPD | AMS | WOWZA | +| ----------- | ------- | ----- | --------- | -------- | ------ | +| RTMP | Stable | Stable | Stable | Stable | Stable | +| HLS | Stable | Stable | X | Stable | Stable | +| HTTP FLV | Stable | X | X | X | X | +| HLS(aonly) | Stable | X | X | Stable | Stable | +| HDS | Experiment| X | X | Stable | Stable | +| MPEG-DASH | Experiment| X | X | X | X | +| HTTP Server | Stable | Stable | X | X | Stable | + + +**Cluster** + +| Feature | SRS | NGINX | CRTMPD | AMS | WOWZA | +| ----------- | ------- | ----- | --------- | -------- | ------ | +| RTMP Edge | Stable | X | X | Stable | X | +| RTMP Backup | Stable | X | X | X | X | +| VHOST | Stable | X | X | Stable | Stable | +| Reload | Stable | X | X | X | X | +| Forward | Stable | X | X | X | X | +| ATC | Stable | X | X | X | X | +| Docker | Stable | X | X | X | X | + + +**Stream Service** + +| Feature | SRS | NGINX | CRTMPD | AMS | WOWZA | +| ----------- | ------- | ----- | --------- | -------- | ------ | +| DVR | Stable | Stable | X | X | Stable | +| DVR API | Stable | Stable | X | X | X | +| DVR MP4 | Stable | X | X | X | X | +| EXEC | Stable | Stable | X | X | X | +| Transcode | Stable | X | X | X | Stable | +| HTTP API | Stable | Stable | X | X | Stable | +| HTTP RAW API | Stable | X | X | X | X | +| HTTP hooks | Stable | X | X | X | X | +| GopCache | Stable | X | X | Stable | X | +| Security | Stable | Stable | X | X | Stable | +| Token Traverse| Stable | X | X | Stable | X | + + +**Efficiency** + +| Feature | SRS | NGINX | CRTMPD | AMS | WOWZA | +| ----------- | ------- | ----- | --------- | -------- | ------ | +| Concurrency | 7.5k | 3k | 2k | 2k | 3k | +|MultipleProcess| [Stable][v3_CN_ReusePort] | Stable | X | X | X | +| RTMP Latency| 0.1s | 3s | 3s | 3s | 3s | +| HLS Latency | 10s | 30s | X | 30s | 30s | + + +**Stream Caster** + +| Feature | SRS | NGINX | CRTMPD | AMS | WOWZA | +| ----------- | ------- | ----- | --------- | -------- | ------ | +| Ingest | Stable | X | X | X | X | +| Push MPEGTS | Experiment| X | X | X | Stable | +| Push RTSP | Experiment| X | Stable | X | Stable | +| Push HTTP FLV | Experiment| X | X | X | X | + + +**Debug System** + +| Feature | SRS | NGINX | CRTMPD | AMS | WOWZA | +| ----------- | ------- | ----- | --------- | -------- | ------ | +| BW check | Stable | X | X | X | X | +| Tracable Log | Stable | X | X | X | X | + + +**Docs** + +| Feature | SRS | NGINX | CRTMPD | AMS | WOWZA | +| ----------- | ------- | ----- | --------- | -------- | ------ | +| Demos | Stable | X | X | X | X | +| WIKI(EN+CN) | Stable | EN only | X | X | Stable | + + +**Others** + +| Feature | SRS | NGINX | CRTMPD | AMS | WOWZA | +| ----------- | ------- | ----- | --------- | -------- | ------ | +| ARM/MIPS | Stable | Stable | X | X | X | +| Client Library| Stable | X | X | X | X | + +Remark: + +1. Concurrency: We only benchmark the concurrency of single process. +1. MultipleProcess: SRS supports multiple processes by [go-oryx][oryx]. +1. HLS aonly: HLS supports audio only mode without video stream. +1. BW check: The bandwidth check feature is used to detect the bandwidth between server and client. +1. Security: The security includes access control, token authentication and referer check. +1. Reload: SRS and Nginx supports reload, but nginx-rtmp doesn't. + ## Performance -Performance benchmark history, on virtual box. +The performance benchmark data and corelative commits are listed here. -* See also: [Performance for x86/x64 Test Guide][v1_CN_Performance] -* See also: [Performance for RaspberryPi][v1_CN_RaspberryPi] -* About multiple-process performance, read [srs-dolphin][srs-dolphin]. +* See also: [Performance for x86/x64 Test Guide][v1_CN_Performance]. +* See also: [Performance for RaspberryPi][v1_CN_RaspberryPi]. +* For multiple processes performance, read [#775: REUSEPORT][bug #775] or OriginCluster([CN][v3_EN_OriginCluster]/[EN][v3_EN_OriginCluster]) or [go-oryx][oryx]. -### Play RTMP benchmark + +**Play RTMP benchmark** -The play RTMP benchmark by [SB][srs-bench]: +The data for playing RTMP was benchmarked by [SB][srs-bench]: | Update | SRS | Clients | Type | CPU | Memory | Commit | | ------------- | --------- | ------------- | ------------- | --------- | -------- | ------------ | -| 2013-11-28 | 0.5.0 | 1.8k(1800) | players | 90% | 41M | - | -| 2014-07-12 | 0.9.156 | 1.8k(1800) | players | 68% | 38MB | - | -| 2014-07-12 | 0.9.156 | 2.7k(2700) | players | 89% | 61MB | [code][p6] | -| 2014-11-11 | 1.0.5 | 2.7k(2700) | players | 85% | 66MB | - | -| 2014-11-11 | 2.0.12 | 2.7k(2700) | players | 85% | 66MB | - | -| 2014-11-12 | 2.0.14 | 2.7k(2700) | players | 69% | 59MB | - | -| 2014-11-12 | 2.0.14 | 3.5k(3500) | players | 95% | 78MB | [code][p7] | -| 2014-11-13 | 2.0.15 | 6.0k(6000) | players | 82% | 203MB | [code][p8] | -| 2014-11-22 | 2.0.30 | 7.5k(7500) | players | 87% | 320MB | [code][p9] | -| 2014-12-05 | 2.0.55 | 8.0k(8000) | players | 89% | 360MB | [code][p10] | -| 2014-12-05 | 2.0.57 | 9.0k(9000) | players | 90% | 468MB | [code][p11] | | 2014-12-07 | 2.0.67 | 10k(10000) | players | 95% | 656MB | [code][p12] | +| 2014-12-05 | 2.0.57 | 9.0k(9000) | players | 90% | 468MB | [code][p11] | +| 2014-12-05 | 2.0.55 | 8.0k(8000) | players | 89% | 360MB | [code][p10] | +| 2014-11-22 | 2.0.30 | 7.5k(7500) | players | 87% | 320MB | [code][p9] | +| 2014-11-13 | 2.0.15 | 6.0k(6000) | players | 82% | 203MB | [code][p8] | +| 2014-11-12 | 2.0.14 | 3.5k(3500) | players | 95% | 78MB | [code][p7] | +| 2014-11-12 | 2.0.14 | 2.7k(2700) | players | 69% | 59MB | - | +| 2014-11-11 | 2.0.12 | 2.7k(2700) | players | 85% | 66MB | - | +| 2014-11-11 | 1.0.5 | 2.7k(2700) | players | 85% | 66MB | - | +| 2014-07-12 | 0.9.156 | 2.7k(2700) | players | 89% | 61MB | [code][p6] | +| 2014-07-12 | 0.9.156 | 1.8k(1800) | players | 68% | 38MB | - | +| 2013-11-28 | 0.5.0 | 1.8k(1800) | players | 90% | 41M | - | -### Publish RTMP benchmark + +**Publish RTMP benchmark** -The publish RTMP benchmark by [SB][srs-bench]: +The data for publishing RTMP was benchmarked by [SB][srs-bench]: | Update | SRS | Clients | Type | CPU | Memory | Commit | | ------------- | --------- | ------------- | ------------- | --------- | -------- | ------------ | -| 2014-12-03 | 1.0.10 | 1.2k(1200) | publishers | 96% | 43MB | - | -| 2014-12-03 | 2.0.12 | 1.2k(1200) | publishers | 96% | 43MB | - | -| 2014-12-03 | 2.0.47 | 1.2k(1200) | publishers | 84% | 76MB | [code][p1] | -| 2014-12-03 | 2.0.47 | 1.4k(1400) | publishers | 95% | 140MB | - | -| 2014-12-03 | 2.0.48 | 1.4k(1400) | publishers | 95% | 140MB | [code][p2] | -| 2014-12-04 | 2.0.49 | 1.4k(1400) | publishers | 68% | 144MB | - | -| 2014-12-04 | 2.0.49 | 2.5k(2500) | publishers | 95% | 404MB | [code][p3] | -| 2014-12-04 | 2.0.51 | 2.5k(2500) | publishers | 91% | 259MB | [code][p4] | | 2014-12-04 | 2.0.52 | 4.0k(4000) | publishers | 80% | 331MB | [code][p5] | +| 2014-12-04 | 2.0.51 | 2.5k(2500) | publishers | 91% | 259MB | [code][p4] | +| 2014-12-04 | 2.0.49 | 2.5k(2500) | publishers | 95% | 404MB | [code][p3] | +| 2014-12-04 | 2.0.49 | 1.4k(1400) | publishers | 68% | 144MB | - | +| 2014-12-03 | 2.0.48 | 1.4k(1400) | publishers | 95% | 140MB | [code][p2] | +| 2014-12-03 | 2.0.47 | 1.4k(1400) | publishers | 95% | 140MB | - | +| 2014-12-03 | 2.0.47 | 1.2k(1200) | publishers | 84% | 76MB | [code][p1] | +| 2014-12-03 | 2.0.12 | 1.2k(1200) | publishers | 96% | 43MB | - | +| 2014-12-03 | 1.0.10 | 1.2k(1200) | publishers | 96% | 43MB | - | -### Play HTTP FLV benchmark + +**Play HTTP FLV benchmark** -The play HTTP FLV benchmark by [SB][srs-bench]: +The data for playing HTTP FLV was benchmarked by [SB][srs-bench]: | Update | SRS | Clients | Type | CPU | Memory | Commit | | ------------- | --------- | ------------- | ------------- | --------- | -------- | ------------ | -| 2014-05-24 | 2.0.167 | 1.0k(1000) | players | 82% | 86MB | - | -| 2014-05-24 | 2.0.168 | 2.3k(2300) | players | 92% | 276MB | [code][p17] | -| 2014-05-24 | 2.0.169 | 3.0k(3000) | players | 94% | 188MB | [code][p18] | -| 2014-05-24 | 2.0.170 | 3.0k(3000) | players | 89% | 96MB | [code][p19] | | 2014-05-25 | 2.0.171 | 6.0k(6000) | players | 84% | 297MB | [code][p20] | +| 2014-05-24 | 2.0.170 | 3.0k(3000) | players | 89% | 96MB | [code][p19] | +| 2014-05-24 | 2.0.169 | 3.0k(3000) | players | 94% | 188MB | [code][p18] | +| 2014-05-24 | 2.0.168 | 2.3k(2300) | players | 92% | 276MB | [code][p17] | +| 2014-05-24 | 2.0.167 | 1.0k(1000) | players | 82% | 86MB | - | -### Latency benchmark + +**Latency benchmark** -The latency between encoder and player with realtime config([CN][v2_CN_LowLatency], [EN][v2_EN_LowLatency]): +The latency between encoder and player with realtime config([CN][v3_CN_LowLatency], [EN][v3_EN_LowLatency]): | | Update | SRS | VP6 | H.264 | VP6+MP3 | H.264+MP3 | | ------------- | --------- | --------- | --------- | --------- | -------- | -| 2014-12-03 | 1.0.10 | 0.4s | 0.4s | 0.9s | 1.2s | -| 2014-12-12 | 2.0.70 |[0.1s][p13]|[0.4s][p14]| 1.0s | 0.9s | | 2014-12-16 | 2.0.72 | 0.1s | 0.4s |[0.8s][p15]|[0.6s][p16]| +| 2014-12-12 | 2.0.70 |[0.1s][p13]|[0.4s][p14]| 1.0s | 0.9s | +| 2014-12-03 | 1.0.10 | 0.4s | 0.4s | 0.9s | 1.2s | > 2018-08-05, [c45f72e](https://github.com/ossrs/srs/commit/c45f72ef7bac9c7cf85b9125fc9e3aafd53f396f), Refine HTTP-FLV latency, support realtime mode. 2.0.252 -We use FMLE as encoder for benchmark. The latency of server is 0.1s+, -and the bottleneck is the encoder. For more information, read +We used FMLE as encoder for benchmark. The latency of server was 0.1s+, +and the bottleneck was the encoder. For more information, read [bug #257][bug #257-c0]. -### HLS overhead + +**HLS overhead** -About the HLS overhead of SRS, we compare the overhead to FLV by remux the HLS to FLV by ffmpeg. +About the overhead of HLS overhead, we compared FFMPEG and SRS. | Bitrate | Duration | FLV(KB) | HLS(KB) | Overhead | | ------- | -------- | ------- | -------- | --------- | @@ -880,62 +1044,74 @@ About the HLS overhead of SRS, we compare the overhead to FLV by remux the HLS t The HLS overhead is calc by: (HLS - FLV) / FLV * 100%. -The overhead is larger than this benchmark(48kbps audio is best overhead), for we fix the [#512][bug #512]. +The overhead should be larger than this benchmark(48kbps audio is best overhead), for we fix the [#512][bug #512]. ## Architecture -SRS always use the most simple architecture to support complex transaction. +SRS always use the simplest architecture to solve complex domain problems. + * System arch: the system structure and arch. * Modularity arch: the main modularity of SRS. * Stream arch: the stream dispatch arch of SRS. -### System Architecture +## System Architecture -
+```
 +------------------------------------------------------+
-|             SRS(Simple RTMP Server)                  |
+|                    Application                       |
+|            Origin/Edge/HTTP-FLV/StreamCaster         |
 +---------------+---------------+-----------+----------+
-|   API/hook    |   Transcoder  |  HLS/HDS  | RTMP/FLV |
+|   RAW API/    |     EXEC/     |  DVR/HLS  | FLV/TS/  |
+|   API/hook    |   Transcoder  |  HDS/DASH | AMF0/JSON|
++---------------+---------------+-----------+ RTMP/RTSP|
 |  http-parser  |  FFMPEG/x264  |  NGINX/ts | protocol |
 +---------------+---------------+-----------+----------+
 |              Network(state-threads)                  |
 +------------------------------------------------------+
 |    All Linux/Unix(RHEL,CentOS,Ubuntu,Fedora...)      |
 +------------------------------------------------------+
-
+``` -### Modularity Architecture +## Modularity Architecture -
+```
 +------------------------------------------------------+
-|             Main(srs/ingest-hls/librtmp)             |
+|    SRS server    |   Programs in Main or Research    |
 +------------------------------------------------------+
-|           App(Server/Client application)             |
+|  App(For SRS)    | Modules(1) |   research/librtmp   |
 +------------------------------------------------------+
-|         RTMP/HTTP/RawStream(Protocol stack)          |
+|    Service(C/S apps over ST)  | Libs(Export librtmp) |
 +------------------------------------------------------+
-|      Kernel(depends on Core, provides error/log)     |
+|   Protocol Stack(RTMP/HTTP/RTSP/JSON/AMF/Format)     |
 +------------------------------------------------------+
-|         Core(depends only on system apis)            |
+|      Kernel(File, Codec, Stream, LB services)        |
 +------------------------------------------------------+
-
+| Core(Macros and very low-level APIs) | ++------------------------------------------------------+ +``` -### Stream Architecture +Remark: -
-                   +---------+              +----------+
-                   | Publish |              |  Deliver |
-                   +---|-----+              +----|-----+
+1. Modules: SRS supports code-level modularity, read [modules][modules].
+
+## Stream Architecture
+
+```
++---------+              +----------+
+| Publish |              |  Deliver |
++---|-----+              +----|-----+
 +----------------------+-------------------------+----------------+
 |     Input            | SRS(Simple RTMP Server) |     Output     |
 +----------------------+-------------------------+----------------+
+|                      |   +-> DASH -------------+-> DASH player  |
 |    Encoder(1)        |   +-> RTMP/HDS  --------+-> Flash player |
-|  (FMLE,FFMPEG, -rtmp-+->-+-> HLS/HTTP ---------+-> M3u8 player  |
+|  (FMLE,FFMPEG, -rtmp-+->-+-> HLS/HTTP ---------+-> M3U8 player  |
 |  Flash,XSPLIT,       |   +-> FLV/MP3/Aac/Ts ---+-> HTTP player  |
 |  ......)             |   +-> Fowarder ---------+-> RTMP server  |
 |                      |   +-> Transcoder -------+-> RTMP server  |
-|                      |   +-> DVR --------------+-> Flv file     |
-|                      |   +-> BandwidthTest ----+-> flash        |
+|                      |   +-> EXEC(5) ----------+-> External app |
+|                      |   +-> DVR --------------+-> FLV file     |
+|                      |   +-> BandwidthTest ----+-> Flash        |
 +----------------------+                         |                |
 |  MediaSource(2)      |                         |                |
 |  (RTSP,FILE,         |                         |                |
@@ -950,18 +1126,79 @@ SRS always use the most simple architecture to support complex transaction.
 |   ......)            |                         |                |
 +----------------------+-------------------------+----------------+
 
+```
+
 Remark:
-(1) Encoder: encoder must push RTMP stream to SRS server.
-(2) MediaSource: any media source, which can be ingest by ffmpeg.
-(3) Ingester: SRS will fork a process to run ffmpeg(or your application) 
-to ingest any input to rtmp, push to SRS. Read Ingest.
-(4) Streamer: SRS will listen for some protocol and accept stream push 
-over some protocol and remux to rtmp to SRS. Read Streamer.
-
+ +1. Encoder: Encoder pushs RTMP stream to SRS. +1. MediaSource: Supports any media source, ingesting by ffmpeg. +1. Ingester: Forks a ffmpeg(or other tools) to ingest as rtmp to SRS, please read [Ingest][v1_CN_Ingest]. +1. Streamer: Remuxs other protocols to RTMP, please read [Streamer][v2_CN_Streamer]. +1. EXEC: Like NGINX-RTMP, EXEC forks external tools for events, please read [ng-exec][v3_CN_NgExec]. + +## AUTHORS + +There are two types of people that have contributed to the SRS project: + +* Maintainers: Contribute and maintain important features. SRS always remembers and thanks you by writing your names in stream metadata. +* [Contributors][authors]: Submit patches, report bugs, add translations, help answer newbie questions, and generally make SRS much better. + +Maintainers of SRS project: + +* [Winlin](https://github.com/winlinvip): All areas of streaming server and documents. +* [Wenjie](https://github.com/wenjiegit): The focus of his work is on the [HDS](https://github.com/simple-rtmp-server/srs/wiki/v2_CN_DeliveryHDS) module. +* [Runner365](https://github.com/runner365): The focus of his work is on the [SRT](https://github.com/simple-rtmp-server/srs/wiki/v4_CN_SRTWiki) module. + +A big THANK YOU goes to: + +* All friends of SRS for [big supports][bigthanks]. +* Genes amd Mabbott for creating [st][st]([state-threads][st2]). +* [Michael Talyanksy](https://github.com/michaeltalyansky) for introducing ST to us. + +## Mirrors + +Gitee: [https://gitee.com/ossrs/srs][gitee], the GIT usage([CN][v1_CN_Git], [EN][v1_EN_Git]) + +``` +git clone https://gitee.com/ossrs/srs.git && +cd srs && git remote set-url origin https://github.com/ossrs/srs.git && git pull +``` + +> Remark: For users in China, recomment to use mirror from CSDN or OSChina, because they are much faster. + +Gitlab: [https://gitlab.com/winlinvip/srs-gitlab][gitlab], the GIT usage([CN][v1_CN_Git], [EN][v1_EN_Git]) + +``` +git clone https://gitlab.com/winlinvip/srs-gitlab.git srs && +cd srs && git remote set-url origin https://github.com/ossrs/srs.git && git pull +``` + +Github: [https://github.com/ossrs/srs][srs], the GIT usage([CN][v1_CN_Git], [EN][v1_EN_Git]) + +``` +git clone https://github.com/ossrs/srs.git +``` + +| Branch | Cost | Size | CMD | +| --- | --- | --- | --- | +| 3.0release | 2m19.931s | 262MB | git clone -b 3.0release https://gitee.com/ossrs/srs.git | +| 3.0release | 0m56.515s | 95MB | git clone -b 3.0release --depth=1 https://gitee.com/ossrs/srs.git | +| develop | 2m22.430s | 234MB | git clone -b develop https://gitee.com/ossrs/srs.git | +| develop | 0m46.421s | 42MB | git clone -b develop --depth=1 https://gitee.com/ossrs/srs.git | +| min | 2m22.865s | 217MB | git clone -b min https://gitee.com/ossrs/srs.git | +| min | 0m36.472s | 11MB | git clone -b min --depth=1 https://gitee.com/ossrs/srs.git | + +## System Requirements + +Supported operating systems and hardware: + +* All Linux, both 32 and 64 bits +* Other OS, such as Windows, please use [docker][docker-srs3]. Beijing, 2013.10
Winlin + [p1]: https://github.com/ossrs/srs/commit/787ab674e38734ea8e0678101614fdcd84645dc8 [p2]: https://github.com/ossrs/srs/commit/f35ec2155b1408d528a9f37da7904c9625186bcf [p3]: https://github.com/ossrs/srs/commit/29324fab469e0f7cef9ad04ffdbce832ac7dd9ff @@ -991,8 +1228,8 @@ Winlin [authors]: https://github.com/ossrs/srs/blob/develop/AUTHORS.txt [bigthanks]: https://github.com/ossrs/srs/wiki/v1_CN_Product#bigthanks [st]: https://github.com/winlinvip/state-threads -[st2]: http://sourceforge.net/projects/state-threads/ -[state-threads]: http://sourceforge.net/projects/state-threads/ +[st2]: https://github.com/ossrs/state-threads/tree/srs +[state-threads]: https://github.com/ossrs/state-threads/tree/srs [nginx-rtmp]: https://github.com/arut/nginx-rtmp-module [http-parser]: https://github.com/joyent/http-parser [nginx]: http://nginx.org/ @@ -1000,142 +1237,167 @@ Winlin [libx264]: http://www.videolan.org/ [srs]: https://github.com/ossrs/srs [csdn]: https://code.csdn.net/winlinvip/srs-csdn -[oschina]: http://git.oschina.net/winlinvip/srs.oschina +[gitee]: https://gitee.com/ossrs/srs [srs-dolphin]: https://github.com/ossrs/srs-dolphin +[oryx]: https://github.com/ossrs/go-oryx [srs-bench]: https://github.com/ossrs/srs-bench [srs-ngb]: https://github.com/ossrs/srs-ngb [srs-librtmp]: https://github.com/ossrs/srs-librtmp [gitlab]: https://gitlab.com/winlinvip/srs-gitlab [console]: http://ossrs.net:1985/console -[docker]: https://github.com/ossrs/srs-docker/tree/centos#usage +[player]: http://ossrs.net/players/srs_player.html +[modules]: https://github.com/ossrs/srs/blob/develop/trunk/modules/readme.txt +[docker-srs3]: https://github.com/ossrs/srs-docker/tree/v3#usage +[docker-dev]: https://github.com/ossrs/srs-docker/tree/dev#usage [v1_CN_Git]: https://github.com/ossrs/srs/wiki/v1_CN_Git [v1_EN_Git]: https://github.com/ossrs/srs/wiki/v1_EN_Git [v1_CN_SampleRTMP]: https://github.com/ossrs/srs/wiki/v1_CN_SampleRTMP [v1_EN_SampleRTMP]: https://github.com/ossrs/srs/wiki/v1_EN_SampleRTMP -[v1_CN_SampleRTMPCluster]: https://github.com/ossrs/srs/wiki/v1_CN_SampleRTMPCluster -[v1_EN_SampleRTMPCluster]: https://github.com/ossrs/srs/wiki/v1_EN_SampleRTMPCluster -[v2_CN_SampleHLS]: https://github.com/ossrs/srs/wiki/v2_CN_SampleHLS -[v2_EN_SampleHLS]: https://github.com/ossrs/srs/wiki/v2_EN_SampleHLS -[v2_CN_SampleTranscode2HLS]: https://github.com/ossrs/srs/wiki/v2_CN_SampleTranscode2HLS -[v2_EN_SampleTranscode2HLS]: https://github.com/ossrs/srs/wiki/v2_EN_SampleTranscode2HLS +[v3_CN_SampleRTMPCluster]: https://github.com/ossrs/srs/wiki/v3_CN_SampleRTMPCluster +[v3_EN_SampleRTMPCluster]: https://github.com/ossrs/srs/wiki/v3_EN_SampleRTMPCluster +[v3_CN_SampleOriginCluster]: https://github.com/ossrs/srs/wiki/v3_CN_SampleOriginCluster +[v3_EN_SampleOriginCluster]: https://github.com/ossrs/srs/wiki/v3_EN_SampleOriginCluster +[v3_CN_SampleHLS]: https://github.com/ossrs/srs/wiki/v3_CN_SampleHLS +[v3_EN_SampleHLS]: https://github.com/ossrs/srs/wiki/v3_EN_SampleHLS +[v3_CN_SampleTranscode2HLS]: https://github.com/ossrs/srs/wiki/v3_CN_SampleTranscode2HLS +[v3_EN_SampleTranscode2HLS]: https://github.com/ossrs/srs/wiki/v3_EN_SampleTranscode2HLS [v2_CN_SampleFFMPEG]: https://github.com/ossrs/srs/wiki/v2_CN_SampleFFMPEG [v2_EN_SampleFFMPEG]: https://github.com/ossrs/srs/wiki/v2_EN_SampleFFMPEG -[v1_CN_SampleForward]: https://github.com/ossrs/srs/wiki/v1_CN_SampleForward -[v1_EN_SampleForward]: https://github.com/ossrs/srs/wiki/v1_EN_SampleForward -[v2_CN_SampleRealtime]: https://github.com/ossrs/srs/wiki/v2_CN_SampleRealtime -[v2_EN_SampleRealtime]: https://github.com/ossrs/srs/wiki/v2_EN_SampleRealtime +[v3_CN_SampleForward]: https://github.com/ossrs/srs/wiki/v3_CN_SampleForward +[v3_EN_SampleForward]: https://github.com/ossrs/srs/wiki/v3_EN_SampleForward +[v3_CN_SampleRealtime]: https://github.com/ossrs/srs/wiki/v3_CN_SampleRealtime +[v3_EN_SampleRealtime]: https://github.com/ossrs/srs/wiki/v3_EN_SampleRealtime [v1_CN_SampleARM]: https://github.com/ossrs/srs/wiki/v1_CN_SampleARM [v1_EN_SampleARM]: https://github.com/ossrs/srs/wiki/v1_EN_SampleARM [v1_CN_SampleIngest]: https://github.com/ossrs/srs/wiki/v1_CN_SampleIngest [v1_EN_SampleIngest]: https://github.com/ossrs/srs/wiki/v1_EN_SampleIngest -[v2_CN_SampleHTTP]: https://github.com/ossrs/srs/wiki/v2_CN_SampleHTTP -[v2_EN_SampleHTTP]: https://github.com/ossrs/srs/wiki/v2_EN_SampleHTTP +[v3_CN_SampleHTTP]: https://github.com/ossrs/srs/wiki/v3_CN_SampleHTTP +[v3_EN_SampleHTTP]: https://github.com/ossrs/srs/wiki/v3_EN_SampleHTTP [v1_CN_SampleDemo]: https://github.com/ossrs/srs/wiki/v1_CN_SampleDemo [v1_EN_SampleDemo]: https://github.com/ossrs/srs/wiki/v1_EN_SampleDemo -[v2_CN_SrsLibrtmp2]: https://github.com/ossrs/srs/wiki/v2_CN_SrsLibrtmp#publish-h264-raw-data -[v2_EN_SrsLibrtmp2]: https://github.com/ossrs/srs/wiki/v2_EN_SrsLibrtmp#publish-h264-raw-data +[v3_CN_OriginCluster]: https://github.com/ossrs/srs/wiki/v3_CN_OriginCluster +[v3_EN_OriginCluster]: https://github.com/ossrs/srs/wiki/v3_EN_OriginCluster +[v3_CN_SrsLibrtmp2]: https://github.com/ossrs/srs/wiki/v3_CN_SrsLibrtmp#publish-h264-raw-data +[v3_EN_SrsLibrtmp2]: https://github.com/ossrs/srs/wiki/v3_EN_SrsLibrtmp#publish-h264-raw-data +[v3_CN_REUSEPORT]: https://github.com/ossrs/srs/wiki/v3_CN_REUSEPORT +[v3_EN_REUSEPORT]: https://github.com/ossrs/srs/wiki/v3_EN_REUSEPORT [v1_CN_Sample]: https://github.com/ossrs/srs/wiki/v1_CN_Sample [v1_EN_Sample]: https://github.com/ossrs/srs/wiki/v1_EN_Sample [v1_CN_Product]: https://github.com/ossrs/srs/wiki/v1_CN_Product [v1_EN_Product]: https://github.com/ossrs/srs/wiki/v1_EN_Product +[v1-wiki-cn]: https://github.com/ossrs/srs/wiki/v1_CN_Home +[v1-wiki-en]: https://github.com/ossrs/srs/wiki/v1_EN_Home +[v2-wiki-cn]: https://github.com/ossrs/srs/wiki/v2_CN_Home +[v2-wiki-en]: https://github.com/ossrs/srs/wiki/v2_EN_Home [v1_CN_Home]: https://github.com/ossrs/srs/wiki/v1_CN_Home [v1_EN_Home]: https://github.com/ossrs/srs/wiki/v1_EN_Home [v2_CN_Home]: https://github.com/ossrs/srs/wiki/v2_CN_Home [v2_EN_Home]: https://github.com/ossrs/srs/wiki/v2_EN_Home +[v3_CN_Home]: https://github.com/ossrs/srs/wiki/v3_CN_Home +[v3_EN_Home]: https://github.com/ossrs/srs/wiki/v3_EN_Home [donation0]: http://winlinvip.github.io/srs.release/donation/index.html -[donation1]: http://www.ossrs.net/srs.release/donation/index.html +[donation1]: http://ossrs.net/srs.release/donation/index.html +[donation2]: http://ossrs.net/srs.release/donation/paypal.html [donations]: https://github.com/ossrs/srs/blob/develop/DONATIONS.txt -[v2_CN_Build]: https://github.com/ossrs/srs/wiki/v2_CN_Build -[v2_EN_Build]: https://github.com/ossrs/srs/wiki/v2_EN_Build +[v1_CN_Compare]: https://github.com/ossrs/srs/wiki/v1_CN_Compare +[v1_EN_Compare]: https://github.com/ossrs/srs/wiki/v1_EN_Compare +[v3_CN_Build]: https://github.com/ossrs/srs/wiki/v3_CN_Build +[v3_EN_Build]: https://github.com/ossrs/srs/wiki/v3_EN_Build [v1_CN_Performance]: https://github.com/ossrs/srs/wiki/v1_CN_Performance [v1_EN_Performance]: https://github.com/ossrs/srs/wiki/v1_EN_Performance [v1_CN_DeliveryRTMP]: https://github.com/ossrs/srs/wiki/v1_CN_DeliveryRTMP [v1_EN_DeliveryRTMP]: https://github.com/ossrs/srs/wiki/v1_EN_DeliveryRTMP -[v2_CN_Edge]: https://github.com/ossrs/srs/wiki/v2_CN_Edge -[v2_EN_Edge]: https://github.com/ossrs/srs/wiki/v2_EN_Edge +[v3_CN_Edge]: https://github.com/ossrs/srs/wiki/v3_CN_Edge +[v3_EN_Edge]: https://github.com/ossrs/srs/wiki/v3_EN_Edge [v1_CN_RtmpUrlVhost]: https://github.com/ossrs/srs/wiki/v1_CN_RtmpUrlVhost [v1_EN_RtmpUrlVhost]: https://github.com/ossrs/srs/wiki/v1_EN_RtmpUrlVhost [v1_CN_RTMPHandshake]: https://github.com/ossrs/srs/wiki/v1_CN_RTMPHandshake [v1_EN_RTMPHandshake]: https://github.com/ossrs/srs/wiki/v1_EN_RTMPHandshake [v2_CN_HTTPServer]: https://github.com/ossrs/srs/wiki/v2_CN_HTTPServer [v2_EN_HTTPServer]: https://github.com/ossrs/srs/wiki/v2_EN_HTTPServer -[v2_CN_DeliveryHLS]: https://github.com/ossrs/srs/wiki/v2_CN_DeliveryHLS -[v2_EN_DeliveryHLS]: https://github.com/ossrs/srs/wiki/v2_EN_DeliveryHLS -[v2_CN_DeliveryHLS2]: https://github.com/ossrs/srs/wiki/v2_CN_DeliveryHLS#hlsaudioonly -[v2_EN_DeliveryHLS2]: https://github.com/ossrs/srs/wiki/v2_EN_DeliveryHLS#hlsaudioonly +[v3_CN_DeliveryHLS]: https://github.com/ossrs/srs/wiki/v3_CN_DeliveryHLS +[v3_EN_DeliveryHLS]: https://github.com/ossrs/srs/wiki/v3_EN_DeliveryHLS +[v3_CN_DeliveryHLS2]: https://github.com/ossrs/srs/wiki/v3_CN_DeliveryHLS#hlsaudioonly +[v3_EN_DeliveryHLS2]: https://github.com/ossrs/srs/wiki/v3_EN_DeliveryHLS#hlsaudioonly [v1_CN_Reload]: https://github.com/ossrs/srs/wiki/v1_CN_Reload [v1_EN_Reload]: https://github.com/ossrs/srs/wiki/v1_EN_Reload -[v2_CN_LowLatency2]: https://github.com/ossrs/srs/wiki/v2_CN_LowLatency#gop-cache -[v2_EN_LowLatency2]: https://github.com/ossrs/srs/wiki/v2_EN_LowLatency#gop-cache -[v1_CN_Forward]: https://github.com/ossrs/srs/wiki/v1_CN_Forward -[v1_EN_Forward]: https://github.com/ossrs/srs/wiki/v1_EN_Forward -[v2_CN_FFMPEG]: https://github.com/ossrs/srs/wiki/v2_CN_FFMPEG -[v2_EN_FFMPEG]: https://github.com/ossrs/srs/wiki/v2_EN_FFMPEG -[v2_CN_HTTPCallback]: https://github.com/ossrs/srs/wiki/v2_CN_HTTPCallback -[v2_EN_HTTPCallback]: https://github.com/ossrs/srs/wiki/v2_EN_HTTPCallback +[v3_CN_LowLatency2]: https://github.com/ossrs/srs/wiki/v3_CN_LowLatency#gop-cache +[v3_EN_LowLatency2]: https://github.com/ossrs/srs/wiki/v3_EN_LowLatency#gop-cache +[v3_CN_Forward]: https://github.com/ossrs/srs/wiki/v3_CN_Forward +[v3_EN_Forward]: https://github.com/ossrs/srs/wiki/v3_EN_Forward +[v3_CN_FFMPEG]: https://github.com/ossrs/srs/wiki/v3_CN_FFMPEG +[v3_EN_FFMPEG]: https://github.com/ossrs/srs/wiki/v3_EN_FFMPEG +[v3_CN_HTTPCallback]: https://github.com/ossrs/srs/wiki/v3_CN_HTTPCallback +[v3_EN_HTTPCallback]: https://github.com/ossrs/srs/wiki/v3_EN_HTTPCallback [v1_CN_BandwidthTestTool]: https://github.com/ossrs/srs/wiki/v1_CN_BandwidthTestTool [v1_EN_BandwidthTestTool]: https://github.com/ossrs/srs/wiki/v1_EN_BandwidthTestTool [v1_CN_SampleDemo]: https://github.com/ossrs/srs/wiki/v1_CN_SampleDemo [v1_EN_SampleDemo]: https://github.com/ossrs/srs/wiki/v1_EN_SampleDemo -[v2_CN_SrsLibrtmp]: https://github.com/ossrs/srs/wiki/v2_CN_SrsLibrtmp -[v2_EN_SrsLibrtmp]: https://github.com/ossrs/srs/wiki/v2_EN_SrsLibrtmp +[v3_CN_SrsLibrtmp]: https://github.com/ossrs/srs/wiki/v3_CN_SrsLibrtmp +[v3_EN_SrsLibrtmp]: https://github.com/ossrs/srs/wiki/v3_EN_SrsLibrtmp [v1_CN_SrsLinuxArm]: https://github.com/ossrs/srs/wiki/v1_CN_SrsLinuxArm [v1_EN_SrsLinuxArm]: https://github.com/ossrs/srs/wiki/v1_EN_SrsLinuxArm [v1_CN_LinuxService]: https://github.com/ossrs/srs/wiki/v1_CN_LinuxService [v1_EN_LinuxService]: https://github.com/ossrs/srs/wiki/v1_EN_LinuxService -[v1_CN_RTMP-ATC]: https://github.com/ossrs/srs/wiki/v1_CN_RTMP-ATC -[v1_EN_RTMP-ATC]: https://github.com/ossrs/srs/wiki/v1_EN_RTMP-ATC -[v2_CN_HTTPApi]: https://github.com/ossrs/srs/wiki/v2_CN_HTTPApi -[v2_EN_HTTPApi]: https://github.com/ossrs/srs/wiki/v2_EN_HTTPApi +[v3_CN_RTMP-ATC]: https://github.com/ossrs/srs/wiki/v3_CN_RTMP-ATC +[v3_EN_RTMP-ATC]: https://github.com/ossrs/srs/wiki/v3_EN_RTMP-ATC +[v3_CN_HTTPApi]: https://github.com/ossrs/srs/wiki/v3_CN_HTTPApi +[v3_EN_HTTPApi]: https://github.com/ossrs/srs/wiki/v3_EN_HTTPApi [v1_CN_Ingest]: https://github.com/ossrs/srs/wiki/v1_CN_Ingest [v1_EN_Ingest]: https://github.com/ossrs/srs/wiki/v1_EN_Ingest -[v2_CN_DVR]: https://github.com/ossrs/srs/wiki/v2_CN_DVR -[v2_EN_DVR]: https://github.com/ossrs/srs/wiki/v2_EN_DVR +[v3_CN_DVR]: https://github.com/ossrs/srs/wiki/v3_CN_DVR +[v3_EN_DVR]: https://github.com/ossrs/srs/wiki/v3_EN_DVR [v1_CN_SrsLog]: https://github.com/ossrs/srs/wiki/v1_CN_SrsLog [v1_EN_SrsLog]: https://github.com/ossrs/srs/wiki/v1_EN_SrsLog -[v1_CN_DRM2]: https://github.com/ossrs/srs/wiki/v1_CN_DRM#tokentraverse -[v1_EN_DRM2]: https://github.com/ossrs/srs/wiki/v1_EN_DRM#tokentraverse -[v2_CN_SampleHTTP]: https://github.com/ossrs/srs/wiki/v2_CN_SampleHTTP -[v2_EN_SampleHTTP]: https://github.com/ossrs/srs/wiki/v2_EN_SampleHTTP +[v3_CN_DRM2]: https://github.com/ossrs/srs/wiki/v3_CN_DRM#tokentraverse +[v3_EN_DRM2]: https://github.com/ossrs/srs/wiki/v3_EN_DRM#tokentraverse +[v3_CN_SampleHTTP]: https://github.com/ossrs/srs/wiki/v3_CN_SampleHTTP +[v3_EN_SampleHTTP]: https://github.com/ossrs/srs/wiki/v3_EN_SampleHTTP [v2_CN_FlvVodStream]: https://github.com/ossrs/srs/wiki/v2_CN_FlvVodStream [v2_EN_FlvVodStream]: https://github.com/ossrs/srs/wiki/v2_EN_FlvVodStream -[v2_CN_SrsLibrtmp2]: https://github.com/ossrs/srs/wiki/v2_CN_SrsLibrtmp#publish-h264-raw-data -[v2_EN_SrsLibrtmp2]: https://github.com/ossrs/srs/wiki/v2_EN_SrsLibrtmp#publish-h264-raw-data -[v2_CN_SrsLibrtmp3]: https://github.com/ossrs/srs/wiki/v2_CN_SrsLibrtmp#publish-audio-raw-stream -[v2_EN_SrsLibrtmp3]: https://github.com/ossrs/srs/wiki/v2_EN_SrsLibrtmp#publish-audio-raw-stream +[v3_CN_SrsLibrtmp2]: https://github.com/ossrs/srs/wiki/v3_CN_SrsLibrtmp#publish-h264-raw-data +[v3_EN_SrsLibrtmp2]: https://github.com/ossrs/srs/wiki/v3_EN_SrsLibrtmp#publish-h264-raw-data +[v3_CN_SrsLibrtmp3]: https://github.com/ossrs/srs/wiki/v3_CN_SrsLibrtmp#publish-audio-raw-stream +[v3_EN_SrsLibrtmp3]: https://github.com/ossrs/srs/wiki/v3_EN_SrsLibrtmp#publish-audio-raw-stream [v2_CN_Security]: https://github.com/ossrs/srs/wiki/v2_CN_Security [v2_EN_Security]: https://github.com/ossrs/srs/wiki/v2_EN_Security [v2_CN_DeliveryHttpStream]: https://github.com/ossrs/srs/wiki/v2_CN_DeliveryHttpStream [v2_EN_DeliveryHttpStream]: https://github.com/ossrs/srs/wiki/v2_EN_DeliveryHttpStream [v2_CN_DeliveryHDS]: https://github.com/ossrs/srs/wiki/v2_CN_DeliveryHDS [v2_EN_DeliveryHDS]: https://github.com/ossrs/srs/wiki/v2_EN_DeliveryHDS +[v2_CN_Streamer]: https://github.com/ossrs/srs/wiki/v2_CN_Streamer +[v2_EN_Streamer]: https://github.com/ossrs/srs/wiki/v2_EN_Streamer [v2_CN_Streamer2]: https://github.com/ossrs/srs/wiki/v2_CN_Streamer#push-http-flv-to-srs [v2_EN_Streamer2]: https://github.com/ossrs/srs/wiki/v2_EN_Streamer#push-http-flv-to-srs -[v2_CN_SampleHttpFlv]: https://github.com/ossrs/srs/wiki/v2_CN_SampleHttpFlv -[v2_EN_SampleHttpFlv]: https://github.com/ossrs/srs/wiki/v2_EN_SampleHttpFlv -[v2_CN_SampleHttpFlvCluster]: https://github.com/ossrs/srs/wiki/v2_CN_SampleHttpFlvCluster -[v2_EN_SampleHttpFlvCluster]: https://github.com/ossrs/srs/wiki/v2_EN_SampleHttpFlvCluster -[v2_CN_LowLatency]: https://github.com/ossrs/srs/wiki/v2_CN_LowLatency -[v2_EN_LowLatency]: https://github.com/ossrs/srs/wiki/v2_EN_LowLatency -[v2_EN_LowLatency#merged-read]: https://github.com/ossrs/srs/wiki/v2_EN_LowLatency#merged-read +[v3_CN_SampleHttpFlv]: https://github.com/ossrs/srs/wiki/v3_CN_SampleHttpFlv +[v3_EN_SampleHttpFlv]: https://github.com/ossrs/srs/wiki/v3_EN_SampleHttpFlv +[v3_CN_SampleHttpFlvCluster]: https://github.com/ossrs/srs/wiki/v3_CN_SampleHttpFlvCluster +[v3_EN_SampleHttpFlvCluster]: https://github.com/ossrs/srs/wiki/v3_EN_SampleHttpFlvCluster +[v3_CN_LowLatency]: https://github.com/ossrs/srs/wiki/v3_CN_LowLatency +[v3_EN_LowLatency]: https://github.com/ossrs/srs/wiki/v3_EN_LowLatency +[v3_EN_LowLatency#merged-read]: https://github.com/ossrs/srs/wiki/v3_EN_LowLatency#merged-read [v1_CN_Performance#performancereport4k]: https://github.com/ossrs/srs/wiki/v1_CN_Performance#performancereport4k -[v1_CN_DRM#tokentraverse]: https://github.com/ossrs/srs/wiki/v1_CN_DRM#tokentraverse +[v3_CN_DRM#tokentraverse]: https://github.com/ossrs/srs/wiki/v3_CN_DRM#tokentraverse [v1_CN_RaspberryPi]: https://github.com/ossrs/srs/wiki/v1_CN_RaspberryPi -[v2_CN_SrsLibrtmp]: https://github.com/ossrs/srs/wiki/v2_CN_SrsLibrtmp -[v2_CN_Build]: https://github.com/ossrs/srs/wiki/v2_CN_Build -[v2_CN_LowLatency]: https://github.com/ossrs/srs/wiki/v2_CN_LowLatency +[v3_CN_SrsLibrtmp]: https://github.com/ossrs/srs/wiki/v3_CN_SrsLibrtmp +[v3_CN_Build]: https://github.com/ossrs/srs/wiki/v3_CN_Build +[v3_CN_LowLatency]: https://github.com/ossrs/srs/wiki/v3_CN_LowLatency [v1_CN_HowToAskQuestion]: https://github.com/ossrs/srs/wiki/v1_CN_HowToAskQuestion -[v2_CN_Build]: https://github.com/ossrs/srs/wiki/v2_CN_Build +[v3_CN_Build]: https://github.com/ossrs/srs/wiki/v3_CN_Build [v1_CN_Performance]: https://github.com/ossrs/srs/wiki/v1_CN_Performance [v1_CN_RaspberryPi]: https://github.com/ossrs/srs/wiki/v1_CN_RaspberryPi -[v2_CN_LowLatency#merged-read]: https://github.com/ossrs/srs/wiki/v2_CN_LowLatency#merged-read +[v3_CN_LowLatency#merged-read]: https://github.com/ossrs/srs/wiki/v3_CN_LowLatency#merged-read [v1_CN_Product]: https://github.com/ossrs/srs/wiki/v1_CN_Product [v1_CN_ServerSideScript]: https://github.com/ossrs/srs/wiki/v1_CN_ServerSideScript -[v2_EN_LowLatency#merged-write]: https://github.com/ossrs/srs/wiki/v2_EN_LowLatency#merged-write +[v3_EN_LowLatency#merged-write]: https://github.com/ossrs/srs/wiki/v3_EN_LowLatency#merged-write [v2_CN_IDE]: https://github.com/ossrs/srs/wiki/v2_CN_IDE -[v2_CN_LowLatency#merged-write]: https://github.com/ossrs/srs/wiki/v2_CN_LowLatency#merged-write +[v3_CN_LowLatency#merged-write]: https://github.com/ossrs/srs/wiki/v3_CN_LowLatency#merged-write +[v3_CN_NgExec]:https://github.com/ossrs/srs/wiki/v3_CN_NgExec +[v3_EN_NgExec]:https://github.com/ossrs/srs/wiki/v3_EN_NgExec +[v3_CN_ReusePort]:https://github.com/ossrs/srs/wiki/v3_CN_ReusePort +[v3_EN_ReusePort]:https://github.com/ossrs/srs/wiki/v3_EN_ReusePort [bug #213]: https://github.com/ossrs/srs/issues/213 [bug #194]: https://github.com/ossrs/srs/issues/194 @@ -1150,6 +1412,8 @@ Winlin [bug #304]: https://github.com/ossrs/srs/issues/304 [bug #133]: https://github.com/ossrs/srs/issues/133 [bug #92]: https://github.com/ossrs/srs/issues/92 +[bug #367]: https://github.com/ossrs/srs/issues/367 +[bug #471]: https://github.com/ossrs/srs/issues/471 [bug #380]: https://github.com/ossrs/srs/issues/380 [bug #474]: https://github.com/ossrs/srs/issues/474 [bug #484]: https://github.com/ossrs/srs/issues/484 @@ -1200,8 +1464,6 @@ Winlin [bug #310]: https://github.com/ossrs/srs/issues/310 [bug #136]: https://github.com/ossrs/srs/issues/136 [bug #250]: https://github.com/ossrs/srs/issues/250 -[bug #301]: https://github.com/ossrs/srs/issues/301 -[bug #301]: https://github.com/ossrs/srs/issues/301 [bug #268]: https://github.com/ossrs/srs/issues/268 [bug #151]: https://github.com/ossrs/srs/issues/151 [bug #151]: https://github.com/ossrs/srs/issues/151 @@ -1296,7 +1558,6 @@ Winlin [bug #50]: https://github.com/ossrs/srs/issues/50 [bug #34]: https://github.com/ossrs/srs/issues/34 [bug #257-c0]: https://github.com/ossrs/srs/issues/257#issuecomment-66864413 -[bug #110]: https://github.com/ossrs/srs/issues/110 [bug #109]: https://github.com/ossrs/srs/issues/109 [bug #108]: https://github.com/ossrs/srs/issues/108 [bug #104]: https://github.com/ossrs/srs/issues/104 @@ -1315,6 +1576,22 @@ Winlin [bug #59]: https://github.com/ossrs/srs/issues/59 [bug #50]: https://github.com/ossrs/srs/issues/50 [bug #34]: https://github.com/ossrs/srs/issues/34 +[bug #367]: https://github.com/ossrs/srs/issues/367 +[bug #319]: https://github.com/ossrs/srs/issues/319 +[bug #367]: https://github.com/ossrs/srs/issues/367 +[bug #459]: https://github.com/ossrs/srs/issues/459 +[bug #470]: https://github.com/ossrs/srs/issues/470 +[bug #319]: https://github.com/ossrs/srs/issues/319 +[bug #467]: https://github.com/ossrs/srs/issues/467 +[bug #464]: https://github.com/ossrs/srs/issues/464 +[bug #465]: https://github.com/ossrs/srs/issues/465 +[bug #299]: https://github.com/ossrs/srs/issues/299 +[bug #92]: https://github.com/ossrs/srs/issues/92 +[bug #299]: https://github.com/ossrs/srs/issues/299 +[bug #466]: https://github.com/ossrs/srs/issues/466 +[bug #468]: https://github.com/ossrs/srs/issues/468 +[bug #502]: https://github.com/ossrs/srs/issues/502 +[bug #467]: https://github.com/ossrs/srs/issues/467 [bug #512]: https://github.com/ossrs/srs/issues/512 [bug #515]: https://github.com/ossrs/srs/issues/515 [bug #511]: https://github.com/ossrs/srs/issues/511 @@ -1324,6 +1601,9 @@ Winlin [bug #418]: https://github.com/ossrs/srs/issues/418 [bug #509]: https://github.com/ossrs/srs/issues/509 [bug #511]: https://github.com/ossrs/srs/issues/511 +[bug #717]: https://github.com/ossrs/srs/issues/717 +[bug #691]: https://github.com/ossrs/srs/issues/691 +[bug #711]: https://github.com/ossrs/srs/issues/711 [bug #640]: https://github.com/ossrs/srs/issues/640 [bug #661]: https://github.com/ossrs/srs/issues/661 [bug #666]: https://github.com/ossrs/srs/issues/666 @@ -1370,13 +1650,105 @@ Winlin [bug #1339]: https://github.com/ossrs/srs/pull/1339 [bug #1312]: https://github.com/ossrs/srs/pull/1312 [bug #1304]: https://github.com/ossrs/srs/pull/1304 +[bug #1524]: https://github.com/ossrs/srs/issues/1524 [bug #1488]: https://github.com/ossrs/srs/issues/1488 [bug #1551]: https://github.com/ossrs/srs/pull/1551 [bug #1554]: https://github.com/ossrs/srs/pull/1554 +[bug #1672]: https://github.com/ossrs/srs/issues/1672 [bug #xxxxxxxxxx]: https://github.com/ossrs/srs/issues/xxxxxxxxxx +[bug #735]: https://github.com/ossrs/srs/issues/735 +[bug #742]: https://github.com/ossrs/srs/issues/742 +[bug #738]: https://github.com/ossrs/srs/issues/738 +[bug #786]: https://github.com/ossrs/srs/issues/786 +[bug #820]: https://github.com/ossrs/srs/issues/820 +[bug #547]: https://github.com/ossrs/srs/issues/547 +[bug #904]: https://github.com/ossrs/srs/issues/904 +[bug #821]: https://github.com/ossrs/srs/issues/821 +[bug #913]: https://github.com/ossrs/srs/issues/913 +[bug #460]: https://github.com/ossrs/srs/issues/460 +[bug #775]: https://github.com/ossrs/srs/issues/775 +[bug #1057]: https://github.com/ossrs/srs/issues/1057 +[bug #105]: https://github.com/ossrs/srs/issues/105 +[bug #727]: https://github.com/ossrs/srs/issues/727 +[bug #1087]: https://github.com/ossrs/srs/issues/1087 +[bug #1051]: https://github.com/ossrs/srs/issues/1051 +[bug #1093]: https://github.com/ossrs/srs/issues/1093 +[bug #1501]: https://github.com/ossrs/srs/issues/1501 +[bug #1229]: https://github.com/ossrs/srs/issues/1229 +[bug #1042]: https://github.com/ossrs/srs/issues/1042 +[bug #1445]: https://github.com/ossrs/srs/issues/1445 +[bug #1506]: https://github.com/ossrs/srs/issues/1506 +[bug #1520]: https://github.com/ossrs/srs/issues/1520 +[bug #1223]: https://github.com/ossrs/srs/issues/1223 +[bug #1508]: https://github.com/ossrs/srs/issues/1508 +[bug #1535]: https://github.com/ossrs/srs/issues/1535 +[bug #1537]: https://github.com/ossrs/srs/issues/1537 +[bug #1538]: https://github.com/ossrs/srs/issues/1538 +[bug #1282]: https://github.com/ossrs/srs/issues/1282 +[bug #1105]: https://github.com/ossrs/srs/issues/1105 +[bug #1544]: https://github.com/ossrs/srs/issues/1544 +[bug #1255]: https://github.com/ossrs/srs/issues/1255 +[bug #1543]: https://github.com/ossrs/srs/issues/1543 +[bug #1509]: https://github.com/ossrs/srs/issues/1509 +[bug #1575]: https://github.com/ossrs/srs/issues/1575 +[bug #307]: https://github.com/ossrs/srs/issues/307 +[bug #1070]: https://github.com/ossrs/srs/issues/1070 +[bug #1580]: https://github.com/ossrs/srs/issues/1580 +[bug #1547]: https://github.com/ossrs/srs/issues/1547 +[bug #1221]: https://github.com/ossrs/srs/issues/1221 +[bug #1108]: https://github.com/ossrs/srs/issues/1108 +[bug #703]: https://github.com/ossrs/srs/issues/703 +[bug #878]: https://github.com/ossrs/srs/issues/878 +[bug #607]: https://github.com/ossrs/srs/issues/607 +[bug #1303]: https://github.com/ossrs/srs/issues/1303 +[bug #1230]: https://github.com/ossrs/srs/issues/1230 +[bug #1206]: https://github.com/ossrs/srs/issues/1206 +[bug #939]: https://github.com/ossrs/srs/issues/939 +[bug #1186]: https://github.com/ossrs/srs/issues/1186 +[bug #1592]: https://github.com/ossrs/srs/issues/1592 +[bug #665]: https://github.com/ossrs/srs/issues/665 +[bug #1595]: https://github.com/ossrs/srs/issues/1595 +[bug #1601]: https://github.com/ossrs/srs/issues/1601 +[bug #1579]: https://github.com/ossrs/srs/issues/1579 +[bug #1598]: https://github.com/ossrs/srs/issues/1598 +[bug #1634]: https://github.com/ossrs/srs/issues/1634 +[bug #1594]: https://github.com/ossrs/srs/issues/1594 +[bug #1630]: https://github.com/ossrs/srs/issues/1630 +[bug #1635]: https://github.com/ossrs/srs/issues/1635 +[bug #1651]: https://github.com/ossrs/srs/issues/1651 +[bug #1619]: https://github.com/ossrs/srs/issues/1619 +[bug #1629]: https://github.com/ossrs/srs/issues/1629 +[bug #1780]: https://github.com/ossrs/srs/issues/1780 +[bug #1987]: https://github.com/ossrs/srs/issues/1987 +[bug #1548]: https://github.com/ossrs/srs/issues/1548 +[bug #1694]: https://github.com/ossrs/srs/issues/1694 +[bug #2311]: https://github.com/ossrs/srs/issues/2311 +[bug #yyyyyyyyyyyyy]: https://github.com/ossrs/srs/issues/yyyyyyyyyyyyy + [exo #828]: https://github.com/google/ExoPlayer/pull/828 +[r3.0r5]: https://github.com/ossrs/srs/releases/tag/v3.0-r5 +[r3.0r4]: https://github.com/ossrs/srs/releases/tag/v3.0-r4 +[r3.0r3]: https://github.com/ossrs/srs/releases/tag/v3.0-r3 +[r3.0r2]: https://github.com/ossrs/srs/releases/tag/v3.0-r2 +[r3.0r1]: https://github.com/ossrs/srs/releases/tag/v3.0-r1 +[r3.0r0]: https://github.com/ossrs/srs/releases/tag/v3.0-r0 +[r3.0b4]: https://github.com/ossrs/srs/releases/tag/v3.0-b4 +[r3.0b3]: https://github.com/ossrs/srs/releases/tag/v3.0-b3 +[r3.0b2]: https://github.com/ossrs/srs/releases/tag/v3.0-b2 +[r3.0b1]: https://github.com/ossrs/srs/releases/tag/v3.0-b1 +[r3.0b0]: https://github.com/ossrs/srs/releases/tag/v3.0-b0 +[r3.0a9]: https://github.com/ossrs/srs/releases/tag/v3.0-a9 +[r3.0a8]: https://github.com/ossrs/srs/releases/tag/v3.0-a8 +[r3.0a7]: https://github.com/ossrs/srs/releases/tag/v3.0-a7 +[r3.0a6]: https://github.com/ossrs/srs/releases/tag/v3.0-a6 +[r3.0a5]: https://github.com/ossrs/srs/releases/tag/v3.0-a5 +[r3.0a4]: https://github.com/ossrs/srs/releases/tag/v3.0-a4 +[r3.0a3]: https://github.com/ossrs/srs/releases/tag/v3.0-a3 +[r3.0a2]: https://github.com/ossrs/srs/releases/tag/v3.0-a2 +[r3.0a1]: https://github.com/ossrs/srs/releases/tag/v3.0-a1 +[r3.0a0]: https://github.com/ossrs/srs/releases/tag/v3.0-a0 [r2.0r8]: https://github.com/ossrs/srs/releases/tag/v2.0-r8 [r2.0r7]: https://github.com/ossrs/srs/releases/tag/v2.0-r7 [r2.0r6]: https://github.com/ossrs/srs/releases/tag/v2.0-r6 @@ -1420,15 +1792,18 @@ Winlin [contact]: https://github.com/ossrs/srs/wiki/v1_CN_Contact +[v1_CN_Contact]: https://github.com/ossrs/srs/wiki/v1_CN_Contact +[v1_EN_Contact]: https://github.com/ossrs/srs/wiki/v1_EN_Contact [more0]: http://winlinvip.github.io/srs.release/releases/ -[more1]: http://www.ossrs.net/srs.release/releases/ +[more1]: http://ossrs.net/srs.release/releases/ -[srs_CN]: https://github.com/ossrs/srs/wiki/v2_CN_Home -[srs_EN]: https://github.com/ossrs/srs/wiki/v2_EN_Home +[LICENSE]: https://github.com/ossrs/srs/blob/3.0release/LICENSE +[LicenseMixing]: https://github.com/ossrs/srs/wiki/LicenseMixing + +[srs_CN]: https://github.com/ossrs/srs/wiki/v3_CN_Home +[srs_EN]: https://github.com/ossrs/srs/wiki/v3_EN_Home [branch1]: https://github.com/ossrs/srs/tree/1.0release [branch2]: https://github.com/ossrs/srs/tree/2.0release [release2]: https://github.com/ossrs/srs/wiki/v1_CN_Product#release20 [release3]: https://github.com/ossrs/srs/wiki/v1_CN_Product#release30 -[centos0]: http://winlinvip.github.io/srs.release/releases/files/SRS-CentOS6-x86_64-2.0.243.zip -[centos1]: http://www.ossrs.net/srs.release/releases/files/SRS-CentOS6-x86_64-2.0.243.zip diff --git a/trunk/.gitignore b/trunk/.gitignore index 916ba6e72..5ab388b11 100644 --- a/trunk/.gitignore +++ b/trunk/.gitignore @@ -1,14 +1,19 @@ +.idea/ /*.conf -/doc/frozen* -/doc/kungfupanda* -/doc/time* +/*.txt +/*.flv +/*.mp4 +/doc/frozen*.flv +/doc/kungfupanda*.flv +/doc/time*.flv +/doc/source*.mp4 /html /ide/srs_xcode/srs_xcode.xcodeproj/project.xcworkspace/xcshareddata/ /ide/srs_xcode/srs_xcode.xcodeproj/project.xcworkspace/xcuserdata/ /ide/srs_xcode/srs_xcode.xcodeproj/xcuserdata/ /research/aac/ -/research/api-server/.idea/ /research/api-server/static-dir/mse +/research/api-server/static-dir/crossdomain.xml /research/bat/ /research/big/ /research/bitch/ @@ -16,17 +21,25 @@ /research/cgo/ /research/dns/ /research/empty/ -/research/golang/ -/research/librtmp/*.mp4 +/research/golang/golang +/research/golang/temp.flv +/research/librtmp/720p.h264.raw +/research/librtmp/test.h264 /research/licenser/ -/research/players/.idea/ /research/players/fls_player/ /research/players/mic/ -/research/players/srs_player/.idea/ /research/proxy/ /research/redis-ocluster/ /research/rtmfp/ /research/snap/ /research/speex/ /test/ - +.DS_Store +srs +*.dSYM/ +*.gcov +*.ts +*.h264 +*.264 +3rdparty/ffmpeg-4.2-fit +bug diff --git a/trunk/3rdparty/fdk-aac-0.1.3.zip b/trunk/3rdparty/fdk-aac-0.1.3.zip deleted file mode 100644 index c7aeab7bd..000000000 Binary files a/trunk/3rdparty/fdk-aac-0.1.3.zip and /dev/null differ diff --git a/trunk/3rdparty/ffmpeg-4.1.zip b/trunk/3rdparty/ffmpeg-4.1.zip deleted file mode 100644 index 18e5e58f6..000000000 Binary files a/trunk/3rdparty/ffmpeg-4.1.zip and /dev/null differ diff --git a/trunk/3rdparty/gprof/build_gprof2dot.sh b/trunk/3rdparty/gprof/build_gprof2dot.sh deleted file mode 100755 index c8a53ff6d..000000000 --- a/trunk/3rdparty/gprof/build_gprof2dot.sh +++ /dev/null @@ -1,17 +0,0 @@ -#!/bin/bash - -# check exists. -if [[ -d graphviz-2.18 ]]; then - echo "graphviz is ok"; - exit 0; -fi - -# check sudoer. -sudo echo "ok" > /dev/null 2>&1; -ret=$?; if [[ 0 -ne ${ret} ]]; then echo "you must be sudoer"; exit 1; fi - -unzip -q graphviz-2.36.0.zip -cd graphviz-2.36.0 && ./configure && make && sudo make install -ret=$?; if [[ $ret -ne 0 ]]; then echo "build gprof2dot failed."; exit $ret; fi - -echo "we test in Centos6.0, it's ok" diff --git a/trunk/3rdparty/gprof/gprof2dot.py b/trunk/3rdparty/gprof/gprof2dot.py deleted file mode 100755 index 55eb53ad8..000000000 --- a/trunk/3rdparty/gprof/gprof2dot.py +++ /dev/null @@ -1,2227 +0,0 @@ -#!/usr/bin/env python -# -# Copyright 2008-2009 Jose Fonseca -# -# 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 of the License, or -# (at your option) any later version. -# -# This program 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 this program. If not, see . -# - -"""Generate a dot graph from the output of several profilers.""" - -__author__ = "Jose Fonseca" - -__version__ = "1.0" - - -import sys -import math -import os.path -import re -import textwrap -import optparse -import xml.parsers.expat - - -try: - # Debugging helper module - import debug -except ImportError: - pass - - -def percentage(p): - return "%.02f%%" % (p*100.0,) - -def add(a, b): - return a + b - -def equal(a, b): - if a == b: - return a - else: - return None - -def fail(a, b): - assert False - - -tol = 2 ** -23 - -def ratio(numerator, denominator): - try: - ratio = float(numerator)/float(denominator) - except ZeroDivisionError: - # 0/0 is undefined, but 1.0 yields more useful results - return 1.0 - if ratio < 0.0: - if ratio < -tol: - sys.stderr.write('warning: negative ratio (%s/%s)\n' % (numerator, denominator)) - return 0.0 - if ratio > 1.0: - if ratio > 1.0 + tol: - sys.stderr.write('warning: ratio greater than one (%s/%s)\n' % (numerator, denominator)) - return 1.0 - return ratio - - -class UndefinedEvent(Exception): - """Raised when attempting to get an event which is undefined.""" - - def __init__(self, event): - Exception.__init__(self) - self.event = event - - def __str__(self): - return 'unspecified event %s' % self.event.name - - -class Event(object): - """Describe a kind of event, and its basic operations.""" - - def __init__(self, name, null, aggregator, formatter = str): - self.name = name - self._null = null - self._aggregator = aggregator - self._formatter = formatter - - def __eq__(self, other): - return self is other - - def __hash__(self): - return id(self) - - def null(self): - return self._null - - def aggregate(self, val1, val2): - """Aggregate two event values.""" - assert val1 is not None - assert val2 is not None - return self._aggregator(val1, val2) - - def format(self, val): - """Format an event value.""" - assert val is not None - return self._formatter(val) - - -MODULE = Event("Module", None, equal) -PROCESS = Event("Process", None, equal) - -CALLS = Event("Calls", 0, add) -SAMPLES = Event("Samples", 0, add) -SAMPLES2 = Event("Samples", 0, add) - -TIME = Event("Time", 0.0, add, lambda x: '(' + str(x) + ')') -TIME_RATIO = Event("Time ratio", 0.0, add, lambda x: '(' + percentage(x) + ')') -TOTAL_TIME = Event("Total time", 0.0, fail) -TOTAL_TIME_RATIO = Event("Total time ratio", 0.0, fail, percentage) - -CALL_RATIO = Event("Call ratio", 0.0, add, percentage) - -PRUNE_RATIO = Event("Prune ratio", 0.0, add, percentage) - - -class Object(object): - """Base class for all objects in profile which can store events.""" - - def __init__(self, events=None): - if events is None: - self.events = {} - else: - self.events = events - - def __hash__(self): - return id(self) - - def __eq__(self, other): - return self is other - - def __contains__(self, event): - return event in self.events - - def __getitem__(self, event): - try: - return self.events[event] - except KeyError: - raise UndefinedEvent(event) - - def __setitem__(self, event, value): - if value is None: - if event in self.events: - del self.events[event] - else: - self.events[event] = value - - -class Call(Object): - """A call between functions. - - There should be at most one call object for every pair of functions. - """ - - def __init__(self, callee_id): - Object.__init__(self) - self.callee_id = callee_id - - -class Function(Object): - """A function.""" - - def __init__(self, id, name): - Object.__init__(self) - self.id = id - self.name = name - self.calls = {} - self.cycle = None - - def add_call(self, call): - if call.callee_id in self.calls: - sys.stderr.write('warning: overwriting call from function %s to %s\n' % (str(self.id), str(call.callee_id))) - self.calls[call.callee_id] = call - - # TODO: write utility functions - - def __repr__(self): - return self.name - - -class Cycle(Object): - """A cycle made from recursive function calls.""" - - def __init__(self): - Object.__init__(self) - # XXX: Do cycles need an id? - self.functions = set() - - def add_function(self, function): - assert function not in self.functions - self.functions.add(function) - # XXX: Aggregate events? - if function.cycle is not None: - for other in function.cycle.functions: - if function not in self.functions: - self.add_function(other) - function.cycle = self - - -class Profile(Object): - """The whole profile.""" - - def __init__(self): - Object.__init__(self) - self.functions = {} - self.cycles = [] - - def add_function(self, function): - if function.id in self.functions: - sys.stderr.write('warning: overwriting function %s (id %s)\n' % (function.name, str(function.id))) - self.functions[function.id] = function - - def add_cycle(self, cycle): - self.cycles.append(cycle) - - def validate(self): - """Validate the edges.""" - - for function in self.functions.itervalues(): - for callee_id in function.calls.keys(): - assert function.calls[callee_id].callee_id == callee_id - if callee_id not in self.functions: - sys.stderr.write('warning: call to undefined function %s from function %s\n' % (str(callee_id), function.name)) - del function.calls[callee_id] - - def find_cycles(self): - """Find cycles using Tarjan's strongly connected components algorithm.""" - - # Apply the Tarjan's algorithm successively until all functions are visited - visited = set() - for function in self.functions.itervalues(): - if function not in visited: - self._tarjan(function, 0, [], {}, {}, visited) - cycles = [] - for function in self.functions.itervalues(): - if function.cycle is not None and function.cycle not in cycles: - cycles.append(function.cycle) - self.cycles = cycles - if 0: - for cycle in cycles: - sys.stderr.write("Cycle:\n") - for member in cycle.functions: - sys.stderr.write("\tFunction %s\n" % member.name) - - def _tarjan(self, function, order, stack, orders, lowlinks, visited): - """Tarjan's strongly connected components algorithm. - - See also: - - http://en.wikipedia.org/wiki/Tarjan's_strongly_connected_components_algorithm - """ - - visited.add(function) - orders[function] = order - lowlinks[function] = order - order += 1 - pos = len(stack) - stack.append(function) - for call in function.calls.itervalues(): - callee = self.functions[call.callee_id] - # TODO: use a set to optimize lookup - if callee not in orders: - order = self._tarjan(callee, order, stack, orders, lowlinks, visited) - lowlinks[function] = min(lowlinks[function], lowlinks[callee]) - elif callee in stack: - lowlinks[function] = min(lowlinks[function], orders[callee]) - if lowlinks[function] == orders[function]: - # Strongly connected component found - members = stack[pos:] - del stack[pos:] - if len(members) > 1: - cycle = Cycle() - for member in members: - cycle.add_function(member) - return order - - def call_ratios(self, event): - # Aggregate for incoming calls - cycle_totals = {} - for cycle in self.cycles: - cycle_totals[cycle] = 0.0 - function_totals = {} - for function in self.functions.itervalues(): - function_totals[function] = 0.0 - for function in self.functions.itervalues(): - for call in function.calls.itervalues(): - if call.callee_id != function.id: - callee = self.functions[call.callee_id] - function_totals[callee] += call[event] - if callee.cycle is not None and callee.cycle is not function.cycle: - cycle_totals[callee.cycle] += call[event] - - # Compute the ratios - for function in self.functions.itervalues(): - for call in function.calls.itervalues(): - assert CALL_RATIO not in call - if call.callee_id != function.id: - callee = self.functions[call.callee_id] - if callee.cycle is not None and callee.cycle is not function.cycle: - total = cycle_totals[callee.cycle] - else: - total = function_totals[callee] - call[CALL_RATIO] = ratio(call[event], total) - - def integrate(self, outevent, inevent): - """Propagate function time ratio allong the function calls. - - Must be called after finding the cycles. - - See also: - - http://citeseer.ist.psu.edu/graham82gprof.html - """ - - # Sanity checking - assert outevent not in self - for function in self.functions.itervalues(): - assert outevent not in function - assert inevent in function - for call in function.calls.itervalues(): - assert outevent not in call - if call.callee_id != function.id: - assert CALL_RATIO in call - - # Aggregate the input for each cycle - for cycle in self.cycles: - total = inevent.null() - for function in self.functions.itervalues(): - total = inevent.aggregate(total, function[inevent]) - self[inevent] = total - - # Integrate along the edges - total = inevent.null() - for function in self.functions.itervalues(): - total = inevent.aggregate(total, function[inevent]) - self._integrate_function(function, outevent, inevent) - self[outevent] = total - - def _integrate_function(self, function, outevent, inevent): - if function.cycle is not None: - return self._integrate_cycle(function.cycle, outevent, inevent) - else: - if outevent not in function: - total = function[inevent] - for call in function.calls.itervalues(): - if call.callee_id != function.id: - total += self._integrate_call(call, outevent, inevent) - function[outevent] = total - return function[outevent] - - def _integrate_call(self, call, outevent, inevent): - assert outevent not in call - assert CALL_RATIO in call - callee = self.functions[call.callee_id] - subtotal = call[CALL_RATIO]*self._integrate_function(callee, outevent, inevent) - call[outevent] = subtotal - return subtotal - - def _integrate_cycle(self, cycle, outevent, inevent): - if outevent not in cycle: - - # Compute the outevent for the whole cycle - total = inevent.null() - for member in cycle.functions: - subtotal = member[inevent] - for call in member.calls.itervalues(): - callee = self.functions[call.callee_id] - if callee.cycle is not cycle: - subtotal += self._integrate_call(call, outevent, inevent) - total += subtotal - cycle[outevent] = total - - # Compute the time propagated to callers of this cycle - callees = {} - for function in self.functions.itervalues(): - if function.cycle is not cycle: - for call in function.calls.itervalues(): - callee = self.functions[call.callee_id] - if callee.cycle is cycle: - try: - callees[callee] += call[CALL_RATIO] - except KeyError: - callees[callee] = call[CALL_RATIO] - - for member in cycle.functions: - member[outevent] = outevent.null() - - for callee, call_ratio in callees.iteritems(): - ranks = {} - call_ratios = {} - partials = {} - self._rank_cycle_function(cycle, callee, 0, ranks) - self._call_ratios_cycle(cycle, callee, ranks, call_ratios, set()) - partial = self._integrate_cycle_function(cycle, callee, call_ratio, partials, ranks, call_ratios, outevent, inevent) - assert partial == max(partials.values()) - assert not total or abs(1.0 - partial/(call_ratio*total)) <= 0.001 - - return cycle[outevent] - - def _rank_cycle_function(self, cycle, function, rank, ranks): - if function not in ranks or ranks[function] > rank: - ranks[function] = rank - for call in function.calls.itervalues(): - if call.callee_id != function.id: - callee = self.functions[call.callee_id] - if callee.cycle is cycle: - self._rank_cycle_function(cycle, callee, rank + 1, ranks) - - def _call_ratios_cycle(self, cycle, function, ranks, call_ratios, visited): - if function not in visited: - visited.add(function) - for call in function.calls.itervalues(): - if call.callee_id != function.id: - callee = self.functions[call.callee_id] - if callee.cycle is cycle: - if ranks[callee] > ranks[function]: - call_ratios[callee] = call_ratios.get(callee, 0.0) + call[CALL_RATIO] - self._call_ratios_cycle(cycle, callee, ranks, call_ratios, visited) - - def _integrate_cycle_function(self, cycle, function, partial_ratio, partials, ranks, call_ratios, outevent, inevent): - if function not in partials: - partial = partial_ratio*function[inevent] - for call in function.calls.itervalues(): - if call.callee_id != function.id: - callee = self.functions[call.callee_id] - if callee.cycle is not cycle: - assert outevent in call - partial += partial_ratio*call[outevent] - else: - if ranks[callee] > ranks[function]: - callee_partial = self._integrate_cycle_function(cycle, callee, partial_ratio, partials, ranks, call_ratios, outevent, inevent) - call_ratio = ratio(call[CALL_RATIO], call_ratios[callee]) - call_partial = call_ratio*callee_partial - try: - call[outevent] += call_partial - except UndefinedEvent: - call[outevent] = call_partial - partial += call_partial - partials[function] = partial - try: - function[outevent] += partial - except UndefinedEvent: - function[outevent] = partial - return partials[function] - - def aggregate(self, event): - """Aggregate an event for the whole profile.""" - - total = event.null() - for function in self.functions.itervalues(): - try: - total = event.aggregate(total, function[event]) - except UndefinedEvent: - return - self[event] = total - - def ratio(self, outevent, inevent): - assert outevent not in self - assert inevent in self - for function in self.functions.itervalues(): - assert outevent not in function - assert inevent in function - function[outevent] = ratio(function[inevent], self[inevent]) - for call in function.calls.itervalues(): - assert outevent not in call - if inevent in call: - call[outevent] = ratio(call[inevent], self[inevent]) - self[outevent] = 1.0 - - def prune(self, node_thres, edge_thres): - """Prune the profile""" - - # compute the prune ratios - for function in self.functions.itervalues(): - try: - function[PRUNE_RATIO] = function[TOTAL_TIME_RATIO] - except UndefinedEvent: - pass - - for call in function.calls.itervalues(): - callee = self.functions[call.callee_id] - - if TOTAL_TIME_RATIO in call: - # handle exact cases first - call[PRUNE_RATIO] = call[TOTAL_TIME_RATIO] - else: - try: - # make a safe estimate - call[PRUNE_RATIO] = min(function[TOTAL_TIME_RATIO], callee[TOTAL_TIME_RATIO]) - except UndefinedEvent: - pass - - # prune the nodes - for function_id in self.functions.keys(): - function = self.functions[function_id] - try: - if function[PRUNE_RATIO] < node_thres: - del self.functions[function_id] - except UndefinedEvent: - pass - - # prune the egdes - for function in self.functions.itervalues(): - for callee_id in function.calls.keys(): - call = function.calls[callee_id] - try: - if callee_id not in self.functions or call[PRUNE_RATIO] < edge_thres: - del function.calls[callee_id] - except UndefinedEvent: - pass - - def dump(self): - for function in self.functions.itervalues(): - sys.stderr.write('Function %s:\n' % (function.name,)) - self._dump_events(function.events) - for call in function.calls.itervalues(): - callee = self.functions[call.callee_id] - sys.stderr.write(' Call %s:\n' % (callee.name,)) - self._dump_events(call.events) - for cycle in self.cycles: - sys.stderr.write('Cycle:\n') - self._dump_events(cycle.events) - for function in cycle.functions: - sys.stderr.write(' Function %s\n' % (function.name,)) - - def _dump_events(self, events): - for event, value in events.iteritems(): - sys.stderr.write(' %s: %s\n' % (event.name, event.format(value))) - - -class Struct: - """Masquerade a dictionary with a structure-like behavior.""" - - def __init__(self, attrs = None): - if attrs is None: - attrs = {} - self.__dict__['_attrs'] = attrs - - def __getattr__(self, name): - try: - return self._attrs[name] - except KeyError: - raise AttributeError(name) - - def __setattr__(self, name, value): - self._attrs[name] = value - - def __str__(self): - return str(self._attrs) - - def __repr__(self): - return repr(self._attrs) - - -class ParseError(Exception): - """Raised when parsing to signal mismatches.""" - - def __init__(self, msg, line): - self.msg = msg - # TODO: store more source line information - self.line = line - - def __str__(self): - return '%s: %r' % (self.msg, self.line) - - -class Parser: - """Parser interface.""" - - def __init__(self): - pass - - def parse(self): - raise NotImplementedError - - -class LineParser(Parser): - """Base class for parsers that read line-based formats.""" - - def __init__(self, file): - Parser.__init__(self) - self._file = file - self.__line = None - self.__eof = False - - def readline(self): - line = self._file.readline() - if not line: - self.__line = '' - self.__eof = True - self.__line = line.rstrip('\r\n') - - def lookahead(self): - assert self.__line is not None - return self.__line - - def consume(self): - assert self.__line is not None - line = self.__line - self.readline() - return line - - def eof(self): - assert self.__line is not None - return self.__eof - - -XML_ELEMENT_START, XML_ELEMENT_END, XML_CHARACTER_DATA, XML_EOF = range(4) - - -class XmlToken: - - def __init__(self, type, name_or_data, attrs = None, line = None, column = None): - assert type in (XML_ELEMENT_START, XML_ELEMENT_END, XML_CHARACTER_DATA, XML_EOF) - self.type = type - self.name_or_data = name_or_data - self.attrs = attrs - self.line = line - self.column = column - - def __str__(self): - if self.type == XML_ELEMENT_START: - return '<' + self.name_or_data + ' ...>' - if self.type == XML_ELEMENT_END: - return '' - if self.type == XML_CHARACTER_DATA: - return self.name_or_data - if self.type == XML_EOF: - return 'end of file' - assert 0 - - -class XmlTokenizer: - """Expat based XML tokenizer.""" - - def __init__(self, fp, skip_ws = True): - self.fp = fp - self.tokens = [] - self.index = 0 - self.final = False - self.skip_ws = skip_ws - - self.character_pos = 0, 0 - self.character_data = '' - - self.parser = xml.parsers.expat.ParserCreate() - self.parser.StartElementHandler = self.handle_element_start - self.parser.EndElementHandler = self.handle_element_end - self.parser.CharacterDataHandler = self.handle_character_data - - def handle_element_start(self, name, attributes): - self.finish_character_data() - line, column = self.pos() - token = XmlToken(XML_ELEMENT_START, name, attributes, line, column) - self.tokens.append(token) - - def handle_element_end(self, name): - self.finish_character_data() - line, column = self.pos() - token = XmlToken(XML_ELEMENT_END, name, None, line, column) - self.tokens.append(token) - - def handle_character_data(self, data): - if not self.character_data: - self.character_pos = self.pos() - self.character_data += data - - def finish_character_data(self): - if self.character_data: - if not self.skip_ws or not self.character_data.isspace(): - line, column = self.character_pos - token = XmlToken(XML_CHARACTER_DATA, self.character_data, None, line, column) - self.tokens.append(token) - self.character_data = '' - - def next(self): - size = 16*1024 - while self.index >= len(self.tokens) and not self.final: - self.tokens = [] - self.index = 0 - data = self.fp.read(size) - self.final = len(data) < size - try: - self.parser.Parse(data, self.final) - except xml.parsers.expat.ExpatError, e: - #if e.code == xml.parsers.expat.errors.XML_ERROR_NO_ELEMENTS: - if e.code == 3: - pass - else: - raise e - if self.index >= len(self.tokens): - line, column = self.pos() - token = XmlToken(XML_EOF, None, None, line, column) - else: - token = self.tokens[self.index] - self.index += 1 - return token - - def pos(self): - return self.parser.CurrentLineNumber, self.parser.CurrentColumnNumber - - -class XmlTokenMismatch(Exception): - - def __init__(self, expected, found): - self.expected = expected - self.found = found - - def __str__(self): - return '%u:%u: %s expected, %s found' % (self.found.line, self.found.column, str(self.expected), str(self.found)) - - -class XmlParser(Parser): - """Base XML document parser.""" - - def __init__(self, fp): - Parser.__init__(self) - self.tokenizer = XmlTokenizer(fp) - self.consume() - - def consume(self): - self.token = self.tokenizer.next() - - def match_element_start(self, name): - return self.token.type == XML_ELEMENT_START and self.token.name_or_data == name - - def match_element_end(self, name): - return self.token.type == XML_ELEMENT_END and self.token.name_or_data == name - - def element_start(self, name): - while self.token.type == XML_CHARACTER_DATA: - self.consume() - if self.token.type != XML_ELEMENT_START: - raise XmlTokenMismatch(XmlToken(XML_ELEMENT_START, name), self.token) - if self.token.name_or_data != name: - raise XmlTokenMismatch(XmlToken(XML_ELEMENT_START, name), self.token) - attrs = self.token.attrs - self.consume() - return attrs - - def element_end(self, name): - while self.token.type == XML_CHARACTER_DATA: - self.consume() - if self.token.type != XML_ELEMENT_END: - raise XmlTokenMismatch(XmlToken(XML_ELEMENT_END, name), self.token) - if self.token.name_or_data != name: - raise XmlTokenMismatch(XmlToken(XML_ELEMENT_END, name), self.token) - self.consume() - - def character_data(self, strip = True): - data = '' - while self.token.type == XML_CHARACTER_DATA: - data += self.token.name_or_data - self.consume() - if strip: - data = data.strip() - return data - - -class GprofParser(Parser): - """Parser for GNU gprof output. - - See also: - - Chapter "Interpreting gprof's Output" from the GNU gprof manual - http://sourceware.org/binutils/docs-2.18/gprof/Call-Graph.html#Call-Graph - - File "cg_print.c" from the GNU gprof source code - http://sourceware.org/cgi-bin/cvsweb.cgi/~checkout~/src/gprof/cg_print.c?rev=1.12&cvsroot=src - """ - - def __init__(self, fp): - Parser.__init__(self) - self.fp = fp - self.functions = {} - self.cycles = {} - - def readline(self): - line = self.fp.readline() - if not line: - sys.stderr.write('error: unexpected end of file\n') - sys.exit(1) - line = line.rstrip('\r\n') - return line - - _int_re = re.compile(r'^\d+$') - _float_re = re.compile(r'^\d+\.\d+$') - - def translate(self, mo): - """Extract a structure from a match object, while translating the types in the process.""" - attrs = {} - groupdict = mo.groupdict() - for name, value in groupdict.iteritems(): - if value is None: - value = None - elif self._int_re.match(value): - value = int(value) - elif self._float_re.match(value): - value = float(value) - attrs[name] = (value) - return Struct(attrs) - - _cg_header_re = re.compile( - # original gprof header - r'^\s+called/total\s+parents\s*$|' + - r'^index\s+%time\s+self\s+descendents\s+called\+self\s+name\s+index\s*$|' + - r'^\s+called/total\s+children\s*$|' + - # GNU gprof header - r'^index\s+%\s+time\s+self\s+children\s+called\s+name\s*$' - ) - - _cg_ignore_re = re.compile( - # spontaneous - r'^\s+\s*$|' - # internal calls (such as "mcount") - r'^.*\((\d+)\)$' - ) - - _cg_primary_re = re.compile( - r'^\[(?P\d+)\]?' + - r'\s+(?P\d+\.\d+)' + - r'\s+(?P\d+\.\d+)' + - r'\s+(?P\d+\.\d+)' + - r'\s+(?:(?P\d+)(?:\+(?P\d+))?)?' + - r'\s+(?P\S.*?)' + - r'(?:\s+\d+)>)?' + - r'\s\[(\d+)\]$' - ) - - _cg_parent_re = re.compile( - r'^\s+(?P\d+\.\d+)?' + - r'\s+(?P\d+\.\d+)?' + - r'\s+(?P\d+)(?:/(?P\d+))?' + - r'\s+(?P\S.*?)' + - r'(?:\s+\d+)>)?' + - r'\s\[(?P\d+)\]$' - ) - - _cg_child_re = _cg_parent_re - - _cg_cycle_header_re = re.compile( - r'^\[(?P\d+)\]?' + - r'\s+(?P\d+\.\d+)' + - r'\s+(?P\d+\.\d+)' + - r'\s+(?P\d+\.\d+)' + - r'\s+(?:(?P\d+)(?:\+(?P\d+))?)?' + - r'\s+\d+)\sas\sa\swhole>' + - r'\s\[(\d+)\]$' - ) - - _cg_cycle_member_re = re.compile( - r'^\s+(?P\d+\.\d+)?' + - r'\s+(?P\d+\.\d+)?' + - r'\s+(?P\d+)(?:\+(?P\d+))?' + - r'\s+(?P\S.*?)' + - r'(?:\s+\d+)>)?' + - r'\s\[(?P\d+)\]$' - ) - - _cg_sep_re = re.compile(r'^--+$') - - def parse_function_entry(self, lines): - parents = [] - children = [] - - while True: - if not lines: - sys.stderr.write('warning: unexpected end of entry\n') - line = lines.pop(0) - if line.startswith('['): - break - - # read function parent line - mo = self._cg_parent_re.match(line) - if not mo: - if self._cg_ignore_re.match(line): - continue - sys.stderr.write('warning: unrecognized call graph entry: %r\n' % line) - else: - parent = self.translate(mo) - parents.append(parent) - - # read primary line - mo = self._cg_primary_re.match(line) - if not mo: - sys.stderr.write('warning: unrecognized call graph entry: %r\n' % line) - return - else: - function = self.translate(mo) - - while lines: - line = lines.pop(0) - - # read function subroutine line - mo = self._cg_child_re.match(line) - if not mo: - if self._cg_ignore_re.match(line): - continue - sys.stderr.write('warning: unrecognized call graph entry: %r\n' % line) - else: - child = self.translate(mo) - children.append(child) - - function.parents = parents - function.children = children - - self.functions[function.index] = function - - def parse_cycle_entry(self, lines): - - # read cycle header line - line = lines[0] - mo = self._cg_cycle_header_re.match(line) - if not mo: - sys.stderr.write('warning: unrecognized call graph entry: %r\n' % line) - return - cycle = self.translate(mo) - - # read cycle member lines - cycle.functions = [] - for line in lines[1:]: - mo = self._cg_cycle_member_re.match(line) - if not mo: - sys.stderr.write('warning: unrecognized call graph entry: %r\n' % line) - continue - call = self.translate(mo) - cycle.functions.append(call) - - self.cycles[cycle.cycle] = cycle - - def parse_cg_entry(self, lines): - if lines[0].startswith("["): - self.parse_cycle_entry(lines) - else: - self.parse_function_entry(lines) - - def parse_cg(self): - """Parse the call graph.""" - - # skip call graph header - while not self._cg_header_re.match(self.readline()): - pass - line = self.readline() - while self._cg_header_re.match(line): - line = self.readline() - - # process call graph entries - entry_lines = [] - while line != '\014': # form feed - if line and not line.isspace(): - if self._cg_sep_re.match(line): - self.parse_cg_entry(entry_lines) - entry_lines = [] - else: - entry_lines.append(line) - line = self.readline() - - def parse(self): - self.parse_cg() - self.fp.close() - - profile = Profile() - profile[TIME] = 0.0 - - cycles = {} - for index in self.cycles.iterkeys(): - cycles[index] = Cycle() - - for entry in self.functions.itervalues(): - # populate the function - function = Function(entry.index, entry.name) - function[TIME] = entry.self - if entry.called is not None: - function[CALLS] = entry.called - if entry.called_self is not None: - call = Call(entry.index) - call[CALLS] = entry.called_self - function[CALLS] += entry.called_self - - # populate the function calls - for child in entry.children: - call = Call(child.index) - - assert child.called is not None - call[CALLS] = child.called - - if child.index not in self.functions: - # NOTE: functions that were never called but were discovered by gprof's - # static call graph analysis dont have a call graph entry so we need - # to add them here - missing = Function(child.index, child.name) - function[TIME] = 0.0 - function[CALLS] = 0 - profile.add_function(missing) - - function.add_call(call) - - profile.add_function(function) - - if entry.cycle is not None: - try: - cycle = cycles[entry.cycle] - except KeyError: - sys.stderr.write('warning: entry missing\n' % entry.cycle) - cycle = Cycle() - cycles[entry.cycle] = cycle - cycle.add_function(function) - - profile[TIME] = profile[TIME] + function[TIME] - - for cycle in cycles.itervalues(): - profile.add_cycle(cycle) - - # Compute derived events - profile.validate() - profile.ratio(TIME_RATIO, TIME) - profile.call_ratios(CALLS) - profile.integrate(TOTAL_TIME, TIME) - profile.ratio(TOTAL_TIME_RATIO, TOTAL_TIME) - - return profile - - -class OprofileParser(LineParser): - """Parser for oprofile callgraph output. - - See also: - - http://oprofile.sourceforge.net/doc/opreport.html#opreport-callgraph - """ - - _fields_re = { - 'samples': r'(?P\d+)', - '%': r'(?P\S+)', - 'linenr info': r'(?P\(no location information\)|\S+:\d+)', - 'image name': r'(?P\S+(?:\s\(tgid:[^)]*\))?)', - 'app name': r'(?P\S+)', - 'symbol name': r'(?P\(no symbols\)|.+?)', - } - - def __init__(self, infile): - LineParser.__init__(self, infile) - self.entries = {} - self.entry_re = None - - def add_entry(self, callers, function, callees): - try: - entry = self.entries[function.id] - except KeyError: - self.entries[function.id] = (callers, function, callees) - else: - callers_total, function_total, callees_total = entry - self.update_subentries_dict(callers_total, callers) - function_total.samples += function.samples - self.update_subentries_dict(callees_total, callees) - - def update_subentries_dict(self, totals, partials): - for partial in partials.itervalues(): - try: - total = totals[partial.id] - except KeyError: - totals[partial.id] = partial - else: - total.samples += partial.samples - - def parse(self): - # read lookahead - self.readline() - - self.parse_header() - while self.lookahead(): - self.parse_entry() - - profile = Profile() - - reverse_call_samples = {} - - # populate the profile - profile[SAMPLES] = 0 - for _callers, _function, _callees in self.entries.itervalues(): - function = Function(_function.id, _function.name) - function[SAMPLES] = _function.samples - profile.add_function(function) - profile[SAMPLES] += _function.samples - - if _function.application: - function[PROCESS] = os.path.basename(_function.application) - if _function.image: - function[MODULE] = os.path.basename(_function.image) - - total_callee_samples = 0 - for _callee in _callees.itervalues(): - total_callee_samples += _callee.samples - - for _callee in _callees.itervalues(): - if not _callee.self: - call = Call(_callee.id) - call[SAMPLES2] = _callee.samples - function.add_call(call) - - # compute derived data - profile.validate() - profile.find_cycles() - profile.ratio(TIME_RATIO, SAMPLES) - profile.call_ratios(SAMPLES2) - profile.integrate(TOTAL_TIME_RATIO, TIME_RATIO) - - return profile - - def parse_header(self): - while not self.match_header(): - self.consume() - line = self.lookahead() - fields = re.split(r'\s\s+', line) - entry_re = r'^\s*' + r'\s+'.join([self._fields_re[field] for field in fields]) + r'(?P\s+\[self\])?$' - self.entry_re = re.compile(entry_re) - self.skip_separator() - - def parse_entry(self): - callers = self.parse_subentries() - if self.match_primary(): - function = self.parse_subentry() - if function is not None: - callees = self.parse_subentries() - self.add_entry(callers, function, callees) - self.skip_separator() - - def parse_subentries(self): - subentries = {} - while self.match_secondary(): - subentry = self.parse_subentry() - subentries[subentry.id] = subentry - return subentries - - def parse_subentry(self): - entry = Struct() - line = self.consume() - mo = self.entry_re.match(line) - if not mo: - raise ParseError('failed to parse', line) - fields = mo.groupdict() - entry.samples = int(fields.get('samples', 0)) - entry.percentage = float(fields.get('percentage', 0.0)) - if 'source' in fields and fields['source'] != '(no location information)': - source = fields['source'] - filename, lineno = source.split(':') - entry.filename = filename - entry.lineno = int(lineno) - else: - source = '' - entry.filename = None - entry.lineno = None - entry.image = fields.get('image', '') - entry.application = fields.get('application', '') - if 'symbol' in fields and fields['symbol'] != '(no symbols)': - entry.symbol = fields['symbol'] - else: - entry.symbol = '' - if entry.symbol.startswith('"') and entry.symbol.endswith('"'): - entry.symbol = entry.symbol[1:-1] - entry.id = ':'.join((entry.application, entry.image, source, entry.symbol)) - entry.self = fields.get('self', None) != None - if entry.self: - entry.id += ':self' - if entry.symbol: - entry.name = entry.symbol - else: - entry.name = entry.image - return entry - - def skip_separator(self): - while not self.match_separator(): - self.consume() - self.consume() - - def match_header(self): - line = self.lookahead() - return line.startswith('samples') - - def match_separator(self): - line = self.lookahead() - return line == '-'*len(line) - - def match_primary(self): - line = self.lookahead() - return not line[:1].isspace() - - def match_secondary(self): - line = self.lookahead() - return line[:1].isspace() - - -class SysprofParser(XmlParser): - - def __init__(self, stream): - XmlParser.__init__(self, stream) - - def parse(self): - objects = {} - nodes = {} - - self.element_start('profile') - while self.token.type == XML_ELEMENT_START: - if self.token.name_or_data == 'objects': - assert not objects - objects = self.parse_items('objects') - elif self.token.name_or_data == 'nodes': - assert not nodes - nodes = self.parse_items('nodes') - else: - self.parse_value(self.token.name_or_data) - self.element_end('profile') - - return self.build_profile(objects, nodes) - - def parse_items(self, name): - assert name[-1] == 's' - items = {} - self.element_start(name) - while self.token.type == XML_ELEMENT_START: - id, values = self.parse_item(name[:-1]) - assert id not in items - items[id] = values - self.element_end(name) - return items - - def parse_item(self, name): - attrs = self.element_start(name) - id = int(attrs['id']) - values = self.parse_values() - self.element_end(name) - return id, values - - def parse_values(self): - values = {} - while self.token.type == XML_ELEMENT_START: - name = self.token.name_or_data - value = self.parse_value(name) - assert name not in values - values[name] = value - return values - - def parse_value(self, tag): - self.element_start(tag) - value = self.character_data() - self.element_end(tag) - if value.isdigit(): - return int(value) - if value.startswith('"') and value.endswith('"'): - return value[1:-1] - return value - - def build_profile(self, objects, nodes): - profile = Profile() - - profile[SAMPLES] = 0 - for id, object in objects.iteritems(): - # Ignore fake objects (process names, modules, "Everything", "kernel", etc.) - if object['self'] == 0: - continue - - function = Function(id, object['name']) - function[SAMPLES] = object['self'] - profile.add_function(function) - profile[SAMPLES] += function[SAMPLES] - - for id, node in nodes.iteritems(): - # Ignore fake calls - if node['self'] == 0: - continue - - # Find a non-ignored parent - parent_id = node['parent'] - while parent_id != 0: - parent = nodes[parent_id] - caller_id = parent['object'] - if objects[caller_id]['self'] != 0: - break - parent_id = parent['parent'] - if parent_id == 0: - continue - - callee_id = node['object'] - - assert objects[caller_id]['self'] - assert objects[callee_id]['self'] - - function = profile.functions[caller_id] - - samples = node['self'] - try: - call = function.calls[callee_id] - except KeyError: - call = Call(callee_id) - call[SAMPLES2] = samples - function.add_call(call) - else: - call[SAMPLES2] += samples - - # Compute derived events - profile.validate() - profile.find_cycles() - profile.ratio(TIME_RATIO, SAMPLES) - profile.call_ratios(SAMPLES2) - profile.integrate(TOTAL_TIME_RATIO, TIME_RATIO) - - return profile - - -class SharkParser(LineParser): - """Parser for MacOSX Shark output. - - Author: tom@dbservice.com - """ - - def __init__(self, infile): - LineParser.__init__(self, infile) - self.stack = [] - self.entries = {} - - def add_entry(self, function): - try: - entry = self.entries[function.id] - except KeyError: - self.entries[function.id] = (function, { }) - else: - function_total, callees_total = entry - function_total.samples += function.samples - - def add_callee(self, function, callee): - func, callees = self.entries[function.id] - try: - entry = callees[callee.id] - except KeyError: - callees[callee.id] = callee - else: - entry.samples += callee.samples - - def parse(self): - self.readline() - self.readline() - self.readline() - self.readline() - - match = re.compile(r'(?P[|+ ]*)(?P\d+), (?P[^,]+), (?P.*)') - - while self.lookahead(): - line = self.consume() - mo = match.match(line) - if not mo: - raise ParseError('failed to parse', line) - - fields = mo.groupdict() - prefix = len(fields.get('prefix', 0)) / 2 - 1 - - symbol = str(fields.get('symbol', 0)) - image = str(fields.get('image', 0)) - - entry = Struct() - entry.id = ':'.join([symbol, image]) - entry.samples = int(fields.get('samples', 0)) - - entry.name = symbol - entry.image = image - - # adjust the callstack - if prefix < len(self.stack): - del self.stack[prefix:] - - if prefix == len(self.stack): - self.stack.append(entry) - - # if the callstack has had an entry, it's this functions caller - if prefix > 0: - self.add_callee(self.stack[prefix - 1], entry) - - self.add_entry(entry) - - profile = Profile() - profile[SAMPLES] = 0 - for _function, _callees in self.entries.itervalues(): - function = Function(_function.id, _function.name) - function[SAMPLES] = _function.samples - profile.add_function(function) - profile[SAMPLES] += _function.samples - - if _function.image: - function[MODULE] = os.path.basename(_function.image) - - for _callee in _callees.itervalues(): - call = Call(_callee.id) - call[SAMPLES] = _callee.samples - function.add_call(call) - - # compute derived data - profile.validate() - profile.find_cycles() - profile.ratio(TIME_RATIO, SAMPLES) - profile.call_ratios(SAMPLES) - profile.integrate(TOTAL_TIME_RATIO, TIME_RATIO) - - return profile - - -class SleepyParser(Parser): - """Parser for GNU gprof output. - - See also: - - http://www.codersnotes.com/sleepy/ - - http://sleepygraph.sourceforge.net/ - """ - - def __init__(self, filename): - Parser.__init__(self) - - from zipfile import ZipFile - - self.database = ZipFile(filename) - - self.symbols = {} - self.calls = {} - - self.profile = Profile() - - _symbol_re = re.compile( - r'^(?P\w+)' + - r'\s+"(?P[^"]*)"' + - r'\s+"(?P[^"]*)"' + - r'\s+"(?P[^"]*)"' + - r'\s+(?P\d+)$' - ) - - def parse_symbols(self): - lines = self.database.read('symbols.txt').splitlines() - for line in lines: - mo = self._symbol_re.match(line) - if mo: - symbol_id, module, procname, sourcefile, sourceline = mo.groups() - - function_id = ':'.join([module, procname]) - - try: - function = self.profile.functions[function_id] - except KeyError: - function = Function(function_id, procname) - function[SAMPLES] = 0 - self.profile.add_function(function) - - self.symbols[symbol_id] = function - - def parse_callstacks(self): - lines = self.database.read("callstacks.txt").splitlines() - for line in lines: - fields = line.split() - samples = int(fields[0]) - callstack = fields[1:] - - callstack = [self.symbols[symbol_id] for symbol_id in callstack] - - callee = callstack[0] - - callee[SAMPLES] += samples - self.profile[SAMPLES] += samples - - for caller in callstack[1:]: - try: - call = caller.calls[callee.id] - except KeyError: - call = Call(callee.id) - call[SAMPLES2] = samples - caller.add_call(call) - else: - call[SAMPLES2] += samples - - callee = caller - - def parse(self): - profile = self.profile - profile[SAMPLES] = 0 - - self.parse_symbols() - self.parse_callstacks() - - # Compute derived events - profile.validate() - profile.find_cycles() - profile.ratio(TIME_RATIO, SAMPLES) - profile.call_ratios(SAMPLES2) - profile.integrate(TOTAL_TIME_RATIO, TIME_RATIO) - - return profile - - -class AQtimeTable: - - def __init__(self, name, fields): - self.name = name - - self.fields = fields - self.field_column = {} - for column in range(len(fields)): - self.field_column[fields[column]] = column - self.rows = [] - - def __len__(self): - return len(self.rows) - - def __iter__(self): - for values, children in self.rows: - fields = {} - for name, value in zip(self.fields, values): - fields[name] = value - children = dict([(child.name, child) for child in children]) - yield fields, children - raise StopIteration - - def add_row(self, values, children=()): - self.rows.append((values, children)) - - -class AQtimeParser(XmlParser): - - def __init__(self, stream): - XmlParser.__init__(self, stream) - self.tables = {} - - def parse(self): - self.element_start('AQtime_Results') - self.parse_headers() - results = self.parse_results() - self.element_end('AQtime_Results') - return self.build_profile(results) - - def parse_headers(self): - self.element_start('HEADERS') - while self.token.type == XML_ELEMENT_START: - self.parse_table_header() - self.element_end('HEADERS') - - def parse_table_header(self): - attrs = self.element_start('TABLE_HEADER') - name = attrs['NAME'] - id = int(attrs['ID']) - field_types = [] - field_names = [] - while self.token.type == XML_ELEMENT_START: - field_type, field_name = self.parse_table_field() - field_types.append(field_type) - field_names.append(field_name) - self.element_end('TABLE_HEADER') - self.tables[id] = name, field_types, field_names - - def parse_table_field(self): - attrs = self.element_start('TABLE_FIELD') - type = attrs['TYPE'] - name = self.character_data() - self.element_end('TABLE_FIELD') - return type, name - - def parse_results(self): - self.element_start('RESULTS') - table = self.parse_data() - self.element_end('RESULTS') - return table - - def parse_data(self): - rows = [] - attrs = self.element_start('DATA') - table_id = int(attrs['TABLE_ID']) - table_name, field_types, field_names = self.tables[table_id] - table = AQtimeTable(table_name, field_names) - while self.token.type == XML_ELEMENT_START: - row, children = self.parse_row(field_types) - table.add_row(row, children) - self.element_end('DATA') - return table - - def parse_row(self, field_types): - row = [None]*len(field_types) - children = [] - self.element_start('ROW') - while self.token.type == XML_ELEMENT_START: - if self.token.name_or_data == 'FIELD': - field_id, field_value = self.parse_field(field_types) - row[field_id] = field_value - elif self.token.name_or_data == 'CHILDREN': - children = self.parse_children() - else: - raise XmlTokenMismatch(" or ", self.token) - self.element_end('ROW') - return row, children - - def parse_field(self, field_types): - attrs = self.element_start('FIELD') - id = int(attrs['ID']) - type = field_types[id] - value = self.character_data() - if type == 'Integer': - value = int(value) - elif type == 'Float': - value = float(value) - elif type == 'Address': - value = int(value) - elif type == 'String': - pass - else: - assert False - self.element_end('FIELD') - return id, value - - def parse_children(self): - children = [] - self.element_start('CHILDREN') - while self.token.type == XML_ELEMENT_START: - table = self.parse_data() - assert table.name not in children - children.append(table) - self.element_end('CHILDREN') - return children - - def build_profile(self, results): - assert results.name == 'Routines' - profile = Profile() - profile[TIME] = 0.0 - for fields, tables in results: - function = self.build_function(fields) - children = tables['Children'] - for fields, _ in children: - call = self.build_call(fields) - function.add_call(call) - profile.add_function(function) - profile[TIME] = profile[TIME] + function[TIME] - profile[TOTAL_TIME] = profile[TIME] - profile.ratio(TOTAL_TIME_RATIO, TOTAL_TIME) - return profile - - def build_function(self, fields): - function = Function(self.build_id(fields), self.build_name(fields)) - function[TIME] = fields['Time'] - function[TOTAL_TIME] = fields['Time with Children'] - #function[TIME_RATIO] = fields['% Time']/100.0 - #function[TOTAL_TIME_RATIO] = fields['% with Children']/100.0 - return function - - def build_call(self, fields): - call = Call(self.build_id(fields)) - call[TIME] = fields['Time'] - call[TOTAL_TIME] = fields['Time with Children'] - #call[TIME_RATIO] = fields['% Time']/100.0 - #call[TOTAL_TIME_RATIO] = fields['% with Children']/100.0 - return call - - def build_id(self, fields): - return ':'.join([fields['Module Name'], fields['Unit Name'], fields['Routine Name']]) - - def build_name(self, fields): - # TODO: use more fields - return fields['Routine Name'] - - -class PstatsParser: - """Parser python profiling statistics saved with te pstats module.""" - - def __init__(self, *filename): - import pstats - try: - self.stats = pstats.Stats(*filename) - except ValueError: - import hotshot.stats - self.stats = hotshot.stats.load(filename[0]) - self.profile = Profile() - self.function_ids = {} - - def get_function_name(self, (filename, line, name)): - module = os.path.splitext(filename)[0] - module = os.path.basename(module) - return "%s:%d:%s" % (module, line, name) - - def get_function(self, key): - try: - id = self.function_ids[key] - except KeyError: - id = len(self.function_ids) - name = self.get_function_name(key) - function = Function(id, name) - self.profile.functions[id] = function - self.function_ids[key] = id - else: - function = self.profile.functions[id] - return function - - def parse(self): - self.profile[TIME] = 0.0 - self.profile[TOTAL_TIME] = self.stats.total_tt - for fn, (cc, nc, tt, ct, callers) in self.stats.stats.iteritems(): - callee = self.get_function(fn) - callee[CALLS] = nc - callee[TOTAL_TIME] = ct - callee[TIME] = tt - self.profile[TIME] += tt - self.profile[TOTAL_TIME] = max(self.profile[TOTAL_TIME], ct) - for fn, value in callers.iteritems(): - caller = self.get_function(fn) - call = Call(callee.id) - if isinstance(value, tuple): - for i in xrange(0, len(value), 4): - nc, cc, tt, ct = value[i:i+4] - if CALLS in call: - call[CALLS] += cc - else: - call[CALLS] = cc - - if TOTAL_TIME in call: - call[TOTAL_TIME] += ct - else: - call[TOTAL_TIME] = ct - - else: - call[CALLS] = value - call[TOTAL_TIME] = ratio(value, nc)*ct - - caller.add_call(call) - #self.stats.print_stats() - #self.stats.print_callees() - - # Compute derived events - self.profile.validate() - self.profile.ratio(TIME_RATIO, TIME) - self.profile.ratio(TOTAL_TIME_RATIO, TOTAL_TIME) - - return self.profile - - -class Theme: - - def __init__(self, - bgcolor = (0.0, 0.0, 1.0), - mincolor = (0.0, 0.0, 0.0), - maxcolor = (0.0, 0.0, 1.0), - fontname = "Arial", - minfontsize = 10.0, - maxfontsize = 10.0, - minpenwidth = 0.5, - maxpenwidth = 4.0, - gamma = 2.2, - skew = 1.0): - self.bgcolor = bgcolor - self.mincolor = mincolor - self.maxcolor = maxcolor - self.fontname = fontname - self.minfontsize = minfontsize - self.maxfontsize = maxfontsize - self.minpenwidth = minpenwidth - self.maxpenwidth = maxpenwidth - self.gamma = gamma - self.skew = skew - - def graph_bgcolor(self): - return self.hsl_to_rgb(*self.bgcolor) - - def graph_fontname(self): - return self.fontname - - def graph_fontsize(self): - return self.minfontsize - - def node_bgcolor(self, weight): - return self.color(weight) - - def node_fgcolor(self, weight): - return self.graph_bgcolor() - - def node_fontsize(self, weight): - return self.fontsize(weight) - - def edge_color(self, weight): - return self.color(weight) - - def edge_fontsize(self, weight): - return self.fontsize(weight) - - def edge_penwidth(self, weight): - return max(weight*self.maxpenwidth, self.minpenwidth) - - def edge_arrowsize(self, weight): - return 0.5 * math.sqrt(self.edge_penwidth(weight)) - - def fontsize(self, weight): - return max(weight**2 * self.maxfontsize, self.minfontsize) - - def color(self, weight): - weight = min(max(weight, 0.0), 1.0) - - hmin, smin, lmin = self.mincolor - hmax, smax, lmax = self.maxcolor - - if self.skew < 0: - raise ValueError("Skew must be greater than 0") - elif self.skew == 1.0: - h = hmin + weight*(hmax - hmin) - s = smin + weight*(smax - smin) - l = lmin + weight*(lmax - lmin) - else: - base = self.skew - h = hmin + ((hmax-hmin)*(-1.0 + (base ** weight)) / (base - 1.0)) - s = smin + ((smax-smin)*(-1.0 + (base ** weight)) / (base - 1.0)) - l = lmin + ((lmax-lmin)*(-1.0 + (base ** weight)) / (base - 1.0)) - - return self.hsl_to_rgb(h, s, l) - - def hsl_to_rgb(self, h, s, l): - """Convert a color from HSL color-model to RGB. - - See also: - - http://www.w3.org/TR/css3-color/#hsl-color - """ - - h = h % 1.0 - s = min(max(s, 0.0), 1.0) - l = min(max(l, 0.0), 1.0) - - if l <= 0.5: - m2 = l*(s + 1.0) - else: - m2 = l + s - l*s - m1 = l*2.0 - m2 - r = self._hue_to_rgb(m1, m2, h + 1.0/3.0) - g = self._hue_to_rgb(m1, m2, h) - b = self._hue_to_rgb(m1, m2, h - 1.0/3.0) - - # Apply gamma correction - r **= self.gamma - g **= self.gamma - b **= self.gamma - - return (r, g, b) - - def _hue_to_rgb(self, m1, m2, h): - if h < 0.0: - h += 1.0 - elif h > 1.0: - h -= 1.0 - if h*6 < 1.0: - return m1 + (m2 - m1)*h*6.0 - elif h*2 < 1.0: - return m2 - elif h*3 < 2.0: - return m1 + (m2 - m1)*(2.0/3.0 - h)*6.0 - else: - return m1 - - -TEMPERATURE_COLORMAP = Theme( - mincolor = (2.0/3.0, 0.80, 0.25), # dark blue - maxcolor = (0.0, 1.0, 0.5), # satured red - gamma = 1.0 -) - -PINK_COLORMAP = Theme( - mincolor = (0.0, 1.0, 0.90), # pink - maxcolor = (0.0, 1.0, 0.5), # satured red -) - -GRAY_COLORMAP = Theme( - mincolor = (0.0, 0.0, 0.85), # light gray - maxcolor = (0.0, 0.0, 0.0), # black -) - -BW_COLORMAP = Theme( - minfontsize = 8.0, - maxfontsize = 24.0, - mincolor = (0.0, 0.0, 0.0), # black - maxcolor = (0.0, 0.0, 0.0), # black - minpenwidth = 0.1, - maxpenwidth = 8.0, -) - - -class DotWriter: - """Writer for the DOT language. - - See also: - - "The DOT Language" specification - http://www.graphviz.org/doc/info/lang.html - """ - - def __init__(self, fp): - self.fp = fp - - def graph(self, profile, theme): - self.begin_graph() - - fontname = theme.graph_fontname() - - self.attr('graph', fontname=fontname, ranksep=0.25, nodesep=0.125) - self.attr('node', fontname=fontname, shape="box", style="filled", fontcolor="white", width=0, height=0) - self.attr('edge', fontname=fontname) - - for function in profile.functions.itervalues(): - labels = [] - for event in PROCESS, MODULE: - if event in function.events: - label = event.format(function[event]) - labels.append(label) - labels.append(function.name) - for event in TOTAL_TIME_RATIO, TIME_RATIO, CALLS: - if event in function.events: - label = event.format(function[event]) - labels.append(label) - - try: - weight = function[PRUNE_RATIO] - except UndefinedEvent: - weight = 0.0 - - label = '\n'.join(labels) - self.node(function.id, - label = label, - color = self.color(theme.node_bgcolor(weight)), - fontcolor = self.color(theme.node_fgcolor(weight)), - fontsize = "%.2f" % theme.node_fontsize(weight), - ) - - for call in function.calls.itervalues(): - callee = profile.functions[call.callee_id] - - labels = [] - for event in TOTAL_TIME_RATIO, CALLS: - if event in call.events: - label = event.format(call[event]) - labels.append(label) - - try: - weight = call[PRUNE_RATIO] - except UndefinedEvent: - try: - weight = callee[PRUNE_RATIO] - except UndefinedEvent: - weight = 0.0 - - label = '\n'.join(labels) - - self.edge(function.id, call.callee_id, - label = label, - color = self.color(theme.edge_color(weight)), - fontcolor = self.color(theme.edge_color(weight)), - fontsize = "%.2f" % theme.edge_fontsize(weight), - penwidth = "%.2f" % theme.edge_penwidth(weight), - labeldistance = "%.2f" % theme.edge_penwidth(weight), - arrowsize = "%.2f" % theme.edge_arrowsize(weight), - ) - - self.end_graph() - - def begin_graph(self): - self.write('digraph {\n') - - def end_graph(self): - self.write('}\n') - - def attr(self, what, **attrs): - self.write("\t") - self.write(what) - self.attr_list(attrs) - self.write(";\n") - - def node(self, node, **attrs): - self.write("\t") - self.id(node) - self.attr_list(attrs) - self.write(";\n") - - def edge(self, src, dst, **attrs): - self.write("\t") - self.id(src) - self.write(" -> ") - self.id(dst) - self.attr_list(attrs) - self.write(";\n") - - def attr_list(self, attrs): - if not attrs: - return - self.write(' [') - first = True - for name, value in attrs.iteritems(): - if first: - first = False - else: - self.write(", ") - self.id(name) - self.write('=') - self.id(value) - self.write(']') - - def id(self, id): - if isinstance(id, (int, float)): - s = str(id) - elif isinstance(id, basestring): - if id.isalnum(): - s = id - else: - s = self.escape(id) - else: - raise TypeError - self.write(s) - - def color(self, (r, g, b)): - - def float2int(f): - if f <= 0.0: - return 0 - if f >= 1.0: - return 255 - return int(255.0*f + 0.5) - - return "#" + "".join(["%02x" % float2int(c) for c in (r, g, b)]) - - def escape(self, s): - s = s.encode('utf-8') - s = s.replace('\\', r'\\') - s = s.replace('\n', r'\n') - s = s.replace('\t', r'\t') - s = s.replace('"', r'\"') - return '"' + s + '"' - - def write(self, s): - self.fp.write(s) - - -class Main: - """Main program.""" - - themes = { - "color": TEMPERATURE_COLORMAP, - "pink": PINK_COLORMAP, - "gray": GRAY_COLORMAP, - "bw": BW_COLORMAP, - } - - def main(self): - """Main program.""" - - parser = optparse.OptionParser( - usage="\n\t%prog [options] [file] ...", - version="%%prog %s" % __version__) - parser.add_option( - '-o', '--output', metavar='FILE', - type="string", dest="output", - help="output filename [stdout]") - parser.add_option( - '-n', '--node-thres', metavar='PERCENTAGE', - type="float", dest="node_thres", default=0.5, - help="eliminate nodes below this threshold [default: %default]") - parser.add_option( - '-e', '--edge-thres', metavar='PERCENTAGE', - type="float", dest="edge_thres", default=0.1, - help="eliminate edges below this threshold [default: %default]") - parser.add_option( - '-f', '--format', - type="choice", choices=('prof', 'oprofile', 'sysprof', 'pstats', 'shark', 'sleepy', 'aqtime'), - dest="format", default="prof", - help="profile format: prof, oprofile, sysprof, shark, sleepy, aqtime, or pstats [default: %default]") - parser.add_option( - '-c', '--colormap', - type="choice", choices=('color', 'pink', 'gray', 'bw'), - dest="theme", default="color", - help="color map: color, pink, gray, or bw [default: %default]") - parser.add_option( - '-s', '--strip', - action="store_true", - dest="strip", default=False, - help="strip function parameters, template parameters, and const modifiers from demangled C++ function names") - parser.add_option( - '-w', '--wrap', - action="store_true", - dest="wrap", default=False, - help="wrap function names") - # add a new option to control skew of the colorization curve - parser.add_option( - '--skew', - type="float", dest="theme_skew", default=1.0, - help="skew the colorization curve. Values < 1.0 give more variety to lower percentages. Value > 1.0 give less variety to lower percentages") - (self.options, self.args) = parser.parse_args(sys.argv[1:]) - - if len(self.args) > 1 and self.options.format != 'pstats': - parser.error('incorrect number of arguments') - - try: - self.theme = self.themes[self.options.theme] - except KeyError: - parser.error('invalid colormap \'%s\'' % self.options.theme) - - # set skew on the theme now that it has been picked. - if self.options.theme_skew: - self.theme.skew = self.options.theme_skew - - if self.options.format == 'prof': - if not self.args: - fp = sys.stdin - else: - fp = open(self.args[0], 'rt') - parser = GprofParser(fp) - elif self.options.format == 'oprofile': - if not self.args: - fp = sys.stdin - else: - fp = open(self.args[0], 'rt') - parser = OprofileParser(fp) - elif self.options.format == 'sysprof': - if not self.args: - fp = sys.stdin - else: - fp = open(self.args[0], 'rt') - parser = SysprofParser(fp) - elif self.options.format == 'pstats': - if not self.args: - parser.error('at least a file must be specified for pstats input') - parser = PstatsParser(*self.args) - elif self.options.format == 'shark': - if not self.args: - fp = sys.stdin - else: - fp = open(self.args[0], 'rt') - parser = SharkParser(fp) - elif self.options.format == 'sleepy': - if len(self.args) != 1: - parser.error('exactly one file must be specified for sleepy input') - parser = SleepyParser(self.args[0]) - elif self.options.format == 'aqtime': - if not self.args: - fp = sys.stdin - else: - fp = open(self.args[0], 'rt') - parser = AQtimeParser(fp) - else: - parser.error('invalid format \'%s\'' % self.options.format) - - self.profile = parser.parse() - - if self.options.output is None: - self.output = sys.stdout - else: - self.output = open(self.options.output, 'wt') - - self.write_graph() - - _parenthesis_re = re.compile(r'\([^()]*\)') - _angles_re = re.compile(r'<[^<>]*>') - _const_re = re.compile(r'\s+const$') - - def strip_function_name(self, name): - """Remove extraneous information from C++ demangled function names.""" - - # Strip function parameters from name by recursively removing paired parenthesis - while True: - name, n = self._parenthesis_re.subn('', name) - if not n: - break - - # Strip const qualifier - name = self._const_re.sub('', name) - - # Strip template parameters from name by recursively removing paired angles - while True: - name, n = self._angles_re.subn('', name) - if not n: - break - - return name - - def wrap_function_name(self, name): - """Split the function name on multiple lines.""" - - if len(name) > 32: - ratio = 2.0/3.0 - height = max(int(len(name)/(1.0 - ratio) + 0.5), 1) - width = max(len(name)/height, 32) - # TODO: break lines in symbols - name = textwrap.fill(name, width, break_long_words=False) - - # Take away spaces - name = name.replace(", ", ",") - name = name.replace("> >", ">>") - name = name.replace("> >", ">>") # catch consecutive - - return name - - def compress_function_name(self, name): - """Compress function name according to the user preferences.""" - - if self.options.strip: - name = self.strip_function_name(name) - - if self.options.wrap: - name = self.wrap_function_name(name) - - # TODO: merge functions with same resulting name - - return name - - def write_graph(self): - dot = DotWriter(self.output) - profile = self.profile - profile.prune(self.options.node_thres/100.0, self.options.edge_thres/100.0) - - for function in profile.functions.itervalues(): - function.name = self.compress_function_name(function.name) - - dot.graph(profile, self.theme) - - -if __name__ == '__main__': - Main().main() diff --git a/trunk/3rdparty/gprof/graphviz-2.36.0.zip b/trunk/3rdparty/gprof/graphviz-2.36.0.zip deleted file mode 100644 index d4a096043..000000000 Binary files a/trunk/3rdparty/gprof/graphviz-2.36.0.zip and /dev/null differ diff --git a/trunk/3rdparty/gprof/readme.txt b/trunk/3rdparty/gprof/readme.txt index 3e104a9a1..fea0fa778 100644 --- a/trunk/3rdparty/gprof/readme.txt +++ b/trunk/3rdparty/gprof/readme.txt @@ -1,36 +1,4 @@ -gprof图形化输出工具: gprof2dot.py graphviz-2.18.tar.gz build_gprof2dot.sh -dot: - http://www.graphviz.org/ - http://www.graphviz.org/Download_source.php - graphviz-2.18.tar.gz 绘图工具 - build_gprof2dot.sh 编译graphviz,命令为dot。 - 要求是sudoer,需要sudo make install。 - -gprof2dot.py: - 将gprof的日志绘图。 - -使用方法: -0. 若需要图形化,编译dot: - cd 3rdparty/gprof && bash build_gprof2dot.sh -1. srs配置时: - ./configure --with-gprof - 脚本会加入编译参数"-pg -lc_p",gcc -g -pg -lc_p -c xxx -o xxx.o,即在configure中打开 Performance="-pg -lc_p" - 链接时,加入链接选项"-pg",否则无法工作:gcc -pg -o srs xxxx.o,即在configure中打开 PerformanceLink="-pg" -2. 编译和启动程序:make && ./objs/srs -c conf/srs.conf - 退出程序,按CTRL+C,可以看到生成了gmon.out,这个就是性能的统计数据。 -3. gprof生成报表: - gprof -b ./objs/srs gmon.out > gprof.srs.log -4. 将报表生成图片: - ./3rdparty/gprof/gprof2dot.py gprof.srs.log | dot -Tpng -o ~/winlin.png +为了压缩仓库尺寸,gprof的文件不在新版本提供,可以切换到2.0分支安装: +https://github.com/ossrs/srs/tree/2.0release/trunk/3rdparty/gprof -缩写语句: - # 生成 ~/winlin.log ~/winlin.png - rm -f gmon.out; ./objs/srs -c conf/srs.conf - # 用户按CTRL+C - file="winlin";gprof -b ./objs/srs gmon.out > ~/${file}.log; ./3rdparty/gprof/gprof2dot.py ~/${file}.log | dot -Tpng -o ~/${file}.png - -备注: - 其实gprof生成的日志就可以看,不一定要图形化。 - 也就是dot和gprof2dot都不用执行。 - 参考:http://www.cs.utah.edu/dept/old/texinfo/as/gprof.html diff --git a/trunk/3rdparty/http-parser-2.1.zip b/trunk/3rdparty/http-parser-2.1.zip deleted file mode 100644 index e8452f83b..000000000 Binary files a/trunk/3rdparty/http-parser-2.1.zip and /dev/null differ diff --git a/trunk/3rdparty/lame-3.99.5.zip b/trunk/3rdparty/lame-3.99.5.zip deleted file mode 100644 index a2d625b6b..000000000 Binary files a/trunk/3rdparty/lame-3.99.5.zip and /dev/null differ diff --git a/trunk/3rdparty/nginx-1.5.7.zip b/trunk/3rdparty/nginx-1.5.7.zip deleted file mode 100644 index d331fa59e..000000000 Binary files a/trunk/3rdparty/nginx-1.5.7.zip and /dev/null differ diff --git a/trunk/3rdparty/openssl-1.0.1f.zip b/trunk/3rdparty/openssl-1.0.1f.zip deleted file mode 100644 index edcde9dcd..000000000 Binary files a/trunk/3rdparty/openssl-1.0.1f.zip and /dev/null differ diff --git a/trunk/3rdparty/openssl-1.1.0e.zip b/trunk/3rdparty/openssl-1.1.0e.zip new file mode 100644 index 000000000..1361d91cc Binary files /dev/null and b/trunk/3rdparty/openssl-1.1.0e.zip differ diff --git a/trunk/3rdparty/patches/1.st.arm.patch b/trunk/3rdparty/patches/1.st.arm.patch deleted file mode 100644 index 0e949631e..000000000 --- a/trunk/3rdparty/patches/1.st.arm.patch +++ /dev/null @@ -1,19 +0,0 @@ -*** md.h Fri Oct 2 02:46:43 2009 ---- ../st-1.9-patch/md.h Mon Mar 16 15:11:49 2015 -*************** -*** 422,428 **** - #define MD_STACK_GROWS_DOWN - - #if defined(__GLIBC__) && __GLIBC__ >= 2 -! #define MD_GET_SP(_t) (_t)->context[0].__jmpbuf[20] - #else - #error "ARM/Linux pre-glibc2 not supported yet" - #endif /* defined(__GLIBC__) && __GLIBC__ >= 2 */ ---- 422,428 ---- - #define MD_STACK_GROWS_DOWN - - #if defined(__GLIBC__) && __GLIBC__ >= 2 -! #define MD_GET_SP(_t) (_t)->context[0].__jmpbuf[8] - #else - #error "ARM/Linux pre-glibc2 not supported yet" - #endif /* defined(__GLIBC__) && __GLIBC__ >= 2 */ diff --git a/trunk/3rdparty/patches/2.http.parser.patch b/trunk/3rdparty/patches/2.http.parser.patch deleted file mode 100644 index 108880944..000000000 --- a/trunk/3rdparty/patches/2.http.parser.patch +++ /dev/null @@ -1,16 +0,0 @@ -*** Makefile Wed Mar 27 06:35:20 2013 ---- ../http-parser-2.1-patch/Makefile Mon Mar 16 15:25:59 2015 -*************** -*** 12,17 **** ---- 12,22 ---- - CFLAGS_FAST = $(CFLAGS) -O3 $(CFLAGS_FAST_EXTRA) - CFLAGS_LIB = $(CFLAGS_FAST) -fPIC - -+ # patch by winlin -+ CPPFLAGS_FAST = $(CPPFLAGS_DEBUG) -+ CFLAGS_FAST = $(CFLAGS_DEBUG) -+ CFLAGS_LIB = $(CFLAGS_FAST) -fPIC -+ - test: test_g test_fast - ./test_g - ./test_fast diff --git a/trunk/3rdparty/patches/3.st.osx.kqueue.patch b/trunk/3rdparty/patches/3.st.osx.kqueue.patch deleted file mode 100644 index d3e016b10..000000000 --- a/trunk/3rdparty/patches/3.st.osx.kqueue.patch +++ /dev/null @@ -1,19 +0,0 @@ -*** io.c Fri Oct 2 06:49:07 2009 ---- ../st-1.9-patch/io.c Mon Mar 16 15:06:28 2015 -*************** -*** 89,94 **** ---- 89,102 ---- - if (fdlim > 0 && rlim.rlim_max > (rlim_t) fdlim) { - rlim.rlim_max = fdlim; - } -+ -+ /* when rlimit max is negative, for example, osx, use cur directly. */ -+ /* @see https://github.com/winlinvip/simple-rtmp-server/issues/336 */ -+ if ((int)rlim.rlim_max < 0) { -+ _st_osfd_limit = (int)(fdlim > 0? fdlim : rlim.rlim_cur); -+ return 0; -+ } -+ - rlim.rlim_cur = rlim.rlim_max; - if (setrlimit(RLIMIT_NOFILE, &rlim) < 0) - return -1; diff --git a/trunk/3rdparty/patches/4.st.disable.examples.patch b/trunk/3rdparty/patches/4.st.disable.examples.patch deleted file mode 100644 index c775f30df..000000000 --- a/trunk/3rdparty/patches/4.st.disable.examples.patch +++ /dev/null @@ -1,15 +0,0 @@ -*** Makefile Fri Oct 2 06:55:03 2009 ---- ../st-1.9-patch/Makefile Mon Mar 16 15:30:35 2015 -*************** -*** 310,315 **** ---- 310,319 ---- - EXAMPLES = - endif - -+ # disable examples for ubuntu crossbuild failed. -+ # @see https://github.com/winlinvip/simple-rtmp-server/issues/308 -+ EXAMPLES = -+ - ifeq ($(OS), DARWIN) - LINKNAME = libst.$(DSO_SUFFIX) - SONAME = libst.$(MAJOR).$(DSO_SUFFIX) diff --git a/trunk/3rdparty/patches/5.x264.osx.gcc.patch b/trunk/3rdparty/patches/5.x264.osx.gcc.patch deleted file mode 100644 index 2b6411a41..000000000 --- a/trunk/3rdparty/patches/5.x264.osx.gcc.patch +++ /dev/null @@ -1,19 +0,0 @@ -*** configure Sat Nov 30 05:45:08 2013 ---- ../x264-snapshot-20131129-2245-stable-patch/configure Tue Mar 17 21:42:24 2015 -*************** -*** 466,472 **** - ;; - darwin*) - SYS="MACOSX" -! CFLAGS="$CFLAGS -falign-loops=16" - libm="-lm" - if [ "$pic" = "no" ]; then - cc_check "" -mdynamic-no-pic && CFLAGS="$CFLAGS -mdynamic-no-pic" ---- 466,472 ---- - ;; - darwin*) - SYS="MACOSX" -! CFLAGS="$CFLAGS" - libm="-lm" - if [ "$pic" = "no" ]; then - cc_check "" -mdynamic-no-pic && CFLAGS="$CFLAGS -mdynamic-no-pic" diff --git a/trunk/3rdparty/patches/6.st.osx10.14.build.patch b/trunk/3rdparty/patches/6.st.osx10.14.build.patch deleted file mode 100644 index a6e376171..000000000 --- a/trunk/3rdparty/patches/6.st.osx10.14.build.patch +++ /dev/null @@ -1,13 +0,0 @@ ---- Makefile 2018-10-28 13:49:27.000000000 +0800 -+++ ../st-1.9-patch/Makefile 2018-10-28 13:50:07.000000000 +0800 -@@ -139,8 +139,8 @@ CFLAGS += -arch ppc - LDFLAGS += -arch ppc - endif - ifeq ($(INTEL), yes) --CFLAGS += -arch i386 -arch x86_64 --LDFLAGS += -arch i386 -arch x86_64 -+CFLAGS += -arch x86_64 -+LDFLAGS += -arch x86_64 - endif - LDFLAGS += -dynamiclib -install_name /sw/lib/libst.$(MAJOR).$(DSO_SUFFIX) -compatibility_version $(MAJOR) -current_version $(VERSION) - OTHER_FLAGS = -Wall diff --git a/trunk/3rdparty/patches/7.http.parser.patch b/trunk/3rdparty/patches/7.http.parser.patch deleted file mode 100644 index 89655993c..000000000 --- a/trunk/3rdparty/patches/7.http.parser.patch +++ /dev/null @@ -1,12 +0,0 @@ ---- Makefile 2019-04-05 19:43:32.000000000 +0800 -+++ ../http-parser-2.1-patch/Makefile 2019-04-05 19:50:26.000000000 +0800 -@@ -7,7 +7,8 @@ CPPFLAGS_DEBUG += $(CPPFLAGS_DEBUG_EXTRA - CPPFLAGS_FAST = $(CPPFLAGS) -DHTTP_PARSER_STRICT=0 - CPPFLAGS_FAST += $(CPPFLAGS_FAST_EXTRA) - --CFLAGS += -Wall -Wextra -Werror -+# patch by winlin -+CFLAGS += -Wall -Wextra - CFLAGS_DEBUG = $(CFLAGS) -O0 -g $(CFLAGS_DEBUG_EXTRA) - CFLAGS_FAST = $(CFLAGS) -O3 $(CFLAGS_FAST_EXTRA) - CFLAGS_LIB = $(CFLAGS_FAST) -fPIC diff --git a/trunk/3rdparty/readme.txt b/trunk/3rdparty/readme.txt index 094504d8c..ca54471f2 100644 --- a/trunk/3rdparty/readme.txt +++ b/trunk/3rdparty/readme.txt @@ -7,13 +7,13 @@ nginx-1.5.7.zip st-1.9.zip basic framework for srs. -openssl-1.0.1f.zip +openssl-1.1.0e.zip openssl for SRS(with-ssl) RTMP complex handshake to delivery h264+aac stream. CherryPy-3.2.4.zip sample api server for srs. -ffmpeg-2.1.1.tar.gz +ffmpeg-3.2.4.tar.gz yasm-1.2.0.tar.gz lame-3.99.5.tar.gz speex-1.2rc1.zip @@ -27,15 +27,14 @@ fdk-aac-0.1.3.zip tools/ccache-3.1.9.zip to fast build. -1.st.arm.Makefile.patch - stűûָcc - gtest-1.6.0.zip - googleԪԿܡ + google test framework. gperftools-2.1.zip - googleܷͲԹߡ - ʹòοѹļеREADMEdocĿ¼ + gperf tools for performance benchmark. + +state-threads-1.9.1.tar.gz: + patched st from https://github.com/ossrs/state-threads/releases/tag/v1.9.1 links: nginx: @@ -46,7 +45,7 @@ links: http://sourceforge.net/projects/state-threads ffmpeg: http://ffmpeg.org/ - http://ffmpeg.org/releases/ffmpeg-2.1.1.tar.gz + http://ffmpeg.org/releases/ffmpeg-3.2.4.tar.gz x264: http://www.videolan.org/ ftp://ftp.videolan.org/pub/videolan/x264/snapshots/x264-snapshot-20131129-2245-stable.tar.bz2 @@ -61,7 +60,7 @@ links: https://pypi.python.org/pypi/CherryPy/3.2.4 openssl: http://www.openssl.org/ - http://www.openssl.org/source/openssl-1.0.1f.tar.gz + http://www.openssl.org/source/openssl-1.1.0e.tar.gz gtest: https://code.google.com/p/googletest https://code.google.com/p/googletest/downloads/list diff --git a/trunk/3rdparty/speex-1.2rc1.zip b/trunk/3rdparty/speex-1.2rc1.zip deleted file mode 100644 index 4743180cc..000000000 Binary files a/trunk/3rdparty/speex-1.2rc1.zip and /dev/null differ diff --git a/trunk/3rdparty/st-1.9.zip b/trunk/3rdparty/st-1.9.zip deleted file mode 100644 index d757be00f..000000000 Binary files a/trunk/3rdparty/st-1.9.zip and /dev/null differ diff --git a/trunk/3rdparty/st-srs/.gitignore b/trunk/3rdparty/st-srs/.gitignore new file mode 100644 index 000000000..97cd5081d --- /dev/null +++ b/trunk/3rdparty/st-srs/.gitignore @@ -0,0 +1,4 @@ +DARWIN_*_DBG +LINUX_*_DBG +obj +st.pc diff --git a/trunk/3rdparty/st-srs/Makefile b/trunk/3rdparty/st-srs/Makefile new file mode 100644 index 000000000..601d3e507 --- /dev/null +++ b/trunk/3rdparty/st-srs/Makefile @@ -0,0 +1,480 @@ +# The contents of this file are subject to the Mozilla Public +# License Version 1.1 (the "License"); you may not use this file +# except in compliance with the License. You may obtain a copy of +# the License at http://www.mozilla.org/MPL/ +# +# Software distributed under the License is distributed on an "AS +# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or +# implied. See the License for the specific language governing +# rights and limitations under the License. +# +# The Original Code is the Netscape Portable Runtime library. +# +# The Initial Developer of the Original Code is Netscape +# Communications Corporation. Portions created by Netscape are +# Copyright (C) 1994-2000 Netscape Communications Corporation. All +# Rights Reserved. +# +# Contributor(s): Silicon Graphics, Inc. +# +# Portions created by SGI are Copyright (C) 2000-2001 Silicon +# Graphics, Inc. All Rights Reserved. +# +# Alternatively, the contents of this file may be used under the +# terms of the GNU General Public License Version 2 or later (the +# "GPL"), in which case the provisions of the GPL are applicable +# instead of those above. If you wish to allow use of your +# version of this file only under the terms of the GPL and not to +# allow others to use your version of this file under the MPL, +# indicate your decision by deleting the provisions above and +# replace them with the notice and other provisions required by +# the GPL. If you do not delete the provisions above, a recipient +# may use your version of this file under either the MPL or the +# GPL. + +# This is the full version of the libst library - modify carefully +VERSION = 1.9 + +########################## +# Supported OSes: +# +#OS = AIX +#OS = CYGWIN +#OS = DARWIN +#OS = FREEBSD +#OS = HPUX +#OS = HPUX_64 +#OS = IRIX +#OS = IRIX_64 +#OS = LINUX +#OS = NETBSD +#OS = OPENBSD +#OS = OSF1 +#OS = SOLARIS +#OS = SOLARIS_64 + +# Please see the "Other possible defines" section below for +# possible compilation options. +########################## + +CC = cc +AR = ar +LD = ld +RANLIB = ranlib +LN = ln + +SHELL = /bin/sh +ECHO = /bin/echo + +BUILD = DBG +TARGETDIR = $(OS)_$(shell uname -r)_$(BUILD) + +DEFINES = -D$(OS) +CFLAGS = +SFLAGS = +ARFLAGS = -rv +LNFLAGS = -s +DSO_SUFFIX = so + +MAJOR = $(shell echo $(VERSION) | sed 's/^\([^\.]*\).*/\1/') +DESC = st.pc + +########################## +# Platform section. +# Possible targets: + +TARGETS = aix-debug aix-optimized \ + cygwin-debug cygwin-optimized \ + darwin-debug darwin-optimized \ + freebsd-debug freebsd-optimized \ + hpux-debug hpux-optimized \ + hpux-64-debug hpux-64-optimized \ + irix-n32-debug irix-n32-optimized \ + irix-64-debug irix-64-optimized \ + linux-debug linux-optimized \ + netbsd-debug netbsd-optimized \ + openbsd-debug openbsd-optimized \ + osf1-debug osf1-optimized \ + solaris-debug solaris-optimized \ + solaris-64-debug solaris-64-optimized + +# +# Platform specifics +# + +ifeq ($(OS), AIX) +AIX_VERSION = $(shell uname -v).$(shell uname -r) +TARGETDIR = $(OS)_$(AIX_VERSION)_$(BUILD) +CC = xlC +STATIC_ONLY = yes +ifeq ($(BUILD), OPT) +OTHER_FLAGS = -w +endif +ifneq ($(filter-out 4.1 4.2, $(AIX_VERSION)),) +DEFINES += -DMD_HAVE_SOCKLEN_T +endif +endif + +ifeq ($(OS), CYGWIN) +TARGETDIR = $(OS)_$(BUILD) +CC = gcc +LD = gcc +DSO_SUFFIX = dll +SLIBRARY = $(TARGETDIR)/libst.dll.a +DLIBRARY = $(TARGETDIR)/libst.dll +DEF_FILE = $(TARGETDIR)/libst.def +LDFLAGS = libst.def -shared --enable-auto-image-base -Wl,--output-def,$(DEF_FILE),--out-implib,$(SLIBRARY) +OTHER_FLAGS = -Wall +endif + +ifeq ($(OS), DARWIN) +EXTRA_OBJS = $(TARGETDIR)/md_darwin.o +LD = cc +SFLAGS = -fPIC -fno-common +DSO_SUFFIX = dylib +RELEASE = $(shell uname -r | cut -d. -f1) +PPC = $(shell test $(RELEASE) -le 9 && echo yes) +INTEL = $(shell test $(RELEASE) -ge 9 && echo yes) +ifeq ($(PPC), yes) +CFLAGS += -arch ppc +LDFLAGS += -arch ppc +endif +ifeq ($(INTEL), yes) +CFLAGS += -arch x86_64 +LDFLAGS += -arch x86_64 +endif +LDFLAGS += -dynamiclib -install_name /sw/lib/libst.$(MAJOR).$(DSO_SUFFIX) -compatibility_version $(MAJOR) -current_version $(VERSION) +OTHER_FLAGS = -Wall +endif + +ifeq ($(OS), FREEBSD) +SFLAGS = -fPIC +LDFLAGS = -shared -soname=$(SONAME) -lc +OTHER_FLAGS = -Wall +ifeq ($(shell test -f /usr/include/sys/event.h && echo yes), yes) +DEFINES += -DMD_HAVE_KQUEUE +endif +endif + +ifeq (HPUX, $(findstring HPUX, $(OS))) +ifeq ($(OS), HPUX_64) +DEFINES = -DHPUX +CFLAGS = -Ae +DD64 +Z +else +CFLAGS = -Ae +DAportable +Z +endif +RANLIB = true +LDFLAGS = -b +DSO_SUFFIX = sl +endif + +ifeq (IRIX, $(findstring IRIX, $(OS))) +ifeq ($(OS), IRIX_64) +DEFINES = -DIRIX +ABIFLAG = -64 +else +ABIFLAG = -n32 +endif +RANLIB = true +CFLAGS = $(ABIFLAG) -mips3 +LDFLAGS = $(ABIFLAG) -shared +OTHER_FLAGS = -fullwarn +endif + +ifeq ($(OS), LINUX) +EXTRA_OBJS = $(TARGETDIR)/md.o +SFLAGS = -fPIC +LDFLAGS = -shared -soname=$(SONAME) -lc +OTHER_FLAGS = -Wall +ifeq ($(shell test -f /usr/include/sys/epoll.h && echo yes), yes) +DEFINES += -DMD_HAVE_EPOLL +endif +endif + +ifeq ($(OS), NETBSD) +SFLAGS = -fPIC +LDFLAGS = -shared -soname=$(SONAME) -lc +OTHER_FLAGS = -Wall +endif + +ifeq ($(OS), OPENBSD) +SFLAGS = -fPIC +LDFLAGS = -shared -soname=$(SONAME) -lc +OTHER_FLAGS = -Wall +ifeq ($(shell test -f /usr/include/sys/event.h && echo yes), yes) +DEFINES += -DMD_HAVE_KQUEUE +endif +endif + +ifeq ($(OS), OSF1) +RANLIB = true +LDFLAGS = -shared -all -expect_unresolved "*" +endif + +ifeq (SOLARIS, $(findstring SOLARIS, $(OS))) +TARGETDIR = $(OS)_$(shell uname -r | sed 's/^5/2/')_$(BUILD) +CC = gcc +LD = gcc +RANLIB = true +LDFLAGS = -G +OTHER_FLAGS = -Wall +ifeq ($(OS), SOLARIS_64) +DEFINES = -DSOLARIS +CFLAGS += -m64 +LDFLAGS += -m64 +endif +endif + +# +# End of platform section. +########################## + + +ifeq ($(BUILD), OPT) +OTHER_FLAGS += -O +else +OTHER_FLAGS += -g +DEFINES += -DDEBUG +endif + +########################## +# Other possible defines: +# To use poll(2) instead of select(2) for events checking: +# DEFINES += -DUSE_POLL +# You may prefer to use select for applications that have many threads +# using one file descriptor, and poll for applications that have many +# different file descriptors. With USE_POLL poll() is called with at +# least one pollfd per I/O-blocked thread, so 1000 threads sharing one +# descriptor will poll 1000 identical pollfds and select would be more +# efficient. But if the threads all use different descriptors poll() +# may be better depending on your operating system's implementation of +# poll and select. Really, it's up to you. Oh, and on some platforms +# poll() fails with more than a few dozen descriptors. +# +# Some platforms allow to define FD_SETSIZE (if select() is used), e.g.: +# DEFINES += -DFD_SETSIZE=4096 +# +# To use malloc(3) instead of mmap(2) for stack allocation: +# DEFINES += -DMALLOC_STACK +# +# To provision more than the default 16 thread-specific-data keys +# (but not too many!): +# DEFINES += -DST_KEYS_MAX= +# +# To start with more than the default 64 initial pollfd slots +# (but the table grows dynamically anyway): +# DEFINES += -DST_MIN_POLLFDS_SIZE= +# +# Note that you can also add these defines by specifying them as +# make/gmake arguments (without editing this Makefile). For example: +# +# make EXTRA_CFLAGS=-DUSE_POLL +# +# (replace make with gmake if needed). +# +# You can also modify the default selection of an alternative event +# notification mechanism. E.g., to enable kqueue(2) support (if it's not +# enabled by default): +# +# gmake EXTRA_CFLAGS=-DMD_HAVE_KQUEUE +# +# or to disable default epoll(4) support: +# +# make EXTRA_CFLAGS=-UMD_HAVE_EPOLL +# +########################## + +CFLAGS += $(DEFINES) $(OTHER_FLAGS) $(EXTRA_CFLAGS) + +OBJS = $(TARGETDIR)/sched.o \ + $(TARGETDIR)/stk.o \ + $(TARGETDIR)/sync.o \ + $(TARGETDIR)/key.o \ + $(TARGETDIR)/io.o \ + $(TARGETDIR)/event.o +OBJS += $(EXTRA_OBJS) +HEADER = $(TARGETDIR)/st.h +SLIBRARY = $(TARGETDIR)/libst.a +DLIBRARY = $(TARGETDIR)/libst.$(DSO_SUFFIX).$(VERSION) +EXAMPLES = examples + +LINKNAME = libst.$(DSO_SUFFIX) +SONAME = libst.$(DSO_SUFFIX).$(MAJOR) +FULLNAME = libst.$(DSO_SUFFIX).$(VERSION) + +ifeq ($(OS), CYGWIN) +SONAME = cygst.$(DSO_SUFFIX) +SLIBRARY = $(TARGETDIR)/libst.dll.a +DLIBRARY = $(TARGETDIR)/$(SONAME) +LINKNAME = +# examples directory does not compile under cygwin +EXAMPLES = +endif + +# for SRS +# disable examples for ubuntu crossbuild failed. +# @see https://github.com/winlinvip/simple-rtmp-server/issues/308 +ifeq ($(OS), LINUX) +EXAMPLES = +endif + +ifeq ($(OS), DARWIN) +LINKNAME = libst.$(DSO_SUFFIX) +SONAME = libst.$(MAJOR).$(DSO_SUFFIX) +FULLNAME = libst.$(VERSION).$(DSO_SUFFIX) +endif + +ifeq ($(STATIC_ONLY), yes) +LIBRARIES = $(SLIBRARY) +else +LIBRARIES = $(SLIBRARY) $(DLIBRARY) +endif + +ifeq ($(OS),) +ST_ALL = unknown +else +ST_ALL = $(TARGETDIR) $(LIBRARIES) $(HEADER) $(EXAMPLES) $(DESC) +endif + +all: $(ST_ALL) + +unknown: + @echo + @echo "Please specify one of the following targets:" + @echo + @for target in $(TARGETS); do echo $$target; done + @echo + +st.pc: st.pc.in + sed "s/@VERSION@/${VERSION}/g" < $< > $@ + +$(TARGETDIR): + if [ ! -d $(TARGETDIR) ]; then mkdir $(TARGETDIR); fi + +$(SLIBRARY): $(OBJS) + $(AR) $(ARFLAGS) $@ $(OBJS) + $(RANLIB) $@ + rm -f obj; $(LN) $(LNFLAGS) $(TARGETDIR) obj + +$(DLIBRARY): $(OBJS:%.o=%-pic.o) + $(LD) $(LDFLAGS) $^ -o $@ + if test "$(LINKNAME)"; then \ + cd $(TARGETDIR); \ + rm -f $(SONAME) $(LINKNAME); \ + $(LN) $(LNFLAGS) $(FULLNAME) $(SONAME); \ + $(LN) $(LNFLAGS) $(FULLNAME) $(LINKNAME); \ + fi + +$(HEADER): public.h + rm -f $@ + cp public.h $@ + +$(TARGETDIR)/md.o: md.S + $(CC) $(CFLAGS) -c $< -o $@ + +$(TARGETDIR)/md_darwin.o: md_darwin.S + $(CC) $(CFLAGS) -c $< -o $@ + +$(TARGETDIR)/%.o: %.c common.h md.h + $(CC) $(CFLAGS) -c $< -o $@ + +examples: $(SLIBRARY) + @echo Making $@ + @cd $@; $(MAKE) CC="$(CC)" CFLAGS="$(CFLAGS)" OS="$(OS)" TARGETDIR="$(TARGETDIR)" + +clean: + rm -rf *_OPT *_DBG obj st.pc + +########################## +# Pattern rules: + +ifneq ($(SFLAGS),) +# Compile with shared library options if it's a C file +$(TARGETDIR)/%-pic.o: %.c common.h md.h + $(CC) $(CFLAGS) $(SFLAGS) -c $< -o $@ +endif + +# Compile assembly as normal or C as normal if no SFLAGS +%-pic.o: %.o + rm -f $@; $(LN) $(LNFLAGS) $(. Install them with: + # rpm -i libst*.rpm +Requires GNU automake and rpm 3.0.3 or later. + +Debian users: + If you run potato, please upgrade to woody. + If you run woody, "apt-get install libst-dev" will get you v1.3. + If you run testing/unstable, you will get the newest available version. + If you *must* have the newest libst in woody, you may follow these + not-recommended instructions: + 1. Add "deb-src unstable main" to your + /etc/apt/sources.list + 2. apt-get update + 3. apt-get source st + 4. cd st-1.4 (or whatever version you got) + 5. debuild + 6. dpkg -i ../*.deb + +If your application uses autoconf to search for dependencies and you +want to search for a given version of libst, you can simply add + PKG_CHECK_MODULES(MYAPP, st >= 1.3 mumble >= 0.2.23) +to your configure.ac/in. This will define @MYAPP_LIBS@ and +@MYAPP_CFLAGS@ which you may then use in your Makefile.am/in files to +link against mumble and st. + + +LICENSE + +The State Threads library is a derivative of the Netscape Portable +Runtime library (NSPR). All source code in this directory is +distributed under the terms of the Mozilla Public License (MPL) version +1.1 or the GNU General Public License (GPL) version 2 or later. For +more information about these licenses please see +http://www.mozilla.org/MPL/ and http://www.gnu.org/copyleft/. + +All source code in the "examples" directory is distributed under the BSD +style license. + + +PLATFORMS + +Please see the "docs/notes.html" file for the list of currently +supported platforms. + + +DEBUGGER SUPPORT + +It's almost impossible to print SP and PC in a portable way. The only +way to see thread's stack platform-independently is to actually jump to +the saved context. That's what the _st_iterate_threads() function does. +Do the following to iterate over all threads: + +- set the _st_iterate_threads_flag to 1 in debugger +- set breakpoint at the _st_show_thread_stack() function + (which does nothing) +- call the _st_iterate_threads() function which jumps to the + next thread +- at each break you can explore thread's stack +- continue +- when iteration is complete, you return to the original + point (you can see thread id and a message as arguments of + the _st_show_thread_stack() function). + +You can call _st_iterate_threads() in three ways: + +- Insert it into your source code at the point you want to + go over threads. +- Just run application and this function will be called at + the first context switch. +- Call it directly from the debugger at any point. + +This works with gdb and dbx. + +Example using gdb: + +(gdb) set _st_iterate_threads_flag = 1 +(gdb) b _st_show_thread_stack +... +(gdb) call _st_iterate_threads() +... +(gdb) bt +... +(gdb) c +... +(gdb) bt +... +(gdb) c +... +and so on... + +_st_iterate_threads_flag will be set to 0 automatically +after iteration is over or you can set it to 0 at any time +to stop iteration. + +Sometimes gdb complains about SIGSEGV when you call a function +directly at gdb command-line. It can be ignored -- just call the +same function right away again, it works just fine. For example: + +(gdb) set _st_iterate_threads_flag = 1 +(gdb) b _st_show_thread_stack +Breakpoint 1 at 0x809bbbb: file sched.c, line 856. +(gdb) call _st_iterate_threads() +Program received signal SIGSEGV, Segmentation fault. +.... +(gdb) # just call the function again: +(gdb) call _st_iterate_threads() +Breakpoint 1, _st_show_thread_stack (thread=0x4017aee4, messg=0x80ae7a2 +"Iteration started") at sched.c:856 +856 } +.... + +You can use simple gdb command-line scripting to display +all threads and their stack traces at once: + +(gdb) while _st_iterate_threads_flag + >bt + >c + >end +.... + +Another script to stop at the thread with the specific thread id +(e.g., 0x40252ee4): + +(gdb) # set the flag again: +(gdb) set _st_iterate_threads_flag = 1 +(gdb) call _st_iterate_threads() +Breakpoint 1, _st_show_thread_stack (thread=0x4017aee4, messg=0x80ae7a2 +"Iteration started") at sched.c:856 +856 } +.... +(gdb) while thread != 0x40252ee4 + >c + >end +.... +.... +Breakpoint 1, _st_show_thread_stack (thread=0x40252ee4, messg=0x0) at +sched.c:856 +856 } +(gdb) bt +.... +(gdb) # don't want to continue iteration, unset the flag: +(gdb) set _st_iterate_threads_flag = 0 +(gdb) c +Continuing. +Breakpoint 1, _st_show_thread_stack (thread=0x0, messg=0x80ae78e "Iteration +completed") + at sched.c:856 +856 } +(gdb) c +Continuing. +(gdb) return +Make selected stack frame return now? (y or n) y +#0 0x4011254e in __select () + from /lib/libc.so.6 +(gdb) detach + + +CHANGE LOG + +Changes from 1.8 to 1.9. +------------------------ +o Support 32-bit and 64-bit Intel Macs. + +o Added ST_VERSION string, and ST_VERSION_MAJOR and ST_VERSION_MINOR + [bug 1796801]. + +o Fixed some compiler warnings, based on a patch from Brian Wellington + [bug 1932741]. + + +Changes from 1.7 to 1.8. +-------------------------- +o Added support for kqueue and epoll on platforms that support them. + Added ability to choose the event notification system at program + startup. + +o Long-overdue public definitions of ST_UTIME_NO_TIMEOUT (-1ULL) and + ST_UTIME_NO_WAIT (0) [bug 1514436]. + +o Documentation patch for st_utime() [bug 1514484]. + +o Documentation patch for st_timecache_set() [bug 1514486]. + +o Documentation patch for st_netfd_serialize_accept() [bug 1514494]. + +o Added st_writev_resid() [rfe 1538344]. + +o Added st_readv_resid() [rfe 1538768] and, for symmetry, st_readv(). + + +Changes from 1.6 to 1.7. +------------------------ +o Support glibc 2.4, which breaks programs that manipulate jump buffers. + Replaced Linux IA64 special cases with new md.S that covers all + Linux. + + +Changes from 1.5.2 to 1.6. +-------------------------- +none + + +Changes from 1.5.1 to 1.5.2. +---------------------------- +o Alfred Perlstein's context switch callback feature. + +o Claus Assmann's st_recvmsg/st_sendmsg wrappers. + +o Extra stack padding for platforms that need it. + +o Ron Arts's timeout clarifications in the reference manual. + +o Raymond Bero and Anton Berezin's AMD64 FreeBSD port. + +o Claus Assmann's AMD64 SunOS 5.10 port. + +o Claus Assmann's AMD64 OpenBSD port. + +o Michael Abd-El-Malek's Mac OS X port. + +o Michael Abd-El-Malek's stack printing patch. + + +Changes from 1.5.0 to 1.5.1. +---------------------------- +o Andreas Gustafsson's USE_POLL fix. + +o Gene's st_set_utime_function() enhancement. + + +Changes from 1.4 to 1.5.0. +-------------------------- +o Andreas Gustafsson's performance patch. + +o New extensions: Improved DNS resolver, generic LRU cache, in-process + DNS cache, and a program to test the resolver and cache. + +o Support for AMD Opteron 64-bit CPUs under Linux. + +o Support for SPARC-64 under Solaris. + +o Andreas Gustafsson's support for VAX under NetBSD. + +o Changed unportable #warning directives in md.h to #error. + + +Changes from 1.3 to 1.4. +------------------------ +o Andreas Gustafsson's NetBSD port. + +o Wesley W. Terpstra's Darwin (MacOS X) port. + +o Support for many CPU architectures under Linux and *BSD. + +o Renamed private typedefs so they don't conflict with public ones any + more. + +o common.h now includes public.h for strict prototyping. + +o Joshua Levy's recommendation to make st_connect() and st_sendto() + accept const struct sockaddr pointers, as the originals do. + +o Clarified the documentation regarding blocking vs. non-blocking I/O. + +o Cygwin support. + +o Created the extensions directory. + +o Fixed warnings from ia64asm.S. + + +Changes from 1.2 to 1.3. +------------------------ +o Added st_read_resid() and st_write_resid() to allow the caller to know + how much data was transferred before an error occurred. Updated + documentation. + +o Updated project link, copyrights, and documentation regarding + timeouts. Added comment to st_connect(). + +o Optimized the _st_add_sleep_q() function in sched.c. Now we walk the + sleep queue *backward* when inserting a thread into it. When you + have lots (hundreds) of threads and several timeout values, it takes + a while to insert a thread at the appropriate point in the sleep + queue. The idea is that often this appropriate point is closer to + the end of the queue rather than the beginning. Measurements show + performance improves with this change. In any case this change + should do no harm. + +o Added a hint of when to define USE_POLL and when not to, to the + Makefile. + +o Added debugging support (files common.h and sched.c). See above. + +o Decreased the number of reallocations of _ST_POLLFDS in sched.c. + Inspired by Lev Walkin. + +o Fixed st_usleep(-1) and st_sleep(-1), and added a warning to the + documentation about too-large timeouts. + +o Linux/*BSD Alpha port. + +o Wesley W. Terpstra modernized the build process: + - properly build relocatable libraries under bsd and linux + - use library versioning + - added rpm spec file + - added debian/ files + See above for build instructions. + + +Changes from 1.1 to 1.2. +------------------------ +o Added st_randomize_stacks(). + +o Added a patch contributed by Sascha Schumann. + + +Changes from 1.0 to 1.1. +------------------------ +o Relicensed under dual MPL-GPL. + +o OpenBSD port. + +o Compile-time option to use poll() instead of select() for + event polling (see Makefile). + This is useful if you want to support a large number of open + file descriptors (larger than FD_SETSIZE) within a single + process. + +o Linux IA-64 port. + Two issues make IA-64 different from other platforms: + + - Besides the traditional call stack in memory, IA-64 uses the + general register stack. Thus each thread needs a backing store + for the register stack in addition to the memory stack. + + - Current implementation of setjmp()/longjmp() can not be used + for thread context-switching since it assumes that only one + register stack exists. Using special assembly functions for + context-switching is unavoidable. + +o Thread stack capping on IRIX. + This allows some profiling tools (such as SpeedShop) to know when + to stop unwinding the stack. Without this libexc, used by SpeedShop, + traces right off the stack and crashes. + +o Miscellaneous documentation additions. + + +COPYRIGHTS + +Portions created by SGI are Copyright (C) 2000 Silicon Graphics, Inc. +All Rights Reserved. diff --git a/trunk/3rdparty/st-srs/README.md b/trunk/3rdparty/st-srs/README.md new file mode 100644 index 000000000..60fc1a651 --- /dev/null +++ b/trunk/3rdparty/st-srs/README.md @@ -0,0 +1,88 @@ +# state-threads + +![](http://ossrs.net:8000/gif/v1/sls.gif?site=github.com&path=/srs/srsst) +[![](https://cloud.githubusercontent.com/assets/2777660/22814959/c51cbe72-ef92-11e6-81cc-32b657b285d5.png)](https://github.com/ossrs/srs/wiki/v1_CN_Contact#wechat) + +Fork from http://sourceforge.net/projects/state-threads, patched for [SRS](https://github.com/ossrs/srs/tree/2.0release). + +> See: https://github.com/ossrs/state-threads/blob/srs/README + +For original ST without any changes, checkout the [ST master branch](https://github.com/ossrs/state-threads/tree/master). + +## Branch SRS + +The branch [srs](https://github.com/ossrs/state-threads/tree/srs) will be patched the following patches: + +- [x] Patch [st.arm.patch](https://github.com/ossrs/srs/blob/2.0release/trunk/3rdparty/patches/1.st.arm.patch), for ARM. +- [x] Patch [st.osx.kqueue.patch](https://github.com/ossrs/srs/blob/2.0release/trunk/3rdparty/patches/3.st.osx.kqueue.patch), for osx. +- [x] Patch [st.disable.examples.patch](https://github.com/ossrs/srs/blob/2.0release/trunk/3rdparty/patches/4.st.disable.examples.patch), for ubuntu. +- [x] [Refine TAB of code](https://github.com/ossrs/state-threads/compare/c2001d30ca58f55d72a6cc6b9b6c70391eaf14db...d2101b26988b0e0db0aabc53ddf452068c1e2cbc). +- [x] Merge from [michaeltalyansky](https://github.com/michaeltalyansky/state-threads) and [xzh3836598](https://github.com/ossrs/state-threads/commit/9a17dec8f9c2814d93761665df7c5575a4d2d8a3), support [ARM](https://github.com/ossrs/state-threads/issues/1). +- [x] Merge from [toffaletti](https://github.com/toffaletti/state-threads), support [valgrind](https://github.com/ossrs/state-threads/issues/2) for ST. +- [x] Patch [st.osx10.14.build.patch](https://github.com/ossrs/srs/blob/2.0release/trunk/3rdparty/patches/6.st.osx10.14.build.patch), for osx 10.14 build. +- [x] Support macro `MD_ST_NO_ASM` to disable ASM, [#8](https://github.com/ossrs/state-threads/issues/8). +- [x] Merge patch [srs#1282](https://github.com/ossrs/srs/issues/1282#issuecomment-445539513) to support aarch64, [#9](https://github.com/ossrs/state-threads/issues/9). + +## Docs + +* Introduction: http://ossrs.github.io/state-threads/docs/st.html +* API reference: http://ossrs.github.io/state-threads/docs/reference.html +* Programming notes: http://ossrs.github.io/state-threads/docs/notes.html + +## Usage + +Get code: + +``` +git clone https://github.com/ossrs/state-threads.git st-1.9 && +git checkout -b srs origin/srs +``` + +For Linux: + +``` +make linux-debug EXTRA_CFLAGS="-DMD_HAVE_EPOLL" +``` + +For OSX: + +``` +make darwin-debug EXTRA_CFLAGS="-DMD_HAVE_KQUEUE" +``` + +Linux with valgrind: + +``` +make linux-debug EXTRA_CFLAGS="-DMD_VALGRIND" +``` + +> Remark: User must install valgrind, for instance, in centos6 `sudo yum install -y valgrind valgrind-devel`. + +Linux with valgrind and epoll: + +``` +make linux-debug EXTRA_CFLAGS="-DMD_HAVE_EPOLL -DMD_VALGRIND" +``` + +For OSX, user must specifies the valgrind header files: + +``` +make darwin-debug EXTRA_CFLAGS="-DMD_HAVE_KQUEUE -DMD_VALGRIND -I/usr/local/include" +``` + +> Remark: Latest OSX does not support ST, please use docker to run ST. + +## Valgrind + +How to debug with gdb under valgrind, read [valgrind manual](http://valgrind.org/docs/manual/manual-core-adv.html#manual-core-adv.gdbserver-simple). + +About startup parameters, read [valgrind cli](http://valgrind.org/docs/manual/mc-manual.html#mc-manual.options). + +Important cli options: + +1. `--undef-value-errors= [default: yes]`, Controls whether Memcheck reports uses of undefined value errors. Set this to no if you don't want to see undefined value errors. It also has the side effect of speeding up Memcheck somewhat. +1. `--leak-check= [default: summary]`, When enabled, search for memory leaks when the client program finishes. If set to summary, it says how many leaks occurred. If set to full or yes, each individual leak will be shown in detail and/or counted as an error, as specified by the options `--show-leak-kinds` and `--errors-for-leak-kinds`. +1. `--track-origins= [default: no]`, Controls whether Memcheck tracks the origin of uninitialised values. By default, it does not, which means that although it can tell you that an uninitialised value is being used in a dangerous way, it cannot tell you where the uninitialised value came from. This often makes it difficult to track down the root problem. +1. `--show-reachable= , --show-possibly-lost=`, to show the using memory. + +Winlin 2016 diff --git a/trunk/3rdparty/st-srs/common.h b/trunk/3rdparty/st-srs/common.h new file mode 100644 index 000000000..0c0685b9a --- /dev/null +++ b/trunk/3rdparty/st-srs/common.h @@ -0,0 +1,479 @@ +/* + * The contents of this file are subject to the Mozilla Public + * License Version 1.1 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS + * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or + * implied. See the License for the specific language governing + * rights and limitations under the License. + * + * The Original Code is the Netscape Portable Runtime library. + * + * The Initial Developer of the Original Code is Netscape + * Communications Corporation. Portions created by Netscape are + * Copyright (C) 1994-2000 Netscape Communications Corporation. All + * Rights Reserved. + * + * Contributor(s): Silicon Graphics, Inc. + * + * Portions created by SGI are Copyright (C) 2000-2001 Silicon + * Graphics, Inc. All Rights Reserved. + * + * Alternatively, the contents of this file may be used under the + * terms of the GNU General Public License Version 2 or later (the + * "GPL"), in which case the provisions of the GPL are applicable + * instead of those above. If you wish to allow use of your + * version of this file only under the terms of the GPL and not to + * allow others to use your version of this file under the MPL, + * indicate your decision by deleting the provisions above and + * replace them with the notice and other provisions required by + * the GPL. If you do not delete the provisions above, a recipient + * may use your version of this file under either the MPL or the + * GPL. + */ + +/* + * This file is derived directly from Netscape Communications Corporation, + * and consists of extensive modifications made during the year(s) 1999-2000. + */ + +#ifndef __ST_COMMON_H__ +#define __ST_COMMON_H__ + +#include +#include +#include +#include +#include + +/* Enable assertions only if DEBUG is defined */ +#ifndef DEBUG + #define NDEBUG +#endif +#include +#define ST_ASSERT(expr) assert(expr) + +#define ST_BEGIN_MACRO { +#define ST_END_MACRO } + +#ifdef DEBUG + #define ST_HIDDEN /*nothing*/ +#else + #define ST_HIDDEN static +#endif + +#include "public.h" +#include "md.h" + +/* merge from https://github.com/toffaletti/state-threads/commit/7f57fc9acc05e657bca1223f1e5b9b1a45ed929b */ +#ifndef MD_VALGRIND + #ifndef NVALGRIND + #define NVALGRIND + #endif +#else + #undef NVALGRIND +#endif + + +/***************************************** + * Circular linked list definitions + */ + +typedef struct _st_clist { + struct _st_clist *next; + struct _st_clist *prev; +} _st_clist_t; + +/* Insert element "_e" into the list, before "_l" */ +#define ST_INSERT_BEFORE(_e,_l) \ + ST_BEGIN_MACRO \ + (_e)->next = (_l); \ + (_e)->prev = (_l)->prev; \ + (_l)->prev->next = (_e); \ + (_l)->prev = (_e); \ + ST_END_MACRO + +/* Insert element "_e" into the list, after "_l" */ +#define ST_INSERT_AFTER(_e,_l) \ + ST_BEGIN_MACRO \ + (_e)->next = (_l)->next; \ + (_e)->prev = (_l); \ + (_l)->next->prev = (_e); \ + (_l)->next = (_e); \ + ST_END_MACRO + +/* Return the element following element "_e" */ +#define ST_NEXT_LINK(_e) ((_e)->next) + +/* Append an element "_e" to the end of the list "_l" */ +#define ST_APPEND_LINK(_e,_l) ST_INSERT_BEFORE(_e,_l) + +/* Insert an element "_e" at the head of the list "_l" */ +#define ST_INSERT_LINK(_e,_l) ST_INSERT_AFTER(_e,_l) + +/* Return the head/tail of the list */ +#define ST_LIST_HEAD(_l) (_l)->next +#define ST_LIST_TAIL(_l) (_l)->prev + +/* Remove the element "_e" from it's circular list */ +#define ST_REMOVE_LINK(_e) \ + ST_BEGIN_MACRO \ + (_e)->prev->next = (_e)->next; \ + (_e)->next->prev = (_e)->prev; \ + ST_END_MACRO + +/* Return non-zero if the given circular list "_l" is empty, */ +/* zero if the circular list is not empty */ +#define ST_CLIST_IS_EMPTY(_l) \ + ((_l)->next == (_l)) + +/* Initialize a circular list */ +#define ST_INIT_CLIST(_l) \ + ST_BEGIN_MACRO \ + (_l)->next = (_l); \ + (_l)->prev = (_l); \ + ST_END_MACRO + +#define ST_INIT_STATIC_CLIST(_l) \ + {(_l), (_l)} + + +/***************************************** + * Basic types definitions + */ + +typedef void (*_st_destructor_t)(void *); + + +typedef struct _st_stack { + _st_clist_t links; + char *vaddr; /* Base of stack's allocated memory */ + int vaddr_size; /* Size of stack's allocated memory */ + int stk_size; /* Size of usable portion of the stack */ + char *stk_bottom; /* Lowest address of stack's usable portion */ + char *stk_top; /* Highest address of stack's usable portion */ + void *sp; /* Stack pointer from C's point of view */ +#ifdef __ia64__ + void *bsp; /* Register stack backing store pointer */ +#endif + /* merge from https://github.com/toffaletti/state-threads/commit/7f57fc9acc05e657bca1223f1e5b9b1a45ed929b */ +#ifndef NVALGRIND + /* id returned by VALGRIND_STACK_REGISTER */ + /* http://valgrind.org/docs/manual/manual-core-adv.html */ + unsigned long valgrind_stack_id; +#endif +} _st_stack_t; + + +typedef struct _st_cond { + _st_clist_t wait_q; /* Condition variable wait queue */ +} _st_cond_t; + + +typedef struct _st_thread _st_thread_t; + +struct _st_thread { + int state; /* Thread's state */ + int flags; /* Thread's flags */ + + void *(*start)(void *arg); /* The start function of the thread */ + void *arg; /* Argument of the start function */ + void *retval; /* Return value of the start function */ + + _st_stack_t *stack; /* Info about thread's stack */ + + _st_clist_t links; /* For putting on run/sleep/zombie queue */ + _st_clist_t wait_links; /* For putting on mutex/condvar wait queue */ +#ifdef DEBUG + _st_clist_t tlink; /* For putting on thread queue */ +#endif + + st_utime_t due; /* Wakeup time when thread is sleeping */ + _st_thread_t *left; /* For putting in timeout heap */ + _st_thread_t *right; /* -- see docs/timeout_heap.txt for details */ + int heap_index; + + void **private_data; /* Per thread private data */ + + _st_cond_t *term; /* Termination condition variable for join */ + + jmp_buf context; /* Thread's context */ +}; + + +typedef struct _st_mutex { + _st_thread_t *owner; /* Current mutex owner */ + _st_clist_t wait_q; /* Mutex wait queue */ +} _st_mutex_t; + + +typedef struct _st_pollq { + _st_clist_t links; /* For putting on io queue */ + _st_thread_t *thread; /* Polling thread */ + struct pollfd *pds; /* Array of poll descriptors */ + int npds; /* Length of the array */ + int on_ioq; /* Is it on ioq? */ +} _st_pollq_t; + + +typedef struct _st_eventsys_ops { + const char *name; /* Name of this event system */ + int val; /* Type of this event system */ + int (*init)(void); /* Initialization */ + void (*dispatch)(void); /* Dispatch function */ + int (*pollset_add)(struct pollfd *, int); /* Add descriptor set */ + void (*pollset_del)(struct pollfd *, int); /* Delete descriptor set */ + int (*fd_new)(int); /* New descriptor allocated */ + int (*fd_close)(int); /* Descriptor closed */ + int (*fd_getlimit)(void); /* Descriptor hard limit */ +} _st_eventsys_t; + + +typedef struct _st_vp { + _st_thread_t *idle_thread; /* Idle thread for this vp */ + st_utime_t last_clock; /* The last time we went into vp_check_clock() */ + + _st_clist_t run_q; /* run queue for this vp */ + _st_clist_t io_q; /* io queue for this vp */ + _st_clist_t zombie_q; /* zombie queue for this vp */ +#ifdef DEBUG + _st_clist_t thread_q; /* all threads of this vp */ +#endif + int pagesize; + + _st_thread_t *sleep_q; /* sleep queue for this vp */ + int sleepq_size; /* number of threads on sleep queue */ + +#ifdef ST_SWITCH_CB + st_switch_cb_t switch_out_cb; /* called when a thread is switched out */ + st_switch_cb_t switch_in_cb; /* called when a thread is switched in */ +#endif +} _st_vp_t; + + +typedef struct _st_netfd { + int osfd; /* Underlying OS file descriptor */ + int inuse; /* In-use flag */ + void *private_data; /* Per descriptor private data */ + _st_destructor_t destructor; /* Private data destructor function */ + void *aux_data; /* Auxiliary data for internal use */ + struct _st_netfd *next; /* For putting on the free list */ +} _st_netfd_t; + + +/***************************************** + * Current vp, thread, and event system + */ + +extern _st_vp_t _st_this_vp; +extern _st_thread_t *_st_this_thread; +extern _st_eventsys_t *_st_eventsys; + +#define _ST_CURRENT_THREAD() (_st_this_thread) +#define _ST_SET_CURRENT_THREAD(_thread) (_st_this_thread = (_thread)) + +#define _ST_LAST_CLOCK (_st_this_vp.last_clock) + +#define _ST_RUNQ (_st_this_vp.run_q) +#define _ST_IOQ (_st_this_vp.io_q) +#define _ST_ZOMBIEQ (_st_this_vp.zombie_q) +#ifdef DEBUG + #define _ST_THREADQ (_st_this_vp.thread_q) +#endif + +#define _ST_PAGE_SIZE (_st_this_vp.pagesize) + +#define _ST_SLEEPQ (_st_this_vp.sleep_q) +#define _ST_SLEEPQ_SIZE (_st_this_vp.sleepq_size) + +#define _ST_VP_IDLE() (*_st_eventsys->dispatch)() + + +/***************************************** + * vp queues operations + */ + +#define _ST_ADD_IOQ(_pq) ST_APPEND_LINK(&_pq.links, &_ST_IOQ) +#define _ST_DEL_IOQ(_pq) ST_REMOVE_LINK(&_pq.links) + +#define _ST_ADD_RUNQ(_thr) ST_APPEND_LINK(&(_thr)->links, &_ST_RUNQ) +#define _ST_DEL_RUNQ(_thr) ST_REMOVE_LINK(&(_thr)->links) + +#define _ST_ADD_SLEEPQ(_thr, _timeout) _st_add_sleep_q(_thr, _timeout) +#define _ST_DEL_SLEEPQ(_thr) _st_del_sleep_q(_thr) + +#define _ST_ADD_ZOMBIEQ(_thr) ST_APPEND_LINK(&(_thr)->links, &_ST_ZOMBIEQ) +#define _ST_DEL_ZOMBIEQ(_thr) ST_REMOVE_LINK(&(_thr)->links) + +#ifdef DEBUG + #define _ST_ADD_THREADQ(_thr) ST_APPEND_LINK(&(_thr)->tlink, &_ST_THREADQ) + #define _ST_DEL_THREADQ(_thr) ST_REMOVE_LINK(&(_thr)->tlink) +#endif + + +/***************************************** + * Thread states and flags + */ + +#define _ST_ST_RUNNING 0 +#define _ST_ST_RUNNABLE 1 +#define _ST_ST_IO_WAIT 2 +#define _ST_ST_LOCK_WAIT 3 +#define _ST_ST_COND_WAIT 4 +#define _ST_ST_SLEEPING 5 +#define _ST_ST_ZOMBIE 6 +#define _ST_ST_SUSPENDED 7 + +#define _ST_FL_PRIMORDIAL 0x01 +#define _ST_FL_IDLE_THREAD 0x02 +#define _ST_FL_ON_SLEEPQ 0x04 +#define _ST_FL_INTERRUPT 0x08 +#define _ST_FL_TIMEDOUT 0x10 + + +/***************************************** + * Pointer conversion + */ + +#ifndef offsetof + #define offsetof(type, identifier) ((size_t)&(((type *)0)->identifier)) +#endif + +#define _ST_THREAD_PTR(_qp) \ + ((_st_thread_t *)((char *)(_qp) - offsetof(_st_thread_t, links))) + +#define _ST_THREAD_WAITQ_PTR(_qp) \ + ((_st_thread_t *)((char *)(_qp) - offsetof(_st_thread_t, wait_links))) + +#define _ST_THREAD_STACK_PTR(_qp) \ + ((_st_stack_t *)((char*)(_qp) - offsetof(_st_stack_t, links))) + +#define _ST_POLLQUEUE_PTR(_qp) \ + ((_st_pollq_t *)((char *)(_qp) - offsetof(_st_pollq_t, links))) + +#ifdef DEBUG + #define _ST_THREAD_THREADQ_PTR(_qp) \ + ((_st_thread_t *)((char *)(_qp) - offsetof(_st_thread_t, tlink))) +#endif + + +/***************************************** + * Constants + */ + +#ifndef ST_UTIME_NO_TIMEOUT + #define ST_UTIME_NO_TIMEOUT ((st_utime_t) -1LL) +#endif + +#ifndef __ia64__ + #define ST_DEFAULT_STACK_SIZE (64*1024) +#else + #define ST_DEFAULT_STACK_SIZE (128*1024) /* Includes register stack size */ +#endif + +#ifndef ST_KEYS_MAX + #define ST_KEYS_MAX 16 +#endif + +#ifndef ST_MIN_POLLFDS_SIZE + #define ST_MIN_POLLFDS_SIZE 64 +#endif + + +/***************************************** + * Threads context switching + */ + +#ifdef DEBUG + void _st_iterate_threads(void); + #define ST_DEBUG_ITERATE_THREADS() _st_iterate_threads() +#else + #define ST_DEBUG_ITERATE_THREADS() +#endif + +#ifdef ST_SWITCH_CB + #define ST_SWITCH_OUT_CB(_thread) \ + if (_st_this_vp.switch_out_cb != NULL && \ + _thread != _st_this_vp.idle_thread && \ + _thread->state != _ST_ST_ZOMBIE) { \ + _st_this_vp.switch_out_cb(); \ + } + #define ST_SWITCH_IN_CB(_thread) \ + if (_st_this_vp.switch_in_cb != NULL && \ + _thread != _st_this_vp.idle_thread && \ + _thread->state != _ST_ST_ZOMBIE) { \ + _st_this_vp.switch_in_cb(); \ + } +#else + #define ST_SWITCH_OUT_CB(_thread) + #define ST_SWITCH_IN_CB(_thread) +#endif + +/* + * Switch away from the current thread context by saving its state and + * calling the thread scheduler + */ +#define _ST_SWITCH_CONTEXT(_thread) \ + ST_BEGIN_MACRO \ + ST_SWITCH_OUT_CB(_thread); \ + if (!MD_SETJMP((_thread)->context)) { \ + _st_vp_schedule(); \ + } \ + ST_DEBUG_ITERATE_THREADS(); \ + ST_SWITCH_IN_CB(_thread); \ + ST_END_MACRO + +/* + * Restore a thread context that was saved by _ST_SWITCH_CONTEXT or + * initialized by _ST_INIT_CONTEXT + */ +#define _ST_RESTORE_CONTEXT(_thread) \ + ST_BEGIN_MACRO \ + _ST_SET_CURRENT_THREAD(_thread); \ + MD_LONGJMP((_thread)->context, 1); \ + ST_END_MACRO + +/* + * Initialize the thread context preparing it to execute _main + */ +#ifdef MD_INIT_CONTEXT + #define _ST_INIT_CONTEXT MD_INIT_CONTEXT +#else + #error Unknown OS +#endif + +/* + * Number of bytes reserved under the stack "bottom" + */ +#define _ST_STACK_PAD_SIZE MD_STACK_PAD_SIZE + + +/***************************************** + * Forward declarations + */ + +void _st_vp_schedule(void); +void _st_vp_check_clock(void); +void *_st_idle_thread_start(void *arg); +void _st_thread_main(void); +void _st_thread_cleanup(_st_thread_t *thread); +void _st_add_sleep_q(_st_thread_t *thread, st_utime_t timeout); +void _st_del_sleep_q(_st_thread_t *thread); +_st_stack_t *_st_stack_new(int stack_size); +void _st_stack_free(_st_stack_t *ts); +int _st_io_init(void); + +st_utime_t st_utime(void); +_st_cond_t *st_cond_new(void); +int st_cond_destroy(_st_cond_t *cvar); +int st_cond_timedwait(_st_cond_t *cvar, st_utime_t timeout); +int st_cond_signal(_st_cond_t *cvar); +ssize_t st_read(_st_netfd_t *fd, void *buf, size_t nbyte, st_utime_t timeout); +ssize_t st_write(_st_netfd_t *fd, const void *buf, size_t nbyte, st_utime_t timeout); +int st_poll(struct pollfd *pds, int npds, st_utime_t timeout); +_st_thread_t *st_thread_create(void *(*start)(void *arg), void *arg, int joinable, int stk_size); + +#endif /* !__ST_COMMON_H__ */ + diff --git a/trunk/3rdparty/st-srs/docs/fig.gif b/trunk/3rdparty/st-srs/docs/fig.gif new file mode 100644 index 000000000..7265a05db Binary files /dev/null and b/trunk/3rdparty/st-srs/docs/fig.gif differ diff --git a/trunk/3rdparty/st-srs/docs/notes.html b/trunk/3rdparty/st-srs/docs/notes.html new file mode 100644 index 000000000..5a24369e2 --- /dev/null +++ b/trunk/3rdparty/st-srs/docs/notes.html @@ -0,0 +1,434 @@ + + +State Threads Library Programming Notes + + +

Programming Notes

+

+ +

+ +

+


+

+ +

Porting

+The State Threads library uses OS concepts that are available in some +form on most UNIX platforms, making the library very portable across +many flavors of UNIX. However, there are several parts of the library +that rely on platform-specific features. Here is the list of such parts: +

+

    +
  • Thread context initialization: Two ingredients of the +jmp_buf +data structure (the program counter and the stack pointer) have to be +manually set in the thread creation routine. The jmp_buf data +structure is defined in the setjmp.h header file and differs from +platform to platform. Usually the program counter is a structure member +with PC in the name and the stack pointer is a structure member +with SP in the name. One can also look in the +Netscape's NSPR library source +which already has this code for many UNIX-like platforms +(mozilla/nsprpub/pr/include/md/*.h files). +

    +Note that on some BSD-derived platforms _setjmp(3)/_longjmp(3) +calls should be used instead of setjmp(3)/longjmp(3) (that is +the calls that manipulate only the stack and registers and do not +save and restore the process's signal mask).

  • +

    +Starting with glibc 2.4 on Linux the opacity of the jmp_buf data +structure is enforced by setjmp(3)/longjmp(3) so the +jmp_buf ingredients cannot be accessed directly anymore (unless +special environmental variable LD_POINTER_GUARD is set before application +execution). To avoid dependency on custom environment, the State Threads +library provides setjmp/longjmp replacement functions for +all Intel CPU architectures. Other CPU architectures can also be easily +supported (the setjmp/longjmp source code is widely available for +many CPU architectures). +

    +

  • High resolution time function: Some platforms (IRIX, Solaris) +provide a high resolution time function based on the free running hardware +counter. This function returns the time counted since some arbitrary +moment in the past (usually machine power up time). It is not correlated in +any way to the time of day, and thus is not subject to resetting, +drifting, etc. This type of time is ideal for tasks where cheap, accurate +interval timing is required. If such a function is not available on a +particular platform, the gettimeofday(3) function can be used +(though on some platforms it involves a system call). +

    +

  • The stack growth direction: The library needs to know whether the +stack grows toward lower (down) or higher (up) memory addresses. +One can write a simple test program that detects the stack growth direction +on a particular platform.
  • +

    +

  • Non-blocking attribute inheritance: On some platforms (e.g. IRIX) +the socket created as a result of the accept(2) call inherits the +non-blocking attribute of the listening socket. One needs to consult the manual +pages or write a simple test program to see if this applies to a specific +platform.
  • +

    +

  • Anonymous memory mapping: The library allocates memory segments +for thread stacks by doing anonymous memory mapping (mmap(2)). This +mapping is somewhat different on SVR4 and BSD4.3 derived platforms. +

    +The memory mapping can be avoided altogether by using malloc(3) for +stack allocation. In this case the MALLOC_STACK macro should be +defined.

  • +
+

+All machine-dependent feature test macros should be defined in the +md.h header file. The assembly code for setjmp/longjmp +replacement functions for all CPU architectures should be placed in +the md.S file. +

+The current version of the library is ported to: +

    +
  • IRIX 6.x (both 32 and 64 bit)
  • +
  • Linux (kernel 2.x and glibc 2.x) on x86, Alpha, MIPS and MIPSEL, + SPARC, ARM, PowerPC, 68k, HPPA, S390, IA-64, and Opteron (AMD-64)
  • +
  • Solaris 2.x (SunOS 5.x) on x86, AMD64, SPARC, and SPARC-64
  • +
  • AIX 4.x
  • +
  • HP-UX 11 (both 32 and 64 bit)
  • +
  • Tru64/OSF1
  • +
  • FreeBSD on x86, AMD64, and Alpha
  • +
  • OpenBSD on x86, AMD64, Alpha, and SPARC
  • +
  • NetBSD on x86, Alpha, SPARC, and VAX
  • +
  • MacOS X (Darwin) on PowerPC (32 bit) and Intel (both 32 and 64 bit) [universal]
  • +
  • Cygwin
  • +
+

+ + +

Signals

+Signal handling in an application using State Threads should be treated the +same way as in a classical UNIX process application. There is no such +thing as per-thread signal mask, all threads share the same signal handlers, +and only asynchronous-safe functions can be used in signal handlers. +However, there is a way to process signals synchronously by converting a +signal event to an I/O event: a signal catching function does a write to +a pipe which will be processed synchronously by a dedicated signal handling +thread. The following code demonstrates this technique (error handling is +omitted for clarity): +
+
+/* Per-process pipe which is used as a signal queue. */
+/* Up to PIPE_BUF/sizeof(int) signals can be queued up. */
+int sig_pipe[2];
+
+/* Signal catching function. */
+/* Converts signal event to I/O event. */
+void sig_catcher(int signo)
+{
+  int err;
+
+  /* Save errno to restore it after the write() */
+  err = errno;
+  /* write() is reentrant/async-safe */
+  write(sig_pipe[1], &signo, sizeof(int));
+  errno = err;
+}
+
+/* Signal processing function. */
+/* This is the "main" function of the signal processing thread. */
+void *sig_process(void *arg)
+{
+  st_netfd_t nfd;
+  int signo;
+
+  nfd = st_netfd_open(sig_pipe[0]);
+
+  for ( ; ; ) {
+    /* Read the next signal from the pipe */
+    st_read(nfd, &signo, sizeof(int), ST_UTIME_NO_TIMEOUT);
+
+    /* Process signal synchronously */
+    switch (signo) {
+    case SIGHUP:
+      /* do something here - reread config files, etc. */
+      break;
+    case SIGTERM:
+      /* do something here - cleanup, etc. */
+      break;
+      /*      .
+              .
+         Other signals
+              .
+              .
+      */
+    }
+  }
+
+  return NULL;
+}
+
+int main(int argc, char *argv[])
+{
+  struct sigaction sa;
+        .
+        .
+        .
+
+  /* Create signal pipe */
+  pipe(sig_pipe);
+
+  /* Create signal processing thread */
+  st_thread_create(sig_process, NULL, 0, 0);
+
+  /* Install sig_catcher() as a signal handler */
+  sa.sa_handler = sig_catcher;
+  sigemptyset(&sa.sa_mask);
+  sa.sa_flags = 0;
+  sigaction(SIGHUP, &sa, NULL);
+
+  sa.sa_handler = sig_catcher;
+  sigemptyset(&sa.sa_mask);
+  sa.sa_flags = 0;
+  sigaction(SIGTERM, &sa, NULL);
+
+        .
+        .
+        .
+      
+}
+
+
+

+Note that if multiple processes are used (see below), the signal pipe should +be initialized after the fork(2) call so that each process has its +own private pipe. +

+ + +

Intra-Process Synchronization

+Due to the event-driven nature of the library scheduler, the thread context +switch (process state change) can only happen in a well-known set of +library functions. This set includes functions in which a thread may +"block": I/O functions (st_read(), st_write(), etc.), +sleep functions (st_sleep(), etc.), and thread synchronization +functions (st_thread_join(), st_cond_wait(), etc.). As a result, +process-specific global data need not to be protected by locks since a thread +cannot be rescheduled while in a critical section (and only one thread at a +time can access the same memory location). By the same token, +non thread-safe functions (in a traditional sense) can be safely used with +the State Threads. The library's mutex facilities are practically useless +for a correctly written application (no blocking functions in critical +section) and are provided mostly for completeness. This absence of locking +greatly simplifies an application design and provides a foundation for +scalability. +

+ + +

Inter-Process Synchronization

+The State Threads library makes it possible to multiplex a large number +of simultaneous connections onto a much smaller number of separate +processes, where each process uses a many-to-one user-level threading +implementation (N of M:1 mappings rather than one M:N +mapping used in native threading libraries on some platforms). This design +is key to the application's scalability. One can think about it as if a +set of all threads is partitioned into separate groups (processes) where +each group has a separate pool of resources (virtual address space, file +descriptors, etc.). An application designer has full control of how many +groups (processes) an application creates and what resources, if any, +are shared among different groups via standard UNIX inter-process +communication (IPC) facilities.

+There are several reasons for creating multiple processes: +

+

    +
  • To take advantage of multiple hardware entities (CPUs, disks, etc.) +available in the system (hardware parallelism).
  • +

    +

  • To reduce risk of losing a large number of user connections when one of +the processes crashes. For example, if C user connections (threads) +are multiplexed onto P processes and one of the processes crashes, +only a fraction (C/P) of all connections will be lost.
  • +

    +

  • To overcome per-process resource limitations imposed by the OS. For +example, if select(2) is used for event polling, the number of +simultaneous connections (threads) per process is +limited by the FD_SETSIZE parameter (see select(2)). +If FD_SETSIZE is equal to 1024 and each connection needs one file +descriptor, then an application should create 10 processes to support 10,000 +simultaneous connections.
  • +
+

+Ideally all user sessions are completely independent, so there is no need for +inter-process communication. It is always better to have several separate +smaller process-specific resources (e.g., data caches) than to have one large +resource shared (and modified) by all processes. Sometimes, however, there +is a need to share a common resource among different processes. In that case, +standard UNIX IPC facilities can be used. In addition to that, there is a way +to synchronize different processes so that only the thread accessing the +shared resource will be suspended (but not the entire process) if that resource +is unavailable. In the following code fragment a pipe is used as a counting +semaphore for inter-process synchronization: +

+#ifndef PIPE_BUF
+#define PIPE_BUF 512  /* POSIX */
+#endif
+
+/* Semaphore data structure */
+typedef struct ipc_sem {
+  st_netfd_t rdfd;  /* read descriptor */
+  st_netfd_t wrfd;  /* write descriptor */
+} ipc_sem_t;
+
+/* Create and initialize the semaphore. Should be called before fork(2). */
+/* 'value' must be less than PIPE_BUF. */
+/* If 'value' is 1, the semaphore works as mutex. */
+ipc_sem_t *ipc_sem_create(int value)
+{
+  ipc_sem_t *sem;
+  int p[2];
+  char b[PIPE_BUF];
+
+  /* Error checking is omitted for clarity */
+  sem = malloc(sizeof(ipc_sem_t));
+
+  /* Create the pipe */
+  pipe(p);
+  sem->rdfd = st_netfd_open(p[0]);
+  sem->wrfd = st_netfd_open(p[1]);
+
+  /* Initialize the semaphore: put 'value' bytes into the pipe */
+  write(p[1], b, value);
+
+  return sem;
+}
+
+/* Try to decrement the "value" of the semaphore. */
+/* If "value" is 0, the calling thread blocks on the semaphore. */
+int ipc_sem_wait(ipc_sem_t *sem)
+{
+  char c;
+
+  /* Read one byte from the pipe */
+  if (st_read(sem->rdfd, &c, 1, ST_UTIME_NO_TIMEOUT) != 1)
+    return -1;
+
+  return 0;
+}
+
+/* Increment the "value" of the semaphore. */
+int ipc_sem_post(ipc_sem_t *sem)
+{
+  char c;
+
+  if (st_write(sem->wrfd, &c, 1, ST_UTIME_NO_TIMEOUT) != 1)
+    return -1;
+
+  return 0;
+}
+
+
+

+ +Generally, the following steps should be followed when writing an application +using the State Threads library: +

+

    +
  1. Initialize the library (st_init()).
  2. +

    +

  3. Create resources that will be shared among different processes: + create and bind listening sockets, create shared memory segments, IPC + channels, synchronization primitives, etc.
  4. +

    +

  5. Create several processes (fork(2)). The parent process should + either exit or become a "watchdog" (e.g., it starts a new process when + an existing one crashes, does a cleanup upon application termination, + etc.).
  6. +

    +

  7. In each child process create a pool of threads + (st_thread_create()) to handle user connections.
  8. +
+

+ + +

Non-Network I/O

+ +The State Threads architecture uses non-blocking I/O on +st_netfd_t objects for concurrent processing of multiple user +connections. This architecture has a drawback: the entire process and +all its threads may block for the duration of a disk or other +non-network I/O operation, whether through State Threads I/O functions, +direct system calls, or standard I/O functions. (This is applicable +mostly to disk reads; disk writes are usually performed +asynchronously -- data goes to the buffer cache to be written to disk +later.) Fortunately, disk I/O (unlike network I/O) usually takes a +finite and predictable amount of time, but this may not be true for +special devices or user input devices (including stdin). Nevertheless, +such I/O reduces throughput of the system and increases response times. +There are several ways to design an application to overcome this +drawback: + +

+

+

+ + +

Timeouts

+ +The timeout parameter to st_cond_timedwait() and the +I/O functions, and the arguments to st_sleep() and +st_usleep() specify a maximum time to wait since the last +context switch not since the beginning of the function call. + +

The State Threads' time resolution is actually the time interval +between context switches. That time interval may be large in some +situations, for example, when a single thread does a lot of work +continuously. Note that a steady, uninterrupted stream of network I/O +qualifies for this description; a context switch occurs only when a +thread blocks. + +

If a specified I/O timeout is less than the time interval between +context switches the function may return with a timeout error before +that amount of time has elapsed since the beginning of the function +call. For example, if eight milliseconds have passed since the last +context switch and an I/O function with a timeout of 10 milliseconds +blocks, causing a switch, the call may return with a timeout error as +little as two milliseconds after it was called. (On Linux, +select()'s timeout is an upper bound on the amount of +time elapsed before select returns.) Similarly, if 12 ms have passed +already, the function may return immediately. + +

In almost all cases I/O timeouts should be used only for detecting a +broken network connection or for preventing a peer from holding an idle +connection for too long. Therefore for most applications realistic I/O +timeouts should be on the order of seconds. Furthermore, there's +probably no point in retrying operations that time out. Rather than +retrying simply use a larger timeout in the first place. + +

The largest valid timeout value is platform-dependent and may be +significantly less than INT_MAX seconds for select() +or INT_MAX milliseconds for poll(). Generally, you +should not use timeouts exceeding several hours. Use +ST_UTIME_NO_TIMEOUT (-1) as a special value to +indicate infinite timeout or indefinite sleep. Use +ST_UTIME_NO_WAIT (0) to indicate no waiting at all. + +

+


+

+ + + diff --git a/trunk/3rdparty/st-srs/docs/reference.html b/trunk/3rdparty/st-srs/docs/reference.html new file mode 100644 index 000000000..3c9c7bd78 --- /dev/null +++ b/trunk/3rdparty/st-srs/docs/reference.html @@ -0,0 +1,3120 @@ + + +State Threads Library Reference + + + +

State Threads Library Reference

+ +
+
Types
+
st_thread_t
+
st_cond_t
+
st_mutex_t
+
st_utime_t
+
st_netfd_t
+
st_switch_cb_t
+

+

Error Handling
+

+

Library Initialization
+

+

st_init()
+
st_getfdlimit()
+
st_set_eventsys()
+
st_get_eventsys()
+
st_get_eventsys_name()
+
st_set_utime_function()
+
st_timecache_set()
+
st_randomize_stacks()
+

+

st_switch_cb_t type
+
st_set_switch_in_cb()
+
st_set_switch_out_cb()
+

+

Thread Control and Identification
+

+

st_thread_t type
+
st_thread_create()
+
st_thread_exit()
+
st_thread_join()
+
st_thread_self()
+
st_thread_interrupt()
+
st_sleep()
+
st_usleep()
+
st_randomize_stacks()
+

+

Per-Thread Private Data
+

+

st_key_create()
+
st_key_getlimit()
+
st_thread_setspecific()
+
st_thread_getspecific()
+

+

Synchronization
+

+

st_cond_t type
+
st_cond_new()
+
st_cond_destroy()
+
st_cond_wait()
+
st_cond_timedwait()
+
st_cond_signal()
+
st_cond_broadcast()
+

+

st_mutex_t type
+
st_mutex_new()
+
st_mutex_destroy()
+
st_mutex_lock()
+
st_mutex_trylock()
+
st_mutex_unlock()
+

+

Timing
+

+

st_utime_t type
+
st_utime()
+
st_set_utime_function()
+
st_timecache_set()
+
st_time()
+

+

I/O Functions
+

+

st_netfd_t type
+
st_netfd_open()
+
st_netfd_open_socket()
+
st_netfd_free()
+
st_netfd_close()
+
st_netfd_fileno()
+
st_netfd_setspecific()
+
st_netfd_getspecific()
+
st_netfd_serialize_accept()
+
+
st_netfd_poll()
+

+

st_accept()
+
st_connect()
+
st_read()
+
st_read_fully()
+
st_read_resid()
+
st_readv()
+
st_readv_resid()
+
st_write()
+
st_write_resid()
+
st_writev()
+
st_writev_resid()
+
st_recvfrom()
+
st_sendto()
+
st_recvmsg()
+
st_sendmsg()
+

+

st_open()
+
st_poll()
+

+

Program Structure
+

+

List of Blocking Functions
+

+

+

+


+

+ + + +

Types

+ +The State Thread library defines the following types in the st.h +header file: +

+

+
st_thread_t
+
st_cond_t
+
st_mutex_t
+
st_utime_t
+
st_netfd_t
+
+

+


+

+ + +

st_thread_t

+ +Thread type. +

+

Syntax
+ +
+#include <st.h>
+
+typedef void *  st_thread_t;
+
+

+

Description
+ +A thread is represented and identified by a pointer to an opaque data +structure. This pointer is a required parameter for most of the functions +that operate on threads. +

+The thread identifier remains valid until the thread returns from its root +function and, if the thread was created joinable, is joined. +

+


+

+ + +

st_cond_t

+ +Condition variable type. +

+

Syntax
+ +
+#include <st.h>
+
+typedef void *  st_cond_t;
+
+

+

Description
+ +A condition variable is an opaque object identified by a pointer. +Condition variables provide synchronization primitives to wait for or wake +up threads waiting for certain conditions to be satisfied. +

+In the State Threads library there is no need to lock a mutex before +waiting on a condition variable. +

+


+

+ + +

st_mutex_t

+ +Mutex type. +

+

Syntax
+ +
+#include <st.h>
+
+typedef void *  st_mutex_t;
+
+

+

Description
+ +A mutex is an opaque object identified by a pointer. +Mutual exclusion locks (mutexes) are used to serialize the execution of +threads through critical sections of code. +

+If application using the State Threads library is written with no +I/O or control yielding in critical sections (that is no +blocking functions in critical sections), then there is +no need for mutexes.

+These mutexes can only be used for intra-process thread synchronization. +They cannot be used for inter-process synchronization. +

+


+

+ + +

st_utime_t

+ +High resolution time type ("u" stands for "micro"). +

+

Syntax
+ +
+#include <st.h>
+
+typedef unsigned long long  st_utime_t;
+
+

+

Description
+ +This datatype (unsigned 64-bit integer) represents high-resolution real time +expressed in microseconds since some arbitrary time in the past. It is not +correlated in any way to the time of day. +

+


+

+ + +

st_netfd_t

+ +File descriptor type. +

+

Syntax
+ +
+#include <st.h>
+
+typedef void *  st_netfd_t;
+
+

+

Description
+ +This datatype typically represents any open end point of network +communication (socket, end point of a pipe, FIFO, etc.) but can +encapsulate any open file descriptor. Objects of this type are +identified by a pointer to an opaque data structure. + +

+


+

+ + +

st_switch_cb_t

+ +Context switch callback function type. +

+

Syntax
+ +
+#include <st.h>
+
+typedef void (*st_switch_cb_t)(void);
+
+

+

Description
+ +This datatype is a convenience type for describing a pointer +to a function that will be called when a thread is set to stop +or set to run. +This feature is available only when ST_SWITCH_CB is defined +in <st.h>. + +

+


+

+ + +

Error Handling

+ + +All State Threads library non-void functions return on success either a +non-negative integer or a pointer to a newly created object (constructor-type +functions). On failure they return either -1 or a NULL +pointer respectively and set global errno to indicate the error. +It is safe to use errno because it is set right before the function +return and only one thread at a time can modify its value.

+The perror(3) function can be used to produce an error message on the +standard error output. +

+


+

+ + +

Library Initialization

+ +

+

+
st_init()
+
st_getfdlimit()
+
st_set_eventsys()
+
st_get_eventsys()
+
st_get_eventsys_name()
+

+These functions operate on a callback function of type +st_switch_cb_t: +

st_set_switch_in_cb()
+
st_set_switch_out_cb()
+
+

+


+

+ + +

st_init()

+ +Initializes the runtime. +

+

Syntax
+ +
+#include <st.h>
+
+int st_init(void);
+
+

+

Parameters
+None. +

+

Returns
+Upon successful completion, a value of 0 is returned. +Otherwise, a value of -1 is returned and errno is set +to indicate the error. +

+

Description
+This function initializes the library runtime. It should be called near +the beginning of the application's main() function before any other +State Threads library function is called.

+Among other things, this function limits the number of open file descriptors +to the OS imposed per-process maximum number or, if select(2) is +used, to FD_SETSIZE, whichever is less (getrlimit(2)). +This limit can be +retrieved by st_getfdlimit(). It also sets the +disposition of the SIGPIPE signal to SIG_IGN (to be ignored) +(signal(5)). +

+Unlike POSIX threads, a new process created by the fork(2) system +call is an exact copy of the calling process and all state threads +which are running in the parent do exist in the child. That means that +st_init() may be called either before or after multiple processes +are created by fork(2). +

+If the library runtime is not properly initialized (e.g., st_init() +is accidentally omitted), then the process will receive either an arithmetic +exception (SIGFPE or SIGTRAP) or segmentation fault (SIGSEGV) signal upon +new thread creation or the first context switch, respectively. +

+


+

+ +

st_getfdlimit()

+ +Returns the maximum number of file descriptors that the calling process +can open. +

+

Syntax
+ +
+#include <st.h>
+
+int st_getfdlimit(void);
+
+

+

Parameters
+None. +

+

Returns
+The maximum number of file descriptors that the calling process can open. +If this function is called before the library is successfully initialized by +st_init(), a value of -1 is returned. +

+

Description
+This function returns the limit on the number of open file descriptors which +is set by the st_init() function. +

+


+

+ + +

st_set_eventsys()

+ +Sets event notification mechanism. +

+

Syntax
+ +
+#include <st.h>
+
+int st_set_eventsys(int eventsys);
+
+

+

Parameters
+st_set_eventsys() has the following parameter:

+eventsys

+An integer value identifying selected event notification mechanism. The +following values are defined in the st.h header file: +

+ + + + + + + + + + + + + + + +
ST_EVENTSYS_DEFAULTUse default event notification mechanism. Usually it's select(2) +but if the library was compiled with the USE_POLL macro defined +then the default is poll(2).
ST_EVENTSYS_SELECTUse select(2) as an event notification mechanism.
ST_EVENTSYS_POLLUse poll(2) as an event notification mechanism.
ST_EVENTSYS_ALTUse an alternative event notification mechanism. The actual +mechanism selected depends on OS support. For example, epoll(4) +will be used on Linux if supported and kqueue(2) will be used +on FreeBSD/OpenBSD. If the OS supports no alternative event +notification mechanism, setting ST_EVENTSYS_ALT has no effect +and the ST_EVENTSYS_DEFAULT mechanism will be used.
+

+

Returns
+Upon successful completion, a value of 0 is returned. +Otherwise, a value of -1 is returned and errno is set +to indicate the error:

+ + + + + +
EINVAL +The supplied eventsys parameter has an invalid value. +
EBUSY +The event notification mechanism has already been set. +
+

+

Description
+This function sets the event notification mechanism that will be used by +the State Threads library. To have any effect, it must be called +before the st_init() function which performs +the actual initialization. If st_set_eventsys() is not called, +st_init() will set the ST_EVENTSYS_DEFAULT +mechanism. The mechanism cannot be changed once set. +

+There are no strict rules for selecting an event notification +mechanism. The "best" one depends on how your application behaves. +Try a few to see which one works best for you. As a rule of +thumb, you should use the ST_EVENTSYS_ALT mechanism if your +application deals with a very large number of network connections of +which only a few are active at once. +

+


+

+ +

st_get_eventsys()

+ +Returns the integer value identifying the event notification mechanism +being used by the State Threads library. +

+

Syntax
+ +
+#include <st.h>
+
+int st_get_eventsys(void);
+
+

+

Parameters
+None. +

+

Returns
+The integer value identifying the current event notification mechanism. +This value can be one of the following (see st_set_eventsys()): +ST_EVENTSYS_SELECT, ST_EVENTSYS_POLL, or +ST_EVENTSYS_ALT. Future versions of the library may return other +values. If a mechanism hasn't been set yet, a value of -1 is returned. +

+

Description
+This function returns the integer value identifying the event notification +mechanism which is actually being used by the State Threads library. +

+


+

+ +

st_get_eventsys_name()

+ +Returns the name of the event notification mechanism being used by the +State Threads library. +

+

Syntax
+ +
+#include <st.h>
+
+const char *st_get_eventsys_name(void);
+
+

+

Parameters
+None. +

+

Returns
+The string identifying the current event notification mechanism. If a +mechanism hasn't been set yet (see st_set_eventsys()), an empty string is +returned. Possible return values are "select", +"poll", "kqueue", or "epoll". Future versions +of the library may return other values. +

+

Description
+This function returns the string identifying the event notification +mechanism which is actually being used by the State Threads library. +

+


+

+ + +

st_set_switch_in_cb()

+ + +

st_set_switch_out_cb()

+
+Set the optional callback function for thread switches. +

+

Syntax
+ +
+#include <st.h>
+
+st_switch_cb_t st_set_switch_in_cb(st_switch_cb_t cb);
+st_switch_cb_t st_set_switch_out_cb(st_switch_cb_t cb);
+
+

+

Parameters
+st_set_switch_in_cb() and st_set_switch_out_cb() have the +following parameter:

+cb

+A function to be called when a thread is resumed and stopped respectively.

+

Returns
+The previous callback function pointer. +

+

Description
+These functions set the callback for when a thread is resumed and stopped +respectively. After being called any thread switch will call the callback. +Use a NULL pointer to disable the callback (this is the default). +Use st_thread_self() or thread +specific data to differentiate between threads.

+These functions can be called at any time.

+This feature is available only when ST_SWITCH_CB is defined +in <st.h>. +

+


+

+ + +

Thread Control and Identification

+ +

+These functions operate on a thread object of type +st_thread_t. +

+

+
st_thread_create()
+
st_thread_exit()
+
st_thread_join()
+
st_thread_self()
+
st_thread_interrupt()
+
st_sleep()
+
st_usleep()
+
st_randomize_stacks()
+
+

+


+

+ +

st_thread_create()

+ +Creates a new thread. +

+

Syntax
+ +
+#include <st.h>
+
+st_thread_t st_thread_create(void *(*start)(void *arg), void *arg,
+                             int joinable, int stack_size);
+
+
+

+

Parameters
+st_thread_create() has the following parameters:

+start

+A pointer to the thread's start function, which is called as the root of the +new thread. Return from this function terminates a thread.

+arg

+A pointer to the root function's only parameter.

+joinable

+Specifies whether the thread is joinable or unjoinable. If this parameter +is zero, the thread is unjoinable. Otherwise, it is joinable. +See also st_thread_join().

+stack_size

+Specifies your preference for the size of the stack, in bytes, associated +with the newly created thread. If you pass zero in this parameter, the +default stack size will be used. The default stack size is 128 KB on IA-64 +and 64 KB on all other platforms. On IA-64 only a half of stack_size +bytes is used for the memory stack. The other half is used for the register +stack backing store. +

+

Returns
+Upon successful completion, a new thread identifier is returned (this +identifier remains valid until the thread returns from its start function). +Otherwise, NULL is returned and errno is set +to indicate the error. +

+

Description
+This function creates a new thread. Note that the total number of threads +created by the application is limited by the amount of swap space available. +Upon thread creation, stack_size bytes are reserved on the swap +space. The stack pages are not actually used (valid) until touched by the +application. +

+


+

+ +

st_thread_exit()

+ +Terminates the calling thread. +

+

Syntax
+ +
+#include <st.h>
+
+void st_thread_exit(void *retval);
+
+

+

Parameters
+st_thread_exit() has the following parameters:

+retval

+If the thread is joinable, then the value retval may be retrieved +by st_thread_join(). If a thread returns from its +start function, it acts as if it had called st_thread_exit() with +retval as the value returned. +

+

Returns
+Nothing. +

+

Description
+This function terminates the calling thread. When a thread exits, per-thread +private data is destroyed by invoking the destructor function for any +non-NULL thread specific values associated with active keys (see +st_key_create()). This function is implicitly called +when a thread returns from its start function.

+When the last thread terminates the process exits with a zero status value. +

+


+

+ +

st_thread_join()

+ +Blocks the calling thread until a specified thread terminates. +

+

Syntax
+ +
+#include <st.h>
+
+int st_thread_join(st_thread_t thread, void **retvalp);
+
+

+

Parameters
+st_thread_join() has the following parameters:

+thread

+A valid identifier for the thread that is to be joined.

+retvalp

+If this parameter is not NULL, then the exit value of the +thread will be placed in the location referenced by this parameter +(see st_thread_exit()). +

+

Returns
+Upon successful completion, a value of 0 is returned. +Otherwise, a value of -1 is returned and errno is set +to indicate the error:

+ + + + + +
EINVALTarget thread is unjoinable.
EINVALOther thread already waits on the same +joinable thread.
EDEADLKTarget thread is the same as the +calling thread.
EINTRCurrent thread was interrupted by +st_thread_interrupt().
+

+

Description
+This function is used to synchronize the termination of a thread and possibly +retrieve its exit value. Several threads cannot wait for the same thread +to complete - one of the calling threads operates successfully, and the others +terminate with the error. The calling thread is not blocked if the target +thread has already terminated. +

+


+

+ +

st_thread_self()

+ +Identifies the calling thread. +

+

Syntax
+ +
+#include <st.h>
+
+st_thread_t st_thread_self(void);
+
+

+

Parameters
+None. +

+

Returns
+Always returns a valid reference to the calling thread - a self-identity. +

+

Description
+This function identifies the calling thread. This is the same identifier +that the creating thread obtains from +st_thread_create(). +

+


+

+ +

st_thread_interrupt()

+ +Interrupts a target thread. +

+

Syntax
+ +
+#include <st.h>
+
+void st_thread_interrupt(st_thread_t thread);
+
+

+

Parameters
+st_thread_interrupt() has the following parameters:

+thread

+A valid identifier for the thread being interrupted. +

+

Returns
+Nothing. +

+

Description
+This function interrupts (unblocks) a target thread that is blocked in one +of the blocking functions. A function that was interrupted +returns an error and sets errno to EINTR. It is up to +the target thread to act upon an interrupt (e.g., it may exit or just +abort the current transaction).

+Note: State Threads library functions are never interrupted by a +caught signal. A blocking library function returns an error and sets +errno to EINTR only if the current thread was +interrupted via st_thread_interrupt(). +

+If a target thread is already runnable or running (e.g., it is a newly +created thread or calling thread itself), this function will prevent it +from subsequent blocking. In other words, the interrupt will be "delivered" +only when a target thread is about to block. +

+


+

+ +

st_sleep(), st_usleep()

+ +Suspends current thread for a specified amount of time. +

+

Syntax
+ +
+#include <st.h>
+
+int st_sleep(int secs);
+
+int st_usleep(st_utime_t usecs);
+
+

+

Parameters
+st_sleep() has the following parameters:

+secs

+The number of seconds you want the thread to sleep for. +

+st_usleep() has the following parameters:

+usecs

+The number of microseconds you want the thread to sleep for. This parameter +is a variable of type st_utime_t. +

+

Returns
+Upon successful completion, a value of 0 is returned. +Otherwise, a value of -1 is returned and errno is set +to indicate the error:

+ + +
EINTRThe current thread was interrupted by +st_thread_interrupt().
+

+

Description
+These functions suspend the calling thread from execution for a specified +number of seconds (st_sleep()) or microseconds (st_usleep()). +

+ +If zero is passed as a parameter to st_sleep(), or +ST_UTIME_NO_WAIT (0) is passed to +st_usleep(), the calling thread yields, thus potentially +allowing another thread to run. + +

+ +If -1 is passed as a parameter to st_sleep(), or +ST_UTIME_NO_TIMEOUT (-1) is passed to +st_usleep(), the calling thread will be suspended permanently. +It can be resumed again by interrupting it via st_thread_interrupt(). + +

+


+

+ +

st_randomize_stacks()

+ +Turns stack base address randomization on or off. +

+

Syntax
+ +
+#include <st.h>
+
+int st_randomize_stacks(int on);
+
+

+

Parameters
+st_randomize_stacks() has the following parameters:

+on

+If this parameter has a non-zero value, the State Threads library +randomizes the base addresses of stacks allocated for threads created +after this call. Otherwise new threads' stacks are typically page +aligned. +

+

Returns
+The previous state of stack randomization (a value of 0 if it +was off and a non-zero value otherwise). +

+

Description
+Randomizing state threads' stack bases may improve cache performance on +some systems when large numbers of state threads all perform roughly the +same work, as when they all start from the same root function. On many +modern systems the performance increase is negligible. You should +compare your application's performance with this feature on and off to +see if you really need it. +

+When randomization is enabled, new stacks are allocated one page larger +to accomodate the randomization. +

+This call affects only threads created afterward. It has no effect on +existing threads. +

+


+

+ + +

Per-Thread Private Data

+ +These functions allow to associate private data with each of the threads in +a process. +

+

+
st_key_create()
+
st_key_getlimit()
+
st_thread_setspecific()
+
st_thread_getspecific()
+
+

+


+

+ +

st_key_create()

+ +Creates a key (non-negative integer) that can be used by all +threads in the process to get and set thread-specific data. +

+

Syntax
+ +
+#include <st.h>
+
+int st_key_create(int *keyp, void (*destructor)(void *));
+
+

+

Parameters
+st_key_create() has the following parameters:

+keyp

+The newly created key is returned in the memory pointed to by this parameter. +The new key can be used with +st_thread_setspecific() and +st_thread_getspecific().

+destructor

+Specifies an optional destructor function for the private data associated +with the key. This function can be specified as NULL. +Upon thread exit (see st_thread_exit()), if a key +has a non-NULL destructor and has a non-NULL value +associated with that key, then the destructor function will be +called with the associated value. +

+

Returns
+Upon successful completion, a value of 0 is returned. +Otherwise, a value of -1 is returned and errno is set +to indicate the error:

+ + +
EAGAINThe limit on the total number of keys per +process has been exceeded (see st_key_getlimit()). +
+

+

Description
+If this function is successful, every thread in the same process is capable +of associating private data with the new key. After a new key is created, all +active threads have the value NULL associated with that key. +After a new thread is created, the value NULL is associated with +all keys for that thread. If a non-NULL destructor function is +registered with a new key, it will be called at one of two times, as long as +the private data is not NULL: + +

+The key maintains independent data values for each binding thread. A thread +can get access only to its own thread-specific data. There is no way to +deallocate a private data key once it is allocated. +

+


+

+ +

st_key_getlimit()

+ +Returns the key limit. +

+

Syntax
+ +
+#include <st.h>
+
+int st_key_getlimit(void);
+
+

+

Parameters
+None. +

+

Returns
+The limit on the total number of keys per process. +

+

Description
+This function can be used to obtain the limit on the total number of keys +per process (see st_key_create()). +

+


+

+ +

st_thread_setspecific()

+ +Sets per-thread private data. +

+

Syntax
+ +
+#include <st.h>
+
+int st_thread_setspecific(int key, void *value);
+
+

+

Parameters
+st_thread_setspecific() has the following parameters:

+key

+This parameter represents a key with which thread-specific data is associated. +

+value

+The per-thread private data, or more likely, a pointer to the data which is +associated with key. +

+

Returns
+Upon successful completion, a value of 0 is returned. +Otherwise, a value of -1 is returned and errno is set +to indicate the error:

+ + + +
EINVALThe specified key is invalid.
+

+

Description
+This function associates a thread-specific value with key. +Different threads may bind different values to the same key.

+If the thread already has non-NULL private data associated with +key, and if the destructor function for that key is not +NULL, this destructor function will be called before setting the +new data value. +

+


+

+ +

st_thread_getspecific()

+ +Retrieves the per-thread private data for the current thread. +

+

Syntax
+ +
+#include <st.h>
+
+void *st_thread_getspecific(int key);
+
+

+

Parameters
+st_thread_getspecific() has the following parameters:

+key

+This parameter represents a key with which thread-specific data is associated. +

+

Returns
+The thread-specific data associated with key. If no data is +associated with key, then NULL is returned. +

+

Description
+This function returns the calling thread's value that is bound to the +specified key (see +st_thread_setspecific()). +

+


+

+ + +

Synchronization

+ +

+These functions operate on condition variables +and mutual exclusion locks (mutexes).

+Functions are provided to wait on a condition variable and to wake up +(signal) threads that are waiting on the condition variable. +

+

+
st_cond_new()
+
st_cond_destroy()
+
st_cond_wait()
+
st_cond_timedwait()
+
st_cond_signal()
+
st_cond_broadcast()
+

+

st_mutex_new()
+
st_mutex_destroy()
+
st_mutex_lock()
+
st_mutex_trylock()
+
st_mutex_unlock()
+
+

+


+

+ +

st_cond_new()

+ +Creates a new condition variable. +

+

Syntax
+ +
+#include <st.h>
+
+st_cond_t st_cond_new(void);
+
+

+

Parameters
+None. +

+

Returns
+Upon successful completion, a new condition variable identifier is returned. +Otherwise, NULL is returned and errno is set +to indicate the error. +

+

Description
+This function creates a new condition variable. +

+


+

+ +

st_cond_destroy()

+ +Destroys a condition variable. +

+

Syntax
+ +
+#include <st.h>
+
+int st_cond_destroy(st_cond_t cvar);
+
+

+

Parameters
+st_cond_destroy() has the following parameters:

+cvar

+An identifier of the condition variable object to be destroyed. +

+

Returns
+Upon successful completion, a value of 0 is returned. +Otherwise, a value of -1 is returned and errno is set +to indicate the error:

+ + + +
EBUSYThe condition variable is currently being +used by one or more threads.
+

+

Description
+This function destroys a condition variable. The caller is responsible for +ensuring that the condition variable is no longer in use. +

+


+

+ +

st_cond_wait()

+ +Waits on a condition. +

+

Syntax
+ +
+#include <st.h>
+
+int st_cond_wait(st_cond_t cvar);
+
+

+

Parameters
+st_cond_wait() has the following parameters:

+cvar

+The condition variable on which to wait. +

+

Returns
+Upon successful completion, a value of 0 is returned. +Otherwise, a value of -1 is returned and errno is set +to indicate the error:

+ + +
EINTRThe current thread was interrupted by +st_thread_interrupt().
+

+

Description
+This function is used to block on a condition variable. A return from this +function does not guarantee that the condition or event for which the caller +was waiting actually occurred. It is the responsibility of the caller +to recheck the condition wait predicate before proceeding.

+Note: The State Threads library scheduling guarantees that the +condition cannot change between the checking and blocking, therefore there +is no need for mutex protection. You must not call any +blocking functions between the condition checking and +the st_cond_wait() call. +

+


+

+ +

st_cond_timedwait()

+ +Waits on a condition. +

+

Syntax
+ +
+#include <st.h>
+
+int st_cond_timedwait(st_cond_t cvar, st_utime_t timeout);
+
+

+

Parameters
+st_cond_timedwait() has the following parameters:

+cvar

+The condition variable on which to wait.

+timeout

+If the number of microseconds specified by this parameter passes before the +waiting thread is signalled, an error is returned. This parameter is a +variable of type st_utime_t. Note that this +time value is a time delta; it is not an absolute time. +Also note that timeouts are measured since +the last context switch. +

+

Returns
+Upon successful completion, a value of 0 is returned. +Otherwise, a value of -1 is returned and errno is set +to indicate the error:

+ + + +
EINTRThe current thread was interrupted by +st_thread_interrupt().
ETIMEThe timeout occurred before the thread was +awakened by st_cond_signal() or +st_cond_broadcast().
+

+

Description
+This function works the same way as st_cond_wait(), +except that an error is returned if the number of microseconds specified by +timeout passes before the waiting thread is signalled. +

+


+

+ +

st_cond_signal()

+ +Unblocks a thread waiting on a condition variable. +

+

Syntax
+ +
+#include <st.h>
+
+int st_cond_signal(st_cond_t cvar);
+
+

+

Parameters
+st_cond_signal() has the following parameters:

+cvar

+The condition variable to signal. +

+

Returns
+Always zero. +

+

Description
+This function unblocks (signals) one of the threads that are blocked on +cvar at the time of the call. If no thread is waiting on the +condition variable, the signal operation is a no-op. +

+


+

+ +

st_cond_broadcast()

+ +Unblocks all threads waiting on a condition variable. +

+

Syntax
+ +
+#include <st.h>
+
+int st_cond_broadcast(st_cond_t cvar);
+
+

+

Parameters
+st_cond_broadcast() has the following parameters:

+cvar

+The condition variable to broadcast. +

+

Returns
+Always zero. +

+

Description
+This function unblocks all threads blocked on the specified condition +variable at the time of the call. If no threads are waiting, this operation +is a no-op. +

+


+

+ + +

st_mutex_new()

+ +Creates a new mutual exclusion lock (mutex). +

+

Syntax
+ +
+#include <st.h>
+
+st_mutex_t st_mutex_new(void);
+
+

+

Parameters
+None. +

+

Returns
+Upon successful completion, a new mutex identifier is returned. +Otherwise, NULL is returned and errno is set to +indicate the error. +

+

Description
+This function creates a new opaque mutual exclusion lock (see +st_mutex_t). +

+


+

+ +

st_mutex_destroy()

+ +Destroys a specified mutex object. +

+

Syntax
+ +
+#include <st.h>
+
+int st_mutex_destroy(st_mutex_t lock);
+
+

+

Parameters
+st_mutex_destroy() has the following parameters:

+lock

+An identifier of the mutex object to be destroyed. +

+

Returns
+Upon successful completion, a value of 0 is returned. +Otherwise, a value of -1 is returned and errno is set +to indicate the error:

+ + + +
EBUSYThe mutex is currently being used by +other threads.
+

+

Description
+This function destroys a mutex. The caller is responsible for ensuring +that the mutex is no longer in use. +

+


+

+ +

st_mutex_lock()

+ +Locks a specified mutex object. +

+

Syntax
+ +
+#include <st.h>
+
+int st_mutex_lock(st_mutex_t lock);
+
+

+

Parameters
+st_mutex_lock() has the following parameters:

+lock

+An identifier of the mutex object to be locked. +

+

Returns
+Upon successful completion, a value of 0 is returned. +Otherwise, a value of -1 is returned and errno is set +to indicate the error:

+ + + +
EDEADLKThe current thread already owns the mutex. +
EINTRThe current thread was interrupted by +st_thread_interrupt().
+

+

Description
+A thread that calls this function will block until it can gain exclusive +ownership of a mutex, and retains ownership until it calls +st_mutex_unlock(). +

+


+

+ +

st_mutex_trylock()

+ +Attempts to acquire a mutex. +

+

Syntax
+ +
+#include <st.h>
+
+int st_mutex_trylock(st_mutex_t lock);
+
+

+

Parameters
+st_mutex_trylock() has the following parameters:

+lock

+An identifier of the mutex object to be locked. +

+

Returns
+Upon successful completion, a value of 0 is returned. +Otherwise, a value of -1 is returned and errno is set +to indicate the error:

+ + +
EBUSYThe mutex is currently held by another +thread.
+

+

Description
+This function attempts to acquire a mutex. If the mutex object is locked +(by any thread, including the current thread), the call returns immediately +with an error. +

+


+

+ +

st_mutex_unlock()

+ +Releases a specified mutex object. +

+

Syntax
+ +
+#include <st.h>
+
+int st_mutex_unlock(st_mutex_t lock);
+
+

+

Parameters
+st_mutex_unlock() has the following parameters:

+lock

+An identifier of the mutex object to be unlocked. +

+

Returns
+Upon successful completion, a value of 0 is returned. +Otherwise, a value of -1 is returned and errno is set +to indicate the error:

+ + +
EPERMThe current thread does not own the mutex. +
+

+

Description
+This function releases a specified mutex object previously acquired by +st_mutex_lock() or +st_mutex_trylock(). Only the thread that locked +a mutex should unlock it. +

+


+

+ + +

Timing

+ +

+

+
st_utime()
+
st_set_utime_function()
+
st_timecache_set()
+
st_time()
+
+

+


+

+ +

st_utime()

+ +Returns current high-resolution time. +

+

Syntax
+ +
+#include <st.h>
+
+st_utime_t st_utime(void);
+
+

+

Parameters
+None. +

+

Returns
+Current high-resolution time value of type +st_utime_t. +

+

Description
+This function returns the current high-resolution time. Time is +expressed as microseconds since some arbitrary time in the past. It is +not correlated in any way to the time of day. See also st_utime_t and st_time(). +

+


+

+ +

st_set_utime_function()

+ +Set high-resolution time function. +

+

Syntax
+ +
+#include <st.h>
+
+int st_set_utime_function(st_utime_t (*func)(void));
+
+

+

Parameters
+st_set_utime_function() has the following parameters:

+func

+This function will be called to get high-resolution time instead of the +default st_utime() function. It must return +number of microseconds since some arbitrary time in the past. +

+

Returns
+Upon successful completion, a value of 0 is returned. +Otherwise, a value of -1 is returned and errno is set +to EINVAL to indicate the error. +

+

Description
+This function may be called to replace the default implementation of the +st_utime() function. It must be called before the ST +library has been initialized (see st_init()). +The user-provided function func will be invoked whenever +st_utime() is called to obtain current high-resolution time. +Replacing default implementation may be useful, for example, for taking +advantage of high performance CPU cycle counters. +

+


+

+ +

st_timecache_set()

+ +Turns the time caching on or off. +

+

Syntax
+ +
+#include <st.h>
+
+int st_timecache_set(int on);
+
+

+

Parameters
+st_timecache_set() has the following parameters:

+on

+If this parameter has a non-zero value, the time caching is turned on +(enabled). Otherwise, the time caching is turned off (disabled). +By default time caching is disabled. +

+

Returns
+The previous state of time caching (a value of 0 if it was off and +a value of 1 otherwise). +

+

Description
+The State Threads library has the ability to "cache" the time value that is +reported by the time(2) system call. If the time caching is enabled +by calling this function with a non-zero argument, then the result value +of time(2) will be stored and updated at most once per second. The +cached time can be retrieved by st_time(). +By default time caching is disabled. +You may enable or disable time caching at any time but generally +you enable it once (if desired) during program initialization.

+Note: There are some pathological cases (e.g., very heavy loads during +application benchmarking) when a single thread runs for a long time without +giving up control and the cached time value is not updated properly. If you +always need "real-time" time values, don't enable the time caching. +

+


+

+ +

st_time()

+ +Returns the value of time in seconds since 00:00:00 UTC, January 1, 1970. +

+

Syntax
+ +
+#include <st.h>
+
+time_t st_time(void);
+
+

+

Parameters
+None. +

+

Returns
+The value of time in seconds since 00:00:00 UTC, January 1, 1970 as reported +by the time(2) system call. +

+

Description
+If the time caching was enabled by +st_timecache_set(), then this function returns +the cached result. Otherwise, it just calls time(2). +

+


+

+ + +

I/O Functions

+ +

+Most State Threads library I/O functions look like corresponding C library +functions with two exceptions: +

    +
  • They operate on file descriptor objects of type +st_netfd_t.
  • +
  • They take an additional argument of type +st_utime_t which represents an inactivity +timeout: if no I/O is possible during this amount of time, I/O functions +return an error code and set errno to ETIME. + +The boundary values ST_UTIME_NO_WAIT (0) and +ST_UTIME_NO_TIMEOUT (-1) for this argument indicate +that the thread should wait no time (function returns immediately) or +wait forever (never time out), respectively. + +Note that timeouts are measured since the +last context switch. +
  • +
+

+

+
st_netfd_open()
+
st_netfd_open_socket()
+
st_netfd_free()
+
st_netfd_close()
+
st_netfd_fileno()
+
st_netfd_setspecific()
+
st_netfd_getspecific()
+
st_netfd_serialize_accept()
+
st_netfd_poll()
+

+

st_accept()
+
st_connect()
+
st_read()
+
st_read_fully()
+
st_read_resid()
+
st_readv()
+
st_read_resid()
+
st_write()
+
st_write_resid()
+
st_writev()
+
st_writev_resid()
+
st_recvfrom()
+
st_sendto()
+
st_recvmsg()
+
st_sendmsg()
+
st_open()
+
st_poll()
+
+

+


+

+ +

st_netfd_open()

+ +Creates a new file descriptor object. +

+

Syntax
+ +
+#include <st.h>
+
+st_netfd_t st_netfd_open(int osfd);
+
+

+

Parameters
+st_netfd_open() has the following parameters:

+osfd

+ +Any open OS file descriptor; can be obtained from calls to +functions including, but not restricted to, pipe(2), socket(3), +socketpair(3), fcntl(2), dup(2), etc. + + +

+

Returns
+Upon successful completion, a new file descriptor object identifier is +returned. Otherwise, NULL is returned and errno is set +to indicate the error. +

+

Description
+This function creates a new file descriptor object of type +st_netfd_t.

+ +Note: Among other things, this function sets a non-blocking +flag on the underlying OS file descriptor. You should not modify this +flag directly. Also, once an st_netfd_t +has been created with a given file descriptor, you should avoid +passing that descriptor to normal I/O or stdio functions. Since the +O_NONBLOCK flag is shared across dup(2), this applies to +dup()'ed file descriptors as well - for instance, if you pass +standard output or standard input to st_netfd_open(), then +you should use st_write() instead of write +or fprintf when writing to standard error as well - since all +three descriptors could point to the same terminal. If necessary, you +can still use write directly if you remember to check +errno for EAGAIN, but fprintf and other +stdio functions should be avoided completely because, at least on +Linux, the stdio library cannot be made to work reliably with +non-blocking files. (This only applies to file descriptors which are +passed to st_netfd_open() or st_netfd_open_socket(), or which are +related to such descriptors through dup(); other file +descriptors are untouched by State Threads.) +

+


+

+ +

st_netfd_open_socket()

+ +Creates a new file descriptor object from a socket. +

+

Syntax
+ +
+#include <st.h>
+
+st_netfd_t st_netfd_open_socket(int osfd);
+
+

+

Parameters
+st_netfd_open_socket() has the following parameters:

+osfd

+An open OS file descriptor which is a socket initially obtained from a +socket(3) or socketpair(3) call. +

+

Returns
+Upon successful completion, a new file descriptor object identifier is +returned. Otherwise, NULL is returned and errno is set +to indicate the error. +

+

Description
+This function creates a new file descriptor object of type +st_netfd_t which represents an open end +point of network communication.

+Unlike the st_netfd_open() function which may be used +on OS file descriptors of any origin, st_netfd_open_socket() must +be used only on sockets. It is slightly more efficient than +st_netfd_open().

+Note: Among other things, this function sets a non-blocking flag +on the underlying OS socket. You should not modify this flag directly. +See st_netfd_open(). +

+


+

+ +

st_netfd_free()

+ +Frees a file descriptor object without closing the underlying OS file +descriptor. +

+

Syntax
+ +
+#include <st.h>
+
+void st_netfd_free(st_netfd_t fd);
+
+

+

Parameters
+st_netfd_free() has the following parameters:

+fd

+A file descriptor object identifier (see +st_netfd_t). +

+

Returns
+Nothing. +

+

Description
+This function frees the memory and other resources identified by the +fd parameter without closing the underlying OS file descriptor. +Any non-NULL descriptor-specific data is destroyed by invoking +the specified destructor function (see st_netfd_setspecific()).

A thread should +not free file descriptor objects that are in use by other threads +because it may lead to unpredictable results (e.g., a freed file +descriptor may be reused without other threads knowing that). +

+


+

+ +

st_netfd_close()

+ +Closes a file descriptor. +

+

Syntax
+ +
+#include <st.h>
+
+int st_netfd_close(st_netfd_t fd);
+
+

+

Parameters
+st_netfd_close() has the following parameters:

+fd

+A file descriptor object identifier (see +st_netfd_t). +

+

Returns
+Upon successful completion, a value of 0 is returned. +Otherwise, a value of -1 is returned and errno is set +to indicate the error. +

+

Description
+This function closes the underlying OS file descriptor, frees the memory and +other resources identified by the fd parameter. Any non-NULL +descriptor-specific data is destroyed by invoking the specified destructor +function (see st_netfd_setspecific()).

+A thread should not close file descriptor objects that are in use by other +threads because it may lead to unpredictable results (e.g., a closed +file descriptor may be reused without other threads knowing that). +

+


+

+ +

st_netfd_fileno()

+ +Returns an underlying OS file descriptor. +

+

Syntax
+ +
+#include <st.h>
+
+int st_netfd_fileno(st_netfd_t fd);
+
+

+

Parameters
+st_netfd_fileno() has the following parameters:

+fd

+A file descriptor object identifier (see +st_netfd_t). +

+

Returns
+An underlying OS file descriptor. +

+

Description
+This function returns the integer OS file descriptor associated with the named +file descriptor object. +

+


+

+ +

st_netfd_setspecific()

+ +Sets per-descriptor private data. +

+

Syntax
+ +
+#include <st.h>
+
+void st_netfd_setspecific(st_netfd_t fd, void *value,
+                          void (*destructor)(void *));
+
+

+

Parameters
+st_netfd_setspecific() has the following parameters:

+fd

+A valid file descriptor object identifier (see +st_netfd_t). +

+value

+The per-descriptor private data, or more likely, a pointer to the data which +is being associated with the named file descriptor object. +

+destructor

+Specifies an optional destructor function for the private data associated +with fd. This function can be specified as NULL. +If value is not NULL, then this destructor function will +be called with value as an argument upon freeing the file descriptor +object (see st_netfd_free() and +st_netfd_close()). +

+

Returns
+Nothing. +

+

Description
+This function allows to associate any data with the specified file +descriptor object (network connection). If a non-NULL destructor +function is registered, it will be called at one of two times, as long as +the associated data is not NULL: +
    +
  • when private data is replaced by calling +st_netfd_setspecific() again +
  • upon freeing the file descriptor object (see +st_netfd_free() and +st_netfd_close()) +
+

+


+

+ +

st_netfd_getspecific()

+ +Retrieves the per-descriptor private data. +

+

Syntax
+ +
+#include <st.h>
+
+void *st_netfd_getspecific(st_netfd_t fd);
+
+

+

Parameters
+st_netfd_getspecific() has the following parameters:

+fd

+A valid file descriptor object identifier (see +st_netfd_t). +

+

Returns
+The data associated with the named file descriptor object. If no data is +associated with fd, then NULL is returned. +

+

Description
+This function allows to retrieve the data that was associated with the +specified file descriptor object (see +st_netfd_setspecific()). +

+


+

+ +

st_netfd_serialize_accept()

+ +Serializes all subsequent accept(3) calls on a specified file +descriptor object. +

+

Syntax
+ +
+#include <st.h>
+
+int st_netfd_serialize_accept(st_netfd_t fd);
+
+

+

Parameters
+st_netfd_serialize_accept() has the following parameters:

+fd

+A file descriptor object identifier (see +st_netfd_t) which has been successfully created +from a valid listening socket by st_netfd_open() or +st_netfd_open_socket(). +

+

Returns
+Upon successful completion, a value of 0 is returned. +Otherwise, a value of -1 is returned and errno is set +to indicate the error. +

+

Description
+On some platforms (e.g., Solaris 2.5 and possibly other SVR4 implementations) +accept(3) calls from different processes on +the same listening socket (see bind(3), listen(3)) must be +serialized. This function causes all subsequent accept(3) calls +made by st_accept() on the specified file descriptor +object to be serialized. +

+st_netfd_serialize_accept() must be called before +creating multiple server processes via fork(2). If the application +does not create multiple processes to accept network connections on +the same listening socket, there is no need to call this function. +

+Deciding whether or not to serialize accepts is tricky. On some +platforms (IRIX, Linux) it's not needed at all and +st_netfd_serialize_accept() is a no-op. On other platforms +it depends on the version of the OS (Solaris 2.6 doesn't need it but +earlier versions do). Serializing accepts does incur a slight +performance penalty so you want to enable it only if necessary. Read +your system's manual pages for accept(2) and select(2) +to see if accept serialization is necessary on your system. +

+st_netfd_serialize_accept() allocates resources that are +freed upon freeing of the specified file descriptor object (see +st_netfd_free() and +st_netfd_close()). +

+


+

+ +

st_netfd_poll()

+ +Waits for I/O on a single file descriptor object. +

+

Syntax
+ +
+#include <st.h>
+
+int st_netfd_poll(st_netfd_t fd, int how, st_utime_t timeout);
+
+

+

Parameters
+st_netfd_poll() has the following parameters:

+fd

+A file descriptor object identifier (see +st_netfd_t). +

+how

+Specifies I/O events of interest. This parameter can be constructed by +OR-ing any combination of the following event flags which are defined +in the poll.h header file:

+ + + + + +
POLLINfd is readable.
POLLOUTfd is is writable.
POLLPRIfd has an exception condition.
+

+timeout

+Amount of time in microseconds the call will block waiting for I/O +to become ready. This parameter is a variable of type +st_utime_t. If this time expires without any +I/O becoming ready, st_netfd_poll() returns an error and sets +errno to ETIME. +Note that timeouts are measured since the +last context switch. +

+

Returns
+If the named file descriptor object is ready for I/O within the specified +amount of time, a value of 0 is returned. Otherwise, a value +of -1 is returned and errno is set to indicate the error: +

+ + + + +
EBADFThe underlying OS file descriptor is invalid. +
EINTRThe current thread was interrupted by +st_thread_interrupt().
ETIMEThe timeout occurred without any I/O +becoming ready.
+

+

Description
+This function returns as soon as I/O is ready on the named file +descriptor object or the specified amount of time expires. The +how parameter should be set to the I/O events (readable, +writable, exception, or some combination) that the caller is interested +in. If the value of timeout is ST_UTIME_NO_TIMEOUT +(-1), this function blocks until a requested I/O event occurs +or until the call is interrupted by st_thread_interrupt().

+Despite having an interface like poll(2), this function uses +the same event notification mechanism as the rest of the library. For +instance if an alternative event nofication mechanism was set using st_set_eventsys(), this function uses that +mechanism to check for events.

+Note: if kqueue(2) is used as an alternative event +notification mechanism (see st_set_eventsys()), the POLLPRI +event flag is not supported and st_netfd_poll() will return an error +if it's set (errno will be set to EINVAL). +

+


+

+ +

st_accept()

+ +Accepts a connection on a specified file descriptor object. +

+

Syntax
+ +
+#include <st.h>
+
+st_netfd_t st_accept(st_netfd_t fd, struct sockaddr *addr, int *addrlen,
+                     st_utime_t timeout);
+
+

+

Parameters
+st_accept() has the following parameters:

+fd

+A file descriptor object identifier (see +st_netfd_t) representing the rendezvous socket +on which the caller is willing to accept new connections. This object has been +created from a valid listening socket by +st_netfd_open() or +st_netfd_open_socket().

+addr

+If this value is non-zero, it is a result parameter that is filled +in with the address of the connecting entity, as known to the communications +layer (see accept(3)).

+addrlen

+This parameter should initially contain the amount of space pointed to by +addr; on return it will contain the actual length (in bytes) of the +address returned (see accept(3)).

+timeout

+A value of type st_utime_t specifying the time +limit in microseconds for completion of the accept operation. +Note that timeouts are measured since the +last context switch. +

+

Returns
+Upon successful completion, a new file descriptor object identifier +representing the newly accepted connection is returned. Otherwise, +NULL is returned and errno is set to indicate the error. +Possible errno values are the same as set by the accept(3) +call with two exceptions:

+ + + +
EINTRThe current thread was interrupted by +st_thread_interrupt().
ETIMEThe timeout occurred and no pending +connection was accepted.
+

+

Description
+This function accepts the first connection from the queue of pending +connections and creates a new file descriptor object for the newly +accepted connection. The rendezvous socket can still be used to accept +more connections.

+st_accept() blocks the calling thread until either a new connection +is successfully accepted or an error occurs. If no pending connection can +be accepted before the time limit, this function returns NULL +and sets errno to ETIME. +

+


+

+ +

st_connect()

+ +Initiates a connection on a specified file descriptor object. +

+

Syntax
+ +
+#include <st.h>
+
+int st_connect(st_netfd_t fd, struct sockaddr *addr, int addrlen,
+               st_utime_t timeout);
+
+

+

Parameters
+st_connect() has the following parameters:

+fd

+A file descriptor object identifier (see +st_netfd_t) representing a socket.

+addr

+A pointer to the address of the peer to which the socket is to be connected. +

+addrlen

+This parameter specifies the amount of space pointed to by addr. +

+timeout

+A value of type st_utime_t specifying the time +limit in microseconds for completion of the connect operation. +Note that timeouts are measured since the +last context switch. +

+

Returns
+Upon successful completion, a value of 0 is returned. +Otherwise, a value of -1 is returned and errno is set +to indicate the error. Possible errno values are the same as set +by the connect(3) call with two exceptions:

+ + + +
EINTRThe current thread was interrupted by +st_thread_interrupt().
ETIMEThe timeout occurred and connection setup +was not completed.
+

+

Description
+This function is usually invoked on a file descriptor object representing +a TCP socket. Upon completion it establishes a TCP connection to the peer. +If the underlying OS socket is not bound, it will be bound to an arbitrary +local address (see connect(3)).

+st_connect() blocks the calling thread until either the connection +is successfully established or an error occurs. If the connection setup +cannot complete before the specified time limit, this function fails with +errno set to ETIME. +

+


+

+ +

st_read()

+ +Reads data from a specified file descriptor object. +

+

Syntax
+ +
+#include <st.h>
+
+ssize_t st_read(st_netfd_t fd, void *buf, size_t nbyte, st_utime_t timeout);
+
+

+

Parameters
+st_read() has the following parameters:

+fd

+A file descriptor object identifier (see +st_netfd_t).

+buf

+A pointer to a buffer to hold the data read in. On output the buffer +contains the data.

+nbyte

+The size of buf in bytes.

+timeout

+A value of type st_utime_t specifying the time +limit in microseconds for completion of the read operation. +Note that timeouts are measured since the +last context switch. +

+

Returns
+On success a non-negative integer indicating the number of bytes actually +read is returned (a value of 0 means the network connection is +closed or end of file is reached). Otherwise, a value of -1 is +returned and errno is set to indicate the error. +Possible errno values are the same as set by the read(2) +call with two exceptions:

+ + + +
EINTRThe current thread was interrupted by +st_thread_interrupt().
ETIMEThe timeout occurred and no data was read. +
+

+

Description
+This function blocks the calling thread until it encounters an end-of-stream +indication, some positive number of bytes (but no more than nbyte +bytes) are read in, a timeout occurs, or an error occurs. +

+


+

+ +

st_read_fully()

+ +Reads the specified amount of data in full from a file descriptor object. +

+

Syntax
+ +
+#include <st.h>
+
+ssize_t st_read_fully(st_netfd_t fd, void *buf, size_t nbyte,
+                      st_utime_t timeout);
+
+

+

Parameters
+st_read_fully() has the following parameters:

+fd

+A file descriptor object identifier (see +st_netfd_t).

+buf

+A pointer to a buffer to hold the data read in. On output the buffer +contains the data.

+nbyte

+The amount of data to be read in full (in bytes). It must not exceed the +size of buf.

+timeout

+A value of type st_utime_t specifying the +inactivity timeout (in microseconds). +Note that timeouts are measured since the +last context switch. +

+

Returns
+On success a non-negative integer indicating the number of bytes actually +read is returned (a value less than nbyte means the network +connection is closed or end of file is reached). Otherwise, a value of +-1 is returned and errno is set to indicate the error. +Possible errno values are the same as set by the read(2) +call with two exceptions:

+ + + +
EINTRThe current thread was interrupted by +st_thread_interrupt().
ETIMEThe timeout occurred. +
+

+

Description
+This function blocks the calling thread until the specified amount of data +is read in full, it encounters an end-of-stream indication, a timeout occurs, +or an error occurs. +

+


+

+ +

st_read_resid()

+ +Reads the specified amount of data in full from a file descriptor object. +

+

Syntax
+ +
+#include <st.h>
+
+int st_read_resid(st_netfd_t fd, void *buf, size_t *resid,
+		  st_utime_t timeout);
+
+

+

Parameters
+st_read_resid() has the following parameters:

+fd

+A file descriptor object identifier (see +st_netfd_t).

+buf

+A pointer to a buffer to hold the data read in. On output the buffer +contains the data.

+resid

+A pointer to a number of bytes. +On entry, the amount of data to be read in full. +It must not exceed the size of buf. +On return, the amount of data remaining to be read. +(A non-zero returned value means some but not all of the data was read.)

+timeout

+A value of type st_utime_t specifying the +inactivity timeout (in microseconds). +Note that timeouts are measured since the +last context switch. +

+

Returns
+On success, zero is returned. *resid may be zero, indicating +a complete read, or non-zero, indicating the network +connection is closed or end of file is reached. +

+Otherwise, a value of -1 is returned, *resid is non-zero, +and errno is set to indicate the error. +Possible errno values are the same as set by the read(2) +call with two exceptions:

+ + + +
EINTRThe current thread was interrupted by +st_thread_interrupt().
ETIMEThe timeout occurred. +
+

+

Description
+This function blocks the calling thread until the specified amount of data +is read in full, it encounters an end-of-stream indication, a timeout occurs, +or an error occurs. It differs from st_read_fully() only in that +it allows the caller to know how many bytes were transferred before an error +occurred. +

+


+

+ +

st_readv()

+ +Reads data from a specified file descriptor object into multiple buffers. +

+

Syntax
+ +
+#include <st.h>
+
+ssize_t st_readv(st_netfd_t fd, const struct iovec *iov, int iov_size,
+		 st_utime_t timeout);
+
+

+

Parameters
+st_readv() has the following parameters:

+fd

+A file descriptor object identifier (see +st_netfd_t).

+iov

+An array of iovec structures that identify the buffers for holding +the data read in. +On return the buffers contain the data.

+iov_size

+The number of iovec structures in the iov array.

+timeout

+A value of type st_utime_t specifying the time +limit in microseconds for completion of the read operation. +Note that timeouts are measured since the +last context switch. +

+

Returns
+On success a non-negative integer indicating the number of bytes actually +read is returned (a value of 0 means the network connection is +closed or end of file is reached). Otherwise, a value of -1 is +returned and errno is set to indicate the error. +Possible errno values are the same as set by the readv(2) +call with two exceptions:

+ + + +
EINTRThe current thread was interrupted by +st_thread_interrupt().
ETIMEThe timeout occurred and no data was read. +
+

+

Description
+This function blocks the calling thread until it encounters an end-of-stream +indication, some positive number of bytes (but no more than fit in the buffers) +are read in, a timeout occurs, or an error occurs. +

+


+

+ +

st_readv_resid()

+ +Reads the specified amount of data in full from a file descriptor object +into multiple buffers. +

+

Syntax
+ +
+#include <st.h>
+
+int st_readv_resid(st_netfd_t fd, struct iovec **iov, int *iov_size,
+		   st_utime_t timeout);
+
+

+

Parameters
+st_readv_resid() has the following parameters:

+fd

+A file descriptor object identifier (see +st_netfd_t).

+iov

+A pointer to an array of iovec structures. +On entry, the iovecs identify the buffers for holding the data read in. +On return, the incomplete iovecs. +This function modifies both the pointer and the array to which it points.

+iov_size

+A pointer to a number of iovec structures. +On entry, the number of iovec structures pointed to by *iov. +On return, the number of incomplete or unused iovec structures. +(A non-zero returned value means some but not all of the data was read.)

+timeout

+A value of type st_utime_t specifying the +inactivity timeout (in microseconds). +Note that timeouts are measured since the +last context switch. +

+

Returns
+On success, zero is returned. *iov_size may be zero, indicating +a complete read, or non-zero, indicating the network connection is +closed or end of file is reached. *iov points to the first +iovec after the end of the original array on a complete read, or to the +first incomplete iovec on an incomplete read. +

+Otherwise, a value of -1 is returned, *iov_size is non-zero, +and errno is set to indicate the error. *iov points to the +first unused iovec. +Possible errno values are the same as set by the readv(2) +call with two exceptions:

+ + + +
EINTRThe current thread was interrupted by +st_thread_interrupt().
ETIMEThe timeout occurred. +
+

All of the iovecs before *iov are modified such that +iov_base points to the end of the original buffer and +iov_len is zero. +

+

Description
+This function blocks the calling thread until the specified amount of data +is read in full, it encounters an end-of-stream indication, a timeout occurs, +or an error occurs. Like st_read_resid() it blocks the thread until +all of the requested data is read or an error occurs. Use +st_readv() to read up to the requested amount of data. +

+


+

+ +

st_write()

+ +Writes a buffer of data to a specified file descriptor object. +

+

Syntax
+ +
+#include <st.h>
+
+ssize_t st_write(st_netfd_t fd, const void *buf, size_t nbyte,
+                 st_utime_t timeout);
+
+

+

Parameters
+st_write() has the following parameters:

+fd

+A file descriptor object identifier (see +st_netfd_t).

+buf

+A pointer to the buffer holding the data to be written.

+nbyte

+The amount of data in bytes to be written from the buffer.

+timeout

+A value of type st_utime_t specifying the +inactivity timeout (in microseconds). +Note that timeouts are measured since the +last context switch. +

+

Returns
+On success a non-negative integer equal to nbyte is returned. +Otherwise, a value of -1 is returned and errno is set +to indicate the error. Possible errno values are the same as set +by the write(2) call with two exceptions:

+ + + +
EINTRThe current thread was interrupted by +st_thread_interrupt().
ETIMEThe timeout occurred. +
+

+

Description
+This function blocks the calling thread until all the data is written, +a timeout occurs, or the write operation fails. The return value is equal to +either nbyte (on success) or -1 (on failure). Note that if +st_write() returns -1, some data (less than nbyte +bytes) may have been written before an error occurred. +

+


+

+ +

st_write_resid()

+ +Writes a buffer of data to a specified file descriptor object. +

+

Syntax
+ +
+#include <st.h>
+
+int st_write_resid(st_netfd_t fd, const void *buf, size_t *resid,
+                   st_utime_t timeout);
+
+

+

Parameters
+st_write_resid() has the following parameters:

+fd

+A file descriptor object identifier (see +st_netfd_t).

+buf

+A pointer to the buffer holding the data to be written.

+resid

+A pointer to a number of bytes. +On entry, the amount of data to be written from the buffer. +On return, the amount of data remaining to be written. +(A non-zero returned value means some but not all of the data was written.)

+timeout

+A value of type st_utime_t specifying the +inactivity timeout (in microseconds). +Note that timeouts are measured since the +last context switch. +

+

Returns
+On success, zero is returned and *resid is zero. +Otherwise, a value of -1 is returned, *resid is non-zero, +and errno is set +to indicate the error. Possible errno values are the same as set +by the write(2) call with two exceptions:

+ + + +
EINTRThe current thread was interrupted by +st_thread_interrupt().
ETIMEThe timeout occurred. +
+

+

Description
+This function blocks the calling thread until all the data is written, +a timeout occurs, or the write operation fails. It differs from +st_write() only in that it allows the caller to know how many bytes +were transferred before an error occurred. +

+


+

+ +

st_writev()

+ +Writes data to a specified file descriptor object from multiple buffers. +

+

Syntax
+ +
+#include <st.h>
+
+ssize_t st_writev(st_netfd_t fd, const struct iovec *iov, int iov_size,
+                  st_utime_t timeout);
+
+

+

Parameters
+st_writev() has the following parameters:

+fd

+A file descriptor object identifier (see +st_netfd_t).

+iov

+An array of iovec structures that describe the buffers to write +from (see writev(2)).

+iov_size

+Number of iovec structures in the iov array.

+timeout

+A value of type st_utime_t specifying the +inactivity timeout (in microseconds). +Note that timeouts are measured since the +last context switch. +

+

Returns
+On success a non-negative integer equal to the sum of all the buffer lengths +is returned. Otherwise, a value of -1 is returned and errno +is set to indicate the error. Possible errno values are the same as +set by the writev(2) call with two exceptions:

+ + + +
EINTRThe current thread was interrupted by +st_thread_interrupt().
ETIMEThe timeout occurred. +
+

+

Description
+This function blocks the calling thread until all the data is written, +a timeout occurs, or the write operation fails. The return value is equal to +either the sum of all the buffer lengths (on success) or -1 (on +failure). Note that if st_writev() returns -1, part of the +data may have been written before an error occurred. +

+


+

+ +

st_writev_resid()

+ +Writes multiple buffers of data to a specified file descriptor object. +

+

Syntax
+ +
+#include <st.h>
+
+int st_writev_resid(st_netfd_t fd, struct iovec **iov, int *iov_size,
+		    st_utime_t timeout);
+
+

+

Parameters
+st_writev_resid() has the following parameters:

+fd

+A file descriptor object identifier (see +st_netfd_t).

+iov

+A pointer to an array of iovec structures. +On entry, the iovecs identify the buffers holding the data to write. +On return, the incomplete iovecs. +This function modifies both the pointer and the array to which it points.

+iov_size

+A pointer to a number of iovec structures. +On entry, the number of iovec structures pointed to by *iov. +On return, the number of incomplete or unused iovec structures. +(A non-zero returned value means some but not all of the data was written.)

+timeout

+A value of type st_utime_t specifying the +inactivity timeout (in microseconds). +Note that timeouts are measured since the +last context switch. +

+

Returns
+On success, zero is returned, *iov_size is zero, and *iov +points to the first iovec after the end of the original array. +Otherwise, a value of -1 is returned, *iov_size is non-zero, +*iov points to the first incomplete iovec, and errno is set +to indicate the error. Possible errno values are the same as set +by the writev(2) call with two exceptions:

+ + + +
EINTRThe current thread was interrupted by +st_thread_interrupt().
ETIMEThe timeout occurred. +
+

+All of the iovecs before *iov are modified such that +iov_base points to the end of the original buffer and +iov_len is zero. +

+

Description
+This function blocks the calling thread until all the data is written, +a timeout occurs, or the write operation fails. It differs from +st_writev() only in that it allows the caller to know how many bytes +were transferred before an error occurred. +

+


+

+ +

st_recvfrom()

+ +Receives bytes from a file descriptor object and stores the sending peer's +address. +

+

Syntax
+ +
+#include <st.h>
+
+int st_recvfrom(st_netfd_t fd, void *buf, int len, struct sockaddr *from,
+                int *fromlen, st_utime_t timeout);
+
+

+

Parameters
+st_recvfrom() has the following parameters:

+fd

+A file descriptor object identifier (see +st_netfd_t) representing a UDP socket.

+buf

+A pointer to a buffer to hold the data received.

+len

+The size of buf in bytes.

+from

+If this parameter is not a NULL pointer, the source address of the +message is filled in (see recvfrom(3)).

+fromlen

+This is a value-result parameter, initialized to the size of the buffer +associated with from, and modified on return to indicate the actual +size of the address stored there.

+timeout

+A value of type st_utime_t specifying the time +limit in microseconds for completion of the receive operation. +Note that timeouts are measured since the +last context switch. +

+

Returns
+On success a non-negative integer indicating the length of the received +message in bytes is returned. Otherwise, a value of -1 is returned +and errno is set to indicate the error. Possible errno +values are the same as set by the recvfrom(3) call with two +exceptions:

+ + + +
EINTRThe current thread was interrupted by +st_thread_interrupt().
ETIMEThe timeout occurred and no data was received. +
+

+

Description
+This function receives up to a specified number of bytes from the specified +file descriptor object representing a UDP socket.

+st_recvfrom() blocks the calling thread until one or more bytes are +transferred, a timeout has occurred, or there is an error. No more than +len bytes will be transferred. +

+


+

+ +

st_sendto()

+ +Sends bytes to a specified destination. +

+

Syntax
+ +
+#include <st.h>
+
+int st_sendto(st_netfd_t fd, const void *msg, int len, struct sockaddr *to,
+              int tolen, st_utime_t timeout);
+
+

+

Parameters
+st_sendto() has the following parameters:

+fd

+A file descriptor object identifier (see +st_netfd_t) representing a UDP socket.

+msg

+A pointer to a buffer containing the message to be sent.

+len

+The length of the message to be sent (in bytes).

+to

+A pointer to the address of the destination (see sendto(3)).

+tolen

+This parameter specifies the size of the destination address.

+timeout

+A value of type st_utime_t specifying the time +limit in microseconds for completion of the send operation. +Note that timeouts are measured since the +last context switch. +

+

Returns
+On success a non-negative integer indicating the number of bytes sent is +returned. Otherwise, a value of -1 is returned and errno is +set to indicate the error. Possible errno values are the same as +set by the sendto(3) call with two exceptions:

+ + + +
EINTRThe current thread was interrupted by +st_thread_interrupt().
ETIMEThe timeout occurred and no data was sent. +
+

+

Description
+This function sends a specified number of bytes from a file descriptor +object representing a UDP socket to the specified destination address. +If no buffer space is available at the underlying OS socket to hold the +message to be transmitted, then st_sendto() blocks the calling +thread until the space becomes available, a timeout occurs, or an error +occurs. +

+


+

+ +

st_recvmsg()

+ +Receives a message from a file descriptor object. +

+

Syntax
+ +
+#include <st.h>
+
+int st_recvmsg(st_netfd_t fd, struct msghdr *msg, int flags,
+               st_utime_t timeout);
+
+

+

Parameters
+st_recvmsg() has the following parameters:

+fd

+A file descriptor object identifier (see +st_netfd_t) representing a UDP socket.

+msg

+A pointer to a msghdr structure to describe the data received.

+flags

+Control flags for recvmsg(3).

+timeout

+A value of type st_utime_t specifying the time +limit in microseconds for completion of the receive operation. +Note that timeouts are measured since the +last context switch. +

+

Returns
+On success a non-negative integer indicating the number of bytes received +is returned. Otherwise, a value of -1 is returned +and errno is set to indicate the error. Possible errno +values are the same as set by the recvmsg(3) call with two +exceptions:

+ + + +
EINTRThe current thread was interrupted by +st_thread_interrupt().
ETIMEThe timeout occurred and no data was received. +
+

+

Description
+This function receives bytes from the specified file descriptor object +representing a UDP socket. The operation is controlled by the in/out +msg parameter.

+st_recvmsg() blocks the calling thread until one or more bytes are +transferred, a timeout has occurred, or there is an error. +

+


+

+ +

st_sendmsg()

+ +Sends a message to a file descriptor object. +

+

Syntax
+ +
+#include <st.h>
+
+int st_sendmsg(st_netfd_t fd, const struct msghdr *msg, int flags,
+               st_utime_t timeout);
+
+

+

Parameters
+st_sendmsg() has the following parameters:

+fd

+A file descriptor object identifier (see +st_netfd_t) representing a UDP socket.

+msg

+A pointer to a msghdr structure describing the message to be sent.

+flags

+Control flags for sendmsg(3).

+timeout

+A value of type st_utime_t specifying the time +limit in microseconds for completion of the send operation. +Note that timeouts are measured since the +last context switch. +

+

Returns
+On success a non-negative integer indicating the number of bytes sent is +returned. Otherwise, a value of -1 is returned and errno is +set to indicate the error. Possible errno values are the same as +set by the sendmsg(3) call with two exceptions:

+ + + +
EINTRThe current thread was interrupted by +st_thread_interrupt().
ETIMEThe timeout occurred and no data was sent. +
+

+

Description
+This function sends bytes to a file descriptor object representing a UDP +socket. The operation is controlled by the msg parameter. +If no buffer space is available at the underlying OS socket to hold the +message to be transmitted, then st_sendmsg() blocks the calling +thread until the space becomes available, a timeout occurs, or an error +occurs. +

+


+

+ +

st_open()

+ +Opens a file for reading, writing, or both. +

+

Syntax
+ +
+#include <st.h>
+
+st_netfd_t st_open(const char *path, int oflags, mode_t mode);
+
+

+

Parameters
+st_open() has the following parameters:

+path

+The pathname of the file to be opened.

+oflags

+File status flags. These are the same flags that are used by the +open(2) system call.

+mode

+Access permission bits of the file mode, if the file is created when +O_CREAT is set in oflags (see open(2)). +

+

Returns
+Upon successful completion, a new file descriptor object identifier is +returned. Otherwise, NULL is returned and errno is set +to indicate the error. +

+

Description
+This function creates a new file descriptor object of type +st_netfd_t for the file with the pathname +path. This object can be freed by +st_netfd_free() or +st_netfd_close().

+The primary purpose of this function is to open FIFOs (named pipes) or +other special files in order to create an end point of communication. +However, it can be used on regular files as well.

+Among other things, this function always sets a non-blocking flag on the +underlying OS file descriptor, so there is no need to include that flag in +oflags. +

+


+

+ +

st_poll()

+ +Detects when I/O is ready for a set of OS file descriptors. +

+

Syntax
+ +
+#include <st.h>
+
+int st_poll(struct pollfd *pds, int npds, st_utime_t timeout);
+
+

+

Parameters
+st_poll() has the following parameters:

+pds

+A pointer to an array of pollfd structures (see poll(2)). +

+npds

+The number of elements in the pds array.

+timeout

+A value of type st_utime_t specifying the +amount of time in microseconds the call will block waiting for I/O +to become ready. If this time expires without any I/O becoming ready, +st_poll() returns zero. +Note that timeouts are measured since the +last context switch. +

+

Returns
+Upon successful completion, a non-negative value is returned. A positive +value indicates the total number of OS file descriptors in pds +that have events. A value of 0 indicates that the call timed out. +Upon failure, a value of -1 is returned and errno is set +to indicate the error:

+ + +
EINTRThe current thread was interrupted by +st_thread_interrupt().
+

+If an alternative event notification mechanism has been set by +st_set_eventsys(), other values of +errno could be set upon failure as well. The values +depend on the specific mechanism in use. +

+

Description
+This function returns as soon as I/O is ready on one or more of the specified +OS file descriptors. A count of the number of ready descriptors is returned +unless a timeout occurs, in which case zero is returned.

+The pollfd structure is defined in the poll.h header file +and contains the following members:

+

+    int fd;             /* OS file descriptor */
+    short events;       /* requested events   */
+    short revents;      /* returned events    */
+
+The events field should be set to the I/O events (readable, +writable, exception, or some combination) that the caller is interested in. +On return, the revents field is set to indicate what kind of I/O +is ready on the respective descriptor.

+The events and revents fields are constructed by OR-ing +any combination of the following event flags (defined in poll.h): +

+ + + + + + +
POLLINfd is readable.
POLLOUTfd is is writable.
POLLPRIfd has an exception condition.
POLLNVALfd is bad.
+

+The POLLNVAL flag is only valid in the revents field; +it is not used in the events field.

+Despite having an interface like poll(2), this function uses +the same event notification mechanism as the rest of the library. For +instance if an alternative event nofication mechanism was set using st_set_eventsys(), this function uses that +mechanism to check for events.

+Note that unlike the poll(2) call, this function has the +timeout parameter expressed in microseconds. If the value of +timeout is ST_UTIME_NO_TIMEOUT +(-1), this function blocks until a requested I/O +event occurs or until the call is interrupted by +st_thread_interrupt(). +

+Note: if kqueue(2) is used as an alternative event +notification mechanism (see st_set_eventsys()), the POLLPRI +event flag is not supported and st_poll() will return an error +if it's set (errno will be set to EINVAL). +

+


+

+ + +

Program Structure

+ +

+Generally, the following steps should be followed when writing an application +using the State Threads library: +

+

    +
  1. Configure the library by calling these pre-init functions, if desired. + +
  2. +

    +

  3. Initialize the library by calling st_init().
  4. +

    +

  5. Configure the library by calling these post-init functions, if desired. + +
  6. +

    +

  7. Create resources that will be shared among different processes: + create and bind listening sockets (see socket(3), + bind(3), listen(3), + st_netfd_open_socket(), and possibly + st_netfd_serialize_accept()), + create shared memory segments, inter-process communication (IPC) + channels and synchronization primitives (if any).
  8. +

    +

  9. Create several processes via fork(2). The parent process should + either exit or become a "watchdog" (e.g., it starts a new process when + an existing one crashes, does a cleanup upon application termination, + etc.).
  10. +

    +

  11. In each child process create a pool of threads (see + st_thread_create()) to handle user + connections. Each thread in the pool may accept client connections + (st_accept()), connect to other servers + (st_connect()), perform various network I/O + (st_read(), st_write(), etc.).
  12. +
+

+Note that only State Threads library I/O functions should +be used for a network I/O: any other I/O calls may block the calling process +indefinitely. For example, standard I/O functions (fgets(3), +fread(3), fwrite(3), fprintf(3), etc.) call +read(2) and write(2) directly and therefore should not be +used on sockets or pipes. +

+Also note that for short timeouts to work the program +should do context switches (for example by calling +st_usleep()) on a regular basis. +

+


+

+ + +

List of Blocking Functions

+ +

+The thread context switch (process state change) can only happen +in a well-known set of blocking functions. +Only the following functions can block the calling thread: +

+

+
st_thread_join()
+
st_sleep()
+
st_usleep()
+
st_cond_wait()
+
st_cond_timedwait()
+
st_mutex_lock()
+
st_netfd_poll()
+
st_accept()
+
st_connect()
+
st_read()
+
st_read_fully()
+
st_read_resid()
+
st_readv()
+
st_readv_resid()
+
st_write()
+
st_write_resid()
+
st_writev()
+
st_writev_resid()
+
st_recvfrom()
+
st_sendto()
+
st_recvmsg()
+
st_sendmsg()
+
st_poll()
+
+

+


+

+ + + + diff --git a/trunk/3rdparty/st-srs/docs/st.html b/trunk/3rdparty/st-srs/docs/st.html new file mode 100644 index 000000000..a6b932a81 --- /dev/null +++ b/trunk/3rdparty/st-srs/docs/st.html @@ -0,0 +1,504 @@ + + +State Threads for Internet Applications + + +

State Threads for Internet Applications

+

Introduction

+

+State Threads is an application library which provides a +foundation for writing fast and highly scalable Internet Applications +on UNIX-like platforms. It combines the simplicity of the multithreaded +programming paradigm, in which one thread supports each simultaneous +connection, with the performance and scalability of an event-driven +state machine architecture.

+ +

1. Definitions

+

+ +

1.1 Internet Applications

+ +

+An Internet Application (IA) is either a server or client network +application that accepts connections from clients and may or may not +connect to servers. In an IA the arrival or departure of network data +often controls processing (that is, IA is a data-driven application). +For each connection, an IA does some finite amount of work +involving data exchange with its peer, where its peer may be either +a client or a server. +The typical transaction steps of an IA are to accept a connection, +read a request, do some finite and predictable amount of work to +process the request, then write a response to the peer that sent the +request. One example of an IA is a Web server; +the most general example of an IA is a proxy server, because it both +accepts connections from clients and connects to other servers.

+

+We assume that the performance of an IA is constrained by available CPU +cycles rather than network bandwidth or disk I/O (that is, CPU +is a bottleneck resource). +

+ + +

1.2 Performance and Scalability

+ +

+The performance of an IA is usually evaluated as its +throughput measured in transactions per second or bytes per second (one +can be converted to the other, given the average transaction size). There are +several benchmarks that can be used to measure throughput of Web serving +applications for specific workloads (such as +SPECweb96, +WebStone, +WebBench). +Although there is no common definition for scalability, in general it +expresses the ability of an application to sustain its performance when some +external condition changes. For IAs this external condition is either the +number of clients (also known as "users," "simultaneous connections," or "load +generators") or the underlying hardware system size (number of CPUs, memory +size, and so on). Thus there are two types of scalability: load +scalability and system scalability, respectively. +

+The figure below shows how the throughput of an idealized IA changes with +the increasing number of clients (solid blue line). Initially the throughput +grows linearly (the slope represents the maximal throughput that one client +can provide). Within this initial range, the IA is underutilized and CPUs are +partially idle. Further increase in the number of clients leads to a system +saturation, and the throughput gradually stops growing as all CPUs become fully +utilized. After that point, the throughput stays flat because there are no +more CPU cycles available. +In the real world, however, each simultaneous connection +consumes some computational and memory resources, even when idle, and this +overhead grows with the number of clients. Therefore, the throughput of the +real world IA starts dropping after some point (dashed blue line in the figure +below). The rate at which the throughput drops depends, among other things, on +application design. +

+We say that an application has a good load scalability if it can +sustain its throughput over a wide range of loads. +Interestingly, the SPECweb99 +benchmark somewhat reflects the Web server's load scalability because it +measures the number of clients (load generators) given a mandatory minimal +throughput per client (that is, it measures the server's capacity). +This is unlike SPECweb96 and +other benchmarks that use the throughput as their main metric (see the figure +below). +

+

Figure: Throughput vs. Number of clients +
+

+System scalability is the ability of an application to sustain its +performance per hardware unit (such as a CPU) with the increasing number of +these units. In other words, good system scalability means that doubling the +number of processors will roughly double the application's throughput (dashed +green line). We assume here that the underlying operating system also scales +well. Good system scalability allows you to initially run an application on +the smallest system possible, while retaining the ability to move that +application to a larger system if necessary, without excessive effort or +expense. That is, an application need not be rewritten or even undergo a +major porting effort when changing system size. +

+Although scalability and performance are more important in the case of server +IAs, they should also be considered for some client applications (such as +benchmark load generators). +

+ + +

1.3 Concurrency

+ +

+Concurrency reflects the parallelism in a system. The two unrelated types +are virtual concurrency and real concurrency. +

    +
  • Virtual (or apparent) concurrency is the number of simultaneous +connections that a system supports. +

    +
  • Real concurrency is the number of hardware devices, including +CPUs, network cards, and disks, that actually allow a system to perform +tasks in parallel. +
+

+An IA must provide virtual concurrency in order to serve many users +simultaneously. +To achieve maximum performance and scalability in doing so, the number of +programming entities than an IA creates to be scheduled by the OS kernel +should be +kept close to (within an order of magnitude of) the real concurrency found on +the system. These programming entities scheduled by the kernel are known as +kernel execution vehicles. Examples of kernel execution vehicles +include Solaris lightweight processes and IRIX kernel threads. +In other words, the number of kernel execution vehicles should be dictated by +the system size and not by the number of simultaneous connections. +

+ +

2. Existing Architectures

+

+There are a few different architectures that are commonly used by IAs. +These include the Multi-Process, +Multi-Threaded, and Event-Driven State Machine +architectures. +

+ +

2.1 Multi-Process Architecture

+ +

+In the Multi-Process (MP) architecture, an individual process is +dedicated to each simultaneous connection. +A process performs all of a transaction's initialization steps +and services a connection completely before moving on to service +a new connection. +

+User sessions in IAs are relatively independent; therefore, no +synchronization between processes handling different connections is +necessary. Because each process has its own private address space, +this architecture is very robust. If a process serving one of the connections +crashes, the other sessions will not be affected. However, to serve many +concurrent connections, an equal number of processes must be employed. +Because processes are kernel entities (and are in fact the heaviest ones), +the number of kernel entities will be at least as large as the number of +concurrent sessions. On most systems, good performance will not be achieved +when more than a few hundred processes are created because of the high +context-switching overhead. In other words, MP applications have poor load +scalability. +

+On the other hand, MP applications have very good system scalability, because +no resources are shared among different processes and there is no +synchronization overhead. +

+The Apache Web Server 1.x ([Reference 1]) uses the MP +architecture on UNIX systems. +

+ +

2.2 Multi-Threaded Architecture

+ +

+In the Multi-Threaded (MT) architecture, multiple independent threads +of control are employed within a single shared address space. Like a +process in the MP architecture, each thread performs all of a +transaction's initialization steps and services a connection completely +before moving on to service a new connection. +

+Many modern UNIX operating systems implement a many-to-few model when +mapping user-level threads to kernel entities. In this model, an +arbitrarily large number of user-level threads is multiplexed onto a +lesser number of kernel execution vehicles. Kernel execution +vehicles are also known as virtual processors. Whenever a user-level +thread makes a blocking system call, the kernel execution vehicle it is using +will become blocked in the kernel. If there are no other non-blocked kernel +execution vehicles and there are other runnable user-level threads, a new +kernel execution vehicle will be created automatically. This prevents the +application from blocking when it can continue to make useful forward +progress. +

+Because IAs are by nature network I/O driven, all concurrent sessions block on +network I/O at various points. As a result, the number of virtual processors +created in the kernel grows close to the number of user-level threads +(or simultaneous connections). When this occurs, the many-to-few model +effectively degenerates to a one-to-one model. Again, like in +the MP architecture, the number of kernel execution vehicles is dictated by +the number of simultaneous connections rather than by number of CPUs. This +reduces an application's load scalability. However, because kernel threads +(lightweight processes) use fewer resources and are more light-weight than +traditional UNIX processes, an MT application should scale better with load +than an MP application. +

+Unexpectedly, the small number of virtual processors sharing the same address +space in the MT architecture destroys an application's system scalability +because of contention among the threads on various locks. Even if an +application itself is carefully +optimized to avoid lock contention around its own global data (a non-trivial +task), there are still standard library functions and system calls +that use common resources hidden from the application. For example, +on many platforms thread safety of memory allocation routines +(malloc(3), free(3), and so on) is achieved by using a single +global lock. Another example is a per-process file descriptor table. +This common resource table is shared by all kernel execution vehicles within +the same process and must be protected when one modifies it via +certain system calls (such as open(2), close(2), and so on). +In addition to that, maintaining the caches coherent +among CPUs on multiprocessor systems hurts performance when different threads +running on different CPUs modify data items on the same cache line. +

+In order to improve load scalability, some applications employ a different +type of MT architecture: they create one or more thread(s) per task +rather than one thread per connection. For example, one small group +of threads may be responsible for accepting client connections, another +for request processing, and yet another for serving responses. The main +advantage of this architecture is that it eliminates the tight coupling +between the number of threads and number of simultaneous connections. However, +in this architecture, different task-specific thread groups must share common +work queues that must be protected by mutual exclusion locks (a typical +producer-consumer problem). This adds synchronization overhead that causes an +application to perform badly on multiprocessor systems. In other words, in +this architecture, the application's system scalability is sacrificed for the +sake of load scalability. +

+Of course, the usual nightmares of threaded programming, including data +corruption, deadlocks, and race conditions, also make MT architecture (in any +form) non-simplistic to use. +

+ + +

2.3 Event-Driven State Machine Architecture

+ +

+In the Event-Driven State Machine (EDSM) architecture, a single process +is employed to concurrently process multiple connections. The basics of this +architecture are described in Comer and Stevens +[Reference 2]. +The EDSM architecture performs one basic data-driven step associated with +a particular connection at a time, thus multiplexing many concurrent +connections. The process operates as a state machine that receives an event +and then reacts to it. +

+In the idle state the EDSM calls select(2) or poll(2) to +wait for network I/O events. When a particular file descriptor is ready for +I/O, the EDSM completes the corresponding basic step (usually by invoking a +handler function) and starts the next one. This architecture uses +non-blocking system calls to perform asynchronous network I/O operations. +For more details on non-blocking I/O see Stevens +[Reference 3]. +

+To take advantage of hardware parallelism (real concurrency), multiple +identical processes may be created. This is called Symmetric Multi-Process +EDSM and is used, for example, in the Zeus Web Server +([Reference 4]). To more efficiently multiplex disk I/O, +special "helper" processes may be created. This is called Asymmetric +Multi-Process EDSM and was proposed for Web servers by Druschel +and others [Reference 5]. +

+EDSM is probably the most scalable architecture for IAs. +Because the number of simultaneous connections (virtual concurrency) is +completely decoupled from the number of kernel execution vehicles (processes), +this architecture has very good load scalability. It requires only minimal +user-level resources to create and maintain additional connection. +

+Like MP applications, Multi-Process EDSM has very good system scalability +because no resources are shared among different processes and there is no +synchronization overhead. +

+Unfortunately, the EDSM architecture is monolithic rather than based on the +concept of threads, so new applications generally need to be implemented from +the ground up. In effect, the EDSM architecture simulates threads and their +stacks the hard way. +

+ + +

3. State Threads Library

+ +

+The State Threads library combines the advantages of all of the above +architectures. The interface preserves the programming simplicity of thread +abstraction, allowing each simultaneous connection to be treated as a separate +thread of execution within a single process. The underlying implementation is +close to the EDSM architecture as the state of each particular concurrent +session is saved in a separate memory segment. +

+ +

3.1 State Changes and Scheduling

+

+The state of each concurrent session includes its stack environment +(stack pointer, program counter, CPU registers) and its stack. Conceptually, +a thread context switch can be viewed as a process changing its state. There +are no kernel entities involved other than processes. +Unlike other general-purpose threading libraries, the State Threads library +is fully deterministic. The thread context switch (process state change) can +only happen in a well-known set of functions (at I/O points or at explicit +synchronization points). As a result, process-specific global data does not +have to be protected by mutual exclusion locks in most cases. The entire +application is free to use all the static variables and non-reentrant library +functions it wants, greatly simplifying programming and debugging while +increasing performance. This is somewhat similar to a co-routine model +(co-operatively multitasked threads), except that no explicit yield is needed +-- +sooner or later, a thread performs a blocking I/O operation and thus surrenders +control. All threads of execution (simultaneous connections) have the +same priority, so scheduling is non-preemptive, like in the EDSM architecture. +Because IAs are data-driven (processing is limited by the size of network +buffers and data arrival rates), scheduling is non-time-slicing. +

+Only two types of external events are handled by the library's +scheduler, because only these events can be detected by +select(2) or poll(2): I/O events (a file descriptor is ready +for I/O) and time events +(some timeout has expired). However, other types of events (such as +a signal sent to a process) can also be handled by converting them to I/O +events. For example, a signal handling function can perform a write to a pipe +(write(2) is reentrant/asynchronous-safe), thus converting a signal +event to an I/O event. +

+To take advantage of hardware parallelism, as in the EDSM architecture, +multiple processes can be created in either a symmetric or asymmetric manner. +Process management is not in the library's scope but instead is left up to the +application. +

+There are several general-purpose threading libraries that implement a +many-to-one model (many user-level threads to one kernel execution +vehicle), using the same basic techniques as the State Threads library +(non-blocking I/O, event-driven scheduler, and so on). For an example, see GNU +Portable Threads ([Reference 6]). Because they are +general-purpose, these libraries have different objectives than the State +Threads library. The State Threads library is not a general-purpose +threading library, +but rather an application library that targets only certain types of +applications (IAs) in order to achieve the highest possible performance and +scalability for those applications. +

+ +

3.2 Scalability

+

+State threads are very lightweight user-level entities, and therefore creating +and maintaining user connections requires minimal resources. An application +using the State Threads library scales very well with the increasing number +of connections. +

+On multiprocessor systems an application should create multiple processes +to take advantage of hardware parallelism. Using multiple separate processes +is the only way to achieve the highest possible system scalability. +This is because duplicating per-process resources is the only way to avoid +significant synchronization overhead on multiprocessor systems. Creating +separate UNIX processes naturally offers resource duplication. Again, +as in the EDSM architecture, there is no connection between the number of +simultaneous connections (which may be very large and changes within a wide +range) and the number of kernel entities (which is usually small and constant). +In other words, the State Threads library makes it possible to multiplex a +large number of simultaneous connections onto a much smaller number of +separate processes, thus allowing an application to scale well with both +the load and system size. +

+ +

3.3 Performance

+

+Performance is one of the library's main objectives. The State Threads +library is implemented to minimize the number of system calls and +to make thread creation and context switching as fast as possible. +For example, per-thread signal mask does not exist (unlike +POSIX threads), so there is no need to save and restore a process's +signal mask on every thread context switch. This eliminates two system +calls per context switch. Signal events can be handled much more +efficiently by converting them to I/O events (see above). +

+ +

3.4 Portability

+

+The library uses the same general, underlying concepts as the EDSM +architecture, including non-blocking I/O, file descriptors, and +I/O multiplexing. These concepts are available in some form on most +UNIX platforms, making the library very portable across many +flavors of UNIX. There are only a few platform-dependent sections in the +source. +

+ +

3.5 State Threads and NSPR

+

+The State Threads library is a derivative of the Netscape Portable +Runtime library (NSPR) [Reference 7]. The primary goal of +NSPR is to provide a platform-independent layer for system facilities, +where system facilities include threads, thread synchronization, and I/O. +Performance and scalability are not the main concern of NSPR. The +State Threads library addresses performance and scalability while +remaining much smaller than NSPR. It is contained in 8 source files +as opposed to more than 400, but provides all the functionality that +is needed to write efficient IAs on UNIX-like platforms. +

+ + + + + + + + + + + + + + + + + + + + + + + + + + + +
NSPRState Threads
Lines of code~150,000~3000
Dynamic library size  
(debug version)
IRIX~700 KB~60 KB
Linux~900 KB~70 KB
+

+ +

Conclusion

+

+State Threads is an application library which provides a foundation for +writing Internet Applications. To summarize, it has the +following advantages: +

+

    +
  • It allows the design of fast and highly scalable applications. An +application will scale well with both load and number of CPUs. +

    +

  • It greatly simplifies application programming and debugging because, as a +rule, no mutual exclusion locking is necessary and the entire application is +free to use static variables and non-reentrant library functions. +
+

+The library's main limitation: +

+

    +
  • All I/O operations on sockets must use the State Thread library's I/O +functions because only those functions perform thread scheduling and prevent +the application's processes from blocking. +
+

+ +

References

+
    + +
  1. Apache Software Foundation, +http://www.apache.org. + +
  2. Douglas E. Comer, David L. Stevens, Internetworking With TCP/IP, +Vol. III: Client-Server Programming And Applications, Second Edition, +Ch. 8, 12. + +
  3. W. Richard Stevens, UNIX Network Programming, Second Edition, +Vol. 1, Ch. 15. + +
  4. Zeus Technology Limited, +http://www.zeus.co.uk. + +
  5. Peter Druschel, Vivek S. Pai, Willy Zwaenepoel, + +Flash: An Efficient and Portable Web Server. In Proceedings of the +USENIX 1999 Annual Technical Conference, Monterey, CA, June 1999. + +
  6. GNU Portable Threads, +http://www.gnu.org/software/pth/. + +
  7. Netscape Portable Runtime, +http://www.mozilla.org/docs/refList/refNSPR/. +
+ +

Other resources covering various architectural issues in IAs

+
    +
  1. Dan Kegel, The C10K problem, +http://www.kegel.com/c10k.html. +
  2. +
  3. James C. Hu, Douglas C. Schmidt, Irfan Pyarali, JAWS: Understanding +High Performance Web Systems, +http://www.cs.wustl.edu/~jxh/research/research.html.
  4. +
+

+


+

+ +

Portions created by SGI are Copyright © 2000 +Silicon Graphics, Inc. All rights reserved.
+

+ + + + diff --git a/trunk/3rdparty/st-srs/docs/timeout_heap.txt b/trunk/3rdparty/st-srs/docs/timeout_heap.txt new file mode 100644 index 000000000..1582dc129 --- /dev/null +++ b/trunk/3rdparty/st-srs/docs/timeout_heap.txt @@ -0,0 +1,60 @@ +How the timeout heap works + +As of version 1.5, the State Threads Library represents the queue of +sleeping threads using a heap data structure rather than a sorted +linked list. This improves performance when there is a large number +of sleeping threads, since insertion into a heap takes O(log N) time +while insertion into a sorted list takes O(N) time. For example, in +one test 1000 threads were created, each thread called st_usleep() +with a random time interval, and then all the threads where +immediately interrupted and joined before the sleeps had a chance to +finish. The whole process was repeated 1000 times, for a total of a +million sleep queue insertions and removals. With the old list-based +sleep queue, this test took 100 seconds; now it takes only 12 seconds. + +Heap data structures are typically based on dynamically resized +arrays. However, since the existing ST code base was very nicely +structured around linking the thread objects into pointer-based lists +without the need for any auxiliary data structures, implementing the +heap using a similar nodes-and-pointers based approach seemed more +appropriate for ST than introducing a separate array. + +Thus, the new ST timeout heap works by organizing the existing +_st_thread_t objects in a balanced binary tree, just as they were +previously organized into a doubly-linked, sorted list. The global +_ST_SLEEPQ variable, formerly a linked list head, is now simply a +pointer to the root of this tree, and the root node of the tree is the +thread with the earliest timeout. Each thread object has two child +pointers, "left" and "right", pointing to threads with later timeouts. + +Each node in the tree is numbered with an integer index, corresponding +to the array index in an array-based heap, and the tree is kept fully +balanced and left-adjusted at all times. In other words, the tree +consists of any number of fully populated top levels, followed by a +single bottom level which may be partially populated, such that any +existing nodes form a contiguous block to the left and the spaces for +missing nodes form a contiguous block to the right. For example, if +there are nine threads waiting for a timeout, they are numbered and +arranged in a tree exactly as follows: + + 1 + / \ + 2 3 + / \ / \ + 4 5 6 7 + / \ + 8 9 + +Each node has either no children, only a left child, or both a left +and a right child. Children always time out later than their parents +(this is called the "heap invariant"), but when a node has two +children, their mutual order is unspecified - the left child may time +out before or after the right child. If a node is numbered N, its +left child is numbered 2N, and its right child is numbered 2N+1. + +There is no pointer from a child to its parent; all pointers point +downward. Additions and deletions both work by starting at the root +and traversing the tree towards the leaves, going left or right +according to the binary digits forming the index of the destination +node. As nodes are added or deleted, existing nodes are rearranged to +maintain the heap invariant. diff --git a/trunk/3rdparty/st-srs/event.c b/trunk/3rdparty/st-srs/event.c new file mode 100644 index 000000000..142882a51 --- /dev/null +++ b/trunk/3rdparty/st-srs/event.c @@ -0,0 +1,1413 @@ +/* + * The contents of this file are subject to the Mozilla Public + * License Version 1.1 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS + * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or + * implied. See the License for the specific language governing + * rights and limitations under the License. + * + * The Original Code is the Netscape Portable Runtime library. + * + * The Initial Developer of the Original Code is Netscape + * Communications Corporation. Portions created by Netscape are + * Copyright (C) 1994-2000 Netscape Communications Corporation. All + * Rights Reserved. + * + * Contributor(s): Silicon Graphics, Inc. + * Yahoo! Inc. + * + * Alternatively, the contents of this file may be used under the + * terms of the GNU General Public License Version 2 or later (the + * "GPL"), in which case the provisions of the GPL are applicable + * instead of those above. If you wish to allow use of your + * version of this file only under the terms of the GPL and not to + * allow others to use your version of this file under the MPL, + * indicate your decision by deleting the provisions above and + * replace them with the notice and other provisions required by + * the GPL. If you do not delete the provisions above, a recipient + * may use your version of this file under either the MPL or the + * GPL. + */ + +#include +#include +#include +#include +#include +#include +#include "common.h" + +#ifdef MD_HAVE_KQUEUE +#include +#endif +#ifdef MD_HAVE_EPOLL +#include +#endif + +#if defined(USE_POLL) && !defined(MD_HAVE_POLL) + /* Force poll usage if explicitly asked for it */ + #define MD_HAVE_POLL +#endif + + +static struct _st_seldata { + fd_set fd_read_set, fd_write_set, fd_exception_set; + int fd_ref_cnts[FD_SETSIZE][3]; + int maxfd; +} *_st_select_data; + +#define _ST_SELECT_MAX_OSFD (_st_select_data->maxfd) +#define _ST_SELECT_READ_SET (_st_select_data->fd_read_set) +#define _ST_SELECT_WRITE_SET (_st_select_data->fd_write_set) +#define _ST_SELECT_EXCEP_SET (_st_select_data->fd_exception_set) +#define _ST_SELECT_READ_CNT(fd) (_st_select_data->fd_ref_cnts[fd][0]) +#define _ST_SELECT_WRITE_CNT(fd) (_st_select_data->fd_ref_cnts[fd][1]) +#define _ST_SELECT_EXCEP_CNT(fd) (_st_select_data->fd_ref_cnts[fd][2]) + + +#ifdef MD_HAVE_POLL +static struct _st_polldata { + struct pollfd *pollfds; + int pollfds_size; + int fdcnt; +} *_st_poll_data; + +#define _ST_POLL_OSFD_CNT (_st_poll_data->fdcnt) +#define _ST_POLLFDS (_st_poll_data->pollfds) +#define _ST_POLLFDS_SIZE (_st_poll_data->pollfds_size) +#endif /* MD_HAVE_POLL */ + + +#ifdef MD_HAVE_KQUEUE +typedef struct _kq_fd_data { + int rd_ref_cnt; + int wr_ref_cnt; + int revents; +} _kq_fd_data_t; + +static struct _st_kqdata { + _kq_fd_data_t *fd_data; + struct kevent *evtlist; + struct kevent *addlist; + struct kevent *dellist; + int fd_data_size; + int evtlist_size; + int addlist_size; + int addlist_cnt; + int dellist_size; + int dellist_cnt; + int kq; + pid_t pid; +} *_st_kq_data; + +#ifndef ST_KQ_MIN_EVTLIST_SIZE +#define ST_KQ_MIN_EVTLIST_SIZE 64 +#endif + +#define _ST_KQ_READ_CNT(fd) (_st_kq_data->fd_data[fd].rd_ref_cnt) +#define _ST_KQ_WRITE_CNT(fd) (_st_kq_data->fd_data[fd].wr_ref_cnt) +#define _ST_KQ_REVENTS(fd) (_st_kq_data->fd_data[fd].revents) +#endif /* MD_HAVE_KQUEUE */ + + +#ifdef MD_HAVE_EPOLL +typedef struct _epoll_fd_data { + int rd_ref_cnt; + int wr_ref_cnt; + int ex_ref_cnt; + int revents; +} _epoll_fd_data_t; + +static struct _st_epolldata { + _epoll_fd_data_t *fd_data; + struct epoll_event *evtlist; + int fd_data_size; + int evtlist_size; + int evtlist_cnt; + int fd_hint; + int epfd; + pid_t pid; +} *_st_epoll_data; + +#ifndef ST_EPOLL_EVTLIST_SIZE + /* Not a limit, just a hint */ + #define ST_EPOLL_EVTLIST_SIZE 4096 +#endif + +#define _ST_EPOLL_READ_CNT(fd) (_st_epoll_data->fd_data[fd].rd_ref_cnt) +#define _ST_EPOLL_WRITE_CNT(fd) (_st_epoll_data->fd_data[fd].wr_ref_cnt) +#define _ST_EPOLL_EXCEP_CNT(fd) (_st_epoll_data->fd_data[fd].ex_ref_cnt) +#define _ST_EPOLL_REVENTS(fd) (_st_epoll_data->fd_data[fd].revents) + +#define _ST_EPOLL_READ_BIT(fd) (_ST_EPOLL_READ_CNT(fd) ? EPOLLIN : 0) +#define _ST_EPOLL_WRITE_BIT(fd) (_ST_EPOLL_WRITE_CNT(fd) ? EPOLLOUT : 0) +#define _ST_EPOLL_EXCEP_BIT(fd) (_ST_EPOLL_EXCEP_CNT(fd) ? EPOLLPRI : 0) +#define _ST_EPOLL_EVENTS(fd) \ + (_ST_EPOLL_READ_BIT(fd)|_ST_EPOLL_WRITE_BIT(fd)|_ST_EPOLL_EXCEP_BIT(fd)) + +#endif /* MD_HAVE_EPOLL */ + +_st_eventsys_t *_st_eventsys = NULL; + + +/***************************************** + * select event system + */ + +ST_HIDDEN int _st_select_init(void) +{ + _st_select_data = (struct _st_seldata *) malloc(sizeof(*_st_select_data)); + if (!_st_select_data) + return -1; + + memset(_st_select_data, 0, sizeof(*_st_select_data)); + _st_select_data->maxfd = -1; + + return 0; +} + +ST_HIDDEN int _st_select_pollset_add(struct pollfd *pds, int npds) +{ + struct pollfd *pd; + struct pollfd *epd = pds + npds; + + /* Do checks up front */ + for (pd = pds; pd < epd; pd++) { + if (pd->fd < 0 || pd->fd >= FD_SETSIZE || !pd->events || + (pd->events & ~(POLLIN | POLLOUT | POLLPRI))) { + errno = EINVAL; + return -1; + } + } + + for (pd = pds; pd < epd; pd++) { + if (pd->events & POLLIN) { + FD_SET(pd->fd, &_ST_SELECT_READ_SET); + _ST_SELECT_READ_CNT(pd->fd)++; + } + if (pd->events & POLLOUT) { + FD_SET(pd->fd, &_ST_SELECT_WRITE_SET); + _ST_SELECT_WRITE_CNT(pd->fd)++; + } + if (pd->events & POLLPRI) { + FD_SET(pd->fd, &_ST_SELECT_EXCEP_SET); + _ST_SELECT_EXCEP_CNT(pd->fd)++; + } + if (_ST_SELECT_MAX_OSFD < pd->fd) + _ST_SELECT_MAX_OSFD = pd->fd; + } + + return 0; +} + +ST_HIDDEN void _st_select_pollset_del(struct pollfd *pds, int npds) +{ + struct pollfd *pd; + struct pollfd *epd = pds + npds; + + for (pd = pds; pd < epd; pd++) { + if (pd->events & POLLIN) { + if (--_ST_SELECT_READ_CNT(pd->fd) == 0) + FD_CLR(pd->fd, &_ST_SELECT_READ_SET); + } + if (pd->events & POLLOUT) { + if (--_ST_SELECT_WRITE_CNT(pd->fd) == 0) + FD_CLR(pd->fd, &_ST_SELECT_WRITE_SET); + } + if (pd->events & POLLPRI) { + if (--_ST_SELECT_EXCEP_CNT(pd->fd) == 0) + FD_CLR(pd->fd, &_ST_SELECT_EXCEP_SET); + } + } +} + +ST_HIDDEN void _st_select_find_bad_fd(void) +{ + _st_clist_t *q; + _st_pollq_t *pq; + int notify; + struct pollfd *pds, *epds; + int pq_max_osfd, osfd; + short events; + + _ST_SELECT_MAX_OSFD = -1; + + for (q = _ST_IOQ.next; q != &_ST_IOQ; q = q->next) { + pq = _ST_POLLQUEUE_PTR(q); + notify = 0; + epds = pq->pds + pq->npds; + pq_max_osfd = -1; + + for (pds = pq->pds; pds < epds; pds++) { + osfd = pds->fd; + pds->revents = 0; + if (pds->events == 0) + continue; + if (fcntl(osfd, F_GETFL, 0) < 0) { + pds->revents = POLLNVAL; + notify = 1; + } + if (osfd > pq_max_osfd) { + pq_max_osfd = osfd; + } + } + + if (notify) { + ST_REMOVE_LINK(&pq->links); + pq->on_ioq = 0; + /* + * Decrement the count of descriptors for each descriptor/event + * because this I/O request is being removed from the ioq + */ + for (pds = pq->pds; pds < epds; pds++) { + osfd = pds->fd; + events = pds->events; + if (events & POLLIN) { + if (--_ST_SELECT_READ_CNT(osfd) == 0) { + FD_CLR(osfd, &_ST_SELECT_READ_SET); + } + } + if (events & POLLOUT) { + if (--_ST_SELECT_WRITE_CNT(osfd) == 0) { + FD_CLR(osfd, &_ST_SELECT_WRITE_SET); + } + } + if (events & POLLPRI) { + if (--_ST_SELECT_EXCEP_CNT(osfd) == 0) { + FD_CLR(osfd, &_ST_SELECT_EXCEP_SET); + } + } + } + + if (pq->thread->flags & _ST_FL_ON_SLEEPQ) + _ST_DEL_SLEEPQ(pq->thread); + pq->thread->state = _ST_ST_RUNNABLE; + _ST_ADD_RUNQ(pq->thread); + } else { + if (_ST_SELECT_MAX_OSFD < pq_max_osfd) + _ST_SELECT_MAX_OSFD = pq_max_osfd; + } + } +} + +ST_HIDDEN void _st_select_dispatch(void) +{ + struct timeval timeout, *tvp; + fd_set r, w, e; + fd_set *rp, *wp, *ep; + int nfd, pq_max_osfd, osfd; + _st_clist_t *q; + st_utime_t min_timeout; + _st_pollq_t *pq; + int notify; + struct pollfd *pds, *epds; + short events, revents; + + /* + * Assignment of fd_sets + */ + r = _ST_SELECT_READ_SET; + w = _ST_SELECT_WRITE_SET; + e = _ST_SELECT_EXCEP_SET; + + rp = &r; + wp = &w; + ep = &e; + + if (_ST_SLEEPQ == NULL) { + tvp = NULL; + } else { + min_timeout = (_ST_SLEEPQ->due <= _ST_LAST_CLOCK) ? 0 : + (_ST_SLEEPQ->due - _ST_LAST_CLOCK); + timeout.tv_sec = (int) (min_timeout / 1000000); + timeout.tv_usec = (int) (min_timeout % 1000000); + tvp = &timeout; + } + + /* Check for I/O operations */ + nfd = select(_ST_SELECT_MAX_OSFD + 1, rp, wp, ep, tvp); + + /* Notify threads that are associated with the selected descriptors */ + if (nfd > 0) { + _ST_SELECT_MAX_OSFD = -1; + for (q = _ST_IOQ.next; q != &_ST_IOQ; q = q->next) { + pq = _ST_POLLQUEUE_PTR(q); + notify = 0; + epds = pq->pds + pq->npds; + pq_max_osfd = -1; + + for (pds = pq->pds; pds < epds; pds++) { + osfd = pds->fd; + events = pds->events; + revents = 0; + if ((events & POLLIN) && FD_ISSET(osfd, rp)) { + revents |= POLLIN; + } + if ((events & POLLOUT) && FD_ISSET(osfd, wp)) { + revents |= POLLOUT; + } + if ((events & POLLPRI) && FD_ISSET(osfd, ep)) { + revents |= POLLPRI; + } + pds->revents = revents; + if (revents) { + notify = 1; + } + if (osfd > pq_max_osfd) { + pq_max_osfd = osfd; + } + } + if (notify) { + ST_REMOVE_LINK(&pq->links); + pq->on_ioq = 0; + /* + * Decrement the count of descriptors for each descriptor/event + * because this I/O request is being removed from the ioq + */ + for (pds = pq->pds; pds < epds; pds++) { + osfd = pds->fd; + events = pds->events; + if (events & POLLIN) { + if (--_ST_SELECT_READ_CNT(osfd) == 0) { + FD_CLR(osfd, &_ST_SELECT_READ_SET); + } + } + if (events & POLLOUT) { + if (--_ST_SELECT_WRITE_CNT(osfd) == 0) { + FD_CLR(osfd, &_ST_SELECT_WRITE_SET); + } + } + if (events & POLLPRI) { + if (--_ST_SELECT_EXCEP_CNT(osfd) == 0) { + FD_CLR(osfd, &_ST_SELECT_EXCEP_SET); + } + } + } + + if (pq->thread->flags & _ST_FL_ON_SLEEPQ) + _ST_DEL_SLEEPQ(pq->thread); + pq->thread->state = _ST_ST_RUNNABLE; + _ST_ADD_RUNQ(pq->thread); + } else { + if (_ST_SELECT_MAX_OSFD < pq_max_osfd) + _ST_SELECT_MAX_OSFD = pq_max_osfd; + } + } + } else if (nfd < 0) { + /* + * It can happen when a thread closes file descriptor + * that is being used by some other thread -- BAD! + */ + if (errno == EBADF) + _st_select_find_bad_fd(); + } +} + +ST_HIDDEN int _st_select_fd_new(int osfd) +{ + if (osfd >= FD_SETSIZE) { + errno = EMFILE; + return -1; + } + + return 0; +} + +ST_HIDDEN int _st_select_fd_close(int osfd) +{ + if (_ST_SELECT_READ_CNT(osfd) || _ST_SELECT_WRITE_CNT(osfd) || + _ST_SELECT_EXCEP_CNT(osfd)) { + errno = EBUSY; + return -1; + } + + return 0; +} + +ST_HIDDEN int _st_select_fd_getlimit(void) +{ + return FD_SETSIZE; +} + +static _st_eventsys_t _st_select_eventsys = { + "select", + ST_EVENTSYS_SELECT, + _st_select_init, + _st_select_dispatch, + _st_select_pollset_add, + _st_select_pollset_del, + _st_select_fd_new, + _st_select_fd_close, + _st_select_fd_getlimit +}; + + +#ifdef MD_HAVE_POLL +/***************************************** + * poll event system + */ + +ST_HIDDEN int _st_poll_init(void) +{ + _st_poll_data = (struct _st_polldata *) malloc(sizeof(*_st_poll_data)); + if (!_st_poll_data) + return -1; + + _ST_POLLFDS = (struct pollfd *) malloc(ST_MIN_POLLFDS_SIZE * + sizeof(struct pollfd)); + if (!_ST_POLLFDS) { + free(_st_poll_data); + _st_poll_data = NULL; + return -1; + } + _ST_POLLFDS_SIZE = ST_MIN_POLLFDS_SIZE; + _ST_POLL_OSFD_CNT = 0; + + return 0; +} + +ST_HIDDEN int _st_poll_pollset_add(struct pollfd *pds, int npds) +{ + struct pollfd *pd; + struct pollfd *epd = pds + npds; + + for (pd = pds; pd < epd; pd++) { + if (pd->fd < 0 || !pd->events) { + errno = EINVAL; + return -1; + } + } + + _ST_POLL_OSFD_CNT += npds; + + return 0; +} + +/* ARGSUSED */ +ST_HIDDEN void _st_poll_pollset_del(struct pollfd *pds, int npds) +{ + _ST_POLL_OSFD_CNT -= npds; + ST_ASSERT(_ST_POLL_OSFD_CNT >= 0); +} + +ST_HIDDEN void _st_poll_dispatch(void) +{ + int timeout, nfd; + _st_clist_t *q; + st_utime_t min_timeout; + _st_pollq_t *pq; + struct pollfd *pds, *epds, *pollfds; + + /* + * Build up the array of struct pollfd to wait on. + * If existing array is not big enough, release it and allocate a new one. + */ + ST_ASSERT(_ST_POLL_OSFD_CNT >= 0); + if (_ST_POLL_OSFD_CNT > _ST_POLLFDS_SIZE) { + free(_ST_POLLFDS); + _ST_POLLFDS = (struct pollfd *) malloc((_ST_POLL_OSFD_CNT + 10) * + sizeof(struct pollfd)); + ST_ASSERT(_ST_POLLFDS != NULL); + _ST_POLLFDS_SIZE = _ST_POLL_OSFD_CNT + 10; + } + pollfds = _ST_POLLFDS; + + /* Gather all descriptors into one array */ + for (q = _ST_IOQ.next; q != &_ST_IOQ; q = q->next) { + pq = _ST_POLLQUEUE_PTR(q); + memcpy(pollfds, pq->pds, sizeof(struct pollfd) * pq->npds); + pollfds += pq->npds; + } + ST_ASSERT(pollfds <= _ST_POLLFDS + _ST_POLLFDS_SIZE); + + if (_ST_SLEEPQ == NULL) { + timeout = -1; + } else { + min_timeout = (_ST_SLEEPQ->due <= _ST_LAST_CLOCK) ? 0 : + (_ST_SLEEPQ->due - _ST_LAST_CLOCK); + timeout = (int) (min_timeout / 1000); + } + + /* Check for I/O operations */ + nfd = poll(_ST_POLLFDS, _ST_POLL_OSFD_CNT, timeout); + + /* Notify threads that are associated with the selected descriptors */ + if (nfd > 0) { + pollfds = _ST_POLLFDS; + for (q = _ST_IOQ.next; q != &_ST_IOQ; q = q->next) { + pq = _ST_POLLQUEUE_PTR(q); + epds = pollfds + pq->npds; + for (pds = pollfds; pds < epds; pds++) { + if (pds->revents) + break; + } + if (pds < epds) { + memcpy(pq->pds, pollfds, sizeof(struct pollfd) * pq->npds); + ST_REMOVE_LINK(&pq->links); + pq->on_ioq = 0; + + if (pq->thread->flags & _ST_FL_ON_SLEEPQ) + _ST_DEL_SLEEPQ(pq->thread); + pq->thread->state = _ST_ST_RUNNABLE; + _ST_ADD_RUNQ(pq->thread); + + _ST_POLL_OSFD_CNT -= pq->npds; + ST_ASSERT(_ST_POLL_OSFD_CNT >= 0); + } + pollfds = epds; + } + } +} + +/* ARGSUSED */ +ST_HIDDEN int _st_poll_fd_new(int osfd) +{ + return 0; +} + +/* ARGSUSED */ +ST_HIDDEN int _st_poll_fd_close(int osfd) +{ + /* + * We don't maintain I/O counts for poll event system + * so nothing to check here. + */ + return 0; +} + +ST_HIDDEN int _st_poll_fd_getlimit(void) +{ + /* zero means no specific limit */ + return 0; +} + +static _st_eventsys_t _st_poll_eventsys = { + "poll", + ST_EVENTSYS_POLL, + _st_poll_init, + _st_poll_dispatch, + _st_poll_pollset_add, + _st_poll_pollset_del, + _st_poll_fd_new, + _st_poll_fd_close, + _st_poll_fd_getlimit +}; +#endif /* MD_HAVE_POLL */ + + +#ifdef MD_HAVE_KQUEUE +/***************************************** + * kqueue event system + */ + +ST_HIDDEN int _st_kq_init(void) +{ + int err = 0; + int rv = 0; + + _st_kq_data = (struct _st_kqdata *) calloc(1, sizeof(*_st_kq_data)); + if (!_st_kq_data) + return -1; + + if ((_st_kq_data->kq = kqueue()) < 0) { + err = errno; + rv = -1; + goto cleanup_kq; + } + fcntl(_st_kq_data->kq, F_SETFD, FD_CLOEXEC); + _st_kq_data->pid = getpid(); + + /* + * Allocate file descriptor data array. + * FD_SETSIZE looks like good initial size. + */ + _st_kq_data->fd_data_size = FD_SETSIZE; + _st_kq_data->fd_data = (_kq_fd_data_t *)calloc(_st_kq_data->fd_data_size, sizeof(_kq_fd_data_t)); + if (!_st_kq_data->fd_data) { + err = errno; + rv = -1; + goto cleanup_kq; + } + + /* Allocate event lists */ + _st_kq_data->evtlist_size = ST_KQ_MIN_EVTLIST_SIZE; + _st_kq_data->evtlist = (struct kevent *)malloc(_st_kq_data->evtlist_size * sizeof(struct kevent)); + _st_kq_data->addlist_size = ST_KQ_MIN_EVTLIST_SIZE; + _st_kq_data->addlist = (struct kevent *)malloc(_st_kq_data->addlist_size * sizeof(struct kevent)); + _st_kq_data->dellist_size = ST_KQ_MIN_EVTLIST_SIZE; + _st_kq_data->dellist = (struct kevent *)malloc(_st_kq_data->dellist_size * sizeof(struct kevent)); + if (!_st_kq_data->evtlist || !_st_kq_data->addlist || + !_st_kq_data->dellist) { + err = ENOMEM; + rv = -1; + } + + cleanup_kq: + if (rv < 0) { + if (_st_kq_data->kq >= 0) + close(_st_kq_data->kq); + free(_st_kq_data->fd_data); + free(_st_kq_data->evtlist); + free(_st_kq_data->addlist); + free(_st_kq_data->dellist); + free(_st_kq_data); + _st_kq_data = NULL; + errno = err; + } + + return rv; +} + +ST_HIDDEN int _st_kq_fd_data_expand(int maxfd) +{ + _kq_fd_data_t *ptr; + int n = _st_kq_data->fd_data_size; + + while (maxfd >= n) + n <<= 1; + + ptr = (_kq_fd_data_t *)realloc(_st_kq_data->fd_data, n * sizeof(_kq_fd_data_t)); + if (!ptr) + return -1; + + memset(ptr + _st_kq_data->fd_data_size, 0, (n - _st_kq_data->fd_data_size) * sizeof(_kq_fd_data_t)); + + _st_kq_data->fd_data = ptr; + _st_kq_data->fd_data_size = n; + + return 0; +} + +ST_HIDDEN int _st_kq_addlist_expand(int avail) +{ + struct kevent *ptr; + int n = _st_kq_data->addlist_size; + + while (avail > n - _st_kq_data->addlist_cnt) + n <<= 1; + + ptr = (struct kevent *)realloc(_st_kq_data->addlist, n * sizeof(struct kevent)); + if (!ptr) + return -1; + + _st_kq_data->addlist = ptr; + _st_kq_data->addlist_size = n; + + /* + * Try to expand the result event list too + * (although we don't have to do it). + */ + ptr = (struct kevent *)realloc(_st_kq_data->evtlist, n * sizeof(struct kevent)); + if (ptr) { + _st_kq_data->evtlist = ptr; + _st_kq_data->evtlist_size = n; + } + + return 0; +} + +ST_HIDDEN void _st_kq_addlist_add(const struct kevent *kev) +{ + ST_ASSERT(_st_kq_data->addlist_cnt < _st_kq_data->addlist_size); + memcpy(_st_kq_data->addlist + _st_kq_data->addlist_cnt, kev, sizeof(struct kevent)); + _st_kq_data->addlist_cnt++; +} + +ST_HIDDEN void _st_kq_dellist_add(const struct kevent *kev) +{ + int n = _st_kq_data->dellist_size; + + if (_st_kq_data->dellist_cnt >= n) { + struct kevent *ptr; + + n <<= 1; + ptr = (struct kevent *)realloc(_st_kq_data->dellist, n * sizeof(struct kevent)); + if (!ptr) { + /* See comment in _st_kq_pollset_del() */ + return; + } + + _st_kq_data->dellist = ptr; + _st_kq_data->dellist_size = n; + } + + memcpy(_st_kq_data->dellist + _st_kq_data->dellist_cnt, kev, sizeof(struct kevent)); + _st_kq_data->dellist_cnt++; +} + +ST_HIDDEN int _st_kq_pollset_add(struct pollfd *pds, int npds) +{ + struct kevent kev; + struct pollfd *pd; + struct pollfd *epd = pds + npds; + + /* + * Pollset adding is "atomic". That is, either it succeeded for + * all descriptors in the set or it failed. It means that we + * need to do all the checks up front so we don't have to + * "unwind" if adding of one of the descriptors failed. + */ + for (pd = pds; pd < epd; pd++) { + /* POLLIN and/or POLLOUT must be set, but nothing else */ + if (pd->fd < 0 || !pd->events || (pd->events & ~(POLLIN | POLLOUT))) { + errno = EINVAL; + return -1; + } + if (pd->fd >= _st_kq_data->fd_data_size && + _st_kq_fd_data_expand(pd->fd) < 0) + return -1; + } + + /* + * Make sure we have enough room in the addlist for twice as many + * descriptors as in the pollset (for both READ and WRITE filters). + */ + npds <<= 1; + if (npds > _st_kq_data->addlist_size - _st_kq_data->addlist_cnt && _st_kq_addlist_expand(npds) < 0) + return -1; + + for (pd = pds; pd < epd; pd++) { + if ((pd->events & POLLIN) && (_ST_KQ_READ_CNT(pd->fd)++ == 0)) { + memset(&kev, 0, sizeof(kev)); + kev.ident = pd->fd; + kev.filter = EVFILT_READ; +#ifdef NOTE_EOF + /* Make it behave like select() and poll() */ + kev.fflags = NOTE_EOF; +#endif + kev.flags = (EV_ADD | EV_ONESHOT); + _st_kq_addlist_add(&kev); + } + if ((pd->events & POLLOUT) && (_ST_KQ_WRITE_CNT(pd->fd)++ == 0)) { + memset(&kev, 0, sizeof(kev)); + kev.ident = pd->fd; + kev.filter = EVFILT_WRITE; + kev.flags = (EV_ADD | EV_ONESHOT); + _st_kq_addlist_add(&kev); + } + } + + return 0; +} + +ST_HIDDEN void _st_kq_pollset_del(struct pollfd *pds, int npds) +{ + struct kevent kev; + struct pollfd *pd; + struct pollfd *epd = pds + npds; + + /* + * It's OK if deleting fails because a descriptor will either be + * closed or fire only once (we set EV_ONESHOT flag). + */ + _st_kq_data->dellist_cnt = 0; + for (pd = pds; pd < epd; pd++) { + if ((pd->events & POLLIN) && (--_ST_KQ_READ_CNT(pd->fd) == 0)) { + memset(&kev, 0, sizeof(kev)); + kev.ident = pd->fd; + kev.filter = EVFILT_READ; + kev.flags = EV_DELETE; + _st_kq_dellist_add(&kev); + } + if ((pd->events & POLLOUT) && (--_ST_KQ_WRITE_CNT(pd->fd) == 0)) { + memset(&kev, 0, sizeof(kev)); + kev.ident = pd->fd; + kev.filter = EVFILT_WRITE; + kev.flags = EV_DELETE; + _st_kq_dellist_add(&kev); + } + } + + if (_st_kq_data->dellist_cnt > 0) { + /* + * We do "synchronous" kqueue deletes to avoid deleting + * closed descriptors and other possible problems. + */ + int rv; + do { + /* This kevent() won't block since result list size is 0 */ + rv = kevent(_st_kq_data->kq, _st_kq_data->dellist, _st_kq_data->dellist_cnt, NULL, 0, NULL); + } while (rv < 0 && errno == EINTR); + } +} + +ST_HIDDEN void _st_kq_dispatch(void) +{ + struct timespec timeout, *tsp; + struct kevent kev; + st_utime_t min_timeout; + _st_clist_t *q; + _st_pollq_t *pq; + struct pollfd *pds, *epds; + int nfd, i, osfd, notify, filter; + short events, revents; + + if (_ST_SLEEPQ == NULL) { + tsp = NULL; + } else { + min_timeout = (_ST_SLEEPQ->due <= _ST_LAST_CLOCK) ? 0 : (_ST_SLEEPQ->due - _ST_LAST_CLOCK); + timeout.tv_sec = (time_t) (min_timeout / 1000000); + timeout.tv_nsec = (long) ((min_timeout % 1000000) * 1000); + tsp = &timeout; + } + + retry_kevent: + /* Check for I/O operations */ + nfd = kevent(_st_kq_data->kq, + _st_kq_data->addlist, _st_kq_data->addlist_cnt, + _st_kq_data->evtlist, _st_kq_data->evtlist_size, tsp); + + _st_kq_data->addlist_cnt = 0; + + if (nfd > 0) { + for (i = 0; i < nfd; i++) { + osfd = _st_kq_data->evtlist[i].ident; + filter = _st_kq_data->evtlist[i].filter; + + if (filter == EVFILT_READ) { + _ST_KQ_REVENTS(osfd) |= POLLIN; + } else if (filter == EVFILT_WRITE) { + _ST_KQ_REVENTS(osfd) |= POLLOUT; + } + if (_st_kq_data->evtlist[i].flags & EV_ERROR) { + if (_st_kq_data->evtlist[i].data == EBADF) { + _ST_KQ_REVENTS(osfd) |= POLLNVAL; + } else { + _ST_KQ_REVENTS(osfd) |= POLLERR; + } + } + } + + _st_kq_data->dellist_cnt = 0; + + for (q = _ST_IOQ.next; q != &_ST_IOQ; q = q->next) { + pq = _ST_POLLQUEUE_PTR(q); + notify = 0; + epds = pq->pds + pq->npds; + + for (pds = pq->pds; pds < epds; pds++) { + osfd = pds->fd; + events = pds->events; + revents = (short)(_ST_KQ_REVENTS(osfd) & ~(POLLIN | POLLOUT)); + if ((events & POLLIN) && (_ST_KQ_REVENTS(osfd) & POLLIN)) { + revents |= POLLIN; + } + if ((events & POLLOUT) && (_ST_KQ_REVENTS(osfd) & POLLOUT)) { + revents |= POLLOUT; + } + pds->revents = revents; + if (revents) { + notify = 1; + } + } + if (notify) { + ST_REMOVE_LINK(&pq->links); + pq->on_ioq = 0; + for (pds = pq->pds; pds < epds; pds++) { + osfd = pds->fd; + events = pds->events; + /* + * We set EV_ONESHOT flag so we only need to delete + * descriptor if it didn't fire. + */ + if ((events & POLLIN) && (--_ST_KQ_READ_CNT(osfd) == 0) && ((_ST_KQ_REVENTS(osfd) & POLLIN) == 0)) { + memset(&kev, 0, sizeof(kev)); + kev.ident = osfd; + kev.filter = EVFILT_READ; + kev.flags = EV_DELETE; + _st_kq_dellist_add(&kev); + } + if ((events & POLLOUT) && (--_ST_KQ_WRITE_CNT(osfd) == 0) && ((_ST_KQ_REVENTS(osfd) & POLLOUT) == 0)) { + memset(&kev, 0, sizeof(kev)); + kev.ident = osfd; + kev.filter = EVFILT_WRITE; + kev.flags = EV_DELETE; + _st_kq_dellist_add(&kev); + } + } + + if (pq->thread->flags & _ST_FL_ON_SLEEPQ) + _ST_DEL_SLEEPQ(pq->thread); + pq->thread->state = _ST_ST_RUNNABLE; + _ST_ADD_RUNQ(pq->thread); + } + } + + if (_st_kq_data->dellist_cnt > 0) { + int rv; + do { + /* This kevent() won't block since result list size is 0 */ + rv = kevent(_st_kq_data->kq, _st_kq_data->dellist, _st_kq_data->dellist_cnt, NULL, 0, NULL); + } while (rv < 0 && errno == EINTR); + } + + for (i = 0; i < nfd; i++) { + osfd = _st_kq_data->evtlist[i].ident; + _ST_KQ_REVENTS(osfd) = 0; + } + + } else if (nfd < 0) { + if (errno == EBADF && _st_kq_data->pid != getpid()) { + /* We probably forked, reinitialize kqueue */ + if ((_st_kq_data->kq = kqueue()) < 0) { + /* There is nothing we can do here, will retry later */ + return; + } + fcntl(_st_kq_data->kq, F_SETFD, FD_CLOEXEC); + _st_kq_data->pid = getpid(); + /* Re-register all descriptors on ioq with new kqueue */ + memset(_st_kq_data->fd_data, 0, _st_kq_data->fd_data_size * sizeof(_kq_fd_data_t)); + for (q = _ST_IOQ.next; q != &_ST_IOQ; q = q->next) { + pq = _ST_POLLQUEUE_PTR(q); + _st_kq_pollset_add(pq->pds, pq->npds); + } + goto retry_kevent; + } + } +} + +ST_HIDDEN int _st_kq_fd_new(int osfd) +{ + if (osfd >= _st_kq_data->fd_data_size && _st_kq_fd_data_expand(osfd) < 0) + return -1; + + return 0; +} + +ST_HIDDEN int _st_kq_fd_close(int osfd) +{ + if (_ST_KQ_READ_CNT(osfd) || _ST_KQ_WRITE_CNT(osfd)) { + errno = EBUSY; + return -1; + } + + return 0; +} + +ST_HIDDEN int _st_kq_fd_getlimit(void) +{ + /* zero means no specific limit */ + return 0; +} + +static _st_eventsys_t _st_kq_eventsys = { + "kqueue", + ST_EVENTSYS_ALT, + _st_kq_init, + _st_kq_dispatch, + _st_kq_pollset_add, + _st_kq_pollset_del, + _st_kq_fd_new, + _st_kq_fd_close, + _st_kq_fd_getlimit +}; +#endif /* MD_HAVE_KQUEUE */ + + +#ifdef MD_HAVE_EPOLL +/***************************************** + * epoll event system + */ + +ST_HIDDEN int _st_epoll_init(void) +{ + int fdlim; + int err = 0; + int rv = 0; + + _st_epoll_data = (struct _st_epolldata *) calloc(1, sizeof(*_st_epoll_data)); + if (!_st_epoll_data) + return -1; + + fdlim = st_getfdlimit(); + _st_epoll_data->fd_hint = (fdlim > 0 && fdlim < ST_EPOLL_EVTLIST_SIZE) ? fdlim : ST_EPOLL_EVTLIST_SIZE; + + if ((_st_epoll_data->epfd = epoll_create(_st_epoll_data->fd_hint)) < 0) { + err = errno; + rv = -1; + goto cleanup_epoll; + } + fcntl(_st_epoll_data->epfd, F_SETFD, FD_CLOEXEC); + _st_epoll_data->pid = getpid(); + + /* Allocate file descriptor data array */ + _st_epoll_data->fd_data_size = _st_epoll_data->fd_hint; + _st_epoll_data->fd_data = (_epoll_fd_data_t *)calloc(_st_epoll_data->fd_data_size, sizeof(_epoll_fd_data_t)); + if (!_st_epoll_data->fd_data) { + err = errno; + rv = -1; + goto cleanup_epoll; + } + + /* Allocate event lists */ + _st_epoll_data->evtlist_size = _st_epoll_data->fd_hint; + _st_epoll_data->evtlist = (struct epoll_event *)malloc(_st_epoll_data->evtlist_size * sizeof(struct epoll_event)); + if (!_st_epoll_data->evtlist) { + err = errno; + rv = -1; + } + + cleanup_epoll: + if (rv < 0) { + if (_st_epoll_data->epfd >= 0) + close(_st_epoll_data->epfd); + free(_st_epoll_data->fd_data); + free(_st_epoll_data->evtlist); + free(_st_epoll_data); + _st_epoll_data = NULL; + errno = err; + } + + return rv; +} + +ST_HIDDEN int _st_epoll_fd_data_expand(int maxfd) +{ + _epoll_fd_data_t *ptr; + int n = _st_epoll_data->fd_data_size; + + while (maxfd >= n) + n <<= 1; + + ptr = (_epoll_fd_data_t *)realloc(_st_epoll_data->fd_data, n * sizeof(_epoll_fd_data_t)); + if (!ptr) + return -1; + + memset(ptr + _st_epoll_data->fd_data_size, 0, (n - _st_epoll_data->fd_data_size) * sizeof(_epoll_fd_data_t)); + + _st_epoll_data->fd_data = ptr; + _st_epoll_data->fd_data_size = n; + + return 0; +} + +ST_HIDDEN void _st_epoll_evtlist_expand(void) +{ + struct epoll_event *ptr; + int n = _st_epoll_data->evtlist_size; + + while (_st_epoll_data->evtlist_cnt > n) + n <<= 1; + + ptr = (struct epoll_event *)realloc(_st_epoll_data->evtlist, n * sizeof(struct epoll_event)); + if (ptr) { + _st_epoll_data->evtlist = ptr; + _st_epoll_data->evtlist_size = n; + } +} + +ST_HIDDEN void _st_epoll_pollset_del(struct pollfd *pds, int npds) +{ + struct epoll_event ev; + struct pollfd *pd; + struct pollfd *epd = pds + npds; + int old_events, events, op; + + /* + * It's more or less OK if deleting fails because a descriptor + * will either be closed or deleted in dispatch function after + * it fires. + */ + for (pd = pds; pd < epd; pd++) { + old_events = _ST_EPOLL_EVENTS(pd->fd); + + if (pd->events & POLLIN) + _ST_EPOLL_READ_CNT(pd->fd)--; + if (pd->events & POLLOUT) + _ST_EPOLL_WRITE_CNT(pd->fd)--; + if (pd->events & POLLPRI) + _ST_EPOLL_EXCEP_CNT(pd->fd)--; + + events = _ST_EPOLL_EVENTS(pd->fd); + /* + * The _ST_EPOLL_REVENTS check below is needed so we can use + * this function inside dispatch(). Outside of dispatch() + * _ST_EPOLL_REVENTS is always zero for all descriptors. + */ + if (events != old_events && _ST_EPOLL_REVENTS(pd->fd) == 0) { + op = events ? EPOLL_CTL_MOD : EPOLL_CTL_DEL; + ev.events = events; + ev.data.fd = pd->fd; + if (epoll_ctl(_st_epoll_data->epfd, op, pd->fd, &ev) == 0 && op == EPOLL_CTL_DEL) { + _st_epoll_data->evtlist_cnt--; + } + } + } +} + +ST_HIDDEN int _st_epoll_pollset_add(struct pollfd *pds, int npds) +{ + struct epoll_event ev; + int i, fd; + int old_events, events, op; + + /* Do as many checks as possible up front */ + for (i = 0; i < npds; i++) { + fd = pds[i].fd; + if (fd < 0 || !pds[i].events || + (pds[i].events & ~(POLLIN | POLLOUT | POLLPRI))) { + errno = EINVAL; + return -1; + } + if (fd >= _st_epoll_data->fd_data_size && _st_epoll_fd_data_expand(fd) < 0) + return -1; + } + + for (i = 0; i < npds; i++) { + fd = pds[i].fd; + old_events = _ST_EPOLL_EVENTS(fd); + + if (pds[i].events & POLLIN) + _ST_EPOLL_READ_CNT(fd)++; + if (pds[i].events & POLLOUT) + _ST_EPOLL_WRITE_CNT(fd)++; + if (pds[i].events & POLLPRI) + _ST_EPOLL_EXCEP_CNT(fd)++; + + events = _ST_EPOLL_EVENTS(fd); + if (events != old_events) { + op = old_events ? EPOLL_CTL_MOD : EPOLL_CTL_ADD; + ev.events = events; + ev.data.fd = fd; + if (epoll_ctl(_st_epoll_data->epfd, op, fd, &ev) < 0 && (op != EPOLL_CTL_ADD || errno != EEXIST)) + break; + if (op == EPOLL_CTL_ADD) { + _st_epoll_data->evtlist_cnt++; + if (_st_epoll_data->evtlist_cnt > _st_epoll_data->evtlist_size) + _st_epoll_evtlist_expand(); + } + } + } + + if (i < npds) { + /* Error */ + int err = errno; + /* Unroll the state */ + _st_epoll_pollset_del(pds, i + 1); + errno = err; + return -1; + } + + return 0; +} + +ST_HIDDEN void _st_epoll_dispatch(void) +{ + st_utime_t min_timeout; + _st_clist_t *q; + _st_pollq_t *pq; + struct pollfd *pds, *epds; + struct epoll_event ev; + int timeout, nfd, i, osfd, notify; + int events, op; + short revents; + + if (_ST_SLEEPQ == NULL) { + timeout = -1; + } else { + min_timeout = (_ST_SLEEPQ->due <= _ST_LAST_CLOCK) ? 0 : (_ST_SLEEPQ->due - _ST_LAST_CLOCK); + timeout = (int) (min_timeout / 1000); + } + + if (_st_epoll_data->pid != getpid()) { + /* We probably forked, reinitialize epoll set */ + close(_st_epoll_data->epfd); + _st_epoll_data->epfd = epoll_create(_st_epoll_data->fd_hint); + if (_st_epoll_data->epfd < 0) { + /* There is nothing we can do here, will retry later */ + return; + } + fcntl(_st_epoll_data->epfd, F_SETFD, FD_CLOEXEC); + _st_epoll_data->pid = getpid(); + + /* Put all descriptors on ioq into new epoll set */ + memset(_st_epoll_data->fd_data, 0, _st_epoll_data->fd_data_size * sizeof(_epoll_fd_data_t)); + _st_epoll_data->evtlist_cnt = 0; + for (q = _ST_IOQ.next; q != &_ST_IOQ; q = q->next) { + pq = _ST_POLLQUEUE_PTR(q); + _st_epoll_pollset_add(pq->pds, pq->npds); + } + } + + /* Check for I/O operations */ + nfd = epoll_wait(_st_epoll_data->epfd, _st_epoll_data->evtlist, _st_epoll_data->evtlist_size, timeout); + + if (nfd > 0) { + for (i = 0; i < nfd; i++) { + osfd = _st_epoll_data->evtlist[i].data.fd; + _ST_EPOLL_REVENTS(osfd) = _st_epoll_data->evtlist[i].events; + if (_ST_EPOLL_REVENTS(osfd) & (EPOLLERR | EPOLLHUP)) { + /* Also set I/O bits on error */ + _ST_EPOLL_REVENTS(osfd) |= _ST_EPOLL_EVENTS(osfd); + } + } + + for (q = _ST_IOQ.next; q != &_ST_IOQ; q = q->next) { + pq = _ST_POLLQUEUE_PTR(q); + notify = 0; + epds = pq->pds + pq->npds; + + for (pds = pq->pds; pds < epds; pds++) { + if (_ST_EPOLL_REVENTS(pds->fd) == 0) { + pds->revents = 0; + continue; + } + osfd = pds->fd; + events = pds->events; + revents = 0; + if ((events & POLLIN) && (_ST_EPOLL_REVENTS(osfd) & EPOLLIN)) + revents |= POLLIN; + if ((events & POLLOUT) && (_ST_EPOLL_REVENTS(osfd) & EPOLLOUT)) + revents |= POLLOUT; + if ((events & POLLPRI) && (_ST_EPOLL_REVENTS(osfd) & EPOLLPRI)) + revents |= POLLPRI; + if (_ST_EPOLL_REVENTS(osfd) & EPOLLERR) + revents |= POLLERR; + if (_ST_EPOLL_REVENTS(osfd) & EPOLLHUP) + revents |= POLLHUP; + + pds->revents = revents; + if (revents) { + notify = 1; + } + } + if (notify) { + ST_REMOVE_LINK(&pq->links); + pq->on_ioq = 0; + /* + * Here we will only delete/modify descriptors that + * didn't fire (see comments in _st_epoll_pollset_del()). + */ + _st_epoll_pollset_del(pq->pds, pq->npds); + + if (pq->thread->flags & _ST_FL_ON_SLEEPQ) + _ST_DEL_SLEEPQ(pq->thread); + pq->thread->state = _ST_ST_RUNNABLE; + _ST_ADD_RUNQ(pq->thread); + } + } + + for (i = 0; i < nfd; i++) { + /* Delete/modify descriptors that fired */ + osfd = _st_epoll_data->evtlist[i].data.fd; + _ST_EPOLL_REVENTS(osfd) = 0; + events = _ST_EPOLL_EVENTS(osfd); + op = events ? EPOLL_CTL_MOD : EPOLL_CTL_DEL; + ev.events = events; + ev.data.fd = osfd; + if (epoll_ctl(_st_epoll_data->epfd, op, osfd, &ev) == 0 && op == EPOLL_CTL_DEL) { + _st_epoll_data->evtlist_cnt--; + } + } + } +} + +ST_HIDDEN int _st_epoll_fd_new(int osfd) +{ + if (osfd >= _st_epoll_data->fd_data_size && _st_epoll_fd_data_expand(osfd) < 0) + return -1; + + return 0; +} + +ST_HIDDEN int _st_epoll_fd_close(int osfd) +{ + if (_ST_EPOLL_READ_CNT(osfd) || _ST_EPOLL_WRITE_CNT(osfd) || _ST_EPOLL_EXCEP_CNT(osfd)) { + errno = EBUSY; + return -1; + } + + return 0; +} + +ST_HIDDEN int _st_epoll_fd_getlimit(void) +{ + /* zero means no specific limit */ + return 0; +} + +/* + * Check if epoll functions are just stubs. + */ +ST_HIDDEN int _st_epoll_is_supported(void) +{ + struct epoll_event ev; + + ev.events = EPOLLIN; + ev.data.ptr = NULL; + /* Guaranteed to fail */ + epoll_ctl(-1, EPOLL_CTL_ADD, -1, &ev); + + return (errno != ENOSYS); +} + +static _st_eventsys_t _st_epoll_eventsys = { + "epoll", + ST_EVENTSYS_ALT, + _st_epoll_init, + _st_epoll_dispatch, + _st_epoll_pollset_add, + _st_epoll_pollset_del, + _st_epoll_fd_new, + _st_epoll_fd_close, + _st_epoll_fd_getlimit +}; +#endif /* MD_HAVE_EPOLL */ + + +/***************************************** + * Public functions + */ + +int st_set_eventsys(int eventsys) +{ + if (_st_eventsys) { + errno = EBUSY; + return -1; + } + + switch (eventsys) { + case ST_EVENTSYS_DEFAULT: +#ifdef USE_POLL + _st_eventsys = &_st_poll_eventsys; +#else + _st_eventsys = &_st_select_eventsys; +#endif + break; + case ST_EVENTSYS_SELECT: + _st_eventsys = &_st_select_eventsys; + break; +#ifdef MD_HAVE_POLL + case ST_EVENTSYS_POLL: + _st_eventsys = &_st_poll_eventsys; + break; +#endif + case ST_EVENTSYS_ALT: +#if defined (MD_HAVE_KQUEUE) + _st_eventsys = &_st_kq_eventsys; +#elif defined (MD_HAVE_EPOLL) + if (_st_epoll_is_supported()) + _st_eventsys = &_st_epoll_eventsys; +#endif + break; + default: + errno = EINVAL; + return -1; + } + + return 0; +} + +int st_get_eventsys(void) +{ + return _st_eventsys ? _st_eventsys->val : -1; +} + +const char *st_get_eventsys_name(void) +{ + return _st_eventsys ? _st_eventsys->name : ""; +} + diff --git a/trunk/3rdparty/st-srs/examples/Makefile b/trunk/3rdparty/st-srs/examples/Makefile new file mode 100644 index 000000000..31c0a6e24 --- /dev/null +++ b/trunk/3rdparty/st-srs/examples/Makefile @@ -0,0 +1,115 @@ +# +# Portions created by SGI are Copyright (C) 2000 Silicon Graphics, Inc. +# All Rights Reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions +# are met: +# +# 1. Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# 2. Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# 3. Neither the name of Silicon Graphics, Inc. nor the names of its +# contributors may be used to endorse or promote products derived from +# this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +# HOLDERS AND CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED +# TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +########################## +# Supported OSes: +# +# AIX +# FREEBSD +# HPUX +# HPUX_64 +# IRIX +# IRIX_64 +# LINUX +# LINUX_IA64 +# NETBSD +# OPENBSD +# OSF1 +# SOLARIS +# SOLARIS_64 + +########################## + +CC = cc + +SHELL = /bin/sh +ECHO = /bin/echo + +DEPTH = .. +BUILD = +TARGETDIR = + +DEFINES = +CFLAGS = +OTHER_FLAGS = + +OBJDIR = $(DEPTH)/$(TARGETDIR) +INCDIR = $(DEPTH)/$(TARGETDIR) +LIBST = $(OBJDIR)/libst.a +HEADER = $(INCDIR)/st.h + +LIBRESOLV = +EXTRALIBS = + +ifeq ($(OS),) +EXAMPLES = unknown +else +EXAMPLES = $(OBJDIR)/lookupdns $(OBJDIR)/proxy $(OBJDIR)/server +endif + + +########################## +# Platform section. +# + +ifeq (DARWIN, $(findstring DARWIN, $(OS))) +LIBRESOLV = -lresolv +endif + +ifeq (LINUX, $(findstring LINUX, $(OS))) +LIBRESOLV = -lresolv +endif + +ifeq (SOLARIS, $(findstring SOLARIS, $(OS))) +LIBRESOLV = -lresolv +EXTRALIBS = -lsocket -lnsl +endif + +# +# End of platform section. +########################## + + +all: $(EXAMPLES) + +$(OBJDIR)/lookupdns: lookupdns.c $(OBJDIR)/res.o $(LIBST) $(HEADER) + $(CC) $(CFLAGS) -I$(INCDIR) lookupdns.c $(OBJDIR)/res.o $(LIBST) $(LIBRESOLV) $(EXTRALIBS) -o $@ + +$(OBJDIR)/proxy: proxy.c $(LIBST) $(HEADER) + $(CC) $(CFLAGS) -I$(INCDIR) proxy.c $(LIBST) $(EXTRALIBS) -o $@ + +$(OBJDIR)/server: server.c $(OBJDIR)/error.o $(LIBST) $(HEADER) + $(CC) $(CFLAGS) -I$(INCDIR) server.c $(OBJDIR)/error.o $(LIBST) $(EXTRALIBS) -o $@ + +$(OBJDIR)/%.o: %.c + $(CC) $(CFLAGS) -I$(INCDIR) -c $< -o $@ + +.DEFAULT: + @cd $(DEPTH); $(MAKE) $@ + diff --git a/trunk/3rdparty/st-srs/examples/README b/trunk/3rdparty/st-srs/examples/README new file mode 100644 index 000000000..646d4f623 --- /dev/null +++ b/trunk/3rdparty/st-srs/examples/README @@ -0,0 +1,98 @@ +Portions created by SGI are Copyright (C) 2000 Silicon Graphics, Inc. +All Rights Reserved. + + +This directory contains three example programs. + + +--------------------------------------------------------------------------- + +PROGRAM + + lookupdns + +FILES + + lookupdns.c + res.c + +USAGE + + lookupdns [] ... + +DESCRIPTION + + This program performs asynchronous DNS host name resolution and reports + IP address for each specified as a command line argument. + One ST thread is created for each host name. All threads do host name + resolution concurrently. + + +--------------------------------------------------------------------------- + +PROGRAM + + proxy + +FILES + + proxy.c + +USAGE + + proxy -l -r [-p ] [-S] + + -l bind to local address specified as []: + -r connect to remote address specified as : + -p create specified number of processes + -S serialize accept() calls from different processes + on the same listening socket (if needed). + +DESCRIPTION + + This program acts as a generic gateway. It listens for connections to a + local address. Upon accepting a client connection, it connects to the + specified remote address and then just pumps the data through without any + modification. + + +--------------------------------------------------------------------------- + +PROGRAM + + server + +FILES + + server.c + error.c + +USAGE + + server -l [] + + -l open all log files in specified directory. + + Possible options: + + -b : bind to specified address (multiple addresses + are permitted) + -p create specified number of processes + -t : specify thread limits per listening socket + across all processes + -u change server's user id to specified value + -q set max length of pending connections queue + -a enable access logging + -i run in interactive mode (useful for debugging) + -S serialize accept() calls from different processes + on the same listening socket (if needed). + +DESCRIPTION + + This program is a general server example. It accepts a client connection + and outputs a short HTML page. It can be easily adapted to provide + other services. + + +--------------------------------------------------------------------------- + diff --git a/trunk/3rdparty/st-srs/examples/error.c b/trunk/3rdparty/st-srs/examples/error.c new file mode 100644 index 000000000..0b2e77287 --- /dev/null +++ b/trunk/3rdparty/st-srs/examples/error.c @@ -0,0 +1,168 @@ +/* + * Portions created by SGI are Copyright (C) 2000 Silicon Graphics, Inc. + * All Rights Reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of Silicon Graphics, Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * HOLDERS AND CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED + * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#include +#include +#include +#include +#include +#include "st.h" + +/* + * Simple error reporting functions. + * Suggested in W. Richard Stevens' "Advanced Programming in UNIX + * Environment". + */ + +#define MAXLINE 4096 /* max line length */ + +static void err_doit(int, int, const char *, va_list); + + +/* + * Nonfatal error related to a system call. + * Print a message and return. + */ +void err_sys_report(int fd, const char *fmt, ...) +{ + va_list ap; + + va_start(ap, fmt); + err_doit(fd, 1, fmt, ap); + va_end(ap); +} + + +/* + * Fatal error related to a system call. + * Print a message and terminate. + */ +void err_sys_quit(int fd, const char *fmt, ...) +{ + va_list ap; + + va_start(ap, fmt); + err_doit(fd, 1, fmt, ap); + va_end(ap); + exit(1); +} + + +/* + * Fatal error related to a system call. + * Print a message, dump core, and terminate. + */ +void err_sys_dump(int fd, const char *fmt, ...) +{ + va_list ap; + + va_start(ap, fmt); + err_doit(fd, 1, fmt, ap); + va_end(ap); + abort(); /* dump core and terminate */ + exit(1); /* shouldn't get here */ +} + + +/* + * Nonfatal error unrelated to a system call. + * Print a message and return. + */ +void err_report(int fd, const char *fmt, ...) +{ + va_list ap; + + va_start(ap, fmt); + err_doit(fd, 0, fmt, ap); + va_end(ap); +} + + +/* + * Fatal error unrelated to a system call. + * Print a message and terminate. + */ +void err_quit(int fd, const char *fmt, ...) +{ + va_list ap; + + va_start(ap, fmt); + err_doit(fd, 0, fmt, ap); + va_end(ap); + exit(1); +} + + +/* + * Return a pointer to a string containing current time. + */ +char *err_tstamp(void) +{ + static char *months[] = { "Jan", "Feb", "Mar", "Apr", "May", "Jun", + "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" }; + static char str[32]; + static time_t lastt = 0; + struct tm *tmp; + time_t currt = st_time(); + + if (currt == lastt) + return str; + + tmp = localtime(&currt); + sprintf(str, "[%02d/%s/%d:%02d:%02d:%02d] ", tmp->tm_mday, + months[tmp->tm_mon], 1900 + tmp->tm_year, tmp->tm_hour, + tmp->tm_min, tmp->tm_sec); + lastt = currt; + + return str; +} + + +/* + * Print a message and return to caller. + * Caller specifies "errnoflag". + */ +static void err_doit(int fd, int errnoflag, const char *fmt, va_list ap) +{ + int errno_save; + char buf[MAXLINE]; + + errno_save = errno; /* value caller might want printed */ + strcpy(buf, err_tstamp()); /* prepend a message with time stamp */ + vsprintf(buf + strlen(buf), fmt, ap); + if (errnoflag) + sprintf(buf + strlen(buf), ": %s\n", strerror(errno_save)); + else + strcat(buf, "\n"); + write(fd, buf, strlen(buf)); + errno = errno_save; +} + diff --git a/trunk/3rdparty/st-srs/examples/lookupdns.c b/trunk/3rdparty/st-srs/examples/lookupdns.c new file mode 100644 index 000000000..98f6ec5d8 --- /dev/null +++ b/trunk/3rdparty/st-srs/examples/lookupdns.c @@ -0,0 +1,103 @@ +/* + * Portions created by SGI are Copyright (C) 2000 Silicon Graphics, Inc. + * All Rights Reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of Silicon Graphics, Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * HOLDERS AND CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED + * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "st.h" + +#if !defined(NETDB_INTERNAL) && defined(h_NETDB_INTERNAL) +#define NETDB_INTERNAL h_NETDB_INTERNAL +#endif + +/* Resolution timeout (in microseconds) */ +#define TIMEOUT (2*1000000LL) + +/* External function defined in the res.c file */ +int dns_getaddr(const char *host, struct in_addr *addr, st_utime_t timeout); + + +void *do_resolve(void *host) +{ + struct in_addr addr; + + /* Use dns_getaddr() instead of gethostbyname(3) to get IP address */ + if (dns_getaddr(host, &addr, TIMEOUT) < 0) { + fprintf(stderr, "dns_getaddr: can't resolve %s: ", (char *)host); + if (h_errno == NETDB_INTERNAL) + perror(""); + else + herror(""); + } else + printf("%-40s %s\n", (char *)host, inet_ntoa(addr)); + + return NULL; +} + + +/* + * Asynchronous DNS host name resolution. This program creates one + * ST thread for each host name (specified as command line arguments). + * All threads do host name resolution concurrently. + */ +int main(int argc, char *argv[]) +{ + int i; + + if (argc < 2) { + fprintf(stderr, "Usage: %s [] ...\n", argv[0]); + exit(1); + } + + if (st_init() < 0) { + perror("st_init"); + exit(1); + } + + for (i = 1; i < argc; i++) { + /* Create a separate thread for each host name */ + if (st_thread_create(do_resolve, argv[i], 0, 0) == NULL) { + perror("st_thread_create"); + exit(1); + } + } + + st_thread_exit(NULL); + + /* NOTREACHED */ + return 1; +} + diff --git a/trunk/3rdparty/st-srs/examples/proxy.c b/trunk/3rdparty/st-srs/examples/proxy.c new file mode 100644 index 000000000..2f4636d6b --- /dev/null +++ b/trunk/3rdparty/st-srs/examples/proxy.c @@ -0,0 +1,541 @@ +/* + * Portions created by SGI are Copyright (C) 2000 Silicon Graphics, Inc. + * All Rights Reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of Silicon Graphics, Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * HOLDERS AND CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED + * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "st.h" + +#define IOBUFSIZE (16*1024) + +#define IOV_LEN 256 +#define IOV_COUNT (IOBUFSIZE / IOV_LEN) + +#ifndef INADDR_NONE +#define INADDR_NONE 0xffffffff +#endif + +static char *prog; /* Program name */ +static struct sockaddr_in rmt_addr; /* Remote address */ + +static unsigned long testing; +#define TESTING_VERBOSE 0x1 +#define TESTING_READV 0x2 +#define TESTING_READ_RESID 0x4 +#define TESTING_WRITEV 0x8 +#define TESTING_WRITE_RESID 0x10 + +static void read_address(const char *str, struct sockaddr_in *sin); +static void start_daemon(void); +static int cpu_count(void); +static void set_concurrency(int nproc); +static void *handle_request(void *arg); +static void print_sys_error(const char *msg); + + +/* + * This program acts as a generic gateway. It listens for connections + * to a local address ('-l' option). Upon accepting a client connection, + * it connects to the specified remote address ('-r' option) and then + * just pumps the data through without any modification. + */ +int main(int argc, char *argv[]) +{ + extern char *optarg; + int opt, sock, n; + int laddr, raddr, num_procs, alt_ev, one_process; + int serialize_accept = 0; + struct sockaddr_in lcl_addr, cli_addr; + st_netfd_t cli_nfd, srv_nfd; + + prog = argv[0]; + num_procs = laddr = raddr = alt_ev = one_process = 0; + + /* Parse arguments */ + while((opt = getopt(argc, argv, "l:r:p:Saht:X")) != EOF) { + switch (opt) { + case 'a': + alt_ev = 1; + break; + case 'l': + read_address(optarg, &lcl_addr); + laddr = 1; + break; + case 'r': + read_address(optarg, &rmt_addr); + if (rmt_addr.sin_addr.s_addr == INADDR_ANY) { + fprintf(stderr, "%s: invalid remote address: %s\n", prog, optarg); + exit(1); + } + raddr = 1; + break; + case 'p': + num_procs = atoi(optarg); + if (num_procs < 1) { + fprintf(stderr, "%s: invalid number of processes: %s\n", prog, optarg); + exit(1); + } + break; + case 'S': + /* + * Serialization decision is tricky on some platforms. For example, + * Solaris 2.6 and above has kernel sockets implementation, so supposedly + * there is no need for serialization. The ST library may be compiled + * on one OS version, but used on another, so the need for serialization + * should be determined at run time by the application. Since it's just + * an example, the serialization decision is left up to user. + * Only on platforms where the serialization is never needed on any OS + * version st_netfd_serialize_accept() is a no-op. + */ + serialize_accept = 1; + break; + case 't': + testing = strtoul(optarg, NULL, 0); + break; + case 'X': + one_process = 1; + break; + case 'h': + case '?': + fprintf(stderr, "Usage: %s [options] -l <[host]:port> -r \n", + prog); + fprintf(stderr, "options are:\n"); + fprintf(stderr, " -p number of parallel processes\n"); + fprintf(stderr, " -S serialize accepts\n"); + fprintf(stderr, " -a use alternate event system\n"); +#ifdef DEBUG + fprintf(stderr, " -t mask testing/debugging mode\n"); + fprintf(stderr, " -X one process, don't daemonize\n"); +#endif + exit(1); + } + } + if (!laddr) { + fprintf(stderr, "%s: local address required\n", prog); + exit(1); + } + if (!raddr) { + fprintf(stderr, "%s: remote address required\n", prog); + exit(1); + } + if (num_procs == 0) + num_procs = cpu_count(); + + fprintf(stderr, "%s: starting proxy daemon on %s:%d\n", prog, + inet_ntoa(lcl_addr.sin_addr), ntohs(lcl_addr.sin_port)); + + /* Start the daemon */ + if (one_process) + num_procs = 1; + else + start_daemon(); + + if (alt_ev) + st_set_eventsys(ST_EVENTSYS_ALT); + + /* Initialize the ST library */ + if (st_init() < 0) { + print_sys_error("st_init"); + exit(1); + } + + /* Create and bind listening socket */ + if ((sock = socket(PF_INET, SOCK_STREAM, 0)) < 0) { + print_sys_error("socket"); + exit(1); + } + n = 1; + if (setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, (char *)&n, sizeof(n)) < 0) { + print_sys_error("setsockopt"); + exit(1); + } + if (bind(sock, (struct sockaddr *)&lcl_addr, sizeof(lcl_addr)) < 0) { + print_sys_error("bind"); + exit(1); + } + listen(sock, 128); + if ((srv_nfd = st_netfd_open_socket(sock)) == NULL) { + print_sys_error("st_netfd_open"); + exit(1); + } + /* See the comment regarding serialization decision above */ + if (num_procs > 1 && serialize_accept && st_netfd_serialize_accept(srv_nfd) + < 0) { + print_sys_error("st_netfd_serialize_accept"); + exit(1); + } + + /* Start server processes */ + if (!one_process) + set_concurrency(num_procs); + + for ( ; ; ) { + n = sizeof(cli_addr); + cli_nfd = st_accept(srv_nfd, (struct sockaddr *)&cli_addr, &n, + ST_UTIME_NO_TIMEOUT); + if (cli_nfd == NULL) { + print_sys_error("st_accept"); + exit(1); + } + if (st_thread_create(handle_request, cli_nfd, 0, 0) == NULL) { + print_sys_error("st_thread_create"); + exit(1); + } + } + + /* NOTREACHED */ + return 1; +} + + +static void read_address(const char *str, struct sockaddr_in *sin) +{ + char host[128], *p; + struct hostent *hp; + unsigned short port; + + strcpy(host, str); + if ((p = strchr(host, ':')) == NULL) { + fprintf(stderr, "%s: invalid address: %s\n", prog, host); + exit(1); + } + *p++ = '\0'; + port = (unsigned short) atoi(p); + if (port < 1) { + fprintf(stderr, "%s: invalid port: %s\n", prog, p); + exit(1); + } + + memset(sin, 0, sizeof(struct sockaddr_in)); + sin->sin_family = AF_INET; + sin->sin_port = htons(port); + if (host[0] == '\0') { + sin->sin_addr.s_addr = INADDR_ANY; + return; + } + sin->sin_addr.s_addr = inet_addr(host); + if (sin->sin_addr.s_addr == INADDR_NONE) { + /* not dotted-decimal */ + if ((hp = gethostbyname(host)) == NULL) { + fprintf(stderr, "%s: can't resolve address: %s\n", prog, host); + exit(1); + } + memcpy(&sin->sin_addr, hp->h_addr, hp->h_length); + } +} + +#ifdef DEBUG +static void show_iov(const struct iovec *iov, int niov) +{ + int i; + size_t total; + + printf("iov %p has %d entries:\n", iov, niov); + total = 0; + for (i = 0; i < niov; i++) { + printf("iov[%3d] iov_base=%p iov_len=0x%lx(%lu)\n", + i, iov[i].iov_base, (unsigned long) iov[i].iov_len, + (unsigned long) iov[i].iov_len); + total += iov[i].iov_len; + } + printf("total 0x%lx(%ld)\n", (unsigned long) total, (unsigned long) total); +} + +/* + * This version is tricked out to test all the + * st_(read|write)v?(_resid)? variants. Use the non-DEBUG version for + * anything serious. st_(read|write) are all this function really + * needs. + */ +static int pass(st_netfd_t in, st_netfd_t out) +{ + char buf[IOBUFSIZE]; + struct iovec iov[IOV_COUNT]; + int ioviter, nw, nr; + + if (testing & TESTING_READV) { + for (ioviter = 0; ioviter < IOV_COUNT; ioviter++) { + iov[ioviter].iov_base = &buf[ioviter * IOV_LEN]; + iov[ioviter].iov_len = IOV_LEN; + } + if (testing & TESTING_VERBOSE) { + printf("readv(%p)...\n", in); + show_iov(iov, IOV_COUNT); + } + if (testing & TESTING_READ_RESID) { + struct iovec *riov = iov; + int riov_cnt = IOV_COUNT; + if (st_readv_resid(in, &riov, &riov_cnt, ST_UTIME_NO_TIMEOUT) == 0) { + if (testing & TESTING_VERBOSE) { + printf("resid\n"); + show_iov(riov, riov_cnt); + printf("full\n"); + show_iov(iov, IOV_COUNT); + } + nr = 0; + for (ioviter = 0; ioviter < IOV_COUNT; ioviter++) + nr += iov[ioviter].iov_len; + nr = IOBUFSIZE - nr; + } else + nr = -1; + } else + nr = (int) st_readv(in, iov, IOV_COUNT, ST_UTIME_NO_TIMEOUT); + } else { + if (testing & TESTING_READ_RESID) { + size_t resid = IOBUFSIZE; + if (st_read_resid(in, buf, &resid, ST_UTIME_NO_TIMEOUT) == 0) + nr = IOBUFSIZE - resid; + else + nr = -1; + } else + nr = (int) st_read(in, buf, IOBUFSIZE, ST_UTIME_NO_TIMEOUT); + } + if (testing & TESTING_VERBOSE) + printf("got 0x%x(%d) E=%d\n", nr, nr, errno); + + if (nr <= 0) + return 0; + + if (testing & TESTING_WRITEV) { + for (nw = 0, ioviter = 0; nw < nr; + nw += iov[ioviter].iov_len, ioviter++) { + iov[ioviter].iov_base = &buf[nw]; + iov[ioviter].iov_len = nr - nw; + if (iov[ioviter].iov_len > IOV_LEN) + iov[ioviter].iov_len = IOV_LEN; + } + if (testing & TESTING_VERBOSE) { + printf("writev(%p)...\n", out); + show_iov(iov, ioviter); + } + if (testing & TESTING_WRITE_RESID) { + struct iovec *riov = iov; + int riov_cnt = ioviter; + if (st_writev_resid(out, &riov, &riov_cnt, ST_UTIME_NO_TIMEOUT) == 0) { + if (testing & TESTING_VERBOSE) { + printf("resid\n"); + show_iov(riov, riov_cnt); + printf("full\n"); + show_iov(iov, ioviter); + } + nw = 0; + while (--ioviter >= 0) + nw += iov[ioviter].iov_len; + nw = nr - nw; + } else + nw = -1; + } else + nw = st_writev(out, iov, ioviter, ST_UTIME_NO_TIMEOUT); + } else { + if (testing & TESTING_WRITE_RESID) { + size_t resid = nr; + if (st_write_resid(out, buf, &resid, ST_UTIME_NO_TIMEOUT) == 0) + nw = nr - resid; + else + nw = -1; + } else + nw = st_write(out, buf, nr, ST_UTIME_NO_TIMEOUT); + } + if (testing & TESTING_VERBOSE) + printf("put 0x%x(%d) E=%d\n", nw, nw, errno); + + if (nw != nr) + return 0; + + return 1; +} +#else /* DEBUG */ +/* + * This version is the simple one suitable for serious use. + */ +static int pass(st_netfd_t in, st_netfd_t out) +{ + char buf[IOBUFSIZE]; + int nw, nr; + + nr = (int) st_read(in, buf, IOBUFSIZE, ST_UTIME_NO_TIMEOUT); + if (nr <= 0) + return 0; + + nw = st_write(out, buf, nr, ST_UTIME_NO_TIMEOUT); + if (nw != nr) + return 0; + + return 1; +} +#endif + +static void *handle_request(void *arg) +{ + struct pollfd pds[2]; + st_netfd_t cli_nfd, rmt_nfd; + int sock; + + cli_nfd = (st_netfd_t) arg; + pds[0].fd = st_netfd_fileno(cli_nfd); + pds[0].events = POLLIN; + + /* Connect to remote host */ + if ((sock = socket(PF_INET, SOCK_STREAM, 0)) < 0) { + print_sys_error("socket"); + goto done; + } + if ((rmt_nfd = st_netfd_open_socket(sock)) == NULL) { + print_sys_error("st_netfd_open_socket"); + close(sock); + goto done; + } + if (st_connect(rmt_nfd, (struct sockaddr *)&rmt_addr, + sizeof(rmt_addr), ST_UTIME_NO_TIMEOUT) < 0) { + print_sys_error("st_connect"); + st_netfd_close(rmt_nfd); + goto done; + } + pds[1].fd = sock; + pds[1].events = POLLIN; + + /* + * Now just pump the data through. + * XXX This should use one thread for each direction for true full-duplex. + */ + for ( ; ; ) { + pds[0].revents = 0; + pds[1].revents = 0; + + if (st_poll(pds, 2, ST_UTIME_NO_TIMEOUT) <= 0) { + print_sys_error("st_poll"); + break; + } + + if (pds[0].revents & POLLIN) { + if (!pass(cli_nfd, rmt_nfd)) + break; + } + + if (pds[1].revents & POLLIN) { + if (!pass(rmt_nfd, cli_nfd)) + break; + } + } + st_netfd_close(rmt_nfd); + +done: + + st_netfd_close(cli_nfd); + + return NULL; +} + +static void start_daemon(void) +{ + pid_t pid; + + /* Start forking */ + if ((pid = fork()) < 0) { + print_sys_error("fork"); + exit(1); + } + if (pid > 0) + exit(0); /* parent */ + + /* First child process */ + setsid(); /* become session leader */ + + if ((pid = fork()) < 0) { + print_sys_error("fork"); + exit(1); + } + if (pid > 0) /* first child */ + exit(0); + + chdir("/"); + umask(022); +} + +/* + * Create separate processes ("virtual processors"). Since it's just an + * example, there is no watchdog - the parent just exits leaving children + * on their own. + */ +static void set_concurrency(int nproc) +{ + pid_t pid; + int i; + + if (nproc < 1) + nproc = 1; + + for (i = 0; i < nproc; i++) { + if ((pid = fork()) < 0) { + print_sys_error("fork"); + exit(1); + } + /* Child returns */ + if (pid == 0) + return; + } + + /* Parent just exits */ + exit(0); +} + +static int cpu_count(void) +{ + int n; + +#if defined (_SC_NPROCESSORS_ONLN) + n = (int) sysconf(_SC_NPROCESSORS_ONLN); +#elif defined (_SC_NPROC_ONLN) + n = (int) sysconf(_SC_NPROC_ONLN); +#elif defined (HPUX) +#include + n = mpctl(MPC_GETNUMSPUS, 0, 0); +#else + n = -1; + errno = ENOSYS; +#endif + + return n; +} + +static void print_sys_error(const char *msg) +{ + fprintf(stderr, "%s: %s: %s\n", prog, msg, strerror(errno)); +} + diff --git a/trunk/3rdparty/st-srs/examples/res.c b/trunk/3rdparty/st-srs/examples/res.c new file mode 100644 index 000000000..14ecd8c92 --- /dev/null +++ b/trunk/3rdparty/st-srs/examples/res.c @@ -0,0 +1,305 @@ +/* + * Copyright (c) 1985, 1988, 1993 + * The Regents of the University of California. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by the University of + * California, Berkeley and its contributors. + * 4. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * Portions created by SGI are Copyright (C) 2000 Silicon Graphics, Inc. + * All Rights Reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of Silicon Graphics, Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * HOLDERS AND CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED + * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#if defined (DARWIN) +#define BIND_8_COMPAT +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "st.h" + +#define MAXPACKET 1024 + +#if !defined(NETDB_INTERNAL) && defined(h_NETDB_INTERNAL) +#define NETDB_INTERNAL h_NETDB_INTERNAL +#endif + +/* New in Solaris 7 */ +#if !defined(_getshort) && defined(ns_get16) +#define _getshort(cp) ns_get16(cp) +#endif + +typedef union { + HEADER hdr; + u_char buf[MAXPACKET]; +} querybuf_t; + + +static int parse_answer(querybuf_t *ans, int len, struct in_addr *addr) +{ + char buf[MAXPACKET]; + HEADER *ahp; + u_char *cp, *eoa; + int type, n; + + ahp = &ans->hdr; + eoa = ans->buf + len; + cp = ans->buf + sizeof(HEADER); + + while (ahp->qdcount > 0) { + ahp->qdcount--; + cp += dn_skipname(cp, eoa) + QFIXEDSZ; + } + while (ahp->ancount > 0 && cp < eoa) { + ahp->ancount--; + if ((n = dn_expand(ans->buf, eoa, cp, buf, sizeof(buf))) < 0) + break; + cp += n; + type = _getshort(cp); + cp += 8; + n = _getshort(cp); + cp += 2; + if (type == T_CNAME) { + cp += n; + continue; + } + memcpy(addr, cp, n); + return 0; + } + + h_errno = TRY_AGAIN; + return -1; +} + + +static int query_domain(st_netfd_t nfd, const char *name, struct in_addr *addr, + st_utime_t timeout) +{ + querybuf_t qbuf; + u_char *buf = qbuf.buf; + HEADER *hp = &qbuf.hdr; + int blen = sizeof(qbuf); + int i, len, id; + + for (i = 0; i < _res.nscount; i++) { + len = res_mkquery(QUERY, name, C_IN, T_A, NULL, 0, NULL, buf, blen); + if (len <= 0) { + h_errno = NO_RECOVERY; + return -1; + } + id = hp->id; + + if (st_sendto(nfd, buf, len, (struct sockaddr *)&(_res.nsaddr_list[i]), + sizeof(struct sockaddr), timeout) != len) { + h_errno = NETDB_INTERNAL; + /* EINTR means interrupt by other thread, NOT by a caught signal */ + if (errno == EINTR) + return -1; + continue; + } + + /* Wait for reply */ + do { + len = st_recvfrom(nfd, buf, blen, NULL, NULL, timeout); + if (len <= 0) + break; + } while (id != hp->id); + + if (len < HFIXEDSZ) { + h_errno = NETDB_INTERNAL; + if (len >= 0) + errno = EMSGSIZE; + else if (errno == EINTR) /* see the comment above */ + return -1; + continue; + } + + hp->ancount = ntohs(hp->ancount); + hp->qdcount = ntohs(hp->qdcount); + if ((hp->rcode != NOERROR) || (hp->ancount == 0)) { + switch (hp->rcode) { + case NXDOMAIN: + h_errno = HOST_NOT_FOUND; + break; + case SERVFAIL: + h_errno = TRY_AGAIN; + break; + case NOERROR: + h_errno = NO_DATA; + break; + case FORMERR: + case NOTIMP: + case REFUSED: + default: + h_errno = NO_RECOVERY; + } + continue; + } + + if (parse_answer(&qbuf, len, addr) == 0) + return 0; + } + + return -1; +} + + +#define CLOSE_AND_RETURN(ret) \ + { \ + n = errno; \ + st_netfd_close(nfd); \ + errno = n; \ + return (ret); \ + } + + +int dns_getaddr(const char *host, struct in_addr *addr, st_utime_t timeout) +{ + char name[MAXDNAME], **domain; + const char *cp; + int s, n, maxlen, dots; + int trailing_dot, tried_as_is; + st_netfd_t nfd; + + if ((_res.options & RES_INIT) == 0 && res_init() == -1) { + h_errno = NETDB_INTERNAL; + return -1; + } + if (_res.options & RES_USEVC) { + h_errno = NETDB_INTERNAL; + errno = ENOSYS; + return -1; + } + if (!host || *host == '\0') { + h_errno = HOST_NOT_FOUND; + return -1; + } + + /* Create UDP socket */ + if ((s = socket(PF_INET, SOCK_DGRAM, 0)) < 0) { + h_errno = NETDB_INTERNAL; + return -1; + } + if ((nfd = st_netfd_open_socket(s)) == NULL) { + h_errno = NETDB_INTERNAL; + n = errno; + close(s); + errno = n; + return -1; + } + + maxlen = sizeof(name) - 1; + n = 0; + dots = 0; + trailing_dot = 0; + tried_as_is = 0; + + for (cp = host; *cp && n < maxlen; cp++) { + dots += (*cp == '.'); + name[n++] = *cp; + } + if (name[n - 1] == '.') + trailing_dot = 1; + + /* + * If there are dots in the name already, let's just give it a try + * 'as is'. The threshold can be set with the "ndots" option. + */ + if (dots >= _res.ndots) { + if (query_domain(nfd, host, addr, timeout) == 0) + CLOSE_AND_RETURN(0); + if (h_errno == NETDB_INTERNAL && errno == EINTR) + CLOSE_AND_RETURN(-1); + tried_as_is = 1; + } + + /* + * We do at least one level of search if + * - there is no dot and RES_DEFNAME is set, or + * - there is at least one dot, there is no trailing dot, + * and RES_DNSRCH is set. + */ + if ((!dots && (_res.options & RES_DEFNAMES)) || + (dots && !trailing_dot && (_res.options & RES_DNSRCH))) { + name[n++] = '.'; + for (domain = _res.dnsrch; *domain; domain++) { + strncpy(name + n, *domain, maxlen - n); + if (query_domain(nfd, name, addr, timeout) == 0) + CLOSE_AND_RETURN(0); + if (h_errno == NETDB_INTERNAL && errno == EINTR) + CLOSE_AND_RETURN(-1); + if (!(_res.options & RES_DNSRCH)) + break; + } + } + + /* + * If we have not already tried the name "as is", do that now. + * note that we do this regardless of how many dots were in the + * name or whether it ends with a dot. + */ + if (!tried_as_is) { + if (query_domain(nfd, host, addr, timeout) == 0) + CLOSE_AND_RETURN(0); + } + + CLOSE_AND_RETURN(-1); +} + diff --git a/trunk/3rdparty/st-srs/examples/server.c b/trunk/3rdparty/st-srs/examples/server.c new file mode 100644 index 000000000..5d5aa6d72 --- /dev/null +++ b/trunk/3rdparty/st-srs/examples/server.c @@ -0,0 +1,1025 @@ +/* + * Portions created by SGI are Copyright (C) 2000 Silicon Graphics, Inc. + * All Rights Reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of Silicon Graphics, Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * HOLDERS AND CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED + * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "st.h" + + +/****************************************************************** + * Server configuration parameters + */ + +/* Log files */ +#define PID_FILE "pid" +#define ERRORS_FILE "errors" +#define ACCESS_FILE "access" + +/* Default server port */ +#define SERV_PORT_DEFAULT 8000 + +/* Socket listen queue size */ +#define LISTENQ_SIZE_DEFAULT 256 + +/* Max number of listening sockets ("hardware virtual servers") */ +#define MAX_BIND_ADDRS 16 + +/* Max number of "spare" threads per process per socket */ +#define MAX_WAIT_THREADS_DEFAULT 8 + +/* Number of file descriptors needed to handle one client session */ +#define FD_PER_THREAD 2 + +/* Access log buffer flushing interval (in seconds) */ +#define ACCLOG_FLUSH_INTERVAL 30 + +/* Request read timeout (in seconds) */ +#define REQUEST_TIMEOUT 30 + + +/****************************************************************** + * Global data + */ + +struct socket_info { + st_netfd_t nfd; /* Listening socket */ + char *addr; /* Bind address */ + unsigned int port; /* Port */ + int wait_threads; /* Number of threads waiting to accept */ + int busy_threads; /* Number of threads processing request */ + int rqst_count; /* Total number of processed requests */ +} srv_socket[MAX_BIND_ADDRS]; /* Array of listening sockets */ + +static int sk_count = 0; /* Number of listening sockets */ + +static int vp_count = 0; /* Number of server processes (VPs) */ +static pid_t *vp_pids; /* Array of VP pids */ + +static int my_index = -1; /* Current process index */ +static pid_t my_pid = -1; /* Current process pid */ + +static st_netfd_t sig_pipe[2]; /* Signal pipe */ + +/* + * Configuration flags/parameters + */ +static int interactive_mode = 0; +static int serialize_accept = 0; +static int log_access = 0; +static char *logdir = NULL; +static char *username = NULL; +static int listenq_size = LISTENQ_SIZE_DEFAULT; +static int errfd = STDERR_FILENO; + +/* + * Thread throttling parameters (all numbers are per listening socket). + * Zero values mean use default. + */ +static int max_threads = 0; /* Max number of threads */ +static int max_wait_threads = 0; /* Max number of "spare" threads */ +static int min_wait_threads = 2; /* Min number of "spare" threads */ + + +/****************************************************************** + * Useful macros + */ + +#ifndef INADDR_NONE +#define INADDR_NONE 0xffffffff +#endif + +#define SEC2USEC(s) ((s)*1000000LL) + +#define WAIT_THREADS(i) (srv_socket[i].wait_threads) +#define BUSY_THREADS(i) (srv_socket[i].busy_threads) +#define TOTAL_THREADS(i) (WAIT_THREADS(i) + BUSY_THREADS(i)) +#define RQST_COUNT(i) (srv_socket[i].rqst_count) + + +/****************************************************************** + * Forward declarations + */ + +static void usage(const char *progname); +static void parse_arguments(int argc, char *argv[]); +static void start_daemon(void); +static void set_thread_throttling(void); +static void create_listeners(void); +static void change_user(void); +static void open_log_files(void); +static void start_processes(void); +static void wdog_sighandler(int signo); +static void child_sighandler(int signo); +static void install_sighandlers(void); +static void start_threads(void); +static void *process_signals(void *arg); +static void *flush_acclog_buffer(void *arg); +static void *handle_connections(void *arg); +static void dump_server_info(void); + +static void Signal(int sig, void (*handler)(int)); +static int cpu_count(void); + +extern void handle_session(long srv_socket_index, st_netfd_t cli_nfd); +extern void load_configs(void); +extern void logbuf_open(void); +extern void logbuf_flush(void); +extern void logbuf_close(void); + +/* Error reporting functions defined in the error.c file */ +extern void err_sys_report(int fd, const char *fmt, ...); +extern void err_sys_quit(int fd, const char *fmt, ...); +extern void err_sys_dump(int fd, const char *fmt, ...); +extern void err_report(int fd, const char *fmt, ...); +extern void err_quit(int fd, const char *fmt, ...); + + +/* + * General server example: accept a client connection and do something. + * This program just outputs a short HTML page, but can be easily adapted + * to do other things. + * + * This server creates a constant number of processes ("virtual processors" + * or VPs) and replaces them when they die. Each virtual processor manages + * its own independent set of state threads (STs), the number of which varies + * with load against the server. Each state thread listens to exactly one + * listening socket. The initial process becomes the watchdog, waiting for + * children (VPs) to die or for a signal requesting termination or restart. + * Upon receiving a restart signal (SIGHUP), all VPs close and then reopen + * log files and reload configuration. All currently active connections remain + * active. It is assumed that new configuration affects only request + * processing and not the general server parameters such as number of VPs, + * thread limits, bind addresses, etc. Those are specified as command line + * arguments, so the server has to be stopped and then started again in order + * to change them. + * + * Each state thread loops processing connections from a single listening + * socket. Only one ST runs on a VP at a time, and VPs do not share memory, + * so no mutual exclusion locking is necessary on any data, and the entire + * server is free to use all the static variables and non-reentrant library + * functions it wants, greatly simplifying programming and debugging and + * increasing performance (for example, it is safe to ++ and -- all global + * counters or call inet_ntoa(3) without any mutexes). The current thread on + * each VP maintains equilibrium on that VP, starting a new thread or + * terminating itself if the number of spare threads exceeds the lower or + * upper limit. + * + * All I/O operations on sockets must use the State Thread library's I/O + * functions because only those functions prevent blocking of the entire VP + * process and perform state thread scheduling. + */ +int main(int argc, char *argv[]) +{ + /* Parse command-line options */ + parse_arguments(argc, argv); + + /* Allocate array of server pids */ + if ((vp_pids = calloc(vp_count, sizeof(pid_t))) == NULL) + err_sys_quit(errfd, "ERROR: calloc failed"); + + /* Start the daemon */ + if (!interactive_mode) + start_daemon(); + + /* Initialize the ST library */ + if (st_init() < 0) + err_sys_quit(errfd, "ERROR: initialization failed: st_init"); + + /* Set thread throttling parameters */ + set_thread_throttling(); + + /* Create listening sockets */ + create_listeners(); + + /* Change the user */ + if (username) + change_user(); + + /* Open log files */ + open_log_files(); + + /* Start server processes (VPs) */ + start_processes(); + + /* Turn time caching on */ + st_timecache_set(1); + + /* Install signal handlers */ + install_sighandlers(); + + /* Load configuration from config files */ + load_configs(); + + /* Start all threads */ + start_threads(); + + /* Become a signal processing thread */ + process_signals(NULL); + + /* NOTREACHED */ + return 1; +} + + +/******************************************************************/ + +static void usage(const char *progname) +{ + fprintf(stderr, "Usage: %s -l []\n\n" + "Possible options:\n\n" + "\t-b : Bind to specified address. Multiple" + " addresses\n" + "\t are permitted.\n" + "\t-p Create specified number of processes.\n" + "\t-t : Specify thread limits per listening" + " socket\n" + "\t across all processes.\n" + "\t-u Change server's user id to specified" + " value.\n" + "\t-q Set max length of pending connections" + " queue.\n" + "\t-a Enable access logging.\n" + "\t-i Run in interactive mode.\n" + "\t-S Serialize all accept() calls.\n" + "\t-h Print this message.\n", + progname); + exit(1); +} + + +/******************************************************************/ + +static void parse_arguments(int argc, char *argv[]) +{ + extern char *optarg; + int opt; + char *c; + + while ((opt = getopt(argc, argv, "b:p:l:t:u:q:aiSh")) != EOF) { + switch (opt) { + case 'b': + if (sk_count >= MAX_BIND_ADDRS) + err_quit(errfd, "ERROR: max number of bind addresses (%d) exceeded", + MAX_BIND_ADDRS); + if ((c = strdup(optarg)) == NULL) + err_sys_quit(errfd, "ERROR: strdup"); + srv_socket[sk_count++].addr = c; + break; + case 'p': + vp_count = atoi(optarg); + if (vp_count < 1) + err_quit(errfd, "ERROR: invalid number of processes: %s", optarg); + break; + case 'l': + logdir = optarg; + break; + case 't': + max_wait_threads = (int) strtol(optarg, &c, 10); + if (*c++ == ':') + max_threads = atoi(c); + if (max_wait_threads < 0 || max_threads < 0) + err_quit(errfd, "ERROR: invalid number of threads: %s", optarg); + break; + case 'u': + username = optarg; + break; + case 'q': + listenq_size = atoi(optarg); + if (listenq_size < 1) + err_quit(errfd, "ERROR: invalid listen queue size: %s", optarg); + break; + case 'a': + log_access = 1; + break; + case 'i': + interactive_mode = 1; + break; + case 'S': + /* + * Serialization decision is tricky on some platforms. For example, + * Solaris 2.6 and above has kernel sockets implementation, so supposedly + * there is no need for serialization. The ST library may be compiled + * on one OS version, but used on another, so the need for serialization + * should be determined at run time by the application. Since it's just + * an example, the serialization decision is left up to user. + * Only on platforms where the serialization is never needed on any OS + * version st_netfd_serialize_accept() is a no-op. + */ + serialize_accept = 1; + break; + case 'h': + case '?': + usage(argv[0]); + } + } + + if (logdir == NULL && !interactive_mode) { + err_report(errfd, "ERROR: logging directory is required\n"); + usage(argv[0]); + } + + if (getuid() == 0 && username == NULL) + err_report(errfd, "WARNING: running as super-user!"); + + if (vp_count == 0 && (vp_count = cpu_count()) < 1) + vp_count = 1; + + if (sk_count == 0) { + sk_count = 1; + srv_socket[0].addr = "0.0.0.0"; + } +} + + +/******************************************************************/ + +static void start_daemon(void) +{ + pid_t pid; + + /* Start forking */ + if ((pid = fork()) < 0) + err_sys_quit(errfd, "ERROR: fork"); + if (pid > 0) + exit(0); /* parent */ + + /* First child process */ + setsid(); /* become session leader */ + + if ((pid = fork()) < 0) + err_sys_quit(errfd, "ERROR: fork"); + if (pid > 0) /* first child */ + exit(0); + + umask(022); + + if (chdir(logdir) < 0) + err_sys_quit(errfd, "ERROR: can't change directory to %s: chdir", logdir); +} + + +/****************************************************************** + * For simplicity, the minimal size of thread pool is considered + * as a maximum number of spare threads (max_wait_threads) that + * will be created upon server startup. The pool size can grow up + * to the max_threads value. Note that this is a per listening + * socket limit. It is also possible to limit the total number of + * threads for all sockets rather than impose a per socket limit. + */ + +static void set_thread_throttling(void) +{ + /* + * Calculate total values across all processes. + * All numbers are per listening socket. + */ + if (max_wait_threads == 0) + max_wait_threads = MAX_WAIT_THREADS_DEFAULT * vp_count; + /* Assuming that each client session needs FD_PER_THREAD file descriptors */ + if (max_threads == 0) + max_threads = (st_getfdlimit() * vp_count) / FD_PER_THREAD / sk_count; + if (max_wait_threads > max_threads) + max_wait_threads = max_threads; + + /* + * Now calculate per-process values. + */ + if (max_wait_threads % vp_count) + max_wait_threads = max_wait_threads / vp_count + 1; + else + max_wait_threads = max_wait_threads / vp_count; + if (max_threads % vp_count) + max_threads = max_threads / vp_count + 1; + else + max_threads = max_threads / vp_count; + + if (min_wait_threads > max_wait_threads) + min_wait_threads = max_wait_threads; +} + + +/******************************************************************/ + +static void create_listeners(void) +{ + int i, n, sock; + char *c; + struct sockaddr_in serv_addr; + struct hostent *hp; + unsigned short port; + + for (i = 0; i < sk_count; i++) { + port = 0; + if ((c = strchr(srv_socket[i].addr, ':')) != NULL) { + *c++ = '\0'; + port = (unsigned short) atoi(c); + } + if (srv_socket[i].addr[0] == '\0') + srv_socket[i].addr = "0.0.0.0"; + if (port == 0) + port = SERV_PORT_DEFAULT; + + /* Create server socket */ + if ((sock = socket(PF_INET, SOCK_STREAM, 0)) < 0) + err_sys_quit(errfd, "ERROR: can't create socket: socket"); + n = 1; + if (setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, (char *)&n, sizeof(n)) < 0) + err_sys_quit(errfd, "ERROR: can't set SO_REUSEADDR: setsockopt"); + memset(&serv_addr, 0, sizeof(serv_addr)); + serv_addr.sin_family = AF_INET; + serv_addr.sin_port = htons(port); + serv_addr.sin_addr.s_addr = inet_addr(srv_socket[i].addr); + if (serv_addr.sin_addr.s_addr == INADDR_NONE) { + /* not dotted-decimal */ + if ((hp = gethostbyname(srv_socket[i].addr)) == NULL) + err_quit(errfd, "ERROR: can't resolve address: %s", + srv_socket[i].addr); + memcpy(&serv_addr.sin_addr, hp->h_addr, hp->h_length); + } + srv_socket[i].port = port; + + /* Do bind and listen */ + if (bind(sock, (struct sockaddr *)&serv_addr, sizeof(serv_addr)) < 0) + err_sys_quit(errfd, "ERROR: can't bind to address %s, port %hu", + srv_socket[i].addr, port); + if (listen(sock, listenq_size) < 0) + err_sys_quit(errfd, "ERROR: listen"); + + /* Create file descriptor object from OS socket */ + if ((srv_socket[i].nfd = st_netfd_open_socket(sock)) == NULL) + err_sys_quit(errfd, "ERROR: st_netfd_open_socket"); + /* + * On some platforms (e.g. IRIX, Linux) accept() serialization is never + * needed for any OS version. In that case st_netfd_serialize_accept() + * is just a no-op. Also see the comment above. + */ + if (serialize_accept && st_netfd_serialize_accept(srv_socket[i].nfd) < 0) + err_sys_quit(errfd, "ERROR: st_netfd_serialize_accept"); + } +} + + +/******************************************************************/ + +static void change_user(void) +{ + struct passwd *pw; + + if ((pw = getpwnam(username)) == NULL) + err_quit(errfd, "ERROR: can't find user '%s': getpwnam failed", username); + + if (setgid(pw->pw_gid) < 0) + err_sys_quit(errfd, "ERROR: can't change group id: setgid"); + if (setuid(pw->pw_uid) < 0) + err_sys_quit(errfd, "ERROR: can't change user id: setuid"); + + err_report(errfd, "INFO: changed process user id to '%s'", username); +} + + +/******************************************************************/ + +static void open_log_files(void) +{ + int fd; + char str[32]; + + if (interactive_mode) + return; + + /* Open access log */ + if (log_access) + logbuf_open(); + + /* Open and write pid to pid file */ + if ((fd = open(PID_FILE, O_CREAT | O_WRONLY | O_TRUNC, 0644)) < 0) + err_sys_quit(errfd, "ERROR: can't open pid file: open"); + sprintf(str, "%d\n", (int)getpid()); + if (write(fd, str, strlen(str)) != strlen(str)) + err_sys_quit(errfd, "ERROR: can't write to pid file: write"); + close(fd); + + /* Open error log file */ + if ((fd = open(ERRORS_FILE, O_CREAT | O_WRONLY | O_APPEND, 0644)) < 0) + err_sys_quit(errfd, "ERROR: can't open error log file: open"); + errfd = fd; + + err_report(errfd, "INFO: starting the server..."); +} + + +/******************************************************************/ + +static void start_processes(void) +{ + int i, status; + pid_t pid; + sigset_t mask, omask; + + if (interactive_mode) { + my_index = 0; + my_pid = getpid(); + return; + } + + for (i = 0; i < vp_count; i++) { + if ((pid = fork()) < 0) { + err_sys_report(errfd, "ERROR: can't create process: fork"); + if (i == 0) + exit(1); + err_report(errfd, "WARN: started only %d processes out of %d", i, + vp_count); + vp_count = i; + break; + } + if (pid == 0) { + my_index = i; + my_pid = getpid(); + /* Child returns to continue in main() */ + return; + } + vp_pids[i] = pid; + } + + /* + * Parent process becomes a "watchdog" and never returns to main(). + */ + + /* Install signal handlers */ + Signal(SIGTERM, wdog_sighandler); /* terminate */ + Signal(SIGHUP, wdog_sighandler); /* restart */ + Signal(SIGUSR1, wdog_sighandler); /* dump info */ + + /* Now go to sleep waiting for a child termination or a signal */ + for ( ; ; ) { + if ((pid = wait(&status)) < 0) { + if (errno == EINTR) + continue; + err_sys_quit(errfd, "ERROR: watchdog: wait"); + } + /* Find index of the exited child */ + for (i = 0; i < vp_count; i++) { + if (vp_pids[i] == pid) + break; + } + + /* Block signals while printing and forking */ + sigemptyset(&mask); + sigaddset(&mask, SIGTERM); + sigaddset(&mask, SIGHUP); + sigaddset(&mask, SIGUSR1); + sigprocmask(SIG_BLOCK, &mask, &omask); + + if (WIFEXITED(status)) + err_report(errfd, "WARN: watchdog: process %d (pid %d) exited" + " with status %d", i, pid, WEXITSTATUS(status)); + else if (WIFSIGNALED(status)) + err_report(errfd, "WARN: watchdog: process %d (pid %d) terminated" + " by signal %d", i, pid, WTERMSIG(status)); + else if (WIFSTOPPED(status)) + err_report(errfd, "WARN: watchdog: process %d (pid %d) stopped" + " by signal %d", i, pid, WSTOPSIG(status)); + else + err_report(errfd, "WARN: watchdog: process %d (pid %d) terminated:" + " unknown termination reason", i, pid); + + /* Fork another VP */ + if ((pid = fork()) < 0) { + err_sys_report(errfd, "ERROR: watchdog: can't create process: fork"); + } else if (pid == 0) { + my_index = i; + my_pid = getpid(); + /* Child returns to continue in main() */ + return; + } + vp_pids[i] = pid; + + /* Restore the signal mask */ + sigprocmask(SIG_SETMASK, &omask, NULL); + } +} + + +/******************************************************************/ + +static void wdog_sighandler(int signo) +{ + int i, err; + + /* Save errno */ + err = errno; + /* Forward the signal to all children */ + for (i = 0; i < vp_count; i++) { + if (vp_pids[i] > 0) + kill(vp_pids[i], signo); + } + /* + * It is safe to do pretty much everything here because process is + * sleeping in wait() which is async-safe. + */ + switch (signo) { + case SIGHUP: + err_report(errfd, "INFO: watchdog: caught SIGHUP"); + /* Reopen log files - needed for log rotation */ + if (log_access) { + logbuf_close(); + logbuf_open(); + } + close(errfd); + if ((errfd = open(ERRORS_FILE, O_CREAT | O_WRONLY | O_APPEND, 0644)) < 0) + err_sys_quit(STDERR_FILENO, "ERROR: watchdog: open"); + break; + case SIGTERM: + /* Non-graceful termination */ + err_report(errfd, "INFO: watchdog: caught SIGTERM, terminating"); + unlink(PID_FILE); + exit(0); + case SIGUSR1: + err_report(errfd, "INFO: watchdog: caught SIGUSR1"); + break; + default: + err_report(errfd, "INFO: watchdog: caught signal %d", signo); + } + /* Restore errno */ + errno = err; +} + + +/******************************************************************/ + +static void install_sighandlers(void) +{ + sigset_t mask; + int p[2]; + + /* Create signal pipe */ + if (pipe(p) < 0) + err_sys_quit(errfd, "ERROR: process %d (pid %d): can't create" + " signal pipe: pipe", my_index, my_pid); + if ((sig_pipe[0] = st_netfd_open(p[0])) == NULL || + (sig_pipe[1] = st_netfd_open(p[1])) == NULL) + err_sys_quit(errfd, "ERROR: process %d (pid %d): can't create" + " signal pipe: st_netfd_open", my_index, my_pid); + + /* Install signal handlers */ + Signal(SIGTERM, child_sighandler); /* terminate */ + Signal(SIGHUP, child_sighandler); /* restart */ + Signal(SIGUSR1, child_sighandler); /* dump info */ + + /* Unblock signals */ + sigemptyset(&mask); + sigaddset(&mask, SIGTERM); + sigaddset(&mask, SIGHUP); + sigaddset(&mask, SIGUSR1); + sigprocmask(SIG_UNBLOCK, &mask, NULL); +} + + +/******************************************************************/ + +static void child_sighandler(int signo) +{ + int err, fd; + + err = errno; + fd = st_netfd_fileno(sig_pipe[1]); + + /* write() is async-safe */ + if (write(fd, &signo, sizeof(int)) != sizeof(int)) + err_sys_quit(errfd, "ERROR: process %d (pid %d): child's signal" + " handler: write", my_index, my_pid); + errno = err; +} + + +/****************************************************************** + * The "main" function of the signal processing thread. + */ + +/* ARGSUSED */ +static void *process_signals(void *arg) +{ + int signo; + + for ( ; ; ) { + /* Read the next signal from the signal pipe */ + if (st_read(sig_pipe[0], &signo, sizeof(int), + ST_UTIME_NO_TIMEOUT) != sizeof(int)) + err_sys_quit(errfd, "ERROR: process %d (pid %d): signal processor:" + " st_read", my_index, my_pid); + + switch (signo) { + case SIGHUP: + err_report(errfd, "INFO: process %d (pid %d): caught SIGHUP," + " reloading configuration", my_index, my_pid); + if (interactive_mode) { + load_configs(); + break; + } + /* Reopen log files - needed for log rotation */ + if (log_access) { + logbuf_flush(); + logbuf_close(); + logbuf_open(); + } + close(errfd); + if ((errfd = open(ERRORS_FILE, O_CREAT | O_WRONLY | O_APPEND, 0644)) < 0) + err_sys_quit(STDERR_FILENO, "ERROR: process %d (pid %d): signal" + " processor: open", my_index, my_pid); + /* Reload configuration */ + load_configs(); + break; + case SIGTERM: + /* + * Terminate ungracefully since it is generally not known how long + * it will take to gracefully complete all client sessions. + */ + err_report(errfd, "INFO: process %d (pid %d): caught SIGTERM," + " terminating", my_index, my_pid); + if (log_access) + logbuf_flush(); + exit(0); + case SIGUSR1: + err_report(errfd, "INFO: process %d (pid %d): caught SIGUSR1", + my_index, my_pid); + /* Print server info to stderr */ + dump_server_info(); + break; + default: + err_report(errfd, "INFO: process %d (pid %d): caught signal %d", + my_index, my_pid, signo); + } + } + + /* NOTREACHED */ + return NULL; +} + + +/****************************************************************** + * The "main" function of the access log flushing thread. + */ + +/* ARGSUSED */ +static void *flush_acclog_buffer(void *arg) +{ + for ( ; ; ) { + st_sleep(ACCLOG_FLUSH_INTERVAL); + logbuf_flush(); + } + + /* NOTREACHED */ + return NULL; +} + + +/******************************************************************/ + +static void start_threads(void) +{ + long i, n; + + /* Create access log flushing thread */ + if (log_access && st_thread_create(flush_acclog_buffer, NULL, 0, 0) == NULL) + err_sys_quit(errfd, "ERROR: process %d (pid %d): can't create" + " log flushing thread", my_index, my_pid); + + /* Create connections handling threads */ + for (i = 0; i < sk_count; i++) { + err_report(errfd, "INFO: process %d (pid %d): starting %d threads" + " on %s:%u", my_index, my_pid, max_wait_threads, + srv_socket[i].addr, srv_socket[i].port); + WAIT_THREADS(i) = 0; + BUSY_THREADS(i) = 0; + RQST_COUNT(i) = 0; + for (n = 0; n < max_wait_threads; n++) { + if (st_thread_create(handle_connections, (void *)i, 0, 0) != NULL) + WAIT_THREADS(i)++; + else + err_sys_report(errfd, "ERROR: process %d (pid %d): can't create" + " thread", my_index, my_pid); + } + if (WAIT_THREADS(i) == 0) + exit(1); + } +} + + +/******************************************************************/ + +static void *handle_connections(void *arg) +{ + st_netfd_t srv_nfd, cli_nfd; + struct sockaddr_in from; + int fromlen; + long i = (long) arg; + + srv_nfd = srv_socket[i].nfd; + fromlen = sizeof(from); + + while (WAIT_THREADS(i) <= max_wait_threads) { + cli_nfd = st_accept(srv_nfd, (struct sockaddr *)&from, &fromlen, + ST_UTIME_NO_TIMEOUT); + if (cli_nfd == NULL) { + err_sys_report(errfd, "ERROR: can't accept connection: st_accept"); + continue; + } + /* Save peer address, so we can retrieve it later */ + st_netfd_setspecific(cli_nfd, &from.sin_addr, NULL); + + WAIT_THREADS(i)--; + BUSY_THREADS(i)++; + if (WAIT_THREADS(i) < min_wait_threads && TOTAL_THREADS(i) < max_threads) { + /* Create another spare thread */ + if (st_thread_create(handle_connections, (void *)i, 0, 0) != NULL) + WAIT_THREADS(i)++; + else + err_sys_report(errfd, "ERROR: process %d (pid %d): can't create" + " thread", my_index, my_pid); + } + + handle_session(i, cli_nfd); + + st_netfd_close(cli_nfd); + WAIT_THREADS(i)++; + BUSY_THREADS(i)--; + } + + WAIT_THREADS(i)--; + return NULL; +} + + +/******************************************************************/ + +static void dump_server_info(void) +{ + char *buf; + int i, len; + + if ((buf = malloc(sk_count * 512)) == NULL) { + err_sys_report(errfd, "ERROR: malloc failed"); + return; + } + + len = sprintf(buf, "\n\nProcess #%d (pid %d):\n", my_index, (int)my_pid); + for (i = 0; i < sk_count; i++) { + len += sprintf(buf + len, "\nListening Socket #%d:\n" + "-------------------------\n" + "Address %s:%u\n" + "Thread limits (min/max) %d/%d\n" + "Waiting threads %d\n" + "Busy threads %d\n" + "Requests served %d\n", + i, srv_socket[i].addr, srv_socket[i].port, + max_wait_threads, max_threads, + WAIT_THREADS(i), BUSY_THREADS(i), RQST_COUNT(i)); + } + + write(STDERR_FILENO, buf, len); + free(buf); +} + + +/****************************************************************** + * Stubs + */ + +/* + * Session handling function stub. Just dumps small HTML page. + */ +void handle_session(long srv_socket_index, st_netfd_t cli_nfd) +{ + static char resp[] = "HTTP/1.0 200 OK\r\nContent-type: text/html\r\n" + "Connection: close\r\n\r\n

It worked!

\n"; + char buf[512]; + int n = sizeof(resp) - 1; + struct in_addr *from = st_netfd_getspecific(cli_nfd); + + if (st_read(cli_nfd, buf, sizeof(buf), SEC2USEC(REQUEST_TIMEOUT)) < 0) { + err_sys_report(errfd, "WARN: can't read request from %s: st_read", + inet_ntoa(*from)); + return; + } + if (st_write(cli_nfd, resp, n, ST_UTIME_NO_TIMEOUT) != n) { + err_sys_report(errfd, "WARN: can't write response to %s: st_write", + inet_ntoa(*from)); + return; + } + + RQST_COUNT(srv_socket_index)++; +} + + +/* + * Configuration loading function stub. + */ +void load_configs(void) +{ + err_report(errfd, "INFO: process %d (pid %d): configuration loaded", + my_index, my_pid); +} + + +/* + * Buffered access logging methods. + * Note that stdio functions (fopen(3), fprintf(3), fflush(3), etc.) cannot + * be used if multiple VPs are created since these functions can flush buffer + * at any point and thus write only partial log record to disk. + * Also, it is completely safe for all threads of the same VP to write to + * the same log buffer without any mutex protection (one buffer per VP, of + * course). + */ +void logbuf_open(void) +{ + +} + + +void logbuf_flush(void) +{ + +} + + +void logbuf_close(void) +{ + +} + + +/****************************************************************** + * Small utility functions + */ + +static void Signal(int sig, void (*handler)(int)) +{ + struct sigaction sa; + + sa.sa_handler = handler; + sigemptyset(&sa.sa_mask); + sa.sa_flags = 0; + sigaction(sig, &sa, NULL); +} + +static int cpu_count(void) +{ + int n; + +#if defined (_SC_NPROCESSORS_ONLN) + n = (int) sysconf(_SC_NPROCESSORS_ONLN); +#elif defined (_SC_NPROC_ONLN) + n = (int) sysconf(_SC_NPROC_ONLN); +#elif defined (HPUX) +#include + n = mpctl(MPC_GETNUMSPUS, 0, 0); +#else + n = -1; + errno = ENOSYS; +#endif + + return n; +} + +/******************************************************************/ + diff --git a/trunk/3rdparty/st-srs/extensions/Makefile b/trunk/3rdparty/st-srs/extensions/Makefile new file mode 100644 index 000000000..fc6634f93 --- /dev/null +++ b/trunk/3rdparty/st-srs/extensions/Makefile @@ -0,0 +1,91 @@ +# +# Portions created by SGI are Copyright (C) 2000 Silicon Graphics, Inc. +# All Rights Reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions +# are met: +# +# 1. Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# 2. Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# 3. Neither the name of Silicon Graphics, Inc. nor the names of its +# contributors may be used to endorse or promote products derived from +# this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +# HOLDERS AND CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED +# TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +CC = cc + +SHELL = /bin/sh +ECHO = /bin/echo + +DEPTH = .. +BUILD = +TARGETDIR = obj + +DEFINES = +OTHER_FLAGS = +CFLAGS = + +OBJDIR = $(DEPTH)/$(TARGETDIR) +INCDIR = $(DEPTH)/$(TARGETDIR) + +LIBRESOLV = +EXTRALIBS = + +SLIBRARY = $(OBJDIR)/libstx.a +OBJS = $(OBJDIR)/dnscache.o $(OBJDIR)/dnsres.o $(OBJDIR)/lrucache.o + + +CFLAGS += -Wall -I$(INCDIR) +AR = ar +ARFLAGS = rv +RANLIB = ranlib + + +########################## +# Platform section. +# + +ifeq (LINUX, $(findstring LINUX, $(OS))) +LIBRESOLV = -lresolv +endif + +ifeq ($(OS), SOLARIS) +LIBRESOLV = -lresolv +EXTRALIBS = -lsocket -lnsl +endif + +# +# End of platform section. +########################## + + +all: $(SLIBRARY) + +$(SLIBRARY): $(OBJS) + $(AR) $(ARFLAGS) $@ $(OBJS) + $(RANLIB) $@ + +$(OBJDIR)/%.o: %.c stx.h common.h + $(CC) $(CFLAGS) -c $< -o $@ + +clean: + rm -rf $(OBJS) $(SLIBRARY) + +#.DEFAULT: +# @cd $(DEPTH); $(MAKE) $@ + diff --git a/trunk/3rdparty/st-srs/extensions/README b/trunk/3rdparty/st-srs/extensions/README new file mode 100644 index 000000000..f768aa712 --- /dev/null +++ b/trunk/3rdparty/st-srs/extensions/README @@ -0,0 +1,42 @@ +This directory contains extensions to the core State Threads Library +that were contributed by users. All files hereunder are not part of the +State Threads Library itself. They are provided as-is, without warranty +or support, and under whatever license terms their authors provided. To +contribute your own extensions, just mail them to the project +administrators or to one of the project's mailing lists; see +state-threads.sourceforge.net. Please indicate the license terms under +which the project may distribute your contribution. + +======================================================================== + +stx_fileio +---------- +Contributed by Jeff , 4 Nov 2002. + +Provides non-blocking random access file reading capability for +programs using the State Threads library. There is one public function: + +ssize_t stx_file_read(st_netfd_t fd, off_t offset, + void *buf, size_t nbytes, st_utime_t timeout); + +The implementation is not optimal in that the data is copied at least once +more than should be necessary. Its usefulness is limited to cases where +random access to a file is required and where starvation of other threads +is unacceptable. + +The particular application which motivated this implementation was a UDP +file transfer protocol. Because the OS does very little buffering of UDP +traffic it is important that UDP transmission threads are not starved for +periods of time which are long relative to the interval required to +maintain a steady send rate. + +Licensed under the same dual MPL/GPL as core State Threads. + +======================================================================== + +stx_dns +------- + +Documentation coming. + +======================================================================== diff --git a/trunk/3rdparty/st-srs/extensions/common.h b/trunk/3rdparty/st-srs/extensions/common.h new file mode 100644 index 000000000..f6298ba09 --- /dev/null +++ b/trunk/3rdparty/st-srs/extensions/common.h @@ -0,0 +1,77 @@ +#ifndef _STX_COMMON_H_ +#define _STX_COMMON_H_ + +#include +#include + + +#define STX_BEGIN_MACRO { +#define STX_END_MACRO } + + +/***************************************** + * Circular linked list definitions + */ + +typedef struct _stx_clist { + struct _stx_clist *next; + struct _stx_clist *prev; +} stx_clist_t; + +/* Insert element "_e" into the list, before "_l" */ +#define STX_CLIST_INSERT_BEFORE(_e,_l) \ + STX_BEGIN_MACRO \ + (_e)->next = (_l); \ + (_e)->prev = (_l)->prev; \ + (_l)->prev->next = (_e); \ + (_l)->prev = (_e); \ + STX_END_MACRO + +/* Insert element "_e" into the list, after "_l" */ +#define STX_CLIST_INSERT_AFTER(_e,_l) \ + STX_BEGIN_MACRO \ + (_e)->next = (_l)->next; \ + (_e)->prev = (_l); \ + (_l)->next->prev = (_e); \ + (_l)->next = (_e); \ + STX_END_MACRO + +/* Append an element "_e" to the end of the list "_l" */ +#define STX_CLIST_APPEND_LINK(_e,_l) STX_CLIST_INSERT_BEFORE(_e,_l) + +/* Remove the element "_e" from it's circular list */ +#define STX_CLIST_REMOVE_LINK(_e) \ + STX_BEGIN_MACRO \ + (_e)->prev->next = (_e)->next; \ + (_e)->next->prev = (_e)->prev; \ + STX_END_MACRO + +/* Return the head/tail of the list */ +#define STX_CLIST_HEAD(_l) (_l)->next +#define STX_CLIST_TAIL(_l) (_l)->prev + +/* Return non-zero if the given circular list "_l" is empty, */ +/* zero if the circular list is not empty */ +#define STX_CLIST_IS_EMPTY(_l) \ + ((_l)->next == (_l)) + +/* Initialize a circular list */ +#define STX_CLIST_INIT_CLIST(_l) \ + STX_BEGIN_MACRO \ + (_l)->next = (_l); \ + (_l)->prev = (_l); \ + STX_END_MACRO + + +/***************************************** + * Useful macros + */ + +#ifndef offsetof +#define offsetof(type, identifier) ((size_t)&(((type *)0)->identifier)) +#endif + +#define STX_MIN(a, b) (((a) < (b)) ? (a) : (b)) + +#endif /* !_STX_COMMON_H_ */ + diff --git a/trunk/3rdparty/st-srs/extensions/dnscache.c b/trunk/3rdparty/st-srs/extensions/dnscache.c new file mode 100644 index 000000000..ac49166a1 --- /dev/null +++ b/trunk/3rdparty/st-srs/extensions/dnscache.c @@ -0,0 +1,190 @@ +#include "stx.h" +#include "common.h" + + +/***************************************** + * Basic types definitions + */ + +typedef struct _stx_dns_data { + struct in_addr *addrs; + int num_addrs; + int cur; + time_t expires; +} stx_dns_data_t; + + +#define MAX_HOST_ADDRS 1024 + +static struct in_addr addr_list[MAX_HOST_ADDRS]; + +stx_cache_t *_stx_dns_cache = NULL; + +extern int _stx_dns_ttl; +extern int _stx_dns_getaddrlist(const char *hostname, struct in_addr *addrs, + int *num_addrs, st_utime_t timeout); + + +static unsigned long hash_hostname(const void *key) +{ + const char *name = (const char *)key; + unsigned long hash = 0; + + while (*name) + hash = (hash << 4) - hash + *name++; /* hash = hash * 15 + *name++ */ + + return hash; +} + +static void cleanup_entry(void *key, void *data) +{ + if (key) + free(key); + + if (data) { + if (((stx_dns_data_t *)data)->addrs) + free(((stx_dns_data_t *)data)->addrs); + free(data); + } +} + +static int lookup_entry(const char *host, struct in_addr *addrs, + int *num_addrs, int rotate) +{ + stx_cache_entry_t *entry; + stx_dns_data_t *data; + int n; + + entry = stx_cache_entry_lookup(_stx_dns_cache, host); + if (entry) { + data = (stx_dns_data_t *)stx_cache_entry_getdata(entry); + if (st_time() <= data->expires) { + if (*num_addrs == 1) { + if (rotate) { + *addrs = data->addrs[data->cur++]; + if (data->cur >= data->num_addrs) + data->cur = 0; + } else { + *addrs = data->addrs[0]; + } + } else { + n = STX_MIN(*num_addrs, data->num_addrs); + memcpy(addrs, data->addrs, n * sizeof(*addrs)); + *num_addrs = n; + } + + stx_cache_entry_release(_stx_dns_cache, entry); + return 1; + } + + /* + * Cache entry expired: decrement its refcount and purge it from cache. + */ + stx_cache_entry_release(_stx_dns_cache, entry); + stx_cache_entry_delete(_stx_dns_cache, entry); + } + + return 0; +} + +static void insert_entry(const char *host, struct in_addr *addrs, int count) +{ + stx_cache_entry_t *entry; + stx_dns_data_t *data; + char *key; + size_t n; + + if (_stx_dns_ttl > 0) { + key = strdup(host); + data = (stx_dns_data_t *)malloc(sizeof(stx_dns_data_t)); + n = count * sizeof(*addrs); + if (data) { + data->addrs = (struct in_addr *)malloc(n); + if (data->addrs) + memcpy(data->addrs, addrs, n); + data->num_addrs = count; + data->cur = 0; + data->expires = st_time() + _stx_dns_ttl; + } + entry = stx_cache_entry_create(key, data, strlen(host) + 1 + + sizeof(stx_dns_data_t) + n + + stx_cache_entry_sizeof()); + if (key && data && data->addrs && entry && + stx_cache_entry_insert(_stx_dns_cache, entry) == 0) { + stx_cache_entry_release(_stx_dns_cache, entry); + return; + } + + if (entry) + stx_cache_entry_delete(_stx_dns_cache, entry); + else + cleanup_entry(key, data); + } +} + + + +int _stx_dns_cache_getaddrlist(const char *hostname, struct in_addr *addrs, + int *num_addrs, st_utime_t timeout, + int rotate) +{ + char host[128]; + int n, count; + + if (!_stx_dns_cache) + return _stx_dns_getaddrlist(hostname, addrs, num_addrs, timeout); + + for (n = 0; n < sizeof(host) - 1 && hostname[n]; n++) { + host[n] = tolower(hostname[n]); + } + host[n] = '\0'; + + if (lookup_entry(host, addrs, num_addrs, rotate)) + return 0; + + count = MAX_HOST_ADDRS; + if (_stx_dns_getaddrlist(host, addr_list, &count, timeout) < 0) + return -1; + n = STX_MIN(*num_addrs, count); + memcpy(addrs, addr_list, n * sizeof(*addrs)); + *num_addrs = n; + + insert_entry(host, addr_list, count); + return 0; +} + + +int stx_dns_cache_init(size_t max_size, size_t max_bytes, size_t hash_size) +{ + _stx_dns_cache = stx_cache_create(max_size, max_bytes, hash_size, + hash_hostname, + (long (*)(const void *, const void *))strcmp, + cleanup_entry); + if (!_stx_dns_cache) + return -1; + + return 0; +} + +void stx_dns_cache_getinfo(stx_cache_info_t *info) +{ + if (_stx_dns_cache) + stx_cache_getinfo(_stx_dns_cache, info); + else + memset(info, 0, sizeof(stx_cache_info_t)); +} + +int stx_dns_getaddrlist(const char *hostname, struct in_addr *addrs, + int *num_addrs, st_utime_t timeout) +{ + return _stx_dns_cache_getaddrlist(hostname, addrs, num_addrs, timeout, 0); +} + +int stx_dns_getaddr(const char *hostname, struct in_addr *addr, + st_utime_t timeout) +{ + int n = 1; + + return _stx_dns_cache_getaddrlist(hostname, addr, &n, timeout, 1); +} + diff --git a/trunk/3rdparty/st-srs/extensions/dnsres.c b/trunk/3rdparty/st-srs/extensions/dnsres.c new file mode 100644 index 000000000..04a91ccaf --- /dev/null +++ b/trunk/3rdparty/st-srs/extensions/dnsres.c @@ -0,0 +1,305 @@ +/* + * Copyright (c) 1985, 1988, 1993 + * The Regents of the University of California. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by the University of + * California, Berkeley and its contributors. + * 4. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * Portions created by SGI are Copyright (C) 2000 Silicon Graphics, Inc. + * All Rights Reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of Silicon Graphics, Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * HOLDERS AND CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED + * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "stx.h" + +#define MAXPACKET 1024 + +#if !defined(NETDB_INTERNAL) && defined(h_NETDB_INTERNAL) +#define NETDB_INTERNAL h_NETDB_INTERNAL +#endif + +/* New in Solaris 7 */ +#if !defined(_getshort) && defined(ns_get16) +#define _getshort(cp) ns_get16(cp) +#define _getlong(cp) ns_get32(cp) +#endif + +typedef union { + HEADER hdr; + u_char buf[MAXPACKET]; +} querybuf_t; + +int _stx_dns_ttl; + + +static int parse_answer(querybuf_t *ans, int len, struct in_addr *addrs, + int *num_addrs) +{ + char buf[MAXPACKET]; + HEADER *ahp; + u_char *cp, *eoa; + int type, n, i; + + ahp = &ans->hdr; + eoa = ans->buf + len; + cp = ans->buf + sizeof(HEADER); + h_errno = TRY_AGAIN; + _stx_dns_ttl = -1; + i = 0; + + while (ahp->qdcount > 0) { + ahp->qdcount--; + cp += dn_skipname(cp, eoa) + QFIXEDSZ; + } + while (ahp->ancount > 0 && cp < eoa && i < *num_addrs) { + ahp->ancount--; + if ((n = dn_expand(ans->buf, eoa, cp, buf, sizeof(buf))) < 0) + return -1; + cp += n; + if (cp + 4 + 4 + 2 >= eoa) + return -1; + type = _getshort(cp); + cp += 4; + if (type == T_A) + _stx_dns_ttl = _getlong(cp); + cp += 4; + n = _getshort(cp); + cp += 2; + if (type == T_A) { + if (n > sizeof(*addrs) || cp + n > eoa) + return -1; + memcpy(&addrs[i++], cp, n); + } + cp += n; + } + + *num_addrs = i; + return 0; +} + + +static int query_domain(st_netfd_t nfd, const char *name, + struct in_addr *addrs, int *num_addrs, + st_utime_t timeout) +{ + querybuf_t qbuf; + u_char *buf = qbuf.buf; + HEADER *hp = &qbuf.hdr; + int blen = sizeof(qbuf); + int i, len, id; + + for (i = 0; i < _res.nscount; i++) { + len = res_mkquery(QUERY, name, C_IN, T_A, NULL, 0, NULL, buf, blen); + if (len <= 0) { + h_errno = NO_RECOVERY; + return -1; + } + id = hp->id; + + if (st_sendto(nfd, buf, len, (struct sockaddr *)&(_res.nsaddr_list[i]), + sizeof(struct sockaddr), timeout) != len) { + h_errno = NETDB_INTERNAL; + /* EINTR means interrupt by other thread, NOT by a caught signal */ + if (errno == EINTR) + return -1; + continue; + } + + /* Wait for reply */ + do { + len = st_recvfrom(nfd, buf, blen, NULL, NULL, timeout); + if (len <= 0) + break; + } while (id != hp->id); + + if (len < HFIXEDSZ) { + h_errno = NETDB_INTERNAL; + if (len >= 0) + errno = EMSGSIZE; + else if (errno == EINTR) /* see the comment above */ + return -1; + continue; + } + + hp->ancount = ntohs(hp->ancount); + hp->qdcount = ntohs(hp->qdcount); + if ((hp->rcode != NOERROR) || (hp->ancount == 0)) { + switch (hp->rcode) { + case NXDOMAIN: + h_errno = HOST_NOT_FOUND; + break; + case SERVFAIL: + h_errno = TRY_AGAIN; + break; + case NOERROR: + h_errno = NO_DATA; + break; + case FORMERR: + case NOTIMP: + case REFUSED: + default: + h_errno = NO_RECOVERY; + } + continue; + } + + if (parse_answer(&qbuf, len, addrs, num_addrs) == 0) + return 0; + } + + return -1; +} + + +#define CLOSE_AND_RETURN(ret) \ + { \ + n = errno; \ + st_netfd_close(nfd); \ + errno = n; \ + return (ret); \ + } + + +int _stx_dns_getaddrlist(const char *host, struct in_addr *addrs, + int *num_addrs, st_utime_t timeout) +{ + char name[MAXDNAME], **domain; + const char *cp; + int s, n, maxlen, dots; + int trailing_dot, tried_as_is; + st_netfd_t nfd; + + if ((_res.options & RES_INIT) == 0 && res_init() == -1) { + h_errno = NETDB_INTERNAL; + return -1; + } + if (_res.options & RES_USEVC) { + h_errno = NETDB_INTERNAL; + errno = ENOSYS; + return -1; + } + if (!host || *host == '\0') { + h_errno = HOST_NOT_FOUND; + return -1; + } + + /* Create UDP socket */ + if ((s = socket(PF_INET, SOCK_DGRAM, 0)) < 0) { + h_errno = NETDB_INTERNAL; + return -1; + } + if ((nfd = st_netfd_open_socket(s)) == NULL) { + h_errno = NETDB_INTERNAL; + n = errno; + close(s); + errno = n; + return -1; + } + + maxlen = sizeof(name) - 1; + n = 0; + dots = 0; + trailing_dot = 0; + tried_as_is = 0; + + for (cp = host; *cp && n < maxlen; cp++) { + dots += (*cp == '.'); + name[n++] = *cp; + } + if (name[n - 1] == '.') + trailing_dot = 1; + + /* + * If there are dots in the name already, let's just give it a try + * 'as is'. The threshold can be set with the "ndots" option. + */ + if (dots >= _res.ndots) { + if (query_domain(nfd, host, addrs, num_addrs, timeout) == 0) + CLOSE_AND_RETURN(0); + if (h_errno == NETDB_INTERNAL && errno == EINTR) + CLOSE_AND_RETURN(-1); + tried_as_is = 1; + } + + /* + * We do at least one level of search if + * - there is no dot and RES_DEFNAME is set, or + * - there is at least one dot, there is no trailing dot, + * and RES_DNSRCH is set. + */ + if ((!dots && (_res.options & RES_DEFNAMES)) || + (dots && !trailing_dot && (_res.options & RES_DNSRCH))) { + name[n++] = '.'; + for (domain = _res.dnsrch; *domain; domain++) { + strncpy(name + n, *domain, maxlen - n); + if (query_domain(nfd, name, addrs, num_addrs, timeout) == 0) + CLOSE_AND_RETURN(0); + if (h_errno == NETDB_INTERNAL && errno == EINTR) + CLOSE_AND_RETURN(-1); + if (!(_res.options & RES_DNSRCH)) + break; + } + } + + /* + * If we have not already tried the name "as is", do that now. + * note that we do this regardless of how many dots were in the + * name or whether it ends with a dot. + */ + if (!tried_as_is) { + if (query_domain(nfd, host, addrs, num_addrs, timeout) == 0) + CLOSE_AND_RETURN(0); + } + + CLOSE_AND_RETURN(-1); +} + diff --git a/trunk/3rdparty/st-srs/extensions/lrucache.c b/trunk/3rdparty/st-srs/extensions/lrucache.c new file mode 100644 index 000000000..33494fee6 --- /dev/null +++ b/trunk/3rdparty/st-srs/extensions/lrucache.c @@ -0,0 +1,343 @@ +#include "stx.h" +#include "common.h" + + +/***************************************** + * Basic types definitions + */ + +struct _stx_centry { + void *key; /* key for doing lookups */ + void *data; /* data in the cache */ + size_t weight; /* "weight" of this entry */ + struct _stx_centry *next; /* next entry */ + struct _stx_centry **pthis; + stx_clist_t lru_link; /* for putting this entry on LRU list */ + int ref_count; /* use count for this entry */ + int delete_pending; /* pending delete flag */ +}; + +struct _stx_cache { + size_t max_size; /* max size of cache */ + size_t cur_size; /* current size of cache */ + + size_t max_weight; /* cache capacity */ + size_t cur_weight; /* current total "weight" of all entries */ + + size_t hash_size; /* size of hash table */ + stx_cache_entry_t **table; /* hash table for this cache */ + + stx_clist_t lru_list; /* least-recently-used list */ + + /* Cache stats */ + unsigned long hits; /* num cache hits */ + unsigned long lookups; /* num cache lookups */ + unsigned long inserts; /* num inserts */ + unsigned long deletes; /* num deletes */ + + /* Functions */ + unsigned long (*key_hash_fn)(const void *); + long (*key_cmp_fn)(const void *, const void *); + void (*cleanup_fn)(void *, void *); +}; + + +#define STX_CACHE_ENTRY_PTR(_qp) \ + ((stx_cache_entry_t *)((char *)(_qp) - offsetof(stx_cache_entry_t, lru_link))) + + +/***************************************** + * Cache methods + */ + +stx_cache_t *stx_cache_create(size_t max_size, size_t max_weight, + size_t hash_size, + unsigned long (*key_hash_fn)(const void *key), + long (*key_cmp_fn)(const void *key1, + const void *key2), + void (*cleanup_fn)(void *key, void *data)) +{ + stx_cache_t *newcache; + + newcache = (stx_cache_t *)calloc(1, sizeof(stx_cache_t)); + if (newcache == NULL) + return NULL; + newcache->table = (stx_cache_entry_t **)calloc(hash_size, + sizeof(stx_cache_entry_t *)); + if (newcache->table == NULL) { + free(newcache); + return NULL; + } + + newcache->max_size = max_size; + newcache->max_weight = max_weight; + newcache->hash_size = hash_size; + STX_CLIST_INIT_CLIST(&(newcache->lru_list)); + newcache->key_hash_fn = key_hash_fn; + newcache->key_cmp_fn = key_cmp_fn; + newcache->cleanup_fn = cleanup_fn; + + return newcache; +} + + +void stx_cache_empty(stx_cache_t *cache) +{ + size_t i; + stx_cache_entry_t *entry, *next_entry; + + for (i = 0; i < cache->hash_size; i++) { + entry = cache->table[i]; + while (entry) { + next_entry = entry->next; + stx_cache_entry_delete(cache, entry); + entry = next_entry; + } + } +} + + +void stx_cache_traverse(stx_cache_t *cache, + void (*callback)(void *key, void *data)) +{ + size_t i; + stx_cache_entry_t *entry; + + for (i = 0; i < cache->hash_size; i++) { + for (entry = cache->table[i]; entry; entry = entry->next) { + if (!entry->delete_pending) + (*callback)(entry->key, entry->data); + } + } +} + + +void stx_cache_traverse_lru(stx_cache_t *cache, + void (*callback)(void *key, void *data), + unsigned int n) +{ + stx_clist_t *q; + stx_cache_entry_t *entry; + + for (q = STX_CLIST_HEAD(&cache->lru_list); q != &cache->lru_list && n; + q = q->next, n--) { + entry = STX_CACHE_ENTRY_PTR(q); + (*callback)(entry->key, entry->data); + } +} + + +void stx_cache_traverse_mru(stx_cache_t *cache, + void (*callback)(void *key, void *data), + unsigned int n) +{ + stx_clist_t *q; + stx_cache_entry_t *entry; + + for (q = STX_CLIST_TAIL(&cache->lru_list); q != &cache->lru_list && n; + q = q->prev, n--) { + entry = STX_CACHE_ENTRY_PTR(q); + (*callback)(entry->key, entry->data); + } +} + + +size_t stx_cache_getsize(stx_cache_t *cache) +{ + return cache->cur_size; +} + + +size_t stx_cache_getweight(stx_cache_t *cache) +{ + return cache->cur_weight; +} + + +void stx_cache_getinfo(stx_cache_t *cache, stx_cache_info_t *info) +{ + info->max_size = cache->max_size; + info->max_weight = cache->max_weight; + info->hash_size = cache->hash_size; + info->cur_size = cache->cur_size; + info->cur_weight = cache->cur_weight; + info->hits = cache->hits; + info->lookups = cache->lookups; + info->inserts = cache->inserts; + info->deletes = cache->deletes; +} + + +/***************************************** + * Cache entry methods + */ + +stx_cache_entry_t *stx_cache_entry_create(void *key, void *data, + size_t weight) +{ + stx_cache_entry_t *newentry; + + newentry = (stx_cache_entry_t *)calloc(1, sizeof(stx_cache_entry_t)); + if (newentry == NULL) + return NULL; + + newentry->key = key; + newentry->data = data; + newentry->weight = weight; + + return newentry; +} + + +void stx_cache_entry_delete(stx_cache_t *cache, stx_cache_entry_t *entry) +{ + entry->delete_pending = 1; + + if (entry->ref_count > 0) + return; + + if (entry->pthis) { + *entry->pthis = entry->next; + if (entry->next) + entry->next->pthis = entry->pthis; + + cache->cur_size--; + cache->cur_weight -= entry->weight; + cache->deletes++; + STX_CLIST_REMOVE_LINK(&(entry->lru_link)); + } + + if (cache->cleanup_fn) + cache->cleanup_fn(entry->key, entry->data); + + entry->pthis = NULL; + entry->key = NULL; + entry->data = NULL; + free(entry); +} + + +stx_cache_entry_t *stx_cache_entry_lookup(stx_cache_t *cache, const void *key) +{ + unsigned long bucket; + stx_cache_entry_t *entry; + + cache->lookups++; + bucket = cache->key_hash_fn(key) % cache->hash_size; + for (entry = cache->table[bucket]; entry; entry = entry->next) { + if (!entry->delete_pending && cache->key_cmp_fn(key, entry->key) == 0) + break; + } + if (entry) { + cache->hits++; + if (entry->ref_count == 0) + STX_CLIST_REMOVE_LINK(&(entry->lru_link)); + entry->ref_count++; + } + + return entry; +} + + +void stx_cache_entry_release(stx_cache_t *cache, stx_cache_entry_t *entry) +{ + if (entry->ref_count == 0) + return; + + entry->ref_count--; + + if (entry->ref_count == 0) { + STX_CLIST_APPEND_LINK(&(entry->lru_link), &(cache->lru_list)); + if (entry->delete_pending) + stx_cache_entry_delete(cache, entry); + } +} + + +int stx_cache_entry_insert(stx_cache_t *cache, stx_cache_entry_t *entry) +{ + stx_cache_entry_t *old_entry; + unsigned long bucket; + + /* + * If cache capacity is exceeded, try to remove LRU entries till there is + * enough room or LRU list is empty. + */ + while (cache->cur_weight + entry->weight > cache->max_weight) { + old_entry = stx_cache_entry_getlru(cache); + if (!old_entry) { + /* cache capacity is exceeded and all entries are in use */ + return -1; + } + stx_cache_entry_delete(cache, old_entry); + } + + /* If cache size is exceeded, remove LRU entry */ + if (cache->cur_size >= cache->max_size) { + old_entry = stx_cache_entry_getlru(cache); + if (!old_entry) { + /* cache size is exceeded and all entries are in use */ + return -1; + } + stx_cache_entry_delete(cache, old_entry); + } + + /* Don't add duplicate entries in the cache */ + bucket = cache->key_hash_fn(entry->key) % cache->hash_size; + for (old_entry = cache->table[bucket]; old_entry; + old_entry = old_entry->next) { + if (!old_entry->delete_pending && + cache->key_cmp_fn(entry->key, old_entry->key) == 0) + break; + } + if (old_entry) + stx_cache_entry_delete(cache, old_entry); + + /* Insert in the hash table */ + entry->next = cache->table[bucket]; + cache->table[bucket] = entry; + entry->pthis = &cache->table[bucket]; + if (entry->next) + entry->next->pthis = &entry->next; + entry->ref_count++; + + cache->inserts++; + cache->cur_size++; + cache->cur_weight += entry->weight; + + return 0; +} + + +stx_cache_entry_t *stx_cache_entry_getlru(stx_cache_t *cache) +{ + if (STX_CLIST_IS_EMPTY(&(cache->lru_list))) + return NULL; + + return STX_CACHE_ENTRY_PTR(STX_CLIST_HEAD(&(cache->lru_list))); +} + + +int stx_cache_entry_sizeof(void) +{ + return (int)sizeof(stx_cache_entry_t); +} + + +void *stx_cache_entry_getdata(stx_cache_entry_t *entry) +{ + return entry->data; +} + + +void *stx_cache_entry_getkey(stx_cache_entry_t *entry) +{ + return entry->key; +} + + +size_t stx_cache_entry_getweight(stx_cache_entry_t *entry) +{ + return entry->weight; +} + diff --git a/trunk/3rdparty/st-srs/extensions/print_stk.patch b/trunk/3rdparty/st-srs/extensions/print_stk.patch new file mode 100644 index 000000000..f7451c7b0 --- /dev/null +++ b/trunk/3rdparty/st-srs/extensions/print_stk.patch @@ -0,0 +1,367 @@ +Michael Abd-El-Malek contributed this patch. He wrote: +---------------------------------------- +Hello, + +This is a patch that enables programmatically dumping the stack of +every thread. This has been useful in debugging deadlocks, etc... +Our usage model is that the SIGUSR2 handler calls the new +_st_print_thread_stacks function, which dumps the stack for all +threads. A convenient feature is that for thread stacks that are the +same (which is common for application with a lot of worker threads +waiting for work), only one stack trace is printed, along with a +count of how many threads have that same stack. + +I use the glibc backtrace function to get the backtrace, and then use +popen to execute addr2line and convert memory addresses to file +names, function names, and line numbers. If glibc isn't available, +_st_print_thread_stacks just prints a warning. And this feature is +only available if DEBUG is turned on. + +We've found this feature extremely helpful when debugging. + +The patch can be a bit more robust (it assumes addr2line exists). +But I didn't want to go through the hassle of doing this, if the +StateThreads community doesn't want to use this patch. (In our +environment, addr2line will always be there.) + +Cheers, +Mike +---------------------------------------- +Invoking complex functions from a signal handler is not recommended, +plus this patch changes the behavior of existing API hooks. It will +not become part of State Threads proper but you may find it useful +nonetheless. This patch applies to st-1.5.2. + +diff -Nur Makefile.1.5.2 Makefile +--- Makefile.1.5.2 Wed Sep 7 14:19:50 2005 ++++ Makefile Wed Sep 7 14:33:08 2005 +@@ -255,7 +255,8 @@ + $(TARGETDIR)/stk.o \ + $(TARGETDIR)/sync.o \ + $(TARGETDIR)/key.o \ +- $(TARGETDIR)/io.o ++ $(TARGETDIR)/io.o \ ++ $(TARGETDIR)/backtrace.o + OBJS += $(EXTRA_OBJS) + HEADER = $(TARGETDIR)/st.h + SLIBRARY = $(TARGETDIR)/libst.a +diff -Nur backtrace.c.1.5.2 backtrace.c +--- backtrace.c.1.5.2 Wed Dec 31 16:00:00 1969 ++++ backtrace.c Wed Sep 7 13:40:21 2005 +@@ -0,0 +1,211 @@ ++/* ++ * The contents of this file are subject to the Mozilla Public ++ * License Version 1.1 (the "License"); you may not use this file ++ * except in compliance with the License. You may obtain a copy of ++ * the License at http://www.mozilla.org/MPL/ ++ * ++ * Software distributed under the License is distributed on an "AS ++ * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or ++ * implied. See the License for the specific language governing ++ * rights and limitations under the License. ++ * ++ * Contributor(s): Michael Abd-El-Malek (mabdelmalek@cmu.edu) ++ * Carnegie Mellon University ++ * ++ * Alternatively, the contents of this file may be used under the ++ * terms of the GNU General Public License Version 2 or later (the ++ * "GPL"), in which case the provisions of the GPL are applicable ++ * instead of those above. If you wish to allow use of your ++ * version of this file only under the terms of the GPL and not to ++ * allow others to use your version of this file under the MPL, ++ * indicate your decision by deleting the provisions above and ++ * replace them with the notice and other provisions required by ++ * the GPL. If you do not delete the provisions above, a recipient ++ * may use your version of this file under either the MPL or the ++ * GPL. ++ */ ++ ++ ++ ++/* ++ * This file contains routines for printing a stack trace of all threads. ++ * Only works when DEBUG is defined and where glibc is available, since it ++ * provides the backtrace() function. ++ */ ++ ++#define _GNU_SOURCE /* to get program_invocation_name */ ++ ++#include ++#include ++ ++ ++#if defined(DEBUG) && defined(__GLIBC__) ++ ++#include ++#include "common.h" ++#include ++#include ++#include ++ ++ ++/* The maximum number of frames to get a stack trace for. If a thread has more ++ * frames than this, then we only show the latest X frames. */ ++#define MAX_NUM_FRAMES 64 ++ ++ ++typedef struct thread_stack_s { ++ uint32_t num_frames; ++ void* addresses[MAX_NUM_FRAMES]; /* frame pointers */ ++ char* locations[MAX_NUM_FRAMES]; /* file/function/line numbers */ ++ uint32_t num_matches; ++ ++ struct thread_stack_s* next; ++} thread_stack_t; ++ ++static thread_stack_t* stacks = NULL; ++ ++ ++/* Converts the function's memory addresses to function names, file names, and ++ * line numbers. Calls binutil's addr2line program. */ ++static void get_symbol_names(thread_stack_t *stack) ++{ ++ char program_to_run[1024], function[256], filename_lineno[256], temp[19]; ++ FILE* output; ++ int num_bytes_left; ++ uint32_t i; ++ ++ /* Construct the arguments to addr2line */ ++ num_bytes_left = sizeof(program_to_run); ++ num_bytes_left -= snprintf(program_to_run, sizeof(program_to_run), ++ "addr2line -fCe %s", program_invocation_name); ++ for (i = 0; i < stack->num_frames && num_bytes_left > 0; ++i) { ++ num_bytes_left -= snprintf(temp, sizeof(temp), " %p", stack->addresses[i]); ++ strncat(program_to_run, temp, num_bytes_left); ++ } ++ ++ /* Use popen to execute addr2line and read its ouput */ ++ output = popen(program_to_run, "r"); ++ for (i = 0; i < stack->num_frames; ++i) { ++ char* function_listing = (char*) malloc(512); ++ fscanf(output, "%255s\n", function); ++ fscanf(output, "%255s\n", filename_lineno); ++ snprintf(function_listing, 512, "%s at %s", function, filename_lineno); ++ stack->locations[i] = function_listing; ++ } ++ pclose(output); ++} ++ ++ ++static void print_stack(thread_stack_t* stack) ++{ ++ int skip_offset = 0, cmp_len; ++ uint32_t i; ++ ++ /* Get the function names/filenames/line numbers */ ++ get_symbol_names(stack); ++ ++ cmp_len = strlen("_st_iterate_threads_helper"); ++ ++ /* Print the backtrace */ ++ for (i = 0; i < stack->num_frames; ++i) { ++ /* Skip frames we don't have location info for */ ++ if (!strncmp(stack->locations[i], "??", 2)) { ++ continue; ++ } ++ ++ /* Skip the frames that are used for printing the stack trace */ ++ if (skip_offset) { ++ printf("\t#%2d %s %p\n", i - skip_offset, stack->locations[i], ++ stack->addresses[i]); ++ } else if (!strncmp(stack->locations[i], "_st_iterate_threads_helper", ++ cmp_len)) { ++ skip_offset = i + 1; ++ } ++ } ++} ++ ++ ++static void add_current_thread_stack(void) ++{ ++ thread_stack_t *new_stack = malloc(sizeof(thread_stack_t)); ++ thread_stack_t *search; ++ ++ /* Call glibc function to get the backtrace */ ++ new_stack->num_frames = backtrace(new_stack->addresses, MAX_NUM_FRAMES); ++ ++ /* Check if we have another stacks that is equivalent. If so, then coaelsce ++ * two stacks into one, to minimize output to user. */ ++ search = stacks; ++ while (search) { ++ if (search->num_frames == new_stack->num_frames && ++ !memcmp(search->addresses, new_stack->addresses, ++ search->num_frames * sizeof(void*))) { ++ /* Found an existing stack that is the same as this thread's stack */ ++ ++search->num_matches; ++ free(new_stack); ++ return; ++ } else { ++ search = search->next; ++ } ++ } ++ ++ /* This is a new stack. Add it to the list of stacks. */ ++ new_stack->num_matches = 1; ++ new_stack->next = stacks; ++ stacks = new_stack; ++} ++ ++static void print_stack_frames(void) ++{ ++ while (stacks) { ++ printf("\n%u thread(s) with this backtrace:\n", stacks->num_matches); ++ print_stack(stacks); ++ stacks = stacks->next; ++ } ++ printf("\n"); ++} ++ ++static void free_stacks(void) ++{ ++ uint32_t i; ++ while (stacks) { ++ thread_stack_t *next = stacks->next; ++ for (i = 0; i < stacks->num_frames; ++i) { ++ free(stacks->locations[i]); ++ } ++ free(stacks); ++ stacks = next; ++ } ++ stacks = NULL; ++} ++ ++ ++static void st_print_thread_stack(_st_thread_t *thread, int start_flag, ++ int end_flag) ++{ ++ if (end_flag == 0) { ++ add_current_thread_stack(); ++ } else { ++ print_stack_frames(); ++ } ++} ++ ++ ++void _st_print_thread_stacks(int ignore) ++{ ++ _st_iterate_threads_flag = 1; ++ _st_iterate_threads_helper(st_print_thread_stack); ++ _st_iterate_threads_flag = 0; ++ ++ /* Deallocate memory */ ++ free_stacks(); ++} ++ ++#else /* defined(DEBUG) && defined(__GLIBC__) */ ++ ++void _st_print_thread_stacks(int ignore) ++{ ++ printf("%s: need DEBUG mode and glibc-specific functions to read stack.\n", ++ __FUNCTION__); ++} ++#endif /* defined(DEBUG) && defined(__GLIBC__) */ +diff -Nur common.h.1.5.2 common.h +--- common.h.1.5.2 Wed Sep 7 14:18:37 2005 ++++ common.h Wed Sep 7 14:35:36 2005 +@@ -371,8 +371,18 @@ + */ + + #ifdef DEBUG +-void _st_iterate_threads(void); +-#define ST_DEBUG_ITERATE_THREADS() _st_iterate_threads() ++typedef void(*_st_func_ptr_t)(_st_thread_t *thread, ++ int start_flag, ++ int end_flag); ++/* Pointer to function that will be called on thread switch */ ++extern _st_func_ptr_t _st_iterate_func_ptr; ++extern int _st_iterate_threads_flag; ++/* Thread iteration function that will call an arbitrary function */ ++extern void _st_iterate_threads_helper(_st_func_ptr_t func); ++#define ST_DEBUG_ITERATE_THREADS() \ ++ if (_st_iterate_func_ptr) { \ ++ _st_iterate_threads_helper(_st_iterate_func_ptr); \ ++ } + #else + #define ST_DEBUG_ITERATE_THREADS() + #endif +diff -Nur public.h.1.5.2 public.h +--- public.h.1.5.2 Wed Sep 7 11:46:58 2005 ++++ public.h Wed Sep 7 13:38:46 2005 +@@ -171,8 +171,10 @@ + extern st_netfd_t st_open(const char *path, int oflags, mode_t mode); + + #ifdef DEBUG +-extern void _st_show_thread_stack(st_thread_t thread, const char *messg); ++extern void _st_show_thread_stack(st_thread_t thread, int start_flag, ++ int end_flag); + extern void _st_iterate_threads(void); ++extern void _st_print_thread_stacks(int ignore); + #endif + + #ifdef __cplusplus +diff -Nur sched.c.1.5.2 sched.c +--- sched.c.1.5.2 Wed Sep 7 10:48:05 2005 ++++ sched.c Wed Sep 7 13:38:46 2005 +@@ -919,16 +919,13 @@ + + + #ifdef DEBUG +-/* ARGSUSED */ +-void _st_show_thread_stack(_st_thread_t *thread, const char *messg) +-{ +- +-} +- + /* To be set from debugger */ + int _st_iterate_threads_flag = 0; ++/* Thread iteration function that will call an arbitrary function */ ++_st_func_ptr_t _st_iterate_func_ptr = NULL; + +-void _st_iterate_threads(void) ++/* This function iterates over all threads, calling "func" for each thread. */ ++void _st_iterate_threads_helper(_st_func_ptr_t func) + { + static _st_thread_t *thread = NULL; + static jmp_buf orig_jb, save_jb; +@@ -944,16 +941,20 @@ + + if (thread) { + memcpy(thread->context, save_jb, sizeof(jmp_buf)); +- _st_show_thread_stack(thread, NULL); ++ func(thread, 0, 0); + } else { + if (MD_SETJMP(orig_jb)) { + _st_iterate_threads_flag = 0; ++ _st_iterate_func_ptr = NULL; + thread = NULL; +- _st_show_thread_stack(thread, "Iteration completed"); ++ /* Last thread to iterate through */ ++ func(thread, 0, 1); + return; + } ++ /* First thread to iterate through */ + thread = _ST_CURRENT_THREAD(); +- _st_show_thread_stack(thread, "Iteration started"); ++ _st_iterate_func_ptr = func; ++ func(thread, 1, 0); + } + + q = thread->tlink.next; +@@ -966,5 +967,17 @@ + memcpy(save_jb, thread->context, sizeof(jmp_buf)); + MD_LONGJMP(thread->context, 1); + } ++ ++/* ARGSUSED */ ++void _st_show_thread_stack(_st_thread_t *thread, int start_flag, int end_flag) ++{ ++} ++ ++/* Iterate over threads inside debugger; see st/README */ ++void _st_iterate_threads(void) ++{ ++ _st_iterate_threads_helper(_st_show_thread_stack); ++} ++ + #endif /* DEBUG */ + diff --git a/trunk/3rdparty/st-srs/extensions/stx.h b/trunk/3rdparty/st-srs/extensions/stx.h new file mode 100644 index 000000000..8371e0d93 --- /dev/null +++ b/trunk/3rdparty/st-srs/extensions/stx.h @@ -0,0 +1,91 @@ +#ifndef _STX_H_ +#define _STX_H_ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "st.h" + + +#ifdef __cplusplus +extern "C" { +#endif + + +/***************************************** + * Basic types definitions + */ + +typedef struct _stx_centry stx_cache_entry_t; +typedef struct _stx_cache stx_cache_t; + +/* This is public type */ +typedef struct _stx_cache_info { + size_t max_size; + size_t max_weight; + size_t hash_size; + size_t cur_size; + size_t cur_weight; + unsigned long hits; + unsigned long lookups; + unsigned long inserts; + unsigned long deletes; +} stx_cache_info_t; + + +/***************************************** + * Cache and cache entry methods + */ + +stx_cache_t *stx_cache_create(size_t max_size, size_t max_weight, + size_t hash_size, + unsigned long (*key_hash_fn)(const void *key), + long (*key_cmp_fn)(const void *key1, + const void *key2), + void (*cleanup_fn)(void *key, void *data)); +void stx_cache_empty(stx_cache_t *cache); +void stx_cache_traverse(stx_cache_t *cache, + void (*callback)(void *key, void *data)); +void stx_cache_traverse_lru(stx_cache_t *, void (*)(void *, void *), + unsigned int); +void stx_cache_traverse_mru(stx_cache_t *, void (*)(void *, void *), + unsigned int); +void stx_cache_getinfo(stx_cache_t *cache, stx_cache_info_t *info); +size_t stx_cache_getsize(stx_cache_t *cache); +size_t stx_cache_getweight(stx_cache_t *cache); + + +stx_cache_entry_t *stx_cache_entry_create(void *key, void *data, + size_t weight); +void stx_cache_entry_delete(stx_cache_t *cache, stx_cache_entry_t *entry); +stx_cache_entry_t *stx_cache_entry_lookup(stx_cache_t *cache, const void *key); +void stx_cache_entry_release(stx_cache_t *, stx_cache_entry_t *); +int stx_cache_entry_insert(stx_cache_t *cache, stx_cache_entry_t *entry); +stx_cache_entry_t *stx_cache_entry_getlru(stx_cache_t *cache); +int stx_cache_entry_sizeof(void); +void *stx_cache_entry_getdata(stx_cache_entry_t *entry); +void *stx_cache_entry_getkey(stx_cache_entry_t *entry); +size_t stx_cache_entry_getweight(stx_cache_entry_t *entry); + + +int stx_dns_cache_init(size_t max_size, size_t max_bytes, size_t hash_size); +void stx_dns_cache_getinfo(stx_cache_info_t *info); +int stx_dns_getaddrlist(const char *hostname, struct in_addr *addrs, + int *num_addrs, st_utime_t timeout); +int stx_dns_getaddr(const char *hostname, struct in_addr *addr, + st_utime_t timeout); + +#ifdef __cplusplus +} +#endif + +#endif /* !_STX_H_ */ + diff --git a/trunk/3rdparty/st-srs/extensions/stx_fileio.c b/trunk/3rdparty/st-srs/extensions/stx_fileio.c new file mode 100644 index 000000000..cb24346e8 --- /dev/null +++ b/trunk/3rdparty/st-srs/extensions/stx_fileio.c @@ -0,0 +1,197 @@ +/* + * File I/O extension to the State Threads Library. + */ + +/* + * The contents of this file are subject to the Mozilla Public + * License Version 1.1 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS + * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or + * implied. See the License for the specific language governing + * rights and limitations under the License. + * + * The Original Code is the file I/O extension to the State Threads Library. + * + * The Initial Developer of the Original Code is Jeff + * . Portions created by the Initial + * Developer are Copyright (C) 2002 the Initial Developer. All Rights + * Reserved. + * + * Contributor(s): (none) + * + * Alternatively, the contents of this file may be used under the + * terms of the GNU General Public License Version 2 or later (the + * "GPL"), in which case the provisions of the GPL are applicable + * instead of those above. If you wish to allow use of your + * version of this file only under the terms of the GPL and not to + * allow others to use your version of this file under the MPL, + * indicate your decision by deleting the provisions above and + * replace them with the notice and other provisions required by + * the GPL. If you do not delete the provisions above, a recipient + * may use your version of this file under either the MPL or the + * GPL. + */ + +#include + +#include "stx_fileio.h" + +#define STX_FILEIO_SIGNUM SIGUSR2 + +typedef struct { + st_netfd_t data_fd; + st_netfd_t control_fd; + pid_t pid; +} fileio_data_t; + +#define FILEREADER_MAX_READ 1024 + +typedef struct { + off_t offset; + ssize_t nbytes; +} file_reader_cb_t; + +/** + * Fork a process to read a file and return its pid. Receives + * offset/length commands from control stream and sends corresponding data + * to out stream. A zero length on the control stream signals an end. + * + * @param fd stream from which to read + * @param control_out receives the file descriptor to which control commands can be sent + * @param fd_out receives the file descriptor from which the output of the command can be read. + * @return PID of the process created to execute the command + */ +pid_t +file_reader(int fd, int *fd_control, int *fd_out) +{ + pid_t pid; + int control_pipe[2], out_pipe[2]; + + if (pipe(control_pipe) < 0 || pipe(out_pipe) < 0) + return (pid_t)-1; + + pid = fork(); + if (pid == (pid_t) -1) + { + close(control_pipe[0]); + close(control_pipe[1]); + close(out_pipe[0]); + close(out_pipe[1]); + return pid; + } + else if (pid == (pid_t) 0) + { + // child + off_t pos = 0; + file_reader_cb_t cb; + char buf[FILEREADER_MAX_READ]; + if (fd == -1) + _exit(EXIT_FAILURE); + + while (sizeof(cb) == read(control_pipe[0], &cb, sizeof(cb))) { + ssize_t nb; + if (0 >= cb.nbytes) + goto clean_exit; + if (pos != cb.offset) { + pos = lseek(fd, cb.offset, SEEK_SET); + if (pos == (off_t)-1) + break; + } + nb = read(fd, buf, cb.nbytes); + if (nb == (ssize_t)-1) + break; + pos += nb; + write(out_pipe[1], (char *)&nb, sizeof(nb)); + write(out_pipe[1], buf, nb); + } + perror("ERROR: file_reader: "); + clean_exit: + close(control_pipe[0]); + close(control_pipe[1]); + close(out_pipe[0]); + close(out_pipe[1]); + _exit(EXIT_SUCCESS); + } + + // parent + close(out_pipe[1]); + close(control_pipe[0]); + *fd_out = out_pipe[0]; + *fd_control = control_pipe[1]; + return pid; +} + +/** + * fileio_data_t destructor callback + */ +static void +fileio_data_destructor(void *dat_in) +{ + if (dat_in) { + fileio_data_t *dat = (fileio_data_t *)dat_in; + file_reader_cb_t cb; + cb.offset = 0; + cb.nbytes = 0; + st_write(dat->control_fd, (char *)&cb, sizeof(cb), + ST_UTIME_NO_TIMEOUT); + waitpid(dat->pid, NULL, 0); + st_netfd_close(dat->control_fd); + st_netfd_close(dat->data_fd); + free(dat_in); + } +} + +/** + * Retrieve fileio_data_t struct from an st descriptor. Create and store + * a new one if needed. + */ +static fileio_data_t *get_fileio_data(st_netfd_t fd) +{ + fileio_data_t *dat = (fileio_data_t *)st_netfd_getspecific(fd); + if (!dat) { + int fd_control, fd_out; + pid_t pid = file_reader(st_netfd_fileno(fd), &fd_control, &fd_out); + if (pid != (pid_t)-1) { + dat = (fileio_data_t *)calloc(1, sizeof(fileio_data_t)); + dat->control_fd = st_netfd_open(fd_control); + dat->data_fd = st_netfd_open(fd_out); + dat->pid = pid; + st_netfd_setspecific(fd, dat, fileio_data_destructor); + } + } + return dat; +} + +/** + * Read data from the specified section of a file. Uses a forked + * file_reader process to do the actual reading so as to avoid causing all + * State Threads to block. + * + * @param fd must refer to a seekable file. + * @param offset absolute offset within the file + * @param buf output buffer + * @param nbytes size of the output buffer + * @param timeout + */ +ssize_t +stx_file_read(st_netfd_t fd, off_t offset, void *buf, size_t nbytes, st_utime_t timeout) +{ + fileio_data_t *dat = get_fileio_data(fd); + if (dat) { + file_reader_cb_t cb; + ssize_t ret = (ssize_t)-1; + cb.offset = offset; + cb.nbytes = nbytes; + st_write(dat->control_fd, (char *)&cb, sizeof(cb), timeout); + if (sizeof(ret) == st_read(dat->data_fd, (char *)&ret, sizeof(ret), timeout) && 0 < ret && ret <= nbytes) { + return st_read(dat->data_fd, buf, ret, timeout); + } else { + return ret; + } + } + + return (ssize_t)-1; +} diff --git a/trunk/3rdparty/st-srs/extensions/stx_fileio.h b/trunk/3rdparty/st-srs/extensions/stx_fileio.h new file mode 100644 index 000000000..b6bec190b --- /dev/null +++ b/trunk/3rdparty/st-srs/extensions/stx_fileio.h @@ -0,0 +1,52 @@ +/* + * File I/O extension to the State Threads Library. + */ + +/* + * The contents of this file are subject to the Mozilla Public + * License Version 1.1 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS + * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or + * implied. See the License for the specific language governing + * rights and limitations under the License. + * + * The Original Code is the file I/O extension to the State Threads Library. + * + * The Initial Developer of the Original Code is Jeff + * . Portions created by the Initial + * Developer are Copyright (C) 2002 the Initial Developer. All Rights + * Reserved. + * + * Contributor(s): (none) + * + * Alternatively, the contents of this file may be used under the + * terms of the GNU General Public License Version 2 or later (the + * "GPL"), in which case the provisions of the GPL are applicable + * instead of those above. If you wish to allow use of your + * version of this file only under the terms of the GPL and not to + * allow others to use your version of this file under the MPL, + * indicate your decision by deleting the provisions above and + * replace them with the notice and other provisions required by + * the GPL. If you do not delete the provisions above, a recipient + * may use your version of this file under either the MPL or the + * GPL. + */ + +#ifndef __STX_FILEIO_H__ +#define __STX_FILEIO_H__ + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +extern ssize_t stx_file_read(st_netfd_t fd, off_t offset, void *buf, size_t nbytes, st_utime_t timeout); + +#ifdef __cplusplus +} +#endif +#endif /* !__STX_FILEIO_H__ */ diff --git a/trunk/3rdparty/st-srs/extensions/testdns.c b/trunk/3rdparty/st-srs/extensions/testdns.c new file mode 100644 index 000000000..aa896b25e --- /dev/null +++ b/trunk/3rdparty/st-srs/extensions/testdns.c @@ -0,0 +1,112 @@ +#include "stx.h" +#include +#include + + +#define MAX_ADDRS 128 +#define TIMEOUT (4*1000000LL) + +static void do_resolve(const char *host) +{ + struct in_addr addrs[MAX_ADDRS]; + int i, n = MAX_ADDRS; + + if (stx_dns_getaddrlist(host, addrs, &n, TIMEOUT) < 0) { + fprintf(stderr, "stx_dns_getaddrlist: can't resolve %s: ", host); + if (h_errno == NETDB_INTERNAL) + perror(""); + else + herror(""); + } else { + if (n > 0) + printf("%-40s %s\n", (char *)host, inet_ntoa(addrs[0])); + for (i = 1; i < n; i++) + printf("%-40s %s\n", "", inet_ntoa(addrs[i])); + } +} + +static void show_info(void) +{ + stx_cache_info_t info; + + stx_dns_cache_getinfo(&info); + printf("DNS cache info:\n\n"); + printf("max_size: %8d\n", (int)info.max_size); + printf("capacity: %8d bytes\n", (int)info.max_weight); + printf("hash_size: %8d\n", (int)info.hash_size); + printf("cur_size: %8d\n" + "cur_mem: %8d bytes\n" + "hits: %8d\n" + "lookups: %8d\n" + "inserts: %8d\n" + "deletes: %8d\n", + (int)info.cur_size, (int)info.cur_weight, (int)info.hits, + (int)info.lookups, (int)info.inserts, (int)info.deletes); +} + +extern stx_cache_t *_stx_dns_cache; + +static void printhost(void *host, void *data) +{ + printf("%s\n", (char *)host); +} + +static void show_lru(void) +{ + printf("LRU hosts:\n\n"); + stx_cache_traverse_lru(_stx_dns_cache, printhost, 10); +} + +static void show_mru(void) +{ + printf("MRU hosts:\n\n"); + stx_cache_traverse_mru(_stx_dns_cache, printhost, 10); +} + +static void flush_cache(void) +{ + stx_cache_empty(_stx_dns_cache); + printf("DNS cache is empty\n"); +} + + +int main() +{ + char line[256]; + char str[sizeof(line)]; + + st_init(); + stx_dns_cache_init(100, 10000, 101); + + for ( ; ; ) { + fputs("> ", stdout); + fflush(stdout); + if (!fgets(line, sizeof(line), stdin)) + break; + if (sscanf(line, "%s", str) != 1) + continue; + if (strcmp(str, "exit") == 0 || strcmp(str, "quit") == 0) + break; + if (strcmp(str, "info") == 0) { + show_info(); + continue; + } + if (strcmp(str, "lru") == 0) { + show_lru(); + continue; + } + if (strcmp(str, "mru") == 0) { + show_mru(); + continue; + } + if (strcmp(str, "flush") == 0) { + flush_cache(); + continue; + } + + do_resolve(str); + } + + return 0; +} + diff --git a/trunk/3rdparty/st-srs/io.c b/trunk/3rdparty/st-srs/io.c new file mode 100644 index 000000000..912de0b28 --- /dev/null +++ b/trunk/3rdparty/st-srs/io.c @@ -0,0 +1,769 @@ +/* + * The contents of this file are subject to the Mozilla Public + * License Version 1.1 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS + * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or + * implied. See the License for the specific language governing + * rights and limitations under the License. + * + * The Original Code is the Netscape Portable Runtime library. + * + * The Initial Developer of the Original Code is Netscape + * Communications Corporation. Portions created by Netscape are + * Copyright (C) 1994-2000 Netscape Communications Corporation. All + * Rights Reserved. + * + * Contributor(s): Silicon Graphics, Inc. + * + * Portions created by SGI are Copyright (C) 2000-2001 Silicon + * Graphics, Inc. All Rights Reserved. + * + * Alternatively, the contents of this file may be used under the + * terms of the GNU General Public License Version 2 or later (the + * "GPL"), in which case the provisions of the GPL are applicable + * instead of those above. If you wish to allow use of your + * version of this file only under the terms of the GPL and not to + * allow others to use your version of this file under the MPL, + * indicate your decision by deleting the provisions above and + * replace them with the notice and other provisions required by + * the GPL. If you do not delete the provisions above, a recipient + * may use your version of this file under either the MPL or the + * GPL. + */ + +/* + * This file is derived directly from Netscape Communications Corporation, + * and consists of extensive modifications made during the year(s) 1999-2000. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "common.h" + + +#if EAGAIN != EWOULDBLOCK + #define _IO_NOT_READY_ERROR ((errno == EAGAIN) || (errno == EWOULDBLOCK)) +#else + #define _IO_NOT_READY_ERROR (errno == EAGAIN) +#endif + +#define _LOCAL_MAXIOV 16 + +/* File descriptor object free list */ +static _st_netfd_t *_st_netfd_freelist = NULL; +/* Maximum number of file descriptors that the process can open */ +static int _st_osfd_limit = -1; + +static void _st_netfd_free_aux_data(_st_netfd_t *fd); + +int _st_io_init(void) +{ + struct sigaction sigact; + struct rlimit rlim; + int fdlim; + + /* Ignore SIGPIPE */ + sigact.sa_handler = SIG_IGN; + sigemptyset(&sigact.sa_mask); + sigact.sa_flags = 0; + if (sigaction(SIGPIPE, &sigact, NULL) < 0) + return -1; + + /* Set maximum number of open file descriptors */ + if (getrlimit(RLIMIT_NOFILE, &rlim) < 0) + return -1; + + fdlim = (*_st_eventsys->fd_getlimit)(); + if (fdlim > 0 && rlim.rlim_max > (rlim_t) fdlim) { + rlim.rlim_max = fdlim; + } + + /** + * by SRS, for osx. + * when rlimit max is negative, for example, osx, use cur directly. + * @see https://github.com/winlinvip/simple-rtmp-server/issues/336 + */ + if ((int)rlim.rlim_max < 0) { + _st_osfd_limit = (int)(fdlim > 0? fdlim : rlim.rlim_cur); + return 0; + } + + rlim.rlim_cur = rlim.rlim_max; + if (setrlimit(RLIMIT_NOFILE, &rlim) < 0) + return -1; + _st_osfd_limit = (int) rlim.rlim_max; + + return 0; +} + + +int st_getfdlimit(void) +{ + return _st_osfd_limit; +} + + +void st_netfd_free(_st_netfd_t *fd) +{ + if (!fd->inuse) + return; + + fd->inuse = 0; + if (fd->aux_data) + _st_netfd_free_aux_data(fd); + if (fd->private_data && fd->destructor) + (*(fd->destructor))(fd->private_data); + fd->private_data = NULL; + fd->destructor = NULL; + fd->next = _st_netfd_freelist; + _st_netfd_freelist = fd; +} + + +static _st_netfd_t *_st_netfd_new(int osfd, int nonblock, int is_socket) +{ + _st_netfd_t *fd; + int flags = 1; + + if ((*_st_eventsys->fd_new)(osfd) < 0) + return NULL; + + if (_st_netfd_freelist) { + fd = _st_netfd_freelist; + _st_netfd_freelist = _st_netfd_freelist->next; + } else { + fd = calloc(1, sizeof(_st_netfd_t)); + if (!fd) + return NULL; + } + + fd->osfd = osfd; + fd->inuse = 1; + fd->next = NULL; + + if (nonblock) { + /* Use just one system call */ + if (is_socket && ioctl(osfd, FIONBIO, &flags) != -1) + return fd; + /* Do it the Posix way */ + if ((flags = fcntl(osfd, F_GETFL, 0)) < 0 || + fcntl(osfd, F_SETFL, flags | O_NONBLOCK) < 0) { + st_netfd_free(fd); + return NULL; + } + } + + return fd; +} + + +_st_netfd_t *st_netfd_open(int osfd) +{ + return _st_netfd_new(osfd, 1, 0); +} + + +_st_netfd_t *st_netfd_open_socket(int osfd) +{ + return _st_netfd_new(osfd, 1, 1); +} + + +int st_netfd_close(_st_netfd_t *fd) +{ + if ((*_st_eventsys->fd_close)(fd->osfd) < 0) + return -1; + + st_netfd_free(fd); + return close(fd->osfd); +} + + +int st_netfd_fileno(_st_netfd_t *fd) +{ + return (fd->osfd); +} + + +void st_netfd_setspecific(_st_netfd_t *fd, void *value, _st_destructor_t destructor) +{ + if (value != fd->private_data) { + /* Free up previously set non-NULL data value */ + if (fd->private_data && fd->destructor) + (*(fd->destructor))(fd->private_data); + } + fd->private_data = value; + fd->destructor = destructor; +} + + +void *st_netfd_getspecific(_st_netfd_t *fd) +{ + return (fd->private_data); +} + + +/* + * Wait for I/O on a single descriptor. + */ +int st_netfd_poll(_st_netfd_t *fd, int how, st_utime_t timeout) +{ + struct pollfd pd; + int n; + + pd.fd = fd->osfd; + pd.events = (short) how; + pd.revents = 0; + + if ((n = st_poll(&pd, 1, timeout)) < 0) + return -1; + if (n == 0) { + /* Timed out */ + errno = ETIME; + return -1; + } + if (pd.revents & POLLNVAL) { + errno = EBADF; + return -1; + } + + return 0; +} + + +#ifdef MD_ALWAYS_UNSERIALIZED_ACCEPT +/* No-op */ +int st_netfd_serialize_accept(_st_netfd_t *fd) +{ + fd->aux_data = NULL; + return 0; +} + +/* No-op */ +static void _st_netfd_free_aux_data(_st_netfd_t *fd) +{ + fd->aux_data = NULL; +} + +_st_netfd_t *st_accept(_st_netfd_t *fd, struct sockaddr *addr, int *addrlen, st_utime_t timeout) +{ + int osfd, err; + _st_netfd_t *newfd; + + while ((osfd = accept(fd->osfd, addr, (socklen_t *)addrlen)) < 0) { + if (errno == EINTR) + continue; + if (!_IO_NOT_READY_ERROR) + return NULL; + /* Wait until the socket becomes readable */ + if (st_netfd_poll(fd, POLLIN, timeout) < 0) + return NULL; + } + + /* On some platforms the new socket created by accept() inherits */ + /* the nonblocking attribute of the listening socket */ +#if defined (MD_ACCEPT_NB_INHERITED) + newfd = _st_netfd_new(osfd, 0, 1); +#elif defined (MD_ACCEPT_NB_NOT_INHERITED) + newfd = _st_netfd_new(osfd, 1, 1); +#else + #error Unknown OS +#endif + + if (!newfd) { + err = errno; + close(osfd); + errno = err; + } + + return newfd; +} + +#else /* MD_ALWAYS_UNSERIALIZED_ACCEPT */ +/* + * On some platforms accept() calls from different processes + * on the same listen socket must be serialized. + * The following code serializes accept()'s without process blocking. + * A pipe is used as an inter-process semaphore. + */ +int st_netfd_serialize_accept(_st_netfd_t *fd) +{ + _st_netfd_t **p; + int osfd[2], err; + + if (fd->aux_data) { + errno = EINVAL; + return -1; + } + if ((p = (_st_netfd_t **)calloc(2, sizeof(_st_netfd_t *))) == NULL) + return -1; + if (pipe(osfd) < 0) { + free(p); + return -1; + } + if ((p[0] = st_netfd_open(osfd[0])) != NULL && (p[1] = st_netfd_open(osfd[1])) != NULL && write(osfd[1], " ", 1) == 1) { + fd->aux_data = p; + return 0; + } + /* Error */ + err = errno; + if (p[0]) + st_netfd_free(p[0]); + if (p[1]) + st_netfd_free(p[1]); + close(osfd[0]); + close(osfd[1]); + free(p); + errno = err; + + return -1; +} + +static void _st_netfd_free_aux_data(_st_netfd_t *fd) +{ + _st_netfd_t **p = (_st_netfd_t **) fd->aux_data; + + st_netfd_close(p[0]); + st_netfd_close(p[1]); + free(p); + fd->aux_data = NULL; +} + +_st_netfd_t *st_accept(_st_netfd_t *fd, struct sockaddr *addr, int *addrlen, st_utime_t timeout) +{ + int osfd, err; + _st_netfd_t *newfd; + _st_netfd_t **p = (_st_netfd_t **) fd->aux_data; + ssize_t n; + char c; + + for ( ; ; ) { + if (p == NULL) { + osfd = accept(fd->osfd, addr, (socklen_t *)addrlen); + } else { + /* Get the lock */ + n = st_read(p[0], &c, 1, timeout); + if (n < 0) + return NULL; + ST_ASSERT(n == 1); + /* Got the lock */ + osfd = accept(fd->osfd, addr, (socklen_t *)addrlen); + /* Unlock */ + err = errno; + n = st_write(p[1], &c, 1, timeout); + ST_ASSERT(n == 1); + errno = err; + } + if (osfd >= 0) + break; + if (errno == EINTR) + continue; + if (!_IO_NOT_READY_ERROR) + return NULL; + /* Wait until the socket becomes readable */ + if (st_netfd_poll(fd, POLLIN, timeout) < 0) + return NULL; + } + + /* On some platforms the new socket created by accept() inherits */ + /* the nonblocking attribute of the listening socket */ +#if defined (MD_ACCEPT_NB_INHERITED) + newfd = _st_netfd_new(osfd, 0, 1); +#elif defined (MD_ACCEPT_NB_NOT_INHERITED) + newfd = _st_netfd_new(osfd, 1, 1); +#else +#error Unknown OS +#endif + + if (!newfd) { + err = errno; + close(osfd); + errno = err; + } + + return newfd; +} +#endif /* MD_ALWAYS_UNSERIALIZED_ACCEPT */ + + +int st_connect(_st_netfd_t *fd, const struct sockaddr *addr, int addrlen, st_utime_t timeout) +{ + int n, err = 0; + + while (connect(fd->osfd, addr, addrlen) < 0) { + if (errno != EINTR) { + /* + * On some platforms, if connect() is interrupted (errno == EINTR) + * after the kernel binds the socket, a subsequent connect() + * attempt will fail with errno == EADDRINUSE. Ignore EADDRINUSE + * iff connect() was previously interrupted. See Rich Stevens' + * "UNIX Network Programming," Vol. 1, 2nd edition, p. 413 + * ("Interrupted connect"). + */ + if (errno != EINPROGRESS && (errno != EADDRINUSE || err == 0)) + return -1; + /* Wait until the socket becomes writable */ + if (st_netfd_poll(fd, POLLOUT, timeout) < 0) + return -1; + /* Try to find out whether the connection setup succeeded or failed */ + n = sizeof(int); + if (getsockopt(fd->osfd, SOL_SOCKET, SO_ERROR, (char *)&err, (socklen_t *)&n) < 0) + return -1; + if (err) { + errno = err; + return -1; + } + break; + } + err = 1; + } + + return 0; +} + + +ssize_t st_read(_st_netfd_t *fd, void *buf, size_t nbyte, st_utime_t timeout) +{ + ssize_t n; + + while ((n = read(fd->osfd, buf, nbyte)) < 0) { + if (errno == EINTR) + continue; + if (!_IO_NOT_READY_ERROR) + return -1; + /* Wait until the socket becomes readable */ + if (st_netfd_poll(fd, POLLIN, timeout) < 0) + return -1; + } + + return n; +} + + +int st_read_resid(_st_netfd_t *fd, void *buf, size_t *resid, st_utime_t timeout) +{ + struct iovec iov, *riov; + int riov_size, rv; + + iov.iov_base = buf; + iov.iov_len = *resid; + riov = &iov; + riov_size = 1; + rv = st_readv_resid(fd, &riov, &riov_size, timeout); + *resid = iov.iov_len; + return rv; +} + + +ssize_t st_readv(_st_netfd_t *fd, const struct iovec *iov, int iov_size, st_utime_t timeout) +{ + ssize_t n; + + while ((n = readv(fd->osfd, iov, iov_size)) < 0) { + if (errno == EINTR) + continue; + if (!_IO_NOT_READY_ERROR) + return -1; + /* Wait until the socket becomes readable */ + if (st_netfd_poll(fd, POLLIN, timeout) < 0) + return -1; + } + + return n; +} + +int st_readv_resid(_st_netfd_t *fd, struct iovec **iov, int *iov_size, st_utime_t timeout) +{ + ssize_t n; + + while (*iov_size > 0) { + if (*iov_size == 1) + n = read(fd->osfd, (*iov)->iov_base, (*iov)->iov_len); + else + n = readv(fd->osfd, *iov, *iov_size); + if (n < 0) { + if (errno == EINTR) + continue; + if (!_IO_NOT_READY_ERROR) + return -1; + } else if (n == 0) + break; + else { + while ((size_t) n >= (*iov)->iov_len) { + n -= (*iov)->iov_len; + (*iov)->iov_base = (char *) (*iov)->iov_base + (*iov)->iov_len; + (*iov)->iov_len = 0; + (*iov)++; + (*iov_size)--; + if (n == 0) + break; + } + if (*iov_size == 0) + break; + (*iov)->iov_base = (char *) (*iov)->iov_base + n; + (*iov)->iov_len -= n; + } + /* Wait until the socket becomes readable */ + if (st_netfd_poll(fd, POLLIN, timeout) < 0) + return -1; + } + + return 0; +} + + +ssize_t st_read_fully(_st_netfd_t *fd, void *buf, size_t nbyte, st_utime_t timeout) +{ + size_t resid = nbyte; + return st_read_resid(fd, buf, &resid, timeout) == 0 ? + (ssize_t) (nbyte - resid) : -1; +} + + +int st_write_resid(_st_netfd_t *fd, const void *buf, size_t *resid, st_utime_t timeout) +{ + struct iovec iov, *riov; + int riov_size, rv; + + iov.iov_base = (void *) buf; /* we promise not to modify buf */ + iov.iov_len = *resid; + riov = &iov; + riov_size = 1; + rv = st_writev_resid(fd, &riov, &riov_size, timeout); + *resid = iov.iov_len; + return rv; +} + + +ssize_t st_write(_st_netfd_t *fd, const void *buf, size_t nbyte, st_utime_t timeout) +{ + size_t resid = nbyte; + return st_write_resid(fd, buf, &resid, timeout) == 0 ? + (ssize_t) (nbyte - resid) : -1; +} + + +ssize_t st_writev(_st_netfd_t *fd, const struct iovec *iov, int iov_size, st_utime_t timeout) +{ + ssize_t n, rv; + size_t nleft, nbyte; + int index, iov_cnt; + struct iovec *tmp_iov; + struct iovec local_iov[_LOCAL_MAXIOV]; + + /* Calculate the total number of bytes to be sent */ + nbyte = 0; + for (index = 0; index < iov_size; index++) + nbyte += iov[index].iov_len; + + rv = (ssize_t)nbyte; + nleft = nbyte; + tmp_iov = (struct iovec *) iov; /* we promise not to modify iov */ + iov_cnt = iov_size; + + while (nleft > 0) { + if (iov_cnt == 1) { + if (st_write(fd, tmp_iov[0].iov_base, nleft, timeout) != (ssize_t) nleft) + rv = -1; + break; + } + if ((n = writev(fd->osfd, tmp_iov, iov_cnt)) < 0) { + if (errno == EINTR) + continue; + if (!_IO_NOT_READY_ERROR) { + rv = -1; + break; + } + } else { + if ((size_t) n == nleft) + break; + nleft -= n; + /* Find the next unwritten vector */ + n = (ssize_t)(nbyte - nleft); + for (index = 0; (size_t) n >= iov[index].iov_len; index++) + n -= iov[index].iov_len; + + if (tmp_iov == iov) { + /* Must copy iov's around */ + if (iov_size - index <= _LOCAL_MAXIOV) { + tmp_iov = local_iov; + } else { + tmp_iov = calloc(1, (iov_size - index) * sizeof(struct iovec)); + if (tmp_iov == NULL) + return -1; + } + } + + /* Fill in the first partial read */ + tmp_iov[0].iov_base = &(((char *)iov[index].iov_base)[n]); + tmp_iov[0].iov_len = iov[index].iov_len - n; + index++; + /* Copy the remaining vectors */ + for (iov_cnt = 1; index < iov_size; iov_cnt++, index++) { + tmp_iov[iov_cnt].iov_base = iov[index].iov_base; + tmp_iov[iov_cnt].iov_len = iov[index].iov_len; + } + } + /* Wait until the socket becomes writable */ + if (st_netfd_poll(fd, POLLOUT, timeout) < 0) { + rv = -1; + break; + } + } + + if (tmp_iov != iov && tmp_iov != local_iov) + free(tmp_iov); + + return rv; +} + + +int st_writev_resid(_st_netfd_t *fd, struct iovec **iov, int *iov_size, st_utime_t timeout) +{ + ssize_t n; + + while (*iov_size > 0) { + if (*iov_size == 1) + n = write(fd->osfd, (*iov)->iov_base, (*iov)->iov_len); + else + n = writev(fd->osfd, *iov, *iov_size); + if (n < 0) { + if (errno == EINTR) + continue; + if (!_IO_NOT_READY_ERROR) + return -1; + } else { + while ((size_t) n >= (*iov)->iov_len) { + n -= (*iov)->iov_len; + (*iov)->iov_base = (char *) (*iov)->iov_base + (*iov)->iov_len; + (*iov)->iov_len = 0; + (*iov)++; + (*iov_size)--; + if (n == 0) + break; + } + if (*iov_size == 0) + break; + (*iov)->iov_base = (char *) (*iov)->iov_base + n; + (*iov)->iov_len -= n; + } + /* Wait until the socket becomes writable */ + if (st_netfd_poll(fd, POLLOUT, timeout) < 0) + return -1; + } + + return 0; +} + + +/* + * Simple I/O functions for UDP. + */ +int st_recvfrom(_st_netfd_t *fd, void *buf, int len, struct sockaddr *from, int *fromlen, st_utime_t timeout) +{ + int n; + + while ((n = recvfrom(fd->osfd, buf, len, 0, from, (socklen_t *)fromlen)) < 0) { + if (errno == EINTR) + continue; + if (!_IO_NOT_READY_ERROR) + return -1; + /* Wait until the socket becomes readable */ + if (st_netfd_poll(fd, POLLIN, timeout) < 0) + return -1; + } + + return n; +} + + +int st_sendto(_st_netfd_t *fd, const void *msg, int len, const struct sockaddr *to, int tolen, st_utime_t timeout) +{ + int n; + + while ((n = sendto(fd->osfd, msg, len, 0, to, tolen)) < 0) { + if (errno == EINTR) + continue; + if (!_IO_NOT_READY_ERROR) + return -1; + /* Wait until the socket becomes writable */ + if (st_netfd_poll(fd, POLLOUT, timeout) < 0) + return -1; + } + + return n; +} + + +int st_recvmsg(_st_netfd_t *fd, struct msghdr *msg, int flags, st_utime_t timeout) +{ + int n; + + while ((n = recvmsg(fd->osfd, msg, flags)) < 0) { + if (errno == EINTR) + continue; + if (!_IO_NOT_READY_ERROR) + return -1; + /* Wait until the socket becomes readable */ + if (st_netfd_poll(fd, POLLIN, timeout) < 0) + return -1; + } + + return n; +} + + +int st_sendmsg(_st_netfd_t *fd, const struct msghdr *msg, int flags, st_utime_t timeout) +{ + int n; + + while ((n = sendmsg(fd->osfd, msg, flags)) < 0) { + if (errno == EINTR) + continue; + if (!_IO_NOT_READY_ERROR) + return -1; + /* Wait until the socket becomes writable */ + if (st_netfd_poll(fd, POLLOUT, timeout) < 0) + return -1; + } + + return n; +} + + + +/* + * To open FIFOs or other special files. + */ +_st_netfd_t *st_open(const char *path, int oflags, mode_t mode) +{ + int osfd, err; + _st_netfd_t *newfd; + + while ((osfd = open(path, oflags | O_NONBLOCK, mode)) < 0) { + if (errno != EINTR) + return NULL; + } + + newfd = _st_netfd_new(osfd, 0, 0); + if (!newfd) { + err = errno; + close(osfd); + errno = err; + } + + return newfd; +} + diff --git a/trunk/3rdparty/st-srs/key.c b/trunk/3rdparty/st-srs/key.c new file mode 100644 index 000000000..5e64022c0 --- /dev/null +++ b/trunk/3rdparty/st-srs/key.c @@ -0,0 +1,121 @@ +/* + * The contents of this file are subject to the Mozilla Public + * License Version 1.1 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS + * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or + * implied. See the License for the specific language governing + * rights and limitations under the License. + * + * The Original Code is the Netscape Portable Runtime library. + * + * The Initial Developer of the Original Code is Netscape + * Communications Corporation. Portions created by Netscape are + * Copyright (C) 1994-2000 Netscape Communications Corporation. All + * Rights Reserved. + * + * Contributor(s): Silicon Graphics, Inc. + * + * Portions created by SGI are Copyright (C) 2000-2001 Silicon + * Graphics, Inc. All Rights Reserved. + * + * Alternatively, the contents of this file may be used under the + * terms of the GNU General Public License Version 2 or later (the + * "GPL"), in which case the provisions of the GPL are applicable + * instead of those above. If you wish to allow use of your + * version of this file only under the terms of the GPL and not to + * allow others to use your version of this file under the MPL, + * indicate your decision by deleting the provisions above and + * replace them with the notice and other provisions required by + * the GPL. If you do not delete the provisions above, a recipient + * may use your version of this file under either the MPL or the + * GPL. + */ + +/* + * This file is derived directly from Netscape Communications Corporation, + * and consists of extensive modifications made during the year(s) 1999-2000. + */ + +#include +#include +#include "common.h" + + +/* + * Destructor table for per-thread private data + */ +static _st_destructor_t _st_destructors[ST_KEYS_MAX]; +static int key_max = 0; + + +/* + * Return a key to be used for thread specific data + */ +int st_key_create(int *keyp, _st_destructor_t destructor) +{ + if (key_max >= ST_KEYS_MAX) { + errno = EAGAIN; + return -1; + } + + *keyp = key_max++; + _st_destructors[*keyp] = destructor; + + return 0; +} + + +int st_key_getlimit(void) +{ + return ST_KEYS_MAX; +} + + +int st_thread_setspecific(int key, void *value) +{ + _st_thread_t *me = _ST_CURRENT_THREAD(); + + if (key < 0 || key >= key_max) { + errno = EINVAL; + return -1; + } + + if (value != me->private_data[key]) { + /* free up previously set non-NULL data value */ + if (me->private_data[key] && _st_destructors[key]) { + (*_st_destructors[key])(me->private_data[key]); + } + me->private_data[key] = value; + } + + return 0; +} + + +void *st_thread_getspecific(int key) +{ + if (key < 0 || key >= key_max) + return NULL; + + return ((_ST_CURRENT_THREAD())->private_data[key]); +} + + +/* + * Free up all per-thread private data + */ +void _st_thread_cleanup(_st_thread_t *thread) +{ + int key; + + for (key = 0; key < key_max; key++) { + if (thread->private_data[key] && _st_destructors[key]) { + (*_st_destructors[key])(thread->private_data[key]); + thread->private_data[key] = NULL; + } + } +} + diff --git a/trunk/3rdparty/st-srs/libst.def b/trunk/3rdparty/st-srs/libst.def new file mode 100644 index 000000000..6eaf149a9 --- /dev/null +++ b/trunk/3rdparty/st-srs/libst.def @@ -0,0 +1,51 @@ +EXPORTS + st_accept @62 + st_cond_broadcast @63 + st_cond_destroy @64 + st_cond_new @65 + st_cond_signal @66 + st_cond_timedwait @67 + st_cond_wait @68 + st_connect @69 + st_getfdlimit @70 + st_init @71 + st_key_create @72 + st_key_getlimit @73 + st_mutex_destroy @74 + st_mutex_lock @75 + st_mutex_new @76 + st_mutex_trylock @77 + st_mutex_unlock @78 + st_netfd_close @79 + st_netfd_fileno @80 + st_netfd_free @81 + st_netfd_getspecific @82 + st_netfd_open @83 + st_netfd_open_socket @84 + st_netfd_poll @85 + st_netfd_serialize_accept @86 + st_netfd_setspecific @87 + st_open @88 + st_poll @89 + st_randomize_stacks @90 + st_read @91 + st_read_fully @92 + st_read_resid @93 + st_recvfrom @94 + st_sendto @95 + st_sleep @96 + st_thread_create @97 + st_thread_exit @98 + st_thread_getspecific @99 + st_thread_interrupt @100 + st_thread_join @101 + st_thread_self @102 + st_thread_setspecific @103 + st_time @104 + st_timecache_set @105 + st_usleep @106 + st_utime @107 + st_utime_last_clock @108 + st_write @109 + st_write_resid @110 + st_writev @111 diff --git a/trunk/3rdparty/st-srs/md.S b/trunk/3rdparty/st-srs/md.S new file mode 100644 index 000000000..2ef9c41f7 --- /dev/null +++ b/trunk/3rdparty/st-srs/md.S @@ -0,0 +1,644 @@ + +/* If user disable the ASM, such as avoiding bugs in ASM, donot compile it. */ +#if !defined(MD_ST_NO_ASM) + +/* + * Portions created by SGI are Copyright (C) 2000 Silicon Graphics, Inc. + * All Rights Reserved. + */ + +#if defined(__ia64__) + + /****************************************************************/ + + /* + * The internal __jmp_buf layout is different from one used + * by setjmp()/longjmp(). + * + * Offset Description + * ------ ----------- + * 0x000 stack pointer (r12) + * 0x008 gp (r1) + * 0x010 caller's unat + * 0x018 fpsr + * 0x020 r4 + * 0x028 r5 + * 0x030 r6 + * 0x038 r7 + * 0x040 rp (b0) + * 0x048 b1 + * 0x050 b2 + * 0x058 b3 + * 0x060 b4 + * 0x068 b5 + * 0x070 ar.pfs + * 0x078 ar.lc + * 0x080 pr + * 0x088 ar.bsp + * 0x090 ar.unat + * 0x098 &__jmp_buf + * 0x0a0 ar.rsc + * 0x0a8 ar.rnat + * 0x0b0 f2 + * 0x0c0 f3 + * 0x0d0 f4 + * 0x0e0 f5 + * 0x0f0 f16 + * 0x100 f17 + * 0x110 f18 + * 0x120 f19 + * 0x130 f20 + * 0x130 f21 + * 0x140 f22 + * 0x150 f23 + * 0x160 f24 + * 0x170 f25 + * 0x180 f26 + * 0x190 f27 + * 0x1a0 f28 + * 0x1b0 f29 + * 0x1c0 f30 + * 0x1d0 f31 + * + * Note that the address of __jmp_buf is saved but not used: we assume + * that the jmp_buf data structure is never moved around in memory. + */ + + /* + * Implemented according to "IA-64 Software Conventions and Runtime + * Architecture Guide", Chapter 10: "Context Management". + */ + + .text + .psr abi64 + .psr lsb + .lsb + + /* _st_md_cxt_save(__jmp_buf env) */ + .align 32 + .global _st_md_cxt_save + .proc _st_md_cxt_save + _st_md_cxt_save: + alloc r14 = ar.pfs,1,0,0,0 + mov r16 = ar.unat + ;; + mov r17 = ar.fpsr + mov r2 = in0 + add r3 = 8,in0 + ;; + st8.spill.nta [r2] = sp,16 // r12 (sp) + ;; + st8.spill.nta [r3] = gp,16 // r1 (gp) + ;; + st8.nta [r2] = r16,16 // save caller's unat + st8.nta [r3] = r17,16 // save fpsr + add r8 = 0xb0,in0 + ;; + st8.spill.nta [r2] = r4,16 // r4 + ;; + st8.spill.nta [r3] = r5,16 // r5 + add r9 = 0xc0,in0 + ;; + stf.spill.nta [r8] = f2,32 + stf.spill.nta [r9] = f3,32 + mov r15 = rp + ;; + stf.spill.nta [r8] = f4,32 + stf.spill.nta [r9] = f5,32 + mov r17 = b1 + ;; + stf.spill.nta [r8] = f16,32 + stf.spill.nta [r9] = f17,32 + mov r18 = b2 + ;; + stf.spill.nta [r8] = f18,32 + stf.spill.nta [r9] = f19,32 + mov r19 = b3 + ;; + stf.spill.nta [r8] = f20,32 + stf.spill.nta [r9] = f21,32 + mov r20 = b4 + ;; + stf.spill.nta [r8] = f22,32 + stf.spill.nta [r9] = f23,32 + mov r21 = b5 + ;; + stf.spill.nta [r8] = f24,32 + stf.spill.nta [r9] = f25,32 + mov r22 = ar.lc + ;; + stf.spill.nta [r8] = f26,32 + stf.spill.nta [r9] = f27,32 + mov r24 = pr + ;; + stf.spill.nta [r8] = f28,32 + stf.spill.nta [r9] = f29,32 + ;; + stf.spill.nta [r8] = f30 + stf.spill.nta [r9] = f31 + + st8.spill.nta [r2] = r6,16 // r6 + ;; + st8.spill.nta [r3] = r7,16 // r7 + ;; + mov r23 = ar.bsp + mov r25 = ar.unat + + st8.nta [r2] = r15,16 // b0 + st8.nta [r3] = r17,16 // b1 + ;; + st8.nta [r2] = r18,16 // b2 + st8.nta [r3] = r19,16 // b3 + mov r26 = ar.rsc + ;; + st8.nta [r2] = r20,16 // b4 + st8.nta [r3] = r21,16 // b5 + ;; + st8.nta [r2] = r14,16 // ar.pfs + st8.nta [r3] = r22,16 // ar.lc + ;; + st8.nta [r2] = r24,16 // pr + st8.nta [r3] = r23,16 // ar.bsp + ;; + st8.nta [r2] = r25,16 // ar.unat + st8.nta [r3] = in0,16 // &__jmp_buf (just in case) + ;; + st8.nta [r2] = r26 // ar.rsc + ;; + flushrs // flush dirty regs to backing store + ;; + and r27 = ~0x3,r26 // clear ar.rsc.mode + ;; + mov ar.rsc = r27 // put RSE in enforced lazy mode + ;; + mov r28 = ar.rnat + ;; + st8.nta [r3] = r28 // ar.rnat + mov ar.rsc = r26 // restore ar.rsc + ;; + mov r8 = 0 + br.ret.sptk.few b0 + .endp _st_md_cxt_save + + + /****************************************************************/ + + /* _st_md_cxt_restore(__jmp_buf env, int val) */ + .global _st_md_cxt_restore + .proc _st_md_cxt_restore + _st_md_cxt_restore: + alloc r8 = ar.pfs,2,0,0,0 + add r2 = 0x88,in0 // r2 <- &jmpbuf.ar_bsp + mov r16 = ar.rsc + ;; + flushrs // flush dirty regs to backing store + ;; + and r17 = ~0x3,r16 // clear ar.rsc.mode + ;; + mov ar.rsc = r17 // put RSE in enforced lazy mode + ;; + invala // invalidate the ALAT + ;; + ld8 r23 = [r2],8 // r23 <- jmpbuf.ar_bsp + ;; + mov ar.bspstore = r23 // write BSPSTORE + ld8 r25 = [r2],24 // r25 <- jmpbuf.ar_unat + ;; + ld8 r26 = [r2],-8 // r26 <- jmpbuf.ar_rnat + ;; + mov ar.rnat = r26 // write RNAT + ld8 r27 = [r2] // r27 <- jmpbuf.ar_rsc + ;; + mov ar.rsc = r27 // write RSE control + mov r2 = in0 + ;; + mov ar.unat = r25 // write ar.unat + add r3 = 8,in0 + ;; + ld8.fill.nta sp = [r2],16 // r12 (sp) + ld8.fill.nta gp = [r3],16 // r1 (gp) + ;; + ld8.nta r16 = [r2],16 // caller's unat + ld8.nta r17 = [r3],16 // fpsr + ;; + ld8.fill.nta r4 = [r2],16 // r4 + ld8.fill.nta r5 = [r3],16 // r5 + ;; + ld8.fill.nta r6 = [r2],16 // r6 + ld8.fill.nta r7 = [r3],16 // r7 + ;; + mov ar.unat = r16 // restore caller's unat + mov ar.fpsr = r17 // restore fpsr + ;; + ld8.nta r16 = [r2],16 // b0 + ld8.nta r17 = [r3],16 // b1 + ;; + ld8.nta r18 = [r2],16 // b2 + ld8.nta r19 = [r3],16 // b3 + ;; + ld8.nta r20 = [r2],16 // b4 + ld8.nta r21 = [r3],16 // b5 + ;; + ld8.nta r11 = [r2],16 // ar.pfs + ld8.nta r22 = [r3],72 // ar.lc + ;; + ld8.nta r24 = [r2],48 // pr + mov b0 = r16 + ;; + ldf.fill.nta f2 = [r2],32 + ldf.fill.nta f3 = [r3],32 + mov b1 = r17 + ;; + ldf.fill.nta f4 = [r2],32 + ldf.fill.nta f5 = [r3],32 + mov b2 = r18 + ;; + ldf.fill.nta f16 = [r2],32 + ldf.fill.nta f17 = [r3],32 + mov b3 = r19 + ;; + ldf.fill.nta f18 = [r2],32 + ldf.fill.nta f19 = [r3],32 + mov b4 = r20 + ;; + ldf.fill.nta f20 = [r2],32 + ldf.fill.nta f21 = [r3],32 + mov b5 = r21 + ;; + ldf.fill.nta f22 = [r2],32 + ldf.fill.nta f23 = [r3],32 + mov ar.lc = r22 + ;; + ldf.fill.nta f24 = [r2],32 + ldf.fill.nta f25 = [r3],32 + cmp.eq p6,p7 = 0,in1 + ;; + ldf.fill.nta f26 = [r2],32 + ldf.fill.nta f27 = [r3],32 + mov ar.pfs = r11 + ;; + ldf.fill.nta f28 = [r2],32 + ldf.fill.nta f29 = [r3],32 + ;; + ldf.fill.nta f30 = [r2] + ldf.fill.nta f31 = [r3] + (p6) mov r8 = 1 + (p7) mov r8 = in1 + + mov pr = r24,-1 + br.ret.sptk.few b0 + .endp _st_md_cxt_restore + + /****************************************************************/ + + + + + + + + + +#elif defined(__i386__) + + /****************************************************************/ + + /* + * Internal __jmp_buf layout + */ + #define JB_BX 0 + #define JB_SI 1 + #define JB_DI 2 + #define JB_BP 3 + #define JB_SP 4 + #define JB_PC 5 + + .file "md.S" + .text + + /* _st_md_cxt_save(__jmp_buf env) */ + .globl _st_md_cxt_save + .type _st_md_cxt_save, @function + .align 16 + _st_md_cxt_save: + movl 4(%esp), %eax + + /* + * Save registers. + */ + movl %ebx, (JB_BX*4)(%eax) + movl %esi, (JB_SI*4)(%eax) + movl %edi, (JB_DI*4)(%eax) + /* Save SP */ + leal 4(%esp), %ecx + movl %ecx, (JB_SP*4)(%eax) + /* Save PC we are returning to */ + movl 0(%esp), %ecx + movl %ecx, (JB_PC*4)(%eax) + /* Save caller frame pointer */ + movl %ebp, (JB_BP*4)(%eax) + xorl %eax, %eax + ret + .size _st_md_cxt_save, .-_st_md_cxt_save + + + /****************************************************************/ + + /* _st_md_cxt_restore(__jmp_buf env, int val) */ + .globl _st_md_cxt_restore + .type _st_md_cxt_restore, @function + .align 16 + _st_md_cxt_restore: + /* First argument is jmp_buf */ + movl 4(%esp), %ecx + /* Second argument is return value */ + movl 8(%esp), %eax + /* Set the return address */ + movl (JB_PC*4)(%ecx), %edx + /* + * Restore registers. + */ + movl (JB_BX*4)(%ecx), %ebx + movl (JB_SI*4)(%ecx), %esi + movl (JB_DI*4)(%ecx), %edi + movl (JB_BP*4)(%ecx), %ebp + movl (JB_SP*4)(%ecx), %esp + testl %eax, %eax + jnz 1f + incl %eax + /* Jump to saved PC */ + 1: jmp *%edx + .size _st_md_cxt_restore, .-_st_md_cxt_restore + + /****************************************************************/ + + + + + + + + + + +#elif defined(__amd64__) || defined(__x86_64__) + + /****************************************************************/ + + /* + * Internal __jmp_buf layout + */ + #define JB_RBX 0 + #define JB_RBP 1 + #define JB_R12 2 + #define JB_R13 3 + #define JB_R14 4 + #define JB_R15 5 + #define JB_RSP 6 + #define JB_PC 7 + + .file "md.S" + .text + + /* _st_md_cxt_save(__jmp_buf env) */ + .globl _st_md_cxt_save + .type _st_md_cxt_save, @function + .align 16 + _st_md_cxt_save: + /* + * Save registers. + */ + movq %rbx, (JB_RBX*8)(%rdi) + movq %rbp, (JB_RBP*8)(%rdi) + movq %r12, (JB_R12*8)(%rdi) + movq %r13, (JB_R13*8)(%rdi) + movq %r14, (JB_R14*8)(%rdi) + movq %r15, (JB_R15*8)(%rdi) + /* Save SP */ + leaq 8(%rsp), %rdx + movq %rdx, (JB_RSP*8)(%rdi) + /* Save PC we are returning to */ + movq (%rsp), %rax + movq %rax, (JB_PC*8)(%rdi) + xorq %rax, %rax + ret + .size _st_md_cxt_save, .-_st_md_cxt_save + + + /****************************************************************/ + + /* _st_md_cxt_restore(__jmp_buf env, int val) */ + .globl _st_md_cxt_restore + .type _st_md_cxt_restore, @function + .align 16 + _st_md_cxt_restore: + /* + * Restore registers. + */ + movq (JB_RBX*8)(%rdi), %rbx + movq (JB_RBP*8)(%rdi), %rbp + movq (JB_R12*8)(%rdi), %r12 + movq (JB_R13*8)(%rdi), %r13 + movq (JB_R14*8)(%rdi), %r14 + movq (JB_R15*8)(%rdi), %r15 + /* Set return value */ + test %esi, %esi + mov $01, %eax + cmove %eax, %esi + mov %esi, %eax + movq (JB_PC*8)(%rdi), %rdx + movq (JB_RSP*8)(%rdi), %rsp + /* Jump to saved PC */ + jmpq *%rdx + .size _st_md_cxt_restore, .-_st_md_cxt_restore + + /****************************************************************/ + + + + + + + + + + +#elif defined(__aarch64__) + + /****************************************************************/ + /* https://github.com/ossrs/srs/issues/1282#issuecomment-445539513 */ + + #define JB_X19 0 + #define JB_X20 1 + #define JB_X21 2 + #define JB_X22 3 + #define JB_X23 4 + #define JB_X24 5 + #define JB_X25 6 + #define JB_X26 7 + #define JB_X27 8 + #define JB_X28 9 + #define JB_X29 10 + #define JB_LR 11 + #define JB_SP 13 + + #define JB_D8 14 + #define JB_D9 15 + #define JB_D10 16 + #define JB_D11 17 + #define JB_D12 18 + #define JB_D13 19 + #define JB_D14 20 + #define JB_D15 21 + + .file "md.S" + .text + + /* _st_md_cxt_save(__jmp_buf env) */ + .globl _st_md_cxt_save + .type _st_md_cxt_save, %function + .align 4 + _st_md_cxt_save: + stp x19, x20, [x0, #JB_X19<<3] + stp x21, x22, [x0, #JB_X21<<3] + stp x23, x24, [x0, #JB_X23<<3] + stp x25, x26, [x0, #JB_X25<<3] + stp x27, x28, [x0, #JB_X27<<3] + stp x29, x30, [x0, #JB_X29<<3] + + stp d8, d9, [x0, #JB_D8<<3] + stp d10, d11, [x0, #JB_D10<<3] + stp d12, d13, [x0, #JB_D12<<3] + stp d14, d15, [x0, #JB_D14<<3] + mov x2, sp + str x2, [x0, #JB_SP<<3] + + mov x0, #0 + ret + .size _st_md_cxt_save, .-_st_md_cxt_save + + /****************************************************************/ + + /* _st_md_cxt_restore(__jmp_buf env, int val) */ + .globl _st_md_cxt_restore + .type _st_md_cxt_restore, %function + .align 4 + _st_md_cxt_restore: + ldp x19, x20, [x0, #JB_X19<<3] + ldp x21, x22, [x0, #JB_X21<<3] + ldp x23, x24, [x0, #JB_X23<<3] + ldp x25, x26, [x0, #JB_X25<<3] + ldp x27, x28, [x0, #JB_X27<<3] + + ldp x29, x30, [x0, #JB_X29<<3] + + ldp d8, d9, [x0, #JB_D8<<3] + ldp d10, d11, [x0, #JB_D10<<3] + ldp d12, d13, [x0, #JB_D12<<3] + ldp d14, d15, [x0, #JB_D14<<3] + + ldr x5, [x0, #JB_SP<<3] + mov sp, x5 + + cmp x1, #0 + mov x0, #1 + csel x0, x1, x0, ne + /* Use br instead of ret because ret is guaranteed to mispredict */ + br x30 + .size _st_md_cxt_restore, .-_st_md_cxt_restore + + /****************************************************************/ + + + + + + + + + + +#elif defined(__arm__) + + /****************************************************************/ + /* https://github.com/ossrs/srs/issues/1282#issuecomment-445539513 */ + + /* Register list for a ldm/stm instruction to load/store + the general registers from a __jmp_buf. */ + # define JMP_BUF_REGLIST {v1-v6, sl, fp, sp, lr} + + .file "md.S" + .text + + /* _st_md_cxt_save(__jmp_buf env) */ + .globl _st_md_cxt_save + .type _st_md_cxt_save, %function + .align 2 + _st_md_cxt_save: + mov ip, r0 + + /* Save registers */ + stmia ip!, JMP_BUF_REGLIST + + #ifdef __VFP_FP__ + /* Store the VFP registers. */ + /* Following instruction is vstmia ip!, {d8-d15}. */ + stc p11, cr8, [ip], #64 + #endif + + #ifdef __IWMMXT__ + /* Save the call-preserved iWMMXt registers. */ + /* Following instructions are wstrd wr10, [ip], #8 (etc.) */ + stcl p1, cr10, [r12], #8 + stcl p1, cr11, [r12], #8 + stcl p1, cr12, [r12], #8 + stcl p1, cr13, [r12], #8 + stcl p1, cr14, [r12], #8 + stcl p1, cr15, [r12], #8 + #endif + + mov r0, #0 + bx lr + + .size _st_md_cxt_save, .-_st_md_cxt_save + + /****************************************************************/ + + /* _st_md_cxt_restore(__jmp_buf env, int val) */ + .globl _st_md_cxt_restore + .type _st_md_cxt_restore, %function + .align 2 + _st_md_cxt_restore: + mov ip, r0 + + /* Restore registers */ + ldmia ip!, JMP_BUF_REGLIST + + #ifdef __VFP_FP__ + /* Restore the VFP registers. */ + /* Following instruction is vldmia ip!, {d8-d15}. */ + ldc p11, cr8, [r12], #64 + #endif + + #ifdef __IWMMXT__ + /* Restore the call-preserved iWMMXt registers. */ + /* Following instructions are wldrd wr10, [ip], #8 (etc.) */ + ldcl p1, cr10, [r12], #8 + ldcl p1, cr11, [r12], #8 + ldcl p1, cr12, [r12], #8 + ldcl p1, cr13, [r12], #8 + ldcl p1, cr14, [r12], #8 + ldcl p1, cr15, [r12], #8 + #endif + + movs r0, r1 /* get the return value in place */ + moveq r0, #1 /* can't let setjmp() return zero! */ + bx lr + + .size _st_md_cxt_restore, .-_st_md_cxt_restore + + /****************************************************************/ + +#endif + +#endif diff --git a/trunk/3rdparty/st-srs/md.h b/trunk/3rdparty/st-srs/md.h new file mode 100644 index 000000000..8c0a222d6 --- /dev/null +++ b/trunk/3rdparty/st-srs/md.h @@ -0,0 +1,645 @@ +/* + * The contents of this file are subject to the Mozilla Public + * License Version 1.1 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS + * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or + * implied. See the License for the specific language governing + * rights and limitations under the License. + * + * The Original Code is the Netscape Portable Runtime library. + * + * The Initial Developer of the Original Code is Netscape + * Communications Corporation. Portions created by Netscape are + * Copyright (C) 1994-2000 Netscape Communications Corporation. All + * Rights Reserved. + * + * Contributor(s): Silicon Graphics, Inc. + * + * Portions created by SGI are Copyright (C) 2000-2001 Silicon + * Graphics, Inc. All Rights Reserved. + * + * Alternatively, the contents of this file may be used under the + * terms of the GNU General Public License Version 2 or later (the + * "GPL"), in which case the provisions of the GPL are applicable + * instead of those above. If you wish to allow use of your + * version of this file only under the terms of the GPL and not to + * allow others to use your version of this file under the MPL, + * indicate your decision by deleting the provisions above and + * replace them with the notice and other provisions required by + * the GPL. If you do not delete the provisions above, a recipient + * may use your version of this file under either the MPL or the + * GPL. + */ + +/* + * This file is derived directly from Netscape Communications Corporation, + * and consists of extensive modifications made during the year(s) 1999-2000. + */ + +#ifndef __ST_MD_H__ +#define __ST_MD_H__ + +#if defined(ETIMEDOUT) && !defined(ETIME) + #define ETIME ETIMEDOUT +#endif + +#if defined(MAP_ANONYMOUS) && !defined(MAP_ANON) + #define MAP_ANON MAP_ANONYMOUS +#endif + +#ifndef MAP_FAILED + #define MAP_FAILED -1 +#endif + +/***************************************** + * Platform specifics + */ + +#if defined (AIX) + + #define MD_STACK_GROWS_DOWN + #define MD_USE_SYSV_ANON_MMAP + #define MD_ACCEPT_NB_INHERITED + #define MD_ALWAYS_UNSERIALIZED_ACCEPT + + #ifndef MD_HAVE_SOCKLEN_T + #define MD_HAVE_SOCKLEN_T + #define socklen_t unsigned long + #endif + + #define MD_SETJMP(env) _setjmp(env) + #define MD_LONGJMP(env, val) _longjmp(env, val) + + #define MD_INIT_CONTEXT(_thread, _sp, _main) \ + ST_BEGIN_MACRO \ + if (MD_SETJMP((_thread)->context)) \ + _main(); \ + (_thread)->context[3] = (long) (_sp); \ + ST_END_MACRO + + #define MD_GET_UTIME() \ + timebasestruct_t rt; \ + (void) read_real_time(&rt, TIMEBASE_SZ); \ + (void) time_base_to_time(&rt, TIMEBASE_SZ); \ + return (rt.tb_high * 1000000LL + rt.tb_low / 1000) + +#elif defined (CYGWIN) + + #define MD_STACK_GROWS_DOWN + #define MD_USE_BSD_ANON_MMAP + #define MD_ACCEPT_NB_NOT_INHERITED + #define MD_ALWAYS_UNSERIALIZED_ACCEPT + + #define MD_SETJMP(env) setjmp(env) + #define MD_LONGJMP(env, val) longjmp(env, val) + + #define MD_JB_SP 7 + + #define MD_GET_SP(_t) (_t)->context[MD_JB_SP] + + #define MD_INIT_CONTEXT(_thread, _sp, _main) \ + ST_BEGIN_MACRO \ + if (MD_SETJMP((_thread)->context)) \ + _main(); \ + MD_GET_SP(_thread) = (long) (_sp); \ + ST_END_MACRO + + #define MD_GET_UTIME() \ + struct timeval tv; \ + (void) gettimeofday(&tv, NULL); \ + return (tv.tv_sec * 1000000LL + tv.tv_usec) + +#elif defined (DARWIN) + + #define MD_STACK_GROWS_DOWN + #define MD_USE_BSD_ANON_MMAP + #define MD_ACCEPT_NB_INHERITED + #define MD_ALWAYS_UNSERIALIZED_ACCEPT + #define MD_HAVE_SOCKLEN_T + + #define MD_USE_BUILTIN_SETJMP + + #if defined(__amd64__) || defined(__x86_64__) + #define JB_SP 12 + #define MD_GET_SP(_t) *((long *)&((_t)->context[JB_SP])) + #else + #error Unknown CPU architecture + #endif + + #define MD_INIT_CONTEXT(_thread, _sp, _main) \ + ST_BEGIN_MACRO \ + if (MD_SETJMP((_thread)->context)) \ + _main(); \ + MD_GET_SP(_thread) = (long) (_sp); \ + ST_END_MACRO + + #if defined(MD_USE_BUILTIN_SETJMP) + #define MD_SETJMP(env) _st_md_cxt_save(env) + #define MD_LONGJMP(env, val) _st_md_cxt_restore(env, val) + + extern int _st_md_cxt_save(jmp_buf env); + extern void _st_md_cxt_restore(jmp_buf env, int val); + #endif + + #define MD_GET_UTIME() \ + struct timeval tv; \ + (void) gettimeofday(&tv, NULL); \ + return (tv.tv_sec * 1000000LL + tv.tv_usec) + +#elif defined (FREEBSD) + + #define MD_STACK_GROWS_DOWN + #define MD_USE_BSD_ANON_MMAP + #define MD_ACCEPT_NB_INHERITED + #define MD_ALWAYS_UNSERIALIZED_ACCEPT + + #define MD_SETJMP(env) _setjmp(env) + #define MD_LONGJMP(env, val) _longjmp(env, val) + + #if defined(__i386__) + #define MD_JB_SP 2 + #elif defined(__alpha__) + #define MD_JB_SP 34 + #elif defined(__amd64__) + #define MD_JB_SP 2 + #else + #error Unknown CPU architecture + #endif + + #define MD_INIT_CONTEXT(_thread, _sp, _main) \ + ST_BEGIN_MACRO \ + if (MD_SETJMP((_thread)->context)) \ + _main(); \ + (_thread)->context[0]._jb[MD_JB_SP] = (long) (_sp); \ + ST_END_MACRO + + #define MD_GET_UTIME() \ + struct timeval tv; \ + (void) gettimeofday(&tv, NULL); \ + return (tv.tv_sec * 1000000LL + tv.tv_usec) + +#elif defined (HPUX) + + #define MD_STACK_GROWS_UP + #define MD_USE_BSD_ANON_MMAP + #define MD_ACCEPT_NB_INHERITED + #define MD_ALWAYS_UNSERIALIZED_ACCEPT + + #define MD_SETJMP(env) _setjmp(env) + #define MD_LONGJMP(env, val) _longjmp(env, val) + + #ifndef __LP64__ + /* 32-bit mode (ILP32 data model) */ + #define MD_INIT_CONTEXT(_thread, _sp, _main) \ + ST_BEGIN_MACRO \ + if (MD_SETJMP((_thread)->context)) \ + _main(); \ + ((long *)((_thread)->context))[1] = (long) (_sp); \ + ST_END_MACRO + #else + /* 64-bit mode (LP64 data model) */ + #define MD_STACK_PAD_SIZE 256 + /* Last stack frame must be preserved */ + #define MD_INIT_CONTEXT(_thread, _sp, _main) \ + ST_BEGIN_MACRO \ + if (MD_SETJMP((_thread)->context)) \ + _main(); \ + memcpy((char *)(_sp) - MD_STACK_PAD_SIZE, \ + ((char **)((_thread)->context))[1] - MD_STACK_PAD_SIZE, \ + MD_STACK_PAD_SIZE); \ + ((long *)((_thread)->context))[1] = (long) (_sp); \ + ST_END_MACRO + #endif /* !__LP64__ */ + + #define MD_GET_UTIME() \ + struct timeval tv; \ + (void) gettimeofday(&tv, NULL); \ + return (tv.tv_sec * 1000000LL + tv.tv_usec) + +#elif defined (IRIX) + + #include + + #define MD_STACK_GROWS_DOWN + #define MD_USE_SYSV_ANON_MMAP + #define MD_ACCEPT_NB_INHERITED + #define MD_ALWAYS_UNSERIALIZED_ACCEPT + + #define MD_SETJMP(env) setjmp(env) + #define MD_LONGJMP(env, val) longjmp(env, val) + + #define MD_INIT_CONTEXT(_thread, _sp, _main) \ + ST_BEGIN_MACRO \ + (void) MD_SETJMP((_thread)->context); \ + (_thread)->context[JB_SP] = (long) (_sp); \ + (_thread)->context[JB_PC] = (long) _main; \ + ST_END_MACRO + + #define MD_GET_UTIME() \ + static int inited = 0; \ + static clockid_t clock_id = CLOCK_SGI_CYCLE; \ + struct timespec ts; \ + if (!inited) { \ + if (syssgi(SGI_CYCLECNTR_SIZE) < 64) \ + clock_id = CLOCK_REALTIME; \ + inited = 1; \ + } \ + (void) clock_gettime(clock_id, &ts); \ + return (ts.tv_sec * 1000000LL + ts.tv_nsec / 1000) + + /* + * Cap the stack by zeroing out the saved return address register + * value. This allows libexc, used by SpeedShop, to know when to stop + * backtracing since it won't find main, start, or any other known + * stack root function in a state thread's stack. Without this libexc + * traces right off the stack and crashes. + * The function preamble stores ra at 8(sp), this stores zero there. + * N.B. This macro is compiler/ABI dependent. It must change if ANY more + * automatic variables are added to the _st_thread_main() routine, because + * the address where ra is stored will change. + */ + #if !defined(__GNUC__) && defined(_MIPS_SIM) && _MIPS_SIM != _ABIO32 + #define MD_CAP_STACK(var_addr) \ + (((volatile __uint64_t *)(var_addr))[1] = 0) + #endif + +#elif defined (LINUX) + + /* + * These are properties of the linux kernel and are the same on every + * flavor and architecture. + */ + #define MD_USE_BSD_ANON_MMAP + #define MD_ACCEPT_NB_NOT_INHERITED + #define MD_ALWAYS_UNSERIALIZED_ACCEPT + /* + * Modern GNU/Linux is Posix.1g compliant. + */ + #define MD_HAVE_SOCKLEN_T + + /* + * All architectures and flavors of linux have the gettimeofday + * function but if you know of a faster way, use it. + */ + #define MD_GET_UTIME() \ + struct timeval tv; \ + (void) gettimeofday(&tv, NULL); \ + return (tv.tv_sec * 1000000LL + tv.tv_usec) + + #if defined(__ia64__) + #define MD_STACK_GROWS_DOWN + + /* + * IA-64 architecture. Besides traditional memory call stack, IA-64 + * uses general register stack. Thus each thread needs a backing store + * for register stack in addition to memory stack. Standard + * setjmp()/longjmp() cannot be used for thread context switching + * because their implementation implicitly assumes that only one + * register stack exists. + */ + #ifdef USE_LIBC_SETJMP + #undef USE_LIBC_SETJMP + #endif + #define MD_USE_BUILTIN_SETJMP + + #define MD_STACK_PAD_SIZE 128 + /* Last register stack frame must be preserved */ + #define MD_INIT_CONTEXT(_thread, _sp, _bsp, _main) \ + ST_BEGIN_MACRO \ + if (MD_SETJMP((_thread)->context)) \ + _main(); \ + memcpy((char *)(_bsp) - MD_STACK_PAD_SIZE, \ + (char *)(_thread)->context[0].__jmpbuf[17] - MD_STACK_PAD_SIZE, \ + MD_STACK_PAD_SIZE); \ + (_thread)->context[0].__jmpbuf[0] = (long) (_sp); \ + (_thread)->context[0].__jmpbuf[17] = (long) (_bsp); \ + ST_END_MACRO + + #elif defined(__mips__) + #define MD_STACK_GROWS_DOWN + + #define MD_INIT_CONTEXT(_thread, _sp, _main) \ + ST_BEGIN_MACRO \ + MD_SETJMP((_thread)->context); \ + _thread->context[0].__jmpbuf[0].__pc = (__ptr_t) _main; \ + _thread->context[0].__jmpbuf[0].__sp = _sp; \ + ST_END_MACRO + + #else /* Not IA-64 or mips */ + + /* + * On linux, there are a few styles of jmpbuf format. These vary based + * on architecture/glibc combination. + * + * Most of the glibc based toggles were lifted from: + * mozilla/nsprpub/pr/include/md/_linux.h + */ + + /* + * Starting with glibc 2.4, JB_SP definitions are not public anymore. + * They, however, can still be found in glibc source tree in + * architecture-specific "jmpbuf-offsets.h" files. + * Most importantly, the content of jmp_buf is mangled by setjmp to make + * it completely opaque (the mangling can be disabled by setting the + * LD_POINTER_GUARD environment variable before application execution). + * Therefore we will use built-in _st_md_cxt_save/_st_md_cxt_restore + * functions as a setjmp/longjmp replacement wherever they are available + * unless USE_LIBC_SETJMP is defined. + */ + + #if defined(__powerpc__) + #define MD_STACK_GROWS_DOWN + + #if (__GLIBC__ > 2) || (__GLIBC__ == 2 && __GLIBC_MINOR__ >= 1) + #ifndef JB_GPR1 + #define JB_GPR1 0 + #endif + #define MD_GET_SP(_t) (_t)->context[0].__jmpbuf[JB_GPR1] + #else + /* not an error but certainly cause for caution */ + #error "Untested use of old glibc on powerpc" + #define MD_GET_SP(_t) (_t)->context[0].__jmpbuf[0].__misc[0] + #endif /* glibc 2.1 or later */ + + #elif defined(__alpha) + #define MD_STACK_GROWS_DOWN + + #if defined(__GLIBC__) && __GLIBC__ >= 2 + #ifndef JB_SP + #define JB_SP 8 + #endif + #define MD_GET_SP(_t) (_t)->context[0].__jmpbuf[JB_SP] + #else + /* not an error but certainly cause for caution */ + #error "Untested use of old glibc on alpha" + #define MD_GET_SP(_t) (_t)->context[0].__jmpbuf[0].__sp + #endif + + #elif defined(__mc68000__) + #define MD_STACK_GROWS_DOWN + + /* m68k still uses old style sigjmp_buf */ + #define MD_GET_SP(_t) (_t)->context[0].__jmpbuf[0].__sp + + #elif defined(__sparc__) + #define MD_STACK_GROWS_DOWN + + #if defined(__GLIBC__) && __GLIBC__ >= 2 + #ifndef JB_SP + #define JB_SP 0 + #endif + #define MD_GET_SP(_t) (_t)->context[0].__jmpbuf[JB_SP] + #else + /* not an error but certainly cause for caution */ + #error "Untested use of old glic on sparc -- also using odd mozilla derived __fp" + #define MD_GET_SP(_t) (_t)->context[0].__jmpbuf[0].__fp + #endif + + #elif defined(__i386__) + #define MD_STACK_GROWS_DOWN + #define MD_USE_BUILTIN_SETJMP + + #if defined(__GLIBC__) && __GLIBC__ >= 2 + #ifndef JB_SP + #define JB_SP 4 + #endif + #define MD_GET_SP(_t) (_t)->context[0].__jmpbuf[JB_SP] + #else + /* not an error but certainly cause for caution */ + #error "Untested use of old glibc on i386" + #define MD_GET_SP(_t) (_t)->context[0].__jmpbuf[0].__sp + #endif + + #elif defined(__amd64__) || defined(__x86_64__) + #define MD_STACK_GROWS_DOWN + #define MD_USE_BUILTIN_SETJMP + + #ifndef JB_RSP + #define JB_RSP 6 + #endif + #define MD_GET_SP(_t) (_t)->context[0].__jmpbuf[JB_RSP] + + #elif defined(__aarch64__) + /* https://github.com/ossrs/state-threads/issues/9 */ + #define MD_STACK_GROWS_DOWN + #define MD_USE_BUILTIN_SETJMP + #define MD_GET_SP(_t) (_t)->context[0].__jmpbuf[13] + + #elif defined(__arm__) + #define MD_STACK_GROWS_DOWN + /* https://github.com/ossrs/state-threads/issues/1#issuecomment-244648573 */ + #define MD_USE_BUILTIN_SETJMP + + /* force to use glibc solution, hack the guard jmpbuf from michaeltalyansky */ + #ifdef USE_LIBC_SETJMP + #undef MD_USE_BUILTIN_SETJMP + #endif + + #if defined(__GLIBC__) && __GLIBC__ >= 2 + /* Merge from https://github.com/michaeltalyansky/state-threads/commit/56554a5c425aee8e7a73782eae23d74d83c4120a */ + #define MD_GET_SP(_t) (_t)->context[0].__jmpbuf[8] + #else + #error "ARM/Linux pre-glibc2 not supported yet" + #endif /* defined(__GLIBC__) && __GLIBC__ >= 2 */ + + #elif defined(__s390__) + #define MD_STACK_GROWS_DOWN + + /* There is no JB_SP in glibc at this time. (glibc 2.2.5) + */ + #define MD_GET_SP(_t) (_t)->context[0].__jmpbuf[0].__gregs[9] + + #elif defined(__hppa__) + #define MD_STACK_GROWS_UP + + /* yes, this is gross, unfortunately at the moment (2002/08/01) there is + * a bug in hppa's glibc header definition for JB_SP, so we can't + * use that... + */ + #define MD_GET_SP(_t) (*(long *)(((char *)&(_t)->context[0].__jmpbuf[0]) + 76)) + + #else + #error "Unknown CPU architecture" + #endif /* Cases with common MD_INIT_CONTEXT and different SP locations */ + + #define MD_INIT_CONTEXT(_thread, _sp, _main) \ + ST_BEGIN_MACRO \ + if (MD_SETJMP((_thread)->context)) \ + _main(); \ + MD_GET_SP(_thread) = (long) (_sp); \ + ST_END_MACRO + + #endif /* Cases with different MD_INIT_CONTEXT */ + + #if defined(MD_USE_BUILTIN_SETJMP) && !defined(USE_LIBC_SETJMP) + #define MD_SETJMP(env) _st_md_cxt_save(env) + #define MD_LONGJMP(env, val) _st_md_cxt_restore(env, val) + + extern int _st_md_cxt_save(jmp_buf env); + extern void _st_md_cxt_restore(jmp_buf env, int val); + #else + #define MD_SETJMP(env) setjmp(env) + #define MD_LONGJMP(env, val) longjmp(env, val) + #endif + +#elif defined (NETBSD) + + #define MD_STACK_GROWS_DOWN + #define MD_USE_BSD_ANON_MMAP + #define MD_ACCEPT_NB_INHERITED + #define MD_ALWAYS_UNSERIALIZED_ACCEPT + #define MD_HAVE_SOCKLEN_T + + #define MD_SETJMP(env) _setjmp(env) + #define MD_LONGJMP(env, val) _longjmp(env, val) + + #if defined(__i386__) + #define MD_JB_SP 2 + #elif defined(__alpha__) + #define MD_JB_SP 34 + #elif defined(__sparc__) + #define MD_JB_SP 0 + #elif defined(__vax__) + #define MD_JB_SP 2 + #else + #error Unknown CPU architecture + #endif + + #define MD_INIT_CONTEXT(_thread, _sp, _main) \ + ST_BEGIN_MACRO \ + if (MD_SETJMP((_thread)->context)) \ + _main(); \ + (_thread)->context[MD_JB_SP] = (long) (_sp); \ + ST_END_MACRO + + #define MD_GET_UTIME() \ + struct timeval tv; \ + (void) gettimeofday(&tv, NULL); \ + return (tv.tv_sec * 1000000LL + tv.tv_usec) + +#elif defined (OPENBSD) + + #define MD_STACK_GROWS_DOWN + #define MD_USE_BSD_ANON_MMAP + #define MD_ACCEPT_NB_INHERITED + #define MD_ALWAYS_UNSERIALIZED_ACCEPT + + #define MD_SETJMP(env) _setjmp(env) + #define MD_LONGJMP(env, val) _longjmp(env, val) + + #if defined(__i386__) + #define MD_JB_SP 2 + #elif defined(__alpha__) + #define MD_JB_SP 34 + #elif defined(__sparc__) + #define MD_JB_SP 0 + #elif defined(__amd64__) + #define MD_JB_SP 6 + #else + #error Unknown CPU architecture + #endif + + #define MD_INIT_CONTEXT(_thread, _sp, _main) \ + ST_BEGIN_MACRO \ + if (MD_SETJMP((_thread)->context)) \ + _main(); \ + (_thread)->context[MD_JB_SP] = (long) (_sp); \ + ST_END_MACRO + + #define MD_GET_UTIME() \ + struct timeval tv; \ + (void) gettimeofday(&tv, NULL); \ + return (tv.tv_sec * 1000000LL + tv.tv_usec) + +#elif defined (OSF1) + + #include + + #define MD_STACK_GROWS_DOWN + #define MD_USE_SYSV_ANON_MMAP + #define MD_ACCEPT_NB_NOT_INHERITED + #define MD_ALWAYS_UNSERIALIZED_ACCEPT + + #define MD_SETJMP(env) _setjmp(env) + #define MD_LONGJMP(env, val) _longjmp(env, val) + + #define MD_INIT_CONTEXT(_thread, _sp, _main) \ + ST_BEGIN_MACRO \ + if (MD_SETJMP((_thread)->context)) \ + _main(); \ + ((struct sigcontext *)((_thread)->context))->sc_sp = (long) (_sp); \ + ST_END_MACRO + + #define MD_GET_UTIME() \ + struct timeval tv; \ + (void) gettimeofday(&tv, NULL); \ + return (tv.tv_sec * 1000000LL + tv.tv_usec) + +#elif defined (SOLARIS) + + #include + extern int getpagesize(void); + + #define MD_STACK_GROWS_DOWN + #define MD_USE_SYSV_ANON_MMAP + #define MD_ACCEPT_NB_NOT_INHERITED + + #define MD_SETJMP(env) setjmp(env) + #define MD_LONGJMP(env, val) longjmp(env, val) + + #if defined(sparc) || defined(__sparc) + #ifdef _LP64 + #define MD_STACK_PAD_SIZE 4095 + #endif + #define MD_INIT_CONTEXT(_thread, _sp, _main) \ + ST_BEGIN_MACRO \ + (void) MD_SETJMP((_thread)->context); \ + (_thread)->context[1] = (long) (_sp); \ + (_thread)->context[2] = (long) _main; \ + ST_END_MACRO + #elif defined(i386) || defined(__i386) + #define MD_INIT_CONTEXT(_thread, _sp, _main) \ + ST_BEGIN_MACRO \ + (void) MD_SETJMP((_thread)->context); \ + (_thread)->context[4] = (long) (_sp); \ + (_thread)->context[5] = (long) _main; \ + ST_END_MACRO + #elif defined(__amd64__) + #define MD_INIT_CONTEXT(_thread, _sp, _main) \ + ST_BEGIN_MACRO \ + if (MD_SETJMP((_thread)->context)) \ + _main(); \ + (_thread)->context[6] = (long) (_sp); \ + ST_END_MACRO + #else + #error Unknown CPU architecture + #endif + + #define MD_GET_UTIME() \ + return (gethrtime() / 1000) + +#else + #error Unknown OS +#endif /* OS */ + +#if !defined(MD_HAVE_POLL) && !defined(MD_DONT_HAVE_POLL) + #define MD_HAVE_POLL +#endif + +#ifndef MD_STACK_PAD_SIZE + #define MD_STACK_PAD_SIZE 128 +#endif + +#if !defined(MD_HAVE_SOCKLEN_T) && !defined(socklen_t) + #define socklen_t int +#endif + +#ifndef MD_CAP_STACK + #define MD_CAP_STACK(var_addr) +#endif + +#endif /* !__ST_MD_H__ */ + diff --git a/trunk/3rdparty/st-srs/md_darwin.S b/trunk/3rdparty/st-srs/md_darwin.S new file mode 100644 index 000000000..6cd163d44 --- /dev/null +++ b/trunk/3rdparty/st-srs/md_darwin.S @@ -0,0 +1,76 @@ + +/* If user disable the ASM, such as avoiding bugs in ASM, donot compile it. */ +#if !defined(MD_ST_NO_ASM) + +#if defined(__amd64__) || defined(__x86_64__) + + /****************************************************************/ + + /* + * Internal __jmp_buf layout + */ + #define JB_RBX 0 + #define JB_RBP 1 + #define JB_R12 2 /* Backup IP, https://www.cnblogs.com/Five100Miles/p/8458561.html */ + #define JB_R13 3 /* Backup SP, https://www.cnblogs.com/Five100Miles/p/8458561.html */ + #define JB_R14 4 /* Backup LR, https://www.cnblogs.com/Five100Miles/p/8458561.html */ + #define JB_R15 5 /* Backup PC, https://www.cnblogs.com/Five100Miles/p/8458561.html */ + #define JB_RSP 6 + #define JB_PC 7 + + .file "md_darwin.S" + .text + + /* _st_md_cxt_save(__jmp_buf env) */ /* The env is rdi, http://blog.chinaunix.net/uid-20157960-id-1974354.html */ + .globl __st_md_cxt_save + .align 16 + __st_md_cxt_save: + /* + * Save registers. + */ + movq %rbx, (JB_RBX*8)(%rdi) /* Save rbx to env[0], *(int64_t*)(rdi+0)=rbx */ + movq %rbp, (JB_RBP*8)(%rdi) /* Save rbp to env[1], *(int64_t*)(rdi+1)=rbp */ + movq %r12, (JB_R12*8)(%rdi) /* Save r12 to env[2], *(int64_t*)(rdi+2)=r12 */ + movq %r13, (JB_R13*8)(%rdi) /* Save r13 to env[3], *(int64_t*)(rdi+3)=r13 */ + movq %r14, (JB_R14*8)(%rdi) /* Save r14 to env[4], *(int64_t*)(rdi+4)=r14 */ + movq %r15, (JB_R15*8)(%rdi) /* Save r15 to env[5], *(int64_t*)(rdi+5)=r15 */ + /* Save SP */ + leaq 8(%rsp), %rdx /* Save *(int64_t*)(rsp+8) to rdx, https://my.oschina.net/guonaihong/blog/508907 */ + movq %rdx, (JB_RSP*8)(%rdi) /* Save rdx(rsp) to env[6], *(int64_t*)(rdi+6)=rdx */ + /* Save PC we are returning to */ + movq (%rsp), %rax /* Save PC(parent function address) %(rsp) to rax */ + movq %rax, (JB_PC*8)(%rdi) /* Save rax(PC) to env[7], *(int64_t*)(rdi+7)=rax */ + xorq %rax, %rax /* Reset rax to 0 */ + ret + + + /****************************************************************/ + + /* _st_md_cxt_restore(__jmp_buf env, int val) */ /* The env is rdi, val is esi/rsi, http://blog.chinaunix.net/uid-20157960-id-1974354.html */ + .globl __st_md_cxt_restore + .align 16 + __st_md_cxt_restore: + /* + * Restore registers. + */ + movq (JB_RBX*8)(%rdi), %rbx /* Load rbx from env[0] */ + movq (JB_RBP*8)(%rdi), %rbp /* Load rbp from env[1] */ + movq (JB_R12*8)(%rdi), %r12 /* Load r12 from env[2] */ + movq (JB_R13*8)(%rdi), %r13 /* Load r13 from env[3] */ + movq (JB_R14*8)(%rdi), %r14 /* Load r14 from env[4] */ + movq (JB_R15*8)(%rdi), %r15 /* Load r15 from env[5] */ + /* Set return value */ /* The esi is param1 val, the eax is return value */ + test %esi, %esi /* if (!val) { */ + mov $01, %eax /* val=1; */ + cmove %eax, %esi /* } */ + mov %esi, %eax /* return val; */ + movq (JB_PC*8)(%rdi), %rdx /* Load rdx(PC) from env[7] */ + movq (JB_RSP*8)(%rdi), %rsp /* Load rsp from env[6] */ + /* Jump to saved PC */ + jmpq *%rdx /* Jump to rdx(PC) */ + + /****************************************************************/ + +#endif + +#endif diff --git a/trunk/3rdparty/st-srs/osguess.sh b/trunk/3rdparty/st-srs/osguess.sh new file mode 100644 index 000000000..531681efe --- /dev/null +++ b/trunk/3rdparty/st-srs/osguess.sh @@ -0,0 +1,45 @@ +# +# This script can be used to automatically guess target OS. +# It requires the config.guess utility which is a part of GNU Autoconf. +# GNU Autoconf can be downloaded from ftp://ftp.gnu.org/gnu/autoconf/ +# +# Use "default" as a make target for automatic builds. +# + + +# Specify path to the config.guess utility (unless set via environment) +#CONFIG_GUESS_PATH= + + +if [ x"$CONFIG_GUESS_PATH" = x ]; then + echo "Error: CONFIG_GUESS_PATH variable is not set" + exit 1 +fi + +if [ ! -f "$CONFIG_GUESS_PATH/config.guess" ]; then + echo "Can't find $CONFIG_GUESS_PATH/config.guess utility. Wrong path?" + exit 1 +fi + +sys_info=`/bin/sh $CONFIG_GUESS_PATH/config.guess` + +echo "Building for $sys_info" + +case "$sys_info" in + *-ibm-aix4* ) OS=AIX ;; + *-freebsd* ) OS=FREEBSD ;; + hppa*-hp-hpux11*) OS=HPUX ;; + *-sgi-irix6* ) OS=IRIX ;; + *-linux* ) OS=LINUX ;; + *-netbsd* ) OS=NETBSD ;; + *-openbsd* ) OS=OPENBSD ;; + *-dec-osf* ) OS=OSF1 ;; + *-solaris2* ) OS=SOLARIS ;; + *-darwin* ) OS=DARWIN ;; + * ) OS= + echo "Sorry, unsupported OS" + exit 1 ;; +esac + +echo "Making with OS=$OS" + diff --git a/trunk/3rdparty/st-srs/public.h b/trunk/3rdparty/st-srs/public.h new file mode 100644 index 000000000..20b09407b --- /dev/null +++ b/trunk/3rdparty/st-srs/public.h @@ -0,0 +1,166 @@ +/* + * The contents of this file are subject to the Mozilla Public + * License Version 1.1 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS + * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or + * implied. See the License for the specific language governing + * rights and limitations under the License. + * + * The Original Code is the Netscape Portable Runtime library. + * + * The Initial Developer of the Original Code is Netscape + * Communications Corporation. Portions created by Netscape are + * Copyright (C) 1994-2000 Netscape Communications Corporation. All + * Rights Reserved. + * + * Contributor(s): Silicon Graphics, Inc. + * + * Portions created by SGI are Copyright (C) 2000-2001 Silicon + * Graphics, Inc. All Rights Reserved. + * + * Alternatively, the contents of this file may be used under the + * terms of the GNU General Public License Version 2 or later (the + * "GPL"), in which case the provisions of the GPL are applicable + * instead of those above. If you wish to allow use of your + * version of this file only under the terms of the GPL and not to + * allow others to use your version of this file under the MPL, + * indicate your decision by deleting the provisions above and + * replace them with the notice and other provisions required by + * the GPL. If you do not delete the provisions above, a recipient + * may use your version of this file under either the MPL or the + * GPL. + */ + +#ifndef __ST_THREAD_H__ +#define __ST_THREAD_H__ + +#include +#include +#include +#include +#include +#include +#include + +#define ST_VERSION "1.9" +#define ST_VERSION_MAJOR 1 +#define ST_VERSION_MINOR 9 + +/* Undefine this to remove the context switch callback feature. */ +#define ST_SWITCH_CB + +#ifndef ETIME + #define ETIME ETIMEDOUT +#endif + +#ifndef ST_UTIME_NO_TIMEOUT + #define ST_UTIME_NO_TIMEOUT ((st_utime_t) -1LL) +#endif + +#ifndef ST_UTIME_NO_WAIT + #define ST_UTIME_NO_WAIT 0 +#endif + +#define ST_EVENTSYS_DEFAULT 0 +#define ST_EVENTSYS_SELECT 1 +#define ST_EVENTSYS_POLL 2 +#define ST_EVENTSYS_ALT 3 + +#ifdef __cplusplus +extern "C" { +#endif + +typedef unsigned long long st_utime_t; +typedef struct _st_thread * st_thread_t; +typedef struct _st_cond * st_cond_t; +typedef struct _st_mutex * st_mutex_t; +typedef struct _st_netfd * st_netfd_t; +#ifdef ST_SWITCH_CB +typedef void (*st_switch_cb_t)(void); +#endif + +extern int st_init(void); +extern int st_getfdlimit(void); + +extern int st_set_eventsys(int eventsys); +extern int st_get_eventsys(void); +extern const char *st_get_eventsys_name(void); + +#ifdef ST_SWITCH_CB +extern st_switch_cb_t st_set_switch_in_cb(st_switch_cb_t cb); +extern st_switch_cb_t st_set_switch_out_cb(st_switch_cb_t cb); +#endif + +extern st_thread_t st_thread_self(void); +extern void st_thread_exit(void *retval); +extern int st_thread_join(st_thread_t thread, void **retvalp); +extern void st_thread_interrupt(st_thread_t thread); +extern st_thread_t st_thread_create(void *(*start)(void *arg), void *arg, int joinable, int stack_size); +extern int st_randomize_stacks(int on); +extern int st_set_utime_function(st_utime_t (*func)(void)); + +extern st_utime_t st_utime(void); +extern st_utime_t st_utime_last_clock(void); +extern int st_timecache_set(int on); +extern time_t st_time(void); +extern int st_usleep(st_utime_t usecs); +extern int st_sleep(int secs); +extern st_cond_t st_cond_new(void); +extern int st_cond_destroy(st_cond_t cvar); +extern int st_cond_timedwait(st_cond_t cvar, st_utime_t timeout); +extern int st_cond_wait(st_cond_t cvar); +extern int st_cond_signal(st_cond_t cvar); +extern int st_cond_broadcast(st_cond_t cvar); +extern st_mutex_t st_mutex_new(void); +extern int st_mutex_destroy(st_mutex_t lock); +extern int st_mutex_lock(st_mutex_t lock); +extern int st_mutex_unlock(st_mutex_t lock); +extern int st_mutex_trylock(st_mutex_t lock); + +extern int st_key_create(int *keyp, void (*destructor)(void *)); +extern int st_key_getlimit(void); +extern int st_thread_setspecific(int key, void *value); +extern void *st_thread_getspecific(int key); + +extern st_netfd_t st_netfd_open(int osfd); +extern st_netfd_t st_netfd_open_socket(int osfd); +extern void st_netfd_free(st_netfd_t fd); +extern int st_netfd_close(st_netfd_t fd); +extern int st_netfd_fileno(st_netfd_t fd); +extern void st_netfd_setspecific(st_netfd_t fd, void *value, void (*destructor)(void *)); +extern void *st_netfd_getspecific(st_netfd_t fd); +extern int st_netfd_serialize_accept(st_netfd_t fd); +extern int st_netfd_poll(st_netfd_t fd, int how, st_utime_t timeout); + +extern int st_poll(struct pollfd *pds, int npds, st_utime_t timeout); +extern st_netfd_t st_accept(st_netfd_t fd, struct sockaddr *addr, int *addrlen, st_utime_t timeout); +extern int st_connect(st_netfd_t fd, const struct sockaddr *addr, int addrlen, st_utime_t timeout); +extern ssize_t st_read(st_netfd_t fd, void *buf, size_t nbyte, st_utime_t timeout); +extern ssize_t st_read_fully(st_netfd_t fd, void *buf, size_t nbyte, st_utime_t timeout); +extern int st_read_resid(st_netfd_t fd, void *buf, size_t *resid, st_utime_t timeout); +extern ssize_t st_readv(st_netfd_t fd, const struct iovec *iov, int iov_size, st_utime_t timeout); +extern int st_readv_resid(st_netfd_t fd, struct iovec **iov, int *iov_size, st_utime_t timeout); +extern ssize_t st_write(st_netfd_t fd, const void *buf, size_t nbyte, st_utime_t timeout); +extern int st_write_resid(st_netfd_t fd, const void *buf, size_t *resid, st_utime_t timeout); +extern ssize_t st_writev(st_netfd_t fd, const struct iovec *iov, int iov_size, st_utime_t timeout); +extern int st_writev_resid(st_netfd_t fd, struct iovec **iov, int *iov_size, st_utime_t timeout); +extern int st_recvfrom(st_netfd_t fd, void *buf, int len, struct sockaddr *from, int *fromlen, st_utime_t timeout); +extern int st_sendto(st_netfd_t fd, const void *msg, int len, const struct sockaddr *to, int tolen, st_utime_t timeout); +extern int st_recvmsg(st_netfd_t fd, struct msghdr *msg, int flags, st_utime_t timeout); +extern int st_sendmsg(st_netfd_t fd, const struct msghdr *msg, int flags, st_utime_t timeout); +extern st_netfd_t st_open(const char *path, int oflags, mode_t mode); + +#ifdef DEBUG +extern void _st_show_thread_stack(st_thread_t thread, const char *messg); +extern void _st_iterate_threads(void); +#endif + +#ifdef __cplusplus +} +#endif + +#endif /* !__ST_THREAD_H__ */ + diff --git a/trunk/3rdparty/st-srs/sched.c b/trunk/3rdparty/st-srs/sched.c new file mode 100644 index 000000000..87515827e --- /dev/null +++ b/trunk/3rdparty/st-srs/sched.c @@ -0,0 +1,705 @@ +/* + * The contents of this file are subject to the Mozilla Public + * License Version 1.1 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS + * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or + * implied. See the License for the specific language governing + * rights and limitations under the License. + * + * The Original Code is the Netscape Portable Runtime library. + * + * The Initial Developer of the Original Code is Netscape + * Communications Corporation. Portions created by Netscape are + * Copyright (C) 1994-2000 Netscape Communications Corporation. All + * Rights Reserved. + * + * Contributor(s): Silicon Graphics, Inc. + * + * Portions created by SGI are Copyright (C) 2000-2001 Silicon + * Graphics, Inc. All Rights Reserved. + * + * Alternatively, the contents of this file may be used under the + * terms of the GNU General Public License Version 2 or later (the + * "GPL"), in which case the provisions of the GPL are applicable + * instead of those above. If you wish to allow use of your + * version of this file only under the terms of the GPL and not to + * allow others to use your version of this file under the MPL, + * indicate your decision by deleting the provisions above and + * replace them with the notice and other provisions required by + * the GPL. If you do not delete the provisions above, a recipient + * may use your version of this file under either the MPL or the + * GPL. + */ + +/* + * This file is derived directly from Netscape Communications Corporation, + * and consists of extensive modifications made during the year(s) 1999-2000. + */ + +#include +#include +#include +#include +#include +#include +#include "common.h" + +/* merge from https://github.com/toffaletti/state-threads/commit/7f57fc9acc05e657bca1223f1e5b9b1a45ed929b */ +#ifndef NVALGRIND +#include +#endif + + +/* Global data */ +_st_vp_t _st_this_vp; /* This VP */ +_st_thread_t *_st_this_thread; /* Current thread */ +int _st_active_count = 0; /* Active thread count */ + +time_t _st_curr_time = 0; /* Current time as returned by time(2) */ +st_utime_t _st_last_tset; /* Last time it was fetched */ + + +int st_poll(struct pollfd *pds, int npds, st_utime_t timeout) +{ + struct pollfd *pd; + struct pollfd *epd = pds + npds; + _st_pollq_t pq; + _st_thread_t *me = _ST_CURRENT_THREAD(); + int n; + + if (me->flags & _ST_FL_INTERRUPT) { + me->flags &= ~_ST_FL_INTERRUPT; + errno = EINTR; + return -1; + } + + if ((*_st_eventsys->pollset_add)(pds, npds) < 0) + return -1; + + pq.pds = pds; + pq.npds = npds; + pq.thread = me; + pq.on_ioq = 1; + _ST_ADD_IOQ(pq); + if (timeout != ST_UTIME_NO_TIMEOUT) + _ST_ADD_SLEEPQ(me, timeout); + me->state = _ST_ST_IO_WAIT; + + _ST_SWITCH_CONTEXT(me); + + n = 0; + if (pq.on_ioq) { + /* If we timed out, the pollq might still be on the ioq. Remove it */ + _ST_DEL_IOQ(pq); + (*_st_eventsys->pollset_del)(pds, npds); + } else { + /* Count the number of ready descriptors */ + for (pd = pds; pd < epd; pd++) { + if (pd->revents) + n++; + } + } + + if (me->flags & _ST_FL_INTERRUPT) { + me->flags &= ~_ST_FL_INTERRUPT; + errno = EINTR; + return -1; + } + + return n; +} + + +void _st_vp_schedule(void) +{ + _st_thread_t *thread; + + if (_ST_RUNQ.next != &_ST_RUNQ) { + /* Pull thread off of the run queue */ + thread = _ST_THREAD_PTR(_ST_RUNQ.next); + _ST_DEL_RUNQ(thread); + } else { + /* If there are no threads to run, switch to the idle thread */ + thread = _st_this_vp.idle_thread; + } + ST_ASSERT(thread->state == _ST_ST_RUNNABLE); + + /* Resume the thread */ + thread->state = _ST_ST_RUNNING; + _ST_RESTORE_CONTEXT(thread); +} + + +/* + * Initialize this Virtual Processor + */ +int st_init(void) +{ + _st_thread_t *thread; + + if (_st_active_count) { + /* Already initialized */ + return 0; + } + + /* We can ignore return value here */ + st_set_eventsys(ST_EVENTSYS_DEFAULT); + + if (_st_io_init() < 0) + return -1; + + memset(&_st_this_vp, 0, sizeof(_st_vp_t)); + + ST_INIT_CLIST(&_ST_RUNQ); + ST_INIT_CLIST(&_ST_IOQ); + ST_INIT_CLIST(&_ST_ZOMBIEQ); +#ifdef DEBUG + ST_INIT_CLIST(&_ST_THREADQ); +#endif + + if ((*_st_eventsys->init)() < 0) + return -1; + + _st_this_vp.pagesize = getpagesize(); + _st_this_vp.last_clock = st_utime(); + + /* + * Create idle thread + */ + _st_this_vp.idle_thread = st_thread_create(_st_idle_thread_start, NULL, 0, 0); + if (!_st_this_vp.idle_thread) + return -1; + _st_this_vp.idle_thread->flags = _ST_FL_IDLE_THREAD; + _st_active_count--; + _ST_DEL_RUNQ(_st_this_vp.idle_thread); + + /* + * Initialize primordial thread + */ + thread = (_st_thread_t *) calloc(1, sizeof(_st_thread_t) + (ST_KEYS_MAX * sizeof(void *))); + if (!thread) + return -1; + thread->private_data = (void **) (thread + 1); + thread->state = _ST_ST_RUNNING; + thread->flags = _ST_FL_PRIMORDIAL; + _ST_SET_CURRENT_THREAD(thread); + _st_active_count++; +#ifdef DEBUG + _ST_ADD_THREADQ(thread); +#endif + + return 0; +} + + +#ifdef ST_SWITCH_CB +st_switch_cb_t st_set_switch_in_cb(st_switch_cb_t cb) +{ + st_switch_cb_t ocb = _st_this_vp.switch_in_cb; + _st_this_vp.switch_in_cb = cb; + return ocb; +} + +st_switch_cb_t st_set_switch_out_cb(st_switch_cb_t cb) +{ + st_switch_cb_t ocb = _st_this_vp.switch_out_cb; + _st_this_vp.switch_out_cb = cb; + return ocb; +} +#endif + + +/* + * Start function for the idle thread + */ +/* ARGSUSED */ +void *_st_idle_thread_start(void *arg) +{ + _st_thread_t *me = _ST_CURRENT_THREAD(); + + while (_st_active_count > 0) { + /* Idle vp till I/O is ready or the smallest timeout expired */ + _ST_VP_IDLE(); + + /* Check sleep queue for expired threads */ + _st_vp_check_clock(); + + me->state = _ST_ST_RUNNABLE; + _ST_SWITCH_CONTEXT(me); + } + + /* No more threads */ + exit(0); + + /* NOTREACHED */ + return NULL; +} + + +void st_thread_exit(void *retval) +{ + _st_thread_t *thread = _ST_CURRENT_THREAD(); + + thread->retval = retval; + _st_thread_cleanup(thread); + _st_active_count--; + if (thread->term) { + /* Put thread on the zombie queue */ + thread->state = _ST_ST_ZOMBIE; + _ST_ADD_ZOMBIEQ(thread); + + /* Notify on our termination condition variable */ + st_cond_signal(thread->term); + + /* Switch context and come back later */ + _ST_SWITCH_CONTEXT(thread); + + /* Continue the cleanup */ + st_cond_destroy(thread->term); + thread->term = NULL; + } + +#ifdef DEBUG + _ST_DEL_THREADQ(thread); +#endif + + /* merge from https://github.com/toffaletti/state-threads/commit/7f57fc9acc05e657bca1223f1e5b9b1a45ed929b */ +#ifndef NVALGRIND + if (!(thread->flags & _ST_FL_PRIMORDIAL)) { + VALGRIND_STACK_DEREGISTER(thread->stack->valgrind_stack_id); + } +#endif + + if (!(thread->flags & _ST_FL_PRIMORDIAL)) + _st_stack_free(thread->stack); + + /* Find another thread to run */ + _ST_SWITCH_CONTEXT(thread); + /* Not going to land here */ +} + + +int st_thread_join(_st_thread_t *thread, void **retvalp) +{ + _st_cond_t *term = thread->term; + + /* Can't join a non-joinable thread */ + if (term == NULL) { + errno = EINVAL; + return -1; + } + if (_ST_CURRENT_THREAD() == thread) { + errno = EDEADLK; + return -1; + } + + /* Multiple threads can't wait on the same joinable thread */ + if (term->wait_q.next != &term->wait_q) { + errno = EINVAL; + return -1; + } + + while (thread->state != _ST_ST_ZOMBIE) { + if (st_cond_timedwait(term, ST_UTIME_NO_TIMEOUT) != 0) + return -1; + } + + if (retvalp) + *retvalp = thread->retval; + + /* + * Remove target thread from the zombie queue and make it runnable. + * When it gets scheduled later, it will do the clean up. + */ + thread->state = _ST_ST_RUNNABLE; + _ST_DEL_ZOMBIEQ(thread); + _ST_ADD_RUNQ(thread); + + return 0; +} + + +void _st_thread_main(void) +{ + _st_thread_t *thread = _ST_CURRENT_THREAD(); + + /* + * Cap the stack by zeroing out the saved return address register + * value. This allows some debugging/profiling tools to know when + * to stop unwinding the stack. It's a no-op on most platforms. + */ + MD_CAP_STACK(&thread); + + /* Run thread main */ + thread->retval = (*thread->start)(thread->arg); + + /* All done, time to go away */ + st_thread_exit(thread->retval); +} + + +/* + * Insert "thread" into the timeout heap, in the position + * specified by thread->heap_index. See docs/timeout_heap.txt + * for details about the timeout heap. + */ +static _st_thread_t **heap_insert(_st_thread_t *thread) { + int target = thread->heap_index; + int s = target; + _st_thread_t **p = &_ST_SLEEPQ; + int bits = 0; + int bit; + int index = 1; + + while (s) { + s >>= 1; + bits++; + } + for (bit = bits - 2; bit >= 0; bit--) { + if (thread->due < (*p)->due) { + _st_thread_t *t = *p; + thread->left = t->left; + thread->right = t->right; + *p = thread; + thread->heap_index = index; + thread = t; + } + index <<= 1; + if (target & (1 << bit)) { + p = &((*p)->right); + index |= 1; + } else { + p = &((*p)->left); + } + } + thread->heap_index = index; + *p = thread; + thread->left = thread->right = NULL; + return p; +} + + +/* + * Delete "thread" from the timeout heap. + */ +static void heap_delete(_st_thread_t *thread) { + _st_thread_t *t, **p; + int bits = 0; + int s, bit; + + /* First find and unlink the last heap element */ + p = &_ST_SLEEPQ; + s = _ST_SLEEPQ_SIZE; + while (s) { + s >>= 1; + bits++; + } + for (bit = bits - 2; bit >= 0; bit--) { + if (_ST_SLEEPQ_SIZE & (1 << bit)) { + p = &((*p)->right); + } else { + p = &((*p)->left); + } + } + t = *p; + *p = NULL; + --_ST_SLEEPQ_SIZE; + if (t != thread) { + /* + * Insert the unlinked last element in place of the element we are deleting + */ + t->heap_index = thread->heap_index; + p = heap_insert(t); + t = *p; + t->left = thread->left; + t->right = thread->right; + + /* + * Reestablish the heap invariant. + */ + for (;;) { + _st_thread_t *y; /* The younger child */ + int index_tmp; + if (t->left == NULL) + break; + else if (t->right == NULL) + y = t->left; + else if (t->left->due < t->right->due) + y = t->left; + else + y = t->right; + if (t->due > y->due) { + _st_thread_t *tl = y->left; + _st_thread_t *tr = y->right; + *p = y; + if (y == t->left) { + y->left = t; + y->right = t->right; + p = &y->left; + } else { + y->left = t->left; + y->right = t; + p = &y->right; + } + t->left = tl; + t->right = tr; + index_tmp = t->heap_index; + t->heap_index = y->heap_index; + y->heap_index = index_tmp; + } else { + break; + } + } + } + thread->left = thread->right = NULL; +} + + +void _st_add_sleep_q(_st_thread_t *thread, st_utime_t timeout) +{ + thread->due = _ST_LAST_CLOCK + timeout; + thread->flags |= _ST_FL_ON_SLEEPQ; + thread->heap_index = ++_ST_SLEEPQ_SIZE; + heap_insert(thread); +} + + +void _st_del_sleep_q(_st_thread_t *thread) +{ + heap_delete(thread); + thread->flags &= ~_ST_FL_ON_SLEEPQ; +} + + +void _st_vp_check_clock(void) +{ + _st_thread_t *thread; + st_utime_t elapsed, now; + + now = st_utime(); + elapsed = now - _ST_LAST_CLOCK; + _ST_LAST_CLOCK = now; + + if (_st_curr_time && now - _st_last_tset > 999000) { + _st_curr_time = time(NULL); + _st_last_tset = now; + } + + while (_ST_SLEEPQ != NULL) { + thread = _ST_SLEEPQ; + ST_ASSERT(thread->flags & _ST_FL_ON_SLEEPQ); + if (thread->due > now) + break; + _ST_DEL_SLEEPQ(thread); + + /* If thread is waiting on condition variable, set the time out flag */ + if (thread->state == _ST_ST_COND_WAIT) + thread->flags |= _ST_FL_TIMEDOUT; + + /* Make thread runnable */ + ST_ASSERT(!(thread->flags & _ST_FL_IDLE_THREAD)); + thread->state = _ST_ST_RUNNABLE; + _ST_ADD_RUNQ(thread); + } +} + + +void st_thread_interrupt(_st_thread_t *thread) +{ + /* If thread is already dead */ + if (thread->state == _ST_ST_ZOMBIE) + return; + + thread->flags |= _ST_FL_INTERRUPT; + + if (thread->state == _ST_ST_RUNNING || thread->state == _ST_ST_RUNNABLE) + return; + + if (thread->flags & _ST_FL_ON_SLEEPQ) + _ST_DEL_SLEEPQ(thread); + + /* Make thread runnable */ + thread->state = _ST_ST_RUNNABLE; + _ST_ADD_RUNQ(thread); +} + + +/* Merge from https://github.com/michaeltalyansky/state-threads/commit/cce736426c2320ffec7c9820df49ee7a18ae638c */ +#if defined(__arm__) && !defined(MD_USE_BUILTIN_SETJMP) && __GLIBC_MINOR__ >= 19 + extern unsigned long __pointer_chk_guard; + #define PTR_MANGLE(var) \ + (var) = (__typeof (var)) ((unsigned long) (var) ^ __pointer_chk_guard) + #define PTR_DEMANGLE(var) PTR_MANGLE (var) +#endif + + +_st_thread_t *st_thread_create(void *(*start)(void *arg), void *arg, int joinable, int stk_size) +{ + _st_thread_t *thread; + _st_stack_t *stack; + void **ptds; + char *sp; +#ifdef __ia64__ + char *bsp; +#endif + + /* Adjust stack size */ + if (stk_size == 0) + stk_size = ST_DEFAULT_STACK_SIZE; + stk_size = ((stk_size + _ST_PAGE_SIZE - 1) / _ST_PAGE_SIZE) * _ST_PAGE_SIZE; + stack = _st_stack_new(stk_size); + if (!stack) + return NULL; + + /* Allocate thread object and per-thread data off the stack */ +#if defined (MD_STACK_GROWS_DOWN) + sp = stack->stk_top; +#ifdef __ia64__ + /* + * The stack segment is split in the middle. The upper half is used + * as backing store for the register stack which grows upward. + * The lower half is used for the traditional memory stack which + * grows downward. Both stacks start in the middle and grow outward + * from each other. + */ + sp -= (stk_size >> 1); + bsp = sp; + /* Make register stack 64-byte aligned */ + if ((unsigned long)bsp & 0x3f) + bsp = bsp + (0x40 - ((unsigned long)bsp & 0x3f)); + stack->bsp = bsp + _ST_STACK_PAD_SIZE; +#endif + sp = sp - (ST_KEYS_MAX * sizeof(void *)); + ptds = (void **) sp; + sp = sp - sizeof(_st_thread_t); + thread = (_st_thread_t *) sp; + + /* Make stack 64-byte aligned */ + if ((unsigned long)sp & 0x3f) + sp = sp - ((unsigned long)sp & 0x3f); + stack->sp = sp - _ST_STACK_PAD_SIZE; +#elif defined (MD_STACK_GROWS_UP) + sp = stack->stk_bottom; + thread = (_st_thread_t *) sp; + sp = sp + sizeof(_st_thread_t); + ptds = (void **) sp; + sp = sp + (ST_KEYS_MAX * sizeof(void *)); + + /* Make stack 64-byte aligned */ + if ((unsigned long)sp & 0x3f) + sp = sp + (0x40 - ((unsigned long)sp & 0x3f)); + stack->sp = sp + _ST_STACK_PAD_SIZE; +#else +#error Unknown OS +#endif + + memset(thread, 0, sizeof(_st_thread_t)); + memset(ptds, 0, ST_KEYS_MAX * sizeof(void *)); + + /* Initialize thread */ + thread->private_data = ptds; + thread->stack = stack; + thread->start = start; + thread->arg = arg; + +#ifndef __ia64__ + /* Merge from https://github.com/michaeltalyansky/state-threads/commit/cce736426c2320ffec7c9820df49ee7a18ae638c */ + #if defined(__arm__) && !defined(MD_USE_BUILTIN_SETJMP) && __GLIBC_MINOR__ >= 19 + volatile void * lsp = PTR_MANGLE(stack->sp); + if (_setjmp ((thread)->context)) + _st_thread_main(); + (thread)->context[0].__jmpbuf[8] = (long) (lsp); + #else + _ST_INIT_CONTEXT(thread, stack->sp, _st_thread_main); + #endif +#else + _ST_INIT_CONTEXT(thread, stack->sp, stack->bsp, _st_thread_main); +#endif + + /* If thread is joinable, allocate a termination condition variable */ + if (joinable) { + thread->term = st_cond_new(); + if (thread->term == NULL) { + _st_stack_free(thread->stack); + return NULL; + } + } + + /* Make thread runnable */ + thread->state = _ST_ST_RUNNABLE; + _st_active_count++; + _ST_ADD_RUNQ(thread); +#ifdef DEBUG + _ST_ADD_THREADQ(thread); +#endif + + /* merge from https://github.com/toffaletti/state-threads/commit/7f57fc9acc05e657bca1223f1e5b9b1a45ed929b */ +#ifndef NVALGRIND + if (!(thread->flags & _ST_FL_PRIMORDIAL)) { + thread->stack->valgrind_stack_id = VALGRIND_STACK_REGISTER(thread->stack->stk_top, thread->stack->stk_bottom); + } +#endif + + return thread; +} + + +_st_thread_t *st_thread_self(void) +{ + return _ST_CURRENT_THREAD(); +} + + +#ifdef DEBUG +/* ARGSUSED */ +void _st_show_thread_stack(_st_thread_t *thread, const char *messg) +{ + +} + +/* To be set from debugger */ +int _st_iterate_threads_flag = 0; + +void _st_iterate_threads(void) +{ + static _st_thread_t *thread = NULL; + static jmp_buf orig_jb, save_jb; + _st_clist_t *q; + + if (!_st_iterate_threads_flag) { + if (thread) { + memcpy(thread->context, save_jb, sizeof(jmp_buf)); + MD_LONGJMP(orig_jb, 1); + } + return; + } + + if (thread) { + memcpy(thread->context, save_jb, sizeof(jmp_buf)); + _st_show_thread_stack(thread, NULL); + } else { + if (MD_SETJMP(orig_jb)) { + _st_iterate_threads_flag = 0; + thread = NULL; + _st_show_thread_stack(thread, "Iteration completed"); + return; + } + thread = _ST_CURRENT_THREAD(); + _st_show_thread_stack(thread, "Iteration started"); + } + + q = thread->tlink.next; + if (q == &_ST_THREADQ) + q = q->next; + ST_ASSERT(q != &_ST_THREADQ); + thread = _ST_THREAD_THREADQ_PTR(q); + if (thread == _ST_CURRENT_THREAD()) + MD_LONGJMP(orig_jb, 1); + memcpy(save_jb, thread->context, sizeof(jmp_buf)); + MD_LONGJMP(thread->context, 1); +} +#endif /* DEBUG */ + diff --git a/trunk/3rdparty/st-srs/st.pc.in b/trunk/3rdparty/st-srs/st.pc.in new file mode 100644 index 000000000..46c39ec52 --- /dev/null +++ b/trunk/3rdparty/st-srs/st.pc.in @@ -0,0 +1,10 @@ +prefix=@prefix@ +exec_prefix=${prefix} +libdir=${exec_prefix}/lib +includedir=${prefix}/include + +Name: libst +Description: State Thread Library +Version: @VERSION@ +Libs: -L${libdir} -lst +Cflags: -I${includedir} diff --git a/trunk/3rdparty/st-srs/st.spec b/trunk/3rdparty/st-srs/st.spec new file mode 100644 index 000000000..4914aa196 --- /dev/null +++ b/trunk/3rdparty/st-srs/st.spec @@ -0,0 +1,79 @@ +Summary: State Threads Library +Name: st +Version: 1.9 +Release: 1 +Copyright: MPL 1.2 or GPL 2+ +Packager: Wesley W. Terpstra +Source: http://prdownloads.sourceforge.net/state-threads/st-%{version}.tar.gz +Prefix: /usr +BuildRoot: /tmp/%{name}-%{version}-build +Group: Development/Libraries + +%description +The State Threads library has an interface similar to POSIX threads. + +However, the threads are actually all run in-process. This type of +threading allows for controlled schedualing points. It is highly useful +for designing robust and extremely scalable internet applications since +there is no resource contention and locking is generally unnecessary. + +It can be combined with traditional threading or multiple process +parallelism to take advantage of multiple processors. + +See: for further +information about how state threads improve performance. + +%package -n libst-devel +Summary: State Threads Library - Development Files +Group: Development/Libraries +Requires: libst1 + +%description -n libst-devel +Development headers and documentation for libst + +%package -n libst1 +Summary: State Threads Library - Shared Libs Major 1 +Group: System/Libraries + +%description -n libst1 +Shared libraries for running applications linked against api version 1. + +%prep +%setup -q + +%build +make CONFIG_GUESS_PATH=/usr/share/automake default-optimized + +%install +if [ -d ${RPM_BUILD_ROOT} ]; then rm -rf ${RPM_BUILD_ROOT}; fi + +mkdir -m 0755 -p ${RPM_BUILD_ROOT}/%{prefix}/lib/pkgconfig +mkdir -m 0755 -p ${RPM_BUILD_ROOT}/%{prefix}/include +mkdir -m 0755 -p ${RPM_BUILD_ROOT}/%{prefix}/share/doc/libst-devel +cp -a obj/libst.* ${RPM_BUILD_ROOT}/%{prefix}/lib +cp -a obj/st.h ${RPM_BUILD_ROOT}/%{prefix}/include +sed "s*@prefix@*%{prefix}*g" ${RPM_BUILD_ROOT}/%{prefix}/lib/pkgconfig/st.pc +cp -a docs/* ${RPM_BUILD_ROOT}/%{prefix}/share/doc/libst-devel/ +cp -a examples ${RPM_BUILD_ROOT}/%{prefix}/share/doc/libst-devel/ + +%post -n libst1 +/sbin/ldconfig %{prefix}/lib + +%files -n libst1 +%defattr(-,root,root) +%{prefix}/lib/lib*.so.* + +%files -n libst-devel +%defattr(-,root,root) +%{prefix}/include/* +%{prefix}/lib/lib*.a +%{prefix}/lib/lib*.so +%{prefix}/lib/pkgconfig/st.pc +%{prefix}/share/doc/libst-devel/* + +%clean +if [ -d ${RPM_BUILD_ROOT} ]; then rm -rf ${RPM_BUILD_ROOT}; fi + +%changelog +* Wed Dec 26 2001 Wesley W. Terpstra +- first rpms for libst-1.3.tar.gz diff --git a/trunk/3rdparty/st-srs/stk.c b/trunk/3rdparty/st-srs/stk.c new file mode 100644 index 000000000..3e681e595 --- /dev/null +++ b/trunk/3rdparty/st-srs/stk.c @@ -0,0 +1,173 @@ +/* + * The contents of this file are subject to the Mozilla Public + * License Version 1.1 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS + * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or + * implied. See the License for the specific language governing + * rights and limitations under the License. + * + * The Original Code is the Netscape Portable Runtime library. + * + * The Initial Developer of the Original Code is Netscape + * Communications Corporation. Portions created by Netscape are + * Copyright (C) 1994-2000 Netscape Communications Corporation. All + * Rights Reserved. + * + * Contributor(s): Silicon Graphics, Inc. + * + * Portions created by SGI are Copyright (C) 2000-2001 Silicon + * Graphics, Inc. All Rights Reserved. + * + * Alternatively, the contents of this file may be used under the + * terms of the GNU General Public License Version 2 or later (the + * "GPL"), in which case the provisions of the GPL are applicable + * instead of those above. If you wish to allow use of your + * version of this file only under the terms of the GPL and not to + * allow others to use your version of this file under the MPL, + * indicate your decision by deleting the provisions above and + * replace them with the notice and other provisions required by + * the GPL. If you do not delete the provisions above, a recipient + * may use your version of this file under either the MPL or the + * GPL. + */ + +/* + * This file is derived directly from Netscape Communications Corporation, + * and consists of extensive modifications made during the year(s) 1999-2000. + */ + +#include +#include +#include +#include +#include +#include "common.h" + + +/* How much space to leave between the stacks, at each end */ +#define REDZONE _ST_PAGE_SIZE + +_st_clist_t _st_free_stacks = ST_INIT_STATIC_CLIST(&_st_free_stacks); +int _st_num_free_stacks = 0; +int _st_randomize_stacks = 0; + +static char *_st_new_stk_segment(int size); + +_st_stack_t *_st_stack_new(int stack_size) +{ + _st_clist_t *qp; + _st_stack_t *ts; + int extra; + + for (qp = _st_free_stacks.next; qp != &_st_free_stacks; qp = qp->next) { + ts = _ST_THREAD_STACK_PTR(qp); + if (ts->stk_size >= stack_size) { + /* Found a stack that is big enough */ + ST_REMOVE_LINK(&ts->links); + _st_num_free_stacks--; + ts->links.next = NULL; + ts->links.prev = NULL; + return ts; + } + } + + /* Make a new thread stack object. */ + if ((ts = (_st_stack_t *)calloc(1, sizeof(_st_stack_t))) == NULL) + return NULL; + extra = _st_randomize_stacks ? _ST_PAGE_SIZE : 0; + ts->vaddr_size = stack_size + 2*REDZONE + extra; + ts->vaddr = _st_new_stk_segment(ts->vaddr_size); + if (!ts->vaddr) { + free(ts); + return NULL; + } + ts->stk_size = stack_size; + ts->stk_bottom = ts->vaddr + REDZONE; + ts->stk_top = ts->stk_bottom + stack_size; + +#ifdef DEBUG + mprotect(ts->vaddr, REDZONE, PROT_NONE); + mprotect(ts->stk_top + extra, REDZONE, PROT_NONE); +#endif + + if (extra) { + long offset = (random() % extra) & ~0xf; + + ts->stk_bottom += offset; + ts->stk_top += offset; + } + + return ts; +} + + +/* + * Free the stack for the current thread + */ +void _st_stack_free(_st_stack_t *ts) +{ + if (!ts) + return; + + /* Put the stack on the free list */ + ST_APPEND_LINK(&ts->links, _st_free_stacks.prev); + _st_num_free_stacks++; +} + + +static char *_st_new_stk_segment(int size) +{ +#ifdef MALLOC_STACK + void *vaddr = malloc(size); +#else + static int zero_fd = -1; + int mmap_flags = MAP_PRIVATE; + void *vaddr; + +#if defined (MD_USE_SYSV_ANON_MMAP) + if (zero_fd < 0) { + if ((zero_fd = open("/dev/zero", O_RDWR, 0)) < 0) + return NULL; + fcntl(zero_fd, F_SETFD, FD_CLOEXEC); + } +#elif defined (MD_USE_BSD_ANON_MMAP) + mmap_flags |= MAP_ANON; +#else +#error Unknown OS +#endif + + vaddr = mmap(NULL, size, PROT_READ | PROT_WRITE, mmap_flags, zero_fd, 0); + if (vaddr == (void *)MAP_FAILED) + return NULL; + +#endif /* MALLOC_STACK */ + + return (char *)vaddr; +} + + +/* Not used */ +#if 0 +void _st_delete_stk_segment(char *vaddr, int size) +{ +#ifdef MALLOC_STACK + free(vaddr); +#else + (void) munmap(vaddr, size); +#endif +} +#endif + +int st_randomize_stacks(int on) +{ + int wason = _st_randomize_stacks; + + _st_randomize_stacks = on; + if (on) + srandom((unsigned int) st_utime()); + + return wason; +} diff --git a/trunk/3rdparty/st-srs/sync.c b/trunk/3rdparty/st-srs/sync.c new file mode 100644 index 000000000..907fdfac3 --- /dev/null +++ b/trunk/3rdparty/st-srs/sync.c @@ -0,0 +1,368 @@ +/* + * The contents of this file are subject to the Mozilla Public + * License Version 1.1 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS + * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or + * implied. See the License for the specific language governing + * rights and limitations under the License. + * + * The Original Code is the Netscape Portable Runtime library. + * + * The Initial Developer of the Original Code is Netscape + * Communications Corporation. Portions created by Netscape are + * Copyright (C) 1994-2000 Netscape Communications Corporation. All + * Rights Reserved. + * + * Contributor(s): Silicon Graphics, Inc. + * + * Portions created by SGI are Copyright (C) 2000-2001 Silicon + * Graphics, Inc. All Rights Reserved. + * + * Alternatively, the contents of this file may be used under the + * terms of the GNU General Public License Version 2 or later (the + * "GPL"), in which case the provisions of the GPL are applicable + * instead of those above. If you wish to allow use of your + * version of this file only under the terms of the GPL and not to + * allow others to use your version of this file under the MPL, + * indicate your decision by deleting the provisions above and + * replace them with the notice and other provisions required by + * the GPL. If you do not delete the provisions above, a recipient + * may use your version of this file under either the MPL or the + * GPL. + */ + +/* + * This file is derived directly from Netscape Communications Corporation, + * and consists of extensive modifications made during the year(s) 1999-2000. + */ + +#include +#include +#include +#include "common.h" + + +extern time_t _st_curr_time; +extern st_utime_t _st_last_tset; +extern int _st_active_count; + +static st_utime_t (*_st_utime)(void) = NULL; + + +/***************************************** + * Time functions + */ + +st_utime_t st_utime(void) +{ + if (_st_utime == NULL) { +#ifdef MD_GET_UTIME + MD_GET_UTIME(); +#else +#error Unknown OS +#endif + } + + return (*_st_utime)(); +} + + +int st_set_utime_function(st_utime_t (*func)(void)) +{ + if (_st_active_count) { + errno = EINVAL; + return -1; + } + + _st_utime = func; + + return 0; +} + + +st_utime_t st_utime_last_clock(void) +{ + return _ST_LAST_CLOCK; +} + + +int st_timecache_set(int on) +{ + int wason = (_st_curr_time) ? 1 : 0; + + if (on) { + _st_curr_time = time(NULL); + _st_last_tset = st_utime(); + } else + _st_curr_time = 0; + + return wason; +} + + +time_t st_time(void) +{ + if (_st_curr_time) + return _st_curr_time; + + return time(NULL); +} + + +int st_usleep(st_utime_t usecs) +{ + _st_thread_t *me = _ST_CURRENT_THREAD(); + + if (me->flags & _ST_FL_INTERRUPT) { + me->flags &= ~_ST_FL_INTERRUPT; + errno = EINTR; + return -1; + } + + if (usecs != ST_UTIME_NO_TIMEOUT) { + me->state = _ST_ST_SLEEPING; + _ST_ADD_SLEEPQ(me, usecs); + } else + me->state = _ST_ST_SUSPENDED; + + _ST_SWITCH_CONTEXT(me); + + if (me->flags & _ST_FL_INTERRUPT) { + me->flags &= ~_ST_FL_INTERRUPT; + errno = EINTR; + return -1; + } + + return 0; +} + + +int st_sleep(int secs) +{ + return st_usleep((secs >= 0) ? secs * (st_utime_t) 1000000LL : ST_UTIME_NO_TIMEOUT); +} + + +/***************************************** + * Condition variable functions + */ + +_st_cond_t *st_cond_new(void) +{ + _st_cond_t *cvar; + + cvar = (_st_cond_t *) calloc(1, sizeof(_st_cond_t)); + if (cvar) { + ST_INIT_CLIST(&cvar->wait_q); + } + + return cvar; +} + + +int st_cond_destroy(_st_cond_t *cvar) +{ + if (cvar->wait_q.next != &cvar->wait_q) { + errno = EBUSY; + return -1; + } + + free(cvar); + + return 0; +} + + +int st_cond_timedwait(_st_cond_t *cvar, st_utime_t timeout) +{ + _st_thread_t *me = _ST_CURRENT_THREAD(); + int rv; + + if (me->flags & _ST_FL_INTERRUPT) { + me->flags &= ~_ST_FL_INTERRUPT; + errno = EINTR; + return -1; + } + + /* Put caller thread on the condition variable's wait queue */ + me->state = _ST_ST_COND_WAIT; + ST_APPEND_LINK(&me->wait_links, &cvar->wait_q); + + if (timeout != ST_UTIME_NO_TIMEOUT) + _ST_ADD_SLEEPQ(me, timeout); + + _ST_SWITCH_CONTEXT(me); + + ST_REMOVE_LINK(&me->wait_links); + rv = 0; + + if (me->flags & _ST_FL_TIMEDOUT) { + me->flags &= ~_ST_FL_TIMEDOUT; + errno = ETIME; + rv = -1; + } + if (me->flags & _ST_FL_INTERRUPT) { + me->flags &= ~_ST_FL_INTERRUPT; + errno = EINTR; + rv = -1; + } + + return rv; +} + + +int st_cond_wait(_st_cond_t *cvar) +{ + return st_cond_timedwait(cvar, ST_UTIME_NO_TIMEOUT); +} + + +static int _st_cond_signal(_st_cond_t *cvar, int broadcast) +{ + _st_thread_t *thread; + _st_clist_t *q; + + for (q = cvar->wait_q.next; q != &cvar->wait_q; q = q->next) { + thread = _ST_THREAD_WAITQ_PTR(q); + if (thread->state == _ST_ST_COND_WAIT) { + if (thread->flags & _ST_FL_ON_SLEEPQ) + _ST_DEL_SLEEPQ(thread); + + /* Make thread runnable */ + thread->state = _ST_ST_RUNNABLE; + _ST_ADD_RUNQ(thread); + if (!broadcast) + break; + } + } + + return 0; +} + + +int st_cond_signal(_st_cond_t *cvar) +{ + return _st_cond_signal(cvar, 0); +} + + +int st_cond_broadcast(_st_cond_t *cvar) +{ + return _st_cond_signal(cvar, 1); +} + + +/***************************************** + * Mutex functions + */ + +_st_mutex_t *st_mutex_new(void) +{ + _st_mutex_t *lock; + + lock = (_st_mutex_t *) calloc(1, sizeof(_st_mutex_t)); + if (lock) { + ST_INIT_CLIST(&lock->wait_q); + lock->owner = NULL; + } + + return lock; +} + + +int st_mutex_destroy(_st_mutex_t *lock) +{ + if (lock->owner != NULL || lock->wait_q.next != &lock->wait_q) { + errno = EBUSY; + return -1; + } + + free(lock); + + return 0; +} + + +int st_mutex_lock(_st_mutex_t *lock) +{ + _st_thread_t *me = _ST_CURRENT_THREAD(); + + if (me->flags & _ST_FL_INTERRUPT) { + me->flags &= ~_ST_FL_INTERRUPT; + errno = EINTR; + return -1; + } + + if (lock->owner == NULL) { + /* Got the mutex */ + lock->owner = me; + return 0; + } + + if (lock->owner == me) { + errno = EDEADLK; + return -1; + } + + /* Put caller thread on the mutex's wait queue */ + me->state = _ST_ST_LOCK_WAIT; + ST_APPEND_LINK(&me->wait_links, &lock->wait_q); + + _ST_SWITCH_CONTEXT(me); + + ST_REMOVE_LINK(&me->wait_links); + + if ((me->flags & _ST_FL_INTERRUPT) && lock->owner != me) { + me->flags &= ~_ST_FL_INTERRUPT; + errno = EINTR; + return -1; + } + + return 0; +} + + +int st_mutex_unlock(_st_mutex_t *lock) +{ + _st_thread_t *thread; + _st_clist_t *q; + + if (lock->owner != _ST_CURRENT_THREAD()) { + errno = EPERM; + return -1; + } + + for (q = lock->wait_q.next; q != &lock->wait_q; q = q->next) { + thread = _ST_THREAD_WAITQ_PTR(q); + if (thread->state == _ST_ST_LOCK_WAIT) { + lock->owner = thread; + /* Make thread runnable */ + thread->state = _ST_ST_RUNNABLE; + _ST_ADD_RUNQ(thread); + return 0; + } + } + + /* No threads waiting on this mutex */ + lock->owner = NULL; + + return 0; +} + + +int st_mutex_trylock(_st_mutex_t *lock) +{ + if (lock->owner != NULL) { + errno = EBUSY; + return -1; + } + + /* Got the mutex */ + lock->owner = _ST_CURRENT_THREAD(); + + return 0; +} + diff --git a/trunk/3rdparty/x264-snapshot-20181116-2245.zip b/trunk/3rdparty/x264-snapshot-20181116-2245.zip deleted file mode 100644 index 295c3a54c..000000000 Binary files a/trunk/3rdparty/x264-snapshot-20181116-2245.zip and /dev/null differ diff --git a/trunk/3rdparty/yasm-1.2.0.zip b/trunk/3rdparty/yasm-1.2.0.zip deleted file mode 100644 index 93b5e384f..000000000 Binary files a/trunk/3rdparty/yasm-1.2.0.zip and /dev/null differ diff --git a/trunk/Dockerfile b/trunk/Dockerfile new file mode 100644 index 000000000..7a3e32d34 --- /dev/null +++ b/trunk/Dockerfile @@ -0,0 +1,36 @@ +FROM ossrs/srs:dev AS build + +# Install depends tools. +RUN yum install -y gcc make gcc-c++ patch unzip perl git + +# Build and install SRS. +COPY . /srs +WORKDIR /srs/trunk +RUN ./configure --jobs=2 && make -j2 && make install + +# All config files for SRS. +RUN cp -R conf /usr/local/srs/conf +# The default index.html and srs-console. +RUN cp research/api-server/static-dir/index.html /usr/local/srs/objs/nginx/html/ +RUN cp research/api-server/static-dir/favicon.ico /usr/local/srs/objs/nginx/html/ +RUN cp research/players/crossdomain.xml /usr/local/srs/objs/nginx/html/ +RUN cp -R research/console /usr/local/srs/objs/nginx/html/ +RUN cp -R research/players /usr/local/srs/objs/nginx/html/ +#RUN cp -R 3rdparty/signaling/www/demos /usr/local/srs/objs/nginx/html/ + +############################################################ +# dist +############################################################ +FROM centos:7 AS dist + +# Expose ports for live streaming +EXPOSE 1935 1985 8080 + +# FFMPEG 4.1 +COPY --from=build /usr/local/bin/ffmpeg /usr/local/srs/objs/ffmpeg/bin/ffmpeg +# SRS binary, config files and srs-console. +COPY --from=build /usr/local/srs /usr/local/srs + +# Default workdir and command. +WORKDIR /usr/local/srs +CMD ["./objs/srs", "-c", "conf/srs.conf"] diff --git a/trunk/Dockerfile.cov b/trunk/Dockerfile.cov new file mode 100644 index 000000000..a898cb969 --- /dev/null +++ b/trunk/Dockerfile.cov @@ -0,0 +1,9 @@ +FROM ossrs/srs:dev + +# Install depends tools. +RUN yum install -y gcc make gcc-c++ patch unzip perl git + +# Build and install SRS. +COPY . /srs +WORKDIR /srs/trunk +RUN ./configure --with-utest --gcov --jobs=2 && make -j2 diff --git a/trunk/Dockerfile.test b/trunk/Dockerfile.test new file mode 100644 index 000000000..de1eb633e --- /dev/null +++ b/trunk/Dockerfile.test @@ -0,0 +1,9 @@ +FROM ossrs/srs:dev + +# Install depends tools. +RUN yum install -y gcc make gcc-c++ patch unzip perl git + +# Build and install SRS. +COPY . /srs +WORKDIR /srs/trunk +RUN ./configure --with-utest --jobs=2 && make -j2 diff --git a/trunk/auto/apps.sh b/trunk/auto/apps.sh index 6b9fbf88f..aaccac70d 100755 --- a/trunk/auto/apps.sh +++ b/trunk/auto/apps.sh @@ -17,12 +17,14 @@ FILE=${SRS_OBJS}/${SRS_MAKEFILE} APP_TARGET="${SRS_OBJS_DIR}/${APP_NAME}" -echo "generate app ${APP_NAME} depends..."; +echo "Generating app ${APP_NAME} depends."; echo "# build ${APP_TARGET}" >> ${FILE} # generate the binary depends, for example: # srs: objs/srs echo "${BUILD_KEY}: ${APP_TARGET}" >> ${FILE} +echo "" >> ${FILE} + # the link commands, for example: # objs/srs: objs/src/core/srs_core.o echo -n "${APP_TARGET}: " >> ${FILE} @@ -52,7 +54,7 @@ for item in ${MODULE_OBJS[*]}; do done echo "" >> ${FILE} -echo "generate app ${APP_NAME} link..."; +echo "Generating app ${APP_NAME} link."; # genereate the actual link command, for example: # $(LINK) -o objs/srs objs/src/core/srs_core.o -ldl @@ -88,5 +90,6 @@ done # link options. echo -n "${LINK_OPTIONS}" >> ${FILE} echo "" >> ${FILE} +echo "" >> ${FILE} -echo -n "generate app ${APP_NAME} ok"; echo '!'; +echo -n "Generate app ${APP_NAME} ok"; echo '!'; diff --git a/trunk/auto/auto_headers.sh b/trunk/auto/auto_headers.sh index 11949e151..3983433a9 100755 --- a/trunk/auto/auto_headers.sh +++ b/trunk/auto/auto_headers.sh @@ -11,6 +11,7 @@ echo "#ifndef SRS_AUTO_HEADER_HPP" >> $SRS_AUTO_HEADERS_H echo "#define SRS_AUTO_HEADER_HPP" >> $SRS_AUTO_HEADERS_H echo "" >> $SRS_AUTO_HEADERS_H +echo "#define SRS_AUTO_PACKAGER \"${SRS_AUTO_PACKAGER}\"" >> $SRS_AUTO_HEADERS_H echo "#define SRS_AUTO_BUILD_TS \"`date +%s`\"" >> $SRS_AUTO_HEADERS_H echo "#define SRS_AUTO_BUILD_DATE \"`date \"+%Y-%m-%d %H:%M:%S\"`\"" >> $SRS_AUTO_HEADERS_H echo "#define SRS_AUTO_UNAME \"`uname -a`\"" >> $SRS_AUTO_HEADERS_H @@ -18,24 +19,36 @@ echo "#define SRS_AUTO_USER_CONFIGURE \"${SRS_AUTO_USER_CONFIGURE}\"" >> $SRS_AU echo "#define SRS_AUTO_CONFIGURE \"${SRS_AUTO_CONFIGURE}\"" >> $SRS_AUTO_HEADERS_H echo "" >> $SRS_AUTO_HEADERS_H +function srs_define_macro() +{ + macro=$1 && file=$2 + echo "#define $macro" >> $file + echo "#define ${macro}_BOOL true" >> $file +} + +function srs_define_macro_value() +{ + macro=$1 && value=$2 && file=$3 + echo "#define $macro $value" >> $file + echo "#define ${macro}_BOOL true" >> $file +} + +function srs_undefine_macro() +{ + macro=$1 && file=$2 + echo "#undef $macro" >> $file + echo "#define ${macro}_BOOL false" >> $file +} + # export the preset. -if [ $SRS_OSX = YES ]; then - echo "#define SRS_OSX" >> $SRS_AUTO_HEADERS_H -fi if [ $SRS_X86_X64 = YES ]; then - echo "#define SRS_X86_X64" >> $SRS_AUTO_HEADERS_H -fi -if [ $SRS_ARM_UBUNTU12 = YES ]; then - echo "#define SRS_ARM_UBUNTU12" >> $SRS_AUTO_HEADERS_H -fi -if [ $SRS_MIPS_UBUNTU12 = YES ]; then - echo "#define SRS_MIPS_UBUNTU12" >> $SRS_AUTO_HEADERS_H + srs_define_macro "SRS_X86_X64" $SRS_AUTO_HEADERS_H fi if [ $SRS_PI = YES ]; then - echo "#define SRS_PI" >> $SRS_AUTO_HEADERS_H + srs_define_macro "SRS_PI" $SRS_AUTO_HEADERS_H fi if [ $SRS_CUBIE = YES ]; then - echo "#define SRS_CUBIE" >> $SRS_AUTO_HEADERS_H + srs_define_macro "SRS_CUBIE" $SRS_AUTO_HEADERS_H fi if [ $SRS_EXPORT_LIBRTMP_PROJECT != NO ]; then echo "#define SRS_EXPORT_LIBRTMP" >> $SRS_AUTO_HEADERS_H @@ -48,176 +61,87 @@ echo "" >> $SRS_AUTO_HEADERS_H ##################################################################################### # generate auto headers file, depends on the finished of options.sh ##################################################################################### -# write to source file -if [ $SRS_CROSS_BUILD = YES ]; then - echo "cc=$SrsArmCC gcc=$SrsArmGCC g++=$SrsArmCXX ar=$SrsArmAR ld=$SrsArmLD randlib=$SrsArmRANDLIB" - echo "#define SRS_AUTO_EMBEDED_TOOL_CHAIN \"cc=$SrsArmCC gcc=$SrsArmGCC g++=$SrsArmCXX ar=$SrsArmAR ld=$SrsArmLD randlib=$SrsArmRANDLIB\"" >> $SRS_AUTO_HEADERS_H -else - echo "#define SRS_AUTO_EMBEDED_TOOL_CHAIN \"normal x86/x64 gcc\"" >> $SRS_AUTO_HEADERS_H -fi -echo "" >> $SRS_AUTO_HEADERS_H - # auto headers in depends. -if [ $SRS_HTTP_CORE = YES ]; then - echo "#define SRS_AUTO_HTTP_CORE" >> $SRS_AUTO_HEADERS_H -else - echo "#undef SRS_AUTO_HTTP_CORE" >> $SRS_AUTO_HEADERS_H -fi - -if [ $SRS_HTTP_SERVER = YES ]; then - echo "#define SRS_AUTO_HTTP_SERVER" >> $SRS_AUTO_HEADERS_H -else - echo "#undef SRS_AUTO_HTTP_SERVER" >> $SRS_AUTO_HEADERS_H -fi - -if [ $SRS_STREAM_CASTER = YES ]; then - echo "#define SRS_AUTO_STREAM_CASTER" >> $SRS_AUTO_HEADERS_H -else - echo "#undef SRS_AUTO_STREAM_CASTER" >> $SRS_AUTO_HEADERS_H -fi - -if [ $SRS_HTTP_API = YES ]; then - echo "#define SRS_AUTO_HTTP_API" >> $SRS_AUTO_HEADERS_H -else - echo "#undef SRS_AUTO_HTTP_API" >> $SRS_AUTO_HEADERS_H -fi - -if [ $SRS_NGINX = YES ]; then - echo "#define SRS_AUTO_NGINX" >> $SRS_AUTO_HEADERS_H -else - echo "#undef SRS_AUTO_NGINX" >> $SRS_AUTO_HEADERS_H -fi - -if [ $SRS_DVR = YES ]; then - echo "#define SRS_AUTO_DVR" >> $SRS_AUTO_HEADERS_H -else - echo "#undef SRS_AUTO_DVR" >> $SRS_AUTO_HEADERS_H -fi - -if [ $SRS_HLS = YES ]; then - echo "#define SRS_AUTO_HLS" >> $SRS_AUTO_HEADERS_H -else - echo "#undef SRS_AUTO_HLS" >> $SRS_AUTO_HEADERS_H -fi - if [ $SRS_HDS = YES ]; then - echo "#define SRS_AUTO_HDS" >> $SRS_AUTO_HEADERS_H + srs_define_macro "SRS_AUTO_HDS" $SRS_AUTO_HEADERS_H else - echo "#undef SRS_AUTO_HDS" >> $SRS_AUTO_HEADERS_H -fi - -if [ $SRS_HTTP_CALLBACK = YES ]; then - echo "#define SRS_AUTO_HTTP_CALLBACK" >> $SRS_AUTO_HEADERS_H -else - echo "#undef SRS_AUTO_HTTP_CALLBACK" >> $SRS_AUTO_HEADERS_H -fi - -if [ $SRS_SSL = YES ]; then - echo "#define SRS_AUTO_SSL" >> $SRS_AUTO_HEADERS_H -else - echo "#undef SRS_AUTO_SSL" >> $SRS_AUTO_HEADERS_H + srs_undefine_macro "SRS_AUTO_HDS" $SRS_AUTO_HEADERS_H fi if [ $SRS_MEM_WATCH = YES ]; then - echo "#define SRS_AUTO_MEM_WATCH" >> $SRS_AUTO_HEADERS_H + srs_define_macro "SRS_AUTO_MEM_WATCH" $SRS_AUTO_HEADERS_H else - echo "#undef SRS_AUTO_MEM_WATCH" >> $SRS_AUTO_HEADERS_H + srs_undefine_macro "SRS_AUTO_MEM_WATCH" $SRS_AUTO_HEADERS_H fi -# whether compile ffmpeg tool -if [ $SRS_FFMPEG_TOOL = YES ]; then - echo "#define SRS_AUTO_FFMPEG_TOOL" >> $SRS_AUTO_HEADERS_H +if [ $SRS_UTEST = YES ]; then + srs_define_macro "SRS_AUTO_UTEST" $SRS_AUTO_HEADERS_H else - echo "#undef SRS_AUTO_FFMPEG_TOOL" >> $SRS_AUTO_HEADERS_H + srs_undefine_macro "SRS_AUTO_UTEST" $SRS_AUTO_HEADERS_H fi # whatever the FFMPEG tools, if transcode and ingest specified, # srs always compile the FFMPEG tool stub which used to start the FFMPEG process. if [ $SRS_FFMPEG_STUB = YES ]; then - echo "#define SRS_AUTO_FFMPEG_STUB" >> $SRS_AUTO_HEADERS_H + srs_define_macro "SRS_AUTO_FFMPEG_STUB" $SRS_AUTO_HEADERS_H else - echo "#undef SRS_AUTO_FFMPEG_STUB" >> $SRS_AUTO_HEADERS_H -fi - -if [ $SRS_TRANSCODE = YES ]; then - echo "#define SRS_AUTO_TRANSCODE" >> $SRS_AUTO_HEADERS_H -else - echo "#undef SRS_AUTO_TRANSCODE" >> $SRS_AUTO_HEADERS_H -fi - -if [ $SRS_INGEST = YES ]; then - echo "#define SRS_AUTO_INGEST" >> $SRS_AUTO_HEADERS_H -else - echo "#undef SRS_AUTO_INGEST" >> $SRS_AUTO_HEADERS_H -fi - -# for statistic. -if [ $SRS_STAT = YES ]; then - echo "#define SRS_AUTO_STAT" >> $SRS_AUTO_HEADERS_H -else - echo "#undef SRS_AUTO_STAT" >> $SRS_AUTO_HEADERS_H + srs_undefine_macro "SRS_AUTO_FFMPEG_STUB" $SRS_AUTO_HEADERS_H fi if [ $SRS_GPERF = YES ]; then - echo "#define SRS_AUTO_GPERF" >> $SRS_AUTO_HEADERS_H + srs_define_macro "SRS_AUTO_GPERF" $SRS_AUTO_HEADERS_H else - echo "#undef SRS_AUTO_GPERF" >> $SRS_AUTO_HEADERS_H + srs_undefine_macro "SRS_AUTO_GPERF" $SRS_AUTO_HEADERS_H fi if [ $SRS_GPERF_MC = YES ]; then - echo "#define SRS_AUTO_GPERF_MC" >> $SRS_AUTO_HEADERS_H + srs_define_macro "SRS_AUTO_GPERF_MC" $SRS_AUTO_HEADERS_H else - echo "#undef SRS_AUTO_GPERF_MC" >> $SRS_AUTO_HEADERS_H + srs_undefine_macro "SRS_AUTO_GPERF_MC" $SRS_AUTO_HEADERS_H +fi +if [ $SRS_GPERF_MD = YES ]; then + srs_define_macro "SRS_AUTO_GPERF_MD" $SRS_AUTO_HEADERS_H +else + srs_undefine_macro "SRS_AUTO_GPERF_MD" $SRS_AUTO_HEADERS_H fi if [ $SRS_GPERF_MP = YES ]; then - echo "#define SRS_AUTO_GPERF_MP" >> $SRS_AUTO_HEADERS_H + srs_define_macro "SRS_AUTO_GPERF_MP" $SRS_AUTO_HEADERS_H else - echo "#undef SRS_AUTO_GPERF_MP" >> $SRS_AUTO_HEADERS_H + srs_undefine_macro "SRS_AUTO_GPERF_MP" $SRS_AUTO_HEADERS_H fi if [ $SRS_GPERF_CP = YES ]; then - echo "#define SRS_AUTO_GPERF_CP" >> $SRS_AUTO_HEADERS_H + srs_define_macro "SRS_AUTO_GPERF_CP" $SRS_AUTO_HEADERS_H else - echo "#undef SRS_AUTO_GPERF_CP" >> $SRS_AUTO_HEADERS_H + srs_undefine_macro "SRS_AUTO_GPERF_CP" $SRS_AUTO_HEADERS_H fi ##################################################################################### # for embeded. ##################################################################################### -if [ $SRS_CROSS_BUILD = YES ]; then - echo "#define SRS_AUTO_EMBEDED_CPU" >> $SRS_AUTO_HEADERS_H -else - echo "#undef SRS_AUTO_EMBEDED_CPU" >> $SRS_AUTO_HEADERS_H -fi - -# arm -if [ $SRS_ARM_UBUNTU12 = YES ]; then - echo "#define SRS_AUTO_ARM_UBUNTU12" >> $SRS_AUTO_HEADERS_H -else - echo "#undef SRS_AUTO_ARM_UBUNTU12" >> $SRS_AUTO_HEADERS_H -fi - -# mips -if [ $SRS_MIPS_UBUNTU12 = YES ]; then - echo "#define SRS_AUTO_MIPS_UBUNTU12" >> $SRS_AUTO_HEADERS_H -else - echo "#undef SRS_AUTO_MIPS_UBUNTU12" >> $SRS_AUTO_HEADERS_H -fi - -echo "" >> $SRS_AUTO_HEADERS_H # for log level compile settings if [ $SRS_LOG_VERBOSE = YES ]; then - echo "#define SRS_AUTO_VERBOSE" >> $SRS_AUTO_HEADERS_H + srs_define_macro "SRS_AUTO_VERBOSE" $SRS_AUTO_HEADERS_H else - echo "#undef SRS_AUTO_VERBOSE" >> $SRS_AUTO_HEADERS_H + srs_undefine_macro "SRS_AUTO_VERBOSE" $SRS_AUTO_HEADERS_H fi if [ $SRS_LOG_INFO = YES ]; then - echo "#define SRS_AUTO_INFO" >> $SRS_AUTO_HEADERS_H + srs_define_macro "SRS_AUTO_INFO" $SRS_AUTO_HEADERS_H else - echo "#undef SRS_AUTO_INFO" >> $SRS_AUTO_HEADERS_H + srs_undefine_macro "SRS_AUTO_INFO" $SRS_AUTO_HEADERS_H fi if [ $SRS_LOG_TRACE = YES ]; then - echo "#define SRS_AUTO_TRACE" >> $SRS_AUTO_HEADERS_H + srs_define_macro "SRS_AUTO_TRACE" $SRS_AUTO_HEADERS_H else - echo "#undef SRS_AUTO_TRACE" >> $SRS_AUTO_HEADERS_H + srs_undefine_macro "SRS_AUTO_TRACE" $SRS_AUTO_HEADERS_H +fi +if [ $SRS_CROSS_BUILD = YES ]; then + srs_define_macro "SRS_AUTO_CROSSBUILD" $SRS_AUTO_HEADERS_H +else + srs_undefine_macro "SRS_AUTO_CROSSBUILD" $SRS_AUTO_HEADERS_H +fi +if [ $SRS_OSX = YES ]; then + srs_define_macro "SRS_AUTO_OSX" $SRS_AUTO_HEADERS_H +else + srs_undefine_macro "SRS_AUTO_OSX" $SRS_AUTO_HEADERS_H fi # prefix @@ -229,17 +153,23 @@ echo "" >> $SRS_AUTO_HEADERS_H ##################################################################################### # generated the contributors from AUTHORS.txt ##################################################################################### -SRS_CONSTRIBUTORS=`cat ../AUTHORS.txt|grep "*"|awk '{print $2}'` -echo "#define SRS_AUTO_CONSTRIBUTORS \"\\" >> $SRS_AUTO_HEADERS_H -for CONTRIBUTOR in $SRS_CONSTRIBUTORS; do - echo "${CONTRIBUTOR} \\" >> $SRS_AUTO_HEADERS_H -done -echo "\"" >> $SRS_AUTO_HEADERS_H +if [[ -f ../AUTHORS.txt ]]; then + SRS_CONSTRIBUTORS=`cat ../AUTHORS.txt|grep "*"|awk '{print $2}'` + echo "#define SRS_AUTO_CONSTRIBUTORS \"\\" >> $SRS_AUTO_HEADERS_H + for CONTRIBUTOR in $SRS_CONSTRIBUTORS; do + echo "${CONTRIBUTOR} \\" >> $SRS_AUTO_HEADERS_H + done + echo "\"" >> $SRS_AUTO_HEADERS_H +else + echo "#define SRS_AUTO_CONSTRIBUTORS \"ossrs\"" >> $SRS_AUTO_HEADERS_H +fi # new empty line to auto headers file. echo "" >> $SRS_AUTO_HEADERS_H +##################################################################################### # auto header EOF. +##################################################################################### echo "#endif" >> $SRS_AUTO_HEADERS_H echo "" >> $SRS_AUTO_HEADERS_H diff --git a/trunk/auto/build_ffmpeg.sh b/trunk/auto/build_ffmpeg.sh index 23a914d94..46fe24833 100755 --- a/trunk/auto/build_ffmpeg.sh +++ b/trunk/auto/build_ffmpeg.sh @@ -76,7 +76,6 @@ else cd $ff_current_dir && rm -rf x264-snapshot-20181116-2245 && unzip -q ${ff_src_dir}/x264-snapshot-20181116-2245.zip && cd x264-snapshot-20181116-2245 && -# chmod +w configure && patch -p0 <../../../3rdparty/patches/5.x264.osx.gcc.patch && ./configure --prefix=${ff_release_dir} --disable-opencl --bit-depth=all \ --enable-static --disable-avs --disable-swscale --disable-lavf \ --disable-ffms --disable-gpac --disable-cli && diff --git a/trunk/auto/coverage.sh b/trunk/auto/coverage.sh new file mode 100644 index 000000000..25f7041d5 --- /dev/null +++ b/trunk/auto/coverage.sh @@ -0,0 +1,45 @@ +#!/bin/bash + +# In .circleci/config.yml, generate *.gcno with +# ./configure --gcov --with-utest --without-research --without-librtmp && make +# and generate *.gcda by +# ./objs/srs_utest + +# Workdir is objs/cover. +workdir=`pwd`/objs/cover + +# Create trunk under workdir. +mkdir -p $workdir && cd $workdir +ret=$?; if [[ $ret -ne 0 ]]; then echo "Enter workdir failed, ret=$ret"; exit $ret; fi + +CODECOV_ARGS="" +if [[ $SRS_PROJECT != '' ]]; then + # -R root dir Used when not in git/hg project to identify project root directory + # -p dir Project root directory. Also used when preparing gcov + CODECOV_ARGS="$CODECOV_ARGS -R $SRS_PROJECT -p $SRS_PROJECT" +fi +if [[ $SRS_BRANCH != '' ]]; then + # -B branch Specify the branch name + CODECOV_ARGS="$CODECOV_ARGS -B $SRS_BRANCH" +fi +if [[ $SRS_SHA != '' ]]; then + # -C sha Specify the commit sha + CODECOV_ARGS="$CODECOV_ARGS -C $SRS_SHA" +fi +if [[ $SRS_PR != '' ]]; then + # -P pr Specify the pull request number + CODECOV_ARGS="$CODECOV_ARGS -P $SRS_PR" +fi + +# Upload report with *.gcov +# Remark: The file codecov.yml is not neccessary. It literally depends on git. +# Note: The right path is like: +# https://codecov.io/gh/ossrs/srs/src/3.0release/trunk/src/protocol/srs_rtmp_stack.cpp +# https://codecov.io/gh/ossrs/srs/src/20fbb4466fdc8ba5d810b8570df6004063212838/trunk/src/protocol/srs_rtmp_stack.cpp +# Remark: It takes a few minutes to sync with github, so it might not available when CircleCI is done. +# https://circleci.com/gh/ossrs/srs/tree/3.0release +cd $workdir && +export CODECOV_TOKEN="493bba46-c468-4e73-8b45-8cdd8ff62d96" && +bash <(curl -s https://codecov.io/bash) $CODECOV_ARGS && +echo "Done" && exit 0 + diff --git a/trunk/auto/depends.sh b/trunk/auto/depends.sh index a4c682277..6edde1710 100755 --- a/trunk/auto/depends.sh +++ b/trunk/auto/depends.sh @@ -25,8 +25,8 @@ function require_sudoer() } # TODO: check gcc/g++ -echo "check gcc/g++/gdb/make" -echo "depends tools are ok" +echo "Checking gcc/g++/gdb/make." +echo "Required tools are ok." ##################################################################################### # for Ubuntu, auto install tools by apt-get ##################################################################################### @@ -34,7 +34,7 @@ OS_IS_UBUNTU=NO function Ubuntu_prepare() { if [ $SRS_CUBIE = YES ]; then - echo "for cubieboard, use ubuntu prepare" + echo "For cubieboard, please use ubuntu prepare." else uname -v|grep Ubuntu >/dev/null 2>&1 ret=$?; if [[ 0 -ne $ret ]]; then @@ -45,102 +45,71 @@ function Ubuntu_prepare() fi fi fi - - # cross build for arm, install the cross build tool chain. - if [ $SRS_ARM_UBUNTU12 = YES ]; then - $SrsArmCC --help >/dev/null 2>&1; ret=$?; if [[ 0 -ne $ret ]]; then - echo "install gcc-arm-linux-gnueabi g++-arm-linux-gnueabi" - require_sudoer "sudo apt-get install -y --force-yes gcc-arm-linux-gnueabi g++-arm-linux-gnueabi" - sudo apt-get install -y --force-yes gcc-arm-linux-gnueabi g++-arm-linux-gnueabi; ret=$?; if [[ 0 -ne $ret ]]; then return $ret; fi - echo "install gcc-arm-linux-gnueabi g++-arm-linux-gnueabi success" - fi - fi - - # cross build for mips, user must installed the tool chain. - if [ $SRS_MIPS_UBUNTU12 = YES ]; then - $SrsArmCC --help >/dev/null 2>&1; ret=$?; if [[ 0 -ne $ret ]]; then - echo "user must install the tool chain: $SrsArmCC" - return 2 - fi - fi OS_IS_UBUNTU=YES - echo "Ubuntu detected, install tools if needed" + echo "Installing tools for Ubuntu." gcc --help >/dev/null 2>&1; ret=$?; if [[ 0 -ne $ret ]]; then - echo "install gcc" + echo "Installing gcc." require_sudoer "sudo apt-get install -y --force-yes gcc" sudo apt-get install -y --force-yes gcc; ret=$?; if [[ 0 -ne $ret ]]; then return $ret; fi - echo "install gcc success" + echo "The gcc is installed." fi g++ --help >/dev/null 2>&1; ret=$?; if [[ 0 -ne $ret ]]; then - echo "install g++" + echo "Installing g++." require_sudoer "sudo apt-get install -y --force-yes g++" sudo apt-get install -y --force-yes g++; ret=$?; if [[ 0 -ne $ret ]]; then return $ret; fi - echo "install g++ success" + echo "The g++ is installed." fi make --help >/dev/null 2>&1; ret=$?; if [[ 0 -ne $ret ]]; then - echo "install make" + echo "Installing make." require_sudoer "sudo apt-get install -y --force-yes make" sudo apt-get install -y --force-yes make; ret=$?; if [[ 0 -ne $ret ]]; then return $ret; fi - echo "install make success" + echo "The make is installed." fi patch --help >/dev/null 2>&1; ret=$?; if [[ 0 -ne $ret ]]; then - echo "install patch" + echo "Installing patch." require_sudoer "sudo apt-get install -y --force-yes patch" sudo apt-get install -y --force-yes patch; ret=$?; if [[ 0 -ne $ret ]]; then return $ret; fi - echo "install patch success" + echo "The patch is installed." fi unzip --help >/dev/null 2>&1; ret=$?; if [[ 0 -ne $ret ]]; then - echo "install unzip" + echo "Installing unzip." require_sudoer "sudo apt-get install -y --force-yes unzip" sudo apt-get install -y --force-yes unzip; ret=$?; if [[ 0 -ne $ret ]]; then return $ret; fi - echo "install unzip success" + echo "The unzip is installed." fi - if [ $SRS_NGINX = YES ]; then - if [[ ! -f /usr/include/pcre.h ]]; then - echo "install libpcre3-dev" - require_sudoer "sudo apt-get install -y --force-yes libpcre3-dev" - sudo apt-get install -y --force-yes libpcre3-dev; ret=$?; if [[ 0 -ne $ret ]]; then return $ret; fi - echo "install libpcre3-dev success" + if [[ $SRS_VALGRIND == YES ]]; then + valgrind --help >/dev/null 2>&1; ret=$?; if [[ 0 -ne $ret ]]; then + echo "Installing valgrind." + require_sudoer "sudo apt-get install -y --force-yes valgrind" + sudo apt-get install -y --force-yes valgrind; ret=$?; if [[ 0 -ne $ret ]]; then return $ret; fi + echo "The valgrind is installed." + fi + fi + + if [[ $SRS_VALGRIND == YES ]]; then + if [[ ! -f /usr/include/valgrind/valgrind.h ]]; then + echo "Installing valgrind-dev." + require_sudoer "sudo apt-get install -y --force-yes valgrind-dbg" + sudo apt-get install -y --force-yes valgrind-dev; ret=$?; if [[ 0 -ne $ret ]]; then return $ret; fi + echo "The valgrind-dev is installed." fi fi - if [ $SRS_FFMPEG_TOOL = YES ]; then - autoconf --help >/dev/null 2>&1; ret=$?; if [[ 0 -ne $ret ]]; then - echo "install autoconf" - require_sudoer "sudo apt-get install -y --force-yes autoconf" - sudo apt-get install -y --force-yes autoconf; ret=$?; if [[ 0 -ne $ret ]]; then return $ret; fi - echo "install autoconf success" - fi - - libtool --help >/dev/null 2>&1; ret=$?; if [[ 0 -ne $ret ]]; then - echo "install libtool" - require_sudoer "sudo apt-get install -y --force-yes libtool" - sudo apt-get install -y --force-yes libtool; ret=$?; if [[ 0 -ne $ret ]]; then return $ret; fi - echo "install libtool success" - fi - - if [[ ! -f /usr/include/zlib.h ]]; then - echo "install zlib1g-dev" - require_sudoer "sudo apt-get install -y --force-yes zlib1g-dev" - sudo apt-get install -y --force-yes zlib1g-dev; ret=$?; if [[ 0 -ne $ret ]]; then return $ret; fi - echo "install zlib1g-dev success" - fi - fi - - echo "Ubuntu install tools success" + echo "Tools for Ubuntu are installed." return 0 } # donot prepare tools, for srs-librtmp depends only gcc and g++. if [ $SRS_EXPORT_LIBRTMP_PROJECT = NO ]; then - Ubuntu_prepare; ret=$?; if [[ 0 -ne $ret ]]; then echo "Ubuntu prepare failed, ret=$ret"; exit $ret; fi + Ubuntu_prepare; ret=$?; if [[ 0 -ne $ret ]]; then echo "Install tools for ubuntu failed, ret=$ret"; exit $ret; fi fi + ##################################################################################### # for Centos, auto install tools by yum ##################################################################################### @@ -150,99 +119,73 @@ function Centos_prepare() if [[ ! -f /etc/redhat-release ]]; then return 0; fi - - # cross build for arm, install the cross build tool chain. - if [ $SRS_CROSS_BUILD = YES ]; then - echo "embeded(arm/mips) is invalid for CentOS" - return 1 - fi OS_IS_CENTOS=YES - echo "Centos detected, install tools if needed" + echo "Installing tools for Centos." gcc --help >/dev/null 2>&1; ret=$?; if [[ 0 -ne $ret ]]; then - echo "install gcc" + echo "Installing gcc." require_sudoer "sudo yum install -y gcc" sudo yum install -y gcc; ret=$?; if [[ 0 -ne $ret ]]; then return $ret; fi - echo "install gcc success" + echo "The gcc is installed." fi g++ --help >/dev/null 2>&1; ret=$?; if [[ 0 -ne $ret ]]; then - echo "install gcc-c++" + echo "Installing gcc-c++." require_sudoer "sudo yum install -y gcc-c++" sudo yum install -y gcc-c++; ret=$?; if [[ 0 -ne $ret ]]; then return $ret; fi - echo "install gcc-c++ success" + echo "The gcc-c++ is installed." fi make --help >/dev/null 2>&1; ret=$?; if [[ 0 -ne $ret ]]; then - echo "install make" + echo "Installing make." require_sudoer "sudo yum install -y make" sudo yum install -y make; ret=$?; if [[ 0 -ne $ret ]]; then return $ret; fi - echo "install make success" + echo "The make is installed." fi patch --help >/dev/null 2>&1; ret=$?; if [[ 0 -ne $ret ]]; then - echo "install patch" + echo "Installing patch." require_sudoer "sudo yum install -y patch" sudo yum install -y patch; ret=$?; if [[ 0 -ne $ret ]]; then return $ret; fi - echo "install patch success" + echo "The patch is installed." fi unzip --help >/dev/null 2>&1; ret=$?; if [[ 0 -ne $ret ]]; then - echo "install unzip" + echo "Installing unzip." require_sudoer "sudo yum install -y unzip" sudo yum install -y unzip; ret=$?; if [[ 0 -ne $ret ]]; then return $ret; fi - echo "install unzip success" + echo "The unzip is installed." fi - if [ $SRS_NGINX = YES ]; then - if [[ ! -f /usr/include/pcre.h ]]; then - echo "install pcre-devel" - require_sudoer "sudo yum install -y pcre-devel" - sudo yum install -y pcre-devel; ret=$?; if [[ 0 -ne $ret ]]; then return $ret; fi - echo "install pcre-devel success" + if [[ $SRS_VALGRIND == YES ]]; then + valgrind --help >/dev/null 2>&1; ret=$?; if [[ 0 -ne $ret ]]; then + echo "Installing valgrind." + require_sudoer "sudo yum install -y valgrind" + sudo yum install -y valgrind; ret=$?; if [[ 0 -ne $ret ]]; then return $ret; fi + echo "The valgrind is installed." + fi + fi + + if [[ $SRS_VALGRIND == YES ]]; then + if [[ ! -f /usr/include/valgrind/valgrind.h ]]; then + echo "Installing valgrind-devel." + require_sudoer "sudo yum install -y valgrind-devel" + sudo yum install -y valgrind-devel; ret=$?; if [[ 0 -ne $ret ]]; then return $ret; fi + echo "The valgrind-devel is installed." fi fi - if [ $SRS_FFMPEG_TOOL = YES ]; then - automake --help >/dev/null 2>&1; ret=$?; if [[ 0 -ne $ret ]]; then - echo "install automake" - require_sudoer "sudo yum install -y automake" - sudo yum install -y automake; ret=$?; if [[ 0 -ne $ret ]]; then return $ret; fi - echo "install automake success" - fi - - autoconf --help >/dev/null 2>&1; ret=$?; if [[ 0 -ne $ret ]]; then - echo "install autoconf" - require_sudoer "sudo yum install -y autoconf" - sudo yum install -y autoconf; ret=$?; if [[ 0 -ne $ret ]]; then return $ret; fi - echo "install autoconf success" - fi - - libtool --help >/dev/null 2>&1; ret=$?; if [[ 0 -ne $ret ]]; then - echo "install libtool" - require_sudoer "sudo yum install -y libtool" - sudo yum install -y libtool; ret=$?; if [[ 0 -ne $ret ]]; then return $ret; fi - echo "install libtool success" - fi - - if [[ ! -f /usr/include/zlib.h ]]; then - echo "install zlib-devel" - require_sudoer "sudo yum install -y zlib-devel" - sudo yum install -y zlib-devel; ret=$?; if [[ 0 -ne $ret ]]; then return $ret; fi - echo "install zlib-devel success" - fi - fi - - echo "Centos install tools success" + echo "Tools for Centos are installed." return 0 } # donot prepare tools, for srs-librtmp depends only gcc and g++. if [ $SRS_EXPORT_LIBRTMP_PROJECT = NO ]; then - Centos_prepare; ret=$?; if [[ 0 -ne $ret ]]; then echo "CentOS prepare failed, ret=$ret"; exit $ret; fi + Centos_prepare; ret=$?; if [[ 0 -ne $ret ]]; then echo "Install tools for CentOS failed, ret=$ret"; exit $ret; fi fi + ##################################################################################### -# for Centos, auto install tools by yum +# For OSX, auto install tools by brew ##################################################################################### OS_IS_OSX=NO function OSX_prepare() @@ -255,7 +198,7 @@ function OSX_prepare() fi return 0; fi - + # cross build for arm, install the cross build tool chain. if [ $SRS_CROSS_BUILD = YES ]; then echo "embeded(arm/mips) is invalid for OSX" @@ -278,35 +221,35 @@ function OSX_prepare() ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)"; ret=$?; if [[ 0 -ne $ret ]]; then return $ret; fi echo "install brew success" fi - + gcc --help >/dev/null 2>&1; ret=$?; if [[ 0 -ne $ret ]]; then echo "install gcc" echo "brew install gcc" brew install gcc; ret=$?; if [[ 0 -ne $ret ]]; then return $ret; fi echo "install gcc success" fi - + g++ --help >/dev/null 2>&1; ret=$?; if [[ 0 -ne $ret ]]; then echo "install gcc-c++" echo "brew install gcc-c++" brew install gcc-c++; ret=$?; if [[ 0 -ne $ret ]]; then return $ret; fi echo "install gcc-c++ success" fi - + make --help >/dev/null 2>&1; ret=$?; if [[ 0 -ne $ret ]]; then echo "install make" echo "brew install make" brew install make; ret=$?; if [[ 0 -ne $ret ]]; then return $ret; fi echo "install make success" fi - + patch --help >/dev/null 2>&1; ret=$?; if [[ 0 -ne $ret ]]; then echo "install patch" echo "brew install patch" brew install patch; ret=$?; if [[ 0 -ne $ret ]]; then return $ret; fi echo "install patch success" fi - + unzip --help >/dev/null 2>&1; ret=$?; if [[ 0 -ne $ret ]]; then echo "install unzip" echo "brew install unzip" @@ -314,45 +257,6 @@ function OSX_prepare() echo "install unzip success" fi - if [ $SRS_NGINX = YES ]; then - if [[ ! -f /usr/local/include/pcre.h ]]; then - echo "install pcre" - echo "brew install pcre" - brew install pcre; ret=$?; if [[ 0 -ne $ret ]]; then return $ret; fi - echo "install pcre success" - fi - fi - - if [ $SRS_FFMPEG_TOOL = YES ]; then - automake --help >/dev/null 2>&1; ret=$?; if [[ 0 -ne $ret ]]; then - echo "install automake" - echo "brew install automake" - brew install automake; ret=$?; if [[ 0 -ne $ret ]]; then return $ret; fi - echo "install automake success" - fi - - autoconf --help >/dev/null 2>&1; ret=$?; if [[ 0 -ne $ret ]]; then - echo "install autoconf" - echo "brew install autoconf" - brew install autoconf; ret=$?; if [[ 0 -ne $ret ]]; then return $ret; fi - echo "install autoconf success" - fi - - which libtool >/dev/null 2>&1; ret=$?; if [[ 0 -ne $ret ]]; then - echo "install libtool" - echo "brew install libtool" - brew install libtool; ret=$?; if [[ 0 -ne $ret ]]; then return $ret; fi - echo "install libtool success" - fi - - brew info zlib >/dev/null 2>&1; ret=$?; if [[ 0 -ne $ret ]]; then - echo "install zlib" - echo "brew install zlib" - brew install zlib; ret=$?; if [[ 0 -ne $ret ]]; then return $ret; fi - echo "install zlib success" - fi - fi - echo "OSX install tools success" return 0 } @@ -361,14 +265,17 @@ if [ $SRS_EXPORT_LIBRTMP_PROJECT = NO ]; then OSX_prepare; ret=$?; if [[ 0 -ne $ret ]]; then echo "OSX prepare failed, ret=$ret"; exit $ret; fi fi -# the sed command +##################################################################################### +# for Centos, auto install tools by yum +##################################################################################### +# We must use a bash function instead of variable. function sed_utility() { if [ $OS_IS_OSX = YES ]; then sed -i '' "$@" else sed -i "$@" fi - + ret=$?; if [[ $ret -ne 0 ]]; then if [ $OS_IS_OSX = YES ]; then echo "sed -i '' \"$@\"" @@ -383,118 +290,69 @@ SED="sed_utility" && echo "SED is $SED" ##################################################################################### # check the os. ##################################################################################### -# user must specifies something what a fuck, we suppport following os: -# centos/ubuntu/osx, +# Only supports: +# linux, centos/ubuntu as such, # cross build for embeded system, for example, mips or arm, # directly build on arm/mips, for example, pi or cubie, # export srs-librtmp # others is invalid. if [[ $OS_IS_UBUNTU = NO && $OS_IS_CENTOS = NO && $OS_IS_OSX = NO && $SRS_EXPORT_LIBRTMP_PROJECT = NO ]]; then if [[ $SRS_PI = NO && $SRS_CUBIE = NO && $SRS_CROSS_BUILD = NO ]]; then - echo "what a fuck, os not supported." + echo "Your OS `uname -s` is not supported." exit 1 fi fi ##################################################################################### -# st-1.9 +# state-threads ##################################################################################### if [ $SRS_EXPORT_LIBRTMP_PROJECT = NO ]; then # check the cross build flag file, if flag changed, need to rebuild the st. - _ST_MAKE=linux-debug && _ST_EXTRA_CFLAGS="-DMD_HAVE_EPOLL" + _ST_MAKE=linux-debug && _ST_EXTRA_CFLAGS="-DMD_HAVE_EPOLL" && _ST_LD=${SRS_TOOL_LD} && _ST_OBJ="LINUX_*" + if [[ $SRS_VALGRIND == YES ]]; then + _ST_EXTRA_CFLAGS="$_ST_EXTRA_CFLAGS -DMD_VALGRIND" + fi # for osx, use darwin for st, donot use epoll. - if [ $OS_IS_OSX = YES ]; then - _ST_MAKE=darwin-debug && _ST_EXTRA_CFLAGS="-DMD_HAVE_KQUEUE" + if [[ $SRS_OSX == YES ]]; then + _ST_MAKE=darwin-debug && _ST_EXTRA_CFLAGS="-DMD_HAVE_KQUEUE" && _ST_LD=${SRS_TOOL_CC} && _ST_OBJ="DARWIN_*" fi - # memory leak for linux-optimized - # @see: https://github.com/ossrs/srs/issues/197 - if [ $SRS_CROSS_BUILD = YES ]; then - # ok, arm specified, if the flag filed does not exists, need to rebuild. - if [[ -f ${SRS_OBJS}/_flag.st.cross.build.tmp && -f ${SRS_OBJS}/st/libst.a ]]; then - echo "st-1.9t for arm is ok."; - else - # TODO: FIXME: patch the bug. - # patch st for arm, @see: https://github.com/ossrs/srs/wiki/v1_CN_SrsLinuxArm#st-arm-bug-fix - echo "build st-1.9t for arm"; - ( - rm -rf ${SRS_OBJS}/st-1.9 && cd ${SRS_OBJS} && - unzip -q ../3rdparty/st-1.9.zip && cd st-1.9 && chmod +w * && - patch -p0 < ../../3rdparty/patches/1.st.arm.patch && - patch -p0 < ../../3rdparty/patches/3.st.osx.kqueue.patch && - patch -p0 < ../../3rdparty/patches/4.st.disable.examples.patch && - patch -p0 < ../../3rdparty/patches/6.st.osx10.14.build.patch && - make ${_ST_MAKE} CC=${SrsArmCC} AR=${SrsArmAR} LD=${SrsArmLD} RANDLIB=${SrsArmRANDLIB} EXTRA_CFLAGS="${_ST_EXTRA_CFLAGS}" && - cd .. && rm -rf st && ln -sf st-1.9/obj st && - cd .. && touch ${SRS_OBJS}/_flag.st.cross.build.tmp - ) - fi + # Always alloc on heap, @see https://github.com/ossrs/srs/issues/509#issuecomment-719931676 + _ST_EXTRA_CFLAGS="$_ST_EXTRA_CFLAGS -DMALLOC_STACK" + # Pass the global extra flags. + if [[ $SRS_EXTRA_FLAGS != '' ]]; then + _ST_EXTRA_CFLAGS="$_ST_EXTRA_CFLAGS $SRS_EXTRA_FLAGS" + fi + # Patched ST from https://github.com/ossrs/state-threads/tree/srs + if [[ -f ${SRS_OBJS}/${SRS_PLATFORM}/st/libst.a ]]; then + echo "The state-threads is ok."; else - if [[ ! -f ${SRS_OBJS}/_flag.st.cross.build.tmp && -f ${SRS_OBJS}/st/libst.a ]]; then - echo "st-1.9t is ok."; - else - # patch st for arm, @see: https://github.com/ossrs/srs/wiki/v1_CN_SrsLinuxArm#st-arm-bug-fix - echo "build st-1.9t"; - ( - rm -rf ${SRS_OBJS}/st-1.9 && cd ${SRS_OBJS} && - unzip -q ../3rdparty/st-1.9.zip && cd st-1.9 && chmod +w * && - patch -p0 < ../../3rdparty/patches/1.st.arm.patch && - patch -p0 < ../../3rdparty/patches/3.st.osx.kqueue.patch && - patch -p0 < ../../3rdparty/patches/4.st.disable.examples.patch && - patch -p0 < ../../3rdparty/patches/6.st.osx10.14.build.patch && - make ${_ST_MAKE} EXTRA_CFLAGS="${_ST_EXTRA_CFLAGS}" && - cd .. && rm -rf st && ln -sf st-1.9/obj st && - cd .. && rm -f ${SRS_OBJS}/_flag.st.cross.build.tmp - ) - fi + echo "Building state-threads."; + ( + rm -rf ${SRS_OBJS}/${SRS_PLATFORM}/st-srs && mkdir -p ${SRS_OBJS}/${SRS_PLATFORM}/st-srs && + # Create a hidden directory .src + cd ${SRS_OBJS}/${SRS_PLATFORM}/st-srs && ln -sf ../../../3rdparty/st-srs .src && + # Link source files under .src + for file in `(cd .src && find . -maxdepth 1 -type f ! -name '*.o' ! -name '*.d')`; do + ln -sf .src/$file $file; + done && + # Link source files under .src/xxx, the first child dir. + for dir in `(cd .src && find . -maxdepth 1 -type d|grep '\./'|grep -v Linux|grep -v Darwin)`; do + mkdir -p $dir && + for file in `(cd .src/$dir && find . -maxdepth 1 -type f ! -name '*.o' ! -name '*.d')`; do + ln -sf ../.src/$dir/$file $dir/$file; + done; + done && + # Build source code. + make ${_ST_MAKE} EXTRA_CFLAGS="${_ST_EXTRA_CFLAGS}" \ + CC=${SRS_TOOL_CC} AR=${SRS_TOOL_AR} LD=${_ST_LD} RANDLIB=${SRS_TOOL_RANDLIB} && + cd .. && rm -f st && ln -sf st-srs/${_ST_OBJ} st + ) fi # check status - ret=$?; if [[ $ret -ne 0 ]]; then echo "build st-1.9 failed, ret=$ret"; exit $ret; fi - if [ ! -f ${SRS_OBJS}/st/libst.a ]; then echo "build st-1.9 static lib failed."; exit -1; fi -fi - -##################################################################################### -# http-parser-2.1 -##################################################################################### -# check the cross build flag file, if flag changed, need to rebuild the st. -if [ $SRS_HTTP_CORE = YES ]; then - # ok, arm specified, if the flag filed does not exists, need to rebuild. - if [ $SRS_CROSS_BUILD = YES ]; then - if [[ -f ${SRS_OBJS}/_flag.st.hp.tmp && -f ${SRS_OBJS}/hp/http_parser.h && -f ${SRS_OBJS}/hp/libhttp_parser.a ]]; then - echo "http-parser-2.1 for arm is ok."; - else - echo "build http-parser-2.1 for arm"; - ( - rm -rf ${SRS_OBJS}/http-parser-2.1 && cd ${SRS_OBJS} && unzip -q ../3rdparty/http-parser-2.1.zip && - cd http-parser-2.1 && - patch -p0 < ../../3rdparty/patches/2.http.parser.patch && - make CC=${SrsArmCC} AR=${SrsArmAR} package && - cd .. && rm -rf hp && ln -sf http-parser-2.1 hp && - cd .. && touch ${SRS_OBJS}/_flag.st.hp.tmp - ) - fi - else - # cross build not specified, if exists flag, need to rebuild for no-arm platform. - if [[ ! -f ${SRS_OBJS}/_flag.st.hp.tmp && -f ${SRS_OBJS}/hp/http_parser.h && -f ${SRS_OBJS}/hp/libhttp_parser.a ]]; then - echo "http-parser-2.1 is ok."; - else - echo "build http-parser-2.1"; - ( - rm -rf ${SRS_OBJS}/http-parser-2.1 && cd ${SRS_OBJS} && unzip -q ../3rdparty/http-parser-2.1.zip && - cd http-parser-2.1 && - patch -p0 < ../../3rdparty/patches/2.http.parser.patch && - # Patch build error for https://github.com/ossrs/srs/pull/1312#issuecomment-480243404 - patch -p0 < ../../3rdparty/patches/7.http.parser.patch && - make package && - cd .. && rm -rf hp && ln -sf http-parser-2.1 hp && - cd .. && rm -f ${SRS_OBJS}/_flag.st.hp.tmp - ) - fi - fi - - # check status - ret=$?; if [[ $ret -ne 0 ]]; then echo "build http-parser-2.1 failed, ret=$ret"; exit $ret; fi - if [[ ! -f ${SRS_OBJS}/hp/http_parser.h ]]; then echo "build http-parser-2.1 failed"; exit -1; fi - if [[ ! -f ${SRS_OBJS}/hp/libhttp_parser.a ]]; then echo "build http-parser-2.1 failed"; exit -1; fi + ret=$?; if [[ $ret -ne 0 ]]; then echo "Build state-threads failed, ret=$ret"; exit $ret; fi + # Always update the links. + (cd ${SRS_OBJS} && rm -rf st && ln -sf ${SRS_PLATFORM}/st-srs/${_ST_OBJ} st) + if [ ! -f ${SRS_OBJS}/st/libst.a ]; then echo "Build state-threads static lib failed."; exit -1; fi fi ##################################################################################### @@ -512,30 +370,6 @@ END if [ $SRS_EXPORT_LIBRTMP_PROJECT = NO ]; then mkdir -p ${SRS_OBJS}/nginx fi -# make nginx -__SRS_BUILD_NGINX=NO; if [ $SRS_CROSS_BUILD = NO ]; then if [ $SRS_NGINX = YES ]; then __SRS_BUILD_NGINX=YES; fi fi -if [ $__SRS_BUILD_NGINX = YES ]; then - if [[ -f ${SRS_OBJS}/nginx/sbin/nginx ]]; then - echo "nginx-1.5.7 is ok."; - else - echo "build nginx-1.5.7"; - ( - rm -rf ${SRS_OBJS}/nginx-1.5.7 && cd ${SRS_OBJS} && - unzip -q ../3rdparty/nginx-1.5.7.zip && cd nginx-1.5.7 && - ./configure --prefix=`pwd`/_release && make ${SRS_JOBS} && make install && - cd .. && rm -rf nginx && ln -sf nginx-1.5.7/_release nginx - ) - fi - # check status - ret=$?; if [[ $ret -ne 0 ]]; then echo "build nginx-1.5.7 failed, ret=$ret"; exit $ret; fi - if [ ! -f ${SRS_OBJS}/nginx/sbin/nginx ]; then echo "build nginx-1.5.7 failed."; exit -1; fi - - # use current user to config nginx, - # srs will write ts/m3u8 file use current user, - # nginx default use nobody, so cannot read the ts/m3u8 created by srs. - cp ${SRS_OBJS}/nginx/conf/nginx.conf ${SRS_OBJS}/nginx/conf/nginx.conf.bk - $SED "s/^.user nobody;/user `whoami`;/g" ${SRS_OBJS}/nginx/conf/nginx.conf -fi # the demo dir. if [ $SRS_EXPORT_LIBRTMP_PROJECT = NO ]; then @@ -554,39 +388,43 @@ if [ $SRS_EXPORT_LIBRTMP_PROJECT = NO ]; then # copy players to nginx html dir. rm -rf ${SRS_OBJS}/nginx/html/players && - ln -sf `pwd`/research/players ${SRS_OBJS}/nginx/html/players && - rm -f ${SRS_OBJS}/nginx/crossdomain.xml && - ln -sf `pwd`/research/players/crossdomain.xml ${SRS_OBJS}/nginx/html/crossdomain.xml + ln -sf `pwd`/research/players ${SRS_OBJS}/nginx/html/players + + # For srs-console. + rm -rf ${SRS_OBJS}/nginx/html/console && + ln -sf `pwd`/research/console ${SRS_OBJS}/nginx/html/console # for favicon.ico rm -rf ${SRS_OBJS}/nginx/html/favicon.ico && ln -sf `pwd`/research/api-server/static-dir/favicon.ico ${SRS_OBJS}/nginx/html/favicon.ico + # For home page index.html + rm -rf ${SRS_OBJS}/nginx/html/index.html && + ln -sf `pwd`/research/api-server/static-dir/index.html ${SRS_OBJS}/nginx/html/index.html + # nginx.html to detect whether nginx is alive - echo "nginx is ok" > ${SRS_OBJS}/nginx/html/nginx.html + echo "Nginx is ok." > ${SRS_OBJS}/nginx/html/nginx.html fi ##################################################################################### # cherrypy for http hooks callback, CherryPy-3.2.4 ##################################################################################### -if [ $SRS_HTTP_CALLBACK = YES ]; then - if [[ -f ${SRS_OBJS}/CherryPy-3.2.4/setup.py ]]; then +if [ $SRS_EXPORT_LIBRTMP_PROJECT = NO ]; then + if [[ -f ${SRS_OBJS}/${SRS_PLATFORM}/CherryPy-3.2.4/setup.py ]]; then echo "CherryPy-3.2.4 is ok."; else - echo "install CherryPy-3.2.4"; + echo "Installing CherryPy-3.2.4"; ( - rm -rf ${SRS_OBJS}/CherryPy-3.2.4 && cd ${SRS_OBJS} && - unzip -q ../3rdparty/CherryPy-3.2.4.zip && cd CherryPy-3.2.4 && + rm -rf ${SRS_OBJS}/CherryPy-3.2.4 && cd ${SRS_OBJS}/${SRS_PLATFORM} && + unzip -q ../../3rdparty/CherryPy-3.2.4.zip && cd CherryPy-3.2.4 && python setup.py install --user ) fi # check status ret=$?; if [[ $ret -ne 0 ]]; then echo "build CherryPy-3.2.4 failed, ret=$ret"; exit $ret; fi - if [ ! -f ${SRS_OBJS}/CherryPy-3.2.4/setup.py ]; then echo "build CherryPy-3.2.4 failed."; exit -1; fi -fi + if [ ! -f ${SRS_OBJS}/${SRS_PLATFORM}/CherryPy-3.2.4/setup.py ]; then echo "build CherryPy-3.2.4 failed."; exit -1; fi -if [ $SRS_EXPORT_LIBRTMP_PROJECT = NO ]; then - echo "link players to cherrypy static-dir" + echo "Link players to cherrypy static-dir" rm -rf research/api-server/static-dir/players && ln -sf `pwd`/research/players research/api-server/static-dir/players && rm -f research/api-server/static-dir/crossdomain.xml && @@ -597,108 +435,82 @@ if [ $SRS_EXPORT_LIBRTMP_PROJECT = NO ]; then rm -rf research/api-server/static-dir/forward && mkdir -p `pwd`/${SRS_OBJS}/nginx/html/forward && ln -sf `pwd`/${SRS_OBJS}/nginx/html/forward research/api-server/static-dir/forward - ret=$?; if [[ $ret -ne 0 ]]; then echo "[warn] link players to cherrypy static-dir failed"; fi + ret=$?; if [[ $ret -ne 0 ]]; then echo "Warning: Ignore error to link players to cherrypy static-dir."; fi fi ##################################################################################### -# generate demo index.html +# openssl, for rtmp complex handshake and HLS encryption. ##################################################################################### -# if nginx enalbed, generate nginx index file. -if [ $__SRS_BUILD_NGINX = YES ]; then - rm -f ${SRS_OBJS}/nginx/html/index.html && - ln -sf `pwd`/research/players/nginx_index.html ${SRS_OBJS}/nginx/html/index.html +if [[ $SRS_SSL == YES && $SRS_USE_SYS_SSL == YES ]]; then + echo "Warning: Use system libssl, without compiling openssl." fi -# if http-server enalbed, use srs embeded http-server -if [ $SRS_HTTP_SERVER = YES ]; then - rm -f ${SRS_OBJS}/nginx/html/index.html && - ln -sf `pwd`/research/players/srs-http-server_index.html ${SRS_OBJS}/nginx/html/index.html -fi -# if api-server enabled, generate for api server. -if [ $SRS_HTTP_CALLBACK = YES ]; then - rm -f ${SRS_OBJS}/nginx/html/index.html && - ln -sf `pwd`/research/players/api-server_index.html ${SRS_OBJS}/nginx/html/index.html -fi - -##################################################################################### -# openssl, for rtmp complex handshake -##################################################################################### -# extra configure options -CONFIGURE_TOOL="./config" -if [ $SRS_CROSS_BUILD = YES ]; then - CONFIGURE_TOOL="./Configure linux-armv4" -fi -if [ $SRS_OSX = YES ]; then - CONFIGURE_TOOL="./Configure darwin64-`uname -m`-cc" -fi -OPENSSL_HOTFIX="-DOPENSSL_NO_HEARTBEATS" # @see http://www.openssl.org/news/secadv/20140407.txt -# Affected users should upgrade to OpenSSL 1.0.1g. Users unable to immediately +# Affected users should upgrade to OpenSSL 1.1.0e. Users unable to immediately # upgrade can alternatively recompile OpenSSL with -DOPENSSL_NO_HEARTBEATS. -if [ $SRS_SSL = YES ]; then - if [ $SRS_USE_SYS_SSL = YES ]; then - echo "warning: donot compile ssl, use system ssl" - else - # check the cross build flag file, if flag changed, need to rebuild the st. - if [ $SRS_CROSS_BUILD = YES ]; then - # ok, arm specified, if the flag filed does not exists, need to rebuild. - if [[ -f ${SRS_OBJS}/_flag.ssl.cross.build.tmp && -f ${SRS_OBJS}/openssl/lib/libssl.a ]]; then - echo "openssl-1.0.1f for arm is ok."; - else - echo "build openssl-1.0.1f for arm"; - ( - rm -rf ${SRS_OBJS}/openssl-1.0.1f && cd ${SRS_OBJS} && - unzip -q ../3rdparty/openssl-1.0.1f.zip && cd openssl-1.0.1f && - $CONFIGURE_TOOL --prefix=`pwd`/_release -no-shared no-asm $OPENSSL_HOTFIX && - make CC=${SrsArmCC} GCC=${SrsArmGCC} AR="${SrsArmAR} r" \ - LD=${SrsArmLD} LINK=${SrsArmGCC} RANDLIB=${SrsArmRANDLIB} && - make install_sw && - cd .. && rm -rf openssl && ln -sf openssl-1.0.1f/_release openssl && - cd .. && touch ${SRS_OBJS}/_flag.ssl.cross.build.tmp - ) - fi - else - # cross build not specified, if exists flag, need to rebuild for no-arm platform. - if [[ ! -f ${SRS_OBJS}/_flag.ssl.cross.build.tmp && -f ${SRS_OBJS}/openssl/lib/libssl.a ]]; then - echo "openssl-1.0.1f is ok."; - else - echo "build openssl-1.0.1f"; - ( - rm -rf ${SRS_OBJS}/openssl-1.0.1f && cd ${SRS_OBJS} && - unzip -q ../3rdparty/openssl-1.0.1f.zip && cd openssl-1.0.1f && - $CONFIGURE_TOOL --prefix=`pwd`/_release -no-shared $OPENSSL_HOTFIX && - make && make install_sw && - cd .. && rm -rf openssl && ln -sf openssl-1.0.1f/_release openssl && - cd .. && rm -f ${SRS_OBJS}/_flag.ssl.cross.build.tmp - ) - fi +if [[ $SRS_SSL == YES && $SRS_USE_SYS_SSL != YES ]]; then + OPENSSL_OPTIONS="-no-shared -no-threads -no-asm -DOPENSSL_NO_HEARTBEATS" + OPENSSL_CONFIG="./config" + # https://stackoverflow.com/questions/15539062/cross-compiling-of-openssl-for-linux-arm-v5te-linux-gnueabi-toolchain + if [[ $SRS_CROSS_BUILD == YES ]]; then + OPENSSL_CONFIG="./Configure linux-armv4" + elif [[ ! -f ${SRS_OBJS}/${SRS_PLATFORM}/openssl/lib/libssl.a ]]; then + # Try to use exists libraries. + if [[ -f /usr/local/ssl/lib/libssl.a ]]; then + (mkdir -p ${SRS_OBJS}/${SRS_PLATFORM}/openssl/lib && cd ${SRS_OBJS}/${SRS_PLATFORM}/openssl/lib && + ln -sf /usr/local/ssl/lib/libssl.a && ln -sf /usr/local/ssl/lib/libcrypto.a && + mkdir -p /usr/local/ssl/lib/pkgconfig && ln -sf /usr/local/ssl/lib/pkgconfig) + (mkdir -p ${SRS_OBJS}/${SRS_PLATFORM}/openssl/include && cd ${SRS_OBJS}/${SRS_PLATFORM}/openssl/include && + ln -sf /usr/local/ssl/include/openssl) fi - # check status - ret=$?; if [[ $ret -ne 0 ]]; then echo "build openssl-1.0.1f failed, ret=$ret"; exit $ret; fi - if [ ! -f ${SRS_OBJS}/openssl/lib/libssl.a ]; then echo "build openssl-1.0.1f failed."; exit -1; fi fi + # Which lib we use. + OPENSSL_LIB="openssl-1.1.0e/_release" + if [[ ! -f ${SRS_OBJS}/${SRS_PLATFORM}/${OPENSSL_LIB}/lib/libssl.a ]]; then + OPENSSL_LIB="openssl" + fi + # cross build not specified, if exists flag, need to rebuild for no-arm platform. + if [[ -f ${SRS_OBJS}/${SRS_PLATFORM}/openssl/lib/libssl.a ]]; then + echo "Openssl-1.1.0e is ok."; + else + echo "Building openssl-1.1.0e."; + ( + rm -rf ${SRS_OBJS}/${SRS_PLATFORM}/openssl-1.1.0e && cd ${SRS_OBJS}/${SRS_PLATFORM} && + unzip -q ../../3rdparty/openssl-1.1.0e.zip && cd openssl-1.1.0e && + ${OPENSSL_CONFIG} --prefix=`pwd`/_release $OPENSSL_OPTIONS && + make CC=${SRS_TOOL_CC} AR="${SRS_TOOL_AR} -rs" LD=${SRS_TOOL_LD} RANDLIB=${SRS_TOOL_RANDLIB} && make install_sw && + cd .. && rm -rf openssl && ln -sf openssl-1.1.0e/_release openssl + ) + fi + # check status + ret=$?; if [[ $ret -ne 0 ]]; then echo "Build openssl-1.1.0e failed, ret=$ret"; exit $ret; fi + # Always update the links. + (cd ${SRS_OBJS} && rm -rf openssl && ln -sf ${SRS_PLATFORM}/${OPENSSL_LIB} openssl) + if [ ! -f ${SRS_OBJS}/openssl/lib/libssl.a ]; then echo "Build openssl-1.1.0e failed."; exit -1; fi fi ##################################################################################### # live transcoding, ffmpeg-4.1, x264-core157, lame-3.99.5, libaacplus-2.0.2. ##################################################################################### +# Always link the ffmpeg tools if exists. +if [[ -f /usr/local/bin/ffmpeg && ! -f ${SRS_OBJS}/${SRS_PLATFORM}/ffmpeg ]]; then + mkdir -p ${SRS_OBJS}/${SRS_PLATFORM}/ffmpeg/bin && + ln -sf /usr/local/bin/ffmpeg ${SRS_OBJS}/${SRS_PLATFORM}/ffmpeg/bin/ffmpeg && + (cd ${SRS_OBJS} && rm -rf ffmpeg && ln -sf ${SRS_PLATFORM}/ffmpeg) +fi if [ $SRS_FFMPEG_TOOL = YES ]; then - if [[ -f /usr/local/bin/ffmpeg && ! -f ${SRS_OBJS}/ffmpeg/bin/ffmpeg ]]; then - mkdir -p ${SRS_OBJS}/ffmpeg/bin && ln -sf /usr/local/bin/ffmpeg ${SRS_OBJS}/ffmpeg/bin/ffmpeg - fi - if [[ -f ${SRS_OBJS}/ffmpeg/bin/ffmpeg ]]; then + if [[ -f ${SRS_OBJS}/${SRS_PLATFORM}/ffmpeg/bin/ffmpeg ]]; then echo "ffmpeg-4.1 is ok."; else - echo "build ffmpeg-4.1"; - ( - cd ${SRS_OBJS} && pwd_dir=`pwd` && - rm -rf ffmepg.src && mkdir -p ffmpeg.src && cd ffmpeg.src && - rm -f build_ffmpeg.sh && ln -sf ../../auto/build_ffmpeg.sh && . build_ffmpeg.sh && - cd ${pwd_dir} && rm -rf ffmpeg && ln -sf ffmpeg.src/_release ffmpeg - ) + echo -e "${RED}Error: No FFmpeg found at /usr/local/bin/ffmpeg${BLACK}" + echo -e "${RED} Please copy it from srs-docker${BLACK}" + echo -e "${RED} or download from http://ffmpeg.org/download.html${BLACK}" + echo -e "${RED} or disable it by --without-ffmpeg${BLACK}" + exit -1; + fi + # Always update the links. + if [[ -f ${SRS_OBJS}/${SRS_PLATFORM}/ffmpeg ]]; then + (cd ${SRS_OBJS} && rm -rf ffmpeg && ln -sf ${SRS_PLATFORM}/ffmpeg) fi - # check status - ret=$?; if [[ $ret -ne 0 ]]; then echo "build ffmpeg-4.1 failed, ret=$ret"; exit $ret; fi - if [ ! -f ${SRS_OBJS}/ffmpeg/bin/ffmpeg ]; then echo "build ffmpeg-4.1 failed."; exit -1; fi fi ##################################################################################### @@ -709,63 +521,63 @@ if [ $SRS_EXPORT_LIBRTMP_PROJECT = NO ]; then mkdir -p ${SRS_OBJS}/research (cd ${SRS_WORKDIR}/research/hls && make ${SRS_JOBS} && mv ts_info ../../${SRS_OBJS_DIR}/research) - ret=$?; if [[ $ret -ne 0 ]]; then echo "build research/hls failed, ret=$ret"; exit $ret; fi + ret=$?; if [[ $ret -ne 0 ]]; then echo "Build research/hls failed, ret=$ret"; exit $ret; fi (cd research/ffempty && make ${SRS_JOBS} && mv ffempty ../../${SRS_OBJS_DIR}/research) - ret=$?; if [[ $ret -ne 0 ]]; then echo "build research/ffempty failed, ret=$ret"; exit $ret; fi + ret=$?; if [[ $ret -ne 0 ]]; then echo "Build research/ffempty failed, ret=$ret"; exit $ret; fi fi fi -if [ $SRS_LIBRTMP = YES ]; then +if [[ $SRS_LIBRTMP == YES ]]; then mkdir -p ${SRS_OBJS}/research # librtmp - (cd ${SRS_WORKDIR}/research/librtmp && mkdir -p objs && ln -sf `pwd`/objs ../../${SRS_OBJS_DIR}/research/librtmp) - ret=$?; if [[ $ret -ne 0 ]]; then echo "link research/librtmp failed, ret=$ret"; exit $ret; fi + (cd ${SRS_WORKDIR}/research/librtmp && mkdir -p objs && + rm -rf ../../${SRS_OBJS_DIR}/research/librtmp && + ln -sf `pwd`/objs ../../${SRS_OBJS_DIR}/research/librtmp) + ret=$?; if [[ $ret -ne 0 ]]; then echo "Link research/librtmp failed, ret=$ret"; exit $ret; fi fi ##################################################################################### # build utest code ##################################################################################### if [ $SRS_UTEST = YES ]; then - if [[ -f ${SRS_OBJS}/gtest/include/gtest/gtest.h ]]; then - echo "gtest-1.6.0 is ok."; + if [[ -f ${SRS_OBJS}/${SRS_PLATFORM}/gtest/include/gtest/gtest.h ]]; then + echo "The gtest-1.6.0 is ok."; else - echo "build gtest-1.6.0"; + echo "Build gtest-1.6.0"; ( - rm -rf ${SRS_OBJS}/gtest-1.6.0 && cd ${SRS_OBJS} && - unzip -q ../3rdparty/gtest-1.6.0.zip && + rm -rf ${SRS_OBJS}/${SRS_PLATFORM}/gtest-1.6.0 && cd ${SRS_OBJS}/${SRS_PLATFORM} && + unzip -q ../../3rdparty/gtest-1.6.0.zip && rm -rf gtest && ln -sf gtest-1.6.0 gtest ) fi # check status - ret=$?; if [[ $ret -ne 0 ]]; then echo "build gtest-1.6.0 failed, ret=$ret"; exit $ret; fi - if [ ! -f ${SRS_OBJS}/gtest/include/gtest/gtest.h ]; then echo "build gtest-1.6.0 failed."; exit -1; fi + ret=$?; if [[ $ret -ne 0 ]]; then echo "Build gtest-1.6.0 failed, ret=$ret"; exit $ret; fi + # Always update the links. + (cd ${SRS_OBJS} && rm -rf gtest && ln -sf ${SRS_PLATFORM}/gtest-1.6.0 gtest) + if [ ! -f ${SRS_OBJS}/gtest/include/gtest/gtest.h ]; then echo "Build gtest-1.6.0 failed."; exit -1; fi fi ##################################################################################### # build gperf code ##################################################################################### if [ $SRS_GPERF = YES ]; then - if [[ -f ${SRS_OBJS}/gperf/bin/pprof ]]; then - echo "gperftools-2.1 is ok."; + if [[ -f ${SRS_OBJS}/${SRS_PLATFORM}/gperf/bin/pprof ]]; then + echo "The gperftools-2.1 is ok."; else - echo "build gperftools-2.1"; + echo "Build gperftools-2.1"; ( - rm -rf ${SRS_OBJS}/gperftools-2.1 && cd ${SRS_OBJS} && - unzip -q ../3rdparty/gperftools-2.1.zip && cd gperftools-2.1 && + rm -rf ${SRS_OBJS}/${SRS_PLATFORM}/gperftools-2.1 && cd ${SRS_OBJS}/${SRS_PLATFORM} && + unzip -q ../../3rdparty/gperftools-2.1.zip && cd gperftools-2.1 && ./configure --prefix=`pwd`/_release --enable-frame-pointers && make ${SRS_JOBS} && make install && cd .. && rm -rf gperf && ln -sf gperftools-2.1/_release gperf && rm -rf pprof && ln -sf gperf/bin/pprof pprof ) fi # check status - ret=$?; if [[ $ret -ne 0 ]]; then echo "build gperftools-2.1 failed, ret=$ret"; exit $ret; fi - if [ ! -f ${SRS_OBJS}/gperf/bin/pprof ]; then echo "build gperftools-2.1 failed."; exit -1; fi + ret=$?; if [[ $ret -ne 0 ]]; then echo "Build gperftools-2.1 failed, ret=$ret"; exit $ret; fi + # Always update the links. + (cd ${SRS_OBJS} && rm -rf pprof && ln -sf ${SRS_PLATFORM}/gperf/bin/pprof pprof) + if [ ! -f ${SRS_OBJS}/gperf/bin/pprof ]; then echo "Build gperftools-2.1 failed."; exit -1; fi fi - -##################################################################################### -# generated the test script -##################################################################################### -rm -rf ${SRS_OBJS}/srs.test && ln -sf `pwd`/scripts/srs.test objs/srs.test - diff --git a/trunk/auto/generate-srs-librtmp-single.sh b/trunk/auto/generate-srs-librtmp-single.sh index 46511927e..cb2174daa 100755 --- a/trunk/auto/generate-srs-librtmp-single.sh +++ b/trunk/auto/generate-srs-librtmp-single.sh @@ -1,12 +1,5 @@ #!/bin/bash -OS_IS_OSX=NO -uname -s|grep Darwin >/dev/null 2>&1 -ret=$?; if [[ 0 -eq $ret ]]; then - OS_IS_OSX=YES -fi -echo "Is OSX: ${OS_IS_OSX}" - # when export srs-librtmp single files # package the whole project to srs_librtmp.h and srs_librtmp.cpp # @@ -27,28 +20,28 @@ cp $SRS_EXPORT_LIBRTMP_SINGLE/src/libs/srs_librtmp.hpp $SRS_EXPORT_LIBRTMP_SINGL # create srs_librtmp.cpp FILE=$SRS_EXPORT_LIBRTMP_SINGLE/srs_librtmp.cpp cat << END >$FILE -/* -The MIT License (MIT) - -Copyright (c) 2013-2015 SRS(ossrs) - -Permission is hereby granted, free of charge, to any person obtaining a copy of -this software and associated documentation files (the "Software"), to deal in -the Software without restriction, including without limitation the rights to -use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of -the Software, and to permit persons to whom the Software is furnished to do so, -subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS -FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR -COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER -IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN -CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -*/ +/** + * The MIT License (MIT) + * + * Copyright (c) 2013-2017 OSSRS(winlin) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ #include "srs_librtmp.h" @@ -59,7 +52,7 @@ ret=$?; if [[ $ret -ne 0 ]]; then echo -e "${RED}failed to generate the srs_librtmp.cpp${BLACK}" exit $ret fi -# module to cpp files. +# module to hpp files. function build_module_hpp() { echo "build files ${SRS_LIBRTMP_OBJS} to $FILE" @@ -127,14 +120,14 @@ int main(int argc, char** argv) END -# compile the example -(cd $SRS_EXPORT_LIBRTMP_SINGLE && echo "${SRS_SINGLE_LIBRTMP_COMPILE}" && -`${SRS_SINGLE_LIBRTMP_COMPILE}` && ./example && rm -f example) -ret=$?; if [[ $ret -ne 0 ]]; then - echo "(cd $SRS_EXPORT_LIBRTMP_SINGLE && ${SRS_SINGLE_LIBRTMP_COMPILE} && ./example && rm -f example)" - echo -e "${RED}failed to compile example.${BLACK}" - exit $ret -fi +## compile the example +#(cd $SRS_EXPORT_LIBRTMP_SINGLE && echo "${SRS_SINGLE_LIBRTMP_COMPILE}" && +#`${SRS_SINGLE_LIBRTMP_COMPILE}` && ./example && rm -f example) +#ret=$?; if [[ $ret -ne 0 ]]; then +# echo "(cd $SRS_EXPORT_LIBRTMP_SINGLE && ${SRS_SINGLE_LIBRTMP_COMPILE} && ./example && rm -f example)" +# echo -e "${RED}failed to compile example.${BLACK}" +# exit $ret +#fi # clear the files for srs-librtmp project, generated by generate-srs-librtmp-project.sh (cd $SRS_EXPORT_LIBRTMP_SINGLE && rm -rf auto $SRS_OBJS_DIR research src Makefile) diff --git a/trunk/auto/libs.sh b/trunk/auto/libs.sh index 2795aed7e..af1103414 100755 --- a/trunk/auto/libs.sh +++ b/trunk/auto/libs.sh @@ -14,11 +14,12 @@ FILE=${SRS_OBJS}/${SRS_MAKEFILE} LIB_TARGET="${SRS_OBJS_DIR}/${LIB_NAME}" LIB_TAGET_STATIC="${LIB_TARGET}.a" -echo "generate lib ${LIB_NAME} depends..." +echo "Generating lib ${LIB_NAME} depends." echo "" >> ${FILE} echo "# archive library ${LIB_TAGET_STATIC}" >> ${FILE} echo "${BUILD_KEY}: ${LIB_TAGET_STATIC}" >> ${FILE} +echo "" >> ${FILE} # build depends echo -n "${LIB_TAGET_STATIC}: " >> ${FILE} @@ -41,7 +42,7 @@ echo -n " @bash auto/generate_header.sh ${SRS_OBJS_DIR}" >> ${FILE} echo "" >> ${FILE} # archive librtmp.a -echo -n " \$(AR) -rs ${LIB_TAGET_STATIC} " >> ${FILE} +echo -n " \$(AR) \$(ARFLAGS) ${LIB_TAGET_STATIC} " >> ${FILE} for item in ${MODULE_OBJS[*]}; do FILE_NAME=`basename $item` FILE_NAME=${FILE_NAME%.*} @@ -60,4 +61,4 @@ echo "" >> ${FILE} echo " @mkdir -p ${SRS_OBJS_DIR}/include" >> ${SRS_WORKDIR}/${SRS_MAKEFILE} echo " @mkdir -p ${SRS_OBJS_DIR}/lib" >> ${SRS_WORKDIR}/${SRS_MAKEFILE} -echo -n "generate lib ${LIB_NAME} ok"; echo '!'; +echo -n "Generate lib ${LIB_NAME} ok"; echo '!'; diff --git a/trunk/auto/modules.sh b/trunk/auto/modules.sh index 1088e71b6..053eb9104 100755 --- a/trunk/auto/modules.sh +++ b/trunk/auto/modules.sh @@ -17,7 +17,7 @@ FILE=${SRS_OBJS}/${SRS_MAKEFILE} echo "#####################################################################################" >> ${FILE} -echo "# the ${MODULE_ID} module." >> ${FILE} +echo "# The module ${MODULE_ID}." >> ${FILE} echo "#####################################################################################" >> ${FILE} echo >> ${FILE} @@ -41,7 +41,7 @@ echo -n "${INCS_NAME} = -I${MODULE_DIR} " >> ${FILE} for item in ${MODULE_DEPENDS[*]}; do DEP_INCS_NAME="${item}_INCS"do DEP_INCS_NAME="${item}_MODULE_INCS" - echo -n "\$(${DEP_INCS_NAME}) " >> ${FILE} + echo -n "\$(${DEP_INCS_NAME})" >> ${FILE} done # # depends library header files @@ -79,7 +79,8 @@ for item in ${MODULE_FILES[*]}; do MODULE_OBJS="${MODULE_OBJS[@]} ${CPP_FILE}" if [ -f ${CPP_FILE} ]; then echo "${OBJ_FILE}: \$(${DEPS_NAME}) ${CPP_FILE} " >> ${FILE} - echo " \$(CXX) -c \$(CXXFLAGS) ${DEFINES} \$(${INCS_NAME})\\" >> ${FILE} + echo " \$(CXX) -c \$(CXXFLAGS) ${DEFINES}\\" >> ${FILE} + echo " \$(${INCS_NAME})\\" >> ${FILE} echo " -o ${OBJ_FILE} ${CPP_FILE}" >> ${FILE} fi done @@ -88,4 +89,4 @@ echo "" >> ${FILE} # parent Makefile, to create module output dir before compile it. echo " @mkdir -p ${SRS_OBJS_DIR}/${MODULE_DIR}" >> ${SRS_WORKDIR}/${SRS_MAKEFILE} -echo -n "generate module ${MODULE_ID} ok"; echo '!'; +echo -n "Generate modules ${MODULE_ID} ok"; echo '!'; diff --git a/trunk/auto/options.sh b/trunk/auto/options.sh index 1e994556f..d6d1ddd3c 100755 --- a/trunk/auto/options.sh +++ b/trunk/auto/options.sh @@ -15,46 +15,47 @@ help=no ################################################################ # feature options -SRS_HLS=RESERVED -SRS_HDS=RESERVED -SRS_DVR=RESERVED -SRS_NGINX=RESERVED -SRS_SSL=RESERVED -SRS_FFMPEG_TOOL=RESERVED -SRS_TRANSCODE=RESERVED -SRS_INGEST=RESERVED -SRS_STAT=RESERVED -SRS_HTTP_CALLBACK=RESERVED -SRS_HTTP_SERVER=RESERVED -SRS_STREAM_CASTER=RESERVED -SRS_HTTP_API=RESERVED -SRS_LIBRTMP=RESERVED -SRS_RESEARCH=RESERVED -SRS_UTEST=RESERVED -# tcmalloc -SRS_GPERF=RESERVED -# gperf memory check -SRS_GPERF_MC=RESERVED -# gperf memory profile -SRS_GPERF_MP=RESERVED -# gperf cpu profile -SRS_GPERF_CP=RESERVED -# gprof -SRS_GPROF=RESERVED +SRS_HDS=NO +SRS_NGINX=NO +SRS_FFMPEG_TOOL=NO +SRS_LIBRTMP=NO +SRS_RESEARCH=NO +SRS_UTEST=NO +SRS_GPERF=NO # Performance test: tcmalloc +SRS_GPERF_MC=NO # Performance test: gperf memory check +SRS_GPERF_MD=NO # Performance test: gperf memory defence +SRS_GPERF_MP=NO # Performance test: gperf memory profile +SRS_GPERF_CP=NO # Performance test: gperf cpu profile +SRS_GPROF=NO # Performance test: gprof +# Always enable the bellow features. +SRS_STREAM_CASTER=YES +SRS_INGEST=YES +SRS_SSL=YES +SRS_STAT=YES +SRS_TRANSCODE=YES +SRS_HTTP_CALLBACK=YES +SRS_HTTP_SERVER=YES +SRS_HTTP_API=YES +SRS_HTTP_CORE=YES +SRS_HLS=YES +SRS_DVR=YES # ################################################################ # libraries -SRS_FFMPEG_STUB=RESERVED -SRS_HTTP_CORE=RESERVED +SRS_FFMPEG_STUB=NO # arguments SRS_PREFIX=/usr/local/srs SRS_JOBS=1 -SRS_STATIC=RESERVED +SRS_STATIC=NO +# If enabled, link shared libraries for libst.so which uses MPL license. +SRS_SHARED_ST=NO +# whether enable the gcov +SRS_GCOV=NO # whether enable the log verbose/info/trace level. # always enable the warn/error level. -SRS_LOG_VERBOSE=RESERVED -SRS_LOG_INFO=RESERVED -SRS_LOG_TRACE=RESERVED +SRS_LOG_VERBOSE=NO +SRS_LOG_INFO=NO +SRS_LOG_TRACE=NO # ################################################################ # experts @@ -67,6 +68,8 @@ SRS_MEM_WATCH=NO SRS_EXPORT_LIBRTMP_PROJECT=NO # export the srs-librtmp to a single .h and .c, NO to disable it. SRS_EXPORT_LIBRTMP_SINGLE=NO +# valgrind +SRS_VALGRIND=NO # ################################################################ # presets @@ -74,11 +77,6 @@ SRS_EXPORT_LIBRTMP_SINGLE=NO SRS_X86_X64=NO # for osx system SRS_OSX=NO -SRS_ALLOW_OSX=NO -# armhf(v7cpu) built on ubuntu12 -SRS_ARM_UBUNTU12=NO -# mips built on ubuntu12 -SRS_MIPS_UBUNTU12=NO # dev, open all features for dev, no gperf/prof/arm. SRS_DEV=NO # dev, open main server feature for dev, no utest/research/librtmp @@ -93,16 +91,22 @@ SRS_CUBIE=NO SRS_FAST=NO # only support RTMP with ssl. SRS_PURE_RTMP=NO -# only support RTMP+HLS with ssl. -SRS_RTMP_HLS=NO # the most fast compile, nothing, only support vp6 RTMP. SRS_DISABLE_ALL=NO # all features is on SRS_ENABLE_ALL=NO # -################################################################ -# whether cross build for embed cpu, arm/mips +##################################################################################### +# Toolchain crossbuild for ARM or MIPS. SRS_CROSS_BUILD=NO +SRS_TOOL_CC=gcc +SRS_TOOL_CXX=g++ +SRS_TOOL_AR=ar +SRS_TOOL_LD=ld +SRS_TOOL_RANDLIB=randlib +SRS_EXTRA_FLAGS= +# Set the object files tag name. +SRS_BUILD_TAG= ##################################################################################### # menu @@ -110,103 +114,89 @@ SRS_CROSS_BUILD=NO function show_help() { cat << END -Options: - -h, --help print this message - - --with-ssl enable rtmp complex handshake, requires openssl-devel installed. - to delivery h264 video and aac audio to flash player. - --with-hls enable hls streaming, mux RTMP to m3u8/ts files. - --with-hds enable hds streaming, mux RTMP to f4m/f4v files. - --with-dvr enable dvr, mux RTMP to flv files. - --with-nginx enable delivery HTTP stream with nginx. - build nginx at: ./objs/nginx/sbin/nginx - --with-http-callback enable http hooks, build cherrypy as demo api server. - --with-http-server enable http server to delivery http stream. - --with-stream-caster enable stream caster to serve other stream over other protocol. - --with-http-api enable http api, to manage SRS by http api. - --with-ffmpeg enable transcoding tool ffmpeg. - build ffmpeg at: ./objs/ffmpeg/bin/ffmpeg - --with-transcode enable transcoding features. - user must specifies the transcode tools in conf. - --with-ingest enable ingest features. - user must specifies the ingest tools in conf. - --with-stat enable the data statistic, for http api. - --with-librtmp enable srs-librtmp, library for client. - --with-research build the research tools. - --with-utest build the utest for SRS. - --with-gperf build SRS with gperf tools(no gmc/gmp/gcp, with tcmalloc only). - --with-gmc build memory check for SRS with gperf tools. - --with-gmp build memory profile for SRS with gperf tools. - --with-gcp build cpu profile for SRS with gperf tools. - --with-gprof build SRS with gprof(GNU profile tool). - --with-arm-ubuntu12 cross build SRS on ubuntu12 for armhf(v7cpu). - - --without-ssl disable rtmp complex handshake. - --without-hls disable hls, the apple http live streaming. - --without-hds disable hds, the adobe http dynamic streaming. - --without-dvr disable dvr, donot support record RTMP stream to flv. - --without-nginx disable delivery HTTP stream with nginx. - --without-http-callback disable http, http hooks callback. - --without-http-server disable http server, use external server to delivery http stream. - --without-stream-caster disable stream caster, only listen and serve RTMP/HTTP. - --without-http-api disable http api, only use console to manage SRS process. - --without-ffmpeg disable the ffmpeg transcode tool feature. - --without-transcode disable the transcoding feature. - --without-ingest disable the ingest feature. - --without-stat disable the data statistic feature. - --without-librtmp disable srs-librtmp, library for client. - --without-research do not build the research tools. - --without-utest do not build the utest for SRS. - --without-gperf do not build SRS with gperf tools(without tcmalloc and gmc/gmp/gcp). - --without-gmc do not build memory check for SRS with gperf tools. - --without-gmp do not build memory profile for SRS with gperf tools. - --without-gcp do not build cpu profile for SRS with gperf tools. - --without-gprof do not build srs with gprof(GNU profile tool). - --without-arm-ubuntu12 do not cross build srs on ubuntu12 for armhf(v7cpu). - - --prefix= the absolute install path for srs. - --static whether add '-static' to link options. - --jobs[=N] Allow N jobs at once; infinite jobs with no arg. - used for make in the configure, for example, to make ffmpeg. - --log-verbose whether enable the log verbose level. default: no. - --log-info whether enable the log info level. default: no. - --log-trace whether enable the log trace level. default: yes. - Presets: - --x86-x64 [default] for x86/x64 cpu, common pc and servers. - --osx for osx(darwin) system to build SRS. - --pi for raspberry-pi(directly build), open features hls/ssl/static. - --cubie for cubieboard(directly build), open features except ffmpeg/nginx. - --arm alias for --with-arm-ubuntu12, for ubuntu12, arm crossbuild - --mips alias for --with-mips-ubuntu12, for ubuntu12, mips crossbuild - --fast the most fast compile, nothing, only support vp6 RTMP. - --pure-rtmp only support RTMP with ssl. - --rtmp-hls only support RTMP+HLS with ssl. - --disable-all disable all features, only support vp6 RTMP. - --dev for dev, open all features, no nginx/gperf/gprof/arm. - --fast-dev for dev fast compile, the RTMP server, without librtmp/utest/research. - --demo for srs demo, @see: https://github.com/ossrs/srs/wiki/v1_CN_SampleDemo - --full enable all features, no gperf/gprof/arm. - + --x86-64, --x86-x64 [default] For x86/x64 cpu, common pc and servers. + --arm Enable crossbuild for ARM, should also set bellow toolchain options. + --mips Enable crossbuild for MIPS + +Features: + -h, --help Print this message and exit 0. + + --with-ssl Enable rtmp complex handshake, requires openssl-devel installed. + --with-hds Enable hds streaming, mux RTMP to F4M/F4V files. + --with-stream-caster Enable stream caster to serve other stream over other protocol. + --with-stat Enable the data statistic, for http api. + --with-librtmp Enable srs-librtmp, library for client. + --with-research Build the research tools. + --with-utest Build the utest for SRS. + + --without-ssl Disable rtmp complex handshake. + --without-hds Disable hds, the adobe http dynamic streaming. + --without-stream-caster Disable stream caster, only listen and serve RTMP/HTTP. + --without-stat Disable the data statistic feature. + --without-librtmp Disable srs-librtmp, library for client. + --without-research Do not build the research tools. + --without-utest Do not build the utest for SRS. + + --prefix= The absolute installation path for srs. Default: $SRS_PREFIX + --static Whether add '-static' to link options. + --gcov Whether enable the GCOV compiler options. + --jobs[=N] Allow N jobs at once; infinite jobs with no arg. + Used for make in the configure, for example, to make ffmpeg. + --log-verbose Whether enable the log verbose level. default: no. + --log-info Whether enable the log info level. default: no. + --log-trace Whether enable the log trace level. default: yes. + +Performance: @see https://blog.csdn.net/win_lin/article/details/53503869 + --with-valgrind Support valgrind for memory check. + --with-gperf Build SRS with gperf tools(no gmd/gmc/gmp/gcp, with tcmalloc only). + --with-gmc Build memory check for SRS with gperf tools. + --with-gmd Build memory defense(corrupt memory) for SRS with gperf tools. + --with-gmp Build memory profile for SRS with gperf tools. + --with-gcp Build cpu profile for SRS with gperf tools. + --with-gprof Build SRS with gprof(GNU profile tool). + + --without-valgrind Do not support valgrind for memory check. + --without-gperf Do not build SRS with gperf tools(without tcmalloc and gmd/gmc/gmp/gcp). + --without-gmc Do not build memory check for SRS with gperf tools. + --without-gmd Do not build memory defense for SRS with gperf tools. + --without-gmp Do not build memory profile for SRS with gperf tools. + --without-gcp Do not build cpu profile for SRS with gperf tools. + --without-gprof Do not build srs with gprof(GNU profile tool). + +Toolchain options: @see https://github.com/ossrs/srs/issues/1547#issuecomment-576078411 + --arm Enable crossbuild for ARM. + --mips Enable crossbuild for MIPS. + --cc= Use c compiler CC, default is gcc. + --cxx= Use c++ compiler CXX, default is g++. + --ar= Use archive tool AR, default is ar. + --ld= Use linker tool LD, default is ld. + --randlib= Use randlib tool RANDLIB, default is randlib. + --extra-flags= Set EFLAGS as CFLAGS and CXXFLAGS. Also passed to ST as EXTRA_CFLAGS. + --build-tag= Set the build object directory suffix. + Conflicts: 1. --with-gmc vs --with-gmp: @see: http://google-perftools.googlecode.com/svn/trunk/doc/heap_checker.html 2. --with-gperf/gmc/gmp vs --with-gprof: - gperftools not compatible with gprof. + The gperftools not compatible with gprof. 3. --arm vs --with-ffmpeg/gperf/gmc/gmp/gprof: - the complex tools not available for arm. + The complex tools not available for arm. Experts: - --use-sys-ssl donot compile ssl, use system ssl(-lssl) if required. - --memory-watch enable memory watch to detect memory leaking(hurts performance). - --export-librtmp-project= export srs-librtmp to specified project in path. - --export-librtmp-single= export srs-librtmp to a single file(.h+.cpp) in path. + --use-sys-ssl Do not compile ssl, use system ssl(-lssl) if required. + --use-shared-st Use link shared libraries for ST which uses MPL license. + --export-librtmp-project= Export srs-librtmp to specified project in path. + --export-librtmp-single= Export srs-librtmp to a single file(.h+.cpp) in path. Workflow: - 1. apply "Presets". if not specified, use default preset. - 2. apply "Options". user specified option will override the preset. - 3. check conflicts. @see Conflicts section. - 4. generate detail features. + 1. Apply "Presets". if not specified, use default preset. + 2. Apply "Features", "Performance" and others. user specified option will override the preset. + 3. Check conflicts, fail if exists conflicts. + 4. Generate Makefile. + +Remark: + 1. For performance improving, read https://blog.csdn.net/win_lin/article/details/53503869 END } @@ -217,52 +207,39 @@ function parse_user_option() { --help) help=yes ;; --with-ssl) SRS_SSL=YES ;; - --with-hls) SRS_HLS=YES ;; --with-hds) SRS_HDS=YES ;; - --with-dvr) SRS_DVR=YES ;; --with-nginx) SRS_NGINX=YES ;; --with-ffmpeg) SRS_FFMPEG_TOOL=YES ;; --with-transcode) SRS_TRANSCODE=YES ;; --with-ingest) SRS_INGEST=YES ;; --with-stat) SRS_STAT=YES ;; - --with-http-callback) SRS_HTTP_CALLBACK=YES ;; - --with-http-server) SRS_HTTP_SERVER=YES ;; --with-stream-caster) SRS_STREAM_CASTER=YES ;; - --with-http-api) SRS_HTTP_API=YES ;; --with-librtmp) SRS_LIBRTMP=YES ;; --with-research) SRS_RESEARCH=YES ;; --with-utest) SRS_UTEST=YES ;; --with-gperf) SRS_GPERF=YES ;; --with-gmc) SRS_GPERF_MC=YES ;; + --with-gmd) SRS_GPERF_MD=YES ;; --with-gmp) SRS_GPERF_MP=YES ;; --with-gcp) SRS_GPERF_CP=YES ;; --with-gprof) SRS_GPROF=YES ;; - --with-arm-ubuntu12) SRS_ARM_UBUNTU12=YES ;; - --with-mips-ubuntu12) SRS_MIPS_UBUNTU12=YES ;; - - --without-ssl) SRS_SSL=NO ;; - --without-hls) SRS_HLS=NO ;; + --with-arm-ubuntu12) SRS_CROSS_BUILD=YES ;; + --with-mips-ubuntu12) SRS_CROSS_BUILD=YES ;; + --without-hds) SRS_HDS=NO ;; - --without-dvr) SRS_DVR=NO ;; --without-nginx) SRS_NGINX=NO ;; --without-ffmpeg) SRS_FFMPEG_TOOL=NO ;; - --without-transcode) SRS_TRANSCODE=NO ;; - --without-ingest) SRS_INGEST=NO ;; - --without-stat) SRS_STAT=NO ;; - --without-http-callback) SRS_HTTP_CALLBACK=NO ;; - --without-http-server) SRS_HTTP_SERVER=NO ;; - --without-stream-caster) SRS_STREAM_CASTER=NO ;; - --without-http-api) SRS_HTTP_API=NO ;; --without-librtmp) SRS_LIBRTMP=NO ;; --without-research) SRS_RESEARCH=NO ;; --without-utest) SRS_UTEST=NO ;; --without-gperf) SRS_GPERF=NO ;; --without-gmc) SRS_GPERF_MC=NO ;; + --without-gmd) SRS_GPERF_MD=NO ;; --without-gmp) SRS_GPERF_MP=NO ;; --without-gcp) SRS_GPERF_CP=NO ;; --without-gprof) SRS_GPROF=NO ;; - --without-arm-ubuntu12) SRS_ARM_UBUNTU12=NO ;; - --without-mips-ubuntu12) SRS_MIPS_UBUNTU12=NO ;; + --without-arm-ubuntu12) SRS_CROSS_BUILD=NO ;; + --without-mips-ubuntu12) SRS_CROSS_BUILD=NO ;; --jobs) SRS_JOBS=${value} ;; --prefix) SRS_PREFIX=${value} ;; @@ -270,12 +247,22 @@ function parse_user_option() { --log-verbose) SRS_LOG_VERBOSE=YES ;; --log-info) SRS_LOG_INFO=YES ;; --log-trace) SRS_LOG_TRACE=YES ;; - + --gcov) SRS_GCOV=YES ;; + + --arm) SRS_CROSS_BUILD=YES ;; + --mips) SRS_CROSS_BUILD=YES ;; + --cc) SRS_TOOL_CC=${value} ;; + --cxx) SRS_TOOL_CXX=${value} ;; + --ar) SRS_TOOL_AR=${value} ;; + --ld) SRS_TOOL_LD=${value} ;; + --randlib) SRS_TOOL_RANDLIB=${value} ;; + --extra-flags) SRS_EXTRA_FLAGS=${value} ;; + --build-tag) SRS_BUILD_TAG=${value} ;; + --x86-x64) SRS_X86_X64=YES ;; + --x86-64) SRS_X86_X64=YES ;; --osx) SRS_OSX=YES ;; - --allow-osx) SRS_ALLOW_OSX=YES ;; - --arm) SRS_ARM_UBUNTU12=YES ;; - --mips) SRS_MIPS_UBUNTU12=YES ;; + --allow-osx) SRS_OSX=YES ;; --pi) SRS_PI=YES ;; --cubie) SRS_CUBIE=YES ;; --dev) SRS_DEV=YES ;; @@ -284,13 +271,33 @@ function parse_user_option() { --fast) SRS_FAST=YES ;; --disable-all) SRS_DISABLE_ALL=YES ;; --pure-rtmp) SRS_PURE_RTMP=YES ;; - --rtmp-hls) SRS_RTMP_HLS=YES ;; --full) SRS_ENABLE_ALL=YES ;; --use-sys-ssl) SRS_USE_SYS_SSL=YES ;; + --use-shared-st) SRS_SHARED_ST=YES ;; + --memory-watch) SRS_MEM_WATCH=YES ;; --export-librtmp-project) SRS_EXPORT_LIBRTMP_PROJECT=${value} ;; --export-librtmp-single) SRS_EXPORT_LIBRTMP_SINGLE=${value} ;; + --with-valgrind) SRS_VALGRIND=YES ;; + --without-valgrind) SRS_VALGRIND=NO ;; + + --with-http-callback) SRS_HTTP_CALLBACK=YES ;; + --with-http-api) SRS_HTTP_API=YES ;; + --with-http-server) SRS_HTTP_SERVER=YES ;; + --with-hls) SRS_HLS=YES ;; + --with-dvr) SRS_DVR=YES ;; + + --without-stream-caster) echo "ignore option \"$option\"" ;; + --without-ingest) echo "ignore option \"$option\"" ;; + --without-ssl) echo "ignore option \"$option\"" ;; + --without-stat) echo "ignore option \"$option\"" ;; + --without-transcode) echo "ignore option \"$option\"" ;; + --without-http-callback) echo "ignore option \"$option\"" ;; + --without-http-server) echo "ignore option \"$option\"" ;; + --without-http-api) echo "ignore option \"$option\"" ;; + --without-hls) echo "ignore option \"$option\"" ;; + --without-dvr) echo "ignore option \"$option\"" ;; *) echo "$0: error: invalid option \"$option\"" @@ -302,8 +309,8 @@ function parse_user_option() { function parse_user_option_to_value_and_option() { case "$option" in -*=*) - value=`echo "$option" | sed -e 's|[-_a-zA-Z0-9/]*=||'` - option=`echo "$option" | sed -e 's|=[-_a-zA-Z0-9/.]*||'` + value=`echo "$option" | sed -e 's|[-_a-zA-Z0-9/]*=||'` + option=`echo "$option" | sed -e 's|=[-_a-zA-Z0-9/. +]*||'` ;; *) value="" ;; esac @@ -333,420 +340,108 @@ function apply_user_presets() { SRS_LOG_TRACE=YES # set default preset if not specifies - if [ $SRS_RTMP_HLS = NO ]; then - if [ $SRS_PURE_RTMP = NO ]; then - if [ $SRS_FAST = NO ]; then - if [ $SRS_DISABLE_ALL = NO ]; then - if [ $SRS_ENABLE_ALL = NO ]; then - if [ $SRS_DEV = NO ]; then - if [ $SRS_FAST_DEV = NO ]; then - if [ $SRS_DEMO = NO ]; then - if [ $SRS_ARM_UBUNTU12 = NO ]; then - if [ $SRS_MIPS_UBUNTU12 = NO ]; then - if [ $SRS_PI = NO ]; then - if [ $SRS_CUBIE = NO ]; then - if [ $SRS_X86_X64 = NO ]; then - if [ $SRS_OSX = NO ]; then - SRS_X86_X64=YES; opt="--x86-x64 $opt"; - fi - fi - fi - fi - fi - fi - fi - fi - fi - fi - fi - fi - fi - fi - - # whether embeded cpu. - if [ $SRS_ARM_UBUNTU12 = YES ]; then - SRS_CROSS_BUILD=YES - fi - if [ $SRS_MIPS_UBUNTU12 = YES ]; then - SRS_CROSS_BUILD=YES + if [[ $SRS_PURE_RTMP == NO && $SRS_FAST == NO && $SRS_DISABLE_ALL == NO && $SRS_ENABLE_ALL == NO && \ + $SRS_DEV == NO && $SRS_FAST_DEV == NO && $SRS_DEMO == NO && $SRS_PI == NO && $SRS_CUBIE == NO && \ + $SRS_X86_X64 == NO && $SRS_OSX == NO && $SRS_CROSS_BUILD == NO \ + ]]; then + SRS_X86_X64=YES; opt="--x86-x64 $opt"; fi # all disabled. if [ $SRS_DISABLE_ALL = YES ]; then - SRS_HLS=NO SRS_HDS=NO - SRS_DVR=NO - SRS_NGINX=NO - SRS_SSL=NO - SRS_FFMPEG_TOOL=NO - SRS_TRANSCODE=NO - SRS_INGEST=NO - SRS_STAT=NO - SRS_HTTP_CORE=NO - SRS_HTTP_CALLBACK=NO - SRS_HTTP_SERVER=NO - SRS_STREAM_CASTER=NO - SRS_HTTP_API=NO SRS_LIBRTMP=NO SRS_RESEARCH=NO SRS_UTEST=NO - SRS_GPERF=NO - SRS_GPERF_MC=NO - SRS_GPERF_MP=NO - SRS_GPERF_CP=NO - SRS_GPROF=NO SRS_STATIC=NO fi # all enabled. if [ $SRS_ENABLE_ALL = YES ]; then - SRS_HLS=YES SRS_HDS=YES - SRS_DVR=YES - SRS_NGINX=YES - SRS_SSL=YES - SRS_FFMPEG_TOOL=YES - SRS_TRANSCODE=YES - SRS_INGEST=YES - SRS_STAT=YES - SRS_HTTP_CORE=YES - SRS_HTTP_CALLBACK=YES - SRS_HTTP_SERVER=YES - SRS_STREAM_CASTER=YES - SRS_HTTP_API=YES SRS_LIBRTMP=YES SRS_RESEARCH=YES SRS_UTEST=YES - SRS_GPERF=NO - SRS_GPERF_MC=NO - SRS_GPERF_MP=NO - SRS_GPERF_CP=NO - SRS_GPROF=NO SRS_STATIC=NO fi # only rtmp vp6 if [ $SRS_FAST = YES ]; then - SRS_HLS=NO SRS_HDS=NO - SRS_DVR=NO - SRS_NGINX=NO - SRS_SSL=NO - SRS_FFMPEG_TOOL=NO - SRS_TRANSCODE=NO - SRS_INGEST=NO - SRS_STAT=NO - SRS_HTTP_CORE=NO - SRS_HTTP_CALLBACK=NO - SRS_HTTP_SERVER=NO - SRS_STREAM_CASTER=NO - SRS_HTTP_API=NO SRS_LIBRTMP=NO SRS_RESEARCH=NO SRS_UTEST=NO - SRS_GPERF=NO - SRS_GPERF_MC=NO - SRS_GPERF_MP=NO - SRS_GPERF_CP=NO - SRS_GPROF=NO - SRS_STATIC=NO - fi - - # all disabled. - if [ $SRS_RTMP_HLS = YES ]; then - SRS_HLS=YES - SRS_HDS=YES - SRS_DVR=NO - SRS_NGINX=NO - SRS_SSL=YES - SRS_FFMPEG_TOOL=NO - SRS_TRANSCODE=NO - SRS_INGEST=NO - SRS_STAT=NO - SRS_HTTP_CORE=NO - SRS_HTTP_CALLBACK=NO - SRS_HTTP_SERVER=NO - SRS_STREAM_CASTER=NO - SRS_HTTP_API=NO - SRS_LIBRTMP=NO - SRS_RESEARCH=NO - SRS_UTEST=NO - SRS_GPERF=NO - SRS_GPERF_MC=NO - SRS_GPERF_MP=NO - SRS_GPERF_CP=NO - SRS_GPROF=NO SRS_STATIC=NO fi # only ssl for RTMP with complex handshake. if [ $SRS_PURE_RTMP = YES ]; then - SRS_HLS=NO SRS_HDS=NO - SRS_DVR=NO - SRS_NGINX=NO - SRS_SSL=YES - SRS_FFMPEG_TOOL=NO - SRS_TRANSCODE=NO - SRS_INGEST=NO - SRS_STAT=NO - SRS_HTTP_CORE=NO - SRS_HTTP_CALLBACK=NO - SRS_HTTP_SERVER=NO - SRS_STREAM_CASTER=NO - SRS_HTTP_API=NO SRS_LIBRTMP=NO SRS_RESEARCH=NO SRS_UTEST=NO - SRS_GPERF=NO - SRS_GPERF_MC=NO - SRS_GPERF_MP=NO - SRS_GPERF_CP=NO - SRS_GPROF=NO - SRS_STATIC=NO - fi - - # if arm specified, set some default to disabled. - if [ $SRS_ARM_UBUNTU12 = YES ]; then - SRS_HLS=YES - SRS_HDS=YES - SRS_DVR=YES - SRS_NGINX=NO - SRS_SSL=YES - SRS_FFMPEG_TOOL=NO - SRS_TRANSCODE=YES - SRS_INGEST=YES - SRS_STAT=YES - SRS_HTTP_CORE=YES - SRS_HTTP_CALLBACK=YES - SRS_HTTP_SERVER=YES - SRS_STREAM_CASTER=NO - SRS_HTTP_API=YES - SRS_LIBRTMP=YES - SRS_RESEARCH=NO - SRS_UTEST=NO - SRS_GPERF=NO - SRS_GPERF_MC=NO - SRS_GPERF_MP=NO - SRS_GPERF_CP=NO - SRS_GPROF=NO - # TODO: FIXME: need static? maybe donot. - SRS_STATIC=YES - fi - - # if mips specified, set some default to disabled. - if [ $SRS_MIPS_UBUNTU12 = YES ]; then - SRS_HLS=YES - SRS_HDS=YES - SRS_DVR=YES - SRS_NGINX=NO - SRS_SSL=YES - SRS_FFMPEG_TOOL=NO - SRS_TRANSCODE=YES - SRS_INGEST=YES - SRS_STAT=YES - SRS_HTTP_CORE=YES - SRS_HTTP_CALLBACK=YES - SRS_HTTP_SERVER=YES - SRS_STREAM_CASTER=NO - SRS_HTTP_API=YES - SRS_LIBRTMP=YES - SRS_RESEARCH=NO - SRS_UTEST=NO - SRS_GPERF=NO - SRS_GPERF_MC=NO - SRS_GPERF_MP=NO - SRS_GPERF_CP=NO - SRS_GPROF=NO SRS_STATIC=NO fi # defaults for x86/x64 if [ $SRS_X86_X64 = YES ]; then - SRS_HLS=YES SRS_HDS=YES - SRS_DVR=YES - SRS_NGINX=NO - SRS_SSL=YES - SRS_FFMPEG_TOOL=NO - SRS_TRANSCODE=YES - SRS_INGEST=YES - SRS_STAT=YES - SRS_HTTP_CORE=YES - SRS_HTTP_CALLBACK=YES - SRS_HTTP_SERVER=YES - SRS_STREAM_CASTER=NO - SRS_HTTP_API=YES SRS_LIBRTMP=YES SRS_RESEARCH=NO - SRS_UTEST=YES - SRS_GPERF=NO - SRS_GPERF_MC=NO - SRS_GPERF_MP=NO - SRS_GPERF_CP=NO - SRS_GPROF=NO - SRS_STATIC=NO - fi - - # for osx(darwin) - if [ $SRS_OSX = YES ]; then - SRS_HLS=YES - SRS_HDS=YES - SRS_DVR=YES - SRS_NGINX=NO - SRS_SSL=YES - SRS_FFMPEG_TOOL=NO - SRS_TRANSCODE=YES - SRS_INGEST=YES - SRS_STAT=YES - SRS_HTTP_CORE=YES - SRS_HTTP_CALLBACK=YES - SRS_HTTP_SERVER=YES - SRS_STREAM_CASTER=NO - SRS_HTTP_API=YES - SRS_LIBRTMP=NO - SRS_RESEARCH=NO - SRS_UTEST=YES - SRS_GPERF=NO - SRS_GPERF_MC=NO - SRS_GPERF_MP=NO - SRS_GPERF_CP=NO - SRS_GPROF=NO + SRS_UTEST=NO SRS_STATIC=NO fi # if dev specified, open features if possible. if [ $SRS_DEV = YES ]; then - SRS_HLS=YES SRS_HDS=YES - SRS_DVR=YES - SRS_NGINX=NO - SRS_SSL=YES - SRS_FFMPEG_TOOL=YES - SRS_TRANSCODE=YES - SRS_INGEST=YES - SRS_STAT=YES - SRS_HTTP_CORE=YES - SRS_HTTP_CALLBACK=YES - SRS_HTTP_SERVER=YES - SRS_STREAM_CASTER=NO - SRS_HTTP_API=YES SRS_LIBRTMP=YES SRS_RESEARCH=YES SRS_UTEST=YES - SRS_GPERF=NO - SRS_GPERF_MC=NO - SRS_GPERF_MP=NO - SRS_GPERF_CP=NO - SRS_GPROF=NO SRS_STATIC=NO fi # if fast dev specified, open main server features. if [ $SRS_FAST_DEV = YES ]; then - SRS_HLS=YES SRS_HDS=YES - SRS_DVR=YES - SRS_NGINX=NO - SRS_SSL=YES - SRS_FFMPEG_TOOL=NO - SRS_TRANSCODE=YES - SRS_INGEST=YES - SRS_STAT=YES - SRS_HTTP_CORE=YES - SRS_HTTP_CALLBACK=YES - SRS_HTTP_SERVER=YES - SRS_STREAM_CASTER=NO - SRS_HTTP_API=YES SRS_LIBRTMP=NO SRS_RESEARCH=NO SRS_UTEST=NO - SRS_GPERF=NO - SRS_GPERF_MC=NO - SRS_GPERF_MP=NO - SRS_GPERF_CP=NO - SRS_GPROF=NO SRS_STATIC=NO fi # for srs demo if [ $SRS_DEMO = YES ]; then - SRS_HLS=YES SRS_HDS=YES - SRS_DVR=YES - SRS_NGINX=NO - SRS_SSL=YES - SRS_FFMPEG_TOOL=YES - SRS_TRANSCODE=YES - SRS_INGEST=YES - SRS_STAT=YES - SRS_HTTP_CORE=YES - SRS_HTTP_CALLBACK=YES - SRS_HTTP_SERVER=YES - SRS_STREAM_CASTER=NO - SRS_HTTP_API=YES SRS_LIBRTMP=YES SRS_RESEARCH=NO - SRS_UTEST=YES - SRS_GPERF=NO - SRS_GPERF_MC=NO - SRS_GPERF_MP=NO - SRS_GPERF_CP=NO - SRS_GPROF=NO + SRS_UTEST=NO SRS_STATIC=NO fi # if raspberry-pi specified, open ssl/hls/static features if [ $SRS_PI = YES ]; then - SRS_HLS=YES SRS_HDS=YES - SRS_DVR=YES - SRS_NGINX=NO - SRS_SSL=YES - SRS_FFMPEG_TOOL=NO - SRS_TRANSCODE=YES - SRS_INGEST=YES - SRS_STAT=YES - SRS_HTTP_CORE=YES - SRS_HTTP_CALLBACK=YES - SRS_HTTP_SERVER=YES - SRS_STREAM_CASTER=NO - SRS_HTTP_API=YES SRS_LIBRTMP=YES SRS_RESEARCH=NO SRS_UTEST=NO - SRS_GPERF=NO - SRS_GPERF_MC=NO - SRS_GPERF_MP=NO - SRS_GPERF_CP=NO - SRS_GPROF=NO SRS_STATIC=NO fi # if cubieboard specified, open features except ffmpeg/nginx. if [ $SRS_CUBIE = YES ]; then - SRS_HLS=YES SRS_HDS=YES - SRS_DVR=YES - SRS_NGINX=NO - SRS_SSL=YES - SRS_FFMPEG_TOOL=YES - SRS_TRANSCODE=YES - SRS_INGEST=YES - SRS_STAT=YES - SRS_HTTP_CORE=YES - SRS_HTTP_CALLBACK=YES - SRS_HTTP_SERVER=YES - SRS_STREAM_CASTER=NO - SRS_HTTP_API=YES SRS_LIBRTMP=YES SRS_RESEARCH=NO SRS_UTEST=NO - SRS_GPERF=NO - SRS_GPERF_MC=NO - SRS_GPERF_MP=NO - SRS_GPERF_CP=NO - SRS_GPROF=NO + SRS_STATIC=NO + fi + + # if crossbuild, disable research and librtmp. + if [[ $SRS_CROSS_BUILD == YES ]]; then + SRS_LIBRTMP=NO + SRS_RESEARCH=NO + SRS_UTEST=NO SRS_STATIC=NO fi } @@ -767,11 +462,18 @@ function apply_user_detail_options() { if [ $SRS_TRANSCODE = YES ]; then SRS_FFMPEG_STUB=YES; fi if [ $SRS_INGEST = YES ]; then SRS_FFMPEG_STUB=YES; fi - # if http-xxxx specified, open the SRS_HTTP_CORE - SRS_HTTP_CORE=NO - if [ $SRS_HTTP_CALLBACK = YES ]; then SRS_HTTP_CORE=YES; fi - if [ $SRS_HTTP_SERVER = YES ]; then SRS_HTTP_CORE=YES; fi - if [ $SRS_HTTP_API = YES ]; then SRS_HTTP_CORE=YES; fi + # Always enable HTTP utilies. + if [ $SRS_HTTP_CORE = NO ]; then SRS_HTTP_CORE=YES; echo -e "${YELLOW}[WARN] Always enable HTTP utilies.${BLACK}"; fi + if [ $SRS_STREAM_CASTER = NO ]; then SRS_STREAM_CASTER=YES; echo -e "${YELLOW}[WARN] Always enable StreamCaster.${BLACK}"; fi + if [ $SRS_INGEST = NO ]; then SRS_INGEST=YES; echo -e "${YELLOW}[WARN] Always enable Ingest.${BLACK}"; fi + if [ $SRS_SSL = NO ]; then SRS_SSL=YES; echo -e "${YELLOW}[WARN] Always enable SSL.${BLACK}"; fi + if [ $SRS_STAT = NO ]; then SRS_STAT=YES; echo -e "${YELLOW}[WARN] Always enable Statistic.${BLACK}"; fi + if [ $SRS_TRANSCODE = NO ]; then SRS_TRANSCODE=YES; echo -e "${YELLOW}[WARN] Always enable Transcode.${BLACK}"; fi + if [ $SRS_HTTP_CALLBACK = NO ]; then SRS_HTTP_CALLBACK=YES; echo -e "${YELLOW}[WARN] Always enable HTTP callback.${BLACK}"; fi + if [ $SRS_HTTP_SERVER = NO ]; then SRS_HTTP_SERVER=YES; echo -e "${YELLOW}[WARN] Always enable HTTP server.${BLACK}"; fi + if [ $SRS_HTTP_API = NO ]; then SRS_HTTP_API=YES; echo -e "${YELLOW}[WARN] Always enable HTTP API.${BLACK}"; fi + if [ $SRS_HLS = NO ]; then SRS_HLS=YES; echo -e "${YELLOW}[WARN] Always enable HLS.${BLACK}"; fi + if [ $SRS_DVR = NO ]; then SRS_DVR=YES; echo -e "${YELLOW}[WARN] Always enable DVR.${BLACK}"; fi # parse the jobs for make if [[ "" -eq SRS_JOBS ]]; then @@ -787,25 +489,19 @@ function apply_user_detail_options() { # disable almost all features for export srs-librtmp. if [ $SRS_EXPORT_LIBRTMP_PROJECT != NO ]; then - SRS_HLS=NO SRS_HDS=NO - SRS_DVR=NO - SRS_NGINX=NO SRS_SSL=NO - SRS_FFMPEG_TOOL=NO SRS_TRANSCODE=NO + SRS_HTTP_CALLBACK=NO SRS_INGEST=NO SRS_STAT=NO - SRS_HTTP_CORE=NO - SRS_HTTP_CALLBACK=NO - SRS_HTTP_SERVER=NO SRS_STREAM_CASTER=NO - SRS_HTTP_API=NO SRS_LIBRTMP=YES SRS_RESEARCH=YES SRS_UTEST=NO SRS_GPERF=NO SRS_GPERF_MC=NO + SRS_GPERF_MD=NO SRS_GPERF_MP=NO SRS_GPERF_CP=NO SRS_GPROF=NO @@ -822,9 +518,7 @@ SRS_AUTO_CONFIGURE="--prefix=${SRS_PREFIX}" if [ $SRS_HLS = YES ]; then SRS_AUTO_CONFIGURE="${SRS_AUTO_CONFIGURE} --with-hls"; else SRS_AUTO_CONFIGURE="${SRS_AUTO_CONFIGURE} --without-hls"; fi if [ $SRS_HDS = YES ]; then SRS_AUTO_CONFIGURE="${SRS_AUTO_CONFIGURE} --with-hds"; else SRS_AUTO_CONFIGURE="${SRS_AUTO_CONFIGURE} --without-hds"; fi if [ $SRS_DVR = YES ]; then SRS_AUTO_CONFIGURE="${SRS_AUTO_CONFIGURE} --with-dvr"; else SRS_AUTO_CONFIGURE="${SRS_AUTO_CONFIGURE} --without-dvr"; fi - if [ $SRS_NGINX = YES ]; then SRS_AUTO_CONFIGURE="${SRS_AUTO_CONFIGURE} --with-nginx"; else SRS_AUTO_CONFIGURE="${SRS_AUTO_CONFIGURE} --without-nginx"; fi if [ $SRS_SSL = YES ]; then SRS_AUTO_CONFIGURE="${SRS_AUTO_CONFIGURE} --with-ssl"; else SRS_AUTO_CONFIGURE="${SRS_AUTO_CONFIGURE} --without-ssl"; fi - if [ $SRS_FFMPEG_TOOL = YES ]; then SRS_AUTO_CONFIGURE="${SRS_AUTO_CONFIGURE} --with-ffmpeg"; else SRS_AUTO_CONFIGURE="${SRS_AUTO_CONFIGURE} --without-ffmpeg"; fi if [ $SRS_TRANSCODE = YES ]; then SRS_AUTO_CONFIGURE="${SRS_AUTO_CONFIGURE} --with-transcode"; else SRS_AUTO_CONFIGURE="${SRS_AUTO_CONFIGURE} --without-transcode"; fi if [ $SRS_INGEST = YES ]; then SRS_AUTO_CONFIGURE="${SRS_AUTO_CONFIGURE} --with-ingest"; else SRS_AUTO_CONFIGURE="${SRS_AUTO_CONFIGURE} --without-ingest"; fi if [ $SRS_STAT = YES ]; then SRS_AUTO_CONFIGURE="${SRS_AUTO_CONFIGURE} --with-stat"; else SRS_AUTO_CONFIGURE="${SRS_AUTO_CONFIGURE} --without-stat"; fi @@ -837,16 +531,25 @@ SRS_AUTO_CONFIGURE="--prefix=${SRS_PREFIX}" if [ $SRS_UTEST = YES ]; then SRS_AUTO_CONFIGURE="${SRS_AUTO_CONFIGURE} --with-utest"; else SRS_AUTO_CONFIGURE="${SRS_AUTO_CONFIGURE} --without-utest"; fi if [ $SRS_GPERF = YES ]; then SRS_AUTO_CONFIGURE="${SRS_AUTO_CONFIGURE} --with-gperf"; else SRS_AUTO_CONFIGURE="${SRS_AUTO_CONFIGURE} --without-gperf"; fi if [ $SRS_GPERF_MC = YES ]; then SRS_AUTO_CONFIGURE="${SRS_AUTO_CONFIGURE} --with-gmc"; else SRS_AUTO_CONFIGURE="${SRS_AUTO_CONFIGURE} --without-gmc"; fi + if [ $SRS_GPERF_MD = YES ]; then SRS_AUTO_CONFIGURE="${SRS_AUTO_CONFIGURE} --with-gmd"; else SRS_AUTO_CONFIGURE="${SRS_AUTO_CONFIGURE} --without-gmd"; fi if [ $SRS_GPERF_MP = YES ]; then SRS_AUTO_CONFIGURE="${SRS_AUTO_CONFIGURE} --with-gmp"; else SRS_AUTO_CONFIGURE="${SRS_AUTO_CONFIGURE} --without-gmp"; fi if [ $SRS_GPERF_CP = YES ]; then SRS_AUTO_CONFIGURE="${SRS_AUTO_CONFIGURE} --with-gcp"; else SRS_AUTO_CONFIGURE="${SRS_AUTO_CONFIGURE} --without-gcp"; fi if [ $SRS_GPROF = YES ]; then SRS_AUTO_CONFIGURE="${SRS_AUTO_CONFIGURE} --with-gprof"; else SRS_AUTO_CONFIGURE="${SRS_AUTO_CONFIGURE} --without-gprof"; fi - if [ $SRS_ARM_UBUNTU12 = YES ]; then SRS_AUTO_CONFIGURE="${SRS_AUTO_CONFIGURE} --with-arm-ubuntu12"; else SRS_AUTO_CONFIGURE="${SRS_AUTO_CONFIGURE} --without-arm-ubuntu12"; fi - if [ $SRS_MIPS_UBUNTU12 = YES ]; then SRS_AUTO_CONFIGURE="${SRS_AUTO_CONFIGURE} --with-mips-ubuntu12"; else SRS_AUTO_CONFIGURE="${SRS_AUTO_CONFIGURE} --without-mips-ubuntu12"; fi if [ $SRS_STATIC = YES ]; then SRS_AUTO_CONFIGURE="${SRS_AUTO_CONFIGURE} --static"; fi + if [ $SRS_SHARED_ST = YES ]; then SRS_AUTO_CONFIGURE="${SRS_AUTO_CONFIGURE} --use-shared-st"; fi if [ $SRS_LOG_VERBOSE = YES ]; then SRS_AUTO_CONFIGURE="${SRS_AUTO_CONFIGURE} --log-verbose"; fi if [ $SRS_LOG_INFO = YES ]; then SRS_AUTO_CONFIGURE="${SRS_AUTO_CONFIGURE} --log-info"; fi if [ $SRS_LOG_TRACE = YES ]; then SRS_AUTO_CONFIGURE="${SRS_AUTO_CONFIGURE} --log-trace"; fi - echo "regenerate config: ${SRS_AUTO_CONFIGURE}" + if [ $SRS_GCOV = YES ]; then SRS_AUTO_CONFIGURE="${SRS_AUTO_CONFIGURE} --gcov"; fi + if [[ $SRS_EXTRA_FLAGS != '' ]]; then SRS_AUTO_CONFIGURE="${SRS_AUTO_CONFIGURE} --extra-flags=\\\"$SRS_EXTRA_FLAGS\\\""; fi + if [[ $SRS_BUILD_TAG != '' ]]; then SRS_AUTO_CONFIGURE="${SRS_AUTO_CONFIGURE} --build-tag=\\\"$SRS_BUILD_TAG\\\""; fi + if [[ $SRS_TOOL_CC != '' ]]; then SRS_AUTO_CONFIGURE="${SRS_AUTO_CONFIGURE} --cc=$SRS_TOOL_CC"; fi + if [[ $SRS_TOOL_CXX != '' ]]; then SRS_AUTO_CONFIGURE="${SRS_AUTO_CONFIGURE} --cxx=$SRS_TOOL_CXX"; fi + if [[ $SRS_TOOL_AR != '' ]]; then SRS_AUTO_CONFIGURE="${SRS_AUTO_CONFIGURE} --ar=$SRS_TOOL_AR"; fi + if [[ $SRS_TOOL_LD != '' ]]; then SRS_AUTO_CONFIGURE="${SRS_AUTO_CONFIGURE} --ld=$SRS_TOOL_LD"; fi + if [[ $SRS_TOOL_RANDLIB != '' ]]; then SRS_AUTO_CONFIGURE="${SRS_AUTO_CONFIGURE} --randlib=$SRS_TOOL_RANDLIB"; fi + echo "User config: $SRS_AUTO_USER_CONFIGURE" + echo "Detail config: ${SRS_AUTO_CONFIGURE}" } regenerate_options @@ -854,10 +557,25 @@ regenerate_options # check user options ##################################################################################### function check_option_conflicts() { + if [[ $SRS_TOOL_CC == '' || $SRS_TOOL_CXX == '' || $SRS_TOOL_AR == '' || $SRS_TOOL_LD == '' || $SRS_TOOL_RANDLIB == '' ]]; then + echo "No crossbuild tools, cc: $SRS_TOOL_CC, cxx: $SRS_TOOL_CXX, ar: $SRS_TOOL_AR, ld: $SRS_TOOL_LD, randlib: $SRS_TOOL_RANDLIB"; exit -1 + fi + + if [[ $SRS_CROSS_BUILD == YES && ($SRS_TOOL_CC == 'gcc' || $SRS_TOOL_CXX == 'g++' || $SRS_TOOL_AR == 'ar') ]]; then + echo "For crossbuild, must not use default toolchain, cc: $SRS_TOOL_CC, cxx: $SRS_TOOL_CXX, ar: $SRS_TOOL_AR"; exit -1 + fi + + if [[ $SRS_NGINX == YES ]]; then + echo "Don't support building NGINX, please use docker https://github.com/ossrs/srs-docker"; exit -1; + fi + + # TODO: FIXME: check more os. + __check_ok=YES # check conflict if [ $SRS_GPERF = NO ]; then if [ $SRS_GPERF_MC = YES ]; then echo "gperf-mc depends on gperf, see: ./configure --help"; __check_ok=NO; fi + if [ $SRS_GPERF_MD = YES ]; then echo "gperf-md depends on gperf, see: ./configure --help"; __check_ok=NO; fi if [ $SRS_GPERF_MP = YES ]; then echo "gperf-mp depends on gperf, see: ./configure --help"; __check_ok=NO; fi if [ $SRS_GPERF_CP = YES ]; then echo "gperf-cp depends on gperf, see: ./configure --help"; __check_ok=NO; fi fi @@ -867,71 +585,32 @@ function check_option_conflicts() { echo "Note that since the heap-checker uses the heap-profiling framework internally, it is not possible to run both the heap-checker and heap profiler at the same time"; __check_ok=NO fi - if [[ $SRS_HTTP_CORE = NO && $SRS_STREAM_CASTER = YES ]]; then - echo "stream-caster depends on http-api or http-server, see: ./configure --help"; __check_ok=NO; - fi # generate the group option: SRS_GPERF __gperf_slow=NO if [ $SRS_GPERF_MC = YES ]; then SRS_GPERF=YES; __gperf_slow=YES; fi + if [ $SRS_GPERF_MD = YES ]; then SRS_GPERF=YES; __gperf_slow=YES; fi if [ $SRS_GPERF_MP = YES ]; then SRS_GPERF=YES; __gperf_slow=YES; fi if [ $SRS_GPERF_CP = YES ]; then SRS_GPERF=YES; __gperf_slow=YES; fi if [ $__gperf_slow = YES ]; then if [ $SRS_GPROF = YES ]; then echo "gmc/gmp/gcp not compatible with gprof, see: ./configure --help"; __check_ok=NO; fi fi - # check embeded(arm/mips), if embeded enabled, only allow st/ssl/librtmp, - # user should disable all other features - if [ $SRS_CROSS_BUILD = YES ]; then - if [ $SRS_FFMPEG_TOOL = YES ]; then echo "ffmpeg for arm is not available, see: ./configure --help"; __check_ok=NO; fi - if [ $SRS_RESEARCH = YES ]; then echo "research for arm is not available, see: ./configure --help"; __check_ok=NO; fi - if [ $SRS_GPERF = YES ]; then echo "gperf for arm is not available, see: ./configure --help"; __check_ok=NO; fi - if [ $SRS_GPERF_MC = YES ]; then echo "gmc for arm is not available, see: ./configure --help"; __check_ok=NO; fi - if [ $SRS_GPERF_MP = YES ]; then echo "gmp for arm is not available, see: ./configure --help"; __check_ok=NO; fi - if [ $SRS_GPERF_CP = YES ]; then echo "gcp for arm is not available, see: ./configure --help"; __check_ok=NO; fi - if [ $SRS_GPROF = YES ]; then echo "gprof for arm is not available, see: ./configure --help"; __check_ok=NO; fi - fi - - # if x86/x64 or directly build, never use static - if [[ $SRS_X86_X64 = YES && $SRS_STATIC = YES ]]; then - echo "x86/x64 should never use static, see: ./configure --help"; __check_ok=NO; - fi - - # TODO: FIXME: check more os. - # check variable neccessary - if [ $SRS_HLS = RESERVED ]; then echo "you must specifies the hls, see: ./configure --help"; __check_ok=NO; fi if [ $SRS_HDS = RESERVED ]; then echo "you must specifies the hds, see: ./configure --help"; __check_ok=NO; fi - if [ $SRS_DVR = RESERVED ]; then echo "you must specifies the dvr, see: ./configure --help"; __check_ok=NO; fi - if [ $SRS_NGINX = RESERVED ]; then echo "you must specifies the nginx, see: ./configure --help"; __check_ok=NO; fi if [ $SRS_SSL = RESERVED ]; then echo "you must specifies the ssl, see: ./configure --help"; __check_ok=NO; fi - if [ $SRS_FFMPEG_TOOL = RESERVED ]; then echo "you must specifies the ffmpeg, see: ./configure --help"; __check_ok=NO; fi - if [ $SRS_HTTP_CALLBACK = RESERVED ]; then echo "you must specifies the http-callback, see: ./configure --help"; __check_ok=NO; fi - if [ $SRS_HTTP_SERVER = RESERVED ]; then echo "you must specifies the http-server, see: ./configure --help"; __check_ok=NO; fi if [ $SRS_STREAM_CASTER = RESERVED ]; then echo "you must specifies the stream-caster, see: ./configure --help"; __check_ok=NO; fi - if [ $SRS_HTTP_API = RESERVED ]; then echo "you must specifies the http-api, see: ./configure --help"; __check_ok=NO; fi if [ $SRS_LIBRTMP = RESERVED ]; then echo "you must specifies the librtmp, see: ./configure --help"; __check_ok=NO; fi if [ $SRS_RESEARCH = RESERVED ]; then echo "you must specifies the research, see: ./configure --help"; __check_ok=NO; fi if [ $SRS_UTEST = RESERVED ]; then echo "you must specifies the utest, see: ./configure --help"; __check_ok=NO; fi if [ $SRS_GPERF = RESERVED ]; then echo "you must specifies the gperf, see: ./configure --help"; __check_ok=NO; fi if [ $SRS_GPERF_MC = RESERVED ]; then echo "you must specifies the gperf-mc, see: ./configure --help"; __check_ok=NO; fi + if [ $SRS_GPERF_MD = RESERVED ]; then echo "you must specifies the gperf-md, see: ./configure --help"; __check_ok=NO; fi if [ $SRS_GPERF_MP = RESERVED ]; then echo "you must specifies the gperf-mp, see: ./configure --help"; __check_ok=NO; fi if [ $SRS_GPERF_CP = RESERVED ]; then echo "you must specifies the gperf-cp, see: ./configure --help"; __check_ok=NO; fi if [ $SRS_GPROF = RESERVED ]; then echo "you must specifies the gprof, see: ./configure --help"; __check_ok=NO; fi - if [ $SRS_ARM_UBUNTU12 = RESERVED ]; then echo "you must specifies the arm-ubuntu12, see: ./configure --help"; __check_ok=NO; fi - if [ $SRS_MIPS_UBUNTU12 = RESERVED ]; then echo "you must specifies the mips-ubuntu12, see: ./configure --help"; __check_ok=NO; fi if [[ -z $SRS_PREFIX ]]; then echo "you must specifies the prefix, see: ./configure --prefix"; __check_ok=NO; fi if [ $__check_ok = NO ]; then exit 1; fi - - if [[ $SRS_OSX == YES && $SRS_ALLOW_OSX == NO ]]; then - macOSVersion=`sw_vers -productVersion` - macOSVersionMajor=`echo $macOSVersion|awk -F '.' '{print $1}'` - macOSVersionMinor=`echo $macOSVersion|awk -F '.' '{print $2}'` - if [[ $macOSVersionMajor -ge 10 && $macOSVersionMinor -ge 14 ]]; then - echo "macOS $macOSVersion is not supported, read https://github.com/ossrs/srs/issues/1250" - exit -1 - fi - fi } check_option_conflicts diff --git a/trunk/auto/setup_variables.sh b/trunk/auto/setup_variables.sh old mode 100644 new mode 100755 index d1d643b4f..73c5793f7 --- a/trunk/auto/setup_variables.sh +++ b/trunk/auto/setup_variables.sh @@ -1,30 +1,29 @@ #!/bin/bash # when options parsed, setup some variables, then build the depends. +OS_KERNEL_NAME=$(uname -s) +OS_KERNRL_RELEASE=$(uname -r|awk -F '-' '{print $1}') +OS_PREFIX="Platform" -# when arm specified, setup the cross build variables. -if [ $SRS_ARM_UBUNTU12 = YES ]; then - __SrsArmCC="arm-linux-gnueabi-gcc"; - __SrsArmGCC="arm-linux-gnueabi-gcc"; - __SrsArmCXX="arm-linux-gnueabi-g++"; - __SrsArmAR="arm-linux-gnueabi-ar"; - __SrsArmLD="arm-linux-gnueabi-ld"; - __SrsArmRANDLIB="arm-linux-gnueabi-ranlib"; +# Build platform cache. +SRS_PLATFORM="${OS_PREFIX}-${OS_KERNEL_NAME}-${OS_KERNRL_RELEASE}" +if [[ ${SRS_BUILD_TAG} != "" ]]; then + SRS_PLATFORM="${SRS_PLATFORM}-${SRS_BUILD_TAG}" fi +# Use isolate cache for different SRS version. +SRS_PLATFORM="${SRS_PLATFORM}-SRS3" -if [ $SRS_MIPS_UBUNTU12 = YES ]; then - __SrsArmCC="mipsel-openwrt-linux-gcc"; - __SrsArmGCC="mipsel-openwrt-linux-gcc"; - __SrsArmCXX="mipsel-openwrt-linux-g++"; - __SrsArmAR="mipsel-openwrt-linux-ar"; - __SrsArmLD="mipsel-openwrt-linux-ld"; - __SrsArmRANDLIB="mipsel-openwrt-linux-ranlib"; -fi +echo "SRS_WORKDIR: ${SRS_WORKDIR}, SRS_OBJS_DIR: ${SRS_OBJS_DIR}, SRS_OBJS: ${SRS_OBJS}, SRS_PLATFORM: ${SRS_PLATFORM}" + +# For src object files on each platform. +( + mkdir -p ${SRS_OBJS_DIR} && cd ${SRS_OBJS_DIR} && + rm -rf src utest srs srs_utest research include lib srs_hls_ingester srs_mp4_parser && + mkdir -p ${SRS_PLATFORM}/src && ln -sf ${SRS_PLATFORM}/src && + mkdir -p ${SRS_PLATFORM}/utest && ln -sf ${SRS_PLATFORM}/utest && + mkdir -p ${SRS_PLATFORM}/research && ln -sf ${SRS_PLATFORM}/research && + mkdir -p ${SRS_PLATFORM}/include && ln -sf ${SRS_PLATFORM}/include && + mkdir -p ${SRS_PLATFORM}/lib && ln -sf ${SRS_PLATFORM}/lib +) +echo "Fast cleanup, if need to do full cleanup, please use: make clean" -# the arm-ubuntu12 options for make for depends -if [[ -z $SrsArmCC ]]; then SrsArmCC=$__SrsArmCC; fi -if [[ -z $SrsArmGCC ]]; then SrsArmGCC=$__SrsArmGCC; fi -if [[ -z $SrsArmCXX ]]; then SrsArmCXX=$__SrsArmCXX; fi -if [[ -z $SrsArmAR ]]; then SrsArmAR=$__SrsArmAR; fi -if [[ -z $SrsArmLD ]]; then SrsArmLD=$__SrsArmLD; fi -if [[ -z $SrsArmRANDLIB ]]; then SrsArmRANDLIB=$__SrsArmRANDLIB; fi diff --git a/trunk/auto/summary.sh b/trunk/auto/summary.sh index f9884a562..0af1673b1 100755 --- a/trunk/auto/summary.sh +++ b/trunk/auto/summary.sh @@ -1,26 +1,28 @@ #!/bin/bash # colorful summary -SrsHlsSummaryColor="\${YELLOW}{disabled} "; if [ $SRS_HLS = YES ]; then SrsHlsSummaryColor="\${GREEN}"; fi -SrsDvrSummaryColor="\${YELLOW}{disabled} "; if [ $SRS_DVR = YES ]; then SrsDvrSummaryColor="\${GREEN}"; fi -SrsNginxSummaryColor="\${GREEN}{disabled} "; if [ $SRS_NGINX = YES ]; then SrsNginxSummaryColor="\${GREEN}"; fi -SrsSslSummaryColor="\${YELLOW}{disabled} "; if [ $SRS_SSL = YES ]; then SrsSslSummaryColor="\${GREEN}"; fi -SrsFfmpegSummaryColor="\${YELLOW}{disabled} "; if [ $SRS_FFMPEG_TOOL = YES ]; then SrsFfmpegSummaryColor="\${GREEN}"; fi -SrsTranscodeSummaryColor="\${YELLOW}{disabled} "; if [ $SRS_TRANSCODE = YES ]; then SrsTranscodeSummaryColor="\${GREEN}"; fi -SrsIngestSummaryColor="\${YELLOW}{disabled} "; if [ $SRS_INGEST = YES ]; then SrsIngestSummaryColor="\${GREEN}"; fi -SrsHttpCallbackSummaryColor="\${YELLOW}{disabled} "; if [ $SRS_HTTP_CALLBACK = YES ]; then SrsHttpCallbackSummaryColor="\${GREEN}"; fi -SrsHttpServerSummaryColor="\${YELLOW}{disabled} "; if [ $SRS_HTTP_SERVER = YES ]; then SrsHttpServerSummaryColor="\${GREEN}"; fi -SrsHttpApiSummaryColor="\${YELLOW}{disabled} "; if [ $SRS_HTTP_API = YES ]; then SrsHttpApiSummaryColor="\${GREEN}"; fi -SrsStreamCasterSummaryColor="\${YELLOW}{disabled} "; if [ $SRS_STREAM_CASTER = YES ]; then SrsStreamCasterSummaryColor="\${GREEN}"; fi -SrsLibrtmpSummaryColor="\${YELLOW}{disabled} "; if [ $SRS_LIBRTMP = YES ]; then SrsLibrtmpSummaryColor="\${GREEN}"; fi -SrsLibrtmpSSLSummaryColor="\${YELLOW}{disabled} "; if [ $SRS_LIBRTMP = YES ]; then if [ $SRS_SSL = YES ]; then SrsLibrtmpSSLSummaryColor="\${GREEN}"; fi fi -SrsResearchSummaryColor="\${GREEN}{disabled} "; if [ $SRS_RESEARCH = YES ]; then SrsResearchSummaryColor="\${GREEN}"; fi -SrsUtestSummaryColor="\${YELLOW}{disabled} "; if [ $SRS_UTEST = YES ]; then SrsUtestSummaryColor="\${GREEN}"; fi -SrsGperfSummaryColor="\${GREEN}{disabled} "; if [ $SRS_GPERF = YES ]; then SrsGperfSummaryColor="\${GREEN}"; fi -SrsGperfMCSummaryColor="\${GREEN}{disabled} "; if [ $SRS_GPERF_MC = YES ]; then SrsGperfMCSummaryColor="\${YELLOW}"; fi -SrsGperfMPSummaryColor="\${GREEN}{disabled} "; if [ $SRS_GPERF_MP = YES ]; then SrsGperfMPSummaryColor="\${YELLOW}"; fi -SrsGperfCPSummaryColor="\${GREEN}{disabled} "; if [ $SRS_GPERF_CP = YES ]; then SrsGperfCPSummaryColor="\${YELLOW}"; fi -SrsGprofSummaryColor="\${GREEN}{disabled} "; if [ $SRS_GPROF = YES ]; then SrsGprofSummaryColor="\${YELLOW}"; fi +SrsHlsSummaryColor="\${YELLOW}(Disabled) "; if [ $SRS_HLS = YES ]; then SrsHlsSummaryColor="\${GREEN}"; fi +SrsDvrSummaryColor="\${YELLOW}(Disabled) "; if [ $SRS_DVR = YES ]; then SrsDvrSummaryColor="\${GREEN}"; fi +SrsNginxSummaryColor="\${GREEN}(Disabled) "; if [ $SRS_NGINX = YES ]; then SrsNginxSummaryColor="\${GREEN}"; fi +SrsSslSummaryColor="\${YELLOW}(Disabled) "; if [ $SRS_SSL = YES ]; then SrsSslSummaryColor="\${GREEN}"; fi +SrsFfmpegSummaryColor="\${YELLOW}(Disabled) "; if [ $SRS_FFMPEG_TOOL = YES ]; then SrsFfmpegSummaryColor="\${GREEN}"; fi +SrsTranscodeSummaryColor="\${YELLOW}(Disabled) "; if [ $SRS_TRANSCODE = YES ]; then SrsTranscodeSummaryColor="\${GREEN}"; fi +SrsIngestSummaryColor="\${YELLOW}(Disabled) "; if [ $SRS_INGEST = YES ]; then SrsIngestSummaryColor="\${GREEN}"; fi +SrsHttpCallbackSummaryColor="\${YELLOW}(Disabled) "; if [ $SRS_HTTP_CALLBACK = YES ]; then SrsHttpCallbackSummaryColor="\${GREEN}"; fi +SrsHttpServerSummaryColor="\${YELLOW}(Disabled) "; if [ $SRS_HTTP_SERVER = YES ]; then SrsHttpServerSummaryColor="\${GREEN}"; fi +SrsHttpApiSummaryColor="\${YELLOW}(Disabled) "; if [ $SRS_HTTP_API = YES ]; then SrsHttpApiSummaryColor="\${GREEN}"; fi +SrsStreamCasterSummaryColor="\${YELLOW}(Disabled) "; if [ $SRS_STREAM_CASTER = YES ]; then SrsStreamCasterSummaryColor="\${GREEN}"; fi +SrsLibrtmpSummaryColor="\${YELLOW}(Disabled) "; if [ $SRS_LIBRTMP = YES ]; then SrsLibrtmpSummaryColor="\${GREEN}"; fi +SrsLibrtmpSSLSummaryColor="\${YELLOW}(Disabled) "; if [ $SRS_LIBRTMP = YES ]; then if [ $SRS_SSL = YES ]; then SrsLibrtmpSSLSummaryColor="\${GREEN}"; fi fi +SrsResearchSummaryColor="\${GREEN}(Disabled) "; if [ $SRS_RESEARCH = YES ]; then SrsResearchSummaryColor="\${GREEN}"; fi +SrsUtestSummaryColor="\${YELLOW}(Disabled) "; if [ $SRS_UTEST = YES ]; then SrsUtestSummaryColor="\${GREEN}"; fi +SrsGperfSummaryColor="\${GREEN}(Disabled) "; if [ $SRS_GPERF = YES ]; then SrsGperfSummaryColor="\${GREEN}"; fi +SrsGperfMCSummaryColor="\${GREEN}(Disabled) "; if [ $SRS_GPERF_MC = YES ]; then SrsGperfMCSummaryColor="\${YELLOW}"; fi +SrsGperfMDSummaryColor="\${GREEN}(Disabled) "; if [ $SRS_GPERF_MD = YES ]; then SrsGperfMDSummaryColor="\${YELLOW}"; fi +SrsGperfMPSummaryColor="\${GREEN}(Disabled) "; if [ $SRS_GPERF_MP = YES ]; then SrsGperfMPSummaryColor="\${YELLOW}"; fi +SrsGperfCPSummaryColor="\${GREEN}(Disabled) "; if [ $SRS_GPERF_CP = YES ]; then SrsGperfCPSummaryColor="\${YELLOW}"; fi +SrsGprofSummaryColor="\${GREEN}(Disabled) "; if [ $SRS_GPROF = YES ]; then SrsGprofSummaryColor="\${YELLOW}"; fi +SrsValgrindSummaryColor="\${YELLOW}(Disabled) "; if [ $SRS_VALGRIND = YES ]; then SrsValgrindSummaryColor="\${GREEN}"; fi if [ $SRS_EXPORT_LIBRTMP_PROJECT = NO ]; then cat < ${SRS_OBJS}/${SRS_BUILD_SUMMARY} @@ -33,76 +35,24 @@ GREEN="\\${GREEN}" YELLOW="\\${YELLOW}" BLACK="\\${BLACK}" -echo -e "\${GREEN}build summary:\${BLACK}" +echo -e "\${GREEN}The build summary:\${BLACK}" echo -e " \${BLACK}+------------------------------------------------------------------------------------\${BLACK}" -echo -e " |${SrsGperfSummaryColor}gperf @see: https://github.com/ossrs/srs/wiki/v1_CN_GPERF\${BLACK}" -echo -e " | ${SrsGperfMCSummaryColor}gmc @see: http://google-perftools.googlecode.com/svn/trunk/doc/heap_checker.html\${BLACK}" -echo -e " | ${SrsGperfMCSummaryColor}gmc: gperf memory check, or memory leak detect\${BLACK}" -echo -e " | ${SrsGperfMCSummaryColor}env PPROF_PATH=./objs/pprof HEAPCHECK=normal ./objs/srs -c conf/console.conf 2>gmc.log # start gmc\${BLACK}" -echo -e " | ${SrsGperfMCSummaryColor}killall -2 srs # or CTRL+C to stop gmc\${BLACK}" -echo -e " | ${SrsGperfMCSummaryColor}cat gmc.log # to analysis memory leak\${BLACK}" -echo -e " | ${SrsGperfMPSummaryColor}gmp @see: http://google-perftools.googlecode.com/svn/trunk/doc/heapprofile.html\${BLACK}" -echo -e " | ${SrsGperfMPSummaryColor}gmp: gperf memory profile, similar to gcp\${BLACK}" -echo -e " | ${SrsGperfMPSummaryColor}rm -f gperf.srs.gmp*; ./objs/srs -c conf/console.conf # start gmp\${BLACK}" -echo -e " | ${SrsGperfMPSummaryColor}killall -2 srs # or CTRL+C to stop gmp\${BLACK}" -echo -e " | ${SrsGperfMPSummaryColor}./objs/pprof --text objs/srs gperf.srs.gmp* # to analysis memory profile\${BLACK}" -echo -e " | ${SrsGperfCPSummaryColor}gcp @see: http://google-perftools.googlecode.com/svn/trunk/doc/cpuprofile.html\${BLACK}" -echo -e " | ${SrsGperfCPSummaryColor}gcp: gperf cpu profile\${BLACK}" -echo -e " | ${SrsGperfCPSummaryColor}rm -f gperf.srs.gcp*; ./objs/srs -c conf/console.conf # start gcp\${BLACK}" -echo -e " | ${SrsGperfCPSummaryColor}killall -2 srs # or CTRL+C to stop gcp\${BLACK}" -echo -e " | ${SrsGperfCPSummaryColor}./objs/pprof --text objs/srs gperf.srs.gcp* # to analysis cpu profile\${BLACK}" +echo -e " \${GREEN}For SRS benchmark, gperf, gprof and valgrind, please read:\${BLACK}" +echo -e " \${GREEN} http://blog.csdn.net/win_lin/article/details/53503869\${BLACK}" echo -e " \${BLACK}+------------------------------------------------------------------------------------\${BLACK}" -echo -e " |${SrsGprofSummaryColor}gprof @see: https://github.com/ossrs/srs/wiki/v1_CN_GPROF\${BLACK}" -echo -e " |${SrsGprofSummaryColor}gprof: GNU profile tool, @see: http://www.cs.utah.edu/dept/old/texinfo/as/gprof.html\${BLACK}" -echo -e " | ${SrsGprofSummaryColor}rm -f gmon.out; ./objs/srs -c conf/console.conf # start gprof\${BLACK}" -echo -e " | ${SrsGprofSummaryColor}killall -2 srs # or CTRL+C to stop gprof\${BLACK}" -echo -e " | ${SrsGprofSummaryColor}gprof -b ./objs/srs gmon.out > gprof.srs.log && rm -f gmon.out # gprof report to gprof.srs.log\${BLACK}" +echo -e " |\${GREEN}The main server usage: ./objs/srs -c conf/srs.conf, start the srs server\${BLACK}" +echo -e " | ${SrsHlsSummaryColor}About HLS, please read https://github.com/ossrs/srs/wiki/v2_CN_DeliveryHLS\${BLACK}" +echo -e " | ${SrsDvrSummaryColor}About DVR, please read https://github.com/ossrs/srs/wiki/v3_CN_DVR\${BLACK}" +echo -e " | ${SrsSslSummaryColor}About SSL, please read https://github.com/ossrs/srs/wiki/v1_CN_RTMPHandshake\${BLACK}" +echo -e " | ${SrsTranscodeSummaryColor}About transcoding, please read https://github.com/ossrs/srs/wiki/v3_CN_FFMPEG\${BLACK}" +echo -e " | ${SrsIngestSummaryColor}About ingester, please read https://github.com/ossrs/srs/wiki/v1_CN_Ingest\${BLACK}" +echo -e " | ${SrsHttpCallbackSummaryColor}About http-callback, please read https://github.com/ossrs/srs/wiki/v3_CN_HTTPCallback\${BLACK}" +echo -e " | ${SrsHttpServerSummaryColor}Aoubt http-server, please read https://github.com/ossrs/srs/wiki/v2_CN_HTTPServer\${BLACK}" +echo -e " | ${SrsHttpApiSummaryColor}About http-api, please read https://github.com/ossrs/srs/wiki/v3_CN_HTTPApi\${BLACK}" +echo -e " | ${SrsStreamCasterSummaryColor}About stream-caster, please read https://github.com/ossrs/srs/wiki/v2_CN_Streamer\${BLACK}" +echo -e " | ${SrsValgrindSummaryColor}About VALGRIND, please read https://github.com/ossrs/state-threads/issues/2\${BLACK}" echo -e " \${BLACK}+------------------------------------------------------------------------------------\${BLACK}" -echo -e " |${SrsUtestSummaryColor}utest: ./objs/srs_utest, the utest for srs\${BLACK}" -echo -e " \${BLACK}+------------------------------------------------------------------------------------\${BLACK}" -echo -e " |${SrsLibrtmpSummaryColor}librtmp @see: https://github.com/ossrs/srs/wiki/v1_CN_SrsLibrtmp\${BLACK}" -echo -e " |${SrsLibrtmpSummaryColor}librtmp: ./objs/include, ./objs/lib, the srs-librtmp library\${BLACK}" -echo -e " | ${SrsLibrtmpSummaryColor}simple handshake: publish/play stream with simple handshake to server\${BLACK}" -echo -e " | ${SrsLibrtmpSSLSummaryColor}complex handshake: it's not required for client, recommend disable it\${BLACK}" -echo -e " | ${SrsLibrtmpSummaryColor}librtmp-sample: ./research/librtmp, the srs-librtmp client sample\${BLACK}" -echo -e " | ${SrsLibrtmpSummaryColor}librtmp-sample: ./research/librtmp/objs/srs_ingest_flv\${BLACK}" -echo -e " | ${SrsLibrtmpSummaryColor}librtmp-sample: ./research/librtmp/objs/srs_ingest_rtmp\${BLACK}" -echo -e " | ${SrsLibrtmpSummaryColor}librtmp-sample: ./research/librtmp/objs/srs_detect_rtmp\${BLACK}" -echo -e " | ${SrsLibrtmpSummaryColor}librtmp-sample: ./research/librtmp/objs/srs_bandwidth_check\${BLACK}" -echo -e " \${BLACK}+------------------------------------------------------------------------------------\${BLACK}" -echo -e " |${SrsResearchSummaryColor}research: ./objs/research, api server, players, ts info, librtmp.\${BLACK}" -echo -e " | ${SrsResearchSummaryColor} @see https://github.com/ossrs/srs/wiki/v2_CN_SrsLibrtmp#srs-librtmp-examples\${BLACK}" -echo -e " \${BLACK}+------------------------------------------------------------------------------------\${BLACK}" -echo -e " |\${GREEN}tools: important tool, others @see https://github.com/ossrs/srs/wiki/v2_CN_SrsLibrtmp#srs-librtmp-examples\${BLACK}" -echo -e " | \${GREEN}./objs/srs_ingest_hls -i http://ossrs.net/live/livestream.m3u8 -y rtmp://127.0.0.1/live/livestream\${BLACK}" -echo -e " \${BLACK}+------------------------------------------------------------------------------------\${BLACK}" -echo -e " |\${GREEN}server: ./objs/srs -c conf/srs.conf, start the srs server\${BLACK}" -echo -e " | ${SrsHlsSummaryColor}hls @see: https://github.com/ossrs/srs/wiki/v2_CN_DeliveryHLS\${BLACK}" -echo -e " | ${SrsHlsSummaryColor}hls: generate m3u8 and ts from rtmp stream\${BLACK}" -echo -e " | ${SrsDvrSummaryColor}dvr @see: https://github.com/ossrs/srs/wiki/v2_CN_DVR\${BLACK}" -echo -e " | ${SrsDvrSummaryColor}dvr: record RTMP stream to flv files.\${BLACK}" -echo -e " | ${SrsNginxSummaryColor}nginx @see: https://github.com/ossrs/srs/wiki/v2_CN_DeliveryHLS\${BLACK}" -echo -e " | ${SrsNginxSummaryColor}nginx: delivery HLS stream by nginx\${BLACK}" -echo -e " | ${SrsNginxSummaryColor}nginx: sudo ./objs/nginx/sbin/nginx\${BLACK}" -echo -e " | ${SrsSslSummaryColor}ssl @see: https://github.com/ossrs/srs/wiki/v1_CN_RTMPHandshake\${BLACK}" -echo -e " | ${SrsSslSummaryColor}ssl: support RTMP complex handshake for client required, for instance, flash\${BLACK}" -echo -e " | ${SrsFfmpegSummaryColor}ffmpeg @see: https://github.com/ossrs/srs/wiki/v1_CN_FFMPEG\${BLACK}" -echo -e " | ${SrsFfmpegSummaryColor}ffmpeg: transcode, mux, ingest tool\${BLACK}" -echo -e " | ${SrsFfmpegSummaryColor}ffmpeg: ./objs/ffmpeg/bin/ffmpeg\${BLACK}" -echo -e " | ${SrsTranscodeSummaryColor}transcode @see: https://github.com/ossrs/srs/wiki/v1_CN_FFMPEG\${BLACK}" -echo -e " | ${SrsTranscodeSummaryColor}transcode: support transcoding RTMP stream\${BLACK}" -echo -e " | ${SrsIngestSummaryColor}ingest @see: https://github.com/ossrs/srs/wiki/v1_CN_Ingest\${BLACK}" -echo -e " | ${SrsIngestSummaryColor}ingest: support ingest file/stream/device then push to SRS by RTMP stream\${BLACK}" -echo -e " | ${SrsHttpCallbackSummaryColor}http-callback @see: https://github.com/ossrs/srs/wiki/v2_CN_HTTPCallback\${BLACK}" -echo -e " | ${SrsHttpCallbackSummaryColor}http-callback: support http callback for authentication and event injection\${BLACK}" -echo -e " | ${SrsHttpServerSummaryColor}http-server @see: https://github.com/ossrs/srs/wiki/v2_CN_HTTPServer\${BLACK}" -echo -e " | ${SrsHttpServerSummaryColor}http-server: support http server to delivery http stream\${BLACK}" -echo -e " | ${SrsHttpApiSummaryColor}http-api @see: https://github.com/ossrs/srs/wiki/v2_CN_HTTPApi\${BLACK}" -echo -e " | ${SrsHttpApiSummaryColor}http-api: support http api to manage server\${BLACK}" -echo -e " | ${SrsStreamCasterSummaryColor}stream-caster @see: https://github.com/ossrs/srs/wiki/v2_CN_Streamer\${BLACK}" -echo -e " | ${SrsStreamCasterSummaryColor}stream-caster: start server to cast stream over other protocols.\${BLACK}" -echo -e " \${BLACK}+------------------------------------------------------------------------------------\${BLACK}" -echo -e "\${GREEN}binaries @see: https://github.com/ossrs/srs/wiki/v2_CN_Build\${BLACK}" +echo -e "\${GREEN}binaries, please read https://github.com/ossrs/srs/wiki/v2_CN_Build\${BLACK}" echo "You can:" echo " ./objs/srs -c conf/srs.conf" diff --git a/trunk/auto/utest.sh b/trunk/auto/utest.sh index 393802c6a..b359d4211 100755 --- a/trunk/auto/utest.sh +++ b/trunk/auto/utest.sh @@ -16,19 +16,10 @@ mkdir -p ${SRS_OBJS}/utest # the prefix to generate the objs/utest/Makefile # dirs relative to current dir(objs/utest), it's trunk/objs/utest # trunk of srs, which contains the src dir, relative to objs/utest, it's trunk -SRS_TRUNK_PREFIX=../.. +SRS_TRUNK_PREFIX=../../.. # gest dir, relative to objs/utest, it's trunk/objs/gtest GTEST_DIR=${SRS_TRUNK_PREFIX}/${SRS_OBJS_DIR}/gtest -# the extra defines to compile utest. -EXTRA_DEFINES="" - -# for osx to disable the error. -# gtest/include/gtest/internal/gtest-port.h:499:13: fatal error: 'tr1/tuple' file not found -if [ $SRS_OSX = YES ]; then - EXTRA_DEFINES="$EXTRA_DEFINES -DGTEST_HAS_TR1_TUPLE=0" -fi - cat << END > ${FILE} # user must run make the ${SRS_OBJS_DIR}/utest dir # at the same dir of Makefile. @@ -173,13 +164,17 @@ echo "" >> ${FILE} echo "# link all depends libraries" >> ${FILE} echo -n "DEPS_LIBRARIES_FILES = " >> ${FILE} for item in ${ModuleLibFiles[*]}; do - echo -n "${SRS_TRUNK_PREFIX}/${item} " >> ${FILE} + if [[ -f ${item} ]]; then + echo -n "${SRS_TRUNK_PREFIX}/${item} " >> ${FILE} + else + echo -n "${item} " >> ${FILE} + fi done echo "" >> ${FILE}; echo "" >> ${FILE} # echo "# generate the utest binary" >> ${FILE} cat << END >> ${FILE} -${SRS_TRUNK_PREFIX}/${SRS_OBJS_DIR}/${APP_NAME} : \$(SRS_UTEST_DEPS) ${MODULE_OBJS} gtest_main.a +${SRS_TRUNK_PREFIX}/${SRS_OBJS_DIR}/${APP_NAME} : \$(SRS_UTEST_DEPS) ${MODULE_OBJS} gtest.a \$(CXX) -o \$@ \$(CPPFLAGS) \$(CXXFLAGS) \$^ \$(DEPS_LIBRARIES_FILES) ${LINK_OPTIONS} END @@ -187,4 +182,4 @@ END # parent Makefile, to create module output dir before compile it. echo " @mkdir -p ${SRS_OBJS_DIR}/utest" >> ${SRS_WORKDIR}/${SRS_MAKEFILE} -echo -n "generate utest ok"; echo '!'; +echo -n "Generate utest ok"; echo '!'; diff --git a/trunk/conf/compatible.conf b/trunk/conf/compatible.conf new file mode 100644 index 000000000..6b7a2042f --- /dev/null +++ b/trunk/conf/compatible.conf @@ -0,0 +1,79 @@ +listen 1935; +pid ./objs/srs.pid; +srs_log_tank console; +srs_log_level trace; +max_connections 1000; +daemon off; +http_api { + enabled on; + listen 1985; + crossdomain on; + raw_api { + enabled on; + allow_reload on; + allow_query on; + allow_update on; + } +} +# for SRS1. +http_stream { + enabled on; + listen 8080; + dir ./objs/nginx/html; +} +vhost __defaultVhost__ { + # for SRS1. + http { + enabled on; + mount [vhost]/hls; + dir ./objs/nginx/html/hls; + } + + # for SRS1. + refer github.com github.io; + refer_publish github.com github.io; + refer_play github.com github.io; + + # for SRS2 + publish_1stpkt_timeout 20000; + publish_normal_timeout 7000; + + # for SRS2 + mr { + enabled off; + latency 350; + } + + # for SRS1 + mode remote; + origin 127.0.0.1:1935 localhost:1935; + + token_traverse off; + vhost same.edge.srs.com; + + debug_srs_upnode off; + + # for SRS1 + forward 127.0.0.1:1936 127.0.0.1:1937; + + # for SRS1 + time_jitter full; + + # for SRS2 + mix_correct off; + + #for SRS1 + atc on; + atc_auto on; + + # for SRS2 + mw_latency 100; + + # for SRS1 + gop_cache off; + queue_length 10; + + # for SRS2 + send_min_interval 10.0; + reduce_sequence_header on; +} diff --git a/trunk/conf/dash.conf b/trunk/conf/dash.conf new file mode 100644 index 000000000..9d7f21fdd --- /dev/null +++ b/trunk/conf/dash.conf @@ -0,0 +1,23 @@ +# the config for srs to delivery dash +# @see https://github.com/ossrs/srs/wiki/v1_CN_SampleDASH +# @see full.conf for detail config. + +listen 1935; +max_connections 1000; +daemon off; +srs_log_tank console; +http_server { + enabled on; + listen 8080; + dir ./objs/nginx/html; +} +vhost __defaultVhost__ { + dash { + enabled on; + dash_fragment 30; + dash_update_period 150; + dash_timeshift 300; + dash_path ./objs/nginx/html; + dash_mpd_file [app]/[stream].mpd; + } +} diff --git a/trunk/conf/demo.19350.conf b/trunk/conf/demo.19350.conf index a6dd3ef98..c27e57500 100644 --- a/trunk/conf/demo.19350.conf +++ b/trunk/conf/demo.19350.conf @@ -11,7 +11,9 @@ pid ./objs/srs.demo.19350.pid; vhost __defaultVhost__ { enabled on; - gop_cache on; + play { + gop_cache on; + } hls { enabled on; hls_path ./objs/nginx/html/forward; diff --git a/trunk/conf/demo.conf b/trunk/conf/demo.conf index b6fb87aa1..4b1ef9879 100644 --- a/trunk/conf/demo.conf +++ b/trunk/conf/demo.conf @@ -44,7 +44,10 @@ vhost demo.srs.com { enabled on; gop_cache on; queue_length 30; - forward 127.0.0.1:19350; + forward { + enabled on; + destination 127.0.0.1:19350; + } bandcheck { enabled off; } diff --git a/trunk/conf/docker.conf b/trunk/conf/docker.conf new file mode 100644 index 000000000..a72ab137d --- /dev/null +++ b/trunk/conf/docker.conf @@ -0,0 +1,29 @@ +# main config for srs. +# @see full.conf for detail config. + +listen 1935; +max_connections 1000; +srs_log_tank console; +daemon off; +http_api { + enabled on; + listen 1985; +} +http_server { + enabled on; + listen 8080; + dir ./objs/nginx/html; +} +stats { + network 0; + disk sda sdb xvda xvdb; +} +vhost __defaultVhost__ { + hls { + enabled on; + } + http_remux { + enabled on; + mount [vhost]/[app]/[stream].flv; + } +} diff --git a/trunk/conf/dvr.mp4.conf b/trunk/conf/dvr.mp4.conf new file mode 100644 index 000000000..dccb8c3d6 --- /dev/null +++ b/trunk/conf/dvr.mp4.conf @@ -0,0 +1,13 @@ +# the config for srs to dvr in session mode +# @see https://github.com/ossrs/srs/wiki/v3_CN_DVR +# @see full.conf for detail config. + +listen 1935; +max_connections 1000; +vhost __defaultVhost__ { + dvr { + enabled on; + dvr_path ./objs/nginx/html/[app]/[stream].[timestamp].mp4; + dvr_plan session; + } +} diff --git a/trunk/conf/dvr.path.conf b/trunk/conf/dvr.path.conf index c5951f0db..bd43b4e8b 100644 --- a/trunk/conf/dvr.path.conf +++ b/trunk/conf/dvr.path.conf @@ -1,6 +1,6 @@ # the config for srs to dvr in custom path. -# @see https://github.com/ossrs/srs/wiki/v2_CN_DVR#custom-path -# @see https://github.com/ossrs/srs/wiki/v2_EN_DVR#custom-path +# @see https://github.com/ossrs/srs/wiki/v3_CN_DVR#custom-path +# @see https://github.com/ossrs/srs/wiki/v3_EN_DVR#custom-path # @see full.conf for detail config. listen 1935; diff --git a/trunk/conf/dvr.segment.conf b/trunk/conf/dvr.segment.conf index 2265ad07e..2d6bf1bf8 100644 --- a/trunk/conf/dvr.segment.conf +++ b/trunk/conf/dvr.segment.conf @@ -1,5 +1,5 @@ # the config for srs to dvr in segment mode -# @see https://github.com/ossrs/srs/wiki/v2_CN_DVR +# @see https://github.com/ossrs/srs/wiki/v3_CN_DVR # @see full.conf for detail config. listen 1935; diff --git a/trunk/conf/dvr.session.conf b/trunk/conf/dvr.session.conf index 8e8d89351..385980944 100644 --- a/trunk/conf/dvr.session.conf +++ b/trunk/conf/dvr.session.conf @@ -1,5 +1,5 @@ # the config for srs to dvr in session mode -# @see https://github.com/ossrs/srs/wiki/v2_CN_DVR +# @see https://github.com/ossrs/srs/wiki/v3_CN_DVR # @see full.conf for detail config. listen 1935; diff --git a/trunk/conf/edge.conf b/trunk/conf/edge.conf index 2a8662f3c..d4233b31f 100644 --- a/trunk/conf/edge.conf +++ b/trunk/conf/edge.conf @@ -5,9 +5,9 @@ listen 1935; max_connections 1000; pid objs/edge.pid; -daemon off; -srs_log_tank console; vhost __defaultVhost__ { - mode remote; - origin 127.0.0.1:19350; + cluster { + mode remote; + origin 127.0.0.1:19350; + } } diff --git a/trunk/conf/edge.token.traverse.conf b/trunk/conf/edge.token.traverse.conf index bad2412f7..6fe35b7e2 100644 --- a/trunk/conf/edge.token.traverse.conf +++ b/trunk/conf/edge.token.traverse.conf @@ -7,7 +7,9 @@ max_connections 1000; daemon off; srs_log_tank console; vhost __defaultVhost__ { - mode remote; - origin 127.0.0.1:19350; - token_traverse on; + cluster { + mode remote; + origin 127.0.0.1:19350; + token_traverse on; + } } diff --git a/trunk/conf/edge2.conf b/trunk/conf/edge2.conf new file mode 100644 index 000000000..52d469a44 --- /dev/null +++ b/trunk/conf/edge2.conf @@ -0,0 +1,13 @@ +# the config for srs origin-edge cluster +# @see https://github.com/ossrs/srs/wiki/v1_CN_Edge +# @see full.conf for detail config. + +listen 1935; +max_connections 1000; +pid objs/edge2.pid; +vhost __defaultVhost__ { + cluster { + mode remote; + origin 127.0.0.1:19350; + } +} diff --git a/trunk/conf/exec.conf b/trunk/conf/exec.conf new file mode 100644 index 000000000..6390217d6 --- /dev/null +++ b/trunk/conf/exec.conf @@ -0,0 +1,12 @@ +# the config for srs to support nginx-rtmp exec. +# @see https://github.com/ossrs/srs/wiki/v3_CN_NgExec +# @see full.conf for detail config. + +listen 1935; +max_connections 1000; +vhost __defaultVhost__ { + exec { + enabled on; + publish ./objs/ffmpeg/bin/ffmpeg -f flv -i [url] -c copy -y ./[stream].flv; + } +} diff --git a/trunk/conf/ffmpeg.transcode.conf b/trunk/conf/ffmpeg.transcode.conf index d785a44ff..1bb505556 100644 --- a/trunk/conf/ffmpeg.transcode.conf +++ b/trunk/conf/ffmpeg.transcode.conf @@ -4,8 +4,6 @@ listen 1935; max_connections 1000; -daemon off; -srs_log_tank console; vhost __defaultVhost__ { transcode { enabled on; diff --git a/trunk/conf/forward.master.conf b/trunk/conf/forward.master.conf index af6d502ed..2366f3195 100644 --- a/trunk/conf/forward.master.conf +++ b/trunk/conf/forward.master.conf @@ -5,8 +5,9 @@ listen 1935; max_connections 1000; pid ./objs/srs.master.pid; -daemon off; -srs_log_tank console; vhost __defaultVhost__ { - forward 127.0.0.1:19350; + forward { + enabled on; + destination 127.0.0.1:19350; + } } diff --git a/trunk/conf/forward.slave.conf b/trunk/conf/forward.slave.conf index 98286e33e..f4cc1a37f 100644 --- a/trunk/conf/forward.slave.conf +++ b/trunk/conf/forward.slave.conf @@ -5,7 +5,5 @@ listen 19350; max_connections 1000; pid ./objs/srs.slave.pid; -daemon off; -srs_log_tank console; vhost __defaultVhost__ { } diff --git a/trunk/conf/full.conf b/trunk/conf/full.conf index 4c088705e..8e8d15270 100644 --- a/trunk/conf/full.conf +++ b/trunk/conf/full.conf @@ -9,21 +9,26 @@ listen 1935; # the pid file # to ensure only one process can use a pid file -# and provides the current running process id, for script, +# and provides the current running process id, for script, # for example, init.d script to manage the server. # default: ./objs/srs.pid pid ./objs/srs.pid; # the default chunk size is 128, max is 65536, # some client does not support chunk size change, -# however, most clients supports it and it can improve +# however, most clients support it and it can improve # performance about 10%. # default: 60000 chunk_size 60000; -# the logs dir. -# if enabled ffmpeg, each stracoding stream will create a log file. +# the log dir for FFMPEG. +# if enabled ffmpeg, each transcoding stream will create a log file. # /dev/null to disable the log. # default: ./objs ff_log_dir ./objs; +# the log level for FFMPEG. +# info warning error fatal panic quiet +# trace debug verbose +# default: info +ff_log_level info; # the log tank, console or file. # if console, print log to console. # if file, write log to file. requires srs_log_file if log to file. @@ -41,7 +46,7 @@ srs_log_file ./objs/srs.log; # default: 1000 max_connections 1000; # whether start as daemon -# @remark: donot support reload. +# @remark: do not support reload. # default: on daemon on; # whether use utc_time to generate the time struct, @@ -49,6 +54,11 @@ daemon on; # if on, use gmtime() instead, which use UTC time. # default: off utc_time off; +# config for the pithy print in ms, +# which always print constant message specified by interval, +# whatever the clients in concurrency. +# default: 10000 +pithy_print_ms 10000; # the work dir for server, to chdir(work_dir) when not empty or "./" # user can config this directory to change the dir. @@ -62,6 +72,38 @@ work_dir ./; # @reamrk do not support reload. # default: off asprocess off; +# Whether client empty IP is ok, for example, health checking by SLB. +# If ok(on), we will ignore this connection without warnings or errors. +# default: on +empty_ip_ok on; + +# For gracefully quit, wait for a while then close listeners, +# because K8S notify SRS with SIGQUIT and update Service simultaneously, +# maybe there is some new connections incoming before Service updated. +# @see https://github.com/ossrs/srs/issues/1595#issuecomment-587516567 +# default: 2300 +grace_start_wait 2300; +# For gracefully quit, final wait for cleanup in milliseconds. +# @see https://github.com/ossrs/srs/issues/1579#issuecomment-587414898 +# default: 3200 +grace_final_wait 3200; +# Whether force gracefully quit, never fast quit. +# By default, SIGTERM which means fast quit, is sent by K8S, so we need to +# force SRS to treat SIGTERM as gracefully quit for gray release or canary. +# @see https://github.com/ossrs/srs/issues/1579#issuecomment-587475077 +# default: off +force_grace_quit off; +# Whether disable daemon for docker. +# If on, it will set daemon to off in docker, even daemon is on. +# default: on +disable_daemon_for_docker on; +# Whether auto reload by watching the config file by inotify. +# default: off +inotify_auto_reload off; +# Whether enable inotify_auto_reload for docker. +# If on, it will set inotify_auto_reload to on in docker, even it's off. +# default: on +auto_reload_for_docker on; # Query the latest available version of SRS, write a log to notice user to upgrade. # @see https://github.com/ossrs/srs/issues/2424 @@ -75,7 +117,7 @@ query_latest_version on; # @remark, the ip report to server, is retrieve from system stat, # which need the config item stats.network. heartbeat { - # whether heartbeat is enalbed. + # whether heartbeat is enabled. # default: off enabled off; # the interval seconds for heartbeat, @@ -90,7 +132,7 @@ heartbeat { # } # default: http://127.0.0.1:8085/api/v1/servers url http://127.0.0.1:8085/api/v1/servers; - # the id of devide. + # the id of device. device_id "my-srs-device"; # whether report with summaries # if on, put /api/v1/summaries to the request data: @@ -114,7 +156,7 @@ stats { # default: 0 network 0; # the device name to stat the disk iops. - # ignore the device of /proc/diskstats if not configed. + # ignore the device of /proc/diskstats if not configured. disk sda sdb xvda xvdb; } @@ -139,16 +181,31 @@ http_api { # whether enable crossdomain request. # default: on crossdomain on; + # the HTTP RAW API is more powerful api to change srs state and reload. + raw_api { + # whether enable the HTTP RAW API. + # default: off + enabled off; + # whether enable rpc reload. + # default: off + allow_reload off; + # whether enable rpc query. + # default: off + allow_query off; + # whether enable rpc update. + # default: off + allow_update off; + } } -# embeded http server in srs. +# embedded http server in srs. # the http streaming config, for HLS/HDS/DASH/HTTPProgressive # global config for http streaming, user must config the http section for each vhost. # the embed http server used to substitute nginx in ./objs/nginx, -# for example, srs runing in arm, can provides RTMP and HTTP service, only with srs installed. +# for example, srs running in arm, can provides RTMP and HTTP service, only with srs installed. # user can access the http server pages, generally: # curl http://192.168.1.170:80/srs.html # which will show srs version and welcome to srs. -# @remark, the http embeded stream need to config the vhost, for instance, the __defaultVhost__ +# @remark, the http embedded stream need to config the vhost, for instance, the __defaultVhost__ # need to open the feature http of vhost. http_server { # whether http streaming service is enabled. @@ -163,6 +220,10 @@ http_server { # the default dir for http root. # default: ./objs/nginx/html dir ./objs/nginx/html; + # whether enable crossdomain request. + # for both http static and stream server and apply on all vhosts. + # default: on + crossdomain on; } ############################################################################################# @@ -177,16 +238,22 @@ stream_caster { # the caster type of stream, the casters: # mpegts_over_udp, MPEG-TS over UDP caster. # rtsp, Real Time Streaming Protocol (RTSP). - # flv, FLV over HTTP POST. + # flv, FLV over HTTP by POST. caster mpegts_over_udp; # the output rtmp url. # for mpegts_over_udp caster, the typically output url: - # rtmp://127.0.0.1/live/livestream + # rtmp://127.0.0.1/live/livestream # for rtsp caster, the typically output url: - # rtmp://127.0.0.1/[app]/[stream] + # rtmp://127.0.0.1/[app]/[stream] # for example, the rtsp url: # rtsp://192.168.1.173:8544/live/livestream.sdp - # where the [app] is "live" and [stream] is "livestream", output is: + # where the [app] is "live" and [stream] is "livestream", output is: + # rtmp://127.0.0.1/live/livestream + # for flv caster, the typically output url: + # rtmp://127.0.0.1/[app]/[stream] + # for example, POST to url: + # http://127.0.0.1:8936/live/livestream.flv + # where the [app] is "live" and [stream] is "livestream", output is: # rtmp://127.0.0.1/live/livestream output rtmp://127.0.0.1/live/livestream; # the listen port for stream caster. @@ -231,10 +298,425 @@ stream_caster { vhost __defaultVhost__ { } +# the vhost scope configs. +vhost scope.vhost.srs.com { + # whether the vhost is enabled. + # if off, all request access denied. + # default: on + enabled off; + + # whether enable min delay mode for vhost. + # for min latency mode: + # 1. disable the publish.mr for vhost. + # 2. use timeout for cond wait for consumer queue. + # @see https://github.com/ossrs/srs/issues/257 + # default: off + min_latency off; + + # whether enable the TCP_NODELAY + # if on, set the nodelay of fd by setsockopt + # default: off + tcp_nodelay off; + + # the default chunk size is 128, max is 65536, + # some client does not support chunk size change, + # vhost chunk size will override the global value. + # default: global chunk size. + chunk_size 128; + + # The input ack size, 0 to not set. + # Generally, it's set by the message from peer, + # but for some peer(encoder), it never send message but use a different ack size. + # We can chnage the default ack size in server-side, to send acknowledge message, + # or the encoder maybe blocked after publishing for some time. + # Default: 0 + in_ack_size 0; + + # The output ack size, 0 to not set. + # This is used to notify the peer(player) to send acknowledge to server. + # Default: 2500000 + out_ack_size 2500000; +} + +# set the chunk size of vhost. +vhost chunksize.srs.com { + # @see scope.vhost.srs.com + chunk_size 128; +} + +# the vhost disabled. +vhost removed.srs.com { + # @see scope.vhost.srs.com + enabled off; +} + +# vhost for stream cluster for RTMP/FLV +vhost cluster.srs.com { + # The config for cluster. + cluster { + # The cluster mode, local or remote. + # local: It's an origin server, serve streams itself. + # remote: It's an edge server, fetch or push stream to origin server. + # default: local + mode remote; + + # For edge(mode remote), user must specifies the origin server + # format as: [:port] + # @remark user can specifies multiple origin for error backup, by space, + # for example, 192.168.1.100:1935 192.168.1.101:1935 192.168.1.102:1935 + origin 127.0.0.1:1935 localhost:1935; + + # For edge(mode remote), whether open the token traverse mode, + # if token traverse on, all connections of edge will forward to origin to check(auth), + # it's very important for the edge to do the token auth. + # the better way is use http callback to do the token auth by the edge, + # but if user prefer origin check(auth), the token_traverse if better solution. + # default: off + token_traverse off; + + # For edge(mode remote), the vhost to transform for edge, + # to fetch from the specified vhost at origin, + # if not specified, use the current vhost of edge in origin, the variable [vhost]. + # default: [vhost] + vhost same.edge.srs.com; + + # For edge(mode remote), when upnode(forward to, edge push to, edge pull from) is srs, + # it's strongly recommend to open the debug_srs_upnode, + # when connect to upnode, it will take the debug info, + # for example, the id, source id, pid. + # please see: https://github.com/ossrs/srs/wiki/v1_CN_SrsLog + # default: on + debug_srs_upnode on; + + # For origin(mode local) cluster, turn on the cluster. + # @remark Origin cluster only supports RTMP, use Edge to transmux RTMP to FLV. + # default: off + # TODO: FIXME: Support reload. + origin_cluster off; + + # For origin (mode local) cluster, the co-worker's HTTP APIs. + # This origin will connect to co-workers and communicate with them. + # please read: https://github.com/ossrs/srs/wiki/v3_EN_OriginCluster + # TODO: FIXME: Support reload. + coworkers 127.0.0.1:9091 127.0.0.1:9092; + } +} + +# vhost for edge, edge and origin is the same vhost +vhost same.edge.srs.com { + # @see cluster.srs.com + cluster { + mode remote; + origin 127.0.0.1:1935 localhost:1935; + token_traverse off; + } +} + +# vhost for edge, edge transform vhost to fetch from another vhost. +vhost transform.edge.srs.com { + # @see cluster.srs.com + cluster { + mode remote; + origin 127.0.0.1:1935; + vhost same.edge.srs.com; + } +} + +# the vhost for srs debug info, whether send args in connect(tcUrl). +vhost debug.srs.com { + # @see cluster.srs.com + cluster { + debug_srs_upnode on; + } +} + +# the vhost which forward publish streams. +vhost same.vhost.forward.srs.com { + # forward stream to other servers. + forward { + # whether enable the forward. + # default: off + enabled on; + # forward all publish stream to the specified server. + # this used to split/forward the current stream for cluster active-standby, + # active-active for cdn to build high available fault tolerance system. + # format: {ip}:{port} {ip_N}:{port_N} + destination 127.0.0.1:1936 127.0.0.1:1937; + } +} + +# the play specified configs +vhost play.srs.com { + # for play client, both RTMP and other stream clients, + # for instance, the HTTP FLV stream clients. + play { + # whether cache the last gop. + # if on, cache the last gop and dispatch to client, + # to enabled fast startup for client, client play immediately. + # if off, send the latest media data to client, + # client need to wait for the next Iframe to decode and show the video. + # set to off if requires min delay; + # set to on if requires client fast startup. + # default: on + gop_cache off; + # the max live queue length in seconds. + # if the messages in the queue exceed the max length, + # drop the old whole gop. + # default: 30 + queue_length 10; + + # about the stream monotonically increasing: + # 1. video timestamp is monotonically increasing, + # 2. audio timestamp is monotonically increasing, + # 3. video and audio timestamp is interleaved/mixed monotonically increasing. + # it's specified by RTMP specification, @see 3. Byte Order, Alignment, and Time Format + # however, some encoder cannot provides this feature, please set this to off to ignore time jitter. + # the time jitter algorithm: + # 1. full, to ensure stream start at zero, and ensure stream monotonically increasing. + # 2. zero, only ensure stream start at zero, ignore timestamp jitter. + # 3. off, disable the time jitter algorithm, like atc. + # @remark for full, correct timestamp only when |delta| > 250ms. + # @remark disabled when atc is on. + # default: full + time_jitter full; + # vhost for atc for hls/hds/rtmp backup. + # generally, atc default to off, server delivery rtmp stream to client(flash) timestamp from 0. + # when atc is on, server delivery rtmp stream by absolute time. + # atc is used, for instance, encoder will copy stream to master and slave server, + # server use atc to delivery stream to edge/client, where stream time from master/slave server + # is always the same, client/tools can slice RTMP stream to HLS according to the same time, + # if the time not the same, the HLS stream cannot slice to support system backup. + # + # @see http://www.adobe.com/cn/devnet/adobe-media-server/articles/varnish-sample-for-failover.html + # @see http://www.baidu.com/#wd=hds%20hls%20atc + # + # @remark when atc is on, auto off the time_jitter + # default: off + atc off; + # whether use the interleaved/mixed algorithm to correct the timestamp. + # if on, always ensure the timestamp of audio+video is interleaved/mixed monotonically increase. + # if off, use time_jitter to correct the timestamp if required. + # @remark to use mix_correct, atc should on(or time_jitter should off). + # default: off + mix_correct off; + + # whether enable the auto atc, + # if enabled, detect the bravo_atc="true" in onMetaData packet, + # set atc to on if matched. + # always ignore the onMetaData if atc_auto is off. + # default: off + atc_auto off; + + # set the MW(merged-write) latency in ms. + # SRS always set mw on, so we just set the latency value. + # the latency of stream >= mw_latency + mr_latency + # the value recomment is [300, 1800] + # default: 350 + mw_latency 350; + + # the minimal packets send interval in ms, + # used to control the ndiff of stream by srs_rtmp_dump, + # for example, some device can only accept some stream which + # delivery packets in constant interval(not cbr). + # @remark 0 to disable the minimal interval. + # @remark >0 to make the srs to send message one by one. + # @remark user can get the right packets interval in ms by srs_rtmp_dump. + # default: 0 + send_min_interval 10.0; + # whether reduce the sequence header, + # for some client which cannot got duplicated sequence header, + # while the sequence header is not changed yet. + # default: off + reduce_sequence_header on; + } +} + +# vhost for time jitter +vhost jitter.srs.com { + # @see play.srs.com + # to use time_jitter full, the default config. + play { + } + # to use mix_correct. + play { + time_jitter off; + mix_correct on; + } + play { + atc on; + mix_correct on; + } + # to use atc + play { + atc on; + } +} + +# vhost for atc. +vhost atc.srs.com { + # @see play.srs.com + play { + atc on; + atc_auto on; + } +} + +# the MR(merged-read) setting for publisher. +# the MW(merged-write) settings for player. +vhost mrw.srs.com { + # @see scope.vhost.srs.com + min_latency off; + + # @see play.srs.com + play { + mw_latency 350; + } + + # @see publish.srs.com + publish { + mr on; + mr_latency 350; + } +} + +# the vhost for min delay, do not cache any stream. +vhost min.delay.com { + # @see scope.vhost.srs.com + min_latency on; + # @see scope.vhost.srs.com + tcp_nodelay on; + + # @see play.srs.com + play { + mw_latency 100; + gop_cache off; + queue_length 10; + } + + # @see publish.srs.com + publish { + mr off; + } +} + +# whether disable the sps parse, for the resolution of video. +vhost no.parse.sps.com { + # @see publish.srs.com + publish { + parse_sps on; + } +} + +# the vhost to control the stream delivery feature +vhost stream.control.com { + # @see scope.vhost.srs.com + min_latency on; + # @see scope.vhost.srs.com + tcp_nodelay on; + + # @see play.srs.com + play { + mw_latency 100; + queue_length 10; + send_min_interval 10.0; + reduce_sequence_header on; + } + + # @see publish.srs.com + publish { + mr off; + firstpkt_timeout 20000; + normal_timeout 7000; + } +} + +# the publish specified configs +vhost publish.srs.com { + # the config for FMLE/Flash publisher, which push RTMP to SRS. + publish { + # about MR, read https://github.com/ossrs/srs/issues/241 + # when enabled the mr, SRS will read as large as possible. + # default: off + mr off; + # the latency in ms for MR(merged-read), + # the performance+ when latency+, and memory+, + # memory(buffer) = latency * kbps / 8 + # for example, latency=500ms, kbps=3000kbps, each publish connection will consume + # memory = 500 * 3000 / 8 = 187500B = 183KB + # when there are 2500 publisher, the total memory of SRS at least: + # 183KB * 2500 = 446MB + # the recommended value is [300, 2000] + # default: 350 + mr_latency 350; + + # the 1st packet timeout in ms for encoder. + # default: 20000 + firstpkt_timeout 20000; + # the normal packet timeout in ms for encoder. + # default: 5000 + normal_timeout 7000; + # whether parse the sps when publish stream. + # we can got the resolution of video for stat api. + # but we may failed to cause publish failed. + # default: on + parse_sps on; + } +} + +# the vhost for anti-suck. +vhost refer.anti_suck.com { + # refer hotlink-denial. + refer { + # whether enable the refer hotlink-denial. + # default: off. + enabled on; + # the common refer for play and publish. + # if the page url of client not in the refer, access denied. + # if not specified this field, allow all. + # default: not specified. + all github.com github.io; + # refer for publish clients specified. + # the common refer is not overridden by this. + # if not specified this field, allow all. + # default: not specified. + publish github.com github.io; + # refer for play clients specified. + # the common refer is not overridden by this. + # if not specified this field, allow all. + # default: not specified. + play github.com github.io; + } +} + +# vhost for bwt(bandwidth check) +# generally, the bandcheck vhost must be: bandcheck.srs.com, +# or need to modify the vhost of client. +vhost bandcheck.srs.com { + enabled on; + chunk_size 65000; + # bandwidth check config. + bandcheck { + # whether support bandwidth check, + # default: off. + enabled on; + # the key for server to valid, + # if invalid key, server disconnect and abort the bandwidth check. + key "35c9b402c12a7246868752e2878f7e0e"; + # the interval in seconds for bandwidth check, + # server do not allow new test request. + # default: 30 + interval 30; + # the max available check bandwidth in kbps. + # to avoid attack of bandwidth check. + # default: 1000 + limit_kbps 4000; + } +} + # the security to allow or deny clients. vhost security.srs.com { # security for host to allow or deny clients. - # @see https://github.com/ossrs/srs/issues/211 + # @see https://github.com/ossrs/srs/issues/211 security { # whether enable the security for vhost. # default: off @@ -260,195 +742,6 @@ vhost security.srs.com { } } -# the MR(merged-read) setting for publisher. -# the MW(merged-write) settings for player. -vhost mrw.srs.com { - # whether enable min delay mode for vhost. - # for min latence mode: - # 1. disable the mr for vhost. - # 2. use timeout for cond wait for consumer queue. - # @see https://github.com/ossrs/srs/issues/257 - # default: off - min_latency off; - # about MR, read https://github.com/ossrs/srs/issues/241 - mr { - # whether enable the MR(merged-read) - # default: off - enabled on; - # the latency in ms for MR(merged-read), - # the performance+ when latency+, and memory+, - # memory(buffer) = latency * kbps / 8 - # for example, latency=500ms, kbps=3000kbps, each publish connection will consume - # memory = 500 * 3000 / 8 = 187500B = 183KB - # when there are 2500 publisher, the total memory of SRS atleast: - # 183KB * 2500 = 446MB - # the value recomment is [300, 2000] - # default: 350 - latency 350; - } - # set the MW(merged-write) latency in ms. - # SRS always set mw on, so we just set the latency value. - # the latency of stream >= mw_latency + mr_latency - # the value recomment is [300, 1800] - # default: 350 - mw_latency 350; -} - -# vhost for edge, edge and origin is the same vhost -vhost same.edge.srs.com { - # the mode of vhost, local or remote. - # local: vhost is origin vhost, which provides stream source. - # remote: vhost is edge vhost, which pull/push to origin. - # default: local - mode remote; - # for edge(remote mode), user must specifies the origin server - # format as: [:port] - # @remark user can specifies multiple origin for error backup, by space, - # for example, 192.168.1.100:1935 192.168.1.101:1935 192.168.1.102:1935 - origin 127.0.0.1:1935 localhost:1935; - # for edge, whether open the token traverse mode, - # if token traverse on, all connections of edge will forward to origin to check(auth), - # it's very important for the edge to do the token auth. - # the better way is use http callback to do the token auth by the edge, - # but if user prefer origin check(auth), the token_traverse if better solution. - # default: off - token_traverse off; -} - -# vhost for edge, edge transform vhost to fetch from another vhost. -vhost transform.edge.srs.com { - mode remote; - origin 127.0.0.1:1935; - # the vhost to transform for edge, - # to fetch from the specified vhost at origin, - # if not specified, use the current vhost of edge in origin, the variable [vhost]. - # default: [vhost] - vhost same.edge.srs.com; -} - -# vhost for dvr -vhost dvr.srs.com { - # dvr RTMP stream to file, - # start to record to file when encoder publish, - # reap flv according by specified dvr_plan. - dvr { - # whether enabled dvr features - # default: off - enabled on; - # the dvr plan. canbe: - # session reap flv when session end(unpublish). - # segment reap flv when flv duration exceed the specified dvr_duration. - # append always append to flv file, never reap it. - # default: session - dvr_plan session; - # the dvr output path. - # we supports some variables to generate the filename. - # [vhost], the vhost of stream. - # [app], the app of stream. - # [stream], the stream name of stream. - # [2006], replace this const to current year. - # [01], replace this const to current month. - # [02], replace this const to current date. - # [15], replace this const to current hour. - # [04], repleace this const to current minute. - # [05], repleace this const to current second. - # [999], repleace this const to current millisecond. - # [timestamp],replace this const to current UNIX timestamp in ms. - # @remark we use golang time format "2006-01-02 15:04:05.999" as "[2006]-[01]-[02]_[15].[04].[05]_[999]" - # for example, for url rtmp://ossrs.net/live/livestream and time 2015-01-03 10:57:30.776 - # 1. No variables, the rule of SRS1.0(auto add [stream].[timestamp].flv as filename): - # dvr_path ./objs/nginx/html; - # => - # dvr_path ./objs/nginx/html/live/livestream.1420254068776.flv; - # 2. Use stream and date as dir name, time as filename: - # dvr_path /data/[vhost]/[app]/[stream]/[2006]/[01]/[02]/[15].[04].[05].[999].flv; - # => - # dvr_path /data/ossrs.net/live/livestream/2015/01/03/10.57.30.776.flv; - # 3. Use stream and year/month as dir name, date and time as filename: - # dvr_path /data/[vhost]/[app]/[stream]/[2006]/[01]/[02]-[15].[04].[05].[999].flv; - # => - # dvr_path /data/ossrs.net/live/livestream/2015/01/03-10.57.30.776.flv; - # 4. Use vhost/app and year/month as dir name, stream/date/time as filename: - # dvr_path /data/[vhost]/[app]/[2006]/[01]/[stream]-[02]-[15].[04].[05].[999].flv; - # => - # dvr_path /data/ossrs.net/live/2015/01/livestream-03-10.57.30.776.flv; - # @see https://github.com/ossrs/srs/wiki/v2_CN_DVR#custom-path - # @see https://github.com/ossrs/srs/wiki/v2_EN_DVR#custom-path - # segment,session apply it. - # default: ./objs/nginx/html/[app]/[stream].[timestamp].flv - dvr_path ./objs/nginx/html/[app]/[stream].[timestamp].flv; - # the duration for dvr file, reap if exeed, in seconds. - # segment apply it. - # session,append ignore. - # default: 30 - dvr_duration 30; - # whether wait keyframe to reap segment, - # if off, reap segment when duration exceed the dvr_duration, - # if on, reap segment when duration exceed and got keyframe. - # segment apply it. - # session,append ignore. - # default: on - dvr_wait_keyframe on; - # about the stream monotonically increasing: - # 1. video timestamp is monotonically increasing, - # 2. audio timestamp is monotonically increasing, - # 3. video and audio timestamp is interleaved monotonically increasing. - # it's specified by RTMP specification, @see 3. Byte Order, Alignment, and Time Format - # however, some encoder cannot provides this feature, please set this to off to ignore time jitter. - # the time jitter algorithm: - # 1. full, to ensure stream start at zero, and ensure stream monotonically increasing. - # 2. zero, only ensure sttream start at zero, ignore timestamp jitter. - # 3. off, disable the time jitter algorithm, like atc. - # apply for all dvr plan. - # default: full - time_jitter full; - - # on_dvr, never config in here, should config in http_hooks. - # for the dvr http callback, @see http_hooks.on_dvr of vhost hooks.callback.srs.com - # @read https://github.com/ossrs/srs/wiki/v2_CN_DVR#http-callback - # @read https://github.com/ossrs/srs/wiki/v2_EN_DVR#http-callback - } -} - -# vhost for ingest -vhost ingest.srs.com { - # ingest file/stream/device then push to SRS over RTMP. - # the name/id used to identify the ingest, must be unique in global. - # ingest id is used in reload or http api management. - ingest livestream { - # whether enabled ingest features - # default: off - enabled on; - # input file/stream/device - # @remark only support one input. - input { - # the type of input. - # can be file/stream/device, that is, - # file: ingest file specifies by url. - # stream: ingest stream specifeis by url. - # device: not support yet. - # default: file - type file; - # the url of file/stream. - url ./doc/source.200kbps.768x320.flv; - } - # the ffmpeg - ffmpeg ./objs/ffmpeg/bin/ffmpeg; - # the transcode engine, @see all.transcode.srs.com - # @remark, the output is specified following. - engine { - # @see enabled of transcode engine. - # if disabled or vcodec/acodec not specified, use copy. - # default: off. - enabled off; - # output stream. variables: - # [vhost] current vhost which start the ingest. - # [port] system RTMP stream port. - output rtmp://127.0.0.1:[port]/live?vhost=[vhost]/livestream; - } - } -} - # vhost for http static and flv vod stream for each vhost. vhost http.static.srs.com { # http static vhost specified config @@ -456,7 +749,7 @@ vhost http.static.srs.com { # whether enabled the http static service for vhost. # default: off enabled on; - # the url to mount to, + # the url to mount to, # typical mount to [vhost]/ # the variables: # [vhost] current vhost for http server. @@ -496,7 +789,7 @@ vhost http.remux.srs.com { # @remark 0 to disable fast cache for http audio stream. # default: 0 fast_cache 30; - # the stream mout for rtmp to remux to live streaming. + # the stream mount for rtmp to remux to live streaming. # typical mount to [vhost]/[app]/[stream].flv # the variables: # [vhost] current vhost for http live stream. @@ -523,19 +816,214 @@ vhost http.remux.srs.com { # @remark the port of http is specified by http_server section. # default: [vhost]/[app]/[stream].flv mount [vhost]/[app]/[stream].flv; - # whether http stream trigger rtmp stream source when no stream available, - # for example, when encoder has not publish stream yet, - # user can play the http flv stream and wait for stream. - # default: on - hstrs on; + } +} + +# the http hook callback vhost, srs will invoke the hooks for specified events. +vhost hooks.callback.srs.com { + http_hooks { + # whether the http hooks enable. + # default off. + enabled on; + # when client connect to vhost/app, call the hook, + # the request in the POST data string is a object encode by json: + # { + # "action": "on_connect", + # "client_id": 1985, + # "ip": "192.168.1.10", "vhost": "video.test.com", "app": "live", + # "tcUrl": "rtmp://video.test.com/live?key=d2fa801d08e3f90ed1e1670e6e52651a", + # "pageUrl": "http://www.test.com/live.html" + # } + # if valid, the hook must return HTTP code 200(Status OK) and response + # an int value specifies the error code(0 corresponding to success): + # 0 + # support multiple api hooks, format: + # on_connect http://xxx/api0 http://xxx/api1 http://xxx/apiN + on_connect http://127.0.0.1:8085/api/v1/clients http://localhost:8085/api/v1/clients; + # when client close/disconnect to vhost/app/stream, call the hook, + # the request in the POST data string is a object encode by json: + # { + # "action": "on_close", + # "client_id": 1985, + # "ip": "192.168.1.10", "vhost": "video.test.com", "app": "live", + # "send_bytes": 10240, "recv_bytes": 10240 + # } + # if valid, the hook must return HTTP code 200(Status OK) and response + # an int value specifies the error code(0 corresponding to success): + # 0 + # support multiple api hooks, format: + # on_close http://xxx/api0 http://xxx/api1 http://xxx/apiN + on_close http://127.0.0.1:8085/api/v1/clients http://localhost:8085/api/v1/clients; + # when client(encoder) publish to vhost/app/stream, call the hook, + # the request in the POST data string is a object encode by json: + # { + # "action": "on_publish", + # "client_id": 1985, + # "ip": "192.168.1.10", "vhost": "video.test.com", "app": "live", + # "stream": "livestream", "param":"?token=xxx&salt=yyy" + # } + # if valid, the hook must return HTTP code 200(Status OK) and response + # an int value specifies the error code(0 corresponding to success): + # 0 + # support multiple api hooks, format: + # on_publish http://xxx/api0 http://xxx/api1 http://xxx/apiN + on_publish http://127.0.0.1:8085/api/v1/streams http://localhost:8085/api/v1/streams; + # when client(encoder) stop publish to vhost/app/stream, call the hook, + # the request in the POST data string is a object encode by json: + # { + # "action": "on_unpublish", + # "client_id": 1985, + # "ip": "192.168.1.10", "vhost": "video.test.com", "app": "live", + # "stream": "livestream", "param":"?token=xxx&salt=yyy" + # } + # if valid, the hook must return HTTP code 200(Status OK) and response + # an int value specifies the error code(0 corresponding to success): + # 0 + # support multiple api hooks, format: + # on_unpublish http://xxx/api0 http://xxx/api1 http://xxx/apiN + on_unpublish http://127.0.0.1:8085/api/v1/streams http://localhost:8085/api/v1/streams; + # when client start to play vhost/app/stream, call the hook, + # the request in the POST data string is a object encode by json: + # { + # "action": "on_play", + # "client_id": 1985, + # "ip": "192.168.1.10", "vhost": "video.test.com", "app": "live", + # "stream": "livestream", "param":"?token=xxx&salt=yyy", + # "pageUrl": "http://www.test.com/live.html" + # } + # if valid, the hook must return HTTP code 200(Status OK) and response + # an int value specifies the error code(0 corresponding to success): + # 0 + # support multiple api hooks, format: + # on_play http://xxx/api0 http://xxx/api1 http://xxx/apiN + on_play http://127.0.0.1:8085/api/v1/sessions http://localhost:8085/api/v1/sessions; + # when client stop to play vhost/app/stream, call the hook, + # the request in the POST data string is a object encode by json: + # { + # "action": "on_stop", + # "client_id": 1985, + # "ip": "192.168.1.10", "vhost": "video.test.com", "app": "live", + # "stream": "livestream", "param":"?token=xxx&salt=yyy" + # } + # if valid, the hook must return HTTP code 200(Status OK) and response + # an int value specifies the error code(0 corresponding to success): + # 0 + # support multiple api hooks, format: + # on_stop http://xxx/api0 http://xxx/api1 http://xxx/apiN + on_stop http://127.0.0.1:8085/api/v1/sessions http://localhost:8085/api/v1/sessions; + # when srs reap a dvr file, call the hook, + # the request in the POST data string is a object encode by json: + # { + # "action": "on_dvr", + # "client_id": 1985, + # "ip": "192.168.1.10", "vhost": "video.test.com", "app": "live", + # "stream": "livestream", "param":"?token=xxx&salt=yyy", + # "cwd": "/usr/local/srs", + # "file": "./objs/nginx/html/live/livestream.1420254068776.flv" + # } + # if valid, the hook must return HTTP code 200(Status OK) and response + # an int value specifies the error code(0 corresponding to success): + # 0 + on_dvr http://127.0.0.1:8085/api/v1/dvrs http://localhost:8085/api/v1/dvrs; + # when srs reap a ts file of hls, call the hook, + # the request in the POST data string is a object encode by json: + # { + # "action": "on_hls", + # "client_id": 1985, + # "ip": "192.168.1.10", "vhost": "video.test.com", "app": "live", + # "stream": "livestream", "param":"?token=xxx&salt=yyy", + # "duration": 9.36, // in seconds + # "cwd": "/usr/local/srs", + # "file": "./objs/nginx/html/live/livestream/2015-04-23/01/476584165.ts", + # "url": "live/livestream/2015-04-23/01/476584165.ts", + # "m3u8": "./objs/nginx/html/live/livestream/live.m3u8", + # "m3u8_url": "live/livestream/live.m3u8", + # "seq_no": 100 + # } + # if valid, the hook must return HTTP code 200(Status OK) and response + # an int value specifies the error code(0 corresponding to success): + # 0 + on_hls http://127.0.0.1:8085/api/v1/hls http://localhost:8085/api/v1/hls; + # when srs reap a ts file of hls, call this hook, + # used to push file to cdn network, by get the ts file from cdn network. + # so we use HTTP GET and use the variable following: + # [app], replace with the app. + # [stream], replace with the stream. + # [param], replace with the param. + # [ts_url], replace with the ts url. + # ignore any return data of server. + # @remark random select a url to report, not report all. + on_hls_notify http://127.0.0.1:8085/api/v1/hls/[app]/[stream]/[ts_url][param]; + } +} + +# the vhost for exec, fork process when publish stream. +vhost exec.srs.com { + # the exec used to fork process when got some event. + exec { + # whether enable the exec. + # default: off. + enabled off; + # when publish stream, exec the process with variables: + # [vhost] the input stream vhost. + # [port] the input stream port. + # [app] the input stream app. + # [stream] the input stream name. + # [engine] the transcode engine name. + # other variables for exec only: + # [url] the rtmp url which trigger the publish. + # [tcUrl] the client request tcUrl. + # [swfUrl] the client request swfUrl. + # [pageUrl] the client request pageUrl. + # we also support datetime variables. + # [2006], replace this const to current year. + # [01], replace this const to current month. + # [02], replace this const to current date. + # [15], replace this const to current hour. + # [04], replace this const to current minute. + # [05], replace this const to current second. + # [999], replace this const to current millisecond. + # [timestamp],replace this const to current UNIX timestamp in ms. + # @remark we use golang time format "2006-01-02 15:04:05.999" as "[2006]-[01]-[02]_[15].[04].[05]_[999]" + # @remark empty to ignore this exec. + publish ./objs/ffmpeg/bin/ffmpeg -f flv -i [url] -c copy -y ./[stream].flv; + } +} + +# The vhost for MPEG-DASH. +vhost dash.srs.com { + dash { + # Whether DASH is enabled. + # Transmux RTMP to DASH if on. + # Default: off + enabled on; + # The duration of segment in seconds. + # Default: 30 + dash_fragment 30; + # The period to update the MPD in seconds. + # Default: 150 + dash_update_period 150; + # The depth of timeshift buffer in seconds. + # Default: 300 + dash_timeshift 300; + # The base/home dir/path for dash. + # All init and segment files will write under this dir. + dash_path ./objs/nginx/html; + # The DASH MPD file path. + # We supports some variables to generate the filename. + # [vhost], the vhost of stream. + # [app], the app of stream. + # [stream], the stream name of stream. + # Default: [app]/[stream].mpd + dash_mpd_file [app]/[stream].mpd; } } # the vhost with hls specified. -vhost with-hls.srs.com { +vhost hls.srs.com { hls { # whether the hls is enabled. - # if off, donot write hls(ts and m3u8) when publish. + # if off, do not write hls(ts and m3u8) when publish. # default: off enabled on; # the hls fragment in seconds, the duration of a piece of ts. @@ -549,14 +1037,14 @@ vhost with-hls.srs.com { hls_td_ratio 1.5; # the audio overflow ratio. # for pure audio, the duration to reap the segment. - # for example, the hls_fragment is 10s, hsl_aof_ratio is 2.0, - # the segemnt will reap to 20s for pure audio. + # for example, the hls_fragment is 10s, hls_aof_ratio is 2.0, + # the segment will reap to 20s for pure audio. # default: 2.0 hls_aof_ratio 2.0; # the hls window in seconds, the number of ts in m3u8. # default: 60 hls_window 60; - # the error strategy. canbe: + # the error strategy. can be: # ignore, disable the hls. # disconnect, require encoder republish. # continue, ignore failed try to continue output hls. @@ -564,9 +1052,9 @@ vhost with-hls.srs.com { # default: continue hls_on_error continue; # the hls output path. - # the m3u8 file is configed by hls_path/hls_m3u8_file, the default is: + # the m3u8 file is configured by hls_path/hls_m3u8_file, the default is: # ./objs/nginx/html/[app]/[stream].m3u8 - # the ts file is configed by hls_path/hls_ts_file, the default is: + # the ts file is configured by hls_path/hls_ts_file, the default is: # ./objs/nginx/html/[app]/[stream]-[seq].ts # @remark the hls_path is compatible with srs v1 config. # default: ./objs/nginx/html @@ -587,23 +1075,26 @@ vhost with-hls.srs.com { # [01], replace this const to current month. # [02], replace this const to current date. # [15], replace this const to current hour. - # [04], repleace this const to current minute. - # [05], repleace this const to current second. - # [999], repleace this const to current millisecond. + # [04], replace this const to current minute. + # [05], replace this const to current second. + # [999], replace this const to current millisecond. # [timestamp],replace this const to current UNIX timestamp in ms. # [seq], the sequence number of ts. + # [duration], replace this const to current ts duration. # @see https://github.com/ossrs/srs/wiki/v2_CN_DVR#custom-path # @see https://github.com/ossrs/srs/wiki/v2_CN_DeliveryHLS#hls-config # default: [app]/[stream]-[seq].ts hls_ts_file [app]/[stream]-[seq].ts; # whether use floor for the hls_ts_file path generation. # if on, use floor(timestamp/hls_fragment) as the variable [timestamp], - # and use enahanced algorithm to calc deviation for segment. + # and use enhanced algorithm to calc deviation for segment. # @remark when floor on, recommend the hls_segment>=2*gop. # default: off hls_ts_floor off; # the hls entry prefix, which is base url of ts url. - # if specified, the ts path in m3u8 will be like: + # for example, the prefix is: + # http://your-server/ + # then, the ts path in m3u8 will be like: # http://your-server/live/livestream-0.ts # http://your-server/live/livestream-1.ts # ... @@ -612,7 +1103,7 @@ vhost with-hls.srs.com { # the default audio codec of hls. # when codec changed, write the PAT/PMT table, but maybe ok util next ts. # so user can set the default codec for mp3. - # the available audio codec: + # the available audio codec: # aac, mp3, an # default: aac hls_acodec aac; @@ -626,9 +1117,8 @@ vhost with-hls.srs.com { # whether cleanup the old expired ts files. # default: on hls_cleanup on; - # the timeout in seconds to dispose the hls, - # dispose is to remove all hls files, m3u8 and ts files. - # when publisher timeout dispose hls. + # If there is no incoming packets, dispose HLS in this timeout in seconds, + # which removes all HLS files including m3u8 and ts files. # @remark 0 to disable dispose for publisher. # @remark apply for publisher timeout only, while "etc/init.d/srs stop" always dispose hls. # default: 0 @@ -644,15 +1134,46 @@ vhost with-hls.srs.com { # default: on hls_wait_keyframe on; + # whether using AES encryption. + # default: off + hls_keys on; + # the number of clear ts which one key can encrypt. + # default: 5 + hls_fragments_per_key 5; + # the hls key file name. + # we supports some variables to generate the filename. + # [vhost], the vhost of stream. + # [app], the app of stream. + # [stream], the stream name of stream. + # [seq], the sequence number of key corresponding to the ts. + hls_key_file [app]/[stream]-[seq].key; + # the key output path. + # the key file is configed by hls_path/hls_key_file, the default is: + # ./objs/nginx/html/[app]/[stream]-[seq].key + hls_key_file_path ./objs/nginx/html; + # the key root URL, use this can support https. + # @remark It's optional. + hls_key_url https://localhost:8080; + + # Special control controls. + ########################################### + # Whether calculate the DTS of audio frame directly. + # If on, guess the specific DTS by AAC samples, please read https://github.com/ossrs/srs/issues/547#issuecomment-294350544 + # If off, directly turn the FLV timestamp to DTS, which might cause corrupt audio stream. + # @remark Recommend to set to off, unless your audio stream sample-rate and timestamp is not correct. + # Default: on + hls_dts_directly on; + # on_hls, never config in here, should config in http_hooks. # for the hls http callback, @see http_hooks.on_hls of vhost hooks.callback.srs.com # @read https://github.com/ossrs/srs/wiki/v2_CN_DeliveryHLS#http-callback # @read https://github.com/ossrs/srs/wiki/v2_EN_DeliveryHLS#http-callback - + # on_hls_notify, never config in here, should config in http_hooks. # we support the variables to generate the notify url: # [app], replace with the app. # [stream], replace with the stream. + # [param], replace with the param. # [ts_url], replace with the ts url. # for the hls http callback, @see http_hooks.on_hls_notify of vhost hooks.callback.srs.com # @read https://github.com/ossrs/srs/wiki/v2_CN_DeliveryHLS#on-hls-notify @@ -663,7 +1184,7 @@ vhost with-hls.srs.com { vhost no-hls.srs.com { hls { # whether the hls is enabled. - # if off, donot write hls(ts and m3u8) when publish. + # if off, do not write hls(ts and m3u8) when publish. # default: off enabled off; } @@ -687,279 +1208,240 @@ vhost hds.srs.com { } } -# the http hook callback vhost, srs will invoke the hooks for specified events. -vhost hooks.callback.srs.com { - http_hooks { - # whether the http hooks enalbe. - # default off. +# vhost for dvr +vhost dvr.srs.com { + # DVR RTMP stream to file, + # start to record to file when encoder publish, + # reap flv/mp4 according by specified dvr_plan. + dvr { + # whether enabled dvr features + # default: off enabled on; - # when client connect to vhost/app, call the hook, - # the request in the POST data string is a object encode by json: - # { - # "action": "on_connect", - # "client_id": 1985, - # "ip": "192.168.1.10", "vhost": "video.test.com", "app": "live", - # "tcUrl": "rtmp://video.test.com/live?key=d2fa801d08e3f90ed1e1670e6e52651a", - # "pageUrl": "http://www.test.com/live.html" - # } - # if valid, the hook must return HTTP code 200(Stauts OK) and response - # an int value specifies the error code(0 corresponding to success): - # 0 - # support multiple api hooks, format: - # on_connect http://xxx/api0 http://xxx/api1 http://xxx/apiN - on_connect http://127.0.0.1:8085/api/v1/clients http://localhost:8085/api/v1/clients; - # when client close/disconnect to vhost/app/stream, call the hook, - # the request in the POST data string is a object encode by json: - # { - # "action": "on_close", - # "client_id": 1985, - # "ip": "192.168.1.10", "vhost": "video.test.com", "app": "live", - # "send_bytes": 10240, "recv_bytes": 10240 - # } - # if valid, the hook must return HTTP code 200(Stauts OK) and response - # an int value specifies the error code(0 corresponding to success): - # 0 - # support multiple api hooks, format: - # on_close http://xxx/api0 http://xxx/api1 http://xxx/apiN - on_close http://127.0.0.1:8085/api/v1/clients http://localhost:8085/api/v1/clients; - # when client(encoder) publish to vhost/app/stream, call the hook, - # the request in the POST data string is a object encode by json: - # { - # "action": "on_publish", - # "client_id": 1985, - # "ip": "192.168.1.10", "vhost": "video.test.com", "app": "live", - # "stream": "livestream", "param":"?token=xxx&salt=yyy" - # } - # if valid, the hook must return HTTP code 200(Stauts OK) and response - # an int value specifies the error code(0 corresponding to success): - # 0 - # support multiple api hooks, format: - # on_publish http://xxx/api0 http://xxx/api1 http://xxx/apiN - on_publish http://127.0.0.1:8085/api/v1/streams http://localhost:8085/api/v1/streams; - # when client(encoder) stop publish to vhost/app/stream, call the hook, - # the request in the POST data string is a object encode by json: - # { - # "action": "on_unpublish", - # "client_id": 1985, - # "ip": "192.168.1.10", "vhost": "video.test.com", "app": "live", - # "stream": "livestream", "param":"?token=xxx&salt=yyy" - # } - # if valid, the hook must return HTTP code 200(Stauts OK) and response - # an int value specifies the error code(0 corresponding to success): - # 0 - # support multiple api hooks, format: - # on_unpublish http://xxx/api0 http://xxx/api1 http://xxx/apiN - on_unpublish http://127.0.0.1:8085/api/v1/streams http://localhost:8085/api/v1/streams; - # when client start to play vhost/app/stream, call the hook, - # the request in the POST data string is a object encode by json: - # { - # "action": "on_play", - # "client_id": 1985, - # "ip": "192.168.1.10", "vhost": "video.test.com", "app": "live", - # "stream": "livestream", "param":"?token=xxx&salt=yyy", - # "pageUrl": "http://www.test.com/live.html" - # } - # if valid, the hook must return HTTP code 200(Stauts OK) and response - # an int value specifies the error code(0 corresponding to success): - # 0 - # support multiple api hooks, format: - # on_play http://xxx/api0 http://xxx/api1 http://xxx/apiN - on_play http://127.0.0.1:8085/api/v1/sessions http://localhost:8085/api/v1/sessions; - # when client stop to play vhost/app/stream, call the hook, - # the request in the POST data string is a object encode by json: - # { - # "action": "on_stop", - # "client_id": 1985, - # "ip": "192.168.1.10", "vhost": "video.test.com", "app": "live", - # "stream": "livestream", "param":"?token=xxx&salt=yyy" - # } - # if valid, the hook must return HTTP code 200(Stauts OK) and response - # an int value specifies the error code(0 corresponding to success): - # 0 - # support multiple api hooks, format: - # on_stop http://xxx/api0 http://xxx/api1 http://xxx/apiN - on_stop http://127.0.0.1:8085/api/v1/sessions http://localhost:8085/api/v1/sessions; - # when srs reap a dvr file, call the hook, - # the request in the POST data string is a object encode by json: - # { - # "action": "on_dvr", - # "client_id": 1985, - # "ip": "192.168.1.10", "vhost": "video.test.com", "app": "live", - # "stream": "livestream", "param":"?token=xxx&salt=yyy", - # "cwd": "/usr/local/srs", - # "file": "./objs/nginx/html/live/livestream.1420254068776.flv" - # } - # if valid, the hook must return HTTP code 200(Stauts OK) and response - # an int value specifies the error code(0 corresponding to success): - # 0 - on_dvr http://127.0.0.1:8085/api/v1/dvrs http://localhost:8085/api/v1/dvrs; - # when srs reap a ts file of hls, call the hook, - # the request in the POST data string is a object encode by json: - # { - # "action": "on_hls", - # "client_id": 1985, - # "ip": "192.168.1.10", "vhost": "video.test.com", "app": "live", - # "stream": "livestream", "param":"?token=xxx&salt=yyy", - # "duration": 9.36, // in seconds - # "cwd": "/usr/local/srs", - # "file": "./objs/nginx/html/live/livestream/2015-04-23/01/476584165.ts", - # "url": "live/livestream/2015-04-23/01/476584165.ts", - # "m3u8": "./objs/nginx/html/live/livestream/live.m3u8", - # "m3u8_url": "live/livestream/live.m3u8", - # "seq_no": 100 - # } - # if valid, the hook must return HTTP code 200(Stauts OK) and response - # an int value specifies the error code(0 corresponding to success): - # 0 - on_hls http://127.0.0.1:8085/api/v1/hls http://localhost:8085/api/v1/hls; - # when srs reap a ts file of hls, call this hook, - # used to push file to cdn network, by get the ts file from cdn network. - # so we use HTTP GET and use the variable following: - # [app], replace with the app. - # [stream], replace with the stream. - # [param], replace with the param. - # [ts_url], replace with the ts url. - # ignore any return data of server. - # @remark random select a url to report, not report all. - on_hls_notify http://127.0.0.1:8085/api/v1/hls/[app]/[stream]/[ts_url][param]; - } -} - -# the vhost for srs debug info, whether send args in connect(tcUrl). -vhost debug.srs.com { - # when upnode(forward to, edge push to, edge pull from) is srs, - # it's strongly recommend to open the debug_srs_upnode, - # when connect to upnode, it will take the debug info, - # for example, the id, source id, pid. - # please see: https://github.com/ossrs/srs/wiki/v1_CN_SrsLog - # default: on - debug_srs_upnode on; -} - -# the vhost for min delay, donot cache any stream. -vhost min.delay.com { - # @see vhost mrw.srs.com for detail. - min_latency on; - mr { - enabled off; - } - mw_latency 100; - # whether cache the last gop. - # if on, cache the last gop and dispatch to client, - # to enabled fast startup for client, client play immediately. - # if off, send the latest media data to client, - # client need to wait for the next Iframe to decode and show the video. - # set to off if requires min delay; - # set to on if requires client fast startup. - # default: on - gop_cache off; - # the max live queue length in seconds. - # if the messages in the queue exceed the max length, - # drop the old whole gop. - # default: 30 - queue_length 10; - # whether enable the TCP_NODELAY - # if on, set the nodelay of fd by setsockopt - # default: off - tcp_nodelay on; -} - -# whether disable the sps parse, for the resolution of video. -vhost no.parse.sps.com { - publish { - # whether parse the sps when publish stream. - # we can got the resolution of video for stat api. - # but we may failed to cause publish failed. + # the filter for dvr to apply to. + # all, dvr all streams of all apps. + # /, apply to specified stream of app. + # for example, to dvr the following two streams: + # live/stream1 live/stream2 + # default: all + dvr_apply all; + # the dvr plan. canbe: + # session reap flv/mp4 when session end(unpublish). + # segment reap flv/mp4 when flv duration exceed the specified dvr_duration. + # @remark The plan append is removed in SRS3+, for it's no use. + # default: session + dvr_plan session; + # the dvr output path, *.flv or *.mp4. + # we supports some variables to generate the filename. + # [vhost], the vhost of stream. + # [app], the app of stream. + # [stream], the stream name of stream. + # [2006], replace this const to current year. + # [01], replace this const to current month. + # [02], replace this const to current date. + # [15], replace this const to current hour. + # [04], replace this const to current minute. + # [05], replace this const to current second. + # [999], replace this const to current millisecond. + # [timestamp],replace this const to current UNIX timestamp in ms. + # @remark we use golang time format "2006-01-02 15:04:05.999" as "[2006]-[01]-[02]_[15].[04].[05]_[999]" + # for example, for url rtmp://ossrs.net/live/livestream and time 2015-01-03 10:57:30.776 + # 1. No variables, the rule of SRS1.0(auto add [stream].[timestamp].flv as filename): + # dvr_path ./objs/nginx/html; + # => + # dvr_path ./objs/nginx/html/live/livestream.1420254068776.flv; + # 2. Use stream and date as dir name, time as filename: + # dvr_path /data/[vhost]/[app]/[stream]/[2006]/[01]/[02]/[15].[04].[05].[999].flv; + # => + # dvr_path /data/ossrs.net/live/livestream/2015/01/03/10.57.30.776.flv; + # 3. Use stream and year/month as dir name, date and time as filename: + # dvr_path /data/[vhost]/[app]/[stream]/[2006]/[01]/[02]-[15].[04].[05].[999].flv; + # => + # dvr_path /data/ossrs.net/live/livestream/2015/01/03-10.57.30.776.flv; + # 4. Use vhost/app and year/month as dir name, stream/date/time as filename: + # dvr_path /data/[vhost]/[app]/[2006]/[01]/[stream]-[02]-[15].[04].[05].[999].flv; + # => + # dvr_path /data/ossrs.net/live/2015/01/livestream-03-10.57.30.776.flv; + # 5. DVR to mp4: + # dvr_path ./objs/nginx/html/[app]/[stream].[timestamp].mp4; + # => + # dvr_path ./objs/nginx/html/live/livestream.1420254068776.mp4; + # @see https://github.com/ossrs/srs/wiki/v3_CN_DVR#custom-path + # @see https://github.com/ossrs/srs/wiki/v3_EN_DVR#custom-path + # segment,session apply it. + # default: ./objs/nginx/html/[app]/[stream].[timestamp].flv + dvr_path ./objs/nginx/html/[app]/[stream].[timestamp].flv; + # the duration for dvr file, reap if exceed, in seconds. + # segment apply it. + # session,append ignore. + # default: 30 + dvr_duration 30; + # whether wait keyframe to reap segment, + # if off, reap segment when duration exceed the dvr_duration, + # if on, reap segment when duration exceed and got keyframe. + # segment apply it. + # session,append ignore. # default: on - parse_sps on; + dvr_wait_keyframe on; + # about the stream monotonically increasing: + # 1. video timestamp is monotonically increasing, + # 2. audio timestamp is monotonically increasing, + # 3. video and audio timestamp is interleaved monotonically increasing. + # it's specified by RTMP specification, @see 3. Byte Order, Alignment, and Time Format + # however, some encoder cannot provides this feature, please set this to off to ignore time jitter. + # the time jitter algorithm: + # 1. full, to ensure stream start at zero, and ensure stream monotonically increasing. + # 2. zero, only ensure stream start at zero, ignore timestamp jitter. + # 3. off, disable the time jitter algorithm, like atc. + # apply for all dvr plan. + # default: full + time_jitter full; + + # on_dvr, never config in here, should config in http_hooks. + # for the dvr http callback, @see http_hooks.on_dvr of vhost hooks.callback.srs.com + # @read https://github.com/ossrs/srs/wiki/v2_CN_DVR#http-callback + # @read https://github.com/ossrs/srs/wiki/v2_EN_DVR#http-callback } } -# the vhost to control the stream delivery feature -vhost stream.control.com { - # @see vhost mrw.srs.com for detail. - min_latency on; - mr { - enabled off; +# vhost for ingest +vhost ingest.srs.com { + # ingest file/stream/device then push to SRS over RTMP. + # the name/id used to identify the ingest, must be unique in global. + # ingest id is used in reload or http api management. + # @remark vhost can contains multiple ingest + ingest livestream { + # whether enabled ingest features + # default: off + enabled on; + # input file/stream/device + # @remark only support one input. + input { + # the type of input. + # can be file/stream/device, that is, + # file: ingest file specified by url. + # stream: ingest stream specified by url. + # device: not support yet. + # default: file + type file; + # the url of file/stream. + url ./doc/source.200kbps.768x320.flv; + } + # the ffmpeg + ffmpeg ./objs/ffmpeg/bin/ffmpeg; + # the transcode engine, @see all.transcode.srs.com + # @remark, the output is specified following. + engine { + # @see enabled of transcode engine. + # if disabled or vcodec/acodec not specified, use copy. + # default: off. + enabled off; + # output stream. variables: + # [vhost] current vhost which start the ingest. + # [port] system RTMP stream port. + # we also support datetime variables. + # [2006], replace this const to current year. + # [01], replace this const to current month. + # [02], replace this const to current date. + # [15], replace this const to current hour. + # [04], replace this const to current minute. + # [05], replace this const to current second. + # [999], replace this const to current millisecond. + # [timestamp],replace this const to current UNIX timestamp in ms. + # @remark we use golang time format "2006-01-02 15:04:05.999" as "[2006]-[01]-[02]_[15].[04].[05]_[999]" + output rtmp://127.0.0.1:[port]/live?vhost=[vhost]/livestream; + } } - mw_latency 100; - # @see vhost min.delay.com - queue_length 10; - tcp_nodelay on; - # the minimal packets send interval in ms, - # used to control the ndiff of stream by srs_rtmp_dump, - # for example, some device can only accept some stream which - # delivery packets in constant interval(not cbr). - # @remark 0 to disable the minimal interval. - # @remark >0 to make the srs to send message one by one. - # @remark user can get the right packets interval in ms by srs_rtmp_dump. - # default: 0 - send_min_interval 10.0; - # whether reduce the sequence header, - # for some client which cannot got duplicated sequence header, - # while the sequence header is not changed yet. - # default: off - reduce_sequence_header on; - # the 1st packet timeout in ms for encoder. - # default: 20000 - publish_1stpkt_timeout 20000; - # the normal packet timeout in ms for encoder. - # default: 5000 - publish_normal_timeout 7000; } -# the vhost for antisuck. -vhost refer.anti_suck.com { - # the common refer for play and publish. - # if the page url of client not in the refer, access denied. - # if not specified this field, allow all. - # default: not specified. - refer github.com github.io; - # refer for publish clients specified. - # the common refer is not overrided by this. - # if not specified this field, allow all. - # default: not specified. - refer_publish github.com github.io; - # refer for play clients specified. - # the common refer is not overrided by this. - # if not specified this field, allow all. - # default: not specified. - refer_play github.com github.io; -} - -# the vhost which forward publish streams. -vhost same.vhost.forward.srs.com { - # forward all publish stream to the specified server. - # this used to split/forward the current stream for cluster active-standby, - # active-active for cdn to build high available fault tolerance system. - # format: {ip}:{port} {ip_N}:{port_N} - forward 127.0.0.1:1936 127.0.0.1:1937; +# the vhost for ingest with transcode engine. +vhost transcode.ingest.srs.com { + ingest livestream { + enabled on; + input { + type file; + url ./doc/source.200kbps.768x320.flv; + } + ffmpeg ./objs/ffmpeg/bin/ffmpeg; + engine { + enabled off; + perfile { + re; + rtsp_transport tcp; + } + iformat flv; + vfilter { + i ./doc/ffmpeg-logo.png; + filter_complex 'overlay=10:10'; + } + vcodec libx264; + vbitrate 1500; + vfps 25; + vwidth 768; + vheight 320; + vthreads 12; + vprofile main; + vpreset medium; + vparams { + t 100; + coder 1; + b_strategy 2; + bf 3; + refs 10; + } + acodec libfdk_aac; + abitrate 70; + asample_rate 44100; + achannels 2; + aparams { + profile:a aac_low; + } + oformat flv; + output rtmp://127.0.0.1:[port]/[app]?vhost=[vhost]/[stream]; + } + } } # the main comments for transcode vhost example.transcode.srs.com { # the streaming transcode configs. + # @remark vhost can contains multiple transcode transcode { # whether the transcode enabled. # if off, donot transcode. # default: off. enabled on; - # the ffmpeg + # the ffmpeg ffmpeg ./objs/ffmpeg/bin/ffmpeg; # the transcode engine for matched stream. # all matched stream will transcoded to the following stream. # the transcode set name(ie. hd) is optional and not used. + # we will build the parameters to fork ffmpeg: + # ffmpeg + # -i + # + # -vcodec -b:v -r -s x -profile:v -preset + # + # -acodec -b:a -ar -ac + # + # -f + # -y engine example { # whether the engine is enabled # default: off. enabled on; - # input format, can be: - # off, do not specifies the format, ffmpeg will guess it. - # flv, for flv or RTMP stream. - # other format, for example, mp4/aac whatever. + # pre-file options, before "-i" + perfile { + re; + rtsp_transport tcp; + } + # input format "-i", can be: + # off, do not specifies the format, ffmpeg will guess it. + # flv, for flv or RTMP stream. + # other format, for example, mp4/aac whatever. # default: flv iformat flv; - # ffmpeg filters, follows the main input. + # ffmpeg filters, between "-i" and "-vcodec" + # follows the main input. vfilter { # the logo input file. i ./doc/ffmpeg-logo.png; @@ -967,38 +1449,42 @@ vhost example.transcode.srs.com { # for filters, @see: http://ffmpeg.org/ffmpeg-filters.html filter_complex 'overlay=10:10'; } - # video encoder name. can be: + # video encoder name, "ffmpeg -vcodec" + # can be: # libx264: use h.264(libx264) video encoder. + # png: use png to snapshot thumbnail. # copy: donot encoder the video stream, copy it. # vn: disable video output. vcodec libx264; - # video bitrate, in kbps + # video bitrate, in kbps, "ffmepg -b:v" # @remark 0 to use source video bitrate. # default: 0 vbitrate 1500; - # video framerate. + # video framerate, "ffmepg -r" # @remark 0 to use source video fps. # default: 0 vfps 25; - # video width, must be even numbers. + # video width, must be even numbers, "ffmepg -s" # @remark 0 to use source video width. # default: 0 vwidth 768; - # video height, must be even numbers. + # video height, must be even numbers, "ffmepg -s" # @remark 0 to use source video height. # default: 0 vheight 320; - # the max threads for ffmpeg to used. + # the max threads for ffmpeg to used, "ffmepg -thread" # default: 1 vthreads 12; - # x264 profile, @see x264 -help, can be: + # x264 profile, "ffmepg -profile:v" + # @see x264 -help, can be: # high,main,baseline vprofile main; - # x264 preset, @see x264 -help, can be: + # x264 preset, "ffmpeg -preset" + # @see x264 -help, can be: # ultrafast,superfast,veryfast,faster,fast # medium,slow,slower,veryslow,placebo vpreset medium; - # other x264 or ffmpeg video params + # other x264 or ffmpeg video params, between "-preset" and "-acodec" vparams { # ffmpeg options, @see: http://ffmpeg.org/ffmpeg.html t 100; @@ -1008,43 +1494,58 @@ vhost example.transcode.srs.com { bf 3; refs 10; } - # audio encoder name. can be: + # audio encoder name, "ffmpeg -acodec" + # can be: # libfdk_aac: use aac(libfdk_aac) audio encoder. # copy: donot encoder the audio stream, copy it. # an: disable audio output. acodec libfdk_aac; - # audio bitrate, in kbps. [16, 72] for libfdk_aac. + # audio bitrate, in kbps, "ffmpeg -b:a" + # [16, 72] for libfdk_aac. # @remark 0 to use source audio bitrate. # default: 0 abitrate 70; - # audio sample rate. for flv/rtmp, it must be: + # audio sample rate, "ffmpeg -ar" + # for flv/rtmp, it must be: # 44100,22050,11025,5512 # @remark 0 to use source audio sample rate. # default: 0 asample_rate 44100; - # audio channel, 1 for mono, 2 for stereo. + # audio channel, "ffmpeg -ac" + # 1 for mono, 2 for stereo. # @remark 0 to use source audio channels. # default: 0 achannels 2; - # other ffmpeg audio params + # other ffmpeg audio params, between "-ac" and "-f"/"-y" aparams { # audio params, @see: http://ffmpeg.org/ffmpeg-codecs.html#Audio-Encoders # @remark SRS supported aac profile for HLS is: aac_low, aac_he, aac_he_v2 profile:a aac_low; bsf:a aac_adtstoasc; } - # output format, can be: + # output format, "ffmpeg -f" can be: # off, do not specifies the format, ffmpeg will guess it. # flv, for flv or RTMP stream. + # image2, for vcodec png to snapshot thumbnail. # other format, for example, mp4/aac whatever. # default: flv oformat flv; - # output stream. variables: + # output stream, "ffmpeg -y", variables: # [vhost] the input stream vhost. - # [port] the intput stream port. + # [port] the input stream port. # [app] the input stream app. # [stream] the input stream name. - # [engine] the tanscode engine name. + # [engine] the transcode engine name. + # we also support datetime variables. + # [2006], replace this const to current year. + # [01], replace this const to current month. + # [02], replace this const to current date. + # [15], replace this const to current hour. + # [04], replace this const to current minute. + # [05], replace this const to current second. + # [999], replace this const to current millisecond. + # [timestamp],replace this const to current UNIX timestamp in ms. + # @remark we use golang time format "2006-01-02 15:04:05.999" as "[2006]-[01]-[02]_[15].[04].[05]_[999]" output rtmp://127.0.0.1:[port]/[app]?vhost=[vhost]/[stream]_[engine]; } } @@ -1144,7 +1645,7 @@ vhost logo.transcode.srs.com { } } # audio transcode only. -# for example, FMLE publish audio codec in mp3, and donot support HLS output, +# for example, FMLE publish audio codec in mp3, and do not support HLS output, # we can transcode the audio to aac and copy video to the new stream with HLS. vhost audio.transcode.srs.com { transcode { @@ -1328,7 +1829,7 @@ vhost all.transcode.srs.com { } } } -# transcode all stream using the empty ffmpeg demo, donothing. +# transcode all stream using the empty ffmpeg demo, do nothing. vhost ffempty.transcode.srs.com { transcode { enabled on; @@ -1380,94 +1881,29 @@ vhost stream.transcode.srs.com { } } -# vhost for bandwidth check -# generally, the bandcheck vhost must be: bandcheck.srs.com, -# or need to modify the vhost of client. -vhost bandcheck.srs.com { +############################################################################################# +# The origin cluster section +############################################################################################# +http_api { enabled on; - chunk_size 65000; - # bandwidth check config. - bandcheck { - # whether support bandwidth check, - # default: off. - enabled on; - # the key for server to valid, - # if invalid key, server disconnect and abort the bandwidth check. - key "35c9b402c12a7246868752e2878f7e0e"; - # the interval in seconds for bandwidth check, - # server donot allow new test request. - # default: 30 - interval 30; - # the max available check bandwidth in kbps. - # to avoid attack of bandwidth check. - # default: 1000 - limit_kbps 4000; + listen 9090; +} +vhost a.origin.cluster.srs.com { + cluster { + mode local; + origin_cluster on; + coworkers 127.0.0.1:9091; } } -# set the chunk size of vhost. -vhost chunksize.srs.com { - # the default chunk size is 128, max is 65536, - # some client does not support chunk size change, - # vhost chunk size will override the global value. - # default: global chunk size. - chunk_size 128; +http_api { + enabled on; + listen 9091; } - -# vhost for time jitter -vhost jitter.srs.com { - # about the stream monotonically increasing: - # 1. video timestamp is monotonically increasing, - # 2. audio timestamp is monotonically increasing, - # 3. video and audio timestamp is interleaved/mixed monotonically increasing. - # it's specified by RTMP specification, @see 3. Byte Order, Alignment, and Time Format - # however, some encoder cannot provides this feature, please set this to off to ignore time jitter. - # the time jitter algorithm: - # 1. full, to ensure stream start at zero, and ensure stream monotonically increasing. - # 2. zero, only ensure sttream start at zero, ignore timestamp jitter. - # 3. off, disable the time jitter algorithm, like atc. - # default: full - time_jitter full; - # whether use the interleaved/mixed algorithm to correct the timestamp. - # if on, always ensure the timestamp of audio+video is interleaved/mixed monotonically increase. - # if off, use time_jitter to correct the timestamp if required. - # default: off - mix_correct off; +vhost b.origin.cluster.srs.com { + cluster { + mode local; + origin_cluster on; + coworkers 127.0.0.1:9090; + } } - -# vhost for atc. -vhost atc.srs.com { - # vhost for atc for hls/hds/rtmp backup. - # generally, atc default to off, server delivery rtmp stream to client(flash) timestamp from 0. - # when atc is on, server delivery rtmp stream by absolute time. - # atc is used, for instance, encoder will copy stream to master and slave server, - # server use atc to delivery stream to edge/client, where stream time from master/slave server - # is always the same, client/tools can slice RTMP stream to HLS according to the same time, - # if the time not the same, the HLS stream cannot slice to support system backup. - # - # @see http://www.adobe.com/cn/devnet/adobe-media-server/articles/varnish-sample-for-failover.html - # @see http://www.baidu.com/#wd=hds%20hls%20atc - # - # default: off - atc on; - # whether enable the auto atc, - # if enabled, detect the bravo_atc="true" in onMetaData packet, - # set atc to on if matched. - # always ignore the onMetaData if atc_auto is off. - # default: on - atc_auto on; -} - -# the vhost disabled. -vhost removed.srs.com { - # whether the vhost is enabled. - # if off, all request access denied. - # default: on - enabled off; -} - -# config for the pithy print, -# which always print constant message specified by interval, -# whatever the clients in concurrency. -# default: 10000 -pithy_print_ms 10000; diff --git a/trunk/conf/go-oryx-edge.conf b/trunk/conf/go-oryx-edge.conf new file mode 100644 index 000000000..8b07031e0 --- /dev/null +++ b/trunk/conf/go-oryx-edge.conf @@ -0,0 +1,15 @@ +# the config for srs origin-edge cluster +# @see https://github.com/ossrs/srs/wiki/v1_CN_Edge +# @see full.conf for detail config. + +listen 19351; +max_connections 1000; +pid objs/edge1.pid; +daemon off; +srs_log_tank console; +vhost __defaultVhost__ { + cluster { + mode remote; + origin 127.0.0.1:19350; + } +} diff --git a/trunk/conf/go-oryx-edge2.conf b/trunk/conf/go-oryx-edge2.conf new file mode 100644 index 000000000..74ea54f01 --- /dev/null +++ b/trunk/conf/go-oryx-edge2.conf @@ -0,0 +1,15 @@ +# the config for srs origin-edge cluster +# @see https://github.com/ossrs/srs/wiki/v1_CN_Edge +# @see full.conf for detail config. + +listen 19352; +max_connections 1000; +pid objs/edge2.pid; +daemon off; +srs_log_tank console; +vhost __defaultVhost__ { + cluster { + mode remote; + origin 127.0.0.1:19350; + } +} diff --git a/trunk/conf/hls.conf b/trunk/conf/hls.conf index d0fa42397..a0979eb19 100644 --- a/trunk/conf/hls.conf +++ b/trunk/conf/hls.conf @@ -4,15 +4,16 @@ listen 1935; max_connections 1000; -daemon off; -srs_log_tank console; +http_server { + enabled on; + listen 8080; + dir ./objs/nginx/html; +} vhost __defaultVhost__ { hls { enabled on; + hls_path ./objs/nginx/html; hls_fragment 10; hls_window 60; - hls_path ./objs/nginx/html; - hls_m3u8_file [app]/[stream].m3u8; - hls_ts_file [app]/[stream]-[seq].ts; } } diff --git a/trunk/conf/hls.realtime.conf b/trunk/conf/hls.realtime.conf new file mode 100644 index 000000000..470d1e85f --- /dev/null +++ b/trunk/conf/hls.realtime.conf @@ -0,0 +1,14 @@ +# the config for srs to delivery realtime RTMP stream +# @see https://github.com/ossrs/srs/wiki/v2_CN_SampleRealtime +# @see full.conf for detail config. + +listen 1935; +max_connections 1000; +vhost __defaultVhost__ { + hls { + enabled on; + hls_fragment 0.2; + hls_window 2; + hls_wait_keyframe off; + } +} diff --git a/trunk/conf/http.aac.live.conf b/trunk/conf/http.aac.live.conf index 22aa1bda6..56a8387a6 100644 --- a/trunk/conf/http.aac.live.conf +++ b/trunk/conf/http.aac.live.conf @@ -16,7 +16,5 @@ vhost __defaultVhost__ { enabled on; fast_cache 30; mount [vhost]/[app]/[stream].aac; - hstrs on; - } } diff --git a/trunk/conf/http.api.raw.conf b/trunk/conf/http.api.raw.conf new file mode 100644 index 000000000..00b2f80a6 --- /dev/null +++ b/trunk/conf/http.api.raw.conf @@ -0,0 +1,28 @@ +# main config for srs. +# @see full.conf for detail config. + +listen 1935; +max_connections 1000; +srs_log_tank file; +srs_log_file ./objs/srs.log; +http_api { + enabled on; + listen 1985; + raw_api { + enabled on; + allow_reload on; + allow_query on; + allow_update on; + } +} +http_server { + enabled on; + listen 8080; + dir ./objs/nginx/html; +} +stats { + network 0; + disk sda sdb xvda xvdb; +} +vhost __defaultVhost__ { +} diff --git a/trunk/conf/http.flv.live.conf b/trunk/conf/http.flv.live.conf index 944889fb7..09ba1dffc 100644 --- a/trunk/conf/http.flv.live.conf +++ b/trunk/conf/http.flv.live.conf @@ -4,8 +4,6 @@ listen 1935; max_connections 1000; -daemon off; -srs_log_tank console; http_server { enabled on; listen 8080; @@ -15,6 +13,5 @@ vhost __defaultVhost__ { http_remux { enabled on; mount [vhost]/[app]/[stream].flv; - hstrs on; } } diff --git a/trunk/conf/http.flv.live.edge1.conf b/trunk/conf/http.flv.live.edge1.conf index e827c221e..ac2f6b344 100644 --- a/trunk/conf/http.flv.live.edge1.conf +++ b/trunk/conf/http.flv.live.edge1.conf @@ -5,19 +5,18 @@ listen 19351; max_connections 1000; pid objs/srs.flv.19351.pid; -daemon off; -srs_log_tank console; http_server { enabled on; listen 8081; dir ./objs/nginx/html; } vhost __defaultVhost__ { - mode remote; - origin 127.0.0.1; + cluster { + mode remote; + origin 127.0.0.1; + } http_remux { enabled on; mount [vhost]/[app]/[stream].flv; - hstrs on; } } diff --git a/trunk/conf/http.flv.live.edge2.conf b/trunk/conf/http.flv.live.edge2.conf index 669c38037..c1ab67551 100644 --- a/trunk/conf/http.flv.live.edge2.conf +++ b/trunk/conf/http.flv.live.edge2.conf @@ -5,19 +5,18 @@ listen 19352; max_connections 1000; pid objs/srs.flv.19352.pid; -daemon off; -srs_log_tank console; http_server { enabled on; listen 8082; dir ./objs/nginx/html; } vhost __defaultVhost__ { - mode remote; - origin 127.0.0.1; + cluster { + mode remote; + origin 127.0.0.1; + } http_remux { enabled on; mount [vhost]/[app]/[stream].flv; - hstrs on; } } diff --git a/trunk/conf/http.mp3.live.conf b/trunk/conf/http.mp3.live.conf index 690a4539e..d07f62086 100644 --- a/trunk/conf/http.mp3.live.conf +++ b/trunk/conf/http.mp3.live.conf @@ -16,7 +16,5 @@ vhost __defaultVhost__ { enabled on; fast_cache 30; mount [vhost]/[app]/[stream].mp3; - hstrs on; - } } diff --git a/trunk/conf/http.server.conf b/trunk/conf/http.server.conf index 0c840894f..f3aafc079 100644 --- a/trunk/conf/http.server.conf +++ b/trunk/conf/http.server.conf @@ -11,7 +11,7 @@ http_server { dir ./objs/nginx/html; } vhost ossrs.net { - http { + http_static { enabled on; mount [vhost]/; dir ./objs/nginx/html; diff --git a/trunk/conf/http.ts.live.conf b/trunk/conf/http.ts.live.conf index d44567248..db6978c70 100644 --- a/trunk/conf/http.ts.live.conf +++ b/trunk/conf/http.ts.live.conf @@ -15,6 +15,5 @@ vhost __defaultVhost__ { http_remux { enabled on; mount [vhost]/[app]/[stream].ts; - hstrs on; } } diff --git a/trunk/conf/ingest.conf b/trunk/conf/ingest.conf index d77a2f954..7b3d1c2ed 100644 --- a/trunk/conf/ingest.conf +++ b/trunk/conf/ingest.conf @@ -4,8 +4,6 @@ listen 1935; max_connections 1000; -daemon off; -srs_log_tank console; vhost __defaultVhost__ { ingest livestream { enabled on; diff --git a/trunk/conf/ingest.rtsp.conf b/trunk/conf/ingest.rtsp.conf new file mode 100644 index 000000000..c89d7b5e1 --- /dev/null +++ b/trunk/conf/ingest.rtsp.conf @@ -0,0 +1,26 @@ +# use ffmpeg to ingest file/stream/device to SRS +# @see https://github.com/ossrs/srs/wiki/v1_CN_SampleIngest +# @see full.conf for detail config. + +listen 1935; +max_connections 1000; +vhost __defaultVhost__ { + ingest { + enabled on; + input { + type stream; + url rtsp://admin:12345678@192.168.2.21:554/Streaming/Channels/501?transportmode=unicast; + } + ffmpeg ./objs/ffmpeg/bin/ffmpeg; + engine { + enabled on; + perfile { + rtsp_transport tcp; + } + vcodec copy; + acodec copy; + output rtmp://127.0.0.1:[port]/live?vhost=[vhost]/livestream; + } + } +} + diff --git a/trunk/conf/origin.cluster.edge.conf b/trunk/conf/origin.cluster.edge.conf new file mode 100644 index 000000000..318fca63c --- /dev/null +++ b/trunk/conf/origin.cluster.edge.conf @@ -0,0 +1,15 @@ +# the config for srs origin-edge cluster +# @see https://github.com/ossrs/srs/wiki/v3_EN_OriginCluster +# @see full.conf for detail config. + +listen 1935; +max_connections 1000; +pid objs/edge.pid; +daemon off; +srs_log_tank console; +vhost __defaultVhost__ { + cluster { + mode remote; + origin 127.0.0.1:19351 127.0.0.1:19350; + } +} diff --git a/trunk/conf/origin.cluster.serverA.conf b/trunk/conf/origin.cluster.serverA.conf new file mode 100644 index 000000000..7bc73c63a --- /dev/null +++ b/trunk/conf/origin.cluster.serverA.conf @@ -0,0 +1,20 @@ +# the config for srs origin-origin cluster +# @see https://github.com/ossrs/srs/wiki/v3_EN_OriginCluster +# @see full.conf for detail config. + +listen 19350; +max_connections 1000; +daemon off; +srs_log_tank console; +pid ./objs/origin.cluster.serverA.pid; +http_api { + enabled on; + listen 9090; +} +vhost __defaultVhost__ { + cluster { + mode local; + origin_cluster on; + coworkers 127.0.0.1:9090 127.0.0.1:9091 127.0.0.1:9092; + } +} diff --git a/trunk/conf/origin.cluster.serverB.conf b/trunk/conf/origin.cluster.serverB.conf new file mode 100644 index 000000000..b06417cd8 --- /dev/null +++ b/trunk/conf/origin.cluster.serverB.conf @@ -0,0 +1,20 @@ +# the config for srs origin-origin cluster +# @see https://github.com/ossrs/srs/wiki/v3_EN_OriginCluster +# @see full.conf for detail config. + +listen 19351; +max_connections 1000; +daemon off; +srs_log_tank console; +pid ./objs/origin.cluster.serverB.pid; +http_api { + enabled on; + listen 9091; +} +vhost __defaultVhost__ { + cluster { + mode local; + origin_cluster on; + coworkers 127.0.0.1:9090 127.0.0.1:9091 127.0.0.1:9092; + } +} diff --git a/trunk/conf/origin.cluster.serverC.conf b/trunk/conf/origin.cluster.serverC.conf new file mode 100644 index 000000000..efb991cc7 --- /dev/null +++ b/trunk/conf/origin.cluster.serverC.conf @@ -0,0 +1,20 @@ +# the config for srs origin-origin cluster +# @see https://github.com/ossrs/srs/wiki/v3_EN_OriginCluster +# @see full.conf for detail config. + +listen 19352; +max_connections 1000; +daemon off; +srs_log_tank console; +pid ./objs/origin.cluster.serverC.pid; +http_api { + enabled on; + listen 9092; +} +vhost __defaultVhost__ { + cluster { + mode local; + origin_cluster on; + coworkers 127.0.0.1:9090 127.0.0.1:9091 127.0.0.1:9092; + } +} diff --git a/trunk/conf/origin.conf b/trunk/conf/origin.conf index f27abb0ad..5f9eaeefc 100644 --- a/trunk/conf/origin.conf +++ b/trunk/conf/origin.conf @@ -4,8 +4,6 @@ listen 19350; max_connections 1000; -daemon off; -srs_log_tank console; pid ./objs/origin.pid; vhost __defaultVhost__ { } diff --git a/trunk/conf/realtime.conf b/trunk/conf/realtime.conf index 249c7501a..26b9b989f 100644 --- a/trunk/conf/realtime.conf +++ b/trunk/conf/realtime.conf @@ -4,15 +4,17 @@ listen 1935; max_connections 1000; -daemon off; -srs_log_tank console; vhost __defaultVhost__ { - gop_cache off; - queue_length 10; + tcp_nodelay on min_latency on; - mr { - enabled off; + + play { + gop_cache off; + queue_length 10; + mw_latency 100; + } + + publish { + mr off; } - mw_latency 100; - tcp_nodelay on; } diff --git a/trunk/conf/rtmp.conf b/trunk/conf/rtmp.conf index e05f15408..94c740c50 100644 --- a/trunk/conf/rtmp.conf +++ b/trunk/conf/rtmp.conf @@ -4,7 +4,5 @@ listen 1935; max_connections 1000; -daemon off; -srs_log_tank console; vhost __defaultVhost__ { } diff --git a/trunk/conf/srs.conf b/trunk/conf/srs.conf index da062849a..736547445 100644 --- a/trunk/conf/srs.conf +++ b/trunk/conf/srs.conf @@ -5,6 +5,7 @@ listen 1935; max_connections 1000; srs_log_tank file; srs_log_file ./objs/srs.log; +daemon on; http_api { enabled on; listen 1985; @@ -19,4 +20,11 @@ stats { disk sda sdb xvda xvdb; } vhost __defaultVhost__ { + hls { + enabled on; + } + http_remux { + enabled on; + mount [vhost]/[app]/[stream].flv; + } } diff --git a/trunk/conf/transform.edge.conf b/trunk/conf/transform.edge.conf index 5b3a04f02..2b950f5be 100644 --- a/trunk/conf/transform.edge.conf +++ b/trunk/conf/transform.edge.conf @@ -23,7 +23,9 @@ vhost source.srs.com { } } vhost transform.srs.edge.com { - mode remote; - origin 127.0.0.1:1935; - vhost source.srs.com; + cluster { + mode remote; + origin 127.0.0.1:1935; + vhost source.srs.com; + } } diff --git a/trunk/configure b/trunk/configure index 372f2df13..916e1ba99 100755 --- a/trunk/configure +++ b/trunk/configure @@ -17,7 +17,7 @@ BLACK="\\033[0m" ##################################################################################### # parse user options, set the variables like: -# srs features: SRS_SSL/SRS_HLS/SRS_NGINX/SRS_FFMPEG_TOOL/SRS_HTTP_CALLBACK/...... +# srs features: SRS_SSL/SRS_HLS/SRS_HTTP_CALLBACK/...... # build options: SRS_JOBS ##################################################################################### # parse options, exit with error when parse options invalid. @@ -26,16 +26,11 @@ BLACK="\\033[0m" # setup variables when options parsed. . auto/setup_variables.sh -# clean the exists, when not export srs-librtmp. -# do this only when the options is ok. -if [[ -f Makefile ]]; then -make clean -fi -# remove makefile +# We don't need to cleanup the exists files. rm -f ${SRS_WORKDIR}/${SRS_MAKEFILE} # create objs -mkdir -p ${SRS_OBJS} +mkdir -p ${SRS_OBJS}/${SRS_PLATFORM} # for export srs-librtmp, change target to it. . auto/generate-srs-librtmp-project.sh @@ -57,7 +52,7 @@ SrsLibrtmpSampleEntry="nossl" if [ $SRS_SSL = YES ]; then SrsLibrtmpSampleEntry="ssl";fi # utest make entry, (cd utest; make) SrsUtestMakeEntry="@echo -e \"ignore utest for it's disabled\"" -if [ $SRS_UTEST = YES ]; then SrsUtestMakeEntry="(cd ${SRS_OBJS_DIR}/utest; \$(MAKE))"; fi +if [ $SRS_UTEST = YES ]; then SrsUtestMakeEntry="(cd ${SRS_OBJS_DIR}/${SRS_PLATFORM}/utest && \$(MAKE))"; fi ##################################################################################### # finger out modules to install. @@ -68,17 +63,28 @@ __mfiles=`find modules -name "config"` && for __mfile in $__mfiles; do done # variables for makefile for all modules. -__mphonys="" && __mdefaults="" && __mcleanups="" +__mphonys="" && __mdefaults="" && __mcleanups="" && __makefiles="" # add each modules for application for SRS_MODULE in ${SRS_MODULES[*]}; do echo "install module at: $SRS_MODULE" . $SRS_MODULE/config - if [[ 0 -eq ${#SRS_MODULE_MAIN[@]} ]]; then continue; fi - __mphonys="$__mphonys $SRS_MODULE_NAME" - __mdefaults="$__mdefaults $SRS_MODULE_NAME" - __mcleanups="$__mcleanups $SRS_MODULE_NAME" + if [[ $SRS_MODULE_MAKEFILE != "" ]]; then + __makefiles="$__makefiles $SRS_MODULE_MAKEFILE" + fi + if [[ 0 -ne ${#SRS_MODULE_MAIN[@]} ]]; then + __mphonys="$__mphonys $SRS_MODULE_NAME" + __mdefaults="$__mdefaults $SRS_MODULE_NAME" + __mcleanups="$__mcleanups $SRS_MODULE_NAME" + fi done +# generate extra phony for each modules. +cat << END > ${SRS_OBJS}/${SRS_MAKEFILE} + +.PHONY: $__mphonys + +END + ##################################################################################### # build tools or compiler args. # enable gdb debug @@ -88,19 +94,36 @@ WarnLevel=" -Wall" # the compile standard. CppStd="-ansi" # for library compile -LibraryCompile=" -fPIC" +if [[ $SRS_EXPORT_LIBRTMP_PROJECT == YES ]]; then + LibraryCompile=" -fPIC" +fi # performance of gprof SrsGprof=""; SrsGprofLink=""; if [ $SRS_GPROF = YES ]; then SrsGprof=" -pg -lc_p"; SrsGprofLink=" -pg"; fi # performance of gperf SrsGperf=""; SrsGperfLink=""; if [ $SRS_GPERF = YES ]; then SrsGperfLink=" -lpthread"; fi # the cxx flag generated. CXXFLAGS="${CXXFLAGS} ${CppStd}${WarnLevel}${GDBDebug}${LibraryCompile}${SrsGprof}" -if [ $SRS_GPERF = YES ]; then CXXFLAGS="${CXXFLAGS} -fno-builtin-malloc -fno-builtin-calloc -fno-builtin-realloc -fno-builtin-free"; fi -cat << END > ${SRS_OBJS}/${SRS_MAKEFILE} -GCC = gcc -CXX = g++ -AR = ar -LINK = g++ +if [ $SRS_GPERF = YES ]; then + CXXFLAGS="${CXXFLAGS} -fno-builtin-malloc -fno-builtin-calloc -fno-builtin-realloc -fno-builtin-free"; +fi +# For coverage. +if [[ $SRS_GCOV == YES ]]; then + SrsGcov="-fprofile-arcs -ftest-coverage" +fi +if [[ $SRS_GCOV == YES ]]; then + CXXFLAGS="${CXXFLAGS} ${SrsGcov}"; +fi +# User configed options. +if [[ $SRS_EXTRA_FLAGS != '' ]]; then + CXXFLAGS="${CXXFLAGS} $SRS_EXTRA_FLAGS"; +fi +# Start to generate the Makefile. +cat << END >> ${SRS_OBJS}/${SRS_MAKEFILE} +GCC = ${SRS_TOOL_CC} +CXX = ${SRS_TOOL_CXX} +AR = ${SRS_TOOL_AR} +ARFLAGS = -rs +LINK = ${SRS_TOOL_CXX} CXXFLAGS = ${CXXFLAGS} .PHONY: default srs srs_ingest_hls librtmp @@ -116,23 +139,34 @@ END # # st(state-threads) the basic network library for SRS. LibSTRoot="${SRS_OBJS_DIR}/st"; LibSTfile="${LibSTRoot}/libst.a" -# hp(http-parser) the http request/url parser, for SRS to support HTTP callback. -LibHttpParserRoot=""; LibHttpParserfile="" -if [ $SRS_HTTP_CORE = YES ]; then LibHttpParserRoot="${SRS_OBJS_DIR}/hp"; LibHttpParserfile="${LibHttpParserRoot}/libhttp_parser.a"; fi -# openssl-1.0.1f, for the RTMP complex handshake. +if [[ $SRS_SHARED_ST == YES ]]; then LibSTfile="-lst"; fi +# openssl-1.1.0e, for the RTMP complex handshake. LibSSLRoot="";LibSSLfile="" -if [ $SRS_SSL = YES ]; then if [ $SRS_USE_SYS_SSL = NO ]; then LibSSLRoot="${SRS_OBJS_DIR}/openssl/include"; LibSSLfile="${SRS_OBJS_DIR}/openssl/lib/libssl.a ${SRS_OBJS_DIR}/openssl/lib/libcrypto.a"; fi fi +if [[ $SRS_SSL == YES && $SRS_USE_SYS_SSL == NO ]]; then + LibSSLRoot="${SRS_OBJS_DIR}/openssl/include"; LibSSLfile="${SRS_OBJS_DIR}/openssl/lib/libssl.a ${SRS_OBJS_DIR}/openssl/lib/libcrypto.a"; +fi # gperftools-2.1, for mem check and mem/cpu profile LibGperfRoot=""; LibGperfFile="" -if [ $SRS_GPERF = YES ]; then LibGperfRoot="${SRS_OBJS_DIR}/gperf/include"; LibGperfFile="${SRS_OBJS_DIR}/gperf/lib/libtcmalloc_and_profiler.a"; fi +if [ $SRS_GPERF = YES ]; then + LibGperfRoot="${SRS_OBJS_DIR}/gperf/include"; LibGperfFile="${SRS_OBJS_DIR}/gperf/lib/libtcmalloc_and_profiler.a"; +fi +if [ $SRS_GPERF_MD = YES ]; then + LibGperfFile="${SRS_OBJS_DIR}/gperf/lib/libtcmalloc_debug.a"; +fi # the link options, always use static link -SrsLinkOptions="-ldl"; -if [ $SRS_SSL = YES ]; then if [ $SRS_USE_SYS_SSL = YES ]; then SrsLinkOptions="${SrsLinkOptions} -lssl -lcrypto"; fi fi +SrsLinkOptions="-ldl"; +if [[ $SRS_SSL == YES && $SRS_USE_SYS_SSL == YES ]]; then + SrsLinkOptions="${SrsLinkOptions} -lssl -lcrypto"; +fi # if static specified, add static # TODO: FIXME: remove static. -if [ $SRS_STATIC = YES ]; then SrsLinkOptions="${SrsLinkOptions} -static"; fi -# if mips, add -lgcc_eh, or stl compile failed. -if [ $SRS_MIPS_UBUNTU12 = YES ]; then SrsLinkOptions="${SrsLinkOptions} -lgcc_eh"; fi +if [ $SRS_STATIC = YES ]; then + SrsLinkOptions="${SrsLinkOptions} -static"; +fi +# For coverage. +if [[ $SRS_GCOV == YES ]]; then + SrsLinkOptions="${SrsLinkOptions} ${SrsGcov}"; +fi ##################################################################################### # Modules, compile each module, then link to binary @@ -141,18 +175,19 @@ if [ $SRS_MIPS_UBUNTU12 = YES ]; then SrsLinkOptions="${SrsLinkOptions} -lgcc_eh MODULE_ID="CORE" MODULE_DEPENDS=() ModuleLibIncs=(${SRS_OBJS_DIR}) -MODULE_FILES=("srs_core" "srs_core_autofree" "srs_core_performance" "srs_core_mem_watch") +MODULE_FILES=("srs_core" "srs_core_version3" "srs_core_autofree" "srs_core_performance" + "srs_core_mem_watch" "srs_core_time") CORE_INCS="src/core"; MODULE_DIR=${CORE_INCS} . auto/modules.sh CORE_OBJS="${MODULE_OBJS[@]}" # #Kernel, depends on core, provides error/log/config, nothing about stream information. MODULE_ID="KERNEL" MODULE_DEPENDS=("CORE") -ModuleLibIncs=(${SRS_OBJS_DIR}) -MODULE_FILES=("srs_kernel_error" "srs_kernel_log" "srs_kernel_stream" - "srs_kernel_utility" "srs_kernel_flv" "srs_kernel_codec" "srs_kernel_file" +ModuleLibIncs=(${SRS_OBJS_DIR} ${LibSSLRoot}) +MODULE_FILES=("srs_kernel_error" "srs_kernel_log" "srs_kernel_buffer" + "srs_kernel_utility" "srs_kernel_flv" "srs_kernel_codec" "srs_kernel_io" "srs_kernel_consts" "srs_kernel_aac" "srs_kernel_mp3" "srs_kernel_ts" - "srs_kernel_buffer") + "srs_kernel_stream" "srs_kernel_balance" "srs_kernel_mp4" "srs_kernel_file") KERNEL_INCS="src/kernel"; MODULE_DIR=${KERNEL_INCS} . auto/modules.sh KERNEL_OBJS="${MODULE_OBJS[@]}" # @@ -160,26 +195,42 @@ KERNEL_OBJS="${MODULE_OBJS[@]}" MODULE_ID="PROTOCOL" MODULE_DEPENDS=("CORE" "KERNEL") ModuleLibIncs=(${SRS_OBJS_DIR} ${LibSSLRoot}) -MODULE_FILES=("srs_rtmp_amf0" "srs_rtmp_io" "srs_rtmp_stack" - "srs_rtmp_handshake" "srs_rtmp_utility" "srs_rtmp_msg_array" "srs_protocol_buffer" - "srs_raw_avc" "srs_rtsp_stack" "srs_http_stack" "srs_protocol_kbps" "srs_protocol_json") +MODULE_FILES=("srs_protocol_amf0" "srs_protocol_io" "srs_rtmp_stack" + "srs_rtmp_handshake" "srs_protocol_utility" "srs_rtmp_msg_array" "srs_protocol_stream" + "srs_raw_avc" "srs_rtsp_stack" "srs_http_stack" "srs_protocol_kbps" "srs_protocol_json" + "srs_protocol_format") PROTOCOL_INCS="src/protocol"; MODULE_DIR=${PROTOCOL_INCS} . auto/modules.sh PROTOCOL_OBJS="${MODULE_OBJS[@]}" # -#App Module +#Service Module, for both Server and Client Modules. +if [ $SRS_EXPORT_LIBRTMP_PROJECT = NO ]; then + MODULE_ID="SERVICE" + MODULE_DEPENDS=("CORE" "KERNEL" "PROTOCOL") + ModuleLibIncs=(${LibSTRoot} ${SRS_OBJS_DIR} ${LibSSLRoot}) + MODULE_FILES=("srs_service_log" "srs_service_st" "srs_service_http_client" + "srs_service_http_conn" "srs_service_rtmp_conn" "srs_service_utility" + "srs_service_conn") + DEFINES="" + SERVICE_INCS="src/service"; MODULE_DIR=${SERVICE_INCS} . auto/modules.sh + SERVICE_OBJS="${MODULE_OBJS[@]}" +fi +# +#App Module, for SRS server only. if [ $SRS_EXPORT_LIBRTMP_PROJECT = NO ]; then MODULE_ID="APP" - MODULE_DEPENDS=("CORE" "KERNEL" "PROTOCOL") - ModuleLibIncs=(${LibSTRoot} ${LibHttpParserRoot} ${LibSSLRoot} ${SRS_OBJS_DIR}) + MODULE_DEPENDS=("CORE" "KERNEL" "PROTOCOL" "SERVICE") + ModuleLibIncs=(${LibSTRoot} ${SRS_OBJS_DIR} ${LibSSLRoot}) MODULE_FILES=("srs_app_server" "srs_app_conn" "srs_app_rtmp_conn" "srs_app_source" "srs_app_refer" "srs_app_hls" "srs_app_forward" "srs_app_encoder" "srs_app_http_stream" "srs_app_thread" "srs_app_bandwidth" "srs_app_st" "srs_app_log" "srs_app_config" "srs_app_pithy_print" "srs_app_reload" "srs_app_http_api" "srs_app_http_conn" "srs_app_http_hooks" - "srs_app_ingest" "srs_app_ffmpeg" "srs_app_utility" "srs_app_dvr" "srs_app_edge" + "srs_app_ingest" "srs_app_ffmpeg" "srs_app_utility" "srs_app_edge" "srs_app_heartbeat" "srs_app_empty" "srs_app_http_client" "srs_app_http_static" "srs_app_recv_thread" "srs_app_security" "srs_app_statistic" "srs_app_hds" "srs_app_mpegts_udp" "srs_app_rtsp" "srs_app_listener" "srs_app_async_call" - "srs_app_caster_flv" "srs_app_latest_version" "srs_app_uuid") + "srs_app_caster_flv" "srs_app_latest_version" "srs_app_uuid" "srs_app_process" "srs_app_ng_exec" + "srs_app_hourglass" "srs_app_dash" "srs_app_fragment" "srs_app_dvr" + "srs_app_coworkers") DEFINES="" # add each modules for app for SRS_MODULE in ${SRS_MODULES[*]}; do @@ -199,16 +250,28 @@ MODULE_FILES=("srs_librtmp" "srs_lib_simple_socket" "srs_lib_bandwidth") LIBS_INCS="src/libs"; MODULE_DIR=${LIBS_INCS} . auto/modules.sh LIBS_OBJS="${MODULE_OBJS[@]}" # -#Main Module +#Server Module, for SRS only. if [ $SRS_EXPORT_LIBRTMP_PROJECT = NO ]; then - MODULE_ID="MAIN" - MODULE_DEPENDS=("CORE" "KERNEL" "PROTOCOL" "APP") - ModuleLibIncs=(${LibSTRoot} ${SRS_OBJS_DIR} ${LibGperfRoot} ${LibHttpParserRoot} ${LibSSLRoot}) - MODULE_FILES=("srs_main_server" "srs_main_ingest_hls") + MODULE_ID="SERVER" + MODULE_DEPENDS=("CORE" "KERNEL" "PROTOCOL" "SERVICE" "APP") + ModuleLibIncs=(${LibSTRoot} ${SRS_OBJS_DIR} ${LibGperfRoot} ${LibSSLRoot}) + MODULE_FILES=("srs_main_server") + SERVER_INCS="src/main"; MODULE_DIR=${SERVER_INCS} . auto/modules.sh + SERVER_OBJS="${MODULE_OBJS[@]}" +fi +# +#Main Module, for app from modules. +if [ $SRS_EXPORT_LIBRTMP_PROJECT = NO ]; then + MODULE_ID="MAIN" + MODULE_DEPENDS=("CORE" "KERNEL" "PROTOCOL" "SERVICE") + ModuleLibIncs=(${LibSTRoot} ${SRS_OBJS_DIR} ${LibGperfRoot} ${LibSSLRoot}) + MODULE_FILES=() + DEFINES="" # add each modules for main for SRS_MODULE in ${SRS_MODULES[*]}; do . $SRS_MODULE/config MODULE_FILES+=("${SRS_MODULE_MAIN[*]}") + DEFINES="${DEFINES} ${SRS_MODULE_DEFINES}" done MAIN_INCS="src/main"; MODULE_DIR=${MAIN_INCS} . auto/modules.sh MAIN_OBJS="${MODULE_OBJS[@]}" @@ -218,33 +281,32 @@ fi # Binaries, main entrances, link the module and its depends modules, # then link to a binary, for example, objs/srs # -# disable all app when export librtmp +# Disable SRS application for exporting librtmp. if [ $SRS_EXPORT_LIBRTMP_PROJECT = NO ]; then # all main entrances - MAIN_ENTRANCES=("srs_main_server" "srs_main_ingest_hls") - # add each modules for main + MAIN_ENTRANCES=("srs_main_server") for SRS_MODULE in ${SRS_MODULES[*]}; do . $SRS_MODULE/config MAIN_ENTRANCES+=("${SRS_MODULE_MAIN[*]}") done - # + # # all depends libraries - ModuleLibFiles=(${LibSTfile} ${LibHttpParserfile} ${LibSSLfile} ${LibGperfFile}) + ModuleLibFiles=(${LibSTfile} ${LibSSLfile} ${LibGperfFile}) # all depends objects - MODULE_OBJS="${CORE_OBJS[@]} ${KERNEL_OBJS[@]} ${PROTOCOL_OBJS[@]} ${APP_OBJS[@]} ${MAIN_OBJS[@]}" + MODULE_OBJS="${CORE_OBJS[@]} ${KERNEL_OBJS[@]} ${PROTOCOL_OBJS[@]} ${SERVICE_OBJS[@]} ${APP_OBJS[@]} ${SERVER_OBJS[@]}" LINK_OPTIONS="${SrsLinkOptions}${SrsGprofLink}${SrsGperfLink}" # # srs: srs(simple rtmp server) over st(state-threads) BUILD_KEY="srs" APP_MAIN="srs_main_server" APP_NAME="srs" . auto/apps.sh - # - # srs_ingest_hls: to ingest hls stream to srs. - BUILD_KEY="srs_ingest_hls" APP_MAIN="srs_main_ingest_hls" APP_NAME="srs_ingest_hls" . auto/apps.sh - # add each modules for application + # + # For modules, without the app module. + MODULE_OBJS="${CORE_OBJS[@]} ${KERNEL_OBJS[@]} ${PROTOCOL_OBJS[@]} ${SERVICE_OBJS[@]} ${MAIN_OBJS[@]}" + # for SRS_MODULE in ${SRS_MODULES[*]}; do . $SRS_MODULE/config - # no SRS_MODULE_MAIN + # no SRS_MODULE_MAIN if [[ 0 -eq ${#SRS_MODULE_MAIN[@]} ]]; then continue; fi - BUILD_KEY="$SRS_MODULE_NAME" APP_MAIN="$SRS_MODULE_MAIN" APP_NAME="$SRS_MODULE_NAME" . auto/apps.sh + BUILD_KEY="$SRS_MODULE_NAME" APP_MAIN="${SRS_MODULE_MAIN[0]}" APP_NAME="$SRS_MODULE_NAME" . auto/apps.sh done fi # srs librtmp @@ -252,16 +314,21 @@ if [ $SRS_LIBRTMP = YES ]; then MODULE_OBJS="${CORE_OBJS[@]} ${KERNEL_OBJS[@]} ${PROTOCOL_OBJS[@]} ${LIBS_OBJS[@]}" BUILD_KEY="librtmp" LIB_NAME="lib/srs_librtmp" . auto/libs.sh fi +# For utest on mac. +# @see https://github.com/protocolbuffers/protobuf/issues/51#issuecomment-111044468 +if [[ $SRS_OSX == YES ]]; then + EXTRA_DEFINES="-DGTEST_USE_OWN_TR1_TUPLE=1" +fi # # utest, the unit-test cases of srs, base on gtest1.6 if [ $SRS_UTEST = YES ]; then - MODULE_FILES=("srs_utest" "srs_utest_amf0" "srs_utest_protocol" - "srs_utest_kernel" "srs_utest_core" "srs_utest_config" - "srs_utest_reload") + MODULE_FILES=("srs_utest" "srs_utest_amf0" "srs_utest_protocol" "srs_utest_kernel" "srs_utest_core" + "srs_utest_config" "srs_utest_rtmp" "srs_utest_http" "srs_utest_avc" "srs_utest_reload" + "srs_utest_mp4" "srs_utest_service" "srs_utest_app") ModuleLibIncs=(${SRS_OBJS_DIR} ${LibSTRoot} ${LibSSLRoot}) - ModuleLibFiles=(${LibSTfile} ${LibHttpParserfile} ${LibSSLfile}) - MODULE_DEPENDS=("CORE" "KERNEL" "PROTOCOL" "APP") - MODULE_OBJS="${CORE_OBJS[@]} ${KERNEL_OBJS[@]} ${PROTOCOL_OBJS[@]} ${APP_OBJS[@]}" + ModuleLibFiles=(${LibSTfile} ${LibSSLfile}) + MODULE_DEPENDS=("CORE" "KERNEL" "PROTOCOL" "SERVICE" "APP") + MODULE_OBJS="${CORE_OBJS[@]} ${KERNEL_OBJS[@]} ${PROTOCOL_OBJS[@]} ${SERVICE_OBJS[@]} ${APP_OBJS[@]}" LINK_OPTIONS="-lpthread ${SrsLinkOptions}" MODULE_DIR="src/utest" APP_NAME="srs_utest" . auto/utest.sh fi @@ -271,7 +338,7 @@ fi ##################################################################################### # makefile -echo "generate Makefile" +echo "Generate Makefile" # backup old makefile. rm -f ${SRS_WORKDIR}/${SRS_MAKEFILE}.bk && @@ -279,79 +346,103 @@ mv ${SRS_WORKDIR}/${SRS_MAKEFILE} ${SRS_WORKDIR}/${SRS_MAKEFILE}.bk # generate phony header cat << END > ${SRS_WORKDIR}/${SRS_MAKEFILE} -.PHONY: default _default install install-api help clean server srs_ingest_hls librtmp utest _prepare_dir $__mphonys +.PHONY: default _default install help clean destroy server srs_ingest_hls librtmp utest _prepare_dir $__mphonys +.PHONY: clean_srs clean_modules clean_st clean_openssl clean_ffmpeg clean_nginx clean_cherrypy +.PHONY: st # install prefix. SRS_PREFIX=${SRS_PREFIX} __REAL_INSTALL=\$(DESTDIR)\$(SRS_PREFIX) -END - -# embeded, ubuntu12, use embeded tool chain. -if [ $SRS_CROSS_BUILD = YES ]; then - cat << END >> ${SRS_WORKDIR}/${SRS_MAKEFILE} -default: - \$(MAKE) GCC=${SrsArmGCC} CXX=${SrsArmCXX} AR=${SrsArmAR} LINK=${SrsArmCXX} _default - -END -# x86/x64, use gnu-gcc/g++ tool chain. -else - cat << END >> ${SRS_WORKDIR}/${SRS_MAKEFILE} default: \$(MAKE) _default END -fi # the real entry for all platform: # the server, librtmp and utest # where the bellow will check and disable some entry by only echo. cat << END >> ${SRS_WORKDIR}/${SRS_MAKEFILE} -_default: server srs_ingest_hls librtmp utest $__mdefaults +_default: server srs_ingest_hls librtmp utest __modules $__mdefaults @bash objs/_srs_build_summary.sh help: - @echo "Usage: make ||||||||" + @echo "Usage: make |||||||" @echo " help display this help menu" - @echo " clean cleanup project" + @echo " clean cleanup project and all depends" + @echo " destroy Cleanup all files for this platform in ${SRS_OBJS_DIR}/${SRS_PLATFORM}" @echo " server build the srs(simple rtmp server) over st(state-threads)" - @echo " srs_ingest_hls build the hls ingest tool of srs." @echo " librtmp build the client publish/play library, and samples" @echo " utest build the utest for srs" @echo " install install srs to the prefix path" - @echo " install-api install srs and api-server to the prefix path" @echo " uninstall uninstall srs from prefix path" + @echo "To clean special module:" + @echo " clean_st Clean depend st-srs in ${SRS_OBJS_DIR}/${SRS_PLATFORM}/st-srs" + @echo " clean_openssl Clean depend openssl in objs" + @echo " clean_ffmpeg Clean depend ffmpeg in objs" @echo "@remark all modules will auto genearted and build" @echo "For example:" @echo " make" @echo " make help" -clean: +doclean: (cd ${SRS_OBJS_DIR} && rm -rf srs srs_utest $__mcleanups) - (cd ${SRS_OBJS_DIR} && rm -rf src include lib) - (cd ${SRS_OBJS_DIR}/utest && rm -rf *.o *.a) + (cd ${SRS_OBJS_DIR} && rm -rf src/* include lib) + (mkdir -p ${SRS_OBJS_DIR}/utest && cd ${SRS_OBJS_DIR}/utest && rm -rf *.o *.a) + (cd research/librtmp && make clean) + (cd research/api-server/static-dir && rm -rf crossdomain.xml forward live players) + +clean: clean_srs clean_modules + @echo "You can clean each some components, see make help" + +destroy: clean_st clean_openssl clean_ffmpeg clean_nginx clean_cherrypy + (cd ${SRS_OBJS_DIR} && rm -rf ${SRS_PLATFORM}) + +clean_srs: + (cd ${SRS_OBJS_DIR} && rm -rf srs srs_utest) + (cd ${SRS_OBJS_DIR}/${SRS_PLATFORM} && rm -rf src/* include/* lib/* utest/*) + +clean_modules: + (cd ${SRS_OBJS_DIR} && rm -rf $__mdefaults) + +clean_st: + (cd ${SRS_OBJS_DIR}/${SRS_PLATFORM}/st-srs && make clean) + +clean_openssl: + (cd ${SRS_OBJS_DIR}/${SRS_PLATFORM} && rm -rf openssl*) + +clean_ffmpeg: + (cd ${SRS_OBJS_DIR}/${SRS_PLATFORM} && rm -rf ffmpeg) + +clean_nginx: + (cd ${SRS_OBJS_DIR} && rm -rf nginx) + +clean_cherrypy: (cd research/librtmp && make clean) (cd research/api-server/static-dir && rm -rf crossdomain.xml forward live players) END +# for Makefile of all modules. +# depends on server, for some modules maybe use srs files. +echo "__modules: server" >> ${SRS_WORKDIR}/${SRS_MAKEFILE} +for MMF in ${__makefiles[*]}; do + echo " \$(MAKE) -f $MMF" >> ${SRS_WORKDIR}/${SRS_MAKEFILE} +done +echo "" >> ${SRS_WORKDIR}/${SRS_MAKEFILE} + # if export librtmp, donot build the srs server. if [ $SRS_EXPORT_LIBRTMP_PROJECT != NO ]; then cat << END >> ${SRS_WORKDIR}/${SRS_MAKEFILE} server: _prepare_dir - @echo "donot build the srs(simple rtmp server) for srs-librtmp" -srs_ingest_hls: _prepare_dir - @echo "donot build the srs_ingest_hls for srs-librtmp" + @echo "Ingore srs(simple rtmp server) for srs-librtmp" END else cat << END >> ${SRS_WORKDIR}/${SRS_MAKEFILE} server: _prepare_dir - @echo "build the srs(simple rtmp server) over st(state-threads)" + @echo "Build the srs(simple rtmp server) over ST(state-threads)" \$(MAKE) -f ${SRS_OBJS_DIR}/${SRS_MAKEFILE} srs -srs_ingest_hls: _prepare_dir - @echo "build the srs_ingest_hls for srs" - \$(MAKE) -f ${SRS_OBJS_DIR}/${SRS_MAKEFILE} srs_ingest_hls END fi @@ -361,14 +452,14 @@ for SRS_MODULE in ${SRS_MODULES[*]}; do # if export librtmp, donot build the bravo-ingest. if [ $SRS_EXPORT_LIBRTMP_PROJECT != NO ]; then cat << END >> ${SRS_WORKDIR}/${SRS_MAKEFILE} -$SRS_MODULE_NAME: _prepare_dir - @echo "donot build the $SRS_MODULE_NAME for srs-librtmp" +$SRS_MODULE_NAME: _prepare_dir server + @echo "Ingore the $SRS_MODULE_NAME for srs-librtmp" END else cat << END >> ${SRS_WORKDIR}/${SRS_MAKEFILE} -$SRS_MODULE_NAME: _prepare_dir - @echo "build the $SRS_MODULE_NAME over SRS" +$SRS_MODULE_NAME: _prepare_dir server + @echo "Build the $SRS_MODULE_NAME over SRS" \$(MAKE) -f ${SRS_OBJS_DIR}/${SRS_MAKEFILE} $SRS_MODULE_NAME END @@ -379,13 +470,10 @@ done if [ $SRS_EXPORT_LIBRTMP_PROJECT != NO ]; then cat << END >> ${SRS_WORKDIR}/${SRS_MAKEFILE} uninstall: - @echo "disable uninstall for srs-librtmp" - -install-api: - @echo "disable install-api for srs-librtmp" + @echo "Disable uninstall for srs-librtmp" install: - @echo "disable install for srs-librtmp" + @echo "Disable install for srs-librtmp" END else @@ -394,51 +482,35 @@ uninstall: @echo "rmdir \$(SRS_PREFIX)" @rm -rf \$(SRS_PREFIX) -install-api: install - @echo "mkdir \$(__REAL_INSTALL)" - @mkdir -p \$(__REAL_INSTALL) - @echo "copy binary files" - @mkdir -p \$(__REAL_INSTALL)/research/api-server - @cp research/api-server/server.py \$(__REAL_INSTALL)/research/api-server - @mkdir -p \$(__REAL_INSTALL)/objs/ffmpeg/bin - @cp objs/ffmpeg/bin/ffmpeg \$(__REAL_INSTALL)/objs/ffmpeg/bin - @echo "copy html files" - @mkdir -p \$(__REAL_INSTALL)/research/api-server/static-dir/players - @cp research/api-server/static-dir/crossdomain.xml \$(__REAL_INSTALL)/research/api-server/static-dir - @cp research/api-server/static-dir/index.html \$(__REAL_INSTALL)/research/api-server/static-dir - @cp -r research/api-server/static-dir/players/* \$(__REAL_INSTALL)/research/api-server/static-dir/players - @echo "copy init.d script files" - @mkdir -p \$(__REAL_INSTALL)/etc/init.d - @cp etc/init.d/srs-api \$(__REAL_INSTALL)/etc/init.d - @sed -i "s|^ROOT=.*|ROOT=\"\$(SRS_PREFIX)\"|g" \$(__REAL_INSTALL)/etc/init.d/srs-api - @echo "" - @echo "api installed, to link and start api:" - @echo " sudo ln -sf \$(SRS_PREFIX)/etc/init.d/srs-api /etc/init.d/srs-api" - @echo " /etc/init.d/srs-api start" - @echo " http://\$(shell bash auto/local_ip.sh):8085" - @echo "@see: https://github.com/ossrs/srs/wiki/v1_CN_LinuxService" - install: - @echo "mkdir \$(__REAL_INSTALL)" + @echo "Now mkdir \$(__REAL_INSTALL)" @mkdir -p \$(__REAL_INSTALL) - @echo "make the http root dir" + @echo "Now make the http root dir" @mkdir -p \$(__REAL_INSTALL)/objs/nginx/html - @cp research/api-server/static-dir/crossdomain.xml \$(__REAL_INSTALL)/objs/nginx/html - @echo "copy binary files" + @cp -f research/api-server/static-dir/crossdomain.xml \$(__REAL_INSTALL)/objs/nginx/html + @echo "Now copy binary files" @mkdir -p \$(__REAL_INSTALL)/objs - @cp objs/srs \$(__REAL_INSTALL)/objs - @echo "copy srs conf files" + @cp -f objs/srs \$(__REAL_INSTALL)/objs + @echo "Now copy srs conf files" @mkdir -p \$(__REAL_INSTALL)/conf - @cp conf/*.conf \$(__REAL_INSTALL)/conf - @echo "copy init.d script files" + @cp -f conf/*.conf \$(__REAL_INSTALL)/conf + @echo "Now copy init.d script files" @mkdir -p \$(__REAL_INSTALL)/etc/init.d - @cp etc/init.d/srs \$(__REAL_INSTALL)/etc/init.d + @cp -f etc/init.d/srs \$(__REAL_INSTALL)/etc/init.d @sed -i "s|^ROOT=.*|ROOT=\"\$(SRS_PREFIX)\"|g" \$(__REAL_INSTALL)/etc/init.d/srs + @echo "Now copy systemctl service files" + @mkdir -p \$(__REAL_INSTALL)/usr/lib/systemd/system + @cp -f usr/lib/systemd/system/srs.service \$(__REAL_INSTALL)/usr/lib/systemd/system/srs.service @echo "" - @echo "srs installed, to link and start srs:" - @echo " sudo ln -sf \$(SRS_PREFIX)/etc/init.d/srs /etc/init.d/srs" + @echo "The api installed, to link and start srs, please" + @echo "For CentOS6:" + @echo " sudo ln -sf \$(SRS_PREFIX)/etc/init.d/srs /etc/init.d/srs &&" @echo " /etc/init.d/srs start" - @echo "@see: https://github.com/ossrs/srs/wiki/v1_CN_LinuxService" + @echo "For CentOS7:" + @echo " sudo ln -sf \$(SRS_PREFIX)/etc/init.d/srs /etc/init.d/srs &&" + @echo " sudo cp -f \$(SRS_PREFIX)/usr/lib/systemd/system/srs.service /usr/lib/systemd/system/srs.service && sudo systemctl daemon-reload && sudo systemctl enable srs &&" + @echo " sudo systemctl start srs" + @echo "@see: https://github.com/ossrs/srs/wiki/v3_CN_LinuxService" END fi @@ -447,16 +519,16 @@ fi if [ $SRS_LIBRTMP = YES ]; then cat << END >> ${SRS_WORKDIR}/${SRS_MAKEFILE} librtmp: server - @echo "build the client publish/play library." + @echo "Building the client publish/play library." \$(MAKE) -f ${SRS_OBJS_DIR}/${SRS_MAKEFILE} librtmp - @echo "build the srs-librtmp sample" - (cd research/librtmp; \$(MAKE) ${SrsLibrtmpSampleEntry}) + @echo "Building the srs-librtmp example." + (cd research/librtmp && \$(MAKE) EXTRA_CXXFLAGS="${SrsGcov}" ${SrsLibrtmpSampleEntry}) END else cat << END >> ${SRS_WORKDIR}/${SRS_MAKEFILE} librtmp: server - @echo "srs-librtmp is disabled, ignore." + @echo "Ignore srs-librtmp for it's disabled." END fi @@ -464,15 +536,15 @@ fi if [ $SRS_UTEST = YES ]; then cat << END >> ${SRS_WORKDIR}/${SRS_MAKEFILE} utest: server - @echo "build the utest for srs" + @echo "Building the utest for srs" ${SrsUtestMakeEntry} - @echo "utest for srs build success" + @echo "The utest is built ok." END else cat << END >> ${SRS_WORKDIR}/${SRS_MAKEFILE} utest: server - @echo "utest is disabled, ignore" + @echo "Ignore utest for it's disabled." END fi @@ -487,7 +559,7 @@ END cat ${SRS_WORKDIR}/${SRS_MAKEFILE}.bk >> ${SRS_WORKDIR}/${SRS_MAKEFILE} && rm -f ${SRS_WORKDIR}/${SRS_MAKEFILE}.bk -echo 'configure ok! ' +echo 'Configure ok! ' ##################################################################################### # when configure success, prepare build @@ -503,122 +575,112 @@ fi # summary if [ $SRS_EXPORT_LIBRTMP_PROJECT = NO ]; then echo "" - echo "configure summary:" - echo " ${SRS_AUTO_USER_CONFIGURE}" - echo " ${SRS_AUTO_CONFIGURE}" + echo "Configure summary:" + echo " ${SRS_AUTO_USER_CONFIGURE}" + echo " ${SRS_AUTO_CONFIGURE}" if [ $SRS_HLS = YES ]; then - echo -e "${GREEN}HLS is enabled${BLACK}" + echo -e "${GREEN}HLS is enabled.${BLACK}" else - echo -e "${YELLOW}warning: without HLS support${BLACK}" + echo -e "${YELLOW}Warning: HLS is disabled.${BLACK}" fi if [ $SRS_STREAM_CASTER = YES ]; then - echo -e "${YELLOW}Experiment: StreamCaster is enabled${BLACK}" + echo -e "${YELLOW}Experiment: StreamCaster is enabled.${BLACK}" else - echo -e "${GREEN}note: without StreamCaster support${BLACK}" + echo -e "${GREEN}Note: StreamCaster is disabled.${BLACK}" fi if [ $SRS_HDS = YES ]; then - echo -e "${YELLOW}Experiment: HDS is enabled${BLACK}" + echo -e "${YELLOW}Experiment: HDS is enabled.${BLACK}" else - echo -e "${GREEN}warning: without HDS support${BLACK}" - fi - if [ $SRS_NGINX = YES ]; then - echo -e "${GREEN}Nginx http server is enabled${BLACK}" - else - echo -e "${GREEN}note: Nginx http server is disabled${BLACK}" + echo -e "${GREEN}Warning: HDS is disabled.${BLACK}" fi if [ $SRS_DVR = YES ]; then - echo -e "${GREEN}DVR is enabled${BLACK}" + echo -e "${GREEN}DVR is enabled.${BLACK}" else - echo -e "${YELLOW}warning: without DVR support${BLACK}" + echo -e "${YELLOW}Warning: DVR is disabled.${BLACK}" fi if [ $SRS_SSL = YES ]; then - echo -e "${GREEN}rtmp complex handshake is enabled${BLACK}" + echo -e "${GREEN}RTMP complex handshake is enabled${BLACK}" else - echo -e "${YELLOW}warning: without rtmp complex handshake support, donot support h264/aac to adobe flash player${BLACK}" - fi - if [ $SRS_FFMPEG_TOOL = YES ]; then - echo -e "${GREEN}transcode/mux/ingest tool FFMPEG is enabled${BLACK}" - else - echo -e "${YELLOW}warning: without transcode/mux/ingest tool FFMPEG support${BLACK}" + echo -e "${YELLOW}Warning: RTMP complex handshake is disabled, flash cann't play h264/aac.${BLACK}" fi if [ $SRS_TRANSCODE = YES ]; then - echo -e "${GREEN}transcoding RTMP stream is enabled${BLACK}" + echo -e "${GREEN}The transcoding is enabled${BLACK}" else - echo -e "${YELLOW}warning: without transcoding RTMP stream support${BLACK}" + echo -e "${YELLOW}Warning: The transcoding is disabled.${BLACK}" fi if [ $SRS_INGEST = YES ]; then - echo -e "${GREEN}ingest file/stream/device is enabled${BLACK}" + echo -e "${GREEN}The ingesting is enabled.${BLACK}" else - echo -e "${YELLOW}warning: without ingest file/stream/device support${BLACK}" + echo -e "${YELLOW}Warning: The ingesting is disabled.${BLACK}" fi if [ $SRS_HTTP_CALLBACK = YES ]; then - echo -e "${GREEN}http hooks callback over CherryPy is enabled${BLACK}" + echo -e "${GREEN}The http-callback is enabled${BLACK}" else - echo -e "${YELLOW}warning: without http hooks callback over CherryPy support${BLACK}" + echo -e "${YELLOW}Warning: The http-callback is disabled.${BLACK}" fi if [ $SRS_HTTP_SERVER = YES ]; then - echo -e "${GREEN}http server to delivery http stream is enabled${BLACK}" + echo -e "${GREEN}Embeded HTTP server for HTTP-FLV/HLS is enabled.${BLACK}" else - echo -e "${YELLOW}warning: without http server to delivery http stream support${BLACK}" + echo -e "${YELLOW}Warning: Embeded HTTP server is disabled, HTTP-FLV is disabled, please use nginx to delivery HLS.${BLACK}" fi if [ $SRS_HTTP_API = YES ]; then - echo -e "${GREEN}http api to manage server is enabled${BLACK}" + echo -e "${GREEN}The HTTP API is enabled${BLACK}" else - echo -e "${YELLOW}warning: without http api to manage server support${BLACK}" + echo -e "${YELLOW}Warning: The HTTP API is disabled.${BLACK}" fi if [ $SRS_LIBRTMP = YES ]; then - echo -e "${GREEN}srs-librtmp for client is enabled${BLACK}" + echo -e "${GREEN}The client-side srs-librtmp is enabled.${BLACK}" else - echo -e "${YELLOW}note: srs-librtmp for client is disabled${BLACK}" + echo -e "${YELLOW}Note: The client-side srs-librtmp is disabled.${BLACK}" fi if [ $SRS_RESEARCH = YES ]; then - echo -e "${GREEN}research tools are builded${BLACK}" + echo -e "${GREEN}The research tools are enabled.${BLACK}" else - echo -e "${GREEN}note: research tools are not builded${BLACK}" + echo -e "${GREEN}Note: The research tools are disabled.${BLACK}" fi if [ $SRS_UTEST = YES ]; then - echo -e "${GREEN}utest for srs are builded${BLACK}" + echo -e "${GREEN}The utests are enabled.${BLACK}" else - echo -e "${YELLOW}note: utest for srs are not builded${BLACK}" + echo -e "${YELLOW}Note: The utests are disabled.${BLACK}" fi if [ $SRS_GPERF = YES ]; then - echo -e "${GREEN}gperf(tcmalloc) for srs are builded${BLACK}" + echo -e "${GREEN}The gperf(tcmalloc) is enabled.${BLACK}" else - echo -e "${GREEN}note: gperf(tcmalloc) for srs are not builded${BLACK}" + echo -e "${GREEN}Note: The gperf(tcmalloc) is disabled.${BLACK}" fi if [ $SRS_GPERF_MC = YES ]; then - echo -e "${YELLOW}gmc(gperf memory check) for srs are builded -- Performance may suffer${BLACK}" + echo -e "${YELLOW}The gmc(gperf memory check) is enabled, performance may suffer.${BLACK}" else - echo -e "${GREEN}note: gmc(gperf memory check) for srs are not builded${BLACK}" + echo -e "${GREEN}Note: The gmc(gperf memory check) is disabled.${BLACK}" + fi + if [ $SRS_GPERF_MD = YES ]; then + echo -e "${YELLOW}The gmd(gperf memory defense) is enabled, performance may suffer.${BLACK}" + else + echo -e "${GREEN}Note: The gmd(gperf memory defense) is disabled.${BLACK}" fi if [ $SRS_GPERF_MP = YES ]; then - echo -e "${YELLOW}gmp(gperf memory profile) for srs are builded -- Performance may suffer${BLACK}" + echo -e "${YELLOW}The gmp(gperf memory profile) is enabled, performance may suffer.${BLACK}" else - echo -e "${GREEN}note: gmp(gperf memory profile) for srs are not builded${BLACK}" + echo -e "${GREEN}Note: The gmp(gperf memory profile) is disabled.${BLACK}" fi if [ $SRS_GPERF_CP = YES ]; then - echo -e "${YELLOW}gcp(gperf cpu profile) for srs are builded -- Performance may suffer${BLACK}" + echo -e "${YELLOW}The gcp(gperf cpu profile) is enabled, performance may suffer.${BLACK}" else - echo -e "${GREEN}note: gcp(gperf cpu profile) for srs are not builded${BLACK}" + echo -e "${GREEN}Note: The gcp(gperf cpu profile) is disabled.${BLACK}" fi if [ $SRS_GPROF = YES ]; then - echo -e "${YELLOW}gprof(GNU profile tool) for srs are builded -- Performance may suffer${BLACK}" + echo -e "${YELLOW}The gprof(GNU profile tool) is enabled, performance may suffer.${BLACK}" else - echo -e "${GREEN}note: gprof(GNU profile tool) for srs are not builded${BLACK}" + echo -e "${GREEN}Note: The gprof(GNU profile tool) is disabled.${BLACK}" fi - if [ $SRS_ARM_UBUNTU12 = YES ]; then - echo -e "${GREEN}arm-ubuntu12(armhf, v7cpu) for srs are builded${BLACK}" + if [ $SRS_VALGRIND = YES ]; then + echo -e "${GREEN}The valgrind is enabled.${BLACK}" else - echo -e "${GREEN}note: arm-ubuntu12(armhf, v7cpu) for srs are not builded${BLACK}" - fi - if [ $SRS_MIPS_UBUNTU12 = YES ]; then - echo -e "${GREEN}mips-ubuntu12 for srs are builded${BLACK}" - else - echo -e "${GREEN}note: mips-ubuntu12 for srs are not builded${BLACK}" + echo -e "${GREEN}Note: The valgrind is disabled.${BLACK}" fi # add each modules for application for SRS_MODULE in ${SRS_MODULES[*]}; do - echo -e "${GREEN}module: $SRS_MODULE${BLACK}" + echo -e "${GREEN}Enable module: $SRS_MODULE${BLACK}" done fi @@ -628,25 +690,19 @@ fi if [ $SRS_EXPORT_LIBRTMP_PROJECT = NO ]; then ip=`ifconfig|grep "inet addr"| grep -v "127.0.0.1"|awk '{print $2}'|awk -F ':' 'NR==1 {print $2}'` echo "" - echo "to run 3rdparty application:" - if [ $SRS_NGINX = YES ]; then - echo "\" sudo ./objs/nginx/sbin/nginx \" to start the nginx http server for hls" - fi - if [ $SRS_FFMPEG_TOOL = YES ]; then - echo -e "\" ./objs/ffmpeg/bin/ffmpeg \" is used for live stream transcoding" - fi + echo "You can run 3rdparty applications:" if [ $SRS_HTTP_CALLBACK = YES ]; then echo -e "\" python ./research/api-server/server.py 8085 \" to start the api-server" fi echo "" - echo "to build:" + echo "You can build SRS:" echo "\" make \" to build the srs(simple rtmp server)." echo "\" make help \" to get the usage of make" else # for srs-librtmp single file, # package the whole project to srs_librtmp.h and srs_librtmp.cpp if [ $SRS_EXPORT_LIBRTMP_SINGLE != NO ]; then - echo "package the whole project to srs_librtmp.h and srs_librtmp.cpp" + echo "Packaging the whole project to srs_librtmp.h and srs_librtmp.cpp" . $SRS_EXPORT_LIBRTMP_SINGLE/auto/generate-srs-librtmp-single.sh echo -e "${GREEN}Please use the srs-librtmp files: ${BLACK}" echo -e "${GREEN} $SRS_EXPORT_LIBRTMP_PROJECT/srs_librtmp.h ${BLACK}" @@ -659,4 +715,6 @@ else echo -e "${GREEN}Please use the srs-librtmp project: ${BLACK}" echo -e "${GREEN} cd $SRS_EXPORT_LIBRTMP_PROJECT && make ${BLACK}" fi + # Change to experiment. + echo -e "${YELLOW}Warning: Notice srs-librtmp is deprecated and maybe removed in future.${BLACK}" fi diff --git a/trunk/doc/aac-iso-13818-7.pdf b/trunk/doc/ISO_IEC_13818-7-AAC-2004.pdf similarity index 100% rename from trunk/doc/aac-iso-13818-7.pdf rename to trunk/doc/ISO_IEC_13818-7-AAC-2004.pdf diff --git a/trunk/doc/ISO_IEC_14496-1-System-2010.pdf b/trunk/doc/ISO_IEC_14496-1-System-2010.pdf new file mode 100644 index 000000000..d804ac825 Binary files /dev/null and b/trunk/doc/ISO_IEC_14496-1-System-2010.pdf differ diff --git a/trunk/doc/H.264-AVC-ISO_IEC_14496-10.pdf b/trunk/doc/ISO_IEC_14496-10-AVC-2003.pdf similarity index 100% rename from trunk/doc/H.264-AVC-ISO_IEC_14496-10.pdf rename to trunk/doc/ISO_IEC_14496-10-AVC-2003.pdf diff --git a/trunk/doc/H.264-AVC-ISO_IEC_14496-10-2012.pdf b/trunk/doc/ISO_IEC_14496-10-AVC-2012.pdf similarity index 100% rename from trunk/doc/H.264-AVC-ISO_IEC_14496-10-2012.pdf rename to trunk/doc/ISO_IEC_14496-10-AVC-2012.pdf diff --git a/trunk/doc/ISO_IEC_14496-12-base-format-2005.pdf b/trunk/doc/ISO_IEC_14496-12-base-format-2005.pdf new file mode 100644 index 000000000..5a6a6290f Binary files /dev/null and b/trunk/doc/ISO_IEC_14496-12-base-format-2005.pdf differ diff --git a/trunk/doc/ISO_IEC_14496-12-base-format-2012.pdf b/trunk/doc/ISO_IEC_14496-12-base-format-2012.pdf new file mode 100644 index 000000000..d58e84ec1 Binary files /dev/null and b/trunk/doc/ISO_IEC_14496-12-base-format-2012.pdf differ diff --git a/trunk/doc/ISO_IEC_14496-14-MP4-2003.pdf b/trunk/doc/ISO_IEC_14496-14-MP4-2003.pdf new file mode 100644 index 000000000..865f91456 Binary files /dev/null and b/trunk/doc/ISO_IEC_14496-14-MP4-2003.pdf differ diff --git a/trunk/doc/H.264-AVC-ISO_IEC_14496-15.pdf b/trunk/doc/ISO_IEC_14496-15-AVC-format-2012.pdf similarity index 100% rename from trunk/doc/H.264-AVC-ISO_IEC_14496-15.pdf rename to trunk/doc/ISO_IEC_14496-15-AVC-format-2012.pdf diff --git a/trunk/doc/aac-mp4a-format-ISO_IEC_14496-3+2001.pdf b/trunk/doc/ISO_IEC_14496-3-AAC-2001.pdf similarity index 100% rename from trunk/doc/aac-mp4a-format-ISO_IEC_14496-3+2001.pdf rename to trunk/doc/ISO_IEC_14496-3-AAC-2001.pdf diff --git a/trunk/doc/ISO_IEC_23009-1-DASH-2012.pdf b/trunk/doc/ISO_IEC_23009-1-DASH-2012.pdf new file mode 100644 index 000000000..bde1fecc4 Binary files /dev/null and b/trunk/doc/ISO_IEC_23009-1-DASH-2012.pdf differ diff --git a/trunk/doc/kafka-160915-0553-82964.pdf b/trunk/doc/kafka-160915-0553-82964.pdf new file mode 100644 index 000000000..b820fcd17 Binary files /dev/null and b/trunk/doc/kafka-160915-0553-82964.pdf differ diff --git a/trunk/doc/readme.txt b/trunk/doc/readme.txt index 6de58c500..0ca6e4e33 100644 --- a/trunk/doc/readme.txt +++ b/trunk/doc/readme.txt @@ -18,6 +18,15 @@ amf3_spec_121207.pdf H.264-AVC-ISO_IEC_14496-10.pdf avc标准,编码部分。 + +H.264-AVC-ISO_IEC_14496-12_2012-mp4.pdf + mp4标准。 + +ISO_14496-14_2003_mp4-file-format.pdf + mp4文件格式。 + +MPEG-DASH-ISO_IEC_23009-1_2012.pdf + MPEG-DASH标准。 H.264-AVC-ISO_IEC_14496-10-2012.pdf avc标准,编码部分。 @@ -66,5 +75,15 @@ arpa-internet-text-messages-rfc822.txt mp3规范: mp3.id3v2.3.0.pdf http://id3.org/id3v2.3.0 + +kafka协议: +kafka-160915-0553-82964.pdf + https://cwiki.apache.org/confluence/display/KAFKA/A+Guide+To+The+Kafka+Protocol + +STUN: https://tools.ietf.org/html/rfc5389 +TURN: https://tools.ietf.org/html/rfc5766 +ICE: https://tools.ietf.org/html/rfc5245 +SIP: https://tools.ietf.org/html/rfc3261 Winlin + diff --git a/trunk/doc/http1.0-rfc1945.txt b/trunk/doc/rfc1945-1996-http1.0.txt similarity index 100% rename from trunk/doc/http1.0-rfc1945.txt rename to trunk/doc/rfc1945-1996-http1.0.txt diff --git a/trunk/doc/rtsp-rfc2326-1998.pdf b/trunk/doc/rfc2326-1998-rtsp.pdf similarity index 100% rename from trunk/doc/rtsp-rfc2326-1998.pdf rename to trunk/doc/rfc2326-1998-rtsp.pdf diff --git a/trunk/doc/http1.1-rfc2616.txt b/trunk/doc/rfc2616-1999-http1.1.txt similarity index 100% rename from trunk/doc/http1.1-rfc2616.txt rename to trunk/doc/rfc2616-1999-http1.1.txt diff --git a/trunk/doc/rfc3261-2002-sip.pdf b/trunk/doc/rfc3261-2002-sip.pdf new file mode 100644 index 000000000..1a4d0e2bd Binary files /dev/null and b/trunk/doc/rfc3261-2002-sip.pdf differ diff --git a/trunk/doc/rtp-rfc3550-2003.pdf b/trunk/doc/rfc3550-2003-rtp.pdf similarity index 100% rename from trunk/doc/rtp-rfc3550-2003.pdf rename to trunk/doc/rfc3550-2003-rtp.pdf diff --git a/trunk/doc/rfc5245-2010-ice.pdf b/trunk/doc/rfc5245-2010-ice.pdf new file mode 100644 index 000000000..6124de6f6 Binary files /dev/null and b/trunk/doc/rfc5245-2010-ice.pdf differ diff --git a/trunk/doc/rfc5389-2008-stun.pdf b/trunk/doc/rfc5389-2008-stun.pdf new file mode 100644 index 000000000..19533b04d Binary files /dev/null and b/trunk/doc/rfc5389-2008-stun.pdf differ diff --git a/trunk/doc/rfc5766-2010-turn.pdf b/trunk/doc/rfc5766-2010-turn.pdf new file mode 100644 index 000000000..04e9a54bb Binary files /dev/null and b/trunk/doc/rfc5766-2010-turn.pdf differ diff --git a/trunk/doc/rtmfp-rfc7016.pdf b/trunk/doc/rfc7016-2013-rtmfp.pdf similarity index 100% rename from trunk/doc/rtmfp-rfc7016.pdf rename to trunk/doc/rfc7016-2013-rtmfp.pdf diff --git a/trunk/doc/arpa-internet-text-messages-rfc822.txt b/trunk/doc/rfc822-1982-arpa-internet-text-messages.txt similarity index 100% rename from trunk/doc/arpa-internet-text-messages-rfc822.txt rename to trunk/doc/rfc822-1982-arpa-internet-text-messages.txt diff --git a/trunk/doc/source.flv b/trunk/doc/source.flv new file mode 120000 index 000000000..32d9ecfc2 --- /dev/null +++ b/trunk/doc/source.flv @@ -0,0 +1 @@ +source.200kbps.768x320.flv \ No newline at end of file diff --git a/trunk/etc/init.d/srs b/trunk/etc/init.d/srs index 9ef98e75f..371b450cb 100755 --- a/trunk/etc/init.d/srs +++ b/trunk/etc/init.d/srs @@ -2,8 +2,8 @@ ### BEGIN INIT INFO # Provides: ossrs(srs) -# RequiRED-Start: $all -# RequiRED-Stop: $all +# Required-Start: $all +# Required-Stop: $all # Default-Start: 2 3 4 5 # Default-Stop: 0 1 6 # Short-Description: ossrs(srs) @@ -51,10 +51,10 @@ load_process_info() { pid_file=`(cd ${ROOT}; cd $pid_dir; pwd)`/`basename $pid_file` srs_pid=`cat $pid_file 2>/dev/null` - ret=$?; if [[ 0 -ne $ret ]]; then error_msg="file $pid_file does not exists"; return 1; fi + ret=$?; if [[ 0 -ne $ret ]]; then error_msg="No pid file $pid_file"; return 1; fi ps -p ${srs_pid} >/dev/null 2>/dev/null - ret=$?; if [[ 0 -ne $ret ]]; then error_msg="process $srs_pid does not exists"; return 2; fi + ret=$?; if [[ 0 -ne $ret ]]; then error_msg="Process $srs_pid does not exists"; return 2; fi return 0; } @@ -68,7 +68,7 @@ start() { ok_msg "Starting SRS..." # get log file - log_file=`cd ${ROOT} && cat ${CONFIG} |grep '^log_file'| awk '{print $2}'| awk -F ';' '{print $1}'` + log_file=`cd ${ROOT} && cat ${CONFIG} |grep '^srs_log_file'| awk '{print $2}'| awk -F ';' '{print $1}'` if [[ -z $log_file ]]; then log_file=${DEFAULT_LOG_FILE}; fi # get abs path log_dir=`dirname $log_file` @@ -78,7 +78,7 @@ start() { if [[ -z $log_file ]]; then (ulimit -c unlimited && cd ${ROOT}; ${APP} -c ${CONFIG} >/dev/null 2>&1) else - (ulimit -c unlimited && cd ${ROOT}; ${APP} -c ${CONFIG} >> $log_file 2>&1) + (ulimit -c unlimited && cd ${ROOT}; ${APP} -c ${CONFIG} >> $log_file.sys 2>&1) fi # check again after start server @@ -142,9 +142,9 @@ stop() { # @return 0 if srs is running; otherwise, 1 for stopped. status() { load_process_info - ret=$?; if [[ 0 -eq $ret ]]; then echo "SRS(pid ${srs_pid}) is running."; return 0; fi + ret=$?; if [[ 0 -eq $ret ]]; then ok_msg "SRS(pid ${srs_pid}) is running."; return 0; fi - echo "SRS is stopped, $error_msg" + failed_msg "Error: $error_msg" return 1 } @@ -166,6 +166,30 @@ reload() { return 0 } +logrotate() { + # not start, exit + load_process_info + if [[ 0 -ne $? ]]; then failed_msg "SRS not start."; return 0; fi + + ok_msg "Reopen log file of SRS(pid ${srs_pid})..." + kill -s SIGUSR1 ${srs_pid} + + ok_msg "Log rotated" + return 0 +} + +grace() { + # not start, exit + load_process_info + if [[ 0 -ne $? ]]; then failed_msg "SRS not start."; return 0; fi + + ok_msg "Gracefully quit for SRS(pid ${srs_pid})..." + kill -s SIGQUIT ${srs_pid} + + ok_msg "Gracefully quit" + return 0 +} + menu() { case "$1" in start) @@ -184,8 +208,17 @@ menu() { reload) reload ;; + rotate) + logrotate + ;; + grace) + grace + ;; *) - echo "Usage: $0 {start|stop|status|restart|reload}" + echo "Usage: $0 {start|stop|status|restart|reload|rotate|grace}" + echo " reload Apply log file by not restarting SRS" + echo " rotate For log rotate, to send SIGUSR1 to SRS to reopen the log file." + echo " grace For gracefully quit, to send SIGQUIT to SRS." return 1 ;; esac diff --git a/trunk/etc/init.d/srs-api b/trunk/etc/init.d/srs-api index 593c8a11e..64e5413c7 100755 --- a/trunk/etc/init.d/srs-api +++ b/trunk/etc/init.d/srs-api @@ -2,8 +2,8 @@ ### BEGIN INIT INFO # Provides: ossrs-api(srs-api) -# RequiRED-Start: $all -# RequiRED-Stop: $all +# Required-Start: $all +# Required-Stop: $all # Default-Start: 2 3 4 5 # Default-Stop: 0 1 6 # Short-Description: ossrs-api(srs-api) @@ -54,7 +54,7 @@ start() { ok_msg "Starting SRS-api..." # TODO: FIXME: set limit by, for instance, "ulimit -HSn 10000" # TODO: FIXME: write log to, for instance, the same dir of log. - # TODO: FIXME: support deamon, without nohup. + # TODO: FIXME: support daemon, without nohup. (cd ${ROOT}; nohup ${APP} ${CONFIG} >/dev/null 2>&1 &) # check again after start server diff --git a/trunk/etc/init.d/srs-demo b/trunk/etc/init.d/srs-demo index 1212456f1..f714dbc1a 100755 --- a/trunk/etc/init.d/srs-demo +++ b/trunk/etc/init.d/srs-demo @@ -2,8 +2,8 @@ ### BEGIN INIT INFO # Provides: ossrs(srs) -# RequiRED-Start: $all -# RequiRED-Stop: $all +# Required-Start: $all +# Required-Stop: $all # Default-Start: 2 3 4 5 # Default-Stop: 0 1 6 # Short-Description: ossrs(srs) @@ -78,7 +78,7 @@ start() { if [[ -z $log_file ]]; then (cd ${ROOT}; ${APP} -c ${CONFIG} >/dev/null 2>&1) else - (cd ${ROOT}; ${APP} -c ${CONFIG} >> $log_file 2>&1) + (cd ${ROOT}; ${APP} -c ${CONFIG} >> $log_file.sys 2>&1) fi # check again after start server diff --git a/trunk/etc/init.d/srs-demo-19350 b/trunk/etc/init.d/srs-demo-19350 index 00e72389f..4e55540c8 100755 --- a/trunk/etc/init.d/srs-demo-19350 +++ b/trunk/etc/init.d/srs-demo-19350 @@ -2,8 +2,8 @@ ### BEGIN INIT INFO # Provides: ossrs(srs) -# RequiRED-Start: $all -# RequiRED-Stop: $all +# Required-Start: $all +# Required-Stop: $all # Default-Start: 2 3 4 5 # Default-Stop: 0 1 6 # Short-Description: ossrs(srs) @@ -78,7 +78,7 @@ start() { if [[ -z $log_file ]]; then (cd ${ROOT}; ${APP} -c ${CONFIG} >/dev/null 2>&1) else - (cd ${ROOT}; ${APP} -c ${CONFIG} >> $log_file 2>&1) + (cd ${ROOT}; ${APP} -c ${CONFIG} >> $log_file.sys 2>&1) fi # check again after start server diff --git a/trunk/ide/srs_upp/srs_upp.upp b/trunk/ide/srs_upp/srs_upp.upp index f3a43e124..7f04cce13 100755 --- a/trunk/ide/srs_upp/srs_upp.upp +++ b/trunk/ide/srs_upp/srs_upp.upp @@ -1,178 +1,188 @@ file - main readonly separator, - ../../src/main/srs_main_server.cpp, - ../../src/main/srs_main_ingest_hls.cpp, - auto readonly separator, - ../../objs/srs_auto_headers.hpp, - libs readonly separator, - ../../src/libs/srs_librtmp.hpp, - ../../src/libs/srs_librtmp.cpp, - ../../src/libs/srs_lib_bandwidth.hpp, - ../../src/libs/srs_lib_bandwidth.cpp, - ../../src/libs/srs_lib_simple_socket.hpp, - ../../src/libs/srs_lib_simple_socket.cpp, - core readonly separator, - ../../src/core/srs_core.hpp, - ../../src/core/srs_core.cpp, - ../../src/core/srs_core_autofree.hpp, - ../../src/core/srs_core_autofree.cpp, - ../../src/core/srs_core_mem_watch.hpp, - ../../src/core/srs_core_mem_watch.cpp, - ../../src/core/srs_core_performance.hpp, - ../../src/core/srs_core_performance.cpp, - kernel readonly separator, - ../../src/kernel/srs_kernel_aac.hpp, - ../../src/kernel/srs_kernel_aac.cpp, - ../../src/kernel/srs_kernel_buffer.hpp, - ../../src/kernel/srs_kernel_buffer.cpp, - ../../src/kernel/srs_kernel_codec.hpp, - ../../src/kernel/srs_kernel_codec.cpp, - ../../src/kernel/srs_kernel_consts.hpp, - ../../src/kernel/srs_kernel_consts.cpp, - ../../src/kernel/srs_kernel_error.hpp, - ../../src/kernel/srs_kernel_error.cpp, - ../../src/kernel/srs_kernel_file.hpp, - ../../src/kernel/srs_kernel_file.cpp, - ../../src/kernel/srs_kernel_flv.hpp, - ../../src/kernel/srs_kernel_flv.cpp, - ../../src/kernel/srs_kernel_log.hpp, - ../../src/kernel/srs_kernel_log.cpp, - ../../src/kernel/srs_kernel_mp3.hpp, - ../../src/kernel/srs_kernel_mp3.cpp, - ../../src/kernel/srs_rtsp_stack.hpp, - ../../src/kernel/srs_rtsp_stack.cpp, - ../../src/kernel/srs_kernel_stream.hpp, - ../../src/kernel/srs_kernel_stream.cpp, - ../../src/kernel/srs_kernel_ts.cpp, - ../../src/kernel/srs_kernel_ts.hpp, - ../../src/kernel/srs_kernel_utility.hpp, - ../../src/kernel/srs_kernel_utility.cpp, - protocol readonly separator, - ../../src/protocol/srs_http_stack.hpp, - ../../src/protocol/srs_http_stack.cpp, - ../../src/protocol/srs_protocol_kbps.hpp, - ../../src/protocol/srs_protocol_kbps.cpp, - ../../src/protocol/srs_raw_avc.hpp, - ../../src/protocol/srs_raw_avc.cpp, - ../../src/protocol/srs_rtmp_amf0.hpp, - ../../src/protocol/srs_rtmp_amf0.cpp, - ../../src/protocol/srs_protocol_buffer.hpp, - ../../src/protocol/srs_protocol_buffer.cpp, - ../../src/protocol/srs_protocol_json.hpp, - ../../src/protocol/srs_protocol_json.cpp, - ../../src/protocol/srs_rtmp_handshake.hpp, - ../../src/protocol/srs_rtmp_handshake.cpp, - ../../src/protocol/srs_rtmp_io.hpp, - ../../src/protocol/srs_rtmp_io.cpp, - ../../src/protocol/srs_rtmp_msg_array.hpp, - ../../src/protocol/srs_rtmp_msg_array.cpp, - ../../src/protocol/srs_rtmp_stack.hpp, - ../../src/protocol/srs_rtmp_stack.cpp, - ../../src/protocol/srs_rtmp_utility.hpp, - ../../src/protocol/srs_rtmp_utility.cpp, - app readonly separator, - ../../src/app/srs_app_async_call.hpp, - ../../src/app/srs_app_async_call.cpp, - ../../src/app/srs_app_bandwidth.hpp, - ../../src/app/srs_app_bandwidth.cpp, - ../../src/app/srs_app_caster_flv.hpp, - ../../src/app/srs_app_caster_flv.cpp, - ../../src/app/srs_app_conn.hpp, - ../../src/app/srs_app_conn.cpp, - ../../src/app/srs_app_config.hpp, - ../../src/app/srs_app_config.cpp, - ../../src/app/srs_app_dvr.hpp, - ../../src/app/srs_app_dvr.cpp, - ../../src/app/srs_app_edge.hpp, - ../../src/app/srs_app_edge.cpp, - ../../src/app/srs_app_empty.hpp, - ../../src/app/srs_app_empty.cpp, - ../../src/app/srs_app_encoder.hpp, - ../../src/app/srs_app_encoder.cpp, - ../../src/app/srs_app_ffmpeg.hpp, - ../../src/app/srs_app_ffmpeg.cpp, - ../../src/app/srs_app_forward.hpp, - ../../src/app/srs_app_forward.cpp, - ../../src/app/srs_app_heartbeat.hpp, - ../../src/app/srs_app_heartbeat.cpp, - ../../src/app/srs_app_hls.hpp, - ../../src/app/srs_app_hls.cpp, - ../../src/app/srs_app_http_api.hpp, - ../../src/app/srs_app_http_api.cpp, - ../../src/app/srs_app_http_client.hpp, - ../../src/app/srs_app_http_client.cpp, - ../../src/app/srs_app_http_conn.hpp, - ../../src/app/srs_app_http_conn.cpp, - ../../src/app/srs_app_http_hooks.hpp, - ../../src/app/srs_app_http_hooks.cpp, - ../../src/app/srs_app_http_stream.hpp, - ../../src/app/srs_app_http_stream.cpp, - ../../src/app/srs_app_http_static.hpp, - ../../src/app/srs_app_http_static.cpp, - ../../src/app/srs_app_ingest.hpp, - ../../src/app/srs_app_ingest.cpp, - ../../src/app/srs_app_listener.hpp, - ../../src/app/srs_app_listener.cpp, - ../../src/app/srs_app_log.hpp, - ../../src/app/srs_app_log.cpp, - ../../src/app/srs_app_mpegts_udp.hpp, - ../../src/app/srs_app_mpegts_udp.cpp, - ../../src/app/srs_app_recv_thread.hpp, - ../../src/app/srs_app_recv_thread.cpp, - ../../src/app/srs_app_refer.hpp, - ../../src/app/srs_app_refer.cpp, - ../../src/app/srs_app_reload.hpp, - ../../src/app/srs_app_reload.cpp, - ../../src/app/srs_app_rtmp_conn.hpp, - ../../src/app/srs_app_rtmp_conn.cpp, - ../../src/app/srs_app_rtsp.hpp, - ../../src/app/srs_app_rtsp.cpp, - ../../src/app/srs_app_pithy_print.hpp, - ../../src/app/srs_app_pithy_print.cpp, - ../../src/app/srs_app_security.hpp, - ../../src/app/srs_app_security.cpp, - ../../src/app/srs_app_server.hpp, - ../../src/app/srs_app_server.cpp, - ../../src/app/srs_app_st.hpp, - ../../src/app/srs_app_st.cpp, - ../../src/app/srs_app_statistic.hpp, - ../../src/app/srs_app_statistic.cpp, - ../../src/app/srs_app_source.hpp, - ../../src/app/srs_app_source.cpp, - ../../src/app/srs_app_thread.hpp, - ../../src/app/srs_app_thread.cpp, - ../../src/app/srs_app_utility.hpp, - ../../src/app/srs_app_utility.cpp, - utest readonly separator, - ../../src/utest/srs_utest.hpp, - ../../src/utest/srs_utest.cpp, - ../../src/utest/srs_utest_amf0.hpp, - ../../src/utest/srs_utest_amf0.cpp, - ../../src/utest/srs_utest_config.hpp, - ../../src/utest/srs_utest_config.cpp, - ../../src/utest/srs_utest_core.hpp, - ../../src/utest/srs_utest_core.cpp, - ../../src/utest/srs_utest_kernel.hpp, - ../../src/utest/srs_utest_kernel.cpp, - ../../src/utest/srs_utest_protocol.hpp, - ../../src/utest/srs_utest_protocol.cpp, - ../../src/utest/srs_utest_reload.hpp, - ../../src/utest/srs_utest_reload.cpp, - research readonly separator, - ../../research/librtmp/srs_aac_raw_publish.c, - ../../research/librtmp/srs_audio_raw_publish.c, - ../../research/librtmp/srs_bandwidth_check.c, - ../../research/librtmp/srs_detect_rtmp.c, - ../../research/librtmp/srs_flv_injecter.c, - ../../research/librtmp/srs_flv_parser.c, - ../../research/librtmp/srs_h264_raw_publish.c, - ../../research/librtmp/srs_ingest_flv.c, - ../../research/librtmp/srs_ingest_rtmp.c, - ../../research/librtmp/srs_play.c, - ../../research/librtmp/srs_publish.c, - ../../research/librtmp/srs_rtmp_dump.c, - ../../research/hls/ts_info.cc; + main readonly separator, + ../../src/main/srs_main_server.cpp, + ../../src/main/srs_main_ingest_hls.cpp, + auto readonly separator, + ../../objs/srs_auto_headers.hpp, + libs readonly separator, + ../../src/libs/srs_librtmp.hpp, + ../../src/libs/srs_librtmp.cpp, + ../../src/libs/srs_lib_bandwidth.hpp, + ../../src/libs/srs_lib_bandwidth.cpp, + ../../src/libs/srs_lib_simple_socket.hpp, + ../../src/libs/srs_lib_simple_socket.cpp, + core readonly separator, + ../../src/core/srs_core.hpp, + ../../src/core/srs_core.cpp, + ../../src/core/srs_core_autofree.hpp, + ../../src/core/srs_core_autofree.cpp, + ../../src/core/srs_core_mem_watch.hpp, + ../../src/core/srs_core_mem_watch.cpp, + ../../src/core/srs_core_performance.hpp, + ../../src/core/srs_core_performance.cpp, + kernel readonly separator, + ../../src/kernel/srs_kernel_aac.hpp, + ../../src/kernel/srs_kernel_aac.cpp, + ../../src/kernel/srs_kernel_balance.hpp, + ../../src/kernel/srs_kernel_balance.cpp, + ../../src/kernel/srs_kernel_stream.hpp, + ../../src/kernel/srs_kernel_stream.cpp, + ../../src/kernel/srs_kernel_codec.hpp, + ../../src/kernel/srs_kernel_codec.cpp, + ../../src/kernel/srs_kernel_consts.hpp, + ../../src/kernel/srs_kernel_consts.cpp, + ../../src/kernel/srs_kernel_error.hpp, + ../../src/kernel/srs_kernel_error.cpp, + ../../src/kernel/srs_kernel_file.hpp, + ../../src/kernel/srs_kernel_file.cpp, + ../../src/kernel/srs_kernel_flv.hpp, + ../../src/kernel/srs_kernel_flv.cpp, + ../../src/kernel/srs_kernel_log.hpp, + ../../src/kernel/srs_kernel_log.cpp, + ../../src/kernel/srs_kernel_mp3.hpp, + ../../src/kernel/srs_kernel_mp3.cpp, + ../../src/kernel/srs_rtsp_stack.hpp, + ../../src/kernel/srs_rtsp_stack.cpp, + ../../src/kernel/srs_kernel_buffer.hpp, + ../../src/kernel/srs_kernel_buffer.cpp, + ../../src/kernel/srs_kernel_ts.cpp, + ../../src/kernel/srs_kernel_ts.hpp, + ../../src/kernel/srs_kernel_utility.hpp, + ../../src/kernel/srs_kernel_utility.cpp, + protocol readonly separator, + ../../src/protocol/srs_http_stack.hpp, + ../../src/protocol/srs_http_stack.cpp, + ../../src/protocol/srs_kafka_stack.hpp, + ../../src/protocol/srs_kafka_stack.cpp, + ../../src/protocol/srs_protocol_kbps.hpp, + ../../src/protocol/srs_protocol_kbps.cpp, + ../../src/protocol/srs_raw_avc.hpp, + ../../src/protocol/srs_raw_avc.cpp, + ../../src/protocol/srs_protocol_amf0.hpp, + ../../src/protocol/srs_protocol_amf0.cpp, + ../../src/protocol/srs_protocol_stream.hpp, + ../../src/protocol/srs_protocol_stream.cpp, + ../../src/protocol/srs_protocol_json.hpp, + ../../src/protocol/srs_protocol_json.cpp, + ../../src/protocol/srs_rtmp_handshake.hpp, + ../../src/protocol/srs_rtmp_handshake.cpp, + ../../src/protocol/srs_protocol_io.hpp, + ../../src/protocol/srs_protocol_io.cpp, + ../../src/protocol/srs_rtmp_msg_array.hpp, + ../../src/protocol/srs_rtmp_msg_array.cpp, + ../../src/protocol/srs_rtmp_stack.hpp, + ../../src/protocol/srs_rtmp_stack.cpp, + ../../src/protocol/srs_protocol_utility.hpp, + ../../src/protocol/srs_protocol_utility.cpp, + app readonly separator, + ../../src/app/srs_app_async_call.hpp, + ../../src/app/srs_app_async_call.cpp, + ../../src/app/srs_app_bandwidth.hpp, + ../../src/app/srs_app_bandwidth.cpp, + ../../src/app/srs_app_caster_flv.hpp, + ../../src/app/srs_app_caster_flv.cpp, + ../../src/app/srs_app_conn.hpp, + ../../src/app/srs_app_conn.cpp, + ../../src/app/srs_app_config.hpp, + ../../src/app/srs_app_config.cpp, + ../../src/app/srs_app_dvr.hpp, + ../../src/app/srs_app_dvr.cpp, + ../../src/app/srs_app_edge.hpp, + ../../src/app/srs_app_edge.cpp, + ../../src/app/srs_app_empty.hpp, + ../../src/app/srs_app_empty.cpp, + ../../src/app/srs_app_encoder.hpp, + ../../src/app/srs_app_encoder.cpp, + ../../src/app/srs_app_ffmpeg.hpp, + ../../src/app/srs_app_ffmpeg.cpp, + ../../src/app/srs_app_forward.hpp, + ../../src/app/srs_app_forward.cpp, + ../../src/app/srs_app_heartbeat.hpp, + ../../src/app/srs_app_heartbeat.cpp, + ../../src/app/srs_app_hls.hpp, + ../../src/app/srs_app_hls.cpp, + ../../src/app/srs_app_http_api.hpp, + ../../src/app/srs_app_http_api.cpp, + ../../src/app/srs_app_http_client.hpp, + ../../src/app/srs_app_http_client.cpp, + ../../src/app/srs_app_http_conn.hpp, + ../../src/app/srs_app_http_conn.cpp, + ../../src/app/srs_app_http_hooks.hpp, + ../../src/app/srs_app_http_hooks.cpp, + ../../src/app/srs_app_http_stream.hpp, + ../../src/app/srs_app_http_stream.cpp, + ../../src/app/srs_app_http_static.hpp, + ../../src/app/srs_app_http_static.cpp, + ../../src/app/srs_app_ingest.hpp, + ../../src/app/srs_app_ingest.cpp, + ../../src/app/srs_app_kafka.hpp, + ../../src/app/srs_app_kafka.cpp, + ../../src/app/srs_app_listener.hpp, + ../../src/app/srs_app_listener.cpp, + ../../src/app/srs_app_log.hpp, + ../../src/app/srs_app_log.cpp, + ../../src/app/srs_app_mpegts_udp.hpp, + ../../src/app/srs_app_mpegts_udp.cpp, + ../../src/app/srs_app_ng_exec.hpp, + ../../src/app/srs_app_ng_exec.cpp, + ../../src/app/srs_app_process.hpp, + ../../src/app/srs_app_process.cpp, + ../../src/app/srs_app_recv_thread.hpp, + ../../src/app/srs_app_recv_thread.cpp, + ../../src/app/srs_app_refer.hpp, + ../../src/app/srs_app_refer.cpp, + ../../src/app/srs_app_reload.hpp, + ../../src/app/srs_app_reload.cpp, + ../../src/app/srs_app_rtmp_conn.hpp, + ../../src/app/srs_app_rtmp_conn.cpp, + ../../src/app/srs_app_rtsp.hpp, + ../../src/app/srs_app_rtsp.cpp, + ../../src/app/srs_app_pithy_print.hpp, + ../../src/app/srs_app_pithy_print.cpp, + ../../src/app/srs_app_security.hpp, + ../../src/app/srs_app_security.cpp, + ../../src/app/srs_app_server.hpp, + ../../src/app/srs_app_server.cpp, + ../../src/app/srs_app_st.hpp, + ../../src/app/srs_app_st.cpp, + ../../src/app/srs_app_statistic.hpp, + ../../src/app/srs_app_statistic.cpp, + ../../src/app/srs_app_source.hpp, + ../../src/app/srs_app_source.cpp, + ../../src/app/srs_app_thread.hpp, + ../../src/app/srs_app_thread.cpp, + ../../src/app/srs_app_utility.hpp, + ../../src/app/srs_app_utility.cpp, + utest readonly separator, + ../../src/utest/srs_utest.hpp, + ../../src/utest/srs_utest.cpp, + ../../src/utest/srs_utest_amf0.hpp, + ../../src/utest/srs_utest_amf0.cpp, + ../../src/utest/srs_utest_config.hpp, + ../../src/utest/srs_utest_config.cpp, + ../../src/utest/srs_utest_core.hpp, + ../../src/utest/srs_utest_core.cpp, + ../../src/utest/srs_utest_kernel.hpp, + ../../src/utest/srs_utest_kernel.cpp, + ../../src/utest/srs_utest_protocol.hpp, + ../../src/utest/srs_utest_protocol.cpp, + ../../src/utest/srs_utest_reload.hpp, + ../../src/utest/srs_utest_reload.cpp, + research readonly separator, + ../../research/librtmp/srs_aac_raw_publish.c, + ../../research/librtmp/srs_audio_raw_publish.c, + ../../research/librtmp/srs_bandwidth_check.c, + ../../research/librtmp/srs_detect_rtmp.c, + ../../research/librtmp/srs_flv_injecter.c, + ../../research/librtmp/srs_flv_parser.c, + ../../research/librtmp/srs_h264_raw_publish.c, + ../../research/librtmp/srs_ingest_flv.c, + ../../research/librtmp/srs_ingest_rtmp.c, + ../../research/librtmp/srs_play.c, + ../../research/librtmp/srs_publish.c, + ../../research/librtmp/srs_rtmp_dump.c, + ../../research/hls/ts_info.cc; mainconfig - "" = "MAIN"; + "" = "MAIN"; diff --git a/trunk/ide/srs_vs2010/srs.vcxproj b/trunk/ide/srs_vs2010/srs.vcxproj index 0959cdcc2..8edc0b8b4 100755 --- a/trunk/ide/srs_vs2010/srs.vcxproj +++ b/trunk/ide/srs_vs2010/srs.vcxproj @@ -105,7 +105,7 @@ - + @@ -113,7 +113,7 @@ - + @@ -121,13 +121,13 @@ - - + + - + - + @@ -187,7 +187,7 @@ - + @@ -195,7 +195,7 @@ - + @@ -204,13 +204,13 @@ - - + + - + - + diff --git a/trunk/ide/srs_vs2010/srs.vcxproj.filters b/trunk/ide/srs_vs2010/srs.vcxproj.filters index 02de8979f..bbf7fbc0d 100755 --- a/trunk/ide/srs_vs2010/srs.vcxproj.filters +++ b/trunk/ide/srs_vs2010/srs.vcxproj.filters @@ -157,7 +157,7 @@ srs - + srs @@ -199,10 +199,10 @@ srs - + srs - + srs @@ -211,7 +211,7 @@ srs - + srs @@ -220,7 +220,7 @@ srs - + srs @@ -372,7 +372,7 @@ srs - + srs @@ -393,10 +393,10 @@ srs - + srs - + srs @@ -405,7 +405,7 @@ srs - + srs @@ -414,7 +414,7 @@ srs - + srs diff --git a/trunk/ide/srs_xcode/srs_xcode.xcodeproj/project.pbxproj b/trunk/ide/srs_xcode/srs_xcode.xcodeproj/project.pbxproj index 4ab868aeb..4aa190f06 100644 --- a/trunk/ide/srs_xcode/srs_xcode.xcodeproj/project.pbxproj +++ b/trunk/ide/srs_xcode/srs_xcode.xcodeproj/project.pbxproj @@ -10,7 +10,7 @@ 3C036B551B2D0AC10078E2E0 /* srs_app_http_static.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 3C036B511B2D0AC10078E2E0 /* srs_app_http_static.cpp */; }; 3C036B561B2D0AC10078E2E0 /* srs_app_http_stream.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 3C036B531B2D0AC10078E2E0 /* srs_app_http_stream.cpp */; }; 3C068D6A1B10149F00AA722C /* srs_protocol_kbps.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 3C068D681B10149F00AA722C /* srs_protocol_kbps.cpp */; }; - 3C068D6D1B10175500AA722C /* srs_protocol_buffer.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 3C068D6B1B10175500AA722C /* srs_protocol_buffer.cpp */; }; + 3C068D6D1B10175500AA722C /* srs_protocol_stream.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 3C068D6B1B10175500AA722C /* srs_protocol_stream.cpp */; }; 3C0D422E1B87165900C2508B /* srs_protocol_json.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 3C0D422C1B87165900C2508B /* srs_protocol_json.cpp */; }; 3C0E1B8D1B0F5ADF003ADEF7 /* srs_http_stack.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 3C0E1B8B1B0F5ADF003ADEF7 /* srs_http_stack.cpp */; }; 3C1231F61AAE652D00CE8F6C /* srs_core_autofree.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 3C1231F01AAE652C00CE8F6C /* srs_core_autofree.cpp */; }; @@ -18,7 +18,7 @@ 3C1231F81AAE652D00CE8F6C /* srs_core.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 3C1231F41AAE652D00CE8F6C /* srs_core.cpp */; }; 3C1232061AAE812C00CE8F6C /* srs_main_server.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 3C1232051AAE812C00CE8F6C /* srs_main_server.cpp */; }; 3C1232201AAE814D00CE8F6C /* srs_kernel_aac.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 3C1232081AAE814D00CE8F6C /* srs_kernel_aac.cpp */; }; - 3C1232211AAE814D00CE8F6C /* srs_kernel_buffer.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 3C12320A1AAE814D00CE8F6C /* srs_kernel_buffer.cpp */; }; + 3C1232211AAE814D00CE8F6C /* srs_kernel_stream.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 3C12320A1AAE814D00CE8F6C /* srs_kernel_stream.cpp */; }; 3C1232221AAE814D00CE8F6C /* srs_kernel_codec.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 3C12320C1AAE814D00CE8F6C /* srs_kernel_codec.cpp */; }; 3C1232231AAE814D00CE8F6C /* srs_kernel_consts.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 3C12320E1AAE814D00CE8F6C /* srs_kernel_consts.cpp */; }; 3C1232241AAE814D00CE8F6C /* srs_kernel_error.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 3C1232101AAE814D00CE8F6C /* srs_kernel_error.cpp */; }; @@ -26,21 +26,20 @@ 3C1232261AAE814D00CE8F6C /* srs_kernel_flv.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 3C1232141AAE814D00CE8F6C /* srs_kernel_flv.cpp */; }; 3C1232271AAE814D00CE8F6C /* srs_kernel_log.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 3C1232161AAE814D00CE8F6C /* srs_kernel_log.cpp */; }; 3C1232281AAE814D00CE8F6C /* srs_kernel_mp3.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 3C1232181AAE814D00CE8F6C /* srs_kernel_mp3.cpp */; }; - 3C1232291AAE814D00CE8F6C /* srs_kernel_stream.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 3C12321A1AAE814D00CE8F6C /* srs_kernel_stream.cpp */; }; + 3C1232291AAE814D00CE8F6C /* srs_kernel_buffer.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 3C12321A1AAE814D00CE8F6C /* srs_kernel_buffer.cpp */; }; 3C12322A1AAE814D00CE8F6C /* srs_kernel_ts.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 3C12321C1AAE814D00CE8F6C /* srs_kernel_ts.cpp */; }; 3C12322B1AAE814D00CE8F6C /* srs_kernel_utility.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 3C12321E1AAE814D00CE8F6C /* srs_kernel_utility.cpp */; }; 3C1232411AAE81A400CE8F6C /* srs_raw_avc.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 3C12322D1AAE81A400CE8F6C /* srs_raw_avc.cpp */; }; - 3C1232421AAE81A400CE8F6C /* srs_rtmp_amf0.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 3C12322F1AAE81A400CE8F6C /* srs_rtmp_amf0.cpp */; }; + 3C1232421AAE81A400CE8F6C /* srs_protocol_amf0.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 3C12322F1AAE81A400CE8F6C /* srs_protocol_amf0.cpp */; }; 3C1232441AAE81A400CE8F6C /* srs_rtmp_handshake.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 3C1232331AAE81A400CE8F6C /* srs_rtmp_handshake.cpp */; }; - 3C1232451AAE81A400CE8F6C /* srs_rtmp_io.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 3C1232351AAE81A400CE8F6C /* srs_rtmp_io.cpp */; }; + 3C1232451AAE81A400CE8F6C /* srs_protocol_io.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 3C1232351AAE81A400CE8F6C /* srs_protocol_io.cpp */; }; 3C1232461AAE81A400CE8F6C /* srs_rtmp_msg_array.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 3C1232371AAE81A400CE8F6C /* srs_rtmp_msg_array.cpp */; }; 3C1232481AAE81A400CE8F6C /* srs_rtmp_stack.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 3C12323B1AAE81A400CE8F6C /* srs_rtmp_stack.cpp */; }; - 3C1232491AAE81A400CE8F6C /* srs_rtmp_utility.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 3C12323D1AAE81A400CE8F6C /* srs_rtmp_utility.cpp */; }; + 3C1232491AAE81A400CE8F6C /* srs_protocol_utility.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 3C12323D1AAE81A400CE8F6C /* srs_protocol_utility.cpp */; }; 3C12324A1AAE81A400CE8F6C /* srs_rtsp_stack.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 3C12323F1AAE81A400CE8F6C /* srs_rtsp_stack.cpp */; }; 3C1232941AAE81D900CE8F6C /* srs_app_bandwidth.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 3C12324C1AAE81D900CE8F6C /* srs_app_bandwidth.cpp */; }; 3C1232951AAE81D900CE8F6C /* srs_app_config.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 3C12324E1AAE81D900CE8F6C /* srs_app_config.cpp */; }; 3C1232961AAE81D900CE8F6C /* srs_app_conn.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 3C1232501AAE81D900CE8F6C /* srs_app_conn.cpp */; }; - 3C1232971AAE81D900CE8F6C /* srs_app_dvr.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 3C1232521AAE81D900CE8F6C /* srs_app_dvr.cpp */; }; 3C1232981AAE81D900CE8F6C /* srs_app_edge.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 3C1232541AAE81D900CE8F6C /* srs_app_edge.cpp */; }; 3C1232991AAE81D900CE8F6C /* srs_app_empty.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 3C1232561AAE81D900CE8F6C /* srs_app_empty.cpp */; }; 3C12329A1AAE81D900CE8F6C /* srs_app_encoder.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 3C1232581AAE81D900CE8F6C /* srs_app_encoder.cpp */; }; @@ -72,13 +71,19 @@ 3C1232D31AAEA56B00CE8F6C /* libst.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 3C1232D21AAEA56B00CE8F6C /* libst.a */; }; 3C1232E91AAEA5D000CE8F6C /* libcrypto.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 3C1232E71AAEA5D000CE8F6C /* libcrypto.a */; }; 3C1232EA1AAEA5D000CE8F6C /* libssl.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 3C1232E81AAEA5D000CE8F6C /* libssl.a */; }; - 3C1232ED1AAEA70F00CE8F6C /* libhttp_parser.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 3C1232EC1AAEA70F00CE8F6C /* libhttp_parser.a */; }; 3C1EE6AE1AB1055800576EE9 /* srs_app_hds.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 3C1EE6AC1AB1055800576EE9 /* srs_app_hds.cpp */; }; 3C1EE6D71AB1367D00576EE9 /* README.md in Sources */ = {isa = PBXBuildFile; fileRef = 3C1EE6D61AB1367D00576EE9 /* README.md */; }; + 3C24ECCD1C3B824800460622 /* memory.error.notcmalloc.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 3C24ECCB1C3B824800460622 /* memory.error.notcmalloc.cpp */; }; + 3C24ECCE1C3B824800460622 /* memory.error.tcmalloc.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 3C24ECCC1C3B824800460622 /* memory.error.tcmalloc.cpp */; }; + 3C26E3C61BB146FF00D0F9DB /* srs_app_kafka.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 3C26E3C41BB146FF00D0F9DB /* srs_app_kafka.cpp */; }; 3C28EDDF1AF5C43F00A3AEAC /* srs_app_caster_flv.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 3C28EDDD1AF5C43F00A3AEAC /* srs_app_caster_flv.cpp */; }; 3C36DB5B1ABD1CB90066CCAF /* srs_lib_bandwidth.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 3C36DB551ABD1CB90066CCAF /* srs_lib_bandwidth.cpp */; }; 3C36DB5C1ABD1CB90066CCAF /* srs_lib_simple_socket.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 3C36DB571ABD1CB90066CCAF /* srs_lib_simple_socket.cpp */; }; 3C36DB5D1ABD1CB90066CCAF /* srs_librtmp.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 3C36DB591ABD1CB90066CCAF /* srs_librtmp.cpp */; }; + 3C44AACF1E3AF50200D4ABC3 /* srs_kernel_mp4.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 3C44AACD1E3AF50200D4ABC3 /* srs_kernel_mp4.cpp */; }; + 3C4AB9331B8C9148006627D3 /* srs_app_ng_exec.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 3C4AB9311B8C9148006627D3 /* srs_app_ng_exec.cpp */; }; + 3C4D184C1E73F133008806F7 /* srs_app_fragment.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 3C4D184A1E73F133008806F7 /* srs_app_fragment.cpp */; }; + 3C4F97121B8B466D00FF0E46 /* srs_app_process.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 3C4F97101B8B466D00FF0E46 /* srs_app_process.cpp */; }; 3C5265B41B241BF0009CA186 /* srs_core_mem_watch.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 3C5265B21B241BF0009CA186 /* srs_core_mem_watch.cpp */; }; 3C663F0F1AB0155100286D8B /* srs_aac_raw_publish.c in Sources */ = {isa = PBXBuildFile; fileRef = 3C663F021AB0155100286D8B /* srs_aac_raw_publish.c */; }; 3C663F101AB0155100286D8B /* srs_audio_raw_publish.c in Sources */ = {isa = PBXBuildFile; fileRef = 3C663F031AB0155100286D8B /* srs_audio_raw_publish.c */; }; @@ -92,13 +97,14 @@ 3C663F181AB0155100286D8B /* srs_play.c in Sources */ = {isa = PBXBuildFile; fileRef = 3C663F0B1AB0155100286D8B /* srs_play.c */; }; 3C663F191AB0155100286D8B /* srs_publish.c in Sources */ = {isa = PBXBuildFile; fileRef = 3C663F0C1AB0155100286D8B /* srs_publish.c */; }; 3C663F1A1AB0155100286D8B /* srs_rtmp_dump.c in Sources */ = {isa = PBXBuildFile; fileRef = 3C663F0D1AB0155100286D8B /* srs_rtmp_dump.c */; }; - 3C689F961AB6AAAC00C9CEEE /* event.c in Sources */ = {isa = PBXBuildFile; fileRef = 3C689F921AB6AAAC00C9CEEE /* event.c */; }; - 3C689F971AB6AAAC00C9CEEE /* io.c in Sources */ = {isa = PBXBuildFile; fileRef = 3C689F931AB6AAAC00C9CEEE /* io.c */; }; - 3C689F981AB6AAAC00C9CEEE /* key.c in Sources */ = {isa = PBXBuildFile; fileRef = 3C689F941AB6AAAC00C9CEEE /* key.c */; }; - 3C689F9E1AB6AAC800C9CEEE /* md.S in Sources */ = {isa = PBXBuildFile; fileRef = 3C689F991AB6AAC800C9CEEE /* md.S */; }; - 3C689F9F1AB6AAC800C9CEEE /* sched.c in Sources */ = {isa = PBXBuildFile; fileRef = 3C689F9B1AB6AAC800C9CEEE /* sched.c */; }; - 3C689FA01AB6AAC800C9CEEE /* stk.c in Sources */ = {isa = PBXBuildFile; fileRef = 3C689F9C1AB6AAC800C9CEEE /* stk.c */; }; - 3C689FA11AB6AAC800C9CEEE /* sync.c in Sources */ = {isa = PBXBuildFile; fileRef = 3C689F9D1AB6AAC800C9CEEE /* sync.c */; }; + 3C6F2D751E8653BF003D0805 /* srs_main_mp4_parser.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 3C6F2D741E8653BF003D0805 /* srs_main_mp4_parser.cpp */; }; + 3C82802C1BAFF8CC004A1794 /* srs_kafka_stack.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 3C82802A1BAFF8CC004A1794 /* srs_kafka_stack.cpp */; }; + 3C8CE01E1C3F482100548CC6 /* srs_app_hourglass.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 3C8CE01D1C3F482100548CC6 /* srs_app_hourglass.cpp */; }; + 3C9F82221E4ECA8200F5B2D2 /* srs_app_dash.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 3C9F82201E4ECA8200F5B2D2 /* srs_app_dash.cpp */; }; + 3C9F82251E4F5D2A00F5B2D2 /* srs_protocol_format.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 3C9F82231E4F5D2A00F5B2D2 /* srs_protocol_format.cpp */; }; + 3CA432A81E3F46DD001DA0C6 /* srs_kernel_io.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 3CA432A61E3F46DD001DA0C6 /* srs_kernel_io.cpp */; }; + 3CA432AB1E40AEBC001DA0C6 /* Makefile in Sources */ = {isa = PBXBuildFile; fileRef = 3CA432A91E40AEBC001DA0C6 /* Makefile */; }; + 3CA432AC1E40AEBC001DA0C6 /* srs_ingest_mp4.c in Sources */ = {isa = PBXBuildFile; fileRef = 3CA432AA1E40AEBC001DA0C6 /* srs_ingest_mp4.c */; }; 3CB25C2A1BB269FD00C97A63 /* jmp_sp.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 3CB25C291BB269FD00C97A63 /* jmp_sp.cpp */; }; 3CC52DD81ACE4023006FEB01 /* srs_utest_amf0.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 3CC52DCA1ACE4023006FEB01 /* srs_utest_amf0.cpp */; }; 3CC52DD91ACE4023006FEB01 /* srs_utest_config.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 3CC52DCC1ACE4023006FEB01 /* srs_utest_config.cpp */; }; @@ -107,8 +113,26 @@ 3CC52DDC1ACE4023006FEB01 /* srs_utest_protocol.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 3CC52DD21ACE4023006FEB01 /* srs_utest_protocol.cpp */; }; 3CC52DDD1ACE4023006FEB01 /* srs_utest_reload.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 3CC52DD41ACE4023006FEB01 /* srs_utest_reload.cpp */; }; 3CC52DDE1ACE4023006FEB01 /* srs_utest.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 3CC52DD61ACE4023006FEB01 /* srs_utest.cpp */; }; + 3CD247C31BB3F14100DC1922 /* srs_kernel_balance.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 3CD247C11BB3F14000DC1922 /* srs_kernel_balance.cpp */; }; 3CD88B3F1ACA9C58000359E0 /* srs_app_async_call.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 3CD88B3D1ACA9C58000359E0 /* srs_app_async_call.cpp */; }; 3CE6CD311AE4AFB800706E07 /* srs_main_ingest_hls.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 3CE6CD301AE4AFB800706E07 /* srs_main_ingest_hls.cpp */; }; + 3CE893B51E87508D000B742D /* srs_app_dvr.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 3CE893B31E87508D000B742D /* srs_app_dvr.cpp */; }; + 3CE893B91E8750A9000B742D /* srs_service_log.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 3CE893B71E8750A9000B742D /* srs_service_log.cpp */; }; + 3CE893BC1E875108000B742D /* srs_service_st.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 3CE893BA1E875108000B742D /* srs_service_st.cpp */; }; + 3CE893BF1E876A97000B742D /* srs_service_http_client.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 3CE893BD1E876A97000B742D /* srs_service_http_client.cpp */; }; + 3CE893C21E876B9E000B742D /* srs_service_http_conn.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 3CE893C01E876B9E000B742D /* srs_service_http_conn.cpp */; }; + 3CE893C51E876C39000B742D /* srs_service_rtmp_conn.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 3CE893C31E876C39000B742D /* srs_service_rtmp_conn.cpp */; }; + 3CE893C81E876D04000B742D /* srs_service_utility.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 3CE893C61E876D04000B742D /* srs_service_utility.cpp */; }; + 3CE893CB1E8770E2000B742D /* srs_service_conn.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 3CE893C91E8770E2000B742D /* srs_service_conn.cpp */; }; + 3CECAF991EDC100F00C50501 /* event.c in Sources */ = {isa = PBXBuildFile; fileRef = 3CECAF8F1EDC100F00C50501 /* event.c */; }; + 3CECAF9A1EDC100F00C50501 /* io.c in Sources */ = {isa = PBXBuildFile; fileRef = 3CECAF901EDC100F00C50501 /* io.c */; }; + 3CECAF9B1EDC100F00C50501 /* key.c in Sources */ = {isa = PBXBuildFile; fileRef = 3CECAF911EDC100F00C50501 /* key.c */; }; + 3CECAF9C1EDC100F00C50501 /* Makefile in Sources */ = {isa = PBXBuildFile; fileRef = 3CECAF921EDC100F00C50501 /* Makefile */; }; + 3CECAF9D1EDC100F00C50501 /* md.S in Sources */ = {isa = PBXBuildFile; fileRef = 3CECAF941EDC100F00C50501 /* md.S */; }; + 3CECAF9E1EDC100F00C50501 /* sched.c in Sources */ = {isa = PBXBuildFile; fileRef = 3CECAF961EDC100F00C50501 /* sched.c */; }; + 3CECAF9F1EDC100F00C50501 /* stk.c in Sources */ = {isa = PBXBuildFile; fileRef = 3CECAF971EDC100F00C50501 /* stk.c */; }; + 3CECAFA01EDC100F00C50501 /* sync.c in Sources */ = {isa = PBXBuildFile; fileRef = 3CECAF981EDC100F00C50501 /* sync.c */; }; + 8C0652B12035B5BA000B0661 /* srs_app_coworkers.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 8C0652B02035B5B9000B0661 /* srs_app_coworkers.cpp */; }; /* End PBXBuildFile section */ /* Begin PBXCopyFilesBuildPhase section */ @@ -130,8 +154,8 @@ 3C036B541B2D0AC10078E2E0 /* srs_app_http_stream.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = srs_app_http_stream.hpp; path = ../../../src/app/srs_app_http_stream.hpp; sourceTree = ""; }; 3C068D681B10149F00AA722C /* srs_protocol_kbps.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = srs_protocol_kbps.cpp; path = ../../../src/protocol/srs_protocol_kbps.cpp; sourceTree = ""; }; 3C068D691B10149F00AA722C /* srs_protocol_kbps.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = srs_protocol_kbps.hpp; path = ../../../src/protocol/srs_protocol_kbps.hpp; sourceTree = ""; }; - 3C068D6B1B10175500AA722C /* srs_protocol_buffer.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = srs_protocol_buffer.cpp; path = ../../../src/protocol/srs_protocol_buffer.cpp; sourceTree = ""; }; - 3C068D6C1B10175500AA722C /* srs_protocol_buffer.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = srs_protocol_buffer.hpp; path = ../../../src/protocol/srs_protocol_buffer.hpp; sourceTree = ""; }; + 3C068D6B1B10175500AA722C /* srs_protocol_stream.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = srs_protocol_stream.cpp; path = ../../../src/protocol/srs_protocol_stream.cpp; sourceTree = ""; }; + 3C068D6C1B10175500AA722C /* srs_protocol_stream.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = srs_protocol_stream.hpp; path = ../../../src/protocol/srs_protocol_stream.hpp; sourceTree = ""; }; 3C0D422C1B87165900C2508B /* srs_protocol_json.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = srs_protocol_json.cpp; path = ../../../src/protocol/srs_protocol_json.cpp; sourceTree = ""; }; 3C0D422D1B87165900C2508B /* srs_protocol_json.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = srs_protocol_json.hpp; path = ../../../src/protocol/srs_protocol_json.hpp; sourceTree = ""; }; 3C0E1B8B1B0F5ADF003ADEF7 /* srs_http_stack.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = srs_http_stack.cpp; path = ../../../src/protocol/srs_http_stack.cpp; sourceTree = ""; }; @@ -147,8 +171,8 @@ 3C1232051AAE812C00CE8F6C /* srs_main_server.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = srs_main_server.cpp; path = ../../../src/main/srs_main_server.cpp; sourceTree = ""; }; 3C1232081AAE814D00CE8F6C /* srs_kernel_aac.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = srs_kernel_aac.cpp; path = ../../../src/kernel/srs_kernel_aac.cpp; sourceTree = ""; }; 3C1232091AAE814D00CE8F6C /* srs_kernel_aac.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = srs_kernel_aac.hpp; path = ../../../src/kernel/srs_kernel_aac.hpp; sourceTree = ""; }; - 3C12320A1AAE814D00CE8F6C /* srs_kernel_buffer.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = srs_kernel_buffer.cpp; path = ../../../src/kernel/srs_kernel_buffer.cpp; sourceTree = ""; }; - 3C12320B1AAE814D00CE8F6C /* srs_kernel_buffer.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = srs_kernel_buffer.hpp; path = ../../../src/kernel/srs_kernel_buffer.hpp; sourceTree = ""; }; + 3C12320A1AAE814D00CE8F6C /* srs_kernel_stream.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = srs_kernel_stream.cpp; path = ../../../src/kernel/srs_kernel_stream.cpp; sourceTree = ""; }; + 3C12320B1AAE814D00CE8F6C /* srs_kernel_stream.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = srs_kernel_stream.hpp; path = ../../../src/kernel/srs_kernel_stream.hpp; sourceTree = ""; }; 3C12320C1AAE814D00CE8F6C /* srs_kernel_codec.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = srs_kernel_codec.cpp; path = ../../../src/kernel/srs_kernel_codec.cpp; sourceTree = ""; }; 3C12320D1AAE814D00CE8F6C /* srs_kernel_codec.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = srs_kernel_codec.hpp; path = ../../../src/kernel/srs_kernel_codec.hpp; sourceTree = ""; }; 3C12320E1AAE814D00CE8F6C /* srs_kernel_consts.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = srs_kernel_consts.cpp; path = ../../../src/kernel/srs_kernel_consts.cpp; sourceTree = ""; }; @@ -163,26 +187,26 @@ 3C1232171AAE814D00CE8F6C /* srs_kernel_log.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = srs_kernel_log.hpp; path = ../../../src/kernel/srs_kernel_log.hpp; sourceTree = ""; }; 3C1232181AAE814D00CE8F6C /* srs_kernel_mp3.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = srs_kernel_mp3.cpp; path = ../../../src/kernel/srs_kernel_mp3.cpp; sourceTree = ""; }; 3C1232191AAE814D00CE8F6C /* srs_kernel_mp3.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = srs_kernel_mp3.hpp; path = ../../../src/kernel/srs_kernel_mp3.hpp; sourceTree = ""; }; - 3C12321A1AAE814D00CE8F6C /* srs_kernel_stream.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = srs_kernel_stream.cpp; path = ../../../src/kernel/srs_kernel_stream.cpp; sourceTree = ""; }; - 3C12321B1AAE814D00CE8F6C /* srs_kernel_stream.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = srs_kernel_stream.hpp; path = ../../../src/kernel/srs_kernel_stream.hpp; sourceTree = ""; }; + 3C12321A1AAE814D00CE8F6C /* srs_kernel_buffer.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = srs_kernel_buffer.cpp; path = ../../../src/kernel/srs_kernel_buffer.cpp; sourceTree = ""; }; + 3C12321B1AAE814D00CE8F6C /* srs_kernel_buffer.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = srs_kernel_buffer.hpp; path = ../../../src/kernel/srs_kernel_buffer.hpp; sourceTree = ""; }; 3C12321C1AAE814D00CE8F6C /* srs_kernel_ts.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = srs_kernel_ts.cpp; path = ../../../src/kernel/srs_kernel_ts.cpp; sourceTree = ""; }; 3C12321D1AAE814D00CE8F6C /* srs_kernel_ts.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = srs_kernel_ts.hpp; path = ../../../src/kernel/srs_kernel_ts.hpp; sourceTree = ""; }; 3C12321E1AAE814D00CE8F6C /* srs_kernel_utility.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = srs_kernel_utility.cpp; path = ../../../src/kernel/srs_kernel_utility.cpp; sourceTree = ""; }; 3C12321F1AAE814D00CE8F6C /* srs_kernel_utility.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = srs_kernel_utility.hpp; path = ../../../src/kernel/srs_kernel_utility.hpp; sourceTree = ""; }; 3C12322D1AAE81A400CE8F6C /* srs_raw_avc.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = srs_raw_avc.cpp; path = ../../../src/protocol/srs_raw_avc.cpp; sourceTree = ""; }; 3C12322E1AAE81A400CE8F6C /* srs_raw_avc.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = srs_raw_avc.hpp; path = ../../../src/protocol/srs_raw_avc.hpp; sourceTree = ""; }; - 3C12322F1AAE81A400CE8F6C /* srs_rtmp_amf0.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = srs_rtmp_amf0.cpp; path = ../../../src/protocol/srs_rtmp_amf0.cpp; sourceTree = ""; }; - 3C1232301AAE81A400CE8F6C /* srs_rtmp_amf0.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = srs_rtmp_amf0.hpp; path = ../../../src/protocol/srs_rtmp_amf0.hpp; sourceTree = ""; }; + 3C12322F1AAE81A400CE8F6C /* srs_protocol_amf0.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = srs_protocol_amf0.cpp; path = ../../../src/protocol/srs_protocol_amf0.cpp; sourceTree = ""; }; + 3C1232301AAE81A400CE8F6C /* srs_protocol_amf0.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = srs_protocol_amf0.hpp; path = ../../../src/protocol/srs_protocol_amf0.hpp; sourceTree = ""; }; 3C1232331AAE81A400CE8F6C /* srs_rtmp_handshake.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = srs_rtmp_handshake.cpp; path = ../../../src/protocol/srs_rtmp_handshake.cpp; sourceTree = ""; }; 3C1232341AAE81A400CE8F6C /* srs_rtmp_handshake.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = srs_rtmp_handshake.hpp; path = ../../../src/protocol/srs_rtmp_handshake.hpp; sourceTree = ""; }; - 3C1232351AAE81A400CE8F6C /* srs_rtmp_io.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = srs_rtmp_io.cpp; path = ../../../src/protocol/srs_rtmp_io.cpp; sourceTree = ""; }; - 3C1232361AAE81A400CE8F6C /* srs_rtmp_io.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = srs_rtmp_io.hpp; path = ../../../src/protocol/srs_rtmp_io.hpp; sourceTree = ""; }; + 3C1232351AAE81A400CE8F6C /* srs_protocol_io.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = srs_protocol_io.cpp; path = ../../../src/protocol/srs_protocol_io.cpp; sourceTree = ""; }; + 3C1232361AAE81A400CE8F6C /* srs_protocol_io.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = srs_protocol_io.hpp; path = ../../../src/protocol/srs_protocol_io.hpp; sourceTree = ""; }; 3C1232371AAE81A400CE8F6C /* srs_rtmp_msg_array.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = srs_rtmp_msg_array.cpp; path = ../../../src/protocol/srs_rtmp_msg_array.cpp; sourceTree = ""; }; 3C1232381AAE81A400CE8F6C /* srs_rtmp_msg_array.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = srs_rtmp_msg_array.hpp; path = ../../../src/protocol/srs_rtmp_msg_array.hpp; sourceTree = ""; }; 3C12323B1AAE81A400CE8F6C /* srs_rtmp_stack.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = srs_rtmp_stack.cpp; path = ../../../src/protocol/srs_rtmp_stack.cpp; sourceTree = ""; }; 3C12323C1AAE81A400CE8F6C /* srs_rtmp_stack.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = srs_rtmp_stack.hpp; path = ../../../src/protocol/srs_rtmp_stack.hpp; sourceTree = ""; }; - 3C12323D1AAE81A400CE8F6C /* srs_rtmp_utility.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = srs_rtmp_utility.cpp; path = ../../../src/protocol/srs_rtmp_utility.cpp; sourceTree = ""; }; - 3C12323E1AAE81A400CE8F6C /* srs_rtmp_utility.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = srs_rtmp_utility.hpp; path = ../../../src/protocol/srs_rtmp_utility.hpp; sourceTree = ""; }; + 3C12323D1AAE81A400CE8F6C /* srs_protocol_utility.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = srs_protocol_utility.cpp; path = ../../../src/protocol/srs_protocol_utility.cpp; sourceTree = ""; }; + 3C12323E1AAE81A400CE8F6C /* srs_protocol_utility.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = srs_protocol_utility.hpp; path = ../../../src/protocol/srs_protocol_utility.hpp; sourceTree = ""; }; 3C12323F1AAE81A400CE8F6C /* srs_rtsp_stack.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = srs_rtsp_stack.cpp; path = ../../../src/protocol/srs_rtsp_stack.cpp; sourceTree = ""; }; 3C1232401AAE81A400CE8F6C /* srs_rtsp_stack.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = srs_rtsp_stack.hpp; path = ../../../src/protocol/srs_rtsp_stack.hpp; sourceTree = ""; }; 3C12324C1AAE81D900CE8F6C /* srs_app_bandwidth.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = srs_app_bandwidth.cpp; path = ../../../src/app/srs_app_bandwidth.cpp; sourceTree = ""; }; @@ -191,8 +215,6 @@ 3C12324F1AAE81D900CE8F6C /* srs_app_config.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = srs_app_config.hpp; path = ../../../src/app/srs_app_config.hpp; sourceTree = ""; }; 3C1232501AAE81D900CE8F6C /* srs_app_conn.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = srs_app_conn.cpp; path = ../../../src/app/srs_app_conn.cpp; sourceTree = ""; }; 3C1232511AAE81D900CE8F6C /* srs_app_conn.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = srs_app_conn.hpp; path = ../../../src/app/srs_app_conn.hpp; sourceTree = ""; }; - 3C1232521AAE81D900CE8F6C /* srs_app_dvr.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = srs_app_dvr.cpp; path = ../../../src/app/srs_app_dvr.cpp; sourceTree = ""; }; - 3C1232531AAE81D900CE8F6C /* srs_app_dvr.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = srs_app_dvr.hpp; path = ../../../src/app/srs_app_dvr.hpp; sourceTree = ""; }; 3C1232541AAE81D900CE8F6C /* srs_app_edge.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = srs_app_edge.cpp; path = ../../../src/app/srs_app_edge.cpp; sourceTree = ""; }; 3C1232551AAE81D900CE8F6C /* srs_app_edge.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = srs_app_edge.hpp; path = ../../../src/app/srs_app_edge.hpp; sourceTree = ""; }; 3C1232561AAE81D900CE8F6C /* srs_app_empty.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = srs_app_empty.cpp; path = ../../../src/app/srs_app_empty.cpp; sourceTree = ""; }; @@ -274,13 +296,14 @@ 3C1232D01AAE833300CE8F6C /* stop.sh */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.script.sh; name = stop.sh; path = ../../../scripts/stop.sh; sourceTree = ""; }; 3C1232D11AAE833300CE8F6C /* test_configure.sh */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.script.sh; name = test_configure.sh; path = ../../../scripts/test_configure.sh; sourceTree = ""; }; 3C1232D21AAEA56B00CE8F6C /* libst.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libst.a; path = "../../objs/st-1.9/DARWIN_14.0.0_DBG/libst.a"; sourceTree = ""; }; - 3C1232E71AAEA5D000CE8F6C /* libcrypto.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libcrypto.a; path = "../../objs/openssl-1.0.1f/_release/lib/libcrypto.a"; sourceTree = ""; }; - 3C1232E81AAEA5D000CE8F6C /* libssl.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libssl.a; path = "../../objs/openssl-1.0.1f/_release/lib/libssl.a"; sourceTree = ""; }; + 3C1232E71AAEA5D000CE8F6C /* libcrypto.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libcrypto.a; path = "../../objs/openssl-1.1.0e/_release/lib/libcrypto.a"; sourceTree = ""; }; + 3C1232E81AAEA5D000CE8F6C /* libssl.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libssl.a; path = "../../objs/openssl-1.1.0e/_release/lib/libssl.a"; sourceTree = ""; }; 3C1232EC1AAEA70F00CE8F6C /* libhttp_parser.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libhttp_parser.a; path = "../../objs/http-parser-2.1/libhttp_parser.a"; sourceTree = ""; }; 3C1232F11AAEAC7000CE8F6C /* srs */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.script.sh; name = srs; path = ../../../etc/init.d/srs; sourceTree = ""; }; 3C1232F21AAEAC7000CE8F6C /* srs-api */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.script.sh; name = "srs-api"; path = "../../../etc/init.d/srs-api"; sourceTree = ""; }; 3C1232F31AAEAC7000CE8F6C /* srs-demo */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.script.sh; name = "srs-demo"; path = "../../../etc/init.d/srs-demo"; sourceTree = ""; }; 3C1232F41AAEAC7000CE8F6C /* srs-demo-19350 */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.script.sh; name = "srs-demo-19350"; path = "../../../etc/init.d/srs-demo-19350"; sourceTree = ""; }; + 3C1CDBFB2205CE0300A8C08E /* coverage.sh */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.script.sh; name = coverage.sh; path = ../../../auto/coverage.sh; sourceTree = ""; }; 3C1EE6AC1AB1055800576EE9 /* srs_app_hds.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = srs_app_hds.cpp; path = ../../../src/app/srs_app_hds.cpp; sourceTree = ""; }; 3C1EE6AD1AB1055800576EE9 /* srs_app_hds.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = srs_app_hds.hpp; path = ../../../src/app/srs_app_hds.hpp; sourceTree = ""; }; 3C1EE6B01AB1080900576EE9 /* bandwidth.conf */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; name = bandwidth.conf; path = ../../../conf/bandwidth.conf; sourceTree = ""; }; @@ -317,9 +340,12 @@ 3C1EE6D01AB1080900576EE9 /* srs.conf */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; name = srs.conf; path = ../../../conf/srs.conf; sourceTree = ""; }; 3C1EE6D11AB1080900576EE9 /* transcode2hls.audio.only.conf */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; name = transcode2hls.audio.only.conf; path = ../../../conf/transcode2hls.audio.only.conf; sourceTree = ""; }; 3C1EE6D31AB1367D00576EE9 /* AUTHORS.txt */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; name = AUTHORS.txt; path = ../../../AUTHORS.txt; sourceTree = ""; }; - 3C1EE6D41AB1367D00576EE9 /* DONATIONS.txt */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; name = DONATIONS.txt; path = ../../../DONATIONS.txt; sourceTree = ""; }; 3C1EE6D51AB1367D00576EE9 /* LICENSE */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; name = LICENSE; path = ../../../LICENSE; sourceTree = ""; }; 3C1EE6D61AB1367D00576EE9 /* README.md */ = {isa = PBXFileReference; explicitFileType = net.daringfireball.markdown; fileEncoding = 4; name = README.md; path = ../../../README.md; sourceTree = ""; wrapsLines = 0; }; + 3C24ECCB1C3B824800460622 /* memory.error.notcmalloc.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = memory.error.notcmalloc.cpp; path = ../../../research/gperftools/memory.error.notcmalloc.cpp; sourceTree = ""; }; + 3C24ECCC1C3B824800460622 /* memory.error.tcmalloc.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = memory.error.tcmalloc.cpp; path = ../../../research/gperftools/memory.error.tcmalloc.cpp; sourceTree = ""; }; + 3C26E3C41BB146FF00D0F9DB /* srs_app_kafka.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = srs_app_kafka.cpp; path = ../../../src/app/srs_app_kafka.cpp; sourceTree = ""; }; + 3C26E3C51BB146FF00D0F9DB /* srs_app_kafka.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = srs_app_kafka.hpp; path = ../../../src/app/srs_app_kafka.hpp; sourceTree = ""; }; 3C28EDDD1AF5C43F00A3AEAC /* srs_app_caster_flv.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = srs_app_caster_flv.cpp; path = ../../../src/app/srs_app_caster_flv.cpp; sourceTree = ""; }; 3C28EDDE1AF5C43F00A3AEAC /* srs_app_caster_flv.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = srs_app_caster_flv.hpp; path = ../../../src/app/srs_app_caster_flv.hpp; sourceTree = ""; }; 3C36DB551ABD1CB90066CCAF /* srs_lib_bandwidth.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = srs_lib_bandwidth.cpp; path = ../../../src/libs/srs_lib_bandwidth.cpp; sourceTree = ""; }; @@ -328,6 +354,16 @@ 3C36DB581ABD1CB90066CCAF /* srs_lib_simple_socket.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = srs_lib_simple_socket.hpp; path = ../../../src/libs/srs_lib_simple_socket.hpp; sourceTree = ""; }; 3C36DB591ABD1CB90066CCAF /* srs_librtmp.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = srs_librtmp.cpp; path = ../../../src/libs/srs_librtmp.cpp; sourceTree = ""; }; 3C36DB5A1ABD1CB90066CCAF /* srs_librtmp.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = srs_librtmp.hpp; path = ../../../src/libs/srs_librtmp.hpp; sourceTree = ""; }; + 3C4468E81BB0E31300589C9D /* sources_replace.sh */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.script.sh; name = sources_replace.sh; path = ../../../scripts/sources_replace.sh; sourceTree = ""; }; + 3C44AACD1E3AF50200D4ABC3 /* srs_kernel_mp4.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = srs_kernel_mp4.cpp; path = ../../../src/kernel/srs_kernel_mp4.cpp; sourceTree = ""; }; + 3C44AACE1E3AF50200D4ABC3 /* srs_kernel_mp4.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = srs_kernel_mp4.hpp; path = ../../../src/kernel/srs_kernel_mp4.hpp; sourceTree = ""; }; + 3C4AB9311B8C9148006627D3 /* srs_app_ng_exec.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = srs_app_ng_exec.cpp; path = ../../../src/app/srs_app_ng_exec.cpp; sourceTree = ""; }; + 3C4AB9321B8C9148006627D3 /* srs_app_ng_exec.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = srs_app_ng_exec.hpp; path = ../../../src/app/srs_app_ng_exec.hpp; sourceTree = ""; }; + 3C4AB9341B8C9FF9006627D3 /* exec.conf */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; name = exec.conf; path = ../../../conf/exec.conf; sourceTree = ""; }; + 3C4D184A1E73F133008806F7 /* srs_app_fragment.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = srs_app_fragment.cpp; path = ../../../src/app/srs_app_fragment.cpp; sourceTree = ""; }; + 3C4D184B1E73F133008806F7 /* srs_app_fragment.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = srs_app_fragment.hpp; path = ../../../src/app/srs_app_fragment.hpp; sourceTree = ""; }; + 3C4F97101B8B466D00FF0E46 /* srs_app_process.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = srs_app_process.cpp; path = ../../../src/app/srs_app_process.cpp; sourceTree = ""; }; + 3C4F97111B8B466D00FF0E46 /* srs_app_process.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = srs_app_process.hpp; path = ../../../src/app/srs_app_process.hpp; sourceTree = ""; }; 3C5265B21B241BF0009CA186 /* srs_core_mem_watch.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = srs_core_mem_watch.cpp; path = ../../../src/core/srs_core_mem_watch.cpp; sourceTree = ""; }; 3C5265B31B241BF0009CA186 /* srs_core_mem_watch.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = srs_core_mem_watch.hpp; path = ../../../src/core/srs_core_mem_watch.hpp; sourceTree = ""; }; 3C663F021AB0155100286D8B /* srs_aac_raw_publish.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = srs_aac_raw_publish.c; path = ../../../research/librtmp/srs_aac_raw_publish.c; sourceTree = ""; }; @@ -342,16 +378,27 @@ 3C663F0B1AB0155100286D8B /* srs_play.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = srs_play.c; path = ../../../research/librtmp/srs_play.c; sourceTree = ""; }; 3C663F0C1AB0155100286D8B /* srs_publish.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = srs_publish.c; path = ../../../research/librtmp/srs_publish.c; sourceTree = ""; }; 3C663F0D1AB0155100286D8B /* srs_rtmp_dump.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = srs_rtmp_dump.c; path = ../../../research/librtmp/srs_rtmp_dump.c; sourceTree = ""; }; - 3C689F911AB6AAAC00C9CEEE /* common.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = common.h; path = "../../objs/st-1.9/common.h"; sourceTree = ""; }; - 3C689F921AB6AAAC00C9CEEE /* event.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = event.c; path = "../../objs/st-1.9/event.c"; sourceTree = ""; }; - 3C689F931AB6AAAC00C9CEEE /* io.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = io.c; path = "../../objs/st-1.9/io.c"; sourceTree = ""; }; - 3C689F941AB6AAAC00C9CEEE /* key.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = key.c; path = "../../objs/st-1.9/key.c"; sourceTree = ""; }; - 3C689F951AB6AAAC00C9CEEE /* md.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = md.h; path = "../../objs/st-1.9/md.h"; sourceTree = ""; }; - 3C689F991AB6AAC800C9CEEE /* md.S */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.asm; name = md.S; path = "../../objs/st-1.9/md.S"; sourceTree = ""; }; - 3C689F9A1AB6AAC800C9CEEE /* public.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = public.h; path = "../../objs/st-1.9/public.h"; sourceTree = ""; }; - 3C689F9B1AB6AAC800C9CEEE /* sched.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = sched.c; path = "../../objs/st-1.9/sched.c"; sourceTree = ""; }; - 3C689F9C1AB6AAC800C9CEEE /* stk.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = stk.c; path = "../../objs/st-1.9/stk.c"; sourceTree = ""; }; - 3C689F9D1AB6AAC800C9CEEE /* sync.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = sync.c; path = "../../objs/st-1.9/sync.c"; sourceTree = ""; }; + 3C6673CF1DF7B93200A6DF57 /* readme.txt */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; name = readme.txt; path = ../../../modules/readme.txt; sourceTree = ""; }; + 3C6673D11DF7B95E00A6DF57 /* config */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; name = config; path = "../../../modules/hls-ingester/config"; sourceTree = ""; }; + 3C6F2D731E86536B003D0805 /* config */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; name = config; path = "../../../modules/mp4-parser/config"; sourceTree = ""; }; + 3C6F2D741E8653BF003D0805 /* srs_main_mp4_parser.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = srs_main_mp4_parser.cpp; path = ../../../src/main/srs_main_mp4_parser.cpp; sourceTree = ""; }; + 3C8280241BAFF896004A1794 /* compatible.conf */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; name = compatible.conf; path = ../../../conf/compatible.conf; sourceTree = ""; }; + 3C8280261BAFF896004A1794 /* http.flv.live.edge1.conf */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; name = http.flv.live.edge1.conf; path = ../../../conf/http.flv.live.edge1.conf; sourceTree = ""; }; + 3C8280271BAFF896004A1794 /* http.flv.live.edge2.conf */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; name = http.flv.live.edge2.conf; path = ../../../conf/http.flv.live.edge2.conf; sourceTree = ""; }; + 3C8280281BAFF896004A1794 /* push.flv.conf */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; name = push.flv.conf; path = ../../../conf/push.flv.conf; sourceTree = ""; }; + 3C8280291BAFF896004A1794 /* transform.edge.conf */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; name = transform.edge.conf; path = ../../../conf/transform.edge.conf; sourceTree = ""; }; + 3C82802A1BAFF8CC004A1794 /* srs_kafka_stack.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = srs_kafka_stack.cpp; path = ../../../src/protocol/srs_kafka_stack.cpp; sourceTree = ""; }; + 3C82802B1BAFF8CC004A1794 /* srs_kafka_stack.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = srs_kafka_stack.hpp; path = ../../../src/protocol/srs_kafka_stack.hpp; sourceTree = ""; }; + 3C8CE01C1C3F482100548CC6 /* srs_app_hourglass.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = srs_app_hourglass.hpp; path = ../../../src/app/srs_app_hourglass.hpp; sourceTree = ""; }; + 3C8CE01D1C3F482100548CC6 /* srs_app_hourglass.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = srs_app_hourglass.cpp; path = ../../../src/app/srs_app_hourglass.cpp; sourceTree = ""; }; + 3C9F82201E4ECA8200F5B2D2 /* srs_app_dash.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = srs_app_dash.cpp; path = ../../../src/app/srs_app_dash.cpp; sourceTree = ""; }; + 3C9F82211E4ECA8200F5B2D2 /* srs_app_dash.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = srs_app_dash.hpp; path = ../../../src/app/srs_app_dash.hpp; sourceTree = ""; }; + 3C9F82231E4F5D2A00F5B2D2 /* srs_protocol_format.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = srs_protocol_format.cpp; path = ../../../src/protocol/srs_protocol_format.cpp; sourceTree = ""; }; + 3C9F82241E4F5D2A00F5B2D2 /* srs_protocol_format.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = srs_protocol_format.hpp; path = ../../../src/protocol/srs_protocol_format.hpp; sourceTree = ""; }; + 3CA432A61E3F46DD001DA0C6 /* srs_kernel_io.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = srs_kernel_io.cpp; path = ../../../src/kernel/srs_kernel_io.cpp; sourceTree = ""; }; + 3CA432A71E3F46DD001DA0C6 /* srs_kernel_io.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = srs_kernel_io.hpp; path = ../../../src/kernel/srs_kernel_io.hpp; sourceTree = ""; }; + 3CA432A91E40AEBC001DA0C6 /* Makefile */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.make; name = Makefile; path = ../../../research/librtmp/Makefile; sourceTree = ""; }; + 3CA432AA1E40AEBC001DA0C6 /* srs_ingest_mp4.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = srs_ingest_mp4.c; path = ../../../research/librtmp/srs_ingest_mp4.c; sourceTree = ""; }; 3CB25C281BB2596300C97A63 /* setup_variables.sh */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.script.sh; name = setup_variables.sh; path = ../../../auto/setup_variables.sh; sourceTree = ""; }; 3CB25C291BB269FD00C97A63 /* jmp_sp.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = jmp_sp.cpp; path = ../../../research/arm/jmp_sp.cpp; sourceTree = ""; }; 3CC52DCA1ACE4023006FEB01 /* srs_utest_amf0.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = srs_utest_amf0.cpp; path = ../../src/utest/srs_utest_amf0.cpp; sourceTree = ""; }; @@ -368,9 +415,40 @@ 3CC52DD51ACE4023006FEB01 /* srs_utest_reload.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = srs_utest_reload.hpp; path = ../../src/utest/srs_utest_reload.hpp; sourceTree = ""; }; 3CC52DD61ACE4023006FEB01 /* srs_utest.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = srs_utest.cpp; path = ../../src/utest/srs_utest.cpp; sourceTree = ""; }; 3CC52DD71ACE4023006FEB01 /* srs_utest.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = srs_utest.hpp; path = ../../src/utest/srs_utest.hpp; sourceTree = ""; }; + 3CD247C11BB3F14000DC1922 /* srs_kernel_balance.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = srs_kernel_balance.cpp; path = ../../../src/kernel/srs_kernel_balance.cpp; sourceTree = ""; }; + 3CD247C21BB3F14000DC1922 /* srs_kernel_balance.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = srs_kernel_balance.hpp; path = ../../../src/kernel/srs_kernel_balance.hpp; sourceTree = ""; }; 3CD88B3D1ACA9C58000359E0 /* srs_app_async_call.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = srs_app_async_call.cpp; path = ../../../src/app/srs_app_async_call.cpp; sourceTree = ""; }; 3CD88B3E1ACA9C58000359E0 /* srs_app_async_call.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = srs_app_async_call.hpp; path = ../../../src/app/srs_app_async_call.hpp; sourceTree = ""; }; 3CE6CD301AE4AFB800706E07 /* srs_main_ingest_hls.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = srs_main_ingest_hls.cpp; path = ../../../src/main/srs_main_ingest_hls.cpp; sourceTree = ""; }; + 3CE893B31E87508D000B742D /* srs_app_dvr.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = srs_app_dvr.cpp; path = ../../../src/app/srs_app_dvr.cpp; sourceTree = ""; }; + 3CE893B41E87508D000B742D /* srs_app_dvr.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = srs_app_dvr.hpp; path = ../../../src/app/srs_app_dvr.hpp; sourceTree = ""; }; + 3CE893B71E8750A9000B742D /* srs_service_log.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = srs_service_log.cpp; path = ../../../src/service/srs_service_log.cpp; sourceTree = ""; }; + 3CE893B81E8750A9000B742D /* srs_service_log.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = srs_service_log.hpp; path = ../../../src/service/srs_service_log.hpp; sourceTree = ""; }; + 3CE893BA1E875108000B742D /* srs_service_st.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = srs_service_st.cpp; path = ../../../src/service/srs_service_st.cpp; sourceTree = ""; }; + 3CE893BB1E875108000B742D /* srs_service_st.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = srs_service_st.hpp; path = ../../../src/service/srs_service_st.hpp; sourceTree = ""; }; + 3CE893BD1E876A97000B742D /* srs_service_http_client.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = srs_service_http_client.cpp; path = ../../../src/service/srs_service_http_client.cpp; sourceTree = ""; }; + 3CE893BE1E876A97000B742D /* srs_service_http_client.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = srs_service_http_client.hpp; path = ../../../src/service/srs_service_http_client.hpp; sourceTree = ""; }; + 3CE893C01E876B9E000B742D /* srs_service_http_conn.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = srs_service_http_conn.cpp; path = ../../../src/service/srs_service_http_conn.cpp; sourceTree = ""; }; + 3CE893C11E876B9E000B742D /* srs_service_http_conn.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = srs_service_http_conn.hpp; path = ../../../src/service/srs_service_http_conn.hpp; sourceTree = ""; }; + 3CE893C31E876C39000B742D /* srs_service_rtmp_conn.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = srs_service_rtmp_conn.cpp; path = ../../../src/service/srs_service_rtmp_conn.cpp; sourceTree = ""; }; + 3CE893C41E876C39000B742D /* srs_service_rtmp_conn.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = srs_service_rtmp_conn.hpp; path = ../../../src/service/srs_service_rtmp_conn.hpp; sourceTree = ""; }; + 3CE893C61E876D04000B742D /* srs_service_utility.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = srs_service_utility.cpp; path = ../../../src/service/srs_service_utility.cpp; sourceTree = ""; }; + 3CE893C71E876D04000B742D /* srs_service_utility.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = srs_service_utility.hpp; path = ../../../src/service/srs_service_utility.hpp; sourceTree = ""; }; + 3CE893C91E8770E2000B742D /* srs_service_conn.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = srs_service_conn.cpp; path = ../../../src/service/srs_service_conn.cpp; sourceTree = ""; }; + 3CE893CA1E8770E2000B742D /* srs_service_conn.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = srs_service_conn.hpp; path = ../../../src/service/srs_service_conn.hpp; sourceTree = ""; }; + 3CECAF8E1EDC100F00C50501 /* common.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = common.h; path = "../../../objs/state-threads-1.9.1/common.h"; sourceTree = ""; }; + 3CECAF8F1EDC100F00C50501 /* event.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = event.c; path = "../../../objs/state-threads-1.9.1/event.c"; sourceTree = ""; }; + 3CECAF901EDC100F00C50501 /* io.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = io.c; path = "../../../objs/state-threads-1.9.1/io.c"; sourceTree = ""; }; + 3CECAF911EDC100F00C50501 /* key.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = key.c; path = "../../../objs/state-threads-1.9.1/key.c"; sourceTree = ""; }; + 3CECAF921EDC100F00C50501 /* Makefile */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.make; name = Makefile; path = "../../../objs/state-threads-1.9.1/Makefile"; sourceTree = ""; }; + 3CECAF931EDC100F00C50501 /* md.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = md.h; path = "../../../objs/state-threads-1.9.1/md.h"; sourceTree = ""; }; + 3CECAF941EDC100F00C50501 /* md.S */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.asm; name = md.S; path = "../../../objs/state-threads-1.9.1/md.S"; sourceTree = ""; }; + 3CECAF951EDC100F00C50501 /* public.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = public.h; path = "../../../objs/state-threads-1.9.1/public.h"; sourceTree = ""; }; + 3CECAF961EDC100F00C50501 /* sched.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = sched.c; path = "../../../objs/state-threads-1.9.1/sched.c"; sourceTree = ""; }; + 3CECAF971EDC100F00C50501 /* stk.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = stk.c; path = "../../../objs/state-threads-1.9.1/stk.c"; sourceTree = ""; }; + 3CECAF981EDC100F00C50501 /* sync.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = sync.c; path = "../../../objs/state-threads-1.9.1/sync.c"; sourceTree = ""; }; + 8C0652AF2035B5B9000B0661 /* srs_app_coworkers.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = srs_app_coworkers.hpp; path = ../../../src/app/srs_app_coworkers.hpp; sourceTree = ""; }; + 8C0652B02035B5B9000B0661 /* srs_app_coworkers.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = srs_app_coworkers.cpp; path = ../../../src/app/srs_app_coworkers.cpp; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -378,7 +456,6 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - 3C1232ED1AAEA70F00CE8F6C /* libhttp_parser.a in Frameworks */, 3C1232E91AAEA5D000CE8F6C /* libcrypto.a in Frameworks */, 3C1232EA1AAEA5D000CE8F6C /* libssl.a in Frameworks */, 3C1232D31AAEA56B00CE8F6C /* libst.a in Frameworks */, @@ -391,7 +468,6 @@ 3C1231DC1AAE64A400CE8F6C = { isa = PBXGroup; children = ( - 3C689F901AB6AA9100C9CEEE /* st-1.9 */, 3C1EE6D21AB1366500576EE9 /* doc */, 3C1231E61AAE64A400CE8F6C /* Products */, 3C1232EE1AAEA71C00CE8F6C /* links */, @@ -413,17 +489,20 @@ isa = PBXGroup; children = ( 3C1232B81AAE824500CE8F6C /* configure */, + 3C1232BA1AAE826F00CE8F6C /* auto */, 3C1231EF1AAE651100CE8F6C /* core */, 3C1232071AAE814200CE8F6C /* kernel */, 3C12322C1AAE819900CE8F6C /* protocol */, + 3CE893B61E875095000B742D /* service */, 3C12324B1AAE81CE00CE8F6C /* app */, + 3C96ADC41B00A71000885304 /* modules */, 3C1232041AAE80CB00CE8F6C /* main */, - 3C1231F91AAE670E00CE8F6C /* objs */, - 3C1232BA1AAE826F00CE8F6C /* auto */, - 3C1232B91AAE825100CE8F6C /* scripts */, - 3C1EE6AF1AB107EE00576EE9 /* conf */, 3C36DB541ABD1CA70066CCAF /* libs */, + 3C1231F91AAE670E00CE8F6C /* objs */, + 3C1EE6AF1AB107EE00576EE9 /* conf */, 3C1232EF1AAEAC5800CE8F6C /* etc */, + 3C1232B91AAE825100CE8F6C /* scripts */, + 3C7175A61E1DEA0500E8C49F /* st */, ); path = srs_xcode; sourceTree = ""; @@ -455,6 +534,7 @@ isa = PBXGroup; children = ( 3CE6CD301AE4AFB800706E07 /* srs_main_ingest_hls.cpp */, + 3C6F2D741E8653BF003D0805 /* srs_main_mp4_parser.cpp */, 3C1232051AAE812C00CE8F6C /* srs_main_server.cpp */, ); name = main; @@ -465,8 +545,10 @@ children = ( 3C1232081AAE814D00CE8F6C /* srs_kernel_aac.cpp */, 3C1232091AAE814D00CE8F6C /* srs_kernel_aac.hpp */, - 3C12320A1AAE814D00CE8F6C /* srs_kernel_buffer.cpp */, - 3C12320B1AAE814D00CE8F6C /* srs_kernel_buffer.hpp */, + 3CD247C11BB3F14000DC1922 /* srs_kernel_balance.cpp */, + 3CD247C21BB3F14000DC1922 /* srs_kernel_balance.hpp */, + 3C12321A1AAE814D00CE8F6C /* srs_kernel_buffer.cpp */, + 3C12321B1AAE814D00CE8F6C /* srs_kernel_buffer.hpp */, 3C12320C1AAE814D00CE8F6C /* srs_kernel_codec.cpp */, 3C12320D1AAE814D00CE8F6C /* srs_kernel_codec.hpp */, 3C12320E1AAE814D00CE8F6C /* srs_kernel_consts.cpp */, @@ -477,12 +559,16 @@ 3C1232131AAE814D00CE8F6C /* srs_kernel_file.hpp */, 3C1232141AAE814D00CE8F6C /* srs_kernel_flv.cpp */, 3C1232151AAE814D00CE8F6C /* srs_kernel_flv.hpp */, + 3CA432A61E3F46DD001DA0C6 /* srs_kernel_io.cpp */, + 3CA432A71E3F46DD001DA0C6 /* srs_kernel_io.hpp */, 3C1232161AAE814D00CE8F6C /* srs_kernel_log.cpp */, 3C1232171AAE814D00CE8F6C /* srs_kernel_log.hpp */, 3C1232181AAE814D00CE8F6C /* srs_kernel_mp3.cpp */, 3C1232191AAE814D00CE8F6C /* srs_kernel_mp3.hpp */, - 3C12321A1AAE814D00CE8F6C /* srs_kernel_stream.cpp */, - 3C12321B1AAE814D00CE8F6C /* srs_kernel_stream.hpp */, + 3C44AACD1E3AF50200D4ABC3 /* srs_kernel_mp4.cpp */, + 3C44AACE1E3AF50200D4ABC3 /* srs_kernel_mp4.hpp */, + 3C12320A1AAE814D00CE8F6C /* srs_kernel_stream.cpp */, + 3C12320B1AAE814D00CE8F6C /* srs_kernel_stream.hpp */, 3C12321C1AAE814D00CE8F6C /* srs_kernel_ts.cpp */, 3C12321D1AAE814D00CE8F6C /* srs_kernel_ts.hpp */, 3C12321E1AAE814D00CE8F6C /* srs_kernel_utility.cpp */, @@ -496,26 +582,30 @@ children = ( 3C0E1B8B1B0F5ADF003ADEF7 /* srs_http_stack.cpp */, 3C0E1B8C1B0F5ADF003ADEF7 /* srs_http_stack.hpp */, - 3C068D6B1B10175500AA722C /* srs_protocol_buffer.cpp */, - 3C068D6C1B10175500AA722C /* srs_protocol_buffer.hpp */, + 3C82802A1BAFF8CC004A1794 /* srs_kafka_stack.cpp */, + 3C82802B1BAFF8CC004A1794 /* srs_kafka_stack.hpp */, + 3C12322F1AAE81A400CE8F6C /* srs_protocol_amf0.cpp */, + 3C1232301AAE81A400CE8F6C /* srs_protocol_amf0.hpp */, + 3C9F82231E4F5D2A00F5B2D2 /* srs_protocol_format.cpp */, + 3C9F82241E4F5D2A00F5B2D2 /* srs_protocol_format.hpp */, + 3C1232351AAE81A400CE8F6C /* srs_protocol_io.cpp */, + 3C1232361AAE81A400CE8F6C /* srs_protocol_io.hpp */, 3C0D422C1B87165900C2508B /* srs_protocol_json.cpp */, 3C0D422D1B87165900C2508B /* srs_protocol_json.hpp */, 3C068D681B10149F00AA722C /* srs_protocol_kbps.cpp */, 3C068D691B10149F00AA722C /* srs_protocol_kbps.hpp */, + 3C068D6B1B10175500AA722C /* srs_protocol_stream.cpp */, + 3C068D6C1B10175500AA722C /* srs_protocol_stream.hpp */, + 3C12323D1AAE81A400CE8F6C /* srs_protocol_utility.cpp */, + 3C12323E1AAE81A400CE8F6C /* srs_protocol_utility.hpp */, 3C12322D1AAE81A400CE8F6C /* srs_raw_avc.cpp */, 3C12322E1AAE81A400CE8F6C /* srs_raw_avc.hpp */, - 3C12322F1AAE81A400CE8F6C /* srs_rtmp_amf0.cpp */, - 3C1232301AAE81A400CE8F6C /* srs_rtmp_amf0.hpp */, 3C1232331AAE81A400CE8F6C /* srs_rtmp_handshake.cpp */, 3C1232341AAE81A400CE8F6C /* srs_rtmp_handshake.hpp */, - 3C1232351AAE81A400CE8F6C /* srs_rtmp_io.cpp */, - 3C1232361AAE81A400CE8F6C /* srs_rtmp_io.hpp */, 3C1232371AAE81A400CE8F6C /* srs_rtmp_msg_array.cpp */, 3C1232381AAE81A400CE8F6C /* srs_rtmp_msg_array.hpp */, 3C12323B1AAE81A400CE8F6C /* srs_rtmp_stack.cpp */, 3C12323C1AAE81A400CE8F6C /* srs_rtmp_stack.hpp */, - 3C12323D1AAE81A400CE8F6C /* srs_rtmp_utility.cpp */, - 3C12323E1AAE81A400CE8F6C /* srs_rtmp_utility.hpp */, 3C12323F1AAE81A400CE8F6C /* srs_rtsp_stack.cpp */, 3C1232401AAE81A400CE8F6C /* srs_rtsp_stack.hpp */, ); @@ -535,8 +625,12 @@ 3C12324F1AAE81D900CE8F6C /* srs_app_config.hpp */, 3C1232501AAE81D900CE8F6C /* srs_app_conn.cpp */, 3C1232511AAE81D900CE8F6C /* srs_app_conn.hpp */, - 3C1232521AAE81D900CE8F6C /* srs_app_dvr.cpp */, - 3C1232531AAE81D900CE8F6C /* srs_app_dvr.hpp */, + 8C0652B02035B5B9000B0661 /* srs_app_coworkers.cpp */, + 8C0652AF2035B5B9000B0661 /* srs_app_coworkers.hpp */, + 3C9F82201E4ECA8200F5B2D2 /* srs_app_dash.cpp */, + 3C9F82211E4ECA8200F5B2D2 /* srs_app_dash.hpp */, + 3CE893B31E87508D000B742D /* srs_app_dvr.cpp */, + 3CE893B41E87508D000B742D /* srs_app_dvr.hpp */, 3C1232541AAE81D900CE8F6C /* srs_app_edge.cpp */, 3C1232551AAE81D900CE8F6C /* srs_app_edge.hpp */, 3C1232561AAE81D900CE8F6C /* srs_app_empty.cpp */, @@ -547,12 +641,16 @@ 3C12325B1AAE81D900CE8F6C /* srs_app_ffmpeg.hpp */, 3C12325C1AAE81D900CE8F6C /* srs_app_forward.cpp */, 3C12325D1AAE81D900CE8F6C /* srs_app_forward.hpp */, + 3C4D184A1E73F133008806F7 /* srs_app_fragment.cpp */, + 3C4D184B1E73F133008806F7 /* srs_app_fragment.hpp */, 3C1EE6AC1AB1055800576EE9 /* srs_app_hds.cpp */, 3C1EE6AD1AB1055800576EE9 /* srs_app_hds.hpp */, 3C12325E1AAE81D900CE8F6C /* srs_app_heartbeat.cpp */, 3C12325F1AAE81D900CE8F6C /* srs_app_heartbeat.hpp */, 3C1232601AAE81D900CE8F6C /* srs_app_hls.cpp */, 3C1232611AAE81D900CE8F6C /* srs_app_hls.hpp */, + 3C8CE01D1C3F482100548CC6 /* srs_app_hourglass.cpp */, + 3C8CE01C1C3F482100548CC6 /* srs_app_hourglass.hpp */, 3C1232621AAE81D900CE8F6C /* srs_app_http_api.cpp */, 3C1232631AAE81D900CE8F6C /* srs_app_http_api.hpp */, 3C1232641AAE81D900CE8F6C /* srs_app_http_client.cpp */, @@ -567,14 +665,20 @@ 3C036B541B2D0AC10078E2E0 /* srs_app_http_stream.hpp */, 3C12326C1AAE81D900CE8F6C /* srs_app_ingest.cpp */, 3C12326D1AAE81D900CE8F6C /* srs_app_ingest.hpp */, + 3C26E3C41BB146FF00D0F9DB /* srs_app_kafka.cpp */, + 3C26E3C51BB146FF00D0F9DB /* srs_app_kafka.hpp */, 3C1232721AAE81D900CE8F6C /* srs_app_listener.cpp */, 3C1232731AAE81D900CE8F6C /* srs_app_listener.hpp */, 3C1232741AAE81D900CE8F6C /* srs_app_log.cpp */, 3C1232751AAE81D900CE8F6C /* srs_app_log.hpp */, 3C1232761AAE81D900CE8F6C /* srs_app_mpegts_udp.cpp */, 3C1232771AAE81D900CE8F6C /* srs_app_mpegts_udp.hpp */, + 3C4AB9311B8C9148006627D3 /* srs_app_ng_exec.cpp */, + 3C4AB9321B8C9148006627D3 /* srs_app_ng_exec.hpp */, 3C1232781AAE81D900CE8F6C /* srs_app_pithy_print.cpp */, 3C1232791AAE81D900CE8F6C /* srs_app_pithy_print.hpp */, + 3C4F97101B8B466D00FF0E46 /* srs_app_process.cpp */, + 3C4F97111B8B466D00FF0E46 /* srs_app_process.hpp */, 3C12327A1AAE81D900CE8F6C /* srs_app_recv_thread.cpp */, 3C12327B1AAE81D900CE8F6C /* srs_app_recv_thread.hpp */, 3C12327C1AAE81D900CE8F6C /* srs_app_refer.cpp */, @@ -613,6 +717,7 @@ 3C1232CC1AAE833300CE8F6C /* install.sh */, 3C1232CD1AAE833300CE8F6C /* package.sh */, 3C1232CE1AAE833300CE8F6C /* run.sh */, + 3C4468E81BB0E31300589C9D /* sources_replace.sh */, 3C1232CF1AAE833300CE8F6C /* srs.test */, 3C1232D01AAE833300CE8F6C /* stop.sh */, 3C1232D11AAE833300CE8F6C /* test_configure.sh */, @@ -626,6 +731,7 @@ 3C1232BB1AAE827E00CE8F6C /* apps.sh */, 3C1232BC1AAE827E00CE8F6C /* auto_headers.sh */, 3C1232BD1AAE827E00CE8F6C /* build_ffmpeg.sh */, + 3C1CDBFB2205CE0300A8C08E /* coverage.sh */, 3C1232BE1AAE827E00CE8F6C /* depends.sh */, 3C1232BF1AAE827E00CE8F6C /* generate_header.sh */, 3C1232C01AAE827E00CE8F6C /* generate-srs-librtmp-project.sh */, @@ -675,6 +781,7 @@ isa = PBXGroup; children = ( 3C1EE6B01AB1080900576EE9 /* bandwidth.conf */, + 3C8280241BAFF896004A1794 /* compatible.conf */, 3C1EE6B11AB1080900576EE9 /* console.conf */, 3C1EE6B21AB1080900576EE9 /* demo.19350.conf */, 3C1EE6B31AB1080900576EE9 /* demo.conf */, @@ -683,6 +790,7 @@ 3C1EE6B61AB1080900576EE9 /* dvr.session.conf */, 3C1EE6B71AB1080900576EE9 /* edge.conf */, 3C1EE6B81AB1080900576EE9 /* edge.token.traverse.conf */, + 3C4AB9341B8C9FF9006627D3 /* exec.conf */, 3C1EE6B91AB1080900576EE9 /* ffmpeg.transcode.conf */, 3C1EE6BA1AB1080900576EE9 /* forward.master.conf */, 3C1EE6BB1AB1080900576EE9 /* forward.slave.conf */, @@ -691,6 +799,8 @@ 3C1EE6BE1AB1080900576EE9 /* hls.conf */, 3C1EE6BF1AB1080900576EE9 /* http.aac.live.conf */, 3C1EE6C01AB1080900576EE9 /* http.flv.live.conf */, + 3C8280261BAFF896004A1794 /* http.flv.live.edge1.conf */, + 3C8280271BAFF896004A1794 /* http.flv.live.edge2.conf */, 3C1EE6C11AB1080900576EE9 /* http.heartbeat.conf */, 3C1EE6C21AB1080900576EE9 /* http.hls.conf */, 3C1EE6C31AB1080900576EE9 /* http.hooks.callback.conf */, @@ -700,6 +810,7 @@ 3C1EE6C71AB1080900576EE9 /* ingest.conf */, 3C1EE6C81AB1080900576EE9 /* mac.dev.conf */, 3C1EE6C91AB1080900576EE9 /* origin.conf */, + 3C8280281BAFF896004A1794 /* push.flv.conf */, 3C1EE6CA1AB1080900576EE9 /* push.mpegts.over.udp.conf */, 3C1EE6CB1AB1080900576EE9 /* push.rtsp.conf */, 3C1EE6CD1AB1080900576EE9 /* realtime.conf */, @@ -707,6 +818,7 @@ 3C1EE6CF1AB1080900576EE9 /* security.deny.publish.conf */, 3C1EE6D01AB1080900576EE9 /* srs.conf */, 3C1EE6D11AB1080900576EE9 /* transcode2hls.audio.only.conf */, + 3C8280291BAFF896004A1794 /* transform.edge.conf */, ); name = conf; sourceTree = ""; @@ -715,7 +827,6 @@ isa = PBXGroup; children = ( 3C1EE6D31AB1367D00576EE9 /* AUTHORS.txt */, - 3C1EE6D41AB1367D00576EE9 /* DONATIONS.txt */, 3C1EE6D51AB1367D00576EE9 /* LICENSE */, 3C1EE6D61AB1367D00576EE9 /* README.md */, ); @@ -739,6 +850,9 @@ isa = PBXGroup; children = ( 3CB25C291BB269FD00C97A63 /* jmp_sp.cpp */, + 3CA432A91E40AEBC001DA0C6 /* Makefile */, + 3C24ECCB1C3B824800460622 /* memory.error.notcmalloc.cpp */, + 3C24ECCC1C3B824800460622 /* memory.error.tcmalloc.cpp */, 3C663F021AB0155100286D8B /* srs_aac_raw_publish.c */, 3C663F031AB0155100286D8B /* srs_audio_raw_publish.c */, 3C663F041AB0155100286D8B /* srs_bandwidth_check.c */, @@ -747,6 +861,7 @@ 3C663F071AB0155100286D8B /* srs_flv_parser.c */, 3C663F081AB0155100286D8B /* srs_h264_raw_publish.c */, 3C663F091AB0155100286D8B /* srs_ingest_flv.c */, + 3CA432AA1E40AEBC001DA0C6 /* srs_ingest_mp4.c */, 3C663F0A1AB0155100286D8B /* srs_ingest_rtmp.c */, 3C663F0B1AB0155100286D8B /* srs_play.c */, 3C663F0C1AB0155100286D8B /* srs_publish.c */, @@ -756,21 +871,48 @@ path = srs_xcode; sourceTree = ""; }; - 3C689F901AB6AA9100C9CEEE /* st-1.9 */ = { + 3C6673D01DF7B95000A6DF57 /* hls-ingester */ = { isa = PBXGroup; children = ( - 3C689F991AB6AAC800C9CEEE /* md.S */, - 3C689F9A1AB6AAC800C9CEEE /* public.h */, - 3C689F9B1AB6AAC800C9CEEE /* sched.c */, - 3C689F9C1AB6AAC800C9CEEE /* stk.c */, - 3C689F9D1AB6AAC800C9CEEE /* sync.c */, - 3C689F911AB6AAAC00C9CEEE /* common.h */, - 3C689F921AB6AAAC00C9CEEE /* event.c */, - 3C689F931AB6AAAC00C9CEEE /* io.c */, - 3C689F941AB6AAAC00C9CEEE /* key.c */, - 3C689F951AB6AAAC00C9CEEE /* md.h */, + 3C6673D11DF7B95E00A6DF57 /* config */, ); - name = "st-1.9"; + name = "hls-ingester"; + sourceTree = ""; + }; + 3C6F2D721E86535D003D0805 /* mp4-parser */ = { + isa = PBXGroup; + children = ( + 3C6F2D731E86536B003D0805 /* config */, + ); + name = "mp4-parser"; + sourceTree = ""; + }; + 3C7175A61E1DEA0500E8C49F /* st */ = { + isa = PBXGroup; + children = ( + 3CECAF8E1EDC100F00C50501 /* common.h */, + 3CECAF8F1EDC100F00C50501 /* event.c */, + 3CECAF901EDC100F00C50501 /* io.c */, + 3CECAF911EDC100F00C50501 /* key.c */, + 3CECAF921EDC100F00C50501 /* Makefile */, + 3CECAF931EDC100F00C50501 /* md.h */, + 3CECAF941EDC100F00C50501 /* md.S */, + 3CECAF951EDC100F00C50501 /* public.h */, + 3CECAF961EDC100F00C50501 /* sched.c */, + 3CECAF971EDC100F00C50501 /* stk.c */, + 3CECAF981EDC100F00C50501 /* sync.c */, + ); + name = st; + sourceTree = ""; + }; + 3C96ADC41B00A71000885304 /* modules */ = { + isa = PBXGroup; + children = ( + 3C6673D01DF7B95000A6DF57 /* hls-ingester */, + 3C6F2D721E86535D003D0805 /* mp4-parser */, + 3C6673CF1DF7B93200A6DF57 /* readme.txt */, + ); + name = modules; sourceTree = ""; }; 3CC52DC91ACE4006006FEB01 /* utest */ = { @@ -794,6 +936,27 @@ name = utest; sourceTree = ""; }; + 3CE893B61E875095000B742D /* service */ = { + isa = PBXGroup; + children = ( + 3CE893C91E8770E2000B742D /* srs_service_conn.cpp */, + 3CE893CA1E8770E2000B742D /* srs_service_conn.hpp */, + 3CE893BD1E876A97000B742D /* srs_service_http_client.cpp */, + 3CE893BE1E876A97000B742D /* srs_service_http_client.hpp */, + 3CE893C01E876B9E000B742D /* srs_service_http_conn.cpp */, + 3CE893C11E876B9E000B742D /* srs_service_http_conn.hpp */, + 3CE893B71E8750A9000B742D /* srs_service_log.cpp */, + 3CE893B81E8750A9000B742D /* srs_service_log.hpp */, + 3CE893C31E876C39000B742D /* srs_service_rtmp_conn.cpp */, + 3CE893C41E876C39000B742D /* srs_service_rtmp_conn.hpp */, + 3CE893BA1E875108000B742D /* srs_service_st.cpp */, + 3CE893BB1E875108000B742D /* srs_service_st.hpp */, + 3CE893C61E876D04000B742D /* srs_service_utility.cpp */, + 3CE893C71E876D04000B742D /* srs_service_utility.hpp */, + ); + name = service; + sourceTree = ""; + }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ @@ -852,29 +1015,35 @@ files = ( 3C1232951AAE81D900CE8F6C /* srs_app_config.cpp in Sources */, 3C663F0F1AB0155100286D8B /* srs_aac_raw_publish.c in Sources */, - 3C689FA01AB6AAC800C9CEEE /* stk.c in Sources */, 3CD88B3F1ACA9C58000359E0 /* srs_app_async_call.cpp in Sources */, 3C1232961AAE81D900CE8F6C /* srs_app_conn.cpp in Sources */, 3C12322A1AAE814D00CE8F6C /* srs_kernel_ts.cpp in Sources */, 3C12329E1AAE81D900CE8F6C /* srs_app_hls.cpp in Sources */, 3CC52DD91ACE4023006FEB01 /* srs_utest_config.cpp in Sources */, 3C663F171AB0155100286D8B /* srs_ingest_rtmp.c in Sources */, + 3C26E3C61BB146FF00D0F9DB /* srs_app_kafka.cpp in Sources */, 3C663F131AB0155100286D8B /* srs_flv_injecter.c in Sources */, - 3C1232971AAE81D900CE8F6C /* srs_app_dvr.cpp in Sources */, + 3C24ECCD1C3B824800460622 /* memory.error.notcmalloc.cpp in Sources */, + 3CD247C31BB3F14100DC1922 /* srs_kernel_balance.cpp in Sources */, 3C1232271AAE814D00CE8F6C /* srs_kernel_log.cpp in Sources */, - 3C689F961AB6AAAC00C9CEEE /* event.c in Sources */, 3C1232A81AAE81D900CE8F6C /* srs_app_log.cpp in Sources */, + 3CECAF9D1EDC100F00C50501 /* md.S in Sources */, 3C1232A41AAE81D900CE8F6C /* srs_app_ingest.cpp in Sources */, + 3CE893B51E87508D000B742D /* srs_app_dvr.cpp in Sources */, 3C0D422E1B87165900C2508B /* srs_protocol_json.cpp in Sources */, 3C1232B41AAE81D900CE8F6C /* srs_app_st.cpp in Sources */, + 3C9F82221E4ECA8200F5B2D2 /* srs_app_dash.cpp in Sources */, 3C1232481AAE81A400CE8F6C /* srs_rtmp_stack.cpp in Sources */, 3C1232B01AAE81D900CE8F6C /* srs_app_security.cpp in Sources */, 3C12322B1AAE814D00CE8F6C /* srs_kernel_utility.cpp in Sources */, 3C12324A1AAE81A400CE8F6C /* srs_rtsp_stack.cpp in Sources */, + 3CE893BF1E876A97000B742D /* srs_service_http_client.cpp in Sources */, 3C36DB5D1ABD1CB90066CCAF /* srs_librtmp.cpp in Sources */, 3C12329F1AAE81D900CE8F6C /* srs_app_http_api.cpp in Sources */, + 3CECAF991EDC100F00C50501 /* event.c in Sources */, 3C1EE6AE1AB1055800576EE9 /* srs_app_hds.cpp in Sources */, 3C663F101AB0155100286D8B /* srs_audio_raw_publish.c in Sources */, + 3CECAFA01EDC100F00C50501 /* sync.c in Sources */, 3C663F111AB0155100286D8B /* srs_bandwidth_check.c in Sources */, 3CC52DDE1ACE4023006FEB01 /* srs_utest.cpp in Sources */, 3C1232A11AAE81D900CE8F6C /* srs_app_http_conn.cpp in Sources */, @@ -883,71 +1052,91 @@ 3CC52DDA1ACE4023006FEB01 /* srs_utest_core.cpp in Sources */, 3C36DB5C1ABD1CB90066CCAF /* srs_lib_simple_socket.cpp in Sources */, 3C1232201AAE814D00CE8F6C /* srs_kernel_aac.cpp in Sources */, + 3C8CE01E1C3F482100548CC6 /* srs_app_hourglass.cpp in Sources */, 3C1232941AAE81D900CE8F6C /* srs_app_bandwidth.cpp in Sources */, 3C1232221AAE814D00CE8F6C /* srs_kernel_codec.cpp in Sources */, 3C1232B71AAE81D900CE8F6C /* srs_app_utility.cpp in Sources */, 3C1232AB1AAE81D900CE8F6C /* srs_app_recv_thread.cpp in Sources */, + 3CE893C51E876C39000B742D /* srs_service_rtmp_conn.cpp in Sources */, 3CC52DDC1ACE4023006FEB01 /* srs_utest_protocol.cpp in Sources */, 3C663F151AB0155100286D8B /* srs_h264_raw_publish.c in Sources */, 3C1231F61AAE652D00CE8F6C /* srs_core_autofree.cpp in Sources */, + 8C0652B12035B5BA000B0661 /* srs_app_coworkers.cpp in Sources */, 3C5265B41B241BF0009CA186 /* srs_core_mem_watch.cpp in Sources */, 3C1EE6D71AB1367D00576EE9 /* README.md in Sources */, + 3C82802C1BAFF8CC004A1794 /* srs_kafka_stack.cpp in Sources */, 3C1232411AAE81A400CE8F6C /* srs_raw_avc.cpp in Sources */, - 3C1232491AAE81A400CE8F6C /* srs_rtmp_utility.cpp in Sources */, + 3C1232491AAE81A400CE8F6C /* srs_protocol_utility.cpp in Sources */, 3C663F191AB0155100286D8B /* srs_publish.c in Sources */, + 3C9F82251E4F5D2A00F5B2D2 /* srs_protocol_format.cpp in Sources */, 3C0E1B8D1B0F5ADF003ADEF7 /* srs_http_stack.cpp in Sources */, 3C1232A01AAE81D900CE8F6C /* srs_app_http_client.cpp in Sources */, - 3C689F981AB6AAAC00C9CEEE /* key.c in Sources */, 3C12329B1AAE81D900CE8F6C /* srs_app_ffmpeg.cpp in Sources */, - 3C1232421AAE81A400CE8F6C /* srs_rtmp_amf0.cpp in Sources */, + 3C1232421AAE81A400CE8F6C /* srs_protocol_amf0.cpp in Sources */, + 3C4AB9331B8C9148006627D3 /* srs_app_ng_exec.cpp in Sources */, 3C1232AA1AAE81D900CE8F6C /* srs_app_pithy_print.cpp in Sources */, + 3CECAF9F1EDC100F00C50501 /* stk.c in Sources */, 3C12329C1AAE81D900CE8F6C /* srs_app_forward.cpp in Sources */, 3C1232251AAE814D00CE8F6C /* srs_kernel_file.cpp in Sources */, + 3CECAF9A1EDC100F00C50501 /* io.c in Sources */, 3C1232AD1AAE81D900CE8F6C /* srs_app_reload.cpp in Sources */, + 3CE893C81E876D04000B742D /* srs_service_utility.cpp in Sources */, 3C1231F81AAE652D00CE8F6C /* srs_core.cpp in Sources */, 3C1232A21AAE81D900CE8F6C /* srs_app_http_hooks.cpp in Sources */, 3C663F121AB0155100286D8B /* srs_detect_rtmp.c in Sources */, + 3CE893CB1E8770E2000B742D /* srs_service_conn.cpp in Sources */, 3C1232B11AAE81D900CE8F6C /* srs_app_server.cpp in Sources */, - 3C689F9F1AB6AAC800C9CEEE /* sched.c in Sources */, 3C1232061AAE812C00CE8F6C /* srs_main_server.cpp in Sources */, 3C1232281AAE814D00CE8F6C /* srs_kernel_mp3.cpp in Sources */, + 3CA432AC1E40AEBC001DA0C6 /* srs_ingest_mp4.c in Sources */, + 3C24ECCE1C3B824800460622 /* memory.error.tcmalloc.cpp in Sources */, + 3CA432AB1E40AEBC001DA0C6 /* Makefile in Sources */, 3C1232B21AAE81D900CE8F6C /* srs_app_source.cpp in Sources */, 3C1231F71AAE652D00CE8F6C /* srs_core_performance.cpp in Sources */, + 3C4D184C1E73F133008806F7 /* srs_app_fragment.cpp in Sources */, + 3CECAF9B1EDC100F00C50501 /* key.c in Sources */, + 3CE893BC1E875108000B742D /* srs_service_st.cpp in Sources */, 3CC52DD81ACE4023006FEB01 /* srs_utest_amf0.cpp in Sources */, + 3C4F97121B8B466D00FF0E46 /* srs_app_process.cpp in Sources */, 3C1232981AAE81D900CE8F6C /* srs_app_edge.cpp in Sources */, 3CC52DDB1ACE4023006FEB01 /* srs_utest_kernel.cpp in Sources */, - 3C689F9E1AB6AAC800C9CEEE /* md.S in Sources */, 3C1232461AAE81A400CE8F6C /* srs_rtmp_msg_array.cpp in Sources */, 3C1232A71AAE81D900CE8F6C /* srs_app_listener.cpp in Sources */, 3C1232261AAE814D00CE8F6C /* srs_kernel_flv.cpp in Sources */, 3C036B551B2D0AC10078E2E0 /* srs_app_http_static.cpp in Sources */, 3C663F1A1AB0155100286D8B /* srs_rtmp_dump.c in Sources */, 3CE6CD311AE4AFB800706E07 /* srs_main_ingest_hls.cpp in Sources */, + 3C6F2D751E8653BF003D0805 /* srs_main_mp4_parser.cpp in Sources */, 3C28EDDF1AF5C43F00A3AEAC /* srs_app_caster_flv.cpp in Sources */, 3C1232241AAE814D00CE8F6C /* srs_kernel_error.cpp in Sources */, 3C036B561B2D0AC10078E2E0 /* srs_app_http_stream.cpp in Sources */, + 3CE893C21E876B9E000B742D /* srs_service_http_conn.cpp in Sources */, + 3C068D6D1B10175500AA722C /* srs_protocol_stream.cpp in Sources */, 3CB25C2A1BB269FD00C97A63 /* jmp_sp.cpp in Sources */, - 3C068D6D1B10175500AA722C /* srs_protocol_buffer.cpp in Sources */, + 3C068D6D1B10175500AA722C /* srs_protocol_stream.cpp in Sources */, 3C1232441AAE81A400CE8F6C /* srs_rtmp_handshake.cpp in Sources */, - 3C1232291AAE814D00CE8F6C /* srs_kernel_stream.cpp in Sources */, + 3C1232291AAE814D00CE8F6C /* srs_kernel_buffer.cpp in Sources */, 3C663F181AB0155100286D8B /* srs_play.c in Sources */, - 3C689F971AB6AAAC00C9CEEE /* io.c in Sources */, + 3CECAF9E1EDC100F00C50501 /* sched.c in Sources */, 3C1232B61AAE81D900CE8F6C /* srs_app_thread.cpp in Sources */, 3C1232A91AAE81D900CE8F6C /* srs_app_mpegts_udp.cpp in Sources */, + 3CE893B91E8750A9000B742D /* srs_service_log.cpp in Sources */, + 3CECAF9C1EDC100F00C50501 /* Makefile in Sources */, 3C1232AE1AAE81D900CE8F6C /* srs_app_rtmp_conn.cpp in Sources */, 3C1232B51AAE81D900CE8F6C /* srs_app_statistic.cpp in Sources */, 3C663F161AB0155100286D8B /* srs_ingest_flv.c in Sources */, 3C663F141AB0155100286D8B /* srs_flv_parser.c in Sources */, - 3C1232451AAE81A400CE8F6C /* srs_rtmp_io.cpp in Sources */, - 3C1232211AAE814D00CE8F6C /* srs_kernel_buffer.cpp in Sources */, + 3C1232451AAE81A400CE8F6C /* srs_protocol_io.cpp in Sources */, + 3C1232211AAE814D00CE8F6C /* srs_kernel_stream.cpp in Sources */, 3C36DB5B1ABD1CB90066CCAF /* srs_lib_bandwidth.cpp in Sources */, 3C12329D1AAE81D900CE8F6C /* srs_app_heartbeat.cpp in Sources */, 3C1232231AAE814D00CE8F6C /* srs_kernel_consts.cpp in Sources */, 3C1232AF1AAE81D900CE8F6C /* srs_app_rtsp.cpp in Sources */, + 3CA432A81E3F46DD001DA0C6 /* srs_kernel_io.cpp in Sources */, 3CC52DDD1ACE4023006FEB01 /* srs_utest_reload.cpp in Sources */, - 3C689FA11AB6AAC800C9CEEE /* sync.c in Sources */, 3C068D6A1B10149F00AA722C /* srs_protocol_kbps.cpp in Sources */, 3C12329A1AAE81D900CE8F6C /* srs_app_encoder.cpp in Sources */, + 3C44AACF1E3AF50200D4ABC3 /* srs_kernel_mp4.cpp in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -1034,12 +1223,9 @@ ALWAYS_SEARCH_USER_PATHS = YES; CLANG_CXX_LANGUAGE_STANDARD = "c++98"; CLANG_CXX_LIBRARY = "libstdc++"; - LIBRARY_SEARCH_PATHS = ( - "../../objs/**", - "/Users/winlin/Desktop/git/ossrs/trunk/objs/http-parser-2.1", - ); + LIBRARY_SEARCH_PATHS = "../../objs/**"; PRODUCT_NAME = "$(TARGET_NAME)"; - "USER_HEADER_SEARCH_PATHS[arch=*]" = "../../src/** ../../objs ../../objs/st ../../objs/hp ../../objs/openssl"; + "USER_HEADER_SEARCH_PATHS[arch=*]" = "../../src/** ../../objs ../../objs/st ../../objs/openssl"; }; name = Debug; }; @@ -1049,10 +1235,7 @@ ALWAYS_SEARCH_USER_PATHS = YES; CLANG_CXX_LANGUAGE_STANDARD = "c++98"; CLANG_CXX_LIBRARY = "libstdc++"; - LIBRARY_SEARCH_PATHS = ( - "../../objs/**", - "/Users/winlin/Desktop/git/ossrs/trunk/objs/http-parser-2.1", - ); + LIBRARY_SEARCH_PATHS = "../../objs/**"; PRODUCT_NAME = "$(TARGET_NAME)"; }; name = Release; diff --git a/trunk/modules/hls-ingester/config b/trunk/modules/hls-ingester/config new file mode 100644 index 000000000..eda6a1363 --- /dev/null +++ b/trunk/modules/hls-ingester/config @@ -0,0 +1,7 @@ + +# The module to ingest hls to replace ffmpeg with better behavior. +SRS_MODULE_NAME=("srs_hls_ingester") +SRS_MODULE_MAIN=("srs_main_ingest_hls") +SRS_MODULE_APP=() +SRS_MODULE_DEFINES="" +SRS_MODULE_MAKEFILE="" diff --git a/trunk/modules/mp4-parser/config b/trunk/modules/mp4-parser/config new file mode 100644 index 000000000..7f9adfd66 --- /dev/null +++ b/trunk/modules/mp4-parser/config @@ -0,0 +1,7 @@ + +# The module to parse mp4 file. +SRS_MODULE_NAME=("srs_mp4_parser") +SRS_MODULE_MAIN=("srs_main_mp4_parser") +SRS_MODULE_APP=() +SRS_MODULE_DEFINES="" +SRS_MODULE_MAKEFILE="" diff --git a/trunk/modules/readme.txt b/trunk/modules/readme.txt index dd2f9b4e5..32d7891f0 100644 --- a/trunk/modules/readme.txt +++ b/trunk/modules/readme.txt @@ -1,12 +1,27 @@ -SRS模块规则: -1. 一个模块一个目录 -2. 目录下放一个config文件 -3. 所有的configure中的变量模块中可以使用 +SRS Module Rules(SRS模块规则) +1. Each module in its seperate home directory(一个模块一个目录). +2. There is a config file in home(目录下放一个config文件). +3. All variables in configure are available(所有的configure中的变量模块中可以使用). -模块中需要定义变量,例如: -1. SRS_MODULE_NAME:模块名称,用来做Makefile的phony以及执行binary文件名。 -2. SRS_MODULE_MAIN:模块的main函数所在的cpp文件,在src/main目录。 -3. SRS_MODULE_APP:模块在src/app目录的源文件列表。 -4. SRS_MODULE_DEFINES: 模块编译时的额外宏定义。 +The Variables in config(模块中需要定义变量,例如): +1. SRS_MODULE_NAME:The application binary name, optional. (模块名称,用来做Makefile的phony以及执行binary文件名。模块的二进制输出。为空时没有独立的二进制。) +2. SRS_MODULE_MAIN:The source file in src/main directory, optional. (模块的main函数所在的cpp文件,在src/main目录。模块在main的文件。可以为空。) +3. SRS_MODULE_APP:The source file in src/app directory, optional. (模块在src/app目录的源文件列表。模块在app的文件。可以为空。) +4. SRS_MODULE_DEFINES: The extra defined macros, optional. (模块编译时的额外宏定义。在app和main模块加入。可以为空。) +5. SRS_MODULE_MAKEFILE: The specified Makefile, optional. (模块的Makefile。在make时会执行这个Makefile。可以为空。) + +Reset all Variables at the beginning(在配置开头必须清空这些变量): +SRS_MODULE_NAME=() +SRS_MODULE_MAIN=() +SRS_MODULE_APP=() +SRS_MODULE_DEFINES="" +SRS_MODULE_MAKEFILE="" + +For example(下面是一个实例): +SRS_MODULE_NAME=("srs_rtmfpd") +SRS_MODULE_MAIN=("srs_main_rtmfpd") +SRS_MODULE_APP=("srs_app_rtfmpd") +SRS_MODULE_DEFINES="-DRTMFPD" +SRS_MODULE_MAKEFILE="modules/rtmfpd/Makefile" winlin, 2015.3 diff --git a/trunk/research/api-server/server.py b/trunk/research/api-server/server.py index fd1412eae..888569638 100755 --- a/trunk/research/api-server/server.py +++ b/trunk/research/api-server/server.py @@ -2,7 +2,7 @@ ''' The MIT License (MIT) -Copyright (c) 2013-2015 SRS(ossrs) +Copyright (c) 2013-2016 SRS(ossrs) Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in @@ -36,7 +36,8 @@ reload(sys) exec("sys.setdefaultencoding('utf-8')") assert sys.getdefaultencoding().lower() == "utf-8" -import os, json, time, datetime, cherrypy, threading, urllib2 +import os, json, time, datetime, cherrypy, threading, urllib2, shlex, subprocess +import cherrypy.process.plugins # simple log functions. def trace(msg): @@ -116,7 +117,7 @@ class RESTClients(object): except Exception, ex: code = Error.system_parse_json trace("parse the request to json failed, req=%s, ex=%s, code=%s"%(req, ex, code)) - return str(code) + return json.dumps({"code": int(code), "data": None}) action = json_req["action"] if action == "on_connect": @@ -127,7 +128,7 @@ class RESTClients(object): trace("invalid request action: %s"%(json_req["action"])) code = Error.request_invalid_action - return str(code) + return json.dumps({"code": int(code), "data": None}) def OPTIONS(self, *args, **kwargs): enable_crossdomain() @@ -175,7 +176,7 @@ class RESTStreams(object): "action": "on_publish", "client_id": 1985, "ip": "192.168.1.10", "vhost": "video.test.com", "app": "live", - "stream": "livestream" + "stream": "livestream", "param":"?token=xxx&salt=yyy" } on_unpublish: when client(encoder) stop publish to vhost/app/stream, call the hook, @@ -184,7 +185,7 @@ class RESTStreams(object): "action": "on_unpublish", "client_id": 1985, "ip": "192.168.1.10", "vhost": "video.test.com", "app": "live", - "stream": "livestream" + "stream": "livestream", "param":"?token=xxx&salt=yyy" } if valid, the hook must return HTTP code 200(Stauts OK) and response an int value specifies the error code(0 corresponding to success): @@ -203,7 +204,7 @@ class RESTStreams(object): except Exception, ex: code = Error.system_parse_json trace("parse the request to json failed, req=%s, ex=%s, code=%s"%(req, ex, code)) - return str(code) + return json.dumps({"code": int(code), "data": None}) action = json_req["action"] if action == "on_publish": @@ -214,7 +215,7 @@ class RESTStreams(object): trace("invalid request action: %s"%(json_req["action"])) code = Error.request_invalid_action - return str(code) + return json.dumps({"code": int(code), "data": None}) def OPTIONS(self, *args, **kwargs): enable_crossdomain() @@ -222,8 +223,8 @@ class RESTStreams(object): def __on_publish(self, req): code = Error.success - trace("srs %s: client id=%s, ip=%s, vhost=%s, app=%s, stream=%s"%( - req["action"], req["client_id"], req["ip"], req["vhost"], req["app"], req["stream"] + trace("srs %s: client id=%s, ip=%s, vhost=%s, app=%s, stream=%s, param=%s"%( + req["action"], req["client_id"], req["ip"], req["vhost"], req["app"], req["stream"], req["param"] )) # TODO: process the on_publish event @@ -233,8 +234,8 @@ class RESTStreams(object): def __on_unpublish(self, req): code = Error.success - trace("srs %s: client id=%s, ip=%s, vhost=%s, app=%s, stream=%s"%( - req["action"], req["client_id"], req["ip"], req["vhost"], req["app"], req["stream"] + trace("srs %s: client id=%s, ip=%s, vhost=%s, app=%s, stream=%s, param=%s"%( + req["action"], req["client_id"], req["ip"], req["vhost"], req["app"], req["stream"], req["param"] )) # TODO: process the on_unpublish event @@ -262,7 +263,7 @@ class RESTDvrs(object): "action": "on_dvr", "client_id": 1985, "ip": "192.168.1.10", "vhost": "video.test.com", "app": "live", - "stream": "livestream", + "stream": "livestream", "param":"?token=xxx&salt=yyy", "cwd": "/usr/local/srs", "file": "./objs/nginx/html/live/livestream.1420254068776.flv" } @@ -283,7 +284,7 @@ class RESTDvrs(object): except Exception, ex: code = Error.system_parse_json trace("parse the request to json failed, req=%s, ex=%s, code=%s"%(req, ex, code)) - return str(code) + return json.dumps({"code": int(code), "data": None}) action = json_req["action"] if action == "on_dvr": @@ -292,7 +293,7 @@ class RESTDvrs(object): trace("invalid request action: %s"%(json_req["action"])) code = Error.request_invalid_action - return str(code) + return json.dumps({"code": int(code), "data": None}) def OPTIONS(self, *args, **kwargs): enable_crossdomain() @@ -300,8 +301,8 @@ class RESTDvrs(object): def __on_dvr(self, req): code = Error.success - trace("srs %s: client id=%s, ip=%s, vhost=%s, app=%s, stream=%s, cwd=%s, file=%s"%( - req["action"], req["client_id"], req["ip"], req["vhost"], req["app"], req["stream"], + trace("srs %s: client id=%s, ip=%s, vhost=%s, app=%s, stream=%s, param=%s, cwd=%s, file=%s"%( + req["action"], req["client_id"], req["ip"], req["vhost"], req["app"], req["stream"], req["param"], req["cwd"], req["file"] )) @@ -324,6 +325,7 @@ class RESTProxy(object): so we use HTTP GET and use the variable following: [app], replace with the app. [stream], replace with the stream. + [param], replace with the param. [ts_url], replace with the ts url. ignore any return data of server. ''' @@ -358,6 +360,7 @@ class RESTHls(object): so we use HTTP GET and use the variable following: [app], replace with the app. [stream], replace with the stream. + [param], replace with the param. [ts_url], replace with the ts url. ignore any return data of server. ''' @@ -381,7 +384,7 @@ class RESTHls(object): "ip": "192.168.1.10", "vhost": "video.test.com", "app": "live", - "stream": "livestream", + "stream": "livestream", "param":"?token=xxx&salt=yyy", "duration": 9.68, // in seconds "cwd": "/usr/local/srs", "file": "./objs/nginx/html/live/livestream.1420254068776-100.ts", @@ -404,7 +407,7 @@ class RESTHls(object): except Exception, ex: code = Error.system_parse_json trace("parse the request to json failed, req=%s, ex=%s, code=%s"%(req, ex, code)) - return str(code) + return json.dumps({"code": int(code), "data": None}) action = json_req["action"] if action == "on_hls": @@ -413,7 +416,7 @@ class RESTHls(object): trace("invalid request action: %s"%(json_req["action"])) code = Error.request_invalid_action - return str(code) + return json.dumps({"code": int(code), "data": None}) def OPTIONS(self, *args, **kwargs): enable_crossdomain() @@ -421,8 +424,8 @@ class RESTHls(object): def __on_hls(self, req): code = Error.success - trace("srs %s: client id=%s, ip=%s, vhost=%s, app=%s, stream=%s, duration=%s, cwd=%s, file=%s, seq_no=%s"%( - req["action"], req["client_id"], req["ip"], req["vhost"], req["app"], req["stream"], req["duration"], + trace("srs %s: client id=%s, ip=%s, vhost=%s, app=%s, stream=%s, param=%s, duration=%s, cwd=%s, file=%s, seq_no=%s"%( + req["action"], req["client_id"], req["ip"], req["vhost"], req["app"], req["stream"], req["param"], req["duration"], req["cwd"], req["file"], req["seq_no"] )) @@ -451,7 +454,7 @@ class RESTSessions(object): "action": "on_play", "client_id": 1985, "ip": "192.168.1.10", "vhost": "video.test.com", "app": "live", - "stream": "livestream", + "stream": "livestream", "param":"?token=xxx&salt=yyy", "pageUrl": "http://www.test.com/live.html" } on_stop: @@ -461,7 +464,7 @@ class RESTSessions(object): "action": "on_stop", "client_id": 1985, "ip": "192.168.1.10", "vhost": "video.test.com", "app": "live", - "stream": "livestream" + "stream": "livestream", "param":"?token=xxx&salt=yyy" } if valid, the hook must return HTTP code 200(Stauts OK) and response an int value specifies the error code(0 corresponding to success): @@ -480,7 +483,7 @@ class RESTSessions(object): except Exception, ex: code = Error.system_parse_json trace("parse the request to json failed, req=%s, ex=%s, code=%s"%(req, ex, code)) - return str(code) + return json.dumps({"code": int(code), "data": None}) action = json_req["action"] if action == "on_play": @@ -491,7 +494,7 @@ class RESTSessions(object): trace("invalid request action: %s"%(json_req["action"])) code = Error.request_invalid_action - return str(code) + return json.dumps({"code": int(code), "data": None}) def OPTIONS(self, *args, **kwargs): enable_crossdomain() @@ -499,8 +502,8 @@ class RESTSessions(object): def __on_play(self, req): code = Error.success - trace("srs %s: client id=%s, ip=%s, vhost=%s, app=%s, stream=%s, pageUrl=%s"%( - req["action"], req["client_id"], req["ip"], req["vhost"], req["app"], req["stream"], req["pageUrl"] + trace("srs %s: client id=%s, ip=%s, vhost=%s, app=%s, stream=%s, param=%s, pageUrl=%s"%( + req["action"], req["client_id"], req["ip"], req["vhost"], req["app"], req["stream"], req["param"], req["pageUrl"] )) # TODO: process the on_play event @@ -510,8 +513,8 @@ class RESTSessions(object): def __on_stop(self, req): code = Error.success - trace("srs %s: client id=%s, ip=%s, vhost=%s, app=%s, stream=%s"%( - req["action"], req["client_id"], req["ip"], req["vhost"], req["app"], req["stream"] + trace("srs %s: client id=%s, ip=%s, vhost=%s, app=%s, stream=%s, param=%s"%( + req["action"], req["client_id"], req["ip"], req["vhost"], req["app"], req["stream"], req["param"] )) # TODO: process the on_stop event @@ -778,6 +781,46 @@ class RESTChats(object): def OPTIONS(self, *args, **kwargs): enable_crossdomain() +''' +the snapshot api, +to start a snapshot when encoder start publish stream, +stop the snapshot worker when stream finished. +''' +class RESTSnapshots(object): + exposed = True + + def __init__(self): + pass + + def POST(self): + enable_crossdomain() + + # return the error code in str + code = Error.success + + req = cherrypy.request.body.read() + trace("post to streams, req=%s"%(req)) + try: + json_req = json.loads(req) + except Exception, ex: + code = Error.system_parse_json + trace("parse the request to json failed, req=%s, ex=%s, code=%s"%(req, ex, code)) + return json.dumps({"code": int(code), "data": None}) + + action = json_req["action"] + if action == "on_publish": + code = worker.snapshot_create(json_req) + elif action == "on_unpublish": + code = worker.snapshot_destroy(json_req) + else: + trace("invalid request action: %s"%(json_req["action"])) + code = Error.request_invalid_action + + return json.dumps({"code": int(code), "data": None}) + + def OPTIONS(self, *args, **kwargs): + enable_crossdomain() + # HTTP RESTful path. class Root(object): exposed = True @@ -818,6 +861,7 @@ class V1(object): self.proxy = RESTProxy() self.chats = RESTChats() self.servers = RESTServers() + self.snapshots = RESTSnapshots() def GET(self): enable_crossdomain(); return json.dumps({"code":Error.success, "urls":{ @@ -844,7 +888,7 @@ if __name__ != "__main__": # check the user options if len(sys.argv) <= 1: - print "SRS api callback server, Copyright (c) 2013-2015 SRS(ossrs)" + print "SRS api callback server, Copyright (c) 2013-2016 SRS(ossrs)" print "Usage: python %s "%(sys.argv[0]) print " port: the port to listen at." print "For example:" @@ -858,10 +902,149 @@ port = int(sys.argv[1]) static_dir = os.path.abspath(os.path.join(os.path.dirname(sys.argv[0]), "static-dir")) trace("api server listen at port: %s, static_dir: %s"%(port, static_dir)) + +discard = open("/dev/null", "rw") +''' +create process by specifies command. +@param command the command str to start the process. +@param stdout_fd an int fd specifies the stdout fd. +@param stderr_fd an int fd specifies the stderr fd. +@param log_file a file object specifies the additional log to write to. ignore if None. +@return a Popen object created by subprocess.Popen(). +''' +def create_process(command, stdout_fd, stderr_fd): + # log the original command + msg = "process start command: %s"%(command); + + # to avoid shell injection, directly use the command, no need to filter. + args = shlex.split(str(command)); + process = subprocess.Popen(args, stdout=stdout_fd, stderr=stderr_fd); + + return process; +''' +isolate thread for srs worker, to do some job in background, +for example, to snapshot thumbnail of RTMP stream. +''' +class SrsWorker(cherrypy.process.plugins.SimplePlugin): + def __init__(self, bus): + cherrypy.process.plugins.SimplePlugin.__init__(self, bus); + self.__snapshots = {} + + def start(self): + print "srs worker thread started" + + def stop(self): + print "srs worker thread stopped" + + def main(self): + for url in self.__snapshots: + snapshot = self.__snapshots[url] + + diff = time.time() - snapshot['timestamp'] + process = snapshot['process'] + + # aborted. + if process is not None and snapshot['abort']: + process.kill() + process.poll() + del self.__snapshots[url] + print 'abort snapshot %s'%snapshot['cmd'] + break + + # how many snapshots to output. + vframes = 5 + # the expire in seconds for ffmpeg to snapshot. + expire = 1 + # the timeout to kill ffmpeg. + kill_ffmpeg_timeout = 30 * expire + # the ffmpeg binary path + ffmpeg = "./objs/ffmpeg/bin/ffmpeg" + # the best url for thumbnail. + besturl = os.path.join(static_dir, "%s/%s-best.png"%(snapshot['app'], snapshot['stream'])) + # the lambda to generate the thumbnail with index. + lgo = lambda dir, app, stream, index: os.path.join(dir, "%s/%s-%03d.png"%(app, stream, index)) + # the output for snapshot command + output = os.path.join(static_dir, "%s/%s-%%03d.png"%(snapshot['app'], snapshot['stream'])) + # the ffmepg command to snapshot + cmd = '%s -i %s -vf fps=1 -vcodec png -f image2 -an -y -vframes %s -y %s'%(ffmpeg, url, vframes, output) + + # already snapshoted and not expired. + if process is not None and diff < expire: + continue + + # terminate the active process + if process is not None: + # the poll will set the process.returncode + process.poll() + + # None incidates the process hasn't terminate yet. + if process.returncode is not None: + # process terminated with error. + if process.returncode != 0: + print 'process terminated with error=%s, cmd=%s'%(process.returncode, snapshot['cmd']) + # process terminated normally. + else: + # guess the best one. + bestsize = 0 + for i in range(0, vframes): + output = lgo(static_dir, snapshot['app'], snapshot['stream'], i + 1) + fsize = os.path.getsize(output) + if bestsize < fsize: + os.system("rm -f '%s'"%besturl) + os.system("ln -sf '%s' '%s'"%(output, besturl)) + bestsize = fsize + print 'the best thumbnail is %s'%besturl + else: + # wait for process to terminate, timeout is N*expire. + if diff < kill_ffmpeg_timeout: + continue + # kill the process when user cancel. + else: + process.kill() + print 'kill the process %s'%snapshot['cmd'] + + # create new process to snapshot. + print 'snapshot by: %s'%cmd + + process = create_process(cmd, discard.fileno(), discard.fileno()) + snapshot['process'] = process + snapshot['cmd'] = cmd + snapshot['timestamp'] = time.time() + pass; + + # {"action":"on_publish","client_id":108,"ip":"127.0.0.1","vhost":"__defaultVhost__","app":"live","stream":"livestream"} + # ffmpeg -i rtmp://127.0.0.1:1935/live?vhost=dev/stream -vf fps=1 -vcodec png -f image2 -an -y -vframes 3 -y static-dir/live/livestream-%03d.png + def snapshot_create(self, req): + url = "rtmp://127.0.0.1/%s...vhost...%s/%s"%(req['app'], req['vhost'], req['stream']) + if url in self.__snapshots: + print 'ignore exists %s'%url + return Error.success + + req['process'] = None + req['abort'] = False + req['timestamp'] = time.time() + self.__snapshots[url] = req + return Error.success + + # {"action":"on_unpublish","client_id":108,"ip":"127.0.0.1","vhost":"__defaultVhost__","app":"live","stream":"livestream"} + def snapshot_destroy(self, req): + url = "rtmp://127.0.0.1/%s...vhost...%s/%s"%(req['app'], req['vhost'], req['stream']) + if url in self.__snapshots: + snapshot = self.__snapshots[url] + snapshot['abort'] = True + return Error.success + +# subscribe the plugin to cherrypy. +worker = SrsWorker(cherrypy.engine) +worker.subscribe(); + +# disable the autoreloader to make it more simple. +cherrypy.engine.autoreload.unsubscribe(); + # cherrypy config. conf = { 'global': { - 'server.shutdown_timeout': 1, + 'server.shutdown_timeout': 3, 'server.socket_host': '0.0.0.0', 'server.socket_port': port, 'tools.encode.on': True, diff --git a/trunk/research/api-server/static-dir/index.html b/trunk/research/api-server/static-dir/index.html index b4b4782c8..b3ecfc979 100755 --- a/trunk/research/api-server/static-dir/index.html +++ b/trunk/research/api-server/static-dir/index.html @@ -1,50 +1,26 @@ - + - SRS + SRS - - - - - - - - -
-
- -
+

SRS works!

+

+ Click here to enter SRS console.
+ 点击进入SRS控制台 +

+

+ Click here to start SRS player.
+ 点击进入SRS播放器 +

+

SRS Team © 2021

+ diff --git a/trunk/research/arm/jmp_sp.cpp b/trunk/research/arm/jmp_sp.cpp index 9b1c71511..ddb3f56f2 100644 --- a/trunk/research/arm/jmp_sp.cpp +++ b/trunk/research/arm/jmp_sp.cpp @@ -1,10 +1,10 @@ /* -# see: https://github.com/ossrs/srs/issues/190 -# see: https://github.com/ossrs/srs/wiki/v1_CN_SrsLinuxArm - g++ -g -O0 -o jmp_sp jmp_sp.cpp - arm-linux-gnueabi-g++ -g -o jmp_sp jmp_sp.cpp -static - arm-linux-gnueabi-strip jmp_sp -*/ + # see: https://github.com/ossrs/srs/issues/190 + # see: https://github.com/ossrs/srs/wiki/v1_CN_SrsLinuxArm + g++ -g -O0 -o jmp_sp jmp_sp.cpp + arm-linux-gnueabi-g++ -g -o jmp_sp jmp_sp.cpp -static + arm-linux-gnueabi-strip jmp_sp + */ #include #include #include @@ -14,14 +14,14 @@ jmp_buf context; void do_longjmp() { /** - the definition of jmp_buf: - typedef struct __jmp_buf_tag jmp_buf[1]; - struct __jmp_buf_tag { - __jmp_buf __jmpbuf; - int __mask_was_saved; - __sigset_t __saved_mask; - }; - */ + the definition of jmp_buf: + typedef struct __jmp_buf_tag jmp_buf[1]; + struct __jmp_buf_tag { + __jmp_buf __jmpbuf; + int __mask_was_saved; + __sigset_t __saved_mask; + }; + */ #if defined(__amd64__) || defined(__x86_64__) // http://ftp.gnu.org/gnu/glibc/glibc-2.12.2.tar.xz // http://ftp.gnu.org/gnu/glibc/glibc-2.12.1.tar.gz @@ -38,21 +38,21 @@ void do_longjmp() */ // for glibc 2.4+, it's not possible to get and set the sp in jmp_buf /** - for example, the following is show the jmp_buf when setjmp: - (gdb) x /64xb context[0].__jmpbuf - 0x600ca0 : 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 - 0x600ca8 : 0xf8 0xc1 0x71 0xe5 0xa8 0x88 0xb4 0x15 - 0x600cb0 : 0xa0 0x05 0x40 0x00 0x00 0x00 0x00 0x00 - 0x600cb8 : 0x90 0xe4 0xff 0xff 0xff 0x7f 0x00 0x00 - 0x600cc0 : 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 - 0x600cc8 : 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 - 0x600cd0 : 0xf8 0xc1 0x51 0xe5 0xa8 0x88 0xb4 0x15 - 0x600cd8 : 0xf8 0xc1 0xd9 0x2f 0xd7 0x77 0x4b 0xea - (gdb) p /x $sp - $4 = 0x7fffffffe380 - we cannot finger the sp out. - where the glibc is 2.12. - */ + for example, the following is show the jmp_buf when setjmp: + (gdb) x /64xb context[0].__jmpbuf + 0x600ca0 : 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 + 0x600ca8 : 0xf8 0xc1 0x71 0xe5 0xa8 0x88 0xb4 0x15 + 0x600cb0 : 0xa0 0x05 0x40 0x00 0x00 0x00 0x00 0x00 + 0x600cb8 : 0x90 0xe4 0xff 0xff 0xff 0x7f 0x00 0x00 + 0x600cc0 : 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 + 0x600cc8 : 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 + 0x600cd0 : 0xf8 0xc1 0x51 0xe5 0xa8 0x88 0xb4 0x15 + 0x600cd8 : 0xf8 0xc1 0xd9 0x2f 0xd7 0x77 0x4b 0xea + (gdb) p /x $sp + $4 = 0x7fffffffe380 + we cannot finger the sp out. + where the glibc is 2.12. + */ register long int rsp0 asm("rsp"); int ret = setjmp(context); @@ -64,46 +64,46 @@ void do_longjmp() } printf("\n"); #endif - + #if defined(__arm__) /** - /usr/arm-linux-gnueabi/include/bits/setjmp.h - #ifndef _ASM - The exact set of registers saved may depend on the particular core - in use, as some coprocessor registers may need to be saved. The C - Library ABI requires that the buffer be 8-byte aligned, and - recommends that the buffer contain 64 words. The first 28 words - are occupied by v1-v6, sl, fp, sp, pc, d8-d15, and fpscr. (Note - that d8-15 require 17 words, due to the use of fstmx.) - typedef int __jmp_buf[64] __attribute__((__aligned__ (8))); - - the layout of setjmp for arm: - 0-5: v1-v6 - 6: sl - 7: fp - 8: sp - 9: pc - 10-26: d8-d15 17words - 27: fpscr - */ + /usr/arm-linux-gnueabi/include/bits/setjmp.h + #ifndef _ASM + The exact set of registers saved may depend on the particular core + in use, as some coprocessor registers may need to be saved. The C + Library ABI requires that the buffer be 8-byte aligned, and + recommends that the buffer contain 64 words. The first 28 words + are occupied by v1-v6, sl, fp, sp, pc, d8-d15, and fpscr. (Note + that d8-15 require 17 words, due to the use of fstmx.) + typedef int __jmp_buf[64] __attribute__((__aligned__ (8))); + + the layout of setjmp for arm: + 0-5: v1-v6 + 6: sl + 7: fp + 8: sp + 9: pc + 10-26: d8-d15 17words + 27: fpscr + */ /** - For example, on raspberry-pi, armv6 cpu: - (gdb) x /64xb (char*)context[0].__jmpbuf - v1, 0: 0x00 0x00 0x00 0x00 - v2, 1: 0x00 0x00 0x00 0x00 - v3, 2: 0x2c 0x84 0x00 0x00 - v4, 3: 0x00 0x00 0x00 0x00 - v5, 4: 0x00 0x00 0x00 0x00 - v6, 5: 0x00 0x00 0x00 0x00 - sl, 6: 0x00 0xf0 0xff 0xb6 - fp, 7: 0x9c 0xfb 0xff 0xbe - sp, 8: 0x88 0xfb 0xff 0xbe - pc, 9: 0x08 0x85 0x00 0x00 - (gdb) p /x $sp - $5 = 0xbefffb88 - (gdb) p /x $pc - $4 = 0x850c - */ + For example, on raspberry-pi, armv6 cpu: + (gdb) x /64xb (char*)context[0].__jmpbuf + v1, 0: 0x00 0x00 0x00 0x00 + v2, 1: 0x00 0x00 0x00 0x00 + v3, 2: 0x2c 0x84 0x00 0x00 + v4, 3: 0x00 0x00 0x00 0x00 + v5, 4: 0x00 0x00 0x00 0x00 + v6, 5: 0x00 0x00 0x00 0x00 + sl, 6: 0x00 0xf0 0xff 0xb6 + fp, 7: 0x9c 0xfb 0xff 0xbe + sp, 8: 0x88 0xfb 0xff 0xbe + pc, 9: 0x08 0x85 0x00 0x00 + (gdb) p /x $sp + $5 = 0xbefffb88 + (gdb) p /x $pc + $4 = 0x850c + */ int ret = setjmp(context); printf("setjmp func1 ret=%d\n", ret); @@ -116,23 +116,23 @@ void do_longjmp() #endif } -int main(int argc, char** argv) +int main(int argc, char** argv) { #if defined(__amd64__) || defined(__x86_64__) printf("x86_64 sizeof(long int)=%d, sizeof(long)=%d, " - "sizeof(int)=%d, __WORDSIZE=%d, __GLIBC__=%d, __GLIBC_MINOR__=%d\n", - (int)sizeof(long int), (int)sizeof(long), (int)sizeof(int), - (int)__WORDSIZE, (int)__GLIBC__, (int)__GLIBC_MINOR__); + "sizeof(int)=%d, __WORDSIZE=%d, __GLIBC__=%d, __GLIBC_MINOR__=%d\n", + (int)sizeof(long int), (int)sizeof(long), (int)sizeof(int), + (int)__WORDSIZE, (int)__GLIBC__, (int)__GLIBC_MINOR__); #else printf("arm sizeof(long int)=%d, sizeof(long)=%d, " - "sizeof(int)=%d, __GLIBC__=%d,__GLIBC_MINOR__=%d\n", - (int)sizeof(long int), (int)sizeof(long), (int)sizeof(int), - (int)__GLIBC__, (int)__GLIBC_MINOR__); + "sizeof(int)=%d, __GLIBC__=%d,__GLIBC_MINOR__=%d\n", + (int)sizeof(long int), (int)sizeof(long), (int)sizeof(int), + (int)__GLIBC__, (int)__GLIBC_MINOR__); #endif - + do_longjmp(); printf("terminated\n"); - + return 0; } diff --git a/trunk/research/code-statistic/cs.py b/trunk/research/code-statistic/cs.py index 6581ada5c..bdc1b1e27 100755 --- a/trunk/research/code-statistic/cs.py +++ b/trunk/research/code-statistic/cs.py @@ -2,7 +2,7 @@ ''' The MIT License (MIT) -Copyright (c) 2013-2015 SRS(ossrs) +Copyright (c) 2013-2016 SRS(ossrs) Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in diff --git a/trunk/research/code-statistic/csr.py b/trunk/research/code-statistic/csr.py index 6bffe9735..6da006ab1 100755 --- a/trunk/research/code-statistic/csr.py +++ b/trunk/research/code-statistic/csr.py @@ -2,7 +2,7 @@ ''' The MIT License (MIT) -Copyright (c) 2013-2015 SRS(ossrs) +Copyright (c) 2013-2016 SRS(ossrs) Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in diff --git a/trunk/research/console/en_index.html b/trunk/research/console/en_index.html new file mode 100644 index 000000000..c0aa7df67 --- /dev/null +++ b/trunk/research/console/en_index.html @@ -0,0 +1,68 @@ + + + + + SRS控制台 + + + + + + + + + + + + + + + +
+
+
+ + {{log.level}}: {{log.msg}} +
+
+ + + + diff --git a/trunk/research/console/index.html b/trunk/research/console/index.html new file mode 100644 index 000000000..3b90af64e --- /dev/null +++ b/trunk/research/console/index.html @@ -0,0 +1,12 @@ + + + + + SrsConsole + + + + + diff --git a/trunk/research/console/js/3rdparty/angular-resource.js b/trunk/research/console/js/3rdparty/angular-resource.js new file mode 100644 index 000000000..712b32ead --- /dev/null +++ b/trunk/research/console/js/3rdparty/angular-resource.js @@ -0,0 +1,610 @@ +/** + * @license AngularJS v1.2.17 + * (c) 2010-2014 Google, Inc. http://angularjs.org + * License: MIT + */ +(function(window, angular, undefined) {'use strict'; + +var $resourceMinErr = angular.$$minErr('$resource'); + +// Helper functions and regex to lookup a dotted path on an object +// stopping at undefined/null. The path must be composed of ASCII +// identifiers (just like $parse) +var MEMBER_NAME_REGEX = /^(\.[a-zA-Z_$][0-9a-zA-Z_$]*)+$/; + +function isValidDottedPath(path) { + return (path != null && path !== '' && path !== 'hasOwnProperty' && + MEMBER_NAME_REGEX.test('.' + path)); +} + +function lookupDottedPath(obj, path) { + if (!isValidDottedPath(path)) { + throw $resourceMinErr('badmember', 'Dotted member path "@{0}" is invalid.', path); + } + var keys = path.split('.'); + for (var i = 0, ii = keys.length; i < ii && obj !== undefined; i++) { + var key = keys[i]; + obj = (obj !== null) ? obj[key] : undefined; + } + return obj; +} + +/** + * Create a shallow copy of an object and clear other fields from the destination + */ +function shallowClearAndCopy(src, dst) { + dst = dst || {}; + + angular.forEach(dst, function(value, key){ + delete dst[key]; + }); + + for (var key in src) { + if (src.hasOwnProperty(key) && !(key.charAt(0) === '$' && key.charAt(1) === '$')) { + dst[key] = src[key]; + } + } + + return dst; +} + +/** + * @ngdoc module + * @name ngResource + * @description + * + * # ngResource + * + * The `ngResource` module provides interaction support with RESTful services + * via the $resource service. + * + * + *
+ * + * See {@link ngResource.$resource `$resource`} for usage. + */ + +/** + * @ngdoc service + * @name $resource + * @requires $http + * + * @description + * A factory which creates a resource object that lets you interact with + * [RESTful](http://en.wikipedia.org/wiki/Representational_State_Transfer) server-side data sources. + * + * The returned resource object has action methods which provide high-level behaviors without + * the need to interact with the low level {@link ng.$http $http} service. + * + * Requires the {@link ngResource `ngResource`} module to be installed. + * + * @param {string} url A parametrized URL template with parameters prefixed by `:` as in + * `/user/:username`. If you are using a URL with a port number (e.g. + * `http://example.com:8080/api`), it will be respected. + * + * If you are using a url with a suffix, just add the suffix, like this: + * `$resource('http://example.com/resource.json')` or `$resource('http://example.com/:id.json')` + * or even `$resource('http://example.com/resource/:resource_id.:format')` + * If the parameter before the suffix is empty, :resource_id in this case, then the `/.` will be + * collapsed down to a single `.`. If you need this sequence to appear and not collapse then you + * can escape it with `/\.`. + * + * @param {Object=} paramDefaults Default values for `url` parameters. These can be overridden in + * `actions` methods. If any of the parameter value is a function, it will be executed every time + * when a param value needs to be obtained for a request (unless the param was overridden). + * + * Each key value in the parameter object is first bound to url template if present and then any + * excess keys are appended to the url search query after the `?`. + * + * Given a template `/path/:verb` and parameter `{verb:'greet', salutation:'Hello'}` results in + * URL `/path/greet?salutation=Hello`. + * + * If the parameter value is prefixed with `@` then the value of that parameter will be taken + * from the corresponding key on the data object (useful for non-GET operations). + * + * @param {Object.=} actions Hash with declaration of custom action that should extend + * the default set of resource actions. The declaration should be created in the format of {@link + * ng.$http#usage_parameters $http.config}: + * + * {action1: {method:?, params:?, isArray:?, headers:?, ...}, + * action2: {method:?, params:?, isArray:?, headers:?, ...}, + * ...} + * + * Where: + * + * - **`action`** – {string} – The name of action. This name becomes the name of the method on + * your resource object. + * - **`method`** – {string} – HTTP request method. Valid methods are: `GET`, `POST`, `PUT`, + * `DELETE`, and `JSONP`. + * - **`params`** – {Object=} – Optional set of pre-bound parameters for this action. If any of + * the parameter value is a function, it will be executed every time when a param value needs to + * be obtained for a request (unless the param was overridden). + * - **`url`** – {string} – action specific `url` override. The url templating is supported just + * like for the resource-level urls. + * - **`isArray`** – {boolean=} – If true then the returned object for this action is an array, + * see `returns` section. + * - **`transformRequest`** – + * `{function(data, headersGetter)|Array.}` – + * transform function or an array of such functions. The transform function takes the http + * request body and headers and returns its transformed (typically serialized) version. + * - **`transformResponse`** – + * `{function(data, headersGetter)|Array.}` – + * transform function or an array of such functions. The transform function takes the http + * response body and headers and returns its transformed (typically deserialized) version. + * - **`cache`** – `{boolean|Cache}` – If true, a default $http cache will be used to cache the + * GET request, otherwise if a cache instance built with + * {@link ng.$cacheFactory $cacheFactory}, this cache will be used for + * caching. + * - **`timeout`** – `{number|Promise}` – timeout in milliseconds, or {@link ng.$q promise} that + * should abort the request when resolved. + * - **`withCredentials`** - `{boolean}` - whether to set the `withCredentials` flag on the + * XHR object. See + * [requests with credentials](https://developer.mozilla.org/en/http_access_control#section_5) + * for more information. + * - **`responseType`** - `{string}` - see + * [requestType](https://developer.mozilla.org/en-US/docs/DOM/XMLHttpRequest#responseType). + * - **`interceptor`** - `{Object=}` - The interceptor object has two optional methods - + * `response` and `responseError`. Both `response` and `responseError` interceptors get called + * with `http response` object. See {@link ng.$http $http interceptors}. + * + * @returns {Object} A resource "class" object with methods for the default set of resource actions + * optionally extended with custom `actions`. The default set contains these actions: + * ```js + * { 'get': {method:'GET'}, + * 'save': {method:'POST'}, + * 'query': {method:'GET', isArray:true}, + * 'remove': {method:'DELETE'}, + * 'delete': {method:'DELETE'} }; + * ``` + * + * Calling these methods invoke an {@link ng.$http} with the specified http method, + * destination and parameters. When the data is returned from the server then the object is an + * instance of the resource class. The actions `save`, `remove` and `delete` are available on it + * as methods with the `$` prefix. This allows you to easily perform CRUD operations (create, + * read, update, delete) on server-side data like this: + * ```js + * var User = $resource('/user/:userId', {userId:'@id'}); + * var user = User.get({userId:123}, function() { + * user.abc = true; + * user.$save(); + * }); + * ``` + * + * It is important to realize that invoking a $resource object method immediately returns an + * empty reference (object or array depending on `isArray`). Once the data is returned from the + * server the existing reference is populated with the actual data. This is a useful trick since + * usually the resource is assigned to a model which is then rendered by the view. Having an empty + * object results in no rendering, once the data arrives from the server then the object is + * populated with the data and the view automatically re-renders itself showing the new data. This + * means that in most cases one never has to write a callback function for the action methods. + * + * The action methods on the class object or instance object can be invoked with the following + * parameters: + * + * - HTTP GET "class" actions: `Resource.action([parameters], [success], [error])` + * - non-GET "class" actions: `Resource.action([parameters], postData, [success], [error])` + * - non-GET instance actions: `instance.$action([parameters], [success], [error])` + * + * Success callback is called with (value, responseHeaders) arguments. Error callback is called + * with (httpResponse) argument. + * + * Class actions return empty instance (with additional properties below). + * Instance actions return promise of the action. + * + * The Resource instances and collection have these additional properties: + * + * - `$promise`: the {@link ng.$q promise} of the original server interaction that created this + * instance or collection. + * + * On success, the promise is resolved with the same resource instance or collection object, + * updated with data from server. This makes it easy to use in + * {@link ngRoute.$routeProvider resolve section of $routeProvider.when()} to defer view + * rendering until the resource(s) are loaded. + * + * On failure, the promise is resolved with the {@link ng.$http http response} object, without + * the `resource` property. + * + * If an interceptor object was provided, the promise will instead be resolved with the value + * returned by the interceptor. + * + * - `$resolved`: `true` after first server interaction is completed (either with success or + * rejection), `false` before that. Knowing if the Resource has been resolved is useful in + * data-binding. + * + * @example + * + * # Credit card resource + * + * ```js + // Define CreditCard class + var CreditCard = $resource('/user/:userId/card/:cardId', + {userId:123, cardId:'@id'}, { + charge: {method:'POST', params:{charge:true}} + }); + + // We can retrieve a collection from the server + var cards = CreditCard.query(function() { + // GET: /user/123/card + // server returns: [ {id:456, number:'1234', name:'Smith'} ]; + + var card = cards[0]; + // each item is an instance of CreditCard + expect(card instanceof CreditCard).toEqual(true); + card.name = "J. Smith"; + // non GET methods are mapped onto the instances + card.$save(); + // POST: /user/123/card/456 {id:456, number:'1234', name:'J. Smith'} + // server returns: {id:456, number:'1234', name: 'J. Smith'}; + + // our custom method is mapped as well. + card.$charge({amount:9.99}); + // POST: /user/123/card/456?amount=9.99&charge=true {id:456, number:'1234', name:'J. Smith'} + }); + + // we can create an instance as well + var newCard = new CreditCard({number:'0123'}); + newCard.name = "Mike Smith"; + newCard.$save(); + // POST: /user/123/card {number:'0123', name:'Mike Smith'} + // server returns: {id:789, number:'0123', name: 'Mike Smith'}; + expect(newCard.id).toEqual(789); + * ``` + * + * The object returned from this function execution is a resource "class" which has "static" method + * for each action in the definition. + * + * Calling these methods invoke `$http` on the `url` template with the given `method`, `params` and + * `headers`. + * When the data is returned from the server then the object is an instance of the resource type and + * all of the non-GET methods are available with `$` prefix. This allows you to easily support CRUD + * operations (create, read, update, delete) on server-side data. + + ```js + var User = $resource('/user/:userId', {userId:'@id'}); + User.get({userId:123}, function(user) { + user.abc = true; + user.$save(); + }); + ``` + * + * It's worth noting that the success callback for `get`, `query` and other methods gets passed + * in the response that came from the server as well as $http header getter function, so one + * could rewrite the above example and get access to http headers as: + * + ```js + var User = $resource('/user/:userId', {userId:'@id'}); + User.get({userId:123}, function(u, getResponseHeaders){ + u.abc = true; + u.$save(function(u, putResponseHeaders) { + //u => saved user object + //putResponseHeaders => $http header getter + }); + }); + ``` + * + * You can also access the raw `$http` promise via the `$promise` property on the object returned + * + ``` + var User = $resource('/user/:userId', {userId:'@id'}); + User.get({userId:123}) + .$promise.then(function(user) { + $scope.user = user; + }); + ``` + + * # Creating a custom 'PUT' request + * In this example we create a custom method on our resource to make a PUT request + * ```js + * var app = angular.module('app', ['ngResource', 'ngRoute']); + * + * // Some APIs expect a PUT request in the format URL/object/ID + * // Here we are creating an 'update' method + * app.factory('Notes', ['$resource', function($resource) { + * return $resource('/notes/:id', null, + * { + * 'update': { method:'PUT' } + * }); + * }]); + * + * // In our controller we get the ID from the URL using ngRoute and $routeParams + * // We pass in $routeParams and our Notes factory along with $scope + * app.controller('NotesCtrl', ['$scope', '$routeParams', 'Notes', + function($scope, $routeParams, Notes) { + * // First get a note object from the factory + * var note = Notes.get({ id:$routeParams.id }); + * $id = note.id; + * + * // Now call update passing in the ID first then the object you are updating + * Notes.update({ id:$id }, note); + * + * // This will PUT /notes/ID with the note object in the request payload + * }]); + * ``` + */ +angular.module('ngResource', ['ng']). + factory('$resource', ['$http', '$q', function($http, $q) { + + var DEFAULT_ACTIONS = { + 'get': {method:'GET'}, + 'save': {method:'POST'}, + 'query': {method:'GET', isArray:true}, + 'remove': {method:'DELETE'}, + 'delete': {method:'DELETE'} + }; + var noop = angular.noop, + forEach = angular.forEach, + extend = angular.extend, + copy = angular.copy, + isFunction = angular.isFunction; + + /** + * We need our custom method because encodeURIComponent is too aggressive and doesn't follow + * http://www.ietf.org/rfc/rfc3986.txt with regards to the character set (pchar) allowed in path + * segments: + * segment = *pchar + * pchar = unreserved / pct-encoded / sub-delims / ":" / "@" + * pct-encoded = "%" HEXDIG HEXDIG + * unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~" + * sub-delims = "!" / "$" / "&" / "'" / "(" / ")" + * / "*" / "+" / "," / ";" / "=" + */ + function encodeUriSegment(val) { + return encodeUriQuery(val, true). + replace(/%26/gi, '&'). + replace(/%3D/gi, '='). + replace(/%2B/gi, '+'); + } + + + /** + * This method is intended for encoding *key* or *value* parts of query component. We need a + * custom method because encodeURIComponent is too aggressive and encodes stuff that doesn't + * have to be encoded per http://tools.ietf.org/html/rfc3986: + * query = *( pchar / "/" / "?" ) + * pchar = unreserved / pct-encoded / sub-delims / ":" / "@" + * unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~" + * pct-encoded = "%" HEXDIG HEXDIG + * sub-delims = "!" / "$" / "&" / "'" / "(" / ")" + * / "*" / "+" / "," / ";" / "=" + */ + function encodeUriQuery(val, pctEncodeSpaces) { + return encodeURIComponent(val). + replace(/%40/gi, '@'). + replace(/%3A/gi, ':'). + replace(/%24/g, '$'). + replace(/%2C/gi, ','). + replace(/%20/g, (pctEncodeSpaces ? '%20' : '+')); + } + + function Route(template, defaults) { + this.template = template; + this.defaults = defaults || {}; + this.urlParams = {}; + } + + Route.prototype = { + setUrlParams: function(config, params, actionUrl) { + var self = this, + url = actionUrl || self.template, + val, + encodedVal; + + var urlParams = self.urlParams = {}; + forEach(url.split(/\W/), function(param){ + if (param === 'hasOwnProperty') { + throw $resourceMinErr('badname', "hasOwnProperty is not a valid parameter name."); + } + if (!(new RegExp("^\\d+$").test(param)) && param && + (new RegExp("(^|[^\\\\]):" + param + "(\\W|$)").test(url))) { + urlParams[param] = true; + } + }); + url = url.replace(/\\:/g, ':'); + + params = params || {}; + forEach(self.urlParams, function(_, urlParam){ + val = params.hasOwnProperty(urlParam) ? params[urlParam] : self.defaults[urlParam]; + if (angular.isDefined(val) && val !== null) { + encodedVal = encodeUriSegment(val); + url = url.replace(new RegExp(":" + urlParam + "(\\W|$)", "g"), function(match, p1) { + return encodedVal + p1; + }); + } else { + url = url.replace(new RegExp("(\/?):" + urlParam + "(\\W|$)", "g"), function(match, + leadingSlashes, tail) { + if (tail.charAt(0) == '/') { + return tail; + } else { + return leadingSlashes + tail; + } + }); + } + }); + + // strip trailing slashes and set the url + url = url.replace(/\/+$/, '') || '/'; + // then replace collapse `/.` if found in the last URL path segment before the query + // E.g. `http://url.com/id./format?q=x` becomes `http://url.com/id.format?q=x` + url = url.replace(/\/\.(?=\w+($|\?))/, '.'); + // replace escaped `/\.` with `/.` + config.url = url.replace(/\/\\\./, '/.'); + + + // set params - delegate param encoding to $http + forEach(params, function(value, key){ + if (!self.urlParams[key]) { + config.params = config.params || {}; + config.params[key] = value; + } + }); + } + }; + + + function resourceFactory(url, paramDefaults, actions) { + var route = new Route(url); + + actions = extend({}, DEFAULT_ACTIONS, actions); + + function extractParams(data, actionParams){ + var ids = {}; + actionParams = extend({}, paramDefaults, actionParams); + forEach(actionParams, function(value, key){ + if (isFunction(value)) { value = value(); } + ids[key] = value && value.charAt && value.charAt(0) == '@' ? + lookupDottedPath(data, value.substr(1)) : value; + }); + return ids; + } + + function defaultResponseInterceptor(response) { + return response.resource; + } + + function Resource(value){ + shallowClearAndCopy(value || {}, this); + } + + forEach(actions, function(action, name) { + var hasBody = /^(POST|PUT|PATCH)$/i.test(action.method); + + Resource[name] = function(a1, a2, a3, a4) { + var params = {}, data, success, error; + + /* jshint -W086 */ /* (purposefully fall through case statements) */ + switch(arguments.length) { + case 4: + error = a4; + success = a3; + //fallthrough + case 3: + case 2: + if (isFunction(a2)) { + if (isFunction(a1)) { + success = a1; + error = a2; + break; + } + + success = a2; + error = a3; + //fallthrough + } else { + params = a1; + data = a2; + success = a3; + break; + } + case 1: + if (isFunction(a1)) success = a1; + else if (hasBody) data = a1; + else params = a1; + break; + case 0: break; + default: + throw $resourceMinErr('badargs', + "Expected up to 4 arguments [params, data, success, error], got {0} arguments", + arguments.length); + } + /* jshint +W086 */ /* (purposefully fall through case statements) */ + + var isInstanceCall = this instanceof Resource; + var value = isInstanceCall ? data : (action.isArray ? [] : new Resource(data)); + var httpConfig = {}; + var responseInterceptor = action.interceptor && action.interceptor.response || + defaultResponseInterceptor; + var responseErrorInterceptor = action.interceptor && action.interceptor.responseError || + undefined; + + forEach(action, function(value, key) { + if (key != 'params' && key != 'isArray' && key != 'interceptor') { + httpConfig[key] = copy(value); + } + }); + + if (hasBody) httpConfig.data = data; + route.setUrlParams(httpConfig, + extend({}, extractParams(data, action.params || {}), params), + action.url); + + var promise = $http(httpConfig).then(function(response) { + var data = response.data, + promise = value.$promise; + + if (data) { + // Need to convert action.isArray to boolean in case it is undefined + // jshint -W018 + if (angular.isArray(data) !== (!!action.isArray)) { + throw $resourceMinErr('badcfg', 'Error in resource configuration. Expected ' + + 'response to contain an {0} but got an {1}', + action.isArray?'array':'object', angular.isArray(data)?'array':'object'); + } + // jshint +W018 + if (action.isArray) { + value.length = 0; + forEach(data, function(item) { + value.push(new Resource(item)); + }); + } else { + shallowClearAndCopy(data, value); + value.$promise = promise; + } + } + + value.$resolved = true; + + response.resource = value; + + return response; + }, function(response) { + value.$resolved = true; + + (error||noop)(response); + + return $q.reject(response); + }); + + promise = promise.then( + function(response) { + var value = responseInterceptor(response); + (success||noop)(value, response.headers); + return value; + }, + responseErrorInterceptor); + + if (!isInstanceCall) { + // we are creating instance / collection + // - set the initial promise + // - return the instance / collection + value.$promise = promise; + value.$resolved = false; + + return value; + } + + // instance call + return promise; + }; + + + Resource.prototype['$' + name] = function(params, success, error) { + if (isFunction(params)) { + error = success; success = params; params = {}; + } + var result = Resource[name].call(this, params, this, success, error); + return result.$promise || result; + }; + }); + + Resource.bind = function(additionalParamDefaults){ + return resourceFactory(url, extend({}, paramDefaults, additionalParamDefaults), actions); + }; + + return Resource; + } + + return resourceFactory; + }]); + + +})(window, window.angular); diff --git a/trunk/research/console/js/3rdparty/angular-resource.min.js b/trunk/research/console/js/3rdparty/angular-resource.min.js new file mode 100644 index 000000000..badd75458 --- /dev/null +++ b/trunk/research/console/js/3rdparty/angular-resource.min.js @@ -0,0 +1,13 @@ +/* + AngularJS v1.2.17 + (c) 2010-2014 Google, Inc. http://angularjs.org + License: MIT +*/ +(function(H,a,A){'use strict';function D(p,g){g=g||{};a.forEach(g,function(a,c){delete g[c]});for(var c in p)!p.hasOwnProperty(c)||"$"===c.charAt(0)&&"$"===c.charAt(1)||(g[c]=p[c]);return g}var v=a.$$minErr("$resource"),C=/^(\.[a-zA-Z_$][0-9a-zA-Z_$]*)+$/;a.module("ngResource",["ng"]).factory("$resource",["$http","$q",function(p,g){function c(a,c){this.template=a;this.defaults=c||{};this.urlParams={}}function t(n,w,l){function r(h,d){var e={};d=x({},w,d);s(d,function(b,d){u(b)&&(b=b());var k;if(b&& +b.charAt&&"@"==b.charAt(0)){k=h;var a=b.substr(1);if(null==a||""===a||"hasOwnProperty"===a||!C.test("."+a))throw v("badmember",a);for(var a=a.split("."),f=0,c=a.length;f + */ + /* global -ngRouteModule */ +var ngRouteModule = angular.module('ngRoute', ['ng']). + provider('$route', $RouteProvider); + +/** + * @ngdoc provider + * @name $routeProvider + * @kind function + * + * @description + * + * Used for configuring routes. + * + * ## Example + * See {@link ngRoute.$route#example $route} for an example of configuring and using `ngRoute`. + * + * ## Dependencies + * Requires the {@link ngRoute `ngRoute`} module to be installed. + */ +function $RouteProvider(){ + function inherit(parent, extra) { + return angular.extend(new (angular.extend(function() {}, {prototype:parent}))(), extra); + } + + var routes = {}; + + /** + * @ngdoc method + * @name $routeProvider#when + * + * @param {string} path Route path (matched against `$location.path`). If `$location.path` + * contains redundant trailing slash or is missing one, the route will still match and the + * `$location.path` will be updated to add or drop the trailing slash to exactly match the + * route definition. + * + * * `path` can contain named groups starting with a colon: e.g. `:name`. All characters up + * to the next slash are matched and stored in `$routeParams` under the given `name` + * when the route matches. + * * `path` can contain named groups starting with a colon and ending with a star: + * e.g.`:name*`. All characters are eagerly stored in `$routeParams` under the given `name` + * when the route matches. + * * `path` can contain optional named groups with a question mark: e.g.`:name?`. + * + * For example, routes like `/color/:color/largecode/:largecode*\/edit` will match + * `/color/brown/largecode/code/with/slashes/edit` and extract: + * + * * `color: brown` + * * `largecode: code/with/slashes`. + * + * + * @param {Object} route Mapping information to be assigned to `$route.current` on route + * match. + * + * Object properties: + * + * - `controller` – `{(string|function()=}` – Controller fn that should be associated with + * newly created scope or the name of a {@link angular.Module#controller registered + * controller} if passed as a string. + * - `controllerAs` – `{string=}` – A controller alias name. If present the controller will be + * published to scope under the `controllerAs` name. + * - `template` – `{string=|function()=}` – html template as a string or a function that + * returns an html template as a string which should be used by {@link + * ngRoute.directive:ngView ngView} or {@link ng.directive:ngInclude ngInclude} directives. + * This property takes precedence over `templateUrl`. + * + * If `template` is a function, it will be called with the following parameters: + * + * - `{Array.}` - route parameters extracted from the current + * `$location.path()` by applying the current route + * + * - `templateUrl` – `{string=|function()=}` – path or function that returns a path to an html + * template that should be used by {@link ngRoute.directive:ngView ngView}. + * + * If `templateUrl` is a function, it will be called with the following parameters: + * + * - `{Array.}` - route parameters extracted from the current + * `$location.path()` by applying the current route + * + * - `resolve` - `{Object.=}` - An optional map of dependencies which should + * be injected into the controller. If any of these dependencies are promises, the router + * will wait for them all to be resolved or one to be rejected before the controller is + * instantiated. + * If all the promises are resolved successfully, the values of the resolved promises are + * injected and {@link ngRoute.$route#$routeChangeSuccess $routeChangeSuccess} event is + * fired. If any of the promises are rejected the + * {@link ngRoute.$route#$routeChangeError $routeChangeError} event is fired. The map object + * is: + * + * - `key` – `{string}`: a name of a dependency to be injected into the controller. + * - `factory` - `{string|function}`: If `string` then it is an alias for a service. + * Otherwise if function, then it is {@link auto.$injector#invoke injected} + * and the return value is treated as the dependency. If the result is a promise, it is + * resolved before its value is injected into the controller. Be aware that + * `ngRoute.$routeParams` will still refer to the previous route within these resolve + * functions. Use `$route.current.params` to access the new route parameters, instead. + * + * - `redirectTo` – {(string|function())=} – value to update + * {@link ng.$location $location} path with and trigger route redirection. + * + * If `redirectTo` is a function, it will be called with the following parameters: + * + * - `{Object.}` - route parameters extracted from the current + * `$location.path()` by applying the current route templateUrl. + * - `{string}` - current `$location.path()` + * - `{Object}` - current `$location.search()` + * + * The custom `redirectTo` function is expected to return a string which will be used + * to update `$location.path()` and `$location.search()`. + * + * - `[reloadOnSearch=true]` - {boolean=} - reload route when only `$location.search()` + * or `$location.hash()` changes. + * + * If the option is set to `false` and url in the browser changes, then + * `$routeUpdate` event is broadcasted on the root scope. + * + * - `[caseInsensitiveMatch=false]` - {boolean=} - match routes without being case sensitive + * + * If the option is set to `true`, then the particular route can be matched without being + * case sensitive + * + * @returns {Object} self + * + * @description + * Adds a new route definition to the `$route` service. + */ + this.when = function(path, route) { + routes[path] = angular.extend( + {reloadOnSearch: true}, + route, + path && pathRegExp(path, route) + ); + + // create redirection for trailing slashes + if (path) { + var redirectPath = (path[path.length-1] == '/') + ? path.substr(0, path.length-1) + : path +'/'; + + routes[redirectPath] = angular.extend( + {redirectTo: path}, + pathRegExp(redirectPath, route) + ); + } + + return this; + }; + + /** + * @param path {string} path + * @param opts {Object} options + * @return {?Object} + * + * @description + * Normalizes the given path, returning a regular expression + * and the original path. + * + * Inspired by pathRexp in visionmedia/express/lib/utils.js. + */ + function pathRegExp(path, opts) { + var insensitive = opts.caseInsensitiveMatch, + ret = { + originalPath: path, + regexp: path + }, + keys = ret.keys = []; + + path = path + .replace(/([().])/g, '\\$1') + .replace(/(\/)?:(\w+)([\?\*])?/g, function(_, slash, key, option){ + var optional = option === '?' ? option : null; + var star = option === '*' ? option : null; + keys.push({ name: key, optional: !!optional }); + slash = slash || ''; + return '' + + (optional ? '' : slash) + + '(?:' + + (optional ? slash : '') + + (star && '(.+?)' || '([^/]+)') + + (optional || '') + + ')' + + (optional || ''); + }) + .replace(/([\/$\*])/g, '\\$1'); + + ret.regexp = new RegExp('^' + path + '$', insensitive ? 'i' : ''); + return ret; + } + + /** + * @ngdoc method + * @name $routeProvider#otherwise + * + * @description + * Sets route definition that will be used on route change when no other route definition + * is matched. + * + * @param {Object} params Mapping information to be assigned to `$route.current`. + * @returns {Object} self + */ + this.otherwise = function(params) { + this.when(null, params); + return this; + }; + + + this.$get = ['$rootScope', + '$location', + '$routeParams', + '$q', + '$injector', + '$http', + '$templateCache', + '$sce', + function($rootScope, $location, $routeParams, $q, $injector, $http, $templateCache, $sce) { + + /** + * @ngdoc service + * @name $route + * @requires $location + * @requires $routeParams + * + * @property {Object} current Reference to the current route definition. + * The route definition contains: + * + * - `controller`: The controller constructor as define in route definition. + * - `locals`: A map of locals which is used by {@link ng.$controller $controller} service for + * controller instantiation. The `locals` contain + * the resolved values of the `resolve` map. Additionally the `locals` also contain: + * + * - `$scope` - The current route scope. + * - `$template` - The current route template HTML. + * + * @property {Object} routes Object with all route configuration Objects as its properties. + * + * @description + * `$route` is used for deep-linking URLs to controllers and views (HTML partials). + * It watches `$location.url()` and tries to map the path to an existing route definition. + * + * Requires the {@link ngRoute `ngRoute`} module to be installed. + * + * You can define routes through {@link ngRoute.$routeProvider $routeProvider}'s API. + * + * The `$route` service is typically used in conjunction with the + * {@link ngRoute.directive:ngView `ngView`} directive and the + * {@link ngRoute.$routeParams `$routeParams`} service. + * + * @example + * This example shows how changing the URL hash causes the `$route` to match a route against the + * URL, and the `ngView` pulls in the partial. + * + * Note that this example is using {@link ng.directive:script inlined templates} + * to get it working on jsfiddle as well. + * + * + * + *
+ * Choose: + * Moby | + * Moby: Ch1 | + * Gatsby | + * Gatsby: Ch4 | + * Scarlet Letter
+ * + *
+ * + *
+ * + *
$location.path() = {{$location.path()}}
+ *
$route.current.templateUrl = {{$route.current.templateUrl}}
+ *
$route.current.params = {{$route.current.params}}
+ *
$route.current.scope.name = {{$route.current.scope.name}}
+ *
$routeParams = {{$routeParams}}
+ *
+ *
+ * + * + * controller: {{name}}
+ * Book Id: {{params.bookId}}
+ *
+ * + * + * controller: {{name}}
+ * Book Id: {{params.bookId}}
+ * Chapter Id: {{params.chapterId}} + *
+ * + * + * angular.module('ngRouteExample', ['ngRoute']) + * + * .controller('MainController', function($scope, $route, $routeParams, $location) { + * $scope.$route = $route; + * $scope.$location = $location; + * $scope.$routeParams = $routeParams; + * }) + * + * .controller('BookController', function($scope, $routeParams) { + * $scope.name = "BookController"; + * $scope.params = $routeParams; + * }) + * + * .controller('ChapterController', function($scope, $routeParams) { + * $scope.name = "ChapterController"; + * $scope.params = $routeParams; + * }) + * + * .config(function($routeProvider, $locationProvider) { + * $routeProvider + * .when('/Book/:bookId', { + * templateUrl: 'book.html', + * controller: 'BookController', + * resolve: { + * // I will cause a 1 second delay + * delay: function($q, $timeout) { + * var delay = $q.defer(); + * $timeout(delay.resolve, 1000); + * return delay.promise; + * } + * } + * }) + * .when('/Book/:bookId/ch/:chapterId', { + * templateUrl: 'chapter.html', + * controller: 'ChapterController' + * }); + * + * // configure html5 to get links working on jsfiddle + * $locationProvider.html5Mode(true); + * }); + * + * + * + * + * it('should load and compile correct template', function() { + * element(by.linkText('Moby: Ch1')).click(); + * var content = element(by.css('[ng-view]')).getText(); + * expect(content).toMatch(/controller\: ChapterController/); + * expect(content).toMatch(/Book Id\: Moby/); + * expect(content).toMatch(/Chapter Id\: 1/); + * + * element(by.partialLinkText('Scarlet')).click(); + * + * content = element(by.css('[ng-view]')).getText(); + * expect(content).toMatch(/controller\: BookController/); + * expect(content).toMatch(/Book Id\: Scarlet/); + * }); + * + *
+ */ + + /** + * @ngdoc event + * @name $route#$routeChangeStart + * @eventType broadcast on root scope + * @description + * Broadcasted before a route change. At this point the route services starts + * resolving all of the dependencies needed for the route change to occur. + * Typically this involves fetching the view template as well as any dependencies + * defined in `resolve` route property. Once all of the dependencies are resolved + * `$routeChangeSuccess` is fired. + * + * @param {Object} angularEvent Synthetic event object. + * @param {Route} next Future route information. + * @param {Route} current Current route information. + */ + + /** + * @ngdoc event + * @name $route#$routeChangeSuccess + * @eventType broadcast on root scope + * @description + * Broadcasted after a route dependencies are resolved. + * {@link ngRoute.directive:ngView ngView} listens for the directive + * to instantiate the controller and render the view. + * + * @param {Object} angularEvent Synthetic event object. + * @param {Route} current Current route information. + * @param {Route|Undefined} previous Previous route information, or undefined if current is + * first route entered. + */ + + /** + * @ngdoc event + * @name $route#$routeChangeError + * @eventType broadcast on root scope + * @description + * Broadcasted if any of the resolve promises are rejected. + * + * @param {Object} angularEvent Synthetic event object + * @param {Route} current Current route information. + * @param {Route} previous Previous route information. + * @param {Route} rejection Rejection of the promise. Usually the error of the failed promise. + */ + + /** + * @ngdoc event + * @name $route#$routeUpdate + * @eventType broadcast on root scope + * @description + * + * The `reloadOnSearch` property has been set to false, and we are reusing the same + * instance of the Controller. + */ + + var forceReload = false, + $route = { + routes: routes, + + /** + * @ngdoc method + * @name $route#reload + * + * @description + * Causes `$route` service to reload the current route even if + * {@link ng.$location $location} hasn't changed. + * + * As a result of that, {@link ngRoute.directive:ngView ngView} + * creates new scope, reinstantiates the controller. + */ + reload: function() { + forceReload = true; + $rootScope.$evalAsync(updateRoute); + } + }; + + $rootScope.$on('$locationChangeSuccess', updateRoute); + + return $route; + + ///////////////////////////////////////////////////// + + /** + * @param on {string} current url + * @param route {Object} route regexp to match the url against + * @return {?Object} + * + * @description + * Check if the route matches the current url. + * + * Inspired by match in + * visionmedia/express/lib/router/router.js. + */ + function switchRouteMatcher(on, route) { + var keys = route.keys, + params = {}; + + if (!route.regexp) return null; + + var m = route.regexp.exec(on); + if (!m) return null; + + for (var i = 1, len = m.length; i < len; ++i) { + var key = keys[i - 1]; + + var val = 'string' == typeof m[i] + ? decodeURIComponent(m[i]) + : m[i]; + + if (key && val) { + params[key.name] = val; + } + } + return params; + } + + function updateRoute() { + var next = parseRoute(), + last = $route.current; + + if (next && last && next.$$route === last.$$route + && angular.equals(next.pathParams, last.pathParams) + && !next.reloadOnSearch && !forceReload) { + last.params = next.params; + angular.copy(last.params, $routeParams); + $rootScope.$broadcast('$routeUpdate', last); + } else if (next || last) { + forceReload = false; + $rootScope.$broadcast('$routeChangeStart', next, last); + $route.current = next; + if (next) { + if (next.redirectTo) { + if (angular.isString(next.redirectTo)) { + $location.path(interpolate(next.redirectTo, next.params)).search(next.params) + .replace(); + } else { + $location.url(next.redirectTo(next.pathParams, $location.path(), $location.search())) + .replace(); + } + } + } + + $q.when(next). + then(function() { + if (next) { + var locals = angular.extend({}, next.resolve), + template, templateUrl; + + angular.forEach(locals, function(value, key) { + locals[key] = angular.isString(value) ? + $injector.get(value) : $injector.invoke(value); + }); + + if (angular.isDefined(template = next.template)) { + if (angular.isFunction(template)) { + template = template(next.params); + } + } else if (angular.isDefined(templateUrl = next.templateUrl)) { + if (angular.isFunction(templateUrl)) { + templateUrl = templateUrl(next.params); + } + templateUrl = $sce.getTrustedResourceUrl(templateUrl); + if (angular.isDefined(templateUrl)) { + next.loadedTemplateUrl = templateUrl; + template = $http.get(templateUrl, {cache: $templateCache}). + then(function(response) { return response.data; }); + } + } + if (angular.isDefined(template)) { + locals['$template'] = template; + } + return $q.all(locals); + } + }). + // after route change + then(function(locals) { + if (next == $route.current) { + if (next) { + next.locals = locals; + angular.copy(next.params, $routeParams); + } + $rootScope.$broadcast('$routeChangeSuccess', next, last); + } + }, function(error) { + if (next == $route.current) { + $rootScope.$broadcast('$routeChangeError', next, last, error); + } + }); + } + } + + + /** + * @returns {Object} the current active route, by matching it against the URL + */ + function parseRoute() { + // Match a route + var params, match; + angular.forEach(routes, function(route, path) { + if (!match && (params = switchRouteMatcher($location.path(), route))) { + match = inherit(route, { + params: angular.extend({}, $location.search(), params), + pathParams: params}); + match.$$route = route; + } + }); + // No route matched; fallback to "otherwise" route + return match || routes[null] && inherit(routes[null], {params: {}, pathParams:{}}); + } + + /** + * @returns {string} interpolation of the redirect path with the parameters + */ + function interpolate(string, params) { + var result = []; + angular.forEach((string||'').split(':'), function(segment, i) { + if (i === 0) { + result.push(segment); + } else { + var segmentMatch = segment.match(/(\w+)(.*)/); + var key = segmentMatch[1]; + result.push(params[key]); + result.push(segmentMatch[2] || ''); + delete params[key]; + } + }); + return result.join(''); + } + }]; +} + +ngRouteModule.provider('$routeParams', $RouteParamsProvider); + + +/** + * @ngdoc service + * @name $routeParams + * @requires $route + * + * @description + * The `$routeParams` service allows you to retrieve the current set of route parameters. + * + * Requires the {@link ngRoute `ngRoute`} module to be installed. + * + * The route parameters are a combination of {@link ng.$location `$location`}'s + * {@link ng.$location#search `search()`} and {@link ng.$location#path `path()`}. + * The `path` parameters are extracted when the {@link ngRoute.$route `$route`} path is matched. + * + * In case of parameter name collision, `path` params take precedence over `search` params. + * + * The service guarantees that the identity of the `$routeParams` object will remain unchanged + * (but its properties will likely change) even when a route change occurs. + * + * Note that the `$routeParams` are only updated *after* a route change completes successfully. + * This means that you cannot rely on `$routeParams` being correct in route resolve functions. + * Instead you can use `$route.current.params` to access the new route's parameters. + * + * @example + * ```js + * // Given: + * // URL: http://server.com/index.html#/Chapter/1/Section/2?search=moby + * // Route: /Chapter/:chapterId/Section/:sectionId + * // + * // Then + * $routeParams ==> {chapterId:'1', sectionId:'2', search:'moby'} + * ``` + */ +function $RouteParamsProvider() { + this.$get = function() { return {}; }; +} + +ngRouteModule.directive('ngView', ngViewFactory); +ngRouteModule.directive('ngView', ngViewFillContentFactory); + + +/** + * @ngdoc directive + * @name ngView + * @restrict ECA + * + * @description + * # Overview + * `ngView` is a directive that complements the {@link ngRoute.$route $route} service by + * including the rendered template of the current route into the main layout (`index.html`) file. + * Every time the current route changes, the included view changes with it according to the + * configuration of the `$route` service. + * + * Requires the {@link ngRoute `ngRoute`} module to be installed. + * + * @animations + * enter - animation is used to bring new content into the browser. + * leave - animation is used to animate existing content away. + * + * The enter and leave animation occur concurrently. + * + * @scope + * @priority 400 + * @param {string=} onload Expression to evaluate whenever the view updates. + * + * @param {string=} autoscroll Whether `ngView` should call {@link ng.$anchorScroll + * $anchorScroll} to scroll the viewport after the view is updated. + * + * - If the attribute is not set, disable scrolling. + * - If the attribute is set without value, enable scrolling. + * - Otherwise enable scrolling only if the `autoscroll` attribute value evaluated + * as an expression yields a truthy value. + * @example + + +
+ Choose: + Moby | + Moby: Ch1 | + Gatsby | + Gatsby: Ch4 | + Scarlet Letter
+ +
+
+
+
+ +
$location.path() = {{main.$location.path()}}
+
$route.current.templateUrl = {{main.$route.current.templateUrl}}
+
$route.current.params = {{main.$route.current.params}}
+
$route.current.scope.name = {{main.$route.current.scope.name}}
+
$routeParams = {{main.$routeParams}}
+
+
+ + +
+ controller: {{book.name}}
+ Book Id: {{book.params.bookId}}
+
+
+ + +
+ controller: {{chapter.name}}
+ Book Id: {{chapter.params.bookId}}
+ Chapter Id: {{chapter.params.chapterId}} +
+
+ + + .view-animate-container { + position:relative; + height:100px!important; + position:relative; + background:white; + border:1px solid black; + height:40px; + overflow:hidden; + } + + .view-animate { + padding:10px; + } + + .view-animate.ng-enter, .view-animate.ng-leave { + -webkit-transition:all cubic-bezier(0.250, 0.460, 0.450, 0.940) 1.5s; + transition:all cubic-bezier(0.250, 0.460, 0.450, 0.940) 1.5s; + + display:block; + width:100%; + border-left:1px solid black; + + position:absolute; + top:0; + left:0; + right:0; + bottom:0; + padding:10px; + } + + .view-animate.ng-enter { + left:100%; + } + .view-animate.ng-enter.ng-enter-active { + left:0; + } + .view-animate.ng-leave.ng-leave-active { + left:-100%; + } + + + + angular.module('ngViewExample', ['ngRoute', 'ngAnimate']) + .config(['$routeProvider', '$locationProvider', + function($routeProvider, $locationProvider) { + $routeProvider + .when('/Book/:bookId', { + templateUrl: 'book.html', + controller: 'BookCtrl', + controllerAs: 'book' + }) + .when('/Book/:bookId/ch/:chapterId', { + templateUrl: 'chapter.html', + controller: 'ChapterCtrl', + controllerAs: 'chapter' + }); + + // configure html5 to get links working on jsfiddle + $locationProvider.html5Mode(true); + }]) + .controller('MainCtrl', ['$route', '$routeParams', '$location', + function($route, $routeParams, $location) { + this.$route = $route; + this.$location = $location; + this.$routeParams = $routeParams; + }]) + .controller('BookCtrl', ['$routeParams', function($routeParams) { + this.name = "BookCtrl"; + this.params = $routeParams; + }]) + .controller('ChapterCtrl', ['$routeParams', function($routeParams) { + this.name = "ChapterCtrl"; + this.params = $routeParams; + }]); + + + + + it('should load and compile correct template', function() { + element(by.linkText('Moby: Ch1')).click(); + var content = element(by.css('[ng-view]')).getText(); + expect(content).toMatch(/controller\: ChapterCtrl/); + expect(content).toMatch(/Book Id\: Moby/); + expect(content).toMatch(/Chapter Id\: 1/); + + element(by.partialLinkText('Scarlet')).click(); + + content = element(by.css('[ng-view]')).getText(); + expect(content).toMatch(/controller\: BookCtrl/); + expect(content).toMatch(/Book Id\: Scarlet/); + }); + +
+ */ + + +/** + * @ngdoc event + * @name ngView#$viewContentLoaded + * @eventType emit on the current ngView scope + * @description + * Emitted every time the ngView content is reloaded. + */ +ngViewFactory.$inject = ['$route', '$anchorScroll', '$animate']; +function ngViewFactory( $route, $anchorScroll, $animate) { + return { + restrict: 'ECA', + terminal: true, + priority: 400, + transclude: 'element', + link: function(scope, $element, attr, ctrl, $transclude) { + var currentScope, + currentElement, + previousElement, + autoScrollExp = attr.autoscroll, + onloadExp = attr.onload || ''; + + scope.$on('$routeChangeSuccess', update); + update(); + + function cleanupLastView() { + if(previousElement) { + previousElement.remove(); + previousElement = null; + } + if(currentScope) { + currentScope.$destroy(); + currentScope = null; + } + if(currentElement) { + $animate.leave(currentElement, function() { + previousElement = null; + }); + previousElement = currentElement; + currentElement = null; + } + } + + function update() { + var locals = $route.current && $route.current.locals, + template = locals && locals.$template; + + if (angular.isDefined(template)) { + var newScope = scope.$new(); + var current = $route.current; + + // Note: This will also link all children of ng-view that were contained in the original + // html. If that content contains controllers, ... they could pollute/change the scope. + // However, using ng-view on an element with additional content does not make sense... + // Note: We can't remove them in the cloneAttchFn of $transclude as that + // function is called before linking the content, which would apply child + // directives to non existing elements. + var clone = $transclude(newScope, function(clone) { + $animate.enter(clone, null, currentElement || $element, function onNgViewEnter () { + if (angular.isDefined(autoScrollExp) + && (!autoScrollExp || scope.$eval(autoScrollExp))) { + $anchorScroll(); + } + }); + cleanupLastView(); + }); + + currentElement = clone; + currentScope = current.scope = newScope; + currentScope.$emit('$viewContentLoaded'); + currentScope.$eval(onloadExp); + } else { + cleanupLastView(); + } + } + } + }; +} + +// This directive is called during the $transclude call of the first `ngView` directive. +// It will replace and compile the content of the element with the loaded template. +// We need this directive so that the element content is already filled when +// the link function of another directive on the same element as ngView +// is called. +ngViewFillContentFactory.$inject = ['$compile', '$controller', '$route']; +function ngViewFillContentFactory($compile, $controller, $route) { + return { + restrict: 'ECA', + priority: -400, + link: function(scope, $element) { + var current = $route.current, + locals = current.locals; + + $element.html(locals.$template); + + var link = $compile($element.contents()); + + if (current.controller) { + locals.$scope = scope; + var controller = $controller(current.controller, locals); + if (current.controllerAs) { + scope[current.controllerAs] = controller; + } + $element.data('$ngControllerController', controller); + $element.children().data('$ngControllerController', controller); + } + + link(scope); + } + }; +} + + +})(window, window.angular); diff --git a/trunk/research/console/js/3rdparty/angular-route.min.js b/trunk/research/console/js/3rdparty/angular-route.min.js new file mode 100644 index 000000000..bf7ff1a0e --- /dev/null +++ b/trunk/research/console/js/3rdparty/angular-route.min.js @@ -0,0 +1,14 @@ +/* + AngularJS v1.2.17 + (c) 2010-2014 Google, Inc. http://angularjs.org + License: MIT +*/ +(function(n,e,A){'use strict';function x(s,g,k){return{restrict:"ECA",terminal:!0,priority:400,transclude:"element",link:function(a,c,b,f,w){function y(){p&&(p.remove(),p=null);h&&(h.$destroy(),h=null);l&&(k.leave(l,function(){p=null}),p=l,l=null)}function v(){var b=s.current&&s.current.locals;if(e.isDefined(b&&b.$template)){var b=a.$new(),d=s.current;l=w(b,function(d){k.enter(d,null,l||c,function(){!e.isDefined(t)||t&&!a.$eval(t)||g()});y()});h=d.scope=b;h.$emit("$viewContentLoaded");h.$eval(u)}else y()} +var h,l,p,t=b.autoscroll,u=b.onload||"";a.$on("$routeChangeSuccess",v);v()}}}function z(e,g,k){return{restrict:"ECA",priority:-400,link:function(a,c){var b=k.current,f=b.locals;c.html(f.$template);var w=e(c.contents());b.controller&&(f.$scope=a,f=g(b.controller,f),b.controllerAs&&(a[b.controllerAs]=f),c.data("$ngControllerController",f),c.children().data("$ngControllerController",f));w(a)}}}n=e.module("ngRoute",["ng"]).provider("$route",function(){function s(a,c){return e.extend(new (e.extend(function(){}, +{prototype:a})),c)}function g(a,e){var b=e.caseInsensitiveMatch,f={originalPath:a,regexp:a},k=f.keys=[];a=a.replace(/([().])/g,"\\$1").replace(/(\/)?:(\w+)([\?\*])?/g,function(a,e,b,c){a="?"===c?c:null;c="*"===c?c:null;k.push({name:b,optional:!!a});e=e||"";return""+(a?"":e)+"(?:"+(a?e:"")+(c&&"(.+?)"||"([^/]+)")+(a||"")+")"+(a||"")}).replace(/([\/$\*])/g,"\\$1");f.regexp=RegExp("^"+a+"$",b?"i":"");return f}var k={};this.when=function(a,c){k[a]=e.extend({reloadOnSearch:!0},c,a&&g(a,c));if(a){var b= +"/"==a[a.length-1]?a.substr(0,a.length-1):a+"/";k[b]=e.extend({redirectTo:a},g(b,c))}return this};this.otherwise=function(a){this.when(null,a);return this};this.$get=["$rootScope","$location","$routeParams","$q","$injector","$http","$templateCache","$sce",function(a,c,b,f,g,n,v,h){function l(){var d=p(),m=r.current;if(d&&m&&d.$$route===m.$$route&&e.equals(d.pathParams,m.pathParams)&&!d.reloadOnSearch&&!u)m.params=d.params,e.copy(m.params,b),a.$broadcast("$routeUpdate",m);else if(d||m)u=!1,a.$broadcast("$routeChangeStart", +d,m),(r.current=d)&&d.redirectTo&&(e.isString(d.redirectTo)?c.path(t(d.redirectTo,d.params)).search(d.params).replace():c.url(d.redirectTo(d.pathParams,c.path(),c.search())).replace()),f.when(d).then(function(){if(d){var a=e.extend({},d.resolve),c,b;e.forEach(a,function(d,c){a[c]=e.isString(d)?g.get(d):g.invoke(d)});e.isDefined(c=d.template)?e.isFunction(c)&&(c=c(d.params)):e.isDefined(b=d.templateUrl)&&(e.isFunction(b)&&(b=b(d.params)),b=h.getTrustedResourceUrl(b),e.isDefined(b)&&(d.loadedTemplateUrl= +b,c=n.get(b,{cache:v}).then(function(a){return a.data})));e.isDefined(c)&&(a.$template=c);return f.all(a)}}).then(function(c){d==r.current&&(d&&(d.locals=c,e.copy(d.params,b)),a.$broadcast("$routeChangeSuccess",d,m))},function(c){d==r.current&&a.$broadcast("$routeChangeError",d,m,c)})}function p(){var a,b;e.forEach(k,function(f,k){var q;if(q=!b){var g=c.path();q=f.keys;var l={};if(f.regexp)if(g=f.regexp.exec(g)){for(var h=1,p=g.length;h 0 && (length - 1) in obj; +} + +/** + * @ngdoc function + * @name angular.forEach + * @module ng + * @kind function + * + * @description + * Invokes the `iterator` function once for each item in `obj` collection, which can be either an + * object or an array. The `iterator` function is invoked with `iterator(value, key)`, where `value` + * is the value of an object property or an array element and `key` is the object property key or + * array element index. Specifying a `context` for the function is optional. + * + * It is worth noting that `.forEach` does not iterate over inherited properties because it filters + * using the `hasOwnProperty` method. + * + ```js + var values = {name: 'misko', gender: 'male'}; + var log = []; + angular.forEach(values, function(value, key) { + this.push(key + ': ' + value); + }, log); + expect(log).toEqual(['name: misko', 'gender: male']); + ``` + * + * @param {Object|Array} obj Object to iterate over. + * @param {Function} iterator Iterator function. + * @param {Object=} context Object to become context (`this`) for the iterator function. + * @returns {Object|Array} Reference to `obj`. + */ +function forEach(obj, iterator, context) { + var key; + if (obj) { + if (isFunction(obj)) { + for (key in obj) { + // Need to check if hasOwnProperty exists, + // as on IE8 the result of querySelectorAll is an object without a hasOwnProperty function + if (key != 'prototype' && key != 'length' && key != 'name' && (!obj.hasOwnProperty || obj.hasOwnProperty(key))) { + iterator.call(context, obj[key], key); + } + } + } else if (obj.forEach && obj.forEach !== forEach) { + obj.forEach(iterator, context); + } else if (isArrayLike(obj)) { + for (key = 0; key < obj.length; key++) + iterator.call(context, obj[key], key); + } else { + for (key in obj) { + if (obj.hasOwnProperty(key)) { + iterator.call(context, obj[key], key); + } + } + } + } + return obj; +} + +function sortedKeys(obj) { + var keys = []; + for (var key in obj) { + if (obj.hasOwnProperty(key)) { + keys.push(key); + } + } + return keys.sort(); +} + +function forEachSorted(obj, iterator, context) { + var keys = sortedKeys(obj); + for ( var i = 0; i < keys.length; i++) { + iterator.call(context, obj[keys[i]], keys[i]); + } + return keys; +} + + +/** + * when using forEach the params are value, key, but it is often useful to have key, value. + * @param {function(string, *)} iteratorFn + * @returns {function(*, string)} + */ +function reverseParams(iteratorFn) { + return function(value, key) { iteratorFn(key, value); }; +} + +/** + * A consistent way of creating unique IDs in angular. The ID is a sequence of alpha numeric + * characters such as '012ABC'. The reason why we are not using simply a number counter is that + * the number string gets longer over time, and it can also overflow, where as the nextId + * will grow much slower, it is a string, and it will never overflow. + * + * @returns {string} an unique alpha-numeric string + */ +function nextUid() { + var index = uid.length; + var digit; + + while(index) { + index--; + digit = uid[index].charCodeAt(0); + if (digit == 57 /*'9'*/) { + uid[index] = 'A'; + return uid.join(''); + } + if (digit == 90 /*'Z'*/) { + uid[index] = '0'; + } else { + uid[index] = String.fromCharCode(digit + 1); + return uid.join(''); + } + } + uid.unshift('0'); + return uid.join(''); +} + + +/** + * Set or clear the hashkey for an object. + * @param obj object + * @param h the hashkey (!truthy to delete the hashkey) + */ +function setHashKey(obj, h) { + if (h) { + obj.$$hashKey = h; + } + else { + delete obj.$$hashKey; + } +} + +/** + * @ngdoc function + * @name angular.extend + * @module ng + * @kind function + * + * @description + * Extends the destination object `dst` by copying all of the properties from the `src` object(s) + * to `dst`. You can specify multiple `src` objects. + * + * @param {Object} dst Destination object. + * @param {...Object} src Source object(s). + * @returns {Object} Reference to `dst`. + */ +function extend(dst) { + var h = dst.$$hashKey; + forEach(arguments, function(obj) { + if (obj !== dst) { + forEach(obj, function(value, key) { + dst[key] = value; + }); + } + }); + + setHashKey(dst,h); + return dst; +} + +function int(str) { + return parseInt(str, 10); +} + + +function inherit(parent, extra) { + return extend(new (extend(function() {}, {prototype:parent}))(), extra); +} + +/** + * @ngdoc function + * @name angular.noop + * @module ng + * @kind function + * + * @description + * A function that performs no operations. This function can be useful when writing code in the + * functional style. + ```js + function foo(callback) { + var result = calculateResult(); + (callback || angular.noop)(result); + } + ``` + */ +function noop() {} +noop.$inject = []; + + +/** + * @ngdoc function + * @name angular.identity + * @module ng + * @kind function + * + * @description + * A function that returns its first argument. This function is useful when writing code in the + * functional style. + * + ```js + function transformer(transformationFn, value) { + return (transformationFn || angular.identity)(value); + }; + ``` + */ +function identity($) {return $;} +identity.$inject = []; + + +function valueFn(value) {return function() {return value;};} + +/** + * @ngdoc function + * @name angular.isUndefined + * @module ng + * @kind function + * + * @description + * Determines if a reference is undefined. + * + * @param {*} value Reference to check. + * @returns {boolean} True if `value` is undefined. + */ +function isUndefined(value){return typeof value === 'undefined';} + + +/** + * @ngdoc function + * @name angular.isDefined + * @module ng + * @kind function + * + * @description + * Determines if a reference is defined. + * + * @param {*} value Reference to check. + * @returns {boolean} True if `value` is defined. + */ +function isDefined(value){return typeof value !== 'undefined';} + + +/** + * @ngdoc function + * @name angular.isObject + * @module ng + * @kind function + * + * @description + * Determines if a reference is an `Object`. Unlike `typeof` in JavaScript, `null`s are not + * considered to be objects. Note that JavaScript arrays are objects. + * + * @param {*} value Reference to check. + * @returns {boolean} True if `value` is an `Object` but not `null`. + */ +function isObject(value){return value != null && typeof value === 'object';} + + +/** + * @ngdoc function + * @name angular.isString + * @module ng + * @kind function + * + * @description + * Determines if a reference is a `String`. + * + * @param {*} value Reference to check. + * @returns {boolean} True if `value` is a `String`. + */ +function isString(value){return typeof value === 'string';} + + +/** + * @ngdoc function + * @name angular.isNumber + * @module ng + * @kind function + * + * @description + * Determines if a reference is a `Number`. + * + * @param {*} value Reference to check. + * @returns {boolean} True if `value` is a `Number`. + */ +function isNumber(value){return typeof value === 'number';} + + +/** + * @ngdoc function + * @name angular.isDate + * @module ng + * @kind function + * + * @description + * Determines if a value is a date. + * + * @param {*} value Reference to check. + * @returns {boolean} True if `value` is a `Date`. + */ +function isDate(value) { + return toString.call(value) === '[object Date]'; +} + + +/** + * @ngdoc function + * @name angular.isArray + * @module ng + * @kind function + * + * @description + * Determines if a reference is an `Array`. + * + * @param {*} value Reference to check. + * @returns {boolean} True if `value` is an `Array`. + */ +function isArray(value) { + return toString.call(value) === '[object Array]'; +} + + +/** + * @ngdoc function + * @name angular.isFunction + * @module ng + * @kind function + * + * @description + * Determines if a reference is a `Function`. + * + * @param {*} value Reference to check. + * @returns {boolean} True if `value` is a `Function`. + */ +function isFunction(value){return typeof value === 'function';} + + +/** + * Determines if a value is a regular expression object. + * + * @private + * @param {*} value Reference to check. + * @returns {boolean} True if `value` is a `RegExp`. + */ +function isRegExp(value) { + return toString.call(value) === '[object RegExp]'; +} + + +/** + * Checks if `obj` is a window object. + * + * @private + * @param {*} obj Object to check + * @returns {boolean} True if `obj` is a window obj. + */ +function isWindow(obj) { + return obj && obj.document && obj.location && obj.alert && obj.setInterval; +} + + +function isScope(obj) { + return obj && obj.$evalAsync && obj.$watch; +} + + +function isFile(obj) { + return toString.call(obj) === '[object File]'; +} + + +function isBlob(obj) { + return toString.call(obj) === '[object Blob]'; +} + + +function isBoolean(value) { + return typeof value === 'boolean'; +} + + +var trim = (function() { + // native trim is way faster: http://jsperf.com/angular-trim-test + // but IE doesn't have it... :-( + // TODO: we should move this into IE/ES5 polyfill + if (!String.prototype.trim) { + return function(value) { + return isString(value) ? value.replace(/^\s\s*/, '').replace(/\s\s*$/, '') : value; + }; + } + return function(value) { + return isString(value) ? value.trim() : value; + }; +})(); + + +/** + * @ngdoc function + * @name angular.isElement + * @module ng + * @kind function + * + * @description + * Determines if a reference is a DOM element (or wrapped jQuery element). + * + * @param {*} value Reference to check. + * @returns {boolean} True if `value` is a DOM element (or wrapped jQuery element). + */ +function isElement(node) { + return !!(node && + (node.nodeName // we are a direct element + || (node.prop && node.attr && node.find))); // we have an on and find method part of jQuery API +} + +/** + * @param str 'key1,key2,...' + * @returns {object} in the form of {key1:true, key2:true, ...} + */ +function makeMap(str) { + var obj = {}, items = str.split(","), i; + for ( i = 0; i < items.length; i++ ) + obj[ items[i] ] = true; + return obj; +} + + +if (msie < 9) { + nodeName_ = function(element) { + element = element.nodeName ? element : element[0]; + return (element.scopeName && element.scopeName != 'HTML') + ? uppercase(element.scopeName + ':' + element.nodeName) : element.nodeName; + }; +} else { + nodeName_ = function(element) { + return element.nodeName ? element.nodeName : element[0].nodeName; + }; +} + + +function map(obj, iterator, context) { + var results = []; + forEach(obj, function(value, index, list) { + results.push(iterator.call(context, value, index, list)); + }); + return results; +} + + +/** + * @description + * Determines the number of elements in an array, the number of properties an object has, or + * the length of a string. + * + * Note: This function is used to augment the Object type in Angular expressions. See + * {@link angular.Object} for more information about Angular arrays. + * + * @param {Object|Array|string} obj Object, array, or string to inspect. + * @param {boolean} [ownPropsOnly=false] Count only "own" properties in an object + * @returns {number} The size of `obj` or `0` if `obj` is neither an object nor an array. + */ +function size(obj, ownPropsOnly) { + var count = 0, key; + + if (isArray(obj) || isString(obj)) { + return obj.length; + } else if (isObject(obj)) { + for (key in obj) + if (!ownPropsOnly || obj.hasOwnProperty(key)) + count++; + } + + return count; +} + + +function includes(array, obj) { + return indexOf(array, obj) != -1; +} + +function indexOf(array, obj) { + if (array.indexOf) return array.indexOf(obj); + + for (var i = 0; i < array.length; i++) { + if (obj === array[i]) return i; + } + return -1; +} + +function arrayRemove(array, value) { + var index = indexOf(array, value); + if (index >=0) + array.splice(index, 1); + return value; +} + +function isLeafNode (node) { + if (node) { + switch (node.nodeName) { + case "OPTION": + case "PRE": + case "TITLE": + return true; + } + } + return false; +} + +/** + * @ngdoc function + * @name angular.copy + * @module ng + * @kind function + * + * @description + * Creates a deep copy of `source`, which should be an object or an array. + * + * * If no destination is supplied, a copy of the object or array is created. + * * If a destination is provided, all of its elements (for array) or properties (for objects) + * are deleted and then all elements/properties from the source are copied to it. + * * If `source` is not an object or array (inc. `null` and `undefined`), `source` is returned. + * * If `source` is identical to 'destination' an exception will be thrown. + * + * @param {*} source The source that will be used to make a copy. + * Can be any type, including primitives, `null`, and `undefined`. + * @param {(Object|Array)=} destination Destination into which the source is copied. If + * provided, must be of the same type as `source`. + * @returns {*} The copy or updated `destination`, if `destination` was specified. + * + * @example + + +
+
+ Name:
+ E-mail:
+ Gender: male + female
+ + +
+
form = {{user | json}}
+
master = {{master | json}}
+
+ + +
+
+ */ +function copy(source, destination, stackSource, stackDest) { + if (isWindow(source) || isScope(source)) { + throw ngMinErr('cpws', + "Can't copy! Making copies of Window or Scope instances is not supported."); + } + + if (!destination) { + destination = source; + if (source) { + if (isArray(source)) { + destination = copy(source, [], stackSource, stackDest); + } else if (isDate(source)) { + destination = new Date(source.getTime()); + } else if (isRegExp(source)) { + destination = new RegExp(source.source); + } else if (isObject(source)) { + destination = copy(source, {}, stackSource, stackDest); + } + } + } else { + if (source === destination) throw ngMinErr('cpi', + "Can't copy! Source and destination are identical."); + + stackSource = stackSource || []; + stackDest = stackDest || []; + + if (isObject(source)) { + var index = indexOf(stackSource, source); + if (index !== -1) return stackDest[index]; + + stackSource.push(source); + stackDest.push(destination); + } + + var result; + if (isArray(source)) { + destination.length = 0; + for ( var i = 0; i < source.length; i++) { + result = copy(source[i], null, stackSource, stackDest); + if (isObject(source[i])) { + stackSource.push(source[i]); + stackDest.push(result); + } + destination.push(result); + } + } else { + var h = destination.$$hashKey; + forEach(destination, function(value, key) { + delete destination[key]; + }); + for ( var key in source) { + result = copy(source[key], null, stackSource, stackDest); + if (isObject(source[key])) { + stackSource.push(source[key]); + stackDest.push(result); + } + destination[key] = result; + } + setHashKey(destination,h); + } + + } + return destination; +} + +/** + * Creates a shallow copy of an object, an array or a primitive + */ +function shallowCopy(src, dst) { + if (isArray(src)) { + dst = dst || []; + + for ( var i = 0; i < src.length; i++) { + dst[i] = src[i]; + } + } else if (isObject(src)) { + dst = dst || {}; + + for (var key in src) { + if (hasOwnProperty.call(src, key) && !(key.charAt(0) === '$' && key.charAt(1) === '$')) { + dst[key] = src[key]; + } + } + } + + return dst || src; +} + + +/** + * @ngdoc function + * @name angular.equals + * @module ng + * @kind function + * + * @description + * Determines if two objects or two values are equivalent. Supports value types, regular + * expressions, arrays and objects. + * + * Two objects or values are considered equivalent if at least one of the following is true: + * + * * Both objects or values pass `===` comparison. + * * Both objects or values are of the same type and all of their properties are equal by + * comparing them with `angular.equals`. + * * Both values are NaN. (In JavaScript, NaN == NaN => false. But we consider two NaN as equal) + * * Both values represent the same regular expression (In JavaScript, + * /abc/ == /abc/ => false. But we consider two regular expressions as equal when their textual + * representation matches). + * + * During a property comparison, properties of `function` type and properties with names + * that begin with `$` are ignored. + * + * Scope and DOMWindow objects are being compared only by identify (`===`). + * + * @param {*} o1 Object or value to compare. + * @param {*} o2 Object or value to compare. + * @returns {boolean} True if arguments are equal. + */ +function equals(o1, o2) { + if (o1 === o2) return true; + if (o1 === null || o2 === null) return false; + if (o1 !== o1 && o2 !== o2) return true; // NaN === NaN + var t1 = typeof o1, t2 = typeof o2, length, key, keySet; + if (t1 == t2) { + if (t1 == 'object') { + if (isArray(o1)) { + if (!isArray(o2)) return false; + if ((length = o1.length) == o2.length) { + for(key=0; key 2 ? sliceArgs(arguments, 2) : []; + if (isFunction(fn) && !(fn instanceof RegExp)) { + return curryArgs.length + ? function() { + return arguments.length + ? fn.apply(self, curryArgs.concat(slice.call(arguments, 0))) + : fn.apply(self, curryArgs); + } + : function() { + return arguments.length + ? fn.apply(self, arguments) + : fn.call(self); + }; + } else { + // in IE, native methods are not functions so they cannot be bound (note: they don't need to be) + return fn; + } +} + + +function toJsonReplacer(key, value) { + var val = value; + + if (typeof key === 'string' && key.charAt(0) === '$') { + val = undefined; + } else if (isWindow(value)) { + val = '$WINDOW'; + } else if (value && document === value) { + val = '$DOCUMENT'; + } else if (isScope(value)) { + val = '$SCOPE'; + } + + return val; +} + + +/** + * @ngdoc function + * @name angular.toJson + * @module ng + * @kind function + * + * @description + * Serializes input into a JSON-formatted string. Properties with leading $ characters will be + * stripped since angular uses this notation internally. + * + * @param {Object|Array|Date|string|number} obj Input to be serialized into JSON. + * @param {boolean=} pretty If set to true, the JSON output will contain newlines and whitespace. + * @returns {string|undefined} JSON-ified string representing `obj`. + */ +function toJson(obj, pretty) { + if (typeof obj === 'undefined') return undefined; + return JSON.stringify(obj, toJsonReplacer, pretty ? ' ' : null); +} + + +/** + * @ngdoc function + * @name angular.fromJson + * @module ng + * @kind function + * + * @description + * Deserializes a JSON string. + * + * @param {string} json JSON string to deserialize. + * @returns {Object|Array|string|number} Deserialized thingy. + */ +function fromJson(json) { + return isString(json) + ? JSON.parse(json) + : json; +} + + +function toBoolean(value) { + if (typeof value === 'function') { + value = true; + } else if (value && value.length !== 0) { + var v = lowercase("" + value); + value = !(v == 'f' || v == '0' || v == 'false' || v == 'no' || v == 'n' || v == '[]'); + } else { + value = false; + } + return value; +} + +/** + * @returns {string} Returns the string representation of the element. + */ +function startingTag(element) { + element = jqLite(element).clone(); + try { + // turns out IE does not let you set .html() on elements which + // are not allowed to have children. So we just ignore it. + element.empty(); + } catch(e) {} + // As Per DOM Standards + var TEXT_NODE = 3; + var elemHtml = jqLite('
').append(element).html(); + try { + return element[0].nodeType === TEXT_NODE ? lowercase(elemHtml) : + elemHtml. + match(/^(<[^>]+>)/)[1]. + replace(/^<([\w\-]+)/, function(match, nodeName) { return '<' + lowercase(nodeName); }); + } catch(e) { + return lowercase(elemHtml); + } + +} + + +///////////////////////////////////////////////// + +/** + * Tries to decode the URI component without throwing an exception. + * + * @private + * @param str value potential URI component to check. + * @returns {boolean} True if `value` can be decoded + * with the decodeURIComponent function. + */ +function tryDecodeURIComponent(value) { + try { + return decodeURIComponent(value); + } catch(e) { + // Ignore any invalid uri component + } +} + + +/** + * Parses an escaped url query string into key-value pairs. + * @returns {Object.} + */ +function parseKeyValue(/**string*/keyValue) { + var obj = {}, key_value, key; + forEach((keyValue || "").split('&'), function(keyValue) { + if ( keyValue ) { + key_value = keyValue.split('='); + key = tryDecodeURIComponent(key_value[0]); + if ( isDefined(key) ) { + var val = isDefined(key_value[1]) ? tryDecodeURIComponent(key_value[1]) : true; + if (!obj[key]) { + obj[key] = val; + } else if(isArray(obj[key])) { + obj[key].push(val); + } else { + obj[key] = [obj[key],val]; + } + } + } + }); + return obj; +} + +function toKeyValue(obj) { + var parts = []; + forEach(obj, function(value, key) { + if (isArray(value)) { + forEach(value, function(arrayValue) { + parts.push(encodeUriQuery(key, true) + + (arrayValue === true ? '' : '=' + encodeUriQuery(arrayValue, true))); + }); + } else { + parts.push(encodeUriQuery(key, true) + + (value === true ? '' : '=' + encodeUriQuery(value, true))); + } + }); + return parts.length ? parts.join('&') : ''; +} + + +/** + * We need our custom method because encodeURIComponent is too aggressive and doesn't follow + * http://www.ietf.org/rfc/rfc3986.txt with regards to the character set (pchar) allowed in path + * segments: + * segment = *pchar + * pchar = unreserved / pct-encoded / sub-delims / ":" / "@" + * pct-encoded = "%" HEXDIG HEXDIG + * unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~" + * sub-delims = "!" / "$" / "&" / "'" / "(" / ")" + * / "*" / "+" / "," / ";" / "=" + */ +function encodeUriSegment(val) { + return encodeUriQuery(val, true). + replace(/%26/gi, '&'). + replace(/%3D/gi, '='). + replace(/%2B/gi, '+'); +} + + +/** + * This method is intended for encoding *key* or *value* parts of query component. We need a custom + * method because encodeURIComponent is too aggressive and encodes stuff that doesn't have to be + * encoded per http://tools.ietf.org/html/rfc3986: + * query = *( pchar / "/" / "?" ) + * pchar = unreserved / pct-encoded / sub-delims / ":" / "@" + * unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~" + * pct-encoded = "%" HEXDIG HEXDIG + * sub-delims = "!" / "$" / "&" / "'" / "(" / ")" + * / "*" / "+" / "," / ";" / "=" + */ +function encodeUriQuery(val, pctEncodeSpaces) { + return encodeURIComponent(val). + replace(/%40/gi, '@'). + replace(/%3A/gi, ':'). + replace(/%24/g, '$'). + replace(/%2C/gi, ','). + replace(/%20/g, (pctEncodeSpaces ? '%20' : '+')); +} + + +/** + * @ngdoc directive + * @name ngApp + * @module ng + * + * @element ANY + * @param {angular.Module} ngApp an optional application + * {@link angular.module module} name to load. + * + * @description + * + * Use this directive to **auto-bootstrap** an AngularJS application. The `ngApp` directive + * designates the **root element** of the application and is typically placed near the root element + * of the page - e.g. on the `` or `` tags. + * + * Only one AngularJS application can be auto-bootstrapped per HTML document. The first `ngApp` + * found in the document will be used to define the root element to auto-bootstrap as an + * application. To run multiple applications in an HTML document you must manually bootstrap them using + * {@link angular.bootstrap} instead. AngularJS applications cannot be nested within each other. + * + * You can specify an **AngularJS module** to be used as the root module for the application. This + * module will be loaded into the {@link auto.$injector} when the application is bootstrapped and + * should contain the application code needed or have dependencies on other modules that will + * contain the code. See {@link angular.module} for more information. + * + * In the example below if the `ngApp` directive were not placed on the `html` element then the + * document would not be compiled, the `AppController` would not be instantiated and the `{{ a+b }}` + * would not be resolved to `3`. + * + * `ngApp` is the easiest, and most common, way to bootstrap an application. + * + + +
+ I can add: {{a}} + {{b}} = {{ a+b }} +
+
+ + angular.module('ngAppDemo', []).controller('ngAppDemoController', function($scope) { + $scope.a = 1; + $scope.b = 2; + }); + +
+ * + */ +function angularInit(element, bootstrap) { + var elements = [element], + appElement, + module, + names = ['ng:app', 'ng-app', 'x-ng-app', 'data-ng-app'], + NG_APP_CLASS_REGEXP = /\sng[:\-]app(:\s*([\w\d_]+);?)?\s/; + + function append(element) { + element && elements.push(element); + } + + forEach(names, function(name) { + names[name] = true; + append(document.getElementById(name)); + name = name.replace(':', '\\:'); + if (element.querySelectorAll) { + forEach(element.querySelectorAll('.' + name), append); + forEach(element.querySelectorAll('.' + name + '\\:'), append); + forEach(element.querySelectorAll('[' + name + ']'), append); + } + }); + + forEach(elements, function(element) { + if (!appElement) { + var className = ' ' + element.className + ' '; + var match = NG_APP_CLASS_REGEXP.exec(className); + if (match) { + appElement = element; + module = (match[2] || '').replace(/\s+/g, ','); + } else { + forEach(element.attributes, function(attr) { + if (!appElement && names[attr.name]) { + appElement = element; + module = attr.value; + } + }); + } + } + }); + if (appElement) { + bootstrap(appElement, module ? [module] : []); + } +} + +/** + * @ngdoc function + * @name angular.bootstrap + * @module ng + * @description + * Use this function to manually start up angular application. + * + * See: {@link guide/bootstrap Bootstrap} + * + * Note that ngScenario-based end-to-end tests cannot use this function to bootstrap manually. + * They must use {@link ng.directive:ngApp ngApp}. + * + * Angular will detect if it has been loaded into the browser more than once and only allow the + * first loaded script to be bootstrapped and will report a warning to the browser console for + * each of the subsequent scripts. This prevents strange results in applications, where otherwise + * multiple instances of Angular try to work on the DOM. + * + * + * + * + *
+ * + * + * + * + * + * + * + *
{{heading}}
{{fill}}
+ *
+ *
+ * + * var app = angular.module('multi-bootstrap', []) + * + * .controller('BrokenTable', function($scope) { + * $scope.headings = ['One', 'Two', 'Three']; + * $scope.fillings = [[1, 2, 3], ['A', 'B', 'C'], [7, 8, 9]]; + * }); + * + * + * it('should only insert one table cell for each item in $scope.fillings', function() { + * expect(element.all(by.css('td')).count()) + * .toBe(9); + * }); + * + *
+ * + * @param {DOMElement} element DOM element which is the root of angular application. + * @param {Array=} modules an array of modules to load into the application. + * Each item in the array should be the name of a predefined module or a (DI annotated) + * function that will be invoked by the injector as a run block. + * See: {@link angular.module modules} + * @returns {auto.$injector} Returns the newly created injector for this app. + */ +function bootstrap(element, modules) { + var doBootstrap = function() { + element = jqLite(element); + + if (element.injector()) { + var tag = (element[0] === document) ? 'document' : startingTag(element); + throw ngMinErr('btstrpd', "App Already Bootstrapped with this Element '{0}'", tag); + } + + modules = modules || []; + modules.unshift(['$provide', function($provide) { + $provide.value('$rootElement', element); + }]); + modules.unshift('ng'); + var injector = createInjector(modules); + injector.invoke(['$rootScope', '$rootElement', '$compile', '$injector', '$animate', + function(scope, element, compile, injector, animate) { + scope.$apply(function() { + element.data('$injector', injector); + compile(element)(scope); + }); + }] + ); + return injector; + }; + + var NG_DEFER_BOOTSTRAP = /^NG_DEFER_BOOTSTRAP!/; + + if (window && !NG_DEFER_BOOTSTRAP.test(window.name)) { + return doBootstrap(); + } + + window.name = window.name.replace(NG_DEFER_BOOTSTRAP, ''); + angular.resumeBootstrap = function(extraModules) { + forEach(extraModules, function(module) { + modules.push(module); + }); + doBootstrap(); + }; +} + +var SNAKE_CASE_REGEXP = /[A-Z]/g; +function snake_case(name, separator) { + separator = separator || '_'; + return name.replace(SNAKE_CASE_REGEXP, function(letter, pos) { + return (pos ? separator : '') + letter.toLowerCase(); + }); +} + +function bindJQuery() { + // bind to jQuery if present; + jQuery = window.jQuery; + // Use jQuery if it exists with proper functionality, otherwise default to us. + // Angular 1.2+ requires jQuery 1.7.1+ for on()/off() support. + if (jQuery && jQuery.fn.on) { + jqLite = jQuery; + extend(jQuery.fn, { + scope: JQLitePrototype.scope, + isolateScope: JQLitePrototype.isolateScope, + controller: JQLitePrototype.controller, + injector: JQLitePrototype.injector, + inheritedData: JQLitePrototype.inheritedData + }); + // Method signature: + // jqLitePatchJQueryRemove(name, dispatchThis, filterElems, getterIfNoArguments) + jqLitePatchJQueryRemove('remove', true, true, false); + jqLitePatchJQueryRemove('empty', false, false, false); + jqLitePatchJQueryRemove('html', false, false, true); + } else { + jqLite = JQLite; + } + angular.element = jqLite; +} + +/** + * throw error if the argument is falsy. + */ +function assertArg(arg, name, reason) { + if (!arg) { + throw ngMinErr('areq', "Argument '{0}' is {1}", (name || '?'), (reason || "required")); + } + return arg; +} + +function assertArgFn(arg, name, acceptArrayAnnotation) { + if (acceptArrayAnnotation && isArray(arg)) { + arg = arg[arg.length - 1]; + } + + assertArg(isFunction(arg), name, 'not a function, got ' + + (arg && typeof arg == 'object' ? arg.constructor.name || 'Object' : typeof arg)); + return arg; +} + +/** + * throw error if the name given is hasOwnProperty + * @param {String} name the name to test + * @param {String} context the context in which the name is used, such as module or directive + */ +function assertNotHasOwnProperty(name, context) { + if (name === 'hasOwnProperty') { + throw ngMinErr('badname', "hasOwnProperty is not a valid {0} name", context); + } +} + +/** + * Return the value accessible from the object by path. Any undefined traversals are ignored + * @param {Object} obj starting object + * @param {String} path path to traverse + * @param {boolean} [bindFnToScope=true] + * @returns {Object} value as accessible by path + */ +//TODO(misko): this function needs to be removed +function getter(obj, path, bindFnToScope) { + if (!path) return obj; + var keys = path.split('.'); + var key; + var lastInstance = obj; + var len = keys.length; + + for (var i = 0; i < len; i++) { + key = keys[i]; + if (obj) { + obj = (lastInstance = obj)[key]; + } + } + if (!bindFnToScope && isFunction(obj)) { + return bind(lastInstance, obj); + } + return obj; +} + +/** + * Return the DOM siblings between the first and last node in the given array. + * @param {Array} array like object + * @returns {DOMElement} object containing the elements + */ +function getBlockElements(nodes) { + var startNode = nodes[0], + endNode = nodes[nodes.length - 1]; + if (startNode === endNode) { + return jqLite(startNode); + } + + var element = startNode; + var elements = [element]; + + do { + element = element.nextSibling; + if (!element) break; + elements.push(element); + } while (element !== endNode); + + return jqLite(elements); +} + +/** + * @ngdoc type + * @name angular.Module + * @module ng + * @description + * + * Interface for configuring angular {@link angular.module modules}. + */ + +function setupModuleLoader(window) { + + var $injectorMinErr = minErr('$injector'); + var ngMinErr = minErr('ng'); + + function ensure(obj, name, factory) { + return obj[name] || (obj[name] = factory()); + } + + var angular = ensure(window, 'angular', Object); + + // We need to expose `angular.$$minErr` to modules such as `ngResource` that reference it during bootstrap + angular.$$minErr = angular.$$minErr || minErr; + + return ensure(angular, 'module', function() { + /** @type {Object.} */ + var modules = {}; + + /** + * @ngdoc function + * @name angular.module + * @module ng + * @description + * + * The `angular.module` is a global place for creating, registering and retrieving Angular + * modules. + * All modules (angular core or 3rd party) that should be available to an application must be + * registered using this mechanism. + * + * When passed two or more arguments, a new module is created. If passed only one argument, an + * existing module (the name passed as the first argument to `module`) is retrieved. + * + * + * # Module + * + * A module is a collection of services, directives, filters, and configuration information. + * `angular.module` is used to configure the {@link auto.$injector $injector}. + * + * ```js + * // Create a new module + * var myModule = angular.module('myModule', []); + * + * // register a new service + * myModule.value('appName', 'MyCoolApp'); + * + * // configure existing services inside initialization blocks. + * myModule.config(['$locationProvider', function($locationProvider) { + * // Configure existing providers + * $locationProvider.hashPrefix('!'); + * }]); + * ``` + * + * Then you can create an injector and load your modules like this: + * + * ```js + * var injector = angular.injector(['ng', 'myModule']) + * ``` + * + * However it's more likely that you'll just use + * {@link ng.directive:ngApp ngApp} or + * {@link angular.bootstrap} to simplify this process for you. + * + * @param {!string} name The name of the module to create or retrieve. +<<<<<* @param {!Array.=} requires If specified then new module is being created. If +>>>>>* unspecified then the module is being retrieved for further configuration. + * @param {Function} configFn Optional configuration function for the module. Same as + * {@link angular.Module#config Module#config()}. + * @returns {module} new module with the {@link angular.Module} api. + */ + return function module(name, requires, configFn) { + var assertNotHasOwnProperty = function(name, context) { + if (name === 'hasOwnProperty') { + throw ngMinErr('badname', 'hasOwnProperty is not a valid {0} name', context); + } + }; + + assertNotHasOwnProperty(name, 'module'); + if (requires && modules.hasOwnProperty(name)) { + modules[name] = null; + } + return ensure(modules, name, function() { + if (!requires) { + throw $injectorMinErr('nomod', "Module '{0}' is not available! You either misspelled " + + "the module name or forgot to load it. If registering a module ensure that you " + + "specify the dependencies as the second argument.", name); + } + + /** @type {!Array.>} */ + var invokeQueue = []; + + /** @type {!Array.} */ + var runBlocks = []; + + var config = invokeLater('$injector', 'invoke'); + + /** @type {angular.Module} */ + var moduleInstance = { + // Private state + _invokeQueue: invokeQueue, + _runBlocks: runBlocks, + + /** + * @ngdoc property + * @name angular.Module#requires + * @module ng + * @returns {Array.} List of module names which must be loaded before this module. + * @description + * Holds the list of modules which the injector will load before the current module is + * loaded. + */ + requires: requires, + + /** + * @ngdoc property + * @name angular.Module#name + * @module ng + * @returns {string} Name of the module. + * @description + */ + name: name, + + + /** + * @ngdoc method + * @name angular.Module#provider + * @module ng + * @param {string} name service name + * @param {Function} providerType Construction function for creating new instance of the + * service. + * @description + * See {@link auto.$provide#provider $provide.provider()}. + */ + provider: invokeLater('$provide', 'provider'), + + /** + * @ngdoc method + * @name angular.Module#factory + * @module ng + * @param {string} name service name + * @param {Function} providerFunction Function for creating new instance of the service. + * @description + * See {@link auto.$provide#factory $provide.factory()}. + */ + factory: invokeLater('$provide', 'factory'), + + /** + * @ngdoc method + * @name angular.Module#service + * @module ng + * @param {string} name service name + * @param {Function} constructor A constructor function that will be instantiated. + * @description + * See {@link auto.$provide#service $provide.service()}. + */ + service: invokeLater('$provide', 'service'), + + /** + * @ngdoc method + * @name angular.Module#value + * @module ng + * @param {string} name service name + * @param {*} object Service instance object. + * @description + * See {@link auto.$provide#value $provide.value()}. + */ + value: invokeLater('$provide', 'value'), + + /** + * @ngdoc method + * @name angular.Module#constant + * @module ng + * @param {string} name constant name + * @param {*} object Constant value. + * @description + * Because the constant are fixed, they get applied before other provide methods. + * See {@link auto.$provide#constant $provide.constant()}. + */ + constant: invokeLater('$provide', 'constant', 'unshift'), + + /** + * @ngdoc method + * @name angular.Module#animation + * @module ng + * @param {string} name animation name + * @param {Function} animationFactory Factory function for creating new instance of an + * animation. + * @description + * + * **NOTE**: animations take effect only if the **ngAnimate** module is loaded. + * + * + * Defines an animation hook that can be later used with + * {@link ngAnimate.$animate $animate} service and directives that use this service. + * + * ```js + * module.animation('.animation-name', function($inject1, $inject2) { + * return { + * eventName : function(element, done) { + * //code to run the animation + * //once complete, then run done() + * return function cancellationFunction(element) { + * //code to cancel the animation + * } + * } + * } + * }) + * ``` + * + * See {@link ngAnimate.$animateProvider#register $animateProvider.register()} and + * {@link ngAnimate ngAnimate module} for more information. + */ + animation: invokeLater('$animateProvider', 'register'), + + /** + * @ngdoc method + * @name angular.Module#filter + * @module ng + * @param {string} name Filter name. + * @param {Function} filterFactory Factory function for creating new instance of filter. + * @description + * See {@link ng.$filterProvider#register $filterProvider.register()}. + */ + filter: invokeLater('$filterProvider', 'register'), + + /** + * @ngdoc method + * @name angular.Module#controller + * @module ng + * @param {string|Object} name Controller name, or an object map of controllers where the + * keys are the names and the values are the constructors. + * @param {Function} constructor Controller constructor function. + * @description + * See {@link ng.$controllerProvider#register $controllerProvider.register()}. + */ + controller: invokeLater('$controllerProvider', 'register'), + + /** + * @ngdoc method + * @name angular.Module#directive + * @module ng + * @param {string|Object} name Directive name, or an object map of directives where the + * keys are the names and the values are the factories. + * @param {Function} directiveFactory Factory function for creating new instance of + * directives. + * @description + * See {@link ng.$compileProvider#directive $compileProvider.directive()}. + */ + directive: invokeLater('$compileProvider', 'directive'), + + /** + * @ngdoc method + * @name angular.Module#config + * @module ng + * @param {Function} configFn Execute this function on module load. Useful for service + * configuration. + * @description + * Use this method to register work which needs to be performed on module loading. + * For more about how to configure services, see + * {@link providers#providers_provider-recipe Provider Recipe}. + */ + config: config, + + /** + * @ngdoc method + * @name angular.Module#run + * @module ng + * @param {Function} initializationFn Execute this function after injector creation. + * Useful for application initialization. + * @description + * Use this method to register work which should be performed when the injector is done + * loading all modules. + */ + run: function(block) { + runBlocks.push(block); + return this; + } + }; + + if (configFn) { + config(configFn); + } + + return moduleInstance; + + /** + * @param {string} provider + * @param {string} method + * @param {String=} insertMethod + * @returns {angular.Module} + */ + function invokeLater(provider, method, insertMethod) { + return function() { + invokeQueue[insertMethod || 'push']([provider, method, arguments]); + return moduleInstance; + }; + } + }); + }; + }); + +} + +/* global + angularModule: true, + version: true, + + $LocaleProvider, + $CompileProvider, + + htmlAnchorDirective, + inputDirective, + inputDirective, + formDirective, + scriptDirective, + selectDirective, + styleDirective, + optionDirective, + ngBindDirective, + ngBindHtmlDirective, + ngBindTemplateDirective, + ngClassDirective, + ngClassEvenDirective, + ngClassOddDirective, + ngCspDirective, + ngCloakDirective, + ngControllerDirective, + ngFormDirective, + ngHideDirective, + ngIfDirective, + ngIncludeDirective, + ngIncludeFillContentDirective, + ngInitDirective, + ngNonBindableDirective, + ngPluralizeDirective, + ngRepeatDirective, + ngShowDirective, + ngStyleDirective, + ngSwitchDirective, + ngSwitchWhenDirective, + ngSwitchDefaultDirective, + ngOptionsDirective, + ngTranscludeDirective, + ngModelDirective, + ngListDirective, + ngChangeDirective, + requiredDirective, + requiredDirective, + ngValueDirective, + ngAttributeAliasDirectives, + ngEventDirectives, + + $AnchorScrollProvider, + $AnimateProvider, + $BrowserProvider, + $CacheFactoryProvider, + $ControllerProvider, + $DocumentProvider, + $ExceptionHandlerProvider, + $FilterProvider, + $InterpolateProvider, + $IntervalProvider, + $HttpProvider, + $HttpBackendProvider, + $LocationProvider, + $LogProvider, + $ParseProvider, + $RootScopeProvider, + $QProvider, + $$SanitizeUriProvider, + $SceProvider, + $SceDelegateProvider, + $SnifferProvider, + $TemplateCacheProvider, + $TimeoutProvider, + $$RAFProvider, + $$AsyncCallbackProvider, + $WindowProvider +*/ + + +/** + * @ngdoc object + * @name angular.version + * @module ng + * @description + * An object that contains information about the current AngularJS version. This object has the + * following properties: + * + * - `full` – `{string}` – Full version string, such as "0.9.18". + * - `major` – `{number}` – Major version number, such as "0". + * - `minor` – `{number}` – Minor version number, such as "9". + * - `dot` – `{number}` – Dot version number, such as "18". + * - `codeName` – `{string}` – Code name of the release, such as "jiggling-armfat". + */ +var version = { + full: '1.2.17', // all of these placeholder strings will be replaced by grunt's + major: 1, // package task + minor: 2, + dot: 17, + codeName: 'quantum-disentanglement' +}; + + +function publishExternalAPI(angular){ + extend(angular, { + 'bootstrap': bootstrap, + 'copy': copy, + 'extend': extend, + 'equals': equals, + 'element': jqLite, + 'forEach': forEach, + 'injector': createInjector, + 'noop':noop, + 'bind':bind, + 'toJson': toJson, + 'fromJson': fromJson, + 'identity':identity, + 'isUndefined': isUndefined, + 'isDefined': isDefined, + 'isString': isString, + 'isFunction': isFunction, + 'isObject': isObject, + 'isNumber': isNumber, + 'isElement': isElement, + 'isArray': isArray, + 'version': version, + 'isDate': isDate, + 'lowercase': lowercase, + 'uppercase': uppercase, + 'callbacks': {counter: 0}, + '$$minErr': minErr, + '$$csp': csp + }); + + angularModule = setupModuleLoader(window); + try { + angularModule('ngLocale'); + } catch (e) { + angularModule('ngLocale', []).provider('$locale', $LocaleProvider); + } + + angularModule('ng', ['ngLocale'], ['$provide', + function ngModule($provide) { + // $$sanitizeUriProvider needs to be before $compileProvider as it is used by it. + $provide.provider({ + $$sanitizeUri: $$SanitizeUriProvider + }); + $provide.provider('$compile', $CompileProvider). + directive({ + a: htmlAnchorDirective, + input: inputDirective, + textarea: inputDirective, + form: formDirective, + script: scriptDirective, + select: selectDirective, + style: styleDirective, + option: optionDirective, + ngBind: ngBindDirective, + ngBindHtml: ngBindHtmlDirective, + ngBindTemplate: ngBindTemplateDirective, + ngClass: ngClassDirective, + ngClassEven: ngClassEvenDirective, + ngClassOdd: ngClassOddDirective, + ngCloak: ngCloakDirective, + ngController: ngControllerDirective, + ngForm: ngFormDirective, + ngHide: ngHideDirective, + ngIf: ngIfDirective, + ngInclude: ngIncludeDirective, + ngInit: ngInitDirective, + ngNonBindable: ngNonBindableDirective, + ngPluralize: ngPluralizeDirective, + ngRepeat: ngRepeatDirective, + ngShow: ngShowDirective, + ngStyle: ngStyleDirective, + ngSwitch: ngSwitchDirective, + ngSwitchWhen: ngSwitchWhenDirective, + ngSwitchDefault: ngSwitchDefaultDirective, + ngOptions: ngOptionsDirective, + ngTransclude: ngTranscludeDirective, + ngModel: ngModelDirective, + ngList: ngListDirective, + ngChange: ngChangeDirective, + required: requiredDirective, + ngRequired: requiredDirective, + ngValue: ngValueDirective + }). + directive({ + ngInclude: ngIncludeFillContentDirective + }). + directive(ngAttributeAliasDirectives). + directive(ngEventDirectives); + $provide.provider({ + $anchorScroll: $AnchorScrollProvider, + $animate: $AnimateProvider, + $browser: $BrowserProvider, + $cacheFactory: $CacheFactoryProvider, + $controller: $ControllerProvider, + $document: $DocumentProvider, + $exceptionHandler: $ExceptionHandlerProvider, + $filter: $FilterProvider, + $interpolate: $InterpolateProvider, + $interval: $IntervalProvider, + $http: $HttpProvider, + $httpBackend: $HttpBackendProvider, + $location: $LocationProvider, + $log: $LogProvider, + $parse: $ParseProvider, + $rootScope: $RootScopeProvider, + $q: $QProvider, + $sce: $SceProvider, + $sceDelegate: $SceDelegateProvider, + $sniffer: $SnifferProvider, + $templateCache: $TemplateCacheProvider, + $timeout: $TimeoutProvider, + $window: $WindowProvider, + $$rAF: $$RAFProvider, + $$asyncCallback : $$AsyncCallbackProvider + }); + } + ]); +} + +/* global + + -JQLitePrototype, + -addEventListenerFn, + -removeEventListenerFn, + -BOOLEAN_ATTR +*/ + +////////////////////////////////// +//JQLite +////////////////////////////////// + +/** + * @ngdoc function + * @name angular.element + * @module ng + * @kind function + * + * @description + * Wraps a raw DOM element or HTML string as a [jQuery](http://jquery.com) element. + * + * If jQuery is available, `angular.element` is an alias for the + * [jQuery](http://api.jquery.com/jQuery/) function. If jQuery is not available, `angular.element` + * delegates to Angular's built-in subset of jQuery, called "jQuery lite" or "jqLite." + * + *
jqLite is a tiny, API-compatible subset of jQuery that allows + * Angular to manipulate the DOM in a cross-browser compatible way. **jqLite** implements only the most + * commonly needed functionality with the goal of having a very small footprint.
+ * + * To use jQuery, simply load it before `DOMContentLoaded` event fired. + * + *
**Note:** all element references in Angular are always wrapped with jQuery or + * jqLite; they are never raw DOM references.
+ * + * ## Angular's jqLite + * jqLite provides only the following jQuery methods: + * + * - [`addClass()`](http://api.jquery.com/addClass/) + * - [`after()`](http://api.jquery.com/after/) + * - [`append()`](http://api.jquery.com/append/) + * - [`attr()`](http://api.jquery.com/attr/) + * - [`bind()`](http://api.jquery.com/bind/) - Does not support namespaces, selectors or eventData + * - [`children()`](http://api.jquery.com/children/) - Does not support selectors + * - [`clone()`](http://api.jquery.com/clone/) + * - [`contents()`](http://api.jquery.com/contents/) + * - [`css()`](http://api.jquery.com/css/) + * - [`data()`](http://api.jquery.com/data/) + * - [`empty()`](http://api.jquery.com/empty/) + * - [`eq()`](http://api.jquery.com/eq/) + * - [`find()`](http://api.jquery.com/find/) - Limited to lookups by tag name + * - [`hasClass()`](http://api.jquery.com/hasClass/) + * - [`html()`](http://api.jquery.com/html/) + * - [`next()`](http://api.jquery.com/next/) - Does not support selectors + * - [`on()`](http://api.jquery.com/on/) - Does not support namespaces, selectors or eventData + * - [`off()`](http://api.jquery.com/off/) - Does not support namespaces or selectors + * - [`one()`](http://api.jquery.com/one/) - Does not support namespaces or selectors + * - [`parent()`](http://api.jquery.com/parent/) - Does not support selectors + * - [`prepend()`](http://api.jquery.com/prepend/) + * - [`prop()`](http://api.jquery.com/prop/) + * - [`ready()`](http://api.jquery.com/ready/) + * - [`remove()`](http://api.jquery.com/remove/) + * - [`removeAttr()`](http://api.jquery.com/removeAttr/) + * - [`removeClass()`](http://api.jquery.com/removeClass/) + * - [`removeData()`](http://api.jquery.com/removeData/) + * - [`replaceWith()`](http://api.jquery.com/replaceWith/) + * - [`text()`](http://api.jquery.com/text/) + * - [`toggleClass()`](http://api.jquery.com/toggleClass/) + * - [`triggerHandler()`](http://api.jquery.com/triggerHandler/) - Passes a dummy event object to handlers. + * - [`unbind()`](http://api.jquery.com/unbind/) - Does not support namespaces + * - [`val()`](http://api.jquery.com/val/) + * - [`wrap()`](http://api.jquery.com/wrap/) + * + * ## jQuery/jqLite Extras + * Angular also provides the following additional methods and events to both jQuery and jqLite: + * + * ### Events + * - `$destroy` - AngularJS intercepts all jqLite/jQuery's DOM destruction apis and fires this event + * on all DOM nodes being removed. This can be used to clean up any 3rd party bindings to the DOM + * element before it is removed. + * + * ### Methods + * - `controller(name)` - retrieves the controller of the current element or its parent. By default + * retrieves controller associated with the `ngController` directive. If `name` is provided as + * camelCase directive name, then the controller for this directive will be retrieved (e.g. + * `'ngModel'`). + * - `injector()` - retrieves the injector of the current element or its parent. + * - `scope()` - retrieves the {@link ng.$rootScope.Scope scope} of the current + * element or its parent. + * - `isolateScope()` - retrieves an isolate {@link ng.$rootScope.Scope scope} if one is attached directly to the + * current element. This getter should be used only on elements that contain a directive which starts a new isolate + * scope. Calling `scope()` on this element always returns the original non-isolate scope. + * - `inheritedData()` - same as `data()`, but walks up the DOM until a value is found or the top + * parent element is reached. + * + * @param {string|DOMElement} element HTML string or DOMElement to be wrapped into jQuery. + * @returns {Object} jQuery object. + */ + +var jqCache = JQLite.cache = {}, + jqName = JQLite.expando = 'ng' + new Date().getTime(), + jqId = 1, + addEventListenerFn = (window.document.addEventListener + ? function(element, type, fn) {element.addEventListener(type, fn, false);} + : function(element, type, fn) {element.attachEvent('on' + type, fn);}), + removeEventListenerFn = (window.document.removeEventListener + ? function(element, type, fn) {element.removeEventListener(type, fn, false); } + : function(element, type, fn) {element.detachEvent('on' + type, fn); }); + +/* + * !!! This is an undocumented "private" function !!! + */ +var jqData = JQLite._data = function(node) { + //jQuery always returns an object on cache miss + return this.cache[node[this.expando]] || {}; +}; + +function jqNextId() { return ++jqId; } + + +var SPECIAL_CHARS_REGEXP = /([\:\-\_]+(.))/g; +var MOZ_HACK_REGEXP = /^moz([A-Z])/; +var jqLiteMinErr = minErr('jqLite'); + +/** + * Converts snake_case to camelCase. + * Also there is special case for Moz prefix starting with upper case letter. + * @param name Name to normalize + */ +function camelCase(name) { + return name. + replace(SPECIAL_CHARS_REGEXP, function(_, separator, letter, offset) { + return offset ? letter.toUpperCase() : letter; + }). + replace(MOZ_HACK_REGEXP, 'Moz$1'); +} + +///////////////////////////////////////////// +// jQuery mutation patch +// +// In conjunction with bindJQuery intercepts all jQuery's DOM destruction apis and fires a +// $destroy event on all DOM nodes being removed. +// +///////////////////////////////////////////// + +function jqLitePatchJQueryRemove(name, dispatchThis, filterElems, getterIfNoArguments) { + var originalJqFn = jQuery.fn[name]; + originalJqFn = originalJqFn.$original || originalJqFn; + removePatch.$original = originalJqFn; + jQuery.fn[name] = removePatch; + + function removePatch(param) { + // jshint -W040 + var list = filterElems && param ? [this.filter(param)] : [this], + fireEvent = dispatchThis, + set, setIndex, setLength, + element, childIndex, childLength, children; + + if (!getterIfNoArguments || param != null) { + while(list.length) { + set = list.shift(); + for(setIndex = 0, setLength = set.length; setIndex < setLength; setIndex++) { + element = jqLite(set[setIndex]); + if (fireEvent) { + element.triggerHandler('$destroy'); + } else { + fireEvent = !fireEvent; + } + for(childIndex = 0, childLength = (children = element.children()).length; + childIndex < childLength; + childIndex++) { + list.push(jQuery(children[childIndex])); + } + } + } + } + return originalJqFn.apply(this, arguments); + } +} + +var SINGLE_TAG_REGEXP = /^<(\w+)\s*\/?>(?:<\/\1>|)$/; +var HTML_REGEXP = /<|&#?\w+;/; +var TAG_NAME_REGEXP = /<([\w:]+)/; +var XHTML_TAG_REGEXP = /<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:]+)[^>]*)\/>/gi; + +var wrapMap = { + 'option': [1, ''], + + 'thead': [1, '', '
'], + 'col': [2, '', '
'], + 'tr': [2, '', '
'], + 'td': [3, '', '
'], + '_default': [0, "", ""] +}; + +wrapMap.optgroup = wrapMap.option; +wrapMap.tbody = wrapMap.tfoot = wrapMap.colgroup = wrapMap.caption = wrapMap.thead; +wrapMap.th = wrapMap.td; + +function jqLiteIsTextNode(html) { + return !HTML_REGEXP.test(html); +} + +function jqLiteBuildFragment(html, context) { + var elem, tmp, tag, wrap, + fragment = context.createDocumentFragment(), + nodes = [], i, j, jj; + + if (jqLiteIsTextNode(html)) { + // Convert non-html into a text node + nodes.push(context.createTextNode(html)); + } else { + tmp = fragment.appendChild(context.createElement('div')); + // Convert html into DOM nodes + tag = (TAG_NAME_REGEXP.exec(html) || ["", ""])[1].toLowerCase(); + wrap = wrapMap[tag] || wrapMap._default; + tmp.innerHTML = '
 
' + + wrap[1] + html.replace(XHTML_TAG_REGEXP, "<$1>") + wrap[2]; + tmp.removeChild(tmp.firstChild); + + // Descend through wrappers to the right content + i = wrap[0]; + while (i--) { + tmp = tmp.lastChild; + } + + for (j=0, jj=tmp.childNodes.length; j -1); +} + +function jqLiteRemoveClass(element, cssClasses) { + if (cssClasses && element.setAttribute) { + forEach(cssClasses.split(' '), function(cssClass) { + element.setAttribute('class', trim( + (" " + (element.getAttribute('class') || '') + " ") + .replace(/[\n\t]/g, " ") + .replace(" " + trim(cssClass) + " ", " ")) + ); + }); + } +} + +function jqLiteAddClass(element, cssClasses) { + if (cssClasses && element.setAttribute) { + var existingClasses = (' ' + (element.getAttribute('class') || '') + ' ') + .replace(/[\n\t]/g, " "); + + forEach(cssClasses.split(' '), function(cssClass) { + cssClass = trim(cssClass); + if (existingClasses.indexOf(' ' + cssClass + ' ') === -1) { + existingClasses += cssClass + ' '; + } + }); + + element.setAttribute('class', trim(existingClasses)); + } +} + +function jqLiteAddNodes(root, elements) { + if (elements) { + elements = (!elements.nodeName && isDefined(elements.length) && !isWindow(elements)) + ? elements + : [ elements ]; + for(var i=0; i < elements.length; i++) { + root.push(elements[i]); + } + } +} + +function jqLiteController(element, name) { + return jqLiteInheritedData(element, '$' + (name || 'ngController' ) + 'Controller'); +} + +function jqLiteInheritedData(element, name, value) { + element = jqLite(element); + + // if element is the document object work with the html element instead + // this makes $(document).scope() possible + if(element[0].nodeType == 9) { + element = element.find('html'); + } + var names = isArray(name) ? name : [name]; + + while (element.length) { + var node = element[0]; + for (var i = 0, ii = names.length; i < ii; i++) { + if ((value = element.data(names[i])) !== undefined) return value; + } + + // If dealing with a document fragment node with a host element, and no parent, use the host + // element as the parent. This enables directives within a Shadow DOM or polyfilled Shadow DOM + // to lookup parent controllers. + element = jqLite(node.parentNode || (node.nodeType === 11 && node.host)); + } +} + +function jqLiteEmpty(element) { + for (var i = 0, childNodes = element.childNodes; i < childNodes.length; i++) { + jqLiteDealoc(childNodes[i]); + } + while (element.firstChild) { + element.removeChild(element.firstChild); + } +} + +////////////////////////////////////////// +// Functions which are declared directly. +////////////////////////////////////////// +var JQLitePrototype = JQLite.prototype = { + ready: function(fn) { + var fired = false; + + function trigger() { + if (fired) return; + fired = true; + fn(); + } + + // check if document already is loaded + if (document.readyState === 'complete'){ + setTimeout(trigger); + } else { + this.on('DOMContentLoaded', trigger); // works for modern browsers and IE9 + // we can not use jqLite since we are not done loading and jQuery could be loaded later. + // jshint -W064 + JQLite(window).on('load', trigger); // fallback to window.onload for others + // jshint +W064 + } + }, + toString: function() { + var value = []; + forEach(this, function(e){ value.push('' + e);}); + return '[' + value.join(', ') + ']'; + }, + + eq: function(index) { + return (index >= 0) ? jqLite(this[index]) : jqLite(this[this.length + index]); + }, + + length: 0, + push: push, + sort: [].sort, + splice: [].splice +}; + +////////////////////////////////////////// +// Functions iterating getter/setters. +// these functions return self on setter and +// value on get. +////////////////////////////////////////// +var BOOLEAN_ATTR = {}; +forEach('multiple,selected,checked,disabled,readOnly,required,open'.split(','), function(value) { + BOOLEAN_ATTR[lowercase(value)] = value; +}); +var BOOLEAN_ELEMENTS = {}; +forEach('input,select,option,textarea,button,form,details'.split(','), function(value) { + BOOLEAN_ELEMENTS[uppercase(value)] = true; +}); + +function getBooleanAttrName(element, name) { + // check dom last since we will most likely fail on name + var booleanAttr = BOOLEAN_ATTR[name.toLowerCase()]; + + // booleanAttr is here twice to minimize DOM access + return booleanAttr && BOOLEAN_ELEMENTS[element.nodeName] && booleanAttr; +} + +forEach({ + data: jqLiteData, + inheritedData: jqLiteInheritedData, + + scope: function(element) { + // Can't use jqLiteData here directly so we stay compatible with jQuery! + return jqLite(element).data('$scope') || jqLiteInheritedData(element.parentNode || element, ['$isolateScope', '$scope']); + }, + + isolateScope: function(element) { + // Can't use jqLiteData here directly so we stay compatible with jQuery! + return jqLite(element).data('$isolateScope') || jqLite(element).data('$isolateScopeNoTemplate'); + }, + + controller: jqLiteController, + + injector: function(element) { + return jqLiteInheritedData(element, '$injector'); + }, + + removeAttr: function(element,name) { + element.removeAttribute(name); + }, + + hasClass: jqLiteHasClass, + + css: function(element, name, value) { + name = camelCase(name); + + if (isDefined(value)) { + element.style[name] = value; + } else { + var val; + + if (msie <= 8) { + // this is some IE specific weirdness that jQuery 1.6.4 does not sure why + val = element.currentStyle && element.currentStyle[name]; + if (val === '') val = 'auto'; + } + + val = val || element.style[name]; + + if (msie <= 8) { + // jquery weirdness :-/ + val = (val === '') ? undefined : val; + } + + return val; + } + }, + + attr: function(element, name, value){ + var lowercasedName = lowercase(name); + if (BOOLEAN_ATTR[lowercasedName]) { + if (isDefined(value)) { + if (!!value) { + element[name] = true; + element.setAttribute(name, lowercasedName); + } else { + element[name] = false; + element.removeAttribute(lowercasedName); + } + } else { + return (element[name] || + (element.attributes.getNamedItem(name)|| noop).specified) + ? lowercasedName + : undefined; + } + } else if (isDefined(value)) { + element.setAttribute(name, value); + } else if (element.getAttribute) { + // the extra argument "2" is to get the right thing for a.href in IE, see jQuery code + // some elements (e.g. Document) don't have get attribute, so return undefined + var ret = element.getAttribute(name, 2); + // normalize non-existing attributes to undefined (as jQuery) + return ret === null ? undefined : ret; + } + }, + + prop: function(element, name, value) { + if (isDefined(value)) { + element[name] = value; + } else { + return element[name]; + } + }, + + text: (function() { + var NODE_TYPE_TEXT_PROPERTY = []; + if (msie < 9) { + NODE_TYPE_TEXT_PROPERTY[1] = 'innerText'; /** Element **/ + NODE_TYPE_TEXT_PROPERTY[3] = 'nodeValue'; /** Text **/ + } else { + NODE_TYPE_TEXT_PROPERTY[1] = /** Element **/ + NODE_TYPE_TEXT_PROPERTY[3] = 'textContent'; /** Text **/ + } + getText.$dv = ''; + return getText; + + function getText(element, value) { + var textProp = NODE_TYPE_TEXT_PROPERTY[element.nodeType]; + if (isUndefined(value)) { + return textProp ? element[textProp] : ''; + } + element[textProp] = value; + } + })(), + + val: function(element, value) { + if (isUndefined(value)) { + if (nodeName_(element) === 'SELECT' && element.multiple) { + var result = []; + forEach(element.options, function (option) { + if (option.selected) { + result.push(option.value || option.text); + } + }); + return result.length === 0 ? null : result; + } + return element.value; + } + element.value = value; + }, + + html: function(element, value) { + if (isUndefined(value)) { + return element.innerHTML; + } + for (var i = 0, childNodes = element.childNodes; i < childNodes.length; i++) { + jqLiteDealoc(childNodes[i]); + } + element.innerHTML = value; + }, + + empty: jqLiteEmpty +}, function(fn, name){ + /** + * Properties: writes return selection, reads return first value + */ + JQLite.prototype[name] = function(arg1, arg2) { + var i, key; + + // jqLiteHasClass has only two arguments, but is a getter-only fn, so we need to special-case it + // in a way that survives minification. + // jqLiteEmpty takes no arguments but is a setter. + if (fn !== jqLiteEmpty && + (((fn.length == 2 && (fn !== jqLiteHasClass && fn !== jqLiteController)) ? arg1 : arg2) === undefined)) { + if (isObject(arg1)) { + + // we are a write, but the object properties are the key/values + for (i = 0; i < this.length; i++) { + if (fn === jqLiteData) { + // data() takes the whole object in jQuery + fn(this[i], arg1); + } else { + for (key in arg1) { + fn(this[i], key, arg1[key]); + } + } + } + // return self for chaining + return this; + } else { + // we are a read, so read the first child. + var value = fn.$dv; + // Only if we have $dv do we iterate over all, otherwise it is just the first element. + var jj = (value === undefined) ? Math.min(this.length, 1) : this.length; + for (var j = 0; j < jj; j++) { + var nodeValue = fn(this[j], arg1, arg2); + value = value ? value + nodeValue : nodeValue; + } + return value; + } + } else { + // we are a write, so apply to all children + for (i = 0; i < this.length; i++) { + fn(this[i], arg1, arg2); + } + // return self for chaining + return this; + } + }; +}); + +function createEventHandler(element, events) { + var eventHandler = function (event, type) { + if (!event.preventDefault) { + event.preventDefault = function() { + event.returnValue = false; //ie + }; + } + + if (!event.stopPropagation) { + event.stopPropagation = function() { + event.cancelBubble = true; //ie + }; + } + + if (!event.target) { + event.target = event.srcElement || document; + } + + if (isUndefined(event.defaultPrevented)) { + var prevent = event.preventDefault; + event.preventDefault = function() { + event.defaultPrevented = true; + prevent.call(event); + }; + event.defaultPrevented = false; + } + + event.isDefaultPrevented = function() { + return event.defaultPrevented || event.returnValue === false; + }; + + // Copy event handlers in case event handlers array is modified during execution. + var eventHandlersCopy = shallowCopy(events[type || event.type] || []); + + forEach(eventHandlersCopy, function(fn) { + fn.call(element, event); + }); + + // Remove monkey-patched methods (IE), + // as they would cause memory leaks in IE8. + if (msie <= 8) { + // IE7/8 does not allow to delete property on native object + event.preventDefault = null; + event.stopPropagation = null; + event.isDefaultPrevented = null; + } else { + // It shouldn't affect normal browsers (native methods are defined on prototype). + delete event.preventDefault; + delete event.stopPropagation; + delete event.isDefaultPrevented; + } + }; + eventHandler.elem = element; + return eventHandler; +} + +////////////////////////////////////////// +// Functions iterating traversal. +// These functions chain results into a single +// selector. +////////////////////////////////////////// +forEach({ + removeData: jqLiteRemoveData, + + dealoc: jqLiteDealoc, + + on: function onFn(element, type, fn, unsupported){ + if (isDefined(unsupported)) throw jqLiteMinErr('onargs', 'jqLite#on() does not support the `selector` or `eventData` parameters'); + + var events = jqLiteExpandoStore(element, 'events'), + handle = jqLiteExpandoStore(element, 'handle'); + + if (!events) jqLiteExpandoStore(element, 'events', events = {}); + if (!handle) jqLiteExpandoStore(element, 'handle', handle = createEventHandler(element, events)); + + forEach(type.split(' '), function(type){ + var eventFns = events[type]; + + if (!eventFns) { + if (type == 'mouseenter' || type == 'mouseleave') { + var contains = document.body.contains || document.body.compareDocumentPosition ? + function( a, b ) { + // jshint bitwise: false + var adown = a.nodeType === 9 ? a.documentElement : a, + bup = b && b.parentNode; + return a === bup || !!( bup && bup.nodeType === 1 && ( + adown.contains ? + adown.contains( bup ) : + a.compareDocumentPosition && a.compareDocumentPosition( bup ) & 16 + )); + } : + function( a, b ) { + if ( b ) { + while ( (b = b.parentNode) ) { + if ( b === a ) { + return true; + } + } + } + return false; + }; + + events[type] = []; + + // Refer to jQuery's implementation of mouseenter & mouseleave + // Read about mouseenter and mouseleave: + // http://www.quirksmode.org/js/events_mouse.html#link8 + var eventmap = { mouseleave : "mouseout", mouseenter : "mouseover"}; + + onFn(element, eventmap[type], function(event) { + var target = this, related = event.relatedTarget; + // For mousenter/leave call the handler if related is outside the target. + // NB: No relatedTarget if the mouse left/entered the browser window + if ( !related || (related !== target && !contains(target, related)) ){ + handle(event, type); + } + }); + + } else { + addEventListenerFn(element, type, handle); + events[type] = []; + } + eventFns = events[type]; + } + eventFns.push(fn); + }); + }, + + off: jqLiteOff, + + one: function(element, type, fn) { + element = jqLite(element); + + //add the listener twice so that when it is called + //you can remove the original function and still be + //able to call element.off(ev, fn) normally + element.on(type, function onFn() { + element.off(type, fn); + element.off(type, onFn); + }); + element.on(type, fn); + }, + + replaceWith: function(element, replaceNode) { + var index, parent = element.parentNode; + jqLiteDealoc(element); + forEach(new JQLite(replaceNode), function(node){ + if (index) { + parent.insertBefore(node, index.nextSibling); + } else { + parent.replaceChild(node, element); + } + index = node; + }); + }, + + children: function(element) { + var children = []; + forEach(element.childNodes, function(element){ + if (element.nodeType === 1) + children.push(element); + }); + return children; + }, + + contents: function(element) { + return element.contentDocument || element.childNodes || []; + }, + + append: function(element, node) { + forEach(new JQLite(node), function(child){ + if (element.nodeType === 1 || element.nodeType === 11) { + element.appendChild(child); + } + }); + }, + + prepend: function(element, node) { + if (element.nodeType === 1) { + var index = element.firstChild; + forEach(new JQLite(node), function(child){ + element.insertBefore(child, index); + }); + } + }, + + wrap: function(element, wrapNode) { + wrapNode = jqLite(wrapNode)[0]; + var parent = element.parentNode; + if (parent) { + parent.replaceChild(wrapNode, element); + } + wrapNode.appendChild(element); + }, + + remove: function(element) { + jqLiteDealoc(element); + var parent = element.parentNode; + if (parent) parent.removeChild(element); + }, + + after: function(element, newElement) { + var index = element, parent = element.parentNode; + forEach(new JQLite(newElement), function(node){ + parent.insertBefore(node, index.nextSibling); + index = node; + }); + }, + + addClass: jqLiteAddClass, + removeClass: jqLiteRemoveClass, + + toggleClass: function(element, selector, condition) { + if (selector) { + forEach(selector.split(' '), function(className){ + var classCondition = condition; + if (isUndefined(classCondition)) { + classCondition = !jqLiteHasClass(element, className); + } + (classCondition ? jqLiteAddClass : jqLiteRemoveClass)(element, className); + }); + } + }, + + parent: function(element) { + var parent = element.parentNode; + return parent && parent.nodeType !== 11 ? parent : null; + }, + + next: function(element) { + if (element.nextElementSibling) { + return element.nextElementSibling; + } + + // IE8 doesn't have nextElementSibling + var elm = element.nextSibling; + while (elm != null && elm.nodeType !== 1) { + elm = elm.nextSibling; + } + return elm; + }, + + find: function(element, selector) { + if (element.getElementsByTagName) { + return element.getElementsByTagName(selector); + } else { + return []; + } + }, + + clone: jqLiteClone, + + triggerHandler: function(element, eventName, eventData) { + var eventFns = (jqLiteExpandoStore(element, 'events') || {})[eventName]; + + eventData = eventData || []; + + var event = [{ + preventDefault: noop, + stopPropagation: noop + }]; + + forEach(eventFns, function(fn) { + fn.apply(element, event.concat(eventData)); + }); + } +}, function(fn, name){ + /** + * chaining functions + */ + JQLite.prototype[name] = function(arg1, arg2, arg3) { + var value; + for(var i=0; i < this.length; i++) { + if (isUndefined(value)) { + value = fn(this[i], arg1, arg2, arg3); + if (isDefined(value)) { + // any function which returns a value needs to be wrapped + value = jqLite(value); + } + } else { + jqLiteAddNodes(value, fn(this[i], arg1, arg2, arg3)); + } + } + return isDefined(value) ? value : this; + }; + + // bind legacy bind/unbind to on/off + JQLite.prototype.bind = JQLite.prototype.on; + JQLite.prototype.unbind = JQLite.prototype.off; +}); + +/** + * Computes a hash of an 'obj'. + * Hash of a: + * string is string + * number is number as string + * object is either result of calling $$hashKey function on the object or uniquely generated id, + * that is also assigned to the $$hashKey property of the object. + * + * @param obj + * @returns {string} hash string such that the same input will have the same hash string. + * The resulting string key is in 'type:hashKey' format. + */ +function hashKey(obj) { + var objType = typeof obj, + key; + + if (objType == 'object' && obj !== null) { + if (typeof (key = obj.$$hashKey) == 'function') { + // must invoke on object to keep the right this + key = obj.$$hashKey(); + } else if (key === undefined) { + key = obj.$$hashKey = nextUid(); + } + } else { + key = obj; + } + + return objType + ':' + key; +} + +/** + * HashMap which can use objects as keys + */ +function HashMap(array){ + forEach(array, this.put, this); +} +HashMap.prototype = { + /** + * Store key value pair + * @param key key to store can be any type + * @param value value to store can be any type + */ + put: function(key, value) { + this[hashKey(key)] = value; + }, + + /** + * @param key + * @returns {Object} the value for the key + */ + get: function(key) { + return this[hashKey(key)]; + }, + + /** + * Remove the key/value pair + * @param key + */ + remove: function(key) { + var value = this[key = hashKey(key)]; + delete this[key]; + return value; + } +}; + +/** + * @ngdoc function + * @module ng + * @name angular.injector + * @kind function + * + * @description + * Creates an injector function that can be used for retrieving services as well as for + * dependency injection (see {@link guide/di dependency injection}). + * + + * @param {Array.} modules A list of module functions or their aliases. See + * {@link angular.module}. The `ng` module must be explicitly added. + * @returns {function()} Injector function. See {@link auto.$injector $injector}. + * + * @example + * Typical usage + * ```js + * // create an injector + * var $injector = angular.injector(['ng']); + * + * // use the injector to kick off your application + * // use the type inference to auto inject arguments, or use implicit injection + * $injector.invoke(function($rootScope, $compile, $document){ + * $compile($document)($rootScope); + * $rootScope.$digest(); + * }); + * ``` + * + * Sometimes you want to get access to the injector of a currently running Angular app + * from outside Angular. Perhaps, you want to inject and compile some markup after the + * application has been bootstrapped. You can do this using the extra `injector()` added + * to JQuery/jqLite elements. See {@link angular.element}. + * + * *This is fairly rare but could be the case if a third party library is injecting the + * markup.* + * + * In the following example a new block of HTML containing a `ng-controller` + * directive is added to the end of the document body by JQuery. We then compile and link + * it into the current AngularJS scope. + * + * ```js + * var $div = $('
{{content.label}}
'); + * $(document.body).append($div); + * + * angular.element(document).injector().invoke(function($compile) { + * var scope = angular.element($div).scope(); + * $compile($div)(scope); + * }); + * ``` + */ + + +/** + * @ngdoc module + * @name auto + * @description + * + * Implicit module which gets automatically added to each {@link auto.$injector $injector}. + */ + +var FN_ARGS = /^function\s*[^\(]*\(\s*([^\)]*)\)/m; +var FN_ARG_SPLIT = /,/; +var FN_ARG = /^\s*(_?)(\S+?)\1\s*$/; +var STRIP_COMMENTS = /((\/\/.*$)|(\/\*[\s\S]*?\*\/))/mg; +var $injectorMinErr = minErr('$injector'); +function annotate(fn) { + var $inject, + fnText, + argDecl, + last; + + if (typeof fn == 'function') { + if (!($inject = fn.$inject)) { + $inject = []; + if (fn.length) { + fnText = fn.toString().replace(STRIP_COMMENTS, ''); + argDecl = fnText.match(FN_ARGS); + forEach(argDecl[1].split(FN_ARG_SPLIT), function(arg){ + arg.replace(FN_ARG, function(all, underscore, name){ + $inject.push(name); + }); + }); + } + fn.$inject = $inject; + } + } else if (isArray(fn)) { + last = fn.length - 1; + assertArgFn(fn[last], 'fn'); + $inject = fn.slice(0, last); + } else { + assertArgFn(fn, 'fn', true); + } + return $inject; +} + +/////////////////////////////////////// + +/** + * @ngdoc service + * @name $injector + * @kind function + * + * @description + * + * `$injector` is used to retrieve object instances as defined by + * {@link auto.$provide provider}, instantiate types, invoke methods, + * and load modules. + * + * The following always holds true: + * + * ```js + * var $injector = angular.injector(); + * expect($injector.get('$injector')).toBe($injector); + * expect($injector.invoke(function($injector){ + * return $injector; + * }).toBe($injector); + * ``` + * + * # Injection Function Annotation + * + * JavaScript does not have annotations, and annotations are needed for dependency injection. The + * following are all valid ways of annotating function with injection arguments and are equivalent. + * + * ```js + * // inferred (only works if code not minified/obfuscated) + * $injector.invoke(function(serviceA){}); + * + * // annotated + * function explicit(serviceA) {}; + * explicit.$inject = ['serviceA']; + * $injector.invoke(explicit); + * + * // inline + * $injector.invoke(['serviceA', function(serviceA){}]); + * ``` + * + * ## Inference + * + * In JavaScript calling `toString()` on a function returns the function definition. The definition + * can then be parsed and the function arguments can be extracted. *NOTE:* This does not work with + * minification, and obfuscation tools since these tools change the argument names. + * + * ## `$inject` Annotation + * By adding an `$inject` property onto a function the injection parameters can be specified. + * + * ## Inline + * As an array of injection names, where the last item in the array is the function to call. + */ + +/** + * @ngdoc method + * @name $injector#get + * + * @description + * Return an instance of the service. + * + * @param {string} name The name of the instance to retrieve. + * @return {*} The instance. + */ + +/** + * @ngdoc method + * @name $injector#invoke + * + * @description + * Invoke the method and supply the method arguments from the `$injector`. + * + * @param {!Function} fn The function to invoke. Function parameters are injected according to the + * {@link guide/di $inject Annotation} rules. + * @param {Object=} self The `this` for the invoked method. + * @param {Object=} locals Optional object. If preset then any argument names are read from this + * object first, before the `$injector` is consulted. + * @returns {*} the value returned by the invoked `fn` function. + */ + +/** + * @ngdoc method + * @name $injector#has + * + * @description + * Allows the user to query if the particular service exists. + * + * @param {string} Name of the service to query. + * @returns {boolean} returns true if injector has given service. + */ + +/** + * @ngdoc method + * @name $injector#instantiate + * @description + * Create a new instance of JS type. The method takes a constructor function, invokes the new + * operator, and supplies all of the arguments to the constructor function as specified by the + * constructor annotation. + * + * @param {Function} Type Annotated constructor function. + * @param {Object=} locals Optional object. If preset then any argument names are read from this + * object first, before the `$injector` is consulted. + * @returns {Object} new instance of `Type`. + */ + +/** + * @ngdoc method + * @name $injector#annotate + * + * @description + * Returns an array of service names which the function is requesting for injection. This API is + * used by the injector to determine which services need to be injected into the function when the + * function is invoked. There are three ways in which the function can be annotated with the needed + * dependencies. + * + * # Argument names + * + * The simplest form is to extract the dependencies from the arguments of the function. This is done + * by converting the function into a string using `toString()` method and extracting the argument + * names. + * ```js + * // Given + * function MyController($scope, $route) { + * // ... + * } + * + * // Then + * expect(injector.annotate(MyController)).toEqual(['$scope', '$route']); + * ``` + * + * This method does not work with code minification / obfuscation. For this reason the following + * annotation strategies are supported. + * + * # The `$inject` property + * + * If a function has an `$inject` property and its value is an array of strings, then the strings + * represent names of services to be injected into the function. + * ```js + * // Given + * var MyController = function(obfuscatedScope, obfuscatedRoute) { + * // ... + * } + * // Define function dependencies + * MyController['$inject'] = ['$scope', '$route']; + * + * // Then + * expect(injector.annotate(MyController)).toEqual(['$scope', '$route']); + * ``` + * + * # The array notation + * + * It is often desirable to inline Injected functions and that's when setting the `$inject` property + * is very inconvenient. In these situations using the array notation to specify the dependencies in + * a way that survives minification is a better choice: + * + * ```js + * // We wish to write this (not minification / obfuscation safe) + * injector.invoke(function($compile, $rootScope) { + * // ... + * }); + * + * // We are forced to write break inlining + * var tmpFn = function(obfuscatedCompile, obfuscatedRootScope) { + * // ... + * }; + * tmpFn.$inject = ['$compile', '$rootScope']; + * injector.invoke(tmpFn); + * + * // To better support inline function the inline annotation is supported + * injector.invoke(['$compile', '$rootScope', function(obfCompile, obfRootScope) { + * // ... + * }]); + * + * // Therefore + * expect(injector.annotate( + * ['$compile', '$rootScope', function(obfus_$compile, obfus_$rootScope) {}]) + * ).toEqual(['$compile', '$rootScope']); + * ``` + * + * @param {Function|Array.} fn Function for which dependent service names need to + * be retrieved as described above. + * + * @returns {Array.} The names of the services which the function requires. + */ + + + + +/** + * @ngdoc object + * @name $provide + * + * @description + * + * The {@link auto.$provide $provide} service has a number of methods for registering components + * with the {@link auto.$injector $injector}. Many of these functions are also exposed on + * {@link angular.Module}. + * + * An Angular **service** is a singleton object created by a **service factory**. These **service + * factories** are functions which, in turn, are created by a **service provider**. + * The **service providers** are constructor functions. When instantiated they must contain a + * property called `$get`, which holds the **service factory** function. + * + * When you request a service, the {@link auto.$injector $injector} is responsible for finding the + * correct **service provider**, instantiating it and then calling its `$get` **service factory** + * function to get the instance of the **service**. + * + * Often services have no configuration options and there is no need to add methods to the service + * provider. The provider will be no more than a constructor function with a `$get` property. For + * these cases the {@link auto.$provide $provide} service has additional helper methods to register + * services without specifying a provider. + * + * * {@link auto.$provide#provider provider(provider)} - registers a **service provider** with the + * {@link auto.$injector $injector} + * * {@link auto.$provide#constant constant(obj)} - registers a value/object that can be accessed by + * providers and services. + * * {@link auto.$provide#value value(obj)} - registers a value/object that can only be accessed by + * services, not providers. + * * {@link auto.$provide#factory factory(fn)} - registers a service **factory function**, `fn`, + * that will be wrapped in a **service provider** object, whose `$get` property will contain the + * given factory function. + * * {@link auto.$provide#service service(class)} - registers a **constructor function**, `class` + * that will be wrapped in a **service provider** object, whose `$get` property will instantiate + * a new object using the given constructor function. + * + * See the individual methods for more information and examples. + */ + +/** + * @ngdoc method + * @name $provide#provider + * @description + * + * Register a **provider function** with the {@link auto.$injector $injector}. Provider functions + * are constructor functions, whose instances are responsible for "providing" a factory for a + * service. + * + * Service provider names start with the name of the service they provide followed by `Provider`. + * For example, the {@link ng.$log $log} service has a provider called + * {@link ng.$logProvider $logProvider}. + * + * Service provider objects can have additional methods which allow configuration of the provider + * and its service. Importantly, you can configure what kind of service is created by the `$get` + * method, or how that service will act. For example, the {@link ng.$logProvider $logProvider} has a + * method {@link ng.$logProvider#debugEnabled debugEnabled} + * which lets you specify whether the {@link ng.$log $log} service will log debug messages to the + * console or not. + * + * @param {string} name The name of the instance. NOTE: the provider will be available under `name + + 'Provider'` key. + * @param {(Object|function())} provider If the provider is: + * + * - `Object`: then it should have a `$get` method. The `$get` method will be invoked using + * {@link auto.$injector#invoke $injector.invoke()} when an instance needs to be created. + * - `Constructor`: a new instance of the provider will be created using + * {@link auto.$injector#instantiate $injector.instantiate()}, then treated as `object`. + * + * @returns {Object} registered provider instance + + * @example + * + * The following example shows how to create a simple event tracking service and register it using + * {@link auto.$provide#provider $provide.provider()}. + * + * ```js + * // Define the eventTracker provider + * function EventTrackerProvider() { + * var trackingUrl = '/track'; + * + * // A provider method for configuring where the tracked events should been saved + * this.setTrackingUrl = function(url) { + * trackingUrl = url; + * }; + * + * // The service factory function + * this.$get = ['$http', function($http) { + * var trackedEvents = {}; + * return { + * // Call this to track an event + * event: function(event) { + * var count = trackedEvents[event] || 0; + * count += 1; + * trackedEvents[event] = count; + * return count; + * }, + * // Call this to save the tracked events to the trackingUrl + * save: function() { + * $http.post(trackingUrl, trackedEvents); + * } + * }; + * }]; + * } + * + * describe('eventTracker', function() { + * var postSpy; + * + * beforeEach(module(function($provide) { + * // Register the eventTracker provider + * $provide.provider('eventTracker', EventTrackerProvider); + * })); + * + * beforeEach(module(function(eventTrackerProvider) { + * // Configure eventTracker provider + * eventTrackerProvider.setTrackingUrl('/custom-track'); + * })); + * + * it('tracks events', inject(function(eventTracker) { + * expect(eventTracker.event('login')).toEqual(1); + * expect(eventTracker.event('login')).toEqual(2); + * })); + * + * it('saves to the tracking url', inject(function(eventTracker, $http) { + * postSpy = spyOn($http, 'post'); + * eventTracker.event('login'); + * eventTracker.save(); + * expect(postSpy).toHaveBeenCalled(); + * expect(postSpy.mostRecentCall.args[0]).not.toEqual('/track'); + * expect(postSpy.mostRecentCall.args[0]).toEqual('/custom-track'); + * expect(postSpy.mostRecentCall.args[1]).toEqual({ 'login': 1 }); + * })); + * }); + * ``` + */ + +/** + * @ngdoc method + * @name $provide#factory + * @description + * + * Register a **service factory**, which will be called to return the service instance. + * This is short for registering a service where its provider consists of only a `$get` property, + * which is the given service factory function. + * You should use {@link auto.$provide#factory $provide.factory(getFn)} if you do not need to + * configure your service in a provider. + * + * @param {string} name The name of the instance. + * @param {function()} $getFn The $getFn for the instance creation. Internally this is a short hand + * for `$provide.provider(name, {$get: $getFn})`. + * @returns {Object} registered provider instance + * + * @example + * Here is an example of registering a service + * ```js + * $provide.factory('ping', ['$http', function($http) { + * return function ping() { + * return $http.send('/ping'); + * }; + * }]); + * ``` + * You would then inject and use this service like this: + * ```js + * someModule.controller('Ctrl', ['ping', function(ping) { + * ping(); + * }]); + * ``` + */ + + +/** + * @ngdoc method + * @name $provide#service + * @description + * + * Register a **service constructor**, which will be invoked with `new` to create the service + * instance. + * This is short for registering a service where its provider's `$get` property is the service + * constructor function that will be used to instantiate the service instance. + * + * You should use {@link auto.$provide#service $provide.service(class)} if you define your service + * as a type/class. + * + * @param {string} name The name of the instance. + * @param {Function} constructor A class (constructor function) that will be instantiated. + * @returns {Object} registered provider instance + * + * @example + * Here is an example of registering a service using + * {@link auto.$provide#service $provide.service(class)}. + * ```js + * var Ping = function($http) { + * this.$http = $http; + * }; + * + * Ping.$inject = ['$http']; + * + * Ping.prototype.send = function() { + * return this.$http.get('/ping'); + * }; + * $provide.service('ping', Ping); + * ``` + * You would then inject and use this service like this: + * ```js + * someModule.controller('Ctrl', ['ping', function(ping) { + * ping.send(); + * }]); + * ``` + */ + + +/** + * @ngdoc method + * @name $provide#value + * @description + * + * Register a **value service** with the {@link auto.$injector $injector}, such as a string, a + * number, an array, an object or a function. This is short for registering a service where its + * provider's `$get` property is a factory function that takes no arguments and returns the **value + * service**. + * + * Value services are similar to constant services, except that they cannot be injected into a + * module configuration function (see {@link angular.Module#config}) but they can be overridden by + * an Angular + * {@link auto.$provide#decorator decorator}. + * + * @param {string} name The name of the instance. + * @param {*} value The value. + * @returns {Object} registered provider instance + * + * @example + * Here are some examples of creating value services. + * ```js + * $provide.value('ADMIN_USER', 'admin'); + * + * $provide.value('RoleLookup', { admin: 0, writer: 1, reader: 2 }); + * + * $provide.value('halfOf', function(value) { + * return value / 2; + * }); + * ``` + */ + + +/** + * @ngdoc method + * @name $provide#constant + * @description + * + * Register a **constant service**, such as a string, a number, an array, an object or a function, + * with the {@link auto.$injector $injector}. Unlike {@link auto.$provide#value value} it can be + * injected into a module configuration function (see {@link angular.Module#config}) and it cannot + * be overridden by an Angular {@link auto.$provide#decorator decorator}. + * + * @param {string} name The name of the constant. + * @param {*} value The constant value. + * @returns {Object} registered instance + * + * @example + * Here a some examples of creating constants: + * ```js + * $provide.constant('SHARD_HEIGHT', 306); + * + * $provide.constant('MY_COLOURS', ['red', 'blue', 'grey']); + * + * $provide.constant('double', function(value) { + * return value * 2; + * }); + * ``` + */ + + +/** + * @ngdoc method + * @name $provide#decorator + * @description + * + * Register a **service decorator** with the {@link auto.$injector $injector}. A service decorator + * intercepts the creation of a service, allowing it to override or modify the behaviour of the + * service. The object returned by the decorator may be the original service, or a new service + * object which replaces or wraps and delegates to the original service. + * + * @param {string} name The name of the service to decorate. + * @param {function()} decorator This function will be invoked when the service needs to be + * instantiated and should return the decorated service instance. The function is called using + * the {@link auto.$injector#invoke injector.invoke} method and is therefore fully injectable. + * Local injection arguments: + * + * * `$delegate` - The original service instance, which can be monkey patched, configured, + * decorated or delegated to. + * + * @example + * Here we decorate the {@link ng.$log $log} service to convert warnings to errors by intercepting + * calls to {@link ng.$log#error $log.warn()}. + * ```js + * $provide.decorator('$log', ['$delegate', function($delegate) { + * $delegate.warn = $delegate.error; + * return $delegate; + * }]); + * ``` + */ + + +function createInjector(modulesToLoad) { + var INSTANTIATING = {}, + providerSuffix = 'Provider', + path = [], + loadedModules = new HashMap(), + providerCache = { + $provide: { + provider: supportObject(provider), + factory: supportObject(factory), + service: supportObject(service), + value: supportObject(value), + constant: supportObject(constant), + decorator: decorator + } + }, + providerInjector = (providerCache.$injector = + createInternalInjector(providerCache, function() { + throw $injectorMinErr('unpr', "Unknown provider: {0}", path.join(' <- ')); + })), + instanceCache = {}, + instanceInjector = (instanceCache.$injector = + createInternalInjector(instanceCache, function(servicename) { + var provider = providerInjector.get(servicename + providerSuffix); + return instanceInjector.invoke(provider.$get, provider); + })); + + + forEach(loadModules(modulesToLoad), function(fn) { instanceInjector.invoke(fn || noop); }); + + return instanceInjector; + + //////////////////////////////////// + // $provider + //////////////////////////////////// + + function supportObject(delegate) { + return function(key, value) { + if (isObject(key)) { + forEach(key, reverseParams(delegate)); + } else { + return delegate(key, value); + } + }; + } + + function provider(name, provider_) { + assertNotHasOwnProperty(name, 'service'); + if (isFunction(provider_) || isArray(provider_)) { + provider_ = providerInjector.instantiate(provider_); + } + if (!provider_.$get) { + throw $injectorMinErr('pget', "Provider '{0}' must define $get factory method.", name); + } + return providerCache[name + providerSuffix] = provider_; + } + + function factory(name, factoryFn) { return provider(name, { $get: factoryFn }); } + + function service(name, constructor) { + return factory(name, ['$injector', function($injector) { + return $injector.instantiate(constructor); + }]); + } + + function value(name, val) { return factory(name, valueFn(val)); } + + function constant(name, value) { + assertNotHasOwnProperty(name, 'constant'); + providerCache[name] = value; + instanceCache[name] = value; + } + + function decorator(serviceName, decorFn) { + var origProvider = providerInjector.get(serviceName + providerSuffix), + orig$get = origProvider.$get; + + origProvider.$get = function() { + var origInstance = instanceInjector.invoke(orig$get, origProvider); + return instanceInjector.invoke(decorFn, null, {$delegate: origInstance}); + }; + } + + //////////////////////////////////// + // Module Loading + //////////////////////////////////// + function loadModules(modulesToLoad){ + var runBlocks = [], moduleFn, invokeQueue, i, ii; + forEach(modulesToLoad, function(module) { + if (loadedModules.get(module)) return; + loadedModules.put(module, true); + + try { + if (isString(module)) { + moduleFn = angularModule(module); + runBlocks = runBlocks.concat(loadModules(moduleFn.requires)).concat(moduleFn._runBlocks); + + for(invokeQueue = moduleFn._invokeQueue, i = 0, ii = invokeQueue.length; i < ii; i++) { + var invokeArgs = invokeQueue[i], + provider = providerInjector.get(invokeArgs[0]); + + provider[invokeArgs[1]].apply(provider, invokeArgs[2]); + } + } else if (isFunction(module)) { + runBlocks.push(providerInjector.invoke(module)); + } else if (isArray(module)) { + runBlocks.push(providerInjector.invoke(module)); + } else { + assertArgFn(module, 'module'); + } + } catch (e) { + if (isArray(module)) { + module = module[module.length - 1]; + } + if (e.message && e.stack && e.stack.indexOf(e.message) == -1) { + // Safari & FF's stack traces don't contain error.message content + // unlike those of Chrome and IE + // So if stack doesn't contain message, we create a new string that contains both. + // Since error.stack is read-only in Safari, I'm overriding e and not e.stack here. + /* jshint -W022 */ + e = e.message + '\n' + e.stack; + } + throw $injectorMinErr('modulerr', "Failed to instantiate module {0} due to:\n{1}", + module, e.stack || e.message || e); + } + }); + return runBlocks; + } + + //////////////////////////////////// + // internal Injector + //////////////////////////////////// + + function createInternalInjector(cache, factory) { + + function getService(serviceName) { + if (cache.hasOwnProperty(serviceName)) { + if (cache[serviceName] === INSTANTIATING) { + throw $injectorMinErr('cdep', 'Circular dependency found: {0}', path.join(' <- ')); + } + return cache[serviceName]; + } else { + try { + path.unshift(serviceName); + cache[serviceName] = INSTANTIATING; + return cache[serviceName] = factory(serviceName); + } catch (err) { + if (cache[serviceName] === INSTANTIATING) { + delete cache[serviceName]; + } + throw err; + } finally { + path.shift(); + } + } + } + + function invoke(fn, self, locals){ + var args = [], + $inject = annotate(fn), + length, i, + key; + + for(i = 0, length = $inject.length; i < length; i++) { + key = $inject[i]; + if (typeof key !== 'string') { + throw $injectorMinErr('itkn', + 'Incorrect injection token! Expected service name as string, got {0}', key); + } + args.push( + locals && locals.hasOwnProperty(key) + ? locals[key] + : getService(key) + ); + } + if (!fn.$inject) { + // this means that we must be an array. + fn = fn[length]; + } + + // http://jsperf.com/angularjs-invoke-apply-vs-switch + // #5388 + return fn.apply(self, args); + } + + function instantiate(Type, locals) { + var Constructor = function() {}, + instance, returnedValue; + + // Check if Type is annotated and use just the given function at n-1 as parameter + // e.g. someModule.factory('greeter', ['$window', function(renamed$window) {}]); + Constructor.prototype = (isArray(Type) ? Type[Type.length - 1] : Type).prototype; + instance = new Constructor(); + returnedValue = invoke(Type, instance, locals); + + return isObject(returnedValue) || isFunction(returnedValue) ? returnedValue : instance; + } + + return { + invoke: invoke, + instantiate: instantiate, + get: getService, + annotate: annotate, + has: function(name) { + return providerCache.hasOwnProperty(name + providerSuffix) || cache.hasOwnProperty(name); + } + }; + } +} + +/** + * @ngdoc service + * @name $anchorScroll + * @kind function + * @requires $window + * @requires $location + * @requires $rootScope + * + * @description + * When called, it checks current value of `$location.hash()` and scrolls to the related element, + * according to rules specified in + * [Html5 spec](http://dev.w3.org/html5/spec/Overview.html#the-indicated-part-of-the-document). + * + * It also watches the `$location.hash()` and scrolls whenever it changes to match any anchor. + * This can be disabled by calling `$anchorScrollProvider.disableAutoScrolling()`. + * + * @example + + +
+ Go to bottom + You're at the bottom! +
+
+ + function ScrollCtrl($scope, $location, $anchorScroll) { + $scope.gotoBottom = function (){ + // set the location.hash to the id of + // the element you wish to scroll to. + $location.hash('bottom'); + + // call $anchorScroll() + $anchorScroll(); + }; + } + + + #scrollArea { + height: 350px; + overflow: auto; + } + + #bottom { + display: block; + margin-top: 2000px; + } + +
+ */ +function $AnchorScrollProvider() { + + var autoScrollingEnabled = true; + + this.disableAutoScrolling = function() { + autoScrollingEnabled = false; + }; + + this.$get = ['$window', '$location', '$rootScope', function($window, $location, $rootScope) { + var document = $window.document; + + // helper function to get first anchor from a NodeList + // can't use filter.filter, as it accepts only instances of Array + // and IE can't convert NodeList to an array using [].slice + // TODO(vojta): use filter if we change it to accept lists as well + function getFirstAnchor(list) { + var result = null; + forEach(list, function(element) { + if (!result && lowercase(element.nodeName) === 'a') result = element; + }); + return result; + } + + function scroll() { + var hash = $location.hash(), elm; + + // empty hash, scroll to the top of the page + if (!hash) $window.scrollTo(0, 0); + + // element with given id + else if ((elm = document.getElementById(hash))) elm.scrollIntoView(); + + // first anchor with given name :-D + else if ((elm = getFirstAnchor(document.getElementsByName(hash)))) elm.scrollIntoView(); + + // no element and hash == 'top', scroll to the top of the page + else if (hash === 'top') $window.scrollTo(0, 0); + } + + // does not scroll when user clicks on anchor link that is currently on + // (no url change, no $location.hash() change), browser native does scroll + if (autoScrollingEnabled) { + $rootScope.$watch(function autoScrollWatch() {return $location.hash();}, + function autoScrollWatchAction() { + $rootScope.$evalAsync(scroll); + }); + } + + return scroll; + }]; +} + +var $animateMinErr = minErr('$animate'); + +/** + * @ngdoc provider + * @name $animateProvider + * + * @description + * Default implementation of $animate that doesn't perform any animations, instead just + * synchronously performs DOM + * updates and calls done() callbacks. + * + * In order to enable animations the ngAnimate module has to be loaded. + * + * To see the functional implementation check out src/ngAnimate/animate.js + */ +var $AnimateProvider = ['$provide', function($provide) { + + + this.$$selectors = {}; + + + /** + * @ngdoc method + * @name $animateProvider#register + * + * @description + * Registers a new injectable animation factory function. The factory function produces the + * animation object which contains callback functions for each event that is expected to be + * animated. + * + * * `eventFn`: `function(Element, doneFunction)` The element to animate, the `doneFunction` + * must be called once the element animation is complete. If a function is returned then the + * animation service will use this function to cancel the animation whenever a cancel event is + * triggered. + * + * + * ```js + * return { + * eventFn : function(element, done) { + * //code to run the animation + * //once complete, then run done() + * return function cancellationFunction() { + * //code to cancel the animation + * } + * } + * } + * ``` + * + * @param {string} name The name of the animation. + * @param {Function} factory The factory function that will be executed to return the animation + * object. + */ + this.register = function(name, factory) { + var key = name + '-animation'; + if (name && name.charAt(0) != '.') throw $animateMinErr('notcsel', + "Expecting class selector starting with '.' got '{0}'.", name); + this.$$selectors[name.substr(1)] = key; + $provide.factory(key, factory); + }; + + /** + * @ngdoc method + * @name $animateProvider#classNameFilter + * + * @description + * Sets and/or returns the CSS class regular expression that is checked when performing + * an animation. Upon bootstrap the classNameFilter value is not set at all and will + * therefore enable $animate to attempt to perform an animation on any element. + * When setting the classNameFilter value, animations will only be performed on elements + * that successfully match the filter expression. This in turn can boost performance + * for low-powered devices as well as applications containing a lot of structural operations. + * @param {RegExp=} expression The className expression which will be checked against all animations + * @return {RegExp} The current CSS className expression value. If null then there is no expression value + */ + this.classNameFilter = function(expression) { + if(arguments.length === 1) { + this.$$classNameFilter = (expression instanceof RegExp) ? expression : null; + } + return this.$$classNameFilter; + }; + + this.$get = ['$timeout', '$$asyncCallback', function($timeout, $$asyncCallback) { + + function async(fn) { + fn && $$asyncCallback(fn); + } + + /** + * + * @ngdoc service + * @name $animate + * @description The $animate service provides rudimentary DOM manipulation functions to + * insert, remove and move elements within the DOM, as well as adding and removing classes. + * This service is the core service used by the ngAnimate $animator service which provides + * high-level animation hooks for CSS and JavaScript. + * + * $animate is available in the AngularJS core, however, the ngAnimate module must be included + * to enable full out animation support. Otherwise, $animate will only perform simple DOM + * manipulation operations. + * + * To learn more about enabling animation support, click here to visit the {@link ngAnimate + * ngAnimate module page} as well as the {@link ngAnimate.$animate ngAnimate $animate service + * page}. + */ + return { + + /** + * + * @ngdoc method + * @name $animate#enter + * @kind function + * @description Inserts the element into the DOM either after the `after` element or within + * the `parent` element. Once complete, the done() callback will be fired (if provided). + * @param {DOMElement} element the element which will be inserted into the DOM + * @param {DOMElement} parent the parent element which will append the element as + * a child (if the after element is not present) + * @param {DOMElement} after the sibling element which will append the element + * after itself + * @param {Function=} done callback function that will be called after the element has been + * inserted into the DOM + */ + enter : function(element, parent, after, done) { + if (after) { + after.after(element); + } else { + if (!parent || !parent[0]) { + parent = after.parent(); + } + parent.append(element); + } + async(done); + }, + + /** + * + * @ngdoc method + * @name $animate#leave + * @kind function + * @description Removes the element from the DOM. Once complete, the done() callback will be + * fired (if provided). + * @param {DOMElement} element the element which will be removed from the DOM + * @param {Function=} done callback function that will be called after the element has been + * removed from the DOM + */ + leave : function(element, done) { + element.remove(); + async(done); + }, + + /** + * + * @ngdoc method + * @name $animate#move + * @kind function + * @description Moves the position of the provided element within the DOM to be placed + * either after the `after` element or inside of the `parent` element. Once complete, the + * done() callback will be fired (if provided). + * + * @param {DOMElement} element the element which will be moved around within the + * DOM + * @param {DOMElement} parent the parent element where the element will be + * inserted into (if the after element is not present) + * @param {DOMElement} after the sibling element where the element will be + * positioned next to + * @param {Function=} done the callback function (if provided) that will be fired after the + * element has been moved to its new position + */ + move : function(element, parent, after, done) { + // Do not remove element before insert. Removing will cause data associated with the + // element to be dropped. Insert will implicitly do the remove. + this.enter(element, parent, after, done); + }, + + /** + * + * @ngdoc method + * @name $animate#addClass + * @kind function + * @description Adds the provided className CSS class value to the provided element. Once + * complete, the done() callback will be fired (if provided). + * @param {DOMElement} element the element which will have the className value + * added to it + * @param {string} className the CSS class which will be added to the element + * @param {Function=} done the callback function (if provided) that will be fired after the + * className value has been added to the element + */ + addClass : function(element, className, done) { + className = isString(className) ? + className : + isArray(className) ? className.join(' ') : ''; + forEach(element, function (element) { + jqLiteAddClass(element, className); + }); + async(done); + }, + + /** + * + * @ngdoc method + * @name $animate#removeClass + * @kind function + * @description Removes the provided className CSS class value from the provided element. + * Once complete, the done() callback will be fired (if provided). + * @param {DOMElement} element the element which will have the className value + * removed from it + * @param {string} className the CSS class which will be removed from the element + * @param {Function=} done the callback function (if provided) that will be fired after the + * className value has been removed from the element + */ + removeClass : function(element, className, done) { + className = isString(className) ? + className : + isArray(className) ? className.join(' ') : ''; + forEach(element, function (element) { + jqLiteRemoveClass(element, className); + }); + async(done); + }, + + /** + * + * @ngdoc method + * @name $animate#setClass + * @kind function + * @description Adds and/or removes the given CSS classes to and from the element. + * Once complete, the done() callback will be fired (if provided). + * @param {DOMElement} element the element which will have its CSS classes changed + * removed from it + * @param {string} add the CSS classes which will be added to the element + * @param {string} remove the CSS class which will be removed from the element + * @param {Function=} done the callback function (if provided) that will be fired after the + * CSS classes have been set on the element + */ + setClass : function(element, add, remove, done) { + forEach(element, function (element) { + jqLiteAddClass(element, add); + jqLiteRemoveClass(element, remove); + }); + async(done); + }, + + enabled : noop + }; + }]; +}]; + +function $$AsyncCallbackProvider(){ + this.$get = ['$$rAF', '$timeout', function($$rAF, $timeout) { + return $$rAF.supported + ? function(fn) { return $$rAF(fn); } + : function(fn) { + return $timeout(fn, 0, false); + }; + }]; +} + +/** + * ! This is a private undocumented service ! + * + * @name $browser + * @requires $log + * @description + * This object has two goals: + * + * - hide all the global state in the browser caused by the window object + * - abstract away all the browser specific features and inconsistencies + * + * For tests we provide {@link ngMock.$browser mock implementation} of the `$browser` + * service, which can be used for convenient testing of the application without the interaction with + * the real browser apis. + */ +/** + * @param {object} window The global window object. + * @param {object} document jQuery wrapped document. + * @param {function()} XHR XMLHttpRequest constructor. + * @param {object} $log console.log or an object with the same interface. + * @param {object} $sniffer $sniffer service + */ +function Browser(window, document, $log, $sniffer) { + var self = this, + rawDocument = document[0], + location = window.location, + history = window.history, + setTimeout = window.setTimeout, + clearTimeout = window.clearTimeout, + pendingDeferIds = {}; + + self.isMock = false; + + var outstandingRequestCount = 0; + var outstandingRequestCallbacks = []; + + // TODO(vojta): remove this temporary api + self.$$completeOutstandingRequest = completeOutstandingRequest; + self.$$incOutstandingRequestCount = function() { outstandingRequestCount++; }; + + /** + * Executes the `fn` function(supports currying) and decrements the `outstandingRequestCallbacks` + * counter. If the counter reaches 0, all the `outstandingRequestCallbacks` are executed. + */ + function completeOutstandingRequest(fn) { + try { + fn.apply(null, sliceArgs(arguments, 1)); + } finally { + outstandingRequestCount--; + if (outstandingRequestCount === 0) { + while(outstandingRequestCallbacks.length) { + try { + outstandingRequestCallbacks.pop()(); + } catch (e) { + $log.error(e); + } + } + } + } + } + + /** + * @private + * Note: this method is used only by scenario runner + * TODO(vojta): prefix this method with $$ ? + * @param {function()} callback Function that will be called when no outstanding request + */ + self.notifyWhenNoOutstandingRequests = function(callback) { + // force browser to execute all pollFns - this is needed so that cookies and other pollers fire + // at some deterministic time in respect to the test runner's actions. Leaving things up to the + // regular poller would result in flaky tests. + forEach(pollFns, function(pollFn){ pollFn(); }); + + if (outstandingRequestCount === 0) { + callback(); + } else { + outstandingRequestCallbacks.push(callback); + } + }; + + ////////////////////////////////////////////////////////////// + // Poll Watcher API + ////////////////////////////////////////////////////////////// + var pollFns = [], + pollTimeout; + + /** + * @name $browser#addPollFn + * + * @param {function()} fn Poll function to add + * + * @description + * Adds a function to the list of functions that poller periodically executes, + * and starts polling if not started yet. + * + * @returns {function()} the added function + */ + self.addPollFn = function(fn) { + if (isUndefined(pollTimeout)) startPoller(100, setTimeout); + pollFns.push(fn); + return fn; + }; + + /** + * @param {number} interval How often should browser call poll functions (ms) + * @param {function()} setTimeout Reference to a real or fake `setTimeout` function. + * + * @description + * Configures the poller to run in the specified intervals, using the specified + * setTimeout fn and kicks it off. + */ + function startPoller(interval, setTimeout) { + (function check() { + forEach(pollFns, function(pollFn){ pollFn(); }); + pollTimeout = setTimeout(check, interval); + })(); + } + + ////////////////////////////////////////////////////////////// + // URL API + ////////////////////////////////////////////////////////////// + + var lastBrowserUrl = location.href, + baseElement = document.find('base'), + newLocation = null; + + /** + * @name $browser#url + * + * @description + * GETTER: + * Without any argument, this method just returns current value of location.href. + * + * SETTER: + * With at least one argument, this method sets url to new value. + * If html5 history api supported, pushState/replaceState is used, otherwise + * location.href/location.replace is used. + * Returns its own instance to allow chaining + * + * NOTE: this api is intended for use only by the $location service. Please use the + * {@link ng.$location $location service} to change url. + * + * @param {string} url New url (when used as setter) + * @param {boolean=} replace Should new url replace current history record ? + */ + self.url = function(url, replace) { + // Android Browser BFCache causes location, history reference to become stale. + if (location !== window.location) location = window.location; + if (history !== window.history) history = window.history; + + // setter + if (url) { + if (lastBrowserUrl == url) return; + lastBrowserUrl = url; + if ($sniffer.history) { + if (replace) history.replaceState(null, '', url); + else { + history.pushState(null, '', url); + // Crazy Opera Bug: http://my.opera.com/community/forums/topic.dml?id=1185462 + baseElement.attr('href', baseElement.attr('href')); + } + } else { + newLocation = url; + if (replace) { + location.replace(url); + } else { + location.href = url; + } + } + return self; + // getter + } else { + // - newLocation is a workaround for an IE7-9 issue with location.replace and location.href + // methods not updating location.href synchronously. + // - the replacement is a workaround for https://bugzilla.mozilla.org/show_bug.cgi?id=407172 + return newLocation || location.href.replace(/%27/g,"'"); + } + }; + + var urlChangeListeners = [], + urlChangeInit = false; + + function fireUrlChange() { + newLocation = null; + if (lastBrowserUrl == self.url()) return; + + lastBrowserUrl = self.url(); + forEach(urlChangeListeners, function(listener) { + listener(self.url()); + }); + } + + /** + * @name $browser#onUrlChange + * + * @description + * Register callback function that will be called, when url changes. + * + * It's only called when the url is changed from outside of angular: + * - user types different url into address bar + * - user clicks on history (forward/back) button + * - user clicks on a link + * + * It's not called when url is changed by $browser.url() method + * + * The listener gets called with new url as parameter. + * + * NOTE: this api is intended for use only by the $location service. Please use the + * {@link ng.$location $location service} to monitor url changes in angular apps. + * + * @param {function(string)} listener Listener function to be called when url changes. + * @return {function(string)} Returns the registered listener fn - handy if the fn is anonymous. + */ + self.onUrlChange = function(callback) { + // TODO(vojta): refactor to use node's syntax for events + if (!urlChangeInit) { + // We listen on both (hashchange/popstate) when available, as some browsers (e.g. Opera) + // don't fire popstate when user change the address bar and don't fire hashchange when url + // changed by push/replaceState + + // html5 history api - popstate event + if ($sniffer.history) jqLite(window).on('popstate', fireUrlChange); + // hashchange event + if ($sniffer.hashchange) jqLite(window).on('hashchange', fireUrlChange); + // polling + else self.addPollFn(fireUrlChange); + + urlChangeInit = true; + } + + urlChangeListeners.push(callback); + return callback; + }; + + ////////////////////////////////////////////////////////////// + // Misc API + ////////////////////////////////////////////////////////////// + + /** + * @name $browser#baseHref + * + * @description + * Returns current + * (always relative - without domain) + * + * @returns {string} The current base href + */ + self.baseHref = function() { + var href = baseElement.attr('href'); + return href ? href.replace(/^(https?\:)?\/\/[^\/]*/, '') : ''; + }; + + ////////////////////////////////////////////////////////////// + // Cookies API + ////////////////////////////////////////////////////////////// + var lastCookies = {}; + var lastCookieString = ''; + var cookiePath = self.baseHref(); + + /** + * @name $browser#cookies + * + * @param {string=} name Cookie name + * @param {string=} value Cookie value + * + * @description + * The cookies method provides a 'private' low level access to browser cookies. + * It is not meant to be used directly, use the $cookie service instead. + * + * The return values vary depending on the arguments that the method was called with as follows: + * + * - cookies() -> hash of all cookies, this is NOT a copy of the internal state, so do not modify + * it + * - cookies(name, value) -> set name to value, if value is undefined delete the cookie + * - cookies(name) -> the same as (name, undefined) == DELETES (no one calls it right now that + * way) + * + * @returns {Object} Hash of all cookies (if called without any parameter) + */ + self.cookies = function(name, value) { + /* global escape: false, unescape: false */ + var cookieLength, cookieArray, cookie, i, index; + + if (name) { + if (value === undefined) { + rawDocument.cookie = escape(name) + "=;path=" + cookiePath + + ";expires=Thu, 01 Jan 1970 00:00:00 GMT"; + } else { + if (isString(value)) { + cookieLength = (rawDocument.cookie = escape(name) + '=' + escape(value) + + ';path=' + cookiePath).length + 1; + + // per http://www.ietf.org/rfc/rfc2109.txt browser must allow at minimum: + // - 300 cookies + // - 20 cookies per unique domain + // - 4096 bytes per cookie + if (cookieLength > 4096) { + $log.warn("Cookie '"+ name + + "' possibly not set or overflowed because it was too large ("+ + cookieLength + " > 4096 bytes)!"); + } + } + } + } else { + if (rawDocument.cookie !== lastCookieString) { + lastCookieString = rawDocument.cookie; + cookieArray = lastCookieString.split("; "); + lastCookies = {}; + + for (i = 0; i < cookieArray.length; i++) { + cookie = cookieArray[i]; + index = cookie.indexOf('='); + if (index > 0) { //ignore nameless cookies + name = unescape(cookie.substring(0, index)); + // the first value that is seen for a cookie is the most + // specific one. values for the same cookie name that + // follow are for less specific paths. + if (lastCookies[name] === undefined) { + lastCookies[name] = unescape(cookie.substring(index + 1)); + } + } + } + } + return lastCookies; + } + }; + + + /** + * @name $browser#defer + * @param {function()} fn A function, who's execution should be deferred. + * @param {number=} [delay=0] of milliseconds to defer the function execution. + * @returns {*} DeferId that can be used to cancel the task via `$browser.defer.cancel()`. + * + * @description + * Executes a fn asynchronously via `setTimeout(fn, delay)`. + * + * Unlike when calling `setTimeout` directly, in test this function is mocked and instead of using + * `setTimeout` in tests, the fns are queued in an array, which can be programmatically flushed + * via `$browser.defer.flush()`. + * + */ + self.defer = function(fn, delay) { + var timeoutId; + outstandingRequestCount++; + timeoutId = setTimeout(function() { + delete pendingDeferIds[timeoutId]; + completeOutstandingRequest(fn); + }, delay || 0); + pendingDeferIds[timeoutId] = true; + return timeoutId; + }; + + + /** + * @name $browser#defer.cancel + * + * @description + * Cancels a deferred task identified with `deferId`. + * + * @param {*} deferId Token returned by the `$browser.defer` function. + * @returns {boolean} Returns `true` if the task hasn't executed yet and was successfully + * canceled. + */ + self.defer.cancel = function(deferId) { + if (pendingDeferIds[deferId]) { + delete pendingDeferIds[deferId]; + clearTimeout(deferId); + completeOutstandingRequest(noop); + return true; + } + return false; + }; + +} + +function $BrowserProvider(){ + this.$get = ['$window', '$log', '$sniffer', '$document', + function( $window, $log, $sniffer, $document){ + return new Browser($window, $document, $log, $sniffer); + }]; +} + +/** + * @ngdoc service + * @name $cacheFactory + * + * @description + * Factory that constructs {@link $cacheFactory.Cache Cache} objects and gives access to + * them. + * + * ```js + * + * var cache = $cacheFactory('cacheId'); + * expect($cacheFactory.get('cacheId')).toBe(cache); + * expect($cacheFactory.get('noSuchCacheId')).not.toBeDefined(); + * + * cache.put("key", "value"); + * cache.put("another key", "another value"); + * + * // We've specified no options on creation + * expect(cache.info()).toEqual({id: 'cacheId', size: 2}); + * + * ``` + * + * + * @param {string} cacheId Name or id of the newly created cache. + * @param {object=} options Options object that specifies the cache behavior. Properties: + * + * - `{number=}` `capacity` — turns the cache into LRU cache. + * + * @returns {object} Newly created cache object with the following set of methods: + * + * - `{object}` `info()` — Returns id, size, and options of cache. + * - `{{*}}` `put({string} key, {*} value)` — Puts a new key-value pair into the cache and returns + * it. + * - `{{*}}` `get({string} key)` — Returns cached value for `key` or undefined for cache miss. + * - `{void}` `remove({string} key)` — Removes a key-value pair from the cache. + * - `{void}` `removeAll()` — Removes all cached values. + * - `{void}` `destroy()` — Removes references to this cache from $cacheFactory. + * + * @example + + +
+ + + + +

Cached Values

+
+ + : + +
+ +

Cache Info

+
+ + : + +
+
+
+ + angular.module('cacheExampleApp', []). + controller('CacheController', ['$scope', '$cacheFactory', function($scope, $cacheFactory) { + $scope.keys = []; + $scope.cache = $cacheFactory('cacheId'); + $scope.put = function(key, value) { + $scope.cache.put(key, value); + $scope.keys.push(key); + }; + }]); + + + p { + margin: 10px 0 3px; + } + +
+ */ +function $CacheFactoryProvider() { + + this.$get = function() { + var caches = {}; + + function cacheFactory(cacheId, options) { + if (cacheId in caches) { + throw minErr('$cacheFactory')('iid', "CacheId '{0}' is already taken!", cacheId); + } + + var size = 0, + stats = extend({}, options, {id: cacheId}), + data = {}, + capacity = (options && options.capacity) || Number.MAX_VALUE, + lruHash = {}, + freshEnd = null, + staleEnd = null; + + /** + * @ngdoc type + * @name $cacheFactory.Cache + * + * @description + * A cache object used to store and retrieve data, primarily used by + * {@link $http $http} and the {@link ng.directive:script script} directive to cache + * templates and other data. + * + * ```js + * angular.module('superCache') + * .factory('superCache', ['$cacheFactory', function($cacheFactory) { + * return $cacheFactory('super-cache'); + * }]); + * ``` + * + * Example test: + * + * ```js + * it('should behave like a cache', inject(function(superCache) { + * superCache.put('key', 'value'); + * superCache.put('another key', 'another value'); + * + * expect(superCache.info()).toEqual({ + * id: 'super-cache', + * size: 2 + * }); + * + * superCache.remove('another key'); + * expect(superCache.get('another key')).toBeUndefined(); + * + * superCache.removeAll(); + * expect(superCache.info()).toEqual({ + * id: 'super-cache', + * size: 0 + * }); + * })); + * ``` + */ + return caches[cacheId] = { + + /** + * @ngdoc method + * @name $cacheFactory.Cache#put + * @kind function + * + * @description + * Inserts a named entry into the {@link $cacheFactory.Cache Cache} object to be + * retrieved later, and incrementing the size of the cache if the key was not already + * present in the cache. If behaving like an LRU cache, it will also remove stale + * entries from the set. + * + * It will not insert undefined values into the cache. + * + * @param {string} key the key under which the cached data is stored. + * @param {*} value the value to store alongside the key. If it is undefined, the key + * will not be stored. + * @returns {*} the value stored. + */ + put: function(key, value) { + if (capacity < Number.MAX_VALUE) { + var lruEntry = lruHash[key] || (lruHash[key] = {key: key}); + + refresh(lruEntry); + } + + if (isUndefined(value)) return; + if (!(key in data)) size++; + data[key] = value; + + if (size > capacity) { + this.remove(staleEnd.key); + } + + return value; + }, + + /** + * @ngdoc method + * @name $cacheFactory.Cache#get + * @kind function + * + * @description + * Retrieves named data stored in the {@link $cacheFactory.Cache Cache} object. + * + * @param {string} key the key of the data to be retrieved + * @returns {*} the value stored. + */ + get: function(key) { + if (capacity < Number.MAX_VALUE) { + var lruEntry = lruHash[key]; + + if (!lruEntry) return; + + refresh(lruEntry); + } + + return data[key]; + }, + + + /** + * @ngdoc method + * @name $cacheFactory.Cache#remove + * @kind function + * + * @description + * Removes an entry from the {@link $cacheFactory.Cache Cache} object. + * + * @param {string} key the key of the entry to be removed + */ + remove: function(key) { + if (capacity < Number.MAX_VALUE) { + var lruEntry = lruHash[key]; + + if (!lruEntry) return; + + if (lruEntry == freshEnd) freshEnd = lruEntry.p; + if (lruEntry == staleEnd) staleEnd = lruEntry.n; + link(lruEntry.n,lruEntry.p); + + delete lruHash[key]; + } + + delete data[key]; + size--; + }, + + + /** + * @ngdoc method + * @name $cacheFactory.Cache#removeAll + * @kind function + * + * @description + * Clears the cache object of any entries. + */ + removeAll: function() { + data = {}; + size = 0; + lruHash = {}; + freshEnd = staleEnd = null; + }, + + + /** + * @ngdoc method + * @name $cacheFactory.Cache#destroy + * @kind function + * + * @description + * Destroys the {@link $cacheFactory.Cache Cache} object entirely, + * removing it from the {@link $cacheFactory $cacheFactory} set. + */ + destroy: function() { + data = null; + stats = null; + lruHash = null; + delete caches[cacheId]; + }, + + + /** + * @ngdoc method + * @name $cacheFactory.Cache#info + * @kind function + * + * @description + * Retrieve information regarding a particular {@link $cacheFactory.Cache Cache}. + * + * @returns {object} an object with the following properties: + *
    + *
  • **id**: the id of the cache instance
  • + *
  • **size**: the number of entries kept in the cache instance
  • + *
  • **...**: any additional properties from the options object when creating the + * cache.
  • + *
+ */ + info: function() { + return extend({}, stats, {size: size}); + } + }; + + + /** + * makes the `entry` the freshEnd of the LRU linked list + */ + function refresh(entry) { + if (entry != freshEnd) { + if (!staleEnd) { + staleEnd = entry; + } else if (staleEnd == entry) { + staleEnd = entry.n; + } + + link(entry.n, entry.p); + link(entry, freshEnd); + freshEnd = entry; + freshEnd.n = null; + } + } + + + /** + * bidirectionally links two entries of the LRU linked list + */ + function link(nextEntry, prevEntry) { + if (nextEntry != prevEntry) { + if (nextEntry) nextEntry.p = prevEntry; //p stands for previous, 'prev' didn't minify + if (prevEntry) prevEntry.n = nextEntry; //n stands for next, 'next' didn't minify + } + } + } + + + /** + * @ngdoc method + * @name $cacheFactory#info + * + * @description + * Get information about all the caches that have been created + * + * @returns {Object} - key-value map of `cacheId` to the result of calling `cache#info` + */ + cacheFactory.info = function() { + var info = {}; + forEach(caches, function(cache, cacheId) { + info[cacheId] = cache.info(); + }); + return info; + }; + + + /** + * @ngdoc method + * @name $cacheFactory#get + * + * @description + * Get access to a cache object by the `cacheId` used when it was created. + * + * @param {string} cacheId Name or id of a cache to access. + * @returns {object} Cache object identified by the cacheId or undefined if no such cache. + */ + cacheFactory.get = function(cacheId) { + return caches[cacheId]; + }; + + + return cacheFactory; + }; +} + +/** + * @ngdoc service + * @name $templateCache + * + * @description + * The first time a template is used, it is loaded in the template cache for quick retrieval. You + * can load templates directly into the cache in a `script` tag, or by consuming the + * `$templateCache` service directly. + * + * Adding via the `script` tag: + * + * ```html + * + * ``` + * + * **Note:** the `script` tag containing the template does not need to be included in the `head` of + * the document, but it must be below the `ng-app` definition. + * + * Adding via the $templateCache service: + * + * ```js + * var myApp = angular.module('myApp', []); + * myApp.run(function($templateCache) { + * $templateCache.put('templateId.html', 'This is the content of the template'); + * }); + * ``` + * + * To retrieve the template later, simply use it in your HTML: + * ```html + *
+ * ``` + * + * or get it via Javascript: + * ```js + * $templateCache.get('templateId.html') + * ``` + * + * See {@link ng.$cacheFactory $cacheFactory}. + * + */ +function $TemplateCacheProvider() { + this.$get = ['$cacheFactory', function($cacheFactory) { + return $cacheFactory('templates'); + }]; +} + +/* ! VARIABLE/FUNCTION NAMING CONVENTIONS THAT APPLY TO THIS FILE! + * + * DOM-related variables: + * + * - "node" - DOM Node + * - "element" - DOM Element or Node + * - "$node" or "$element" - jqLite-wrapped node or element + * + * + * Compiler related stuff: + * + * - "linkFn" - linking fn of a single directive + * - "nodeLinkFn" - function that aggregates all linking fns for a particular node + * - "childLinkFn" - function that aggregates all linking fns for child nodes of a particular node + * - "compositeLinkFn" - function that aggregates all linking fns for a compilation root (nodeList) + */ + + +/** + * @ngdoc service + * @name $compile + * @kind function + * + * @description + * Compiles an HTML string or DOM into a template and produces a template function, which + * can then be used to link {@link ng.$rootScope.Scope `scope`} and the template together. + * + * The compilation is a process of walking the DOM tree and matching DOM elements to + * {@link ng.$compileProvider#directive directives}. + * + *
+ * **Note:** This document is an in-depth reference of all directive options. + * For a gentle introduction to directives with examples of common use cases, + * see the {@link guide/directive directive guide}. + *
+ * + * ## Comprehensive Directive API + * + * There are many different options for a directive. + * + * The difference resides in the return value of the factory function. + * You can either return a "Directive Definition Object" (see below) that defines the directive properties, + * or just the `postLink` function (all other properties will have the default values). + * + *
+ * **Best Practice:** It's recommended to use the "directive definition object" form. + *
+ * + * Here's an example directive declared with a Directive Definition Object: + * + * ```js + * var myModule = angular.module(...); + * + * myModule.directive('directiveName', function factory(injectables) { + * var directiveDefinitionObject = { + * priority: 0, + * template: '
', // or // function(tElement, tAttrs) { ... }, + * // or + * // templateUrl: 'directive.html', // or // function(tElement, tAttrs) { ... }, + * transclude: false, + * restrict: 'A', + * scope: false, + * controller: function($scope, $element, $attrs, $transclude, otherInjectables) { ... }, + * controllerAs: 'stringAlias', + * require: 'siblingDirectiveName', // or // ['^parentDirectiveName', '?optionalDirectiveName', '?^optionalParent'], + * compile: function compile(tElement, tAttrs, transclude) { + * return { + * pre: function preLink(scope, iElement, iAttrs, controller) { ... }, + * post: function postLink(scope, iElement, iAttrs, controller) { ... } + * } + * // or + * // return function postLink( ... ) { ... } + * }, + * // or + * // link: { + * // pre: function preLink(scope, iElement, iAttrs, controller) { ... }, + * // post: function postLink(scope, iElement, iAttrs, controller) { ... } + * // } + * // or + * // link: function postLink( ... ) { ... } + * }; + * return directiveDefinitionObject; + * }); + * ``` + * + *
+ * **Note:** Any unspecified options will use the default value. You can see the default values below. + *
+ * + * Therefore the above can be simplified as: + * + * ```js + * var myModule = angular.module(...); + * + * myModule.directive('directiveName', function factory(injectables) { + * var directiveDefinitionObject = { + * link: function postLink(scope, iElement, iAttrs) { ... } + * }; + * return directiveDefinitionObject; + * // or + * // return function postLink(scope, iElement, iAttrs) { ... } + * }); + * ``` + * + * + * + * ### Directive Definition Object + * + * The directive definition object provides instructions to the {@link ng.$compile + * compiler}. The attributes are: + * + * #### `priority` + * When there are multiple directives defined on a single DOM element, sometimes it + * is necessary to specify the order in which the directives are applied. The `priority` is used + * to sort the directives before their `compile` functions get called. Priority is defined as a + * number. Directives with greater numerical `priority` are compiled first. Pre-link functions + * are also run in priority order, but post-link functions are run in reverse order. The order + * of directives with the same priority is undefined. The default priority is `0`. + * + * #### `terminal` + * If set to true then the current `priority` will be the last set of directives + * which will execute (any directives at the current priority will still execute + * as the order of execution on same `priority` is undefined). + * + * #### `scope` + * **If set to `true`,** then a new scope will be created for this directive. If multiple directives on the + * same element request a new scope, only one new scope is created. The new scope rule does not + * apply for the root of the template since the root of the template always gets a new scope. + * + * **If set to `{}` (object hash),** then a new "isolate" scope is created. The 'isolate' scope differs from + * normal scope in that it does not prototypically inherit from the parent scope. This is useful + * when creating reusable components, which should not accidentally read or modify data in the + * parent scope. + * + * The 'isolate' scope takes an object hash which defines a set of local scope properties + * derived from the parent scope. These local properties are useful for aliasing values for + * templates. Locals definition is a hash of local scope property to its source: + * + * * `@` or `@attr` - bind a local scope property to the value of DOM attribute. The result is + * always a string since DOM attributes are strings. If no `attr` name is specified then the + * attribute name is assumed to be the same as the local name. + * Given `` and widget definition + * of `scope: { localName:'@myAttr' }`, then widget scope property `localName` will reflect + * the interpolated value of `hello {{name}}`. As the `name` attribute changes so will the + * `localName` property on the widget scope. The `name` is read from the parent scope (not + * component scope). + * + * * `=` or `=attr` - set up bi-directional binding between a local scope property and the + * parent scope property of name defined via the value of the `attr` attribute. If no `attr` + * name is specified then the attribute name is assumed to be the same as the local name. + * Given `` and widget definition of + * `scope: { localModel:'=myAttr' }`, then widget scope property `localModel` will reflect the + * value of `parentModel` on the parent scope. Any changes to `parentModel` will be reflected + * in `localModel` and any changes in `localModel` will reflect in `parentModel`. If the parent + * scope property doesn't exist, it will throw a NON_ASSIGNABLE_MODEL_EXPRESSION exception. You + * can avoid this behavior using `=?` or `=?attr` in order to flag the property as optional. + * + * * `&` or `&attr` - provides a way to execute an expression in the context of the parent scope. + * If no `attr` name is specified then the attribute name is assumed to be the same as the + * local name. Given `` and widget definition of + * `scope: { localFn:'&myAttr' }`, then isolate scope property `localFn` will point to + * a function wrapper for the `count = count + value` expression. Often it's desirable to + * pass data from the isolated scope via an expression and to the parent scope, this can be + * done by passing a map of local variable names and values into the expression wrapper fn. + * For example, if the expression is `increment(amount)` then we can specify the amount value + * by calling the `localFn` as `localFn({amount: 22})`. + * + * + * + * #### `controller` + * Controller constructor function. The controller is instantiated before the + * pre-linking phase and it is shared with other directives (see + * `require` attribute). This allows the directives to communicate with each other and augment + * each other's behavior. The controller is injectable (and supports bracket notation) with the following locals: + * + * * `$scope` - Current scope associated with the element + * * `$element` - Current element + * * `$attrs` - Current attributes object for the element + * * `$transclude` - A transclude linking function pre-bound to the correct transclusion scope. + * The scope can be overridden by an optional first argument. + * `function([scope], cloneLinkingFn)`. + * + * + * #### `require` + * Require another directive and inject its controller as the fourth argument to the linking function. The + * `require` takes a string name (or array of strings) of the directive(s) to pass in. If an array is used, the + * injected argument will be an array in corresponding order. If no such directive can be + * found, or if the directive does not have a controller, then an error is raised. The name can be prefixed with: + * + * * (no prefix) - Locate the required controller on the current element. Throw an error if not found. + * * `?` - Attempt to locate the required controller or pass `null` to the `link` fn if not found. + * * `^` - Locate the required controller by searching the element's parents. Throw an error if not found. + * * `?^` - Attempt to locate the required controller by searching the element's parents or pass `null` to the + * `link` fn if not found. + * + * + * #### `controllerAs` + * Controller alias at the directive scope. An alias for the controller so it + * can be referenced at the directive template. The directive needs to define a scope for this + * configuration to be used. Useful in the case when directive is used as component. + * + * + * #### `restrict` + * String of subset of `EACM` which restricts the directive to a specific directive + * declaration style. If omitted, the default (attributes only) is used. + * + * * `E` - Element name: `` + * * `A` - Attribute (default): `
` + * * `C` - Class: `
` + * * `M` - Comment: `` + * + * + * #### `template` + * replace the current element with the contents of the HTML. The replacement process + * migrates all of the attributes / classes from the old element to the new one. See the + * {@link guide/directive#creating-custom-directives_creating-directives_template-expanding-directive + * Directives Guide} for an example. + * + * You can specify `template` as a string representing the template or as a function which takes + * two arguments `tElement` and `tAttrs` (described in the `compile` function api below) and + * returns a string value representing the template. + * + * + * #### `templateUrl` + * Same as `template` but the template is loaded from the specified URL. Because + * the template loading is asynchronous the compilation/linking is suspended until the template + * is loaded. + * + * You can specify `templateUrl` as a string representing the URL or as a function which takes two + * arguments `tElement` and `tAttrs` (described in the `compile` function api below) and returns + * a string value representing the url. In either case, the template URL is passed through {@link + * api/ng.$sce#getTrustedResourceUrl $sce.getTrustedResourceUrl}. + * + * + * #### `replace` ([*DEPRECATED*!], will be removed in next major release) + * specify where the template should be inserted. Defaults to `false`. + * + * * `true` - the template will replace the current element. + * * `false` - the template will replace the contents of the current element. + * + * + * #### `transclude` + * compile the content of the element and make it available to the directive. + * Typically used with {@link ng.directive:ngTransclude + * ngTransclude}. The advantage of transclusion is that the linking function receives a + * transclusion function which is pre-bound to the correct scope. In a typical setup the widget + * creates an `isolate` scope, but the transclusion is not a child, but a sibling of the `isolate` + * scope. This makes it possible for the widget to have private state, and the transclusion to + * be bound to the parent (pre-`isolate`) scope. + * + * * `true` - transclude the content of the directive. + * * `'element'` - transclude the whole element including any directives defined at lower priority. + * + * + * #### `compile` + * + * ```js + * function compile(tElement, tAttrs, transclude) { ... } + * ``` + * + * The compile function deals with transforming the template DOM. Since most directives do not do + * template transformation, it is not used often. The compile function takes the following arguments: + * + * * `tElement` - template element - The element where the directive has been declared. It is + * safe to do template transformation on the element and child elements only. + * + * * `tAttrs` - template attributes - Normalized list of attributes declared on this element shared + * between all directive compile functions. + * + * * `transclude` - [*DEPRECATED*!] A transclude linking function: `function(scope, cloneLinkingFn)` + * + *
+ * **Note:** The template instance and the link instance may be different objects if the template has + * been cloned. For this reason it is **not** safe to do anything other than DOM transformations that + * apply to all cloned DOM nodes within the compile function. Specifically, DOM listener registration + * should be done in a linking function rather than in a compile function. + *
+ + *
+ * **Note:** The compile function cannot handle directives that recursively use themselves in their + * own templates or compile functions. Compiling these directives results in an infinite loop and a + * stack overflow errors. + * + * This can be avoided by manually using $compile in the postLink function to imperatively compile + * a directive's template instead of relying on automatic template compilation via `template` or + * `templateUrl` declaration or manual compilation inside the compile function. + *
+ * + *
+ * **Note:** The `transclude` function that is passed to the compile function is deprecated, as it + * e.g. does not know about the right outer scope. Please use the transclude function that is passed + * to the link function instead. + *
+ + * A compile function can have a return value which can be either a function or an object. + * + * * returning a (post-link) function - is equivalent to registering the linking function via the + * `link` property of the config object when the compile function is empty. + * + * * returning an object with function(s) registered via `pre` and `post` properties - allows you to + * control when a linking function should be called during the linking phase. See info about + * pre-linking and post-linking functions below. + * + * + * #### `link` + * This property is used only if the `compile` property is not defined. + * + * ```js + * function link(scope, iElement, iAttrs, controller, transcludeFn) { ... } + * ``` + * + * The link function is responsible for registering DOM listeners as well as updating the DOM. It is + * executed after the template has been cloned. This is where most of the directive logic will be + * put. + * + * * `scope` - {@link ng.$rootScope.Scope Scope} - The scope to be used by the + * directive for registering {@link ng.$rootScope.Scope#$watch watches}. + * + * * `iElement` - instance element - The element where the directive is to be used. It is safe to + * manipulate the children of the element only in `postLink` function since the children have + * already been linked. + * + * * `iAttrs` - instance attributes - Normalized list of attributes declared on this element shared + * between all directive linking functions. + * + * * `controller` - a controller instance - A controller instance if at least one directive on the + * element defines a controller. The controller is shared among all the directives, which allows + * the directives to use the controllers as a communication channel. + * + * * `transcludeFn` - A transclude linking function pre-bound to the correct transclusion scope. + * The scope can be overridden by an optional first argument. This is the same as the `$transclude` + * parameter of directive controllers. + * `function([scope], cloneLinkingFn)`. + * + * + * #### Pre-linking function + * + * Executed before the child elements are linked. Not safe to do DOM transformation since the + * compiler linking function will fail to locate the correct elements for linking. + * + * #### Post-linking function + * + * Executed after the child elements are linked. It is safe to do DOM transformation in the post-linking function. + * + * + * ### Attributes + * + * The {@link ng.$compile.directive.Attributes Attributes} object - passed as a parameter in the + * `link()` or `compile()` functions. It has a variety of uses. + * + * accessing *Normalized attribute names:* + * Directives like 'ngBind' can be expressed in many ways: 'ng:bind', `data-ng-bind`, or 'x-ng-bind'. + * the attributes object allows for normalized access to + * the attributes. + * + * * *Directive inter-communication:* All directives share the same instance of the attributes + * object which allows the directives to use the attributes object as inter directive + * communication. + * + * * *Supports interpolation:* Interpolation attributes are assigned to the attribute object + * allowing other directives to read the interpolated value. + * + * * *Observing interpolated attributes:* Use `$observe` to observe the value changes of attributes + * that contain interpolation (e.g. `src="{{bar}}"`). Not only is this very efficient but it's also + * the only way to easily get the actual value because during the linking phase the interpolation + * hasn't been evaluated yet and so the value is at this time set to `undefined`. + * + * ```js + * function linkingFn(scope, elm, attrs, ctrl) { + * // get the attribute value + * console.log(attrs.ngModel); + * + * // change the attribute + * attrs.$set('ngModel', 'new value'); + * + * // observe changes to interpolated attribute + * attrs.$observe('ngModel', function(value) { + * console.log('ngModel has changed value to ' + value); + * }); + * } + * ``` + * + * Below is an example using `$compileProvider`. + * + *
+ * **Note**: Typically directives are registered with `module.directive`. The example below is + * to illustrate how `$compile` works. + *
+ * + + + +
+
+
+
+
+
+ + it('should auto compile', function() { + var textarea = $('textarea'); + var output = $('div[compile]'); + // The initial state reads 'Hello Angular'. + expect(output.getText()).toBe('Hello Angular'); + textarea.clear(); + textarea.sendKeys('{{name}}!'); + expect(output.getText()).toBe('Angular!'); + }); + +
+ + * + * + * @param {string|DOMElement} element Element or HTML string to compile into a template function. + * @param {function(angular.Scope, cloneAttachFn=)} transclude function available to directives. + * @param {number} maxPriority only apply directives lower than given priority (Only effects the + * root element(s), not their children) + * @returns {function(scope, cloneAttachFn=)} a link function which is used to bind template + * (a DOM element/tree) to a scope. Where: + * + * * `scope` - A {@link ng.$rootScope.Scope Scope} to bind to. + * * `cloneAttachFn` - If `cloneAttachFn` is provided, then the link function will clone the + * `template` and call the `cloneAttachFn` function allowing the caller to attach the + * cloned elements to the DOM document at the appropriate place. The `cloneAttachFn` is + * called as:
`cloneAttachFn(clonedElement, scope)` where: + * + * * `clonedElement` - is a clone of the original `element` passed into the compiler. + * * `scope` - is the current scope with which the linking function is working with. + * + * Calling the linking function returns the element of the template. It is either the original + * element passed in, or the clone of the element if the `cloneAttachFn` is provided. + * + * After linking the view is not updated until after a call to $digest which typically is done by + * Angular automatically. + * + * If you need access to the bound view, there are two ways to do it: + * + * - If you are not asking the linking function to clone the template, create the DOM element(s) + * before you send them to the compiler and keep this reference around. + * ```js + * var element = $compile('

{{total}}

')(scope); + * ``` + * + * - if on the other hand, you need the element to be cloned, the view reference from the original + * example would not point to the clone, but rather to the original template that was cloned. In + * this case, you can access the clone via the cloneAttachFn: + * ```js + * var templateElement = angular.element('

{{total}}

'), + * scope = ....; + * + * var clonedElement = $compile(templateElement)(scope, function(clonedElement, scope) { + * //attach the clone to DOM document at the right place + * }); + * + * //now we have reference to the cloned DOM via `clonedElement` + * ``` + * + * + * For information on how the compiler works, see the + * {@link guide/compiler Angular HTML Compiler} section of the Developer Guide. + */ + +var $compileMinErr = minErr('$compile'); + +/** + * @ngdoc provider + * @name $compileProvider + * @kind function + * + * @description + */ +$CompileProvider.$inject = ['$provide', '$$sanitizeUriProvider']; +function $CompileProvider($provide, $$sanitizeUriProvider) { + var hasDirectives = {}, + Suffix = 'Directive', + COMMENT_DIRECTIVE_REGEXP = /^\s*directive\:\s*([\d\w_\-]+)\s+(.*)$/, + CLASS_DIRECTIVE_REGEXP = /(([\d\w_\-]+)(?:\:([^;]+))?;?)/; + + // Ref: http://developers.whatwg.org/webappapis.html#event-handler-idl-attributes + // The assumption is that future DOM event attribute names will begin with + // 'on' and be composed of only English letters. + var EVENT_HANDLER_ATTR_REGEXP = /^(on[a-z]+|formaction)$/; + + /** + * @ngdoc method + * @name $compileProvider#directive + * @kind function + * + * @description + * Register a new directive with the compiler. + * + * @param {string|Object} name Name of the directive in camel-case (i.e. ngBind which + * will match as ng-bind), or an object map of directives where the keys are the + * names and the values are the factories. + * @param {Function|Array} directiveFactory An injectable directive factory function. See + * {@link guide/directive} for more info. + * @returns {ng.$compileProvider} Self for chaining. + */ + this.directive = function registerDirective(name, directiveFactory) { + assertNotHasOwnProperty(name, 'directive'); + if (isString(name)) { + assertArg(directiveFactory, 'directiveFactory'); + if (!hasDirectives.hasOwnProperty(name)) { + hasDirectives[name] = []; + $provide.factory(name + Suffix, ['$injector', '$exceptionHandler', + function($injector, $exceptionHandler) { + var directives = []; + forEach(hasDirectives[name], function(directiveFactory, index) { + try { + var directive = $injector.invoke(directiveFactory); + if (isFunction(directive)) { + directive = { compile: valueFn(directive) }; + } else if (!directive.compile && directive.link) { + directive.compile = valueFn(directive.link); + } + directive.priority = directive.priority || 0; + directive.index = index; + directive.name = directive.name || name; + directive.require = directive.require || (directive.controller && directive.name); + directive.restrict = directive.restrict || 'A'; + directives.push(directive); + } catch (e) { + $exceptionHandler(e); + } + }); + return directives; + }]); + } + hasDirectives[name].push(directiveFactory); + } else { + forEach(name, reverseParams(registerDirective)); + } + return this; + }; + + + /** + * @ngdoc method + * @name $compileProvider#aHrefSanitizationWhitelist + * @kind function + * + * @description + * Retrieves or overrides the default regular expression that is used for whitelisting of safe + * urls during a[href] sanitization. + * + * The sanitization is a security measure aimed at prevent XSS attacks via html links. + * + * Any url about to be assigned to a[href] via data-binding is first normalized and turned into + * an absolute url. Afterwards, the url is matched against the `aHrefSanitizationWhitelist` + * regular expression. If a match is found, the original url is written into the dom. Otherwise, + * the absolute url is prefixed with `'unsafe:'` string and only then is it written into the DOM. + * + * @param {RegExp=} regexp New regexp to whitelist urls with. + * @returns {RegExp|ng.$compileProvider} Current RegExp if called without value or self for + * chaining otherwise. + */ + this.aHrefSanitizationWhitelist = function(regexp) { + if (isDefined(regexp)) { + $$sanitizeUriProvider.aHrefSanitizationWhitelist(regexp); + return this; + } else { + return $$sanitizeUriProvider.aHrefSanitizationWhitelist(); + } + }; + + + /** + * @ngdoc method + * @name $compileProvider#imgSrcSanitizationWhitelist + * @kind function + * + * @description + * Retrieves or overrides the default regular expression that is used for whitelisting of safe + * urls during img[src] sanitization. + * + * The sanitization is a security measure aimed at prevent XSS attacks via html links. + * + * Any url about to be assigned to img[src] via data-binding is first normalized and turned into + * an absolute url. Afterwards, the url is matched against the `imgSrcSanitizationWhitelist` + * regular expression. If a match is found, the original url is written into the dom. Otherwise, + * the absolute url is prefixed with `'unsafe:'` string and only then is it written into the DOM. + * + * @param {RegExp=} regexp New regexp to whitelist urls with. + * @returns {RegExp|ng.$compileProvider} Current RegExp if called without value or self for + * chaining otherwise. + */ + this.imgSrcSanitizationWhitelist = function(regexp) { + if (isDefined(regexp)) { + $$sanitizeUriProvider.imgSrcSanitizationWhitelist(regexp); + return this; + } else { + return $$sanitizeUriProvider.imgSrcSanitizationWhitelist(); + } + }; + + this.$get = [ + '$injector', '$interpolate', '$exceptionHandler', '$http', '$templateCache', '$parse', + '$controller', '$rootScope', '$document', '$sce', '$animate', '$$sanitizeUri', + function($injector, $interpolate, $exceptionHandler, $http, $templateCache, $parse, + $controller, $rootScope, $document, $sce, $animate, $$sanitizeUri) { + + var Attributes = function(element, attr) { + this.$$element = element; + this.$attr = attr || {}; + }; + + Attributes.prototype = { + $normalize: directiveNormalize, + + + /** + * @ngdoc method + * @name $compile.directive.Attributes#$addClass + * @kind function + * + * @description + * Adds the CSS class value specified by the classVal parameter to the element. If animations + * are enabled then an animation will be triggered for the class addition. + * + * @param {string} classVal The className value that will be added to the element + */ + $addClass : function(classVal) { + if(classVal && classVal.length > 0) { + $animate.addClass(this.$$element, classVal); + } + }, + + /** + * @ngdoc method + * @name $compile.directive.Attributes#$removeClass + * @kind function + * + * @description + * Removes the CSS class value specified by the classVal parameter from the element. If + * animations are enabled then an animation will be triggered for the class removal. + * + * @param {string} classVal The className value that will be removed from the element + */ + $removeClass : function(classVal) { + if(classVal && classVal.length > 0) { + $animate.removeClass(this.$$element, classVal); + } + }, + + /** + * @ngdoc method + * @name $compile.directive.Attributes#$updateClass + * @kind function + * + * @description + * Adds and removes the appropriate CSS class values to the element based on the difference + * between the new and old CSS class values (specified as newClasses and oldClasses). + * + * @param {string} newClasses The current CSS className value + * @param {string} oldClasses The former CSS className value + */ + $updateClass : function(newClasses, oldClasses) { + var toAdd = tokenDifference(newClasses, oldClasses); + var toRemove = tokenDifference(oldClasses, newClasses); + + if(toAdd.length === 0) { + $animate.removeClass(this.$$element, toRemove); + } else if(toRemove.length === 0) { + $animate.addClass(this.$$element, toAdd); + } else { + $animate.setClass(this.$$element, toAdd, toRemove); + } + }, + + /** + * Set a normalized attribute on the element in a way such that all directives + * can share the attribute. This function properly handles boolean attributes. + * @param {string} key Normalized key. (ie ngAttribute) + * @param {string|boolean} value The value to set. If `null` attribute will be deleted. + * @param {boolean=} writeAttr If false, does not write the value to DOM element attribute. + * Defaults to true. + * @param {string=} attrName Optional none normalized name. Defaults to key. + */ + $set: function(key, value, writeAttr, attrName) { + // TODO: decide whether or not to throw an error if "class" + //is set through this function since it may cause $updateClass to + //become unstable. + + var booleanKey = getBooleanAttrName(this.$$element[0], key), + normalizedVal, + nodeName; + + if (booleanKey) { + this.$$element.prop(key, value); + attrName = booleanKey; + } + + this[key] = value; + + // translate normalized key to actual key + if (attrName) { + this.$attr[key] = attrName; + } else { + attrName = this.$attr[key]; + if (!attrName) { + this.$attr[key] = attrName = snake_case(key, '-'); + } + } + + nodeName = nodeName_(this.$$element); + + // sanitize a[href] and img[src] values + if ((nodeName === 'A' && key === 'href') || + (nodeName === 'IMG' && key === 'src')) { + this[key] = value = $$sanitizeUri(value, key === 'src'); + } + + if (writeAttr !== false) { + if (value === null || value === undefined) { + this.$$element.removeAttr(attrName); + } else { + this.$$element.attr(attrName, value); + } + } + + // fire observers + var $$observers = this.$$observers; + $$observers && forEach($$observers[key], function(fn) { + try { + fn(value); + } catch (e) { + $exceptionHandler(e); + } + }); + }, + + + /** + * @ngdoc method + * @name $compile.directive.Attributes#$observe + * @kind function + * + * @description + * Observes an interpolated attribute. + * + * The observer function will be invoked once during the next `$digest` following + * compilation. The observer is then invoked whenever the interpolated value + * changes. + * + * @param {string} key Normalized key. (ie ngAttribute) . + * @param {function(interpolatedValue)} fn Function that will be called whenever + the interpolated value of the attribute changes. + * See the {@link guide/directive#Attributes Directives} guide for more info. + * @returns {function()} the `fn` parameter. + */ + $observe: function(key, fn) { + var attrs = this, + $$observers = (attrs.$$observers || (attrs.$$observers = {})), + listeners = ($$observers[key] || ($$observers[key] = [])); + + listeners.push(fn); + $rootScope.$evalAsync(function() { + if (!listeners.$$inter) { + // no one registered attribute interpolation function, so lets call it manually + fn(attrs[key]); + } + }); + return fn; + } + }; + + var startSymbol = $interpolate.startSymbol(), + endSymbol = $interpolate.endSymbol(), + denormalizeTemplate = (startSymbol == '{{' || endSymbol == '}}') + ? identity + : function denormalizeTemplate(template) { + return template.replace(/\{\{/g, startSymbol).replace(/}}/g, endSymbol); + }, + NG_ATTR_BINDING = /^ngAttr[A-Z]/; + + + return compile; + + //================================ + + function compile($compileNodes, transcludeFn, maxPriority, ignoreDirective, + previousCompileContext) { + if (!($compileNodes instanceof jqLite)) { + // jquery always rewraps, whereas we need to preserve the original selector so that we can + // modify it. + $compileNodes = jqLite($compileNodes); + } + // We can not compile top level text elements since text nodes can be merged and we will + // not be able to attach scope data to them, so we will wrap them in + forEach($compileNodes, function(node, index){ + if (node.nodeType == 3 /* text node */ && node.nodeValue.match(/\S+/) /* non-empty */ ) { + $compileNodes[index] = node = jqLite(node).wrap('').parent()[0]; + } + }); + var compositeLinkFn = + compileNodes($compileNodes, transcludeFn, $compileNodes, + maxPriority, ignoreDirective, previousCompileContext); + safeAddClass($compileNodes, 'ng-scope'); + return function publicLinkFn(scope, cloneConnectFn, transcludeControllers){ + assertArg(scope, 'scope'); + // important!!: we must call our jqLite.clone() since the jQuery one is trying to be smart + // and sometimes changes the structure of the DOM. + var $linkNode = cloneConnectFn + ? JQLitePrototype.clone.call($compileNodes) // IMPORTANT!!! + : $compileNodes; + + forEach(transcludeControllers, function(instance, name) { + $linkNode.data('$' + name + 'Controller', instance); + }); + + // Attach scope only to non-text nodes. + for(var i = 0, ii = $linkNode.length; i + addDirective(directives, + directiveNormalize(nodeName_(node).toLowerCase()), 'E', maxPriority, ignoreDirective); + + // iterate over the attributes + for (var attr, name, nName, ngAttrName, value, nAttrs = node.attributes, + j = 0, jj = nAttrs && nAttrs.length; j < jj; j++) { + var attrStartName = false; + var attrEndName = false; + + attr = nAttrs[j]; + if (!msie || msie >= 8 || attr.specified) { + name = attr.name; + // support ngAttr attribute binding + ngAttrName = directiveNormalize(name); + if (NG_ATTR_BINDING.test(ngAttrName)) { + name = snake_case(ngAttrName.substr(6), '-'); + } + + var directiveNName = ngAttrName.replace(/(Start|End)$/, ''); + if (ngAttrName === directiveNName + 'Start') { + attrStartName = name; + attrEndName = name.substr(0, name.length - 5) + 'end'; + name = name.substr(0, name.length - 6); + } + + nName = directiveNormalize(name.toLowerCase()); + attrsMap[nName] = name; + attrs[nName] = value = trim(attr.value); + if (getBooleanAttrName(node, nName)) { + attrs[nName] = true; // presence means true + } + addAttrInterpolateDirective(node, directives, value, nName); + addDirective(directives, nName, 'A', maxPriority, ignoreDirective, attrStartName, + attrEndName); + } + } + + // use class as directive + className = node.className; + if (isString(className) && className !== '') { + while (match = CLASS_DIRECTIVE_REGEXP.exec(className)) { + nName = directiveNormalize(match[2]); + if (addDirective(directives, nName, 'C', maxPriority, ignoreDirective)) { + attrs[nName] = trim(match[3]); + } + className = className.substr(match.index + match[0].length); + } + } + break; + case 3: /* Text Node */ + addTextInterpolateDirective(directives, node.nodeValue); + break; + case 8: /* Comment */ + try { + match = COMMENT_DIRECTIVE_REGEXP.exec(node.nodeValue); + if (match) { + nName = directiveNormalize(match[1]); + if (addDirective(directives, nName, 'M', maxPriority, ignoreDirective)) { + attrs[nName] = trim(match[2]); + } + } + } catch (e) { + // turns out that under some circumstances IE9 throws errors when one attempts to read + // comment's node value. + // Just ignore it and continue. (Can't seem to reproduce in test case.) + } + break; + } + + directives.sort(byPriority); + return directives; + } + + /** + * Given a node with an directive-start it collects all of the siblings until it finds + * directive-end. + * @param node + * @param attrStart + * @param attrEnd + * @returns {*} + */ + function groupScan(node, attrStart, attrEnd) { + var nodes = []; + var depth = 0; + if (attrStart && node.hasAttribute && node.hasAttribute(attrStart)) { + var startNode = node; + do { + if (!node) { + throw $compileMinErr('uterdir', + "Unterminated attribute, found '{0}' but no matching '{1}' found.", + attrStart, attrEnd); + } + if (node.nodeType == 1 /** Element **/) { + if (node.hasAttribute(attrStart)) depth++; + if (node.hasAttribute(attrEnd)) depth--; + } + nodes.push(node); + node = node.nextSibling; + } while (depth > 0); + } else { + nodes.push(node); + } + + return jqLite(nodes); + } + + /** + * Wrapper for linking function which converts normal linking function into a grouped + * linking function. + * @param linkFn + * @param attrStart + * @param attrEnd + * @returns {Function} + */ + function groupElementsLinkFnWrapper(linkFn, attrStart, attrEnd) { + return function(scope, element, attrs, controllers, transcludeFn) { + element = groupScan(element[0], attrStart, attrEnd); + return linkFn(scope, element, attrs, controllers, transcludeFn); + }; + } + + /** + * Once the directives have been collected, their compile functions are executed. This method + * is responsible for inlining directive templates as well as terminating the application + * of the directives if the terminal directive has been reached. + * + * @param {Array} directives Array of collected directives to execute their compile function. + * this needs to be pre-sorted by priority order. + * @param {Node} compileNode The raw DOM node to apply the compile functions to + * @param {Object} templateAttrs The shared attribute function + * @param {function(angular.Scope, cloneAttachFn=)} transcludeFn A linking function, where the + * scope argument is auto-generated to the new + * child of the transcluded parent scope. + * @param {JQLite} jqCollection If we are working on the root of the compile tree then this + * argument has the root jqLite array so that we can replace nodes + * on it. + * @param {Object=} originalReplaceDirective An optional directive that will be ignored when + * compiling the transclusion. + * @param {Array.} preLinkFns + * @param {Array.} postLinkFns + * @param {Object} previousCompileContext Context used for previous compilation of the current + * node + * @returns {Function} linkFn + */ + function applyDirectivesToNode(directives, compileNode, templateAttrs, transcludeFn, + jqCollection, originalReplaceDirective, preLinkFns, postLinkFns, + previousCompileContext) { + previousCompileContext = previousCompileContext || {}; + + var terminalPriority = -Number.MAX_VALUE, + newScopeDirective, + controllerDirectives = previousCompileContext.controllerDirectives, + newIsolateScopeDirective = previousCompileContext.newIsolateScopeDirective, + templateDirective = previousCompileContext.templateDirective, + nonTlbTranscludeDirective = previousCompileContext.nonTlbTranscludeDirective, + hasTranscludeDirective = false, + hasElementTranscludeDirective = previousCompileContext.hasElementTranscludeDirective, + $compileNode = templateAttrs.$$element = jqLite(compileNode), + directive, + directiveName, + $template, + replaceDirective = originalReplaceDirective, + childTranscludeFn = transcludeFn, + linkFn, + directiveValue; + + // executes all directives on the current element + for(var i = 0, ii = directives.length; i < ii; i++) { + directive = directives[i]; + var attrStart = directive.$$start; + var attrEnd = directive.$$end; + + // collect multiblock sections + if (attrStart) { + $compileNode = groupScan(compileNode, attrStart, attrEnd); + } + $template = undefined; + + if (terminalPriority > directive.priority) { + break; // prevent further processing of directives + } + + if (directiveValue = directive.scope) { + newScopeDirective = newScopeDirective || directive; + + // skip the check for directives with async templates, we'll check the derived sync + // directive when the template arrives + if (!directive.templateUrl) { + assertNoDuplicate('new/isolated scope', newIsolateScopeDirective, directive, + $compileNode); + if (isObject(directiveValue)) { + newIsolateScopeDirective = directive; + } + } + } + + directiveName = directive.name; + + if (!directive.templateUrl && directive.controller) { + directiveValue = directive.controller; + controllerDirectives = controllerDirectives || {}; + assertNoDuplicate("'" + directiveName + "' controller", + controllerDirectives[directiveName], directive, $compileNode); + controllerDirectives[directiveName] = directive; + } + + if (directiveValue = directive.transclude) { + hasTranscludeDirective = true; + + // Special case ngIf and ngRepeat so that we don't complain about duplicate transclusion. + // This option should only be used by directives that know how to safely handle element transclusion, + // where the transcluded nodes are added or replaced after linking. + if (!directive.$$tlb) { + assertNoDuplicate('transclusion', nonTlbTranscludeDirective, directive, $compileNode); + nonTlbTranscludeDirective = directive; + } + + if (directiveValue == 'element') { + hasElementTranscludeDirective = true; + terminalPriority = directive.priority; + $template = groupScan(compileNode, attrStart, attrEnd); + $compileNode = templateAttrs.$$element = + jqLite(document.createComment(' ' + directiveName + ': ' + + templateAttrs[directiveName] + ' ')); + compileNode = $compileNode[0]; + replaceWith(jqCollection, jqLite(sliceArgs($template)), compileNode); + + childTranscludeFn = compile($template, transcludeFn, terminalPriority, + replaceDirective && replaceDirective.name, { + // Don't pass in: + // - controllerDirectives - otherwise we'll create duplicates controllers + // - newIsolateScopeDirective or templateDirective - combining templates with + // element transclusion doesn't make sense. + // + // We need only nonTlbTranscludeDirective so that we prevent putting transclusion + // on the same element more than once. + nonTlbTranscludeDirective: nonTlbTranscludeDirective + }); + } else { + $template = jqLite(jqLiteClone(compileNode)).contents(); + $compileNode.empty(); // clear contents + childTranscludeFn = compile($template, transcludeFn); + } + } + + if (directive.template) { + assertNoDuplicate('template', templateDirective, directive, $compileNode); + templateDirective = directive; + + directiveValue = (isFunction(directive.template)) + ? directive.template($compileNode, templateAttrs) + : directive.template; + + directiveValue = denormalizeTemplate(directiveValue); + + if (directive.replace) { + replaceDirective = directive; + if (jqLiteIsTextNode(directiveValue)) { + $template = []; + } else { + $template = jqLite(trim(directiveValue)); + } + compileNode = $template[0]; + + if ($template.length != 1 || compileNode.nodeType !== 1) { + throw $compileMinErr('tplrt', + "Template for directive '{0}' must have exactly one root element. {1}", + directiveName, ''); + } + + replaceWith(jqCollection, $compileNode, compileNode); + + var newTemplateAttrs = {$attr: {}}; + + // combine directives from the original node and from the template: + // - take the array of directives for this element + // - split it into two parts, those that already applied (processed) and those that weren't (unprocessed) + // - collect directives from the template and sort them by priority + // - combine directives as: processed + template + unprocessed + var templateDirectives = collectDirectives(compileNode, [], newTemplateAttrs); + var unprocessedDirectives = directives.splice(i + 1, directives.length - (i + 1)); + + if (newIsolateScopeDirective) { + markDirectivesAsIsolate(templateDirectives); + } + directives = directives.concat(templateDirectives).concat(unprocessedDirectives); + mergeTemplateAttributes(templateAttrs, newTemplateAttrs); + + ii = directives.length; + } else { + $compileNode.html(directiveValue); + } + } + + if (directive.templateUrl) { + assertNoDuplicate('template', templateDirective, directive, $compileNode); + templateDirective = directive; + + if (directive.replace) { + replaceDirective = directive; + } + + nodeLinkFn = compileTemplateUrl(directives.splice(i, directives.length - i), $compileNode, + templateAttrs, jqCollection, childTranscludeFn, preLinkFns, postLinkFns, { + controllerDirectives: controllerDirectives, + newIsolateScopeDirective: newIsolateScopeDirective, + templateDirective: templateDirective, + nonTlbTranscludeDirective: nonTlbTranscludeDirective + }); + ii = directives.length; + } else if (directive.compile) { + try { + linkFn = directive.compile($compileNode, templateAttrs, childTranscludeFn); + if (isFunction(linkFn)) { + addLinkFns(null, linkFn, attrStart, attrEnd); + } else if (linkFn) { + addLinkFns(linkFn.pre, linkFn.post, attrStart, attrEnd); + } + } catch (e) { + $exceptionHandler(e, startingTag($compileNode)); + } + } + + if (directive.terminal) { + nodeLinkFn.terminal = true; + terminalPriority = Math.max(terminalPriority, directive.priority); + } + + } + + nodeLinkFn.scope = newScopeDirective && newScopeDirective.scope === true; + nodeLinkFn.transclude = hasTranscludeDirective && childTranscludeFn; + previousCompileContext.hasElementTranscludeDirective = hasElementTranscludeDirective; + + // might be normal or delayed nodeLinkFn depending on if templateUrl is present + return nodeLinkFn; + + //////////////////// + + function addLinkFns(pre, post, attrStart, attrEnd) { + if (pre) { + if (attrStart) pre = groupElementsLinkFnWrapper(pre, attrStart, attrEnd); + pre.require = directive.require; + pre.directiveName = directiveName; + if (newIsolateScopeDirective === directive || directive.$$isolateScope) { + pre = cloneAndAnnotateFn(pre, {isolateScope: true}); + } + preLinkFns.push(pre); + } + if (post) { + if (attrStart) post = groupElementsLinkFnWrapper(post, attrStart, attrEnd); + post.require = directive.require; + post.directiveName = directiveName; + if (newIsolateScopeDirective === directive || directive.$$isolateScope) { + post = cloneAndAnnotateFn(post, {isolateScope: true}); + } + postLinkFns.push(post); + } + } + + + function getControllers(directiveName, require, $element, elementControllers) { + var value, retrievalMethod = 'data', optional = false; + if (isString(require)) { + while((value = require.charAt(0)) == '^' || value == '?') { + require = require.substr(1); + if (value == '^') { + retrievalMethod = 'inheritedData'; + } + optional = optional || value == '?'; + } + value = null; + + if (elementControllers && retrievalMethod === 'data') { + value = elementControllers[require]; + } + value = value || $element[retrievalMethod]('$' + require + 'Controller'); + + if (!value && !optional) { + throw $compileMinErr('ctreq', + "Controller '{0}', required by directive '{1}', can't be found!", + require, directiveName); + } + return value; + } else if (isArray(require)) { + value = []; + forEach(require, function(require) { + value.push(getControllers(directiveName, require, $element, elementControllers)); + }); + } + return value; + } + + + function nodeLinkFn(childLinkFn, scope, linkNode, $rootElement, boundTranscludeFn) { + var attrs, $element, i, ii, linkFn, controller, isolateScope, elementControllers = {}, transcludeFn; + + if (compileNode === linkNode) { + attrs = templateAttrs; + } else { + attrs = shallowCopy(templateAttrs, new Attributes(jqLite(linkNode), templateAttrs.$attr)); + } + $element = attrs.$$element; + + if (newIsolateScopeDirective) { + var LOCAL_REGEXP = /^\s*([@=&])(\??)\s*(\w*)\s*$/; + var $linkNode = jqLite(linkNode); + + isolateScope = scope.$new(true); + + if (templateDirective && (templateDirective === newIsolateScopeDirective || + templateDirective === newIsolateScopeDirective.$$originalDirective)) { + $linkNode.data('$isolateScope', isolateScope) ; + } else { + $linkNode.data('$isolateScopeNoTemplate', isolateScope); + } + + + + safeAddClass($linkNode, 'ng-isolate-scope'); + + forEach(newIsolateScopeDirective.scope, function(definition, scopeName) { + var match = definition.match(LOCAL_REGEXP) || [], + attrName = match[3] || scopeName, + optional = (match[2] == '?'), + mode = match[1], // @, =, or & + lastValue, + parentGet, parentSet, compare; + + isolateScope.$$isolateBindings[scopeName] = mode + attrName; + + switch (mode) { + + case '@': + attrs.$observe(attrName, function(value) { + isolateScope[scopeName] = value; + }); + attrs.$$observers[attrName].$$scope = scope; + if( attrs[attrName] ) { + // If the attribute has been provided then we trigger an interpolation to ensure + // the value is there for use in the link fn + isolateScope[scopeName] = $interpolate(attrs[attrName])(scope); + } + break; + + case '=': + if (optional && !attrs[attrName]) { + return; + } + parentGet = $parse(attrs[attrName]); + if (parentGet.literal) { + compare = equals; + } else { + compare = function(a,b) { return a === b; }; + } + parentSet = parentGet.assign || function() { + // reset the change, or we will throw this exception on every $digest + lastValue = isolateScope[scopeName] = parentGet(scope); + throw $compileMinErr('nonassign', + "Expression '{0}' used with directive '{1}' is non-assignable!", + attrs[attrName], newIsolateScopeDirective.name); + }; + lastValue = isolateScope[scopeName] = parentGet(scope); + isolateScope.$watch(function parentValueWatch() { + var parentValue = parentGet(scope); + if (!compare(parentValue, isolateScope[scopeName])) { + // we are out of sync and need to copy + if (!compare(parentValue, lastValue)) { + // parent changed and it has precedence + isolateScope[scopeName] = parentValue; + } else { + // if the parent can be assigned then do so + parentSet(scope, parentValue = isolateScope[scopeName]); + } + } + return lastValue = parentValue; + }, null, parentGet.literal); + break; + + case '&': + parentGet = $parse(attrs[attrName]); + isolateScope[scopeName] = function(locals) { + return parentGet(scope, locals); + }; + break; + + default: + throw $compileMinErr('iscp', + "Invalid isolate scope definition for directive '{0}'." + + " Definition: {... {1}: '{2}' ...}", + newIsolateScopeDirective.name, scopeName, definition); + } + }); + } + transcludeFn = boundTranscludeFn && controllersBoundTransclude; + if (controllerDirectives) { + forEach(controllerDirectives, function(directive) { + var locals = { + $scope: directive === newIsolateScopeDirective || directive.$$isolateScope ? isolateScope : scope, + $element: $element, + $attrs: attrs, + $transclude: transcludeFn + }, controllerInstance; + + controller = directive.controller; + if (controller == '@') { + controller = attrs[directive.name]; + } + + controllerInstance = $controller(controller, locals); + // For directives with element transclusion the element is a comment, + // but jQuery .data doesn't support attaching data to comment nodes as it's hard to + // clean up (http://bugs.jquery.com/ticket/8335). + // Instead, we save the controllers for the element in a local hash and attach to .data + // later, once we have the actual element. + elementControllers[directive.name] = controllerInstance; + if (!hasElementTranscludeDirective) { + $element.data('$' + directive.name + 'Controller', controllerInstance); + } + + if (directive.controllerAs) { + locals.$scope[directive.controllerAs] = controllerInstance; + } + }); + } + + // PRELINKING + for(i = 0, ii = preLinkFns.length; i < ii; i++) { + try { + linkFn = preLinkFns[i]; + linkFn(linkFn.isolateScope ? isolateScope : scope, $element, attrs, + linkFn.require && getControllers(linkFn.directiveName, linkFn.require, $element, elementControllers), transcludeFn); + } catch (e) { + $exceptionHandler(e, startingTag($element)); + } + } + + // RECURSION + // We only pass the isolate scope, if the isolate directive has a template, + // otherwise the child elements do not belong to the isolate directive. + var scopeToChild = scope; + if (newIsolateScopeDirective && (newIsolateScopeDirective.template || newIsolateScopeDirective.templateUrl === null)) { + scopeToChild = isolateScope; + } + childLinkFn && childLinkFn(scopeToChild, linkNode.childNodes, undefined, boundTranscludeFn); + + // POSTLINKING + for(i = postLinkFns.length - 1; i >= 0; i--) { + try { + linkFn = postLinkFns[i]; + linkFn(linkFn.isolateScope ? isolateScope : scope, $element, attrs, + linkFn.require && getControllers(linkFn.directiveName, linkFn.require, $element, elementControllers), transcludeFn); + } catch (e) { + $exceptionHandler(e, startingTag($element)); + } + } + + // This is the function that is injected as `$transclude`. + function controllersBoundTransclude(scope, cloneAttachFn) { + var transcludeControllers; + + // no scope passed + if (arguments.length < 2) { + cloneAttachFn = scope; + scope = undefined; + } + + if (hasElementTranscludeDirective) { + transcludeControllers = elementControllers; + } + + return boundTranscludeFn(scope, cloneAttachFn, transcludeControllers); + } + } + } + + function markDirectivesAsIsolate(directives) { + // mark all directives as needing isolate scope. + for (var j = 0, jj = directives.length; j < jj; j++) { + directives[j] = inherit(directives[j], {$$isolateScope: true}); + } + } + + /** + * looks up the directive and decorates it with exception handling and proper parameters. We + * call this the boundDirective. + * + * @param {string} name name of the directive to look up. + * @param {string} location The directive must be found in specific format. + * String containing any of theses characters: + * + * * `E`: element name + * * `A': attribute + * * `C`: class + * * `M`: comment + * @returns {boolean} true if directive was added. + */ + function addDirective(tDirectives, name, location, maxPriority, ignoreDirective, startAttrName, + endAttrName) { + if (name === ignoreDirective) return null; + var match = null; + if (hasDirectives.hasOwnProperty(name)) { + for(var directive, directives = $injector.get(name + Suffix), + i = 0, ii = directives.length; i directive.priority) && + directive.restrict.indexOf(location) != -1) { + if (startAttrName) { + directive = inherit(directive, {$$start: startAttrName, $$end: endAttrName}); + } + tDirectives.push(directive); + match = directive; + } + } catch(e) { $exceptionHandler(e); } + } + } + return match; + } + + + /** + * When the element is replaced with HTML template then the new attributes + * on the template need to be merged with the existing attributes in the DOM. + * The desired effect is to have both of the attributes present. + * + * @param {object} dst destination attributes (original DOM) + * @param {object} src source attributes (from the directive template) + */ + function mergeTemplateAttributes(dst, src) { + var srcAttr = src.$attr, + dstAttr = dst.$attr, + $element = dst.$$element; + + // reapply the old attributes to the new element + forEach(dst, function(value, key) { + if (key.charAt(0) != '$') { + if (src[key] && src[key] !== value) { + value += (key === 'style' ? ';' : ' ') + src[key]; + } + dst.$set(key, value, true, srcAttr[key]); + } + }); + + // copy the new attributes on the old attrs object + forEach(src, function(value, key) { + if (key == 'class') { + safeAddClass($element, value); + dst['class'] = (dst['class'] ? dst['class'] + ' ' : '') + value; + } else if (key == 'style') { + $element.attr('style', $element.attr('style') + ';' + value); + dst['style'] = (dst['style'] ? dst['style'] + ';' : '') + value; + // `dst` will never contain hasOwnProperty as DOM parser won't let it. + // You will get an "InvalidCharacterError: DOM Exception 5" error if you + // have an attribute like "has-own-property" or "data-has-own-property", etc. + } else if (key.charAt(0) != '$' && !dst.hasOwnProperty(key)) { + dst[key] = value; + dstAttr[key] = srcAttr[key]; + } + }); + } + + + function compileTemplateUrl(directives, $compileNode, tAttrs, + $rootElement, childTranscludeFn, preLinkFns, postLinkFns, previousCompileContext) { + var linkQueue = [], + afterTemplateNodeLinkFn, + afterTemplateChildLinkFn, + beforeTemplateCompileNode = $compileNode[0], + origAsyncDirective = directives.shift(), + // The fact that we have to copy and patch the directive seems wrong! + derivedSyncDirective = extend({}, origAsyncDirective, { + templateUrl: null, transclude: null, replace: null, $$originalDirective: origAsyncDirective + }), + templateUrl = (isFunction(origAsyncDirective.templateUrl)) + ? origAsyncDirective.templateUrl($compileNode, tAttrs) + : origAsyncDirective.templateUrl; + + $compileNode.empty(); + + $http.get($sce.getTrustedResourceUrl(templateUrl), {cache: $templateCache}). + success(function(content) { + var compileNode, tempTemplateAttrs, $template, childBoundTranscludeFn; + + content = denormalizeTemplate(content); + + if (origAsyncDirective.replace) { + if (jqLiteIsTextNode(content)) { + $template = []; + } else { + $template = jqLite(trim(content)); + } + compileNode = $template[0]; + + if ($template.length != 1 || compileNode.nodeType !== 1) { + throw $compileMinErr('tplrt', + "Template for directive '{0}' must have exactly one root element. {1}", + origAsyncDirective.name, templateUrl); + } + + tempTemplateAttrs = {$attr: {}}; + replaceWith($rootElement, $compileNode, compileNode); + var templateDirectives = collectDirectives(compileNode, [], tempTemplateAttrs); + + if (isObject(origAsyncDirective.scope)) { + markDirectivesAsIsolate(templateDirectives); + } + directives = templateDirectives.concat(directives); + mergeTemplateAttributes(tAttrs, tempTemplateAttrs); + } else { + compileNode = beforeTemplateCompileNode; + $compileNode.html(content); + } + + directives.unshift(derivedSyncDirective); + + afterTemplateNodeLinkFn = applyDirectivesToNode(directives, compileNode, tAttrs, + childTranscludeFn, $compileNode, origAsyncDirective, preLinkFns, postLinkFns, + previousCompileContext); + forEach($rootElement, function(node, i) { + if (node == compileNode) { + $rootElement[i] = $compileNode[0]; + } + }); + afterTemplateChildLinkFn = compileNodes($compileNode[0].childNodes, childTranscludeFn); + + + while(linkQueue.length) { + var scope = linkQueue.shift(), + beforeTemplateLinkNode = linkQueue.shift(), + linkRootElement = linkQueue.shift(), + boundTranscludeFn = linkQueue.shift(), + linkNode = $compileNode[0]; + + if (beforeTemplateLinkNode !== beforeTemplateCompileNode) { + var oldClasses = beforeTemplateLinkNode.className; + + if (!(previousCompileContext.hasElementTranscludeDirective && + origAsyncDirective.replace)) { + // it was cloned therefore we have to clone as well. + linkNode = jqLiteClone(compileNode); + } + + replaceWith(linkRootElement, jqLite(beforeTemplateLinkNode), linkNode); + + // Copy in CSS classes from original node + safeAddClass(jqLite(linkNode), oldClasses); + } + if (afterTemplateNodeLinkFn.transclude) { + childBoundTranscludeFn = createBoundTranscludeFn(scope, afterTemplateNodeLinkFn.transclude); + } else { + childBoundTranscludeFn = boundTranscludeFn; + } + afterTemplateNodeLinkFn(afterTemplateChildLinkFn, scope, linkNode, $rootElement, + childBoundTranscludeFn); + } + linkQueue = null; + }). + error(function(response, code, headers, config) { + throw $compileMinErr('tpload', 'Failed to load template: {0}', config.url); + }); + + return function delayedNodeLinkFn(ignoreChildLinkFn, scope, node, rootElement, boundTranscludeFn) { + if (linkQueue) { + linkQueue.push(scope); + linkQueue.push(node); + linkQueue.push(rootElement); + linkQueue.push(boundTranscludeFn); + } else { + afterTemplateNodeLinkFn(afterTemplateChildLinkFn, scope, node, rootElement, boundTranscludeFn); + } + }; + } + + + /** + * Sorting function for bound directives. + */ + function byPriority(a, b) { + var diff = b.priority - a.priority; + if (diff !== 0) return diff; + if (a.name !== b.name) return (a.name < b.name) ? -1 : 1; + return a.index - b.index; + } + + + function assertNoDuplicate(what, previousDirective, directive, element) { + if (previousDirective) { + throw $compileMinErr('multidir', 'Multiple directives [{0}, {1}] asking for {2} on: {3}', + previousDirective.name, directive.name, what, startingTag(element)); + } + } + + + function addTextInterpolateDirective(directives, text) { + var interpolateFn = $interpolate(text, true); + if (interpolateFn) { + directives.push({ + priority: 0, + compile: valueFn(function textInterpolateLinkFn(scope, node) { + var parent = node.parent(), + bindings = parent.data('$binding') || []; + bindings.push(interpolateFn); + safeAddClass(parent.data('$binding', bindings), 'ng-binding'); + scope.$watch(interpolateFn, function interpolateFnWatchAction(value) { + node[0].nodeValue = value; + }); + }) + }); + } + } + + + function getTrustedContext(node, attrNormalizedName) { + if (attrNormalizedName == "srcdoc") { + return $sce.HTML; + } + var tag = nodeName_(node); + // maction[xlink:href] can source SVG. It's not limited to . + if (attrNormalizedName == "xlinkHref" || + (tag == "FORM" && attrNormalizedName == "action") || + (tag != "IMG" && (attrNormalizedName == "src" || + attrNormalizedName == "ngSrc"))) { + return $sce.RESOURCE_URL; + } + } + + + function addAttrInterpolateDirective(node, directives, value, name) { + var interpolateFn = $interpolate(value, true); + + // no interpolation found -> ignore + if (!interpolateFn) return; + + + if (name === "multiple" && nodeName_(node) === "SELECT") { + throw $compileMinErr("selmulti", + "Binding to the 'multiple' attribute is not supported. Element: {0}", + startingTag(node)); + } + + directives.push({ + priority: 100, + compile: function() { + return { + pre: function attrInterpolatePreLinkFn(scope, element, attr) { + var $$observers = (attr.$$observers || (attr.$$observers = {})); + + if (EVENT_HANDLER_ATTR_REGEXP.test(name)) { + throw $compileMinErr('nodomevents', + "Interpolations for HTML DOM event attributes are disallowed. Please use the " + + "ng- versions (such as ng-click instead of onclick) instead."); + } + + // we need to interpolate again, in case the attribute value has been updated + // (e.g. by another directive's compile function) + interpolateFn = $interpolate(attr[name], true, getTrustedContext(node, name)); + + // if attribute was updated so that there is no interpolation going on we don't want to + // register any observers + if (!interpolateFn) return; + + // TODO(i): this should likely be attr.$set(name, iterpolateFn(scope) so that we reset the + // actual attr value + attr[name] = interpolateFn(scope); + ($$observers[name] || ($$observers[name] = [])).$$inter = true; + (attr.$$observers && attr.$$observers[name].$$scope || scope). + $watch(interpolateFn, function interpolateFnWatchAction(newValue, oldValue) { + //special case for class attribute addition + removal + //so that class changes can tap into the animation + //hooks provided by the $animate service. Be sure to + //skip animations when the first digest occurs (when + //both the new and the old values are the same) since + //the CSS classes are the non-interpolated values + if(name === 'class' && newValue != oldValue) { + attr.$updateClass(newValue, oldValue); + } else { + attr.$set(name, newValue); + } + }); + } + }; + } + }); + } + + + /** + * This is a special jqLite.replaceWith, which can replace items which + * have no parents, provided that the containing jqLite collection is provided. + * + * @param {JqLite=} $rootElement The root of the compile tree. Used so that we can replace nodes + * in the root of the tree. + * @param {JqLite} elementsToRemove The jqLite element which we are going to replace. We keep + * the shell, but replace its DOM node reference. + * @param {Node} newNode The new DOM node. + */ + function replaceWith($rootElement, elementsToRemove, newNode) { + var firstElementToRemove = elementsToRemove[0], + removeCount = elementsToRemove.length, + parent = firstElementToRemove.parentNode, + i, ii; + + if ($rootElement) { + for(i = 0, ii = $rootElement.length; i < ii; i++) { + if ($rootElement[i] == firstElementToRemove) { + $rootElement[i++] = newNode; + for (var j = i, j2 = j + removeCount - 1, + jj = $rootElement.length; + j < jj; j++, j2++) { + if (j2 < jj) { + $rootElement[j] = $rootElement[j2]; + } else { + delete $rootElement[j]; + } + } + $rootElement.length -= removeCount - 1; + break; + } + } + } + + if (parent) { + parent.replaceChild(newNode, firstElementToRemove); + } + var fragment = document.createDocumentFragment(); + fragment.appendChild(firstElementToRemove); + newNode[jqLite.expando] = firstElementToRemove[jqLite.expando]; + for (var k = 1, kk = elementsToRemove.length; k < kk; k++) { + var element = elementsToRemove[k]; + jqLite(element).remove(); // must do this way to clean up expando + fragment.appendChild(element); + delete elementsToRemove[k]; + } + + elementsToRemove[0] = newNode; + elementsToRemove.length = 1; + } + + + function cloneAndAnnotateFn(fn, annotation) { + return extend(function() { return fn.apply(null, arguments); }, fn, annotation); + } + }]; +} + +var PREFIX_REGEXP = /^(x[\:\-_]|data[\:\-_])/i; +/** + * Converts all accepted directives format into proper directive name. + * All of these will become 'myDirective': + * my:Directive + * my-directive + * x-my-directive + * data-my:directive + * + * Also there is special case for Moz prefix starting with upper case letter. + * @param name Name to normalize + */ +function directiveNormalize(name) { + return camelCase(name.replace(PREFIX_REGEXP, '')); +} + +/** + * @ngdoc type + * @name $compile.directive.Attributes + * + * @description + * A shared object between directive compile / linking functions which contains normalized DOM + * element attributes. The values reflect current binding state `{{ }}`. The normalization is + * needed since all of these are treated as equivalent in Angular: + * + * ``` + * + * ``` + */ + +/** + * @ngdoc property + * @name $compile.directive.Attributes#$attr + * @returns {object} A map of DOM element attribute names to the normalized name. This is + * needed to do reverse lookup from normalized name back to actual name. + */ + + +/** + * @ngdoc method + * @name $compile.directive.Attributes#$set + * @kind function + * + * @description + * Set DOM element attribute value. + * + * + * @param {string} name Normalized element attribute name of the property to modify. The name is + * reverse-translated using the {@link ng.$compile.directive.Attributes#$attr $attr} + * property to the original name. + * @param {string} value Value to set the attribute to. The value can be an interpolated string. + */ + + + +/** + * Closure compiler type information + */ + +function nodesetLinkingFn( + /* angular.Scope */ scope, + /* NodeList */ nodeList, + /* Element */ rootElement, + /* function(Function) */ boundTranscludeFn +){} + +function directiveLinkingFn( + /* nodesetLinkingFn */ nodesetLinkingFn, + /* angular.Scope */ scope, + /* Node */ node, + /* Element */ rootElement, + /* function(Function) */ boundTranscludeFn +){} + +function tokenDifference(str1, str2) { + var values = '', + tokens1 = str1.split(/\s+/), + tokens2 = str2.split(/\s+/); + + outer: + for(var i = 0; i < tokens1.length; i++) { + var token = tokens1[i]; + for(var j = 0; j < tokens2.length; j++) { + if(token == tokens2[j]) continue outer; + } + values += (values.length > 0 ? ' ' : '') + token; + } + return values; +} + +/** + * @ngdoc provider + * @name $controllerProvider + * @description + * The {@link ng.$controller $controller service} is used by Angular to create new + * controllers. + * + * This provider allows controller registration via the + * {@link ng.$controllerProvider#register register} method. + */ +function $ControllerProvider() { + var controllers = {}, + CNTRL_REG = /^(\S+)(\s+as\s+(\w+))?$/; + + + /** + * @ngdoc method + * @name $controllerProvider#register + * @param {string|Object} name Controller name, or an object map of controllers where the keys are + * the names and the values are the constructors. + * @param {Function|Array} constructor Controller constructor fn (optionally decorated with DI + * annotations in the array notation). + */ + this.register = function(name, constructor) { + assertNotHasOwnProperty(name, 'controller'); + if (isObject(name)) { + extend(controllers, name); + } else { + controllers[name] = constructor; + } + }; + + + this.$get = ['$injector', '$window', function($injector, $window) { + + /** + * @ngdoc service + * @name $controller + * @requires $injector + * + * @param {Function|string} constructor If called with a function then it's considered to be the + * controller constructor function. Otherwise it's considered to be a string which is used + * to retrieve the controller constructor using the following steps: + * + * * check if a controller with given name is registered via `$controllerProvider` + * * check if evaluating the string on the current scope returns a constructor + * * check `window[constructor]` on the global `window` object + * + * @param {Object} locals Injection locals for Controller. + * @return {Object} Instance of given controller. + * + * @description + * `$controller` service is responsible for instantiating controllers. + * + * It's just a simple call to {@link auto.$injector $injector}, but extracted into + * a service, so that one can override this service with [BC version](https://gist.github.com/1649788). + */ + return function(expression, locals) { + var instance, match, constructor, identifier; + + if(isString(expression)) { + match = expression.match(CNTRL_REG), + constructor = match[1], + identifier = match[3]; + expression = controllers.hasOwnProperty(constructor) + ? controllers[constructor] + : getter(locals.$scope, constructor, true) || getter($window, constructor, true); + + assertArgFn(expression, constructor, true); + } + + instance = $injector.instantiate(expression, locals); + + if (identifier) { + if (!(locals && typeof locals.$scope == 'object')) { + throw minErr('$controller')('noscp', + "Cannot export controller '{0}' as '{1}'! No $scope object provided via `locals`.", + constructor || expression.name, identifier); + } + + locals.$scope[identifier] = instance; + } + + return instance; + }; + }]; +} + +/** + * @ngdoc service + * @name $document + * @requires $window + * + * @description + * A {@link angular.element jQuery or jqLite} wrapper for the browser's `window.document` object. + * + * @example + + +
+

$document title:

+

window.document title:

+
+
+ + function MainCtrl($scope, $document) { + $scope.title = $document[0].title; + $scope.windowTitle = angular.element(window.document)[0].title; + } + +
+ */ +function $DocumentProvider(){ + this.$get = ['$window', function(window){ + return jqLite(window.document); + }]; +} + +/** + * @ngdoc service + * @name $exceptionHandler + * @requires ng.$log + * + * @description + * Any uncaught exception in angular expressions is delegated to this service. + * The default implementation simply delegates to `$log.error` which logs it into + * the browser console. + * + * In unit tests, if `angular-mocks.js` is loaded, this service is overridden by + * {@link ngMock.$exceptionHandler mock $exceptionHandler} which aids in testing. + * + * ## Example: + * + * ```js + * angular.module('exceptionOverride', []).factory('$exceptionHandler', function () { + * return function (exception, cause) { + * exception.message += ' (caused by "' + cause + '")'; + * throw exception; + * }; + * }); + * ``` + * + * This example will override the normal action of `$exceptionHandler`, to make angular + * exceptions fail hard when they happen, instead of just logging to the console. + * + * @param {Error} exception Exception associated with the error. + * @param {string=} cause optional information about the context in which + * the error was thrown. + * + */ +function $ExceptionHandlerProvider() { + this.$get = ['$log', function($log) { + return function(exception, cause) { + $log.error.apply($log, arguments); + }; + }]; +} + +/** + * Parse headers into key value object + * + * @param {string} headers Raw headers as a string + * @returns {Object} Parsed headers as key value object + */ +function parseHeaders(headers) { + var parsed = {}, key, val, i; + + if (!headers) return parsed; + + forEach(headers.split('\n'), function(line) { + i = line.indexOf(':'); + key = lowercase(trim(line.substr(0, i))); + val = trim(line.substr(i + 1)); + + if (key) { + if (parsed[key]) { + parsed[key] += ', ' + val; + } else { + parsed[key] = val; + } + } + }); + + return parsed; +} + + +/** + * Returns a function that provides access to parsed headers. + * + * Headers are lazy parsed when first requested. + * @see parseHeaders + * + * @param {(string|Object)} headers Headers to provide access to. + * @returns {function(string=)} Returns a getter function which if called with: + * + * - if called with single an argument returns a single header value or null + * - if called with no arguments returns an object containing all headers. + */ +function headersGetter(headers) { + var headersObj = isObject(headers) ? headers : undefined; + + return function(name) { + if (!headersObj) headersObj = parseHeaders(headers); + + if (name) { + return headersObj[lowercase(name)] || null; + } + + return headersObj; + }; +} + + +/** + * Chain all given functions + * + * This function is used for both request and response transforming + * + * @param {*} data Data to transform. + * @param {function(string=)} headers Http headers getter fn. + * @param {(Function|Array.)} fns Function or an array of functions. + * @returns {*} Transformed data. + */ +function transformData(data, headers, fns) { + if (isFunction(fns)) + return fns(data, headers); + + forEach(fns, function(fn) { + data = fn(data, headers); + }); + + return data; +} + + +function isSuccess(status) { + return 200 <= status && status < 300; +} + + +function $HttpProvider() { + var JSON_START = /^\s*(\[|\{[^\{])/, + JSON_END = /[\}\]]\s*$/, + PROTECTION_PREFIX = /^\)\]\}',?\n/, + CONTENT_TYPE_APPLICATION_JSON = {'Content-Type': 'application/json;charset=utf-8'}; + + var defaults = this.defaults = { + // transform incoming response data + transformResponse: [function(data) { + if (isString(data)) { + // strip json vulnerability protection prefix + data = data.replace(PROTECTION_PREFIX, ''); + if (JSON_START.test(data) && JSON_END.test(data)) + data = fromJson(data); + } + return data; + }], + + // transform outgoing request data + transformRequest: [function(d) { + return isObject(d) && !isFile(d) && !isBlob(d) ? toJson(d) : d; + }], + + // default headers + headers: { + common: { + 'Accept': 'application/json, text/plain, */*' + }, + post: shallowCopy(CONTENT_TYPE_APPLICATION_JSON), + put: shallowCopy(CONTENT_TYPE_APPLICATION_JSON), + patch: shallowCopy(CONTENT_TYPE_APPLICATION_JSON) + }, + + xsrfCookieName: 'XSRF-TOKEN', + xsrfHeaderName: 'X-XSRF-TOKEN' + }; + + /** + * Are ordered by request, i.e. they are applied in the same order as the + * array, on request, but reverse order, on response. + */ + var interceptorFactories = this.interceptors = []; + + /** + * For historical reasons, response interceptors are ordered by the order in which + * they are applied to the response. (This is the opposite of interceptorFactories) + */ + var responseInterceptorFactories = this.responseInterceptors = []; + + this.$get = ['$httpBackend', '$browser', '$cacheFactory', '$rootScope', '$q', '$injector', + function($httpBackend, $browser, $cacheFactory, $rootScope, $q, $injector) { + + var defaultCache = $cacheFactory('$http'); + + /** + * Interceptors stored in reverse order. Inner interceptors before outer interceptors. + * The reversal is needed so that we can build up the interception chain around the + * server request. + */ + var reversedInterceptors = []; + + forEach(interceptorFactories, function(interceptorFactory) { + reversedInterceptors.unshift(isString(interceptorFactory) + ? $injector.get(interceptorFactory) : $injector.invoke(interceptorFactory)); + }); + + forEach(responseInterceptorFactories, function(interceptorFactory, index) { + var responseFn = isString(interceptorFactory) + ? $injector.get(interceptorFactory) + : $injector.invoke(interceptorFactory); + + /** + * Response interceptors go before "around" interceptors (no real reason, just + * had to pick one.) But they are already reversed, so we can't use unshift, hence + * the splice. + */ + reversedInterceptors.splice(index, 0, { + response: function(response) { + return responseFn($q.when(response)); + }, + responseError: function(response) { + return responseFn($q.reject(response)); + } + }); + }); + + + /** + * @ngdoc service + * @kind function + * @name $http + * @requires ng.$httpBackend + * @requires $cacheFactory + * @requires $rootScope + * @requires $q + * @requires $injector + * + * @description + * The `$http` service is a core Angular service that facilitates communication with the remote + * HTTP servers via the browser's [XMLHttpRequest](https://developer.mozilla.org/en/xmlhttprequest) + * object or via [JSONP](http://en.wikipedia.org/wiki/JSONP). + * + * For unit testing applications that use `$http` service, see + * {@link ngMock.$httpBackend $httpBackend mock}. + * + * For a higher level of abstraction, please check out the {@link ngResource.$resource + * $resource} service. + * + * The $http API is based on the {@link ng.$q deferred/promise APIs} exposed by + * the $q service. While for simple usage patterns this doesn't matter much, for advanced usage + * it is important to familiarize yourself with these APIs and the guarantees they provide. + * + * + * # General usage + * The `$http` service is a function which takes a single argument — a configuration object — + * that is used to generate an HTTP request and returns a {@link ng.$q promise} + * with two $http specific methods: `success` and `error`. + * + * ```js + * $http({method: 'GET', url: '/someUrl'}). + * success(function(data, status, headers, config) { + * // this callback will be called asynchronously + * // when the response is available + * }). + * error(function(data, status, headers, config) { + * // called asynchronously if an error occurs + * // or server returns response with an error status. + * }); + * ``` + * + * Since the returned value of calling the $http function is a `promise`, you can also use + * the `then` method to register callbacks, and these callbacks will receive a single argument – + * an object representing the response. See the API signature and type info below for more + * details. + * + * A response status code between 200 and 299 is considered a success status and + * will result in the success callback being called. Note that if the response is a redirect, + * XMLHttpRequest will transparently follow it, meaning that the error callback will not be + * called for such responses. + * + * # Writing Unit Tests that use $http + * When unit testing (using {@link ngMock ngMock}), it is necessary to call + * {@link ngMock.$httpBackend#flush $httpBackend.flush()} to flush each pending + * request using trained responses. + * + * ``` + * $httpBackend.expectGET(...); + * $http.get(...); + * $httpBackend.flush(); + * ``` + * + * # Shortcut methods + * + * Shortcut methods are also available. All shortcut methods require passing in the URL, and + * request data must be passed in for POST/PUT requests. + * + * ```js + * $http.get('/someUrl').success(successCallback); + * $http.post('/someUrl', data).success(successCallback); + * ``` + * + * Complete list of shortcut methods: + * + * - {@link ng.$http#get $http.get} + * - {@link ng.$http#head $http.head} + * - {@link ng.$http#post $http.post} + * - {@link ng.$http#put $http.put} + * - {@link ng.$http#delete $http.delete} + * - {@link ng.$http#jsonp $http.jsonp} + * + * + * # Setting HTTP Headers + * + * The $http service will automatically add certain HTTP headers to all requests. These defaults + * can be fully configured by accessing the `$httpProvider.defaults.headers` configuration + * object, which currently contains this default configuration: + * + * - `$httpProvider.defaults.headers.common` (headers that are common for all requests): + * - `Accept: application/json, text/plain, * / *` + * - `$httpProvider.defaults.headers.post`: (header defaults for POST requests) + * - `Content-Type: application/json` + * - `$httpProvider.defaults.headers.put` (header defaults for PUT requests) + * - `Content-Type: application/json` + * + * To add or overwrite these defaults, simply add or remove a property from these configuration + * objects. To add headers for an HTTP method other than POST or PUT, simply add a new object + * with the lowercased HTTP method name as the key, e.g. + * `$httpProvider.defaults.headers.get = { 'My-Header' : 'value' }. + * + * The defaults can also be set at runtime via the `$http.defaults` object in the same + * fashion. For example: + * + * ``` + * module.run(function($http) { + * $http.defaults.headers.common.Authorization = 'Basic YmVlcDpib29w' + * }); + * ``` + * + * In addition, you can supply a `headers` property in the config object passed when + * calling `$http(config)`, which overrides the defaults without changing them globally. + * + * + * # Transforming Requests and Responses + * + * Both requests and responses can be transformed using transform functions. By default, Angular + * applies these transformations: + * + * Request transformations: + * + * - If the `data` property of the request configuration object contains an object, serialize it + * into JSON format. + * + * Response transformations: + * + * - If XSRF prefix is detected, strip it (see Security Considerations section below). + * - If JSON response is detected, deserialize it using a JSON parser. + * + * To globally augment or override the default transforms, modify the + * `$httpProvider.defaults.transformRequest` and `$httpProvider.defaults.transformResponse` + * properties. These properties are by default an array of transform functions, which allows you + * to `push` or `unshift` a new transformation function into the transformation chain. You can + * also decide to completely override any default transformations by assigning your + * transformation functions to these properties directly without the array wrapper. These defaults + * are again available on the $http factory at run-time, which may be useful if you have run-time + * services you wish to be involved in your transformations. + * + * Similarly, to locally override the request/response transforms, augment the + * `transformRequest` and/or `transformResponse` properties of the configuration object passed + * into `$http`. + * + * + * # Caching + * + * To enable caching, set the request configuration `cache` property to `true` (to use default + * cache) or to a custom cache object (built with {@link ng.$cacheFactory `$cacheFactory`}). + * When the cache is enabled, `$http` stores the response from the server in the specified + * cache. The next time the same request is made, the response is served from the cache without + * sending a request to the server. + * + * Note that even if the response is served from cache, delivery of the data is asynchronous in + * the same way that real requests are. + * + * If there are multiple GET requests for the same URL that should be cached using the same + * cache, but the cache is not populated yet, only one request to the server will be made and + * the remaining requests will be fulfilled using the response from the first request. + * + * You can change the default cache to a new object (built with + * {@link ng.$cacheFactory `$cacheFactory`}) by updating the + * {@link ng.$http#properties_defaults `$http.defaults.cache`} property. All requests who set + * their `cache` property to `true` will now use this cache object. + * + * If you set the default cache to `false` then only requests that specify their own custom + * cache object will be cached. + * + * # Interceptors + * + * Before you start creating interceptors, be sure to understand the + * {@link ng.$q $q and deferred/promise APIs}. + * + * For purposes of global error handling, authentication, or any kind of synchronous or + * asynchronous pre-processing of request or postprocessing of responses, it is desirable to be + * able to intercept requests before they are handed to the server and + * responses before they are handed over to the application code that + * initiated these requests. The interceptors leverage the {@link ng.$q + * promise APIs} to fulfill this need for both synchronous and asynchronous pre-processing. + * + * The interceptors are service factories that are registered with the `$httpProvider` by + * adding them to the `$httpProvider.interceptors` array. The factory is called and + * injected with dependencies (if specified) and returns the interceptor. + * + * There are two kinds of interceptors (and two kinds of rejection interceptors): + * + * * `request`: interceptors get called with a http `config` object. The function is free to + * modify the `config` object or create a new one. The function needs to return the `config` + * object directly, or a promise containing the `config` or a new `config` object. + * * `requestError`: interceptor gets called when a previous interceptor threw an error or + * resolved with a rejection. + * * `response`: interceptors get called with http `response` object. The function is free to + * modify the `response` object or create a new one. The function needs to return the `response` + * object directly, or as a promise containing the `response` or a new `response` object. + * * `responseError`: interceptor gets called when a previous interceptor threw an error or + * resolved with a rejection. + * + * + * ```js + * // register the interceptor as a service + * $provide.factory('myHttpInterceptor', function($q, dependency1, dependency2) { + * return { + * // optional method + * 'request': function(config) { + * // do something on success + * return config; + * }, + * + * // optional method + * 'requestError': function(rejection) { + * // do something on error + * if (canRecover(rejection)) { + * return responseOrNewPromise + * } + * return $q.reject(rejection); + * }, + * + * + * + * // optional method + * 'response': function(response) { + * // do something on success + * return response; + * }, + * + * // optional method + * 'responseError': function(rejection) { + * // do something on error + * if (canRecover(rejection)) { + * return responseOrNewPromise + * } + * return $q.reject(rejection); + * } + * }; + * }); + * + * $httpProvider.interceptors.push('myHttpInterceptor'); + * + * + * // alternatively, register the interceptor via an anonymous factory + * $httpProvider.interceptors.push(function($q, dependency1, dependency2) { + * return { + * 'request': function(config) { + * // same as above + * }, + * + * 'response': function(response) { + * // same as above + * } + * }; + * }); + * ``` + * + * # Response interceptors (DEPRECATED) + * + * Before you start creating interceptors, be sure to understand the + * {@link ng.$q $q and deferred/promise APIs}. + * + * For purposes of global error handling, authentication or any kind of synchronous or + * asynchronous preprocessing of received responses, it is desirable to be able to intercept + * responses for http requests before they are handed over to the application code that + * initiated these requests. The response interceptors leverage the {@link ng.$q + * promise apis} to fulfil this need for both synchronous and asynchronous preprocessing. + * + * The interceptors are service factories that are registered with the $httpProvider by + * adding them to the `$httpProvider.responseInterceptors` array. The factory is called and + * injected with dependencies (if specified) and returns the interceptor — a function that + * takes a {@link ng.$q promise} and returns the original or a new promise. + * + * ```js + * // register the interceptor as a service + * $provide.factory('myHttpInterceptor', function($q, dependency1, dependency2) { + * return function(promise) { + * return promise.then(function(response) { + * // do something on success + * return response; + * }, function(response) { + * // do something on error + * if (canRecover(response)) { + * return responseOrNewPromise + * } + * return $q.reject(response); + * }); + * } + * }); + * + * $httpProvider.responseInterceptors.push('myHttpInterceptor'); + * + * + * // register the interceptor via an anonymous factory + * $httpProvider.responseInterceptors.push(function($q, dependency1, dependency2) { + * return function(promise) { + * // same as above + * } + * }); + * ``` + * + * + * # Security Considerations + * + * When designing web applications, consider security threats from: + * + * - [JSON vulnerability](http://haacked.com/archive/2008/11/20/anatomy-of-a-subtle-json-vulnerability.aspx) + * - [XSRF](http://en.wikipedia.org/wiki/Cross-site_request_forgery) + * + * Both server and the client must cooperate in order to eliminate these threats. Angular comes + * pre-configured with strategies that address these issues, but for this to work backend server + * cooperation is required. + * + * ## JSON Vulnerability Protection + * + * A [JSON vulnerability](http://haacked.com/archive/2008/11/20/anatomy-of-a-subtle-json-vulnerability.aspx) + * allows third party website to turn your JSON resource URL into + * [JSONP](http://en.wikipedia.org/wiki/JSONP) request under some conditions. To + * counter this your server can prefix all JSON requests with following string `")]}',\n"`. + * Angular will automatically strip the prefix before processing it as JSON. + * + * For example if your server needs to return: + * ```js + * ['one','two'] + * ``` + * + * which is vulnerable to attack, your server can return: + * ```js + * )]}', + * ['one','two'] + * ``` + * + * Angular will strip the prefix, before processing the JSON. + * + * + * ## Cross Site Request Forgery (XSRF) Protection + * + * [XSRF](http://en.wikipedia.org/wiki/Cross-site_request_forgery) is a technique by which + * an unauthorized site can gain your user's private data. Angular provides a mechanism + * to counter XSRF. When performing XHR requests, the $http service reads a token from a cookie + * (by default, `XSRF-TOKEN`) and sets it as an HTTP header (`X-XSRF-TOKEN`). Since only + * JavaScript that runs on your domain could read the cookie, your server can be assured that + * the XHR came from JavaScript running on your domain. The header will not be set for + * cross-domain requests. + * + * To take advantage of this, your server needs to set a token in a JavaScript readable session + * cookie called `XSRF-TOKEN` on the first HTTP GET request. On subsequent XHR requests the + * server can verify that the cookie matches `X-XSRF-TOKEN` HTTP header, and therefore be sure + * that only JavaScript running on your domain could have sent the request. The token must be + * unique for each user and must be verifiable by the server (to prevent the JavaScript from + * making up its own tokens). We recommend that the token is a digest of your site's + * authentication cookie with a [salt](https://en.wikipedia.org/wiki/Salt_(cryptography)) + * for added security. + * + * The name of the headers can be specified using the xsrfHeaderName and xsrfCookieName + * properties of either $httpProvider.defaults at config-time, $http.defaults at run-time, + * or the per-request config object. + * + * + * @param {object} config Object describing the request to be made and how it should be + * processed. The object has following properties: + * + * - **method** – `{string}` – HTTP method (e.g. 'GET', 'POST', etc) + * - **url** – `{string}` – Absolute or relative URL of the resource that is being requested. + * - **params** – `{Object.}` – Map of strings or objects which will be turned + * to `?key1=value1&key2=value2` after the url. If the value is not a string, it will be + * JSONified. + * - **data** – `{string|Object}` – Data to be sent as the request message data. + * - **headers** – `{Object}` – Map of strings or functions which return strings representing + * HTTP headers to send to the server. If the return value of a function is null, the + * header will not be sent. + * - **xsrfHeaderName** – `{string}` – Name of HTTP header to populate with the XSRF token. + * - **xsrfCookieName** – `{string}` – Name of cookie containing the XSRF token. + * - **transformRequest** – + * `{function(data, headersGetter)|Array.}` – + * transform function or an array of such functions. The transform function takes the http + * request body and headers and returns its transformed (typically serialized) version. + * - **transformResponse** – + * `{function(data, headersGetter)|Array.}` – + * transform function or an array of such functions. The transform function takes the http + * response body and headers and returns its transformed (typically deserialized) version. + * - **cache** – `{boolean|Cache}` – If true, a default $http cache will be used to cache the + * GET request, otherwise if a cache instance built with + * {@link ng.$cacheFactory $cacheFactory}, this cache will be used for + * caching. + * - **timeout** – `{number|Promise}` – timeout in milliseconds, or {@link ng.$q promise} + * that should abort the request when resolved. + * - **withCredentials** - `{boolean}` - whether to set the `withCredentials` flag on the + * XHR object. See [requests with credentials]https://developer.mozilla.org/en/http_access_control#section_5 + * for more information. + * - **responseType** - `{string}` - see + * [requestType](https://developer.mozilla.org/en-US/docs/DOM/XMLHttpRequest#responseType). + * + * @returns {HttpPromise} Returns a {@link ng.$q promise} object with the + * standard `then` method and two http specific methods: `success` and `error`. The `then` + * method takes two arguments a success and an error callback which will be called with a + * response object. The `success` and `error` methods take a single argument - a function that + * will be called when the request succeeds or fails respectively. The arguments passed into + * these functions are destructured representation of the response object passed into the + * `then` method. The response object has these properties: + * + * - **data** – `{string|Object}` – The response body transformed with the transform + * functions. + * - **status** – `{number}` – HTTP status code of the response. + * - **headers** – `{function([headerName])}` – Header getter function. + * - **config** – `{Object}` – The configuration object that was used to generate the request. + * - **statusText** – `{string}` – HTTP status text of the response. + * + * @property {Array.} pendingRequests Array of config objects for currently pending + * requests. This is primarily meant to be used for debugging purposes. + * + * + * @example + + +
+ + +
+ + + +
http status code: {{status}}
+
http response data: {{data}}
+
+
+ + function FetchCtrl($scope, $http, $templateCache) { + $scope.method = 'GET'; + $scope.url = 'http-hello.html'; + + $scope.fetch = function() { + $scope.code = null; + $scope.response = null; + + $http({method: $scope.method, url: $scope.url, cache: $templateCache}). + success(function(data, status) { + $scope.status = status; + $scope.data = data; + }). + error(function(data, status) { + $scope.data = data || "Request failed"; + $scope.status = status; + }); + }; + + $scope.updateModel = function(method, url) { + $scope.method = method; + $scope.url = url; + }; + } + + + Hello, $http! + + + var status = element(by.binding('status')); + var data = element(by.binding('data')); + var fetchBtn = element(by.id('fetchbtn')); + var sampleGetBtn = element(by.id('samplegetbtn')); + var sampleJsonpBtn = element(by.id('samplejsonpbtn')); + var invalidJsonpBtn = element(by.id('invalidjsonpbtn')); + + it('should make an xhr GET request', function() { + sampleGetBtn.click(); + fetchBtn.click(); + expect(status.getText()).toMatch('200'); + expect(data.getText()).toMatch(/Hello, \$http!/); + }); + + it('should make a JSONP request to angularjs.org', function() { + sampleJsonpBtn.click(); + fetchBtn.click(); + expect(status.getText()).toMatch('200'); + expect(data.getText()).toMatch(/Super Hero!/); + }); + + it('should make JSONP request to invalid URL and invoke the error handler', + function() { + invalidJsonpBtn.click(); + fetchBtn.click(); + expect(status.getText()).toMatch('0'); + expect(data.getText()).toMatch('Request failed'); + }); + +
+ */ + function $http(requestConfig) { + var config = { + method: 'get', + transformRequest: defaults.transformRequest, + transformResponse: defaults.transformResponse + }; + var headers = mergeHeaders(requestConfig); + + extend(config, requestConfig); + config.headers = headers; + config.method = uppercase(config.method); + + var xsrfValue = urlIsSameOrigin(config.url) + ? $browser.cookies()[config.xsrfCookieName || defaults.xsrfCookieName] + : undefined; + if (xsrfValue) { + headers[(config.xsrfHeaderName || defaults.xsrfHeaderName)] = xsrfValue; + } + + + var serverRequest = function(config) { + headers = config.headers; + var reqData = transformData(config.data, headersGetter(headers), config.transformRequest); + + // strip content-type if data is undefined + if (isUndefined(config.data)) { + forEach(headers, function(value, header) { + if (lowercase(header) === 'content-type') { + delete headers[header]; + } + }); + } + + if (isUndefined(config.withCredentials) && !isUndefined(defaults.withCredentials)) { + config.withCredentials = defaults.withCredentials; + } + + // send request + return sendReq(config, reqData, headers).then(transformResponse, transformResponse); + }; + + var chain = [serverRequest, undefined]; + var promise = $q.when(config); + + // apply interceptors + forEach(reversedInterceptors, function(interceptor) { + if (interceptor.request || interceptor.requestError) { + chain.unshift(interceptor.request, interceptor.requestError); + } + if (interceptor.response || interceptor.responseError) { + chain.push(interceptor.response, interceptor.responseError); + } + }); + + while(chain.length) { + var thenFn = chain.shift(); + var rejectFn = chain.shift(); + + promise = promise.then(thenFn, rejectFn); + } + + promise.success = function(fn) { + promise.then(function(response) { + fn(response.data, response.status, response.headers, config); + }); + return promise; + }; + + promise.error = function(fn) { + promise.then(null, function(response) { + fn(response.data, response.status, response.headers, config); + }); + return promise; + }; + + return promise; + + function transformResponse(response) { + // make a copy since the response must be cacheable + var resp = extend({}, response, { + data: transformData(response.data, response.headers, config.transformResponse) + }); + return (isSuccess(response.status)) + ? resp + : $q.reject(resp); + } + + function mergeHeaders(config) { + var defHeaders = defaults.headers, + reqHeaders = extend({}, config.headers), + defHeaderName, lowercaseDefHeaderName, reqHeaderName; + + defHeaders = extend({}, defHeaders.common, defHeaders[lowercase(config.method)]); + + // execute if header value is function + execHeaders(defHeaders); + execHeaders(reqHeaders); + + // using for-in instead of forEach to avoid unecessary iteration after header has been found + defaultHeadersIteration: + for (defHeaderName in defHeaders) { + lowercaseDefHeaderName = lowercase(defHeaderName); + + for (reqHeaderName in reqHeaders) { + if (lowercase(reqHeaderName) === lowercaseDefHeaderName) { + continue defaultHeadersIteration; + } + } + + reqHeaders[defHeaderName] = defHeaders[defHeaderName]; + } + + return reqHeaders; + + function execHeaders(headers) { + var headerContent; + + forEach(headers, function(headerFn, header) { + if (isFunction(headerFn)) { + headerContent = headerFn(); + if (headerContent != null) { + headers[header] = headerContent; + } else { + delete headers[header]; + } + } + }); + } + } + } + + $http.pendingRequests = []; + + /** + * @ngdoc method + * @name $http#get + * + * @description + * Shortcut method to perform `GET` request. + * + * @param {string} url Relative or absolute URL specifying the destination of the request + * @param {Object=} config Optional configuration object + * @returns {HttpPromise} Future object + */ + + /** + * @ngdoc method + * @name $http#delete + * + * @description + * Shortcut method to perform `DELETE` request. + * + * @param {string} url Relative or absolute URL specifying the destination of the request + * @param {Object=} config Optional configuration object + * @returns {HttpPromise} Future object + */ + + /** + * @ngdoc method + * @name $http#head + * + * @description + * Shortcut method to perform `HEAD` request. + * + * @param {string} url Relative or absolute URL specifying the destination of the request + * @param {Object=} config Optional configuration object + * @returns {HttpPromise} Future object + */ + + /** + * @ngdoc method + * @name $http#jsonp + * + * @description + * Shortcut method to perform `JSONP` request. + * + * @param {string} url Relative or absolute URL specifying the destination of the request. + * Should contain `JSON_CALLBACK` string. + * @param {Object=} config Optional configuration object + * @returns {HttpPromise} Future object + */ + createShortMethods('get', 'delete', 'head', 'jsonp'); + + /** + * @ngdoc method + * @name $http#post + * + * @description + * Shortcut method to perform `POST` request. + * + * @param {string} url Relative or absolute URL specifying the destination of the request + * @param {*} data Request content + * @param {Object=} config Optional configuration object + * @returns {HttpPromise} Future object + */ + + /** + * @ngdoc method + * @name $http#put + * + * @description + * Shortcut method to perform `PUT` request. + * + * @param {string} url Relative or absolute URL specifying the destination of the request + * @param {*} data Request content + * @param {Object=} config Optional configuration object + * @returns {HttpPromise} Future object + */ + createShortMethodsWithData('post', 'put'); + + /** + * @ngdoc property + * @name $http#defaults + * + * @description + * Runtime equivalent of the `$httpProvider.defaults` property. Allows configuration of + * default headers, withCredentials as well as request and response transformations. + * + * See "Setting HTTP Headers" and "Transforming Requests and Responses" sections above. + */ + $http.defaults = defaults; + + + return $http; + + + function createShortMethods(names) { + forEach(arguments, function(name) { + $http[name] = function(url, config) { + return $http(extend(config || {}, { + method: name, + url: url + })); + }; + }); + } + + + function createShortMethodsWithData(name) { + forEach(arguments, function(name) { + $http[name] = function(url, data, config) { + return $http(extend(config || {}, { + method: name, + url: url, + data: data + })); + }; + }); + } + + + /** + * Makes the request. + * + * !!! ACCESSES CLOSURE VARS: + * $httpBackend, defaults, $log, $rootScope, defaultCache, $http.pendingRequests + */ + function sendReq(config, reqData, reqHeaders) { + var deferred = $q.defer(), + promise = deferred.promise, + cache, + cachedResp, + url = buildUrl(config.url, config.params); + + $http.pendingRequests.push(config); + promise.then(removePendingReq, removePendingReq); + + + if ((config.cache || defaults.cache) && config.cache !== false && config.method == 'GET') { + cache = isObject(config.cache) ? config.cache + : isObject(defaults.cache) ? defaults.cache + : defaultCache; + } + + if (cache) { + cachedResp = cache.get(url); + if (isDefined(cachedResp)) { + if (cachedResp.then) { + // cached request has already been sent, but there is no response yet + cachedResp.then(removePendingReq, removePendingReq); + return cachedResp; + } else { + // serving from cache + if (isArray(cachedResp)) { + resolvePromise(cachedResp[1], cachedResp[0], shallowCopy(cachedResp[2]), cachedResp[3]); + } else { + resolvePromise(cachedResp, 200, {}, 'OK'); + } + } + } else { + // put the promise for the non-transformed response into cache as a placeholder + cache.put(url, promise); + } + } + + // if we won't have the response in cache, send the request to the backend + if (isUndefined(cachedResp)) { + $httpBackend(config.method, url, reqData, done, reqHeaders, config.timeout, + config.withCredentials, config.responseType); + } + + return promise; + + + /** + * Callback registered to $httpBackend(): + * - caches the response if desired + * - resolves the raw $http promise + * - calls $apply + */ + function done(status, response, headersString, statusText) { + if (cache) { + if (isSuccess(status)) { + cache.put(url, [status, response, parseHeaders(headersString), statusText]); + } else { + // remove promise from the cache + cache.remove(url); + } + } + + resolvePromise(response, status, headersString, statusText); + if (!$rootScope.$$phase) $rootScope.$apply(); + } + + + /** + * Resolves the raw $http promise. + */ + function resolvePromise(response, status, headers, statusText) { + // normalize internal statuses to 0 + status = Math.max(status, 0); + + (isSuccess(status) ? deferred.resolve : deferred.reject)({ + data: response, + status: status, + headers: headersGetter(headers), + config: config, + statusText : statusText + }); + } + + + function removePendingReq() { + var idx = indexOf($http.pendingRequests, config); + if (idx !== -1) $http.pendingRequests.splice(idx, 1); + } + } + + + function buildUrl(url, params) { + if (!params) return url; + var parts = []; + forEachSorted(params, function(value, key) { + if (value === null || isUndefined(value)) return; + if (!isArray(value)) value = [value]; + + forEach(value, function(v) { + if (isObject(v)) { + v = toJson(v); + } + parts.push(encodeUriQuery(key) + '=' + + encodeUriQuery(v)); + }); + }); + if(parts.length > 0) { + url += ((url.indexOf('?') == -1) ? '?' : '&') + parts.join('&'); + } + return url; + } + + + }]; +} + +function createXhr(method) { + //if IE and the method is not RFC2616 compliant, or if XMLHttpRequest + //is not available, try getting an ActiveXObject. Otherwise, use XMLHttpRequest + //if it is available + if (msie <= 8 && (!method.match(/^(get|post|head|put|delete|options)$/i) || + !window.XMLHttpRequest)) { + return new window.ActiveXObject("Microsoft.XMLHTTP"); + } else if (window.XMLHttpRequest) { + return new window.XMLHttpRequest(); + } + + throw minErr('$httpBackend')('noxhr', "This browser does not support XMLHttpRequest."); +} + +/** + * @ngdoc service + * @name $httpBackend + * @requires $window + * @requires $document + * + * @description + * HTTP backend used by the {@link ng.$http service} that delegates to + * XMLHttpRequest object or JSONP and deals with browser incompatibilities. + * + * You should never need to use this service directly, instead use the higher-level abstractions: + * {@link ng.$http $http} or {@link ngResource.$resource $resource}. + * + * During testing this implementation is swapped with {@link ngMock.$httpBackend mock + * $httpBackend} which can be trained with responses. + */ +function $HttpBackendProvider() { + this.$get = ['$browser', '$window', '$document', function($browser, $window, $document) { + return createHttpBackend($browser, createXhr, $browser.defer, $window.angular.callbacks, $document[0]); + }]; +} + +function createHttpBackend($browser, createXhr, $browserDefer, callbacks, rawDocument) { + var ABORTED = -1; + + // TODO(vojta): fix the signature + return function(method, url, post, callback, headers, timeout, withCredentials, responseType) { + var status; + $browser.$$incOutstandingRequestCount(); + url = url || $browser.url(); + + if (lowercase(method) == 'jsonp') { + var callbackId = '_' + (callbacks.counter++).toString(36); + callbacks[callbackId] = function(data) { + callbacks[callbackId].data = data; + callbacks[callbackId].called = true; + }; + + var jsonpDone = jsonpReq(url.replace('JSON_CALLBACK', 'angular.callbacks.' + callbackId), + callbackId, function(status, text) { + completeRequest(callback, status, callbacks[callbackId].data, "", text); + callbacks[callbackId] = noop; + }); + } else { + + var xhr = createXhr(method); + + xhr.open(method, url, true); + forEach(headers, function(value, key) { + if (isDefined(value)) { + xhr.setRequestHeader(key, value); + } + }); + + // In IE6 and 7, this might be called synchronously when xhr.send below is called and the + // response is in the cache. the promise api will ensure that to the app code the api is + // always async + xhr.onreadystatechange = function() { + // onreadystatechange might get called multiple times with readyState === 4 on mobile webkit caused by + // xhrs that are resolved while the app is in the background (see #5426). + // since calling completeRequest sets the `xhr` variable to null, we just check if it's not null before + // continuing + // + // we can't set xhr.onreadystatechange to undefined or delete it because that breaks IE8 (method=PATCH) and + // Safari respectively. + if (xhr && xhr.readyState == 4) { + var responseHeaders = null, + response = null; + + if(status !== ABORTED) { + responseHeaders = xhr.getAllResponseHeaders(); + + // responseText is the old-school way of retrieving response (supported by IE8 & 9) + // response/responseType properties were introduced in XHR Level2 spec (supported by IE10) + response = ('response' in xhr) ? xhr.response : xhr.responseText; + } + + completeRequest(callback, + status || xhr.status, + response, + responseHeaders, + xhr.statusText || ''); + } + }; + + if (withCredentials) { + xhr.withCredentials = true; + } + + if (responseType) { + try { + xhr.responseType = responseType; + } catch (e) { + // WebKit added support for the json responseType value on 09/03/2013 + // https://bugs.webkit.org/show_bug.cgi?id=73648. Versions of Safari prior to 7 are + // known to throw when setting the value "json" as the response type. Other older + // browsers implementing the responseType + // + // The json response type can be ignored if not supported, because JSON payloads are + // parsed on the client-side regardless. + if (responseType !== 'json') { + throw e; + } + } + } + + xhr.send(post || null); + } + + if (timeout > 0) { + var timeoutId = $browserDefer(timeoutRequest, timeout); + } else if (timeout && timeout.then) { + timeout.then(timeoutRequest); + } + + + function timeoutRequest() { + status = ABORTED; + jsonpDone && jsonpDone(); + xhr && xhr.abort(); + } + + function completeRequest(callback, status, response, headersString, statusText) { + // cancel timeout and subsequent timeout promise resolution + timeoutId && $browserDefer.cancel(timeoutId); + jsonpDone = xhr = null; + + // fix status code when it is 0 (0 status is undocumented). + // Occurs when accessing file resources or on Android 4.1 stock browser + // while retrieving files from application cache. + if (status === 0) { + status = response ? 200 : urlResolve(url).protocol == 'file' ? 404 : 0; + } + + // normalize IE bug (http://bugs.jquery.com/ticket/1450) + status = status === 1223 ? 204 : status; + statusText = statusText || ''; + + callback(status, response, headersString, statusText); + $browser.$$completeOutstandingRequest(noop); + } + }; + + function jsonpReq(url, callbackId, done) { + // we can't use jQuery/jqLite here because jQuery does crazy shit with script elements, e.g.: + // - fetches local scripts via XHR and evals them + // - adds and immediately removes script elements from the document + var script = rawDocument.createElement('script'), callback = null; + script.type = "text/javascript"; + script.src = url; + script.async = true; + + callback = function(event) { + removeEventListenerFn(script, "load", callback); + removeEventListenerFn(script, "error", callback); + rawDocument.body.removeChild(script); + script = null; + var status = -1; + var text = "unknown"; + + if (event) { + if (event.type === "load" && !callbacks[callbackId].called) { + event = { type: "error" }; + } + text = event.type; + status = event.type === "error" ? 404 : 200; + } + + if (done) { + done(status, text); + } + }; + + addEventListenerFn(script, "load", callback); + addEventListenerFn(script, "error", callback); + + if (msie <= 8) { + script.onreadystatechange = function() { + if (isString(script.readyState) && /loaded|complete/.test(script.readyState)) { + script.onreadystatechange = null; + callback({ + type: 'load' + }); + } + }; + } + + rawDocument.body.appendChild(script); + return callback; + } +} + +var $interpolateMinErr = minErr('$interpolate'); + +/** + * @ngdoc provider + * @name $interpolateProvider + * @kind function + * + * @description + * + * Used for configuring the interpolation markup. Defaults to `{{` and `}}`. + * + * @example + + + +
+ //demo.label// +
+
+ + it('should interpolate binding with custom symbols', function() { + expect(element(by.binding('demo.label')).getText()).toBe('This binding is brought you by // interpolation symbols.'); + }); + +
+ */ +function $InterpolateProvider() { + var startSymbol = '{{'; + var endSymbol = '}}'; + + /** + * @ngdoc method + * @name $interpolateProvider#startSymbol + * @description + * Symbol to denote start of expression in the interpolated string. Defaults to `{{`. + * + * @param {string=} value new value to set the starting symbol to. + * @returns {string|self} Returns the symbol when used as getter and self if used as setter. + */ + this.startSymbol = function(value){ + if (value) { + startSymbol = value; + return this; + } else { + return startSymbol; + } + }; + + /** + * @ngdoc method + * @name $interpolateProvider#endSymbol + * @description + * Symbol to denote the end of expression in the interpolated string. Defaults to `}}`. + * + * @param {string=} value new value to set the ending symbol to. + * @returns {string|self} Returns the symbol when used as getter and self if used as setter. + */ + this.endSymbol = function(value){ + if (value) { + endSymbol = value; + return this; + } else { + return endSymbol; + } + }; + + + this.$get = ['$parse', '$exceptionHandler', '$sce', function($parse, $exceptionHandler, $sce) { + var startSymbolLength = startSymbol.length, + endSymbolLength = endSymbol.length; + + /** + * @ngdoc service + * @name $interpolate + * @kind function + * + * @requires $parse + * @requires $sce + * + * @description + * + * Compiles a string with markup into an interpolation function. This service is used by the + * HTML {@link ng.$compile $compile} service for data binding. See + * {@link ng.$interpolateProvider $interpolateProvider} for configuring the + * interpolation markup. + * + * + * ```js + * var $interpolate = ...; // injected + * var exp = $interpolate('Hello {{name | uppercase}}!'); + * expect(exp({name:'Angular'}).toEqual('Hello ANGULAR!'); + * ``` + * + * + * @param {string} text The text with markup to interpolate. + * @param {boolean=} mustHaveExpression if set to true then the interpolation string must have + * embedded expression in order to return an interpolation function. Strings with no + * embedded expression will return null for the interpolation function. + * @param {string=} trustedContext when provided, the returned function passes the interpolated + * result through {@link ng.$sce#getTrusted $sce.getTrusted(interpolatedResult, + * trustedContext)} before returning it. Refer to the {@link ng.$sce $sce} service that + * provides Strict Contextual Escaping for details. + * @returns {function(context)} an interpolation function which is used to compute the + * interpolated string. The function has these parameters: + * + * * `context`: an object against which any expressions embedded in the strings are evaluated + * against. + * + */ + function $interpolate(text, mustHaveExpression, trustedContext) { + var startIndex, + endIndex, + index = 0, + parts = [], + length = text.length, + hasInterpolation = false, + fn, + exp, + concat = []; + + while(index < length) { + if ( ((startIndex = text.indexOf(startSymbol, index)) != -1) && + ((endIndex = text.indexOf(endSymbol, startIndex + startSymbolLength)) != -1) ) { + (index != startIndex) && parts.push(text.substring(index, startIndex)); + parts.push(fn = $parse(exp = text.substring(startIndex + startSymbolLength, endIndex))); + fn.exp = exp; + index = endIndex + endSymbolLength; + hasInterpolation = true; + } else { + // we did not find anything, so we have to add the remainder to the parts array + (index != length) && parts.push(text.substring(index)); + index = length; + } + } + + if (!(length = parts.length)) { + // we added, nothing, must have been an empty string. + parts.push(''); + length = 1; + } + + // Concatenating expressions makes it hard to reason about whether some combination of + // concatenated values are unsafe to use and could easily lead to XSS. By requiring that a + // single expression be used for iframe[src], object[src], etc., we ensure that the value + // that's used is assigned or constructed by some JS code somewhere that is more testable or + // make it obvious that you bound the value to some user controlled value. This helps reduce + // the load when auditing for XSS issues. + if (trustedContext && parts.length > 1) { + throw $interpolateMinErr('noconcat', + "Error while interpolating: {0}\nStrict Contextual Escaping disallows " + + "interpolations that concatenate multiple expressions when a trusted value is " + + "required. See http://docs.angularjs.org/api/ng.$sce", text); + } + + if (!mustHaveExpression || hasInterpolation) { + concat.length = length; + fn = function(context) { + try { + for(var i = 0, ii = length, part; i + * **Note**: Intervals created by this service must be explicitly destroyed when you are finished + * with them. In particular they are not automatically destroyed when a controller's scope or a + * directive's element are destroyed. + * You should take this into consideration and make sure to always cancel the interval at the + * appropriate moment. See the example below for more details on how and when to do this. + * + * + * @param {function()} fn A function that should be called repeatedly. + * @param {number} delay Number of milliseconds between each function call. + * @param {number=} [count=0] Number of times to repeat. If not set, or 0, will repeat + * indefinitely. + * @param {boolean=} [invokeApply=true] If set to `false` skips model dirty checking, otherwise + * will invoke `fn` within the {@link ng.$rootScope.Scope#$apply $apply} block. + * @returns {promise} A promise which will be notified on each iteration. + * + * @example + * + * + * + * + *
+ *
+ * Date format:
+ * Current time is: + *
+ * Blood 1 : {{blood_1}} + * Blood 2 : {{blood_2}} + * + * + * + *
+ *
+ * + *
+ *
+ */ + function interval(fn, delay, count, invokeApply) { + var setInterval = $window.setInterval, + clearInterval = $window.clearInterval, + deferred = $q.defer(), + promise = deferred.promise, + iteration = 0, + skipApply = (isDefined(invokeApply) && !invokeApply); + + count = isDefined(count) ? count : 0; + + promise.then(null, null, fn); + + promise.$$intervalId = setInterval(function tick() { + deferred.notify(iteration++); + + if (count > 0 && iteration >= count) { + deferred.resolve(iteration); + clearInterval(promise.$$intervalId); + delete intervals[promise.$$intervalId]; + } + + if (!skipApply) $rootScope.$apply(); + + }, delay); + + intervals[promise.$$intervalId] = deferred; + + return promise; + } + + + /** + * @ngdoc method + * @name $interval#cancel + * + * @description + * Cancels a task associated with the `promise`. + * + * @param {promise} promise returned by the `$interval` function. + * @returns {boolean} Returns `true` if the task was successfully canceled. + */ + interval.cancel = function(promise) { + if (promise && promise.$$intervalId in intervals) { + intervals[promise.$$intervalId].reject('canceled'); + clearInterval(promise.$$intervalId); + delete intervals[promise.$$intervalId]; + return true; + } + return false; + }; + + return interval; + }]; +} + +/** + * @ngdoc service + * @name $locale + * + * @description + * $locale service provides localization rules for various Angular components. As of right now the + * only public api is: + * + * * `id` – `{string}` – locale id formatted as `languageId-countryId` (e.g. `en-us`) + */ +function $LocaleProvider(){ + this.$get = function() { + return { + id: 'en-us', + + NUMBER_FORMATS: { + DECIMAL_SEP: '.', + GROUP_SEP: ',', + PATTERNS: [ + { // Decimal Pattern + minInt: 1, + minFrac: 0, + maxFrac: 3, + posPre: '', + posSuf: '', + negPre: '-', + negSuf: '', + gSize: 3, + lgSize: 3 + },{ //Currency Pattern + minInt: 1, + minFrac: 2, + maxFrac: 2, + posPre: '\u00A4', + posSuf: '', + negPre: '(\u00A4', + negSuf: ')', + gSize: 3, + lgSize: 3 + } + ], + CURRENCY_SYM: '$' + }, + + DATETIME_FORMATS: { + MONTH: + 'January,February,March,April,May,June,July,August,September,October,November,December' + .split(','), + SHORTMONTH: 'Jan,Feb,Mar,Apr,May,Jun,Jul,Aug,Sep,Oct,Nov,Dec'.split(','), + DAY: 'Sunday,Monday,Tuesday,Wednesday,Thursday,Friday,Saturday'.split(','), + SHORTDAY: 'Sun,Mon,Tue,Wed,Thu,Fri,Sat'.split(','), + AMPMS: ['AM','PM'], + medium: 'MMM d, y h:mm:ss a', + short: 'M/d/yy h:mm a', + fullDate: 'EEEE, MMMM d, y', + longDate: 'MMMM d, y', + mediumDate: 'MMM d, y', + shortDate: 'M/d/yy', + mediumTime: 'h:mm:ss a', + shortTime: 'h:mm a' + }, + + pluralCat: function(num) { + if (num === 1) { + return 'one'; + } + return 'other'; + } + }; + }; +} + +var PATH_MATCH = /^([^\?#]*)(\?([^#]*))?(#(.*))?$/, + DEFAULT_PORTS = {'http': 80, 'https': 443, 'ftp': 21}; +var $locationMinErr = minErr('$location'); + + +/** + * Encode path using encodeUriSegment, ignoring forward slashes + * + * @param {string} path Path to encode + * @returns {string} + */ +function encodePath(path) { + var segments = path.split('/'), + i = segments.length; + + while (i--) { + segments[i] = encodeUriSegment(segments[i]); + } + + return segments.join('/'); +} + +function parseAbsoluteUrl(absoluteUrl, locationObj, appBase) { + var parsedUrl = urlResolve(absoluteUrl, appBase); + + locationObj.$$protocol = parsedUrl.protocol; + locationObj.$$host = parsedUrl.hostname; + locationObj.$$port = int(parsedUrl.port) || DEFAULT_PORTS[parsedUrl.protocol] || null; +} + + +function parseAppUrl(relativeUrl, locationObj, appBase) { + var prefixed = (relativeUrl.charAt(0) !== '/'); + if (prefixed) { + relativeUrl = '/' + relativeUrl; + } + var match = urlResolve(relativeUrl, appBase); + locationObj.$$path = decodeURIComponent(prefixed && match.pathname.charAt(0) === '/' ? + match.pathname.substring(1) : match.pathname); + locationObj.$$search = parseKeyValue(match.search); + locationObj.$$hash = decodeURIComponent(match.hash); + + // make sure path starts with '/'; + if (locationObj.$$path && locationObj.$$path.charAt(0) != '/') { + locationObj.$$path = '/' + locationObj.$$path; + } +} + + +/** + * + * @param {string} begin + * @param {string} whole + * @returns {string} returns text from whole after begin or undefined if it does not begin with + * expected string. + */ +function beginsWith(begin, whole) { + if (whole.indexOf(begin) === 0) { + return whole.substr(begin.length); + } +} + + +function stripHash(url) { + var index = url.indexOf('#'); + return index == -1 ? url : url.substr(0, index); +} + + +function stripFile(url) { + return url.substr(0, stripHash(url).lastIndexOf('/') + 1); +} + +/* return the server only (scheme://host:port) */ +function serverBase(url) { + return url.substring(0, url.indexOf('/', url.indexOf('//') + 2)); +} + + +/** + * LocationHtml5Url represents an url + * This object is exposed as $location service when HTML5 mode is enabled and supported + * + * @constructor + * @param {string} appBase application base URL + * @param {string} basePrefix url path prefix + */ +function LocationHtml5Url(appBase, basePrefix) { + this.$$html5 = true; + basePrefix = basePrefix || ''; + var appBaseNoFile = stripFile(appBase); + parseAbsoluteUrl(appBase, this, appBase); + + + /** + * Parse given html5 (regular) url string into properties + * @param {string} newAbsoluteUrl HTML5 url + * @private + */ + this.$$parse = function(url) { + var pathUrl = beginsWith(appBaseNoFile, url); + if (!isString(pathUrl)) { + throw $locationMinErr('ipthprfx', 'Invalid url "{0}", missing path prefix "{1}".', url, + appBaseNoFile); + } + + parseAppUrl(pathUrl, this, appBase); + + if (!this.$$path) { + this.$$path = '/'; + } + + this.$$compose(); + }; + + /** + * Compose url and update `absUrl` property + * @private + */ + this.$$compose = function() { + var search = toKeyValue(this.$$search), + hash = this.$$hash ? '#' + encodeUriSegment(this.$$hash) : ''; + + this.$$url = encodePath(this.$$path) + (search ? '?' + search : '') + hash; + this.$$absUrl = appBaseNoFile + this.$$url.substr(1); // first char is always '/' + }; + + this.$$rewrite = function(url) { + var appUrl, prevAppUrl; + + if ( (appUrl = beginsWith(appBase, url)) !== undefined ) { + prevAppUrl = appUrl; + if ( (appUrl = beginsWith(basePrefix, appUrl)) !== undefined ) { + return appBaseNoFile + (beginsWith('/', appUrl) || appUrl); + } else { + return appBase + prevAppUrl; + } + } else if ( (appUrl = beginsWith(appBaseNoFile, url)) !== undefined ) { + return appBaseNoFile + appUrl; + } else if (appBaseNoFile == url + '/') { + return appBaseNoFile; + } + }; +} + + +/** + * LocationHashbangUrl represents url + * This object is exposed as $location service when developer doesn't opt into html5 mode. + * It also serves as the base class for html5 mode fallback on legacy browsers. + * + * @constructor + * @param {string} appBase application base URL + * @param {string} hashPrefix hashbang prefix + */ +function LocationHashbangUrl(appBase, hashPrefix) { + var appBaseNoFile = stripFile(appBase); + + parseAbsoluteUrl(appBase, this, appBase); + + + /** + * Parse given hashbang url into properties + * @param {string} url Hashbang url + * @private + */ + this.$$parse = function(url) { + var withoutBaseUrl = beginsWith(appBase, url) || beginsWith(appBaseNoFile, url); + var withoutHashUrl = withoutBaseUrl.charAt(0) == '#' + ? beginsWith(hashPrefix, withoutBaseUrl) + : (this.$$html5) + ? withoutBaseUrl + : ''; + + if (!isString(withoutHashUrl)) { + throw $locationMinErr('ihshprfx', 'Invalid url "{0}", missing hash prefix "{1}".', url, + hashPrefix); + } + parseAppUrl(withoutHashUrl, this, appBase); + + this.$$path = removeWindowsDriveName(this.$$path, withoutHashUrl, appBase); + + this.$$compose(); + + /* + * In Windows, on an anchor node on documents loaded from + * the filesystem, the browser will return a pathname + * prefixed with the drive name ('/C:/path') when a + * pathname without a drive is set: + * * a.setAttribute('href', '/foo') + * * a.pathname === '/C:/foo' //true + * + * Inside of Angular, we're always using pathnames that + * do not include drive names for routing. + */ + function removeWindowsDriveName (path, url, base) { + /* + Matches paths for file protocol on windows, + such as /C:/foo/bar, and captures only /foo/bar. + */ + var windowsFilePathExp = /^\/[A-Z]:(\/.*)/; + + var firstPathSegmentMatch; + + //Get the relative path from the input URL. + if (url.indexOf(base) === 0) { + url = url.replace(base, ''); + } + + // The input URL intentionally contains a first path segment that ends with a colon. + if (windowsFilePathExp.exec(url)) { + return path; + } + + firstPathSegmentMatch = windowsFilePathExp.exec(path); + return firstPathSegmentMatch ? firstPathSegmentMatch[1] : path; + } + }; + + /** + * Compose hashbang url and update `absUrl` property + * @private + */ + this.$$compose = function() { + var search = toKeyValue(this.$$search), + hash = this.$$hash ? '#' + encodeUriSegment(this.$$hash) : ''; + + this.$$url = encodePath(this.$$path) + (search ? '?' + search : '') + hash; + this.$$absUrl = appBase + (this.$$url ? hashPrefix + this.$$url : ''); + }; + + this.$$rewrite = function(url) { + if(stripHash(appBase) == stripHash(url)) { + return url; + } + }; +} + + +/** + * LocationHashbangUrl represents url + * This object is exposed as $location service when html5 history api is enabled but the browser + * does not support it. + * + * @constructor + * @param {string} appBase application base URL + * @param {string} hashPrefix hashbang prefix + */ +function LocationHashbangInHtml5Url(appBase, hashPrefix) { + this.$$html5 = true; + LocationHashbangUrl.apply(this, arguments); + + var appBaseNoFile = stripFile(appBase); + + this.$$rewrite = function(url) { + var appUrl; + + if ( appBase == stripHash(url) ) { + return url; + } else if ( (appUrl = beginsWith(appBaseNoFile, url)) ) { + return appBase + hashPrefix + appUrl; + } else if ( appBaseNoFile === url + '/') { + return appBaseNoFile; + } + }; + + this.$$compose = function() { + var search = toKeyValue(this.$$search), + hash = this.$$hash ? '#' + encodeUriSegment(this.$$hash) : ''; + + this.$$url = encodePath(this.$$path) + (search ? '?' + search : '') + hash; + // include hashPrefix in $$absUrl when $$url is empty so IE8 & 9 do not reload page because of removal of '#' + this.$$absUrl = appBase + hashPrefix + this.$$url; + }; + +} + + +LocationHashbangInHtml5Url.prototype = + LocationHashbangUrl.prototype = + LocationHtml5Url.prototype = { + + /** + * Are we in html5 mode? + * @private + */ + $$html5: false, + + /** + * Has any change been replacing ? + * @private + */ + $$replace: false, + + /** + * @ngdoc method + * @name $location#absUrl + * + * @description + * This method is getter only. + * + * Return full url representation with all segments encoded according to rules specified in + * [RFC 3986](http://www.ietf.org/rfc/rfc3986.txt). + * + * @return {string} full url + */ + absUrl: locationGetter('$$absUrl'), + + /** + * @ngdoc method + * @name $location#url + * + * @description + * This method is getter / setter. + * + * Return url (e.g. `/path?a=b#hash`) when called without any parameter. + * + * Change path, search and hash, when called with parameter and return `$location`. + * + * @param {string=} url New url without base prefix (e.g. `/path?a=b#hash`) + * @param {string=} replace The path that will be changed + * @return {string} url + */ + url: function(url, replace) { + if (isUndefined(url)) + return this.$$url; + + var match = PATH_MATCH.exec(url); + if (match[1]) this.path(decodeURIComponent(match[1])); + if (match[2] || match[1]) this.search(match[3] || ''); + this.hash(match[5] || '', replace); + + return this; + }, + + /** + * @ngdoc method + * @name $location#protocol + * + * @description + * This method is getter only. + * + * Return protocol of current url. + * + * @return {string} protocol of current url + */ + protocol: locationGetter('$$protocol'), + + /** + * @ngdoc method + * @name $location#host + * + * @description + * This method is getter only. + * + * Return host of current url. + * + * @return {string} host of current url. + */ + host: locationGetter('$$host'), + + /** + * @ngdoc method + * @name $location#port + * + * @description + * This method is getter only. + * + * Return port of current url. + * + * @return {Number} port + */ + port: locationGetter('$$port'), + + /** + * @ngdoc method + * @name $location#path + * + * @description + * This method is getter / setter. + * + * Return path of current url when called without any parameter. + * + * Change path when called with parameter and return `$location`. + * + * Note: Path should always begin with forward slash (/), this method will add the forward slash + * if it is missing. + * + * @param {string=} path New path + * @return {string} path + */ + path: locationGetterSetter('$$path', function(path) { + return path.charAt(0) == '/' ? path : '/' + path; + }), + + /** + * @ngdoc method + * @name $location#search + * + * @description + * This method is getter / setter. + * + * Return search part (as object) of current url when called without any parameter. + * + * Change search part when called with parameter and return `$location`. + * + * + * ```js + * // given url http://example.com/#/some/path?foo=bar&baz=xoxo + * var searchObject = $location.search(); + * // => {foo: 'bar', baz: 'xoxo'} + * + * + * // set foo to 'yipee' + * $location.search('foo', 'yipee'); + * // => $location + * ``` + * + * @param {string|Object.|Object.>} search New search params - string or + * hash object. + * + * When called with a single argument the method acts as a setter, setting the `search` component + * of `$location` to the specified value. + * + * If the argument is a hash object containing an array of values, these values will be encoded + * as duplicate search parameters in the url. + * + * @param {(string|Array)=} paramValue If `search` is a string, then `paramValue` will + * override only a single search property. + * + * If `paramValue` is an array, it will override the property of the `search` component of + * `$location` specified via the first argument. + * + * If `paramValue` is `null`, the property specified via the first argument will be deleted. + * + * @return {Object} If called with no arguments returns the parsed `search` object. If called with + * one or more arguments returns `$location` object itself. + */ + search: function(search, paramValue) { + switch (arguments.length) { + case 0: + return this.$$search; + case 1: + if (isString(search)) { + this.$$search = parseKeyValue(search); + } else if (isObject(search)) { + this.$$search = search; + } else { + throw $locationMinErr('isrcharg', + 'The first argument of the `$location#search()` call must be a string or an object.'); + } + break; + default: + if (isUndefined(paramValue) || paramValue === null) { + delete this.$$search[search]; + } else { + this.$$search[search] = paramValue; + } + } + + this.$$compose(); + return this; + }, + + /** + * @ngdoc method + * @name $location#hash + * + * @description + * This method is getter / setter. + * + * Return hash fragment when called without any parameter. + * + * Change hash fragment when called with parameter and return `$location`. + * + * @param {string=} hash New hash fragment + * @return {string} hash + */ + hash: locationGetterSetter('$$hash', identity), + + /** + * @ngdoc method + * @name $location#replace + * + * @description + * If called, all changes to $location during current `$digest` will be replacing current history + * record, instead of adding new one. + */ + replace: function() { + this.$$replace = true; + return this; + } +}; + +function locationGetter(property) { + return function() { + return this[property]; + }; +} + + +function locationGetterSetter(property, preprocess) { + return function(value) { + if (isUndefined(value)) + return this[property]; + + this[property] = preprocess(value); + this.$$compose(); + + return this; + }; +} + + +/** + * @ngdoc service + * @name $location + * + * @requires $rootElement + * + * @description + * The $location service parses the URL in the browser address bar (based on the + * [window.location](https://developer.mozilla.org/en/window.location)) and makes the URL + * available to your application. Changes to the URL in the address bar are reflected into + * $location service and changes to $location are reflected into the browser address bar. + * + * **The $location service:** + * + * - Exposes the current URL in the browser address bar, so you can + * - Watch and observe the URL. + * - Change the URL. + * - Synchronizes the URL with the browser when the user + * - Changes the address bar. + * - Clicks the back or forward button (or clicks a History link). + * - Clicks on a link. + * - Represents the URL object as a set of methods (protocol, host, port, path, search, hash). + * + * For more information see {@link guide/$location Developer Guide: Using $location} + */ + +/** + * @ngdoc provider + * @name $locationProvider + * @description + * Use the `$locationProvider` to configure how the application deep linking paths are stored. + */ +function $LocationProvider(){ + var hashPrefix = '', + html5Mode = false; + + /** + * @ngdoc property + * @name $locationProvider#hashPrefix + * @description + * @param {string=} prefix Prefix for hash part (containing path and search) + * @returns {*} current value if used as getter or itself (chaining) if used as setter + */ + this.hashPrefix = function(prefix) { + if (isDefined(prefix)) { + hashPrefix = prefix; + return this; + } else { + return hashPrefix; + } + }; + + /** + * @ngdoc property + * @name $locationProvider#html5Mode + * @description + * @param {boolean=} mode Use HTML5 strategy if available. + * @returns {*} current value if used as getter or itself (chaining) if used as setter + */ + this.html5Mode = function(mode) { + if (isDefined(mode)) { + html5Mode = mode; + return this; + } else { + return html5Mode; + } + }; + + /** + * @ngdoc event + * @name $location#$locationChangeStart + * @eventType broadcast on root scope + * @description + * Broadcasted before a URL will change. This change can be prevented by calling + * `preventDefault` method of the event. See {@link ng.$rootScope.Scope#$on} for more + * details about event object. Upon successful change + * {@link ng.$location#events_$locationChangeSuccess $locationChangeSuccess} is fired. + * + * @param {Object} angularEvent Synthetic event object. + * @param {string} newUrl New URL + * @param {string=} oldUrl URL that was before it was changed. + */ + + /** + * @ngdoc event + * @name $location#$locationChangeSuccess + * @eventType broadcast on root scope + * @description + * Broadcasted after a URL was changed. + * + * @param {Object} angularEvent Synthetic event object. + * @param {string} newUrl New URL + * @param {string=} oldUrl URL that was before it was changed. + */ + + this.$get = ['$rootScope', '$browser', '$sniffer', '$rootElement', + function( $rootScope, $browser, $sniffer, $rootElement) { + var $location, + LocationMode, + baseHref = $browser.baseHref(), // if base[href] is undefined, it defaults to '' + initialUrl = $browser.url(), + appBase; + + if (html5Mode) { + appBase = serverBase(initialUrl) + (baseHref || '/'); + LocationMode = $sniffer.history ? LocationHtml5Url : LocationHashbangInHtml5Url; + } else { + appBase = stripHash(initialUrl); + LocationMode = LocationHashbangUrl; + } + $location = new LocationMode(appBase, '#' + hashPrefix); + $location.$$parse($location.$$rewrite(initialUrl)); + + $rootElement.on('click', function(event) { + // TODO(vojta): rewrite link when opening in new tab/window (in legacy browser) + // currently we open nice url link and redirect then + + if (event.ctrlKey || event.metaKey || event.which == 2) return; + + var elm = jqLite(event.target); + + // traverse the DOM up to find first A tag + while (lowercase(elm[0].nodeName) !== 'a') { + // ignore rewriting if no A tag (reached root element, or no parent - removed from document) + if (elm[0] === $rootElement[0] || !(elm = elm.parent())[0]) return; + } + + var absHref = elm.prop('href'); + + if (isObject(absHref) && absHref.toString() === '[object SVGAnimatedString]') { + // SVGAnimatedString.animVal should be identical to SVGAnimatedString.baseVal, unless during + // an animation. + absHref = urlResolve(absHref.animVal).href; + } + + // Make relative links work in HTML5 mode for legacy browsers (or at least IE8 & 9) + // The href should be a regular url e.g. /link/somewhere or link/somewhere or ../somewhere or + // somewhere#anchor or http://example.com/somewhere + if (LocationMode === LocationHashbangInHtml5Url) { + // get the actual href attribute - see + // http://msdn.microsoft.com/en-us/library/ie/dd347148(v=vs.85).aspx + var href = elm.attr('href') || elm.attr('xlink:href'); + + if (href.indexOf('://') < 0) { // Ignore absolute URLs + var prefix = '#' + hashPrefix; + if (href[0] == '/') { + // absolute path - replace old path + absHref = appBase + prefix + href; + } else if (href[0] == '#') { + // local anchor + absHref = appBase + prefix + ($location.path() || '/') + href; + } else { + // relative path - join with current path + var stack = $location.path().split("/"), + parts = href.split("/"); + for (var i=0; i html5 url + if ($location.absUrl() != initialUrl) { + $browser.url($location.absUrl(), true); + } + + // update $location when $browser url changes + $browser.onUrlChange(function(newUrl) { + if ($location.absUrl() != newUrl) { + $rootScope.$evalAsync(function() { + var oldUrl = $location.absUrl(); + + $location.$$parse(newUrl); + if ($rootScope.$broadcast('$locationChangeStart', newUrl, + oldUrl).defaultPrevented) { + $location.$$parse(oldUrl); + $browser.url(oldUrl); + } else { + afterLocationChange(oldUrl); + } + }); + if (!$rootScope.$$phase) $rootScope.$digest(); + } + }); + + // update browser + var changeCounter = 0; + $rootScope.$watch(function $locationWatch() { + var oldUrl = $browser.url(); + var currentReplace = $location.$$replace; + + if (!changeCounter || oldUrl != $location.absUrl()) { + changeCounter++; + $rootScope.$evalAsync(function() { + if ($rootScope.$broadcast('$locationChangeStart', $location.absUrl(), oldUrl). + defaultPrevented) { + $location.$$parse(oldUrl); + } else { + $browser.url($location.absUrl(), currentReplace); + afterLocationChange(oldUrl); + } + }); + } + $location.$$replace = false; + + return changeCounter; + }); + + return $location; + + function afterLocationChange(oldUrl) { + $rootScope.$broadcast('$locationChangeSuccess', $location.absUrl(), oldUrl); + } +}]; +} + +/** + * @ngdoc service + * @name $log + * @requires $window + * + * @description + * Simple service for logging. Default implementation safely writes the message + * into the browser's console (if present). + * + * The main purpose of this service is to simplify debugging and troubleshooting. + * + * The default is to log `debug` messages. You can use + * {@link ng.$logProvider ng.$logProvider#debugEnabled} to change this. + * + * @example + + + function LogCtrl($scope, $log) { + $scope.$log = $log; + $scope.message = 'Hello World!'; + } + + +
+

Reload this page with open console, enter text and hit the log button...

+ Message: + + + + + +
+
+
+ */ + +/** + * @ngdoc provider + * @name $logProvider + * @description + * Use the `$logProvider` to configure how the application logs messages + */ +function $LogProvider(){ + var debug = true, + self = this; + + /** + * @ngdoc property + * @name $logProvider#debugEnabled + * @description + * @param {boolean=} flag enable or disable debug level messages + * @returns {*} current value if used as getter or itself (chaining) if used as setter + */ + this.debugEnabled = function(flag) { + if (isDefined(flag)) { + debug = flag; + return this; + } else { + return debug; + } + }; + + this.$get = ['$window', function($window){ + return { + /** + * @ngdoc method + * @name $log#log + * + * @description + * Write a log message + */ + log: consoleLog('log'), + + /** + * @ngdoc method + * @name $log#info + * + * @description + * Write an information message + */ + info: consoleLog('info'), + + /** + * @ngdoc method + * @name $log#warn + * + * @description + * Write a warning message + */ + warn: consoleLog('warn'), + + /** + * @ngdoc method + * @name $log#error + * + * @description + * Write an error message + */ + error: consoleLog('error'), + + /** + * @ngdoc method + * @name $log#debug + * + * @description + * Write a debug message + */ + debug: (function () { + var fn = consoleLog('debug'); + + return function() { + if (debug) { + fn.apply(self, arguments); + } + }; + }()) + }; + + function formatError(arg) { + if (arg instanceof Error) { + if (arg.stack) { + arg = (arg.message && arg.stack.indexOf(arg.message) === -1) + ? 'Error: ' + arg.message + '\n' + arg.stack + : arg.stack; + } else if (arg.sourceURL) { + arg = arg.message + '\n' + arg.sourceURL + ':' + arg.line; + } + } + return arg; + } + + function consoleLog(type) { + var console = $window.console || {}, + logFn = console[type] || console.log || noop, + hasApply = false; + + // Note: reading logFn.apply throws an error in IE11 in IE8 document mode. + // The reason behind this is that console.log has type "object" in IE8... + try { + hasApply = !!logFn.apply; + } catch (e) {} + + if (hasApply) { + return function() { + var args = []; + forEach(arguments, function(arg) { + args.push(formatError(arg)); + }); + return logFn.apply(console, args); + }; + } + + // we are IE which either doesn't have window.console => this is noop and we do nothing, + // or we are IE where console.log doesn't have apply so we log at least first 2 args + return function(arg1, arg2) { + logFn(arg1, arg2 == null ? '' : arg2); + }; + } + }]; +} + +var $parseMinErr = minErr('$parse'); +var promiseWarningCache = {}; +var promiseWarning; + +// Sandboxing Angular Expressions +// ------------------------------ +// Angular expressions are generally considered safe because these expressions only have direct +// access to $scope and locals. However, one can obtain the ability to execute arbitrary JS code by +// obtaining a reference to native JS functions such as the Function constructor. +// +// As an example, consider the following Angular expression: +// +// {}.toString.constructor(alert("evil JS code")) +// +// We want to prevent this type of access. For the sake of performance, during the lexing phase we +// disallow any "dotted" access to any member named "constructor". +// +// For reflective calls (a[b]) we check that the value of the lookup is not the Function constructor +// while evaluating the expression, which is a stronger but more expensive test. Since reflective +// calls are expensive anyway, this is not such a big deal compared to static dereferencing. +// +// This sandboxing technique is not perfect and doesn't aim to be. The goal is to prevent exploits +// against the expression language, but not to prevent exploits that were enabled by exposing +// sensitive JavaScript or browser apis on Scope. Exposing such objects on a Scope is never a good +// practice and therefore we are not even trying to protect against interaction with an object +// explicitly exposed in this way. +// +// A developer could foil the name check by aliasing the Function constructor under a different +// name on the scope. +// +// In general, it is not possible to access a Window object from an angular expression unless a +// window or some DOM object that has a reference to window is published onto a Scope. + +function ensureSafeMemberName(name, fullExpression) { + if (name === "constructor") { + throw $parseMinErr('isecfld', + 'Referencing "constructor" field in Angular expressions is disallowed! Expression: {0}', + fullExpression); + } + return name; +} + +function ensureSafeObject(obj, fullExpression) { + // nifty check if obj is Function that is fast and works across iframes and other contexts + if (obj) { + if (obj.constructor === obj) { + throw $parseMinErr('isecfn', + 'Referencing Function in Angular expressions is disallowed! Expression: {0}', + fullExpression); + } else if (// isWindow(obj) + obj.document && obj.location && obj.alert && obj.setInterval) { + throw $parseMinErr('isecwindow', + 'Referencing the Window in Angular expressions is disallowed! Expression: {0}', + fullExpression); + } else if (// isElement(obj) + obj.children && (obj.nodeName || (obj.prop && obj.attr && obj.find))) { + throw $parseMinErr('isecdom', + 'Referencing DOM nodes in Angular expressions is disallowed! Expression: {0}', + fullExpression); + } + } + return obj; +} + +var OPERATORS = { + /* jshint bitwise : false */ + 'null':function(){return null;}, + 'true':function(){return true;}, + 'false':function(){return false;}, + undefined:noop, + '+':function(self, locals, a,b){ + a=a(self, locals); b=b(self, locals); + if (isDefined(a)) { + if (isDefined(b)) { + return a + b; + } + return a; + } + return isDefined(b)?b:undefined;}, + '-':function(self, locals, a,b){ + a=a(self, locals); b=b(self, locals); + return (isDefined(a)?a:0)-(isDefined(b)?b:0); + }, + '*':function(self, locals, a,b){return a(self, locals)*b(self, locals);}, + '/':function(self, locals, a,b){return a(self, locals)/b(self, locals);}, + '%':function(self, locals, a,b){return a(self, locals)%b(self, locals);}, + '^':function(self, locals, a,b){return a(self, locals)^b(self, locals);}, + '=':noop, + '===':function(self, locals, a, b){return a(self, locals)===b(self, locals);}, + '!==':function(self, locals, a, b){return a(self, locals)!==b(self, locals);}, + '==':function(self, locals, a,b){return a(self, locals)==b(self, locals);}, + '!=':function(self, locals, a,b){return a(self, locals)!=b(self, locals);}, + '<':function(self, locals, a,b){return a(self, locals)':function(self, locals, a,b){return a(self, locals)>b(self, locals);}, + '<=':function(self, locals, a,b){return a(self, locals)<=b(self, locals);}, + '>=':function(self, locals, a,b){return a(self, locals)>=b(self, locals);}, + '&&':function(self, locals, a,b){return a(self, locals)&&b(self, locals);}, + '||':function(self, locals, a,b){return a(self, locals)||b(self, locals);}, + '&':function(self, locals, a,b){return a(self, locals)&b(self, locals);}, +// '|':function(self, locals, a,b){return a|b;}, + '|':function(self, locals, a,b){return b(self, locals)(self, locals, a(self, locals));}, + '!':function(self, locals, a){return !a(self, locals);} +}; +/* jshint bitwise: true */ +var ESCAPE = {"n":"\n", "f":"\f", "r":"\r", "t":"\t", "v":"\v", "'":"'", '"':'"'}; + + +///////////////////////////////////////// + + +/** + * @constructor + */ +var Lexer = function (options) { + this.options = options; +}; + +Lexer.prototype = { + constructor: Lexer, + + lex: function (text) { + this.text = text; + + this.index = 0; + this.ch = undefined; + this.lastCh = ':'; // can start regexp + + this.tokens = []; + + while (this.index < this.text.length) { + this.ch = this.text.charAt(this.index); + if (this.is('"\'')) { + this.readString(this.ch); + } else if (this.isNumber(this.ch) || this.is('.') && this.isNumber(this.peek())) { + this.readNumber(); + } else if (this.isIdent(this.ch)) { + this.readIdent(); + } else if (this.is('(){}[].,;:?')) { + this.tokens.push({ + index: this.index, + text: this.ch + }); + this.index++; + } else if (this.isWhitespace(this.ch)) { + this.index++; + continue; + } else { + var ch2 = this.ch + this.peek(); + var ch3 = ch2 + this.peek(2); + var fn = OPERATORS[this.ch]; + var fn2 = OPERATORS[ch2]; + var fn3 = OPERATORS[ch3]; + if (fn3) { + this.tokens.push({index: this.index, text: ch3, fn: fn3}); + this.index += 3; + } else if (fn2) { + this.tokens.push({index: this.index, text: ch2, fn: fn2}); + this.index += 2; + } else if (fn) { + this.tokens.push({ + index: this.index, + text: this.ch, + fn: fn + }); + this.index += 1; + } else { + this.throwError('Unexpected next character ', this.index, this.index + 1); + } + } + this.lastCh = this.ch; + } + return this.tokens; + }, + + is: function(chars) { + return chars.indexOf(this.ch) !== -1; + }, + + was: function(chars) { + return chars.indexOf(this.lastCh) !== -1; + }, + + peek: function(i) { + var num = i || 1; + return (this.index + num < this.text.length) ? this.text.charAt(this.index + num) : false; + }, + + isNumber: function(ch) { + return ('0' <= ch && ch <= '9'); + }, + + isWhitespace: function(ch) { + // IE treats non-breaking space as \u00A0 + return (ch === ' ' || ch === '\r' || ch === '\t' || + ch === '\n' || ch === '\v' || ch === '\u00A0'); + }, + + isIdent: function(ch) { + return ('a' <= ch && ch <= 'z' || + 'A' <= ch && ch <= 'Z' || + '_' === ch || ch === '$'); + }, + + isExpOperator: function(ch) { + return (ch === '-' || ch === '+' || this.isNumber(ch)); + }, + + throwError: function(error, start, end) { + end = end || this.index; + var colStr = (isDefined(start) + ? 's ' + start + '-' + this.index + ' [' + this.text.substring(start, end) + ']' + : ' ' + end); + throw $parseMinErr('lexerr', 'Lexer Error: {0} at column{1} in expression [{2}].', + error, colStr, this.text); + }, + + readNumber: function() { + var number = ''; + var start = this.index; + while (this.index < this.text.length) { + var ch = lowercase(this.text.charAt(this.index)); + if (ch == '.' || this.isNumber(ch)) { + number += ch; + } else { + var peekCh = this.peek(); + if (ch == 'e' && this.isExpOperator(peekCh)) { + number += ch; + } else if (this.isExpOperator(ch) && + peekCh && this.isNumber(peekCh) && + number.charAt(number.length - 1) == 'e') { + number += ch; + } else if (this.isExpOperator(ch) && + (!peekCh || !this.isNumber(peekCh)) && + number.charAt(number.length - 1) == 'e') { + this.throwError('Invalid exponent'); + } else { + break; + } + } + this.index++; + } + number = 1 * number; + this.tokens.push({ + index: start, + text: number, + literal: true, + constant: true, + fn: function() { return number; } + }); + }, + + readIdent: function() { + var parser = this; + + var ident = ''; + var start = this.index; + + var lastDot, peekIndex, methodName, ch; + + while (this.index < this.text.length) { + ch = this.text.charAt(this.index); + if (ch === '.' || this.isIdent(ch) || this.isNumber(ch)) { + if (ch === '.') lastDot = this.index; + ident += ch; + } else { + break; + } + this.index++; + } + + //check if this is not a method invocation and if it is back out to last dot + if (lastDot) { + peekIndex = this.index; + while (peekIndex < this.text.length) { + ch = this.text.charAt(peekIndex); + if (ch === '(') { + methodName = ident.substr(lastDot - start + 1); + ident = ident.substr(0, lastDot - start); + this.index = peekIndex; + break; + } + if (this.isWhitespace(ch)) { + peekIndex++; + } else { + break; + } + } + } + + + var token = { + index: start, + text: ident + }; + + // OPERATORS is our own object so we don't need to use special hasOwnPropertyFn + if (OPERATORS.hasOwnProperty(ident)) { + token.fn = OPERATORS[ident]; + token.literal = true; + token.constant = true; + } else { + var getter = getterFn(ident, this.options, this.text); + token.fn = extend(function(self, locals) { + return (getter(self, locals)); + }, { + assign: function(self, value) { + return setter(self, ident, value, parser.text, parser.options); + } + }); + } + + this.tokens.push(token); + + if (methodName) { + this.tokens.push({ + index:lastDot, + text: '.' + }); + this.tokens.push({ + index: lastDot + 1, + text: methodName + }); + } + }, + + readString: function(quote) { + var start = this.index; + this.index++; + var string = ''; + var rawString = quote; + var escape = false; + while (this.index < this.text.length) { + var ch = this.text.charAt(this.index); + rawString += ch; + if (escape) { + if (ch === 'u') { + var hex = this.text.substring(this.index + 1, this.index + 5); + if (!hex.match(/[\da-f]{4}/i)) + this.throwError('Invalid unicode escape [\\u' + hex + ']'); + this.index += 4; + string += String.fromCharCode(parseInt(hex, 16)); + } else { + var rep = ESCAPE[ch]; + if (rep) { + string += rep; + } else { + string += ch; + } + } + escape = false; + } else if (ch === '\\') { + escape = true; + } else if (ch === quote) { + this.index++; + this.tokens.push({ + index: start, + text: rawString, + string: string, + literal: true, + constant: true, + fn: function() { return string; } + }); + return; + } else { + string += ch; + } + this.index++; + } + this.throwError('Unterminated quote', start); + } +}; + + +/** + * @constructor + */ +var Parser = function (lexer, $filter, options) { + this.lexer = lexer; + this.$filter = $filter; + this.options = options; +}; + +Parser.ZERO = extend(function () { + return 0; +}, { + constant: true +}); + +Parser.prototype = { + constructor: Parser, + + parse: function (text) { + this.text = text; + + this.tokens = this.lexer.lex(text); + + var value = this.statements(); + + if (this.tokens.length !== 0) { + this.throwError('is an unexpected token', this.tokens[0]); + } + + value.literal = !!value.literal; + value.constant = !!value.constant; + + return value; + }, + + primary: function () { + var primary; + if (this.expect('(')) { + primary = this.filterChain(); + this.consume(')'); + } else if (this.expect('[')) { + primary = this.arrayDeclaration(); + } else if (this.expect('{')) { + primary = this.object(); + } else { + var token = this.expect(); + primary = token.fn; + if (!primary) { + this.throwError('not a primary expression', token); + } + primary.literal = !!token.literal; + primary.constant = !!token.constant; + } + + var next, context; + while ((next = this.expect('(', '[', '.'))) { + if (next.text === '(') { + primary = this.functionCall(primary, context); + context = null; + } else if (next.text === '[') { + context = primary; + primary = this.objectIndex(primary); + } else if (next.text === '.') { + context = primary; + primary = this.fieldAccess(primary); + } else { + this.throwError('IMPOSSIBLE'); + } + } + return primary; + }, + + throwError: function(msg, token) { + throw $parseMinErr('syntax', + 'Syntax Error: Token \'{0}\' {1} at column {2} of the expression [{3}] starting at [{4}].', + token.text, msg, (token.index + 1), this.text, this.text.substring(token.index)); + }, + + peekToken: function() { + if (this.tokens.length === 0) + throw $parseMinErr('ueoe', 'Unexpected end of expression: {0}', this.text); + return this.tokens[0]; + }, + + peek: function(e1, e2, e3, e4) { + if (this.tokens.length > 0) { + var token = this.tokens[0]; + var t = token.text; + if (t === e1 || t === e2 || t === e3 || t === e4 || + (!e1 && !e2 && !e3 && !e4)) { + return token; + } + } + return false; + }, + + expect: function(e1, e2, e3, e4){ + var token = this.peek(e1, e2, e3, e4); + if (token) { + this.tokens.shift(); + return token; + } + return false; + }, + + consume: function(e1){ + if (!this.expect(e1)) { + this.throwError('is unexpected, expecting [' + e1 + ']', this.peek()); + } + }, + + unaryFn: function(fn, right) { + return extend(function(self, locals) { + return fn(self, locals, right); + }, { + constant:right.constant + }); + }, + + ternaryFn: function(left, middle, right){ + return extend(function(self, locals){ + return left(self, locals) ? middle(self, locals) : right(self, locals); + }, { + constant: left.constant && middle.constant && right.constant + }); + }, + + binaryFn: function(left, fn, right) { + return extend(function(self, locals) { + return fn(self, locals, left, right); + }, { + constant:left.constant && right.constant + }); + }, + + statements: function() { + var statements = []; + while (true) { + if (this.tokens.length > 0 && !this.peek('}', ')', ';', ']')) + statements.push(this.filterChain()); + if (!this.expect(';')) { + // optimize for the common case where there is only one statement. + // TODO(size): maybe we should not support multiple statements? + return (statements.length === 1) + ? statements[0] + : function(self, locals) { + var value; + for (var i = 0; i < statements.length; i++) { + var statement = statements[i]; + if (statement) { + value = statement(self, locals); + } + } + return value; + }; + } + } + }, + + filterChain: function() { + var left = this.expression(); + var token; + while (true) { + if ((token = this.expect('|'))) { + left = this.binaryFn(left, token.fn, this.filter()); + } else { + return left; + } + } + }, + + filter: function() { + var token = this.expect(); + var fn = this.$filter(token.text); + var argsFn = []; + while (true) { + if ((token = this.expect(':'))) { + argsFn.push(this.expression()); + } else { + var fnInvoke = function(self, locals, input) { + var args = [input]; + for (var i = 0; i < argsFn.length; i++) { + args.push(argsFn[i](self, locals)); + } + return fn.apply(self, args); + }; + return function() { + return fnInvoke; + }; + } + } + }, + + expression: function() { + return this.assignment(); + }, + + assignment: function() { + var left = this.ternary(); + var right; + var token; + if ((token = this.expect('='))) { + if (!left.assign) { + this.throwError('implies assignment but [' + + this.text.substring(0, token.index) + '] can not be assigned to', token); + } + right = this.ternary(); + return function(scope, locals) { + return left.assign(scope, right(scope, locals), locals); + }; + } + return left; + }, + + ternary: function() { + var left = this.logicalOR(); + var middle; + var token; + if ((token = this.expect('?'))) { + middle = this.ternary(); + if ((token = this.expect(':'))) { + return this.ternaryFn(left, middle, this.ternary()); + } else { + this.throwError('expected :', token); + } + } else { + return left; + } + }, + + logicalOR: function() { + var left = this.logicalAND(); + var token; + while (true) { + if ((token = this.expect('||'))) { + left = this.binaryFn(left, token.fn, this.logicalAND()); + } else { + return left; + } + } + }, + + logicalAND: function() { + var left = this.equality(); + var token; + if ((token = this.expect('&&'))) { + left = this.binaryFn(left, token.fn, this.logicalAND()); + } + return left; + }, + + equality: function() { + var left = this.relational(); + var token; + if ((token = this.expect('==','!=','===','!=='))) { + left = this.binaryFn(left, token.fn, this.equality()); + } + return left; + }, + + relational: function() { + var left = this.additive(); + var token; + if ((token = this.expect('<', '>', '<=', '>='))) { + left = this.binaryFn(left, token.fn, this.relational()); + } + return left; + }, + + additive: function() { + var left = this.multiplicative(); + var token; + while ((token = this.expect('+','-'))) { + left = this.binaryFn(left, token.fn, this.multiplicative()); + } + return left; + }, + + multiplicative: function() { + var left = this.unary(); + var token; + while ((token = this.expect('*','/','%'))) { + left = this.binaryFn(left, token.fn, this.unary()); + } + return left; + }, + + unary: function() { + var token; + if (this.expect('+')) { + return this.primary(); + } else if ((token = this.expect('-'))) { + return this.binaryFn(Parser.ZERO, token.fn, this.unary()); + } else if ((token = this.expect('!'))) { + return this.unaryFn(token.fn, this.unary()); + } else { + return this.primary(); + } + }, + + fieldAccess: function(object) { + var parser = this; + var field = this.expect().text; + var getter = getterFn(field, this.options, this.text); + + return extend(function(scope, locals, self) { + return getter(self || object(scope, locals)); + }, { + assign: function(scope, value, locals) { + return setter(object(scope, locals), field, value, parser.text, parser.options); + } + }); + }, + + objectIndex: function(obj) { + var parser = this; + + var indexFn = this.expression(); + this.consume(']'); + + return extend(function(self, locals) { + var o = obj(self, locals), + i = indexFn(self, locals), + v, p; + + if (!o) return undefined; + v = ensureSafeObject(o[i], parser.text); + if (v && v.then && parser.options.unwrapPromises) { + p = v; + if (!('$$v' in v)) { + p.$$v = undefined; + p.then(function(val) { p.$$v = val; }); + } + v = v.$$v; + } + return v; + }, { + assign: function(self, value, locals) { + var key = indexFn(self, locals); + // prevent overwriting of Function.constructor which would break ensureSafeObject check + var safe = ensureSafeObject(obj(self, locals), parser.text); + return safe[key] = value; + } + }); + }, + + functionCall: function(fn, contextGetter) { + var argsFn = []; + if (this.peekToken().text !== ')') { + do { + argsFn.push(this.expression()); + } while (this.expect(',')); + } + this.consume(')'); + + var parser = this; + + return function(scope, locals) { + var args = []; + var context = contextGetter ? contextGetter(scope, locals) : scope; + + for (var i = 0; i < argsFn.length; i++) { + args.push(argsFn[i](scope, locals)); + } + var fnPtr = fn(scope, locals, context) || noop; + + ensureSafeObject(context, parser.text); + ensureSafeObject(fnPtr, parser.text); + + // IE stupidity! (IE doesn't have apply for some native functions) + var v = fnPtr.apply + ? fnPtr.apply(context, args) + : fnPtr(args[0], args[1], args[2], args[3], args[4]); + + return ensureSafeObject(v, parser.text); + }; + }, + + // This is used with json array declaration + arrayDeclaration: function () { + var elementFns = []; + var allConstant = true; + if (this.peekToken().text !== ']') { + do { + if (this.peek(']')) { + // Support trailing commas per ES5.1. + break; + } + var elementFn = this.expression(); + elementFns.push(elementFn); + if (!elementFn.constant) { + allConstant = false; + } + } while (this.expect(',')); + } + this.consume(']'); + + return extend(function(self, locals) { + var array = []; + for (var i = 0; i < elementFns.length; i++) { + array.push(elementFns[i](self, locals)); + } + return array; + }, { + literal: true, + constant: allConstant + }); + }, + + object: function () { + var keyValues = []; + var allConstant = true; + if (this.peekToken().text !== '}') { + do { + if (this.peek('}')) { + // Support trailing commas per ES5.1. + break; + } + var token = this.expect(), + key = token.string || token.text; + this.consume(':'); + var value = this.expression(); + keyValues.push({key: key, value: value}); + if (!value.constant) { + allConstant = false; + } + } while (this.expect(',')); + } + this.consume('}'); + + return extend(function(self, locals) { + var object = {}; + for (var i = 0; i < keyValues.length; i++) { + var keyValue = keyValues[i]; + object[keyValue.key] = keyValue.value(self, locals); + } + return object; + }, { + literal: true, + constant: allConstant + }); + } +}; + + +////////////////////////////////////////////////// +// Parser helper functions +////////////////////////////////////////////////// + +function setter(obj, path, setValue, fullExp, options) { + //needed? + options = options || {}; + + var element = path.split('.'), key; + for (var i = 0; element.length > 1; i++) { + key = ensureSafeMemberName(element.shift(), fullExp); + var propertyObj = obj[key]; + if (!propertyObj) { + propertyObj = {}; + obj[key] = propertyObj; + } + obj = propertyObj; + if (obj.then && options.unwrapPromises) { + promiseWarning(fullExp); + if (!("$$v" in obj)) { + (function(promise) { + promise.then(function(val) { promise.$$v = val; }); } + )(obj); + } + if (obj.$$v === undefined) { + obj.$$v = {}; + } + obj = obj.$$v; + } + } + key = ensureSafeMemberName(element.shift(), fullExp); + obj[key] = setValue; + return setValue; +} + +var getterFnCache = {}; + +/** + * Implementation of the "Black Hole" variant from: + * - http://jsperf.com/angularjs-parse-getter/4 + * - http://jsperf.com/path-evaluation-simplified/7 + */ +function cspSafeGetterFn(key0, key1, key2, key3, key4, fullExp, options) { + ensureSafeMemberName(key0, fullExp); + ensureSafeMemberName(key1, fullExp); + ensureSafeMemberName(key2, fullExp); + ensureSafeMemberName(key3, fullExp); + ensureSafeMemberName(key4, fullExp); + + return !options.unwrapPromises + ? function cspSafeGetter(scope, locals) { + var pathVal = (locals && locals.hasOwnProperty(key0)) ? locals : scope; + + if (pathVal == null) return pathVal; + pathVal = pathVal[key0]; + + if (!key1) return pathVal; + if (pathVal == null) return undefined; + pathVal = pathVal[key1]; + + if (!key2) return pathVal; + if (pathVal == null) return undefined; + pathVal = pathVal[key2]; + + if (!key3) return pathVal; + if (pathVal == null) return undefined; + pathVal = pathVal[key3]; + + if (!key4) return pathVal; + if (pathVal == null) return undefined; + pathVal = pathVal[key4]; + + return pathVal; + } + : function cspSafePromiseEnabledGetter(scope, locals) { + var pathVal = (locals && locals.hasOwnProperty(key0)) ? locals : scope, + promise; + + if (pathVal == null) return pathVal; + + pathVal = pathVal[key0]; + if (pathVal && pathVal.then) { + promiseWarning(fullExp); + if (!("$$v" in pathVal)) { + promise = pathVal; + promise.$$v = undefined; + promise.then(function(val) { promise.$$v = val; }); + } + pathVal = pathVal.$$v; + } + + if (!key1) return pathVal; + if (pathVal == null) return undefined; + pathVal = pathVal[key1]; + if (pathVal && pathVal.then) { + promiseWarning(fullExp); + if (!("$$v" in pathVal)) { + promise = pathVal; + promise.$$v = undefined; + promise.then(function(val) { promise.$$v = val; }); + } + pathVal = pathVal.$$v; + } + + if (!key2) return pathVal; + if (pathVal == null) return undefined; + pathVal = pathVal[key2]; + if (pathVal && pathVal.then) { + promiseWarning(fullExp); + if (!("$$v" in pathVal)) { + promise = pathVal; + promise.$$v = undefined; + promise.then(function(val) { promise.$$v = val; }); + } + pathVal = pathVal.$$v; + } + + if (!key3) return pathVal; + if (pathVal == null) return undefined; + pathVal = pathVal[key3]; + if (pathVal && pathVal.then) { + promiseWarning(fullExp); + if (!("$$v" in pathVal)) { + promise = pathVal; + promise.$$v = undefined; + promise.then(function(val) { promise.$$v = val; }); + } + pathVal = pathVal.$$v; + } + + if (!key4) return pathVal; + if (pathVal == null) return undefined; + pathVal = pathVal[key4]; + if (pathVal && pathVal.then) { + promiseWarning(fullExp); + if (!("$$v" in pathVal)) { + promise = pathVal; + promise.$$v = undefined; + promise.then(function(val) { promise.$$v = val; }); + } + pathVal = pathVal.$$v; + } + return pathVal; + }; +} + +function simpleGetterFn1(key0, fullExp) { + ensureSafeMemberName(key0, fullExp); + + return function simpleGetterFn1(scope, locals) { + if (scope == null) return undefined; + return ((locals && locals.hasOwnProperty(key0)) ? locals : scope)[key0]; + }; +} + +function simpleGetterFn2(key0, key1, fullExp) { + ensureSafeMemberName(key0, fullExp); + ensureSafeMemberName(key1, fullExp); + + return function simpleGetterFn2(scope, locals) { + if (scope == null) return undefined; + scope = ((locals && locals.hasOwnProperty(key0)) ? locals : scope)[key0]; + return scope == null ? undefined : scope[key1]; + }; +} + +function getterFn(path, options, fullExp) { + // Check whether the cache has this getter already. + // We can use hasOwnProperty directly on the cache because we ensure, + // see below, that the cache never stores a path called 'hasOwnProperty' + if (getterFnCache.hasOwnProperty(path)) { + return getterFnCache[path]; + } + + var pathKeys = path.split('.'), + pathKeysLength = pathKeys.length, + fn; + + // When we have only 1 or 2 tokens, use optimized special case closures. + // http://jsperf.com/angularjs-parse-getter/6 + if (!options.unwrapPromises && pathKeysLength === 1) { + fn = simpleGetterFn1(pathKeys[0], fullExp); + } else if (!options.unwrapPromises && pathKeysLength === 2) { + fn = simpleGetterFn2(pathKeys[0], pathKeys[1], fullExp); + } else if (options.csp) { + if (pathKeysLength < 6) { + fn = cspSafeGetterFn(pathKeys[0], pathKeys[1], pathKeys[2], pathKeys[3], pathKeys[4], fullExp, + options); + } else { + fn = function(scope, locals) { + var i = 0, val; + do { + val = cspSafeGetterFn(pathKeys[i++], pathKeys[i++], pathKeys[i++], pathKeys[i++], + pathKeys[i++], fullExp, options)(scope, locals); + + locals = undefined; // clear after first iteration + scope = val; + } while (i < pathKeysLength); + return val; + }; + } + } else { + var code = 'var p;\n'; + forEach(pathKeys, function(key, index) { + ensureSafeMemberName(key, fullExp); + code += 'if(s == null) return undefined;\n' + + 's='+ (index + // we simply dereference 's' on any .dot notation + ? 's' + // but if we are first then we check locals first, and if so read it first + : '((k&&k.hasOwnProperty("' + key + '"))?k:s)') + '["' + key + '"]' + ';\n' + + (options.unwrapPromises + ? 'if (s && s.then) {\n' + + ' pw("' + fullExp.replace(/(["\r\n])/g, '\\$1') + '");\n' + + ' if (!("$$v" in s)) {\n' + + ' p=s;\n' + + ' p.$$v = undefined;\n' + + ' p.then(function(v) {p.$$v=v;});\n' + + '}\n' + + ' s=s.$$v\n' + + '}\n' + : ''); + }); + code += 'return s;'; + + /* jshint -W054 */ + var evaledFnGetter = new Function('s', 'k', 'pw', code); // s=scope, k=locals, pw=promiseWarning + /* jshint +W054 */ + evaledFnGetter.toString = valueFn(code); + fn = options.unwrapPromises ? function(scope, locals) { + return evaledFnGetter(scope, locals, promiseWarning); + } : evaledFnGetter; + } + + // Only cache the value if it's not going to mess up the cache object + // This is more performant that using Object.prototype.hasOwnProperty.call + if (path !== 'hasOwnProperty') { + getterFnCache[path] = fn; + } + return fn; +} + +/////////////////////////////////// + +/** + * @ngdoc service + * @name $parse + * @kind function + * + * @description + * + * Converts Angular {@link guide/expression expression} into a function. + * + * ```js + * var getter = $parse('user.name'); + * var setter = getter.assign; + * var context = {user:{name:'angular'}}; + * var locals = {user:{name:'local'}}; + * + * expect(getter(context)).toEqual('angular'); + * setter(context, 'newValue'); + * expect(context.user.name).toEqual('newValue'); + * expect(getter(context, locals)).toEqual('local'); + * ``` + * + * + * @param {string} expression String expression to compile. + * @returns {function(context, locals)} a function which represents the compiled expression: + * + * * `context` – `{object}` – an object against which any expressions embedded in the strings + * are evaluated against (typically a scope object). + * * `locals` – `{object=}` – local variables context object, useful for overriding values in + * `context`. + * + * The returned function also has the following properties: + * * `literal` – `{boolean}` – whether the expression's top-level node is a JavaScript + * literal. + * * `constant` – `{boolean}` – whether the expression is made entirely of JavaScript + * constant literals. + * * `assign` – `{?function(context, value)}` – if the expression is assignable, this will be + * set to a function to change its value on the given context. + * + */ + + +/** + * @ngdoc provider + * @name $parseProvider + * @kind function + * + * @description + * `$parseProvider` can be used for configuring the default behavior of the {@link ng.$parse $parse} + * service. + */ +function $ParseProvider() { + var cache = {}; + + var $parseOptions = { + csp: false, + unwrapPromises: false, + logPromiseWarnings: true + }; + + + /** + * @deprecated Promise unwrapping via $parse is deprecated and will be removed in the future. + * + * @ngdoc method + * @name $parseProvider#unwrapPromises + * @description + * + * **This feature is deprecated, see deprecation notes below for more info** + * + * If set to true (default is false), $parse will unwrap promises automatically when a promise is + * found at any part of the expression. In other words, if set to true, the expression will always + * result in a non-promise value. + * + * While the promise is unresolved, it's treated as undefined, but once resolved and fulfilled, + * the fulfillment value is used in place of the promise while evaluating the expression. + * + * **Deprecation notice** + * + * This is a feature that didn't prove to be wildly useful or popular, primarily because of the + * dichotomy between data access in templates (accessed as raw values) and controller code + * (accessed as promises). + * + * In most code we ended up resolving promises manually in controllers anyway and thus unifying + * the model access there. + * + * Other downsides of automatic promise unwrapping: + * + * - when building components it's often desirable to receive the raw promises + * - adds complexity and slows down expression evaluation + * - makes expression code pre-generation unattractive due to the amount of code that needs to be + * generated + * - makes IDE auto-completion and tool support hard + * + * **Warning Logs** + * + * If the unwrapping is enabled, Angular will log a warning about each expression that unwraps a + * promise (to reduce the noise, each expression is logged only once). To disable this logging use + * `$parseProvider.logPromiseWarnings(false)` api. + * + * + * @param {boolean=} value New value. + * @returns {boolean|self} Returns the current setting when used as getter and self if used as + * setter. + */ + this.unwrapPromises = function(value) { + if (isDefined(value)) { + $parseOptions.unwrapPromises = !!value; + return this; + } else { + return $parseOptions.unwrapPromises; + } + }; + + + /** + * @deprecated Promise unwrapping via $parse is deprecated and will be removed in the future. + * + * @ngdoc method + * @name $parseProvider#logPromiseWarnings + * @description + * + * Controls whether Angular should log a warning on any encounter of a promise in an expression. + * + * The default is set to `true`. + * + * This setting applies only if `$parseProvider.unwrapPromises` setting is set to true as well. + * + * @param {boolean=} value New value. + * @returns {boolean|self} Returns the current setting when used as getter and self if used as + * setter. + */ + this.logPromiseWarnings = function(value) { + if (isDefined(value)) { + $parseOptions.logPromiseWarnings = value; + return this; + } else { + return $parseOptions.logPromiseWarnings; + } + }; + + + this.$get = ['$filter', '$sniffer', '$log', function($filter, $sniffer, $log) { + $parseOptions.csp = $sniffer.csp; + + promiseWarning = function promiseWarningFn(fullExp) { + if (!$parseOptions.logPromiseWarnings || promiseWarningCache.hasOwnProperty(fullExp)) return; + promiseWarningCache[fullExp] = true; + $log.warn('[$parse] Promise found in the expression `' + fullExp + '`. ' + + 'Automatic unwrapping of promises in Angular expressions is deprecated.'); + }; + + return function(exp) { + var parsedExpression; + + switch (typeof exp) { + case 'string': + + if (cache.hasOwnProperty(exp)) { + return cache[exp]; + } + + var lexer = new Lexer($parseOptions); + var parser = new Parser(lexer, $filter, $parseOptions); + parsedExpression = parser.parse(exp); + + if (exp !== 'hasOwnProperty') { + // Only cache the value if it's not going to mess up the cache object + // This is more performant that using Object.prototype.hasOwnProperty.call + cache[exp] = parsedExpression; + } + + return parsedExpression; + + case 'function': + return exp; + + default: + return noop; + } + }; + }]; +} + +/** + * @ngdoc service + * @name $q + * @requires $rootScope + * + * @description + * A promise/deferred implementation inspired by [Kris Kowal's Q](https://github.com/kriskowal/q). + * + * [The CommonJS Promise proposal](http://wiki.commonjs.org/wiki/Promises) describes a promise as an + * interface for interacting with an object that represents the result of an action that is + * performed asynchronously, and may or may not be finished at any given point in time. + * + * From the perspective of dealing with error handling, deferred and promise APIs are to + * asynchronous programming what `try`, `catch` and `throw` keywords are to synchronous programming. + * + * ```js + * // for the purpose of this example let's assume that variables `$q`, `scope` and `okToGreet` + * // are available in the current lexical scope (they could have been injected or passed in). + * + * function asyncGreet(name) { + * var deferred = $q.defer(); + * + * setTimeout(function() { + * // since this fn executes async in a future turn of the event loop, we need to wrap + * // our code into an $apply call so that the model changes are properly observed. + * scope.$apply(function() { + * deferred.notify('About to greet ' + name + '.'); + * + * if (okToGreet(name)) { + * deferred.resolve('Hello, ' + name + '!'); + * } else { + * deferred.reject('Greeting ' + name + ' is not allowed.'); + * } + * }); + * }, 1000); + * + * return deferred.promise; + * } + * + * var promise = asyncGreet('Robin Hood'); + * promise.then(function(greeting) { + * alert('Success: ' + greeting); + * }, function(reason) { + * alert('Failed: ' + reason); + * }, function(update) { + * alert('Got notification: ' + update); + * }); + * ``` + * + * At first it might not be obvious why this extra complexity is worth the trouble. The payoff + * comes in the way of guarantees that promise and deferred APIs make, see + * https://github.com/kriskowal/uncommonjs/blob/master/promises/specification.md. + * + * Additionally the promise api allows for composition that is very hard to do with the + * traditional callback ([CPS](http://en.wikipedia.org/wiki/Continuation-passing_style)) approach. + * For more on this please see the [Q documentation](https://github.com/kriskowal/q) especially the + * section on serial or parallel joining of promises. + * + * + * # The Deferred API + * + * A new instance of deferred is constructed by calling `$q.defer()`. + * + * The purpose of the deferred object is to expose the associated Promise instance as well as APIs + * that can be used for signaling the successful or unsuccessful completion, as well as the status + * of the task. + * + * **Methods** + * + * - `resolve(value)` – resolves the derived promise with the `value`. If the value is a rejection + * constructed via `$q.reject`, the promise will be rejected instead. + * - `reject(reason)` – rejects the derived promise with the `reason`. This is equivalent to + * resolving it with a rejection constructed via `$q.reject`. + * - `notify(value)` - provides updates on the status of the promise's execution. This may be called + * multiple times before the promise is either resolved or rejected. + * + * **Properties** + * + * - promise – `{Promise}` – promise object associated with this deferred. + * + * + * # The Promise API + * + * A new promise instance is created when a deferred instance is created and can be retrieved by + * calling `deferred.promise`. + * + * The purpose of the promise object is to allow for interested parties to get access to the result + * of the deferred task when it completes. + * + * **Methods** + * + * - `then(successCallback, errorCallback, notifyCallback)` – regardless of when the promise was or + * will be resolved or rejected, `then` calls one of the success or error callbacks asynchronously + * as soon as the result is available. The callbacks are called with a single argument: the result + * or rejection reason. Additionally, the notify callback may be called zero or more times to + * provide a progress indication, before the promise is resolved or rejected. + * + * This method *returns a new promise* which is resolved or rejected via the return value of the + * `successCallback`, `errorCallback`. It also notifies via the return value of the + * `notifyCallback` method. The promise can not be resolved or rejected from the notifyCallback + * method. + * + * - `catch(errorCallback)` – shorthand for `promise.then(null, errorCallback)` + * + * - `finally(callback)` – allows you to observe either the fulfillment or rejection of a promise, + * but to do so without modifying the final value. This is useful to release resources or do some + * clean-up that needs to be done whether the promise was rejected or resolved. See the [full + * specification](https://github.com/kriskowal/q/wiki/API-Reference#promisefinallycallback) for + * more information. + * + * Because `finally` is a reserved word in JavaScript and reserved keywords are not supported as + * property names by ES3, you'll need to invoke the method like `promise['finally'](callback)` to + * make your code IE8 and Android 2.x compatible. + * + * # Chaining promises + * + * Because calling the `then` method of a promise returns a new derived promise, it is easily + * possible to create a chain of promises: + * + * ```js + * promiseB = promiseA.then(function(result) { + * return result + 1; + * }); + * + * // promiseB will be resolved immediately after promiseA is resolved and its value + * // will be the result of promiseA incremented by 1 + * ``` + * + * It is possible to create chains of any length and since a promise can be resolved with another + * promise (which will defer its resolution further), it is possible to pause/defer resolution of + * the promises at any point in the chain. This makes it possible to implement powerful APIs like + * $http's response interceptors. + * + * + * # Differences between Kris Kowal's Q and $q + * + * There are two main differences: + * + * - $q is integrated with the {@link ng.$rootScope.Scope} Scope model observation + * mechanism in angular, which means faster propagation of resolution or rejection into your + * models and avoiding unnecessary browser repaints, which would result in flickering UI. + * - Q has many more features than $q, but that comes at a cost of bytes. $q is tiny, but contains + * all the important functionality needed for common async tasks. + * + * # Testing + * + * ```js + * it('should simulate promise', inject(function($q, $rootScope) { + * var deferred = $q.defer(); + * var promise = deferred.promise; + * var resolvedValue; + * + * promise.then(function(value) { resolvedValue = value; }); + * expect(resolvedValue).toBeUndefined(); + * + * // Simulate resolving of promise + * deferred.resolve(123); + * // Note that the 'then' function does not get called synchronously. + * // This is because we want the promise API to always be async, whether or not + * // it got called synchronously or asynchronously. + * expect(resolvedValue).toBeUndefined(); + * + * // Propagate promise resolution to 'then' functions using $apply(). + * $rootScope.$apply(); + * expect(resolvedValue).toEqual(123); + * })); + * ``` + */ +function $QProvider() { + + this.$get = ['$rootScope', '$exceptionHandler', function($rootScope, $exceptionHandler) { + return qFactory(function(callback) { + $rootScope.$evalAsync(callback); + }, $exceptionHandler); + }]; +} + + +/** + * Constructs a promise manager. + * + * @param {function(Function)} nextTick Function for executing functions in the next turn. + * @param {function(...*)} exceptionHandler Function into which unexpected exceptions are passed for + * debugging purposes. + * @returns {object} Promise manager. + */ +function qFactory(nextTick, exceptionHandler) { + + /** + * @ngdoc method + * @name $q#defer + * @kind function + * + * @description + * Creates a `Deferred` object which represents a task which will finish in the future. + * + * @returns {Deferred} Returns a new instance of deferred. + */ + var defer = function() { + var pending = [], + value, deferred; + + deferred = { + + resolve: function(val) { + if (pending) { + var callbacks = pending; + pending = undefined; + value = ref(val); + + if (callbacks.length) { + nextTick(function() { + var callback; + for (var i = 0, ii = callbacks.length; i < ii; i++) { + callback = callbacks[i]; + value.then(callback[0], callback[1], callback[2]); + } + }); + } + } + }, + + + reject: function(reason) { + deferred.resolve(createInternalRejectedPromise(reason)); + }, + + + notify: function(progress) { + if (pending) { + var callbacks = pending; + + if (pending.length) { + nextTick(function() { + var callback; + for (var i = 0, ii = callbacks.length; i < ii; i++) { + callback = callbacks[i]; + callback[2](progress); + } + }); + } + } + }, + + + promise: { + then: function(callback, errback, progressback) { + var result = defer(); + + var wrappedCallback = function(value) { + try { + result.resolve((isFunction(callback) ? callback : defaultCallback)(value)); + } catch(e) { + result.reject(e); + exceptionHandler(e); + } + }; + + var wrappedErrback = function(reason) { + try { + result.resolve((isFunction(errback) ? errback : defaultErrback)(reason)); + } catch(e) { + result.reject(e); + exceptionHandler(e); + } + }; + + var wrappedProgressback = function(progress) { + try { + result.notify((isFunction(progressback) ? progressback : defaultCallback)(progress)); + } catch(e) { + exceptionHandler(e); + } + }; + + if (pending) { + pending.push([wrappedCallback, wrappedErrback, wrappedProgressback]); + } else { + value.then(wrappedCallback, wrappedErrback, wrappedProgressback); + } + + return result.promise; + }, + + "catch": function(callback) { + return this.then(null, callback); + }, + + "finally": function(callback) { + + function makePromise(value, resolved) { + var result = defer(); + if (resolved) { + result.resolve(value); + } else { + result.reject(value); + } + return result.promise; + } + + function handleCallback(value, isResolved) { + var callbackOutput = null; + try { + callbackOutput = (callback ||defaultCallback)(); + } catch(e) { + return makePromise(e, false); + } + if (callbackOutput && isFunction(callbackOutput.then)) { + return callbackOutput.then(function() { + return makePromise(value, isResolved); + }, function(error) { + return makePromise(error, false); + }); + } else { + return makePromise(value, isResolved); + } + } + + return this.then(function(value) { + return handleCallback(value, true); + }, function(error) { + return handleCallback(error, false); + }); + } + } + }; + + return deferred; + }; + + + var ref = function(value) { + if (value && isFunction(value.then)) return value; + return { + then: function(callback) { + var result = defer(); + nextTick(function() { + result.resolve(callback(value)); + }); + return result.promise; + } + }; + }; + + + /** + * @ngdoc method + * @name $q#reject + * @kind function + * + * @description + * Creates a promise that is resolved as rejected with the specified `reason`. This api should be + * used to forward rejection in a chain of promises. If you are dealing with the last promise in + * a promise chain, you don't need to worry about it. + * + * When comparing deferreds/promises to the familiar behavior of try/catch/throw, think of + * `reject` as the `throw` keyword in JavaScript. This also means that if you "catch" an error via + * a promise error callback and you want to forward the error to the promise derived from the + * current promise, you have to "rethrow" the error by returning a rejection constructed via + * `reject`. + * + * ```js + * promiseB = promiseA.then(function(result) { + * // success: do something and resolve promiseB + * // with the old or a new result + * return result; + * }, function(reason) { + * // error: handle the error if possible and + * // resolve promiseB with newPromiseOrValue, + * // otherwise forward the rejection to promiseB + * if (canHandle(reason)) { + * // handle the error and recover + * return newPromiseOrValue; + * } + * return $q.reject(reason); + * }); + * ``` + * + * @param {*} reason Constant, message, exception or an object representing the rejection reason. + * @returns {Promise} Returns a promise that was already resolved as rejected with the `reason`. + */ + var reject = function(reason) { + var result = defer(); + result.reject(reason); + return result.promise; + }; + + var createInternalRejectedPromise = function(reason) { + return { + then: function(callback, errback) { + var result = defer(); + nextTick(function() { + try { + result.resolve((isFunction(errback) ? errback : defaultErrback)(reason)); + } catch(e) { + result.reject(e); + exceptionHandler(e); + } + }); + return result.promise; + } + }; + }; + + + /** + * @ngdoc method + * @name $q#when + * @kind function + * + * @description + * Wraps an object that might be a value or a (3rd party) then-able promise into a $q promise. + * This is useful when you are dealing with an object that might or might not be a promise, or if + * the promise comes from a source that can't be trusted. + * + * @param {*} value Value or a promise + * @returns {Promise} Returns a promise of the passed value or promise + */ + var when = function(value, callback, errback, progressback) { + var result = defer(), + done; + + var wrappedCallback = function(value) { + try { + return (isFunction(callback) ? callback : defaultCallback)(value); + } catch (e) { + exceptionHandler(e); + return reject(e); + } + }; + + var wrappedErrback = function(reason) { + try { + return (isFunction(errback) ? errback : defaultErrback)(reason); + } catch (e) { + exceptionHandler(e); + return reject(e); + } + }; + + var wrappedProgressback = function(progress) { + try { + return (isFunction(progressback) ? progressback : defaultCallback)(progress); + } catch (e) { + exceptionHandler(e); + } + }; + + nextTick(function() { + ref(value).then(function(value) { + if (done) return; + done = true; + result.resolve(ref(value).then(wrappedCallback, wrappedErrback, wrappedProgressback)); + }, function(reason) { + if (done) return; + done = true; + result.resolve(wrappedErrback(reason)); + }, function(progress) { + if (done) return; + result.notify(wrappedProgressback(progress)); + }); + }); + + return result.promise; + }; + + + function defaultCallback(value) { + return value; + } + + + function defaultErrback(reason) { + return reject(reason); + } + + + /** + * @ngdoc method + * @name $q#all + * @kind function + * + * @description + * Combines multiple promises into a single promise that is resolved when all of the input + * promises are resolved. + * + * @param {Array.|Object.} promises An array or hash of promises. + * @returns {Promise} Returns a single promise that will be resolved with an array/hash of values, + * each value corresponding to the promise at the same index/key in the `promises` array/hash. + * If any of the promises is resolved with a rejection, this resulting promise will be rejected + * with the same rejection value. + */ + function all(promises) { + var deferred = defer(), + counter = 0, + results = isArray(promises) ? [] : {}; + + forEach(promises, function(promise, key) { + counter++; + ref(promise).then(function(value) { + if (results.hasOwnProperty(key)) return; + results[key] = value; + if (!(--counter)) deferred.resolve(results); + }, function(reason) { + if (results.hasOwnProperty(key)) return; + deferred.reject(reason); + }); + }); + + if (counter === 0) { + deferred.resolve(results); + } + + return deferred.promise; + } + + return { + defer: defer, + reject: reject, + when: when, + all: all + }; +} + +function $$RAFProvider(){ //rAF + this.$get = ['$window', '$timeout', function($window, $timeout) { + var requestAnimationFrame = $window.requestAnimationFrame || + $window.webkitRequestAnimationFrame || + $window.mozRequestAnimationFrame; + + var cancelAnimationFrame = $window.cancelAnimationFrame || + $window.webkitCancelAnimationFrame || + $window.mozCancelAnimationFrame || + $window.webkitCancelRequestAnimationFrame; + + var rafSupported = !!requestAnimationFrame; + var raf = rafSupported + ? function(fn) { + var id = requestAnimationFrame(fn); + return function() { + cancelAnimationFrame(id); + }; + } + : function(fn) { + var timer = $timeout(fn, 16.66, false); // 1000 / 60 = 16.666 + return function() { + $timeout.cancel(timer); + }; + }; + + raf.supported = rafSupported; + + return raf; + }]; +} + +/** + * DESIGN NOTES + * + * The design decisions behind the scope are heavily favored for speed and memory consumption. + * + * The typical use of scope is to watch the expressions, which most of the time return the same + * value as last time so we optimize the operation. + * + * Closures construction is expensive in terms of speed as well as memory: + * - No closures, instead use prototypical inheritance for API + * - Internal state needs to be stored on scope directly, which means that private state is + * exposed as $$____ properties + * + * Loop operations are optimized by using while(count--) { ... } + * - this means that in order to keep the same order of execution as addition we have to add + * items to the array at the beginning (unshift) instead of at the end (push) + * + * Child scopes are created and removed often + * - Using an array would be slow since inserts in middle are expensive so we use linked list + * + * There are few watches then a lot of observers. This is why you don't want the observer to be + * implemented in the same way as watch. Watch requires return of initialization function which + * are expensive to construct. + */ + + +/** + * @ngdoc provider + * @name $rootScopeProvider + * @description + * + * Provider for the $rootScope service. + */ + +/** + * @ngdoc method + * @name $rootScopeProvider#digestTtl + * @description + * + * Sets the number of `$digest` iterations the scope should attempt to execute before giving up and + * assuming that the model is unstable. + * + * The current default is 10 iterations. + * + * In complex applications it's possible that the dependencies between `$watch`s will result in + * several digest iterations. However if an application needs more than the default 10 digest + * iterations for its model to stabilize then you should investigate what is causing the model to + * continuously change during the digest. + * + * Increasing the TTL could have performance implications, so you should not change it without + * proper justification. + * + * @param {number} limit The number of digest iterations. + */ + + +/** + * @ngdoc service + * @name $rootScope + * @description + * + * Every application has a single root {@link ng.$rootScope.Scope scope}. + * All other scopes are descendant scopes of the root scope. Scopes provide separation + * between the model and the view, via a mechanism for watching the model for changes. + * They also provide an event emission/broadcast and subscription facility. See the + * {@link guide/scope developer guide on scopes}. + */ +function $RootScopeProvider(){ + var TTL = 10; + var $rootScopeMinErr = minErr('$rootScope'); + var lastDirtyWatch = null; + + this.digestTtl = function(value) { + if (arguments.length) { + TTL = value; + } + return TTL; + }; + + this.$get = ['$injector', '$exceptionHandler', '$parse', '$browser', + function( $injector, $exceptionHandler, $parse, $browser) { + + /** + * @ngdoc type + * @name $rootScope.Scope + * + * @description + * A root scope can be retrieved using the {@link ng.$rootScope $rootScope} key from the + * {@link auto.$injector $injector}. Child scopes are created using the + * {@link ng.$rootScope.Scope#$new $new()} method. (Most scopes are created automatically when + * compiled HTML template is executed.) + * + * Here is a simple scope snippet to show how you can interact with the scope. + * ```html + * + * ``` + * + * # Inheritance + * A scope can inherit from a parent scope, as in this example: + * ```js + var parent = $rootScope; + var child = parent.$new(); + + parent.salutation = "Hello"; + child.name = "World"; + expect(child.salutation).toEqual('Hello'); + + child.salutation = "Welcome"; + expect(child.salutation).toEqual('Welcome'); + expect(parent.salutation).toEqual('Hello'); + * ``` + * + * + * @param {Object.=} providers Map of service factory which need to be + * provided for the current scope. Defaults to {@link ng}. + * @param {Object.=} instanceCache Provides pre-instantiated services which should + * append/override services provided by `providers`. This is handy + * when unit-testing and having the need to override a default + * service. + * @returns {Object} Newly created scope. + * + */ + function Scope() { + this.$id = nextUid(); + this.$$phase = this.$parent = this.$$watchers = + this.$$nextSibling = this.$$prevSibling = + this.$$childHead = this.$$childTail = null; + this['this'] = this.$root = this; + this.$$destroyed = false; + this.$$asyncQueue = []; + this.$$postDigestQueue = []; + this.$$listeners = {}; + this.$$listenerCount = {}; + this.$$isolateBindings = {}; + } + + /** + * @ngdoc property + * @name $rootScope.Scope#$id + * @returns {number} Unique scope ID (monotonically increasing alphanumeric sequence) useful for + * debugging. + */ + + + Scope.prototype = { + constructor: Scope, + /** + * @ngdoc method + * @name $rootScope.Scope#$new + * @kind function + * + * @description + * Creates a new child {@link ng.$rootScope.Scope scope}. + * + * The parent scope will propagate the {@link ng.$rootScope.Scope#$digest $digest()} and + * {@link ng.$rootScope.Scope#$digest $digest()} events. The scope can be removed from the + * scope hierarchy using {@link ng.$rootScope.Scope#$destroy $destroy()}. + * + * {@link ng.$rootScope.Scope#$destroy $destroy()} must be called on a scope when it is + * desired for the scope and its child scopes to be permanently detached from the parent and + * thus stop participating in model change detection and listener notification by invoking. + * + * @param {boolean} isolate If true, then the scope does not prototypically inherit from the + * parent scope. The scope is isolated, as it can not see parent scope properties. + * When creating widgets, it is useful for the widget to not accidentally read parent + * state. + * + * @returns {Object} The newly created child scope. + * + */ + $new: function(isolate) { + var ChildScope, + child; + + if (isolate) { + child = new Scope(); + child.$root = this.$root; + // ensure that there is just one async queue per $rootScope and its children + child.$$asyncQueue = this.$$asyncQueue; + child.$$postDigestQueue = this.$$postDigestQueue; + } else { + // Only create a child scope class if somebody asks for one, + // but cache it to allow the VM to optimize lookups. + if (!this.$$childScopeClass) { + this.$$childScopeClass = function() { + this.$$watchers = this.$$nextSibling = + this.$$childHead = this.$$childTail = null; + this.$$listeners = {}; + this.$$listenerCount = {}; + this.$id = nextUid(); + this.$$childScopeClass = null; + }; + this.$$childScopeClass.prototype = this; + } + child = new this.$$childScopeClass(); + } + child['this'] = child; + child.$parent = this; + child.$$prevSibling = this.$$childTail; + if (this.$$childHead) { + this.$$childTail.$$nextSibling = child; + this.$$childTail = child; + } else { + this.$$childHead = this.$$childTail = child; + } + return child; + }, + + /** + * @ngdoc method + * @name $rootScope.Scope#$watch + * @kind function + * + * @description + * Registers a `listener` callback to be executed whenever the `watchExpression` changes. + * + * - The `watchExpression` is called on every call to {@link ng.$rootScope.Scope#$digest + * $digest()} and should return the value that will be watched. (Since + * {@link ng.$rootScope.Scope#$digest $digest()} reruns when it detects changes the + * `watchExpression` can execute multiple times per + * {@link ng.$rootScope.Scope#$digest $digest()} and should be idempotent.) + * - The `listener` is called only when the value from the current `watchExpression` and the + * previous call to `watchExpression` are not equal (with the exception of the initial run, + * see below). Inequality is determined according to reference inequality, + * [strict comparison](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Comparison_Operators) + * via the `!==` Javascript operator, unless `objectEquality == true` + * (see next point) + * - When `objectEquality == true`, inequality of the `watchExpression` is determined + * according to the {@link angular.equals} function. To save the value of the object for + * later comparison, the {@link angular.copy} function is used. This therefore means that + * watching complex objects will have adverse memory and performance implications. + * - The watch `listener` may change the model, which may trigger other `listener`s to fire. + * This is achieved by rerunning the watchers until no changes are detected. The rerun + * iteration limit is 10 to prevent an infinite loop deadlock. + * + * + * If you want to be notified whenever {@link ng.$rootScope.Scope#$digest $digest} is called, + * you can register a `watchExpression` function with no `listener`. (Since `watchExpression` + * can execute multiple times per {@link ng.$rootScope.Scope#$digest $digest} cycle when a + * change is detected, be prepared for multiple calls to your listener.) + * + * After a watcher is registered with the scope, the `listener` fn is called asynchronously + * (via {@link ng.$rootScope.Scope#$evalAsync $evalAsync}) to initialize the + * watcher. In rare cases, this is undesirable because the listener is called when the result + * of `watchExpression` didn't change. To detect this scenario within the `listener` fn, you + * can compare the `newVal` and `oldVal`. If these two values are identical (`===`) then the + * listener was called due to initialization. + * + * The example below contains an illustration of using a function as your $watch listener + * + * + * # Example + * ```js + // let's assume that scope was dependency injected as the $rootScope + var scope = $rootScope; + scope.name = 'misko'; + scope.counter = 0; + + expect(scope.counter).toEqual(0); + scope.$watch('name', function(newValue, oldValue) { + scope.counter = scope.counter + 1; + }); + expect(scope.counter).toEqual(0); + + scope.$digest(); + // the listener is always called during the first $digest loop after it was registered + expect(scope.counter).toEqual(1); + + scope.$digest(); + // but now it will not be called unless the value changes + expect(scope.counter).toEqual(1); + + scope.name = 'adam'; + scope.$digest(); + expect(scope.counter).toEqual(2); + + + + // Using a listener function + var food; + scope.foodCounter = 0; + expect(scope.foodCounter).toEqual(0); + scope.$watch( + // This is the listener function + function() { return food; }, + // This is the change handler + function(newValue, oldValue) { + if ( newValue !== oldValue ) { + // Only increment the counter if the value changed + scope.foodCounter = scope.foodCounter + 1; + } + } + ); + // No digest has been run so the counter will be zero + expect(scope.foodCounter).toEqual(0); + + // Run the digest but since food has not changed count will still be zero + scope.$digest(); + expect(scope.foodCounter).toEqual(0); + + // Update food and run digest. Now the counter will increment + food = 'cheeseburger'; + scope.$digest(); + expect(scope.foodCounter).toEqual(1); + + * ``` + * + * + * + * @param {(function()|string)} watchExpression Expression that is evaluated on each + * {@link ng.$rootScope.Scope#$digest $digest} cycle. A change in the return value triggers + * a call to the `listener`. + * + * - `string`: Evaluated as {@link guide/expression expression} + * - `function(scope)`: called with current `scope` as a parameter. + * @param {(function()|string)=} listener Callback called whenever the return value of + * the `watchExpression` changes. + * + * - `string`: Evaluated as {@link guide/expression expression} + * - `function(newValue, oldValue, scope)`: called with current and previous values as + * parameters. + * + * @param {boolean=} objectEquality Compare for object equality using {@link angular.equals} instead of + * comparing for reference equality. + * @returns {function()} Returns a deregistration function for this listener. + */ + $watch: function(watchExp, listener, objectEquality) { + var scope = this, + get = compileToFn(watchExp, 'watch'), + array = scope.$$watchers, + watcher = { + fn: listener, + last: initWatchVal, + get: get, + exp: watchExp, + eq: !!objectEquality + }; + + lastDirtyWatch = null; + + // in the case user pass string, we need to compile it, do we really need this ? + if (!isFunction(listener)) { + var listenFn = compileToFn(listener || noop, 'listener'); + watcher.fn = function(newVal, oldVal, scope) {listenFn(scope);}; + } + + if (typeof watchExp == 'string' && get.constant) { + var originalFn = watcher.fn; + watcher.fn = function(newVal, oldVal, scope) { + originalFn.call(this, newVal, oldVal, scope); + arrayRemove(array, watcher); + }; + } + + if (!array) { + array = scope.$$watchers = []; + } + // we use unshift since we use a while loop in $digest for speed. + // the while loop reads in reverse order. + array.unshift(watcher); + + return function deregisterWatch() { + arrayRemove(array, watcher); + lastDirtyWatch = null; + }; + }, + + + /** + * @ngdoc method + * @name $rootScope.Scope#$watchCollection + * @kind function + * + * @description + * Shallow watches the properties of an object and fires whenever any of the properties change + * (for arrays, this implies watching the array items; for object maps, this implies watching + * the properties). If a change is detected, the `listener` callback is fired. + * + * - The `obj` collection is observed via standard $watch operation and is examined on every + * call to $digest() to see if any items have been added, removed, or moved. + * - The `listener` is called whenever anything within the `obj` has changed. Examples include + * adding, removing, and moving items belonging to an object or array. + * + * + * # Example + * ```js + $scope.names = ['igor', 'matias', 'misko', 'james']; + $scope.dataCount = 4; + + $scope.$watchCollection('names', function(newNames, oldNames) { + $scope.dataCount = newNames.length; + }); + + expect($scope.dataCount).toEqual(4); + $scope.$digest(); + + //still at 4 ... no changes + expect($scope.dataCount).toEqual(4); + + $scope.names.pop(); + $scope.$digest(); + + //now there's been a change + expect($scope.dataCount).toEqual(3); + * ``` + * + * + * @param {string|function(scope)} obj Evaluated as {@link guide/expression expression}. The + * expression value should evaluate to an object or an array which is observed on each + * {@link ng.$rootScope.Scope#$digest $digest} cycle. Any shallow change within the + * collection will trigger a call to the `listener`. + * + * @param {function(newCollection, oldCollection, scope)} listener a callback function called + * when a change is detected. + * - The `newCollection` object is the newly modified data obtained from the `obj` expression + * - The `oldCollection` object is a copy of the former collection data. + * Due to performance considerations, the`oldCollection` value is computed only if the + * `listener` function declares two or more arguments. + * - The `scope` argument refers to the current scope. + * + * @returns {function()} Returns a de-registration function for this listener. When the + * de-registration function is executed, the internal watch operation is terminated. + */ + $watchCollection: function(obj, listener) { + var self = this; + // the current value, updated on each dirty-check run + var newValue; + // a shallow copy of the newValue from the last dirty-check run, + // updated to match newValue during dirty-check run + var oldValue; + // a shallow copy of the newValue from when the last change happened + var veryOldValue; + // only track veryOldValue if the listener is asking for it + var trackVeryOldValue = (listener.length > 1); + var changeDetected = 0; + var objGetter = $parse(obj); + var internalArray = []; + var internalObject = {}; + var initRun = true; + var oldLength = 0; + + function $watchCollectionWatch() { + newValue = objGetter(self); + var newLength, key; + + if (!isObject(newValue)) { // if primitive + if (oldValue !== newValue) { + oldValue = newValue; + changeDetected++; + } + } else if (isArrayLike(newValue)) { + if (oldValue !== internalArray) { + // we are transitioning from something which was not an array into array. + oldValue = internalArray; + oldLength = oldValue.length = 0; + changeDetected++; + } + + newLength = newValue.length; + + if (oldLength !== newLength) { + // if lengths do not match we need to trigger change notification + changeDetected++; + oldValue.length = oldLength = newLength; + } + // copy the items to oldValue and look for changes. + for (var i = 0; i < newLength; i++) { + var bothNaN = (oldValue[i] !== oldValue[i]) && + (newValue[i] !== newValue[i]); + if (!bothNaN && (oldValue[i] !== newValue[i])) { + changeDetected++; + oldValue[i] = newValue[i]; + } + } + } else { + if (oldValue !== internalObject) { + // we are transitioning from something which was not an object into object. + oldValue = internalObject = {}; + oldLength = 0; + changeDetected++; + } + // copy the items to oldValue and look for changes. + newLength = 0; + for (key in newValue) { + if (newValue.hasOwnProperty(key)) { + newLength++; + if (oldValue.hasOwnProperty(key)) { + if (oldValue[key] !== newValue[key]) { + changeDetected++; + oldValue[key] = newValue[key]; + } + } else { + oldLength++; + oldValue[key] = newValue[key]; + changeDetected++; + } + } + } + if (oldLength > newLength) { + // we used to have more keys, need to find them and destroy them. + changeDetected++; + for(key in oldValue) { + if (oldValue.hasOwnProperty(key) && !newValue.hasOwnProperty(key)) { + oldLength--; + delete oldValue[key]; + } + } + } + } + return changeDetected; + } + + function $watchCollectionAction() { + if (initRun) { + initRun = false; + listener(newValue, newValue, self); + } else { + listener(newValue, veryOldValue, self); + } + + // make a copy for the next time a collection is changed + if (trackVeryOldValue) { + if (!isObject(newValue)) { + //primitive + veryOldValue = newValue; + } else if (isArrayLike(newValue)) { + veryOldValue = new Array(newValue.length); + for (var i = 0; i < newValue.length; i++) { + veryOldValue[i] = newValue[i]; + } + } else { // if object + veryOldValue = {}; + for (var key in newValue) { + if (hasOwnProperty.call(newValue, key)) { + veryOldValue[key] = newValue[key]; + } + } + } + } + } + + return this.$watch($watchCollectionWatch, $watchCollectionAction); + }, + + /** + * @ngdoc method + * @name $rootScope.Scope#$digest + * @kind function + * + * @description + * Processes all of the {@link ng.$rootScope.Scope#$watch watchers} of the current scope and + * its children. Because a {@link ng.$rootScope.Scope#$watch watcher}'s listener can change + * the model, the `$digest()` keeps calling the {@link ng.$rootScope.Scope#$watch watchers} + * until no more listeners are firing. This means that it is possible to get into an infinite + * loop. This function will throw `'Maximum iteration limit exceeded.'` if the number of + * iterations exceeds 10. + * + * Usually, you don't call `$digest()` directly in + * {@link ng.directive:ngController controllers} or in + * {@link ng.$compileProvider#directive directives}. + * Instead, you should call {@link ng.$rootScope.Scope#$apply $apply()} (typically from within + * a {@link ng.$compileProvider#directive directives}), which will force a `$digest()`. + * + * If you want to be notified whenever `$digest()` is called, + * you can register a `watchExpression` function with + * {@link ng.$rootScope.Scope#$watch $watch()} with no `listener`. + * + * In unit tests, you may need to call `$digest()` to simulate the scope life cycle. + * + * # Example + * ```js + var scope = ...; + scope.name = 'misko'; + scope.counter = 0; + + expect(scope.counter).toEqual(0); + scope.$watch('name', function(newValue, oldValue) { + scope.counter = scope.counter + 1; + }); + expect(scope.counter).toEqual(0); + + scope.$digest(); + // the listener is always called during the first $digest loop after it was registered + expect(scope.counter).toEqual(1); + + scope.$digest(); + // but now it will not be called unless the value changes + expect(scope.counter).toEqual(1); + + scope.name = 'adam'; + scope.$digest(); + expect(scope.counter).toEqual(2); + * ``` + * + */ + $digest: function() { + var watch, value, last, + watchers, + asyncQueue = this.$$asyncQueue, + postDigestQueue = this.$$postDigestQueue, + length, + dirty, ttl = TTL, + next, current, target = this, + watchLog = [], + logIdx, logMsg, asyncTask; + + beginPhase('$digest'); + + lastDirtyWatch = null; + + do { // "while dirty" loop + dirty = false; + current = target; + + while(asyncQueue.length) { + try { + asyncTask = asyncQueue.shift(); + asyncTask.scope.$eval(asyncTask.expression); + } catch (e) { + clearPhase(); + $exceptionHandler(e); + } + lastDirtyWatch = null; + } + + traverseScopesLoop: + do { // "traverse the scopes" loop + if ((watchers = current.$$watchers)) { + // process our watches + length = watchers.length; + while (length--) { + try { + watch = watchers[length]; + // Most common watches are on primitives, in which case we can short + // circuit it with === operator, only when === fails do we use .equals + if (watch) { + if ((value = watch.get(current)) !== (last = watch.last) && + !(watch.eq + ? equals(value, last) + : (typeof value == 'number' && typeof last == 'number' + && isNaN(value) && isNaN(last)))) { + dirty = true; + lastDirtyWatch = watch; + watch.last = watch.eq ? copy(value, null) : value; + watch.fn(value, ((last === initWatchVal) ? value : last), current); + if (ttl < 5) { + logIdx = 4 - ttl; + if (!watchLog[logIdx]) watchLog[logIdx] = []; + logMsg = (isFunction(watch.exp)) + ? 'fn: ' + (watch.exp.name || watch.exp.toString()) + : watch.exp; + logMsg += '; newVal: ' + toJson(value) + '; oldVal: ' + toJson(last); + watchLog[logIdx].push(logMsg); + } + } else if (watch === lastDirtyWatch) { + // If the most recently dirty watcher is now clean, short circuit since the remaining watchers + // have already been tested. + dirty = false; + break traverseScopesLoop; + } + } + } catch (e) { + clearPhase(); + $exceptionHandler(e); + } + } + } + + // Insanity Warning: scope depth-first traversal + // yes, this code is a bit crazy, but it works and we have tests to prove it! + // this piece should be kept in sync with the traversal in $broadcast + if (!(next = (current.$$childHead || + (current !== target && current.$$nextSibling)))) { + while(current !== target && !(next = current.$$nextSibling)) { + current = current.$parent; + } + } + } while ((current = next)); + + // `break traverseScopesLoop;` takes us to here + + if((dirty || asyncQueue.length) && !(ttl--)) { + clearPhase(); + throw $rootScopeMinErr('infdig', + '{0} $digest() iterations reached. Aborting!\n' + + 'Watchers fired in the last 5 iterations: {1}', + TTL, toJson(watchLog)); + } + + } while (dirty || asyncQueue.length); + + clearPhase(); + + while(postDigestQueue.length) { + try { + postDigestQueue.shift()(); + } catch (e) { + $exceptionHandler(e); + } + } + }, + + + /** + * @ngdoc event + * @name $rootScope.Scope#$destroy + * @eventType broadcast on scope being destroyed + * + * @description + * Broadcasted when a scope and its children are being destroyed. + * + * Note that, in AngularJS, there is also a `$destroy` jQuery event, which can be used to + * clean up DOM bindings before an element is removed from the DOM. + */ + + /** + * @ngdoc method + * @name $rootScope.Scope#$destroy + * @kind function + * + * @description + * Removes the current scope (and all of its children) from the parent scope. Removal implies + * that calls to {@link ng.$rootScope.Scope#$digest $digest()} will no longer + * propagate to the current scope and its children. Removal also implies that the current + * scope is eligible for garbage collection. + * + * The `$destroy()` is usually used by directives such as + * {@link ng.directive:ngRepeat ngRepeat} for managing the + * unrolling of the loop. + * + * Just before a scope is destroyed, a `$destroy` event is broadcasted on this scope. + * Application code can register a `$destroy` event handler that will give it a chance to + * perform any necessary cleanup. + * + * Note that, in AngularJS, there is also a `$destroy` jQuery event, which can be used to + * clean up DOM bindings before an element is removed from the DOM. + */ + $destroy: function() { + // we can't destroy the root scope or a scope that has been already destroyed + if (this.$$destroyed) return; + var parent = this.$parent; + + this.$broadcast('$destroy'); + this.$$destroyed = true; + if (this === $rootScope) return; + + forEach(this.$$listenerCount, bind(null, decrementListenerCount, this)); + + // sever all the references to parent scopes (after this cleanup, the current scope should + // not be retained by any of our references and should be eligible for garbage collection) + if (parent.$$childHead == this) parent.$$childHead = this.$$nextSibling; + if (parent.$$childTail == this) parent.$$childTail = this.$$prevSibling; + if (this.$$prevSibling) this.$$prevSibling.$$nextSibling = this.$$nextSibling; + if (this.$$nextSibling) this.$$nextSibling.$$prevSibling = this.$$prevSibling; + + + // All of the code below is bogus code that works around V8's memory leak via optimized code + // and inline caches. + // + // see: + // - https://code.google.com/p/v8/issues/detail?id=2073#c26 + // - https://github.com/angular/angular.js/issues/6794#issuecomment-38648909 + // - https://github.com/angular/angular.js/issues/1313#issuecomment-10378451 + + this.$parent = this.$$nextSibling = this.$$prevSibling = this.$$childHead = + this.$$childTail = this.$root = null; + + // don't reset these to null in case some async task tries to register a listener/watch/task + this.$$listeners = {}; + this.$$watchers = this.$$asyncQueue = this.$$postDigestQueue = []; + + // prevent NPEs since these methods have references to properties we nulled out + this.$destroy = this.$digest = this.$apply = noop; + this.$on = this.$watch = function() { return noop; }; + }, + + /** + * @ngdoc method + * @name $rootScope.Scope#$eval + * @kind function + * + * @description + * Executes the `expression` on the current scope and returns the result. Any exceptions in + * the expression are propagated (uncaught). This is useful when evaluating Angular + * expressions. + * + * # Example + * ```js + var scope = ng.$rootScope.Scope(); + scope.a = 1; + scope.b = 2; + + expect(scope.$eval('a+b')).toEqual(3); + expect(scope.$eval(function(scope){ return scope.a + scope.b; })).toEqual(3); + * ``` + * + * @param {(string|function())=} expression An angular expression to be executed. + * + * - `string`: execute using the rules as defined in {@link guide/expression expression}. + * - `function(scope)`: execute the function with the current `scope` parameter. + * + * @param {(object)=} locals Local variables object, useful for overriding values in scope. + * @returns {*} The result of evaluating the expression. + */ + $eval: function(expr, locals) { + return $parse(expr)(this, locals); + }, + + /** + * @ngdoc method + * @name $rootScope.Scope#$evalAsync + * @kind function + * + * @description + * Executes the expression on the current scope at a later point in time. + * + * The `$evalAsync` makes no guarantees as to when the `expression` will be executed, only + * that: + * + * - it will execute after the function that scheduled the evaluation (preferably before DOM + * rendering). + * - at least one {@link ng.$rootScope.Scope#$digest $digest cycle} will be performed after + * `expression` execution. + * + * Any exceptions from the execution of the expression are forwarded to the + * {@link ng.$exceptionHandler $exceptionHandler} service. + * + * __Note:__ if this function is called outside of a `$digest` cycle, a new `$digest` cycle + * will be scheduled. However, it is encouraged to always call code that changes the model + * from within an `$apply` call. That includes code evaluated via `$evalAsync`. + * + * @param {(string|function())=} expression An angular expression to be executed. + * + * - `string`: execute using the rules as defined in {@link guide/expression expression}. + * - `function(scope)`: execute the function with the current `scope` parameter. + * + */ + $evalAsync: function(expr) { + // if we are outside of an $digest loop and this is the first time we are scheduling async + // task also schedule async auto-flush + if (!$rootScope.$$phase && !$rootScope.$$asyncQueue.length) { + $browser.defer(function() { + if ($rootScope.$$asyncQueue.length) { + $rootScope.$digest(); + } + }); + } + + this.$$asyncQueue.push({scope: this, expression: expr}); + }, + + $$postDigest : function(fn) { + this.$$postDigestQueue.push(fn); + }, + + /** + * @ngdoc method + * @name $rootScope.Scope#$apply + * @kind function + * + * @description + * `$apply()` is used to execute an expression in angular from outside of the angular + * framework. (For example from browser DOM events, setTimeout, XHR or third party libraries). + * Because we are calling into the angular framework we need to perform proper scope life + * cycle of {@link ng.$exceptionHandler exception handling}, + * {@link ng.$rootScope.Scope#$digest executing watches}. + * + * ## Life cycle + * + * # Pseudo-Code of `$apply()` + * ```js + function $apply(expr) { + try { + return $eval(expr); + } catch (e) { + $exceptionHandler(e); + } finally { + $root.$digest(); + } + } + * ``` + * + * + * Scope's `$apply()` method transitions through the following stages: + * + * 1. The {@link guide/expression expression} is executed using the + * {@link ng.$rootScope.Scope#$eval $eval()} method. + * 2. Any exceptions from the execution of the expression are forwarded to the + * {@link ng.$exceptionHandler $exceptionHandler} service. + * 3. The {@link ng.$rootScope.Scope#$watch watch} listeners are fired immediately after the + * expression was executed using the {@link ng.$rootScope.Scope#$digest $digest()} method. + * + * + * @param {(string|function())=} exp An angular expression to be executed. + * + * - `string`: execute using the rules as defined in {@link guide/expression expression}. + * - `function(scope)`: execute the function with current `scope` parameter. + * + * @returns {*} The result of evaluating the expression. + */ + $apply: function(expr) { + try { + beginPhase('$apply'); + return this.$eval(expr); + } catch (e) { + $exceptionHandler(e); + } finally { + clearPhase(); + try { + $rootScope.$digest(); + } catch (e) { + $exceptionHandler(e); + throw e; + } + } + }, + + /** + * @ngdoc method + * @name $rootScope.Scope#$on + * @kind function + * + * @description + * Listens on events of a given type. See {@link ng.$rootScope.Scope#$emit $emit} for + * discussion of event life cycle. + * + * The event listener function format is: `function(event, args...)`. The `event` object + * passed into the listener has the following attributes: + * + * - `targetScope` - `{Scope}`: the scope on which the event was `$emit`-ed or + * `$broadcast`-ed. + * - `currentScope` - `{Scope}`: the current scope which is handling the event. + * - `name` - `{string}`: name of the event. + * - `stopPropagation` - `{function=}`: calling `stopPropagation` function will cancel + * further event propagation (available only for events that were `$emit`-ed). + * - `preventDefault` - `{function}`: calling `preventDefault` sets `defaultPrevented` flag + * to true. + * - `defaultPrevented` - `{boolean}`: true if `preventDefault` was called. + * + * @param {string} name Event name to listen on. + * @param {function(event, ...args)} listener Function to call when the event is emitted. + * @returns {function()} Returns a deregistration function for this listener. + */ + $on: function(name, listener) { + var namedListeners = this.$$listeners[name]; + if (!namedListeners) { + this.$$listeners[name] = namedListeners = []; + } + namedListeners.push(listener); + + var current = this; + do { + if (!current.$$listenerCount[name]) { + current.$$listenerCount[name] = 0; + } + current.$$listenerCount[name]++; + } while ((current = current.$parent)); + + var self = this; + return function() { + namedListeners[indexOf(namedListeners, listener)] = null; + decrementListenerCount(self, 1, name); + }; + }, + + + /** + * @ngdoc method + * @name $rootScope.Scope#$emit + * @kind function + * + * @description + * Dispatches an event `name` upwards through the scope hierarchy notifying the + * registered {@link ng.$rootScope.Scope#$on} listeners. + * + * The event life cycle starts at the scope on which `$emit` was called. All + * {@link ng.$rootScope.Scope#$on listeners} listening for `name` event on this scope get + * notified. Afterwards, the event traverses upwards toward the root scope and calls all + * registered listeners along the way. The event will stop propagating if one of the listeners + * cancels it. + * + * Any exception emitted from the {@link ng.$rootScope.Scope#$on listeners} will be passed + * onto the {@link ng.$exceptionHandler $exceptionHandler} service. + * + * @param {string} name Event name to emit. + * @param {...*} args Optional one or more arguments which will be passed onto the event listeners. + * @return {Object} Event object (see {@link ng.$rootScope.Scope#$on}). + */ + $emit: function(name, args) { + var empty = [], + namedListeners, + scope = this, + stopPropagation = false, + event = { + name: name, + targetScope: scope, + stopPropagation: function() {stopPropagation = true;}, + preventDefault: function() { + event.defaultPrevented = true; + }, + defaultPrevented: false + }, + listenerArgs = concat([event], arguments, 1), + i, length; + + do { + namedListeners = scope.$$listeners[name] || empty; + event.currentScope = scope; + for (i=0, length=namedListeners.length; i= 8 ) { + normalizedVal = urlResolve(uri).href; + if (normalizedVal !== '' && !normalizedVal.match(regex)) { + return 'unsafe:'+normalizedVal; + } + } + return uri; + }; + }; +} + +var $sceMinErr = minErr('$sce'); + +var SCE_CONTEXTS = { + HTML: 'html', + CSS: 'css', + URL: 'url', + // RESOURCE_URL is a subtype of URL used in contexts where a privileged resource is sourced from a + // url. (e.g. ng-include, script src, templateUrl) + RESOURCE_URL: 'resourceUrl', + JS: 'js' +}; + +// Helper functions follow. + +// Copied from: +// http://docs.closure-library.googlecode.com/git/closure_goog_string_string.js.source.html#line962 +// Prereq: s is a string. +function escapeForRegexp(s) { + return s.replace(/([-()\[\]{}+?*.$\^|,:# -1) { + throw $sceMinErr('iwcard', + 'Illegal sequence *** in string matcher. String: {0}', matcher); + } + matcher = escapeForRegexp(matcher). + replace('\\*\\*', '.*'). + replace('\\*', '[^:/.?&;]*'); + return new RegExp('^' + matcher + '$'); + } else if (isRegExp(matcher)) { + // The only other type of matcher allowed is a Regexp. + // Match entire URL / disallow partial matches. + // Flags are reset (i.e. no global, ignoreCase or multiline) + return new RegExp('^' + matcher.source + '$'); + } else { + throw $sceMinErr('imatcher', + 'Matchers may only be "self", string patterns or RegExp objects'); + } +} + + +function adjustMatchers(matchers) { + var adjustedMatchers = []; + if (isDefined(matchers)) { + forEach(matchers, function(matcher) { + adjustedMatchers.push(adjustMatcher(matcher)); + }); + } + return adjustedMatchers; +} + + +/** + * @ngdoc service + * @name $sceDelegate + * @kind function + * + * @description + * + * `$sceDelegate` is a service that is used by the `$sce` service to provide {@link ng.$sce Strict + * Contextual Escaping (SCE)} services to AngularJS. + * + * Typically, you would configure or override the {@link ng.$sceDelegate $sceDelegate} instead of + * the `$sce` service to customize the way Strict Contextual Escaping works in AngularJS. This is + * because, while the `$sce` provides numerous shorthand methods, etc., you really only need to + * override 3 core functions (`trustAs`, `getTrusted` and `valueOf`) to replace the way things + * work because `$sce` delegates to `$sceDelegate` for these operations. + * + * Refer {@link ng.$sceDelegateProvider $sceDelegateProvider} to configure this service. + * + * The default instance of `$sceDelegate` should work out of the box with little pain. While you + * can override it completely to change the behavior of `$sce`, the common case would + * involve configuring the {@link ng.$sceDelegateProvider $sceDelegateProvider} instead by setting + * your own whitelists and blacklists for trusting URLs used for loading AngularJS resources such as + * templates. Refer {@link ng.$sceDelegateProvider#resourceUrlWhitelist + * $sceDelegateProvider.resourceUrlWhitelist} and {@link + * ng.$sceDelegateProvider#resourceUrlBlacklist $sceDelegateProvider.resourceUrlBlacklist} + */ + +/** + * @ngdoc provider + * @name $sceDelegateProvider + * @description + * + * The `$sceDelegateProvider` provider allows developers to configure the {@link ng.$sceDelegate + * $sceDelegate} service. This allows one to get/set the whitelists and blacklists used to ensure + * that the URLs used for sourcing Angular templates are safe. Refer {@link + * ng.$sceDelegateProvider#resourceUrlWhitelist $sceDelegateProvider.resourceUrlWhitelist} and + * {@link ng.$sceDelegateProvider#resourceUrlBlacklist $sceDelegateProvider.resourceUrlBlacklist} + * + * For the general details about this service in Angular, read the main page for {@link ng.$sce + * Strict Contextual Escaping (SCE)}. + * + * **Example**: Consider the following case. + * + * - your app is hosted at url `http://myapp.example.com/` + * - but some of your templates are hosted on other domains you control such as + * `http://srv01.assets.example.com/`,  `http://srv02.assets.example.com/`, etc. + * - and you have an open redirect at `http://myapp.example.com/clickThru?...`. + * + * Here is what a secure configuration for this scenario might look like: + * + *
+ *    angular.module('myApp', []).config(function($sceDelegateProvider) {
+ *      $sceDelegateProvider.resourceUrlWhitelist([
+ *        // Allow same origin resource loads.
+ *        'self',
+ *        // Allow loading from our assets domain.  Notice the difference between * and **.
+ *        'http://srv*.assets.example.com/**']);
+ *
+ *      // The blacklist overrides the whitelist so the open redirect here is blocked.
+ *      $sceDelegateProvider.resourceUrlBlacklist([
+ *        'http://myapp.example.com/clickThru**']);
+ *      });
+ * 
+ */ + +function $SceDelegateProvider() { + this.SCE_CONTEXTS = SCE_CONTEXTS; + + // Resource URLs can also be trusted by policy. + var resourceUrlWhitelist = ['self'], + resourceUrlBlacklist = []; + + /** + * @ngdoc method + * @name $sceDelegateProvider#resourceUrlWhitelist + * @kind function + * + * @param {Array=} whitelist When provided, replaces the resourceUrlWhitelist with the value + * provided. This must be an array or null. A snapshot of this array is used so further + * changes to the array are ignored. + * + * Follow {@link ng.$sce#resourceUrlPatternItem this link} for a description of the items + * allowed in this array. + * + * Note: **an empty whitelist array will block all URLs**! + * + * @return {Array} the currently set whitelist array. + * + * The **default value** when no whitelist has been explicitly set is `['self']` allowing only + * same origin resource requests. + * + * @description + * Sets/Gets the whitelist of trusted resource URLs. + */ + this.resourceUrlWhitelist = function (value) { + if (arguments.length) { + resourceUrlWhitelist = adjustMatchers(value); + } + return resourceUrlWhitelist; + }; + + /** + * @ngdoc method + * @name $sceDelegateProvider#resourceUrlBlacklist + * @kind function + * + * @param {Array=} blacklist When provided, replaces the resourceUrlBlacklist with the value + * provided. This must be an array or null. A snapshot of this array is used so further + * changes to the array are ignored. + * + * Follow {@link ng.$sce#resourceUrlPatternItem this link} for a description of the items + * allowed in this array. + * + * The typical usage for the blacklist is to **block + * [open redirects](http://cwe.mitre.org/data/definitions/601.html)** served by your domain as + * these would otherwise be trusted but actually return content from the redirected domain. + * + * Finally, **the blacklist overrides the whitelist** and has the final say. + * + * @return {Array} the currently set blacklist array. + * + * The **default value** when no whitelist has been explicitly set is the empty array (i.e. there + * is no blacklist.) + * + * @description + * Sets/Gets the blacklist of trusted resource URLs. + */ + + this.resourceUrlBlacklist = function (value) { + if (arguments.length) { + resourceUrlBlacklist = adjustMatchers(value); + } + return resourceUrlBlacklist; + }; + + this.$get = ['$injector', function($injector) { + + var htmlSanitizer = function htmlSanitizer(html) { + throw $sceMinErr('unsafe', 'Attempting to use an unsafe value in a safe context.'); + }; + + if ($injector.has('$sanitize')) { + htmlSanitizer = $injector.get('$sanitize'); + } + + + function matchUrl(matcher, parsedUrl) { + if (matcher === 'self') { + return urlIsSameOrigin(parsedUrl); + } else { + // definitely a regex. See adjustMatchers() + return !!matcher.exec(parsedUrl.href); + } + } + + function isResourceUrlAllowedByPolicy(url) { + var parsedUrl = urlResolve(url.toString()); + var i, n, allowed = false; + // Ensure that at least one item from the whitelist allows this url. + for (i = 0, n = resourceUrlWhitelist.length; i < n; i++) { + if (matchUrl(resourceUrlWhitelist[i], parsedUrl)) { + allowed = true; + break; + } + } + if (allowed) { + // Ensure that no item from the blacklist blocked this url. + for (i = 0, n = resourceUrlBlacklist.length; i < n; i++) { + if (matchUrl(resourceUrlBlacklist[i], parsedUrl)) { + allowed = false; + break; + } + } + } + return allowed; + } + + function generateHolderType(Base) { + var holderType = function TrustedValueHolderType(trustedValue) { + this.$$unwrapTrustedValue = function() { + return trustedValue; + }; + }; + if (Base) { + holderType.prototype = new Base(); + } + holderType.prototype.valueOf = function sceValueOf() { + return this.$$unwrapTrustedValue(); + }; + holderType.prototype.toString = function sceToString() { + return this.$$unwrapTrustedValue().toString(); + }; + return holderType; + } + + var trustedValueHolderBase = generateHolderType(), + byType = {}; + + byType[SCE_CONTEXTS.HTML] = generateHolderType(trustedValueHolderBase); + byType[SCE_CONTEXTS.CSS] = generateHolderType(trustedValueHolderBase); + byType[SCE_CONTEXTS.URL] = generateHolderType(trustedValueHolderBase); + byType[SCE_CONTEXTS.JS] = generateHolderType(trustedValueHolderBase); + byType[SCE_CONTEXTS.RESOURCE_URL] = generateHolderType(byType[SCE_CONTEXTS.URL]); + + /** + * @ngdoc method + * @name $sceDelegate#trustAs + * + * @description + * Returns an object that is trusted by angular for use in specified strict + * contextual escaping contexts (such as ng-bind-html, ng-include, any src + * attribute interpolation, any dom event binding attribute interpolation + * such as for onclick, etc.) that uses the provided value. + * See {@link ng.$sce $sce} for enabling strict contextual escaping. + * + * @param {string} type The kind of context in which this value is safe for use. e.g. url, + * resourceUrl, html, js and css. + * @param {*} value The value that that should be considered trusted/safe. + * @returns {*} A value that can be used to stand in for the provided `value` in places + * where Angular expects a $sce.trustAs() return value. + */ + function trustAs(type, trustedValue) { + var Constructor = (byType.hasOwnProperty(type) ? byType[type] : null); + if (!Constructor) { + throw $sceMinErr('icontext', + 'Attempted to trust a value in invalid context. Context: {0}; Value: {1}', + type, trustedValue); + } + if (trustedValue === null || trustedValue === undefined || trustedValue === '') { + return trustedValue; + } + // All the current contexts in SCE_CONTEXTS happen to be strings. In order to avoid trusting + // mutable objects, we ensure here that the value passed in is actually a string. + if (typeof trustedValue !== 'string') { + throw $sceMinErr('itype', + 'Attempted to trust a non-string value in a content requiring a string: Context: {0}', + type); + } + return new Constructor(trustedValue); + } + + /** + * @ngdoc method + * @name $sceDelegate#valueOf + * + * @description + * If the passed parameter had been returned by a prior call to {@link ng.$sceDelegate#trustAs + * `$sceDelegate.trustAs`}, returns the value that had been passed to {@link + * ng.$sceDelegate#trustAs `$sceDelegate.trustAs`}. + * + * If the passed parameter is not a value that had been returned by {@link + * ng.$sceDelegate#trustAs `$sceDelegate.trustAs`}, returns it as-is. + * + * @param {*} value The result of a prior {@link ng.$sceDelegate#trustAs `$sceDelegate.trustAs`} + * call or anything else. + * @returns {*} The `value` that was originally provided to {@link ng.$sceDelegate#trustAs + * `$sceDelegate.trustAs`} if `value` is the result of such a call. Otherwise, returns + * `value` unchanged. + */ + function valueOf(maybeTrusted) { + if (maybeTrusted instanceof trustedValueHolderBase) { + return maybeTrusted.$$unwrapTrustedValue(); + } else { + return maybeTrusted; + } + } + + /** + * @ngdoc method + * @name $sceDelegate#getTrusted + * + * @description + * Takes the result of a {@link ng.$sceDelegate#trustAs `$sceDelegate.trustAs`} call and + * returns the originally supplied value if the queried context type is a supertype of the + * created type. If this condition isn't satisfied, throws an exception. + * + * @param {string} type The kind of context in which this value is to be used. + * @param {*} maybeTrusted The result of a prior {@link ng.$sceDelegate#trustAs + * `$sceDelegate.trustAs`} call. + * @returns {*} The value the was originally provided to {@link ng.$sceDelegate#trustAs + * `$sceDelegate.trustAs`} if valid in this context. Otherwise, throws an exception. + */ + function getTrusted(type, maybeTrusted) { + if (maybeTrusted === null || maybeTrusted === undefined || maybeTrusted === '') { + return maybeTrusted; + } + var constructor = (byType.hasOwnProperty(type) ? byType[type] : null); + if (constructor && maybeTrusted instanceof constructor) { + return maybeTrusted.$$unwrapTrustedValue(); + } + // If we get here, then we may only take one of two actions. + // 1. sanitize the value for the requested type, or + // 2. throw an exception. + if (type === SCE_CONTEXTS.RESOURCE_URL) { + if (isResourceUrlAllowedByPolicy(maybeTrusted)) { + return maybeTrusted; + } else { + throw $sceMinErr('insecurl', + 'Blocked loading resource from url not allowed by $sceDelegate policy. URL: {0}', + maybeTrusted.toString()); + } + } else if (type === SCE_CONTEXTS.HTML) { + return htmlSanitizer(maybeTrusted); + } + throw $sceMinErr('unsafe', 'Attempting to use an unsafe value in a safe context.'); + } + + return { trustAs: trustAs, + getTrusted: getTrusted, + valueOf: valueOf }; + }]; +} + + +/** + * @ngdoc provider + * @name $sceProvider + * @description + * + * The $sceProvider provider allows developers to configure the {@link ng.$sce $sce} service. + * - enable/disable Strict Contextual Escaping (SCE) in a module + * - override the default implementation with a custom delegate + * + * Read more about {@link ng.$sce Strict Contextual Escaping (SCE)}. + */ + +/* jshint maxlen: false*/ + +/** + * @ngdoc service + * @name $sce + * @kind function + * + * @description + * + * `$sce` is a service that provides Strict Contextual Escaping services to AngularJS. + * + * # Strict Contextual Escaping + * + * Strict Contextual Escaping (SCE) is a mode in which AngularJS requires bindings in certain + * contexts to result in a value that is marked as safe to use for that context. One example of + * such a context is binding arbitrary html controlled by the user via `ng-bind-html`. We refer + * to these contexts as privileged or SCE contexts. + * + * As of version 1.2, Angular ships with SCE enabled by default. + * + * Note: When enabled (the default), IE8 in quirks mode is not supported. In this mode, IE8 allows + * one to execute arbitrary javascript by the use of the expression() syntax. Refer + * to learn more about them. + * You can ensure your document is in standards mode and not quirks mode by adding `` + * to the top of your HTML document. + * + * SCE assists in writing code in way that (a) is secure by default and (b) makes auditing for + * security vulnerabilities such as XSS, clickjacking, etc. a lot easier. + * + * Here's an example of a binding in a privileged context: + * + *
+ *     
+ *     
+ *
+ * + * Notice that `ng-bind-html` is bound to `userHtml` controlled by the user. With SCE + * disabled, this application allows the user to render arbitrary HTML into the DIV. + * In a more realistic example, one may be rendering user comments, blog articles, etc. via + * bindings. (HTML is just one example of a context where rendering user controlled input creates + * security vulnerabilities.) + * + * For the case of HTML, you might use a library, either on the client side, or on the server side, + * to sanitize unsafe HTML before binding to the value and rendering it in the document. + * + * How would you ensure that every place that used these types of bindings was bound to a value that + * was sanitized by your library (or returned as safe for rendering by your server?) How can you + * ensure that you didn't accidentally delete the line that sanitized the value, or renamed some + * properties/fields and forgot to update the binding to the sanitized value? + * + * To be secure by default, you want to ensure that any such bindings are disallowed unless you can + * determine that something explicitly says it's safe to use a value for binding in that + * context. You can then audit your code (a simple grep would do) to ensure that this is only done + * for those values that you can easily tell are safe - because they were received from your server, + * sanitized by your library, etc. You can organize your codebase to help with this - perhaps + * allowing only the files in a specific directory to do this. Ensuring that the internal API + * exposed by that code doesn't markup arbitrary values as safe then becomes a more manageable task. + * + * In the case of AngularJS' SCE service, one uses {@link ng.$sce#trustAs $sce.trustAs} + * (and shorthand methods such as {@link ng.$sce#trustAsHtml $sce.trustAsHtml}, etc.) to + * obtain values that will be accepted by SCE / privileged contexts. + * + * + * ## How does it work? + * + * In privileged contexts, directives and code will bind to the result of {@link ng.$sce#getTrusted + * $sce.getTrusted(context, value)} rather than to the value directly. Directives use {@link + * ng.$sce#parse $sce.parseAs} rather than `$parse` to watch attribute bindings, which performs the + * {@link ng.$sce#getTrusted $sce.getTrusted} behind the scenes on non-constant literals. + * + * As an example, {@link ng.directive:ngBindHtml ngBindHtml} uses {@link + * ng.$sce#parseAsHtml $sce.parseAsHtml(binding expression)}. Here's the actual code (slightly + * simplified): + * + *
+ *   var ngBindHtmlDirective = ['$sce', function($sce) {
+ *     return function(scope, element, attr) {
+ *       scope.$watch($sce.parseAsHtml(attr.ngBindHtml), function(value) {
+ *         element.html(value || '');
+ *       });
+ *     };
+ *   }];
+ * 
+ * + * ## Impact on loading templates + * + * This applies both to the {@link ng.directive:ngInclude `ng-include`} directive as well as + * `templateUrl`'s specified by {@link guide/directive directives}. + * + * By default, Angular only loads templates from the same domain and protocol as the application + * document. This is done by calling {@link ng.$sce#getTrustedResourceUrl + * $sce.getTrustedResourceUrl} on the template URL. To load templates from other domains and/or + * protocols, you may either either {@link ng.$sceDelegateProvider#resourceUrlWhitelist whitelist + * them} or {@link ng.$sce#trustAsResourceUrl wrap it} into a trusted value. + * + * *Please note*: + * The browser's + * [Same Origin Policy](https://code.google.com/p/browsersec/wiki/Part2#Same-origin_policy_for_XMLHttpRequest) + * and [Cross-Origin Resource Sharing (CORS)](http://www.w3.org/TR/cors/) + * policy apply in addition to this and may further restrict whether the template is successfully + * loaded. This means that without the right CORS policy, loading templates from a different domain + * won't work on all browsers. Also, loading templates from `file://` URL does not work on some + * browsers. + * + * ## This feels like too much overhead for the developer? + * + * It's important to remember that SCE only applies to interpolation expressions. + * + * If your expressions are constant literals, they're automatically trusted and you don't need to + * call `$sce.trustAs` on them (remember to include the `ngSanitize` module) (e.g. + * `
`) just works. + * + * Additionally, `a[href]` and `img[src]` automatically sanitize their URLs and do not pass them + * through {@link ng.$sce#getTrusted $sce.getTrusted}. SCE doesn't play a role here. + * + * The included {@link ng.$sceDelegate $sceDelegate} comes with sane defaults to allow you to load + * templates in `ng-include` from your application's domain without having to even know about SCE. + * It blocks loading templates from other domains or loading templates over http from an https + * served document. You can change these by setting your own custom {@link + * ng.$sceDelegateProvider#resourceUrlWhitelist whitelists} and {@link + * ng.$sceDelegateProvider#resourceUrlBlacklist blacklists} for matching such URLs. + * + * This significantly reduces the overhead. It is far easier to pay the small overhead and have an + * application that's secure and can be audited to verify that with much more ease than bolting + * security onto an application later. + * + * + * ## What trusted context types are supported? + * + * | Context | Notes | + * |---------------------|----------------| + * | `$sce.HTML` | For HTML that's safe to source into the application. The {@link ng.directive:ngBindHtml ngBindHtml} directive uses this context for bindings. If an unsafe value is encountered and the {@link ngSanitize $sanitize} module is present this will sanitize the value instead of throwing an error. | + * | `$sce.CSS` | For CSS that's safe to source into the application. Currently unused. Feel free to use it in your own directives. | + * | `$sce.URL` | For URLs that are safe to follow as links. Currently unused (`
Note that `$sce.RESOURCE_URL` makes a stronger statement about the URL than `$sce.URL` does and therefore contexts requiring values trusted for `$sce.RESOURCE_URL` can be used anywhere that values trusted for `$sce.URL` are required. | + * | `$sce.JS` | For JavaScript that is safe to execute in your application's context. Currently unused. Feel free to use it in your own directives. | + * + * ## Format of items in {@link ng.$sceDelegateProvider#resourceUrlWhitelist resourceUrlWhitelist}/{@link ng.$sceDelegateProvider#resourceUrlBlacklist Blacklist}
+ * + * Each element in these arrays must be one of the following: + * + * - **'self'** + * - The special **string**, `'self'`, can be used to match against all URLs of the **same + * domain** as the application document using the **same protocol**. + * - **String** (except the special value `'self'`) + * - The string is matched against the full *normalized / absolute URL* of the resource + * being tested (substring matches are not good enough.) + * - There are exactly **two wildcard sequences** - `*` and `**`. All other characters + * match themselves. + * - `*`: matches zero or more occurrences of any character other than one of the following 6 + * characters: '`:`', '`/`', '`.`', '`?`', '`&`' and ';'. It's a useful wildcard for use + * in a whitelist. + * - `**`: matches zero or more occurrences of *any* character. As such, it's not + * not appropriate to use in for a scheme, domain, etc. as it would match too much. (e.g. + * http://**.example.com/ would match http://evil.com/?ignore=.example.com/ and that might + * not have been the intention.) Its usage at the very end of the path is ok. (e.g. + * http://foo.example.com/templates/**). + * - **RegExp** (*see caveat below*) + * - *Caveat*: While regular expressions are powerful and offer great flexibility, their syntax + * (and all the inevitable escaping) makes them *harder to maintain*. It's easy to + * accidentally introduce a bug when one updates a complex expression (imho, all regexes should + * have good test coverage.). For instance, the use of `.` in the regex is correct only in a + * small number of cases. A `.` character in the regex used when matching the scheme or a + * subdomain could be matched against a `:` or literal `.` that was likely not intended. It + * is highly recommended to use the string patterns and only fall back to regular expressions + * if they as a last resort. + * - The regular expression must be an instance of RegExp (i.e. not a string.) It is + * matched against the **entire** *normalized / absolute URL* of the resource being tested + * (even when the RegExp did not have the `^` and `$` codes.) In addition, any flags + * present on the RegExp (such as multiline, global, ignoreCase) are ignored. + * - If you are generating your JavaScript from some other templating engine (not + * recommended, e.g. in issue [#4006](https://github.com/angular/angular.js/issues/4006)), + * remember to escape your regular expression (and be aware that you might need more than + * one level of escaping depending on your templating engine and the way you interpolated + * the value.) Do make use of your platform's escaping mechanism as it might be good + * enough before coding your own. e.g. Ruby has + * [Regexp.escape(str)](http://www.ruby-doc.org/core-2.0.0/Regexp.html#method-c-escape) + * and Python has [re.escape](http://docs.python.org/library/re.html#re.escape). + * Javascript lacks a similar built in function for escaping. Take a look at Google + * Closure library's [goog.string.regExpEscape(s)]( + * http://docs.closure-library.googlecode.com/git/closure_goog_string_string.js.source.html#line962). + * + * Refer {@link ng.$sceDelegateProvider $sceDelegateProvider} for an example. + * + * ## Show me an example using SCE. + * + * @example + + +
+

+ User comments
+ By default, HTML that isn't explicitly trusted (e.g. Alice's comment) is sanitized when + $sanitize is available. If $sanitize isn't available, this results in an error instead of an + exploit. +
+
+ {{userComment.name}}: + +
+
+
+
+
+ + + var mySceApp = angular.module('mySceApp', ['ngSanitize']); + + mySceApp.controller("myAppController", function myAppController($http, $templateCache, $sce) { + var self = this; + $http.get("test_data.json", {cache: $templateCache}).success(function(userComments) { + self.userComments = userComments; + }); + self.explicitlyTrustedHtml = $sce.trustAsHtml( + 'Hover over this text.'); + }); + + + +[ + { "name": "Alice", + "htmlComment": + "Is anyone reading this?" + }, + { "name": "Bob", + "htmlComment": "Yes! Am I the only other one?" + } +] + + + + describe('SCE doc demo', function() { + it('should sanitize untrusted values', function() { + expect(element(by.css('.htmlComment')).getInnerHtml()) + .toBe('Is anyone reading this?'); + }); + + it('should NOT sanitize explicitly trusted values', function() { + expect(element(by.id('explicitlyTrustedHtml')).getInnerHtml()).toBe( + 'Hover over this text.'); + }); + }); + +
+ * + * + * + * ## Can I disable SCE completely? + * + * Yes, you can. However, this is strongly discouraged. SCE gives you a lot of security benefits + * for little coding overhead. It will be much harder to take an SCE disabled application and + * either secure it on your own or enable SCE at a later stage. It might make sense to disable SCE + * for cases where you have a lot of existing code that was written before SCE was introduced and + * you're migrating them a module at a time. + * + * That said, here's how you can completely disable SCE: + * + *
+ *   angular.module('myAppWithSceDisabledmyApp', []).config(function($sceProvider) {
+ *     // Completely disable SCE.  For demonstration purposes only!
+ *     // Do not use in new projects.
+ *     $sceProvider.enabled(false);
+ *   });
+ * 
+ * + */ +/* jshint maxlen: 100 */ + +function $SceProvider() { + var enabled = true; + + /** + * @ngdoc method + * @name $sceProvider#enabled + * @kind function + * + * @param {boolean=} value If provided, then enables/disables SCE. + * @return {boolean} true if SCE is enabled, false otherwise. + * + * @description + * Enables/disables SCE and returns the current value. + */ + this.enabled = function (value) { + if (arguments.length) { + enabled = !!value; + } + return enabled; + }; + + + /* Design notes on the default implementation for SCE. + * + * The API contract for the SCE delegate + * ------------------------------------- + * The SCE delegate object must provide the following 3 methods: + * + * - trustAs(contextEnum, value) + * This method is used to tell the SCE service that the provided value is OK to use in the + * contexts specified by contextEnum. It must return an object that will be accepted by + * getTrusted() for a compatible contextEnum and return this value. + * + * - valueOf(value) + * For values that were not produced by trustAs(), return them as is. For values that were + * produced by trustAs(), return the corresponding input value to trustAs. Basically, if + * trustAs is wrapping the given values into some type, this operation unwraps it when given + * such a value. + * + * - getTrusted(contextEnum, value) + * This function should return the a value that is safe to use in the context specified by + * contextEnum or throw and exception otherwise. + * + * NOTE: This contract deliberately does NOT state that values returned by trustAs() must be + * opaque or wrapped in some holder object. That happens to be an implementation detail. For + * instance, an implementation could maintain a registry of all trusted objects by context. In + * such a case, trustAs() would return the same object that was passed in. getTrusted() would + * return the same object passed in if it was found in the registry under a compatible context or + * throw an exception otherwise. An implementation might only wrap values some of the time based + * on some criteria. getTrusted() might return a value and not throw an exception for special + * constants or objects even if not wrapped. All such implementations fulfill this contract. + * + * + * A note on the inheritance model for SCE contexts + * ------------------------------------------------ + * I've used inheritance and made RESOURCE_URL wrapped types a subtype of URL wrapped types. This + * is purely an implementation details. + * + * The contract is simply this: + * + * getTrusted($sce.RESOURCE_URL, value) succeeding implies that getTrusted($sce.URL, value) + * will also succeed. + * + * Inheritance happens to capture this in a natural way. In some future, we + * may not use inheritance anymore. That is OK because no code outside of + * sce.js and sceSpecs.js would need to be aware of this detail. + */ + + this.$get = ['$parse', '$sniffer', '$sceDelegate', function( + $parse, $sniffer, $sceDelegate) { + // Prereq: Ensure that we're not running in IE8 quirks mode. In that mode, IE allows + // the "expression(javascript expression)" syntax which is insecure. + if (enabled && $sniffer.msie && $sniffer.msieDocumentMode < 8) { + throw $sceMinErr('iequirks', + 'Strict Contextual Escaping does not support Internet Explorer version < 9 in quirks ' + + 'mode. You can fix this by adding the text to the top of your HTML ' + + 'document. See http://docs.angularjs.org/api/ng.$sce for more information.'); + } + + var sce = shallowCopy(SCE_CONTEXTS); + + /** + * @ngdoc method + * @name $sce#isEnabled + * @kind function + * + * @return {Boolean} true if SCE is enabled, false otherwise. If you want to set the value, you + * have to do it at module config time on {@link ng.$sceProvider $sceProvider}. + * + * @description + * Returns a boolean indicating if SCE is enabled. + */ + sce.isEnabled = function () { + return enabled; + }; + sce.trustAs = $sceDelegate.trustAs; + sce.getTrusted = $sceDelegate.getTrusted; + sce.valueOf = $sceDelegate.valueOf; + + if (!enabled) { + sce.trustAs = sce.getTrusted = function(type, value) { return value; }; + sce.valueOf = identity; + } + + /** + * @ngdoc method + * @name $sce#parse + * + * @description + * Converts Angular {@link guide/expression expression} into a function. This is like {@link + * ng.$parse $parse} and is identical when the expression is a literal constant. Otherwise, it + * wraps the expression in a call to {@link ng.$sce#getTrusted $sce.getTrusted(*type*, + * *result*)} + * + * @param {string} type The kind of SCE context in which this result will be used. + * @param {string} expression String expression to compile. + * @returns {function(context, locals)} a function which represents the compiled expression: + * + * * `context` – `{object}` – an object against which any expressions embedded in the strings + * are evaluated against (typically a scope object). + * * `locals` – `{object=}` – local variables context object, useful for overriding values in + * `context`. + */ + sce.parseAs = function sceParseAs(type, expr) { + var parsed = $parse(expr); + if (parsed.literal && parsed.constant) { + return parsed; + } else { + return function sceParseAsTrusted(self, locals) { + return sce.getTrusted(type, parsed(self, locals)); + }; + } + }; + + /** + * @ngdoc method + * @name $sce#trustAs + * + * @description + * Delegates to {@link ng.$sceDelegate#trustAs `$sceDelegate.trustAs`}. As such, + * returns an object that is trusted by angular for use in specified strict contextual + * escaping contexts (such as ng-bind-html, ng-include, any src attribute + * interpolation, any dom event binding attribute interpolation such as for onclick, etc.) + * that uses the provided value. See * {@link ng.$sce $sce} for enabling strict contextual + * escaping. + * + * @param {string} type The kind of context in which this value is safe for use. e.g. url, + * resource_url, html, js and css. + * @param {*} value The value that that should be considered trusted/safe. + * @returns {*} A value that can be used to stand in for the provided `value` in places + * where Angular expects a $sce.trustAs() return value. + */ + + /** + * @ngdoc method + * @name $sce#trustAsHtml + * + * @description + * Shorthand method. `$sce.trustAsHtml(value)` → + * {@link ng.$sceDelegate#trustAs `$sceDelegate.trustAs($sce.HTML, value)`} + * + * @param {*} value The value to trustAs. + * @returns {*} An object that can be passed to {@link ng.$sce#getTrustedHtml + * $sce.getTrustedHtml(value)} to obtain the original value. (privileged directives + * only accept expressions that are either literal constants or are the + * return value of {@link ng.$sce#trustAs $sce.trustAs}.) + */ + + /** + * @ngdoc method + * @name $sce#trustAsUrl + * + * @description + * Shorthand method. `$sce.trustAsUrl(value)` → + * {@link ng.$sceDelegate#trustAs `$sceDelegate.trustAs($sce.URL, value)`} + * + * @param {*} value The value to trustAs. + * @returns {*} An object that can be passed to {@link ng.$sce#getTrustedUrl + * $sce.getTrustedUrl(value)} to obtain the original value. (privileged directives + * only accept expressions that are either literal constants or are the + * return value of {@link ng.$sce#trustAs $sce.trustAs}.) + */ + + /** + * @ngdoc method + * @name $sce#trustAsResourceUrl + * + * @description + * Shorthand method. `$sce.trustAsResourceUrl(value)` → + * {@link ng.$sceDelegate#trustAs `$sceDelegate.trustAs($sce.RESOURCE_URL, value)`} + * + * @param {*} value The value to trustAs. + * @returns {*} An object that can be passed to {@link ng.$sce#getTrustedResourceUrl + * $sce.getTrustedResourceUrl(value)} to obtain the original value. (privileged directives + * only accept expressions that are either literal constants or are the return + * value of {@link ng.$sce#trustAs $sce.trustAs}.) + */ + + /** + * @ngdoc method + * @name $sce#trustAsJs + * + * @description + * Shorthand method. `$sce.trustAsJs(value)` → + * {@link ng.$sceDelegate#trustAs `$sceDelegate.trustAs($sce.JS, value)`} + * + * @param {*} value The value to trustAs. + * @returns {*} An object that can be passed to {@link ng.$sce#getTrustedJs + * $sce.getTrustedJs(value)} to obtain the original value. (privileged directives + * only accept expressions that are either literal constants or are the + * return value of {@link ng.$sce#trustAs $sce.trustAs}.) + */ + + /** + * @ngdoc method + * @name $sce#getTrusted + * + * @description + * Delegates to {@link ng.$sceDelegate#getTrusted `$sceDelegate.getTrusted`}. As such, + * takes the result of a {@link ng.$sce#trustAs `$sce.trustAs`}() call and returns the + * originally supplied value if the queried context type is a supertype of the created type. + * If this condition isn't satisfied, throws an exception. + * + * @param {string} type The kind of context in which this value is to be used. + * @param {*} maybeTrusted The result of a prior {@link ng.$sce#trustAs `$sce.trustAs`} + * call. + * @returns {*} The value the was originally provided to + * {@link ng.$sce#trustAs `$sce.trustAs`} if valid in this context. + * Otherwise, throws an exception. + */ + + /** + * @ngdoc method + * @name $sce#getTrustedHtml + * + * @description + * Shorthand method. `$sce.getTrustedHtml(value)` → + * {@link ng.$sceDelegate#getTrusted `$sceDelegate.getTrusted($sce.HTML, value)`} + * + * @param {*} value The value to pass to `$sce.getTrusted`. + * @returns {*} The return value of `$sce.getTrusted($sce.HTML, value)` + */ + + /** + * @ngdoc method + * @name $sce#getTrustedCss + * + * @description + * Shorthand method. `$sce.getTrustedCss(value)` → + * {@link ng.$sceDelegate#getTrusted `$sceDelegate.getTrusted($sce.CSS, value)`} + * + * @param {*} value The value to pass to `$sce.getTrusted`. + * @returns {*} The return value of `$sce.getTrusted($sce.CSS, value)` + */ + + /** + * @ngdoc method + * @name $sce#getTrustedUrl + * + * @description + * Shorthand method. `$sce.getTrustedUrl(value)` → + * {@link ng.$sceDelegate#getTrusted `$sceDelegate.getTrusted($sce.URL, value)`} + * + * @param {*} value The value to pass to `$sce.getTrusted`. + * @returns {*} The return value of `$sce.getTrusted($sce.URL, value)` + */ + + /** + * @ngdoc method + * @name $sce#getTrustedResourceUrl + * + * @description + * Shorthand method. `$sce.getTrustedResourceUrl(value)` → + * {@link ng.$sceDelegate#getTrusted `$sceDelegate.getTrusted($sce.RESOURCE_URL, value)`} + * + * @param {*} value The value to pass to `$sceDelegate.getTrusted`. + * @returns {*} The return value of `$sce.getTrusted($sce.RESOURCE_URL, value)` + */ + + /** + * @ngdoc method + * @name $sce#getTrustedJs + * + * @description + * Shorthand method. `$sce.getTrustedJs(value)` → + * {@link ng.$sceDelegate#getTrusted `$sceDelegate.getTrusted($sce.JS, value)`} + * + * @param {*} value The value to pass to `$sce.getTrusted`. + * @returns {*} The return value of `$sce.getTrusted($sce.JS, value)` + */ + + /** + * @ngdoc method + * @name $sce#parseAsHtml + * + * @description + * Shorthand method. `$sce.parseAsHtml(expression string)` → + * {@link ng.$sce#parse `$sce.parseAs($sce.HTML, value)`} + * + * @param {string} expression String expression to compile. + * @returns {function(context, locals)} a function which represents the compiled expression: + * + * * `context` – `{object}` – an object against which any expressions embedded in the strings + * are evaluated against (typically a scope object). + * * `locals` – `{object=}` – local variables context object, useful for overriding values in + * `context`. + */ + + /** + * @ngdoc method + * @name $sce#parseAsCss + * + * @description + * Shorthand method. `$sce.parseAsCss(value)` → + * {@link ng.$sce#parse `$sce.parseAs($sce.CSS, value)`} + * + * @param {string} expression String expression to compile. + * @returns {function(context, locals)} a function which represents the compiled expression: + * + * * `context` – `{object}` – an object against which any expressions embedded in the strings + * are evaluated against (typically a scope object). + * * `locals` – `{object=}` – local variables context object, useful for overriding values in + * `context`. + */ + + /** + * @ngdoc method + * @name $sce#parseAsUrl + * + * @description + * Shorthand method. `$sce.parseAsUrl(value)` → + * {@link ng.$sce#parse `$sce.parseAs($sce.URL, value)`} + * + * @param {string} expression String expression to compile. + * @returns {function(context, locals)} a function which represents the compiled expression: + * + * * `context` – `{object}` – an object against which any expressions embedded in the strings + * are evaluated against (typically a scope object). + * * `locals` – `{object=}` – local variables context object, useful for overriding values in + * `context`. + */ + + /** + * @ngdoc method + * @name $sce#parseAsResourceUrl + * + * @description + * Shorthand method. `$sce.parseAsResourceUrl(value)` → + * {@link ng.$sce#parse `$sce.parseAs($sce.RESOURCE_URL, value)`} + * + * @param {string} expression String expression to compile. + * @returns {function(context, locals)} a function which represents the compiled expression: + * + * * `context` – `{object}` – an object against which any expressions embedded in the strings + * are evaluated against (typically a scope object). + * * `locals` – `{object=}` – local variables context object, useful for overriding values in + * `context`. + */ + + /** + * @ngdoc method + * @name $sce#parseAsJs + * + * @description + * Shorthand method. `$sce.parseAsJs(value)` → + * {@link ng.$sce#parse `$sce.parseAs($sce.JS, value)`} + * + * @param {string} expression String expression to compile. + * @returns {function(context, locals)} a function which represents the compiled expression: + * + * * `context` – `{object}` – an object against which any expressions embedded in the strings + * are evaluated against (typically a scope object). + * * `locals` – `{object=}` – local variables context object, useful for overriding values in + * `context`. + */ + + // Shorthand delegations. + var parse = sce.parseAs, + getTrusted = sce.getTrusted, + trustAs = sce.trustAs; + + forEach(SCE_CONTEXTS, function (enumValue, name) { + var lName = lowercase(name); + sce[camelCase("parse_as_" + lName)] = function (expr) { + return parse(enumValue, expr); + }; + sce[camelCase("get_trusted_" + lName)] = function (value) { + return getTrusted(enumValue, value); + }; + sce[camelCase("trust_as_" + lName)] = function (value) { + return trustAs(enumValue, value); + }; + }); + + return sce; + }]; +} + +/** + * !!! This is an undocumented "private" service !!! + * + * @name $sniffer + * @requires $window + * @requires $document + * + * @property {boolean} history Does the browser support html5 history api ? + * @property {boolean} hashchange Does the browser support hashchange event ? + * @property {boolean} transitions Does the browser support CSS transition events ? + * @property {boolean} animations Does the browser support CSS animation events ? + * + * @description + * This is very simple implementation of testing browser's features. + */ +function $SnifferProvider() { + this.$get = ['$window', '$document', function($window, $document) { + var eventSupport = {}, + android = + int((/android (\d+)/.exec(lowercase(($window.navigator || {}).userAgent)) || [])[1]), + boxee = /Boxee/i.test(($window.navigator || {}).userAgent), + document = $document[0] || {}, + documentMode = document.documentMode, + vendorPrefix, + vendorRegex = /^(Moz|webkit|O|ms)(?=[A-Z])/, + bodyStyle = document.body && document.body.style, + transitions = false, + animations = false, + match; + + if (bodyStyle) { + for(var prop in bodyStyle) { + if(match = vendorRegex.exec(prop)) { + vendorPrefix = match[0]; + vendorPrefix = vendorPrefix.substr(0, 1).toUpperCase() + vendorPrefix.substr(1); + break; + } + } + + if(!vendorPrefix) { + vendorPrefix = ('WebkitOpacity' in bodyStyle) && 'webkit'; + } + + transitions = !!(('transition' in bodyStyle) || (vendorPrefix + 'Transition' in bodyStyle)); + animations = !!(('animation' in bodyStyle) || (vendorPrefix + 'Animation' in bodyStyle)); + + if (android && (!transitions||!animations)) { + transitions = isString(document.body.style.webkitTransition); + animations = isString(document.body.style.webkitAnimation); + } + } + + + return { + // Android has history.pushState, but it does not update location correctly + // so let's not use the history API at all. + // http://code.google.com/p/android/issues/detail?id=17471 + // https://github.com/angular/angular.js/issues/904 + + // older webkit browser (533.9) on Boxee box has exactly the same problem as Android has + // so let's not use the history API also + // We are purposefully using `!(android < 4)` to cover the case when `android` is undefined + // jshint -W018 + history: !!($window.history && $window.history.pushState && !(android < 4) && !boxee), + // jshint +W018 + hashchange: 'onhashchange' in $window && + // IE8 compatible mode lies + (!documentMode || documentMode > 7), + hasEvent: function(event) { + // IE9 implements 'input' event it's so fubared that we rather pretend that it doesn't have + // it. In particular the event is not fired when backspace or delete key are pressed or + // when cut operation is performed. + if (event == 'input' && msie == 9) return false; + + if (isUndefined(eventSupport[event])) { + var divElm = document.createElement('div'); + eventSupport[event] = 'on' + event in divElm; + } + + return eventSupport[event]; + }, + csp: csp(), + vendorPrefix: vendorPrefix, + transitions : transitions, + animations : animations, + android: android, + msie : msie, + msieDocumentMode: documentMode + }; + }]; +} + +function $TimeoutProvider() { + this.$get = ['$rootScope', '$browser', '$q', '$exceptionHandler', + function($rootScope, $browser, $q, $exceptionHandler) { + var deferreds = {}; + + + /** + * @ngdoc service + * @name $timeout + * + * @description + * Angular's wrapper for `window.setTimeout`. The `fn` function is wrapped into a try/catch + * block and delegates any exceptions to + * {@link ng.$exceptionHandler $exceptionHandler} service. + * + * The return value of registering a timeout function is a promise, which will be resolved when + * the timeout is reached and the timeout function is executed. + * + * To cancel a timeout request, call `$timeout.cancel(promise)`. + * + * In tests you can use {@link ngMock.$timeout `$timeout.flush()`} to + * synchronously flush the queue of deferred functions. + * + * @param {function()} fn A function, whose execution should be delayed. + * @param {number=} [delay=0] Delay in milliseconds. + * @param {boolean=} [invokeApply=true] If set to `false` skips model dirty checking, otherwise + * will invoke `fn` within the {@link ng.$rootScope.Scope#$apply $apply} block. + * @returns {Promise} Promise that will be resolved when the timeout is reached. The value this + * promise will be resolved with is the return value of the `fn` function. + * + */ + function timeout(fn, delay, invokeApply) { + var deferred = $q.defer(), + promise = deferred.promise, + skipApply = (isDefined(invokeApply) && !invokeApply), + timeoutId; + + timeoutId = $browser.defer(function() { + try { + deferred.resolve(fn()); + } catch(e) { + deferred.reject(e); + $exceptionHandler(e); + } + finally { + delete deferreds[promise.$$timeoutId]; + } + + if (!skipApply) $rootScope.$apply(); + }, delay); + + promise.$$timeoutId = timeoutId; + deferreds[timeoutId] = deferred; + + return promise; + } + + + /** + * @ngdoc method + * @name $timeout#cancel + * + * @description + * Cancels a task associated with the `promise`. As a result of this, the promise will be + * resolved with a rejection. + * + * @param {Promise=} promise Promise returned by the `$timeout` function. + * @returns {boolean} Returns `true` if the task hasn't executed yet and was successfully + * canceled. + */ + timeout.cancel = function(promise) { + if (promise && promise.$$timeoutId in deferreds) { + deferreds[promise.$$timeoutId].reject('canceled'); + delete deferreds[promise.$$timeoutId]; + return $browser.defer.cancel(promise.$$timeoutId); + } + return false; + }; + + return timeout; + }]; +} + +// NOTE: The usage of window and document instead of $window and $document here is +// deliberate. This service depends on the specific behavior of anchor nodes created by the +// browser (resolving and parsing URLs) that is unlikely to be provided by mock objects and +// cause us to break tests. In addition, when the browser resolves a URL for XHR, it +// doesn't know about mocked locations and resolves URLs to the real document - which is +// exactly the behavior needed here. There is little value is mocking these out for this +// service. +var urlParsingNode = document.createElement("a"); +var originUrl = urlResolve(window.location.href, true); + + +/** + * + * Implementation Notes for non-IE browsers + * ---------------------------------------- + * Assigning a URL to the href property of an anchor DOM node, even one attached to the DOM, + * results both in the normalizing and parsing of the URL. Normalizing means that a relative + * URL will be resolved into an absolute URL in the context of the application document. + * Parsing means that the anchor node's host, hostname, protocol, port, pathname and related + * properties are all populated to reflect the normalized URL. This approach has wide + * compatibility - Safari 1+, Mozilla 1+, Opera 7+,e etc. See + * http://www.aptana.com/reference/html/api/HTMLAnchorElement.html + * + * Implementation Notes for IE + * --------------------------- + * IE >= 8 and <= 10 normalizes the URL when assigned to the anchor node similar to the other + * browsers. However, the parsed components will not be set if the URL assigned did not specify + * them. (e.g. if you assign a.href = "foo", then a.protocol, a.host, etc. will be empty.) We + * work around that by performing the parsing in a 2nd step by taking a previously normalized + * URL (e.g. by assigning to a.href) and assigning it a.href again. This correctly populates the + * properties such as protocol, hostname, port, etc. + * + * IE7 does not normalize the URL when assigned to an anchor node. (Apparently, it does, if one + * uses the inner HTML approach to assign the URL as part of an HTML snippet - + * http://stackoverflow.com/a/472729) However, setting img[src] does normalize the URL. + * Unfortunately, setting img[src] to something like "javascript:foo" on IE throws an exception. + * Since the primary usage for normalizing URLs is to sanitize such URLs, we can't use that + * method and IE < 8 is unsupported. + * + * References: + * http://developer.mozilla.org/en-US/docs/Web/API/HTMLAnchorElement + * http://www.aptana.com/reference/html/api/HTMLAnchorElement.html + * http://url.spec.whatwg.org/#urlutils + * https://github.com/angular/angular.js/pull/2902 + * http://james.padolsey.com/javascript/parsing-urls-with-the-dom/ + * + * @kind function + * @param {string} url The URL to be parsed. + * @description Normalizes and parses a URL. + * @returns {object} Returns the normalized URL as a dictionary. + * + * | member name | Description | + * |---------------|----------------| + * | href | A normalized version of the provided URL if it was not an absolute URL | + * | protocol | The protocol including the trailing colon | + * | host | The host and port (if the port is non-default) of the normalizedUrl | + * | search | The search params, minus the question mark | + * | hash | The hash string, minus the hash symbol + * | hostname | The hostname + * | port | The port, without ":" + * | pathname | The pathname, beginning with "/" + * + */ +function urlResolve(url, base) { + var href = url; + + if (msie) { + // Normalize before parse. Refer Implementation Notes on why this is + // done in two steps on IE. + urlParsingNode.setAttribute("href", href); + href = urlParsingNode.href; + } + + urlParsingNode.setAttribute('href', href); + + // urlParsingNode provides the UrlUtils interface - http://url.spec.whatwg.org/#urlutils + return { + href: urlParsingNode.href, + protocol: urlParsingNode.protocol ? urlParsingNode.protocol.replace(/:$/, '') : '', + host: urlParsingNode.host, + search: urlParsingNode.search ? urlParsingNode.search.replace(/^\?/, '') : '', + hash: urlParsingNode.hash ? urlParsingNode.hash.replace(/^#/, '') : '', + hostname: urlParsingNode.hostname, + port: urlParsingNode.port, + pathname: (urlParsingNode.pathname.charAt(0) === '/') + ? urlParsingNode.pathname + : '/' + urlParsingNode.pathname + }; +} + +/** + * Parse a request URL and determine whether this is a same-origin request as the application document. + * + * @param {string|object} requestUrl The url of the request as a string that will be resolved + * or a parsed URL object. + * @returns {boolean} Whether the request is for the same origin as the application document. + */ +function urlIsSameOrigin(requestUrl) { + var parsed = (isString(requestUrl)) ? urlResolve(requestUrl) : requestUrl; + return (parsed.protocol === originUrl.protocol && + parsed.host === originUrl.host); +} + +/** + * @ngdoc service + * @name $window + * + * @description + * A reference to the browser's `window` object. While `window` + * is globally available in JavaScript, it causes testability problems, because + * it is a global variable. In angular we always refer to it through the + * `$window` service, so it may be overridden, removed or mocked for testing. + * + * Expressions, like the one defined for the `ngClick` directive in the example + * below, are evaluated with respect to the current scope. Therefore, there is + * no risk of inadvertently coding in a dependency on a global value in such an + * expression. + * + * @example + + + +
+ + +
+
+ + it('should display the greeting in the input box', function() { + element(by.model('greeting')).sendKeys('Hello, E2E Tests'); + // If we click the button it will block the test runner + // element(':button').click(); + }); + +
+ */ +function $WindowProvider(){ + this.$get = valueFn(window); +} + +/** + * @ngdoc provider + * @name $filterProvider + * @description + * + * Filters are just functions which transform input to an output. However filters need to be + * Dependency Injected. To achieve this a filter definition consists of a factory function which is + * annotated with dependencies and is responsible for creating a filter function. + * + * ```js + * // Filter registration + * function MyModule($provide, $filterProvider) { + * // create a service to demonstrate injection (not always needed) + * $provide.value('greet', function(name){ + * return 'Hello ' + name + '!'; + * }); + * + * // register a filter factory which uses the + * // greet service to demonstrate DI. + * $filterProvider.register('greet', function(greet){ + * // return the filter function which uses the greet service + * // to generate salutation + * return function(text) { + * // filters need to be forgiving so check input validity + * return text && greet(text) || text; + * }; + * }); + * } + * ``` + * + * The filter function is registered with the `$injector` under the filter name suffix with + * `Filter`. + * + * ```js + * it('should be the same instance', inject( + * function($filterProvider) { + * $filterProvider.register('reverse', function(){ + * return ...; + * }); + * }, + * function($filter, reverseFilter) { + * expect($filter('reverse')).toBe(reverseFilter); + * }); + * ``` + * + * + * For more information about how angular filters work, and how to create your own filters, see + * {@link guide/filter Filters} in the Angular Developer Guide. + */ +/** + * @ngdoc method + * @name $filterProvider#register + * @description + * Register filter factory function. + * + * @param {String} name Name of the filter. + * @param {Function} fn The filter factory function which is injectable. + */ + + +/** + * @ngdoc service + * @name $filter + * @kind function + * @description + * Filters are used for formatting data displayed to the user. + * + * The general syntax in templates is as follows: + * + * {{ expression [| filter_name[:parameter_value] ... ] }} + * + * @param {String} name Name of the filter function to retrieve + * @return {Function} the filter function + * @example + + +
+

{{ originalText }}

+

{{ filteredText }}

+
+
+ + + angular.module('filterExample', []) + .controller('MainCtrl', function($scope, $filter) { + $scope.originalText = 'hello'; + $scope.filteredText = $filter('uppercase')($scope.originalText); + }); + +
+ */ +$FilterProvider.$inject = ['$provide']; +function $FilterProvider($provide) { + var suffix = 'Filter'; + + /** + * @ngdoc method + * @name $controllerProvider#register + * @param {string|Object} name Name of the filter function, or an object map of filters where + * the keys are the filter names and the values are the filter factories. + * @returns {Object} Registered filter instance, or if a map of filters was provided then a map + * of the registered filter instances. + */ + function register(name, factory) { + if(isObject(name)) { + var filters = {}; + forEach(name, function(filter, key) { + filters[key] = register(key, filter); + }); + return filters; + } else { + return $provide.factory(name + suffix, factory); + } + } + this.register = register; + + this.$get = ['$injector', function($injector) { + return function(name) { + return $injector.get(name + suffix); + }; + }]; + + //////////////////////////////////////// + + /* global + currencyFilter: false, + dateFilter: false, + filterFilter: false, + jsonFilter: false, + limitToFilter: false, + lowercaseFilter: false, + numberFilter: false, + orderByFilter: false, + uppercaseFilter: false, + */ + + register('currency', currencyFilter); + register('date', dateFilter); + register('filter', filterFilter); + register('json', jsonFilter); + register('limitTo', limitToFilter); + register('lowercase', lowercaseFilter); + register('number', numberFilter); + register('orderBy', orderByFilter); + register('uppercase', uppercaseFilter); +} + +/** + * @ngdoc filter + * @name filter + * @kind function + * + * @description + * Selects a subset of items from `array` and returns it as a new array. + * + * @param {Array} array The source array. + * @param {string|Object|function()} expression The predicate to be used for selecting items from + * `array`. + * + * Can be one of: + * + * - `string`: The string is evaluated as an expression and the resulting value is used for substring match against + * the contents of the `array`. All strings or objects with string properties in `array` that contain this string + * will be returned. The predicate can be negated by prefixing the string with `!`. + * + * - `Object`: A pattern object can be used to filter specific properties on objects contained + * by `array`. For example `{name:"M", phone:"1"}` predicate will return an array of items + * which have property `name` containing "M" and property `phone` containing "1". A special + * property name `$` can be used (as in `{$:"text"}`) to accept a match against any + * property of the object. That's equivalent to the simple substring match with a `string` + * as described above. + * + * - `function(value)`: A predicate function can be used to write arbitrary filters. The function is + * called for each element of `array`. The final result is an array of those elements that + * the predicate returned true for. + * + * @param {function(actual, expected)|true|undefined} comparator Comparator which is used in + * determining if the expected value (from the filter expression) and actual value (from + * the object in the array) should be considered a match. + * + * Can be one of: + * + * - `function(actual, expected)`: + * The function will be given the object value and the predicate value to compare and + * should return true if the item should be included in filtered result. + * + * - `true`: A shorthand for `function(actual, expected) { return angular.equals(expected, actual)}`. + * this is essentially strict comparison of expected and actual. + * + * - `false|undefined`: A short hand for a function which will look for a substring match in case + * insensitive way. + * + * @example + + +
+ + Search: + + + + + + +
NamePhone
{{friend.name}}{{friend.phone}}
+
+ Any:
+ Name only
+ Phone only
+ Equality
+ + + + + + +
NamePhone
{{friendObj.name}}{{friendObj.phone}}
+
+ + var expectFriendNames = function(expectedNames, key) { + element.all(by.repeater(key + ' in friends').column(key + '.name')).then(function(arr) { + arr.forEach(function(wd, i) { + expect(wd.getText()).toMatch(expectedNames[i]); + }); + }); + }; + + it('should search across all fields when filtering with a string', function() { + var searchText = element(by.model('searchText')); + searchText.clear(); + searchText.sendKeys('m'); + expectFriendNames(['Mary', 'Mike', 'Adam'], 'friend'); + + searchText.clear(); + searchText.sendKeys('76'); + expectFriendNames(['John', 'Julie'], 'friend'); + }); + + it('should search in specific fields when filtering with a predicate object', function() { + var searchAny = element(by.model('search.$')); + searchAny.clear(); + searchAny.sendKeys('i'); + expectFriendNames(['Mary', 'Mike', 'Julie', 'Juliette'], 'friendObj'); + }); + it('should use a equal comparison when comparator is true', function() { + var searchName = element(by.model('search.name')); + var strict = element(by.model('strict')); + searchName.clear(); + searchName.sendKeys('Julie'); + strict.click(); + expectFriendNames(['Julie'], 'friendObj'); + }); + +
+ */ +function filterFilter() { + return function(array, expression, comparator) { + if (!isArray(array)) return array; + + var comparatorType = typeof(comparator), + predicates = []; + + predicates.check = function(value) { + for (var j = 0; j < predicates.length; j++) { + if(!predicates[j](value)) { + return false; + } + } + return true; + }; + + if (comparatorType !== 'function') { + if (comparatorType === 'boolean' && comparator) { + comparator = function(obj, text) { + return angular.equals(obj, text); + }; + } else { + comparator = function(obj, text) { + if (obj && text && typeof obj === 'object' && typeof text === 'object') { + for (var objKey in obj) { + if (objKey.charAt(0) !== '$' && hasOwnProperty.call(obj, objKey) && + comparator(obj[objKey], text[objKey])) { + return true; + } + } + return false; + } + text = (''+text).toLowerCase(); + return (''+obj).toLowerCase().indexOf(text) > -1; + }; + } + } + + var search = function(obj, text){ + if (typeof text == 'string' && text.charAt(0) === '!') { + return !search(obj, text.substr(1)); + } + switch (typeof obj) { + case "boolean": + case "number": + case "string": + return comparator(obj, text); + case "object": + switch (typeof text) { + case "object": + return comparator(obj, text); + default: + for ( var objKey in obj) { + if (objKey.charAt(0) !== '$' && search(obj[objKey], text)) { + return true; + } + } + break; + } + return false; + case "array": + for ( var i = 0; i < obj.length; i++) { + if (search(obj[i], text)) { + return true; + } + } + return false; + default: + return false; + } + }; + switch (typeof expression) { + case "boolean": + case "number": + case "string": + // Set up expression object and fall through + expression = {$:expression}; + // jshint -W086 + case "object": + // jshint +W086 + for (var key in expression) { + (function(path) { + if (typeof expression[path] == 'undefined') return; + predicates.push(function(value) { + return search(path == '$' ? value : (value && value[path]), expression[path]); + }); + })(key); + } + break; + case 'function': + predicates.push(expression); + break; + default: + return array; + } + var filtered = []; + for ( var j = 0; j < array.length; j++) { + var value = array[j]; + if (predicates.check(value)) { + filtered.push(value); + } + } + return filtered; + }; +} + +/** + * @ngdoc filter + * @name currency + * @kind function + * + * @description + * Formats a number as a currency (ie $1,234.56). When no currency symbol is provided, default + * symbol for current locale is used. + * + * @param {number} amount Input to filter. + * @param {string=} symbol Currency symbol or identifier to be displayed. + * @returns {string} Formatted number. + * + * + * @example + + + +
+
+ default currency symbol ($): {{amount | currency}}
+ custom currency identifier (USD$): {{amount | currency:"USD$"}} +
+
+ + it('should init with 1234.56', function() { + expect(element(by.id('currency-default')).getText()).toBe('$1,234.56'); + expect(element(by.binding('amount | currency:"USD$"')).getText()).toBe('USD$1,234.56'); + }); + it('should update', function() { + if (browser.params.browser == 'safari') { + // Safari does not understand the minus key. See + // https://github.com/angular/protractor/issues/481 + return; + } + element(by.model('amount')).clear(); + element(by.model('amount')).sendKeys('-1234'); + expect(element(by.id('currency-default')).getText()).toBe('($1,234.00)'); + expect(element(by.binding('amount | currency:"USD$"')).getText()).toBe('(USD$1,234.00)'); + }); + +
+ */ +currencyFilter.$inject = ['$locale']; +function currencyFilter($locale) { + var formats = $locale.NUMBER_FORMATS; + return function(amount, currencySymbol){ + if (isUndefined(currencySymbol)) currencySymbol = formats.CURRENCY_SYM; + return formatNumber(amount, formats.PATTERNS[1], formats.GROUP_SEP, formats.DECIMAL_SEP, 2). + replace(/\u00A4/g, currencySymbol); + }; +} + +/** + * @ngdoc filter + * @name number + * @kind function + * + * @description + * Formats a number as text. + * + * If the input is not a number an empty string is returned. + * + * @param {number|string} number Number to format. + * @param {(number|string)=} fractionSize Number of decimal places to round the number to. + * If this is not provided then the fraction size is computed from the current locale's number + * formatting pattern. In the case of the default locale, it will be 3. + * @returns {string} Number rounded to decimalPlaces and places a “,” after each third digit. + * + * @example + + + +
+ Enter number:
+ Default formatting: {{val | number}}
+ No fractions: {{val | number:0}}
+ Negative number: {{-val | number:4}} +
+
+ + it('should format numbers', function() { + expect(element(by.id('number-default')).getText()).toBe('1,234.568'); + expect(element(by.binding('val | number:0')).getText()).toBe('1,235'); + expect(element(by.binding('-val | number:4')).getText()).toBe('-1,234.5679'); + }); + + it('should update', function() { + element(by.model('val')).clear(); + element(by.model('val')).sendKeys('3374.333'); + expect(element(by.id('number-default')).getText()).toBe('3,374.333'); + expect(element(by.binding('val | number:0')).getText()).toBe('3,374'); + expect(element(by.binding('-val | number:4')).getText()).toBe('-3,374.3330'); + }); + +
+ */ + + +numberFilter.$inject = ['$locale']; +function numberFilter($locale) { + var formats = $locale.NUMBER_FORMATS; + return function(number, fractionSize) { + return formatNumber(number, formats.PATTERNS[0], formats.GROUP_SEP, formats.DECIMAL_SEP, + fractionSize); + }; +} + +var DECIMAL_SEP = '.'; +function formatNumber(number, pattern, groupSep, decimalSep, fractionSize) { + if (number == null || !isFinite(number) || isObject(number)) return ''; + + var isNegative = number < 0; + number = Math.abs(number); + var numStr = number + '', + formatedText = '', + parts = []; + + var hasExponent = false; + if (numStr.indexOf('e') !== -1) { + var match = numStr.match(/([\d\.]+)e(-?)(\d+)/); + if (match && match[2] == '-' && match[3] > fractionSize + 1) { + numStr = '0'; + } else { + formatedText = numStr; + hasExponent = true; + } + } + + if (!hasExponent) { + var fractionLen = (numStr.split(DECIMAL_SEP)[1] || '').length; + + // determine fractionSize if it is not specified + if (isUndefined(fractionSize)) { + fractionSize = Math.min(Math.max(pattern.minFrac, fractionLen), pattern.maxFrac); + } + + var pow = Math.pow(10, fractionSize + 1); + number = Math.floor(number * pow + 5) / pow; + var fraction = ('' + number).split(DECIMAL_SEP); + var whole = fraction[0]; + fraction = fraction[1] || ''; + + var i, pos = 0, + lgroup = pattern.lgSize, + group = pattern.gSize; + + if (whole.length >= (lgroup + group)) { + pos = whole.length - lgroup; + for (i = 0; i < pos; i++) { + if ((pos - i)%group === 0 && i !== 0) { + formatedText += groupSep; + } + formatedText += whole.charAt(i); + } + } + + for (i = pos; i < whole.length; i++) { + if ((whole.length - i)%lgroup === 0 && i !== 0) { + formatedText += groupSep; + } + formatedText += whole.charAt(i); + } + + // format fraction part. + while(fraction.length < fractionSize) { + fraction += '0'; + } + + if (fractionSize && fractionSize !== "0") formatedText += decimalSep + fraction.substr(0, fractionSize); + } else { + + if (fractionSize > 0 && number > -1 && number < 1) { + formatedText = number.toFixed(fractionSize); + } + } + + parts.push(isNegative ? pattern.negPre : pattern.posPre); + parts.push(formatedText); + parts.push(isNegative ? pattern.negSuf : pattern.posSuf); + return parts.join(''); +} + +function padNumber(num, digits, trim) { + var neg = ''; + if (num < 0) { + neg = '-'; + num = -num; + } + num = '' + num; + while(num.length < digits) num = '0' + num; + if (trim) + num = num.substr(num.length - digits); + return neg + num; +} + + +function dateGetter(name, size, offset, trim) { + offset = offset || 0; + return function(date) { + var value = date['get' + name](); + if (offset > 0 || value > -offset) + value += offset; + if (value === 0 && offset == -12 ) value = 12; + return padNumber(value, size, trim); + }; +} + +function dateStrGetter(name, shortForm) { + return function(date, formats) { + var value = date['get' + name](); + var get = uppercase(shortForm ? ('SHORT' + name) : name); + + return formats[get][value]; + }; +} + +function timeZoneGetter(date) { + var zone = -1 * date.getTimezoneOffset(); + var paddedZone = (zone >= 0) ? "+" : ""; + + paddedZone += padNumber(Math[zone > 0 ? 'floor' : 'ceil'](zone / 60), 2) + + padNumber(Math.abs(zone % 60), 2); + + return paddedZone; +} + +function ampmGetter(date, formats) { + return date.getHours() < 12 ? formats.AMPMS[0] : formats.AMPMS[1]; +} + +var DATE_FORMATS = { + yyyy: dateGetter('FullYear', 4), + yy: dateGetter('FullYear', 2, 0, true), + y: dateGetter('FullYear', 1), + MMMM: dateStrGetter('Month'), + MMM: dateStrGetter('Month', true), + MM: dateGetter('Month', 2, 1), + M: dateGetter('Month', 1, 1), + dd: dateGetter('Date', 2), + d: dateGetter('Date', 1), + HH: dateGetter('Hours', 2), + H: dateGetter('Hours', 1), + hh: dateGetter('Hours', 2, -12), + h: dateGetter('Hours', 1, -12), + mm: dateGetter('Minutes', 2), + m: dateGetter('Minutes', 1), + ss: dateGetter('Seconds', 2), + s: dateGetter('Seconds', 1), + // while ISO 8601 requires fractions to be prefixed with `.` or `,` + // we can be just safely rely on using `sss` since we currently don't support single or two digit fractions + sss: dateGetter('Milliseconds', 3), + EEEE: dateStrGetter('Day'), + EEE: dateStrGetter('Day', true), + a: ampmGetter, + Z: timeZoneGetter +}; + +var DATE_FORMATS_SPLIT = /((?:[^yMdHhmsaZE']+)|(?:'(?:[^']|'')*')|(?:E+|y+|M+|d+|H+|h+|m+|s+|a|Z))(.*)/, + NUMBER_STRING = /^\-?\d+$/; + +/** + * @ngdoc filter + * @name date + * @kind function + * + * @description + * Formats `date` to a string based on the requested `format`. + * + * `format` string can be composed of the following elements: + * + * * `'yyyy'`: 4 digit representation of year (e.g. AD 1 => 0001, AD 2010 => 2010) + * * `'yy'`: 2 digit representation of year, padded (00-99). (e.g. AD 2001 => 01, AD 2010 => 10) + * * `'y'`: 1 digit representation of year, e.g. (AD 1 => 1, AD 199 => 199) + * * `'MMMM'`: Month in year (January-December) + * * `'MMM'`: Month in year (Jan-Dec) + * * `'MM'`: Month in year, padded (01-12) + * * `'M'`: Month in year (1-12) + * * `'dd'`: Day in month, padded (01-31) + * * `'d'`: Day in month (1-31) + * * `'EEEE'`: Day in Week,(Sunday-Saturday) + * * `'EEE'`: Day in Week, (Sun-Sat) + * * `'HH'`: Hour in day, padded (00-23) + * * `'H'`: Hour in day (0-23) + * * `'hh'`: Hour in am/pm, padded (01-12) + * * `'h'`: Hour in am/pm, (1-12) + * * `'mm'`: Minute in hour, padded (00-59) + * * `'m'`: Minute in hour (0-59) + * * `'ss'`: Second in minute, padded (00-59) + * * `'s'`: Second in minute (0-59) + * * `'.sss' or ',sss'`: Millisecond in second, padded (000-999) + * * `'a'`: am/pm marker + * * `'Z'`: 4 digit (+sign) representation of the timezone offset (-1200-+1200) + * + * `format` string can also be one of the following predefined + * {@link guide/i18n localizable formats}: + * + * * `'medium'`: equivalent to `'MMM d, y h:mm:ss a'` for en_US locale + * (e.g. Sep 3, 2010 12:05:08 pm) + * * `'short'`: equivalent to `'M/d/yy h:mm a'` for en_US locale (e.g. 9/3/10 12:05 pm) + * * `'fullDate'`: equivalent to `'EEEE, MMMM d,y'` for en_US locale + * (e.g. Friday, September 3, 2010) + * * `'longDate'`: equivalent to `'MMMM d, y'` for en_US locale (e.g. September 3, 2010) + * * `'mediumDate'`: equivalent to `'MMM d, y'` for en_US locale (e.g. Sep 3, 2010) + * * `'shortDate'`: equivalent to `'M/d/yy'` for en_US locale (e.g. 9/3/10) + * * `'mediumTime'`: equivalent to `'h:mm:ss a'` for en_US locale (e.g. 12:05:08 pm) + * * `'shortTime'`: equivalent to `'h:mm a'` for en_US locale (e.g. 12:05 pm) + * + * `format` string can contain literal values. These need to be quoted with single quotes (e.g. + * `"h 'in the morning'"`). In order to output single quote, use two single quotes in a sequence + * (e.g. `"h 'o''clock'"`). + * + * @param {(Date|number|string)} date Date to format either as Date object, milliseconds (string or + * number) or various ISO 8601 datetime string formats (e.g. yyyy-MM-ddTHH:mm:ss.SSSZ and its + * shorter versions like yyyy-MM-ddTHH:mmZ, yyyy-MM-dd or yyyyMMddTHHmmssZ). If no timezone is + * specified in the string input, the time is considered to be in the local timezone. + * @param {string=} format Formatting rules (see Description). If not specified, + * `mediumDate` is used. + * @returns {string} Formatted string or the input if input is not recognized as date/millis. + * + * @example + + + {{1288323623006 | date:'medium'}}: + {{1288323623006 | date:'medium'}}
+ {{1288323623006 | date:'yyyy-MM-dd HH:mm:ss Z'}}: + {{1288323623006 | date:'yyyy-MM-dd HH:mm:ss Z'}}
+ {{1288323623006 | date:'MM/dd/yyyy @ h:mma'}}: + {{'1288323623006' | date:'MM/dd/yyyy @ h:mma'}}
+
+ + it('should format date', function() { + expect(element(by.binding("1288323623006 | date:'medium'")).getText()). + toMatch(/Oct 2\d, 2010 \d{1,2}:\d{2}:\d{2} (AM|PM)/); + expect(element(by.binding("1288323623006 | date:'yyyy-MM-dd HH:mm:ss Z'")).getText()). + toMatch(/2010\-10\-2\d \d{2}:\d{2}:\d{2} (\-|\+)?\d{4}/); + expect(element(by.binding("'1288323623006' | date:'MM/dd/yyyy @ h:mma'")).getText()). + toMatch(/10\/2\d\/2010 @ \d{1,2}:\d{2}(AM|PM)/); + }); + +
+ */ +dateFilter.$inject = ['$locale']; +function dateFilter($locale) { + + + var R_ISO8601_STR = /^(\d{4})-?(\d\d)-?(\d\d)(?:T(\d\d)(?::?(\d\d)(?::?(\d\d)(?:\.(\d+))?)?)?(Z|([+-])(\d\d):?(\d\d))?)?$/; + // 1 2 3 4 5 6 7 8 9 10 11 + function jsonStringToDate(string) { + var match; + if (match = string.match(R_ISO8601_STR)) { + var date = new Date(0), + tzHour = 0, + tzMin = 0, + dateSetter = match[8] ? date.setUTCFullYear : date.setFullYear, + timeSetter = match[8] ? date.setUTCHours : date.setHours; + + if (match[9]) { + tzHour = int(match[9] + match[10]); + tzMin = int(match[9] + match[11]); + } + dateSetter.call(date, int(match[1]), int(match[2]) - 1, int(match[3])); + var h = int(match[4]||0) - tzHour; + var m = int(match[5]||0) - tzMin; + var s = int(match[6]||0); + var ms = Math.round(parseFloat('0.' + (match[7]||0)) * 1000); + timeSetter.call(date, h, m, s, ms); + return date; + } + return string; + } + + + return function(date, format) { + var text = '', + parts = [], + fn, match; + + format = format || 'mediumDate'; + format = $locale.DATETIME_FORMATS[format] || format; + if (isString(date)) { + if (NUMBER_STRING.test(date)) { + date = int(date); + } else { + date = jsonStringToDate(date); + } + } + + if (isNumber(date)) { + date = new Date(date); + } + + if (!isDate(date)) { + return date; + } + + while(format) { + match = DATE_FORMATS_SPLIT.exec(format); + if (match) { + parts = concat(parts, match, 1); + format = parts.pop(); + } else { + parts.push(format); + format = null; + } + } + + forEach(parts, function(value){ + fn = DATE_FORMATS[value]; + text += fn ? fn(date, $locale.DATETIME_FORMATS) + : value.replace(/(^'|'$)/g, '').replace(/''/g, "'"); + }); + + return text; + }; +} + + +/** + * @ngdoc filter + * @name json + * @kind function + * + * @description + * Allows you to convert a JavaScript object into JSON string. + * + * This filter is mostly useful for debugging. When using the double curly {{value}} notation + * the binding is automatically converted to JSON. + * + * @param {*} object Any JavaScript object (including arrays and primitive types) to filter. + * @returns {string} JSON string. + * + * + * @example + + +
{{ {'name':'value'} | json }}
+
+ + it('should jsonify filtered objects', function() { + expect(element(by.binding("{'name':'value'}")).getText()).toMatch(/\{\n "name": ?"value"\n}/); + }); + +
+ * + */ +function jsonFilter() { + return function(object) { + return toJson(object, true); + }; +} + + +/** + * @ngdoc filter + * @name lowercase + * @kind function + * @description + * Converts string to lowercase. + * @see angular.lowercase + */ +var lowercaseFilter = valueFn(lowercase); + + +/** + * @ngdoc filter + * @name uppercase + * @kind function + * @description + * Converts string to uppercase. + * @see angular.uppercase + */ +var uppercaseFilter = valueFn(uppercase); + +/** + * @ngdoc filter + * @name limitTo + * @kind function + * + * @description + * Creates a new array or string containing only a specified number of elements. The elements + * are taken from either the beginning or the end of the source array or string, as specified by + * the value and sign (positive or negative) of `limit`. + * + * @param {Array|string} input Source array or string to be limited. + * @param {string|number} limit The length of the returned array or string. If the `limit` number + * is positive, `limit` number of items from the beginning of the source array/string are copied. + * If the number is negative, `limit` number of items from the end of the source array/string + * are copied. The `limit` will be trimmed if it exceeds `array.length` + * @returns {Array|string} A new sub-array or substring of length `limit` or less if input array + * had less than `limit` elements. + * + * @example + + + +
+ Limit {{numbers}} to: +

Output numbers: {{ numbers | limitTo:numLimit }}

+ Limit {{letters}} to: +

Output letters: {{ letters | limitTo:letterLimit }}

+
+
+ + var numLimitInput = element(by.model('numLimit')); + var letterLimitInput = element(by.model('letterLimit')); + var limitedNumbers = element(by.binding('numbers | limitTo:numLimit')); + var limitedLetters = element(by.binding('letters | limitTo:letterLimit')); + + it('should limit the number array to first three items', function() { + expect(numLimitInput.getAttribute('value')).toBe('3'); + expect(letterLimitInput.getAttribute('value')).toBe('3'); + expect(limitedNumbers.getText()).toEqual('Output numbers: [1,2,3]'); + expect(limitedLetters.getText()).toEqual('Output letters: abc'); + }); + + it('should update the output when -3 is entered', function() { + numLimitInput.clear(); + numLimitInput.sendKeys('-3'); + letterLimitInput.clear(); + letterLimitInput.sendKeys('-3'); + expect(limitedNumbers.getText()).toEqual('Output numbers: [7,8,9]'); + expect(limitedLetters.getText()).toEqual('Output letters: ghi'); + }); + + it('should not exceed the maximum size of input array', function() { + numLimitInput.clear(); + numLimitInput.sendKeys('100'); + letterLimitInput.clear(); + letterLimitInput.sendKeys('100'); + expect(limitedNumbers.getText()).toEqual('Output numbers: [1,2,3,4,5,6,7,8,9]'); + expect(limitedLetters.getText()).toEqual('Output letters: abcdefghi'); + }); + +
+ */ +function limitToFilter(){ + return function(input, limit) { + if (!isArray(input) && !isString(input)) return input; + + if (Math.abs(Number(limit)) === Infinity) { + limit = Number(limit); + } else { + limit = int(limit); + } + + if (isString(input)) { + //NaN check on limit + if (limit) { + return limit >= 0 ? input.slice(0, limit) : input.slice(limit, input.length); + } else { + return ""; + } + } + + var out = [], + i, n; + + // if abs(limit) exceeds maximum length, trim it + if (limit > input.length) + limit = input.length; + else if (limit < -input.length) + limit = -input.length; + + if (limit > 0) { + i = 0; + n = limit; + } else { + i = input.length + limit; + n = input.length; + } + + for (; i} expression A predicate to be + * used by the comparator to determine the order of elements. + * + * Can be one of: + * + * - `function`: Getter function. The result of this function will be sorted using the + * `<`, `=`, `>` operator. + * - `string`: An Angular expression which evaluates to an object to order by, such as 'name' + * to sort by a property called 'name'. Optionally prefixed with `+` or `-` to control + * ascending or descending sort order (for example, +name or -name). + * - `Array`: An array of function or string predicates. The first predicate in the array + * is used for sorting, but when two items are equivalent, the next predicate is used. + * + * @param {boolean=} reverse Reverse the order of the array. + * @returns {Array} Sorted copy of the source array. + * + * @example + + + +
+
Sorting predicate = {{predicate}}; reverse = {{reverse}}
+
+ [ unsorted ] + + + + + + + + + + + +
Name + (^)Phone NumberAge
{{friend.name}}{{friend.phone}}{{friend.age}}
+
+
+
+ * + * It's also possible to call the orderBy filter manually, by injecting `$filter`, retrieving the + * filter routine with `$filter('orderBy')`, and calling the returned filter routine with the + * desired parameters. + * + * Example: + * + * @example + + +
+ + + + + + + + + + + +
Name + (^)Phone NumberAge
{{friend.name}}{{friend.phone}}{{friend.age}}
+
+
+ + + function Ctrl($scope, $filter) { + var orderBy = $filter('orderBy'); + $scope.friends = [ + { name: 'John', phone: '555-1212', age: 10 }, + { name: 'Mary', phone: '555-9876', age: 19 }, + { name: 'Mike', phone: '555-4321', age: 21 }, + { name: 'Adam', phone: '555-5678', age: 35 }, + { name: 'Julie', phone: '555-8765', age: 29 } + ]; + + $scope.order = function(predicate, reverse) { + $scope.friends = orderBy($scope.friends, predicate, reverse); + }; + $scope.order('-age',false); + } + +
+ */ +orderByFilter.$inject = ['$parse']; +function orderByFilter($parse){ + return function(array, sortPredicate, reverseOrder) { + if (!isArray(array)) return array; + if (!sortPredicate) return array; + sortPredicate = isArray(sortPredicate) ? sortPredicate: [sortPredicate]; + sortPredicate = map(sortPredicate, function(predicate){ + var descending = false, get = predicate || identity; + if (isString(predicate)) { + if ((predicate.charAt(0) == '+' || predicate.charAt(0) == '-')) { + descending = predicate.charAt(0) == '-'; + predicate = predicate.substring(1); + } + get = $parse(predicate); + if (get.constant) { + var key = get(); + return reverseComparator(function(a,b) { + return compare(a[key], b[key]); + }, descending); + } + } + return reverseComparator(function(a,b){ + return compare(get(a),get(b)); + }, descending); + }); + var arrayCopy = []; + for ( var i = 0; i < array.length; i++) { arrayCopy.push(array[i]); } + return arrayCopy.sort(reverseComparator(comparator, reverseOrder)); + + function comparator(o1, o2){ + for ( var i = 0; i < sortPredicate.length; i++) { + var comp = sortPredicate[i](o1, o2); + if (comp !== 0) return comp; + } + return 0; + } + function reverseComparator(comp, descending) { + return toBoolean(descending) + ? function(a,b){return comp(b,a);} + : comp; + } + function compare(v1, v2){ + var t1 = typeof v1; + var t2 = typeof v2; + if (t1 == t2) { + if (t1 == "string") { + v1 = v1.toLowerCase(); + v2 = v2.toLowerCase(); + } + if (v1 === v2) return 0; + return v1 < v2 ? -1 : 1; + } else { + return t1 < t2 ? -1 : 1; + } + } + }; +} + +function ngDirective(directive) { + if (isFunction(directive)) { + directive = { + link: directive + }; + } + directive.restrict = directive.restrict || 'AC'; + return valueFn(directive); +} + +/** + * @ngdoc directive + * @name a + * @restrict E + * + * @description + * Modifies the default behavior of the html A tag so that the default action is prevented when + * the href attribute is empty. + * + * This change permits the easy creation of action links with the `ngClick` directive + * without changing the location or causing page reloads, e.g.: + * `Add Item` + */ +var htmlAnchorDirective = valueFn({ + restrict: 'E', + compile: function(element, attr) { + + if (msie <= 8) { + + // turn link into a stylable link in IE + // but only if it doesn't have name attribute, in which case it's an anchor + if (!attr.href && !attr.name) { + attr.$set('href', ''); + } + + // add a comment node to anchors to workaround IE bug that causes element content to be reset + // to new attribute content if attribute is updated with value containing @ and element also + // contains value with @ + // see issue #1949 + element.append(document.createComment('IE fix')); + } + + if (!attr.href && !attr.xlinkHref && !attr.name) { + return function(scope, element) { + // SVGAElement does not use the href attribute, but rather the 'xlinkHref' attribute. + var href = toString.call(element.prop('href')) === '[object SVGAnimatedString]' ? + 'xlink:href' : 'href'; + element.on('click', function(event){ + // if we have no href url, then don't navigate anywhere. + if (!element.attr(href)) { + event.preventDefault(); + } + }); + }; + } + } +}); + +/** + * @ngdoc directive + * @name ngHref + * @restrict A + * @priority 99 + * + * @description + * Using Angular markup like `{{hash}}` in an href attribute will + * make the link go to the wrong URL if the user clicks it before + * Angular has a chance to replace the `{{hash}}` markup with its + * value. Until Angular replaces the markup the link will be broken + * and will most likely return a 404 error. + * + * The `ngHref` directive solves this problem. + * + * The wrong way to write it: + * ```html + * + * ``` + * + * The correct way to write it: + * ```html + * + * ``` + * + * @element A + * @param {template} ngHref any string which can contain `{{}}` markup. + * + * @example + * This example shows various combinations of `href`, `ng-href` and `ng-click` attributes + * in links and their different behaviors: + + +
+
link 1 (link, don't reload)
+ link 2 (link, don't reload)
+ link 3 (link, reload!)
+ anchor (link, don't reload)
+ anchor (no link)
+ link (link, change location) +
+ + it('should execute ng-click but not reload when href without value', function() { + element(by.id('link-1')).click(); + expect(element(by.model('value')).getAttribute('value')).toEqual('1'); + expect(element(by.id('link-1')).getAttribute('href')).toBe(''); + }); + + it('should execute ng-click but not reload when href empty string', function() { + element(by.id('link-2')).click(); + expect(element(by.model('value')).getAttribute('value')).toEqual('2'); + expect(element(by.id('link-2')).getAttribute('href')).toBe(''); + }); + + it('should execute ng-click and change url when ng-href specified', function() { + expect(element(by.id('link-3')).getAttribute('href')).toMatch(/\/123$/); + + element(by.id('link-3')).click(); + + // At this point, we navigate away from an Angular page, so we need + // to use browser.driver to get the base webdriver. + + browser.wait(function() { + return browser.driver.getCurrentUrl().then(function(url) { + return url.match(/\/123$/); + }); + }, 1000, 'page should navigate to /123'); + }); + + xit('should execute ng-click but not reload when href empty string and name specified', function() { + element(by.id('link-4')).click(); + expect(element(by.model('value')).getAttribute('value')).toEqual('4'); + expect(element(by.id('link-4')).getAttribute('href')).toBe(''); + }); + + it('should execute ng-click but not reload when no href but name specified', function() { + element(by.id('link-5')).click(); + expect(element(by.model('value')).getAttribute('value')).toEqual('5'); + expect(element(by.id('link-5')).getAttribute('href')).toBe(null); + }); + + it('should only change url when only ng-href', function() { + element(by.model('value')).clear(); + element(by.model('value')).sendKeys('6'); + expect(element(by.id('link-6')).getAttribute('href')).toMatch(/\/6$/); + + element(by.id('link-6')).click(); + + // At this point, we navigate away from an Angular page, so we need + // to use browser.driver to get the base webdriver. + browser.wait(function() { + return browser.driver.getCurrentUrl().then(function(url) { + return url.match(/\/6$/); + }); + }, 1000, 'page should navigate to /6'); + }); + + + */ + +/** + * @ngdoc directive + * @name ngSrc + * @restrict A + * @priority 99 + * + * @description + * Using Angular markup like `{{hash}}` in a `src` attribute doesn't + * work right: The browser will fetch from the URL with the literal + * text `{{hash}}` until Angular replaces the expression inside + * `{{hash}}`. The `ngSrc` directive solves this problem. + * + * The buggy way to write it: + * ```html + * + * ``` + * + * The correct way to write it: + * ```html + * + * ``` + * + * @element IMG + * @param {template} ngSrc any string which can contain `{{}}` markup. + */ + +/** + * @ngdoc directive + * @name ngSrcset + * @restrict A + * @priority 99 + * + * @description + * Using Angular markup like `{{hash}}` in a `srcset` attribute doesn't + * work right: The browser will fetch from the URL with the literal + * text `{{hash}}` until Angular replaces the expression inside + * `{{hash}}`. The `ngSrcset` directive solves this problem. + * + * The buggy way to write it: + * ```html + * + * ``` + * + * The correct way to write it: + * ```html + * + * ``` + * + * @element IMG + * @param {template} ngSrcset any string which can contain `{{}}` markup. + */ + +/** + * @ngdoc directive + * @name ngDisabled + * @restrict A + * @priority 100 + * + * @description + * + * The following markup will make the button enabled on Chrome/Firefox but not on IE8 and older IEs: + * ```html + *
+ * + *
+ * ``` + * + * The HTML specification does not require browsers to preserve the values of boolean attributes + * such as disabled. (Their presence means true and their absence means false.) + * If we put an Angular interpolation expression into such an attribute then the + * binding information would be lost when the browser removes the attribute. + * The `ngDisabled` directive solves this problem for the `disabled` attribute. + * This complementary directive is not removed by the browser and so provides + * a permanent reliable place to store the binding information. + * + * @example + + + Click me to toggle:
+ +
+ + it('should toggle button', function() { + expect(element(by.css('button')).getAttribute('disabled')).toBeFalsy(); + element(by.model('checked')).click(); + expect(element(by.css('button')).getAttribute('disabled')).toBeTruthy(); + }); + +
+ * + * @element INPUT + * @param {expression} ngDisabled If the {@link guide/expression expression} is truthy, + * then special attribute "disabled" will be set on the element + */ + + +/** + * @ngdoc directive + * @name ngChecked + * @restrict A + * @priority 100 + * + * @description + * The HTML specification does not require browsers to preserve the values of boolean attributes + * such as checked. (Their presence means true and their absence means false.) + * If we put an Angular interpolation expression into such an attribute then the + * binding information would be lost when the browser removes the attribute. + * The `ngChecked` directive solves this problem for the `checked` attribute. + * This complementary directive is not removed by the browser and so provides + * a permanent reliable place to store the binding information. + * @example + + + Check me to check both:
+ +
+ + it('should check both checkBoxes', function() { + expect(element(by.id('checkSlave')).getAttribute('checked')).toBeFalsy(); + element(by.model('master')).click(); + expect(element(by.id('checkSlave')).getAttribute('checked')).toBeTruthy(); + }); + +
+ * + * @element INPUT + * @param {expression} ngChecked If the {@link guide/expression expression} is truthy, + * then special attribute "checked" will be set on the element + */ + + +/** + * @ngdoc directive + * @name ngReadonly + * @restrict A + * @priority 100 + * + * @description + * The HTML specification does not require browsers to preserve the values of boolean attributes + * such as readonly. (Their presence means true and their absence means false.) + * If we put an Angular interpolation expression into such an attribute then the + * binding information would be lost when the browser removes the attribute. + * The `ngReadonly` directive solves this problem for the `readonly` attribute. + * This complementary directive is not removed by the browser and so provides + * a permanent reliable place to store the binding information. + * @example + + + Check me to make text readonly:
+ +
+ + it('should toggle readonly attr', function() { + expect(element(by.css('[type="text"]')).getAttribute('readonly')).toBeFalsy(); + element(by.model('checked')).click(); + expect(element(by.css('[type="text"]')).getAttribute('readonly')).toBeTruthy(); + }); + +
+ * + * @element INPUT + * @param {expression} ngReadonly If the {@link guide/expression expression} is truthy, + * then special attribute "readonly" will be set on the element + */ + + +/** + * @ngdoc directive + * @name ngSelected + * @restrict A + * @priority 100 + * + * @description + * The HTML specification does not require browsers to preserve the values of boolean attributes + * such as selected. (Their presence means true and their absence means false.) + * If we put an Angular interpolation expression into such an attribute then the + * binding information would be lost when the browser removes the attribute. + * The `ngSelected` directive solves this problem for the `selected` attribute. + * This complementary directive is not removed by the browser and so provides + * a permanent reliable place to store the binding information. + * + * @example + + + Check me to select:
+ +
+ + it('should select Greetings!', function() { + expect(element(by.id('greet')).getAttribute('selected')).toBeFalsy(); + element(by.model('selected')).click(); + expect(element(by.id('greet')).getAttribute('selected')).toBeTruthy(); + }); + +
+ * + * @element OPTION + * @param {expression} ngSelected If the {@link guide/expression expression} is truthy, + * then special attribute "selected" will be set on the element + */ + +/** + * @ngdoc directive + * @name ngOpen + * @restrict A + * @priority 100 + * + * @description + * The HTML specification does not require browsers to preserve the values of boolean attributes + * such as open. (Their presence means true and their absence means false.) + * If we put an Angular interpolation expression into such an attribute then the + * binding information would be lost when the browser removes the attribute. + * The `ngOpen` directive solves this problem for the `open` attribute. + * This complementary directive is not removed by the browser and so provides + * a permanent reliable place to store the binding information. + * @example + + + Check me check multiple:
+
+ Show/Hide me +
+
+ + it('should toggle open', function() { + expect(element(by.id('details')).getAttribute('open')).toBeFalsy(); + element(by.model('open')).click(); + expect(element(by.id('details')).getAttribute('open')).toBeTruthy(); + }); + +
+ * + * @element DETAILS + * @param {expression} ngOpen If the {@link guide/expression expression} is truthy, + * then special attribute "open" will be set on the element + */ + +var ngAttributeAliasDirectives = {}; + + +// boolean attrs are evaluated +forEach(BOOLEAN_ATTR, function(propName, attrName) { + // binding to multiple is not supported + if (propName == "multiple") return; + + var normalized = directiveNormalize('ng-' + attrName); + ngAttributeAliasDirectives[normalized] = function() { + return { + priority: 100, + link: function(scope, element, attr) { + scope.$watch(attr[normalized], function ngBooleanAttrWatchAction(value) { + attr.$set(attrName, !!value); + }); + } + }; + }; +}); + + +// ng-src, ng-srcset, ng-href are interpolated +forEach(['src', 'srcset', 'href'], function(attrName) { + var normalized = directiveNormalize('ng-' + attrName); + ngAttributeAliasDirectives[normalized] = function() { + return { + priority: 99, // it needs to run after the attributes are interpolated + link: function(scope, element, attr) { + var propName = attrName, + name = attrName; + + if (attrName === 'href' && + toString.call(element.prop('href')) === '[object SVGAnimatedString]') { + name = 'xlinkHref'; + attr.$attr[name] = 'xlink:href'; + propName = null; + } + + attr.$observe(normalized, function(value) { + if (!value) + return; + + attr.$set(name, value); + + // on IE, if "ng:src" directive declaration is used and "src" attribute doesn't exist + // then calling element.setAttribute('src', 'foo') doesn't do anything, so we need + // to set the property as well to achieve the desired effect. + // we use attr[attrName] value since $set can sanitize the url. + if (msie && propName) element.prop(propName, attr[name]); + }); + } + }; + }; +}); + +/* global -nullFormCtrl */ +var nullFormCtrl = { + $addControl: noop, + $removeControl: noop, + $setValidity: noop, + $setDirty: noop, + $setPristine: noop +}; + +/** + * @ngdoc type + * @name form.FormController + * + * @property {boolean} $pristine True if user has not interacted with the form yet. + * @property {boolean} $dirty True if user has already interacted with the form. + * @property {boolean} $valid True if all of the containing forms and controls are valid. + * @property {boolean} $invalid True if at least one containing control or form is invalid. + * + * @property {Object} $error Is an object hash, containing references to all invalid controls or + * forms, where: + * + * - keys are validation tokens (error names), + * - values are arrays of controls or forms that are invalid for given error name. + * + * + * Built-in validation tokens: + * + * - `email` + * - `max` + * - `maxlength` + * - `min` + * - `minlength` + * - `number` + * - `pattern` + * - `required` + * - `url` + * + * @description + * `FormController` keeps track of all its controls and nested forms as well as the state of them, + * such as being valid/invalid or dirty/pristine. + * + * Each {@link ng.directive:form form} directive creates an instance + * of `FormController`. + * + */ +//asks for $scope to fool the BC controller module +FormController.$inject = ['$element', '$attrs', '$scope', '$animate']; +function FormController(element, attrs, $scope, $animate) { + var form = this, + parentForm = element.parent().controller('form') || nullFormCtrl, + invalidCount = 0, // used to easily determine if we are valid + errors = form.$error = {}, + controls = []; + + // init state + form.$name = attrs.name || attrs.ngForm; + form.$dirty = false; + form.$pristine = true; + form.$valid = true; + form.$invalid = false; + + parentForm.$addControl(form); + + // Setup initial state of the control + element.addClass(PRISTINE_CLASS); + toggleValidCss(true); + + // convenience method for easy toggling of classes + function toggleValidCss(isValid, validationErrorKey) { + validationErrorKey = validationErrorKey ? '-' + snake_case(validationErrorKey, '-') : ''; + $animate.removeClass(element, (isValid ? INVALID_CLASS : VALID_CLASS) + validationErrorKey); + $animate.addClass(element, (isValid ? VALID_CLASS : INVALID_CLASS) + validationErrorKey); + } + + /** + * @ngdoc method + * @name form.FormController#$addControl + * + * @description + * Register a control with the form. + * + * Input elements using ngModelController do this automatically when they are linked. + */ + form.$addControl = function(control) { + // Breaking change - before, inputs whose name was "hasOwnProperty" were quietly ignored + // and not added to the scope. Now we throw an error. + assertNotHasOwnProperty(control.$name, 'input'); + controls.push(control); + + if (control.$name) { + form[control.$name] = control; + } + }; + + /** + * @ngdoc method + * @name form.FormController#$removeControl + * + * @description + * Deregister a control from the form. + * + * Input elements using ngModelController do this automatically when they are destroyed. + */ + form.$removeControl = function(control) { + if (control.$name && form[control.$name] === control) { + delete form[control.$name]; + } + forEach(errors, function(queue, validationToken) { + form.$setValidity(validationToken, true, control); + }); + + arrayRemove(controls, control); + }; + + /** + * @ngdoc method + * @name form.FormController#$setValidity + * + * @description + * Sets the validity of a form control. + * + * This method will also propagate to parent forms. + */ + form.$setValidity = function(validationToken, isValid, control) { + var queue = errors[validationToken]; + + if (isValid) { + if (queue) { + arrayRemove(queue, control); + if (!queue.length) { + invalidCount--; + if (!invalidCount) { + toggleValidCss(isValid); + form.$valid = true; + form.$invalid = false; + } + errors[validationToken] = false; + toggleValidCss(true, validationToken); + parentForm.$setValidity(validationToken, true, form); + } + } + + } else { + if (!invalidCount) { + toggleValidCss(isValid); + } + if (queue) { + if (includes(queue, control)) return; + } else { + errors[validationToken] = queue = []; + invalidCount++; + toggleValidCss(false, validationToken); + parentForm.$setValidity(validationToken, false, form); + } + queue.push(control); + + form.$valid = false; + form.$invalid = true; + } + }; + + /** + * @ngdoc method + * @name form.FormController#$setDirty + * + * @description + * Sets the form to a dirty state. + * + * This method can be called to add the 'ng-dirty' class and set the form to a dirty + * state (ng-dirty class). This method will also propagate to parent forms. + */ + form.$setDirty = function() { + $animate.removeClass(element, PRISTINE_CLASS); + $animate.addClass(element, DIRTY_CLASS); + form.$dirty = true; + form.$pristine = false; + parentForm.$setDirty(); + }; + + /** + * @ngdoc method + * @name form.FormController#$setPristine + * + * @description + * Sets the form to its pristine state. + * + * This method can be called to remove the 'ng-dirty' class and set the form to its pristine + * state (ng-pristine class). This method will also propagate to all the controls contained + * in this form. + * + * Setting a form back to a pristine state is often useful when we want to 'reuse' a form after + * saving or resetting it. + */ + form.$setPristine = function () { + $animate.removeClass(element, DIRTY_CLASS); + $animate.addClass(element, PRISTINE_CLASS); + form.$dirty = false; + form.$pristine = true; + forEach(controls, function(control) { + control.$setPristine(); + }); + }; +} + + +/** + * @ngdoc directive + * @name ngForm + * @restrict EAC + * + * @description + * Nestable alias of {@link ng.directive:form `form`} directive. HTML + * does not allow nesting of form elements. It is useful to nest forms, for example if the validity of a + * sub-group of controls needs to be determined. + * + * Note: the purpose of `ngForm` is to group controls, + * but not to be a replacement for the `
` tag with all of its capabilities + * (e.g. posting to the server, ...). + * + * @param {string=} ngForm|name Name of the form. If specified, the form controller will be published into + * related scope, under this name. + * + */ + + /** + * @ngdoc directive + * @name form + * @restrict E + * + * @description + * Directive that instantiates + * {@link form.FormController FormController}. + * + * If the `name` attribute is specified, the form controller is published onto the current scope under + * this name. + * + * # Alias: {@link ng.directive:ngForm `ngForm`} + * + * In Angular forms can be nested. This means that the outer form is valid when all of the child + * forms are valid as well. However, browsers do not allow nesting of `` elements, so + * Angular provides the {@link ng.directive:ngForm `ngForm`} directive which behaves identically to + * `` but can be nested. This allows you to have nested forms, which is very useful when + * using Angular validation directives in forms that are dynamically generated using the + * {@link ng.directive:ngRepeat `ngRepeat`} directive. Since you cannot dynamically generate the `name` + * attribute of input elements using interpolation, you have to wrap each set of repeated inputs in an + * `ngForm` directive and nest these in an outer `form` element. + * + * + * # CSS classes + * - `ng-valid` is set if the form is valid. + * - `ng-invalid` is set if the form is invalid. + * - `ng-pristine` is set if the form is pristine. + * - `ng-dirty` is set if the form is dirty. + * + * Keep in mind that ngAnimate can detect each of these classes when added and removed. + * + * + * # Submitting a form and preventing the default action + * + * Since the role of forms in client-side Angular applications is different than in classical + * roundtrip apps, it is desirable for the browser not to translate the form submission into a full + * page reload that sends the data to the server. Instead some javascript logic should be triggered + * to handle the form submission in an application-specific way. + * + * For this reason, Angular prevents the default action (form submission to the server) unless the + * `` element has an `action` attribute specified. + * + * You can use one of the following two ways to specify what javascript method should be called when + * a form is submitted: + * + * - {@link ng.directive:ngSubmit ngSubmit} directive on the form element + * - {@link ng.directive:ngClick ngClick} directive on the first + * button or input field of type submit (input[type=submit]) + * + * To prevent double execution of the handler, use only one of the {@link ng.directive:ngSubmit ngSubmit} + * or {@link ng.directive:ngClick ngClick} directives. + * This is because of the following form submission rules in the HTML specification: + * + * - If a form has only one input field then hitting enter in this field triggers form submit + * (`ngSubmit`) + * - if a form has 2+ input fields and no buttons or input[type=submit] then hitting enter + * doesn't trigger submit + * - if a form has one or more input fields and one or more buttons or input[type=submit] then + * hitting enter in any of the input fields will trigger the click handler on the *first* button or + * input[type=submit] (`ngClick`) *and* a submit handler on the enclosing form (`ngSubmit`) + * + * @param {string=} name Name of the form. If specified, the form controller will be published into + * related scope, under this name. + * + * ## Animation Hooks + * + * Animations in ngForm are triggered when any of the associated CSS classes are added and removed. + * These classes are: `.ng-pristine`, `.ng-dirty`, `.ng-invalid` and `.ng-valid` as well as any + * other validations that are performed within the form. Animations in ngForm are similar to how + * they work in ngClass and animations can be hooked into using CSS transitions, keyframes as well + * as JS animations. + * + * The following example shows a simple way to utilize CSS transitions to style a form element + * that has been rendered as invalid after it has been validated: + * + *
+ * //be sure to include ngAnimate as a module to hook into more
+ * //advanced animations
+ * .my-form {
+ *   transition:0.5s linear all;
+ *   background: white;
+ * }
+ * .my-form.ng-invalid {
+ *   background: red;
+ *   color:white;
+ * }
+ * 
+ * + * @example + + + + + + userType: + Required!
+ userType = {{userType}}
+ myForm.input.$valid = {{myForm.input.$valid}}
+ myForm.input.$error = {{myForm.input.$error}}
+ myForm.$valid = {{myForm.$valid}}
+ myForm.$error.required = {{!!myForm.$error.required}}
+ +
+ + it('should initialize to model', function() { + var userType = element(by.binding('userType')); + var valid = element(by.binding('myForm.input.$valid')); + + expect(userType.getText()).toContain('guest'); + expect(valid.getText()).toContain('true'); + }); + + it('should be invalid if empty', function() { + var userType = element(by.binding('userType')); + var valid = element(by.binding('myForm.input.$valid')); + var userInput = element(by.model('userType')); + + userInput.clear(); + userInput.sendKeys(''); + + expect(userType.getText()).toEqual('userType ='); + expect(valid.getText()).toContain('false'); + }); + +
+ * + */ +var formDirectiveFactory = function(isNgForm) { + return ['$timeout', function($timeout) { + var formDirective = { + name: 'form', + restrict: isNgForm ? 'EAC' : 'E', + controller: FormController, + compile: function() { + return { + pre: function(scope, formElement, attr, controller) { + if (!attr.action) { + // we can't use jq events because if a form is destroyed during submission the default + // action is not prevented. see #1238 + // + // IE 9 is not affected because it doesn't fire a submit event and try to do a full + // page reload if the form was destroyed by submission of the form via a click handler + // on a button in the form. Looks like an IE9 specific bug. + var preventDefaultListener = function(event) { + event.preventDefault + ? event.preventDefault() + : event.returnValue = false; // IE + }; + + addEventListenerFn(formElement[0], 'submit', preventDefaultListener); + + // unregister the preventDefault listener so that we don't not leak memory but in a + // way that will achieve the prevention of the default action. + formElement.on('$destroy', function() { + $timeout(function() { + removeEventListenerFn(formElement[0], 'submit', preventDefaultListener); + }, 0, false); + }); + } + + var parentFormCtrl = formElement.parent().controller('form'), + alias = attr.name || attr.ngForm; + + if (alias) { + setter(scope, alias, controller, alias); + } + if (parentFormCtrl) { + formElement.on('$destroy', function() { + parentFormCtrl.$removeControl(controller); + if (alias) { + setter(scope, alias, undefined, alias); + } + extend(controller, nullFormCtrl); //stop propagating child destruction handlers upwards + }); + } + } + }; + } + }; + + return formDirective; + }]; +}; + +var formDirective = formDirectiveFactory(); +var ngFormDirective = formDirectiveFactory(true); + +/* global + + -VALID_CLASS, + -INVALID_CLASS, + -PRISTINE_CLASS, + -DIRTY_CLASS +*/ + +var URL_REGEXP = /^(ftp|http|https):\/\/(\w+:{0,1}\w*@)?(\S+)(:[0-9]+)?(\/|\/([\w#!:.?+=&%@!\-\/]))?$/; +var EMAIL_REGEXP = /^[a-z0-9!#$%&'*+/=?^_`{|}~.-]+@[a-z0-9-]+(\.[a-z0-9-]+)*$/i; +var NUMBER_REGEXP = /^\s*(\-|\+)?(\d+|(\d*(\.\d*)))\s*$/; + +var inputType = { + + /** + * @ngdoc input + * @name input[text] + * + * @description + * Standard HTML text input with angular data binding. + * + * @param {string} ngModel Assignable angular expression to data-bind to. + * @param {string=} name Property name of the form under which the control is published. + * @param {string=} required Adds `required` validation error key if the value is not entered. + * @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to + * the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of + * `required` when you want to data-bind to the `required` attribute. + * @param {number=} ngMinlength Sets `minlength` validation error key if the value is shorter than + * minlength. + * @param {number=} ngMaxlength Sets `maxlength` validation error key if the value is longer than + * maxlength. + * @param {string=} ngPattern Sets `pattern` validation error key if the value does not match the + * RegExp pattern expression. Expected value is `/regexp/` for inline patterns or `regexp` for + * patterns defined as scope expressions. + * @param {string=} ngChange Angular expression to be executed when input changes due to user + * interaction with the input element. + * @param {boolean=} [ngTrim=true] If set to false Angular will not automatically trim the input. + * + * @example + + + +
+ Single word: + + Required! + + Single word only! + + text = {{text}}
+ myForm.input.$valid = {{myForm.input.$valid}}
+ myForm.input.$error = {{myForm.input.$error}}
+ myForm.$valid = {{myForm.$valid}}
+ myForm.$error.required = {{!!myForm.$error.required}}
+
+
+ + var text = element(by.binding('text')); + var valid = element(by.binding('myForm.input.$valid')); + var input = element(by.model('text')); + + it('should initialize to model', function() { + expect(text.getText()).toContain('guest'); + expect(valid.getText()).toContain('true'); + }); + + it('should be invalid if empty', function() { + input.clear(); + input.sendKeys(''); + + expect(text.getText()).toEqual('text ='); + expect(valid.getText()).toContain('false'); + }); + + it('should be invalid if multi word', function() { + input.clear(); + input.sendKeys('hello world'); + + expect(valid.getText()).toContain('false'); + }); + +
+ */ + 'text': textInputType, + + + /** + * @ngdoc input + * @name input[number] + * + * @description + * Text input with number validation and transformation. Sets the `number` validation + * error if not a valid number. + * + * @param {string} ngModel Assignable angular expression to data-bind to. + * @param {string=} name Property name of the form under which the control is published. + * @param {string=} min Sets the `min` validation error key if the value entered is less than `min`. + * @param {string=} max Sets the `max` validation error key if the value entered is greater than `max`. + * @param {string=} required Sets `required` validation error key if the value is not entered. + * @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to + * the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of + * `required` when you want to data-bind to the `required` attribute. + * @param {number=} ngMinlength Sets `minlength` validation error key if the value is shorter than + * minlength. + * @param {number=} ngMaxlength Sets `maxlength` validation error key if the value is longer than + * maxlength. + * @param {string=} ngPattern Sets `pattern` validation error key if the value does not match the + * RegExp pattern expression. Expected value is `/regexp/` for inline patterns or `regexp` for + * patterns defined as scope expressions. + * @param {string=} ngChange Angular expression to be executed when input changes due to user + * interaction with the input element. + * + * @example + + + +
+ Number: + + Required! + + Not valid number! + value = {{value}}
+ myForm.input.$valid = {{myForm.input.$valid}}
+ myForm.input.$error = {{myForm.input.$error}}
+ myForm.$valid = {{myForm.$valid}}
+ myForm.$error.required = {{!!myForm.$error.required}}
+
+
+ + var value = element(by.binding('value')); + var valid = element(by.binding('myForm.input.$valid')); + var input = element(by.model('value')); + + it('should initialize to model', function() { + expect(value.getText()).toContain('12'); + expect(valid.getText()).toContain('true'); + }); + + it('should be invalid if empty', function() { + input.clear(); + input.sendKeys(''); + expect(value.getText()).toEqual('value ='); + expect(valid.getText()).toContain('false'); + }); + + it('should be invalid if over max', function() { + input.clear(); + input.sendKeys('123'); + expect(value.getText()).toEqual('value ='); + expect(valid.getText()).toContain('false'); + }); + +
+ */ + 'number': numberInputType, + + + /** + * @ngdoc input + * @name input[url] + * + * @description + * Text input with URL validation. Sets the `url` validation error key if the content is not a + * valid URL. + * + * @param {string} ngModel Assignable angular expression to data-bind to. + * @param {string=} name Property name of the form under which the control is published. + * @param {string=} required Sets `required` validation error key if the value is not entered. + * @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to + * the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of + * `required` when you want to data-bind to the `required` attribute. + * @param {number=} ngMinlength Sets `minlength` validation error key if the value is shorter than + * minlength. + * @param {number=} ngMaxlength Sets `maxlength` validation error key if the value is longer than + * maxlength. + * @param {string=} ngPattern Sets `pattern` validation error key if the value does not match the + * RegExp pattern expression. Expected value is `/regexp/` for inline patterns or `regexp` for + * patterns defined as scope expressions. + * @param {string=} ngChange Angular expression to be executed when input changes due to user + * interaction with the input element. + * + * @example + + + +
+ URL: + + Required! + + Not valid url! + text = {{text}}
+ myForm.input.$valid = {{myForm.input.$valid}}
+ myForm.input.$error = {{myForm.input.$error}}
+ myForm.$valid = {{myForm.$valid}}
+ myForm.$error.required = {{!!myForm.$error.required}}
+ myForm.$error.url = {{!!myForm.$error.url}}
+
+
+ + var text = element(by.binding('text')); + var valid = element(by.binding('myForm.input.$valid')); + var input = element(by.model('text')); + + it('should initialize to model', function() { + expect(text.getText()).toContain('http://google.com'); + expect(valid.getText()).toContain('true'); + }); + + it('should be invalid if empty', function() { + input.clear(); + input.sendKeys(''); + + expect(text.getText()).toEqual('text ='); + expect(valid.getText()).toContain('false'); + }); + + it('should be invalid if not url', function() { + input.clear(); + input.sendKeys('box'); + + expect(valid.getText()).toContain('false'); + }); + +
+ */ + 'url': urlInputType, + + + /** + * @ngdoc input + * @name input[email] + * + * @description + * Text input with email validation. Sets the `email` validation error key if not a valid email + * address. + * + * @param {string} ngModel Assignable angular expression to data-bind to. + * @param {string=} name Property name of the form under which the control is published. + * @param {string=} required Sets `required` validation error key if the value is not entered. + * @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to + * the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of + * `required` when you want to data-bind to the `required` attribute. + * @param {number=} ngMinlength Sets `minlength` validation error key if the value is shorter than + * minlength. + * @param {number=} ngMaxlength Sets `maxlength` validation error key if the value is longer than + * maxlength. + * @param {string=} ngPattern Sets `pattern` validation error key if the value does not match the + * RegExp pattern expression. Expected value is `/regexp/` for inline patterns or `regexp` for + * patterns defined as scope expressions. + * @param {string=} ngChange Angular expression to be executed when input changes due to user + * interaction with the input element. + * + * @example + + + +
+ Email: + + Required! + + Not valid email! + text = {{text}}
+ myForm.input.$valid = {{myForm.input.$valid}}
+ myForm.input.$error = {{myForm.input.$error}}
+ myForm.$valid = {{myForm.$valid}}
+ myForm.$error.required = {{!!myForm.$error.required}}
+ myForm.$error.email = {{!!myForm.$error.email}}
+
+
+ + var text = element(by.binding('text')); + var valid = element(by.binding('myForm.input.$valid')); + var input = element(by.model('text')); + + it('should initialize to model', function() { + expect(text.getText()).toContain('me@example.com'); + expect(valid.getText()).toContain('true'); + }); + + it('should be invalid if empty', function() { + input.clear(); + input.sendKeys(''); + expect(text.getText()).toEqual('text ='); + expect(valid.getText()).toContain('false'); + }); + + it('should be invalid if not email', function() { + input.clear(); + input.sendKeys('xxx'); + + expect(valid.getText()).toContain('false'); + }); + +
+ */ + 'email': emailInputType, + + + /** + * @ngdoc input + * @name input[radio] + * + * @description + * HTML radio button. + * + * @param {string} ngModel Assignable angular expression to data-bind to. + * @param {string} value The value to which the expression should be set when selected. + * @param {string=} name Property name of the form under which the control is published. + * @param {string=} ngChange Angular expression to be executed when input changes due to user + * interaction with the input element. + * @param {string} ngValue Angular expression which sets the value to which the expression should + * be set when selected. + * + * @example + + + +
+ Red
+ Green
+ Blue
+ color = {{color | json}}
+
+ Note that `ng-value="specialValue"` sets radio item's value to be the value of `$scope.specialValue`. +
+ + it('should change state', function() { + var color = element(by.binding('color')); + + expect(color.getText()).toContain('blue'); + + element.all(by.model('color')).get(0).click(); + + expect(color.getText()).toContain('red'); + }); + +
+ */ + 'radio': radioInputType, + + + /** + * @ngdoc input + * @name input[checkbox] + * + * @description + * HTML checkbox. + * + * @param {string} ngModel Assignable angular expression to data-bind to. + * @param {string=} name Property name of the form under which the control is published. + * @param {string=} ngTrueValue The value to which the expression should be set when selected. + * @param {string=} ngFalseValue The value to which the expression should be set when not selected. + * @param {string=} ngChange Angular expression to be executed when input changes due to user + * interaction with the input element. + * + * @example + + + +
+ Value1:
+ Value2:
+ value1 = {{value1}}
+ value2 = {{value2}}
+
+
+ + it('should change state', function() { + var value1 = element(by.binding('value1')); + var value2 = element(by.binding('value2')); + + expect(value1.getText()).toContain('true'); + expect(value2.getText()).toContain('YES'); + + element(by.model('value1')).click(); + element(by.model('value2')).click(); + + expect(value1.getText()).toContain('false'); + expect(value2.getText()).toContain('NO'); + }); + +
+ */ + 'checkbox': checkboxInputType, + + 'hidden': noop, + 'button': noop, + 'submit': noop, + 'reset': noop, + 'file': noop +}; + +// A helper function to call $setValidity and return the value / undefined, +// a pattern that is repeated a lot in the input validation logic. +function validate(ctrl, validatorName, validity, value){ + ctrl.$setValidity(validatorName, validity); + return validity ? value : undefined; +} + + +function addNativeHtml5Validators(ctrl, validatorName, element) { + var validity = element.prop('validity'); + if (isObject(validity)) { + var validator = function(value) { + // Don't overwrite previous validation, don't consider valueMissing to apply (ng-required can + // perform the required validation) + if (!ctrl.$error[validatorName] && (validity.badInput || validity.customError || + validity.typeMismatch) && !validity.valueMissing) { + ctrl.$setValidity(validatorName, false); + return; + } + return value; + }; + ctrl.$parsers.push(validator); + } +} + +function textInputType(scope, element, attr, ctrl, $sniffer, $browser) { + var validity = element.prop('validity'); + var placeholder = element[0].placeholder, noevent = {}; + + // In composition mode, users are still inputing intermediate text buffer, + // hold the listener until composition is done. + // More about composition events: https://developer.mozilla.org/en-US/docs/Web/API/CompositionEvent + if (!$sniffer.android) { + var composing = false; + + element.on('compositionstart', function(data) { + composing = true; + }); + + element.on('compositionend', function() { + composing = false; + listener(); + }); + } + + var listener = function(ev) { + if (composing) return; + var value = element.val(); + + // IE (11 and under) seem to emit an 'input' event if the placeholder value changes. + // We don't want to dirty the value when this happens, so we abort here. Unfortunately, + // IE also sends input events for other non-input-related things, (such as focusing on a + // form control), so this change is not entirely enough to solve this. + if (msie && (ev || noevent).type === 'input' && element[0].placeholder !== placeholder) { + placeholder = element[0].placeholder; + return; + } + + // By default we will trim the value + // If the attribute ng-trim exists we will avoid trimming + // e.g. + if (toBoolean(attr.ngTrim || 'T')) { + value = trim(value); + } + + if (ctrl.$viewValue !== value || + // If the value is still empty/falsy, and there is no `required` error, run validators + // again. This enables HTML5 constraint validation errors to affect Angular validation + // even when the first character entered causes an error. + (validity && value === '' && !validity.valueMissing)) { + if (scope.$$phase) { + ctrl.$setViewValue(value); + } else { + scope.$apply(function() { + ctrl.$setViewValue(value); + }); + } + } + }; + + // if the browser does support "input" event, we are fine - except on IE9 which doesn't fire the + // input event on backspace, delete or cut + if ($sniffer.hasEvent('input')) { + element.on('input', listener); + } else { + var timeout; + + var deferListener = function() { + if (!timeout) { + timeout = $browser.defer(function() { + listener(); + timeout = null; + }); + } + }; + + element.on('keydown', function(event) { + var key = event.keyCode; + + // ignore + // command modifiers arrows + if (key === 91 || (15 < key && key < 19) || (37 <= key && key <= 40)) return; + + deferListener(); + }); + + // if user modifies input value using context menu in IE, we need "paste" and "cut" events to catch it + if ($sniffer.hasEvent('paste')) { + element.on('paste cut', deferListener); + } + } + + // if user paste into input using mouse on older browser + // or form autocomplete on newer browser, we need "change" event to catch it + element.on('change', listener); + + ctrl.$render = function() { + element.val(ctrl.$isEmpty(ctrl.$viewValue) ? '' : ctrl.$viewValue); + }; + + // pattern validator + var pattern = attr.ngPattern, + patternValidator, + match; + + if (pattern) { + var validateRegex = function(regexp, value) { + return validate(ctrl, 'pattern', ctrl.$isEmpty(value) || regexp.test(value), value); + }; + match = pattern.match(/^\/(.*)\/([gim]*)$/); + if (match) { + pattern = new RegExp(match[1], match[2]); + patternValidator = function(value) { + return validateRegex(pattern, value); + }; + } else { + patternValidator = function(value) { + var patternObj = scope.$eval(pattern); + + if (!patternObj || !patternObj.test) { + throw minErr('ngPattern')('noregexp', + 'Expected {0} to be a RegExp but was {1}. Element: {2}', pattern, + patternObj, startingTag(element)); + } + return validateRegex(patternObj, value); + }; + } + + ctrl.$formatters.push(patternValidator); + ctrl.$parsers.push(patternValidator); + } + + // min length validator + if (attr.ngMinlength) { + var minlength = int(attr.ngMinlength); + var minLengthValidator = function(value) { + return validate(ctrl, 'minlength', ctrl.$isEmpty(value) || value.length >= minlength, value); + }; + + ctrl.$parsers.push(minLengthValidator); + ctrl.$formatters.push(minLengthValidator); + } + + // max length validator + if (attr.ngMaxlength) { + var maxlength = int(attr.ngMaxlength); + var maxLengthValidator = function(value) { + return validate(ctrl, 'maxlength', ctrl.$isEmpty(value) || value.length <= maxlength, value); + }; + + ctrl.$parsers.push(maxLengthValidator); + ctrl.$formatters.push(maxLengthValidator); + } +} + +function numberInputType(scope, element, attr, ctrl, $sniffer, $browser) { + textInputType(scope, element, attr, ctrl, $sniffer, $browser); + + ctrl.$parsers.push(function(value) { + var empty = ctrl.$isEmpty(value); + if (empty || NUMBER_REGEXP.test(value)) { + ctrl.$setValidity('number', true); + return value === '' ? null : (empty ? value : parseFloat(value)); + } else { + ctrl.$setValidity('number', false); + return undefined; + } + }); + + addNativeHtml5Validators(ctrl, 'number', element); + + ctrl.$formatters.push(function(value) { + return ctrl.$isEmpty(value) ? '' : '' + value; + }); + + if (attr.min) { + var minValidator = function(value) { + var min = parseFloat(attr.min); + return validate(ctrl, 'min', ctrl.$isEmpty(value) || value >= min, value); + }; + + ctrl.$parsers.push(minValidator); + ctrl.$formatters.push(minValidator); + } + + if (attr.max) { + var maxValidator = function(value) { + var max = parseFloat(attr.max); + return validate(ctrl, 'max', ctrl.$isEmpty(value) || value <= max, value); + }; + + ctrl.$parsers.push(maxValidator); + ctrl.$formatters.push(maxValidator); + } + + ctrl.$formatters.push(function(value) { + return validate(ctrl, 'number', ctrl.$isEmpty(value) || isNumber(value), value); + }); +} + +function urlInputType(scope, element, attr, ctrl, $sniffer, $browser) { + textInputType(scope, element, attr, ctrl, $sniffer, $browser); + + var urlValidator = function(value) { + return validate(ctrl, 'url', ctrl.$isEmpty(value) || URL_REGEXP.test(value), value); + }; + + ctrl.$formatters.push(urlValidator); + ctrl.$parsers.push(urlValidator); +} + +function emailInputType(scope, element, attr, ctrl, $sniffer, $browser) { + textInputType(scope, element, attr, ctrl, $sniffer, $browser); + + var emailValidator = function(value) { + return validate(ctrl, 'email', ctrl.$isEmpty(value) || EMAIL_REGEXP.test(value), value); + }; + + ctrl.$formatters.push(emailValidator); + ctrl.$parsers.push(emailValidator); +} + +function radioInputType(scope, element, attr, ctrl) { + // make the name unique, if not defined + if (isUndefined(attr.name)) { + element.attr('name', nextUid()); + } + + element.on('click', function() { + if (element[0].checked) { + scope.$apply(function() { + ctrl.$setViewValue(attr.value); + }); + } + }); + + ctrl.$render = function() { + var value = attr.value; + element[0].checked = (value == ctrl.$viewValue); + }; + + attr.$observe('value', ctrl.$render); +} + +function checkboxInputType(scope, element, attr, ctrl) { + var trueValue = attr.ngTrueValue, + falseValue = attr.ngFalseValue; + + if (!isString(trueValue)) trueValue = true; + if (!isString(falseValue)) falseValue = false; + + element.on('click', function() { + scope.$apply(function() { + ctrl.$setViewValue(element[0].checked); + }); + }); + + ctrl.$render = function() { + element[0].checked = ctrl.$viewValue; + }; + + // Override the standard `$isEmpty` because a value of `false` means empty in a checkbox. + ctrl.$isEmpty = function(value) { + return value !== trueValue; + }; + + ctrl.$formatters.push(function(value) { + return value === trueValue; + }); + + ctrl.$parsers.push(function(value) { + return value ? trueValue : falseValue; + }); +} + + +/** + * @ngdoc directive + * @name textarea + * @restrict E + * + * @description + * HTML textarea element control with angular data-binding. The data-binding and validation + * properties of this element are exactly the same as those of the + * {@link ng.directive:input input element}. + * + * @param {string} ngModel Assignable angular expression to data-bind to. + * @param {string=} name Property name of the form under which the control is published. + * @param {string=} required Sets `required` validation error key if the value is not entered. + * @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to + * the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of + * `required` when you want to data-bind to the `required` attribute. + * @param {number=} ngMinlength Sets `minlength` validation error key if the value is shorter than + * minlength. + * @param {number=} ngMaxlength Sets `maxlength` validation error key if the value is longer than + * maxlength. + * @param {string=} ngPattern Sets `pattern` validation error key if the value does not match the + * RegExp pattern expression. Expected value is `/regexp/` for inline patterns or `regexp` for + * patterns defined as scope expressions. + * @param {string=} ngChange Angular expression to be executed when input changes due to user + * interaction with the input element. + */ + + +/** + * @ngdoc directive + * @name input + * @restrict E + * + * @description + * HTML input element control with angular data-binding. Input control follows HTML5 input types + * and polyfills the HTML5 validation behavior for older browsers. + * + * @param {string} ngModel Assignable angular expression to data-bind to. + * @param {string=} name Property name of the form under which the control is published. + * @param {string=} required Sets `required` validation error key if the value is not entered. + * @param {boolean=} ngRequired Sets `required` attribute if set to true + * @param {number=} ngMinlength Sets `minlength` validation error key if the value is shorter than + * minlength. + * @param {number=} ngMaxlength Sets `maxlength` validation error key if the value is longer than + * maxlength. + * @param {string=} ngPattern Sets `pattern` validation error key if the value does not match the + * RegExp pattern expression. Expected value is `/regexp/` for inline patterns or `regexp` for + * patterns defined as scope expressions. + * @param {string=} ngChange Angular expression to be executed when input changes due to user + * interaction with the input element. + * + * @example + + + +
+
+ User name: + + Required!
+ Last name: + + Too short! + + Too long!
+
+
+ user = {{user}}
+ myForm.userName.$valid = {{myForm.userName.$valid}}
+ myForm.userName.$error = {{myForm.userName.$error}}
+ myForm.lastName.$valid = {{myForm.lastName.$valid}}
+ myForm.lastName.$error = {{myForm.lastName.$error}}
+ myForm.$valid = {{myForm.$valid}}
+ myForm.$error.required = {{!!myForm.$error.required}}
+ myForm.$error.minlength = {{!!myForm.$error.minlength}}
+ myForm.$error.maxlength = {{!!myForm.$error.maxlength}}
+
+
+ + var user = element(by.binding('{{user}}')); + var userNameValid = element(by.binding('myForm.userName.$valid')); + var lastNameValid = element(by.binding('myForm.lastName.$valid')); + var lastNameError = element(by.binding('myForm.lastName.$error')); + var formValid = element(by.binding('myForm.$valid')); + var userNameInput = element(by.model('user.name')); + var userLastInput = element(by.model('user.last')); + + it('should initialize to model', function() { + expect(user.getText()).toContain('{"name":"guest","last":"visitor"}'); + expect(userNameValid.getText()).toContain('true'); + expect(formValid.getText()).toContain('true'); + }); + + it('should be invalid if empty when required', function() { + userNameInput.clear(); + userNameInput.sendKeys(''); + + expect(user.getText()).toContain('{"last":"visitor"}'); + expect(userNameValid.getText()).toContain('false'); + expect(formValid.getText()).toContain('false'); + }); + + it('should be valid if empty when min length is set', function() { + userLastInput.clear(); + userLastInput.sendKeys(''); + + expect(user.getText()).toContain('{"name":"guest","last":""}'); + expect(lastNameValid.getText()).toContain('true'); + expect(formValid.getText()).toContain('true'); + }); + + it('should be invalid if less than required min length', function() { + userLastInput.clear(); + userLastInput.sendKeys('xx'); + + expect(user.getText()).toContain('{"name":"guest"}'); + expect(lastNameValid.getText()).toContain('false'); + expect(lastNameError.getText()).toContain('minlength'); + expect(formValid.getText()).toContain('false'); + }); + + it('should be invalid if longer than max length', function() { + userLastInput.clear(); + userLastInput.sendKeys('some ridiculously long name'); + + expect(user.getText()).toContain('{"name":"guest"}'); + expect(lastNameValid.getText()).toContain('false'); + expect(lastNameError.getText()).toContain('maxlength'); + expect(formValid.getText()).toContain('false'); + }); + +
+ */ +var inputDirective = ['$browser', '$sniffer', function($browser, $sniffer) { + return { + restrict: 'E', + require: '?ngModel', + link: function(scope, element, attr, ctrl) { + if (ctrl) { + (inputType[lowercase(attr.type)] || inputType.text)(scope, element, attr, ctrl, $sniffer, + $browser); + } + } + }; +}]; + +var VALID_CLASS = 'ng-valid', + INVALID_CLASS = 'ng-invalid', + PRISTINE_CLASS = 'ng-pristine', + DIRTY_CLASS = 'ng-dirty'; + +/** + * @ngdoc type + * @name ngModel.NgModelController + * + * @property {string} $viewValue Actual string value in the view. + * @property {*} $modelValue The value in the model, that the control is bound to. + * @property {Array.} $parsers Array of functions to execute, as a pipeline, whenever + the control reads value from the DOM. Each function is called, in turn, passing the value + through to the next. The last return value is used to populate the model. + Used to sanitize / convert the value as well as validation. For validation, + the parsers should update the validity state using + {@link ngModel.NgModelController#$setValidity $setValidity()}, + and return `undefined` for invalid values. + + * + * @property {Array.} $formatters Array of functions to execute, as a pipeline, whenever + the model value changes. Each function is called, in turn, passing the value through to the + next. Used to format / convert values for display in the control and validation. + * ```js + * function formatter(value) { + * if (value) { + * return value.toUpperCase(); + * } + * } + * ngModel.$formatters.push(formatter); + * ``` + * + * @property {Array.} $viewChangeListeners Array of functions to execute whenever the + * view value has changed. It is called with no arguments, and its return value is ignored. + * This can be used in place of additional $watches against the model value. + * + * @property {Object} $error An object hash with all errors as keys. + * + * @property {boolean} $pristine True if user has not interacted with the control yet. + * @property {boolean} $dirty True if user has already interacted with the control. + * @property {boolean} $valid True if there is no error. + * @property {boolean} $invalid True if at least one error on the control. + * + * @description + * + * `NgModelController` provides API for the `ng-model` directive. The controller contains + * services for data-binding, validation, CSS updates, and value formatting and parsing. It + * purposefully does not contain any logic which deals with DOM rendering or listening to + * DOM events. Such DOM related logic should be provided by other directives which make use of + * `NgModelController` for data-binding. + * + * ## Custom Control Example + * This example shows how to use `NgModelController` with a custom control to achieve + * data-binding. Notice how different directives (`contenteditable`, `ng-model`, and `required`) + * collaborate together to achieve the desired result. + * + * Note that `contenteditable` is an HTML5 attribute, which tells the browser to let the element + * contents be edited in place by the user. This will not work on older browsers. + * + * We are using the {@link ng.service:$sce $sce} service here and include the {@link ngSanitize $sanitize} + * module to automatically remove "bad" content like inline event listener (e.g. ``). + * However, as we are using `$sce` the model can still decide to to provide unsafe content if it marks + * that content using the `$sce` service. + * + * + + [contenteditable] { + border: 1px solid black; + background-color: white; + min-height: 20px; + } + + .ng-invalid { + border: 1px solid red; + } + + + + angular.module('customControl', ['ngSanitize']). + directive('contenteditable', ['$sce', function($sce) { + return { + restrict: 'A', // only activate on element attribute + require: '?ngModel', // get a hold of NgModelController + link: function(scope, element, attrs, ngModel) { + if(!ngModel) return; // do nothing if no ng-model + + // Specify how UI should be updated + ngModel.$render = function() { + element.html($sce.getTrustedHtml(ngModel.$viewValue || '')); + }; + + // Listen for change events to enable binding + element.on('blur keyup change', function() { + scope.$apply(read); + }); + read(); // initialize + + // Write data to the model + function read() { + var html = element.html(); + // When we clear the content editable the browser leaves a
behind + // If strip-br attribute is provided then we strip this out + if( attrs.stripBr && html == '
' ) { + html = ''; + } + ngModel.$setViewValue(html); + } + } + }; + }]); +
+ +
+
Change me!
+ Required! +
+ +
+
+ + it('should data-bind and become invalid', function() { + if (browser.params.browser == 'safari' || browser.params.browser == 'firefox') { + // SafariDriver can't handle contenteditable + // and Firefox driver can't clear contenteditables very well + return; + } + var contentEditable = element(by.css('[contenteditable]')); + var content = 'Change me!'; + + expect(contentEditable.getText()).toEqual(content); + + contentEditable.clear(); + contentEditable.sendKeys(protractor.Key.BACK_SPACE); + expect(contentEditable.getText()).toEqual(''); + expect(contentEditable.getAttribute('class')).toMatch(/ng-invalid-required/); + }); + + *
+ * + * + */ +var NgModelController = ['$scope', '$exceptionHandler', '$attrs', '$element', '$parse', '$animate', + function($scope, $exceptionHandler, $attr, $element, $parse, $animate) { + this.$viewValue = Number.NaN; + this.$modelValue = Number.NaN; + this.$parsers = []; + this.$formatters = []; + this.$viewChangeListeners = []; + this.$pristine = true; + this.$dirty = false; + this.$valid = true; + this.$invalid = false; + this.$name = $attr.name; + + var ngModelGet = $parse($attr.ngModel), + ngModelSet = ngModelGet.assign; + + if (!ngModelSet) { + throw minErr('ngModel')('nonassign', "Expression '{0}' is non-assignable. Element: {1}", + $attr.ngModel, startingTag($element)); + } + + /** + * @ngdoc method + * @name ngModel.NgModelController#$render + * + * @description + * Called when the view needs to be updated. It is expected that the user of the ng-model + * directive will implement this method. + */ + this.$render = noop; + + /** + * @ngdoc method + * @name ngModel.NgModelController#$isEmpty + * + * @description + * This is called when we need to determine if the value of the input is empty. + * + * For instance, the required directive does this to work out if the input has data or not. + * The default `$isEmpty` function checks whether the value is `undefined`, `''`, `null` or `NaN`. + * + * You can override this for input directives whose concept of being empty is different to the + * default. The `checkboxInputType` directive does this because in its case a value of `false` + * implies empty. + * + * @param {*} value Reference to check. + * @returns {boolean} True if `value` is empty. + */ + this.$isEmpty = function(value) { + return isUndefined(value) || value === '' || value === null || value !== value; + }; + + var parentForm = $element.inheritedData('$formController') || nullFormCtrl, + invalidCount = 0, // used to easily determine if we are valid + $error = this.$error = {}; // keep invalid keys here + + + // Setup initial state of the control + $element.addClass(PRISTINE_CLASS); + toggleValidCss(true); + + // convenience method for easy toggling of classes + function toggleValidCss(isValid, validationErrorKey) { + validationErrorKey = validationErrorKey ? '-' + snake_case(validationErrorKey, '-') : ''; + $animate.removeClass($element, (isValid ? INVALID_CLASS : VALID_CLASS) + validationErrorKey); + $animate.addClass($element, (isValid ? VALID_CLASS : INVALID_CLASS) + validationErrorKey); + } + + /** + * @ngdoc method + * @name ngModel.NgModelController#$setValidity + * + * @description + * Change the validity state, and notifies the form when the control changes validity. (i.e. it + * does not notify form if given validator is already marked as invalid). + * + * This method should be called by validators - i.e. the parser or formatter functions. + * + * @param {string} validationErrorKey Name of the validator. the `validationErrorKey` will assign + * to `$error[validationErrorKey]=isValid` so that it is available for data-binding. + * The `validationErrorKey` should be in camelCase and will get converted into dash-case + * for class name. Example: `myError` will result in `ng-valid-my-error` and `ng-invalid-my-error` + * class and can be bound to as `{{someForm.someControl.$error.myError}}` . + * @param {boolean} isValid Whether the current state is valid (true) or invalid (false). + */ + this.$setValidity = function(validationErrorKey, isValid) { + // Purposeful use of ! here to cast isValid to boolean in case it is undefined + // jshint -W018 + if ($error[validationErrorKey] === !isValid) return; + // jshint +W018 + + if (isValid) { + if ($error[validationErrorKey]) invalidCount--; + if (!invalidCount) { + toggleValidCss(true); + this.$valid = true; + this.$invalid = false; + } + } else { + toggleValidCss(false); + this.$invalid = true; + this.$valid = false; + invalidCount++; + } + + $error[validationErrorKey] = !isValid; + toggleValidCss(isValid, validationErrorKey); + + parentForm.$setValidity(validationErrorKey, isValid, this); + }; + + /** + * @ngdoc method + * @name ngModel.NgModelController#$setPristine + * + * @description + * Sets the control to its pristine state. + * + * This method can be called to remove the 'ng-dirty' class and set the control to its pristine + * state (ng-pristine class). + */ + this.$setPristine = function () { + this.$dirty = false; + this.$pristine = true; + $animate.removeClass($element, DIRTY_CLASS); + $animate.addClass($element, PRISTINE_CLASS); + }; + + /** + * @ngdoc method + * @name ngModel.NgModelController#$setViewValue + * + * @description + * Update the view value. + * + * This method should be called when the view value changes, typically from within a DOM event handler. + * For example {@link ng.directive:input input} and + * {@link ng.directive:select select} directives call it. + * + * It will update the $viewValue, then pass this value through each of the functions in `$parsers`, + * which includes any validators. The value that comes out of this `$parsers` pipeline, be applied to + * `$modelValue` and the **expression** specified in the `ng-model` attribute. + * + * Lastly, all the registered change listeners, in the `$viewChangeListeners` list, are called. + * + * Note that calling this function does not trigger a `$digest`. + * + * @param {string} value Value from the view. + */ + this.$setViewValue = function(value) { + this.$viewValue = value; + + // change to dirty + if (this.$pristine) { + this.$dirty = true; + this.$pristine = false; + $animate.removeClass($element, PRISTINE_CLASS); + $animate.addClass($element, DIRTY_CLASS); + parentForm.$setDirty(); + } + + forEach(this.$parsers, function(fn) { + value = fn(value); + }); + + if (this.$modelValue !== value) { + this.$modelValue = value; + ngModelSet($scope, value); + forEach(this.$viewChangeListeners, function(listener) { + try { + listener(); + } catch(e) { + $exceptionHandler(e); + } + }); + } + }; + + // model -> value + var ctrl = this; + + $scope.$watch(function ngModelWatch() { + var value = ngModelGet($scope); + + // if scope model value and ngModel value are out of sync + if (ctrl.$modelValue !== value) { + + var formatters = ctrl.$formatters, + idx = formatters.length; + + ctrl.$modelValue = value; + while(idx--) { + value = formatters[idx](value); + } + + if (ctrl.$viewValue !== value) { + ctrl.$viewValue = value; + ctrl.$render(); + } + } + + return value; + }); +}]; + + +/** + * @ngdoc directive + * @name ngModel + * + * @element input + * + * @description + * The `ngModel` directive binds an `input`,`select`, `textarea` (or custom form control) to a + * property on the scope using {@link ngModel.NgModelController NgModelController}, + * which is created and exposed by this directive. + * + * `ngModel` is responsible for: + * + * - Binding the view into the model, which other directives such as `input`, `textarea` or `select` + * require. + * - Providing validation behavior (i.e. required, number, email, url). + * - Keeping the state of the control (valid/invalid, dirty/pristine, validation errors). + * - Setting related css classes on the element (`ng-valid`, `ng-invalid`, `ng-dirty`, `ng-pristine`) including animations. + * - Registering the control with its parent {@link ng.directive:form form}. + * + * Note: `ngModel` will try to bind to the property given by evaluating the expression on the + * current scope. If the property doesn't already exist on this scope, it will be created + * implicitly and added to the scope. + * + * For best practices on using `ngModel`, see: + * + * - [https://github.com/angular/angular.js/wiki/Understanding-Scopes] + * + * For basic examples, how to use `ngModel`, see: + * + * - {@link ng.directive:input input} + * - {@link input[text] text} + * - {@link input[checkbox] checkbox} + * - {@link input[radio] radio} + * - {@link input[number] number} + * - {@link input[email] email} + * - {@link input[url] url} + * - {@link ng.directive:select select} + * - {@link ng.directive:textarea textarea} + * + * # CSS classes + * The following CSS classes are added and removed on the associated input/select/textarea element + * depending on the validity of the model. + * + * - `ng-valid` is set if the model is valid. + * - `ng-invalid` is set if the model is invalid. + * - `ng-pristine` is set if the model is pristine. + * - `ng-dirty` is set if the model is dirty. + * + * Keep in mind that ngAnimate can detect each of these classes when added and removed. + * + * ## Animation Hooks + * + * Animations within models are triggered when any of the associated CSS classes are added and removed + * on the input element which is attached to the model. These classes are: `.ng-pristine`, `.ng-dirty`, + * `.ng-invalid` and `.ng-valid` as well as any other validations that are performed on the model itself. + * The animations that are triggered within ngModel are similar to how they work in ngClass and + * animations can be hooked into using CSS transitions, keyframes as well as JS animations. + * + * The following example shows a simple way to utilize CSS transitions to style an input element + * that has been rendered as invalid after it has been validated: + * + *
+ * //be sure to include ngAnimate as a module to hook into more
+ * //advanced animations
+ * .my-input {
+ *   transition:0.5s linear all;
+ *   background: white;
+ * }
+ * .my-input.ng-invalid {
+ *   background: red;
+ *   color:white;
+ * }
+ * 
+ * + * @example + * + + + + Update input to see transitions when valid/invalid. + Integer is a valid value. +
+ +
+
+ *
+ */ +var ngModelDirective = function() { + return { + require: ['ngModel', '^?form'], + controller: NgModelController, + link: function(scope, element, attr, ctrls) { + // notify others, especially parent forms + + var modelCtrl = ctrls[0], + formCtrl = ctrls[1] || nullFormCtrl; + + formCtrl.$addControl(modelCtrl); + + scope.$on('$destroy', function() { + formCtrl.$removeControl(modelCtrl); + }); + } + }; +}; + + +/** + * @ngdoc directive + * @name ngChange + * + * @description + * Evaluate the given expression when the user changes the input. + * The expression is evaluated immediately, unlike the JavaScript onchange event + * which only triggers at the end of a change (usually, when the user leaves the + * form element or presses the return key). + * The expression is not evaluated when the value change is coming from the model. + * + * Note, this directive requires `ngModel` to be present. + * + * @element input + * @param {expression} ngChange {@link guide/expression Expression} to evaluate upon change + * in input value. + * + * @example + * + * + * + *
+ * + * + *
+ * debug = {{confirmed}}
+ * counter = {{counter}}
+ *
+ *
+ * + * var counter = element(by.binding('counter')); + * var debug = element(by.binding('confirmed')); + * + * it('should evaluate the expression if changing from view', function() { + * expect(counter.getText()).toContain('0'); + * + * element(by.id('ng-change-example1')).click(); + * + * expect(counter.getText()).toContain('1'); + * expect(debug.getText()).toContain('true'); + * }); + * + * it('should not evaluate the expression if changing from model', function() { + * element(by.id('ng-change-example2')).click(); + + * expect(counter.getText()).toContain('0'); + * expect(debug.getText()).toContain('true'); + * }); + * + *
+ */ +var ngChangeDirective = valueFn({ + require: 'ngModel', + link: function(scope, element, attr, ctrl) { + ctrl.$viewChangeListeners.push(function() { + scope.$eval(attr.ngChange); + }); + } +}); + + +var requiredDirective = function() { + return { + require: '?ngModel', + link: function(scope, elm, attr, ctrl) { + if (!ctrl) return; + attr.required = true; // force truthy in case we are on non input element + + var validator = function(value) { + if (attr.required && ctrl.$isEmpty(value)) { + ctrl.$setValidity('required', false); + return; + } else { + ctrl.$setValidity('required', true); + return value; + } + }; + + ctrl.$formatters.push(validator); + ctrl.$parsers.unshift(validator); + + attr.$observe('required', function() { + validator(ctrl.$viewValue); + }); + } + }; +}; + + +/** + * @ngdoc directive + * @name ngList + * + * @description + * Text input that converts between a delimited string and an array of strings. The delimiter + * can be a fixed string (by default a comma) or a regular expression. + * + * @element input + * @param {string=} ngList optional delimiter that should be used to split the value. If + * specified in form `/something/` then the value will be converted into a regular expression. + * + * @example + + + +
+ List: + + Required! +
+ names = {{names}}
+ myForm.namesInput.$valid = {{myForm.namesInput.$valid}}
+ myForm.namesInput.$error = {{myForm.namesInput.$error}}
+ myForm.$valid = {{myForm.$valid}}
+ myForm.$error.required = {{!!myForm.$error.required}}
+
+
+ + var listInput = element(by.model('names')); + var names = element(by.binding('{{names}}')); + var valid = element(by.binding('myForm.namesInput.$valid')); + var error = element(by.css('span.error')); + + it('should initialize to model', function() { + expect(names.getText()).toContain('["igor","misko","vojta"]'); + expect(valid.getText()).toContain('true'); + expect(error.getCssValue('display')).toBe('none'); + }); + + it('should be invalid if empty', function() { + listInput.clear(); + listInput.sendKeys(''); + + expect(names.getText()).toContain(''); + expect(valid.getText()).toContain('false'); + expect(error.getCssValue('display')).not.toBe('none'); }); + +
+ */ +var ngListDirective = function() { + return { + require: 'ngModel', + link: function(scope, element, attr, ctrl) { + var match = /\/(.*)\//.exec(attr.ngList), + separator = match && new RegExp(match[1]) || attr.ngList || ','; + + var parse = function(viewValue) { + // If the viewValue is invalid (say required but empty) it will be `undefined` + if (isUndefined(viewValue)) return; + + var list = []; + + if (viewValue) { + forEach(viewValue.split(separator), function(value) { + if (value) list.push(trim(value)); + }); + } + + return list; + }; + + ctrl.$parsers.push(parse); + ctrl.$formatters.push(function(value) { + if (isArray(value)) { + return value.join(', '); + } + + return undefined; + }); + + // Override the standard $isEmpty because an empty array means the input is empty. + ctrl.$isEmpty = function(value) { + return !value || !value.length; + }; + } + }; +}; + + +var CONSTANT_VALUE_REGEXP = /^(true|false|\d+)$/; +/** + * @ngdoc directive + * @name ngValue + * + * @description + * Binds the given expression to the value of `input[select]` or `input[radio]`, so + * that when the element is selected, the `ngModel` of that element is set to the + * bound value. + * + * `ngValue` is useful when dynamically generating lists of radio buttons using `ng-repeat`, as + * shown below. + * + * @element input + * @param {string=} ngValue angular expression, whose value will be bound to the `value` attribute + * of the `input` element + * + * @example + + + +
+

Which is your favorite?

+ +
You chose {{my.favorite}}
+
+
+ + var favorite = element(by.binding('my.favorite')); + + it('should initialize to model', function() { + expect(favorite.getText()).toContain('unicorns'); + }); + it('should bind the values to the inputs', function() { + element.all(by.model('my.favorite')).get(0).click(); + expect(favorite.getText()).toContain('pizza'); + }); + +
+ */ +var ngValueDirective = function() { + return { + priority: 100, + compile: function(tpl, tplAttr) { + if (CONSTANT_VALUE_REGEXP.test(tplAttr.ngValue)) { + return function ngValueConstantLink(scope, elm, attr) { + attr.$set('value', scope.$eval(attr.ngValue)); + }; + } else { + return function ngValueLink(scope, elm, attr) { + scope.$watch(attr.ngValue, function valueWatchAction(value) { + attr.$set('value', value); + }); + }; + } + } + }; +}; + +/** + * @ngdoc directive + * @name ngBind + * @restrict AC + * + * @description + * The `ngBind` attribute tells Angular to replace the text content of the specified HTML element + * with the value of a given expression, and to update the text content when the value of that + * expression changes. + * + * Typically, you don't use `ngBind` directly, but instead you use the double curly markup like + * `{{ expression }}` which is similar but less verbose. + * + * It is preferable to use `ngBind` instead of `{{ expression }}` when a template is momentarily + * displayed by the browser in its raw state before Angular compiles it. Since `ngBind` is an + * element attribute, it makes the bindings invisible to the user while the page is loading. + * + * An alternative solution to this problem would be using the + * {@link ng.directive:ngCloak ngCloak} directive. + * + * + * @element ANY + * @param {expression} ngBind {@link guide/expression Expression} to evaluate. + * + * @example + * Enter a name in the Live Preview text box; the greeting below the text box changes instantly. + + + +
+ Enter name:
+ Hello ! +
+
+ + it('should check ng-bind', function() { + var nameInput = element(by.model('name')); + + expect(element(by.binding('name')).getText()).toBe('Whirled'); + nameInput.clear(); + nameInput.sendKeys('world'); + expect(element(by.binding('name')).getText()).toBe('world'); + }); + +
+ */ +var ngBindDirective = ngDirective(function(scope, element, attr) { + element.addClass('ng-binding').data('$binding', attr.ngBind); + scope.$watch(attr.ngBind, function ngBindWatchAction(value) { + // We are purposefully using == here rather than === because we want to + // catch when value is "null or undefined" + // jshint -W041 + element.text(value == undefined ? '' : value); + }); +}); + + +/** + * @ngdoc directive + * @name ngBindTemplate + * + * @description + * The `ngBindTemplate` directive specifies that the element + * text content should be replaced with the interpolation of the template + * in the `ngBindTemplate` attribute. + * Unlike `ngBind`, the `ngBindTemplate` can contain multiple `{{` `}}` + * expressions. This directive is needed since some HTML elements + * (such as TITLE and OPTION) cannot contain SPAN elements. + * + * @element ANY + * @param {string} ngBindTemplate template of form + * {{ expression }} to eval. + * + * @example + * Try it here: enter text in text box and watch the greeting change. + + + +
+ Salutation:
+ Name:
+

+       
+
+ + it('should check ng-bind', function() { + var salutationElem = element(by.binding('salutation')); + var salutationInput = element(by.model('salutation')); + var nameInput = element(by.model('name')); + + expect(salutationElem.getText()).toBe('Hello World!'); + + salutationInput.clear(); + salutationInput.sendKeys('Greetings'); + nameInput.clear(); + nameInput.sendKeys('user'); + + expect(salutationElem.getText()).toBe('Greetings user!'); + }); + +
+ */ +var ngBindTemplateDirective = ['$interpolate', function($interpolate) { + return function(scope, element, attr) { + // TODO: move this to scenario runner + var interpolateFn = $interpolate(element.attr(attr.$attr.ngBindTemplate)); + element.addClass('ng-binding').data('$binding', interpolateFn); + attr.$observe('ngBindTemplate', function(value) { + element.text(value); + }); + }; +}]; + + +/** + * @ngdoc directive + * @name ngBindHtml + * + * @description + * Creates a binding that will innerHTML the result of evaluating the `expression` into the current + * element in a secure way. By default, the innerHTML-ed content will be sanitized using the {@link + * ngSanitize.$sanitize $sanitize} service. To utilize this functionality, ensure that `$sanitize` + * is available, for example, by including {@link ngSanitize} in your module's dependencies (not in + * core Angular.) You may also bypass sanitization for values you know are safe. To do so, bind to + * an explicitly trusted value via {@link ng.$sce#trustAsHtml $sce.trustAsHtml}. See the example + * under {@link ng.$sce#Example Strict Contextual Escaping (SCE)}. + * + * Note: If a `$sanitize` service is unavailable and the bound value isn't explicitly trusted, you + * will have an exception (instead of an exploit.) + * + * @element ANY + * @param {expression} ngBindHtml {@link guide/expression Expression} to evaluate. + * + * @example + Try it here: enter text in text box and watch the greeting change. + + + +
+

+
+
+ + + angular.module('ngBindHtmlExample', ['ngSanitize']) + + .controller('ngBindHtmlCtrl', ['$scope', function ngBindHtmlCtrl($scope) { + $scope.myHTML = + 'I am an HTMLstring with links! and other stuff'; + }]); + + + + it('should check ng-bind-html', function() { + expect(element(by.binding('myHTML')).getText()).toBe( + 'I am an HTMLstring with links! and other stuff'); + }); + +
+ */ +var ngBindHtmlDirective = ['$sce', '$parse', function($sce, $parse) { + return function(scope, element, attr) { + element.addClass('ng-binding').data('$binding', attr.ngBindHtml); + + var parsed = $parse(attr.ngBindHtml); + function getStringValue() { return (parsed(scope) || '').toString(); } + + scope.$watch(getStringValue, function ngBindHtmlWatchAction(value) { + element.html($sce.getTrustedHtml(parsed(scope)) || ''); + }); + }; +}]; + +function classDirective(name, selector) { + name = 'ngClass' + name; + return ['$animate', function($animate) { + return { + restrict: 'AC', + link: function(scope, element, attr) { + var oldVal; + + scope.$watch(attr[name], ngClassWatchAction, true); + + attr.$observe('class', function(value) { + ngClassWatchAction(scope.$eval(attr[name])); + }); + + + if (name !== 'ngClass') { + scope.$watch('$index', function($index, old$index) { + // jshint bitwise: false + var mod = $index & 1; + if (mod !== (old$index & 1)) { + var classes = arrayClasses(scope.$eval(attr[name])); + mod === selector ? + addClasses(classes) : + removeClasses(classes); + } + }); + } + + function addClasses(classes) { + var newClasses = digestClassCounts(classes, 1); + attr.$addClass(newClasses); + } + + function removeClasses(classes) { + var newClasses = digestClassCounts(classes, -1); + attr.$removeClass(newClasses); + } + + function digestClassCounts (classes, count) { + var classCounts = element.data('$classCounts') || {}; + var classesToUpdate = []; + forEach(classes, function (className) { + if (count > 0 || classCounts[className]) { + classCounts[className] = (classCounts[className] || 0) + count; + if (classCounts[className] === +(count > 0)) { + classesToUpdate.push(className); + } + } + }); + element.data('$classCounts', classCounts); + return classesToUpdate.join(' '); + } + + function updateClasses (oldClasses, newClasses) { + var toAdd = arrayDifference(newClasses, oldClasses); + var toRemove = arrayDifference(oldClasses, newClasses); + toRemove = digestClassCounts(toRemove, -1); + toAdd = digestClassCounts(toAdd, 1); + + if (toAdd.length === 0) { + $animate.removeClass(element, toRemove); + } else if (toRemove.length === 0) { + $animate.addClass(element, toAdd); + } else { + $animate.setClass(element, toAdd, toRemove); + } + } + + function ngClassWatchAction(newVal) { + if (selector === true || scope.$index % 2 === selector) { + var newClasses = arrayClasses(newVal || []); + if (!oldVal) { + addClasses(newClasses); + } else if (!equals(newVal,oldVal)) { + var oldClasses = arrayClasses(oldVal); + updateClasses(oldClasses, newClasses); + } + } + oldVal = shallowCopy(newVal); + } + } + }; + + function arrayDifference(tokens1, tokens2) { + var values = []; + + outer: + for(var i = 0; i < tokens1.length; i++) { + var token = tokens1[i]; + for(var j = 0; j < tokens2.length; j++) { + if(token == tokens2[j]) continue outer; + } + values.push(token); + } + return values; + } + + function arrayClasses (classVal) { + if (isArray(classVal)) { + return classVal; + } else if (isString(classVal)) { + return classVal.split(' '); + } else if (isObject(classVal)) { + var classes = [], i = 0; + forEach(classVal, function(v, k) { + if (v) { + classes = classes.concat(k.split(' ')); + } + }); + return classes; + } + return classVal; + } + }]; +} + +/** + * @ngdoc directive + * @name ngClass + * @restrict AC + * + * @description + * The `ngClass` directive allows you to dynamically set CSS classes on an HTML element by databinding + * an expression that represents all classes to be added. + * + * The directive operates in three different ways, depending on which of three types the expression + * evaluates to: + * + * 1. If the expression evaluates to a string, the string should be one or more space-delimited class + * names. + * + * 2. If the expression evaluates to an array, each element of the array should be a string that is + * one or more space-delimited class names. + * + * 3. If the expression evaluates to an object, then for each key-value pair of the + * object with a truthy value the corresponding key is used as a class name. + * + * The directive won't add duplicate classes if a particular class was already set. + * + * When the expression changes, the previously added classes are removed and only then the + * new classes are added. + * + * @animations + * add - happens just before the class is applied to the element + * remove - happens just before the class is removed from the element + * + * @element ANY + * @param {expression} ngClass {@link guide/expression Expression} to eval. The result + * of the evaluation can be a string representing space delimited class + * names, an array, or a map of class names to boolean values. In the case of a map, the + * names of the properties whose values are truthy will be added as css classes to the + * element. + * + * @example Example that demonstrates basic bindings via ngClass directive. + + +

Map Syntax Example

+ deleted (apply "strike" class)
+ important (apply "bold" class)
+ error (apply "red" class) +
+

Using String Syntax

+ +
+

Using Array Syntax

+
+
+
+
+ + .strike { + text-decoration: line-through; + } + .bold { + font-weight: bold; + } + .red { + color: red; + } + + + var ps = element.all(by.css('p')); + + it('should let you toggle the class', function() { + + expect(ps.first().getAttribute('class')).not.toMatch(/bold/); + expect(ps.first().getAttribute('class')).not.toMatch(/red/); + + element(by.model('important')).click(); + expect(ps.first().getAttribute('class')).toMatch(/bold/); + + element(by.model('error')).click(); + expect(ps.first().getAttribute('class')).toMatch(/red/); + }); + + it('should let you toggle string example', function() { + expect(ps.get(1).getAttribute('class')).toBe(''); + element(by.model('style')).clear(); + element(by.model('style')).sendKeys('red'); + expect(ps.get(1).getAttribute('class')).toBe('red'); + }); + + it('array example should have 3 classes', function() { + expect(ps.last().getAttribute('class')).toBe(''); + element(by.model('style1')).sendKeys('bold'); + element(by.model('style2')).sendKeys('strike'); + element(by.model('style3')).sendKeys('red'); + expect(ps.last().getAttribute('class')).toBe('bold strike red'); + }); + +
+ + ## Animations + + The example below demonstrates how to perform animations using ngClass. + + + + + +
+ Sample Text +
+ + .base-class { + -webkit-transition:all cubic-bezier(0.250, 0.460, 0.450, 0.940) 0.5s; + transition:all cubic-bezier(0.250, 0.460, 0.450, 0.940) 0.5s; + } + + .base-class.my-class { + color: red; + font-size:3em; + } + + + it('should check ng-class', function() { + expect(element(by.css('.base-class')).getAttribute('class')).not. + toMatch(/my-class/); + + element(by.id('setbtn')).click(); + + expect(element(by.css('.base-class')).getAttribute('class')). + toMatch(/my-class/); + + element(by.id('clearbtn')).click(); + + expect(element(by.css('.base-class')).getAttribute('class')).not. + toMatch(/my-class/); + }); + +
+ + + ## ngClass and pre-existing CSS3 Transitions/Animations + The ngClass directive still supports CSS3 Transitions/Animations even if they do not follow the ngAnimate CSS naming structure. + Upon animation ngAnimate will apply supplementary CSS classes to track the start and end of an animation, but this will not hinder + any pre-existing CSS transitions already on the element. To get an idea of what happens during a class-based animation, be sure + to view the step by step details of {@link ngAnimate.$animate#addclass $animate.addClass} and + {@link ngAnimate.$animate#removeclass $animate.removeClass}. + */ +var ngClassDirective = classDirective('', true); + +/** + * @ngdoc directive + * @name ngClassOdd + * @restrict AC + * + * @description + * The `ngClassOdd` and `ngClassEven` directives work exactly as + * {@link ng.directive:ngClass ngClass}, except they work in + * conjunction with `ngRepeat` and take effect only on odd (even) rows. + * + * This directive can be applied only within the scope of an + * {@link ng.directive:ngRepeat ngRepeat}. + * + * @element ANY + * @param {expression} ngClassOdd {@link guide/expression Expression} to eval. The result + * of the evaluation can be a string representing space delimited class names or an array. + * + * @example + + +
    +
  1. + + {{name}} + +
  2. +
+
+ + .odd { + color: red; + } + .even { + color: blue; + } + + + it('should check ng-class-odd and ng-class-even', function() { + expect(element(by.repeater('name in names').row(0).column('name')).getAttribute('class')). + toMatch(/odd/); + expect(element(by.repeater('name in names').row(1).column('name')).getAttribute('class')). + toMatch(/even/); + }); + +
+ */ +var ngClassOddDirective = classDirective('Odd', 0); + +/** + * @ngdoc directive + * @name ngClassEven + * @restrict AC + * + * @description + * The `ngClassOdd` and `ngClassEven` directives work exactly as + * {@link ng.directive:ngClass ngClass}, except they work in + * conjunction with `ngRepeat` and take effect only on odd (even) rows. + * + * This directive can be applied only within the scope of an + * {@link ng.directive:ngRepeat ngRepeat}. + * + * @element ANY + * @param {expression} ngClassEven {@link guide/expression Expression} to eval. The + * result of the evaluation can be a string representing space delimited class names or an array. + * + * @example + + +
    +
  1. + + {{name}}       + +
  2. +
+
+ + .odd { + color: red; + } + .even { + color: blue; + } + + + it('should check ng-class-odd and ng-class-even', function() { + expect(element(by.repeater('name in names').row(0).column('name')).getAttribute('class')). + toMatch(/odd/); + expect(element(by.repeater('name in names').row(1).column('name')).getAttribute('class')). + toMatch(/even/); + }); + +
+ */ +var ngClassEvenDirective = classDirective('Even', 1); + +/** + * @ngdoc directive + * @name ngCloak + * @restrict AC + * + * @description + * The `ngCloak` directive is used to prevent the Angular html template from being briefly + * displayed by the browser in its raw (uncompiled) form while your application is loading. Use this + * directive to avoid the undesirable flicker effect caused by the html template display. + * + * The directive can be applied to the `` element, but the preferred usage is to apply + * multiple `ngCloak` directives to small portions of the page to permit progressive rendering + * of the browser view. + * + * `ngCloak` works in cooperation with the following css rule embedded within `angular.js` and + * `angular.min.js`. + * For CSP mode please add `angular-csp.css` to your html file (see {@link ng.directive:ngCsp ngCsp}). + * + * ```css + * [ng\:cloak], [ng-cloak], [data-ng-cloak], [x-ng-cloak], .ng-cloak, .x-ng-cloak { + * display: none !important; + * } + * ``` + * + * When this css rule is loaded by the browser, all html elements (including their children) that + * are tagged with the `ngCloak` directive are hidden. When Angular encounters this directive + * during the compilation of the template it deletes the `ngCloak` element attribute, making + * the compiled element visible. + * + * For the best result, the `angular.js` script must be loaded in the head section of the html + * document; alternatively, the css rule above must be included in the external stylesheet of the + * application. + * + * Legacy browsers, like IE7, do not provide attribute selector support (added in CSS 2.1) so they + * cannot match the `[ng\:cloak]` selector. To work around this limitation, you must add the css + * class `ng-cloak` in addition to the `ngCloak` directive as shown in the example below. + * + * @element ANY + * + * @example + + +
{{ 'hello' }}
+
{{ 'hello IE7' }}
+
+ + it('should remove the template directive and css class', function() { + expect($('#template1').getAttribute('ng-cloak')). + toBeNull(); + expect($('#template2').getAttribute('ng-cloak')). + toBeNull(); + }); + +
+ * + */ +var ngCloakDirective = ngDirective({ + compile: function(element, attr) { + attr.$set('ngCloak', undefined); + element.removeClass('ng-cloak'); + } +}); + +/** + * @ngdoc directive + * @name ngController + * + * @description + * The `ngController` directive attaches a controller class to the view. This is a key aspect of how angular + * supports the principles behind the Model-View-Controller design pattern. + * + * MVC components in angular: + * + * * Model — The Model is scope properties; scopes are attached to the DOM where scope properties + * are accessed through bindings. + * * View — The template (HTML with data bindings) that is rendered into the View. + * * Controller — The `ngController` directive specifies a Controller class; the class contains business + * logic behind the application to decorate the scope with functions and values + * + * Note that you can also attach controllers to the DOM by declaring it in a route definition + * via the {@link ngRoute.$route $route} service. A common mistake is to declare the controller + * again using `ng-controller` in the template itself. This will cause the controller to be attached + * and executed twice. + * + * @element ANY + * @scope + * @param {expression} ngController Name of a globally accessible constructor function or an + * {@link guide/expression expression} that on the current scope evaluates to a + * constructor function. The controller instance can be published into a scope property + * by specifying `as propertyName`. + * + * @example + * Here is a simple form for editing user contact information. Adding, removing, clearing, and + * greeting are methods declared on the controller (see source tab). These methods can + * easily be called from the angular markup. Any changes to the data are automatically reflected + * in the View without the need for a manual update. + * + * Two different declaration styles are included below: + * + * * one binds methods and properties directly onto the controller using `this`: + * `ng-controller="SettingsController1 as settings"` + * * one injects `$scope` into the controller: + * `ng-controller="SettingsController2"` + * + * The second option is more common in the Angular community, and is generally used in boilerplates + * and in this guide. However, there are advantages to binding properties directly to the controller + * and avoiding scope. + * + * * Using `controller as` makes it obvious which controller you are accessing in the template when + * multiple controllers apply to an element. + * * If you are writing your controllers as classes you have easier access to the properties and + * methods, which will appear on the scope, from inside the controller code. + * * Since there is always a `.` in the bindings, you don't have to worry about prototypal + * inheritance masking primitives. + * + * This example demonstrates the `controller as` syntax. + * + * + * + *
+ * Name: + * [ greet ]
+ * Contact: + *
    + *
  • + * + * + * [ clear + * | X ] + *
  • + *
  • [ add ]
  • + *
+ *
+ *
+ * + * function SettingsController1() { + * this.name = "John Smith"; + * this.contacts = [ + * {type: 'phone', value: '408 555 1212'}, + * {type: 'email', value: 'john.smith@example.org'} ]; + * } + * + * SettingsController1.prototype.greet = function() { + * alert(this.name); + * }; + * + * SettingsController1.prototype.addContact = function() { + * this.contacts.push({type: 'email', value: 'yourname@example.org'}); + * }; + * + * SettingsController1.prototype.removeContact = function(contactToRemove) { + * var index = this.contacts.indexOf(contactToRemove); + * this.contacts.splice(index, 1); + * }; + * + * SettingsController1.prototype.clearContact = function(contact) { + * contact.type = 'phone'; + * contact.value = ''; + * }; + * + * + * it('should check controller as', function() { + * var container = element(by.id('ctrl-as-exmpl')); + * expect(container.findElement(by.model('settings.name')) + * .getAttribute('value')).toBe('John Smith'); + * + * var firstRepeat = + * container.findElement(by.repeater('contact in settings.contacts').row(0)); + * var secondRepeat = + * container.findElement(by.repeater('contact in settings.contacts').row(1)); + * + * expect(firstRepeat.findElement(by.model('contact.value')).getAttribute('value')) + * .toBe('408 555 1212'); + * + * expect(secondRepeat.findElement(by.model('contact.value')).getAttribute('value')) + * .toBe('john.smith@example.org'); + * + * firstRepeat.findElement(by.linkText('clear')).click(); + * + * expect(firstRepeat.findElement(by.model('contact.value')).getAttribute('value')) + * .toBe(''); + * + * container.findElement(by.linkText('add')).click(); + * + * expect(container.findElement(by.repeater('contact in settings.contacts').row(2)) + * .findElement(by.model('contact.value')) + * .getAttribute('value')) + * .toBe('yourname@example.org'); + * }); + * + *
+ * + * This example demonstrates the "attach to `$scope`" style of controller. + * + * + * + *
+ * Name: + * [ greet ]
+ * Contact: + *
    + *
  • + * + * + * [ clear + * | X ] + *
  • + *
  • [ add ]
  • + *
+ *
+ *
+ * + * function SettingsController2($scope) { + * $scope.name = "John Smith"; + * $scope.contacts = [ + * {type:'phone', value:'408 555 1212'}, + * {type:'email', value:'john.smith@example.org'} ]; + * + * $scope.greet = function() { + * alert($scope.name); + * }; + * + * $scope.addContact = function() { + * $scope.contacts.push({type:'email', value:'yourname@example.org'}); + * }; + * + * $scope.removeContact = function(contactToRemove) { + * var index = $scope.contacts.indexOf(contactToRemove); + * $scope.contacts.splice(index, 1); + * }; + * + * $scope.clearContact = function(contact) { + * contact.type = 'phone'; + * contact.value = ''; + * }; + * } + * + * + * it('should check controller', function() { + * var container = element(by.id('ctrl-exmpl')); + * + * expect(container.findElement(by.model('name')) + * .getAttribute('value')).toBe('John Smith'); + * + * var firstRepeat = + * container.findElement(by.repeater('contact in contacts').row(0)); + * var secondRepeat = + * container.findElement(by.repeater('contact in contacts').row(1)); + * + * expect(firstRepeat.findElement(by.model('contact.value')).getAttribute('value')) + * .toBe('408 555 1212'); + * expect(secondRepeat.findElement(by.model('contact.value')).getAttribute('value')) + * .toBe('john.smith@example.org'); + * + * firstRepeat.findElement(by.linkText('clear')).click(); + * + * expect(firstRepeat.findElement(by.model('contact.value')).getAttribute('value')) + * .toBe(''); + * + * container.findElement(by.linkText('add')).click(); + * + * expect(container.findElement(by.repeater('contact in contacts').row(2)) + * .findElement(by.model('contact.value')) + * .getAttribute('value')) + * .toBe('yourname@example.org'); + * }); + * + *
+ + */ +var ngControllerDirective = [function() { + return { + scope: true, + controller: '@', + priority: 500 + }; +}]; + +/** + * @ngdoc directive + * @name ngCsp + * + * @element html + * @description + * Enables [CSP (Content Security Policy)](https://developer.mozilla.org/en/Security/CSP) support. + * + * This is necessary when developing things like Google Chrome Extensions. + * + * CSP forbids apps to use `eval` or `Function(string)` generated functions (among other things). + * For us to be compatible, we just need to implement the "getterFn" in $parse without violating + * any of these restrictions. + * + * AngularJS uses `Function(string)` generated functions as a speed optimization. Applying the `ngCsp` + * directive will cause Angular to use CSP compatibility mode. When this mode is on AngularJS will + * evaluate all expressions up to 30% slower than in non-CSP mode, but no security violations will + * be raised. + * + * CSP forbids JavaScript to inline stylesheet rules. In non CSP mode Angular automatically + * includes some CSS rules (e.g. {@link ng.directive:ngCloak ngCloak}). + * To make those directives work in CSP mode, include the `angular-csp.css` manually. + * + * In order to use this feature put the `ngCsp` directive on the root element of the application. + * + * *Note: This directive is only available in the `ng-csp` and `data-ng-csp` attribute form.* + * + * @example + * This example shows how to apply the `ngCsp` directive to the `html` tag. + ```html + + + ... + ... + + ``` + */ + +// ngCsp is not implemented as a proper directive any more, because we need it be processed while we bootstrap +// the system (before $parse is instantiated), for this reason we just have a csp() fn that looks for ng-csp attribute +// anywhere in the current doc + +/** + * @ngdoc directive + * @name ngClick + * + * @description + * The ngClick directive allows you to specify custom behavior when + * an element is clicked. + * + * @element ANY + * @priority 0 + * @param {expression} ngClick {@link guide/expression Expression} to evaluate upon + * click. ({@link guide/expression#-event- Event object is available as `$event`}) + * + * @example + + + + count: {{count}} + + + it('should check ng-click', function() { + expect(element(by.binding('count')).getText()).toMatch('0'); + element(by.css('button')).click(); + expect(element(by.binding('count')).getText()).toMatch('1'); + }); + + + */ +/* + * A directive that allows creation of custom onclick handlers that are defined as angular + * expressions and are compiled and executed within the current scope. + * + * Events that are handled via these handler are always configured not to propagate further. + */ +var ngEventDirectives = {}; +forEach( + 'click dblclick mousedown mouseup mouseover mouseout mousemove mouseenter mouseleave keydown keyup keypress submit focus blur copy cut paste'.split(' '), + function(name) { + var directiveName = directiveNormalize('ng-' + name); + ngEventDirectives[directiveName] = ['$parse', function($parse) { + return { + compile: function($element, attr) { + var fn = $parse(attr[directiveName]); + return function(scope, element, attr) { + element.on(lowercase(name), function(event) { + scope.$apply(function() { + fn(scope, {$event:event}); + }); + }); + }; + } + }; + }]; + } +); + +/** + * @ngdoc directive + * @name ngDblclick + * + * @description + * The `ngDblclick` directive allows you to specify custom behavior on a dblclick event. + * + * @element ANY + * @priority 0 + * @param {expression} ngDblclick {@link guide/expression Expression} to evaluate upon + * a dblclick. (The Event object is available as `$event`) + * + * @example + + + + count: {{count}} + + + */ + + +/** + * @ngdoc directive + * @name ngMousedown + * + * @description + * The ngMousedown directive allows you to specify custom behavior on mousedown event. + * + * @element ANY + * @priority 0 + * @param {expression} ngMousedown {@link guide/expression Expression} to evaluate upon + * mousedown. ({@link guide/expression#-event- Event object is available as `$event`}) + * + * @example + + + + count: {{count}} + + + */ + + +/** + * @ngdoc directive + * @name ngMouseup + * + * @description + * Specify custom behavior on mouseup event. + * + * @element ANY + * @priority 0 + * @param {expression} ngMouseup {@link guide/expression Expression} to evaluate upon + * mouseup. ({@link guide/expression#-event- Event object is available as `$event`}) + * + * @example + + + + count: {{count}} + + + */ + +/** + * @ngdoc directive + * @name ngMouseover + * + * @description + * Specify custom behavior on mouseover event. + * + * @element ANY + * @priority 0 + * @param {expression} ngMouseover {@link guide/expression Expression} to evaluate upon + * mouseover. ({@link guide/expression#-event- Event object is available as `$event`}) + * + * @example + + + + count: {{count}} + + + */ + + +/** + * @ngdoc directive + * @name ngMouseenter + * + * @description + * Specify custom behavior on mouseenter event. + * + * @element ANY + * @priority 0 + * @param {expression} ngMouseenter {@link guide/expression Expression} to evaluate upon + * mouseenter. ({@link guide/expression#-event- Event object is available as `$event`}) + * + * @example + + + + count: {{count}} + + + */ + + +/** + * @ngdoc directive + * @name ngMouseleave + * + * @description + * Specify custom behavior on mouseleave event. + * + * @element ANY + * @priority 0 + * @param {expression} ngMouseleave {@link guide/expression Expression} to evaluate upon + * mouseleave. ({@link guide/expression#-event- Event object is available as `$event`}) + * + * @example + + + + count: {{count}} + + + */ + + +/** + * @ngdoc directive + * @name ngMousemove + * + * @description + * Specify custom behavior on mousemove event. + * + * @element ANY + * @priority 0 + * @param {expression} ngMousemove {@link guide/expression Expression} to evaluate upon + * mousemove. ({@link guide/expression#-event- Event object is available as `$event`}) + * + * @example + + + + count: {{count}} + + + */ + + +/** + * @ngdoc directive + * @name ngKeydown + * + * @description + * Specify custom behavior on keydown event. + * + * @element ANY + * @priority 0 + * @param {expression} ngKeydown {@link guide/expression Expression} to evaluate upon + * keydown. (Event object is available as `$event` and can be interrogated for keyCode, altKey, etc.) + * + * @example + + + + key down count: {{count}} + + + */ + + +/** + * @ngdoc directive + * @name ngKeyup + * + * @description + * Specify custom behavior on keyup event. + * + * @element ANY + * @priority 0 + * @param {expression} ngKeyup {@link guide/expression Expression} to evaluate upon + * keyup. (Event object is available as `$event` and can be interrogated for keyCode, altKey, etc.) + * + * @example + + +

Typing in the input box below updates the key count

+ key up count: {{count}} + +

Typing in the input box below updates the keycode

+ +

event keyCode: {{ event.keyCode }}

+

event altKey: {{ event.altKey }}

+
+
+ */ + + +/** + * @ngdoc directive + * @name ngKeypress + * + * @description + * Specify custom behavior on keypress event. + * + * @element ANY + * @param {expression} ngKeypress {@link guide/expression Expression} to evaluate upon + * keypress. ({@link guide/expression#-event- Event object is available as `$event`} + * and can be interrogated for keyCode, altKey, etc.) + * + * @example + + + + key press count: {{count}} + + + */ + + +/** + * @ngdoc directive + * @name ngSubmit + * + * @description + * Enables binding angular expressions to onsubmit events. + * + * Additionally it prevents the default action (which for form means sending the request to the + * server and reloading the current page), but only if the form does not contain `action`, + * `data-action`, or `x-action` attributes. + * + * @element form + * @priority 0 + * @param {expression} ngSubmit {@link guide/expression Expression} to eval. + * ({@link guide/expression#-event- Event object is available as `$event`}) + * + * @example + + + +
+ Enter text and hit enter: + + +
list={{list}}
+
+
+ + it('should check ng-submit', function() { + expect(element(by.binding('list')).getText()).toBe('list=[]'); + element(by.css('#submit')).click(); + expect(element(by.binding('list')).getText()).toContain('hello'); + expect(element(by.input('text')).getAttribute('value')).toBe(''); + }); + it('should ignore empty strings', function() { + expect(element(by.binding('list')).getText()).toBe('list=[]'); + element(by.css('#submit')).click(); + element(by.css('#submit')).click(); + expect(element(by.binding('list')).getText()).toContain('hello'); + }); + +
+ */ + +/** + * @ngdoc directive + * @name ngFocus + * + * @description + * Specify custom behavior on focus event. + * + * @element window, input, select, textarea, a + * @priority 0 + * @param {expression} ngFocus {@link guide/expression Expression} to evaluate upon + * focus. ({@link guide/expression#-event- Event object is available as `$event`}) + * + * @example + * See {@link ng.directive:ngClick ngClick} + */ + +/** + * @ngdoc directive + * @name ngBlur + * + * @description + * Specify custom behavior on blur event. + * + * @element window, input, select, textarea, a + * @priority 0 + * @param {expression} ngBlur {@link guide/expression Expression} to evaluate upon + * blur. ({@link guide/expression#-event- Event object is available as `$event`}) + * + * @example + * See {@link ng.directive:ngClick ngClick} + */ + +/** + * @ngdoc directive + * @name ngCopy + * + * @description + * Specify custom behavior on copy event. + * + * @element window, input, select, textarea, a + * @priority 0 + * @param {expression} ngCopy {@link guide/expression Expression} to evaluate upon + * copy. ({@link guide/expression#-event- Event object is available as `$event`}) + * + * @example + + + + copied: {{copied}} + + + */ + +/** + * @ngdoc directive + * @name ngCut + * + * @description + * Specify custom behavior on cut event. + * + * @element window, input, select, textarea, a + * @priority 0 + * @param {expression} ngCut {@link guide/expression Expression} to evaluate upon + * cut. ({@link guide/expression#-event- Event object is available as `$event`}) + * + * @example + + + + cut: {{cut}} + + + */ + +/** + * @ngdoc directive + * @name ngPaste + * + * @description + * Specify custom behavior on paste event. + * + * @element window, input, select, textarea, a + * @priority 0 + * @param {expression} ngPaste {@link guide/expression Expression} to evaluate upon + * paste. ({@link guide/expression#-event- Event object is available as `$event`}) + * + * @example + + + + pasted: {{paste}} + + + */ + +/** + * @ngdoc directive + * @name ngIf + * @restrict A + * + * @description + * The `ngIf` directive removes or recreates a portion of the DOM tree based on an + * {expression}. If the expression assigned to `ngIf` evaluates to a false + * value then the element is removed from the DOM, otherwise a clone of the + * element is reinserted into the DOM. + * + * `ngIf` differs from `ngShow` and `ngHide` in that `ngIf` completely removes and recreates the + * element in the DOM rather than changing its visibility via the `display` css property. A common + * case when this difference is significant is when using css selectors that rely on an element's + * position within the DOM, such as the `:first-child` or `:last-child` pseudo-classes. + * + * Note that when an element is removed using `ngIf` its scope is destroyed and a new scope + * is created when the element is restored. The scope created within `ngIf` inherits from + * its parent scope using + * [prototypal inheritance](https://github.com/angular/angular.js/wiki/The-Nuances-of-Scope-Prototypal-Inheritance). + * An important implication of this is if `ngModel` is used within `ngIf` to bind to + * a javascript primitive defined in the parent scope. In this case any modifications made to the + * variable within the child scope will override (hide) the value in the parent scope. + * + * Also, `ngIf` recreates elements using their compiled state. An example of this behavior + * is if an element's class attribute is directly modified after it's compiled, using something like + * jQuery's `.addClass()` method, and the element is later removed. When `ngIf` recreates the element + * the added class will be lost because the original compiled state is used to regenerate the element. + * + * Additionally, you can provide animations via the `ngAnimate` module to animate the `enter` + * and `leave` effects. + * + * @animations + * enter - happens just after the ngIf contents change and a new DOM element is created and injected into the ngIf container + * leave - happens just before the ngIf contents are removed from the DOM + * + * @element ANY + * @scope + * @priority 600 + * @param {expression} ngIf If the {@link guide/expression expression} is falsy then + * the element is removed from the DOM tree. If it is truthy a copy of the compiled + * element is added to the DOM tree. + * + * @example + + + Click me:
+ Show when checked: + + I'm removed when the checkbox is unchecked. + +
+ + .animate-if { + background:white; + border:1px solid black; + padding:10px; + } + + .animate-if.ng-enter, .animate-if.ng-leave { + -webkit-transition:all cubic-bezier(0.250, 0.460, 0.450, 0.940) 0.5s; + transition:all cubic-bezier(0.250, 0.460, 0.450, 0.940) 0.5s; + } + + .animate-if.ng-enter, + .animate-if.ng-leave.ng-leave-active { + opacity:0; + } + + .animate-if.ng-leave, + .animate-if.ng-enter.ng-enter-active { + opacity:1; + } + +
+ */ +var ngIfDirective = ['$animate', function($animate) { + return { + transclude: 'element', + priority: 600, + terminal: true, + restrict: 'A', + $$tlb: true, + link: function ($scope, $element, $attr, ctrl, $transclude) { + var block, childScope, previousElements; + $scope.$watch($attr.ngIf, function ngIfWatchAction(value) { + + if (toBoolean(value)) { + if (!childScope) { + childScope = $scope.$new(); + $transclude(childScope, function (clone) { + clone[clone.length++] = document.createComment(' end ngIf: ' + $attr.ngIf + ' '); + // Note: We only need the first/last node of the cloned nodes. + // However, we need to keep the reference to the jqlite wrapper as it might be changed later + // by a directive with templateUrl when its template arrives. + block = { + clone: clone + }; + $animate.enter(clone, $element.parent(), $element); + }); + } + } else { + if(previousElements) { + previousElements.remove(); + previousElements = null; + } + if(childScope) { + childScope.$destroy(); + childScope = null; + } + if(block) { + previousElements = getBlockElements(block.clone); + $animate.leave(previousElements, function() { + previousElements = null; + }); + block = null; + } + } + }); + } + }; +}]; + +/** + * @ngdoc directive + * @name ngInclude + * @restrict ECA + * + * @description + * Fetches, compiles and includes an external HTML fragment. + * + * By default, the template URL is restricted to the same domain and protocol as the + * application document. This is done by calling {@link ng.$sce#getTrustedResourceUrl + * $sce.getTrustedResourceUrl} on it. To load templates from other domains or protocols + * you may either {@link ng.$sceDelegateProvider#resourceUrlWhitelist whitelist them} or + * [wrap them](ng.$sce#trustAsResourceUrl) as trusted values. Refer to Angular's {@link + * ng.$sce Strict Contextual Escaping}. + * + * In addition, the browser's + * [Same Origin Policy](https://code.google.com/p/browsersec/wiki/Part2#Same-origin_policy_for_XMLHttpRequest) + * and [Cross-Origin Resource Sharing (CORS)](http://www.w3.org/TR/cors/) + * policy may further restrict whether the template is successfully loaded. + * For example, `ngInclude` won't work for cross-domain requests on all browsers and for `file://` + * access on some browsers. + * + * @animations + * enter - animation is used to bring new content into the browser. + * leave - animation is used to animate existing content away. + * + * The enter and leave animation occur concurrently. + * + * @scope + * @priority 400 + * + * @param {string} ngInclude|src angular expression evaluating to URL. If the source is a string constant, + * make sure you wrap it in **single** quotes, e.g. `src="'myPartialTemplate.html'"`. + * @param {string=} onload Expression to evaluate when a new partial is loaded. + * + * @param {string=} autoscroll Whether `ngInclude` should call {@link ng.$anchorScroll + * $anchorScroll} to scroll the viewport after the content is loaded. + * + * - If the attribute is not set, disable scrolling. + * - If the attribute is set without value, enable scrolling. + * - Otherwise enable scrolling only if the expression evaluates to truthy value. + * + * @example + + +
+ + url of the template: {{template.url}} +
+
+
+
+
+
+ + function Ctrl($scope) { + $scope.templates = + [ { name: 'template1.html', url: 'template1.html'}, + { name: 'template2.html', url: 'template2.html'} ]; + $scope.template = $scope.templates[0]; + } + + + Content of template1.html + + + Content of template2.html + + + .slide-animate-container { + position:relative; + background:white; + border:1px solid black; + height:40px; + overflow:hidden; + } + + .slide-animate { + padding:10px; + } + + .slide-animate.ng-enter, .slide-animate.ng-leave { + -webkit-transition:all cubic-bezier(0.250, 0.460, 0.450, 0.940) 0.5s; + transition:all cubic-bezier(0.250, 0.460, 0.450, 0.940) 0.5s; + + position:absolute; + top:0; + left:0; + right:0; + bottom:0; + display:block; + padding:10px; + } + + .slide-animate.ng-enter { + top:-50px; + } + .slide-animate.ng-enter.ng-enter-active { + top:0; + } + + .slide-animate.ng-leave { + top:0; + } + .slide-animate.ng-leave.ng-leave-active { + top:50px; + } + + + var templateSelect = element(by.model('template')); + var includeElem = element(by.css('[ng-include]')); + + it('should load template1.html', function() { + expect(includeElem.getText()).toMatch(/Content of template1.html/); + }); + + it('should load template2.html', function() { + if (browser.params.browser == 'firefox') { + // Firefox can't handle using selects + // See https://github.com/angular/protractor/issues/480 + return; + } + templateSelect.click(); + templateSelect.element.all(by.css('option')).get(2).click(); + expect(includeElem.getText()).toMatch(/Content of template2.html/); + }); + + it('should change to blank', function() { + if (browser.params.browser == 'firefox') { + // Firefox can't handle using selects + return; + } + templateSelect.click(); + templateSelect.element.all(by.css('option')).get(0).click(); + expect(includeElem.isPresent()).toBe(false); + }); + +
+ */ + + +/** + * @ngdoc event + * @name ngInclude#$includeContentRequested + * @eventType emit on the scope ngInclude was declared in + * @description + * Emitted every time the ngInclude content is requested. + */ + + +/** + * @ngdoc event + * @name ngInclude#$includeContentLoaded + * @eventType emit on the current ngInclude scope + * @description + * Emitted every time the ngInclude content is reloaded. + */ +var ngIncludeDirective = ['$http', '$templateCache', '$anchorScroll', '$animate', '$sce', + function($http, $templateCache, $anchorScroll, $animate, $sce) { + return { + restrict: 'ECA', + priority: 400, + terminal: true, + transclude: 'element', + controller: angular.noop, + compile: function(element, attr) { + var srcExp = attr.ngInclude || attr.src, + onloadExp = attr.onload || '', + autoScrollExp = attr.autoscroll; + + return function(scope, $element, $attr, ctrl, $transclude) { + var changeCounter = 0, + currentScope, + previousElement, + currentElement; + + var cleanupLastIncludeContent = function() { + if(previousElement) { + previousElement.remove(); + previousElement = null; + } + if(currentScope) { + currentScope.$destroy(); + currentScope = null; + } + if(currentElement) { + $animate.leave(currentElement, function() { + previousElement = null; + }); + previousElement = currentElement; + currentElement = null; + } + }; + + scope.$watch($sce.parseAsResourceUrl(srcExp), function ngIncludeWatchAction(src) { + var afterAnimation = function() { + if (isDefined(autoScrollExp) && (!autoScrollExp || scope.$eval(autoScrollExp))) { + $anchorScroll(); + } + }; + var thisChangeId = ++changeCounter; + + if (src) { + $http.get(src, {cache: $templateCache}).success(function(response) { + if (thisChangeId !== changeCounter) return; + var newScope = scope.$new(); + ctrl.template = response; + + // Note: This will also link all children of ng-include that were contained in the original + // html. If that content contains controllers, ... they could pollute/change the scope. + // However, using ng-include on an element with additional content does not make sense... + // Note: We can't remove them in the cloneAttchFn of $transclude as that + // function is called before linking the content, which would apply child + // directives to non existing elements. + var clone = $transclude(newScope, function(clone) { + cleanupLastIncludeContent(); + $animate.enter(clone, null, $element, afterAnimation); + }); + + currentScope = newScope; + currentElement = clone; + + currentScope.$emit('$includeContentLoaded'); + scope.$eval(onloadExp); + }).error(function() { + if (thisChangeId === changeCounter) cleanupLastIncludeContent(); + }); + scope.$emit('$includeContentRequested'); + } else { + cleanupLastIncludeContent(); + ctrl.template = null; + } + }); + }; + } + }; +}]; + +// This directive is called during the $transclude call of the first `ngInclude` directive. +// It will replace and compile the content of the element with the loaded template. +// We need this directive so that the element content is already filled when +// the link function of another directive on the same element as ngInclude +// is called. +var ngIncludeFillContentDirective = ['$compile', + function($compile) { + return { + restrict: 'ECA', + priority: -400, + require: 'ngInclude', + link: function(scope, $element, $attr, ctrl) { + $element.html(ctrl.template); + $compile($element.contents())(scope); + } + }; + }]; + +/** + * @ngdoc directive + * @name ngInit + * @restrict AC + * + * @description + * The `ngInit` directive allows you to evaluate an expression in the + * current scope. + * + *
+ * The only appropriate use of `ngInit` is for aliasing special properties of + * {@link ng.directive:ngRepeat `ngRepeat`}, as seen in the demo below. Besides this case, you + * should use {@link guide/controller controllers} rather than `ngInit` + * to initialize values on a scope. + *
+ *
+ * **Note**: If you have assignment in `ngInit` along with {@link ng.$filter `$filter`}, make + * sure you have parenthesis for correct precedence: + *
+ *   
+ *
+ *
+ * + * @priority 450 + * + * @element ANY + * @param {expression} ngInit {@link guide/expression Expression} to eval. + * + * @example + + + +
+
+
+ list[ {{outerIndex}} ][ {{innerIndex}} ] = {{value}}; +
+
+
+
+ + it('should alias index positions', function() { + var elements = element.all(by.css('.example-init')); + expect(elements.get(0).getText()).toBe('list[ 0 ][ 0 ] = a;'); + expect(elements.get(1).getText()).toBe('list[ 0 ][ 1 ] = b;'); + expect(elements.get(2).getText()).toBe('list[ 1 ][ 0 ] = c;'); + expect(elements.get(3).getText()).toBe('list[ 1 ][ 1 ] = d;'); + }); + +
+ */ +var ngInitDirective = ngDirective({ + priority: 450, + compile: function() { + return { + pre: function(scope, element, attrs) { + scope.$eval(attrs.ngInit); + } + }; + } +}); + +/** + * @ngdoc directive + * @name ngNonBindable + * @restrict AC + * @priority 1000 + * + * @description + * The `ngNonBindable` directive tells Angular not to compile or bind the contents of the current + * DOM element. This is useful if the element contains what appears to be Angular directives and + * bindings but which should be ignored by Angular. This could be the case if you have a site that + * displays snippets of code, for instance. + * + * @element ANY + * + * @example + * In this example there are two locations where a simple interpolation binding (`{{}}`) is present, + * but the one wrapped in `ngNonBindable` is left alone. + * + * @example + + +
Normal: {{1 + 2}}
+
Ignored: {{1 + 2}}
+
+ + it('should check ng-non-bindable', function() { + expect(element(by.binding('1 + 2')).getText()).toContain('3'); + expect(element.all(by.css('div')).last().getText()).toMatch(/1 \+ 2/); + }); + +
+ */ +var ngNonBindableDirective = ngDirective({ terminal: true, priority: 1000 }); + +/** + * @ngdoc directive + * @name ngPluralize + * @restrict EA + * + * @description + * `ngPluralize` is a directive that displays messages according to en-US localization rules. + * These rules are bundled with angular.js, but can be overridden + * (see {@link guide/i18n Angular i18n} dev guide). You configure ngPluralize directive + * by specifying the mappings between + * [plural categories](http://unicode.org/repos/cldr-tmp/trunk/diff/supplemental/language_plural_rules.html) + * and the strings to be displayed. + * + * # Plural categories and explicit number rules + * There are two + * [plural categories](http://unicode.org/repos/cldr-tmp/trunk/diff/supplemental/language_plural_rules.html) + * in Angular's default en-US locale: "one" and "other". + * + * While a plural category may match many numbers (for example, in en-US locale, "other" can match + * any number that is not 1), an explicit number rule can only match one number. For example, the + * explicit number rule for "3" matches the number 3. There are examples of plural categories + * and explicit number rules throughout the rest of this documentation. + * + * # Configuring ngPluralize + * You configure ngPluralize by providing 2 attributes: `count` and `when`. + * You can also provide an optional attribute, `offset`. + * + * The value of the `count` attribute can be either a string or an {@link guide/expression + * Angular expression}; these are evaluated on the current scope for its bound value. + * + * The `when` attribute specifies the mappings between plural categories and the actual + * string to be displayed. The value of the attribute should be a JSON object. + * + * The following example shows how to configure ngPluralize: + * + * ```html + * + * + *``` + * + * In the example, `"0: Nobody is viewing."` is an explicit number rule. If you did not + * specify this rule, 0 would be matched to the "other" category and "0 people are viewing" + * would be shown instead of "Nobody is viewing". You can specify an explicit number rule for + * other numbers, for example 12, so that instead of showing "12 people are viewing", you can + * show "a dozen people are viewing". + * + * You can use a set of closed braces (`{}`) as a placeholder for the number that you want substituted + * into pluralized strings. In the previous example, Angular will replace `{}` with + * `{{personCount}}`. The closed braces `{}` is a placeholder + * for {{numberExpression}}. + * + * # Configuring ngPluralize with offset + * The `offset` attribute allows further customization of pluralized text, which can result in + * a better user experience. For example, instead of the message "4 people are viewing this document", + * you might display "John, Kate and 2 others are viewing this document". + * The offset attribute allows you to offset a number by any desired value. + * Let's take a look at an example: + * + * ```html + * + * + * ``` + * + * Notice that we are still using two plural categories(one, other), but we added + * three explicit number rules 0, 1 and 2. + * When one person, perhaps John, views the document, "John is viewing" will be shown. + * When three people view the document, no explicit number rule is found, so + * an offset of 2 is taken off 3, and Angular uses 1 to decide the plural category. + * In this case, plural category 'one' is matched and "John, Marry and one other person are viewing" + * is shown. + * + * Note that when you specify offsets, you must provide explicit number rules for + * numbers from 0 up to and including the offset. If you use an offset of 3, for example, + * you must provide explicit number rules for 0, 1, 2 and 3. You must also provide plural strings for + * plural categories "one" and "other". + * + * @param {string|expression} count The variable to be bound to. + * @param {string} when The mapping between plural category to its corresponding strings. + * @param {number=} offset Offset to deduct from the total number. + * + * @example + + + +
+ Person 1:
+ Person 2:
+ Number of People:
+ + + Without Offset: + +
+ + + With Offset(2): + + +
+
+ + it('should show correct pluralized string', function() { + var withoutOffset = element.all(by.css('ng-pluralize')).get(0); + var withOffset = element.all(by.css('ng-pluralize')).get(1); + var countInput = element(by.model('personCount')); + + expect(withoutOffset.getText()).toEqual('1 person is viewing.'); + expect(withOffset.getText()).toEqual('Igor is viewing.'); + + countInput.clear(); + countInput.sendKeys('0'); + + expect(withoutOffset.getText()).toEqual('Nobody is viewing.'); + expect(withOffset.getText()).toEqual('Nobody is viewing.'); + + countInput.clear(); + countInput.sendKeys('2'); + + expect(withoutOffset.getText()).toEqual('2 people are viewing.'); + expect(withOffset.getText()).toEqual('Igor and Misko are viewing.'); + + countInput.clear(); + countInput.sendKeys('3'); + + expect(withoutOffset.getText()).toEqual('3 people are viewing.'); + expect(withOffset.getText()).toEqual('Igor, Misko and one other person are viewing.'); + + countInput.clear(); + countInput.sendKeys('4'); + + expect(withoutOffset.getText()).toEqual('4 people are viewing.'); + expect(withOffset.getText()).toEqual('Igor, Misko and 2 other people are viewing.'); + }); + it('should show data-bound names', function() { + var withOffset = element.all(by.css('ng-pluralize')).get(1); + var personCount = element(by.model('personCount')); + var person1 = element(by.model('person1')); + var person2 = element(by.model('person2')); + personCount.clear(); + personCount.sendKeys('4'); + person1.clear(); + person1.sendKeys('Di'); + person2.clear(); + person2.sendKeys('Vojta'); + expect(withOffset.getText()).toEqual('Di, Vojta and 2 other people are viewing.'); + }); + +
+ */ +var ngPluralizeDirective = ['$locale', '$interpolate', function($locale, $interpolate) { + var BRACE = /{}/g; + return { + restrict: 'EA', + link: function(scope, element, attr) { + var numberExp = attr.count, + whenExp = attr.$attr.when && element.attr(attr.$attr.when), // we have {{}} in attrs + offset = attr.offset || 0, + whens = scope.$eval(whenExp) || {}, + whensExpFns = {}, + startSymbol = $interpolate.startSymbol(), + endSymbol = $interpolate.endSymbol(), + isWhen = /^when(Minus)?(.+)$/; + + forEach(attr, function(expression, attributeName) { + if (isWhen.test(attributeName)) { + whens[lowercase(attributeName.replace('when', '').replace('Minus', '-'))] = + element.attr(attr.$attr[attributeName]); + } + }); + forEach(whens, function(expression, key) { + whensExpFns[key] = + $interpolate(expression.replace(BRACE, startSymbol + numberExp + '-' + + offset + endSymbol)); + }); + + scope.$watch(function ngPluralizeWatch() { + var value = parseFloat(scope.$eval(numberExp)); + + if (!isNaN(value)) { + //if explicit number rule such as 1, 2, 3... is defined, just use it. Otherwise, + //check it against pluralization rules in $locale service + if (!(value in whens)) value = $locale.pluralCat(value - offset); + return whensExpFns[value](scope, element, true); + } else { + return ''; + } + }, function ngPluralizeWatchAction(newVal) { + element.text(newVal); + }); + } + }; +}]; + +/** + * @ngdoc directive + * @name ngRepeat + * + * @description + * The `ngRepeat` directive instantiates a template once per item from a collection. Each template + * instance gets its own scope, where the given loop variable is set to the current collection item, + * and `$index` is set to the item index or key. + * + * Special properties are exposed on the local scope of each template instance, including: + * + * | Variable | Type | Details | + * |-----------|-----------------|-----------------------------------------------------------------------------| + * | `$index` | {@type number} | iterator offset of the repeated element (0..length-1) | + * | `$first` | {@type boolean} | true if the repeated element is first in the iterator. | + * | `$middle` | {@type boolean} | true if the repeated element is between the first and last in the iterator. | + * | `$last` | {@type boolean} | true if the repeated element is last in the iterator. | + * | `$even` | {@type boolean} | true if the iterator position `$index` is even (otherwise false). | + * | `$odd` | {@type boolean} | true if the iterator position `$index` is odd (otherwise false). | + * + * Creating aliases for these properties is possible with {@link ng.directive:ngInit `ngInit`}. + * This may be useful when, for instance, nesting ngRepeats. + * + * # Special repeat start and end points + * To repeat a series of elements instead of just one parent element, ngRepeat (as well as other ng directives) supports extending + * the range of the repeater by defining explicit start and end points by using **ng-repeat-start** and **ng-repeat-end** respectively. + * The **ng-repeat-start** directive works the same as **ng-repeat**, but will repeat all the HTML code (including the tag it's defined on) + * up to and including the ending HTML tag where **ng-repeat-end** is placed. + * + * The example below makes use of this feature: + * ```html + *
+ * Header {{ item }} + *
+ *
+ * Body {{ item }} + *
+ *
+ * Footer {{ item }} + *
+ * ``` + * + * And with an input of {@type ['A','B']} for the items variable in the example above, the output will evaluate to: + * ```html + *
+ * Header A + *
+ *
+ * Body A + *
+ *
+ * Footer A + *
+ *
+ * Header B + *
+ *
+ * Body B + *
+ *
+ * Footer B + *
+ * ``` + * + * The custom start and end points for ngRepeat also support all other HTML directive syntax flavors provided in AngularJS (such + * as **data-ng-repeat-start**, **x-ng-repeat-start** and **ng:repeat-start**). + * + * @animations + * **.enter** - when a new item is added to the list or when an item is revealed after a filter + * + * **.leave** - when an item is removed from the list or when an item is filtered out + * + * **.move** - when an adjacent item is filtered out causing a reorder or when the item contents are reordered + * + * @element ANY + * @scope + * @priority 1000 + * @param {repeat_expression} ngRepeat The expression indicating how to enumerate a collection. These + * formats are currently supported: + * + * * `variable in expression` – where variable is the user defined loop variable and `expression` + * is a scope expression giving the collection to enumerate. + * + * For example: `album in artist.albums`. + * + * * `(key, value) in expression` – where `key` and `value` can be any user defined identifiers, + * and `expression` is the scope expression giving the collection to enumerate. + * + * For example: `(name, age) in {'adam':10, 'amalie':12}`. + * + * * `variable in expression track by tracking_expression` – You can also provide an optional tracking function + * which can be used to associate the objects in the collection with the DOM elements. If no tracking function + * is specified the ng-repeat associates elements by identity in the collection. It is an error to have + * more than one tracking function to resolve to the same key. (This would mean that two distinct objects are + * mapped to the same DOM element, which is not possible.) Filters should be applied to the expression, + * before specifying a tracking expression. + * + * For example: `item in items` is equivalent to `item in items track by $id(item)`. This implies that the DOM elements + * will be associated by item identity in the array. + * + * For example: `item in items track by $id(item)`. A built in `$id()` function can be used to assign a unique + * `$$hashKey` property to each item in the array. This property is then used as a key to associated DOM elements + * with the corresponding item in the array by identity. Moving the same object in array would move the DOM + * element in the same way in the DOM. + * + * For example: `item in items track by item.id` is a typical pattern when the items come from the database. In this + * case the object identity does not matter. Two objects are considered equivalent as long as their `id` + * property is same. + * + * For example: `item in items | filter:searchText track by item.id` is a pattern that might be used to apply a filter + * to items in conjunction with a tracking expression. + * + * @example + * This example initializes the scope to a list of names and + * then uses `ngRepeat` to display every person: + + +
+ I have {{friends.length}} friends. They are: + +
    +
  • + [{{$index + 1}}] {{friend.name}} who is {{friend.age}} years old. +
  • +
+
+
+ + .example-animate-container { + background:white; + border:1px solid black; + list-style:none; + margin:0; + padding:0 10px; + } + + .animate-repeat { + line-height:40px; + list-style:none; + box-sizing:border-box; + } + + .animate-repeat.ng-move, + .animate-repeat.ng-enter, + .animate-repeat.ng-leave { + -webkit-transition:all linear 0.5s; + transition:all linear 0.5s; + } + + .animate-repeat.ng-leave.ng-leave-active, + .animate-repeat.ng-move, + .animate-repeat.ng-enter { + opacity:0; + max-height:0; + } + + .animate-repeat.ng-leave, + .animate-repeat.ng-move.ng-move-active, + .animate-repeat.ng-enter.ng-enter-active { + opacity:1; + max-height:40px; + } + + + var friends = element.all(by.repeater('friend in friends')); + + it('should render initial data set', function() { + expect(friends.count()).toBe(10); + expect(friends.get(0).getText()).toEqual('[1] John who is 25 years old.'); + expect(friends.get(1).getText()).toEqual('[2] Jessie who is 30 years old.'); + expect(friends.last().getText()).toEqual('[10] Samantha who is 60 years old.'); + expect(element(by.binding('friends.length')).getText()) + .toMatch("I have 10 friends. They are:"); + }); + + it('should update repeater when filter predicate changes', function() { + expect(friends.count()).toBe(10); + + element(by.model('q')).sendKeys('ma'); + + expect(friends.count()).toBe(2); + expect(friends.get(0).getText()).toEqual('[1] Mary who is 28 years old.'); + expect(friends.last().getText()).toEqual('[2] Samantha who is 60 years old.'); + }); + +
+ */ +var ngRepeatDirective = ['$parse', '$animate', function($parse, $animate) { + var NG_REMOVED = '$$NG_REMOVED'; + var ngRepeatMinErr = minErr('ngRepeat'); + return { + transclude: 'element', + priority: 1000, + terminal: true, + $$tlb: true, + link: function($scope, $element, $attr, ctrl, $transclude){ + var expression = $attr.ngRepeat; + var match = expression.match(/^\s*([\s\S]+?)\s+in\s+([\s\S]+?)(?:\s+track\s+by\s+([\s\S]+?))?\s*$/), + trackByExp, trackByExpGetter, trackByIdExpFn, trackByIdArrayFn, trackByIdObjFn, + lhs, rhs, valueIdentifier, keyIdentifier, + hashFnLocals = {$id: hashKey}; + + if (!match) { + throw ngRepeatMinErr('iexp', "Expected expression in form of '_item_ in _collection_[ track by _id_]' but got '{0}'.", + expression); + } + + lhs = match[1]; + rhs = match[2]; + trackByExp = match[3]; + + if (trackByExp) { + trackByExpGetter = $parse(trackByExp); + trackByIdExpFn = function(key, value, index) { + // assign key, value, and $index to the locals so that they can be used in hash functions + if (keyIdentifier) hashFnLocals[keyIdentifier] = key; + hashFnLocals[valueIdentifier] = value; + hashFnLocals.$index = index; + return trackByExpGetter($scope, hashFnLocals); + }; + } else { + trackByIdArrayFn = function(key, value) { + return hashKey(value); + }; + trackByIdObjFn = function(key) { + return key; + }; + } + + match = lhs.match(/^(?:([\$\w]+)|\(([\$\w]+)\s*,\s*([\$\w]+)\))$/); + if (!match) { + throw ngRepeatMinErr('iidexp', "'_item_' in '_item_ in _collection_' should be an identifier or '(_key_, _value_)' expression, but got '{0}'.", + lhs); + } + valueIdentifier = match[3] || match[1]; + keyIdentifier = match[2]; + + // Store a list of elements from previous run. This is a hash where key is the item from the + // iterator, and the value is objects with following properties. + // - scope: bound scope + // - element: previous element. + // - index: position + var lastBlockMap = {}; + + //watch props + $scope.$watchCollection(rhs, function ngRepeatAction(collection){ + var index, length, + previousNode = $element[0], // current position of the node + nextNode, + // Same as lastBlockMap but it has the current state. It will become the + // lastBlockMap on the next iteration. + nextBlockMap = {}, + arrayLength, + childScope, + key, value, // key/value of iteration + trackById, + trackByIdFn, + collectionKeys, + block, // last object information {scope, element, id} + nextBlockOrder = [], + elementsToRemove; + + + if (isArrayLike(collection)) { + collectionKeys = collection; + trackByIdFn = trackByIdExpFn || trackByIdArrayFn; + } else { + trackByIdFn = trackByIdExpFn || trackByIdObjFn; + // if object, extract keys, sort them and use to determine order of iteration over obj props + collectionKeys = []; + for (key in collection) { + if (collection.hasOwnProperty(key) && key.charAt(0) != '$') { + collectionKeys.push(key); + } + } + collectionKeys.sort(); + } + + arrayLength = collectionKeys.length; + + // locate existing items + length = nextBlockOrder.length = collectionKeys.length; + for(index = 0; index < length; index++) { + key = (collection === collectionKeys) ? index : collectionKeys[index]; + value = collection[key]; + trackById = trackByIdFn(key, value, index); + assertNotHasOwnProperty(trackById, '`track by` id'); + if(lastBlockMap.hasOwnProperty(trackById)) { + block = lastBlockMap[trackById]; + delete lastBlockMap[trackById]; + nextBlockMap[trackById] = block; + nextBlockOrder[index] = block; + } else if (nextBlockMap.hasOwnProperty(trackById)) { + // restore lastBlockMap + forEach(nextBlockOrder, function(block) { + if (block && block.scope) lastBlockMap[block.id] = block; + }); + // This is a duplicate and we need to throw an error + throw ngRepeatMinErr('dupes', "Duplicates in a repeater are not allowed. Use 'track by' expression to specify unique keys. Repeater: {0}, Duplicate key: {1}", + expression, trackById); + } else { + // new never before seen block + nextBlockOrder[index] = { id: trackById }; + nextBlockMap[trackById] = false; + } + } + + // remove existing items + for (key in lastBlockMap) { + // lastBlockMap is our own object so we don't need to use special hasOwnPropertyFn + if (lastBlockMap.hasOwnProperty(key)) { + block = lastBlockMap[key]; + elementsToRemove = getBlockElements(block.clone); + $animate.leave(elementsToRemove); + forEach(elementsToRemove, function(element) { element[NG_REMOVED] = true; }); + block.scope.$destroy(); + } + } + + // we are not using forEach for perf reasons (trying to avoid #call) + for (index = 0, length = collectionKeys.length; index < length; index++) { + key = (collection === collectionKeys) ? index : collectionKeys[index]; + value = collection[key]; + block = nextBlockOrder[index]; + if (nextBlockOrder[index - 1]) previousNode = getBlockEnd(nextBlockOrder[index - 1]); + + if (block.scope) { + // if we have already seen this object, then we need to reuse the + // associated scope/element + childScope = block.scope; + + nextNode = previousNode; + do { + nextNode = nextNode.nextSibling; + } while(nextNode && nextNode[NG_REMOVED]); + + if (getBlockStart(block) != nextNode) { + // existing item which got moved + $animate.move(getBlockElements(block.clone), null, jqLite(previousNode)); + } + previousNode = getBlockEnd(block); + } else { + // new item which we don't know about + childScope = $scope.$new(); + } + + childScope[valueIdentifier] = value; + if (keyIdentifier) childScope[keyIdentifier] = key; + childScope.$index = index; + childScope.$first = (index === 0); + childScope.$last = (index === (arrayLength - 1)); + childScope.$middle = !(childScope.$first || childScope.$last); + // jshint bitwise: false + childScope.$odd = !(childScope.$even = (index&1) === 0); + // jshint bitwise: true + + if (!block.scope) { + $transclude(childScope, function(clone) { + clone[clone.length++] = document.createComment(' end ngRepeat: ' + expression + ' '); + $animate.enter(clone, null, jqLite(previousNode)); + previousNode = clone; + block.scope = childScope; + // Note: We only need the first/last node of the cloned nodes. + // However, we need to keep the reference to the jqlite wrapper as it might be changed later + // by a directive with templateUrl when its template arrives. + block.clone = clone; + nextBlockMap[block.id] = block; + }); + } + } + lastBlockMap = nextBlockMap; + }); + } + }; + + function getBlockStart(block) { + return block.clone[0]; + } + + function getBlockEnd(block) { + return block.clone[block.clone.length - 1]; + } +}]; + +/** + * @ngdoc directive + * @name ngShow + * + * @description + * The `ngShow` directive shows or hides the given HTML element based on the expression + * provided to the ngShow attribute. The element is shown or hidden by removing or adding + * the `ng-hide` CSS class onto the element. The `.ng-hide` CSS class is predefined + * in AngularJS and sets the display style to none (using an !important flag). + * For CSP mode please add `angular-csp.css` to your html file (see {@link ng.directive:ngCsp ngCsp}). + * + * ```html + * + *
+ * + * + *
+ * ``` + * + * When the ngShow expression evaluates to false then the ng-hide CSS class is added to the class attribute + * on the element causing it to become hidden. When true, the ng-hide CSS class is removed + * from the element causing the element not to appear hidden. + * + *
+ * **Note:** Here is a list of values that ngShow will consider as a falsy value (case insensitive):
+ * "f" / "0" / "false" / "no" / "n" / "[]" + *
+ * + * ## Why is !important used? + * + * You may be wondering why !important is used for the .ng-hide CSS class. This is because the `.ng-hide` selector + * can be easily overridden by heavier selectors. For example, something as simple + * as changing the display style on a HTML list item would make hidden elements appear visible. + * This also becomes a bigger issue when dealing with CSS frameworks. + * + * By using !important, the show and hide behavior will work as expected despite any clash between CSS selector + * specificity (when !important isn't used with any conflicting styles). If a developer chooses to override the + * styling to change how to hide an element then it is just a matter of using !important in their own CSS code. + * + * ### Overriding .ng-hide + * + * By default, the `.ng-hide` class will style the element with `display:none!important`. If you wish to change + * the hide behavior with ngShow/ngHide then this can be achieved by restating the styles for the `.ng-hide` + * class in CSS: + * + * ```css + * .ng-hide { + * //this is just another form of hiding an element + * display:block!important; + * position:absolute; + * top:-9999px; + * left:-9999px; + * } + * ``` + * + * By default you don't need to override in CSS anything and the animations will work around the display style. + * + * ## A note about animations with ngShow + * + * Animations in ngShow/ngHide work with the show and hide events that are triggered when the directive expression + * is true and false. This system works like the animation system present with ngClass except that + * you must also include the !important flag to override the display property + * so that you can perform an animation when the element is hidden during the time of the animation. + * + * ```css + * // + * //a working example can be found at the bottom of this page + * // + * .my-element.ng-hide-add, .my-element.ng-hide-remove { + * transition:0.5s linear all; + * } + * + * .my-element.ng-hide-add { ... } + * .my-element.ng-hide-add.ng-hide-add-active { ... } + * .my-element.ng-hide-remove { ... } + * .my-element.ng-hide-remove.ng-hide-remove-active { ... } + * ``` + * + * Keep in mind that, as of AngularJS version 1.2.17 (and 1.3.0-beta.11), there is no need to change the display + * property to block during animation states--ngAnimate will handle the style toggling automatically for you. + * + * @animations + * addClass: .ng-hide - happens after the ngShow expression evaluates to a truthy value and the just before contents are set to visible + * removeClass: .ng-hide - happens after the ngShow expression evaluates to a non truthy value and just before the contents are set to hidden + * + * @element ANY + * @param {expression} ngShow If the {@link guide/expression expression} is truthy + * then the element is shown or hidden respectively. + * + * @example + + + Click me:
+
+ Show: +
+ I show up when your checkbox is checked. +
+
+
+ Hide: +
+ I hide when your checkbox is checked. +
+
+
+ + @import url(//netdna.bootstrapcdn.com/bootstrap/3.0.0/css/bootstrap-glyphicons.css); + + + .animate-show { + -webkit-transition:all linear 0.5s; + transition:all linear 0.5s; + line-height:20px; + opacity:1; + padding:10px; + border:1px solid black; + background:white; + } + + .animate-show.ng-hide { + line-height:0; + opacity:0; + padding:0 10px; + } + + .check-element { + padding:10px; + border:1px solid black; + background:white; + } + + + var thumbsUp = element(by.css('span.glyphicon-thumbs-up')); + var thumbsDown = element(by.css('span.glyphicon-thumbs-down')); + + it('should check ng-show / ng-hide', function() { + expect(thumbsUp.isDisplayed()).toBeFalsy(); + expect(thumbsDown.isDisplayed()).toBeTruthy(); + + element(by.model('checked')).click(); + + expect(thumbsUp.isDisplayed()).toBeTruthy(); + expect(thumbsDown.isDisplayed()).toBeFalsy(); + }); + +
+ */ +var ngShowDirective = ['$animate', function($animate) { + return function(scope, element, attr) { + scope.$watch(attr.ngShow, function ngShowWatchAction(value){ + $animate[toBoolean(value) ? 'removeClass' : 'addClass'](element, 'ng-hide'); + }); + }; +}]; + + +/** + * @ngdoc directive + * @name ngHide + * + * @description + * The `ngHide` directive shows or hides the given HTML element based on the expression + * provided to the ngHide attribute. The element is shown or hidden by removing or adding + * the `ng-hide` CSS class onto the element. The `.ng-hide` CSS class is predefined + * in AngularJS and sets the display style to none (using an !important flag). + * For CSP mode please add `angular-csp.css` to your html file (see {@link ng.directive:ngCsp ngCsp}). + * + * ```html + * + *
+ * + * + *
+ * ``` + * + * When the ngHide expression evaluates to true then the .ng-hide CSS class is added to the class attribute + * on the element causing it to become hidden. When false, the ng-hide CSS class is removed + * from the element causing the element not to appear hidden. + * + *
+ * **Note:** Here is a list of values that ngHide will consider as a falsy value (case insensitive):
+ * "f" / "0" / "false" / "no" / "n" / "[]" + *
+ * + * ## Why is !important used? + * + * You may be wondering why !important is used for the .ng-hide CSS class. This is because the `.ng-hide` selector + * can be easily overridden by heavier selectors. For example, something as simple + * as changing the display style on a HTML list item would make hidden elements appear visible. + * This also becomes a bigger issue when dealing with CSS frameworks. + * + * By using !important, the show and hide behavior will work as expected despite any clash between CSS selector + * specificity (when !important isn't used with any conflicting styles). If a developer chooses to override the + * styling to change how to hide an element then it is just a matter of using !important in their own CSS code. + * + * ### Overriding .ng-hide + * + * By default, the `.ng-hide` class will style the element with `display:none!important`. If you wish to change + * the hide behavior with ngShow/ngHide then this can be achieved by restating the styles for the `.ng-hide` + * class in CSS: + * + * ```css + * .ng-hide { + * //this is just another form of hiding an element + * display:block!important; + * position:absolute; + * top:-9999px; + * left:-9999px; + * } + * ``` + * + * By default you don't need to override in CSS anything and the animations will work around the display style. + * + * ## A note about animations with ngHide + * + * Animations in ngShow/ngHide work with the show and hide events that are triggered when the directive expression + * is true and false. This system works like the animation system present with ngClass, except that the `.ng-hide` + * CSS class is added and removed for you instead of your own CSS class. + * + * ```css + * // + * //a working example can be found at the bottom of this page + * // + * .my-element.ng-hide-add, .my-element.ng-hide-remove { + * transition:0.5s linear all; + * } + * + * .my-element.ng-hide-add { ... } + * .my-element.ng-hide-add.ng-hide-add-active { ... } + * .my-element.ng-hide-remove { ... } + * .my-element.ng-hide-remove.ng-hide-remove-active { ... } + * ``` + * + * Keep in mind that, as of AngularJS version 1.2.17 (and 1.3.0-beta.11), there is no need to change the display + * property to block during animation states--ngAnimate will handle the style toggling automatically for you. + * + * @animations + * removeClass: .ng-hide - happens after the ngHide expression evaluates to a truthy value and just before the contents are set to hidden + * addClass: .ng-hide - happens after the ngHide expression evaluates to a non truthy value and just before the contents are set to visible + * + * @element ANY + * @param {expression} ngHide If the {@link guide/expression expression} is truthy then + * the element is shown or hidden respectively. + * + * @example + + + Click me:
+
+ Show: +
+ I show up when your checkbox is checked. +
+
+
+ Hide: +
+ I hide when your checkbox is checked. +
+
+
+ + @import url(//netdna.bootstrapcdn.com/bootstrap/3.0.0/css/bootstrap-glyphicons.css); + + + .animate-hide { + -webkit-transition:all linear 0.5s; + transition:all linear 0.5s; + line-height:20px; + opacity:1; + padding:10px; + border:1px solid black; + background:white; + } + + .animate-hide.ng-hide { + line-height:0; + opacity:0; + padding:0 10px; + } + + .check-element { + padding:10px; + border:1px solid black; + background:white; + } + + + var thumbsUp = element(by.css('span.glyphicon-thumbs-up')); + var thumbsDown = element(by.css('span.glyphicon-thumbs-down')); + + it('should check ng-show / ng-hide', function() { + expect(thumbsUp.isDisplayed()).toBeFalsy(); + expect(thumbsDown.isDisplayed()).toBeTruthy(); + + element(by.model('checked')).click(); + + expect(thumbsUp.isDisplayed()).toBeTruthy(); + expect(thumbsDown.isDisplayed()).toBeFalsy(); + }); + +
+ */ +var ngHideDirective = ['$animate', function($animate) { + return function(scope, element, attr) { + scope.$watch(attr.ngHide, function ngHideWatchAction(value){ + $animate[toBoolean(value) ? 'addClass' : 'removeClass'](element, 'ng-hide'); + }); + }; +}]; + +/** + * @ngdoc directive + * @name ngStyle + * @restrict AC + * + * @description + * The `ngStyle` directive allows you to set CSS style on an HTML element conditionally. + * + * @element ANY + * @param {expression} ngStyle + * + * {@link guide/expression Expression} which evals to an + * object whose keys are CSS style names and values are corresponding values for those CSS + * keys. + * + * Since some CSS style names are not valid keys for an object, they must be quoted. + * See the 'background-color' style in the example below. + * + * @example + + + + + +
+ Sample Text +
myStyle={{myStyle}}
+
+ + span { + color: black; + } + + + var colorSpan = element(by.css('span')); + + iit('should check ng-style', function() { + expect(colorSpan.getCssValue('color')).toBe('rgba(0, 0, 0, 1)'); + element(by.css('input[value=\'set color\']')).click(); + expect(colorSpan.getCssValue('color')).toBe('rgba(255, 0, 0, 1)'); + element(by.css('input[value=clear]')).click(); + expect(colorSpan.getCssValue('color')).toBe('rgba(0, 0, 0, 1)'); + }); + +
+ */ +var ngStyleDirective = ngDirective(function(scope, element, attr) { + scope.$watch(attr.ngStyle, function ngStyleWatchAction(newStyles, oldStyles) { + if (oldStyles && (newStyles !== oldStyles)) { + forEach(oldStyles, function(val, style) { element.css(style, '');}); + } + if (newStyles) element.css(newStyles); + }, true); +}); + +/** + * @ngdoc directive + * @name ngSwitch + * @restrict EA + * + * @description + * The `ngSwitch` directive is used to conditionally swap DOM structure on your template based on a scope expression. + * Elements within `ngSwitch` but without `ngSwitchWhen` or `ngSwitchDefault` directives will be preserved at the location + * as specified in the template. + * + * The directive itself works similar to ngInclude, however, instead of downloading template code (or loading it + * from the template cache), `ngSwitch` simply chooses one of the nested elements and makes it visible based on which element + * matches the value obtained from the evaluated expression. In other words, you define a container element + * (where you place the directive), place an expression on the **`on="..."` attribute** + * (or the **`ng-switch="..."` attribute**), define any inner elements inside of the directive and place + * a when attribute per element. The when attribute is used to inform ngSwitch which element to display when the on + * expression is evaluated. If a matching expression is not found via a when attribute then an element with the default + * attribute is displayed. + * + *
+ * Be aware that the attribute values to match against cannot be expressions. They are interpreted + * as literal string values to match against. + * For example, **`ng-switch-when="someVal"`** will match against the string `"someVal"` not against the + * value of the expression `$scope.someVal`. + *
+ + * @animations + * enter - happens after the ngSwitch contents change and the matched child element is placed inside the container + * leave - happens just after the ngSwitch contents change and just before the former contents are removed from the DOM + * + * @usage + * + * ``` + * + * ... + * ... + * ... + * + * ``` + * + * + * @scope + * @priority 800 + * @param {*} ngSwitch|on expression to match against ng-switch-when. + * On child elements add: + * + * * `ngSwitchWhen`: the case statement to match against. If match then this + * case will be displayed. If the same match appears multiple times, all the + * elements will be displayed. + * * `ngSwitchDefault`: the default case when no other case match. If there + * are multiple default cases, all of them will be displayed when no other + * case match. + * + * + * @example + + +
+ + selection={{selection}} +
+
+
Settings Div
+
Home Span
+
default
+
+
+
+ + function Ctrl($scope) { + $scope.items = ['settings', 'home', 'other']; + $scope.selection = $scope.items[0]; + } + + + .animate-switch-container { + position:relative; + background:white; + border:1px solid black; + height:40px; + overflow:hidden; + } + + .animate-switch { + padding:10px; + } + + .animate-switch.ng-animate { + -webkit-transition:all cubic-bezier(0.250, 0.460, 0.450, 0.940) 0.5s; + transition:all cubic-bezier(0.250, 0.460, 0.450, 0.940) 0.5s; + + position:absolute; + top:0; + left:0; + right:0; + bottom:0; + } + + .animate-switch.ng-leave.ng-leave-active, + .animate-switch.ng-enter { + top:-50px; + } + .animate-switch.ng-leave, + .animate-switch.ng-enter.ng-enter-active { + top:0; + } + + + var switchElem = element(by.css('[ng-switch]')); + var select = element(by.model('selection')); + + it('should start in settings', function() { + expect(switchElem.getText()).toMatch(/Settings Div/); + }); + it('should change to home', function() { + select.element.all(by.css('option')).get(1).click(); + expect(switchElem.getText()).toMatch(/Home Span/); + }); + it('should select default', function() { + select.element.all(by.css('option')).get(2).click(); + expect(switchElem.getText()).toMatch(/default/); + }); + +
+ */ +var ngSwitchDirective = ['$animate', function($animate) { + return { + restrict: 'EA', + require: 'ngSwitch', + + // asks for $scope to fool the BC controller module + controller: ['$scope', function ngSwitchController() { + this.cases = {}; + }], + link: function(scope, element, attr, ngSwitchController) { + var watchExpr = attr.ngSwitch || attr.on, + selectedTranscludes = [], + selectedElements = [], + previousElements = [], + selectedScopes = []; + + scope.$watch(watchExpr, function ngSwitchWatchAction(value) { + var i, ii; + for (i = 0, ii = previousElements.length; i < ii; ++i) { + previousElements[i].remove(); + } + previousElements.length = 0; + + for (i = 0, ii = selectedScopes.length; i < ii; ++i) { + var selected = selectedElements[i]; + selectedScopes[i].$destroy(); + previousElements[i] = selected; + $animate.leave(selected, function() { + previousElements.splice(i, 1); + }); + } + + selectedElements.length = 0; + selectedScopes.length = 0; + + if ((selectedTranscludes = ngSwitchController.cases['!' + value] || ngSwitchController.cases['?'])) { + scope.$eval(attr.change); + forEach(selectedTranscludes, function(selectedTransclude) { + var selectedScope = scope.$new(); + selectedScopes.push(selectedScope); + selectedTransclude.transclude(selectedScope, function(caseElement) { + var anchor = selectedTransclude.element; + + selectedElements.push(caseElement); + $animate.enter(caseElement, anchor.parent(), anchor); + }); + }); + } + }); + } + }; +}]; + +var ngSwitchWhenDirective = ngDirective({ + transclude: 'element', + priority: 800, + require: '^ngSwitch', + link: function(scope, element, attrs, ctrl, $transclude) { + ctrl.cases['!' + attrs.ngSwitchWhen] = (ctrl.cases['!' + attrs.ngSwitchWhen] || []); + ctrl.cases['!' + attrs.ngSwitchWhen].push({ transclude: $transclude, element: element }); + } +}); + +var ngSwitchDefaultDirective = ngDirective({ + transclude: 'element', + priority: 800, + require: '^ngSwitch', + link: function(scope, element, attr, ctrl, $transclude) { + ctrl.cases['?'] = (ctrl.cases['?'] || []); + ctrl.cases['?'].push({ transclude: $transclude, element: element }); + } +}); + +/** + * @ngdoc directive + * @name ngTransclude + * @restrict AC + * + * @description + * Directive that marks the insertion point for the transcluded DOM of the nearest parent directive that uses transclusion. + * + * Any existing content of the element that this directive is placed on will be removed before the transcluded content is inserted. + * + * @element ANY + * + * @example + + + +
+
+
+ {{text}} +
+
+ + it('should have transcluded', function() { + var titleElement = element(by.model('title')); + titleElement.clear(); + titleElement.sendKeys('TITLE'); + var textElement = element(by.model('text')); + textElement.clear(); + textElement.sendKeys('TEXT'); + expect(element(by.binding('title')).getText()).toEqual('TITLE'); + expect(element(by.binding('text')).getText()).toEqual('TEXT'); + }); + +
+ * + */ +var ngTranscludeDirective = ngDirective({ + link: function($scope, $element, $attrs, controller, $transclude) { + if (!$transclude) { + throw minErr('ngTransclude')('orphan', + 'Illegal use of ngTransclude directive in the template! ' + + 'No parent directive that requires a transclusion found. ' + + 'Element: {0}', + startingTag($element)); + } + + $transclude(function(clone) { + $element.empty(); + $element.append(clone); + }); + } +}); + +/** + * @ngdoc directive + * @name script + * @restrict E + * + * @description + * Load the content of a ` + + Load inlined template +
+ + + it('should load template defined inside script tag', function() { + element(by.css('#tpl-link')).click(); + expect(element(by.css('#tpl-content')).getText()).toMatch(/Content of the template/); + }); + + + */ +var scriptDirective = ['$templateCache', function($templateCache) { + return { + restrict: 'E', + terminal: true, + compile: function(element, attr) { + if (attr.type == 'text/ng-template') { + var templateUrl = attr.id, + // IE is not consistent, in scripts we have to read .text but in other nodes we have to read .textContent + text = element[0].text; + + $templateCache.put(templateUrl, text); + } + } + }; +}]; + +var ngOptionsMinErr = minErr('ngOptions'); +/** + * @ngdoc directive + * @name select + * @restrict E + * + * @description + * HTML `SELECT` element with angular data-binding. + * + * # `ngOptions` + * + * The `ngOptions` attribute can be used to dynamically generate a list of `` + * DOM element. + * * `trackexpr`: Used when working with an array of objects. The result of this expression will be + * used to identify the objects in the array. The `trackexpr` will most likely refer to the + * `value` variable (e.g. `value.propertyName`). + * + * @example + + + +
+
    +
  • + Name: + [X] +
  • +
  • + [add] +
  • +
+
+ Color (null not allowed): +
+ + Color (null allowed): + + +
+ + Color grouped by shade: +
+ + + Select bogus.
+
+ Currently selected: {{ {selected_color:myColor} }} +
+
+
+
+ + it('should check ng-options', function() { + expect(element(by.binding('{selected_color:myColor}')).getText()).toMatch('red'); + element.all(by.select('myColor')).first().click(); + element.all(by.css('select[ng-model="myColor"] option')).first().click(); + expect(element(by.binding('{selected_color:myColor}')).getText()).toMatch('black'); + element(by.css('.nullable select[ng-model="myColor"]')).click(); + element.all(by.css('.nullable select[ng-model="myColor"] option')).first().click(); + expect(element(by.binding('{selected_color:myColor}')).getText()).toMatch('null'); + }); + +
+ */ + +var ngOptionsDirective = valueFn({ terminal: true }); +// jshint maxlen: false +var selectDirective = ['$compile', '$parse', function($compile, $parse) { + //000011111111110000000000022222222220000000000000000000003333333333000000000000004444444444444440000000005555555555555550000000666666666666666000000000000000777777777700000000000000000008888888888 + var NG_OPTIONS_REGEXP = /^\s*([\s\S]+?)(?:\s+as\s+([\s\S]+?))?(?:\s+group\s+by\s+([\s\S]+?))?\s+for\s+(?:([\$\w][\$\w]*)|(?:\(\s*([\$\w][\$\w]*)\s*,\s*([\$\w][\$\w]*)\s*\)))\s+in\s+([\s\S]+?)(?:\s+track\s+by\s+([\s\S]+?))?$/, + nullModelCtrl = {$setViewValue: noop}; +// jshint maxlen: 100 + + return { + restrict: 'E', + require: ['select', '?ngModel'], + controller: ['$element', '$scope', '$attrs', function($element, $scope, $attrs) { + var self = this, + optionsMap = {}, + ngModelCtrl = nullModelCtrl, + nullOption, + unknownOption; + + + self.databound = $attrs.ngModel; + + + self.init = function(ngModelCtrl_, nullOption_, unknownOption_) { + ngModelCtrl = ngModelCtrl_; + nullOption = nullOption_; + unknownOption = unknownOption_; + }; + + + self.addOption = function(value) { + assertNotHasOwnProperty(value, '"option value"'); + optionsMap[value] = true; + + if (ngModelCtrl.$viewValue == value) { + $element.val(value); + if (unknownOption.parent()) unknownOption.remove(); + } + }; + + + self.removeOption = function(value) { + if (this.hasOption(value)) { + delete optionsMap[value]; + if (ngModelCtrl.$viewValue == value) { + this.renderUnknownOption(value); + } + } + }; + + + self.renderUnknownOption = function(val) { + var unknownVal = '? ' + hashKey(val) + ' ?'; + unknownOption.val(unknownVal); + $element.prepend(unknownOption); + $element.val(unknownVal); + unknownOption.prop('selected', true); // needed for IE + }; + + + self.hasOption = function(value) { + return optionsMap.hasOwnProperty(value); + }; + + $scope.$on('$destroy', function() { + // disable unknown option so that we don't do work when the whole select is being destroyed + self.renderUnknownOption = noop; + }); + }], + + link: function(scope, element, attr, ctrls) { + // if ngModel is not defined, we don't need to do anything + if (!ctrls[1]) return; + + var selectCtrl = ctrls[0], + ngModelCtrl = ctrls[1], + multiple = attr.multiple, + optionsExp = attr.ngOptions, + nullOption = false, // if false, user will not be able to select it (used by ngOptions) + emptyOption, + // we can't just jqLite('