diff --git a/.nojekyll b/.nojekyll new file mode 100644 index 0000000..e69de29 diff --git a/2009/08/11/aao-a150l-step-by-step-installation-process-for-linux4one.html b/2009/08/11/aao-a150l-step-by-step-installation-process-for-linux4one.html new file mode 100644 index 0000000..681870a --- /dev/null +++ b/2009/08/11/aao-a150l-step-by-step-installation-process-for-linux4one.html @@ -0,0 +1,1846 @@ + + + + + + + + + + + + + + + + + + + + + + + + + AAO A150L Step by Step Installation Process for Linux4one - Make Gadgets Work + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + Skip to content + + +
+
+ +
+ + + + +
+ + +
+ +
+ + + + + + + + + +
+
+ + + + + + + + + + + + + + + +
+
+
+
+ + +
+
+
+
+ + + + + + + + + + + + + + + + + + + + +

AAO A150L Step by Step Installation Process for Linux4one

+ +

Linux4one is the only Linux distro other than pre-installed Linpus that worked out of the box on my and two other Acer aspire one A150L which belonged to my friends. With the GUI same as that for Ubuntu Netbook Remix, it is a fantastic OS to have on your machine. The only downside I felt was that the icons looked little washed out compared to those on actual UNR but that is not so much of an issue. Everything pretty much worked out of the box which matters the most. I did find the splash screen at the time of start-up a little childish but again these are not major issues.

+ + +

However if you would rather have a more professional look and feel, Ubuntu may be your choice. Refer to other post on step by step process for installing Ubuntu Netbook Remix but be forewarned that not everything works as well on UNR as of now, at least not for me. I am still working and finding out more on how these can work but given the fact that my knowledge of Linux is zero it can take some time. I have compiled these tutorials solely by hunting on net and felt this will be my way of giving back to community. I hope you find it useful. If you do find this useful, please don’t forget to put a comment. A thanks will be enough to keep me motivated.

+

Without further ado, the process is as below.

+

You will need:

+
    +
  • Two 2GB card with a USB Card Reader or a 2GB USB stick formatted to FAT32
  • +
  • One 1GB card with a USB Card Reader or a 1GB USB stick formatted to FAT32
  • +
  • One PC / Laptop with optical drive (DVD Drive) and windows installed as this tutorial uses a Windows based laptop.
  • +
  • Ensure that you AAO is having a constant AC supply and you are not relying on battery.
  • +
  • Lot of patience
  • +
+

1. Create a recovery USB for Acer Aspire One A150L: (Optional)

+

This should be quite straight forward. Insert the recovery DVD that came with your Acer Aspire One A150. Connect the 2GB Card / USB stick and start the process.

+

On different laptops this may mean pressing a different function key (F1 to F12) but generally it will be F2, F8 or F12. +So once your laptop shows the first screen after switching on, at that time press the relevant key to enter the boot set-up, now goto boot and change the order of boot to select DVD as first preference. Now press F10 to save and exit. +Your laptop will now boot using the recovery DVD and should help you create the recovery USB.

+

It is best to once test if the recovery USB is working or not. You can do so by now plugging your recovery DVD into AAO and starting it, press F12 and change the boot order to make USB disk the first option. If the recovery USB is created properly it will start the recovery process. +If this does not happen, you are among the unfortunate few who received a faulty DVD as I have seen many posts around these. In such situations, you will find this blogpost very handy:

+ +
+

Tip

+

macles.blogspot.com has a wealth of information on how to customise Linpus Linux and everything on this site is explained very nicely so incase you want to stick to Linpus Linus, it is worth to bookmark this site.

+
+

Anyway, now one way or the other once your recovery USB is ready, we have minimised the risk of not being able to restore to factory conditions in case you do not like the end result of what we have set out to achieve.

+

2. Update BIOS Version to latest V.3309 (Optional)

+

You will be able to download the latest BIOS V.3309 from ACER website. The ftp link to the site is ftp://ftp.acer-euro.com/netbook/aspire_one_110/bios/ and http link is http://support.acer-euro.com/drivers/notebook/as_one_150.html +Once you have downloaded the latest version, you will need to make a bootable USB. I used Unetbootin which will be used later on for creating installatioon USB for Linux4one as well so you will need to now download it from here. +After downloading, connect your 1 GB Card / USB stick and run the unetbootin-windows-357.exe file and select the screen as shown below: +With this setup achieved, click OK. Once this is completed, extract all the files from BIOS zip file into the root of USB Drive. Remember it is important to have it in Root. +Now connect this USB drive to your Acer Aspire One, reboot it and press F12 to select the bootable device.System will show an error message around missing CD ROm. Ignore it. The AAO150 screen will now show A prompt A>. +Type C: in front of A> and press enter. This should change your drive to C>. Now type 3309.BAT. +This should start the flashing of BIOS to V.3309. Once completed AAO150 will reboot. +This is it. Your BIOS should now be updated to latest version.

+

3. Create a installation USB for Linux4one v1.1:

+

While the latest version of Linux4one is 1.5 and is available for download I was unable to make a successful installation USB from available iso. Therefore, I downloaded the V1.1 from this link:

+

http://downloads.quellicheilpc.com/linux4one/1.1/index.html +I don't know how long this version will be hosted there so I will at a later point in time try and upload this iso to some other filehost as well. +Once you have downloaded the iso, plug your 2GB card / USB and start Unetbootin. +Select the Disk image radio button as shown in figure and select the path for linux4one iso image that you have downloaded.Select the USB Drive and click OK. +This will take some time but once done it will ask for a restart. Do NOT restart and cancel it.

+

Your installation USB is now ready.

+

4. Install Linux4one

+

Official Guide +Now plug the Installation USB made above and plug it into your AAO150 and boot it. Press F12 and select the boot device. +Once the system starts booting throgh USB you will be presented with the options, select Option 'Install Linux4One'. +Now the only place which turns out confusing could be when during installation it asks for partitioning. I did not want dual boot so I picked up option 2 which installs on whole hard drive.If you are not a power user, I don't recommend having dual boot for AAO150 but that has to be your call. +Right, this will ensure that your Linux4one V1.1 is now installed. At this stage, you will reboot, configure location keyboard etc etc and restart once again.

+

Check that all is working as expected so far. Only thing that I found not responding well was microphone. It was not working for Skype and hence the next step.

+

5. Download update script for Linux4one v1.5

+

Official Guide +Download the script from here onto your AAO150L using mozilla browser. +Now goto Accessories and click on 'Terminal'. As mozilla downloads directly to 'Desktop' directory you will need to change your directore accordingly. The easiest way will be to click on the 'Desktop' link in right hand side navigator on your main screen. This will open the browser, now note the path to Desktop. In my case it was /home/Ankit/Desktop/ so I gave the following commands:

+
1
+2
+3
+4
cd /home/Ankit/Desktop/
+tar -xjvf linux4one-1.5.tar.bz2
+cd linux4one-1.5
+sudo ./aggiornamento.sh
+
+

After this it will start installing and ask several questions that you just type Y and press enter. +At one point it will also ask do you want to uninstall mozilla and you answer Y as anyway we will install it again in next step. +Right now with V1.5 the browser provided is IceCat which is same as mozilla anyway. +This will take about good 15 to 20 minutes but at the end you would have got latest version of Linux4one installed.

+

6. Install Mozilla (Optional):

+

With Linux4one installed, more than half of the work is already done but with last step mozilla was uninstalled and if you are used to Mozilla and would want to install it back it is quite straight forward. Just goto Administration and select Synaptic Package Manager. It will then ask you to enter your password to perform administrative tasks. You must now enter the password that you gave during installation. +It will now open a window. In Search field enter 'mozilla' and it will show several options listed. If you scroll you will find an entry 'firefox' under package. When you select that you can read the description that will be something like 'Safe and easy web browser from Mozilla'. Select the checkbox next to this entry and click 'Mark for Installation'. It will show you dependencies, just say yes to everything and then it will download and install Mozilla Browser for you.

+

7. Install and configure Skype

+

Goto www.skype.com and download the skype version for Ubuntu 7.04 - 8.04. Mozilla browser will download it to Desktop folder. Now once the download is complete right click on the downloaded file and click on 'Open with 'GDebi Package Installer'. You will be asked for password, enter the password you gave during installation of Linux4one. After that Skype installation will begin. There will be some dialogue boxes on dependencies for which you just click Yes / OK. +Once skype is installed it will appear as an icon in your desktop under Internet. If you want it under favourite, right click the icon and select add to favourite. This can be done for all installed softwares.

+

Now open skype, login and at left side bottom corner click on skype settings, select Options. Now in Options select 'Video Devices' and click on Test to check that Webcam is set correct. +Next click on 'Sound Devices' and click on Make a sound Test and Make a test call. If all is configured fine you should hear a voice and a test call will be made and in test call you will be asked to speak and what you speak will be played back that you should be able to hear.

+

It did not work for me at first but when I selected HDA Intel (hw:Intel,0) in all three it worked. However for my friends netbook I had to do more circus and if you are not able to resolve your mic issues, it is time to move to next step.

+

8. Modify Mic settings

+
+

Tip" "Remember

+

You need to do this only if in previous step mic did not work.

+
+

Official Guide

+

While there is some explanation on Official guide it took me some time to figure out how to get those windows to look like they do in the screenshots there.

+

First goto Preferences and scroll down. You will see 'Sound' icon. Click on this and change 'Sound Playback' and 'Sound Capture' to OSS. +Next change the 'Device' to 'Realtek ALC268 (OSS Mixer)

+

Now close this window and click on Volume icon in right hand corner of screen and then click on Edit and select device. Then for HDA Intel (ALSA Mixer) select all options that are not selected - Capture, Capture 1, Beep, Input, Input 1. I have borrowed the image from link above to keep information at one place. +Ensure that on last tab you select 'Mic' and not 'Front Mic'. That is it. This should complete the mic settings. Now we need to check it in Skype. In official tutorial they suggest testing with recorder but that did not work for my friends AAO150 but skype did. I figure we anyway need it for skype so why bother with voice recorder at all. So over to next step then.

+

9. Test Skype

+

Test skype the same way we did in step 7 and see if it is working. Click on 'Sound Devices' and click on Make a sound Test and Make a test call. If all is configured fine you should hear a voice and a test call will be made and in test call you will be asked to speak and what you speak will be played back that you should be able to hear. +If this does not work try different combinations from the dropdown one should definitely work.

+

10. Extra – Install Moneydance (Optional)

+

OK now is one piece of software that is not used by everyone. Moneydance is a moneymanagement tool. It is a paid software that I use for managing my finances and have grown quite fond of it and dependent on it at the same time and I really wanted this installed on my netbook and it was one of the not so easy things to install but with following instructions it should be piece of cake really.

+
    +
  1. Download linux version of Moneydance from this here. Mozilla will download it into your Desktop folder. + +([http://www.moneydance.com/download/2008/installers/Moneydance_linux_x86.tar.gz](http://www.moneydance.com/download/2008/installers/Moneydance_linux_x86.tar.gz)) +
  2. Now goto Accessories -> Terminal. +
  3. Type cd /home/YourUserName/Desktop/ and press enter +
  4. Type tar xfvz Moneydance_linux_x86.tar.gz and press enter +
  5. It will print following kind of stuff: +
+ +
 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
Moneydance/
+Moneydance/.install4j/
+Moneydance/.install4j/i4jruntime.jar
+Moneydance/Moneydance
+Moneydance/.install4j/firstrun
+Moneydance/.install4j/i4jparams.conf
+Moneydance/.install4j/MessagesDefault
+Moneydance/.install4j/user.jar
+Moneydance/appsrc.jar
+Moneydance/jcommon-1.0.12.jar
+Moneydance/jfreechart-1.0.9.jar
+Moneydance/license.txt
+Moneydance/moneydance.jar
+Moneydance/moneydance_icon32.png
+
+
    +
  1. Now it will present the command prompt again. Type cd Moneydance +
  2. Now Type `./Moneydance` +
+

It will display testing JVM in /usr and then open the Monedance software. +This is it software is now installed. +Unfortunately, this does not create any icon that you can directly click every-time and can only be run by clicking on Moneydance.sh file in Moneydance folder. So to make it easy do the following.

+
    +
  1. Goto Prefrences -> Main Menu -> Favourites. +
  2. Now click on 'New Item'. It will open a dialogue box 4 fields. In second field Enter 'Moneydance'. +
  3. In third field click on Browse and navigate to Desktop->Moneydance->Moneydance.sh +
  4. If you want to change the icon click on icon and select the one that you want. +
  5. Click OK. +
+ +

This is it you are all done. +Ensure that now you restart your Acer Aspire One 150 atleast three times before you start using as I noticed that it was only after three or four restarts that everything was well settled.

+

11. That's it friends....Now Enjoy.

+

Please let me know if you are stuck somewhere and I might be able to help.

+ + + + + + + + + + + + + + + + + + + + + + +
+
+ + + + + +
+ + + +
+ + + +
+
+
+
+ + + + + + + + + + + + \ No newline at end of file diff --git a/2009/08/13/get-gmail-as-push-email-on-sony-p990i.html b/2009/08/13/get-gmail-as-push-email-on-sony-p990i.html new file mode 100644 index 0000000..9cc8d53 --- /dev/null +++ b/2009/08/13/get-gmail-as-push-email-on-sony-p990i.html @@ -0,0 +1,1693 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + Get Gmail as Push Email on Sony P990i - Make Gadgets Work + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+
+ +
+ + + + +
+ + +
+ +
+ + + + + + + + + +
+
+ + + + + + + + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+
+ + +
+
+
+
+ + + + + + + + + + + + + + + + + + + + +

Get Gmail as Push Email on Sony P990i

+ +

While I have moved on from P990i which is now in safe hands of my dear wife, I remember doing some good amount of web searching and still had no idea how to set up push-mail on P990i.

+

I then thought I will configure a mail anyway and to my surprise the device is capable of enabling push-mail on it`s own as I found while fiddling along. Anyway I have configured push-mail for wifey dear and she was mighty impressed. 😄

+

The steps I followed are as below:

+ + +
    +
  • +

    Main Menu -> Tools -> Control Panel -> Messaging -> Email Accounts -> New

    +
  • +
  • +

    Fill Account Name as Gmail

    +
  • +
  • +

    Your Name as ..well... your name.. 😄

    +
  • +
  • +

    Email Address - Your gmail address

    +
  • +
  • +

    In Connection Type select IMAP from the drop down list.

    +
  • +
  • +

    Tick on Push email

    +
  • +
  • +

    Now goto Inbox Tab.

    +
  • +
  • +

    Type imap.gmail.com under Incoming Server Address

    +
  • +
  • +

    Type your email address xyz@gmail.com under Username

    +
  • +
  • +

    Enter your password under Password

    +
  • +
  • +

    Depending on you data plan you might want to put something else but I have unlimited internet plan so I have selected No Restriction under Download Restrictions

    +
  • +
  • +

    Limit number of Emails - I have selected 100, you can chose as per your needs.

    +
  • +
  • +

    Receive using Group - Select the group of internet account that has your phone provider configured. (I am assuming that you have already configured internet settings on your phone. If not that is a separate topic but you can easily find information on user manual so at the moment let`s consider it as out of scope for this entry.)

    +
  • +
  • +

    Now goto Outbox Tab.

    +
  • +
  • +

    Type smtp.gmail.com under Outgoing Server Address

    +
  • +
  • +

    Tick the checkbox Use SMTP Authentication

    +
  • +
  • +

    Tick the checkbox Use Inbox Login details

    +
  • +
  • +

    Select the same internet group as above under Send using Group

    +
  • +
  • +

    Now click on the arrow next to Email account on top of the screen and click on Advanced from the dropdown list

    +
  • +
  • +

    Under Incoming tab Select SSL under the field Secure connection

    +
  • +
  • +

    Type 993 under field Incoming mail port

    +
  • +
  • +

    Next in Outgoing tab under Secure Connection filed select TLS

    +
  • +
  • +

    Type 587 under field Outgoing mail port

    +
  • +
  • +

    Tick the checkbox Use MIME encoding

    +
  • +
  • +

    Click on Save

    +
  • +
  • +

    Click on Save again

    +
  • +
  • +

    Now the screen will show Gmail listed as your email account.

    +
  • +
  • +

    On this screen again click on the arrow next to Email account on top of the screen and select Always on Pushmail.

    +
  • +
  • +

    Tick the checkbox next to Always On

    +
  • +
  • +

    Select the timings and select the phone providers datasetting name under Internet Account and click Save.

    +
  • +
  • +

    This is it. You are now all set to receive your mails on the go...

    +
  • +
+

Have fun !!!

+ + + + + + + + + + + + + + + + + + + + + + +
+
+ + + + + +
+ + + +
+ + + +
+
+
+
+ + + + + + + + + + + + \ No newline at end of file diff --git a/2009/09/15/sony-vaio-fe21h-webcam-on-skype.html b/2009/09/15/sony-vaio-fe21h-webcam-on-skype.html new file mode 100644 index 0000000..f1bd352 --- /dev/null +++ b/2009/09/15/sony-vaio-fe21h-webcam-on-skype.html @@ -0,0 +1,1611 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + Sony VAIO FE21H Webcam on skype - Make Gadgets Work + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+
+ +
+ + + + +
+ + +
+ +
+ + + + + + + + + +
+
+ + + + + + + + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+
+ + +
+
+
+
+ + + + + + + + + + + + + + + + + + + + +

Sony VAIO FE21H Webcam on skype

+ +

Alright friends not a huge tip but still I, being new fan of Ubuntu, installed it on my media laptop...Sony VAIO...

+

One might ask what is media laptop...well I had a bit of problem with my Sony VAIO some 1.5 years back and a key got stuck and as it goes with any Sony product there was no cheap solution at hand...

+

At that time for some reason it appeared like a motherboard issue and I just bought another Dell laptop...later I realised that it was a keyboard issue so I invested in a wireless keyboard and had two lappies at home just like that 😄... I figured that with wireless keyboard VAIO laptop will make perfect companion to my 32' Samsung and hence it got the name of media laptop.

+ + +

I use this laptop to browse without fear as I will never ever do any important work on it so am never worried of loosing..not that I ended up having any virus at all....and now with Linux I doubt if there will ever be any virus on this laptop....but anyway I have gone on a tangent...

+

What I started to tell was a tip on how we can make VAIO's inbuilt motion-eye webcam to behave and the tip goes as below... +Incase your webcam is not giving good images on ur Sony VAIO, you may want to try this:

+

1.Goto System->Preferences->Main Menu

+

1.In Main Menu window click on Internet in left hand navigation under 'Menu' and then click on Skype' in right hand under 'Items'.

+

1.Now click on 'Properties' button.

+

1.It will open a Launcher Properties dialogue box.

+

1.In third field 'Command' replace 'Skype' with this line: +

bash -c 'LD_PRELOAD=/usr/lib/libv4l/v4l1compat.so skype'
+

+

That is it. You should now have a properly working motion-eye with skype. +There is more info on this link

+

Ciao !!!

+ + + + + + + + + + + + + + + + + + + + + + +
+
+ + + + + +
+ + + +
+ + + +
+
+
+
+ + + + + + + + + + + + \ No newline at end of file diff --git a/2009/09/16/sony-vaio-n-vidia-setup-to-make-s-video-work.html b/2009/09/16/sony-vaio-n-vidia-setup-to-make-s-video-work.html new file mode 100644 index 0000000..6f4e74a --- /dev/null +++ b/2009/09/16/sony-vaio-n-vidia-setup-to-make-s-video-work.html @@ -0,0 +1,2201 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + Sony VAIO N-VIDIA setup to make S-Video work - Make Gadgets Work + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + Skip to content + + +
+
+ +
+ + + + +
+ + +
+ +
+ + + + + + + + + +
+
+ + + + + + + + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+
+ + +
+
+
+
+ + + + + + + + + + + + + + + + + + + + +

Sony VAIO N-VIDIA setup to make S-Video work

+ +
+

UPDATE for 9.10

+

For Ubuntu 9.10 I was just not able to get it work and finally I figured it out. The problem is with the latest Nvidia driver, the one that ubuntu says recommended. In order to make your TV Out work, you must uninstall the latest release version and install the one just lower than that. (The working version for me was 173.) Then follow these steps: +

1
+2
+3
sudo nvidia-xconfig
+sudo reboot
+sudo nvidia-settings
+
+Then follow the steps shown below. If you are lucky you wont need to refer Section A of this post at all. I did not have to do that.

+
+ + +

Background

+

S-Video did not work out of the box on ubuntu Sony Vaio combination but it was not difficult to have it sorted once the following steps were taken.

+

I am giving the steps to set-up TV-Out assuming your n-vidia file is working fine, if not follow steps in Section A before completing steps below.

+
    +
  • +

    Step 1: Connect laptop and TV through S-Video Cable.

    +
  • +
  • +

    Step 2. System->Administration->Nvidia X-Server Settings

    +
  • +
  • +

    Step 3: Click on X-Server Display Configuration and then click on 'Detect Displays'. If you have connected fine, the screen should be something like this:

    +

    image

    +
  • +
  • +

    Step 4: Click on the TV icon, following screen will appear.

    +

    image

    +
  • +
  • +

    Step 5: Click on 'Configure' and then select 'Twinview'

    +

    image

    +
  • +
  • +

    Step 6: Now I found that keeping TV on left ensured that while using dailymotion full screen it will come on TV but keeping on right it came on laptop monitor so you may want to depending on your preference change the location to left as shown below.

    +

    image

    +
  • +
+

This is it.You should now be able to watch streaming video on your TV. 😄

+

Section A

+

I found the most useful instructions here, but I did have to make few tweaks.

+

Right, I will walk step by step based on the guide from the source mentioned above and mention the changes / tweaks I did to make it work on my laptop. I am sure it should work with same conf on other VAIO as well.

+
    +
  • +

    Step 1: Aplication-> Accessories-> Terminal

    +
  • +
  • +

    Step 2: Type sudo cp /etc/X11/xorg.conf /etc/X11/xorg.conf.backup

    +
  • +
  • +

    Step 3: You will need to enter password at this stage. Enter the password.

    +
  • +
  • +

    Step 4: Now Type: sudo gedit /etc/X11/xorg.conf

    +
  • +
+

Difference in old and new xorg.conf files:

+

I used the following command on terminal to compare the old and new .conf files:

+

diff -u /etc/X11/xorg.conf.backup /etc/X11/xorg.conf

+

The numbers between @@ represent Row number and column number. - stands for old and + for new.

+

/etc/X11/xorg.conf
 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
+26
+27
+28
+29
+30
@@ -61,9 +61,9 @@
+Section "Monitor"
+Identifier "Monitor0"
+VendorName "Unknown"
+- ModelName "TV-0"
+- HorizSync 28.0 - 55.0
+- VertRefresh 43.0 - 72.0
++ ModelName "Nvidia Default Flat Panel"
++ HorizSync 29.0 - 49.0
++ VertRefresh 0.0 - 60.0
+EndSection
+
+Section "Monitor"
+@@ -110,12 +110,14 @@
+
+## Removed Option "TwinView" "0"
+## Removed Option "metamodes" "DFP: nvidia-auto-select +0+0"
++## Removed Option "TwinView" "1"
++## Removed Option "metamodes" "TV: nvidia-auto-select +0+0, DFP: nvidia-auto-select +1024+0"
+Identifier "Screen0"
+Device "Device0"
+Monitor "Monitor0"
+DefaultDepth 24
+- Option "TwinView" "1"
+- Option "metamodes" "TV: nvidia-auto-select +0+0, DFP: nvidia-auto-select +1024+0"
++ Option "TwinView" "0"
++ Option "metamodes" "DFP: nvidia-auto-select +0+0"
+SubSection "Display"
+Depth 24
+EndSubSection
+
+Copy of my xorg.conf and xorg.conf.backup

+
xorg.conf - CURRENT - WORKING VERSION
  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
+ 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
+ 52
+ 53
+ 54
+ 55
+ 56
+ 57
+ 58
+ 59
+ 60
+ 61
+ 62
+ 63
+ 64
+ 65
+ 66
+ 67
+ 68
+ 69
+ 70
+ 71
+ 72
+ 73
+ 74
+ 75
+ 76
+ 77
+ 78
+ 79
+ 80
+ 81
+ 82
+ 83
+ 84
+ 85
+ 86
+ 87
+ 88
+ 89
+ 90
+ 91
+ 92
+ 93
+ 94
+ 95
+ 96
+ 97
+ 98
+ 99
+100
+101
+102
+103
+104
+105
+106
+107
+108
+109
+110
+111
+112
+113
+114
+115
+116
+117
+118
+119
+120
+121
+122
+123
+124
+125
+126
+127
+128
+129
+130
+131
+132
+133
+134
+135
+136
+137
+138
+139
+140
+141
+142
+143
+144
+145
+146
+147
+148
+149
+150
+151
+152
+153
+154
+155
+156
+157
+158
+159
+160
+161
+162
+163
+164
+165
+166
+167
+168
+169
+170
+171
+172
+173
+174
+175
+176
+177
+178
+179
+180
+181
+182
+183
+184
+185
+186
+187
+188
+189
+190
+191
+192
+193
+194
+195
+196
+197
+198
+199
+200
+201
+202
+203
+204
+205
+206
+207
+208
+209
+210
+211
+212
+213
+214
+215
+216
+217
+218
+219
+220
+221
+222
+223
+224
+225
+226
+227
Section "ServerLayout"
+Identifier "Default Layout"
+Screen 0 "Screen0" 0 0
+InputDevice "Keyboard0" "CoreKeyboard"
+InputDevice "Mouse0" "CorePointer"
+EndSection
+
+Section "Module"
+Load "glx"
+EndSection
+
+Section "ServerFlags"
+Option "Xinerama" "0"
+EndSection
+
+Section "InputDevice"
+
+## generated from default
+Identifier "Keyboard0"
+Driver "kbd"
+EndSection
+
+Section "InputDevice"
+
+## generated from default
+Identifier "Mouse0"
+Driver "mouse"
+Option "Protocol" "auto"
+Option "Device" "/dev/psaux"
+Option "Emulate3Buttons" "no"
+Option "ZAxisMapping" "4 5"
+EndSection
+
+Section "Monitor"
+Identifier "Configured Monitor"
+EndSection
+
+Section "Monitor"
+Identifier "Monitor0"
+VendorName "Unknown"
+ModelName "Nvidia Default Flat Panel"
+HorizSync 29.0 - 49.0
+VertRefresh 0.0 - 60.0
+EndSection
+
+Section "Monitor"
+Identifier "Monitor1"
+VendorName "Unknown"
+ModelName "TV-0"
+HorizSync 28.0 - 55.0
+VertRefresh 43.0 - 72.0
+EndSection
+
+Section "Device"
+Identifier "Configured Video Device"
+Driver "nvidia"
+Option "NoLogo" "True"
+EndSection
+
+Section "Device"
+Identifier "Device0"
+Driver "nvidia"
+VendorName "NVIDIA Corporation"
+BoardName "GeForce Go 7400"
+EndSection
+
+Section "Device"
+Identifier "Device1"
+Driver "nvidia"
+VendorName "NVIDIA Corporation"
+BoardName "GeForce Go 7400"
+Option "TVOutFormat" "SVIDEO"
+Option "TVStandard" "PAL-G"
+Option "ConnectedMonitor" "Monitor[1]"
+BusID "PCI:1:0:0"
+Screen 1
+EndSection
+
+Section "Screen"
+Identifier "Default Screen"
+Device "Configured Video Device"
+Monitor "Configured Monitor"
+DefaultDepth 24
+EndSection
+
+Section "Screen"
+
+## Removed Option "TwinView" "0"
+## Removed Option "metamodes" "DFP: nvidia-auto-select +0+0"
+## Removed Option "TwinView" "1"
+## Removed Option "metamodes" "TV: nvidia-auto-select +0+0, DFP: nvidia-auto-select +1024+0"
+Identifier "Screen0"
+Device "Device0"
+Monitor "Monitor0"
+DefaultDepth 24
+Option "TwinView" "0"
+Option "metamodes" "DFP: nvidia-auto-select +0+0"
+SubSection "Display"
+Depth 24
+EndSubSection
+EndSection
+
+Section "Screen"
+Identifier "Screen1"
+Device "Device1"
+Monitor "Monitor1"
+DefaultDepth 24
+Option "TwinView" "0"
+Option "metamodes" "TV: nvidia-auto-select +0+0"
+SubSection "Display"
+Depth 24
+EndSubSection
+EndSection
+</pre>
+xorg.conf.backup - OLD COPY
+<pre>
+Section "ServerLayout"
+Identifier "Default Layout"
+Screen 0 "Screen0" 0 0
+InputDevice "Keyboard0" "CoreKeyboard"
+InputDevice "Mouse0" "CorePointer"
+EndSection
+
+Section "Module"
+Load "glx"
+EndSection
+
+Section "ServerFlags"
+Option "Xinerama" "0"
+EndSection
+
+Section "InputDevice"
+
+## generated from default
+Identifier "Keyboard0"
+Driver "kbd"
+EndSection
+
+Section "InputDevice"
+
+## generated from default
+Identifier "Mouse0"
+Driver "mouse"
+Option "Protocol" "auto"
+Option "Device" "/dev/psaux"
+Option "Emulate3Buttons" "no"
+Option "ZAxisMapping" "4 5"
+EndSection
+
+Section "Monitor"
+Identifier "Configured Monitor"
+EndSection
+
+Section "Monitor"
+Identifier "Monitor0"
+VendorName "Unknown"
+ModelName "TV-0"
+HorizSync 28.0 - 55.0
+VertRefresh 43.0 - 72.0
+EndSection
+
+Section "Monitor"
+Identifier "Monitor1"
+VendorName "Unknown"
+ModelName "TV-0"
+HorizSync 28.0 - 55.0
+VertRefresh 43.0 - 72.0
+EndSection
+
+Section "Device"
+Identifier "Configured Video Device"
+Driver "nvidia"
+Option "NoLogo" "True"
+EndSection
+
+Section "Device"
+Identifier "Device0"
+Driver "nvidia"
+VendorName "NVIDIA Corporation"
+BoardName "GeForce Go 7400"
+EndSection
+
+Section "Device"
+Identifier "Device1"
+Driver "nvidia"
+VendorName "NVIDIA Corporation"
+BoardName "GeForce Go 7400"
+Option "TVOutFormat" "SVIDEO"
+Option "TVStandard" "PAL-G"
+Option "ConnectedMonitor" "Monitor[1]"
+BusID "PCI:1:0:0"
+Screen 1
+EndSection
+
+Section "Screen"
+Identifier "Default Screen"
+Device "Configured Video Device"
+Monitor "Configured Monitor"
+DefaultDepth 24
+EndSection
+
+Section "Screen"
+
+## Removed Option "TwinView" "0"
+## Removed Option "metamodes" "DFP: nvidia-auto-select +0+0"
+Identifier "Screen0"
+Device "Device0"
+Monitor "Monitor0"
+DefaultDepth 24
+Option "TwinView" "1"
+Option "metamodes" "TV: nvidia-auto-select +0+0, DFP: nvidia-auto-select +1024+0"
+SubSection "Display"
+Depth 24
+EndSubSection
+EndSection
+
+Section "Screen"
+Identifier "Screen1"
+Device "Device1"
+Monitor "Monitor1"
+DefaultDepth 24
+Option "TwinView" "0"
+Option "metamodes" "TV: nvidia-auto-select +0+0"
+SubSection "Display"
+Depth 24
+EndSubSection
+EndSection
+
+ + + + + + + + + + + + + + + + + + + + + + +
+
+ + + + + +
+ + + +
+ + + +
+
+
+
+ + + + + + + + + + + + \ No newline at end of file diff --git a/2009/10/07/o2-xda-serra-official-htc-upgrade-with-tf3d-of-touch-pro-2.html b/2009/10/07/o2-xda-serra-official-htc-upgrade-with-tf3d-of-touch-pro-2.html new file mode 100644 index 0000000..d74ca2c --- /dev/null +++ b/2009/10/07/o2-xda-serra-official-htc-upgrade-with-tf3d-of-touch-pro-2.html @@ -0,0 +1,1875 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + O2 XDA Serra - Official HTC upgrade with TF3D of Touch Pro 2 - Make Gadgets Work + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + Skip to content + + +
+
+ +
+ + + + +
+ + +
+ +
+ + + + + + + + + +
+
+ + + + + + + + + + + + + + + +
+
+
+
+ + +
+
+
+
+ + + + + + + + + + + + + + + + + + + + +

O2 XDA Serra - Official HTC upgrade with TF3D of Touch Pro 2

+ +

Background

+

Ever since I laid my hands on O2 XDA Serra aka HTC Raphael aka HTC Touch Pro , I have always loved the device despite it’s limitations on battery life and have found some really useful apps during my association with windows mobile (for last five years or so) which found their way onto Serra as well. While I have known for a while that we can flash ROMS, I was perhaps over protective when it came to Serra and never really went beyond doing a HardSPL for device but last Sunday when I was fiddling with the facebook application and SMS registration, I realised that each time I sent an SMS and then tried to open the internet explorer or opera they will just crash demanding a soft reset. I could instantly find the problem to be Kaspersky Anti-Virus(KAV) failure. It is not possible to remove KAV without a hard reset at least not to my knowledge. (A hard reset is what brings your device back to factory conditions). Now since a Hard Reset would mean lot of work in terms of reinstalling all my beloved apps etc, I figured I might as well see if there is some other ROM I could use. This is what triggered my quest.

+ + +

What started as fixing the issue caused with a Kaspersky Anti Virus failure rendering crashes to internet browsers on my HTC Touch Pro ended in my pleasantly getting a complete device makeover and in the process making my gadget work way better than it was working before. Lovely GUI, great Facebook integration, faster boot time and most important of all a functional GPS, yes you read it right a functional GPS. The problem that plagued almost all handsets provided by O2.

+

I must start by saying that if it worked for me it must work for you but if it does not please don’t hold me responsible. Another thing you must know is doing a HardSPL strips your device of the warranty. There is a way of putting back the StockSPL for warranty reasons but I have not tried it but then it does not sound too complicated. You may also find views on forums that suggest installing a cooked ROM is better but as I said I am too much in love with the device to install flaky ROMs I actually opted for the official update ROM released by HTC and then gave it the touchflo3d of touch pro 2 and after some tweaks here and there which I will all list down, I am completely (I stress the word completely) satisfied with the final results.

+

Ok so now over to the process:

+

Objective

+
    +
  1. To install the manufacturer provided ROM
  2. +
  3. To get the handset a good look and feel.
  4. +
  5. Ensure that GPS functions
  6. +
  7. To get phone address book integrated with face-book so you can get your contacts pictures from their FB profile onto your device.
  8. +
  9. To get Weather for the local city that may not be present in the default list.
  10. +
  11. Gmail sync to ensure Gmail is configured as push mail on your device.
  12. +
  13. The Google calendar sync to your on device outlook calendar.
  14. +
+

Pre-requisites

+
    +
  1. A windows PC / laptop.
  2. +
  3. O2 XDA Serra / HTC Raphael / HTC Touch Pro
  4. +
  5. USB cable to connect XDA to PC / laptop
  6. +
  7. At least a 1 GB Mini SD Card. Considering you have XDA, you may want to get a bigger capacity memory card (of up to 8GB or 16GB…). I have a 8GB one.
  8. +
  9. Access to internet.
  10. +
  11. A good data plan that will ensure good use of the effort you are about to put in.
  12. +
  13. Downloaded cabs and ROM. You can either download all in bulk from here (http://www.mediafire.com/?l12vaj688qwh85w) (I have uploaded them for ease as a bundle) or follow the links and download individually under each section.
  14. +
  15. and as I always ask, lot of patience.
  16. +
+

Steps:

+
    +
  1. HardSPL
  2. +
  3. Install HTC ROM
  4. +
  5. Install touchflo3d
  6. +
  7. Install Dialler
  8. +
  9. Install .net 3.5 and weather database editor
  10. +
  11. Configure Device
  12. +
  13. Configure Email
  14. +
+ +

Step 1: HardSPL

+

Now to start, we will first need to apply HardSPL to the device. I do not know what the words SPL stand for but what I have come to understand after reading for hours on XDA-developer forums is that if you want to install ROM that is not provided by the vendor who sold you the device (O2 in my case), you will need to apply this HardSPL or you may either not be able to install the new ROM or may even be at risk of bricking your device. HardSPL is not complicated and all credit goes to developers at xda-developer community. You will have official instructions if you follow this lin>.

+

For the sake of completeness, I am anyway including instructions here.

+

Instructions:

+
    +
  1. Download Hard-SPL package from attachment, extract to an empty folder. Make sure it's launched from a local drive (not through network drive, etc.)
  2. +
  3. You must Have Phone Synced with PC in Windows Mobile!
  4. +
  5. Run RaphaelHardSPL-Unsigned_190_1_3.exe
  6. +
  7. Follow steps in the RUU, check device for prompts after PC shows loading bar.
  8. +
  9. It should go to black screen now.
  10. +
  11. SPL flashes, device automatically reboots, job done.
  12. +
  13. To confirm you got it installed, go into bootloader mode (tricolour screen!) and verify the screen shows 1.90.OliNex.
  14. +
+
+

Note

+
    +
  1. You will not see the SPL version during normal boot, that is the OS version, not SPL! to enter bootloader mode to see version: with the device turned on, press and hold the volume down button, then press the reset button with the stylus tip, then release the volume down button when bootloader tricolour screen appears.
  2. +
  3. Anyone having problems with the device entering SSPL automatically, please copy SSPLManual.exe from second attachment to the device and run it. then once the screen is black, run RUU manually. i.e. you run the RUU on the PC, if it isn't obvious.
  4. +
  5. This is unsigned Hard-SPL. no limitations on flashing ROMs or radio packages. also, this has overwrite protection, if someone needs to revert to stock SPL for warranty reasons, we will soon post a stock SPL downgrade package.
  6. +
  7. Do not use this RUU for anything other than SPL flashing (i.e. hardspl or stock spl restore)!!! if you want to flash some other rom, then use customruu from: http://forum.xda-developers.com/showthread.php?t=410761
  8. +
+
+

Return to stock spl (for warranty reasons only!)

+
    +
  1. Be sure to first restore stock OS, and stock radio. stock SPL is always to be done last!!
  2. +
  3. Download the stock spl package from this post.
  4. +
  5. Also download the original hardspl from the attachments in this post.
  6. +
  7. Run the hardspl exe but do not click anything in RUU yet. just let the hardspl EXE extract the files for flashing.
  8. +
  9. The SPL you want to revert to is a .NBH file, put the NBH in the extracted hardspl package, overwriting the original NBH file in it!
  10. +
  11. Continue with the RUU (or if needed, run SSPL-Manual.exe manually, then run RUU when it goes to black screen), it goes to 100%, reboots, done.
  12. +
  13. To verify, volume down + reset, see version number on tricolour screen, should now just say 1.90.0000.
  14. +
+

Step 2: Install HTC ROM

+

Straight forward really. Connect your phone to the PC through the USB cable. Ensure that a connection is established with pc through active sync. Now double click on the ROM update utility (RUU) (RUU_Raphael_HTC_Europe_5.05.401.1_R2_Radio_Signed_Raphael_52.58.25.30_1.11.25.01_Ship) and it will guide you through the process.

+

If you want to directly download from HTC you can do so from this link

+

You will have to use the non-O2 serial number. The one I used was HT833K016924.

+
+

Side Note

+

Now if all went well what follows is the non-risky bit. To be honest I don’t even understand why we have to do this but then that’s the process and I was not about to experiment on my lovely instrument based on my limited knowledge led beliefs so I did this nonetheless.

+
+

Once completed it will restart XDA. Once it reaches the screen where it asks to configure Stylus, do a Hard Reset by pressing the Volume down key and the enter key (Round centre key) simultaneously and pressing the reset key with your stylus. (Reset Key is a small hole at the bottom of your handset next to the charging slot.) Once the system switches off release the reset key but continue to hold Volume Down key and enter key.

+

It will now show a screen cautioning you that if you continue it will result in loss of data and if you wish to continue press Vol Up key. Press the Vol up key now. I don’t distinctly remember but I think it asked me twice to press Vol Up which I did. Basically just follow the instructions.

+

That should sort out the new ROM installation.

+

Now at this stage, you would have got rid of the O2 splash screen which has given way to elegant Touch Pro splash screen. This itself was an extreme form of happiness as I was not a huge fan of the O2 splash screen anyway. Ok you may want to take a moment with the new screen and play around. If you are happy and are not overly worried about facebook integration, I will recommend that you skip to Step 5. If however you want FB integration and would want a more Touch Pro 2 kind of interface there is still some work to be done. Moving on, then to Step 3.

+

Step 3: Install touchflo3d

+
text borrowed from XDA-developer website
http://forum.xda-developers.com/showthread.php?t=542113
+Instructions
+1) Disable "TouchFLO 3D" from your Today items. Soft reset.
+2) Go to System Settings, "Power", and uncheck all of the Options on the "Advanced" tab.
+3) Install the "Gen.Y_Manila_R1_5.cab" (install may take 10 minutes or more). DO NOT RESTART.
+4) Install Language Pack cab 0409 Gen.Y_Manila_R1_5. DO NOT RESTART.
+5) Install the HTC Scroll_1_0_1914_2726 cab file. NOW SOFT RESET.
+Done!
+
+

I did not install anything in the optional installs but if you would want to, you can follow the instructions on the post by Captain Throwback on the link above. The version I downloaded was R1.5 which is included in the pack.

+

Step 4: Install Dialler

+

I downloaded the dialler from ppcgeek forums, many thanks to them. It is not available on the link provided in xda-developer post. If you want to download from there, you will have to register on their site and do a bit of googling. Alternatively, it is included in the pack above, just the non confusing cab file named - .

+

Move it to your memory card on phone. Now from XDA go to the location where you saved it and click the cab to install it. It will take about 3 to 5 minutes.

+

Step 5: Install .net 3.5 and weather database editor

+

Copy the < NETCFv35.wm.armv4i> cab(.net 3.5) and < WeatherDatabaseEditor 1.1 Modified> cab(weather DB) onto the memory card on your device. Now first install .net 3.5 by clicking the cab file from the location where you copied it.

+

To install weather DB follow the steps below:

+
    +
  1. +

    Install Weather Database Editor

    +
  2. +
  3. +

    Find your locCode on http://www.accuweather.com.

    +
    +

    Example

    +

    Accuweather URL for Northwich in UK is : http://www.accuweather.com/world-index-forecast.asp?partner=accuweather&traveler=0&loccode=EUR|UK|UK123|Northwich, The locCode is EUR|UK|UK123|Northwich

    +
    +
  4. +
  5. +

    Now open the weather DB on XDA, select your country and then select a city that begins with the same alphabet as your city and the one you are unlikely to use. For this example lets say - Nuneaton. Click on Edit -> Edit City.

    +
  6. +
  7. +

    Now change the name of city to the one you want and in “Accu Weather Code” Field enter the locCode obtained in Step 5.2. For our example it would be - EUR|UK|UK123|Northwich

    +
  8. +
  9. +

    Click on File and exit.

    +
  10. +
  11. +

    Go to Weather tab and add your city and update the weather.

    +
    +

    Tip

    +

    Steps 7 to 9 are not relevant to you if you directly jumped to this section from Step 2 and have not installed the new TF3D

    +
    +
  12. +
  13. +

    Go to home page on XDA and click on watch, now click on “Add City” and add the local city.

    +
  14. +
  15. Make this as your default city by clicking the radio button.
  16. +
  17. Now click on Menu-> Rearrange Cities and bring your local city to top of the list.
  18. +
+

This is it, we will now move to next step.

+

Step 6: Configure Device

+

Many people don’t care to update the owner details but I think it is important to have that updated and hence my first step when I am done with initial installations and all is to update owner details. This ensures that if phone is lost and lands in honest hands you still have a chance of getting it back. It did happen to me so it isn’t a fairy tale…J

+

Click on Windows icon -> Settings-> All Settings ->Personal -> Owner Information and update these details. Ensure that the telephone number you enter is different than the one for this instrument else it pretty much defies the purpose of entering this information on the first place.

+

Next give your XDA a name. Click on Windows icon -> Settings-> All Settings -> System -> About. -> Device ID. Update your device name and description.

+

Personalise weather updates. Slide over to weather, add any other cities you are interested in and click update now.

+
+

Tip

+

I recommend that you install registry editor through the cab file provided in my bundle named and a file Explorer named but these are completely optional

+
+

Your basic configuration is now complete.

+

Step 7: Configure Email

+

If you use hotmail or live and you have a good data plan (mine is unlimited internet usage) it may be a good idea to configure windows live as that ensures push mail so you have instant delivery of your mail as soon as it arrives into your handset. +To do this, Click on Windows icon -> All Programmes -> Windows Live. Enter details and configure.

+
+

Tip

+

Ensure that you change sync settings from “Manual” to “As soon as it comes” and the time schedule according to your needs.

+
+

Gmail has recently introduced Gmail Sync that can sync your email, calendar and address book. They have provided instructions but they were not exactly mapping to how it was shown on my device. I instead used the following route.

+

Connect your phone to your PC / laptop through ActiveSync and open Device Centre. Now click on Setup device -> Sync Setup and in server name enter m.google.com. enter your username and password. The SSL checkbox is clicked by default so let it remain. If it is not checked, please select it in the checkbox.

+

Save settings. Now under items to be synced, of the three : email, calendar and address book select whichever you want synced…(Remember if you set anything other than these three to be synced through exchange nothing will work on Google Sync).

+

I did not want my address book sync with Gmail so I left it and for other two I changed the sync settings to Exchange server. This is it your email will now be delivered into outlook mailbox on your XDA. You may want to check in your Gmail settings that you have activated IMAP or else even though this setup is correct you will not get mails delivered.

+

Congratulations on successful completion of the whole process. Now enjoy the new look XDA with enhanced capabilities!!!

+ + + + + + + + + + + + + + + + + + + + + + +
+
+ + + + + +
+ + + +
+ + + +
+
+
+
+ + + + + + + + + + + + \ No newline at end of file diff --git a/2010/03/07/kubuntu-blog-entry.html b/2010/03/07/kubuntu-blog-entry.html new file mode 100644 index 0000000..3063240 --- /dev/null +++ b/2010/03/07/kubuntu-blog-entry.html @@ -0,0 +1,1751 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + KUBUNTU Blog Entry - Make Gadgets Work + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + Skip to content + + +
+
+ +
+ + + + +
+ + +
+ +
+ + + + + + + + + +
+
+ + + + + + + + + + + + + + + +
+
+
+
+ + +
+
+
+
+ + + + + + + + + + + + + + + + + + + + +

KUBUNTU Blog Entry

+ +

This is how my love for Kubuntu has started....and growing by the minute...

+

I was experimenting with different Linux distros and then I went ahead with Linux Mint – which is quite popular and is know as “ubuntu done right”. I liked it and started dwelling more and played around with themes and such before I figured it is becoming kind of prescriptive and thought will actually play with the a distro with KDE not too far from Ubuntu – Kubuntu and boy was I surprised with the difference … all to their own opinion but mine is KDE is one helluva beautiful thing....the stuff that is done here is simply amazing …. alright once I was done with dropping my jaw at every step I figured there is a bit of learning curve right from the word go and so before I start forgetting what all I have learnt I felt it will be safe jotting it all down and thus this post.

+

Right so if any of this catches your fancy, please be my guest... 😄

+ + +

How to upgrade Ubuntu 9.10 to Kubuntu 9.10

+

Plenty of information on the website and better presented at the one site than other. I liked the way it was explained here

+

This site is also a good source of information in general so well worth a bookmark.

+

Restoring the default panel at bottom of the screen?

+

Once I finished installing Kubuntu as per the instruction from above link, I started playing around and ended up deleting the panel thus learning about how to get that back. In the process I learnt important lesson on making a backup of setting as soon as you have configured system to your first level of satisfaction as if you do not take this precaution you will have to start all over again. +The steps I took were as below:

+

Goto Applications tab, click on Settings → Terminal and open the terminal, Type following commands:

+

sudo cp -R ~/.kde .kde_backup   sudo rm -rfv .kde   kquitapp plasma-desktop   sudo restart
+
+This is it.

+

Now once the system restarts, your panel will be in place. However you will have to put all your configuration again. So to avoid that in future, repeat first two steps and next time you end up in similar situation all you need to do is give this command sudo cp -R ~/.kde_backup .kde and you should be good to go.

+

Problems and Resolutions

+

Problems with system audio, you-tube audio and how it all finally started working for me.

+

When I opened a video on youtube, I was not getting any sound. After some googling with no results I tried this and again today after restoring panel the sound was gone and trying this made it work so I assume at-least for all Dell Inspiron 1525 this should work. +Open the voice control by clicking on volume icon in system tray and clicking on Mixer as and then unmute the “Headphone”. +After this I restarted the computer and logged into GNOME session, and played a youtube video. Sound was coming alright. I logged out from GNOME session and logged in again in KDE session and tried a video on youtube and it was working fine.

+

Change the theme and wallpaper.

+

To change the theme, it is quite simple. Just right click anywhere on desktop and select desktop settings, and under theme either select the available option or download from available options and install. For wallpaper also do the same thing.

+

Change the splash screen

+

Download the splash screen. I downloaded “Kcarbon” splash screen from here as it was going nicely with my desktop theme and wallpaper.

+

Once it was downloaded, I opened from Application Menu → Computer → System Settings → Appearance (under look and feel section) → Splash Screens to get to this screen and then click install, select location where you downloaded the tar file.

+

Change the Log-in Screen (KDM Screen)

+

UPDATE

+
    +
  • Open the 'System Settings' KDE application.
  • +
  • Choose the 'Advanced' tab.
  • +
  • Go to 'Login Manager'.
  • +
  • Choose 'Theme' tab.
  • +
  • Click on the 'Install new theme' button.
  • +
  • Choose the compressed file and click ok.
  • +
+

This one was rather flaky solution. I could not find anything that could change the initial background screen where we enter user credentials and password. This is called KDM Theme as I learnt while finding out how to do it. Several searches on google talked about something called KDM Theme Manager but they are all old mails and for new KDE 4+ it comes pre-installed and I did not know what to do. +So I downloaded one of the KDM themes from http://kde-look.org and unzipped it into the following location as root.

+
kdesudo dolphin /usr/share/kde4/apps/kdm/themes/
+
+

Next, I renamed the folder “oxygen-air” as “oxygen-air_old” and the downloaded and unzipped folder as “oxygen-air”. +This is it. When I restarted I had the brand new KDM Theme to my satisfaction.:)

+

Change the boot splash screen

+

I tried many suggestions but only after going through the whole GRUB2 entries as explained on this link, I got the result I was looking for. However, I did have to do a little bit of trial and error so I will put the steps that finally worked for me. These may also come handy if you do not want to know in depth about all GRUB 2 stuff.

+
    +
  • +

    In GIMP, open the image you want to show as the background when GRUB shows choices to select which OS you want to load at start-up

    +
  • +
  • +

    Now goto Image -> Scale and then enter height as 640, press tab.

    +
  • +
  • +

    Now save the image as a .png file.

    +
  • +
  • +

    in the terminal type kdesudo dolphin /usr/share/images/desktop-base

    +
  • +
  • +

    Copy the image saved in step 3 and paste it in the desktop-base folder

    +
  • +
  • +

    Now in the terminal type kdesudo kate /etc/grub.d/05_debian_theme

    +
  • +
  • +

    This will open 05_debian_theme file with kate text editor.

    +
  • +
  • +

    Go to line 16 and find the following line and edit the highlighted area, replacing it with the name of image copied into desktop-base folder in Step 5 for i in {/boot/grub,/usr/share/images/desktop-base}/moreblue-orbit-grub.{png,tga} ; do

    +
  • +
  • +

    Save the file and in the terminal type:

    +
  • +
+
sudo update-grub2
+sudo reboot
+
+ + + + + + + + + + + + + + + + + + + + + + +
+
+ + + + + +
+ + + +
+ + + +
+
+
+
+ + + + + + + + + + + + \ No newline at end of file diff --git a/2010/03/15/configure-blogtk-1-0-for-blogger.html b/2010/03/15/configure-blogtk-1-0-for-blogger.html new file mode 100644 index 0000000..a4badc8 --- /dev/null +++ b/2010/03/15/configure-blogtk-1-0-for-blogger.html @@ -0,0 +1,1604 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + Configure BlogTK 1.0 for blogger - Make Gadgets Work + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+
+ +
+ + + + +
+ + +
+ +
+ + + + + + + + + +
+
+ + + + + + + + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+
+ + +
+
+
+
+ + + + + + + + + + + + + + + + + + + + +

Configure BlogTK 1.0 for blogger

+ +

Right so I am happy with Blogilo, then why BlogTK?

+

It so happened that I was trying to edit my last post and for some unknown reason I was getting error in updating through Blogilo. I was not able to update through Blogilo and I think it is because of the snapshots included in the post.

+ + +

Anyhoo...so I installed BlogTK and after entering the details I found it not connecting so I opened the gnome-blog and then when I was configuring its preference I saw the default entry in the field XML/RPC URL - http://www.blogger.com/api/RPC2

+

I opened BlogTK, Edit -> Accounts and Settings and then in Server URL entered - http://www.blogger.com/api/RPC2

+

Next entered username and password fields. Clicked Save and then OK. Now on main window File-> Connect and it showed the message connected to server at http://www.blogger.com/api/RPC2.

+

I was then able to edit the post however the Edit post etc were not all that great and meanwhile I found that Blogilo is working fine so am back to Blogilo.

+

Not a huge tip and am sure many might already know but it took me a while to work out the working configuration.

+

However, if someone knows better way to connect, please do let me know as BlogTK sure wasn't living up to the name and fame it has with this way of configuration so maybe there is a better way to do it.

+ + + + + + + + + + + + + + + + + + + + + + +
+
+ + + + + +
+ + + +
+ + + +
+
+
+
+ + + + + + + + + + + + \ No newline at end of file diff --git a/2010/03/19/audio-problem-in-wine-under-kde-4-4-1-solved.html b/2010/03/19/audio-problem-in-wine-under-kde-4-4-1-solved.html new file mode 100644 index 0000000..d481329 --- /dev/null +++ b/2010/03/19/audio-problem-in-wine-under-kde-4-4-1-solved.html @@ -0,0 +1,1605 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + Audio problem in Wine under KDE 4.4.1 - Solved - Make Gadgets Work + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+
+ +
+ + + + +
+ + +
+ +
+ + + + + + + + + +
+
+ + + + + + + + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+
+ + +
+
+
+
+ + + + + + + + + + + + + + + + + + + + +

Audio problem in Wine under KDE 4.4.1 - Solved

+ +

I was trying to install spotify on linux which used to work perfectly on Gnome and when I tried on KDE it was giving error box. I wrote winecfg on terminal and got following error:

+
+

Error

+

fixme:jack:JACK_drvLoad error loading the jack library libjack.so.0, please install this library to use jack

+
+ + +

After a bit of google I found the answer to be as simple as running this command on terminal:

+
sudo apt-get install libjack0
+
+

After this audio was working fine. Hope this helps few others.

+ + + + + + + + + + + + + + + + + + + + + + +
+
+ + + + + +
+ + + +
+ + + +
+
+
+
+ + + + + + + + + + + + \ No newline at end of file diff --git a/2010/03/22/install-maitreya-vedic-astrology-software-on-ubuntu-kbuntu.html b/2010/03/22/install-maitreya-vedic-astrology-software-on-ubuntu-kbuntu.html new file mode 100644 index 0000000..1f22bf5 --- /dev/null +++ b/2010/03/22/install-maitreya-vedic-astrology-software-on-ubuntu-kbuntu.html @@ -0,0 +1,1790 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + Install Maitreya - Vedic Astrology Software on Ubuntu / Kbuntu - Make Gadgets Work + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + Skip to content + + +
+
+ +
+ + + + +
+ + +
+ +
+ + + + + + + + + +
+
+ + + + + + + + + + + + + + + +
+
+
+
+ + +
+
+
+
+ + + + + + + + + + + + + + + + + + + + +

Install Maitreya - Vedic Astrology Software on Ubuntu / Kbuntu

+ +

First I must thank the Maitreya developers for coming up with such a wonderful Vedic Astrology software that works on Linux. I am not aware of any other vedic astrology software that works on Linux.

+

Next I have to say that the install instructions on the site (and in the tarball) are not written with a newbie in mind and I had to struggle a bit before I could get it to work. The compiling of source code is really similar for I guess all source codes in general on linux and in particular in Ubuntu / Kubuntu but thing is when you are new to the platform and probably have almost all required software in repositories you rarely come across a situation where you need to compile from a source code to install your favourite software. Anyway so without wasting time in chit-chat, below is the step by step installation of Maitreya 6 on Ubuntu / Kubuntu.

+ + +

Download Maitreya 6 tarball

+

From your browser goto http://maitreya.svn.sourceforge.net/viewvc/maitreya/ and click on the link 'Download GNU tarball'. This will download the maitreya.tar.gz file on your system.

+

Prepare the system for installing

+

I used the instructions from ubuntu help documentation that can be found here. However, I have documented the step that I used in sequence to achieve the objective.

+
    +
  1. +

    Open the terminal.

    +
  2. +
  3. +

    Type - sudo apt-get install build-essential checkinstall

    +
  4. +
  5. +

    You will be asked for the password. Enter it.

    +
  6. +
  7. +

    Once system completes the above instruction and is ready to take next instruction then Type: sudo apt-get install cvs subversion git-core mercurial

    +
  8. +
  9. +

    Next type: sudo chown yourusername /usr/local/src

    +
    +

    Tip

    +

    Please note that here you need to put the your user name so don't just copy paste. So if say user name is "ankit" , you will type sudo chown ankit /usr/local/src

    +
    +
  10. +
  11. +

    Next Type: sudo chmod u+rwx /usr/local/src

    +
  12. +
+

Now the system is prepared for installing.

+

Install wxWidgets 2.8 Dependencies

+

As mentioned on Maitreya site, the software depends on wxWidgets 2.8 and corresponding packages must be installed. On the site itself is given the code that needs to copied onto the terminal to achieve this.

+
    +
  1. +

    Either from the site or from below select the text.

    +
    sudo apt-get install libwxbase2.8-0 libwxbase2.8-dev libwxgtk2.8-0 libwxgtk2.8-dev wx2.8-headers wx2.8-i18n
    +
    +
  2. +
  3. +

    Press Ctrl+C

    +
  4. +
  5. +

    Goto Terminal window and press Ctrl+Shift+V or click on Edit from top menu and select Paste.

    +
  6. +
  7. +

    Press Enter.

    +
  8. +
  9. +

    You may have to provide password.

    +
  10. +
  11. +

    This should now install wxWidgets 2.8

    +
  12. +
+

Over to next step.

+

Configure and Build

+

There are few things that should be done before we start to configure the downloaded source code.

+
    +
  1. +

    First goto the folder where maitreya.tar.gz file was downloaded in Step 1.

    +
  2. +
  3. +

    Now right click and select 'Extract Archive To'.

    +
  4. +
  5. +

    Select the location /usr/local/src

    +
  6. +
  7. +

    This will extract a folder named maiterya into the location /usr/local/src

    +
  8. +
+

The content downloaded may change in future but at the time of writing this article it has two folders maitreya5 and maitreya6. All next steps will assume that folders name maitreya6 is present in your download but if not you will just have to replace with whatever folder is inside the extracted maitreya folder.

+
    +
  1. +

    Goto your termincal window and type: cd /usr/local/src/maitreya/maitreya6/trunk

    +
  2. +
  3. +

    Now type ./configure.

    +
  4. +
  5. +

    If it gives some bash error like permission denied or file does not exist etc try sh ./confgure. This is what worked from me though everywhere instructions were to just use without sh.

    +
  6. +
  7. +

    It will take a bit of time but it should complete without any error and end with some instructions about using 'make'.

    +
  8. +
  9. +

    Now type make on terminal.

    +
  10. +
  11. +

    It should be done with no errors if all steps were followed correctly so far.

    +
  12. +
+

Over to next step.

+

Install

+

Again there were options around using sudo make install but the ubuntu help I have referred to (link above), suggested that checkinstall is better and it also creates a .deb file and I like the idea so that next time if I have to reinstall I don't need to do all this circus so I used the following and would recommend the same.

+
    +
  1. +

    So in terminal window type sudo checkinstall.

    +
  2. +
  3. +

    When you run it it will ask some questions like doc-pak is not there, do you want to create?(Y) just type Y.

    +
  4. +
  5. +

    Then it shows some options and asks if you want to edit. I typed 2 to edit name and entered Maitreya.

    +
  6. +
  7. +

    It threw a warning that maitreya 6-1_i386 is not complaint with debian standard or something to that effect and I just pressed enter.

    +
  8. +
  9. +

    After this it was all fine and finally a message was shown telling a debian package has been created and to remove use dpkg ...

    +
  10. +
+

The software should now be installed and you can test it by clicking the file named Maitreya6 at Location - /usr/local/src/maitreya/maitreya6/trunk/src/gui. However if you want to access it easily from desktop there is just one more step.

+

Create a desktop icon

+

To create a desktop icon goto the folder /usr/local/src/maitreya/maitreya6/trunk and copy the file maitreya6.desktop and paste in on desktop.

+

This is it you have now installed the Maitreya software !!!

+

As I plan to move away from disqus comment system, I am just including the screenshots of comments just so any information shared by or to the readers is retained.

+

Image title +Image title

+ + + + + + + + + + + + + + + + + +
+ Comments from Disqus +

As I plan to move away from disqus comment system, I am just including the screenshots of comments just so any information shared by or to the readers is retained.

+

+ Disqus Comments + Disqus Comments

+
+ + + + + + +
+
+ + + + + +
+ + + +
+ + + +
+
+
+
+ + + + + + + + + + + + \ No newline at end of file diff --git a/2010/05/03/jagannath-hora-on-linux-play-on-linux-magic.html b/2010/05/03/jagannath-hora-on-linux-play-on-linux-magic.html new file mode 100644 index 0000000..ef3ce8e --- /dev/null +++ b/2010/05/03/jagannath-hora-on-linux-play-on-linux-magic.html @@ -0,0 +1,1674 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + Jagannath Hora on Linux - Play on Linux Magic - Make Gadgets Work + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + Skip to content + + +
+
+ +
+ + + + +
+ + +
+ +
+ + + + + + + + + +
+
+ + + + + + + + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+
+ + +
+
+
+
+ + + + + + + + + + + + + + + + + + + + +

Jagannath Hora on Linux - Play on Linux Magic

+ +

Background

+

While I prefer Maitreya as it can run with Linux as native, quite a few of my friends have asked me if Jagannatha Hora will work on Linux and hence this post.

+

Right, when I tried last time around mid Oct'09, JHora was not working using wine. However, recently things have changed. +If you are not aware or have not used Play On Linux then you should try it out. It works. Anyway back to original topic, the steps to install Jagannatha Hora on Kubuntu 10.04 are as below. (I am mighty sure this will work on other linux machines but I have tried it only on Kubuntu 10.04 which is increasingly becoming my OS of choice.)

+ + +

Install Play On Linux (POL)

+

You can install using Package Manager - Synaptic or KPackagekit but you will most likely get a little older version. I installed using Synaptic but then updated it by downloading directly from the site or follow this link to download the version 3.7.6. This is latest at the time of writing but it may be best to just get it from the website here. Once downloaded and installed, we move to a very simple and straightforward Step 2.

+

Download Jagannatha Hora (JH)

+
+

Tip

+

I am sure if you have come here searching on net, you don't need introduction to this software but in any case I admire the author of this software Mr. P. V. R Narsimha Rao for various reasons. One I consider him my Guru and as I am just a beginner to the world of astrology I find his to book the best reference one can get. I purchased it in 2006 when it was not easily available in UK but now the book itself is included in the software. Though the topic of this post does not require mention of book, I will take a moment to write few words on it. As the author is an engineer from IIT, his approach to subject was structured much like an engineering book. Very logical flow that takes you through the concepts in a way I have not found in any other book till date. It does not mean I don't like other authors. It just means that I recommend this to any beginner as it makes the concepts easy to grasp.

+
+

OK, now back to topic, if you don't already know, you can get this software by following this link

+

Once you download the zip file, extract it in the same folder. Just remember where you extracted. The file you have extracted will be named - jh_full_install.exe.

+

Install JH using POL

+

Open the Play on Linux software. In Kubuntu you will find it by clicking KLauncher -> Applications -> Games. Once it starts, click on 'Install'. This will open a new window.

+

In this window, click on the line in lower left hand side corner - “Install a .pol or an unsupported application”. This will close the above window and open another window.

+
+

Tip

+

Please note that if this window does not close, it appears that Play On Linux has hanged. In such case click on cross to close that window and it will show an error message suggesting that “PlayOnLinux” is not responding or something to that effect but you select to force close it and you will still land up on the next window. I found that this problem went away when I installed the latest version 3.7.6.

+
+

a) In this window select “Manual installation” and click on “Next”. +b) On the next window that appears, click “Forward”. +c) You will be presented with another window with choices. Default choice being – “Install a program in a new prefix”. Keep it selected or if it is not selected, select it and click on “Forward”, +d) Next will be presented a window asking name for wine prefix. Type “Jhora” and click “Forward” +e) On the next window, there will be two check-boxes. Leave them un-ticked, do nothing and click Forward. +f) Once prefix is created, you will be presented with this window. Browse to the the file extracted in step 2 and then press Forward. +g) Now it should start installing and once complete, it will ask if you want it to be shown on desktop. I chose Yes but it will be up to personal preference.

+

Play On Linux can also be used to install Spotify and 7zip, but more importantly you can see that an entry for Jhora has now been created.

+

Finally on desktop you should be able to see the link for JH. You should now be able to use JH without any problems.Hope you found this post useful.

+

-Ankit.

+ + + + + + + + + + + + + + + + + + + + + + +
+
+ + + + + +
+ + + +
+ + + +
+
+
+
+ + + + + + + + + + + + \ No newline at end of file diff --git a/2010/11/13/part-1-configure-epson-s515w-on-linux-mint-ubuntu-10-04.html b/2010/11/13/part-1-configure-epson-s515w-on-linux-mint-ubuntu-10-04.html new file mode 100644 index 0000000..d7ffbba --- /dev/null +++ b/2010/11/13/part-1-configure-epson-s515w-on-linux-mint-ubuntu-10-04.html @@ -0,0 +1,1733 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + Part 1 - Configure Epson S515W on Linux Mint / Ubuntu 10.04 - Make Gadgets Work + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + Skip to content + + +
+
+ +
+ + + + +
+ + +
+ +
+ + + + + + + + + +
+
+ + + + + + + + + + + + + + + +
+
+
+
+ + +
+
+
+
+ + + + + + + + + + + + + + + + + + + + +

Part 1 - Configure Epson S515W on Linux Mint / Ubuntu 10.04

+ +
+

UPDATE

+

While this tutorial will get your printer up and running, you should also follow Part 2 to ensure that it continues to work even after you have restarted your printer.

+
+ + +

Lately I have not done much experiments and hence a stable system. I did not require any changes and all was well and then recently I changed over to Linux Mint which by the way is a UBUNTU derivative, very slick and very cool. Now after this reinstall I was able to get back all my previous installs and everything the usual way but scanning using Epson S515W was not out of the box and below are the steps I followed to get it working.

+ +

Step 1: Configure Printer

+

This really works quite easily in all Ubuntu installs. Goto System > Administration > Printer

+

Alt Text

+

Now you can see my printer configured but when you will get this window, you wil only see Print_to_PDF. You should then Click on arrow next to ADD and select Printer. It will open the window below:

+

Alt Text

+

Again under Network Printer it may not show anything initially but if your printer is on and configured to your wireless router it will identify in a moment or two. If it does not, click on Find Network Printer.

+

Alt Text

+

Once your printer appears, click on it and then click on 'Forward'.

+ +

Step 2: Print a test Page

+

Open the Printer window again from System>Administration>Printer and double click on printer. It should open the following window:

+

Alt Text

+

Click on 'Print Test Page'. If it prints the ubuntu test page correctly your printer is configured. Mine was so I will not go more in details of alternative option. If you do want to know more let me know and I will put a post. +However if now you will try to scan you will find that scanner either is not identified or if it is it does not complete the scanning and printer gets hanged, so over to next steps then.

+ +

Step 3 - Install drivers and software for scanner

+

Goto the avasys site that provides downloads and navigate for your model or if you have the same as mine just click on the link below and then goto Downloads and select All In One Printers. On the next page scroll down all the way and select the radio button for your printer.

+ + +

Alt Text

+

Now these installs need to happen in a sequence and we don't need all deb files on the next page anyway so I will mention download and install in sequence as it should be done.

+
    +
  1. +

    From the section 'Download for Epson Stylus NX510/NX515/SX510W/SX515W/TX550W data package' download the data package iscan-data_1.4.0-1_all.deb.

    +
  2. +
  3. +

    Install this package now.

    +
  4. +
  5. +

    From the section 'Download for Epson Stylus NX510/NX515/SX510W/SX515W/TX550W core package' download iscan_2.26.0-3.ltdl7_i386.deb.

    +
  6. +
  7. +

    Install this package now

    +
  8. +
  9. +

    From the section 'Download for Epson Stylus NX510/NX515/SX510W/SX515W/TX550W network plugin package' download iscan-network-nt_1.1.0-2_i386.deb

    +
  10. +
  11. +

    Install this package now.

    +
  12. +
  13. +

    Now on your printer(actual machine not on laptop / computer) goto Settings (Press the button with Wrench and Screwdriver symbol as shown in the image Printer Control).

    +

    Alt Text

    +
  14. +
  15. +

    Using left arrow navigate to Network Settings (Computer and Printer icon)

    +
  16. +
  17. +

    Click OK button.

    +
  18. +
  19. +

    Using Down Arrow select 'Confirm Settings' and click OK button.

    +
  20. +
  21. +

    Press Down Arrow twice and it should show the Printer IP Address. Note it Down. It will be something like 192.168.1.60.

    +
  22. +
  23. +

    Now back on your laptop open terminal and type gksudo nautilus. You will be asked your superuser password. Provide that. It will open the file explorer with admin privileges. Be extremely careful now, you do not want to delete anything in this privilege mode.

    +
  24. +
  25. +

    In the file explorer, click on 'File System' and browse to the file path etc>sane.d and open the file named epkowa.conf. (Screenshot below)

    +
  26. +
  27. +

    In the file below usb and scsi add the line net 1865 so for our example this will be net 192.168.1.60 1865. (In gedit it was line 12 for me.)

    +
  28. +
  29. +

    Save and close the file.

    +
  30. +
  31. +

    Close all windows.

    +

    Alt Text

    +
  32. +
+

Step 4 - Test Scanner

+

Now open Menu>All Applications>Graphics>Image Scan! for Linux.

+

Alt Text

+

This should open the software window. Now click on preview to see that a document preview is produced. If it does you are all set. If not you must check the configuration of epkowa.conf and try again.

+

Alt Text

+

The preview of my 3.5 year olds drawing from scanner :

+

Alt Text

+

and the final scan is shown below:

+

Alt Text

+ + + + + + + + + + + + + + + + + + + + + + +
+
+ + + + + +
+ + + +
+ + + +
+
+
+
+ + + + + + + + + + + + \ No newline at end of file diff --git a/2011/02/19/call-us-and-several-other-countries-free-using-android.html b/2011/02/19/call-us-and-several-other-countries-free-using-android.html new file mode 100644 index 0000000..97985de --- /dev/null +++ b/2011/02/19/call-us-and-several-other-countries-free-using-android.html @@ -0,0 +1,1626 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + Call US and several other countries free using Android - Make Gadgets Work + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+
+ +
+ + + + +
+ + +
+ +
+ + + + + + + + + +
+
+ + + + + + + + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+
+ + +
+
+
+
+ + + + + + + + + + + + + + + + + + + + +

Call US and several other countries free using Android

+ +

That's right...with smartphones all that was possible using computers is increasingly becoming feasible through phones. I just now configured my Nexus S to make free phone calls over internet to USA. How you ask? Well here we go:

+ + +

The site offering free calls to USA landlines and mobiles is www.voipstunt.com so I registered an account with this site.

+

Now Android 2.3 has inbuilt SIP support so I directly configured instead of downloading any app from market but it can be as easily done using sipdroid or csipsimple but anyway since there is good dialer integration I opted for direct route.

+
    +
  1. +

    Goto Settings -> Call Settings and under Internet Call Settings click on 'Accounts'.

    +
  2. +
  3. +

    Untick receive calls.

    +
  4. +
  5. +

    Click on Add Account.

    +
  6. +
  7. +

    Now in username enter the username with which you have registered on voipstunt website.

    +
  8. +
  9. +

    Enter the password used for voipstunt account in password field.

    +
  10. +
  11. +

    Enter sip.voipstunt.com in server.

    +
  12. +
  13. +

    Untick the 'Set as primary account' field.

    +
  14. +
  15. +

    Press back button till you are back to call settings. Now under Internet Call Settings click on 'Use Internet Calling' and select 'Ask Each Time'.

    +
  16. +
+

This is it. All done. Try calling landline or mobile number of your US contact and before placing the call your phone will ask whether to place call over internet or cellular network. Choose internet and you are off to free calls.

+ + + + + + + + + + + + + + + + + + + + + + +
+
+ + + + + +
+ + + +
+ + + +
+
+
+
+ + + + + + + + + + + + \ No newline at end of file diff --git a/2011/02/20/google-voice-sip2sip-ikall-free-international-calls-to-known-contacts.html b/2011/02/20/google-voice-sip2sip-ikall-free-international-calls-to-known-contacts.html new file mode 100644 index 0000000..2edbe77 --- /dev/null +++ b/2011/02/20/google-voice-sip2sip-ikall-free-international-calls-to-known-contacts.html @@ -0,0 +1,1853 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + Google Voice + SIP2SIP + Ikall = Free international Calls to known contacts - Make Gadgets Work + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + Skip to content + + +
+
+ +
+ + + + +
+ + +
+ +
+ + + + + + + + + +
+
+ + + + + + + + + + + + + + + +
+
+
+
+ + +
+
+
+
+ + + + + + + + + + + + + + + + + + + + +

Google Voice + SIP2SIP + Ikall = Free international Calls to known contacts

+ +

Background

+

Second post in succession on the topic but believe me when I get a new gadget I try getting all information and then once I have had the stuff working I have to make a post right away or I will forget and hence this post. This post also overrides the previous posts. +Right then, let's get down to business.

+
+

Objective

+

We aim to make free international calls and there is nothing illegal in this set-up, not to my knowledge.

+
+
+

How?

+

Google has introduced Google Voice and allows free calls to all US numbers. This can be done using their google talk plugin in the gmail browser.

+
+ + +

Pre-Requisites

+
    +
  • +

    Gmail account Preferably a new one and not the one you use for your day to day usage.

    +
  • +
  • +

    Ability to install X-Lite 4.0 which will require a machine running Windows PC directly or on virtualbox.

    +
  • +
  • +

    Ability to install Hotspot Shield if residing outside of USA.

    +
  • +
  • +

    An Android device at both source and destination of the call.

    +
  • +
+

Get a SIP number

+

If outside of USA, install hotspot shield on windows machine by going to this site - http://www.hotspotshield.com .I am not sure how safe this is from spyware and all security perspective. I am not really worried about it as I carry out these things on virtualmachine using virtualbox and there is no real threat to my actual machine which runs on Linux. So if Windows is your primary machine you do it on your own risk. +Once installed, open the site www.sip2sip.info.Register on sip2sip using your new gmail account. Ensure that you select US Central as your region. +You will get an email from sip2sip on the new gmail account giving your login details which will be something like:

+
1
+2
SIP address: 2233xxxxxx@sip2sip.info
+Password: abcdabcdabcd
+
+

Open the email and click on the link provided for lgging in. Enter the login details provided and goto settings tab (3rd from left). +In the first field under SIP Account, enter a new easy to remember password and click SAVE. +This completes Step 1 and you have successfully created SIP number for yourself.

+

Get a US phone number using ipkall

+

Go to the ipkall website and click on sign-up.On the sign-up page complete the following details using the email from sip2sip:

+
1
+2
+3
+4
+5
SIP username:  2233xxxxxx
+Hostname or IP address: sip2sip@info  
+Email Address: Preferably the email address you used to register at sip2sip
+Password: Prefereably the same password as what you changed on sip2sip in Step 1-5.
+Enter the human verification codes and click 'Submit'.
+
+

You will receive a mail with in your email account with a new US number with text of something to following effect:

+
+

Success

+

Thank you for signing up. Your IPKall phone number is: 253-XXX-XXXX. +SIP Phone Number: 2233xxxxxx +SIP Proxy: sip2sip.info +Email: abcdefg@gmail.com +Password: qwerty

+
+

This completes Step 2. You have now received a US phone number that is linked to your sip2sip account.

+

Set-up Xlite / softphone to receive calls made to US phone number

+

In order to activate Google Voice account, it is important to be able to receive call on the new phone number that we have created in Step 2 so install Xlite v4.0 from here

+

Once X-Lite is installed, open it and click on Softphone -> Account Settings. Now fill the following fields:

+
1
+2
+3
+4
Account Name: Fill your gmail username.
+User ID: 2233xxxxxx
+Domain: sip2sip.info
+Password: sip2sip password (qwerty for this example)
+
+

Click OK at the bottom of the window. This completes Step 3.

+

Activate Google Voice for this Gmail account

+
    +
  • +

    Log into the google voice account.

    +
  • +
  • +

    Provide a user pin to retrieve voicemails

    +
  • +
  • +

    Now provide the US phone number obtained in Step 2-4 (253-XXX-XXXX)

    +
  • +
  • +

    A window will be shown with two numbers and a button call now.

    +
  • +
  • +

    Click on Call Now and you should receive call on Xlite phone.

    +
  • +
  • +

    Accept the call on Xlite and enter the two numbers shown in google's window.

    +
  • +
  • +

    You will get confirmation that the numbers are correct and will be asked to set-up voice mail greeting or hang-up.

    +
  • +
  • +

    Hang-up now as you can set this up later.

    +
  • +
+

Google Voice account is now set-up.

+

Configure Android to receive calls using Google Voice

+

If you are using nexus S, good news, it has inbuilt capability to get the SIP calls though this can be also be done using SIPDROID on other android devices. For Nexus S you can follow the steps below:

+
    +
  • +

    Goto Settings -> Call Settings and under Internet Call Settings click on 'Accounts'.

    +
  • +
  • +

    Untick receive calls.

    +
  • +
  • +

    Click on Add Account.

    +
  • +
  • +

    Now in username enter the 2233xxxxxx provided by sip2sip

    +
  • +
  • +

    Enter the password used for sip2sip account in password field. (qwerty in this example)

    +
  • +
  • +

    Enter sip2sip.info in server.

    +
  • +
  • +

    Untick the 'Set as primary account' field.

    +
  • +
  • +

    Click on Optional Settings and in Outbound proxy address enter proxy.sipthor.net

    +
  • +
  • +

    Press back button till you are back to call settings. Now under Internet Call Settings click on 'Use Internet Calling' and select 'Only for Internet Calls'.

    +
  • +
+

For SIPDROID once you have downloaded and installed it from android market, follow the steps below:

+
    +
  • +

    Open SIPDROID, and goto Settings ->SIP Account.

    +
  • +
  • +

    In Authorization Username enter: 2233xxxxxx@sip2sip.info

    +
  • +
  • +

    Enter the password used for sip2sip account in password field. (qwerty in this example)

    +
  • +
  • +

    In Server or Proxy enter proxy.sipthor.net

    +
  • +
  • +

    In Domain enter sip2sip.info

    +
  • +
+

Now save and exit. SIPDROID will register the VOIP and turn green. +Now from gtalk plugin in the browser from some other gmail account try to call US phone number obtained in Step 2-4 (253-XXX-XXXX). Your phone should ring and so should the Xlite. +If step 4 did not happen as expected, you need to review the configuration and once it does happen as expected, your set-up to recieve calls is completed.

+

Configure Android to make calls using Google Voice

+
    +
  • +

    Download and install Google Voice Callback on android device - https://market.android.com/details?id=com.xinlu.gvdial&feature=search_result

    +
  • +
  • +

    Provide the gmail credentials for the application. This will perhaps explain my recommendation for a new gmail account. You will be giving login credentials to a third party application but since it's a new account with no confidential info, it should really be safe.

    +
  • +
  • +

    In the settings for when to use callback select 'Ask Everytime' if you are outside of USA.

    +
  • +
+

This is it. Try calling one of your USA contact and this application should make a call back and you should be able to talk for free to your US friends.

+

This is happy ending for those who don't have sight of our aim - make free international calls. For free international calls though you have reached a point where you will have dependency on person you are calling. Following 3 options will be possible:

+

If the person you are calling also has android device and they follow this tutorial they will have a US number which you can then store in your contacts against that person's name and from thereon you both can call each other absolutely free.

+

If your friend has a SIP enabled device, they can follow this tutorial and replace the set-up of Android device to setting up their own SIP device.

+

If above two are not viable options, you can ask your friend to call you on your international number through googletalk plugin in their web browser. Unfortunately this takes away the flexibility of you being able to call them but given the constraints this may still be a good option to talk for free.

+
+

Final words - I know it's a long post and looks complex but believe me if you do it right it takes roughly 20 minutes.

+
+

Hope you find the post helpful.

+ + + + + + + + + + + + + + + + + + + + + + +
+
+ + + + + +
+ + + +
+ + + +
+
+
+
+ + + + + + + + + + + + \ No newline at end of file diff --git a/2011/04/17/time-for-a-bit-of-show-off.html b/2011/04/17/time-for-a-bit-of-show-off.html new file mode 100644 index 0000000..b49f650 --- /dev/null +++ b/2011/04/17/time-for-a-bit-of-show-off.html @@ -0,0 +1,1620 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + Time for a bit of show-off - Make Gadgets Work + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+
+ +
+ + + + +
+ + +
+ +
+ + + + + + + + + +
+
+ + + + + + + + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+
+ + +
+
+
+
+ + + + + + + + + + + + + + + + + + + + +

Time for a bit of show-off

+ +

Hey Hey I made an Android app named "Sai Satcharitra". It's a very basic app and +all it does is helps people read Sai Satcharitra on their Android Smartphones, +something that can also be achieved by maybe converting the text ino a pdf, epub +file etc.

+ + +

Image 1 +Image 2

+

Anyhoo, all I wanted was to experiment with Android coding to see how much of +that developer is still alive in this business project manager. It took me about +5 hours to complete it after understanding the nuances of Android SDK and +refreshing my java coding skills which were quite minimal to start with :winking:. I +used to be more of a PL/SQL / Lotus Script / C++ kind of developer in my prime.

+

Image 3

+

So well long story short, I paid the amount to be registered as developer so I +can publish this app on android market all the while thinking that if people do +find it useful, I will assume this amount and effort is my way of giving +something as Charity in name of Sai Baba.

+

I published the app on Market on 26/03/2011 and then I was busy with all the +work that spring brings with it. Then today when I checked my account I was +happily surprised to see the stats. The app has been downloaded over 200 times +with about 180 active installs.

+

It felt very good to see that many people are finding my work useful.I was a bit +disappointed that of the 200+ downloads only 2 have given feedback but hey, I +wasn't there fishing for reviews so as long as it helps someone, I am a happy +dude.

+ + + + + + + + + + + + + + + + + + + + + + +
+
+ + + + + +
+ + + +
+ + + +
+
+
+
+ + + + + + + + + + + + \ No newline at end of file diff --git a/2011/04/24/logitech-ex100-wireless-keyboard-and-mouse-not-working-read-on.html b/2011/04/24/logitech-ex100-wireless-keyboard-and-mouse-not-working-read-on.html new file mode 100644 index 0000000..1270eef --- /dev/null +++ b/2011/04/24/logitech-ex100-wireless-keyboard-and-mouse-not-working-read-on.html @@ -0,0 +1,1624 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + Logitech Ex100 - Wireless Keyboard and Mouse not working? Read on.. - Make Gadgets Work + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+
+ +
+ + + + +
+ + +
+ +
+ + + + + + + + + +
+
+ + + + + + + + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+
+ + +
+
+
+
+ + + + + + + + + + + + + + + + + + + + +

Logitech Ex100 - Wireless Keyboard and Mouse not working? Read on..

+ +

Today for some unknown reason suddenly my Logitech Wireless Keyboard and Mouse - Model Number EX100 (Logitech Cordless Desktop EX100 (920-000879)) stopped working and very stubbornly denied to work despite my repeated attempts on all obvious and logical steps - resets, battery change and such.

+

After trawling through the internet for some time I figured that I am not alone in facing the issue but surely have been hit quite late by it. It appears Logitech was more than happy to replace it at one point.

+ + +

Anyway, further forum hopping lead me to a post that felt like a very unlikely solution but I gave it a shot and should I say more? It worked indeed. So this post is not only for world but for my future reference.

+

With thanks to "Andromedia" on logitech forum, I present the solution that resurrected my dead keyboard and mouse.

+

For Keyboard:

+
    +
  1. Remove the batteries from the Keyboard.
  2. +
  3. Type on the keyboard for about 10 to 30 seconds.
  4. +
  5. Insert the batteries back into Keyboard.
  6. +
  7. Press left Alt+left Ctrl+F12 all at the same time and hold for about a + second.
  8. +
  9. Now PRESS the Connect Button on Receiver. (DO NOT HOLD)
  10. +
  11. Now PRESS the Connect Button on Keyboard. (DO NOT HOLD)
  12. +
  13. Finally, Press "Esc" on Keyboard.
  14. +
+

For Mouse:

+
    +
  1. PRESS the Connect Button on Receiver. (DO NOT HOLD)
  2. +
  3. Now PRESS the Connect Button on Mouse. (DO NOT HOLD)
  4. +
+

That should do the trick. At least for me it did. 😄

+ + + + + + + + + + + + + + + + + +
+ Comments from Disqus +

As I plan to move away from disqus comment system, I am just including the screenshots of comments just so any information shared by or to the readers is retained.

+

+ Disqus Comments + Disqus Comments

+
+ + + + + + +
+
+ + + + + +
+ + + +
+ + + +
+
+
+
+ + + + + + + + + + + + \ No newline at end of file diff --git a/2011/04/29/mmc-convert-media-files-on-linux.html b/2011/04/29/mmc-convert-media-files-on-linux.html new file mode 100644 index 0000000..fd94f3d --- /dev/null +++ b/2011/04/29/mmc-convert-media-files-on-linux.html @@ -0,0 +1,1660 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + MMC Convert media files on Linux - Make Gadgets Work + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + Skip to content + + +
+
+ +
+ + + + +
+ + +
+ +
+ + + + + + + + + +
+
+ + + + + + + + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+
+ + +
+
+
+
+ + + + + + + + + + + + + + + + + + + + +

MMC Convert media files on Linux

+ +

I have been using Mobile Media Converter for almost a year now and it is such a great tool. I think it is a must have at-least on Linux. +It not only converts almost all popular media formats into other formats but also has an inbuilt youtube video downloader which comes very handy for converting all the free official(and unofficial) song videos into MP3. +Let me spell it out, how it all works.

+ + +

1. Download and Install MMC

+

Well you download and install MMC from this site

+

Scroll down to the part of screen that shows the following:

+

Alt Text

+

Now follow the instructions to add Medibuntu repo if you haven't already done so.

+

2. Download media:

+

Open youtube and search for a song that you want to have on you mp3 player. Copy the link from browser and open MMC. In Linux Mint follow Menu - All Applications - Sound & Video, to open following window.

+

Alt Text

+

Now clicking on Add YouTube video will open following window: +Only in this window your link copied from youtube would be already populated. +Click on Download and wait till it downloads your song.

+

Alt Text

+

3. Convert it to MP3

+

Once downloaded the video gets added into the queue as shown in the following screenshot:

+

Alt Text

+

Now you can select the location where you want to store the mp3 and the conversion type as can be seen below.

+

Alt Text

+

This also shows how many other formats this nice little piece of software can handle. +Anyway, select MP3 Audio and click on Convert. +That's it all done. +Now I don't condone the download of any material with copyright, I do support use of anything that is legal. +There are some really good official videos (like the one in this example) that can be converted into MP3 or even video formats to enjoy on a long journey.

+ + + + + + + + + + + + + + + + + + + + + + +
+
+ + + + + +
+ + + +
+ + + +
+
+
+
+ + + + + + + + + + + + \ No newline at end of file diff --git a/2011/05/02/glympse.html b/2011/05/02/glympse.html new file mode 100644 index 0000000..12097f1 --- /dev/null +++ b/2011/05/02/glympse.html @@ -0,0 +1,1603 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + Glympse - Make Gadgets Work + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+
+ +
+ + + + +
+ + +
+ +
+ + + + + + + + + +
+
+ + + + + + + + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+
+ + +
+
+
+
+ + + + + + + + + + + + + + + + + + + + +

Glympse

+ +

Every now and then we come across something that has a strong potential, something that is a great phenomenon in making and Glympse to me is that idea.

+

The simple to use and highly effective application available on android market for free that allows you to share your position during the travel in real time.

+ + +

While it's moderately useful for long journeys as was evident on my holiday travel this weekend when my host was able to monitor my progress to their house 250+ miles away, I think this application can find great and effective use in postal and transportation sector.

+

It not only allows you to share your journey but also shows you when was your progress last viewed and who is viewing it right now.I provides you the control on who can have access to this real time view and for how long. You provide a time-frame for which you would prefer this monitoring to be available and then allows you to add more time in 15 minutes intervals. It integrates with phone contacts and has a very slick GUI.

+

All in all very impressed with this application and the fact that it's available for free is the icing on cake.

+

If you have an android phone, do lot of travelling and need to keep calling for your whereabouts, guess this application can save you on unnecessary calls while letting you concentrate on your journey. +Highly recommended application.

+ + + + + + + + + + + + + + + + + + + + + + +
+
+ + + + + +
+ + + +
+ + + +
+
+
+
+ + + + + + + + + + + + \ No newline at end of file diff --git a/2011/05/04/part-2-configure-epson-s515w-on-linux-mint-ubuntu-10-04.html b/2011/05/04/part-2-configure-epson-s515w-on-linux-mint-ubuntu-10-04.html new file mode 100644 index 0000000..ab7ccc5 --- /dev/null +++ b/2011/05/04/part-2-configure-epson-s515w-on-linux-mint-ubuntu-10-04.html @@ -0,0 +1,1648 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + Part 2 - Configure Epson S515W on Linux Mint / Ubuntu 10.04 - Make Gadgets Work + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+
+ +
+ + + + +
+ + +
+ +
+ + + + + + + + + +
+
+ + + + + + + + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+
+ + +
+
+
+
+ + + + + + + + + + + + + + + + + + + + +

Part 2 - Configure Epson S515W on Linux Mint / Ubuntu 10.04

+ +

While the majority of settings are covered in my previous post here I found that each time I switched off my printer, it used to change it's IP address and I had to repeat Steps 7 to 16 each time. My printing and scanning needs are pretty limited so I never bothered working out a solution until today and knowing what I now know, it was quite a simple and quick solution I should have done this on the first place. Anyway as they say better late than never. 😄

+

So to carry out the magical transformation to your printing experience follow the steps below:

+ + +
    +
  1. On your printer (actual machine not on laptop / computer) go to Settings (Press the button with Wrench and Screwdriver symbol as shown in picture).
  2. +
+

Alt Text

+
    +
  1. +

    Using left arrow navigate to Network Settings (Computer and Printer icon)

    +
  2. +
  3. +

    Click OK button.

    +
  4. +
  5. +

    Using down arrow navigate to 'General Setup' and click OK button.

    +
  6. +
  7. +

    Printer will show the question 'After changing the settings, network may be disconnected. Continue?' and present options Yes and No with No selected (highlighted in Yellow.)

    +
  8. +
  9. +

    Using left arrow move to Yes and press OK.

    +
  10. +
  11. +

    Printer will now show the screen for 'Printer Name Setup', do not change anything. Just Click OK to proceed to next screen.

    +
  12. +
  13. +

    Now printer will show screen for TCP/IP screen with two options Auto which is selected by default and highlighted in yellow and Manual. Here using down arrow key select Manual.

    +
  14. +
  15. +

    Click OK button.

    +
  16. +
  17. +

    The screen will show current IP address assigned to printer something like 192.168.1.66.

    +
  18. +
  19. +

    Now I am assuming that you have already followed part 1 of this tutorial so on your laptop open the file epkowa.conf from /etc/sane.d. You can also open this file by typing the following command in terminal gedit /etc/sane.d/epkowa.conf

    +
  20. +
  21. +

    Note down the IP address you entered last time 192.168.1.60 below usb and scsi. (Line 12 on my file.)

    +
  22. +
  23. +

    Now on your printer change IP address to match the one on last step. To do so, first click on left arrow on printer to reach the last digit of displayed IP address and then adjust the number using up or down arrow.

    +
  24. +
  25. +

    Once it is same as that on epkowa.conf file (192.168.1.60 in this example), click OK button. Now continue to click OK button on all remaining screens till you reach Network Settings screen (About 5 times)

    +
  26. +
  27. +

    Press OK one final time and that's it you are done.

    +
  28. +
+

If you are to now switch off and switch on the printer and test scan it should work without any issues. +Hope you find this useful.

+ + + + + + + + + + + + + + + + + + + + + + +
+
+ + + + + +
+ + + +
+ + + +
+
+
+
+ + + + + + + + + + + + \ No newline at end of file diff --git a/2011/05/07/how-to-edit-pdf-in-linux-the-easy-way.html b/2011/05/07/how-to-edit-pdf-in-linux-the-easy-way.html new file mode 100644 index 0000000..7f3b14d --- /dev/null +++ b/2011/05/07/how-to-edit-pdf-in-linux-the-easy-way.html @@ -0,0 +1,1719 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + How to edit PDF in Linux - The easy way. - Make Gadgets Work + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + Skip to content + + +
+
+ +
+ + + + +
+ + +
+ +
+ + + + + + + + + +
+
+ + + + + + + + + + + + + + + +
+
+
+
+ + +
+
+
+
+ + + + + + + + + + + + + + + + + + + + +

How to edit PDF in Linux - The easy way.

+ +

Background

+

Recently someone asked this question to me and after some search on Google I came across mentions of PDFedit, Scribus, flpsed, Gimp, PDFMod and even the openoffice plugin for importing PDF and I tried all of these. PDFescape is probably a good solution but is only online and limited to 30 pages in it's free avatar. If your aim is similar to the one stated below, these tools are way too complicated (PDFedit), limiting (PDFescape) or ineffective (rest of them). +So, Let's start with the usual what was my aim question.

+
+

Objective

+

To be able to highlight and put his own annotations, highlights and comments in the lecture notes that he gets from his college lecturers in pdf and be able to print the amended notes from any platform. (Linux / Mac / Windows.)

+
+ + +

What Worked?

+

What I gave my friend as his final solution, in the end, kills two birds with one stone - Use of 'Calibre' to convert the pdf to rtf and then use the open office to edit the notes. Where is the second bird, you ask...well Calibre is a great tool, infact in my opinion the best tool for ebook management. Using Calibre will not only help in editing the notes from the pdf but enable us to have a copy of final notes for ready reference right on our smartphone. There you have the second bird. 😄

+

Step 1 - Install and set-up Calibre:

+

The current version of Calibre at the time of writing is 0.8 while the one in ubuntu repository is 0.6 so I recommend you should update after installing from repo as that will ensure all dependencies are satisfied and then you have the latest version as well. +So first open your package manager (on Linux Mint: Menu - Package Manager). You will need to provide root password and then search for Calibre and install it. +Now to update to latest version, open the terminal (on Linux Mint: Menu - Terminal) and type the following command:

+
sudo python -c "import urllib2; exec urllib2.urlopen('http://status.calibre-ebook.com/linux_installer').read(); main()"
+
+

You will be asked for your root password so provide that and terminal will show a question:

+

Enter the installation directory for calibre [/opt]: # (1)
+
+1. Type /opt and press enter.

+

Now type Calibre and it will fire up the welcome wizard.

+

Follow these screen-shots:

+

Select your language and Directory where you will want your ebooks to be stored. I keep it default.

+

Alt Text

+

Select your ebook Device. I have selected Android as that is the smartphone I use for reading ebooks.

+

Alt Text

+

That's Calibre configured. Easy-Peasy !!! :)

+

Alt Text

+

Step 2: Import pdf into Calibre

+

Open Calibre (On Linux Mint: Menu - All Applications - Office - Calibre or just by typing calibre on terminal).

+

Alt Text

+

Now click on 'Add Books' and select 'Add books from single directory.

+

Alt Text

+

It will open the dialogue box for you to select file. Go to the location where you have saved your pdf. In my case it was in /Downloads/Tutorials as you can see in next screenshot.

+

Alt Text

+

Select the file and click open. Calibre will then import the pdf. While importing it shows the 'Adding' screen as can be seen in screenshot below.

+

Alt Text

+

Depending on size of the pdf it can take a while or be very quick. In this case as it's a small lecture note it was hardly few seconds. Once completed, imported file will appear in your library as shown.

+

Alt Text

+

Once completed, it's time to move to next step.

+

Step 3: Convert pdf to rtf using Calibre

+

Select and right click on the pdf imported into Calibre and select Convert - Convert Individually.

+

Alt Text

+

It will open the window to show options for conversion as shown below. In this window on right hand side corner select 'rtf' from the drop-down against 'Output'.

+

Alt Text

+

Now click OK. Once Converted it will show the rtf format in right pane as shown in red circle in following figure.

+

Alt Text

+

Step 4: Open rtf file and start editing

+

Click on rtf and it will open the file in open-office and you are ready to edit, highlight and do pretty much what you like. +You may notice that some pictures are not coming on the page but all that is required is to do some formatting for openoffice which is quite straight forward.

+

Double click on the picture and in most cases it will be fixed if you click on 'keep Ratio' and changing the anchor to Page.

+

Alt Text

+

Some changes to page layout from Portrait to landscape may also help. To do so go to Format and select Page. It will open the dialogue box as shown below where you can switch between Portrait and landscape.

+

Alt Text

+

Step 5: Convert final version of rtf to epub and transfer to your phone (Optional)

+

Once done save your work with a different filename and follow steps 2 and 3 to import the saved rtf file into calibre and convert to epub this time (Keep output to epub in step 3) and then transfer it to your smartphone through mail or by usb transfer. +That's it all done. It's a long tutorial keeping in mind people who are new to Linux but carrying out the steps to convert doesn't take more than 2 minutes so trying this will be well worth it.

+

I must add that this may not be required if all you will ever need is to add notes and comments and highlight in pdf and then print from your own laptop / computer. In which case OKULAR is a far superior way of doing it and you must try it out. However, having said that I don't know many college going students who have easy access to Printers at their hostel and in all likelihood their print-outs will be from college library in which case solution above should come handy.

+ + + + + + + + + + + + + + + + + + + + + + +
+
+ + + + + +
+ + + +
+ + + +
+
+
+
+ + + + + + + + + + + + \ No newline at end of file diff --git a/2011/06/04/install-open-workbench-and-jre-on-wine-in-linux-mint.html b/2011/06/04/install-open-workbench-and-jre-on-wine-in-linux-mint.html new file mode 100644 index 0000000..245f2e3 --- /dev/null +++ b/2011/06/04/install-open-workbench-and-jre-on-wine-in-linux-mint.html @@ -0,0 +1,1741 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + Install Open Workbench and JRE on Wine in Linux Mint - Make Gadgets Work + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + Skip to content + + +
+
+ +
+ + + + +
+ + +
+ +
+ + + + + + + + + +
+
+ + + + + + + + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+
+ + +
+
+
+
+ + + + + + + + + + + + + + + + + + + + +

Install Open Workbench and JRE on Wine in Linux Mint

+ +

What is Open Workbench?

+

Open Workbench is a Project Planning Software comparable to Microsoft Project. There are mixed views on whether it is truly open source or not but as it is considered a very good alternative for Microsoft Project and it is free to download, it is something I as a Project Manager would want to know about. After all some of my clients are going to be using this. :)

+ + +

Now then, as of writing this article Open Workbench is not available from the well publicised site www.openworkbench.org which seems to be down. Instead it is available for download fromhttp://www.itdesign.de/en/products-solutions/open-workbench.html

+

As you will find that the installer is an exe and the product is made for Windows platform and requires Java Runtime Environment, we will need to either use Windows in virtualbox or Wine. I am going to cover below the steps for installing the software on Linux using Wine.

+

Steps to install Open Workbench and JRE on Wine V1.2.2 in Linux Mint:

+
    +
  1. Install JRE in wine
  2. +
  3. Install Open Workbench in wine
  4. +
  5. Configure wine to play well with Open Workbench
  6. +
  7. Configure icon to launch Open Workbench
  8. +
+ +

What do we need?

+
    +
  1. We will need to download the installer exe file from [http://www.itdesign.de/en/products-solutions/open-workbench.html](http://www.itdesign.de/en/products-solutions/open-workbench.html)
  2. +
  3. As the product requires Java Runtime Environment, we will need to download the JRE for windows.
  4. +
  5. We will need access to system32 folder of a WindowsXP machine or in Virtualbox.
  6. +
+

Step 1: Install JRE in wine

+

You can download the JRE file from http://www.oracle.com/technetwork/java/javase/downloads/index.html . Remember, we only need JRE not JDK so click the download button in second column. +This will open another page for you to select which JRE package you want to download.
+Alt Text +Click on the radio-button to accept license agreement and then select Windows x86 Offline for download.

+
+

Tip

+

I am assuming that your hardware is 32 bit, if not please select Windows x64 but it will not have the offline version and I have no way of checking if it will work or not; so please leave a comment either way as it will help others.

+
+

Alt Text

+

a) Copy the downloaded (jre-6u25-windows-i586.exe) file into .wine/drive_C/Program Files. You can do this using GUI on Linux Mint using steps below:

+
    +
  • Goto MENU-ALL APPLICATIONS-WINE and click on BROWSE C: DRIVE
  • +
  • Click on Program Files folder.
  • +
  • Paste the copied exe file here.
  • +
+

Alt Text +b) Right click on the exe file and select 'open with wine windows program loader'.
+c) Follow the installation wizard.
+Alt Text
+Once completed move on to next step.

+

Step 2: Install Open Workbench in wine

+

a) Download the “Open Workbench” installer file(Open_Workbench_Setup_1.1.6.exe) and copy it into .wine/drive_C/Program Files.

+
+

Tip

+

You can use Step 1 bullet "a" to reach drive_c and then click on windows folder and then on system32 folder.

+
+

b) Right click on the exe file and select 'open with wine windows program loader'.

+
+

Tip

+

You can use Step 1 bullet "b" to reach drive_c and then click on windows folder and then on system32 folder.

+
+

c) Follow the installation wizard.

+

Step 3: Configure wine to play well with Open Workbench +Due to some bug identified in Wine V1.2 the odbc32.dll file does not work and gives the error “err:module:attach_process_dlls “odbc32.dll” failed to initialize, aborting” if you try to run file from Terminal. +The following workaround is one suggested on Launchpad (https://bugs.launchpad.net/ubuntu/+source/wine1.2/+bug/572393) and does solve the problem.

+

I have just tried to make it easier to follow the instructions by providing screenshots and explaining each step.

+
    +
  1. +On the WindowsXP machine / virtualbox goto following location – C:\WINDOWS\system32
    +![Alt Text](../assets/images/2016/07/20110604_Fig_6.png) +
  2. +
  3. +Now copy odbc32.dll and odbcint.dll files onto the Linux machine at this location - .wine/drive_c/windows/system32 . +
  4. +
+
+

Tip

+

You can use Step 1 bullet "a" to reach drive_c and then click on windows folder and then on system32 folder.

+
+
    +
  1. +Now goto wine configuration (On Linux Mint: MENU-ALL APPLICATIONS-WINE-CONFIGURE WINE) +
  2. +
  3. +Click on Libraries tab. +
  4. +
  5. +From the dropdown select odbc32 and click add. +
  6. +
  7. +Click OK +
  8. +
+

Step 4: Configure Open Workbench icon to launch the application

+
    +
  1. +Open terminal (MENU - TERMINAL) +
  2. +
  3. +Type: `wine ~/.wine/drive_c/Program\ Files/Open\ Workbench/bin/npWBench.exe` +
  4. +
  5. +Once Open Workbench is opened we are sure the command works. Select this command and right click and select copy. +
  6. +
+
+

Tip

+

You can also press Ctrl+Shift+C to copy the selected text from terminal

+
+
    +
  1. +Close 'Open Workbench' and goto icon in MENU-ALL APPLICATIONS-WINE. +
  2. +
  3. +Right click the 'Open Workbench' icon and click on 'Edit Properties'. +
  4. +
  5. +In Command click on browse and navigate to following location: `.wine/drive_c/Program Files/Open Workbench/bin/ and select the file npWBench.exe`. +
  6. +
+

Alt Text

+
    +
  1. Now click 'OK'
  2. +
+

This is it. You are ready to use Open workbench on Linux using wine.

+ + + + + + + + + + + + + + + + + + + + + + +
+
+ + + + + +
+ + + +
+ + + +
+
+
+
+ + + + + + + + + + + + \ No newline at end of file diff --git a/2011/06/20/visio-alternative-on-linux-business-process-model-and-notation-tool.html b/2011/06/20/visio-alternative-on-linux-business-process-model-and-notation-tool.html new file mode 100644 index 0000000..e33726f --- /dev/null +++ b/2011/06/20/visio-alternative-on-linux-business-process-model-and-notation-tool.html @@ -0,0 +1,1655 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + Visio Alternative on Linux - Business Process Model and Notation Tool - Make Gadgets Work + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+
+ +
+ + + + +
+ + +
+ +
+ + + + + + + + + +
+
+ + + + + + + + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+
+ + +
+
+
+
+ + + + + + + + + + + + + + + + + + + + +

Visio Alternative on Linux - Business Process Model and Notation Tool

+ +
+

UPDATE

+

PENCIL is an opensource software which is a pretty good alternative as well with a smaller learning curve and is Opensource.

+
+ + +

I was recently looking for Visio alternatives on Linux and while Dia is good it lacks the oomph of Visio and hence not an easy sell for new and potential Linux converts.

+

Now good news for those open to enter the wonderful world of Linux is that any OS need not have an alternative for it to be usable to you, all it needs is the ability to let you run the software you find useful.

+

So this post isn't about why Linux is better. It is about knowing that there is something better than the software you have got accustomed to. If someone is adamant on continuing with the software they have fallen in love with, there is always WINE / Virtualbox but if you have opened to the idea of new OS but you are taking baby steps software like yEd can help you take a giant leap. yEd is written in Java and can run on Windows, Mac and of-course Linux.

+

Before we move into the steps of how to get it and use it let me bore you little with what are the other alternatives I explored. +During my quest I came across two other useful candidates Activiti 5.6, ARIS Express 2.3. What went against these and in favour of yEd? Read on:

+
    +
  1. +Ease of installation +
      +
    • +First on the list is ARIS Express 2.3. Now this requires registration which is my first gripe. I could have lived with that but as claimed on their website the product is meant for Windwos and is reported to be working fine for Linux. What they forgot to mention is on linux using Wine or atleast that is what it did on my set-up. Now am not sure if it has something to do with my installing JRE in wine for using Open Workbench (see last post) or it is because the product works on similar lines as Open Workbench installer but eighther way it does not run natively on Linux and for that reason alone it is unreliable and looses points even to Activiti. +
    • +
    • +For easiest installation, Activiti 5.6 can be added into Eclipse but that results in a bit complicated and not so user friendly user-experience. You will at-least need to have one Activiti project created in eclipse before you can draw using Activiti Diagram. (Check Screenshot) + ![Alt Text](/content/images/2016/07/20110620_Fig_2.png) +
    • +
    • +Above might be circumvented by installing standalone Activiti bit that involves configuring ANT and other stuff. While guide is there, it is not very straight forward and considering we want to win new users not scare them, it is best left as untouched topic. +
    • +
    +
  2. +
+
    +
  • Ease of Use - While all the three products yEd, Activiti and ARIS are superior to Visio, in terms of ease of use I think it's yEd which wins hands down even to Visio. The one aspect of this software that won me over was it's ability to auto align the whole flowchart / diagram with just one click and it can do it in several layouts.
  • +
+

OK, now that I have covered comparison, let's get down to how we install it on our beloved Linux Mint. It's really very simple. +Step 1: Go to this website - http://www.yworks.com/en/products_yed_download.html +Step 2: Download yEd for your platform. In our case for Linux Mint the 43 MB sh file in front of Linux. +Step 3:

+
    +
  • a) Go to the folder where it is downloaded, select the yEd-3.7.sh file and right click on it.
  • +
  • b) In the right click menu select Properties (last entry). This will open the yEd-3.7.sh properties box.
  • +
  • c) On the Properties box, click on Permissions tab.
  • +
  • d) Click the checkbox in front of Execute Allow executing file as program.
  • +
  • e) Click Close.
  • +
  • f) Double click on the yEd-3.7.sh file. It will display a dialogue box with four options Run in terminal, Display, Cancel, Run. Select Run in terminal
  • +
  • g) yEd will now install, it may give one small error but it did not affect anything at all so ignore it if you get it.
  • +
  • h) Follow the wizard and leave default options.
  • +
  • i) Once done, you will have yEd successfully installed and can be found in MENU ALL APPLICATIONS-OTHER.
  • +
  • j) Create a shortcut of yEd by dragging yEd from MENU ALL APPLICATIONS-OTHER onto desktop. You may need to make this dragged link executable by right clicking the link and following steps b,c and d.
  • +
+

All Done !!! +A quick tutorial from yWorks:

+ +

Find more on http://www.yworks.com/en/products_yed_videos.html +Having seen, how nice and easy it is to create the flowchart, let's also see the best feature of the product that is one click alignment.

+ + + + + + + + + + + + + + + + + + +
+ Comments from Disqus +

As I plan to move away from disqus comment system, I am just including the screenshots of comments just so any information shared by or to the readers is retained.

+

+ Disqus Comments + Disqus Comments

+
+ + + + + + +
+
+ + + + + +
+ + + +
+ + + +
+
+
+
+ + + + + + + + + + + + \ No newline at end of file diff --git a/2011/11/04/access-files-folders-directories-from-linux-mint-on-android.html b/2011/11/04/access-files-folders-directories-from-linux-mint-on-android.html new file mode 100644 index 0000000..16ae940 --- /dev/null +++ b/2011/11/04/access-files-folders-directories-from-linux-mint-on-android.html @@ -0,0 +1,1751 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + Access (files / folders) Directories from Linux Mint on Android - Make Gadgets Work + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + Skip to content + + +
+
+ +
+ + + + +
+ + +
+ +
+ + + + + + + + + +
+
+ + + + + + + + + + + + + + + +
+
+
+
+ + +
+
+
+
+ + + + + + + + + + + + + + + + + + + + +

Access (files / folders) Directories from Linux Mint on Android

+ +

Fig-1

+

Let me clear the air before anyone mentions. Yes, I know it's directories and not folders and yes I know many still call these folders and this may encourage the wrong usage but my aim is to help not educate but hopefully making this the first line in post will have some educational effect 😄.

+ + +

Right, so to start with, presented below is the problem statement:

+

We want to share particular folder on our home WiFi network such that all members of the family can access it using their gadget - laptop / smartphone / netbook etc. Now like a true project manager, I will scope it down by making an assumption that any gadget that runs a variant of linux - Android included. It's not to say that the solution will not work on iOS or Windows, it's to say that these operating systems are not covered but might as well work with right application downloaded.However the solution offered will make no such claims...:p. Having said that, I will actually only be covering Android in this article as for Linux on desktop, it is fairly straight forward by going through 'Network' places.

+

Ok now to the exciting stuff:

+

Step 1: Decide which Directory (Folder) you want to share.

+

For purpose of this demonstration, let's create a new folder called 'DemoShare'.

+

Step 2: Decide whether you want it to be accessed by specific users or anyone and everyone connected to your home network

+

a) If you would want the shared directory (folder) to be password protected go through all steps below.

+

b) If you would want to give guest access to all users, skip to Step 4.

+

Step 3

+

a) Create new User(s) - Let's create a new user - androshare. This can be done by following the images below:

+

Fig-2

+

Fig-3

+

Fig-4

+

Fig-5

+

Fig-6

+

Fig-7

+

Fig-8

+

Obviously you are free to be creative with the username. Just replace androshare with your username in subsequent commands.

+

b) Create samba username(s)/password(s)

+

Open the terminal and type the following command:

+

sudo smbpasswd -a androshare.

+

You will be asked for the root password and then new password for the username. Once you type in new password, you will be asked to retype the new password. Again as shown in image.

+

Image

+

c) Restart Samba

+

Open the terminal and type the following command:

+

sudo service smbd restart

+

Step 4: Update Share Properties of the Directory(ies) to be shared

+

Goto the folder "DemoShare" created in Step 1, right click on the folder and select properties. On the properties dialogue box click on Share tab. Click on share the folder checkbox.

+

Fig-10

+

For scenario (a) of Step 2 click on checkbox - "Allow users to create and delete files in this folder" as shown in the figure below and click on Create Share.

+

Fig-11

+

For scenario (b) of Step 2, mark the "Guest access" Checkbox too as shown in next figure before clicking the Create Share button.

+

Fig-12

+

After the Create Share button is clicked following dialogue will appear. Select "Add the permissions automatically"

+

Fig-11

+

The folder icon will then change to show a share flag as shown below.

+

Image

+

Step 5: Download and install the file explorer app

+

ES File explorer is what I use and recommend. It can be downloaded from market here - https://market.android.com/details?id=com.estrongs.android.pop

+

Image

+

Step 6: Configure the app to access the shared directory

+

On your computer, open the terminal and type: +ifconfig

+

In the resulting information, locate wlan0 (last entry) and under that in second line you will find something like 'inet addr: 192.168.1.74', Note this down.

+

Open the app and goto LAN tab.

+

Fig-16

+

Press the menu button and click on 'New'.

+

Now click on 'Server'. This will open the 'New/Edit Samba Server Screen'.

+

Fig-17

+

Complete it as shown with following information:

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
FieldScenario AScenario B
ServerIP Address from aboveIP Address from above
UsernameAndroshareBLANK
PasswordAs given in Step 3aBLANK
AnonymousBLANKSelect the checkbox
+

Click OK.

+

Now if you click on the IP address, it should show the shared directory. As you can see in the screenshot at the beginning of this post, I have already shared my Calibre Library and it does make life really simple. I can download files and e-books without actually going to my laptop.

+

That is all there is to it.

+

Hope you find it useful.

+ + + + + + + + + + + + + + + + + + + + + + +
+
+ + + + + +
+ + + +
+ + + +
+
+
+
+ + + + + + + + + + + + \ No newline at end of file diff --git a/2011/11/06/openvpn-on-linux-mint-to-access-us.html b/2011/11/06/openvpn-on-linux-mint-to-access-us.html new file mode 100644 index 0000000..8bf4168 --- /dev/null +++ b/2011/11/06/openvpn-on-linux-mint-to-access-us.html @@ -0,0 +1,1717 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + OpenVPN on Linux Mint to access US sites - Make Gadgets Work + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + Skip to content + + +
+
+ +
+ + + + +
+ + +
+ +
+ + + + + + + + + +
+
+ + + + + + + + + + + + + + + +
+
+
+
+ + +
+
+
+
+ + + + + + + + + + + + + + + + + + + + +

OpenVPN on Linux Mint to access US sites

+ +
+
+

UPDATE

+

LOOKS LIKE HOSTIZZLE IS NOT WORKING ANYMORE. THE STEPS IN THIS GUIDE WILL STILL BE RELEVANT FOR SETTING UP OPEN VPN, JUST THAT YOU WILL NEED TO FIND SOME OTHER PROVIDER.**

+
+
+ + + +

I received a Google Music invite and as it is only available in US, I used OpenVPN on linux. Now this is a handy little trick to bypass the geographical constraints that are placed to block services on net. All this without opening the windows box to install dodgy looking services of Hotspot Shield which anyway does not work on Linux.

+

Ever come across 'This video is not available in your country.' on you tube or ever wanted to access Hulu only to be greeted by a similar message. Well then, if yes is your answer, openVPN is the solution to such sorrows.

+

It also comes with some other benefits such as anonymous browsing but then highlighting benefits of VPN is not exactly the aim of this post. The aim of this post is:

+

To configure a free VPN service that allows you to bypass country restrictions for US based sites. Keyword - US. As the site I am going to use only gives a US IP. Ofcourse if we remove the word free it opens up a range of possibilities which I will leave the readers to explore. 😄

+

Alright, let's get on with the business:

+

Step 1: Install OpenVPN

+

Open Synaptics and type OpenVPN in 'Quick Search'. Then select all the packages marked green in screenshot below but not in your synaptic window and click on 'Apply'.

+

20111106_Fig_1.png

+

Then type 'pptp' in 'Quick Search' and select all the packages marked green in the screenshot below but not in your synaptic window and click on 'Apply'.

+

20111106_Fig_2

+

Step 2: Register with the free OpenVPN provider 'Hostizzle'

+

Open the website http://hostizzle.com/ and follow the screenshots below:

+

20111106_Fig_3

+

image

+

image

+

Step 3: Set-up OpenVPN on Linux Mint

+

Open Terminal and type the following command:

+
gksudo nautilus /etc/openvpn/
+
+

You will be asked for root password and the File Explorer will be opened with root access. This is necessary as /opt directory only gives write access to root and as you wil see next step requires some files to be extracted in this directory. This extracted directory will be some long number.

+
+

Tip

+

Please be extremely careful now, as you are accessing your file system as root.

+
+

Now copy the zip file downloaded from Hostizzle in Step 2, to this directory and extract it here.

+

Step 4: Configure network manager

+

Open the Network Manager as shown in following screenshot:

+

Menu > All Applications > Preferences > Network Connections

+

image

+

This will open the following window:

+

image

+

Now click on VPN tab and then on Import button as shown in next screenshot.

+

It will open the file selection box. Navigate to File System/etc/openvpn/ and now open the long numbered directory that was extracted in Step 3(a) as shown in next figure.

+

image

+

Here select the .ovpn file and click Open. Following dialogue box will open.

+

Fig-9

+

Just accept defaults and click 'Apply'.

+

Then Click on 'Close' on Network Connections window.

+

image

+

Step 5: Connect to the OpenVPN

+

Restart your system and once it is connected to your home WiFi, click on Network Indicator in the panel and select 'VPN Connections'.

+

image

+

This will list all available VPN connections as can be seen in the screenshot. Select the VPN name with big number which is what we have just configured.

+

image

+

Once done if everything was done correctly, it should connect to VPN and you should be able to browse with an american IP address. +Some key points to remember:

+

Free service of Hostizzle is quite generous compared to so many other options I explored, yet it is worth knowing the restrictions:

+
    +
  1. 100GB of bandwidth each month.
  2. +
  3. US IP
  4. +
  5. Limited to one month. You need to download new Keys and configure VPN using steps below each month.
  6. +
+

Now considering that it takes less than 5 minutes to complete these steps and that it is a free alternative with a generous bandwidth, I would say it is all worth it.

+

Ofcourse, if you are not happy with the restrictions, there are some paid alternatives that can be implemented for as low as $4 per month from various providers, Hostizzle included. Go shopping !!!

+ + + + + + + + + + + + + + + + + + + + + + +
+
+ + + + + +
+ + + +
+ + + +
+
+
+
+ + + + + + + + + + + + \ No newline at end of file diff --git a/2012/02/20/how-to-boot-from-usb-when-bios-does-not-have-the-option.html b/2012/02/20/how-to-boot-from-usb-when-bios-does-not-have-the-option.html new file mode 100644 index 0000000..dfc4e78 --- /dev/null +++ b/2012/02/20/how-to-boot-from-usb-when-bios-does-not-have-the-option.html @@ -0,0 +1,1778 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + How to boot from USB when BIOS does not have the option. - Make Gadgets Work + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+
+ +
+ + + + +
+ + +
+ +
+ + + + + + + + + +
+
+ + + + + + + + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+
+ + +
+
+
+
+ + + + + + + + + + + + + + + + + + + + +

How to boot from USB when BIOS does not have the option.

+ +

I have an old Sony VAIO which is not in it's best of health and has long been really a companion for my telly, faithfully streaming media from bbc iplayer, youtube, dailymotion and likes. Internet enabled TV arrived in my home long long back 😄.

+

Now the thing with this laptop is that it's kinda gimpy - inbuilt keyboard won't work, battery is dead and it hangs on life with constant supply of energy from the AC source on the wall and the one thing that helps me load new OS on this machine - the optical reader - is temperamental and may or may not work and is moody in selecting which CD / DVD it will read and which it won't. It does in particular like CD's authored by Linux Format guys though. Writing is a skill it has forgotten long back and if it reads something, anything I am found celebrating.

+ + +

Anyway, I had ubuntu installed on this laptop for quite some time but as this laptop has one more flaw - the nvidia graphic card - and the latest update from ubuntu broke the nvidia drivers which aren't all that well supported anyway, I was being forced to reformat the machine. So I decided to try a new distro named Bodhi Linux which is very cool and uses Enlightenment as desktop which is very very good and way better than Unity and some might argue even Gnome 3.2. However, in order to do so I had to cross the hurdle of burning a CD that my laptop's Optical Drive will find intresting enough to read.

+

I have checked and rechecked the BIOS of this laptop and there is no way to make BIOS understand that it can boot from USB. The only options it provides are Hard Drive, Floppy Disk, Optical Reader and Network Boot. None of these were particularly useful for reasons explained above and unfortunately I was hitting the wall. So I started looking for alternative ways to get Bodhi Linux installed. Alternative is what I found in Plop Boot Manager.

+

Now open source enthusiasts at this point be aware, this nifty piece of software is not open source but it is so useful that this one minor flaw must be completely ignored. It gives you options to boot from USB in several ways - you can burn a CD and fool BIOS to boot from CD onto plop boot manager which in turn allows you to boot from device of your choice aka USB, it can also be put on floppy or the option that I have used - install on hard drive and configured through GRUB.

+

So presented below without further ado is the guide to how you can install this on your hard drive but before I do so a quick thanks to several boards and posts I referred in the process of making this work:

+

Steps:

+
+

Info

+

Please note that these instructions will work for machines that have one of these OS installed: Bodhi Linux and Ubuntu variants. For other linux versions it should be on similar lines

+
+
    +
  1. +

    Download the Plop Boot Manager (plpbt-5.0.14.zip file) on the machine where you want to achieve the result from this link

    +

    Fig-1

    +
  2. +
  3. +

    Extract the zip file in folder where it was downloaded. In my case I have my browser setting set to download everything to 'Download' folder.

    +

    Fig-2

    +
  4. +
  5. +

    Now open the terminal and type following command relevant to your distro:

    +
      +
    • +

      For UBUNTU

      +
      gksu nautilus  /boot
      +
      +
    • +
    • +

      "For Bodhi"

      +
      sudo pcmanfm /boot
      +
      +
    • +
    +

    You will be presented with a dialogue box to enter password. Once you enter the password you will be presented the contents of boot folder as shown below.

    +

    Fig-3

    +
  6. +
  7. +

    Now

    +
      +
    1. +

      Go to the extracted plpbt-5.0.14 from step 2, click on Linux folder

      +

      Fig-4

      +
    2. +
    3. +

      Then copy the files - 'plpbt.bin' and 'plpcfgbt' and paste them in the boot folder opened through step 3.

      +

      Fig-5

      +
    4. +
    +
  8. +
  9. +

    Once the files are copied in boot folder, double click on folder named grub and there open the file named 'grub.cfg' in texteditor.

    +

    Fig-6

    +

    Once the file is opened, press 'Ctrl+f' and search for string 'END /etc/grub.d/10_linux'. Now copy the text as highlighted in screen below and paste it in a new text-editor window.

    +

    Fig-7

    +
  10. +
  11. +

    In the new text editor after pasting the four lines from above

    +
      +
    1. +

      Edit the fourth line so it reads as below:

      +
      linux16 /boot/plpbt.bin
      +
      +
    2. +
    3. +

      Complete the block in new editor to read as below, keeping the first three lines intact from what was copied in step 5 from grub.cfg file. In our example it will read as below:

      +
      1
      +2
      +3
      +4
      +5
      +6
      menuentry 'Plop Bootmanager' {
      +insmod ext2
      +set root='(hd0,6)'
      +search --no-floppy --fs-uuid --set cb7a6eb7-b355-4d0f-865e-f7312880f887
      +linux16 /boot/plpbt.bin
      +}
      +
      +
    4. +
    +

    Once again, this is important so remember the final structure will be achieved by following steps below.

    +

    Copy Paste this in Line 1: menuentry 'Plop Bootmanager' {

    +

    Next three lines remain same as copied from step 5:

    +
    1
    +2
    +3
    insmod ext2
    +set root='(hd0,6)'
    +search --no-floppy --fs-uuid --set cb7a6eb7-b355-4d0f-865e-f7312880f887
    +
    +

    Then the fourth line will be edited to look as shown below. You can copy paste this in fourth line.

    +
    linux16 /boot/plpbt.bin
    +
    +

    Fifth line will be closing bracket - }

    +
  12. +
  13. +

    Now open another terminal window and type following command relevant to your distro:

    +
      +
    • +

      For UBUNTU

      +
      gksu nautilus  /etc/grub.d
      +
      +
    • +
    • +

      "For Bodhi"

      +
      sudo pcmanfm /etc/grub.d
      +
      +
    • +
    +

    You will be presented with a dialogue box to enter password. Once you enter the password you will be presented the contents of boot folder as shown below.

    +

    Fig-8

    +
  14. +
  15. +

    Open the file 40_custom (highlighted in screenshot above) in texteditor - gedit on ubuntu or leafpad on Bodhi and paste the block from step 6(b) in this file and save it.

    +
     1
    + 2
    + 3
    + 4
    + 5
    + 6
    + 7
    + 8
    + 9
    +10
    +11
    #!/bin/sh
    +exec tail -n +3 $0
    +## This file provides an easy way to add custom menu entries.  Simply type the
    +## menu entries you want to add after this comment.  Be careful not to change
    +## the 'exec tail' line above.
    +menuentry 'Plop Bootmanager' {
    +insmod ext2
    +set root='(hd0,6)'
    +search --no-floppy --fs-uuid --set cb7a6eb7-b355-4d0f-865e-f7312880f887
    +linux16 /boot/plpbt.bin
    +}
    +
    +
    +

    Important

    +

    Press Enter at-least twice after pasting to ensure there are atleast two new lines below closing bracket.

    +
    +
  16. +
  17. +

    Close all windows and open terminal once again and type following command:

    +
    sudo update-grub
    +
    +
  18. +
  19. +

    Now open /boot/grub/grub.conf and you should find the following entry on it:

    +
     1
    + 2
    + 3
    + 4
    + 5
    + 6
    + 7
    + 8
    + 9
    +10
    +11
    #### BEGIN /etc/grub.d/40_custom ###
    +## This file provides an easy way to add custom menu entries.  Simply type the
    +## menu entries you want to add after this comment.  Be careful not to change
    +## the 'exec tail' line above.
    +menuentry 'Plop Bootmanager' {
    +insmod ext2
    +set root='(hd0,6)'
    +search --no-floppy --fs-uuid --set cb7a6eb7-b355-4d0f-865e-f7312880f887
    +linux16 /boot/plpbt.bin
    +}
    +#### END /etc/grub.d/40_custom ###
    +
    +
  20. +
  21. +

    Now reboot your machine with your Live USB plugged in. If you already dual boot your grub will show on restart and will have an additional option in the end - 'Plop Bootmanager'.

    +

    If however, you just have single OS ubuntu, press Shift once the bios logo shows up and keep holding for getting the system to show Grub.

    +

    Once Grub is shown it will have the additional 'Plop Bootmanager' menu entry on grub.

    +

    Select the 'Plop Bootmanager' and press enter.

    +
  22. +
  23. +

    Plop Bootmanager will show option to boot from USB. It was third option for me. Select and press enter. If your Live USB is working, you will be able to load OS from it.

    +
  24. +
+

This is it. You can check the video

+

Please do let me know in comments if you found this useful. If there are any other ways I will be keen to hear those too.

+ + + + + + + + + + + + + + + + + + + + + + +
+
+ + + + + +
+ + + +
+ + + +
+
+
+
+ + + + + + + + + + + + \ No newline at end of file diff --git a/2012/03/04/stagevu-divx-video-on-bodhi-linux.html b/2012/03/04/stagevu-divx-video-on-bodhi-linux.html new file mode 100644 index 0000000..97fb835 --- /dev/null +++ b/2012/03/04/stagevu-divx-video-on-bodhi-linux.html @@ -0,0 +1,1630 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + StageVu / DivX video on Bodhi Linux - Make Gadgets Work + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+
+ +
+ + + + +
+ + +
+ +
+ + + + + + + + + +
+
+ + + + + + + + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+
+ + +
+
+
+
+ + + + + + + + + + + + + + + + + + + + +

StageVu / DivX video on Bodhi Linux

+ +

If you read my last post you will know I have been playing with Bodhi Linux lately. One of the selling point for this distro is it's minimalistic approach. However, that also means it doesn't come with some of the media packages that help stream all type of media - stagevu (DivX) included.

+

Fortunately it does not require lot of effort to get them all working. At-least for me it was quite quick.

+

The steps I followed are listed below:

+ + + +

Let me know your experiences and tips in comments.

+ + + + + + + + + + + + + + + + + + + + + + +
+
+ + + + + +
+ + + +
+ + + +
+
+
+
+ + + + + + + + + + + + \ No newline at end of file diff --git a/2012/03/16/exchange-2007-on-thunderbird-using-davmail.html b/2012/03/16/exchange-2007-on-thunderbird-using-davmail.html new file mode 100644 index 0000000..a82c5ca --- /dev/null +++ b/2012/03/16/exchange-2007-on-thunderbird-using-davmail.html @@ -0,0 +1,1723 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + Exchange 2007 on Thunderbird using DAVMail - Make Gadgets Work + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + Skip to content + + +
+
+ +
+ + + + +
+ + +
+ +
+ + + + + + + + + +
+
+ + + + + + + + + + + + + + + +
+
+
+
+ + +
+
+
+
+ + + + + + + + + + + + + + + + + + + + +

Exchange 2007 on Thunderbird using DAVMail

+ +

Background

+

The start of this week was like a nightmare for me. Whole family was down with flu and I had the fever that is probably the highest ever of my entire life at 40.5 C (~ 106 F).

+

Anyway, surviving that was easier compared to the aftermaths of this health problem that forced me to stay in bed and inadvertently deal with office mails at home on a non-IE browser with Lite version which is crap and makes you feel miserable enough to kill yourself.

+

So the option I had was to either boot windows on a virtual machine or find a solution within linux. Obviously, I prefer the latter and was glad to work out a solution that can help me avoid booting windows.

+

I have listed below the steps I followed to achieve this, though in all honesty the documentation is quite good on sourceforge site itself. It's just that some of their screenshots are dated and in French (literally, no pun intended).

+

Now there are several options floating around but this set-up works flawlessly for me and so I will obviously recommend this over other methods.

+

It's so easy to configure that there really is no reason not to give it a shot.

+ + +

Step 1. Install DAVMail

+

You can get the latest version from sourcefourge by following this link.

+

Fig-1

+
+

Tip

+

Select the download based on your distro. For Linux Mint 12 it will be Green Box and for non-debian based distros the red.

+
+

Now double click on the downloaded .deb file and DAVMail will be installed taking care of any required dependencies.

+

Step 2. Configure DAVMail

+

Once installed, you can open DAVMail on Linux Mint 12 under Internet.

+

Fig-2

+

You can copy all the settings from this screenshot, leave them with what is there by default or change if you need to. Most important part is the first field OWA (Exchange) URL. You must paste your exchange 2007 OWA URL here.

+

Fig-3

+

Believe it or not, that's all the configuration you need to do for DavMail.

+

Step 3. Configure Thunderbird Email Client

+

Now over to configuring the Thunderbird Email Client. Enter Details and Click on Continue. It will show next screen by itself.

+

Fig-4

+

Follow instructions as per next screenshot.

+

Fig-5

+

If you see following warning, tick the checkbox and Click on Create Account.

+

Fig-6

+

After this you may be presented with a dialogue box to enter the username and password once again. If so provide details. Remember to try just abc.xyz as username for abc.xyz@officemail.com and only if that does not work and you are presented with the dialogue box again should you try the whole email id as username.

+

It does take quite some time in first run as Thunderbird downloads all the mails. I was happy with the speed though YMMV.

+

I have applied an MS Office based theme that you can find in add-ons directory on this link.

+

Finally my mailbox looks as below:

+

Fig-7

+

Step 4. Configure Calendar

+

On Linux Mint 12 Lightning the Thunderbird calendar client does not come pre-installed. So head over to Synaptic package manager and search for 'xul-ext-light', select the shown items and install them.

+

Fig-8

+

Once installed, restart thunderbird and you will see calendar icon and Task Pane.

+

Fig-9

+

Either click on Calendar or just press Ctrl+Shift+C. This will open Calendar view as shown below:

+

Fig-10

+

Select 'New Calendar'.

+

Fig-11

+

Select 'On the Network' radio button and click on 'Next'

+

Fig-12

+

Select CalDAV radio button, and fill the location field with http://localhost:1081/users/ankit@officemail.com/calendar.

+
+

Important

+

Make sure you adjust the port as per your settings of Step 2.

+
+

Fig-13

+

Give a name to this Calendar - say Office Cal for instance - and click Next.

+

Fig-14

+

After this you may be presented with a dialogue box to enter the username and password once again. If so provide details. Here provide abc.xyz@officemail.com and if that does not work and you are presented with the dialogue box again try with just the abc.xyz as username.

+

Once connected, your Calendar will be in sync as will be your tasks.

+

Step 5: Configure Global Address Book

+

Open Address Book and then select File > New > LDAP Directory. It will open the following box.

+

Fig-15

+

Fill as shown below and make sure Port number is as advised in red text. Then Click 'OK'

+

Fig-16

+

That is it. Test and see if you are now able to update your calendar, tasks, emails etc. and if all has gone as explained above you should be good to go.

+ + + + + + + + + + + + + + + + + + + + + + +
+
+ + + + + +
+ + + +
+ + + +
+
+
+
+ + + + + + + + + + + + \ No newline at end of file diff --git a/2012/08/06/top-10-android-apps-that-i-use.html b/2012/08/06/top-10-android-apps-that-i-use.html new file mode 100644 index 0000000..835cb6a --- /dev/null +++ b/2012/08/06/top-10-android-apps-that-i-use.html @@ -0,0 +1,1889 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + Top 10 Android Apps that I use !!!! - Make Gadgets Work + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + Skip to content + + +
+
+ +
+ + + + +
+ + +
+ +
+ + + + + + + + + +
+
+ + + + + + + + + + + + + + + +
+
+
+
+ + +
+
+
+
+ + + + + + + + + + + + + + + + + + + + +

Top 10 Android Apps that I use !!!!

+ +

I have been using Android Phones and Tablets for quite some time now and have +monitored my use for apps that I use on a daily basis and those I would not even +come to know if they were removed without my knowledge. So clearly there is a +personal perspective on the list below:

+ + +

Gmail

+

The most obvious and the most heavily used app. No introduction required.

+
    +
  • +

    What I like best?

    +
      +
    • Clean Interface
    • +
    • Push Notification
    • +
    +
  • +
  • +

    What can improve?

    +
      +
    • Ability to zoom messages.
    • +
    +
  • +
+

AppyGeek / AppyGeek for Tablet

+

I use this app on a daily basis to get my daily dose of news in technical world.

+
    +
  • +

    What I like best?

    +
      +
    • It pulls news based on keywords you can select. So my feed includes news on tags such as "Linux", "Smartphones", "Galaxy Tab", "Nexus", "Raspberry Pi" ...you get the gist.
    • +
    +
  • +
  • +

    What can improve?

    +
      +
    • Ability to access to stories in offline mode.
    • +
    +
  • +
+

Guardian Anywhere

+

Fine, I admit wholeheartedly, I am left of the centre in my politics. Now with that out of the way, I prefer this app over the official Guardian app as this downloads content overnight.

+
    +
  • +

    What I like best?

    +
      +
    • Offline Access to stories.
    • +
    • You can schedule the time for download, I prefer 4:00 AM so I have all fresh news first thing in the morning.
    • +
    • "Picks" tab which learns my taste over time and provides stories to match it.
    • +
    +
  • +
  • +

    What can improve?

    +
      +
    • Ability to copy a small section of text from the story.
    • +
    +
  • +
+

Financisto

+

It is an opensource app to manage your budget and track your expenses. It's very comprehensive and one of the best app available on any mobile platform in my opinion. It does require initial set-up and perhaps something I may write about in another post soon but generally I find using it a very easy affair.

+
    +
  • +

    What I like best?

    +
      +
    • Complete Package equivalent to any desktop finance management software.
    • +
    +
  • +
  • +

    What can improve?

    +
      +
    • App can do with a basic list of category to reduce the burden of creating list of categories from scratch.
    • +
    • Improved Documentation
    • +
    +
  • +
+

QuickPic

+

It is a photo gallery which have got accustomed to as initially the stock gallery app on Android was not very good. However, recent gallery app has seen some very good enhancements yet I still feel more at home using QuickPic.

+
    +
  • +

    What I like best?

    +
      +
    • Ability to hide folders that have downloaded photos from other apps and are not taken by phone camera.
    • +
    • Ability to share a photo right from within the app through gmail, email, bluetooth, G+, FB etc.
    • +
    +
  • +
  • +

    What can improve?

    +
      +
    • I don't think there is much to improve. It's a pretty well rounded app in my opinion.
    • +
    +
  • +
+

Android Assistant

+

It's a android system utility for monitoring system tasks, killing apps etc. All these are not what make it interesting though as there are several apps out there doing the same stuff. What I like most is listed below:

+
    +
  • +

    What I like best?

    +
      +
    • With latest version, it prompts to move any newly installed/updated app to SD card if it is possible.
    • +
    • It shows the total start-up time and allows you to select apps that should be silent at start-up.
    • +
    • It allows for batch uninstall, which comes quite handy when on a system clean-up spree.
    • +
    +
  • +
  • +

    What can improve?

    +
      +
    • Can do with a more glossy makeover.
    • +
    +
  • +
+

ES File Explorer File Manager

+

The primary function of this app is to allow user to browse the file system but it really has loads of features under the hood.

+
    +
  • +

    What I like best?

    +
      +
    • Simplicity.
    • +
    • Cloud Connection to support likes of Dropbox, Google Drive, Ubuntu One etc.
    • +
    • FTP
    • +
    • Everything really.
    • +
    +
  • +
  • +

    What can improve?

    +
      +
    • This app covers the area of accessing files, network and so on. It has a lots to offer but I don't think it's used to it's full potential by many. I think this app can do with a strong tutorial for each of it's feature.
    • +
    +
  • +
+

Profile Scheduler

+

This is better app than Smart Profile and I have found that it's ability to switch profile based on WiFi saves me a complex set-up that would have been based on time but even so the time based set-up is much easier and straight forward and it does allow priority over rules which makes things super easy. It's not a drain on battery and I am very satisfied with this app. Nothing to improve really.

+

Smart Profile

+

This app is a profile management tool that helps in switching to a profile based on user defined time slots. I have FOUR profiles that I switch between Sleep (Completely Silent Mode every day from 23:45), Work (Vibrate mode Monday to Friday from 09:00), Normal (Monday to Friday from 17:00), 0600 Weekends + Mornings (Normal Profile everyday from 06:00). How it works then is Starting Monday morning 06:00 phone goes on normal mode, then at 09:00 turns to vibrate mode, then at 17:00 switches to Normal profile and in night at 23:45 goes to sleep mode. This continues till Saturday morning but as "Work Profile" is scheduled only from Monday to Friday so from Saturday 06:00 onwards phone continues on normal mode whole day till 23:45 in night when it switches to sleep mode, only to get back to normal mode on Sunday at 06:00. Then again remains on normal mode whole of Sunday and in night 23:45 goes back to sleep mode. Then the cycle starts again.

+
    +
  • +

    What I like best?

    +
      +
    • Free yet effective.
    • +
    +
  • +
  • +

    What can improve?

    +
      +
    • As you can see from my explanation above, it is a bit complex to set-up. There was an app called audioguru which stopped working on ICS forcing me to switch but it had a simpler interface. That said in free version of audioguru there was no ability to schedule weekend so this is much better at the lovely price tag of "free". 😄
    • +
    +
  • +
+

HulloMail

+

It's a visual voicemail app that works with most of the providers in the UK for their pay-monthly customers.

+
    +
  • +

    What I like best?

    +
      +
    • Ability to select the voicemail I want to listen to first.
    • +
    • Replay the important part of voicemail
    • +
    • Store VM as mp3 for future refrence.
    • +
    +
  • +
  • +

    What can improve?

    +
      +
    • I don't want much else from this app in it's free version. So it's alright as is.
    • +
    +
  • +
+

Go SMS Pro

+

It is a SMS client which is better than the stock client as it provides many useful additional features

+
    +
  • +

    What I like best?

    +
      +
    • Ability to schedule SMS.
    • +
    • Ability to send SMS in batches / groups
    • +
    • Cool skins
    • +
    • Go Chat is another useful feature as long as your mates are using it too.
    • +
    +
  • +
  • +

    What can improve?

    +
      +
    • They are improving it all the time but I can't think of any complaints at the moment.
    • +
    +
  • +
+ + + + + + + + + + + + + + + + + + + + + + +
+
+ + + + + +
+ + + +
+ + + +
+
+
+
+ + + + + + + + + + + + \ No newline at end of file diff --git a/2012/10/15/flash-samsung-galaxy-s-gt-i9000-with-cyanogenmod-9-on-linux.html b/2012/10/15/flash-samsung-galaxy-s-gt-i9000-with-cyanogenmod-9-on-linux.html new file mode 100644 index 0000000..f2a8462 --- /dev/null +++ b/2012/10/15/flash-samsung-galaxy-s-gt-i9000-with-cyanogenmod-9-on-linux.html @@ -0,0 +1,1647 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + Flash Samsung Galaxy S (GT -I9000) with Cyanogenmod 9 on Linux - Make Gadgets Work + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+
+ +
+ + + + +
+ + +
+ +
+ + + + + + + + + +
+
+ + + + + + + + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+
+ + +
+
+
+
+ + + + + + + + + + + + + + + + + + + + +

Flash Samsung Galaxy S (GT -I9000) with Cyanogenmod 9 on Linux

+ +

I had an old Samsung Galaxy S which was still on stock ROM hence it only ever got to gingerbread and then Samsung just decided not to upgrade and I upgraded the phone so this little gadget was till yesterday destined to live with the old gingerbread. +Then yesterday, I just decided to play around with it and started reading so I know what are my options. Now wiki guide on Cyanogenmod site is quite nicely written but there were one or two steps here and there which had me confused for a little while so here is my usual step-by-step guide on how to go about it.

+
+

UPDATE

+

Uploaded all the files to mediafire as requested in one of the comments below. These can be downloaded from this link.

+
+ + +

Step 1:

+
    +
  1. Download Heimdall Suite 1.3.2 Command-line Binary for your OS from here ; for Linux Mint you can use the Ubuntu link and install the downloaded 'heimdall_1.3.2_i386.deb' file.
  2. +
  3. Download hardcore's Kernel with the ClockworkMod Recovery 2.5 here. This will download a file named 'hardcore-speedmod.tar'. I am assuming that it will be saved in 'Downloads' directory but if you have a different location, please replace 'Downloads' with appropriate directory.
  4. +
+

Step 2: Just to avoid any confusion, make a new directory in Downloads and name it 'Galaxy_S'.

+

Step 3: Copy the downloaded file (from 1-b) 'hardcore-speedmod.tar' into the new directory 'Galaxy_S'

+

Step 4: Now right-click on 'hardcore-speedmod.tar' and select extract here as shown. This will extract the file zImage into the directory Galaxy_S.

+

Fig-1

+

Step 5: Connect microUSB cable to your computer but not the phone.

+

Step 6: Power off the Samsung Galaxy S.

+

Step 7: Connect the microUSB cable to Samsung Galaxy S.

+

Step 8: Boot the phone in download mode by holding 'HOME+Volume Down+POWER' buttons.

+

Step 9: Open the terminal and type following commands:

+
1
+2
cd Downloads/Galaxy_S
+sudo heimdall flash --kernel zImage
+
+

A blue transfer bar will appear on the phone showing the kernel being transferred. Once completed, the device will reboot automatically. +Step 10: Disconnect the phone from microUSB cable, switch it on and connect to your computer using the microUSB cable as mass storage.

+
+

Tip

+

You may need to go to phone settings and change USB connection settings to be able to connect the phone as mass storage.

+
+

Step 11: Download the latest Cyanogenmod ROM from here.

+

Fig-2

+

Step 12: Follow this link to land at above page and then download the latest version of Google Apps.

+

Fig-3

+

Step 13: You will now have following two zip files in your 'Downloads' directory:

+
    +
  1. cm-9.1.0-galaxysmtd.zip from Step 11.
  2. +
  3. gapps-ics-20120317-signed.zip from Step 12
  4. +
+

Copy these two files into the root directory of the Samsung Galaxy S.

+

Step 14: Now, disconnect the phone from microUSB and switch it off.

+

Step 15: Boot the phone in Recovery mode by holding 'HOME+Volume Up+POWER' buttons. You will be presented with various recovery options such as reboot phone etc.

+

Step 17: Now use the Volume Up and Volume Down buttons to navigate options. Use Volume Down button to reach the option to Wipe data/factory reset and press POWER button to select this option.

+

Step 18: Once done, Use Volume Down button to reach the option to wipe cache partition and press POWER button to select this option.

+

Step 19: Next select 'Install zip from sdcard' which will present another set of options where you should select 'Choose zip from sdcard'

+

Step 20: Now you will see the list of files on your SD Cards root directory. Select the file 'cm-9.1.0-galaxysmtd.zip' and on following screen of options select 'Yes'.

+

Step 21: Once installed, select +++++Go Back+++++ and again select 'Install zip from sdcard' which will present another set of options where you should select 'Choose zip from sdcard'

+

Step 22: This time select the file 'gapps-ics-20120317-signed.zip' and on following screen of options select 'Yes'.

+

Step 23: Once installed, select +++++Go Back+++++ to return to main menu.

+

Step 24: On main menu select 'Reboot System now' option.

+

Step 25: If all has gone as above, your phone will restart and after a while you will see cyanogenmod flash screen. This screen will be there for a good 1 to 2 minutes. Don't panic and dont mess. Let system do it's work and in some time you would have given a fresh lease of life to your old dieing Samsung Galaxy S.

+ + + + + + + + + + + + + + + + + + + + + + +
+
+ + + + + +
+ + + +
+ + + +
+
+
+
+ + + + + + + + + + + + \ No newline at end of file diff --git a/2012/10/17/prepare-linux-mint-13-for-android-development.html b/2012/10/17/prepare-linux-mint-13-for-android-development.html new file mode 100644 index 0000000..d7757c8 --- /dev/null +++ b/2012/10/17/prepare-linux-mint-13-for-android-development.html @@ -0,0 +1,1979 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + Prepare Linux Mint 13 for Android development - Make Gadgets Work + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + Skip to content + + +
+
+ +
+ + + + +
+ + +
+ +
+ + + + + + + + + +
+
+ + + + + + + + + + + + + + + +
+
+
+
+ + +
+
+
+
+ + + + + + + + + + + + + + + + + + + + +

Prepare Linux Mint 13 for Android development

+ +

Background

+

Few weeks back I updated to the latest Linux Mint offering 'Maya' a.k.a Linux Mint 13. Now this is a LTS (Long Term Support) version and I wanted to be in a position to install everything right just so I can keep it for a longer duration and hence have been taking my time configuring stuff.

+

Last time when I had set up system for Android Development I remember messing up a lot and ending up installing too many things here and there and in the process did learn how to do it properly. I did not document that as a blog as it was too fragmented an experience at that time but this time round I did it properly and everything (well, okay, almost everything) was perfect.

+ + +

There is lot of material on the web but again that is what led to a less than perfect install last time as it is all disjointed, making the sequence go wrong, using one way for one thing another way for second and ending us with a not so nice experience distracting you from what you want to do, start developing something on android platform or perhaps just get the adb set-up to flash your nexus phone.

+

So presented below is a guide that will help you prepare your Linux Mint 13 or equivalent distro for you to start android development. +You will notice that these notes do not require downloading 'android sdk' separately and that is to save time and effort, trust me.

+

Step 1: Download required files

+

Download following files:

+

Fig-2

+

Links to original file locations: +* Script to Update Java (Updated link to the latest version of script as highlighted in screenshot.) +* Java 7 SDK +* Java 7 Samples and Demo +* Java 7 API Docs +* Java 6 Samples and Demo +* Java 6 API Docs +* Java 6 SDK

+

If you don't want to move around different webpages and websites, I have also uploaded all these files on mediafire on this link

+

Step 2: Install Oracle Java

+

You might ask, why do we need to do it this way?

+

That's because Oracle Java 6 SDK is a pre-requisite for installing Android SDK but it's not available in Ubuntu or Linux Mint repository so it can't be installed using synaptic or apt-get.

+

Right then, I am assuming that all the files downloaded above are placed in 'Downloads' directory. If not, please replace 'Downloads' in all commands with whichever directory you have downloaded these file to.

+

 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
+26
+27
+28
+29
+30
+31
+32
+33
+34
#1. Open terminal and type:
+cd Downloads 
+#2. Make the downloaded .bin Java 6 file executable and run
+chmod a+x jdk-6u37-linux-i586.bin 
+./jdk-6u37-linux-i586.bin 
+###This will create a directory named "jdk1.6.0_37".
+#3. Untar the Java 7 SDK 
+tar -xvf jdk-7u7-linux-i586.tar.gz
+###This will create a directory named "jdk1.7.0_07".
+#4. Unzip the Java 6 api docs 
+unzip jdk-6u30-apidocs.zip -d jdk1.6.0_37/
+###This command will unzip the apidocs zip file 
+###and place the contents in "jdk1.6.0_37"
+#5. Unzip the Java 7 api docs
+unzip jdk-7u6-apidocs.zip -d jdk1.7.0_07/
+###This command will unzip the apidocs zip file 
+###and place the contents in "jdk1.7.0_07"
+#6. Untar the Java 6 demos and samples
+tar -xvf jdk-6u37-linux-i586-demos.tar.gz
+###This command will untar the demos and samples file 
+###and place the contents in "jdk1.6.0_37"
+#7. Untar the Java 7 demos and samples
+tar -xvf jdk-7u9-linux-i586-demos.tar.gz
+###This will create a folder named jdk1.7.0_09. 
+###Copy the contents of this folder into jdk1.7.0_07.
+#8. Move the Java 6 to it's proper location
+sudo mv jdk1.6.0_37 /usr/lib/jvm
+###You will be asked to provide root password.
+#9. Now move the Java 7 to it's proper location
+sudo mv jdk1.7.0_07 /usr/lib/jvm
+#10. Make the script to update java update-java-0.5b executable
+#then execute it by using following commands in terminal.
+chmod +x update-java-0.5b
+sudo ./update-java-0.5b
+
+You will be presented with following selection box:

+

Fig-3

+

Once you click on OK you will be presented with following screen:

+

Fig-4

+

Select the radio button and click on OK.

+

Once Java 6 SDK is installed repeat step 10 and this time when you reach the selection window select Java 7 as shown below.

+

Fig-5

+

Now Oracle Java 6 and Oracle Java 7 will both be installed on your system. To check this you can use the tool 'galternatives'. This can be installed by typing following command on the terminal window:

+
1
+2
sudo apt-get install galternatives
+galternatives
+
+

This will open the 'G Alternatives' window, scroll down to 'Java' in left hand pane and click on it. You should see both versions installed and radio button for highest version selected as shown below:

+

Fig-6

+

Step 3: Install eclipse

+
    +
  1. +

    Open Synaptic package manager and type eclipse, click on the check-box next to it and select 'Mark for installation' then click on 'Apply' as shown below.

    +

    Fig-7

    +
  2. +
  3. +

    Once Eclipse is installed, we need to find out whether the necessary SWT (Standard Widget Toolkit) libraries link are set correctly or not. This is to avoid Eclipse throwing a tantrum and not starting because it is unable to find the SWT library. As you can see in the screenshot below it's a pretty quick thing:

    +

    Fig-8

    +
      +
    1. First type the following command to check if whether SWT directory exists or not:
    2. +
    +

    ls ~/.swt/lib/linux/x86/
    +
    + If you see the same message as shown in screenshot above 'ls: cannot access /home//.swt/lib/linux/x86/: No such file or directory', then it means we need to create the SWT directory so continue to sub-step B.

    +

    If this command does not result in this message nor does it show a list of files (blue text in screenshot) then skip directly to sub-step C.

    +

    If it does show the list of files in blue in above screenshot, you don't need to do anything further and for you it's time to move to next step.

    +
      +
    1. Type the following command in terminal to create the SWT directory:
    2. +
    +

    bash + mkdir -p ~/.swt/lib/linux/x86/

    +
      +
    1. If the swt directory exist, but nothing is listed (no blue text), then run the following command in terminal:
    2. +
    +

    ln -s /usr/lib/jni/libswt-* ~/.swt/lib/linux/x86/
    +
    +3. Finally as shown in screenshot, type the command in sub-step 2.1 once again and you should see the list shown in blue on the screenshot.

    +
  4. +
+

Time to move on to install ADT (Android Development Tools Plugin on eclipse.

+

Step 4: Install ADT Plug-in for Eclipse

+

OK we have so far installed Java, installed eclipse and now we are all set to install Android. To do so we will follow the screenshots below:

+
    +
  • +

    When you start eclipse, you will be shown a splash screen, ask you to set workspace which I leave default and finally this window will open. As shown, click on the Workbench in right hand side corner. This will lead to following window.

    +

    Fig-9

    +
  • +
  • +

    Here Click on Help > Install New Software. This will open following window.

    +

    Fig-10

    +

    Fig-13

    +
  • +
  • +

    Click on 'Add' button in red rectangle above. Following pop-up window will appear.

    +

    Fig-11

    +
  • +
  • +

    In Name type 'ADT Plugin' or whatever name you want to give.

    +
  • +
  • +

    In Location type 'https://dl-ssl.google.com/android/eclipse/'

    +
  • +
  • +

    Then click OK. On following window it will first show pending but eventually it will look as below.

    +

    Fig-12

    +
  • +
  • +

    Here select the first option "Developer Tools" and click OK.

    +

    Fig-14

    +
  • +
  • +

    Click "NEXT".

    +

    Fig-14

    +
  • +
  • +

    Click "NEXT".

    +

    Fig-15

    +
  • +
  • +

    Let it run in foreground i.e. don't do anything and just wait.

    +

    Fig-16

    +
  • +
  • +

    Click "RESTART NOW". You will be presented with following window on restart.

    +

    Fig-17

    +
  • +
  • +

    Now leave the target location as is and leave everything as default and click "NEXT".

    +

    Fig-18

    +
  • +
  • +

    As shown above, selected the "ACCEPT ALL" radio button and click "INSTALL".

    +
  • +
  • +

    Now the android SDK is installed on the system along with other things. Close all the open windows just to clear any screen clutter.

    +
  • +
  • +

    Now open the terminal and type following commands:

    +

    Fig-19

    +
    cd android-sdks/tools
    +./android 
    +
    +
  • +
  • +

    It will open the following window:

    +

    Fig-20

    +
    +

    Note

    +
      +
    1. Select as shown above and optionally you can also select checkbox against Documentation and Samples. Once done click on 'INSTALL x Packages'.
    2. +
    3. On the next window, for which I did not take a screenshot but which is same as the one before previous screenshot of terminal, select radio button 'ACCEPT ALL' and click on 'INSTALL'.
    4. +
    5. Now once you are back to this window select 'Google APIs' and click on 'Install 1 package'.
    6. +
    7. On the package list, click on 'ACCEPT' radio button and click on 'INSTALL'.
    8. +
    +
    +
  • +
+

Android Development Tools and SDK is now installed. We just need to configure few things.

+

Step 5: Final Configurations

+

adb Environmental Variables

+
    +
  1. +

    Go to home folder

    +
  2. +
  3. +

    Press Ctrl+H

    +

    Fig-21

    +
  4. +
  5. +

    Locate the .bashrc file, if it does not exist, create one (In home folder, right click and select 'Create New Document > Empty Document').

    +
  6. +
  7. +

    Open the file in gedit and add export PATH=${PATH}:/tools:/platform-tools at the end, if you created the file yourself just add this line and save.

    +

    Fig-22

    +
  8. +
+

Set the PATH environment

+
    +
  1. Go to home folder
  2. +
  3. Press Ctrl+H
  4. +
  5. Locate the .profile file
  6. +
  7. +

    Open the file in gedit and add the following at the end.

    +
    1
    +2
    +3
    +4
    +5
    +6
    +7
    ## set PATH so it includes user's Android SDK if it exists
    +
    +if [ -d "$HOME/android-sdks" ] ; then
    +
    +  PATH="$HOME/android-sdks:$HOME/android-sdks/tools:$PATH"
    +
    +fi
    +
    +

    Fig-23

    +
  8. +
+

Set-up udev

+

This step is only required if you want to use your android device for development purpose or if you want to flash a custom ROM.

+
    +
  1. +

    Open the terminal and type following:

    +
    gksudo nautilus /etc/udev/rules.d
    +
    +
  2. +
  3. +

    Create new document like we did above- Right click on empty space and select 'Create New Document' and then 'Empty Document.

    +
  4. +
  5. +

    Name this document as '51-android.rules'

    +
  6. +
  7. +

    Open the document with Gedit and add following lines - assuming you are using 'Nexus S' like me, if not see if your phone details are available on this link: http://wiki.cyanogenmod.com/wiki/Udev and if not follow the guideline under the heading 'Manually create udev rules' on that link.

    +

    Fig-24

    +
  8. +
  9. +

    Now make this 51-android.rules file executable by typing following command in the terminal window:

    +
  10. +
+
sudo chmod a+r /etc/udev/rules.d/51-android.rules
+
+

Step 6: Create Android Virtual Device

+
    +
  • +

    Open eclipse and follow the screenshots below:

    +

    Fig-25

    +
  • +
  • +

    Click on Window - AVD Manager to get to next screen except that in your screen there will be no entry.

    +

    Fig-26

    +
  • +
  • +

    Click on "NEW" to create a new AVD. It will open next window.

    +

    Fig-27

    +
  • +
  • +

    Fill details as above - You can chose name of your choice and change details as per your requirements.

    +

    Fig-28

    +
  • +
  • +

    To Start the AVD, click on Start.

    +
  • +
  • +

    Finally, you will be able to see the AVD as below:

    +

    Fig-29

    +
  • +
+

This is it. Restart and your system is now all set for developing wonderful Android Applications.

+ + + + + + + + + + + + + + + + + + + + + + +
+
+ + + + + +
+ + + +
+ + + +
+
+
+
+ + + + + + + + + + + + \ No newline at end of file diff --git a/2013/01/01/root-nexus-4-on-linux-mint-13-and-access-all-files-on-computer.html b/2013/01/01/root-nexus-4-on-linux-mint-13-and-access-all-files-on-computer.html new file mode 100644 index 0000000..9cf9419 --- /dev/null +++ b/2013/01/01/root-nexus-4-on-linux-mint-13-and-access-all-files-on-computer.html @@ -0,0 +1,1877 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + Root Nexus 4 on Linux Mint 13 and access all files on computer - Make Gadgets Work + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + Skip to content + + +
+
+ +
+ + + + +
+ + +
+ +
+ + + + + + + + + +
+
+ + + + + + + + + + + + + + + +
+
+
+
+ + +
+
+
+
+ + + + + + + + + + + + + + + + + + + + +

Root Nexus 4 on Linux Mint 13 and access all files on computer

+ +

Background

+

Having a rooted phone and then going to one that does not have root access is like getting used to driving a luxury car but then being forced to drive a tractor. So with arrival of my shining new nexus 4 once the novelty worn of in 8 hours or so, I sat down and rooted the device. Now there are plenty of guides out there but not many specific to Linux just yet. One reason might just be the fact the Linux Users are really smart and know how to figure it out but what about users who are new ...well at least for them I am sure this post will be useful and while we are at it, I felt I will install the touch version of CWM...

+ + +

Pre-requisite

+

For purpose of this tutorial, I will assume that adb set-up is in place using steps explained in my last post specifically up to Step It is totally worth doing it before you proceed but if you would prefer a shorter route, please refer some guide on how to install just adb. Though I would not recommend any other approach. (Not because they will not work but because this approach will ensure clean system and something I have tested 4 times already to work and if you will follow this post, it makes sense to do it the way I did)

+
    +
  1. Clockwork Recovery Mod (Touch Version)
  2. +
  3. Super User App
  4. +
  5. Fastboot
  6. +
+
+

Danger

+

This process will wipe out all user data and apps. Appropriate back-ups should be taken and restoring those is beyond the scope of this post.

+
+

Items 1 and 2 can be downloaded directly from http://www.clockworkmod.com/rommanager and http://download.chainfire.eu/282/SuperSU/ or as before I have uploaded them in mediafire and can be downloaded from this link.

+

I downloaded fastboot few months back from xda using this link and am not sure if it is still there or not so I have uploaded it to mediafire as well.

+

Fig-1

+

Step 1: Prepare Nexus 4 and Linux Mint 13

+
    +
  1. +

    Check if fastboot is already there in the the /android-sdks/platform-tools directory. If yes skip the next step.

    +
  2. +
  3. +

    Extract the fastboot from downloaded zip file and place it in the /android-sdks/platform-tools directory if it is not already there and make it executable (Right Click, Select properties, Go To permissions tab and select the checkbox in front of 'Execute')

    +
  4. +
  5. +

    Copy the downloaded Clock Work Mod file (recovery-clockwork-touch-6.0.2.3-mako.img) in the /android-sdks/platform-tools directory.

    +
  6. +
  7. +

    Activate debug mode in nexus 4 - to do this go to settings > About Phone and then click 7 to 8 times on 'Build Number'. This will activate developer mode.

    +
  8. +
  9. +

    Now click back and got to {} Developer Options and Click the checkbox against USB debugging.

    +
  10. +
  11. +

    Set up udev on linux mint:

    +
      +
    1. +

      Assuming that you have followed last post you would already have a 51-android.rules file created.

      +
    2. +
    3. +

      Open the file with Gedit using following command

      +

      sudo gedit /etc/udev/rules.d/51-android.rules
      +
      + 3. Add following lines:

      +
      #LG - Nexus 4
      +SUBSYSTEM=='usb', ATTR{idVendor}=='1004', MODE='0666'
      +SUBSYSTEMS=='usb', ATTRS{idVendor}=='18d1', ATTRS{idProduct}=='4ee1', MODE='0660', OWNER='ankit' #Normal nexus 4
      +SUBSYSTEMS=='usb', ATTRS{idVendor}=='18d1', ATTRS{idProduct}=='4ee2', MODE='0660', OWNER='ankit' #Debug &amp; Recovery nexus 4
      +SUBSYSTEMS=='usb', ATTRS{idVendor}=='18d1', ATTRS{idProduct}=='4ee0', MODE='0660', OWNER='ankit' #Fastboot nexus 4
      +
      +
    4. +
    +

    I got the Vendor ID and Product ID by connecting the phone in different states (USB debug checked, unchecked and also after the phone was connected and rebooted into bootloader using command 'adb reboot bootloader') as per the guidance given here.

    +
      +
    1. +

      Now Save the file, then chmod to all read using following command:

      +
      sudo chmod +x /etc/udev/rules.d/51-android.rules 
      +
      +
    2. +
    +
  12. +
+

Step 2: Unblock bootloader for Nexus 4

+
    +
  1. +

    Plug your phone into the computer and type the following command in terminal:

    +
    adb reboot bootloader
    +
    +
  2. +
  3. +

    Once Nexus 4 has rebooted in recovery mode, type the following command in terminal:

    +
    fastboot oem unlock
    +
    +
  4. +
+

If terminal displays the message 'waiting for device' 51-android.rules file is not set correctly and you might need to check vendor ID and product id using lsusb command in different modes and update it with appropriate data.

+
    +
  1. Phone will display a long message and ask for confirmation to unlock bootloader. Select 'Yes' by using the volume keys and use power to select it.
  2. +
+

Now, using volume keys navigate to 'Recovery Mode' and select it using 'Power' key.

+

After a while Android with blue progress bar should appear and phone should reboot but if it does not and instead shows a screen with 'Android' on it's back with an exclamation mark on it's tummy, don't panic. Just press 'Power' and 'Volume Up' till it shows recovery menu and then select 'reboot'.

+

The phone is now bootloader unlocked.

+

Step 3: Root Nexus 4

+

OK now when the phone boots, it will be fresh with factory reset, no data or apps whatsoever other than those that are there by default and when the phone boots, it will ask all details like selecting country etc. Just enter quickly without bothering to enter gmail, wifi etc. Once done:

+
    +
  1. +

    Install mtpfs from synaptics.

    +
  2. +
  3. +

    Plug the phone to computer.

    +
  4. +
  5. +

    Type the following commands:

    +
    1
    +2
    sudo mkdir /media/nexus4 
    +sudo chmod 755 /media/nexus4 
    +
    +
  6. +
  7. +

    Now mount the nexus 4 using following command so we can transfer files:

    +
    sudo mtpfs -o allow_other /media/nexus4 
    +
    +
  8. +
  9. +

    Copy the downloaded file 'CWM-SuperSU-v0.98.zip' on Nexus

    +
  10. +
  11. +

    Type the following command to unmount nexus 4:

    +
    sudo umount /media/nexus4
    +
    +
  12. +
  13. +

    Reactivate the debug mode using Step 1.4 and Step 1.5 above.

    +
  14. +
  15. +

    On terminal type the command:

    +
    1
    +2
    adb reboot bootloader 
    +fastboot flash recovery /android-sdks/platform-tools/recovery-clockwork-touch-6.0.2.3-mako.img
    +
    +
    +

    Tip

    +

    Make sure in above command you replace PATH_TO with the actual path to the file. An easy way can be to right click on "recovery-clockwork-touch-6.0.2.3-mako.img" file and click on properties thene select the path and paste in this command.

    +
    +

    Fig-2

    +
  16. +
  17. +

    Once completed, on the phone navigate to 'Recover Mode' using volume keys and select using power key.

    +
  18. +
  19. +

    The new recovery menu will be presented. Select 'install zip from sd card' and 'choose zip from sd card.' and select the file we had put in step 3.5 - 'CWM-SuperSU-v0.98.zip'.

    +
  20. +
  21. +

    Once done, go back to reboot and 'Reboot' the phone.

    +
  22. +
+

At this point your phone is rooted,however, you will get a message suggesting the recovery will not be permanent or something to that effect which is fine if you aren't bothered about it but if you would rather want to keep this CWM we need to move to next step.

+

Step 4: Make Clockwork Mod Permanent

+
    +
  1. +

    On your phone install ES File Explorer.

    +
  2. +
  3. +

    Select Settings.

    +

    Fig-3

    +
  4. +
  5. +

    Select 'Root Settings'.

    +

    Fig-4

    +
  6. +
  7. +

    Select all checkboxes. You will be asked for superuser access, say Yes.

    +

    Fig-5

    +
  8. +
  9. +

    Now go back to main screen of ES File Explorer and select the third tab with an icon of Folder with up arrow and text Up. This should bring you to root.

    +
  10. +
  11. +

    Now using ES File Explorer navigate on your phone to /system/etc, find the file named install-recovery.sh and rename it to install-recovery.bak

    +
  12. +
  13. +

    Repeat Steps 3.7 to 3.11.

    +
  14. +
+

Now your clockwork mod is permanent.

+

Step 5: Mount and Un-Mount Nexus 4 to access files from Linux Mint

+

Now, with the steps 3.1 to 3.6 we have laid the foundation for being able to connect Nexus 4 and transfer files using USB. Something earlier was possible using USB Mass storage mode but is not present jelly bean onwards. +The commands in 3.4 and 3.6 are key to achieve this but rather than remembering these and typing each time, I have made a menu entries for each of these (Mount Nexus 4, Unmount Nexus 4) and after connecting phone via USB, I simply click on these, system asks root password and then connects nexus 4 as mass storage as shown below.

+

Fig-3

+

In order to get these you will follow the steps below:

+
    +
  1. +

    Right Click on Menu and select 'edit Menu'.

    +
  2. +
  3. +

    Now click on 'New Menu' and Enter a menu entry 'Phone'.

    +
  4. +
  5. +

    Select the checkbox next to the new menu entry 'Phone' in middle pane.

    +
  6. +
  7. +

    Select new menu entry 'Phone' in left pane.

    +
  8. +
  9. +

    Click on 'New Item' in right Pane and in the dialogue box fill the fields as below and save:

    +
    Type:       Application in terminal
    +Name:       Mount Nexus 4
    +Command:    sudo mtpfs -o allow_other /media/nexus4
    +
    +
  10. +
  11. +

    Again click on 'New Item' in right Pane and in the dialogue box fill the fields as below and save:

    +
    Type:            Application in terminal
    +Name:            Unmount Nexus 4
    +Command:         sudo umount /media/nexus4
    +
    +
  12. +
  13. +

    Make sure checkbox next to these new items is ticked. Save and Close.

    +
  14. +
+

That's it !!! All Done.

+

Hope some will find this post useful.

+ + + + + + + + + + + + + + + + + + + + + + +
+
+ + + + + +
+ + + +
+ + + +
+
+
+
+ + + + + + + + + + + + \ No newline at end of file diff --git a/2013/01/18/conky-on-my-desktop-step-by-step.html b/2013/01/18/conky-on-my-desktop-step-by-step.html new file mode 100644 index 0000000..8853c36 --- /dev/null +++ b/2013/01/18/conky-on-my-desktop-step-by-step.html @@ -0,0 +1,1709 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + Conky on my desktop - step by step - Make Gadgets Work + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + Skip to content + + +
+
+ +
+ + + + +
+ + +
+ +
+ + + + + + + + + +
+
+ + + + + + + + + + + + + + + +
+
+
+
+ + +
+
+
+
+ + + + + + + + + + + + + + + + + + + + +

Conky on my desktop - step by step

+ +

Fig-1

+

Background

+

My new friend Damjan recently mentioned that he liked the Conky on my desktop and asked for details as have few others so I figured a post on the topic will be useful.

+

Many who have been playing with conky seem to believe it's real easy stuff but I feel there are too many options and very little explanation which means it can be lot of messing around with different options and can take a while to get to a point where you have what you want.

+

To make this conky appear exactly the way it is on my screen on your desktop follow below.

+ + +

Step 1. Install Conky from Synaptic

+

Fig-2

+

Open the synaptic package manager and follow steps as shown in screenshot above. Actually I don't think conky colors is required so if it does not appear in search result don't bother. From memory, that was installed by me as part of experimenting.

+

Step 2. Install the fonts used in this conky

+

Download and install the fonts 'pf_tempesta_five_condensed.ttf' and 'ZegoeUI-U.ttf' from web or I have uploaded these to mediafire and can be download from this link.

+

Step 3. Change the conky config file

+
    +
  1. Download my conky configuration in the file named .conkykrc from this mediafire link.
  2. +
  3. Open 'Home' directory and press ++Ctrl+h++ to show hidden files.
  4. +
  5. Locate the .conkykrc file and delete it.
  6. +
  7. Now paste the .conkykrc file downloaded in step 1 to home folder.
  8. +
+

Step 4. Enable settings to run conky at start-up

+
    +
  1. +

    Open 'Startup Application' from Menu > Preferences.

    +

    Fig-3

    +
  2. +
  3. +

    Click on 'Add' button and fill the fields as shown above.

    +

    Fig-4

    +
  4. +
  5. +

    Restart the computer and it's all done.

    +
  6. +
+

Step 5: Configure time-zones on the conky config

+

OK, so you have conky running as it is for me but you might want different time-zones than the one I have in which case you will need to modify the .conkykrc file in 'Home' directory.

+
    +
  1. Open 'Home' directory and press 'Ctrl+h' to show hidden files.
  2. +
  3. Locate the .conkykrc file and open it in text editor of your choice. I use gedit.
  4. +
  5. Now Open Terminal and Type 'tzselect' and press enter.
  6. +
  7. A list of continents will be displayed, select the one of your interest by typing the number against it and press enter. - I selected 'Americas' so I typed '2'
  8. +
  9. Then you will be presented a list of countries. Again select the one of your interest by typing the number against it and press enter. - I selected 'North America' so I typed '49'
  10. +
  11. Finally, it will display time-zones in that country. As above, select the one of your interest by typing the number against it and press enter. - I selected 'Mountain Time' so I typed '18'
  12. +
  13. It will confirm selection and then mention TZ being used. - For my selection it displayed 'Therefore TZ='America/Denver' will be used.'
  14. +
  15. This text after 'TZ=' is what is important for us. Copy this and go back to .conkykrc file.
  16. +
  17. Under 'TIMEZONE' say you want to replace Nashville with Denver then you locate Nashville (Line 83, Col 64) and replace the text with the city of your choice, in this case 'Denver'.
  18. +
  19. Now to reflect the appropriate time-zone, replace the existing time-zone 'America/Chicago' with the one from step above. - In this example with 'America/Denver''.
  20. +
+

That's it. All Done !!!

+

Hope you find it useful. Feel free to ask questions on settings here and if it's something I was stuck with I might be able to help.

+ + + + + + + + + + + + + + + + + + + + + + +
+
+ + + + + +
+ + + +
+ + + +
+
+
+
+ + + + + + + + + + + + \ No newline at end of file diff --git a/2013/03/01/python-eric-rad-ide-and-qt-designer.html b/2013/03/01/python-eric-rad-ide-and-qt-designer.html new file mode 100644 index 0000000..cf90b03 --- /dev/null +++ b/2013/03/01/python-eric-rad-ide-and-qt-designer.html @@ -0,0 +1,1610 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + Python, ERIC RAD IDE and QT designer - Make Gadgets Work + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+
+ +
+ + + + +
+ + +
+ +
+ + + + + + + + + +
+
+ + + + + + + + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+
+ + +
+
+
+
+ + + + + + + + + + + + + + + + + + + + +

Python, ERIC RAD IDE and QT designer

+ +

Right, I have decided to play around a little with the most loved language of open source a. k. a. Python.

+ + +

How do we do that? By coding?

+

Aha and how do I code for Python and be productive given I am a semi-part-time-hobbyist kinda programmer...I am heavily inclined to use an IDE...and an IDE that actually has some GUI editor...so began my search to find out what are my options in the exciting open source world.

+

I quickly worked out that IDE and RAD IDE are two different things. RAD IDE is what I am after and well there is nothing straight forward and best bet was to use QT with PySide or PyQT and there is an argument on each side for PyQT vs PySide.

+

Anyhoo, PySide is not compatible yet with QT5 and QT4.8.x is not available for download from QT official downloads. Whatever happened to providing an archive. QT itself is a world in itself with lot of modules and plugins etc and I was feeling lost.

+

I diverted efforts to find what else and stumbled upon ERIC IDE for Python and from here things started tying up to a point where I could logically assimilate the available information to start preparing my system for my kind of Python Development.

+

However, this information isn't as readily available as it should be and most mentioned on trusty old Internet Google search is combination of PySide and the non existent QT4 which had it not been for Linux package managers, might as well be a lost cause.

+

All it involves is downloading Eric from repository which satisfied most of the dependencies and then I downloaded qt4 designer and included qt4 creator just for good measure.

+

So there now we have the tool and all I need is setting it up and getting started.

+

Following links will come handy I suppose.

+

http://eric-ide.python-projects.org/tutorials/MiniBrowser/

+

http://anh.cs.luc.edu/python/hands-on/handsonHtml/handson.html#x1-310001.9.3

+

Here's to some exciting learning experience.

+ + + + + + + + + + + + + + + + + + + + + + +
+
+ + + + + +
+ + + +
+ + + +
+
+
+
+ + + + + + + + + + + + \ No newline at end of file diff --git a/2013/03/17/linux-mint-on-android-through-vnc-and-jump.html b/2013/03/17/linux-mint-on-android-through-vnc-and-jump.html new file mode 100644 index 0000000..0237604 --- /dev/null +++ b/2013/03/17/linux-mint-on-android-through-vnc-and-jump.html @@ -0,0 +1,1831 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + Linux Mint on Android through VNC and Jump - Make Gadgets Work + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + Skip to content + + +
+
+ +
+ + + + +
+ + +
+ +
+ + + + + + + + + +
+
+ + + + + + + + + + + + + + + +
+
+
+
+ + +
+
+
+
+ + + + + + + + + + + + + + + + + + + + +

Linux Mint on Android through VNC and Jump

+ +

Today "Jump" was available for free on Amazon as the app of the day and since it's nearly 7 quids on google play store, I grabbed it. For windows and Mac users, they have a pretty straight forward set-up but as usual for Linux it means some work but, in the end, it leaves you with a set-up you can trust and feel secure about.

+ + +

Set-up x11vnc and ssh servers

+

Linux Mint machine should be set-up for x11vnc and ssh servers

+

Install VNC

+

Install X11VNC by typing following command in terminal:

+
sudo apt-get install x11vnc
+
+

Configure VNC

+

Create a password for VNC Using following command in terminal and providing a password and answering yes for the prompt to store password in a file.

+
x11vnc -storepasswd
+
+

Now to ensure that X11VNC starts at boot go to menu and type start, click on startup application as shown in the screenshot below:

+

Fig-1

+

Then in the window that this will open click on "Add" and enter a "Name" and in "Command" field paste the command below:

+
x11vnc -forever -xkb -usepw -display :0
+
+

Fig-2

+

VNC set-up on machine is complete.

+

Install openssh

+

Install openssh-server using following command on terminal:

+
sudo apt-get install openssh-server
+
+

Configure openssh

+

We will need to change some parameters in ssh configuration for making it secure as by default it allows root login but I dont want that for remote access and would advice most regular users to do so as well.

+
    +
  • +

    So we will first make a backup of existing configuration file using the command below: +

    sudo cp /etc/ssh/sshd_config ~
    +

    +
  • +
  • +

    Now, we will edit the actual config file using following command: +

    gksudo gedit /etc/ssh/sshd_config
    +

    +
  • +
  • +

    Once the file is open change the parameter "PermitRootLogin" to "no". It's on line 27 for me.

    +
  • +
  • +

    Now, the default port for ssh is 22 but I recommend changing it to something else such as 5432.

    +
  • +
  • To do so change the parameter "Port" from 22 to whatever port you want to put. In this example it will be 5432. For me "Port" parameter is on line 5.
  • +
  • +

    Save the changes and close gedit.

    +
  • +
  • +

    Now we will restart the ssh server using following command in terminal +

    sudo restart ssh
    +

    +
  • +
  • +

    For Arch, you can use the command: +

    sudo systemctl start sshd
    +
    + followed by +
    sudo systemctl enable sshd.service
    +
    + to ensure ssh daemon is enabled at startup.

    +
  • +
  • +

    Restart the machine and machine set-up is done.

    +
  • +
+

Configure Router firewall

+

Router firewall should be configured to allow inbound traffic on specific ports. This may involve different steps from those given below depending on the router in use. Following steps are meant for configuring the sky router. However, principle is same. We will be creating specific service definition and port on router and then create a firewall rule that allows inbound traffic and directs it to Linux machine we configured above.

+
    +
  • +

    Type following command on terminal: +

    ifconfig
    +

    +
  • +
  • +

    This will list lot of numbers, what we are interested in is the number just after "inet addr:" under wlan0. It will be something like 192.168.0.10.

    +
  • +
  • +

    Open sky router config through browser using 192.168.0.1 and click on "Security". You will need to enter router username and password.

    +

    Fig-3

    +
  • +
  • +

    Then click on "Services" and then click on "Add Custom Services".

    +
  • +
  • +

    Enter as shown in Figure 4 and Start Port as 5900, Finish Port as 5900 and click on "Apply".

    +

    Fig-4

    +
  • +
+
+

Tip

+

5900 is default port for display 0 in VNC. If you have changed it like me you will need to enter that port. To change port you will need to use x11vnc -forever -xkb -usepw -autoport nnnn -display :0 option in third step of VNC config above. This is not required for security but in case you have two different machines then this approach will come handy.

+
+
    +
  • +

    Now click on "Add Custom Services" again and this time enter as shown in next screenshot. Start Port and End Port should be same as entered in step 1.8, so for this example it will be 5432. Then click on "Apply".

    +

    Fig-5

    +
  • +
  • +

    Now we need to set the firewall for these services. To do so, click on "Firewall Rules" then click on "Add" under inbound services.

    +
  • +
  • +

    Configure fields as shown in next screen-shot below and click on "Apply".

    +

    Fig-6

    +
  • +
  • +

    Now we will do same for SSH, so again click on "Add" under inbound services and configure fields as shown in screen-shot below and click on "Apply".

    +

    Fig-7

    +
  • +
  • +

    Click on "Apply" under "Inbound Services".

    +
  • +
  • +

    In browser on the router management page, click on "Advanced" > "Remote Management" and on this screen make note of the IP address (number after http:// in red box in next screenshot) shown under "Remote Management Address".

    +

    Fig-8

    +
  • +
  • +

    Go to https://www.dlinkddns.com/signin and create an account. Refer this page for the how-to and you will need to use the IP from step above as the host. At the end of it you will have a hostname like "yourname.dlinkddns.com", username and password for logging in to dlinkddns site.

    +
  • +
  • +

    Once this is done, go to the browser with sky router management and click on "Advanced">"Dynamic DNS" and fill as shown in screen-shot below:

    +

    Fig-9

    +
    1
    +2
    +3
    Host Name: Hostname from Step above (yourname.dlinkddns.com in this example)
    +User Name: D-Link site username
    +Password: D-Link site password
    +
    +

    Fig-10

    +
  • +
  • +

    Once above information is filled, click on "Apply" and then click on "Show Status". It should open a separate window and showing the message "request successful".

    +
  • +
+

Sky Router is now configured.

+

Jump or an equivalent VNC viewer should be configured on the android device.

+
    +
  1. On the android device open Jump and click on the "+" sign in right hand corner.
  2. +
  3. In the "Address" Field enter the hostname from 2.12 (yourname.dlinkddns.com in this example) and select connection type as "VNC" and click save.
  4. +
  5. Change the "Authentication Method" to "VNC Password"
  6. +
  7. Tap on "SSH Tunnel", click on "Enabled" checkbox.
  8. +
  9. In Username enter the username used to log into the machine configured above in Step 1.
  10. +
  11. In Host Name, use the the hostname from 2.12 (yourname.dlinkddns.com in this example)
  12. +
  13. Change the port to one used in 1.8. So in this case 5432.
  14. +
  15. Password can be left empty and when asked during connection provide the one used to log on to the machine with this username.
  16. +
  17. Press back button and click on entry. You will be shown a SSH key notification, say ok. Then you will be asked for a password, provide the password you use to log onto your machine with the username provided in 3.6.
  18. +
  19. Then you will be asked for the VNC password, provide the password from step 1.2.
  20. +
+

You will now be able to view your desktop on your android machine.

+

Fig-11 +Fig-12

+

All Done !!!

+ + + + + + + + + + + + + + + + + + + + + + +
+
+ + + + + +
+ + + +
+ + + +
+
+
+
+ + + + + + + + + + + + \ No newline at end of file diff --git a/2014/02/19/arch-linux-nearly-a-year-on.html b/2014/02/19/arch-linux-nearly-a-year-on.html new file mode 100644 index 0000000..111e50f --- /dev/null +++ b/2014/02/19/arch-linux-nearly-a-year-on.html @@ -0,0 +1,1616 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + Arch Linux - nearly a year on.... - Make Gadgets Work + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+
+ +
+ + + + +
+ + +
+ +
+ + + + + + + + + +
+
+ + + + + + + + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+
+ + +
+
+
+
+ + + + + + + + + + + + + + + + + + + + +

Arch Linux - nearly a year on....

+ +

I have been dwelling in the world of Arch Linux for just under a year now and must admit the experience is nothing less than liberating. Granted that the barrier to entry was big when I dived in, as a pure Arch install takes some reading and learning but the rewards are worth the pain.

+ + +

While it may seem easier to stick with dear old .deb files in the end AUR has an equally large collection if not more and the fact that it's a rolling release means that the initial set-up pain is very well negated by another fact that unless like me you are forced to change your hard disk or something that sinister; there is no reason to do a reinstall which is often the case with Debian based distros should you chose to upgrade to their latest offering.

+

I have read and heard words such as "Arch is not for faint hearted" or "Arch is for motivated newbie" and so on but in my opinion there are two types of Linux users:

+
    +
  1. Those who have an itch to tinker (someone like me)
  2. +
  3. Those who don't care as long as they can get on with their day to day + business (someone like my wife)
  4. +
+

It's the type 1 who is intended audience of this post because type 2 can't be bothered. Now if you have an itch to tinker there will come a point where you will find that all existing distros have a limit on what you can tinker without messing something to the point where you feel like you have to give another distro a try and before you know it a reinstall is on hand, which ofcourse means type 2 user has to deal with the changes a new distro will bring and this in turn will make them frown and thereby increase the risk of them secretly cursing the day you - the type 1 user - encountered the world of Linux.

+

Now Arch is very stable and still gives you the freedom to tinker, learn and do all great things rather than worry about the cosmetics and allow your tinker angel to quench it's thirst by allowing you to focus your learning potential on something that just doesn't scratch the surface so you can feel good about yourself but actually allows you to learn a great deal in the process. The helpful Arch community usually has the answers for nearly every problem I encountered while trying to achieve my goals.

+

I do not think that for a Type 1 user - newbie or not - Arch is hard or unfriendly. There is lot more satisfaction once you have your desktop exactly the way you wanted. Getting applications is as easy as using software centre in other distros and now that I have been using it, I find using "yaourt" (Arch equivalent of Synaptics) much better and the fact that repositories are maintained on servers in nearly all locations, it does seem to get new updates to your favourite applications rather quickly too.

+

I will substantiate this claim with following examples:

+

Getting android sdk configured on debian based distros is a right pain in you know where but with Arch it's what we like to call a doddle in Great Britain. Getting intellij IDE on other distro repositories is not an option but in Arch - you got it, it's a doddle.

+

Why is that? It's because Type 1 users when they switch to Arch they soon realise that they now have time on their hands which earlier used to disappear in distro hopping. So what do they do with this time? They search for software they want to use and if it isn't in the repo then with their new found confidence they don't shy away from building it from source and when they have done that they want to showcase their work and they can do so in AUR short for Arch User Repository - though with all that is already there this showcasing will only be possible if you genuinely need a very unique software not used by any Arch user - highly unlike scenario if you were to go by my experience . You will find nearly all software you can think of in AUR and since it's being maintained by someone who needed it, chances are he / she will keep it updated for his / her use thereby ensuring you get the latest and greatest as it happens. What a wonderful model it is indeed.

+

Now if you are still not geared up to go all the way to start from scratch on Arch but want to get a flavour, why not start with the new kid on the block - Manjaro Linux?

+

Manjaro linux is an Arch based distro, with a rolling release schedule and will still give you the access to AUR. It takes away the pain of setting up your system on command line and does the hard work for you so you can get the benefits of Arch but not the pain. Seems like a win win especially for those who are repelled by the very thought of terminal based system configuration.

+

Manjaro Linux offers all leading desktop environments - Gnome, KDE, Openbox but their best offering is with XFCE. XFCE is very good as well what with it being light weight and not making your Type 2 users feel that the system has been rendered useless. The default configuration of Manjaro XFCE flavour does come with a very ready to use set-up yet enough opportunity to make it and configure just the way you like it. In addition a type 1 user will be pleasantly surprised that using XFCE does wonders to satisfy that itch to tinker.

+

The login manager used with XFCE is MDM but can be easily replaced to the slick lovely light and beautiful SLiM and by installing slimlock you can also get rid of the ugly default screen lock that comes bundled with XFCE.

+

Explore some more and key bindings on this desktop are much more satisfying and easier to manage than on Gnome or KDE - IMHO.

+

Manjaro also comes with GUI based synaptics equivalent called "packer" but like I said once you are used to "yaourt" interface everything else GUI based just seems pale in comparison becaue it does not give the same satisfaction of installing stuff.

+

The one situation where Manjaro might actually be a better choice than Arch is when you are dealing with a potential convert. Offer them Arch with yaourt and they might be very sceptical but offer Manjaro with packer and they just might convert. Then again for this scenario perhaps Linux Mint or Zorin OS may be even better choice.

+

Point is Arch should enter into anyone's life where they make the choice but not when someone else is making that choice on their behalf as they may or may not develop the habit of tinkering. When and if they do, they will need to decide on their own to move to this platform but if you have been calling yourself a linux newbie and have found yourself reviewing new distros or worse distro hopping and by extension forum hopping, tailoring your linux install and generally enthusistic when it comes to all things Linux, I think it is time to get your feets wet and hands dirty. Get Arch experience in it's purest form or by using Manjaro, you will not regret it.

+ + + + + + + + + + + + + + + + + + + + + + +
+
+ + + + + +
+ + + +
+ + + +
+
+
+
+ + + + + + + + + + + + \ No newline at end of file diff --git a/2016/02/18/mysql-stored-procedure-to-return-json-for-google-charts.html b/2016/02/18/mysql-stored-procedure-to-return-json-for-google-charts.html new file mode 100644 index 0000000..f12500d --- /dev/null +++ b/2016/02/18/mysql-stored-procedure-to-return-json-for-google-charts.html @@ -0,0 +1,2402 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + MySQL Stored Procedure to return JSON for google charts on BIRT - Make Gadgets Work + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + Skip to content + + +
+
+ +
+ + + + +
+ + +
+ +
+ + + + + + + + + +
+
+ + + + + + + + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+
+ + +
+
+
+
+ + + + + + + + + + + + + + + + + + + + +

MySQL Stored Procedure to return JSON for google charts on BIRT

+ +

Background

+

My requirement was to get the data in a format that google chart can use to draw the chart I want. Now gogle chart accepts data in json format where all column names separated with comma are in first square bracket set followed by values in rest of the square bracket sets and each square bracket set is separated by comma as well.

+ + +

Having searched on good old google, there did not appear to be any quick way of doing it without getting hands dirty with likes of php and as I was to plug this into a BIRT report where a simple html would do the trick, I really just needed the data-set to be returned in format that google-chart understood.

+

I figured it can be easily done using a MySQL stored procedure and can be a repeatable process which resulted in creation of stored procedure presented below.

+

The code

+
 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
+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
+52
+53
+54
+55
+56
+57
+58
+59
+60
+61
+62
+63
+64
+65
+66
+67
+68
+69
+70
+71
+72
+73
+74
+75
+76
+77
+78
+79
+80
CREATE DEFINER=`root`@`localhost` PROCEDURE `json_builder_multiple_string`(IN `var1` varchar(10000), IN `tab_name` text, IN `int_col_as_str` int)
+    LANGUAGE SQL
+    NOT DETERMINISTIC
+    CONTAINS SQL
+    SQL SECURITY DEFINER
+    COMMENT ''
+BEGIN
+/*
+- This procedure will take columns and tablename as parameter.
+- Third parameter is to tell the procedure how many columns are to be returned as string - surrounded by quotes
+- Third parameter must always be less than the total columns being requested.
+- Rest of the columns must have numerical values as they won't be surrounded with quotes
+- Procedure returns 0 for each null value.
+
+SAMPLE PROCEDURE CALL
+CALL `json_builder_multiple_string`('Year,Month,Region,Sales,Expenses', 'myjsonexample',4);
+
+SAMPLE QUERY GENERATED:
+SELECT 
+    CONCAT('[\'Year\',\'Month\',\'Region\',\'Sales\',\'Expenses\'],',
+            GROUP_CONCAT('[',
+                CONCAT_WS(',',
+                        CONCAT('\'',
+                                IFNULL(`Year`, 0),
+                                '\',\'',
+                                IFNULL(`Month`, 0),
+                                '\',\'',
+                                IFNULL(`Region`, 0),
+                                '\',\'',
+                                IFNULL(`Sales`, 0),
+                                '\''),
+                        IFNULL(`Expenses`, 0))
+                SEPARATOR '],'),
+            ']') AS data_set
+FROM
+    myjsonexample
+
+SAMPLE OUTPUT:
+['Year','Month','Region','Sales','Expenses'],['2004','JAN','NW','1000',400],['2005','Feb','SW','1170',460],['2006','Mar','NE','2000',1210],['2007','Apr','SE','650',540],['2008','May','EC','0',0]
+
+SAMPLE TABLE USED:
+Drop Table if exists `myjsonexample`;
+CREATE TABLE `myjsonexample` (
+  `Year` int(11) NOT NULL,
+  `Month` varchar(25),
+  `Region` varchar(25),
+  `Sales` int(11) ,
+  `Expenses` int(11) 
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+INSERT INTO `myjsonexample`
+(`Year`,
+`Month`,
+`Region`,
+`Sales`,
+`Expenses`)
+VALUES
+(2004,'JAN','NW',1000,400),
+(2005,'Feb','SW',1170,460),
+(2006,'Mar','NE',2000,1210),
+(2007,'Apr','SE',650,540),
+(2008,'May','EC',null,null);
+*/
+SET SESSION group_concat_max_len = 1000000;
+Set @stmt1 = null;
+select concat('Select concat(\'',concat('[\\\'',replace(replace(var1,'_',' '),',','\\\',\\\''),'\\\'],'),'\'',
+', group_concat(\'[\',
+concat_ws(\',\',
+CONCAT(\'\\\'\', ifnull(`',replace(substring_index(var1,',',int_col_as_str),',','`,0),\'\\\',\\\'\',ifnull(`'),
+'`,0), \'\\\'\'),',
+(concat('ifnull(`',
+replace(
+                SUBSTRING(var1,LENGTH(SUBSTRING_INDEX(var1, ',', int_col_as_str)) + 2,LENGTH(var1)),
+                ',',
+                '`,0),ifnull(`'),
+'`,0)')
+)
+,') SEPARATOR \'],\'),\']\') as data_set from ', tab_name) into @stmt1;
+Prepare stmt2 from @stmt1;
+Execute stmt2;
+END
+
+

The alternative code

+

Another variation of above stored procedure where the output is returned in an output parameter can be created simply by adding two more lines to above code after Execute stmt2 and also including the output parameter in first line. Complete code below:

+
 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
+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
+52
+53
+54
+55
+56
+57
+58
+59
+60
+61
+62
+63
+64
+65
+66
+67
+68
+69
+70
+71
+72
+73
+74
+75
+76
+77
+78
+79
+80
+81
+82
CREATE DEFINER=`root`@`localhost` PROCEDURE `json_builder_outparam`(IN `var1` varchar(10000), IN `tab_name` text, OUT `varout` text)
+    LANGUAGE SQL
+    NOT DETERMINISTIC
+    CONTAINS SQL
+    SQL SECURITY DEFINER
+    COMMENT ''
+BEGIN
+/*
+- This procedure will take columns and tablename as parameter.
+- Third parameter is to tell the procedure how many columns are to be returned as string - surrounded by quotes
+- Third parameter must always be less than the total columns being requested.
+- Rest of the columns must have numerical values as they won't be surrounded with quotes
+- Procedure returns 0 for each null value.
+
+SAMPLE PROCEDURE CALL
+CALL `json_builder_multiple_string`('Year,Month,Region,Sales,Expenses', 'myjsonexample',4);
+
+SAMPLE QUERY GENERATED:
+SELECT 
+    CONCAT('[\'Year\',\'Month\',\'Region\',\'Sales\',\'Expenses\'],',
+            GROUP_CONCAT('[',
+                CONCAT_WS(',',
+                        CONCAT('\'',
+                                IFNULL(`Year`, 0),
+                                '\',\'',
+                                IFNULL(`Month`, 0),
+                                '\',\'',
+                                IFNULL(`Region`, 0),
+                                '\',\'',
+                                IFNULL(`Sales`, 0),
+                                '\''),
+                        IFNULL(`Expenses`, 0))
+                SEPARATOR '],'),
+            ']') AS data_set
+FROM
+    myjsonexample
+
+SAMPLE OUTPUT:
+['Year','Month','Region','Sales','Expenses'],['2004','JAN','NW','1000',400],['2005','Feb','SW','1170',460],['2006','Mar','NE','2000',1210],['2007','Apr','SE','650',540],['2008','May','EC','0',0]
+
+SAMPLE TABLE USED:
+Drop Table if exists `myjsonexample`;
+CREATE TABLE `myjsonexample` (
+  `Year` int(11) NOT NULL,
+  `Month` varchar(25),
+  `Region` varchar(25),
+  `Sales` int(11) ,
+  `Expenses` int(11) 
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+INSERT INTO `myjsonexample`
+(`Year`,
+`Month`,
+`Region`,
+`Sales`,
+`Expenses`)
+VALUES
+(2004,'JAN','NW',1000,400),
+(2005,'Feb','SW',1170,460),
+(2006,'Mar','NE',2000,1210),
+(2007,'Apr','SE',650,540),
+(2008,'May','EC',null,null);
+*/
+SET SESSION group_concat_max_len = 1000000;
+Set @stmt1 = null;
+select concat('Select concat(\'',concat('[\\\'',replace(replace(var1,'_',' '),',','\\\',\\\''),'\\\'],'),'\'',
+', group_concat(\'[\',
+concat_ws(\',\',
+CONCAT(\'\\\'\', ifnull(`',replace(substring_index(var1,',',int_col_as_str),',','`,0),\'\\\',\\\'\',ifnull(`'),
+'`,0), \'\\\'\'),',
+(concat('ifnull(`',
+replace(
+                SUBSTRING(var1,LENGTH(SUBSTRING_INDEX(var1, ',', int_col_as_str)) + 2,LENGTH(var1)),
+                ',',
+                '`,0),ifnull(`'),
+'`,0)')
+)
+,') SEPARATOR \'],\'),\']\') as data_set from ', tab_name) into @stmt1;
+Prepare stmt2 from @stmt1;
+Execute stmt2;
+Set varout = @varouttemp;
+Select varout;
+END
+
+

The BIRT usage explained

+

My sample BIRT reports are available for download HERE. Enabling the google chart on BIRT couldn't be simpler.

+
    +
  • +

    Create a new report

    +
  • +
  • +

    Add a text field, open it and change it to HTML as shown in the image below

    +

    Change Text Field

    +
  • +
  • +

    Copy the html from codeblock below. +

      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
    + 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
    + 52
    + 53
    + 54
    + 55
    + 56
    + 57
    + 58
    + 59
    + 60
    + 61
    + 62
    + 63
    + 64
    + 65
    + 66
    + 67
    + 68
    + 69
    + 70
    + 71
    + 72
    + 73
    + 74
    + 75
    + 76
    + 77
    + 78
    + 79
    + 80
    + 81
    + 82
    + 83
    + 84
    + 85
    + 86
    + 87
    + 88
    + 89
    + 90
    + 91
    + 92
    + 93
    + 94
    + 95
    + 96
    + 97
    + 98
    + 99
    +100
    +101
    +102
    +103
    +104
    +105
    +106
    +107
    +108
    +109
    +110
    +111
    +112
    +113
    +114
    +115
    +116
    +117
    +118
    +119
    +120
    +121
    +122
    +123
    +124
    +125
    +126
    +127
    +128
    +129
    +130
    +131
    +132
    +133
    +134
    +135
    +136
    +137
    +138
    +139
    +140
    +141
    +142
    +143
    +144
    +145
    +146
    +147
    +148
    +149
    +150
    +151
    +152
    +153
    +154
    +155
    +156
    +157
    +158
    +159
    +160
    +161
    +162
    +163
    +164
    +165
    +166
    +167
    +168
    +169
    +170
    +171
    +172
    +173
    +174
    +175
    +176
    +177
    +178
    +179
    +180
    +181
    +182
    +183
    +184
    +185
    +186
    +187
    +188
    +189
    <html>
    +  <head>
    +    <script type="text/javascript" src="https://www.gstatic.com/charts/loader.js"></script>
    +    <script type="text/javascript" src="https://www.google.com/jsapi"></script>
    +    <script type="text/javascript">
    +google.charts.load('current', {
    +        'packages': ['geochart', 'corechart', 'table', 'controls']
    +      });
    +      google.charts.setOnLoadCallback(drawRegionsMap);
    +
    +      function drawRegionsMap() {
    +        // Create our data table.
    +            var data = google.visualization.arrayToDataTable([
    +            ['Country', 'Period', 'RMPV', 'TARGET', 'Average'],
    +            ['Germany', 'Nov-2015', 75, 120, 97.5],
    +            ['Germany', 'Dec-2015', 55, 65, 60],
    +            ['Canada', 'Nov-2015', 200, 85,42.5],
    +            ['Canada', 'Dec-2015', 55, 65, 60],
    +            ['IN', 'Nov-2015', 328, 30, 179],
    +            ['IN', 'Dec-2015', 55, 65, 60],
    +            ['France', 'Dec-2015', 30, 100, 65],
    +            ['France', 'Nov-2015', 75, 120, 97.5],
    +            ['Brazil', 'Dec-2015', 50, 100, 75],
    +            ['Brazil', 'Nov-2015', 200, 85, 142.5],
    +            ['United States', 'Dec-2015', 55, 65, 60],
    +            ['United States', 'Nov-2015', 328, 30, 179]
    +          ]);
    +
    +        var data1 = google.visualization.data.group(data,[0],
    +        [{'column': 4, 'aggregation': google.visualization.data.avg, 'type': 'number'}]
    +        );
    +
    +        var options = {
    +          colorAxis: {
    +//          values: [0,30,90,150],
    +          colors: ['#F40EF4','#FCFC2A','#7BFC2A', '#FC4A2A']
    +          },
    +          backgroundColor: '#F1F7FD',
    +          datalessRegionColor: '#8B8E91',
    +          defaultColor: '#f5f5f5'
    +        };
    +        var chart = new google.visualization.GeoChart(document.getElementById('regions_div'));
    +
    +        function selectHandler() {
    +          var selectedItem = chart.getSelection()[0];
    +          if (selectedItem) {
    +            var message = data1.getValue(selectedItem.row, 0);
    +//            alert(message)
    +            //if(message=='Canada'){
    +            var display_value = [message];
    +          //};
    +          };
    +          /* Start of testing*/
    +
    +          // Create a dashboard.
    +          var dashboard = new google.visualization.Dashboard(
    +            document.getElementById('dashboard_div'));
    +
    +          // Create a range slider, passing some options
    +          var donutRangeSlider = new google.visualization.ControlWrapper({
    +            'controlType': 'NumberRangeFilter',
    +            'containerId': 'filter_div',
    +            'options': {
    +              //'filterColumnLabel': 'M602'
    +              'filterColumnIndex': [2]
    +            }
    +          });
    +
    +          // Create a range slider, passing some options
    +          var donutRangeSlider1 = new google.visualization.ControlWrapper({
    +            'controlType': 'NumberRangeFilter',
    +            'containerId': 'filter_div1',
    +            'options': {
    +              //'filterColumnLabel': 'M525'
    +              'filterColumnIndex': [3]
    +            }
    +          });
    +
    +          var categoryPicker = new google.visualization.ControlWrapper({
    +            controlType: 'CategoryFilter',
    +            containerId: 'control3',
    +            state: {
    +              selectedValues: display_value
    +            },
    +            options: {
    +              //filterColumnLabel: 'Location Type',
    +              filterColumnIndex: [0],
    +              ui: {
    +                labelStacking: 'vertical',
    +                allowTyping: false,
    +                allowMultiple: false,
    +                allowNone: false
    +              }
    +            }
    +          });
    +          // Create a column chart, passing some options
    +          var ColumnChart = new google.visualization.ChartWrapper({
    +            'chartType': 'ColumnChart',
    +            'containerId': 'chart1',
    +            'options': {
    +              'width': '900px',
    +              'legend': 'bottom',
    +              'vAxes': {
    +                0: {
    +                  title: 'Average Utilisation/Target Utilisation'
    +                },
    +                1: {
    +                  title: 'RMPV Utilisation'
    +                }
    +              },
    +              'series': {
    +                1: {
    +                  targetAxisIndex: 1
    +                }
    +              }
    +            },
    +            view: {
    +              columns: [1, 2, 3, 4]
    +            }
    +
    +
    +          });
    +
    +          // Create a table chart, passing some options
    +          var tableChart = new google.visualization.ChartWrapper({
    +            'chartType': 'Table',
    +            'containerId': 'chart2',
    +          });
    +
    +          var pie = new google.visualization.ChartWrapper({
    +            chartType: 'PieChart',
    +            containerId: 'chart3',
    +            options: {
    +              legend: 'bottom',
    +              title: 'Average Utilisation per month',
    +              pieSliceText: 'value'
    +            },
    +            // Instruct the piechart to use colums 0 (Name) and 3 (Donuts Eaten)
    +            // from the 'data' DataTable.
    +            view: {
    +              columns: [1, 4]
    +            }
    +          });
    +
    +          // Establish dependencies, declaring that 'filter' drives 'pieChart',
    +          // so that the pie chart will only display entries that are let through
    +          // given the chosen slider range.
    +          dashboard.bind([donutRangeSlider, donutRangeSlider1, categoryPicker], [ColumnChart, tableChart, pie]);
    +
    +          // Draw the dashboard.
    +          dashboard.draw(data);
    +          //redraw the main chart so selection doesn't retun empty
    +          chart.draw(data1, options);
    +          // End of testing*/
    +        };
    +
    +        google.visualization.events.addListener(chart, 'select', selectHandler);
    +
    +        chart.draw(data1, options);
    +      }
    +
    +    </script>
    +  </head>
    +  <body>
    +    <div id="regions_div" style="width: 900px; height: 500px;"></div>
    +    <!--Div that will hold the dashboard-->
    +  <div id="dashboard_div">
    +    <!--Divs that will hold each control and chart-->
    +    <table>
    +      <tr>
    +        <td>
    +        <div id="control3" align="center"></div>
    +        </td>
    +      </tr>
    +      <tr>
    +        <td>
    +          <div id="filter_div"></div>
    +        </td>
    +        <td>
    +          <div id="filter_div1"></div>
    +        </td>
    +      </tr>
    +    </table>
    +    <div id="chart1"></div>
    +    <div id="chart3"></div>
    +    <div id="chart2" align="center"></div>
    +    </div>
    +  </body>
    +</html>
    +

    +
  • +
  • +

    Modify as required and paste in to the text field. I used jsfiddle for checking the code and making changes, link here.

    +
  • +
  • +

    Run

    +
  • +
  • +

    Once the BIRT preview is working fine, create a dataset by calling the stored-procedure above and bind it to this text field.

    +
  • +
  • +

    To bind a dataset, in 'Report Design' perspective, click on the field and then in 'Property Editor' select 'Binding' as shown below:

    +

    Report Design

    +
  • +
  • +

    Then right click on the text field and click on edit and then click on the 'Fx' symbol next to where field was changed to HTML on top of the window. This will open javascript editor.

    +
  • +
  • +

    On javascript editor select the static dataset and replace it by selecting the dataset like so:

    +

    static dataset

    +
  • +
  • +

    That should change the HTML to dynamically get the data from json builder.

    +
  • +
+ + + + + + + + + + + + + + + + + + + + + +

+ + + + Comments +

+ + + + + + + +
+
+ + + + + +
+ + + +
+ + + +
+
+
+
+ + + + + + + + + + + + \ No newline at end of file diff --git a/2016/06/06/crossover.html b/2016/06/06/crossover.html new file mode 100644 index 0000000..1069209 --- /dev/null +++ b/2016/06/06/crossover.html @@ -0,0 +1,1733 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + Crossover - Make Gadgets Work + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + Skip to content + + +
+
+ +
+ + + + +
+ + +
+ +
+ + + + + + + + + +
+
+ + + + + + + + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+
+ + +
+
+
+
+ + + + + + + + + + + + + + + + + + + + +

Crossover

+ +

There are technologies that I can use in my personal life, that I am proud of and those that I play around with but reality is that as a Senior Project Manager, there aren't very many times when I get the opportunity to influence the choice of software to be used.

+

As a principle, my personal choices are driven by opensource and professional choices are driven by client needs. However, listed below are opensource technologies that crossed over from my personal to professional realm.

+
+

Three open-source technologies that jumped from my hobbyist curiosity to my professional life

+
+ + +

PlantUML

+

What is it?

+

PlantUML is a great tool to create flow and activity diagrams. It uses plain text to create nice looking flow diagrams which are really quick to prepare. Postioning and all the other stuff is just taken care of by the algorithm.

+

How do I use it?

+

As such there is no install required. For Chrome, there is an extension which can be installed or from any browser using [PlantText] (http://www.planttext.com).

+

I use it directly on the text editor at hand and create the flow I am after while taking notes on the process. It just works.

+

SpagoBI

+

What is it?

+

SpagoBI is a fully working Business Intelligence Suite which just works.

+

How do I use it?

+

As part of one of my projects. I had the freedom to pick any MI solution that was cost effective, robust and fit for purpose. SpagoBI ticks all the boxes so I took the opportunity to bring in this opensource solution.

+

It has very nice GUI and end user experience is great which meant it was an instant hit. It is live and am a user as well.

+

Talend

+

What is it?

+

Talend - does not really need an introduction. It is a well established name and is what techies call ETL - Enterprise Transport Layer.

+

To explain with an analogy, if different databases were people speaking different languages, then Talend is the translator who can not only explain what other party is speaking but can also perform some important jobs like getting the groceries, throwing garbage and arranging school pickups.

+

How do I use it?

+

Again as part of the aforementioned project, Talend is what was used to interact with an obscure offering from intuit called Quickbase as well as with MSSQL and mariaDB (another opensource tech we all love).

+

The biggest benefit that I derived apart from the regular data integration came from the fact that it is also able to place the dataset directly into a spreadsheet (Excel or LibreCalc).

+

Now, having all charts and formulae already in place as a template on spreadsheet kind of allows for report creation on a technology that these recipients are already working with and so they don't have to learn any new technology.

+

Instead of learning and struggling with something new, they are happy to get the information in a format they all accept and are comfortable with and can get on with their day job and make informed decisions based on what the report says. Icing on the cake, the emailing for these spreadsheets, once they were agreed as final versions, is also scheduled so the reports can be delivered in the format end-user likes without any human intervention.

+

What more can a project manager ask. 😀

+ + + + + + + + + + + + + + + + + + + + + + +
+
+ + + + + +
+ + + +
+ + + +
+
+
+
+ + + + + + + + + + + + \ No newline at end of file diff --git a/2016/06/27/seafile-server-behind-nginx-on-fedora-24-security-lab-spin.html b/2016/06/27/seafile-server-behind-nginx-on-fedora-24-security-lab-spin.html new file mode 100644 index 0000000..beddae3 --- /dev/null +++ b/2016/06/27/seafile-server-behind-nginx-on-fedora-24-security-lab-spin.html @@ -0,0 +1,2368 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + Seafile Server behind nginx on Fedora 24 Security Lab Spin - Make Gadgets Work + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + Skip to content + + +
+
+ +
+ + + + +
+ + +
+ +
+ + + + + + + + + +
+
+ + + + + + + + + + + + + + + +
+
+
+
+ + +
+
+
+
+ + + + + + + + + + + + + + + + + + + + +

Seafile Server behind nginx on Fedora 24 Security Lab Spin

+ +

I have recently been intrigued by the idea of replacing the likes of “Dropbox” and “Google Drive” with a cloud set-up of my own. I had "Owncloud" set-up for nearly a year but was not happy with it. There were minor niggles aside form speed and thumbnails and then “Owncloud” had a recent split leading to creation of “Nextcloud”.

+

While “Nextcloud” is the one that is more aligned to the general principles of community driven software, it is new and is still plagued with owncloud issues as it is essentially same stuff in new packaging at the moment.

+

In the meantime, every now and then I was reading all the good stuff people had to say about "Seafile" and so I wanted to give it a try. Now for the past year and a half I have also been using Fedora Security Lab spin on my home server and I just wanted to get the Seafile set-up on it so I did a few “duckduckgo” searches on the net and finally had the steps to achieve the objective. Obviously it all worked and my Seafile server is live and kicking, hence the post. 😄

+
+

So in nutshell my objective was to:

+

Install seafile-server-5 behind nginx on Fedora 24 Security Lab spin all on a 32 bit 12 year old laptop.

+
+

The steps I followed are listed below with detailed notes of what I did. I do not claim these to be perfect but this is what worked for me. If you know that something can be done better, please do let me know in the comments.

+ + +

Install required software to support seafile with nginx:

+
    +
  1. python
  2. +
  3. python-imaging
  4. +
  5. MySQL-python
  6. +
  7. python-setuptools
  8. +
  9. nginx
  10. +
  11. mariadb
  12. +
  13. mariadb-server
  14. +
  15. policycoreutils-python
  16. +
  17. setroubleshoot
  18. +
+

All this can be done with one single command:

+
 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
sudo dnf install \
+    python \
+    python-imaging \
+    MySQL-python \
+    python-setuptools \
+    nginx \
+    mariadb \
+    mariadb-server \
+    policycoreutils-python \
+    setroubleshoot
+
+

Start mariadb-server and set up basic security settings.

+

Now as we need to run all commands with “sudo”, it is actually easier to just go root. In other words, type "su" on the terminal and provide your password so you are logged in as root on the terminal. When you are logged in as root the $ changes to ## at command prompt. All commands below are on root so if you are not logged in as root, you will need to run these with "sudo".

+
1
+2
+3
+4
#Start mariadb: 
+[root@localhost /] systemctl start mariadb
+#Initiate the mariadb secure installation:
+[root@localhost /] mysql_secure_installation
+
+

Above command will trigger a set of questions around port, username, password etc. You can of course change these. If you do so, you must keep note of it and change your specific details for database in subsequent steps but if you leave it default you will basically have a user: root, with a password of your choice (we will use “sqlpasswd” for this example) and a database server running at port 3306.

+

Set up databases and privileges for "seafile'

+

Type following:

+
[root@localhost /] mysql -p
+
+

You will be prompted to enter a password. Provide the password you have set-up for maria-db server in step above.

+

:info: For our example it will be: sqlpasswd

+

Now on mysql prompt type following commands one after one along with semi-colon:

+
1
+2
+3
create database `ccnet-db` character set = 'utf8';
+create database `seafile-db` character set = 'utf8';
+create database `seahub-db` character set = 'utf8'
+
+

Now we will create a user named "seafile" for mysql with a password 'seafilepwd'. You must replace this password with one of your own. To do so use the command below and change seafilepwd with a password of your choice.

+
create user 'seafile'@'localhost' identified by 'seafilepwd';
+
+

Once seafile user is created, we need to grant permissions to this user on the three databases we created above. To do so, use the following one by one on mysql command prompt:

+
1
+2
+3
GRANT ALL PRIVILEGES ON `ccnet-db`.* to `seafile`@localhost;
+GRANT ALL PRIVILEGES ON `seafile-db`.* to `seafile`@localhost;
+GRANT ALL PRIVILEGES ON `seahub-db`.* to `seafile`@localhost;
+
+

Finally we will make sure that mariadb-server starts every-time the system is started. To do this simply copy the command below:

+
[root@localhost /] systemctl enable mariadb
+
+

Create directories to download and extract seafile server:

+
1
+2
+3
+4
#Create a directory called seafile:
+[root@localhost /] mkdir /opt/seafile
+Now change directory to the newly created path
+[root@localhost /] cd /opt/seafile
+
+

Download the latest seafile-server relevant to your machine architecture (32 bit, 64 bit etc). As I am using a 32 bit laptop, I used the link meant for that.

+
+

Tip

+

You can get the relevant link by first opening the seafile site in firefox (https://www.seafile.com/en/download/) and scrolling down all the way to "Server" section. Then under linux section you will see link for 64 bit and 32 bit versions. Right click on one you need and click on "Copy Link" Location&quot;. The copied text is what you need to paste after wget in command below.

+
+
[root@localhost /] wget https://bintray.com/artifact/download/seafile-org/seafile/seafile-server_5.1.3_i386.tar.gz
+
+

Now to extract the downloaded file, type following command.

+
[root@localhost /] tar -xzf seafile-server_5.1.3_i386.tar.gz
+
+
+

Tip

+

If downloaded version is different, the filename "seafile-server_5.1.3_i386.tar.gz" will be different in the link you would have got from previous step. Use the file name right at the end of the link you copied.

+
+

OK, now we will create a directory named “installed” and move the downloaded file in there.

+
1
+2
[root@localhost /] mkdir installed
+[root@localhost /] mv seafile-server_5.1.3_i386.tar.gz installed
+
+

Checkpoint

+

At this point, if you give the tree command, your directory structure would look as shown below

+
 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
[root@localhost seafile] tree -L 2
+
+.
+|---installed
+|----- seafile-server_5.1.3_i386.tar.gz
+|---seafile-server-5.1.3
+|----- check_init_admin.py
+|----- reset-admin.sh
+|----- runtime
+|----- seaf-fsck.sh
+|----- seaf-fuse.sh
+|----- seaf-gc.sh
+|----- seafile
+|----- seafile.sh
+|----- seahub
+|----- seahub.sh
+|----- setup-seafile-mysql.py
+|----- setup-seafile-mysql.sh
+|----- setup-seafile.sh
+|----- upgrade
+
+

Configure the seafile-server

+
1
+2
+3
+4
#Change directory to seafile-server where the execution scripts are residing.
+[root@localhost seafile] cd seafile-server-5.1.3
+#Now run the execution script:
+[root@localhost seafile-server-5.1.3] ./setup-seafile-mysql.sh
+
+

Running this script will initiate the seafile server set-up for mysql. You will need to provide answers to some questions. I am providing those below where you need to deviate from default or need to provide specifc information:

+
    +
  1. Name of the server. Provide a servername like "my_seafile_cloud" or "cloudy_lemon" ...you get the gist.
  2. +
  3. +

    IP or Domain of Server: + !!! site-tip "Tip" + Now let's take a bit of time to understand what should go here. For my usage I wanted to use dyndns so I can access this server from outside of my home network. So I had to configure my dyndns url to a port on my home router such that router understood that incoming traffic to that particular port must be transferred to this machine where the server is hosted.

    +
    Then on this machine, I have forwarded the traffic coming to a specific port again to whichever server I want to access thus having my unique URL for each service I am interested in. I will go into in a bit more detail later but for now, just ensure you are providing 1 the internal IP as well as dyndns IP along with the port you have configured on your router to reach this machine in particular where you are configuring the server.
    +
    +So if internal IP of this machine is 192.168.1.24 and my dyndns url is banana.dyndns.com and I have forwarded port 9994 on my router to this machine and on machine incoming traffic to 9994 is forwarded to the port relevant to seahub then I will provide following entries as answer to this question: *192.168.1.24:9994`, banana.dyndns.com*
    +
    +
      +
    1. Choose a way to initiatlise seafile databases: Now remember we have already done this in step 2. So for this question we will need to give option 2. so just type 2 and press enter.
    2. +
    3. Host of mysql server: Unless you changed this in step 2, leave it as default (localhost)
    4. +
    5. Port for mysql server: as above leave it default (3306) unless you changed in Step 2.
    6. +
    7. User for seafile: seafile
    8. +
    9. Password for user "seafile': Use the password provided in Step 3. "seafilepwd" is what we provided for this example.
    10. +
    11. Database name for ccnet: ccnet-db
    12. +
    13. Database name for seafile: seafile-db
    14. +
    15. Database name for seahub: seahub-db
    16. +
    +

    Once this wizard has completed configuring, you will get a confirmation that will tell you that seafile is now listening on port 8082 and seahub on port 8000 unless you changed these while providing answers to the wizard.

    +
  4. +
+

Add the user and provide right access

+

Use following commands to go up one directory, add a user “seafile” and provide right privileges.

+
1
+2
+3
[root@localhost seafile-server-5.1.3] cd ..
+[root@localhost seafile] adduser seafile
+[root@localhost seafile] chown -R seafile .
+
+

Generate SSL Certificate:

+
1
+2
+3
+4
+5
#Change directory to /etc/ssl
+[root@localhost seafile] cd /etc/ssl
+#Generate private and public ssl certificates
+[root@localhost ssl] openssl genrsa -out seafile_privkey.pem 2048
+[root@localhost ssl] openssl req -new -x509 -key seafile_privkey.pem -out seafile_cacert.pem -days 1095
+
+

Create the nginx config file for seafile

+

Use following command to create seafile.conf file:

+
[root@localhost ssl] nano /etc/nginx/conf.d/seafile.conf
+
+

Copy Paste the following but do make relevant changes where required for servername etc:

+
/etc/nginx/conf.d/seafile.conf
 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
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
########################################################
+server {
+    listen      80;
+    server_name *banana.dyndns.com*; --> Must change this as per your set-up. Notice no port 9994 here.
+    rewrite ^ https://$http_host$request_uri? permanent;   ## force redirect http to https. 
+}
+server {
+    listen 443;
+    ssl on;
+    ssl_certificate /etc/ssl/seafile_cacert.pem;
+    ssl_certificate_key /etc/ssl/seafile_privkey.pem;
+    server_name *banana.dyndns.com*; -->Must change this as per your set-up. Notice no port 9994 here. 
+    proxy_set_header X-Forwarded-For $remote_addr;
+    add_header Strict-Transport-Security "max-age=31536000; includeSubdomains&amp;quot;;
+    server_tokens off;
+    location / {
+        fastcgi_pass   127.0.0.1:8000;
+        fastcgi_param  SCRIPT_FILENAME    $document_root$fastcgi_script_name;
+        fastcgi_param  PATH_INFO          $fastcgi_script_name;
+        fastcgi_param  SERVER_PROTOCOL    $server_protocol;
+        fastcgi_param  QUERY_STRING       $query_string;
+        fastcgi_param  REQUEST_METHOD     $request_method;
+        fastcgi_param  CONTENT_TYPE       $content_type;
+        fastcgi_param  CONTENT_LENGTH     $content_length;
+        fastcgi_param  SERVER_ADDR        $server_addr;
+        fastcgi_param  SERVER_PORT        $server_port;
+        fastcgi_param  SERVER_NAME        $server_name;
+        fastcgi_param  HTTPS              on;
+        fastcgi_param  HTTP_SCHEME        https;
+        access_log     /var/log/nginx/seahub.access.log;
+        error_log      /var/log/nginx/seahub.error.log;
+    }
+    location /seafhttp {
+        rewrite ^/seafhttp(.*)$ $1 break;
+        proxy_pass http://127.0.0.1:8082;
+        client_max_body_size 0;
+        proxy_connect_timeout 36000s;
+        proxy_read_timeout 36000s;
+        proxy_send_timeout 36000s;
+    }
+    location /media {
+        root /opt/seafile/seafile-server-latest/seahub;
+    }
+
+########################################################
+
+

Apply config changes to seafile

+

1
+2
+3
+4
#Change to conf directory at /opt/seafile.
+[root@localhost ssl] cd /opt/seafile/conf
+#Now open the ccnet.conf file:
+[root@localhost conf] nano ccnet.conf
+
+Amend the file as per below:

+
/opt/seafile/conf/ccnet.conf
 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
+26
+27
########################################################
+
+[General]
+USER_NAME = my_seafile_cloud 
+#**Must change this as per your setup.**
+ID =
+NAME = my_seafile_cloud ####&lt;--------- Must change this as per your setup.
+SERVICE_URL = https://192.168.1.24:9994,https://banana.dyndns.com:9994 
+#**Must change this as per your setup.**
+#**Also note that here we have provided the 9994 port **
+#**- the one we used as port forward on router **
+#**to send traffic to this machine.**
+
+[Client]
+PORT = 13419
+
+[Database]
+ENGINE = mysql
+HOST = 127.0.0.1
+PORT = 3306
+USER = seafile
+PASSWD = sqlpasswd 
+#**This would be same as what was provided in Step 2.**
+DB = ccnet-db
+CONNECTION_CHARSET = utf8
+
+########################################################
+
+

Open the seahub_settings.py file

+
[root@localhost conf] nano seahub_settings.py
+
+

Add the line FILE_SERVER_ROOT= 'https://banana.dyndns.com:9994/seafhttp' replacing banana.dyndns.com:9994 with url to reach your machine.

+
seahub_settings.py
 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
########################################################
+
+DATABASES = 
+{
+    'default': 
+    {
+    'ENGINE': 'django.db.backends.mysql',
+    'NAME': 'seahub-db',
+    'USER': 'seafile',
+    'PASSWORD': 'sqlpasswd', # (1) 
+    'HOST': '127.0.0.1',
+    'PORT': '3306'
+    }
+}
+
+FILE_SERVER_ROOT= 'https://banana.dyndns.com:9994/seafhttp'  # (2)
+#**Must change this as per your setup.**
+#**Also note that here we have provided the 9994 port **
+#**- the one we used as port forward on router **
+#**to send traffic to this machine.**
+
+
    +
  1. This will be same as password created in Step 2.
  2. +
  3. Replace banana.dyndns.com:9994 with url and port rerquired to reach your machine
  4. +
+

Create and enable the services to run at system startup

+

Create a seafile.service file:

+
[root@localhost conf] nano /etc/systemd/system/seafile.service
+
+

Paste the below into the file:

+
/etc/systemd/system/seafile.service
 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
########################################################
+
+[Unit]
+Description=Seafile
+After=network.target mariadb.service
+
+[Service]
+Type=oneshot
+ExecStart=/opt/seafile/seafile-server-latest/seafile.sh start
+ExecStop=/opt/seafile/seafile-server-latest/seafile.sh stop
+RemainAfterExit=yes
+User=seafile
+Group=seafile
+
+[Install]
+WantedBy=multi-user.target
+#########################################################
+
+

Now reload the systemd and enable seafile service to start at system start-up.

+
1
+2
[root@localhost conf] systemctl daemon-reload
+[root@localhost conf] systemctl enable seafile
+
+

Create a seahub.service file:

+
[root@localhost conf] nano /etc/systemd/system/seahub.service
+
+

Paste the following in the file and save.

+
/etc/systemd/system/seahub.service
 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
##########################################################
+[Unit]
+Description=Seafile hub
+After=network.target seafile.service
+
+[Service]
+ExecStart=/opt/seafile/seafile-server-latest/seahub.sh start-fastcgi
+ExecStop=/opt/seafile/seafile-server-latest/seahub.sh stop
+User=seafile
+Group=seafile
+Type=oneshot
+RemainAfterExit=yes
+
+[Install]
+WantedBy=multi-user.target
+##########################################################
+
+

Now, enable seahub to start at system start-up and start seafile and seahub services.

+
1
+2
+3
[root@localhost conf] systemctl enable seahub
+[root@localhost conf] systemctl start seafile
+[root@localhost conf] systemctl start seahub
+
+

Nginx Service

+

Start nginx service and enable to run at system start

+
1
+2
[root@localhost conf] systemctl enable nginx.service
+[root@localhost conf] systemctl start nginx.service
+
+

Fedora specific steps:

+

Now, at this point there were few final things specific to Fedora that needed attention. I was ending up getting error 500 and what not and after a bit of searching on duckduckgo, I tried steps listed in this section that fixed the issue.

+

I am not entirely sure what this does but what I understood is selinux was not allowing some access between nginx and seafile which this resolved. How? Well, you tell me 😀.

+

I had to install policycoreutils-python and setroubleshoot packages for these commands to work, so I have included them in Step 1 anyway.

+
1
+2
+3
[root@localhost conf] cat /var/log/audit/audit.log | grep nginx | grep denied | audit2allow -M mynginx
+[root@localhost conf] semodule -i mynginx.pp
+[root@localhost conf] setsebool httpd_can_network_connect 1
+
+

Re-set admin user and password for seafile:

+
1
+2
+3
+4
#Change directory to seafile
+[root@localhost conf] cd /opt/seafile/seafile-server-5.1.3/
+#Now run the reset-admin
+[root@localhost seafile-server-5.1.3] ./reset-admin.sh
+
+

Firewall configuration and port forwarding:

+

Notes below are relevant to system running Fedora Security Lab spin with XFCE but general principle will be same.

+
    +
  • +

    Application>Administration>Firewall will open firewall gui.

    +

    Firewall GUI

    +
  • +
  • +

    You will be asked for password and upon entering you will be presented with Zones and Services.

    +

    Zones and Services

    +
  • +
  • +

    In Services enable https by clicking the checkbox against “https”.

    +

    Enable https

    +
  • +
  • +

    Now click on "Ports" and click on "Add".

    +

    Click Add

    +
  • +
  • +

    Then add port 8082 in "Port / Port Range" and select TCP as Protocol.

    +
  • +
  • Then add port 8082 in "Port / Port Range" and select UDP as Protocol.
  • +
  • Then add port 8000 in "Port / Port Range" and select TCP as Protocol.
  • +
  • Then add port 8000 in "Port / Port Range" and select UDP as Protocol.
  • +
  • +

    Then add port 443 in "Port / Port Range" and select TCP as Protocol.

    +

    Protocol

    +
  • +
  • +

    Now click on Port Forward tab and click on Add. Then fill field as shown below:

    +
    1
    +2
    +3
    +4
    +5
    +6
    +7
    Protocol: tcp  
    +Port / Port Range: 9994 
    +## This is the port you have used on your router for port forwarding to this machine.
    +Under Destination section, tick the checkbox for "Local Forwarding"
    +Port / Port Forwarding field: 443 
    +## If you have used above configuration, SSL is enabled at 443. 
    +## If you have changed it, mention the relevant port.
    +
    +
    +

    Tip

    +

    Once you have made these changes don't forget to save this from run time to permanent or these changes will be lost when you restart the machine. [Options-> Runtime to Permanent]

    +
    +
  • +
+

Finally, restart nginx:

+

Use the following command to restart nginx services.

+
[root@localhost seafile-server-5.1.3] systemctl restart nginx.service
+
+

This is it. All Done !!!

+

If you now type your URL: (https://banana.dyndns.com:9994 from this example) on a browser, you will be presented with login page where you should use the admin login details you have created above.

+ + + + + + + + + + + + + + + + + + + + + + +
+
+ + + + + +
+ + + +
+ + + +
+
+
+
+ + + + + + + + + + + + \ No newline at end of file diff --git a/2016/07/01/ghost-on-fedora-24.html b/2016/07/01/ghost-on-fedora-24.html new file mode 100644 index 0000000..a2f95e3 --- /dev/null +++ b/2016/07/01/ghost-on-fedora-24.html @@ -0,0 +1,1910 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + Ghost on Fedora 24 - Make Gadgets Work + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + Skip to content + + +
+
+ +
+ + + + +
+ + +
+ +
+ + + + + + + + + +
+
+ + + + + + + + + + + + + + + +
+
+
+
+ + +
+
+
+
+ + + + + + + + + + + + + + + + + + + + +

Ghost on Fedora 24

+ +

To install Ghost as my blogging platform, I had to go through a number of hoops and one of them was to get the nodejs working and what not. I figured this might as well be worth documenting in case I have to do this all over again. It might also be helpful for some other inquisitive minds. 😄

+

The most useful reference I found was the post on rosehosting website specific to CentOS 7.

+

It would have all gone well too; had it not been for the nodejs related issues which resulted in me finding the other helpful pointers from various forums.

+

Anyway, the steps I took to get this all working are detailed in my notes below - keeping it, where I can, true to the post I have referred above:

+ + +

Step 1: Install nodejs and npm

+

On Fedora 24 node.js package already includes npm and if you try installing npm separately it will throw an error so just install node.js and npm will be installed along with it.

+
1
+2
sudo dnf distro-sync
+sudo dnf install nodejs
+
+

Step 2: Install dependencies

+
 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
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
sudo dnf install php php-fpm php-cli php-mysql php-curl php-gd
+
+#Create a directory for the website:
+mkdir /var/www/html/[sitefolder. eg: blog, myblog, banana] 
+
+#Change to the newly created directory:
+cd /var/www/html/[sitefolder. eg: blog, myblog, banana] 
+
+#Set access permissions for this directory
+chown -R /var/www/html/[sitefolder. eg: blog, myblog, banana]
+
+#Download latest version of Ghost:
+curl -L https://ghost.org/zip/ghost-latest.zip -o ghost.zip
+
+#Unzip the downloaded file
+unzip ghost.zip
+
+#Finally check the directory structure
+tree -L 2
+
+#OUTPUT of above command should look like as shown below:
+.
+├── config.example.js
+├── config.js
+├── content
+   ├── apps
+   ├── data
+   ├── images
+   └── themes
+├── core
+   ├── built
+   ├── index.js
+   ├── server
+   └── shared
+├── ghost.zip
+├── Gruntfile.js
+├── index.js
+├── LICENSE
+├── npm-shrinkwrap.json
+├── package.json
+├── PRIVACY.md
+└── README.md
+
+

Step 3: Install npm modules

+

While installing/initiating npm modules, there were several errors that system was throwing. They were in two categories:

+
    +
  • Access Related
  • +
  • Dependencies Related
  • +
+

Access Related - I was getting EACCES error and solution given on on npmjs.com under Option 2 is what sorted the access issues.

+

1
+2
+3
mkdir ~/.npm-global
+npm config set prefix '~/.npm-global'
+nano ~/.profile
+
+Add the line export PATH=~/.npm-global/bin:$PATH in the opened file.

+

source ~/.profile
+
+Dependencies Related - Some forum hopping later I just followed the advice on Ghost support and installed the dependencies. Steps below:

+
 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
#install required dependencies:
+npm install -g node-gyp
+sudo dnf install gcc gcc-c++`</pre>
+Once above dependencies are installed following code should just work.
+<div class="boxed">NOTE: 
+Make sure you are in the directory you created in step2.</div>
+<pre class="line-numbers language-bash">`#Install PM2 a process manager to control Node.js applications 
+#It will help in keeping specified Node.js applications alive forever:
+npm install pm2 -g
+#Install 
+npm install --production
+#Start Ghost with pm2 and create a name for the pm2
+NODE_ENV=production pm2 start index.js --name "Ghost"
+
+

Step 4: Tell Ghost your blog URL

+

A very simple change is required to config.js file as shown below:

+
1
+2
+3
#Copy the sample config file
+cp config.example.js config.js
+nano config.js
+
+

File that opens will have following javascript: +

 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
// ## Ghost Configuration
+// Setup your Ghost install for various environments.
+// Ghost runs in `development` mode by default. Full documentation can be found at https://support.ghost.org
+
+var path = require('path'),
+    config;
+
+config = {
+    // #### Production
+    // When running Ghost in the wild, use the production environment.
+    // Configure your URL and mail settings here
+    production: {
+        url: 'http://your.blog.com',
+

+

It's the line number 14 in above code block where you need to replace http://your.blog.com with actual url of your blog.

+

Step 5: Configure Nginx

+

NGINX install and configuration is something I covered in my post for installing Seafile on Fedora 24. So I already had a running nginx. I just needed to create a reverse proxy for Ghost on the existing nginx server.

+

Open the hostfile using following command:

+
sudo nano /etc/hosts
+
+

Now, in the hosts file add the localhost alias for blog - in this example it is your.localhost.com.

+

127.0.0.1  localhost.localdomain localhost your.seafile.com your.blog.com
+
+Open the file using following command.

+
+

Note

+

Replace yourblog.conf with your actual blog's conf file name.

+
+
nano /etc/nginx/conf.d/yourblog.conf
+
+

On the file that opens copy and paste the following code.

+
+

Note

+

Replace your.blog.com on line number 6 below with alias for localhost for this blog you added to the host file above.

+
+
 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
upstream ghost {
+server 127.0.0.1:2368;
+}
+server {
+     listen      80;
+     server_name your.blog.com;
+     access_log  /var/log/nginx/ghost.access.log;
+     error_log   /var/log/nginx/ghost.error.log;
+     proxy_buffers 16 64k;
+     proxy_buffer_size 128k;
+
+        location / {
+         proxy_pass  http://ghost;
+         proxy_next_upstream error timeout invalid_header http_500 http_502 http_503 http_504;
+         proxy_redirect off;
+         proxy_set_header    Host            $host;
+         proxy_set_header    X-Real-IP       $remote_addr;
+         proxy_set_header    X-Forwarded-For $proxy_add_x_forwarded_for;
+         proxy_set_header    X-Forwarded-Proto https;
+     }
+  }
+
+

Step 6: Start Ghost and nginx

+

After all the above steps are completed issue following commands to restart ghost and nginx.

+
1
+2
sudo systemctl restart nginx.service
+pm2 restart Ghost
+
+

All Done !!!

+ + + + + + + + + + + + + + + + + + + + + + +
+
+ + + + + +
+ + + +
+ + + +
+
+
+
+ + + + + + + + + + + + \ No newline at end of file diff --git a/2016/07/17/the-complete-walkthrough-of-my-blogger-to-ghost-migration.html b/2016/07/17/the-complete-walkthrough-of-my-blogger-to-ghost-migration.html new file mode 100644 index 0000000..bf934a5 --- /dev/null +++ b/2016/07/17/the-complete-walkthrough-of-my-blogger-to-ghost-migration.html @@ -0,0 +1,1930 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + The complete walkthrough of my blogger to ghost migration - Make Gadgets Work + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + Skip to content + + +
+
+ +
+ + + + +
+ + +
+ +
+ + + + + + + + + +
+
+ + + + + + + + + + + + + + + +
+
+
+
+ + +
+
+
+
+ + + + + + + + + + + + + + + + + + + + +

The complete walkthrough of my blogger to ghost migration

+ +

The 7 Year Itch

+

It can't possibly be a coincidence that this is the 7th year since I started blogging on blogger and therefore it is very likely to be a strong case of the 7 year itch syndrome but whichever way you look at it, divorce was inevitable given blogger had just stopped inspiring me.

+

I have been fiddling with different blogging platforms while getting accused of neglecting my sweet and loving family...😢. Ghost caught my fancy three weeks back. The last post was the beginning of our courtship and this post tells the tale of how a casual fling turned into marital commitment. 😂

+

To start a fresh blog, choosing any platform is easy and straight forward but to move from one platform to another is - umm... lets just say a very involved process - rewarding but involved.

+

Love can move mountains!!!

+

A complete migration from blogger to WordPress would have been way simpler. I know this as I have done it in past and it appeared like moving to Ghost would require migrating to a WordPress instance anyway. There was - I must admit - a temptation to call WordPress the home but that wouldn't have made a great love-story now - would it?

+

However, the much publicised WordPress route to Ghost migration did not work for me and eventually after a lot of manual copying, pasting, cleaning, pruning, hiding, reading and learning later, the self-hosted blog is all complete.

+ + +

Install Ghost

+

This is covered in my last post. Once it was installed, I took some time exploring and learning Markdown. +Last post was my first one using Markdown and it was a very pleasing experience indeed. That nice experience paved way as well as helped me finalise the decision to go the whole nine yards.

+

Install a theme

+

There are some very beautiful themes available on Ghost Marketplace. I have used the theme called scrawl and then tweaked it a bit.

+

Once I found the theme I liked, I downloaded the zip file, unzipped it and deleted the zip file like so.

+
1
+2
+3
+4
+5
+6
+7
+8
#Download
+curl -L https://github.com/ktweeden/scrawl/archive/master.zip -o master.zip
+#unzip
+unzip -o master.zip
+#Delete zip file
+rm master.zip
+#restart Ghost
+pm2 restart Ghost
+
+

Theme is now installed.

+

Configure the theme

+
    +
  1. The first thing I wanted to configure on my new theme was the code block. Prismjs is the way to go and it is already included in the theme I downloaded but the line numbering was not there. After reading a bit on PrismJS website, I understood that core css file from prism did not have this and also I wanted the dark theme so I downloaded the 'okadia' theme css along with line number plugin.
  2. +
+

I then replaced the content of /var/www/html/site-name/content/themes/scrawl-master/assets/css/prism.css with the content in downloaded CSS.

+
    +
  1. Next thing I wanted to change was the header background colour and also the link colour. While it was very close to what I wanted, my actual liking is for the colour #F2C20F and a bit darker link colour #B710EF. To do this I edited the _global_styles.scss like so:
  2. +
+ +
1
+2
+3
+4
#change directory
+cd /var/www/html/site-name/content/themes/scrawl-master/sass/partials 
+/#Edit _global_styles.scss
+nano _global_styles.scss 
+
+

Now change the colour of $primary-colour and also add $link-colour. After this your / Colour / section will look as shown below: +

_global_styles.scss
/* Colours */
+$primary-colour: #F2C20F;
+$secondary-colour: #254E70;
+$tertiary-colour: #FF4B3E;
+$font-colour: #011627;
+$background-colour: #EFEFEF;
+$link-colour: #B710EF;
+

+

Then I changed directory using cd /var/www/html/site-name/content/themes/scrawl-master/assets/css and opened index.css and post.css where I changed the background-color property to #F2C20F as shown below:

+

=== index.css

+
```css
+/**
+  MIXINS
+**/
+.blog-title-background {
+  background-color: #F2C20F;
+  width: 100%;
+  display: -webkit-box;
+  display: -ms-flexbox;
+  display: flex;
+  -webkit-box-orient: vertical;
+  -webkit-box-direction: normal;
+      -ms-flex-direction: column;
+          flex-direction: column; }
+```
+
+

=== post.css

+
/**line 86/434 (19%), col 22/29**/
+.blog-title-background {
+  background-color: #F2C20F;
+  width: 100%; }
+
+

Enable commenting on the blog with DISQUS

+

Using DISQUS to enable comments is extremely simple especially on scrawl - the theme I have used and is as explained under the DISQUS section of the theme website:

+

To enable commenting:

+
    +
  • I created an account on Disqus.
  • +
  • Then I created a 'channel' for this blog in DISQUS using steps below:
  • +
  • Open [DISQUS](https://disqus.com/) and click on 'Get Started' to sign-up
  • +
  • Once the sign-up was completed, email verified etc, I went to [channels link on home page of disqus](https://disqus.com/home/channels/) and created a channel say `thetestchannel`
  • +
  • Copied the following code into the blog header code injection:
  • +
+ +
<script>window.__themeCfg.disqusUsername = 'thetestchannel';</script> // (1)
+
+
    +
  1. thetestchannel must be replaced with the name of the channel created on DISQUS.
  2. +
+

Migrate content from blogger

+

Now, this was the most painful of all the things I had to do - the moving of mountain if you like - because the automated solution using Blogger2Ghost just wouldn't work for me. So I, basically, copy pasted most of the content from blogger over to Ghost and placed the various screenshots manually.

+

If my posts were in 3 digits, I would have persevered and tried contacting someone for help but as it was relatively less content, I just went ahead and did it manually and am glad I did so because that way I was able to do a bit of clean-up too.

+

Redirect Traffic from old blog

+

There are a huge number of posts on www for 301 redirect and what not but I felt, it is only fair to let the redirects land on old blog and users be told there of the new destination so like last step, I manually updated the posts on blogger to just let the reader know that the post they are looking for has moved to new location and link for that post on new site. +Not most elegant and efficient approach but I am happy that way.

+
+

Warning

+

If there are huge entries and one still has to go the manual route, it will save a lot of pain if the permanent link for new posts on Ghost is same as that on old blog and this can be achieved by clicking on clog next to "Save Draft" on the post and changing the "Post URL"

+
+

Migrate comments from old blog

+

Migrating comments from blogger to DISQUS is very easy.

+
    +
  • I opened the blogger importing tool which can be accessed using a URL similar to this - https://thetestchannel.disqus.com/admin/discussions/import/platform/blogger/
  • +
+

However, getting them reflected on new ghost instance has three approaches dependent on the route one takes for redirecting from old blog. As I chose no redirection as such, I had to go for a CSV mapping file between my old and new blog URL per post.

+

Once again, for the number of posts I have this was not a challenge at all but I can't imagine doing such a thing for a big content transfer and it may be worth paying attention to the note in step above if one has huge content to transfer as it will reduce the pain for comments transfer.

+

Enable Search for your blog

+

Ghost recommends Swifttype and the popular site Ghost for beginners provides guidance for Google CSE (Custom Search Engine). I did not like the idea of paying for Swifttype for my small site and Google Search Engine was taking it's sweet time to crawl my site - unlike in past now we cant order / request a crawl and there was ofcourse Google Custom Search watermark which I am not all that fond of.

+
+

Success

+

I updated the theme I am using to include search feature.

+
+ +

This is, much like many other customisations I did, a very theme specific step and for the theme I have chosen it is fortunately very easy to achieve except for linkedin and google+ for which I had to add few lines of code. I achieved this using steps below:

+
    +
  • Adding already available social icons is very simple and most of the social links are actually available out of the box. So for Tumblr and Instagram all I had to do was open the admin panel of Ghost, click on Code Injection link and paste the following two lines replacing yourusername with my username on that platform.
  • +
+
1
+2
<script>window.__themeCfg.tumblrUsername = 'yourusername';</script>
+<script>window.__themeCfg.instagramUsername = 'yourusername';</script>
+
+
    +
  • For LinkedIn and Google+:
      +
    • Open the footer.hbs using nano /var/www/html/site-name/content/themes/scrawl-master/partials/footer.hbs and pasted the following code under div class footer-container around line 14. +
      1
      +2
      <a class="social-button linkedin hidden"><i class="icon-linkedin"></i></a>
      +<a class="social-button gplus hidden"><i class="icon-gplus"></i></a>
      +
    • +
    • Then under scripts I added following code around line number 42 +
       1
      + 2
      + 3
      + 4
      + 5
      + 6
      + 7
      + 8
      + 9
      +10
      +11
      +12
      <ul>
      +<li>
      +case "Linkedin":
      +return "https://uk.linkedin.com/in/" + username;
      +</li>
      +</ul>
      +<ul>
      +<li>
      +case "Gplus":<br>
      +return `[https://plus.google.com/](https://plus.google.com/)` + username;<br>
      +</li>
      +</ul>
      +
    • +
    • Finally under the function revealSocialLinks around line number 69, I pasted the following code: +
      1
      +2
      revealPlatform('Linkedin');
      +revealPlatform('Gplus');
      +
    • +
    • Finally I added the following in the header part of the code injection in front end admin panel of Ghost instance; replacing yourusername with my username for the relevant platform. +
      1
      +2
      <script>window.__themeCfg.linkedinUsername = 'yourusername';</script>
      +<script>window.__themeCfg.gplusUsername = 'yourusername';</script>
      +
    • +
    +
  • +
+

My Learning Curve

+

While there were many insights and learnings, the top 5 I think on the list of learning for me while migrating from blogger to ghost are:

+
    +
  1. +Clarity on using Nginx (refer last post.) - Never before have I played around with nginx but having read so many good things about it, I was keen to try and I can say it is indeed living up to all the praise I have read. It is fast and lightweight. Will I use it for production? Ofcourse I will. Will I favour it over Apache - hmm...Maybe... Maybe not. I think I will weigh my options based on what is it I want to achieve but to know about both nginx as well as Apache Server can't hurt. +
  2. +
  3. +Better Idea of Networking Concepts - With Seafile and Ghost implementation in succession, I actually learnt about quite a few concepts on configuring firewall, DNS, reverse proxy, port forwarding to list a few but more importantly I read a lot about networking and this helped me understand the core concepts involved. +
  4. +
  5. +Deeper understanding of Web Technologies and inner workings of Ghost - While trying to modify my blog's look and feel to my liking, I had to mess around with CSS, HTML. I have used the theme scrawl by ktweeden and modified it a bit. What attracted me most to it was the fact that it used a default colour theme similar to what I had in mind. I modified it but in doing so got a bit more understanding of how it all ties together - the default.hbs, index.hbs, post.hbs and the underlying handlebar scripts. +
  6. +
  7. +Markdown - I had some basic idea before I started using Ghost but if I were to use Ghost full-time, I needed to know all there is to know about markdown and while there isn't a lot - it is damn straight forward and simple - it is a very good tool indeed. I am glad I learnt it. +
  8. +
  9. +CSS plugins - Prismjs comes pre-loaded with scrawl theme but the default skin and plugins were not enough for me so I had to replace these. Now this might be very simple for people in the know. For me, it was new and exciting and end result was so cool with numbers in code box and all that. I am obviously very happy with the end result and it did help me learn yet again how things interact within the Ghost platform. +
  10. +
+ +

The Beginning !!! 😍😃😂

+ + + + + + + + + + + + + + + + + + + + + + +
+
+ + + + + +
+ + + +
+ + + +
+
+
+
+ + + + + + + + + + + + \ No newline at end of file diff --git a/2016/07/19/mysql-function-to-calculate-elapsed-working-time.html b/2016/07/19/mysql-function-to-calculate-elapsed-working-time.html new file mode 100644 index 0000000..4c96571 --- /dev/null +++ b/2016/07/19/mysql-function-to-calculate-elapsed-working-time.html @@ -0,0 +1,2011 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + MySQL function to calculate elapsed working time - Make Gadgets Work + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + Skip to content + + +
+
+ +
+ + + + +
+ + +
+ +
+ + + + + + + + + +
+
+ + + + + + + + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+
+ + +
+
+
+
+ + + + + + + + + + + + + + + + + + + + +

MySQL function to calculate elapsed working time

+ +

I wrote this function to cater for a specific requirement and I don't know if there are better ways of doing it but this saved tremendous amount of time and might have real time application elsewhere.

+

Problem Statement:

+

Find out the age of an incident in working minutes, given the following:

+
    +
  1. Time and Date of when an incident was logged
  2. +
  3. Time and date of when the same incident was closed
  4. +
  5. Opening time of the site for which the incident was logged
  6. +
  7. Closing Time of the site for which incident was logged
  8. +
  9. Country of the site for which incident has been logged
  10. +
+
+

Assumption

+

It is assumed that opening and closing times are same on all working days and that all the sites are closed on holidays and weekends

+
+

Function should take all the above five "given" as parameter and then calculate age of the incident.

+ + +

Example of problem

+

Let's say an incident was logged on "Friday 10th June 2016 at 12:00" for a site in the "UK" which opens between 09:00 to 16:00. This incident was then closed on "Tuesday 14th June 2016 at 14:00".

+

For the above incident function should calculate the age as 960 minutes = 16 hours = [4 hours on Friday (12:00 to 16:00) + 7 hours on Monday (09:00 to 16:00) + 5 hours on Tuesday (09:00 to 14:00)]

+
+

home_image

+
+

Flowchart for function

+
+
+

Pre-requisites:

+

A holiday table for the "Country" of the site for which incident is being provided should already be created on the database with the name holiday_table. Created using code below:

+
 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
CREATE TABLE `holiday_table` (
+    `holiday_table_id` INT(11) NOT NULL,
+    `holiday_date` DATETIME NULL DEFAULT NULL,
+    `week_day` VARCHAR(12) NULL DEFAULT NULL,
+    `holiday_name` VARCHAR(45) NULL DEFAULT NULL,
+    `Country_codes` VARCHAR(45) NOT NULL DEFAULT 'ALL',
+    PRIMARY KEY (`holiday_table_id`)
+)
+COLLATE='utf8_general_ci'
+ENGINE=InnoDB
+;
+
+

Sample sql-insert Data for holiday_table:

+
 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
INSERT INTO `holiday_table` (`holiday_table_id`, `holiday_date`, `week_day`, `holiday_name`, `Country_codes`) VALUES (2, '2016-03-25 00:00:00', 'Friday', 'Good Friday', 'ALL');
+INSERT INTO `holiday_table` (`holiday_table_id`, `holiday_date`, `week_day`, `holiday_name`, `Country_codes`) VALUES (3, '2016-03-28 00:00:00', 'Monday', 'Easter Monday', 'ALL');
+INSERT INTO `holiday_table` (`holiday_table_id`, `holiday_date`, `week_day`, `holiday_name`, `Country_codes`) VALUES (4, '2016-05-02 00:00:00', 'Monday', 'Early May bank holiday', 'ALL');
+INSERT INTO `holiday_table` (`holiday_table_id`, `holiday_date`, `week_day`, `holiday_name`, `Country_codes`) VALUES (5, '2016-05-30 00:00:00', 'Monday', 'Spring bank holiday', 'ALL');
+INSERT INTO `holiday_table` (`holiday_table_id`, `holiday_date`, `week_day`, `holiday_name`, `Country_codes`) VALUES (6, '2016-08-29 00:00:00', 'Monday', 'Summer bank holiday', 'ALL');
+INSERT INTO `holiday_table` (`holiday_table_id`, `holiday_date`, `week_day`, `holiday_name`, `Country_codes`) VALUES (7, '2016-12-26 00:00:00', 'Monday', 'Boxing Day', 'ALL');
+INSERT INTO `holiday_table` (`holiday_table_id`, `holiday_date`, `week_day`, `holiday_name`, `Country_codes`) VALUES (8, '2016-12-27 00:00:00', 'Tuesday', 'Christmas Day (substitute day)', 'ALL');
+INSERT INTO `holiday_table` (`holiday_table_id`, `holiday_date`, `week_day`, `holiday_name`, `Country_codes`) VALUES (9, '2016-01-01 00:00:00', 'Friday', 'New Year’s Day', 'SG');
+INSERT INTO `holiday_table` (`holiday_table_id`, `holiday_date`, `week_day`, `holiday_name`, `Country_codes`) VALUES (10, '2016-02-08 00:00:00', 'Monday', 'Chinese New Year', 'SG');
+INSERT INTO `holiday_table` (`holiday_table_id`, `holiday_date`, `week_day`, `holiday_name`, `Country_codes`) VALUES (11, '2016-02-09 00:00:00', 'Tuesday', 'Chinese New Year', 'SG');
+INSERT INTO `holiday_table` (`holiday_table_id`, `holiday_date`, `week_day`, `holiday_name`, `Country_codes`) VALUES (12, '2016-05-21 00:00:00', 'Saturday', 'Vesak Day', 'SG');
+INSERT INTO `holiday_table` (`holiday_table_id`, `holiday_date`, `week_day`, `holiday_name`, `Country_codes`) VALUES (13, '2016-07-06 00:00:00', 'Wednesday', 'Hari Raya Puasa', 'SG');
+INSERT INTO `holiday_table` (`holiday_table_id`, `holiday_date`, `week_day`, `holiday_name`, `Country_codes`) VALUES (14, '2016-08-09 00:00:00', 'Tuesday', 'National Day', 'SG');
+INSERT INTO `holiday_table` (`holiday_table_id`, `holiday_date`, `week_day`, `holiday_name`, `Country_codes`) VALUES (15, '2016-09-12 00:00:00', 'Monday', 'Hari Raya Haji', 'SG');
+INSERT INTO `holiday_table` (`holiday_table_id`, `holiday_date`, `week_day`, `holiday_name`, `Country_codes`) VALUES (16, '2016-10-29 00:00:00', 'Saturday', 'Deepavali', 'SG');
+INSERT INTO `holiday_table` (`holiday_table_id`, `holiday_date`, `week_day`, `holiday_name`, `Country_codes`) VALUES (17, '2016-01-01 00:00:00', 'Friday', 'New Year’s Day', 'IN');
+INSERT INTO `holiday_table` (`holiday_table_id`, `holiday_date`, `week_day`, `holiday_name`, `Country_codes`) VALUES (18, '2016-01-26 00:00:00', 'Tuesday', 'Republic Day', 'IN');
+INSERT INTO `holiday_table` (`holiday_table_id`, `holiday_date`, `week_day`, `holiday_name`, `Country_codes`) VALUES (19, '2016-07-06 00:00:00', 'Wednesday', 'Idul Fitr', 'IN');
+INSERT INTO `holiday_table` (`holiday_table_id`, `holiday_date`, `week_day`, `holiday_name`, `Country_codes`) VALUES (20, '2016-08-15 00:00:00', 'Monday', 'Independence Day', 'IN');
+INSERT INTO `holiday_table` (`holiday_table_id`, `holiday_date`, `week_day`, `holiday_name`, `Country_codes`) VALUES (21, '2016-10-11 00:00:00', 'Tuesday', 'Dussehra/Durga Puja', 'IN');
+INSERT INTO `holiday_table` (`holiday_table_id`, `holiday_date`, `week_day`, `holiday_name`, `Country_codes`) VALUES (22, '2016-10-31 00:00:00', 'Monday', 'Diwali Privilege Holiday/Gobardhan Puja', 'IN');
+
+

Function code and explanation

+
  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
+ 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
+ 52
+ 53
+ 54
+ 55
+ 56
+ 57
+ 58
+ 59
+ 60
+ 61
+ 62
+ 63
+ 64
+ 65
+ 66
+ 67
+ 68
+ 69
+ 70
+ 71
+ 72
+ 73
+ 74
+ 75
+ 76
+ 77
+ 78
+ 79
+ 80
+ 81
+ 82
+ 83
+ 84
+ 85
+ 86
+ 87
+ 88
+ 89
+ 90
+ 91
+ 92
+ 93
+ 94
+ 95
+ 96
+ 97
+ 98
+ 99
+100
+101
CREATE DEFINER=`root`@`localhost` FUNCTION `workday_time_diff_holiday_table`(
+`param_country` varchar(10), 
+`assigneddatetime` varchar(20), 
+`closeddatetime` varchar(20), 
+`starttime` varchar(20), 
+`endtime` varchar(20)
+)
+    RETURNS int(11)
+    LANGUAGE SQL
+    NOT DETERMINISTIC
+    CONTAINS SQL
+    SQL SECURITY DEFINER
+    COMMENT ''
+BEGIN 
+Set @starttime = starttime;
+Set @endtime = endtime;
+Select time_to_sec(timediff(@endtime,@starttime))/3600 into @maxhoursaday;
+Set @assigneddate = assigneddatetime; 
+Set @closeddate = closeddatetime; 
+Set @timecount = 0;
+Set @timevar1 = @assigneddate;
+Set @nextdate = @assigneddate;
+Set @timevar2 = null;
+Set @param_country = param_country;
+############# 
+/*Check if the assigned time was before the starttime 
+or closed time was after the endtime provided*/
+#############
+Set @checkstart = null;
+Set @checkend = null;
+Select CONCAT(SUBSTRING_INDEX(@assigneddate, ' ', 1), ' ',@starttime),
+CONCAT(SUBSTRING_INDEX(@closeddate, ' ', 1), ' ',@endtime)  into @checkstart, @checkend;
+
+if (@assigneddate > @checkstart) then
+        if (@closeddate<@checkend) then
+            Set @assigneddate = @assigneddate;
+            Set @closeddate = @closeddate;
+        else
+            Set @assigneddate = @assigneddate;
+            Set @closeddate = @checkend;
+        end if;
+    else
+        if (@closeddate<@checkend) then
+            SET @assigneddate = @checkstart;
+            Set @closeddate = @closeddate;
+        else
+            SET @assigneddate = @checkstart;
+            Set @closeddate = @checkend;
+        end if;
+    end if;
+
+/*After above check, the assigneddate and closeddate
+variables will be reset in accordance with the checks.*/
+
+SELECT DATEDIFF(@closeddate, @assigneddate) INTO @fixcount; -- check the difference between assigned date and closed date. 
+Set @count = @fixcount; -- allocate the difference between closed date and assigned date to a counter
+If @fixcount > 0 then --  true if line 57 resulted in more than 1 then run the while loop on next line
+    while @count>=0 do -- run the while loop until the count which is right now difference between closed and assigned becomes zero
+        select weekday(@nextdate) into @weekday; -- Assign the weekday value to @weekday. Weekday returns 0 for Monday, 2 for Tuesday ...5 for Saturday and 6 for Sunday
+
+/*Check if the date stored in nextdate 
+(which is assigneddate on first run of while loop and closeddate on last run) 
+is a holiday and set the holiday flag*/
+        Select sum(if(date_format(holiday_date,'%Y-%m-%d') = substring_index(@nextdate,' ',1),1,0)) 
+        from holiday_table 
+        where Country_codes = 'ALL' or instr(Country_codes,@param_country)>0
+        into @holidayflag; 
+        if ( @weekday<5 and @holidayflag=0) then #Proceed if the date in nextdate variable is neither weekend nor a holiday
+            if (@count = @fixcount) then #Check if it is first run.ie. if nextdate is assigneddate
+                Set @timevar1 = @assigneddate; #assign assigndate to variable timevar1
+                SELECT CONCAT(SUBSTRING_INDEX(@assigneddate, ' ', 1), ' ',@endtime) INTO @timevar2;#get site closing time on assigned date and store it on to timevar2
+            elseif (@count = 0) then #if the date in nextdate variable is closeddate then do the following otherwise proceed
+                Select concat(substring_index(@closeddate,' ',1),' ',@starttime) into @timevar1; ## 
+                Set @timevar2 = @closeddate;
+            else
+                Select concat(@nextdate,' ',@starttime) into @timevar1;
+                SELECT CONCAT(@nextdate, ' ', @endtime) INTO @timevar2;
+            end if;
+    SELECT 
+        LEAST(Greatest(((TIME_TO_SEC(TIMEDIFF(@timevar2, @timevar1))) / 3600),0),@maxhoursaday) 
+        INTO @timecounttemp;
+
+            Set @timecount = @timecounttemp + @timecount;
+        end if;
+        Set @timevar1 = @nextdate;
+        SELECT 
+        ADDDATE(SUBSTRING_INDEX(@timevar1, ' ', 1),1) 
+        INTO @nextdate;
+        Set @count = @count - 1;
+    end while;
+else 
+    #check if the assigned date / closed date is a holiday or weekend
+    select weekday(@assigneddate) into @weekday; ## Assign the weekday value to @weekday. Weekday returns o for Monday, 2 for Tuesday ...5 for Saturday and 6 for Sunday
+    Select sum(if(date_format(holiday_date,'%Y-%m-%d') = substring_index(@assigneddate,' ',1),1,0)) from holiday_table where Country_codes = 'ALL' or instr(Country_codes,@param_country)>0 into @holidayflag; #Check if the date stored in assigneddate is a holiday and set the holiday flag
+    if ( @weekday<5 and @holidayflag=0) then #Proceed if the date in assigneddate variable is neither weekend nor a holiday
+        SELECT Least(Greatest(((TIME_TO_SEC(TIMEDIFF(@closeddate, @assigneddate))) / 3600),0),@maxhoursaday) INTO @timecount;
+    else        Set @timecount = 0;
+    end if;
+end if;
+RETURN @timecount*60;
+END
+
+

Calling the function

+

Function will expect 5 parameters and with specific format as explained below:

+
    +
  • param_country - This is the country code as specified in holiday table
  • +
  • assigneddatetime - This must be provided in the format %Y-%m-%d %H-%i-%s. So for our example it will be 2016-06-10 12:00:00
  • +
  • closeddatetime - This must be provided in the format %Y-%m-%d %H-%i-%s. So for our example it will be 2016-06-14 14:00:00
  • +
  • starttime - This must be in the format %H:%i. So for our example it will be 09:00
  • +
  • endtime - This must be in the format %H:%i. So for our example it will be 16:00
  • +
+

The call for this function will be as below:

+
1
+2
+3
+4
+5
+6
+7
+8
-- To get number of minutes
+Select `WORKDAY_TIME_DIFF_HOLIDAY_TABLE`('UK','2016-06-10 12:00:00','2016-06-14 14:00:00','09:00','16:00');
+
+-- To get number of hours
+Select `WORKDAY_TIME_DIFF_HOLIDAY_TABLE`('UK','2016-06-10 12:00:00','2016-06-14 14:00:00','09:00','16:00')/60;
+
+-- To get in number of working days
+Select (`WORKDAY_TIME_DIFF_HOLIDAY_TABLE`('UK','2016-06-10 12:00:00','2016-06-14 14:00:00','09:00','16:00')/60)/(substring_index('16:00',':',1)-substring_index('09:00',':',1));
+
+

Complete Flowchart

+

The plantuml code for this can be checked by copying the image link and decoding it on Plantuml Online Server

+

+ + + + + + + + + + + + + + + + + +
+ Comments from Disqus +

As I plan to move away from disqus comment system, I am just including the screenshots of comments just so any information shared by or to the readers is retained.

+

+ Disqus Comments + Disqus Comments

+
+ + + + + +

+ + + + Comments +

+ + + + + + + +
+
+ + + + + +
+ + + +
+ + + +
+
+
+
+ + + + + + + + + + + + \ No newline at end of file diff --git a/2016/07/20/ddclient-on-fedora-2.html b/2016/07/20/ddclient-on-fedora-2.html new file mode 100644 index 0000000..715151e --- /dev/null +++ b/2016/07/20/ddclient-on-fedora-2.html @@ -0,0 +1,1817 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + DDCLIENT set-up on Fedora for Namecheap - Make Gadgets Work + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + Skip to content + + +
+
+ +
+ + + + +
+ + +
+ +
+ + + + + + + + + +
+
+ + + + + + + + + + + +
+ +
+ + + +
+
+
+
+ + +
+
+
+
+ + + + + + + + + + + + + + + + + + + + +

DDCLIENT set-up on Fedora for Namecheap

+ +

Configure Namecheap

+

Follow the Namecheap guide here

+
+

Tip

+

For a subdomain "oxygen.copper.com", just replace @ with "oxygen"

+
+ + +

Set-up DDCLIENT

+
1
+2
+3
+4
#Install DDCLIENT on Fedora
+sudo dnf install ddclient
+#Edit the configuration file to update IP on your Dynamic DNS host
+sudo nano /etc/ddclient/ddclient.conf
+
+

Press Ctrl+W and type the name of host for your Dynamic DNS. Mine is with Namecheap and I needed to configure for the subdomain hence following config reflects how to do it for Namecheap guidance. For other hosts, you will need to refer their documentation.

+
1
+2
+3
+4
+5
+6
+7
+8
### NameCheap (namecheap.com)
+use=web, web=dynamicdns.park-your-domain.com/getip
+protocol=namecheap
+server=dynamicdns.park-your-domain.com
+login=copper.com
+password= your ddns password # (1)
+oxygen
+## myhost.namecheap.com
+
+
    +
  1. Copy the password from namecheap advanced DNS section
  2. +
+
+

image

+

The password to be provided above is what you will find on namecheap dashboard (Ref. Screenshot above).

+
    +
  • Log in to the namecheap account.
  • +
  • Go to Advanced DNS
  • +
  • Scroll down to Dynamic DNS section
  • +
  • Copy the password
  • +
  • Paste in ddclient config file
  • +
+
+

Test DDCLIENT

+

Before we schedule ddclient to run at boot, we need to test if it has been configured properly and is able to communicate with Namecheap by sudo ddclient -daemon=0 -debug -verbose -noquiet. If it is configured properly, you will see a message similar to this as part of the final output.

+
SUCCESS: updating oxygen: good: IP address set to 92.117.273.56
+
+
+

Important

+

If it is not what you see, and more importantly, if you do not see last line as "Success", then there is something wrong with configuration and you must correct it before proceeding.

+
+

If this test worked, we are ready to update the DDCLIENT service.

+

Set up DDCLIENT to run at start-up

+

When we install ddclient using dnf, a ddclient.service file is automatically created in the location /etc/systemd/system/ddclient.service with following content.

+
/etc/systemd/system/ddclient.service
 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
[Unit]
+Description=A Perl Client Used To Update Dynamic DNS
+After=syslog.target network.target nss-lookup.target
+
+[Service]
+User=ddclient
+Group=ddclient
+Type=forking
+PIDFile=/var/run/ddclient/ddclient.pid
+EnvironmentFile=-/etc/sysconfig/ddclient
+ExecStartPre=/bin/touch /var/cache/ddclient/ddclient.cache
+ExecStart=/usr/sbin/ddclient $DDCLIENT_OPTIONS
+
+[Install]
+WantedBy=multi-user.target
+
+

We will enable and start this service by issuing following commands:

+
1
+2
sudo systemctl enable ddclient.service
+sudo systemctl start ddclient.service
+
+

One would think that enabling and starting this service is all you need to do but that is not usually the case. I was getting following error:

+
+

Error

+

/bin/touch: cannot touch `/var/cache/ddclient/ddclient.cache': Permission denied

+
+

So if sudo systemctl start ddclient results in above error, you may need to do the following:

+
 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
#Go Root
+su
+#Create a directory for ddclient
+mkdir /var/run/ddclient
+#Chown the various directories for ddclient as user
+chown ddclient:ddclient /etc/ddclient.conf
+chown ddclient:ddclient /var/run/ddclient/
+#change directory
+cd /var/run/ddclient
+#delete ddclient.cache if it exists
+rm ddclient.cache
+#change directory
+cd /etc/sysconfig
+#delete ddclients.cache
+rm ddclients.cache
+#create a blank ddclient.cache
+nano /var/run/ddclient/ddclient.cache
+#chown it for ddclient user
+chown ddclient:ddclient /var/run/ddclient/ddclient.cache
+#exit root
+exit
+#enable and start ddclient service
+sudo systemctl enable ddclient.service
+sudo systemctl start ddclient.service
+
+

Done. 🥳

+

Known Issue with DDCLIENT

+

There is a known issue and I can confirm that I have seen on my logfile as recently as today.

+
+

Error

+

WARNING: cannot connect to dynamicdns.park-your-domain.com:80 socket: IO::Socket::INET: Bad hostname 'dynamicdns.park-your-domain.com'

+
+

It isn't major but it is there and restarting the service by issuing the command sudo systemctl restart ddclient.service fixes the problem.

+ + + + + + + + + + + + + + + + + +
+ Comments from Disqus +

As I plan to move away from disqus comment system, I am just including the screenshots of comments just so any information shared by or to the readers is retained.

+

+ Disqus Comments + Disqus Comments

+
+ + + + + + +
+
+ + + + + +
+ + + +
+ + + +
+
+
+
+ + + + + + + + + + + + \ No newline at end of file diff --git a/2016/07/25/tomcat-on-fedora-behind-nginx.html b/2016/07/25/tomcat-on-fedora-behind-nginx.html new file mode 100644 index 0000000..61d824d --- /dev/null +++ b/2016/07/25/tomcat-on-fedora-behind-nginx.html @@ -0,0 +1,1912 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + Tomcat 8.5.4 on Fedora behind Nginx - Make Gadgets Work + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + Skip to content + + +
+
+ +
+ + + + +
+ + +
+ +
+ + + + + + + + + +
+
+ + + + + + + + + + + + + + + +
+
+
+
+ + +
+
+
+
+ + + + + + + + + + + + + + + + + + + + +

Tomcat 8.5.4 on Fedora behind Nginx

+ +

Install Oracle Java

+
1
+2
+3
+4
+5
+6
#install jdk
+wget --no-cookies --no-check-certificate --header "Cookie: gpw_e24=http%3A%2F%2Fwww.oracle.com%2F; oraclelicense=accept-securebackup-cookie" "http://download.oracle.com/otn-pub/java/jdk/8u102-b14/jdk-8u102-linux-i586.rpm"
+#install jre
+wget --no-cookies --no-check-certificate --header "Cookie: gpw_e24=http://www.oracle.com/ oraclelicense=accept-securebackup-cookie" "http://download.oracle.com/otn-pub/java/jdk/8u102-b14/jre-8u102-linux-i586.rpm"
+#enable firefox plugin
+alternatives --install /usr/lib/mozilla/plugins/libjavaplugin.so libjavaplugin.so /usr/java/jdk1.8.0_102/jre/lib/i386/libnpjp2.so 20000
+
+
+

Tip

+

URL for JDK and JRE is best obtained directly from oracle website

+
+ + +

Download Tomcat

+
1
+2
+3
+4
su
+mkdir /opt/tomcat/ && cd /opt/tomcat
+wget http://mirror.ox.ac.uk/sites/rsync.apache.org/tomcat/tomcat-8/v8.5.4/bin/apache-tomcat-8.5.4.zip
+wget https://www.apache.org/dist/tomcat/tomcat-8/v8.5.4/bin/apache-tomcat-8.5.4.zip.md5
+
+

Check MD5

+
1
+2
+3
cat apache-tomcat-8.5.4.zip.md5
+md5sum apache-tomcat-8.5.4.zip
+unzip apache-tomcat-8.5.4.zip
+
+

Create a TOMCAT Group and User then grant access

+
1
+2
+3
+4
+5
+6
+7
groupadd tomcat
+useradd -M -s /bin/nologin -g tomcat -d /opt/tomcat tomcat
+cd /opt/tomcat
+chgrp -R tomcat conf
+chmod g+rwx conf
+chmod g+r conf/*
+chown -R tomcat bin/ webapps/ work/ temp/ logs/
+
+

Create Service for Tomcat

+
nano /etc/systemd/system/tomcat.service
+
+
/etc/systemd/system/tomcat.service
 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
## Systemd unit file for tomcat
+[Unit]
+Description=Apache Tomcat Web Application Container
+After=syslog.target network.target
+
+[Service]
+Type=forking
+
+ExecStart=/opt/tomcat/apache-tomcat-8.5.4/bin/startup.sh
+ExecStop=/opt/tomcat/apache-tomcat-8.5.4/bin/shutdown.sh
+
+User=tomcat
+Group=tomcat
+
+[Install]
+WantedBy=multi-user.target
+
+
1
+2
systemctl start tomcat.service
+systemctl enable tomcat.service
+
+

Alternative start and stop

+
1
+2
+3
+4
+5
+6
cd apache-tomcat-8.5.4/bin
+chmod 700 /opt/tomcat/apache-tomcat-8.5.4/bin/*.sh
+ln -s /opt/tomcat/apache-tomcat-8.5.4/bin/startup.sh /usr/bin/tomcatup
+ln -s /opt/tomcat/apache-tomcat-8.5.4/bin/shutdown.sh /usr/bin/tomcatdown
+tomcatup
+tomcatdown
+
+

Change port

+
nano /opt/tomcat/apache-tomcat-8.5.4/conf/server.xml
+
+

Around line 69 is the connector tag where the port=8080 is specified. For this example lets change it to 8081. After change the connector tag in server.xml will look as below:

+
/opt/tomcat/apache-tomcat-8.5.4/conf/server.xml
1
+2
+3
<Connector port="8081" protocol="HTTP/1.1"
+               connectionTimeout="20000"
+               redirectPort="8443" >;
+
+

Add tomcat Users

+

Open tomcat-users.xml and add new users before tag

+

nano /opt/tomcat/apache-tomcat-8.5.4/conf/tomcat-users.xml
+
+sample user: +
/opt/tomcat/apache-tomcat-8.5.4/conf/tomcat-users.xml
1
+2
+3
+4
<role rolename="admin-gui"/><br>
+<user username="admin" password="some admin password" roles="admin-gui"/><br>
+<role rolename="manager-gui"/><br>
+<user username="jhondoe" password="some password" roles="manager-gui"/>
+

+

Test

+
1
+2
+3
+4
touch /opt/tomcat/apache-tomcat-8.5.4/webapps/ROOT/testankit.jsp
+nano /opt/tomcat/apache-tomcat-8.5.4/webapps/ROOT/testankit.jsp
+#restart tomcat
+systemctl restart tomcat.service
+
+

Open the browser and enter http://localhost:8080 (or whatever port you have configured Tomcat on.)

+

Configure Nginx Reverse Proxy for Tomcat

+
    +
  • +

    Configure the dynamic DNS. Steps will be as per my previous post. For purpose of this step I will be assuming you created a DDNS named tomcat.yoursite.com

    +
  • +
  • +

    Update /etc/hosts to include tomcat.yoursite.com

    +
  • +
+
/etc/hosts
1
+2
+3
sudo nano /etc/hosts
+#Make an entry in your hosts
+127.0.0.1  localhost.localdomain localhost your.seafile.com your.blog.com tomcat.yoursite.com
+
+
    +
  • Now create nginx conf file using sudo nano /etc/nginx/conf.d/tomcat.conf as shown below:
  • +
+
/etc/nginx/conf.d/tomcat.conf
 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
upstream tomcat {
+server 127.0.0.1:8081;
+}
+
+server {
+      listen      80;
+      server_name tomcat.yoursite.com;
+      access_log  /var/log/nginx/tomcat.access.log;
+      error_log   /var/log/nginx/tomcat.error.log;
+      proxy_buffers 16 64k;
+      proxy_buffer_size 128k;
+
+        location / {
+          proxy_pass  http://tomcat;
+          proxy_next_upstream error timeout invalid_header http_500 http_502 http_503 http_504;
+          proxy_redirect off;
+          proxy_set_header    Host            $host;
+          proxy_set_header    X-Real-IP       $remote_addr;
+          proxy_set_header    X-Forwarded-For $proxy_add_x_forwarded_for;
+          proxy_set_header    X-Forwarded-Proto https;
+      }
+  }
+
+
    +
  • Finally reload and restart services
  • +
+
1
+2
+3
sudo systemctl daemon-reload
+sudo systemctl start nginx.service
+sudo systemctl start tomcat.service
+
+ + + + + + + + + + + + + + + + + +
+ Comments from Disqus +

As I plan to move away from disqus comment system, I am just including the screenshots of comments just so any information shared by or to the readers is retained.

+

+ Disqus Comments + Disqus Comments

+
+ + + + + + +
+
+ + + + + +
+ + + +
+ + + +
+
+
+
+ + + + + + + + + + + + \ No newline at end of file diff --git a/2016/07/28/update-ghost-on-fedora.html b/2016/07/28/update-ghost-on-fedora.html new file mode 100644 index 0000000..9963bd7 --- /dev/null +++ b/2016/07/28/update-ghost-on-fedora.html @@ -0,0 +1,1703 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + Update Ghost on Fedora - Make Gadgets Work + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+
+ +
+ + + + +
+ + +
+ +
+ + + + + + + + + +
+
+ + + + + + + + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+
+ + +
+
+
+
+ + + + + + + + + + + + + + + + + + + + +

Update Ghost on Fedora

+ +

While the guidance on Ghost website is very clear, I did get issues that required steps in troubleshooting. Something to do with lodash and npm version 2 stuff (node_modules/knex requires lodash@'^3.7.0') that I read on one of the forums specifically the comment from ErisDS on 13/06.

+ + +

Anyway, reading this I deleted node_modules followed by npm install and it worked. All commands in order as I did are listed below. If my previous posts were used to create the blog nothing here will require sudo or root privileges. +As before all this was done on Fedora 24 Linux OS and following commands will need to be changed where it mentions yoursite and username. If the path is different then obviously entire path needs to be replaced.

+
 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
+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
#Copy the entire site as backup. It will be a verbose copy an all access rights will be preserved. 
+cp -avr /var/www/html/yoursite /home/<username>/
+
+#Now in the site directory create a directory ghostlatest
+mkdir /var/www/html/yoursite/ghostlatest
+
+#change directory to ghostlatest
+cd /var/www/html/yoursite/ghostlatest
+
+#now download the latest ghost zip file
+curl -LOk https://ghost.org/zip/ghost-latest.zip
+
+#unzip the downloaded file
+unzip ghost-latest.zip
+
+#Stop your Ghost instance (assuming Ghost is the alias
+#created as per my previous post else replace with 
+#whatever alias was used with pm2). 
+pm2 stop Ghost
+
+#Change directory and delete old folders and files
+cd /var/www/html/yoursite
+rm -rf core
+rm -rf index.js
+rm -rf *.md
+rm -rf *.json
+rm -rf /var/www/html/yoursite/content/themes/casper
+
+#Remove node_modules because anyway the lodash issue will hit later on.
+rm -rf node_modules
+
+#Copy from ghost latest to site directory new folders
+cp -avr /var/www/html/yoursite/ghostlatest/core /var/www/html/yoursite
+cp -avr /var/www/html/yoursite/ghostlatest/index.js /var/www/html/yoursite
+cp -avr /var/www/html/yoursite/ghostlatest/*.md /var/www/html/yoursite
+cp -avr /var/www/html/yoursite/ghostlatest/*.json /var/www/html/yoursite
+
+#Optional if you haven't made customisation to default theme.
+cp -avr /var/www/html/yoursite/ghostlatest/content/themes/casper /var/www/html/yoursite/content/themes
+
+#Install Latest Version
+npm cache clean
+npm update
+npm install --production
+
+#Start to update dependencies
+npm start --production
+
+#Once above command is complete, stop the server and restart using pm2
+Ctrl+C
+pm2 start Ghost
+
+ + + + + + + + + + + + + + + + + + + + + + +
+
+ + + + + +
+ + + +
+ + + +
+
+
+
+ + + + + + + + + + + + \ No newline at end of file diff --git a/2016/07/31/fix-for-php-issues-after-upgrade-to-ubuntu-16-04-1-xenial.html b/2016/07/31/fix-for-php-issues-after-upgrade-to-ubuntu-16-04-1-xenial.html new file mode 100644 index 0000000..c77650f --- /dev/null +++ b/2016/07/31/fix-for-php-issues-after-upgrade-to-ubuntu-16-04-1-xenial.html @@ -0,0 +1,1676 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + Fix for PHP Issues after upgrade to Ubuntu 16.04.1 (Xenial) - Make Gadgets Work + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+
+ +
+ + + + +
+ + +
+ +
+ + + + + + + + + +
+
+ + + + + + + + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+
+ + +
+
+
+
+ + + + + + + + + + + + + + + + + + + + +

Fix for PHP Issues after upgrade to Ubuntu 16.04.1 (Xenial)

+ +

After updating from Ubuntu 14.04, the php and Apache stopped being friends and one of the WordPress site I maintain went all white and admin page was just showing php code. +This is apparently because of a known issue in 16.04 with upgrade to php7 as shown on the ubuntu forum here.

+

Using the guidance from this link and with some more of duckduckgo search later, I managed to resolve the problem thus:

+ + +
 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
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
#1. Install aptitude if it is not already installed using 
+sudo apt-get install aptitude
+
+#2. Removed php7 and unwanted php using
+sudo aptitude purge `dpkg -l | grep php| awk '{print $2}' |tr '\n' ' '`
+
+#3. Added old repo using
+sudo add-apt-repository ppa:ondrej/php
+
+#4. Updated repo
+sudo apt-get update
+
+#5. Installed php5.6
+sudo apt-get install php5.6
+sudo apt-get install php5.6-mbstring php5.6-mcrypt php5.6-mysql php5.6-xml php5.6-curl php5.6-gd php5.6-zip
+
+#6. Checked php version
+sudo php -v
+
+#7. Enabled mod_php
+sudo a2enmod php5
+Ignored error message
+
+#8. Opened php5.6 conf
+sudo nano /etc/apache2/mods-enabled/php5.6.conf
+
+#9. Commented following lines
+<IfModule mod_userdir.c>
+   <Directory /home/*/public_html>
+      php_admin_flag engine Off
+    </Directory>
+</IfModule>
+
+#10. Restarted the server
+sudo service apache2 restart
+
+ + + + + + + + + + + + + + + + + +
+ Comments from Disqus +

As I plan to move away from disqus comment system, I am just including the screenshots of comments just so any information shared by or to the readers is retained.

+

+ Disqus Comments + Disqus Comments

+
+ + + + + + +
+
+ + + + + +
+ + + +
+ + + +
+
+
+
+ + + + + + + + + + + + \ No newline at end of file diff --git a/2016/08/12/ethercalc.html b/2016/08/12/ethercalc.html new file mode 100644 index 0000000..ce9c45c --- /dev/null +++ b/2016/08/12/ethercalc.html @@ -0,0 +1,1662 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + Ethercalc - Make Gadgets Work + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+
+ +
+ + + + +
+ + +
+ +
+ + + + + + + + + +
+
+ + + + + + + + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+
+ + +
+
+
+
+ + + + + + + + + + + + + + + + + + + + +

Ethercalc

+ +

Ethercalc is good tool which can be selfhosted. It is fairly simple to do so. Though it will be available for anyone who has the URL because there is no inbuilt login mechanism.

+ + +

I did not dig into making it accessible with a login interface as I lost interest after I made it work on my server and played around a bit with it but it was simply because I got interested in other things and not because the tool isn't fascinating enough. I am fairly certain this will not be overly complicated but for a simple selfhosted spreadsheet solution this is definitely worth playing around with.

+

The steps I took are as below:

+
 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
+26
+27
+28
+29
+30
+31
+32
#Ethercalc plays well wth redis as per their documentation. So Install and start 'redis'
+sudo dnf install redis
+sudo systemctl start redis.service
+
+#Test if 'redis' is working
+redis-cli ping
+
+#Enable redis to automatically start at the time of system start-up
+sudo systemctl enable redis.service
+
+#check if it runs
+ethercalc
+#Press Ctrl+C to exit
+
+#To run it forever use pm2
+pm2 start ethercalc
+npm list -g --depth=0
+
+#Change port to whichever port you want Ethercalc to run on by opening app.js 
+#and changing port.
+nano /home/&lt;yourusername&gt;/.npm-global/lib/node_modules/ethercalc/app.js
+#change port and save
+
+#run with pm2 and alias as Ecalc
+pm2 start ethercalc  --name 'Ecalc'
+
+#check logs using pm2
+pm2 logs Ecalc
+
+#Reverse proxy Ethercalc using nginx
+sudo nano /etc/nginx/conf.d/ecalc.conf
+sudo systemctl restart nginx.service
+
+ + + + + + + + + + + + + + + + + + + + + + +
+
+ + + + + +
+ + + +
+ + + +
+
+
+
+ + + + + + + + + + + + \ No newline at end of file diff --git a/2016/09/05/the-pain-of-using-windows.html b/2016/09/05/the-pain-of-using-windows.html new file mode 100644 index 0000000..6423423 --- /dev/null +++ b/2016/09/05/the-pain-of-using-windows.html @@ -0,0 +1,1613 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + Windows 10 - a bucket load of pain - Make Gadgets Work + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+
+ +
+ + + + +
+ + +
+ +
+ + + + + + + + + +
+
+ + + + + + + + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+
+ + +
+
+
+
+ + + + + + + + + + + + + + + + + + + + +

Windows 10 - a bucket load of pain

+ +
+

As Windows 10 is a commercial offering, one would think it will be working as expected and it does so long as like me one has come to expect pain from Microsoft in general and Windows in particular - because pain is what you get when you use Windows 10.

+
+ + +

When we work in office environment we are not as heavily exposed to the pain that accompanies Windows usage, mainly because everything is configured and dealt with through a central desktop provisioning team and there is usually a dedicated IT Help-desk in a big corporate.

+

However, using Windows at home is not as smooth a sailing (I am really being liberal with usage of word smooth) and if you don't trust me just try searching for issues that accompany upgrade to windows 10 and look at the range of issues.

+

Now I, ofcourse, don't blindly believe these search based opinions and have my own take based on my very own personal trauma. The story goes like so:

+
+

Why - one may ask - do I even bother with windows at home if it is so painful? This is because as much as my daughter loves the penguin, the windows is what her teacher uses and so she does need to be aware of the necessary evil.

+
+

So, there is this laptop I have which is exclusively for Windows and is only ever used by my daughter for her schoolwork. It isn't some cheap stuff but a state of the art touch screen detachable monitor kinda laptop from HP with intel core i5 inside.

+

However, as it is a detachable tablet, it has two HDD - 50 GB for monitor and 500 GB on keyboard that acts as extended memory.

+

Now, it originally came with Windows 8 and then came Windows 10 followed by Windows 10 anniversary edition. When I upgraded from Windows 8 to Windows 10, I suddenly found that the whole of 50 gig was fully utilised making it impossible to do anything at all.

+

Now a quick duckduckgo search later it was as simple as removing the 'previous installs' which windows itself removes after a month or so they say.

+

In my case the self-removal-a-month-later thing just did not happen and disk clean did not help either so I was basically left with no choice but to reformat the whole damn thing.

+

This as I recollected from my old days with windows is quite a long process with lots of downloads and waits...and boy has that continued to be the same... Microsoft is nothing if not consistent. Finally after several hours of ordeal, the system was refreshed and my storage space reclaimed and a semi-functional laptop was there or thereabouts.

+

I say semi-functional because unlike on Linux not all drivers are there and you have to individually download the drivers from HP website, install and then keep your fingers and toes crossed while you hope it works...finally sound was sorted but WiFi is still flaky.

+

Now compare this to the seamless upgrade from one major version of Fedora 23 to Fedora 24...it did take a while to download and install but the actual effort involved was typing 4 lines on the terminal - that's it. When I logged into Fedora 24 upgraded machine, everything just worked as before - no driver issues, no WiFi problems, nothing.

+

RANT OVER

+ + + + + + + + + + + + + + + + + + + + + + +
+
+ + + + + +
+ + + +
+ + + +
+
+
+
+ + + + + + + + + + + + \ No newline at end of file diff --git a/2016/10/04/grav-cms-with-a-difference.html b/2016/10/04/grav-cms-with-a-difference.html new file mode 100644 index 0000000..1f243d9 --- /dev/null +++ b/2016/10/04/grav-cms-with-a-difference.html @@ -0,0 +1,1670 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + Grav - CMS with a difference - Make Gadgets Work + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+
+ +
+ + + + +
+ + +
+ +
+ + + + + + + + + +
+
+ + + + + + + + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+
+ + +
+
+
+
+ + + + + + + + + + + + + + + + + + + + +

Grav - CMS with a difference

+ +

While I love Ghost as a blogging platform, it is not best placed for things other than blogs - after all that is the basic idea behind creation of this wonderful tool.

+

As I wanted to host a static website using tools that don't require a database or rely on php, I went searching on interwebs. +I came across a lot of options and the most popular one appears to be Jekyll and it's variants (Nikola and such) but they require a lot of terminal activity which won't go well for regular end user responsible for maintaining content of the static website in question.So I continued looking and came across this wonderful project called Grav.

+

Grav is super fast, very pretty and extremely easy to deploy and maintain. Additionally, it has very good documentation.

+

The key features that I absolutely loved are as below:

+ + +
    +
  1. Easy installation - Easier than Ghost / Wordpress IMHO
  2. +
  3. Good help and documentation
  4. +
  5. Responsive themes and skeletons
  6. +
  7. Built-in Markdown Support and then some
  8. +
  9. Lot of useful plugins
  10. +
  11. Browser based admin page
  12. +
  13. Active development
  14. +
  15. Easy upgrade and back-up
  16. +
+

My steps for installation:

+
    +
  • Download a skeleton you fancy
  • +
  • Unzip the downloaded file into your server root and move it into a folder named grav:
  • +
+
1
+2
+3
sudo unzip grav-skeleton-appi-1.0.0.zip -d /var/www/html/
+cd /var/www/html/
+sudo mv grav-skeleton-appi-1.0.0/ grav/
+
+
    +
  • Fix permissions (replace username with your user on the server. This is important to ensure files can be modified both from browser as well as from terminal. So create a bash file by issuing command nano fixpermissions.sh and paste the following code in there.
  • +
+
1
+2
+3
+4
+5
+6
+7
+8
##!/bin/sh
+chown &lt;username&gt;:www-data .
+chown -R &lt;username&gt;:www-data *
+find . -type f | xargs chmod 664
+find ./bin -type f | xargs chmod 775
+find . -type d | xargs chmod 775
+find . -type d | xargs chmod +s
+umask 0002
+
+
    +
  • +

    Now make this bash file executable using chmod a+x fixpermissions.sh

    +
  • +
  • +

    Finally run the bash file from within the grav directory:

    +
  • +
+
1
+2
cd /var/www/html/grav/
+sudo bash /<path_to_fixpermissions.sh>/fixpermissions.sh
+
+
    +
  • +

    Open &lt;server-ip-address&gt;/grav from the browser and check your install is working

    +
  • +
  • +

    Once it is confirmed to be working, install admin plugin from terminal like so:

    +
  • +
+
/bin/gpm install admin
+
+
    +
  • Open <server-ip-address>/grav/admin from the browser and create your admin login details
  • +
+

Working on Grav is an absolutely pleasing experience and the swift turnaround for a static website is phenomenal. Suffice to say, I hope that this project goes from strength to strength.

+ + + + + + + + + + + + + + + + + + + + + + +
+
+ + + + + +
+ + + +
+ + + +
+
+
+
+ + + + + + + + + + + + \ No newline at end of file diff --git a/2016/11/23/note-7-to-oneplus-3.html b/2016/11/23/note-7-to-oneplus-3.html new file mode 100644 index 0000000..6bc6fc9 --- /dev/null +++ b/2016/11/23/note-7-to-oneplus-3.html @@ -0,0 +1,1647 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + Note 7 to OnePlus 3 - Make Gadgets Work + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+
+ +
+ + + + +
+ + +
+ +
+ + + + + + + + + +
+
+ + + + + + + + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+
+ + +
+
+
+
+ + + + + + + + + + + + + + + + + + + + +

Note 7 to OnePlus 3

+ +

I was a super excited owner of Note 7 in September this year and then in few +days the happiness started disappearing as the news of exploding Note 7 started +appearing all over the internet.

+

Then came the notice to exchange the Note 7 which I dutifully did and assured +that nothing will now go wrong I started enjoying that gem of a device. Alas, it +was not to be and the device was recalled for good. Being a user of Note devices +for so long and yet unable to continue with my old Note 3, I decided to go for +some interim device - you know until Samsung decides to get the act right and +bring the next Note device to the market.

+ + +

After going through a number of possible options ranging from Note 7's close +cousin Galaxy S7 edge+ all the way to newly released Pixel, I actually settled +for OnePlus 3. It was not a gamble actually as my wife is already using it and +is quite pleased with it's performance and the price is just right for this to +qualify as the interim phone.

+

Anyway, so I ordered and waited because there was a good 4 week wait period. The +ordering and delivery all went as planned while in the meantime I was back with +my trusty Note 3.

+

OnePlus 3 arrived and I set it up to my taste and my oh my what an experience. +Apart from Stylus it was lacking nothing and is very smooth, very light and +camera quality is very good as well - not as good as Note 7 but definitely +better than Note 3. I have now been with this device for just over a week and +although I miss the S-Pen, I am very happy with the device in general and it's +battery life in particular.

+

I have listed below the things that I like and the things I hope it had:

+

Features I loved ❤ :

+
    +
  1. Battery life
  2. +
  3. Superfast Charging with Dash Charger
  4. +
  5. Display and inbuilt Night mode
  6. +
  7. Ringer Switch
  8. +
  9. Inbuilt Colour control for LED Notification
  10. +
  11. Smooth minimal interface
  12. +
  13. No bloatware
  14. +
  15. Fingerprint Scanner doubles as Home button
  16. +
  17. Gesture support for flash light
  18. +
  19. Ability to swap Capacitive button functionality
  20. +
  21. Dual Sim Card capability - Allows me to carry one phone for both work and personal numbers.
  22. +
+

Sure all of the above can be pretty similar on all high end handsets but what +makes it unique for OnePlus 3 is the price point of £329. That price is +unbeatable and the phone, in my humble opinion, must be the first choice for +anyone who isn't fussed about a stylus.

+

There can be a comparison between camera results where Samsung outperforms in my +experience but only slightly and still the photo quality from OnePlus 3 is +definitely very good second only to Samsung and no other.

+

Wishlist 👩‍🎄 :

+
    +
  1. Functional Stylus (Frankly no other manufacturer is tapping on this market. God knows why?)
  2. +
  3. Note 7 had a good integration of Fingerprint Scanner with browser to login to various sites. I miss that on OnePlus 3.
  4. +
  5. External Memory Card slot.
  6. +
+

Here's hoping that OnePlus 3 team will listen to my wishlist 😄.

+ + + + + + + + + + + + + + + + + + + + + + +
+
+ + + + + +
+ + + +
+ + + +
+
+
+
+ + + + + + + + + + + + \ No newline at end of file diff --git a/2016/12/14/swap-file-to-create-extra-memory.html b/2016/12/14/swap-file-to-create-extra-memory.html new file mode 100644 index 0000000..fdb4aa0 --- /dev/null +++ b/2016/12/14/swap-file-to-create-extra-memory.html @@ -0,0 +1,1643 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + Swap File to create extra memory - Make Gadgets Work + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+
+ +
+ + + + +
+ + +
+ +
+ + + + + + + + + +
+
+ + + + + + + + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+
+ + +
+
+
+
+ + + + + + + + + + + + + + + + + + + + +

Swap File to create extra memory

+ +

While renewing my LetsEncrypt certificate, I found myself in a strange situation where the certbot won't run asking me to update pip and then each time I tried updating pip it failed with the error error: command 'x86_64-linux-gnu-gcc' failed with exit status 4.

+

It turns out that this happens due to low memory and with my digitalocean droplet being the cheapest one this was bound to happen sooner rather than later. Fortunately there is a way around it as explained below.

+

Use of following commands will ensure that the swap file is created which in turn will help avoid the 'error: command 'x86_64-linux-gnu-gcc' failed with exit status 4'.

+

Following commands will create a swap file:

+ + +

1
+2
+3
+4
+5
sudo dd if=/dev/zero of=/swapfile1 bs=1024 count=524288
+sudo mkswap /swapfile1
+sudo chown root:root /swapfile1
+sudo chmod 0600 /swapfile1
+sudo swapon /swapfile1
+
+The swap file will now be activated but will be gone after the reboot. It can be reactivated using the last command (I hope so know as I have now tried it).

+

Anyway, after creating the swapfile, you will be able to upgrade pip without the aforementioned error. 😄

+
+

Update: 02/03/2017

+

I ran into memory issues yet again and I thought instead of increasing the memory for swapfile1, what if I can create another swapfile. I tried this and it works. Infact I felt quite nice uncovering a concept of multiple swapfiles purely based on my whim 😉. All I really had to do was repeat above code replacing swapfile1 with swapfile2 and I had two swapfiles working together increasing available memory for my server.

+
+
1
+2
+3
+4
+5
sudo dd if=/dev/zero of=/swapfile2 bs=1024 count=524288
+sudo mkswap /swapfile2
+sudo chown root:root /swapfile2
+sudo chmod 0600 /swapfile2
+sudo swapon /swapfile2
+
+

Thing is after it worked I was a bit intrigued by the concept and read a bit more on swapon / swapoff and few useful commands are listed below:

+
1
+2
+3
+4
+5
+6
+7
+8
## To enable all swapfiles 
+swapon -a
+## To disable all swapfiles
+swapoff -a
+## To see all available swapfiles 
+swapon -s
+## To enable a particular swapfile
+swapon <filename>
+
+ + + + + + + + + + + + + + + + + + + + + + +
+
+ + + + + +
+ + + +
+ + + +
+
+
+
+ + + + + + + + + + + + \ No newline at end of file diff --git a/2017/02/02/dd-wrt-firmware-on-tp-link-tl-wr841n-v11.html b/2017/02/02/dd-wrt-firmware-on-tp-link-tl-wr841n-v11.html new file mode 100644 index 0000000..9979801 --- /dev/null +++ b/2017/02/02/dd-wrt-firmware-on-tp-link-tl-wr841n-v11.html @@ -0,0 +1,1807 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + DD-WRT firmware on TP-LINK TL-WR841N v11 - Make Gadgets Work + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + Skip to content + + +
+
+ +
+ + + + +
+ + +
+ +
+ + + + + + + + + +
+
+ + + + + + + + + + + + + + + +
+
+
+
+ + +
+
+
+
+ + + + + + + + + + + + + + + + + + + + +

DD-WRT firmware on TP-LINK TL-WR841N v11

+ +

Background

+

I have been with PlusNet for over two years now and am a happy camper as far as fiber optic broadband is concerned but as I am no longer on a broadband contract with PlusNet and had no intention of going on one, so the only way I could get a change to my ageing router was by purchasing a new one.

+

Hence I started reading about my options and soon enough realised that an old router can be given new lease of life using DD-WRT. Equally soon-ish I also realised that the router from PlusNet - TG582n - is quite rubbish and does not play nice with any of the open source firmwares.

+

So I figured that if I have to just play around a bit, I might as well start with something cheaper and cheaper is what I found in the TP-Link router TL-WR841n at just £16.00. You can't get any cheaper than that in my opinion. OK, so now that we have established that I am cheap and my new router is cheap, let's move on to interesting stuff.

+

I had read that TP-Link router and specifically TL-WR841n plays nicely with DD-WRT but it was only after I had my new toy did I realise that these things also come in hardware version and while interwebs is filled with instructions on installing DD-WRT for upto v9, when it comes to v11 in Europe, it can be a bit tricky to proceed. There are some instructions in forums1 but nothing that walks one end to end hence this post.

+ + +

Flash router with DD-WRT firmware

+

There are two ways to ensure that the DD-WRT firmware gets flashed on the router:

+
    +
  1. +

    Apply Unlocked stock firmware - This is the one I used and to use this I downloaded the modified firmware from NeDark that he has provided in a post on the OpenWRT forum here

    +
  2. +
  3. +

    Using TFTP server - This is considered to be a safe approach because you do not have to use any modded version of stock firmware to apply it. However, if like me, you are anyway going to flash it with DD-WRT, I feel it's a bit of a pain that can be avoided as it involves setting up TFTP server and making your router to connect to this server can take some time and effort. It is explained here

    +
  4. +
+

Right, so assuming you want to go with the first and easier approach, first you need to download the modded firmware from NeDark. He has uploaded it on his dropbox link and I have also uploaded a copy of this firmware here.

+

I recommend that you download all the three files from my folder on Mediafire2 using the link below but if you would much rather download directly from DD-WRT, then the links for rest of the two files that I used are also in footnote references34.

+

Assuming that now you have all the three files safely downloaded to you computer we just follow the simple steps below:

+
    +
  1. Connect the power supply to the TP-Link router and switch it on.
  2. +
  3. Connect the ethernet cable in one of the yellow LAN slots of the router.
  4. +
  5. Switch-off the wifi on your computer and connect the other end of the ethernet cable to the ethernet port of your computer.
  6. +
  7. Once the ethernet connection on your laptop is established, open a browser and type 192.168.0.1 and press enter.
  8. +
  9. You will be presented with TP-LINK admin interface.
  10. +
  11. Login using the credentials username: admin; password: admin.
  12. +
  13. On left hand navigation locate and click on System Tools and then on expanded menu click on Firmware Upgrade
  14. +
  15. Now click on Browse button and select the file from NeDARK - wr841n(EU)_v11_150616.bin from your downloaded folder.
  16. +
  17. Click on Upgrade button. It will take roughly 30 to 40 seconds and router will reboot.
  18. +
  19. Refresh the browser screen and you will once again be presented with TP-LINK admin interface.
  20. +
  21. Once again login using the credentials username: admin; password: admin.
  22. +
  23. On left hand navigation locate and click on System Tools and then on expanded menu click on Firmware Upgrade
  24. +
  25. Now click on Browse button and select the file factory-to-ddwrt.bin.
  26. +
  27. Click on Upgrade button. It will take roughly 30 to 40 seconds and router will reboot.
  28. +
  29. Disconnect the ethernet cable from Laptop and reboot the laptop - This isn't always required but just to be safe.
  30. +
  31. Reconnect the ethernet cable to the laptop. Make sure that wifi is switched off on the laptop.
  32. +
  33. Once the ethernet connection on your laptop is established, open a browser and type 192.168.1.1 and press enter. <-- Notice the different IP than what was used in step 4.
  34. +
  35. If all has gone well until now you will be greeted with DD-WRT login interface and will actually be asked to change the password.
  36. +
  37. After providing the password, navigate to tab Administration and then sub-tab named Firmware upgrade.
  38. +
  39. Click on Browse and this time select tl-wr841nd-webflash.bin and Click upgrade. This will take 40 seconds or so and your router has now been liberated.
  40. +
+

This is it for flashing TP-LINK TL-WR841N v11 router with DD-WRT. I made fiber optic broadband work with no fuss but with NEST there were some issues basically down to auto setting changing to channel 13.

+

Making NEST play nice with DD-WRT

+

The issue was that NEST would suddenly drop internet connection and then not identify the SSID for my router. The SSID just won't appear in the list of available wireless networks.

+
    +
  • *NEST apparently does not like channel 13, possibly even Channel 11 and 12. Changing the channel to any of the single digit (1 - 9) works well.
  • +
  • In addition giving a static IP for NEST MAC seems to have resolved any network drops whatsoever.
  • +
  • Under Wireless Setting, I also changed the Beacon Interval to 211.
  • +
  • Under Administration tab > Management > IP Filter Settings, I changed the TCP timeout to 1800 and UDP timeout to 3600.
  • +
+

Configure router for PlusNet Fiber Optic Broadband

+

For configuring PlusNet fiber optic broadband, the settings I used are as below:

+

Setup > Basic Setup

+

Setup 1

+

Setup 2

+

Setup > DDNS

+

Setup 3

+

Wireless > Basic Settings

+

Wireless-Basic-Settings

+

Wireless > Wireless Security

+

Wireless-Wireless-Security

+

Administration > Management

+

Administration-Management

+

Administration > Commands

+

Add the following in the startup commands

+
1
+2
+3
## Fix lan port communication 841 v7, v9, v11 
+swconfig dev eth0 set enable_vlan 1 
+swconfig dev eth0 set apply
+
+

as shown below:

+

Administration-Command

+

Above basic setting ensures that PlusNet fiber optic broadband works perfectly fine.

+
+
+
    +
  1. +

    https://www.quora.com/Is-the-TP-Link-TL-WR841n-v11-router-supported-by-DD-WRT 

    +
  2. +
  3. +

    https://www.mediafire.com/folder/q56dcdecfh3v1/Router_Firmware 

    +
  4. +
  5. +

    http://download1.dd-wrt.com/dd-wrtv2/downloads/betas/2016/12-15-2016-r30949/tplink_tl-wr841ndv11/factory-to-ddwrt.bin 

    +
  6. +
  7. +

    http://download1.dd-wrt.com/dd-wrtv2/downloads/betas/2016/12-15-2016-r30949/tplink_tl-wr841ndv11/tl-wr841nd-webflash.bin 

    +
  8. +
+
+ + + + + + + + + + + + + + + + + +
+ Comments from Disqus +

As I plan to move away from disqus comment system, I am just including the screenshots of comments just so any information shared by or to the readers is retained.

+

+ Disqus Comments + Disqus Comments

+
+ + + + + + +
+
+ + + + + +
+ + + +
+ + + +
+
+
+
+ + + + + + + + + + + + \ No newline at end of file diff --git a/2017/02/13/markdown-and-gantt-charts.html b/2017/02/13/markdown-and-gantt-charts.html new file mode 100644 index 0000000..f71b88b --- /dev/null +++ b/2017/02/13/markdown-and-gantt-charts.html @@ -0,0 +1,1976 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + Markdown and Gantt Charts - Make Gadgets Work + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + Skip to content + + +
+
+ +
+ + + + +
+ + +
+ +
+ + + + + + + + + +
+
+ + + + + + + + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+
+ + +
+
+
+
+ + + + + + + + + + + + + + + + + + + + +

Markdown and Gantt Charts

+ +

For a fairly long time, I have been looking for a simple markdown type of solution to be able to quickly draw Gantt charts but never came across what one would call a quick option. It has always been an involved process.

+

To my simplistic mind, a simple solution would just be an option where I can type the action, a start date and an end date for the action line after line and it get's displayed in the Gantt chart.

+ + +

The latest edition of Linux Format (Issue 221, Page 52) had mermaid as one of the hotpicks and highlighted the availability of Gantt charts which piqued my interest, I tried the solution and it is indeed what I was looking for. There are a few rough edges but it is a working solution and in normal circumstances will save considerable time and hence will become my tool of choice. +There are few ways of using it and because it has been made to work with MarkDown syntax, it can work on a number of tools too. I found CuteMarkEd to be the easiest one to use but the various ways to use it are listed below.

+

Install CuteMarkEd

+

CuteMarkEd is not available in debian repositories nor does it have a binary for debian. Although it does have an rpm binary. As I have been playing with Debian / Ubuntu based Elementary OS lately, instead of my regular Fedora install, I figured might as well compile from source code. Steps are as below and are also available on the github page for CuteMarkEd

+
 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
#Prepare the environment for building from source code.
+sudo apt-get install build-essential checkinstall git
+
+#Get the code from github
+git clone --recursive https://github.com/cloose/CuteMarkEd.git
+
+#Install dependencies for CuteMarkEd
+sudo apt-get install libqt5webkit5-dev qttools5-dev-tools qt5-default discount libmarkdown2-dev libhunspell-dev
+
+#Compile and install CuteMarkEd
+qmake CuteMarkEd.pro
+cd CuteMarkEd/
+qmake CuteMarkEd.pro
+make
+echo 'A Qt-based Markdown editor with live HTML preview and syntax highlighting of markdown document.' > description-pak
+sudo checkinstall --requires 'libqt5webkit5, libmarkdown2, libhunspell-1.3-0, discount'
+sudo ln -s /usr/lib/x86_64-linux-gnu/qt5/bin/cutemarked /usr/local/bin/
+sudo mkdir -p /usr/local/share/icons
+sudo cp app/icons/scalable/cutemarked.svg /usr/local/share/icons/cutemarked.svg
+sudo apt-get install fcitx-libs-qt5
+
+

Now there is a bug in the tool which makes the Gantt charts appear as monochrome but fear not for there is a workaround for it. Once installed, open CuteMarkEd and add a snippet in the tool to ensure Gantt charts display properly. Steps below:

+
    +
  1. +

    Enable diagram support by clicking Extras->Options->Diagram Support

    +
      +
    • image
    • +
    +
  2. +
  3. +

    Add the snippet for quick creation by clicking Extras->Options->Snippets->Add

    +
  4. +
  5. +

    Then type ~~~gantt and in description something to the effect of Adds the codeblock for mermaid with the necessary javascript

    +
  6. +
  7. +

    Now in the snippet box add the following code.

    +
  8. +
  9. +

    To invoke the snippet simply type in editor ~~~gantt followed by pressing the Ctrl+Space keys.

    +
  10. +
+
 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
+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
+52
+53
+54
+55
+56
+57
+58
+59
+60
+61
+62
+63
+64
~~~mermaid
+gantt  
+    dateFormat  YYYY-MM-DD
+    title <Name of the project>
+
+%%  <Name of Activity>      : crit if critical else empty,done, active or empty, reference name or empty, Start Date or dependency, End Date or Duration
+    section Phase 1 Name
+    Activity 1          :    done,    des1, 2017-01-06, 2017-01-08
+    Activity 2          :    active,  des2, 2017-01-09, 2017-01-12
+    Activity 3          :             des3, 2017-01-12, 5d
+    Activity 4          :             des4, after des3, 5d
+
+    section Phase 2 Name
+    Activity 5          : crit, done,       2017-01-06, 24h
+    Activity 6          : crit, done,       after des1, 2d
+    Activity 7          : crit, active,             3d
+    Activity 8          : crit,                 5d
+    Activity 9          :                   2d
+    Activity 10         :                   1d
+
+    section Phase 3 Name
+    Activity 11         :   active,   a1,   after des1, 3d
+    Activity 12         :           after a1  , 20h
+    Activity 13         :        doc1,  after a1  , 48h
+
+    section Phase 4 Name
+    Activity 12         :           after doc1, 3d
+    Activity 15         :                   20h
+    Activity 16         :                   48h
+~~~
+<script>  
+mermaid.ganttConfig = {
+    titleTopMargin:25,
+    barHeight:20,
+    barGap:4,
+    topPadding:50,
+    sidePadding:100,
+    gridLineStartPadding:35,
+    fontSize:11,
+    numberSectionStyles:3,
+    axisFormatter: [
+        // Within a day
+        ['%I:%M', function (d) {
+            return d.getHours();
+        }],
+        // Monday a week
+        ['w. %U', function (d) {
+            return d.getDay() == 1;
+        }],
+        // Day within a week (not monday)
+        ['%a %d', function (d) {
+            return d.getDay() &amp;&amp; d.getDate() != 1;
+        }],
+        // within a month
+        ['%b %d', function (d) {
+            return d.getDate() != 1;
+        }],
+        // Month
+        ['%m-%y', function (d) {
+            return d.getMonth();
+        }]
+    ]
+};
+</script>
+
+

This will then show in the preview pane following Gantt Chart and we can edit, add and modify the data as per the requirements:

+

image

+
+

Tip

+

You can make as many snippets as you want and for the simple solution that I mentioned right at the top, I made one snippet called dategantt replacing the code between line 3 to line 29 of the snippet above

+
 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
dateFormat  DD/MM/YY
+title Project Name
+
+Section Pre-condition
+Activity 1          :21/12/16, 22/12/16
+Activity 2      :21/12/16, 22/12/16
+Activity 3  :16/01/17, 02/02/17
+Activity 4      :01/02/17, 02/02/17
+
+Section Kick-off
+Activity 5  :01/02/17, 03/02/17
+Activity 6  :01/02/17, 03/02/17
+Project Initiated       :01/02/17, 03/02/17
+
+Section Tech Design
+Technical Design        :crit, active, T1, 06/02/17, 21/03/17
+
+Section Delivery    
+Activity 7      :06/02/17, 10/02/17
+Activity 8          :10/02/17, 14/02/17
+Order               :15/02/17, 14/03/17
+Deployment          :crit, 15/03/17, 21/03/17
+Activity 9          :crit,T2, after T1, 23/03/17
+Project Close-Down      :after T2, 24/03/17
+
+

Once done, just type dategantt followed by Ctrl+Space and you will have the framework to enter activities, start dates and end dates. Gantt Chart will be ready.

+
+

Install Mermaid CLI

+

One of the drawbacks of the CuteMarkEd is that the pdf export there just does not work and you will need to rely on a screenshot. Now if the Gantt chart is really huge a screen shot wont cut it and mermaid does offer the ability to generate svg file for your effort. It is ofcourse a command line tool but is actually fairly easy to use once installed.

+
 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
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
#check if nodejs is installed. If command below results in nothing then you need to install nodejs
+nodejs -v
+
+## Instal nodejs
+sudo apt install nodejs
+#On Ubuntu there is some link issue where node is 
+#referenced as nodejs which results in issues 
+#while installing npm modules. This can be avoided 
+#by creating a symlink using following command.
+sudo ln -s /usr/bin/nodejs /usr/bin/node
+
+#Install npm
+sudo apt install npm
+
+#In order to install npm modules without sudo,
+#As explained in one of my previous post:
+#    http://mgw.dumatics.com/ghost-on-fedora-24/#step3installnpmmodules, 
+#it requires fixing the permissions using following steps.
+#This needs to be done only once so if you have 
+#done this in past, then there is no need to repeat.
+
+mkdir ~/.npm-global
+npm config set prefix '~/.npm-global'
+nano ~/.profile
+source ~/.profile
+
+#Install Mermaid and phantomjs on which mermaid depends.
+
+npm install -g phantomjs
+npm install -g mermaid
+
+#Check if mermaid is installed by issuing the command
+mermaid --h
+
+#Generate a png using following command
+mermaid -p path/to/markdown file with .md or .mmd extension.
+
+

Install Markdown-plus

+

MarkdownPlus is a good markdown editor and can be used on their hosted solution or for offline use it can be installed as a local copy and accessed from the browser by pointing the browser to the index.html. The bug from CuteMarkEd is not present here. +To access offline, you can clone it from github using following command.

+
git clone --recursive https://github.com/tylingsoft/markdown-plus.git
+
+

Then open the folder markdown-plus and right click on the index.html file and open it using your browser of choice. Alternatively, in the browser enter file:///home/path/to/markdown-plus//index.html.

+

Mermaid Live Editor

+

While Markdown Plus is quite a versatile browser based editor, I did not like the fact that there is no easy way to save the file you have created and all I could work out was that you will have to copy paste your text and save it using another text editor. +I felt, for this reason that the Mermaid Live editor was better compared to this option and the live editor is quite basic to be honest and can't be used offline.

+ + + + + + + + + + + + + + + + + + + + + + +
+
+ + + + + +
+ + + +
+ + + +
+
+
+
+ + + + + + + + + + + + \ No newline at end of file diff --git a/2017/03/03/rstudio-server-setup-with-ssl-behind-apache-proxy-server.html b/2017/03/03/rstudio-server-setup-with-ssl-behind-apache-proxy-server.html new file mode 100644 index 0000000..113206e --- /dev/null +++ b/2017/03/03/rstudio-server-setup-with-ssl-behind-apache-proxy-server.html @@ -0,0 +1,1905 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + Rstudio Server Setup with SSL behind Apache proxy server - Make Gadgets Work + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + Skip to content + + +
+
+ +
+ + + + +
+ + +
+ +
+ + + + + + + + + +
+
+ + + + + + + + + + + + + + + +
+
+
+
+ + +
+
+
+
+ + + + + + + + + + + + + + + + + + + + +

Rstudio Server Setup with SSL behind Apache proxy server

+ +

Install R using following commands:

+
1
+2
+3
+4
+5
sudo apt-get install r-base libapparmor1 gdebi-core
+## Check that R is installed
+R
+#quit R
+q()
+
+ + +

Install Rstudio IDE server

+
1
+2
+3
cd Downloads/
+wget https://download2.rstudio.org/rstudio-server-1.0.136-amd64.deb
+sudo gdebi rstudio-server-1.0.136-amd64.deb
+
+

At this point if all goes well you can check the status of rstudio server by issuing the command:

+
sudo systemctl status rstudio-server.service
+
+

The server is started automatically at port 8787 and can be accessed using <ip_address:8787> in browser of your choice, provided all firewall settings have been taken care of.

+

However, when you open the Rstudio server you will be presented with a logon screen and while you can access this using the users for the machine it is hosted on, it will be prudent to create a lower privilege user as explained in next section.

+

Add User to access the RStudio

+
sudo adduser rstudio
+
+

Set up SSL and reverse proxy for R-Studio Server

+

Now important thing to note is that community version of Rstudio server does not come with SSL enabled but just to run it on a secure socket layer you don't necessarily need the pro version. By following the steps below, your communication with the server will be on SSL.

+

However to achieve the objective we need to accomplish following steps:

+
    +
  1. Enable modules on Apache to help set up proxy
  2. +
+
    +
  • Configure a proxy to control access to RStudio Server
  • +
  • Use LetsEncrypt to enable SSL
  • +
  • Restrict access to Rstudio server only through proxy
  • +
  • Restart both Rstudio and Apache servers
  • +
+ +

Step 1: Enable modules on Apache to help set up proxy

+

There is guidance on how to do this on Rstudio Support. However, there was a bit of hair pulling and head scratching involved to get all the steps above work together so stick with me but keep that link in back pocket for variations or when you are stuck.

+

With head scratching and hair pulling I mean that I encountered errors such as these - AH01102 error reading status line from remote server, Rstudio Proxy redirect changing the URL to localhost and many others which can be avoided by following steps as explained below. Anyway so we need to enable mod_proxy and mod_proxy_wstunnel modules. As Apache is already installed and mod_proxy already enabled I did not have to install the module itself, but if it needs to be done the commands are:

+
1
+2
sudo apt-get install libapache2-mod-proxy-html
+sudo apt-get install libxml2-dev
+
+

Issuing the following commands should enable the relevant modules: +

1
+2
+3
sudo a2enmod proxy
+sudo a2enmod proxy_http
+sudo a2enmod proxy_wstunnel
+

+

Step 2: Configure a proxy to control access to RStudio Server

+
1
+2
+3
+4
## Change directory to sites-available
+cd /etc/apache2/sites-available
+## create a rstudio conf file
+sudo nano rstudio.conf
+
+

Paste the following in the conf file but make sure to change details relevant to your domain name for each entry (line numbers 2, 3, 4, 15 and 16 below):

+
 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
+26
+27
<VirtualHost *:80>;
+        ServerAdmin user@yoursite.com <!-- (1) -->
+        ServerName yoursite.com <!-- (2) -->
+        ServerAlias whatever.yoursite.com <!-- (3) -->
+
+#Specify path for Logs
+        ErrorLog ${APACHE_LOG_DIR}/error.log
+        CustomLog ${APACHE_LOG_DIR}/access.log combined
+
+        RewriteEngine on
+
+#Rewrite the url supplied to ensure https is applied
+        RewriteCond %{SERVER_NAME} =yoursite.com [OR] <!-- (5) -->
+        RewriteCond %{SERVER_NAME} =whatever.yoursite.com <!-- (6) -->
+        RewriteRule ^ https://%{SERVER_NAME}%{REQUEST_URI} [END,QSA,R=permanent]
+
+## Following lines should open rstudio directly from the url
+        RewriteCond %{HTTP:Upgrade} =websocket
+        RewriteRule /(.*)     ws://localhost:8787/$1  [P,L]
+        RewriteCond %{HTTP:Upgrade} !=websocket
+        RewriteRule /(.*)     http://localhost:8787/$1 [P,L]
+        ProxyPass / http://localhost:8787/
+        ProxyPassReverse / http://localhost:8787/
+
+</VirtualHost>;
+
+## vim: syntax=apache ts=4 sw=4 sts=4 sr noet
+
+
    +
  1. 💡 Change details relevant to your domain name
  2. +
  3. 💡 Change details relevant to your domain name
  4. +
  5. 💡 Change details relevant to your domain name
  6. +
  7. 💡 Change details relevant to your domain name
  8. +
  9. 💡 Change details relevant to your domain name
  10. +
  11. 💡 Change details relevant to your domain name
  12. +
+

Press Ctrl+X and save the file.

+
+

Tip

+

If you just want reverse proxy and no SSL, you can just comment out line 15, 16 and 17 in above conf file and you are all set. If you do want to enable SSL, enabling the site with commands below won't probably work just yet and subsequent steps will need to be completed.

+
+

Now enable the new site by issuing the commands:

+
1
+2
sudo a2ensite rstudio.conf
+sudo service apache2 restart
+
+

Step 3: Use LetsEncrypt to enable SSL

+

Follow the instructions here for specific usecase but one way or the other using Certbot you will be able to obtain the LetsEncrypt SSL certificate and enable it on your server.

+

Once certbot has completed doing it's thing you would find an additional conf file in /etc/apache2/sites-available named rstudio-le-ssl.conf. It will be pretty much same content as in rstudio.conf with very minor changes. The first line will be listening on 443 instead of 80 and the ssl certificates will be included. Normally, you would not need to tweak anything in the resultant file but just for reference the contents of this file will look as below:

+
 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
<IfModule mod_ssl.c>
+<VirtualHost *:443>
+
+        ServerAdmin user@yoursite.com
+        ServerName yoursite.com
+        ServerAlias whatever.yoursite.com
+
+        ErrorLog ${APACHE_LOG_DIR}/error.log
+        CustomLog ${APACHE_LOG_DIR}/access.log combined
+
+RewriteEngine on
+## Following lines should open rstudio directly from the url
+        RewriteCond %{HTTP:Upgrade} =websocket
+        RewriteRule /(.*)     ws://localhost:8787/$1  [P,L]
+        RewriteCond %{HTTP:Upgrade} !=websocket
+        RewriteRule /(.*)     http://localhost:8787/$1 [P,L]
+        ProxyPass / http://localhost:8787/
+        ProxyPassReverse / http://localhost:8787/
+
+SSLCertificateFile /etc/letsencrypt/live/whatever.yoursite.com/fullchain.pem
+SSLCertificateKeyFile /etc/letsencrypt/live/whatever.yoursite.com/privkey.pem
+Include /etc/letsencrypt/options-ssl-apache.conf
+</VirtualHost>
+## vim: syntax=apache ts=4 sw=4 sts=4 sr noet
+</IfModule>
+
+

Step 4: Restrict access to Rstudio server only through proxy

+

Finally, we want to ensure that access to the Rstudio server is only through the proxy we configured and to do that we just need to specify this in the rstudio server configuration the attribute that tells it to only serve requests from localhost.

+
sudo nano /etc/rstudio/rserver.conf
+
+

Now on the opened file type www-address=127.0.0.1 and press Ctrl+X and save the file.

+

Step 5: Restart both Rstudio and Apache servers

+

Finally issue the following commands to restart both the servers:

+
1
+2
sudo systemctl restart rstudio-server.service
+sudo service apache2 restart
+
+

This is it. Now your new Rstudio server is ready to be used through secure socket layer.

+ + + + + + + + + + + + + + + + + +
+ Comments from Disqus +

As I plan to move away from disqus comment system, I am just including the screenshots of comments just so any information shared by or to the readers is retained.

+

+ Disqus Comments + Disqus Comments

+
+ + + + + + +
+
+ + + + + +
+ + + +
+ + + +
+
+
+
+ + + + + + + + + + + + \ No newline at end of file diff --git a/2017/10/05/ghost-v1-0-upgrade-related-quirks-and-fixes.html b/2017/10/05/ghost-v1-0-upgrade-related-quirks-and-fixes.html new file mode 100644 index 0000000..72962ed --- /dev/null +++ b/2017/10/05/ghost-v1-0-upgrade-related-quirks-and-fixes.html @@ -0,0 +1,1824 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + Ghost V1.0 Upgrade on Apache stack, related quirks and fixes - Make Gadgets Work + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + Skip to content + + +
+
+ +
+ + + + +
+ + +
+ +
+ + + + + + + + + +
+
+ + + + + + + + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+
+ + +
+
+
+
+ + + + + + + + + + + + + + + + + + + + +

Ghost V1.0 Upgrade on Apache stack, related quirks and fixes

+ +

Right then, the Ghost V1.0 was out a while back and they made Ghost 0.11.x an LTS so I was not in any rush to upgrade too. I have not had much time to sort this out for a while and two days back when I finally came around to check how to upgrade, my first moment of concern was that officially supported stack is for NGINX.

+

I have moved my blog to the Apache Stack on DigitalOcean and while on my sandbox environment I still have NGINX, that is not a place I want to host my blog from. Anyhoo, I realised soon enough that while not officially supported it s easy to bypass the restrictions so I went ahead.

+

The upgrade itself couldn't have been simpler considering the major version bump. The answer to the question 'Was it worth it?' is something we will have to wait and see although I am liking what I see except for the initial hiccups.

+
+

Success

+

EDITED AFTER THE POST: Boy oh boy - just after I finished this post I saw the latest version of Ghost V1.12 is out and it was such a painless process compared to past. Just a simple command 'ghost update' and job done. That itself makes this whole pain kind of worth it.

+
+

OK so the steps I took are as presented below.

+ + +

Backup

+

We will take the back-up from front end for all the posts and we will also backup on the server the entire directory where old instance of the blog is residing. To take backup of all the content and download it in a json file, open your ghost site on a browser, navidate to Settings and then click on Export.

+

Next create the backup of folder on the server itself. To do this issue the following commands on the terminal.

+
 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
#Updateand upgrade the OS repo
+sudo apt-get update
+sudo apt-get upgrade
+#Stop the ghost server
+pm2 stop Ghost 'assuming Ghost is the pm2 id for the site'
+## Change directory to web-server root
+cd /var/www/html/'path to your ghost directory say 'ghost' '
+## Create a new directory for backup
+sudo mkdir old_ghost_bkp
+sudo mv ghost old_ghost_bkp
+## Recreate the ghost directory
+sudo mkdir ghost
+cd ghost
+## Give right privileges to the new directory
+sudo chown -R &lt;your username&gt;:www-data .
+
+

Download and Install

+

As we are already in the right directory lets get on with installing the latest version of Ghost using npm.

+
1
+2
+3
+4
+5
sudo npm install -g ghost-cli
+#Make sure you are in the directory where new ghost is to be installed.
+#If you have followed all commands so far, you will already be in 
+#required the directory
+ghost install
+
+
+

Warning

+

It is at this point that you will have to deviate from official guide if you have Apache instead of NGINX. You will be prompted by the installer that it could not find NGINX and do you still want to continue. Default is 'No' so make sure you enter 'Y' and then press enter.

+

nginx-not-found

+
+

For me, rest of the install went smoothly.

+

finish-install-process

+

Setup Wizard

+

Immediately after the install is complete, you will be presented with following questions:

+
+

Info

+

Please note that if you have configured SSL using LetsEncrypt as explained in previous posts on this blog then even if you are using https, the answer to blog url must be the with http and not https.

+

For example: I gave http://mgw.dumatics.com and not https://mgw.dumatics.com

+
+
 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
Enter your blog url: http://your.blog.url
+Enter your MySQL Hostname: localhost
+Enter your MySQL Username: root
+Enter your MySQL Password: 'your mysql root password'
+Enter your Ghost database Name: "a relevant name" 
+## for security reasons you may want to keep it different from your blog name";
+Do you wish to set up Nginx: no # (1)
+Do you wish to set uo Ghost MySQL User: yes
+Do you wish to set up Systemd: yes
+Do you want to start Ghost: yes
+
+
    +
  1. Please do note that the response to 'Setup Nginx' must be 'no'
  2. +
+
+

Warning

+

Please do note that the response on line 6 above to 'Setup Nginx' must be 'no'

+
+

After the questions are complete you will get a notification You can access you blog at http://your.blog.url

+

At this point, it is best to see which port is configured by ghost CLI for this installation. you can do so by checking the configuration file like so:

+
nano config.production.json
+
+

You can change the port if you like but if it is different than the port you originally had for old version of ghost you can either change it here or you need to change Apache conf file in next step.

+

If you do decide to change the port here, then there should be no need to carry out the next step - Configure Apache.

+

Configure Apache

+

Assuming that the port in Ghost config file was 1234, there will be some changes that you will need to make in Apache conf files like so:

+
1
+2
cd /etc/apache2/sites-available/
+sudo nano ghost.conf
+
+

Now change the port on ProxyPass and ProxyPassReverse to be same as what is in the config.production.json file and save it by pressing Ctrl+x and y.- so for this example it will be changed to 1234 and change will look as below:

+
1
+2
+3
ProxyRequests off
+ProxyPass / http://localhost:1234/
+ProxyPassReverse / http:/localhost:1234/
+
+

Now open the ssl config file for the site using commands below and make the same changes as above.

+
sudo nano ghost-le-ssl.conf
+
+
+

Tip

+

If done using LetsEncrypt, it will be named something like ghost-le-ssl.conf.

+
+

Once the changes are saved, disable and enable the configurations using following commands:

+
1
+2
+3
+4
+5
+6
sudo a2dissite ghost.conf
+sudo a2dissite ghost-le-ssl.conf
+sudo a2ensite ghost.conf
+sudo a2ensite ghost-le-ssl
+#Restart the server
+sudo service apache2 restart
+
+

Now if you enter you blog url in a browser, you should be presented with vanilla Ghost site. If not, something in server set-up has not worked and you will need to troubleshoot it and fix - luckily for me all worked like a charm.

+

Restore

+

Right, so you are now on the browser looking at the Vanilla Ghost install. First thing you need to do now is create the user with same credentials you had on your old version of ghost. To do his you will first need to enter the admin url for ghost and follow the steps to create your user.

+

Once you are into the admin interface, navigate to 'Settings' > 'Labs' and click on 'Choose File' button, select the json backup that was exported from your old version of the blog and then click on 'Import' button.

+

Now to restore the images from your old blog on the server issue following commands:

+
1
+2
+3
+4
+5
+6
+7
+8
cd /var/www/html/ghost/content
+sudo rm images
+sudo cp /var/www/html/old_ghost_bkp/ghost/content/images /var/www/html/ghost/content/
+#### Make the new image directory is writable or image uploads will fail
+sudo chown -R ghost:ghost images/
+#### Restart Ghost
+cd ..
+ghost restart
+
+
+

Warning

+
    +
  1. The ghost CLI commands like stop, start and restart will require you to be in the directory where ghost is installed.
  2. +
  3. While start and stop commands of ghost specifically ask for sudo credentials, restart command just keeps rotating and hence it is better to issue a command with sudo before you issue ghost restart.
  4. +
+
+

This is it. Your old blog is now fully restored.

+

Tweak

+

This section is a bit of a pain because there are quite a few things that break with this version. So if you have heavily used html, you will painstakingly need to go through posts and add a new line between markdown and html content for it to be parsed properly or else it will display quite wiered outputs on your blog.

+

If you have used code blocks with syntax highlighting, another change is with older version you would have given three backticks followed by language-sql but now you just need to give three backticks followed by sql.

+

If you have used line numbering using prism.js, it just wont work and you will need to apply changes to your theme the way I did. Without going too much in detail on that, you can get the copy of prism.js, prism-custom-line-number.js, prism.css, prism-line-number.css using the github links for my theme and place them in assets directory of your theme.

+

Then make sure you include them in relevant files where your theme calls the javascripts.

+

Once done, issue the command ghost restart and things should look pretty again.

+

Happy Migrating !!!

+ + + + + + + + + + + + + + + + + + + + + + +
+
+ + + + + +
+ + + +
+ + + +
+
+
+
+ + + + + + + + + + + + \ No newline at end of file diff --git a/2017/11/19/unprotect-sheets-in-libre-calc-excel.html b/2017/11/19/unprotect-sheets-in-libre-calc-excel.html new file mode 100644 index 0000000..74dc200 --- /dev/null +++ b/2017/11/19/unprotect-sheets-in-libre-calc-excel.html @@ -0,0 +1,1624 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + Unprotect Sheets in Libre Calc, Excel - Make Gadgets Work + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+
+ +
+ + + + +
+ + +
+ +
+ + + + + + + + + +
+
+ + + + + + + + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+
+ + +
+
+
+
+ + + + + + + + + + + + + + + + + + + + +

Unprotect Sheets in Libre Calc, Excel

+ +

A friend of mine today had an issue. He had created a template for some really complex calculations and to ensure he does not mess up with the forumlae by mistake he had password protected sheets and cells. He had done this back in 2012 and now he wanted change something in there but he had forgotten the password so he asked me if I can help.

+

Now, I do not know much about how it could be done on windows or on excel but I knew a small trick on LibreOffice so I asked him to send me that excel file by email. I then took following steps:

+ + +
    +
  1. +

    Opened the excel file Locked.xls in LibreCalc.

    +
  2. +
  3. +

    Saved it as an Locked.ods file.

    +
  4. +
  5. +

    From file browser, right click on newly saved Locked.ods file -> Open With -> Archive Manager as shown below.

    +
      +
    • Menu_001
    • +
    +
  6. +
  7. +

    Now open the content.xml

    +
  8. +
  9. +

    Find table:protected='true' and Replace All with table:protected='false'

    +
  10. +
  11. +

    Save the xml file.

    +
  12. +
  13. +

    Now open the Locked.ods in LibreCalc and save it as Unlocked.xlsx

    +
  14. +
+

This should do the trick and unlock all password protected sheets and cells to be freely modified.

+ + + + + + + + + + + + + + + + + + + + + + +
+
+ + + + + +
+ + + +
+ + + +
+
+
+
+ + + + + + + + + + + + \ No newline at end of file diff --git a/2018/01/04/ghost-upgrade-errors-and-fixes-1-19-x.html b/2018/01/04/ghost-upgrade-errors-and-fixes-1-19-x.html new file mode 100644 index 0000000..f04edb1 --- /dev/null +++ b/2018/01/04/ghost-upgrade-errors-and-fixes-1-19-x.html @@ -0,0 +1,1687 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + Ghost Upgrade errors and fixes (1.19.x) - Make Gadgets Work + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+
+ +
+ + + + +
+ + +
+ +
+ + + + + + + + + +
+
+ + + + + + + + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+
+ + +
+
+
+
+ + + + + + + + + + + + + + + + + + + + +

Ghost Upgrade errors and fixes (1.19.x)

+ +

I have found the recent ghost upgrades quite painless but there have been few hiccups for last two times so I kept a record of what helped and it is as listed below:

+

General Update should be as simple as:

+ + +

1
+2
cd /var/www/html/ghost/
+ghost update
+
+If after this step, there are any errors or an indication to update ghost-cli, following command should be used.

+

sudo npm install -g ghost-cli
+
+After this if there are issues accessing the blog over internet, we may need to do a bit of checks. Logical sequence is to first check that access for all folders is right and proper. If it needs to be updated, command to be used is:

+

1
+2
+3
+4
cd ghost
+sudo chown -R &lt;your username&gt;:www-data .
+cd content/
+sudo chown -R ghost:ghost images/
+
+If there are any errors when starting ghost, following command is indicated by the ghost-cli and it does help.

+

ghost setup linux-user systemd
+
+If still accessing the blog is an issue following command will list the ghost log file:

+

ghost log

+

If there are database migration errors on log file, following commands may help:

+
1
+2
sudo npm install -g knex-migrator
+ghost setup migrate
+
+
+

Tip

+

ghost setup migrate does work very well and must be remembered.

+
+

If you have digitalocean setup such as mine, memory can be an issue you may want to restart the virtual machine and reload the swap files.

+
1
+2
+3
sudo reboot now
+sudo swapon /swapfile1
+sudo swapon /swapfile2
+
+

If update if failing with the message not enough memory try running the following:

+
ghost update --no-check-mem
+
+

If there are still issues accessing the blog with error 503, check the apache logs:

+
sudo nano /var/log/apache2/error.log
+
+

If issue is with accessing the upstream ghost server. Try changing the port on ghost config and updating the apache conf files.

+
 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
##Change Directory to ghost install
+cd /var/www/html/ghost
+##Stop the ghost server
+ghost stop
+##Change port to another number
+nano config.production.json
+##Change directory apache2 server
+cd /etc/apache2/sites-available/
+##Open the ghost.conf file and change the localhost port to same 
+##number that was changed in config.production.json
+sudo nano ghost.conf
+##Open the ghost-le-ssl.conf file and change the localhost port 
+##to same number that was changed in config.production.json
+sudo nano ghost-le-ssl.conf
+##Disable and enable the conf files on apache.
+sudo a2dissite ghost.conf ghost-le-ssl.conf
+sudo a2ensite ghost-le-ssl.conf ghost.conf 
+sudo service apache2 reload 
+##Finally change directory to your ghost install 
+##and start ghost server.
+cd /var/www/html/ghost
+ghost start
+
+ + + + + + + + + + + + + + + + + + + + + + +
+
+ + + + + +
+ + + +
+ + + +
+
+
+
+ + + + + + + + + + + + \ No newline at end of file diff --git a/2018/02/20/mysql-function-to-calculate-closing-workday-date-given-elapsed-working-time.html b/2018/02/20/mysql-function-to-calculate-closing-workday-date-given-elapsed-working-time.html new file mode 100644 index 0000000..fe59367 --- /dev/null +++ b/2018/02/20/mysql-function-to-calculate-closing-workday-date-given-elapsed-working-time.html @@ -0,0 +1,1874 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + MySQL Function to calculate closing WorkDay date given elapsed working time - Make Gadgets Work + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + Skip to content + + +
+
+ +
+ + + + +
+ + +
+ +
+ + + + + + + + + +
+
+ + + + + + + + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+
+ + +
+
+
+
+ + + + + + + + + + + + + + + + + + + + +

MySQL Function to calculate closing WorkDay date given elapsed working time

+ +

On my post MySQL function to calculate elapsed working time I was asked in comments if the assumptions can be reversed such that given start date, starting time and closing time of site and the elapsed working hours, function should return the closed date. I was convinced that it will be possible to achieve this with minor tweaks to original logic and so I only concentrated on original code and how to tweak it to achieve the result. It is very likely that there might exist a more elegant solution but frankly I did not have a usecase I did find a usecase afterall - Clue was in redefining the problem description for this. Presented below is the function that with my minimal testing seems to give correct results. Feel free to try it and as always any feedback is welcome. 😄

+

Problem Description:

+

Find out the WorkDay date by when a ticket must be closed given:

+
    +
  1. Country of site for which incident was logged.
  2. +
  3. Time and Date when Incident was logged.
  4. +
  5. Elapsed working hours in decimal. (Ten and a half hours as 10.5 and so on)
  6. +
  7. Opening time of the site for which the incident was logged.
  8. +
  9. Closing Time of the site for which incident was logged.
  10. +
  11. SLA Type - Is the output date to be based purely on number of hours or based on SLA in days (eg: Next Business Day).
  12. +
+
+

Assumption

+

It is assumed that opening and closing times are same on all working days and that all the sites are closed on holidays and weekends.

+
+ + +

Example of problem

+

Let's say an incident was logged on "Friday 23rd Feb 2018 at 15:00" for a site in the "UK" which opens between 08:00 to 16:00. Now if the SLA is purely based on hours i.e. Tickets must be closed withing 16 hours then for the above incident function should calculate the closing date as:

+

2018-02-27 15:00 = 1 hour on Friday, then skip Saturday and Sunday, 8 hours on Monday and 7 hours on Tuesday the 27th of Feb 2018. However, if the SLA is based on number of days, then 16 hours will translate to an SLA of next business day hence in this case function should return 2018-02-26 16:00 which is a Monday end of day and the next business day for this incident. The use case really is in creating a report that shows the target closed date based on SLA. Specifically Next Business Day SLA, which isn't direct hours calculation and is really always the next working day but based on cut-off time when the call is logged. So a call logged on a working day 1 after cut-off will be counted as logged on working day 2 and hence will need to be closed by working day 3 whereas a call logged within cut-off of working day 1 will need to be closed by close of business working day 2. This is turn can allow for a comparison between Target Closed Date from this function and Actual Closed Date to show SLA failures where Actual Closed Date is greater than Target Closed Date.

+

Pre-Requisites:

+

Follow from previous post

+

Function code and explanation

+
 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
+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
+52
+53
+54
+55
+56
+57
+58
+59
+60
+61
+62
+63
+64
+65
+66
+67
+68
+69
+70
+71
+72
CREATE DEFINER=`ankit`@`%` FUNCTION `get_closed_date_given_start_time`(
+    `param_country` VARCHAR(20),
+    `assigneddatetime` VARCHAR(20),
+    `param_elapsedhours` VARCHAR(10),
+    `starttime` VARCHAR(20),
+    `endtime` VARCHAR(20),
+    `sla_type` VARCHAR(10)
+
+)
+RETURNS TEXT
+LANGUAGE SQL
+NOT DETERMINISTIC
+CONTAINS SQL
+SQL SECURITY DEFINER
+COMMENT ''
+BEGIN
+Set @starttime = starttime;
+Set @endtime = endtime;
+Select time_to_sec(timediff(@endtime,@starttime))/3600 into @maxhoursaday;
+Set @assigneddate = assigneddatetime;  
+Set @timecount = param_elapsedhours;
+Set @timevar1 = @assigneddate;
+Set @nextdate = @assigneddate;
+Set @timevar2 = null;
+Set @param_country = param_country;
+############ Check if the assigned time was before the starttime or closed time was after the endtime provided #############
+Set @checkstart = null;
+Set @checkend = null;
+Set @slatype = sla_type;
+
+Select CONCAT(SUBSTRING_INDEX(@assigneddate, ' ', 1), ' ',@starttime),
+CONCAT(SUBSTRING_INDEX(@assigneddate, ' ', 1), ' ',@endtime)  into @checkstart, @checkend;
+
+if (@slatype = 'H') then
+    if (@assigneddate < @checkstart) then
+        SET @nextdate = @checkstart;    
+    end if;
+else
+    if (@assigneddate < @checkend) then
+        SET @nextdate = @checkstart;
+    end if;
+end if;
+
+Set @count = @timecount;
+while @count>0 do 
+    select weekday(@nextdate) into @weekday; # Assign the weekday value to @weekday. Weekday returns o for Monday, 2 for Tuesday ...5 for Saturday and 6 for Sunday
+     Select sum(if(date_format(holiday_date,'%Y-%m-%d') = substring_index(@nextdate,' ',1),1,0)) from holiday_table 
+     where Country_codes = 'ALL' or instr(Country_codes,@param_country)>0
+     into @holidayflag; #Check if the date stored in nextdate (which is assigneddate on first run of while loop) is a holiday and set the holiday flag
+    if ( @weekday<5 and @holidayflag=0) then #Proceed if the date in nextdate variable is neither weekend nor a holiday
+        if (@count = @timecount) then #Check if it is first run.ie. if nextdate is assigneddate
+            Set @timevar1 = @nextdate; #assign assigndate to variable timevar1
+            SELECT CONCAT(SUBSTRING_INDEX(@nextdate, ' ', 1), ' ',@endtime) INTO @timevar2;#get site closing time on assigned date and store it on to timevar2
+        else
+            Select CONCAT(substring_index(@nextdate,' ',1),' ',@starttime) into @timevar1;
+            SELECT CONCAT(substring_index(@nextdate,' ',1), ' ', @endtime) INTO @timevar2;
+        end if;
+        SELECT LEAST(Greatest(((TIME_TO_SEC(TIMEDIFF(@timevar2, @timevar1))) / 3600),0),@maxhoursaday) INTO @timecounttemp;   
+        Set @count = @count - @timecounttemp;
+    end if;
+
+    if @count > 0 then
+     Select adddate(concat(substring_index(@nextdate,' ',1),' ',@starttime),1) INTO @nextdate;
+     else
+     Select SUBSTRING_INDEX(@nextdate, ' ', 1) INTO @nextdate;
+   end if;
+end while;
+Set @finaldate = null;
+Select concat(@nextdate,' ',substring_index(addtime(@endtime,sec_to_time(3600*@count)),':',2)) INTO @finaldate;
+RETURN @finaldate;
+
+END
+
+

Calling the function

+

Function will expect 6 parameters and with specific format as explained below:

+
    +
  • param_country - This is the country code as specified in holiday table
  • +
  • assigneddatetime - This must be provided in the format %Y-%m-%d %H-%i-%s. So for our example it will be 2018-02-23 15:00:00
  • +
  • param_elapsedhours - This must be provided in decimal hours format.
      +
    • Bear in mind that if SLA Type is not H, you need to understand the number of working hours in allowed number of days in SLA. So for 8 working hours in a day:
        +
      • Same day SLA will have 8 hours
      • +
      • Next Business Day will have 16 hours(2 times 8)
      • +
      • A 2 day SLA will have 24 hours
      • +
      • and so on
      • +
      +
    • +
    +
  • +
  • starttime - This must be in the format %H:%i. So for our example it will be 08:00
  • +
  • endtime - This must be in the format %H:%i. So for our example it will be 16:00
  • +
  • sla_type - This must be 'H' if the sla is based on hours or anything else (say 'D') if SLA is based on days.
  • +
+

The call for this function will be as below:

+

For Hours SLA:

+
Select get_closed_date_given_start_time('ALL','2018-02-23 15:00:00','16','08:00','16:00','H'); --(1)
+
+
    +
  1. This will give an output of 2018-02-27 15:00
  2. +
+

For Next Business Day SLA:

+
Select get_closed_date_given_start_time('ALL','2018-02-23 15:00:00','16','08:00','16:00','D'); --(1)
+
+
    +
  1. This will give an output of 2018-02-26 16:00
  2. +
+

For just calculating next working day date given number of hours:

+
Select get_closed_date_given_start_time('ALL','2018-02-23 15:00:00','10.5','08:00','16:00','H'); --(1)
+
+
    +
  1. This will give an output of 2018-02-27 09:30
  2. +
+ + + + + + + + + + + + + + + + + + + + + +

+ + + + Comments +

+ + + + + + + +
+
+ + + + + +
+ + + +
+ + + +
+
+
+
+ + + + + + + + + + + + \ No newline at end of file diff --git a/2018/05/14/home-networking.html b/2018/05/14/home-networking.html new file mode 100644 index 0000000..a295e3b --- /dev/null +++ b/2018/05/14/home-networking.html @@ -0,0 +1,1769 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + Home Networking - Make Gadgets Work + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + Skip to content + + +
+
+ +
+ + + + +
+ + +
+ +
+ + + + + + + + + +
+
+ + + + + + + + + + + +
+
+ +
+
+ + + +
+
+
+
+ + +
+
+
+
+ + + + + + + + + + + + + + + + + + + + +

Home Networking

+ +

Network routing

+
    +
  1. +

    Router - Router is the device at home that connects all devices in your house to the internet.

    +
      +
    1. It does so by assigning IP address to each of your device but this IP address is only relevant for devices connected to this router. What this means is that if you use an IP address allocated by this router from a coffee shop and you are not connected to your home router then you will not be able to reach the device that IP address belonged to. For this reason these IP addresses are called internal IP addresses.
    2. +
    3. Now what happens is when you try to access a website, your device sends the request to your router, router in turn sends it to the router of your ISP which in turn eventually connects to the server that is hosting the website. The information flows back through same route and your router finally gets the information and then passes it back to your device and it is able to do so because it identifies your device based on the Internal IP address it assigned to your device.
    4. +
    5. By default pretty much all routers are configured to allocate these Internal IP Addresses using DHCP which is just fancy term for allocating a random number to the device. They do also provide the facility to reserve an IP address for a particular device if we tell it to do so - This is called "assigning a static IP on the router"; in technical speak. This is important when we are trying to host service from home based server and is explained next
    6. +
    7. Many ISP provided routers are locked in and it will serve you well to get a router and flash it with an open source firmware like DD-WRT
    8. +
    +
  2. +
  3. +

    Internal LAN set-up - Ensure that on your internal LAN network you assign static IP address to device that will be running the server. If your router allows for LAN Domain to be configured then do so and allocate appropriate LAN Hostname as it makes it easier to access the server from within LAN than just by trying to remember the IP address.

    +
  4. +
  5. +

    Port Forwarding - When the request comes from Internet, it will be with http, ftp, smtp and those kind of headers.

    +
      +
    • These headers operate on standard ports. However for security as well as application related issues, the ports on actual server within your home network can be significantly different.
    • +
    • All this needs to be translated for communication to be completed and application to receive the information it requested.
    • +
    • Port Forwarding at router level is simply us telling router which IP address to pass on the received request based on the port that request is trying to access.
    • +
    • For example http requests are on port 80 so if we want to tell the router that any requests coming on port 80 must be forwarded to a laptop in your house with internal ip address of 192.168.1.44, then port forwarding should be the way to do so.
    • +
    +
  6. +
+

Internet to Home

+

Use of Dynamic DNS Services

+
    +
  1. +

    Ensure that the router is informing your Dynamic DNS provider with latest IP address assigned by your ISP.

    +
      +
    1. Use of duckdns.org or dtdns.com etc
    2. +
    3. Use of DNS-O-Matic to dynamically update the External IP address for your home router as soon as it is changed by the ISP.
    4. +
    +
  2. +
  3. +

    Allocate the Dynamic DNS to DNS name purchased from Domain Name registrar like Namecheap etc.

    +
  4. +
+

Hosting a service from Home Server:

+
+

Question we are first going to answer is:

+

If we want to host on server from home, what do we need to do?

+
+

Pre-requisites

+
    +
  • +

    Ensure you are able to do changes to your router setting that allows you to:

    +
      +
    1. Create port forwarding rules
    2. +
    3. Update the DynamicDNS each time your ISP changes External IP address for your router.
    4. +
    +
  • +
  • +

    We need to decide the operating system and web server we want to use?

    +
      +
    • The request from Internet when it reaches the router it is just dealing in numbers and not strings so it does not really know what information to send back unless it gets it back from the device where the information resides.
    • +
    • In order for the device, that has the requested information, to provide it such that router can send it out, we need to have these web servers, that do the job of translating the information exchange, between router and the device, into a format that the router and eventually the device can understand.
    • +
    • So as an example, if the request was on http protocol, it would mean it is for a web page and router knows that it is for port 80, and will forward it to the IP address you assigned for port 80 - Router is assuming ofcourse that you know what you are doing.
    • +
    • In other words it is your (human) responsibility to ensure that when this request lands on said IP address someone is there to receive it - that someone is the Web Server we are now talking about.
    • +
    +
  • +
+

Web Servers

+

The two commonly used web-servers are:

+
    +
  1. Apache
  2. +
  3. Nginx
  4. +
+

There are other servers too for various other scenarios but the most popular options are the two above.

+
+

Can we have both web servers running on same machine?

+

Yes, the routing of information will be similar to the flow below:

+

Internet -> Home Router -> Primary Server -> Other servers in home network accessed through reverse proxy and connected to world only through Primary Server.

+
+

Operating System

+

Assuming that we decide to go with Apache Server. You will need to install it on your laptop. This brings us to the first controversial topic, that of operating system your laptop is using. It can be anything from Windows, Mac to a range of Linux Distros. As we are talking open source, lets assume it is will be a linux distro but there are so many to chose from, so which one?

+

A safe choice is to opt for Debian based distro as it is easy to start with. In that too, Debian 9 (code named: Stretch) at the time of writing is latest stable version and is most recommended. I will assume for the purpose of this article that the reader will use Debian 9 (Stretch). I will also assume that reader is not aware of working on a Linux environment. Advanced users may find some of the information here obvious or boring as I explain few things at length.

+

Prepare the laptop

+
    +
  • Step 1 - Find out whether the laptop is 32 bit or 64 bit.
  • +
  • Step 2 - Download a copy of the Debian 9 (Stretch) applicable to the specific version of your laptop (32 bit / 64 bit)
  • +
  • Step 3 - Create a Live DVD / USB and test drive the OS
  • +
  • Step 4 - Install it. You will be asked to provide root password, username and user password. Make note of each of these as you will need them later.
  • +
  • Step 5 - Update, Upgrade and Dist Upgrade
  • +
+

Open the terminal of the laptop and type following commands:

+
1
+2
+3
sudo apt update
+sudo apt upgrade
+sudo apt dist-upgrade
+
+ + + + + + + + + + + + + + + + + + + + + + +
+
+ + + + + +
+ + + +
+ + + +
+
+
+
+ + + + + + + + + + + + \ No newline at end of file diff --git a/2018/06/15/prosody-behind-apache-on-debian-stretch.html b/2018/06/15/prosody-behind-apache-on-debian-stretch.html new file mode 100644 index 0000000..7a9dd3e --- /dev/null +++ b/2018/06/15/prosody-behind-apache-on-debian-stretch.html @@ -0,0 +1,2324 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + Prosody behind Apache on Debian Stretch with Conversations - Make Gadgets Work + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + Skip to content + + +
+
+ +
+ + + + +
+ + +
+ +
+ + + + + + + + + +
+
+ + + + + + + + + + + + + + + +
+
+
+
+ + +
+
+
+
+ + + + + + + + + + + + + + + + + + + + +

Prosody behind Apache on Debian Stretch with Conversations

+ +

Genreal Guidance

+

This how-to assumes that:

+
    +
  • User has access to a registered domain - domain.name. Any reference to domain.name throughout the tutorial therefore, must be replaced with your own domain name.
  • +
  • If stuck anywhere, please do get in touch with prosody support chat - They are a very helpful lot and are very kind too.
  • +
+ + +

Network Setup:

+

It is not mandatory to follow the flow of this how-to but if you do, please do it till the end before you try and access the IM server from any client to save yourself some pain.

+

Obtain the IP address

+

.... of the machine where your XMPP server will be installed using following command on the terminal ip -a address On the result shown, IP address against inet family is the one we are interested in as can be seen in screenshot below:

+

PUTTY_2018-06-15_12-49-42

+

Copy this ip address, we will need it in next step.

+

Port Forwarding on router if hosting from home.

+

You have to ensure that following ports are opened and directed to above IP address from your router. 5222, 5269, 5280, 5281 On DD-WRT this can be done as shown in screenshot below. This will, ofcourse, vary based on router you are using.

+

chrome_2018-06-15_11-34-57

+

Use the IP address from previous step to fill the IP address field. Basically this set-up is informing your router that if a request comes to you asking for one of the above mentioned ports direct that request to this IP address on the same port.

+

Create a sub-domain record

+
+

Danger

+

Please note that the official Prosody Documentation recommends using Server Record and even the experts on their chat support strongly recommend that approach.

+

I did not have that option unfortunately - read I was being lazy / adventurous - anyway the C-Name set-up has worked fine for me and hence the guide follows this route.

+

You have been warned !!!

+
+

This step will vary based on the domain name registrar you use. I use Namecheap for which you could do it as shown in screenshot below:

+

chrome_2018-06-15_11-43-31

+

Ensure you have some sort of Dynamic DNS update set-up in place

+

You can create an account here -> DNSOMATIC If you are going to use DNSOMATIC which allows you to update several places like so:

+

chrome_2018-06-15_11-53-35

+

For namecheap in order to get the value for password, login to Namecheap dashboard, go to Advanced DNS and scroll down to \"Dynamic DNS\" section and copy the value in front of the field named \"Dynamic DNS Password" as shown below:

+

chrome_2018-06-15_17-02-32.png

+

The setting to update DNSOMATIC on DD-WRT are as shown below:

+

DYDNS Server:`updates.dnsomatic.com` 
+Hostname: `all.dnsomatic.com` 
+URL: `https://updates.dnsomatic.com/nic/update?hostname=`
+
+chrome_2018-06-15_11-48-32

+

For all other Dynamic DNS set-ups ensure that your subdomain will always get updated with latest IP address to ensure continuity of service.

+

Server Setup:

+

Normally the XMPP clients on phone directly access port 5222 or 5269 for MUC (Multi User Chats aka Group Chats) but to make sure image uploads work and for being able to access IM from web we need to enable reverse proxy using Apache server. As this is fairly straight forward, lets get that out of the way.

+

Apache with reverse Proxy enabled

+

Make directory for subdomain:

+

sudo mkdir /var/www/html/im

+

Then create a server conf file and open for editing in nano using following command:\

+

sudo nano /etc/apache2/sites-available/prosody.conf

+

Paste the following in conf file:

+
 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
<VirtualHost *:80>
+    ServerName im.domain.name
+    ServerAlias im.domain.name
+    DocumentRoot /var/www/html/im
+
+    ErrorLog ${APACHE_LOG_DIR}/error_im.log
+    CustomLog ${APACHE_LOG_DIR}/access_im.log combined
+
+    ProxyPass /http-bind/ http://im.domain.name:5280/http-bind/
+    ProxyPassReverse /http-bind/ http://im.domain.name:5280/http-bind/
+
+
+RewriteEngine on
+RewriteCond %{SERVER_NAME} =im.domain.name
+RewriteRule ^ https://%{SERVER_NAME}%{REQUEST_URI} [END,NE,R=permanent]
+</VirtualHost>
+
+

Save the file with Ctrl+x, y and Enter. Finally enable site and reload server.

+
1
+2
    sudo a2ensite prosody.conf
+    sudo systemctl reload apache2
+
+

Enable SSL set-up using Let's Encrypt

+

It is assumed at this point that certbot is already installed but if not, follow the tutorial here On terminal give the command sudo certbot

+

PUTTY_2018-06-15_12-08-21

+

Then follow the wizard and once completed check that an additional conf file named prosody-le-ssl.conf is created. If not something did not go well so check again and repeat but if yes move to next step.

+

Ensure certbot is renewing automatically

+

Debian stretch gets the service certbot.timer already on it which when enabled will try to autorenew twice a day ensuring a continuous certificate availability. To locate and edit the certbot.timerfollowing commands are helpful:

+

PUTTY_2018-06-15_12-19-30

+

1
+2
locate certbot.timer
+nano /etc/systemd/system/timers.target.wants/certbot.timer
+
+The certbot.timer entry on my system is as below: +PUTTY_2018-06-15_17-23-18

+

Once edited, make sure it is enabled using following commands:

+
1
+2
sudo systemctl enable certbot.timer
+sudo systemctl start certbot.timer
+
+

Prosody XMPP server

+
+

Assumption

+

Apache 2.4 web server is already installed and functioning.

+
+

Install

+

Option 1 (Not Recommended):

+
sudo nano /etc/apt/sources.list`
+
+

Then add this -> deb http://packages.prosody.im/debian stretch mainon the last line.

+

Option 2 (Recommended):

+
echo deb http://packages.prosody.im/debian $(lsb_release -sc) main | sudo tee -a /etc/apt/sources.list
+
+

Above command is recommended because it takes the release of your installed version and adds it directly rather than the hard coded "stretch" in Option 1 which must be changes specific to the version of debian. Now add the key, update repository, upgrade for good measure and then install using the following commands:

+
1
+2
+3
+4
wget https://prosody.im/files/prosody-debian-packages.key -O- | sudo apt-key add
+sudo apt-get update
+sudo apt-get upgrade
+sudo apt-get install prosody
+
+

Install plugins / modules

+

Latest updated documentation is ofcourse on project website but at the time of writing these are the instructions that were followed:

+
1
+2
cd /usr/lib/prosody/
+sudo hg clone https://hg.prosody.im/prosody-modules/ prosody-modules
+
+

To update:

+
1
+2
cd /usr/lib/prosody/prosody-modules/
+sudo hg pull --update
+
+

Import SSL using certbot

+

Issue following commands to get the SSL activated for Prosody:

+
1
+2
+3
sudo certbot renew --deploy-hook "prosodyctl --root cert import /etc/letsencrypt/live"
+sudo prosodyctl --root cert import /etc/letsencrypt/live
+sudo prosodyctl cert generate localhost 
+
+

Configure

+

The configuration file for prosody is very well documented and only needs minor tweaks. However, it is lot of commenting and to keep this readable, I have put my configuration here that can either be directly pasted or can be compared to update the default conf file. Either way, first you would need to open the config file:

+

sudo nano /etc/prosody/prosody.cfg.lua

+

Now the working config file for me is as shown below and can be compared to amend or can be directly copy pasted.

+

Bear in mind that all references to domain.name must be replaced with your registered domain name.

+
 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
+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
+52
+53
+54
+55
+56
+57
+58
+59
+60
+61
+62
+63
+64
+65
+66
+67
+68
+69
+70
+71
+72
+73
+74
+75
+76
+77
+78
+79
+80
+81
+82
+83
+84
+85
+86
+87
+88
+89
+90
+91
+92
+93
+94
admins = {"admin1@im.domain.name"} -- (1)
+
+plugin_paths = {"/usr/lib/prosody/prosody-modules" }
+consider_bosh_secure = true
+cross_domain_bosh = true
+modules_enabled = {
+
+        -- Generally required
+                "roster"; -- Allow users to have a roster. Recommended ;)
+                "saslauth"; -- Authentication for clients and servers. Recommended if you want to log in.
+                "tls"; -- Add support for secure TLS on c2s/s2s connections
+                "dialback"; -- s2s dialback support
+                "disco"; -- Service discovery
+
+        -- Not essential, but recommended
+                "carbons"; -- Keep multiple clients in sync
+                "pep"; -- Enables users to publish the1ir mood, activity, playing music and more
+                "omemo_all_access"; -- xep-0060 for enabling omemo access to non subscribers
+                "private"; -- Private XML storage (for room bookmarks, etc.)
+                "blocklist"; -- Allow users to block communications with other users
+                "vcard"; -- Allow users to set vCards
+
+        -- Nice to have
+                "version"; -- Replies to server version requests
+                "uptime"; -- Report how long server has been running
+                "time"; -- Let others know the time here on this server
+                "ping"; -- Replies to XMPP pings with pongs
+                "register"; -- Allow users to register on this server using a client and change passwords
+                "mam"; -- Store messages in an archive and allow users to access it
+                --"mam_muc"; -- store group chat messages
+
+        -- Admin interfaces
+                "admin_adhoc"; -- Allows administration via an XMPP client that supports ad-hoc commands
+                --"admin_telnet"; -- Opens telnet console interface on localhost port 5582
+
+        -- HTTP modules
+                "bosh"; -- Enable BOSH clients, aka "Jabber over HTTP"
+                "websocket"; -- XMPP over WebSockets
+                "http_files"; -- Serve static files from a directory over HTTP
+                "http_upload"; -- module to enable file upload in group chat
+
+        -- Other specific functionality
+                "groups"; -- Shared roster support
+                "proxy65"; -- Enables a file transfer proxy service which clients behind NAT can use
+                "smacks";
+                "csi";
+                "cloud_notify";
+                "conversejs";
+}
+
+modules_disabled = {
+        -- "offline"; -- Store offline messages
+        -- "c2s"; -- Handle client connections
+        -- "s2s"; -- Handle server-to-server connections
+        -- "posix"; -- POSIX functionality, sends server to background, enables syslog, etc.
+}
+
+allow_registration = false
+
+c2s_require_encryption = false
+
+s2s_require_encryption = true
+
+s2s_secure_auth = true
+
+s2s_secure_domains = { "domain.name" } -- (2)
+
+pidfile = "/var/run/prosody/prosody.pid"
+
+authentication = "internal_hashed"
+
+archive_expires_after = "1w" -- Remove archived messages after 1 week
+
+log = {
+        info = "/var/log/prosody/prosody.log"; -- Change 'info' to 'debug' for verbose logging
+        error = "/var/log/prosody/prosody.err";
+        -- "*syslog"; -- Uncomment this for logging to syslog
+        -- "*console"; -- Log to the console, useful for debugging with daemonize=false
+}
+
+certificates = "certs"
+
+----------- Virtual hosts -----------
+-- You need to add a VirtualHost entry for each domain you wish Prosody to serve.
+-- Settings under each VirtualHost entry apply *only* to that host.
+
+VirtualHost "localhost"
+
+VirtualHost "im.domain.name" -- (3)
+
+------ Components ------
+---Set up a MUC (multi-user chat) room server on conference.example.com:
+    Component "conference.im.domain.name" "muc" -- (4)
+        restrict_room_creation = "local"
+
+
    +
  1. 💡 Replace with your registered domain name.
  2. +
  3. 💡 Replace with your registered domain name.
  4. +
  5. 💡 Replace with your registered domain name.
  6. +
  7. 💡 Replace with your registered domain name.
  8. +
+

Once completed, save it and proceed to next steps.

+
1
+2
+3
+4
+5
+6
+7
+8
+9
sudo service prosody start
+sudo service prosody stop
+sudo service prosody start -v
+sudo service prosody stop
+
+## Enable prosody as a service so it restarts each time after system reboot
+
+sudo systemctl is-enabled prosody.service
+sudo service prosody restart
+
+

Add User

+

Use prosodyctl command to add user like so:

+

sudo prosodyctl adduser admin1@im.domain.name

+

and then provide password for the user. Ofcourse you must add as many users as you need and bear in mind the conf file above has registration switched-off so the users must be manually added using this step. Check the manpage using man prosodyctl to see how to update password for an existing user and to delete a user.

+

Clients Setup

+

Conversations on Android

+

Conversations is the best client for XMPP on Android and is available on playstore for a small amount or it can be installed from f-droid for free. The free version has some limitations with regards to push but I have been using it with no issues what-so-ever.

+

OpenPGP using OpenKeyChain

+

Install the OpenKeyChain app, create the key pair and trigger Conversations to start from OpenKeyChain. The other person must also have OpenPGP Key pair and you should have the public key of other person added on openkeychain instaled on your phone. Do bear in mind Conversations developer says it is an experimental feature. Although it did work perfectly fine for me while I tested with about 4 or 5 users.

+

Conversejs.org on web

+

Go to the web root we defined initially and create an index.html:

+

sudo nano /var/www/html/im/index.html

+

Then add following html code in there:

+

Bear in mind that all references to domain.name must be replaced with your registered domain name.

+
 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
+26
+27
+28
+29
+30
+31
    <!doctype html>
+    <html class="no-js" lang="en">
+    <head>
+        <meta charset="utf-8"/>
+        <meta name="viewport" content="width=device-width, initial-scale=1.0"/>
+        <title>Converse</title>
+        <link rel="shortcut icon" type="image/ico" href="css/images/favicon.ico"/>
+        <link type="text/css" rel="stylesheet" media="screen" href="https://cdn.conversejs.org/3.3.4/css/converse.min.css" />
+        <link type="text/css" rel="stylesheet" media="screen" href="https://cdn.conversejs.org/3.3.4/css/mobile.min.css" />
+        <script src="https://cdn.conversejs.org/3.3.4/dist/converse.min.js"></script>
+    </head>
+    <body class="reset">
+        <div class="content">
+            <div class="inner-content">
+                <h1 class="brand-heading"><i class="icon-conversejs"></i> Converse</h1>
+            </div>
+        </div>
+    <script>
+        converse.initialize({
+            authentication: 'login',
+            auto_away: 300,
+            auto_reconnect: true,
+            bosh_service_url: 'https://im.domain.name/http-bind/', //<!-- (1) -->
+            show_controlbox_by_default: true,
+            message_archiving: 'always',
+            allow_logout: true,
+            allow_dragresize: true
+        });
+    </script>
+    </body>
+    </html>
+
+
    +
  1. 💡 Replace with your registered domain name.
  2. +
+

Save the file with Ctrl+X, ++Y++ and ++Enter++. Once done restart the webserver as well as prosody for good measure:

+
1
+2
sudo systemctl restart apache2.service
+sudo prosodyctl restart
+
+

ChatSecure on iOS

+

Now I do not have an iphone but a few of my friends do. They were able to use ChatSecure which is also an opensource app. Unfortunately ChatSecure is unable to get the images on group chat from Conversations but I am hoping that developer of ChatSecure will fix this issue sooner rather than later.

+

Desktop Client

+

For desktop I found Gajim to be most polished and it can be downloaded for your OS from here All works great on it except for the http_upload which seems to fail for transferring images with an error "Unsecured". I am not sure what the issue is a search results did not help much - I left it because I dont really need this feature from desktop. However if someone has any ideas on how to fix this, please do leave it in the comments. The plugins on Gajim that I have applied and activated can be seen below:

+

Gajim_2018-06-15_17-10-11.png

+

Maintenance

+

The service does generate a lot of logs and it will serve you well to delete generated logs daily using Cronjob like so

+
sudo crontab -e
+
+

Then add the following on the cronjob:

+
1
+2
+3
############## DELETE PROSODY LOG FILE ARCHIVES ###########################
+MAILTO="your@email.com"
+0 0 * * 0-6 rm  /var/log/prosody/*.gz
+
+ + + + + + + + + + + + + + + + + +
+ Comments from Disqus +

As I plan to move away from disqus comment system, I am just including the screenshots of comments just so any information shared by or to the readers is retained.

+

+ Disqus Comments + Disqus Comments

+
+ + + + + + +
+
+ + + + + +
+ + + +
+ + + +
+
+
+
+ + + + + + + + + + + + \ No newline at end of file diff --git a/2018/12/05/metabase-a-bi-solution-that-just-works.html b/2018/12/05/metabase-a-bi-solution-that-just-works.html new file mode 100644 index 0000000..dfa75be --- /dev/null +++ b/2018/12/05/metabase-a-bi-solution-that-just-works.html @@ -0,0 +1,1824 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + Metabase - A BI solution that just works - Make Gadgets Work + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + Skip to content + + +
+
+ +
+ + + + +
+ + +
+ +
+ + + + + + + + + +
+
+ + + + + + + + + + + + + + + +
+
+
+
+ + +
+
+
+
+ + + + + + + + + + + + + + + + + + + + +

Metabase - A BI solution that just works

+ +

I like exploring new solutions and anything to do with data always piques my interest. I came across this nice tool through the list of free self hosted software.

+
+

UPDATE

+

I am still getting introduced to the world of docker and while most of it just works few things confuse the hell out of me.

+

Anyway what happened is there was an update for Metabase and I wanted to apply it on my container and in doing so I realised that some of this post can be changed to make it a better experience from get go because after update all my config and dashboards were gone and I had to reconfigure everything.

+

It could be that my lack of knowledge meant I did something wrong and faced the issue but irrespective doing it as per updated post below will ensure you dont face the issue at all because I have now tested it twice to ensure it works well.

+
+ + +

I have also added the steps to update the metabase to latest docker image as it worked for me.

+

The overall impression when I opened their site can be summed up with WOW!! Now they offer simple docker install supported officially by them and the guide to install docker image is simple enough but the few +simple steps I followed are as below:

+
    +
  1. Create an A-record for a subdomain for metabase
  2. +
  3. Download the official metabse docker image from dockerhub
  4. +
  5. Create an apache server conf file for metabase
  6. +
  7. Run the metabase docker image
  8. +
  9. Access metabase docker image using subdomain URL
  10. +
  11. Configure your database on metabase
  12. +
  13. Run Certbot to make the URL https
  14. +
  15. Done!!
  16. +
+

Create A-record for subdomain

+

Using the process for your Domain Name Regsitrar create an A-Record for +a subdomain you would want to use for metabase. For this guide we will +assume that the subdomain being created is called "metabase" so if +your domain name is say yourdomain.com then the URL to access metabase +will be metabase .yourdomain .com.

+
+

Note

+

It is important that if you use a different subdomain name, please replace metabase with the subdomain name you have chosen in all Apache conf file samples in sections below.

+
+

More detailed steps are available on previous post HERE

+

Download and Create Apache Conf

+

1
+2
+3
docker pull metabase/metabase
+cd /etc/apache2/sites-available/
+sudo nano metabase.conf
+
+Create a conf file similar to what is shown below:

+

 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
<VirtualHost *:80>
+    ServerName metabase.yourdomain.com
+    ServerAlias metabase.yourdomain.com
+
+    ErrorLog ${APACHE_LOG_DIR}/error_metabase.log
+    CustomLog ${APACHE_LOG_DIR}/access_metabase.log combined
+
+    ProxyPreserveHost On
+    ProxyPass / http://localhost:3000/
+    ProxyPassReverse / http:/localhost:3000/
+    ProxyPass "/ws2/"  "ws://localhost:3000/"
+    ProxyPass "/wss2/" "wss://localhost:3000/"        
+ </VirtualHost>
+
+Save this by pressing Ctrl+X and issue following commands to enable site and reload the apache server.

+
1
+2
 sudo a2ensite metabase.conf
+ sudo systemctl reload apache2
+
+

Run Metabase docker image

+

docker run -d -p 3000:3000 --name metabase metabase/metabase
+
+If all goes well, you should have metabase already running. If you get an error to the effect saying port 3000 is already in use, you will perhaps have to run it on another port (say 12345) but as metabase container would have been created you will need to issue following commands.

+

1
+2
docker rm metabase
+docker run -d -p 12345:3000 --name metabase metabase/metabase
+
+In addition to avoid running the database through the container we must anyway move it to filesystem. Using commands below:

+

mkdir /var/www/metabase
+
+This command will create metabase directory on the path /var/www/metabase but you can as easily chose another path as you wish. If you do, just remember to update it in next commands.

+

docker ps
+
+This will show you the active docker containers. Copy the container ID in first column for the row where under Names you see "metabase" which is the container name we have given using previosu commands.Now copy the +database from container to local filesystem.

+
1
+2
cd /var/www/metabase
+docker cp <container ID>:/metabase.db ./
+
+

Now Stop and rename the container

+
1
+2
docker stop metabase
+docker rename metabase metabase_old
+
+

Finally run Metabase Docker Image while pointing it to your filesystem for database file.

+

docker run -d -p 12345:3000 -v ~/metabase-data:/var/www/metabase -e "MB_DB_FILE=/var/www/metabase/metabase.db" --name metabase metabase/metabase
+
+The above docker command can be explained as below:

+

docker run -d- This is telling system to start a container in detached mode. By design, containers started in detached mode exit when the root process used to run the container exits.

+

-p 12345:3000 - This is telling docker to make the container map port 12345 to port 3000 which is the default port on which metabase starts its server.

+

-v ~/metabase-data:/var/www/metabase- This part is telling docker to map the "metabase-data" directory in the container to "/var/www/metabase" on the filesystem. In other words, mounting the local volume at path "/var/www/metabase" onto the container in "metabase-data" directory.

+

--name metabase metabase/metabase- Finally this part of command is telling docker to name the container "metabase" and to use the docker image "metabase/metabase" from dockerhub

+

Do bear in mind if you change the port as explained above, you will also need to change the port in apache conf file in previous step. So your apache conf file in this case will look as shown below: +

 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
    <VirtualHost *:80>
+        ServerName metabase.yourdomain.com
+        ServerAlias metabase.yourdomain.com
+
+        ErrorLog ${APACHE_LOG_DIR}/error_metabase.log
+        CustomLog ${APACHE_LOG_DIR}/access_metabase.log combined
+
+        ProxyPreserveHost On
+        ProxyPass / http://localhost:12345/
+        ProxyPassReverse / http:/localhost:12345/
+        ProxyPass "/ws2/"  "ws://localhost:12345/"
+        ProxyPass "/wss2/" "wss://localhost:12345/"
+
+    </VirtualHost>
+

+

Access and configure on browser

+
+

Note

+

Access your metabase using the subdomain url - http:// metabase.yourdomain .com and it should walk you through the initial configuration. If you do not want to add a database and just play with the tool, they have kindly provided a sample database.

+
+

It might be useful for you to create a "user" in your database that has "read only" access and then use that "user" when you configure metabase. Metabase will only ever need read access as it cannot be used to modify data in your database.

+

Enable SSL

+

Once you are happy with your configuration from step above, just run the certbot to enable https using letsencrypt certificate. On Debian this could be as simple as issuing the command

+

sudo certbot
+
+Once above step is created you will have an additional conf file named metabase-le-ssl.conf created in the location /etc/apache2/sites-available. Check this and once it is there you are all set to access your new functional metabase instance on https. More detailed steps are available on previous post HERE

+

Update version

+

Updating on docker image is pretty straight forward and provided you have followed steps above should not result in you loosing all the effort once update has completed.

+
1
+2
+3
+4
+5
+6
+7
#docker rm metabase_old
+docker stop metabase
+
+#docker rename metabase metabase_old
+docker pull metabase/metabase
+
+docker start metabase
+
+

From my recent messing around, I have come to realise that so long as the first start of the docker image was done using environment variables pointing to correct database directory, there is no need to run the commented lines above and a simple stop, upgrade and start should work just fine. Enjoy!!!

+ + + + + + + + + + + + + + + + + + + + + + +
+
+ + + + + +
+ + + +
+ + + +
+
+
+
+ + + + + + + + + + + + \ No newline at end of file diff --git a/2020/05/19/converting-aax-audible-to-mp3.html b/2020/05/19/converting-aax-audible-to-mp3.html new file mode 100644 index 0000000..96b6e19 --- /dev/null +++ b/2020/05/19/converting-aax-audible-to-mp3.html @@ -0,0 +1,1640 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + Converting AAX (Audible) to mp3 - Make Gadgets Work + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+
+ +
+ + + + +
+ + +
+ +
+ + + + + + + + + +
+
+ + + + + + + + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+
+ + +
+
+
+
+ + + + + + + + + + + + + + + + + + + + +

Converting AAX (Audible) to mp3

+ +

Lately I have been a bit frustrated because while I subscribe to Audible services, Amazon and Google do not play nice with each other limiting me - the owner of books with rights to listen my purchase on any device - in my options on where I can listen. Now I am yet to find a good solution that can get my files playing on google home and not just Alexa, I guess first step was to free my audiobooks from Amazon jail. That as it turns out is rather simple to do.I must at this point mention OpenAudible which is one fine tool. However, their latest version (2.0.7 at the time of writing) added some limitations whereby you can get it to download the AAX files but not convert them to mp3 without purchasing their license. However,converting AAX to mp3 is accomplished using ffmpeg and I do not believe a nice GUI wrapper is what I need and hence I do not believe my money is well spent in getting a license just to achieve from a graphical user interface what I can achieve for free from the terminal with a single line.There are 3 steps in the conversion process:

+ + +
    +
  1. +

    Get a copy of the AAX file you own from your audible account: This + can be done in one of two ways as listed below:

    +

    a. Install OpenAudible and follow instructions to download the AAX +files.
    +b. Copy it from the filesystem of your Android phone.

    +
  2. +
  3. +

    Obtain the activation_bytes for your AAX file: + a. If you installed OpenAudible, you can use the following steps: +

    1
    +2
    cd /opt/OpenAudible/bin/linux/
    +ffprobe /home/OpenAudible/aax/name_of_the_book.AAX 
    +
    + b. This should give you a lot of output and a hash code. Let's say + the hashcode you get is a6ger35cf22u03c9x16743j06f6df50a00b811c3 + aax_hash_code + c. Use this hashcode with following command to get the activation_bytes: +
    ./rcrack path /opt/OpenAudible/bin/tables/ -h a6ger35cf22u03c9x16743j06f6df50a00b811c3
    +
    + d. This should then output the activation_bytes in last line - something like hex:abc43opu as shown below: + activation_byte

    +
    +

    Info

    +

    Above steps are required only once. Keep the activation_bytes code that you obtain above safe as it can be used for converting all your audiobooks.

    +
    +
  4. +
  5. +

    Now use the following commands to convert AAX file to mp3: +

    1
    +2
    +3
    #change directory to where the AAX files are saved. If using OpenAudible it will be in /home/<username>/OpenAudible/aax
    +cd /home/<username>/OpenAudible/aax
    +ffmpeg -activation_bytes xxxxxxxx -i 'name of the book.AAX' -map_metadata 0 -codec:a libmp3lame -qscale:a 6 /home/<username>/OpenAudible/mp3/name\ of\ the\ book.mp3
    +

    +
    +

    Note

    +

    Remember to change xxxxxxxx with the activation_bytes code you obtained in step 2 and the name of book as well as destination path and name of the book.

    +
    +
  6. +
+

Now ofcourse you can accomplish first the Step 2 above without OpenAudible. If you would want to do so, just follow the guidance on github.

+

Hope this will be helpful for a few. In the meantime, I am yet to work out the best way to make google home to play my audiobooks when I so desire. If anyone has any tips they will be gratefully received.Have fun !!!

+ + + + + + + + + + + + + + + + + + + + + + +
+
+ + + + + +
+ + + +
+ + + +
+
+
+
+ + + + + + + + + + + + \ No newline at end of file diff --git a/2021/02/10/upgrading-php-version-on-linux-for-apache.html b/2021/02/10/upgrading-php-version-on-linux-for-apache.html new file mode 100644 index 0000000..b4792cb --- /dev/null +++ b/2021/02/10/upgrading-php-version-on-linux-for-apache.html @@ -0,0 +1,1630 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + Upgrading PHP version on Linux for Apache - Make Gadgets Work + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+
+ +
+ + + + +
+ + +
+ +
+ + + + + + + + + +
+
+ + + + + + + + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+
+ + +
+
+
+
+ + + + + + + + + + + + + + + + + + + + +

Upgrading PHP version on Linux for Apache

+ +
    +
  1. +

    Install the Apache module for specific php version

    +
    sudo apt install libapache2-mod-php7.3
    +
    +
  2. +
+ + +
    +
  1. +

    Copy the php.ini from previous version to newer version after making + a backup of the original php.ini for new version.

    +
    1
    +2
    sudo cp /etc/php/7.3/apache2/php.ini php.ini.original
    +sudo cp /etc/php/7.2/apache2/php.ini /etc/php/7.3/apache2/php.ini
    +
    +
  2. +
  3. +

    Install specific php modules for Apache and enable the php modules + on new version of php.

    +
    1
    +2
    sudo apt install php7.3-curl php7.3-gd php7.3-gmp php7.3-intl php7.3-mbstring php7.3-simplexml php7.3-soap php7.3-wddx php7.3-xmlreader php7.3-xmlrpc php7.3-xmlwriter php7.3-xsl php7.3-zip php7.3-xml php7.3-mysql
    +sudo phpenmod -v 7.3 pdo_mysql soap wddx xmlreader xmlrpc xsl zip intl gd dom curl mysqlnd gmp simplexml mysqli mbstring
    +
    +
  4. +
  5. +

    Disable old php version, enable new version and restart Apache + server.

    +
    1
    +2
    +3
    sudo a2dismod php7.2
    +sudo a2enmod php7.3
    +sudo systemctl restart apache2
    +
    +
  6. +
+ + + + + + + + + + + + + + + + + + + + + + +
+
+ + + + + +
+ + + +
+ + + +
+
+
+
+ + + + + + + + + + + + \ No newline at end of file diff --git a/2021/03/05/tech-for-diabetics.html b/2021/03/05/tech-for-diabetics.html new file mode 100644 index 0000000..455616d --- /dev/null +++ b/2021/03/05/tech-for-diabetics.html @@ -0,0 +1,1855 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + Tech for Diabetics - Make Gadgets Work + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + Skip to content + + +
+
+ +
+ + + + +
+ + +
+ +
+ + + + + + + + + +
+
+ + + + + + + + + + + + + + + +
+
+
+
+ + +
+
+
+
+ + + + + + + + + + + + + + + + + + + + +

Tech for Diabetics

+ +

Background

+

As a diabetic there are a number of things we are not able to control but one thing we can do is keep tabs on our data and while in past it would have meant meticulously noting down BG readings in a diary, today it is much much easier with all the apps and connected services. In this article, I aim to go through the list of technologies that I am aware of and use to keep on top of my diabetic data right from my phone.

+ + +

Blood Glucose Meter

+

While there are a number of options available, I settled for CareSens Dual. The biggest advantage with this model is that it plays very nicely with xdrip+ app although lately I am relying more on Diabox so the point is kind of moot. Apart from that, one factor we must bear in mind while chosing the device is not just the cost of the device itself but also that of testing strips as it will be a recurring expense and this device has one of the most reasonably priced testing strips.

+

Freestyle Libre 2 (FSL2)

+

FSL2 became available in the UK only from Jan'21 and this or it's previous version allow for a more regular check of the BG trends. FSL1 +in my experience was a bit less accurate than FSL2. However these sensors along with the official app from manufacturer require manual +scan, don't give all datapoints and can not be calibrated which is again where xdrip+ / Diabox apps come into the picture.

+
+

Freestyle Libre 1 (FSL1) with Transmitter (Miaomiao / Bubble)

+

Before Jan'21, in the UK we only had the option of FSL1 and as this version does not have bluetooth communication; the possibility to calibrate and get automated data points required usage of a transmitter device such as Miaomiao or Bubble. I ordered a Miaomiao transmitter back then and I still can use it with FSL2 albiet with slightly modified approach. I do it because I have the device but with FSL2 a transmitter is not needed as explained later.

+
+

Freestyle Libre App

+

Now the fact is I dont really use this app for anything other than activating the sensor but it is for this reason alone an app you can not avoid or do without. In order to use the sensors to feed data directly and automatically to Diabox the sensor at the time of activation should not be paired through bluetooth to this app. Now there are several ways to achieve this. One is to install the app on a completely separate phone and turn off Bluetooth on that phone. Another is just do not give this app the "Location" permission which would then not allow app access to Bluetooth. Either scenario should work in theory but I use the safest approach of activating sensor through this app on a compltely different phone (that of my daughter actually 😄)

+

Diabox (Especially with FSL2)

+

I have been using this app only for 10 days at the time of writing but it works really well with FSL2 and as it gets the data directly from the sensor it removes the additional cost of the transmitter and that makes this a very useful app indeed. The app is available or both iOS as well as Android but from what I gathered at some point Abbot Laboratories (Manufacturers of FSL sensor) complained about he app to Google and so google was obligated to takedown this app from playstore. App therefore is available as an apk file on github and the latest release for it can be downloaded from this GitHub Link The developer has a Diabox Dev website with lot more information on user interface and setup etc. but I will explain my setup below.

+
+
    +
  1. +

    Download the latest version of the app from GitHub Link

    +
  2. +
  3. +

    Install the app - As it is not from playstore, the permission to install from unknown sources1 should be activated in Android Settings.

    +
  4. +
  5. +

    Once installed, open the app. It will ask to scan the sensor. Once you scan, the phone will automatically pair the sensor with the Bluetooth of the Libre.

    +
  6. +
  7. +

    Now to enable calibration tap on the sensor image as shown in screenshot below:

    +

    adfd44c4c0cf4e91aeeb8792970dbf67

    +
  8. +
  9. +

    Now tap 5 times on the text "Factory" in the last entry "Calibration Mode" as shown below:

    +

    00aec12e25c74fed8e124e788850090e

    +
  10. +
  11. +

    This will present a dialogbox "Sensor Magic Code". Type "GODMODE" in the dialogbox field and press "OK".

    +
  12. +
  13. +

    This should activate the calibration mode and screen should look as shown below:

    +

    4aa44c106c724c16b27129ccc3acfb3a

    +
  14. +
  15. +

    Go back to homepage and you will now see a red coloured circular button with "+" symbol on it as you can see on the first screenshot above.

    +
  16. +
  17. +

    Tapping on the add button will reveal an icon with a glucometer as shown below:

    +

    d417b24684524a05985e737b656b55a9

    +
  18. +
  19. +

    Tapping on the glucometer icon will open a sliding scale to provide the calibration value.

    +

    8f671fb4efa542d08bdf60c1ead2756d

    +
  20. +
  21. +

    Add your calibration and click on Save button.

    +
  22. +
+
+

Now, the app also allows uploading the data it collects to LibreView and Nighscout. I am not so keen on LibreView web interface so I never tried uploading on that service but have it syncing with Nightscout. For Nightscout sync, you will need to have a Nightscout instance up and running2 but assuming it is in place, the steps will be simple to follow:

+
    +
  1. Click on "Settings"
  2. +
  3. Click on "Integration"
  4. +
  5. Enable "Nightscout Share Server Upload"
  6. +
  7. Enter the URL for Nightscout instance, something like https://your_nightscout.herokuapp.com/api/v1/
  8. +
  9. Enter the password for your instance.
  10. +
  11. Click on Connect Test
  12. +
  13. If the test is a success, click on Save button.
  14. +
+

Nightscout

+

Nightscout is a fairly detailed open source solution with loads and loads of functionalities but as a type 2 user I merely use it as a +backup of my data.

+
+

Danger

+

One key thing to note is that once you setup this server your BG data is available to anyone with access to your URL as there is no password block or similar which in my eyes is no big deal. What can anyone do with just my blood glucose data and how or why will they get access to an obscure URL anyway but if this makes you worried perhaps you can skip this part. If this is not of concern then I must say that while the setup guide2 looks very long and complex, it really isn't. They have taken care to make it so very simple that its infact easier than even completing the registration form for some of the online services we have got accustomed to and it will just work.

+
+

To set it up you can follow the official set-up and installation guide.

+

Diabetes:M

+

Diabetes-M is a very good app and creates some very useful and pretty reports that can be helpful during discussion with Diabetic Nurse / GP. One big advantage of using this app is that it is able to import data from Nightscout directly and keep it in sync every 15 minutes and then that data is also backed-up in Diabetes-M servers. The app also allows for automatic data sync with a number of popular Glucometers but that is a paid feature and I do not use it so I cannot comment on its reliability. For my usecase I anyway calibrate Diabox each time manually when I take the reading from finger prick and that is synched to Nightscout which is synched to Diabetes-M so the data is anyway there when I need it. Steps to sync with Nightscout are as below:

+
    +
  1. +

    Open your nightscout instance in a browser on a laptop / PC .

    +
  2. +
  3. +

    Click on the site in sequence as shown below:

    +

    2707fa8797d94ed08f1d6c44fc04ef85

    +
  4. +
  5. +

    On resulting screen - Administration panel, check if the role "readable" exists.

    +

    cc9cdbfe94474be4b87515de72d9f98c

    +
  6. +
  7. +

    If not create it by clicking on the button Add new Role and in "Permissions" type *.*.read as shown below.

    +

    053fbb025cc74ebc880f729d3dc3b6b2

    +
  8. +
  9. +

    If it exists, go to the section "Subjects - People, Devices, etc" and click on "Add new Subject" and fill the resulting dialogbox like shown below and click Save:

    +

    64ed999845d644239615c3b0e2aab454

    +
  10. +
  11. +

    This will generate the Access Token for the app and it will be shown in the table in the section:

    +

    8c623ef74cdc41d8a6ba5fd05e30b32d

    +
  12. +
  13. +

    Open the Diabetes:M app and click on the hamburger menu on top left corner of the screen. Then on the panel locate and tap on the Data Sync icon.

    +

    5229542f7a364b07bbed0356ec8f3565

    +
  14. +
  15. +

    On resulting screen under section "Link external sources" locate Nightscout and click on setting icon.

    +

    4c718c71ade84cb7a2c9eeb3596e32c1

    +
  16. +
  17. +

    Now fill the URL for your nightscout instance, something like https://your_nightscout.herokuapp.com

    +
  18. +
  19. +

    Leave "Secret" empty and in in the field "Access token" type the access token shown generated in step 6 above from the browser and click Save.

    +
  20. +
+

Direct links to appstore:

+
    +
  1. Google Playstore
  2. +
  3. Apple Appstore
  4. +
+

XDRIP+

+

xdrip+ is an opensource Android app (an iOS variant exists but I havent read much about it so have no knowledge to share) and as it is not available from playstore it can be obtained directly from developer site: https://jamorham.github.io/#xdrip-plus To use xdrip, you will also need to order a transmitter called miaomiao - It usually comes within 2 to 3 days - for me atleast it came in 2 days back in October when I started using FSL1. It is a bit of investment at about £162 but it pays for itself in long run. I ordered from their +site3. Once you get the transmitter, the process will be like so:

+
    +
  1. It will come with a USB charging cable so place it on charger. It should start blinking blue but if it does not there is a very small hole and you will need a safety pin to press and reset it. Insert the safety pin and keep it pressed for 10 seconds. Then place it on charge.
  2. +
  3. Once the transmitter is charged it will display green light.
  4. +
  5. Now there is also a strap they provide with the transmitter which you can use to place it over the libre sensor on your arm.
  6. +
  7. Open xdrip app and follow the xdrip+ installation guide4.
  8. +
+

Once connected, it will ask you to calibrate the app with readings from two finger prick tests. You can do one and provide twice or you can do twice. I usually do it twice for each new sensor.

+

Tidepool

+

This is a not-for-profit site which xdrip+ can directly communicate with and push the readings to every 15 minutes thus creating a backup for your data in the cloud. As I still use the miaomiao transmitter paired with xdrip+ I have created a sync from xdrip+ to tidepool. Now the good thing with tidepool is that it shows glucometer readings in a separate table and while for Diabox I have to manually enter the finger prick test data, Caresens Dual is paired perfectly with xdrip and every single time I take a finger prick test, xdrip+ automatically fetches the reading from meter via Bluetooth and then that reading is fed into Tidepool which allows me to see my BG data at one place like so:

+

tidepool

+

Eufy Smartscales

+

Eufy smartscale is very reasonably priced and I ordered it from Amazon. The scale is reliable, uses standard 3x "AAA" battery which last for years not months. The app interface is good too. One drawback perhaps is that data is only accessible through app on the phone and not through any website. It does sync weight with google fit so that can be accessed and shared across other services if one is so inclined but rest of it will need to be manually captured for any statistical fun. However, if I knew before what I know now I would have perhaps opted for a device from this list as then I could have had my data collected through the open source app openScale and would have had better freedom on how to move it around. Hope this list is useful. If there are any questions, please do not hesitate to ask. 😄

+
+ + + + + + + + + + + + + + + + + + + + + + + +
+
+ + + + + +
+ + + +
+ + + +
+
+
+
+ + + + + + + + + + + + \ No newline at end of file diff --git a/2023/01/25/jupyterlite-on-github-enterprise-with-panel-enabled.html b/2023/01/25/jupyterlite-on-github-enterprise-with-panel-enabled.html new file mode 100644 index 0000000..f1f770d --- /dev/null +++ b/2023/01/25/jupyterlite-on-github-enterprise-with-panel-enabled.html @@ -0,0 +1,1812 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + JupyterLite on Github Enterprise with Panel enabled - Make Gadgets Work + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + Skip to content + + +
+
+ +
+ + + + +
+ + +
+ +
+ + + + + + + + + +
+
+ + + + + + + + + + + + + + + +
+
+
+
+ + +
+
+
+
+ + + + + + + + + + + + + + + + + + + + +

JupyterLite on Github Enterprise with Panel enabled

+

Recently I found myself in a scenario where I had access to Github Enterprise but was limited to a windows machine. Now I wanted to host JupyterLite on Github Enterprise so it can be used by some other people on my team. Challenge was the "Actions" on this instance of Github enterprise were disabled and so I had to build Jupyterlite locally and push it on Github. I figured while at it, I might as well enable the "Panel" and few other dependencies. The steps that worked for me are documented here. This is ofcourse assuming that Python is already installed on the device.

+

The final hosted JupyterLite can be seen in action here.

+

The steps below created a Jupyterlite instance that allows use of Panel using the %pip install panel magic command, and if used on Chrome or its derivatives it also allows exploring local file system.

+ + +

Create and activate Jupyterlite Environment

+
1
+2
+3
+4
+5
mkdir panel_lite
+cd .\panel_lite\
+py -m venv panel_v
+.\panel_v\Scripts\activate
+pip install --upgrade pip
+
+

Install jupyterlite and other extensions

+
1
+2
pip install --pre jupyterlite
+pip install jupyter-bokeh ipython ipywidgets jupyterlab-drawio jupyterlab-markup jupyterlab-myst jupyterlab-pygments jupyterlite-p5-kernel jupyterlite-xeus-sqlite libarchive-c matplotlib matplotlib-inline matplotlib-venn myst-nb myst-parser nbconvert numpy openpyxl pandas pandocfilters pkginfo pyopenssl python-dateutil python-dotenv pyviz-comms pyxlsb scipy sql SQLAlchemy sqlparse tornado widgetsnbextension xlrd XlsxWriter zipp jupyterlab-filesystem-access
+
+

Prepare build directory

+
1
+2
+3
+4
+5
+6
mkdir jupyterlite_panel
+cd .\jupyterlite_panel\
+mkdir pypi
+cd .\pypi\
+wget "https://cdn.holoviz.org/panel/0.14.2/dist/wheels/bokeh-2.4.3-py3-none-any.whl" -outfile "bokeh-2.4.3-py3-none-any.whl"
+wget "https://cdn.holoviz.org/panel/0.14.2/dist/wheels/panel-0.14.2-py3-none-any.whl" -outfile "panel-0.14.2-py3-none-any.whl"
+
+

Make a record of all that you have installed.

+
pip freeze > requirements.txt
+
+

Including any existing notebooks

+

If you have some existing notebooks or files you would want to include on the hosted version of Jupyterlite, you must create a directory and name it files

+

Build Jupyterlite and serve locally to test

+
1
+2
+3
+4
jupyter lite build --output-dir ./dist
+cd .\dist\
+py -m http.server 8000
+deactivate
+
+

The folder structure after build should look somewhat like below:

+
 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
panel_lite:.
+├───.cache
+├───dist
+   ├───api
+   ├───build
+   ├───doc
+   ├───extensions
+   ├───files
+   ├───kernelspecs
+   ├───lab
+   ├───pypi
+   ├───repl
+   ├───retro
+   └───tree
+├───files
+   ├───data
+   └───sample.ipynb
+├───pypi
+└───panel_v
+
+

Directory Structure Overall +Directory Structure of built dist folder

+

Push it to Github Enterprise

+

Github Desktop is the easiest way to do it.

+

Open Github desktop and create new reporsitory:

+

Create New Repository

+

On Github desktop point the Local Path field to \panel_lite\jupyterlite_panel\dist and do the initial commit and push to origin.

+

Provide Local Path

+

Host Jupyterlite as Github pages

+
    +
  • Click on button View on Github on Github Desktop.
  • +
+

Open Github Enterprise

+
    +
  • Github Enterprise will open in browser to the created repository
  • +
+

Enable Pages on Github Enterprise

+
    +
  • Click on Settings > Pages
  • +
  • select Branch: master (or main) and root as source.
  • +
  • Click Save and the message should be displayed in green box with the URL to access Jupyterlite.
  • +
+

Finally open the URL to access JupyterLite

+

JupyterLite hosted on Github Enterprise

+ + + + + + + + + + + + + + + + + + + + + +

+ + + + Comments +

+ + + + + + + +
+
+ + + + + +
+ + + +
+ + + +
+
+
+
+ + + + + + + + + + + + \ No newline at end of file diff --git a/2023/02/26/git-basics.html b/2023/02/26/git-basics.html new file mode 100644 index 0000000..e522b6c --- /dev/null +++ b/2023/02/26/git-basics.html @@ -0,0 +1,1764 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + Git Basics - Make Gadgets Work + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + Skip to content + + +
+
+ +
+ + + + +
+ + +
+ +
+ + + + + + + + + +
+
+ + + + + + + + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+
+ + +
+
+
+
+ + + + + + + + + + + + + + + + + + + + +

Git Basics

+ +

Basics of Git

+

Recently, I was asked to help people understand what is this whole business with GIT and I ran a few sessions on the topic specifically keeping in mind that my audience consisted of tech enthusiasts with minimal tech background. The content below is how I explained and as it went down well, I figured it may be helpful to someone else too; hence this post. 😄

+ + +

Version Control System

+

To understand the tool git lets first start by understanding what problem does it actually solve. At the most basic level git is a version control system. What this means is that it allows automated control of maintaining different versions of a digital asset such as a text file, CV, or indeed code files for a software. Now for simpler things like CV or text file one may not need something as sophisticated and complex as git but when it comes to maintaining assets for a software it is indeed a very useful tool.

+

To understand how significant this is, lets put it in perspective of a typical business operations scenario completely unrelated to software development:

+
    +
  • Say there was an RFP response on which multiple people from Sales, Marketing, Pursuit and Technical team were helping prepare a response.
  • +
  • Each team will complete one section of the response document relevant to their area.
  • +
  • This will then all need to be integrated into the final response and reviewed by the Sales Lead.
  • +
  • Any changes that Sales Lead identifies will need further changes which will then again need to be amended on the document.
  • +
+

Even with change tracking enabled in MS Word, we all know how painful this entire exercise is. So much so that at times we try doing it together so all changes can be done once or we opt for the approach of making changes sequentially where one section is completed by one team before another can be started by another team. This when we have one document!!!

+

Now imagine if we had 1000s of such documents - that is a real scenario for software developers as each software has multiple files that may need input from multiple people. So having an automated version control helps save time because it, at the most basic level, saves time on integrating changes carried out by different people or teams that were carried out in parallel.

+

Now when it comes to version control there are two main ways in which it can be achieved:

+
    +
  • +

    Centralised Version Control System: In our RFP example, the approach where everyone gathers together to carry out change on the main version will be a good simulation of centralised version control. What this means is that the changes to digital asset are done at one central location which is tightly controlled. This might as well be right approach for a smaller undertaking such as our RFP example but when it comes to software development it is not considered to be the best.

    +
  • +
  • +

    Distributed Version Control System: This is loosely what would have been the approach in our RFP example if people were to carry out changes on their copy of document which will then be merged by Sales Lead to prepare the final response. Basically a distributed version control system works on the principle that each contributor to the change of digital asset will make change on their local copy and then submit the change for version maintenance to the version controller. Git as a system shines in this approach of distributed version control.

    +
  • +
+

Working Directory

+

Directory is technically correct name for what are called folders in Windows Operating System and Working Directory is the directory in which the user is currently working. For example, let's say I create a folder called "Project_1" in windows on path "C:/Documents" and save a python script in that folder, then the working directory will be "Project_1" and working directory path will be "C:/Documents/Project_1".

+

In order to version control this file, one can use git but this will include understanding the steps of how to go about it as explained in following sections.

+

Initiatlise

+

Now when let's say the script file script.py is created by developer. It is not being tracked by git at this time. Now if we want it tracked by git, we need to first initiate git on the working directory. This is done using the command: git init but before typing this on command line, one needs to first get into the working directory on command line or terminal.

+

In our example this will be done like so:

+
cd C:/Documents/Project_1
+git init
+
+

This will create a .git directory (folder) inside the working directory.

+

This is equivalent of git software saying it is now ready to start tracking but it will still need to know what exactly we want it to track. This is what git add does.

+

Add

+

To tell git what we want it to track, we have to use the command git add <filenames / foldernames>. However, if there are 100 script files in directory it will be very cumbersome to type every single filename and as a shortcut one can use dot notation for current working directory which is dot(.) and the command can be issued as:

+
## To add all content of the working directory
+git add .
+
+## To add specific file say script.py
+git add script.py
+
+

This tells git that we want all files and directories (folders) in the current working directory to be tracked.

+

At this point, git is basically aware that it has to monitor changes on these files but it will not assume that every change we have made needs to be applied to maintained version so when we have added these files, we have basically also just said prepare these files for finalising their version - In other words we have asked git to stage.

+

In order to finalise the versioning, we have to now tell this specifically to git which is done using commit. This will also be reflected in the tracking status which can be checked using status

+

Status

+

One can check current tracking status of various files and directories on the git initialised working directory by using the command:

+
git status
+
+

Commit

+

So we have got git to stage the files and now we want it to label the latest changes as a new version and this is achieved using the command:

+

git commit -m <Comment for the commit>
+
+This command ensures that git now makes the latest changes baseline and still keep record of previous changes. This is especially useful if in case we want to revert to a previous version for some reason - say we found that in latest version there were quite a few mistakes which will need to be rewworked and until then previous version is what we want to keep as baseline for anyone who wants to refer and make changes to. This reverting can be achieved using revert

+

Revert

+

Now reverting to a previous version requires that we know the commit id that git applied to previous commits and then identify the exact commit id to which we want git to revert to. This can be achieved by checking git logs, so the sequence to git revert will be like so:

+
## Find the commit id to revert to
+git log
+
+## Copy the commit id of the version to revert to and issue following command
+git revert <commit id to revert to>
+
+

Now at this point, all changes are being tracked on the local machine of the user, so how can this help with collboration. This is achieved by synchronising the latest version on local machine to a central server. This could be a server hosted by an organisation or it can be something public like Github, Gitlab and such. As GitHub is most frequently used in enterprise environment, lets see how that works.

+

Github basically is a web based utility which acts as a remote location where we can push our finalised version to be saved in what is called a github repository for other collborators to then download (clone) as their local copy and work on. Once each poarty has made changes, the can each push their changes to the github with help of git and the version control will be applied automagically or in some instance with minimal manual intervention from the maintaining or one pushing the changes.

+

On Device[system]Working DirectoryGIT TrackerGithub[system]Working FileAdd FileCommit Filemain repoUserLocal DeviceGit on local deviceGitHub (Remote)Make changesTracked by GitPush commitsgit commit -m<message>git add ./<file>git push origin main

+

There is lots going on in this last bit so lets break it down to digestable chunks of information in next session.

+ + + + + + + + + + + + + + + + + + + + + +

+ + + + Comments +

+ + + + + + + +
+
+ + + + + +
+ + + +
+ + + +
+
+
+
+ + + + + + + + + + + + \ No newline at end of file diff --git a/2023/02/28/git-jargon.html b/2023/02/28/git-jargon.html new file mode 100644 index 0000000..08f88cd --- /dev/null +++ b/2023/02/28/git-jargon.html @@ -0,0 +1,1781 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + Git Jargon Explained - Make Gadgets Work + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + Skip to content + + +
+
+ +
+ + + + +
+ + +
+ +
+ + + + + + + + + +
+
+ + + + + + + + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+
+ + +
+
+
+
+ + + + + + + + + + + + + + + + + + + + +

Git Jargon Explained

+ +

Git Jargon

+

Before we delve into the topic of how collaboration happens using git, lets get some terminology out of the way.

+ + +

repo

+

When talking to techy chaps, one will encounter the word repo very often. However, repo is just a shortform for the complete word repository.

+

repository

+

Now repository in git speak is basically specific to a project and is nothing but a fancy alias for the working directory where all the digital assets being tracked for the project are placed.

+

root

+

The term root when used with reference to a folder, directory, repo or repository is basically the directory in which all the digital assets of your project are stored. This directory may or may not contain other sub directories.

+

remote

+

remote is a way to refer to server. So if a techy were to say "I have updated File X on remote" - it will basically translate in plain English to something like this - "I updated the File X on my laptop and have then also taken required actions to get those updates reflected on the copy saved on the server."

+

push

+

This refers to sending updates carried out (commited) on a local machine to the server (remote). Fairly frequently one may come across statements when checking for status updates on the lines of "I have pushed my changes to remote" which in normal English means "I made required changes on my local machine, committed them and then sent (pushed) them to the server (remote)"

+

pull

+

This refers to getting updates from the server (remote) on to the local machine. This is an activity typically carried out before one is planning to make any changes or updates to last known version. So if say a task is assigned to an individual for updating file , a reply can be "Alright, I will pull the latest from remote and make changes and will let you know once I have pushed my changes back." which will translate to "Alright, I will get the latest copy from server, make the changes you have asked for and then commit those changes and send it back as updated version on the server"

+

main

+

This refers to the final / published version of project's repository.

+

branch

+

This term is more of a concept really. You see branch is a mechanism to carry out changes without impacting the main version and therefore a branch is basically just a new or separate version of the main repository where one can make changes to their heart's content without worrying about messing up with the last known final version.

+

merge

+

Once the experimental changes in a branch are completed and proven not be breaking any other digital assets, it is desirable indeed to get these changes reflected on the main version and this action of bringing digital assets in main to reflect same changes as that on the branch is called merge.

+

modified

+

In git speak, any changes (updates, additions, deletions) to a digital asset (including deletion of entire digital asset) then that digital asset will be identified as modified

+

staged

+

The modified digital assets are not automatically assumed to contribute a version change and that is only done when those changes are added explicity by issuing the command git add <filenaem / foldername> and at this point the changes are said to be staged.

+

Recap

+

With this in mind, the flow of GIT can now be explained as below:

+
    +
  1. To work with Git, you first initialise it on a directory which will make it a Repository
  2. +
  3. Once initialised, Git creates a hidden firectory called .git which is used to keep track of changes in the initialised directory.
  4. +
  5. At this point, we need to make Git aware of the files in this repository which we want Git to track and in doing so we stage those digital assets.
  6. +
  7. From this point on, any changes made to digital assets in project folder can be tracked.
  8. +
  9. If a digital asset which is being tracked is changed, it gets the status of modified.
  10. +
  11. Any digital asset with status must be staged before it can be passed on for version control.
  12. +
  13. Once staged files must be committed so that the changes can be treated as the latest version snapshot.
  14. +
  15. As each commit is tracked by Git, it is possible to revert to a previous version by first identifying the commit id one wants to revert to using git log and then providing that commit id through git revert <commit_id>
  16. +
+ + + + + + + + + + + + + + + + + + + + + +

+ + + + Comments +

+ + + + + + + +
+
+ + + + + +
+ + + +
+ + + +
+
+
+
+ + + + + + + + + + + + \ No newline at end of file diff --git a/2023/03/29/read-excel-csv-recursive.html b/2023/03/29/read-excel-csv-recursive.html new file mode 100644 index 0000000..98ab757 --- /dev/null +++ b/2023/03/29/read-excel-csv-recursive.html @@ -0,0 +1,2008 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + Python Function read excel / csv files from a given directory and its subdirectories - Make Gadgets Work + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + Skip to content + + +
+
+ +
+ + + + +
+ + +
+ +
+ + + + + + + + + +
+
+ + + + + + + + + + + + + + + +
+
+
+
+ + +
+
+
+
+ + + + + + + + + + + + + + + + + + + + +

Python Function read excel / csv files from a given directory and its subdirectories

+ +

Code

+

The code for the function is as shown below:

+ + +
 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
+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
+52
+53
+54
+55
+56
+57
+58
+59
+60
+61
+62
+63
+64
+65
+66
+67
+68
+69
+70
+71
+72
+73
+74
+75
+76
+77
+78
+79
+80
+81
+82
+83
+84
+85
+86
+87
+88
+89
+90
+91
+92
+93
import os
+import pandas as pd
+import numpy as np
+from datetime import datetime 
+
+def extract_data(directory_path, columns_to_extract, date_columns=[], rows_to_skip=0, output_filename='output.csv'):
+    """
+    Extracts data from all Excel and CSV files in the specified directory and its subdirectories that contain all the
+    specified columns.
+
+    :param directory_path: The path to the directory to search for Excel and CSV files.
+    :param columns_to_extract: A list of column names to extract from each file.
+    :param date_columns: A list of column names to parse as dates using pd.to_datetime().
+    :param output_filename: The path and filename to save the extracted data in a csv
+    :return: A DataFrame containing the extracted data from all Excel and CSV files that contain the specified columns,
+    or None if no files contain the specified columns.
+    """
+    start_time = datetime.now()
+    extracted_data = pd.DataFrame()
+    files_with_no_columns = []
+    sheet_read = ''
+    extracted_columns = columns_to_extract.copy()
+    extracted_columns.extend(['File Name','SubDir','CreatedDate','LastModifiedDate'])
+
+    for root, dirs, files in os.walk(directory_path):
+        for file in files:
+            print('Started reading ' + file + ' at: {}'.format(datetime.now()) + ' ...')
+            if file.endswith('.xlsx') or file.endswith('.xls') or file.endswith('.csv'):
+                file_path = os.path.join(root, file)
+                columns_found_outer = False
+                if file.endswith('.csv'):
+                    df = pd.read_csv(file_path,skiprows=rows_to_skip)
+                    for col in columns_to_extract:
+                        if col in df.columns:
+                            columns_found_outer = True
+                            #print(col + 'in csv true loop')
+                        else:
+                            columns_found_outer = False
+                            #print(col + 'in false loop')
+                            break
+                else:
+                    dfs = pd.read_excel(file_path, sheet_name=None,skiprows=rows_to_skip)
+                    for key in dfs.keys():
+                        columns_found = False
+                        for col in columns_to_extract:
+                            if col in dfs[key].columns:
+                                columns_found = True
+                                #print(col + 'in true loop')
+                            else:
+                                columns_found = False
+                                #print(col + 'in false loop')
+                                break
+                        if columns_found:
+                            columns_found_outer = True
+                            df = dfs[key]
+                            sheet_read = key
+                            break
+                if columns_found_outer:
+                    if len(date_columns) > 0:
+                        for col in date_columns:
+                            if col in df.columns:
+                                df[col] = pd.to_datetime(df[col])
+                    df['File Name'] = file
+                    df['SubDir'] = os.path.basename(root)
+
+                    createx = modx = os.path.getctime(file_path)
+                    xcreate = datetime.fromtimestamp(modx)
+                    df['CreatedDate'] = xcreate
+
+                    modx = os.path.getmtime(file_path)
+                    xmod = datetime.fromtimestamp(modx)
+                    df['LastModifiedDate'] = xmod
+                    extracted_data = pd.concat([extracted_data, df[extracted_columns]], ignore_index=True)
+                    #print(xmod)
+                    if file.endswith('.csv'):
+                        print(file + ' has been read')
+                    else:
+                        print(file + ' has been read and it was last modified on ' + xmod.strftime('%Y-%m-%d') + '. The name of the sheet that was read is: ' + sheet_read)
+                else:
+                    files_with_no_columns.append(file_path)
+    if len(files_with_no_columns) > 0:
+        print("The following files do not contain the specified columns:")
+        for file_path in files_with_no_columns:
+            print(file_path)
+    if not extracted_data.empty:
+        extracted_data = extracted_data.applymap(lambda s: s.upper() if type(s) == str else s).fillna('')
+        extracted_data.to_csv(output_filename)
+        print('Started at: {}'.format(start_time) + '. \nEnded at: {}'.format(datetime.now()) + '. \nTime elapsed (hh:mm:ss.ms) {}'.format(datetime.now() - start_time))
+        return extracted_data
+    else:
+        print("Specified columns do not exist in any file in the provided directory.")
+        print('Started at: {}'.format(start_time) + '. \nEnded at: {}'.format(datetime.now()) + '. \nTime elapsed (hh:mm:ss.ms) {}'.format(datetime.now() - start_time))
+        return None
+
+

Explanation

+
    +
  1. +

    The function extract_data takes in the following parameters:

    +
      +
    • directory_path: the path to the directory to search for Excel and CSV files
    • +
    • columns_to_extract: a list of column names to extract from each file
    • +
    • date_columns: a list of column names to parse as dates using pd.to_datetime()
    • +
    • rows_to_skip: the number of rows to skip when reading each file
    • +
    • output_filename: the path and filename to save the extracted data in a CSV file
    • +
    +
  2. +
  3. +

    The function starts by creating an empty DataFrame called extracted_data.

    +
  4. +
  5. +

    It also initializes variables files_with_no_columns and sheet_read to track files that do not contain the specified columns and to keep track of the current sheet being read when extracting data from Excel files.

    +
  6. +
  7. +

    The list extracted_columns is created by copying the input columns_to_extract list and adding additional columns for the filename, subdirectory name, file creation date, and last modified date.

    +
  8. +
  9. +

    The function then loops through all the files in the specified directory and its subdirectories using os.walk().

    +
  10. +
  11. +

    For each file, it checks if it has a ".xlsx", ".xls", or ".csv" extension.

    +
  12. +
  13. +

    If the file is a CSV file, the function reads it into a DataFrame using pd.read_csv(), skipping the number of rows specified by rows_to_skip.

    +
  14. +
  15. +

    It then checks if all the columns in columns_to_extract are present in the DataFrame. If so, it sets columns_found_outer to True and proceeds to the next step. If not, it sets columns_found_outer to False and moves on to the next file.

    +
  16. +
  17. +

    If the file is an Excel file, the function reads all sheets in the file into a dictionary of DataFrames using pd.read_excel() and sheet_name=None. It then loops through all the sheets and all the columns in columns_to_extract, checking if each column is present in each sheet's DataFrame. If all the columns are present in a sheet, it sets columns_found to True and proceeds to the next step. If not, it sets columns_found to False and moves on to the next sheet in the same Excel file. If at least one sheet contains all the specified columns, the function combines the DataFrames of all sheets into one using pd.concat().

    +
  18. +
  19. +

    If columns_found_outer is True, it extracts the filename, subdirectory name, file creation date, and last modified date using os.path.basename(), os.path.getctime(), and os.path.getmtime(), and adds them as new columns to the DataFrame. It then appends the DataFrame to the extracted_data DataFrame.

    +
  20. +
  21. +

    If columns_found_outer is False, it increments the files_with_no_columns counter and prints a warning message.

    +
  22. +
  23. +

    Finally, the function checks if any files contained the specified columns. If not, it returns None. Otherwise, it sorts the extracted_data DataFrame by filename and saves it to a CSV file using to_csv(). It then prints the number of files processed, the number of files that did not contain the specified columns, and the path to the output file.

    +
  24. +
+

Sample Usage

+

The function can be called in python as shown below:

+
# set directory path
+directory_path = './Work' # Path for the directory where all the files containing data for  extraction are to be searched
+
+# set the columns you want to extract
+columns_to_extract = ['Region','Country', 'Product Number','Quantity','Date of Sale']
+date_col = ['Date of Sale']
+
+# set the rows to skip
+skiprows = 0 #this basically will be number of rows in begining of the files which must be skipped to reach the header row of the data
+output_filename = 'Regional Sales Data.csv'
+
+#Call function
+df = extract_data(directory_path,columns_to_extract,date_col,skiprows,output_filename)
+
+

Now, assuming there were 12 separate files for past 12 months inside the folder then so long as all those files, irrespective of whether they are csv or excel, have the columns Region,Country, Product Number,Quantity,Date of Sale; the function will read the files and extract the data and return it to the dataframe df.

+

GUI Implementation

+

A very basic GUI implementation of above function using PySimpleGUI with all code is available here

+

Usage

+

The script can directly be copied to a Jupyter cell or can be run from terminal. Following command should ensure all dependencies are installed:

+
py -m pip install pandas, numpy, pysimplegui
+
+

Some things the GUI takes care of are:

+
    +
  • Allows selection of columns to be extracted from a sample .csv file
  • +
  • Allows user to specify which of the selected columns should be parsed as date
  • +
  • Gives a date based filename to output
  • +
  • Shows colour coded log for which files were read in green and which were ignored in red background.
  • +
+

Screenshots

+

Some screenshots of the resulting app are as shown below:

+

Empty Form

+

image

+

Filled Form

+

image

+

Displays extracted output

+

image

+

Displays filename of the output and location where it is saved

+

image

+

Colour coded log

+

image

+ + + + + + + + + + + + + + + + + + + + + +

+ + + + Comments +

+ + + + + + + +
+
+ + + + + +
+ + + +
+ + + +
+
+
+
+ + + + + + + + + + + + \ No newline at end of file diff --git a/2024/08/13/logseq-customisation-for-project-management-teamplate.html b/2024/08/13/logseq-customisation-for-project-management-teamplate.html new file mode 100644 index 0000000..3e32493 --- /dev/null +++ b/2024/08/13/logseq-customisation-for-project-management-teamplate.html @@ -0,0 +1,3683 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + Logseq Customisations for Project Management Template - Make Gadgets Work + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + Skip to content + + +
+
+ +
+ + + + +
+ + +
+ +
+ + + + + + + + + +
+
+ + + + + + + + + + + + + + + +
+
+
+
+ + +
+
+
+
+ + + + + + + + + + + + + + + + + + + + +

Logseq Customisations for Project Management Template

+ +

Background

+

While the overall planning of project timeline gets lot of attention in the world of software, the most important aspect of project management in my experience is maintaining and tracking Issues and Actions. This in normal project management practice is carried out through the use of an Actions Log and an Issues Log. In addition any medium complexity project invariably will have internal and external dependencies / constraints, risks which I tend to track on Constraints Log and Risks Log. Finally, every project has decisions that I track on a Decisions Log.

+

Additionally, any opportunities that I identify during the course of project that are not in the scope of my project I capture those too in an Opportunities Log

+

Now all these logs have fairly standard fields so I created and started using an excel template and began calling it CARDIO Log short for log of Constraints, Actions, Risks, Decisions, Issues, Opportunities for a given project. This has worked well for me over the past 15 years or so but there are times where in order to maintain it meant cross linking an action to an issue or a risk and so on and more often than not it would become easier to just track actions in one of the other logs and that would make it a bit chaotic.

+

That problem, however, is what I thought, can be resolved using Logseq especially after starting with the template by the Logseq community user Luhman and starting with his template and explanation provided here.

+ + +

Using logseq from browser when binary install is not possible

+

When binary install is not possible, one can still use logseq from browser by navigating to the web app demo page from Chrome browser and then selecting a local directory where notes will be saved. It works perfectly fine except there is no option to install plugins. So inorder for issues table etc to work from broswer following steps will suffice:

+

Option 1

+
    +
  • Select the directory where your notes (or graphs in logseq speak) will be saved.
  • +
  • Grant permission for local file access when asked by the browser.
  • +
  • Now if you will press Ctrl+K it will open search box but the cusrsor goes to the omnibox (where you type the url for the page) and then you will have to either manually click into the logseq search or press Esc key three times to be able to get the cursor back in the search box. It may be easier to just click on Search icon in left hand navigation at top of the screen (next to the hamburger menu).
      +
    • Alternatively configure the keyboard shortcut to +alt+k++ as that does not conflict with any other keyboard shortcut.
    • +
    • To add custom shortcuts, you can navigate to the shortcuts page with g s. Press the blue button corresponding to a given shortcut and a modal should pop up. Press the keybinding you want and then press Save.
    • +
    +
  • +
  • Now search for logseq/custom.css and add the whole css from this gist.
  • +
  • Then search for logseq/config.edn and add the whole edn from this gist
  • +
+
+

Tip

+

If you were installing the binary, you can still copy the entire css and edn from above. If you do that, you can directly skip to the section - Create Templates.

+
+

Option 2

+
    +
  • Download the archive from my github repository here
  • +
  • Unzip the downlaoded repository and delete the README.md file.
  • +
  • Navigate to journals directory where there will be three files. Delete all these files.
  • +
  • Navigate back and open the pages directory which will have 7 files. Except for content.md and templates.md, delete all other files.
  • +
  • Now copy the logseq and pages directories into the directory where you want your notes saved.
  • +
  • Open logseq from chrome and add the directory from step above where you copied the two folders.
  • +
+
+

Tip

+

If you were installing the binary, you can follow option 2 and then there will be no need to read rest of this article 😄.

+
+

Customisations

+

Sample Project Log

+

Screenshot_13-8-2024_15949_

+

Preparation

+
    +
  • +

    First we will ensure our actions log which will be generated using the Logseq's inbuilt feature for To Do Lists displays fields that we want it to display.

    +
      +
    • Press Ctrl+K and search for logseq/config.edn
    • +
    • In the code block search for following lines:
    • +
    +
    1
    +2
    +3
    +4
    +5
    +6
    ;; Advanced queries `:result-transform` function.
    +;; Transform the query result before displaying it.
    +:query/result-transforms
    +{:sort-by-priority
    + (fn [result] (sort-by (fn [h] (get h :block/priority "Z")) result))
    +}
    +
    +
  • +
  • +

    Now replace these with following which is slightly reduced compared to the Source as I only needed "Deadline" for my purposes and I don't use the "Scheduled" part of the Logseq task management feature:

    +
     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
    +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
    +52
    +53
    +54
    +55
    +56
    +57
    +58
    +59
    +60
    +61
    +62
    +63
    +64
    +65
    +66
    +67
    +68
    +69
    +70
    +71
    +72
    +73
    +74
    +75
    +76
    +77
    +78
    +79
    +80
    +81
    +82
    +83
    +84
    +85
    +86
    +87
    +88
    +89
    +90
    +91
    +92
    +93
    +94
    +95
    +96
    +97
    +98
    ;; Advanced queries `:result-transform` function.
    +;; Transform the query result before displaying it.
    +:query/result-transforms
    +{:sort-by-priority
    + (fn [result] (sort-by (fn [h] (get h :block/priority "Z")) result))
    + :add-task-attrs (fn [result]
    +   (def months {1 "January" 2 "February"  3 "March" 4 "April" 5 "May" 6 "June" 7 "July" 8 "August" 9 "September" 10 "October" 11 "November" 12 "December"})
    +   (def monthName (fn [dd]
    +         (get months (int dd))
    +        ))
    +
    +   ;; source: https://discuss.logseq.com/t/add-query-input-or-function-day-of-week/18361/12
    +   (def days {0 "Saturday" 1 "Sunday" 2 "Monday" 3 "Tuesday" 4 "Wednesday" 5 "Thursday" 6 "Friday"})
    +   (def weekDay (fn [date]
    +         (def month (quot (mod date 10000) 100))
    +         (def month6 (quot (- month 8) 6))
    +         (def year6 (+ (quot date 10000) month6))
    +         (def yearnum (mod year6 100))
    +         (def century (quot year6 100))
    +         (def d (mod (+ (mod date 100) (quot (* 13 (inc (- month (* month6 12)))) 5) yearnum (quot   yearnum 4) 
    +         (quot century 4) (* 5 century)) 7))
    +         (get days d)
    +        ))
    +
    +   (def suffixes {0 "th" 1 "st" 2 "nd" 3 "rd" 4 "th" 5 "th" 6 "th" 7 "th" 8 "th" 9 "th"})
    +   (def positionalSuffix (fn [dd]
    +         (if (or (= dd "11") (= dd "12") (= dd "13"))
    +           (get suffixes 0)
    +           (get suffixes (int (subs dd (count dd) 1))) )
    +        ))
    +
    +   (def token (fn [s] (str "⟨" s "⟩")))
    +   (def format
    +     (-> (get (js->clj (call-api "get_user_configs")) "preferredDateFormat")
    +        (clojure.string/replace "do" (token "1"))
    +        (clojure.string/replace "dd" (token "2"))
    +        (clojure.string/replace "d" (token "3"))
    +        (clojure.string/replace "EEEE" (token "4"))
    +        (clojure.string/replace "EEE" (token "5"))
    +        (clojure.string/replace "EE" (token "6"))
    +        (clojure.string/replace "E" (token "7"))
    +        (clojure.string/replace "MMMM" (token "8"))
    +        (clojure.string/replace "MMM" (token "9"))
    +        (clojure.string/replace "MM" (token "10"))
    +        (clojure.string/replace "M" (token "11"))
    +        (clojure.string/replace "yyyy" (token "12"))
    +        (clojure.string/replace "yy" (token "13"))
    +     ))
    +
    +   (def parseDate (fn [date]
    +         (if-not date nil
    +           (let [
    +               regex (re-pattern "(\\d{4})(\\d{2})(\\d{2})")
    +               [_ yyyy mm dd] (re-matches regex (str date))
    +               yy (subs yyyy 2 4)
    +               d (str (int dd))
    +               do (str d (positionalSuffix dd))
    +               mmmm (monthName mm)
    +               mmm (subs mmmm 0 3)
    +               m (str (int mm))
    +               eeee (weekDay date)
    +               eee (subs eeee 0 3)
    +               ee (subs eeee 0 2)
    +               e eee
    +             ]
    +             (-> format
    +               (clojure.string/replace (token "1") do)
    +               (clojure.string/replace (token "2") dd)
    +               (clojure.string/replace (token "3") d)
    +               (clojure.string/replace (token "4") eeee)
    +               (clojure.string/replace (token "5") eee)
    +               (clojure.string/replace (token "6") ee)
    +               (clojure.string/replace (token "7") e)
    +               (clojure.string/replace (token "8") mmmm)
    +               (clojure.string/replace (token "9") mmm)
    +               (clojure.string/replace (token "10") mm)
    +               (clojure.string/replace (token "11") m)
    +               (clojure.string/replace (token "12") yyyy)
    +               (clojure.string/replace (token "13") yy)
    +             )
    +           )
    +         )
    +        ))
    +
    +
    +   (map (fn [x]
    +     (update x :block/properties (fn [u]
    +       (-> u
    +           (assoc :marker (str (get x :block/marker)) )
    +           (assoc :priority (str (get x :block/priority)) )
    +           (assoc :deadline (parseDate (get x :block/deadline)) )
    +           (assoc :repeated? (str (get x :block/repeated?)) )
    +       )
    +     ))
    +   )
    +   result)
    + )
    + }
    +
    +
  • +
+

Plugins

+
    +
  • Click on Three Dots in top right corner of the screen and click on menu entry Plugins and then click on Marketplace
  • +
  • Now install the following plugins:
      +
    • logseq-agenda
    • +
    • logseq-automatic-linker
    • +
    • logseq-datenlp-plugin
    • +
    • logseq-diagrams-as-code
    • +
    • logseq-doc
    • +
    • logseq-emoji-picker-fork
    • +
    • logseq-emoji-shortcodes
    • +
    • logseq-luckysheet
    • +
    • logseq-markdown-table
    • +
    • logseq-paste-more
    • +
    • logseq-plugin-automatic-url-title
    • +
    • logseq-plugin-show-weekday-and-week-number
    • +
    • logseq13-full-house
    • +
    • logseq13-missing-commands
    • +
    +
  • +
+

Look and Feel

+
    +
  • Now, I quite like the Mia Quattro Theme that can either be installed as a theme from marketplace or just by including the following line in logseq/custom.css just under the comment /*Theme*/ right at the top of the file like so:
  • +
+
1
+2
/*Theme*/
+@import url('https://playerofgames.github.io/logseq-mia-theme/mia_quattro.css');
+
+
    +
  • However this theme had some quirks which can be refined by adding following css overrides:
  • +
+
  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
+ 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
+ 52
+ 53
+ 54
+ 55
+ 56
+ 57
+ 58
+ 59
+ 60
+ 61
+ 62
+ 63
+ 64
+ 65
+ 66
+ 67
+ 68
+ 69
+ 70
+ 71
+ 72
+ 73
+ 74
+ 75
+ 76
+ 77
+ 78
+ 79
+ 80
+ 81
+ 82
+ 83
+ 84
+ 85
+ 86
+ 87
+ 88
+ 89
+ 90
+ 91
+ 92
+ 93
+ 94
+ 95
+ 96
+ 97
+ 98
+ 99
+100
+101
+102
+103
+104
+105
+106
+107
+108
+109
+110
+111
+112
+113
+114
+115
+116
+117
+118
+119
+120
+121
+122
+123
+124
+125
+126
+127
+128
+129
+130
+131
+132
+133
+134
+135
+136
+137
+138
+139
+140
+141
/* Override tag and note color scheme for tags from mia_quattro theme  */
+a.tag{
+ font-size: 100%;
+ color: var(--lx-accent-11,var(--ls-tag-text-color,hsl(var(--primary))));
+}
+
+svg.note {
+   color: var(--lx-accent-11, var(--rx-yellow-08))
+}
+
+svg.tip {
+   color: var(--lx-accent-11,var(--rx-blue-08))
+}
+
+/* Selection lists (search, tag complete, etc.) */
+
+#ui__ac .chosen,
+.chosen {
+    --ls-primary-text-color: #ee0606;
+    --ls-link-ref-text-color: #eaeaea;
+    --ls-link-ref-text-hover-color: #fafafa;
+    --ls-quaternary-background-color: var(--accent-dark-color);
+    --ls-icon-color: var(--ls-primary-text-color);
+}
+
+#ui__ac .chosen {
+    background-color: var(--accent-dark-color);
+}
+
+.menu-link.chosen  {
+    color: var(--ls-primary-text-color) !important;
+}
+
+/* to make todo checkbox visible */
+.form-checkbox {
+    --ls-page-checkbox-border-color: var(--accent-color);
+
+    border: 1px solid var(--ls-page-checkbox-border-color);
+    border-radius: 5px;
+    opacity: 1;
+}
+/* blockquote tweaks (reduce margin & padding and add custom colours) */
+
+blockquote {
+ padding: 8px 12px;
+ border-left: 5px solid;
+ border-left-color: var(--ls-page-blockquote-border-color, #7cfc00);
+ margin: 0.3rem 0 !important;
+}
+
+blockquote.yellow {
+ border-left-color: #ffe85580;
+}
+
+blockquote.blue {
+ border-left-color: #84b5ff80;
+}
+
+blockquote.red {
+ border-left-color: #ff558280;
+}
+
+.ls-block[data-refs-self^=".blue"] .blockquote{
+ border-left-color: #84b5ff80;
+}
+
+/* ==mark==  tweaks */
+
+mark {
+  background: var(--ls-page-mark-bg-color);
+  color: var(--ls-page-mark-color);
+  padding: 1px 2px;
+  margin: 0 2px;
+  border-radius: 3px;
+}
+
+mark.yellow {
+  background: var(--ls-page-mark-bg-color);
+  color: var(--ls-page-mark-color);
+}
+
+mark.pink {
+  background-color: #ff89be80;
+  color: white;
+}
+
+mark.blue {
+  background-color: #84b5ff80;
+  color:white;
+}
+
+mark.green {
+  background-color: #97ff9780;
+  color: yellow;
+}
+
+mark.red {
+  background-color: #ff558280;
+  color: white;
+}
+
+mark.grey {
+  background-color: #80808080;
+  color: white;
+}
+
+mark.gray {
+  background-color: #80808080;
+  color: white;
+}
+
+mark.orange {
+  background-color: #ffb86c80;
+  color: white;
+}
+
+mark.purple {
+  background-color: #c097ff80;
+  color: white;
+}
+
+/* add traffic lights to prioritized tasks */
+
+.priority[href="#/page/A" i]::before {
+ content: "🔴";
+ margin-right: 2px;
+}
+
+.priority[href="#/page/B" i]::before {
+ content: "🟡";
+ margin-right: 2px;
+}
+
+.priority[href="#/page/C" i]::before {
+ content: "🟢";
+ margin-right: 2px;
+}
+
+.opacity-50 {
+ opacity: 1;
+}
+
+
    +
  • As our templates later will depend on v-kanban plugin but I did not want to include the whole css and also wanted to modify the icons it shows with Pros and Cons, I include the following css on logseq/custom.css:
  • +
+
  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
+ 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
+ 52
+ 53
+ 54
+ 55
+ 56
+ 57
+ 58
+ 59
+ 60
+ 61
+ 62
+ 63
+ 64
+ 65
+ 66
+ 67
+ 68
+ 69
+ 70
+ 71
+ 72
+ 73
+ 74
+ 75
+ 76
+ 77
+ 78
+ 79
+ 80
+ 81
+ 82
+ 83
+ 84
+ 85
+ 86
+ 87
+ 88
+ 89
+ 90
+ 91
+ 92
+ 93
+ 94
+ 95
+ 96
+ 97
+ 98
+ 99
+100
+101
+102
+103
+104
+105
+106
+107
+108
+109
+110
+111
+112
+113
+114
+115
+116
+117
+118
+119
+120
+121
+122
+123
+124
+125
+126
+127
+128
+129
+130
+131
+132
+133
+134
+135
+136
+137
+138
+139
+140
+141
+142
+143
+144
+145
+146
+147
+148
+149
+150
+151
+152
+153
+154
+155
+156
+157
+158
+159
+160
+161
+162
+163
+164
+165
+166
+167
+168
+169
+170
+171
+172
+173
+174
+175
+176
+177
+178
+179
+180
+181
+182
+183
+184
+185
+186
+187
+188
+189
+190
+191
+192
+193
+194
+195
+196
+197
+198
+199
+200
+201
+202
+203
+204
+205
+206
+207
+208
+209
+210
+211
+212
+213
+214
+215
+216
+217
/* -- like dislike ----------------------------------------- */
+
+.ls-block[data-refs-self*="pros"] .block-children .bullet-container .bullet {
+ display: none;
+}
+
+.ls-block[data-refs-self*="pros"] .block-children .bullet-container:after {
+ content: "+";
+ font-size: 20px;
+ color: #1cd41c;
+}
+
+.ls-block[data-refs-self*="cons"] .block-children .bullet-container:after {
+ content: "-";
+ color: red;
+}
+
+a.tag[data-ref*="pros"] {
+ font-size: .8rem;
+ background: #014935e0;
+ color: rgb(202, 247, 118);
+ padding: 0 6px 3px;
+ border-radius: var(--ls-border-radius-low);
+ border: 1px solid rgba(137, 207, 96, 0.925);
+}
+
+a.tag[data-ref*="cons"] {
+ font-size: 13px;
+ background: #4b033bda;
+ color: rgb(255, 116, 128);
+ padding: 0 6px 3px;
+ border-radius: var(--ls-border-radius-low);
+ border: 1px solid rgba(182, 13, 41, 0.925);
+}
+
+a.tag[data-ref*="pros"]:hover {
+ filter: contrast(2) brightness(10);
+}
+
+a.tag[data-ref*="pros"]:before {
+ content: "✅ ";
+ font-size: 13px
+}
+
+a.tag[data-ref*="cons"]:before {
+ content: "❌ ";
+ font-size: 13px
+}
+
+
+a.tag[data-ref*="red"]:before {
+ content: "🔴";
+ margin-right: 2px;
+}
+
+a.tag[data-ref*="amber"]:before {
+ content: "🟠";
+ margin-right: 2px;
+}
+
+a.tag[data-ref*="green"]:before {
+ content: "🟢";
+ margin-right: 2px;
+}
+
+a.tag[data-ref*="on-hold"]:before {
+ content: "🟣";
+ margin-right: 2px;
+}
+
+a.tag[data-ref*="yellow"]:before {
+ content: "🟡";
+ margin-right: 2px;
+}
+
+a.tag[data-ref*="closed"]:before {
+ content: "🔵";
+ margin-right: 2px;
+}
+
+a.tag[data-ref*="no-go"]:before {
+ content: "🚫";
+ margin-right: 2px;
+}
+
+a.tag[data-ref*="merged"]:before {
+ content: "⚪";
+ margin-right: 2px;
+}
+
+/* -------------------------------- like dislike end  ------ */
+
+/*===========================================================*/
+/* css columns view / kanban  v20220510--------------------- */
+/* use: inline tag #kanban, #kanban-small or #kanban-wXXX    */
+/* try:  #kanban-w200,#kanban-w300, #kanban-w400             */
+
+div[data-refs-self*="kanban"]>.block-children-container.flex {
+    width: 100%;
+}
+
+div[data-refs-self*="kanban"]>.block-children-container.flex>.block-children.w-full {
+    display: inline-flex;
+    position: relative;
+    overflow-x: auto !important;
+    overflow-y: hidden;
+    margin: 0 10px;
+}
+
+div[data-refs-self*="kanban"]>.block-children-container.flex>.block-children.w-full>div.ls-block {
+    display: inline-block;
+    padding: 0;
+    width: inherit;
+    min-width: 200px;
+    margin-right: 10px;
+}
+
+/* wide */
+
+div[data-refs-self*="kanban-small"]>.block-children-container.flex>.block-children,
+div[data-refs-self*="kanban-wide"]>.block-children-container.flex>.block-children {
+   min-width: 90vw;
+   left: 50%;
+   transform: translate(-50%);
+   background-color: var(--ls-primary-background-color);
+   overflow-x: scroll !important;
+   overflow-y: hidden;
+   margin: 10px 30px;
+}
+
+div[data-refs-self*="kanban-wide"]>.block-children-container.flex>.block-children>div.ls-block {
+   display: inline-block;
+   min-width: 350px;
+   padding: 8px 0px !important;
+   font-size: 0.85rem;
+   margin: 5px 0px;
+   background-color: var(--ls-secondary-background-color);
+   box-shadow: 2px 2px 2px 1px rgba(0, 0, 0, 0.2);
+   border-radius: var(--ls-border-radius-medium);
+}
+
+
+/* #kanbansmall : smaller font with hover zoom */
+
+div[data-refs-self*="kanban-small"]>.block-children-container.flex>.block-children>div.ls-block {
+   display: inline-block;
+   min-width: 350px;
+}
+
+div[data-refs-self*="kanban-small"]>.block-children-container.flex>.block-children .block-content {
+   font-size: 10px;
+   font-weight: 300;
+}
+
+div[data-refs-self*="kanban-small"]>.block-children-container.flex>.block-children .block-content:hover {
+   font-size: 14px !important;
+   min-width: 100px;
+}
+
+
+/* #kanban-w[100-300] : force width of the columns */
+
+div[data-refs-self*="kanban-w100"]>.block-children-container.flex>.block-children.w-full>div.ls-block {
+   min-width: 100px;
+}
+
+div[data-refs-self*="kanban-w150"]>.block-children-container.flex>.block-children.w-full>div.ls-block {
+   min-width: 150px;
+}
+
+div[data-refs-self*="kanban-w200"]>.block-children-container.flex>.block-children.w-full>div.ls-block {
+   min-width: 200px;
+}
+div[data-refs-self*="kanban-w300"]>.block-children-container.flex>.block-children.w-full>div.ls-block {
+   min-width: 300px;
+}
+div[data-refs-self*="kanban-w400"]>.block-children-container.flex>.block-children.w-full>div.ls-block {
+   min-width: 400px;
+}
+div[data-refs-self*="kanban-fit"]>.block-children-container.flex>.block-children.w-full>div.ls-block {
+   min-width: 400px;
+       width: max-content; 
+}
+
+/* remove left border for kanbanized */
+[data-refs-self*="kanban"] .block-children-left-border {
+   opacity: 0;
+}
+
+/* fix modal list not appearing*/
+.block-children {
+   overflow: visible !important;
+} 
+
+.ls-block[data-refs-self*="kanban"] .absolute-modal,
+.ls-block[data-refs-self*="kanban"] #ui__ac {
+   min-height: 80px;
+}
+
+/*--------------------------------------------- kanban end-- */
+
+/*------------------expreimetal for better table view START-------------------*/
+.table-wrapper {
+    width: 100% !important;
+    max-width: 100% !important;
+}
+
+table td {
+    min-width:100px;
+    word-wrap:break-word;
+}
+
+
+table th {
+    word-break: keep-all;
+}
+/*------------------expreimetal for better table view END-------------------*/
+
+
    +
  • In order to invoke some of the above tweaks, we will also create keyboard shortcuts and shortcodes to have a simpler way to change colour of the blockquote side border and highlights. So open logseq/config.edn and do the following:
  • +
+

a) Search for: +

1
+2
+3
+4
+5
+6
+7
+8
;; Macros replace texts and will make you more productive.
+;; Example usage:
+;; Change the :macros value below to:
+;; {"poem" "Rose is $1, violet's $2. Life's ordered: Org assists you."}
+;; input "{{poem red,blue}}"
+;; becomes
+;; Rose is red, violet's blue. Life's ordered: Org assists you.
+:macros {}
+

+

b) and replace above with: +

 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
;; Macros replace texts and will make you more productive.
+;; Example usage:
+;; Change the :macros value below to:
+;; {"poem" "Rose is $1, violet's $2. Life's ordered: Org assists you."}
+;; input "{{poem red,blue}}"
+;; becomes
+;; Rose is red, violet's blue. Life's ordered: Org assists you.
+:macros {
+ ">" "<blockquote class='$1'>$2</blockquote>" ;;usage {{ > orange,Text to be presented in the blockquote }}
+ "==" "<mark class='$1'>$2</mark>" ;;usage {{ == red,Text to be highlighted without linebreak }}
+}
+

+

c) search for: +

1
+2
+3
+4
+5
+6
+7
+8
;; Add custom commands to the command palette
+;; Example usage:
+;; :commands
+;; [
+;;  ["js" "Javascript"]
+;;  ["md" "Markdown"]
+;;  ]
+:commands []
+

+

d) and replace above with: +

 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
;; Add custom commands to the command palette
+;; To quickly call these commands, just type / (backslash) followed by characters in square bracket
+:commands [
+            ["bookmark                                        [.b]" [[:editor/input "{{ renderer :template, Bookmark}}" ]]],
+            ["date_today                                      [dt]" [[:editor/input "{{renderer :template, Date Today}}" ]]],
+            ["issue_table                                     [.it]" [[:editor/input "{{renderer :template, Issue_table}}" ]]],
+            ["issue                                           [.is]" [[:editor/input "{{renderer :template, Issue}}" ]]],
+            ["circle                                          [.c]" [[:editor/input "{{renderer :template-view, circle-template, :color orange}}" ]]],          
+            ["Blue Highlighter                                [=b]" [[:editor/input "<mark class='blue'></mark>" {:backward-pos 7}]]],
+            ["Green Highlighter                               [=g]" [[:editor/input "<mark class='green'></mark>" {:backward-pos 7}]]],
+            ["Gray Highlighter                                [=gra]" [[:edior/input "<mark class='gray'></mark>" {:backward-pos 7}]]],
+            ["Grey Highlighter                                [=gre]" [[:editor/input "<mark class='grey'></mark>" {:backward-pos 7}]]],
+            ["Orange Highlighter                              [=o]" [[:editor/input "<mark class='orange'></mark>" {:backward-pos 7}]]],
+            ["Pink Highlighter                                [=p]" [[:editor/input "<mark class='pink'></mark>" {:backward-pos 7}]]],
+            ["Red Highlighter                                 [=r]" [[:editor/input "<mark class='red'></mark>" {:backward-pos 7}]]],
+            ["Yellow Highlighter                              [=y]" [[:editor/input "<mark class='yellow'></mark>" {:backward-pos 7}]]],
+            ["Purple Highlighter                              [=pu]" [[:editor/input "<mark class='purple'></mark>" {:backward-pos 7}]]],
+            ["Red Blockquote                                  [>r]" [[:editor/input "<blockquote class='red'></blockquote>" {:backward-pos 13}]]],
+            ["Yellow Blockquote                               [>y]" [[:editor/input "<blockquote class='yellow'></blockquote>" {:backward-pos 13}]]],
+            ["Blue Blockquote                                 [>b]" [[:editor/input "<blockquote class='blue'></blockquote>" {:backward-pos 13}]]],     
+          ]
+

+
+

Warning

+

Now, some of the short-codes above such as /.is, /.it, /dt and /.c will not work just yet because we have not created their associated template. We will get to that in next section.

+
+

Create Templates

+
    +
  • Create a new page named templates.
  • +
  • Press Ctrl+K and search for templates and open the page.
  • +
  • Once on the page, click on Three dots in top right corner of the screen and from the drop down menu select Open in default app
  • +
  • Here paste the following then save and close the default app:
  • +
+
  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
+ 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
+ 52
+ 53
+ 54
+ 55
+ 56
+ 57
+ 58
+ 59
+ 60
+ 61
+ 62
+ 63
+ 64
+ 65
+ 66
+ 67
+ 68
+ 69
+ 70
+ 71
+ 72
+ 73
+ 74
+ 75
+ 76
+ 77
+ 78
+ 79
+ 80
+ 81
+ 82
+ 83
+ 84
+ 85
+ 86
+ 87
+ 88
+ 89
+ 90
+ 91
+ 92
+ 93
+ 94
+ 95
+ 96
+ 97
+ 98
+ 99
+100
+101
+102
+103
+104
+105
+106
+107
+108
+109
+110
+111
+112
+113
+114
+115
+116
+117
+118
+119
+120
+121
+122
+123
+124
+125
+126
+127
+128
+129
+130
+131
+132
+133
+134
+135
+136
+137
+138
+139
+140
+141
+142
+143
+144
+145
+146
+147
+148
+149
+150
+151
+152
+153
+154
+155
+156
+157
+158
+159
+160
+161
+162
+163
+164
+165
+166
+167
+168
+169
+170
+171
+172
+173
+174
+175
+176
+177
+178
+179
+180
+181
+182
+183
+184
+185
+186
+187
+188
+189
+190
+191
+192
+193
+194
+195
+196
+197
+198
+199
+200
+201
+202
+203
+204
+205
+206
+207
+208
+209
+210
+211
+212
+213
+214
+215
+216
+217
+218
+219
+220
+221
+222
+223
+224
+225
+226
+227
+228
+229
+230
+231
+232
+233
+234
+235
+236
+237
+238
+239
+240
+241
+242
+243
+244
+245
+246
+247
+248
+249
+250
+251
+252
+253
+254
+255
+256
+257
+258
+259
+260
+261
+262
+263
+264
+265
+266
+267
+268
+269
+270
+271
+272
+273
+274
        title:: templates
+        visibility:: false
+        icon:: 🧾
+
+        - # Circle
+          id:: 66b0d360-cde5-4e95-87b3-4e97a1e23bb5
+          template:: circle-template
+          template-including-parent:: false
+          arg-color:: red
+            - <span class='circle' style='background: ``c.args.color``; display: inline-block; width: 15px; height: 15px; border-radius: 50%; vertical-align: middle;'></span>
+        - # People Page
+          template:: people page
+          template-including-parent:: false
+            - tags:: people
+              icon:: 👨‍💼
+            - ## Tasks
+                - query-table:: true
+                     query-properties:: [:priority :deadline :block]
+                  #+BEGIN_QUERY
+                  {:title [:h3 "Owned"]
+                  :query [:find (pull ?b [*])
+                       :in $ ?tag
+                       :where
+                       [?b :block/marker ?marker]
+                       [(contains? #{"TODO" "DOING" "NOW" "LATER" "WAITING"} ?marker)]
+                       (page-ref ?b ?tag)
+                       [?ref :block/name "project"]
+                       (not [?b :block/refs ?ref])]
+                  :inputs [:query-page]
+                  :result-transform :add-task-attrs
+                  :breadcrumb-show? true
+                  :group-by-page? false
+                  :collapsed? false
+                  }
+                  #+END_QUERY
+                - query-table:: true
+                  collapsed:: true
+                     query-properties:: [:marker :deadline :block]
+                  #+BEGIN_QUERY
+                  {:title [:h3 "Closed or Cancelled"]
+                  :query [:find (pull ?b [*])
+                       :in $ ?tag
+                       :where
+                       [?b :block/marker ?marker]
+                       [(contains? #{"DONE" "CANCELLED" "CANCELED" } ?marker)]
+                       (page-ref ?b ?tag)
+                       [?ref :block/name "project"]
+                       (not [?b :block/refs ?ref])]
+                  :inputs [:query-page]
+                  :result-transform :add-task-attrs
+                  :breadcrumb-show? true
+                  :group-by-page? false
+                  :collapsed? true
+                  }
+                  #+END_QUERY
+        -
+        - # Issue Table
+          template:: Issue_table
+          template-including-parent:: false
+            - #.tabular
+                - ## 01 ``{|}``Issue Title``{|}``
+
+                  owner::
+                  status:: #Red 
+                  #Issues #.v-kanban
+                    - ### **Issue Description**
+                        -
+                    - ### **Updates**
+                        - <% Today %> :
+                - ## 02 Issue Title
+                  owner::
+                  status:: #Amber
+                  #Issues #.v-kanban
+                    - ### **Issue Description**
+                        -
+                    - ### **Updates**
+                        - <% Today %> :
+                - ## 03 Issue Title
+                  owner:: 
+                  status:: #Yellow
+                  #Issues #.v-kanban
+                    - ### **Issue Description**
+                        -
+                    - ### **Updates**
+                        - <% Today %> :
+                - ## 04 Issue Title
+                  owner:: 
+                  status:: #Green
+                  #Issues #.v-kanban
+                    - ### **Issue Description**
+                        -
+                    - ### **Updates**
+                        - <% Today %> :
+                - ## 05 Issue Title
+                  owner:: 
+                  status:: #on-hold 
+                  #Issues #.v-kanban
+                    - ### **Issue Description**
+                        -
+                    - ### **Updates**
+                        - <% Today %> :
+                - ## 06 Issue Title
+                  owner:: 
+                  status:: #no-go
+                  #Issues #.v-kanban
+                    - ### **Issue Description**
+                        -
+                    - ### **Updates**
+                        - <% Today %> :
+                - ## 07 Issue Title
+                  owner:: 
+                  status:: #closed
+                  #Issues #.v-kanban
+                    - ### **Issue Description**
+                        -
+                    - ### **Updates**
+                        - <% Today %> :
+        - # Issue
+          template:: Issue
+          template-including-parent:: false
+            - ## ``{|}``01 Issue Title``{|}``
+
+              owner::
+              status:: #Red
+              #Issues #.v-kanban
+                - ### **Issue Description**
+                    -
+                - ### **Updates**
+                    - <% Today %> :
+        - # Date Today
+          template:: Date Today
+          template-including-parent:: false
+            - **``today``** ``{|}``
+        - # [[Bookmarks]]
+          template:: Bookmark
+          template-including-parent:: false
+            - url:: ``{|}``
+
+              topic::
+        - # [[Project page]]
+          template:: project page
+          template-including-parent:: false
+            - tags:: project page
+              icon:: 📂
+            - ## Project Meta
+              collapsed:: true
+                - DOING [#B] #project <% current page %>
+            - ## Actions Log
+                - query-properties:: [:deadline :priority :block]
+                  #+BEGIN_QUERY
+                  {:title [:h4 "On ToDo List"]
+                  :query [:find (pull ?b [*])
+                       :in $ ?tag
+                       :where
+                       [?b :block/marker ?marker]
+                       [(contains? #{"TODO"} ?marker)]
+                       (page-ref ?b ?tag)
+                       [?ref :block/name "project"]
+                       (not [?b :block/refs ?ref])]
+                  :inputs [:query-page]
+                  :result-transform :add-task-attrs
+                  :breadcrumb-show? true
+                  }
+                  #+END_QUERY
+                - query-table:: false
+                  query-properties:: [:page :block]
+                  #+BEGIN_QUERY
+                  {:title [:h4 "Ongoing Tasks"]
+                  :query [:find (pull ?b [*])
+                       :in $ ?tag
+                       :where
+                       [?b :block/marker ?marker]
+                       [(contains? #{"DOING" "NOW" "LATER" "WAITING"} ?marker)]
+                       (page-ref ?b ?tag)
+                       [?ref :block/name "project"]
+                       (not [?b :block/refs ?ref])]
+                  :inputs [:query-page]
+                  :result-transform :add-task-attrs
+                  :breadcrumb-show? true
+                  :group-by-page? false
+                  :collapsed? false
+                  }
+                  #+END_QUERY
+                - query-properties:: [:deadline :priority :block]
+                  collapsed:: true
+                  #+BEGIN_QUERY
+                  {:title [:h4 "Completed Tasks"]
+                  :query [:find (pull ?b [*])
+                       :in $ ?tag
+                       :where
+                       [?b :block/marker ?marker]
+                       [(contains? #{"DONE"} ?marker)]
+                       (page-ref ?b ?tag)
+                       [?ref :block/name "project"]
+                       (not [?b :block/refs ?ref])]
+                  :inputs [:query-page]
+                  :result-transform :add-task-attrs
+                  :breadcrumb-show? true
+                  :table-view? false
+                  :collapsed? true
+                  }
+                  #+END_QUERY
+            - ## Issues Log
+                - query-sort-by:: status
+                     query-table:: true
+                     query-sort-desc:: true
+                     query-properties:: [:block :owner :status]
+                  #+BEGIN_QUERY
+                  {:title [:h4 "Open Issues"]
+                  :query [:find (pull ?b [*])
+                       :in $ ?query-page
+                       :where
+                       [?p :block/name ?query-page]
+                     [?tag2 :block/name "issues"]
+                     [?b :block/refs ?tag2]
+                     [?tag1 :block/name "closed"]
+                     (not [?b :block/refs ?tag1])
+                       [?b :block/refs ?p]
+                       [?ref :block/name "project"]
+                       (not [?b :block/refs ?ref])         
+                       ]
+                  :inputs [:query-page]
+                  :breadcrumb-show? false
+                  :table-view? true
+                  :group-by-page? false
+                  :collapsed? false
+                  }
+                  #+END_QUERY
+                - query-properties:: [:block :owner :status]
+                  collapsed:: true
+                  #+BEGIN_QUERY
+                  {:title [:h4 "Closed Issues"]
+                  :query [:find (pull ?b [*])
+                       :in $ ?query-page
+                       :where
+                       [?p :block/name ?query-page]
+                     [?tag2 :block/name "issues"]
+                     [?b :block/refs ?tag2]
+                     [?tag1 :block/name "closed"]
+                     [?b :block/refs ?tag1]
+                       [?b :block/refs ?p]
+                       [?ref :block/name "project"]
+                       (not [?b :block/refs ?ref])         
+                       ]
+                  :inputs [:query-page]
+                  :breadcrumb-show? false
+                  :table-view? true
+                  :group-by-page? false
+                  :collapsed? true
+                  }
+                  #+END_QUERY
+            - #+BEGIN_QUERY
+              {:title [:h2 "Project Notes"]
+              :query [:find (pull ?b [*])
+                   :in $ ?query-page
+                   :where
+                   [?p :block/name ?query-page]
+                   [?b :block/refs ?p]
+                 [?tag2 :block/name "issues"]
+                 (not [?b :block/refs ?tag2])
+                   [?ref :block/name "project"]
+                   (not [?b :block/refs ?ref])        
+                   (not [?b :block/marker _])
+                   ]
+              :inputs [:query-page]
+              :result-transform (fn [result]
+                               (sort-by (fn [b]
+                                          (get b :block/created-at "A")) result))
+              :breadcrumb-show? false
+              :group-by-page? true
+              :collapsed? false
+              }
+              #+END_QUERY
+        -
+
+

Contents page

+
    +
  • Press Ctrl+K and search for contents and open the page.
  • +
  • Once on the page, click on Three dots in top right corner of the screen and from the drop down menu select Open in default app
  • +
  • Here paste the following then save and close the default app:
  • +
+
 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
+26
+27
+28
+29
+30
+31
  - query-properties:: [:icon :page :updated-at]
+  #+BEGIN_QUERY
+  {:title [:h1 "⏳ Ongoing Projects"]
+    :query [:find (pull ?page [*])
+        :where
+          [?block :block/page ?page]
+          [?page :block/name ?pagename]
+          [?block :block/marker ?marker]
+          [(contains? #{"DOING"} ?marker)]
+          (page-ref ?block "project")
+          (not [?page :block/name "templates"])
+      ]
+      :breadcrumb-show? false
+      :collapsed? false}
+  #+END_QUERY
+
+  - query-properties:: [:icon :page :updated-at]
+    #+BEGIN_QUERY
+    {
+    :title [:h1 "👨‍💼People"]
+    ;; ---- Get every block into variable ?block
+    :query [:find (pull ?block [*])
+        ;; ---- filter command
+        :where
+        ;; ---- get page name (lowercase) from the special page block into variable ?pagename
+        [?block :block/name ?pagename]
+        ;; ---- Select if block is a special page block and has a single property (arg1) with value arg2
+        (page-property ?block :tags "people")
+    ]
+    }
+#+END_QUERY
+
+

Sample Content Page

+

screenshot_20240813-152544

+

Usage

+

Sample Day to Day Capture

+

Screenshot_13-8-2024_151147_

+

Create Project

+
    +
  • To create a CARDIO Log for a project, first create a new page and give it name of the Project.
  • +
  • Next, open the newly created project page.
  • +
  • Once opened, press Ctrl+T and select project page template and press Enter.
  • +
  • This will create all relevant sections for the Project.
  • +
+

Create User Page

+
    +
  • Create a new page with the user / resource name.
  • +
  • Open the newly created page.
  • +
  • Once opened, press Ctrl+T and select people page template and press Enter.
  • +
  • This will create all relevant sections to track actions for this resource.
  • +
+

Create New Issues

+
    +
  • Now for everyday usage, each time there is a new issue or a number of issues they can simply be recorded by typing /.is for single issue and /.it for a table of issues.
  • +
  • +

    Once the issue or issues table is created, all usual fields can be filled but most importantly the issue title must include hashtag for the project that the issue belongs to.

    +
      +
    • So an issue for project called North Star must have #[[North Star]] in the title.
    • +
    • As an example say the issue is to do with lack of resources then the title should be #[[North Star]]: Lack of Resources
    • +
    • +

      The issue status or any topic to be highlighted with RAG status can be tagged with following tags:

      + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
      TagCircle Appended
      #red🔴
      #amber🟠
      #green🟢
      #on-hold🟣
      #yellow🟡
      #Closed🔵
      #no-go🚫
      +
    • +
    +
  • +
  • +

    Circles with additional colours can be created using the template. To do so, press /.c and press Enter. This should then put following text on the editor: {{renderer :template-view, circle-template, :color orange}}

    +
  • +
  • +

    Now just replace orange in above with the colour desired.

    +
  • +
+

Maintain Issues Log

+
    +
  • Reviewing the logs is as simple as opening the Contents page, clicking the project name and scrolling down to the Issues Log section.
  • +
  • Now if there are actions identified during the review of an issue, they can be simply added in the updates section under the date of review which can be quickly added by pressing Ctrl+Shift+D and then noting down the action as a sub bullet TODO [#A] Do Something /deadline
  • +
  • I usually log decisions against a logged issue or if they resulted from a general discussion, just as #<projectname> #decision.
  • +
+

Day to day notes

+
    +
  • All the project relevant notes will be automatically collected on the project's page so long as they have been tagged with the project name so for the day to day notes all that is required is add entires in the Journal.
  • +
+

Blockquotes

+
    +
  • +

    As we added css for different coloured borders of blockquotes and also created shortcode and keyboard shortcut we can do this in multiple ways:

    +
      +
    • Type />band press Enter. This will place <blockquote class='blue'></blockquote> and the quote can be written between the tags.
    • +
    • Type the following, replacing yellow with one of the predefined colours: yellow, pink, blue, green, red, grey, gray, orange or purple. + {{> yellow,Some yellow quote}}
    • +
    +

    image

    +
  • +
+

Coloured Highlight

+
    +
  • Type /=y, select Yellow Highlighter from pop-up menu and press Enter. This will place <mark class='yellow'></mark> and text to be highlighted can be written between the tags. It will be presented as Yellow Highlight
  • +
  • Type the following and it will be presented as This text is highlighted. + {{== pink,pink highlight}}
  • +
+

image

+ + + + + + + + + + + + + + + + + + + + + +

+ + + + Comments +

+ + + + + + + +
+
+ + + + + +
+ + + +
+ + + +
+
+
+
+ + + + + + + + + + + + \ No newline at end of file diff --git a/2024/12/10/bluesky-for-comments-on-mkdocs-blog.html b/2024/12/10/bluesky-for-comments-on-mkdocs-blog.html new file mode 100644 index 0000000..9ae18a4 --- /dev/null +++ b/2024/12/10/bluesky-for-comments-on-mkdocs-blog.html @@ -0,0 +1,1915 @@ + + + + + + + + + + + + + + + + + + + + + + + + + Bluesky for comments on mkdocs blog - Make Gadgets Work + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + Skip to content + + +
+
+ +
+ + + + +
+ + +
+ +
+ + + + + + + + + +
+
+ + + + + + + + + + + +
+
+ +
+
+ + + +
+
+
+
+ + +
+
+
+
+ + + + + + + + + + + + + + + + + + + + +

Bluesky for comments on mkdocs blog

+ +

Background

+

I have recently migrated this blog from Jekyll to Mkdocs using material theme. I was also planning to move away from Disqus commenting system given all the known issues and just when I had finished the migration to Mkdocs, there was this post from Emily Liu- which showcases how easily the bluesky replies to a post can be added in comments section of a blog page. Followed closely by this was a post from Cory Zue who created NPM packaging. I initially was looking to use that on my blog and while searching for that package on jsdelivr I cam eacross the bluesky-comments-tag: a package created by Matt Kane.

+

I understood the code behind this much better and that was my starting point. There were two problems with the solution thus far that I wanted to solve.

+

Problem Description

+
    +
  • +

    Problem 1: (SOLVED) For the comments to appear on the blog, the bluesky post url needs to be added to the frontmatter. Now that is a cyclic process because you can't post the url of your blog post on bluesky until you have published it.

    +

    So the workflow would need to be:

    +

    create a blog postpublish the blog postcreate a bluesky postcopy the bluesky post urlpaste it in the frontmatter of blog postrepublish the blog postcomments thread activated on blog post

    +

    This is all too cumbersome and I would much rather the comments were visible once the post was published and if the bluesky post existed for that blog post URL.

    +

    This I have solved to an acceptable flow as shown below and the "How?" is explained in this post.

    +

    create a blog postpublish the blog postcreate a bluesky postcomments thread activated on blog post

    +
  • +
  • +

    Problem 2: (WIP) Solving the above, still means that unless the author has created a bluesky post, comments are not open for that blog post. I am working on a way to automate that using blusky API but it is still very much work in progress.

    +

    create a blog postpublish the blog posttrigger creation of bluesky post automaticallycomments thread activated on blog postyesPost deployed?

    +
  • +
+

Algorithm for Solution 1

+
    +
  1. Take the blog post URL active in the browser.
  2. +
  3. Search on bluesky for the oldest post created by the author containing this URL.
  4. +
  5. Use that URL to display comment thread.
  6. +
  7. If no such URL is found, display a message saying No Bluesky Comments thread found for this post.
  8. +
+

Obtain your DID

+

DID is short for Decentralized identifier.

+
    +
  1. +

    Login to you account on https://bsky.app/

    +
  2. +
  3. +

    Click "Settings" .

    +

    Click "Settings" ....

    +
  4. +
  5. +

    Click "Account" .

    +

    Click "Account" ....

    +
  6. +
  7. +

    Click "Handle" .

    +

    Click "Handle" ....

    +
  8. +
  9. +

    Click "I have my own domain" .

    +

    Click "I have my own domain" ....

    +
  10. +
  11. +

    Now on resulting screen copy the value shown against did and then press Cancel.

    +
  12. +
+

Javascript

+

The required javascript for this code is available in the repo of my blog here. Copy all the content which will be following:

+
docs/assets/js
js
+ |-- src
+ |    |-- BlueskyComments.js
+ |-- bluesky-comments.mjs
+ |-- custom-elements.json
+
+

Modify javascript

+

Now step 2 of algorithm Search on bluesky for the oldest post created by the author containing this URL meant I have done some bit of hardcoding. I can do away with it in due course but for the moment, anyone planning to reuse the code must modify the following code, specifically the highlighted lines:

+
 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
async #loadComments() {      
+  // Get the current URL
+  const currentUrl = window.location.href;
+  // Create a URL object
+  const deconstructedurl = new URL(currentUrl);
+  // Extract the pathname and hash
+  const extractedPart = deconstructedurl.pathname //+ deconstructedurl.hash;
+  console.log(extractedPart); // Output: "/2023/03/29/read-excel-csv-recursive.html#displays-extracted-output"
+  const assembledUrl = "https://mgw.dumatics.com"+extractedPart // (1)
+  console.log(assembledUrl)
+
+  const query = encodeURIComponent(assembledUrl);
+  const author = encodeURIComponent("did:plc:y24b7ow3kvvkm6lpgcbh2j2o"); //(2)
+
+
    +
  1. Change this to your own domain
  2. +
  3. Change this to your own did.
  4. +
+

CSS

+

Make sure you update your extra.css so the comments match your theme colours. The css rules associated with bluesky comment are ,clearly marked in docs/assets/stylesheets/extra.css on my repo. However, specifically following rules must be added to ensure light and dark theme work well:

+
light theme
 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
:root {
+  /* Default Bluesky commenting*/
+  --bluesky-font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
+  --bluesky-font-size: 16px;
+  --bluesky-text-color: #333;
+  --bluesky-handle-color: #888;
+  --bluesky-footer-text-color: rgb(111, 134, 159);
+  --bluesky-bg-color: #fff;
+  --bluesky-hover-bg: #f0f0f0;
+  --bluesky-spacing-xs: 6px;
+  --bluesky-spacing-sm: 10px;
+  --bluesky-spacing-md: 14px;
+  --bluesky-spacing-lg: 19px;
+  --bluesky-avatar-size: 36px;
+  --bluesky-avatar-bg: #e0e0e0;
+
+  /* Comments Structure */
+  --bluesky-reply-border-width: 2px;
+
+  /* Footer */
+  --bluesky-footer-font-size: 15px;
+  --bluesky-icon-size: 18px;
+  --bluesky-border-color: var(--md-hr-color);
+}
+
+
dark theme
 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
[data-md-color-scheme=slate] {
+    /* For bluesky comments */
+    --bluesky-bg-color: hsla(var(--md-hue),15%,14%,1);
+    --bluesky-font-family: system-ui, sans-serif;
+    --bluesky-font-size: 16px;
+    --bluesky-text-color: #cdd6f4;
+    --bluesky-handle-color: #a6adc8;
+    --bluesky-footer-text-color: #7f849c;
+    --bluesky-border-color: var(--md-hr-color);
+    --bluesky-hover-bg: #181825;
+    --bluesky-spacing-xs: 6px;
+    --bluesky-spacing-sm: 10px;
+    --bluesky-spacing-md: 14px;
+    --bluesky-avatar-size: 36px;
+    --bluesky-avatar-bg: #313244;
+    --bluesky-reply-border-width: 2px;
+    --bluesky-footer-font-size: 15px;
+    --bluesky-icon-size: 18px;
+}
+
+

Comments Template

+

Now my comments template currently has some code that ensures the comments from disqus are displayed but assuming that a typical deployment would only need bsky comments, following code should suffice.

+
+

Important

+

Please do make sure you remove space after first curly bracket in each of the highlighted lines so { % becomes {% and { { becomes {{. Refer to code in my repo for clarity.

+
+
 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
{ % if page.meta.bsky %}
+<h2 id="__comments">
+  <span class="twemoji" style="margin-right: 10px;">
+    { % include ".icons/fontawesome/brands/square-bluesky.svg" %}
+  </span>
+  { { lang.t("meta.comments") }}
+</h2>
+<script type="module" src="{ { '/assets/js/bluesky-comments.mjs' }}"></script>
+<bluesky-comments url={ { page.meta.bsky }}>
+</bluesky-comments>
+{% endif %}
+
+
+

Tip

+

This ofcourse means that in your frontmatter of the post there must be an entry for bsky. For example, frontmatter of this post is:

+
post fronmatter
title: "Bluesky for comments on mkdocs blog"
+slug: "bluesky-for-comments-on-mkdocs-blog"
+date: 
+  created: 2024-12-10 21:57:00
+bsky: true
+
+
+

Update mkdocs.yml

+

Finally, update the mkdocs.yml to include the javascript and css file like so:

+
mkdocs.yml
extra_css:
+  - assets/stylesheets/extra.css
+
+extra_javascript:
+  - assets/js/bluesky-comments.mjs
+
+

This should do then get the comments thread from bluesky on your mkdocs blog or socumentation site. Do let me know if something is not working and I will try to help troubleshoot.

+ + + + + + + + + + + + + + + + + + + + + +

+ + + + Comments +

+ + + + + + + +
+
+ + + + + +
+ + + +
+ + + +
+
+
+
+ + + + + + + + + + + + \ No newline at end of file diff --git a/404.html b/404.html new file mode 100644 index 0000000..07e576f --- /dev/null +++ b/404.html @@ -0,0 +1,1380 @@ + + + + + + + + + + + + + + + + + + + + + Make Gadgets Work + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+
+ +
+ + + + +
+ + +
+ +
+ + + + + + + + + +
+
+ + + +
+
+
+ + + + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ +

404 - Not found

+ +
+
+ + + + + +
+ + + +
+ + + +
+
+
+
+ + + + + + + + + + + + \ No newline at end of file diff --git a/CNAME b/CNAME new file mode 100644 index 0000000..7762b7c --- /dev/null +++ b/CNAME @@ -0,0 +1 @@ +mgw.dumatics.com \ No newline at end of file diff --git a/about/index.html b/about/index.html new file mode 100644 index 0000000..a3159da --- /dev/null +++ b/about/index.html @@ -0,0 +1,1880 @@ + + + + + + + + + + + + + + + + + + + + + + + + + Ankit Mittal - Make Gadgets Work + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + Skip to content + + +
+
+ +
+ + + + +
+ + +
+ +
+ + + + + + + + + +
+
+ + + + + + + + + + + + + + + +
+
+ + + + + + + + + + + + + + + + + + + + +

Ankit Mittal

+ +
+ + Ankit + +
+ +
+

EXECUTIVE SUMMARY

+
+

Results-oriented Programme Manager, with over 21 years of experience in +implementation and management of complex IT and Business Transformation +initiatives. Passionate in the delivery of quality services and +demonstrated ability to influence senior management decisions. History +of results and progressive career growth through hands-on leadership, +elimination of barriers and teamwork skills.

+
+

KEY COMPETENCIES

+
+
    +
  • MSP registered practitioner. (Registration Number: MSPR/052974)
  • +
  • PRINCE 2 practitioner. (Registration Number: P2R/220948)
  • +
  • Lean and DFSS (Design for Six Sigma) certified.
  • +
  • Strong Stakeholder Management with ability to provide consultancy to + Senior Management
  • +
  • Commercially aware with an ability to translate technical jargon + from engineering teams into plain English for the benefit of + non-technical stakeholders and decision makers to ensure constant + and timely flow of relevant information across the project.
  • +
  • Experience of working and managing projects in both accelerated + (Agile) as well as controlled (waterfall) development models + and delivering in traditional on-prem as well as DevOps driven + cloud deployments
  • +
  • Managing multiple projects and programmes with large remote + team size (80+)and budgets of up to 12M GBP.
  • +
  • Over 15 years of Business Transformation Programme and Project + Management experience spanning across Financial Services + (Insurance and Banking), Aero-space, Manufacturing, Printing and + FMCG sectors.
  • +
  • Proven technical expertise in database technologies (MSSQL, MySQL + and various BI Suites), web technologies and using Python + for data analysis
  • +
  • Thorough knowledge of DevOps methodology and associated technologies + (Git, Github, GitLab, Ansible, Puppet, Jenkins, Selenium, Kubernetes) + and platforms (Azure, AWS).
  • +
+
+

PROFESSIONAL EXPERIENCE

+
+

Client: HP, various Govt. and Financial institutes across UK and Europe through IT Alliance.

+

Role: Senior Project Manager/Programme Manager/Management Consultant, 11/2014 - Till Date

+
    +
  • Responsible for Print Modernisation and Transformation + programme for two well known high street banks.
  • +
  • Delivered MPCS (Managed Print Cloud Service) for several + organisations including two international logistics provider, + a UK govt. organisation and Nordics postal service provider.
  • +
  • Delivered MPS (Managed Print Service) for two of the + well known Financial Institute in the UK with a global + presence across all continents with key focus on + EMEA, APAC and United States.
  • +
  • Driven the creation and maintenance of Knowledge Base + for North West Europe Market for print business operations + while capturing, creating and implementing business processes + delivering better customer experience and improved team + satisfaction.
  • +
  • Outlined and delivered the complete PMO governance process in line + with programme management best practices.
  • +
  • Matrix managed large teams - typically including a team of + upto 2 - 10 country project managers, 3 technical + consultants, 2 - 20 technicians, 2 PMO support staff, 2 - 4 Helpdesk + Operations Managers, 3 - 6 developers and 2 - 4 project support officers.
  • +
  • Engaged with operational and technical stakeholders from + multiple organisations to deploy Print Modernisation and + Transformation programmes as well as associated process changes, + embedding the helpdesk and implementing changes with benefit tracking.
  • +
  • Delivered new processes within HP to benefit tracking at 84% saving + with an overall reduction in effort of nearly 25 man-days each + month.
  • +
+
+

Client: Unilever, UK through CloudTalent.

+

Role: Senior Project Manager, 05/2014 = 10/2014

+
    +
  • Engaged to manage a vendor selection and procurement project aimed + at delivering a new infrastructure, PaaS and SaaS contract for its + Enterprise Compute Service current mode of operation with a view to + be prepared for future mode of operation and associated + transformation.
  • +
  • Responsible for planning, monitoring and reporting progress of the + project, stakeholder communication and engagement, weekly reports to + senior stakeholders at Unilever and CloudTalent, maintaining RAID + log, resourcing and managing project costs for CloudTalent.
  • +
  • Managed the delivery of RFP to strict deadlines under high pressure + environment (5 months project).
  • +
  • Engaged stakeholders based in different geographies and managing + their expectations.
  • +
  • Prepared detailed weekly report slide decks and used them to drive + structured weekly client and internal delivery review.
  • +
  • Organised confidential RFP related activities and communication with + various internal and external stakeholders.
  • +
  • Managed and reported on costs and resource forecasts.
  • +
  • Facilitated communication between CloudTalent and Unilever Subject + Matter Experts to expedite decisions.
  • +
+
+

Client: Brammer PLC, Knutsford, UK

+

Role: Programme Manager, 04/2013 - 04/2014

+
    +
  • Engaged to set-up and manage the Trading Platform Programme aimed to + unify the IT infrastructure and a Target Operating Model in line + with both financial and technical strategic business goals.
  • +
  • Responsible for planning and monitoring the time, cost and quality + for the programme, stakeholder communication, identifying and + embedding change within the organisation with minimal operational + disruption, provide structured governance for organisation wide + projects that impacted the programme vision, reporting programme + progress to Senior Management Team and Board of Directors.
  • +
  • Rolled out the new service management framework that involved + migration of secondary and tertiary IT support from Manchester to + the IT co-sourcing partner based in Poland while keeping the primary + support in Manchester.
  • +
  • Rollout of iPAD across UK, Germany and Europe as a Sales Support + Tool integrated with the Microsoft Dynamics (CRM).
  • +
  • Implemented Enterprise Service Bus, Master Data Management database, + Transactions Database, Stock Database, abstracted layer for + localised ERP systems in different countries, Brammer e-shop for + European subsidiaries (Spain, Germany, Italy, Belgium, Poland and + Czech)
  • +
  • Initiated business transformation programme for pan-European Key + Accounts operations of Brammer aiming to deliver a target operating + model that supports collaborative cross border order management + while maximising savings for customer as well as Brammer.
  • +
  • Set-up project management internally within Brammer PLC and resource + management practices with IT co-sourcing partners, mentored Junior + Project Managers and PMO Manager to establish regular reporting, + developed and introduced dashboard templates and Graduate Trainee on + techniques involved in Business Analysis such as six sigma + workshops, process mapping.
  • +
+
+

Employer: Amor Transportation Sector, Daresbury, UK

+

Role: Programme Manager and Senior Project Manager, 11/2011 - 04/2013

+
    +
  • Engaged to deliver the programme aimed to development of a new + product called Chroma Human Resource Management System (CHRMS) as an + addition to the existing offering of Chroma Software Suite in + partnership with Manchester Airport while delivering implementation + of Chroma suite at several other airports in the UK and Europe.
  • +
  • Responsible for planning, monitoring and reporting the progress of + programme and other implementation projects to external and internal + stakeholders, optimising resource usage between programme and other + projects, negotiating costs for change requests, agreeing delivery + time-frame and roadmap for enhancement requests, pre-sales support.
  • +
  • Managed the complete development and delivery of new modular product + called Chroma Human Resource Management System (CHRMS).
  • +
  • Managed business change within Transport Sector to deliver new + governance and streamline culture between FS Walker-Hughes and Amor + Group post the merger of two companies.
  • +
  • Managed Infrastructure and Interface development projects part of + the EAL Separation programme.
  • +
  • Delivered AMOR\'s Chroma Airport Suite software across national and + international airports.
  • +
  • Provided responses to RFP's for Australian, Norwegian, Dubai and + American airports with two successes while others were yet to + decide.
  • +
+
+

Employer: Barclays Corporate, Northwich, UK.

+

Role: Project Manager (Business Transformation and IT), 05/2008 - 09/2011

+
    +
  • Engaged to deliver business transformation and change initiatives + undertaken by the Operational Excellence Team of Barclays Corporate.
  • +
  • Responsible for planning, monitoring and reporting of projects, + adhering to project management practice guidelines, ensure + commercially realisable benefits.
  • +
  • Delivered a servicing activities hand-off model that created + capacity within sales realising a benefit of £350,000 in 3 months.
  • +
  • Managed end to end rollout of Siebel and TotalView Workforce + Management system.
  • +
  • Managed the move of IT support services, phased migration from + legacy to strategic VOIP solutions
  • +
  • Delivered the secure email solution for communication between + BARCORP and its business partners.
  • +
  • Delivered business transformation project aimed at segmentation of + client base following the organisation restructure
  • +
  • Delivered an enhanced complaints management process framework within + 6 months making the Barclays Corporate compliant to Regulatory + requirements and closing over 70 audit observations.
  • +
  • Conducted feasibility study with an aim to recommend product that + will enable Automated KYC (Know Your Customer) and provided complete + recommendation.
  • +
+
+

Client: Aviva (then Norwich Union), York and Norwich, UK through Tata Consultancy Services.

+

Role: IT Project Manager, 07/2005 - 12/2007

+
    +
  • Engaged to deliver development project, enhancement and support + services to agreed cost, schedule and quality for Financial Services + Applications Group within NUIT.
  • +
  • Responsible for planning and governance of assigned projects, + managing staff and third party service providers and vendors, + identify resource requirements to feed into the business planning + process and interface between Business and IT community.
  • +
  • Managed an electronic funds transfer systems migration project aimed + at replacing the 9 stand-alone systems through an enhanced Payment + Preparation System to cater for Chaps, Inter account Transfers, + International Payments and Foreign drafts.
  • +
  • Managed policies migration with process to ensure phasing out of + retained policies.
  • +
  • Enhancements to the Payment Preparation System to transmit payments + via Hexagon ABC and ensure conformance to European Payments Council + Resolution of 2005 in respect of Euro cross-border payment standard + (BIC & IBAN).
  • +
  • Migration of old accounts from NU Cash-book to Heritage Cash-book + under Swiss-Re arrangement.
  • +
  • Delivered the system upgrade of finance application suite.
  • +
+

Client: GE Healthcare and Amersham Biosciences, Sweden through Tata Consultancy Services.

+

Role: IT Projects and Engagement Manager, 07/2004 - 07/2005

+
    +
  • Engaged to deliver the knowledge acquisition from Amersham + Biosciences IT Team based in Sweden and facilitate move of IT + Support Services to offshore.
  • +
  • Responsible for working with client services and business partners + to ensure a seamless transition, stakeholder management, resource on + boarding, progress reporting, to be the escalation point, managing + enhancement projects.
  • +
  • Built and delivered a successful outsourcing model aimed at reducing + the IT department's 2004 operating costs by 30%.
  • +
  • Created a new work-flow for production implementation teams to + prioritize requests. Integrally involved with core team that + improved the process to separate support from development requests
  • +
  • Reduced the support team's work-load by over 70% in two-months from + the original 300+ weekly requests by introducing new workflow and + collecting appropriate management information. As a result, team was + able to focus more on value-added enhancements and less on recurring + issues.
  • +
  • Managed complex enhancement projects involving multiple technologies + (JAVA, JDE, Websphere, Apache) with teams based at Sweden, UK, India + and US.
  • +
+
+

Client: Tata Consultancy Services. Support Groups, Mumbai, India

+

Role: Project Leader, ESS-Ultimatix, 09/2003 - 07/2004

+
    +
  • Project documentation, quality assurance, impact analysis, resource + planning related to Change Request and enhancements.
  • +
  • Server Administration, Database design and maintenance.
  • +
+
+

Client: Government of Andhra Pradesh, Hyderabad, India through Tata Consultancy Services.

+

Role: Developer/Module Leader, SmartGov, 04/2001 - 09/2003

+
    +
  • Designed, Developed and Tested Applications using PL/SQL stored + procedures, Lotus Script, JAVA, Javascript
  • +
+
+ + + + + + + + + + + + + + + + + + + + + + +
+
+ + + + + +
+ + + +
+ + + +
+
+
+
+ + + + + + + + + + + + \ No newline at end of file diff --git a/archive/2009.html b/archive/2009.html new file mode 100644 index 0000000..6dc7457 --- /dev/null +++ b/archive/2009.html @@ -0,0 +1,1762 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + 2009 - Make Gadgets Work + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + Skip to content + + +
+
+ +
+ + + + +
+ + +
+ +
+ + + + + + + + + +
+
+ + + +
+
+
+ + + + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + +
+

2009

+
+ + + + + + + + + + + + + +
+
+ + + + +
+
+

O2 XDA Serra - Official HTC upgrade with TF3D of Touch Pro 2

+

Background

+

Ever since I laid my hands on O2 XDA Serra aka HTC Raphael aka HTC Touch Pro , I have always loved the device despite it’s limitations on battery life and have found some really useful apps during my association with windows mobile (for last five years or so) which found their way onto Serra as well. While I have known for a while that we can flash ROMS, I was perhaps over protective when it came to Serra and never really went beyond doing a HardSPL for device but last Sunday when I was fiddling with the facebook application and SMS registration, I realised that each time I sent an SMS and then tried to open the internet explorer or opera they will just crash demanding a soft reset. I could instantly find the problem to be Kaspersky Anti-Virus(KAV) failure. It is not possible to remove KAV without a hard reset at least not to my knowledge. (A hard reset is what brings your device back to factory conditions). Now since a Hard Reset would mean lot of work in terms of reinstalling all my beloved apps etc, I figured I might as well see if there is some other ROM I could use. This is what triggered my quest.

+ + + + +
+
+ +
+
+ + + + +
+
+

Sony VAIO N-VIDIA setup to make S-Video work

+
+

UPDATE for 9.10

+

For Ubuntu 9.10 I was just not able to get it work and finally I figured it out. The problem is with the latest Nvidia driver, the one that ubuntu says recommended. In order to make your TV Out work, you must uninstall the latest release version and install the one just lower than that. (The working version for me was 173.) Then follow these steps: +

1
+2
+3
sudo nvidia-xconfig
+sudo reboot
+sudo nvidia-settings
+
+Then follow the steps shown below. If you are lucky you wont need to refer Section A of this post at all. I did not have to do that.

+
+ + + + +
+
+ +
+
+ + + + +
+
+

Sony VAIO FE21H Webcam on skype

+

Alright friends not a huge tip but still I, being new fan of Ubuntu, installed it on my media laptop...Sony VAIO...

+

One might ask what is media laptop...well I had a bit of problem with my Sony VAIO some 1.5 years back and a key got stuck and as it goes with any Sony product there was no cheap solution at hand...

+

At that time for some reason it appeared like a motherboard issue and I just bought another Dell laptop...later I realised that it was a keyboard issue so I invested in a wireless keyboard and had two lappies at home just like that 😄... I figured that with wireless keyboard VAIO laptop will make perfect companion to my 32' Samsung and hence it got the name of media laptop.

+ + + + +
+
+ +
+
+ + + + +
+
+

Get Gmail as Push Email on Sony P990i

+

While I have moved on from P990i which is now in safe hands of my dear wife, I remember doing some good amount of web searching and still had no idea how to set up push-mail on P990i.

+

I then thought I will configure a mail anyway and to my surprise the device is capable of enabling push-mail on it`s own as I found while fiddling along. Anyway I have configured push-mail for wifey dear and she was mighty impressed. 😄

+

The steps I followed are as below:

+ + + + +
+
+ +
+
+ + + + +
+
+

AAO A150L Step by Step Installation Process for Linux4one

+

Linux4one is the only Linux distro other than pre-installed Linpus that worked out of the box on my and two other Acer aspire one A150L which belonged to my friends. With the GUI same as that for Ubuntu Netbook Remix, it is a fantastic OS to have on your machine. The only downside I felt was that the icons looked little washed out compared to those on actual UNR but that is not so much of an issue. Everything pretty much worked out of the box which matters the most. I did find the splash screen at the time of start-up a little childish but again these are not major issues.

+ + + + +
+
+ + + + + + + + + + + + +
+
+ + + + + +
+ + + +
+ + + +
+
+
+
+ + + + + + + + + + + + \ No newline at end of file diff --git a/archive/2010.html b/archive/2010.html new file mode 100644 index 0000000..7cb43b4 --- /dev/null +++ b/archive/2010.html @@ -0,0 +1,1814 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + 2010 - Make Gadgets Work + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + Skip to content + + +
+
+ +
+ + + + +
+ + +
+ +
+ + + + + + + + + +
+
+ + + +
+
+
+ + + + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + +
+

2010

+
+ + + + + + + + + + + + + + + +
+
+ + + + +
+
+

Jagannath Hora on Linux - Play on Linux Magic

+

Background

+

While I prefer Maitreya as it can run with Linux as native, quite a few of my friends have asked me if Jagannatha Hora will work on Linux and hence this post.

+

Right, when I tried last time around mid Oct'09, JHora was not working using wine. However, recently things have changed. +If you are not aware or have not used Play On Linux then you should try it out. It works. Anyway back to original topic, the steps to install Jagannatha Hora on Kubuntu 10.04 are as below. (I am mighty sure this will work on other linux machines but I have tried it only on Kubuntu 10.04 which is increasingly becoming my OS of choice.)

+ + + + +
+
+ +
+
+ + + + +
+
+

Install Maitreya - Vedic Astrology Software on Ubuntu / Kbuntu

+

First I must thank the Maitreya developers for coming up with such a wonderful Vedic Astrology software that works on Linux. I am not aware of any other vedic astrology software that works on Linux.

+

Next I have to say that the install instructions on the site (and in the tarball) are not written with a newbie in mind and I had to struggle a bit before I could get it to work. The compiling of source code is really similar for I guess all source codes in general on linux and in particular in Ubuntu / Kubuntu but thing is when you are new to the platform and probably have almost all required software in repositories you rarely come across a situation where you need to compile from a source code to install your favourite software. Anyway so without wasting time in chit-chat, below is the step by step installation of Maitreya 6 on Ubuntu / Kubuntu.

+ + + + +
+
+ +
+
+ + + + +
+
+

Audio problem in Wine under KDE 4.4.1 - Solved

+

I was trying to install spotify on linux which used to work perfectly on Gnome and when I tried on KDE it was giving error box. I wrote winecfg on terminal and got following error:

+
+

Error

+

fixme:jack:JACK_drvLoad error loading the jack library libjack.so.0, please install this library to use jack

+
+ + + + +
+
+ +
+
+ + + + +
+
+

Configure BlogTK 1.0 for blogger

+

Right so I am happy with Blogilo, then why BlogTK?

+

It so happened that I was trying to edit my last post and for some unknown reason I was getting error in updating through Blogilo. I was not able to update through Blogilo and I think it is because of the snapshots included in the post.

+ + + + +
+
+ +
+
+ + + + +
+
+

KUBUNTU Blog Entry

+

This is how my love for Kubuntu has started....and growing by the minute...

+

I was experimenting with different Linux distros and then I went ahead with Linux Mint – which is quite popular and is know as “ubuntu done right”. I liked it and started dwelling more and played around with themes and such before I figured it is becoming kind of prescriptive and thought will actually play with the a distro with KDE not too far from Ubuntu – Kubuntu and boy was I surprised with the difference … all to their own opinion but mine is KDE is one helluva beautiful thing....the stuff that is done here is simply amazing …. alright once I was done with dropping my jaw at every step I figured there is a bit of learning curve right from the word go and so before I start forgetting what all I have learnt I felt it will be safe jotting it all down and thus this post.

+

Right so if any of this catches your fancy, please be my guest... 😄

+ + + + +
+
+ + + + + + + + + + + + +
+
+ + + + + +
+ + + +
+ + + +
+
+
+
+ + + + + + + + + + + + \ No newline at end of file diff --git a/archive/2011.html b/archive/2011.html new file mode 100644 index 0000000..11d53ed --- /dev/null +++ b/archive/2011.html @@ -0,0 +1,2024 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + 2011 - Make Gadgets Work + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + Skip to content + + +
+
+ +
+ + + + +
+ + +
+ +
+ + + + + + + + + +
+
+ + + +
+
+
+ + + + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + +
+

2011

+
+ + + + + + + + + + + + + + + +
+
+ + + + +
+
+

Access (files / folders) Directories from Linux Mint on Android

+

Fig-1

+

Let me clear the air before anyone mentions. Yes, I know it's directories and not folders and yes I know many still call these folders and this may encourage the wrong usage but my aim is to help not educate but hopefully making this the first line in post will have some educational effect 😄.

+ + + + +
+
+ + + +
+
+ + + + +
+
+

Install Open Workbench and JRE on Wine in Linux Mint

+

What is Open Workbench?

+

Open Workbench is a Project Planning Software comparable to Microsoft Project. There are mixed views on whether it is truly open source or not but as it is considered a very good alternative for Microsoft Project and it is free to download, it is something I as a Project Manager would want to know about. After all some of my clients are going to be using this. :)

+ + + + +
+
+ +
+
+ + + + +
+
+

How to edit PDF in Linux - The easy way.

+

Background

+

Recently someone asked this question to me and after some search on Google I came across mentions of PDFedit, Scribus, flpsed, Gimp, PDFMod and even the openoffice plugin for importing PDF and I tried all of these. PDFescape is probably a good solution but is only online and limited to 30 pages in it's free avatar. If your aim is similar to the one stated below, these tools are way too complicated (PDFedit), limiting (PDFescape) or ineffective (rest of them). +So, Let's start with the usual what was my aim question.

+
+

Objective

+

To be able to highlight and put his own annotations, highlights and comments in the lecture notes that he gets from his college lecturers in pdf and be able to print the amended notes from any platform. (Linux / Mac / Windows.)

+
+ + + + +
+
+ +
+
+ + + + +
+
+

Part 2 - Configure Epson S515W on Linux Mint / Ubuntu 10.04

+

While the majority of settings are covered in my previous post here I found that each time I switched off my printer, it used to change it's IP address and I had to repeat Steps 7 to 16 each time. My printing and scanning needs are pretty limited so I never bothered working out a solution until today and knowing what I now know, it was quite a simple and quick solution I should have done this on the first place. Anyway as they say better late than never. 😄

+

So to carry out the magical transformation to your printing experience follow the steps below:

+ + + + +
+
+ +
+
+ + + + +
+
+

Glympse

+

Every now and then we come across something that has a strong potential, something that is a great phenomenon in making and Glympse to me is that idea.

+

The simple to use and highly effective application available on android market for free that allows you to share your position during the travel in real time.

+ + + + +
+
+ +
+
+ + + + +
+
+

MMC Convert media files on Linux

+

I have been using Mobile Media Converter for almost a year now and it is such a great tool. I think it is a must have at-least on Linux. +It not only converts almost all popular media formats into other formats but also has an inbuilt youtube video downloader which comes very handy for converting all the free official(and unofficial) song videos into MP3. +Let me spell it out, how it all works.

+ + + + +
+
+ +
+
+ + + + +
+
+

Logitech Ex100 - Wireless Keyboard and Mouse not working? Read on..

+

Today for some unknown reason suddenly my Logitech Wireless Keyboard and Mouse - Model Number EX100 (Logitech Cordless Desktop EX100 (920-000879)) stopped working and very stubbornly denied to work despite my repeated attempts on all obvious and logical steps - resets, battery change and such.

+

After trawling through the internet for some time I figured that I am not alone in facing the issue but surely have been hit quite late by it. It appears Logitech was more than happy to replace it at one point.

+ + + + +
+
+ +
+
+ + + + +
+
+

Time for a bit of show-off

+

Hey Hey I made an Android app named "Sai Satcharitra". It's a very basic app and +all it does is helps people read Sai Satcharitra on their Android Smartphones, +something that can also be achieved by maybe converting the text ino a pdf, epub +file etc.

+ + + + +
+
+ + + + + + + + + + + + +
+
+ + + + + +
+ + + +
+ + + +
+
+
+
+ + + + + + + + + + + + \ No newline at end of file diff --git a/archive/2011/page/2.html b/archive/2011/page/2.html new file mode 100644 index 0000000..6bb494d --- /dev/null +++ b/archive/2011/page/2.html @@ -0,0 +1,1615 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + 2011 - Make Gadgets Work + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + Skip to content + + +
+
+ +
+ + + + +
+ + +
+ +
+ + + + + + + + + +
+
+ + + +
+
+
+ + + + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + +
+

2011

+
+ + + + + + + + + + + + + +
+
+ + + + +
+
+

Google Voice + SIP2SIP + Ikall = Free international Calls to known contacts

+

Background

+

Second post in succession on the topic but believe me when I get a new gadget I try getting all information and then once I have had the stuff working I have to make a post right away or I will forget and hence this post. This post also overrides the previous posts. +Right then, let's get down to business.

+
+

Objective

+

We aim to make free international calls and there is nothing illegal in this set-up, not to my knowledge.

+
+
+

How?

+

Google has introduced Google Voice and allows free calls to all US numbers. This can be done using their google talk plugin in the gmail browser.

+
+ + + + +
+
+ + + + + + + + + + + + + + +
+
+ + + + + +
+ + + +
+ + + +
+
+
+
+ + + + + + + + + + + + \ No newline at end of file diff --git a/archive/2012.html b/archive/2012.html new file mode 100644 index 0000000..31d6a51 --- /dev/null +++ b/archive/2012.html @@ -0,0 +1,1822 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + 2012 - Make Gadgets Work + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + Skip to content + + +
+
+ +
+ + + + +
+ + +
+ +
+ + + + + + + + + +
+
+ + + +
+
+
+ + + + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + +
+

2012

+
+ + + + + + + + + + + + + +
+
+ + + + +
+
+

Prepare Linux Mint 13 for Android development

+

Background

+

Few weeks back I updated to the latest Linux Mint offering 'Maya' a.k.a Linux Mint 13. Now this is a LTS (Long Term Support) version and I wanted to be in a position to install everything right just so I can keep it for a longer duration and hence have been taking my time configuring stuff.

+

Last time when I had set up system for Android Development I remember messing up a lot and ending up installing too many things here and there and in the process did learn how to do it properly. I did not document that as a blog as it was too fragmented an experience at that time but this time round I did it properly and everything (well, okay, almost everything) was perfect.

+ + + + +
+
+ +
+
+ + + + +
+
+

Flash Samsung Galaxy S (GT -I9000) with Cyanogenmod 9 on Linux

+

I had an old Samsung Galaxy S which was still on stock ROM hence it only ever got to gingerbread and then Samsung just decided not to upgrade and I upgraded the phone so this little gadget was till yesterday destined to live with the old gingerbread. +Then yesterday, I just decided to play around with it and started reading so I know what are my options. Now wiki guide on Cyanogenmod site is quite nicely written but there were one or two steps here and there which had me confused for a little while so here is my usual step-by-step guide on how to go about it.

+
+

UPDATE

+

Uploaded all the files to mediafire as requested in one of the comments below. These can be downloaded from this link.

+
+ + + + +
+
+ +
+
+ + + + +
+
+

Top 10 Android Apps that I use !!!!

+

I have been using Android Phones and Tablets for quite some time now and have +monitored my use for apps that I use on a daily basis and those I would not even +come to know if they were removed without my knowledge. So clearly there is a +personal perspective on the list below:

+ + + + +
+
+ +
+
+ + + + +
+
+

Exchange 2007 on Thunderbird using DAVMail

+

Background

+

The start of this week was like a nightmare for me. Whole family was down with flu and I had the fever that is probably the highest ever of my entire life at 40.5 C (~ 106 F).

+

Anyway, surviving that was easier compared to the aftermaths of this health problem that forced me to stay in bed and inadvertently deal with office mails at home on a non-IE browser with Lite version which is crap and makes you feel miserable enough to kill yourself.

+

So the option I had was to either boot windows on a virtual machine or find a solution within linux. Obviously, I prefer the latter and was glad to work out a solution that can help me avoid booting windows.

+

I have listed below the steps I followed to achieve this, though in all honesty the documentation is quite good on sourceforge site itself. It's just that some of their screenshots are dated and in French (literally, no pun intended).

+

Now there are several options floating around but this set-up works flawlessly for me and so I will obviously recommend this over other methods.

+

It's so easy to configure that there really is no reason not to give it a shot.

+ + + + +
+
+ +
+
+ + + + +
+
+

StageVu / DivX video on Bodhi Linux

+

If you read my last post you will know I have been playing with Bodhi Linux lately. One of the selling point for this distro is it's minimalistic approach. However, that also means it doesn't come with some of the media packages that help stream all type of media - stagevu (DivX) included.

+

Fortunately it does not require lot of effort to get them all working. At-least for me it was quite quick.

+

The steps I followed are listed below:

+ + + + +
+
+ +
+
+ + + + +
+
+

How to boot from USB when BIOS does not have the option.

+

I have an old Sony VAIO which is not in it's best of health and has long been really a companion for my telly, faithfully streaming media from bbc iplayer, youtube, dailymotion and likes. Internet enabled TV arrived in my home long long back 😄.

+

Now the thing with this laptop is that it's kinda gimpy - inbuilt keyboard won't work, battery is dead and it hangs on life with constant supply of energy from the AC source on the wall and the one thing that helps me load new OS on this machine - the optical reader - is temperamental and may or may not work and is moody in selecting which CD / DVD it will read and which it won't. It does in particular like CD's authored by Linux Format guys though. Writing is a skill it has forgotten long back and if it reads something, anything I am found celebrating.

+ + + + +
+
+ + + + + + + + + + + + +
+
+ + + + + +
+ + + +
+ + + +
+
+
+
+ + + + + + + + + + + + \ No newline at end of file diff --git a/archive/2013.html b/archive/2013.html new file mode 100644 index 0000000..e1f83fa --- /dev/null +++ b/archive/2013.html @@ -0,0 +1,1714 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + 2013 - Make Gadgets Work + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + Skip to content + + +
+
+ +
+ + + + +
+ + +
+ +
+ + + + + + + + + +
+
+ + + +
+
+
+ + + + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + +
+

2013

+
+ + + + + + + + + + + + + +
+
+ + + + +
+
+

Linux Mint on Android through VNC and Jump

+

Today "Jump" was available for free on Amazon as the app of the day and since it's nearly 7 quids on google play store, I grabbed it. For windows and Mac users, they have a pretty straight forward set-up but as usual for Linux it means some work but, in the end, it leaves you with a set-up you can trust and feel secure about.

+ + + + +
+
+ + + +
+
+ + + + +
+
+

Conky on my desktop - step by step

+

Fig-1

+

Background

+

My new friend Damjan recently mentioned that he liked the Conky on my desktop and asked for details as have few others so I figured a post on the topic will be useful.

+

Many who have been playing with conky seem to believe it's real easy stuff but I feel there are too many options and very little explanation which means it can be lot of messing around with different options and can take a while to get to a point where you have what you want.

+

To make this conky appear exactly the way it is on my screen on your desktop follow below.

+ + + + +
+
+ +
+
+ + + + +
+
+

Root Nexus 4 on Linux Mint 13 and access all files on computer

+

Background

+

Having a rooted phone and then going to one that does not have root access is like getting used to driving a luxury car but then being forced to drive a tractor. So with arrival of my shining new nexus 4 once the novelty worn of in 8 hours or so, I sat down and rooted the device. Now there are plenty of guides out there but not many specific to Linux just yet. One reason might just be the fact the Linux Users are really smart and know how to figure it out but what about users who are new ...well at least for them I am sure this post will be useful and while we are at it, I felt I will install the touch version of CWM...

+ + + + +
+
+ + + + + + + + + + + + +
+
+ + + + + +
+ + + +
+ + + +
+
+
+
+ + + + + + + + + + + + \ No newline at end of file diff --git a/archive/2014.html b/archive/2014.html new file mode 100644 index 0000000..ec8fe26 --- /dev/null +++ b/archive/2014.html @@ -0,0 +1,1556 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + 2014 - Make Gadgets Work + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + Skip to content + + +
+
+ +
+ + + + +
+ + +
+ +
+ + + + + + + + + +
+
+ + + +
+
+
+ + + + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + +
+

2014

+
+ + + + + + + + + + + + + +
+
+ + + + +
+
+

Arch Linux - nearly a year on....

+

I have been dwelling in the world of Arch Linux for just under a year now and must admit the experience is nothing less than liberating. Granted that the barrier to entry was big when I dived in, as a pure Arch install takes some reading and learning but the rewards are worth the pain.

+ + + + +
+
+ + + + + + + + + + + + +
+
+ + + + + +
+ + + +
+ + + +
+
+
+
+ + + + + + + + + + + + \ No newline at end of file diff --git a/archive/2016.html b/archive/2016.html new file mode 100644 index 0000000..008ca0f --- /dev/null +++ b/archive/2016.html @@ -0,0 +1,2054 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + 2016 - Make Gadgets Work + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + Skip to content + + +
+
+ +
+ + + + +
+ + +
+ +
+ + + + + + + + + +
+
+ + + +
+
+
+ + + + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + +
+

2016

+
+ + + + + + + + + + + + + +
+
+ + + + +
+
+

Swap File to create extra memory

+

While renewing my LetsEncrypt certificate, I found myself in a strange situation where the certbot won't run asking me to update pip and then each time I tried updating pip it failed with the error error: command 'x86_64-linux-gnu-gcc' failed with exit status 4.

+

It turns out that this happens due to low memory and with my digitalocean droplet being the cheapest one this was bound to happen sooner rather than later. Fortunately there is a way around it as explained below.

+

Use of following commands will ensure that the swap file is created which in turn will help avoid the 'error: command 'x86_64-linux-gnu-gcc' failed with exit status 4'.

+

Following commands will create a swap file:

+ + + + +
+
+ +
+
+ + + + +
+
+

Note 7 to OnePlus 3

+

I was a super excited owner of Note 7 in September this year and then in few +days the happiness started disappearing as the news of exploding Note 7 started +appearing all over the internet.

+

Then came the notice to exchange the Note 7 which I dutifully did and assured +that nothing will now go wrong I started enjoying that gem of a device. Alas, it +was not to be and the device was recalled for good. Being a user of Note devices +for so long and yet unable to continue with my old Note 3, I decided to go for +some interim device - you know until Samsung decides to get the act right and +bring the next Note device to the market.

+ + + + +
+
+ +
+
+ + + + +
+
+

Grav - CMS with a difference

+

While I love Ghost as a blogging platform, it is not best placed for things other than blogs - after all that is the basic idea behind creation of this wonderful tool.

+

As I wanted to host a static website using tools that don't require a database or rely on php, I went searching on interwebs. +I came across a lot of options and the most popular one appears to be Jekyll and it's variants (Nikola and such) but they require a lot of terminal activity which won't go well for regular end user responsible for maintaining content of the static website in question.So I continued looking and came across this wonderful project called Grav.

+

Grav is super fast, very pretty and extremely easy to deploy and maintain. Additionally, it has very good documentation.

+

The key features that I absolutely loved are as below:

+ + + + +
+
+ +
+
+ + + + +
+
+

Windows 10 - a bucket load of pain

+
+

As Windows 10 is a commercial offering, one would think it will be working as expected and it does so long as like me one has come to expect pain from Microsoft in general and Windows in particular - because pain is what you get when you use Windows 10.

+
+ + + + +
+
+ +
+
+ + + + +
+
+

Ethercalc

+

Ethercalc is good tool which can be selfhosted. It is fairly simple to do so. Though it will be available for anyone who has the URL because there is no inbuilt login mechanism.

+ + + + +
+
+ +
+
+ + + + +
+
+

Fix for PHP Issues after upgrade to Ubuntu 16.04.1 (Xenial)

+

After updating from Ubuntu 14.04, the php and Apache stopped being friends and one of the WordPress site I maintain went all white and admin page was just showing php code. +This is apparently because of a known issue in 16.04 with upgrade to php7 as shown on the ubuntu forum here.

+

Using the guidance from this link and with some more of duckduckgo search later, I managed to resolve the problem thus:

+ + + + +
+
+ +
+
+ + + + +
+
+

Update Ghost on Fedora

+

While the guidance on Ghost website is very clear, I did get issues that required steps in troubleshooting. Something to do with lodash and npm version 2 stuff (node_modules/knex requires lodash@'^3.7.0') that I read on one of the forums specifically the comment from ErisDS on 13/06.

+ + + + +
+
+ +
+
+ + + + +
+
+

Tomcat 8.5.4 on Fedora behind Nginx

+

Install Oracle Java

+
1
+2
+3
+4
+5
+6
#install jdk
+wget --no-cookies --no-check-certificate --header "Cookie: gpw_e24=http%3A%2F%2Fwww.oracle.com%2F; oraclelicense=accept-securebackup-cookie" "http://download.oracle.com/otn-pub/java/jdk/8u102-b14/jdk-8u102-linux-i586.rpm"
+#install jre
+wget --no-cookies --no-check-certificate --header "Cookie: gpw_e24=http://www.oracle.com/ oraclelicense=accept-securebackup-cookie" "http://download.oracle.com/otn-pub/java/jdk/8u102-b14/jre-8u102-linux-i586.rpm"
+#enable firefox plugin
+alternatives --install /usr/lib/mozilla/plugins/libjavaplugin.so libjavaplugin.so /usr/java/jdk1.8.0_102/jre/lib/i386/libnpjp2.so 20000
+
+
+

Tip

+

URL for JDK and JRE is best obtained directly from oracle website

+
+ + + + +
+
+ + + +
+
+ + + + +
+
+

MySQL function to calculate elapsed working time

+

I wrote this function to cater for a specific requirement and I don't know if there are better ways of doing it but this saved tremendous amount of time and might have real time application elsewhere.

+

Problem Statement:

+

Find out the age of an incident in working minutes, given the following:

+
    +
  1. Time and Date of when an incident was logged
  2. +
  3. Time and date of when the same incident was closed
  4. +
  5. Opening time of the site for which the incident was logged
  6. +
  7. Closing Time of the site for which incident was logged
  8. +
  9. Country of the site for which incident has been logged
  10. +
+
+

Assumption

+

It is assumed that opening and closing times are same on all working days and that all the sites are closed on holidays and weekends

+
+

Function should take all the above five "given" as parameter and then calculate age of the incident.

+ + + + +
+
+ + + + + + + + + + + + +
+
+ + + + + +
+ + + +
+ + + +
+
+
+
+ + + + + + + + + + + + \ No newline at end of file diff --git a/archive/2016/page/2.html b/archive/2016/page/2.html new file mode 100644 index 0000000..0714e70 --- /dev/null +++ b/archive/2016/page/2.html @@ -0,0 +1,1776 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + 2016 - Make Gadgets Work + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + Skip to content + + +
+
+ +
+ + + + +
+ + +
+ +
+ + + + + + + + + +
+
+ + + +
+
+
+ + + + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + +
+

2016

+
+ + + + + + + + + + + + + +
+
+ + + + +
+
+

The complete walkthrough of my blogger to ghost migration

+

The 7 Year Itch

+

It can't possibly be a coincidence that this is the 7th year since I started blogging on blogger and therefore it is very likely to be a strong case of the 7 year itch syndrome but whichever way you look at it, divorce was inevitable given blogger had just stopped inspiring me.

+

I have been fiddling with different blogging platforms while getting accused of neglecting my sweet and loving family...😢. Ghost caught my fancy three weeks back. The last post was the beginning of our courtship and this post tells the tale of how a casual fling turned into marital commitment. 😂

+

To start a fresh blog, choosing any platform is easy and straight forward but to move from one platform to another is - umm... lets just say a very involved process - rewarding but involved.

+

Love can move mountains!!!

+

A complete migration from blogger to WordPress would have been way simpler. I know this as I have done it in past and it appeared like moving to Ghost would require migrating to a WordPress instance anyway. There was - I must admit - a temptation to call WordPress the home but that wouldn't have made a great love-story now - would it?

+

However, the much publicised WordPress route to Ghost migration did not work for me and eventually after a lot of manual copying, pasting, cleaning, pruning, hiding, reading and learning later, the self-hosted blog is all complete.

+ + + + +
+
+ +
+
+ + + + +
+
+

Ghost on Fedora 24

+

To install Ghost as my blogging platform, I had to go through a number of hoops and one of them was to get the nodejs working and what not. I figured this might as well be worth documenting in case I have to do this all over again. It might also be helpful for some other inquisitive minds. 😄

+

The most useful reference I found was the post on rosehosting website specific to CentOS 7.

+

It would have all gone well too; had it not been for the nodejs related issues which resulted in me finding the other helpful pointers from various forums.

+

Anyway, the steps I took to get this all working are detailed in my notes below - keeping it, where I can, true to the post I have referred above:

+ + + + +
+
+ +
+
+ + + + +
+
+

Seafile Server behind nginx on Fedora 24 Security Lab Spin

+

I have recently been intrigued by the idea of replacing the likes of “Dropbox” and “Google Drive” with a cloud set-up of my own. I had "Owncloud" set-up for nearly a year but was not happy with it. There were minor niggles aside form speed and thumbnails and then “Owncloud” had a recent split leading to creation of “Nextcloud”.

+

While “Nextcloud” is the one that is more aligned to the general principles of community driven software, it is new and is still plagued with owncloud issues as it is essentially same stuff in new packaging at the moment.

+

In the meantime, every now and then I was reading all the good stuff people had to say about "Seafile" and so I wanted to give it a try. Now for the past year and a half I have also been using Fedora Security Lab spin on my home server and I just wanted to get the Seafile set-up on it so I did a few “duckduckgo” searches on the net and finally had the steps to achieve the objective. Obviously it all worked and my Seafile server is live and kicking, hence the post. 😄

+
+

So in nutshell my objective was to:

+

Install seafile-server-5 behind nginx on Fedora 24 Security Lab spin all on a 32 bit 12 year old laptop.

+
+

The steps I followed are listed below with detailed notes of what I did. I do not claim these to be perfect but this is what worked for me. If you know that something can be done better, please do let me know in the comments.

+ + + + +
+
+ +
+
+ + + + +
+
+

Crossover

+

There are technologies that I can use in my personal life, that I am proud of and those that I play around with but reality is that as a Senior Project Manager, there aren't very many times when I get the opportunity to influence the choice of software to be used.

+

As a principle, my personal choices are driven by opensource and professional choices are driven by client needs. However, listed below are opensource technologies that crossed over from my personal to professional realm.

+
+

Three open-source technologies that jumped from my hobbyist curiosity to my professional life

+
+ + + + +
+
+ +
+
+ + + + +
+
+

MySQL Stored Procedure to return JSON for google charts on BIRT

+

Background

+

My requirement was to get the data in a format that google chart can use to draw the chart I want. Now gogle chart accepts data in json format where all column names separated with comma are in first square bracket set followed by values in rest of the square bracket sets and each square bracket set is separated by comma as well.

+ + + + +
+
+ + + + + + + + + + + + +
+
+ + + + + +
+ + + +
+ + + +
+
+
+
+ + + + + + + + + + + + \ No newline at end of file diff --git a/archive/2017.html b/archive/2017.html new file mode 100644 index 0000000..f4835d0 --- /dev/null +++ b/archive/2017.html @@ -0,0 +1,1778 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + 2017 - Make Gadgets Work + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + Skip to content + + +
+
+ +
+ + + + +
+ + +
+ +
+ + + + + + + + + +
+
+ + + +
+
+
+ + + + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + +
+

2017

+
+ + + + + + + + + + + + + +
+
+ + + + +
+
+

Unprotect Sheets in Libre Calc, Excel

+

A friend of mine today had an issue. He had created a template for some really complex calculations and to ensure he does not mess up with the forumlae by mistake he had password protected sheets and cells. He had done this back in 2012 and now he wanted change something in there but he had forgotten the password so he asked me if I can help.

+

Now, I do not know much about how it could be done on windows or on excel but I knew a small trick on LibreOffice so I asked him to send me that excel file by email. I then took following steps:

+ + + + +
+
+ +
+
+ + + + +
+
+ +

Right then, the Ghost V1.0 was out a while back and they made Ghost 0.11.x an LTS so I was not in any rush to upgrade too. I have not had much time to sort this out for a while and two days back when I finally came around to check how to upgrade, my first moment of concern was that officially supported stack is for NGINX.

+

I have moved my blog to the Apache Stack on DigitalOcean and while on my sandbox environment I still have NGINX, that is not a place I want to host my blog from. Anyhoo, I realised soon enough that while not officially supported it s easy to bypass the restrictions so I went ahead.

+

The upgrade itself couldn't have been simpler considering the major version bump. The answer to the question 'Was it worth it?' is something we will have to wait and see although I am liking what I see except for the initial hiccups.

+
+

Success

+

EDITED AFTER THE POST: Boy oh boy - just after I finished this post I saw the latest version of Ghost V1.12 is out and it was such a painless process compared to past. Just a simple command 'ghost update' and job done. That itself makes this whole pain kind of worth it.

+
+

OK so the steps I took are as presented below.

+ + + + +
+
+ + + +
+
+ + + + +
+
+

Markdown and Gantt Charts

+

For a fairly long time, I have been looking for a simple markdown type of solution to be able to quickly draw Gantt charts but never came across what one would call a quick option. It has always been an involved process.

+

To my simplistic mind, a simple solution would just be an option where I can type the action, a start date and an end date for the action line after line and it get's displayed in the Gantt chart.

+ + + + +
+
+ +
+
+ + + + +
+
+ +

Background

+

I have been with PlusNet for over two years now and am a happy camper as far as fiber optic broadband is concerned but as I am no longer on a broadband contract with PlusNet and had no intention of going on one, so the only way I could get a change to my ageing router was by purchasing a new one.

+

Hence I started reading about my options and soon enough realised that an old router can be given new lease of life using DD-WRT. Equally soon-ish I also realised that the router from PlusNet - TG582n - is quite rubbish and does not play nice with any of the open source firmwares.

+

So I figured that if I have to just play around a bit, I might as well start with something cheaper and cheaper is what I found in the TP-Link router TL-WR841n at just £16.00. You can't get any cheaper than that in my opinion. OK, so now that we have established that I am cheap and my new router is cheap, let's move on to interesting stuff.

+

I had read that TP-Link router and specifically TL-WR841n plays nicely with DD-WRT but it was only after I had my new toy did I realise that these things also come in hardware version and while interwebs is filled with instructions on installing DD-WRT for upto v9, when it comes to v11 in Europe, it can be a bit tricky to proceed. There are some instructions in forums1 but nothing that walks one end to end hence this post.

+ + + + +
+
+ + + + + + + + + + + + +
+
+ + + + + +
+ + + +
+ + + +
+
+
+
+ + + + + + + + + + + + \ No newline at end of file diff --git a/archive/2018.html b/archive/2018.html new file mode 100644 index 0000000..35299f1 --- /dev/null +++ b/archive/2018.html @@ -0,0 +1,1788 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + 2018 - Make Gadgets Work + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + Skip to content + + +
+
+ +
+ + + + +
+ + +
+ +
+ + + + + + + + + +
+
+ + + +
+
+
+ + + + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + +
+

2018

+
+ + + + + + + + + + + + + +
+
+ + + + +
+
+

Metabase - A BI solution that just works

+

I like exploring new solutions and anything to do with data always piques my interest. I came across this nice tool through the list of free self hosted software.

+
+

UPDATE

+

I am still getting introduced to the world of docker and while most of it just works few things confuse the hell out of me.

+

Anyway what happened is there was an update for Metabase and I wanted to apply it on my container and in doing so I realised that some of this post can be changed to make it a better experience from get go because after update all my config and dashboards were gone and I had to reconfigure everything.

+

It could be that my lack of knowledge meant I did something wrong and faced the issue but irrespective doing it as per updated post below will ensure you dont face the issue at all because I have now tested it twice to ensure it works well.

+
+ + + + +
+
+ +
+
+ + + + +
+
+

Prosody behind Apache on Debian Stretch with Conversations

+

Genreal Guidance

+

This how-to assumes that:

+
    +
  • User has access to a registered domain - domain.name. Any reference to domain.name throughout the tutorial therefore, must be replaced with your own domain name.
  • +
  • If stuck anywhere, please do get in touch with prosody support chat - They are a very helpful lot and are very kind too.
  • +
+ + + + +
+
+ +
+
+ + + + +
+
+

Home Networking

+

Network routing

+
    +
  1. +

    Router - Router is the device at home that connects all devices in your house to the internet.

    +
      +
    1. It does so by assigning IP address to each of your device but this IP address is only relevant for devices connected to this router. What this means is that if you use an IP address allocated by this router from a coffee shop and you are not connected to your home router then you will not be able to reach the device that IP address belonged to. For this reason these IP addresses are called internal IP addresses. + + + +
+
+ +
+
+ + + + +
+
+

MySQL Function to calculate closing WorkDay date given elapsed working time

+

On my post MySQL function to calculate elapsed working time I was asked in comments if the assumptions can be reversed such that given start date, starting time and closing time of site and the elapsed working hours, function should return the closed date. I was convinced that it will be possible to achieve this with minor tweaks to original logic and so I only concentrated on original code and how to tweak it to achieve the result. It is very likely that there might exist a more elegant solution but frankly I did not have a usecase I did find a usecase afterall - Clue was in redefining the problem description for this. Presented below is the function that with my minimal testing seems to give correct results. Feel free to try it and as always any feedback is welcome. 😄

+

Problem Description:

+

Find out the WorkDay date by when a ticket must be closed given:

+
    +
  1. Country of site for which incident was logged.
  2. +
  3. Time and Date when Incident was logged.
  4. +
  5. Elapsed working hours in decimal. (Ten and a half hours as 10.5 and so on)
  6. +
  7. Opening time of the site for which the incident was logged.
  8. +
  9. Closing Time of the site for which incident was logged.
  10. +
  11. SLA Type - Is the output date to be based purely on number of hours or based on SLA in days (eg: Next Business Day).
  12. +
+
+

Assumption

+

It is assumed that opening and closing times are same on all working days and that all the sites are closed on holidays and weekends.

+
+ + + + +
+
+ + + + + + + + + + + + + + +
+
+ + + + + +
+ + + +
+ + + +
+
+
+
+ + + + + + + + + + + + \ No newline at end of file diff --git a/archive/2020.html b/archive/2020.html new file mode 100644 index 0000000..9f5673b --- /dev/null +++ b/archive/2020.html @@ -0,0 +1,1557 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + 2020 - Make Gadgets Work + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + Skip to content + + +
+
+ +
+ + + + +
+ + +
+ +
+ + + + + + + + + +
+
+ + + +
+
+
+ + + + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + +
+

2020

+
+ + + + + + + + + + + + + +
+
+ + + + +
+
+

Converting AAX (Audible) to mp3

+

Lately I have been a bit frustrated because while I subscribe to Audible services, Amazon and Google do not play nice with each other limiting me - the owner of books with rights to listen my purchase on any device - in my options on where I can listen. Now I am yet to find a good solution that can get my files playing on google home and not just Alexa, I guess first step was to free my audiobooks from Amazon jail. That as it turns out is rather simple to do.I must at this point mention OpenAudible which is one fine tool. However, their latest version (2.0.7 at the time of writing) added some limitations whereby you can get it to download the AAX files but not convert them to mp3 without purchasing their license. However,converting AAX to mp3 is accomplished using ffmpeg and I do not believe a nice GUI wrapper is what I need and hence I do not believe my money is well spent in getting a license just to achieve from a graphical user interface what I can achieve for free from the terminal with a single line.There are 3 steps in the conversion process:

+ + + + +
+
+ + + + + + + + + + + + +
+
+ + + + + +
+ + + +
+ + + +
+
+
+
+ + + + + + + + + + + + \ No newline at end of file diff --git a/archive/2021.html b/archive/2021.html new file mode 100644 index 0000000..9b1c881 --- /dev/null +++ b/archive/2021.html @@ -0,0 +1,1614 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + 2021 - Make Gadgets Work + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + Skip to content + + +
+
+ +
+ + + + +
+ + +
+ +
+ + + + + + + + + +
+
+ + + +
+
+
+ + + + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + +
+

2021

+
+ + + + + + + + + + + + + +
+
+ + + + +
+
+

Tech for Diabetics

+

Background

+

As a diabetic there are a number of things we are not able to control but one thing we can do is keep tabs on our data and while in past it would have meant meticulously noting down BG readings in a diary, today it is much much easier with all the apps and connected services. In this article, I aim to go through the list of technologies that I am aware of and use to keep on top of my diabetic data right from my phone.

+ + + + +
+
+ + + + + + + + + + + + + + +
+
+ + + + + +
+ + + +
+ + + +
+
+
+
+ + + + + + + + + + + + \ No newline at end of file diff --git a/archive/2023.html b/archive/2023.html new file mode 100644 index 0000000..c0b6753 --- /dev/null +++ b/archive/2023.html @@ -0,0 +1,1711 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + 2023 - Make Gadgets Work + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + Skip to content + + +
+
+ +
+ + + + +
+ + +
+ +
+ + + + + + + + + +
+
+ + + +
+
+
+ + + + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + +
+

2023

+
+ + + + + + + + + + + + + + + + + +
+
+ + + + +
+
+

Git Basics

+

Basics of Git

+

Recently, I was asked to help people understand what is this whole business with GIT and I ran a few sessions on the topic specifically keeping in mind that my audience consisted of tech enthusiasts with minimal tech background. The content below is how I explained and as it went down well, I figured it may be helpful to someone else too; hence this post. 😄

+ + + + +
+
+ +
+
+ + + + +
+
+

JupyterLite on Github Enterprise with Panel enabled

+

Recently I found myself in a scenario where I had access to Github Enterprise but was limited to a windows machine. Now I wanted to host JupyterLite on Github Enterprise so it can be used by some other people on my team. Challenge was the "Actions" on this instance of Github enterprise were disabled and so I had to build Jupyterlite locally and push it on Github. I figured while at it, I might as well enable the "Panel" and few other dependencies. The steps that worked for me are documented here. This is ofcourse assuming that Python is already installed on the device.

+

The final hosted JupyterLite can be seen in action here.

+

The steps below created a Jupyterlite instance that allows use of Panel using the %pip install panel magic command, and if used on Chrome or its derivatives it also allows exploring local file system.

+ + + + +
+
+ + + + + + + + + + + + +
+
+ + + + + +
+ + + +
+ + + +
+
+
+
+ + + + + + + + + + + + \ No newline at end of file diff --git a/archive/2024.html b/archive/2024.html new file mode 100644 index 0000000..f21900f --- /dev/null +++ b/archive/2024.html @@ -0,0 +1,1617 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + 2024 - Make Gadgets Work + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + Skip to content + + +
+
+ +
+ + + + +
+ + +
+ +
+ + + + + + + + + +
+
+ + + +
+
+
+ + + + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + +
+

2024

+
+ + + + + + + + + + + + + +
+
+ + + + +
+
+

Bluesky for comments on mkdocs blog

+

Background

+

I have recently migrated this blog from Jekyll to Mkdocs using material theme. I was also planning to move away from Disqus commenting system given all the known issues and just when I had finished the migration to Mkdocs, there was this post from Emily Liu- which showcases how easily the bluesky replies to a post can be added in comments section of a blog page. Followed closely by this was a post from Cory Zue who created NPM packaging. I initially was looking to use that on my blog and while searching for that package on jsdelivr I cam eacross the bluesky-comments-tag: a package created by Matt Kane.

+

I understood the code behind this much better and that was my starting point. There were two problems with the solution thus far that I wanted to solve.

+

Problem Description

+
    +
  • +

    Problem 1: (SOLVED) For the comments to appear on the blog, the bluesky post url needs to be added to the frontmatter. Now that is a cyclic process because you can't post the url of your blog post on bluesky until you have published it. + +

    + +
+
+ +
+
+ + + + +
+
+

Logseq Customisations for Project Management Template

+

Background

+

While the overall planning of project timeline gets lot of attention in the world of software, the most important aspect of project management in my experience is maintaining and tracking Issues and Actions. This in normal project management practice is carried out through the use of an Actions Log and an Issues Log. In addition any medium complexity project invariably will have internal and external dependencies / constraints, risks which I tend to track on Constraints Log and Risks Log. Finally, every project has decisions that I track on a Decisions Log.

+

Additionally, any opportunities that I identify during the course of project that are not in the scope of my project I capture those too in an Opportunities Log

+

Now all these logs have fairly standard fields so I created and started using an excel template and began calling it CARDIO Log short for log of Constraints, Actions, Risks, Decisions, Issues, Opportunities for a given project. This has worked well for me over the past 15 years or so but there are times where in order to maintain it meant cross linking an action to an issue or a risk and so on and more often than not it would become easier to just track actions in one of the other logs and that would make it a bit chaotic.

+

That problem, however, is what I thought, can be resolved using Logseq especially after starting with the template by the Logseq community user Luhman and starting with his template and explanation provided here.

+ + + + +
+
+ + + + + + + + + + + + +
+
+ + + + + +
+ + + +
+ + + +
+
+
+
+ + + + + + + + + + + + \ No newline at end of file diff --git a/assets/images/2009/09/16/Screenshot-NVIDIA X Server Settings-1.png b/assets/images/2009/09/16/Screenshot-NVIDIA X Server Settings-1.png new file mode 100644 index 0000000..3574591 Binary files /dev/null and b/assets/images/2009/09/16/Screenshot-NVIDIA X Server Settings-1.png differ diff --git a/assets/images/2009/09/16/Screenshot-NVIDIA X Server Settings-2.png b/assets/images/2009/09/16/Screenshot-NVIDIA X Server Settings-2.png new file mode 100644 index 0000000..d74b1ef Binary files /dev/null and b/assets/images/2009/09/16/Screenshot-NVIDIA X Server Settings-2.png differ diff --git a/assets/images/2009/09/16/Screenshot-NVIDIA X Server Settings-3.png b/assets/images/2009/09/16/Screenshot-NVIDIA X Server Settings-3.png new file mode 100644 index 0000000..d5ec387 Binary files /dev/null and b/assets/images/2009/09/16/Screenshot-NVIDIA X Server Settings-3.png differ diff --git a/assets/images/2009/09/16/Screenshot-NVIDIA X Server Settings-4.png b/assets/images/2009/09/16/Screenshot-NVIDIA X Server Settings-4.png new file mode 100644 index 0000000..a708e4c Binary files /dev/null and b/assets/images/2009/09/16/Screenshot-NVIDIA X Server Settings-4.png differ diff --git a/assets/images/2016/07/201011_Fig_1.png b/assets/images/2016/07/201011_Fig_1.png new file mode 100644 index 0000000..3a9df6a Binary files /dev/null and b/assets/images/2016/07/201011_Fig_1.png differ diff --git a/assets/images/2016/07/201011_Fig_10.png b/assets/images/2016/07/201011_Fig_10.png new file mode 100644 index 0000000..28055c7 Binary files /dev/null and b/assets/images/2016/07/201011_Fig_10.png differ diff --git a/assets/images/2016/07/201011_Fig_2.png b/assets/images/2016/07/201011_Fig_2.png new file mode 100644 index 0000000..8edb5d9 Binary files /dev/null and b/assets/images/2016/07/201011_Fig_2.png differ diff --git a/assets/images/2016/07/201011_Fig_3.png b/assets/images/2016/07/201011_Fig_3.png new file mode 100644 index 0000000..b8bcd1b Binary files /dev/null and b/assets/images/2016/07/201011_Fig_3.png differ diff --git a/assets/images/2016/07/201011_Fig_4.png b/assets/images/2016/07/201011_Fig_4.png new file mode 100644 index 0000000..1d0683f Binary files /dev/null and b/assets/images/2016/07/201011_Fig_4.png differ diff --git a/assets/images/2016/07/201011_Fig_5.png b/assets/images/2016/07/201011_Fig_5.png new file mode 100644 index 0000000..579f140 Binary files /dev/null and b/assets/images/2016/07/201011_Fig_5.png differ diff --git a/assets/images/2016/07/201011_Fig_6.png b/assets/images/2016/07/201011_Fig_6.png new file mode 100644 index 0000000..17d7139 Binary files /dev/null and b/assets/images/2016/07/201011_Fig_6.png differ diff --git a/assets/images/2016/07/201011_Fig_7.png b/assets/images/2016/07/201011_Fig_7.png new file mode 100644 index 0000000..98333d4 Binary files /dev/null and b/assets/images/2016/07/201011_Fig_7.png differ diff --git a/assets/images/2016/07/201011_Fig_8.png b/assets/images/2016/07/201011_Fig_8.png new file mode 100644 index 0000000..e5d151f Binary files /dev/null and b/assets/images/2016/07/201011_Fig_8.png differ diff --git a/assets/images/2016/07/201011_Fig_9.png b/assets/images/2016/07/201011_Fig_9.png new file mode 100644 index 0000000..093455c Binary files /dev/null and b/assets/images/2016/07/201011_Fig_9.png differ diff --git a/assets/images/2016/07/20110417_Fig_1.jpg b/assets/images/2016/07/20110417_Fig_1.jpg new file mode 100644 index 0000000..52d85fc Binary files /dev/null and b/assets/images/2016/07/20110417_Fig_1.jpg differ diff --git a/assets/images/2016/07/20110417_Fig_2.jpg b/assets/images/2016/07/20110417_Fig_2.jpg new file mode 100644 index 0000000..d0d55e7 Binary files /dev/null and b/assets/images/2016/07/20110417_Fig_2.jpg differ diff --git a/assets/images/2016/07/20110417_Fig_3.jpg b/assets/images/2016/07/20110417_Fig_3.jpg new file mode 100644 index 0000000..10c4558 Binary files /dev/null and b/assets/images/2016/07/20110417_Fig_3.jpg differ diff --git a/assets/images/2016/07/20110429_Fig_1.png b/assets/images/2016/07/20110429_Fig_1.png new file mode 100644 index 0000000..9b29517 Binary files /dev/null and b/assets/images/2016/07/20110429_Fig_1.png differ diff --git a/assets/images/2016/07/20110429_Fig_2.png b/assets/images/2016/07/20110429_Fig_2.png new file mode 100644 index 0000000..c113720 Binary files /dev/null and b/assets/images/2016/07/20110429_Fig_2.png differ diff --git a/assets/images/2016/07/20110429_Fig_3.png b/assets/images/2016/07/20110429_Fig_3.png new file mode 100644 index 0000000..9d7d11b Binary files /dev/null and b/assets/images/2016/07/20110429_Fig_3.png differ diff --git a/assets/images/2016/07/20110429_Fig_4.png b/assets/images/2016/07/20110429_Fig_4.png new file mode 100644 index 0000000..c47cb8c Binary files /dev/null and b/assets/images/2016/07/20110429_Fig_4.png differ diff --git a/assets/images/2016/07/20110429_Fig_5.png b/assets/images/2016/07/20110429_Fig_5.png new file mode 100644 index 0000000..cf56b2b Binary files /dev/null and b/assets/images/2016/07/20110429_Fig_5.png differ diff --git a/assets/images/2016/07/20110507_Fig_1.png b/assets/images/2016/07/20110507_Fig_1.png new file mode 100644 index 0000000..0582cf9 Binary files /dev/null and b/assets/images/2016/07/20110507_Fig_1.png differ diff --git a/assets/images/2016/07/20110507_Fig_10.png b/assets/images/2016/07/20110507_Fig_10.png new file mode 100644 index 0000000..0f259eb Binary files /dev/null and b/assets/images/2016/07/20110507_Fig_10.png differ diff --git a/assets/images/2016/07/20110507_Fig_11.png b/assets/images/2016/07/20110507_Fig_11.png new file mode 100644 index 0000000..747bb69 Binary files /dev/null and b/assets/images/2016/07/20110507_Fig_11.png differ diff --git a/assets/images/2016/07/20110507_Fig_12.png b/assets/images/2016/07/20110507_Fig_12.png new file mode 100644 index 0000000..76ea7e4 Binary files /dev/null and b/assets/images/2016/07/20110507_Fig_12.png differ diff --git a/assets/images/2016/07/20110507_Fig_13.png b/assets/images/2016/07/20110507_Fig_13.png new file mode 100644 index 0000000..b0f4c6a Binary files /dev/null and b/assets/images/2016/07/20110507_Fig_13.png differ diff --git a/assets/images/2016/07/20110507_Fig_2.png b/assets/images/2016/07/20110507_Fig_2.png new file mode 100644 index 0000000..4a6a23c Binary files /dev/null and b/assets/images/2016/07/20110507_Fig_2.png differ diff --git a/assets/images/2016/07/20110507_Fig_3.png b/assets/images/2016/07/20110507_Fig_3.png new file mode 100644 index 0000000..cc931f8 Binary files /dev/null and b/assets/images/2016/07/20110507_Fig_3.png differ diff --git a/assets/images/2016/07/20110507_Fig_4.png b/assets/images/2016/07/20110507_Fig_4.png new file mode 100644 index 0000000..83698f9 Binary files /dev/null and b/assets/images/2016/07/20110507_Fig_4.png differ diff --git a/assets/images/2016/07/20110507_Fig_5.png b/assets/images/2016/07/20110507_Fig_5.png new file mode 100644 index 0000000..49b0122 Binary files /dev/null and b/assets/images/2016/07/20110507_Fig_5.png differ diff --git a/assets/images/2016/07/20110507_Fig_6.png b/assets/images/2016/07/20110507_Fig_6.png new file mode 100644 index 0000000..e336660 Binary files /dev/null and b/assets/images/2016/07/20110507_Fig_6.png differ diff --git a/assets/images/2016/07/20110507_Fig_7.png b/assets/images/2016/07/20110507_Fig_7.png new file mode 100644 index 0000000..8b6e0b8 Binary files /dev/null and b/assets/images/2016/07/20110507_Fig_7.png differ diff --git a/assets/images/2016/07/20110507_Fig_8.png b/assets/images/2016/07/20110507_Fig_8.png new file mode 100644 index 0000000..3d2c63c Binary files /dev/null and b/assets/images/2016/07/20110507_Fig_8.png differ diff --git a/assets/images/2016/07/20110507_Fig_9.png b/assets/images/2016/07/20110507_Fig_9.png new file mode 100644 index 0000000..ee2be9e Binary files /dev/null and b/assets/images/2016/07/20110507_Fig_9.png differ diff --git a/assets/images/2016/07/20110604_Fig_1.png b/assets/images/2016/07/20110604_Fig_1.png new file mode 100644 index 0000000..383584e Binary files /dev/null and b/assets/images/2016/07/20110604_Fig_1.png differ diff --git a/assets/images/2016/07/20110604_Fig_2.png b/assets/images/2016/07/20110604_Fig_2.png new file mode 100644 index 0000000..20d5a59 Binary files /dev/null and b/assets/images/2016/07/20110604_Fig_2.png differ diff --git a/assets/images/2016/07/20110604_Fig_3.png b/assets/images/2016/07/20110604_Fig_3.png new file mode 100644 index 0000000..6f87147 Binary files /dev/null and b/assets/images/2016/07/20110604_Fig_3.png differ diff --git a/assets/images/2016/07/20110604_Fig_4.png b/assets/images/2016/07/20110604_Fig_4.png new file mode 100644 index 0000000..67627b1 Binary files /dev/null and b/assets/images/2016/07/20110604_Fig_4.png differ diff --git a/assets/images/2016/07/20110604_Fig_5.png b/assets/images/2016/07/20110604_Fig_5.png new file mode 100644 index 0000000..b2df3f1 Binary files /dev/null and b/assets/images/2016/07/20110604_Fig_5.png differ diff --git a/assets/images/2016/07/20110604_Fig_6.png b/assets/images/2016/07/20110604_Fig_6.png new file mode 100644 index 0000000..468e1b8 Binary files /dev/null and b/assets/images/2016/07/20110604_Fig_6.png differ diff --git a/assets/images/2016/07/20110604_Fig_7.png b/assets/images/2016/07/20110604_Fig_7.png new file mode 100644 index 0000000..0aeacdc Binary files /dev/null and b/assets/images/2016/07/20110604_Fig_7.png differ diff --git a/assets/images/2016/07/20110620_Fig_1.png b/assets/images/2016/07/20110620_Fig_1.png new file mode 100644 index 0000000..cb02af9 Binary files /dev/null and b/assets/images/2016/07/20110620_Fig_1.png differ diff --git a/assets/images/2016/07/20110620_Fig_2.png b/assets/images/2016/07/20110620_Fig_2.png new file mode 100644 index 0000000..bb5597c Binary files /dev/null and b/assets/images/2016/07/20110620_Fig_2.png differ diff --git a/assets/images/2016/07/20111104_Fig_1.png b/assets/images/2016/07/20111104_Fig_1.png new file mode 100644 index 0000000..9494d56 Binary files /dev/null and b/assets/images/2016/07/20111104_Fig_1.png differ diff --git a/assets/images/2016/07/20111104_Fig_10.png b/assets/images/2016/07/20111104_Fig_10.png new file mode 100644 index 0000000..71cae8d Binary files /dev/null and b/assets/images/2016/07/20111104_Fig_10.png differ diff --git a/assets/images/2016/07/20111104_Fig_11.png b/assets/images/2016/07/20111104_Fig_11.png new file mode 100644 index 0000000..7644788 Binary files /dev/null and b/assets/images/2016/07/20111104_Fig_11.png differ diff --git a/assets/images/2016/07/20111104_Fig_12.png b/assets/images/2016/07/20111104_Fig_12.png new file mode 100644 index 0000000..e921c5f Binary files /dev/null and b/assets/images/2016/07/20111104_Fig_12.png differ diff --git a/assets/images/2016/07/20111104_Fig_13.png b/assets/images/2016/07/20111104_Fig_13.png new file mode 100644 index 0000000..fff5247 Binary files /dev/null and b/assets/images/2016/07/20111104_Fig_13.png differ diff --git a/assets/images/2016/07/20111104_Fig_14.png b/assets/images/2016/07/20111104_Fig_14.png new file mode 100644 index 0000000..4deb828 Binary files /dev/null and b/assets/images/2016/07/20111104_Fig_14.png differ diff --git a/assets/images/2016/07/20111104_Fig_15.png b/assets/images/2016/07/20111104_Fig_15.png new file mode 100644 index 0000000..2ad61ad Binary files /dev/null and b/assets/images/2016/07/20111104_Fig_15.png differ diff --git a/assets/images/2016/07/20111104_Fig_16.png b/assets/images/2016/07/20111104_Fig_16.png new file mode 100644 index 0000000..69ff58e Binary files /dev/null and b/assets/images/2016/07/20111104_Fig_16.png differ diff --git a/assets/images/2016/07/20111104_Fig_17.png b/assets/images/2016/07/20111104_Fig_17.png new file mode 100644 index 0000000..6447e73 Binary files /dev/null and b/assets/images/2016/07/20111104_Fig_17.png differ diff --git a/assets/images/2016/07/20111104_Fig_2.png b/assets/images/2016/07/20111104_Fig_2.png new file mode 100644 index 0000000..ca3fc77 Binary files /dev/null and b/assets/images/2016/07/20111104_Fig_2.png differ diff --git a/assets/images/2016/07/20111104_Fig_3.png b/assets/images/2016/07/20111104_Fig_3.png new file mode 100644 index 0000000..b23cfee Binary files /dev/null and b/assets/images/2016/07/20111104_Fig_3.png differ diff --git a/assets/images/2016/07/20111104_Fig_4.png b/assets/images/2016/07/20111104_Fig_4.png new file mode 100644 index 0000000..4fcff96 Binary files /dev/null and b/assets/images/2016/07/20111104_Fig_4.png differ diff --git a/assets/images/2016/07/20111104_Fig_5.png b/assets/images/2016/07/20111104_Fig_5.png new file mode 100644 index 0000000..e548239 Binary files /dev/null and b/assets/images/2016/07/20111104_Fig_5.png differ diff --git a/assets/images/2016/07/20111104_Fig_6.png b/assets/images/2016/07/20111104_Fig_6.png new file mode 100644 index 0000000..00eb627 Binary files /dev/null and b/assets/images/2016/07/20111104_Fig_6.png differ diff --git a/assets/images/2016/07/20111104_Fig_7.png b/assets/images/2016/07/20111104_Fig_7.png new file mode 100644 index 0000000..a06b679 Binary files /dev/null and b/assets/images/2016/07/20111104_Fig_7.png differ diff --git a/assets/images/2016/07/20111104_Fig_8.png b/assets/images/2016/07/20111104_Fig_8.png new file mode 100644 index 0000000..b839c57 Binary files /dev/null and b/assets/images/2016/07/20111104_Fig_8.png differ diff --git a/assets/images/2016/07/20111104_Fig_9.png b/assets/images/2016/07/20111104_Fig_9.png new file mode 100644 index 0000000..6f70fc8 Binary files /dev/null and b/assets/images/2016/07/20111104_Fig_9.png differ diff --git a/assets/images/2016/07/20111106_Fig_1.png b/assets/images/2016/07/20111106_Fig_1.png new file mode 100644 index 0000000..36eb9d7 Binary files /dev/null and b/assets/images/2016/07/20111106_Fig_1.png differ diff --git a/assets/images/2016/07/20111106_Fig_10.png b/assets/images/2016/07/20111106_Fig_10.png new file mode 100644 index 0000000..ece4cbe Binary files /dev/null and b/assets/images/2016/07/20111106_Fig_10.png differ diff --git a/assets/images/2016/07/20111106_Fig_11.png b/assets/images/2016/07/20111106_Fig_11.png new file mode 100644 index 0000000..b0ac90f Binary files /dev/null and b/assets/images/2016/07/20111106_Fig_11.png differ diff --git a/assets/images/2016/07/20111106_Fig_12.png b/assets/images/2016/07/20111106_Fig_12.png new file mode 100644 index 0000000..c1976d7 Binary files /dev/null and b/assets/images/2016/07/20111106_Fig_12.png differ diff --git a/assets/images/2016/07/20111106_Fig_2.png b/assets/images/2016/07/20111106_Fig_2.png new file mode 100644 index 0000000..f4a95ec Binary files /dev/null and b/assets/images/2016/07/20111106_Fig_2.png differ diff --git a/assets/images/2016/07/20111106_Fig_3.png b/assets/images/2016/07/20111106_Fig_3.png new file mode 100644 index 0000000..ee12635 Binary files /dev/null and b/assets/images/2016/07/20111106_Fig_3.png differ diff --git a/assets/images/2016/07/20111106_Fig_4.png b/assets/images/2016/07/20111106_Fig_4.png new file mode 100644 index 0000000..dd39b24 Binary files /dev/null and b/assets/images/2016/07/20111106_Fig_4.png differ diff --git a/assets/images/2016/07/20111106_Fig_5.png b/assets/images/2016/07/20111106_Fig_5.png new file mode 100644 index 0000000..78da8b5 Binary files /dev/null and b/assets/images/2016/07/20111106_Fig_5.png differ diff --git a/assets/images/2016/07/20111106_Fig_6.png b/assets/images/2016/07/20111106_Fig_6.png new file mode 100644 index 0000000..c100118 Binary files /dev/null and b/assets/images/2016/07/20111106_Fig_6.png differ diff --git a/assets/images/2016/07/20111106_Fig_7.png b/assets/images/2016/07/20111106_Fig_7.png new file mode 100644 index 0000000..47577c1 Binary files /dev/null and b/assets/images/2016/07/20111106_Fig_7.png differ diff --git a/assets/images/2016/07/20111106_Fig_8.png b/assets/images/2016/07/20111106_Fig_8.png new file mode 100644 index 0000000..66fcae1 Binary files /dev/null and b/assets/images/2016/07/20111106_Fig_8.png differ diff --git a/assets/images/2016/07/20111106_Fig_9.png b/assets/images/2016/07/20111106_Fig_9.png new file mode 100644 index 0000000..d3401cc Binary files /dev/null and b/assets/images/2016/07/20111106_Fig_9.png differ diff --git a/assets/images/2016/07/2011_Fig_11.jpg b/assets/images/2016/07/2011_Fig_11.jpg new file mode 100644 index 0000000..0355038 Binary files /dev/null and b/assets/images/2016/07/2011_Fig_11.jpg differ diff --git a/assets/images/2016/07/20120220_Fig_1.png b/assets/images/2016/07/20120220_Fig_1.png new file mode 100644 index 0000000..4c9531b Binary files /dev/null and b/assets/images/2016/07/20120220_Fig_1.png differ diff --git a/assets/images/2016/07/20120220_Fig_2.png b/assets/images/2016/07/20120220_Fig_2.png new file mode 100644 index 0000000..d94ac6d Binary files /dev/null and b/assets/images/2016/07/20120220_Fig_2.png differ diff --git a/assets/images/2016/07/20120220_Fig_3.png b/assets/images/2016/07/20120220_Fig_3.png new file mode 100644 index 0000000..b198ed5 Binary files /dev/null and b/assets/images/2016/07/20120220_Fig_3.png differ diff --git a/assets/images/2016/07/20120220_Fig_4.png b/assets/images/2016/07/20120220_Fig_4.png new file mode 100644 index 0000000..bd32772 Binary files /dev/null and b/assets/images/2016/07/20120220_Fig_4.png differ diff --git a/assets/images/2016/07/20120220_Fig_5.png b/assets/images/2016/07/20120220_Fig_5.png new file mode 100644 index 0000000..57b65b7 Binary files /dev/null and b/assets/images/2016/07/20120220_Fig_5.png differ diff --git a/assets/images/2016/07/20120220_Fig_6.png b/assets/images/2016/07/20120220_Fig_6.png new file mode 100644 index 0000000..5983d6b Binary files /dev/null and b/assets/images/2016/07/20120220_Fig_6.png differ diff --git a/assets/images/2016/07/20120220_Fig_7.png b/assets/images/2016/07/20120220_Fig_7.png new file mode 100644 index 0000000..ebe8bdf Binary files /dev/null and b/assets/images/2016/07/20120220_Fig_7.png differ diff --git a/assets/images/2016/07/20120220_Fig_8.png b/assets/images/2016/07/20120220_Fig_8.png new file mode 100644 index 0000000..acf1620 Binary files /dev/null and b/assets/images/2016/07/20120220_Fig_8.png differ diff --git a/assets/images/2016/07/20120316_Fig_1.png b/assets/images/2016/07/20120316_Fig_1.png new file mode 100644 index 0000000..781c5e3 Binary files /dev/null and b/assets/images/2016/07/20120316_Fig_1.png differ diff --git a/assets/images/2016/07/20120316_Fig_10.png b/assets/images/2016/07/20120316_Fig_10.png new file mode 100644 index 0000000..581ca29 Binary files /dev/null and b/assets/images/2016/07/20120316_Fig_10.png differ diff --git a/assets/images/2016/07/20120316_Fig_11.png b/assets/images/2016/07/20120316_Fig_11.png new file mode 100644 index 0000000..5b9ce47 Binary files /dev/null and b/assets/images/2016/07/20120316_Fig_11.png differ diff --git a/assets/images/2016/07/20120316_Fig_12.png b/assets/images/2016/07/20120316_Fig_12.png new file mode 100644 index 0000000..f1688e1 Binary files /dev/null and b/assets/images/2016/07/20120316_Fig_12.png differ diff --git a/assets/images/2016/07/20120316_Fig_13.png b/assets/images/2016/07/20120316_Fig_13.png new file mode 100644 index 0000000..d17948d Binary files /dev/null and b/assets/images/2016/07/20120316_Fig_13.png differ diff --git a/assets/images/2016/07/20120316_Fig_14.png b/assets/images/2016/07/20120316_Fig_14.png new file mode 100644 index 0000000..98c7457 Binary files /dev/null and b/assets/images/2016/07/20120316_Fig_14.png differ diff --git a/assets/images/2016/07/20120316_Fig_15.png b/assets/images/2016/07/20120316_Fig_15.png new file mode 100644 index 0000000..c7a5974 Binary files /dev/null and b/assets/images/2016/07/20120316_Fig_15.png differ diff --git a/assets/images/2016/07/20120316_Fig_16.png b/assets/images/2016/07/20120316_Fig_16.png new file mode 100644 index 0000000..bc6270c Binary files /dev/null and b/assets/images/2016/07/20120316_Fig_16.png differ diff --git a/assets/images/2016/07/20120316_Fig_2.png b/assets/images/2016/07/20120316_Fig_2.png new file mode 100644 index 0000000..d365c57 Binary files /dev/null and b/assets/images/2016/07/20120316_Fig_2.png differ diff --git a/assets/images/2016/07/20120316_Fig_3.png b/assets/images/2016/07/20120316_Fig_3.png new file mode 100644 index 0000000..f25fa22 Binary files /dev/null and b/assets/images/2016/07/20120316_Fig_3.png differ diff --git a/assets/images/2016/07/20120316_Fig_4.png b/assets/images/2016/07/20120316_Fig_4.png new file mode 100644 index 0000000..61e8242 Binary files /dev/null and b/assets/images/2016/07/20120316_Fig_4.png differ diff --git a/assets/images/2016/07/20120316_Fig_5.png b/assets/images/2016/07/20120316_Fig_5.png new file mode 100644 index 0000000..17da14f Binary files /dev/null and b/assets/images/2016/07/20120316_Fig_5.png differ diff --git a/assets/images/2016/07/20120316_Fig_6.png b/assets/images/2016/07/20120316_Fig_6.png new file mode 100644 index 0000000..ec62b47 Binary files /dev/null and b/assets/images/2016/07/20120316_Fig_6.png differ diff --git a/assets/images/2016/07/20120316_Fig_7.png b/assets/images/2016/07/20120316_Fig_7.png new file mode 100644 index 0000000..fcdc2ee Binary files /dev/null and b/assets/images/2016/07/20120316_Fig_7.png differ diff --git a/assets/images/2016/07/20120316_Fig_8.png b/assets/images/2016/07/20120316_Fig_8.png new file mode 100644 index 0000000..aa52031 Binary files /dev/null and b/assets/images/2016/07/20120316_Fig_8.png differ diff --git a/assets/images/2016/07/20120316_Fig_9.png b/assets/images/2016/07/20120316_Fig_9.png new file mode 100644 index 0000000..bb157aa Binary files /dev/null and b/assets/images/2016/07/20120316_Fig_9.png differ diff --git a/assets/images/2016/07/20121012_Fig_1.png b/assets/images/2016/07/20121012_Fig_1.png new file mode 100644 index 0000000..4e75c8d Binary files /dev/null and b/assets/images/2016/07/20121012_Fig_1.png differ diff --git a/assets/images/2016/07/20121012_Fig_2.png b/assets/images/2016/07/20121012_Fig_2.png new file mode 100644 index 0000000..72afdce Binary files /dev/null and b/assets/images/2016/07/20121012_Fig_2.png differ diff --git a/assets/images/2016/07/20121012_Fig_3.png b/assets/images/2016/07/20121012_Fig_3.png new file mode 100644 index 0000000..e46e994 Binary files /dev/null and b/assets/images/2016/07/20121012_Fig_3.png differ diff --git a/assets/images/2016/07/20121018_Fig_1.png b/assets/images/2016/07/20121018_Fig_1.png new file mode 100644 index 0000000..5476dcd Binary files /dev/null and b/assets/images/2016/07/20121018_Fig_1.png differ diff --git a/assets/images/2016/07/20121018_Fig_10.png b/assets/images/2016/07/20121018_Fig_10.png new file mode 100644 index 0000000..37a6f32 Binary files /dev/null and b/assets/images/2016/07/20121018_Fig_10.png differ diff --git a/assets/images/2016/07/20121018_Fig_11.png b/assets/images/2016/07/20121018_Fig_11.png new file mode 100644 index 0000000..ddb6c7d Binary files /dev/null and b/assets/images/2016/07/20121018_Fig_11.png differ diff --git a/assets/images/2016/07/20121018_Fig_12.png b/assets/images/2016/07/20121018_Fig_12.png new file mode 100644 index 0000000..7e3e34d Binary files /dev/null and b/assets/images/2016/07/20121018_Fig_12.png differ diff --git a/assets/images/2016/07/20121018_Fig_13.png b/assets/images/2016/07/20121018_Fig_13.png new file mode 100644 index 0000000..58c43e4 Binary files /dev/null and b/assets/images/2016/07/20121018_Fig_13.png differ diff --git a/assets/images/2016/07/20121018_Fig_14.png b/assets/images/2016/07/20121018_Fig_14.png new file mode 100644 index 0000000..aeb3192 Binary files /dev/null and b/assets/images/2016/07/20121018_Fig_14.png differ diff --git a/assets/images/2016/07/20121018_Fig_14a.png b/assets/images/2016/07/20121018_Fig_14a.png new file mode 100644 index 0000000..0f27720 Binary files /dev/null and b/assets/images/2016/07/20121018_Fig_14a.png differ diff --git a/assets/images/2016/07/20121018_Fig_15.png b/assets/images/2016/07/20121018_Fig_15.png new file mode 100644 index 0000000..5f9e38e Binary files /dev/null and b/assets/images/2016/07/20121018_Fig_15.png differ diff --git a/assets/images/2016/07/20121018_Fig_16.png b/assets/images/2016/07/20121018_Fig_16.png new file mode 100644 index 0000000..a263f1b Binary files /dev/null and b/assets/images/2016/07/20121018_Fig_16.png differ diff --git a/assets/images/2016/07/20121018_Fig_17.png b/assets/images/2016/07/20121018_Fig_17.png new file mode 100644 index 0000000..75a25a5 Binary files /dev/null and b/assets/images/2016/07/20121018_Fig_17.png differ diff --git a/assets/images/2016/07/20121018_Fig_18.png b/assets/images/2016/07/20121018_Fig_18.png new file mode 100644 index 0000000..14f26c3 Binary files /dev/null and b/assets/images/2016/07/20121018_Fig_18.png differ diff --git a/assets/images/2016/07/20121018_Fig_19.png b/assets/images/2016/07/20121018_Fig_19.png new file mode 100644 index 0000000..4315412 Binary files /dev/null and b/assets/images/2016/07/20121018_Fig_19.png differ diff --git a/assets/images/2016/07/20121018_Fig_2.png b/assets/images/2016/07/20121018_Fig_2.png new file mode 100644 index 0000000..23240bd Binary files /dev/null and b/assets/images/2016/07/20121018_Fig_2.png differ diff --git a/assets/images/2016/07/20121018_Fig_20.png b/assets/images/2016/07/20121018_Fig_20.png new file mode 100644 index 0000000..d3634ed Binary files /dev/null and b/assets/images/2016/07/20121018_Fig_20.png differ diff --git a/assets/images/2016/07/20121018_Fig_21.png b/assets/images/2016/07/20121018_Fig_21.png new file mode 100644 index 0000000..272a1d6 Binary files /dev/null and b/assets/images/2016/07/20121018_Fig_21.png differ diff --git a/assets/images/2016/07/20121018_Fig_22.png b/assets/images/2016/07/20121018_Fig_22.png new file mode 100644 index 0000000..e65547e Binary files /dev/null and b/assets/images/2016/07/20121018_Fig_22.png differ diff --git a/assets/images/2016/07/20121018_Fig_23.png b/assets/images/2016/07/20121018_Fig_23.png new file mode 100644 index 0000000..73dea7d Binary files /dev/null and b/assets/images/2016/07/20121018_Fig_23.png differ diff --git a/assets/images/2016/07/20121018_Fig_24.png b/assets/images/2016/07/20121018_Fig_24.png new file mode 100644 index 0000000..d015709 Binary files /dev/null and b/assets/images/2016/07/20121018_Fig_24.png differ diff --git a/assets/images/2016/07/20121018_Fig_25.png b/assets/images/2016/07/20121018_Fig_25.png new file mode 100644 index 0000000..294f1c6 Binary files /dev/null and b/assets/images/2016/07/20121018_Fig_25.png differ diff --git a/assets/images/2016/07/20121018_Fig_26.png b/assets/images/2016/07/20121018_Fig_26.png new file mode 100644 index 0000000..d52a4d3 Binary files /dev/null and b/assets/images/2016/07/20121018_Fig_26.png differ diff --git a/assets/images/2016/07/20121018_Fig_27.png b/assets/images/2016/07/20121018_Fig_27.png new file mode 100644 index 0000000..be5c086 Binary files /dev/null and b/assets/images/2016/07/20121018_Fig_27.png differ diff --git a/assets/images/2016/07/20121018_Fig_28.png b/assets/images/2016/07/20121018_Fig_28.png new file mode 100644 index 0000000..fb529ca Binary files /dev/null and b/assets/images/2016/07/20121018_Fig_28.png differ diff --git a/assets/images/2016/07/20121018_Fig_29.png b/assets/images/2016/07/20121018_Fig_29.png new file mode 100644 index 0000000..056640c Binary files /dev/null and b/assets/images/2016/07/20121018_Fig_29.png differ diff --git a/assets/images/2016/07/20121018_Fig_3.png b/assets/images/2016/07/20121018_Fig_3.png new file mode 100644 index 0000000..7ec710c Binary files /dev/null and b/assets/images/2016/07/20121018_Fig_3.png differ diff --git a/assets/images/2016/07/20121018_Fig_4.png b/assets/images/2016/07/20121018_Fig_4.png new file mode 100644 index 0000000..c962c1c Binary files /dev/null and b/assets/images/2016/07/20121018_Fig_4.png differ diff --git a/assets/images/2016/07/20121018_Fig_5.png b/assets/images/2016/07/20121018_Fig_5.png new file mode 100644 index 0000000..a1fe629 Binary files /dev/null and b/assets/images/2016/07/20121018_Fig_5.png differ diff --git a/assets/images/2016/07/20121018_Fig_6.png b/assets/images/2016/07/20121018_Fig_6.png new file mode 100644 index 0000000..7da94bc Binary files /dev/null and b/assets/images/2016/07/20121018_Fig_6.png differ diff --git a/assets/images/2016/07/20121018_Fig_7.png b/assets/images/2016/07/20121018_Fig_7.png new file mode 100644 index 0000000..458081b Binary files /dev/null and b/assets/images/2016/07/20121018_Fig_7.png differ diff --git a/assets/images/2016/07/20121018_Fig_8.png b/assets/images/2016/07/20121018_Fig_8.png new file mode 100644 index 0000000..22350fe Binary files /dev/null and b/assets/images/2016/07/20121018_Fig_8.png differ diff --git a/assets/images/2016/07/20121018_Fig_9.png b/assets/images/2016/07/20121018_Fig_9.png new file mode 100644 index 0000000..e1bf2ed Binary files /dev/null and b/assets/images/2016/07/20121018_Fig_9.png differ diff --git a/assets/images/2016/07/20130101_Fig_1.png b/assets/images/2016/07/20130101_Fig_1.png new file mode 100644 index 0000000..c55d990 Binary files /dev/null and b/assets/images/2016/07/20130101_Fig_1.png differ diff --git a/assets/images/2016/07/20130101_Fig_2.png b/assets/images/2016/07/20130101_Fig_2.png new file mode 100644 index 0000000..ba0fa40 Binary files /dev/null and b/assets/images/2016/07/20130101_Fig_2.png differ diff --git a/assets/images/2016/07/20130101_Fig_3.png b/assets/images/2016/07/20130101_Fig_3.png new file mode 100644 index 0000000..ce62a5b Binary files /dev/null and b/assets/images/2016/07/20130101_Fig_3.png differ diff --git a/assets/images/2016/07/20130101_Fig_4.png b/assets/images/2016/07/20130101_Fig_4.png new file mode 100644 index 0000000..dcd844b Binary files /dev/null and b/assets/images/2016/07/20130101_Fig_4.png differ diff --git a/assets/images/2016/07/20130101_Fig_5.png b/assets/images/2016/07/20130101_Fig_5.png new file mode 100644 index 0000000..f06b625 Binary files /dev/null and b/assets/images/2016/07/20130101_Fig_5.png differ diff --git a/assets/images/2016/07/20130101_Fig_6.png b/assets/images/2016/07/20130101_Fig_6.png new file mode 100644 index 0000000..f0a5423 Binary files /dev/null and b/assets/images/2016/07/20130101_Fig_6.png differ diff --git a/assets/images/2016/07/20130118_Fig_1.png b/assets/images/2016/07/20130118_Fig_1.png new file mode 100644 index 0000000..e82c31c Binary files /dev/null and b/assets/images/2016/07/20130118_Fig_1.png differ diff --git a/assets/images/2016/07/20130118_Fig_2.png b/assets/images/2016/07/20130118_Fig_2.png new file mode 100644 index 0000000..918c8c2 Binary files /dev/null and b/assets/images/2016/07/20130118_Fig_2.png differ diff --git a/assets/images/2016/07/20130118_Fig_3.png b/assets/images/2016/07/20130118_Fig_3.png new file mode 100644 index 0000000..154d02c Binary files /dev/null and b/assets/images/2016/07/20130118_Fig_3.png differ diff --git a/assets/images/2016/07/20130118_Fig_4.png b/assets/images/2016/07/20130118_Fig_4.png new file mode 100644 index 0000000..ad7348d Binary files /dev/null and b/assets/images/2016/07/20130118_Fig_4.png differ diff --git a/assets/images/2016/07/20130317_Fig_1.png b/assets/images/2016/07/20130317_Fig_1.png new file mode 100644 index 0000000..7b719fa Binary files /dev/null and b/assets/images/2016/07/20130317_Fig_1.png differ diff --git a/assets/images/2016/07/20130317_Fig_10.png b/assets/images/2016/07/20130317_Fig_10.png new file mode 100644 index 0000000..257d2d7 Binary files /dev/null and b/assets/images/2016/07/20130317_Fig_10.png differ diff --git a/assets/images/2016/07/20130317_Fig_11.png b/assets/images/2016/07/20130317_Fig_11.png new file mode 100644 index 0000000..74aba20 Binary files /dev/null and b/assets/images/2016/07/20130317_Fig_11.png differ diff --git a/assets/images/2016/07/20130317_Fig_12.png b/assets/images/2016/07/20130317_Fig_12.png new file mode 100644 index 0000000..6d8aa30 Binary files /dev/null and b/assets/images/2016/07/20130317_Fig_12.png differ diff --git a/assets/images/2016/07/20130317_Fig_2.png b/assets/images/2016/07/20130317_Fig_2.png new file mode 100644 index 0000000..0796511 Binary files /dev/null and b/assets/images/2016/07/20130317_Fig_2.png differ diff --git a/assets/images/2016/07/20130317_Fig_3.png b/assets/images/2016/07/20130317_Fig_3.png new file mode 100644 index 0000000..a495993 Binary files /dev/null and b/assets/images/2016/07/20130317_Fig_3.png differ diff --git a/assets/images/2016/07/20130317_Fig_4.png b/assets/images/2016/07/20130317_Fig_4.png new file mode 100644 index 0000000..377ca8b Binary files /dev/null and b/assets/images/2016/07/20130317_Fig_4.png differ diff --git a/assets/images/2016/07/20130317_Fig_5.png b/assets/images/2016/07/20130317_Fig_5.png new file mode 100644 index 0000000..05ae0b1 Binary files /dev/null and b/assets/images/2016/07/20130317_Fig_5.png differ diff --git a/assets/images/2016/07/20130317_Fig_6.png b/assets/images/2016/07/20130317_Fig_6.png new file mode 100644 index 0000000..d524588 Binary files /dev/null and b/assets/images/2016/07/20130317_Fig_6.png differ diff --git a/assets/images/2016/07/20130317_Fig_7.png b/assets/images/2016/07/20130317_Fig_7.png new file mode 100644 index 0000000..ef5e88d Binary files /dev/null and b/assets/images/2016/07/20130317_Fig_7.png differ diff --git a/assets/images/2016/07/20130317_Fig_8.png b/assets/images/2016/07/20130317_Fig_8.png new file mode 100644 index 0000000..9416e6a Binary files /dev/null and b/assets/images/2016/07/20130317_Fig_8.png differ diff --git a/assets/images/2016/07/20130317_Fig_9.png b/assets/images/2016/07/20130317_Fig_9.png new file mode 100644 index 0000000..176fceb Binary files /dev/null and b/assets/images/2016/07/20130317_Fig_9.png differ diff --git a/assets/images/2016/07/20160718_Fig_1.PNG b/assets/images/2016/07/20160718_Fig_1.PNG new file mode 100644 index 0000000..42d35bf Binary files /dev/null and b/assets/images/2016/07/20160718_Fig_1.PNG differ diff --git a/assets/images/2016/07/20160718_Fig_2.PNG b/assets/images/2016/07/20160718_Fig_2.PNG new file mode 100644 index 0000000..56b8d6b Binary files /dev/null and b/assets/images/2016/07/20160718_Fig_2.PNG differ diff --git a/assets/images/2016/07/20160718_Fig_3.PNG b/assets/images/2016/07/20160718_Fig_3.PNG new file mode 100644 index 0000000..fdd5a98 Binary files /dev/null and b/assets/images/2016/07/20160718_Fig_3.PNG differ diff --git a/assets/images/2016/07/20160718_Fig_4.PNG b/assets/images/2016/07/20160718_Fig_4.PNG new file mode 100644 index 0000000..337795c Binary files /dev/null and b/assets/images/2016/07/20160718_Fig_4.PNG differ diff --git a/assets/images/2016/07/Figure-1-1.png b/assets/images/2016/07/Figure-1-1.png new file mode 100644 index 0000000..5d34967 Binary files /dev/null and b/assets/images/2016/07/Figure-1-1.png differ diff --git a/assets/images/2016/07/Figure-2.png b/assets/images/2016/07/Figure-2.png new file mode 100644 index 0000000..6d1afdd Binary files /dev/null and b/assets/images/2016/07/Figure-2.png differ diff --git a/assets/images/2016/07/Figure-3.png b/assets/images/2016/07/Figure-3.png new file mode 100644 index 0000000..77dd65a Binary files /dev/null and b/assets/images/2016/07/Figure-3.png differ diff --git a/assets/images/2016/07/Figure-4.png b/assets/images/2016/07/Figure-4.png new file mode 100644 index 0000000..b2f6522 Binary files /dev/null and b/assets/images/2016/07/Figure-4.png differ diff --git a/assets/images/2016/07/Figure-5.png b/assets/images/2016/07/Figure-5.png new file mode 100644 index 0000000..2dbcc23 Binary files /dev/null and b/assets/images/2016/07/Figure-5.png differ diff --git a/assets/images/2016/07/Maitreya.png b/assets/images/2016/07/Maitreya.png new file mode 100644 index 0000000..e450c85 Binary files /dev/null and b/assets/images/2016/07/Maitreya.png differ diff --git a/assets/images/2016/07/Workday-Time-Diff-mono.svg b/assets/images/2016/07/Workday-Time-Diff-mono.svg new file mode 100644 index 0000000..f7aea8c --- /dev/null +++ b/assets/images/2016/07/Workday-Time-Diff-mono.svg @@ -0,0 +1 @@ +Get the parameter values from calling function- assigneddatetime in 'YYYY-MM-DD hh:mm' format,- closeddatetime in 'YYYY-MM-DD hh:mm' format,- starttime in 'hh:mm' format,- endtime in 'hh:mm' formatSet @starttime = starttimeSet @endtime = endtimeSet @assigneddate = assigneddatetimeSet @closeddate = closeddatetimeSet @timecount = 0Set @timevar1 = @assigneddateSet @nextdate = @assigneddateSet @timevar2 = null1.TIMEDIFF calculates difference between two times2.Time_to_sec function converts data into seconds3.Dividing by 3600 converts the time into hoursSelect time_to_sec(timediff(@endtime,@starttime))/3600into @maxhoursadaySet @checkstart = nullSet @checkend = nullSelect CONCAT(SUBSTRING_INDEX(@assigneddate, ' ', 1), ' ',@starttime),CONCAT(SUBSTRING_INDEX(@closeddate, ' ', 1), ' ',@endtime) into @checkstart, @checkendyes@assigneddate > @checkstart@closeddate<@checkendyesnoSet @assigneddate = @assigneddateSet @closeddate = @closeddateSet @assigneddate = @assigneddateSet @closeddate = @checkendyes@closeddate<@checkendnoSET @assigneddate = @checkstartSet @closeddate = @closeddateSET @assigneddate = @checkstartSet @closeddate = @checkendSELECT DATEDIFF(@closeddate, @assigneddate)INTO @fixcountSet @count = @fixcountfixcount>0?yesif not, avoid while loop asboth assigned and closed dateare same.calculate weekday for nextdateselect weekday(@nextdate) into @weekdaySet the holiday flag toanything other than zeroif the value in nextdatefor this loop is a holidaySelect sum(if(date_format(holiday_date,'%Y-%m-%d') =substring_index(@nextdate,' ',1),1,0)) fromholiday_table where Country_codes = 'ALL'or instr(Country_codes,@param_country)>0 into@holidayflag@weekday<5? and @holidayflag=0yesit is a weekend so get nextdateWeekday_Calculationsyes@count = @fixcount?Set @timevar1 = @assigneddateSELECT CONCAT(SUBSTRING_INDEX(@assigneddate, ' ', 1),' ',@endtime)INTO @timevar2yes@count = 0?noSelect concat(substring_index(@closeddate,' ',1),' ',@starttime)into @timevar1Set @timevar2 = @closeddateif neither of previous conditionsare true,then nextdate is neitherclosedate nor assigneddate so justuse the date with normal start andend timeSelect concat(@nextdate,' ',@starttime)into @timevar1SELECT CONCAT(@nextdate, ' ', @endtime)INTO @timevar21.TIMEDIFF calculates difference between two times2.Time_to_sec function converts data into seconds3.Dividing by 3600 converts the time into hours4.if the difference was negative, GREATEST willignore result in favour of 05.If result is greater than max working hours,LEAST will ignore result in favour of maxhoursdaySELECT LEAST(Greatest((TIME_TO_SEC(TIMEDIFF(@timevar2, @timevar1))) / 3600),0), @maxhoursaday)INTO @timecounttempSet @timecount = @timecounttemp + @timecountSet @timevar1 = @nextdateSELECTADDDATE(SUBSTRING_INDEX(@timevar1, ' ', 1),1)INTO @nextdateSet @count = @count - 1truecount>=0?false1.TIMEDIFF calculates difference between two times2.Time_to_sec function converts data into seconds3.Dividing by 3600 converts the time into hours4.if the difference was negative, GREATEST willignore result in favour of 05.If result is greater than max working hours,LEAST will ignore result in favour of maxhoursdaySELECT Least(Greatest(((TIME_TO_SEC(TIMEDIFF(@closeddate, @assigneddate)))/ 3600),0),@maxhoursaday) INTO @timecountreturn timecount*60 \ No newline at end of file diff --git a/assets/images/2016/07/Workday-Time-Diff.svg b/assets/images/2016/07/Workday-Time-Diff.svg new file mode 100644 index 0000000..26f0dd4 --- /dev/null +++ b/assets/images/2016/07/Workday-Time-Diff.svg @@ -0,0 +1 @@ +Get the parameter values from calling function- assigneddatetime in 'YYYY-MM-DD hh:mm' format,- closeddatetime in 'YYYY-MM-DD hh:mm' format,- starttime in 'hh:mm' format,- endtime in 'hh:mm' formatSet @starttime = starttimeSet @endtime = endtimeSet @assigneddate = assigneddatetimeSet @closeddate = closeddatetimeSet @timecount = 0Set @timevar1 = @assigneddateSet @nextdate = @assigneddateSet @timevar2 = null1.TIMEDIFF calculates difference between two times2.Time_to_sec function converts data into seconds3.Dividing by 3600 converts the time into hoursSelect time_to_sec(timediff(@endtime,@starttime))/3600into @maxhoursadaySet @checkstart = nullSet @checkend = nullSelect CONCAT(SUBSTRING_INDEX(@assigneddate, ' ', 1), ' ',@starttime),CONCAT(SUBSTRING_INDEX(@closeddate, ' ', 1), ' ',@endtime) into @checkstart, @checkendyes@assigneddate > @checkstart@closeddate<@checkendyesnoSet @assigneddate = @assigneddateSet @closeddate = @closeddateSet @assigneddate = @assigneddateSet @closeddate = @checkendyes@closeddate<@checkendnoSET @assigneddate = @checkstartSet @closeddate = @closeddateSET @assigneddate = @checkstartSet @closeddate = @checkendSELECT DATEDIFF(@closeddate, @assigneddate)INTO @fixcountSet @count = @fixcountfixcount>0?yesif not, avoid while loop asboth assigned and closed dateare same.calculate weekday for nextdateselect weekday(@nextdate) into @weekdaySet the holiday flag toanything other than zeroif the value in nextdatefor this loop is a holidaySelect sum(if(date_format(holiday_date,'%Y-%m-%d') =substring_index(@nextdate,' ',1),1,0)) fromholiday_table where Country_codes = 'ALL'or instr(Country_codes,@param_country)>0 into@holidayflag@weekday<5? and @holidayflag=0yesit is a weekend so get nextdateWeekday_Calculationsyes@count = @fixcount?Set @timevar1 = @assigneddateSELECT CONCAT(SUBSTRING_INDEX(@assigneddate, ' ', 1),' ',@endtime)INTO @timevar2yes@count = 0?noSelect concat(substring_index(@closeddate,' ',1),' ',@starttime)into @timevar1Set @timevar2 = @closeddateif neither of previous conditionsare true,then nextdate is neitherclosedate nor assigneddate so justuse the date with normal start andend timeSelect concat(@nextdate,' ',@starttime)into @timevar1SELECT CONCAT(@nextdate, ' ', @endtime)INTO @timevar21.TIMEDIFF calculates difference between two times2.Time_to_sec function converts data into seconds3.Dividing by 3600 converts the time into hours4.if the difference was negative, GREATEST willignore result in favour of 05.If result is greater than max working hours,LEAST will ignore result in favour of maxhoursdaySELECT LEAST(Greatest((TIME_TO_SEC(TIMEDIFF(@timevar2, @timevar1))) / 3600),0), @maxhoursaday)INTO @timecounttempSet @timecount = @timecounttemp + @timecountSet @timevar1 = @nextdateSELECTADDDATE(SUBSTRING_INDEX(@timevar1, ' ', 1),1)INTO @nextdateSet @count = @count - 1truecount>=0?false1.TIMEDIFF calculates difference between two times2.Time_to_sec function converts data into seconds3.Dividing by 3600 converts the time into hours4.if the difference was negative, GREATEST willignore result in favour of 05.If result is greater than max working hours,LEAST will ignore result in favour of maxhoursdaySELECT Least(Greatest(((TIME_TO_SEC(TIMEDIFF(@closeddate, @assigneddate)))/ 3600),0),@maxhoursaday) INTO @timecountreturn timecount*60 \ No newline at end of file diff --git a/assets/images/2016/07/advanced-dns-3-6-2016-171.png b/assets/images/2016/07/advanced-dns-3-6-2016-171.png new file mode 100644 index 0000000..c09aef0 Binary files /dev/null and b/assets/images/2016/07/advanced-dns-3-6-2016-171.png differ diff --git a/assets/images/2016/07/profile.PNG b/assets/images/2016/07/profile.PNG new file mode 100644 index 0000000..1e1f3b6 Binary files /dev/null and b/assets/images/2016/07/profile.PNG differ diff --git a/assets/images/2016/09/ComputerFrustration.jpg b/assets/images/2016/09/ComputerFrustration.jpg new file mode 100644 index 0000000..1666742 Binary files /dev/null and b/assets/images/2016/09/ComputerFrustration.jpg differ diff --git a/assets/images/2017/02/Administration-Command.PNG b/assets/images/2017/02/Administration-Command.PNG new file mode 100644 index 0000000..4dfed14 Binary files /dev/null and b/assets/images/2017/02/Administration-Command.PNG differ diff --git a/assets/images/2017/02/Administration-Management.PNG b/assets/images/2017/02/Administration-Management.PNG new file mode 100644 index 0000000..5dca7c6 Binary files /dev/null and b/assets/images/2017/02/Administration-Management.PNG differ diff --git a/assets/images/2017/02/CuteMarkEd-2017-02-12-20-02-02.png b/assets/images/2017/02/CuteMarkEd-2017-02-12-20-02-02.png new file mode 100644 index 0000000..cad95c1 Binary files /dev/null and b/assets/images/2017/02/CuteMarkEd-2017-02-12-20-02-02.png differ diff --git a/assets/images/2017/02/DDNS.PNG b/assets/images/2017/02/DDNS.PNG new file mode 100644 index 0000000..708af42 Binary files /dev/null and b/assets/images/2017/02/DDNS.PNG differ diff --git a/assets/images/2017/02/DateGantt.PNG b/assets/images/2017/02/DateGantt.PNG new file mode 100644 index 0000000..068c092 Binary files /dev/null and b/assets/images/2017/02/DateGantt.PNG differ diff --git a/assets/images/2017/02/GanttChartCuteMarkEd-1.PNG b/assets/images/2017/02/GanttChartCuteMarkEd-1.PNG new file mode 100644 index 0000000..1b60f6a Binary files /dev/null and b/assets/images/2017/02/GanttChartCuteMarkEd-1.PNG differ diff --git a/assets/images/2017/02/GanttChartCuteMarkEd-2.PNG b/assets/images/2017/02/GanttChartCuteMarkEd-2.PNG new file mode 100644 index 0000000..46d6de5 Binary files /dev/null and b/assets/images/2017/02/GanttChartCuteMarkEd-2.PNG differ diff --git a/assets/images/2017/02/GanttChartCuteMarkEd.PNG b/assets/images/2017/02/GanttChartCuteMarkEd.PNG new file mode 100644 index 0000000..1b60f6a Binary files /dev/null and b/assets/images/2017/02/GanttChartCuteMarkEd.PNG differ diff --git a/assets/images/2017/02/NAT-QOS-PortForwarding.PNG b/assets/images/2017/02/NAT-QOS-PortForwarding.PNG new file mode 100644 index 0000000..bfde33d Binary files /dev/null and b/assets/images/2017/02/NAT-QOS-PortForwarding.PNG differ diff --git a/assets/images/2017/02/NAT-QOS-PortRangeForwarding.PNG b/assets/images/2017/02/NAT-QOS-PortRangeForwarding.PNG new file mode 100644 index 0000000..c41ee4b Binary files /dev/null and b/assets/images/2017/02/NAT-QOS-PortRangeForwarding.PNG differ diff --git a/assets/images/2017/02/Services-Services.PNG b/assets/images/2017/02/Services-Services.PNG new file mode 100644 index 0000000..e4f2f47 Binary files /dev/null and b/assets/images/2017/02/Services-Services.PNG differ diff --git a/assets/images/2017/02/SetupBasicSetup_1.PNG b/assets/images/2017/02/SetupBasicSetup_1.PNG new file mode 100644 index 0000000..5dcb070 Binary files /dev/null and b/assets/images/2017/02/SetupBasicSetup_1.PNG differ diff --git a/assets/images/2017/02/SetupBasicSetup_2.PNG b/assets/images/2017/02/SetupBasicSetup_2.PNG new file mode 100644 index 0000000..8757139 Binary files /dev/null and b/assets/images/2017/02/SetupBasicSetup_2.PNG differ diff --git a/assets/images/2017/02/Wireless-Basic-Settings.PNG b/assets/images/2017/02/Wireless-Basic-Settings.PNG new file mode 100644 index 0000000..81e9637 Binary files /dev/null and b/assets/images/2017/02/Wireless-Basic-Settings.PNG differ diff --git a/assets/images/2017/02/Wireless-Wireless-Security.PNG b/assets/images/2017/02/Wireless-Wireless-Security.PNG new file mode 100644 index 0000000..1a623c6 Binary files /dev/null and b/assets/images/2017/02/Wireless-Wireless-Security.PNG differ diff --git a/assets/images/2017/10/Screenshot-from-2017-10-17-23-32-43.png b/assets/images/2017/10/Screenshot-from-2017-10-17-23-32-43.png new file mode 100644 index 0000000..43b0145 Binary files /dev/null and b/assets/images/2017/10/Screenshot-from-2017-10-17-23-32-43.png differ diff --git a/assets/images/2017/10/finish-install-process.PNG b/assets/images/2017/10/finish-install-process.PNG new file mode 100644 index 0000000..29ff0b8 Binary files /dev/null and b/assets/images/2017/10/finish-install-process.PNG differ diff --git a/assets/images/2017/10/nginx-not-found.PNG b/assets/images/2017/10/nginx-not-found.PNG new file mode 100644 index 0000000..35507d6 Binary files /dev/null and b/assets/images/2017/10/nginx-not-found.PNG differ diff --git a/assets/images/2017/11/Menu_001.png b/assets/images/2017/11/Menu_001.png new file mode 100644 index 0000000..389a2e9 Binary files /dev/null and b/assets/images/2017/11/Menu_001.png differ diff --git a/assets/images/2018/01/20171219_163816.jpg b/assets/images/2018/01/20171219_163816.jpg new file mode 100644 index 0000000..54cc8fc Binary files /dev/null and b/assets/images/2018/01/20171219_163816.jpg differ diff --git a/assets/images/2018/01/Workday-Time-Diff-mono.svg b/assets/images/2018/01/Workday-Time-Diff-mono.svg new file mode 100644 index 0000000..1708067 --- /dev/null +++ b/assets/images/2018/01/Workday-Time-Diff-mono.svg @@ -0,0 +1,169 @@ +Get the parameter values from calling function- assigneddatetime in 'YYYY-MM-DD hh:mm' format,- closeddatetime in 'YYYY-MM-DD hh:mm' format,- starttime in 'hh:mm' format,- endtime in 'hh:mm' formatSet @starttime = starttimeSet @endtime = endtimeSet @assigneddate = assigneddatetimeSet @closeddate = closeddatetimeSet @timecount = 0Set @timevar1 = @assigneddateSet @nextdate = @assigneddateSet @timevar2 = null1.TIMEDIFF calculates difference between two times2.Time_to_sec function converts data into seconds3.Dividing by 3600 converts the time into hoursSelect time_to_sec(timediff(@endtime,@starttime))/3600into @maxhoursadaySet @checkstart = nullSet @checkend = nullSelect CONCAT(SUBSTRING_INDEX(@assigneddate, ' ', 1), ' ',@starttime),CONCAT(SUBSTRING_INDEX(@closeddate, ' ', 1), ' ',@endtime) into @checkstart, @checkendyes@assigneddate > @checkstart@closeddate<@checkendyesnoSet @assigneddate = @assigneddateSet @closeddate = @closeddateSet @assigneddate = @assigneddateSet @closeddate = @checkendyes@closeddate<@checkendnoSET @assigneddate = @checkstartSet @closeddate = @closeddateSET @assigneddate = @checkstartSet @closeddate = @checkendSELECT DATEDIFF(@closeddate, @assigneddate)INTO @fixcountSet @count = @fixcountfixcount>0?yesif not, avoid while loop asboth assigned and closed dateare same.calculate weekday for nextdateselect weekday(@nextdate) into @weekdaySet the holiday flag toanything other than zeroif the value in nextdatefor this loop is a holidaySelect sum(if(date_format(holiday_date,'%Y-%m-%d') =substring_index(@nextdate,' ',1),1,0)) fromholiday_table where Country_codes = 'ALL'or instr(Country_codes,@param_country)>0 into@holidayflagWeekday_Calculationsyes@count = @fixcount?Set @timevar1 = @assigneddateSELECT CONCAT(SUBSTRING_INDEX(@assigneddate, ' ', 1),' ',@endtime)INTO @timevar2yes@count = 0?noSelect concat(substring_index(@closeddate,' ',1),' ',@starttime)into @timevar1Set @timevar2 = @closeddateif neither of previous conditionsare true,then nextdate is neitherclosedate nor assigneddate so justuse the date with normal start andend timeSelect concat(@nextdate,' ',@starttime)into @timevar1SELECT CONCAT(@nextdate, ' ', @endtime)INTO @timevar21.TIMEDIFF calculates difference between two times2.Time_to_sec function converts data into seconds3.Dividing by 3600 converts the time into hours4.if the difference was negative, GREATEST willignore result in favour of 05.If result is greater than max working hours,LEAST will ignore result in favour of maxhoursdaySELECT LEAST(Greatest((TIME_TO_SEC(TIMEDIFF(@timevar2, @timevar1))) / 3600),0), @maxhoursaday)INTO @timecounttempSet @timecount = @timecounttemp + @timecountyes@weekday<5? and @holidayflag=0it is a weekend so get nextdateSet @timevar1 = @nextdateSELECTADDDATE(SUBSTRING_INDEX(@timevar1, ' ', 1),1)INTO @nextdateSet @count = @count - 1truecount>=0?falsecalculate weekday for assigneddateselect weekday(@assigneddate) into @weekdaySet the holiday flag toanything other than zeroif the value in nextdatefor this loop is a holidaySelect sum(if(date_format(holiday_date,'%Y-%m-%d') =substring_index(@assigneddate,' ',1),1,0)) fromholiday_table where Country_codes = 'ALL'or instr(Country_codes,@param_country)>0 into@holidayflag@weekday<5? and @holidayflag=0yesif assigned and closed over holiday / weekend, set @timecount to 01.TIMEDIFF calculates difference between two times2.Time_to_sec function converts data into seconds3.Dividing by 3600 converts the time into hours4.if the difference was negative, GREATEST willignore result in favour of 05.If result is greater than max working hours,LEAST will ignore result in favour of maxhoursdaySELECT Least(Greatest(((TIME_TO_SEC(TIMEDIFF(@closeddate, @assigneddate)))/ 3600),0),@maxhoursaday) INTO @timecountSet @timecount = 0return timecount*60 \ No newline at end of file diff --git a/assets/images/2018/01/Workday-Time-Diff.svg b/assets/images/2018/01/Workday-Time-Diff.svg new file mode 100644 index 0000000..1f1532c --- /dev/null +++ b/assets/images/2018/01/Workday-Time-Diff.svg @@ -0,0 +1,168 @@ +Get the parameter values from calling function- assigneddatetime in 'YYYY-MM-DD hh:mm' format,- closeddatetime in 'YYYY-MM-DD hh:mm' format,- starttime in 'hh:mm' format,- endtime in 'hh:mm' formatSet @starttime = starttimeSet @endtime = endtimeSet @assigneddate = assigneddatetimeSet @closeddate = closeddatetimeSet @timecount = 0Set @timevar1 = @assigneddateSet @nextdate = @assigneddateSet @timevar2 = null1.TIMEDIFF calculates difference between two times2.Time_to_sec function converts data into seconds3.Dividing by 3600 converts the time into hoursSelect time_to_sec(timediff(@endtime,@starttime))/3600into @maxhoursadaySet @checkstart = nullSet @checkend = nullSelect CONCAT(SUBSTRING_INDEX(@assigneddate, ' ', 1), ' ',@starttime),CONCAT(SUBSTRING_INDEX(@closeddate, ' ', 1), ' ',@endtime) into @checkstart, @checkendyes@assigneddate > @checkstart@closeddate<@checkendyesnoSet @assigneddate = @assigneddateSet @closeddate = @closeddateSet @assigneddate = @assigneddateSet @closeddate = @checkendyes@closeddate<@checkendnoSET @assigneddate = @checkstartSet @closeddate = @closeddateSET @assigneddate = @checkstartSet @closeddate = @checkendSELECT DATEDIFF(@closeddate, @assigneddate)INTO @fixcountSet @count = @fixcountfixcount>0?yesif not, avoid while loop asboth assigned and closed dateare same.calculate weekday for nextdateselect weekday(@nextdate) into @weekdaySet the holiday flag toanything other than zeroif the value in nextdatefor this loop is a holidaySelect sum(if(date_format(holiday_date,'%Y-%m-%d') =substring_index(@nextdate,' ',1),1,0)) fromholiday_table where Country_codes = 'ALL'or instr(Country_codes,@param_country)>0 into@holidayflagWeekday_Calculationsyes@count = @fixcount?Set @timevar1 = @assigneddateSELECT CONCAT(SUBSTRING_INDEX(@assigneddate, ' ', 1),' ',@endtime)INTO @timevar2yes@count = 0?noSelect concat(substring_index(@closeddate,' ',1),' ',@starttime)into @timevar1Set @timevar2 = @closeddateif neither of previous conditionsare true,then nextdate is neitherclosedate nor assigneddate so justuse the date with normal start andend timeSelect concat(@nextdate,' ',@starttime)into @timevar1SELECT CONCAT(@nextdate, ' ', @endtime)INTO @timevar21.TIMEDIFF calculates difference between two times2.Time_to_sec function converts data into seconds3.Dividing by 3600 converts the time into hours4.if the difference was negative, GREATEST willignore result in favour of 05.If result is greater than max working hours,LEAST will ignore result in favour of maxhoursdaySELECT LEAST(Greatest((TIME_TO_SEC(TIMEDIFF(@timevar2, @timevar1))) / 3600),0), @maxhoursaday)INTO @timecounttempSet @timecount = @timecounttemp + @timecountyes@weekday<5? and @holidayflag=0it is a weekend so get nextdateSet @timevar1 = @nextdateSELECTADDDATE(SUBSTRING_INDEX(@timevar1, ' ', 1),1)INTO @nextdateSet @count = @count - 1truecount>=0?falsecalculate weekday for assigneddateselect weekday(@assigneddate) into @weekdaySet the holiday flag toanything other than zeroif the value in nextdatefor this loop is a holidaySelect sum(if(date_format(holiday_date,'%Y-%m-%d') =substring_index(@assigneddate,' ',1),1,0)) fromholiday_table where Country_codes = 'ALL'or instr(Country_codes,@param_country)>0 into@holidayflag@weekday<5? and @holidayflag=0yesif assigned and closed over holiday / weekend, set @timecount to 01.TIMEDIFF calculates difference between two times2.Time_to_sec function converts data into seconds3.Dividing by 3600 converts the time into hours4.if the difference was negative, GREATEST willignore result in favour of 05.If result is greater than max working hours,LEAST will ignore result in favour of maxhoursdaySELECT Least(Greatest(((TIME_TO_SEC(TIMEDIFF(@closeddate, @assigneddate)))/ 3600),0),@maxhoursaday) INTO @timecountSet @timecount = 0return timecount*60 \ No newline at end of file diff --git a/assets/images/2018/06/Gajim_2018-06-15_17-10-11.png b/assets/images/2018/06/Gajim_2018-06-15_17-10-11.png new file mode 100644 index 0000000..16bbb1b Binary files /dev/null and b/assets/images/2018/06/Gajim_2018-06-15_17-10-11.png differ diff --git a/assets/images/2018/06/PUTTY_2018-06-15_12-08-21.png b/assets/images/2018/06/PUTTY_2018-06-15_12-08-21.png new file mode 100644 index 0000000..938dd5c Binary files /dev/null and b/assets/images/2018/06/PUTTY_2018-06-15_12-08-21.png differ diff --git a/assets/images/2018/06/PUTTY_2018-06-15_12-19-30.png b/assets/images/2018/06/PUTTY_2018-06-15_12-19-30.png new file mode 100644 index 0000000..5480d0d Binary files /dev/null and b/assets/images/2018/06/PUTTY_2018-06-15_12-19-30.png differ diff --git a/assets/images/2018/06/PUTTY_2018-06-15_12-49-42.png b/assets/images/2018/06/PUTTY_2018-06-15_12-49-42.png new file mode 100644 index 0000000..41ad195 Binary files /dev/null and b/assets/images/2018/06/PUTTY_2018-06-15_12-49-42.png differ diff --git a/assets/images/2018/06/PUTTY_2018-06-15_17-23-18.png b/assets/images/2018/06/PUTTY_2018-06-15_17-23-18.png new file mode 100644 index 0000000..93e1751 Binary files /dev/null and b/assets/images/2018/06/PUTTY_2018-06-15_17-23-18.png differ diff --git a/assets/images/2018/06/chrome_2018-06-15_11-34-57.png b/assets/images/2018/06/chrome_2018-06-15_11-34-57.png new file mode 100644 index 0000000..22ba6b3 Binary files /dev/null and b/assets/images/2018/06/chrome_2018-06-15_11-34-57.png differ diff --git a/assets/images/2018/06/chrome_2018-06-15_11-43-31.png b/assets/images/2018/06/chrome_2018-06-15_11-43-31.png new file mode 100644 index 0000000..abd0c28 Binary files /dev/null and b/assets/images/2018/06/chrome_2018-06-15_11-43-31.png differ diff --git a/assets/images/2018/06/chrome_2018-06-15_11-48-32.png b/assets/images/2018/06/chrome_2018-06-15_11-48-32.png new file mode 100644 index 0000000..7bccb88 Binary files /dev/null and b/assets/images/2018/06/chrome_2018-06-15_11-48-32.png differ diff --git a/assets/images/2018/06/chrome_2018-06-15_11-53-35.png b/assets/images/2018/06/chrome_2018-06-15_11-53-35.png new file mode 100644 index 0000000..f9ade97 Binary files /dev/null and b/assets/images/2018/06/chrome_2018-06-15_11-53-35.png differ diff --git a/assets/images/2018/06/chrome_2018-06-15_17-02-32.png b/assets/images/2018/06/chrome_2018-06-15_17-02-32.png new file mode 100644 index 0000000..73f5a1a Binary files /dev/null and b/assets/images/2018/06/chrome_2018-06-15_17-02-32.png differ diff --git a/assets/images/2018/09/DSC_0469.JPG b/assets/images/2018/09/DSC_0469.JPG new file mode 100644 index 0000000..4af7579 Binary files /dev/null and b/assets/images/2018/09/DSC_0469.JPG differ diff --git a/assets/images/2020/05/2020-05-1919-17-07.png b/assets/images/2020/05/2020-05-1919-17-07.png new file mode 100644 index 0000000..e2b53d4 Binary files /dev/null and b/assets/images/2020/05/2020-05-1919-17-07.png differ diff --git a/assets/images/2020/05/2020-05-1919-17-07_o.png b/assets/images/2020/05/2020-05-1919-17-07_o.png new file mode 100644 index 0000000..543ea0a Binary files /dev/null and b/assets/images/2020/05/2020-05-1919-17-07_o.png differ diff --git a/assets/images/2020/05/2020-05-1919-46-13-1.png b/assets/images/2020/05/2020-05-1919-46-13-1.png new file mode 100644 index 0000000..06b9bde Binary files /dev/null and b/assets/images/2020/05/2020-05-1919-46-13-1.png differ diff --git a/assets/images/2020/05/2020-05-1919-46-13_o-1.png b/assets/images/2020/05/2020-05-1919-46-13_o-1.png new file mode 100644 index 0000000..5b85667 Binary files /dev/null and b/assets/images/2020/05/2020-05-1919-46-13_o-1.png differ diff --git a/assets/images/2020/05/2020-05-1920-34-14.png b/assets/images/2020/05/2020-05-1920-34-14.png new file mode 100644 index 0000000..563dd92 Binary files /dev/null and b/assets/images/2020/05/2020-05-1920-34-14.png differ diff --git a/assets/images/2020/05/2020-05-1920-34-14_o.png b/assets/images/2020/05/2020-05-1920-34-14_o.png new file mode 100644 index 0000000..563dd92 Binary files /dev/null and b/assets/images/2020/05/2020-05-1920-34-14_o.png differ diff --git a/assets/images/2020/05/2020-05-1920-35-14.png b/assets/images/2020/05/2020-05-1920-35-14.png new file mode 100644 index 0000000..8e2ee89 Binary files /dev/null and b/assets/images/2020/05/2020-05-1920-35-14.png differ diff --git a/assets/images/2020/05/2020-05-1920-35-14_o.png b/assets/images/2020/05/2020-05-1920-35-14_o.png new file mode 100644 index 0000000..8e2ee89 Binary files /dev/null and b/assets/images/2020/05/2020-05-1920-35-14_o.png differ diff --git a/assets/images/2021/03/00aec12e25c74fed8e124e788850090e.png b/assets/images/2021/03/00aec12e25c74fed8e124e788850090e.png new file mode 100644 index 0000000..1e2f339 Binary files /dev/null and b/assets/images/2021/03/00aec12e25c74fed8e124e788850090e.png differ diff --git a/assets/images/2021/03/00aec12e25c74fed8e124e788850090e_o.png b/assets/images/2021/03/00aec12e25c74fed8e124e788850090e_o.png new file mode 100644 index 0000000..1e2f339 Binary files /dev/null and b/assets/images/2021/03/00aec12e25c74fed8e124e788850090e_o.png differ diff --git a/assets/images/2021/03/053fbb025cc74ebc880f729d3dc3b6b2.png b/assets/images/2021/03/053fbb025cc74ebc880f729d3dc3b6b2.png new file mode 100644 index 0000000..14c8418 Binary files /dev/null and b/assets/images/2021/03/053fbb025cc74ebc880f729d3dc3b6b2.png differ diff --git a/assets/images/2021/03/053fbb025cc74ebc880f729d3dc3b6b2_o.png b/assets/images/2021/03/053fbb025cc74ebc880f729d3dc3b6b2_o.png new file mode 100644 index 0000000..dd19765 Binary files /dev/null and b/assets/images/2021/03/053fbb025cc74ebc880f729d3dc3b6b2_o.png differ diff --git a/assets/images/2021/03/2707fa8797d94ed08f1d6c44fc04ef85.png b/assets/images/2021/03/2707fa8797d94ed08f1d6c44fc04ef85.png new file mode 100644 index 0000000..2a83de8 Binary files /dev/null and b/assets/images/2021/03/2707fa8797d94ed08f1d6c44fc04ef85.png differ diff --git a/assets/images/2021/03/2707fa8797d94ed08f1d6c44fc04ef85_o.png b/assets/images/2021/03/2707fa8797d94ed08f1d6c44fc04ef85_o.png new file mode 100644 index 0000000..5542b77 Binary files /dev/null and b/assets/images/2021/03/2707fa8797d94ed08f1d6c44fc04ef85_o.png differ diff --git a/assets/images/2021/03/4aa44c106c724c16b27129ccc3acfb3a.png b/assets/images/2021/03/4aa44c106c724c16b27129ccc3acfb3a.png new file mode 100644 index 0000000..325a3e7 Binary files /dev/null and b/assets/images/2021/03/4aa44c106c724c16b27129ccc3acfb3a.png differ diff --git a/assets/images/2021/03/4aa44c106c724c16b27129ccc3acfb3a_o.png b/assets/images/2021/03/4aa44c106c724c16b27129ccc3acfb3a_o.png new file mode 100644 index 0000000..325a3e7 Binary files /dev/null and b/assets/images/2021/03/4aa44c106c724c16b27129ccc3acfb3a_o.png differ diff --git a/assets/images/2021/03/4c718c71ade84cb7a2c9eeb3596e32c1.png b/assets/images/2021/03/4c718c71ade84cb7a2c9eeb3596e32c1.png new file mode 100644 index 0000000..50cee92 Binary files /dev/null and b/assets/images/2021/03/4c718c71ade84cb7a2c9eeb3596e32c1.png differ diff --git a/assets/images/2021/03/4c718c71ade84cb7a2c9eeb3596e32c1_o.png b/assets/images/2021/03/4c718c71ade84cb7a2c9eeb3596e32c1_o.png new file mode 100644 index 0000000..47b6cca Binary files /dev/null and b/assets/images/2021/03/4c718c71ade84cb7a2c9eeb3596e32c1_o.png differ diff --git a/assets/images/2021/03/5229542f7a364b07bbed0356ec8f3565.png b/assets/images/2021/03/5229542f7a364b07bbed0356ec8f3565.png new file mode 100644 index 0000000..dbfe6fb Binary files /dev/null and b/assets/images/2021/03/5229542f7a364b07bbed0356ec8f3565.png differ diff --git a/assets/images/2021/03/5229542f7a364b07bbed0356ec8f3565_o.png b/assets/images/2021/03/5229542f7a364b07bbed0356ec8f3565_o.png new file mode 100644 index 0000000..6c66df2 Binary files /dev/null and b/assets/images/2021/03/5229542f7a364b07bbed0356ec8f3565_o.png differ diff --git a/assets/images/2021/03/64ed999845d644239615c3b0e2aab454.png b/assets/images/2021/03/64ed999845d644239615c3b0e2aab454.png new file mode 100644 index 0000000..e216f27 Binary files /dev/null and b/assets/images/2021/03/64ed999845d644239615c3b0e2aab454.png differ diff --git a/assets/images/2021/03/64ed999845d644239615c3b0e2aab454_o.png b/assets/images/2021/03/64ed999845d644239615c3b0e2aab454_o.png new file mode 100644 index 0000000..296a0ee Binary files /dev/null and b/assets/images/2021/03/64ed999845d644239615c3b0e2aab454_o.png differ diff --git a/assets/images/2021/03/84da158d66534dcc97a7a8bb60cba58e.png b/assets/images/2021/03/84da158d66534dcc97a7a8bb60cba58e.png new file mode 100644 index 0000000..ba66990 Binary files /dev/null and b/assets/images/2021/03/84da158d66534dcc97a7a8bb60cba58e.png differ diff --git a/assets/images/2021/03/84da158d66534dcc97a7a8bb60cba58e_o.png b/assets/images/2021/03/84da158d66534dcc97a7a8bb60cba58e_o.png new file mode 100644 index 0000000..b137146 Binary files /dev/null and b/assets/images/2021/03/84da158d66534dcc97a7a8bb60cba58e_o.png differ diff --git a/assets/images/2021/03/8c623ef74cdc41d8a6ba5fd05e30b32d.png b/assets/images/2021/03/8c623ef74cdc41d8a6ba5fd05e30b32d.png new file mode 100644 index 0000000..765521a Binary files /dev/null and b/assets/images/2021/03/8c623ef74cdc41d8a6ba5fd05e30b32d.png differ diff --git a/assets/images/2021/03/8c623ef74cdc41d8a6ba5fd05e30b32d_o.png b/assets/images/2021/03/8c623ef74cdc41d8a6ba5fd05e30b32d_o.png new file mode 100644 index 0000000..91f7c59 Binary files /dev/null and b/assets/images/2021/03/8c623ef74cdc41d8a6ba5fd05e30b32d_o.png differ diff --git a/assets/images/2021/03/8f671fb4efa542d08bdf60c1ead2756d.png b/assets/images/2021/03/8f671fb4efa542d08bdf60c1ead2756d.png new file mode 100644 index 0000000..a3680a2 Binary files /dev/null and b/assets/images/2021/03/8f671fb4efa542d08bdf60c1ead2756d.png differ diff --git a/assets/images/2021/03/8f671fb4efa542d08bdf60c1ead2756d_o.png b/assets/images/2021/03/8f671fb4efa542d08bdf60c1ead2756d_o.png new file mode 100644 index 0000000..2e5897e Binary files /dev/null and b/assets/images/2021/03/8f671fb4efa542d08bdf60c1ead2756d_o.png differ diff --git a/assets/images/2021/03/ProfilePix.jpg b/assets/images/2021/03/ProfilePix.jpg new file mode 100644 index 0000000..27d5c7b Binary files /dev/null and b/assets/images/2021/03/ProfilePix.jpg differ diff --git a/assets/images/2021/03/ProfilePix_o.jpg b/assets/images/2021/03/ProfilePix_o.jpg new file mode 100644 index 0000000..27d5c7b Binary files /dev/null and b/assets/images/2021/03/ProfilePix_o.jpg differ diff --git a/assets/images/2021/03/adfd44c4c0cf4e91aeeb8792970dbf67.png b/assets/images/2021/03/adfd44c4c0cf4e91aeeb8792970dbf67.png new file mode 100644 index 0000000..952aa3c Binary files /dev/null and b/assets/images/2021/03/adfd44c4c0cf4e91aeeb8792970dbf67.png differ diff --git a/assets/images/2021/03/adfd44c4c0cf4e91aeeb8792970dbf67_o.png b/assets/images/2021/03/adfd44c4c0cf4e91aeeb8792970dbf67_o.png new file mode 100644 index 0000000..65c1be5 Binary files /dev/null and b/assets/images/2021/03/adfd44c4c0cf4e91aeeb8792970dbf67_o.png differ diff --git a/assets/images/2021/03/cc9cdbfe94474be4b87515de72d9f98c.png b/assets/images/2021/03/cc9cdbfe94474be4b87515de72d9f98c.png new file mode 100644 index 0000000..0526618 Binary files /dev/null and b/assets/images/2021/03/cc9cdbfe94474be4b87515de72d9f98c.png differ diff --git a/assets/images/2021/03/cc9cdbfe94474be4b87515de72d9f98c_o.png b/assets/images/2021/03/cc9cdbfe94474be4b87515de72d9f98c_o.png new file mode 100644 index 0000000..7b06bf2 Binary files /dev/null and b/assets/images/2021/03/cc9cdbfe94474be4b87515de72d9f98c_o.png differ diff --git a/assets/images/2021/03/d417b24684524a05985e737b656b55a9.png b/assets/images/2021/03/d417b24684524a05985e737b656b55a9.png new file mode 100644 index 0000000..22338ce Binary files /dev/null and b/assets/images/2021/03/d417b24684524a05985e737b656b55a9.png differ diff --git a/assets/images/2021/03/d417b24684524a05985e737b656b55a9_o.png b/assets/images/2021/03/d417b24684524a05985e737b656b55a9_o.png new file mode 100644 index 0000000..b13a03c Binary files /dev/null and b/assets/images/2021/03/d417b24684524a05985e737b656b55a9_o.png differ diff --git a/assets/images/2021/03/tidepool.png b/assets/images/2021/03/tidepool.png new file mode 100644 index 0000000..c74b578 Binary files /dev/null and b/assets/images/2021/03/tidepool.png differ diff --git a/assets/images/2021/03/tidepool_o.png b/assets/images/2021/03/tidepool_o.png new file mode 100644 index 0000000..6a3bca7 Binary files /dev/null and b/assets/images/2021/03/tidepool_o.png differ diff --git a/assets/images/2023/01/1.png b/assets/images/2023/01/1.png new file mode 100644 index 0000000..a2bf0bd Binary files /dev/null and b/assets/images/2023/01/1.png differ diff --git a/assets/images/2023/01/2.png b/assets/images/2023/01/2.png new file mode 100644 index 0000000..98fd530 Binary files /dev/null and b/assets/images/2023/01/2.png differ diff --git a/assets/images/2023/01/3.png b/assets/images/2023/01/3.png new file mode 100644 index 0000000..4a42de5 Binary files /dev/null and b/assets/images/2023/01/3.png differ diff --git a/assets/images/2023/01/4.png b/assets/images/2023/01/4.png new file mode 100644 index 0000000..34a8c72 Binary files /dev/null and b/assets/images/2023/01/4.png differ diff --git a/assets/images/2023/01/5.png b/assets/images/2023/01/5.png new file mode 100644 index 0000000..0d2bf7b Binary files /dev/null and b/assets/images/2023/01/5.png differ diff --git a/assets/images/2023/01/6.png b/assets/images/2023/01/6.png new file mode 100644 index 0000000..63003f7 Binary files /dev/null and b/assets/images/2023/01/6.png differ diff --git a/assets/images/2023/01/7.png b/assets/images/2023/01/7.png new file mode 100644 index 0000000..41ddf80 Binary files /dev/null and b/assets/images/2023/01/7.png differ diff --git a/assets/images/2024/232877297-2d3a2914-8a7f-4f20-bba1-af8d0e92c039.png b/assets/images/2024/232877297-2d3a2914-8a7f-4f20-bba1-af8d0e92c039.png new file mode 100644 index 0000000..86d7431 Binary files /dev/null and b/assets/images/2024/232877297-2d3a2914-8a7f-4f20-bba1-af8d0e92c039.png differ diff --git a/assets/images/2024/232879216-22e3b81c-10d0-449f-80d1-30725c24b5ff.png b/assets/images/2024/232879216-22e3b81c-10d0-449f-80d1-30725c24b5ff.png new file mode 100644 index 0000000..0d10dac Binary files /dev/null and b/assets/images/2024/232879216-22e3b81c-10d0-449f-80d1-30725c24b5ff.png differ diff --git a/assets/images/2024/232881326-f2ecdd14-8bf6-4f7b-bcd4-3e5de4f361a2.png b/assets/images/2024/232881326-f2ecdd14-8bf6-4f7b-bcd4-3e5de4f361a2.png new file mode 100644 index 0000000..a1f5fd7 Binary files /dev/null and b/assets/images/2024/232881326-f2ecdd14-8bf6-4f7b-bcd4-3e5de4f361a2.png differ diff --git a/assets/images/2024/232881685-6a0eafd9-a5be-464b-ae0c-5814b101fc02.png b/assets/images/2024/232881685-6a0eafd9-a5be-464b-ae0c-5814b101fc02.png new file mode 100644 index 0000000..4e1fc9a Binary files /dev/null and b/assets/images/2024/232881685-6a0eafd9-a5be-464b-ae0c-5814b101fc02.png differ diff --git a/assets/images/2024/232882657-dd1e4e69-26a3-4849-8e94-9811cedab6d7.png b/assets/images/2024/232882657-dd1e4e69-26a3-4849-8e94-9811cedab6d7.png new file mode 100644 index 0000000..604c624 Binary files /dev/null and b/assets/images/2024/232882657-dd1e4e69-26a3-4849-8e94-9811cedab6d7.png differ diff --git a/assets/images/2024/357450387-8c9a7f4c-21a4-42a7-8951-1f4b301fa269.jpeg b/assets/images/2024/357450387-8c9a7f4c-21a4-42a7-8951-1f4b301fa269.jpeg new file mode 100644 index 0000000..01f341b Binary files /dev/null and b/assets/images/2024/357450387-8c9a7f4c-21a4-42a7-8951-1f4b301fa269.jpeg differ diff --git a/assets/images/2024/357452219-097dab9f-33a0-4f4a-a807-15923b754e65.jpeg b/assets/images/2024/357452219-097dab9f-33a0-4f4a-a807-15923b754e65.jpeg new file mode 100644 index 0000000..7cc2ca9 Binary files /dev/null and b/assets/images/2024/357452219-097dab9f-33a0-4f4a-a807-15923b754e65.jpeg differ diff --git a/assets/images/2024/357452627-5a295ec2-ab6c-447f-b485-d70166997eba.png b/assets/images/2024/357452627-5a295ec2-ab6c-447f-b485-d70166997eba.png new file mode 100644 index 0000000..b30b6f8 Binary files /dev/null and b/assets/images/2024/357452627-5a295ec2-ab6c-447f-b485-d70166997eba.png differ diff --git a/assets/images/2024/358212031-4caa35b9-9c71-4862-9dc1-a0c0c111fe23.png b/assets/images/2024/358212031-4caa35b9-9c71-4862-9dc1-a0c0c111fe23.png new file mode 100644 index 0000000..2bc93c3 Binary files /dev/null and b/assets/images/2024/358212031-4caa35b9-9c71-4862-9dc1-a0c0c111fe23.png differ diff --git a/assets/images/2024/358212180-a00cb3b5-497a-4401-810b-1276e7012641.png b/assets/images/2024/358212180-a00cb3b5-497a-4401-810b-1276e7012641.png new file mode 100644 index 0000000..f64117a Binary files /dev/null and b/assets/images/2024/358212180-a00cb3b5-497a-4401-810b-1276e7012641.png differ diff --git a/assets/images/disqus_comments/dd-wrt-firmware-on-tp-link-tl-wr841n-v11-dark.png b/assets/images/disqus_comments/dd-wrt-firmware-on-tp-link-tl-wr841n-v11-dark.png new file mode 100644 index 0000000..24da51f Binary files /dev/null and b/assets/images/disqus_comments/dd-wrt-firmware-on-tp-link-tl-wr841n-v11-dark.png differ diff --git a/assets/images/disqus_comments/ddclient-on-fedora-2-dark.png b/assets/images/disqus_comments/ddclient-on-fedora-2-dark.png new file mode 100644 index 0000000..48ec8bd Binary files /dev/null and b/assets/images/disqus_comments/ddclient-on-fedora-2-dark.png differ diff --git a/assets/images/disqus_comments/ddclient-on-fedora-2-light.png b/assets/images/disqus_comments/ddclient-on-fedora-2-light.png new file mode 100644 index 0000000..c0f0e53 Binary files /dev/null and b/assets/images/disqus_comments/ddclient-on-fedora-2-light.png differ diff --git a/assets/images/disqus_comments/fix-for-php-issues-after-upgrade-to-ubuntu-16-04-1-xenial-dark.png b/assets/images/disqus_comments/fix-for-php-issues-after-upgrade-to-ubuntu-16-04-1-xenial-dark.png new file mode 100644 index 0000000..17ad3fb Binary files /dev/null and b/assets/images/disqus_comments/fix-for-php-issues-after-upgrade-to-ubuntu-16-04-1-xenial-dark.png differ diff --git a/assets/images/disqus_comments/fix-for-php-issues-after-upgrade-to-ubuntu-16-04-1-xenial-light.png b/assets/images/disqus_comments/fix-for-php-issues-after-upgrade-to-ubuntu-16-04-1-xenial-light.png new file mode 100644 index 0000000..cbca6bd Binary files /dev/null and b/assets/images/disqus_comments/fix-for-php-issues-after-upgrade-to-ubuntu-16-04-1-xenial-light.png differ diff --git a/assets/images/disqus_comments/install-maitreya-vedic-astrology-software-on-ubuntu-kbuntu-dark.png b/assets/images/disqus_comments/install-maitreya-vedic-astrology-software-on-ubuntu-kbuntu-dark.png new file mode 100644 index 0000000..a9640a0 Binary files /dev/null and b/assets/images/disqus_comments/install-maitreya-vedic-astrology-software-on-ubuntu-kbuntu-dark.png differ diff --git a/assets/images/disqus_comments/install-maitreya-vedic-astrology-software-on-ubuntu-kbuntu-light.png b/assets/images/disqus_comments/install-maitreya-vedic-astrology-software-on-ubuntu-kbuntu-light.png new file mode 100644 index 0000000..bd5b26f Binary files /dev/null and b/assets/images/disqus_comments/install-maitreya-vedic-astrology-software-on-ubuntu-kbuntu-light.png differ diff --git a/assets/images/disqus_comments/jagannath-hora-on-linux-play-on-linux-magic-dark.png b/assets/images/disqus_comments/jagannath-hora-on-linux-play-on-linux-magic-dark.png new file mode 100644 index 0000000..082fc5e Binary files /dev/null and b/assets/images/disqus_comments/jagannath-hora-on-linux-play-on-linux-magic-dark.png differ diff --git a/assets/images/disqus_comments/jagannath-hora-on-linux-play-on-linux-magic-light.png b/assets/images/disqus_comments/jagannath-hora-on-linux-play-on-linux-magic-light.png new file mode 100644 index 0000000..189a069 Binary files /dev/null and b/assets/images/disqus_comments/jagannath-hora-on-linux-play-on-linux-magic-light.png differ diff --git a/assets/images/disqus_comments/logitech-ex100-wireless-keyboard-and-mouse-not-working-read-on-dark.png b/assets/images/disqus_comments/logitech-ex100-wireless-keyboard-and-mouse-not-working-read-on-dark.png new file mode 100644 index 0000000..94ec4cd Binary files /dev/null and b/assets/images/disqus_comments/logitech-ex100-wireless-keyboard-and-mouse-not-working-read-on-dark.png differ diff --git a/assets/images/disqus_comments/logitech-ex100-wireless-keyboard-and-mouse-not-working-read-on-light.png b/assets/images/disqus_comments/logitech-ex100-wireless-keyboard-and-mouse-not-working-read-on-light.png new file mode 100644 index 0000000..fa74ce0 Binary files /dev/null and b/assets/images/disqus_comments/logitech-ex100-wireless-keyboard-and-mouse-not-working-read-on-light.png differ diff --git a/assets/images/disqus_comments/mysql-function-to-calculate-elapsed-working-time-dark.png b/assets/images/disqus_comments/mysql-function-to-calculate-elapsed-working-time-dark.png new file mode 100644 index 0000000..2e6737a Binary files /dev/null and b/assets/images/disqus_comments/mysql-function-to-calculate-elapsed-working-time-dark.png differ diff --git a/assets/images/disqus_comments/mysql-function-to-calculate-elapsed-working-time-light.png b/assets/images/disqus_comments/mysql-function-to-calculate-elapsed-working-time-light.png new file mode 100644 index 0000000..dbc8ada Binary files /dev/null and b/assets/images/disqus_comments/mysql-function-to-calculate-elapsed-working-time-light.png differ diff --git a/assets/images/disqus_comments/prosody-behind-apache-on-debian-stretch-dark.png b/assets/images/disqus_comments/prosody-behind-apache-on-debian-stretch-dark.png new file mode 100644 index 0000000..27a8bba Binary files /dev/null and b/assets/images/disqus_comments/prosody-behind-apache-on-debian-stretch-dark.png differ diff --git a/assets/images/disqus_comments/prosody-behind-apache-on-debian-stretch-light.png b/assets/images/disqus_comments/prosody-behind-apache-on-debian-stretch-light.png new file mode 100644 index 0000000..d90838d Binary files /dev/null and b/assets/images/disqus_comments/prosody-behind-apache-on-debian-stretch-light.png differ diff --git a/assets/images/disqus_comments/rstudio-server-setup-with-ssl-behind-apache-proxy-server-dark.png b/assets/images/disqus_comments/rstudio-server-setup-with-ssl-behind-apache-proxy-server-dark.png new file mode 100644 index 0000000..0c1da59 Binary files /dev/null and b/assets/images/disqus_comments/rstudio-server-setup-with-ssl-behind-apache-proxy-server-dark.png differ diff --git a/assets/images/disqus_comments/rstudio-server-setup-with-ssl-behind-apache-proxy-server-light.png b/assets/images/disqus_comments/rstudio-server-setup-with-ssl-behind-apache-proxy-server-light.png new file mode 100644 index 0000000..988b6c5 Binary files /dev/null and b/assets/images/disqus_comments/rstudio-server-setup-with-ssl-behind-apache-proxy-server-light.png differ diff --git a/assets/images/disqus_comments/tomcat-on-fedora-behind-nginx-dark.png b/assets/images/disqus_comments/tomcat-on-fedora-behind-nginx-dark.png new file mode 100644 index 0000000..d9e8a5c Binary files /dev/null and b/assets/images/disqus_comments/tomcat-on-fedora-behind-nginx-dark.png differ diff --git a/assets/images/disqus_comments/tomcat-on-fedora-behind-nginx-light.png b/assets/images/disqus_comments/tomcat-on-fedora-behind-nginx-light.png new file mode 100644 index 0000000..b1f0b65 Binary files /dev/null and b/assets/images/disqus_comments/tomcat-on-fedora-behind-nginx-light.png differ diff --git a/assets/images/disqus_comments/visio-alternative-on-linux-business-process-model-and-notation-tool-dark.png b/assets/images/disqus_comments/visio-alternative-on-linux-business-process-model-and-notation-tool-dark.png new file mode 100644 index 0000000..a16f1dd Binary files /dev/null and b/assets/images/disqus_comments/visio-alternative-on-linux-business-process-model-and-notation-tool-dark.png differ diff --git a/assets/images/disqus_comments/visio-alternative-on-linux-business-process-model-and-notation-tool-light.png b/assets/images/disqus_comments/visio-alternative-on-linux-business-process-model-and-notation-tool-light.png new file mode 100644 index 0000000..03ba55f Binary files /dev/null and b/assets/images/disqus_comments/visio-alternative-on-linux-business-process-model-and-notation-tool-light.png differ diff --git a/assets/images/favicon.png b/assets/images/favicon.png new file mode 100644 index 0000000..1cf13b9 Binary files /dev/null and b/assets/images/favicon.png differ diff --git a/assets/images/profile_pix/profile_1.jpg b/assets/images/profile_pix/profile_1.jpg new file mode 100644 index 0000000..e84077b Binary files /dev/null and b/assets/images/profile_pix/profile_1.jpg differ diff --git a/assets/images/social/about/index.png b/assets/images/social/about/index.png new file mode 100644 index 0000000..4ebcd13 Binary files /dev/null and b/assets/images/social/about/index.png differ diff --git a/assets/images/social/archive/2009.png b/assets/images/social/archive/2009.png new file mode 100644 index 0000000..cd6eb26 Binary files /dev/null and b/assets/images/social/archive/2009.png differ diff --git a/assets/images/social/archive/2010.png b/assets/images/social/archive/2010.png new file mode 100644 index 0000000..f758fd6 Binary files /dev/null and b/assets/images/social/archive/2010.png differ diff --git a/assets/images/social/archive/2011.png b/assets/images/social/archive/2011.png new file mode 100644 index 0000000..df3794a Binary files /dev/null and b/assets/images/social/archive/2011.png differ diff --git a/assets/images/social/archive/2011/page/2.png b/assets/images/social/archive/2011/page/2.png new file mode 100644 index 0000000..df3794a Binary files /dev/null and b/assets/images/social/archive/2011/page/2.png differ diff --git a/assets/images/social/archive/2012.png b/assets/images/social/archive/2012.png new file mode 100644 index 0000000..5c6220e Binary files /dev/null and b/assets/images/social/archive/2012.png differ diff --git a/assets/images/social/archive/2013.png b/assets/images/social/archive/2013.png new file mode 100644 index 0000000..e9d7f66 Binary files /dev/null and b/assets/images/social/archive/2013.png differ diff --git a/assets/images/social/archive/2014.png b/assets/images/social/archive/2014.png new file mode 100644 index 0000000..85efcbf Binary files /dev/null and b/assets/images/social/archive/2014.png differ diff --git a/assets/images/social/archive/2016.png b/assets/images/social/archive/2016.png new file mode 100644 index 0000000..2c61b12 Binary files /dev/null and b/assets/images/social/archive/2016.png differ diff --git a/assets/images/social/archive/2016/page/2.png b/assets/images/social/archive/2016/page/2.png new file mode 100644 index 0000000..2c61b12 Binary files /dev/null and b/assets/images/social/archive/2016/page/2.png differ diff --git a/assets/images/social/archive/2017.png b/assets/images/social/archive/2017.png new file mode 100644 index 0000000..710539d Binary files /dev/null and b/assets/images/social/archive/2017.png differ diff --git a/assets/images/social/archive/2018.png b/assets/images/social/archive/2018.png new file mode 100644 index 0000000..2401c14 Binary files /dev/null and b/assets/images/social/archive/2018.png differ diff --git a/assets/images/social/archive/2020.png b/assets/images/social/archive/2020.png new file mode 100644 index 0000000..43a69de Binary files /dev/null and b/assets/images/social/archive/2020.png differ diff --git a/assets/images/social/archive/2021.png b/assets/images/social/archive/2021.png new file mode 100644 index 0000000..a390242 Binary files /dev/null and b/assets/images/social/archive/2021.png differ diff --git a/assets/images/social/archive/2023.png b/assets/images/social/archive/2023.png new file mode 100644 index 0000000..aa6d871 Binary files /dev/null and b/assets/images/social/archive/2023.png differ diff --git a/assets/images/social/archive/2024.png b/assets/images/social/archive/2024.png new file mode 100644 index 0000000..f767bf0 Binary files /dev/null and b/assets/images/social/archive/2024.png differ diff --git a/assets/images/social/category/android.png b/assets/images/social/category/android.png new file mode 100644 index 0000000..af236c2 Binary files /dev/null and b/assets/images/social/category/android.png differ diff --git a/assets/images/social/category/android/page/2.png b/assets/images/social/category/android/page/2.png new file mode 100644 index 0000000..af236c2 Binary files /dev/null and b/assets/images/social/category/android/page/2.png differ diff --git a/assets/images/social/category/blogging.png b/assets/images/social/category/blogging.png new file mode 100644 index 0000000..551f502 Binary files /dev/null and b/assets/images/social/category/blogging.png differ diff --git a/assets/images/social/category/development.png b/assets/images/social/category/development.png new file mode 100644 index 0000000..53167fc Binary files /dev/null and b/assets/images/social/category/development.png differ diff --git a/assets/images/social/category/development/page/2.png b/assets/images/social/category/development/page/2.png new file mode 100644 index 0000000..53167fc Binary files /dev/null and b/assets/images/social/category/development/page/2.png differ diff --git a/assets/images/social/category/git.png b/assets/images/social/category/git.png new file mode 100644 index 0000000..d42d5f4 Binary files /dev/null and b/assets/images/social/category/git.png differ diff --git a/assets/images/social/category/github-hosting.png b/assets/images/social/category/github-hosting.png new file mode 100644 index 0000000..d0bd399 Binary files /dev/null and b/assets/images/social/category/github-hosting.png differ diff --git a/assets/images/social/category/keyboard.png b/assets/images/social/category/keyboard.png new file mode 100644 index 0000000..6b4da2e Binary files /dev/null and b/assets/images/social/category/keyboard.png differ diff --git a/assets/images/social/category/linux.png b/assets/images/social/category/linux.png new file mode 100644 index 0000000..0f6649b Binary files /dev/null and b/assets/images/social/category/linux.png differ diff --git a/assets/images/social/category/linux/page/2.png b/assets/images/social/category/linux/page/2.png new file mode 100644 index 0000000..0f6649b Binary files /dev/null and b/assets/images/social/category/linux/page/2.png differ diff --git a/assets/images/social/category/linux/page/3.png b/assets/images/social/category/linux/page/3.png new file mode 100644 index 0000000..0f6649b Binary files /dev/null and b/assets/images/social/category/linux/page/3.png differ diff --git a/assets/images/social/category/linux/page/4.png b/assets/images/social/category/linux/page/4.png new file mode 100644 index 0000000..0f6649b Binary files /dev/null and b/assets/images/social/category/linux/page/4.png differ diff --git a/assets/images/social/category/linux/page/5.png b/assets/images/social/category/linux/page/5.png new file mode 100644 index 0000000..0f6649b Binary files /dev/null and b/assets/images/social/category/linux/page/5.png differ diff --git a/assets/images/social/category/logseq.png b/assets/images/social/category/logseq.png new file mode 100644 index 0000000..999be88 Binary files /dev/null and b/assets/images/social/category/logseq.png differ diff --git a/assets/images/social/category/network-setup.png b/assets/images/social/category/network-setup.png new file mode 100644 index 0000000..0118f75 Binary files /dev/null and b/assets/images/social/category/network-setup.png differ diff --git a/assets/images/social/category/network-setup/page/2.png b/assets/images/social/category/network-setup/page/2.png new file mode 100644 index 0000000..0118f75 Binary files /dev/null and b/assets/images/social/category/network-setup/page/2.png differ diff --git a/assets/images/social/category/operating-system.png b/assets/images/social/category/operating-system.png new file mode 100644 index 0000000..b1bf5bc Binary files /dev/null and b/assets/images/social/category/operating-system.png differ diff --git a/assets/images/social/category/phone.png b/assets/images/social/category/phone.png new file mode 100644 index 0000000..06d7a12 Binary files /dev/null and b/assets/images/social/category/phone.png differ diff --git a/assets/images/social/category/phone/page/2.png b/assets/images/social/category/phone/page/2.png new file mode 100644 index 0000000..06d7a12 Binary files /dev/null and b/assets/images/social/category/phone/page/2.png differ diff --git a/assets/images/social/category/printer.png b/assets/images/social/category/printer.png new file mode 100644 index 0000000..cfb6e09 Binary files /dev/null and b/assets/images/social/category/printer.png differ diff --git a/assets/images/social/category/project-management.png b/assets/images/social/category/project-management.png new file mode 100644 index 0000000..0b47afb Binary files /dev/null and b/assets/images/social/category/project-management.png differ diff --git a/assets/images/social/category/python.png b/assets/images/social/category/python.png new file mode 100644 index 0000000..671f6e8 Binary files /dev/null and b/assets/images/social/category/python.png differ diff --git a/assets/images/social/category/router.png b/assets/images/social/category/router.png new file mode 100644 index 0000000..1199023 Binary files /dev/null and b/assets/images/social/category/router.png differ diff --git a/assets/images/social/category/server-setup.png b/assets/images/social/category/server-setup.png new file mode 100644 index 0000000..b8912fe Binary files /dev/null and b/assets/images/social/category/server-setup.png differ diff --git a/assets/images/social/category/server-setup/page/2.png b/assets/images/social/category/server-setup/page/2.png new file mode 100644 index 0000000..b8912fe Binary files /dev/null and b/assets/images/social/category/server-setup/page/2.png differ diff --git a/assets/images/social/category/software.png b/assets/images/social/category/software.png new file mode 100644 index 0000000..a07cba8 Binary files /dev/null and b/assets/images/social/category/software.png differ diff --git a/assets/images/social/category/software/page/2.png b/assets/images/social/category/software/page/2.png new file mode 100644 index 0000000..a07cba8 Binary files /dev/null and b/assets/images/social/category/software/page/2.png differ diff --git a/assets/images/social/category/software/page/3.png b/assets/images/social/category/software/page/3.png new file mode 100644 index 0000000..a07cba8 Binary files /dev/null and b/assets/images/social/category/software/page/3.png differ diff --git a/assets/images/social/category/software/page/4.png b/assets/images/social/category/software/page/4.png new file mode 100644 index 0000000..a07cba8 Binary files /dev/null and b/assets/images/social/category/software/page/4.png differ diff --git a/assets/images/social/category/software/page/5.png b/assets/images/social/category/software/page/5.png new file mode 100644 index 0000000..a07cba8 Binary files /dev/null and b/assets/images/social/category/software/page/5.png differ diff --git a/assets/images/social/category/sql.png b/assets/images/social/category/sql.png new file mode 100644 index 0000000..fd8f08e Binary files /dev/null and b/assets/images/social/category/sql.png differ diff --git a/assets/images/social/category/troubleshooting.png b/assets/images/social/category/troubleshooting.png new file mode 100644 index 0000000..da2364b Binary files /dev/null and b/assets/images/social/category/troubleshooting.png differ diff --git a/assets/images/social/category/troubleshooting/page/2.png b/assets/images/social/category/troubleshooting/page/2.png new file mode 100644 index 0000000..da2364b Binary files /dev/null and b/assets/images/social/category/troubleshooting/page/2.png differ diff --git a/assets/images/social/category/troubleshooting/page/3.png b/assets/images/social/category/troubleshooting/page/3.png new file mode 100644 index 0000000..da2364b Binary files /dev/null and b/assets/images/social/category/troubleshooting/page/3.png differ diff --git a/assets/images/social/index.png b/assets/images/social/index.png new file mode 100644 index 0000000..01eac05 Binary files /dev/null and b/assets/images/social/index.png differ diff --git a/assets/images/social/page/2/index.png b/assets/images/social/page/2/index.png new file mode 100644 index 0000000..01eac05 Binary files /dev/null and b/assets/images/social/page/2/index.png differ diff --git a/assets/images/social/page/3/index.png b/assets/images/social/page/3/index.png new file mode 100644 index 0000000..01eac05 Binary files /dev/null and b/assets/images/social/page/3/index.png differ diff --git a/assets/images/social/page/4/index.png b/assets/images/social/page/4/index.png new file mode 100644 index 0000000..01eac05 Binary files /dev/null and b/assets/images/social/page/4/index.png differ diff --git a/assets/images/social/page/5/index.png b/assets/images/social/page/5/index.png new file mode 100644 index 0000000..01eac05 Binary files /dev/null and b/assets/images/social/page/5/index.png differ diff --git a/assets/images/social/page/6/index.png b/assets/images/social/page/6/index.png new file mode 100644 index 0000000..01eac05 Binary files /dev/null and b/assets/images/social/page/6/index.png differ diff --git a/assets/images/social/page/7/index.png b/assets/images/social/page/7/index.png new file mode 100644 index 0000000..01eac05 Binary files /dev/null and b/assets/images/social/page/7/index.png differ diff --git a/assets/images/social/posts/2009-08-11-aao-a150l-step-by-step-installation-process-for-linux4one.png b/assets/images/social/posts/2009-08-11-aao-a150l-step-by-step-installation-process-for-linux4one.png new file mode 100644 index 0000000..f738487 Binary files /dev/null and b/assets/images/social/posts/2009-08-11-aao-a150l-step-by-step-installation-process-for-linux4one.png differ diff --git a/assets/images/social/posts/2009-08-13-get-gmail-as-push-email-on-sony-p990i.png b/assets/images/social/posts/2009-08-13-get-gmail-as-push-email-on-sony-p990i.png new file mode 100644 index 0000000..85a1ec0 Binary files /dev/null and b/assets/images/social/posts/2009-08-13-get-gmail-as-push-email-on-sony-p990i.png differ diff --git a/assets/images/social/posts/2009-09-15-sony-vaio-fe21h-webcam-on-skype.png b/assets/images/social/posts/2009-09-15-sony-vaio-fe21h-webcam-on-skype.png new file mode 100644 index 0000000..d046591 Binary files /dev/null and b/assets/images/social/posts/2009-09-15-sony-vaio-fe21h-webcam-on-skype.png differ diff --git a/assets/images/social/posts/2009-09-16-sony-vaio-n-vidia-setup-to-make-s-video-work.png b/assets/images/social/posts/2009-09-16-sony-vaio-n-vidia-setup-to-make-s-video-work.png new file mode 100644 index 0000000..5732757 Binary files /dev/null and b/assets/images/social/posts/2009-09-16-sony-vaio-n-vidia-setup-to-make-s-video-work.png differ diff --git a/assets/images/social/posts/2009-10-07-o2-xda-serra-official-htc-upgrade-with-tf3d-of-touch-pro-2.png b/assets/images/social/posts/2009-10-07-o2-xda-serra-official-htc-upgrade-with-tf3d-of-touch-pro-2.png new file mode 100644 index 0000000..31f0002 Binary files /dev/null and b/assets/images/social/posts/2009-10-07-o2-xda-serra-official-htc-upgrade-with-tf3d-of-touch-pro-2.png differ diff --git a/assets/images/social/posts/2010-03-07-kubuntu-blog-entry.png b/assets/images/social/posts/2010-03-07-kubuntu-blog-entry.png new file mode 100644 index 0000000..1e63070 Binary files /dev/null and b/assets/images/social/posts/2010-03-07-kubuntu-blog-entry.png differ diff --git a/assets/images/social/posts/2010-03-15-configure-blogtk-1-0-for-blogger.png b/assets/images/social/posts/2010-03-15-configure-blogtk-1-0-for-blogger.png new file mode 100644 index 0000000..0c0a432 Binary files /dev/null and b/assets/images/social/posts/2010-03-15-configure-blogtk-1-0-for-blogger.png differ diff --git a/assets/images/social/posts/2010-03-19-audio-problem-in-wine-under-kde-4-4-1-solved.png b/assets/images/social/posts/2010-03-19-audio-problem-in-wine-under-kde-4-4-1-solved.png new file mode 100644 index 0000000..cc9f36d Binary files /dev/null and b/assets/images/social/posts/2010-03-19-audio-problem-in-wine-under-kde-4-4-1-solved.png differ diff --git a/assets/images/social/posts/2010-03-22-install-maitreya-vedic-astrology-software-on-ubuntu-kbuntu.png b/assets/images/social/posts/2010-03-22-install-maitreya-vedic-astrology-software-on-ubuntu-kbuntu.png new file mode 100644 index 0000000..f2701e2 Binary files /dev/null and b/assets/images/social/posts/2010-03-22-install-maitreya-vedic-astrology-software-on-ubuntu-kbuntu.png differ diff --git a/assets/images/social/posts/2010-05-03-jagannath-hora-on-linux-play-on-linux-magic.png b/assets/images/social/posts/2010-05-03-jagannath-hora-on-linux-play-on-linux-magic.png new file mode 100644 index 0000000..c6bf7c5 Binary files /dev/null and b/assets/images/social/posts/2010-05-03-jagannath-hora-on-linux-play-on-linux-magic.png differ diff --git a/assets/images/social/posts/2010-11-13-part-1-configure-epson-s515w-on-linux-mint-ubuntu-10-04.png b/assets/images/social/posts/2010-11-13-part-1-configure-epson-s515w-on-linux-mint-ubuntu-10-04.png new file mode 100644 index 0000000..4bb2174 Binary files /dev/null and b/assets/images/social/posts/2010-11-13-part-1-configure-epson-s515w-on-linux-mint-ubuntu-10-04.png differ diff --git a/assets/images/social/posts/2011-02-19-call-us-and-several-other-countries-free-using-android.png b/assets/images/social/posts/2011-02-19-call-us-and-several-other-countries-free-using-android.png new file mode 100644 index 0000000..5033eef Binary files /dev/null and b/assets/images/social/posts/2011-02-19-call-us-and-several-other-countries-free-using-android.png differ diff --git a/assets/images/social/posts/2011-02-20-google-voice-sip2sip-ikall-free-international-calls-to-known-contacts.png b/assets/images/social/posts/2011-02-20-google-voice-sip2sip-ikall-free-international-calls-to-known-contacts.png new file mode 100644 index 0000000..6bc8101 Binary files /dev/null and b/assets/images/social/posts/2011-02-20-google-voice-sip2sip-ikall-free-international-calls-to-known-contacts.png differ diff --git a/assets/images/social/posts/2011-04-17-time-for-a-bit-of-show-off.png b/assets/images/social/posts/2011-04-17-time-for-a-bit-of-show-off.png new file mode 100644 index 0000000..a409de2 Binary files /dev/null and b/assets/images/social/posts/2011-04-17-time-for-a-bit-of-show-off.png differ diff --git a/assets/images/social/posts/2011-04-24-logitech-ex100-wireless-keyboard-and-mouse-not-working-read-on.png b/assets/images/social/posts/2011-04-24-logitech-ex100-wireless-keyboard-and-mouse-not-working-read-on.png new file mode 100644 index 0000000..a235dae Binary files /dev/null and b/assets/images/social/posts/2011-04-24-logitech-ex100-wireless-keyboard-and-mouse-not-working-read-on.png differ diff --git a/assets/images/social/posts/2011-04-29-mmc-convert-media-files-on-linux.png b/assets/images/social/posts/2011-04-29-mmc-convert-media-files-on-linux.png new file mode 100644 index 0000000..e79b32c Binary files /dev/null and b/assets/images/social/posts/2011-04-29-mmc-convert-media-files-on-linux.png differ diff --git a/assets/images/social/posts/2011-05-02-glympse.png b/assets/images/social/posts/2011-05-02-glympse.png new file mode 100644 index 0000000..93a6689 Binary files /dev/null and b/assets/images/social/posts/2011-05-02-glympse.png differ diff --git a/assets/images/social/posts/2011-05-04-part-2-configure-epson-s515w-on-linux-mint-ubuntu-10-04.png b/assets/images/social/posts/2011-05-04-part-2-configure-epson-s515w-on-linux-mint-ubuntu-10-04.png new file mode 100644 index 0000000..fe1d918 Binary files /dev/null and b/assets/images/social/posts/2011-05-04-part-2-configure-epson-s515w-on-linux-mint-ubuntu-10-04.png differ diff --git a/assets/images/social/posts/2011-05-07-how-to-edit-pdf-in-linux-the-easy-way.png b/assets/images/social/posts/2011-05-07-how-to-edit-pdf-in-linux-the-easy-way.png new file mode 100644 index 0000000..9d91aa5 Binary files /dev/null and b/assets/images/social/posts/2011-05-07-how-to-edit-pdf-in-linux-the-easy-way.png differ diff --git a/assets/images/social/posts/2011-06-04-install-open-workbench-and-jre-on-wine-in-linux-mint.png b/assets/images/social/posts/2011-06-04-install-open-workbench-and-jre-on-wine-in-linux-mint.png new file mode 100644 index 0000000..3990544 Binary files /dev/null and b/assets/images/social/posts/2011-06-04-install-open-workbench-and-jre-on-wine-in-linux-mint.png differ diff --git a/assets/images/social/posts/2011-06-20-visio-alternative-on-linux-business-process-model-and-notation-tool.png b/assets/images/social/posts/2011-06-20-visio-alternative-on-linux-business-process-model-and-notation-tool.png new file mode 100644 index 0000000..7b09f49 Binary files /dev/null and b/assets/images/social/posts/2011-06-20-visio-alternative-on-linux-business-process-model-and-notation-tool.png differ diff --git a/assets/images/social/posts/2011-11-04-access-files-folders-directories-from-linux-mint-on-android.png b/assets/images/social/posts/2011-11-04-access-files-folders-directories-from-linux-mint-on-android.png new file mode 100644 index 0000000..e7cee9d Binary files /dev/null and b/assets/images/social/posts/2011-11-04-access-files-folders-directories-from-linux-mint-on-android.png differ diff --git a/assets/images/social/posts/2011-11-06-openvpn-on-linux-mint-to-access-us.png b/assets/images/social/posts/2011-11-06-openvpn-on-linux-mint-to-access-us.png new file mode 100644 index 0000000..30436d1 Binary files /dev/null and b/assets/images/social/posts/2011-11-06-openvpn-on-linux-mint-to-access-us.png differ diff --git a/assets/images/social/posts/2012-02-20-how-to-boot-from-usb-when-bios-does-not-have-the-option.png b/assets/images/social/posts/2012-02-20-how-to-boot-from-usb-when-bios-does-not-have-the-option.png new file mode 100644 index 0000000..56a3316 Binary files /dev/null and b/assets/images/social/posts/2012-02-20-how-to-boot-from-usb-when-bios-does-not-have-the-option.png differ diff --git a/assets/images/social/posts/2012-03-04-stagevu-divx-video-on-bodhi-linux.png b/assets/images/social/posts/2012-03-04-stagevu-divx-video-on-bodhi-linux.png new file mode 100644 index 0000000..cb99580 Binary files /dev/null and b/assets/images/social/posts/2012-03-04-stagevu-divx-video-on-bodhi-linux.png differ diff --git a/assets/images/social/posts/2012-03-16-exchange-2007-on-thunderbird-using-davmail.png b/assets/images/social/posts/2012-03-16-exchange-2007-on-thunderbird-using-davmail.png new file mode 100644 index 0000000..6504358 Binary files /dev/null and b/assets/images/social/posts/2012-03-16-exchange-2007-on-thunderbird-using-davmail.png differ diff --git a/assets/images/social/posts/2012-08-06-top-10-android-apps-that-i-use.png b/assets/images/social/posts/2012-08-06-top-10-android-apps-that-i-use.png new file mode 100644 index 0000000..1f83036 Binary files /dev/null and b/assets/images/social/posts/2012-08-06-top-10-android-apps-that-i-use.png differ diff --git a/assets/images/social/posts/2012-10-15-flash-samsung-galaxy-s-gt-i9000-with-cyanogenmod-9-on-linux.png b/assets/images/social/posts/2012-10-15-flash-samsung-galaxy-s-gt-i9000-with-cyanogenmod-9-on-linux.png new file mode 100644 index 0000000..64b4d4e Binary files /dev/null and b/assets/images/social/posts/2012-10-15-flash-samsung-galaxy-s-gt-i9000-with-cyanogenmod-9-on-linux.png differ diff --git a/assets/images/social/posts/2012-10-17-prepare-linux-mint-13-for-android-development.png b/assets/images/social/posts/2012-10-17-prepare-linux-mint-13-for-android-development.png new file mode 100644 index 0000000..24ef0ce Binary files /dev/null and b/assets/images/social/posts/2012-10-17-prepare-linux-mint-13-for-android-development.png differ diff --git a/assets/images/social/posts/2013-01-01-root-nexus-4-on-linux-mint-13-and-access-all-files-on-computer.png b/assets/images/social/posts/2013-01-01-root-nexus-4-on-linux-mint-13-and-access-all-files-on-computer.png new file mode 100644 index 0000000..00a1757 Binary files /dev/null and b/assets/images/social/posts/2013-01-01-root-nexus-4-on-linux-mint-13-and-access-all-files-on-computer.png differ diff --git a/assets/images/social/posts/2013-01-18-conky-on-my-desktop-step-by-step.png b/assets/images/social/posts/2013-01-18-conky-on-my-desktop-step-by-step.png new file mode 100644 index 0000000..bec2453 Binary files /dev/null and b/assets/images/social/posts/2013-01-18-conky-on-my-desktop-step-by-step.png differ diff --git a/assets/images/social/posts/2013-03-01-python-eric-rad-ide-and-qt-designer.png b/assets/images/social/posts/2013-03-01-python-eric-rad-ide-and-qt-designer.png new file mode 100644 index 0000000..868351d Binary files /dev/null and b/assets/images/social/posts/2013-03-01-python-eric-rad-ide-and-qt-designer.png differ diff --git a/assets/images/social/posts/2013-03-17-linux-mint-on-android-through-vnc-and-jump.png b/assets/images/social/posts/2013-03-17-linux-mint-on-android-through-vnc-and-jump.png new file mode 100644 index 0000000..6dd6df5 Binary files /dev/null and b/assets/images/social/posts/2013-03-17-linux-mint-on-android-through-vnc-and-jump.png differ diff --git a/assets/images/social/posts/2014-02-19-arch-linux-nearly-a-year-on.png b/assets/images/social/posts/2014-02-19-arch-linux-nearly-a-year-on.png new file mode 100644 index 0000000..2427580 Binary files /dev/null and b/assets/images/social/posts/2014-02-19-arch-linux-nearly-a-year-on.png differ diff --git a/assets/images/social/posts/2016-02-18-mysql-stored-procedure-to-return-json-for-google-charts.png b/assets/images/social/posts/2016-02-18-mysql-stored-procedure-to-return-json-for-google-charts.png new file mode 100644 index 0000000..a39322e Binary files /dev/null and b/assets/images/social/posts/2016-02-18-mysql-stored-procedure-to-return-json-for-google-charts.png differ diff --git a/assets/images/social/posts/2016-06-06-crossover.png b/assets/images/social/posts/2016-06-06-crossover.png new file mode 100644 index 0000000..bb39305 Binary files /dev/null and b/assets/images/social/posts/2016-06-06-crossover.png differ diff --git a/assets/images/social/posts/2016-06-27-seafile-server-behind-nginx-on-fedora-24-security-lab-spin.png b/assets/images/social/posts/2016-06-27-seafile-server-behind-nginx-on-fedora-24-security-lab-spin.png new file mode 100644 index 0000000..284d23b Binary files /dev/null and b/assets/images/social/posts/2016-06-27-seafile-server-behind-nginx-on-fedora-24-security-lab-spin.png differ diff --git a/assets/images/social/posts/2016-07-01-ghost-on-fedora-24.png b/assets/images/social/posts/2016-07-01-ghost-on-fedora-24.png new file mode 100644 index 0000000..50588f1 Binary files /dev/null and b/assets/images/social/posts/2016-07-01-ghost-on-fedora-24.png differ diff --git a/assets/images/social/posts/2016-07-17-the-complete-walkthrough-of-my-blogger-to-ghost-migration.png b/assets/images/social/posts/2016-07-17-the-complete-walkthrough-of-my-blogger-to-ghost-migration.png new file mode 100644 index 0000000..ff2d460 Binary files /dev/null and b/assets/images/social/posts/2016-07-17-the-complete-walkthrough-of-my-blogger-to-ghost-migration.png differ diff --git a/assets/images/social/posts/2016-07-19-mysql-function-to-calculate-elapsed-working-time.png b/assets/images/social/posts/2016-07-19-mysql-function-to-calculate-elapsed-working-time.png new file mode 100644 index 0000000..83683e6 Binary files /dev/null and b/assets/images/social/posts/2016-07-19-mysql-function-to-calculate-elapsed-working-time.png differ diff --git a/assets/images/social/posts/2016-07-20-ddclient-on-fedora-2.png b/assets/images/social/posts/2016-07-20-ddclient-on-fedora-2.png new file mode 100644 index 0000000..c5ebc32 Binary files /dev/null and b/assets/images/social/posts/2016-07-20-ddclient-on-fedora-2.png differ diff --git a/assets/images/social/posts/2016-07-25-tomcat-on-fedora-behind-nginx.png b/assets/images/social/posts/2016-07-25-tomcat-on-fedora-behind-nginx.png new file mode 100644 index 0000000..83dbf5a Binary files /dev/null and b/assets/images/social/posts/2016-07-25-tomcat-on-fedora-behind-nginx.png differ diff --git a/assets/images/social/posts/2016-07-28-update-ghost-on-fedora.png b/assets/images/social/posts/2016-07-28-update-ghost-on-fedora.png new file mode 100644 index 0000000..b5c885b Binary files /dev/null and b/assets/images/social/posts/2016-07-28-update-ghost-on-fedora.png differ diff --git a/assets/images/social/posts/2016-07-31-fix-for-php-issues-after-upgrade-to-ubuntu-16-04-1-xenial.png b/assets/images/social/posts/2016-07-31-fix-for-php-issues-after-upgrade-to-ubuntu-16-04-1-xenial.png new file mode 100644 index 0000000..ea3137f Binary files /dev/null and b/assets/images/social/posts/2016-07-31-fix-for-php-issues-after-upgrade-to-ubuntu-16-04-1-xenial.png differ diff --git a/assets/images/social/posts/2016-08-12-ethercalc.png b/assets/images/social/posts/2016-08-12-ethercalc.png new file mode 100644 index 0000000..c183cb0 Binary files /dev/null and b/assets/images/social/posts/2016-08-12-ethercalc.png differ diff --git a/assets/images/social/posts/2016-09-05-the-pain-of-using-windows.png b/assets/images/social/posts/2016-09-05-the-pain-of-using-windows.png new file mode 100644 index 0000000..b1272e2 Binary files /dev/null and b/assets/images/social/posts/2016-09-05-the-pain-of-using-windows.png differ diff --git a/assets/images/social/posts/2016-10-04-grav-cms-with-a-difference.png b/assets/images/social/posts/2016-10-04-grav-cms-with-a-difference.png new file mode 100644 index 0000000..397cc48 Binary files /dev/null and b/assets/images/social/posts/2016-10-04-grav-cms-with-a-difference.png differ diff --git a/assets/images/social/posts/2016-11-23-note-7-to-oneplus-3.png b/assets/images/social/posts/2016-11-23-note-7-to-oneplus-3.png new file mode 100644 index 0000000..4ae0215 Binary files /dev/null and b/assets/images/social/posts/2016-11-23-note-7-to-oneplus-3.png differ diff --git a/assets/images/social/posts/2016-12-14-swap-file-to-create-extra-memory.png b/assets/images/social/posts/2016-12-14-swap-file-to-create-extra-memory.png new file mode 100644 index 0000000..25decfc Binary files /dev/null and b/assets/images/social/posts/2016-12-14-swap-file-to-create-extra-memory.png differ diff --git a/assets/images/social/posts/2017-02-02-dd-wrt-firmware-on-tp-link-tl-wr841n-v11.png b/assets/images/social/posts/2017-02-02-dd-wrt-firmware-on-tp-link-tl-wr841n-v11.png new file mode 100644 index 0000000..c62fcc3 Binary files /dev/null and b/assets/images/social/posts/2017-02-02-dd-wrt-firmware-on-tp-link-tl-wr841n-v11.png differ diff --git a/assets/images/social/posts/2017-02-13-markdown-and-gantt-charts.png b/assets/images/social/posts/2017-02-13-markdown-and-gantt-charts.png new file mode 100644 index 0000000..16810a8 Binary files /dev/null and b/assets/images/social/posts/2017-02-13-markdown-and-gantt-charts.png differ diff --git a/assets/images/social/posts/2017-03-03-rstudio-server-setup-with-ssl-behind-apache-proxy-server.png b/assets/images/social/posts/2017-03-03-rstudio-server-setup-with-ssl-behind-apache-proxy-server.png new file mode 100644 index 0000000..a1335fc Binary files /dev/null and b/assets/images/social/posts/2017-03-03-rstudio-server-setup-with-ssl-behind-apache-proxy-server.png differ diff --git a/assets/images/social/posts/2017-10-05-ghost-v1-0-upgrade-related-quirks-and-fixes.png b/assets/images/social/posts/2017-10-05-ghost-v1-0-upgrade-related-quirks-and-fixes.png new file mode 100644 index 0000000..e03133f Binary files /dev/null and b/assets/images/social/posts/2017-10-05-ghost-v1-0-upgrade-related-quirks-and-fixes.png differ diff --git a/assets/images/social/posts/2017-11-19-unprotect-sheets-in-libre-calc-excel.png b/assets/images/social/posts/2017-11-19-unprotect-sheets-in-libre-calc-excel.png new file mode 100644 index 0000000..d962b10 Binary files /dev/null and b/assets/images/social/posts/2017-11-19-unprotect-sheets-in-libre-calc-excel.png differ diff --git a/assets/images/social/posts/2018-01-04-ghost-upgrade-errors-and-fixes-1-19-x.png b/assets/images/social/posts/2018-01-04-ghost-upgrade-errors-and-fixes-1-19-x.png new file mode 100644 index 0000000..9f3c1e6 Binary files /dev/null and b/assets/images/social/posts/2018-01-04-ghost-upgrade-errors-and-fixes-1-19-x.png differ diff --git a/assets/images/social/posts/2018-02-20-mysql-function-to-calculate-closing-workday-date-given-elapsed-working-time.png b/assets/images/social/posts/2018-02-20-mysql-function-to-calculate-closing-workday-date-given-elapsed-working-time.png new file mode 100644 index 0000000..a8fcbdb Binary files /dev/null and b/assets/images/social/posts/2018-02-20-mysql-function-to-calculate-closing-workday-date-given-elapsed-working-time.png differ diff --git a/assets/images/social/posts/2018-05-14-home-networking.png b/assets/images/social/posts/2018-05-14-home-networking.png new file mode 100644 index 0000000..4eb6e6e Binary files /dev/null and b/assets/images/social/posts/2018-05-14-home-networking.png differ diff --git a/assets/images/social/posts/2018-06-15-prosody-behind-apache-on-debian-stretch.png b/assets/images/social/posts/2018-06-15-prosody-behind-apache-on-debian-stretch.png new file mode 100644 index 0000000..6c926f2 Binary files /dev/null and b/assets/images/social/posts/2018-06-15-prosody-behind-apache-on-debian-stretch.png differ diff --git a/assets/images/social/posts/2018-12-05-metabase-a-bi-solution-that-just-works.png b/assets/images/social/posts/2018-12-05-metabase-a-bi-solution-that-just-works.png new file mode 100644 index 0000000..ebda4b1 Binary files /dev/null and b/assets/images/social/posts/2018-12-05-metabase-a-bi-solution-that-just-works.png differ diff --git a/assets/images/social/posts/2020-05-19-converting-aax-audible-to-mp3.png b/assets/images/social/posts/2020-05-19-converting-aax-audible-to-mp3.png new file mode 100644 index 0000000..279a58c Binary files /dev/null and b/assets/images/social/posts/2020-05-19-converting-aax-audible-to-mp3.png differ diff --git a/assets/images/social/posts/2021-02-10-upgrading-php-version-on-linux-for-apache.png b/assets/images/social/posts/2021-02-10-upgrading-php-version-on-linux-for-apache.png new file mode 100644 index 0000000..2106498 Binary files /dev/null and b/assets/images/social/posts/2021-02-10-upgrading-php-version-on-linux-for-apache.png differ diff --git a/assets/images/social/posts/2021-03-05-tech-for-diabetics.png b/assets/images/social/posts/2021-03-05-tech-for-diabetics.png new file mode 100644 index 0000000..41946d3 Binary files /dev/null and b/assets/images/social/posts/2021-03-05-tech-for-diabetics.png differ diff --git a/assets/images/social/posts/2023-01-25-jupyterlite- on-github-enterprise-with-panel-enabled.png b/assets/images/social/posts/2023-01-25-jupyterlite- on-github-enterprise-with-panel-enabled.png new file mode 100644 index 0000000..17da8c6 Binary files /dev/null and b/assets/images/social/posts/2023-01-25-jupyterlite- on-github-enterprise-with-panel-enabled.png differ diff --git a/assets/images/social/posts/2023-02-26-git-basics.png b/assets/images/social/posts/2023-02-26-git-basics.png new file mode 100644 index 0000000..92113f4 Binary files /dev/null and b/assets/images/social/posts/2023-02-26-git-basics.png differ diff --git a/assets/images/social/posts/2023-02-28-git-jargon.png b/assets/images/social/posts/2023-02-28-git-jargon.png new file mode 100644 index 0000000..e02e565 Binary files /dev/null and b/assets/images/social/posts/2023-02-28-git-jargon.png differ diff --git a/assets/images/social/posts/2023-03-29-read-excel-csv-recursive.png b/assets/images/social/posts/2023-03-29-read-excel-csv-recursive.png new file mode 100644 index 0000000..be5e998 Binary files /dev/null and b/assets/images/social/posts/2023-03-29-read-excel-csv-recursive.png differ diff --git a/assets/images/social/posts/2024-08-13-logseq-customisation-for-project-management-teamplate.png b/assets/images/social/posts/2024-08-13-logseq-customisation-for-project-management-teamplate.png new file mode 100644 index 0000000..e34f90d Binary files /dev/null and b/assets/images/social/posts/2024-08-13-logseq-customisation-for-project-management-teamplate.png differ diff --git a/assets/images/social/posts/2024-12-10-bluesky-for-comments-on-mkdocs-blog.png b/assets/images/social/posts/2024-12-10-bluesky-for-comments-on-mkdocs-blog.png new file mode 100644 index 0000000..b4f6f49 Binary files /dev/null and b/assets/images/social/posts/2024-12-10-bluesky-for-comments-on-mkdocs-blog.png differ diff --git a/assets/javascripts/bundle.83f73b43.min.js b/assets/javascripts/bundle.83f73b43.min.js new file mode 100644 index 0000000..43d8b70 --- /dev/null +++ b/assets/javascripts/bundle.83f73b43.min.js @@ -0,0 +1,16 @@ +"use strict";(()=>{var Wi=Object.create;var gr=Object.defineProperty;var Di=Object.getOwnPropertyDescriptor;var Vi=Object.getOwnPropertyNames,Vt=Object.getOwnPropertySymbols,Ni=Object.getPrototypeOf,yr=Object.prototype.hasOwnProperty,ao=Object.prototype.propertyIsEnumerable;var io=(e,t,r)=>t in e?gr(e,t,{enumerable:!0,configurable:!0,writable:!0,value:r}):e[t]=r,$=(e,t)=>{for(var r in t||(t={}))yr.call(t,r)&&io(e,r,t[r]);if(Vt)for(var r of Vt(t))ao.call(t,r)&&io(e,r,t[r]);return e};var so=(e,t)=>{var r={};for(var o in e)yr.call(e,o)&&t.indexOf(o)<0&&(r[o]=e[o]);if(e!=null&&Vt)for(var o of Vt(e))t.indexOf(o)<0&&ao.call(e,o)&&(r[o]=e[o]);return r};var xr=(e,t)=>()=>(t||e((t={exports:{}}).exports,t),t.exports);var zi=(e,t,r,o)=>{if(t&&typeof t=="object"||typeof t=="function")for(let n of Vi(t))!yr.call(e,n)&&n!==r&&gr(e,n,{get:()=>t[n],enumerable:!(o=Di(t,n))||o.enumerable});return e};var Mt=(e,t,r)=>(r=e!=null?Wi(Ni(e)):{},zi(t||!e||!e.__esModule?gr(r,"default",{value:e,enumerable:!0}):r,e));var co=(e,t,r)=>new Promise((o,n)=>{var i=p=>{try{s(r.next(p))}catch(c){n(c)}},a=p=>{try{s(r.throw(p))}catch(c){n(c)}},s=p=>p.done?o(p.value):Promise.resolve(p.value).then(i,a);s((r=r.apply(e,t)).next())});var lo=xr((Er,po)=>{(function(e,t){typeof Er=="object"&&typeof po!="undefined"?t():typeof define=="function"&&define.amd?define(t):t()})(Er,function(){"use strict";function e(r){var o=!0,n=!1,i=null,a={text:!0,search:!0,url:!0,tel:!0,email:!0,password:!0,number:!0,date:!0,month:!0,week:!0,time:!0,datetime:!0,"datetime-local":!0};function s(k){return!!(k&&k!==document&&k.nodeName!=="HTML"&&k.nodeName!=="BODY"&&"classList"in k&&"contains"in k.classList)}function p(k){var ft=k.type,qe=k.tagName;return!!(qe==="INPUT"&&a[ft]&&!k.readOnly||qe==="TEXTAREA"&&!k.readOnly||k.isContentEditable)}function c(k){k.classList.contains("focus-visible")||(k.classList.add("focus-visible"),k.setAttribute("data-focus-visible-added",""))}function l(k){k.hasAttribute("data-focus-visible-added")&&(k.classList.remove("focus-visible"),k.removeAttribute("data-focus-visible-added"))}function f(k){k.metaKey||k.altKey||k.ctrlKey||(s(r.activeElement)&&c(r.activeElement),o=!0)}function u(k){o=!1}function d(k){s(k.target)&&(o||p(k.target))&&c(k.target)}function y(k){s(k.target)&&(k.target.classList.contains("focus-visible")||k.target.hasAttribute("data-focus-visible-added"))&&(n=!0,window.clearTimeout(i),i=window.setTimeout(function(){n=!1},100),l(k.target))}function L(k){document.visibilityState==="hidden"&&(n&&(o=!0),X())}function X(){document.addEventListener("mousemove",J),document.addEventListener("mousedown",J),document.addEventListener("mouseup",J),document.addEventListener("pointermove",J),document.addEventListener("pointerdown",J),document.addEventListener("pointerup",J),document.addEventListener("touchmove",J),document.addEventListener("touchstart",J),document.addEventListener("touchend",J)}function te(){document.removeEventListener("mousemove",J),document.removeEventListener("mousedown",J),document.removeEventListener("mouseup",J),document.removeEventListener("pointermove",J),document.removeEventListener("pointerdown",J),document.removeEventListener("pointerup",J),document.removeEventListener("touchmove",J),document.removeEventListener("touchstart",J),document.removeEventListener("touchend",J)}function J(k){k.target.nodeName&&k.target.nodeName.toLowerCase()==="html"||(o=!1,te())}document.addEventListener("keydown",f,!0),document.addEventListener("mousedown",u,!0),document.addEventListener("pointerdown",u,!0),document.addEventListener("touchstart",u,!0),document.addEventListener("visibilitychange",L,!0),X(),r.addEventListener("focus",d,!0),r.addEventListener("blur",y,!0),r.nodeType===Node.DOCUMENT_FRAGMENT_NODE&&r.host?r.host.setAttribute("data-js-focus-visible",""):r.nodeType===Node.DOCUMENT_NODE&&(document.documentElement.classList.add("js-focus-visible"),document.documentElement.setAttribute("data-js-focus-visible",""))}if(typeof window!="undefined"&&typeof document!="undefined"){window.applyFocusVisiblePolyfill=e;var t;try{t=new CustomEvent("focus-visible-polyfill-ready")}catch(r){t=document.createEvent("CustomEvent"),t.initCustomEvent("focus-visible-polyfill-ready",!1,!1,{})}window.dispatchEvent(t)}typeof document!="undefined"&&e(document)})});var qr=xr((hy,On)=>{"use strict";/*! + * escape-html + * Copyright(c) 2012-2013 TJ Holowaychuk + * Copyright(c) 2015 Andreas Lubbe + * Copyright(c) 2015 Tiancheng "Timothy" Gu + * MIT Licensed + */var $a=/["'&<>]/;On.exports=Pa;function Pa(e){var t=""+e,r=$a.exec(t);if(!r)return t;var o,n="",i=0,a=0;for(i=r.index;i{/*! + * clipboard.js v2.0.11 + * https://clipboardjs.com/ + * + * Licensed MIT © Zeno Rocha + */(function(t,r){typeof It=="object"&&typeof Yr=="object"?Yr.exports=r():typeof define=="function"&&define.amd?define([],r):typeof It=="object"?It.ClipboardJS=r():t.ClipboardJS=r()})(It,function(){return function(){var e={686:function(o,n,i){"use strict";i.d(n,{default:function(){return Ui}});var a=i(279),s=i.n(a),p=i(370),c=i.n(p),l=i(817),f=i.n(l);function u(V){try{return document.execCommand(V)}catch(A){return!1}}var d=function(A){var M=f()(A);return u("cut"),M},y=d;function L(V){var A=document.documentElement.getAttribute("dir")==="rtl",M=document.createElement("textarea");M.style.fontSize="12pt",M.style.border="0",M.style.padding="0",M.style.margin="0",M.style.position="absolute",M.style[A?"right":"left"]="-9999px";var F=window.pageYOffset||document.documentElement.scrollTop;return M.style.top="".concat(F,"px"),M.setAttribute("readonly",""),M.value=V,M}var X=function(A,M){var F=L(A);M.container.appendChild(F);var D=f()(F);return u("copy"),F.remove(),D},te=function(A){var M=arguments.length>1&&arguments[1]!==void 0?arguments[1]:{container:document.body},F="";return typeof A=="string"?F=X(A,M):A instanceof HTMLInputElement&&!["text","search","url","tel","password"].includes(A==null?void 0:A.type)?F=X(A.value,M):(F=f()(A),u("copy")),F},J=te;function k(V){"@babel/helpers - typeof";return typeof Symbol=="function"&&typeof Symbol.iterator=="symbol"?k=function(M){return typeof M}:k=function(M){return M&&typeof Symbol=="function"&&M.constructor===Symbol&&M!==Symbol.prototype?"symbol":typeof M},k(V)}var ft=function(){var A=arguments.length>0&&arguments[0]!==void 0?arguments[0]:{},M=A.action,F=M===void 0?"copy":M,D=A.container,Y=A.target,$e=A.text;if(F!=="copy"&&F!=="cut")throw new Error('Invalid "action" value, use either "copy" or "cut"');if(Y!==void 0)if(Y&&k(Y)==="object"&&Y.nodeType===1){if(F==="copy"&&Y.hasAttribute("disabled"))throw new Error('Invalid "target" attribute. Please use "readonly" instead of "disabled" attribute');if(F==="cut"&&(Y.hasAttribute("readonly")||Y.hasAttribute("disabled")))throw new Error(`Invalid "target" attribute. You can't cut text from elements with "readonly" or "disabled" attributes`)}else throw new Error('Invalid "target" value, use a valid Element');if($e)return J($e,{container:D});if(Y)return F==="cut"?y(Y):J(Y,{container:D})},qe=ft;function Fe(V){"@babel/helpers - typeof";return typeof Symbol=="function"&&typeof Symbol.iterator=="symbol"?Fe=function(M){return typeof M}:Fe=function(M){return M&&typeof Symbol=="function"&&M.constructor===Symbol&&M!==Symbol.prototype?"symbol":typeof M},Fe(V)}function ki(V,A){if(!(V instanceof A))throw new TypeError("Cannot call a class as a function")}function no(V,A){for(var M=0;M0&&arguments[0]!==void 0?arguments[0]:{};this.action=typeof D.action=="function"?D.action:this.defaultAction,this.target=typeof D.target=="function"?D.target:this.defaultTarget,this.text=typeof D.text=="function"?D.text:this.defaultText,this.container=Fe(D.container)==="object"?D.container:document.body}},{key:"listenClick",value:function(D){var Y=this;this.listener=c()(D,"click",function($e){return Y.onClick($e)})}},{key:"onClick",value:function(D){var Y=D.delegateTarget||D.currentTarget,$e=this.action(Y)||"copy",Dt=qe({action:$e,container:this.container,target:this.target(Y),text:this.text(Y)});this.emit(Dt?"success":"error",{action:$e,text:Dt,trigger:Y,clearSelection:function(){Y&&Y.focus(),window.getSelection().removeAllRanges()}})}},{key:"defaultAction",value:function(D){return vr("action",D)}},{key:"defaultTarget",value:function(D){var Y=vr("target",D);if(Y)return document.querySelector(Y)}},{key:"defaultText",value:function(D){return vr("text",D)}},{key:"destroy",value:function(){this.listener.destroy()}}],[{key:"copy",value:function(D){var Y=arguments.length>1&&arguments[1]!==void 0?arguments[1]:{container:document.body};return J(D,Y)}},{key:"cut",value:function(D){return y(D)}},{key:"isSupported",value:function(){var D=arguments.length>0&&arguments[0]!==void 0?arguments[0]:["copy","cut"],Y=typeof D=="string"?[D]:D,$e=!!document.queryCommandSupported;return Y.forEach(function(Dt){$e=$e&&!!document.queryCommandSupported(Dt)}),$e}}]),M}(s()),Ui=Fi},828:function(o){var n=9;if(typeof Element!="undefined"&&!Element.prototype.matches){var i=Element.prototype;i.matches=i.matchesSelector||i.mozMatchesSelector||i.msMatchesSelector||i.oMatchesSelector||i.webkitMatchesSelector}function a(s,p){for(;s&&s.nodeType!==n;){if(typeof s.matches=="function"&&s.matches(p))return s;s=s.parentNode}}o.exports=a},438:function(o,n,i){var a=i(828);function s(l,f,u,d,y){var L=c.apply(this,arguments);return l.addEventListener(u,L,y),{destroy:function(){l.removeEventListener(u,L,y)}}}function p(l,f,u,d,y){return typeof l.addEventListener=="function"?s.apply(null,arguments):typeof u=="function"?s.bind(null,document).apply(null,arguments):(typeof l=="string"&&(l=document.querySelectorAll(l)),Array.prototype.map.call(l,function(L){return s(L,f,u,d,y)}))}function c(l,f,u,d){return function(y){y.delegateTarget=a(y.target,f),y.delegateTarget&&d.call(l,y)}}o.exports=p},879:function(o,n){n.node=function(i){return i!==void 0&&i instanceof HTMLElement&&i.nodeType===1},n.nodeList=function(i){var a=Object.prototype.toString.call(i);return i!==void 0&&(a==="[object NodeList]"||a==="[object HTMLCollection]")&&"length"in i&&(i.length===0||n.node(i[0]))},n.string=function(i){return typeof i=="string"||i instanceof String},n.fn=function(i){var a=Object.prototype.toString.call(i);return a==="[object Function]"}},370:function(o,n,i){var a=i(879),s=i(438);function p(u,d,y){if(!u&&!d&&!y)throw new Error("Missing required arguments");if(!a.string(d))throw new TypeError("Second argument must be a String");if(!a.fn(y))throw new TypeError("Third argument must be a Function");if(a.node(u))return c(u,d,y);if(a.nodeList(u))return l(u,d,y);if(a.string(u))return f(u,d,y);throw new TypeError("First argument must be a String, HTMLElement, HTMLCollection, or NodeList")}function c(u,d,y){return u.addEventListener(d,y),{destroy:function(){u.removeEventListener(d,y)}}}function l(u,d,y){return Array.prototype.forEach.call(u,function(L){L.addEventListener(d,y)}),{destroy:function(){Array.prototype.forEach.call(u,function(L){L.removeEventListener(d,y)})}}}function f(u,d,y){return s(document.body,u,d,y)}o.exports=p},817:function(o){function n(i){var a;if(i.nodeName==="SELECT")i.focus(),a=i.value;else if(i.nodeName==="INPUT"||i.nodeName==="TEXTAREA"){var s=i.hasAttribute("readonly");s||i.setAttribute("readonly",""),i.select(),i.setSelectionRange(0,i.value.length),s||i.removeAttribute("readonly"),a=i.value}else{i.hasAttribute("contenteditable")&&i.focus();var p=window.getSelection(),c=document.createRange();c.selectNodeContents(i),p.removeAllRanges(),p.addRange(c),a=p.toString()}return a}o.exports=n},279:function(o){function n(){}n.prototype={on:function(i,a,s){var p=this.e||(this.e={});return(p[i]||(p[i]=[])).push({fn:a,ctx:s}),this},once:function(i,a,s){var p=this;function c(){p.off(i,c),a.apply(s,arguments)}return c._=a,this.on(i,c,s)},emit:function(i){var a=[].slice.call(arguments,1),s=((this.e||(this.e={}))[i]||[]).slice(),p=0,c=s.length;for(p;p0&&i[i.length-1])&&(c[0]===6||c[0]===2)){r=0;continue}if(c[0]===3&&(!i||c[1]>i[0]&&c[1]=e.length&&(e=void 0),{value:e&&e[o++],done:!e}}};throw new TypeError(t?"Object is not iterable.":"Symbol.iterator is not defined.")}function N(e,t){var r=typeof Symbol=="function"&&e[Symbol.iterator];if(!r)return e;var o=r.call(e),n,i=[],a;try{for(;(t===void 0||t-- >0)&&!(n=o.next()).done;)i.push(n.value)}catch(s){a={error:s}}finally{try{n&&!n.done&&(r=o.return)&&r.call(o)}finally{if(a)throw a.error}}return i}function q(e,t,r){if(r||arguments.length===2)for(var o=0,n=t.length,i;o1||p(d,L)})},y&&(n[d]=y(n[d])))}function p(d,y){try{c(o[d](y))}catch(L){u(i[0][3],L)}}function c(d){d.value instanceof nt?Promise.resolve(d.value.v).then(l,f):u(i[0][2],d)}function l(d){p("next",d)}function f(d){p("throw",d)}function u(d,y){d(y),i.shift(),i.length&&p(i[0][0],i[0][1])}}function uo(e){if(!Symbol.asyncIterator)throw new TypeError("Symbol.asyncIterator is not defined.");var t=e[Symbol.asyncIterator],r;return t?t.call(e):(e=typeof he=="function"?he(e):e[Symbol.iterator](),r={},o("next"),o("throw"),o("return"),r[Symbol.asyncIterator]=function(){return this},r);function o(i){r[i]=e[i]&&function(a){return new Promise(function(s,p){a=e[i](a),n(s,p,a.done,a.value)})}}function n(i,a,s,p){Promise.resolve(p).then(function(c){i({value:c,done:s})},a)}}function H(e){return typeof e=="function"}function ut(e){var t=function(o){Error.call(o),o.stack=new Error().stack},r=e(t);return r.prototype=Object.create(Error.prototype),r.prototype.constructor=r,r}var zt=ut(function(e){return function(r){e(this),this.message=r?r.length+` errors occurred during unsubscription: +`+r.map(function(o,n){return n+1+") "+o.toString()}).join(` + `):"",this.name="UnsubscriptionError",this.errors=r}});function Qe(e,t){if(e){var r=e.indexOf(t);0<=r&&e.splice(r,1)}}var Ue=function(){function e(t){this.initialTeardown=t,this.closed=!1,this._parentage=null,this._finalizers=null}return e.prototype.unsubscribe=function(){var t,r,o,n,i;if(!this.closed){this.closed=!0;var a=this._parentage;if(a)if(this._parentage=null,Array.isArray(a))try{for(var s=he(a),p=s.next();!p.done;p=s.next()){var c=p.value;c.remove(this)}}catch(L){t={error:L}}finally{try{p&&!p.done&&(r=s.return)&&r.call(s)}finally{if(t)throw t.error}}else a.remove(this);var l=this.initialTeardown;if(H(l))try{l()}catch(L){i=L instanceof zt?L.errors:[L]}var f=this._finalizers;if(f){this._finalizers=null;try{for(var u=he(f),d=u.next();!d.done;d=u.next()){var y=d.value;try{ho(y)}catch(L){i=i!=null?i:[],L instanceof zt?i=q(q([],N(i)),N(L.errors)):i.push(L)}}}catch(L){o={error:L}}finally{try{d&&!d.done&&(n=u.return)&&n.call(u)}finally{if(o)throw o.error}}}if(i)throw new zt(i)}},e.prototype.add=function(t){var r;if(t&&t!==this)if(this.closed)ho(t);else{if(t instanceof e){if(t.closed||t._hasParent(this))return;t._addParent(this)}(this._finalizers=(r=this._finalizers)!==null&&r!==void 0?r:[]).push(t)}},e.prototype._hasParent=function(t){var r=this._parentage;return r===t||Array.isArray(r)&&r.includes(t)},e.prototype._addParent=function(t){var r=this._parentage;this._parentage=Array.isArray(r)?(r.push(t),r):r?[r,t]:t},e.prototype._removeParent=function(t){var r=this._parentage;r===t?this._parentage=null:Array.isArray(r)&&Qe(r,t)},e.prototype.remove=function(t){var r=this._finalizers;r&&Qe(r,t),t instanceof e&&t._removeParent(this)},e.EMPTY=function(){var t=new e;return t.closed=!0,t}(),e}();var Tr=Ue.EMPTY;function qt(e){return e instanceof Ue||e&&"closed"in e&&H(e.remove)&&H(e.add)&&H(e.unsubscribe)}function ho(e){H(e)?e():e.unsubscribe()}var Pe={onUnhandledError:null,onStoppedNotification:null,Promise:void 0,useDeprecatedSynchronousErrorHandling:!1,useDeprecatedNextContext:!1};var dt={setTimeout:function(e,t){for(var r=[],o=2;o0},enumerable:!1,configurable:!0}),t.prototype._trySubscribe=function(r){return this._throwIfClosed(),e.prototype._trySubscribe.call(this,r)},t.prototype._subscribe=function(r){return this._throwIfClosed(),this._checkFinalizedStatuses(r),this._innerSubscribe(r)},t.prototype._innerSubscribe=function(r){var o=this,n=this,i=n.hasError,a=n.isStopped,s=n.observers;return i||a?Tr:(this.currentObservers=null,s.push(r),new Ue(function(){o.currentObservers=null,Qe(s,r)}))},t.prototype._checkFinalizedStatuses=function(r){var o=this,n=o.hasError,i=o.thrownError,a=o.isStopped;n?r.error(i):a&&r.complete()},t.prototype.asObservable=function(){var r=new j;return r.source=this,r},t.create=function(r,o){return new To(r,o)},t}(j);var To=function(e){oe(t,e);function t(r,o){var n=e.call(this)||this;return n.destination=r,n.source=o,n}return t.prototype.next=function(r){var o,n;(n=(o=this.destination)===null||o===void 0?void 0:o.next)===null||n===void 0||n.call(o,r)},t.prototype.error=function(r){var o,n;(n=(o=this.destination)===null||o===void 0?void 0:o.error)===null||n===void 0||n.call(o,r)},t.prototype.complete=function(){var r,o;(o=(r=this.destination)===null||r===void 0?void 0:r.complete)===null||o===void 0||o.call(r)},t.prototype._subscribe=function(r){var o,n;return(n=(o=this.source)===null||o===void 0?void 0:o.subscribe(r))!==null&&n!==void 0?n:Tr},t}(g);var _r=function(e){oe(t,e);function t(r){var o=e.call(this)||this;return o._value=r,o}return Object.defineProperty(t.prototype,"value",{get:function(){return this.getValue()},enumerable:!1,configurable:!0}),t.prototype._subscribe=function(r){var o=e.prototype._subscribe.call(this,r);return!o.closed&&r.next(this._value),o},t.prototype.getValue=function(){var r=this,o=r.hasError,n=r.thrownError,i=r._value;if(o)throw n;return this._throwIfClosed(),i},t.prototype.next=function(r){e.prototype.next.call(this,this._value=r)},t}(g);var At={now:function(){return(At.delegate||Date).now()},delegate:void 0};var Ct=function(e){oe(t,e);function t(r,o,n){r===void 0&&(r=1/0),o===void 0&&(o=1/0),n===void 0&&(n=At);var i=e.call(this)||this;return i._bufferSize=r,i._windowTime=o,i._timestampProvider=n,i._buffer=[],i._infiniteTimeWindow=!0,i._infiniteTimeWindow=o===1/0,i._bufferSize=Math.max(1,r),i._windowTime=Math.max(1,o),i}return t.prototype.next=function(r){var o=this,n=o.isStopped,i=o._buffer,a=o._infiniteTimeWindow,s=o._timestampProvider,p=o._windowTime;n||(i.push(r),!a&&i.push(s.now()+p)),this._trimBuffer(),e.prototype.next.call(this,r)},t.prototype._subscribe=function(r){this._throwIfClosed(),this._trimBuffer();for(var o=this._innerSubscribe(r),n=this,i=n._infiniteTimeWindow,a=n._buffer,s=a.slice(),p=0;p0?e.prototype.schedule.call(this,r,o):(this.delay=o,this.state=r,this.scheduler.flush(this),this)},t.prototype.execute=function(r,o){return o>0||this.closed?e.prototype.execute.call(this,r,o):this._execute(r,o)},t.prototype.requestAsyncId=function(r,o,n){return n===void 0&&(n=0),n!=null&&n>0||n==null&&this.delay>0?e.prototype.requestAsyncId.call(this,r,o,n):(r.flush(this),0)},t}(gt);var Lo=function(e){oe(t,e);function t(){return e!==null&&e.apply(this,arguments)||this}return t}(yt);var kr=new Lo(Oo);var Mo=function(e){oe(t,e);function t(r,o){var n=e.call(this,r,o)||this;return n.scheduler=r,n.work=o,n}return t.prototype.requestAsyncId=function(r,o,n){return n===void 0&&(n=0),n!==null&&n>0?e.prototype.requestAsyncId.call(this,r,o,n):(r.actions.push(this),r._scheduled||(r._scheduled=vt.requestAnimationFrame(function(){return r.flush(void 0)})))},t.prototype.recycleAsyncId=function(r,o,n){var i;if(n===void 0&&(n=0),n!=null?n>0:this.delay>0)return e.prototype.recycleAsyncId.call(this,r,o,n);var a=r.actions;o!=null&&((i=a[a.length-1])===null||i===void 0?void 0:i.id)!==o&&(vt.cancelAnimationFrame(o),r._scheduled=void 0)},t}(gt);var _o=function(e){oe(t,e);function t(){return e!==null&&e.apply(this,arguments)||this}return t.prototype.flush=function(r){this._active=!0;var o=this._scheduled;this._scheduled=void 0;var n=this.actions,i;r=r||n.shift();do if(i=r.execute(r.state,r.delay))break;while((r=n[0])&&r.id===o&&n.shift());if(this._active=!1,i){for(;(r=n[0])&&r.id===o&&n.shift();)r.unsubscribe();throw i}},t}(yt);var me=new _o(Mo);var S=new j(function(e){return e.complete()});function Yt(e){return e&&H(e.schedule)}function Hr(e){return e[e.length-1]}function Xe(e){return H(Hr(e))?e.pop():void 0}function ke(e){return Yt(Hr(e))?e.pop():void 0}function Bt(e,t){return typeof Hr(e)=="number"?e.pop():t}var xt=function(e){return e&&typeof e.length=="number"&&typeof e!="function"};function Gt(e){return H(e==null?void 0:e.then)}function Jt(e){return H(e[bt])}function Xt(e){return Symbol.asyncIterator&&H(e==null?void 0:e[Symbol.asyncIterator])}function Zt(e){return new TypeError("You provided "+(e!==null&&typeof e=="object"?"an invalid object":"'"+e+"'")+" where a stream was expected. You can provide an Observable, Promise, ReadableStream, Array, AsyncIterable, or Iterable.")}function Zi(){return typeof Symbol!="function"||!Symbol.iterator?"@@iterator":Symbol.iterator}var er=Zi();function tr(e){return H(e==null?void 0:e[er])}function rr(e){return fo(this,arguments,function(){var r,o,n,i;return Nt(this,function(a){switch(a.label){case 0:r=e.getReader(),a.label=1;case 1:a.trys.push([1,,9,10]),a.label=2;case 2:return[4,nt(r.read())];case 3:return o=a.sent(),n=o.value,i=o.done,i?[4,nt(void 0)]:[3,5];case 4:return[2,a.sent()];case 5:return[4,nt(n)];case 6:return[4,a.sent()];case 7:return a.sent(),[3,2];case 8:return[3,10];case 9:return r.releaseLock(),[7];case 10:return[2]}})})}function or(e){return H(e==null?void 0:e.getReader)}function U(e){if(e instanceof j)return e;if(e!=null){if(Jt(e))return ea(e);if(xt(e))return ta(e);if(Gt(e))return ra(e);if(Xt(e))return Ao(e);if(tr(e))return oa(e);if(or(e))return na(e)}throw Zt(e)}function ea(e){return new j(function(t){var r=e[bt]();if(H(r.subscribe))return r.subscribe(t);throw new TypeError("Provided object does not correctly implement Symbol.observable")})}function ta(e){return new j(function(t){for(var r=0;r=2;return function(o){return o.pipe(e?b(function(n,i){return e(n,i,o)}):le,Te(1),r?De(t):Qo(function(){return new ir}))}}function jr(e){return e<=0?function(){return S}:E(function(t,r){var o=[];t.subscribe(T(r,function(n){o.push(n),e=2,!0))}function pe(e){e===void 0&&(e={});var t=e.connector,r=t===void 0?function(){return new g}:t,o=e.resetOnError,n=o===void 0?!0:o,i=e.resetOnComplete,a=i===void 0?!0:i,s=e.resetOnRefCountZero,p=s===void 0?!0:s;return function(c){var l,f,u,d=0,y=!1,L=!1,X=function(){f==null||f.unsubscribe(),f=void 0},te=function(){X(),l=u=void 0,y=L=!1},J=function(){var k=l;te(),k==null||k.unsubscribe()};return E(function(k,ft){d++,!L&&!y&&X();var qe=u=u!=null?u:r();ft.add(function(){d--,d===0&&!L&&!y&&(f=Ur(J,p))}),qe.subscribe(ft),!l&&d>0&&(l=new at({next:function(Fe){return qe.next(Fe)},error:function(Fe){L=!0,X(),f=Ur(te,n,Fe),qe.error(Fe)},complete:function(){y=!0,X(),f=Ur(te,a),qe.complete()}}),U(k).subscribe(l))})(c)}}function Ur(e,t){for(var r=[],o=2;oe.next(document)),e}function P(e,t=document){return Array.from(t.querySelectorAll(e))}function R(e,t=document){let r=fe(e,t);if(typeof r=="undefined")throw new ReferenceError(`Missing element: expected "${e}" to be present`);return r}function fe(e,t=document){return t.querySelector(e)||void 0}function Ie(){var e,t,r,o;return(o=(r=(t=(e=document.activeElement)==null?void 0:e.shadowRoot)==null?void 0:t.activeElement)!=null?r:document.activeElement)!=null?o:void 0}var wa=O(h(document.body,"focusin"),h(document.body,"focusout")).pipe(_e(1),Q(void 0),m(()=>Ie()||document.body),G(1));function et(e){return wa.pipe(m(t=>e.contains(t)),K())}function $t(e,t){return C(()=>O(h(e,"mouseenter").pipe(m(()=>!0)),h(e,"mouseleave").pipe(m(()=>!1))).pipe(t?Ht(r=>Le(+!r*t)):le,Q(e.matches(":hover"))))}function Jo(e,t){if(typeof t=="string"||typeof t=="number")e.innerHTML+=t.toString();else if(t instanceof Node)e.appendChild(t);else if(Array.isArray(t))for(let r of t)Jo(e,r)}function x(e,t,...r){let o=document.createElement(e);if(t)for(let n of Object.keys(t))typeof t[n]!="undefined"&&(typeof t[n]!="boolean"?o.setAttribute(n,t[n]):o.setAttribute(n,""));for(let n of r)Jo(o,n);return o}function sr(e){if(e>999){let t=+((e-950)%1e3>99);return`${((e+1e-6)/1e3).toFixed(t)}k`}else return e.toString()}function Tt(e){let t=x("script",{src:e});return C(()=>(document.head.appendChild(t),O(h(t,"load"),h(t,"error").pipe(v(()=>$r(()=>new ReferenceError(`Invalid script: ${e}`))))).pipe(m(()=>{}),_(()=>document.head.removeChild(t)),Te(1))))}var Xo=new g,Ta=C(()=>typeof ResizeObserver=="undefined"?Tt("https://unpkg.com/resize-observer-polyfill"):I(void 0)).pipe(m(()=>new ResizeObserver(e=>e.forEach(t=>Xo.next(t)))),v(e=>O(Ye,I(e)).pipe(_(()=>e.disconnect()))),G(1));function ce(e){return{width:e.offsetWidth,height:e.offsetHeight}}function ge(e){let t=e;for(;t.clientWidth===0&&t.parentElement;)t=t.parentElement;return Ta.pipe(w(r=>r.observe(t)),v(r=>Xo.pipe(b(o=>o.target===t),_(()=>r.unobserve(t)))),m(()=>ce(e)),Q(ce(e)))}function St(e){return{width:e.scrollWidth,height:e.scrollHeight}}function cr(e){let t=e.parentElement;for(;t&&(e.scrollWidth<=t.scrollWidth&&e.scrollHeight<=t.scrollHeight);)t=(e=t).parentElement;return t?e:void 0}function Zo(e){let t=[],r=e.parentElement;for(;r;)(e.clientWidth>r.clientWidth||e.clientHeight>r.clientHeight)&&t.push(r),r=(e=r).parentElement;return t.length===0&&t.push(document.documentElement),t}function Ve(e){return{x:e.offsetLeft,y:e.offsetTop}}function en(e){let t=e.getBoundingClientRect();return{x:t.x+window.scrollX,y:t.y+window.scrollY}}function tn(e){return O(h(window,"load"),h(window,"resize")).pipe(Me(0,me),m(()=>Ve(e)),Q(Ve(e)))}function pr(e){return{x:e.scrollLeft,y:e.scrollTop}}function Ne(e){return O(h(e,"scroll"),h(window,"scroll"),h(window,"resize")).pipe(Me(0,me),m(()=>pr(e)),Q(pr(e)))}var rn=new g,Sa=C(()=>I(new IntersectionObserver(e=>{for(let t of e)rn.next(t)},{threshold:0}))).pipe(v(e=>O(Ye,I(e)).pipe(_(()=>e.disconnect()))),G(1));function tt(e){return Sa.pipe(w(t=>t.observe(e)),v(t=>rn.pipe(b(({target:r})=>r===e),_(()=>t.unobserve(e)),m(({isIntersecting:r})=>r))))}function on(e,t=16){return Ne(e).pipe(m(({y:r})=>{let o=ce(e),n=St(e);return r>=n.height-o.height-t}),K())}var lr={drawer:R("[data-md-toggle=drawer]"),search:R("[data-md-toggle=search]")};function nn(e){return lr[e].checked}function Je(e,t){lr[e].checked!==t&&lr[e].click()}function ze(e){let t=lr[e];return h(t,"change").pipe(m(()=>t.checked),Q(t.checked))}function Oa(e,t){switch(e.constructor){case HTMLInputElement:return e.type==="radio"?/^Arrow/.test(t):!0;case HTMLSelectElement:case HTMLTextAreaElement:return!0;default:return e.isContentEditable}}function La(){return O(h(window,"compositionstart").pipe(m(()=>!0)),h(window,"compositionend").pipe(m(()=>!1))).pipe(Q(!1))}function an(){let e=h(window,"keydown").pipe(b(t=>!(t.metaKey||t.ctrlKey)),m(t=>({mode:nn("search")?"search":"global",type:t.key,claim(){t.preventDefault(),t.stopPropagation()}})),b(({mode:t,type:r})=>{if(t==="global"){let o=Ie();if(typeof o!="undefined")return!Oa(o,r)}return!0}),pe());return La().pipe(v(t=>t?S:e))}function ye(){return new URL(location.href)}function lt(e,t=!1){if(B("navigation.instant")&&!t){let r=x("a",{href:e.href});document.body.appendChild(r),r.click(),r.remove()}else location.href=e.href}function sn(){return new g}function cn(){return location.hash.slice(1)}function pn(e){let t=x("a",{href:e});t.addEventListener("click",r=>r.stopPropagation()),t.click()}function Ma(e){return O(h(window,"hashchange"),e).pipe(m(cn),Q(cn()),b(t=>t.length>0),G(1))}function ln(e){return Ma(e).pipe(m(t=>fe(`[id="${t}"]`)),b(t=>typeof t!="undefined"))}function Pt(e){let t=matchMedia(e);return ar(r=>t.addListener(()=>r(t.matches))).pipe(Q(t.matches))}function mn(){let e=matchMedia("print");return O(h(window,"beforeprint").pipe(m(()=>!0)),h(window,"afterprint").pipe(m(()=>!1))).pipe(Q(e.matches))}function Nr(e,t){return e.pipe(v(r=>r?t():S))}function zr(e,t){return new j(r=>{let o=new XMLHttpRequest;return o.open("GET",`${e}`),o.responseType="blob",o.addEventListener("load",()=>{o.status>=200&&o.status<300?(r.next(o.response),r.complete()):r.error(new Error(o.statusText))}),o.addEventListener("error",()=>{r.error(new Error("Network error"))}),o.addEventListener("abort",()=>{r.complete()}),typeof(t==null?void 0:t.progress$)!="undefined"&&(o.addEventListener("progress",n=>{var i;if(n.lengthComputable)t.progress$.next(n.loaded/n.total*100);else{let a=(i=o.getResponseHeader("Content-Length"))!=null?i:0;t.progress$.next(n.loaded/+a*100)}}),t.progress$.next(5)),o.send(),()=>o.abort()})}function je(e,t){return zr(e,t).pipe(v(r=>r.text()),m(r=>JSON.parse(r)),G(1))}function fn(e,t){let r=new DOMParser;return zr(e,t).pipe(v(o=>o.text()),m(o=>r.parseFromString(o,"text/html")),G(1))}function un(e,t){let r=new DOMParser;return zr(e,t).pipe(v(o=>o.text()),m(o=>r.parseFromString(o,"text/xml")),G(1))}function dn(){return{x:Math.max(0,scrollX),y:Math.max(0,scrollY)}}function hn(){return O(h(window,"scroll",{passive:!0}),h(window,"resize",{passive:!0})).pipe(m(dn),Q(dn()))}function bn(){return{width:innerWidth,height:innerHeight}}function vn(){return h(window,"resize",{passive:!0}).pipe(m(bn),Q(bn()))}function gn(){return z([hn(),vn()]).pipe(m(([e,t])=>({offset:e,size:t})),G(1))}function mr(e,{viewport$:t,header$:r}){let o=t.pipe(ee("size")),n=z([o,r]).pipe(m(()=>Ve(e)));return z([r,t,n]).pipe(m(([{height:i},{offset:a,size:s},{x:p,y:c}])=>({offset:{x:a.x-p,y:a.y-c+i},size:s})))}function _a(e){return h(e,"message",t=>t.data)}function Aa(e){let t=new g;return t.subscribe(r=>e.postMessage(r)),t}function yn(e,t=new Worker(e)){let r=_a(t),o=Aa(t),n=new g;n.subscribe(o);let i=o.pipe(Z(),ie(!0));return n.pipe(Z(),Re(r.pipe(W(i))),pe())}var Ca=R("#__config"),Ot=JSON.parse(Ca.textContent);Ot.base=`${new URL(Ot.base,ye())}`;function xe(){return Ot}function B(e){return Ot.features.includes(e)}function Ee(e,t){return typeof t!="undefined"?Ot.translations[e].replace("#",t.toString()):Ot.translations[e]}function Se(e,t=document){return R(`[data-md-component=${e}]`,t)}function ae(e,t=document){return P(`[data-md-component=${e}]`,t)}function ka(e){let t=R(".md-typeset > :first-child",e);return h(t,"click",{once:!0}).pipe(m(()=>R(".md-typeset",e)),m(r=>({hash:__md_hash(r.innerHTML)})))}function xn(e){if(!B("announce.dismiss")||!e.childElementCount)return S;if(!e.hidden){let t=R(".md-typeset",e);__md_hash(t.innerHTML)===__md_get("__announce")&&(e.hidden=!0)}return C(()=>{let t=new g;return t.subscribe(({hash:r})=>{e.hidden=!0,__md_set("__announce",r)}),ka(e).pipe(w(r=>t.next(r)),_(()=>t.complete()),m(r=>$({ref:e},r)))})}function Ha(e,{target$:t}){return t.pipe(m(r=>({hidden:r!==e})))}function En(e,t){let r=new g;return r.subscribe(({hidden:o})=>{e.hidden=o}),Ha(e,t).pipe(w(o=>r.next(o)),_(()=>r.complete()),m(o=>$({ref:e},o)))}function Rt(e,t){return t==="inline"?x("div",{class:"md-tooltip md-tooltip--inline",id:e,role:"tooltip"},x("div",{class:"md-tooltip__inner md-typeset"})):x("div",{class:"md-tooltip",id:e,role:"tooltip"},x("div",{class:"md-tooltip__inner md-typeset"}))}function wn(...e){return x("div",{class:"md-tooltip2",role:"tooltip"},x("div",{class:"md-tooltip2__inner md-typeset"},e))}function Tn(e,t){if(t=t?`${t}_annotation_${e}`:void 0,t){let r=t?`#${t}`:void 0;return x("aside",{class:"md-annotation",tabIndex:0},Rt(t),x("a",{href:r,class:"md-annotation__index",tabIndex:-1},x("span",{"data-md-annotation-id":e})))}else return x("aside",{class:"md-annotation",tabIndex:0},Rt(t),x("span",{class:"md-annotation__index",tabIndex:-1},x("span",{"data-md-annotation-id":e})))}function Sn(e){return x("button",{class:"md-clipboard md-icon",title:Ee("clipboard.copy"),"data-clipboard-target":`#${e} > code`})}var Ln=Mt(qr());function Qr(e,t){let r=t&2,o=t&1,n=Object.keys(e.terms).filter(p=>!e.terms[p]).reduce((p,c)=>[...p,x("del",null,(0,Ln.default)(c))," "],[]).slice(0,-1),i=xe(),a=new URL(e.location,i.base);B("search.highlight")&&a.searchParams.set("h",Object.entries(e.terms).filter(([,p])=>p).reduce((p,[c])=>`${p} ${c}`.trim(),""));let{tags:s}=xe();return x("a",{href:`${a}`,class:"md-search-result__link",tabIndex:-1},x("article",{class:"md-search-result__article md-typeset","data-md-score":e.score.toFixed(2)},r>0&&x("div",{class:"md-search-result__icon md-icon"}),r>0&&x("h1",null,e.title),r<=0&&x("h2",null,e.title),o>0&&e.text.length>0&&e.text,e.tags&&x("nav",{class:"md-tags"},e.tags.map(p=>{let c=s?p in s?`md-tag-icon md-tag--${s[p]}`:"md-tag-icon":"";return x("span",{class:`md-tag ${c}`},p)})),o>0&&n.length>0&&x("p",{class:"md-search-result__terms"},Ee("search.result.term.missing"),": ",...n)))}function Mn(e){let t=e[0].score,r=[...e],o=xe(),n=r.findIndex(l=>!`${new URL(l.location,o.base)}`.includes("#")),[i]=r.splice(n,1),a=r.findIndex(l=>l.scoreQr(l,1)),...p.length?[x("details",{class:"md-search-result__more"},x("summary",{tabIndex:-1},x("div",null,p.length>0&&p.length===1?Ee("search.result.more.one"):Ee("search.result.more.other",p.length))),...p.map(l=>Qr(l,1)))]:[]];return x("li",{class:"md-search-result__item"},c)}function _n(e){return x("ul",{class:"md-source__facts"},Object.entries(e).map(([t,r])=>x("li",{class:`md-source__fact md-source__fact--${t}`},typeof r=="number"?sr(r):r)))}function Kr(e){let t=`tabbed-control tabbed-control--${e}`;return x("div",{class:t,hidden:!0},x("button",{class:"tabbed-button",tabIndex:-1,"aria-hidden":"true"}))}function An(e){return x("div",{class:"md-typeset__scrollwrap"},x("div",{class:"md-typeset__table"},e))}function Ra(e){var o;let t=xe(),r=new URL(`../${e.version}/`,t.base);return x("li",{class:"md-version__item"},x("a",{href:`${r}`,class:"md-version__link"},e.title,((o=t.version)==null?void 0:o.alias)&&e.aliases.length>0&&x("span",{class:"md-version__alias"},e.aliases[0])))}function Cn(e,t){var o;let r=xe();return e=e.filter(n=>{var i;return!((i=n.properties)!=null&&i.hidden)}),x("div",{class:"md-version"},x("button",{class:"md-version__current","aria-label":Ee("select.version")},t.title,((o=r.version)==null?void 0:o.alias)&&t.aliases.length>0&&x("span",{class:"md-version__alias"},t.aliases[0])),x("ul",{class:"md-version__list"},e.map(Ra)))}var Ia=0;function ja(e){let t=z([et(e),$t(e)]).pipe(m(([o,n])=>o||n),K()),r=C(()=>Zo(e)).pipe(ne(Ne),pt(1),He(t),m(()=>en(e)));return t.pipe(Ae(o=>o),v(()=>z([t,r])),m(([o,n])=>({active:o,offset:n})),pe())}function Fa(e,t){let{content$:r,viewport$:o}=t,n=`__tooltip2_${Ia++}`;return C(()=>{let i=new g,a=new _r(!1);i.pipe(Z(),ie(!1)).subscribe(a);let s=a.pipe(Ht(c=>Le(+!c*250,kr)),K(),v(c=>c?r:S),w(c=>c.id=n),pe());z([i.pipe(m(({active:c})=>c)),s.pipe(v(c=>$t(c,250)),Q(!1))]).pipe(m(c=>c.some(l=>l))).subscribe(a);let p=a.pipe(b(c=>c),re(s,o),m(([c,l,{size:f}])=>{let u=e.getBoundingClientRect(),d=u.width/2;if(l.role==="tooltip")return{x:d,y:8+u.height};if(u.y>=f.height/2){let{height:y}=ce(l);return{x:d,y:-16-y}}else return{x:d,y:16+u.height}}));return z([s,i,p]).subscribe(([c,{offset:l},f])=>{c.style.setProperty("--md-tooltip-host-x",`${l.x}px`),c.style.setProperty("--md-tooltip-host-y",`${l.y}px`),c.style.setProperty("--md-tooltip-x",`${f.x}px`),c.style.setProperty("--md-tooltip-y",`${f.y}px`),c.classList.toggle("md-tooltip2--top",f.y<0),c.classList.toggle("md-tooltip2--bottom",f.y>=0)}),a.pipe(b(c=>c),re(s,(c,l)=>l),b(c=>c.role==="tooltip")).subscribe(c=>{let l=ce(R(":scope > *",c));c.style.setProperty("--md-tooltip-width",`${l.width}px`),c.style.setProperty("--md-tooltip-tail","0px")}),a.pipe(K(),ve(me),re(s)).subscribe(([c,l])=>{l.classList.toggle("md-tooltip2--active",c)}),z([a.pipe(b(c=>c)),s]).subscribe(([c,l])=>{l.role==="dialog"?(e.setAttribute("aria-controls",n),e.setAttribute("aria-haspopup","dialog")):e.setAttribute("aria-describedby",n)}),a.pipe(b(c=>!c)).subscribe(()=>{e.removeAttribute("aria-controls"),e.removeAttribute("aria-describedby"),e.removeAttribute("aria-haspopup")}),ja(e).pipe(w(c=>i.next(c)),_(()=>i.complete()),m(c=>$({ref:e},c)))})}function mt(e,{viewport$:t},r=document.body){return Fa(e,{content$:new j(o=>{let n=e.title,i=wn(n);return o.next(i),e.removeAttribute("title"),r.append(i),()=>{i.remove(),e.setAttribute("title",n)}}),viewport$:t})}function Ua(e,t){let r=C(()=>z([tn(e),Ne(t)])).pipe(m(([{x:o,y:n},i])=>{let{width:a,height:s}=ce(e);return{x:o-i.x+a/2,y:n-i.y+s/2}}));return et(e).pipe(v(o=>r.pipe(m(n=>({active:o,offset:n})),Te(+!o||1/0))))}function kn(e,t,{target$:r}){let[o,n]=Array.from(e.children);return C(()=>{let i=new g,a=i.pipe(Z(),ie(!0));return i.subscribe({next({offset:s}){e.style.setProperty("--md-tooltip-x",`${s.x}px`),e.style.setProperty("--md-tooltip-y",`${s.y}px`)},complete(){e.style.removeProperty("--md-tooltip-x"),e.style.removeProperty("--md-tooltip-y")}}),tt(e).pipe(W(a)).subscribe(s=>{e.toggleAttribute("data-md-visible",s)}),O(i.pipe(b(({active:s})=>s)),i.pipe(_e(250),b(({active:s})=>!s))).subscribe({next({active:s}){s?e.prepend(o):o.remove()},complete(){e.prepend(o)}}),i.pipe(Me(16,me)).subscribe(({active:s})=>{o.classList.toggle("md-tooltip--active",s)}),i.pipe(pt(125,me),b(()=>!!e.offsetParent),m(()=>e.offsetParent.getBoundingClientRect()),m(({x:s})=>s)).subscribe({next(s){s?e.style.setProperty("--md-tooltip-0",`${-s}px`):e.style.removeProperty("--md-tooltip-0")},complete(){e.style.removeProperty("--md-tooltip-0")}}),h(n,"click").pipe(W(a),b(s=>!(s.metaKey||s.ctrlKey))).subscribe(s=>{s.stopPropagation(),s.preventDefault()}),h(n,"mousedown").pipe(W(a),re(i)).subscribe(([s,{active:p}])=>{var c;if(s.button!==0||s.metaKey||s.ctrlKey)s.preventDefault();else if(p){s.preventDefault();let l=e.parentElement.closest(".md-annotation");l instanceof HTMLElement?l.focus():(c=Ie())==null||c.blur()}}),r.pipe(W(a),b(s=>s===o),Ge(125)).subscribe(()=>e.focus()),Ua(e,t).pipe(w(s=>i.next(s)),_(()=>i.complete()),m(s=>$({ref:e},s)))})}function Wa(e){return e.tagName==="CODE"?P(".c, .c1, .cm",e):[e]}function Da(e){let t=[];for(let r of Wa(e)){let o=[],n=document.createNodeIterator(r,NodeFilter.SHOW_TEXT);for(let i=n.nextNode();i;i=n.nextNode())o.push(i);for(let i of o){let a;for(;a=/(\(\d+\))(!)?/.exec(i.textContent);){let[,s,p]=a;if(typeof p=="undefined"){let c=i.splitText(a.index);i=c.splitText(s.length),t.push(c)}else{i.textContent=s,t.push(i);break}}}}return t}function Hn(e,t){t.append(...Array.from(e.childNodes))}function fr(e,t,{target$:r,print$:o}){let n=t.closest("[id]"),i=n==null?void 0:n.id,a=new Map;for(let s of Da(t)){let[,p]=s.textContent.match(/\((\d+)\)/);fe(`:scope > li:nth-child(${p})`,e)&&(a.set(p,Tn(p,i)),s.replaceWith(a.get(p)))}return a.size===0?S:C(()=>{let s=new g,p=s.pipe(Z(),ie(!0)),c=[];for(let[l,f]of a)c.push([R(".md-typeset",f),R(`:scope > li:nth-child(${l})`,e)]);return o.pipe(W(p)).subscribe(l=>{e.hidden=!l,e.classList.toggle("md-annotation-list",l);for(let[f,u]of c)l?Hn(f,u):Hn(u,f)}),O(...[...a].map(([,l])=>kn(l,t,{target$:r}))).pipe(_(()=>s.complete()),pe())})}function $n(e){if(e.nextElementSibling){let t=e.nextElementSibling;if(t.tagName==="OL")return t;if(t.tagName==="P"&&!t.children.length)return $n(t)}}function Pn(e,t){return C(()=>{let r=$n(e);return typeof r!="undefined"?fr(r,e,t):S})}var Rn=Mt(Br());var Va=0;function In(e){if(e.nextElementSibling){let t=e.nextElementSibling;if(t.tagName==="OL")return t;if(t.tagName==="P"&&!t.children.length)return In(t)}}function Na(e){return ge(e).pipe(m(({width:t})=>({scrollable:St(e).width>t})),ee("scrollable"))}function jn(e,t){let{matches:r}=matchMedia("(hover)"),o=C(()=>{let n=new g,i=n.pipe(jr(1));n.subscribe(({scrollable:c})=>{c&&r?e.setAttribute("tabindex","0"):e.removeAttribute("tabindex")});let a=[];if(Rn.default.isSupported()&&(e.closest(".copy")||B("content.code.copy")&&!e.closest(".no-copy"))){let c=e.closest("pre");c.id=`__code_${Va++}`;let l=Sn(c.id);c.insertBefore(l,e),B("content.tooltips")&&a.push(mt(l,{viewport$}))}let s=e.closest(".highlight");if(s instanceof HTMLElement){let c=In(s);if(typeof c!="undefined"&&(s.classList.contains("annotate")||B("content.code.annotate"))){let l=fr(c,e,t);a.push(ge(s).pipe(W(i),m(({width:f,height:u})=>f&&u),K(),v(f=>f?l:S)))}}return P(":scope > span[id]",e).length&&e.classList.add("md-code__content"),Na(e).pipe(w(c=>n.next(c)),_(()=>n.complete()),m(c=>$({ref:e},c)),Re(...a))});return B("content.lazy")?tt(e).pipe(b(n=>n),Te(1),v(()=>o)):o}function za(e,{target$:t,print$:r}){let o=!0;return O(t.pipe(m(n=>n.closest("details:not([open])")),b(n=>e===n),m(()=>({action:"open",reveal:!0}))),r.pipe(b(n=>n||!o),w(()=>o=e.open),m(n=>({action:n?"open":"close"}))))}function Fn(e,t){return C(()=>{let r=new g;return r.subscribe(({action:o,reveal:n})=>{e.toggleAttribute("open",o==="open"),n&&e.scrollIntoView()}),za(e,t).pipe(w(o=>r.next(o)),_(()=>r.complete()),m(o=>$({ref:e},o)))})}var Un=".node circle,.node ellipse,.node path,.node polygon,.node rect{fill:var(--md-mermaid-node-bg-color);stroke:var(--md-mermaid-node-fg-color)}marker{fill:var(--md-mermaid-edge-color)!important}.edgeLabel .label rect{fill:#0000}.label{color:var(--md-mermaid-label-fg-color);font-family:var(--md-mermaid-font-family)}.label foreignObject{line-height:normal;overflow:visible}.label div .edgeLabel{color:var(--md-mermaid-label-fg-color)}.edgeLabel,.edgeLabel p,.label div .edgeLabel{background-color:var(--md-mermaid-label-bg-color)}.edgeLabel,.edgeLabel p{fill:var(--md-mermaid-label-bg-color);color:var(--md-mermaid-edge-color)}.edgePath .path,.flowchart-link{stroke:var(--md-mermaid-edge-color);stroke-width:.05rem}.edgePath .arrowheadPath{fill:var(--md-mermaid-edge-color);stroke:none}.cluster rect{fill:var(--md-default-fg-color--lightest);stroke:var(--md-default-fg-color--lighter)}.cluster span{color:var(--md-mermaid-label-fg-color);font-family:var(--md-mermaid-font-family)}g #flowchart-circleEnd,g #flowchart-circleStart,g #flowchart-crossEnd,g #flowchart-crossStart,g #flowchart-pointEnd,g #flowchart-pointStart{stroke:none}g.classGroup line,g.classGroup rect{fill:var(--md-mermaid-node-bg-color);stroke:var(--md-mermaid-node-fg-color)}g.classGroup text{fill:var(--md-mermaid-label-fg-color);font-family:var(--md-mermaid-font-family)}.classLabel .box{fill:var(--md-mermaid-label-bg-color);background-color:var(--md-mermaid-label-bg-color);opacity:1}.classLabel .label{fill:var(--md-mermaid-label-fg-color);font-family:var(--md-mermaid-font-family)}.node .divider{stroke:var(--md-mermaid-node-fg-color)}.relation{stroke:var(--md-mermaid-edge-color)}.cardinality{fill:var(--md-mermaid-label-fg-color);font-family:var(--md-mermaid-font-family)}.cardinality text{fill:inherit!important}defs #classDiagram-compositionEnd,defs #classDiagram-compositionStart,defs #classDiagram-dependencyEnd,defs #classDiagram-dependencyStart,defs #classDiagram-extensionEnd,defs #classDiagram-extensionStart{fill:var(--md-mermaid-edge-color)!important;stroke:var(--md-mermaid-edge-color)!important}defs #classDiagram-aggregationEnd,defs #classDiagram-aggregationStart{fill:var(--md-mermaid-label-bg-color)!important;stroke:var(--md-mermaid-edge-color)!important}g.stateGroup rect{fill:var(--md-mermaid-node-bg-color);stroke:var(--md-mermaid-node-fg-color)}g.stateGroup .state-title{fill:var(--md-mermaid-label-fg-color)!important;font-family:var(--md-mermaid-font-family)}g.stateGroup .composit{fill:var(--md-mermaid-label-bg-color)}.nodeLabel,.nodeLabel p{color:var(--md-mermaid-label-fg-color);font-family:var(--md-mermaid-font-family)}a .nodeLabel{text-decoration:underline}.node circle.state-end,.node circle.state-start,.start-state{fill:var(--md-mermaid-edge-color);stroke:none}.end-state-inner,.end-state-outer{fill:var(--md-mermaid-edge-color)}.end-state-inner,.node circle.state-end{stroke:var(--md-mermaid-label-bg-color)}.transition{stroke:var(--md-mermaid-edge-color)}[id^=state-fork] rect,[id^=state-join] rect{fill:var(--md-mermaid-edge-color)!important;stroke:none!important}.statediagram-cluster.statediagram-cluster .inner{fill:var(--md-default-bg-color)}.statediagram-cluster rect{fill:var(--md-mermaid-node-bg-color);stroke:var(--md-mermaid-node-fg-color)}.statediagram-state rect.divider{fill:var(--md-default-fg-color--lightest);stroke:var(--md-default-fg-color--lighter)}defs #statediagram-barbEnd{stroke:var(--md-mermaid-edge-color)}.attributeBoxEven,.attributeBoxOdd{fill:var(--md-mermaid-node-bg-color);stroke:var(--md-mermaid-node-fg-color)}.entityBox{fill:var(--md-mermaid-label-bg-color);stroke:var(--md-mermaid-node-fg-color)}.entityLabel{fill:var(--md-mermaid-label-fg-color);font-family:var(--md-mermaid-font-family)}.relationshipLabelBox{fill:var(--md-mermaid-label-bg-color);fill-opacity:1;background-color:var(--md-mermaid-label-bg-color);opacity:1}.relationshipLabel{fill:var(--md-mermaid-label-fg-color)}.relationshipLine{stroke:var(--md-mermaid-edge-color)}defs #ONE_OR_MORE_END *,defs #ONE_OR_MORE_START *,defs #ONLY_ONE_END *,defs #ONLY_ONE_START *,defs #ZERO_OR_MORE_END *,defs #ZERO_OR_MORE_START *,defs #ZERO_OR_ONE_END *,defs #ZERO_OR_ONE_START *{stroke:var(--md-mermaid-edge-color)!important}defs #ZERO_OR_MORE_END circle,defs #ZERO_OR_MORE_START circle{fill:var(--md-mermaid-label-bg-color)}.actor{fill:var(--md-mermaid-sequence-actor-bg-color);stroke:var(--md-mermaid-sequence-actor-border-color)}text.actor>tspan{fill:var(--md-mermaid-sequence-actor-fg-color);font-family:var(--md-mermaid-font-family)}line{stroke:var(--md-mermaid-sequence-actor-line-color)}.actor-man circle,.actor-man line{fill:var(--md-mermaid-sequence-actorman-bg-color);stroke:var(--md-mermaid-sequence-actorman-line-color)}.messageLine0,.messageLine1{stroke:var(--md-mermaid-sequence-message-line-color)}.note{fill:var(--md-mermaid-sequence-note-bg-color);stroke:var(--md-mermaid-sequence-note-border-color)}.loopText,.loopText>tspan,.messageText,.noteText>tspan{stroke:none;font-family:var(--md-mermaid-font-family)!important}.messageText{fill:var(--md-mermaid-sequence-message-fg-color)}.loopText,.loopText>tspan{fill:var(--md-mermaid-sequence-loop-fg-color)}.noteText>tspan{fill:var(--md-mermaid-sequence-note-fg-color)}#arrowhead path{fill:var(--md-mermaid-sequence-message-line-color);stroke:none}.loopLine{fill:var(--md-mermaid-sequence-loop-bg-color);stroke:var(--md-mermaid-sequence-loop-border-color)}.labelBox{fill:var(--md-mermaid-sequence-label-bg-color);stroke:none}.labelText,.labelText>span{fill:var(--md-mermaid-sequence-label-fg-color);font-family:var(--md-mermaid-font-family)}.sequenceNumber{fill:var(--md-mermaid-sequence-number-fg-color)}rect.rect{fill:var(--md-mermaid-sequence-box-bg-color);stroke:none}rect.rect+text.text{fill:var(--md-mermaid-sequence-box-fg-color)}defs #sequencenumber{fill:var(--md-mermaid-sequence-number-bg-color)!important}";var Gr,Qa=0;function Ka(){return typeof mermaid=="undefined"||mermaid instanceof Element?Tt("https://unpkg.com/mermaid@11/dist/mermaid.min.js"):I(void 0)}function Wn(e){return e.classList.remove("mermaid"),Gr||(Gr=Ka().pipe(w(()=>mermaid.initialize({startOnLoad:!1,themeCSS:Un,sequence:{actorFontSize:"16px",messageFontSize:"16px",noteFontSize:"16px"}})),m(()=>{}),G(1))),Gr.subscribe(()=>co(this,null,function*(){e.classList.add("mermaid");let t=`__mermaid_${Qa++}`,r=x("div",{class:"mermaid"}),o=e.textContent,{svg:n,fn:i}=yield mermaid.render(t,o),a=r.attachShadow({mode:"closed"});a.innerHTML=n,e.replaceWith(r),i==null||i(a)})),Gr.pipe(m(()=>({ref:e})))}var Dn=x("table");function Vn(e){return e.replaceWith(Dn),Dn.replaceWith(An(e)),I({ref:e})}function Ya(e){let t=e.find(r=>r.checked)||e[0];return O(...e.map(r=>h(r,"change").pipe(m(()=>R(`label[for="${r.id}"]`))))).pipe(Q(R(`label[for="${t.id}"]`)),m(r=>({active:r})))}function Nn(e,{viewport$:t,target$:r}){let o=R(".tabbed-labels",e),n=P(":scope > input",e),i=Kr("prev");e.append(i);let a=Kr("next");return e.append(a),C(()=>{let s=new g,p=s.pipe(Z(),ie(!0));z([s,ge(e),tt(e)]).pipe(W(p),Me(1,me)).subscribe({next([{active:c},l]){let f=Ve(c),{width:u}=ce(c);e.style.setProperty("--md-indicator-x",`${f.x}px`),e.style.setProperty("--md-indicator-width",`${u}px`);let d=pr(o);(f.xd.x+l.width)&&o.scrollTo({left:Math.max(0,f.x-16),behavior:"smooth"})},complete(){e.style.removeProperty("--md-indicator-x"),e.style.removeProperty("--md-indicator-width")}}),z([Ne(o),ge(o)]).pipe(W(p)).subscribe(([c,l])=>{let f=St(o);i.hidden=c.x<16,a.hidden=c.x>f.width-l.width-16}),O(h(i,"click").pipe(m(()=>-1)),h(a,"click").pipe(m(()=>1))).pipe(W(p)).subscribe(c=>{let{width:l}=ce(o);o.scrollBy({left:l*c,behavior:"smooth"})}),r.pipe(W(p),b(c=>n.includes(c))).subscribe(c=>c.click()),o.classList.add("tabbed-labels--linked");for(let c of n){let l=R(`label[for="${c.id}"]`);l.replaceChildren(x("a",{href:`#${l.htmlFor}`,tabIndex:-1},...Array.from(l.childNodes))),h(l.firstElementChild,"click").pipe(W(p),b(f=>!(f.metaKey||f.ctrlKey)),w(f=>{f.preventDefault(),f.stopPropagation()})).subscribe(()=>{history.replaceState({},"",`#${l.htmlFor}`),l.click()})}return B("content.tabs.link")&&s.pipe(Ce(1),re(t)).subscribe(([{active:c},{offset:l}])=>{let f=c.innerText.trim();if(c.hasAttribute("data-md-switching"))c.removeAttribute("data-md-switching");else{let u=e.offsetTop-l.y;for(let y of P("[data-tabs]"))for(let L of P(":scope > input",y)){let X=R(`label[for="${L.id}"]`);if(X!==c&&X.innerText.trim()===f){X.setAttribute("data-md-switching",""),L.click();break}}window.scrollTo({top:e.offsetTop-u});let d=__md_get("__tabs")||[];__md_set("__tabs",[...new Set([f,...d])])}}),s.pipe(W(p)).subscribe(()=>{for(let c of P("audio, video",e))c.pause()}),Ya(n).pipe(w(c=>s.next(c)),_(()=>s.complete()),m(c=>$({ref:e},c)))}).pipe(Ke(se))}function zn(e,{viewport$:t,target$:r,print$:o}){return O(...P(".annotate:not(.highlight)",e).map(n=>Pn(n,{target$:r,print$:o})),...P("pre:not(.mermaid) > code",e).map(n=>jn(n,{target$:r,print$:o})),...P("pre.mermaid",e).map(n=>Wn(n)),...P("table:not([class])",e).map(n=>Vn(n)),...P("details",e).map(n=>Fn(n,{target$:r,print$:o})),...P("[data-tabs]",e).map(n=>Nn(n,{viewport$:t,target$:r})),...P("[title]",e).filter(()=>B("content.tooltips")).map(n=>mt(n,{viewport$:t})))}function Ba(e,{alert$:t}){return t.pipe(v(r=>O(I(!0),I(!1).pipe(Ge(2e3))).pipe(m(o=>({message:r,active:o})))))}function qn(e,t){let r=R(".md-typeset",e);return C(()=>{let o=new g;return o.subscribe(({message:n,active:i})=>{e.classList.toggle("md-dialog--active",i),r.textContent=n}),Ba(e,t).pipe(w(n=>o.next(n)),_(()=>o.complete()),m(n=>$({ref:e},n)))})}var Ga=0;function Ja(e,t){document.body.append(e);let{width:r}=ce(e);e.style.setProperty("--md-tooltip-width",`${r}px`),e.remove();let o=cr(t),n=typeof o!="undefined"?Ne(o):I({x:0,y:0}),i=O(et(t),$t(t)).pipe(K());return z([i,n]).pipe(m(([a,s])=>{let{x:p,y:c}=Ve(t),l=ce(t),f=t.closest("table");return f&&t.parentElement&&(p+=f.offsetLeft+t.parentElement.offsetLeft,c+=f.offsetTop+t.parentElement.offsetTop),{active:a,offset:{x:p-s.x+l.width/2-r/2,y:c-s.y+l.height+8}}}))}function Qn(e){let t=e.title;if(!t.length)return S;let r=`__tooltip_${Ga++}`,o=Rt(r,"inline"),n=R(".md-typeset",o);return n.innerHTML=t,C(()=>{let i=new g;return i.subscribe({next({offset:a}){o.style.setProperty("--md-tooltip-x",`${a.x}px`),o.style.setProperty("--md-tooltip-y",`${a.y}px`)},complete(){o.style.removeProperty("--md-tooltip-x"),o.style.removeProperty("--md-tooltip-y")}}),O(i.pipe(b(({active:a})=>a)),i.pipe(_e(250),b(({active:a})=>!a))).subscribe({next({active:a}){a?(e.insertAdjacentElement("afterend",o),e.setAttribute("aria-describedby",r),e.removeAttribute("title")):(o.remove(),e.removeAttribute("aria-describedby"),e.setAttribute("title",t))},complete(){o.remove(),e.removeAttribute("aria-describedby"),e.setAttribute("title",t)}}),i.pipe(Me(16,me)).subscribe(({active:a})=>{o.classList.toggle("md-tooltip--active",a)}),i.pipe(pt(125,me),b(()=>!!e.offsetParent),m(()=>e.offsetParent.getBoundingClientRect()),m(({x:a})=>a)).subscribe({next(a){a?o.style.setProperty("--md-tooltip-0",`${-a}px`):o.style.removeProperty("--md-tooltip-0")},complete(){o.style.removeProperty("--md-tooltip-0")}}),Ja(o,e).pipe(w(a=>i.next(a)),_(()=>i.complete()),m(a=>$({ref:e},a)))}).pipe(Ke(se))}function Xa({viewport$:e}){if(!B("header.autohide"))return I(!1);let t=e.pipe(m(({offset:{y:n}})=>n),Be(2,1),m(([n,i])=>[nMath.abs(i-n.y)>100),m(([,[n]])=>n),K()),o=ze("search");return z([e,o]).pipe(m(([{offset:n},i])=>n.y>400&&!i),K(),v(n=>n?r:I(!1)),Q(!1))}function Kn(e,t){return C(()=>z([ge(e),Xa(t)])).pipe(m(([{height:r},o])=>({height:r,hidden:o})),K((r,o)=>r.height===o.height&&r.hidden===o.hidden),G(1))}function Yn(e,{header$:t,main$:r}){return C(()=>{let o=new g,n=o.pipe(Z(),ie(!0));o.pipe(ee("active"),He(t)).subscribe(([{active:a},{hidden:s}])=>{e.classList.toggle("md-header--shadow",a&&!s),e.hidden=s});let i=ue(P("[title]",e)).pipe(b(()=>B("content.tooltips")),ne(a=>Qn(a)));return r.subscribe(o),t.pipe(W(n),m(a=>$({ref:e},a)),Re(i.pipe(W(n))))})}function Za(e,{viewport$:t,header$:r}){return mr(e,{viewport$:t,header$:r}).pipe(m(({offset:{y:o}})=>{let{height:n}=ce(e);return{active:o>=n}}),ee("active"))}function Bn(e,t){return C(()=>{let r=new g;r.subscribe({next({active:n}){e.classList.toggle("md-header__title--active",n)},complete(){e.classList.remove("md-header__title--active")}});let o=fe(".md-content h1");return typeof o=="undefined"?S:Za(o,t).pipe(w(n=>r.next(n)),_(()=>r.complete()),m(n=>$({ref:e},n)))})}function Gn(e,{viewport$:t,header$:r}){let o=r.pipe(m(({height:i})=>i),K()),n=o.pipe(v(()=>ge(e).pipe(m(({height:i})=>({top:e.offsetTop,bottom:e.offsetTop+i})),ee("bottom"))));return z([o,n,t]).pipe(m(([i,{top:a,bottom:s},{offset:{y:p},size:{height:c}}])=>(c=Math.max(0,c-Math.max(0,a-p,i)-Math.max(0,c+p-s)),{offset:a-i,height:c,active:a-i<=p})),K((i,a)=>i.offset===a.offset&&i.height===a.height&&i.active===a.active))}function es(e){let t=__md_get("__palette")||{index:e.findIndex(o=>matchMedia(o.getAttribute("data-md-color-media")).matches)},r=Math.max(0,Math.min(t.index,e.length-1));return I(...e).pipe(ne(o=>h(o,"change").pipe(m(()=>o))),Q(e[r]),m(o=>({index:e.indexOf(o),color:{media:o.getAttribute("data-md-color-media"),scheme:o.getAttribute("data-md-color-scheme"),primary:o.getAttribute("data-md-color-primary"),accent:o.getAttribute("data-md-color-accent")}})),G(1))}function Jn(e){let t=P("input",e),r=x("meta",{name:"theme-color"});document.head.appendChild(r);let o=x("meta",{name:"color-scheme"});document.head.appendChild(o);let n=Pt("(prefers-color-scheme: light)");return C(()=>{let i=new g;return i.subscribe(a=>{if(document.body.setAttribute("data-md-color-switching",""),a.color.media==="(prefers-color-scheme)"){let s=matchMedia("(prefers-color-scheme: light)"),p=document.querySelector(s.matches?"[data-md-color-media='(prefers-color-scheme: light)']":"[data-md-color-media='(prefers-color-scheme: dark)']");a.color.scheme=p.getAttribute("data-md-color-scheme"),a.color.primary=p.getAttribute("data-md-color-primary"),a.color.accent=p.getAttribute("data-md-color-accent")}for(let[s,p]of Object.entries(a.color))document.body.setAttribute(`data-md-color-${s}`,p);for(let s=0;sa.key==="Enter"),re(i,(a,s)=>s)).subscribe(({index:a})=>{a=(a+1)%t.length,t[a].click(),t[a].focus()}),i.pipe(m(()=>{let a=Se("header"),s=window.getComputedStyle(a);return o.content=s.colorScheme,s.backgroundColor.match(/\d+/g).map(p=>(+p).toString(16).padStart(2,"0")).join("")})).subscribe(a=>r.content=`#${a}`),i.pipe(ve(se)).subscribe(()=>{document.body.removeAttribute("data-md-color-switching")}),es(t).pipe(W(n.pipe(Ce(1))),ct(),w(a=>i.next(a)),_(()=>i.complete()),m(a=>$({ref:e},a)))})}function Xn(e,{progress$:t}){return C(()=>{let r=new g;return r.subscribe(({value:o})=>{e.style.setProperty("--md-progress-value",`${o}`)}),t.pipe(w(o=>r.next({value:o})),_(()=>r.complete()),m(o=>({ref:e,value:o})))})}var Jr=Mt(Br());function ts(e){e.setAttribute("data-md-copying","");let t=e.closest("[data-copy]"),r=t?t.getAttribute("data-copy"):e.innerText;return e.removeAttribute("data-md-copying"),r.trimEnd()}function Zn({alert$:e}){Jr.default.isSupported()&&new j(t=>{new Jr.default("[data-clipboard-target], [data-clipboard-text]",{text:r=>r.getAttribute("data-clipboard-text")||ts(R(r.getAttribute("data-clipboard-target")))}).on("success",r=>t.next(r))}).pipe(w(t=>{t.trigger.focus()}),m(()=>Ee("clipboard.copied"))).subscribe(e)}function ei(e,t){return e.protocol=t.protocol,e.hostname=t.hostname,e}function rs(e,t){let r=new Map;for(let o of P("url",e)){let n=R("loc",o),i=[ei(new URL(n.textContent),t)];r.set(`${i[0]}`,i);for(let a of P("[rel=alternate]",o)){let s=a.getAttribute("href");s!=null&&i.push(ei(new URL(s),t))}}return r}function ur(e){return un(new URL("sitemap.xml",e)).pipe(m(t=>rs(t,new URL(e))),de(()=>I(new Map)))}function os(e,t){if(!(e.target instanceof Element))return S;let r=e.target.closest("a");if(r===null)return S;if(r.target||e.metaKey||e.ctrlKey)return S;let o=new URL(r.href);return o.search=o.hash="",t.has(`${o}`)?(e.preventDefault(),I(new URL(r.href))):S}function ti(e){let t=new Map;for(let r of P(":scope > *",e.head))t.set(r.outerHTML,r);return t}function ri(e){for(let t of P("[href], [src]",e))for(let r of["href","src"]){let o=t.getAttribute(r);if(o&&!/^(?:[a-z]+:)?\/\//i.test(o)){t[r]=t[r];break}}return I(e)}function ns(e){for(let o of["[data-md-component=announce]","[data-md-component=container]","[data-md-component=header-topic]","[data-md-component=outdated]","[data-md-component=logo]","[data-md-component=skip]",...B("navigation.tabs.sticky")?["[data-md-component=tabs]"]:[]]){let n=fe(o),i=fe(o,e);typeof n!="undefined"&&typeof i!="undefined"&&n.replaceWith(i)}let t=ti(document);for(let[o,n]of ti(e))t.has(o)?t.delete(o):document.head.appendChild(n);for(let o of t.values()){let n=o.getAttribute("name");n!=="theme-color"&&n!=="color-scheme"&&o.remove()}let r=Se("container");return We(P("script",r)).pipe(v(o=>{let n=e.createElement("script");if(o.src){for(let i of o.getAttributeNames())n.setAttribute(i,o.getAttribute(i));return o.replaceWith(n),new j(i=>{n.onload=()=>i.complete()})}else return n.textContent=o.textContent,o.replaceWith(n),S}),Z(),ie(document))}function oi({location$:e,viewport$:t,progress$:r}){let o=xe();if(location.protocol==="file:")return S;let n=ur(o.base);I(document).subscribe(ri);let i=h(document.body,"click").pipe(He(n),v(([p,c])=>os(p,c)),pe()),a=h(window,"popstate").pipe(m(ye),pe());i.pipe(re(t)).subscribe(([p,{offset:c}])=>{history.replaceState(c,""),history.pushState(null,"",p)}),O(i,a).subscribe(e);let s=e.pipe(ee("pathname"),v(p=>fn(p,{progress$:r}).pipe(de(()=>(lt(p,!0),S)))),v(ri),v(ns),pe());return O(s.pipe(re(e,(p,c)=>c)),s.pipe(v(()=>e),ee("pathname"),v(()=>e),ee("hash")),e.pipe(K((p,c)=>p.pathname===c.pathname&&p.hash===c.hash),v(()=>i),w(()=>history.back()))).subscribe(p=>{var c,l;history.state!==null||!p.hash?window.scrollTo(0,(l=(c=history.state)==null?void 0:c.y)!=null?l:0):(history.scrollRestoration="auto",pn(p.hash),history.scrollRestoration="manual")}),e.subscribe(()=>{history.scrollRestoration="manual"}),h(window,"beforeunload").subscribe(()=>{history.scrollRestoration="auto"}),t.pipe(ee("offset"),_e(100)).subscribe(({offset:p})=>{history.replaceState(p,"")}),s}var ni=Mt(qr());function ii(e){let t=e.separator.split("|").map(n=>n.replace(/(\(\?[!=<][^)]+\))/g,"").length===0?"\uFFFD":n).join("|"),r=new RegExp(t,"img"),o=(n,i,a)=>`${i}${a}`;return n=>{n=n.replace(/[\s*+\-:~^]+/g," ").trim();let i=new RegExp(`(^|${e.separator}|)(${n.replace(/[|\\{}()[\]^$+*?.-]/g,"\\$&").replace(r,"|")})`,"img");return a=>(0,ni.default)(a).replace(i,o).replace(/<\/mark>(\s+)]*>/img,"$1")}}function jt(e){return e.type===1}function dr(e){return e.type===3}function ai(e,t){let r=yn(e);return O(I(location.protocol!=="file:"),ze("search")).pipe(Ae(o=>o),v(()=>t)).subscribe(({config:o,docs:n})=>r.next({type:0,data:{config:o,docs:n,options:{suggest:B("search.suggest")}}})),r}function si(e){var l;let{selectedVersionSitemap:t,selectedVersionBaseURL:r,currentLocation:o,currentBaseURL:n}=e,i=(l=Xr(n))==null?void 0:l.pathname;if(i===void 0)return;let a=ss(o.pathname,i);if(a===void 0)return;let s=ps(t.keys());if(!t.has(s))return;let p=Xr(a,s);if(!p||!t.has(p.href))return;let c=Xr(a,r);if(c)return c.hash=o.hash,c.search=o.search,c}function Xr(e,t){try{return new URL(e,t)}catch(r){return}}function ss(e,t){if(e.startsWith(t))return e.slice(t.length)}function cs(e,t){let r=Math.min(e.length,t.length),o;for(o=0;oS)),o=r.pipe(m(n=>{let[,i]=t.base.match(/([^/]+)\/?$/);return n.find(({version:a,aliases:s})=>a===i||s.includes(i))||n[0]}));r.pipe(m(n=>new Map(n.map(i=>[`${new URL(`../${i.version}/`,t.base)}`,i]))),v(n=>h(document.body,"click").pipe(b(i=>!i.metaKey&&!i.ctrlKey),re(o),v(([i,a])=>{if(i.target instanceof Element){let s=i.target.closest("a");if(s&&!s.target&&n.has(s.href)){let p=s.href;return!i.target.closest(".md-version")&&n.get(p)===a?S:(i.preventDefault(),I(new URL(p)))}}return S}),v(i=>ur(i).pipe(m(a=>{var s;return(s=si({selectedVersionSitemap:a,selectedVersionBaseURL:i,currentLocation:ye(),currentBaseURL:t.base}))!=null?s:i})))))).subscribe(n=>lt(n,!0)),z([r,o]).subscribe(([n,i])=>{R(".md-header__topic").appendChild(Cn(n,i))}),e.pipe(v(()=>o)).subscribe(n=>{var a;let i=__md_get("__outdated",sessionStorage);if(i===null){i=!0;let s=((a=t.version)==null?void 0:a.default)||"latest";Array.isArray(s)||(s=[s]);e:for(let p of s)for(let c of n.aliases.concat(n.version))if(new RegExp(p,"i").test(c)){i=!1;break e}__md_set("__outdated",i,sessionStorage)}if(i)for(let s of ae("outdated"))s.hidden=!1})}function ls(e,{worker$:t}){let{searchParams:r}=ye();r.has("q")&&(Je("search",!0),e.value=r.get("q"),e.focus(),ze("search").pipe(Ae(i=>!i)).subscribe(()=>{let i=ye();i.searchParams.delete("q"),history.replaceState({},"",`${i}`)}));let o=et(e),n=O(t.pipe(Ae(jt)),h(e,"keyup"),o).pipe(m(()=>e.value),K());return z([n,o]).pipe(m(([i,a])=>({value:i,focus:a})),G(1))}function pi(e,{worker$:t}){let r=new g,o=r.pipe(Z(),ie(!0));z([t.pipe(Ae(jt)),r],(i,a)=>a).pipe(ee("value")).subscribe(({value:i})=>t.next({type:2,data:i})),r.pipe(ee("focus")).subscribe(({focus:i})=>{i&&Je("search",i)}),h(e.form,"reset").pipe(W(o)).subscribe(()=>e.focus());let n=R("header [for=__search]");return h(n,"click").subscribe(()=>e.focus()),ls(e,{worker$:t}).pipe(w(i=>r.next(i)),_(()=>r.complete()),m(i=>$({ref:e},i)),G(1))}function li(e,{worker$:t,query$:r}){let o=new g,n=on(e.parentElement).pipe(b(Boolean)),i=e.parentElement,a=R(":scope > :first-child",e),s=R(":scope > :last-child",e);ze("search").subscribe(l=>s.setAttribute("role",l?"list":"presentation")),o.pipe(re(r),Wr(t.pipe(Ae(jt)))).subscribe(([{items:l},{value:f}])=>{switch(l.length){case 0:a.textContent=f.length?Ee("search.result.none"):Ee("search.result.placeholder");break;case 1:a.textContent=Ee("search.result.one");break;default:let u=sr(l.length);a.textContent=Ee("search.result.other",u)}});let p=o.pipe(w(()=>s.innerHTML=""),v(({items:l})=>O(I(...l.slice(0,10)),I(...l.slice(10)).pipe(Be(4),Vr(n),v(([f])=>f)))),m(Mn),pe());return p.subscribe(l=>s.appendChild(l)),p.pipe(ne(l=>{let f=fe("details",l);return typeof f=="undefined"?S:h(f,"toggle").pipe(W(o),m(()=>f))})).subscribe(l=>{l.open===!1&&l.offsetTop<=i.scrollTop&&i.scrollTo({top:l.offsetTop})}),t.pipe(b(dr),m(({data:l})=>l)).pipe(w(l=>o.next(l)),_(()=>o.complete()),m(l=>$({ref:e},l)))}function ms(e,{query$:t}){return t.pipe(m(({value:r})=>{let o=ye();return o.hash="",r=r.replace(/\s+/g,"+").replace(/&/g,"%26").replace(/=/g,"%3D"),o.search=`q=${r}`,{url:o}}))}function mi(e,t){let r=new g,o=r.pipe(Z(),ie(!0));return r.subscribe(({url:n})=>{e.setAttribute("data-clipboard-text",e.href),e.href=`${n}`}),h(e,"click").pipe(W(o)).subscribe(n=>n.preventDefault()),ms(e,t).pipe(w(n=>r.next(n)),_(()=>r.complete()),m(n=>$({ref:e},n)))}function fi(e,{worker$:t,keyboard$:r}){let o=new g,n=Se("search-query"),i=O(h(n,"keydown"),h(n,"focus")).pipe(ve(se),m(()=>n.value),K());return o.pipe(He(i),m(([{suggest:s},p])=>{let c=p.split(/([\s-]+)/);if(s!=null&&s.length&&c[c.length-1]){let l=s[s.length-1];l.startsWith(c[c.length-1])&&(c[c.length-1]=l)}else c.length=0;return c})).subscribe(s=>e.innerHTML=s.join("").replace(/\s/g," ")),r.pipe(b(({mode:s})=>s==="search")).subscribe(s=>{switch(s.type){case"ArrowRight":e.innerText.length&&n.selectionStart===n.value.length&&(n.value=e.innerText);break}}),t.pipe(b(dr),m(({data:s})=>s)).pipe(w(s=>o.next(s)),_(()=>o.complete()),m(()=>({ref:e})))}function ui(e,{index$:t,keyboard$:r}){let o=xe();try{let n=ai(o.search,t),i=Se("search-query",e),a=Se("search-result",e);h(e,"click").pipe(b(({target:p})=>p instanceof Element&&!!p.closest("a"))).subscribe(()=>Je("search",!1)),r.pipe(b(({mode:p})=>p==="search")).subscribe(p=>{let c=Ie();switch(p.type){case"Enter":if(c===i){let l=new Map;for(let f of P(":first-child [href]",a)){let u=f.firstElementChild;l.set(f,parseFloat(u.getAttribute("data-md-score")))}if(l.size){let[[f]]=[...l].sort(([,u],[,d])=>d-u);f.click()}p.claim()}break;case"Escape":case"Tab":Je("search",!1),i.blur();break;case"ArrowUp":case"ArrowDown":if(typeof c=="undefined")i.focus();else{let l=[i,...P(":not(details) > [href], summary, details[open] [href]",a)],f=Math.max(0,(Math.max(0,l.indexOf(c))+l.length+(p.type==="ArrowUp"?-1:1))%l.length);l[f].focus()}p.claim();break;default:i!==Ie()&&i.focus()}}),r.pipe(b(({mode:p})=>p==="global")).subscribe(p=>{switch(p.type){case"f":case"s":case"/":i.focus(),i.select(),p.claim();break}});let s=pi(i,{worker$:n});return O(s,li(a,{worker$:n,query$:s})).pipe(Re(...ae("search-share",e).map(p=>mi(p,{query$:s})),...ae("search-suggest",e).map(p=>fi(p,{worker$:n,keyboard$:r}))))}catch(n){return e.hidden=!0,Ye}}function di(e,{index$:t,location$:r}){return z([t,r.pipe(Q(ye()),b(o=>!!o.searchParams.get("h")))]).pipe(m(([o,n])=>ii(o.config)(n.searchParams.get("h"))),m(o=>{var a;let n=new Map,i=document.createNodeIterator(e,NodeFilter.SHOW_TEXT);for(let s=i.nextNode();s;s=i.nextNode())if((a=s.parentElement)!=null&&a.offsetHeight){let p=s.textContent,c=o(p);c.length>p.length&&n.set(s,c)}for(let[s,p]of n){let{childNodes:c}=x("span",null,p);s.replaceWith(...Array.from(c))}return{ref:e,nodes:n}}))}function fs(e,{viewport$:t,main$:r}){let o=e.closest(".md-grid"),n=o.offsetTop-o.parentElement.offsetTop;return z([r,t]).pipe(m(([{offset:i,height:a},{offset:{y:s}}])=>(a=a+Math.min(n,Math.max(0,s-i))-n,{height:a,locked:s>=i+n})),K((i,a)=>i.height===a.height&&i.locked===a.locked))}function Zr(e,o){var n=o,{header$:t}=n,r=so(n,["header$"]);let i=R(".md-sidebar__scrollwrap",e),{y:a}=Ve(i);return C(()=>{let s=new g,p=s.pipe(Z(),ie(!0)),c=s.pipe(Me(0,me));return c.pipe(re(t)).subscribe({next([{height:l},{height:f}]){i.style.height=`${l-2*a}px`,e.style.top=`${f}px`},complete(){i.style.height="",e.style.top=""}}),c.pipe(Ae()).subscribe(()=>{for(let l of P(".md-nav__link--active[href]",e)){if(!l.clientHeight)continue;let f=l.closest(".md-sidebar__scrollwrap");if(typeof f!="undefined"){let u=l.offsetTop-f.offsetTop,{height:d}=ce(f);f.scrollTo({top:u-d/2})}}}),ue(P("label[tabindex]",e)).pipe(ne(l=>h(l,"click").pipe(ve(se),m(()=>l),W(p)))).subscribe(l=>{let f=R(`[id="${l.htmlFor}"]`);R(`[aria-labelledby="${l.id}"]`).setAttribute("aria-expanded",`${f.checked}`)}),fs(e,r).pipe(w(l=>s.next(l)),_(()=>s.complete()),m(l=>$({ref:e},l)))})}function hi(e,t){if(typeof t!="undefined"){let r=`https://api.github.com/repos/${e}/${t}`;return st(je(`${r}/releases/latest`).pipe(de(()=>S),m(o=>({version:o.tag_name})),De({})),je(r).pipe(de(()=>S),m(o=>({stars:o.stargazers_count,forks:o.forks_count})),De({}))).pipe(m(([o,n])=>$($({},o),n)))}else{let r=`https://api.github.com/users/${e}`;return je(r).pipe(m(o=>({repositories:o.public_repos})),De({}))}}function bi(e,t){let r=`https://${e}/api/v4/projects/${encodeURIComponent(t)}`;return st(je(`${r}/releases/permalink/latest`).pipe(de(()=>S),m(({tag_name:o})=>({version:o})),De({})),je(r).pipe(de(()=>S),m(({star_count:o,forks_count:n})=>({stars:o,forks:n})),De({}))).pipe(m(([o,n])=>$($({},o),n)))}function vi(e){let t=e.match(/^.+github\.com\/([^/]+)\/?([^/]+)?/i);if(t){let[,r,o]=t;return hi(r,o)}if(t=e.match(/^.+?([^/]*gitlab[^/]+)\/(.+?)\/?$/i),t){let[,r,o]=t;return bi(r,o)}return S}var us;function ds(e){return us||(us=C(()=>{let t=__md_get("__source",sessionStorage);if(t)return I(t);if(ae("consent").length){let o=__md_get("__consent");if(!(o&&o.github))return S}return vi(e.href).pipe(w(o=>__md_set("__source",o,sessionStorage)))}).pipe(de(()=>S),b(t=>Object.keys(t).length>0),m(t=>({facts:t})),G(1)))}function gi(e){let t=R(":scope > :last-child",e);return C(()=>{let r=new g;return r.subscribe(({facts:o})=>{t.appendChild(_n(o)),t.classList.add("md-source__repository--active")}),ds(e).pipe(w(o=>r.next(o)),_(()=>r.complete()),m(o=>$({ref:e},o)))})}function hs(e,{viewport$:t,header$:r}){return ge(document.body).pipe(v(()=>mr(e,{header$:r,viewport$:t})),m(({offset:{y:o}})=>({hidden:o>=10})),ee("hidden"))}function yi(e,t){return C(()=>{let r=new g;return r.subscribe({next({hidden:o}){e.hidden=o},complete(){e.hidden=!1}}),(B("navigation.tabs.sticky")?I({hidden:!1}):hs(e,t)).pipe(w(o=>r.next(o)),_(()=>r.complete()),m(o=>$({ref:e},o)))})}function bs(e,{viewport$:t,header$:r}){let o=new Map,n=P(".md-nav__link",e);for(let s of n){let p=decodeURIComponent(s.hash.substring(1)),c=fe(`[id="${p}"]`);typeof c!="undefined"&&o.set(s,c)}let i=r.pipe(ee("height"),m(({height:s})=>{let p=Se("main"),c=R(":scope > :first-child",p);return s+.8*(c.offsetTop-p.offsetTop)}),pe());return ge(document.body).pipe(ee("height"),v(s=>C(()=>{let p=[];return I([...o].reduce((c,[l,f])=>{for(;p.length&&o.get(p[p.length-1]).tagName>=f.tagName;)p.pop();let u=f.offsetTop;for(;!u&&f.parentElement;)f=f.parentElement,u=f.offsetTop;let d=f.offsetParent;for(;d;d=d.offsetParent)u+=d.offsetTop;return c.set([...p=[...p,l]].reverse(),u)},new Map))}).pipe(m(p=>new Map([...p].sort(([,c],[,l])=>c-l))),He(i),v(([p,c])=>t.pipe(Fr(([l,f],{offset:{y:u},size:d})=>{let y=u+d.height>=Math.floor(s.height);for(;f.length;){let[,L]=f[0];if(L-c=u&&!y)f=[l.pop(),...f];else break}return[l,f]},[[],[...p]]),K((l,f)=>l[0]===f[0]&&l[1]===f[1])))))).pipe(m(([s,p])=>({prev:s.map(([c])=>c),next:p.map(([c])=>c)})),Q({prev:[],next:[]}),Be(2,1),m(([s,p])=>s.prev.length{let i=new g,a=i.pipe(Z(),ie(!0));if(i.subscribe(({prev:s,next:p})=>{for(let[c]of p)c.classList.remove("md-nav__link--passed"),c.classList.remove("md-nav__link--active");for(let[c,[l]]of s.entries())l.classList.add("md-nav__link--passed"),l.classList.toggle("md-nav__link--active",c===s.length-1)}),B("toc.follow")){let s=O(t.pipe(_e(1),m(()=>{})),t.pipe(_e(250),m(()=>"smooth")));i.pipe(b(({prev:p})=>p.length>0),He(o.pipe(ve(se))),re(s)).subscribe(([[{prev:p}],c])=>{let[l]=p[p.length-1];if(l.offsetHeight){let f=cr(l);if(typeof f!="undefined"){let u=l.offsetTop-f.offsetTop,{height:d}=ce(f);f.scrollTo({top:u-d/2,behavior:c})}}})}return B("navigation.tracking")&&t.pipe(W(a),ee("offset"),_e(250),Ce(1),W(n.pipe(Ce(1))),ct({delay:250}),re(i)).subscribe(([,{prev:s}])=>{let p=ye(),c=s[s.length-1];if(c&&c.length){let[l]=c,{hash:f}=new URL(l.href);p.hash!==f&&(p.hash=f,history.replaceState({},"",`${p}`))}else p.hash="",history.replaceState({},"",`${p}`)}),bs(e,{viewport$:t,header$:r}).pipe(w(s=>i.next(s)),_(()=>i.complete()),m(s=>$({ref:e},s)))})}function vs(e,{viewport$:t,main$:r,target$:o}){let n=t.pipe(m(({offset:{y:a}})=>a),Be(2,1),m(([a,s])=>a>s&&s>0),K()),i=r.pipe(m(({active:a})=>a));return z([i,n]).pipe(m(([a,s])=>!(a&&s)),K(),W(o.pipe(Ce(1))),ie(!0),ct({delay:250}),m(a=>({hidden:a})))}function Ei(e,{viewport$:t,header$:r,main$:o,target$:n}){let i=new g,a=i.pipe(Z(),ie(!0));return i.subscribe({next({hidden:s}){e.hidden=s,s?(e.setAttribute("tabindex","-1"),e.blur()):e.removeAttribute("tabindex")},complete(){e.style.top="",e.hidden=!0,e.removeAttribute("tabindex")}}),r.pipe(W(a),ee("height")).subscribe(({height:s})=>{e.style.top=`${s+16}px`}),h(e,"click").subscribe(s=>{s.preventDefault(),window.scrollTo({top:0})}),vs(e,{viewport$:t,main$:o,target$:n}).pipe(w(s=>i.next(s)),_(()=>i.complete()),m(s=>$({ref:e},s)))}function wi({document$:e,viewport$:t}){e.pipe(v(()=>P(".md-ellipsis")),ne(r=>tt(r).pipe(W(e.pipe(Ce(1))),b(o=>o),m(()=>r),Te(1))),b(r=>r.offsetWidth{let o=r.innerText,n=r.closest("a")||r;return n.title=o,B("content.tooltips")?mt(n,{viewport$:t}).pipe(W(e.pipe(Ce(1))),_(()=>n.removeAttribute("title"))):S})).subscribe(),B("content.tooltips")&&e.pipe(v(()=>P(".md-status")),ne(r=>mt(r,{viewport$:t}))).subscribe()}function Ti({document$:e,tablet$:t}){e.pipe(v(()=>P(".md-toggle--indeterminate")),w(r=>{r.indeterminate=!0,r.checked=!1}),ne(r=>h(r,"change").pipe(Dr(()=>r.classList.contains("md-toggle--indeterminate")),m(()=>r))),re(t)).subscribe(([r,o])=>{r.classList.remove("md-toggle--indeterminate"),o&&(r.checked=!1)})}function gs(){return/(iPad|iPhone|iPod)/.test(navigator.userAgent)}function Si({document$:e}){e.pipe(v(()=>P("[data-md-scrollfix]")),w(t=>t.removeAttribute("data-md-scrollfix")),b(gs),ne(t=>h(t,"touchstart").pipe(m(()=>t)))).subscribe(t=>{let r=t.scrollTop;r===0?t.scrollTop=1:r+t.offsetHeight===t.scrollHeight&&(t.scrollTop=r-1)})}function Oi({viewport$:e,tablet$:t}){z([ze("search"),t]).pipe(m(([r,o])=>r&&!o),v(r=>I(r).pipe(Ge(r?400:100))),re(e)).subscribe(([r,{offset:{y:o}}])=>{if(r)document.body.setAttribute("data-md-scrolllock",""),document.body.style.top=`-${o}px`;else{let n=-1*parseInt(document.body.style.top,10);document.body.removeAttribute("data-md-scrolllock"),document.body.style.top="",n&&window.scrollTo(0,n)}})}Object.entries||(Object.entries=function(e){let t=[];for(let r of Object.keys(e))t.push([r,e[r]]);return t});Object.values||(Object.values=function(e){let t=[];for(let r of Object.keys(e))t.push(e[r]);return t});typeof Element!="undefined"&&(Element.prototype.scrollTo||(Element.prototype.scrollTo=function(e,t){typeof e=="object"?(this.scrollLeft=e.left,this.scrollTop=e.top):(this.scrollLeft=e,this.scrollTop=t)}),Element.prototype.replaceWith||(Element.prototype.replaceWith=function(...e){let t=this.parentNode;if(t){e.length===0&&t.removeChild(this);for(let r=e.length-1;r>=0;r--){let o=e[r];typeof o=="string"?o=document.createTextNode(o):o.parentNode&&o.parentNode.removeChild(o),r?t.insertBefore(this.previousSibling,o):t.replaceChild(o,this)}}}));function ys(){return location.protocol==="file:"?Tt(`${new URL("search/search_index.js",eo.base)}`).pipe(m(()=>__index),G(1)):je(new URL("search/search_index.json",eo.base))}document.documentElement.classList.remove("no-js");document.documentElement.classList.add("js");var ot=Go(),Ut=sn(),Lt=ln(Ut),to=an(),Oe=gn(),hr=Pt("(min-width: 960px)"),Mi=Pt("(min-width: 1220px)"),_i=mn(),eo=xe(),Ai=document.forms.namedItem("search")?ys():Ye,ro=new g;Zn({alert$:ro});var oo=new g;B("navigation.instant")&&oi({location$:Ut,viewport$:Oe,progress$:oo}).subscribe(ot);var Li;((Li=eo.version)==null?void 0:Li.provider)==="mike"&&ci({document$:ot});O(Ut,Lt).pipe(Ge(125)).subscribe(()=>{Je("drawer",!1),Je("search",!1)});to.pipe(b(({mode:e})=>e==="global")).subscribe(e=>{switch(e.type){case"p":case",":let t=fe("link[rel=prev]");typeof t!="undefined"&<(t);break;case"n":case".":let r=fe("link[rel=next]");typeof r!="undefined"&<(r);break;case"Enter":let o=Ie();o instanceof HTMLLabelElement&&o.click()}});wi({viewport$:Oe,document$:ot});Ti({document$:ot,tablet$:hr});Si({document$:ot});Oi({viewport$:Oe,tablet$:hr});var rt=Kn(Se("header"),{viewport$:Oe}),Ft=ot.pipe(m(()=>Se("main")),v(e=>Gn(e,{viewport$:Oe,header$:rt})),G(1)),xs=O(...ae("consent").map(e=>En(e,{target$:Lt})),...ae("dialog").map(e=>qn(e,{alert$:ro})),...ae("palette").map(e=>Jn(e)),...ae("progress").map(e=>Xn(e,{progress$:oo})),...ae("search").map(e=>ui(e,{index$:Ai,keyboard$:to})),...ae("source").map(e=>gi(e))),Es=C(()=>O(...ae("announce").map(e=>xn(e)),...ae("content").map(e=>zn(e,{viewport$:Oe,target$:Lt,print$:_i})),...ae("content").map(e=>B("search.highlight")?di(e,{index$:Ai,location$:Ut}):S),...ae("header").map(e=>Yn(e,{viewport$:Oe,header$:rt,main$:Ft})),...ae("header-title").map(e=>Bn(e,{viewport$:Oe,header$:rt})),...ae("sidebar").map(e=>e.getAttribute("data-md-type")==="navigation"?Nr(Mi,()=>Zr(e,{viewport$:Oe,header$:rt,main$:Ft})):Nr(hr,()=>Zr(e,{viewport$:Oe,header$:rt,main$:Ft}))),...ae("tabs").map(e=>yi(e,{viewport$:Oe,header$:rt})),...ae("toc").map(e=>xi(e,{viewport$:Oe,header$:rt,main$:Ft,target$:Lt})),...ae("top").map(e=>Ei(e,{viewport$:Oe,header$:rt,main$:Ft,target$:Lt})))),Ci=ot.pipe(v(()=>Es),Re(xs),G(1));Ci.subscribe();window.document$=ot;window.location$=Ut;window.target$=Lt;window.keyboard$=to;window.viewport$=Oe;window.tablet$=hr;window.screen$=Mi;window.print$=_i;window.alert$=ro;window.progress$=oo;window.component$=Ci;})(); +//# sourceMappingURL=bundle.83f73b43.min.js.map + diff --git a/assets/javascripts/bundle.83f73b43.min.js.map b/assets/javascripts/bundle.83f73b43.min.js.map new file mode 100644 index 0000000..fe920b7 --- /dev/null +++ b/assets/javascripts/bundle.83f73b43.min.js.map @@ -0,0 +1,7 @@ +{ + "version": 3, + "sources": ["node_modules/focus-visible/dist/focus-visible.js", "node_modules/escape-html/index.js", "node_modules/clipboard/dist/clipboard.js", "src/templates/assets/javascripts/bundle.ts", "node_modules/tslib/tslib.es6.mjs", "node_modules/rxjs/src/internal/util/isFunction.ts", "node_modules/rxjs/src/internal/util/createErrorClass.ts", "node_modules/rxjs/src/internal/util/UnsubscriptionError.ts", "node_modules/rxjs/src/internal/util/arrRemove.ts", "node_modules/rxjs/src/internal/Subscription.ts", "node_modules/rxjs/src/internal/config.ts", "node_modules/rxjs/src/internal/scheduler/timeoutProvider.ts", "node_modules/rxjs/src/internal/util/reportUnhandledError.ts", "node_modules/rxjs/src/internal/util/noop.ts", "node_modules/rxjs/src/internal/NotificationFactories.ts", "node_modules/rxjs/src/internal/util/errorContext.ts", "node_modules/rxjs/src/internal/Subscriber.ts", "node_modules/rxjs/src/internal/symbol/observable.ts", "node_modules/rxjs/src/internal/util/identity.ts", "node_modules/rxjs/src/internal/util/pipe.ts", "node_modules/rxjs/src/internal/Observable.ts", "node_modules/rxjs/src/internal/util/lift.ts", "node_modules/rxjs/src/internal/operators/OperatorSubscriber.ts", "node_modules/rxjs/src/internal/scheduler/animationFrameProvider.ts", "node_modules/rxjs/src/internal/util/ObjectUnsubscribedError.ts", "node_modules/rxjs/src/internal/Subject.ts", "node_modules/rxjs/src/internal/BehaviorSubject.ts", "node_modules/rxjs/src/internal/scheduler/dateTimestampProvider.ts", "node_modules/rxjs/src/internal/ReplaySubject.ts", "node_modules/rxjs/src/internal/scheduler/Action.ts", "node_modules/rxjs/src/internal/scheduler/intervalProvider.ts", "node_modules/rxjs/src/internal/scheduler/AsyncAction.ts", "node_modules/rxjs/src/internal/Scheduler.ts", "node_modules/rxjs/src/internal/scheduler/AsyncScheduler.ts", "node_modules/rxjs/src/internal/scheduler/async.ts", "node_modules/rxjs/src/internal/scheduler/QueueAction.ts", "node_modules/rxjs/src/internal/scheduler/QueueScheduler.ts", "node_modules/rxjs/src/internal/scheduler/queue.ts", "node_modules/rxjs/src/internal/scheduler/AnimationFrameAction.ts", "node_modules/rxjs/src/internal/scheduler/AnimationFrameScheduler.ts", "node_modules/rxjs/src/internal/scheduler/animationFrame.ts", "node_modules/rxjs/src/internal/observable/empty.ts", "node_modules/rxjs/src/internal/util/isScheduler.ts", "node_modules/rxjs/src/internal/util/args.ts", "node_modules/rxjs/src/internal/util/isArrayLike.ts", "node_modules/rxjs/src/internal/util/isPromise.ts", "node_modules/rxjs/src/internal/util/isInteropObservable.ts", "node_modules/rxjs/src/internal/util/isAsyncIterable.ts", "node_modules/rxjs/src/internal/util/throwUnobservableError.ts", "node_modules/rxjs/src/internal/symbol/iterator.ts", "node_modules/rxjs/src/internal/util/isIterable.ts", "node_modules/rxjs/src/internal/util/isReadableStreamLike.ts", "node_modules/rxjs/src/internal/observable/innerFrom.ts", "node_modules/rxjs/src/internal/util/executeSchedule.ts", "node_modules/rxjs/src/internal/operators/observeOn.ts", "node_modules/rxjs/src/internal/operators/subscribeOn.ts", "node_modules/rxjs/src/internal/scheduled/scheduleObservable.ts", "node_modules/rxjs/src/internal/scheduled/schedulePromise.ts", "node_modules/rxjs/src/internal/scheduled/scheduleArray.ts", "node_modules/rxjs/src/internal/scheduled/scheduleIterable.ts", "node_modules/rxjs/src/internal/scheduled/scheduleAsyncIterable.ts", "node_modules/rxjs/src/internal/scheduled/scheduleReadableStreamLike.ts", "node_modules/rxjs/src/internal/scheduled/scheduled.ts", "node_modules/rxjs/src/internal/observable/from.ts", "node_modules/rxjs/src/internal/observable/of.ts", "node_modules/rxjs/src/internal/observable/throwError.ts", "node_modules/rxjs/src/internal/util/EmptyError.ts", "node_modules/rxjs/src/internal/util/isDate.ts", "node_modules/rxjs/src/internal/operators/map.ts", "node_modules/rxjs/src/internal/util/mapOneOrManyArgs.ts", "node_modules/rxjs/src/internal/util/argsArgArrayOrObject.ts", "node_modules/rxjs/src/internal/util/createObject.ts", "node_modules/rxjs/src/internal/observable/combineLatest.ts", "node_modules/rxjs/src/internal/operators/mergeInternals.ts", "node_modules/rxjs/src/internal/operators/mergeMap.ts", "node_modules/rxjs/src/internal/operators/mergeAll.ts", "node_modules/rxjs/src/internal/operators/concatAll.ts", "node_modules/rxjs/src/internal/observable/concat.ts", "node_modules/rxjs/src/internal/observable/defer.ts", "node_modules/rxjs/src/internal/observable/fromEvent.ts", "node_modules/rxjs/src/internal/observable/fromEventPattern.ts", "node_modules/rxjs/src/internal/observable/timer.ts", "node_modules/rxjs/src/internal/observable/merge.ts", "node_modules/rxjs/src/internal/observable/never.ts", "node_modules/rxjs/src/internal/util/argsOrArgArray.ts", "node_modules/rxjs/src/internal/operators/filter.ts", "node_modules/rxjs/src/internal/observable/zip.ts", "node_modules/rxjs/src/internal/operators/audit.ts", "node_modules/rxjs/src/internal/operators/auditTime.ts", "node_modules/rxjs/src/internal/operators/bufferCount.ts", "node_modules/rxjs/src/internal/operators/catchError.ts", "node_modules/rxjs/src/internal/operators/scanInternals.ts", "node_modules/rxjs/src/internal/operators/combineLatest.ts", "node_modules/rxjs/src/internal/operators/combineLatestWith.ts", "node_modules/rxjs/src/internal/operators/debounce.ts", "node_modules/rxjs/src/internal/operators/debounceTime.ts", "node_modules/rxjs/src/internal/operators/defaultIfEmpty.ts", "node_modules/rxjs/src/internal/operators/take.ts", "node_modules/rxjs/src/internal/operators/ignoreElements.ts", "node_modules/rxjs/src/internal/operators/mapTo.ts", "node_modules/rxjs/src/internal/operators/delayWhen.ts", "node_modules/rxjs/src/internal/operators/delay.ts", "node_modules/rxjs/src/internal/operators/distinctUntilChanged.ts", "node_modules/rxjs/src/internal/operators/distinctUntilKeyChanged.ts", "node_modules/rxjs/src/internal/operators/throwIfEmpty.ts", "node_modules/rxjs/src/internal/operators/endWith.ts", "node_modules/rxjs/src/internal/operators/finalize.ts", "node_modules/rxjs/src/internal/operators/first.ts", "node_modules/rxjs/src/internal/operators/takeLast.ts", "node_modules/rxjs/src/internal/operators/merge.ts", "node_modules/rxjs/src/internal/operators/mergeWith.ts", "node_modules/rxjs/src/internal/operators/repeat.ts", "node_modules/rxjs/src/internal/operators/scan.ts", "node_modules/rxjs/src/internal/operators/share.ts", "node_modules/rxjs/src/internal/operators/shareReplay.ts", "node_modules/rxjs/src/internal/operators/skip.ts", "node_modules/rxjs/src/internal/operators/skipUntil.ts", "node_modules/rxjs/src/internal/operators/startWith.ts", "node_modules/rxjs/src/internal/operators/switchMap.ts", "node_modules/rxjs/src/internal/operators/takeUntil.ts", "node_modules/rxjs/src/internal/operators/takeWhile.ts", "node_modules/rxjs/src/internal/operators/tap.ts", "node_modules/rxjs/src/internal/operators/throttle.ts", "node_modules/rxjs/src/internal/operators/throttleTime.ts", "node_modules/rxjs/src/internal/operators/withLatestFrom.ts", "node_modules/rxjs/src/internal/operators/zip.ts", "node_modules/rxjs/src/internal/operators/zipWith.ts", "src/templates/assets/javascripts/browser/document/index.ts", "src/templates/assets/javascripts/browser/element/_/index.ts", "src/templates/assets/javascripts/browser/element/focus/index.ts", "src/templates/assets/javascripts/browser/element/hover/index.ts", "src/templates/assets/javascripts/utilities/h/index.ts", "src/templates/assets/javascripts/utilities/round/index.ts", "src/templates/assets/javascripts/browser/script/index.ts", "src/templates/assets/javascripts/browser/element/size/_/index.ts", "src/templates/assets/javascripts/browser/element/size/content/index.ts", "src/templates/assets/javascripts/browser/element/offset/_/index.ts", "src/templates/assets/javascripts/browser/element/offset/content/index.ts", "src/templates/assets/javascripts/browser/element/visibility/index.ts", "src/templates/assets/javascripts/browser/toggle/index.ts", "src/templates/assets/javascripts/browser/keyboard/index.ts", "src/templates/assets/javascripts/browser/location/_/index.ts", "src/templates/assets/javascripts/browser/location/hash/index.ts", "src/templates/assets/javascripts/browser/media/index.ts", "src/templates/assets/javascripts/browser/request/index.ts", "src/templates/assets/javascripts/browser/viewport/offset/index.ts", "src/templates/assets/javascripts/browser/viewport/size/index.ts", "src/templates/assets/javascripts/browser/viewport/_/index.ts", "src/templates/assets/javascripts/browser/viewport/at/index.ts", "src/templates/assets/javascripts/browser/worker/index.ts", "src/templates/assets/javascripts/_/index.ts", "src/templates/assets/javascripts/components/_/index.ts", "src/templates/assets/javascripts/components/announce/index.ts", "src/templates/assets/javascripts/components/consent/index.ts", "src/templates/assets/javascripts/templates/tooltip/index.tsx", "src/templates/assets/javascripts/templates/annotation/index.tsx", "src/templates/assets/javascripts/templates/clipboard/index.tsx", "src/templates/assets/javascripts/templates/search/index.tsx", "src/templates/assets/javascripts/templates/source/index.tsx", "src/templates/assets/javascripts/templates/tabbed/index.tsx", "src/templates/assets/javascripts/templates/table/index.tsx", "src/templates/assets/javascripts/templates/version/index.tsx", "src/templates/assets/javascripts/components/tooltip2/index.ts", "src/templates/assets/javascripts/components/content/annotation/_/index.ts", "src/templates/assets/javascripts/components/content/annotation/list/index.ts", "src/templates/assets/javascripts/components/content/annotation/block/index.ts", "src/templates/assets/javascripts/components/content/code/_/index.ts", "src/templates/assets/javascripts/components/content/details/index.ts", "src/templates/assets/javascripts/components/content/mermaid/index.css", "src/templates/assets/javascripts/components/content/mermaid/index.ts", "src/templates/assets/javascripts/components/content/table/index.ts", "src/templates/assets/javascripts/components/content/tabs/index.ts", "src/templates/assets/javascripts/components/content/_/index.ts", "src/templates/assets/javascripts/components/dialog/index.ts", "src/templates/assets/javascripts/components/tooltip/index.ts", "src/templates/assets/javascripts/components/header/_/index.ts", "src/templates/assets/javascripts/components/header/title/index.ts", "src/templates/assets/javascripts/components/main/index.ts", "src/templates/assets/javascripts/components/palette/index.ts", "src/templates/assets/javascripts/components/progress/index.ts", "src/templates/assets/javascripts/integrations/clipboard/index.ts", "src/templates/assets/javascripts/integrations/sitemap/index.ts", "src/templates/assets/javascripts/integrations/instant/index.ts", "src/templates/assets/javascripts/integrations/search/highlighter/index.ts", "src/templates/assets/javascripts/integrations/search/worker/message/index.ts", "src/templates/assets/javascripts/integrations/search/worker/_/index.ts", "src/templates/assets/javascripts/integrations/version/findurl/index.ts", "src/templates/assets/javascripts/integrations/version/index.ts", "src/templates/assets/javascripts/components/search/query/index.ts", "src/templates/assets/javascripts/components/search/result/index.ts", "src/templates/assets/javascripts/components/search/share/index.ts", "src/templates/assets/javascripts/components/search/suggest/index.ts", "src/templates/assets/javascripts/components/search/_/index.ts", "src/templates/assets/javascripts/components/search/highlight/index.ts", "src/templates/assets/javascripts/components/sidebar/index.ts", "src/templates/assets/javascripts/components/source/facts/github/index.ts", "src/templates/assets/javascripts/components/source/facts/gitlab/index.ts", "src/templates/assets/javascripts/components/source/facts/_/index.ts", "src/templates/assets/javascripts/components/source/_/index.ts", "src/templates/assets/javascripts/components/tabs/index.ts", "src/templates/assets/javascripts/components/toc/index.ts", "src/templates/assets/javascripts/components/top/index.ts", "src/templates/assets/javascripts/patches/ellipsis/index.ts", "src/templates/assets/javascripts/patches/indeterminate/index.ts", "src/templates/assets/javascripts/patches/scrollfix/index.ts", "src/templates/assets/javascripts/patches/scrolllock/index.ts", "src/templates/assets/javascripts/polyfills/index.ts"], + "sourcesContent": ["(function (global, factory) {\n typeof exports === 'object' && typeof module !== 'undefined' ? factory() :\n typeof define === 'function' && define.amd ? define(factory) :\n (factory());\n}(this, (function () { 'use strict';\n\n /**\n * Applies the :focus-visible polyfill at the given scope.\n * A scope in this case is either the top-level Document or a Shadow Root.\n *\n * @param {(Document|ShadowRoot)} scope\n * @see https://github.com/WICG/focus-visible\n */\n function applyFocusVisiblePolyfill(scope) {\n var hadKeyboardEvent = true;\n var hadFocusVisibleRecently = false;\n var hadFocusVisibleRecentlyTimeout = null;\n\n var inputTypesAllowlist = {\n text: true,\n search: true,\n url: true,\n tel: true,\n email: true,\n password: true,\n number: true,\n date: true,\n month: true,\n week: true,\n time: true,\n datetime: true,\n 'datetime-local': true\n };\n\n /**\n * Helper function for legacy browsers and iframes which sometimes focus\n * elements like document, body, and non-interactive SVG.\n * @param {Element} el\n */\n function isValidFocusTarget(el) {\n if (\n el &&\n el !== document &&\n el.nodeName !== 'HTML' &&\n el.nodeName !== 'BODY' &&\n 'classList' in el &&\n 'contains' in el.classList\n ) {\n return true;\n }\n return false;\n }\n\n /**\n * Computes whether the given element should automatically trigger the\n * `focus-visible` class being added, i.e. whether it should always match\n * `:focus-visible` when focused.\n * @param {Element} el\n * @return {boolean}\n */\n function focusTriggersKeyboardModality(el) {\n var type = el.type;\n var tagName = el.tagName;\n\n if (tagName === 'INPUT' && inputTypesAllowlist[type] && !el.readOnly) {\n return true;\n }\n\n if (tagName === 'TEXTAREA' && !el.readOnly) {\n return true;\n }\n\n if (el.isContentEditable) {\n return true;\n }\n\n return false;\n }\n\n /**\n * Add the `focus-visible` class to the given element if it was not added by\n * the author.\n * @param {Element} el\n */\n function addFocusVisibleClass(el) {\n if (el.classList.contains('focus-visible')) {\n return;\n }\n el.classList.add('focus-visible');\n el.setAttribute('data-focus-visible-added', '');\n }\n\n /**\n * Remove the `focus-visible` class from the given element if it was not\n * originally added by the author.\n * @param {Element} el\n */\n function removeFocusVisibleClass(el) {\n if (!el.hasAttribute('data-focus-visible-added')) {\n return;\n }\n el.classList.remove('focus-visible');\n el.removeAttribute('data-focus-visible-added');\n }\n\n /**\n * If the most recent user interaction was via the keyboard;\n * and the key press did not include a meta, alt/option, or control key;\n * then the modality is keyboard. Otherwise, the modality is not keyboard.\n * Apply `focus-visible` to any current active element and keep track\n * of our keyboard modality state with `hadKeyboardEvent`.\n * @param {KeyboardEvent} e\n */\n function onKeyDown(e) {\n if (e.metaKey || e.altKey || e.ctrlKey) {\n return;\n }\n\n if (isValidFocusTarget(scope.activeElement)) {\n addFocusVisibleClass(scope.activeElement);\n }\n\n hadKeyboardEvent = true;\n }\n\n /**\n * If at any point a user clicks with a pointing device, ensure that we change\n * the modality away from keyboard.\n * This avoids the situation where a user presses a key on an already focused\n * element, and then clicks on a different element, focusing it with a\n * pointing device, while we still think we're in keyboard modality.\n * @param {Event} e\n */\n function onPointerDown(e) {\n hadKeyboardEvent = false;\n }\n\n /**\n * On `focus`, add the `focus-visible` class to the target if:\n * - the target received focus as a result of keyboard navigation, or\n * - the event target is an element that will likely require interaction\n * via the keyboard (e.g. a text box)\n * @param {Event} e\n */\n function onFocus(e) {\n // Prevent IE from focusing the document or HTML element.\n if (!isValidFocusTarget(e.target)) {\n return;\n }\n\n if (hadKeyboardEvent || focusTriggersKeyboardModality(e.target)) {\n addFocusVisibleClass(e.target);\n }\n }\n\n /**\n * On `blur`, remove the `focus-visible` class from the target.\n * @param {Event} e\n */\n function onBlur(e) {\n if (!isValidFocusTarget(e.target)) {\n return;\n }\n\n if (\n e.target.classList.contains('focus-visible') ||\n e.target.hasAttribute('data-focus-visible-added')\n ) {\n // To detect a tab/window switch, we look for a blur event followed\n // rapidly by a visibility change.\n // If we don't see a visibility change within 100ms, it's probably a\n // regular focus change.\n hadFocusVisibleRecently = true;\n window.clearTimeout(hadFocusVisibleRecentlyTimeout);\n hadFocusVisibleRecentlyTimeout = window.setTimeout(function() {\n hadFocusVisibleRecently = false;\n }, 100);\n removeFocusVisibleClass(e.target);\n }\n }\n\n /**\n * If the user changes tabs, keep track of whether or not the previously\n * focused element had .focus-visible.\n * @param {Event} e\n */\n function onVisibilityChange(e) {\n if (document.visibilityState === 'hidden') {\n // If the tab becomes active again, the browser will handle calling focus\n // on the element (Safari actually calls it twice).\n // If this tab change caused a blur on an element with focus-visible,\n // re-apply the class when the user switches back to the tab.\n if (hadFocusVisibleRecently) {\n hadKeyboardEvent = true;\n }\n addInitialPointerMoveListeners();\n }\n }\n\n /**\n * Add a group of listeners to detect usage of any pointing devices.\n * These listeners will be added when the polyfill first loads, and anytime\n * the window is blurred, so that they are active when the window regains\n * focus.\n */\n function addInitialPointerMoveListeners() {\n document.addEventListener('mousemove', onInitialPointerMove);\n document.addEventListener('mousedown', onInitialPointerMove);\n document.addEventListener('mouseup', onInitialPointerMove);\n document.addEventListener('pointermove', onInitialPointerMove);\n document.addEventListener('pointerdown', onInitialPointerMove);\n document.addEventListener('pointerup', onInitialPointerMove);\n document.addEventListener('touchmove', onInitialPointerMove);\n document.addEventListener('touchstart', onInitialPointerMove);\n document.addEventListener('touchend', onInitialPointerMove);\n }\n\n function removeInitialPointerMoveListeners() {\n document.removeEventListener('mousemove', onInitialPointerMove);\n document.removeEventListener('mousedown', onInitialPointerMove);\n document.removeEventListener('mouseup', onInitialPointerMove);\n document.removeEventListener('pointermove', onInitialPointerMove);\n document.removeEventListener('pointerdown', onInitialPointerMove);\n document.removeEventListener('pointerup', onInitialPointerMove);\n document.removeEventListener('touchmove', onInitialPointerMove);\n document.removeEventListener('touchstart', onInitialPointerMove);\n document.removeEventListener('touchend', onInitialPointerMove);\n }\n\n /**\n * When the polfyill first loads, assume the user is in keyboard modality.\n * If any event is received from a pointing device (e.g. mouse, pointer,\n * touch), turn off keyboard modality.\n * This accounts for situations where focus enters the page from the URL bar.\n * @param {Event} e\n */\n function onInitialPointerMove(e) {\n // Work around a Safari quirk that fires a mousemove on whenever the\n // window blurs, even if you're tabbing out of the page. \u00AF\\_(\u30C4)_/\u00AF\n if (e.target.nodeName && e.target.nodeName.toLowerCase() === 'html') {\n return;\n }\n\n hadKeyboardEvent = false;\n removeInitialPointerMoveListeners();\n }\n\n // For some kinds of state, we are interested in changes at the global scope\n // only. For example, global pointer input, global key presses and global\n // visibility change should affect the state at every scope:\n document.addEventListener('keydown', onKeyDown, true);\n document.addEventListener('mousedown', onPointerDown, true);\n document.addEventListener('pointerdown', onPointerDown, true);\n document.addEventListener('touchstart', onPointerDown, true);\n document.addEventListener('visibilitychange', onVisibilityChange, true);\n\n addInitialPointerMoveListeners();\n\n // For focus and blur, we specifically care about state changes in the local\n // scope. This is because focus / blur events that originate from within a\n // shadow root are not re-dispatched from the host element if it was already\n // the active element in its own scope:\n scope.addEventListener('focus', onFocus, true);\n scope.addEventListener('blur', onBlur, true);\n\n // We detect that a node is a ShadowRoot by ensuring that it is a\n // DocumentFragment and also has a host property. This check covers native\n // implementation and polyfill implementation transparently. If we only cared\n // about the native implementation, we could just check if the scope was\n // an instance of a ShadowRoot.\n if (scope.nodeType === Node.DOCUMENT_FRAGMENT_NODE && scope.host) {\n // Since a ShadowRoot is a special kind of DocumentFragment, it does not\n // have a root element to add a class to. So, we add this attribute to the\n // host element instead:\n scope.host.setAttribute('data-js-focus-visible', '');\n } else if (scope.nodeType === Node.DOCUMENT_NODE) {\n document.documentElement.classList.add('js-focus-visible');\n document.documentElement.setAttribute('data-js-focus-visible', '');\n }\n }\n\n // It is important to wrap all references to global window and document in\n // these checks to support server-side rendering use cases\n // @see https://github.com/WICG/focus-visible/issues/199\n if (typeof window !== 'undefined' && typeof document !== 'undefined') {\n // Make the polyfill helper globally available. This can be used as a signal\n // to interested libraries that wish to coordinate with the polyfill for e.g.,\n // applying the polyfill to a shadow root:\n window.applyFocusVisiblePolyfill = applyFocusVisiblePolyfill;\n\n // Notify interested libraries of the polyfill's presence, in case the\n // polyfill was loaded lazily:\n var event;\n\n try {\n event = new CustomEvent('focus-visible-polyfill-ready');\n } catch (error) {\n // IE11 does not support using CustomEvent as a constructor directly:\n event = document.createEvent('CustomEvent');\n event.initCustomEvent('focus-visible-polyfill-ready', false, false, {});\n }\n\n window.dispatchEvent(event);\n }\n\n if (typeof document !== 'undefined') {\n // Apply the polyfill to the global document, so that no JavaScript\n // coordination is required to use the polyfill in the top-level document:\n applyFocusVisiblePolyfill(document);\n }\n\n})));\n", "/*!\n * escape-html\n * Copyright(c) 2012-2013 TJ Holowaychuk\n * Copyright(c) 2015 Andreas Lubbe\n * Copyright(c) 2015 Tiancheng \"Timothy\" Gu\n * MIT Licensed\n */\n\n'use strict';\n\n/**\n * Module variables.\n * @private\n */\n\nvar matchHtmlRegExp = /[\"'&<>]/;\n\n/**\n * Module exports.\n * @public\n */\n\nmodule.exports = escapeHtml;\n\n/**\n * Escape special characters in the given string of html.\n *\n * @param {string} string The string to escape for inserting into HTML\n * @return {string}\n * @public\n */\n\nfunction escapeHtml(string) {\n var str = '' + string;\n var match = matchHtmlRegExp.exec(str);\n\n if (!match) {\n return str;\n }\n\n var escape;\n var html = '';\n var index = 0;\n var lastIndex = 0;\n\n for (index = match.index; index < str.length; index++) {\n switch (str.charCodeAt(index)) {\n case 34: // \"\n escape = '"';\n break;\n case 38: // &\n escape = '&';\n break;\n case 39: // '\n escape = ''';\n break;\n case 60: // <\n escape = '<';\n break;\n case 62: // >\n escape = '>';\n break;\n default:\n continue;\n }\n\n if (lastIndex !== index) {\n html += str.substring(lastIndex, index);\n }\n\n lastIndex = index + 1;\n html += escape;\n }\n\n return lastIndex !== index\n ? html + str.substring(lastIndex, index)\n : html;\n}\n", "/*!\n * clipboard.js v2.0.11\n * https://clipboardjs.com/\n *\n * Licensed MIT \u00A9 Zeno Rocha\n */\n(function webpackUniversalModuleDefinition(root, factory) {\n\tif(typeof exports === 'object' && typeof module === 'object')\n\t\tmodule.exports = factory();\n\telse if(typeof define === 'function' && define.amd)\n\t\tdefine([], factory);\n\telse if(typeof exports === 'object')\n\t\texports[\"ClipboardJS\"] = factory();\n\telse\n\t\troot[\"ClipboardJS\"] = factory();\n})(this, function() {\nreturn /******/ (function() { // webpackBootstrap\n/******/ \tvar __webpack_modules__ = ({\n\n/***/ 686:\n/***/ (function(__unused_webpack_module, __webpack_exports__, __webpack_require__) {\n\n\"use strict\";\n\n// EXPORTS\n__webpack_require__.d(__webpack_exports__, {\n \"default\": function() { return /* binding */ clipboard; }\n});\n\n// EXTERNAL MODULE: ./node_modules/tiny-emitter/index.js\nvar tiny_emitter = __webpack_require__(279);\nvar tiny_emitter_default = /*#__PURE__*/__webpack_require__.n(tiny_emitter);\n// EXTERNAL MODULE: ./node_modules/good-listener/src/listen.js\nvar listen = __webpack_require__(370);\nvar listen_default = /*#__PURE__*/__webpack_require__.n(listen);\n// EXTERNAL MODULE: ./node_modules/select/src/select.js\nvar src_select = __webpack_require__(817);\nvar select_default = /*#__PURE__*/__webpack_require__.n(src_select);\n;// CONCATENATED MODULE: ./src/common/command.js\n/**\n * Executes a given operation type.\n * @param {String} type\n * @return {Boolean}\n */\nfunction command(type) {\n try {\n return document.execCommand(type);\n } catch (err) {\n return false;\n }\n}\n;// CONCATENATED MODULE: ./src/actions/cut.js\n\n\n/**\n * Cut action wrapper.\n * @param {String|HTMLElement} target\n * @return {String}\n */\n\nvar ClipboardActionCut = function ClipboardActionCut(target) {\n var selectedText = select_default()(target);\n command('cut');\n return selectedText;\n};\n\n/* harmony default export */ var actions_cut = (ClipboardActionCut);\n;// CONCATENATED MODULE: ./src/common/create-fake-element.js\n/**\n * Creates a fake textarea element with a value.\n * @param {String} value\n * @return {HTMLElement}\n */\nfunction createFakeElement(value) {\n var isRTL = document.documentElement.getAttribute('dir') === 'rtl';\n var fakeElement = document.createElement('textarea'); // Prevent zooming on iOS\n\n fakeElement.style.fontSize = '12pt'; // Reset box model\n\n fakeElement.style.border = '0';\n fakeElement.style.padding = '0';\n fakeElement.style.margin = '0'; // Move element out of screen horizontally\n\n fakeElement.style.position = 'absolute';\n fakeElement.style[isRTL ? 'right' : 'left'] = '-9999px'; // Move element to the same position vertically\n\n var yPosition = window.pageYOffset || document.documentElement.scrollTop;\n fakeElement.style.top = \"\".concat(yPosition, \"px\");\n fakeElement.setAttribute('readonly', '');\n fakeElement.value = value;\n return fakeElement;\n}\n;// CONCATENATED MODULE: ./src/actions/copy.js\n\n\n\n/**\n * Create fake copy action wrapper using a fake element.\n * @param {String} target\n * @param {Object} options\n * @return {String}\n */\n\nvar fakeCopyAction = function fakeCopyAction(value, options) {\n var fakeElement = createFakeElement(value);\n options.container.appendChild(fakeElement);\n var selectedText = select_default()(fakeElement);\n command('copy');\n fakeElement.remove();\n return selectedText;\n};\n/**\n * Copy action wrapper.\n * @param {String|HTMLElement} target\n * @param {Object} options\n * @return {String}\n */\n\n\nvar ClipboardActionCopy = function ClipboardActionCopy(target) {\n var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {\n container: document.body\n };\n var selectedText = '';\n\n if (typeof target === 'string') {\n selectedText = fakeCopyAction(target, options);\n } else if (target instanceof HTMLInputElement && !['text', 'search', 'url', 'tel', 'password'].includes(target === null || target === void 0 ? void 0 : target.type)) {\n // If input type doesn't support `setSelectionRange`. Simulate it. https://developer.mozilla.org/en-US/docs/Web/API/HTMLInputElement/setSelectionRange\n selectedText = fakeCopyAction(target.value, options);\n } else {\n selectedText = select_default()(target);\n command('copy');\n }\n\n return selectedText;\n};\n\n/* harmony default export */ var actions_copy = (ClipboardActionCopy);\n;// CONCATENATED MODULE: ./src/actions/default.js\nfunction _typeof(obj) { \"@babel/helpers - typeof\"; if (typeof Symbol === \"function\" && typeof Symbol.iterator === \"symbol\") { _typeof = function _typeof(obj) { return typeof obj; }; } else { _typeof = function _typeof(obj) { return obj && typeof Symbol === \"function\" && obj.constructor === Symbol && obj !== Symbol.prototype ? \"symbol\" : typeof obj; }; } return _typeof(obj); }\n\n\n\n/**\n * Inner function which performs selection from either `text` or `target`\n * properties and then executes copy or cut operations.\n * @param {Object} options\n */\n\nvar ClipboardActionDefault = function ClipboardActionDefault() {\n var options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};\n // Defines base properties passed from constructor.\n var _options$action = options.action,\n action = _options$action === void 0 ? 'copy' : _options$action,\n container = options.container,\n target = options.target,\n text = options.text; // Sets the `action` to be performed which can be either 'copy' or 'cut'.\n\n if (action !== 'copy' && action !== 'cut') {\n throw new Error('Invalid \"action\" value, use either \"copy\" or \"cut\"');\n } // Sets the `target` property using an element that will be have its content copied.\n\n\n if (target !== undefined) {\n if (target && _typeof(target) === 'object' && target.nodeType === 1) {\n if (action === 'copy' && target.hasAttribute('disabled')) {\n throw new Error('Invalid \"target\" attribute. Please use \"readonly\" instead of \"disabled\" attribute');\n }\n\n if (action === 'cut' && (target.hasAttribute('readonly') || target.hasAttribute('disabled'))) {\n throw new Error('Invalid \"target\" attribute. You can\\'t cut text from elements with \"readonly\" or \"disabled\" attributes');\n }\n } else {\n throw new Error('Invalid \"target\" value, use a valid Element');\n }\n } // Define selection strategy based on `text` property.\n\n\n if (text) {\n return actions_copy(text, {\n container: container\n });\n } // Defines which selection strategy based on `target` property.\n\n\n if (target) {\n return action === 'cut' ? actions_cut(target) : actions_copy(target, {\n container: container\n });\n }\n};\n\n/* harmony default export */ var actions_default = (ClipboardActionDefault);\n;// CONCATENATED MODULE: ./src/clipboard.js\nfunction clipboard_typeof(obj) { \"@babel/helpers - typeof\"; if (typeof Symbol === \"function\" && typeof Symbol.iterator === \"symbol\") { clipboard_typeof = function _typeof(obj) { return typeof obj; }; } else { clipboard_typeof = function _typeof(obj) { return obj && typeof Symbol === \"function\" && obj.constructor === Symbol && obj !== Symbol.prototype ? \"symbol\" : typeof obj; }; } return clipboard_typeof(obj); }\n\nfunction _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError(\"Cannot call a class as a function\"); } }\n\nfunction _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if (\"value\" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } }\n\nfunction _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); return Constructor; }\n\nfunction _inherits(subClass, superClass) { if (typeof superClass !== \"function\" && superClass !== null) { throw new TypeError(\"Super expression must either be null or a function\"); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, writable: true, configurable: true } }); if (superClass) _setPrototypeOf(subClass, superClass); }\n\nfunction _setPrototypeOf(o, p) { _setPrototypeOf = Object.setPrototypeOf || function _setPrototypeOf(o, p) { o.__proto__ = p; return o; }; return _setPrototypeOf(o, p); }\n\nfunction _createSuper(Derived) { var hasNativeReflectConstruct = _isNativeReflectConstruct(); return function _createSuperInternal() { var Super = _getPrototypeOf(Derived), result; if (hasNativeReflectConstruct) { var NewTarget = _getPrototypeOf(this).constructor; result = Reflect.construct(Super, arguments, NewTarget); } else { result = Super.apply(this, arguments); } return _possibleConstructorReturn(this, result); }; }\n\nfunction _possibleConstructorReturn(self, call) { if (call && (clipboard_typeof(call) === \"object\" || typeof call === \"function\")) { return call; } return _assertThisInitialized(self); }\n\nfunction _assertThisInitialized(self) { if (self === void 0) { throw new ReferenceError(\"this hasn't been initialised - super() hasn't been called\"); } return self; }\n\nfunction _isNativeReflectConstruct() { if (typeof Reflect === \"undefined\" || !Reflect.construct) return false; if (Reflect.construct.sham) return false; if (typeof Proxy === \"function\") return true; try { Date.prototype.toString.call(Reflect.construct(Date, [], function () {})); return true; } catch (e) { return false; } }\n\nfunction _getPrototypeOf(o) { _getPrototypeOf = Object.setPrototypeOf ? Object.getPrototypeOf : function _getPrototypeOf(o) { return o.__proto__ || Object.getPrototypeOf(o); }; return _getPrototypeOf(o); }\n\n\n\n\n\n\n/**\n * Helper function to retrieve attribute value.\n * @param {String} suffix\n * @param {Element} element\n */\n\nfunction getAttributeValue(suffix, element) {\n var attribute = \"data-clipboard-\".concat(suffix);\n\n if (!element.hasAttribute(attribute)) {\n return;\n }\n\n return element.getAttribute(attribute);\n}\n/**\n * Base class which takes one or more elements, adds event listeners to them,\n * and instantiates a new `ClipboardAction` on each click.\n */\n\n\nvar Clipboard = /*#__PURE__*/function (_Emitter) {\n _inherits(Clipboard, _Emitter);\n\n var _super = _createSuper(Clipboard);\n\n /**\n * @param {String|HTMLElement|HTMLCollection|NodeList} trigger\n * @param {Object} options\n */\n function Clipboard(trigger, options) {\n var _this;\n\n _classCallCheck(this, Clipboard);\n\n _this = _super.call(this);\n\n _this.resolveOptions(options);\n\n _this.listenClick(trigger);\n\n return _this;\n }\n /**\n * Defines if attributes would be resolved using internal setter functions\n * or custom functions that were passed in the constructor.\n * @param {Object} options\n */\n\n\n _createClass(Clipboard, [{\n key: \"resolveOptions\",\n value: function resolveOptions() {\n var options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};\n this.action = typeof options.action === 'function' ? options.action : this.defaultAction;\n this.target = typeof options.target === 'function' ? options.target : this.defaultTarget;\n this.text = typeof options.text === 'function' ? options.text : this.defaultText;\n this.container = clipboard_typeof(options.container) === 'object' ? options.container : document.body;\n }\n /**\n * Adds a click event listener to the passed trigger.\n * @param {String|HTMLElement|HTMLCollection|NodeList} trigger\n */\n\n }, {\n key: \"listenClick\",\n value: function listenClick(trigger) {\n var _this2 = this;\n\n this.listener = listen_default()(trigger, 'click', function (e) {\n return _this2.onClick(e);\n });\n }\n /**\n * Defines a new `ClipboardAction` on each click event.\n * @param {Event} e\n */\n\n }, {\n key: \"onClick\",\n value: function onClick(e) {\n var trigger = e.delegateTarget || e.currentTarget;\n var action = this.action(trigger) || 'copy';\n var text = actions_default({\n action: action,\n container: this.container,\n target: this.target(trigger),\n text: this.text(trigger)\n }); // Fires an event based on the copy operation result.\n\n this.emit(text ? 'success' : 'error', {\n action: action,\n text: text,\n trigger: trigger,\n clearSelection: function clearSelection() {\n if (trigger) {\n trigger.focus();\n }\n\n window.getSelection().removeAllRanges();\n }\n });\n }\n /**\n * Default `action` lookup function.\n * @param {Element} trigger\n */\n\n }, {\n key: \"defaultAction\",\n value: function defaultAction(trigger) {\n return getAttributeValue('action', trigger);\n }\n /**\n * Default `target` lookup function.\n * @param {Element} trigger\n */\n\n }, {\n key: \"defaultTarget\",\n value: function defaultTarget(trigger) {\n var selector = getAttributeValue('target', trigger);\n\n if (selector) {\n return document.querySelector(selector);\n }\n }\n /**\n * Allow fire programmatically a copy action\n * @param {String|HTMLElement} target\n * @param {Object} options\n * @returns Text copied.\n */\n\n }, {\n key: \"defaultText\",\n\n /**\n * Default `text` lookup function.\n * @param {Element} trigger\n */\n value: function defaultText(trigger) {\n return getAttributeValue('text', trigger);\n }\n /**\n * Destroy lifecycle.\n */\n\n }, {\n key: \"destroy\",\n value: function destroy() {\n this.listener.destroy();\n }\n }], [{\n key: \"copy\",\n value: function copy(target) {\n var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {\n container: document.body\n };\n return actions_copy(target, options);\n }\n /**\n * Allow fire programmatically a cut action\n * @param {String|HTMLElement} target\n * @returns Text cutted.\n */\n\n }, {\n key: \"cut\",\n value: function cut(target) {\n return actions_cut(target);\n }\n /**\n * Returns the support of the given action, or all actions if no action is\n * given.\n * @param {String} [action]\n */\n\n }, {\n key: \"isSupported\",\n value: function isSupported() {\n var action = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : ['copy', 'cut'];\n var actions = typeof action === 'string' ? [action] : action;\n var support = !!document.queryCommandSupported;\n actions.forEach(function (action) {\n support = support && !!document.queryCommandSupported(action);\n });\n return support;\n }\n }]);\n\n return Clipboard;\n}((tiny_emitter_default()));\n\n/* harmony default export */ var clipboard = (Clipboard);\n\n/***/ }),\n\n/***/ 828:\n/***/ (function(module) {\n\nvar DOCUMENT_NODE_TYPE = 9;\n\n/**\n * A polyfill for Element.matches()\n */\nif (typeof Element !== 'undefined' && !Element.prototype.matches) {\n var proto = Element.prototype;\n\n proto.matches = proto.matchesSelector ||\n proto.mozMatchesSelector ||\n proto.msMatchesSelector ||\n proto.oMatchesSelector ||\n proto.webkitMatchesSelector;\n}\n\n/**\n * Finds the closest parent that matches a selector.\n *\n * @param {Element} element\n * @param {String} selector\n * @return {Function}\n */\nfunction closest (element, selector) {\n while (element && element.nodeType !== DOCUMENT_NODE_TYPE) {\n if (typeof element.matches === 'function' &&\n element.matches(selector)) {\n return element;\n }\n element = element.parentNode;\n }\n}\n\nmodule.exports = closest;\n\n\n/***/ }),\n\n/***/ 438:\n/***/ (function(module, __unused_webpack_exports, __webpack_require__) {\n\nvar closest = __webpack_require__(828);\n\n/**\n * Delegates event to a selector.\n *\n * @param {Element} element\n * @param {String} selector\n * @param {String} type\n * @param {Function} callback\n * @param {Boolean} useCapture\n * @return {Object}\n */\nfunction _delegate(element, selector, type, callback, useCapture) {\n var listenerFn = listener.apply(this, arguments);\n\n element.addEventListener(type, listenerFn, useCapture);\n\n return {\n destroy: function() {\n element.removeEventListener(type, listenerFn, useCapture);\n }\n }\n}\n\n/**\n * Delegates event to a selector.\n *\n * @param {Element|String|Array} [elements]\n * @param {String} selector\n * @param {String} type\n * @param {Function} callback\n * @param {Boolean} useCapture\n * @return {Object}\n */\nfunction delegate(elements, selector, type, callback, useCapture) {\n // Handle the regular Element usage\n if (typeof elements.addEventListener === 'function') {\n return _delegate.apply(null, arguments);\n }\n\n // Handle Element-less usage, it defaults to global delegation\n if (typeof type === 'function') {\n // Use `document` as the first parameter, then apply arguments\n // This is a short way to .unshift `arguments` without running into deoptimizations\n return _delegate.bind(null, document).apply(null, arguments);\n }\n\n // Handle Selector-based usage\n if (typeof elements === 'string') {\n elements = document.querySelectorAll(elements);\n }\n\n // Handle Array-like based usage\n return Array.prototype.map.call(elements, function (element) {\n return _delegate(element, selector, type, callback, useCapture);\n });\n}\n\n/**\n * Finds closest match and invokes callback.\n *\n * @param {Element} element\n * @param {String} selector\n * @param {String} type\n * @param {Function} callback\n * @return {Function}\n */\nfunction listener(element, selector, type, callback) {\n return function(e) {\n e.delegateTarget = closest(e.target, selector);\n\n if (e.delegateTarget) {\n callback.call(element, e);\n }\n }\n}\n\nmodule.exports = delegate;\n\n\n/***/ }),\n\n/***/ 879:\n/***/ (function(__unused_webpack_module, exports) {\n\n/**\n * Check if argument is a HTML element.\n *\n * @param {Object} value\n * @return {Boolean}\n */\nexports.node = function(value) {\n return value !== undefined\n && value instanceof HTMLElement\n && value.nodeType === 1;\n};\n\n/**\n * Check if argument is a list of HTML elements.\n *\n * @param {Object} value\n * @return {Boolean}\n */\nexports.nodeList = function(value) {\n var type = Object.prototype.toString.call(value);\n\n return value !== undefined\n && (type === '[object NodeList]' || type === '[object HTMLCollection]')\n && ('length' in value)\n && (value.length === 0 || exports.node(value[0]));\n};\n\n/**\n * Check if argument is a string.\n *\n * @param {Object} value\n * @return {Boolean}\n */\nexports.string = function(value) {\n return typeof value === 'string'\n || value instanceof String;\n};\n\n/**\n * Check if argument is a function.\n *\n * @param {Object} value\n * @return {Boolean}\n */\nexports.fn = function(value) {\n var type = Object.prototype.toString.call(value);\n\n return type === '[object Function]';\n};\n\n\n/***/ }),\n\n/***/ 370:\n/***/ (function(module, __unused_webpack_exports, __webpack_require__) {\n\nvar is = __webpack_require__(879);\nvar delegate = __webpack_require__(438);\n\n/**\n * Validates all params and calls the right\n * listener function based on its target type.\n *\n * @param {String|HTMLElement|HTMLCollection|NodeList} target\n * @param {String} type\n * @param {Function} callback\n * @return {Object}\n */\nfunction listen(target, type, callback) {\n if (!target && !type && !callback) {\n throw new Error('Missing required arguments');\n }\n\n if (!is.string(type)) {\n throw new TypeError('Second argument must be a String');\n }\n\n if (!is.fn(callback)) {\n throw new TypeError('Third argument must be a Function');\n }\n\n if (is.node(target)) {\n return listenNode(target, type, callback);\n }\n else if (is.nodeList(target)) {\n return listenNodeList(target, type, callback);\n }\n else if (is.string(target)) {\n return listenSelector(target, type, callback);\n }\n else {\n throw new TypeError('First argument must be a String, HTMLElement, HTMLCollection, or NodeList');\n }\n}\n\n/**\n * Adds an event listener to a HTML element\n * and returns a remove listener function.\n *\n * @param {HTMLElement} node\n * @param {String} type\n * @param {Function} callback\n * @return {Object}\n */\nfunction listenNode(node, type, callback) {\n node.addEventListener(type, callback);\n\n return {\n destroy: function() {\n node.removeEventListener(type, callback);\n }\n }\n}\n\n/**\n * Add an event listener to a list of HTML elements\n * and returns a remove listener function.\n *\n * @param {NodeList|HTMLCollection} nodeList\n * @param {String} type\n * @param {Function} callback\n * @return {Object}\n */\nfunction listenNodeList(nodeList, type, callback) {\n Array.prototype.forEach.call(nodeList, function(node) {\n node.addEventListener(type, callback);\n });\n\n return {\n destroy: function() {\n Array.prototype.forEach.call(nodeList, function(node) {\n node.removeEventListener(type, callback);\n });\n }\n }\n}\n\n/**\n * Add an event listener to a selector\n * and returns a remove listener function.\n *\n * @param {String} selector\n * @param {String} type\n * @param {Function} callback\n * @return {Object}\n */\nfunction listenSelector(selector, type, callback) {\n return delegate(document.body, selector, type, callback);\n}\n\nmodule.exports = listen;\n\n\n/***/ }),\n\n/***/ 817:\n/***/ (function(module) {\n\nfunction select(element) {\n var selectedText;\n\n if (element.nodeName === 'SELECT') {\n element.focus();\n\n selectedText = element.value;\n }\n else if (element.nodeName === 'INPUT' || element.nodeName === 'TEXTAREA') {\n var isReadOnly = element.hasAttribute('readonly');\n\n if (!isReadOnly) {\n element.setAttribute('readonly', '');\n }\n\n element.select();\n element.setSelectionRange(0, element.value.length);\n\n if (!isReadOnly) {\n element.removeAttribute('readonly');\n }\n\n selectedText = element.value;\n }\n else {\n if (element.hasAttribute('contenteditable')) {\n element.focus();\n }\n\n var selection = window.getSelection();\n var range = document.createRange();\n\n range.selectNodeContents(element);\n selection.removeAllRanges();\n selection.addRange(range);\n\n selectedText = selection.toString();\n }\n\n return selectedText;\n}\n\nmodule.exports = select;\n\n\n/***/ }),\n\n/***/ 279:\n/***/ (function(module) {\n\nfunction E () {\n // Keep this empty so it's easier to inherit from\n // (via https://github.com/lipsmack from https://github.com/scottcorgan/tiny-emitter/issues/3)\n}\n\nE.prototype = {\n on: function (name, callback, ctx) {\n var e = this.e || (this.e = {});\n\n (e[name] || (e[name] = [])).push({\n fn: callback,\n ctx: ctx\n });\n\n return this;\n },\n\n once: function (name, callback, ctx) {\n var self = this;\n function listener () {\n self.off(name, listener);\n callback.apply(ctx, arguments);\n };\n\n listener._ = callback\n return this.on(name, listener, ctx);\n },\n\n emit: function (name) {\n var data = [].slice.call(arguments, 1);\n var evtArr = ((this.e || (this.e = {}))[name] || []).slice();\n var i = 0;\n var len = evtArr.length;\n\n for (i; i < len; i++) {\n evtArr[i].fn.apply(evtArr[i].ctx, data);\n }\n\n return this;\n },\n\n off: function (name, callback) {\n var e = this.e || (this.e = {});\n var evts = e[name];\n var liveEvents = [];\n\n if (evts && callback) {\n for (var i = 0, len = evts.length; i < len; i++) {\n if (evts[i].fn !== callback && evts[i].fn._ !== callback)\n liveEvents.push(evts[i]);\n }\n }\n\n // Remove event from queue to prevent memory leak\n // Suggested by https://github.com/lazd\n // Ref: https://github.com/scottcorgan/tiny-emitter/commit/c6ebfaa9bc973b33d110a84a307742b7cf94c953#commitcomment-5024910\n\n (liveEvents.length)\n ? e[name] = liveEvents\n : delete e[name];\n\n return this;\n }\n};\n\nmodule.exports = E;\nmodule.exports.TinyEmitter = E;\n\n\n/***/ })\n\n/******/ \t});\n/************************************************************************/\n/******/ \t// The module cache\n/******/ \tvar __webpack_module_cache__ = {};\n/******/ \t\n/******/ \t// The require function\n/******/ \tfunction __webpack_require__(moduleId) {\n/******/ \t\t// Check if module is in cache\n/******/ \t\tif(__webpack_module_cache__[moduleId]) {\n/******/ \t\t\treturn __webpack_module_cache__[moduleId].exports;\n/******/ \t\t}\n/******/ \t\t// Create a new module (and put it into the cache)\n/******/ \t\tvar module = __webpack_module_cache__[moduleId] = {\n/******/ \t\t\t// no module.id needed\n/******/ \t\t\t// no module.loaded needed\n/******/ \t\t\texports: {}\n/******/ \t\t};\n/******/ \t\n/******/ \t\t// Execute the module function\n/******/ \t\t__webpack_modules__[moduleId](module, module.exports, __webpack_require__);\n/******/ \t\n/******/ \t\t// Return the exports of the module\n/******/ \t\treturn module.exports;\n/******/ \t}\n/******/ \t\n/************************************************************************/\n/******/ \t/* webpack/runtime/compat get default export */\n/******/ \t!function() {\n/******/ \t\t// getDefaultExport function for compatibility with non-harmony modules\n/******/ \t\t__webpack_require__.n = function(module) {\n/******/ \t\t\tvar getter = module && module.__esModule ?\n/******/ \t\t\t\tfunction() { return module['default']; } :\n/******/ \t\t\t\tfunction() { return module; };\n/******/ \t\t\t__webpack_require__.d(getter, { a: getter });\n/******/ \t\t\treturn getter;\n/******/ \t\t};\n/******/ \t}();\n/******/ \t\n/******/ \t/* webpack/runtime/define property getters */\n/******/ \t!function() {\n/******/ \t\t// define getter functions for harmony exports\n/******/ \t\t__webpack_require__.d = function(exports, definition) {\n/******/ \t\t\tfor(var key in definition) {\n/******/ \t\t\t\tif(__webpack_require__.o(definition, key) && !__webpack_require__.o(exports, key)) {\n/******/ \t\t\t\t\tObject.defineProperty(exports, key, { enumerable: true, get: definition[key] });\n/******/ \t\t\t\t}\n/******/ \t\t\t}\n/******/ \t\t};\n/******/ \t}();\n/******/ \t\n/******/ \t/* webpack/runtime/hasOwnProperty shorthand */\n/******/ \t!function() {\n/******/ \t\t__webpack_require__.o = function(obj, prop) { return Object.prototype.hasOwnProperty.call(obj, prop); }\n/******/ \t}();\n/******/ \t\n/************************************************************************/\n/******/ \t// module exports must be returned from runtime so entry inlining is disabled\n/******/ \t// startup\n/******/ \t// Load entry module and return exports\n/******/ \treturn __webpack_require__(686);\n/******/ })()\n.default;\n});", "/*\n * Copyright (c) 2016-2024 Martin Donath \n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport \"focus-visible\"\n\nimport {\n EMPTY,\n NEVER,\n Observable,\n Subject,\n defer,\n delay,\n filter,\n map,\n merge,\n mergeWith,\n shareReplay,\n switchMap\n} from \"rxjs\"\n\nimport { configuration, feature } from \"./_\"\nimport {\n at,\n getActiveElement,\n getOptionalElement,\n requestJSON,\n setLocation,\n setToggle,\n watchDocument,\n watchKeyboard,\n watchLocation,\n watchLocationTarget,\n watchMedia,\n watchPrint,\n watchScript,\n watchViewport\n} from \"./browser\"\nimport {\n getComponentElement,\n getComponentElements,\n mountAnnounce,\n mountBackToTop,\n mountConsent,\n mountContent,\n mountDialog,\n mountHeader,\n mountHeaderTitle,\n mountPalette,\n mountProgress,\n mountSearch,\n mountSearchHiglight,\n mountSidebar,\n mountSource,\n mountTableOfContents,\n mountTabs,\n watchHeader,\n watchMain\n} from \"./components\"\nimport {\n SearchIndex,\n setupClipboardJS,\n setupInstantNavigation,\n setupVersionSelector\n} from \"./integrations\"\nimport {\n patchEllipsis,\n patchIndeterminate,\n patchScrollfix,\n patchScrolllock\n} from \"./patches\"\nimport \"./polyfills\"\n\n/* ----------------------------------------------------------------------------\n * Functions - @todo refactor\n * ------------------------------------------------------------------------- */\n\n/**\n * Fetch search index\n *\n * @returns Search index observable\n */\nfunction fetchSearchIndex(): Observable {\n if (location.protocol === \"file:\") {\n return watchScript(\n `${new URL(\"search/search_index.js\", config.base)}`\n )\n .pipe(\n // @ts-ignore - @todo fix typings\n map(() => __index),\n shareReplay(1)\n )\n } else {\n return requestJSON(\n new URL(\"search/search_index.json\", config.base)\n )\n }\n}\n\n/* ----------------------------------------------------------------------------\n * Application\n * ------------------------------------------------------------------------- */\n\n/* Yay, JavaScript is available */\ndocument.documentElement.classList.remove(\"no-js\")\ndocument.documentElement.classList.add(\"js\")\n\n/* Set up navigation observables and subjects */\nconst document$ = watchDocument()\nconst location$ = watchLocation()\nconst target$ = watchLocationTarget(location$)\nconst keyboard$ = watchKeyboard()\n\n/* Set up media observables */\nconst viewport$ = watchViewport()\nconst tablet$ = watchMedia(\"(min-width: 960px)\")\nconst screen$ = watchMedia(\"(min-width: 1220px)\")\nconst print$ = watchPrint()\n\n/* Retrieve search index, if search is enabled */\nconst config = configuration()\nconst index$ = document.forms.namedItem(\"search\")\n ? fetchSearchIndex()\n : NEVER\n\n/* Set up Clipboard.js integration */\nconst alert$ = new Subject()\nsetupClipboardJS({ alert$ })\n\n/* Set up progress indicator */\nconst progress$ = new Subject()\n\n/* Set up instant navigation, if enabled */\nif (feature(\"navigation.instant\"))\n setupInstantNavigation({ location$, viewport$, progress$ })\n .subscribe(document$)\n\n/* Set up version selector */\nif (config.version?.provider === \"mike\")\n setupVersionSelector({ document$ })\n\n/* Always close drawer and search on navigation */\nmerge(location$, target$)\n .pipe(\n delay(125)\n )\n .subscribe(() => {\n setToggle(\"drawer\", false)\n setToggle(\"search\", false)\n })\n\n/* Set up global keyboard handlers */\nkeyboard$\n .pipe(\n filter(({ mode }) => mode === \"global\")\n )\n .subscribe(key => {\n switch (key.type) {\n\n /* Go to previous page */\n case \"p\":\n case \",\":\n const prev = getOptionalElement(\"link[rel=prev]\")\n if (typeof prev !== \"undefined\")\n setLocation(prev)\n break\n\n /* Go to next page */\n case \"n\":\n case \".\":\n const next = getOptionalElement(\"link[rel=next]\")\n if (typeof next !== \"undefined\")\n setLocation(next)\n break\n\n /* Expand navigation, see https://bit.ly/3ZjG5io */\n case \"Enter\":\n const active = getActiveElement()\n if (active instanceof HTMLLabelElement)\n active.click()\n }\n })\n\n/* Set up patches */\npatchEllipsis({ viewport$, document$ })\npatchIndeterminate({ document$, tablet$ })\npatchScrollfix({ document$ })\npatchScrolllock({ viewport$, tablet$ })\n\n/* Set up header and main area observable */\nconst header$ = watchHeader(getComponentElement(\"header\"), { viewport$ })\nconst main$ = document$\n .pipe(\n map(() => getComponentElement(\"main\")),\n switchMap(el => watchMain(el, { viewport$, header$ })),\n shareReplay(1)\n )\n\n/* Set up control component observables */\nconst control$ = merge(\n\n /* Consent */\n ...getComponentElements(\"consent\")\n .map(el => mountConsent(el, { target$ })),\n\n /* Dialog */\n ...getComponentElements(\"dialog\")\n .map(el => mountDialog(el, { alert$ })),\n\n /* Color palette */\n ...getComponentElements(\"palette\")\n .map(el => mountPalette(el)),\n\n /* Progress bar */\n ...getComponentElements(\"progress\")\n .map(el => mountProgress(el, { progress$ })),\n\n /* Search */\n ...getComponentElements(\"search\")\n .map(el => mountSearch(el, { index$, keyboard$ })),\n\n /* Repository information */\n ...getComponentElements(\"source\")\n .map(el => mountSource(el))\n)\n\n/* Set up content component observables */\nconst content$ = defer(() => merge(\n\n /* Announcement bar */\n ...getComponentElements(\"announce\")\n .map(el => mountAnnounce(el)),\n\n /* Content */\n ...getComponentElements(\"content\")\n .map(el => mountContent(el, { viewport$, target$, print$ })),\n\n /* Search highlighting */\n ...getComponentElements(\"content\")\n .map(el => feature(\"search.highlight\")\n ? mountSearchHiglight(el, { index$, location$ })\n : EMPTY\n ),\n\n /* Header */\n ...getComponentElements(\"header\")\n .map(el => mountHeader(el, { viewport$, header$, main$ })),\n\n /* Header title */\n ...getComponentElements(\"header-title\")\n .map(el => mountHeaderTitle(el, { viewport$, header$ })),\n\n /* Sidebar */\n ...getComponentElements(\"sidebar\")\n .map(el => el.getAttribute(\"data-md-type\") === \"navigation\"\n ? at(screen$, () => mountSidebar(el, { viewport$, header$, main$ }))\n : at(tablet$, () => mountSidebar(el, { viewport$, header$, main$ }))\n ),\n\n /* Navigation tabs */\n ...getComponentElements(\"tabs\")\n .map(el => mountTabs(el, { viewport$, header$ })),\n\n /* Table of contents */\n ...getComponentElements(\"toc\")\n .map(el => mountTableOfContents(el, {\n viewport$, header$, main$, target$\n })),\n\n /* Back-to-top button */\n ...getComponentElements(\"top\")\n .map(el => mountBackToTop(el, { viewport$, header$, main$, target$ }))\n))\n\n/* Set up component observables */\nconst component$ = document$\n .pipe(\n switchMap(() => content$),\n mergeWith(control$),\n shareReplay(1)\n )\n\n/* Subscribe to all components */\ncomponent$.subscribe()\n\n/* ----------------------------------------------------------------------------\n * Exports\n * ------------------------------------------------------------------------- */\n\nwindow.document$ = document$ /* Document observable */\nwindow.location$ = location$ /* Location subject */\nwindow.target$ = target$ /* Location target observable */\nwindow.keyboard$ = keyboard$ /* Keyboard observable */\nwindow.viewport$ = viewport$ /* Viewport observable */\nwindow.tablet$ = tablet$ /* Media tablet observable */\nwindow.screen$ = screen$ /* Media screen observable */\nwindow.print$ = print$ /* Media print observable */\nwindow.alert$ = alert$ /* Alert subject */\nwindow.progress$ = progress$ /* Progress indicator subject */\nwindow.component$ = component$ /* Component observable */\n", "/******************************************************************************\nCopyright (c) Microsoft Corporation.\n\nPermission to use, copy, modify, and/or distribute this software for any\npurpose with or without fee is hereby granted.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH\nREGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY\nAND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,\nINDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM\nLOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR\nOTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR\nPERFORMANCE OF THIS SOFTWARE.\n***************************************************************************** */\n/* global Reflect, Promise, SuppressedError, Symbol, Iterator */\n\nvar extendStatics = function(d, b) {\n extendStatics = Object.setPrototypeOf ||\n ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||\n function (d, b) { for (var p in b) if (Object.prototype.hasOwnProperty.call(b, p)) d[p] = b[p]; };\n return extendStatics(d, b);\n};\n\nexport function __extends(d, b) {\n if (typeof b !== \"function\" && b !== null)\n throw new TypeError(\"Class extends value \" + String(b) + \" is not a constructor or null\");\n extendStatics(d, b);\n function __() { this.constructor = d; }\n d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());\n}\n\nexport var __assign = function() {\n __assign = Object.assign || function __assign(t) {\n for (var s, i = 1, n = arguments.length; i < n; i++) {\n s = arguments[i];\n for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p)) t[p] = s[p];\n }\n return t;\n }\n return __assign.apply(this, arguments);\n}\n\nexport function __rest(s, e) {\n var t = {};\n for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)\n t[p] = s[p];\n if (s != null && typeof Object.getOwnPropertySymbols === \"function\")\n for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {\n if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))\n t[p[i]] = s[p[i]];\n }\n return t;\n}\n\nexport function __decorate(decorators, target, key, desc) {\n var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;\n if (typeof Reflect === \"object\" && typeof Reflect.decorate === \"function\") r = Reflect.decorate(decorators, target, key, desc);\n else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;\n return c > 3 && r && Object.defineProperty(target, key, r), r;\n}\n\nexport function __param(paramIndex, decorator) {\n return function (target, key) { decorator(target, key, paramIndex); }\n}\n\nexport function __esDecorate(ctor, descriptorIn, decorators, contextIn, initializers, extraInitializers) {\n function accept(f) { if (f !== void 0 && typeof f !== \"function\") throw new TypeError(\"Function expected\"); return f; }\n var kind = contextIn.kind, key = kind === \"getter\" ? \"get\" : kind === \"setter\" ? \"set\" : \"value\";\n var target = !descriptorIn && ctor ? contextIn[\"static\"] ? ctor : ctor.prototype : null;\n var descriptor = descriptorIn || (target ? Object.getOwnPropertyDescriptor(target, contextIn.name) : {});\n var _, done = false;\n for (var i = decorators.length - 1; i >= 0; i--) {\n var context = {};\n for (var p in contextIn) context[p] = p === \"access\" ? {} : contextIn[p];\n for (var p in contextIn.access) context.access[p] = contextIn.access[p];\n context.addInitializer = function (f) { if (done) throw new TypeError(\"Cannot add initializers after decoration has completed\"); extraInitializers.push(accept(f || null)); };\n var result = (0, decorators[i])(kind === \"accessor\" ? { get: descriptor.get, set: descriptor.set } : descriptor[key], context);\n if (kind === \"accessor\") {\n if (result === void 0) continue;\n if (result === null || typeof result !== \"object\") throw new TypeError(\"Object expected\");\n if (_ = accept(result.get)) descriptor.get = _;\n if (_ = accept(result.set)) descriptor.set = _;\n if (_ = accept(result.init)) initializers.unshift(_);\n }\n else if (_ = accept(result)) {\n if (kind === \"field\") initializers.unshift(_);\n else descriptor[key] = _;\n }\n }\n if (target) Object.defineProperty(target, contextIn.name, descriptor);\n done = true;\n};\n\nexport function __runInitializers(thisArg, initializers, value) {\n var useValue = arguments.length > 2;\n for (var i = 0; i < initializers.length; i++) {\n value = useValue ? initializers[i].call(thisArg, value) : initializers[i].call(thisArg);\n }\n return useValue ? value : void 0;\n};\n\nexport function __propKey(x) {\n return typeof x === \"symbol\" ? x : \"\".concat(x);\n};\n\nexport function __setFunctionName(f, name, prefix) {\n if (typeof name === \"symbol\") name = name.description ? \"[\".concat(name.description, \"]\") : \"\";\n return Object.defineProperty(f, \"name\", { configurable: true, value: prefix ? \"\".concat(prefix, \" \", name) : name });\n};\n\nexport function __metadata(metadataKey, metadataValue) {\n if (typeof Reflect === \"object\" && typeof Reflect.metadata === \"function\") return Reflect.metadata(metadataKey, metadataValue);\n}\n\nexport function __awaiter(thisArg, _arguments, P, generator) {\n function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }\n return new (P || (P = Promise))(function (resolve, reject) {\n function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }\n function rejected(value) { try { step(generator[\"throw\"](value)); } catch (e) { reject(e); } }\n function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }\n step((generator = generator.apply(thisArg, _arguments || [])).next());\n });\n}\n\nexport function __generator(thisArg, body) {\n var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g = Object.create((typeof Iterator === \"function\" ? Iterator : Object).prototype);\n return g.next = verb(0), g[\"throw\"] = verb(1), g[\"return\"] = verb(2), typeof Symbol === \"function\" && (g[Symbol.iterator] = function() { return this; }), g;\n function verb(n) { return function (v) { return step([n, v]); }; }\n function step(op) {\n if (f) throw new TypeError(\"Generator is already executing.\");\n while (g && (g = 0, op[0] && (_ = 0)), _) try {\n if (f = 1, y && (t = op[0] & 2 ? y[\"return\"] : op[0] ? y[\"throw\"] || ((t = y[\"return\"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;\n if (y = 0, t) op = [op[0] & 2, t.value];\n switch (op[0]) {\n case 0: case 1: t = op; break;\n case 4: _.label++; return { value: op[1], done: false };\n case 5: _.label++; y = op[1]; op = [0]; continue;\n case 7: op = _.ops.pop(); _.trys.pop(); continue;\n default:\n if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }\n if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }\n if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }\n if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }\n if (t[2]) _.ops.pop();\n _.trys.pop(); continue;\n }\n op = body.call(thisArg, _);\n } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }\n if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };\n }\n}\n\nexport var __createBinding = Object.create ? (function(o, m, k, k2) {\n if (k2 === undefined) k2 = k;\n var desc = Object.getOwnPropertyDescriptor(m, k);\n if (!desc || (\"get\" in desc ? !m.__esModule : desc.writable || desc.configurable)) {\n desc = { enumerable: true, get: function() { return m[k]; } };\n }\n Object.defineProperty(o, k2, desc);\n}) : (function(o, m, k, k2) {\n if (k2 === undefined) k2 = k;\n o[k2] = m[k];\n});\n\nexport function __exportStar(m, o) {\n for (var p in m) if (p !== \"default\" && !Object.prototype.hasOwnProperty.call(o, p)) __createBinding(o, m, p);\n}\n\nexport function __values(o) {\n var s = typeof Symbol === \"function\" && Symbol.iterator, m = s && o[s], i = 0;\n if (m) return m.call(o);\n if (o && typeof o.length === \"number\") return {\n next: function () {\n if (o && i >= o.length) o = void 0;\n return { value: o && o[i++], done: !o };\n }\n };\n throw new TypeError(s ? \"Object is not iterable.\" : \"Symbol.iterator is not defined.\");\n}\n\nexport function __read(o, n) {\n var m = typeof Symbol === \"function\" && o[Symbol.iterator];\n if (!m) return o;\n var i = m.call(o), r, ar = [], e;\n try {\n while ((n === void 0 || n-- > 0) && !(r = i.next()).done) ar.push(r.value);\n }\n catch (error) { e = { error: error }; }\n finally {\n try {\n if (r && !r.done && (m = i[\"return\"])) m.call(i);\n }\n finally { if (e) throw e.error; }\n }\n return ar;\n}\n\n/** @deprecated */\nexport function __spread() {\n for (var ar = [], i = 0; i < arguments.length; i++)\n ar = ar.concat(__read(arguments[i]));\n return ar;\n}\n\n/** @deprecated */\nexport function __spreadArrays() {\n for (var s = 0, i = 0, il = arguments.length; i < il; i++) s += arguments[i].length;\n for (var r = Array(s), k = 0, i = 0; i < il; i++)\n for (var a = arguments[i], j = 0, jl = a.length; j < jl; j++, k++)\n r[k] = a[j];\n return r;\n}\n\nexport function __spreadArray(to, from, pack) {\n if (pack || arguments.length === 2) for (var i = 0, l = from.length, ar; i < l; i++) {\n if (ar || !(i in from)) {\n if (!ar) ar = Array.prototype.slice.call(from, 0, i);\n ar[i] = from[i];\n }\n }\n return to.concat(ar || Array.prototype.slice.call(from));\n}\n\nexport function __await(v) {\n return this instanceof __await ? (this.v = v, this) : new __await(v);\n}\n\nexport function __asyncGenerator(thisArg, _arguments, generator) {\n if (!Symbol.asyncIterator) throw new TypeError(\"Symbol.asyncIterator is not defined.\");\n var g = generator.apply(thisArg, _arguments || []), i, q = [];\n return i = Object.create((typeof AsyncIterator === \"function\" ? AsyncIterator : Object).prototype), verb(\"next\"), verb(\"throw\"), verb(\"return\", awaitReturn), i[Symbol.asyncIterator] = function () { return this; }, i;\n function awaitReturn(f) { return function (v) { return Promise.resolve(v).then(f, reject); }; }\n function verb(n, f) { if (g[n]) { i[n] = function (v) { return new Promise(function (a, b) { q.push([n, v, a, b]) > 1 || resume(n, v); }); }; if (f) i[n] = f(i[n]); } }\n function resume(n, v) { try { step(g[n](v)); } catch (e) { settle(q[0][3], e); } }\n function step(r) { r.value instanceof __await ? Promise.resolve(r.value.v).then(fulfill, reject) : settle(q[0][2], r); }\n function fulfill(value) { resume(\"next\", value); }\n function reject(value) { resume(\"throw\", value); }\n function settle(f, v) { if (f(v), q.shift(), q.length) resume(q[0][0], q[0][1]); }\n}\n\nexport function __asyncDelegator(o) {\n var i, p;\n return i = {}, verb(\"next\"), verb(\"throw\", function (e) { throw e; }), verb(\"return\"), i[Symbol.iterator] = function () { return this; }, i;\n function verb(n, f) { i[n] = o[n] ? function (v) { return (p = !p) ? { value: __await(o[n](v)), done: false } : f ? f(v) : v; } : f; }\n}\n\nexport function __asyncValues(o) {\n if (!Symbol.asyncIterator) throw new TypeError(\"Symbol.asyncIterator is not defined.\");\n var m = o[Symbol.asyncIterator], i;\n return m ? m.call(o) : (o = typeof __values === \"function\" ? __values(o) : o[Symbol.iterator](), i = {}, verb(\"next\"), verb(\"throw\"), verb(\"return\"), i[Symbol.asyncIterator] = function () { return this; }, i);\n function verb(n) { i[n] = o[n] && function (v) { return new Promise(function (resolve, reject) { v = o[n](v), settle(resolve, reject, v.done, v.value); }); }; }\n function settle(resolve, reject, d, v) { Promise.resolve(v).then(function(v) { resolve({ value: v, done: d }); }, reject); }\n}\n\nexport function __makeTemplateObject(cooked, raw) {\n if (Object.defineProperty) { Object.defineProperty(cooked, \"raw\", { value: raw }); } else { cooked.raw = raw; }\n return cooked;\n};\n\nvar __setModuleDefault = Object.create ? (function(o, v) {\n Object.defineProperty(o, \"default\", { enumerable: true, value: v });\n}) : function(o, v) {\n o[\"default\"] = v;\n};\n\nexport function __importStar(mod) {\n if (mod && mod.__esModule) return mod;\n var result = {};\n if (mod != null) for (var k in mod) if (k !== \"default\" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);\n __setModuleDefault(result, mod);\n return result;\n}\n\nexport function __importDefault(mod) {\n return (mod && mod.__esModule) ? mod : { default: mod };\n}\n\nexport function __classPrivateFieldGet(receiver, state, kind, f) {\n if (kind === \"a\" && !f) throw new TypeError(\"Private accessor was defined without a getter\");\n if (typeof state === \"function\" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError(\"Cannot read private member from an object whose class did not declare it\");\n return kind === \"m\" ? f : kind === \"a\" ? f.call(receiver) : f ? f.value : state.get(receiver);\n}\n\nexport function __classPrivateFieldSet(receiver, state, value, kind, f) {\n if (kind === \"m\") throw new TypeError(\"Private method is not writable\");\n if (kind === \"a\" && !f) throw new TypeError(\"Private accessor was defined without a setter\");\n if (typeof state === \"function\" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError(\"Cannot write private member to an object whose class did not declare it\");\n return (kind === \"a\" ? f.call(receiver, value) : f ? f.value = value : state.set(receiver, value)), value;\n}\n\nexport function __classPrivateFieldIn(state, receiver) {\n if (receiver === null || (typeof receiver !== \"object\" && typeof receiver !== \"function\")) throw new TypeError(\"Cannot use 'in' operator on non-object\");\n return typeof state === \"function\" ? receiver === state : state.has(receiver);\n}\n\nexport function __addDisposableResource(env, value, async) {\n if (value !== null && value !== void 0) {\n if (typeof value !== \"object\" && typeof value !== \"function\") throw new TypeError(\"Object expected.\");\n var dispose, inner;\n if (async) {\n if (!Symbol.asyncDispose) throw new TypeError(\"Symbol.asyncDispose is not defined.\");\n dispose = value[Symbol.asyncDispose];\n }\n if (dispose === void 0) {\n if (!Symbol.dispose) throw new TypeError(\"Symbol.dispose is not defined.\");\n dispose = value[Symbol.dispose];\n if (async) inner = dispose;\n }\n if (typeof dispose !== \"function\") throw new TypeError(\"Object not disposable.\");\n if (inner) dispose = function() { try { inner.call(this); } catch (e) { return Promise.reject(e); } };\n env.stack.push({ value: value, dispose: dispose, async: async });\n }\n else if (async) {\n env.stack.push({ async: true });\n }\n return value;\n}\n\nvar _SuppressedError = typeof SuppressedError === \"function\" ? SuppressedError : function (error, suppressed, message) {\n var e = new Error(message);\n return e.name = \"SuppressedError\", e.error = error, e.suppressed = suppressed, e;\n};\n\nexport function __disposeResources(env) {\n function fail(e) {\n env.error = env.hasError ? new _SuppressedError(e, env.error, \"An error was suppressed during disposal.\") : e;\n env.hasError = true;\n }\n var r, s = 0;\n function next() {\n while (r = env.stack.pop()) {\n try {\n if (!r.async && s === 1) return s = 0, env.stack.push(r), Promise.resolve().then(next);\n if (r.dispose) {\n var result = r.dispose.call(r.value);\n if (r.async) return s |= 2, Promise.resolve(result).then(next, function(e) { fail(e); return next(); });\n }\n else s |= 1;\n }\n catch (e) {\n fail(e);\n }\n }\n if (s === 1) return env.hasError ? Promise.reject(env.error) : Promise.resolve();\n if (env.hasError) throw env.error;\n }\n return next();\n}\n\nexport default {\n __extends,\n __assign,\n __rest,\n __decorate,\n __param,\n __metadata,\n __awaiter,\n __generator,\n __createBinding,\n __exportStar,\n __values,\n __read,\n __spread,\n __spreadArrays,\n __spreadArray,\n __await,\n __asyncGenerator,\n __asyncDelegator,\n __asyncValues,\n __makeTemplateObject,\n __importStar,\n __importDefault,\n __classPrivateFieldGet,\n __classPrivateFieldSet,\n __classPrivateFieldIn,\n __addDisposableResource,\n __disposeResources,\n};\n", "/**\n * Returns true if the object is a function.\n * @param value The value to check\n */\nexport function isFunction(value: any): value is (...args: any[]) => any {\n return typeof value === 'function';\n}\n", "/**\n * Used to create Error subclasses until the community moves away from ES5.\n *\n * This is because compiling from TypeScript down to ES5 has issues with subclassing Errors\n * as well as other built-in types: https://github.com/Microsoft/TypeScript/issues/12123\n *\n * @param createImpl A factory function to create the actual constructor implementation. The returned\n * function should be a named function that calls `_super` internally.\n */\nexport function createErrorClass(createImpl: (_super: any) => any): T {\n const _super = (instance: any) => {\n Error.call(instance);\n instance.stack = new Error().stack;\n };\n\n const ctorFunc = createImpl(_super);\n ctorFunc.prototype = Object.create(Error.prototype);\n ctorFunc.prototype.constructor = ctorFunc;\n return ctorFunc;\n}\n", "import { createErrorClass } from './createErrorClass';\n\nexport interface UnsubscriptionError extends Error {\n readonly errors: any[];\n}\n\nexport interface UnsubscriptionErrorCtor {\n /**\n * @deprecated Internal implementation detail. Do not construct error instances.\n * Cannot be tagged as internal: https://github.com/ReactiveX/rxjs/issues/6269\n */\n new (errors: any[]): UnsubscriptionError;\n}\n\n/**\n * An error thrown when one or more errors have occurred during the\n * `unsubscribe` of a {@link Subscription}.\n */\nexport const UnsubscriptionError: UnsubscriptionErrorCtor = createErrorClass(\n (_super) =>\n function UnsubscriptionErrorImpl(this: any, errors: (Error | string)[]) {\n _super(this);\n this.message = errors\n ? `${errors.length} errors occurred during unsubscription:\n${errors.map((err, i) => `${i + 1}) ${err.toString()}`).join('\\n ')}`\n : '';\n this.name = 'UnsubscriptionError';\n this.errors = errors;\n }\n);\n", "/**\n * Removes an item from an array, mutating it.\n * @param arr The array to remove the item from\n * @param item The item to remove\n */\nexport function arrRemove(arr: T[] | undefined | null, item: T) {\n if (arr) {\n const index = arr.indexOf(item);\n 0 <= index && arr.splice(index, 1);\n }\n}\n", "import { isFunction } from './util/isFunction';\nimport { UnsubscriptionError } from './util/UnsubscriptionError';\nimport { SubscriptionLike, TeardownLogic, Unsubscribable } from './types';\nimport { arrRemove } from './util/arrRemove';\n\n/**\n * Represents a disposable resource, such as the execution of an Observable. A\n * Subscription has one important method, `unsubscribe`, that takes no argument\n * and just disposes the resource held by the subscription.\n *\n * Additionally, subscriptions may be grouped together through the `add()`\n * method, which will attach a child Subscription to the current Subscription.\n * When a Subscription is unsubscribed, all its children (and its grandchildren)\n * will be unsubscribed as well.\n *\n * @class Subscription\n */\nexport class Subscription implements SubscriptionLike {\n /** @nocollapse */\n public static EMPTY = (() => {\n const empty = new Subscription();\n empty.closed = true;\n return empty;\n })();\n\n /**\n * A flag to indicate whether this Subscription has already been unsubscribed.\n */\n public closed = false;\n\n private _parentage: Subscription[] | Subscription | null = null;\n\n /**\n * The list of registered finalizers to execute upon unsubscription. Adding and removing from this\n * list occurs in the {@link #add} and {@link #remove} methods.\n */\n private _finalizers: Exclude[] | null = null;\n\n /**\n * @param initialTeardown A function executed first as part of the finalization\n * process that is kicked off when {@link #unsubscribe} is called.\n */\n constructor(private initialTeardown?: () => void) {}\n\n /**\n * Disposes the resources held by the subscription. May, for instance, cancel\n * an ongoing Observable execution or cancel any other type of work that\n * started when the Subscription was created.\n * @return {void}\n */\n unsubscribe(): void {\n let errors: any[] | undefined;\n\n if (!this.closed) {\n this.closed = true;\n\n // Remove this from it's parents.\n const { _parentage } = this;\n if (_parentage) {\n this._parentage = null;\n if (Array.isArray(_parentage)) {\n for (const parent of _parentage) {\n parent.remove(this);\n }\n } else {\n _parentage.remove(this);\n }\n }\n\n const { initialTeardown: initialFinalizer } = this;\n if (isFunction(initialFinalizer)) {\n try {\n initialFinalizer();\n } catch (e) {\n errors = e instanceof UnsubscriptionError ? e.errors : [e];\n }\n }\n\n const { _finalizers } = this;\n if (_finalizers) {\n this._finalizers = null;\n for (const finalizer of _finalizers) {\n try {\n execFinalizer(finalizer);\n } catch (err) {\n errors = errors ?? [];\n if (err instanceof UnsubscriptionError) {\n errors = [...errors, ...err.errors];\n } else {\n errors.push(err);\n }\n }\n }\n }\n\n if (errors) {\n throw new UnsubscriptionError(errors);\n }\n }\n }\n\n /**\n * Adds a finalizer to this subscription, so that finalization will be unsubscribed/called\n * when this subscription is unsubscribed. If this subscription is already {@link #closed},\n * because it has already been unsubscribed, then whatever finalizer is passed to it\n * will automatically be executed (unless the finalizer itself is also a closed subscription).\n *\n * Closed Subscriptions cannot be added as finalizers to any subscription. Adding a closed\n * subscription to a any subscription will result in no operation. (A noop).\n *\n * Adding a subscription to itself, or adding `null` or `undefined` will not perform any\n * operation at all. (A noop).\n *\n * `Subscription` instances that are added to this instance will automatically remove themselves\n * if they are unsubscribed. Functions and {@link Unsubscribable} objects that you wish to remove\n * will need to be removed manually with {@link #remove}\n *\n * @param teardown The finalization logic to add to this subscription.\n */\n add(teardown: TeardownLogic): void {\n // Only add the finalizer if it's not undefined\n // and don't add a subscription to itself.\n if (teardown && teardown !== this) {\n if (this.closed) {\n // If this subscription is already closed,\n // execute whatever finalizer is handed to it automatically.\n execFinalizer(teardown);\n } else {\n if (teardown instanceof Subscription) {\n // We don't add closed subscriptions, and we don't add the same subscription\n // twice. Subscription unsubscribe is idempotent.\n if (teardown.closed || teardown._hasParent(this)) {\n return;\n }\n teardown._addParent(this);\n }\n (this._finalizers = this._finalizers ?? []).push(teardown);\n }\n }\n }\n\n /**\n * Checks to see if a this subscription already has a particular parent.\n * This will signal that this subscription has already been added to the parent in question.\n * @param parent the parent to check for\n */\n private _hasParent(parent: Subscription) {\n const { _parentage } = this;\n return _parentage === parent || (Array.isArray(_parentage) && _parentage.includes(parent));\n }\n\n /**\n * Adds a parent to this subscription so it can be removed from the parent if it\n * unsubscribes on it's own.\n *\n * NOTE: THIS ASSUMES THAT {@link _hasParent} HAS ALREADY BEEN CHECKED.\n * @param parent The parent subscription to add\n */\n private _addParent(parent: Subscription) {\n const { _parentage } = this;\n this._parentage = Array.isArray(_parentage) ? (_parentage.push(parent), _parentage) : _parentage ? [_parentage, parent] : parent;\n }\n\n /**\n * Called on a child when it is removed via {@link #remove}.\n * @param parent The parent to remove\n */\n private _removeParent(parent: Subscription) {\n const { _parentage } = this;\n if (_parentage === parent) {\n this._parentage = null;\n } else if (Array.isArray(_parentage)) {\n arrRemove(_parentage, parent);\n }\n }\n\n /**\n * Removes a finalizer from this subscription that was previously added with the {@link #add} method.\n *\n * Note that `Subscription` instances, when unsubscribed, will automatically remove themselves\n * from every other `Subscription` they have been added to. This means that using the `remove` method\n * is not a common thing and should be used thoughtfully.\n *\n * If you add the same finalizer instance of a function or an unsubscribable object to a `Subscription` instance\n * more than once, you will need to call `remove` the same number of times to remove all instances.\n *\n * All finalizer instances are removed to free up memory upon unsubscription.\n *\n * @param teardown The finalizer to remove from this subscription\n */\n remove(teardown: Exclude): void {\n const { _finalizers } = this;\n _finalizers && arrRemove(_finalizers, teardown);\n\n if (teardown instanceof Subscription) {\n teardown._removeParent(this);\n }\n }\n}\n\nexport const EMPTY_SUBSCRIPTION = Subscription.EMPTY;\n\nexport function isSubscription(value: any): value is Subscription {\n return (\n value instanceof Subscription ||\n (value && 'closed' in value && isFunction(value.remove) && isFunction(value.add) && isFunction(value.unsubscribe))\n );\n}\n\nfunction execFinalizer(finalizer: Unsubscribable | (() => void)) {\n if (isFunction(finalizer)) {\n finalizer();\n } else {\n finalizer.unsubscribe();\n }\n}\n", "import { Subscriber } from './Subscriber';\nimport { ObservableNotification } from './types';\n\n/**\n * The {@link GlobalConfig} object for RxJS. It is used to configure things\n * like how to react on unhandled errors.\n */\nexport const config: GlobalConfig = {\n onUnhandledError: null,\n onStoppedNotification: null,\n Promise: undefined,\n useDeprecatedSynchronousErrorHandling: false,\n useDeprecatedNextContext: false,\n};\n\n/**\n * The global configuration object for RxJS, used to configure things\n * like how to react on unhandled errors. Accessible via {@link config}\n * object.\n */\nexport interface GlobalConfig {\n /**\n * A registration point for unhandled errors from RxJS. These are errors that\n * cannot were not handled by consuming code in the usual subscription path. For\n * example, if you have this configured, and you subscribe to an observable without\n * providing an error handler, errors from that subscription will end up here. This\n * will _always_ be called asynchronously on another job in the runtime. This is because\n * we do not want errors thrown in this user-configured handler to interfere with the\n * behavior of the library.\n */\n onUnhandledError: ((err: any) => void) | null;\n\n /**\n * A registration point for notifications that cannot be sent to subscribers because they\n * have completed, errored or have been explicitly unsubscribed. By default, next, complete\n * and error notifications sent to stopped subscribers are noops. However, sometimes callers\n * might want a different behavior. For example, with sources that attempt to report errors\n * to stopped subscribers, a caller can configure RxJS to throw an unhandled error instead.\n * This will _always_ be called asynchronously on another job in the runtime. This is because\n * we do not want errors thrown in this user-configured handler to interfere with the\n * behavior of the library.\n */\n onStoppedNotification: ((notification: ObservableNotification, subscriber: Subscriber) => void) | null;\n\n /**\n * The promise constructor used by default for {@link Observable#toPromise toPromise} and {@link Observable#forEach forEach}\n * methods.\n *\n * @deprecated As of version 8, RxJS will no longer support this sort of injection of a\n * Promise constructor. If you need a Promise implementation other than native promises,\n * please polyfill/patch Promise as you see appropriate. Will be removed in v8.\n */\n Promise?: PromiseConstructorLike;\n\n /**\n * If true, turns on synchronous error rethrowing, which is a deprecated behavior\n * in v6 and higher. This behavior enables bad patterns like wrapping a subscribe\n * call in a try/catch block. It also enables producer interference, a nasty bug\n * where a multicast can be broken for all observers by a downstream consumer with\n * an unhandled error. DO NOT USE THIS FLAG UNLESS IT'S NEEDED TO BUY TIME\n * FOR MIGRATION REASONS.\n *\n * @deprecated As of version 8, RxJS will no longer support synchronous throwing\n * of unhandled errors. All errors will be thrown on a separate call stack to prevent bad\n * behaviors described above. Will be removed in v8.\n */\n useDeprecatedSynchronousErrorHandling: boolean;\n\n /**\n * If true, enables an as-of-yet undocumented feature from v5: The ability to access\n * `unsubscribe()` via `this` context in `next` functions created in observers passed\n * to `subscribe`.\n *\n * This is being removed because the performance was severely problematic, and it could also cause\n * issues when types other than POJOs are passed to subscribe as subscribers, as they will likely have\n * their `this` context overwritten.\n *\n * @deprecated As of version 8, RxJS will no longer support altering the\n * context of next functions provided as part of an observer to Subscribe. Instead,\n * you will have access to a subscription or a signal or token that will allow you to do things like\n * unsubscribe and test closed status. Will be removed in v8.\n */\n useDeprecatedNextContext: boolean;\n}\n", "import type { TimerHandle } from './timerHandle';\ntype SetTimeoutFunction = (handler: () => void, timeout?: number, ...args: any[]) => TimerHandle;\ntype ClearTimeoutFunction = (handle: TimerHandle) => void;\n\ninterface TimeoutProvider {\n setTimeout: SetTimeoutFunction;\n clearTimeout: ClearTimeoutFunction;\n delegate:\n | {\n setTimeout: SetTimeoutFunction;\n clearTimeout: ClearTimeoutFunction;\n }\n | undefined;\n}\n\nexport const timeoutProvider: TimeoutProvider = {\n // When accessing the delegate, use the variable rather than `this` so that\n // the functions can be called without being bound to the provider.\n setTimeout(handler: () => void, timeout?: number, ...args) {\n const { delegate } = timeoutProvider;\n if (delegate?.setTimeout) {\n return delegate.setTimeout(handler, timeout, ...args);\n }\n return setTimeout(handler, timeout, ...args);\n },\n clearTimeout(handle) {\n const { delegate } = timeoutProvider;\n return (delegate?.clearTimeout || clearTimeout)(handle as any);\n },\n delegate: undefined,\n};\n", "import { config } from '../config';\nimport { timeoutProvider } from '../scheduler/timeoutProvider';\n\n/**\n * Handles an error on another job either with the user-configured {@link onUnhandledError},\n * or by throwing it on that new job so it can be picked up by `window.onerror`, `process.on('error')`, etc.\n *\n * This should be called whenever there is an error that is out-of-band with the subscription\n * or when an error hits a terminal boundary of the subscription and no error handler was provided.\n *\n * @param err the error to report\n */\nexport function reportUnhandledError(err: any) {\n timeoutProvider.setTimeout(() => {\n const { onUnhandledError } = config;\n if (onUnhandledError) {\n // Execute the user-configured error handler.\n onUnhandledError(err);\n } else {\n // Throw so it is picked up by the runtime's uncaught error mechanism.\n throw err;\n }\n });\n}\n", "/* tslint:disable:no-empty */\nexport function noop() { }\n", "import { CompleteNotification, NextNotification, ErrorNotification } from './types';\n\n/**\n * A completion object optimized for memory use and created to be the\n * same \"shape\" as other notifications in v8.\n * @internal\n */\nexport const COMPLETE_NOTIFICATION = (() => createNotification('C', undefined, undefined) as CompleteNotification)();\n\n/**\n * Internal use only. Creates an optimized error notification that is the same \"shape\"\n * as other notifications.\n * @internal\n */\nexport function errorNotification(error: any): ErrorNotification {\n return createNotification('E', undefined, error) as any;\n}\n\n/**\n * Internal use only. Creates an optimized next notification that is the same \"shape\"\n * as other notifications.\n * @internal\n */\nexport function nextNotification(value: T) {\n return createNotification('N', value, undefined) as NextNotification;\n}\n\n/**\n * Ensures that all notifications created internally have the same \"shape\" in v8.\n *\n * TODO: This is only exported to support a crazy legacy test in `groupBy`.\n * @internal\n */\nexport function createNotification(kind: 'N' | 'E' | 'C', value: any, error: any) {\n return {\n kind,\n value,\n error,\n };\n}\n", "import { config } from '../config';\n\nlet context: { errorThrown: boolean; error: any } | null = null;\n\n/**\n * Handles dealing with errors for super-gross mode. Creates a context, in which\n * any synchronously thrown errors will be passed to {@link captureError}. Which\n * will record the error such that it will be rethrown after the call back is complete.\n * TODO: Remove in v8\n * @param cb An immediately executed function.\n */\nexport function errorContext(cb: () => void) {\n if (config.useDeprecatedSynchronousErrorHandling) {\n const isRoot = !context;\n if (isRoot) {\n context = { errorThrown: false, error: null };\n }\n cb();\n if (isRoot) {\n const { errorThrown, error } = context!;\n context = null;\n if (errorThrown) {\n throw error;\n }\n }\n } else {\n // This is the general non-deprecated path for everyone that\n // isn't crazy enough to use super-gross mode (useDeprecatedSynchronousErrorHandling)\n cb();\n }\n}\n\n/**\n * Captures errors only in super-gross mode.\n * @param err the error to capture\n */\nexport function captureError(err: any) {\n if (config.useDeprecatedSynchronousErrorHandling && context) {\n context.errorThrown = true;\n context.error = err;\n }\n}\n", "import { isFunction } from './util/isFunction';\nimport { Observer, ObservableNotification } from './types';\nimport { isSubscription, Subscription } from './Subscription';\nimport { config } from './config';\nimport { reportUnhandledError } from './util/reportUnhandledError';\nimport { noop } from './util/noop';\nimport { nextNotification, errorNotification, COMPLETE_NOTIFICATION } from './NotificationFactories';\nimport { timeoutProvider } from './scheduler/timeoutProvider';\nimport { captureError } from './util/errorContext';\n\n/**\n * Implements the {@link Observer} interface and extends the\n * {@link Subscription} class. While the {@link Observer} is the public API for\n * consuming the values of an {@link Observable}, all Observers get converted to\n * a Subscriber, in order to provide Subscription-like capabilities such as\n * `unsubscribe`. Subscriber is a common type in RxJS, and crucial for\n * implementing operators, but it is rarely used as a public API.\n *\n * @class Subscriber\n */\nexport class Subscriber extends Subscription implements Observer {\n /**\n * A static factory for a Subscriber, given a (potentially partial) definition\n * of an Observer.\n * @param next The `next` callback of an Observer.\n * @param error The `error` callback of an\n * Observer.\n * @param complete The `complete` callback of an\n * Observer.\n * @return A Subscriber wrapping the (partially defined)\n * Observer represented by the given arguments.\n * @nocollapse\n * @deprecated Do not use. Will be removed in v8. There is no replacement for this\n * method, and there is no reason to be creating instances of `Subscriber` directly.\n * If you have a specific use case, please file an issue.\n */\n static create(next?: (x?: T) => void, error?: (e?: any) => void, complete?: () => void): Subscriber {\n return new SafeSubscriber(next, error, complete);\n }\n\n /** @deprecated Internal implementation detail, do not use directly. Will be made internal in v8. */\n protected isStopped: boolean = false;\n /** @deprecated Internal implementation detail, do not use directly. Will be made internal in v8. */\n protected destination: Subscriber | Observer; // this `any` is the escape hatch to erase extra type param (e.g. R)\n\n /**\n * @deprecated Internal implementation detail, do not use directly. Will be made internal in v8.\n * There is no reason to directly create an instance of Subscriber. This type is exported for typings reasons.\n */\n constructor(destination?: Subscriber | Observer) {\n super();\n if (destination) {\n this.destination = destination;\n // Automatically chain subscriptions together here.\n // if destination is a Subscription, then it is a Subscriber.\n if (isSubscription(destination)) {\n destination.add(this);\n }\n } else {\n this.destination = EMPTY_OBSERVER;\n }\n }\n\n /**\n * The {@link Observer} callback to receive notifications of type `next` from\n * the Observable, with a value. The Observable may call this method 0 or more\n * times.\n * @param {T} [value] The `next` value.\n * @return {void}\n */\n next(value?: T): void {\n if (this.isStopped) {\n handleStoppedNotification(nextNotification(value), this);\n } else {\n this._next(value!);\n }\n }\n\n /**\n * The {@link Observer} callback to receive notifications of type `error` from\n * the Observable, with an attached `Error`. Notifies the Observer that\n * the Observable has experienced an error condition.\n * @param {any} [err] The `error` exception.\n * @return {void}\n */\n error(err?: any): void {\n if (this.isStopped) {\n handleStoppedNotification(errorNotification(err), this);\n } else {\n this.isStopped = true;\n this._error(err);\n }\n }\n\n /**\n * The {@link Observer} callback to receive a valueless notification of type\n * `complete` from the Observable. Notifies the Observer that the Observable\n * has finished sending push-based notifications.\n * @return {void}\n */\n complete(): void {\n if (this.isStopped) {\n handleStoppedNotification(COMPLETE_NOTIFICATION, this);\n } else {\n this.isStopped = true;\n this._complete();\n }\n }\n\n unsubscribe(): void {\n if (!this.closed) {\n this.isStopped = true;\n super.unsubscribe();\n this.destination = null!;\n }\n }\n\n protected _next(value: T): void {\n this.destination.next(value);\n }\n\n protected _error(err: any): void {\n try {\n this.destination.error(err);\n } finally {\n this.unsubscribe();\n }\n }\n\n protected _complete(): void {\n try {\n this.destination.complete();\n } finally {\n this.unsubscribe();\n }\n }\n}\n\n/**\n * This bind is captured here because we want to be able to have\n * compatibility with monoid libraries that tend to use a method named\n * `bind`. In particular, a library called Monio requires this.\n */\nconst _bind = Function.prototype.bind;\n\nfunction bind any>(fn: Fn, thisArg: any): Fn {\n return _bind.call(fn, thisArg);\n}\n\n/**\n * Internal optimization only, DO NOT EXPOSE.\n * @internal\n */\nclass ConsumerObserver implements Observer {\n constructor(private partialObserver: Partial>) {}\n\n next(value: T): void {\n const { partialObserver } = this;\n if (partialObserver.next) {\n try {\n partialObserver.next(value);\n } catch (error) {\n handleUnhandledError(error);\n }\n }\n }\n\n error(err: any): void {\n const { partialObserver } = this;\n if (partialObserver.error) {\n try {\n partialObserver.error(err);\n } catch (error) {\n handleUnhandledError(error);\n }\n } else {\n handleUnhandledError(err);\n }\n }\n\n complete(): void {\n const { partialObserver } = this;\n if (partialObserver.complete) {\n try {\n partialObserver.complete();\n } catch (error) {\n handleUnhandledError(error);\n }\n }\n }\n}\n\nexport class SafeSubscriber extends Subscriber {\n constructor(\n observerOrNext?: Partial> | ((value: T) => void) | null,\n error?: ((e?: any) => void) | null,\n complete?: (() => void) | null\n ) {\n super();\n\n let partialObserver: Partial>;\n if (isFunction(observerOrNext) || !observerOrNext) {\n // The first argument is a function, not an observer. The next\n // two arguments *could* be observers, or they could be empty.\n partialObserver = {\n next: (observerOrNext ?? undefined) as (((value: T) => void) | undefined),\n error: error ?? undefined,\n complete: complete ?? undefined,\n };\n } else {\n // The first argument is a partial observer.\n let context: any;\n if (this && config.useDeprecatedNextContext) {\n // This is a deprecated path that made `this.unsubscribe()` available in\n // next handler functions passed to subscribe. This only exists behind a flag\n // now, as it is *very* slow.\n context = Object.create(observerOrNext);\n context.unsubscribe = () => this.unsubscribe();\n partialObserver = {\n next: observerOrNext.next && bind(observerOrNext.next, context),\n error: observerOrNext.error && bind(observerOrNext.error, context),\n complete: observerOrNext.complete && bind(observerOrNext.complete, context),\n };\n } else {\n // The \"normal\" path. Just use the partial observer directly.\n partialObserver = observerOrNext;\n }\n }\n\n // Wrap the partial observer to ensure it's a full observer, and\n // make sure proper error handling is accounted for.\n this.destination = new ConsumerObserver(partialObserver);\n }\n}\n\nfunction handleUnhandledError(error: any) {\n if (config.useDeprecatedSynchronousErrorHandling) {\n captureError(error);\n } else {\n // Ideal path, we report this as an unhandled error,\n // which is thrown on a new call stack.\n reportUnhandledError(error);\n }\n}\n\n/**\n * An error handler used when no error handler was supplied\n * to the SafeSubscriber -- meaning no error handler was supplied\n * do the `subscribe` call on our observable.\n * @param err The error to handle\n */\nfunction defaultErrorHandler(err: any) {\n throw err;\n}\n\n/**\n * A handler for notifications that cannot be sent to a stopped subscriber.\n * @param notification The notification being sent\n * @param subscriber The stopped subscriber\n */\nfunction handleStoppedNotification(notification: ObservableNotification, subscriber: Subscriber) {\n const { onStoppedNotification } = config;\n onStoppedNotification && timeoutProvider.setTimeout(() => onStoppedNotification(notification, subscriber));\n}\n\n/**\n * The observer used as a stub for subscriptions where the user did not\n * pass any arguments to `subscribe`. Comes with the default error handling\n * behavior.\n */\nexport const EMPTY_OBSERVER: Readonly> & { closed: true } = {\n closed: true,\n next: noop,\n error: defaultErrorHandler,\n complete: noop,\n};\n", "/**\n * Symbol.observable or a string \"@@observable\". Used for interop\n *\n * @deprecated We will no longer be exporting this symbol in upcoming versions of RxJS.\n * Instead polyfill and use Symbol.observable directly *or* use https://www.npmjs.com/package/symbol-observable\n */\nexport const observable: string | symbol = (() => (typeof Symbol === 'function' && Symbol.observable) || '@@observable')();\n", "/**\n * This function takes one parameter and just returns it. Simply put,\n * this is like `(x: T): T => x`.\n *\n * ## Examples\n *\n * This is useful in some cases when using things like `mergeMap`\n *\n * ```ts\n * import { interval, take, map, range, mergeMap, identity } from 'rxjs';\n *\n * const source$ = interval(1000).pipe(take(5));\n *\n * const result$ = source$.pipe(\n * map(i => range(i)),\n * mergeMap(identity) // same as mergeMap(x => x)\n * );\n *\n * result$.subscribe({\n * next: console.log\n * });\n * ```\n *\n * Or when you want to selectively apply an operator\n *\n * ```ts\n * import { interval, take, identity } from 'rxjs';\n *\n * const shouldLimit = () => Math.random() < 0.5;\n *\n * const source$ = interval(1000);\n *\n * const result$ = source$.pipe(shouldLimit() ? take(5) : identity);\n *\n * result$.subscribe({\n * next: console.log\n * });\n * ```\n *\n * @param x Any value that is returned by this function\n * @returns The value passed as the first parameter to this function\n */\nexport function identity(x: T): T {\n return x;\n}\n", "import { identity } from './identity';\nimport { UnaryFunction } from '../types';\n\nexport function pipe(): typeof identity;\nexport function pipe(fn1: UnaryFunction): UnaryFunction;\nexport function pipe(fn1: UnaryFunction, fn2: UnaryFunction): UnaryFunction;\nexport function pipe(fn1: UnaryFunction, fn2: UnaryFunction, fn3: UnaryFunction): UnaryFunction;\nexport function pipe(\n fn1: UnaryFunction,\n fn2: UnaryFunction,\n fn3: UnaryFunction,\n fn4: UnaryFunction\n): UnaryFunction;\nexport function pipe(\n fn1: UnaryFunction,\n fn2: UnaryFunction,\n fn3: UnaryFunction,\n fn4: UnaryFunction,\n fn5: UnaryFunction\n): UnaryFunction;\nexport function pipe(\n fn1: UnaryFunction,\n fn2: UnaryFunction,\n fn3: UnaryFunction,\n fn4: UnaryFunction,\n fn5: UnaryFunction,\n fn6: UnaryFunction\n): UnaryFunction;\nexport function pipe(\n fn1: UnaryFunction,\n fn2: UnaryFunction,\n fn3: UnaryFunction,\n fn4: UnaryFunction,\n fn5: UnaryFunction,\n fn6: UnaryFunction,\n fn7: UnaryFunction\n): UnaryFunction;\nexport function pipe(\n fn1: UnaryFunction,\n fn2: UnaryFunction,\n fn3: UnaryFunction,\n fn4: UnaryFunction,\n fn5: UnaryFunction,\n fn6: UnaryFunction,\n fn7: UnaryFunction,\n fn8: UnaryFunction\n): UnaryFunction;\nexport function pipe(\n fn1: UnaryFunction,\n fn2: UnaryFunction,\n fn3: UnaryFunction,\n fn4: UnaryFunction,\n fn5: UnaryFunction,\n fn6: UnaryFunction,\n fn7: UnaryFunction,\n fn8: UnaryFunction,\n fn9: UnaryFunction\n): UnaryFunction;\nexport function pipe(\n fn1: UnaryFunction,\n fn2: UnaryFunction,\n fn3: UnaryFunction,\n fn4: UnaryFunction,\n fn5: UnaryFunction,\n fn6: UnaryFunction,\n fn7: UnaryFunction,\n fn8: UnaryFunction,\n fn9: UnaryFunction,\n ...fns: UnaryFunction[]\n): UnaryFunction;\n\n/**\n * pipe() can be called on one or more functions, each of which can take one argument (\"UnaryFunction\")\n * and uses it to return a value.\n * It returns a function that takes one argument, passes it to the first UnaryFunction, and then\n * passes the result to the next one, passes that result to the next one, and so on. \n */\nexport function pipe(...fns: Array>): UnaryFunction {\n return pipeFromArray(fns);\n}\n\n/** @internal */\nexport function pipeFromArray(fns: Array>): UnaryFunction {\n if (fns.length === 0) {\n return identity as UnaryFunction;\n }\n\n if (fns.length === 1) {\n return fns[0];\n }\n\n return function piped(input: T): R {\n return fns.reduce((prev: any, fn: UnaryFunction) => fn(prev), input as any);\n };\n}\n", "import { Operator } from './Operator';\nimport { SafeSubscriber, Subscriber } from './Subscriber';\nimport { isSubscription, Subscription } from './Subscription';\nimport { TeardownLogic, OperatorFunction, Subscribable, Observer } from './types';\nimport { observable as Symbol_observable } from './symbol/observable';\nimport { pipeFromArray } from './util/pipe';\nimport { config } from './config';\nimport { isFunction } from './util/isFunction';\nimport { errorContext } from './util/errorContext';\n\n/**\n * A representation of any set of values over any amount of time. This is the most basic building block\n * of RxJS.\n *\n * @class Observable\n */\nexport class Observable implements Subscribable {\n /**\n * @deprecated Internal implementation detail, do not use directly. Will be made internal in v8.\n */\n source: Observable | undefined;\n\n /**\n * @deprecated Internal implementation detail, do not use directly. Will be made internal in v8.\n */\n operator: Operator | undefined;\n\n /**\n * @constructor\n * @param {Function} subscribe the function that is called when the Observable is\n * initially subscribed to. This function is given a Subscriber, to which new values\n * can be `next`ed, or an `error` method can be called to raise an error, or\n * `complete` can be called to notify of a successful completion.\n */\n constructor(subscribe?: (this: Observable, subscriber: Subscriber) => TeardownLogic) {\n if (subscribe) {\n this._subscribe = subscribe;\n }\n }\n\n // HACK: Since TypeScript inherits static properties too, we have to\n // fight against TypeScript here so Subject can have a different static create signature\n /**\n * Creates a new Observable by calling the Observable constructor\n * @owner Observable\n * @method create\n * @param {Function} subscribe? the subscriber function to be passed to the Observable constructor\n * @return {Observable} a new observable\n * @nocollapse\n * @deprecated Use `new Observable()` instead. Will be removed in v8.\n */\n static create: (...args: any[]) => any = (subscribe?: (subscriber: Subscriber) => TeardownLogic) => {\n return new Observable(subscribe);\n };\n\n /**\n * Creates a new Observable, with this Observable instance as the source, and the passed\n * operator defined as the new observable's operator.\n * @method lift\n * @param operator the operator defining the operation to take on the observable\n * @return a new observable with the Operator applied\n * @deprecated Internal implementation detail, do not use directly. Will be made internal in v8.\n * If you have implemented an operator using `lift`, it is recommended that you create an\n * operator by simply returning `new Observable()` directly. See \"Creating new operators from\n * scratch\" section here: https://rxjs.dev/guide/operators\n */\n lift(operator?: Operator): Observable {\n const observable = new Observable();\n observable.source = this;\n observable.operator = operator;\n return observable;\n }\n\n subscribe(observerOrNext?: Partial> | ((value: T) => void)): Subscription;\n /** @deprecated Instead of passing separate callback arguments, use an observer argument. Signatures taking separate callback arguments will be removed in v8. Details: https://rxjs.dev/deprecations/subscribe-arguments */\n subscribe(next?: ((value: T) => void) | null, error?: ((error: any) => void) | null, complete?: (() => void) | null): Subscription;\n /**\n * Invokes an execution of an Observable and registers Observer handlers for notifications it will emit.\n *\n * Use it when you have all these Observables, but still nothing is happening.\n *\n * `subscribe` is not a regular operator, but a method that calls Observable's internal `subscribe` function. It\n * might be for example a function that you passed to Observable's constructor, but most of the time it is\n * a library implementation, which defines what will be emitted by an Observable, and when it be will emitted. This means\n * that calling `subscribe` is actually the moment when Observable starts its work, not when it is created, as it is often\n * the thought.\n *\n * Apart from starting the execution of an Observable, this method allows you to listen for values\n * that an Observable emits, as well as for when it completes or errors. You can achieve this in two\n * of the following ways.\n *\n * The first way is creating an object that implements {@link Observer} interface. It should have methods\n * defined by that interface, but note that it should be just a regular JavaScript object, which you can create\n * yourself in any way you want (ES6 class, classic function constructor, object literal etc.). In particular, do\n * not attempt to use any RxJS implementation details to create Observers - you don't need them. Remember also\n * that your object does not have to implement all methods. If you find yourself creating a method that doesn't\n * do anything, you can simply omit it. Note however, if the `error` method is not provided and an error happens,\n * it will be thrown asynchronously. Errors thrown asynchronously cannot be caught using `try`/`catch`. Instead,\n * use the {@link onUnhandledError} configuration option or use a runtime handler (like `window.onerror` or\n * `process.on('error)`) to be notified of unhandled errors. Because of this, it's recommended that you provide\n * an `error` method to avoid missing thrown errors.\n *\n * The second way is to give up on Observer object altogether and simply provide callback functions in place of its methods.\n * This means you can provide three functions as arguments to `subscribe`, where the first function is equivalent\n * of a `next` method, the second of an `error` method and the third of a `complete` method. Just as in case of an Observer,\n * if you do not need to listen for something, you can omit a function by passing `undefined` or `null`,\n * since `subscribe` recognizes these functions by where they were placed in function call. When it comes\n * to the `error` function, as with an Observer, if not provided, errors emitted by an Observable will be thrown asynchronously.\n *\n * You can, however, subscribe with no parameters at all. This may be the case where you're not interested in terminal events\n * and you also handled emissions internally by using operators (e.g. using `tap`).\n *\n * Whichever style of calling `subscribe` you use, in both cases it returns a Subscription object.\n * This object allows you to call `unsubscribe` on it, which in turn will stop the work that an Observable does and will clean\n * up all resources that an Observable used. Note that cancelling a subscription will not call `complete` callback\n * provided to `subscribe` function, which is reserved for a regular completion signal that comes from an Observable.\n *\n * Remember that callbacks provided to `subscribe` are not guaranteed to be called asynchronously.\n * It is an Observable itself that decides when these functions will be called. For example {@link of}\n * by default emits all its values synchronously. Always check documentation for how given Observable\n * will behave when subscribed and if its default behavior can be modified with a `scheduler`.\n *\n * #### Examples\n *\n * Subscribe with an {@link guide/observer Observer}\n *\n * ```ts\n * import { of } from 'rxjs';\n *\n * const sumObserver = {\n * sum: 0,\n * next(value) {\n * console.log('Adding: ' + value);\n * this.sum = this.sum + value;\n * },\n * error() {\n * // We actually could just remove this method,\n * // since we do not really care about errors right now.\n * },\n * complete() {\n * console.log('Sum equals: ' + this.sum);\n * }\n * };\n *\n * of(1, 2, 3) // Synchronously emits 1, 2, 3 and then completes.\n * .subscribe(sumObserver);\n *\n * // Logs:\n * // 'Adding: 1'\n * // 'Adding: 2'\n * // 'Adding: 3'\n * // 'Sum equals: 6'\n * ```\n *\n * Subscribe with functions ({@link deprecations/subscribe-arguments deprecated})\n *\n * ```ts\n * import { of } from 'rxjs'\n *\n * let sum = 0;\n *\n * of(1, 2, 3).subscribe(\n * value => {\n * console.log('Adding: ' + value);\n * sum = sum + value;\n * },\n * undefined,\n * () => console.log('Sum equals: ' + sum)\n * );\n *\n * // Logs:\n * // 'Adding: 1'\n * // 'Adding: 2'\n * // 'Adding: 3'\n * // 'Sum equals: 6'\n * ```\n *\n * Cancel a subscription\n *\n * ```ts\n * import { interval } from 'rxjs';\n *\n * const subscription = interval(1000).subscribe({\n * next(num) {\n * console.log(num)\n * },\n * complete() {\n * // Will not be called, even when cancelling subscription.\n * console.log('completed!');\n * }\n * });\n *\n * setTimeout(() => {\n * subscription.unsubscribe();\n * console.log('unsubscribed!');\n * }, 2500);\n *\n * // Logs:\n * // 0 after 1s\n * // 1 after 2s\n * // 'unsubscribed!' after 2.5s\n * ```\n *\n * @param {Observer|Function} observerOrNext (optional) Either an observer with methods to be called,\n * or the first of three possible handlers, which is the handler for each value emitted from the subscribed\n * Observable.\n * @param {Function} error (optional) A handler for a terminal event resulting from an error. If no error handler is provided,\n * the error will be thrown asynchronously as unhandled.\n * @param {Function} complete (optional) A handler for a terminal event resulting from successful completion.\n * @return {Subscription} a subscription reference to the registered handlers\n * @method subscribe\n */\n subscribe(\n observerOrNext?: Partial> | ((value: T) => void) | null,\n error?: ((error: any) => void) | null,\n complete?: (() => void) | null\n ): Subscription {\n const subscriber = isSubscriber(observerOrNext) ? observerOrNext : new SafeSubscriber(observerOrNext, error, complete);\n\n errorContext(() => {\n const { operator, source } = this;\n subscriber.add(\n operator\n ? // We're dealing with a subscription in the\n // operator chain to one of our lifted operators.\n operator.call(subscriber, source)\n : source\n ? // If `source` has a value, but `operator` does not, something that\n // had intimate knowledge of our API, like our `Subject`, must have\n // set it. We're going to just call `_subscribe` directly.\n this._subscribe(subscriber)\n : // In all other cases, we're likely wrapping a user-provided initializer\n // function, so we need to catch errors and handle them appropriately.\n this._trySubscribe(subscriber)\n );\n });\n\n return subscriber;\n }\n\n /** @internal */\n protected _trySubscribe(sink: Subscriber): TeardownLogic {\n try {\n return this._subscribe(sink);\n } catch (err) {\n // We don't need to return anything in this case,\n // because it's just going to try to `add()` to a subscription\n // above.\n sink.error(err);\n }\n }\n\n /**\n * Used as a NON-CANCELLABLE means of subscribing to an observable, for use with\n * APIs that expect promises, like `async/await`. You cannot unsubscribe from this.\n *\n * **WARNING**: Only use this with observables you *know* will complete. If the source\n * observable does not complete, you will end up with a promise that is hung up, and\n * potentially all of the state of an async function hanging out in memory. To avoid\n * this situation, look into adding something like {@link timeout}, {@link take},\n * {@link takeWhile}, or {@link takeUntil} amongst others.\n *\n * #### Example\n *\n * ```ts\n * import { interval, take } from 'rxjs';\n *\n * const source$ = interval(1000).pipe(take(4));\n *\n * async function getTotal() {\n * let total = 0;\n *\n * await source$.forEach(value => {\n * total += value;\n * console.log('observable -> ' + value);\n * });\n *\n * return total;\n * }\n *\n * getTotal().then(\n * total => console.log('Total: ' + total)\n * );\n *\n * // Expected:\n * // 'observable -> 0'\n * // 'observable -> 1'\n * // 'observable -> 2'\n * // 'observable -> 3'\n * // 'Total: 6'\n * ```\n *\n * @param next a handler for each value emitted by the observable\n * @return a promise that either resolves on observable completion or\n * rejects with the handled error\n */\n forEach(next: (value: T) => void): Promise;\n\n /**\n * @param next a handler for each value emitted by the observable\n * @param promiseCtor a constructor function used to instantiate the Promise\n * @return a promise that either resolves on observable completion or\n * rejects with the handled error\n * @deprecated Passing a Promise constructor will no longer be available\n * in upcoming versions of RxJS. This is because it adds weight to the library, for very\n * little benefit. If you need this functionality, it is recommended that you either\n * polyfill Promise, or you create an adapter to convert the returned native promise\n * to whatever promise implementation you wanted. Will be removed in v8.\n */\n forEach(next: (value: T) => void, promiseCtor: PromiseConstructorLike): Promise;\n\n forEach(next: (value: T) => void, promiseCtor?: PromiseConstructorLike): Promise {\n promiseCtor = getPromiseCtor(promiseCtor);\n\n return new promiseCtor((resolve, reject) => {\n const subscriber = new SafeSubscriber({\n next: (value) => {\n try {\n next(value);\n } catch (err) {\n reject(err);\n subscriber.unsubscribe();\n }\n },\n error: reject,\n complete: resolve,\n });\n this.subscribe(subscriber);\n }) as Promise;\n }\n\n /** @internal */\n protected _subscribe(subscriber: Subscriber): TeardownLogic {\n return this.source?.subscribe(subscriber);\n }\n\n /**\n * An interop point defined by the es7-observable spec https://github.com/zenparsing/es-observable\n * @method Symbol.observable\n * @return {Observable} this instance of the observable\n */\n [Symbol_observable]() {\n return this;\n }\n\n /* tslint:disable:max-line-length */\n pipe(): Observable;\n pipe(op1: OperatorFunction): Observable;\n pipe(op1: OperatorFunction, op2: OperatorFunction): Observable;\n pipe(op1: OperatorFunction, op2: OperatorFunction, op3: OperatorFunction): Observable;\n pipe(\n op1: OperatorFunction,\n op2: OperatorFunction,\n op3: OperatorFunction,\n op4: OperatorFunction\n ): Observable;\n pipe(\n op1: OperatorFunction,\n op2: OperatorFunction,\n op3: OperatorFunction,\n op4: OperatorFunction,\n op5: OperatorFunction\n ): Observable;\n pipe(\n op1: OperatorFunction,\n op2: OperatorFunction,\n op3: OperatorFunction,\n op4: OperatorFunction,\n op5: OperatorFunction,\n op6: OperatorFunction\n ): Observable;\n pipe(\n op1: OperatorFunction,\n op2: OperatorFunction,\n op3: OperatorFunction,\n op4: OperatorFunction,\n op5: OperatorFunction,\n op6: OperatorFunction,\n op7: OperatorFunction\n ): Observable;\n pipe(\n op1: OperatorFunction,\n op2: OperatorFunction,\n op3: OperatorFunction,\n op4: OperatorFunction,\n op5: OperatorFunction,\n op6: OperatorFunction,\n op7: OperatorFunction,\n op8: OperatorFunction\n ): Observable;\n pipe(\n op1: OperatorFunction,\n op2: OperatorFunction,\n op3: OperatorFunction,\n op4: OperatorFunction,\n op5: OperatorFunction,\n op6: OperatorFunction,\n op7: OperatorFunction,\n op8: OperatorFunction,\n op9: OperatorFunction\n ): Observable;\n pipe(\n op1: OperatorFunction,\n op2: OperatorFunction,\n op3: OperatorFunction,\n op4: OperatorFunction,\n op5: OperatorFunction,\n op6: OperatorFunction,\n op7: OperatorFunction,\n op8: OperatorFunction,\n op9: OperatorFunction,\n ...operations: OperatorFunction[]\n ): Observable;\n /* tslint:enable:max-line-length */\n\n /**\n * Used to stitch together functional operators into a chain.\n * @method pipe\n * @return {Observable} the Observable result of all of the operators having\n * been called in the order they were passed in.\n *\n * ## Example\n *\n * ```ts\n * import { interval, filter, map, scan } from 'rxjs';\n *\n * interval(1000)\n * .pipe(\n * filter(x => x % 2 === 0),\n * map(x => x + x),\n * scan((acc, x) => acc + x)\n * )\n * .subscribe(x => console.log(x));\n * ```\n */\n pipe(...operations: OperatorFunction[]): Observable {\n return pipeFromArray(operations)(this);\n }\n\n /* tslint:disable:max-line-length */\n /** @deprecated Replaced with {@link firstValueFrom} and {@link lastValueFrom}. Will be removed in v8. Details: https://rxjs.dev/deprecations/to-promise */\n toPromise(): Promise;\n /** @deprecated Replaced with {@link firstValueFrom} and {@link lastValueFrom}. Will be removed in v8. Details: https://rxjs.dev/deprecations/to-promise */\n toPromise(PromiseCtor: typeof Promise): Promise;\n /** @deprecated Replaced with {@link firstValueFrom} and {@link lastValueFrom}. Will be removed in v8. Details: https://rxjs.dev/deprecations/to-promise */\n toPromise(PromiseCtor: PromiseConstructorLike): Promise;\n /* tslint:enable:max-line-length */\n\n /**\n * Subscribe to this Observable and get a Promise resolving on\n * `complete` with the last emission (if any).\n *\n * **WARNING**: Only use this with observables you *know* will complete. If the source\n * observable does not complete, you will end up with a promise that is hung up, and\n * potentially all of the state of an async function hanging out in memory. To avoid\n * this situation, look into adding something like {@link timeout}, {@link take},\n * {@link takeWhile}, or {@link takeUntil} amongst others.\n *\n * @method toPromise\n * @param [promiseCtor] a constructor function used to instantiate\n * the Promise\n * @return A Promise that resolves with the last value emit, or\n * rejects on an error. If there were no emissions, Promise\n * resolves with undefined.\n * @deprecated Replaced with {@link firstValueFrom} and {@link lastValueFrom}. Will be removed in v8. Details: https://rxjs.dev/deprecations/to-promise\n */\n toPromise(promiseCtor?: PromiseConstructorLike): Promise {\n promiseCtor = getPromiseCtor(promiseCtor);\n\n return new promiseCtor((resolve, reject) => {\n let value: T | undefined;\n this.subscribe(\n (x: T) => (value = x),\n (err: any) => reject(err),\n () => resolve(value)\n );\n }) as Promise;\n }\n}\n\n/**\n * Decides between a passed promise constructor from consuming code,\n * A default configured promise constructor, and the native promise\n * constructor and returns it. If nothing can be found, it will throw\n * an error.\n * @param promiseCtor The optional promise constructor to passed by consuming code\n */\nfunction getPromiseCtor(promiseCtor: PromiseConstructorLike | undefined) {\n return promiseCtor ?? config.Promise ?? Promise;\n}\n\nfunction isObserver(value: any): value is Observer {\n return value && isFunction(value.next) && isFunction(value.error) && isFunction(value.complete);\n}\n\nfunction isSubscriber(value: any): value is Subscriber {\n return (value && value instanceof Subscriber) || (isObserver(value) && isSubscription(value));\n}\n", "import { Observable } from '../Observable';\nimport { Subscriber } from '../Subscriber';\nimport { OperatorFunction } from '../types';\nimport { isFunction } from './isFunction';\n\n/**\n * Used to determine if an object is an Observable with a lift function.\n */\nexport function hasLift(source: any): source is { lift: InstanceType['lift'] } {\n return isFunction(source?.lift);\n}\n\n/**\n * Creates an `OperatorFunction`. Used to define operators throughout the library in a concise way.\n * @param init The logic to connect the liftedSource to the subscriber at the moment of subscription.\n */\nexport function operate(\n init: (liftedSource: Observable, subscriber: Subscriber) => (() => void) | void\n): OperatorFunction {\n return (source: Observable) => {\n if (hasLift(source)) {\n return source.lift(function (this: Subscriber, liftedSource: Observable) {\n try {\n return init(liftedSource, this);\n } catch (err) {\n this.error(err);\n }\n });\n }\n throw new TypeError('Unable to lift unknown Observable type');\n };\n}\n", "import { Subscriber } from '../Subscriber';\n\n/**\n * Creates an instance of an `OperatorSubscriber`.\n * @param destination The downstream subscriber.\n * @param onNext Handles next values, only called if this subscriber is not stopped or closed. Any\n * error that occurs in this function is caught and sent to the `error` method of this subscriber.\n * @param onError Handles errors from the subscription, any errors that occur in this handler are caught\n * and send to the `destination` error handler.\n * @param onComplete Handles completion notification from the subscription. Any errors that occur in\n * this handler are sent to the `destination` error handler.\n * @param onFinalize Additional teardown logic here. This will only be called on teardown if the\n * subscriber itself is not already closed. This is called after all other teardown logic is executed.\n */\nexport function createOperatorSubscriber(\n destination: Subscriber,\n onNext?: (value: T) => void,\n onComplete?: () => void,\n onError?: (err: any) => void,\n onFinalize?: () => void\n): Subscriber {\n return new OperatorSubscriber(destination, onNext, onComplete, onError, onFinalize);\n}\n\n/**\n * A generic helper for allowing operators to be created with a Subscriber and\n * use closures to capture necessary state from the operator function itself.\n */\nexport class OperatorSubscriber extends Subscriber {\n /**\n * Creates an instance of an `OperatorSubscriber`.\n * @param destination The downstream subscriber.\n * @param onNext Handles next values, only called if this subscriber is not stopped or closed. Any\n * error that occurs in this function is caught and sent to the `error` method of this subscriber.\n * @param onError Handles errors from the subscription, any errors that occur in this handler are caught\n * and send to the `destination` error handler.\n * @param onComplete Handles completion notification from the subscription. Any errors that occur in\n * this handler are sent to the `destination` error handler.\n * @param onFinalize Additional finalization logic here. This will only be called on finalization if the\n * subscriber itself is not already closed. This is called after all other finalization logic is executed.\n * @param shouldUnsubscribe An optional check to see if an unsubscribe call should truly unsubscribe.\n * NOTE: This currently **ONLY** exists to support the strange behavior of {@link groupBy}, where unsubscription\n * to the resulting observable does not actually disconnect from the source if there are active subscriptions\n * to any grouped observable. (DO NOT EXPOSE OR USE EXTERNALLY!!!)\n */\n constructor(\n destination: Subscriber,\n onNext?: (value: T) => void,\n onComplete?: () => void,\n onError?: (err: any) => void,\n private onFinalize?: () => void,\n private shouldUnsubscribe?: () => boolean\n ) {\n // It's important - for performance reasons - that all of this class's\n // members are initialized and that they are always initialized in the same\n // order. This will ensure that all OperatorSubscriber instances have the\n // same hidden class in V8. This, in turn, will help keep the number of\n // hidden classes involved in property accesses within the base class as\n // low as possible. If the number of hidden classes involved exceeds four,\n // the property accesses will become megamorphic and performance penalties\n // will be incurred - i.e. inline caches won't be used.\n //\n // The reasons for ensuring all instances have the same hidden class are\n // further discussed in this blog post from Benedikt Meurer:\n // https://benediktmeurer.de/2018/03/23/impact-of-polymorphism-on-component-based-frameworks-like-react/\n super(destination);\n this._next = onNext\n ? function (this: OperatorSubscriber, value: T) {\n try {\n onNext(value);\n } catch (err) {\n destination.error(err);\n }\n }\n : super._next;\n this._error = onError\n ? function (this: OperatorSubscriber, err: any) {\n try {\n onError(err);\n } catch (err) {\n // Send any errors that occur down stream.\n destination.error(err);\n } finally {\n // Ensure finalization.\n this.unsubscribe();\n }\n }\n : super._error;\n this._complete = onComplete\n ? function (this: OperatorSubscriber) {\n try {\n onComplete();\n } catch (err) {\n // Send any errors that occur down stream.\n destination.error(err);\n } finally {\n // Ensure finalization.\n this.unsubscribe();\n }\n }\n : super._complete;\n }\n\n unsubscribe() {\n if (!this.shouldUnsubscribe || this.shouldUnsubscribe()) {\n const { closed } = this;\n super.unsubscribe();\n // Execute additional teardown if we have any and we didn't already do so.\n !closed && this.onFinalize?.();\n }\n }\n}\n", "import { Subscription } from '../Subscription';\n\ninterface AnimationFrameProvider {\n schedule(callback: FrameRequestCallback): Subscription;\n requestAnimationFrame: typeof requestAnimationFrame;\n cancelAnimationFrame: typeof cancelAnimationFrame;\n delegate:\n | {\n requestAnimationFrame: typeof requestAnimationFrame;\n cancelAnimationFrame: typeof cancelAnimationFrame;\n }\n | undefined;\n}\n\nexport const animationFrameProvider: AnimationFrameProvider = {\n // When accessing the delegate, use the variable rather than `this` so that\n // the functions can be called without being bound to the provider.\n schedule(callback) {\n let request = requestAnimationFrame;\n let cancel: typeof cancelAnimationFrame | undefined = cancelAnimationFrame;\n const { delegate } = animationFrameProvider;\n if (delegate) {\n request = delegate.requestAnimationFrame;\n cancel = delegate.cancelAnimationFrame;\n }\n const handle = request((timestamp) => {\n // Clear the cancel function. The request has been fulfilled, so\n // attempting to cancel the request upon unsubscription would be\n // pointless.\n cancel = undefined;\n callback(timestamp);\n });\n return new Subscription(() => cancel?.(handle));\n },\n requestAnimationFrame(...args) {\n const { delegate } = animationFrameProvider;\n return (delegate?.requestAnimationFrame || requestAnimationFrame)(...args);\n },\n cancelAnimationFrame(...args) {\n const { delegate } = animationFrameProvider;\n return (delegate?.cancelAnimationFrame || cancelAnimationFrame)(...args);\n },\n delegate: undefined,\n};\n", "import { createErrorClass } from './createErrorClass';\n\nexport interface ObjectUnsubscribedError extends Error {}\n\nexport interface ObjectUnsubscribedErrorCtor {\n /**\n * @deprecated Internal implementation detail. Do not construct error instances.\n * Cannot be tagged as internal: https://github.com/ReactiveX/rxjs/issues/6269\n */\n new (): ObjectUnsubscribedError;\n}\n\n/**\n * An error thrown when an action is invalid because the object has been\n * unsubscribed.\n *\n * @see {@link Subject}\n * @see {@link BehaviorSubject}\n *\n * @class ObjectUnsubscribedError\n */\nexport const ObjectUnsubscribedError: ObjectUnsubscribedErrorCtor = createErrorClass(\n (_super) =>\n function ObjectUnsubscribedErrorImpl(this: any) {\n _super(this);\n this.name = 'ObjectUnsubscribedError';\n this.message = 'object unsubscribed';\n }\n);\n", "import { Operator } from './Operator';\nimport { Observable } from './Observable';\nimport { Subscriber } from './Subscriber';\nimport { Subscription, EMPTY_SUBSCRIPTION } from './Subscription';\nimport { Observer, SubscriptionLike, TeardownLogic } from './types';\nimport { ObjectUnsubscribedError } from './util/ObjectUnsubscribedError';\nimport { arrRemove } from './util/arrRemove';\nimport { errorContext } from './util/errorContext';\n\n/**\n * A Subject is a special type of Observable that allows values to be\n * multicasted to many Observers. Subjects are like EventEmitters.\n *\n * Every Subject is an Observable and an Observer. You can subscribe to a\n * Subject, and you can call next to feed values as well as error and complete.\n */\nexport class Subject extends Observable implements SubscriptionLike {\n closed = false;\n\n private currentObservers: Observer[] | null = null;\n\n /** @deprecated Internal implementation detail, do not use directly. Will be made internal in v8. */\n observers: Observer[] = [];\n /** @deprecated Internal implementation detail, do not use directly. Will be made internal in v8. */\n isStopped = false;\n /** @deprecated Internal implementation detail, do not use directly. Will be made internal in v8. */\n hasError = false;\n /** @deprecated Internal implementation detail, do not use directly. Will be made internal in v8. */\n thrownError: any = null;\n\n /**\n * Creates a \"subject\" by basically gluing an observer to an observable.\n *\n * @nocollapse\n * @deprecated Recommended you do not use. Will be removed at some point in the future. Plans for replacement still under discussion.\n */\n static create: (...args: any[]) => any = (destination: Observer, source: Observable): AnonymousSubject => {\n return new AnonymousSubject(destination, source);\n };\n\n constructor() {\n // NOTE: This must be here to obscure Observable's constructor.\n super();\n }\n\n /** @deprecated Internal implementation detail, do not use directly. Will be made internal in v8. */\n lift(operator: Operator): Observable {\n const subject = new AnonymousSubject(this, this);\n subject.operator = operator as any;\n return subject as any;\n }\n\n /** @internal */\n protected _throwIfClosed() {\n if (this.closed) {\n throw new ObjectUnsubscribedError();\n }\n }\n\n next(value: T) {\n errorContext(() => {\n this._throwIfClosed();\n if (!this.isStopped) {\n if (!this.currentObservers) {\n this.currentObservers = Array.from(this.observers);\n }\n for (const observer of this.currentObservers) {\n observer.next(value);\n }\n }\n });\n }\n\n error(err: any) {\n errorContext(() => {\n this._throwIfClosed();\n if (!this.isStopped) {\n this.hasError = this.isStopped = true;\n this.thrownError = err;\n const { observers } = this;\n while (observers.length) {\n observers.shift()!.error(err);\n }\n }\n });\n }\n\n complete() {\n errorContext(() => {\n this._throwIfClosed();\n if (!this.isStopped) {\n this.isStopped = true;\n const { observers } = this;\n while (observers.length) {\n observers.shift()!.complete();\n }\n }\n });\n }\n\n unsubscribe() {\n this.isStopped = this.closed = true;\n this.observers = this.currentObservers = null!;\n }\n\n get observed() {\n return this.observers?.length > 0;\n }\n\n /** @internal */\n protected _trySubscribe(subscriber: Subscriber): TeardownLogic {\n this._throwIfClosed();\n return super._trySubscribe(subscriber);\n }\n\n /** @internal */\n protected _subscribe(subscriber: Subscriber): Subscription {\n this._throwIfClosed();\n this._checkFinalizedStatuses(subscriber);\n return this._innerSubscribe(subscriber);\n }\n\n /** @internal */\n protected _innerSubscribe(subscriber: Subscriber) {\n const { hasError, isStopped, observers } = this;\n if (hasError || isStopped) {\n return EMPTY_SUBSCRIPTION;\n }\n this.currentObservers = null;\n observers.push(subscriber);\n return new Subscription(() => {\n this.currentObservers = null;\n arrRemove(observers, subscriber);\n });\n }\n\n /** @internal */\n protected _checkFinalizedStatuses(subscriber: Subscriber) {\n const { hasError, thrownError, isStopped } = this;\n if (hasError) {\n subscriber.error(thrownError);\n } else if (isStopped) {\n subscriber.complete();\n }\n }\n\n /**\n * Creates a new Observable with this Subject as the source. You can do this\n * to create custom Observer-side logic of the Subject and conceal it from\n * code that uses the Observable.\n * @return {Observable} Observable that the Subject casts to\n */\n asObservable(): Observable {\n const observable: any = new Observable();\n observable.source = this;\n return observable;\n }\n}\n\n/**\n * @class AnonymousSubject\n */\nexport class AnonymousSubject extends Subject {\n constructor(\n /** @deprecated Internal implementation detail, do not use directly. Will be made internal in v8. */\n public destination?: Observer,\n source?: Observable\n ) {\n super();\n this.source = source;\n }\n\n next(value: T) {\n this.destination?.next?.(value);\n }\n\n error(err: any) {\n this.destination?.error?.(err);\n }\n\n complete() {\n this.destination?.complete?.();\n }\n\n /** @internal */\n protected _subscribe(subscriber: Subscriber): Subscription {\n return this.source?.subscribe(subscriber) ?? EMPTY_SUBSCRIPTION;\n }\n}\n", "import { Subject } from './Subject';\nimport { Subscriber } from './Subscriber';\nimport { Subscription } from './Subscription';\n\n/**\n * A variant of Subject that requires an initial value and emits its current\n * value whenever it is subscribed to.\n *\n * @class BehaviorSubject\n */\nexport class BehaviorSubject extends Subject {\n constructor(private _value: T) {\n super();\n }\n\n get value(): T {\n return this.getValue();\n }\n\n /** @internal */\n protected _subscribe(subscriber: Subscriber): Subscription {\n const subscription = super._subscribe(subscriber);\n !subscription.closed && subscriber.next(this._value);\n return subscription;\n }\n\n getValue(): T {\n const { hasError, thrownError, _value } = this;\n if (hasError) {\n throw thrownError;\n }\n this._throwIfClosed();\n return _value;\n }\n\n next(value: T): void {\n super.next((this._value = value));\n }\n}\n", "import { TimestampProvider } from '../types';\n\ninterface DateTimestampProvider extends TimestampProvider {\n delegate: TimestampProvider | undefined;\n}\n\nexport const dateTimestampProvider: DateTimestampProvider = {\n now() {\n // Use the variable rather than `this` so that the function can be called\n // without being bound to the provider.\n return (dateTimestampProvider.delegate || Date).now();\n },\n delegate: undefined,\n};\n", "import { Subject } from './Subject';\nimport { TimestampProvider } from './types';\nimport { Subscriber } from './Subscriber';\nimport { Subscription } from './Subscription';\nimport { dateTimestampProvider } from './scheduler/dateTimestampProvider';\n\n/**\n * A variant of {@link Subject} that \"replays\" old values to new subscribers by emitting them when they first subscribe.\n *\n * `ReplaySubject` has an internal buffer that will store a specified number of values that it has observed. Like `Subject`,\n * `ReplaySubject` \"observes\" values by having them passed to its `next` method. When it observes a value, it will store that\n * value for a time determined by the configuration of the `ReplaySubject`, as passed to its constructor.\n *\n * When a new subscriber subscribes to the `ReplaySubject` instance, it will synchronously emit all values in its buffer in\n * a First-In-First-Out (FIFO) manner. The `ReplaySubject` will also complete, if it has observed completion; and it will\n * error if it has observed an error.\n *\n * There are two main configuration items to be concerned with:\n *\n * 1. `bufferSize` - This will determine how many items are stored in the buffer, defaults to infinite.\n * 2. `windowTime` - The amount of time to hold a value in the buffer before removing it from the buffer.\n *\n * Both configurations may exist simultaneously. So if you would like to buffer a maximum of 3 values, as long as the values\n * are less than 2 seconds old, you could do so with a `new ReplaySubject(3, 2000)`.\n *\n * ### Differences with BehaviorSubject\n *\n * `BehaviorSubject` is similar to `new ReplaySubject(1)`, with a couple of exceptions:\n *\n * 1. `BehaviorSubject` comes \"primed\" with a single value upon construction.\n * 2. `ReplaySubject` will replay values, even after observing an error, where `BehaviorSubject` will not.\n *\n * @see {@link Subject}\n * @see {@link BehaviorSubject}\n * @see {@link shareReplay}\n */\nexport class ReplaySubject extends Subject {\n private _buffer: (T | number)[] = [];\n private _infiniteTimeWindow = true;\n\n /**\n * @param bufferSize The size of the buffer to replay on subscription\n * @param windowTime The amount of time the buffered items will stay buffered\n * @param timestampProvider An object with a `now()` method that provides the current timestamp. This is used to\n * calculate the amount of time something has been buffered.\n */\n constructor(\n private _bufferSize = Infinity,\n private _windowTime = Infinity,\n private _timestampProvider: TimestampProvider = dateTimestampProvider\n ) {\n super();\n this._infiniteTimeWindow = _windowTime === Infinity;\n this._bufferSize = Math.max(1, _bufferSize);\n this._windowTime = Math.max(1, _windowTime);\n }\n\n next(value: T): void {\n const { isStopped, _buffer, _infiniteTimeWindow, _timestampProvider, _windowTime } = this;\n if (!isStopped) {\n _buffer.push(value);\n !_infiniteTimeWindow && _buffer.push(_timestampProvider.now() + _windowTime);\n }\n this._trimBuffer();\n super.next(value);\n }\n\n /** @internal */\n protected _subscribe(subscriber: Subscriber): Subscription {\n this._throwIfClosed();\n this._trimBuffer();\n\n const subscription = this._innerSubscribe(subscriber);\n\n const { _infiniteTimeWindow, _buffer } = this;\n // We use a copy here, so reentrant code does not mutate our array while we're\n // emitting it to a new subscriber.\n const copy = _buffer.slice();\n for (let i = 0; i < copy.length && !subscriber.closed; i += _infiniteTimeWindow ? 1 : 2) {\n subscriber.next(copy[i] as T);\n }\n\n this._checkFinalizedStatuses(subscriber);\n\n return subscription;\n }\n\n private _trimBuffer() {\n const { _bufferSize, _timestampProvider, _buffer, _infiniteTimeWindow } = this;\n // If we don't have an infinite buffer size, and we're over the length,\n // use splice to truncate the old buffer values off. Note that we have to\n // double the size for instances where we're not using an infinite time window\n // because we're storing the values and the timestamps in the same array.\n const adjustedBufferSize = (_infiniteTimeWindow ? 1 : 2) * _bufferSize;\n _bufferSize < Infinity && adjustedBufferSize < _buffer.length && _buffer.splice(0, _buffer.length - adjustedBufferSize);\n\n // Now, if we're not in an infinite time window, remove all values where the time is\n // older than what is allowed.\n if (!_infiniteTimeWindow) {\n const now = _timestampProvider.now();\n let last = 0;\n // Search the array for the first timestamp that isn't expired and\n // truncate the buffer up to that point.\n for (let i = 1; i < _buffer.length && (_buffer[i] as number) <= now; i += 2) {\n last = i;\n }\n last && _buffer.splice(0, last + 1);\n }\n }\n}\n", "import { Scheduler } from '../Scheduler';\nimport { Subscription } from '../Subscription';\nimport { SchedulerAction } from '../types';\n\n/**\n * A unit of work to be executed in a `scheduler`. An action is typically\n * created from within a {@link SchedulerLike} and an RxJS user does not need to concern\n * themselves about creating and manipulating an Action.\n *\n * ```ts\n * class Action extends Subscription {\n * new (scheduler: Scheduler, work: (state?: T) => void);\n * schedule(state?: T, delay: number = 0): Subscription;\n * }\n * ```\n *\n * @class Action\n */\nexport class Action extends Subscription {\n constructor(scheduler: Scheduler, work: (this: SchedulerAction, state?: T) => void) {\n super();\n }\n /**\n * Schedules this action on its parent {@link SchedulerLike} for execution. May be passed\n * some context object, `state`. May happen at some point in the future,\n * according to the `delay` parameter, if specified.\n * @param {T} [state] Some contextual data that the `work` function uses when\n * called by the Scheduler.\n * @param {number} [delay] Time to wait before executing the work, where the\n * time unit is implicit and defined by the Scheduler.\n * @return {void}\n */\n public schedule(state?: T, delay: number = 0): Subscription {\n return this;\n }\n}\n", "import type { TimerHandle } from './timerHandle';\ntype SetIntervalFunction = (handler: () => void, timeout?: number, ...args: any[]) => TimerHandle;\ntype ClearIntervalFunction = (handle: TimerHandle) => void;\n\ninterface IntervalProvider {\n setInterval: SetIntervalFunction;\n clearInterval: ClearIntervalFunction;\n delegate:\n | {\n setInterval: SetIntervalFunction;\n clearInterval: ClearIntervalFunction;\n }\n | undefined;\n}\n\nexport const intervalProvider: IntervalProvider = {\n // When accessing the delegate, use the variable rather than `this` so that\n // the functions can be called without being bound to the provider.\n setInterval(handler: () => void, timeout?: number, ...args) {\n const { delegate } = intervalProvider;\n if (delegate?.setInterval) {\n return delegate.setInterval(handler, timeout, ...args);\n }\n return setInterval(handler, timeout, ...args);\n },\n clearInterval(handle) {\n const { delegate } = intervalProvider;\n return (delegate?.clearInterval || clearInterval)(handle as any);\n },\n delegate: undefined,\n};\n", "import { Action } from './Action';\nimport { SchedulerAction } from '../types';\nimport { Subscription } from '../Subscription';\nimport { AsyncScheduler } from './AsyncScheduler';\nimport { intervalProvider } from './intervalProvider';\nimport { arrRemove } from '../util/arrRemove';\nimport { TimerHandle } from './timerHandle';\n\nexport class AsyncAction extends Action {\n public id: TimerHandle | undefined;\n public state?: T;\n // @ts-ignore: Property has no initializer and is not definitely assigned\n public delay: number;\n protected pending: boolean = false;\n\n constructor(protected scheduler: AsyncScheduler, protected work: (this: SchedulerAction, state?: T) => void) {\n super(scheduler, work);\n }\n\n public schedule(state?: T, delay: number = 0): Subscription {\n if (this.closed) {\n return this;\n }\n\n // Always replace the current state with the new state.\n this.state = state;\n\n const id = this.id;\n const scheduler = this.scheduler;\n\n //\n // Important implementation note:\n //\n // Actions only execute once by default, unless rescheduled from within the\n // scheduled callback. This allows us to implement single and repeat\n // actions via the same code path, without adding API surface area, as well\n // as mimic traditional recursion but across asynchronous boundaries.\n //\n // However, JS runtimes and timers distinguish between intervals achieved by\n // serial `setTimeout` calls vs. a single `setInterval` call. An interval of\n // serial `setTimeout` calls can be individually delayed, which delays\n // scheduling the next `setTimeout`, and so on. `setInterval` attempts to\n // guarantee the interval callback will be invoked more precisely to the\n // interval period, regardless of load.\n //\n // Therefore, we use `setInterval` to schedule single and repeat actions.\n // If the action reschedules itself with the same delay, the interval is not\n // canceled. If the action doesn't reschedule, or reschedules with a\n // different delay, the interval will be canceled after scheduled callback\n // execution.\n //\n if (id != null) {\n this.id = this.recycleAsyncId(scheduler, id, delay);\n }\n\n // Set the pending flag indicating that this action has been scheduled, or\n // has recursively rescheduled itself.\n this.pending = true;\n\n this.delay = delay;\n // If this action has already an async Id, don't request a new one.\n this.id = this.id ?? this.requestAsyncId(scheduler, this.id, delay);\n\n return this;\n }\n\n protected requestAsyncId(scheduler: AsyncScheduler, _id?: TimerHandle, delay: number = 0): TimerHandle {\n return intervalProvider.setInterval(scheduler.flush.bind(scheduler, this), delay);\n }\n\n protected recycleAsyncId(_scheduler: AsyncScheduler, id?: TimerHandle, delay: number | null = 0): TimerHandle | undefined {\n // If this action is rescheduled with the same delay time, don't clear the interval id.\n if (delay != null && this.delay === delay && this.pending === false) {\n return id;\n }\n // Otherwise, if the action's delay time is different from the current delay,\n // or the action has been rescheduled before it's executed, clear the interval id\n if (id != null) {\n intervalProvider.clearInterval(id);\n }\n\n return undefined;\n }\n\n /**\n * Immediately executes this action and the `work` it contains.\n * @return {any}\n */\n public execute(state: T, delay: number): any {\n if (this.closed) {\n return new Error('executing a cancelled action');\n }\n\n this.pending = false;\n const error = this._execute(state, delay);\n if (error) {\n return error;\n } else if (this.pending === false && this.id != null) {\n // Dequeue if the action didn't reschedule itself. Don't call\n // unsubscribe(), because the action could reschedule later.\n // For example:\n // ```\n // scheduler.schedule(function doWork(counter) {\n // /* ... I'm a busy worker bee ... */\n // var originalAction = this;\n // /* wait 100ms before rescheduling the action */\n // setTimeout(function () {\n // originalAction.schedule(counter + 1);\n // }, 100);\n // }, 1000);\n // ```\n this.id = this.recycleAsyncId(this.scheduler, this.id, null);\n }\n }\n\n protected _execute(state: T, _delay: number): any {\n let errored: boolean = false;\n let errorValue: any;\n try {\n this.work(state);\n } catch (e) {\n errored = true;\n // HACK: Since code elsewhere is relying on the \"truthiness\" of the\n // return here, we can't have it return \"\" or 0 or false.\n // TODO: Clean this up when we refactor schedulers mid-version-8 or so.\n errorValue = e ? e : new Error('Scheduled action threw falsy error');\n }\n if (errored) {\n this.unsubscribe();\n return errorValue;\n }\n }\n\n unsubscribe() {\n if (!this.closed) {\n const { id, scheduler } = this;\n const { actions } = scheduler;\n\n this.work = this.state = this.scheduler = null!;\n this.pending = false;\n\n arrRemove(actions, this);\n if (id != null) {\n this.id = this.recycleAsyncId(scheduler, id, null);\n }\n\n this.delay = null!;\n super.unsubscribe();\n }\n }\n}\n", "import { Action } from './scheduler/Action';\nimport { Subscription } from './Subscription';\nimport { SchedulerLike, SchedulerAction } from './types';\nimport { dateTimestampProvider } from './scheduler/dateTimestampProvider';\n\n/**\n * An execution context and a data structure to order tasks and schedule their\n * execution. Provides a notion of (potentially virtual) time, through the\n * `now()` getter method.\n *\n * Each unit of work in a Scheduler is called an `Action`.\n *\n * ```ts\n * class Scheduler {\n * now(): number;\n * schedule(work, delay?, state?): Subscription;\n * }\n * ```\n *\n * @class Scheduler\n * @deprecated Scheduler is an internal implementation detail of RxJS, and\n * should not be used directly. Rather, create your own class and implement\n * {@link SchedulerLike}. Will be made internal in v8.\n */\nexport class Scheduler implements SchedulerLike {\n public static now: () => number = dateTimestampProvider.now;\n\n constructor(private schedulerActionCtor: typeof Action, now: () => number = Scheduler.now) {\n this.now = now;\n }\n\n /**\n * A getter method that returns a number representing the current time\n * (at the time this function was called) according to the scheduler's own\n * internal clock.\n * @return {number} A number that represents the current time. May or may not\n * have a relation to wall-clock time. May or may not refer to a time unit\n * (e.g. milliseconds).\n */\n public now: () => number;\n\n /**\n * Schedules a function, `work`, for execution. May happen at some point in\n * the future, according to the `delay` parameter, if specified. May be passed\n * some context object, `state`, which will be passed to the `work` function.\n *\n * The given arguments will be processed an stored as an Action object in a\n * queue of actions.\n *\n * @param {function(state: ?T): ?Subscription} work A function representing a\n * task, or some unit of work to be executed by the Scheduler.\n * @param {number} [delay] Time to wait before executing the work, where the\n * time unit is implicit and defined by the Scheduler itself.\n * @param {T} [state] Some contextual data that the `work` function uses when\n * called by the Scheduler.\n * @return {Subscription} A subscription in order to be able to unsubscribe\n * the scheduled work.\n */\n public schedule(work: (this: SchedulerAction, state?: T) => void, delay: number = 0, state?: T): Subscription {\n return new this.schedulerActionCtor(this, work).schedule(state, delay);\n }\n}\n", "import { Scheduler } from '../Scheduler';\nimport { Action } from './Action';\nimport { AsyncAction } from './AsyncAction';\nimport { TimerHandle } from './timerHandle';\n\nexport class AsyncScheduler extends Scheduler {\n public actions: Array> = [];\n /**\n * A flag to indicate whether the Scheduler is currently executing a batch of\n * queued actions.\n * @type {boolean}\n * @internal\n */\n public _active: boolean = false;\n /**\n * An internal ID used to track the latest asynchronous task such as those\n * coming from `setTimeout`, `setInterval`, `requestAnimationFrame`, and\n * others.\n * @type {any}\n * @internal\n */\n public _scheduled: TimerHandle | undefined;\n\n constructor(SchedulerAction: typeof Action, now: () => number = Scheduler.now) {\n super(SchedulerAction, now);\n }\n\n public flush(action: AsyncAction): void {\n const { actions } = this;\n\n if (this._active) {\n actions.push(action);\n return;\n }\n\n let error: any;\n this._active = true;\n\n do {\n if ((error = action.execute(action.state, action.delay))) {\n break;\n }\n } while ((action = actions.shift()!)); // exhaust the scheduler queue\n\n this._active = false;\n\n if (error) {\n while ((action = actions.shift()!)) {\n action.unsubscribe();\n }\n throw error;\n }\n }\n}\n", "import { AsyncAction } from './AsyncAction';\nimport { AsyncScheduler } from './AsyncScheduler';\n\n/**\n *\n * Async Scheduler\n *\n * Schedule task as if you used setTimeout(task, duration)\n *\n * `async` scheduler schedules tasks asynchronously, by putting them on the JavaScript\n * event loop queue. It is best used to delay tasks in time or to schedule tasks repeating\n * in intervals.\n *\n * If you just want to \"defer\" task, that is to perform it right after currently\n * executing synchronous code ends (commonly achieved by `setTimeout(deferredTask, 0)`),\n * better choice will be the {@link asapScheduler} scheduler.\n *\n * ## Examples\n * Use async scheduler to delay task\n * ```ts\n * import { asyncScheduler } from 'rxjs';\n *\n * const task = () => console.log('it works!');\n *\n * asyncScheduler.schedule(task, 2000);\n *\n * // After 2 seconds logs:\n * // \"it works!\"\n * ```\n *\n * Use async scheduler to repeat task in intervals\n * ```ts\n * import { asyncScheduler } from 'rxjs';\n *\n * function task(state) {\n * console.log(state);\n * this.schedule(state + 1, 1000); // `this` references currently executing Action,\n * // which we reschedule with new state and delay\n * }\n *\n * asyncScheduler.schedule(task, 3000, 0);\n *\n * // Logs:\n * // 0 after 3s\n * // 1 after 4s\n * // 2 after 5s\n * // 3 after 6s\n * ```\n */\n\nexport const asyncScheduler = new AsyncScheduler(AsyncAction);\n\n/**\n * @deprecated Renamed to {@link asyncScheduler}. Will be removed in v8.\n */\nexport const async = asyncScheduler;\n", "import { AsyncAction } from './AsyncAction';\nimport { Subscription } from '../Subscription';\nimport { QueueScheduler } from './QueueScheduler';\nimport { SchedulerAction } from '../types';\nimport { TimerHandle } from './timerHandle';\n\nexport class QueueAction extends AsyncAction {\n constructor(protected scheduler: QueueScheduler, protected work: (this: SchedulerAction, state?: T) => void) {\n super(scheduler, work);\n }\n\n public schedule(state?: T, delay: number = 0): Subscription {\n if (delay > 0) {\n return super.schedule(state, delay);\n }\n this.delay = delay;\n this.state = state;\n this.scheduler.flush(this);\n return this;\n }\n\n public execute(state: T, delay: number): any {\n return delay > 0 || this.closed ? super.execute(state, delay) : this._execute(state, delay);\n }\n\n protected requestAsyncId(scheduler: QueueScheduler, id?: TimerHandle, delay: number = 0): TimerHandle {\n // If delay exists and is greater than 0, or if the delay is null (the\n // action wasn't rescheduled) but was originally scheduled as an async\n // action, then recycle as an async action.\n\n if ((delay != null && delay > 0) || (delay == null && this.delay > 0)) {\n return super.requestAsyncId(scheduler, id, delay);\n }\n\n // Otherwise flush the scheduler starting with this action.\n scheduler.flush(this);\n\n // HACK: In the past, this was returning `void`. However, `void` isn't a valid\n // `TimerHandle`, and generally the return value here isn't really used. So the\n // compromise is to return `0` which is both \"falsy\" and a valid `TimerHandle`,\n // as opposed to refactoring every other instanceo of `requestAsyncId`.\n return 0;\n }\n}\n", "import { AsyncScheduler } from './AsyncScheduler';\n\nexport class QueueScheduler extends AsyncScheduler {\n}\n", "import { QueueAction } from './QueueAction';\nimport { QueueScheduler } from './QueueScheduler';\n\n/**\n *\n * Queue Scheduler\n *\n * Put every next task on a queue, instead of executing it immediately\n *\n * `queue` scheduler, when used with delay, behaves the same as {@link asyncScheduler} scheduler.\n *\n * When used without delay, it schedules given task synchronously - executes it right when\n * it is scheduled. However when called recursively, that is when inside the scheduled task,\n * another task is scheduled with queue scheduler, instead of executing immediately as well,\n * that task will be put on a queue and wait for current one to finish.\n *\n * This means that when you execute task with `queue` scheduler, you are sure it will end\n * before any other task scheduled with that scheduler will start.\n *\n * ## Examples\n * Schedule recursively first, then do something\n * ```ts\n * import { queueScheduler } from 'rxjs';\n *\n * queueScheduler.schedule(() => {\n * queueScheduler.schedule(() => console.log('second')); // will not happen now, but will be put on a queue\n *\n * console.log('first');\n * });\n *\n * // Logs:\n * // \"first\"\n * // \"second\"\n * ```\n *\n * Reschedule itself recursively\n * ```ts\n * import { queueScheduler } from 'rxjs';\n *\n * queueScheduler.schedule(function(state) {\n * if (state !== 0) {\n * console.log('before', state);\n * this.schedule(state - 1); // `this` references currently executing Action,\n * // which we reschedule with new state\n * console.log('after', state);\n * }\n * }, 0, 3);\n *\n * // In scheduler that runs recursively, you would expect:\n * // \"before\", 3\n * // \"before\", 2\n * // \"before\", 1\n * // \"after\", 1\n * // \"after\", 2\n * // \"after\", 3\n *\n * // But with queue it logs:\n * // \"before\", 3\n * // \"after\", 3\n * // \"before\", 2\n * // \"after\", 2\n * // \"before\", 1\n * // \"after\", 1\n * ```\n */\n\nexport const queueScheduler = new QueueScheduler(QueueAction);\n\n/**\n * @deprecated Renamed to {@link queueScheduler}. Will be removed in v8.\n */\nexport const queue = queueScheduler;\n", "import { AsyncAction } from './AsyncAction';\nimport { AnimationFrameScheduler } from './AnimationFrameScheduler';\nimport { SchedulerAction } from '../types';\nimport { animationFrameProvider } from './animationFrameProvider';\nimport { TimerHandle } from './timerHandle';\n\nexport class AnimationFrameAction extends AsyncAction {\n constructor(protected scheduler: AnimationFrameScheduler, protected work: (this: SchedulerAction, state?: T) => void) {\n super(scheduler, work);\n }\n\n protected requestAsyncId(scheduler: AnimationFrameScheduler, id?: TimerHandle, delay: number = 0): TimerHandle {\n // If delay is greater than 0, request as an async action.\n if (delay !== null && delay > 0) {\n return super.requestAsyncId(scheduler, id, delay);\n }\n // Push the action to the end of the scheduler queue.\n scheduler.actions.push(this);\n // If an animation frame has already been requested, don't request another\n // one. If an animation frame hasn't been requested yet, request one. Return\n // the current animation frame request id.\n return scheduler._scheduled || (scheduler._scheduled = animationFrameProvider.requestAnimationFrame(() => scheduler.flush(undefined)));\n }\n\n protected recycleAsyncId(scheduler: AnimationFrameScheduler, id?: TimerHandle, delay: number = 0): TimerHandle | undefined {\n // If delay exists and is greater than 0, or if the delay is null (the\n // action wasn't rescheduled) but was originally scheduled as an async\n // action, then recycle as an async action.\n if (delay != null ? delay > 0 : this.delay > 0) {\n return super.recycleAsyncId(scheduler, id, delay);\n }\n // If the scheduler queue has no remaining actions with the same async id,\n // cancel the requested animation frame and set the scheduled flag to\n // undefined so the next AnimationFrameAction will request its own.\n const { actions } = scheduler;\n if (id != null && actions[actions.length - 1]?.id !== id) {\n animationFrameProvider.cancelAnimationFrame(id as number);\n scheduler._scheduled = undefined;\n }\n // Return undefined so the action knows to request a new async id if it's rescheduled.\n return undefined;\n }\n}\n", "import { AsyncAction } from './AsyncAction';\nimport { AsyncScheduler } from './AsyncScheduler';\n\nexport class AnimationFrameScheduler extends AsyncScheduler {\n public flush(action?: AsyncAction): void {\n this._active = true;\n // The async id that effects a call to flush is stored in _scheduled.\n // Before executing an action, it's necessary to check the action's async\n // id to determine whether it's supposed to be executed in the current\n // flush.\n // Previous implementations of this method used a count to determine this,\n // but that was unsound, as actions that are unsubscribed - i.e. cancelled -\n // are removed from the actions array and that can shift actions that are\n // scheduled to be executed in a subsequent flush into positions at which\n // they are executed within the current flush.\n const flushId = this._scheduled;\n this._scheduled = undefined;\n\n const { actions } = this;\n let error: any;\n action = action || actions.shift()!;\n\n do {\n if ((error = action.execute(action.state, action.delay))) {\n break;\n }\n } while ((action = actions[0]) && action.id === flushId && actions.shift());\n\n this._active = false;\n\n if (error) {\n while ((action = actions[0]) && action.id === flushId && actions.shift()) {\n action.unsubscribe();\n }\n throw error;\n }\n }\n}\n", "import { AnimationFrameAction } from './AnimationFrameAction';\nimport { AnimationFrameScheduler } from './AnimationFrameScheduler';\n\n/**\n *\n * Animation Frame Scheduler\n *\n * Perform task when `window.requestAnimationFrame` would fire\n *\n * When `animationFrame` scheduler is used with delay, it will fall back to {@link asyncScheduler} scheduler\n * behaviour.\n *\n * Without delay, `animationFrame` scheduler can be used to create smooth browser animations.\n * It makes sure scheduled task will happen just before next browser content repaint,\n * thus performing animations as efficiently as possible.\n *\n * ## Example\n * Schedule div height animation\n * ```ts\n * // html:
\n * import { animationFrameScheduler } from 'rxjs';\n *\n * const div = document.querySelector('div');\n *\n * animationFrameScheduler.schedule(function(height) {\n * div.style.height = height + \"px\";\n *\n * this.schedule(height + 1); // `this` references currently executing Action,\n * // which we reschedule with new state\n * }, 0, 0);\n *\n * // You will see a div element growing in height\n * ```\n */\n\nexport const animationFrameScheduler = new AnimationFrameScheduler(AnimationFrameAction);\n\n/**\n * @deprecated Renamed to {@link animationFrameScheduler}. Will be removed in v8.\n */\nexport const animationFrame = animationFrameScheduler;\n", "import { Observable } from '../Observable';\nimport { SchedulerLike } from '../types';\n\n/**\n * A simple Observable that emits no items to the Observer and immediately\n * emits a complete notification.\n *\n * Just emits 'complete', and nothing else.\n *\n * ![](empty.png)\n *\n * A simple Observable that only emits the complete notification. It can be used\n * for composing with other Observables, such as in a {@link mergeMap}.\n *\n * ## Examples\n *\n * Log complete notification\n *\n * ```ts\n * import { EMPTY } from 'rxjs';\n *\n * EMPTY.subscribe({\n * next: () => console.log('Next'),\n * complete: () => console.log('Complete!')\n * });\n *\n * // Outputs\n * // Complete!\n * ```\n *\n * Emit the number 7, then complete\n *\n * ```ts\n * import { EMPTY, startWith } from 'rxjs';\n *\n * const result = EMPTY.pipe(startWith(7));\n * result.subscribe(x => console.log(x));\n *\n * // Outputs\n * // 7\n * ```\n *\n * Map and flatten only odd numbers to the sequence `'a'`, `'b'`, `'c'`\n *\n * ```ts\n * import { interval, mergeMap, of, EMPTY } from 'rxjs';\n *\n * const interval$ = interval(1000);\n * const result = interval$.pipe(\n * mergeMap(x => x % 2 === 1 ? of('a', 'b', 'c') : EMPTY),\n * );\n * result.subscribe(x => console.log(x));\n *\n * // Results in the following to the console:\n * // x is equal to the count on the interval, e.g. (0, 1, 2, 3, ...)\n * // x will occur every 1000ms\n * // if x % 2 is equal to 1, print a, b, c (each on its own)\n * // if x % 2 is not equal to 1, nothing will be output\n * ```\n *\n * @see {@link Observable}\n * @see {@link NEVER}\n * @see {@link of}\n * @see {@link throwError}\n */\nexport const EMPTY = new Observable((subscriber) => subscriber.complete());\n\n/**\n * @param scheduler A {@link SchedulerLike} to use for scheduling\n * the emission of the complete notification.\n * @deprecated Replaced with the {@link EMPTY} constant or {@link scheduled} (e.g. `scheduled([], scheduler)`). Will be removed in v8.\n */\nexport function empty(scheduler?: SchedulerLike) {\n return scheduler ? emptyScheduled(scheduler) : EMPTY;\n}\n\nfunction emptyScheduled(scheduler: SchedulerLike) {\n return new Observable((subscriber) => scheduler.schedule(() => subscriber.complete()));\n}\n", "import { SchedulerLike } from '../types';\nimport { isFunction } from './isFunction';\n\nexport function isScheduler(value: any): value is SchedulerLike {\n return value && isFunction(value.schedule);\n}\n", "import { SchedulerLike } from '../types';\nimport { isFunction } from './isFunction';\nimport { isScheduler } from './isScheduler';\n\nfunction last(arr: T[]): T | undefined {\n return arr[arr.length - 1];\n}\n\nexport function popResultSelector(args: any[]): ((...args: unknown[]) => unknown) | undefined {\n return isFunction(last(args)) ? args.pop() : undefined;\n}\n\nexport function popScheduler(args: any[]): SchedulerLike | undefined {\n return isScheduler(last(args)) ? args.pop() : undefined;\n}\n\nexport function popNumber(args: any[], defaultValue: number): number {\n return typeof last(args) === 'number' ? args.pop()! : defaultValue;\n}\n", "export const isArrayLike = ((x: any): x is ArrayLike => x && typeof x.length === 'number' && typeof x !== 'function');", "import { isFunction } from \"./isFunction\";\n\n/**\n * Tests to see if the object is \"thennable\".\n * @param value the object to test\n */\nexport function isPromise(value: any): value is PromiseLike {\n return isFunction(value?.then);\n}\n", "import { InteropObservable } from '../types';\nimport { observable as Symbol_observable } from '../symbol/observable';\nimport { isFunction } from './isFunction';\n\n/** Identifies an input as being Observable (but not necessary an Rx Observable) */\nexport function isInteropObservable(input: any): input is InteropObservable {\n return isFunction(input[Symbol_observable]);\n}\n", "import { isFunction } from './isFunction';\n\nexport function isAsyncIterable(obj: any): obj is AsyncIterable {\n return Symbol.asyncIterator && isFunction(obj?.[Symbol.asyncIterator]);\n}\n", "/**\n * Creates the TypeError to throw if an invalid object is passed to `from` or `scheduled`.\n * @param input The object that was passed.\n */\nexport function createInvalidObservableTypeError(input: any) {\n // TODO: We should create error codes that can be looked up, so this can be less verbose.\n return new TypeError(\n `You provided ${\n input !== null && typeof input === 'object' ? 'an invalid object' : `'${input}'`\n } where a stream was expected. You can provide an Observable, Promise, ReadableStream, Array, AsyncIterable, or Iterable.`\n );\n}\n", "export function getSymbolIterator(): symbol {\n if (typeof Symbol !== 'function' || !Symbol.iterator) {\n return '@@iterator' as any;\n }\n\n return Symbol.iterator;\n}\n\nexport const iterator = getSymbolIterator();\n", "import { iterator as Symbol_iterator } from '../symbol/iterator';\nimport { isFunction } from './isFunction';\n\n/** Identifies an input as being an Iterable */\nexport function isIterable(input: any): input is Iterable {\n return isFunction(input?.[Symbol_iterator]);\n}\n", "import { ReadableStreamLike } from '../types';\nimport { isFunction } from './isFunction';\n\nexport async function* readableStreamLikeToAsyncGenerator(readableStream: ReadableStreamLike): AsyncGenerator {\n const reader = readableStream.getReader();\n try {\n while (true) {\n const { value, done } = await reader.read();\n if (done) {\n return;\n }\n yield value!;\n }\n } finally {\n reader.releaseLock();\n }\n}\n\nexport function isReadableStreamLike(obj: any): obj is ReadableStreamLike {\n // We don't want to use instanceof checks because they would return\n // false for instances from another Realm, like an