Tuesday, October 1, 2024

Displaying browsing history

I came across a good article that explains how to access a browser's recent history but it is written in C++ for some reason. I wanted to see how to do it using C# so I wrote this project. The original article is at https://www.codeproject.com/Articles/5388718/Displaying-recent-browsing-history If you don't subscribe to the CodeProject newsletters you can subscribe at https://www.codeproject.com/Feature/Insider/

You will need SQLite and also the .Net wrapper. Start a new WPF C# .NetCore project in Visual Studio and call it BrowserHistory. I'm going to display the Edge browser history but if you read the original article you can see how to easily modify the project to display the Chrome history.

Using Tools -> NuGet Package Manager -> Manage NuGet Packages for Solution, select Browse and search for SQLite (only one L). Install it. Now search for System.Data.SQLite and install that. Your solution looks like this.


Next we will write the XAML. Alter MainWindow.xaml to look like this. It defines a datagrid that binds to a property called dtHistory.

<Window x:Class="BrowserHistory.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:BrowserHistory"
        DataContext="{Binding RelativeSource={RelativeSource Self}}"
        mc:Ignorable="d"
        Title="Edge Browser History" Height="450" Width="800">
    <Grid>
        <DataGrid ItemsSource="{Binding dtHistory}" IsReadOnly="True" AutoGenerateColumns="False">
            <DataGrid.Columns>
                <DataGridTextColumn Header="Visited (UT)" Binding="{Binding DateTimeStamp}" Width="auto"/>
                <DataGridTextColumn Header="URL" Binding="{Binding url}" Width="*"/>
            </DataGrid.Columns>
        </DataGrid>
    </Grid>
</Window>

Now we can work on the fun stuff. The steps are.

  1. Find the browser history
  2. Make a copy (because the browser may have the original locked)
  3. Read parts of the browser history into a data table
  4. Convert microseconds since 1/1/1601 into a date time so we can display it
Step 1. The Edge browsing history for the current user can be found here...

        private String GetHistoryPath()
        {
            return Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData) + "\\Microsoft\\Edge\\User Data\\Default\\History";
        }

Step 2. We copy the database into the temp folder, deleting old copies first.

        private String CopyHistoryToTemp(String HistoryPath)
        {
            String TempPath = "";
            try
            {
                TempPath = Path.Combine(Path.GetTempPath(), "History");
                DeleteTemp(TempPath);
                File.Copy(HistoryPath, TempPath);
            }
            catch (Exception ex)
            {
                MessageBox.Show($"CopyHistoryToTemp:{ex.Message}");
                Shutdown();
            }
            return TempPath;
        }

        private void DeleteTemp(String TempPath)
        {
            try
            {
                File.Delete(TempPath);
            }
            catch (Exception ex)
            {
                // Don't sweat it
            }
        }

Step 3. Open the database and read the history into a data table. 

        private DataTable GetHistoryDT(String TempPath)
        {
            SQLiteConnection db = null;
            String SQL = "SELECT u.url, v.visit_time FROM urls u JOIN visits v on u.id=v.url ORDER BY v.visit_time DESC;";
            SQLiteDataAdapter da;
            SQLiteCommand cmd;
            DataTable dt = new DataTable();

            try
            {
                db = new SQLiteConnection($"data source={TempPath}");
                db.Open();
                cmd = db.CreateCommand();
                cmd.CommandText = SQL;
                da = new SQLiteDataAdapter(cmd);
                da.Fill(dt);
            }
            catch (Exception ex)
            {
                MessageBox.Show($"GetHistoryDT:{ex.Message}");
                Shutdown();
            }
            finally
            {
                if (db != null) db.Close();
            }
            return dt;
        }

Step 4. Create and populate a DateTimeStamp column because microseconds since 1/1/1601 isn't very useful.

        private DataTable PopulateDateTimeStamp(DataTable dt)
        {
            dt.Columns.Add(new DataColumn("DateTimeStamp", typeof(DateTime)));
            foreach (DataRow dr in dt.Rows)
            {
                dr["DateTimeStamp"] = ConvertWebkitToDateTime(Convert.ToInt64(dr["visit_time"]));
            }
            return dt;
        }

        private DateTime ConvertWebkitToDateTime(long WebKitTime)
        {
            return new DateTime(1601, 1, 1).AddMicroseconds(WebKitTime);
        }

Lastly we add a trivial method to terminate the application if there is an error.

        private void Shutdown()
        {
            Environment.Exit(0);
        }

If you run the program you will see results like this.



The complete MainWindow.xaml.cs looks like this.

using System.IO;
using System.Windows;
using System.Data.SQLite;
using System.Data;

namespace BrowserHistory
{
    public partial class MainWindow : Window
    {
        public DataTable dtHistory { get; set; }

        public MainWindow()
        {
            String HistoryPath = "";
            String TempPath = "";

            try
            {
                HistoryPath = GetHistoryPath();
                TempPath = CopyHistoryToTemp(HistoryPath);
                dtHistory = GetHistoryDT(TempPath);
                PopulateDateTimeStamp(dtHistory);
                InitializeComponent();
            }
            catch (Exception ex)
            {
                MessageBox.Show($"Main:{ex.Message}");
            }
            finally
            {
                DeleteTemp(TempPath);
            }
        }

        private String GetHistoryPath()
        {
            return Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData) ,"\\Microsoft\\Edge\\User Data\\Default\\History");
        }

        private String CopyHistoryToTemp(String HistoryPath)
        {
            String TempPath = "";
            try
            {
                TempPath = Path.Combine(Path.GetTempPath(), "History");
                DeleteTemp(TempPath);
                File.Copy(HistoryPath, TempPath);
            }
            catch (Exception ex)
            {
                MessageBox.Show($"CopyHistoryToTemp:{ex.Message}");
                Shutdown();
            }
            return TempPath;
        }

        private void DeleteTemp(String TempPath)
        {
            try
            {
                File.Delete(TempPath);
            }
            catch (Exception ex)
            {
                // Don't sweat it
            }
        }

        private DataTable GetHistoryDT(String TempPath)
        {
            SQLiteConnection db = null;
            String SQL = "SELECT u.url, v.visit_time FROM urls u JOIN visits v on u.id=v.url ORDER BY v.visit_time DESC;";
            SQLiteDataAdapter da;
            SQLiteCommand cmd;
            DataTable dt = new DataTable();

            try
            {
                db = new SQLiteConnection($"data source={TempPath}");
                db.Open();
                cmd = db.CreateCommand();
                cmd.CommandText = SQL;
                da = new SQLiteDataAdapter(cmd);
                da.Fill(dt);
            }
            catch (Exception ex)
            {
                MessageBox.Show($"GetHistoryDT:{ex.Message}");
                DeleteTemp(TempPath);
                Shutdown();
            }
            finally
            {
                if (db != null) db.Close();
            }

            return dt;
        }

        private DataTable PopulateDateTimeStamp(DataTable dt)
        {
            dt.Columns.Add(new DataColumn("DateTimeStamp", typeof(DateTime)));
            foreach (DataRow dr in dt.Rows)
            {
                dr["DateTimeStamp"] = ConvertWebkitToDateTime(Convert.ToInt64(dr["visit_time"]));
            }
            return dt;
        }

        private DateTime ConvertWebkitToDateTime(long WebKitTime)
        {
            return new DateTime(1601, 1, 1).AddMicroseconds(WebKitTime);
        }

        private void Shutdown()
        {
            Environment.Exit(0);
        }
    }
}



No comments:

Post a Comment