In the beginning, the script worked fine but it's getting slow with time as the number of rows is increasing - google-sheets-api

In the beginning, the script worked fine but it's getting slow with time as the number of rows is increasing.
Is there a way to optimize it or should I use some other tool?
function onEdit() {
var ss = SpreadsheetApp.getActiveSpreadsheet().getActiveSheet();
var datass = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("Confidential- 1");
var activeCell = ss.getActiveCell();
if(activeCell.getColumn() == 4 && activeCell.getRow() > 1){
activeCell.offset(0,1).clearContent().clearDataValidations();
var makes = datass.getRange(1,1,1, datass.getLastColumn()).getValues();
var makeIndex = makes[0].indexOf(activeCell.getValue()) + 1;
if(makeIndex != 0){
var validationRange = datass.getRange(3, makeIndex, datass.getLastRow());
var validationRule = SpreadsheetApp.newDataValidation().requireValueInRange(validationRange).build();
activeCell.offset(0,1).setDataValidation(validationRule);
}
}
}

Related

How to script an Interval in Photoshop

I am trying to script something in Photoshop that has to run every X seconds. In JavaScript it would look like this:
function run() {
    alert('Ran!')
}
setInterval(run, 1000)
 
But in Photoshop's JavaScript, "setInterval is not a function". Any idea how else I would get any form of interval working?
This is by no means a good example, however, it stalls for a second. Those that don't know it there's no pause or sleep function in ECMA 3. I don't use a while loop as that's asking for trouble in Photoshop and may lock things up - which may lose your work.
sleepy(1000)
function sleepy(milliseconds)
{
// Start the fans please! I mean timer
var dStart = new Date().getTime();
var longtime = 10000000;
for(var i = 0 ; i < longtime; i++)
{
var now = new Date().getTime();
if (now > dStart + milliseconds)
{
// Stop the fans!
var dEnd = new Date().getTime();
var timeTaken = (dEnd - dStart)/1000;
var msgTime = (timeTaken + " seconds.");
alert(msgTime);
// alert("Ran");
return;
}
}
}
or if it's just a second, just calculate the Fibbonaci sequence to 28 spots. It's also machine dependant (and rather slower than expected in Photoshop)
function fibonacci(n)
{
if (n < 2)
return n
else
{
return fibonacci(n-1) + fibonacci(n-2);
}
}
var numOfNums = 28; // 0.91 seconds
// var numOfNums = 29; // 1.47 seconds
// var numOfNums = 30; // 2.39 seconds
// var numOfNums = 31; // 3.8 seconds
var dStart = new Date().getTime();
var msg = "";
for(var i = 0 ; i < numOfNums; i++)
{
msg += fibonacci(i) + ", ";
}
var dEnd = new Date().getTime();
var timeTaken = (dEnd - dStart)/1000;
// alert(msg);
alert(timeTaken);

Run last Photoshop script (again)

This seems like a trivial issue but I'm not sure Photoshop supports this type of functionality:
Is it possible to implement use last script functionality?
That is without having to add a function on each and every script that writes it's filename to a text file.
Well... It's a bit klunky, but I suppose you could read in the scriptlistener in reverse order and find the first mention of a script file:
// Switch off any dialog boxes
displayDialogs = DialogModes.NO; // OFF
var scripts_folder = "D:\\PS_scripts";
var js = "C:\\Users\\GhoulFool\\Desktop\\ScriptingListenerJS.log";
var jsLog = read_file(js);
var lastScript = process_file(jsLog);
// use function to call scripts
callScript(lastScript)
// Set Display Dialogs back to normal
displayDialogs = DialogModes.ALL; // NORMAL
function callScript (ascript)
{
eval('//#include "' + ascript + '";\r');
}
function process_file(afile)
{
var needle = ".jsx";
var msg = "";
// Let's do this backwards
for (var i = afile.length-1; i>=0; i--)
{
var str = afile[i];
if(str.indexOf(needle) > 0)
{
var regEx = str.replace(/(.+new\sFile\(\s")(.+\.jsx)(.+)/gim, "$2");
if (regEx != null)
{
return regEx;
}
}
}
}
function read_file(inFile)
{
var theFile = new File(inFile);
//read in file
var lines = new Array();
var l = 0;
var txtFile = new File(theFile);
txtFile.open('r');
var str = "";
while(!txtFile.eof)
{
var line = txtFile.readln();
if (line != null && line.length >0)
{
lines[l++] = line;
}
}
txtFile.close();
return lines;
}

how do I change this google app script to hide sheets columns instead of rows based on column dates

so I'm a long way form a competent programmer, but I do dabble in google sheets scripts.
I previously had a script running on a timer trigger to hide rows based on dates found in column A.
function min() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var s = ss.getSheetByName("OLD");
var v = s.getRange("A:A").getValues();
var today = new Date().getTime() - 86400000‬
for (var i = s.getLastRow(); i > 1; i--) {
var t = v[i - 1];
if (t != "") {
var u = new Date(t);
if (u < today) {
s.hideRows(i);
}
}
}
}
function max() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var s = ss.getSheetByName("OLD");
var v = s.getRange("A:A").getValues();
var today = new Date().getTime() + 86400000
for (var i = s.getLastRow(); i > 1; i--) {
var t = v[i - 1];
if (t != "") {
var u = new Date(t);
if (u > today) {
s.hideRows(i);
}
}
}
}
function SHOWROWS() {
// set up spreadsheet and sheet
var ss = SpreadsheetApp.getActiveSpreadsheet(), sheets = ss.getSheets();
for(var i = 0, iLen = sheets.length; i < iLen; i++) {
// get sheet
var sh = sheets[i];
// unhide rows
var rRows = sh.getRange("A:A");
sh.unhideRow(rRows);
}
}
this would be set to run at midnight every night so as to hide all rows not +-1 day from the current date.
I have now switched to using this document primarily through the android app I need to swap the input layout Moving dates from rows and values in columns to the inverse, values are now in rows and the dates are in columns, inputing vertical values would be much quicker....but honestly its more about understanding what im doing wrong thats really motivating my search for a answer.
can anyone help me modify my old script to work on this changed sheet.
my attempts fall flat..eg:
function min() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var s = ss.getSheetByName("OLD");
var v = s.getRange("1:1").getValues();
var today = new Date().getTime() - 86400000‬
for (var i = s.getLastColumn(); i > 1; i--) {
var t = v[i - 1];
if (t != "") {
var u = new Date(t);
if (u < today) {
s.hidecolumns(i);
}
}
}
}
example spreadsheet:
https://docs.google.com/spreadsheets/d/1EjRIeDPrG_qp1S9QhInl9r3G0cZx_16OvnTXORgA3n4/edit?usp=sharing
there is two sheets one with the old version of my layout called "OLD" and my new desired layout called "NEW"
thanks in advance to anyone able to help
This function hides a column whose top row date is less yesterday.
function hideColumnWhenTopRowDateIsBeforeYesterday() {
var ss=SpreadsheetApp.getActiveSpreadsheet();
var sh=ss.getSheetByName("Sheet1");
var rg=sh.getRange(1,1,1,sh.getLastColumn());
var vA=rg.getValues()[0];
var yesterday=new Date(new Date().getFullYear(),new Date().getMonth(),new Date().getDate()-1).valueOf();
vA.forEach(function(e,i){
if(new Date(e).valueOf()<yesterday) {
sh.hideColumns(i+1);
}
});
}
My Spreadsheet before hiding columns:
My Spreadsheet after hiding columns:

Use Cecil to insert begin/end block around functions

this simple code works fine and allows to add a BeginSample/EndSample call around each Update/LateUpdate/FixedUpdate function. However it doesn't take in consideration early return instructions, for example as result of a condition. Do you know how to write a similar function that take in considerations early returns so that the EndSample call will be executed under every circumstance?
Note that I am not a Cecil expert, I am just learning now. It appears to me that Cecil automatically updates the operations that returns early after calling InsertBefore and similar functions. So if a BR opcode was previously jumping to a specific instruction address, the address will be updated after the insertions in order to jump to the original instruction. This is OK in most of the cases, but in my case it means that an if statement would skip the last inserted operation as the BR operation would still point directly to the final Ret instruction. Note that Update, LateUpdate and FixedUpdate are all void functions.
foreach (var method in type.Methods)
{
if ((method.Name == "Update" || method.Name == "LateUpdate" || method.Name == "FixedUpdate") &&
method.HasParameters == false)
{
var beginMethod =
module.ImportReference(typeof (Profiler).GetMethod("BeginSample",
new[] {typeof (string)}));
var endMethod =
module.ImportReference(typeof (Profiler).GetMethod("EndSample",
BindingFlags.Static |
BindingFlags.Public));
Debug.Log(method.Name + " method found in class: " + type.Name);
var ilProcessor = method.Body.GetILProcessor();
var first = method.Body.Instructions[0];
ilProcessor.InsertBefore(first,
Instruction.Create(OpCodes.Ldstr,
type.FullName + "." + method.Name));
ilProcessor.InsertBefore(first, Instruction.Create(OpCodes.Call, beginMethod));
var lastRet = method.Body.Instructions[method.Body.Instructions.Count - 1];
ilProcessor.InsertBefore(lastRet, Instruction.Create(OpCodes.Call, endMethod));
changed = true;
}
}
as a Bonus, if you can explain to me the difference between Emit and Append a newly created instruction with the same operand. does Append execute an Emit under the hood or does something more?
I may have found the solution, at least apparently it works. I followed the code used to solve a similar problem from here:
https://groups.google.com/forum/#!msg/mono-cecil/nE6JBjvEFCQ/MqV6tgDCB4AJ
I adapted it for my purposes and it seemed to work, although I may find out other issues. This is the complete code:
static bool ProcessAssembly(AssemblyDefinition assembly)
{
var changed = false;
var moduleG = assembly.MainModule;
var attributeConstructor =
moduleG.ImportReference(
typeof(RamjetProfilerPostProcessedAssemblyAttribute).GetConstructor(Type.EmptyTypes));
var attribute = new CustomAttribute(attributeConstructor);
var ramjet = moduleG.ImportReference(typeof(RamjetProfilerPostProcessedAssemblyAttribute));
if (assembly.HasCustomAttributes)
{
var attributes = assembly.CustomAttributes;
foreach (var attr in attributes)
{
if (attr.AttributeType.FullName == ramjet.FullName)
{
Debug.LogWarning("<color=yellow>Skipping already-patched assembly:</color> " + assembly.Name);
return false;
}
}
}
assembly.CustomAttributes.Add(attribute);
foreach (var module in assembly.Modules)
{
foreach (var type in module.Types)
{
// Skip any classes related to the RamjetProfiler
if (type.Name.Contains("AssemblyPostProcessor") || type.Name.Contains("RamjetProfiler"))
{
// Todo: use actual type equals, not string matching
Debug.Log("Skipping self class : " + type.Name);
continue;
}
if (type.BaseType != null && type.BaseType.FullName.Contains("UnityEngine.MonoBehaviour"))
{
foreach (var method in type.Methods)
{
if ((method.Name == "Update" || method.Name == "LateUpdate" || method.Name == "FixedUpdate") &&
method.HasParameters == false)
{
var beginMethod =
module.ImportReference(typeof(Profiler).GetMethod("BeginSample",
new[] { typeof(string) }));
var endMethod =
module.ImportReference(typeof(Profiler).GetMethod("EndSample",
BindingFlags.Static |
BindingFlags.Public));
Debug.Log(method.Name + " method found in class: " + type.Name);
var ilProcessor = method.Body.GetILProcessor();
var first = method.Body.Instructions[0];
ilProcessor.InsertBefore(first,
Instruction.Create(OpCodes.Ldstr,
type.FullName + "." + method.Name));
ilProcessor.InsertBefore(first, Instruction.Create(OpCodes.Call, beginMethod));
var lastcall = Instruction.Create(OpCodes.Call, endMethod);
FixReturns(method, lastcall);
changed = true;
}
}
}
}
}
return changed;
}
static void FixReturns(MethodDefinition med, Instruction lastcall)
{
MethodBody body = med.Body;
var instructions = body.Instructions;
Instruction formallyLastInstruction = instructions[instructions.Count - 1];
Instruction lastLeaveInstruction = null;
var lastRet = Instruction.Create(OpCodes.Ret);
instructions.Add(lastcall);
instructions.Add(lastRet);
for (var index = 0; index < instructions.Count - 1; index++)
{
var instruction = instructions[index];
if (instruction.OpCode == OpCodes.Ret)
{
Instruction leaveInstruction = Instruction.Create(OpCodes.Leave, lastcall);
if (instruction == formallyLastInstruction)
{
lastLeaveInstruction = leaveInstruction;
}
instructions[index] = leaveInstruction;
}
}
FixBranchTargets(lastLeaveInstruction, formallyLastInstruction, body);
}
private static void FixBranchTargets(
Instruction lastLeaveInstruction,
Instruction formallyLastRetInstruction,
MethodBody body)
{
for (var index = 0; index < body.Instructions.Count - 2; index++)
{
var instruction = body.Instructions[index];
if (instruction.Operand != null && instruction.Operand == formallyLastRetInstruction)
{
instruction.Operand = lastLeaveInstruction;
}
}
}
basically what it does is to add a Ret instuction, but then replace all the previous Ret (usually one, why should it be more than one?) with a Leave function (don't even know what it means :) ), so that all the previous jumps remain valid. Differently than the original code, I make the Leave instruction point to the EndSample call before the last Ret

Updating the google spreedsheet from MS SQL using Google Scripts

I'm trying to connect the MS SQL to a google spreadsheet using google app scripts. here is my app script code
function SQLdb() {
// Replace the variables in this block with real values.
// Read up to 1000 rows of data from the table and log them.
function readFromTable() {
var user = '{usename}';
var userPwd = '{password}';
var database = '{databasename}'
var connectionString = 'jdbc:sqlserver://server.database.windows.net:1433;databaseName='+database;
var conn = Jdbc.getConnection(connectionString , user, userPwd);
var start = new Date();
var stmt = conn.createStatement();
stmt.setMaxRows(1000);
var results = stmt.executeQuery('SELECT TOP 1000 * FROM dbo.dbo');
var numCols = results.getMetaData().getColumnCount();
while (results.next()) {
var rowString = '';
for (var col = 0; col < numCols; col++) {
rowString += results.getString(col + 1) + '\t';
}
Logger.log(rowString)
}
results.close();
stmt.close();
var end = new Date();
Logger.log('Time elapsed: %sms', end - start);
}
readFromTable();
}
Now when I look the log in Script Editor, I can see that this connection to the SQL database is working and the script is able to read all the table cell. But I couldn't able to get that data into the spreedsheet. I'm new to app scripts. So is there something that I'm missing here ?
Any help would be much appricated!
Here's how I do it. Change all the capitalised bits to the appropriate URL, database name, Google Spreadsheet ID, etc.
To this function pass the SQL query you want to execute on the MSSQL database and the name of the sheet within the Spreadsheet that you want to put the data into. This basically fills that named sheet with the query results including column names.
function getData(query, sheetName) {
//jdbc:sqlserver://localhost;user=MyUserName;password=*****;
var conn = Jdbc.getConnection("jdbc:sqlserver://URL;user=USERNAME;password=PASSWORD;databaseName=DBNAME");
var stmt = conn.createStatement();
stmt.setMaxRows(MAXROWS);
var rs = stmt.executeQuery(query);
Logger.log(rs);
var doc = SpreadsheetApp.openById("SPREADSHEETID");
var sheet = doc.getSheetByName(sheetName);
var results = [];
var cell = doc.getRange('a1');
var row = 0;
cols = rs.getMetaData();
colNames = [];
for (i = 1; i <= cols.getColumnCount(); i++ ) {
Logger.log(cols.getColumnName(i));
colNames.push(cols.getColumnName(i));
}
results.push(colNames);
var rowCount = 1;
while(rs.next()) {
curRow = rs.getMetaData();
rowData = [];
for (i = 1; i <= curRow.getColumnCount(); i++ ) {
rowData.push(rs.getString(i));
}
results.push(rowData);
rowCount++;
}
sheet.getRange(1, 1, MAXROWS, cols.getColumnCount()).clearContent();
sheet.getRange(1, 1, rowCount, cols.getColumnCount()).setValues(results);
Logger.log(results);
rs.close();
stmt.close();
conn.close();
}
Here is how i did it. I also made a JDBC connector tool for Mysql & MSSQL. You can adapt this tool from my GitHub Repo Here: Google Spreadsheet JDBC Connector
function readFromTable(queryType, queryDb, query, tab, startCell) {
// Replace the variables in this block with real values.
var address;
var user;
var userPwd ;
var dbUrl;
switch(queryType) {
case 'sqlserver':
address = '%YOUR SQL HOSTNAME%';
user = '%YOUR USE%';
userPwd = '%YOUR PW%';
dbUrl = 'jdbc:sqlserver://' + address + ':1433;databaseName=' + queryDb;
break;
case 'mysql':
address = '%YOUR MYSQL HOSTNAME%';
user = '%YOUR USER';
userPwd = '%YOUR PW%';
dbUrl = 'jdbc:mysql://'+address + '/' + queryDb;
break;
}
var conn = Jdbc.getConnection(dbUrl, user, userPwd);
var start = new Date();
var stmt = conn.createStatement();
var results = stmt.executeQuery(query);
var sheet = SpreadsheetApp.getActiveSpreadsheet();
var sheetTab = sheet.getSheetByName(tab);
var cell = sheetTab.getRange(startCell);
var numCols = results.getMetaData().getColumnCount();
var numRows = sheetTab.getLastRow();
var headers ;
var row =0;
clearRange(tab,startCell,numRows, numCols);
for(var i = 1; i <= numCols; i++){
headers = results.getMetaData().getColumnName(i);
cell.offset(row, i-1).setValue(headers);
}
while (results.next()) {
var rowString = '';
for (var col = 0; col < numCols; col++) {
rowString += results.getString(col + 1) + '\t';
cell.offset(row +1, col).setValue(results.getString(col +1 ));
}
row++
Logger.log(rowString)
}
results.close();
stmt.close();
var end = new Date();
Logger.log('Time elapsed: %sms', end - start);
}
If you don't want to create your own solution, check out SeekWell. It allows you to connect to databases and write SQL queries directly in Sheets from a sidebar add-on. You can also schedule queries to automatically run daily, hourly or every five minutes.
Disclaimer: I made this.
Ok, I finally managed to make it work.
Some few tips: Script is executed at Google's server, so connection must be done over inetrnet, i.e. connect string should be something like "jdbc:sqlserver://172.217.29.206:7000;databaseName=XXXX" and make sure that ip/port mentioned at connect string, can reach you database from outside world. Open port at firewall, make IP forwarding at router and use dyndns or similar services if you do not have a valid domain etc. Sheet ID is the large id string that appeals at your google document's url.