details: http://www.bx.psu.edu/hg/galaxy/rev/b45a5a51c7d1
changeset: 2814:b45a5a51c7d1
user: Kanwei Li <kanwei(a)gmail.com>
date: Thu Oct 01 14:01:53 2009 -0400
description:
trackster: feature tracks auto-resize to fit data, prepare for LRU cache
5 file(s) affected in this change:
static/scripts/lrucache.js
static/scripts/packed/lrucache.js
static/scripts/packed/trackster.js
static/scripts/trackster.js
static/trackster.css
diffs (407 lines):
diff -r 695f28311b36 -r b45a5a51c7d1 static/scripts/lrucache.js
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/static/scripts/lrucache.js Thu Oct 01 14:01:53 2009 -0400
@@ -0,0 +1,238 @@
+/*
+MIT LICENSE
+Copyright (c) 2007 Monsur Hossain (http://www.monsur.com)
+
+Permission is hereby granted, free of charge, to any person
+obtaining a copy of this software and associated documentation
+files (the "Software"), to deal in the Software without
+restriction, including without limitation the rights to use,
+copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the
+Software is furnished to do so, subject to the following
+conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+OTHER DEALINGS IN THE SOFTWARE.
+*/
+
+// ****************************************************************************
+// CachePriority ENUM
+// An easier way to refer to the priority of a cache item
+var CachePriority = {
+ Low: 1,
+ Normal: 2,
+ High: 4
+}
+
+// ****************************************************************************
+// Cache constructor
+// Creates a new cache object
+// INPUT: maxSize (optional) - indicates how many items the cache can hold.
+// default is -1, which means no limit on the
+// number of items.
+function Cache(maxSize) {
+ this.items = {};
+ this.count = 0;
+ if (maxSize == null)
+ maxSize = -1;
+ this.maxSize = maxSize;
+ this.fillFactor = .75;
+ this.purgeSize = Math.round(this.maxSize * this.fillFactor);
+
+ this.stats = {}
+ this.stats.hits = 0;
+ this.stats.misses = 0;
+}
+
+// ****************************************************************************
+// Cache.getItem
+// retrieves an item from the cache, returns null if the item doesn't exist
+// or it is expired.
+// INPUT: key - the key to load from the cache
+Cache.prototype.getItem = function(key) {
+
+ // retrieve the item from the cache
+ var item = this.items[key];
+
+ if (item != null) {
+ if (!this._isExpired(item)) {
+ // if the item is not expired
+ // update its last accessed date
+ item.lastAccessed = new Date().getTime();
+ } else {
+ // if the item is expired, remove it from the cache
+ this._removeItem(key);
+ item = null;
+ }
+ }
+
+ // return the item value (if it exists), or null
+ var returnVal = null;
+ if (item != null) {
+ returnVal = item.value;
+ this.stats.hits++;
+ } else {
+ this.stats.misses++;
+ }
+ return returnVal;
+}
+
+// ****************************************************************************
+// Cache.setItem
+// sets an item in the cache
+// parameters: key - the key to refer to the object
+// value - the object to cache
+// options - an optional parameter described below
+// the last parameter accepts an object which controls various caching options:
+// expirationAbsolute: the datetime when the item should expire
+// expirationSliding: an integer representing the seconds since
+// the last cache access after which the item
+// should expire
+// priority: How important it is to leave this item in the cache.
+// You can use the values CachePriority.Low, .Normal, or
+// .High, or you can just use an integer. Note that
+// placing a priority on an item does not guarantee
+// it will remain in cache. It can still be purged if
+// an expiration is hit, or if the cache is full.
+// callback: A function that gets called when the item is purged
+// from cache. The key and value of the removed item
+// are passed as parameters to the callback function.
+Cache.prototype.setItem = function(key, value, options) {
+
+ function CacheItem(k, v, o) {
+ if ((k == null) || (k == ''))
+ throw new Error("key cannot be null or empty");
+ this.key = k;
+ this.value = v;
+ if (o == null)
+ o = {};
+ if (o.expirationAbsolute != null)
+ o.expirationAbsolute = o.expirationAbsolute.getTime();
+ if (o.priority == null)
+ o.priority = CachePriority.Normal;
+ this.options = o;
+ this.lastAccessed = new Date().getTime();
+ }
+
+ // add a new cache item to the cache
+ if (this.items[key] != null)
+ this._removeItem(key);
+ this._addItem(new CacheItem(key, value, options));
+
+ // if the cache is full, purge it
+ if ((this.maxSize > 0) && (this.count > this.maxSize)) {
+ this._purge();
+ }
+}
+
+// ****************************************************************************
+// Cache.clear
+// Remove all items from the cache
+Cache.prototype.clear = function() {
+
+ // loop through each item in the cache and remove it
+ for (var key in this.items) {
+ this._removeItem(key);
+ }
+}
+
+// ****************************************************************************
+// Cache._purge (PRIVATE FUNCTION)
+// remove old elements from the cache
+Cache.prototype._purge = function() {
+
+ var tmparray = new Array();
+
+ // loop through the cache, expire items that should be expired
+ // otherwise, add the item to an array
+ for (var key in this.items) {
+ var item = this.items[key];
+ if (this._isExpired(item)) {
+ this._removeItem(key);
+ } else {
+ tmparray.push(item);
+ }
+ }
+
+ if (tmparray.length > this.purgeSize) {
+
+ // sort this array based on cache priority and the last accessed date
+ tmparray = tmparray.sort(function(a, b) {
+ if (a.options.priority != b.options.priority) {
+ return b.options.priority - a.options.priority;
+ } else {
+ return b.lastAccessed - a.lastAccessed;
+ }
+ });
+
+ // remove items from the end of the array
+ while (tmparray.length > this.purgeSize) {
+ var ritem = tmparray.pop();
+ this._removeItem(ritem.key);
+ }
+ }
+}
+
+// ****************************************************************************
+// Cache._addItem (PRIVATE FUNCTION)
+// add an item to the cache
+Cache.prototype._addItem = function(item) {
+ this.items[item.key] = item;
+ this.count++;
+}
+
+// ****************************************************************************
+// Cache._removeItem (PRIVATE FUNCTION)
+// Remove an item from the cache, call the callback function (if necessary)
+Cache.prototype._removeItem = function(key) {
+ var item = this.items[key];
+ delete this.items[key];
+ this.count--;
+
+ // if there is a callback function, call it at the end of execution
+ if (item.options.callback != null) {
+ var callback = function() {
+ item.options.callback(item.key, item.value);
+ }
+ setTimeout(callback, 0);
+ }
+}
+
+// ****************************************************************************
+// Cache._isExpired (PRIVATE FUNCTION)
+// Returns true if the item should be expired based on its expiration options
+Cache.prototype._isExpired = function(item) {
+ var now = new Date().getTime();
+ var expired = false;
+ if ((item.options.expirationAbsolute) && (item.options.expirationAbsolute < now)) {
+ // if the absolute expiration has passed, expire the item
+ expired = true;
+ }
+ if ((expired == false) && (item.options.expirationSliding)) {
+ // if the sliding expiration has passed, expire the item
+ var lastAccess = item.lastAccessed + (item.options.expirationSliding * 1000);
+ if (lastAccess < now) {
+ expired = true;
+ }
+ }
+ return expired;
+}
+
+Cache.prototype.toHtmlString = function() {
+ var returnStr = this.count + " item(s) in cache<br /><ul>";
+ for (var key in this.items) {
+ var item = this.items[key];
+ returnStr = returnStr + "<li>" + item.key.toString() + " = " + item.value.toString() + "</li>";
+ }
+ returnStr = returnStr + "</ul>";
+ return returnStr;
+}
diff -r 695f28311b36 -r b45a5a51c7d1 static/scripts/packed/lrucache.js
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/static/scripts/packed/lrucache.js Thu Oct 01 14:01:53 2009 -0400
@@ -0,0 +1,1 @@
+var CachePriority={Low:1,Normal:2,High:4};function Cache(a){this.items={};this.count=0;if(a==null){a=-1}this.maxSize=a;this.fillFactor=0.75;this.purgeSize=Math.round(this.maxSize*this.fillFactor);this.stats={};this.stats.hits=0;this.stats.misses=0}Cache.prototype.getItem=function(a){var c=this.items[a];if(c!=null){if(!this._isExpired(c)){c.lastAccessed=new Date().getTime()}else{this._removeItem(a);c=null}}var b=null;if(c!=null){b=c.value;this.stats.hits++}else{this.stats.misses++}return b};Cache.prototype.setItem=function(c,d,b){function a(f,e,g){if((f==null)||(f=="")){throw new Error("key cannot be null or empty")}this.key=f;this.value=e;if(g==null){g={}}if(g.expirationAbsolute!=null){g.expirationAbsolute=g.expirationAbsolute.getTime()}if(g.priority==null){g.priority=CachePriority.Normal}this.options=g;this.lastAccessed=new Date().getTime()}if(this.items[c]!=null){this._removeItem(c)}this._addItem(new a(c,d,b));if((this.maxSize>0)&&(this.count>this.maxSize)){this._purge()}}
;Cache.prototype.clear=function(){for(var a in this.items){this._removeItem(a)}};Cache.prototype._purge=function(){var d=new Array();for(var a in this.items){var b=this.items[a];if(this._isExpired(b)){this._removeItem(a)}else{d.push(b)}}if(d.length>this.purgeSize){d=d.sort(function(f,e){if(f.options.priority!=e.options.priority){return e.options.priority-f.options.priority}else{return e.lastAccessed-f.lastAccessed}});while(d.length>this.purgeSize){var c=d.pop();this._removeItem(c.key)}}};Cache.prototype._addItem=function(a){this.items[a.key]=a;this.count++};Cache.prototype._removeItem=function(a){var b=this.items[a];delete this.items[a];this.count--;if(b.options.callback!=null){var c=function(){b.options.callback(b.key,b.value)};setTimeout(c,0)}};Cache.prototype._isExpired=function(c){var a=new Date().getTime();var b=false;if((c.options.expirationAbsolute)&&(c.options.expirationAbsolute<a)){b=true}if((b==false)&&(c.options.expirationSliding)){var d=c.lastAccessed+(c.options.
expirationSliding*1000);if(d<a){b=true}}return b};Cache.prototype.toHtmlString=function(){var b=this.count+" item(s) in cache<br /><ul>";for(var a in this.items){var c=this.items[a];b=b+"<li>"+c.key.toString()+" = "+c.value.toString()+"</li>"}b=b+"</ul>";return b};
\ No newline at end of file
diff -r 695f28311b36 -r b45a5a51c7d1 static/scripts/packed/trackster.js
--- a/static/scripts/packed/trackster.js Thu Oct 01 13:16:43 2009 -0400
+++ b/static/scripts/packed/trackster.js Thu Oct 01 14:01:53 2009 -0400
@@ -1,1 +1,1 @@
-var DENSITY=1000;var DataCache=function(b,a){this.type=b;this.track=a;this.cache=Object()};$.extend(DataCache.prototype,{get:function(d,b){var c=this.cache;if(!(c[d]&&c[d][b])){if(!c[d]){c[d]=Object()}var a=b*DENSITY*d;var e=(b+1)*DENSITY*d;c[d][b]={state:"loading"};$.getJSON(data_url,{track_type:this.track.track_type,chrom:this.track.view.chrom,low:a,high:e,dataset_id:this.track.dataset_id},function(f){if(f=="pending"){setTimeout(fetcher,5000)}else{c[d][b]={state:"loaded",values:f}}$(document).trigger("redraw")})}return c[d][b]}});var View=function(a,b){this.chrom=a;this.tracks=[];this.max_low=0;this.max_high=b;this.low=this.max_low;this.high=this.max_high;this.length=this.max_high-this.max_low};$.extend(View.prototype,{add_track:function(a){a.view=this;this.tracks.push(a);if(a.init){a.init()}},redraw:function(){$("#overview-box").css({left:(this.low/this.length)*$("#overview-viewport").width(),width:Math.max(4,((this.high-this.low)/this.length)*$("#overview-viewport").widt
h())}).show();$("#low").text(this.low);$("#high").text(this.high);for(var a in this.tracks){this.tracks[a].draw()}$("#bottom-spacer").remove();$("#viewport").append('<div id="bottom-spacer" style="height: 200px;"></div>')},move:function(b,a){this.low=Math.max(this.max_low,Math.floor(b));this.high=Math.min(this.length,Math.ceil(a))},zoom_in:function(d,b){if(this.max_high==0){return}var c=this.high-this.low;var e=c/d/2;if(b==undefined){var a=(this.low+this.high)/2}else{var a=this.low+c*b/$(document).width()}this.low=Math.floor(a-e);this.high=Math.ceil(a+e);if(this.low<this.max_low){this.low=this.max_low;this.high=c/d}else{if(this.high>this.max_high){this.high=this.max_high;this.low=this.max_high-c/d}}if(this.high-this.low<1){this.high=this.low+1}},zoom_out:function(c){if(this.max_high==0){return}var a=(this.low+this.high)/2;var b=this.high-this.low;var d=b*c/2;this.low=Math.floor(Math.max(0,a-d));this.high=Math.ceil(Math.min(this.length,a+d))},left:function(b){var a=this.high-
this.low;var c=Math.floor(a/b);if(this.low-c<0){this.low=0;this.high=this.low+a}else{this.low-=c;this.high-=c}},right:function(b){var a=this.high-this.low;var c=Math.floor(a/b);if(this.high+c>this.length){this.high=this.length;this.low=this.high-a}else{this.low+=c;this.high+=c}}});var Track=function(a,b){this.name=a;this.parent_element=b;this.make_container()};$.extend(Track.prototype,{make_container:function(){this.header_div=$("<div class='track-header'>").text(this.name);this.content_div=$("<div class='track-content'>");this.container_div=$("<div class='track'></div>").append(this.header_div).append(this.content_div);this.parent_element.append(this.container_div)}});var TiledTrack=function(){this.last_resolution=null;this.last_w_scale=null;this.tile_cache={}};$.extend(TiledTrack.prototype,Track.prototype,{draw:function(){var i=this.view.low,c=this.view.high,e=c-i;var b=Math.pow(10,Math.ceil(Math.log(e/DENSITY)/Math.log(10)));b=Math.max(b,1);b=Math.min(b,100000);var n=$("<
div style='position: relative;'></div>");this.content_div.children(":first").remove();this.content_div.append(n);var l=this.content_div.width(),d=this.content_div.height(),o=l/e,k={},m={};if(this.last_resolution==b&&this.last_w_scale==o){k=this.tile_cache}var g;var a=Math.floor(i/b/DENSITY);while((a*1000*b)<c){if(a in k){g=k[a];var f=a*DENSITY*b;g.css({left:(f-this.view.low)*o});n.append(g)}else{g=this.draw_tile(b,a,n,o,d)}if(g){m[a]=g}a+=1}this.last_resolution=b;this.last_w_scale=o;this.tile_cache=m}});var LabelTrack=function(a){Track.call(this,null,a);this.container_div.addClass("label-track")};$.extend(LabelTrack.prototype,Track.prototype,{draw:function(){var c=this.view,d=c.high-c.low,g=Math.floor(Math.pow(10,Math.floor(Math.log(d)/Math.log(10)))),a=Math.floor(c.low/g)*g,e=this.content_div.width(),b=$("<div style='position: relative; height: 1.3em;'></div>");while(a<c.high){var f=(a-c.low)/d*e;b.append($("<div class='label'>"+a+"</div>").css({position:"absolute",left:f-1
}));a+=g}this.content_div.children(":first").remove();this.content_div.append(b)}});var LineTrack=function(c,b,a){Track.call(this,c,$("#viewport"));this.track_type="line";this.height_px=(a?a:100);this.container_div.addClass("line-track");this.content_div.css("height",this.height_px+"px");this.dataset_id=b;this.cache=new DataCache("",this)};$.extend(LineTrack.prototype,TiledTrack.prototype,{init:function(){var a=this;$.getJSON(data_url,{stats:true,track_type:a.track_type,chrom:a.view.chrom,low:null,high:null,dataset_id:a.dataset_id},function(b){if(b){if(b=="error"){a.content_div.addClass("error").text("There was an error in indexing this dataset.")}else{if(b=="no data"){a.content_div.addClass("nodata").text("No data for this chrom/contig.")}else{a.min_value=b.min;a.max_value=b.max;a.vertical_range=a.max_value-a.min_value;a.view.redraw()}}}})},draw_tile:function(d,a,o,s,p){if(!this.vertical_range){return}var k=a*DENSITY*d,r=(a+1)*DENSITY*d,c=DENSITY*d;var n=this.cache.get(d,a)
;var h;if(n.state=="loading"){h=$("<div class='loading tile'></div>")}else{h=$("<canvas class='tile'></canvas>")}h.css({position:"absolute",top:0,left:(k-this.view.low)*s,});o.append(h);if(n.state=="loading"){e=false;return null}var b=h;b.get(0).width=Math.ceil(c*s);b.get(0).height=this.height_px;var q=b.get(0).getContext("2d");var e=false;q.beginPath();var g=n.values;if(!g){return}for(var f=0;f<g.length-1;f++){var m=g[f][0]-k;var l=g[f][1];if(isNaN(l)){e=false}else{m=m*s;y_above_min=l-this.min_value;l=y_above_min/this.vertical_range*this.height_px;if(e){q.lineTo(m,l)}else{q.moveTo(m,l);e=true}}}q.stroke();return h}});var FeatureTrack=function(c,b,a){Track.call(this,c,$("#viewport"));this.track_type="feature";this.height_px=(a?a:100);this.container_div.addClass("feature-track");this.content_div.css("height",this.height_px+"px");this.dataset_id=b;this.zo_slots={};this.show_labels_scale=0.01;this.showing_labels=false};$.extend(FeatureTrack.prototype,TiledTrack.prototype,{calc_
slots:function(e){var a=new Array();var d=this.container_div.width()/(this.view.high-this.view.low);if(e){this.zi_slots=new Object()}var c=$("<canvas></canvas>").get(0).getContext("2d");for(var b in this.values){feature=this.values[b];f_start=Math.floor(Math.max(this.view.max_low,(feature.start-this.view.max_low)*d));if(e){f_start-=c.measureText(feature.name).width}f_end=Math.ceil(Math.min(this.view.max_high,(feature.end-this.view.max_low)*d));j=0;while(true){if(a[j]==undefined||a[j]<f_start){a[j]=f_end;if(e){this.zi_slots[feature.name]=j}else{this.zo_slots[feature.name]=j}break}j++}}},init:function(){var a=this;$.getJSON(data_url,{track_type:a.track_type,low:a.view.max_low,high:a.view.max_high,dataset_id:a.dataset_id,chrom:a.view.chrom},function(b){a.values=b;a.calc_slots();a.slots=a.zo_slots;a.draw()})},draw_tile:function(q,t,e,g,f){if(!this.values){return null}if(g>this.show_labels_scale&&!this.showing_labels){this.showing_labels=true;if(!this.zi_slots){this.calc_slots(tr
ue)}this.slots=this.zi_slots}else{if(g<=this.show_labels_scale&&this.showing_labels){this.showing_labels=false;this.slots=this.zo_slots}}var u=t*DENSITY*q,c=(t+1)*DENSITY*q,b=DENSITY*q;var k=this.view,m=k.high-k.low,o=Math.ceil(b*g),h=new Array(),n=200,l=$("<canvas class='tile'></canvas>");l.css({position:"absolute",top:0,left:(u-this.view.low)*g,});l.get(0).width=o;l.get(0).height=n;var p=l.get(0).getContext("2d");var r=0;for(var s in this.values){feature=this.values[s];if(feature.start<=c&&feature.end>=u){f_start=Math.floor(Math.max(0,(feature.start-u)*g));f_end=Math.ceil(Math.min(o,(feature.end-u)*g));p.fillStyle="#000";p.fillRect(f_start,this.slots[feature.name]*10+5,f_end-f_start,1);if(this.showing_labels&&p.fillText){p.font="10px monospace";p.textAlign="right";p.fillText(feature.name,f_start,this.slots[feature.name]*10+8)}if(feature.exon_start&&feature.exon_end){var d=Math.floor(Math.max(0,(feature.exon_start-u)*g));var w=Math.ceil(Math.min(o,(feature.exon_end-u)*g))}f
or(var s in feature.blocks){block=feature.blocks[s];block_start=Math.floor(Math.max(0,(block[0]-u)*g));block_end=Math.ceil(Math.min(o,(block[1]-u)*g));var a=3,v=4;if(d&&block_start>=d&&block_end<=w){a=5,v=3}p.fillRect(d,this.slots[feature.name]*10+v,block_end-block_start,a)}r++}}e.append(l);return l},});
\ No newline at end of file
+var DENSITY=1000,DATA_ERROR="There was an error in indexing this dataset.",DATA_NONE="No data for this chrom/contig.";var DataCache=function(b,a){this.type=b;this.track=a;this.cache=Object()};$.extend(DataCache.prototype,{get:function(d,b){var c=this.cache;if(!(c[d]&&c[d][b])){if(!c[d]){c[d]=Object()}var a=b*DENSITY*d;var e=(b+1)*DENSITY*d;c[d][b]={state:"loading"};$.getJSON(data_url,{track_type:this.track.track_type,chrom:this.track.view.chrom,low:a,high:e,dataset_id:this.track.dataset_id},function(f){if(f=="pending"){setTimeout(fetcher,5000)}else{c[d][b]={state:"loaded",values:f}}$(document).trigger("redraw")})}return c[d][b]}});var View=function(a,b){this.chrom=a;this.tracks=[];this.max_low=0;this.max_high=b;this.low=this.max_low;this.high=this.max_high;this.length=this.max_high-this.max_low};$.extend(View.prototype,{add_track:function(a){a.view=this;this.tracks.push(a);if(a.init){a.init()}},redraw:function(){$("#overview-box").css({left:(this.low/this.length)*$("#overvie
w-viewport").width(),width:Math.max(4,((this.high-this.low)/this.length)*$("#overview-viewport").width())}).show();$("#low").text(this.low);$("#high").text(this.high);for(var a in this.tracks){this.tracks[a].draw()}$("#bottom-spacer").remove();$("#viewport").append('<div id="bottom-spacer" style="height: 200px;"></div>')},move:function(b,a){this.low=Math.max(this.max_low,Math.floor(b));this.high=Math.min(this.length,Math.ceil(a))},zoom_in:function(d,b){if(this.max_high==0){return}var c=this.high-this.low;var e=c/d/2;if(b==undefined){var a=(this.low+this.high)/2}else{var a=this.low+c*b/$(document).width()}this.low=Math.floor(a-e);this.high=Math.ceil(a+e);if(this.low<this.max_low){this.low=this.max_low;this.high=c/d}else{if(this.high>this.max_high){this.high=this.max_high;this.low=this.max_high-c/d}}if(this.high-this.low<1){this.high=this.low+1}},zoom_out:function(c){if(this.max_high==0){return}var a=(this.low+this.high)/2;var b=this.high-this.low;var d=b*c/2;this.low=Math.flo
or(Math.max(0,a-d));this.high=Math.ceil(Math.min(this.length,a+d))},left:function(b){var a=this.high-this.low;var c=Math.floor(a/b);if(this.low-c<0){this.low=0;this.high=this.low+a}else{this.low-=c;this.high-=c}},right:function(b){var a=this.high-this.low;var c=Math.floor(a/b);if(this.high+c>this.length){this.high=this.length;this.low=this.high-a}else{this.low+=c;this.high+=c}}});var Track=function(a,b){this.name=a;this.parent_element=b;this.make_container()};$.extend(Track.prototype,{make_container:function(){this.header_div=$("<div class='track-header'>").text(this.name);this.content_div=$("<div class='track-content'>");this.container_div=$("<div class='track'></div>").append(this.header_div).append(this.content_div);this.parent_element.append(this.container_div)}});var TiledTrack=function(){this.last_resolution=null;this.last_w_scale=null;this.tile_cache={}};$.extend(TiledTrack.prototype,Track.prototype,{draw:function(){var i=this.view.low,c=this.view.high,e=c-i;var b=Mat
h.pow(10,Math.ceil(Math.log(e/DENSITY)/Math.log(10)));b=Math.max(b,1);b=Math.min(b,100000);var m=$("<div style='position: relative;'></div>");this.content_div.children(":first").remove();this.content_div.append(m);var k=this.content_div.width(),d=this.content_div.height(),n=k/e,j={},l={};if(this.last_resolution==b&&this.last_w_scale==n){j=this.tile_cache}var g;var a=Math.floor(i/b/DENSITY);while((a*1000*b)<c){if(a in j){g=j[a];var f=a*DENSITY*b;g.css({left:(f-this.view.low)*n});m.append(g)}else{g=this.draw_tile(b,a,m,n,d)}if(g){l[a]=g}a+=1}this.last_resolution=b;this.last_w_scale=n;this.tile_cache=l}});var LabelTrack=function(a){Track.call(this,null,a);this.container_div.addClass("label-track")};$.extend(LabelTrack.prototype,Track.prototype,{draw:function(){var c=this.view,d=c.high-c.low,g=Math.floor(Math.pow(10,Math.floor(Math.log(d)/Math.log(10)))),a=Math.floor(c.low/g)*g,e=this.content_div.width(),b=$("<div style='position: relative; height: 1.3em;'></div>");while(a<c.hig
h){var f=(a-c.low)/d*e;b.append($("<div class='label'>"+a+"</div>").css({position:"absolute",left:f-1}));a+=g}this.content_div.children(":first").remove();this.content_div.append(b)}});var LineTrack=function(c,b,a){Track.call(this,c,$("#viewport"));this.track_type="line";this.height_px=(a?a:100);this.container_div.addClass("line-track");this.content_div.css("height",this.height_px+"px");this.dataset_id=b;this.cache=new DataCache("",this)};$.extend(LineTrack.prototype,TiledTrack.prototype,{init:function(){var a=this;$.getJSON(data_url,{stats:true,track_type:a.track_type,chrom:a.view.chrom,low:null,high:null,dataset_id:a.dataset_id},function(b){if(!b||b=="error"){a.content_div.addClass("error").text(DATA_ERROR)}else{if(b=="no data"){a.content_div.addClass("nodata").text(DATA_NONE)}else{a.min_value=b.min;a.max_value=b.max;a.vertical_range=a.max_value-a.min_value;a.view.redraw()}}})},draw_tile:function(d,a,n,r,o){if(!this.vertical_range){return}var j=a*DENSITY*d,q=(a+1)*DENSITY*
d,c=DENSITY*d;var m=this.cache.get(d,a);var h;if(m.state=="loading"){h=$("<div class='loading tile'></div>")}else{h=$("<canvas class='tile'></canvas>")}h.css({position:"absolute",top:0,left:(j-this.view.low)*r,});n.append(h);if(m.state=="loading"){e=false;return null}var b=h;b.get(0).width=Math.ceil(c*r);b.get(0).height=this.height_px;var p=b.get(0).getContext("2d");var e=false;p.beginPath();var g=m.values;if(!g){return}for(var f=0;f<g.length-1;f++){var l=g[f][0]-j;var k=g[f][1];if(isNaN(k)){e=false}else{l=l*r;y_above_min=k-this.min_value;k=y_above_min/this.vertical_range*this.height_px;if(e){p.lineTo(l,k)}else{p.moveTo(l,k);e=true}}}p.stroke();return h}});var FeatureTrack=function(c,b,a){Track.call(this,c,$("#viewport"));this.track_type="feature";this.height_px=(a?a:100);this.container_div.addClass("feature-track");this.content_div.css("height",this.height_px+"px");this.dataset_id=b;this.zo_slots={};this.show_labels_scale=0.01;this.showing_labels=false;this.vertical_gap=10}
;$.extend(FeatureTrack.prototype,TiledTrack.prototype,{calc_slots:function(h){var b=[];var a=this.container_div.width()/(this.view.high-this.view.low);if(h){this.zi_slots={}}var g=$("<canvas></canvas>").get(0).getContext("2d");for(var d in this.values){var k=this.values[d];var e=Math.floor(Math.max(this.view.max_low,(k.start-this.view.max_low)*a));if(h){e-=g.measureText(k.name).width;e-=10}var f=Math.ceil(Math.min(this.view.max_high,(k.end-this.view.max_low)*a));var c=0;while(true){if(b[c]==undefined||b[c]<e){b[c]=f;if(h){this.zi_slots[k.name]=c}else{this.zo_slots[k.name]=c}break}c++}}this.height_px=b.length*this.vertical_gap+15;this.content_div.css("height",this.height_px+"px")},init:function(){var a=this;$.getJSON(data_url,{track_type:a.track_type,low:a.view.max_low,high:a.view.max_high,dataset_id:a.dataset_id,chrom:a.view.chrom},function(b){if(b=="error"){a.content_div.addClass("error").text(DATA_ERROR)}else{if(b.length==0||b=="no data"){a.content_div.addClass("nodata").t
ext(DATA_NONE)}else{a.values=b;a.calc_slots();a.slots=a.zo_slots;a.draw()}}})},draw_tile:function(q,t,e,g,f){if(!this.values){return null}if(g>this.show_labels_scale&&!this.showing_labels){this.showing_labels=true;if(!this.zi_slots){this.calc_slots(true)}this.slots=this.zi_slots}else{if(g<=this.show_labels_scale&&this.showing_labels){this.showing_labels=false;this.slots=this.zo_slots}}var u=t*DENSITY*q,c=(t+1)*DENSITY*q,b=DENSITY*q;var k=this.view,m=k.high-k.low,o=Math.ceil(b*g),h=new Array(),n=this.height_px,l=$("<canvas class='tile'></canvas>");l.css({position:"absolute",top:0,left:(u-this.view.low)*g,});l.get(0).width=o;l.get(0).height=n;var p=l.get(0).getContext("2d");var r=0;for(var s in this.values){feature=this.values[s];if(feature.start<=c&&feature.end>=u){f_start=Math.floor(Math.max(0,(feature.start-u)*g));f_end=Math.ceil(Math.min(o,(feature.end-u)*g));p.fillStyle="#000";p.fillRect(f_start,this.slots[feature.name]*this.vertical_gap+5,f_end-f_start,1);if(this.showing
_labels&&p.fillText){p.font="10px monospace";p.textAlign="right";p.fillText(feature.name,f_start,this.slots[feature.name]*10+8)}if(feature.exon_start&&feature.exon_end){var d=Math.floor(Math.max(0,(feature.exon_start-u)*g));var w=Math.ceil(Math.min(o,(feature.exon_end-u)*g))}for(var s in feature.blocks){block=feature.blocks[s];block_start=Math.floor(Math.max(0,(block[0]-u)*g));block_end=Math.ceil(Math.min(o,(block[1]-u)*g));var a=3,v=4;if(d&&block_start>=d&&block_end<=w){a=5,v=3}p.fillRect(d,this.slots[feature.name]*this.vertical_gap+v,block_end-block_start,a)}r++}}e.append(l);return l},});
\ No newline at end of file
diff -r 695f28311b36 -r b45a5a51c7d1 static/scripts/trackster.js
--- a/static/scripts/trackster.js Thu Oct 01 13:16:43 2009 -0400
+++ b/static/scripts/trackster.js Thu Oct 01 14:01:53 2009 -0400
@@ -2,7 +2,9 @@
2009, James Taylor, Kanwei Li
*/
-var DENSITY = 1000;
+var DENSITY = 1000,
+ DATA_ERROR = "There was an error in indexing this dataset.",
+ DATA_NONE = "No data for this chrom/contig.";
var DataCache = function( type, track ) {
this.type = type;
@@ -239,18 +241,16 @@
var track = this;
$.getJSON( data_url, { stats: true, track_type: track.track_type, chrom: track.view.chrom,
low: null, high: null, dataset_id: track.dataset_id }, function ( data ) {
- if (data) {
- if (data == "error") {
- track.content_div.addClass("error").text("There was an error in indexing this dataset.");
- } else if (data == "no data") {
- // console.log(track.content_div);
- track.content_div.addClass("nodata").text("No data for this chrom/contig.");
- } else {
- track.min_value = data['min'];
- track.max_value = data['max'];
- track.vertical_range = track.max_value - track.min_value;
- track.view.redraw();
- }
+ if (!data || data == "error") {
+ track.content_div.addClass("error").text(DATA_ERROR);
+ } else if (data == "no data") {
+ // console.log(track.content_div);
+ track.content_div.addClass("nodata").text(DATA_NONE);
+ } else {
+ track.min_value = data['min'];
+ track.max_value = data['max'];
+ track.vertical_range = track.max_value - track.min_value;
+ track.view.redraw();
}
});
},
@@ -321,27 +321,28 @@
this.zo_slots = {};
this.show_labels_scale = 0.01;
this.showing_labels = false;
+ this.vertical_gap = 10;
};
$.extend( FeatureTrack.prototype, TiledTrack.prototype, {
-
calc_slots: function( include_labels ) {
// console.log("num vals: " + this.values.length);
- var end_ary = new Array();
+ var end_ary = [];
var scale = this.container_div.width() / (this.view.high - this.view.low);
- // console.log(scale);
- if (include_labels) this.zi_slots = new Object();
+ // console.log(scale, this.view.high, this.view.low);
+ if (include_labels) this.zi_slots = {};
var dummy_canvas = $("<canvas></canvas>").get(0).getContext("2d");
for (var i in this.values) {
- feature = this.values[i];
- f_start = Math.floor( Math.max(this.view.max_low, (feature.start - this.view.max_low) * scale) );
+ var feature = this.values[i];
+ var f_start = Math.floor( Math.max(this.view.max_low, (feature.start - this.view.max_low) * scale) );
if (include_labels) {
f_start -= dummy_canvas.measureText(feature.name).width;
+ f_start -= 10; // Spacing between text and line
}
- f_end = Math.ceil( Math.min(this.view.max_high, (feature.end - this.view.max_low) * scale) );
+ var f_end = Math.ceil( Math.min(this.view.max_high, (feature.end - this.view.max_low) * scale) );
// if (include_labels) { console.log(f_start, f_end); }
- j = 0;
+ var j = 0;
while (true) {
if (end_ary[j] == undefined || end_ary[j] < f_start) {
end_ary[j] = f_end;
@@ -355,17 +356,26 @@
j++;
}
}
+ this.height_px = end_ary.length * this.vertical_gap + 15;
+ this.content_div.css( "height", this.height_px + "px" );
},
init: function() {
var track = this;
$.getJSON( data_url, { track_type: track.track_type, low: track.view.max_low, high: track.view.max_high,
dataset_id: track.dataset_id, chrom: track.view.chrom }, function ( data ) {
- track.values = data;
- track.calc_slots();
- track.slots = track.zo_slots;
- // console.log(track.zo_slots);
- track.draw();
+ if (data == "error") {
+ track.content_div.addClass("error").text(DATA_ERROR);
+ } else if (data.length == 0 || data == "no data") {
+ // console.log(track.content_div);
+ track.content_div.addClass("nodata").text(DATA_NONE);
+ } else {
+ track.values = data;
+ track.calc_slots();
+ track.slots = track.zo_slots;
+ // console.log(track.zo_slots);
+ track.draw();
+ }
});
},
@@ -391,7 +401,7 @@
range = view.high - view.low,
width = Math.ceil( tile_length * w_scale ),
slots = new Array(),
- height = 200,
+ height = this.height_px,
new_canvas = $("<canvas class='tile'></canvas>");
new_canvas.css({
@@ -412,7 +422,7 @@
f_end = Math.ceil( Math.min(width, (feature.end - tile_low) * w_scale) );
// console.log(feature.start, feature.end, f_start, f_end, j);
ctx.fillStyle = "#000";
- ctx.fillRect(f_start, this.slots[feature.name] * 10 + 5, f_end - f_start, 1);
+ ctx.fillRect(f_start, this.slots[feature.name] * this.vertical_gap + 5, f_end - f_start, 1);
if (this.showing_labels && ctx.fillText) {
ctx.font = "10px monospace";
@@ -435,7 +445,7 @@
if (exon_start && block_start >= exon_start && block_end <= exon_end) {
thickness = 5, y_start = 3;
}
- ctx.fillRect(exon_start, this.slots[feature.name] * 10 + y_start, block_end - block_start, thickness);
+ ctx.fillRect(exon_start, this.slots[feature.name] * this.vertical_gap + y_start, block_end - block_start, thickness);
// console.log(block_start, block_end);
}
diff -r 695f28311b36 -r b45a5a51c7d1 static/trackster.css
--- a/static/trackster.css Thu Oct 01 13:16:43 2009 -0400
+++ b/static/trackster.css Thu Oct 01 14:01:53 2009 -0400
@@ -86,7 +86,7 @@
.track-content.error {
text-align: center;
padding-top: 30px;
- background-color: #600;
+ background-color: #ECB4AF;
}
.track-content.nodata {
text-align: center;
details: http://www.bx.psu.edu/hg/galaxy/rev/2364764e3604
changeset: 2811:2364764e3604
user: Dan Blankenberg <dan(a)bx.psu.edu>
date: Thu Oct 01 11:39:55 2009 -0400
description:
Fix spelling of Successfully when running a workflow.
1 file(s) affected in this change:
templates/workflow/run_complete.mako
diffs (19 lines):
diff -r 6eb4f3017fbe -r 2364764e3604 templates/workflow/run_complete.mako
--- a/templates/workflow/run_complete.mako Thu Oct 01 09:09:40 2009 -0400
+++ b/templates/workflow/run_complete.mako Thu Oct 01 11:39:55 2009 -0400
@@ -14,7 +14,7 @@
<body>
<div class="donemessage">
<p>
- Sucesfully ran workflow "${workflow.name}", the following datasets have
+ Successfully ran workflow "${workflow.name}", the following datasets have
been added to the queue.
</p>
@@ -28,4 +28,4 @@
</div>
</body>
-</html>
\ No newline at end of file
+</html>
details: http://www.bx.psu.edu/hg/galaxy/rev/d31ab50dc8e0
changeset: 2812:d31ab50dc8e0
user: Nate Coraor <nate(a)bx.psu.edu>
date: Thu Oct 01 12:35:41 2009 -0400
description:
Add a new option, 'allow_library_path_paste' that adds a new upload page
("Upload files from filesystem paths") to the admin-side library upload pages.
This form contains a textarea that allows Galaxy admins to paste any number of
filesystem paths (files or directories) from which Galaxy will import library
datasets, saving the directory structure (if desired). Since such ability
allows admins access to any file on the Galaxy server which is readable by
Galaxy's system user, this option is disabled by default, and system
administrators should take care in assigning Galaxy administrators when this
feature is enabled. Controls on what files are accessible to this tool based
on ownership or other properties can be added at a later date if there is
sufficient interest for such features.
This commit also includes a checkbox on the "Upload directory of files" page
(as well as the new "Upload files from filesystem paths" page above) that will
prevent Galaxy from copying data to its files directory (by default,
'database/files/'). This is useful for large library datasets that live in
their own managed locations on the filesystem, this will prevent the existence
of duplicate copies of datasets (but means administrators must take care to
manage data - moving or removing the data from its Galaxy-external location
will render these datasets invalid within Galaxy).
One unique feature to be aware of: when using the "Copy data into Galaxy?"
checkbox on the "Upload directory of files" page, any symbolic links
encountered in the chosen import directory will be made absolute and
dereferenced ONCE. This allows administrators to link large datasets to the
import directory, rather than having to make full copies, while being able to
delete such links after importing. Only the first symlink (the one in the
import directory itself) is dereferenced; all others remain. See the following
for an example:
library_import_dir = /galaxy/import
% ls -lR /galaxy/import
/galaxy/import:
total 6
drwxr-xr-x 2 nate nate 512 Oct 1 11:31 link/
/galaxy/import/link:
total 10
lrwxrwxrwx 1 nate nate 71 Oct 1 10:38 1.bed -> ../../../home/nate/galaxy/test-data/1.bed
lrwxrwxrwx 1 nate nate 60 Oct 1 10:38 2.bed -> /home/nate/galaxy/test-data/2.bed
lrwxrwxrwx 1 nate nate 11 Oct 1 10:38 3.bed -> ../../3.bed
lrwxrwxrwx 1 nate nate 35 Oct 1 11:30 4.bed -> ../../galaxy_symlink/test-data/4.bed
lrwxrwxrwx 1 nate nate 41 Oct 1 11:31 5.bed -> /galaxy/galaxy_symlink/test-data/5.bed
% ls -l /galaxy/3.bed
lrwxrwxrwx 1 nate nate 60 Oct 1 10:39 /galaxy/3.bed -> /home/nate/galaxy/test-data/3.bed
% ls -l /galaxy/galaxy_symlink
lrwxrwxrwx 1 nate nate 44 Oct 1 11:30 /galaxy/galaxy_symlink -> /home/nate/galaxy/
In this example,
1.bed is a relative symbolic link to the real 1.bed.
2.bed is an absolute symlink to the real 2.bed.
3.bed is a relative symlink to ../../3.bed, aka /galaxy/3.bed, which itself is
a symlink to the real 3.bed.
4.bed is a relative symlink which follows another symlink
(/galaxy/galaxy_symlink) to the real 4.bed.
5.bed is an absolute symlink in the same fashion as 4.bed
If the 'link' server directory is chosen on the "Upload directory of files"
page, and "Copy data into Galaxy?" is checked "No", the following files will be
referenced by Galaxy:
/home/nate/galaxy/test-data/1.bed
/home/nate/galaxy/test-data/2.bed
/galaxy/3.bed
/galaxy/galaxy_symlink/test-data/4.bed
/galaxy/galaxy_symlink/test-data/5.bed
The Galaxy administrator may now safely delete /galaxy/import/link, but should
take care not to remove the referenced symbolic links (/galaxy/3.bed,
/galaxy/galaxy_symlink).
Not all symbolic links are dereferenced because it is assumed that if an
administrator links to a path in the import directory which itself is (or
contains) links, that is the preferred path for accessing the data.
10 file(s) affected in this change:
lib/galaxy/config.py
lib/galaxy/tools/actions/upload_common.py
lib/galaxy/util/__init__.py
lib/galaxy/web/controllers/library_common.py
templates/admin/library/browse_library.mako
templates/admin/library/upload.mako
templates/library/browse_library.mako
templates/library/library_dataset_common.mako
tools/data_source/upload.py
universe_wsgi.ini.sample
diffs (377 lines):
diff -r 2364764e3604 -r d31ab50dc8e0 lib/galaxy/config.py
--- a/lib/galaxy/config.py Thu Oct 01 11:39:55 2009 -0400
+++ b/lib/galaxy/config.py Thu Oct 01 12:35:41 2009 -0400
@@ -87,6 +87,7 @@
self.user_library_import_dir = kwargs.get( 'user_library_import_dir', None )
if self.user_library_import_dir is not None and not os.path.exists( self.user_library_import_dir ):
raise ConfigurationError( "user_library_import_dir specified in config (%s) does not exist" % self.user_library_import_dir )
+ self.allow_library_path_paste = kwargs.get( 'allow_library_path_paste', False )
# Configuration options for taking advantage of nginx features
self.nginx_x_accel_redirect_base = kwargs.get( 'nginx_x_accel_redirect_base', False )
self.nginx_upload_store = kwargs.get( 'nginx_upload_store', False )
diff -r 2364764e3604 -r d31ab50dc8e0 lib/galaxy/tools/actions/upload_common.py
--- a/lib/galaxy/tools/actions/upload_common.py Thu Oct 01 11:39:55 2009 -0400
+++ b/lib/galaxy/tools/actions/upload_common.py Thu Oct 01 12:35:41 2009 -0400
@@ -3,6 +3,7 @@
from galaxy import datatypes, util
from galaxy.datatypes import sniff
from galaxy.util.json import to_json_string
+from galaxy.model.orm import eagerload_all
import logging
log = logging.getLogger( __name__ )
@@ -127,12 +128,29 @@
or trans.user.email in trans.app.config.get( "admin_users", "" ).split( "," ) ):
# This doesn't have to be pretty - the only time this should happen is if someone's being malicious.
raise Exception( "User is not authorized to add datasets to this library." )
+ folder = library_bunch.folder
+ if uploaded_dataset.get( 'in_folder', False ):
+ # Create subfolders if desired
+ for name in uploaded_dataset.in_folder.split( os.path.sep ):
+ folder.refresh()
+ matches = filter( lambda x: x.name == name, active_folders( trans, folder ) )
+ if matches:
+ log.debug( 'DEBUGDEBUG: In %s, found a folder name match: %s:%s' % ( folder.name, matches[0].id, matches[0].name ) )
+ folder = matches[0]
+ else:
+ new_folder = trans.app.model.LibraryFolder( name=name, description='Automatically created by upload tool' )
+ new_folder.genome_build = util.dbnames.default_value
+ folder.add_folder( new_folder )
+ new_folder.flush()
+ trans.app.security_agent.copy_library_permissions( folder, new_folder )
+ log.debug( 'DEBUGDEBUG: In %s, created a new folder: %s:%s' % ( folder.name, new_folder.id, new_folder.name ) )
+ folder = new_folder
if library_bunch.replace_dataset:
ld = library_bunch.replace_dataset
else:
- ld = trans.app.model.LibraryDataset( folder=library_bunch.folder, name=uploaded_dataset.name )
+ ld = trans.app.model.LibraryDataset( folder=folder, name=uploaded_dataset.name )
ld.flush()
- trans.app.security_agent.copy_library_permissions( library_bunch.folder, ld )
+ trans.app.security_agent.copy_library_permissions( folder, ld )
ldda = trans.app.model.LibraryDatasetDatasetAssociation( name = uploaded_dataset.name,
extension = uploaded_dataset.file_type,
dbkey = uploaded_dataset.dbkey,
@@ -153,8 +171,8 @@
else:
# Copy the current user's DefaultUserPermissions to the new LibraryDatasetDatasetAssociation.dataset
trans.app.security_agent.set_all_dataset_permissions( ldda.dataset, trans.app.security_agent.user_get_default_permissions( trans.user ) )
- library_bunch.folder.add_library_dataset( ld, genome_build=uploaded_dataset.dbkey )
- library_bunch.folder.flush()
+ folder.add_library_dataset( ld, genome_build=uploaded_dataset.dbkey )
+ folder.flush()
ld.library_dataset_dataset_association_id = ldda.id
ld.flush()
# Handle template included in the upload form, if any
@@ -230,6 +248,10 @@
is_binary = uploaded_dataset.datatype.is_binary
except:
is_binary = None
+ try:
+ link_data_only = uploaded_dataset.link_data_only
+ except:
+ link_data_only = False
json = dict( file_type = uploaded_dataset.file_type,
ext = uploaded_dataset.ext,
name = uploaded_dataset.name,
@@ -237,6 +259,7 @@
dbkey = uploaded_dataset.dbkey,
type = uploaded_dataset.type,
is_binary = is_binary,
+ link_data_only = link_data_only,
space_to_tab = uploaded_dataset.space_to_tab,
path = uploaded_dataset.path )
json_file.write( to_json_string( json ) + '\n' )
@@ -276,3 +299,13 @@
trans.app.job_queue.put( job.id, tool )
trans.log_event( "Added job to the job queue, id: %s" % str(job.id), tool_id=job.tool_id )
return dict( [ ( 'output%i' % i, v ) for i, v in enumerate( data_list ) ] )
+
+def active_folders( trans, folder ):
+ # Stolen from galaxy.web.controllers.library_common (importing from which causes a circular issues).
+ # Much faster way of retrieving all active sub-folders within a given folder than the
+ # performance of the mapper. This query also eagerloads the permissions on each folder.
+ return trans.sa_session.query( trans.app.model.LibraryFolder ) \
+ .filter_by( parent=folder, deleted=False ) \
+ .options( eagerload_all( "actions" ) ) \
+ .order_by( trans.app.model.LibraryFolder.table.c.name ) \
+ .all()
diff -r 2364764e3604 -r d31ab50dc8e0 lib/galaxy/util/__init__.py
--- a/lib/galaxy/util/__init__.py Thu Oct 01 11:39:55 2009 -0400
+++ b/lib/galaxy/util/__init__.py Thu Oct 01 12:35:41 2009 -0400
@@ -178,7 +178,7 @@
# better solution I think is to more responsibility for
# sanitizing into the tool parameters themselves so that
# different parameters can be sanitized in different ways.
- NEVER_SANITIZE = ['file_data', 'url_paste', 'URL']
+ NEVER_SANITIZE = ['file_data', 'url_paste', 'URL', 'filesystem_paths']
def __init__( self, params, safe=True, sanitize=True, tool=None ):
if safe:
diff -r 2364764e3604 -r d31ab50dc8e0 lib/galaxy/web/controllers/library_common.py
--- a/lib/galaxy/web/controllers/library_common.py Thu Oct 01 11:39:55 2009 -0400
+++ b/lib/galaxy/web/controllers/library_common.py Thu Oct 01 12:35:41 2009 -0400
@@ -81,7 +81,9 @@
tool_params = upload_common.persist_uploads( tool_params )
uploaded_datasets = upload_common.get_uploaded_datasets( trans, tool_params, precreated_datasets, dataset_upload_inputs, library_bunch=library_bunch )
elif upload_option == 'upload_directory':
- uploaded_datasets = self.get_server_dir_uploaded_datasets( trans, params, full_dir, import_dir_desc, library_bunch, err_redirect, msg )
+ uploaded_datasets, err_redirect, msg = self.get_server_dir_uploaded_datasets( trans, params, full_dir, import_dir_desc, library_bunch, err_redirect, msg )
+ elif upload_option == 'upload_paths':
+ uploaded_datasets, err_redirect, msg = self.get_path_paste_uploaded_datasets( trans, params, library_bunch, err_redirect, msg )
upload_common.cleanup_unused_precreated_datasets( precreated_datasets )
if upload_option == 'upload_file' and not uploaded_datasets:
msg = 'Select a file, enter a URL or enter text'
@@ -98,37 +100,86 @@
json_file_path = upload_common.create_paramfile( uploaded_datasets )
data_list = [ ud.data for ud in uploaded_datasets ]
return upload_common.create_job( trans, tool_params, tool, json_file_path, data_list, folder=library_bunch.folder )
+ def make_library_uploaded_dataset( self, trans, params, name, path, type, library_bunch, in_folder=None ):
+ library_bunch.replace_dataset = None # not valid for these types of upload
+ uploaded_dataset = util.bunch.Bunch()
+ uploaded_dataset.name = name
+ uploaded_dataset.path = path
+ uploaded_dataset.type = type
+ uploaded_dataset.ext = None
+ uploaded_dataset.file_type = params.file_type
+ uploaded_dataset.dbkey = params.dbkey
+ uploaded_dataset.space_to_tab = params.space_to_tab
+ if in_folder:
+ uploaded_dataset.in_folder = in_folder
+ uploaded_dataset.data = upload_common.new_upload( trans, uploaded_dataset, library_bunch )
+ if params.get( 'link_data_only', False ):
+ uploaded_dataset.link_data_only = True
+ uploaded_dataset.data.file_name = os.path.abspath( path )
+ uploaded_dataset.data.flush()
+ return uploaded_dataset
def get_server_dir_uploaded_datasets( self, trans, params, full_dir, import_dir_desc, library_bunch, err_redirect, msg ):
files = []
try:
for entry in os.listdir( full_dir ):
# Only import regular files
- if os.path.isfile( os.path.join( full_dir, entry ) ):
- files.append( entry )
+ path = os.path.join( full_dir, entry )
+ if os.path.islink( path ) and os.path.isfile( path ) and params.get( 'link_data_only', False ):
+ # If we're linking instead of copying, link the file the link points to, not the link itself.
+ link_path = os.readlink( path )
+ if os.path.isabs( link_path ):
+ path = link_path
+ else:
+ path = os.path.abspath( os.path.join( os.path.dirname( path ), link_path ) )
+ if os.path.isfile( path ):
+ files.append( path )
except Exception, e:
msg = "Unable to get file list for configured %s, error: %s" % ( import_dir_desc, str( e ) )
err_redirect = True
- return None
+ return None, err_redirect, msg
if not files:
msg = "The directory '%s' contains no valid files" % full_dir
err_redirect = True
- return None
+ return None, err_redirect, msg
uploaded_datasets = []
for file in files:
- library_bunch.replace_dataset = None
- uploaded_dataset = util.bunch.Bunch()
- uploaded_dataset.path = os.path.join( full_dir, file )
- if not os.path.isfile( uploaded_dataset.path ):
+ name = os.path.basename( file )
+ uploaded_datasets.append( self.make_library_uploaded_dataset( trans, params, name, file, 'server_dir', library_bunch ) )
+ return uploaded_datasets, None, None
+ def get_path_paste_uploaded_datasets( self, trans, params, library_bunch, err_redirect, msg ):
+ if params.get( 'filesystem_paths', '' ) == '':
+ msg = "No paths entered in the upload form"
+ err_redirect = True
+ return None, err_redirect, msg
+ preserve_dirs = True
+ if params.get( 'dont_preserve_dirs', False ):
+ preserve_dirs = False
+ # locate files
+ bad_paths = []
+ uploaded_datasets = []
+ for line in [ l.strip() for l in params.filesystem_paths.splitlines() if l.strip() ]:
+ path = os.path.abspath( line )
+ if not os.path.exists( path ):
+ bad_paths.append( path )
continue
- uploaded_dataset.type = 'server_dir'
- uploaded_dataset.name = file
- uploaded_dataset.ext = None
- uploaded_dataset.file_type = params.file_type
- uploaded_dataset.dbkey = params.dbkey
- uploaded_dataset.space_to_tab = params.space_to_tab
- uploaded_dataset.data = upload_common.new_upload( trans, uploaded_dataset, library_bunch )
- uploaded_datasets.append( uploaded_dataset )
- return uploaded_datasets
+ # don't bother processing if we're just going to return an error
+ if not bad_paths:
+ if os.path.isfile( path ):
+ name = os.path.basename( path )
+ uploaded_datasets.append( self.make_library_uploaded_dataset( trans, params, name, path, 'path_paste', library_bunch ) )
+ for basedir, dirs, files in os.walk( line ):
+ for file in files:
+ file_path = os.path.abspath( os.path.join( basedir, file ) )
+ if preserve_dirs:
+ in_folder = os.path.dirname( file_path.replace( path, '', 1 ).lstrip( '/' ) )
+ else:
+ in_folder = None
+ uploaded_datasets.append( self.make_library_uploaded_dataset( trans, params, file, file_path, 'path_paste', library_bunch, in_folder ) )
+ if bad_paths:
+ msg = "Invalid paths:<br><ul><li>%s</li></ul>" % "</li><li>".join( bad_paths )
+ err_redirect = True
+ return None, err_redirect, msg
+ return uploaded_datasets, None, None
@web.expose
def info_template( self, trans, cntrller, library_id, response_action='library', obj_id=None, folder_id=None, ldda_id=None, **kwd ):
# Only adding a new templAte to a library or folder is currently allowed. Editing an existing template is
diff -r 2364764e3604 -r d31ab50dc8e0 templates/admin/library/browse_library.mako
--- a/templates/admin/library/browse_library.mako Thu Oct 01 11:39:55 2009 -0400
+++ b/templates/admin/library/browse_library.mako Thu Oct 01 12:35:41 2009 -0400
@@ -73,7 +73,7 @@
// Make ajax call
$.ajax( {
type: "POST",
- url: "${h.url_for( controller='library_dataset', action='library_item_updates' )}",
+ url: "${h.url_for( controller='library_common', action='library_item_updates' )}",
dataType: "json",
data: { ids: ids.join( "," ), states: states.join( "," ) },
success : function ( data ) {
diff -r 2364764e3604 -r d31ab50dc8e0 templates/admin/library/upload.mako
--- a/templates/admin/library/upload.mako Thu Oct 01 11:39:55 2009 -0400
+++ b/templates/admin/library/upload.mako Thu Oct 01 12:35:41 2009 -0400
@@ -18,6 +18,9 @@
%if trans.app.config.library_import_dir and os.path.exists( trans.app.config.library_import_dir ):
<a class="action-button" href="${h.url_for( controller='library_admin', action='upload_library_dataset', library_id=library_id, folder_id=folder_id, replace_id=replace_id, upload_option='upload_directory' )}">Upload directory of files</a>
%endif
+ %if trans.app.config.allow_library_path_paste:
+ <a class="action-button" href="${h.url_for( controller='library_admin', action='upload_library_dataset', library_id=library_id, folder_id=folder_id, replace_id=replace_id, upload_option='upload_paths' )}">Upload files from filesystem paths</a>
+ %endif
<a class="action-button" href="${h.url_for( controller='library_admin', action='upload_library_dataset', library_id=library_id, folder_id=folder_id, replace_id=replace_id, upload_option='import_from_history' )}">Import datasets from your current history</a>
</div>
<br/><br/>
diff -r 2364764e3604 -r d31ab50dc8e0 templates/library/browse_library.mako
--- a/templates/library/browse_library.mako Thu Oct 01 11:39:55 2009 -0400
+++ b/templates/library/browse_library.mako Thu Oct 01 12:35:41 2009 -0400
@@ -105,7 +105,7 @@
// Make ajax call
$.ajax( {
type: "POST",
- url: "${h.url_for( controller='library_dataset', action='library_item_updates' )}",
+ url: "${h.url_for( controller='library_common', action='library_item_updates' )}",
dataType: "json",
data: { ids: ids.join( "," ), states: states.join( "," ) },
success : function ( data ) {
diff -r 2364764e3604 -r d31ab50dc8e0 templates/library/library_dataset_common.mako
--- a/templates/library/library_dataset_common.mako Thu Oct 01 11:39:55 2009 -0400
+++ b/templates/library/library_dataset_common.mako Thu Oct 01 12:35:41 2009 -0400
@@ -1,11 +1,13 @@
<%def name="render_upload_form( controller, upload_option, action, library_id, folder_id, replace_dataset, file_formats, dbkeys, roles, history )">
<% import os, os.path %>
- %if upload_option in [ 'upload_file', 'upload_directory' ]:
+ %if upload_option in [ 'upload_file', 'upload_directory', 'upload_paths' ]:
<div class="toolForm" id="upload_library_dataset">
- %if upload_option == 'upload_file':
+ %if upload_option == 'upload_directory':
+ <div class="toolFormTitle">Upload a directory of files</div>
+ %elif upload_option == 'upload_paths':
+ <div class="toolFormTitle">Upload files from filesystem paths</div>
+ %else:
<div class="toolFormTitle">Upload files</div>
- %else:
- <div class="toolFormTitle">Upload a directory of files</div>
%endif
<div class="toolFormBody">
<form name="upload_library_dataset" action="${action}" enctype="multipart/form-data" method="post">
@@ -103,6 +105,44 @@
%endif
</div>
<div style="clear: both"></div>
+ </div>
+ %elif upload_option == 'upload_paths':
+ <div class="form-row">
+ <label>Paths to upload</label>
+ <div class="form-row-input">
+ <textarea name="filesystem_paths" rows="10" cols="35"></textarea>
+ </div>
+ <div class="toolParamHelp" style="clear: both;">
+ Upload all files pasted in the box. The (recursive) contents of any pasted directories will be added as well.
+ </div>
+ </div>
+ <div class="form-row">
+ <label>Preserve directory structure?</label>
+ <div class="form-row-input">
+ <input type="checkbox" name="dont_preserve_dirs" value="No"/>No
+ </div>
+ <div class="toolParamHelp" style="clear: both;">
+ If checked, all files in subdirectories on the filesystem will be placed at the top level of the folder, instead of into subfolders.
+ </div>
+ </div>
+ %endif
+ %if upload_option in ( 'upload_directory', 'upload_paths' ):
+ <div class="form-row">
+ <label>Copy data into Galaxy?</label>
+ <div class="form-row-input">
+ <input type="checkbox" name="link_data_only" value="No"/>No
+ </div>
+ <div class="toolParamHelp" style="clear: both;">
+ Normally data uploaded with this tool is copied into Galaxy's "files" directory
+ so any later changes to the data will not affect Galaxy. However, this may not
+ be desired (especially for large NGS datasets), so use of this option will
+ force Galaxy to always read the data from its original path.
+ %if upload_option == 'upload_directory':
+ Any symlinks encountered in the upload directory will be dereferenced once -
+ that is, Galaxy will point directly to the file that is linked, but no other
+ symlinks further down the line will be dereferenced.
+ %endif
+ </div>
</div>
%endif
<div class="form-row">
diff -r 2364764e3604 -r d31ab50dc8e0 tools/data_source/upload.py
--- a/tools/data_source/upload.py Thu Oct 01 11:39:55 2009 -0400
+++ b/tools/data_source/upload.py Thu Oct 01 12:35:41 2009 -0400
@@ -238,7 +238,9 @@
if ext == 'auto':
ext = 'data'
# Move the dataset to its "real" path
- if dataset.type == 'server_dir':
+ if dataset.get( 'link_data_only', False ):
+ pass # data will remain in place
+ elif dataset.type in ( 'server_dir', 'path_paste' ):
shutil.copy( dataset.path, output_path )
else:
shutil.move( dataset.path, output_path )
diff -r 2364764e3604 -r d31ab50dc8e0 universe_wsgi.ini.sample
--- a/universe_wsgi.ini.sample Thu Oct 01 11:39:55 2009 -0400
+++ b/universe_wsgi.ini.sample Thu Oct 01 12:35:41 2009 -0400
@@ -60,13 +60,22 @@
# Galaxy session security
id_secret = changethisinproductiontoo
-# Directories of files contained in the following directory can be uploaded to a library from the Admin view
+# Directories of files contained in the following directory can be uploaded to
+# a library from the Admin view
#library_import_dir = /var/opt/galaxy/import
-# The following can be configured to allow non-admin users to upload a directory of files. The
-# configured directory must contain sub-directories named the same as the non-admin user's Galaxy
-# login ( email ). The non-admin user is restricted to uploading files or sub-directories of files
-# contained in their directory.
-# user_library_import_dir = /var/opt/galaxy/import/users
+
+# The following can be configured to allow non-admin users to upload a
+# directory of files. The configured directory must contain sub-directories
+# named the same as the non-admin user's Galaxy login ( email ). The non-admin
+# user is restricted to uploading files or sub-directories of files contained
+# in their directory.
+#user_library_import_dir = /var/opt/galaxy/import/users
+
+# The admin library upload tool may contain a box allowing admins to paste
+# filesystem paths to files and directories to add to a library. Set to True
+# to enable. Please note the security implication that this will give Galaxy
+# Admins access to anything your Galaxy user has access to.
+#allow_library_path_paste = False
# path to sendmail
sendmail_path = /usr/sbin/sendmail
details: http://www.bx.psu.edu/hg/galaxy/rev/9cc7a17e0ac7
changeset: 2808:9cc7a17e0ac7
user: Greg Von Kuster <greg(a)bx.psu.edu>
date: Wed Sep 30 14:47:17 2009 -0400
description:
If you populate a history before logging in, then login, your current history will be the one you populated before logging in - fixes ticket number 163. Also added additional functional tests to cover scenario.
2 file(s) affected in this change:
lib/galaxy/web/framework/__init__.py
test/functional/test_history_functions.py
diffs (65 lines):
diff -r aafecc059ba6 -r 9cc7a17e0ac7 lib/galaxy/web/framework/__init__.py
--- a/lib/galaxy/web/framework/__init__.py Wed Sep 30 14:11:01 2009 -0400
+++ b/lib/galaxy/web/framework/__init__.py Wed Sep 30 14:47:17 2009 -0400
@@ -404,18 +404,17 @@
except:
users_last_session = None
last_accessed = False
- if users_last_session and users_last_session.current_history:
+ if prev_galaxy_session.current_history and prev_galaxy_session.current_history.datasets:
+ if prev_galaxy_session.current_history.user is None or prev_galaxy_session.current_history.user == user:
+ # If the previous galaxy session had a history, associate it with the new
+ # session, but only if it didn't belong to a different user.
+ history = prev_galaxy_session.current_history
+ elif self.galaxy_session.current_history:
+ history = self.galaxy_session.current_history
+ if not history and users_last_session and users_last_session.current_history:
history = users_last_session.current_history
- if not history:
- if prev_galaxy_session.current_history:
- if prev_galaxy_session.current_history.user is None or prev_galaxy_session.current_history.user == user:
- # If the previous galaxy session had a history, associate it with the new
- # session, but only if it didn't belong to a different user.
- history = prev_galaxy_session.current_history
- elif self.galaxy_session.current_history:
- history = self.galaxy_session.current_history
- else:
- history = self.get_history( create=True )
+ elif not history:
+ history = self.get_history( create=True )
if history not in self.galaxy_session.histories:
self.galaxy_session.add_history( history )
if history.user is None:
diff -r aafecc059ba6 -r 9cc7a17e0ac7 test/functional/test_history_functions.py
--- a/test/functional/test_history_functions.py Wed Sep 30 14:11:01 2009 -0400
+++ b/test/functional/test_history_functions.py Wed Sep 30 14:47:17 2009 -0400
@@ -9,11 +9,28 @@
"""Testing history behavior between logout and login"""
self.logout()
self.history_options()
- # Make sure we have created the following 4 accounts
+ # Create a new, empty history named anonymous
+ name = 'anonymous'
+ self.new_history( name=name )
+ global anonymous_history
+ anonymous_history = galaxy.model.History \
+ .filter( and_( galaxy.model.History.table.c.deleted==False,
+ galaxy.model.History.table.c.name==name ) ) \
+ .order_by( desc( galaxy.model.History.table.c.create_time ) ) \
+ .first()
+ assert anonymous_history is not None, "Problem retrieving anonymous_history from database"
+ # Upload a dataset to anonymous_history so it will be set as the current history after login
+ self.upload_file( '1.bed', dbkey='hg18' )
self.login( email='test1(a)bx.psu.edu' )
global regular_user1
regular_user1 = galaxy.model.User.filter( galaxy.model.User.table.c.email=='test1(a)bx.psu.edu' ).first()
assert regular_user1 is not None, 'Problem retrieving user with email "test1(a)bx.psu.edu" from the database'
+ # Current history should be anonymous_history
+ self.check_history_for_string( name )
+ self.logout()
+ # Login as the same user again to ensure anonymous_history is still the current history
+ self.login( email=regular_user1.email )
+ self.check_history_for_string( name )
self.logout()
self.login( email='test2(a)bx.psu.edu' )
global regular_user2