How can I tag files and search them later based on the tag?

Contents:

  1. Introduction
  2. Installation
  3. Usage
  4. Source code

1. Introduction

This solution consists of two scripts - one for tagging, one for reading the list of files under specific tag. Both have to live in ~/.local/share/nautilus/scripts and activated via right-click in Nautilus file manager on any file, and navigating to Scripts submenu. The source code for each script is provided here as well as on GitHub

2. Installation

Both scripts have to be saved to ~/.local/share/nautilus/scripts , where ~ is user's home directory, and made executable with chmod +x filename. For easy installation, use the following bash script:

#!/bin/bash

N_SCRIPTS="$HOME/.local/share/nautilus/scripts"
cd /tmp
rm master.zip*
rm -rf nautilus_scripts-master
wget https://github.com/SergKolo/nautilus_scripts/archive/master.zip
unzip master.zip
install nautilus_scripts-master/tag_file.py "$N_SCRIPTS/tag_file.py"
install nautilus_scripts-master/read_tags.py "$N_SCRIPTS/read_tags.py"

3. Usage:

Tagging files:

Select files in Nautilus file manager, right click on them, and navigate to Scripts submenu. Select tag_file.py. Hit Enter enter image description here First time you run this script, there will be no configuration file, so you will see this:

enter image description here

Next time, when you already have some files tagged, you will see a popup that allows you to select a tag and/or add new one ( this way you can record files under multiple tags). Hit OK to add files to this tag. Note:Avoid having "|" symbol in the tag name.

enter image description here

The script records everything in ~/.tagged_files. That file is essentially a json dictionary ( which is not something regular users should care about, but it's convenient for programmers :) ). The format of that file is as so:

{
    "Important Screenshots": [
        "/home/xieerqi/\u56fe\u7247/Screenshot from 2016-10-01 09-15-46.png",
        "/home/xieerqi/\u56fe\u7247/Screenshot from 2016-09-30 18-47-12.png",
        "/home/xieerqi/\u56fe\u7247/Screenshot from 2016-09-30 18-46-46.png",
        "/home/xieerqi/\u56fe\u7247/Screenshot from 2016-09-30 17-35-32.png"
    ],
    "Translation Docs": [
        "/home/xieerqi/Downloads/908173 - \u7ffb\u8bd1.doc",
        "/home/xieerqi/Downloads/911683\u7ffb\u8bd1.docx",
        "/home/xieerqi/Downloads/914549 -\u7ffb\u8bd1.txt"
    ]
}

If you ever want to "untag" some file, just delete an entry from that list. Mind the format and commas.

Searching by tag:

Now that you have a nice ~/.tagged_files database of files, you can either read that file, or use read_tags.py script.

Right click on any file in Nautilus ( really doesn't matter which ).Select read_tags.py. Hit Enter enter image description here

You will see a popup asking you what tag you want to search:

enter image description here

Select one, click OK. You will see a list dialog showing you want files are there for the tag you selected. You can select any single file and it will open with a default program assigned to that file type.

enter image description here

4. Source code:

tag_file.py:

#!/usr/bin/env python3
# -*- coding: utf-8 -*-

# Author: Serg Kolo  
# Date: Oct 1st, 2016
# Description: tag_file.py, script for
#    recording paths to files under 
#    specific , user-defined tag
#    in ~/.tagged_files
# Written for: http://askubuntu.com/q/827701/295286
# Tested on : Ubuntu ( Unity ) 16.04

from __future__ import print_function
import subprocess
import json
import os
import sys

def show_error(string):
    subprocess.call(['zenity','--error',
                     '--title',__file__,
                     '--text',string
    ])
    sys.exit(1)

def run_cmd(cmdlist):
    """ Reusable function for running external commands """
    new_env = dict(os.environ)
    new_env['LC_ALL'] = 'C'
    try:
        stdout = subprocess.check_output(cmdlist, env=new_env)
    except subprocess.CalledProcessError:
        pass
    else:
        if stdout:
            return stdout


def write_to_file(conf_file,tag,path_list):

    # if config file exists , read it
    data = {}
    if os.path.exists(conf_file):
        with open(conf_file) as f:
            data = json.load(f)

    if tag in data:
        for path in path_list:
            if path in data[tag]:
               continue
            data[tag].append(path)
    else:
        data[tag] = path_list

    with open(conf_file,'w') as f:
        json.dump(data,f,indent=4,sort_keys=True)

def get_tags(conf_file):

    if os.path.exists(conf_file):
       with open(conf_file) as f:
            data = json.load(f)
            return '|'.join(data.keys())

def main():

    user_home = os.path.expanduser('~')
    config = '.tagged_files'
    conf_path = os.path.join(user_home,config)
    file_paths = [ os.path.abspath(f) for f in sys.argv[1:] ]
    tags = None

    try:
        tags = get_tags(conf_path)
    except Exception as e:
        show_error(e)

    command = [ 'zenity','--forms','--title',
                'Tag the File' 
    ]

    if tags:
       combo = ['--add-combo','Existing Tags',
                '--combo-values',tags
       ]

       command = command + combo

    command = command + ['--add-entry','New Tag']

    result = run_cmd(command)
    if not result: sys.exit(1)
    result = result.decode().strip().split('|')
    for tag in result:
        if tag == '':
           continue
        write_to_file(conf_path,tag,file_paths)

if __name__ == '__main__':
     main()

read_tags.py:

#!/usr/bin/env python3
# -*- coding: utf-8 -*-

# Author: Serg Kolo  
# Date: Oct 1st, 2016
# Description: read_tags.py, script for
#    reading  paths to files under 
#    specific , user-defined tag
#    in ~/.tagged_files
# Written for: http://askubuntu.com/q/827701/295286
# Tested on : Ubuntu ( Unity ) 16.04

import subprocess
import json
import sys
import os


def run_cmd(cmdlist):
    """ Reusable function for running external commands """
    new_env = dict(os.environ)
    new_env['LC_ALL'] = 'C'
    try:
        stdout = subprocess.check_output(cmdlist, env=new_env)
    except subprocess.CalledProcessError as e:
        print(str(e))
    else:
        if stdout:
            return stdout

def show_error(string):
    subprocess.call(['zenity','--error',
                     '--title',__file__,
                     '--text',string
    ])
    sys.exit(1)

def read_tags_file(file,tag):

    if os.path.exists(file):
       with open(file) as f:
            data = json.load(f)
            if tag in data.keys():
                return data[tag]
            else:
                show_error('No such tag')
    else:
       show_error('Config file doesnt exist')

def get_tags(conf_file):
    """ read the tags file, return
        a string joined with | for
        further processing """    
    if os.path.exists(conf_file):
       with open(conf_file) as f:
            data = json.load(f)
            return '|'.join(data.keys())

def main():

    user_home = os.path.expanduser('~')
    config = '.tagged_files'
    conf_path = os.path.join(user_home,config)

    tags = get_tags(conf_path)
    command = ['zenity','--forms','--add-combo',
               'Which tag ?', '--combo-values',tags
    ]

    tag = run_cmd(command)

    if not tag:
       sys.exit(0)

    tag = tag.decode().strip()
    file_list = read_tags_file(conf_path,tag)
    command = ['zenity', '--list', 
               '--text','Select a file to open',
               '--column', 'File paths'
    ]
    selected = run_cmd(command + file_list)    
    if selected:
       selected = selected.decode().strip()
       run_cmd(['xdg-open',selected])

if __name__ == '__main__':
    try:
        main()
    except Exception as e:
        show_error(str(e))


I have found a way to do this.

Open a terminal (CTRL+ALT+T) and then run this command:

sudo add-apt-repository ppa:tracker-team/tracker

Enter your password, and when Prompted, press enter, then run

sudo apt-get update

then

sudo apt-get install tracker tracker-gui

Don't worry if it says it's already the latest version.

Now open Nautilus/Files and right click the document you want to add tags to. Select properties, then select the tab that says "Tags". Enter a tag into the text box and press enter or click the Add button to add it. You can also click a tag you have already added and select the Remove button to remove a tag. Please note that tags are case sensitive. The tags you create will be persistent throughout the system, so you can easily put a check next to a tag you already created to mark the file instead of manually typig it again.

After tagging the items wanted, you can now search for them, but not in Files. Go to activities, and search for the app Desktop Search. Launch it, and look at the options at the top. At the top left side of the Window, click the folder icon with the tool tip "Display results by files in a list". Now you have more options. Select the option just to the left of the search box with the tool tip "Find search criteria in file tags only". Now you can search tags!

To use this, enter the tags you want to search, seperated by commas and press enter. For example:

Important, September, Presentation

This will only show files which have all three tags: "Important", "September", and "Presentation".

By double clicking one, it will open the file in the default program, and by right clicking and selecting "Show Parent Directory", it will open it's location in Nautilus.

In Desktop Search, you can also click the second button from the right at the top of the window (usually a star or heart) to edit tags in the app itself!

There you have it! Hope this helps. If you have any more questions, let me know.