<html> <!--AD_DND--> <!--download.html,v 3.3.2.2 2000/04/12 09:01:29 ron Exp--> <head> <title>Download</title> </head> <body bgcolor=#ffffff text=#000000> <h2>Download</h2> part of the <a href="index.html">ArsDigita Community System</a> by <a href="http://photo.net/philg/">Philip Greenspun</a> <br> implemented by <a href="mailto:ahmeds@mit.edu">Sarah Ahmed</a> and <a href="http://web.mit.edu/mobin/web">Usman Mobin</a> <hr> <ul> <li>User-accessible directory: <a href="/download/">/download/</a> <li>Site administrator directory: <a href="/admin/download/">/admin/download/</a> <li>Group admin pages: <a href="/download/admin/">/download/admin/</a> <li>Data model: <a href="/doc/sql/display-sql.tcl?url=/doc/sql/download.sql">/doc/sql/download.sql</a> <li>Tcl procs: /tcl/download-defs.tcl <li>Downloadable files: /web/foobar/download/ </ul> <h3>The Big Idea</h3> A publisher of reports or software would like to be able to ask "Who has downloaded any version of Bloatware 2000?" or to say "Spam everyone who downloaded Bloatware 2000 v2.3 and tell them to upgrade to the new exciting 3.0 version". Note that implicit in the foregoing is that users must register before downloading. A publisher might wish to charge for downloading or limit downloading to members of a group. <p> You'd think that much of this could be accomplished with our standard user-tracking stuff: <code>user_content_map</code> keeps track of which users have grabbed which files; the member value system can store charges for particular files (prices kept in the <code>static_pages</code> table). Some problems with using these modules include the fact that it is tough to annotate or group files in <code>static_pages</code> and the member value system is really more intended for subscribing users who pay once/month <h3>The Medium-sized Idea</h3> For each file to download, we store <ul> <h4>in the <code>downloads</code> table</h4> <li>download_id (unique key for a particular download) <li>download pretty name, e.g., "Bloatware 2000" <li>directory name, e.g., "bw2000" (valid name for a Unix directory; all versions of this download will be kept here) <li>group_id (is this download associated with a user group?) <li>a description of the item <h4>in the <code>download_versions</code> table</h4> <li>version_id (unique key for a particular version of a particular download) <li>download_id (same for all versions of a prticular download) <li>version (a number; larger is newer) <li>pseudo filename, e.g., "bloatware-2000.23.tar.gz" (what you want it to be saved as on the user's desktop) <li>release date (don't offer until midnight on this date) <li>status (promote, offer_if_asked, removed) </ul> <h4>Administration Setup</h4> <p> When the administrator decides to add a new downloadable file to the system, first the download directory must be created. In the current version of the module, this takes the form of the "Add New Download" function on the admin pages. Doing this function only creates the directory in which the downloadable files are stored. <p> Note that the directory into which the /download/ directory is placed (generally /web/server_name/) must be write accessible to nsadmin. This can either be accomplished by making the owner nsadmin or changing the permissions so that nsadmin has write privileges. Either way, if nsadmin can't write to this directory, an error indicating that "Folder <directory_name> could not be created" will result. <p> Actually placing the files in that directory is a second and seperate step. To upload a file to the server, select the directory name from the admin screens and then choose "Upload New Version" command. This allows the administrator to place a file on the server that is downloadable by users. <p> As a side note, if the intended use of the Download Module involves documents that can be opened by a program on the receiving machine (e.g. files of type .jgp, .html, .doc, .pdf, etc.), then the AOLserver's .ini file (<em>not</em> the ACS .ini file) must be modified to make AOLServer aware of these file types. This entry in the .ini file takes the form: <pre> [ns/server/servername/mimetypes] .doc=application/msword .ppt=application/powerpoint .xls=application/excel </pre> <h4>User View</h4> <p> To regular users, the downloadable file is visible based on the assigned name assigned when the download directory was created, combined with the version number. This isn't always the most obvious presentation of download filename, and will likely be customized by applications that offer other than software releases. <p> Downloads with a status of "promote" are offered on the top-level /download/ page. Downloads with a status of "offer_if_asked" are available on a drill-down page of "/download/one.tcl" where multiple versions of the same download may be offered. Downloads with a status of "removed" are only shown to the site or group administrator who has the option of changing their status or looking at an old version. <p> Users can see all files available for download (even before registration). However, upon actual download request of a file, the system checks the user's registration status and the visibility (e.g. "all", "registered_users", or "group_members") of the specific file and permits download accordingly (e.g. non_registered users are only allowed to anonymously download files with visibility="all", he is required to login otherwise). <h4>Implementation</h4> <p> Files and rows are never removed from the database/system. If someone wants to upload a new copy of version 2.3 of Bloatware 2000, it gets a new <code>version_id</code> and the old row is updated with a status of "removed". <p> We keep a separate table of rules that will allow downloading. The rules can take the following forms: <ul> <li>unregistered users can download any version of download_id 37 <li>all registered users can download any version of download_id 37 <li>all registered users can download v2.3 of download_id 37 <li>members of user_group 2213 can download any version of download_id 37 (user group membership can be made subject to approval) <li>users who have paid $X can download any version of download_id 37 <li>users who have paid $X can download v2.3 of download_id 37 <li>users who have paid $Y and who previously paid to download some other version of download_id 37 can download v2.3 of download_id 37 </ul> <h3>Under the Hood</h3> The files to download are generally stored outside the Web server root in a configurable location (by default /web/yourservername/download/). We chose not to store the downloads in an Oracle BLOB column. Downloads are presumed to be large and may require several minutes to transmit to a user with a modem. If we kept the files in a BLOB, we'd have to either copy the data first into a /tmp file or hold onto an Oracle connection for the entire download time. For a typical site configured with 8 Oracle connections maximum, this would mean that 8 simultaneous downloaders could wedge the service. <p> We keep metadata in an Oracle table and we use Oracle-generated keys as filenames so there is no risk of conflict in the event of concurrent updates of files to be downloaded (assuming there is indeed more than one site administrator). <p> The directory structure bears some mentioning. An objective is to keep this browsable via Emacs and still have some idea of what is going on, i.e., one should not have to also look into Oracle to verify what versions of what downloads are available. <p> <ul> <li>/web/yourservicename/download/bw2000/378.file is some version of Bloatware 2000, published by the site owner, with version_id in the database of 378. A corresponding file /web/foobar/download/bw2000/378.notes contains the latest information from Oracle, e.g., what version it is, what the document is called, the pseudo filename, etc. A notes file is only for sysadmins to read. The module never makes any use of informaiton in a notes file. <li>/web/foobar/download/groups/78/8234.html is version 1.0 of "How to turn your cubicle into an 8000-gallon marine fish tank", a document published by user group #78 (subcommunity of saltwater aquarium lovers). /web/foobar/download/groups/78/8234.notes contains ancillary info. </ul> <p> Via clever use of ns_register_proc, analogous to the static file comment attachment system (see /tcl/ad-html.tcl), we present URLs to the user for final downloading that look like /download/files/378/bloatware-2000.tar.gz (/download/files/ trips off the ns_register_proc; after that the format is /**version_id**/**psuedo-filename**). This will result in their browser defaulting them with a reasonable filename to "save as". <h3>Deep under the hood</h3> <pre> create sequence download_id_sequence start with 1; create table downloads ( download_id integer primary key, -- if scope=public, this is a download for the whole system -- if scope=group, this is a download for/from a subcommunity scope varchar(20) not null, -- will be NULL if scope=public group_id references user_groups on delete cascade, -- e.g., "Bloatware 2000" download_name varchar(100) not null, directory_name varchar(100) not null, description varchar(4000), -- is the description in HTML or plain text (the default) html_p char(1) default 'f' check(html_p in ('t','f')), creation_date date default sysdate not null, creation_user not null references users(user_id), creation_ip_address varchar(50) not null, -- state should be consistent constraint download_scope_check check ((scope='group' and group_id is not null) or (scope='public')) ); create index download_group_idx on downloads ( group_id ); create sequence download_version_id_sequence start with 1; create table download_versions ( version_id integer primary key, download_id not null references downloads on delete cascade, -- when this can go live before the public release_date date not null, pseudo_filename varchar(100) not null, -- might be the same for a series of .tar files, we'll serve -- the one with the largest version_id version varchar(4000), version_description varchar(4000), -- is the description in HTML or plain text (the default) version_html_p char(1) default 'f' check(version_html_p in ('t','f')), status varchar(30) check (status in ('promote', 'offer_if_asked', 'removed')), creation_date date default sysdate not null , creation_user references users on delete set null, creation_ip_address varchar(50) not null ); create sequence download_rule_id_sequence start with 1; create table download_rules ( rule_id integer primary key, -- one of the following will be not null version_id references download_versions on delete cascade, download_id references downloads on delete cascade, visibility varchar(30) check (visibility in ('all', 'registered_users', 'purchasers', 'group_members', 'previous_purchasers')), -- price to purchase or upgrade, typically NULL price number, -- currency code to feed to CyberCash or other credit card system currency varchar(3) default 'USD' references currency_codes , constraint download_version_null_check check ( download_id is not null or version_id is not null) ); -- PL/SQL proc -- returns 't' if a user can download, 'f' if not -- if supplied user_id is NULL, this is an unregistered user and we -- look for rules accordingly create or replace function download_authorized_p (v_version_id IN integer, v_user_id IN integer) return varchar2 IS v_visibility download_rules.visibility%TYPE; v_group_id downloads.group_id%TYPE; v_return_value varchar(30); BEGIN select visibility into v_visibility from download_rules where version_id = v_version_id; if v_visibility = 'all' then return 'authorized'; elsif v_visibility = 'group_members' then select group_id into v_group_id from downloads d, download_versions dv where dv.version_id = v_version_id and dv.download_id = d.download_id; select decode(count(*),0,'not_authorized','authorized') into v_return_value from user_group_map where user_id = v_user_id and group_id = v_group_id; return v_return_value; else select decode(count(*),0,'reg_required','authorized') into v_return_value from users where user_id = v_user_id; return v_return_value; end if; END download_authorized_p; / show errors -- history create sequence download_log_id_sequence start with 1; create table download_log ( log_id integer primary key, version_id not null references download_versions on delete cascade, -- user_id should reference users, but that interferes with -- downloadlog_user_delete_tr below. user_id references users on delete set null, entry_date date not null, ip_address varchar(50) not null, -- keeps track of why people downloaded this download_reasons varchar(4000) ); </pre> <h3>Legal Transactions</h3> From the <a href="/admin/download/">admin</a> pages, the administrator can <ul> <li>Add New Downloads <li>Upload New Versions <li>Add New Rules specific to a particular version/download <li>Edit Download/Version specific data <li>View Download History <li>Spam people who downloaded a specific download /version <li>Remove a version / download both from the file storage and database </ul> From the <a href="/download/">user</a> pages, the user can <ul> <li>Download a specific version that he/she is allowed to download <li>Input reasons for download </ul> <h3>Future Improvements</h3> We have left the money stuff unimplemented in this version of the download module, although the data model supports"purchaser"/ "previous purchaser" user visibility. It could be tied to a future version of the ecommerce module or be standalone. <hr> <a href="ahmeds@mit.edu"><address>ahmeds@mit.edu</address></a> </body> </html>