[prev in list] [next in list] [prev in thread] [next in thread] 

List:       helix-server-cvs
Subject:    [Server-cvs] admin/web/src/srvprxy config_mpeg2ts.html.wasm, 1.10, 1.10.8.1
From:       packard () helixcommunity ! org
Date:       2012-04-28 20:39:21
[Download RAW message or body]

Update of /cvsroot/server/admin/web/src/srvprxy
In directory cvs01.internal.helixcommunity.org:/tmp/cvs-serv22912

Modified Files:
      Tag: SERVER_14_3_VERIMETRIX_HLS_LR
	config_mpeg2ts.html.wasm 
Log Message:
Synopsis
========
DRM HLS LR implementation 
Branches: SERVER_14_3_VERIMETRIX_HLS_LR 

Reviewed by: Xiaocheng Li


Description
===========
I. Intro
===================================================
Verimatrix specializes in securing and enhancing revenue on multi-device digital TV \
services around the globe. VCAS is the solution they provide for Internet TV \
security. It provides a  complete end-to-end pay-TV security solution for \
over-the-top (OTT) service providers,  including both broadcast and video-on-demand \
delivery modes.

VCAS includes many components, from server side management to client side modules.
Generally speaking, it combines the content provider, service provider and client \
side player  together, with its security system, to provide a reliable way to make \
money from end user. What I focus here is making the Helix server works as a part of \
this solution. That is, getting key from its security system(Verimatrix Key Server), \
encrypt content stream  with this key, and provide the m3u8 file with the key URI at \
the same time, send the content  stream to client by using HLS. 
That is all.

II. What feature will be implemented in Helix Server 
===================================================
1. Could set the Verimatrix Key Server URI(IP or Domain name)
2. Could set the port of the Verimatrix Key Server
3. Could connect to the Verimatrix Key Server with SSL(not included in this CR)
4. Could do the keep alive checking by pinging the Verimatrix Key Server with an \
interval.  But as after getting key from KMS, the key would be sustained, so there is \
no need to maintain the keep alive connection with KMS.  So this feature would not be \
implemented. 5. Could map each stream with a VCAS resource ID; generate a different \
set of keys based on the   resource ID
6. Could protect the stream with its random generated key when Verimatrix Key Server \
down

A new web page is created for Verimatrix HLS DRM, The cfg example lists below, 
<List Name="Verimatrix_DRM">
	<Var KeyServerAddr="public-ott.verimatrix.com"/> 
	<Var KeyServerPort="12684"/>
	<Var EnableKeyServerSSL="0"/>
	<Var KeyServerRequestTimeout="25000"/>
	<Var EnableRandomKeyWhenFail="1"/>  # if 1, when fail to get key, use random key, if \
0, don't encrypt segment  <List Name="HLS_DRM">			
		<List Name="ResourceMapping1">
			<Var ResourceIDRange="4002-4122"/> 
			<Var defaultKeyCount="5"/>
		</List>
		<List Name="ResourceMapping2">
			<Var ResourceIDRange="3111-4222"/> 
			<Var defaultKeyCount="100"/>
		</List>
    </List>
</List>
<List Name="MPEG2_TS_Output>
   <List Name="/rtpencoder/sports/"> 
      <Var Enable="1"> 
      <Var TargetMountPoint="iPHone"/> 
      <Var UseVCASEncryption="1"/> 
      <Var VCASServer="ResourceMapping1"/> #map to the \
config.Verimatrix_DRM.HLS_DRM.ResourceMapping1  </List> 
</List>

III. What will be done in next CR 
===================================================
1. Recycle the resource-id.
   when a feed is disconnected, recycle the resource-id for next new feed to use.
   If no availble resour-id, check the EnableRandomKeyWhenFail, if 1, then use random \
key, else return FAIL. 2. move <Var UseVCASEncryption="1"/> <Var \
VCASServer="ResourceMapping1"/> from <List Name="MPEG2_TS_Output>   to \
config.FSMount.mountpoint.MPEG2-TS_Output.  
IV. Solution Description
===================================================
1.	
Inside CStreamHandler::StartProcessing,  adding some functions to read VCASServer DRM \
configuration. Likes GetTargetMP using sourcepath to get \
config.MPEG2_Transport_Stream list node, then use list node TargetMountPoint£¬  then \
using TargetMountPoint to get config.FSMount¡£

Here adding GetTargetVcasDRMNode, ParseTargetVcasDRMNodeConfig and \
ParseTargetVcasDRMConfig to do the similar thing  which load DRM configuration from \
cfg file. We check UseVCASEncryption£¬if  UseVCASEncryption ==1, then load VCAS \
configuration All necessary info are loaded into CStreamHandler::m_config.

After loading successes,  using FSManger::Init to start GET first key£¨p=0£©
Implement InitDone£¨will call m_pFSManager->GetFileObject(m_pRequest, NULL)
Implement FileObjectReady, etc.
Here we don't use CREATE method, because the latest Verimatrix KMS don't need it, GET \
method would also trigger key creation.

Besides, we use a delay way when start the key getting process.
eg. a. If VCAS is enabled, then start the key getting process to get the first \
position, and start a timer(KeyServerRequestTimeout or 5s is KeyServerRequestTimeout \
is not set).  b. If the first key is gotten successfully before timer timeout, then \
triger the normal GetFileHeader process.   Besides, continue to get other keys until \
reach defaultKeyCount or fail to get key.  c. If timer timeout, and key is still not \
ready, we have a choice here.  choice A, EnableRandomKeyWhenFail==1, triger the \
normal GetFileHeader process, and use the random key.  choice B, \
EnableRandomKeyWhenFail==0, return M3U_ERROR, means service is not available.  
	
2. 
Configuration params mapping
Global Param:           	   				  Inner m_config Param
KeyServerAddr  			 	<--->   		VCASHLSServerAddr
KeyServerPort  				<--->    		VCASHLSServerPort
EnableKeyServerSSL 			<---> 			VCASHLSEnableSSL
KeyServerRequestTimeout   	<---> 	    	VCASHLSTimeout
EnableRandomKeyWhenFail     <--->     		VCASEnableRandomKeyWhenFail
UseVCASEncryption           <--->   		UseVCASEncryption
VCASHLSKeyReady£¨if p=0 and key is ready, will set VCASHLSKeyReady =1£©


Node info£º					        		inner m_config param
Type						<---> 			VCASHLSType
Mapping						<---> 			VCASHLSMapping		(not used yet, default is auto)
ResourceIDRange     		<---> 			VCASHLSREID £¨current resouce id£©
defaultKeyCount				<---> 			VCASHLSDefKeyCount

3. 
CTSArchiver::Init, CTSArchiver::FlushQueue, CTSArchiver::_CreatePlaylist, \
CTSArchiver::_ReWritePlaylist,  they are all needed to be changed as they all use the \
encryption key. In CTSArchiver, use CTSArchiver::UpdateVCASHLSKeyInfo to get vcas \
key£¬if current p > 0,  and we can¡¯t get p+1, roll back to p=0( which means if \
default key count is 100, we only get 50 of them, it could also works)

4.
M3u8 update
CTSArchiver::_CreatePlaylist
CTSArchiver::HandleSegmentDone
If m_bEnableEncryption£¬ if vcas enable && key ready£¬using vcas url


V. Implementation Detail
===================================================
1. VCAS Scrambler functions
   these include read configuration file of the VCAS DRM setting, implement the VCAS \
integration API, GET key from KMS, and store it into memory.  The work flow is,
   When CStreamHandler::StartProcessing is called(incoming live feed or vod request), \
check cfg file source path to find UseVCASEncryption.  If UseVCASEncryption==1, then \
find Verimatrix_DRM setting by VCASServer. Load all the info.  Use a callback to \
start the scrambler work, to get the key, there is a defaultKeyCount, the scrambler \
will stop until an error happen or reach the   defaultKeyCount.
   At the same time, if first key is gotten successfully, will call the \
m_pSource->GetFileHeader() which will trigger the normal segmentation function.  
   At last, work flow is passed to CTSArchiver, here we have several choice,
   If VCAS is used, then try to get key to do the encryption, if could not, will use \
the random key.  Another thing to mention is that, in CStreamHandler::Func, there is \
a CHECK_VCAS_KEY_CB callback type, if VCAS is used and key is not gotten \
successfully,  and EnableRandomKeyWhenFail==0, we will return error.
   There exist a possiblity that we could get all the keys when we need to change a \
new key, if we fail to do so, we will roll back to use the first key.  This is done \
in CTSArchiver::UpdateVCASHLSKeyInfo.

   When time need to generate m3u8 file, will choose whether to use the URI for the \
KMS or the local URI.

2. Admin Page   
   A new page is added, which name is "DRM Setting", belongs to Content Management.

3. Httpfsys 
   it doesn't support HXR_SOCK_WOULDBLOCK in CHTTPFileObject::ProcessIdle, which will \
always return error, so change it to support HXR_SOCK_WOULDBLOCK.  VCAS API don't \
need other http header except GET address HTTP/1.1, so add a bUserDrmVCAS parameter \
to let it tell httpfsys not use other header.  
   
VI. Files Affected
==============
   filesystem\http\httpfsys.cpp
   datatype_rn\mpeg2\ts\filewriter\ctsarchiver.cpp
   datatype_rn\mpeg2\ts\filewriter\pub\ctsarchiver.h
   server_rn\datatype\mpeg2ts\streamhanlder.cpp
   server_rn\datatype\mpeg2ts\pub\streamhanlder.h
   server_rn\common\util\pub\mpeg2ts_config_name.h
   server_rn\common\util\pub\drmutil.h
   server_rn\common\util\pub\vcasscrambler.h
   server_rn\common\util\pub\drmpathmap.h
   server_rn\common\util\drmutil.cpp
   server_rn\common\util\vcasstrambler.cpp
   server_rn\common\util\drmpathmap.cpp
   server_rn\common\util\Umakefil
   common\include\hxdrmscrambler.h
   server\admin\web\src\srvprxy\config_mpeg2ts.html.wasm
   server\admin\web\src\srvprxy\config_drm.html.wasm
   server\admin\web\server.opt
   server\admin\web\src\srvprxy\toc~server.js
   server\admin\web\src\srvprxy\pageData.pm

Testing Performed
=================
   Test ViewRight on PC, ViewRight on iPhone/iPad 
   
   


Index: config_mpeg2ts.html.wasm
===================================================================
RCS file: /cvsroot/server/admin/web/src/srvprxy/config_mpeg2ts.html.wasm,v
retrieving revision 1.10
retrieving revision 1.10.8.1
diff -u -d -r1.10 -r1.10.8.1
--- config_mpeg2ts.html.wasm	29 Nov 2011 01:38:28 -0000	1.10
+++ config_mpeg2ts.html.wasm	28 Apr 2012 20:39:18 -0000	1.10.8.1
@@ -1,434 +1,484 @@
-#include "header.wasm"
-
-<@
-        use configLib ;
-        use dbgLib ;
-@>
-
-<HTML>
-<HEAD>
-<@ if( $DEFS::DEBUG ) { @><SCRIPT language="javascript">
-
-</SCRIPT><@ } @>
-
-<@= titleTag @>
-
-<@= commonInc @>
-
-<SCRIPT SRC="propview.js"></SCRIPT>
-<SCRIPT SRC="enumproplist.js"></SCRIPT>
-<SCRIPT SRC="propview_nolist.js"></SCRIPT>
-
-<SCRIPT SRC="servvar.get.html?mpeg2ts=config.MPEG2_Transport_Stream&fs=config.FSMount"></SCRIPT>
                
-
-<SCRIPT language="JavaScript">
-
-var fsLocal = [];
-function findLocalFSCallback ( a, prop, curDepth )
-{
-    var list = a[ prop ];
-
-    // check for a content mountpoint
-    if ( ! ( isObj( list ) &&
-            list[ "MountPoint" ] &&
-            list[ "ShortName" ] &&
-            list[ "ShortName" ].bSearch( /pn-local/i ) ) )
-                return ;
-
-    // check for adminfs related mount point (don't want these)
-    if ( list[ "BaseMountPoint" ] ||
-            list[ "MountPoint" ].bSearch( /admin/i ) )
-                return ;
-
-    //ROOT MP is not allowed
-    if ( list[ "MountPoint" ] == '/')
-        return;
-
-    // it's a keeper
-    fsLocal[ prop ] = list;
-}
-enumPropArray( fs, findLocalFSCallback, 1 );
-
-var propSetParam2 =
-[
-    new PropObj( 'Hostname',  "", false, false, null, null, "hostname", "Hostname" \
                ),     
-];
-var propListParam2 = new PropList( "M3UGEN Mount Point", fs["M3U File Generator"], \
                propSetParam2, null);
-
-var propSetParams =
-[
-    new PropObj( 'MountPoint', "", true, true, null, null, "mountpoint", "Content \
                MountPoint" ),
-    new PropObj('MPEG2-TS_Output.SegmentDuration', "10", false, false, "1", null, \
                null, "int", "Segment Duration"),
-    new PropObj('MPEG2-TS_Output.PlaylistSegments', "3", false, false, "3", null, \
                null, "int", "Playlist Segments"),
-    new PropObj('MPEG2-TS_Output.EnableLiveArchive', "0", false, false, null, null, \
                "int", "Enable LiveArchive"),
-    new PropObj('MPEG2-TS_Output.ArchiveMountPoint', "", false, false, null, null, \
                "mountpoint", "Archive Mountpoint"),
-    new PropObj('MPEG2-TS_Output.SegmentPurgeSize', "5120", false, false, "0", null, \
                null, "int", "Segment Purge Size"),
-    new PropObj('MPEG2-TS_Output.EnableEncryption', "0", false, false, null, null, \
                "int", "Enable Encryption"),
-    new PropObj('MPEG2-TS_Output.KeyFileInterval', "0", false, false, null, null, \
                "", "KeyFile Interval"),
-    new PropObj('MPEG2-TS_Output.KeyFileWriteLocation', "", false, false, null, \
                null, "mountpoint", "KeyFile WriteLocation"),
-    new PropObj('MPEG2-TS_Output.KeyFileURL', "", false, false, null, null, "", \
                "KeyFileURL"),
-    new PropObj('MPEG2-TS_Output.AltURL', "", false, false, null, null, "", \
                "AltURL"),
-    new PropObj('MPEG2-TS_Output.AltServer', "", false, false, null, null, \
                "hostname", "AltServer"),
-    new PropObj('MPEG2-TS_Output.WriteAbsolutePaths', "0", false, false, null, null, \
                "int", "Write Absolute Paths"),
-
-];
-
-propListParams = new PropList( "Content Mount Points", fsLocal, null, propSetParams \
                );
-
-function FSPropView ()
-{
-    this.base = NestedPropListView ;
-    this.base( "Mount Points", propListParams, "Mount Point", 
-               "config.FSMount.", "configvar", "theForm", 
-               "theParamList", "theParamEdit" );
-}
-
-FSPropView.prototype = new NestedPropListView ;
-
-var theForm = null;
-var propSetSources =
-[
-    new PropObj( 'Enable', "1", false, false,  null, null, "int", "Enable \
                SourcePath" ),
-    new PropObj( 'TargetMountPoint', "", false, true, null, null, "mountpoint", \
                "Destination MountPoint" ), 
-    new PropObj( 'PurgeSegmentsOnServerStart', "0", false, false,  null, null, \
                "int", "Purge Segments On Server Start" ),
-];
-
-var propSetMain =
-[
-    new PropObj( 'GlobalEnable', "0", false, false,  null, null, "int", "Enable \
                Segmentation" ),
-    new PropObj( 'ForceHTTPSPlaylistDelivery', "0", false, false,  null, null, \
                "int", "Force Secure Playlists" ),
-    new PropObj( 'UseHTTPSKeyfileDelivery', "1", false, false,  null, null, "int", \
                "Secure KeyFiles" ),
-];
-
-var propList = new PropList( "MPEG2_Transport_Stream", mpeg2ts, propSetMain, \
                propSetSources);
-
-function MPEG2TSPropView ()
-{
-    this.base = NestedPropListView ;
-    this.base( "MPEG2 Transport Stream", propList, "Source Path", 
-               "config.MPEG2_Transport_Stream.", "servvar",
-               "theForm", "theList", "theEdit" );
-    this.m_itemDefValue = "/SourcePath";
-    this.propViewParam = new FSPropView();
-    this.propViewParam2 = new PropListView( "M3UGEN Mount Point", propListParam2, \
                "config.FSMount.M3U File Generator.", "configvar", "theForm" ); 
-}
-
-MPEG2TSPropView.prototype = new NestedPropListView ;
-var propView = new MPEG2TSPropView();
-
-FSPropView.prototype.postFillFormFromSubList = function ( fValidate )
-{
-    EnableDisableControls (theForm["MPEG2-TS_Output.EnableEncryption"]);
-    EnableDisableControls (theForm["MPEG2-TS_Output.AltServer"]);
-
-    return true;
-}
-
-FSPropView.prototype.preValidateSubList = function ( subListName )
-{
-    if(theForm["MPEG2-TS_Output.WriteAbsolutePaths"].value != 0)
-    {
-        if (theForm["Hostname"].value == "")
-        {
-            alert("A Hostname is required if  'Use Absolute Paths for Segments' is \
                enabled");
-            return false;
-        }
-        else
-        {
-            return true;
-        }
-    }
-    else if (theForm["MPEG2-TS_Output.EnableEncryption"].value != 0
-             && theForm["MPEG2-TS_Output.KeyFileURL"].value == ""
-             && theForm["Hostname"].value == "")
-    {
-        alert("A Hostname or KeyFile URL is required if Encryption is Enabled");
-        return false;
-    }
-    else
-    {
-        return true;
-    }
-}
-
-FSPropView.prototype.fillSelectList = function ()
-{
-    if ( ! this.m_bLoaded ) return ;
-
-    var listCtrl = this.getListCtrl();
-    if ( listCtrl )
-    {
-        listCtrl.options.length = 0;
-        // fill the list with the name of each sublist
-        this.m_subListParent.fillSelectList( listCtrl );
-
-        var val = getCtrlValue(theForm.TargetMountPoint);
-        var selDesc = null;
-        for ( var desc in fsLocal )
-        {
-            if (fsLocal[ desc ][ 'MountPoint'] == val)
-            {
-                selDesc = desc;
-                break;
-            }
-        }
-        var selCtrl = theForm.theParamList;
-        if (selCtrl.length > 1)
-        {
-            for ( var i = 0; i < selCtrl.length; i++ )
-            {
-                if (selCtrl.options[ i ].text == selDesc)
-                {
-                    // and select the first one
-                    listCtrl.selectedIndex = i;
-                    break;
-                }
-            }
-        }
-    }
-}// fillSelectList ()
-
-MPEG2TSPropView.prototype.postFillFormFromSubList = function(fValidate)
-{
-    ShowSelectedMP();
-    return true;
-}
-
-MPEG2TSPropView.prototype.preValidateSubList = function ( subListName )
-{
-    var sourcePathVal = theForm.theEdit.value;
-    if (sourcePathVal.charAt(sourcePathVal.length -1 ) != '/' || \
                sourcePathVal.charAt(0) != '/')
-    {
-        this.errMsg = "SourcePath must begin and end with a forward slash ('/').";
-        return rejectInput( theForm.theEdit, this.errMsg );
-    }
-
-    //Dont allw slash
-    var TargetMPVal = getCtrlValue(theForm.TargetMountPoint);
-    if (TargetMPVal == '/')
-    {
-        this.errMsg = "Cannot use '/' as a Destination MountPoint";
-        return rejectInput( theForm.TargetMountPoint, this.errMsg );
-    }
-    return true;
-}
-
-MPEG2TSPropView.prototype.postValidate = function ()
-{
-   if ( ! this.propViewParam.validate( true ) )
-   {
-       return false;
-   }
-   if ( ! this.propViewParam2.validate( true ) )
-   {
-       return false;
-   }
-
-   this.propViewParam.buildParamString();
-   this.m_submitString += this.propViewParam.m_submitString;
-
-   this.propViewParam2.buildParamString();
-   this.m_submitString += this.propViewParam2.m_submitString;
-
-
-   return true;
-}
-
-MPEG2TSPropView.prototype.postSubmit = function ()
-{
-    this.propViewParam.m_propList.commit();
-    this.propViewParam2.m_propList.commit();
-
-}    // postSubmit
-
-function fillMountPointSelectList ()
-{
-    var TargetlistCtrl = theForm.TargetMountPoint;
-    var ArchivelistCtrl = theForm["MPEG2-TS_Output.ArchiveMountPoint"];
-    var KeyfilelistCtrl = theForm["MPEG2-TS_Output.KeyFileWriteLocation"];
-
-    TargetlistCtrl.options.length = 0;
-    ArchivelistCtrl.options.length = 0;
-    KeyfilelistCtrl.options.length = 0;
-
-    selAddOption( TargetlistCtrl, "Pick a Mount Point", "" );
-    selAddOption( ArchivelistCtrl, "Pick a Mount Point", "" );
-    selAddOption( KeyfilelistCtrl, "Use Source Mount Point", "" );
-
-    for ( var desc in fsLocal )
-    {
-        selAddOption( TargetlistCtrl, fsLocal[ desc ][ 'MountPoint' ] );
-        selAddOption( ArchivelistCtrl, fsLocal[ desc ][ 'MountPoint' ] );
-        selAddOption( KeyfilelistCtrl, fsLocal[ desc ][ 'MountPoint' ] );
-    }
-
-}    // fillMountPointSelectList
-
-
-function ShowSelectedMP ()
-{
-    if (propView.propViewParam.m_bLoaded)
-    {
-        propView.propViewParam.initListDataDisplay();
-    }
-}
-
-function EnableDisableControls (controls)
-{
-    if (controls.name == "MPEG2-TS_Output.EnableEncryption")
-    {
-        if (controls.value == 0)
-        {
-            theForm["MPEG2-TS_Output.KeyFileWriteLocation"].disabled = true;
-            theForm["MPEG2-TS_Output.KeyFileInterval"].disabled = true;
-            theForm["MPEG2-TS_Output.KeyFileURL"].disabled = true;
-        }
-        else
-        {
-            theForm["MPEG2-TS_Output.KeyFileWriteLocation"].disabled = false;
-            theForm["MPEG2-TS_Output.KeyFileInterval"].disabled = false;
-            theForm["MPEG2-TS_Output.KeyFileURL"].disabled = false;
-        }
-    }
-    else if (controls.name == "MPEG2-TS_Output.AltServer")
-    {
-        if (controls.value == '')
-        {
-            theForm["MPEG2-TS_Output.AltURL"].disabled = false;
-        }
-        else
-        {
-            theForm["MPEG2-TS_Output.AltURL"].disabled = true;
-        }
-    }
-}
-
-function onLoad ()
-{
-    theForm = document.theForm;
-    fillMountPointSelectList();
-
-    theForm["MountPoint"].disabled = true;
-    propView.onLoad( window );
-    propView.propViewParam.onLoad( window, true);
-    propView.propViewParam2.onLoad( window, true);
-
-    EnableDisableControls(theForm["MPEG2-TS_Output.EnableEncryption"]);
-    EnableDisableControls(theForm["MPEG2-TS_Output.AltServer"]);
-
-}   // onLoad 
-
-</SCRIPT>
-</HEAD>
-<@= bodyTag( "onLoad" ) @>
-
-#include "dbgpanel.wasm"
-
-<@= pageHeader( qq(
-
-Media segmentation breaks any file or stream containing supported codecs into media \
                chunks which can be viewed using the iPhone, 
-or other compatible handset or client.  These media segments can be used in iPhone \
                playback, 
-allowing for features such as on-demand and live rate adaptation, encryption, and \
                live archiving. 
-
-) ); @>
-
-
-<@
-    LEFT_WIDTH( "44%" );
-    RIGHT_WIDTH( "56%" );
-    FORM_STYLE( "NOWRAP");
-@>
-
-<@= formTag2 @>
-
-<@= tabHeader @>
-
-<@= formTableTag @>
-      <@= formElemSelectYesNo( "Enable Segmentation", name => "GlobalEnable"); @>
-      <@= formElemText( "Server Hostname", name => "Hostname" ); @>
-<@
-    FORM_STYLE( "WRAP");
-@>
-<@= spacerRow @>
-<tr>
-    <TD class="input" valign="middle" width="30%">
-       <@= formElemSelectYesNo( "Send Playlists by HTTPS",name => \
                "ForceHTTPSPlaylistDelivery"); @>
-       <BR><BR>
-       <@= formElemSelectYesNo( "Send Encryption Key files via HTTPS", name => \
                "UseHTTPSKeyfileDelivery" ); @>
-       <BR>
-    </TD>
-    <TD colspan="2" bgcolor="#E6E6E6" VALIGN="middle" width="56%" class="body" \
                ALIGN="LEFT">
-        <FONT color="red">(HTTPS delivery to the iPhone will only work with \
                officially signed certificates.
-                          If you have not set up an officially signed certificate, \
                leave these fields set to
-                          No until you do.
-                          Note that self-signed certificates are not adequate)
-        </FONT>
-    </TD>
-</TR>
-<@= spacerRow @>
-<tr>
-    <@= formTableCellLeft @>
-          <@= listCtrl( "Source Paths", 
-                        "theList", 8, "LEFT", 
-                        "Source Path",
-                        { ADD => "propView.createSubList()",
-                          DEL => "propView.removeSubList()" } ); @>
-    </TD>
-    <@= formTableCellRight @>
-        <@= formElemText( "Edit Source Path" , name => "theEdit" ); @>
-        <@= formElemSelectYesNo( "Enable Source Path", name => "Enable" ); @>
-        <BR>
-        <@= formElemSelectYesNo( "Purge Segments On Server Start", name => \
                "PurgeSegmentsOnServerStart" ); @>
-        <BR>
-        <@= formElemSelect( "Destination MountPoint", 
-        ["                             ",0,0,0,0,0,0],
-        name => "TargetMountPoint",
-        onchange => "ShowSelectedMP();"); @>
-        <DIV ID="DIV_1" style="position:absolute;visibility:hidden">
-            <@= formElemText( "Edit Description" , name => "theParamEdit" ); @>
-        </DIV>
-    </TD>
-</TR>
-<@= spacerRow @>
-<tr>
-    <@= formTableCellLeft @>
-          <@= listCtrl( "Mount Point Description", "theParamList", 8, "LEFT",
-                        "Mount Point" ); @>
-    </TD>
-    <@= formTableCellRight @>
-        <@= formElemText( "Mount Point", name => "MountPoint", class => \
                "readOnlyValue" ); @>
-        <BR>
-        <@= formElemText( "Segment Duration" , name => \
"MPEG2-TS_Output.SegmentDuration", class => "input", units=> "seconds", size=>16, \
                maxlength=>16 ); @>
-        <BR>
-        <@= formElemText( "Minimum Playlist Segments" , name => \
                "MPEG2-TS_Output.PlaylistSegments", class => "input", size=>16, \
                maxlength=>16); @>
-        <BR><BR>
-        <@= formElemSelectYesNo( "Enable Live Archive", name => \
                "MPEG2-TS_Output.EnableLiveArchive",  class => "input", maxlength=>16 \
                ); @>
-        <BR>
-        <@= formElemSelect( "Archive Mount Point",
-        ["                             ",0,0,0,0,0,0],
-        name => "MPEG2-TS_Output.ArchiveMountPoint" ); @>
-        <BR><BR>
-        <@= formElemSelectYesNo( "Enable Segment Encryption", name => \
                "MPEG2-TS_Output.EnableEncryption", class => "input", maxlength=>16,
-            onchange => "EnableDisableControls(this);"); @>
-        <BR>
-        <@= formElemText( "KeyFile Interval" , name => \
"MPEG2-TS_Output.KeyFileInterval" , units=> "minutes",  class => "input", size=>16, \
                maxlength=>16); @>
-        <BR>
-        <@= formElemSelect( "KeyFile MountPoint",
-        ["                             ",0,0,0,0,0,0],
-        name => "MPEG2-TS_Output.KeyFileWriteLocation", units=> "(optional)" ); @>
-        <BR>
-        <@= formElemText( "KeyFile URL" , name => "MPEG2-TS_Output.KeyFileURL" ); @>
-
-        <BR><BR>
-        <@= formElemText( "Alternate URL" , name => "MPEG2-TS_Output.AltURL" ); @>
-        <@= formElemText( "Alternate Server" , name => "MPEG2-TS_Output.AltServer", \
                onchange => "EnableDisableControls(this);" ); @>
-        <@= formElemText( "Segment Purge Size" , name => \
"MPEG2-TS_Output.SegmentPurgeSize", units=> "MB",  class => "input", size=>16, \
                maxlength=>16 ); @>
-        <BR>
-        <@= formElemSelectYesNo( "Use Absolute Paths for Segments", name => \
                "MPEG2-TS_Output.WriteAbsolutePaths", class => "input", maxlength=>16 \
                ); @>
-        <BR>
-    </TD>
-</TR>
-<@= submitResetBtns @>
-
-<@= endHTMLTag @>
-
+#include "header.wasm"
+
+<@
+        use configLib ;
+        use dbgLib ;
+@>
+
+<HTML>
+<HEAD>
+<@ if( $DEFS::DEBUG ) { @><SCRIPT language="javascript">
+
+</SCRIPT><@ } @>
+
+<@= titleTag @>
+
+<@= commonInc @>
+
+<SCRIPT SRC="propview.js"></SCRIPT>
+<SCRIPT SRC="enumproplist.js"></SCRIPT>
+<SCRIPT SRC="propview_nolist.js"></SCRIPT>
+
+<SCRIPT SRC="servvar.get.html?mpeg2ts=config.MPEG2_Transport_Stream&fs=config.FSMount&vcaslist=config.Verimatrix_DRM.HLS_DRM"></SCRIPT>
 +
+<SCRIPT language="JavaScript">
+
+var fsLocal = [];
+function findLocalFSCallback ( a, prop, curDepth )
+{
+    var list = a[ prop ];
+
+    // check for a content mountpoint
+    if ( ! ( isObj( list ) &&
+            list[ "MountPoint" ] &&
+            list[ "ShortName" ] &&
+            list[ "ShortName" ].bSearch( /pn-local/i ) ) )
+                return ;
+
+    // check for adminfs related mount point (don't want these)
+    if ( list[ "BaseMountPoint" ] ||
+            list[ "MountPoint" ].bSearch( /admin/i ) )
+                return ;
+
+    //ROOT MP is not allowed
+    if ( list[ "MountPoint" ] == '/')
+        return;
+
+    // it's a keeper
+    fsLocal[ prop ] = list;
+}
+enumPropArray( fs, findLocalFSCallback, 1 );
+
+var propSetParam2 =
+[
+    new PropObj( 'Hostname',  "", false, false, null, null, "hostname", "Hostname" \
),      +];
+var propListParam2 = new PropList( "M3UGEN Mount Point", fs["M3U File Generator"], \
propSetParam2, null); +
+var propSetParams =
+[
+    new PropObj( 'MountPoint', "", true, true, null, null, "mountpoint", "Content \
MountPoint" ), +    new PropObj('MPEG2-TS_Output.SegmentDuration', "10", false, \
false, "1", null, null, "int", "Segment Duration"), +    new \
PropObj('MPEG2-TS_Output.PlaylistSegments', "3", false, false, "3", null, null, \
"int", "Playlist Segments"), +    new PropObj('MPEG2-TS_Output.EnableLiveArchive', \
"0", false, false, null, null, "int", "Enable LiveArchive"), +    new \
PropObj('MPEG2-TS_Output.ArchiveMountPoint', "", false, false, null, null, \
"mountpoint", "Archive Mountpoint"), +    new \
PropObj('MPEG2-TS_Output.SegmentPurgeSize', "5120", false, false, "0", null, null, \
"int", "Segment Purge Size"), +    new PropObj('MPEG2-TS_Output.EnableEncryption', \
"0", false, false, null, null, "int", "Enable Encryption"), +    new \
PropObj('MPEG2-TS_Output.KeyFileInterval', "0", false, false, null, null, "", \
"KeyFile Interval"), +    new PropObj('MPEG2-TS_Output.KeyFileWriteLocation', "", \
false, false, null, null, "mountpoint", "KeyFile WriteLocation"), +    new \
PropObj('MPEG2-TS_Output.KeyFileURL', "", false, false, null, null, "", \
"KeyFileURL"), +    new PropObj('MPEG2-TS_Output.AltURL', "", false, false, null, \
null, "", "AltURL"), +    new PropObj('MPEG2-TS_Output.AltServer', "", false, false, \
null, null, "hostname", "AltServer"), +    new \
PropObj('MPEG2-TS_Output.WriteAbsolutePaths', "0", false, false, null, null, "int", \
"Write Absolute Paths"), +    new PropObj( 'MPEG2-TS_Output.UseVCASEncryption', "0", \
false, false,  null, null, "int", "Use Verimatrix DRM" ), +    new PropObj( \
'MPEG2-TS_Output.VCASNode', "", false, false, null, null, "ShortName", "Verimatrix \
DRM Resouce Node" ),  +];
+
+propListParams = new PropList( "Content Mount Points", fsLocal, null, propSetParams \
); +
+function FSPropView ()
+{
+    this.base = NestedPropListView ;
+    this.base( "Mount Points", propListParams, "Mount Point", 
+               "config.FSMount.", "configvar", "theForm", 
+               "theParamList", "theParamEdit" );
+}
+
+FSPropView.prototype = new NestedPropListView ;
+
+var propHLS_DRMSources =
+[
+	new PropObj( 'ResourceIDRange', "", false, true,  null, null, "hostname", "Resource \
ID Range" ), +	new PropObj( 'defaultKeyCount', "10", false, true,  null, null, "int", \
"default Key Count" ), +];
+propVCASListParams = new PropList( "VCAS Nodes", vcaslist, null, propHLS_DRMSources \
); +function VCASPropView ()
+{
+    this.base = NestedPropListView ;
+    this.base( "Verimatrix Nodes", propVCASListParams, "Verimatrix Nodes", 
+               "config.Verimatrix_DRM.HLS_DRM", "configvar", "theForm", 
+               "theParamList2", "theParamEdit" );
+}
+VCASPropView.prototype = new NestedPropListView ;
+
+var theForm = null;
+var propSetSources =
+[
+    new PropObj( 'Enable', "1", false, false,  null, null, "int", "Enable \
SourcePath" ), +    new PropObj( 'TargetMountPoint', "", false, true, null, null, \
"mountpoint", "Destination MountPoint" ),  +    new PropObj( \
'PurgeSegmentsOnServerStart', "0", false, false,  null, null, "int", "Purge Segments \
On Server Start" ), +];
+
+var propSetMain =
+[
+    new PropObj( 'GlobalEnable', "0", false, false,  null, null, "int", "Enable \
Segmentation" ), +    new PropObj( 'ForceHTTPSPlaylistDelivery', "0", false, false,  \
null, null, "int", "Force Secure Playlists" ), +    new PropObj( \
'UseHTTPSKeyfileDelivery', "1", false, false,  null, null, "int", "Secure KeyFiles" \
), +];
+
+var propList = new PropList( "MPEG2_Transport_Stream", mpeg2ts, propSetMain, \
propSetSources); +
+function MPEG2TSPropView ()
+{
+    this.base = NestedPropListView ;
+    this.base( "MPEG2 Transport Stream", propList, "Source Path", 
+               "config.MPEG2_Transport_Stream.", "servvar",
+               "theForm", "theList", "theEdit" );
+    this.m_itemDefValue = "/SourcePath";
+    this.propViewParam = new FSPropView();
+    this.propViewParam2 = new PropListView( "M3UGEN Mount Point", propListParam2, \
"config.FSMount.M3U File Generator.", "configvar", "theForm" );  \
+	this.propViewParam3 = new VCASPropView(); +}
+
+MPEG2TSPropView.prototype = new NestedPropListView ;
+var propView = new MPEG2TSPropView();
+
+FSPropView.prototype.postFillFormFromSubList = function ( fValidate )
+{
+    EnableDisableControls (theForm["MPEG2-TS_Output.EnableEncryption"]);
+    EnableDisableControls (theForm["MPEG2-TS_Output.AltServer"]);
+
+    return true;
+}
+
+FSPropView.prototype.preValidateSubList = function ( subListName )
+{
+    if(theForm["MPEG2-TS_Output.WriteAbsolutePaths"].value != 0)
+    {
+        if (theForm["Hostname"].value == "")
+        {
+            alert("A Hostname is required if  'Use Absolute Paths for Segments' is \
enabled"); +            return false;
+        }
+        else
+        {
+            return true;
+        }
+    }
+    else if (theForm["MPEG2-TS_Output.EnableEncryption"].value != 0
+             && theForm["MPEG2-TS_Output.KeyFileURL"].value == ""
+             && theForm["Hostname"].value == "")
+    {
+        alert("A Hostname or KeyFile URL is required if Encryption is Enabled");
+        return false;
+    }
+    else
+    {
+        return true;
+    }
+}
+
+FSPropView.prototype.fillSelectList = function ()
+{
+    if ( ! this.m_bLoaded ) return ;
+
+    var listCtrl = this.getListCtrl();
+    if ( listCtrl )
+    {
+        listCtrl.options.length = 0;
+        // fill the list with the name of each sublist
+        this.m_subListParent.fillSelectList( listCtrl );
+
+        var val = getCtrlValue(theForm.TargetMountPoint);
+        var selDesc = null;
+        for ( var desc in fsLocal )
+        {
+            if (fsLocal[ desc ][ 'MountPoint'] == val)
+            {
+                selDesc = desc;
+                break;
+            }
+        }
+        var selCtrl = theForm.theParamList;
+        if (selCtrl.length > 1)
+        {
+            for ( var i = 0; i < selCtrl.length; i++ )
+            {
+                if (selCtrl.options[ i ].text == selDesc)
+                {
+                    // and select the first one
+                    listCtrl.selectedIndex = i;
+                    break;
+                }
+            }
+        }
+    }
+}// fillSelectList ()
+
+MPEG2TSPropView.prototype.postFillFormFromSubList = function(fValidate)
+{
+    ShowSelectedMP();
+    ShowSelectedVCASNode ();
+    return true;
+}
+
+MPEG2TSPropView.prototype.preValidateSubList = function ( subListName )
+{
+    var sourcePathVal = theForm.theEdit.value;
+    if (sourcePathVal.charAt(sourcePathVal.length -1 ) != '/' || \
sourcePathVal.charAt(0) != '/') +    {
+        this.errMsg = "SourcePath must begin and end with a forward slash ('/').";
+        return rejectInput( theForm.theEdit, this.errMsg );
+    }
+
+    //Dont allw slash
+    var TargetMPVal = getCtrlValue(theForm.TargetMountPoint);
+    if (TargetMPVal == '/')
+    {
+        this.errMsg = "Cannot use '/' as a Destination MountPoint";
+        return rejectInput( theForm.TargetMountPoint, this.errMsg );
+    }
+    return true;
+}
+
+MPEG2TSPropView.prototype.postValidate = function ()
+{
+   if ( ! this.propViewParam.validate( true ) )
+   {
+       return false;
+   }
+   if ( ! this.propViewParam2.validate( true ) )
+   {
+       return false;
+   }
+   if ( ! this.propViewParam3.validate( true ) )
+   {
+       return false;
+   }
+
+   this.propViewParam.buildParamString();
+   this.m_submitString += this.propViewParam.m_submitString;
+
+   this.propViewParam2.buildParamString();
+   this.m_submitString += this.propViewParam2.m_submitString;
+
+   this.propViewParam3.buildParamString();
+   this.m_submitString += this.propViewParam3.m_submitString;
+   return true;
+}
+
+MPEG2TSPropView.prototype.postSubmit = function ()
+{
+    this.propViewParam.m_propList.commit();
+    this.propViewParam2.m_propList.commit();
+    this.propViewParam3.m_propList.commit();
+}    // postSubmit
+
+function fillMountPointSelectList ()
+{
+    var VCASlistCtrl = theForm["MPEG2-TS_Output.VCASNode"];
+    var TargetlistCtrl = theForm.TargetMountPoint;
+    var ArchivelistCtrl = theForm["MPEG2-TS_Output.ArchiveMountPoint"];
+    var KeyfilelistCtrl = theForm["MPEG2-TS_Output.KeyFileWriteLocation"];
+
+    TargetlistCtrl.options.length = 0;
+    ArchivelistCtrl.options.length = 0;
+    KeyfilelistCtrl.options.length = 0;
+    VCASlistCtrl.options.length = 0;
+
+    selAddOption( VCASlistCtrl, "Pick a VCAS Node", "" );
+    selAddOption( TargetlistCtrl, "Pick a Mount Point", "" );
+    selAddOption( ArchivelistCtrl, "Pick a Mount Point", "" );
+    selAddOption( KeyfilelistCtrl, "Use Source Mount Point", "" );
+
+    for ( var desc in fsLocal )
+    {
+        selAddOption( TargetlistCtrl, fsLocal[ desc ][ 'MountPoint' ] );
+        selAddOption( ArchivelistCtrl, fsLocal[ desc ][ 'MountPoint' ] );
+        selAddOption( KeyfilelistCtrl, fsLocal[ desc ][ 'MountPoint' ] );
+    }
+	for ( var desc in vcaslist )
+	{
+		selAddOption( VCASlistCtrl, desc );
+	}
+	
+}    // fillMountPointSelectList
+
+
+function ShowSelectedMP ()
+{
+    if (propView.propViewParam.m_bLoaded)
+    {
+        propView.propViewParam.initListDataDisplay();
+    }
+}
+
+function ShowSelectedVCASNode ()
+{
+    if (propView.propViewParam3.m_bLoaded)
+    {
+        propView.propViewParam3.initListDataDisplay();
+    }
+}
+
+function EnableDisableControls (controls)
+{
+    if (controls.name == "MPEG2-TS_Output.EnableEncryption")
+    {
+        if (controls.value == 0)
+        {
+            theForm["MPEG2-TS_Output.KeyFileWriteLocation"].disabled = true;
+            theForm["MPEG2-TS_Output.KeyFileInterval"].disabled = true;
+            theForm["MPEG2-TS_Output.KeyFileURL"].disabled = true;
+        }
+        else
+        {
+            theForm["MPEG2-TS_Output.KeyFileWriteLocation"].disabled = false;
+            theForm["MPEG2-TS_Output.KeyFileInterval"].disabled = false;
+            theForm["MPEG2-TS_Output.KeyFileURL"].disabled = false;
+        }
+    }
+    else if (controls.name == "MPEG2-TS_Output.AltServer")
+    {
+        if (controls.value == '')
+        {
+            theForm["MPEG2-TS_Output.AltURL"].disabled = false;
+        }
+        else
+        {
+            theForm["MPEG2-TS_Output.AltURL"].disabled = true;
+        }
+    }
+}
+
+function onLoad ()
+{
+    theForm = document.theForm;
+    fillMountPointSelectList();
+
+    theForm["MountPoint"].disabled = true;
+    propView.onLoad( window );
+    propView.propViewParam.onLoad( window, true);
+    propView.propViewParam2.onLoad( window, true);
+    propView.propViewParam3.onLoad( window, true);
+
+    EnableDisableControls(theForm["MPEG2-TS_Output.EnableEncryption"]);
+    EnableDisableControls(theForm["MPEG2-TS_Output.AltServer"]);
+
+}   // onLoad 
+
+</SCRIPT>
+</HEAD>
+<@= bodyTag( "onLoad" ) @>
+
+#include "dbgpanel.wasm"
+
+<@= pageHeader( qq(
+
+Media segmentation breaks any file or stream containing supported codecs into media \
chunks which can be viewed using the iPhone,  +or other compatible handset or client. \
These media segments can be used in iPhone playback,  +allowing for features such as \
on-demand and live rate adaptation, encryption, and live archiving.  +
+) ); @>
+
+
+<@
+    LEFT_WIDTH( "44%" );
+    RIGHT_WIDTH( "56%" );
+    FORM_STYLE( "NOWRAP");
+@>
+
+<@= formTag2 @>
+
+<@= tabHeader @>
+
+<@= formTableTag @>
+      <@= formElemSelectYesNo( "Enable Segmentation", name => "GlobalEnable"); @>
+      <@= formElemText( "Server Hostname", name => "Hostname" ); @>
+<@
+    FORM_STYLE( "WRAP");
+@>
+<@= spacerRow @>
+<tr>
+    <TD class="input" valign="middle" width="30%">
+       <@= formElemSelectYesNo( "Send Playlists by HTTPS",name => \
"ForceHTTPSPlaylistDelivery"); @> +       <BR><BR>
+       <@= formElemSelectYesNo( "Send Encryption Key files via HTTPS", name => \
"UseHTTPSKeyfileDelivery" ); @> +       <BR>
+    </TD>
+    <TD colspan="2" bgcolor="#E6E6E6" VALIGN="middle" width="56%" class="body" \
ALIGN="LEFT"> +        <FONT color="red">(HTTPS delivery to the iPhone will only work \
with officially signed certificates. +                          If you have not set \
up an officially signed certificate, leave these fields set to +                      \
No until you do. +                          Note that self-signed certificates are \
not adequate) +        </FONT>
+    </TD>
+</TR>
+<@= spacerRow @>
+<tr>
+    <@= formTableCellLeft @>
+          <@= listCtrl( "Source Paths", 
+                        "theList", 8, "LEFT", 
+                        "Source Path",
+                        { ADD => "propView.createSubList()",
+                          DEL => "propView.removeSubList()" } ); @>
+    </TD>
+    <@= formTableCellRight @>
+        <@= formElemText( "Edit Source Path" , name => "theEdit" ); @>
+        <@= formElemSelectYesNo( "Enable Source Path", name => "Enable" ); @>
+        <BR>
+        <@= formElemSelectYesNo( "Purge Segments On Server Start", name => \
"PurgeSegmentsOnServerStart" ); @> +        <BR>
+        <@= formElemSelect( "Destination MountPoint", 
+        ["                             ",0,0,0,0,0,0],
+        name => "TargetMountPoint",
+        onchange => "ShowSelectedMP();"); @>
+		
+        <DIV ID="DIV_1" style="position:absolute;visibility:hidden">
+            <@= formElemText( "Edit Description" , name => "theParamEdit" ); @>
+        </DIV>
+    </TD>
+</TR>
+<@= spacerRow @>
+<tr>
+    <@= formTableCellLeft @>
+          <@= listCtrl( "Mount Point Description", "theParamList", 8, "LEFT",
+                        "Mount Point" ); @>
+    </TD>
+    <@= formTableCellRight @>
+        <@= formElemText( "Mount Point", name => "MountPoint", class => \
"readOnlyValue" ); @> +        <BR>
+        <@= formElemText( "Segment Duration" , name => \
"MPEG2-TS_Output.SegmentDuration", class => "input", units=> "seconds", size=>16, \
maxlength=>16 ); @> +        <BR>
+        <@= formElemText( "Minimum Playlist Segments" , name => \
"MPEG2-TS_Output.PlaylistSegments", class => "input", size=>16, maxlength=>16); @> +  \
<BR><BR> +        <@= formElemSelectYesNo( "Enable Live Archive", name => \
"MPEG2-TS_Output.EnableLiveArchive",  class => "input", maxlength=>16 ); @> +        \
<BR> +        <@= formElemSelect( "Archive Mount Point",
+        ["                             ",0,0,0,0,0,0],
+        name => "MPEG2-TS_Output.ArchiveMountPoint" ); @>
+        <BR><BR>
+        <@= formElemSelectYesNo( "Enable Segment Encryption", name => \
"MPEG2-TS_Output.EnableEncryption", class => "input", maxlength=>16, +            \
onchange => "EnableDisableControls(this);"); @> +			
+	<BR>
+	<@= formElemSelectYesNo( "Use Verimatrix DRM", name => \
"MPEG2-TS_Output.UseVCASEncryption" ); @> +        <BR>
+        <@= formElemSelect( "Verimatrix DRM Resouce Node", 
+        ["                             ",0,0,0,0,0,0],
+        name => "MPEG2-TS_Output.VCASNode",
+        onchange => "ShowSelectedVCASNode();"); @>
+		
+        <BR>
+        <@= formElemText( "KeyFile Interval" , name => \
"MPEG2-TS_Output.KeyFileInterval" , units=> "minutes",  class => "input", size=>16, \
maxlength=>16); @> +        <BR>
+        <@= formElemSelect( "KeyFile MountPoint",
+        ["                             ",0,0,0,0,0,0],
+        name => "MPEG2-TS_Output.KeyFileWriteLocation", units=> "(optional)" ); @>
+        <BR>
+        <@= formElemText( "KeyFile URL" , name => "MPEG2-TS_Output.KeyFileURL" ); @>
+
+        <BR><BR>
+        <@= formElemText( "Alternate URL" , name => "MPEG2-TS_Output.AltURL" ); @>
+        <@= formElemText( "Alternate Server" , name => "MPEG2-TS_Output.AltServer", \
onchange => "EnableDisableControls(this);" ); @> +        <@= formElemText( "Segment \
Purge Size" , name => "MPEG2-TS_Output.SegmentPurgeSize", units=> "MB",  class => \
"input", size=>16, maxlength=>16 ); @> +        <BR>
+        <@= formElemSelectYesNo( "Use Absolute Paths for Segments", name => \
"MPEG2-TS_Output.WriteAbsolutePaths", class => "input", maxlength=>16 ); @> +        \
<BR> +    </TD>
+</TR>
+
+<@= submitResetBtns @>
+
+<@= endHTMLTag @>
+


_______________________________________________
Server-cvs mailing list
Server-cvs@helixcommunity.org
http://lists.helixcommunity.org/mailman/listinfo/server-cvs


[prev in list] [next in list] [prev in thread] [next in thread] 

Configure | About | News | Add a list | Sponsored by KoreLogic