Monday, May 03, 2021

file written to google drive by service account

When using a service account to create a new file and add it to Google drive, the file owner is the service account.

Can transfer ownership using Google Apps Script also.

https://stackoverflow.com/questions/46680352/how-can-i-change-the-owner-of-a-google-sheets-spreadsheet

https://stackoverflow.com/questions/65256980/google-drive-api-v3-php-client-transfer-file-ownership


restarting remote desktop via ssh or azure portal

Bs series burstable Azure VMs seem to have an endemic problem of the remote desktop service dying or refusing connections after the VM sees some heavy loads. Initially I had no idea why RDesktop was not connecting ... anyway, a couple of workarounds - 

  1. install ssh server and leave the ssh service running
    ssh into the machine when RDP has issues,
    ssh  adminuser@server.tld -p port
    powershell
    get-service termservice
    get-service termservice -dependentservices

    stop-service UmRdpService
    restart-service termservice
    start-service UmRdpService


  2. install Windows Admin Center on the VM and connect via the Azure portal, restarting the same services as above. We need to use the "Connect with Public IP address" method on the Azure portal. 




Sunday, May 02, 2021

limits for google apps scripts execution

A six-minute script execution time is mentioned at
https://developers.google.com/apps-script/guides/services/quotas (also many more limits)
but if we run the script from the Script Editor, the limit is 30 minutes. Probably since our Google Workspace is a non-profit one, so the "paid" limit applies. And a programmatic workaround (which I've not used till now.)

Saturday, May 01, 2021

handling duplicates in an sql query

There was a database in which we needed to join three tables, asset id -> subtheme id -> subtheme and the subtheme ids had duplicates as well as multiple subthemes mapped to an asset id. Getting a go-ahead from the content lead, the solution chosen was to just take a single subtheme, using select distinct - something like the first method at https://www.sisense.com/blog/4-ways-to-join-only-the-first-row-in-sql/  

left join (select distinct on (asset_id) * 
         from asset_subtheme_xref
     order by asset_id asc
) as asx on asx.asset_id = tma.asset_id
        left join subtheme_master lsm on lsm.subtheme_id = asx.subtheme_id

Edit: Later on, multiple subthemes were desired. Then, instead of using select distinct in this way, we would need some sort of subquery similar to the one used below for another field, using concat to add all the different subthemes together with CHR(10) - linefeed - between them - all enclosed in parentheses - 

(select string_agg(concat(btwo.name,',',stwo.name,',',sutwo.NAME,',',ctwo.name), CHR(10))
as "Asset tagged to Board Standard Chapter"
from asset_bssc_xref tabxr 
left join chapters ctwo on ctwo.chapter_id = tabxr.chapter_id
left join board_master btwo on btwo.board_id = ctwo.board_id 
left join standard_master stwo on stwo.standard_id = ctwo.standard_id
left join subject_master sutwo on sutwo.subject_id = ctwo.subject_id 
where 
tabxr.asset_id = tma.asset_id) as "Asset Tagged to"


installing ssh client and server on Windows Server 2016

https://hostadvice.com/how-to/how-to-install-an-openssh-server-client-on-a-windows-2016-server/

Or maybe needs this,

https://github.com/PowerShell/Win32-OpenSSH/wiki/Install-Win32-OpenSSH

or other solutions listed at

https://docs.microsoft.com/en-us/answers/questions/79182/sshd-and-sshd-agent-service-not-appearing-after-in.html

or maybe the powershell method listed at

https://docs.microsoft.com/en-us/windows-server/administration/openssh/openssh_install_firstuse

Friday, April 23, 2021

demo of displaying content from google drive using a service account

Following
https://www.labnol.org/code/20375-service-accounts-google-apps-script

creating a service account and delegating authority domain-wide via the method at
https://developers.google.com/admin-sdk/directory/v1/guides/delegation

The google apps script's Code.gs has the following, and will work even when the doc is not shared with anyone except theuser@ourdomain.tld:

var JSON = {
    "private_key": "-----BEGIN PRIVATE KEY-----\nMI -- snip -- pJKdnI=\n-----END PRIVATE KEY-----\n",
    "client_email": "what-name-we-give@proj-name.iam.gserviceaccount.com",
    "client_id": "1020123456872",
    "user_email": "theuser@ourdomain.tld"
};

function getOAuthService() {
    return OAuth2.createService("Service Account")
        .setTokenUrl('https://accounts.google.com/o/oauth2/token')
        .setPrivateKey(JSON.private_key)
        .setIssuer(JSON.client_email)
        .setSubject(JSON.user_email)
        .setPropertyStore(PropertiesService.getScriptProperties())
        .setParam('access_type', 'offline')
        .setScope('https://www.googleapis.com/auth/drive');
}


function getUserFiles() {
    var service = getOAuthService();
    service.reset();
    //Logger.log("Getting the Access Token:");
    var atoken = service.getAccessToken()
    //Logger.log(atoken);
    if (service.hasAccess()) {      
        var url = 'https://drive.google.com/open?id=1qM_DOC_ID_5QiIJ0Ls';
        var response = UrlFetchApp.fetch(url, {
            headers: {
                Authorization: 'Bearer ' + atoken
            }
        });
        return response.getContentText()        
    }
}

function reset() {
    var service = getOAuthService();
    service.reset();
}


function doGet(e) {    
  var htmlout = HtmlService.createHtmlOutput(getUserFiles());
  return htmlout
      .setTitle('Our Team')
      .setSandboxMode(HtmlService.SandboxMode.IFRAME);
}

Thursday, April 22, 2021

Google sign in on Moodle

The user experience for google sign-in on Moodle is shown at 
https://www.youtube.com/watch?v=1_HDZoir3eQ
and how to set up, for admins, is shown at 
https://www.youtube.com/watch?v=cwUWvTiGSAk
- accurate as of Nov 2020, as google's api and set up screens keeps changing.

Tuesday, April 20, 2021

No matching client found - error for android app

As mentioned on this page - 
https://stackoverflow.com/questions/34990479/no-matching-client-found-for-package-name-google-analytics-multiple-productf

the reason was that the package name in the google-services json and the local package name of the app had a discrepancy. 

monitoring ram usage over time on windows server

One of our servers running a .NET app was undergoing a stress test to check whether it could scale to the required number of users. RAM was the biggest question-mark. Looking for ways to monitor RAM usage, found this page which talks about creating a counter log in perfmon. And how to create a counter log is explained here

Basically - 
  1. Start - Run - perfmon.exe
  2. User defined folder -> New -> Data Collector Set and give it a name.
  3. Choose Create Manually, and Next.
  4. Create Data Logs -> Performance Counter and then click Next.
  5. Choose the desired object(s) to log
  6. (Every 5 seconds may be a good option for short collection runs)
  7. Choose location to save in.
  8. "Start this data collector set now" if desired.
  9. To stop data logging, right-click User Defined -> Data Collector Set -> our set and click Stop.
And later to view the data, choose the View Log Data button, Data Source as Log Files, and add the relevant log file.

And caveat when viewing and using Processor usage, the Scale factor has to be taken into account. 

Sunday, April 18, 2021

search for a string or a number in an entire database

With just sql queries, this would need some amount of scripting as mentioned on stackoverflow. But with visual database tools like DBeaver, it's just a simple operation of doing a full-text search, with the option of including numeric fields or not - "Search in numbers" and the option to search Large Objects or not - "Search in LOBs".

show an image from binary data using php

This SO link gives two techniques - using data URIs and using a helper function to fetch the image from some storage location. 

Edit: My initial implementation was using the simpler data URI method. Later, also implemented using the other method. 

Intially,

    // to prevent images from being dropped due to timeouts, we'll encode them inline
    // https://ads-developers.googleblog.com/2013/11/how-to-send-pdf-reports-with-adwords.html

    // to parse the img src, we'll use regex, since the html is of limited scope
    // https://stackoverflow.com/questions/14939296/extract-image-src-from-a-string/15013465
    // https://support.google.com/a/answer/1346938

    var m;
    imageurls = [];
    regexstring = '(http\\S*' + currentTicket +')'; // the \ is escaped, so it becomes doubled.
    //Logger.log(regexstring);
    //myregex = new RegExp(/(http\S*0d0a226a47229313863)/,"gi");    // the ticket is the string after S*
    myregex = new RegExp(regexstring, "gi");
    
    var imageindex=1;

    while ( m = myregex.exec( contentdata ) ) {
      imageurls.push( m[1] );
      //Logger.log(m[1])
      var imagemimetypeandenc = 'data:image/png;base64,'; // default
      if ( m[1].includes('.jpg?ticket') || m[1].includes('.jpeg?ticket') ) {
        imagemimetypeandenc = 'data:image/jpeg;base64,';
      }
      else if ( m[1].includes('.gif?ticket') || m[1].includes('.GIF?ticket') ) {
        imagemimetypeandenc = 'data:image/gif;base64,';
      }
      else if ( m[1].includes('.tif?ticket') || m[1].includes('.tiff?ticket') ) {
        imagemimetypeandenc = 'data:image/tiff;base64,';
      }

      Logger.log('%s of assetid %s',imageindex.toString(),data_array[i][0].toString());
      imageindex = imageindex + 1;
      var imageBlob = UrlFetchApp.fetch(m[1]).getBlob();
      var base64EncodedBytes = Utilities.base64Encode(imageBlob.getBytes());
      var imageencoded = imagemimetypeandenc + base64EncodedBytes;
      contentdata = contentdata.replace(m[1], imageencoded);

    }

and later, when images were available on Google Drive,

  var placeholder = 'data:image/png;base64,iVBORs_a_few_kb_of_chars_QAAAABJRU5ErkJggg==';
  // made with https://www.w3docs.com/tools/image-base64

.....
var m;
    imageurls = [];
    regexstring = 'img\\s*src\\s*=\\s*"(http\\S*)"'; // the \ is escaped, so it becomes doubled.
    // Only the http part is captured, within parenthesis
    // currently it matches only if the img src is within double quotes. "https://whatever"
    //Logger.log(regexstring);
    
    myregex = new RegExp(regexstring, "gi");
    var imageindex=1;

    while ( m = myregex.exec( contentdata ) ) {
      imageurls.push( m[1] );
      
      Logger.log('%s of assetid %s',imageindex.toString(),data_array[i][0].toString());
      imageindex = imageindex + 1;
      
      // here we use a helper if the url contains the alfresco url
      var response;
      if (m[1].includes('ticket=') ) {
        //use the helper function after extracting the filename
        Logger.log('Downloading %s using Alfresco helper', m[1])
        var fname = m[1].split('/')[9]; // from the url similar to 
        // https://www.ourdomain.tld/alfresco/d/direct/workspace/NameOfStore/f8b56b7c-long-id-a35c30be756a/filename-15092020100930PM?ticket=
        fname = fname.split('?')[0];
        // handle cases where fname does not have extension
        var urlFromHelper = UrlFetchApp.fetch(returnUrl+encodeURIComponent(fname));
        response = UrlFetchApp.fetch(urlFromHelper, { muteHttpExceptions: true });
      } 
      
      
      if (response.getResponseCode()==200) {
        var imageBlob = response.getBlob();
        var base64EncodedBytes = Utilities.base64Encode(imageBlob.getBytes());
        //var ctype = response.getHeaders().Content-Type
        var headermap = new Map(Object.entries(response.getHeaders()));
        var mimetypeofim = headermap.get('Content-Type');
        var imagemimetypeandenc = 'data:'+mimetypeofim+';base64,';

        var imageencoded = imagemimetypeandenc + base64EncodedBytes;
        contentdata = contentdata.replace(m[1], placeholder);
      }
      

And in cases where we did not want the images to be base64 encoded - for example in the case of large images, which would cause the function to run out of memory - the whole code-block above would be not needed, since the img src link in the original html would work OK.

Saturday, April 17, 2021

embedding a pdf in a web page

Several ways to embed a pdf in an html page - 
  1. Using the object or embed tag (deprecated)
  2. Using a tags with href links (not actually embedding, linking)
  3. Using iframe tags where the toolbar can be made hidden with src="/path/my.pdf#toolbar=0"
  4. Using Adobe's SDK - somewhat complicated initial setup
  5. If hosted on Google Drive, using an iframe with src="https://drive.google.com/file/d/FILE_ID/preview">

Friday, April 16, 2021

showing memory usage by process and user

Using ps on the linux terminal, we can check out memory usage - 
https://www.networkworld.com/article/3516319/showing-memory-usage-in-linux-by-process-and-user.html

"sort is being used with the -r (reverse), the -n (numeric) and the -k (key) options which are telling the command to sort the output in reverse numeric order based on the fourth column (memory usage) in the output from ps. If we first display the heading for the ps output, this is a little easier to see."

ps aux | head -1; ps aux | sort -rnk 4 | head -5

resubscribing to letsencrypt emails

According to this thread,
https://community.letsencrypt.org/t/accidentally-unsubscribed/14682/14
the way to resubscribe after accidentally unsubscribing would be to change the registration to email+1@mydomain.com with

certbot update_account --email yourname+1@example.com

Wednesday, April 14, 2021

azcopy with sas tokens and more

I struggled a bit with using azcopy to copy from one Azure storage container to another. The issue finally turned out to be that when a new SAS token was created, the default permissions were not sufficient for what I wanted to do - needed to set the expiry time and read / write permissions correctly. After doing that, the command was:

azcopy cp "https://ourstorage.blob.core.windows.net/ourname/*" "https://ourdestination.blob.core.windows.net/destname?sp=racw&st=-snip-&se=-snip-&spr=https&sv=-snip-&sr=c&sig=-snip-%3D" --recursive

51.3 %, 4919 Done, 0 Failed, 5647 Pending, 0 Skipped, 10566 Total, 2-sec Throughput (Mb/s): 3685.9743
 
- finished in around 5 min.

updating an app on Google's Android Play App store

Updating an already existing app with an already existing developer account - the process was fairly intuitive. Older tutorials point to https://market.android.com/publish/Home - that url now redirects to https://play.google.com/console

Basically we need to click on the relevant app, choose View Releases Overview - Release Dashboard - Create new Release.

To edit the play store listing - the text shown on the play store - we need to scroll all the way down on the left-hand side, Grow - Store presence - Main store listing.

For creating the release, we need to sign with the same keystore and signature used to sign the earlier version, etc. as in this post.

Monday, April 12, 2021

comparing Azure pricing in different locations

An interesting site - https://azureprice.net/ Makes it easy to find the region with lowest pricing for a particular type of VM. 

As the site says, pricing is also different in different currencies. 

Sunday, April 11, 2021

customizing and building the moodle app

After learning how to use an older node version, the moodle app could finally be customized and built - a follow-up to my failed trials here

The customization steps and the relevant modified files are in this location, with the changes being mentioned in the readme as follows:

1. Changes as per the moodle doc on compiling with AOT
Edit Oct 2022 - the link content seems to have changed, so here is the archived version.

2. Change to the build.gradle to force minSdkversion 22

ext.cdvMinSdkVersion = 22

to build with android studio. This can also be done in config.xml

3. Changing all ~ and ^ package dependencies in package.json (except cordova-android itself) to the exact package. Done by replacing

"~ with "

and

"^ with "

in package.json

Or else, all sorts of dependency issues while building the 2019 release in 2021. (The nvm use 11 may also suffice for solving the dependency issues.)

The build directory takes up more than 1 GB, lots of dependencies are downloaded. 

4. Customization - Changes as per the Configuration heading at this post,

config.xml

reduced SplashScreenDelay

and SplashShowOnlyFirstTime true

also, very important,

content src="https://sssvidyavahini.org" would make the app like a simple webview.

We don't want that, so leave the content src as it is, 

but change the siteurl in src/config.json

and onlyallowlistedsites true.

We also set android-minSdkVersion" value="22" in config.xml

google-services.json - changed the package name

and changed the version in src/config.json , ensured it is different, but same number of digits.

5. Changing logo and splash screen as per above url and also resources/android/icon-background.png and icon-foreground.png

and in src dir assets/img

Build steps

Set up environment variables as per

https://cordova.apache.org/docs/en/10.x/guide/platforms/android/


JDK tar.gz needed oracle login, created as my official email

Set JAVA_HOME

https://docs.oracle.com/cd/E19182-01/821-0917/inst_jdk_javahome_t/index.html


in .bashrc, added the following.

export JAVA_HOME=/home/mac/Downloads/jdk1.8.0_281
export PATH=$JAVA_HOME/bin:$PATH
export ANDROID_SDK_ROOT=/home/mac/Android/Sdk
export PATH=$ANDROID_SDK_ROOT/tools:$ANDROID_SDK_ROOT/tools/bin:$ANDROID_SDK_ROOT/platform-tools:$PATH

As mentioned in the cordova documentation, we can open a cordova project inside android studio for the final build - choose the import gradle project option, and choose the platforms/android directory.

But we need to edit www folder outside android studio, then copy over changes by doing cordova build - in our case, npx etc as below.

The build process is detailed in the following steps to build document:

# https://docs.moodle.org/dev/Setting_up_your_development_environment_for_Moodle_Mobile_2

nvm install node 11.15.0

# https://www.sitepoint.com/quick-tip-multiple-versions-node-nvm/

nvm use 11

# this is very important - if starting again in another terminal window, must do nvm use 11

# or else all sorts of hard to diagnose errors occur during the build.


sudo apt-get install libsecret-1-dev


npm install 

# cordova.plugins.diagnostic: Diagnostic plugin - ERROR: ENOENT: no such file or directory, open '/home/mac/StudioProjects/LMSappBuildTrial/config.xml'

# added 1829 packages from 1022 contributors and audited 1958 packages in 98.592s


npx cordova prepare 

# Current working directory is not a Cordova-based project.

# https://stackoverflow.com/questions/21276294/cordova-current-working-directory-is-not-a-cordova-based-project

#mkdir www

npm install cordova

# this is also important. Without this, the www directory is not populated for the final prod build.


npm i -g cordova-res

# This is also important - without this, the image resources won't get processed

# config.xml was missing, so copied over with git

# No platforms added to this project. Please use `cordova platform add <platform>`.

npx ionic cordova platform add android --verbose

#Source path does not exist: resources/android/icon/drawable-hdpi-smallicon.png

#Error: Source path does not exist: resources/android/icon/drawable-hdpi-smallicon.png

# editing gitignore file to add the resources

npx cordova prepare

# ignoring the warning about conflict


npx gulp


npm start

# This is an optional step to check in a browser and verify.

# waiting till transpile started etc. then Ctrl+C

# If we wait till the end - nearly an hour on a 4 GB system? it opens in the default browser.


# https://docs.moodle.org/dev/Setting_up_your_development_environment_for_Moodle_Mobile_2#Compiling_using_AOT

cp -v "changes made to moodleapp files/inside node_modules dir/@angular/platform-browser-dynamic/esm5/platform-browser-dynamic.js" "node_modules/@angular/platform-browser-dynamic/esm5"

cp -v "changes made to moodleapp files/inside node_modules dir/@ionic/app-scripts/dist/util/config.js" "node_modules/@ionic/app-scripts/dist/util/config.js"

# edited gitignore to not ignore this config.js, did git add -f.


npm run ionic:build -- --prod

# this takes a lot of RAM and a lot of time. 

# if possible, avoid swapping by closing all apps and clearning memory before doing this.

# REMEMBER to use nvm use 11 if doing this in a fresh terminal

#PID USER      PR  NI    VIRT    RES    SHR S  %CPU %MEM     TIME+ COMMAND 

# 3682 mac       20   0 3142252 2.215g  28948 R 131.5 59.6   6:34.48 node

#   build prod finished in 3128.04 s - this was on a 4GB machine

# on a 16 GB GCP instance, it took up around 1/3rd of memory - 5 GB+ - 

# and finished in just 5 or 6 minutes instead of 50 minutes.


# npx cordova run android

# Could not find an installed version of Gradle either in Android Studio,

#or on your system to install the gradle wrapper. Please include gradle 

#in your path, or install Android Studio

# So, I just imported the platforms/android directory using the Import Gradle project option in Android Studio 4.1.3, and built from there.


/dev/kvm permission denied fix for Android AVD unable to open in Android studio

I did not go through this, since my machine had only 4 GB RAM and was probably too RAM constrained to run the virtual device, but

https://16shuklarahul.medium.com/how-to-fix-kvm-permission-denied-error-on-ubuntu-18-04-16-04-14-04-f04a6e23c0cd

- install qemu-kvm and then give appropriate permission to the current user.

$ sudo apt install qemu-kvm

% add your user to the kvm group.

$ sudo adduser <username> kvm

% And then 

$ sudo chown <username> /dev/kvm

Saturday, April 10, 2021

uninstall apk before installing with different signature

While developing android apps, we find that sometimes the apks we build don't get installed - the package manager just says Not Installed. The reason is apparently that if the same app is currently installed with a different signature, it will not get overwritten. 

Uninstalling via long press on the home screen on my LG Q6 was not sufficient.

To uninstall cleanly, the way was as listed at this page:

Settings - Apps - Select the app to uninstall - Choose Force Stop
Then choose Storage - Clear Cache - Clear Data
Return to the app screen - choose Uninstall

After doing this, the apk will install. If it doesn't there may be something wrong with the signing etc. which will be the subject of another post of mine