code:
package no.priv.bang.jdbc.datasourceproxy;
/*
 * Copyright 2019-2024 Steinar Bang
 *
 * Licensed under the Apache License, Version 2.0 (the "License")
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and limitations
 * under the License.
 */

import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.Mockito.*;

import java.io.PrintWriter;
import java.sql.Connection;
import java.util.logging.Logger;

import javax.sql.DataSource;

import org.junit.jupiter.api.Test;

class DataSourceProxyTest {

    @Test
    void testWrapDataSource() throws Exception {
        var wrappedDataSource = mock(DataSource.class);
        var logwriter = mock(PrintWriter.class);
        when(wrappedDataSource.getLogWriter()).thenReturn(logwriter);
        when(wrappedDataSource.getLoginTimeout()).thenReturn(42);
        var parentlogger = mock(Logger.class);
        when(wrappedDataSource.getParentLogger()).thenReturn(parentlogger);
        when(wrappedDataSource.isWrapperFor(any())).thenReturn(true);
        var other = mock(DataSource.class);
        when(wrappedDataSource.unwrap(any())).thenReturn(other);
        var connection = mock(Connection.class);
        when(wrappedDataSource.getConnection()).thenReturn(connection);
        when(wrappedDataSource.getConnection(anyString(), anyString())).thenReturn(connection);

        var proxy = new DataSourceProxy();
        proxy.setWrappedDataSource(wrappedDataSource);

        proxy.setLogWriter(logwriter);
        verify(wrappedDataSource, times(1)).setLogWriter(any());
        assertEquals(logwriter, proxy.getLogWriter());
        proxy.setLoginTimeout(42);
        verify(wrappedDataSource, times(1)).setLoginTimeout(anyInt());
        assertEquals(42, proxy.getLoginTimeout());
        assertEquals(parentlogger, proxy.getParentLogger());
        assertTrue(proxy.isWrapperFor(getClass()));
        assertEquals(other, proxy.unwrap(DataSource.class));
        assertEquals(connection, proxy.getConnection());
        assertEquals(connection, proxy.getConnection("karaf", "karaf"));
    }

}


code:
<?xml version="1.0" encoding="UTF-8"?>

<!-- Copyright 2023-2024 Steinar Bang                                                -->
<!--                                                                                -->
<!-- Licensed under the Apache License, Version 2.0 (the "License");                 -->
<!-- you may not use this file except in compliance with the License.                -->
<!-- You may obtain a copy of the License at                                         -->
<!--   http://www.apache.org/licenses/LICENSE-2.0                                    -->
<!-- Unless required by applicable law or agreed to in writing,                      -->
<!-- software distributed under the License is distributed on an "AS IS" BASIS,      -->
<!-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.        -->
<!-- See the License for the specific language governing permissions and limitations -->
<!-- under the License.                                                              -->

<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>
    <parent>
        <groupId>no.priv.bang.jdbc</groupId>
        <artifactId>jdbc</artifactId>
        <version>1.2.1-SNAPSHOT</version>
    </parent>
    <artifactId>jdbc.sqldumper</artifactId>

    <name>Dump SQL resultsets as liquibase compatible SQL</name>

    <properties>
        <karaf-feature-name>jdbc-sqldumper</karaf-feature-name>
        <oldalbum.version>2.2.4</oldalbum.version>
        <osgi-service-adapters.version>1.2.0</osgi-service-adapters.version>
        <project-target-dir>${project.build.directory}</project-target-dir>
    </properties>

    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>no.priv.bang.osgi.service.adapters</groupId>
                <artifactId>adapters-bom</artifactId>
                <version>${osgi-service-adapters.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>
    <dependencies>
        <dependency>
            <groupId>org.junit.jupiter</groupId>
            <artifactId>junit-jupiter-api</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.junit.jupiter</groupId>
            <artifactId>junit-jupiter-engine</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.junit.vintage</groupId>
            <artifactId>junit-vintage-engine</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.assertj</groupId>
            <artifactId>assertj-core</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.mockito</groupId>
            <artifactId>mockito-core</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>no.priv.bang.oldalbum</groupId>
            <artifactId>oldalbum.db.liquibase</artifactId>
            <version>${oldalbum.version}</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>no.priv.bang.oldalbum</groupId>
            <artifactId>oldalbum.db.liquibase.test</artifactId>
            <version>${oldalbum.version}</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>no.priv.bang.karaf</groupId>
            <artifactId>karaf.liquibase.runner</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.osgi</groupId>
            <artifactId>org.osgi.service.jdbc</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.ops4j.pax.jdbc</groupId>
            <artifactId>pax-jdbc</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.ops4j.pax.jdbc</groupId>
            <artifactId>pax-jdbc-config</artifactId>
            <scope>provided</scope>
        </dependency>
        <dependency>
            <groupId>org.ops4j.pax.jdbc</groupId>
            <artifactId>pax-jdbc-derby</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.liquibase</groupId>
            <artifactId>liquibase-core</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.osgi</groupId>
            <artifactId>org.osgi.service.log</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>no.priv.bang.osgi.service.adapters</groupId>
            <artifactId>adapters.logservice</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.felix</groupId>
                <artifactId>maven-bundle-plugin</artifactId>
            </plugin>
            <plugin>
                <groupId>org.apache.karaf.tooling</groupId>
                <artifactId>karaf-maven-plugin</artifactId>
            </plugin>
            <plugin>
                <groupId>org.codehaus.mojo</groupId>
                <artifactId>properties-maven-plugin</artifactId>
                <executions>
                    <execution>
                        <phase>generate-resources</phase>
                        <goals>
                            <goal>write-project-properties</goal>
                        </goals>
                        <configuration>
                            <outputFile>${project.build.testOutputDirectory}/properties-from-pom.properties</outputFile>
                        </configuration>
                    </execution>
                </executions>
            </plugin>
        </plugins>
        <pluginManagement>
            <plugins>
                <!--This plugin's configuration is used to store Eclipse m2e settings only. It has no influence on the Maven build itself.-->
                <plugin>
                    <groupId>org.eclipse.m2e</groupId>
                    <artifactId>lifecycle-mapping</artifactId>
                    <version>1.0.0</version>
                    <configuration>
                        <lifecycleMappingMetadata>
                            <pluginExecutions>
                                <pluginExecution>
                                    <pluginExecutionFilter>
                                        <groupId>org.apache.karaf.tooling</groupId>
                                        <artifactId>karaf-maven-plugin</artifactId>
                                        <versionRange>[4.4.3,)</versionRange>
                                        <goals>
                                            <goal>features-generate-descriptor</goal>
                                        </goals>
                                    </pluginExecutionFilter>
                                    <action>
                                        <ignore />
                                    </action>
                                </pluginExecution>
                            </pluginExecutions>
                        </lifecycleMappingMetadata>
                    </configuration>
                </plugin>
            </plugins>
        </pluginManagement>
    </build>
</project>

repo_name: steinarb/jdbc

license: mit


code:
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<features>
    <feature name="${karaf-feature-name}">
    </feature>
</features>


code:
package no.priv.bang.jdbc.sqldumper;
/*
 * Copyright 2023-2024 Steinar Bang
 *
 * Licensed under the Apache License, Version 2.0 (the "License")
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and limitations
 * under the License.
 */

import java.io.IOException;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.nio.charset.StandardCharsets;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Types;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 *
A Java class for dumping a JDBC
 * {@link ResultSet} to an {@link OutputStream} as an
 * SQL
 * formatted liquibase changeset.
 *
 *
Sample usage:
 *

 *     private void dumpAlbumEntriesAsLiquibaseSql(DataSource oldalbumDatasource, OutputStream outputstream) throws SQLException, IOException {
 *         var sqldumper = new ResultSetSqlDumper();
 *         var changesetId = "sb:album_paths";
 *         var sql = "select * from albumentries";
 *         try(var connection = oldalbumDatasource.getConnection()) {
 *             try(var statement = connection.createStatement()) {
 *                 try(var resultset = statement.executeQuery(sql)) {
 *                     sqldumper.dumpResultSetAsSql(changesetId, resultset, outputstream);
 *                 }
 *             }
 *         }
 *     }
 *

 *
 *
Limitations:
 *

 *
The select that creates the {@link ResultSet} must be from a single table. I.e. the select cannot be a join between table. The SQL file will be generated but won't be importable

 *
The columns of the {@link ResultSet} can't be of complex types like structs or arrays, only numbers, strings, booleans and dates will work

 *
If an autoincremented key is part of the SQL dump, the counter won't be set right after the import, and there is no portable way of resetting the counter (different RDBMSes does it different ways)

 *

 */
public class ResultSetSqlDumper {

    /**
     * Traverse the JDBC {@link ResultSet} {@code
     * resultSetToGenerateSqlFor} and output an SQL
     * formatted liquibase changeset to {@code outputStream} with
     * a liquibase changeset id given by {@code changesetId}, on the
     * form "author:id".
     *
     * @param changesetId the id to use on the generated changeset
     * @param resultSetToGenerateSqlFor the JDBC {@link ResultSet} to generate output for
     * @param outputstream where the liquibase SQL formatted changeset will be written
     * @throws IOException when there is an error writing the changeset as SQL
     * @throws SQLException when there is an error accessing the {@link ResultSet}
     */
    public void dumpResultSetAsSql(String changesetId, ResultSet resultSetToGenerateSqlFor, OutputStream outputstream) throws IOException, SQLException {
        try(var writer = new OutputStreamWriter(outputstream, StandardCharsets.UTF_8)) {
            writer.write("--liquibase formatted sql\n");
            writer.write("--changeset sb:saved_albumentries\n");
            var columnames = findColumnNames(resultSetToGenerateSqlFor);
            var columntypes = findColumntypes(resultSetToGenerateSqlFor);
            var tablename = findTableName(resultSetToGenerateSqlFor);
            while(resultSetToGenerateSqlFor.next()) {
                addInsertStatement(writer, tablename, columnames);
                addValues(writer, resultSetToGenerateSqlFor, columnames, columntypes);
            }
        }
    }

    List<String> findColumnNames(ResultSet resultset) throws SQLException {
        var metadata = resultset.getMetaData();
        var columnames = new ArrayList<String>();
        for (var i = 1; i<=metadata.getColumnCount(); ++i) {
            columnames.add(metadata.getColumnName(i));
        }

        return columnames;
    }

    public Map<String, Integer> findColumntypes(ResultSet resultset) throws SQLException {
        var columtypes = new HashMap<String, Integer>();
        var metadata = resultset.getMetaData();
        for (var i = 1; i<=metadata.getColumnCount(); ++i) {
            columtypes.put(metadata.getColumnName(i), metadata.getColumnType(i));
        }

        return columtypes;
    }

    public String findTableName(ResultSet resultset) throws SQLException {
        var metadata = resultset.getMetaData();
        return metadata.getTableName(1);
    }

    private void addInsertStatement(OutputStreamWriter writer, String tablename, List<String> columnames) throws IOException {
        writer.write("insert into ");
        writer.write(tablename);
        writer.write(" (");
        writer.write(String.join(", ", columnames));
        writer.write(") values (");
    }

    private void addValues(OutputStreamWriter writer, ResultSet resultset, List<String> columnames, Map<String, Integer> columntypes) throws SQLException, IOException {
        var values = new ArrayList<String>();
        for(var columname : columnames) {
            var stringValue = resultset.getString(columname);
            if (columntypes.get(columname) == Types.VARCHAR) {
                values.add(quoteStringButNotNull(stringValue));
            } else if (columntypes.get(columname) == Types.TIMESTAMP && !resultset.wasNull()) {
                values.add(String.format("'%s'", stringValue));
            } else {
                values.add(stringValue);
            }
        }

        writer.write(String.join(", ", values));
        writer.write(");\n");
    }

    private String quoteStringButNotNull(String string) {
        if (string != null) {
            var builder = new StringBuilder(string.length() + 10);
            builder.append("'");
            builder.append(string.replace("'", "''"));
            builder.append("'");
            return builder.toString();
        }
        return string;
    }

}


code:
package no.priv.bang.jdbc.sqldumper;
/*
 * Copyright 2023-2024 Steinar Bang
 *
 * Licensed under the Apache License, Version 2.0 (the "License")
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and limitations
 * under the License.
 */

import static org.junit.jupiter.api.Assertions.*;
import static org.assertj.core.api.Assertions.*;

import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.sql.ResultSet;
import java.sql.Timestamp;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Properties;

import javax.sql.DataSource;

import org.junit.jupiter.api.Test;
import org.ops4j.pax.jdbc.derby.impl.DerbyDataSourceFactory;
import org.osgi.service.jdbc.DataSourceFactory;

import liquibase.Scope;
import liquibase.Scope.ScopedRunner;
import liquibase.changelog.ChangeLogParameters;
import liquibase.command.CommandScope;
import liquibase.command.core.UpdateCommandStep;
import liquibase.command.core.helpers.DatabaseChangelogCommandStep;
import liquibase.command.core.helpers.DbUrlConnectionArgumentsCommandStep;
import liquibase.database.DatabaseFactory;
import liquibase.database.jvm.JdbcConnection;
import liquibase.sdk.resource.MockResourceAccessor;
import no.priv.bang.jdbc.sqldumper.beans.AlbumEntry;
import no.priv.bang.oldalbum.db.liquibase.OldAlbumLiquibase;
import no.priv.bang.oldalbum.db.liquibase.test.OldAlbumDerbyTestDatabase;

class ResultSetSqlDumperTest {
    DataSourceFactory derbyDataSourceFactory = new DerbyDataSourceFactory();

    @Test
    void testDumpResultSetOnOldalbum() throws Exception {
        var changesetId = "sb:album_paths";
        var sqldumper = new ResultSetSqlDumper();
        var oldalbumDatasource = createOldalbumDbWithData("oldalbum1");
        var tempfile = Files.createTempFile(findTempdirAsTargetSubdir(), "oldalbum", "sql");
        Files.delete(tempfile);
        try(var outputstream = Files.newOutputStream(tempfile)) {
            var sql = "select * from albumentries";
            try(var connection = oldalbumDatasource.getConnection()) {
                try(var statement = connection.createStatement()) {
                    try(var resultset = statement.executeQuery(sql)) {
                        sqldumper.dumpResultSetAsSql(changesetId, resultset, outputstream);
                    }
                }
            }
        }

        var dumpedsql = Files.readString(tempfile);
        assertThat(dumpedsql)
            .startsWith("--liquibase formatted sql")
            .contains("--changeset sb:saved_albumentries")
            .contains("insert into ALBUMENTRIES (ALBUMENTRY_ID, PARENT, LOCALPATH, ALBUM, TITLE, DESCRIPTION, IMAGEURL, THUMBNAILURL, SORT, LASTMODIFIED, CONTENTTYPE, CONTENTLENGTH, REQUIRE_LOGIN, GROUP_BY_YEAR) values")
            .contains("1, 0, '/', true, 'Picture archive', '', '', '', 0, null, null, null")
            .contains("11, 4, '/moto/vfr96/acirc3', false, '', 'My VFR 750F at the arctic circle.', 'https://www.bang.priv.no/sb/pics/moto/vfr96/acirc3.jpg', 'https://www.bang.priv.no/sb/pics/moto/vfr96/icons/acirc3.gif', 3, '1996-08-06 18:28:58.0', 'image/jpeg', 57732");

        var restoredOldalbumDatasource = createOldalbumDbWithouthData("oldalbum2");
        assertEmptyAlbumentries(restoredOldalbumDatasource);
        setDatabaseContentAsLiquibaseChangelog(restoredOldalbumDatasource, dumpedsql);
        assertAlbumentriesNotEmpty(restoredOldalbumDatasource);
        var originalAlbumEntries = findAllAlbumentries(oldalbumDatasource);
        var restoredAlbumEntries = findAllAlbumentries(restoredOldalbumDatasource);
        assertThat(restoredAlbumEntries).containsExactlyElementsOf(originalAlbumEntries);
    }

    @Test
    void testFindSchema() throws Exception {
        var sqldumper = new ResultSetSqlDumper();
        var oldalbumDatasource = createOldalbumDbWithData("oldalbum1");
        var sql = "select * from albumentries";
        try(var connection = oldalbumDatasource.getConnection()) {
            try(var statement = connection.createStatement()) {
                try(var resultset = statement.executeQuery(sql)) {
                    List<String> columnames = sqldumper.findColumnNames(resultset);
                    assertThat(columnames).hasSize(14);
                    Map<String, Integer> columntypes = sqldumper.findColumntypes(resultset);
                    assertThat(columntypes).hasSize(columnames.size());
                    String tablename = sqldumper.findTableName(resultset);
                    assertEquals("ALBUMENTRIES", tablename);
                }
            }
        }
    }

    private void assertEmptyAlbumentries(DataSource oldalbumDatasource) throws Exception {
        var sql = "select * from albumentries";
        try(var connection = oldalbumDatasource.getConnection()) {
            try(var statement = connection.createStatement()) {
                try(var resultset = statement.executeQuery(sql)) {
                    assertFalse(resultset.next(), "Expected albumentries table to be empty");
                }
            }
        }
    }

    private void assertAlbumentriesNotEmpty(DataSource oldalbumDatasource) throws Exception {
        var sql = "select * from albumentries";
        try(var connection = oldalbumDatasource.getConnection()) {
            try(var statement = connection.createStatement()) {
                try(var resultset = statement.executeQuery(sql)) {
                    assertTrue(resultset.next(), "Expected albumentries table not to be empty");
                }
            }
        }
    }

    private void setDatabaseContentAsLiquibaseChangelog(DataSource datasource, String contentLiquibaseChangelog) throws Exception {
        var contentByFileName = new HashMap<String, String>();
        contentByFileName.put("dumproutes.sql", contentLiquibaseChangelog);
        try(var connection = datasource.getConnection()) {
            try(var database = DatabaseFactory.getInstance().findCorrectDatabaseImplementation(new JdbcConnection(connection))) {
                Map<String, Object> scopeObjects = Map.of(
                    Scope.Attr.database.name(), database,
                    Scope.Attr.resourceAccessor.name(), new MockResourceAccessor(contentByFileName));

                Scope.child(scopeObjects, (ScopedRunner<?>) () -> new CommandScope("update")
                    .addArgumentValue(DbUrlConnectionArgumentsCommandStep.DATABASE_ARG, database)
                    .addArgumentValue(UpdateCommandStep.CHANGELOG_FILE_ARG, "dumproutes.sql")
                    .addArgumentValue(DatabaseChangelogCommandStep.CHANGELOG_PARAMETERS, new ChangeLogParameters(database))
                    .execute());
            }
        }
    }

    private List<AlbumEntry> findAllAlbumentries(DataSource datasource) throws Exception {
        var allroutes = new ArrayList<AlbumEntry>();

        var sql = "select * from albumentries";
        try (var connection = datasource.getConnection()) {
            try (var statement = connection.createStatement()) {
                try (var results = statement.executeQuery(sql)) {
                    while (results.next()) {
                        var route = unpackAlbumEntry(results);
                        allroutes.add(route);
                    }
                }
            }
        }

        return allroutes;
    }

    private AlbumEntry unpackAlbumEntry(ResultSet results) throws Exception {
        return AlbumEntry.with()
            .id(results.getInt("albumentry_id"))
            .parent(results.getInt("parent"))
            .path(results.getString("localpath"))
            .album(results.getBoolean("album"))
            .title(results.getString("title"))
            .description(results.getString("description"))
            .imageUrl(results.getString("imageurl"))
            .thumbnailUrl(results.getString("thumbnailurl"))
            .sort(results.getInt("sort"))
            .lastModified(timestampToDate(results.getTimestamp("lastmodified")))
            .contentType(results.getString("contenttype"))
            .contentLength(results.getInt("contentlength"))
            .requireLogin(results.getBoolean("require_login"))
            .childcount(findChildCount(results))
            .build();
    }

    private int findChildCount(ResultSet results) throws Exception {
        var columncount = results.getMetaData().getColumnCount();
        return columncount > 13 ? results.getInt(14) : 0;
    }

    private Date timestampToDate(Timestamp lastmodifiedTimestamp) {
        return lastmodifiedTimestamp != null ? Date.from(lastmodifiedTimestamp.toInstant()) : null;
    }

    private DataSource createOldalbumDbWithData(String dbname) throws Exception {
        var oldalbumDatasource = createDatasource(dbname);
        var oldalbumSchemaAndData = new OldAlbumDerbyTestDatabase();
        oldalbumSchemaAndData.activate();
        oldalbumSchemaAndData.prepare(oldalbumDatasource);
        return oldalbumDatasource;
    }

    private DataSource createOldalbumDbWithouthData(String dbname) throws Exception {
        var oldalbumDatasource = createDatasource(dbname);
        var oldalbumSchema = new OldAlbumLiquibase();
        try (var connection = oldalbumDatasource.getConnection()) {
            oldalbumSchema.createInitialSchema(connection);
        }

        try (var connection = oldalbumDatasource.getConnection()) {
            oldalbumSchema.updateSchema(connection);
        }

        return oldalbumDatasource;
    }

    private DataSource createDatasource(String dbname) throws Exception {
        var properties = new Properties();
        properties.setProperty(DataSourceFactory.JDBC_URL, "jdbc:derby:memory:" + dbname + ";create=true");
        return derbyDataSourceFactory.createDataSource(properties);
    }

    private Path findTempdirAsTargetSubdir() throws Exception {
        try(var inputstream = getClass().getClassLoader().getResourceAsStream("properties-from-pom.properties")) {
            var properties = new Properties();
            properties.load(inputstream);
            var tempdir = Paths.get((String)properties.get("project-target-dir"), "temp");
            if (!Files.exists(tempdir)) {
                Files.createDirectory(tempdir);
            }

            return tempdir;
        }
    }
}


code:
package no.priv.bang.jdbc.sqldumper.beans;

/*
 * Copyright 2023-2024 Steinar Bang
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and limitations
 * under the License.
 */

import java.util.Date;

public record AlbumEntry(
    int id,
    int parent,
    String path,
    boolean album,
    String title,
    String description,
    String imageUrl,
    String thumbnailUrl,
    int sort,
    Date lastModified,
    String contentType,
    int contentLength,
    int childcount,
    boolean requireLogin) {

    public static Builder with() {
        return new Builder();
    }

    public static Builder with(AlbumEntry albumEntry) {
        var builder = new Builder();
        builder.id = albumEntry.id;
        builder.parent = albumEntry.parent;
        builder.path = albumEntry.path;
        builder.album = albumEntry.album;
        builder.title = albumEntry.title;
        builder.description = albumEntry.description;
        builder.imageUrl = albumEntry.imageUrl;
        builder.thumbnailUrl = albumEntry.thumbnailUrl;
        builder.sort  = albumEntry.sort;
        builder.lastModified = albumEntry.lastModified;
        builder.contentType = albumEntry.contentType;
        builder.contentLength = albumEntry.contentLength;
        builder.requireLogin = albumEntry.requireLogin;
        builder.childcount = albumEntry.childcount;
        return builder;
    }

    public static class Builder {
        private int id = -1;
        private int parent;
        private String path;
        private boolean album;
        private String title;
        private String description;
        private String imageUrl;
        private String thumbnailUrl;
        private int sort;
        private Date lastModified;
        private String contentType;
        private int contentLength;
        private int childcount;
        private boolean requireLogin;

        private Builder() {}

        public AlbumEntry build() {
            return new AlbumEntry(
                id,
                parent,
                path,
                album,
                title,
                description,
                imageUrl,
                thumbnailUrl,
                sort,
                lastModified,
                contentType,
                contentLength,
                childcount,
                requireLogin);
        }

        public Builder id(int id) {
            this.id = id;
            return this;
        }

        public Builder parent(int parent) {
            this.parent = parent;
            return this;
        }

        public Builder path(String path) {
            this.path = path;
            return this;
        }

        public Builder album(boolean album) {
            this.album = album;
            return this;
        }

        public Builder title(String title) {
            this.title = title;
            return this;
        }

        public Builder description(String description) {
            this.description = description;
            return this;
        }

        public Builder imageUrl(String imageUrl) {
            this.imageUrl = imageUrl;
            return this;
        }

        public Builder thumbnailUrl(String thumbnailUrl) {
            this.thumbnailUrl = thumbnailUrl;
            return this;
        }

        public Builder sort(int sort) {
            this.sort = sort;
            return this;
        }

        public Builder lastModified(Date lastModified) {
            this.lastModified = lastModified;
            return this;
        }

        public Builder contentType(String contentType) {
            this.contentType = contentType;
            return this;
        }

        public Builder contentLength(int contentLength) {
            this.contentLength = contentLength;
            return this;
        }

        public Builder childcount(int childcount) {
            this.childcount = childcount;
            return this;
        }

        public Builder requireLogin(boolean requirelogin) {
            this.requireLogin = requirelogin;
            return this;
        }
    }

}


code:
<?xml version="1.0" encoding="UTF-8"?>

<!-- Copyright 2019-2023 Steinar Bang                                                -->
<!--                                                                                -->
<!-- Licensed under the Apache License, Version 2.0 (the "License");                 -->
<!-- you may not use this file except in compliance with the License.                -->
<!-- You may obtain a copy of the License at                                         -->
<!--   http://www.apache.org/licenses/LICENSE-2.0                                    -->
<!-- Unless required by applicable law or agreed to in writing,                      -->
<!-- software distributed under the License is distributed on an "AS IS" BASIS,      -->
<!-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.        -->
<!-- See the License for the specific language governing permissions and limitations -->
<!-- under the License.                                                              -->

<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" xml:space="preserve">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>no.priv.bang.jdbc</groupId>
        <artifactId>jdbc-parent</artifactId>
        <version>1.2.1-SNAPSHOT</version>
    </parent>
    <groupId>no.priv.bang.jdbc</groupId>
    <artifactId>jdbc</artifactId>
    <version>1.2.1-SNAPSHOT</version>

    <packaging>pom</packaging>

    <name>JDBC utilities build</name>

    <modules>
        <module>jdbc.datasourceproxy</module>
        <module>jdbc.sqldumper</module>
    </modules>

    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>no.priv.bang.pom</groupId>
                <artifactId>bang-bom</artifactId>
                <version>${bang-bom.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
            <dependency>
                <groupId>org.apache.karaf</groupId>
                <artifactId>karaf-bom</artifactId>
                <version>${karaf.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
            <dependency>
                <groupId>org.junit</groupId>
                <artifactId>junit-bom</artifactId>
                <version>${junit.jupiter.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>

    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-source-plugin</artifactId>
                <executions>
                    <execution>
                        <id>attach-sources</id>
                        <goals>
                            <goal>jar</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-release-plugin</artifactId>
            </plugin>
            <plugin>
                <groupId>org.sonatype.central</groupId>
                <artifactId>central-publishing-maven-plugin</artifactId>
            </plugin>
        </plugins>
        <extensions>
            <extension>
                <groupId>org.apache.maven.wagon</groupId>
                <artifactId>wagon-ftp</artifactId>
                <version>3.5.3</version>
            </extension>
        </extensions>
        <pluginManagement>
            <plugins>
                <plugin>
                    <groupId>org.codehaus.mojo</groupId>
                    <artifactId>properties-maven-plugin</artifactId>
                    <version>1.2.1</version>
                </plugin>
            </plugins>
        </pluginManagement>
    </build>

    <distributionManagement>
        <snapshotRepository>
            <id>ossrh</id>
            <url>https://oss.sonatype.org/content/repositories/snapshots</url>
        </snapshotRepository>
        <repository>
            <id>ossrh</id>
            <url>https://oss.sonatype.org/service/local/staging/deploy/maven2/</url>
        </repository>
    </distributionManagement>

    <licenses>
        <license>
            <name>Apache License version 2</name>
            <url>http://www.apache.org/licenses/LICENSE-2.0</url>
        </license>
    </licenses>

    <developers>
        <developer>
            <name>Steinar Bang</name>
            <email>sb@dod.no</email>
            <url>https://steinar.bang.priv.no/</url>
            <organization>steinarb-github</organization>
            <organizationUrl>https://github.com/steinarb</organizationUrl>
        </developer>
    </developers>

    <scm>
        <url>https://github.com/steinarb/jdbc</url>
        <connection>scm:git:https://github.com/steinarb/jdbc.git</connection>
        <tag>HEAD</tag>
    </scm>

    <issueManagement>
        <url>https://github.com/steinarb/jdbc/issues</url>
        <system>Github issue tracker</system>
    </issueManagement>

    <ciManagement>
        <url>https://travis-ci.org/steinarb/jdbc</url>
        <system>travis-ci</system>
    </ciManagement>

    <url>http://steinarb.github.io/jdbc/</url>

    <profiles>
        <profile>
            <id>release-sign-artifact</id>
            <build>
                <plugins>
                    <plugin>
                        <groupId>org.apache.maven.plugins</groupId>
                        <artifactId>maven-gpg-plugin</artifactId>
                        <version>3.2.7</version>
                        <executions>
                            <execution>
                                <id>sign-artifacts</id>
                                <phase>verify</phase>
                                <goals>
                                    <goal>sign</goal>
                                </goals>
                                <configuration>
                                    <gpgArguments>
                                        <arg>--pinentry-mode</arg>
                                        <arg>loopback</arg>
                                    </gpgArguments>
                                </configuration>
                            </execution>
                        </executions>
                    </plugin>
                </plugins>
            </build>
        </profile>
    </profiles>

</project>


code:
<?xml version="1.0" encoding="UTF-8"?>

<!-- Copyright 2019-2025 Steinar Bang                                                -->
<!--                                                                                -->
<!-- Licensed under the Apache License, Version 2.0 (the "License");                 -->
<!-- you may not use this file except in compliance with the License.                -->
<!-- You may obtain a copy of the License at                                         -->
<!--   http://www.apache.org/licenses/LICENSE-2.0                                    -->
<!-- Unless required by applicable law or agreed to in writing,                      -->
<!-- software distributed under the License is distributed on an "AS IS" BASIS,      -->
<!-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.        -->
<!-- See the License for the specific language governing permissions and limitations -->
<!-- under the License.                                                              -->

<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" xml:space="preserve">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>no.priv.bang.pom</groupId>
        <artifactId>bang-pom</artifactId>
        <version>2.1.8</version>
    </parent>
    <groupId>no.priv.bang.jdbc</groupId>
    <artifactId>jdbc-parent</artifactId>
    <version>1.2.1-SNAPSHOT</version>

    <packaging>pom</packaging>

    <name>JDBC utilities</name>
    <description>Common code for JDBC</description>

    <modules>
        <module>jdbc</module>
        <module>jacoco-coverage-report</module>
        <module>jdbc-bom</module>
    </modules>

    <properties>
        <sonar.coverage.jacoco.xmlReportPaths>${project.basedir}/../../jacoco-coverage-report/target/site/jacoco-aggregate/jacoco.xml</sonar.coverage.jacoco.xmlReportPaths>
    </properties>

    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-source-plugin</artifactId>
                <executions>
                    <execution>
                        <id>attach-sources</id>
                        <goals>
                            <goal>jar</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-javadoc-plugin</artifactId>
                <configuration>
                    <source>17</source>
                    <show>private</show>
                </configuration>
                <executions>
                    <execution>
                        <id>attach-javadocs</id>
                        <goals>
                            <goal>jar</goal>
                        </goals>
                    </execution>
                    <execution>
                        <id>aggregate-javadocs</id>
                        <goals>
                            <goal>aggregate-no-fork</goal>
                        </goals>
                        <phase>prepare-package</phase>
                        <inherited>false</inherited>
                    </execution>
                </executions>
            </plugin>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-jar-plugin</artifactId>
                <version>3.4.2</version>
                <executions>
                    <execution>
                        <id>attach-aggregate-javadoc</id>
                        <goals>
                            <goal>jar</goal>
                        </goals>
                        <phase>package</phase>
                        <inherited>false</inherited>
                        <configuration>
                            <classesDirectory>${project.build.directory}/site/apidocs</classesDirectory>
                            <classifier>javadoc</classifier>
                        </configuration>
                    </execution>
                </executions>
            </plugin>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-release-plugin</artifactId>
                <configuration>
                    <releaseProfiles>release-sign-artifact</releaseProfiles>
                    <tagNameFormat>jdbc-@{project.version}</tagNameFormat>
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.sonatype.central</groupId>
                <artifactId>central-publishing-maven-plugin</artifactId>
            </plugin>
        </plugins>
        <extensions>
            <extension>
                <groupId>org.apache.maven.wagon</groupId>
                <artifactId>wagon-ftp</artifactId>
                <version>3.5.3</version>
            </extension>
        </extensions>
    </build>

    <distributionManagement>
        <snapshotRepository>
            <id>ossrh</id>
            <url>https://oss.sonatype.org/content/repositories/snapshots</url>
        </snapshotRepository>
        <repository>
            <id>ossrh</id>
            <url>https://oss.sonatype.org/service/local/staging/deploy/maven2/</url>
        </repository>
    </distributionManagement>

    <licenses>
        <license>
            <name>Apache License version 2</name>
            <url>http://www.apache.org/licenses/LICENSE-2.0</url>
        </license>
    </licenses>

    <developers>
        <developer>
            <name>Steinar Bang</name>
            <email>sb@dod.no</email>
            <url>https://steinar.bang.priv.no/</url>
            <organization>steinarb-github</organization>
            <organizationUrl>https://github.com/steinarb</organizationUrl>
        </developer>
    </developers>

    <scm>
        <url>https://github.com/steinarb/jdbc</url>
        <connection>scm:git:https://github.com/steinarb/jdbc.git</connection>
        <tag>HEAD</tag>
    </scm>

    <issueManagement>
        <url>https://github.com/steinarb/jdbc/issues</url>
        <system>Github issue tracker</system>
    </issueManagement>

    <ciManagement>
        <url>https://travis-ci.org/steinarb/jdbc</url>
        <system>travis-ci</system>
    </ciManagement>

    <url>http://steinarb.github.io/jdbc/</url>

    <profiles>
        <profile>
            <id>release-sign-artifact</id>
            <build>
                <plugins>
                    <plugin>
                        <groupId>org.apache.maven.plugins</groupId>
                        <artifactId>maven-gpg-plugin</artifactId>
                        <version>3.2.7</version>
                        <executions>
                            <execution>
                                <id>sign-artifacts</id>
                                <phase>verify</phase>
                                <goals>
                                    <goal>sign</goal>
                                </goals>
                                <configuration>
                                    <gpgArguments>
                                        <arg>--pinentry-mode</arg>
                                        <arg>loopback</arg>
                                    </gpgArguments>
                                </configuration>
                            </execution>
                        </executions>
                    </plugin>
                </plugins>
            </build>
        </profile>
    </profiles>

</project>


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

# Globalist: manage a global repo via decentral git instances
# you may peer with any number of other Globalist onions

# Think onionshare, but with permanent onion addresses, P2P and DVCS

# Python2/3. Dependencies:
#   - stem (torsocks pip install stem / via distro)
#     a recent version (>= 1.5.0) is needed for auth
#   - git must be installed
#   - torsocks must be installed
#   - tor must be up and running and the ControlPort open

# Use scenario:
# a) Run Tor.
# b) Run the server in the background and schedule a job for pulling from peers.
#    it is a git server that listens on <your-identifier>.onion:9418
#    it's to be expected that peers uptime will intersect with yours
#    only a fraction of the time.
# c) Globalist.py creates a git, which you may use to push and pull your own changes.

# Bugs:
# FIXME: clean up hidservauth entries on stop

import globalist
import sys

if __name__=='__main__':
    globalist.main(args=sys.argv[1:])

repo_name: ts/globalist


code:
version 0.0.6.2
- HidServAuth entries are never cleaned up
- ux: -X does not disable authentication


code:
# Globalist
Globalist provides distributed sharing of repositories without the need of central instances (like GitHub).

This is an attempt to ease the distribution of git repos, to overcome the risk of a central points of failure.

Globalist stands for "Global List" and aims at replacing any EtherPads of more than transient value.

It is also meant to evolve into an experimental distributed asynchronous wiki facility.

Nodes can come and go, and network topology only depends on the peers entries in the nodes' config files. Changes that are merged by one's peers propagate by diffusion.

The official repository can be found at https://notabug.org/themusicgod1/cloudflare-tor

## Usage

To use Globalist.py python3 is needed. Either run with `python3 Globalist.py` or install it as described below.

Per default an open tor ControlPort at 9151 without authentication is expected. You can choose another port with `-C`. For a list of option see `--help`.

### Create repository

Make a new directory and put this in the file ./repo.cfg (when creating a new repository instead of cloning from a peer, the list or indeed the repo.cfg file can remain empty)

```
[network]
peers = <comma-separated list of onion domain names, with or without the suffix .onion>
```

For a public repository, no authentication is needed (option -X). In case authentication is used, prepend the secret as follows: somebody:secret@peeroniondomainname.onion

For each shared repo, Globalist will create one .onion service. Note that it is possible to use either bare repos or not-bare repos.

### Clone a repository

To clone a bare repo:

```
Globalist.py -bc ...
```

To pull once from a bare repo:

```
Globalist.py -bp
```

## To install locally

```
./setup.py install --user
```

or

```
torsocks pip3 install -v -e .
```

## To do

set default commit messages
support signed commits
push?


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

__version__ = "0.0.6.2"

try:
    import ConfigParser as cp
except:
    import configparser as cp # python3
import optparse as op
import re
import os
import sys
import json
import subprocess

import stem
from stem.control import Controller

# Usage:
#
# Make a directory.
#
# Put a configuration file repo.cfg listing some peers. Done.
#
# Initialize:
#  Either a) (git init repo/) ->
#    $ python Globalist.py -i
#  or     b) (torsocks git clone git://example7abcdefgh.onion) ->
#    $ python Globalist.py -c
#
# Have fun:
#  Run server
#    $ python Globalist.py
#  Pull from peers once
#    $ python Globalist.py -p
#  Periodically pull, don't serve
#    $ python Globalist.py -pP 1800
#  Periodically pull and also serve
#    $ python Globalist.py -P 1800
#
# That's it.

# One can simply check in a list of onions for open peering
# as PEERS.txt ...

# A word of CAUTION: anyone can commit anything
# and there's no mechanism for permanently blacklisting
# malicious peers (although one can simply remove them
# as they crop up and roll back their changes).
#
# A future version of Globalist.py should introduce
# signed commits + reputation system, when the need arises.

# [network]
# peers = example7abcdefgh.onion, example8abcdefgh.onion
# (possibly prefixed with somebody:authkey@ ...)

# when using -b (bare), merge remote changes locally after
# git pull origin remote/origin/master.

DEFAULT_CONTROLPORT = 9151

STATUS = {'peers': None, 'socksport': None}

OPTIONS = None

def git(command):
#    print (command)
    p = subprocess.Popen(["git"] + command)
    return p

def make_exportable(path):
    subprocess.Popen(["touch", os.path.abspath(os.path.join(path, "git-daemon-export-ok")) ]).wait()

def run_server(config, localport = 9418):
    print ("Running git server on %s.onion" % config.get('onion', 'hostname'))
    try:
        authkey = config.get('onion', 'clientauth')
        if authkey:
            print ("Client auth is %s" % authkey)
    except (KeyError, cp.NoOptionError) as e:
        print ("No client auth")
    print ("Git server local port is %d" % localport)
    print ("You can now hand out this onion to prospective peers.")
    print ("It will be re-used anytime Globalist starts in this directory.")

    what = "repo"

    if OPTIONS.o_bare:
        make_exportable("repo.git")
        what += ".git"
    else:
        make_exportable(os.path.join("repo",".git"))

    gitdaemon = git(["daemon", "--base-path=%s" % os.path.abspath("."),
                     "--reuseaddr", "--verbose",
    # there could be a global setting enabling write access??
                     "--disable=receive-pack",
                     "--listen=127.0.0.1", "--port=%d" % localport,
                     os.path.abspath(what)])
    output = gitdaemon.communicate()[0]
    print (output)
    # then background this process

def makeonion(controller, config, options):
    # stem docs say: provide the password here if you set one:
    controller.authenticate()
    # todo catch UnreadableCookieFile(

    onion = None

    extra_kwargs = {}

    if config.has_section('onion'):
        print ("Attempting to use saved onion identity")
        (keytype,key) = config.get('onion', 'key').split(':',1)

        if options.o_auth:
            try:
                print ("Attempting to use saved clientauth")
                extra_kwargs['basic_auth'] =\
                dict([config.get('onion', 'clientauth').split(':',1)])
            except (KeyError, cp.NoOptionError) as e:
                print ("No client auth present, generating one")
                extra_kwargs['basic_auth'] = {'somebody': None}
        else:
            print ("Not using clientauth.")

        onion = controller.create_ephemeral_hidden_service(**extra_kwargs, ports={9418: options.a_localport}, discard_key=True, await_publication=options.o_ap, key_type=keytype, key_content=key)

    else:
        print ("I'm afraid we don't have an identity yet, creating one")

        if options.o_auth:
            extra_kwargs['basic_auth'] = {'somebody': None}

        onion = controller.create_ephemeral_hidden_service(**extra_kwargs, ports={9418: options.a_localport}, discard_key=False, await_publication=options.o_ap)

#    print (onion)

    print ("Tor controller says Onion OK")

    if not onion.is_ok():
        raise Exception('Failed to publish onion.')
    else:
        # perhaps avoid overwriting when already present?
        for line in onion:
            if line != "OK":
                k, v = line.split('=', 1)
                # we only request the key if the service is new
                if k == "PrivateKey":
                    try:
                        config.add_section('onion')
                    except cp.DuplicateSectionError as e:
                        pass
                    config.set('onion', 'key', v)
                if k == "ServiceID":
                    try:
                        config.add_section('onion')
                    except cp.DuplicateSectionError as e:
                        pass
                    config.set('onion', 'hostname', v)
                if k == "ClientAuth":
                    try:
                        config.add_section('onion')
                    except cp.DuplicateSectionError as e:
                        pass
                    config.set('onion', 'clientauth', v)
            config.write(open('repo.cfg', 'w'))

def set_client_authentications(ls):
    global OPTIONS
    options = OPTIONS

    controller = Controller.from_port(port = options.a_controlport)
    controller.authenticate()
    # is there no sane way to _append_ a multi-config option in Tor????
    # control protocol badly misdesigned, nobody thought of concurrent access???!?
    controller.set_caching(False)

# except it doesn't work, the 650 message never arrives. why?
#    controller.add_event_listener(my_confchanged_listener, EventType.CONF_CHANGED)
# SETEVENTS conf_changed

    hsa = controller.get_conf_map('hidservauth')

    for authpair in ls:
        if authpair['auth'] and len(authpair['auth']):
            hsa['hidservauth'].append('%s.onion %s' % (authpair['onion'], authpair['auth']))

    hsa['hidservauth'] = list(set(hsa['hidservauth']))

    controller.set_conf('hidservauth', hsa['hidservauth'])
    controller.close()

def getpeers(config):
    if STATUS['peers']:
        return STATUS['peers']

    if config.has_section('network'):
        peerslist = config.get('network', 'peers').split(',')
        peers = []
        authpairs = []

        for peerentry in peerslist:

            # extract what looks like an onion identifier
            try:
                authpair = re.findall('(?:(somebody:[A-Za-z0-9+/]{22})@)?([a-z2-8]{16})', peerentry)[0]

                userpass = authpair[0].split(":",1)
                if not userpass or not len(userpass)==2:
                    userpass = (None, None)

                authpairs += [{'auth':userpass[1],
                               'user':userpass[0], # somebody
                               'onion':authpair[1]}]
                peers += [authpair[1]]

            except Exception as e:
                print (e)

        set_client_authentications(authpairs)

        STATUS['peers'] = peers

        return peers

    else:
        STATUS['peers'] = []

        return []

def clone(config):
    peers = getpeers(config)

    # FIXME: when the first fails, we should move on to the next..

    what  = "git://%s.onion/repo" % peers[0]
    where = "repo"
    how   = []

    if OPTIONS.o_bare:
        what  += ".git"
        where += ".git"
        how   = ["--bare", "--mirror"]

    cloneproc = subprocess.Popen(["torsocks", "-P", STATUS['socksport'], "git", "clone"] + how + [what, where])
    if cloneproc.wait() != 0:
        print ("Error cloning, exiting.")
        return -1
    else:
        make_exportable(where)

    # Make a local editable repo
    git(["clone", "repo.git", "repo"]).wait()

    processes = []
    for peer in peers[1:]:
        processes.append([peer, subprocess.Popen(["torsocks", "-P", STATUS['socksport'], "git", "-C", os.path.abspath("repo"), "pull", "git://%s.onion/repo" % peer])])

    for (peer,proc) in processes:
        if proc.wait() != 0:
            print ("Error with %s" % peer)

def pull(config):
    peers = getpeers(config)

    print ("Pulling from %s" % peers)

    processes = []
    for peer in peers:
        processes.append([peer, subprocess.Popen(["torsocks", "-P", STATUS['socksport'], "git", "-C", os.path.abspath("repo"), "pull", "git://%s.onion/repo" % peer])])

    for (peer,proc) in processes:
        if proc.wait() != 0:
            print ("Error with %s" % peer)

def fetch(config):
    peers = getpeers(config)
    print ("Fetching from %s" % peers)
    processes = []
    for peer in peers:
        processes.append([peer, subprocess.Popen(["torsocks", "-P", STATUS['socksport'], "git", "-C", os.path.abspath("repo.git"), "fetch", "git://%s.onion/repo.git" % peer, '+refs/heads/*:refs/remotes/origin/*'])])
# ,

    for (peer,proc) in processes:
        if proc.wait() != 0:
            print ("Error with %s" % peer)

def init(config):
    global OPTIONS # not needed for read access btw
    options = OPTIONS

    print ("Initializing ...")

    if options.o_bare:
        git(["init", "repo.git", "--bare"]).wait()
        # Make a local editable repo
        git(["clone", "repo.git", "repo"]).wait()

    else:
        git(["init", "repo"]).wait()

    print ("Initialized")

def main(args=[]):
    # OptionParser is capable of printing a helpscreen
    opt = op.OptionParser()

    opt.add_option("-V", "--version", dest="o_version", action="store_true",
                   default=False, help="print version number")

    opt.add_option("-i", "--init", dest="o_init", action="store_true",
                   default=False, help="make new empty repo")

    opt.add_option("-b", "--bare", dest="o_bare", action="store_true",
                   default=False, help="use bare repos and fetch, not pull")

    opt.add_option("-c", "--clone", dest="o_clone", action="store_true",
                   default=False, help="clone repo from 1st peer")

    opt.add_option("-p", "--pull", dest="o_pull", action="store_true",
                   default=False, help="pull / fetch from peers and don't serve")

    opt.add_option("-P", "--periodically-pull", dest="a_pull", action="store",
                   type="int", default=None, metavar="PERIOD",
                   help="pull / fetch from peers every n seconds")

    opt.add_option("-L", "--local", dest="a_localport", action="store", type="int",
                   default=9418, metavar="PORT", help="local port for git daemon")

    opt.add_option("-C", "--control-port", dest="a_controlport", action="store", type="int",
                   default=9151,  metavar="PORT", help="Tor controlport")

    opt.add_option("-a", "--await", dest="o_ap", action="store_true",
                   default=False, help="await publication of .onion in DHT before proceeding")

    opt.add_option("-x", "--auth", action="store_true", default=True,
                   dest="o_auth", help="enable authentication (private)")

    opt.add_option("-X", "--no-auth", action="store_false", default=True,
                   dest="o_auth", help="disable authentication (not private)")

    (options, args) = opt.parse_args(args)

    global OPTIONS
    OPTIONS = options

    if options.o_version:
        print (__version__)
        return 0

    if options.o_auth and stem.__version__ < '1.5.0':
        sys.stderr.write ("stem version >=1.5.0 required for auth\n")
        return 1

    if not options.a_controlport:
        options.a_controlport = DEFAULT_CONTROLPORT

    # Extract socksport via c.get_conf and use this (-P in torsocks)
    controller = Controller.from_port(port = options.a_controlport)
    controller.authenticate()
    STATUS['socksport'] = controller.get_conf('SocksPort').split(" ",1)[0]
    controller.close()

    config = cp.ConfigParser()
    cfgfile = None
    try:
        cfgfile = open('repo.cfg')
    except FileNotFoundError as e:
        print("Trying to make file repo.cfg")
        try:
            os.mknod("repo.cfg")
            os.chmod("repo.cfg", 0o600)
            cfgfile = open('repo.cfg')
        except Exception as e:
            print (e)
            return 1

    config.readfp(cfgfile)

    try:
        os.stat("repo.git")
        if not options.o_bare:
            print ("repo.git exists, setting -b implicitly")
            # TODO -B to override
        options.o_bare = True

    except FileNotFoundError as e:
        if not options.o_init and not options.o_clone and options.o_bare:
            print ("./repo.git/ does not exist, try -ib or -cb")
            return 1

    try:
        os.stat("repo")
    except FileNotFoundError as e:
        if not options.o_init and not options.o_clone and not options.o_bare:
            print("./repo/ does not exist, try -i or -c")
            return 1

    except Exception as e:
        print (e)
        return 1

    if options.o_init:
        init(config)

    peers = getpeers(config)

    if options.o_clone:
        if not len(peers):
            print ("No peers, can't clone. Please enter a peer in repo.cfg")
        clone(config)
        return 1

    threads = []

    if options.a_pull:
        if not len(peers):
            print ("No peers, not starting pulling task.")

        else:
            import threading
            from   datetime import timedelta as td
            from   datetime import datetime

            class T:
                def __init__(self):
                    self.last = datetime.now()

                def run(self):
                    if options.o_bare:
                        fetch(config)
                    else:
                        pull(config)
                    threading.Timer(options.a_pull, T.run, args=(self,)).start()

            task = T()

            t = threading.Thread(target=T.run, args=(task,))
            t . setDaemon(True)
            threads.append(t)
            t.start()

    # It's either pull(once) or serve. It's no problem running pull from
    # another console while the server is up. It's no problem specifying
    # periodic pull with either.

    if options.o_pull and not options.a_pull:
        if options.o_bare:
            fetch(config)
        else:
            pull(config)

    elif not options.o_pull:
        controller = Controller.from_port(port = options.a_controlport)
        makeonion(controller, config, options)
        run_server(config, localport = options.a_localport)
        controller.close()

    for t in threads:
        t.join()

# TODO: should only generate a clientauth on a previously unauthenticated repo if requested by command line option


code:
#!/usr/bin/env python3

from distutils.core import setup

setup(
    name='Globalist',
    version='0.0.6.2',
    description='Globalist distributed git onions',
    author='fnordomat',
#    author_email='',
    url='https://github.com/fnordomat/Globalist',
    packages=['globalist'],
    scripts=['Globalist.py'],
    install_requires=['stem>=1.5.0'],
    license='GPLv3'
)


code:
# ---> Android
# Built application files
*.apk
*.ap_

# Files for the Dalvik VM
*.dex

# Java class files
*.class

# Generated files
bin/
gen/

# Gradle files
.gradle/
build/

# Local configuration file (sdk path, etc)
local.properties

# Proguard folder generated by Eclipse
proguard/

# Log Files
*.log

# Android Studio Navigation editor temp files
.navigation/

# Android Studio captures folder
captures/

path: .gitignore


code:
# BeatOapp

We make good health simple.

We use human support to give personalised healthcare advice to every
BeatO user and technology to be
accessible far and wide


code:
librewolf-bin
insomnia-bin
tuxguitar
vscodium
mhwd-nvidia
linux515-nvidia
nvidia-utils

repo_name: ropemann/bootystrap


code:
base-devel
git
python3
python-pip
lua
rust
rustup
nodejs
node-gyp
npm
yarn
nvm
perl
bzip2
go
cmake


code:
UPDATECMD="sudo pacman -Syyu"
PROGFILES="base.txt pacman.txt aur.txt git.txt"
PACMAN="sudo pacman --noconfirm --needed -S"
AUR="yay -S --noconfirm"
GIT="git clone"


code:
#!/bin/sh

harakiri() {
    printf "!!! %s !!!\n" "$*" &
    exit 1
}

prepare() {
    [ "$(id -u)" = 0 ] && harakiri "Don't go super sayan on this script! >.<"
    [ ! -f "booty.conf" ] && harakiri "Config file booty.conf not found, baka!"

    # shellcheck source=/dev/null
    . ./booty.conf

    [ -z "$UPDATECMD" ] && harakiri "Undefined repo update command!"
    printf "About to update main repo...\n"
    $UPDATECMD

    [ -z "$PACMAN" ] && harakiri "Pacman install command undefined!"
    [ -z "$AUR" ] && harakiri "Aur helper install command undefined!"
    [ -z "$GIT" ] && harakiri "Git command undefined!"

    [ -z "$PROGFILES" ] && harakiri "No progfiles defined!"
    printf "These progfiles were defined: %s\n" "$PROGFILES"
}

read_prog_files() {
    printf "%s\n" "$PROGFILES" | tr ' ' '\n' | while read -r file; do
        printf "Processing %s...\n" "$file"
        if [ -f "$file" ]; then
            process_prog_file "$file"
        else
            printf "Couldn't find %s. Skipping to next file if any.\n" "$file"
        fi
    done
}

process_prog_file() {
    FNAME=$(printf "%s" "$1" | cut -d. -f1)
    while IFS= read -r program; do
        case "$FNAME" in
        "base") $PACMAN "$program" ;;
        "pacman") $PACMAN "$program" ;;
        "aur") $AUR "$program" ;;
        "git") process_git "$program" ;;
        *) printf "Undefined install command. Skipping...\n" ;;
        esac

    done <"$1"
}

process_git() {
    folder_name="/tmp/$(printf "%s" "$1" | sed -e 's/[\/.:-]//g')"
    $GIT "$1" "$folder_name"
}

prepare
read_prog_files


code:
variety
mpv
yt-dlp
pcmanfm
alacritty
firefox
pavucontrol
awesome
bluez
bluez-tools
cups
hplip
htop
neovim
picom
libpamac
pamac-cli
sddm
shellcheck
sxiv
zathura
feh
network-manager-applet
lxqt-policykit
game-devices-udev
steam-native
keepassxc
s-tui
mangohud
mangohud-common
lutris
ffmpeg
imagemagick
libwebp
libnotify
xclip
openscad
freecad
gimp
gvfs


code:
*~
.#*
*.go
*.log
*.trs
autom4te.cache/
Makefile
Makefile.in
/configure
/env
/config.status
/config.log
/aclocal.m4
/pre-inst-env
/missing
/test-driver
/install-sh
/gcrypt/package-config.scm
/build-aux/install-sh
/build-aux/missing
/guile-gcrypt-*.tar.gz
/INSTALL
/config.cache

repo_name: Brendan/guile


code:
Guile-Gcrypt was initially written by:

  Christopher Lemmer Webber <cwebber@dustycloud.org>
  Ludovic Courtès <ludo@gnu.org>

… but ‘git log’ may show other names.  In fact, we’d like to see yours
too, consider joining us!

path: AUTHORS


code: Please use ‘git log’.

path: ChangeLog


code:
## Copyright (C) 2015, 2017 Christopher Allan Webber <cwebber@dustycloud.org>

## parts of this automake recipe borrowed from:

## GNU Guix --- Functional package management for GNU
## Copyright © 2012, 2013, 2014, 2015 Ludovic Courtès <ludo@gnu.org>
## Copyright © 2013 Andreas Enge <andreas@enge.fr>
## Copyright © 2015 Alex Kost <alezost@gmail.com>

## Sly
## Copyright (C) 2013, 2014 David Thompson <dthompson2@worcester.edu>

## This program is free software: you can redistribute it and/or
## modify it under the terms of the GNU General Public License as
## published by the Free Software Foundation, either version 3 of the
## License, or (at your option) any later version.
##
## This program is distributed in the hope that it will be useful, but
## WITHOUT ANY WARRANTY; without even the implied warranty of
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
## General Public License for more details.
##
## You should have received a copy of the GNU General Public License
## along with this program.  If not, see
## <http://www.gnu.org/licenses/>.

GOBJECTS = $(SOURCES:%.scm=%.go)
nobase_mod_DATA = $(SOURCES)
nobase_go_DATA = $(GOBJECTS)

# Make sure source files are installed first, so that the mtime of
# installed compiled files is greater than that of installed source
# files.  See
# <http://lists.gnu.org/archive/html/guile-devel/2010-07/msg00125.html>
# for details.
guile_install_go_files = install-nobase_goDATA
$(guile_install_go_files): install-nobase_modDATA

GUILE_WARNINGS = -Wunbound-variable -Warity-mismatch -Wformat
SUFFIXES = .scm .go
.scm.go:
	$(AM_V_GEN)$(top_builddir)/pre-inst-env $(GUILE_TOOLS) compile $(GUILE_WARNINGS) -o "$@" "$<"

moddir=$(prefix)/share/guile/site/$(GUILE_EFFECTIVE_VERSION)
godir = $(libdir)/guile/$(GUILE_EFFECTIVE_VERSION)/site-ccache

SOURCES =  \
	gcrypt/base16.scm					\
	gcrypt/base64.scm					\
	gcrypt/common.scm					\
	gcrypt/hash.scm						\
	gcrypt/hmac.scm						\
	gcrypt/package-config.scm				\
	gcrypt/pk-crypto.scm					\
	gcrypt/random.scm					\
	gcrypt/utils.scm

TESTS =							\
	tests/base16.scm				\
	tests/base64.scm				\
	tests/hash.scm					\
	tests/hmac.scm					\
	tests/pk-crypto.scm

TEST_EXTENSIONS = .scm

SCM_LOG_DRIVER =				\
	$(top_builddir)/pre-inst-env		\
	$(GUILE) --no-auto-compile -e main	\
	$(top_srcdir)/build-aux/test-driver.scm

# Tell 'build-aux/test-driver.scm' to display only source file names,
# not indivdual test names.
AM_SCM_LOG_DRIVER_FLAGS = --brief=yes

AM_SCM_LOG_FLAGS = --no-auto-compile -L $(top_srcdir)

CLEANFILES = 						\
	$(GOBJECTS)					\
	$(TESTS:tests/%.scm=%.log)			\
	*.log *.tar.gz

EXTRA_DIST = 						\
	$(SOURCES)					\
	$(TESTS)					\
        guix.scm					\
	build-aux/test-driver.scm			\
	pre-inst-env.in

info_TEXINFOS = guile-gcrypt.texi
guile_gcrypt_TEXINFOS = fdl-1.3.texi


code: No news is good news!

path: NEWS


code:
Guile-Gcrypt — Guile bindings to Libgcrypt
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Guile-Gcrypt provides a Guile 2.x interface to a subset of the
GNU Libgcrypt crytographic library, which is itself used by the
GNU Privacy Guard (GPG).

Guile-Gcrypt provides modules for cryptographic hash functions,
message authentication codes (MAC), public-key cryptography, strong
randomness, and more.  It is implemented using the foreign function
interface (FFI) of Guile.

Requirements:

  • GNU Guile 2.x, https://gnu.org/software/guile/
  • GNU Libgcrypt 1.8.x or later, https://gnupg.org/

For more information, patches, bug reports, and all that, see:

  https://notabug.org/cwebber/guile-gcrypt

path: README


code:
#!/bin/sh

autoreconf -vif


code:
;;;; test-driver.scm - Guile test driver for Automake testsuite harness

(define script-version "2017-03-22.13") ;UTC

;;; Copyright © 2015, 2016 Mathieu Lirzin <mthl@gnu.org>
;;;
;;; This program is free software; you can redistribute it and/or modify it
;;; under the terms of the GNU General Public License as published by
;;; the Free Software Foundation; either version 3 of the License, or (at
;;; your option) any later version.
;;;
;;; This program is distributed in the hope that it will be useful, but
;;; WITHOUT ANY WARRANTY; without even the implied warranty of
;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
;;; GNU General Public License for more details.
;;;
;;; You should have received a copy of the GNU General Public License
;;; along with this program.  If not, see <http://www.gnu.org/licenses/>.

;;;; Commentary:
;;;
;;; This script provides a Guile test driver using the SRFI-64 Scheme API for
;;; test suites.  SRFI-64 is distributed with Guile since version 2.0.9.
;;;
;;;; Code:

(use-modules (ice-9 getopt-long)
             (ice-9 pretty-print)
             (srfi srfi-26)
             (srfi srfi-64))

(define (show-help)
  (display "Usage:
   test-driver --test-name=NAME --log-file=PATH --trs-file=PATH
               [--expect-failure={yes|no}] [--color-tests={yes|no}]
               [--enable-hard-errors={yes|no}] [--brief={yes|no}}] [--]
               TEST-SCRIPT [TEST-SCRIPT-ARGUMENTS]
The '--test-name', '--log-file' and '--trs-file' options are mandatory.\n"))

(define %options
  '((test-name                 (value #t))
    (log-file                  (value #t))
    (trs-file                  (value #t))
    (color-tests               (value #t))
    (expect-failure            (value #t)) ;XXX: not implemented yet
    (enable-hard-errors        (value #t)) ;not implemented in SRFI-64
    (brief                     (value #t))
    (help    (single-char #\h) (value #f))
    (version (single-char #\V) (value #f))))

(define (option->boolean options key)
  "Return #t if the value associated with KEY in OPTIONS is \"yes\"."
  (and=> (option-ref options key #f) (cut string=? <> "yes")))

(define* (test-display field value  #:optional (port (current-output-port))
                       #:key pretty?)
  "Display \"FIELD: VALUE\\n\" on PORT."
  (if pretty?
      (begin
        (format port "~A:~%" field)
        (pretty-print value port #:per-line-prefix "+ "))
      (format port "~A: ~S~%" field value)))

(define* (result->string symbol #:key colorize?)
  "Return SYMBOL as an upper case string.  Use colors when COLORIZE is #t."
  (let ((result (string-upcase (symbol->string symbol))))
    (if colorize?
        (string-append (case symbol
                         ((pass)       "")  ;green
                         ((xfail)      "")  ;light green
                         ((skip)       "")  ;blue
                         ((fail xpass) "")  ;red
                         ((error)      "")) ;magenta
                       result
                       "")          ;no color
        result)))

(define* (test-runner-gnu test-name #:key color? brief? out-port trs-port)
  "Return an custom SRFI-64 test runner.  TEST-NAME is a string specifying the
file name of the current the test.  COLOR? specifies whether to use colors,
and BRIEF?, well, you know.  OUT-PORT and TRS-PORT must be output ports.  The
current output port is supposed to be redirected to a '.log' file."

  (define (test-on-test-begin-gnu runner)
    ;; Procedure called at the start of an individual test case, before the
    ;; test expression (and expected value) are evaluated.
    (let ((result (cute assq-ref (test-result-alist runner) <>)))
      (format #t "test-name: ~A~%" (result 'test-name))
      (format #t "location: ~A~%"
              (string-append (result 'source-file) ":"
                             (number->string (result 'source-line))))
      (test-display "source" (result 'source-form) #:pretty? #t)))

  (define (test-on-test-end-gnu runner)
    ;; Procedure called at the end of an individual test case, when the result
    ;; of the test is available.
    (let* ((results (test-result-alist runner))
           (result? (cut assq <> results))
           (result  (cut assq-ref results <>)))
      (unless brief?
        ;; Display the result of each test case on the console.
        (format out-port "~A: ~A - ~A~%"
                (result->string (test-result-kind runner) #:colorize? color?)
                test-name (test-runner-test-name runner)))
      (when (result? 'expected-value)
        (test-display "expected-value" (result 'expected-value)))
      (when (result? 'expected-error)
        (test-display "expected-error" (result 'expected-error) #:pretty? #t))
      (when (result? 'actual-value)
        (test-display "actual-value" (result 'actual-value)))
      (when (result? 'actual-error)
        (test-display "actual-error" (result 'actual-error) #:pretty? #t))
      (format #t "result: ~a~%" (result->string (result 'result-kind)))
      (newline)
      (format trs-port ":test-result: ~A ~A~%"
              (result->string (test-result-kind runner))
              (test-runner-test-name runner))))

  (define (test-on-group-end-gnu runner)
    ;; Procedure called by a 'test-end', including at the end of a test-group.
    (let ((fail (or (positive? (test-runner-fail-count runner))
                    (positive? (test-runner-xpass-count runner))))
          (skip (or (positive? (test-runner-skip-count runner))
                    (positive? (test-runner-xfail-count runner)))))
      ;; XXX: The global results need some refinements for XPASS.
      (format trs-port ":global-test-result: ~A~%"
              (if fail "FAIL" (if skip "SKIP" "PASS")))
      (format trs-port ":recheck: ~A~%"
              (if fail "yes" "no"))
      (format trs-port ":copy-in-global-log: ~A~%"
              (if (or fail skip) "yes" "no"))
      (when brief?
        ;; Display the global test group result on the console.
        (format out-port "~A: ~A~%"
                (result->string (if fail 'fail (if skip 'skip 'pass))
                                #:colorize? color?)
                test-name))
      #f))

  (let ((runner (test-runner-null)))
    (test-runner-on-test-begin! runner test-on-test-begin-gnu)
    (test-runner-on-test-end! runner test-on-test-end-gnu)
    (test-runner-on-group-end! runner test-on-group-end-gnu)
    (test-runner-on-bad-end-name! runner test-on-bad-end-name-simple)
    runner))

;;;
;;; Entry point.
;;;

(define (main . args)
  (let* ((opts   (getopt-long (command-line) %options))
         (option (cut option-ref opts <> <>)))
    (cond
     ((option 'help #f)    (show-help))
     ((option 'version #f) (format #t "test-driver.scm ~A" script-version))
     (else
      (let ((log (open-file (option 'log-file "") "w0"))
            (trs (open-file (option 'trs-file "") "wl"))
            (out (duplicate-port (current-output-port) "wl")))
        (redirect-port log (current-output-port))
        (redirect-port log (current-warning-port))
        (redirect-port log (current-error-port))
        (test-with-runner
            (test-runner-gnu (option 'test-name #f)
                             #:color? (option->boolean opts 'color-tests)
                             #:brief? (option->boolean opts 'brief)
                             #:out-port out #:trs-port trs)
          (load-from-path (option 'test-name #f)))
        (close-port log)
        (close-port trs)
        (close-port out))))
    (exit 0)))

;;; Local Variables:
;;; eval: (add-hook 'write-file-functions 'time-stamp)
;;; time-stamp-start: "(define script-version \""
;;; time-stamp-format: "%:y-%02m-%02d.%02H"
;;; time-stamp-time-zone: "UTC"
;;; time-stamp-end: "\") ;UTC"
;;; End:

;;;; test-driver.scm ends here.


code:
AC_INIT([Guile-Gcrypt], [0.1.0], [cwebber@dustycloud.org],
  [guile-gcrypt], [https://notabug.org/cwebber/guile-gcrypt])

AC_CONFIG_AUX_DIR([build-aux])
AC_CONFIG_MACRO_DIR([m4])

AM_INIT_AUTOMAKE([-Wall -Werror gnu])

dnl We require guile.m4, from Guile.  Make sure it's available.
m4_pattern_forbid([^GUILE_P])
m4_pattern_allow([^GUILE_PKG_ERRORS])

GUILE_PKG([2.2 2.0])
GUILE_PROGS

AC_CONFIG_FILES([Makefile gcrypt/package-config.scm])
AC_CONFIG_FILES([pre-inst-env], [chmod +x pre-inst-env])

dnl Prepare a version of $datadir that does not contain references to
dnl shell variables.  (Borrowed from Sly, which borrowed from Guix...)
guile_gcrypt_prefix="`eval echo $prefix | sed -e"s|NONE|/usr/local|g"`"
guile_gcrypt_datadir="`eval eval echo $datadir | sed -e "s|NONE|$guile_gcrypt_prefix|g"`"
AC_SUBST([guile_gcrypt_datadir])

LIBGCRYPT="libgcrypt"
LIBGCRYPT_LIBDIR="no"
LIBGCRYPT_PREFIX="no"

AC_ARG_WITH([libgcrypt-prefix],
  [AS_HELP_STRING([--with-libgcrypt-prefix=DIR], [search for GNU libgcrypt in DIR])],
  [case "$withval" in
    yes|no)
      ;;
    *)
      LIBGCRYPT="$withval/lib/libgcrypt"
      LIBGCRYPT_PREFIX="$withval"
      LIBGCRYPT_LIBDIR="$withval/lib"
      ;;
   esac])

AC_ARG_WITH([libgcrypt-libdir],
  [AS_HELP_STRING([--with-libgcrypt-libdir=DIR],
     [search for GNU libgcrypt's shared library in DIR])],
  [case "$withval" in
    yes|no)
      LIBGCRYPT="libgcrypt"
      LIBGCRYPT_LIBDIR="no"
      ;;
    *)
      LIBGCRYPT="$withval/libgcrypt"
      LIBGCRYPT_LIBDIR="$withval"
      ;;
   esac])

dnl If none of the --with-libgcrypt-* options was used, try to determine the
dnl absolute file name of libgcrypt.so.
case "x$LIBGCRYPT_PREFIX$LIBGCRYPT_LIBDIR" in
  xnono)
    GUIX_LIBGCRYPT_LIBDIR([LIBGCRYPT_LIBDIR])
    if test "x$LIBGCRYPT_LIBDIR" != x; then
      LIBGCRYPT="$LIBGCRYPT_LIBDIR/libgcrypt"
    else
      dnl 'config-daemon.ac' expects "no" in this case.
      LIBGCRYPT_LIBDIR="no"
    fi
    ;;
esac

dnl Library name suitable for `dynamic-link'.
AC_MSG_CHECKING([for libgcrypt shared library name])
AC_MSG_RESULT([$LIBGCRYPT])
AC_SUBST([LIBGCRYPT])
AC_SUBST([LIBGCRYPT_PREFIX])
AC_SUBST([LIBGCRYPT_LIBDIR])

GUIX_ASSERT_LIBGCRYPT_USABLE

AC_OUTPUT