├── .gitignore ├── README.md ├── lib ├── Get-IniContent.ps1 ├── do-settings.ps1 ├── settings.txt ├── template.ini └── usage.txt ├── polly-auto.bat ├── polly-once.bat ├── polly.bat └── polly.ps1 /.gitignore: -------------------------------------------------------------------------------- 1 | *.swp 2 | *.ini 3 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # polly 2 | Batch file system using Windows Powershell to restore TiddlyWiki files from download directory to their original home directory 3 | 4 | Effectively, this becomes a new way to save your files, but with these features: 5 | 6 | - No binary executables 7 | - Human-readable batch script. 8 | - No special plugins in your TiddlyWiki file 9 | - No special browser required 10 | - No browser extension required. 11 | - No need for node.exe running in background 12 | - Total size expanded package only 100k 13 | - Backups as regular file and/or zip to specified directories 14 | - The ability to "parrot" extra copies to target directories (e.g. a Dropbox folder) 15 | 16 | ## Disclaimer 17 | 18 | **WARNING!** This should be considered Beta or Alpha level software. It has not been tested under all possible 19 | conditions. Please use parallel backup systems for any files you value. In particular, if you are using a networked 20 | file system, check regularly to be sure your files are saving/restoring as intended. It is recommended that you use 21 | one or more the backup systems (either zip or backup directory) but it is currently up to the user to periodically 22 | check and maintain those backup directories. Note that over time the backup directories may accumulate many megabytes 23 | of files. 24 | 25 | ## Prerequisites 26 | 27 | - Windows 7 or 10 28 | - May work with Linux or Mac with Powershell installed. 29 | - Powershell 5+ 30 | - Linux with Powershell 31 | 32 | It may actually work with earlier versions of Powershell, but some menus may disappear. You can download 33 | Powershell 5 from MicroSoft for free. 34 | 35 | ## Setup 36 | 37 | - Download a zip image of the project 38 | - Right click on the zip file and open properties. At the bottom, select "unblock" 39 | - Unzip the file wherever you want to run 40 | - Run polly.bat to begin. This will offer you the chance to set up your own settings.ini file. 41 | - Alternatively, you may want to set up your own settings.ini file by hand. 42 | 43 | ### Known adjustments for Linux (and probably Mac) 44 | 45 | - For Mac or Linux, you may need to edit or modify polly.bat and other files. e.g. 46 | - chmod 744 polly.bat 47 | - Change \ to / inside of polly.bat 48 | 49 | ## From the Usage file 50 | 51 | 52 | | 53 | | powershell -file .\polly.ps1 [-ini "settingsfile.ini"] [-run "menu"|"auto"|"once"|"parrot"] 54 | | 55 | | ----------------------------------------------------------------------------------------- 56 | | 57 | | Command Line -ini "settingsfile.ini", a user defined settings file 58 | | Parameters 59 | | ---------- Without an -ini parameter Polly loads "settings.ini". 60 | | Without "settings.ini" present Polly prompts to create it. 61 | | Once created users can make any number of additional ".ini" files. 62 | | 63 | | -run "menu" displays menu (which can start all other modes) 64 | | "auto" checks & restores wikis at user set intervals 65 | | "once" checks & restores wikis, then exists 66 | | "parrot" runs extended parroting (only), then exits [NOT YET DONE] 67 | | 68 | | Without a -run parameter Polly runs in "menu" mode. 69 | | 70 | | Batch Files Polly comes with three batch files ... 71 | | ----------- 72 | | "polly.bat" runs Polly in "menu" mode 73 | | "polly-auto.bat" runs Polly in "auto" mode 74 | | "polly-once.bat" runs Polly in "once" mode 75 | | 76 | | All load the default "settings.ini" file. 77 | | The user can create as many ".bat" files as needed to load any "-ini". 78 | 79 | -------------------------------------------------------------------------------- /lib/Get-IniContent.ps1: -------------------------------------------------------------------------------- 1 | <# Get-IniContent-01k-TT.ps1 2 | # 3 | # Changes prefixed by "# TT -" & "# MAS -" 4 | # 5 | # ----------------------------------------------------------------------------------- 6 | # This is a modified PS1 module that reads ".ini" files, a component of "PsIni 3.1.2" 7 | # 8 | # Info & code: https://github.com/lipkau/PsIni 9 | # Component source: https://www.powershellgallery.com/packages/PsIni/3.1.2/Content/Functions%5CGet-IniContent.ps1 10 | # Component (MIT) license: https://github.com/lipkau/PsIni/blob/master/LICENSE 11 | #> 12 | 13 | Function Get-IniContent { 14 | <# 15 | .Synopsis 16 | Gets the content of an INI file 17 | 18 | .Description 19 | Gets the content of an INI file and returns it as a hashtable 20 | 21 | .Notes 22 | Author : Oliver Lipkau 23 | Blog : http://oliver.lipkau.net/blog/ 24 | Source : https://github.com/lipkau/PsIni 25 | : http://gallery.technet.microsoft.com/scriptcenter/ea40c1ef-c856-434b-b8fb-ebd7a76e8d91 26 | Version 1.0 - 2010/03/12 - Initial release 27 | 1.1 - 2014/12/11 - Typo (Thx SLDR) 28 | Typo (Thx Dave Stiff) 29 | 1.3 - 2019/07/01 - Don't pass back comments (MAS) 30 | 1.4 - 2019/07/18 - Various changes to section tests (TT) 31 | 1.5 - 2019/07/22 - Needs 'continue' at bottom of section test (MAS) 32 | 33 | #Requires -Version 2.0 34 | 35 | .Inputs 36 | System.String 37 | 38 | .Outputs 39 | System.Collections.Hashtable 40 | 41 | .Parameter FilePath 42 | Specifies the path to the input file. 43 | 44 | .Example 45 | $FileContent = Get-IniContent "C:\myinifile.ini" 46 | ----------- 47 | Description 48 | Saves the content of the c:\myinifile.ini in a hashtable called $FileContent 49 | 50 | .Example 51 | $inifilepath | $FileContent = Get-IniContent 52 | ----------- 53 | Description 54 | Gets the content of the ini file passed through the pipe into a hashtable called $FileContent 55 | 56 | .Example 57 | C:\PS>$FileContent = Get-IniContent "c:\settings.ini" 58 | C:\PS>$FileContent["Section"]["Key"] 59 | ----------- 60 | Description 61 | Returns the key "Key" of the section "Section" from the C:\settings.ini file 62 | 63 | .Link 64 | Out-IniFile 65 | #> 66 | 67 | [CmdletBinding()] 68 | Param( 69 | [ValidateNotNullOrEmpty()] 70 | [ValidateScript({(Test-Path $_) -and ((Get-Item $_).Extension -eq ".ini")})] 71 | [Parameter(ValueFromPipeline=$True,Mandatory=$True)] 72 | [string]$FilePath 73 | ) 74 | 75 | Begin 76 | {Write-Verbose "$($MyInvocation.MyCommand.Name):: Function started"} 77 | 78 | Process 79 | { 80 | Write-Verbose "$($MyInvocation.MyCommand.Name):: Processing file: $Filepath" 81 | 82 | $ini = @{} 83 | switch -regex -file $FilePath 84 | { 85 | # TT - Added discard of leading and trailing whitespace 86 | "^\s*\[(.+?)\]\s*$" # Section 87 | { 88 | $section = $matches[1] 89 | $ini[$section] = @{} 90 | $CommentCount = 0 91 | } 92 | # TT - Added "-" as a possible first character for a comment line 93 | "^([\-;].*)$" # Comment 94 | { 95 | if (!($section)) 96 | { 97 | $section = "No-Section" 98 | $ini[$section] = @{} 99 | } 100 | $value = $matches[1] 101 | $CommentCount = $CommentCount + 1 102 | $name = "Comment" + $CommentCount 103 | # MAS - Fix to enable "continue" 104 | #$ini[$section][$name] = $value 105 | continue 106 | } 107 | # TT - Added discard of excess whitespace 108 | "(.+?)\s*=\s*(.*?)\s*$" # Key 109 | { 110 | if (!($section)) 111 | { 112 | $section = "No-Section" 113 | $ini[$section] = @{} 114 | } 115 | $name,$value = $matches[1..2] 116 | $ini[$section][$name] = $value 117 | } 118 | } 119 | Write-Verbose "$($MyInvocation.MyCommand.Name):: Finished Processing file: $FilePath" 120 | Return $ini 121 | } 122 | 123 | End 124 | {Write-Verbose "$($MyInvocation.MyCommand.Name):: Function ended"} 125 | } -------------------------------------------------------------------------------- /lib/do-settings.ps1: -------------------------------------------------------------------------------- 1 | # do-settings-01k-TT.ps1 2 | cls 3 | 4 | # TEST CONFIG -------------------------------- 5 | <# Run in PollyApp dir if run indepedent of main script 6 | cd "$PSScriptRoot"; cd .. 7 | gci 8 | # Test for "settings.ini" 9 | if(!(test-path ".\settings.ini")){$SettingsIniExists = "no"} 10 | else {$SettingsIniExists = "yes"} 11 | #> 12 | 13 | # CREATE SETTINGS.INI IF IT DOES NOT EXIST ---- 14 | if ($SettingsIniExists -eq "no"){ 15 | echo "" 16 | echo "" 17 | echo " | POLLY SETTINGS - $appAndVer" 18 | echo " |" 19 | echo " |" 20 | write-Warning " | You need to create a ""settings.ini""" 21 | write-warning " | file before Polly can work!" 22 | echo " |" 23 | echo " | C - Create ""settings.ini""" 24 | echo " | " 25 | echo " | Q - Quit?" 26 | } 27 | 28 | # EDIT LOADED INI, OR CREATE NEW INI ---------- 29 | else { 30 | echo "" 31 | echo "" 32 | echo " | POLLY SETTINGS - $appAndVer" 33 | echo " |" 34 | echo " |" 35 | echo " | E - Edit current ""$settingsFile""?" 36 | echo " |" 37 | echo " | N - Create New settings file?" 38 | echo " |" 39 | echo " | Q - Quit?" 40 | } 41 | 42 | # SELECT & DO --------------------------------- 43 | echo "" 44 | $selection = read-host "? Key" 45 | # !! No error checking yet !! 46 | switch ($selection) { 47 | 'C'{ 48 | $iniName = "settings.ini" 49 | #Path check 50 | copy-item "$iniTemplate" -destination "$iniName" 51 | } 52 | 'E'{ 53 | $iniName = "$settingsFile" 54 | #& $editor "$iniName" 55 | } 56 | 'N'{ 57 | echo "" 58 | $iniName = "new.ini" 59 | $iniName = Read-Host -Prompt " | What do you want to call this ""$iniName"" file?" 60 | copy-item ".\lib\template.ini" -destination "$iniName" 61 | #& $editor "$iniName" 62 | } 63 | 'Q' {cls; exit} 64 | } 65 | & $editor "$iniName" 66 | 67 | # FINISH INI SETTINGS ------------------------- 68 | cls; echo ""; echo ""; 69 | get-content $iniHelp -raw 70 | echo "" 71 | echo "" 72 | echo " | EDITING ""$iniName"" in $editor ..." 73 | echo " |" 74 | echo " |" 75 | echo " | Information on available settings is displayed above." 76 | echo " |" 77 | write-warning " | When you have finished editing, save the settings file," 78 | write-warning " | quit, then run the Polly batch for settings to be loaded." 79 | echo " |" 80 | echo " | To Quit..." 81 | echo "" 82 | pause 83 | cls 84 | exit 85 | 86 | # ?? BATCH FILE MAKER ?? -------------------------------------------------------------------------------- /lib/settings.txt: -------------------------------------------------------------------------------- 1 | | .INI FILE SETTINGS 2 | | 3 | | 4 | | "*" indicates minimal settings required for Polly to function. 5 | | 6 | | * [general] - Required. Section header for basic settings 7 | | 8 | | inidescription= 9 | | Optional. Useful if you have more than one .ini file. 10 | | 11 | | waitseconds= 12 | | Optional. Seconds to wait between checks in "auto" mode. 13 | | Defaults to 60 seconds. 14 | | 15 | | * downloaddir= 16 | | Required. Directory where browser downloads are saved. 17 | | 18 | | wikidir= 19 | | Optional. Useful if your wikis are nested under one directory. 20 | | 21 | | backupdir= 22 | | Optional. Directory to create date-stamped backups in. 23 | | 24 | | backupzipdir= 25 | | Optional. Directory to create date-stamped zip archives in. 26 | | 27 | | A basic settings file might look like this ... 28 | | 29 | | [general] 30 | | inidescription=My Basic Settings 31 | | downloaddir=C:\Users\Polly\Downloads 32 | | wikidir=C:\Users\Polly\Documents\tw-wikis 33 | | 34 | | If a setting is not defined its corresponding menu item in Polly 35 | | in "menu" mode is not shown. 36 | | 37 | | * [wikis] - Required. And you need at least one wiki under it. In this 38 | | section you add the paths to the wikis you want Polly to monitor 39 | | & restore. One wiki per line, for example ... 40 | | 41 | | [wikis] 42 | | file1=C:\users\jon\notes.html 43 | | toDoWiki=C:\users\jon\mywikis\todo.html 44 | | ;file2=C:\photos\photo-album.htm 45 | | 46 | | Wiki names must be UNIQUE. 47 | | 48 | | Each file is preceded by a unique identifer, e.g. "file1" 49 | | followed by "=". The identifier should only contain letters or 50 | | numbers. No spaces or special punctuation. 51 | | 52 | | Lines starting ";" are ignored. This lets you switch off 53 | | restoration for that wiki. 54 | | 55 | | [parrots] - Optional. Under this section you add paths in the same way as for 56 | | [wikis] ... 57 | | 58 | | [parrots] 59 | | parrot1=C:\users\jon\Dropbox\notes.html 60 | | parrot2=C:\website\photo-album.htm 61 | | 62 | | The names of parrot wiki need to match the names of items in the 63 | | [wikis] section since [parrots] clone items in [wikis]. 64 | | 65 | | Parrots can include any type of file. -------------------------------------------------------------------------------- /lib/template.ini: -------------------------------------------------------------------------------- 1 | --- POLLY v0.1 SETTINGS FILE ------------------- 2 | --- Replace example lines with your own settings 3 | --- Remove unused setting lines ---------------- 4 | 5 | [general] 6 | --- DESCRIPTION (optional): useful if you have more than one .ini file 7 | inidescription=My first settings 8 | 9 | --- TIMING (required): seconds to wait between checking for changed downloads 10 | waitseconds=60 11 | 12 | --- DOWNLOADS DIRECTORY (required): where browser downloads go 13 | downloaddir=C:\Users\Polly\Downloads 14 | 15 | --- WIKIS DIRECTORY (optional): useful if wikis are nested under one directory 16 | wikidir=C:\tiddlys\tw-wikidir 17 | 18 | --- WIKIS BACKUP DIRECTORY (optional) 19 | backupdir=C:\Users\Polly\Downloads\tw_backups\wikis 20 | 21 | --- WIKIS ZIP ARCHIVES DIRECTORY (optional) 22 | backupzipdir=C:\Users\Polly\Downloads\tw_backups\zipped 23 | 24 | --- WIKIS DIRECTORY (optional): useful if wikis are nested under one directory 25 | [wikidirs] 26 | dir1=C:\tiddlys\tw-wikidir 27 | 28 | [wikis] 29 | --- WIKIS FOR RESTORE: one per line in format: "label1=path-to-wiki-file" 30 | file1=C:\tiddlys\tw-wikidir\parrot\parrot-red.html 31 | file2=C:\tiddlys\tw-wikidir\robin\breasted\red\round-robin.tw 32 | 33 | [parrots] 34 | --- PARROTS (optional): auto-updated secondary copies of wikis 35 | parrot1=C:\tw-sites\www.polly.net\parrot-red.html 36 | -------------------------------------------------------------------------------- /lib/usage.txt: -------------------------------------------------------------------------------- 1 | | 2 | | powershell -file .\polly.ps1 [-ini "settingsfile.ini"] [-run "menu"|"auto"|"once"|"parrot"] 3 | | 4 | | ----------------------------------------------------------------------------------------- 5 | | 6 | | Command Line -ini "settingsfile.ini", a user defined settings file 7 | | Parameters 8 | | ---------- Without an -ini parameter Polly loads "settings.ini". 9 | | Without "settings.ini" present Polly prompts to create it. 10 | | Once created users can make any number of additional ".ini" files. 11 | | 12 | | -run "menu" displays menu (which can start all other modes) 13 | | "auto" checks & restores wikis at user set intervals 14 | | "once" checks & restores wikis, then exists 15 | | "parrot" runs extended parroting (only), then exits [NOT YET DONE] 16 | | 17 | | Without a -run parameter Polly runs in "menu" mode. 18 | | 19 | | Batch Files Polly comes with three batch files ... 20 | | ----------- 21 | | "polly.bat" runs Polly in "menu" mode 22 | | "polly-auto.bat" runs Polly in "auto" mode 23 | | "polly-once.bat" runs Polly in "once" mode 24 | | 25 | | All load the default "settings.ini" file. 26 | | The user can create as many ".bat" files as needed to load any "-ini". 27 | 28 | -------------------------------------------------------------------------------- /polly-auto.bat: -------------------------------------------------------------------------------- 1 | @echo off && mode con:cols=105 2 | 3 | :: START POWERSHELL SCRIPT: has to be in same directory as this batch file 4 | 5 | powershell -file .\polly.ps1 -run "auto" 6 | -------------------------------------------------------------------------------- /polly-once.bat: -------------------------------------------------------------------------------- 1 | @echo off && mode con:cols=85 2 | 3 | :: START POWERSHELL SCRIPT -- has to be in same directory as this batch file 4 | 5 | powershell -file .\polly.ps1 -run once 6 | -------------------------------------------------------------------------------- /polly.bat: -------------------------------------------------------------------------------- 1 | @echo off && mode con:cols=100 2 | 3 | :: START POWERSHELL SCRIPT -- has to be in same directory as this batch file 4 | 5 | powershell -file .\polly.ps1 6 | -------------------------------------------------------------------------------- /polly.ps1: -------------------------------------------------------------------------------- 1 | # polly-01Oa-MS.ps1 -- PS5/PS6/PS7 2 | 3 | # GET COMMAND-LINE PARAMETERS ----------------- 4 | param ([string]$ini, [string]$run); 5 | #cls MAS 6 | 7 | # SET RUN LOCATION ---------------------------- 8 | Set-Location "$PSScriptRoot" 9 | 10 | $stemtracker = @{} # Map to keep track of unique stems. 11 | 12 | # LOAD LIBRARIES ------------------------------ 13 | #region LoadLibraries 14 | 15 | # --- CORE 16 | . ./lib/Get-IniContent.ps1 17 | # Core libraries used 18 | $libs = "./lib/Get-IniContent.ps1, ./lib/do-settings.ps1" 19 | # MAS Marker (c)heck-filedupes 20 | function check-filedupes( [string]$pFile1) { 21 | #param( [string]$pFullname ) 22 | #$fullstem = $pFile1.split($SEPREG)[-1] 23 | $fullstem = Split-Path -Leaf $pFile1 ; 24 | if( $stemtracker.containskey( $fullstem)) { 25 | $file2 = $stemtracker[$fullstem] 26 | echo "" 27 | echo "=== DUPLICATE STEM ===" 28 | echo "Stem file $fullstem for " 29 | echo "$pFile1 is already in use by file " 30 | echo "$file2 . Please check your wiki file list " 31 | echo "and wiki dirs and eliminate duplicates." 32 | echo "Polly will now close in $closeSeconds seconds" 33 | start-sleep -seconds $closeSeconds 34 | exit 35 | } 36 | return $fullstem 37 | } 38 | 39 | ### FOLLOWING FUNCTION CAN BE DELETED 40 | function get-fullstem { 41 | 42 | param( [string]$pFullname ) 43 | $fileName = $pFullname.split($SEPREG)[-1] 44 | #echo "" 45 | #echo "| $fileName" 46 | #echo "|" 47 | 48 | $stempieces = $fileName.split(".") 49 | $exten = $stempieces[-1].trim() 50 | $stempieces = $stempieces[0..($stempieces.length-2)] 51 | $stem = $stempieces -join "." 52 | return $stem.$exten 53 | } 54 | 55 | # ??? 56 | function expand-dir { 57 | param( [string]$pDirname ) 58 | if ([string]::IsNullOrEmpty($pDirname)) {return $null} 59 | $dirName = [System.Environment]::ExpandEnvironmentVariables($pDirname) 60 | 61 | #if ($isWindows) { echo I think this is windows } 62 | #if ($isLinux) { echo I think his is linux } 63 | 64 | if( ($isLinux -or $isMacOS) -and !$dirName.StartsWith("/") -and !$dirName.StartsWith("~") ) { 65 | $dirName = "$scriptDir/$dirName" 66 | } 67 | if( $isWindows -and ![System.IO.Path]::IsPathRooted($dirName) ) { 68 | $dirName = "$scriptDir\$dirName" 69 | } 70 | return $dirName 71 | } 72 | 73 | # --- EXTENSIONS [TESTING] 74 | #. ./dev/experiments.ps1 75 | 76 | #endregion LoadLibraries 77 | 78 | # INTERNAL SETTINGS --------------------------- 79 | #region InternalSettings 80 | 81 | # --- APPLICATION NAME & VERSION 82 | $appAndVer = "POLLY v0.1Oa-MS (PS5/PS6/PS7)" 83 | 84 | # --- PATH SEPARATORS: support o/s variations 85 | $SEPARATOR= [IO.Path]::DirectorySeparatorChar 86 | $SEPREG = '[/\\]+' 87 | 88 | # --- IF PS 6+/CORE NOT RUNNING: do Windows only 89 | if($PSVersionTable.PSVersion.major -lt 6) { 90 | # Variables auto-available in PSCore+ 91 | $isWindows = $true ; 92 | $isMacOS = $false; 93 | $isLinux = $false; 94 | } 95 | 96 | # --- CONSOLE: ?? Not clear yet how cross-platform these settings can be ?? 97 | $psHost = get-host 98 | $console = $psHost.UI.RawUI 99 | $console.WindowTitle = "$appAndVer -- $mode mode" 100 | $console.ForegroundColor = "yellow" 101 | 102 | # ?? Breaks in PS IDE, but otherwise works ?? 103 | #$sizeWidth = $console.WindowSize; $sizeWidth.Width = 85 104 | #$console.WindowSize = "$sizeWidth" 105 | # O/S variants 106 | if($isWindows){$console.BufferSize = New-Object System.Management.Automation.Host.Size(165,5000)} 107 | 108 | # --- RUN MODE 109 | # Run mode default: can be overridden by parameter "-run ["auto"|"once"|"parrot"] 110 | $mode = "menu" 111 | # Command line parameter overrides 112 | if (![string]::IsNullOrEmpty($run)){$mode = "$run"} 113 | # Additional modes from MENU: 'O' for "once-menu", 'P' for "parrot-menu" [NOT COMPLETE] 114 | 115 | # --- TIMING 116 | # polly-once /polly-parrot batch files with paramters "-run $mode" -- timeout to exit 117 | $closeSeconds = 15 118 | # once/'O' & parrot/'P' from menu -- timeout to refesh menu 119 | $menuRefreshSeconds = 10 120 | 121 | # --- PATHING & FILES 122 | # Script file 123 | $scriptDrv = $pwd.drive.name; $scriptDrv = "${scriptDrv}:" 124 | $scriptDir = "$PSScriptRoot" 125 | $scriptFile = $MyInvocation.MyCommand 126 | 127 | # Settings file default: can be overridden via command line parameter "-ini [file.ini]" 128 | $settingsFile = "settings.ini" 129 | # "settings.ini" template 130 | $iniTemplate = "./lib/template.ini" 131 | # Settings help file 132 | $iniHelp = "./lib/settings.txt" 133 | # Command line override 134 | if (![string]::IsNullOrEmpty($ini)) {$settingsFile = "$ini"} 135 | 136 | # Usage notes 137 | $usageFile = "./lib/usage.txt" 138 | 139 | # Resources 140 | $urlPollyHlpDesc = "Polly Help" ; $urlPollyHlp = "https://tidbits.wiki/polly/polly.html" 141 | $urlPollyDevDesc = "Polly GitHub" ; $urlPollyDev = "https://github.com/Marxsal/polly" 142 | 143 | # --- MENU OPTIONS HIDING 144 | # Optional .ini settings that are null or empty are hidden, though active 145 | # Z - Run Tests: "hide" (default) or "show". When hidden, acesss to tests is also removed 146 | $hideTests = "hide" 147 | 148 | # --- EDITOR 149 | # ?? Move to do-settings.ps1 ?? 150 | $editor = "notepad.exe" 151 | 152 | #endregion InternalSettings 153 | 154 | # USER SETTINGS ------------------------------- 155 | #region UserSettings 156 | 157 | # --- CREATE SETTINGS.INI FILE IF MISSING 158 | if(!(test-path "./settings.ini")){#$SettingsIniExists = "no"; 159 | . ./lib/do-settings.ps1; exit} 160 | 161 | # --- GET SETTINGS FROM .INI FILE 162 | $settings = Get-IniContent "$scriptDir/$settingsFile"; 163 | $general = $settings["general"] ; 164 | 165 | # --- DOWNLOADS DIRECTORY: where browser downloads go 166 | $downloaddir = $general["downloaddir"] 167 | # If not defined use o/s "userprofile" variable 168 | if ([string]::IsNullOrEmpty($downloaddir)){$downloaddir = "$Env:userprofile\Downloads"} 169 | # Expand dir in case it contains environmental variables. Also conform to absolute address 170 | $downloaddir = expand-dir($downloaddir) 171 | # ?? should add a path check ?? Some (few?) users reset through registry the downloads dir! 172 | 173 | $filesHolder = $settings["wikis"].values ; 174 | 175 | # Parse files, convert to absolute paths 176 | #echo "I think count is $files.length" 177 | #get-member -InputObject $files 178 | $files = @() 179 | #for ($i = 0; $i -le ($files.length - 1); $i += 1) { 180 | foreach($file in $filesHolder) { 181 | #echo "File before expansion: $file" 182 | $file = expand-dir($file) 183 | #echo "File after expansion: $file" 184 | $file_obj = Get-ChildItem $file 185 | $file_fullname = $file_obj.fullname ; 186 | #echo "File fullname: $file_fullname" 187 | #if( ![System.IO.Path]::IsPathRooted($file) ) { 188 | # $file = "$scriptDir\$file" 189 | #} 190 | $fullstem = check-filedupes $file_fullname 191 | $files += $file 192 | $stemtracker[$fullstem] = $file.fullname 193 | #echo "Expanded and absoluted file: $file" 194 | } 195 | 196 | # Get all htm/html files from specified "wikis" directories 197 | 198 | #echo "Download dir is at: $downloaddir" 199 | $dirsHolder = $settings["wikidirs"].values 200 | #echo $dirsHolder 201 | foreach($dir in $dirsHolder) { 202 | # echo "File before expansion: $dir" 203 | $dir = expand-dir($dir) 204 | # echo "File after expansion: $dir" 205 | if(test-path -path $dir -pathtype "container" ) { 206 | # echo "Comparing paths $dir and $downloaddir BEFORE" 207 | if( (join-path $dir '') -eq (join-path $downloaddir '')) { 208 | echo "Can not use download directory as wikis dir." 209 | } else { 210 | # echo "Dir $dir passes directory tests" 211 | #$temp = compare-object -ReferenceObject $downloaddir -DifferenceObject $dir 212 | #$temp | select-object -property * -erroraction stop 213 | 214 | # echo "Comparing paths $dir and $downloaddir AFTER" 215 | #Get-ChildItem -path $dir -recurse | ? -FilterScript {$_.extension -match "htm*|tw"} | sort-object -property Name | Format-Table Name, "in", Fullname -autosize -hidetableheaders; 216 | $wfiles = Get-ChildItem -path $dir -exclude $downloaddir | ? -FilterScript {$_.extension -match "htm*|tw"} 217 | #$wfiles | select-object 218 | foreach($file in $wfiles) { 219 | #echo $file.fullname 220 | #$file | select-object 221 | # MAS This seems to work, but keep in mind: 222 | # $fullstem = Split-Path -Leaf $pFile1 223 | $fullstem = $file.fullname.split($SEPREG)[-1] 224 | #$fullstem = get-fullstem $file.fullname 225 | 226 | #echo "Fullstem is: $fullstem" 227 | $dummy = check-filedupes $file.fullname 228 | $files += $file.fullname 229 | $stemtracker[$fullstem] = $file.fullname 230 | #get-member #-InputObject $files 231 | } 232 | } 233 | } else { 234 | echo "$dir is not a directory" 235 | } 236 | } 237 | 238 | 239 | 240 | 241 | $parrots = $settings["parrots"].values ; 242 | 243 | # Parse "parrots" to "pollies" to avoid repeating on every restore 244 | $pollies = @{} 245 | foreach ($parrot in $parrots) { 246 | $parrotName = $parrot.split($SEPARATOR)[-1] 247 | #echo "I see parrot stem: $parrotName " 248 | #$parrotName 249 | $parrotDir = $parrot.substring(0,$parrot.length - $parrotName.length) 250 | 251 | $pollies[$parrotName] = $parrotDir 252 | #$stempieces = $parrotName.split(".") 253 | #$exten = $stempieces[-1].trim() 254 | #$stempieces = $stempieces[0..($stempieces.length-2)] 255 | #$stem = $stempieces -join "." 256 | #$copyme = ls $stem*.$exten | sort LastWriteTime | select -last 1 257 | #$copyme_fullname = $copyme.FullName 258 | } 259 | 260 | #-> TEST POLLIES <- 261 | if ($pollies.Count -gt 0 ) { 262 | #echo "I don't think pollies is null. Here's what I see: " 263 | foreach ($polly in $pollies.keys) { 264 | $temp = $pollies[$polly] 265 | #echo "I see polly $polly with dir $temp " 266 | } 267 | } 268 | 269 | # --- DESCRIPTION (optional): useful if you have more than one .ini file 270 | $inidescription = $general["inidescription"] 271 | 272 | 273 | # --- WIKIS DIRECTORY (optional): useful where wikis are nested under one directory 274 | $wikidir = $general["wikidir"] 275 | 276 | # --- WIKIS BACKUP DIR (optional): where to create date-stamped backups 277 | $backupdir= expand-dir $general["backupdir"] 278 | #$backupdir = expand-dir $backupdir 279 | 280 | 281 | # --- WIKIS BACKUP ZIP DIR (optional): where to create date-stamped zip archives 282 | $backupzipdir= expand-dir $general["backupzipdir"] 283 | 284 | # --- TIMING: seconds to wait between checks 285 | $waitSeconds = $general["waitseconds"] 286 | 287 | #endregion UserSettings 288 | 289 | #-> TESTS: when 'Z' menu option activated <- 290 | function tests {echo ""; echo " | TESTS"; echo " |"; echo " | Who's a pretty Polly! Who's a pretty Polly!"; echo "" 291 | 292 | # --- MS INI LOADING CHECK 293 | #$settings = Get-IniContent "$scriptDir\$settingsFile"; 294 | 295 | $files = $settings["wikis"].values ; 296 | foreach ($obj in $files) {echo "I see stem: " $obj.split($SEPARATOR)[-1] } 297 | $objMembers = $settings["wikis"].psobject.members | where-object Name -like 'file'; 298 | $objMembers ; 299 | foreach ($obj in $objMembers){ $obj.Value } ; $objMembers 300 | 301 | #pause 302 | #break 303 | } 304 | 305 | # MAKE ARCHIVE STRING FROM DATE, STEM & EXTEN - 306 | function generate-archivestring { 307 | param( [datetime]$dt, [string]$pFilename, [string]$pExten) 308 | return "$pFilename-$(get-date -year $dt.year -month $dt.month -day $dt.day -hour $dt.hour -minute $dt.minute -second $dt.second -millisecond $dt.millisecond -f yyyy-MM-dd_HHmmss).$pExten" 309 | } 310 | 311 | # RUN INFO ------------------------------------ 312 | function runinfo { 313 | cls 314 | echo "" 315 | echo "" 316 | echo " | RUN INFO - $appAndVer" 317 | echo " |" 318 | echo " |" 319 | write-host " | PowerShell:"$PsVersionTable.PSVersion 320 | echo " | Username: $Env:Username" ; $policy = Get-ExecutionPolicy 321 | echo " | Policy: $policy" 322 | echo " |" 323 | echo " | Polly version: $appAndVer" 324 | echo " |" 325 | echo " | Settings file: $settingsFile" ; if (![string]::IsNullOrEmpty($inidescription)){ 326 | echo " | $inidescription"} 327 | echo " |" 328 | echo " | Script run mode: $mode" 329 | echo " | Auto-mode timer: $waitSeconds seconds between checks" 330 | echo " | Once-mode timer: $closeSeconds seconds to close after restore" 331 | echo " |" 332 | echo " | Script drive: $scriptDrv" 333 | echo " | Script dir: $scriptDir" 334 | echo " | PS1 script: $scriptFile" 335 | echo " | Libraries: $libs" 336 | echo " |" 337 | echo " | O/S Downloads: $Env:userprofile$SEPARATOR`Downloads (guessed)" 338 | echo " | Downloads dir: $downloaddir (used by Polly)" ; if (![string]::IsNullOrEmpty($wikidir)){ 339 | echo " | Wikis dir: $wikidir"} ; if (![string]::IsNullOrEmpty($backupdir)){ 340 | echo " | Backups dir: $backupdir"} ; if (![string]::IsNullOrEmpty($backupzipdir)){ 341 | echo " | Zip backups dir: $backupzipdir"} 342 | echo " |" 343 | echo " | Usage notes: $usageFile" 344 | echo " | $urlPollyHlpDesc`: $urlPollyHlp" 345 | echo " | $urlPollyDevDesc`: $urlPollyDev" ; if($hideTests -eq "show"){ 346 | echo " |" 347 | echo " | Testing: on"} 348 | echo "" 349 | } 350 | 351 | # USAGE --------------------------------------- 352 | function usage { 353 | echo "" 354 | echo "" 355 | echo " | POLLY USAGE - $appAndVer" 356 | echo " |" 357 | get-content $usageFile -raw 358 | } 359 | 360 | # MAKE-DIRECTORY ------------------------------ 361 | function Make-Directory { 362 | 363 | param( [string]$DirectoryToCreate) 364 | 365 | if (-not (Test-Path -LiteralPath $DirectoryToCreate)) { 366 | 367 | try { 368 | New-Item -Path $DirectoryToCreate -ItemType Directory -ErrorAction Stop | Out-Null #-Force 369 | } 370 | catch { 371 | Write-Error -Message "Unable to create directory '$DirectoryToCreate'. Error was: $_" -ErrorAction Stop 372 | } 373 | #echo "| Comparing: ""$short_name"" in downloads with ..." 374 | echo "| New dir: Created directory '$DirectoryToCreate'." 375 | } 376 | else { 377 | #echo "| New dir: Created directory '$DirectoryToCreate'." 378 | "| No new dir: $DirectoryToCreate already exists" 379 | } 380 | } 381 | 382 | # PARROT -------------------------------------- 383 | function parrot { 384 | #echo "I am in parrot function" 385 | # Write pollies/parrots if applicable 386 | if ($pollies.Count -gt 0 ) { 387 | #echo "I don't think pollies is null. Am I repeating myself?" 388 | #$pollies 389 | #echo "Attempting to parrot $stem.$exten to $parrotDir" 390 | if($pollies.ContainsKey("$stem.$exten")) { 391 | $parrotDir = $pollies["$stem.$exten"] 392 | echo "|" 393 | echo "| Parroting: ""$stem.$exten""" 394 | echo "| to: $parrotDir$SEPARATOR$stem.$exten" 395 | #echo "Attempting to parrot $stem.$exten to $parrotDir" 396 | if ($mode -eq "parrot-menu") { 397 | $copyme = get-item "$fileDir$SEPARATOR$stem.$exten" 398 | } 399 | Copy-Item $copyme -Destination "$parrotDir$SEPARATOR$stem.$exten" 400 | } 401 | } 402 | } 403 | 404 | # MENU PRELIM --------------------------------- 405 | echo "" 406 | # PS version 407 | write-host " PowerShell:"$PsVersionTable.PSVersion 408 | 409 | # MENU ---------------------------------------- 410 | function menu { 411 | echo "" 412 | echo " +===========================+" 413 | echo " $appAndVer" 414 | echo " +=============================+" 415 | echo " | A - Auto-Restore every $($waitSeconds.padleft(3,' '))s |" 416 | echo " | O - Restore Once |" ; if (![string]::IsNullOrEmpty($parrots)){ 417 | echo " | P - Parrot Now |"} 418 | echo " | ---------------------- |" 419 | echo " | D - Downloads Folder |" ; if (![string]::IsNullOrEmpty($wikidir)) { 420 | echo " | T - TiddlyWiki Folder |"} ; if (![string]::IsNullOrEmpty($backupzipdir) -or ![string]::IsNullOrEmpty($backupdir)){ 421 | echo " | B - Backups |"} 422 | echo " | ---------------------- |" 423 | echo " | S - Settings |" 424 | echo " | ---------------------- |" 425 | echo " | U - Usage & Run Info |" 426 | echo " | H - Online Help & GitHub |" 427 | echo " | ---------------------- |" ; if($hideTests -eq "show"){ 428 | echo " | Z - Run Tests |" 429 | echo " | ---------------------- |"} 430 | echo " | Q - Quit |" 431 | echo " +=============================+" 432 | echo " $settingsFile active" 433 | echo " +===========================+" 434 | } 435 | # END FUNCTION MENU 436 | 437 | $running=$true 438 | 439 | while($running) { 440 | #echo "INSIDE RUNNING LOOP (pausing)" 441 | #start-sleep -seconds 10 442 | do { 443 | if($mode -eq "auto" -or $mode -eq "once") {$running=$false; break} 444 | menu 445 | $selection = read-host "? Key" 446 | switch ($selection) { 447 | 'A' {$mode = "auto"} 448 | 'O' {$mode = "once" ; $closeSeconds = "$menuRefreshSeconds"; cls} 449 | 'P' {$mode = "parrot-menu"; $closeSeconds = "$menuRefreshSeconds"; cls} 450 | 'D' {cls; echo ""; echo ""; 451 | echo " | SAVED DOWNLOADS (newest first) in ... "; 452 | echo " |"; 453 | echo " | $downloaddir" 454 | Get-ChildItem -path $downloaddir *.* -file | sort-object -property LastWriteTime -descending | Format-Table Name, LastWriteTime, Length -autosize -hidetableheaders; 455 | } 456 | 'T' {cls; echo ""; echo ""; 457 | echo " | TIDDLYWIKI FOLDER $wikidir"; 458 | echo " |" 459 | echo " | Wikis & directories they are in ..."; 460 | Get-ChildItem -path $wikidir -recurse | ? -FilterScript {$_.extension -match "htm*|tw"} | sort-object -property Name | Format-Table Name, "in", Fullname -autosize -hidetableheaders; 461 | } 462 | 'B' {cls; echo "" 463 | if($backupzipdir -ne $null){ 464 | echo "" 465 | echo " | ZIP ARCHIVES of Wikis"; 466 | echo " |" 467 | echo " | Latest zip archive of each wiki, under: $backupzipdir" 468 | echo "" 469 | Get-ChildItem -path $backupzipdir *.* -recurse | where {$_.psiscontainer} | foreach {Get-ChildItem $_.fullname | sort LastWriteTime | select -expand name -last 1} 470 | } 471 | if($backupdir -ne $null){ 472 | echo "" 473 | echo " | BACKUPS of Wikis"; 474 | echo " |" 475 | echo " | Latest backup of each wiki, under: $backupdir" 476 | echo "" 477 | Get-ChildItem -path $backupdir *.* -recurse | where {$_.psiscontainer} | foreach {Get-ChildItem $_.fullname | sort LastWriteTime | select -expand name -last 1} 478 | } 479 | echo "" 480 | } 481 | 'S' {. ./lib/do-settings.ps1; exit} 482 | 'U' {cls; runinfo; usage} 483 | 'H' {cls; echo ""; echo ""; 484 | invoke-expression "start $urlPollyHlp" 485 | invoke-expression "start $urlPollyDev" 486 | echo " | ONLINE RESOURCES LAUNCHED ..."; 487 | echo " |" 488 | echo " | $urlPollyHlpDesc" 489 | echo " | $urlPollyHlp"; 490 | echo " |" 491 | echo " | $urlPollyDevDesc" 492 | echo " | $urlPollyDev"; 493 | echo "" 494 | } 495 | 'Z' {if($hideTests -ne "show"){cls} else {cls; echo ""; runinfo; tests}} 496 | 'Q' { 497 | cls #MAS ; 498 | exit} 499 | } 500 | } 501 | until ($selection -eq 'a' -or $selection -eq 'o' -or $selection -eq 'p') 502 | 503 | #echo "Broke out of menu decider loop (pausing)" 504 | #start-sleep -seconds 5 505 | 506 | # RESTORER ------------------------------------ 507 | #region Restorer 508 | 509 | cls # MAS 510 | echo "" 511 | echo " || AUTO-RESTORE - $appAndVer" 512 | echo " ||" 513 | echo " || Every $waitSeconds seconds checks for new saves in ..." 514 | echo " ||" 515 | echo " || ""$downloaddir"" " 516 | echo " ||" 517 | echo " || Press [Ctrl+C] to exit" 518 | echo "" 519 | 520 | #endregion Restorer 521 | 522 | while(1) { 523 | #echo "INSIDE RESTORER LOOP" 524 | # OLD for($i=0;$i -lt $files.Length; $i++) 525 | foreach($file in $files) { 526 | #$filePath = $[$i] 527 | #echo "File name before split: $file" 528 | #$fileName = $file.split($SEPREG)[-1] # We don't need this -- PS has tools for file path splitting 529 | #echo "Splitting a different way: " 530 | $fileName = Split-Path -Leaf $file ; 531 | echo "" 532 | echo "| $fileName " 533 | echo "|" 534 | 535 | $fileDir = $file.substring(0,$file.length - $fileName.length) 536 | 537 | $stempieces = $fileName.split(".") 538 | $exten = $stempieces[-1].trim() 539 | $stempieces = $stempieces[0..($stempieces.length-2)] 540 | $stem = $stempieces -join "." 541 | 542 | 543 | if($mode -ne "parrot-menu") { 544 | $pattern = [regex]::escape($stem) + "(\s*\(\d+\))*" + [regex]::escape('.'+$exten) 545 | echo "| Using Pat: $pattern" 546 | #echo "| Using Stm: $stem" # MAS 547 | #echo "| Using ext: $exten" # MAS 548 | #echo "| Separator is: $SEPARATOR" #MAS 549 | $copyme = Get-ChildItem $downloaddir$SEPARATOR$stem*.$exten | Where-Object {$_.Name -match $pattern } | sort-object -property LastWriteTime | select -last 1 550 | 551 | 552 | $short_name = $copyme.Name 553 | $copyme_fullname = $copyme.FullName 554 | 555 | if($copyme_fullname -eq $null) { 556 | echo "| Skipping ""$stem.$exten"" as no saves found in downloads," 557 | echo "| ... maybe you have not downloaded it yet?" 558 | echo "" 559 | continue ; 560 | } 561 | 562 | $copyme = $copyme_fullname 563 | #echo "Checking fullname $copyme_fullname" 564 | 565 | $destination = Get-Item "$fileDir$SEPARATOR$stem.$exten" 566 | $destinationTimestamp = $destination.LastWriteTime 567 | #$dt = $destinationTimestamp 568 | $source = Get-Item "$copyme" 569 | echo "| Comparing: ""$short_name"" in downloads with ..." 570 | echo "| Wiki: $destination" 571 | 572 | If( $source.LastWriteTime -gt $destination.LastWriteTime.addSeconds(1) ) { 573 | # Want to perform backup on DESTINATION before it is written over by the copy/move into place 574 | if($backupdir -ne $null ) { 575 | $archiveFilename = generate-archivestring $destinationTimestamp "$stem-$exten" $exten 576 | echo "|" 577 | echo "| Backup: ""$archiveFilename"" of wiki ..." 578 | echo "| saved in: $backupdir$SEPARATOR$stem.$exten\" 579 | #echo "Making backup from $destination to $backupdir\$archiveFilename" 580 | 581 | Make-Directory "$backupdir$SEPARATOR$stem.$exten$SEPARATOR" 582 | Copy-item $destination -Destination "$backupdir$SEPARATOR$stem.$exten$SEPARATOR$archiveFilename" 583 | } 584 | # Want to perform ZIP backup on DESTINATION before it is written over by the copy/move into place 585 | if($backupzipdir -ne $null ) { 586 | $archiveFilename = generate-archivestring $destinationTimestamp "$stem-$exten" "zip" 587 | echo "|" 588 | echo "| Zip archive: ""$archiveFilename"" of the wiki ..." 589 | echo "| saved in: $backupzipdir$SEPARATOR$stem.$exten$SEPARATOR" 590 | 591 | Make-Directory "$backupzipdir$SEPARATOR$stem.$exten$SEPARATOR" 592 | $ProgressPreference = 'SilentlyContinue' 593 | Compress-Archive -LiteralPath $destination -Force -CompressionLevel Optimal -DestinationPath "$backupzipdir$SEPARATOR$stem.$exten$SEPARATOR$archiveFilename" 594 | $ProgressPreference = 'Continue' 595 | } 596 | 597 | Copy-Item $copyme -Destination "$fileDir$SEPARATOR$stem.$exten" 598 | echo "|" 599 | echo "| RESTORING: download ""$short_name"" to ..." 600 | echo "| Wiki: $destination" 601 | echo " Copying $copyme to $fileDir\$stem.$exten" 602 | echo "" 603 | 604 | parrot 605 | 606 | echo "" 607 | } # IF RESTORE IS NEEDED 608 | else { 609 | echo "|" 610 | echo "| Restore of ""$short_name"" not required," 611 | echo "| ... this download has already been restored" 612 | echo "" 613 | } # ELSE RESTORE NOT NEEDED 614 | 615 | } else { # END IF-NOT-PARROT-MENU 616 | 617 | parrot 618 | } 619 | 620 | } # END FOR-EACH-FILE LOOP 621 | 622 | if($mode -eq "parrot-menu" ) { 623 | echo "" 624 | echo " || RUN PARROTS - $appAndVer" 625 | echo " ||" 626 | echo " || Run mode ""$mode"" completed!" 627 | echo " ||" 628 | echo " || Closes in $closeSeconds seconds ..." 629 | $mode = "menu" 630 | start-sleep -seconds $closeSeconds 631 | # DEBUG 632 | cls 633 | break 634 | 635 | } 636 | 637 | if($mode -eq "once") { 638 | echo "" 639 | echo " || RUN ONCE - $appAndVer" 640 | echo " ||" 641 | echo " || Run mode ""$mode"" completed!" 642 | echo " ||" 643 | echo " || Closes in $closeSeconds seconds ..." 644 | $mode = "menu" 645 | start-sleep -seconds $closeSeconds 646 | # DEBUG 647 | cls #MAS - Sometimes need to turn this off during testing. 648 | #echo "About to break out of Once" 649 | break 650 | } 651 | else { 652 | echo "" 653 | echo " || Polly is pausing for $waitSeconds seconds ..." 654 | start-sleep -seconds $waitSeconds 655 | echo "" 656 | } #IF 657 | } #RESTORE 658 | #echo "Outside of restore loop (pausing)" 659 | #start-sleep -seconds 10 660 | 661 | } #RUNNING (MENU) 662 | 663 | 664 | #echo "Outside of running loop (pausing)" 665 | 666 | #start-sleep -seconds 10 667 | --------------------------------------------------------------------------------