Category: Application Development

  • Code Graph showing the Layout of the Code base

    I’ve been mixing data analysis and Java programming recently.  I wrote a tool to do the analysis (Maven/Python).

    Here is the obfuscated output of the analysis, showing the hotspots.  I opted to show a thumbnail of the image here to protect the confidentiality of the project.  The generated image was also 78 Megabytes.  (a bit much, but you can zoom right in).

    Complicated Graph

    If you use a smaller set of classes and imports, the Maven plugin generates a reasonable diagram.csv file using

    mvn example:generate-diagram:99-SNAPSHOT:generate-diagram -f ./myproj/pom.xml

    You then see the output diagram.csv.

    To generate the layout dependencies of classes in your project.  Use the snippets, and the Jupyter Notebook – https://github.com/prb112/examples/blob/master/code-graph/code-graph.ipynb and view at https://nbviewer.jupyter.org/github/prb112/examples/blob/master/code-graph/code-graph.ipynb

    Simple Graph

    Reference

    Code Graph on Git https://github.com/prb112/examples/tree/master/code-graph

    CSV Sample Data diagram.csv

    POM

    <project xmlns="http://maven.apache.org/POM/4.0.0"
    	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    	<modelVersion>4.0.0</modelVersion>
    
    	<groupId>example</groupId>
    	<artifactId>generate-diagram</artifactId>
    	<version>99-SNAPSHOT</version>
    	<packaging>maven-plugin</packaging>
    
    	<name>generate-diagram</name>
    
    	<properties>
    		<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    		<maven.compiler.source>1.8</maven.compiler.source>
    		<maven.compiler.target>1.8</maven.compiler.target>
    		<version.roaster>2.21.0.Final</version.roaster>
    	</properties>
    
    
    
    	<build>
    		<pluginManagement>
    			<plugins>
    				<plugin>
    					<artifactId>maven-jar-plugin</artifactId>
    					<version>2.6</version>
    					<executions>
    						<execution>
    							<goals>
    								<goal>test-jar</goal>
    							</goals>
    						</execution>
    					</executions>
    					<configuration>
    						<archive>
    							<manifest>
    								<addDefaultImplementationEntries>true</addDefaultImplementationEntries>
    								<addDefaultSpecificationEntries>true</addDefaultSpecificationEntries>
    							</manifest>
    						</archive>
    					</configuration>
    				</plugin>
    				<plugin>
    					<groupId>org.apache.maven.plugins</groupId>
    					<artifactId>maven-plugin-plugin</artifactId>
    					<version>3.6.0</version>
    					<configuration>
    						<skipErrorNoDescriptorsFound>true</skipErrorNoDescriptorsFound>
    					</configuration>
    					<executions>
    						<execution>
    							<id>mojo-descriptor</id>
    							<goals>
    								<goal>descriptor</goal>
    							</goals>
    							<phase>process-classes</phase>
    							<configuration>
    								<skipErrorNoDescriptorsFound>true</skipErrorNoDescriptorsFound>
    							</configuration>
    						</execution>
    					</executions>
    				</plugin>
    			</plugins>
    		</pluginManagement>
    
    		<plugins>
    			<plugin>
    				<!-- Embeds the dependencies in fhir-tools into the jar. -->
    				<groupId>org.apache.maven.plugins</groupId>
    				<artifactId>maven-shade-plugin</artifactId>
    				<version>3.2.1</version>
    				<executions>
    					<execution>
    						<phase>package</phase>
    						<goals>
    							<goal>shade</goal>
    						</goals>
    						<configuration>
    							<artifactSet>
    								<excludes>
    									<exclude>org.testng:testng</exclude>
    									<exclude>org.apache.maven:lib:tests</exclude>
    									<exclude>org.apache.maven</exclude>
    								</excludes>
    							</artifactSet>
    						</configuration>
    					</execution>
    				</executions>
    			</plugin>
    		</plugins>
    	</build>
    
    	<dependencies>
    		<dependency>
    			<groupId>org.jboss.forge.roaster</groupId>
    			<artifactId>roaster-api</artifactId>
    			<version>${version.roaster}</version>
    		</dependency>
    		<dependency>
    			<groupId>org.jboss.forge.roaster</groupId>
    			<artifactId>roaster-jdt</artifactId>
    			<version>${version.roaster}</version>
    			<scope>runtime</scope>
    		</dependency>
    		<dependency>
    			<groupId>org.apache.maven</groupId>
    			<artifactId>maven-plugin-api</artifactId>
    			<version>3.6.1</version>
    		</dependency>
    		<dependency>
    			<groupId>org.apache.maven.plugin-tools</groupId>
    			<artifactId>maven-plugin-annotations</artifactId>
    			<version>3.6.0</version>
    			<optional>true</optional>
    			<scope>provided</scope>
    		</dependency>
    		<dependency>
    			<groupId>org.apache.maven</groupId>
    			<artifactId>maven-core</artifactId>
    			<version>3.6.1</version>
    		</dependency>
    		<dependency>
    			<groupId>org.apache.maven</groupId>
    			<artifactId>maven-artifact</artifactId>
    			<version>3.6.0</version>
    		</dependency>
    		<dependency>
    			<groupId>org.apache.maven</groupId>
    			<artifactId>maven-model</artifactId>
    			<version>3.6.0</version>
    		</dependency>
    		<dependency>
    			<groupId>org.apache.maven</groupId>
    			<artifactId>maven-compat</artifactId>
    			<version>3.6.1</version>
    			<scope>test</scope>
    		</dependency>
    		<dependency>
    			<groupId>org.apache.maven.plugin-testing</groupId>
    			<artifactId>maven-plugin-testing-harness</artifactId>
    			<version>3.3.0</version>
    			<scope>test</scope>
    		</dependency>
    	</dependencies>
    </project>
    
    package demo;
    
    import java.io.File;
    import java.util.Properties;
    
    import org.apache.maven.execution.MavenSession;
    import org.apache.maven.plugin.AbstractMojo;
    import org.apache.maven.plugin.MojoExecutionException;
    import org.apache.maven.plugin.MojoFailureException;
    import org.apache.maven.plugins.annotations.Execute;
    import org.apache.maven.plugins.annotations.LifecyclePhase;
    import org.apache.maven.plugins.annotations.Mojo;
    import org.apache.maven.plugins.annotations.Parameter;
    import org.apache.maven.plugins.annotations.ResolutionScope;
    import org.apache.maven.project.MavenProject;
    
    import com.ibm.watsonhealth.fhir.tools.plugin.diagram.DiagramFactory;
    import com.ibm.watsonhealth.fhir.tools.plugin.diagram.impl.IDiagramGenerator;
    
    /**
     * This class coordinates the calls to the Diagram generation plugin
     * 
     * The phase is initialize. To find a list of phases -
     * https://maven.apache.org/guides/introduction/introduction-to-the-lifecycle.html#Lifecycle_Reference
     * 
     * Run the following to setup the plugin: <code>
     * mvn clean package install -f generate-diagram/pom.xml
     * </code>
     * 
     * Run the following to setup the classes in fhir-model: <code> 
     * mvn example:generate-diagram:99-SNAPSHOT:generate-diagram -f ./myproj/pom.xml
     * </code>
     * 
     * @author PBastide
     * 
     * @requiresDependencyResolution runtime
     *
     */
    @Mojo(name = "generate-diagram", //$NON-NLS-1$
            requiresProject = true, requiresDependencyResolution = ResolutionScope.RUNTIME_PLUS_SYSTEM, requiresDependencyCollection = ResolutionScope.RUNTIME_PLUS_SYSTEM, defaultPhase = LifecyclePhase.GENERATE_SOURCES, requiresOnline = false, threadSafe = false, aggregator = true)
    @Execute(phase = LifecyclePhase.GENERATE_SOURCES)
    public class DiagramPlugin extends AbstractMojo {
    
        @Parameter(defaultValue = "${project}", required = true, readonly = true) //$NON-NLS-1$
        protected MavenProject mavenProject;
    
        @Parameter(defaultValue = "${session}")
        private MavenSession session;
    
        @Parameter(defaultValue = "${project.basedir}", required = true, readonly = true) //$NON-NLS-1$
        private File baseDir;
    
        @Override
        public void execute() throws MojoExecutionException, MojoFailureException {
            if (baseDir == null || !baseDir.exists()) {
                throw new MojoFailureException("The Base Directory is not found.  Throwing failure. ");
            }
    
            // Grab the Properties (the correct way)
            // https://maven.apache.org/plugin-developers/common-bugs.html#Using_System_Properties
            Properties userProps = session.getUserProperties();
            String useTestsDirectoryStr = userProps.getProperty("useTestsDirectory", "false");
    
            // Converts Limit value to boolean value.
            boolean useTestsDirectory = Boolean.parseBoolean(useTestsDirectoryStr);
    
            // Grab the right generator and set it up.
            IDiagramGenerator generator = DiagramFactory.getDiagramGenerator();
    
            // Set the use of tests directory
            generator.use(useTestsDirectory);
    
            // Get the base directory .
            generator.setTargetProjectBaseDirectory(baseDir.getAbsolutePath() + "/target");
    
            // Passes the Log to the implementation code.
            generator.setLog(getLog());
    
            // Add the project
            generator.add(mavenProject);
    
            // Builds the Diagram
            generator.generateDiagram();
        }
    }
    package example.impl;
    
    import java.io.File;
    import java.io.FileOutputStream;
    import java.io.IOException;
    import java.io.OutputStream;
    import java.util.ArrayList;
    import java.util.Comparator;
    import java.util.HashMap;
    import java.util.List;
    import java.util.Map;
    import java.util.StringJoiner;
    import java.util.stream.Collectors;
    
    import org.apache.maven.plugin.logging.Log;
    import org.apache.maven.project.MavenProject;
    import org.jboss.forge.roaster.Roaster;
    import org.jboss.forge.roaster.model.JavaType;
    import org.jboss.forge.roaster.model.source.Import;
    import org.jboss.forge.roaster.model.source.JavaAnnotationSource;
    import org.jboss.forge.roaster.model.source.JavaClassSource;
    import org.jboss.forge.roaster.model.source.JavaEnumSource;
    import org.jboss.forge.roaster.model.source.JavaInterfaceSource;
    import org.jboss.forge.roaster.model.source.JavaSource;
    import org.w3c.dom.DOMImplementation;
    import org.w3c.dom.Document;
    import org.w3c.dom.Element;
    
    public class DiagramImpl implements IDiagramGenerator {
    
        private String absolutePath = null;
        private Log log = null;
        private Boolean useTestFiles = false;
    
        private List<MavenProject> projects = new ArrayList<>();
    
        private List<String> sourceDirectories = new ArrayList<>();
    
        private List<String> sourceFiles = new ArrayList<>();
    
        private List<String> countWithNested = new ArrayList<>();
    
        private Map<String, List<String>> sourceFileImports = new HashMap<>();
    
        @Override
        public void add(MavenProject mavenProject) {
            if (mavenProject == null) {
                throw new IllegalArgumentException("no access to the maven project's plugin object");
            }
    
            log.info("Projects added...");
            projects = mavenProject.getCollectedProjects();
    
        }
    
        @Override
        public void use(Boolean useTestFiles) {
            this.useTestFiles = useTestFiles;
    
        }
    
        @Override
        public void setLog(Log log) {
            this.log = log;
        }
    
        @Override
        public void generateDiagram() {
            if (absolutePath == null) {
                throw new IllegalArgumentException("Bad Path " + absolutePath);
            }
    
            if (log == null) {
                throw new IllegalArgumentException("Unexpected no log passed in");
            }
    
            for (MavenProject project : projects) {
    
                List<String> locations = project.getCompileSourceRoots();
                for (String location : locations) {
                    log.info("Location of Directory -> " + location);
    
                }
                sourceDirectories.addAll(locations);
    
                if (useTestFiles) {
                    log.info("Adding the Test Files");
                    sourceDirectories.addAll(project.getTestCompileSourceRoots());
                }
    
            }
    
            // Find the Files in each directory.
            // Don't Follow links.
            for (String directory : sourceDirectories) {
                findSourceFiles(directory);
            }
    
            processCardinalityMap();
    
            printOutCardinality();
    
            log.info("Total Number of Java Files in the Project are: " + sourceFiles.size());
            log.info("Total Number of Classes/Interfaces/Enums in the Project are: " + countWithNested.size());
    
            generateImageFile();
    
        }
    
        private void generateImageFile() {
            Comparator<String> byName = (name1, name2) -> name1.compareTo(name2);
    
            try(FileOutputStream fos = new FileOutputStream("diagram.csv");) {
                
                for (String key : sourceFileImports.keySet().stream().sorted(byName).collect(Collectors.toList())) {
                    
                    StringJoiner joiner = new StringJoiner(",");
                    for(String val : sourceFileImports.get(key)) {
                        joiner.add(val);
                    }
                    
                    String line = key + ",\"" + joiner.toString() + "\"";
                    fos.write(line.getBytes());
                    fos.write("\n".getBytes());
                }
    
            } catch (Exception e) {
                log.warn("Issue processing", e);
            }
    
        }
    
        public void printOutCardinality() {
    
            Comparator<String> byName = (name1, name2) -> name1.compareTo(name2);
    
            //
            log.info("Cardinality count - imports into other classes");
            for (String key : sourceFileImports.keySet().stream().sorted(byName).collect(Collectors.toList())) {
                log.info(key + " -> " + sourceFileImports.get(key).size());
            }
    
        }
    
        public void processCardinalityMap() {
            // Import > List<Classes>
            // Stored in -> sourceFileImports
    
            for (String source : sourceFiles) {
                File srcFile = new File(source);
                try {
                    JavaType<?> jtFile = Roaster.parse(srcFile);
    
                    String parentJavaClass = jtFile.getQualifiedName();
    
                    if (jtFile instanceof JavaClassSource) {
                        countWithNested.add(parentJavaClass);
                        log.info("[C] -> " + parentJavaClass);
                        JavaClassSource jcs = (JavaClassSource) jtFile;
    
                        helperImports(parentJavaClass, jcs.getImports());
    
                        for (JavaSource<?> child : jcs.getNestedTypes()) {
    
                            String childLoc = child.getQualifiedName();
                            countWithNested.add(childLoc);
                            log.info("  [CC] -> " + childLoc);
                        }
                    }
    
                    else if (jtFile instanceof JavaEnumSource) {
                        log.info("[E] -> " + parentJavaClass);
                        countWithNested.add(parentJavaClass);
                        JavaEnumSource jes = (JavaEnumSource) jtFile;
    
                        helperImports(parentJavaClass, jes.getImports());
    
                        for (Object child : jes.getNestedTypes()) {
    
                            String childLoc = child.getClass().getName();
                            countWithNested.add(childLoc);
                            log.info("  [EC] -> " + childLoc);
                        }
    
                    } else if (jtFile instanceof JavaInterfaceSource) {
                        countWithNested.add(parentJavaClass);
    
                        log.info("[I] -> " + parentJavaClass);
                        JavaInterfaceSource jis = (JavaInterfaceSource) jtFile;
    
                        helperImports(parentJavaClass, jis.getImports());
    
                        for (Object child : jis.getNestedTypes()) {
    
                            String childLoc = child.getClass().getName();
                            countWithNested.add(childLoc);
                            log.info("  [IC] -> " + childLoc);
                        }
                    } else if (jtFile instanceof JavaAnnotationSource) {
                        countWithNested.add(parentJavaClass);
    
                        log.info("[A] -> " + parentJavaClass);
                        JavaAnnotationSource jis = (JavaAnnotationSource) jtFile;
    
                        helperImports(parentJavaClass, jis.getImports());
                    }
    
                    else {
                        log.info("[O] -> " + parentJavaClass);
                    }
    
                } catch (IOException e) {
                    log.info("unable to parse file " + srcFile);
                }
    
            }
            log.info("Parsed the Cardinality Map:");
    
        }
    
        private void helperImports(String parentJavaClass, List<Import> imports) {
            // sourceFileImports
            List<String> importOut = sourceFileImports.get(parentJavaClass);
            if (importOut == null) {
                sourceFileImports.put(parentJavaClass, new ArrayList<String>());
            }
    
            for (Import importX : imports) {
                String importXStr = importX.getQualifiedName();
                importOut = sourceFileImports.get(importXStr);
                if (importOut == null) {
                    importOut = new ArrayList<>();
                    sourceFileImports.put(importXStr, importOut);
                }
    
                importOut.add(parentJavaClass);
    
            }
    
        }
    
        public static void main(String... args) {
            SvgDiagramImpl impl = new SvgDiagramImpl();
            String proc = "Test.java";
            Log log = new Log() {
    
                @Override
                public boolean isDebugEnabled() {
                    return false;
                }
    
                @Override
                public void debug(CharSequence content) {
    
                }
    
                @Override
                public void debug(CharSequence content, Throwable error) {
    
                }
    
                @Override
                public void debug(Throwable error) {
    
                }
    
                @Override
                public boolean isInfoEnabled() {
                    return false;
                }
    
                @Override
                public void info(CharSequence content) {
    
                }
    
                @Override
                public void info(CharSequence content, Throwable error) {
    
                }
    
                @Override
                public void info(Throwable error) {
    
                }
    
                @Override
                public boolean isWarnEnabled() {
                    return false;
                }
    
                @Override
                public void warn(CharSequence content) {
    
                }
    
                @Override
                public void warn(CharSequence content, Throwable error) {
    
                }
    
                @Override
                public void warn(Throwable error) {
    
                }
    
                @Override
                public boolean isErrorEnabled() {
                    return false;
                }
    
                @Override
                public void error(CharSequence content) {
    
                }
    
                @Override
                public void error(CharSequence content, Throwable error) {
    
                }
    
                @Override
                public void error(Throwable error) {
    
                }
            };
            impl.setLog(log);
            impl.addSourceFile(proc);
            impl.processCardinalityMap();
        }
    
        private void addSourceFile(String proc) {
            sourceFiles.add(proc);
    
        }
    
        public void findSourceFiles(String directory) {
            File dir = new File(directory);
            if (dir.exists()) {
    
                File[] listFiles = dir.listFiles((file, name) -> {
                    return name.endsWith(".java");
                });
    
                // Add to source directory
                if (listFiles != null) {
                    for (File file : listFiles) {
                        sourceFiles.add(file.getAbsolutePath());
                        log.info(" File Added to Processing: " + file.getAbsolutePath());
                    }
                }
    
                File[] listFilesFolders = dir.listFiles((file, name) -> {
                    return file.isDirectory();
                });
    
                if (listFilesFolders != null) {
                    for (File tmpDir : listFilesFolders) {
                        findSourceFiles(tmpDir.getAbsolutePath());
                    }
                }
    
            } else {
                log.warn("Directory does not exist " + directory);
            }
        }
    
        @Override
        public void setTargetProjectBaseDirectory(String absolutePath) {
            this.absolutePath = absolutePath;
    
        }
    
    }
    
  • Raspberry Pi: Setting up backup

    I have a Raspberry Pi providing household automation and productivity services – WebDav, Backups and Calendar. I always worry about a jolt of power, a failed byte and something that is unrecoverable. Time for a Backup solution.

    I plugged in a USB stick – 64GB, and immediately checked the file system is there and visible as SDA (unmounted).

    pi@raspberrypi:~# sudo su - 
    
    root@raspberrypi:~# lsblk 
     NAME        MAJ:MIN RM  SIZE RO TYPE MOUNTPOINT
     sda           8:0    1 58.2G  1 disk 
     └─sda1        8:1    1 58.2G  1 part 
     mmcblk0     179:0    0 14.9G  0 disk 
     ├─mmcblk0p1 179:1    0 43.9M  0 part /boot
     └─mmcblk0p2 179:2    0 14.8G  0 part /

    I check to see which one is assigned to the SD card slot (mmc), I really don’t want to reformat Raspbian.  I see the USB stick is on /dev/sda. All of my subsequent commands use /dev/sda as part of the command.

    root@raspberrypi:~# parted -ls
    Warning: The driver descriptor says the physical block size is 2048 bytes, but Linux says it is 512 bytes.
     Model: JetFlash Transcend 64GB (scsi)
     Disk /dev/sda: 62.5GB
     Sector size (logical/physical): 512B/512B
     Partition Table: unknown
     Disk Flags: 
    
     Model: SD SC16G (sd/mmc)
     Disk /dev/mmcblk0: 15.9GB
     Sector size (logical/physical): 512B/512B
     Partition Table: msdos
     Disk Flags: 
     Number  Start   End     Size    Type     File system  Flags
      1      4194kB  50.2MB  46.0MB  primary  fat32        lba
      2      50.3MB  15.9GB  15.9GB  primary  ext4

    If you don’t see the relevant information on your HD or run into issues formatting the hard-drive, install hdparm.  and check with hdparm -r0 /dev/sda  .

    TIP: I did run into an issue with an ISO written to a USB drive which locks the partition table and makes it unwriteable.

    root@raspberrypi:~# apt-get install hdparm
     Reading package lists… Done
     Building dependency tree       
     Reading state information… Done
     The following package was automatically installed and is no longer required:
       realpath
     Use 'apt autoremove' to remove it.
     The following additional packages will be installed:
       powermgmt-base
     Suggested packages:
       apmd
     The following NEW packages will be installed:
       hdparm powermgmt-base
     0 upgraded, 2 newly installed, 0 to remove and 148 not upgraded.
     Need to get 114 kB of archives.
     After this operation, 278 kB of additional disk space will be used.
     Do you want to continue? [Y/n] y
     Get:1 http://raspbian.mirror.constant.com/raspbian stretch/main armhf hdparm armhf 9.51+ds-1+deb9u1 [105 kB]
     Get:2 http://raspbian-us.ngc292.space/raspbian stretch/main armhf powermgmt-base all 1.31+nmu1 [9,240 B]
     Fetched 114 kB in 0s (120 kB/s)           
     Selecting previously unselected package hdparm.
     (Reading database … 135688 files and directories currently installed.)
     Preparing to unpack …/hdparm_9.51+ds-1+deb9u1_armhf.deb …
     Unpacking hdparm (9.51+ds-1+deb9u1) …
     Selecting previously unselected package powermgmt-base.
     Preparing to unpack …/powermgmt-base_1.31+nmu1_all.deb …
     Unpacking powermgmt-base (1.31+nmu1) …
     Setting up powermgmt-base (1.31+nmu1) …
     Setting up hdparm (9.51+ds-1+deb9u1) …
     Processing triggers for man-db (2.7.6.1-2) …
    Preparing to unpack …/hdparm_9.51+ds-1+deb9u1_armhf.deb …
    
    Unpacking hdparm (9.51+ds-1+deb9u1) …
    
    Selecting previously unselected package powermgmt-base.
    
    Preparing to unpack …/powermgmt-base_1.31+nmu1_all.deb …
    
    Unpacking powermgmt-base (1.31+nmu1) …
    
    Setting up powermgmt-base (1.31+nmu1) …
    
    Setting up hdparm (9.51+ds-1+deb9u1) …
    
    Processing triggers for man-db (2.7.6.1-2) …
    root@raspberrypi:~# hdparm -r0 /dev/sda
     /dev/sda:
      setting readonly to 0 (off)
      readonly      =  0 (off)

    Now that I know the drive is writeable, I need to create the partition. I used

    cfdisk

    Navigate through the menu and select the maximum size
                                                                                            Disk: /dev/sda
    Size: 58.2 GiB, 62495129600 bytes, 122060800 sectors
    Label: dos, identifier: 0x00000000

    Device Boot Start End Sectors Size Id Type
    >> /dev/sda1 2048 122060799 122058752 58.2G 83 Linux

    Once you see “Syncing disks.”, you can format the disk. I formatted the partition sda1 with ext4 (I may want to encrypt in the future). Unmount and then format.

    root@raspberrypi:~# umount /dev/sda1
    root@raspberrypi:~# mkfs.ext4 /dev/sda1
    mke2fs 1.43.4 (31-Jan-2017)
    Found a dos partition table in /dev/sda1
    Proceed anyway? (y,N) y
    Creating filesystem with 2828032 4k blocks and 707136 inodes
    Filesystem UUID: 363f1b4a-b0f5-4c7b-bf91-66f3823032d6
    Superblock backups stored on blocks:
    32768, 98304, 163840, 229376, 294912, 819200, 884736, 1605632, 2654208

    Allocating group tables: done
    Writing inode tables: done
    Creating journal (16384 blocks): done
    Writing superblocks and filesystem accounting information: done

    root@raspberrypi:~#

    Make the backup directory, edit fstab and mount the directory.  Insert into fstab with your uuid “UUID=363f1b4a-b0f5-4c7b-bf91-66f3823032d6 /backups auto nosuid,nodev,nofail 0 0
    ”   The second to last avoids backup and the last one enables fsck on reboot.

    root@raspberrypi:~# blkid 
    /dev/mmcblk0p1: LABEL="boot" UUID="DDAB-3A15" TYPE="vfat" PARTUUID="b53687e8-01"
    /dev/mmcblk0p2: LABEL="rootfs" UUID="5fa1ec37-3719-4b25-be14-1f7d29135a13" TYPE="ext4" PARTUUID="b53687e8-02"
    /dev/mmcblk0: PTUUID="b53687e8" PTTYPE="dos"
    /dev/sdb: UUID="363f1b4a-b0f5-4c7b-bf91-66f3823032d6" TYPE="ext4"
    root@raspberrypi:~# mkdir /backups 
    root@raspberrypi:~# vim /etc/fstab
    root@raspberrypi:~# mount -a
    root@raspberrypi:~# mount
    UUID=363f1b4a-b0f5-4c7b-bf91-66f3823032d6 /backups auto nosuid,nodev,nofail 0 0


    You should see backups listed. (Note: I bricked my Raspberry Pi with a bad FSTAB entry, and mounted it on my Mac using Paragon and removed the bad fstab entry. )

    Update Crontab with daily backups.

    crontab -e

    Setup an editor for crontab.

    root@raspberrypi:~# crontab -e
    no crontab for root - using an empty one

    Select an editor. To change later, run 'select-editor'.
    1. /bin/ed
    2. /bin/nano <---- easiest
    3. /usr/bin/vim.basic
    4. /usr/bin/vim.tiny

    Choose 1-4 [2]: 3
    crontab: installing new crontab

    I added this line and copied it to /etc/cron.daily/

    0 1 * * * /usr/bin/rsync -r /data/ /backups/`date +%w-%A`

    crontab -l > pi-backup
    mv /root/pi-backup /etc/cron.daily
    run-parts /etc/cron.daily

    Note, I had to add #!/bin/bash after I copied and remove the timing of the job.

    Also, check to see if rsync is installed with which rsync and apt-get install rsync.

    This enables backups on a daily basis rotating every 7 days.

    Check back on the following day to see your backups

    root@raspberrypi:~# /usr/bin/rsync -r /data/ /backups/`date +%w-%A`
    root@raspberrypi:~# find /backups
    /backups
    /backups/lost+found
    /backups/0-Sunday
    /backups/0-Sunday/startup.sh

    Good luck, I hope this helps you with your Raspberry Pi.

    References

  • Jupyter Notebook: Email Analysis to a Lotus Notes View

    I wanted to do an analysis of my emails since I joined IBM, and see the flow of messages in-and-out of my inbox.

    With my preferences for Jupyter Notebooks, I built a small notebook for analysis.

    Steps
    Open IBM Lotus Notes Rich Client

    Open the Notes Database with the View you want to analyze.

    Select the View you are interested in ‘All Documents’. For instance the All Documents view, like my inbox *obfuscated* with a purpose.

    Click File > Export

    Enter a file name – email.csv

    Select Format “Comma Separate Value”

    Click Export

    Upload the Notebook to your Jupyter server

    The notebook is describes the flow through my process. If you encounter ValueError: (‘Unknown string format:’, ’12/10/2018 08:34 AM’), you can refer to https://stackoverflow.com/a/8562577/1873438

    iconv -c -f utf-8 -t ascii email.csv > email.csv.clean

    You can break the data into month-year-day analysis with the following, and peek the results with df_emailA.head()

    When you run the final cell, the code generates a Year-Month-Day count as a bar graph.

        # Title: Volume in Months when emails are sent.
        # Plots volume based on known year-mm-dd
        # to be included in the list, one must have data in those years.
        # Kind is a bar graph, so that the (Y - YYYY,MM can be read)
        y_m_df = df_emailA.groupby(['year','month','day']).year.count()
        y_m_df.plot(kind="bar")
    
        plt.title('Numbers submitted By YYYY-MM-DD')
        plt.xlabel('Email Flow')
        plt.ylabel('Year-Month-Day')
        plt.autoscale(enable=True, axis='both', tight=False)
        plt.rcParams['figure.figsize'] = [20, 200]

    You’ll see the trend of emails I receive over the years.

    Trends of Email
  • CURL and LDAPS – How to Search and Debug

    I hit an issue where I needed to Search LDAP from a machine I didn’t have access to install new RPMs on. I found this cool article on CURL and LDAP Search. I had to make some minor modifications to get it to work with a secure connection (–insecure ldaps:// and 636). I also added -v to diagnosis some connection problems.

    curl "ldaps://127.0.0.1:636/DC=IBM.COM?cn,objectClass?sub?(objectClass=)" -u "cn=user1,ou=test_org3,o=dr,DC=IBM.COM" --insecure -v
    Enter host password for user 'cn=user1,ou=test_org3,o=dr,DC=IBM.COM':
    * Trying 127.0.0.1...
    * Connected to 127.0.0.1 (127.0.0.1) port 636 (#0)
    * LDAP local: LDAP Vendor = OpenLDAP ; LDAP Version = 20428
    * LDAP local: ldaps://127.0.0.1:636/DC=IBM.COM?cn,objectClass?sub?(objectClass=
    )
    * LDAP local: trying to establish encrypted connection
    DN: dc=ibm.com
    objectClass: domain
    objectClass: top

    DN: o=dr,dc=ibm.com
    objectClass: organization
    objectClass: top

    DN: ou=test_org3,o=dr,dc=ibm.com
    objectClass: organizationalunit
    objectClass: top

    You can then find the userids you need quickly. I left them off the output intentionally.

    If you see connected, but no results, I suggest changing to the top level of the ldap, and using this string – ldaps://127.0.0.1:636/DC=IBM.COM?cn,objectClass?sub?(objectClass=*)

  • OAuth 2.0 Flow – A Metaphor

    The following video helps explain the OAuth 2.0 Flow and authorization. This video was originally shared as part of a prior project on Social Application Development.

  • Swagger Enumerations in YAML

    Recently, I have been spending time looking at the Swagger 2.0 Specification.  There XML representation and Enumerations are tricky.  Here is an example of a Swagger Enumeration in YAML.

    You can test the YAML at http://petstore.swagger.io/#!/default/get_demo