* landed code for viewing topics in a conference, and for adding a topic

(first workout of HTML Checker code)
* modified the dictionary implementation to use a trie system rather than
  a set of HashSets, and also started using a new, much smaller dictionary
* general bugfixes and cleanup on other items as needed
This commit is contained in:
Eric J. Bowersox 2001-02-06 04:50:04 +00:00
parent 650691c8d2
commit 8bcc80ddd7
39 changed files with 47278 additions and 264907 deletions

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.5 KiB

View File

@ -1,28 +1,50 @@
The file "webster2.dict" was produced from the word lists supplied as "web2"
and "web2a.gz" in the /usr/share/dict directory of the Debian "miscfiles"
package (GNU miscfiles version 1.2). The following commands were used to
produce this file:
The file "en-us.dict" is taken from the "american-english" word list supplied
in the Debian "wenglish" package. A small amount of processing was used
to produce this file:
zcat /usr/share/dict/web2a.gz | fgrep -v ' ' > temp.dict
cat /usr/share/dict/web2 temp.dict | tr '[A-Z]' [a-z]' | sort \
| uniq > webster2.dict
rm temp.dict
cat /usr/share/dict/american-english | tr '[A-Z]' '[a-z]' | sort \
| uniq > en-us.dict
"webster2.dict" is to be considered "freely redistributable," and is not
"en-us.dict" is to be considered "freely redistributable," and is not
subject to the MPL as with the rest of Venice code. Herewith is the original
README for the dictionary:
copyright for the dictionary:
--- begin README ---
# $NetBSD: README,v 1.2 1997/03/26 07:14:32 mikel Exp $
# @(#)README 8.1 (Berkeley) 6/5/93
This package is now maintained by Charles Briscoe-Smith <cpbs@debian.org>.
WEB ---- (introduction provided by jaw@riacs) -------------------------
I, Erick Branderhorst <branderhorst@heel.fgg.eur.nl>, took over the
wenglish package but I don't know where the list of words is from. If
it is uptodate or copyrighted or whatever.
Welcome to web2 (Webster's Second International) all 234,936 words worth.
The 1934 copyright has elapsed, according to the supplier. The
supplemental 'web2a' list contains hyphenated terms as well as assorted
noun and adverbial phrases. The wordlist makes a dandy 'grep' victim.
This package was debianized by Herbert Xu herbert@debian.org on
Sat, 8 Feb 1997 22:16:50 +1100.
-- James A. Woods {ihnp4,hplabs}!ames!jaw (or jaw@riacs)
--- end README ---
It was downloaded from http://sunsite.unc.edu/pub/Linux/libs/.
Copyright:
Begin2
Title = /usr/dict/words for Linux (linux.words)
Version = 2
Desc1 = This is word list containing 45402 words. Great care has been
Desc2 = taken to be sure that this word list is free of copyright.
Desc3 = This list is suitable for English language spelling checkers
Desc4 = and as a target for look(1).
Author = Rik Faith
AuthorEmail = faith@cs.unc.edu
Maintainer = Rik Faith
MaintEmail = faith@cs.unc.edu
Site1 = ftp.cs.unc.edu
Path1 = /pub/faith/linux/utils
File1 = linux.words.2.tar.gz
FileSize1 = 140k
Site2 = tsx-11.mit.edu
Path2 = /pub/linux/docs
Site3 = sunsite.unc.edu
Path3 = /pub/Linux/libs
CopyPolicy1 = Free of non-commercial restrictions.
CopyPolicy2 = Free for personal, educational, and research purposes.
Keywords = dict, dictionary, words, wordlist
Entered = Sun Oct 10 19:02:47 1993
EnteredBy = Rik Faith
CheckedEmail = faith@cs.unc.edu
End

45375
etc/en-us.dict Normal file

File diff suppressed because it is too large Load Diff

View File

@ -65,10 +65,10 @@
</email>
<!-- This section dictates which dictionary files get loaded into the spelling checker's
main dictionary. The default lexicon is Webster's 2nd, with a supplemental list of
main dictionary. The default lexicon is a standard US English one, with a supplemental list of
words provided by Erbo. -->
<dictionary>
<file>/home/erbo/venice/WEB-INF/webster2.dict</file>
<file>/home/erbo/venice/WEB-INF/en-us.dict</file>
<file>/home/erbo/venice/WEB-INF/erbo.dict</file>
</dictionary>

File diff suppressed because it is too large Load Diff

1
lib/.gitignore vendored Normal file
View File

@ -0,0 +1 @@
mysql.jar

View File

@ -236,7 +236,7 @@ CREATE TABLE confs (
nuke_lvl SMALLINT UNSIGNED NOT NULL,
change_lvl SMALLINT UNSIGNED NOT NULL,
delete_lvl SMALLINT UNSIGNED NOT NULL,
top_topic SMALLINT,
top_topic SMALLINT DEFAULT 0,
name VARCHAR(128) NOT NULL,
descr VARCHAR(255),
icon_url VARCHAR(255),
@ -305,7 +305,7 @@ CREATE TABLE topics (
confid INT NOT NULL,
num SMALLINT NOT NULL,
creator_uid INT NOT NULL,
top_message INT,
top_message INT DEFAULT 0,
frozen TINYINT DEFAULT 0,
archived TINYINT DEFAULT 0,
createdate DATETIME NOT NULL,
@ -328,7 +328,7 @@ CREATE TABLE topicsettings (
# The "header" for a posted message.
CREATE TABLE posts (
postid BIGINT NOT NULL PRIMARY KEY AUTO_INCREMENT,
parent BIGINT NOT NULL,
parent BIGINT NOT NULL DEFAULT 0,
topicid INT NOT NULL,
num INT NOT NULL,
linecount INT,

View File

@ -109,4 +109,7 @@ public interface ConferenceContext
public abstract TopicContext getTopic(short number) throws DataException, AccessError;
public abstract TopicContext addTopic(String title, String zp_pseud, String zp_text)
throws DataException, AccessError;
} // end interface ConferenceContext

View File

@ -18,6 +18,7 @@
package com.silverwrist.venice.core.impl;
import java.sql.*;
import java.util.Date;
public interface ConferenceBackend extends SIGBackend
{
@ -27,6 +28,8 @@ public interface ConferenceBackend extends SIGBackend
public abstract void touchRead(Connection conn) throws SQLException;
public abstract void touchPost(Connection conn, java.util.Date post_date) throws SQLException;
public abstract String realConfAlias();
} // end interface ConferenceBackend

View File

@ -52,7 +52,7 @@ class ConferenceCoreData implements ConferenceData
private int nuke_level; // access level required to delete topics/scribble/nuke posts
private int change_level; // access level required to modify conference profile
private int delete_level; // access level required to delete conference
private int top_topic; // the highest topic number in the conference
private short top_topic; // the highest topic number in the conference
private String name; // the name of the conference
private String description; // the conference's description
private String cached_alias = null; // the cached alias (for getAnAlias)
@ -142,7 +142,7 @@ class ConferenceCoreData implements ConferenceData
nuke_level = rs.getInt("nuke_lvl");
change_level = rs.getInt("change_lvl");
delete_level = rs.getInt("delete_lvl");
top_topic = rs.getInt("top_topic");
top_topic = rs.getShort("top_topic");
name = rs.getString("name");
description = rs.getString("descr");
// "icon_url" and "color" fields are skipped
@ -817,6 +817,121 @@ class ConferenceCoreData implements ConferenceData
} // end getAnAlias
public ReturnTopicInfo createNewTopic(SIGBackend sig, String title, String pseud, String body,
int body_lines) throws DataException
{
Connection conn = null; // database connection
AuditRecord ar = null; // audit record
short new_topic_num; // sequential number of the new topic
int new_topic_id; // ID of the new topic
java.util.Date creation; // creation date for new topic
try
{ // get a database connection
conn = datapool.getConnection();
Statement stmt = conn.createStatement();
// lock the tables we need to use so we can update them
stmt.executeUpdate("LOCK TABLES confs WRITE, topics WRITE, posts WRITE, postdata WRITE;");
try
{ // determine the data for the initial topic
new_topic_num = (short)(top_topic + 1);
// add the topic row to the database
StringBuffer sql = new StringBuffer("INSERT INTO topics (confid, num, creator_uid, createdate, "
+ "lastupdate, name) VALUES (");
sql.append(confid).append(", ").append(new_topic_num).append(", ").append(sig.realUID()).append(", '");
creation = new java.util.Date();
String now_str = SQLUtil.encodeDate(creation);
sql.append(now_str).append("', '").append(now_str).append("', '").append(title).append("');");
if (logger.isDebugEnabled())
logger.debug("SQL: " + sql.toString());
stmt.executeUpdate(sql.toString());
// get the topic ID we just inserted
ResultSet rs = stmt.executeQuery("SELECT LAST_INSERT_ID();");
if (!(rs.next()))
throw new InternalStateError("createNewTopic() could not get back inserted Topic ID");
new_topic_id = rs.getInt(1);
// insert the "header" for the "zero post" in the topic
sql.setLength(0);
sql.append("INSERT INTO posts (topicid, num, linecount, creator_uid, posted, pseud) VALUES (");
sql.append(new_topic_id).append(", 0, ").append(body_lines).append(", ").append(sig.realUID());
sql.append(", '").append(now_str).append("', '").append(pseud).append("');");
if (logger.isDebugEnabled())
logger.debug("SQL: " + sql.toString());
stmt.executeUpdate(sql.toString());
// get the ID of the zero post
rs = stmt.executeQuery("SELECT LAST_INSERT_ID();");
if (!(rs.next()))
throw new InternalStateError("createNewTopic() could not get back inserted zero post ID");
long zero_post_id = rs.getLong(1);
// insert the post data
sql.setLength(0);
sql.append("INSERT INTO postdata (postid, data) VALUES (").append(zero_post_id).append(", '");
sql.append(body).append("');");
stmt.executeUpdate(sql.toString());
// touch the "conference" entry to reflect the update
sql.setLength(0);
sql.append("UPDATE confs SET lastupdate = '").append(now_str).append("', top_topic = ");
sql.append(new_topic_num).append(" WHERE confid = ").append(confid).append(';');
if (logger.isDebugEnabled())
logger.debug("SQL: " + sql.toString());
stmt.executeUpdate(sql.toString());
// touch the local variables, too
top_topic = new_topic_num;
last_update = creation;
// create an audit record indicating we were successful
ar = new AuditRecord(AuditRecord.CREATE_TOPIC,sig.realUID(),sig.userRemoteAddress(),sig.realSIGID(),
"confid=" + String.valueOf(confid),"num=" + String.valueOf(new_topic_num),
"title=" + title);
} // end try
finally
{ // we need to unlock the tables before we go
Statement ulk_stmt = conn.createStatement();
ulk_stmt.executeUpdate("UNLOCK TABLES;");
} // end finally
} // end try
catch (SQLException e)
{ // database error - this is a DataException
logger.error("DB error creating topic: " + e.getMessage(),e);
throw new DataException("unable to create topic: " + e.getMessage(),e);
} // end catch
finally
{ // make sure the connection is released before we go
try
{ // save off the audit record before we go, though
if ((ar!=null) && (conn!=null))
ar.store(conn);
} // end try
catch (SQLException e)
{ // we couldn't store the audit record!
logger.error("DB error saving audit record: " + e.getMessage(),e);
} // end catch
if (conn!=null)
datapool.releaseConnection(conn);
} // end finally
// we have three pieces of data to return, so we need a temporary object
return new ReturnTopicInfo(new_topic_id,new_topic_num,creation);
} // end createNewTopic
/*--------------------------------------------------------------------------------
* External static operations (usable only from within package)
*--------------------------------------------------------------------------------
@ -865,14 +980,14 @@ class ConferenceCoreData implements ConferenceData
// insert the record into the conferences table!
sql.setLength(0);
sql.append("INSERT INTO confs (createdate, read_lvl, post_lvl, create_lvl, hide_lvl, nuke_lvl, "
+ "change_lvl, delete_lvl, top_topic, name, descr) VALUES ('");
+ "change_lvl, delete_lvl, name, descr) VALUES ('");
created = new java.util.Date();
sql.append(SQLUtil.encodeDate(created)).append("', ").append(DefaultLevels.newConferenceRead(pvt));
sql.append(", ").append(DefaultLevels.newConferencePost(pvt)).append(", ");
sql.append(DefaultLevels.newConferenceCreate(pvt)).append(", ");
sql.append(DefaultLevels.newConferenceHide()).append(", ").append(DefaultLevels.newConferenceNuke());
sql.append(", ").append(DefaultLevels.newConferenceChange()).append(", ");
sql.append(DefaultLevels.newConferenceDelete()).append(", 0, '").append(SQLUtil.encodeString(name));
sql.append(DefaultLevels.newConferenceDelete()).append(", '").append(SQLUtil.encodeString(name));
sql.append("', '").append(SQLUtil.encodeString(description)).append("');");
if (logger.isDebugEnabled())
logger.debug("SQL: " + sql.toString());

View File

@ -76,4 +76,7 @@ public interface ConferenceData extends ReferencedData
public abstract String getAnAlias() throws DataException;
public abstract ReturnTopicInfo createNewTopic(SIGBackend sig, String title, String pseud, String body,
int body_lines) throws DataException;
} // end interface ConferenceData

View File

@ -88,4 +88,7 @@ public interface ConferenceSIGContext extends ReferencedData
public abstract String getAnAlias() throws DataException;
public abstract ReturnTopicInfo createNewTopic(SIGBackend sig, String title, String pseud, String body,
int body_lines) throws DataException;
} // end interface ConferenceSIGContext

View File

@ -611,4 +611,11 @@ class ConferenceSIGContextImpl implements ConferenceSIGContext
} // end getAnAlias
public ReturnTopicInfo createNewTopic(SIGBackend sig, String title, String pseud, String body,
int body_lines) throws DataException
{
return getConferenceData().createNewTopic(sig,title,pseud,body,body_lines);
} // end createNewTopic
} // end class ConferenceSIGContextImpl

View File

@ -21,6 +21,7 @@ import java.sql.*;
import java.util.*;
import org.apache.log4j.*;
import com.silverwrist.venice.db.*;
import com.silverwrist.venice.htmlcheck.*;
import com.silverwrist.venice.security.DefaultLevels;
import com.silverwrist.venice.core.*;
@ -810,6 +811,79 @@ class ConferenceUserContextImpl implements ConferenceContext, ConferenceBackend
} // end getTopic
public TopicContext addTopic(String title, String zp_pseud, String zp_text)
throws DataException, AccessError
{
if (!(getConferenceData().canCreateTopic(level)))
{ // not allowed to create a topic - bail out
logger.error("user not permitted to create new topic");
throw new AccessError("You are not permitted to create a topic in this conference.");
} // end if
// preprocess the three arguments through HTML checkers
HTMLChecker title_ch = engine.createCheckerObject(engine.HTMLC_POST_PSEUD);
HTMLChecker zp_pseud_ch = engine.createCheckerObject(engine.HTMLC_POST_PSEUD);
HTMLChecker zp_text_ch = engine.createCheckerObject(engine.HTMLC_POST_BODY);
try
{ // run all three arguments through the HTML checker
title_ch.append(title);
title_ch.finish();
zp_pseud_ch.append(zp_pseud);
zp_pseud_ch.finish();
zp_text_ch.append(zp_text);
zp_text_ch.finish();
} // end try
catch (AlreadyFinishedException e)
{ // this isn't right...
throw new InternalStateError("HTMLChecker erroneously throwing AlreadyFinishedException",e);
} // end catch
// Create the actual topic in the database.
String real_title;
ReturnTopicInfo new_topic_inf;
try
{ // call down to create the new topic!
real_title = title_ch.getValue();
new_topic_inf = getConferenceData().createNewTopic(sig,real_title,zp_pseud_ch.getValue(),
zp_text_ch.getValue(),zp_text_ch.getLines());
} // end try
catch (NotYetFinishedException e)
{ // this isn't right either!
throw new InternalStateError("HTMLChecker erroneously throwing NotYetFinishedException",e);
} // end catch
// now we need to reset our last post date
Connection conn = null;
try
{ // get a connection and feed it to the touchPost function
conn = datapool.getConnection();
touchPost(conn,new_topic_inf.getCreateDate());
} // end try
catch (SQLException e)
{ // this becomes a DataException
logger.error("DB error updating user information: " + e.getMessage(),e);
throw new DataException("unable to update user information: " + e.getMessage(),e);
} // end catch
finally
{ // make sure we release the connection before we go
if (conn!=null)
datapool.releaseConnection(conn);
} // end finally
// create the topic context to return to the user
return new TopicUserContextImpl(engine,this,datapool,new_topic_inf,real_title);
} // end addTopic
/*--------------------------------------------------------------------------------
* Implementations from interface UserBackend
*--------------------------------------------------------------------------------
@ -929,6 +1003,34 @@ class ConferenceUserContextImpl implements ConferenceContext, ConferenceBackend
} // end touchRead
public void touchPost(Connection conn, java.util.Date post_date) throws SQLException
{
Statement stmt = conn.createStatement();
StringBuffer sql = new StringBuffer();
if (settings_loaded)
{ // generate an update statement
sql.append("UPDATE confsettings SET last_post = '").append(SQLUtil.encodeDate(post_date));
sql.append("' WHERE confid = ").append(confid).append(" AND uid = ").append(sig.realUID()).append(';');
} // end if
else
{ // need to insert a confsettings row
sql.append("INSERT INTO confsettings (confid, uid, default_pseud, last_read) VALUES (").append(confid);
sql.append(", ").append(sig.realUID()).append(", '").append(SQLUtil.encodeString(pseud)).append("', '");
sql.append(SQLUtil.encodeDate(post_date)).append("');");
} // end else
// execute the statement
stmt.executeUpdate(sql.toString());
// save off the values to our local fields
last_post = post_date;
settings_loaded = true;
} // end touchRead
public String realConfAlias()
{
try

View File

@ -0,0 +1,69 @@
/*
* 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 Venice Web Community System.
*
* The Initial Developer of the Original Code is Eric J. Bowersox <erbo@silcom.com>,
* for Silverwrist Design Studios. Portions created by Eric J. Bowersox are
* Copyright (C) 2001 Eric J. Bowersox/Silverwrist Design Studios. All Rights Reserved.
*
* Contributor(s):
*/
package com.silverwrist.venice.core.impl;
import java.util.Date;
public class ReturnTopicInfo
{
/*--------------------------------------------------------------------------------
* Attributes
*--------------------------------------------------------------------------------
*/
private int topic_id;
private short topic_num;
private Date create_date;
/*--------------------------------------------------------------------------------
* Constructor
*--------------------------------------------------------------------------------
*/
public ReturnTopicInfo(int topic_id, short topic_num, Date create_date)
{
this.topic_id = topic_id;
this.topic_num = topic_num;
this.create_date = create_date;
} // end constructor
/*--------------------------------------------------------------------------------
* External operations
*--------------------------------------------------------------------------------
*/
public int getTopicID()
{
return topic_id;
} // end getTopicID
public short getTopicNum()
{
return topic_num;
} // end getTopicNum
public Date getCreateDate()
{
return create_date;
} // end getCreateDate
} // end class ReturnTopicInfo

View File

@ -79,7 +79,27 @@ class TopicUserContextImpl implements TopicContext
this.hidden = hidden;
this.unread = unread;
} // end TopicUserContextImpl
} // end constructor
TopicUserContextImpl(EngineBackend engine, ConferenceBackend conf, DataPool datapool, ReturnTopicInfo inf,
String name)
{
this.engine = engine;
this.conf = conf;
this.datapool = datapool;
this.topicid = inf.getTopicID();
this.topicnum = inf.getTopicNum();
this.creator_uid = conf.realUID();
this.top_message = 0;
this.frozen = false;
this.archived = false;
this.created = inf.getCreateDate();
this.lastupdate = inf.getCreateDate();
this.name = name;
this.hidden = false;
this.unread = 1;
} // end constructor
/*--------------------------------------------------------------------------------
* Internal functions
@ -553,23 +573,24 @@ class TopicUserContextImpl implements TopicContext
break;
case ConferenceContext.DISPLAY_NEW:
where_clause = "hidden = 0 AND unread > 0"; // only non-hidden topics w/unread messages
// only non-hidden topics w/unread messages
where_clause = "IFNULL(s.hidden,0) = 0 AND t.top_message > IFNULL(s.last_message,-1)";
break;
case ConferenceContext.DISPLAY_ACTIVE:
where_clause = "t.archived = 0 AND hidden = 0"; // only non-hidden, non-archived topics
where_clause = "t.archived = 0 AND IFNULL(s.hidden,0) = 0"; // only non-hidden, non-archived topics
break;
case ConferenceContext.DISPLAY_ALL:
where_clause = "t.archived = 0 AND hidden = 0"; // only non-hidden, non-archived topics
where_clause = "t.archived = 0 AND IFNULL(s.hidden,0) = 0"; // only non-hidden, non-archived topics
break;
case ConferenceContext.DISPLAY_HIDDEN:
where_clause = "hidden = 1"; // only hidden topics
where_clause = "IFNULL(s.hidden,0) = 1"; // only hidden topics
break;
case ConferenceContext.DISPLAY_ARCHIVED:
where_clause = "t.archived = 1 AND hidden = 0"; // only non-hidden, archived topics
where_clause = "t.archived = 1 AND IFNULL(s.hidden,0) = 0"; // only non-hidden, archived topics
break;
default:

View File

@ -435,7 +435,7 @@ public class VeniceEngineImpl implements VeniceEngine, EngineBackend
String[] dictfiles = new String[dictionary_tmp.size()];
for (int i=0; i<dictionary_tmp.size(); i++)
dictfiles[i] = (String)(dictionary_tmp.get(i));
LazyLexicon lex = new LazyLexicon(dictfiles);
LazyTreeLexicon lex = new LazyTreeLexicon(dictfiles);
spell_rewriter.addDictionary(lex);
html_configs = new HTMLCheckerConfig[4]; // create the array

View File

@ -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 Venice Web Community System.
*
* The Initial Developer of the Original Code is Eric J. Bowersox <erbo@silcom.com>,
* for Silverwrist Design Studios. Portions created by Eric J. Bowersox are
* Copyright (C) 2001 Eric J. Bowersox/Silverwrist Design Studios. All Rights Reserved.
*
* Contributor(s):
*/
package com.silverwrist.venice.htmlcheck.dict;
class DictNode
{
/*--------------------------------------------------------------------------------
* Static data members
*--------------------------------------------------------------------------------
*/
private static final int[] permute_tab =
{ 7, 21, 12, 9, 1, 13, 18, 10, 5, 25, 23, 11, 16, 3, 6, 14, 24, 4, 8, 2, 15, 20, 19, 22, 17, 26 };
/*--------------------------------------------------------------------------------
* Attributes
*--------------------------------------------------------------------------------
*/
private boolean terminal = false;
private DictNode[] p = null;
/*--------------------------------------------------------------------------------
* Constructor
*--------------------------------------------------------------------------------
*/
DictNode()
{ // do nothing
} // end constructor
/*--------------------------------------------------------------------------------
* Internal functions
*--------------------------------------------------------------------------------
*/
private final int permute(char ltr)
{
if (ltr=='-')
return 0;
else
return permute_tab[ltr - 'a'];
} // end permute
/*--------------------------------------------------------------------------------
* External operations
*--------------------------------------------------------------------------------
*/
final DictNode getRef(char ltr)
{
if (p==null)
return null;
int x = permute(ltr);
if (x<p.length)
return p[x];
else
return null;
} // end getRef
final void setRef(char ltr, DictNode node)
{
int x = permute(ltr);
if ((p==null) || (x>=p.length))
{ // the internal array needs to be expanded
DictNode[] new_p = new DictNode[x+1];
int i;
if (p!=null)
{ // copy the old portion over, set the new portion
System.arraycopy(p,0,new_p,0,p.length);
for (i=p.length; i<x+1; i++)
new_p[i] = null;
} // end if
else
{ // initialize the whole thing
for (i=0; i<x+1; i++)
new_p[i] = null;
} // end else
p = new_p;
} // end if
p[x] = node;
} // end setRef
final boolean isTerminal()
{
return terminal;
} // end isTerminal
final void setTerminal()
{
terminal = true;
} // end setTerminal
} // end class DictNode

View File

@ -0,0 +1,162 @@
/*
* 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 Venice Web Community System.
*
* The Initial Developer of the Original Code is Eric J. Bowersox <erbo@silcom.com>,
* for Silverwrist Design Studios. Portions created by Eric J. Bowersox are
* Copyright (C) 2001 Eric J. Bowersox/Silverwrist Design Studios. All Rights Reserved.
*
* Contributor(s):
*/
package com.silverwrist.venice.htmlcheck.dict;
import java.io.*;
import org.apache.log4j.*;
import com.silverwrist.venice.htmlcheck.SpellingDictionary;
public class LazyTreeLexicon implements Runnable, SpellingDictionary
{
/*--------------------------------------------------------------------------------
* Static data members
*--------------------------------------------------------------------------------
*/
private static Category logger = Category.getInstance(LazyTreeLexicon.class.getName());
/*--------------------------------------------------------------------------------
* Attributes
*--------------------------------------------------------------------------------
*/
TreeLexicon inner_lex = null;
private String[] filenames;
/*--------------------------------------------------------------------------------
* Constructor
*--------------------------------------------------------------------------------
*/
public LazyTreeLexicon(String[] filenames)
{
this.filenames = filenames; // save off the file names to be loaded
// spin off the load process into the background
Thread thrd = new Thread(this);
thrd.start();
} // end constructor
/*--------------------------------------------------------------------------------
* Internal functions
*--------------------------------------------------------------------------------
*/
private synchronized TreeLexicon getLex()
{
while (inner_lex==null)
{ // wait for the inner thread to finish creating the lexicon
if (logger.isDebugEnabled())
logger.debug("LazyLexicon: waiting for lex to load...");
try
{ // park the thread here until we know what's up
wait();
} // end try
catch (InterruptedException e)
{ // do nothing
} // end catch
} // end while
return inner_lex;
} // end getLex
/*--------------------------------------------------------------------------------
* Implementations from class Runnable
*--------------------------------------------------------------------------------
*/
public void run()
{
TreeLexicon lex = new TreeLexicon();
if (logger.isDebugEnabled())
logger.debug("LazyTreeLexicon loading " + String.valueOf(filenames.length) + " lexicon(s)");
for (int i=0; i<filenames.length; i++)
{ // load data into the lexicon in turn
if (logger.isDebugEnabled())
logger.debug("LazyTreeLexicon loading file: " + filenames[i]);
try
{ // start reading the file
BufferedReader rdr = new BufferedReader(new FileReader(filenames[i]));
int counter = 0;
String word = rdr.readLine();
while (word!=null)
{ // add each word to the lexicon as we go
lex.addWord(word);
word = rdr.readLine();
if (((++counter % 1000)==0) && logger.isDebugEnabled())
logger.debug("loaded " + String.valueOf(counter) + " words");
} // end while
rdr.close();
if (logger.isDebugEnabled())
logger.debug("finished loading " + filenames[i] + ", " + String.valueOf(counter) + " words loaded");
} // end try
catch (IOException e)
{ // trap it and go on to the next one
logger.error("IOException loading lexfile " + filenames[i] + ": " + e.getMessage());
} // end catch
} // end for
if (logger.isDebugEnabled())
logger.debug("LazyTreeLexicon load complete.");
synchronized (this)
{ // set the lexicon reference and wake up all waiting threads
inner_lex = lex;
notifyAll();
} // end synchronized block
System.gc(); // free any extra crap generated by the lexicon load
if (logger.isDebugEnabled())
logger.debug("LazyTreeLexicon GC complete.");
} // end run
/*--------------------------------------------------------------------------------
* Implementations from interface SpellingDictionary
*--------------------------------------------------------------------------------
*/
public int size()
{
return getLex().size();
} // end size
public boolean checkWord(String word)
{
return getLex().checkWord(word);
} // end checkWord
} // end class LazyTreeLexicon

View File

@ -20,10 +20,18 @@ package com.silverwrist.venice.htmlcheck.dict;
import java.io.*;
import java.util.*;
import java.util.zip.*;
import org.apache.log4j.*;
import com.silverwrist.venice.htmlcheck.ModSpellingDictionary;
public class Lexicon implements ModSpellingDictionary, Serializable
{
/*--------------------------------------------------------------------------------
* Static data members
*--------------------------------------------------------------------------------
*/
private static Category logger = Category.getInstance(Lexicon.class.getName());
/*--------------------------------------------------------------------------------
* Attributes
*--------------------------------------------------------------------------------
@ -152,17 +160,24 @@ public class Lexicon implements ModSpellingDictionary, Serializable
public synchronized void readFile(String filename) throws IOException
{
if (logger.isDebugEnabled())
logger.debug("Lexicon.readFile loading " + filename);
BufferedReader rdr = new BufferedReader(new FileReader(filename));
int counter = 0;
String word = rdr.readLine();
while (word!=null)
{ // add each word to the lexicon as we go
addWord(word);
word = rdr.readLine();
if (((++counter % 1000)==0) && logger.isDebugEnabled())
logger.debug("loaded " + String.valueOf(counter) + " words");
} // end while
rdr.close();
if (logger.isDebugEnabled())
logger.debug("finished loading " + filename + ", " + String.valueOf(counter) + " words loaded");
} // end readFile
@ -184,7 +199,7 @@ public class Lexicon implements ModSpellingDictionary, Serializable
real_stm = new ObjectInputStream(stm);
try
{ // attempt to read the LExicon object
{ // attempt to read the Lexicon object
Object tmp = real_stm.readObject();
if (tmp instanceof Lexicon)
return (Lexicon)tmp;

View File

@ -0,0 +1,120 @@
/*
* 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 Venice Web Community System.
*
* The Initial Developer of the Original Code is Eric J. Bowersox <erbo@silcom.com>,
* for Silverwrist Design Studios. Portions created by Eric J. Bowersox are
* Copyright (C) 2001 Eric J. Bowersox/Silverwrist Design Studios. All Rights Reserved.
*
* Contributor(s):
*/
package com.silverwrist.venice.htmlcheck.dict;
import com.silverwrist.venice.htmlcheck.ModSpellingDictionary;
public class TreeLexicon implements ModSpellingDictionary
{
/*--------------------------------------------------------------------------------
* Attributes
*--------------------------------------------------------------------------------
*/
private DictNode root; // root node of the trie that contains our data
private int count; // number of words in the lexicon
/*--------------------------------------------------------------------------------
* Constructor
*--------------------------------------------------------------------------------
*/
public TreeLexicon()
{
root = new DictNode();
count = 0;
} // end constructor
/*--------------------------------------------------------------------------------
* Implementations from interface SpellingDictionary
*--------------------------------------------------------------------------------
*/
public synchronized int size()
{
return count;
} // end size
public synchronized boolean checkWord(String word)
{
String real_word = word.toLowerCase();
DictNode cur = root;
for (int i=0; i<real_word.length(); i++)
{ // loop through the characters and move down the trie
char ch = real_word.charAt(i);
if (((ch<'a') || (ch>'z')) && (ch!='-'))
return false;
cur = cur.getRef(ch);
if (cur==null)
return false;
} // end for
return cur.isTerminal();
} // end checkWord
/*--------------------------------------------------------------------------------
* Implementations from interface ModSpellingDictionary
*--------------------------------------------------------------------------------
*/
public synchronized void addWord(String word)
{
String real_word = word.toLowerCase();
DictNode cur = root;
for (int i=0; i<real_word.length(); i++)
{ // move through the word - bail out if bogus chars
char ch = real_word.charAt(i);
if (((ch<'a') || (ch>'z')) && (ch!='-'))
return;
// advance down through trie
DictNode next = cur.getRef(ch);
if (next==null)
{ // strike out into a new DictNode
next = new DictNode();
cur.setRef(ch,next);
} // end if
cur = next;
} // end for
cur.setTerminal();
count++;
} // end addWord
public void delWord(String word)
{ // not implemented for this lexicon type
} // end delWord
public synchronized void clear()
{
root = new DictNode();
count = 0;
} // end clear
} // end class TreeLexicon

View File

@ -154,7 +154,7 @@ class HTMLCheckerImpl implements HTMLChecker, HTMLCheckerBackend, RewriterServic
{ // get each rewriter, and wrap it if it has a name
Rewriter r = (Rewriter)(it.next());
String name = r.getName();
if (r!=null)
if (name!=null)
{ // wrap it in a CountingRewriter and hash it...
CountingRewriter cr = new CountingRewriter(r);
counters.put(name,cr);

View File

@ -213,6 +213,8 @@ public class ConfDisplay extends VeniceServlet
{ // all commands require a SIG parameter
sig = getSIGParameter(request,user);
changeMenuSIG(request,sig);
if (logger.isDebugEnabled())
logger.debug("found SIG #" + String.valueOf(sig.getSIGID()));
} // end try
catch (DataException de)
@ -222,11 +224,13 @@ public class ConfDisplay extends VeniceServlet
} // end catch
if (content!=null)
if (content==null)
{ // we got the SIG parameter OK
try
{ // all commands require a conference parameter
conf = getConferenceParameter(request,sig);
if (logger.isDebugEnabled())
logger.debug("found conf #" + String.valueOf(conf.getConfID()));
} // end try
catch (DataException de)
@ -238,11 +242,19 @@ public class ConfDisplay extends VeniceServlet
} // end if
if (content!=null)
if (content==null)
{ // we got the conference parameter OK
try
{ // there's an optional topic parameter
topic = getTopicParameter(request,conf);
if (logger.isDebugEnabled())
{ // do a little debugging output
if (topic!=null)
logger.debug("found topic #" + String.valueOf(topic.getTopicID()));
else
logger.debug("no topic specified");
} // end if
} // end try
catch (DataException de)
@ -268,26 +280,30 @@ public class ConfDisplay extends VeniceServlet
} // end catch
if (content!=null)
if (content==null)
{ // OK, let's handle the display now
if (topic!=null)
{ // we're handling messages within a single topic
if (logger.isDebugEnabled())
logger.debug("MODE: display messages in topic");
// TODO: handle this somehow
} // end if
else
{ // we're displaying the conference's topic list
if (logger.isDebugEnabled())
logger.debug("MODE: display topics in conference");
TopicSortHolder opts = TopicSortHolder.retrieve(request.getSession(true));
try
{ // get any changes to view or sort options
getViewSortDefaults(request,conf.getConfID(),opts);
// get the topic list in the desired order!
List topic_list = conf.getTopicList(opts.getViewOption(conf.getConfID()),
// create the topic list
content = new TopicListing(request,sig,conf,opts.getViewOption(conf.getConfID()),
opts.getSortOption(conf.getConfID()));
// TODO: create the "next topic" list to use for "read next"
// TODO: create the page view
page_title = "Topics in " + conf.getName();
} // end try
catch (ValidationException ve)

View File

@ -7,7 +7,7 @@
* 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 Venice Web Community System.
* The Original Code is the Venice Web Communities System.
*
* The Initial Developer of the Original Code is Eric J. Bowersox <erbo@silcom.com>,
* for Silverwrist Design Studios. Portions created by Eric J. Bowersox are
@ -65,6 +65,32 @@ public class ConfOperations extends VeniceServlet
} // end getSIGParameter
private static ConferenceContext getConferenceParameter(ServletRequest request, SIGContext sig)
throws ValidationException, DataException, AccessError
{
String str = request.getParameter("conf");
if (str==null)
{ // no conference parameter - bail out now!
logger.error("Conference parameter not specified!");
throw new ValidationException("No conference specified.");
} // end if
try
{ // turn the string into a ConfID, and thence to a ConferenceContext
int confid = Integer.parseInt(str);
return sig.getConferenceContext(confid);
} // end try
catch (NumberFormatException nfe)
{ // error in Integer.parseInt
logger.error("Cannot convert conference parameter '" + str + "'!");
throw new ValidationException("Invalid conference parameter.");
} // end catch
} // end getConferenceParameter
private CreateConferenceDialog makeCreateConferenceDialog() throws ServletException
{
final String desired_name = "CreateConferenceDialog";
@ -82,6 +108,28 @@ public class ConfOperations extends VeniceServlet
} // end makeCreateConferenceDialog
private static boolean validateNewTopic(ServletRequest request) throws ValidationException
{
boolean is_title_null, is_zp_null;
String foo = request.getParameter("title");
if (foo==null)
throw new ValidationException("Title parameter was not specified.");
is_title_null = (foo.length()==0);
foo = request.getParameter("pseud");
if (foo==null)
throw new ValidationException("Pseud parameter was not specified.");
foo = request.getParameter("pb");
if (foo==null)
throw new ValidationException("Body text was not specified.");
is_zp_null = (foo.length()==0);
return is_title_null || is_zp_null;
} // end validateNewTopic
/*--------------------------------------------------------------------------------
* Overrides from class HttpServlet
*--------------------------------------------------------------------------------
@ -147,6 +195,42 @@ public class ConfOperations extends VeniceServlet
} // end else
} // end if
else if (cmd.equals("T"))
{ // "T" = "Create topic" (requires conference parameter)
try
{ // start by getting the conference parameter
ConferenceContext conf = getConferenceParameter(request,sig);
// create the New Topic form
NewTopicForm ntf = new NewTopicForm(sig,conf);
ntf.setupNewRequest();
content = ntf;
page_title = "Create New Topic";
} // end try
catch (ValidationException ve)
{ // we can't get the conference parameter
page_title = "Error";
content = new ErrorBox(null,ve.getMessage(),
"sigprofile?sig=" + String.valueOf(sig.getSIGID()));
} // end catch
catch (AccessError ae)
{ // we have some sort of access problem
page_title = "Access Error";
content = new ErrorBox(page_title,ae.getMessage(),
"sigprofile?sig=" + String.valueOf(sig.getSIGID()));
} // end catch
catch (DataException de)
{ // some sort of error in the database
page_title = "Database Error";
content = new ErrorBox(page_title,"Database error finding conference: " + de.getMessage(),
"sigprofile?sig=" + String.valueOf(sig.getSIGID()));
} // end catch
} // end else
else
{ // any unrecognized command jumps us to "conference list"
try
@ -239,9 +323,9 @@ public class ConfOperations extends VeniceServlet
{ // attempt to create the conference!
ConferenceContext conf = dlg.doDialog(sig);
// success! go back to the conference list
// TODO: want to jump to the conference's "topic list" page if possible
rdat.redirectTo(location);
// success! go to the conference's topic list
rdat.redirectTo("confdisp?sig=" + String.valueOf(sig.getSIGID()) + "&conf="
+ String.valueOf(conf.getConfID()));
return;
} // end try
@ -284,7 +368,136 @@ public class ConfOperations extends VeniceServlet
} // end else
} // end if ("C" command)
else if (cmd.equals("T"))
{ // "T" command = Create New Topic (requires conference parameter)
ConferenceContext conf = null;
try
{ // start by getting the conference parameter
conf = getConferenceParameter(request,sig);
} // end try
catch (ValidationException ve)
{ // we can't get the conference parameter
page_title = "Error";
content = new ErrorBox(null,ve.getMessage(),location);
} // end catch
catch (AccessError ae)
{ // we have some sort of access problem
page_title = "Access Error";
content = new ErrorBox(page_title,ae.getMessage(),location);
} // end catch
catch (DataException de)
{ // some sort of error in the database
page_title = "Database Error";
content = new ErrorBox(page_title,"Database error finding conference: " + de.getMessage(),location);
} // end catch
if (content==null)
{ // determine what to do based on the button pressed
String on_error = "confdisp?sig=" + String.valueOf(sig.getSIGID()) + "&conf="
+ String.valueOf(conf.getConfID());
if (isImageButtonClicked(request,"cancel"))
{ // the user chickened out - go back to the conference display
rdat.redirectTo(on_error);
return;
} // end if
if (isImageButtonClicked(request,"preview"))
{ // generate a preview and redisplay the form
NewTopicForm ntf = new NewTopicForm(sig,conf);
try
{ // do a preview generation
ntf.generatePreview(getVeniceEngine(),request);
if (ntf.isNullRequest())
{ // no title or text specified - this is a "204 No Content" return
rdat.nullResponse();
return;
} // end if
content = ntf;
page_title = "Preview New Topic";
} // end try
catch (ValidationException ve)
{ // something messed up in the preview generation
page_title = "Error";
content = new ErrorBox(null,ve.getMessage(),on_error);
} // end catch
} // end if ("preview" button clicked)
else if (isImageButtonClicked(request,"post"))
{ // OK, let's do a post request!
try
{ // first validate that we've got all the parameters
if (validateNewTopic(request))
{ // this is a null request - send a null response
rdat.nullResponse();
return;
} // end if
// add the new topic!
TopicContext topic = conf.addTopic(request.getParameter("title"),request.getParameter("pseud"),
request.getParameter("pb"));
final String yes = "Y";
if (yes.equals(request.getParameter("attach")))
{ // we need to upload an attachment for this post
// TODO: jump somewhere to upload an attachment
rdat.redirectTo(on_error);
return;
} // end if
else
{ // the post is complete, we need only jump to the topic
// TODO: jump straight to the new topic
rdat.redirectTo(on_error);
return;
} // end else
} // end try
catch (ValidationException ve)
{ // the validation of parameters failed
page_title = "Error";
content = new ErrorBox(null,ve.getMessage(),on_error);
} // end catch
catch (DataException de)
{ // display a database error
page_title = "Database Error";
content = new ErrorBox(page_title,"Database error adding topic: " + de.getMessage(),on_error);
} // end catch
catch (AccessError ae)
{ // some sort of access problem
page_title = "Access Error";
content = new ErrorBox(page_title,ae.getMessage(),on_error);
} // end catch
} // end else if
else
{ // we don't know what button was pressed
page_title = "Internal Error";
logger.error("no known button click on ConfOperations.doPost, cmd=T");
content = new ErrorBox(page_title,"Unknown command button pressed",on_error);
} // end else
} // end if (we got the conference parameter OK)
} // end else if ("T" command)
else
{ // unrecognized command!
page_title = "Internal Error";

View File

@ -48,6 +48,7 @@ public class Variables
public static final String LOGIN_COOKIE = "VeniceLogin";
private static Category logger = Category.getInstance(Variables.class.getName());
private static Integer engine_gate = new Integer(0);
/*--------------------------------------------------------------------------------
* External static operations
@ -56,6 +57,8 @@ public class Variables
public static VeniceEngine getVeniceEngine(ServletContext ctxt) throws ServletException
{
synchronized(engine_gate)
{ // we must synchronize efforts to access the engine
Object foo = ctxt.getAttribute(ENGINE_ATTRIBUTE);
if (foo!=null)
return (VeniceEngine)foo;
@ -83,6 +86,8 @@ public class Variables
} // end catch
} // end synchronized block
} // end getVeniceEngine
public static UserContext getUserContext(ServletContext ctxt, ServletRequest request, HttpSession session)

View File

@ -7,7 +7,7 @@
* 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 Venice Web Community System.
* The Original Code is the Venice Web Communities System.
*
* The Initial Developer of the Original Code is Eric J. Bowersox <erbo@silcom.com>,
* for Silverwrist Design Studios. Portions created by Eric J. Bowersox are

View File

@ -0,0 +1,224 @@
/*
* 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 Venice Web Communities System.
*
* The Initial Developer of the Original Code is Eric J. Bowersox <erbo@silcom.com>,
* for Silverwrist Design Studios. Portions created by Eric J. Bowersox are
* Copyright (C) 2001 Eric J. Bowersox/Silverwrist Design Studios. All Rights Reserved.
*
* Contributor(s):
*/
package com.silverwrist.venice.servlets.format;
import java.util.*;
import javax.servlet.*;
import com.silverwrist.venice.ValidationException;
import com.silverwrist.venice.htmlcheck.*;
import com.silverwrist.venice.core.*;
public class NewTopicForm implements JSPRender
{
/*--------------------------------------------------------------------------------
* Static data members
*--------------------------------------------------------------------------------
*/
// Attribute name for request attribute
protected static final String ATTR_NAME = "com.silverwrist.venice.content.NewTopicForm";
/*--------------------------------------------------------------------------------
* Attributes
*--------------------------------------------------------------------------------
*/
private SIGContext sig; // the SIG we're in
private ConferenceContext conf; // the conference being created in
private String topic_name; // the initial topic name
private String pseud; // the pseud to display
private boolean attach; // is there an attachment?
private String post_box; // stuff from the post box
private String preview = null; // the preview & spellchuck data
private int num_errors = 0; // the number of spelling errors we get
/*--------------------------------------------------------------------------------
* Constructor
*--------------------------------------------------------------------------------
*/
public NewTopicForm(SIGContext sig, ConferenceContext conf)
{
this.sig = sig;
this.conf = conf;
} // end constrcutor
/*--------------------------------------------------------------------------------
* External static functions
*--------------------------------------------------------------------------------
*/
public static NewTopicForm retrieve(ServletRequest request)
{
return (NewTopicForm)(request.getAttribute(ATTR_NAME));
} // end retrieve
/*--------------------------------------------------------------------------------
* Implementations from interface JSPRender
*--------------------------------------------------------------------------------
*/
public void store(ServletRequest request)
{
request.setAttribute(ATTR_NAME,this);
} // end store
public String getTargetJSPName()
{
return "newtopic.jsp";
} // end getTargetJSPName
/*--------------------------------------------------------------------------------
* External operations
*--------------------------------------------------------------------------------
*/
public void setupNewRequest()
{
topic_name = "";
pseud = conf.getDefaultPseud();
attach = false;
post_box = "";
} // end setupNewRequest
public void generatePreview(VeniceEngine engine, ServletRequest request) throws ValidationException
{
HTMLChecker check;
try
{ // run the data through the HTML Checker
check = engine.getEscapingChecker();
// sanitize the "title" parameter
String foo = request.getParameter("title");
if (foo==null)
throw new ValidationException("Title parameter was not specified.");
check.append(foo);
check.finish();
topic_name = check.getValue();
check.reset();
// sanitize the "pseud" parameter
foo = request.getParameter("pseud");
if (foo==null)
throw new ValidationException("Pseud parameter was not specified.");
check.append(foo);
check.finish();
pseud = check.getValue();
check.reset();
// sanitize the body text itself
foo = request.getParameter("pb");
if (foo==null)
throw new ValidationException("Body text was not specified.");
check.append(foo);
check.finish();
post_box = check.getValue();
// generate the body text preview
check = engine.getPreviewChecker();
check.append(foo);
check.finish();
preview = check.getValue();
num_errors = check.getCounter("spelling");
} // end try
catch (HTMLCheckerException e)
{ // make sure we catch the HTML Checker exceptions
throw new InternalStateError("spurious HTMLCheckerException in generatePreview",e);
} // end catch
// set the "attach" flag to save the checkbox state
final String yes = "Y";
attach = yes.equals(request.getParameter("attach"));
} // end generatePreview
public int getSIGID()
{
return sig.getSIGID();
} // end getSIGID
public int getConfID()
{
return conf.getConfID();
} // end getConfID
public String getConfName()
{
return conf.getName();
} // end getConfName
public String getTopicName()
{
return topic_name;
} // end getTopicName
public String getPseud()
{
return pseud;
} // end getPseud
public boolean getAttachCheck()
{
return attach;
} // end getAttachCheck
public String getPostBoxData()
{
return post_box;
} // end getPostBoxData
public boolean isPreview()
{
return (preview!=null);
} // end isPreview
public String getPreviewData()
{
return preview;
} // end getPreviewData
public int getNumSpellingErrors()
{
return num_errors;
} // end getNumSpellingErrors
public boolean isNullRequest()
{
return ((topic_name.length()==0) || (post_box.length()==0));
} // end isNullRequest
} // end class NewTopicForm

View File

@ -293,4 +293,10 @@ public class RenderData
} // end getActivityString
public void nullResponse()
{
response.setStatus(response.SC_NO_CONTENT);
} // end nullResponse
} // end class RenderData

View File

@ -0,0 +1,163 @@
/*
* 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 Venice Web Communities System.
*
* The Initial Developer of the Original Code is Eric J. Bowersox <erbo@silcom.com>,
* for Silverwrist Design Studios. Portions created by Eric J. Bowersox are
* Copyright (C) 2001 Eric J. Bowersox/Silverwrist Design Studios. All Rights Reserved.
*
* Contributor(s):
*/
package com.silverwrist.venice.servlets.format;
import java.util.*;
import javax.servlet.*;
import javax.servlet.http.*;
import com.silverwrist.venice.core.*;
public class TopicListing implements JSPRender
{
/*--------------------------------------------------------------------------------
* Static data members
*--------------------------------------------------------------------------------
*/
// Attribute name for request attribute
protected static final String ATTR_NAME = "com.silverwrist.venice.content.TopicListing";
/*--------------------------------------------------------------------------------
* Attributes
*--------------------------------------------------------------------------------
*/
private SIGContext sig; // the SIG we're in
private ConferenceContext conf; // the conference being listed
private int view_opt; // the view option used
private int sort_opt; // the sort option used
private List topic_list; // the topic list
private TopicVisitOrder visit_order; // indicates order in which topics are visited
/*--------------------------------------------------------------------------------
* Constructor
*--------------------------------------------------------------------------------
*/
public TopicListing(HttpServletRequest request, SIGContext sig, ConferenceContext conf, int view_opt,
int sort_opt) throws DataException, AccessError
{
this.sig = sig;
this.conf = conf;
this.view_opt = view_opt;
this.sort_opt = sort_opt;
this.topic_list = conf.getTopicList(view_opt,sort_opt);
this.visit_order = TopicVisitOrder.initialize(request.getSession(true),conf.getConfID(),this.topic_list);
} // end constructor
/*--------------------------------------------------------------------------------
* External static functions
*--------------------------------------------------------------------------------
*/
public static TopicListing retrieve(ServletRequest request)
{
return (TopicListing)(request.getAttribute(ATTR_NAME));
} // end retrieve
/*--------------------------------------------------------------------------------
* Implementations from interface JSPRender
*--------------------------------------------------------------------------------
*/
public void store(ServletRequest request)
{
request.setAttribute(ATTR_NAME,this);
} // end store
public String getTargetJSPName()
{
return "topics.jsp";
} // end getTargetJSPName
/*--------------------------------------------------------------------------------
* External operations
*--------------------------------------------------------------------------------
*/
public int getSIGID()
{
return sig.getSIGID();
} // end getSIGID
public int getConfID()
{
return conf.getConfID();
} // end getConfID
public String getConfName()
{
return conf.getName();
} // end getConfName
public boolean canDoReadNew()
{
return visit_order.isNext();
} // end canDoReadNew
public boolean canCreateTopic()
{
return conf.canCreateTopic();
} // end canCreateTopic
public boolean canAddToHotlist()
{
return false; // TODO: fix this
} // end canAddToHotlist
public boolean anyTopics()
{
return (topic_list.size()>0);
} // end anyTopics
public int getViewOption()
{
return view_opt;
} // end getViewOption
public boolean isView(int value)
{
return (view_opt==value);
} // end isSort
public boolean isSort(int value)
{
return (sort_opt==value);
} // end isSort
public Iterator getTopicIterator()
{
return topic_list.iterator();
} // end getTopicIterator
} // end class TopicListing

View File

@ -0,0 +1,172 @@
/*
* 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 Venice Web Communities System.
*
* The Initial Developer of the Original Code is Eric J. Bowersox <erbo@silcom.com>,
* for Silverwrist Design Studios. Portions created by Eric J. Bowersox are
* Copyright (C) 2001 Eric J. Bowersox/Silverwrist Design Studios. All Rights Reserved.
*
* Contributor(s):
*/
package com.silverwrist.venice.servlets.format;
import java.util.*;
import javax.servlet.*;
import javax.servlet.http.*;
import com.silverwrist.venice.core.*;
public class TopicVisitOrder
{
/*--------------------------------------------------------------------------------
* Static data members
*--------------------------------------------------------------------------------
*/
protected static final String ATTRIBUTE = "conf.display.topic.visitorder";
/*--------------------------------------------------------------------------------
* Attributes
*--------------------------------------------------------------------------------
*/
private int confid;
private short[] topics;
private boolean[] unread;
private int ndx_next;
/*--------------------------------------------------------------------------------
* Constructor
*--------------------------------------------------------------------------------
*/
protected TopicVisitOrder(int confid, List topiclist)
{
this.confid = confid;
this.topics = new short[topiclist.size()];
this.unread = new boolean[topiclist.size()];
ndx_next = -1;
for (int i=0; i<topiclist.size(); i++)
{ // fill in topic numbers and unread states
TopicContext t = (TopicContext)(topiclist.get(i));
topics[i] = t.getTopicNumber();
unread[i] = (t.getUnreadMessages()>0);
if (unread[i] && (ndx_next<0))
ndx_next = i;
} // end for
} // end constructor
private int moveNext()
{
int i = ndx_next;
do
{ // move forward to next "unread" topic
if (unread[i])
break;
if (++i==unread.length)
i = 0;
} while (i!=ndx_next); // end do
return i;
} // end moveNext
/*--------------------------------------------------------------------------------
* External static functions
*--------------------------------------------------------------------------------
*/
public static TopicVisitOrder initialize(HttpSession session, int confid, List topic_list)
{
TopicVisitOrder tvo = new TopicVisitOrder(confid,topic_list);
session.setAttribute(ATTRIBUTE,tvo);
return tvo;
} // end initialize
public static TopicVisitOrder retrieve(HttpSession session, int confid)
{
TopicVisitOrder tvo = (TopicVisitOrder)(session.getAttribute(ATTRIBUTE));
if (tvo!=null)
{ // make sure the conference is OK
if (tvo.confid!=confid)
{ // wrong conference - remove this
session.removeAttribute(ATTRIBUTE);
tvo = null;
} // end if
} // end if
return tvo;
} // end retrieve
/*--------------------------------------------------------------------------------
* External operations
*--------------------------------------------------------------------------------
*/
public short getNext()
{
if (ndx_next<0)
return -1;
else if (unread[ndx_next])
return topics[ndx_next];
else
return -1;
} // end getNext
public boolean isNext()
{
if (ndx_next>=0)
return unread[ndx_next];
else
return false;
} // end isNext
public void visit(short topnum)
{
if (ndx_next<0)
return;
if (topics[ndx_next]!=topnum)
{ // go searching for the matching topic number
for (int i=0; i<topics.length; i++)
{ // simple linear search - can't be tricky because the topic numbers might be
// in any order
if (topnum==topics[i])
{ // plant ourselves right there
unread[i] = false;
ndx_next = i;
break;
} // end if
} // end for
} // end if
else
unread[ndx_next] = false;
// move the "next" pointer
ndx_next = moveNext();
} // end visit
} // end class TopicVisitOrder

View File

@ -7,7 +7,7 @@
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 Venice Web Community System.
The Original Code is the Venice Web Communities System.
The Initial Developer of the Original Code is Eric J. Bowersox <erbo@silcom.com>,
for Silverwrist Design Studios. Portions created by Eric J. Bowersox are
@ -27,7 +27,6 @@
%>
<% if (rdat.useHTMLComments()) { %><!-- Conference list for SIG #<%= data.getSIGID() %> --><% } %>
<% rdat.writeContentHeader(out,"Conference List:",data.getSIGName()); %>
<%-- TODO: controls go here for creating new conferences and such --%>
<% if (data.getNumConferences()>0) { %>
<TABLE BORDER=0 ALIGN=LEFT>
<% for (int i=0; i<data.getNumConferences(); i++) { %>
@ -36,7 +35,7 @@
<IMG SRC="<%= rdat.getFullImagePath("purple-ball.gif") %>" ALT="*" WIDTH=14 HEIGHT=14 BORDER=0>
</TD>
<TD ALIGN=LEFT><%= rdat.getStdFontTag(null,2) %>
<% String path = "TODO?sig=" + String.valueOf(data.getSIGID()) + "&conf="
<% String path = "confdisp?sig=" + String.valueOf(data.getSIGID()) + "&conf="
+ String.valueOf(data.getConferenceID(i)); %>
<A HREF="<%= rdat.getEncodedServletPath(path) %>"><%= StringUtil.encodeHTML(data.getConferenceName(i)) %></A> -
Latest activity: <%= rdat.getActivityString(data.getLastUpdateDate(i)) %><BR>

80
web/format/newtopic.jsp Normal file
View File

@ -0,0 +1,80 @@
<%--
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 Venice Web Communities System.
The Initial Developer of the Original Code is Eric J. Bowersox <erbo@silcom.com>,
for Silverwrist Design Studios. Portions created by Eric J. Bowersox are
Copyright (C) 2001 Eric J. Bowersox/Silverwrist Design Studios. All Rights Reserved.
Contributor(s):
--%>
<%@ page import = "java.util.*" %>
<%@ page import = "com.silverwrist.util.StringUtil" %>
<%@ page import = "com.silverwrist.venice.core.*" %>
<%@ page import = "com.silverwrist.venice.servlets.Variables" %>
<%@ page import = "com.silverwrist.venice.servlets.format.*" %>
<%
NewTopicForm data = NewTopicForm.retrieve(request);
Variables.failIfNull(data);
RenderData rdat = RenderConfig.createRenderData(application,request,response);
%>
<% if (rdat.useHTMLComments()) { %><!-- Topic list for conf #<%= data.getConfID() %> --><% } %>
<% rdat.writeContentHeader(out,data.isPreview() ? "Preview New Topic" : "Create New Topic",
"in: " + data.getConfName()); %>
<% if (data.isPreview()) { %>
<%= rdat.getStdFontTag(null,3) %><B>
<% if (data.getNumSpellingErrors()==0) { %>
Your post did not contain any spelling errors.
<% } else if (data.getNumSpellingErrors()==1) { %>
There was 1 spelling error in your post.
<% } else { %>
There were <%= data.getNumSpellingErrors() %> spelling errors in your post.
<% } // end if %>
</B></FONT>
<P><PRE><%= data.getPreviewData() %></PRE><HR>
<% } // end if %>
<FORM METHOD="POST" ACTION="<%= rdat.getEncodedServletPath("confops") %>">
<INPUT TYPE="HIDDEN" NAME="sig" VALUE="<%= data.getSIGID() %>">
<INPUT TYPE="HIDDEN" NAME="conf" VALUE="<%= data.getConfID() %>">
<INPUT TYPE="HIDDEN" NAME="cmd" VALUE="T">
<TABLE BORDER=0 CELLPADDING=0>
<TR><TD ALIGN=LEFT COLSPAN=2>
<%= rdat.getStdFontTag(null,2) %>New topic name:</FONT><BR>
<INPUT TYPE="TEXT" NAME="title" SIZE=37 MAXLENGTH=255 VALUE="<%= data.getTopicName() %>">
</TD></TR>
<TR><TD ALIGN=LEFT COLSPAN=2>
<%= rdat.getStdFontTag(null,2) %>Your name/header:</FONT><BR>
<INPUT TYPE="TEXT" NAME="pseud" SIZE=37 MAXLENGTH=255 VALUE="<%= data.getPseud() %>">
<%= rdat.getStdFontTag(null,2) %><INPUT TYPE="CHECKBOX" NAME="attach"
VALUE="Y" <% if (data.getAttachCheck()) { %>CHECKED<% } %> > Attach a file</FONT>
</TD></TR>
<TR>
<TD ALIGN=LEFT><%= rdat.getStdFontTag(null,2) %>Message:</FONT></TD>
<TD ALIGN=RIGHT><%= rdat.getStdFontTag(null,2) %>
<A HREF="TODO" TARGET="_blank">HTML Guide</A>
</FONT></TD>
</TR>
<TR><TD ALIGN=LEFT COLSPAN=2>
<TEXTAREA NAME="pb" WRAP=SOFT ROWS=7 COLS=51><%= data.getPostBoxData() %></TEXTAREA>
</TD></TR>
<TR><TD ALIGN=CENTER COLSPAN=2>
<INPUT TYPE="IMAGE" SRC="<%= rdat.getFullImagePath("bn_preview.gif") %>" ALT="Preview" NAME="preview"
WIDTH=80 HEIGHT=24 BORDER=0>
&nbsp;&nbsp;
<INPUT TYPE="IMAGE" SRC="<%= rdat.getFullImagePath("bn_post.gif") %>" ALT="Post" NAME="post"
WIDTH=80 HEIGHT=24 BORDER=0>
&nbsp;&nbsp;
<INPUT TYPE="IMAGE" SRC="<%= rdat.getFullImagePath("bn_cancel.gif") %>" ALT="Cancel" NAME="cancel"
WIDTH=80 HEIGHT=24 BORDER=0>
</TD></TR>
</TABLE>
</FORM>
<% rdat.writeFooter(out); %>

180
web/format/topics.jsp Normal file
View File

@ -0,0 +1,180 @@
<%--
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 Venice Web Communities System.
The Initial Developer of the Original Code is Eric J. Bowersox <erbo@silcom.com>,
for Silverwrist Design Studios. Portions created by Eric J. Bowersox are
Copyright (C) 2001 Eric J. Bowersox/Silverwrist Design Studios. All Rights Reserved.
Contributor(s):
--%>
<%@ page import = "java.util.*" %>
<%@ page import = "com.silverwrist.util.StringUtil" %>
<%@ page import = "com.silverwrist.venice.core.*" %>
<%@ page import = "com.silverwrist.venice.servlets.Variables" %>
<%@ page import = "com.silverwrist.venice.servlets.format.*" %>
<%
TopicListing data = TopicListing.retrieve(request);
Variables.failIfNull(data);
RenderData rdat = RenderConfig.createRenderData(application,request,response);
String self = "confdisp?sig=" + String.valueOf(data.getSIGID()) + "&conf="
+ String.valueOf(data.getConfID());
String tmp;
%>
<% if (rdat.useHTMLComments()) { %><!-- Topic list for conf #<%= data.getConfID() %> --><% } %>
<% rdat.writeContentHeader(out,data.getConfName() + " Topics",null); %>
<%= rdat.getStdFontTag(null,2) %>
<DIV ALIGN="LEFT">
<A HREF="<%= rdat.getEncodedServletPath("confops?sig=" + String.valueOf(data.getSIGID())) %>"><IMG
SRC="<%= rdat.getFullImagePath("bn_conference_list.gif") %>" ALT="Conference List" WIDTH=80 HEIGHT=24
BORDER=0></A>&nbsp;&nbsp;
<% if (data.canCreateTopic()) { %>
<% tmp = "confops?sig=" + String.valueOf(data.getSIGID()) + "&conf="
+ String.valueOf(data.getConfID()) + "&cmd=T"; %>
<A HREF="<%= rdat.getEncodedServletPath(tmp) %>"><IMG SRC="<%= rdat.getFullImagePath("bn_add_topic.gif") %>"
ALT="Add Topic" WIDTH=80 HEIGHT=24 BORDER=0></A>&nbsp;&nbsp;
<% } // end if %>
<% if (data.canDoReadNew()) { %>
<A HREF="TODO"><IMG SRC="<%= rdat.getFullImagePath("bn_read_new.gif") %>"
ALT="Read New" WIDTH=80 HEIGHT=24 BORDER=0></A>&nbsp;&nbsp;
<% } // end if %>
<A HREF="TODO"><IMG SRC="<%= rdat.getFullImagePath("bn_manage.gif") %>"
ALT="Manage" WIDTH=80 HEIGHT=24 BORDER=0></A>&nbsp;&nbsp;
<% if (data.canAddToHotlist()) { %>
<A HREF="TODO"><IMG SRC="<%= rdat.getFullImagePath("bn_add_to_hotlist.gif") %>"
ALT="Add to HotList" WIDTH=80 HEIGHT=24 BORDER=0></A>&nbsp;&nbsp;
<% } // end if %>
</DIV>
<% if (data.anyTopics()) { %>
<TABLE WIDTH="100%" BORDER=0 CELLPADDING=0 CELLSPACING=3>
<TR VALIGN=TOP>
<TD ALIGN=LEFT WIDTH="1%"><%= rdat.getStdFontTag(null,2) %>
<% tmp = self + "&sort="
+ String.valueOf(data.isSort(ConferenceContext.SORT_NUMBER) ? -ConferenceContext.SORT_NUMBER
: ConferenceContext.SORT_NUMBER); %>
<A HREF="<%= rdat.getEncodedServletPath(tmp) %>">#</A>
</FONT></TD>
<TD ALIGN=LEFT><%= rdat.getStdFontTag(null,2) %>
<% tmp = self + "&sort="
+ String.valueOf(data.isSort(ConferenceContext.SORT_NAME) ? -ConferenceContext.SORT_NAME
: ConferenceContext.SORT_NAME); %>
<A HREF="<%= rdat.getEncodedServletPath(tmp) %>">Topic Name</A>
</FONT></TD>
<TD ALIGN=RIGHT><%= rdat.getStdFontTag(null,2) %>
<% tmp = self + "&sort="
+ String.valueOf(data.isSort(ConferenceContext.SORT_UNREAD) ? -ConferenceContext.SORT_UNREAD
: ConferenceContext.SORT_UNREAD); %>
<A HREF="<%= rdat.getEncodedServletPath(tmp) %>">New</A>
</FONT></TD>
<TD ALIGN=RIGHT><%= rdat.getStdFontTag(null,2) %>
<% tmp = self + "&sort="
+ String.valueOf(data.isSort(ConferenceContext.SORT_TOTAL) ? -ConferenceContext.SORT_TOTAL
: ConferenceContext.SORT_TOTAL); %>
<A HREF="<%= rdat.getEncodedServletPath(tmp) %>">Total</A>
</FONT></TD>
<TD ALIGN=LEFT><%= rdat.getStdFontTag(null,2) %>
<% tmp = self + "&sort="
+ String.valueOf(data.isSort(ConferenceContext.SORT_DATE) ? -ConferenceContext.SORT_DATE
: ConferenceContext.SORT_DATE); %>
<A HREF="<%= rdat.getEncodedServletPath(tmp) %>">Last Response</A>
</FONT></TD>
</TR>
<TR VALIGN=TOP><TD ALIGN=LEFT COLSPAN=5><%= rdat.getStdFontTag(null,2) %>&nbsp;</FONT></TD></TR>
<% Iterator it = data.getTopicIterator(); %>
<% while (it.hasNext()) { %>
<% TopicContext topic = (TopicContext)(it.next()); %>
<TR VALIGN=TOP>
<TD ALIGN=LEFT WIDTH="1%"><%= rdat.getStdFontTag(null,2) %>
<A HREF="TODO"><%= topic.getTopicNumber() %></A>
</FONT></TD>
<TD ALIGN=LEFT><%= rdat.getStdFontTag(null,2) %>
<A HREF="TODO"><%= topic.getName() %></A>
</FONT></TD>
<TD ALIGN=RIGHT><%= rdat.getStdFontTag(null,2) %>
<A HREF="TODO"><%= topic.getUnreadMessages() %></A>
</FONT></TD>
<TD ALIGN=RIGHT><%= rdat.getStdFontTag(null,2) %>
<A HREF="TODO"><%= topic.getTotalMessages() %></A>
</FONT></TD>
<TD ALIGN=LEFT><%= rdat.getStdFontTag(null,2) %>
<A HREF="TODO"><%= rdat.formatDateForDisplay(topic.getLastUpdateDate()) %></A>
</FONT></TD>
</TR>
<% } // end while (more topics in enumeration) %>
</TABLE><P>
<% } else { %>
<%
switch (data.getViewOption())
{
case ConferenceContext.DISPLAY_NEW:
%>
<EM>No topics with unread messages found.</EM>
<%
break;
case ConferenceContext.DISPLAY_ACTIVE:
case ConferenceContext.DISPLAY_ALL:
%>
<EM>No active topics found.</EM>
<%
break;
case ConferenceContext.DISPLAY_HIDDEN:
%>
<EM>No hidden topics found.</EM>
<%
break;
case ConferenceContext.DISPLAY_ARCHIVED:
%>
<EM>No archived topics found.</EM>
<%
break;
default:
%>
<EM>Invalid display option selected.</EM>
<%
break;
} // end switch
%>
<P>
<% } // end if %>
<DIV ALIGN="CENTER">
<B>[</B>
<% if (data.isView(ConferenceContext.DISPLAY_NEW)) { %>
<B>New</B>
<% } else { %>
<A HREF="<%= rdat.getEncodedServletPath(self + "&view=" + String.valueOf(ConferenceContext.DISPLAY_NEW)) %>">New</A>
<% } // end if %>
<B>|</B>
<% if (data.isView(ConferenceContext.DISPLAY_ACTIVE)) { %>
<B>Active</B>
<% } else { %>
<A HREF="<%= rdat.getEncodedServletPath(self + "&view=" + String.valueOf(ConferenceContext.DISPLAY_ACTIVE)) %>">Active</A>
<% } // end if %>
<B>|</B>
<% if (data.isView(ConferenceContext.DISPLAY_ALL)) { %>
<B>All</B>
<% } else { %>
<A HREF="<%= rdat.getEncodedServletPath(self + "&view=" + String.valueOf(ConferenceContext.DISPLAY_ALL)) %>">All</A>
<% } // end if %>
<B>|</B>
<% if (data.isView(ConferenceContext.DISPLAY_HIDDEN)) { %>
<B>Hidden</B>
<% } else { %>
<A HREF="<%= rdat.getEncodedServletPath(self + "&view=" + String.valueOf(ConferenceContext.DISPLAY_HIDDEN)) %>">Hidden</A>
<% } // end if %>
<B>|</B>
<% if (data.isView(ConferenceContext.DISPLAY_ARCHIVED)) { %>
<B>Archived</B>
<% } else { %>
<A HREF="<%= rdat.getEncodedServletPath(self + "&view=" + String.valueOf(ConferenceContext.DISPLAY_ARCHIVED)) %>">Archived</A>
<% } // end if %>
<B>]</B>
</DIV>
</FONT>
<% rdat.writeFooter(out); %>

Binary file not shown.

After

Width:  |  Height:  |  Size: 1000 B

BIN
web/images/bn_add_topic.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 954 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1001 B

BIN
web/images/bn_post.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 894 B

BIN
web/images/bn_preview.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 939 B

BIN
web/images/bn_read_new.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 970 B