text:
package jiraiyah.librarian.events;

import com.google.common.collect.ImmutableSetMultimap;
import jiraiyah.librarian.Librarian;
import jiraiyah.librarian.infrastructure.*;
import jiraiyah.librarian.interfaces.IChunkLoader;
import jiraiyah.librarian.utilities.CommonUtils;
import jiraiyah.librarian.utilities.ServerUtils;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.server.management.PlayerManager;
import net.minecraft.world.ChunkCoordIntPair;
import net.minecraft.world.World;
import net.minecraft.world.WorldServer;
import net.minecraft.world.chunk.Chunk;
import net.minecraftforge.common.DimensionManager;
import net.minecraftforge.common.ForgeChunkManager;
import net.minecraftforge.common.ForgeChunkManager.Ticket;
import net.minecraftforge.fml.common.FMLCommonHandler;
import net.minecraftforge.fml.common.Loader;
import net.minecraftforge.fml.common.ModContainer;
import net.minecraftforge.fml.server.FMLServerHandler;
import org.apache.logging.log4j.LogManager;

import java.io.*;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;

public class ChunkLoaderManager
{
    public static boolean reloadDimensions = false;
    private static boolean opInteract = false;
    private static int cleanupTicks;
    private static int maxChunks;
    private static int awayTimeout;
    private static HashMap<Object, ModContainer> mods = new HashMap<>();

    private static boolean loaded = false;
    private static HashMap<String, PlayerOrganiser> playerOrganisers;
    private static HashMap<Object, ModOrganiser> modOrganisers;
    private static HashMap<String, Long> loginTimes;
    private static File saveDir;

    public static void registerMod(Object mod)
    {
        ModContainer container = Loader.instance().getModObjectList().inverse().get(mod);
        if (container == null)
            throw new NullPointerException("Mod container not found for: " + mod);
        mods.put(mod, container);
        ForgeChunkManager.setForcedChunkLoadingCallback(mod, new DummyLoadingCallback());
    }

    public static void loadWorld(WorldServer world)
    {
        ReviveChange.DimensionRevive.list.add(world);
    }

    public static World getWorld(int dim, boolean create) {
        if (create)
            return FMLServerHandler.instance().getServer().worldServerForDimension(dim);
        return DimensionManager.getWorld(dim);
    }

    public static void load(WorldServer world)
    {
        if (loaded)
            return;

        loaded = true;

        playerOrganisers = new HashMap<>();
        modOrganisers = new HashMap<>();
        loginTimes = new HashMap<>();
        ReviveChange.load();

        try {
            saveDir = new File(DimensionManager.getCurrentSaveRootDirectory(), "chickenchunks");
            if (!saveDir.exists())
                saveDir.mkdirs();
            loadPlayerChunks();
            loadModChunks();
            loadLoginTimes();
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    private static void loadLoginTimes() throws IOException
    {
        File saveFile = new File(saveDir, "loginTimes.dat");
        if (!saveFile.exists())
            return;

        DataInputStream datain = new DataInputStream(new FileInputStream(saveFile));
        try {
            int entries = datain.readInt();
            for (int i = 0; i < entries; i++)
                loginTimes.put(datain.readUTF(), datain.readLong());
        } catch (IOException e) {
            LogManager.getLogger("ChickenChunks").error("Error reading loginTimes.dat", e);
        }
        datain.close();

    }

    private static void loadModChunks() throws IOException
    {
        for (Map.Entry<Object, ModContainer> entry : mods.entrySet()) {
            File saveFile = new File(saveDir, entry.getValue().getModId() + ".dat");
            if (!saveFile.exists())
                return;

            DataInputStream datain = new DataInputStream(new FileInputStream(saveFile));
            ModOrganiser organiser = getModOrganiser(entry.getKey());
            ReviveChange.ModRevive.list.add(organiser);

            organiser.load(datain);
            datain.close();
        }
    }

    private static void loadPlayerChunks() throws IOException
    {
        File saveFile = new File(saveDir, "players.dat");
        if (!saveFile.exists())
            return;

        DataInputStream datain = new DataInputStream(new FileInputStream(saveFile));
        int organisers = datain.readInt();
        for (int i = 0; i < organisers; i++)
        {
            String username = datain.readUTF();
            PlayerOrganiser organiser = getPlayerOrganiser(username);
            organiser.setDormant();
            if (allowOffline(username) && loggedInRecently(username))
                ReviveChange.PlayerRevive.list.add(organiser);

            organiser.load(datain);
        }
        datain.close();
    }

    private static boolean loggedInRecently(String username)
    {
        if (awayTimeout == 0)
            return true;

        Long lastLogin = loginTimes.get(username);
        return lastLogin != null && (System.currentTimeMillis() - lastLogin) / 60000L < awayTimeout;

    }

    public static int getPlayerChunkLimit(String username)
    {
        ConfigTag config = Librarian.config.getTag("players");
        if (config.containsTag(username)) {
            int ret = config.getTag(username).getIntValue(0);
            if (ret != 0)
                return ret;
        }

        if (ServerUtils.isPlayerOP(username)) {
            int ret = config.getTag("OP").getIntValue(0);
            if (ret != 0)
                return ret;
        }

        return config.getTag("DEFAULT").getIntValue(5000);
    }

    public static boolean allowOffline(String username)
    {
        ConfigTag config = Librarian.config.getTag("allowoffline");
        if (config.containsTag(username))
            return config.getTag(username).getBooleanValue(true);

        if (ServerUtils.isPlayerOP(username))
            return config.getTag("OP").getBooleanValue(true);

        return config.getTag("DEFAULT").getBooleanValue(true);
    }

    public static boolean allowChunkViewer(String username)
    {
        ConfigTag config = Librarian.config.getTag("allowchunkviewer");
        if (config.containsTag(username))
            return config.getTag(username).getBooleanValue(true);

        if (ServerUtils.isPlayerOP(username))
            return config.getTag("OP").getBooleanValue(true);

        return config.getTag("DEFAULT").getBooleanValue(true);
    }

    public static void initConfig(ConfigFile config)
    {
        config.getTag("players").setPosition(0).useBraces().setComment("Per player chunk limiting. Values ignored if 0.:Simply add <username>=<value>");
        config.getTag("players.DEFAULT").setComment("Forge gives everyone 12500 by default").getIntValue(5000);
        config.getTag("players.OP").setComment("For server op's only.").getIntValue(5000);
        config.getTag("allowoffline").setPosition(1).useBraces().setComment("If set to false, players will have to be logged in for their chunkloaders to work.:Simply add <username>=<true|false>");
        config.getTag("allowoffline.DEFAULT").getBooleanValue(true);
        config.getTag("allowoffline.OP").getBooleanValue(true);
        config.getTag("allowchunkviewer").setPosition(2).useBraces().setComment("Set to false to deny a player access to the chunk viewer");
        config.getTag("allowchunkviewer.DEFAULT").getBooleanValue(true);
        config.getTag("allowchunkviewer.OP").getBooleanValue(true);
        if (!FMLCommonHandler.instance().getModName().contains("mcpc"))
            cleanupTicks = config.getTag("cleanuptime")
                    .setComment("The number of ticks to wait between attempting to unload orphaned chunks")
                    .getIntValue(1200);
        reloadDimensions = config.getTag("reload-dimensions")
                .setComment("Set to false to disable the automatic reloading of mystcraft dimensions on server restart")
                .getBooleanValue(true);
        opInteract = config.getTag("op-interact")
                .setComment("Enabling this lets OPs alter other player's chunkloaders. WARNING: If you change a chunkloader, you have no idea what may break/explode by not being chunkloaded.")
                .getBooleanValue(false);
        maxChunks = config.getTag("maxchunks")
                .setComment("The maximum number of chunks per chunkloader")
                .getIntValue(400);
        awayTimeout = config.getTag("awayTimeout")
                .setComment("The number of minutes since last login within which chunks from a player will remain active, 0 for infinite.")
                .getIntValue(0);
    }

    public static void addChunkLoader(IChunkLoader loader)
    {
        int dim = CommonUtils.getDimension(loader.getWorld());
        ChunkLoaderOrganiser organiser = getOrganiser(loader);
        if (organiser.canForceNewChunks(dim, loader.getChunks()))
            organiser.addChunkLoader(loader);
        else
            loader.deactivate();
    }

    private static ChunkLoaderOrganiser getOrganiser(IChunkLoader loader)
    {
        String owner = loader.getOwner();
        return owner == null ? getModOrganiser(loader.getMod()) : getPlayerOrganiser(owner);
    }

    public static void remChunkLoader(IChunkLoader loader)
    {
        getOrganiser(loader).remChunkLoader(loader);
    }

    public static void updateLoader(IChunkLoader loader)
    {
        getOrganiser(loader).updateChunkLoader(loader);
    }

    public static boolean canLoaderAdd(IChunkLoader loader, Collection<ChunkCoordIntPair> chunks)
    {
        String owner = loader.getOwner();
        int dim = CommonUtils.getDimension(loader.getWorld());
        if (owner != null)
            return getPlayerOrganiser(owner).canForceNewChunks(dim, chunks);

        return false;
    }

    private static PlayerOrganiser getPlayerOrganiser(String username)
    {
        PlayerOrganiser organiser = playerOrganisers.get(username);
        if (organiser == null)
            playerOrganisers.put(username, organiser = new PlayerOrganiser(username));
        return organiser;
    }

    private static ModOrganiser getModOrganiser(Object mod)
    {
        ModOrganiser organiser = modOrganisers.get(mod);
        if (organiser == null) {
            ModContainer container = mods.get(mod);
            if (container == null)
                throw new NullPointerException("Mod not registered with chickenchunks: " + mod);
            modOrganisers.put(mod, organiser = new ModOrganiser(mod, container));
        }
        return organiser;
    }

    public static void serverShutdown()
    {
        loaded = false;
    }

    public static void save(WorldServer world)
    {
        try {
            if (PlayerOrganiser.dirty) {
                File saveFile = new File(saveDir, "players.dat");
                if (!saveFile.exists())
                    saveFile.createNewFile();
                DataOutputStream dataout = new DataOutputStream(new FileOutputStream(saveFile));
                dataout.writeInt(playerOrganisers.size());
                for (PlayerOrganiser organiser : playerOrganisers.values()) {
                    dataout.writeUTF(organiser.username);
                    organiser.save(dataout);
                }
                dataout.close();
                PlayerOrganiser.dirty = false;
            }

            for (ModOrganiser organiser : modOrganisers.values()) {
                if (organiser.dirty) {
                    File saveFile = new File(saveDir, organiser.container.getModId() + ".dat");
                    if (!saveFile.exists())
                        saveFile.createNewFile();

                    DataOutputStream dataout = new DataOutputStream(new FileOutputStream(saveFile));
                    organiser.save(dataout);
                    dataout.close();
                    organiser.dirty = false;
                }
            }
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    public static void cleanChunks(WorldServer world) {
        int dim = CommonUtils.getDimension(world);
        int viewdist = ServerUtils.mc().getPlayerList().getViewDistance();

        HashSet<ChunkCoordIntPair> loadedChunks = new HashSet<>();
        for (EntityPlayer player : ServerUtils.getPlayersInDimension(dim)) {
            int playerChunkX = (int) player.posX >> 4;
            int playerChunkZ = (int) player.posZ >> 4;

            for (int cx = playerChunkX - viewdist; cx <= playerChunkX + viewdist; cx++)
                for (int cz = playerChunkZ - viewdist; cz <= playerChunkZ + viewdist; cz++)
                    loadedChunks.add(new ChunkCoordIntPair(cx, cz));
        }

        ImmutableSetMultimap<ChunkCoordIntPair, Ticket> persistantChunks = world.getPersistentChunks();
        PlayerManager manager = world.getPlayerChunkMap();//.getPlayerManager();

        for (Chunk chunk : world.getChunkProvider().loadedChunks)
        {
            ChunkCoordIntPair coord = chunk.getChunkCoordIntPair();
            if (!loadedChunks.contains(coord) && !persistantChunks.containsKey(coord) && world.getChunkProvider().chunkExists(coord.chunkXPos, coord.chunkZPos))
            {
                PlayerManager.PlayerInstance instance = manager.getEntry(coord.chunkXPos, coord.chunkZPos);//.getOrCreateChunkWatcher(coord.chunkXPos, coord.chunkZPos, false);
                if (instance == null)
                {
                    world.getChunkProvider().dropChunk(coord.chunkXPos, coord.chunkZPos);
                }
                else
                {
                    while (instance.players.size() > 0)
                        instance.removePlayer(instance.players.get(0));
                }
            }
        }

        if (ServerUtils.getPlayersInDimension(dim).isEmpty() && world.getPersistentChunks().isEmpty() && !world.provider.getDimensionType().shouldLoadSpawn())
        {
            DimensionManager.unloadWorld(dim);
        }
    }

    public static void tickEnd(WorldServer world) {
        if (world.getWorldTime() % 1200 == 0)
            updateLoginTimes();

        if (cleanupTicks > 0 && world.getWorldTime() % cleanupTicks == 0)
            cleanChunks(world);

        tickDownUnloads();
        revivePlayerLoaders();
    }

    private static void updateLoginTimes()
    {
        long time = System.currentTimeMillis();
        for (EntityPlayer player : ServerUtils.getPlayers())
            loginTimes.put(player.getName(), time);

        try {
            File saveFile = new File(saveDir, "loginTimes.dat");
            if (!saveFile.exists())
                saveFile.createNewFile();

            DataOutputStream dataout = new DataOutputStream(new FileOutputStream(saveFile));
            dataout.writeInt(loginTimes.size());
            for (Map.Entry<String, Long> entry : loginTimes.entrySet()) {
                dataout.writeUTF(entry.getKey());
                dataout.writeLong(entry.getValue());
            }
            dataout.close();
        } catch (Exception e) {
            throw new RuntimeException(e);
        }

        for (PlayerOrganiser organiser : playerOrganisers.values())
            if (!organiser.isDormant() && !loggedInRecently(organiser.username))
                ReviveChange.PlayerDevive.list.add(organiser);
    }

    private static void tickDownUnloads()
    {
        for (Map.Entry<String, PlayerOrganiser> entry : playerOrganisers.entrySet())
            entry.getValue().tickDownUnloads();

        for (Map.Entry<Object, ModOrganiser> entry : modOrganisers.entrySet())
            entry.getValue().tickDownUnloads();
    }

    private static void revivePlayerLoaders()
    {
        for (Object organiser : ReviveChange.PlayerRevive.list)
            ((PlayerOrganiser) organiser).revive();
        ReviveChange.PlayerRevive.list.clear();

        for (Object organiser : ReviveChange.ModRevive.list)
            ((ModOrganiser) organiser).revive();
        ReviveChange.ModRevive.list.clear();

        for (Object world : ReviveChange.DimensionRevive.list)
            for (PlayerOrganiser organiser : playerOrganisers.values())
                organiser.revive((World) world);
        ReviveChange.DimensionRevive.list.clear();

        for (Object organiser : ReviveChange.PlayerDevive.list)
            ((PlayerOrganiser) organiser).devive();
        ReviveChange.PlayerDevive.list.clear();
    }

    public static void playerLogin(String username)
    {
        loginTimes.put(username, System.currentTimeMillis());
        ReviveChange.PlayerRevive.list.add(getPlayerOrganiser(username));
    }

    public static void playerLogout(String username)
    {
        if (!allowOffline(username))
            ReviveChange.PlayerDevive.list.add(getPlayerOrganiser(username));
    }

    public static int maxChunksPerLoader()
    {
        return maxChunks;
    }

    public static boolean opInteract()
    {
        return opInteract;
    }

    public static void unloadWorld(World world) {
        int dim = CommonUtils.getDimension(world);
        for (TicketManager mgr : playerOrganisers.values())
            mgr.unloadDimension(dim);
        for (TicketManager mgr : modOrganisers.values())
            mgr.unloadDimension(dim);
    }
}


text:
Once we reach version 1.0, TensorFlow will follow Semantic Versioning 2.0
(semver). For details, see <http://semver.org>.  Each release version of
TensorFlow has the form `MAJOR.MINOR.PATCH`.  Changes to the each number have
the following meaning:

* **MAJOR**:  Backwards incompatible changes.  Code and data that worked with
  a previous major release will not necessarily work with a new release.
  However, in some cases existing TensorFlow data (graphs, checkpoints, and
  other protobufs) may be migratable to the newer release; see below for details
  on data compatibility.

* **MINOR**: Backwards compatible features, speed improvements, etc.  Code and
  data that worked with a previous minor release *and* which depends only the
  public API will continue to work unchanged.  For details on what is and is
  not the public API, see below.

* **PATCH**: Backwards compatible bug fixes.

Before 1.0, semver allows backwards incompatible changes at any time.  However,
to support users now, we will use the format `0.MAJOR.MINOR` (shifted one step
to the right).  Thus 0.5.0 to 0.6.0 may be backwards incompatible, but 0.6.0 to
0.6.1 will include only backwards compatible features and bug fixes.

At some point (especially as we approach 1.0) we will likely use prerelease
versions such as X.Y.Z-alpha.1, but we do not yet have specific plans (beyond
the restrictions of semver).

## Public API

Only the public API of TensorFlow is backwards compatible across minor and patch
versions.  The public API consists of

* The documented [C++ and Python APIs](../api_docs).

* The following protocol buffer files:
  [`attr_value`](https://github.com/tensorflow/tensorflow/blob/master/tensorflow/core/framework/attr_value.proto),
  [`config`](https://github.com/tensorflow/tensorflow/blob/master/tensorflow/core/protobuf/config.proto),
  [`event`](https://github.com/tensorflow/tensorflow/blob/master/tensorflow/core/util/event.proto),
  [`graph`](https://github.com/tensorflow/tensorflow/blob/master/tensorflow/core/framework/graph.proto),
  [`op_def`](https://github.com/tensorflow/tensorflow/blob/master/tensorflow/core/framework/op_def.proto),
  [`reader_base`](https://github.com/tensorflow/tensorflow/blob/master/tensorflow/core/kernels/reader_base.proto),
  [`summary`](https://github.com/tensorflow/tensorflow/blob/master/tensorflow/core/framework/summary.proto),
  [`tensor`](https://github.com/tensorflow/tensorflow/blob/master/tensorflow/core/framework/tensor.proto),
  [`tensor_shape`](https://github.com/tensorflow/tensorflow/blob/master/tensorflow/core/framework/tensor_shape.proto),
  and [`types`](https://github.com/tensorflow/tensorflow/blob/master/tensorflow/core/framework/types.proto).

The public C++ API is exposed through the header files in
[`tensorflow/core/public`](https://github.com/tensorflow/tensorflow/tree/master/tensorflow/core/public).
The public Python API is unfortunately **not** everything available through the
tensorflow python module and its submodules, since we do not yet use `__all__`
everywhere ([#421](https://github.com/tensorflow/tensorflow/issues/421)).
 Please refer to the documentation to determine whether a given Python feature
is part of the public API. For now, the protocol buffers are defined in
[`tensorflow/core/framework/*.proto`](https://github.com/tensorflow/tensorflow/tree/master/tensorflow/core/framework)
([#484](https://github.com/tensorflow/tensorflow/issues/484)).

## Details That Are Not Public

The following are specifically **not** part of the public API: they are allowed
to change without notice across minor releases and even patch releases if bug
fixes require it:

* **Details of composite ops:**  Many public functions in Python expand to
  several primitive ops in the graph, and these details will be part of any
  graphs saved to disk as GraphDefs.  These details are allowed to change for
  minor releases. In particular, regressions tests that check for exact
  matching between graphs are likely to break across minor releases, even though
  the behavior of the graph should be unchanged and existing checkpoints will
  still work.

* **Floating point numerical details:** The specific floating point values
  computed by ops may change at any time: users should rely only on approximate
  accuracy and numerical stability, not on the specific bits computed.  Changes
  to numerical formulas in minor and patch releases should result in comparable
  or improved accuracy, with the caveat that in machine learning improved
  accuracy of specific formulas may result in worse accuracy for the overall
  system.

* **Random numbers:** The specific random numbers computed by the [random
  ops](../api_docs/python/constant_op.html#random-tensors) may change at any
  time: users should rely only on approximately correct distributions and
  statistical strength, not the specific bits computed.  However, we will make
  changes to random bits rarely and ideally never for patch releases, and all
  such intended changes will be documented.

## Compatibility for Graphs and Checkpoints {#graphs}

Many users of TensorFlow will be saving graphs and trained models to disk for
later evaluation or more training, often changing versions of TensorFlow in the
process.  First, following semver, any graph or checkpoint written out with one
version of TensorFlow can be loaded and evaluated with a later version of
TensorFlow with the same major release.  However, we will endeavour to preserve
backwards compatibility even across major releases when possible, so that the
serialized files are usable over long periods of time.

There are two main classes of saved TensorFlow data: graphs and checkpoints.
Graphs describe the data flow graphs of ops to be run during training and
inference, and checkpoints contain the saved tensor values of variables in a
graph.

Graphs are serialized via the `GraphDef` protocol buffer.  To facilitate (rare)
backwards incompatible changes to graphs, each `GraphDef` has an integer version
separate from the TensorFlow version.  The semantics are:

* Each version of TensorFlow supports an interval of `GraphDef` versions.  This
  interval with be constant across patch releases, and will only grow across
  minor releases.  Dropping support for a `GraphDef` version will only occur
  for a major release of TensorFlow.

* Newly created graphs use the newest `GraphDef` version.

* If a given version of TensorFlow supports the `GraphDef` version of a graph,
  it will load and evaluate with the same behavior as when it was written out
  (except for floating point numerical details and random numbers), regardless
  of the major version of TensorFlow.  In particular, all checkpoint files will
  be compatible.

* If the `GraphDef` upper bound is increased to X in a (minor) release, there
  will be at least six months before the lower bound is increased to X.

For example (numbers and versions hypothetical), TensorFlow 1.2 might support
`GraphDef` versions 4 to 7.  TensorFlow 1.3 could add `GraphDef` version 8 and
support versions 4 to 8.  At least six months later, TensorFlow 2.0.0 could drop
support for versions 4 to 7, leaving version 8 only.

Finally, when support for a `GraphDef` version is dropped, we will attempt to
provide tools for automatically converting graphs to a newer supported
`GraphDef` version.

## C++ API Compatibility

Only patch releases will be binary compatible at the C++ level.  That is, minor
releases are backwards compatible in terms of behavior but may require a
recompile for downstream C++ code.  As always, backwards compatibility is only
provided for the public C++ API.

redpajama_set_name: RedPajamaGithub


text:
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle 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.
//
// Moodle 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 Moodle.  If not, see <http://www.gnu.org/licenses/>.

define('CLI_SCRIPT', true);

require(__DIR__.'/../../config.php');
require_once($CFG->dirroot . '/mod/chat/lib.php');

// Browser quirks.
define('QUIRK_CHUNK_UPDATE', 0x0001);

// Connection telltale.
define('CHAT_CONNECTION',           0x10);
// Connections: Incrementing sequence, 0x10 to 0x1f.
define('CHAT_CONNECTION_CHANNEL',   0x11);

// Sidekick telltale.
define('CHAT_SIDEKICK',             0x20);
// Sidekicks: Incrementing sequence, 0x21 to 0x2f.
define('CHAT_SIDEKICK_USERS',       0x21);
define('CHAT_SIDEKICK_MESSAGE',     0x22);
define('CHAT_SIDEKICK_BEEP',        0x23);

$phpversion = phpversion();
echo 'Moodle chat daemon v1.0 on PHP '.$phpversion."\n\n";

// Set up all the variables we need.

// The $CFG variables are now defined in database by chat/lib.php.

$_SERVER['PHP_SELF']        = 'dummy';
$_SERVER['SERVER_NAME']     = 'dummy';
$_SERVER['HTTP_USER_AGENT'] = 'dummy';

$_SERVER['SERVER_NAME'] = $CFG->chat_serverhost;
$_SERVER['PHP_SELF']    = "http://$CFG->chat_serverhost:$CFG->chat_serverport/mod/chat/chatd.php";

core_php_time_limit::raise(0);
error_reporting(E_ALL);

function chat_empty_connection() {
    return array('sid' => null, 'handle' => null, 'ip' => null, 'port' => null, 'groupid' => null);
}

class ChatConnection {
    // Chat-related info.
    public $sid  = null;
    public $type = null;

    // PHP-level info.
    public $handle = null;

    // TCP/IP.
    public $ip     = null;
    public $port   = null;

    public function __construct($resource) {
        $this->handle = $resource;
        @socket_getpeername($this->handle, $this->ip, $this->port);
    }
}

class ChatDaemon {
    public $_resetsocket       = false;
    public $_readytogo         = false;
    public $_logfile           = false;
    public $_trace_to_console  = true;
    public $_trace_to_stdout   = true;
    public $_logfile_name      = 'chatd.log';
    public $_last_idle_poll    = 0;

    public $connectionsunidentified  = array(); // Connections not identified yet.
    public $connectionsside = array(); // Sessions with sidekicks waiting for the main connection to be processed.
    public $connectionshalf = array(); // Sessions that have valid connections but not all of them.
    public $connectionssets = array(); // Sessions with complete connection sets.
    public $setsinfo = array(); // Keyed by sessionid exactly like conn_sets, one of these for each of those.
    public $chatrooms = array(); // Keyed by chatid, holding arrays of data.

    // IMPORTANT: $connectionssets, $setsinfo and $chatrooms must remain synchronized!
    //            Pay extra attention when you write code that affects any of them!

    public function __construct() {
        $this->_trace_level         = E_ALL ^ E_USER_NOTICE;
        $this->_pcntl_exists        = function_exists('pcntl_fork');
        $this->_time_rest_socket    = 20;
        $this->_beepsoundsrc        = $GLOBALS['CFG']->wwwroot.'/mod/chat/beep.wav';
        $this->_freq_update_records = 20;
        $this->_freq_poll_idle_chat = $GLOBALS['CFG']->chat_old_ping;
        $this->_stdout = fopen('php://stdout', 'w');
        if ($this->_stdout) {
            // Avoid double traces for everything.
            $this->_trace_to_console = false;
        }
    }

    public function error_handler ($errno, $errmsg, $filename, $linenum, $vars) {
        // Checks if an error needs to be suppressed due to @.
        if (error_reporting() != 0) {
            $this->trace($errmsg.' on line '.$linenum, $errno);
        }
        return true;
    }

    public function poll_idle_chats($now) {
        $this->trace('Polling chats to detect disconnected users');
        if (!empty($this->chatrooms)) {
            foreach ($this->chatrooms as $chatid => $chatroom) {
                if (!empty($chatroom['users'])) {
                    foreach ($chatroom['users'] as $sessionid => $userid) {
                        // We will be polling each user as required.
                        $this->trace('...shall we poll '.$sessionid.'?');
                        if (!empty($this->sets_info[$sessionid]) && isset($this->sets_info[$sessionid]['chatuser']) &&
                                // Having tried to exclude race conditions as already done in user_lazy_update()
                                // please do the real job by checking the last poll.
                                ($this->sets_info[$sessionid]['chatuser']->lastmessageping < $this->_last_idle_poll)) {
                            $this->trace('YES!');
                            // This user hasn't been polled since his last message.
                            $result = $this->write_data($this->conn_sets[$sessionid][CHAT_CONNECTION_CHANNEL], '');
                            if ($result === false) {
                                // User appears to have disconnected.
                                $this->disconnect_session($sessionid);
                            }
                        }
                    }
                }
            }
        }
        $this->_last_idle_poll = $now;
    }

    public function query_start() {
        return $this->_readytogo;
    }

    public function trace($message, $level = E_USER_NOTICE) {
        $severity = '';

        switch($level) {
            case E_USER_WARNING:
                $severity = '*IMPORTANT* ';
                break;
            case E_USER_ERROR:
                $severity = ' *CRITICAL* ';
                break;
            case E_NOTICE:
            case E_WARNING:
                $severity = ' *CRITICAL* [php] ';
                break;
        }

        $date = date('[Y-m-d H:i:s] ');
        $message = $date.$severity.$message."\n";

        if ($this->_trace_level & $level) {
            // It is accepted for output.

            // Error-class traces go to STDERR too.
            if ($level & E_USER_ERROR) {
                fwrite(STDERR, $message);
            }

            // Emit the message to wherever we should.
            if ($this->_trace_to_stdout) {
                fwrite($this->_stdout, $message);
                fflush($this->_stdout);
            }
            if ($this->_trace_to_console) {
                echo $message;
                flush();
            }
            if ($this->_logfile) {
                fwrite($this->_logfile, $message);
                fflush($this->_logfile);
            }
        }
    }

    public function write_data($connection, $text) {
        $written = @socket_write($connection, $text, strlen($text));
        if ($written === false) {
            return false;
        }
        return true;
    }

    public function user_lazy_update($sessionid) {
        global $DB;

        if (empty($this->sets_info[$sessionid])) {
            $this->trace('user_lazy_update() called for an invalid SID: '.$sessionid, E_USER_WARNING);
            return false;
        }

        // Does promote_final() already finish its job?
        if (!isset($this->sets_info[$sessionid]['lastinfocommit'])) {
            return false;
        }

        $now = time();

        // We 'll be cheating a little, and NOT updating the record data as
        // often as we can, so that we save on DB queries (imagine MANY users).
        if ($now - $this->sets_info[$sessionid]['lastinfocommit'] > $this->_freq_update_records) {
            // Commit to permanent storage.
            $this->sets_info[$sessionid]['lastinfocommit'] = $now;
            $DB->update_record('chat_users', $this->sets_info[$sessionid]['chatuser']);
        }
        return true;
    }

    public function get_user_window($sessionid) {
        global $CFG, $OUTPUT;

        static $str;

        $info = &$this->sets_info[$sessionid];

        $timenow = time();

        if (empty($str)) {
            $str = new stdClass();
            $str->idle  = get_string("idle", "chat");
            $str->beep  = get_string("beep", "chat");
            $str->day   = get_string("day");
            $str->days  = get_string("days");
            $str->hour  = get_string("hour");
            $str->hours = get_string("hours");
            $str->min   = get_string("min");
            $str->mins  = get_string("mins");
            $str->sec   = get_string("sec");
            $str->secs  = get_string("secs");
            $str->years = get_string('years');
        }

        ob_start();
        $refreshinval = $CFG->chat_refresh_userlist * 1000;
        echo <<<EOD

        <meta http-equiv="refresh" content="$refreshinval">

EOD;

        // Get the users from that chatroom.
        $users = $this->chatrooms[$info['chatid']]['users'];

        foreach ($users as $usersessionid => $userid) {
            // Fetch each user's sessionid and then the rest of his data from $this->sets_info.
            $userinfo = $this->sets_info[$usersessionid];

            $lastping = $timenow - $userinfo['chatuser']->lastmessageping;

            echo '
';

            $link = '/user/view.php?id='.$userinfo['user']->id.'&course='.$userinfo['courseid'];
            $anchortagcontents = $OUTPUT->user_picture($userinfo['user'], array('courseid' => $userinfo['courseid']));

            $action = new popup_action('click', $link, 'user'.$userinfo['chatuser']->id);
            $anchortag = $OUTPUT->action_link($link, $anchortagcontents, $action);

            echo $anchortag;
            echo "	";
            echo "
";
            echo fullname($userinfo['user'])."
";
            echo "$str->idle: ".format_time($lastping, $str)." ";
            echo 'chat_serverhost.':'.$CFG->chat_serverport.
                 '/?win=beep&beep='.$userinfo['user']->id.
                 '&chat_sid='.$sessionid.'">'.$str->beep."\n";
            echo "
";
            echo "
";
        }

        echo '

';
        echo "\n\n";

        return ob_get_clean();

    }

    public function new_ufo_id() {
        static $id = 0;
        if ($id++ === 0x1000000) { // Cycling very very slowly to prevent overflow.
            $id = 0;
        }
        return $id;
    }

    public function process_sidekicks($sessionid) {
        if (empty($this->conn_side[$sessionid])) {
            return true;
        }
        foreach ($this->conn_side[$sessionid] as $sideid => $sidekick) {
            // TODO: is this late-dispatch working correctly?
            $this->dispatch_sidekick($sidekick['handle'], $sidekick['type'], $sessionid, $sidekick['customdata']);
            unset($this->conn_side[$sessionid][$sideid]);
        }
        return true;
    }

    public function dispatch_sidekick($handle, $type, $sessionid, $customdata) {
        global $CFG, $DB;

        switch($type) {
            case CHAT_SIDEKICK_BEEP:

                // Incoming beep.
                $msg = new stdClass;
                $msg->chatid    = $this->sets_info[$sessionid]['chatid'];
                $msg->userid    = $this->sets_info[$sessionid]['userid'];
                $msg->groupid   = $this->sets_info[$sessionid]['groupid'];
                $msg->system    = 0;
                $msg->message   = 'beep '.$customdata['beep'];
                $msg->timestamp = time();

                // Commit to DB.
                chat_send_chatmessage($this->sets_info[$sessionid]['chatuser'], $msg->message, false,
                    $this->sets_info[$sessionid]['cm']);

                // OK, now push it out to all users.
                $this->message_broadcast($msg, $this->sets_info[$sessionid]['user']);

                // Update that user's lastmessageping.
                $this->sets_info[$sessionid]['chatuser']->lastping        = $msg->timestamp;
                $this->sets_info[$sessionid]['chatuser']->lastmessageping = $msg->timestamp;
                $this->user_lazy_update($sessionid);

                // We did our work, but before slamming the door on the poor browser
                // show the courtesy of responding to the HTTP request. Otherwise, some
                // browsers decide to get vengeance by flooding us with repeat requests.

                $header  = "HTTP/1.1 200 OK\n";
                $header .= "Connection: close\n";
                $header .= "Date: ".date('r')."\n";
                $header .= "Server: Moodle\n";
                $header .= "Content-Type: text/html; charset=utf-8\n";
                $header .= "Last-Modified: ".gmdate("D, d M Y H:i:s")." GMT\n";
                $header .= "Cache-Control: no-cache, must-revalidate\n";
                $header .= "Expires: Wed, 4 Oct 1978 09:32:45 GMT\n";
                $header .= "\n";

                // That's enough headers for one lousy dummy response.
                $this->write_data($handle, $header);
                // All done.
            break;

            case CHAT_SIDEKICK_USERS:
                // A request to paint a user window.

                $content = $this->get_user_window($sessionid);

                $header  = "HTTP/1.1 200 OK\n";
                $header .= "Connection: close\n";
                $header .= "Date: ".date('r')."\n";
                $header .= "Server: Moodle\n";
                $header .= "Content-Type: text/html; charset=utf-8\n";
                $header .= "Last-Modified: ".gmdate("D, d M Y H:i:s")." GMT\n";
                $header .= "Cache-Control: no-cache, must-revalidate\n";
                $header .= "Expires: Wed, 4 Oct 1978 09:32:45 GMT\n";
                $header .= "Content-Length: ".strlen($content)."\n";

                // The refresh value is 2 seconds higher than the configuration variable.
                // This is because we are doing JS refreshes all the time.
                // However, if the JS doesn't work for some reason, we still want to refresh once in a while.
                $header .= "Refresh: ".(intval($CFG->chat_refresh_userlist) + 2).
                           "; url=http://$CFG->chat_serverhost:$CFG->chat_serverport/?win=users&".
                           "chat_sid=".$sessionid."\n";
                $header .= "\n";

                // That's enough headers for one lousy dummy response.
                $this->trace('writing users http response to handle '.$handle);
                $this->write_data($handle, $header . $content);

                // Update that user's lastping.
                $this->sets_info[$sessionid]['chatuser']->lastping = time();
                $this->user_lazy_update($sessionid);

            break;

            case CHAT_SIDEKICK_MESSAGE:
                // Incoming message.

                // Browser stupidity protection from duplicate messages.
                $messageindex = intval($customdata['index']);

                if ($this->sets_info[$sessionid]['lastmessageindex'] >= $messageindex) {
                    // We have already broadcasted that!
                    break;
                } else {
                    // Update our info.
                    $this->sets_info[$sessionid]['lastmessageindex'] = $messageindex;
                }

                $msg = new stdClass;
                $msg->chatid    = $this->sets_info[$sessionid]['chatid'];
                $msg->userid    = $this->sets_info[$sessionid]['userid'];
                $msg->groupid   = $this->sets_info[$sessionid]['groupid'];
                $msg->system    = 0;
                $msg->message   = urldecode($customdata['message']); // Have to undo the browser's encoding.
                $msg->timestamp = time();

                if (empty($msg->message)) {
                    // Someone just hit ENTER, send them on their way.
                    break;
                }

                // A slight hack to prevent malformed SQL inserts.
                $origmsg = $msg->message;
                $msg->message = $msg->message;

                // Commit to DB.
                chat_send_chatmessage($this->sets_info[$sessionid]['chatuser'], $msg->message, false,
                    $this->sets_info[$sessionid]['cm']);

                // Undo the hack.
                $msg->message = $origmsg;

                // OK, now push it out to all users.
                $this->message_broadcast($msg, $this->sets_info[$sessionid]['user']);

                // Update that user's lastmessageping.
                $this->sets_info[$sessionid]['chatuser']->lastping        = $msg->timestamp;
                $this->sets_info[$sessionid]['chatuser']->lastmessageping = $msg->timestamp;
                $this->user_lazy_update($sessionid);

                // We did our work, but before slamming the door on the poor browser
                // show the courtesy of responding to the HTTP request. Otherwise, some
                // browsers decide to get vengeance by flooding us with repeat requests.

                $header  = "HTTP/1.1 200 OK\n";
                $header .= "Connection: close\n";
                $header .= "Date: ".date('r')."\n";
                $header .= "Server: Moodle\n";
                $header .= "Content-Type: text/html; charset=utf-8\n";
                $header .= "Last-Modified: ".gmdate("D, d M Y H:i:s")." GMT\n";
                $header .= "Cache-Control: no-cache, must-revalidate\n";
                $header .= "Expires: Wed, 4 Oct 1978 09:32:45 GMT\n";
                $header .= "\n";

                // That's enough headers for one lousy dummy response.
                $this->write_data($handle, $header);

                // All done.
            break;
        }

        socket_shutdown($handle);
        socket_close($handle);
    }

    public function promote_final($sessionid, $customdata) {
        global $DB;

        if (isset($this->conn_sets[$sessionid])) {
            $this->trace('Set cannot be finalized: Session '.$sessionid.' is already active');
            return false;
        }

        $chatuser = $DB->get_record('chat_users', array('sid' => $sessionid));
        if ($chatuser === false) {
            $this->dismiss_half($sessionid);
            return false;
        }
        $chat = $DB->get_record('chat', array('id' => $chatuser->chatid));
        if ($chat === false) {
            $this->dismiss_half($sessionid);
            return false;
        }
        $user = $DB->get_record('user', array('id' => $chatuser->userid));
        if ($user === false) {
            $this->dismiss_half($sessionid);
            return false;
        }
        $course = $DB->get_record('course', array('id' => $chat->course));
        if ($course === false) {
            $this->dismiss_half($sessionid);
            return false;
        }
        if (!($cm = get_coursemodule_from_instance('chat', $chat->id, $course->id))) {
            $this->dismiss_half($sessionid);
            return false;
        }

        global $CHAT_HTMLHEAD_JS;

        $this->conn_sets[$sessionid] = $this->conn_half[$sessionid];

        // This whole thing needs to be purged of redundant info, and the
        // code base to follow suit. But AFTER development is done.
        $this->sets_info[$sessionid] = array(
            'lastinfocommit' => 0,
            'lastmessageindex' => 0,
            'course'    => $course,
            'courseid'  => $course->id,
            'chatuser'  => $chatuser,
            'chatid'    => $chat->id,
            'cm'        => $cm,
            'user'      => $user,
            'userid'    => $user->id,
            'groupid'   => $chatuser->groupid,
            'lang'      => $chatuser->lang,
            'quirks'    => $customdata['quirks']
        );

        // If we know nothing about this chatroom, initialize it and add the user.
        if (!isset($this->chatrooms[$chat->id]['users'])) {
            $this->chatrooms[$chat->id]['users'] = array($sessionid => $user->id);
        } else {
            // Otherwise just add the user.
            $this->chatrooms[$chat->id]['users'][$sessionid] = $user->id;
        }

        $header  = "HTTP/1.1 200 OK\n";
        $header .= "Connection: close\n";
        $header .= "Date: ".date('r')."\n";
        $header .= "Server: Moodle\n";
        $header .= "Content-Type: text/html; charset=utf-8\n";
        $header .= "Last-Modified: ".gmdate("D, d M Y H:i:s")." GMT\n";
        $header .= "Cache-Control: no-cache, must-revalidate\n";
        $header .= "Expires: Wed, 4 Oct 1978 09:32:45 GMT\n";
        $header .= "\n";

        $this->dismiss_half($sessionid, false);
        $this->write_data($this->conn_sets[$sessionid][CHAT_CONNECTION_CHANNEL], $header . $CHAT_HTMLHEAD_JS);
        $this->trace('Connection accepted: '
                     .$this->conn_sets[$sessionid][CHAT_CONNECTION_CHANNEL]
                     .', SID: '
                     .$sessionid
                     .' UID: '
                     .$chatuser->userid
                     .' GID: '
                     .$chatuser->groupid, E_USER_WARNING);

        // Finally, broadcast the "entered the chat" message.

        $msg = new stdClass;
        $msg->chatid = $chatuser->chatid;
        $msg->userid = $chatuser->userid;
        $msg->groupid = $chatuser->groupid;
        $msg->system = 1;
        $msg->message = 'enter';
        $msg->timestamp = time();

        chat_send_chatmessage($chatuser, $msg->message, true);
        $this->message_broadcast($msg, $this->sets_info[$sessionid]['user']);

        return true;
    }

    public function promote_ufo($handle, $type, $sessionid, $customdata) {
        if (empty($this->conn_ufo)) {
            return false;
        }
        foreach ($this->conn_ufo as $id => $ufo) {
            if ($ufo->handle == $handle) {
                // OK, got the id of the UFO, but what is it?

                if ($type & CHAT_SIDEKICK) {
                    // Is the main connection ready?
                    if (isset($this->conn_sets[$sessionid])) {
                        // Yes, so dispatch this sidekick now and be done with it.
                        $this->dispatch_sidekick($handle, $type, $sessionid, $customdata);
                        $this->dismiss_ufo($handle, false);
                    } else {
                        // No, so put it in the waiting list.
                        $this->trace('sidekick waiting');
                        $this->conn_side[$sessionid][] = array('type' => $type, 'handle' => $handle, 'customdata' => $customdata);
                    }
                    return true;
                }

                // If it's not a sidekick, at this point it can only be da man.

                if ($type & CHAT_CONNECTION) {
                    // This forces a new connection right now.
                    $this->trace('Incoming connection from '.$ufo->ip.':'.$ufo->port);

                    // Do we have such a connection active?
                    if (isset($this->conn_sets[$sessionid])) {
                        // Yes, so regrettably we cannot promote you.
                        $this->trace('Connection rejected: session '.$sessionid.' is already final');
                        $this->dismiss_ufo($handle, true, 'Your SID was rejected.');
                        return false;
                    }

                    // Join this with what we may have already.
                    $this->conn_half[$sessionid][$type] = $handle;

                    // Do the bookkeeping.
                    $this->promote_final($sessionid, $customdata);

                    // It's not a UFO anymore.
                    $this->dismiss_ufo($handle, false);

                    // Dispatch waiting sidekicks.
                    $this->process_sidekicks($sessionid);

                    return true;
                }
            }
        }
        return false;
    }

    public function dismiss_half($sessionid, $disconnect = true) {
        if (!isset($this->conn_half[$sessionid])) {
            return false;
        }
        if ($disconnect) {
            foreach ($this->conn_half[$sessionid] as $handle) {
                @socket_shutdown($handle);
                @socket_close($handle);
            }
        }
        unset($this->conn_half[$sessionid]);
        return true;
    }

    public function dismiss_set($sessionid) {
        if (!empty($this->conn_sets[$sessionid])) {
            foreach ($this->conn_sets[$sessionid] as $handle) {
                // Since we want to dismiss this, don't generate any errors if it's dead already.
                @socket_shutdown($handle);
                @socket_close($handle);
            }
        }
        $chatroom = $this->sets_info[$sessionid]['chatid'];
        $userid   = $this->sets_info[$sessionid]['userid'];
        unset($this->conn_sets[$sessionid]);
        unset($this->sets_info[$sessionid]);
        unset($this->chatrooms[$chatroom]['users'][$sessionid]);
        $this->trace('Removed all traces of user with session '.$sessionid, E_USER_NOTICE);
        return true;
    }

    public function dismiss_ufo($handle, $disconnect = true, $message = null) {
        if (empty($this->conn_ufo)) {
            return false;
        }
        foreach ($this->conn_ufo as $id => $ufo) {
            if ($ufo->handle == $handle) {
                unset($this->conn_ufo[$id]);
                if ($disconnect) {
                    if (!empty($message)) {
                        $this->write_data($handle, $message."\n\n");
                    }
                    socket_shutdown($handle);
                    socket_close($handle);
                }
                return true;
            }
        }
        return false;
    }

    public function conn_accept() {
        $readsocket = array($this->listen_socket);
        $write = null;
        $except = null;
        $changed = socket_select($readsocket, $write, $except, 0, 0);

        if (!$changed) {
            return false;
        }
        $handle = socket_accept($this->listen_socket);
        if (!$handle) {
            return false;
        }

        $newconn = new ChatConnection($handle);
        $id = $this->new_ufo_id();
        $this->conn_ufo[$id] = $newconn;
    }

    public function conn_activity_ufo(&$handles) {
        $monitor = array();
        if (!empty($this->conn_ufo)) {
            foreach ($this->conn_ufo as $ufoid => $ufo) {
                // Avoid socket_select() warnings by preventing the check over invalid resources.
                if (is_resource($ufo->handle)) {
                    $monitor[$ufoid] = $ufo->handle;
                } else {
                    $this->dismiss_ufo($ufo->handle, false);
                }
            }
        }

        if (empty($monitor)) {
            $handles = array();
            return 0;
        }

        $a = null;
        $b = null;
        $retval = socket_select($monitor, $a, $b, null);
        $handles = $monitor;

        return $retval;
    }

    public function message_broadcast($message, $sender) {

        if (empty($this->conn_sets)) {
            return true;
        }

        $now = time();

        // First of all, mark this chatroom as having had activity now.
        $this->chatrooms[$message->chatid]['lastactivity'] = $now;

        foreach ($this->sets_info as $sessionid => $info) {
            // We need to get handles from users that are in the same chatroom, same group.
            if ($info['chatid'] == $message->chatid &&
              ($info['groupid'] == $message->groupid || $message->groupid == 0)) {

                // Simply give them the message.
                $output = chat_format_message_manually($message, $info['courseid'], $sender, $info['user']);
                if ($output !== false) {
                    $this->trace('Delivering message "'.$output->text.'" to ' .
                        $this->conn_sets[$sessionid][CHAT_CONNECTION_CHANNEL]);

                    if ($output->beep) {
                        $this->write_data($this->conn_sets[$sessionid][CHAT_CONNECTION_CHANNEL],
                                          '<embed src="'.$this->_beepsoundsrc.'" autostart="true" hidden="true" />');
                    }

                    if ($info['quirks'] & QUIRK_CHUNK_UPDATE) {
                        $output->html .= $GLOBALS['CHAT_DUMMY_DATA'];
                        $output->html .= $GLOBALS['CHAT_DUMMY_DATA'];
                        $output->html .= $GLOBALS['CHAT_DUMMY_DATA'];
                    }

                    if (!$this->write_data($this->conn_sets[$sessionid][CHAT_CONNECTION_CHANNEL], $output->html)) {
                        $this->disconnect_session($sessionid);
                    }
                }
            }
        }
    }

    public function disconnect_session($sessionid) {
        global $DB;

        $info = $this->sets_info[$sessionid];

        $DB->delete_records('chat_users', array('sid' => $sessionid));
        $msg = new stdClass;
        $msg->chatid = $info['chatid'];
        $msg->userid = $info['userid'];
        $msg->groupid = $info['groupid'];
        $msg->system = 1;
        $msg->message = 'exit';
        $msg->timestamp = time();

        $this->trace('User has disconnected, destroying uid '.$info['userid'].' with SID '.$sessionid, E_USER_WARNING);
        chat_send_chatmessage($info['chatuser'], $msg->message, true);

        // IMPORTANT, kill him BEFORE broadcasting, otherwise we 'll get infinite recursion!
        $latesender = $info['user'];
        $this->dismiss_set($sessionid);
        $this->message_broadcast($msg, $latesender);
    }

    public function fatal($message) {
        $message .= "\n";
        if ($this->_logfile) {
            $this->trace($message, E_USER_ERROR);
        }
        echo "FATAL ERROR:: $message\n";
        die();
    }

    public function init_sockets() {
        global $CFG;

        $this->trace('Setting up sockets');

        if (false === ($this->listen_socket = socket_create(AF_INET, SOCK_STREAM, 0))) {
            // Failed to create socket.
            $lasterr = socket_last_error();
            $this->fatal('socket_create() failed: '. socket_strerror($lasterr).' ['.$lasterr.']');
        }

        if (!socket_bind($this->listen_socket, $CFG->chat_serverip, $CFG->chat_serverport)) {
            // Failed to bind socket.
            $lasterr = socket_last_error();
            $this->fatal('socket_bind() failed: '. socket_strerror($lasterr).' ['.$lasterr.']');
        }

        if (!socket_listen($this->listen_socket, $CFG->chat_servermax)) {
            // Failed to get socket to listen.
            $lasterr = socket_last_error();
            $this->fatal('socket_listen() failed: '. socket_strerror($lasterr).' ['.$lasterr.']');
        }

        // Socket has been initialized and is ready.
        $this->trace('Socket opened on port '.$CFG->chat_serverport);

        // What exactly does this do? http://www.unixguide.net/network/socketfaq/4.5.shtml is still not enlightening enough for me.
        socket_set_option($this->listen_socket, SOL_SOCKET, SO_REUSEADDR, 1);
        socket_set_nonblock($this->listen_socket);
    }

    public function cli_switch($switch, $param = null) {
        switch($switch) { // LOL!
            case 'reset':
                // Reset sockets.
                $this->_resetsocket = true;
                return false;
            case 'start':
                // Start the daemon.
                $this->_readytogo = true;
                return false;
            break;
            case 'v':
                // Verbose mode.
                $this->_trace_level = E_ALL;
                return false;
            break;
            case 'l':
                // Use logfile.
                if (!empty($param)) {
                    $this->_logfile_name = $param;
                }
                $this->_logfile = @fopen($this->_logfile_name, 'a+');
                if ($this->_logfile == false) {
                    $this->fatal('Failed to open '.$this->_logfile_name.' for writing');
                }
                return false;
            default:
                // Unrecognized.
                $this->fatal('Unrecognized command line switch: '.$switch);
            break;
        }
        return false;
    }

}

$daemon = new ChatDaemon;
set_error_handler(array($daemon, 'error_handler'));

// Check the parameters.

unset($argv[0]);
$commandline = implode(' ', $argv);
if (strpos($commandline, '-') === false) {
    if (!empty($commandline)) {
        // We cannot have received any meaningful parameters.
        $daemon->fatal('Garbage in command line');
    }
} else {
    // Parse command line.
    $switches = preg_split('/(-{1,2}[a-zA-Z]+) */', $commandline, -1, PREG_SPLIT_NO_EMPTY | PREG_SPLIT_DELIM_CAPTURE);

    // Taking advantage of the fact that $switches is indexed with incrementing numeric keys.
    // We will be using that to pass additional information to those switches who need it.
    $numswitches = count($switches);

    // Fancy way to give a "hyphen" boolean flag to each "switch".
    $switches = array_map(create_function('$x', 'return array("str" => $x, "hyphen" => (substr($x, 0, 1) == "-"));'), $switches);

    for ($i = 0; $i < $numswitches; ++$i) {

        $switch = $switches[$i]['str'];
        $params = ($i == $numswitches - 1 ? null :
                                            ($switches[$i + 1]['hyphen'] ? null : trim($switches[$i + 1]['str']))
                  );

        if (substr($switch, 0, 2) == '--') {
            // Double-hyphen switch.
            $daemon->cli_switch(strtolower(substr($switch, 2)), $params);
        } else if (substr($switch, 0, 1) == '-') {
            // Single-hyphen switch(es), may be more than one run together.
            $switch = substr($switch, 1); // Get rid of the "-".
            $len = strlen($switch);
            for ($j = 0; $j < $len; ++$j) {
                $daemon->cli_switch(strtolower(substr($switch, $j, 1)), $params);
            }
        }
    }
}

if (!$daemon->query_start()) {
    // For some reason we didn't start, so print out some info.
    echo 'Starts the Moodle chat socket server on port '.$CFG->chat_serverport;
    echo "\n\n";
    echo "Usage: chatd.php [parameters]\n\n";
    echo "Parameters:\n";
    echo "  --start         Starts the daemon\n";
    echo "  -v              Verbose mode (prints trivial information messages)\n";
    echo "  -l [logfile]    Log all messages to logfile (if not specified, chatd.log)\n";
    echo "Example:\n";
    echo "  chatd.php --start -l\n\n";
    die();
}

if (!function_exists('socket_set_option')) {
    echo "Error: Function socket_set_option() does not exist.\n";
    echo "Possibly PHP has not been compiled with --enable-sockets.\n\n";
    die();
}

$daemon->init_sockets();

$daemon->trace('Started Moodle chatd on port '.$CFG->chat_serverport.', listening socket '.$daemon->listen_socket, E_USER_WARNING);

// Clear the decks of old stuff.
$DB->delete_records('chat_users', array('version' => 'sockets'));

while (true) {
    $active = array();

    // First of all, let's see if any of our UFOs have identified itself.
    if ($daemon->conn_activity_ufo($active)) {
        foreach ($active as $handle) {
            $readsocket = array($handle);
            $write = null;
            $except = null;
            $changed = socket_select($readsocket, $write, $except, 0, 0);

            if ($changed > 0) {
                // Let's see what it has to say.

                $data = socket_read($handle, 2048); // Should be more than 512 to prevent empty pages and repeated messages!
                if (empty($data)) {
                    continue;
                }

                if (strlen($data) == 2048) { // If socket_read has more data, ignore all data.
                    $daemon->trace('UFO with '.$handle.': Data too long; connection closed', E_USER_WARNING);
                    $daemon->dismiss_ufo($handle, true, 'Data too long; connection closed');
                    continue;
                }

                // Ignore desktop browser fake "favorite icon" requests.
                if (strpos($data, 'GET /favicon.ico HTTP') === 0) {
                    // Known malformed data, drop it without any further notice.
                    continue;
                }

                if (!preg_match('/win=(chat|users|message|beep).*&chat_sid=([a-zA-Z0-9]*) HTTP/', $data, $info)) {
                    // Malformed data.
                    $daemon->trace('UFO with '.$handle.': Request with malformed data; connection closed', E_USER_WARNING);
                    $daemon->dismiss_ufo($handle, true, 'Request with malformed data; connection closed');
                    continue;
                }

                $type      = $info[1];
                $sessionid = $info[2];

                $customdata = array();

                switch($type) {
                    case 'chat':
                        $type = CHAT_CONNECTION_CHANNEL;
                        $customdata['quirks'] = 0;
                        if (strpos($data, 'Safari')) {
                            $daemon->trace('Safari identified...', E_USER_WARNING);
                            $customdata['quirks'] += QUIRK_CHUNK_UPDATE;
                        }
                    break;
                    case 'users':
                        $type = CHAT_SIDEKICK_USERS;
                    break;
                    case 'beep':
                        $type = CHAT_SIDEKICK_BEEP;
                        if (!preg_match('/beep=([^&]*)[& ]/', $data, $info)) {
                            $daemon->trace('Beep sidekick did not contain a valid userid', E_USER_WARNING);
                            $daemon->dismiss_ufo($handle, true, 'Request with malformed data; connection closed');
                            continue;
                        } else {
                            $customdata = array('beep' => intval($info[1]));
                        }
                    break;
                    case 'message':
                        $type = CHAT_SIDEKICK_MESSAGE;
                        if (!preg_match('/chat_message=([^&]*)[& ]chat_msgidnr=([^&]*)[& ]/', $data, $info)) {
                            $daemon->trace('Message sidekick did not contain a valid message', E_USER_WARNING);
                            $daemon->dismiss_ufo($handle, true, 'Request with malformed data; connection closed');
                            continue;
                        } else {
                            $customdata = array('message' => $info[1], 'index' => $info[2]);
                        }
                    break;
                    default:
                        $daemon->trace('UFO with '.$handle.': Request with unknown type; connection closed', E_USER_WARNING);
                        $daemon->dismiss_ufo($handle, true, 'Request with unknown type; connection closed');
                        continue;
                    break;
                }

                // OK, now we know it's something good. Promote it and pass it all the data it needs.
                $daemon->promote_ufo($handle, $type, $sessionid, $customdata);
                continue;
            }
        }
    }

    $now = time();

    // Clean up chatrooms with no activity as required.
    if ($now - $daemon->_last_idle_poll >= $daemon->_freq_poll_idle_chat) {
        $daemon->poll_idle_chats($now);
    }

    // Finally, accept new connections.
    $daemon->conn_accept();

    usleep($daemon->_time_rest_socket);
}

@socket_shutdown($daemon->listen_socket, 0);
die("\n\n-- terminated --\n");


text: <!DOCTYPE html>


text:
from __future__ import absolute_import, division, print_function
__metaclass__ = type

ANSIBLE_METADATA = {'metadata_version': '1.1',
                    'status': ['stableinterface'],
                    'supported_by': 'certified'}

DOCUMENTATION = r'''
---
module: bigip_vlan
short_description: Manage VLANs on a BIG-IP system
description:
  - Manage VLANs on a BIG-IP system
version_added: 2.2
options:
  description:
    description:
      - The description to give to the VLAN.
    type: str
  tagged_interfaces:
    description:
      - Specifies a list of tagged interfaces and trunks that you want to
        configure for the VLAN. Use tagged interfaces or trunks when
        you want to assign a single interface or trunk to multiple VLANs.
      - This parameter is mutually exclusive with the C(untagged_interfaces)
        and C(interfaces) parameters.
    type: list
    aliases:
      - tagged_interface
  untagged_interfaces:
    description:
      - Specifies a list of untagged interfaces and trunks that you want to
        configure for the VLAN.
      - This parameter is mutually exclusive with the C(tagged_interfaces)
        and C(interfaces) parameters.
    type: list
    aliases:
      - untagged_interface
  name:
    description:
      - The VLAN to manage. If the special VLAN C(ALL) is specified with
        the C(state) value of C(absent) then all VLANs will be removed.
    type: str
    required: True
  state:
    description:
      - The state of the VLAN on the system. When C(present), guarantees
        that the VLAN exists with the provided attributes. When C(absent),
        removes the VLAN from the system.
    type: str
    choices:
      - absent
      - present
    default: present
  tag:
    description:
      - Tag number for the VLAN. The tag number can be any integer between 1
        and 4094. The system automatically assigns a tag number if you do not
        specify a value.
    type: int
  mtu:
    description:
      - Specifies the maximum transmission unit (MTU) for traffic on this VLAN.
        When creating a new VLAN, if this parameter is not specified, the default
        value used will be C(1500).
      - This number must be between 576 to 9198.
    type: int
    version_added: 2.5
  cmp_hash:
    description:
      - Specifies how the traffic on the VLAN will be disaggregated. The value
        selected determines the traffic disaggregation method. You can choose to
        disaggregate traffic based on C(source-address) (the source IP address),
        C(destination-address) (destination IP address), or C(default), which
        specifies that the default CMP hash uses L4 ports.
      - When creating a new VLAN, if this parameter is not specified, the default
        of C(default) is used.
    type: str
    choices:
      - default
      - destination-address
      - source-address
      - dst-ip
      - src-ip
      - dest
      - destination
      - source
      - dst
      - src
    version_added: 2.5
  dag_tunnel:
    description:
      - Specifies how the disaggregator (DAG) distributes received tunnel-encapsulated
        packets to TMM instances. Select C(inner) to distribute packets based on information
        in inner headers. Select C(outer) to distribute packets based on information in
        outer headers without inspecting inner headers.
      - When creating a new VLAN, if this parameter is not specified, the default
        of C(outer) is used.
      - This parameter is not supported on Virtual Editions of BIG-IP.
    type: str
    choices:
      - inner
      - outer
    version_added: 2.5
  dag_round_robin:
    description:
      - Specifies whether some of the stateless traffic on the VLAN should be
        disaggregated in a round-robin order instead of using a static hash. The
        stateless traffic includes non-IP L2 traffic, ICMP, some UDP protocols,
        and so on.
      - When creating a new VLAN, if this parameter is not specified, the default
        of (no) is used.
    type: bool
    version_added: 2.5
  partition:
    description:
      - Device partition to manage resources on.
    type: str
    default: Common
    version_added: 2.5
  source_check:
    description:
      - When C(yes), specifies that the system verifies that the return route to an initial
        packet is the same VLAN from which the packet originated.
      - The system performs this verification only if the C(auto_last_hop) option is C(no).
    type: bool
    version_added: 2.8
  fail_safe:
    description:
      - When C(yes), specifies that the VLAN takes the specified C(fail_safe_action) if the
        system detects a loss of traffic on this VLAN's interfaces.
    type: bool
    version_added: 2.8
  fail_safe_timeout:
    description:
      - Specifies the number of seconds that a system can run without detecting network
        traffic on this VLAN before it takes the C(fail_safe_action).
    type: int
    version_added: 2.8
  fail_safe_action:
    description:
      - Specifies the action that the system takes when it does not detect any traffic on
        this VLAN, and the C(fail_safe_timeout) has expired.
    type: str
    choices:
      - reboot
      - restart-all
      - failover
    version_added: 2.8
  sflow_poll_interval:
    description:
      - Specifies the maximum interval in seconds between two pollings.
    type: int
    version_added: 2.8
  sflow_sampling_rate:
    description:
      - Specifies the ratio of packets observed to the samples generated.
    type: int
    version_added: 2.8
  interfaces:
    description:
      - Interfaces that you want added to the VLAN. This can include both tagged
        and untagged interfaces as the C(tagging) parameter specifies.
      - This parameter is mutually exclusive with the C(untagged_interfaces) and
        C(tagged_interfaces) parameters.
    suboptions:
      interface:
        description:
          - The name of the interface
        type: str
      tagging:
        description:
          - Whether the interface is C(tagged) or C(untagged).
        type: str
        choices:
          - tagged
          - untagged
    type: list
    version_added: 2.8
notes:
  - Requires BIG-IP versions >= 12.0.0
extends_documentation_fragment: f5
author:
  - Tim Rupp (@caphrim007)
  - Wojciech Wypior (@wojtek0806)
'''

EXAMPLES = r'''
- name: Create VLAN
  bigip_vlan:
    name: net1
    provider:
      password: secret
      server: lb.mydomain.com
      user: admin
  delegate_to: localhost

- name: Set VLAN tag
  bigip_vlan:
    name: net1
    tag: 2345
    provider:
      user: admin
      password: secret
      server: lb.mydomain.com
  delegate_to: localhost

- name: Add VLAN 2345 as tagged to interface 1.1
  bigip_vlan:
    tagged_interface: 1.1
    name: net1
    tag: 2345
    provider:
      password: secret
      server: lb.mydomain.com
      user: admin
  delegate_to: localhost

- name: Add VLAN 1234 as tagged to interfaces 1.1 and 1.2
  bigip_vlan:
    tagged_interfaces:
      - 1.1
      - 1.2
    name: net1
    tag: 1234
    provider:
      user: admin
      password: secret
      server: lb.mydomain.com
  delegate_to: localhost
'''

RETURN = r'''
description:
  description: The description set on the VLAN.
  returned: changed
  type: str
  sample: foo VLAN
interfaces:
  description: Interfaces that the VLAN is assigned to.
  returned: changed
  type: list
  sample: ['1.1','1.2']
partition:
  description: The partition that the VLAN was created on.
  returned: changed
  type: str
  sample: Common
tag:
  description: The ID of the VLAN.
  returned: changed
  type: int
  sample: 2345
cmp_hash:
  description: New traffic disaggregation method.
  returned: changed
  type: str
  sample: source-address
dag_tunnel:
  description: The new DAG tunnel setting.
  returned: changed
  type: str
  sample: outer
source_check:
  description: The new Source Check setting.
  returned: changed
  type: bool
  sample: yes
fail_safe:
  description: The new Fail Safe setting.
  returned: changed
  type: bool
  sample: no
fail_safe_timeout:
  description: The new Fail Safe Timeout setting.
  returned: changed
  type: int
  sample: 90
fail_safe_action:
  description: The new Fail Safe Action setting.
  returned: changed
  type: str
  sample: reboot
sflow_poll_interval:
  description: The new sFlow Polling Interval setting.
  returned: changed
  type: int
  sample: 10
sflow_sampling_rate:
  description: The new sFlow Sampling Rate setting.
  returned: changed
  type: int
  sample: 20
'''

from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils.basic import env_fallback

try:
    from library.module_utils.network.f5.bigip import F5RestClient
    from library.module_utils.network.f5.common import F5ModuleError
    from library.module_utils.network.f5.common import AnsibleF5Parameters
    from library.module_utils.network.f5.common import fq_name
    from library.module_utils.network.f5.common import f5_argument_spec
    from library.module_utils.network.f5.common import transform_name
    from library.module_utils.network.f5.common import flatten_boolean
    from library.module_utils.network.f5.compare import compare_complex_list
except ImportError:
    from ansible.module_utils.network.f5.bigip import F5RestClient
    from ansible.module_utils.network.f5.common import F5ModuleError
    from ansible.module_utils.network.f5.common import AnsibleF5Parameters
    from ansible.module_utils.network.f5.common import fq_name
    from ansible.module_utils.network.f5.common import f5_argument_spec
    from ansible.module_utils.network.f5.common import transform_name
    from ansible.module_utils.network.f5.common import flatten_boolean
    from ansible.module_utils.network.f5.compare import compare_complex_list

class Parameters(AnsibleF5Parameters):
    api_map = {
        'cmpHash': 'cmp_hash',
        'dagTunnel': 'dag_tunnel',
        'dagRoundRobin': 'dag_round_robin',
        'interfacesReference': 'interfaces',
        'sourceChecking': 'source_check',
        'failsafe': 'fail_safe',
        'failsafeAction': 'fail_safe_action',
        'failsafeTimeout': 'fail_safe_timeout',
    }

    api_attributes = [
        'description',
        'interfaces',
        'tag',
        'mtu',
        'cmpHash',
        'dagTunnel',
        'dagRoundRobin',
        'sourceChecking',
        'failsafe',
        'failsafeAction',
        'failsafeTimeout',
        'sflow',
    ]

    updatables = [
        'interfaces',
        'tagged_interfaces',
        'untagged_interfaces',
        'tag',
        'description',
        'mtu',
        'cmp_hash',
        'dag_tunnel',
        'dag_round_robin',
        'source_check',
        'fail_safe',
        'fail_safe_action',
        'fail_safe_timeout',
        'sflow_poll_interval',
        'sflow_sampling_rate',
        'sflow',
    ]

    returnables = [
        'description',
        'partition',
        'tag',
        'interfaces',
        'tagged_interfaces',
        'untagged_interfaces',
        'mtu',
        'cmp_hash',
        'dag_tunnel',
        'dag_round_robin',
        'source_check',
        'fail_safe',
        'fail_safe_action',
        'fail_safe_timeout',
        'sflow_poll_interval',
        'sflow_sampling_rate',
        'sflow',
    ]

    @property
    def source_check(self):
        return flatten_boolean(self._values['source_check'])

    @property
    def fail_safe(self):
        return flatten_boolean(self._values['fail_safe'])

class ApiParameters(Parameters):
    @property
    def interfaces(self):
        if self._values['interfaces'] is None:
            return None
        if 'items' not in self._values['interfaces']:
            return None
        result = []
        for item in self._values['interfaces']['items']:
            name = item['name']
            if 'tagged' in item:
                tagged = item['tagged']
                result.append(dict(name=name, tagged=tagged))
            if 'untagged' in item:
                untagged = item['untagged']
                result.append(dict(name=name, untagged=untagged))
        return result

    @property
    def tagged_interfaces(self):
        if self.interfaces is None:
            return None
        result = [str(x['name']) for x in self.interfaces if 'tagged' in x and x['tagged'] is True]
        result = sorted(result)
        return result

    @property
    def untagged_interfaces(self):
        if self.interfaces is None:
            return None
        result = [str(x['name']) for x in self.interfaces if 'untagged' in x and x['untagged'] is True]
        result = sorted(result)
        return result

    @property
    def sflow_poll_interval(self):
        try:
            return self._values['sflow']['pollInterval']
        except (KeyError, TypeError):
            return None

    @property
    def sflow_sampling_rate(self):
        try:
            return self._values['sflow']['samplingRate']
        except (KeyError, TypeError):
            return None

class ModuleParameters(Parameters):
    @property
    def interfaces(self):
        if self._values['interfaces'] is None:
            return None
        elif len(self._values['interfaces']) == 1 and self._values['interfaces'][0] in ['', 'none']:
            return ''
        result = []
        for item in self._values['interfaces']:
            if 'interface' not in item:
                raise F5ModuleError(
                    "An 'interface' key must be provided when specifying a list of interfaces."
                )
            if 'tagging' not in item:
                raise F5ModuleError(
                    "A 'tagging' key must be provided when specifying a list of interfaces."
                )
            name = str(item['interface'])
            tagging = item['tagging']

            if tagging == 'tagged':
                result.append(dict(name=name, tagged=True))
            else:
                result.append(dict(name=name, untagged=True))
        return result

    @property
    def untagged_interfaces(self):
        if self._values['untagged_interfaces'] is None:
            return None
        if self._values['untagged_interfaces'] is None:
            return None
        if len(self._values['untagged_interfaces']) == 1 and self._values['untagged_interfaces'][0] == '':
            return ''
        result = sorted([str(x) for x in self._values['untagged_interfaces']])
        return result

    @property
    def tagged_interfaces(self):
        if self._values['tagged_interfaces'] is None:
            return None
        if self._values['tagged_interfaces'] is None:
            return None
        if len(self._values['tagged_interfaces']) == 1 and self._values['tagged_interfaces'][0] == '':
            return ''
        result = sorted([str(x) for x in self._values['tagged_interfaces']])
        return result

    @property
    def mtu(self):
        if self._values['mtu'] is None:
            return None
        if int(self._values['mtu']) < 576 or int(self._values['mtu']) > 9198:
            raise F5ModuleError(
                "The mtu value must be between 576 - 9198"
            )
        return int(self._values['mtu'])

    @property
    def cmp_hash(self):
        if self._values['cmp_hash'] is None:
            return None
        if self._values['cmp_hash'] in ['source-address', 'src', 'src-ip', 'source']:
            return 'src-ip'
        if self._values['cmp_hash'] in ['destination-address', 'dest', 'dst-ip', 'destination', 'dst']:
            return 'dst-ip'
        else:
            return 'default'

    @property
    def dag_round_robin(self):
        if self._values['dag_round_robin'] is None:
            return None
        if self._values['dag_round_robin'] is True:
            return 'enabled'
        else:
            return 'disabled'

class Changes(Parameters):
    def to_return(self):
        result = {}
        try:
            for returnable in self.returnables:
                result[returnable] = getattr(self, returnable)
            result = self._filter_params(result)
        except Exception:
            pass
        return result

class UsableChanges(Changes):
    @property
    def source_check(self):
        if self._values['source_check'] is None:
            return None
        if self._values['source_check'] == 'yes':
            return 'enabled'
        return 'disabled'

    @property
    def fail_safe(self):
        if self._values['fail_safe'] is None:
            return None
        if self._values['fail_safe'] == 'yes':
            return 'enabled'
        return 'disabled'

class ReportableChanges(Changes):
    @property
    def tagged_interfaces(self):
        if self.interfaces is None:
            return None
        result = [str(x['name']) for x in self.interfaces if 'tagged' in x and x['tagged'] is True]
        result = sorted(result)
        return result

    @property
    def untagged_interfaces(self):
        if self.interfaces is None:
            return None
        result = [str(x['name']) for x in self.interfaces if 'untagged' in x and x['untagged'] is True]
        result = sorted(result)
        return result

    @property
    def source_check(self):
        return flatten_boolean(self._values['source_check'])

    @property
    def fail_safe(self):
        return flatten_boolean(self._values['fail_safe'])

    @property
    def sflow(self):
        return None

    @property
    def sflow_poll_interval(self):
        try:
            return self._values['sflow']['pollInterval']
        except (KeyError, TypeError):
            return None

    @property
    def sflow_sampling_rate(self):
        try:
            return self._values['sflow']['samplingRate']
        except (KeyError, TypeError):
            return None

class Difference(object):
    def __init__(self, want, have=None):
        self.want = want
        self.have = have

    def compare(self, param):
        try:
            result = getattr(self, param)
            return result
        except AttributeError:
            return self.__default(param)

    def __default(self, param):
        attr1 = getattr(self.want, param)
        try:
            attr2 = getattr(self.have, param)
            if attr1 != attr2:
                return attr1
        except AttributeError:
            return attr1

    @property
    def interfaces(self):
        if self.want.interfaces is None:
            return None
        if self.have.interfaces is None and self.want.interfaces in ['', 'none']:
            return None
        if self.have.interfaces is not None and self.want.interfaces in ['', 'none']:
            return []
        if self.have.interfaces is None:
            return dict(
                interfaces=self.want.interfaces
            )
        return compare_complex_list(self.want.interfaces, self.have.interfaces)

    @property
    def untagged_interfaces(self):
        result = self.cmp_interfaces(self.want.untagged_interfaces, self.have.untagged_interfaces, False)
        return result

    @property
    def tagged_interfaces(self):
        result = self.cmp_interfaces(self.want.tagged_interfaces, self.have.tagged_interfaces, True)
        return result

    def cmp_interfaces(self, want, have, tagged):
        result = []
        if tagged:
            tag_key = 'tagged'
        else:
            tag_key = 'untagged'
        if want is None:
            return None
        elif want == '' and have is None:
            return None
        elif want == '' and len(have) > 0:
            pass
        elif not have:
            result = dict(
                interfaces=[{'name': x, tag_key: True} for x in want]
            )
        elif set(want) != set(have):
            result = dict(
                interfaces=[{'name': x, tag_key: True} for x in want]
            )
        else:
            return None
        return result

    @property
    def sflow(self):
        result = {}
        s = self.sflow_poll_interval
        if s:
            result.update(s)
        s = self.sflow_sampling_rate
        if s:
            result.update(s)
        if result:
            return dict(
                sflow=result
            )

    @property
    def sflow_poll_interval(self):
        if self.want.sflow_poll_interval is None:
            return None
        if self.want.sflow_poll_interval != self.have.sflow_poll_interval:
            return dict(
                pollInterval=self.want.sflow_poll_interval
            )

    @property
    def sflow_sampling_rate(self):
        if self.want.sflow_sampling_rate is None:
            return None
        if self.want.sflow_sampling_rate != self.have.sflow_sampling_rate:
            return dict(
                samplingRate=self.want.sflow_sampling_rate
            )

class ModuleManager(object):
    def __init__(self, *args, **kwargs):
        self.module = kwargs.get('module', None)
        self.client = F5RestClient(**self.module.params)
        self.want = ModuleParameters(params=self.module.params)
        self.have = ApiParameters()
        self.changes = UsableChanges()

    def _update_changed_options(self):
        diff = Difference(self.want, self.have)
        updatables = Parameters.updatables
        changed = dict()
        for k in updatables:
            change = diff.compare(k)
            if change is None:
                continue
            else:
                if isinstance(change, dict):
                    changed.update(change)
                else:
                    changed[k] = change
        if changed:
            self.changes = UsableChanges(params=changed)
            return True
        return False

    def exec_module(self):
        changed = False
        result = dict()
        state = self.want.state

        if state == "present":
            changed = self.present()
        elif state == "absent":
            changed = self.absent()
        reportable = ReportableChanges(params=self.changes.to_return())
        changes = reportable.to_return()
        result.update(**changes)
        result.update(dict(changed=changed))
        self._announce_deprecations(result)
        return result

    def _announce_deprecations(self, result):
        warnings = result.pop('__warnings', [])
        for warning in warnings:
            self.module.deprecate(
                msg=warning['msg'],
                version=warning['version']
            )

    def present(self):
        if self.exists():
            return self.update()
        else:
            return self.create()

    def absent(self):
        if self.exists():
            return self.remove()
        return False

    def should_update(self):
        result = self._update_changed_options()
        if result:
            return True
        return False

    def update(self):
        self.have = self.read_current_from_device()
        if not self.should_update():
            return False
        if self.module.check_mode:
            return True
        self.update_on_device()
        return True

    def remove(self):
        if self.module.check_mode:
            return True
        self.remove_from_device()
        if self.exists():
            raise F5ModuleError("Failed to delete the VLAN")
        return True

    def create(self):
        self.have = ApiParameters()
        if self.want.mtu is None:
            self.want.update({'mtu': 1500})
        self._update_changed_options()
        if self.module.check_mode:
            return True
        self.create_on_device()
        return True

    def create_on_device(self):
        params = self.changes.api_params()
        params['name'] = self.want.name
        params['partition'] = self.want.partition
        uri = "https://{0}:{1}/mgmt/tm/net/vlan".format(
            self.client.provider['server'],
            self.client.provider['server_port']
        )
        resp = self.client.api.post(uri, json=params)
        try:
            response = resp.json()
        except ValueError as ex:
            raise F5ModuleError(str(ex))

        if 'code' in response and response['code'] in [400, 403]:
            if 'message' in response:
                raise F5ModuleError(response['message'])
            else:
                raise F5ModuleError(resp.content)
        return response['selfLink']

    def update_on_device(self):
        params = self.changes.api_params()
        uri = "https://{0}:{1}/mgmt/tm/net/vlan/{2}".format(
            self.client.provider['server'],
            self.client.provider['server_port'],
            transform_name(self.want.partition, self.want.name)
        )
        resp = self.client.api.patch(uri, json=params)
        try:
            response = resp.json()
        except ValueError as ex:
            raise F5ModuleError(str(ex))

        if 'code' in response and response['code'] == 400:
            if 'message' in response:
                raise F5ModuleError(response['message'])
            else:
                raise F5ModuleError(resp.content)

    def exists(self):
        uri = "https://{0}:{1}/mgmt/tm/net/vlan/{2}".format(
            self.client.provider['server'],
            self.client.provider['server_port'],
            transform_name(self.want.partition, self.want.name)
        )
        resp = self.client.api.get(uri)
        try:
            response = resp.json()
        except ValueError:
            return False
        if resp.status == 404 or 'code' in response and response['code'] == 404:
            return False
        return True

    def remove_from_device(self):
        uri = "https://{0}:{1}/mgmt/tm/net/vlan/{2}".format(
            self.client.provider['server'],
            self.client.provider['server_port'],
            transform_name(self.want.partition, self.want.name)
        )
        resp = self.client.api.delete(uri)
        if resp.status == 200:
            return True

    def read_current_from_device(self):
        uri = "https://{0}:{1}/mgmt/tm/net/vlan/{2}".format(
            self.client.provider['server'],
            self.client.provider['server_port'],
            transform_name(self.want.partition, self.want.name)
        )
        query = '?expandSubcollections=true'
        resp = self.client.api.get(uri + query)
        try:
            response = resp.json()
        except ValueError as ex:
            raise F5ModuleError(str(ex))

        if 'code' in response and response['code'] == 400:
            if 'message' in response:
                raise F5ModuleError(response['message'])
            else:
                raise F5ModuleError(resp.content)
        return ApiParameters(params=response)

class ArgumentSpec(object):
    def __init__(self):
        self.supports_check_mode = True
        argument_spec = dict(
            name=dict(
                required=True,
            ),
            tagged_interfaces=dict(
                type='list',
                aliases=['tagged_interface']
            ),
            untagged_interfaces=dict(
                type='list',
                aliases=['untagged_interface']
            ),
            interfaces=dict(
                type='list',
                options=dict(
                    interface=dict(),
                    tagging=dict(
                        choice=['tagged', 'untagged']
                    )
                )
            ),
            description=dict(),
            tag=dict(
                type='int'
            ),
            mtu=dict(type='int'),
            cmp_hash=dict(
                choices=[
                    'default',
                    'destination-address', 'dest', 'dst-ip', 'destination', 'dst',
                    'source-address', 'src', 'src-ip', 'source'
                ]
            ),
            dag_tunnel=dict(
                choices=['inner', 'outer']
            ),
            dag_round_robin=dict(type='bool'),
            source_check=dict(type='bool'),
            fail_safe=dict(type='bool'),
            fail_safe_timeout=dict(type='int'),
            fail_safe_action=dict(
                choices=['reboot', 'restart-all', 'failover']
            ),
            sflow_poll_interval=dict(type='int'),
            sflow_sampling_rate=dict(type='int'),
            state=dict(
                default='present',
                choices=['present', 'absent']
            ),
            partition=dict(
                default='Common',
                fallback=(env_fallback, ['F5_PARTITION'])
            ),
        )
        self.argument_spec = {}
        self.argument_spec.update(f5_argument_spec)
        self.argument_spec.update(argument_spec)
        self.mutually_exclusive = [
            ['tagged_interfaces', 'untagged_interfaces', 'interfaces'],
        ]

def main():
    spec = ArgumentSpec()

    module = AnsibleModule(
        argument_spec=spec.argument_spec,
        supports_check_mode=spec.supports_check_mode,
        mutually_exclusive=spec.mutually_exclusive
    )
    try:
        mm = ModuleManager(module=module)
        results = mm.exec_module()
        module.exit_json(**results)
    except F5ModuleError as ex:
        module.fail_json(msg=str(ex))

if __name__ == '__main__':
    main()


text:
ACCEPTED

#### According to
International Plant Names Index

#### Published in
Mitt. Bot. Staatssamml. München 10:126. 1971

#### Original name
null

### Remarks
null