MultiLevelMenu

Apr 17, 2018

Downloading & opening a file in an Ionic app

If you search the Internet for examples to achieve the above, you'll get a number of resources that suggest using the cordova-file-transfer plugin. However, with Ionic 3+ and its native API, you can do this directly using a mix of Angular and Ionic File API.

Essentially, you need to use Angular HttpClient to download a URL as a Blob using the responseType option and then save the resultant Blob to a file on the device using the File API. Finally, open the saved file using the Ionic native FileOpener API.

Here's the method in a provider that is used to download files from a website. Note that this.http is an instance of HttpClient.

/**
  * Download a file from the given url.
  *
  * Params:
  *   url - the URL to download the file from
  * Returns:
  *   A promise which upon resolution returns a dictionary 
  *      {
  *        fileEntry: FileEntry,
  *        type: The content-type string for the file
  *      }
  */
  download(url): Promise<{ fileEntry: FileEntry, type: string }> {
    return new Promise((resolve, reject) => {

      // Get the file from the given URL argument. Note responseType is set to 'blob'.
      // It can also be set to 'arraybuffer', but response.body will be of different
      // type and needs to be handled slightly differently.
      this.http.get(url, {
        observe: 'response',
        responseType: 'blob'})
        .subscribe(response => {

          // Create /Download folder if it doesn't exist
          this.file.createDir(this.file.externalRootDirectory, 'Download', true)
            .then(de => {

              // Write the downloaded Blob as the file. Note that the file will
              // be overwritten if it already exists!
              this.file.writeFile(
                de.toURL(),
                fileName, response.body,
                { replace: true })
                .then(fe => {

                  // All went well, resolve the Promise.
                  return resolve({
                    fileEntry: fe,  // FileEntry instance
                    type: response.body.type  // Content-Type for the file
                  });

                }).catch(err => {

                  // writeFile failed 
                  reject(err);

                })
            }).catch(err => {

              // createDir failed 
              reject(err);

            })
        }, err => {

          // Download failed 
          reject(err);

        });
    });
  }

One of the key points here is that the responseType: 'blob' allows us to retrieve the file type as determined by the server which we can use as argument to the FileOpener API to open the file.

The argument observe: 'response' is not really necessary and can be left out. In this case, the subscribe handler will get the blob object as its argument instead of the higher level HTTP response object.

The call to File.createDir() ensures that the Download folder is created and available before the downloaded content is saved into it.

Finally the call to File.writeFile() saves the file and the function returns the FileEntry return value from this call.

This being a method in a service, it only downloads the file saving it to the device storage. How the saved file is to be processed is left to the page that initiated the download request to decide.

If the page wants to open the file, it can done like this:

this.fileOpener.open(attachment.fileEntry.nativeURL, attachment.type)
  .then(() => {
    // nothing to do actually
  }).catch(e => {
    // show alert message that file open failed.
  });

Important thing to note here is that we're supplying the FileEntry.nativeURL as the first argument to the open() method. Also, the Blob.type that the server returned to us through HttpClient.get() can be used as second argument to help the device determine the most appropriate application to open the file with.

If there are no applications associated with the given file type, open() would result in an error, which should be caught and reflected to the user with an appropriate message.

Cordova build errors


Had this unusual errors crop up when building a cordova app for android. This started appearing after I installed cordova-plugin-file-opener2.

ERROR: In <declare-styleable> FontFamilyFont, unable to find attribute android:fontVariationSettings
ERROR: In <declare-styleable> FontFamilyFont, unable to find attribute android:ttcIndex

Going through platforms/android/project.properties file, I realized that there was something quite odd about it.

cordova.system.library.1=com.android.support:support-v13:26.+
cordova.system.library.2=me.leolin:ShortcutBadger:1.1.17@aar
cordova.system.library.3=com.google.firebase:firebase-messaging:11.0.1
cordova.system.library.4=com.android.support:support-v4:27.1.0
cordova.system.library.5=com.android.support:support-v4:24.1.1+
cordova.system.library.6=com.android.support:support-v4:+

There were multiple versions of the same library being defined.

I removed the last three lines -- library.4, library.5 & library.6, ensuring that the the highest version of com.android.support:support library is linked. (At this stage I'm not sure what's the impact of project.properties and its contents. But I'm assuming it's being used by the build system to do something similar to linking in the traditional C build system).

Anyway voila the problems disappeared!

Jan 17, 2018

Celery on Windows

Running celery on Windows, shows the following error when tasks are queued to be executed:
ValueError: not enough values to unpack (expected 3, got 0)

The way to fix this is to add --pool=solo to the celery daemon command line. This makes celery run as a solo process and without concurrency, which for development purposes shouldn't be a big deal.

So the entire daemon command would look something like this:
python -m celery -A config worker --pool=solo -l info

Taken from this Github thread.

Oct 2, 2017

Steps to Install Redis server

Few steps necessary to build and install Redis on a server. There are no pre-built packages available for you to apt-get install it. So you have to grab the source, compile and install the generated binaries. Fortunately it all goes pretty smooth.
$ wget http://download.redis.io/releases/redis-4.0.2.tar.gz
$ tar xzf redis-4.0.2.tar.gz
$ cd redis-4.0.2
$ make
$ make test

# install compiled binaries to /ur/lib/bin
$ sudo make install

# setup redis-server as daemon
$ cd utils
$ sudo ./install_server.sh

# setup redis-server to autostart
$ sudo update-rc.d redis_6379 defaults

That ought to do it!

Sep 14, 2017

Volvo Door Mirror Mismatched Folding

One of the problems that I encountered with the S80 is that when I unlocked the car and pressed the button to unfold the door mirrors, they would be out of sync with each other. That is one would open and at the same time the other would close. You can open the closed mirror manually, but when you press the button to fold then mirrors, one would close and the the other would remain open.

After some research online, I figured out the problem -- the mirror's folded/open position is stored in memory and the car uses this position to instruct the mirror motors on what action to perform (fold/unfold) when you press the button to unfold the mirrors again. Now if you're in a hurry and happen to switch off the vehicle before the mirror folding operation could be completed, storing of the mirror position in memory cannot be completed accurately.

The solution to the problem turns out to be very simple.
  1. Turn off the car.
  2. Locate the fuse box in the passenger compartment. Locate fuses for left and right door power windows and mirrors. For 2004 S80 these are fuses 35 & 36 respectively. Here is the list of fuses in this fuse box. It looks like this:
     
  3. Remove the left door fuse (fuse number 35).
  4. Manually push the left door mirror position to be in sync with the right door mirror position.
  5. Start the car.
  6. Turn off the car
  7. Replace the fuse you removed in step 2.
  8. Start the car again and now press the door mirror button. If it was the left mirror whose position was wrongly recorded in memory, everything should be fine.
  9. If the mirrors are still out of sync with each other, repeat steps 2-7 with right door fuse (fuse number 36) and manually bringing the right door mirror to be in sync with the left door mirror position.
This technique should work for all Volvo cars with electric door mirrors. Of course you do need consult your vehicle's manual for the right fuse to remove.




How to Integrate Sphinx with a Django Project for Automatic Documentation Generation

A few points on integrating Sphinx with a Django redistributable project for generating documentation embedded in the source files.
  1. Project directory structure should look like this:
    django-redistributable/
      demo/
        demo/
          settings.py
          urls.py
          wsgi.py
        templates/
        manage.py
      docs/
        conf.py
        index.rst
        Makefile
        make.bat
      redistributable/
        models.py
        views.py
        templates/
        static/
      tests/
      setup.py
      MANIFEST.in
      README.rst
      LICENSE
      requirements.txt
    
  2. Install sphinx and sphinx-autobuild using pip.
  3. Run sphinx-quickstart from the docs folder. Provide default answers for all prompts.
  4. Edit docs/conf.py such that the redistributable package and the demo project's apps are importable from the docs folder. At the top of the conf.py file:
  5. import os
    import sys
    import django
    
    sys.path.insert(0, os.path.dirname(os.path.dirname(__file__)))
    sys.path.append(os.path.join(os.path.dirname(os.path.dirname(__file__)), 'demo'))
    
  6. Setup sphinx such that it can safely import the Django source file referred to in any RST files for autodoc. For this you need to setup the DJANGO_SETTINGS_MODULE environment variable to demo project's settings. Also need to initialize Django's AppRegistry with a call to django.setup().
  7. os.environ['DJANGO_SETTINGS_MODULE'] = 'demo.settings'
    import django
    django.setup()
    
  8. Embed the package version in the redistributable folder's __init__.py file as __version__ = "x.y" and then import this version into conf.py. This will make the package version and documentation version concurrent.
  9. import redistributable
    
    version = redistributable.__version__
    release = redistributable.__version__
    
  10. Build the documentation issuing make html from the docs folder.