CasperJS File Download Times Out After 30 Seconds - phantomjs

I'm using CasperJS to download a 15 MB file. In the browser, the download takes about 3 minutes to complete. With Casper, the .download function for the same url returns after exactly 30 seconds, and the file written to disk is 0 bytes. I've tried setting longer timeouts like this:
var casper = require("casper").create({
pageSettings: {
webSecurityEnabled: false
},
waitTimeout: 500000,
stepTimeout: 500000
});
But they have no effect. Here's my download function:
casper.on('resource.received', function (resource) {
var url, file;
if ((resource.url.indexOf("myDownloadUniqueString=") !== -1) ) {
this.echo(resource.url); // the echo'ed url can be downloaded in a web browser
url = resource.url;
file = "downloaded_file.wav"; // this will be 0 bytes
try {
var fs = require("fs"); // phantom js file system (not node)
casper.download(resource.url, file);
} catch (e) {
this.echo(e); // no error is thrown
}
}
});
Any ideas? Perhaps an issue with the PhantomJS fs methods, but that documentation is incomplete...

I solved this problem (for an Excel .xls binary download taking >30s, 6Mb approx) by running an async XMLHTTPrequest (xhr) manually within an evaluate function, then writing the result to a global (window) variable, and waiting for this global to be set.
On the xhr object, you can set a custom timeout, 15 * 60 * 1000 = 15 mins in the example below.
Care needs to be taken with encoding binary downloads as ascii / base64, and then decoding them to write a binary file. This could be adapted / simplified for text downloads.
var fs = require('fs');
var casper = require('casper').create({
//options here
});
var xhr = this.evaluate(function(url){
var xhr = new XMLHttpRequest();
xhr.timeout = 15 * 60 * 1000;
xhr.overrideMimeType("text/plain; charset=x-user-defined");
xhr.open("GET", url); // synchronous request banned, so use waitfor to wait on a global variable
xhr.onreadystatechange = function () {
if (xhr.readyState == 4) {
//if (xhr.status == 200) {
window.xhrstatus = xhr.status; //write to a global (window) variable
window.xhrResponseText = __utils__.encode(xhr.responseText); //base64 encode using casper functions (btoa fails)
//}
}
};
xhr.send(null);
return true;
},'http://example.com/download.xls');
casper.waitFor(function() {
return this.getGlobal('xhrstatus') != undefined;
}, function() {
this.echo('XHR status: ' + this.getGlobal('xhrstatus'));
this.echo('Saving report...');
//http://phantomjs.org/api/fs/method/is-writable.html to check if file writable first
//decode using casper clientutil function and then write binary file
fs.write('saveFileName.xls', decode(this.getGlobal('xhrResponseText')), 'wb');
},null,15*60*1000);
The encode / decode functions from casper.js clientutils library look like this. These seem to work where Javascript's atob() and btoa() don't.
/*
* encode / decode function from casper.js clientutils
* https://github.com/casperjs/casperjs/blob/master/modules/clientutils.js
* Included here for reference - you could just reference the file in your code
*/
var BASE64_ENCODE_CHARS = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
var BASE64_DECODE_CHARS = [
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 62, -1, -1, -1, 63,
52, 53, 54, 55, 56, 57, 58, 59, 60, 61, -1, -1, -1, -1, -1, -1,
-1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14,
15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, -1, -1, -1, -1, -1,
-1, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40,
41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, -1, -1, -1, -1, -1
];
/**
* Decodes a base64 encoded string. Succeeds where window.atob() fails.
*
* #param String str The base64 encoded contents
* #return string
*/
var decode = function decode(str) {
/*eslint max-statements:0, complexity:0 */
var c1, c2, c3, c4, i = 0, len = str.length, out = "";
while (i < len) {
do {
c1 = BASE64_DECODE_CHARS[str.charCodeAt(i++) & 0xff];
} while (i < len && c1 === -1);
if (c1 === -1) {
break;
}
do {
c2 = BASE64_DECODE_CHARS[str.charCodeAt(i++) & 0xff];
} while (i < len && c2 === -1);
if (c2 === -1) {
break;
}
out += String.fromCharCode(c1 << 2 | (c2 & 0x30) >> 4);
do {
c3 = str.charCodeAt(i++) & 0xff;
if (c3 === 61) {
return out;
}
c3 = BASE64_DECODE_CHARS[c3];
} while (i < len && c3 === -1);
if (c3 === -1) {
break;
}
out += String.fromCharCode((c2 & 0XF) << 4 | (c3 & 0x3C) >> 2);
do {
c4 = str.charCodeAt(i++) & 0xff;
if (c4 === 61) {
return out;
}
c4 = BASE64_DECODE_CHARS[c4];
} while (i < len && c4 === -1);
if (c4 === -1) {
break;
}
out += String.fromCharCode((c3 & 0x03) << 6 | c4);
}
return out;
};
/**
* Base64 encodes a string, even binary ones. Succeeds where
* window.btoa() fails.
*
* #param String str The string content to encode
* #return string
*/
var encode = function encode(str) {
/*eslint max-statements:0 */
var out = "", i = 0, len = str.length, c1, c2, c3;
while (i < len) {
c1 = str.charCodeAt(i++) & 0xff;
if (i === len) {
out += BASE64_ENCODE_CHARS.charAt(c1 >> 2);
out += BASE64_ENCODE_CHARS.charAt((c1 & 0x3) << 4);
out += "==";
break;
}
c2 = str.charCodeAt(i++);
if (i === len) {
out += BASE64_ENCODE_CHARS.charAt(c1 >> 2);
out += BASE64_ENCODE_CHARS.charAt((c1 & 0x3) << 4 | (c2 & 0xF0) >> 4);
out += BASE64_ENCODE_CHARS.charAt((c2 & 0xF) << 2);
out += "=";
break;
}
c3 = str.charCodeAt(i++);
out += BASE64_ENCODE_CHARS.charAt(c1 >> 2);
out += BASE64_ENCODE_CHARS.charAt((c1 & 0x3) << 4 | (c2 & 0xF0) >> 4);
out += BASE64_ENCODE_CHARS.charAt((c2 & 0xF) << 2 | (c3 & 0xC0) >> 6);
out += BASE64_ENCODE_CHARS.charAt(c3 & 0x3F);
}
return out;
};

How about adding resourceTimeout:
pageSettings: {
webSecurityEnabled: false,
resourceTimeout: 240000 //240s
},
This answer says it was added in PhantomJS 1.9, but is not documented yet.

To fix this you can casperjs-better-download
casper.on('resource.received', function (resource) {
var url, file;
if ((resource.url.indexOf("myDownloadUniqueString=") !== -1) ) {
this.echo(resource.url); // the echo'ed url can be downloaded in a web browser
url = resource.url;
file = "downloaded_file.wav"; // this will be 0 bytes
try {
var fs = require("fs"); // phantom js file system (not node)
//casper.download(resource.url, file);
var fiveMins = 60000 * 5;
betterDownload({
casper: casper,
url: final_url,
targetFilepath: '/usr/share/nginx/downloaded_file.wav',
waitTimeout: fiveMins
});
} catch (e) {
this.echo(e); // no error is thrown
}
}
});

Related

For loop inside a Redis queue does not work as expected in NestJs

I have the following code, basically it is doing a bruteforce on a key of a hash in the first 4 bytes. Then I loop through each of these positions to test all possible combinations. If I put this code inside the service it runs normally and takes about 200 seconds to find all possible combinations of the first 4 bytes.
But when you're doing long operations, it's good to have a queue to do this in the background. That's what I did, I added it to a nestjs queue with redis but inside the queue when I use for it seems that they are shuffled and they all end together in less than 5 seconds, besides that I don't find the correct combination. And if I use a forEach it either doesn't work or the program crashes during execution.
Basically the first 4 bytes are [192, 156, 127, 0, ...] but it doesn't find it when it's inside a queue. How can I make the background queue run normally as if it were in the service?
import * as crypto from 'crypto';
const DIFFICULTY = {
legendary: {
maxValues: [255, 255, 255, 1],
},
hard: {
maxValues: [255, 255, 255, 0],
},
medium: {
maxValues: [255, 255, 127, 0],
},
easy: {
maxValues: [255, 255, 64, 0],
},
};
#Processor('decrypt-queue')
export class DecryptConsumer {
constructor(
#InjectRepository(Chest) private readonly repository: Repository<Chest>,
) {}
#Process('decrypt-job')
async decryptJob(job: Job) {
const code = job.data;
await job.progress(50);
const chest = await this.repository.findOne({
where: {
code: code,
},
});
await this.repository.save({
...chest,
status: ChestStatus.Oppening,
});
const iv = Buffer.from([1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]);
const original = chest.key;
const range = ({
from = 0,
to,
step = 1,
length = Math.ceil((to - from) / step),
}): number[] => Array.from({ length }, (_, i) => from + i * step);
const first = range({
from: 0,
to: DIFFICULTY[chest.difficulty].maxValues[0],
step: 1,
});
const second = range({
from: 0,
to: DIFFICULTY[chest.difficulty].maxValues[1],
step: 1,
});
const third = range({
from: 0,
to: DIFFICULTY[chest.difficulty].maxValues[2],
step: 1,
});
let four = range({
from: 0,
to: DIFFICULTY[chest.difficulty].maxValues[3],
step: 1,
});
four = [0];
// first.forEach(async (i) => {
// second.forEach(async (j) => {
// third.forEach(async (k) => {
// four.forEach(async (l) => {
// console.log(i, j, k, l);
// if (
// i === original[0] &&
// j === original[1] &&
// k === original[2] &&
// l === original[3]
// ) {
// console.log(i, j, k, l, end());
// const decipher = await crypto.createDecipheriv(
// 'aes-128-cbc',
// Buffer.from([i, j, k, l, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]),
// iv,
// );
// let decrypted = await decipher.update(code, 'hex', 'utf8');
// decrypted += decipher.final('utf8');
// if (decrypted.includes('Hello')) {
// await this.repository.save({
// ...chest,
// status: ChestStatus.Openned,
// });
// await job.progress(100);
// // return {
// // decrypted,
// // };
// } else {
// }
// }
// });
// });
// });
// });
for (let i = 0; i < DIFFICULTY[chest.difficulty].maxValues[0]; i++) {
for (let j = 0; j < DIFFICULTY[chest.difficulty].maxValues[1]; j++) {
for (let k = 0; k < DIFFICULTY[chest.difficulty].maxValues[2]; k++) {
for (let l = 0; l < DIFFICULTY[chest.difficulty].maxValues[3]; l++) {
if (
i === original[0] &&
j === original[1] &&
k === original[2] &&
l === original[3]
) {
console.log(i, j, k, l, end());
const decipher = await crypto.createDecipheriv(
'aes-128-cbc',
Buffer.from([i, j, k, l, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]),
iv,
);
let decrypted = await decipher.update(code, 'hex', 'utf8');
decrypted += decipher.final('utf8');
if (decrypted.includes('Hello')) {
await this.repository.save({
...chest,
status: ChestStatus.Openned,
});
await job.progress(100);
// return {
// decrypted,
// };
} else {
}
}
}
}
}
}
await this.repository.save({
...chest,
status: ChestStatus.Locked,
});
await job.progress(100);
// return {
// decrypted: false,
// };
}
}

Raphael-js drag and click events inaccurate after browser resize

I've created a radial dial using the Raphael-js library and it works as should on page load. It's embedded in a responsive layout so I want it to resize according to it's container, which it does. However, the new container size makes the mouse events inaccurate. When I resize it back to what it was on page load, it works fine.
function RadialDial(paperId, opts) {
var thisObj = this;
this.dialParent = document.querySelector(paperId);
this.divPaper = this.dialParent.querySelector('.radialDial');
this.divPaperW = this.divPaper.clientWidth;
this.scaleRatio = this.divPaperW / 250;
this.outputEle = this.dialParent.querySelector('.dialOutput .val');
this.btnPlus = this.dialParent.querySelector('.btnPlus');
this.btnMinus = this.dialParent.querySelector('.btnMinus');
this.debug = this.dialParent.querySelector('.debug');
this.opts = {
dialCenter: this.divPaperW / 2,
dialRadius: this.divPaperW / 2,
startA: 155,
endA: 25,
arcCentralA: 230,
maxRange: 12,
minRange: 3,
postText: false,
rangeSteps: 3
}
this.currNeedleA;
this.rangeAngles = [];
this.setOptions(opts);
this.paper = Raphael(this.divPaper, this.opts.dialRadius * 2, this.opts.dialRadius * 2);
this.rangeDivisions = Raphael.rad(this.opts.arcCentralA / (this.opts.maxRange - this.opts.minRange));
this.arcStartX = (this.opts.dialCenter + ((this.opts.dialRadius - (30 * this.scaleRatio)) * Math.cos(Raphael.rad(this.opts.startA)))).toString();
this.arcStartY = (this.opts.dialCenter + ((this.opts.dialRadius - (30 * this.scaleRatio)) * Math.sin(Raphael.rad(this.opts.startA)))).toString();
var currSectorX = this.arcStartX;
var currSectorY = this.arcStartY;
var dialFaceAtts = (Raphael.svg) ? {fill: "r#ffffff-#ffffff:85-#999999:75-#cccccc:57-#999999", stroke: "none"} : {fill: "#ffffff", stroke: "#999999", "stroke-width": (1 * this.scaleRatio)};
this.dialFace = this.paper.circle(this.opts.dialCenter, this.opts.dialCenter, this.opts.dialRadius).attr(dialFaceAtts);
var dialFaceRim = this.paper.circle(this.opts.dialCenter, this.opts.dialCenter, (102 * this.scaleRatio)).attr({fill: "none", "stroke-width": (8 * this.scaleRatio), stroke: "#eeeeee", "stroke-opacity": 0.4});
var currSectorAngle = Raphael.rad(this.opts.startA);
var rangeSet = this.paper.set();
for (var i = this.opts.minRange; i <= (this.opts.maxRange); i++) {
currSectorX = (this.opts.dialCenter + ((this.opts.dialRadius - (40 * this.scaleRatio)) * Math.cos(currSectorAngle))).toString();
currSectorY = (this.opts.dialCenter + ((this.opts.dialRadius - (40 * this.scaleRatio)) * Math.sin(currSectorAngle))).toString();
if (i % this.opts.rangeSteps == 0) {
var rangeTxt = this.paper.text(currSectorX, currSectorY, i).attr({fill: "#00a2d8", "font-size": (22 * this.scaleRatio).toString()});
rangeSet.push(rangeTxt);
this.rangeAngles[i] = Raphael.deg(this.rangeDivisions * (i - (this.opts.minRange)));
}
currSectorAngle = currSectorAngle + this.rangeDivisions;
}
this.clickArea = this.paper.circle(this.opts.dialCenter, this.opts.dialCenter, this.opts.dialRadius).attr({fill: "red", "fill-opacity": 0, stroke: "none"});
this.needle = this.paper.path("M" + (this.arcStartX).toString() + "," + (this.arcStartY).toString() +
"L" + (this.opts.dialCenter * (138.89401 / this.opts.dialCenter) * this.scaleRatio).toString() + "," + (this.opts.dialCenter * (107.45764 / this.opts.dialCenter) * this.scaleRatio).toString() +
"L" + (this.opts.dialCenter * (147.34637 / this.opts.dialCenter) * this.scaleRatio).toString() + "," + (this.opts.dialCenter * (125.5838 / this.opts.dialCenter) * this.scaleRatio).toString() + "z").attr({fill: '#0058b6', stroke: "none"});/* */
var needleLine = this.paper.path("M" + (this.opts.dialCenter + (18 * this.scaleRatio)).toString() + ' ' + (this.opts.dialCenter - (8 * this.scaleRatio)).toString() + ", L" + this.arcStartX + "," + this.arcStartY).attr({stroke: "#ffffff", "stroke-width": .7});
var centerCircle = this.paper.circle(this.opts.dialCenter, this.opts.dialCenter, (12 * this.scaleRatio)).attr({fill: "#0058b6", stroke: "none"});
this.needleSet = this.paper.set();
this.needleSet.push(this.needle, needleLine);
this.dialSet = this.paper.set();
this.dialSet.push(dialFaceRim, this.dialFace, this.clickArea, this.needleSet, rangeSet, centerCircle, needleLine);
this.paper.setViewBox(0, 0, this.opts.dialRadius * 2, this.opts.dialRadius * 2, true);
this.paper.canvas.setAttribute('preserveAspectRatio', 'none');
this.needleSet.push(this.needle);
this.needleSet.data('thisObj', thisObj);
this.needleSet.data('paperObj', this.paper.canvas);
this.setNeedleDrag();
this.dialFaceClick();
}
RadialDial.prototype = {
constructor: RadialDial,
setOptions: function (opts) {
for (key in opts) {
if (!opts.hasOwnProperty(key)) {
continue;
}
this.opts[key] = opts[key];
}
},
drawDial: function () {
},
elePosition: function (ele) {
var eleX = 0;
var eleY = 0;
while (ele) {
eleX += (ele.offsetLeft - ele.scrollLeft + ele.clientLeft);
eleY += (ele.offsetTop - ele.scrollTop + ele.clientTop);
ele = ele.offsetParent;
}
return {x: eleX, y: eleY};
},
moveNeedle: function (dx, dy, x, y, e) {
var classObj = this.data('thisObj');
var rectObject = classObj.divPaper.getBoundingClientRect();
var paperXY = classObj.elePosition(classObj.divPaper);
var mouseX, mouseY;
mouseX = e.clientX - rectObject.left;
mouseY = e.clientY - rectObject.top;
var needleA = Raphael.angle(classObj.opts.dialCenter, classObj.opts.dialCenter, classObj.needle.getPointAtLength(classObj.needle.getTotalLength())['x'], classObj.needle.getPointAtLength(classObj.needle.getTotalLength())['y']);
var newA = Raphael.angle(classObj.opts.dialCenter, classObj.opts.dialCenter, mouseX, mouseY);
var rotateAngle = (360 - needleA) + newA;
if (!(newA > (360 - classObj.opts.startA) && newA < (360 - classObj.opts.endA))) {
classObj.needleSet.transform('r' + rotateAngle + "," + classObj.opts.dialCenter + "," + classObj.opts.dialCenter);
}
},
setNeedleDrag: function () {
var startDrag = function () {
}, dragger = this.moveNeedle,
endDrag = this.findNearestStep;
this.needleSet.drag(dragger, startDrag, endDrag);
},
dialFaceClick: function () {
var classObj = this;
this.clickArea.node.onclick = function (e) {
var e = e || window.event;
var rectObject = classObj.divPaper.getBoundingClientRect();
var mouseX, mouseY;
mouseX = e.clientX - rectObject.left;
mouseY = e.clientY - rectObject.top;
var needleA = Raphael.angle(classObj.opts.dialCenter, classObj.opts.dialCenter, classObj.needle.getPointAtLength(classObj.needle.getTotalLength())['x'], classObj.needle.getPointAtLength(classObj.needle.getTotalLength())['y']);
var newA = Raphael.angle(classObj.opts.dialCenter, classObj.opts.dialCenter, mouseX, mouseY);
var rotateAngle = (360 - needleA) + newA;
if (!(newA > (360 - classObj.opts.startA) && newA < (360 - classObj.opts.endA))) {
classObj.needleSet.transform('r' + rotateAngle + "," + classObj.opts.dialCenter.toString() + "," + classObj.opts.dialCenter.toString());
}
classObj.findNearestStep(classObj);
return false;
}
},
findNearestStep: function (obj) {
var classObj = (obj.target || obj.srcElement) ? this.data('thisObj') : obj;
var currVal = Math.round((Raphael.rad(classObj.needle.matrix.split().rotate) * ((classObj.opts.maxRange - classObj.opts.minRange) / Raphael.rad(classObj.opts.arcCentralA))) + classObj.opts.minRange);
var nextVal = currVal;
var prevVal, newA, index;
if (currVal % classObj.opts.rangeSteps != 0) {
while (nextVal % classObj.opts.rangeSteps != 0) {
nextVal = nextVal + 1;
}
if ((nextVal - currVal) > (classObj.opts.rangeSteps / 2)) {
nextVal = nextVal - classObj.opts.rangeSteps;
}
index = nextVal;
} else {
index = currVal;
}
newA = classObj.rangeAngles[index];
classObj.needleSet.transform('r' + (newA) + "," + classObj.opts.dialCenter + "," + classObj.opts.dialCenter);
}
}
Here is my fiddle, http://jsfiddle.net/fiddle_fish/rvLo1cuy/ , dragging the needle makes it follow the mouse pointer closely. Now click the "Resize container" link, whilst the needle still moves it doesn't follow the pointer closely. It seems the resize has created an offset for the mouse event target area.
I've tried changing the viewbox settings, width/height values,removing events and reapplying them, deleting the dial on resize and redrawing the dial but nothing works.
Tried, raphael js, calculate setViewBox width height to fix window
and, raphael js, resize canvas then setViewBox to show all elements
Neither works. :(
I sussed this out. I've multiplied the mouse x-y coordinates with a ratio based on the paper size onload/resize. Works a treat :)
I just encountered the same issue. Basically, on window resize I recalculate the "scale" i.e. the ratio of the svg element's viewBox to its current height/width.
Here's my solution:
var scale = {
x:1,
y:1
}
$(window).resize(function(){
scale = getScale(paper);
})
function getScale(paper){
var x = paper.canvas.viewBox.baseVal.width/$(paper.canvas).width();
var y = paper.canvas.viewBox.baseVal.height/$(paper.canvas).height();
return {
x:x,
y:y
}
}
and then in my "move" function, I added a multiplier to dx and dy:
var move = function (dx, dy,x,y) {
var X = this.cx + dx * scale.x,
Y = this.cy + dy * scale.y;
this.attr({cx: X, cy: Y});
}
For context, the move function is used like so:
var start = function(){
this.cx = this.attr("cx"),
this.cy = this.attr("cy");
}, move = function (dx, dy,x,y) {
var X = this.cx + dx * scale.x,
Y = this.cy + dy * scale.y;
this.attr({cx: X, cy: Y});
}
raphael_element.drag(move,start);
Note that there's some JQuery thrown in here that you could easily do without.

Is there are more elegant Solution for a large switch statement?

I have got map a lot of ranges to a value like 0-300 = 10 , 300-600 = 20, 600-900 = 30 ... 2500000-2700000 = 7000 .... So I could make a really large switch-statement/if-block but I wonder if there is a more elegant approach to solve this little problem.
Ok here is a small subset of the table with real data:
0-300 : 25
301-600. : 45
601-900 : 65
901-1200. : 85
1201-1500: 105
1501-2000 : 133
2001-2500 : 161
2501-3000: 189
3001-3500:217
3501-4000:245
4001-4500:273
4501-5000:301
5001-6000:338
The most common pattern for getting rid of a switch statement is to use a dictionary. In your case, since you're mapping ranges, you'll use an NSArray of range cutoffs instead. This is what it would look like if you're dealing with ints:
NSArray *rangeCutoffs = [NSArray arrayWithObjects:[NSNumber numberWithInt:300],[NSNumberWithInt:600],...,nil];
NSArray *values = [NSArray arrayWithObjects:[NSNumber numberWithInt:10], [NSNumber numberWithInt:20],...,nil];
int mappedInt;
for (int index=0; index <= [rangeCutoffs count]; index++) {
if (intToMap < [[rangeCutoffs objectAtIndex:index] intValue]) {
mappedInt = [[values objectAtIndex:index] intValue];
}
}
if (mappedInt == 0) {
mappedInt = [[values lastObject] intValue];
}
In practice you'd want to load rangeCutoffs and values from a plist instead of hardcoding them.
You could use a table. e.g.
struct Lookup
{
int min;
int max;
int value;
};
struct Lookup table[] =
{
{ 0, 300, 10 },
{ 301, 600, 20 },
{ 601, 900, 30 },
// other ranges
{ 2500000, 2700000, 7000 },
{ -1, -1, -1 } // marks the end of the table
};
And then simply iterate through it to find the right range
int result = -1;
for (int i = 0 ; table[i].min != -1 && result == -1 ; ++i)
{
if (table[i].min <= value && value <= table[i].max)
{
result = table[i].value;
}
}
If it's a really large table, you can use a binary search instead.
You could do something like this (C example):
#include <stdio.h>
#include <stdlib.h>
typedef int range_type;
typedef int value_type;
typedef struct {
range_type min;
range_type max;
value_type value;
} range_t;
const range_t *find_range(const range_t *ranges, size_t rangesSize,
value_type valueToFind)
{
for (size_t i = 0; i < rangesSize; ++i) {
if (ranges[i].min <= valueToFind && valueToFind <= ranges[i].max)
return &ranges[i];
}
return NULL;
}
int main() {
const range_t ranges[] = {
{ 0, 300, 10 },
{ 301, 600, 20 },
{ 601, 900, 30 },
{ 901, 1200, 40 }
// And so on...
};
value_type testValues[] = {
-1, // None
0, 299, 300, // [ 0, 300]
301, 599, 600, // [301, 600]
601, 899, 900, // [601, 900]
901, 1199, 1200, // [901, 1200]
// And so on...
};
for (size_t i = 0; i < sizeof(testValues) / sizeof(testValues[0]); ++i) {
const range_t *match = find_range(
ranges, sizeof(ranges) / sizeof(ranges[0]), testValues[i]);
if (match != NULL)
printf("%d found at [%d..%d]\n", testValues[i], match->min,
match->max);
else
printf("%d not found\n", testValues[i]);
}
return EXIT_SUCCESS;
}
Should output:
-1 not found
0 found at [0..300]
299 found at [0..300]
300 found at [0..300]
301 found at [301..600]
599 found at [301..600]
600 found at [301..600]
601 found at [601..900]
899 found at [601..900]
900 found at [601..900]
901 found at [901..1200]
1199 found at [901..1200]
1200 found at [901..1200]

bytes to friendly NSString format with MB or KB [duplicate]

This question already has answers here:
Closed 10 years ago.
Possible Duplicate:
ObjC/Cocoa class for converting size to human-readable string?
I'm new in Cocoa. I'm trying to get size of folder files properly. And display it in MB if it less 1 GB , or in GB.
The way I want it to display is rounded with one number after point.
Example
5.5 MB if it is more than 1000 > 1.1 GB
I'm trying to use this
unsigned long long size= ([[[NSFileManager defaultManager] attributesOfItemAtPath:fullPath error:nil] fileSize]);
But I can't a way properly convert number, and display it , as I want.
Thanks.
For converting file size to MB, Gb use below function
- (id)transformedValue:(id)value
{
double convertedValue = [value doubleValue];
int multiplyFactor = 0;
NSArray *tokens = #[#"bytes",#"KB",#"MB",#"GB",#"TB",#“PB”, #“EB”, #“ZB”, #“YB”];
while (convertedValue > 1024) {
convertedValue /= 1024;
multiplyFactor++;
}
return [NSString stringWithFormat:#"%4.2f %#",convertedValue, tokens[multiplyFactor]];
}
EDIT:
You can also use NSByteCountFormatter class. Available in iOS 6.0 / OS X v10.8 and later.
[NSByteCountFormatter stringFromByteCount:1999 countStyle:NSByteCountFormatterCountStyleFile];
You can use NSByteCountFormatterCountStyleFile, NSByteCountFormatterCountStyleMemory, NSByteCountFormatterCountStyleDecimal or NSByteCountFormatterCountStyleBinary in countStyle.
NSByteCountFormatterCountStyleFile: Specifies display of file or storage byte counts. The actual behavior for this is
platform-specific; on OS X 10.8, this uses the decimal style, but that
may change over time.
NSByteCountFormatterCountStyleMemory: Specifies display of memory byte counts. The actual behavior for this is platform-specific; on OS
X 10.8, this uses the binary style, but that may change over time.
NSByteCountFormatterCountStyleDecimal: Specifies the number of bytes for KB explicitly, 1000 bytes are shown as 1 KB
NSByteCountFormatterCountStyleBinary: Specifies the number of bytes for KB explicitly, 1024 bytes are shown as 1 KB
If you're targeting OS X 10.8 or iOS 6, you can use NSByteCountFormatter.
I would write your example like this:
NSError *error = nil;
NSDictionary *attribs = [[NSFileManager defaultManager] attributesOfItemAtPath:path error:&error];
if (attribs) {
NSString *string = [NSByteCountFormatter stringFromByteCount:[attribs fileSize] countStyle:NSByteCountFormatterCountStyleFile];
NSLog(#"%#", string);
}
Here is a piece of code from my library. (I hereby release it under the simplified BSD license so there.) It is fairly extensively tested, and it does all the rounding exactly correct. This is not as trivial as it sounds. It always gives two significant figures unless it prints three digits (e.g., 980 B) in which case all three digits are significant.
Using stringWithFormat:#"%..something...f" won't work because if you round 999999 bytes up to 1000 kilobytes, you want to display it as 1.0 MB, not as 1000 kB.
Note that this code also does "banker's rounding" or "unbiased rounding" or "round to even", whichever you want to call it. So 1050 becomes "1.0 kB", but 1150 becomes "1.2 kB". This is the exact same way that printf does it on my system and is the generally preferred rounding method for this sort of thing.
#include <stdio.h>
#include <string.h>
#include <stdint.h>
#define SIZE_BUFSZ 7
static char const SIZE_PREFIXES[] = "kMGTPEZY";
void
format_size(char buf[SIZE_BUFSZ], uint64_t sz)
{
int pfx = 0;
unsigned int m, n, rem, hrem;
uint64_t a;
if (sz <= 0) {
memcpy(buf, "0 B", 3);
return;
}
a = sz;
if (a < 1000) {
n = a;
snprintf(buf, SIZE_BUFSZ, "%u B", n);
return;
}
for (pfx = 0, hrem = 0; ; pfx++) {
rem = a % 1000ULL;
a = a / 1000ULL;
if (!SIZE_PREFIXES[pfx + 1] || a < 1000ULL)
break;
hrem |= rem;
}
n = a;
if (n < 10) {
if (rem >= 950) {
buf[0] = '1';
buf[1] = '0';
buf[2] = ' ';
buf[3] = SIZE_PREFIXES[pfx];
buf[4] = 'B';
buf[5] = '\0';
return;
} else {
m = rem / 100;
rem = rem % 100;
if (rem > 50 || (rem == 50 && ((m & 1) || hrem)))
m++;
snprintf(buf, SIZE_BUFSZ,
"%u.%u %cB", n, m, SIZE_PREFIXES[pfx]);
}
} else {
if (rem > 500 || (rem == 500 && ((n & 1) || hrem)))
n++;
if (n >= 1000 && SIZE_PREFIXES[pfx + 1]) {
buf[0] = '1';
buf[1] = '.';
buf[2] = '0';
buf[3] = ' ';
buf[4] = SIZE_PREFIXES[pfx+1];
buf[5] = 'B';
buf[6] = '\0';
} else {
snprintf(buf, SIZE_BUFSZ,
"%u %cB", n, SIZE_PREFIXES[pfx]);
}
}
}
Here is the test data:
{ 0, "0 B" },
{ 5, "5 B" },
{ 20, "20 B" },
{ 100, "100 B" },
{ 500, "500 B" },
{ 999, "999 B" },
{ 1000, "1.0 kB" },
{ 1050, "1.0 kB" },
{ 1051, "1.1 kB" },
{ 2349, "2.3 kB" },
{ 2350, "2.4 kB" },
{ 9949, "9.9 kB" },
{ 9950, "10 kB" },
{ 10000, "10 kB" },
{ 10500, "10 kB" },
{ 10501, "11 kB" },
{ 99499, "99 kB" },
{ 99500, "100 kB" },
{ 999499, "999 kB" },
{ 999500, "1.0 MB" },
{ 1000000, "1.0 MB" },
{ 952500000, "952 MB" },
{ 952500001, "953 MB" },
{ 1000000000, "1.0 GB" },
{ 2300000000000ULL, "2.3 TB" },
{ 9700000000000000ULL, "9.7 PB" }

cocos2d slot machine animation

I am trying to create an application that simulates a slot machine.
Now, I have all the images ready, with the vertical png file and the plist file. My question is, how do I simulate the spinning (when the user presses a button), and then the stopping of the slot machine (after around 1-2 seconds)?
Note that the png file has to wrap around somehow.
Everything else is all a matter of NSArrays and checking, what I want to figure out is the animation. I hope some can share some code or references to do this.
Thanks!
This is relatively straightforward: just repeat the png file as many times as you need to simulate the spinning action. So it's basically a vertical variation on parallax scrolling. If you have Steffan Itterheim's book, he talks about this type of thing in chapter 7 "Scrolling With Joy". You may also find some help here: Cocos2D vertically scrolling background
Hope this helps!
Mike
I am doing something like this:
#include <ctime>
#include <vector>
#include <cstdlib>
#include <iostream>
#include <SDL2/SDL.h>
#include <SDL2/SDL_image.h>
using namespace std;
static int last[5]={0, 0, 0, 0, 0};
static int stops[5];
static int reels[5][63] = {
{9,7,12,5,7,3,4,11,9,7,12,6,7,4,11,10,4,3,11,12,7,5,10,9,7,5,9,10,8,9,7,4,9,10,11,5,10,3,9,10,3,9,4,8,7,5,11,9,12,6,3,5,7,9,11,10,6,7,3,5,10,8,4,},
{11,5,4,7,6,8,4,9,7,8,3,11,6,5,11,7,12,5,8,6,10,9,5,6,8,7,12,11,5,6,10,3,4,5,9,12,8,9,6,5,12,8,9,12,7,8,5,10,12,7,11,3,4,8,7,4,5,9,8,6,12,4,6,},
{10,4,5,8,6,7,5,9,6,7,8,4,6,5,11,3,9,8,7,11,12,6,8,5,4,8,6,12,9,6,5,11,3,7,4,8,7,3,10,9,5,6,4,3,9,12,10,8,9,6,3,9,10,6,7,5,6,8,4,11,9,7,8,},
{9,3,6,5,3,10,6,9,5,12,4,8,11,10,7,6,5,9,7,3,8,6,12,4,5,7,3,12,10,6,9,7,8,5,6,4,9,6,11,5,8,12,6,7,3,6,10,3,7,5,10,8,9,6,12,4,7,9,5,6,8,3,10,},
{5,3,9,4,6,12,10,5,11,4,8,7,10,5,9,11,4,6,7,3,6,4,8,5,11,8,5,10,8,11,5,10,8,3,7,4,10,11,5,7,8,9,5,11,6,8,10,3,7,9,3,8,10,12,6,8,10,11,7,10,8,11,6,},
};
static SDL_Window *window = NULL;
static SDL_Surface *canvas = NULL;
static const SDL_Surface *backgroundSurface = IMG_Load("./Background.png");
static SDL_Rect backgroundCoordinates = { 0, 0, 0, 0 };
static const SDL_Surface *symbolsSurface[] = {
NULL,
NULL,
NULL,
IMG_Load("./Symbol03.png"),
IMG_Load("./Symbol04.png"),
IMG_Load("./Symbol05.png"),
IMG_Load("./Symbol06.png"),
IMG_Load("./Symbol07.png"),
IMG_Load("./Symbol08.png"),
IMG_Load("./Symbol09.png"),
IMG_Load("./Symbol10.png"),
IMG_Load("./Symbol11.png"),
IMG_Load("./Symbol12.png"),
NULL,
NULL,
NULL,
NULL,
};
static const SDL_Surface *reelsSurface[5] = {NULL, NULL, NULL, NULL, NULL};
static SDL_Rect symbolsCoordinates[5][3] = {
{ { 298, 118, 0, 0 }, { 298, 266, 0, 0 }, { 298, 414, 0, 0 } },
{ { 474, 118, 0, 0 }, { 474, 266, 0, 0 }, { 474, 414, 0, 0 } },
{ { 651, 118, 0, 0 }, { 651, 266, 0, 0 }, { 651, 414, 0, 0 } },
{ { 827, 118, 0, 0 }, { 827, 266, 0, 0 }, { 827, 414, 0, 0 } },
{ { 1003, 118, 0, 0 }, { 1003, 266, 0, 0 }, {1003, 414, 0, 0 } },
};
static unsigned long animationStart = 0;
static unsigned long animationEnd = 0;
static bool stopped[5] = {false, false, false, false, false};
void draw() {
static double y0 = 0;
static double v0[5] = {-9.5, -9.6, -9.7, -9.8, -9.9};
static long t = 0;
static double a = 0.0005;
static int y = 0;
SDL_BlitSurface((SDL_Surface*) backgroundSurface, NULL, canvas, &backgroundCoordinates);
for (int i = 0; i < 5 && animationStart!=animationEnd; i++) {
/*
* y = y0 + v0*t + 1/2*at^2
*/
y0 = last[i] * 140;
t = (1000 * clock() / CLOCKS_PER_SEC) - animationStart;
y = (int)(y0 + v0[i]*t + a*t*t/2) % (63*140);
if(y < 0) {
y += 63*140;
}
/*
* Stop near to the target position.
*/
if(i==0 && abs(y-stops[i]*140)<=140) {
last[i] = stops[i];
stopped[i] = true;
}else if(stopped[i-1] == true && stopped[i] == false && abs(y-stops[i]*140)<=140) {
last[i] = stops[i];
stopped[i] = true;
}
if(stopped[i] == true) {
SDL_SetSurfaceAlphaMod((SDL_Surface*) reelsSurface[i], 255);
y = stops[i] * 140;
} else {
SDL_SetSurfaceAlphaMod((SDL_Surface*) reelsSurface[i], 191);
}
const SDL_Rect frame = {0, y, 140, 3*140};
SDL_BlitSurface((SDL_Surface*) reelsSurface[i], &frame, canvas, &symbolsCoordinates[i][0]);
}
SDL_UpdateWindowSurface(window);
}
int main() {
SDL_Init(SDL_INIT_EVERYTHING);
/*
* Build long strips (two more images at the end).
*/
for(int i=0, index; i<5; i++) {
reelsSurface[i] = IMG_Load("./Reel.png");
for(int j=0; j<(63+2); j++) {
index = reels[i][j%63];
SDL_Rect coordinates = {0, 140*j, 0, 0};
SDL_BlitSurface((SDL_Surface*) symbolsSurface[index], NULL, (SDL_Surface*)reelsSurface[i], &coordinates);
}
}
//window = SDL_CreateWindow("Slot Reels Animation", 0, 0, 1280, 1024, SDL_WINDOW_FULLSCREEN_DESKTOP);
window = SDL_CreateWindow("Slot Reels Animation", 0, 0, 1440, 900, 0);
canvas = SDL_GetWindowSurface(window);
SDL_Event event;
bool done = false;
while (done == false) {
while (SDL_PollEvent(&event)) {
switch (event.type) {
case SDL_QUIT:
done = true;
break;
case SDL_KEYDOWN:
switch (event.key.keysym.sym) {
case SDLK_ESCAPE:
done = true;
break;
case SDLK_RETURN:
//startAnimation();
memset(stopped, false, 5*sizeof(bool));
animationStart = 1000 * clock() / CLOCKS_PER_SEC;
for (int i = 0, r; i < 5; i++) {
stops[i] = rand() % 63;
}
break;
}
}
}
draw();
}
SDL_DestroyWindow(window);
for(int i=0; i<5; i++) {
SDL_FreeSurface((SDL_Surface*)reelsSurface[i]);
}
SDL_Quit();
return EXIT_SUCCESS;
}