Instantly search your Qnap files and emails via Qsirch API

As much as I love using cloud services, in these days I cannot trust them to continue working. I rely on Google Drive and Gmail, but Google’s reputation in keeping services alive is not exactly stellar.

I have a qnap NAS at home to backup all my clouds in case I cannot access them or some Product Manager decides to kill a service I depend on.

This NAS, apart from serving as a backup also contains a copy of all the files I have produced. As such, it’s a perfect place to search through all my content without wondering if the file was in email, google drive or elsewhere.

I implemented an Alfred workflow for blazing fast local search of all my files and email. Here is me unearthing an article I wrote a decade ago and have stored somewhere in my backup:

Here is how to set it up in case you own a QNAP device:

Setting up QSirch

  1. Set up your backup policies for Google Drive / Dropbox / Whatever in HBS
    • There is an option to autmatically convert Google Docs into .docx locally. That way, even if Google Docs stops existing, you still will be able to open your files.
  2. Install QMailAgent app to back up your Gmail
  3. Install QSirch to index your files and emails.

You can use QSirch from your QNAP admin panel, bit it’s just so much friction that I never used it.

Turns out QSirch has an API. Here is a small library I wrote to use it:

QSirch PHP Library

I wrote this simple PHP library to access my QNAP server on the local network:

<?php

class Qnap {
    public $url;
    public $session = '';
    private $user;
    private $password;
    public $cachefile;

    public function __construct( $ip, $user, $password ) {
        $this->url = $ip;
        $this->user = $user;
        $this->password = $password;
        // You might want to store this somewhere else, but I want to keep this token locally.
        $this->cachefile = $_SERVER['HOME'] . '/.cache/alfredqnaptoken';
        $this->session = file_exists( $this->cachefile ) ? file_get_contents( $this->cachefile ) : '';
    }

    function session_cookie() {
        if ( ! $this->session ) {
            return '';
        }
        return "Cookie: QQS_SID={$this->session}\r\n";
    }

	function request( $url, $data = [], $method = 'GET' ) {
		$params = array(
			'header'  => 'Content-Type: application/json' . "\r\n" . $this->session_cookie(),
			'method'  => $method,
            "ignore_errors" => true,
			'content' => json_encode( $data ),
		);
		$context  = stream_context_create( array(
			'http' => $params
		) );
		$result =  file_get_contents( $this->url . $url, false, $context );
		return json_decode( $result );
	}

    public function login( $login, $password ) {
        $data = array('account' => $login, 'password' => $password );

        $data = $this->request(  '/qsirch/latest/api/login/', $data, 'POST' );
        $this->session = $data->qqs_sid;
        file_put_contents( $this->cachefile, $this->session );
        return $data;
    }

    public function search( $str ) {
        $result = '';
        $request_url = "/qsirch/latest/api/search?sort_by=created&sort_dir=desc&limit=100&q=" . urlencode( $str );
        // Maybe we can wing it without relogging in?
        if ( $this->session ) {
            $result = $this->request( $request_url );
            if ( ! isset( $result->error->code ) || $result->error->code !== '101'  ) {
                return $result;
            }
        }
        // Let's log in
        $this->login( $this->user, $this->password );
        return $this->request( $request_url );
    }

    public function map_to_alfred( $results ) {
        return array_map( [ $this, 'item_to_alfred' ], $results->items );
    }
    function metadata_to_array( $item ) {
        $meta = [];
        if ( ! isset( $item->preview->info ) ) {
            return $meta;
        }

        foreach ( $item->preview->info as $m ) {
            $meta[ $m->key ] = $m->value;
        }
        return $meta;
    }

    public function item_to_alfred( $item ) {
        $file = "/Volumes/{$item->path}/{$item->name}.{$item->extension}";

        $title = '';
        $m = $this->metadata_to_array( $item );
        if ( $item->extension = 'eml' && isset( $m['from'], $m['subject'], $m['sent_date'] ) ) {
            $title = explode( ' ', $m['sent_date'] )[0] . ' ' . $m['subject'] . ': ' . $m['from'];
        } else if( isset( $item->title, $item->extension ) ) {
            $title = "{$item->title}.{$item->extension}";
        }
        
        return array(
            'title' => $title,
            'subtitle' => $item->content ?? '',
            'arg' =>  $file,
            'icon' => $this->url . '/' . $item->actions->icon,
            'quicklookurl' => $file,
            // 'mods'=> array(
            //         'cmd'=> array(
            //             'valid' => ( !! $note['source_url'] ),
            //             'arg' => $note['source_url'],
            //             'subtitle' => 'Open URL ' . $note['source_url']
            //         ),
            //         'alt'=> array(
            //             'valid' => true,
            //             'arg' => $cmdresult,
            //             'subtitle' => 'Paste URL ' . $cmdresult
            //         ),
    
            // )
        );
    
    }
}

QSirch Alfred Workflow

This is a very simple Alfred Workflow to search all my files:

Leave a Reply